From 57419a7f96aeadc6ba70400602d2386fabf95eef Mon Sep 17 00:00:00 2001 From: florian Date: Fri, 26 Feb 2021 16:16:37 +0000 Subject: [PATCH] Import dhcpleased(8) - a dhcp daemon to acquire IPv4 address leases from servers. dhcpleased(8) follows the well known three process design of all our privsep daemons. It uses pledge(2) and unveil(2) to restrict access further. In particular the "engine" process, responsible for parsing of untrusted data, is pledge'd "stdio". It cannot access the outside world nor the filesystem at all. Like slaacd(8) for IPv6 it will be always running and acquire addresses for all interface with the autoconf4 flag set. The flag can be set by "ifconfig $if inet autoconf" or by adding "inet autoconf" to /etc/hostname.if. An existing "dhcp" line should be removed. Various iterations tested by deraadt@ The hardest part, finding a name, was handled by jmatthew@ & otto@ "get to it :)" deraadt@ --- sbin/dhcpleased/Makefile | 19 + sbin/dhcpleased/bpf.c | 187 ++++ sbin/dhcpleased/bpf.h | 45 + sbin/dhcpleased/checksum.c | 80 ++ sbin/dhcpleased/checksum.h | 44 + sbin/dhcpleased/control.c | 305 ++++++ sbin/dhcpleased/control.h | 25 + sbin/dhcpleased/dhcpleased.8 | 105 ++ sbin/dhcpleased/dhcpleased.c | 979 +++++++++++++++++++ sbin/dhcpleased/dhcpleased.h | 262 +++++ sbin/dhcpleased/engine.c | 1327 ++++++++++++++++++++++++++ sbin/dhcpleased/engine.h | 29 + sbin/dhcpleased/frontend.c | 979 +++++++++++++++++++ sbin/dhcpleased/frontend.h | 24 + sbin/dhcpleased/log.c | 199 ++++ sbin/dhcpleased/log.h | 60 ++ usr.sbin/dhcpleasectl/Makefile | 17 + usr.sbin/dhcpleasectl/dhcpleasectl.8 | 80 ++ usr.sbin/dhcpleasectl/dhcpleasectl.c | 258 +++++ usr.sbin/dhcpleasectl/parser.c | 207 ++++ usr.sbin/dhcpleasectl/parser.h | 34 + 21 files changed, 5265 insertions(+) create mode 100644 sbin/dhcpleased/Makefile create mode 100644 sbin/dhcpleased/bpf.c create mode 100644 sbin/dhcpleased/bpf.h create mode 100644 sbin/dhcpleased/checksum.c create mode 100644 sbin/dhcpleased/checksum.h create mode 100644 sbin/dhcpleased/control.c create mode 100644 sbin/dhcpleased/control.h create mode 100644 sbin/dhcpleased/dhcpleased.8 create mode 100644 sbin/dhcpleased/dhcpleased.c create mode 100644 sbin/dhcpleased/dhcpleased.h create mode 100644 sbin/dhcpleased/engine.c create mode 100644 sbin/dhcpleased/engine.h create mode 100644 sbin/dhcpleased/frontend.c create mode 100644 sbin/dhcpleased/frontend.h create mode 100644 sbin/dhcpleased/log.c create mode 100644 sbin/dhcpleased/log.h create mode 100644 usr.sbin/dhcpleasectl/Makefile create mode 100644 usr.sbin/dhcpleasectl/dhcpleasectl.8 create mode 100644 usr.sbin/dhcpleasectl/dhcpleasectl.c create mode 100644 usr.sbin/dhcpleasectl/parser.c create mode 100644 usr.sbin/dhcpleasectl/parser.h diff --git a/sbin/dhcpleased/Makefile b/sbin/dhcpleased/Makefile new file mode 100644 index 00000000000..8b18a7a7ff4 --- /dev/null +++ b/sbin/dhcpleased/Makefile @@ -0,0 +1,19 @@ +# $OpenBSD: Makefile,v 1.1 2021/02/26 16:16:37 florian Exp $ + +PROG= dhcpleased +SRCS= bpf.c checksum.c control.c dhcpleased.c engine.c frontend.c log.c + +MAN= dhcpleased.8 + +#DEBUG= -g -DDEBUG=3 -O0 + +CFLAGS+= -Wall -I${.CURDIR} +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare +YFLAGS= +LDADD+= -levent -lutil +DPADD+= ${LIBEVENT} ${LIBUTIL} + +.include diff --git a/sbin/dhcpleased/bpf.c b/sbin/dhcpleased/bpf.c new file mode 100644 index 00000000000..15ba041dd6e --- /dev/null +++ b/sbin/dhcpleased/bpf.c @@ -0,0 +1,187 @@ +/* $OpenBSD: bpf.c,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* BPF socket interface code, originally contributed by Archie Cobbs. */ + +/* + * Copyright (c) 1995, 1996, 1998, 1999 + * The Internet Software Consortium. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of The Internet Software Consortium nor the names + * of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND + * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This software has been written for the Internet Software Consortium + * by Ted Lemon in cooperation with Vixie + * Enterprises. To learn more about the Internet Software Consortium, + * see ``http://www.vix.com/isc''. To learn more about Vixie + * Enterprises, see ``http://www.vix.com''. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "bpf.h" +#include "log.h" + +#define CLIENT_PORT 68 + +/* + * Packet filter program. + */ +struct bpf_insn dhcp_bpf_filter[] = { + /* Make sure this is an IP packet. */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8), + + /* Make sure it's a UDP packet. */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6), + + /* Make sure this isn't a fragment. */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20), + BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0), + + /* Get the IP header length. */ + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14), + + /* Make sure it's to the right port. */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, CLIENT_PORT, 0, 1), + + /* If we passed all the tests, ask for the whole packet. */ + BPF_STMT(BPF_RET+BPF_K, (unsigned int)-1), + + /* Otherwise, drop it. */ + BPF_STMT(BPF_RET+BPF_K, 0), +}; + +/* + * Packet write filter program: + * 'ip and udp and src port bootpc and dst port bootps' + */ +struct bpf_insn dhcp_bpf_wfilter[] = { + BPF_STMT(BPF_LD + BPF_B + BPF_IND, 14), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (IPVERSION << 4) + 5, 0, 12), + + /* Make sure this is an IP packet. */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 10), + + /* Make sure it's a UDP packet. */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 8), + + /* Make sure this isn't a fragment. */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20), + BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 6, 0), /* patched */ + + /* Get the IP header length. */ + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14), + + /* Make sure it's from the right port. */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, 14), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 68, 0, 3), + + /* Make sure it is to the right ports. */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 67, 0, 1), + + /* If we passed all the tests, ask for the whole packet. */ + BPF_STMT(BPF_RET+BPF_K, (unsigned int)-1), + + /* Otherwise, drop it. */ + BPF_STMT(BPF_RET+BPF_K, 0), +}; + +int +get_bpf_sock(const char *name) +{ + struct bpf_program p; + struct ifreq ifr; + u_int sz; + int flag = 1, fildrop = BPF_FILDROP_CAPTURE; + int bpffd; + + if ((bpffd = open("/dev/bpf", O_RDWR | O_CLOEXEC | O_NONBLOCK)) == -1) + fatal("open(/dev/bpf)"); + + sz = BPFLEN; + /* Set the BPF buffer length. */ + if (ioctl(bpffd, BIOCSBLEN, &sz) == -1) + fatal("BIOCSBLEN"); + if (sz != BPFLEN) + fatal("BIOCSBLEN, expected %u, got %u", BPFLEN, sz); + + strlcpy(ifr.ifr_name, name, IFNAMSIZ); + if (ioctl(bpffd, BIOCSETIF, &ifr) == -1) { + log_warn("BIOCSETIF"); /* interface might have disappeared */ + close(bpffd); + return -1; + } + + /* + * Set immediate mode so that reads return as soon as a packet + * comes in, rather than waiting for the input buffer to fill + * with packets. + */ + if (ioctl(bpffd, BIOCIMMEDIATE, &flag) == -1) + fatal("BIOCIMMEDIATE"); + + if (ioctl(bpffd, BIOCSFILDROP, &fildrop) == -1) + fatal("BIOCSFILDROP"); + + /* Set up the bpf filter program structure. */ + p.bf_len = sizeof(dhcp_bpf_filter) / sizeof(struct bpf_insn); + p.bf_insns = dhcp_bpf_filter; + + if (ioctl(bpffd, BIOCSETF, &p) == -1) + fatal("BIOCSETF"); + + /* Set up the bpf write filter program structure. */ + p.bf_len = sizeof(dhcp_bpf_wfilter) / sizeof(struct bpf_insn); + p.bf_insns = dhcp_bpf_wfilter; + + if (dhcp_bpf_wfilter[7].k == 0x1fff) + dhcp_bpf_wfilter[7].k = htons(IP_MF|IP_OFFMASK); + + if (ioctl(bpffd, BIOCSETWF, &p) == -1) + fatal("BIOCSETWF"); + + if (ioctl(bpffd, BIOCLOCK, NULL) == -1) + fatal("BIOCLOCK"); + + return bpffd; +} diff --git a/sbin/dhcpleased/bpf.h b/sbin/dhcpleased/bpf.h new file mode 100644 index 00000000000..b5a60f6aae6 --- /dev/null +++ b/sbin/dhcpleased/bpf.h @@ -0,0 +1,45 @@ +/* $OpenBSD: bpf.h,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* BPF socket interface code, originally contributed by Archie Cobbs. */ + +/* + * Copyright (c) 1995, 1996, 1998, 1999 + * The Internet Software Consortium. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of The Internet Software Consortium nor the names + * of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND + * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This software has been written for the Internet Software Consortium + * by Ted Lemon in cooperation with Vixie + * Enterprises. To learn more about the Internet Software Consortium, + * see ``http://www.vix.com/isc''. To learn more about Vixie + * Enterprises, see ``http://www.vix.com''. + */ + +#define BPFLEN 2048 + +int get_bpf_sock(const char *); diff --git a/sbin/dhcpleased/checksum.c b/sbin/dhcpleased/checksum.c new file mode 100644 index 00000000000..9e26a7eecd5 --- /dev/null +++ b/sbin/dhcpleased/checksum.c @@ -0,0 +1,80 @@ +/* $OpenBSD: checksum.c,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* Packet assembly code, originally contributed by Archie Cobbs. */ + +/* + * Copyright (c) 1995, 1996, 1999 The Internet Software Consortium. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of The Internet Software Consortium nor the names + * of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND + * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This software has been written for the Internet Software Consortium + * by Ted Lemon in cooperation with Vixie + * Enterprises. To learn more about the Internet Software Consortium, + * see ``http://www.vix.com/isc''. To learn more about Vixie + * Enterprises, see ``http://www.vix.com''. + */ + +#include + +#include + +#include "checksum.h" + +uint32_t +checksum(uint8_t *buf, uint32_t nbytes, uint32_t sum) +{ + unsigned int i; + + /* Checksum all the pairs of bytes first. */ + for (i = 0; i < (nbytes & ~1U); i += 2) { + sum += (uint16_t)ntohs(*((uint16_t *)(buf + i))); + if (sum > 0xFFFF) + sum -= 0xFFFF; + } + + /* + * If there's a single byte left over, checksum it, too. + * Network byte order is big-endian, so the remaining byte is + * the high byte. + */ + if (i < nbytes) { + sum += buf[i] << 8; + if (sum > 0xFFFF) + sum -= 0xFFFF; + } + + return sum; +} + +uint32_t +wrapsum(uint32_t sum) +{ + sum = ~sum & 0xFFFF; + return htons(sum); +} diff --git a/sbin/dhcpleased/checksum.h b/sbin/dhcpleased/checksum.h new file mode 100644 index 00000000000..fe5e7cad4af --- /dev/null +++ b/sbin/dhcpleased/checksum.h @@ -0,0 +1,44 @@ +/* $OpenBSD: checksum.h,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* Packet assembly code, originally contributed by Archie Cobbs. */ + +/* + * Copyright (c) 1995, 1996, 1999 The Internet Software Consortium. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of The Internet Software Consortium nor the names + * of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND + * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This software has been written for the Internet Software Consortium + * by Ted Lemon in cooperation with Vixie + * Enterprises. To learn more about the Internet Software Consortium, + * see ``http://www.vix.com/isc''. To learn more about Vixie + * Enterprises, see ``http://www.vix.com''. + */ + +uint32_t checksum(uint8_t *, uint32_t, uint32_t); +uint32_t wrapsum(uint32_t); diff --git a/sbin/dhcpleased/control.c b/sbin/dhcpleased/control.c new file mode 100644 index 00000000000..d413cb3a028 --- /dev/null +++ b/sbin/dhcpleased/control.c @@ -0,0 +1,305 @@ +/* $OpenBSD: control.c,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "dhcpleased.h" +#include "control.h" +#include "frontend.h" + +#define CONTROL_BACKLOG 5 + +struct { + struct event ev; + struct event evt; + int fd; +} control_state = {.fd = -1}; + +struct ctl_conn { + TAILQ_ENTRY(ctl_conn) entry; + struct imsgev iev; +}; + +struct ctl_conn *control_connbyfd(int); +struct ctl_conn *control_connbypid(pid_t); +void control_close(int); + +TAILQ_HEAD(ctl_conns, ctl_conn) ctl_conns = TAILQ_HEAD_INITIALIZER(ctl_conns); + +int +control_init(char *path) +{ + struct sockaddr_un sun; + int fd; + mode_t old_umask; + + if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + 0)) == -1) { + log_warn("%s: socket", __func__); + return (-1); + } + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); + + if (unlink(path) == -1) + if (errno != ENOENT) { + log_warn("%s: unlink %s", __func__, path); + close(fd); + return (-1); + } + + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + log_warn("%s: bind: %s", __func__, path); + close(fd); + umask(old_umask); + return (-1); + } + umask(old_umask); + + if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { + log_warn("%s: chmod", __func__); + close(fd); + (void)unlink(path); + return (-1); + } + + return (fd); +} + +int +control_listen(int fd) +{ + if (control_state.fd != -1) + fatalx("%s: received unexpected controlsock", __func__); + + control_state.fd = fd; + if (listen(control_state.fd, CONTROL_BACKLOG) == -1) { + log_warn("%s: listen", __func__); + return (-1); + } + + event_set(&control_state.ev, control_state.fd, EV_READ, + control_accept, NULL); + event_add(&control_state.ev, NULL); + evtimer_set(&control_state.evt, control_accept, NULL); + + return (0); +} + +void +control_accept(int listenfd, short event, void *bula) +{ + int connfd; + socklen_t len; + struct sockaddr_un sun; + struct ctl_conn *c; + + event_add(&control_state.ev, NULL); + if ((event & EV_TIMEOUT)) + return; + + len = sizeof(sun); + if ((connfd = accept4(listenfd, (struct sockaddr *)&sun, &len, + SOCK_CLOEXEC | SOCK_NONBLOCK)) == -1) { + /* + * Pause accept if we are out of file descriptors, or + * libevent will haunt us here too. + */ + if (errno == ENFILE || errno == EMFILE) { + struct timeval evtpause = { 1, 0 }; + + event_del(&control_state.ev); + evtimer_add(&control_state.evt, &evtpause); + } else if (errno != EWOULDBLOCK && errno != EINTR && + errno != ECONNABORTED) + log_warn("%s: accept4", __func__); + return; + } + + if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) { + log_warn("%s: calloc", __func__); + close(connfd); + return; + } + + imsg_init(&c->iev.ibuf, connfd); + c->iev.handler = control_dispatch_imsg; + c->iev.events = EV_READ; + event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, + c->iev.handler, &c->iev); + event_add(&c->iev.ev, NULL); + + TAILQ_INSERT_TAIL(&ctl_conns, c, entry); +} + +struct ctl_conn * +control_connbyfd(int fd) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) { + if (c->iev.ibuf.fd == fd) + break; + } + + return (c); +} + +struct ctl_conn * +control_connbypid(pid_t pid) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) { + if (c->iev.ibuf.pid == pid) + break; + } + + return (c); +} + +void +control_close(int fd) +{ + struct ctl_conn *c; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warnx("%s: fd %d: not found", __func__, fd); + return; + } + + msgbuf_clear(&c->iev.ibuf.w); + TAILQ_REMOVE(&ctl_conns, c, entry); + + event_del(&c->iev.ev); + close(c->iev.ibuf.fd); + + /* Some file descriptors are available again. */ + if (evtimer_pending(&control_state.evt, NULL)) { + evtimer_del(&control_state.evt); + event_add(&control_state.ev, NULL); + } + + free(c); +} + +void +control_dispatch_imsg(int fd, short event, void *bula) +{ + struct ctl_conn *c; + struct imsg imsg; + ssize_t n; + int verbose; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warnx("%s: fd %d: not found", __func__, fd); + return; + } + + if (event & EV_READ) { + if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) || + n == 0) { + control_close(fd); + return; + } + } + if (event & EV_WRITE) { + if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) { + control_close(fd); + return; + } + } + + for (;;) { + if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) { + control_close(fd); + return; + } + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_CTL_LOG_VERBOSE: + if (IMSG_DATA_SIZE(imsg) != sizeof(verbose)) + break; + c->iev.ibuf.pid = imsg.hdr.pid; + /* Forward to all other processes. */ + frontend_imsg_compose_main(imsg.hdr.type, imsg.hdr.pid, + imsg.data, IMSG_DATA_SIZE(imsg)); + frontend_imsg_compose_engine(imsg.hdr.type, 0, + imsg.hdr.pid, imsg.data, IMSG_DATA_SIZE(imsg)); + + memcpy(&verbose, imsg.data, sizeof(verbose)); + log_setverbose(verbose); + break; + case IMSG_CTL_SHOW_INTERFACE_INFO: + if (IMSG_DATA_SIZE(imsg) != sizeof(uint32_t)) + break; + c->iev.ibuf.pid = imsg.hdr.pid; + frontend_imsg_compose_engine(imsg.hdr.type, 0, + imsg.hdr.pid, imsg.data, IMSG_DATA_SIZE(imsg)); + break; + case IMSG_CTL_SEND_REQUEST: + if (IMSG_DATA_SIZE(imsg) != sizeof(uint32_t)) + break; + c->iev.ibuf.pid = imsg.hdr.pid; + frontend_imsg_compose_engine(imsg.hdr.type, 0, + imsg.hdr.pid, imsg.data, IMSG_DATA_SIZE(imsg)); + break; + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + + imsg_event_add(&c->iev); +} + +int +control_imsg_relay(struct imsg *imsg) +{ + struct ctl_conn *c; + + if ((c = control_connbypid(imsg->hdr.pid)) == NULL) + return (0); + + return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid, + -1, imsg->data, IMSG_DATA_SIZE(*imsg))); +} diff --git a/sbin/dhcpleased/control.h b/sbin/dhcpleased/control.h new file mode 100644 index 00000000000..5ad6237eabf --- /dev/null +++ b/sbin/dhcpleased/control.h @@ -0,0 +1,25 @@ +/* $OpenBSD: control.h,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SMALL +int control_init(char *); +int control_listen(int); +void control_accept(int, short, void *); +void control_dispatch_imsg(int, short, void *); +int control_imsg_relay(struct imsg *); +#endif /* SMALL */ diff --git a/sbin/dhcpleased/dhcpleased.8 b/sbin/dhcpleased/dhcpleased.8 new file mode 100644 index 00000000000..fb6219a8882 --- /dev/null +++ b/sbin/dhcpleased/dhcpleased.8 @@ -0,0 +1,105 @@ +.\" $OpenBSD: dhcpleased.8,v 1.1 2021/02/26 16:16:37 florian Exp $ +.\" +.\" Copyright (c) 2021 Florian Obser +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: February 26 2021 $ +.Dt DHCPLEASED 8 +.Os +.Sh NAME +.Nm dhcpleased +.Nd a dynamic host configuration protocol daemon +.Sh SYNOPSIS +.Nm +.Op Fl dv +.Op Fl s Ar socket +.Sh DESCRIPTION +.Nm +is a dynamic host configuration protocol (DHCP) daemon for clients. +It requests IP configuration information from for example those offered by +.Xr dhcpd 8 , +on interfaces with the +.Sy AUTOCONF4 +flag set. +See +.Xr hostname.if 5 +and +.Xr ifconfig 8 +on how to enable auto configuration on an interface. +.Pp +.Nm +monitors network interface states (interface going up or down, +auto configuration enabled or disabled etc.) and sends requests +when necessary. +.Pp +A running +.Nm +can be controlled with the +.Xr dhcpleasectl 8 +utility. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl d +Do not daemonize. +If this option is specified, +.Nm +will run in the foreground and log to +.Em stderr . +.It Fl s Ar socket +Use an alternate location for the default control socket. +.It Fl v +Produce more verbose output. +Multiple +.Fl v +options increase the verbosity. +.El +.Sh FILES +.Bl -tag -width "/dev/dhcpleased.sockXX" -compact +.It Pa /dev/dhcpleased.sock +.Ux Ns -domain +socket used for communication with +.Xr dhcpleasectl 8 . +.El +.Sh SEE ALSO +.Xr hostname.if 5 , +.Xr dhcpleasectl 8 +.Xr dhcpd 8 , +.Xr ifconfig 8 , +.Sh STANDARDS +.Rs +.%A R. Droms +.%D March 1997 +.%R RFC 2131 +.%T Dynamic Host Configuration Protocol +.Re +.Pp +.Rs +.%A S. Alexander +.%A R. Droms +.%D March 1997 +.%R RFC 2132 +.%T DHCP Options and BOOTP Vendor Extensions +.Re +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 6.9 . +.Sh AUTHORS +.An -nosplit +The +.Nm +program was written by +.An Florian Obser Aq Mt florian@openbsd.org . diff --git a/sbin/dhcpleased/dhcpleased.c b/sbin/dhcpleased/dhcpleased.c new file mode 100644 index 00000000000..38a95d2a4c8 --- /dev/null +++ b/sbin/dhcpleased/dhcpleased.c @@ -0,0 +1,979 @@ +/* $OpenBSD: dhcpleased.c,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* + * Copyright (c) 2017, 2021 Florian Obser + * Copyright (c) 2005 Claudio Jeker + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bpf.h" +#include "log.h" +#include "dhcpleased.h" +#include "frontend.h" +#include "engine.h" +#include "control.h" + +enum dhcpleased_process { + PROC_MAIN, + PROC_ENGINE, + PROC_FRONTEND +}; + +__dead void usage(void); +__dead void main_shutdown(void); + +void main_sig_handler(int, short, void *); + +static pid_t start_child(enum dhcpleased_process, char *, int, int, int); + +void main_dispatch_frontend(int, short, void *); +void main_dispatch_engine(int, short, void *); +void open_bpfsock(uint32_t); +void configure_interface(struct imsg_configure_interface *); +void deconfigure_interface(struct imsg_configure_interface *); +void propose_rdns(struct imsg_propose_rdns *); +void configure_gateway(struct imsg_configure_interface *, uint8_t); +int open_lease_file(int); + +static int main_imsg_send_ipc_sockets(struct imsgbuf *, struct imsgbuf *); +int main_imsg_compose_frontend(int, int, void *, uint16_t); +int main_imsg_compose_engine(int, int, void *, uint16_t); + +static struct imsgev *iev_frontend; +static struct imsgev *iev_engine; + +pid_t frontend_pid; +pid_t engine_pid; + +int routesock, ioctl_sock, rtm_seq = 0; + +void +main_sig_handler(int sig, short event, void *arg) +{ + /* + * Normal signal handler rules don't apply because libevent + * decouples for us. + */ + + switch (sig) { + case SIGTERM: + case SIGINT: + main_shutdown(); + default: + fatalx("unexpected signal"); + } +} + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-dv] [-s socket]\n", + __progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct event ev_sigint, ev_sigterm; + int ch; + int debug = 0, engine_flag = 0, frontend_flag = 0; + int verbose = 0; + char *saved_argv0; + int pipe_main2frontend[2]; + int pipe_main2engine[2]; + int frontend_routesock, rtfilter; + int rtable_any = RTABLE_ANY; + char *csock = DHCPLEASED_SOCKET; +#ifndef SMALL + int control_fd; +#endif /* SMALL */ + + log_init(1, LOG_DAEMON); /* Log to stderr until daemonized. */ + log_setverbose(1); + + saved_argv0 = argv[0]; + if (saved_argv0 == NULL) + saved_argv0 = "dhcpleased"; + + while ((ch = getopt(argc, argv, "dEFs:v")) != -1) { + switch (ch) { + case 'd': + debug = 1; + break; + case 'E': + engine_flag = 1; + break; + case 'F': + frontend_flag = 1; + break; + case 's': + csock = optarg; + break; + case 'v': + verbose++; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + if (argc > 0 || (engine_flag && frontend_flag)) + usage(); + + if (engine_flag) + engine(debug, verbose); + else if (frontend_flag) + frontend(debug, verbose); + + /* Check for root privileges. */ + if (geteuid()) + errx(1, "need root privileges"); + + /* Check for assigned daemon user */ + if (getpwnam(DHCPLEASED_USER) == NULL) + errx(1, "unknown user %s", DHCPLEASED_USER); + + log_init(debug, LOG_DAEMON); + log_setverbose(verbose); + + if (!debug) + daemon(0, 0); + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + PF_UNSPEC, pipe_main2frontend) == -1) + fatal("main2frontend socketpair"); + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + PF_UNSPEC, pipe_main2engine) == -1) + fatal("main2engine socketpair"); + + /* Start children. */ + engine_pid = start_child(PROC_ENGINE, saved_argv0, pipe_main2engine[1], + debug, verbose); + frontend_pid = start_child(PROC_FRONTEND, saved_argv0, + pipe_main2frontend[1], debug, verbose); + + log_procinit("main"); + + if ((routesock = socket(AF_ROUTE, SOCK_RAW | SOCK_CLOEXEC | + SOCK_NONBLOCK, AF_INET)) == -1) + fatal("route socket"); + shutdown(SHUT_RD, routesock); + + event_init(); + + /* Setup signal handler. */ + signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + /* Setup pipes to children. */ + + if ((iev_frontend = malloc(sizeof(struct imsgev))) == NULL || + (iev_engine = malloc(sizeof(struct imsgev))) == NULL) + fatal(NULL); + imsg_init(&iev_frontend->ibuf, pipe_main2frontend[0]); + iev_frontend->handler = main_dispatch_frontend; + imsg_init(&iev_engine->ibuf, pipe_main2engine[0]); + iev_engine->handler = main_dispatch_engine; + + /* Setup event handlers for pipes to engine & frontend. */ + iev_frontend->events = EV_READ; + event_set(&iev_frontend->ev, iev_frontend->ibuf.fd, + iev_frontend->events, iev_frontend->handler, iev_frontend); + event_add(&iev_frontend->ev, NULL); + + iev_engine->events = EV_READ; + event_set(&iev_engine->ev, iev_engine->ibuf.fd, iev_engine->events, + iev_engine->handler, iev_engine); + event_add(&iev_engine->ev, NULL); + + if (main_imsg_send_ipc_sockets(&iev_frontend->ibuf, &iev_engine->ibuf)) + fatal("could not establish imsg links"); + + if ((ioctl_sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0)) == -1) + fatal("socket"); + + if ((frontend_routesock = socket(AF_ROUTE, SOCK_RAW | SOCK_CLOEXEC, + AF_INET)) == -1) + fatal("route socket"); + + rtfilter = ROUTE_FILTER(RTM_IFINFO) | ROUTE_FILTER(RTM_NEWADDR) | + ROUTE_FILTER(RTM_PROPOSAL); + if (setsockopt(frontend_routesock, AF_ROUTE, ROUTE_MSGFILTER, + &rtfilter, sizeof(rtfilter)) == -1) + fatal("setsockopt(ROUTE_MSGFILTER)"); + if (setsockopt(frontend_routesock, AF_ROUTE, ROUTE_TABLEFILTER, + &rtable_any, sizeof(rtable_any)) == -1) + fatal("setsockopt(ROUTE_TABLEFILTER)"); + +#ifndef SMALL + if ((control_fd = control_init(csock)) == -1) + fatalx("control socket setup failed"); +#endif /* SMALL */ + + if (unveil("/dev/bpf", "rw") == -1) + fatal("unveil"); + + if (unveil(LEASE_PATH, "rwc") == -1) + fatal("unveil"); + + if (unveil(NULL, NULL) == -1) + fatal("unveil"); +#if notyet + if (pledge("stdio inet rpath wpath sendfd wroute bpf", NULL) == -1) + fatal("pledge"); +#endif + main_imsg_compose_frontend(IMSG_ROUTESOCK, frontend_routesock, NULL, 0); + +#ifndef SMALL + main_imsg_compose_frontend(IMSG_CONTROLFD, control_fd, NULL, 0); +#endif /* SMALL */ + + main_imsg_compose_frontend(IMSG_STARTUP, -1, NULL, 0); + + event_dispatch(); + + main_shutdown(); + return (0); +} + +__dead void +main_shutdown(void) +{ + pid_t pid; + int status; + + /* Close pipes. */ + msgbuf_clear(&iev_frontend->ibuf.w); + close(iev_frontend->ibuf.fd); + msgbuf_clear(&iev_engine->ibuf.w); + close(iev_engine->ibuf.fd); + + log_debug("waiting for children to terminate"); + do { + pid = wait(&status); + if (pid == -1) { + if (errno != EINTR && errno != ECHILD) + fatal("wait"); + } else if (WIFSIGNALED(status)) + log_warnx("%s terminated; signal %d", + (pid == engine_pid) ? "engine" : + "frontend", WTERMSIG(status)); + } while (pid != -1 || (pid == -1 && errno == EINTR)); + + free(iev_frontend); + free(iev_engine); + + log_info("terminating"); + exit(0); +} + +static pid_t +start_child(enum dhcpleased_process p, char *argv0, int fd, int debug, int + verbose) +{ + char *argv[7]; + int argc = 0; + pid_t pid; + + switch (pid = fork()) { + case -1: + fatal("cannot fork"); + case 0: + break; + default: + close(fd); + return (pid); + } + + if (fd != 3) { + if (dup2(fd, 3) == -1) + fatal("cannot setup imsg fd"); + } else if (fcntl(fd, F_SETFD, 0) == -1) + fatal("cannot setup imsg fd"); + + argv[argc++] = argv0; + switch (p) { + case PROC_MAIN: + fatalx("Can not start main process"); + case PROC_ENGINE: + argv[argc++] = "-E"; + break; + case PROC_FRONTEND: + argv[argc++] = "-F"; + break; + } + if (debug) + argv[argc++] = "-d"; + if (verbose) + argv[argc++] = "-v"; + if (verbose > 1) + argv[argc++] = "-v"; + argv[argc++] = NULL; + + execvp(argv0, argv); + fatal("execvp"); +} + +void +main_dispatch_frontend(int fd, short event, void *bula) +{ + struct imsgev *iev = bula; + struct imsgbuf *ibuf; + struct imsg imsg; + struct imsg_ifinfo imsg_ifinfo; + ssize_t n; + int shut = 0; + uint32_t if_index; +#ifndef SMALL + int verbose; +#endif /* SMALL */ + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("imsg_get"); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case IMSG_OPEN_BPFSOCK: + log_debug("IMSG_OPEN_BPFSOCK"); + if (IMSG_DATA_SIZE(imsg) != sizeof(if_index)) + fatalx("%s: IMSG_OPEN_BPFSOCK wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + memcpy(&if_index, imsg.data, sizeof(if_index)); + open_bpfsock(if_index); + break; +#ifndef SMALL + case IMSG_CTL_LOG_VERBOSE: + if (IMSG_DATA_SIZE(imsg) != sizeof(verbose)) + fatalx("%s: IMSG_CTL_LOG_VERBOSE wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + memcpy(&verbose, imsg.data, sizeof(verbose)); + log_setverbose(verbose); + break; +#endif /* SMALL */ + case IMSG_UPDATE_IF: + if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_ifinfo)) + fatalx("%s: IMSG_UPDATE_IF wrong length: %lu", + __func__, IMSG_DATA_SIZE(imsg)); + memcpy(&imsg_ifinfo, imsg.data, sizeof(imsg_ifinfo)); + main_imsg_compose_engine(IMSG_UPDATE_IF, + open_lease_file(imsg_ifinfo.if_index), &imsg_ifinfo, + sizeof(imsg_ifinfo)); + break; + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +main_dispatch_engine(int fd, short event, void *bula) +{ + struct imsgev *iev = bula; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + int shut = 0; + + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("imsg_get"); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case IMSG_CONFIGURE_INTERFACE: { + struct imsg_configure_interface imsg_interface; + if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_interface)) + fatalx("%s: IMSG_CONFIGURE_INTERFACE wrong " + "length: %lu", __func__, + IMSG_DATA_SIZE(imsg)); + memcpy(&imsg_interface, imsg.data, + sizeof(imsg_interface)); + configure_interface(&imsg_interface); + break; + } + case IMSG_DECONFIGURE_INTERFACE: { + struct imsg_configure_interface imsg_interface; + if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_interface)) + fatalx("%s: IMSG_CONFIGURE_INTERFACE wrong " + "length: %lu", __func__, + IMSG_DATA_SIZE(imsg)); + memcpy(&imsg_interface, imsg.data, + sizeof(imsg_interface)); + deconfigure_interface(&imsg_interface); + main_imsg_compose_frontend(IMSG_CLOSE_UDPSOCK, -1, + &imsg_interface.if_index, + sizeof(imsg_interface.if_index)); + break; + } + case IMSG_PROPOSE_RDNS: { + struct imsg_propose_rdns rdns; + if (IMSG_DATA_SIZE(imsg) != sizeof(rdns)) + fatalx("%s: IMSG_PROPOSE_RDNS wrong " + "length: %lu", __func__, + IMSG_DATA_SIZE(imsg)); + memcpy(&rdns, imsg.data, sizeof(rdns)); + if ((2 + rdns.rdns_count * sizeof(struct in_addr)) > + sizeof(struct sockaddr_rtdns)) + fatalx("%s: rdns_count too big: %d", __func__, + rdns.rdns_count); + propose_rdns(&rdns); + break; + } + case IMSG_WITHDRAW_RDNS: { + struct imsg_propose_rdns rdns; + if (IMSG_DATA_SIZE(imsg) != sizeof(rdns)) + fatalx("%s: IMSG_PROPOSE_RDNS wrong " + "length: %lu", __func__, + IMSG_DATA_SIZE(imsg)); + memcpy(&rdns, imsg.data, sizeof(rdns)); + if (rdns.rdns_count != 0) + fatalx("%s: expected rdns_count == 0: %d", + __func__, rdns.rdns_count); + propose_rdns(&rdns); + break; + } + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler. */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +int +main_imsg_compose_frontend(int type, int fd, void *data, uint16_t datalen) +{ + if (iev_frontend) + return (imsg_compose_event(iev_frontend, type, 0, 0, fd, data, + datalen)); + else + return (-1); +} + +int +main_imsg_compose_engine(int type, int fd, void *data, uint16_t datalen) +{ + if (iev_engine) + return(imsg_compose_event(iev_engine, type, 0, 0, fd, data, + datalen)); + else + return (-1); +} + +void +imsg_event_add(struct imsgev *iev) +{ + iev->events = EV_READ; + if (iev->ibuf.w.queued) + iev->events |= EV_WRITE; + + event_del(&iev->ev); + event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev); + event_add(&iev->ev, NULL); +} + +int +imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid, + pid_t pid, int fd, void *data, uint16_t datalen) +{ + int ret; + + if ((ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data, + datalen)) != -1) + imsg_event_add(iev); + + return (ret); +} + +static int +main_imsg_send_ipc_sockets(struct imsgbuf *frontend_buf, + struct imsgbuf *engine_buf) +{ + int pipe_frontend2engine[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + PF_UNSPEC, pipe_frontend2engine) == -1) + return (-1); + + if (imsg_compose(frontend_buf, IMSG_SOCKET_IPC, 0, 0, + pipe_frontend2engine[0], NULL, 0) == -1) + return (-1); + imsg_flush(frontend_buf); + if (imsg_compose(engine_buf, IMSG_SOCKET_IPC, 0, 0, + pipe_frontend2engine[1], NULL, 0) == -1) + return (-1); + imsg_flush(engine_buf); + return (0); +} + +void +configure_interface(struct imsg_configure_interface *imsg) +{ + struct ifaliasreq ifaliasreq; + struct ifaddrs *ifap, *ifa; + struct sockaddr_in *req_sin_addr, *req_sin_mask; + int found = 0, udpsock, opt = 1, len, fd = -1; + char *if_name; + char ntop_buf[INET_ADDRSTRLEN]; + char lease_buf[LEASE_SIZE]; + char lease_file_buf[sizeof(LEASE_PATH) + + IF_NAMESIZE]; + char tmpl[] = LEASE_PATH"XXXXXXXXXX"; + + log_debug("%s", __func__); + + memset(&ifaliasreq, 0, sizeof(ifaliasreq)); + + if_name = if_indextoname(imsg->if_index, ifaliasreq.ifra_name); + if (if_name == NULL) { + log_warnx("%s: cannot find interface %d", __func__, + imsg->if_index); + return; + } + + if (getifaddrs(&ifap) != 0) + fatal("getifaddrs"); + + req_sin_addr = (struct sockaddr_in *)&ifaliasreq.ifra_addr; + req_sin_addr->sin_family = AF_INET; + req_sin_addr->sin_len = sizeof(*req_sin_addr); + + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + struct in_addr addr, mask; + + if (strcmp(if_name, ifa->ifa_name) != 0) + continue; + if (ifa->ifa_addr == NULL) + continue; + if (ifa->ifa_addr->sa_family != AF_INET) + continue; + + addr.s_addr = ((struct sockaddr_in *)ifa->ifa_addr) + ->sin_addr.s_addr; + mask.s_addr = ((struct sockaddr_in *)ifa->ifa_netmask) + ->sin_addr.s_addr; + + if (imsg->addr.s_addr == addr.s_addr) { + if (imsg->mask.s_addr == mask.s_addr) + found = 1; + else { + req_sin_addr->sin_addr.s_addr = addr.s_addr; + if (ioctl(ioctl_sock, SIOCDIFADDR, &ifaliasreq) + == -1) { + if (errno != EADDRNOTAVAIL) + log_warn("SIOCDIFADDR"); + } + } + break; + } + } + + req_sin_addr->sin_addr.s_addr = imsg->addr.s_addr; + if (!found) { + req_sin_mask = (struct sockaddr_in *)&ifaliasreq.ifra_mask; + req_sin_mask->sin_family = AF_INET; + req_sin_mask->sin_len = sizeof(*req_sin_mask); + req_sin_mask->sin_addr.s_addr = imsg->mask.s_addr; + if (ioctl(ioctl_sock, SIOCAIFADDR, &ifaliasreq) == -1) + fatal("SIOCAIFADDR"); + + /* XXX check weird shit in dhclient/kroute.c set_routes() */ + if (imsg->router.s_addr != INADDR_ANY) + configure_gateway(imsg, RTM_ADD); + } + req_sin_addr->sin_port = ntohs(CLIENT_PORT); + if ((udpsock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + log_warn("socket"); + return; + } + if (setsockopt(udpsock, SOL_SOCKET, SO_REUSEADDR, &opt, + sizeof(opt)) == -1) + log_warn("setting SO_REUSEADDR on socket"); + + if (setsockopt(udpsock, SOL_SOCKET, SO_RTABLE, &imsg->rdomain, + sizeof(imsg->rdomain)) == -1) { + /* we might race against removal of the rdomain */ + log_warn("setsockopt SO_RTABLE"); + close(udpsock); + return; + } + + if (bind(udpsock, (struct sockaddr *)req_sin_addr, + sizeof(*req_sin_addr)) == -1) { + close(udpsock); + return; + } + + shutdown(udpsock, SHUT_RD); + log_debug("%s: udpsock: %d", __func__, udpsock); + main_imsg_compose_frontend(IMSG_UDPSOCK, udpsock, + &imsg->if_index, sizeof(imsg->if_index)); + + if (inet_ntop(AF_INET, &imsg->addr, ntop_buf, sizeof(ntop_buf)) == + NULL) { + log_warn("%s: inet_ntop", __func__); + return; + } + + len = snprintf(lease_file_buf, sizeof(lease_file_buf), "%s%s", + LEASE_PATH, if_name); + if ( len == -1 || (size_t) len >= sizeof(lease_file_buf)) { + log_warnx("%s: failed to encode lease path for %s", __func__, + if_name); + return; + } + + len = snprintf(lease_buf, sizeof(lease_buf), "%s%s\n", LEASE_PREFIX, + ntop_buf); + if ( len == -1 || (size_t) len >= sizeof(lease_buf)) { + log_warnx("%s: failed to encode lease for %s", __func__, + ntop_buf); + return; + } + + if ((fd = mkstemp(tmpl)) == -1) { + log_warn("%s: mkstemp", __func__); + return; + } + + if (write(fd, lease_buf, len) < len) + goto err; + + if (fchmod(fd, 0644) == -1) + goto err; + + if (close(fd) == -1) + goto err; + fd = -1; + + if (rename(tmpl, lease_file_buf) == -1) + goto err; + return; + err: + log_warn("%s", __func__); + if (fd != -1) + close(fd); + unlink(tmpl); +} + +void +deconfigure_interface(struct imsg_configure_interface *imsg) +{ + struct ifaliasreq ifaliasreq; + struct sockaddr_in *req_sin_addr; + + log_debug("%s", __func__); + + memset(&ifaliasreq, 0, sizeof(ifaliasreq)); + + if (if_indextoname(imsg->if_index, ifaliasreq.ifra_name) == NULL) { + log_warnx("%s: cannot find interface %d", __func__, + imsg->if_index); + return; + } + + req_sin_addr = (struct sockaddr_in *)&ifaliasreq.ifra_addr; + req_sin_addr->sin_family = AF_INET; + req_sin_addr->sin_len = sizeof(*req_sin_addr); + + req_sin_addr->sin_addr.s_addr = imsg->addr.s_addr; + if (ioctl(ioctl_sock, SIOCDIFADDR, &ifaliasreq) == -1) { + if (errno != EADDRNOTAVAIL) + log_warn("SIOCDIFADDR"); + } +} + +#define ROUNDUP(a) \ + (((a) & (sizeof(long) - 1)) ? (1 + ((a) | (sizeof(long) - 1))) : (a)) +void +configure_gateway(struct imsg_configure_interface *imsg, uint8_t rtm_type) +{ + struct rt_msghdr rtm; + struct sockaddr_rtlabel rl; + struct sockaddr_in dst, gw, mask; + struct iovec iov[10]; + long pad = 0; + int iovcnt = 0, padlen; + + memset(&rtm, 0, sizeof(rtm)); + + rtm.rtm_version = RTM_VERSION; + rtm.rtm_type = rtm_type; + rtm.rtm_msglen = sizeof(rtm); + rtm.rtm_tableid = imsg->rdomain; + rtm.rtm_index = imsg->if_index; + rtm.rtm_seq = ++rtm_seq; + rtm.rtm_priority = RTP_NONE; + rtm.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK | RTA_LABEL; + rtm.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC; + + iov[iovcnt].iov_base = &rtm; + iov[iovcnt++].iov_len = sizeof(rtm); + + memset(&dst, 0, sizeof(dst)); + dst.sin_family = AF_INET; + dst.sin_len = sizeof(struct sockaddr_in); + + iov[iovcnt].iov_base = &dst; + iov[iovcnt++].iov_len = sizeof(dst); + rtm.rtm_msglen += sizeof(dst); + padlen = ROUNDUP(sizeof(dst)) - sizeof(dst); + if (padlen > 0) { + iov[iovcnt].iov_base = &pad; + iov[iovcnt++].iov_len = padlen; + rtm.rtm_msglen += padlen; + } + + memset(&gw, 0, sizeof(gw)); + memcpy(&gw.sin_addr, &imsg->router, sizeof(gw.sin_addr)); + gw.sin_family = AF_INET; + gw.sin_len = sizeof(struct sockaddr_in); + iov[iovcnt].iov_base = &gw; + iov[iovcnt++].iov_len = sizeof(gw); + rtm.rtm_msglen += sizeof(gw); + padlen = ROUNDUP(sizeof(gw)) - sizeof(gw); + if (padlen > 0) { + iov[iovcnt].iov_base = &pad; + iov[iovcnt++].iov_len = padlen; + rtm.rtm_msglen += padlen; + } + + memset(&mask, 0, sizeof(mask)); + mask.sin_family = AF_INET; + mask.sin_len = sizeof(struct sockaddr_in); + iov[iovcnt].iov_base = &mask; + iov[iovcnt++].iov_len = sizeof(mask); + rtm.rtm_msglen += sizeof(mask); + padlen = ROUNDUP(sizeof(mask)) - sizeof(mask); + if (padlen > 0) { + iov[iovcnt].iov_base = &pad; + iov[iovcnt++].iov_len = padlen; + rtm.rtm_msglen += padlen; + } + + memset(&rl, 0, sizeof(rl)); + rl.sr_len = sizeof(rl); + rl.sr_family = AF_UNSPEC; + (void)snprintf(rl.sr_label, sizeof(rl.sr_label), "%s", + DHCPLEASED_RTA_LABEL); + iov[iovcnt].iov_base = &rl; + iov[iovcnt++].iov_len = sizeof(rl); + rtm.rtm_msglen += sizeof(rl); + padlen = ROUNDUP(sizeof(rl)) - sizeof(rl); + if (padlen > 0) { + iov[iovcnt].iov_base = &pad; + iov[iovcnt++].iov_len = padlen; + rtm.rtm_msglen += padlen; + } + + if (writev(routesock, iov, iovcnt) == -1) + log_warn("failed to send route message"); +} + +#ifndef SMALL +const char* +sin_to_str(struct sockaddr_in *sin) +{ + static char hbuf[NI_MAXHOST]; + int error; + + error = getnameinfo((struct sockaddr *)sin, sin->sin_len, hbuf, + sizeof(hbuf), NULL, 0, NI_NUMERICHOST | NI_NUMERICSERV); + if (error) { + log_warnx("%s", gai_strerror(error)); + strlcpy(hbuf, "unknown", sizeof(hbuf)); + } + return hbuf; +} +#endif /* SMALL */ + +void +open_bpfsock(uint32_t if_index) +{ + int bpfsock; + char ifname[IF_NAMESIZE]; + + log_debug("%s: %d", __func__, if_index); + + if (if_indextoname(if_index, ifname) == 0) { + log_warnx("%s: cannot find interface %d", __func__, if_index); + return; + } + + if ((bpfsock = get_bpf_sock(ifname)) == -1) + return; + + main_imsg_compose_frontend(IMSG_BPFSOCK, bpfsock, &if_index, + sizeof(if_index)); +} + +void +propose_rdns(struct imsg_propose_rdns *rdns) +{ + struct rt_msghdr rtm; + struct sockaddr_rtdns rtdns; + struct iovec iov[3]; + long pad = 0; + int iovcnt = 0, padlen; + + memset(&rtm, 0, sizeof(rtm)); + + rtm.rtm_version = RTM_VERSION; + rtm.rtm_type = RTM_PROPOSAL; + rtm.rtm_msglen = sizeof(rtm); + rtm.rtm_tableid = rdns->rdomain; + rtm.rtm_index = rdns->if_index; + rtm.rtm_seq = ++rtm_seq; + rtm.rtm_priority = RTP_PROPOSAL_DHCLIENT; + rtm.rtm_addrs = RTA_DNS; + rtm.rtm_flags = RTF_UP; + + iov[iovcnt].iov_base = &rtm; + iov[iovcnt++].iov_len = sizeof(rtm); + + memset(&rtdns, 0, sizeof(rtdns)); + rtdns.sr_family = AF_INET; + rtdns.sr_len = 2 + rdns->rdns_count * sizeof(struct in_addr); + memcpy(rtdns.sr_dns, rdns->rdns, sizeof(rtdns.sr_dns)); + + iov[iovcnt].iov_base = &rtdns; + iov[iovcnt++].iov_len = sizeof(rtdns); + rtm.rtm_msglen += sizeof(rtdns); + padlen = ROUNDUP(sizeof(rtdns)) - sizeof(rtdns); + if (padlen > 0) { + iov[iovcnt].iov_base = &pad; + iov[iovcnt++].iov_len = padlen; + rtm.rtm_msglen += padlen; + } + + if (writev(routesock, iov, iovcnt) == -1) + log_warn("failed to send route message"); +} + +int +open_lease_file(int if_index) +{ + int len; + char if_name[IF_NAMESIZE]; + char lease_file_buf[sizeof(LEASE_PATH) + IF_NAMESIZE]; + + if (if_indextoname(if_index, if_name) == 0) { + log_warnx("%s: cannot find interface %d", __func__, if_index); + return -1; + } + + len = snprintf(lease_file_buf, sizeof(lease_file_buf), "%s%s", + LEASE_PATH, if_name); + if ( len == -1 || (size_t) len >= sizeof(lease_file_buf)) { + log_warnx("%s: failed to encode lease path for %s", __func__, + if_name); + return -1; + } + + return (open(lease_file_buf, O_RDONLY)); +} diff --git a/sbin/dhcpleased/dhcpleased.h b/sbin/dhcpleased/dhcpleased.h new file mode 100644 index 00000000000..ff179681a5b --- /dev/null +++ b/sbin/dhcpleased/dhcpleased.h @@ -0,0 +1,262 @@ +/* $OpenBSD: dhcpleased.h,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* + * Copyright (c) 2017, 2021 Florian Obser + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define DHCPLEASED_SOCKET "/dev/dhcpleased.sock" +#define DHCPLEASED_USER "_dhcp" +#define DHCPLEASED_RTA_LABEL "dhcpleased" +#define SERVER_PORT 67 +#define CLIENT_PORT 68 +#define LEASE_PATH "/var/db/dhcpleased/" +#define LEASE_PREFIX "version: 1\nip: " +#define LEASE_SIZE sizeof(LEASE_PREFIX"xxx.xxx.xxx.xxx\n") +/* MAXDNAME from arpa/namesr.h */ +#define DHCPLEASED_MAX_DNSSL 1025 +#define MAX_RDNS_COUNT 8 /* max nameserver in a RTM_PROPOSAL */ + +#define DHCP_COOKIE {99, 130, 83, 99} + +/* Possible values for hardware type (htype) field. */ +#define HTYPE_ETHER 1 +#define HTYPE_IPSEC_TUNNEL 31 + +/* DHCP op code */ +#define DHCP_BOOTREQUEST 1 +#define DHCP_BOOTREPLY 2 + +/* DHCP Option codes: */ +#define DHO_PAD 0 +#define DHO_SUBNET_MASK 1 +#define DHO_TIME_OFFSET 2 +#define DHO_ROUTERS 3 +#define DHO_TIME_SERVERS 4 +#define DHO_NAME_SERVERS 5 +#define DHO_DOMAIN_NAME_SERVERS 6 +#define DHO_LOG_SERVERS 7 +#define DHO_COOKIE_SERVERS 8 +#define DHO_LPR_SERVERS 9 +#define DHO_IMPRESS_SERVERS 10 +#define DHO_RESOURCE_LOCATION_SERVERS 11 +#define DHO_HOST_NAME 12 +#define DHO_BOOT_SIZE 13 +#define DHO_MERIT_DUMP 14 +#define DHO_DOMAIN_NAME 15 +#define DHO_SWAP_SERVER 16 +#define DHO_ROOT_PATH 17 +#define DHO_EXTENSIONS_PATH 18 +#define DHO_IP_FORWARDING 19 +#define DHO_NON_LOCAL_SOURCE_ROUTING 20 +#define DHO_POLICY_FILTER 21 +#define DHO_MAX_DGRAM_REASSEMBLY 22 +#define DHO_DEFAULT_IP_TTL 23 +#define DHO_PATH_MTU_AGING_TIMEOUT 24 +#define DHO_PATH_MTU_PLATEAU_TABLE 25 +#define DHO_INTERFACE_MTU 26 +#define DHO_ALL_SUBNETS_LOCAL 27 +#define DHO_BROADCAST_ADDRESS 28 +#define DHO_PERFORM_MASK_DISCOVERY 29 +#define DHO_MASK_SUPPLIER 30 +#define DHO_ROUTER_DISCOVERY 31 +#define DHO_ROUTER_SOLICITATION_ADDRESS 32 +#define DHO_STATIC_ROUTES 33 +#define DHO_TRAILER_ENCAPSULATION 34 +#define DHO_ARP_CACHE_TIMEOUT 35 +#define DHO_IEEE802_3_ENCAPSULATION 36 +#define DHO_DEFAULT_TCP_TTL 37 +#define DHO_TCP_KEEPALIVE_INTERVAL 38 +#define DHO_TCP_KEEPALIVE_GARBAGE 39 +#define DHO_NIS_DOMAIN 40 +#define DHO_NIS_SERVERS 41 +#define DHO_NTP_SERVERS 42 +#define DHO_VENDOR_ENCAPSULATED_OPTIONS 43 +#define DHO_NETBIOS_NAME_SERVERS 44 +#define DHO_NETBIOS_DD_SERVER 45 +#define DHO_NETBIOS_NODE_TYPE 46 +#define DHO_NETBIOS_SCOPE 47 +#define DHO_FONT_SERVERS 48 +#define DHO_X_DISPLAY_MANAGER 49 +#define DHO_DHCP_REQUESTED_ADDRESS 50 +#define DHO_DHCP_LEASE_TIME 51 +#define DHO_DHCP_OPTION_OVERLOAD 52 +#define DHO_DHCP_MESSAGE_TYPE 53 +#define DHO_DHCP_SERVER_IDENTIFIER 54 +#define DHO_DHCP_PARAMETER_REQUEST_LIST 55 +#define DHO_DHCP_MESSAGE 56 +#define DHO_DHCP_MAX_MESSAGE_SIZE 57 +#define DHO_DHCP_RENEWAL_TIME 58 +#define DHO_DHCP_REBINDING_TIME 59 +#define DHO_DHCP_CLASS_IDENTIFIER 60 +#define DHO_DHCP_CLIENT_IDENTIFIER 61 +#define DHO_NISPLUS_DOMAIN 64 +#define DHO_NISPLUS_SERVERS 65 +#define DHO_TFTP_SERVER 66 +#define DHO_BOOTFILE_NAME 67 +#define DHO_MOBILE_IP_HOME_AGENT 68 +#define DHO_SMTP_SERVER 69 +#define DHO_POP_SERVER 70 +#define DHO_NNTP_SERVER 71 +#define DHO_WWW_SERVER 72 +#define DHO_FINGER_SERVER 73 +#define DHO_IRC_SERVER 74 +#define DHO_STREETTALK_SERVER 75 +#define DHO_STREETTALK_DIRECTORY_ASSISTANCE_SERVER 76 +#define DHO_DHCP_USER_CLASS_ID 77 +#define DHO_RELAY_AGENT_INFORMATION 82 +#define DHO_NDS_SERVERS 85 +#define DHO_NDS_TREE_NAME 86 +#define DHO_NDS_CONTEXT 87 +#define DHO_DOMAIN_SEARCH 119 +#define DHO_CLASSLESS_STATIC_ROUTES 121 +#define DHO_TFTP_CONFIG_FILE 144 +#define DHO_VOIP_CONFIGURATION_SERVER 150 +#define DHO_CLASSLESS_MS_STATIC_ROUTES 249 +#define DHO_AUTOPROXY_SCRIPT 252 +#define DHO_END 255 +#define DHO_COUNT 256 /* # of DHCP options */ + +/* DHCP message types. */ +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPNAK 6 +#define DHCPRELEASE 7 +#define DHCPINFORM 8 + +#define IMSG_DATA_SIZE(imsg) ((imsg).hdr.len - IMSG_HEADER_SIZE) +#define DHCP_SNAME_LEN 64 +#define DHCP_FILE_LEN 128 + +struct dhcp_hdr { + uint8_t op; /* Message opcode/type */ + uint8_t htype; /* Hardware addr type (see net/if_types.h) */ + uint8_t hlen; /* Hardware addr length */ + uint8_t hops; /* Number of relay agent hops from client */ + uint32_t xid; /* Transaction ID */ + uint16_t secs; /* Seconds since client started looking */ + uint16_t flags; /* Flag bits */ + struct in_addr ciaddr; /* Client IP address (if already in use) */ + struct in_addr yiaddr; /* Client IP address */ + struct in_addr siaddr; /* IP address of next server to talk to */ + struct in_addr giaddr; /* DHCP relay agent IP address */ + uint8_t chaddr[16]; /* Client hardware address */ + char sname[DHCP_SNAME_LEN]; /* Server name */ + char file[DHCP_FILE_LEN]; /* Boot filename */ +}; + +struct imsgev { + struct imsgbuf ibuf; + void (*handler)(int, short, void *); + struct event ev; + short events; +}; + +enum imsg_type { + IMSG_NONE, +#ifndef SMALL + IMSG_CTL_LOG_VERBOSE, + IMSG_CTL_SHOW_INTERFACE_INFO, + IMSG_CTL_SEND_REQUEST, + IMSG_CTL_END, +#endif /* SMALL */ + IMSG_SEND_DISCOVER, + IMSG_SEND_REQUEST, + IMSG_SOCKET_IPC, + IMSG_OPEN_BPFSOCK, + IMSG_BPFSOCK, + IMSG_UDPSOCK, + IMSG_CLOSE_UDPSOCK, + IMSG_ROUTESOCK, + IMSG_CONTROLFD, + IMSG_STARTUP, + IMSG_UPDATE_IF, + IMSG_REMOVE_IF, + IMSG_DHCP, + IMSG_CONFIGURE_INTERFACE, + IMSG_DECONFIGURE_INTERFACE, + IMSG_PROPOSE_RDNS, + IMSG_WITHDRAW_RDNS, + IMSG_REPROPOSE_RDNS, +}; + +#ifndef SMALL +struct ctl_engine_info { + uint32_t if_index; + int running; + int link_state; + char state[sizeof("IF_INIT_REBOOT")]; + struct timespec request_time; + struct in_addr server_identifier; + struct in_addr dhcp_server; /* for unicast */ + struct in_addr requested_ip; + struct in_addr mask; + struct in_addr router; + struct in_addr nameservers[MAX_RDNS_COUNT]; + uint32_t lease_time; + uint32_t renewal_time; + uint32_t rebinding_time; +}; + +#endif /* SMALL */ + +struct imsg_ifinfo { + uint32_t if_index; + int rdomain; + int running; + int link_state; + struct ether_addr hw_address; +}; + +struct imsg_propose_rdns { + uint32_t if_index; + int rdomain; + int rdns_count; + struct in_addr rdns[MAX_RDNS_COUNT]; +}; + +struct imsg_dhcp { + uint32_t if_index; + ssize_t len; + uint8_t packet[1500]; +}; + + +struct imsg_req_discover { + uint32_t if_index; + uint32_t xid; +}; + +struct imsg_req_request { + uint32_t if_index; + uint32_t xid; + struct in_addr requested_ip; + struct in_addr server_identifier; + struct in_addr dhcp_server; +}; + +/* dhcpleased.c */ +void imsg_event_add(struct imsgev *); +int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t, + int, void *, uint16_t); +#ifndef SMALL +const char *sin_to_str(struct sockaddr_in *); +#else +#define sin_to_str(x...) "" +#endif /* SMALL */ diff --git a/sbin/dhcpleased/engine.c b/sbin/dhcpleased/engine.c new file mode 100644 index 00000000000..dace5fc85bd --- /dev/null +++ b/sbin/dhcpleased/engine.c @@ -0,0 +1,1327 @@ +/* $OpenBSD: engine.c,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* + * Copyright (c) 2017, 2021 Florian Obser + * Copyright (c) 2004, 2005 Claudio Jeker + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "checksum.h" +#include "log.h" +#include "dhcpleased.h" +#include "engine.h" + +#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) + +enum if_state { + IF_DOWN, + IF_INIT, + /* IF_SELECTING, */ + IF_REQUESTING, + IF_BOUND, + IF_RENEWING, + IF_REBINDING, + /* IF_INIT_REBOOT, */ + IF_REBOOTING, +}; + +const char* if_state_name[] = { + "Down", + "Init", + /* "Selecting", */ + "Requesting", + "Bound", + "Renewing", + "Rebinding", + /* "Init-Reboot", */ + "Rebooting", +}; + +struct dhcpleased_iface { + LIST_ENTRY(dhcpleased_iface) entries; + enum if_state state; + struct event timer; + struct timeval timo; + uint32_t if_index; + int rdomain; + int running; + struct ether_addr hw_address; + int link_state; + uint32_t cur_mtu; + uint32_t xid; + struct timespec request_time; + struct in_addr server_identifier; + struct in_addr dhcp_server; /* for unicast */ + struct in_addr requested_ip; + struct in_addr mask; + struct in_addr router; + struct in_addr nameservers[MAX_RDNS_COUNT]; + uint32_t lease_time; + uint32_t renewal_time; + uint32_t rebinding_time; +}; + +LIST_HEAD(, dhcpleased_iface) dhcpleased_interfaces; + +__dead void engine_shutdown(void); +void engine_sig_handler(int sig, short, void *); +void engine_dispatch_frontend(int, short, void *); +void engine_dispatch_main(int, short, void *); +#ifndef SMALL +void send_interface_info(struct dhcpleased_iface *, pid_t); +void engine_showinfo_ctl(struct imsg *, uint32_t); +#endif /* SMALL */ +void engine_update_iface(struct imsg_ifinfo *, int); +struct dhcpleased_iface *get_dhcpleased_iface_by_id(uint32_t); +void remove_dhcpleased_iface(uint32_t); +void parse_dhcp(struct dhcpleased_iface *, + struct imsg_dhcp *); +void state_transition(struct dhcpleased_iface *, enum + if_state); +void iface_timeout(int, short, void *); +void request_dhcp_discover(struct dhcpleased_iface *); +void request_dhcp_request(struct dhcpleased_iface *); +void send_configure_interface(struct dhcpleased_iface *); +void send_rdns_proposal(struct dhcpleased_iface *); +void send_deconfigure_interface(struct dhcpleased_iface *); +void send_rdns_withdraw(struct dhcpleased_iface *); +void parse_lease(int, struct dhcpleased_iface *); +int engine_imsg_compose_main(int, pid_t, void *, uint16_t); + +static struct imsgev *iev_frontend; +static struct imsgev *iev_main; +int64_t proposal_id; + +void +engine_sig_handler(int sig, short event, void *arg) +{ + /* + * Normal signal handler rules don't apply because libevent + * decouples for us. + */ + + switch (sig) { + case SIGINT: + case SIGTERM: + engine_shutdown(); + default: + fatalx("unexpected signal"); + } +} + +void +engine(int debug, int verbose) +{ + struct event ev_sigint, ev_sigterm; + struct passwd *pw; + + log_init(debug, LOG_DAEMON); + log_setverbose(verbose); + + if ((pw = getpwnam(DHCPLEASED_USER)) == NULL) + fatal("getpwnam"); + + if (chroot(pw->pw_dir) == -1) + fatal("chroot"); + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + + setproctitle("%s", "engine"); + log_procinit("engine"); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("can't drop privileges"); + + if (pledge("stdio recvfd", NULL) == -1) + fatal("pledge"); + + event_init(); + + /* Setup signal handler(s). */ + signal_set(&ev_sigint, SIGINT, engine_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, engine_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + /* Setup pipe and event handler to the main process. */ + if ((iev_main = malloc(sizeof(struct imsgev))) == NULL) + fatal(NULL); + + imsg_init(&iev_main->ibuf, 3); + iev_main->handler = engine_dispatch_main; + + /* Setup event handlers. */ + iev_main->events = EV_READ; + event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events, + iev_main->handler, iev_main); + event_add(&iev_main->ev, NULL); + + LIST_INIT(&dhcpleased_interfaces); + + event_dispatch(); + + engine_shutdown(); +} + +__dead void +engine_shutdown(void) +{ + /* Close pipes. */ + msgbuf_clear(&iev_frontend->ibuf.w); + close(iev_frontend->ibuf.fd); + msgbuf_clear(&iev_main->ibuf.w); + close(iev_main->ibuf.fd); + + free(iev_frontend); + free(iev_main); + + log_info("engine exiting"); + exit(0); +} + +int +engine_imsg_compose_frontend(int type, pid_t pid, void *data, + uint16_t datalen) +{ + return (imsg_compose_event(iev_frontend, type, 0, pid, -1, + data, datalen)); +} + +int +engine_imsg_compose_main(int type, pid_t pid, void *data, + uint16_t datalen) +{ + return (imsg_compose_event(iev_main, type, 0, pid, -1, + data, datalen)); +} + +void +engine_dispatch_frontend(int fd, short event, void *bula) +{ + struct imsgev *iev = bula; + struct imsgbuf *ibuf = &iev->ibuf; + struct imsg imsg; + struct dhcpleased_iface *iface; + ssize_t n; + int shut = 0; +#ifndef SMALL + int verbose; +#endif /* SMALL */ + uint32_t if_index; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("%s: imsg_get error", __func__); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { +#ifndef SMALL + case IMSG_CTL_LOG_VERBOSE: + if (IMSG_DATA_SIZE(imsg) != sizeof(verbose)) + fatalx("%s: IMSG_CTL_LOG_VERBOSE wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + memcpy(&verbose, imsg.data, sizeof(verbose)); + log_setverbose(verbose); + break; + case IMSG_CTL_SHOW_INTERFACE_INFO: + if (IMSG_DATA_SIZE(imsg) != sizeof(if_index)) + fatalx("%s: IMSG_CTL_SHOW_INTERFACE_INFO wrong " + "length: %lu", __func__, + IMSG_DATA_SIZE(imsg)); + memcpy(&if_index, imsg.data, sizeof(if_index)); + engine_showinfo_ctl(&imsg, if_index); + break; + case IMSG_CTL_SEND_REQUEST: + if (IMSG_DATA_SIZE(imsg) != sizeof(if_index)) + fatalx("%s: IMSG_CTL_SEND_DISCOVER wrong " + "length: %lu", __func__, + IMSG_DATA_SIZE(imsg)); + memcpy(&if_index, imsg.data, sizeof(if_index)); + iface = get_dhcpleased_iface_by_id(if_index); + if (iface != NULL) { + switch (iface->state) { + case IF_DOWN: + break; + case IF_INIT: + case IF_REQUESTING: + case IF_RENEWING: + case IF_REBINDING: + case IF_REBOOTING: + state_transition(iface, iface->state); + break; + case IF_BOUND: + state_transition(iface, IF_RENEWING); + break; + } + } + break; +#endif /* SMALL */ + case IMSG_REMOVE_IF: + if (IMSG_DATA_SIZE(imsg) != sizeof(if_index)) + fatalx("%s: IMSG_REMOVE_IF wrong length: %lu", + __func__, IMSG_DATA_SIZE(imsg)); + memcpy(&if_index, imsg.data, sizeof(if_index)); + remove_dhcpleased_iface(if_index); + break; + case IMSG_DHCP: { + struct imsg_dhcp imsg_dhcp; + if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_dhcp)) + fatalx("%s: IMSG_DHCP wrong length: %lu", + __func__, IMSG_DATA_SIZE(imsg)); + memcpy(&imsg_dhcp, imsg.data, sizeof(imsg_dhcp)); + iface = get_dhcpleased_iface_by_id(imsg_dhcp.if_index); + if (iface != NULL) + parse_dhcp(iface, &imsg_dhcp); + break; + } + case IMSG_REPROPOSE_RDNS: + LIST_FOREACH (iface, &dhcpleased_interfaces, entries) + send_rdns_proposal(iface); + break; + default: + log_debug("%s: unexpected imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler. */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +engine_dispatch_main(int fd, short event, void *bula) +{ + struct imsg imsg; + struct imsgev *iev = bula; + struct imsgbuf *ibuf = &iev->ibuf; + struct imsg_ifinfo imsg_ifinfo; + ssize_t n; + int shut = 0; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("%s: imsg_get error", __func__); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case IMSG_SOCKET_IPC: + /* + * Setup pipe and event handler to the frontend + * process. + */ + if (iev_frontend) + fatalx("%s: received unexpected imsg fd " + "to engine", __func__); + + if ((fd = imsg.fd) == -1) + fatalx("%s: expected to receive imsg fd to " + "engine but didn't receive any", __func__); + + iev_frontend = malloc(sizeof(struct imsgev)); + if (iev_frontend == NULL) + fatal(NULL); + + imsg_init(&iev_frontend->ibuf, fd); + iev_frontend->handler = engine_dispatch_frontend; + iev_frontend->events = EV_READ; + + event_set(&iev_frontend->ev, iev_frontend->ibuf.fd, + iev_frontend->events, iev_frontend->handler, + iev_frontend); + event_add(&iev_frontend->ev, NULL); + break; + case IMSG_UPDATE_IF: + if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_ifinfo)) + fatalx("%s: IMSG_UPDATE_IF wrong length: %lu", + __func__, IMSG_DATA_SIZE(imsg)); + memcpy(&imsg_ifinfo, imsg.data, sizeof(imsg_ifinfo)); + engine_update_iface(&imsg_ifinfo, imsg.fd); + break; + default: + log_debug("%s: unexpected imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler. */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +#ifndef SMALL +void +send_interface_info(struct dhcpleased_iface *iface, pid_t pid) +{ + struct ctl_engine_info cei; + + memset(&cei, 0, sizeof(cei)); + cei.if_index = iface->if_index; + cei.running = iface->running; + cei.link_state = iface->link_state; + strlcpy(cei.state, if_state_name[iface->state], sizeof(cei.state)); + memcpy(&cei.request_time, &iface->request_time, + sizeof(cei.request_time)); + cei.server_identifier.s_addr = iface->server_identifier.s_addr; + cei.dhcp_server.s_addr = iface->dhcp_server.s_addr; + cei.requested_ip.s_addr = iface->requested_ip.s_addr; + cei.mask.s_addr = iface->mask.s_addr; + cei.router.s_addr = iface->router.s_addr; + memcpy(cei.nameservers, iface->nameservers, sizeof(cei.nameservers)); + cei.lease_time = iface->lease_time; + cei.renewal_time = iface->renewal_time; + cei.rebinding_time = iface->rebinding_time; + engine_imsg_compose_frontend(IMSG_CTL_SHOW_INTERFACE_INFO, pid, &cei, + sizeof(cei)); +} + +void +engine_showinfo_ctl(struct imsg *imsg, uint32_t if_index) +{ + struct dhcpleased_iface *iface; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_INTERFACE_INFO: + if (if_index == 0) { + LIST_FOREACH (iface, &dhcpleased_interfaces, entries) + send_interface_info(iface, imsg->hdr.pid); + } else { + if ((iface = get_dhcpleased_iface_by_id(if_index)) != + NULL) + send_interface_info(iface, imsg->hdr.pid); + } + engine_imsg_compose_frontend(IMSG_CTL_END, imsg->hdr.pid, NULL, + 0); + break; + default: + log_debug("%s: error handling imsg", __func__); + break; + } +} +#endif /* SMALL */ +void +engine_update_iface(struct imsg_ifinfo *imsg_ifinfo, int fd) +{ + struct dhcpleased_iface *iface; + int need_refresh = 0; + + iface = get_dhcpleased_iface_by_id(imsg_ifinfo->if_index); + + if (iface == NULL) { + if ((iface = calloc(1, sizeof(*iface))) == NULL) + fatal("calloc"); + iface->state = IF_DOWN; + iface->timo.tv_usec = arc4random_uniform(1000000); + evtimer_set(&iface->timer, iface_timeout, iface); + iface->if_index = imsg_ifinfo->if_index; + iface->rdomain = imsg_ifinfo->rdomain; + iface->running = imsg_ifinfo->running; + iface->link_state = imsg_ifinfo->link_state; + iface->requested_ip.s_addr = INADDR_ANY; + memcpy(&iface->hw_address, &imsg_ifinfo->hw_address, + sizeof(struct ether_addr)); + LIST_INSERT_HEAD(&dhcpleased_interfaces, iface, entries); + + if (fd != -1) + parse_lease(fd, iface); + need_refresh = 1; + } else { + if (fd != -1) + close(fd); + + if (memcmp(&iface->hw_address, &imsg_ifinfo->hw_address, + sizeof(struct ether_addr)) != 0) { + memcpy(&iface->hw_address, &imsg_ifinfo->hw_address, + sizeof(struct ether_addr)); + need_refresh = 1; + } + if (imsg_ifinfo->rdomain != iface->rdomain) { + iface->rdomain = imsg_ifinfo->rdomain; + need_refresh = 1; + } + if (imsg_ifinfo->running != iface->running) { + iface->running = imsg_ifinfo->running; + need_refresh = 1; + } + + if (imsg_ifinfo->link_state != iface->link_state) { + iface->link_state = imsg_ifinfo->link_state; + need_refresh = 1; + } + } + + if (!need_refresh) + return; + + if (iface->running && LINK_STATE_IS_UP(iface->link_state)) { + if (iface->requested_ip.s_addr == INADDR_ANY) + state_transition(iface, IF_INIT); + else + state_transition(iface, IF_REBOOTING); + } else + state_transition(iface, IF_DOWN); +} +struct dhcpleased_iface* +get_dhcpleased_iface_by_id(uint32_t if_index) +{ + struct dhcpleased_iface *iface; + LIST_FOREACH (iface, &dhcpleased_interfaces, entries) { + if (iface->if_index == if_index) + return (iface); + } + + return (NULL); +} + +void +remove_dhcpleased_iface(uint32_t if_index) +{ + struct dhcpleased_iface *iface; + + iface = get_dhcpleased_iface_by_id(if_index); + + if (iface == NULL) + return; + + send_deconfigure_interface(iface); + send_rdns_withdraw(iface); + LIST_REMOVE(iface, entries); + evtimer_del(&iface->timer); + free(iface); +} + +void +parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp) +{ + static uint8_t cookie[] = DHCP_COOKIE; + static struct ether_addr bcast_mac; + struct ether_header *eh; + struct ether_addr ether_src, ether_dst; + struct ip *ip; + struct udphdr *udp; + struct dhcp_hdr *dhcp_hdr; + struct in_addr server_identifier, subnet_mask, router; + struct in_addr nameservers[MAX_RDNS_COUNT]; + size_t rem, i; + uint32_t sum, usum, lease_time = 0, renewal_time = 0; + uint32_t rebinding_time = 0; + uint8_t *p, dho = DHO_PAD, dho_len; + uint8_t dhcp_message_type = 0; + char from[sizeof("xx:xx:xx:xx:xx:xx")]; + char to[sizeof("xx:xx:xx:xx:xx:xx")]; + char hbuf_src[INET_ADDRSTRLEN]; + char hbuf_dst[INET_ADDRSTRLEN]; + char hbuf[INET_ADDRSTRLEN]; + char vis_buf[4 * 255 + 1]; + + if (bcast_mac.ether_addr_octet[0] == 0) + memset(bcast_mac.ether_addr_octet, 0xff, ETHER_ADDR_LEN); + + memset(hbuf_src, 0, sizeof(hbuf_src)); + memset(hbuf_dst, 0, sizeof(hbuf_dst)); + + p = dhcp->packet; + rem = dhcp->len; + + if (rem < sizeof(*eh)) { + log_warnx("%s: message too short", __func__); + return; + } + eh = (struct ether_header *)p; + memcpy(ether_src.ether_addr_octet, eh->ether_shost, + sizeof(ether_src.ether_addr_octet)); + strlcpy(from, ether_ntoa(ðer_src), sizeof(from)); + memcpy(ether_dst.ether_addr_octet, eh->ether_dhost, + sizeof(ether_dst.ether_addr_octet)); + strlcpy(to, ether_ntoa(ðer_dst), sizeof(to)); + p += sizeof(*eh); + rem -= sizeof(*eh); + + if (memcmp(ðer_dst, &iface->hw_address, sizeof(ether_dst)) != 0 && + memcmp(ðer_dst, &bcast_mac, sizeof(ether_dst)) != 0) + return ; /* silently ignore packet not for us */ + + if (rem < sizeof(*ip)) + goto too_short; + + log_debug("%s, from: %s, to: %s", __func__, from, to); + + ip = (struct ip *)p; + + if (rem < (size_t)ip->ip_hl << 2) + goto too_short; + + if (wrapsum(checksum((uint8_t *)ip, ip->ip_hl << 2, 0)) != 0) { + log_warnx("%s: bad IP checksum", __func__); + return; + } + if (rem < ntohs(ip->ip_len)) + goto too_short; + + p += ip->ip_hl << 2; + rem -= ip->ip_hl << 2; + + if (inet_ntop(AF_INET, &ip->ip_src, hbuf_src, sizeof(hbuf_src)) == NULL) + hbuf_src[0] = '\0'; + if (inet_ntop(AF_INET, &ip->ip_dst, hbuf_dst, sizeof(hbuf_dst)) == NULL) + hbuf_src[0] = '\0'; + + if (rem < sizeof(*udp)) + goto too_short; + + udp = (struct udphdr *)p; + if (rem < ntohs(udp->uh_ulen)) + goto too_short; + + if (rem > ntohs(udp->uh_ulen)) { + log_debug("%s: accepting packet with %lu bytes of data after " + "udp payload", __func__, rem - ntohs(udp->uh_ulen)); + rem = ntohs(udp->uh_ulen); + } + + p += sizeof(*udp); + rem -= sizeof(*udp); + + usum = udp->uh_sum; + udp->uh_sum = 0; + + sum = wrapsum(checksum((uint8_t *)udp, sizeof(*udp), checksum(p, rem, + checksum((uint8_t *)&ip->ip_src, 2 * sizeof(ip->ip_src), + IPPROTO_UDP + ntohs(udp->uh_ulen))))); + + if (usum != 0 && usum != sum) { + log_warnx("%s: bad UDP checksum", __func__); + return; + } + + log_debug("%s: %s:%d -> %s:%d", __func__, + hbuf_src, ntohs(udp->uh_sport), + hbuf_dst, ntohs(udp->uh_dport)); + + if (ntohs(udp->uh_sport) != SERVER_PORT || + ntohs(udp->uh_dport) != CLIENT_PORT) { + log_warnx("%s: invalid ports used %s:%d -> %s:%d", __func__, + hbuf_src, ntohs(udp->uh_sport), + hbuf_dst, ntohs(udp->uh_dport)); + return; + } + if (rem < sizeof(*dhcp_hdr)) + goto too_short; + + dhcp_hdr = (struct dhcp_hdr *)p; + p += sizeof(*dhcp_hdr); + rem -= sizeof(*dhcp_hdr); + + dhcp_hdr->sname[DHCP_SNAME_LEN -1 ] = '\0'; /* ensure it's a string */ + dhcp_hdr->file[DHCP_FILE_LEN -1 ] = '\0'; /* ensure it's a string */ + log_debug("dhcp_hdr op: %s (%d)", dhcp_hdr->op == DHCP_BOOTREQUEST ? + "Boot Request" : dhcp_hdr->op == DHCP_BOOTREPLY ? "Boot Reply" : + "Unknown", dhcp_hdr->op); + log_debug("dhcp_hdr htype: %s (%d)", dhcp_hdr->htype == 1 ? "Ethernet": + "Unknown", dhcp_hdr->htype); + log_debug("dhcp_hdr hlen: %d", dhcp_hdr->hlen); + log_debug("dhcp_hdr hops: %d", dhcp_hdr->hops); + log_debug("dhcp_hdr xid: 0x%x", dhcp_hdr->xid); + log_debug("dhcp_hdr secs: %u", dhcp_hdr->secs); + log_debug("dhcp_hdr flags: 0x%x", dhcp_hdr->flags); + log_debug("dhcp_hdr ciaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->ciaddr, + hbuf, sizeof(hbuf))); + log_debug("dhcp_hdr yiaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->yiaddr, + hbuf, sizeof(hbuf))); + log_debug("dhcp_hdr siaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->siaddr, + hbuf, sizeof(hbuf))); + log_debug("dhcp_hdr giaddr: %s", inet_ntop(AF_INET, &dhcp_hdr->giaddr, + hbuf, sizeof(hbuf))); + log_debug("dhcp_hdr chaddr: %02x:%02x:%02x:%02x:%02x:%02x " + "(%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x)", + dhcp_hdr->chaddr[0], dhcp_hdr->chaddr[1], dhcp_hdr->chaddr[2], + dhcp_hdr->chaddr[3], dhcp_hdr->chaddr[4], dhcp_hdr->chaddr[5], + dhcp_hdr->chaddr[6], dhcp_hdr->chaddr[7], dhcp_hdr->chaddr[8], + dhcp_hdr->chaddr[9], dhcp_hdr->chaddr[10], dhcp_hdr->chaddr[11], + dhcp_hdr->chaddr[12], dhcp_hdr->chaddr[13], dhcp_hdr->chaddr[14], + dhcp_hdr->chaddr[15]); + /* ignore sname and file, if we ever print it use strvis(3) */ + + if (dhcp_hdr->op != DHCP_BOOTREPLY) { + log_warnx("%s: ignorning non-reply packet", __func__); + return; + } + + if (dhcp_hdr->xid != iface->xid) + return; /* silently ignore wrong xid */ + + if (rem < sizeof(cookie)) + goto too_short; + + if (memcmp(p, cookie, sizeof(cookie)) != 0) { + log_warnx("%s: no dhcp cookie in packet from %s", __func__, + from); + return; + } + p += sizeof(cookie); + rem -= sizeof(cookie); + + memset(&server_identifier, 0, sizeof(server_identifier)); + memset(&subnet_mask, 0, sizeof(subnet_mask)); + memset(&router, 0, sizeof(router)); + memset(&nameservers, 0, sizeof(nameservers)); + + while (rem > 0 && dho != DHO_END) { + dho = *p; + p += 1; + rem -= 1; + /* only DHO_END and DHO_PAD are 1 byte long without length */ + if (dho == DHO_PAD || dho == DHO_END) + dho_len = 0; + else { + if (rem == 0) + goto too_short; /* missing option length */ + dho_len = *p; + p += 1; + rem -= 1; + if (rem < dho_len) + goto too_short; + } + + switch(dho) { + case DHO_PAD: + log_debug("DHO_PAD"); + break; + case DHO_END: + log_debug("DHO_END"); + break; + case DHO_DHCP_MESSAGE_TYPE: + if (dho_len != 1) + goto wrong_length; + dhcp_message_type = *p; + switch (dhcp_message_type) { + case DHCPDISCOVER: + log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPDISCOVER"); + break; + case DHCPOFFER: + log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPOFFER"); + break; + case DHCPREQUEST: + log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPREQUEST"); + break; + case DHCPDECLINE: + log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPDECLINE"); + break; + case DHCPACK: + log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPACK"); + break; + case DHCPNAK: + log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPNAK"); + break; + case DHCPRELEASE: + log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPRELEASE"); + break; + case DHCPINFORM: + log_debug("DHO_DHCP_MESSAGE_TYPE: DHCPINFORM"); + break; + default: + log_debug("DHO_DHCP_MESSAGE_TYPE: Unknown"); + break; + } + p += dho_len; + rem -= dho_len; + break; + case DHO_DHCP_SERVER_IDENTIFIER: + if (dho_len != sizeof(server_identifier)) + goto wrong_length; + memcpy(&server_identifier, p, + sizeof(server_identifier)); + log_debug("DHO_DHCP_SERVER_IDENTIFIER: %s", + inet_ntop(AF_INET, &server_identifier, + hbuf, sizeof(hbuf))); + p += dho_len; + rem -= dho_len; + break; + case DHO_DHCP_LEASE_TIME: + if (dho_len != sizeof(lease_time)) + goto wrong_length; + memcpy(&lease_time, p, sizeof(lease_time)); + lease_time = ntohl(lease_time); + log_debug("DHO_DHCP_LEASE_TIME %us", lease_time); + p += dho_len; + rem -= dho_len; + break; + case DHO_SUBNET_MASK: + if (dho_len != sizeof(subnet_mask)) + goto wrong_length; + memcpy(&subnet_mask, p, sizeof(subnet_mask)); + log_debug("DHO_SUBNET_MASK: %s", inet_ntop(AF_INET, + &subnet_mask, hbuf, sizeof(hbuf))); + p += dho_len; + rem -= dho_len; + break; + case DHO_ROUTERS: + if (dho_len < sizeof(router)) + goto wrong_length; + if (dho_len % sizeof(router) != 0) + goto wrong_length; + /* we only use one router */ + memcpy(&router, p, sizeof(router)); + log_debug("DHO_ROUTER: %s (1/%lu)", inet_ntop(AF_INET, + &router, hbuf, sizeof(hbuf)), dho_len / + sizeof(router)); + p += dho_len; + rem -= dho_len; + break; + case DHO_DOMAIN_NAME_SERVERS: + if (dho_len < sizeof(nameservers[0])) + goto wrong_length; + if (dho_len % sizeof(nameservers[0]) != 0) + goto wrong_length; + /* we limit ourself to 8 nameservers for proposals */ + memcpy(&nameservers, p, MINIMUM(sizeof(nameservers), + dho_len)); + for (i = 0; i < MINIMUM(sizeof(nameservers), dho_len / + sizeof(nameservers[0])); + i++) { + log_debug("DHO_DOMAIN_NAME_SERVERS: %s " + "(%lu/%lu)", inet_ntop(AF_INET, + &nameservers[i], hbuf, sizeof(hbuf)), i + 1, + dho_len / sizeof(nameservers[0])); + } + p += dho_len; + rem -= dho_len; + break; + case DHO_DOMAIN_NAME: + if ( dho_len < 1) + goto wrong_length; + strvisx(vis_buf, p, dho_len, VIS_SAFE); + log_debug("DHO_DOMAIN_NAME: %s", vis_buf); + p += dho_len; + rem -= dho_len; + break; + case DHO_DHCP_RENEWAL_TIME: + if (dho_len != sizeof(renewal_time)) + goto wrong_length; + memcpy(&renewal_time, p, sizeof(renewal_time)); + renewal_time = ntohl(renewal_time); + log_debug("DHO_DHCP_RENEWAL_TIME %us", renewal_time); + p += dho_len; + rem -= dho_len; + break; + case DHO_DHCP_REBINDING_TIME: + if (dho_len != sizeof(rebinding_time)) + goto wrong_length; + memcpy(&rebinding_time, p, sizeof(rebinding_time)); + rebinding_time = ntohl(rebinding_time); + log_debug("DHO_DHCP_REBINDING_TIME %us", + rebinding_time); + p += dho_len; + rem -= dho_len; + break; + case DHO_DHCP_CLIENT_IDENTIFIER: + /* the server is supposed to echo this back to us */ + if (dho_len != 1 + sizeof(iface->hw_address)) + goto wrong_length; + if (*p != HTYPE_ETHER) { + log_warn("DHO_DHCP_CLIENT_IDENTIFIER: wrong " + "type"); + return; + } + if (memcmp(p + 1, &iface->hw_address, + sizeof(iface->hw_address)) != 0) { + log_warn("wrong DHO_DHCP_CLIENT_IDENTIFIER"); + return; + } + p += dho_len; + rem -= dho_len; + break; + default: + log_debug("DHO_%u, len: %u", dho, dho_len); + p += dho_len; + rem -= dho_len; + } + + } + while(rem != 0) { + if (*p != DHO_PAD) + break; + p++; + rem--; + } + if (rem != 0) + log_warnx("%s: %lu bytes garbage data from %s", __func__, rem, + from); + + switch (dhcp_message_type) { + case DHCPOFFER: + if (iface->state != IF_INIT) { + log_debug("ignoring unexpected DHCPOFFER"); + return; + } + if (server_identifier.s_addr == INADDR_ANY && + dhcp_hdr->yiaddr.s_addr == INADDR_ANY) { + log_warnx("%s: did not receive server identifier or " + "offered IP address", __func__); + return; + } + iface->server_identifier.s_addr = server_identifier.s_addr; + iface->requested_ip.s_addr = dhcp_hdr->yiaddr.s_addr; + state_transition(iface, IF_REQUESTING); + break; + case DHCPACK: + switch (iface->state) { + case IF_REQUESTING: + case IF_RENEWING: + case IF_REBINDING: + case IF_REBOOTING: + break; + default: + log_debug("ignoring unexpected DHCPACK"); + return; + } + if (server_identifier.s_addr == INADDR_ANY && + dhcp_hdr->yiaddr.s_addr == INADDR_ANY) { + log_warnx("%s: did not receive server identifier or " + "offered IP address", __func__); + return; + } + if (lease_time == 0) { + log_warnx("%s no lease time from %s", __func__, from); + return; + } + if (subnet_mask.s_addr == INADDR_ANY) { + log_warnx("%s: no subnetmask received from %s", + __func__, from); + return; + } + + /* RFC 2131 4.4.5 */ + if(renewal_time == 0) + renewal_time = lease_time / 2; + if (rebinding_time == 0) + rebinding_time = (lease_time * 7) / 8; + + if (renewal_time >= rebinding_time) { + log_warnx("%s: renewal_time >= rebinding_time " + "(%u >= %u) from %s", __func__, renewal_time, + rebinding_time, from); + return; + } + if (rebinding_time >= lease_time) { + log_warnx("%s: rebinding_time >= lease_time" + "(%u >= %u) from %s", __func__, rebinding_time, + lease_time, from); + return; + } + clock_gettime(CLOCK_MONOTONIC, &iface->request_time); + iface->server_identifier.s_addr = server_identifier.s_addr; + iface->requested_ip.s_addr = dhcp_hdr->yiaddr.s_addr; + iface->mask.s_addr = subnet_mask.s_addr; + iface->router.s_addr = router.s_addr; + iface->lease_time = lease_time; + iface->renewal_time = renewal_time; + iface->rebinding_time = rebinding_time; + memcpy(iface->nameservers, nameservers, + sizeof(iface->nameservers)); + state_transition(iface, IF_BOUND); + break; + case DHCPNAK: + switch (iface->state) { + case IF_REQUESTING: + case IF_RENEWING: + case IF_REBINDING: + case IF_REBOOTING: + break; + default: + log_debug("ignoring unexpected DHCPNAK"); + return; + } + + /* we have been told that our IP is inapropriate, delete now */ + send_deconfigure_interface(iface); + send_rdns_withdraw(iface); + iface->server_identifier.s_addr = INADDR_ANY; + iface->requested_ip.s_addr = INADDR_ANY; + memset(iface->nameservers, 0, sizeof(iface->nameservers)); + state_transition(iface, IF_INIT); + break; + default: + log_warnx("%s: unimplemented message type %d", __func__, + dhcp_message_type); + break; + } + return; + too_short: + log_warnx("%s: message from %s too short", __func__, from); + return; + wrong_length: + log_warnx("%s: received option %d with wrong length: %d", __func__, + dho, dho_len); + return; +} + +/* XXX check valid transitions */ +void +state_transition(struct dhcpleased_iface *iface, enum if_state new_state) +{ + enum if_state old_state = iface->state; + struct timespec now, res; + + iface->state = new_state; + switch (new_state) { + case IF_DOWN: + if (iface->requested_ip.s_addr == INADDR_ANY) { + /* nothing to do until iface comes up */ + iface->timo.tv_sec = -1; + break; + } + if (old_state == IF_DOWN) { + send_deconfigure_interface(iface); + iface->server_identifier.s_addr = INADDR_ANY; + iface->dhcp_server.s_addr = INADDR_ANY; + iface->requested_ip.s_addr = INADDR_ANY; + iface->mask.s_addr = INADDR_ANY; + iface->router.s_addr = INADDR_ANY; + + /* nothing more to do until iface comes back */ + iface->timo.tv_sec = -1; + } else { + send_rdns_withdraw(iface); + memset(iface->nameservers, 0, + sizeof(iface->nameservers)); + clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, &iface->request_time, &res); + iface->timo.tv_sec = iface->lease_time - res.tv_sec; + if (iface->timo.tv_sec < 0) + iface->timo.tv_sec = 0; /* deconfigure now */ + } + break; + case IF_INIT: + if (old_state == IF_REBINDING) { + /* + * XXX move to function, also is this the only + * transition that needs delete? + */ + /* lease expired, delete IP */ + send_deconfigure_interface(iface); + send_rdns_withdraw(iface); + iface->requested_ip.s_addr = INADDR_ANY; + memset(iface->nameservers, 0, + sizeof(iface->nameservers)); + } + if (old_state == IF_INIT) { + iface->timo.tv_sec *= 2; + if (iface->timo.tv_sec > 64) + iface->timo.tv_sec = 64; + } else + iface->timo.tv_sec = 1; + request_dhcp_discover(iface); + break; + case IF_REBOOTING: + if (old_state == IF_REBOOTING) { + iface->timo.tv_sec *= 2; + if (iface->timo.tv_sec > 64) + iface->timo.tv_sec = 64; + } else { + /* make sure we send broadcast */ + iface->dhcp_server.s_addr = INADDR_ANY; + iface->timo.tv_sec = 1; + } + request_dhcp_request(iface); + break; + case IF_REQUESTING: + if (old_state == IF_REQUESTING) { + iface->timo.tv_sec *= 2; + if (iface->timo.tv_sec > 64) + iface->timo.tv_sec = 64; + } else + iface->timo.tv_sec = 1; + request_dhcp_request(iface); + break; + case IF_BOUND: + iface->timo.tv_sec = iface->renewal_time; + if (old_state == IF_REQUESTING || old_state == IF_REBOOTING) { + send_configure_interface(iface); + send_rdns_proposal(iface); + } + break; + case IF_RENEWING: + if (old_state == IF_BOUND) { + iface->dhcp_server.s_addr = + iface->server_identifier.s_addr; + iface->server_identifier.s_addr = INADDR_ANY; + + iface->timo.tv_sec = (iface->rebinding_time - + iface->renewal_time) / 2; /* RFC 2131 4.4.5 */ + } else + iface->timo.tv_sec /= 2; + + if (iface->timo.tv_sec < 60) + iface->timo.tv_sec = 60; + request_dhcp_request(iface); + break; + case IF_REBINDING: + if (old_state == IF_RENEWING) { + iface->dhcp_server.s_addr = INADDR_ANY; + iface->timo.tv_sec = (iface->lease_time - + iface->renewal_time) / 2; /* RFC 2131 4.4.5 */ + } else + iface->timo.tv_sec /= 2; + request_dhcp_request(iface); + break; + default: + fatal("%s: unhandled state: %s", __func__, + if_state_name[new_state]); + } + log_debug("%s %s -> %s, timo: %lld", __func__, if_state_name[old_state], + if_state_name[new_state], iface->timo.tv_sec); + if (iface->timo.tv_sec == -1) { + if (evtimer_pending(&iface->timer, NULL)) + evtimer_del(&iface->timer); + } else + evtimer_add(&iface->timer, &iface->timo); +} + +void +iface_timeout(int fd, short events, void *arg) +{ + struct dhcpleased_iface *iface = (struct dhcpleased_iface *)arg; + struct timespec now, res; + + log_debug("%s[%d]: %s", __func__, iface->if_index, + if_state_name[iface->state]); + + switch (iface->state) { + case IF_DOWN: + state_transition(iface, IF_DOWN); + break; + case IF_INIT: + state_transition(iface, IF_INIT); + break; + case IF_REBOOTING: + if (iface->timo.tv_sec >= 64) + state_transition(iface, IF_INIT); + else + state_transition(iface, IF_REBOOTING); + break; + case IF_REQUESTING: + if (iface->timo.tv_sec >= 64) + state_transition(iface, IF_INIT); + else + state_transition(iface, IF_REQUESTING); + break; + case IF_BOUND: + state_transition(iface, IF_RENEWING); + break; + case IF_RENEWING: + clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, &iface->request_time, &res); + log_debug("%s: res.tv_sec: %lld, rebinding_time: %u", __func__, + res.tv_sec, iface->rebinding_time); + if (res.tv_sec > iface->rebinding_time) + state_transition(iface, IF_REBINDING); + else + state_transition(iface, IF_RENEWING); + break; + case IF_REBINDING: + clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, &iface->request_time, &res); + log_debug("%s: res.tv_sec: %lld, lease_time: %u", __func__, + res.tv_sec, iface->lease_time); + if (res.tv_sec > iface->lease_time) + state_transition(iface, IF_INIT); + else + state_transition(iface, IF_REBINDING); + break; + default: + break; + } +} + +void +request_dhcp_discover(struct dhcpleased_iface *iface) +{ + struct imsg_req_discover imsg_req_discover; + + imsg_req_discover.if_index = iface->if_index; + imsg_req_discover.xid = iface->xid = arc4random(); + engine_imsg_compose_frontend(IMSG_SEND_DISCOVER, 0, &imsg_req_discover, + sizeof(imsg_req_discover)); +} + +void +request_dhcp_request(struct dhcpleased_iface *iface) +{ + struct imsg_req_request imsg_req_request; + + iface->xid = arc4random(); + imsg_req_request.if_index = iface->if_index; + imsg_req_request.xid = iface->xid; + imsg_req_request.server_identifier.s_addr = + iface->server_identifier.s_addr; + imsg_req_request.requested_ip.s_addr = iface->requested_ip.s_addr; + imsg_req_request.dhcp_server.s_addr = iface->dhcp_server.s_addr; + engine_imsg_compose_frontend(IMSG_SEND_REQUEST, 0, &imsg_req_request, + sizeof(imsg_req_request)); +} + +void +send_configure_interface(struct dhcpleased_iface *iface) +{ + struct imsg_configure_interface imsg; + + imsg.if_index = iface->if_index; + imsg.rdomain = iface->rdomain; + imsg.addr.s_addr = iface->requested_ip.s_addr; + imsg.mask.s_addr = iface->mask.s_addr; + imsg.router.s_addr = iface->router.s_addr; + engine_imsg_compose_main(IMSG_CONFIGURE_INTERFACE, 0, &imsg, + sizeof(imsg)); +} + +void +send_deconfigure_interface(struct dhcpleased_iface *iface) +{ + struct imsg_configure_interface imsg; + + imsg.if_index = iface->if_index; + imsg.rdomain = iface->rdomain; + imsg.addr.s_addr = iface->requested_ip.s_addr; + imsg.mask.s_addr = iface->mask.s_addr; + imsg.router.s_addr = iface->router.s_addr; + engine_imsg_compose_main(IMSG_DECONFIGURE_INTERFACE, 0, &imsg, + sizeof(imsg)); +} + +void +send_rdns_proposal(struct dhcpleased_iface *iface) +{ + struct imsg_propose_rdns imsg; + + memset(&imsg, 0, sizeof(imsg)); + + imsg.if_index = iface->if_index; + imsg.rdomain = iface->rdomain; + for (imsg.rdns_count = 0; imsg.rdns_count < MAX_RDNS_COUNT && + iface->nameservers[imsg.rdns_count].s_addr != INADDR_ANY; + imsg.rdns_count++) + ; + memcpy(imsg.rdns, iface->nameservers, sizeof(imsg.rdns)); + engine_imsg_compose_main(IMSG_PROPOSE_RDNS, 0, &imsg, sizeof(imsg)); +} + +void +send_rdns_withdraw(struct dhcpleased_iface *iface) +{ + struct imsg_propose_rdns imsg; + + memset(&imsg, 0, sizeof(imsg)); + + imsg.if_index = iface->if_index; + imsg.rdomain = iface->rdomain; + engine_imsg_compose_main(IMSG_WITHDRAW_RDNS, 0, &imsg, sizeof(imsg)); +} + +void +parse_lease(int fd, struct dhcpleased_iface *iface) +{ + ssize_t len; + char lease_buf[LEASE_SIZE], *p, *p1; + + memset(lease_buf, 0, sizeof(lease_buf)); + + len = read(fd, lease_buf, sizeof(lease_buf) - 1); + + if (len <= 0) + goto out; + + if ((p = strstr(lease_buf, LEASE_PREFIX)) == NULL) + goto out; + + p += sizeof(LEASE_PREFIX) - 1; + if ((p1 = strchr(p, '\n')) == NULL) + goto out; + *p1 = '\0'; + + if (inet_pton(AF_INET, p, &iface->requested_ip) != 1) + iface->requested_ip.s_addr = INADDR_ANY; + out: + close(fd); +} diff --git a/sbin/dhcpleased/engine.h b/sbin/dhcpleased/engine.h new file mode 100644 index 00000000000..317825f2a71 --- /dev/null +++ b/sbin/dhcpleased/engine.h @@ -0,0 +1,29 @@ +/* $OpenBSD: engine.h,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* + * Copyright (c) 2021 Florian Obser + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +struct imsg_configure_interface { + uint32_t if_index; + struct in_addr addr; + struct in_addr mask; + struct in_addr router; + int rdomain; + +}; + +void engine(int, int); +int engine_imsg_compose_frontend(int, pid_t, void *, uint16_t); diff --git a/sbin/dhcpleased/frontend.c b/sbin/dhcpleased/frontend.c new file mode 100644 index 00000000000..7f964e0231d --- /dev/null +++ b/sbin/dhcpleased/frontend.c @@ -0,0 +1,979 @@ +/* $OpenBSD: frontend.c,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* + * Copyright (c) 2017, 2021 Florian Obser + * Copyright (c) 2005 Claudio Jeker + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bpf.h" +#include "log.h" +#include "dhcpleased.h" +#include "frontend.h" +#include "control.h" +#include "checksum.h" + +#define ROUTE_SOCKET_BUF_SIZE 16384 + +struct bpf_ev { + struct event ev; + uint8_t buf[BPFLEN]; +}; + +struct iface { + LIST_ENTRY(iface) entries; + struct bpf_ev bpfev; + struct ether_addr hw_address; + uint32_t if_index; + int rdomain; + int send_discover; + uint32_t xid; + struct in_addr requested_ip; + struct in_addr server_identifier; + struct in_addr dhcp_server; + int udpsock; +}; + +__dead void frontend_shutdown(void); +void frontend_sig_handler(int, short, void *); +void update_iface(uint32_t, char*); +void frontend_startup(void); +void route_receive(int, short, void *); +void handle_route_message(struct rt_msghdr *, struct sockaddr **); +void get_rtaddrs(int, struct sockaddr *, struct sockaddr **); +void bpf_receive(int, short, void *); +int get_flags(char *); +int get_xflags(char *); +int get_ifrdomain(char *); +struct iface *get_iface_by_id(uint32_t); +void remove_iface(uint32_t); +void set_bpfsock(int, uint32_t); +ssize_t build_packet(uint8_t, uint32_t, struct ether_addr *, struct + in_addr *, struct in_addr *); +void send_discover(struct iface *); +void send_request(struct iface *); +void bpf_send_packet(struct iface *, uint8_t *, ssize_t); +void udp_send_packet(struct iface *, uint8_t *, ssize_t); + +LIST_HEAD(, iface) interfaces; +static struct imsgev *iev_main; +static struct imsgev *iev_engine; +struct event ev_route; +int ioctlsock; + +uint8_t dhcp_packet[1500]; + +void +frontend_sig_handler(int sig, short event, void *bula) +{ + /* + * Normal signal handler rules don't apply because libevent + * decouples for us. + */ + + switch (sig) { + case SIGINT: + case SIGTERM: + frontend_shutdown(); + default: + fatalx("unexpected signal"); + } +} + +void +frontend(int debug, int verbose) +{ + struct event ev_sigint, ev_sigterm; + struct passwd *pw; + + log_init(debug, LOG_DAEMON); + log_setverbose(verbose); + + if ((pw = getpwnam(DHCPLEASED_USER)) == NULL) + fatal("getpwnam"); + + if (chroot(pw->pw_dir) == -1) + fatal("chroot"); + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + + setproctitle("%s", "frontend"); + log_procinit("frontend"); + + if ((ioctlsock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0)) == -1) + fatal("socket"); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("can't drop privileges"); + + if (pledge("stdio unix recvfd route", NULL) == -1) + fatal("pledge"); + event_init(); + + /* Setup signal handler. */ + signal_set(&ev_sigint, SIGINT, frontend_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, frontend_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + /* Setup pipe and event handler to the parent process. */ + if ((iev_main = malloc(sizeof(struct imsgev))) == NULL) + fatal(NULL); + imsg_init(&iev_main->ibuf, 3); + iev_main->handler = frontend_dispatch_main; + iev_main->events = EV_READ; + event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events, + iev_main->handler, iev_main); + event_add(&iev_main->ev, NULL); + + LIST_INIT(&interfaces); + event_dispatch(); + + frontend_shutdown(); +} + +__dead void +frontend_shutdown(void) +{ + /* Close pipes. */ + msgbuf_write(&iev_engine->ibuf.w); + msgbuf_clear(&iev_engine->ibuf.w); + close(iev_engine->ibuf.fd); + msgbuf_write(&iev_main->ibuf.w); + msgbuf_clear(&iev_main->ibuf.w); + close(iev_main->ibuf.fd); + + free(iev_engine); + free(iev_main); + + log_info("frontend exiting"); + exit(0); +} + +int +frontend_imsg_compose_main(int type, pid_t pid, void *data, + uint16_t datalen) +{ + return (imsg_compose_event(iev_main, type, 0, pid, -1, data, + datalen)); +} + +int +frontend_imsg_compose_engine(int type, uint32_t peerid, pid_t pid, + void *data, uint16_t datalen) +{ + return (imsg_compose_event(iev_engine, type, peerid, pid, -1, + data, datalen)); +} + +void +frontend_dispatch_main(int fd, short event, void *bula) +{ + struct imsg imsg; + struct imsgev *iev = bula; + struct imsgbuf *ibuf = &iev->ibuf; + struct iface *iface; + ssize_t n; + int shut = 0, bpfsock, if_index, udpsock; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("%s: imsg_get error", __func__); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { + case IMSG_SOCKET_IPC: + /* + * Setup pipe and event handler to the engine + * process. + */ + if (iev_engine) + fatalx("%s: received unexpected imsg fd " + "to frontend", __func__); + + if ((fd = imsg.fd) == -1) + fatalx("%s: expected to receive imsg fd to " + "frontend but didn't receive any", + __func__); + + iev_engine = malloc(sizeof(struct imsgev)); + if (iev_engine == NULL) + fatal(NULL); + + imsg_init(&iev_engine->ibuf, fd); + iev_engine->handler = frontend_dispatch_engine; + iev_engine->events = EV_READ; + + event_set(&iev_engine->ev, iev_engine->ibuf.fd, + iev_engine->events, iev_engine->handler, iev_engine); + event_add(&iev_engine->ev, NULL); + break; + case IMSG_BPFSOCK: + if ((bpfsock = imsg.fd) == -1) + fatalx("%s: expected to receive imsg " + "bpf fd but didn't receive any", + __func__); + if (IMSG_DATA_SIZE(imsg) != sizeof(if_index)) + fatalx("%s: IMSG_BPFSOCK wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + memcpy(&if_index, imsg.data, sizeof(if_index)); + set_bpfsock(bpfsock, if_index); + break; + case IMSG_UDPSOCK: + if ((udpsock = imsg.fd) == -1) + fatalx("%s: expected to receive imsg " + "udpsocket fd but didn't receive any", + __func__); + if (IMSG_DATA_SIZE(imsg) != sizeof(if_index)) + fatalx("%s: IMSG_UDPSOCK wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + memcpy(&if_index, imsg.data, sizeof(if_index)); + if ((iface = get_iface_by_id(if_index)) == NULL) { + close(fd); + break; + } + if (iface->udpsock != -1) + fatalx("%s: received unexpected udpsocket", + __func__); + iface->udpsock = udpsock; + break; + case IMSG_CLOSE_UDPSOCK: + if (IMSG_DATA_SIZE(imsg) != sizeof(if_index)) + fatalx("%s: IMSG_UDPSOCK wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + memcpy(&if_index, imsg.data, sizeof(if_index)); + if ((iface = get_iface_by_id(if_index)) != NULL && + iface->udpsock != -1) { + close(iface->udpsock); + iface->udpsock = -1; + } + break; + case IMSG_ROUTESOCK: + if ((fd = imsg.fd) == -1) + fatalx("%s: expected to receive imsg " + "routesocket fd but didn't receive any", + __func__); + event_set(&ev_route, fd, EV_READ | EV_PERSIST, + route_receive, NULL); + break; + case IMSG_STARTUP: + frontend_startup(); + break; +#ifndef SMALL + case IMSG_CONTROLFD: + if ((fd = imsg.fd) == -1) + fatalx("%s: expected to receive imsg " + "control fd but didn't receive any", + __func__); + /* Listen on control socket. */ + control_listen(fd); + break; + case IMSG_CTL_END: + control_imsg_relay(&imsg); + break; +#endif /* SMALL */ + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler. */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +frontend_dispatch_engine(int fd, short event, void *bula) +{ + struct imsgev *iev = bula; + struct imsgbuf *ibuf = &iev->ibuf; + struct imsg imsg; + struct iface *iface; + ssize_t n; + int shut = 0; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + if (event & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) /* Connection closed. */ + shut = 1; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("%s: imsg_get error", __func__); + if (n == 0) /* No more messages. */ + break; + + switch (imsg.hdr.type) { +#ifndef SMALL + case IMSG_CTL_END: + case IMSG_CTL_SHOW_INTERFACE_INFO: + control_imsg_relay(&imsg); + break; +#endif /* SMALL */ + case IMSG_SEND_DISCOVER: { + struct imsg_req_discover imsg_req_discover; + if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_req_discover)) + fatalx("%s: IMSG_SEND_DISCOVER wrong " + "length: %lu", __func__, + IMSG_DATA_SIZE(imsg)); + memcpy(&imsg_req_discover, imsg.data, + sizeof(imsg_req_discover)); + iface = get_iface_by_id(imsg_req_discover.if_index); + if (iface != NULL) { + iface->xid = imsg_req_discover.xid; + send_discover(iface); + } + break; + } + case IMSG_SEND_REQUEST: { + struct imsg_req_request imsg_req_request; + if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_req_request)) + fatalx("%s: IMSG_SEND_REQUEST wrong " + "length: %lu", __func__, + IMSG_DATA_SIZE(imsg)); + memcpy(&imsg_req_request, imsg.data, + sizeof(imsg_req_request)); + iface = get_iface_by_id(imsg_req_request.if_index); + if (iface != NULL) { + iface->xid = imsg_req_request.xid; + iface->requested_ip.s_addr = + imsg_req_request.requested_ip.s_addr; + iface->server_identifier.s_addr = + imsg_req_request.server_identifier.s_addr; + iface->dhcp_server.s_addr = + imsg_req_request.dhcp_server.s_addr; + send_request(iface); + } + break; + } + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* This pipe is dead. Remove its event handler. */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +int +get_flags(char *if_name) +{ + struct ifreq ifr; + + strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name)); + if (ioctl(ioctlsock, SIOCGIFFLAGS, (caddr_t)&ifr) == -1) { + log_warn("SIOCGIFFLAGS"); + return -1; + } + return ifr.ifr_flags; +} + +int +get_xflags(char *if_name) +{ + struct ifreq ifr; + + strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name)); + if (ioctl(ioctlsock, SIOCGIFXFLAGS, (caddr_t)&ifr) == -1) { + log_warn("SIOCGIFXFLAGS"); + return -1; + } + return ifr.ifr_flags; +} + +int +get_ifrdomain(char *if_name) +{ + struct ifreq ifr; + + strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name)); + if (ioctl(ioctlsock, SIOCGIFRDOMAIN, (caddr_t)&ifr) == -1) { + log_warn("SIOCGIFRDOMAIN"); + return -1; + } + return ifr.ifr_rdomainid; +} + +void +update_iface(uint32_t if_index, char* if_name) +{ + struct iface *iface; + struct imsg_ifinfo imsg_ifinfo; + struct ifaddrs *ifap, *ifa; + struct sockaddr_dl *sdl; + int flags, xflags, ifrdomain; + + if ((flags = get_flags(if_name)) == -1 || (xflags = + get_xflags(if_name)) == -1) + return; + + if (!(xflags & IFXF_AUTOCONF4)) + return; + + if((ifrdomain = get_ifrdomain(if_name)) == -1) + return; + + iface = get_iface_by_id(if_index); + + if (iface != NULL) { + if (iface->rdomain != ifrdomain) { + iface->rdomain = ifrdomain; + if (iface->udpsock != -1) { + close(iface->udpsock); + iface->udpsock = -1; + } + } + } else { + if ((iface = calloc(1, sizeof(*iface))) == NULL) + fatal("calloc"); + iface->if_index = if_index; + iface->rdomain = ifrdomain; + iface->udpsock = -1; + LIST_INSERT_HEAD(&interfaces, iface, entries); + frontend_imsg_compose_main(IMSG_OPEN_BPFSOCK, 0, + &if_index, sizeof(if_index)); + } + + memset(&imsg_ifinfo, 0, sizeof(imsg_ifinfo)); + + imsg_ifinfo.if_index = if_index; + imsg_ifinfo.rdomain = ifrdomain; + + imsg_ifinfo.running = (flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP | + IFF_RUNNING); + + + if (getifaddrs(&ifap) != 0) + fatal("getifaddrs"); + + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + if (strcmp(if_name, ifa->ifa_name) != 0) + continue; + if (ifa->ifa_addr == NULL) + continue; + + switch(ifa->ifa_addr->sa_family) { + case AF_LINK: + imsg_ifinfo.link_state = + ((struct if_data *)ifa->ifa_data)->ifi_link_state; + sdl = (struct sockaddr_dl *)ifa->ifa_addr; + if (sdl->sdl_type != IFT_ETHER || + sdl->sdl_alen != ETHER_ADDR_LEN) + continue; + memcpy(iface->hw_address.ether_addr_octet, + LLADDR(sdl), ETHER_ADDR_LEN); + goto out; + default: + break; + } + } + out: + freeifaddrs(ifap); + + memcpy(&imsg_ifinfo.hw_address, &iface->hw_address, + sizeof(imsg_ifinfo.hw_address)); + + frontend_imsg_compose_main(IMSG_UPDATE_IF, 0, &imsg_ifinfo, + sizeof(imsg_ifinfo)); +} + +void +frontend_startup(void) +{ + struct if_nameindex *ifnidxp, *ifnidx; + + if (!event_initialized(&ev_route)) + fatalx("%s: did not receive a route socket from the main " + "process", __func__); + + event_add(&ev_route, NULL); + + if ((ifnidxp = if_nameindex()) == NULL) + fatalx("if_nameindex"); + + for(ifnidx = ifnidxp; ifnidx->if_index !=0 && ifnidx->if_name != NULL; + ifnidx++) + update_iface(ifnidx->if_index, ifnidx->if_name); + + if_freenameindex(ifnidxp); +} + +void +route_receive(int fd, short events, void *arg) +{ + static uint8_t *buf; + + struct rt_msghdr *rtm; + struct sockaddr *sa, *rti_info[RTAX_MAX]; + ssize_t n; + + if (buf == NULL) { + buf = malloc(ROUTE_SOCKET_BUF_SIZE); + if (buf == NULL) + fatal("malloc"); + } + rtm = (struct rt_msghdr *)buf; + if ((n = read(fd, buf, ROUTE_SOCKET_BUF_SIZE)) == -1) { + if (errno == EAGAIN || errno == EINTR) + return; + log_warn("dispatch_rtmsg: read error"); + return; + } + + if (n == 0) + fatal("routing socket closed"); + + if (n < (ssize_t)sizeof(rtm->rtm_msglen) || n < rtm->rtm_msglen) { + log_warnx("partial rtm of %zd in buffer", n); + return; + } + + if (rtm->rtm_version != RTM_VERSION) + return; + + sa = (struct sockaddr *)(buf + rtm->rtm_hdrlen); + get_rtaddrs(rtm->rtm_addrs, sa, rti_info); + + handle_route_message(rtm, rti_info); +} + +void +handle_route_message(struct rt_msghdr *rtm, struct sockaddr **rti_info) +{ + struct if_msghdr *ifm; + int xflags, if_index; + char ifnamebuf[IFNAMSIZ]; + char *if_name; + + switch (rtm->rtm_type) { + case RTM_IFINFO: + ifm = (struct if_msghdr *)rtm; + if_name = if_indextoname(ifm->ifm_index, ifnamebuf); + if (if_name == NULL) { + log_debug("RTM_IFINFO: lost if %d", ifm->ifm_index); + if_index = ifm->ifm_index; + frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0, + &if_index, sizeof(if_index)); + remove_iface(if_index); + } else { + xflags = get_xflags(if_name); + if (xflags == -1 || !(xflags & IFXF_AUTOCONF4)) { + log_debug("RTM_IFINFO: %s(%d) no(longer) " + "autoconf4", if_name, ifm->ifm_index); + if_index = ifm->ifm_index; + frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, + 0, &if_index, sizeof(if_index)); + } else { + update_iface(ifm->ifm_index, if_name); + } + } + break; + case RTM_NEWADDR: + ifm = (struct if_msghdr *)rtm; + if_name = if_indextoname(ifm->ifm_index, ifnamebuf); + log_debug("RTM_NEWADDR: %s[%u]", if_name, ifm->ifm_index); + update_iface(ifm->ifm_index, if_name); + break; + case RTM_PROPOSAL: + if (rtm->rtm_priority == RTP_PROPOSAL_SOLICIT) { + log_debug("RTP_PROPOSAL_SOLICIT"); + frontend_imsg_compose_engine(IMSG_REPROPOSE_RDNS, + 0, 0, NULL, 0); + } + break; + default: + log_debug("unexpected RTM: %d", rtm->rtm_type); + break; + } + +} + +#define ROUNDUP(a) \ + ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) + +void +get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info) +{ + int i; + + for (i = 0; i < RTAX_MAX; i++) { + if (addrs & (1 << i)) { + rti_info[i] = sa; + sa = (struct sockaddr *)((char *)(sa) + + ROUNDUP(sa->sa_len)); + } else + rti_info[i] = NULL; + } +} + +void +bpf_receive(int fd, short events, void *arg) +{ + struct bpf_hdr *hdr; + struct imsg_dhcp imsg_dhcp; + struct iface *iface; + ssize_t len, rem; + uint8_t *p; + + iface = (struct iface *)arg; + + log_debug("%s: fd: %d", __func__, fd); + if ((len = read(fd, iface->bpfev.buf, BPFLEN)) == -1) { + log_warn("read"); + return; + } + /* XXX len = 0 */ + log_debug("%s: %ld", __func__, len); + + memset(&imsg_dhcp, 0, sizeof(imsg_dhcp)); + imsg_dhcp.if_index = iface->if_index; + + rem = len; + p = iface->bpfev.buf; + + while (rem > 0) { + if ((size_t)rem < sizeof(*hdr)) { + log_warnx("packet too short"); + return; + } + hdr = (struct bpf_hdr *)p; + if (hdr->bh_caplen != hdr->bh_datalen) { + log_warnx("skipping truncated packet"); + goto cont; + } + if (rem < hdr->bh_hdrlen + hdr->bh_caplen) + /* we are done */ + break; + if (hdr->bh_caplen > sizeof(imsg_dhcp.packet)) { + log_warn("packet too big"); + goto cont; + } + memcpy(&imsg_dhcp.packet, p + hdr->bh_hdrlen, hdr->bh_caplen); + imsg_dhcp.len = hdr->bh_caplen; + frontend_imsg_compose_engine(IMSG_DHCP, 0, 0, &imsg_dhcp, + sizeof(imsg_dhcp)); + cont: + p += BPF_WORDALIGN(hdr->bh_hdrlen + hdr->bh_caplen); + rem -= BPF_WORDALIGN(hdr->bh_hdrlen + hdr->bh_caplen); + + } +} + +ssize_t +build_packet(uint8_t message_type, uint32_t xid, struct ether_addr *hw_address, + struct in_addr *requested_ip, struct in_addr *server_identifier) +{ + static uint8_t dhcp_cookie[] = DHCP_COOKIE; + static uint8_t dhcp_message_type[] = {DHO_DHCP_MESSAGE_TYPE, 1, + DHCPDISCOVER}; + static uint8_t dhcp_hostname[255] = {DHO_HOST_NAME, 0 /*, ... */}; + static uint8_t dhcp_client_id[] = {DHO_DHCP_CLIENT_IDENTIFIER, 7, + HTYPE_ETHER, 0, 0, 0, 0, 0, 0}; + static uint8_t dhcp_req_list[] = {DHO_DHCP_PARAMETER_REQUEST_LIST, + 8, DHO_SUBNET_MASK, DHO_ROUTERS, DHO_DOMAIN_NAME_SERVERS, + DHO_HOST_NAME, DHO_DOMAIN_NAME, DHO_BROADCAST_ADDRESS, + DHO_DOMAIN_SEARCH, DHO_CLASSLESS_STATIC_ROUTES}; + static uint8_t dhcp_requested_address[] = {DHO_DHCP_REQUESTED_ADDRESS, + 4, 0, 0, 0, 0}; + static uint8_t dhcp_server_identifier[] = {DHO_DHCP_SERVER_IDENTIFIER, + 4, 0, 0, 0, 0}; + struct dhcp_hdr *hdr; + uint8_t *p; + char *c; + + memset(dhcp_packet, 0, sizeof(dhcp_packet)); + dhcp_message_type[2] = message_type; + p = dhcp_packet; + hdr = (struct dhcp_hdr *)p; + hdr->op = DHCP_BOOTREQUEST; + hdr->htype = HTYPE_ETHER; + hdr->hlen = 6; + hdr->hops = 0; + hdr->xid = xid; + hdr->secs = 0; + memcpy(hdr->chaddr, hw_address, sizeof(*hw_address)); + p += sizeof(struct dhcp_hdr); + memcpy(p, dhcp_cookie, sizeof(dhcp_cookie)); + p += sizeof(dhcp_cookie); + memcpy(p, dhcp_message_type, sizeof(dhcp_message_type)); + p += sizeof(dhcp_message_type); + if (gethostname(dhcp_hostname + 2, sizeof(dhcp_hostname) - 2) == 0) { + if ((c = strchr(dhcp_hostname + 2, '.')) != NULL) + *c = '\0'; + dhcp_hostname[1] = strlen(dhcp_hostname + 2); + memcpy(p, dhcp_hostname, dhcp_hostname[1] + 2); + p += dhcp_hostname[1] + 2; + } + memcpy(dhcp_client_id + 3, hw_address, sizeof(*hw_address)); + memcpy(p, dhcp_client_id, sizeof(dhcp_client_id)); + p += sizeof(dhcp_client_id); + memcpy(p, dhcp_req_list, sizeof(dhcp_req_list)); + p += sizeof(dhcp_req_list); + + if (message_type == DHCPREQUEST) { + memcpy(dhcp_requested_address + 2, requested_ip, + sizeof(*requested_ip)); + memcpy(p, dhcp_requested_address, + sizeof(dhcp_requested_address)); + p += sizeof(dhcp_requested_address); + + if (server_identifier->s_addr != INADDR_ANY) { + memcpy(dhcp_server_identifier + 2, server_identifier, + sizeof(*server_identifier)); + memcpy(p, dhcp_server_identifier, + sizeof(dhcp_server_identifier)); + p += sizeof(dhcp_server_identifier); + } + } + + *p = DHO_END; + p += 1; + + return (p - dhcp_packet); +} + +void +send_discover(struct iface *iface) +{ + ssize_t pkt_len; + + if (!event_initialized(&iface->bpfev.ev)) { + iface->send_discover = 1; + return; + } + iface->send_discover = 0; + log_debug("%s", __func__); + pkt_len = build_packet(DHCPDISCOVER, iface->xid, &iface->hw_address, + &iface->requested_ip, NULL); + log_debug("%s, pkt_len: %ld", __func__, pkt_len); + bpf_send_packet(iface, dhcp_packet, pkt_len); +} + +void +send_request(struct iface *iface) +{ + ssize_t pkt_len; + + pkt_len = build_packet(DHCPREQUEST, iface->xid, &iface->hw_address, + &iface->requested_ip, &iface->server_identifier); + log_debug("%s, pkt_len: %ld", __func__, pkt_len); + if (iface->dhcp_server.s_addr != INADDR_ANY) + udp_send_packet(iface, dhcp_packet, pkt_len); + else + bpf_send_packet(iface, dhcp_packet, pkt_len); +} + +void +udp_send_packet(struct iface *iface, uint8_t *packet, ssize_t len) +{ + struct sockaddr_in to; + + log_debug("%s", __func__); + memset(&to, 0, sizeof(to)); + to.sin_family = AF_INET; + to.sin_len = sizeof(to); + to.sin_addr.s_addr = iface->dhcp_server.s_addr; + to.sin_port = ntohs(SERVER_PORT); + + if (sendto(iface->udpsock, packet, len, 0, (struct sockaddr *)&to, + sizeof(to)) == -1) + log_warn("sendto"); +} +void +bpf_send_packet(struct iface *iface, uint8_t *packet, ssize_t len) +{ + struct iovec iov[4]; + struct ether_header eh; + struct ip ip; + struct udphdr udp; + ssize_t total, result; + int iovcnt = 0, i; + + memset(eh.ether_dhost, 0xff, sizeof(eh.ether_dhost)); + memcpy(eh.ether_shost, &iface->hw_address, sizeof(eh.ether_dhost)); + eh.ether_type = htons(ETHERTYPE_IP); + iov[0].iov_base = &eh; + iov[0].iov_len = sizeof(eh); + iovcnt++; + + ip.ip_v = 4; + ip.ip_hl = 5; + ip.ip_tos = IPTOS_LOWDELAY; + ip.ip_len = htons(sizeof(ip) + sizeof(udp) + len); + ip.ip_id = 0; + ip.ip_off = 0; + ip.ip_ttl = 128; + ip.ip_p = IPPROTO_UDP; + ip.ip_sum = 0; + ip.ip_src.s_addr = 0; + ip.ip_dst.s_addr = INADDR_BROADCAST; + ip.ip_sum = wrapsum(checksum((unsigned char *)&ip, sizeof(ip), 0)); + iov[iovcnt].iov_base = &ip; + iov[iovcnt].iov_len = sizeof(ip); + iovcnt++; + + udp.uh_sport = htons(CLIENT_PORT); + udp.uh_dport = htons(SERVER_PORT); + udp.uh_ulen = htons(sizeof(udp) + len); + udp.uh_sum = 0; + udp.uh_sum = wrapsum(checksum((unsigned char *)&udp, sizeof(udp), + checksum((unsigned char *)packet, len, + checksum((unsigned char *)&ip.ip_src, + 2 * sizeof(ip.ip_src), + IPPROTO_UDP + (uint32_t)ntohs(udp.uh_ulen))))); + iov[iovcnt].iov_base = &udp; + iov[iovcnt].iov_len = sizeof(udp); + iovcnt++; + + iov[iovcnt].iov_base = packet; + iov[iovcnt].iov_len = len; + iovcnt++; + + total = 0; + for (i = 0; i < iovcnt; i++) + total += iov[i].iov_len; + + result = writev(EVENT_FD(&iface->bpfev.ev), iov, iovcnt); + if (result == -1) + log_warn("%s: writev", __func__); + else if (result < total) { + log_warnx("%s, writev: %zd of %zd bytes", __func__, result, + total); + } +} + +struct iface* +get_iface_by_id(uint32_t if_index) +{ + struct iface *iface; + + LIST_FOREACH (iface, &interfaces, entries) { + if (iface->if_index == if_index) + return (iface); + } + + return (NULL); +} + +void +remove_iface(uint32_t if_index) +{ + struct iface *iface; + + iface = get_iface_by_id(if_index); + + if (iface == NULL) + return; + + LIST_REMOVE(iface, entries); + event_del(&iface->bpfev.ev); + close(EVENT_FD(&iface->bpfev.ev)); + if (iface->udpsock != -1) + close(iface->udpsock); + free(iface); +} + +void +set_bpfsock(int bpfsock, uint32_t if_index) +{ + struct iface *iface; + + log_debug("%s: %d fd: %d", __func__, if_index, bpfsock); + + if ((iface = get_iface_by_id(if_index)) == NULL) { + /* + * The interface disappeared while we were waiting for the + * parent process to open the raw socket. + */ + close(bpfsock); + } else { + event_set(&iface->bpfev.ev, bpfsock, EV_READ | + EV_PERSIST, bpf_receive, iface); + event_add(&iface->bpfev.ev, NULL); + if (iface->send_discover) + send_discover(iface); + } +} diff --git a/sbin/dhcpleased/frontend.h b/sbin/dhcpleased/frontend.h new file mode 100644 index 00000000000..7a875d3868b --- /dev/null +++ b/sbin/dhcpleased/frontend.h @@ -0,0 +1,24 @@ +/* $OpenBSD: frontend.h,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* + * Copyright (c) 2004, 2005 Esben Norby + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +void frontend(int, int); +void frontend_dispatch_main(int, short, void *); +void frontend_dispatch_engine(int, short, void *); +int frontend_imsg_compose_main(int, pid_t, void *, uint16_t); +int frontend_imsg_compose_engine(int, uint32_t, pid_t, void *, + uint16_t); diff --git a/sbin/dhcpleased/log.c b/sbin/dhcpleased/log.c new file mode 100644 index 00000000000..3f4a4da52fa --- /dev/null +++ b/sbin/dhcpleased/log.c @@ -0,0 +1,199 @@ +/* $OpenBSD: log.c,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" + +static int debug; +static int verbose; +static const char *log_procname; + +void +log_init(int n_debug, int facility) +{ + extern char *__progname; + + debug = n_debug; + verbose = n_debug; + log_procinit(__progname); + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, facility); + + tzset(); +} + +void +log_procinit(const char *procname) +{ + if (procname != NULL) + log_procname = procname; +} + +void +log_setverbose(int v) +{ + verbose = v; +} + +int +log_getverbose(void) +{ + return (verbose); +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + int saved_errno = errno; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); + + errno = saved_errno; +} + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + int saved_errno = errno; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_ERR, "%s", strerror(saved_errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, + strerror(saved_errno)) == -1) { + /* we tried it... */ + vlog(LOG_ERR, emsg, ap); + logit(LOG_ERR, "%s", strerror(saved_errno)); + } else { + vlog(LOG_ERR, nfmt, ap); + free(nfmt); + } + va_end(ap); + } + + errno = saved_errno; +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_ERR, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (verbose) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +static void +vfatalc(int code, const char *emsg, va_list ap) +{ + static char s[BUFSIZ]; + const char *sep; + + if (emsg != NULL) { + (void)vsnprintf(s, sizeof(s), emsg, ap); + sep = ": "; + } else { + s[0] = '\0'; + sep = ""; + } + if (code) + logit(LOG_CRIT, "fatal in %s: %s%s%s", + log_procname, s, sep, strerror(code)); + else + logit(LOG_CRIT, "fatal in %s%s%s", log_procname, sep, s); +} + +void +fatal(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(errno, emsg, ap); + va_end(ap); + exit(1); +} + +void +fatalx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(0, emsg, ap); + va_end(ap); + exit(1); +} diff --git a/sbin/dhcpleased/log.h b/sbin/dhcpleased/log.h new file mode 100644 index 00000000000..90b094e982f --- /dev/null +++ b/sbin/dhcpleased/log.h @@ -0,0 +1,60 @@ +/* $OpenBSD: log.h,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef LOG_H +#define LOG_H + +#include +#include + +#ifndef SMALL +void log_init(int, int); +void log_procinit(const char *); +void log_setverbose(int); +int log_getverbose(void); +void log_warn(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_warnx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_info(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void logit(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void vlog(int, const char *, va_list) + __attribute__((__format__ (printf, 2, 0))); +__dead void fatal(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +__dead void fatalx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +#else +#define log_init(x...) do {} while(0) +#define log_procinit(x...) do {} while(0) +#define log_setverbose(x...) do {} while(0) +#define log_warn(x...) do {} while(0) +#define log_warnx(x...) do {} while(0) +#define log_info(x...) do {} while(0) +#define log_debug(x...) do {} while(0) +#define logit(x...) do {} while(0) +#define vlog(x...) do {} while(0) +#define fatal(x...) exit(1) +#define fatalx(x...) exit(1) +#endif /* SMALL */ + +#endif /* LOG_H */ diff --git a/usr.sbin/dhcpleasectl/Makefile b/usr.sbin/dhcpleasectl/Makefile new file mode 100644 index 00000000000..af564dda3cc --- /dev/null +++ b/usr.sbin/dhcpleasectl/Makefile @@ -0,0 +1,17 @@ +# $OpenBSD: Makefile,v 1.1 2021/02/26 16:16:37 florian Exp $ + +PROG= dhcpleasectl +SRCS= dhcpleasectl.c parser.c + +MAN= dhcpleasectl.8 + +CFLAGS+= -Wall +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare +CFLAGS+= -I${.CURDIR} -I${.CURDIR}/../../sbin/dhcpleased +LDADD= -lutil +DPADD= ${LIBUTIL} + +.include diff --git a/usr.sbin/dhcpleasectl/dhcpleasectl.8 b/usr.sbin/dhcpleasectl/dhcpleasectl.8 new file mode 100644 index 00000000000..b68d592a25d --- /dev/null +++ b/usr.sbin/dhcpleasectl/dhcpleasectl.8 @@ -0,0 +1,80 @@ +.\" $OpenBSD: dhcpleasectl.8,v 1.1 2021/02/26 16:16:37 florian Exp $ +.\" +.\" Copyright (c) 2021 Florian Obser +.\" Copyright (c) 2016 Kenneth R Westerback +.\" Copyright (c) 2004, 2005 Esben Norby +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: February 26 2021 $ +.Dt DHCPLEASECTL 8 +.Os +.Sh NAME +.Nm dhcpleasectl +.Nd control the dhcpleased daemon +.Sh SYNOPSIS +.Nm +.Op Fl s Ar socket +.Ar command +.Op Ar argument ... +.Sh DESCRIPTION +The +.Nm +program controls the +.Xr dhcpleased 8 +daemon. +.Pp +The following options are available: +.Bl -tag -width Ds +.It Fl s Ar socket +Use +.Ar socket +instead of the default +.Pa /dev/dhcpleased.sock +to communicate with +.Xr dhcpleased 8 . +.El +.Pp +The following commands are available: +.Bl -tag -width Ds +.It Cm log brief +Disable verbose debug logging. +.It Cm log verbose +Enable verbose debug logging. +.It Cm send request Ar interfacename +Send a DHCP request on interface +.Ar interfacename +to force a renew of the lease. +.It Cm show interface Op Ar interfacename +Display status about network interfaces. +If +.Ar interfacename +is specified, only information relative to +.Ar interfacename +is shown. +Otherwise information on all interfaces is shown. +.El +.Sh FILES +.Bl -tag -width "/dev/dhcpleased.sockXX" -compact +.It Pa /dev/dhcpleased.sock +.Ux Ns -domain +socket used for communication with +.Xr dhcpleased 8 . +.El +.Sh SEE ALSO +.Xr dhcpleased 8 +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 6.9 . diff --git a/usr.sbin/dhcpleasectl/dhcpleasectl.c b/usr.sbin/dhcpleasectl/dhcpleasectl.c new file mode 100644 index 00000000000..e05b70fe558 --- /dev/null +++ b/usr.sbin/dhcpleasectl/dhcpleasectl.c @@ -0,0 +1,258 @@ +/* $OpenBSD: dhcpleasectl.c,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* + * Copyright (c) 2021 Florian Obser + * Copyright (c) 2005 Claudio Jeker + * Copyright (c) 2004, 2005 Esben Norby + * Copyright (c) 2003 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dhcpleased.h" +#include "frontend.h" +#include "parser.h" + +__dead void usage(void); +int show_interface_msg(struct imsg *); + +struct imsgbuf *ibuf; + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-s socket] command [argument ...]\n", + __progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct sockaddr_un sun; + struct parse_result *res; + struct imsg imsg; + int ctl_sock; + int done = 0; + int n, verbose = 0; + int ch; + char *sockname; + + sockname = DHCPLEASED_SOCKET; + while ((ch = getopt(argc, argv, "s:")) != -1) { + switch (ch) { + case 's': + sockname = optarg; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (pledge("stdio unix", NULL) == -1) + err(1, "pledge"); + + /* Parse command line. */ + if ((res = parse(argc, argv)) == NULL) + exit(1); + + /* Connect to control socket. */ + if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + err(1, "socket"); + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, sockname, sizeof(sun.sun_path)); + + if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) + err(1, "connect: %s", sockname); + + if (pledge("stdio", NULL) == -1) + err(1, "pledge"); + + if ((ibuf = malloc(sizeof(struct imsgbuf))) == NULL) + err(1, NULL); + imsg_init(ibuf, ctl_sock); + done = 0; + + /* Process user request. */ + switch (res->action) { + case LOG_VERBOSE: + verbose = 1; + /* FALLTHROUGH */ + case LOG_BRIEF: + imsg_compose(ibuf, IMSG_CTL_LOG_VERBOSE, 0, 0, -1, + &verbose, sizeof(verbose)); + printf("logging request sent.\n"); + done = 1; + break; + case SHOW_INTERFACE: + imsg_compose(ibuf, IMSG_CTL_SHOW_INTERFACE_INFO, 0, 0, -1, + &res->if_index, sizeof(res->if_index)); + break; + case SEND_REQUEST: + imsg_compose(ibuf, IMSG_CTL_SEND_REQUEST, 0, 0, -1, + &res->if_index, sizeof(res->if_index)); + done = 1; + break; + default: + usage(); + } + + while (ibuf->w.queued) + if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN) + err(1, "write error"); + + while (!done) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + errx(1, "imsg_read error"); + if (n == 0) + errx(1, "pipe closed"); + + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + errx(1, "imsg_get error"); + if (n == 0) + break; + + switch (res->action) { + case SHOW_INTERFACE: + done = show_interface_msg(&imsg); + break; + default: + break; + } + + imsg_free(&imsg); + } + } + close(ctl_sock); + free(ibuf); + + return (0); +} + +int +show_interface_msg(struct imsg *imsg) +{ + static int if_count = 0; + struct ctl_engine_info *cei; + struct timespec now, diff; + int i, h, m, s; + char buf[IF_NAMESIZE], *bufp; + char ipbuf[INET_ADDRSTRLEN]; + char maskbuf[INET_ADDRSTRLEN]; + char routerbuf[INET_ADDRSTRLEN]; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_INTERFACE_INFO: + cei = imsg->data; + + if (if_count++ > 0) + printf("\n"); + + bufp = if_indextoname(cei->if_index, buf); + printf("%s [%s]:\n", bufp != NULL ? bufp : "unknown", + cei->state); + memset(ipbuf, 0, sizeof(ipbuf)); + if (cei->server_identifier.s_addr != INADDR_ANY) { + if (inet_ntop(AF_INET, &cei->server_identifier, ipbuf, + sizeof(ipbuf)) == NULL) + ipbuf[0] = '\0'; + } else if (cei->dhcp_server.s_addr != INADDR_ANY) { + if (inet_ntop(AF_INET, &cei->dhcp_server, ipbuf, + sizeof(ipbuf)) == NULL) + ipbuf[0] = '\0'; + } + if (ipbuf[0] != '\0') + printf("\tserver: %s\n", ipbuf); + if (cei->requested_ip.s_addr != INADDR_ANY) { + clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, &cei->request_time, &diff); + memset(ipbuf, 0, sizeof(ipbuf)); + memset(maskbuf, 0, sizeof(maskbuf)); + memset(routerbuf, 0, sizeof(routerbuf)); + if (inet_ntop(AF_INET, &cei->requested_ip, ipbuf, + sizeof(ipbuf)) == NULL) + ipbuf[0] = '\0'; + if (inet_ntop(AF_INET, &cei->mask, maskbuf, + sizeof(maskbuf)) == NULL) + maskbuf[0] = '\0'; + if (inet_ntop(AF_INET, &cei->router, routerbuf, + sizeof(routerbuf)) == NULL) + routerbuf[0] = '\0'; + printf("\t IP: %s/%s\n", ipbuf, maskbuf); + if (cei->router.s_addr != INADDR_ANY) + printf("\trouter: %s\n", routerbuf); + if (cei->nameservers[0].s_addr != INADDR_ANY) { + printf("\t DNS:"); + for (i = 0; i < MAX_RDNS_COUNT && + cei->nameservers[i].s_addr != INADDR_ANY; + i++) { + if (inet_ntop(AF_INET, + &cei->nameservers[i], ipbuf, + sizeof(ipbuf)) == NULL) + continue; + printf(" %s", ipbuf); + } + printf("\n"); + } + s = cei->lease_time - diff.tv_sec; + if (s < 0) + s = 0; + h = s / 3600; s -= h * 3600; + m = s / 60; s -= m * 60; + if (h > 0) + printf("\t lease: %dh%dm%ds\n", h, m ,s); + else if (m > 0) + printf("\t lease: %dm%ds\n", m ,s); + else + printf("\t lease: %ds\n", s); + } + break; + case IMSG_CTL_END: + return (1); + default: + break; + } + + return (0); +} diff --git a/usr.sbin/dhcpleasectl/parser.c b/usr.sbin/dhcpleasectl/parser.c new file mode 100644 index 00000000000..eb9a0d88505 --- /dev/null +++ b/usr.sbin/dhcpleasectl/parser.c @@ -0,0 +1,207 @@ +/* $OpenBSD: parser.c,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dhcpleased.h" +#include "parser.h" + +enum token_type { + NOTOKEN, + ENDTOKEN, + INTERFACENAME, + KEYWORD +}; + +struct token { + enum token_type type; + const char *keyword; + int value; + const struct token *next; +}; + +static const struct token t_main[]; +static const struct token t_log[]; +static const struct token t_show[]; +static const struct token t_show_interface[]; +static const struct token t_send[]; +static const struct token t_send_request[]; + +static const struct token t_main[] = { + {KEYWORD, "show", SHOW, t_show}, + {KEYWORD, "log", NONE, t_log}, + {KEYWORD, "send", NONE, t_send}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_log[] = { + {KEYWORD, "verbose", LOG_VERBOSE, NULL}, + {KEYWORD, "brief", LOG_BRIEF, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_show[] = { + {KEYWORD, "interface", SHOW_INTERFACE, t_show_interface}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_send[] = { + {KEYWORD, "request", SEND_REQUEST, t_send_request}, + {ENDTOKEN, "", NONE, NULL} +}; +static const struct token t_send_request[] = { + {INTERFACENAME, "", SEND_REQUEST, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token t_show_interface[] = { + {NOTOKEN, "", NONE, NULL}, + {INTERFACENAME, "", SHOW_INTERFACE, NULL}, + {ENDTOKEN, "", NONE, NULL} +}; + +static const struct token *match_token(const char *, const struct token *, + struct parse_result *); +static void show_valid_args(const struct token *); + +struct parse_result * +parse(int argc, char *argv[]) +{ + static struct parse_result res; + const struct token *table = t_main; + const struct token *match; + + memset(&res, 0, sizeof(res)); + + while (argc >= 0) { + if ((match = match_token(argv[0], table, &res)) == NULL) { + fprintf(stderr, "valid commands/args:\n"); + show_valid_args(table); + return (NULL); + } + + argc--; + argv++; + + if (match->type == NOTOKEN || match->next == NULL) + break; + + table = match->next; + } + + if (argc > 0) { + fprintf(stderr, "superfluous argument: %s\n", argv[0]); + return (NULL); + } + + return (&res); +} + +static const struct token * +match_token(const char *word, const struct token *table, + struct parse_result *res) +{ + u_int i, match; + const struct token *t = NULL; + + match = 0; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + if (word == NULL || strlen(word) == 0) { + match++; + t = &table[i]; + } + break; + case INTERFACENAME: + if (!match && word != NULL && strlen(word) > 0) { + if ((res->if_index = if_nametoindex(word)) == 0) + errx(1, "unknown interface"); + match++; + t = &table[i]; + if (t->value) + res->action = t->value; + } + break; + case KEYWORD: + if (word != NULL && strncmp(word, table[i].keyword, + strlen(word)) == 0) { + match++; + t = &table[i]; + if (t->value) + res->action = t->value; + } + break; + case ENDTOKEN: + break; + } + } + + if (match != 1) { + if (word == NULL) + fprintf(stderr, "missing argument:\n"); + else if (match > 1) + fprintf(stderr, "ambiguous argument: %s\n", word); + else if (match < 1) + fprintf(stderr, "unknown argument: %s\n", word); + return (NULL); + } + + return (t); +} + +static void +show_valid_args(const struct token *table) +{ + int i; + + for (i = 0; table[i].type != ENDTOKEN; i++) { + switch (table[i].type) { + case NOTOKEN: + fprintf(stderr, " \n"); + break; + case INTERFACENAME: + fprintf(stderr, " \n"); + break; + case KEYWORD: + fprintf(stderr, " %s\n", table[i].keyword); + break; + case ENDTOKEN: + break; + } + } +} diff --git a/usr.sbin/dhcpleasectl/parser.h b/usr.sbin/dhcpleasectl/parser.h new file mode 100644 index 00000000000..dad595b313e --- /dev/null +++ b/usr.sbin/dhcpleasectl/parser.h @@ -0,0 +1,34 @@ +/* $OpenBSD: parser.h,v 1.1 2021/02/26 16:16:37 florian Exp $ */ + +/* + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +enum actions { + NONE, + LOG_VERBOSE, + LOG_BRIEF, + SHOW, + SHOW_INTERFACE, + SEND_REQUEST +}; + +struct parse_result { + enum actions action; + uint32_t if_index; +}; + +struct parse_result *parse(int, char *[]); -- 2.20.1