From: florian Date: Sun, 2 Jun 2024 12:28:05 +0000 (+0000) Subject: Import dhcp6leased(8) X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=ad7c548deeaa2c33bdc52cd7728abc53a491537f;p=openbsd Import dhcp6leased(8) dhcp6leased is a daemon to manage IPv6 prefix delegations. It requests a prefix from an upstream DHCPv6 server and configures downstream network interfaces. rad(8) can be used to advertise available prefixes to clients. It's a transmogrified dhcpleased(8), so it's a bit rough around the edges. But it can already request and renew prefixes and configure interfaces. It's time to hack on it in-tree. OK deraadt --- diff --git a/sbin/dhcp6leased/Makefile b/sbin/dhcp6leased/Makefile new file mode 100644 index 00000000000..4574a9a6182 --- /dev/null +++ b/sbin/dhcp6leased/Makefile @@ -0,0 +1,23 @@ +# $OpenBSD: Makefile,v 1.1 2024/06/02 12:28:05 florian Exp $ + +PROG= dhcp6leased +SRCS= control.c dhcp6leased.c engine.c frontend.c log.c +SRCS+= parse.y printconf.c + +MAN= dhcp6leased.8 dhcp6leased.conf.5 + +#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 + +# Don't compile dhcp6leased as static binary by default +LDSTATIC= diff --git a/sbin/dhcp6leased/control.c b/sbin/dhcp6leased/control.c new file mode 100644 index 00000000000..9ded3132d13 --- /dev/null +++ b/sbin/dhcp6leased/control.c @@ -0,0 +1,307 @@ +/* $OpenBSD: control.c,v 1.1 2024/06/02 12:28:05 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 "log.h" +#include "dhcp6leased.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_RELOAD: + frontend_imsg_compose_main(imsg.hdr.type, 0, NULL, 0); + break; + 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_REQUEST_REBOOT, 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/dhcp6leased/control.h b/sbin/dhcp6leased/control.h new file mode 100644 index 00000000000..b1fdef8ca3d --- /dev/null +++ b/sbin/dhcp6leased/control.h @@ -0,0 +1,23 @@ +/* $OpenBSD: control.h,v 1.1 2024/06/02 12:28:05 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. + */ + +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 *); diff --git a/sbin/dhcp6leased/dhcp6leased.8 b/sbin/dhcp6leased/dhcp6leased.8 new file mode 100644 index 00000000000..762fa4344db --- /dev/null +++ b/sbin/dhcp6leased/dhcp6leased.8 @@ -0,0 +1,102 @@ +.\" $OpenBSD: dhcp6leased.8,v 1.1 2024/06/02 12:28:05 florian Exp $ +.\" +.\" Copyright (c) 2024 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: June 2 2024 $ +.Dt DHCP6LEASED 8 +.Os +.Sh NAME +.Nm dhcp6leased +.Nd Dynamic Host Configuration Protocol (DHCPv6) client daemon for IPv6 prefix delegation +.Sh SYNOPSIS +.Nm +.Op Fl dnv +.Op Fl f Ar file +.Op Fl s Ar socket +.Sh DESCRIPTION +.Nm +is an IPv6 dynamic host configuration protocol (DHCPv6) daemon for clients. +It requests IPv6 prefix delegations from DHCPv6 servers for assignment +to downstream interfaces. +.Pp +A running +.Nm +can be controlled with the +.Xr dhcp6leasectl 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 f Ar file +Specify an alternative configuration file. +.It Fl n +Configtest mode. +Only check the configuration file for validity. +.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 "/var/db/dhcp6leased/" -compact +.It Pa /dev/dhcp6leased.sock +.Ux Ns -domain +socket used for communication with +.Xr dhcp6leasectl 8 . +.It Pa /etc/dhcp6leased.conf +Default +.Nm +configuration file. +.It Pa /var/db/dhcp6leased/ Ns Aq Ar if +Interface specific lease files. +.El +.Sh SEE ALSO +.Xr dhcp6leased.conf 5 , +.Xr dhcp6leasectl 8 , +.Xr ifconfig 8 +.Sh STANDARDS +.Rs +.%A T. Mrugalski +.%A M. Siodelski +.%A B. Volz +.%A A. Yourtchenko +.%A M. Richardson +.%A S. Jiang +.%A T. Lemon +.%A T. Winters +.%D November 2018 +.%R RFC 8415 +.%T Dynamic Host Configuration Protocol for IPv6 (DHCPv6) +.Re +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 7.6 . +.Sh AUTHORS +.An -nosplit +The +.Nm +program was written by +.An Florian Obser Aq Mt florian@openbsd.org . diff --git a/sbin/dhcp6leased/dhcp6leased.c b/sbin/dhcp6leased/dhcp6leased.c new file mode 100644 index 00000000000..0165e350088 --- /dev/null +++ b/sbin/dhcp6leased/dhcp6leased.c @@ -0,0 +1,985 @@ +/* $OpenBSD: dhcp6leased.c,v 1.1 2024/06/02 12:28:05 florian Exp $ */ + +/* + * Copyright (c) 2017, 2021, 2024 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 +#include + +#include "log.h" +#include "dhcp6leased.h" +#include "frontend.h" +#include "engine.h" +#include "control.h" + +enum dhcp6leased_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 dhcp6leased_process, char *, int, int, int); + +void main_dispatch_frontend(int, short, void *); +void main_dispatch_engine(int, short, void *); +void open_udpsock(uint32_t); +void configure_address(struct imsg_configure_address *); +void deconfigure_address(struct imsg_configure_address *); +void read_lease_file(struct imsg_ifinfo *); +uint8_t *get_uuid(void); + +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); +int main_imsg_send_config(struct dhcp6leased_conf *); +int main_reload(void); + +static struct imsgev *iev_frontend; +static struct imsgev *iev_engine; + +struct dhcp6leased_conf *main_conf; +const char default_conffile[] = _PATH_CONF_FILE; +const char *conffile = default_conffile; +pid_t frontend_pid; +pid_t engine_pid; + +int routesock, ioctl_sock, rtm_seq, no_lease_files; + +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(); + case SIGHUP: + if (main_reload() == -1) + log_warnx("configuration reload failed"); + else + log_debug("configuration reloaded"); + break; + default: + fatalx("unexpected signal"); + } +} + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-dnv] [-f file] [-s socket]\n", + __progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + struct event ev_sigint, ev_sigterm, ev_sighup; + int ch; + int debug = 0, engine_flag = 0, frontend_flag = 0; + int verbose = 0, no_action = 0; + char *saved_argv0; + int pipe_main2frontend[2]; + int pipe_main2engine[2]; + int frontend_routesock, rtfilter, lockfd; + int rtable_any = RTABLE_ANY; + char *csock = _PATH_CTRL_SOCKET; + int control_fd; + uint8_t *uuid; + + log_init(1, LOG_DAEMON); /* Log to stderr until daemonized. */ + log_setverbose(1); + + saved_argv0 = argv[0]; + if (saved_argv0 == NULL) + saved_argv0 = "dhcp6leased"; + + while ((ch = getopt(argc, argv, "dEFf:ns:v")) != -1) { + switch (ch) { + case 'd': + debug = 1; + break; + case 'E': + engine_flag = 1; + break; + case 'F': + frontend_flag = 1; + break; + case 'f': + conffile = optarg; + break; + case 'n': + no_action = 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); + + /* parse config file */ + if ((main_conf = parse_config(conffile)) == NULL) + exit(1); + + if (no_action) { + if (verbose) + print_config(main_conf, verbose); + else + fprintf(stderr, "configuration OK\n"); + exit(0); + } + + /* Check for root privileges. */ + if (geteuid()) + errx(1, "need root privileges"); + + lockfd = open(_PATH_LOCKFILE, O_CREAT|O_RDWR|O_EXLOCK|O_NONBLOCK, 0600); + if (lockfd == -1) { + if (errno == EAGAIN) + errx(1, "already running"); + err(1, "%s", _PATH_LOCKFILE); + } + + /* Check for assigned daemon user */ + if (getpwnam(DHCP6LEASED_USER) == NULL) + errx(1, "unknown user %s", DHCP6LEASED_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(routesock, SHUT_RD); + + event_init(); + + /* Setup signal handler. */ + signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL); + signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal_add(&ev_sighup, NULL); + signal(SIGPIPE, 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_INET6, 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_IFANNOUNCE); + 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)"); + + uuid = get_uuid(); + + if ((control_fd = control_init(csock)) == -1) + warnx("control socket setup failed"); + + if (unveil(conffile, "r") == -1) + fatal("unveil %s", conffile); + + if (unveil(_PATH_LEASE, "rwc") == -1) { + no_lease_files = 1; + log_warn("disabling lease files, unveil " _PATH_LEASE); + } + + if (unveil(NULL, NULL) == -1) + fatal("unveil"); + + if (pledge("stdio inet rpath wpath sendfd wroute", NULL) == -1) + fatal("pledge"); + + main_imsg_compose_frontend(IMSG_ROUTESOCK, frontend_routesock, NULL, 0); + + main_imsg_compose_frontend(IMSG_UUID, -1, uuid, UUID_SIZE); + main_imsg_compose_engine(IMSG_UUID, -1, uuid, UUID_SIZE); + + if (control_fd != -1) + main_imsg_compose_frontend(IMSG_CONTROLFD, control_fd, NULL, 0); + main_imsg_send_config(main_conf); + + 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); + + config_clear(main_conf); + + 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 dhcp6leased_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; + int verbose; + + 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_UDPSOCK: + if (IMSG_DATA_SIZE(imsg) != sizeof(if_index)) + fatalx("%s: IMSG_OPEN_UDPSOCK wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + memcpy(&if_index, imsg.data, sizeof(if_index)); + open_udpsock(if_index); + break; + case IMSG_CTL_RELOAD: + if (main_reload() == -1) + log_warnx("configuration reload failed"); + else + log_warnx("configuration reloaded"); + break; + 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_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)); + read_lease_file(&imsg_ifinfo); + main_imsg_compose_engine(IMSG_UPDATE_IF, -1, + &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_ADDRESS: { + struct imsg_configure_address imsg_configure_address; + if (IMSG_DATA_SIZE(imsg) != + sizeof(imsg_configure_address)) + fatalx("%s: IMSG_CONFIGURE_ADDRESS wrong " + "length: %lu", __func__, + IMSG_DATA_SIZE(imsg)); + memcpy(&imsg_configure_address, imsg.data, + sizeof(imsg_configure_address)); + configure_address(&imsg_configure_address); + break; + } + case IMSG_DECONFIGURE_ADDRESS: { + struct imsg_configure_address imsg_configure_address; + if (IMSG_DATA_SIZE(imsg) != + sizeof(imsg_configure_address)) + fatalx("%s: IMSG_CONFIGURE_ADDRESS wrong " + "length: %lu", __func__, + IMSG_DATA_SIZE(imsg)); + memcpy(&imsg_configure_address, imsg.data, + sizeof(imsg_configure_address)); + deconfigure_address(&imsg_configure_address); + 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); +} + +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); +} + +int +main_reload(void) +{ + struct dhcp6leased_conf *xconf; + + if ((xconf = parse_config(conffile)) == NULL) + return (-1); + + if (main_imsg_send_config(xconf) == -1) + return (-1); + + merge_config(main_conf, xconf); + + return (0); +} + +int +main_imsg_send_config(struct dhcp6leased_conf *xconf) +{ + struct iface_conf *iface_conf; + struct iface_ia_conf *ia_conf; + struct iface_pd_conf *pd_conf; + + main_imsg_compose_frontend(IMSG_RECONF_CONF, -1, NULL, 0); + main_imsg_compose_engine(IMSG_RECONF_CONF, -1, NULL, 0); + + /* Send the interface list to the frontend & engine. */ + SIMPLEQ_FOREACH(iface_conf, &xconf->iface_list, entry) { + main_imsg_compose_frontend(IMSG_RECONF_IFACE, -1, iface_conf, + sizeof(*iface_conf)); + main_imsg_compose_engine(IMSG_RECONF_IFACE, -1, iface_conf, + sizeof(*iface_conf)); + SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list, + entry) { + main_imsg_compose_frontend(IMSG_RECONF_IFACE_IA, -1, + ia_conf, sizeof(*ia_conf)); + main_imsg_compose_engine(IMSG_RECONF_IFACE_IA, -1, + ia_conf, sizeof(*ia_conf)); + SIMPLEQ_FOREACH(pd_conf, &ia_conf->iface_pd_list, + entry) { + main_imsg_compose_frontend(IMSG_RECONF_IFACE_PD, + -1, pd_conf, sizeof(*pd_conf)); + main_imsg_compose_engine(IMSG_RECONF_IFACE_PD, + -1, pd_conf, sizeof(*pd_conf)); + } + main_imsg_compose_frontend(IMSG_RECONF_IFACE_IA_END, + -1, NULL, 0); + main_imsg_compose_engine(IMSG_RECONF_IFACE_IA_END, + -1, NULL, 0); + } + main_imsg_compose_frontend(IMSG_RECONF_IFACE_END, -1, NULL, 0); + main_imsg_compose_engine(IMSG_RECONF_IFACE_END, -1, NULL, 0); + + } + + /* Config is now complete. */ + main_imsg_compose_frontend(IMSG_RECONF_END, -1, NULL, 0); + main_imsg_compose_engine(IMSG_RECONF_END, -1, NULL, 0); + + return (0); +} + +void +configure_address(struct imsg_configure_address *address) +{ + struct in6_aliasreq in6_addreq; + time_t t; + char *if_name; + + memset(&in6_addreq, 0, sizeof(in6_addreq)); + + if_name = if_indextoname(address->if_index, in6_addreq.ifra_name); + if (if_name == NULL) { + log_warnx("%s: cannot find interface %d", __func__, + address->if_index); + return; + } + + memcpy(&in6_addreq.ifra_addr, &address->addr, + sizeof(in6_addreq.ifra_addr)); + memcpy(&in6_addreq.ifra_prefixmask.sin6_addr, &address->mask, + sizeof(in6_addreq.ifra_prefixmask.sin6_addr)); + in6_addreq.ifra_prefixmask.sin6_family = AF_INET6; + in6_addreq.ifra_prefixmask.sin6_len = + sizeof(in6_addreq.ifra_prefixmask); + + t = time(NULL); + + in6_addreq.ifra_lifetime.ia6t_expire = t + address->vltime; + in6_addreq.ifra_lifetime.ia6t_vltime = address->vltime; + + in6_addreq.ifra_lifetime.ia6t_preferred = t + address->pltime; + in6_addreq.ifra_lifetime.ia6t_pltime = address->pltime; + + log_debug("%s: %s", __func__, if_name); + + if (ioctl(ioctl_sock, SIOCAIFADDR_IN6, &in6_addreq) == -1) + log_warn("SIOCAIFADDR_IN6"); +} + +void +deconfigure_address(struct imsg_configure_address *imsg) +{ + fatalx("%s: not implemented", __func__); /* XXX */ +} + +const char* +sin6_to_str(struct sockaddr_in6 *sin6) +{ + static char hbuf[NI_MAXHOST]; + int error; + + error = getnameinfo((struct sockaddr *)sin6, sin6->sin6_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; +} + +void +open_udpsock(uint32_t if_index) +{ + struct ifaddrs *ifap, *ifa; + struct sockaddr_in6 *sin6; + int udpsock = -1, rdomain, opt = 1; + char if_name[IF_NAMESIZE]; + + if (if_indextoname(if_index, if_name) == NULL) { + log_warnx("%s: cannot find interface %d", __func__, if_index); + return; + } + + 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: { + struct if_data *if_data; + + if_data = (struct if_data *)ifa->ifa_data; + rdomain = if_data->ifi_rdomain; + break; + } + case AF_INET6: { + struct sockaddr_in6 *s6; + s6 = (struct sockaddr_in6 *)ifa->ifa_addr; +#ifdef __KAME__ + if (IN6_IS_ADDR_LINKLOCAL(&s6->sin6_addr) && + s6->sin6_scope_id == 0) { + s6->sin6_scope_id = + ntohs(*(u_int16_t *) + &s6->sin6_addr.s6_addr[2]); + s6->sin6_addr.s6_addr[2] = + s6->sin6_addr.s6_addr[3] = 0; + } +#endif + if (IN6_IS_ADDR_LINKLOCAL(&s6->sin6_addr)) + sin6 = s6; + break; + } + default: + break; + + } + } + sin6->sin6_port = htons(CLIENT_PORT); + log_debug("%s: %s rdomain: %d", __func__, sin6_to_str(sin6), + rdomain); + + if ((udpsock = socket(AF_INET6, 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, &rdomain, + sizeof(rdomain)) == -1) { + /* we might race against removal of the rdomain */ + log_warn("setsockopt SO_RTABLE"); + close(udpsock); + return; + } + + if (bind(udpsock, (struct sockaddr *)sin6, sizeof(*sin6)) == -1) { + close(udpsock); + return; + } + + main_imsg_compose_frontend(IMSG_UDPSOCK, udpsock, &if_index, + sizeof(if_index)); +} + +void +read_lease_file(struct imsg_ifinfo *imsg_ifinfo) +{ + int len, fd; + char if_name[IF_NAMESIZE]; + char lease_file_buf[sizeof(_PATH_LEASE) + IF_NAMESIZE]; + + if (no_lease_files) + return; + + memset(imsg_ifinfo->lease, 0, sizeof(imsg_ifinfo->lease)); + + if (if_indextoname(imsg_ifinfo->if_index, if_name) == NULL) { + log_warnx("%s: cannot find interface %d", __func__, + imsg_ifinfo->if_index); + return; + } + + len = snprintf(lease_file_buf, sizeof(lease_file_buf), "%s%s", + _PATH_LEASE, 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; + } + + if ((fd = open(lease_file_buf, O_RDONLY)) == -1) + return; + + /* no need for error handling, we'll just do a DHCP discover */ + read(fd, imsg_ifinfo->lease, sizeof(imsg_ifinfo->lease) - 1); + close(fd); +} + +void +merge_config(struct dhcp6leased_conf *conf, struct dhcp6leased_conf *xconf) +{ + struct iface_conf *iface_conf; + struct iface_ia_conf *ia_conf; + struct iface_pd_conf *pd_conf; + + /* Remove & discard existing interfaces. */ + while ((iface_conf = SIMPLEQ_FIRST(&conf->iface_list)) != NULL) { + SIMPLEQ_REMOVE_HEAD(&conf->iface_list, entry); + while((ia_conf = + SIMPLEQ_FIRST(&iface_conf->iface_ia_list)) != NULL) { + SIMPLEQ_REMOVE_HEAD(&iface_conf->iface_ia_list, + entry); + while((pd_conf = + SIMPLEQ_FIRST(&ia_conf->iface_pd_list)) != NULL) { + SIMPLEQ_REMOVE_HEAD(&ia_conf->iface_pd_list, + entry); + free(pd_conf); + } + free(ia_conf); + } + free(iface_conf); + } + + /* Add new interfaces. */ + SIMPLEQ_CONCAT(&conf->iface_list, &xconf->iface_list); + + free(xconf); +} + +struct dhcp6leased_conf * +config_new_empty(void) +{ + struct dhcp6leased_conf *xconf; + + xconf = calloc(1, sizeof(*xconf)); + if (xconf == NULL) + fatal(NULL); + + SIMPLEQ_INIT(&xconf->iface_list); + + return (xconf); +} + +void +config_clear(struct dhcp6leased_conf *conf) +{ + struct dhcp6leased_conf *xconf; + + /* Merge current config with an empty config. */ + xconf = config_new_empty(); + merge_config(conf, xconf); + + free(conf); +} + +uint8_t* +get_uuid(void) { + static uint8_t uuid_buf[UUID_SIZE]; + uuid_t uuid; + uint32_t status; + int fd, len; + char *str; + char strbuf[UUID_STR_SIZE]; + char tmpl[] = _PATH_UUID"XXXXXXXXXX"; + + if ((fd = open(_PATH_UUID, O_RDONLY)) == -1) { + gen: + uuid_create(&uuid, NULL); + uuid_to_string(&uuid, &str, &status); + if(status != uuid_s_ok) + fatalx("failed to generate uuid string representation"); + + len = snprintf(strbuf, sizeof(strbuf), "%s\n", str); + if (len < 0 || (size_t)len >= sizeof(strbuf)) + fatalx("uuid string too long"); + free(str); + + if ((fd = mkstemp(tmpl)) == -1) { + log_warn("mkstemp"); + goto err; + } + if (write(fd, strbuf, len) < len) { + log_warn("write"); + goto err; + } + if (fchmod(fd, 0644) == -1) { + log_warn("fchmod"); + goto err; + } + + if (close(fd) == -1) { + log_warn("fchmod"); + goto err; + } + fd = -1; + if (rename(tmpl, _PATH_UUID) == -1) { + log_warn("rename"); + goto err; + } + } else { + read(fd, strbuf, sizeof(strbuf)); + close(fd); + strbuf[sizeof(strbuf) - 2] = '\0'; + + uuid_from_string(strbuf, &uuid, &status); + + if(status != uuid_s_ok) { + log_warnx("failed to convert string to uuid: %s - %d", + strbuf, status); + goto gen; + } + } + uuid_enc_be(uuid_buf, &uuid); + return (uuid_buf); + + err: + if (fd != -1) + close(fd); + unlink(tmpl); + fatal("Could not read or create UUID"); +} diff --git a/sbin/dhcp6leased/dhcp6leased.conf.5 b/sbin/dhcp6leased/dhcp6leased.conf.5 new file mode 100644 index 00000000000..fd5029cc0f0 --- /dev/null +++ b/sbin/dhcp6leased/dhcp6leased.conf.5 @@ -0,0 +1,119 @@ +.\" $OpenBSD: dhcp6leased.conf.5,v 1.1 2024/06/02 12:28:05 florian Exp $ +.\" +.\" Copyright (c) 2018, 2021, 2024 Florian Obser +.\" Copyright (c) 2005 Esben Norby +.\" Copyright (c) 2004 Claudio Jeker +.\" Copyright (c) 2003, 2004 Henning Brauer +.\" Copyright (c) 2002 Daniel Hartmeier +.\" +.\" 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: June 2 2024 $ +.Dt DHCP6LEASED.CONF 5 +.Os +.Sh NAME +.Nm dhcp6leased.conf +.Nd DHCPv6 client configuration file +.Sh DESCRIPTION +The +.Xr dhcp6leased 8 +daemon is a dynamic host configuration protocol client daemon for IPv6 prefix +delegation. +.Pp +The +.Nm +config file is divided into the following main sections: +.Bl -tag -width xxxx +.It Sy Macros +User-defined variables may be defined and used later, simplifying the +configuration file. +.It Sy Prefix delegation +.Xr dhcp6leased 8 +requests prefix delegation from a DHCPv6 server and assigns prefixes +to interfaces. +This section defines on which interfaces prefix delegation should be +requested and to which interfaces prefixes should be assigned. +.El +.Sh MACROS +Macros can be defined that will later be expanded in context. +Macro names must start with a letter, digit, or underscore, +and may contain any of those characters. +Macro names may not be reserved words (for example, +.Ic interface ) . +Macros are not expanded inside quotes. +.Sh PREFIX DELEGATION +A list of interfaces on which to request prefix delegation: +.Bd -unfilled -offset indent +.Ic request prefix delegation on Ar name Ic for Ar { name/prefix Oo Ar name/prefix ... Oc } +.Ed +.Pp +This requests a prefix delegation on +.Ar name +upstream network interface for the list of +.Ar name/prefix +network interfaces. +If +.Ar prefix +is omitted a default of /64 is used. +.Pp +.Xr dhcp6leased 8 +will calculate the prefix length needed to cover all interfaces in the list. +When a prefix is delegated by a DHCPv6 server, +.Xr dhcp6leased 8 +splits the prefix into smaller prefixes and assigns them to the interfaces +in the order they are listed. +This might create unassigned gaps in the delegated prefix. +.Pp +For example if a /64 and /60 prefix are to be assigned to network interfaces, +.Xr dhcp6leased 8 +requests a /59 prefix. +The prefix is then split into two /60 prefixes and the first /64 out of the +first /60 prefix is assigned to the first interface. +The sencond /60 prefix from the delegated /59 is assigned to the +second interface. +This leaves 15 unused /64 prefixes in the first /60. +.Pp +Care should be taken to avoid renumbering of existing interfaces +when new interfaces are added or existing interfaces are removed. +New interfaces can be added to the end of the list or in places +where unassigned gaps were present. +.Pp +The special name +.Cm reserve +can be used to reserve space in the delegated prefix for later use or +when an interface is removed. +.Pp +Running +.Xr dhcp6leased 8 +in configtest mode with a verbosity of two or more will print the +configuration file with comments indicated how prefixes would be +assigned to network interfaces. +This can be used to check that existing interface are not renumbered. +.Pp +More than one prefix can be requested from a DHCPv6 server, however most ISP +DHCPv6 servers will only delegate a single prefix. +Therefore it is better to let +.Xr dhcp6leased 8 +request a single larger prefix and split it up. +.Xr dhcp6leased 8 +has a compile time limit on how many prefix requests per interface it can +handle. +.Sh FILES +.Bl -tag -width /etc/dhcp6leased.conf -compact +.It Pa /etc/dhcp6leased.conf +.Xr dhcp6leased 8 +configuration file. +.El +.Sh SEE ALSO +.Xr dhcp6leasectl 8 , +.Xr dhcp6leased 8 diff --git a/sbin/dhcp6leased/dhcp6leased.h b/sbin/dhcp6leased/dhcp6leased.h new file mode 100644 index 00000000000..e58cbe2376e --- /dev/null +++ b/sbin/dhcp6leased/dhcp6leased.h @@ -0,0 +1,274 @@ +/* $OpenBSD: dhcp6leased.h,v 1.1 2024/06/02 12:28:05 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 _PATH_LOCKFILE "/dev/dhcp6leased.lock" +#define _PATH_CONF_FILE "/etc/dhcp6leased.conf" +#define _PATH_CTRL_SOCKET "/dev/dhcp6leased.sock" +#define DHCP6LEASED_USER "_dhcp6leased" +#define DHCP6LEASED_RTA_LABEL "dhcp6leased" +#define CLIENT_PORT 546 +#define SERVER_PORT 547 +#define _PATH_LEASE "/var/db/dhcp6leased/" +#define _PATH_UUID _PATH_LEASE"uuid" +#define UUID_SIZE 16 +#define UUID_STR_SIZE sizeof("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\n") +#define DUID_UUID_TYPE 4 +#define XID_SIZE 3 +#define SERVERID_SIZE 130 /* 2 octet type, max 128 octets data */ +#define MAX_IA 32 +#define LEASE_VERSION "version: 2" +#define LEASE_IP_PREFIX "ip: " +#define LEASE_NEXTSERVER_PREFIX "next-server: " +#define LEASE_BOOTFILE_PREFIX "filename: " +#define LEASE_HOSTNAME_PREFIX "host-name: " +#define LEASE_DOMAIN_PREFIX "domain-name: " +#define LEASE_SIZE 4096 +/* MAXDNAME from arpa/namesr.h */ +#define DHCP6LEASED_MAX_DNSSL 1025 +#define MAX_RDNS_COUNT 8 /* max nameserver in a RTM_PROPOSAL */ + +/* A 1500 bytes packet can hold less than 300 classless static routes */ +#define MAX_DHCP_ROUTES 256 + +#define OPENBSD_ENTERPRISENO 30155 + +/* DHCP message types. */ +#define DHCPSOLICIT 1 +#define DHCPADVERTISE 2 +#define DHCPREQUEST 3 +#define DHCPCONFIRM 4 +#define DHCPRENEW 5 +#define DHCPREBIND 6 +#define DHCPREPLY 7 +#define DHCPRELEASE 8 +#define DHCPDECLINE 9 +#define DHCPRECONFIGURE 10 +#define DHCPINFORMATIONREQUEST 11 +#define DHCPRELAYFORW 12 +#define DHCPRELAYREPL 13 + +/* DHCP options */ +#define DHO_CLIENTID 1 +#define DHO_SERVERID 2 +#define DHO_ORO 6 +#define DHO_ELAPSED_TIME 8 +#define DHO_STATUS_CODE 13 +#define DHO_RAPID_COMMIT 14 +#define DHO_VENDOR_CLASS 16 +#define DHO_IA_PD 25 +#define DHO_IA_PREFIX 26 +#define DHO_SOL_MAX_RT 82 +#define DHO_INF_MAX_RT 83 + +/* Status Code Option status codes */ +#define DHCP_STATUS_SUCCESS 0 +#define DHCP_STATUS_UNSPECFAIL 1 +#define DHCP_STATUS_NOADDRSAVAIL 2 +#define DHCP_STATUS_NOBINDING 3 +#define DHCP_STATUS_NOTONLINK 4 +#define DHCP_STATUS_USEMULTICAST 5 +#define DHCP_STATUS_NOPREFIXAVAIL 6 + +/* Ignore parts of DHCP lease */ +#define IGN_ROUTES 1 +#define IGN_DNS 2 + +#define MAX_SERVERS 16 /* max servers that can be ignored per if */ + +#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 msg_type; /* Message opcode/type */ + uint8_t xid[XID_SIZE]; /* Transaction ID */ +} __packed; + +struct dhcp_option_hdr { + uint16_t code; + uint16_t len; +} __packed; + +struct dhcp_duid { + uint16_t type; + uint8_t uuid[UUID_SIZE]; +} __packed; + +struct dhcp_iapd { + uint32_t iaid; + uint32_t t1; + uint32_t t2; +} __packed; + +struct dhcp_vendor_class { + uint32_t enterprise_number; + uint16_t vendor_class_len; +} __packed; + +struct dhcp_iaprefix { + uint32_t pltime; + uint32_t vltime; + uint8_t prefix_len; + struct in6_addr prefix; +} __packed; + +struct imsgev { + struct imsgbuf ibuf; + void (*handler)(int, short, void *); + struct event ev; + short events; +}; + +struct dhcp_route { + struct in_addr dst; + struct in_addr mask; + struct in_addr gw; +}; + +enum imsg_type { + IMSG_NONE, + IMSG_CTL_LOG_VERBOSE, + IMSG_CTL_SHOW_INTERFACE_INFO, + IMSG_CTL_SEND_REQUEST, + IMSG_CTL_RELOAD, + IMSG_CTL_END, + IMSG_RECONF_CONF, + IMSG_RECONF_IFACE, + IMSG_RECONF_IFACE_IA, + IMSG_RECONF_IFACE_PD, + IMSG_RECONF_IFACE_IA_END, + IMSG_RECONF_IFACE_END, + IMSG_RECONF_END, + IMSG_SEND_DISCOVER, + IMSG_SEND_REQUEST, + IMSG_SOCKET_IPC, + IMSG_OPEN_UDPSOCK, + IMSG_UDPSOCK, + IMSG_ROUTESOCK, + IMSG_UUID, + IMSG_CONTROLFD, + IMSG_STARTUP, + IMSG_UPDATE_IF, + IMSG_REMOVE_IF, + IMSG_DHCP, + IMSG_CONFIGURE_ADDRESS, + IMSG_DECONFIGURE_ADDRESS, + IMSG_REQUEST_REBOOT, +}; + +struct ctl_engine_info { + uint32_t if_index; + int running; + int link_state; + char state[sizeof("IF_INIT_REBOOT")]; + struct timespec request_time; + uint32_t lease_time; + uint32_t t1; + uint32_t t2; +}; + +struct iface_pd_conf { + SIMPLEQ_ENTRY(iface_pd_conf) entry; + char name[IF_NAMESIZE]; + struct in6_addr prefix_mask; + int prefix_len; +}; + +struct iface_ia_conf { + SIMPLEQ_ENTRY(iface_ia_conf) entry; + SIMPLEQ_HEAD(iface_pd_conf_head, iface_pd_conf) iface_pd_list; + int id; + int prefix_len; +}; + +struct iface_conf { + SIMPLEQ_ENTRY(iface_conf) entry; + SIMPLEQ_HEAD(iface_ia_conf_head, + iface_ia_conf) iface_ia_list; + uint32_t ia_count; + char name[IF_NAMESIZE]; +}; + +struct dhcp6leased_conf { + SIMPLEQ_HEAD(iface_conf_head, iface_conf) iface_list; +}; + +struct imsg_ifinfo { + uint32_t if_index; + int rdomain; + int running; + int link_state; + struct ether_addr hw_address; + char lease[LEASE_SIZE]; +}; + +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 prefix { + struct in6_addr prefix; + int prefix_len; + uint32_t vltime; + uint32_t pltime; +}; + +struct imsg_req_dhcp { + uint32_t if_index; + int elapsed_time; + uint8_t xid[XID_SIZE]; + int serverid_len; + uint8_t serverid[SERVERID_SIZE]; + struct prefix pds[MAX_IA]; +}; + +/* dhcp6leased.c */ +void imsg_event_add(struct imsgev *); +int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, + pid_t, int, void *, uint16_t); +void config_clear(struct dhcp6leased_conf *); +struct dhcp6leased_conf *config_new_empty(void); +void merge_config(struct dhcp6leased_conf *, struct + dhcp6leased_conf *); +const char *sin6_to_str(struct sockaddr_in6 *); + +/* engine.c */ +const char *dhcp_message_type2str(uint8_t); + +/* frontend.c */ +struct iface_conf *find_iface_conf(struct iface_conf_head *, char *); +int *changed_ifaces(struct dhcp6leased_conf *, struct + dhcp6leased_conf *); +/* printconf.c */ +void print_config(struct dhcp6leased_conf *, int); + +/* parse.y */ +struct dhcp6leased_conf *parse_config(const char *); +int cmdline_symset(char *); + diff --git a/sbin/dhcp6leased/engine.c b/sbin/dhcp6leased/engine.c new file mode 100644 index 00000000000..56fd120cfbb --- /dev/null +++ b/sbin/dhcp6leased/engine.c @@ -0,0 +1,1464 @@ +/* $OpenBSD: engine.c,v 1.1 2024/06/02 12:28:05 florian Exp $ */ + +/* + * Copyright (c) 2017, 2021, 2024 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 +#include + +#include "log.h" +#include "dhcp6leased.h" +#include "engine.h" + +/* + * RFC 2131 4.1 p23 has "SHOULD be 4 seconds", we are a bit more aggressive, + * networks are faster these days. + */ +#define START_EXP_BACKOFF 1 +#define MAX_EXP_BACKOFF_SLOW 64 /* RFC 2131 4.1 p23 */ +#define MAX_EXP_BACKOFF_FAST 2 +#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", + "IPv6 only", +}; + +struct dhcp6leased_iface { + LIST_ENTRY(dhcp6leased_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; + uint8_t xid[XID_SIZE]; + int serverid_len; + uint8_t serverid[SERVERID_SIZE]; + struct prefix pds[MAX_IA]; + struct timespec request_time; + struct timespec elapsed_time_start; + uint32_t lease_time; + uint32_t t1; + uint32_t t2; +}; + +LIST_HEAD(, dhcp6leased_iface) dhcp6leased_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 *); +void send_interface_info(struct dhcp6leased_iface *, pid_t); +void engine_showinfo_ctl(struct imsg *, uint32_t); +void engine_update_iface(struct imsg_ifinfo *); +struct dhcp6leased_iface *get_dhcp6leased_iface_by_id(uint32_t); +void remove_dhcp6leased_iface(uint32_t); +void parse_dhcp(struct dhcp6leased_iface *, + struct imsg_dhcp *); +void parse_ia_pd_options(uint8_t *, size_t, struct prefix *); +void state_transition(struct dhcp6leased_iface *, enum + if_state); +void iface_timeout(int, short, void *); +void request_dhcp_discover(struct dhcp6leased_iface *); +void request_dhcp_request(struct dhcp6leased_iface *); +void log_lease(struct dhcp6leased_iface *, int); +void configure_interfaces(struct dhcp6leased_iface *); +void send_configure_interface(struct iface_pd_conf *, + struct prefix *); +void send_deconfigure_interface(struct dhcp6leased_iface *); +void parse_lease(struct dhcp6leased_iface *, + struct imsg_ifinfo *); +int engine_imsg_compose_main(int, pid_t, void *, uint16_t); +const char *dhcp_message_type2str(uint8_t); +const char *dhcp_option_type2str(uint16_t); +const char *dhcp_duid2str(int, uint8_t *); +const char *dhcp_status2str(uint8_t); +void in6_prefixlen2mask(struct in6_addr *, int len); + +struct dhcp6leased_conf *engine_conf; + +static struct imsgev *iev_frontend; +static struct imsgev *iev_main; +int64_t proposal_id; +static struct dhcp_duid duid; + +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; + + engine_conf = config_new_empty(); + + log_init(debug, LOG_DAEMON); + log_setverbose(verbose); + + if ((pw = getpwnam(DHCP6LEASED_USER)) == NULL) + fatal("getpwnam"); + + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + + if (unveil("/", "") == -1) + fatal("unveil /"); + if (unveil(NULL, NULL) == -1) + fatal("unveil"); + + 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(&dhcp6leased_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 dhcp6leased_iface *iface; + ssize_t n; + int shut = 0; + int verbose; + 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) { + 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_REQUEST_REBOOT: + 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_dhcp6leased_iface_by_id(if_index); + if (iface != NULL) { + switch (iface->state) { + case IF_DOWN: + break; + case IF_INIT: + case IF_REQUESTING: + state_transition(iface, iface->state); + break; + case IF_RENEWING: + case IF_REBINDING: + case IF_REBOOTING: + case IF_BOUND: + state_transition(iface, IF_REBOOTING); + break; + } + } + break; + 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_dhcp6leased_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_dhcp6leased_iface_by_id(imsg_dhcp.if_index); + if (iface != NULL) + parse_dhcp(iface, &imsg_dhcp); + 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) +{ + static struct dhcp6leased_conf *nconf; + static struct iface_conf *iface_conf; + static struct iface_ia_conf *iface_ia_conf; + struct iface_pd_conf *iface_pd_conf; + 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_get_fd(&imsg)) == -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); + + if (pledge("stdio", NULL) == -1) + fatal("pledge"); + + break; + case IMSG_UUID: + if (IMSG_DATA_SIZE(imsg) != sizeof(duid.uuid)) + fatalx("%s: IMSG_UUID wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + duid.type = htons(DUID_UUID_TYPE); + memcpy(duid.uuid, imsg.data, sizeof(duid.uuid)); + 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)); + if (imsg_ifinfo.lease[LEASE_SIZE - 1] != '\0') + fatalx("Invalid lease"); + engine_update_iface(&imsg_ifinfo); + break; + case IMSG_RECONF_CONF: + if (nconf != NULL) + fatalx("%s: IMSG_RECONF_CONF already in " + "progress", __func__); + if ((nconf = malloc(sizeof(struct dhcp6leased_conf))) == + NULL) + fatal(NULL); + SIMPLEQ_INIT(&nconf->iface_list); + break; + case IMSG_RECONF_IFACE: + if (IMSG_DATA_SIZE(imsg) != sizeof(struct + iface_conf)) + fatalx("%s: IMSG_RECONF_IFACE wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + if ((iface_conf = malloc(sizeof(struct iface_conf))) + == NULL) + fatal(NULL); + memcpy(iface_conf, imsg.data, sizeof(struct + iface_conf)); + SIMPLEQ_INIT(&iface_conf->iface_ia_list); + SIMPLEQ_INSERT_TAIL(&nconf->iface_list, + iface_conf, entry); + iface_conf->ia_count = 0; + break; + case IMSG_RECONF_IFACE_IA: + if (IMSG_DATA_SIZE(imsg) != sizeof(struct + iface_ia_conf)) + fatalx("%s: IMSG_RECONF_IFACE_IA wrong " + "length: %lu", __func__, + IMSG_DATA_SIZE(imsg)); + if ((iface_ia_conf = + malloc(sizeof(struct iface_ia_conf))) == NULL) + fatal(NULL); + memcpy(iface_ia_conf, imsg.data, sizeof(struct + iface_ia_conf)); + SIMPLEQ_INIT(&iface_ia_conf->iface_pd_list); + SIMPLEQ_INSERT_TAIL(&iface_conf->iface_ia_list, + iface_ia_conf, entry); + iface_conf->ia_count++; + if (iface_conf->ia_count > MAX_IA) + fatalx("Too many prefix delegation requests."); + break; + case IMSG_RECONF_IFACE_PD: + if (IMSG_DATA_SIZE(imsg) != sizeof(struct + iface_pd_conf)) + fatalx("%s: IMSG_RECONF_IFACE_PD wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + if ((iface_pd_conf = + malloc(sizeof(struct iface_pd_conf))) == NULL) + fatal(NULL); + memcpy(iface_pd_conf, imsg.data, sizeof(struct + iface_pd_conf)); + SIMPLEQ_INSERT_TAIL(&iface_ia_conf->iface_pd_list, + iface_pd_conf, entry); + break; + case IMSG_RECONF_IFACE_IA_END: + iface_ia_conf = NULL; + break; + case IMSG_RECONF_IFACE_END: + iface_conf = NULL; + break; + case IMSG_RECONF_END: { + struct dhcp6leased_iface *iface; + int *ifaces; + int i, if_index; + char *if_name; + char ifnamebuf[IF_NAMESIZE]; + + if (nconf == NULL) + fatalx("%s: IMSG_RECONF_END without " + "IMSG_RECONF_CONF", __func__); + ifaces = changed_ifaces(engine_conf, nconf); + merge_config(engine_conf, nconf); + nconf = NULL; + for (i = 0; ifaces[i] != 0; i++) { + if_index = ifaces[i]; + if_name = if_indextoname(if_index, ifnamebuf); + iface = get_dhcp6leased_iface_by_id(if_index); + if (if_name == NULL || iface == NULL) + continue; + iface_conf = find_iface_conf( + &engine_conf->iface_list, if_name); + if (iface_conf == NULL) + continue; + } + free(ifaces); + 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 +send_interface_info(struct dhcp6leased_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.lease_time = iface->lease_time; + cei.t1 = iface->t1; + cei.t2 = iface->t2; + 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 dhcp6leased_iface *iface; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_INTERFACE_INFO: + if ((iface = get_dhcp6leased_iface_by_id(if_index)) != NULL) + send_interface_info(iface, imsg->hdr.pid); + else + engine_imsg_compose_frontend(IMSG_CTL_END, + imsg->hdr.pid, NULL, 0); + break; + default: + log_debug("%s: error handling imsg", __func__); + break; + } +} + +void +engine_update_iface(struct imsg_ifinfo *imsg_ifinfo) +{ + struct dhcp6leased_iface *iface; + int need_refresh = 0; + + iface = get_dhcp6leased_iface_by_id(imsg_ifinfo->if_index); + + if (iface == NULL) { + if ((iface = calloc(1, sizeof(*iface))) == NULL) + fatal("calloc"); + iface->state = IF_DOWN; + arc4random_buf(iface->xid, sizeof(iface->xid)); + 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; + memcpy(&iface->hw_address, &imsg_ifinfo->hw_address, + sizeof(struct ether_addr)); + LIST_INSERT_HEAD(&dhcp6leased_interfaces, iface, entries); + need_refresh = 1; + } else { + 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 0 +XXXX + if (iface->requested_ip.s_addr == INADDR_ANY) + parse_lease(iface, imsg_ifinfo); + + if (iface->requested_ip.s_addr == INADDR_ANY) + state_transition(iface, IF_INIT); + else + state_transition(iface, IF_REBOOTING); +#endif + state_transition(iface, IF_INIT); + } else + state_transition(iface, IF_DOWN); +} +struct dhcp6leased_iface* +get_dhcp6leased_iface_by_id(uint32_t if_index) +{ + struct dhcp6leased_iface *iface; + LIST_FOREACH (iface, &dhcp6leased_interfaces, entries) { + if (iface->if_index == if_index) + return (iface); + } + + return (NULL); +} + +void +remove_dhcp6leased_iface(uint32_t if_index) +{ + struct dhcp6leased_iface *iface; + + iface = get_dhcp6leased_iface_by_id(if_index); + + if (iface == NULL) + return; + + send_deconfigure_interface(iface); + LIST_REMOVE(iface, entries); + evtimer_del(&iface->timer); + free(iface); +} + +void +parse_dhcp(struct dhcp6leased_iface *iface, struct imsg_dhcp *dhcp) +{ + struct iface_conf *iface_conf; + struct iface_ia_conf *ia_conf; + struct dhcp_hdr hdr; + struct dhcp_option_hdr opt_hdr; + struct dhcp_iapd iapd; + struct prefix *pds = NULL; + size_t rem; + uint32_t t1, t2; + int serverid_len; + uint8_t serverid[SERVERID_SIZE]; + uint8_t *p; + char ifnamebuf[IF_NAMESIZE], *if_name; + char ntopbuf[INET6_ADDRSTRLEN]; + + if ((if_name = if_indextoname(iface->if_index, ifnamebuf)) == NULL) { + log_debug("%s: unknown interface %d", __func__, + iface->if_index); + goto out; + } + if ((iface_conf = find_iface_conf(&engine_conf->iface_list, if_name)) + == NULL) { + log_debug("%s: no interface configuration for %d", __func__, + iface->if_index); + goto out; + } + + log_debug("%s: %s ia_count: %d", __func__, if_name, + iface_conf->ia_count); + + pds = calloc(iface_conf->ia_count, sizeof(struct prefix)); + if (pds == NULL) + fatal("%s: calloc", __func__); + + serverid_len = t1 = t2 = 0; + + p = dhcp->packet; + rem = dhcp->len; + + if (rem < sizeof(struct dhcp_hdr)) { + log_warnx("%s: message too short", __func__); + goto out; + } + memcpy(&hdr, p, sizeof(struct dhcp_hdr)); + p += sizeof(struct dhcp_hdr); + rem -= sizeof(struct dhcp_hdr); + + if (log_getverbose() > 1) + log_debug("%s: %s, xid: 0x%02x%02x%02x", __func__, + dhcp_message_type2str(hdr.msg_type), hdr.xid[0], hdr.xid[1], + hdr.xid[2]); + + while(rem >= sizeof(struct dhcp_option_hdr)) { + memcpy(&opt_hdr, p, sizeof(struct dhcp_option_hdr)); + opt_hdr.code = ntohs(opt_hdr.code); + opt_hdr.len = ntohs(opt_hdr.len); + p += sizeof(struct dhcp_option_hdr); + rem -= sizeof(struct dhcp_option_hdr); + if (log_getverbose() > 1) + log_debug("%s: %s, len: %u", __func__, + dhcp_option_type2str(opt_hdr.code), opt_hdr.len); + + if (rem < opt_hdr.len) { + log_warnx("%s: malformed packet, ignoring", __func__); + goto out; + } + + switch (opt_hdr.code) { + case DHO_CLIENTID: + if (opt_hdr.len != sizeof(struct dhcp_duid) || + memcmp(&duid, p, sizeof(struct dhcp_duid)) != 0) { + log_debug("%s: message not for us", __func__); + goto out; + } + break; + case DHO_SERVERID: + /* + * RFC 8415, 11.1: + * The length of the DUID (not including the type code) + * is at least 1 octet and at most 128 octets. + */ + if (opt_hdr.len < 2 + 1) { + log_warnx("%s: SERVERID too short", __func__); + goto out; + } + if (opt_hdr.len > SERVERID_SIZE) { + log_warnx("%s: SERVERID too long", __func__); + goto out; + } + log_debug("%s: SERVERID: %s", __func__, + dhcp_duid2str(opt_hdr.len, p)); + if (serverid_len != 0) { + log_warnx("%s: duplicate SERVERID option", + __func__); + goto out; + } + serverid_len = opt_hdr.len; + memcpy(serverid, p, serverid_len); + break; + case DHO_IA_PD: + if (opt_hdr.len < sizeof(struct dhcp_iapd)) { + log_warnx("%s: IA_PD too short", __func__); + goto out; + } + memcpy(&iapd, p, sizeof(struct dhcp_iapd)); + + if (t1 == 0 || t1 > ntohl(iapd.t1)) + t1 = ntohl(iapd.t1); + if (t2 == 0 || t2 > ntohl(iapd.t2)) + t2 = ntohl(iapd.t2); + + log_debug("%s: IA_PD, IAID: %08x, T1: %u, T2: %u", + __func__, ntohl(iapd.iaid), ntohl(iapd.t1), + ntohl(iapd.t2)); + if (ntohl(iapd.iaid) <= iface_conf->ia_count) + parse_ia_pd_options(p + + sizeof(struct dhcp_iapd), opt_hdr.len - + sizeof(struct dhcp_iapd), + &pds[ntohl(iapd.iaid) -1]); + break; + default: + log_debug("unhandled option: %u", opt_hdr.code); + break; + } + + p += opt_hdr.len; + rem -= opt_hdr.len; + } + + /* check that we got all the information we need */ + if (serverid_len == 0) { + log_warnx("%s: Did not receive server identifier", __func__); + goto out; + } + + + SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list, entry) { + struct prefix *pd = &pds[ia_conf->id - 1]; + + if(pd->prefix_len == 0) { + log_warnx("%s: no IA for IAID %d found", __func__, + ia_conf->id); + goto out; + } + if(pd->prefix_len > ia_conf->prefix_len) { + log_warnx("%s: prefix for IAID %d too small: %d > %d", + __func__, ia_conf->id, pd->prefix_len, + ia_conf->prefix_len); + goto out; + } + log_debug("%s: pltime: %u, vltime: %u, prefix: %s/%u", + __func__, pd->pltime, pd->vltime, inet_ntop(AF_INET6, + &pd->prefix, ntopbuf, INET6_ADDRSTRLEN), pd->prefix_len); + } + + switch (hdr.msg_type) { + case DHCPSOLICIT: + case DHCPREQUEST: + case DHCPCONFIRM: + case DHCPRENEW: + case DHCPREBIND: + case DHCPRELEASE: + case DHCPDECLINE: + case DHCPINFORMATIONREQUEST: + log_warnx("%s: Ignoring client-only message (%s) from server", + __func__, dhcp_message_type2str(hdr.msg_type)); + goto out; + case DHCPRELAYFORW: + case DHCPRELAYREPL: + log_warnx("%s: Ignoring relay-agent-only message (%s) from " + "server", __func__, dhcp_message_type2str(hdr.msg_type)); + goto out; + case DHCPADVERTISE: + if (iface->state != IF_INIT) { + log_debug("%s: ignoring unexpected %s", __func__, + dhcp_message_type2str(hdr.msg_type)); + goto out; + } + iface->serverid_len = serverid_len; + memcpy(iface->serverid, serverid, SERVERID_SIZE); + memset(iface->pds, 0, sizeof(iface->pds)); + memcpy(iface->pds, pds, + iface_conf->ia_count * sizeof(struct prefix)); + state_transition(iface, IF_REQUESTING); + break; + case DHCPREPLY: + /* XXX rapid commit, rebinding, renewing */ + if (iface->state != IF_REQUESTING) { + log_debug("%s: ignoring unexpected %s", __func__, + dhcp_message_type2str(hdr.msg_type)); + goto out; + } + iface->serverid_len = serverid_len; + memcpy(iface->serverid, serverid, SERVERID_SIZE); + memset(iface->pds, 0, sizeof(iface->pds)); + memcpy(iface->pds, pds, + iface_conf->ia_count * sizeof(struct prefix)); + /* XXX handle t1 = 0 or t2 = 0 */ + iface->t1 = t1; + iface->t2 = t2; + state_transition(iface, IF_BOUND); + break; + case DHCPRECONFIGURE: + log_warnx("%s: Ignoring %s from server", + __func__, dhcp_message_type2str(hdr.msg_type)); + goto out; + default: + fatalx("%s: %s unhandled", + __func__, dhcp_message_type2str(hdr.msg_type)); + break; + } + out: + free(pds); +} + +void +parse_ia_pd_options(uint8_t *p, size_t len, struct prefix *prefix) +{ + struct dhcp_option_hdr opt_hdr; + struct dhcp_iaprefix iaprefix; + struct in6_addr mask; + int i; + uint16_t status_code; + char ntopbuf[INET6_ADDRSTRLEN], *visbuf; + + while(len >= sizeof(struct dhcp_option_hdr)) { + memcpy(&opt_hdr, p, sizeof(struct dhcp_option_hdr)); + opt_hdr.code = ntohs(opt_hdr.code); + opt_hdr.len = ntohs(opt_hdr.len); + p += sizeof(struct dhcp_option_hdr); + len -= sizeof(struct dhcp_option_hdr); + if (log_getverbose() > 1) + log_debug("%s: %s, len: %u", __func__, + dhcp_option_type2str(opt_hdr.code), opt_hdr.len); + if (len < opt_hdr.len) { + log_warnx("%s: malformed packet, ignoring", __func__); + return; + } + + switch (opt_hdr.code) { + case DHO_IA_PREFIX: + if (len < sizeof(struct dhcp_iaprefix)) { + log_warnx("%s: malformed packet, ignoring", + __func__); + return; + } + + memcpy(&iaprefix, p, sizeof(struct dhcp_iaprefix)); + log_debug("%s: pltime: %u, vltime: %u, prefix: %s/%u", + __func__, ntohl(iaprefix.pltime), + ntohl(iaprefix.vltime), inet_ntop(AF_INET6, + &iaprefix.prefix, ntopbuf, INET6_ADDRSTRLEN), + iaprefix.prefix_len); + if (ntohl(iaprefix.vltime) < ntohl(iaprefix.pltime)) { + log_warnx("%s: vltime < pltime, ignoring IA_PD", + __func__); + break; + } + + prefix->prefix = iaprefix.prefix; + prefix->prefix_len = iaprefix.prefix_len; + prefix->vltime = ntohl(iaprefix.vltime); + prefix->pltime = ntohl(iaprefix.pltime); + + /* make sure prefix is mask correctly */ + memset(&mask, 0, sizeof(mask)); + in6_prefixlen2mask(&mask, prefix->prefix_len); + for (i = 0; i < 16; i++) + prefix->prefix.s6_addr[i] &= mask.s6_addr[i]; + + break; + case DHO_STATUS_CODE: + /* + * XXX handle STATUS_CODE if not success + * STATUS_CODE can also appear in other parts of + * the packet. + */ + if (len < 2) { + log_warnx("%s: malformed packet, ignoring", + __func__); + return; + } + memcpy(&status_code, p, sizeof(uint16_t)); + status_code = ntohs(status_code); + visbuf = calloc(4, len - 2); + strvisx(visbuf, p + 2, len - 2, VIS_SAFE); + log_debug("%s: %s - %s", __func__, + dhcp_status2str(status_code), visbuf); + break; + default: + log_debug("unhandled option: %u", opt_hdr.code); + } + p += opt_hdr.len; + len -= opt_hdr.len; + } +} + +/* XXX check valid transitions */ +void +state_transition(struct dhcp6leased_iface *iface, enum if_state new_state) +{ + enum if_state old_state = iface->state; + char ifnamebuf[IF_NAMESIZE], *if_name; + + iface->state = new_state; + + switch (new_state) { + case IF_DOWN: +#if 0 +XXXX + 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) { + /* nameservers already withdrawn when if went down */ + send_deconfigure_interface(iface); + /* nothing more to do until iface comes back */ + iface->timo.tv_sec = -1; + } else { + 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 */ + } +#endif + /* nothing to do until iface comes up */ + iface->timo.tv_sec = -1; + break; + case IF_INIT: + switch (old_state) { + case IF_INIT: + if (iface->timo.tv_sec < MAX_EXP_BACKOFF_SLOW) + iface->timo.tv_sec *= 2; + break; + case IF_REQUESTING: + case IF_RENEWING: + case IF_REBINDING: + case IF_REBOOTING: + /* lease expired, got DHCPNAK or timeout: delete IP */ + send_deconfigure_interface(iface); + /* fall through */ + case IF_DOWN: + iface->timo.tv_sec = START_EXP_BACKOFF; + clock_gettime(CLOCK_MONOTONIC, + &iface->elapsed_time_start); + break; + case IF_BOUND: + fatal("invalid transition Bound -> Init"); + break; + } + request_dhcp_discover(iface); + break; + case IF_REBOOTING: + if (old_state == IF_REBOOTING) + iface->timo.tv_sec *= 2; + else { + iface->timo.tv_sec = START_EXP_BACKOFF; + arc4random_buf(iface->xid, sizeof(iface->xid)); + } + request_dhcp_request(iface); + break; + case IF_REQUESTING: + if (old_state == IF_REQUESTING) + iface->timo.tv_sec *= 2; + else { + iface->timo.tv_sec = START_EXP_BACKOFF; + clock_gettime(CLOCK_MONOTONIC, + &iface->elapsed_time_start); + } + request_dhcp_request(iface); + break; + case IF_BOUND: + iface->timo.tv_sec = iface->t1; + if (old_state == IF_REQUESTING || old_state == IF_REBOOTING) { + configure_interfaces(iface); + } + break; + case IF_RENEWING: + if (old_state == IF_BOUND) { + iface->timo.tv_sec = (iface->t2 - + iface->t1) / 2; /* RFC 2131 4.4.5 */ + arc4random_buf(iface->xid, sizeof(iface->xid)); + } 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->timo.tv_sec = (iface->lease_time - + iface->t2) / 2; /* RFC 2131 4.4.5 */ + } else + iface->timo.tv_sec /= 2; + request_dhcp_request(iface); + break; + } + + if_name = if_indextoname(iface->if_index, ifnamebuf); + log_debug("%s[%s] %s -> %s, timo: %lld", __func__, if_name == NULL ? + "?" : if_name, 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 dhcp6leased_iface *iface = (struct dhcp6leased_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 >= MAX_EXP_BACKOFF_FAST) + state_transition(iface, IF_INIT); + else + state_transition(iface, IF_REBOOTING); + break; + case IF_REQUESTING: + if (iface->timo.tv_sec >= MAX_EXP_BACKOFF_SLOW) + 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, t2: %u", __func__, + res.tv_sec, iface->t2); + if (res.tv_sec > iface->t2) + 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; + } +} + +void +request_dhcp_discover(struct dhcp6leased_iface *iface) +{ + struct imsg_req_dhcp imsg; + struct timespec now, res; + + memset(&imsg, 0, sizeof(imsg)); + imsg.if_index = iface->if_index; + memcpy(imsg.xid, iface->xid, sizeof(imsg.xid)); + clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, &iface->elapsed_time_start, &res); + if (res.tv_sec * 100 > 0xffff) + imsg.elapsed_time = 0xffff; + else + imsg.elapsed_time = res.tv_sec * 100; + engine_imsg_compose_frontend(IMSG_SEND_DISCOVER, 0, &imsg, sizeof(imsg)); +} + +void +request_dhcp_request(struct dhcp6leased_iface *iface) +{ + struct imsg_req_dhcp imsg; + struct timespec now, res; + + memset(&imsg, 0, sizeof(imsg)); + imsg.if_index = iface->if_index; + memcpy(imsg.xid, iface->xid, sizeof(imsg.xid)); + + clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, &iface->elapsed_time_start, &res); + if (res.tv_sec * 100 > 0xffff) + imsg.elapsed_time = 0xffff; + else + imsg.elapsed_time = res.tv_sec * 100; + + switch (iface->state) { + case IF_DOWN: + fatalx("invalid state IF_DOWN in %s", __func__); + break; + case IF_INIT: + fatalx("invalid state IF_INIT in %s", __func__); + break; + case IF_BOUND: + fatalx("invalid state IF_BOUND in %s", __func__); + break; + case IF_REBOOTING: + fatalx("XXX state IF_REBOOTING in %s not IMPL", __func__); + break; + case IF_REQUESTING: + imsg.serverid_len = iface->serverid_len; + memcpy(imsg.serverid, iface->serverid, SERVERID_SIZE); + memcpy(imsg.pds, iface->pds, sizeof(iface->pds)); + break; + case IF_RENEWING: + fatalx("XXX state IF_RENEWING in %s not IMPL", __func__); + break; + case IF_REBINDING: + fatalx("XXX state IF_REBINDING in %s not IMPL", __func__); + break; + } + + engine_imsg_compose_frontend(IMSG_SEND_REQUEST, 0, &imsg, sizeof(imsg)); +} + +void +log_lease(struct dhcp6leased_iface *iface, int deconfigure) +{ + fatalx("%s: not implemented", __func__); /* XXX */ +} + +/* XXX we need to install a reject route for the delegated prefix */ +void +configure_interfaces(struct dhcp6leased_iface *iface) +{ + struct iface_conf *iface_conf; + struct iface_ia_conf *ia_conf; + struct iface_pd_conf *pd_conf; + char ifnamebuf[IF_NAMESIZE], *if_name; + + + if ((if_name = if_indextoname(iface->if_index, ifnamebuf)) == NULL) { + log_debug("%s: unknown interface %d", __func__, + iface->if_index); + return; + } + if ((iface_conf = find_iface_conf(&engine_conf->iface_list, if_name)) + == NULL) { + log_debug("%s: no interface configuration for %d", __func__, + iface->if_index); + return; + } + + SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list, entry) { + struct prefix *pd = &iface->pds[ia_conf->id - 1]; + + SIMPLEQ_FOREACH(pd_conf, &ia_conf->iface_pd_list, entry) { + send_configure_interface(pd_conf, pd); + } + } +} + +void +send_configure_interface(struct iface_pd_conf *pd_conf, struct prefix *pd) +{ + struct imsg_configure_address address; + uint32_t if_index; + int i; + char ntopbuf[INET6_ADDRSTRLEN]; + + if (strcmp(pd_conf->name, "reserve") == 0) + return; + + if ((if_index = if_nametoindex(pd_conf->name)) == 0) + return; + + memset(&address, 0, sizeof(address)); + + address.if_index = if_index; + address.addr.sin6_family = AF_INET6; + address.addr.sin6_len = sizeof(address.addr); + address.addr.sin6_addr = pd->prefix; + + for (i = 0; i < 16; i++) + address.addr.sin6_addr.s6_addr[i] |= + pd_conf->prefix_mask.s6_addr[i]; + + /* XXX make this configurable & use SOII */ + address.addr.sin6_addr.s6_addr[15] |= 1; + + in6_prefixlen2mask(&address.mask, pd_conf->prefix_len); + + log_debug("%s: %s: %s/%d", __func__, pd_conf->name, + inet_ntop(AF_INET6, &address.addr.sin6_addr, ntopbuf, + INET6_ADDRSTRLEN), pd_conf->prefix_len); + + address.vltime = pd->vltime; + address.pltime = pd->pltime; + + engine_imsg_compose_main(IMSG_CONFIGURE_ADDRESS, 0, &address, + sizeof(address)); +} + +void +send_deconfigure_interface(struct dhcp6leased_iface *iface) +{ + fatalx("%s: not implemented", __func__); /* XXX */ +} + +void +parse_lease(struct dhcp6leased_iface *iface, struct imsg_ifinfo *imsg_ifinfo) +{ + fatalx("%s: not implemented", __func__); /* XXX */ +} + +const char * +dhcp_message_type2str(uint8_t type) +{ + static char buf[sizeof("Unknown [255]")]; + + switch (type) { + case DHCPSOLICIT: + return "DHCPSOLICIT"; + case DHCPADVERTISE: + return "DHCPADVERTISE"; + case DHCPREQUEST: + return "DHCPREQUEST"; + case DHCPCONFIRM: + return "DHCPCONFIRM"; + case DHCPRENEW: + return "DHCPRENEW"; + case DHCPREBIND: + return "DHCPREBIND"; + case DHCPREPLY: + return "DHCPREPLY"; + case DHCPRELEASE: + return "DHCPRELEASE"; + case DHCPDECLINE: + return "DHCPDECLINE"; + case DHCPRECONFIGURE: + return "DHCPRECONFIGURE"; + case DHCPINFORMATIONREQUEST: + return "DHCPINFORMATIONREQUEST"; + case DHCPRELAYFORW: + return "DHCPRELAYFORW"; + case DHCPRELAYREPL: + return "DHCPRELAYREPL"; + default: + snprintf(buf, sizeof(buf), "Unknown [%u]", type); + return buf; + } +} + +const char * +dhcp_option_type2str(uint16_t code) +{ + static char buf[sizeof("Unknown [65535]")]; + switch (code) { + case DHO_CLIENTID: + return "DHO_CLIENTID"; + case DHO_SERVERID: + return "DHO_SERVERID"; + case DHO_ORO: + return "DHO_ORO"; + case DHO_ELAPSED_TIME: + return "DHO_ELAPSED_TIME"; + case DHO_STATUS_CODE: + return "DHO_STATUS_CODE"; + case DHO_RAPID_COMMIT: + return "DHO_RAPID_COMMIT"; + case DHO_VENDOR_CLASS: + return "DHO_VENDOR_CLASS"; + case DHO_IA_PD: + return "DHO_IA_PD"; + case DHO_IA_PREFIX: + return "DHO_IA_PREFIX"; + case DHO_SOL_MAX_RT: + return "DHO_SOL_MAX_RT"; + case DHO_INF_MAX_RT: + return "DHO_INF_MAX_RT"; + default: + snprintf(buf, sizeof(buf), "Unknown [%u]", code); + return buf; + } +} + +const char* +dhcp_duid2str(int len, uint8_t *p) +{ + static char buf[2 * 130]; + int i, rem; + char *pbuf; + + if (len > 130) + return "invalid"; + + pbuf = buf; + rem = sizeof(buf); + for (i = 0; i < len && rem > 0; i++, pbuf += 2, rem -=2) + snprintf(pbuf, rem, "%02x", p[i]); + + return buf; +} + +const char* +dhcp_status2str(uint8_t status) +{ + static char buf[sizeof("Unknown [255]")]; + + switch (status) { + case DHCP_STATUS_SUCCESS: + return "Success"; + case DHCP_STATUS_UNSPECFAIL: + return "UnspecFail"; + case DHCP_STATUS_NOADDRSAVAIL: + return "NoAddrsAvail"; + case DHCP_STATUS_NOBINDING: + return "NoBinding"; + case DHCP_STATUS_NOTONLINK: + return "NotOnLink"; + case DHCP_STATUS_USEMULTICAST: + return "UseMulticast"; + case DHCP_STATUS_NOPREFIXAVAIL: + return "NoPrefixAvail"; + default: + snprintf(buf, sizeof(buf), "Unknown [%u]", status); + return buf; + } +} + +/* from sys/netinet6/in6.c */ +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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 project 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 PROJECT 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 PROJECT 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. + */ +void +in6_prefixlen2mask(struct in6_addr *maskp, int len) +{ + u_char maskarray[8] = {0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff}; + int bytelen, bitlen, i; + + if (0 > len || len > 128) + fatalx("%s: invalid prefix length(%d)\n", __func__, len); + + bzero(maskp, sizeof(*maskp)); + bytelen = len / 8; + bitlen = len % 8; + for (i = 0; i < bytelen; i++) + maskp->s6_addr[i] = 0xff; + /* len == 128 is ok because bitlen == 0 then */ + if (bitlen) + maskp->s6_addr[bytelen] = maskarray[bitlen - 1]; +} diff --git a/sbin/dhcp6leased/engine.h b/sbin/dhcp6leased/engine.h new file mode 100644 index 00000000000..61c0fd15f7a --- /dev/null +++ b/sbin/dhcp6leased/engine.h @@ -0,0 +1,28 @@ +/* $OpenBSD: engine.h,v 1.1 2024/06/02 12:28:05 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_address { + uint32_t if_index; + struct sockaddr_in6 addr; + struct in6_addr mask; + uint32_t vltime; + uint32_t pltime; +}; + +void engine(int, int); +int engine_imsg_compose_frontend(int, pid_t, void *, uint16_t); diff --git a/sbin/dhcp6leased/frontend.c b/sbin/dhcp6leased/frontend.c new file mode 100644 index 00000000000..5e2e54c46fb --- /dev/null +++ b/sbin/dhcp6leased/frontend.c @@ -0,0 +1,1103 @@ +/* $OpenBSD: frontend.c,v 1.1 2024/06/02 12:28:05 florian Exp $ */ + +/* + * Copyright (c) 2017, 2021, 2024 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 "log.h" +#include "dhcp6leased.h" +#include "frontend.h" +#include "control.h" + +#define ALL_DHCP_RELAY_AGENTS_AND_SERVERS "ff02::1:2" +#define ROUTE_SOCKET_BUF_SIZE 16384 + +struct iface { + LIST_ENTRY(iface) entries; + struct event udpev; + struct imsg_ifinfo ifinfo; + int send_solicit; + int elapsed_time; + uint8_t xid[XID_SIZE]; + int serverid_len; + uint8_t serverid[SERVERID_SIZE]; + struct prefix pds[MAX_IA]; +}; + +__dead void frontend_shutdown(void); +void frontend_sig_handler(int, short, void *); +void rtsock_update_iface(struct if_msghdr *, struct sockaddr_dl *); +void frontend_startup(void); +void update_iface(uint32_t); +void route_receive(int, short, void *); +void handle_route_message(struct rt_msghdr *, struct sockaddr **); +void get_rtaddrs(int, struct sockaddr *, struct sockaddr **); +void udp_receive(int, short, void *); +int get_flags(char *); +struct iface *get_iface_by_id(uint32_t); +struct iface *get_iface_by_name(const char *); +void remove_iface(uint32_t); +void set_udpsock(int, uint32_t); +void iface_data_from_imsg(struct iface*, struct imsg_req_dhcp *); +ssize_t build_packet(uint8_t, struct iface *, char *); +void send_packet(uint8_t, struct iface *); +void udp_send_packet(struct iface *, uint8_t *, ssize_t); +int iface_conf_cmp(struct iface_conf *, struct iface_conf *); + +LIST_HEAD(, iface) interfaces; +struct dhcp6leased_conf *frontend_conf; +static struct imsgev *iev_main; +static struct imsgev *iev_engine; +struct event ev_route; +int ioctlsock; + +uint8_t dhcp_packet[1500]; +static struct dhcp_duid duid; +char *vendor_class_data; +int vendor_class_len; + +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; + struct utsname utsname; + + frontend_conf = config_new_empty(); + + log_init(debug, LOG_DAEMON); + log_setverbose(verbose); + + if ((pw = getpwnam(DHCP6LEASED_USER)) == NULL) + fatal("getpwnam"); + + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + + if (unveil("/", "") == -1) + fatal("unveil /"); + if (unveil(NULL, NULL) == -1) + fatal("unveil"); + + 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"); + + if (uname(&utsname) == -1) + fatal("uname"); + vendor_class_len = asprintf(&vendor_class_data, "%s %s %s", + utsname.sysname, utsname.release, utsname.machine); + if (vendor_class_len == -1) + fatal("Cannot generate vendor-class-data"); + + 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); + + config_clear(frontend_conf); + + 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) +{ + static struct dhcp6leased_conf *nconf; + static struct iface_conf *iface_conf; + static struct iface_ia_conf *iface_ia_conf; + struct iface_pd_conf *iface_pd_conf; + struct imsg imsg; + struct imsgev *iev = bula; + struct imsgbuf *ibuf = &iev->ibuf; + ssize_t n; + int shut = 0, udpsock, 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) { + 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_get_fd(&imsg)) == -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_UDPSOCK: + if ((udpsock = imsg_get_fd(&imsg)) == -1) + fatalx("%s: expected to receive imsg " + "udp 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)); + set_udpsock(udpsock, if_index); + break; + case IMSG_ROUTESOCK: + if ((fd = imsg_get_fd(&imsg)) == -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_UUID: + if (IMSG_DATA_SIZE(imsg) != sizeof(duid.uuid)) + fatalx("%s: IMSG_UUID wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + duid.type = htons(DUID_UUID_TYPE); + memcpy(duid.uuid, imsg.data, sizeof(duid.uuid)); + break; + case IMSG_STARTUP: + frontend_startup(); + break; + case IMSG_RECONF_CONF: + if (nconf != NULL) + fatalx("%s: IMSG_RECONF_CONF already in " + "progress", __func__); + if ((nconf = malloc(sizeof(struct dhcp6leased_conf))) == + NULL) + fatal(NULL); + SIMPLEQ_INIT(&nconf->iface_list); + break; + case IMSG_RECONF_IFACE: + if (IMSG_DATA_SIZE(imsg) != sizeof(struct + iface_conf)) + fatalx("%s: IMSG_RECONF_IFACE wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + if ((iface_conf = malloc(sizeof(struct iface_conf))) + == NULL) + fatal(NULL); + memcpy(iface_conf, imsg.data, sizeof(struct + iface_conf)); + SIMPLEQ_INIT(&iface_conf->iface_ia_list); + SIMPLEQ_INSERT_TAIL(&nconf->iface_list, + iface_conf, entry); + iface_conf->ia_count = 0; + break; + case IMSG_RECONF_IFACE_IA: + if (IMSG_DATA_SIZE(imsg) != sizeof(struct + iface_ia_conf)) + fatalx("%s: IMSG_RECONF_IFACE_IA wrong " + "length: %lu", __func__, + IMSG_DATA_SIZE(imsg)); + if ((iface_ia_conf = + malloc(sizeof(struct iface_ia_conf))) == NULL) + fatal(NULL); + memcpy(iface_ia_conf, imsg.data, sizeof(struct + iface_ia_conf)); + SIMPLEQ_INIT(&iface_ia_conf->iface_pd_list); + SIMPLEQ_INSERT_TAIL(&iface_conf->iface_ia_list, + iface_ia_conf, entry); + iface_conf->ia_count++; + if (iface_conf->ia_count > MAX_IA) + fatalx("Too many prefix delegation requests."); + break; + case IMSG_RECONF_IFACE_PD: + if (IMSG_DATA_SIZE(imsg) != sizeof(struct + iface_pd_conf)) + fatalx("%s: IMSG_RECONF_IFACE_PD wrong length: " + "%lu", __func__, IMSG_DATA_SIZE(imsg)); + if ((iface_pd_conf = + malloc(sizeof(struct iface_pd_conf))) == NULL) + fatal(NULL); + memcpy(iface_pd_conf, imsg.data, sizeof(struct + iface_pd_conf)); + SIMPLEQ_INSERT_TAIL(&iface_ia_conf->iface_pd_list, + iface_pd_conf, entry); + break; + case IMSG_RECONF_IFACE_IA_END: + iface_ia_conf = NULL; + break; + case IMSG_RECONF_IFACE_END: + iface_conf = NULL; + break; + case IMSG_RECONF_END: { + int i; + int *ifaces; + char ifnamebuf[IF_NAMESIZE], *if_name; + + if (nconf == NULL) + fatalx("%s: IMSG_RECONF_END without " + "IMSG_RECONF_CONF", __func__); + + ifaces = changed_ifaces(frontend_conf, nconf); + merge_config(frontend_conf, nconf); + nconf = NULL; + for (i = 0; ifaces[i] != 0; i++) { + if_index = ifaces[i]; + if_name = if_indextoname(if_index, ifnamebuf); + log_debug("changed iface: %s[%d]", if_name != + NULL ? if_name : "", if_index); + update_iface(if_index); + frontend_imsg_compose_engine( + IMSG_REQUEST_REBOOT, 0, 0, &if_index, + sizeof(if_index)); + } + free(ifaces); + break; + } + case IMSG_CONTROLFD: + if ((fd = imsg_get_fd(&imsg)) == -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; + 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) { + case IMSG_CTL_END: + case IMSG_CTL_SHOW_INTERFACE_INFO: + control_imsg_relay(&imsg); + break; + case IMSG_SEND_DISCOVER: { + struct imsg_req_dhcp imsg_req_dhcp; + if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_req_dhcp)) + fatalx("%s: IMSG_SEND_DISCOVER wrong " + "length: %lu", __func__, + IMSG_DATA_SIZE(imsg)); + memcpy(&imsg_req_dhcp, imsg.data, + sizeof(imsg_req_dhcp)); + + iface = get_iface_by_id(imsg_req_dhcp.if_index); + + if (iface == NULL) + break; + + iface_data_from_imsg(iface, &imsg_req_dhcp); + send_packet(DHCPSOLICIT, iface); + break; + } + case IMSG_SEND_REQUEST: { + struct imsg_req_dhcp imsg_req_dhcp; + if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_req_dhcp)) + fatalx("%s: IMSG_SEND_REQUEST wrong " + "length: %lu", __func__, + IMSG_DATA_SIZE(imsg)); + memcpy(&imsg_req_dhcp, imsg.data, + sizeof(imsg_req_dhcp)); + + iface = get_iface_by_id(imsg_req_dhcp.if_index); + + if (iface == NULL) + break; + + iface_data_from_imsg(iface, &imsg_req_dhcp); + send_packet(DHCPREQUEST, 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; +} + +void +update_iface(uint32_t if_index) +{ + struct ifaddrs *ifap, *ifa; + struct iface *iface; + struct imsg_ifinfo ifinfo; + int flags; + char ifnamebuf[IF_NAMESIZE], *if_name; + + if (getifaddrs(&ifap) != 0) + fatal("getifaddrs"); + + if ((if_name = if_indextoname(if_index, ifnamebuf)) == NULL) + return; + + if ((flags = get_flags(if_name)) == -1) + return; + + memset(&ifinfo, 0, sizeof(ifinfo)); + ifinfo.if_index = if_index; + ifinfo.link_state = -1; + ifinfo.running = (flags & (IFF_UP | IFF_RUNNING)) == + (IFF_UP | IFF_RUNNING); + + 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: { + struct if_data *if_data; + struct sockaddr_dl *sdl; + + sdl = (struct sockaddr_dl *)ifa->ifa_addr; + if ((sdl->sdl_type != IFT_ETHER && + sdl->sdl_type != IFT_CARP) || + sdl->sdl_alen != ETHER_ADDR_LEN) + continue; + memcpy(ifinfo.hw_address.ether_addr_octet, + LLADDR(sdl), ETHER_ADDR_LEN); + + if_data = (struct if_data *)ifa->ifa_data; + ifinfo.link_state = if_data->ifi_link_state; + ifinfo.rdomain = if_data->ifi_rdomain; + goto out; + } + default: + break; + } + } + out: + + iface = get_iface_by_id(if_index); + if (iface == NULL) { + if ((iface = calloc(1, sizeof(*iface))) == NULL) + fatal("calloc"); + //iface->send_solicit = 1; + memcpy(&iface->ifinfo, &ifinfo, sizeof(iface->ifinfo)); + LIST_INSERT_HEAD(&interfaces, iface, entries); + frontend_imsg_compose_main(IMSG_OPEN_UDPSOCK, 0, + &if_index, sizeof(if_index)); + } else + /* XXX check rdomain changed ?*/ + memcpy(&iface->ifinfo, &ifinfo, sizeof(iface->ifinfo)); + + frontend_imsg_compose_main(IMSG_UPDATE_IF, 0, &iface->ifinfo, + sizeof(iface->ifinfo)); +} + +void +rtsock_update_iface(struct if_msghdr *ifm, struct sockaddr_dl *sdl) +{ +#if 0 +XXX + struct iface *iface; + struct imsg_ifinfo ifinfo; + uint32_t if_index; + int flags; + char ifnamebuf[IF_NAMESIZE], *if_name; + + if_index = ifm->ifm_index; + + flags = ifm->ifm_flags; + + iface = get_iface_by_id(if_index); + if_name = if_indextoname(if_index, ifnamebuf); + + if (if_name == NULL) { + if (iface != NULL) { + log_debug("interface with idx %d removed", if_index); + frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0, + &if_index, sizeof(if_index)); + remove_iface(if_index); + } + return; + } + + memset(&ifinfo, 0, sizeof(ifinfo)); + ifinfo.if_index = if_index; + ifinfo.link_state = ifm->ifm_data.ifi_link_state; + ifinfo.rdomain = ifm->ifm_tableid; + ifinfo.running = (flags & (IFF_UP | IFF_RUNNING)) == + (IFF_UP | IFF_RUNNING); + + if (sdl != NULL && (sdl->sdl_type == IFT_ETHER || + sdl->sdl_type == IFT_CARP) && sdl->sdl_alen == ETHER_ADDR_LEN) + memcpy(ifinfo.hw_address.ether_addr_octet, LLADDR(sdl), + ETHER_ADDR_LEN); + else if (iface == NULL) { + log_warnx("Could not find AF_LINK address for %s.", if_name); + return; + } + + if (iface == NULL) { + if ((iface = calloc(1, sizeof(*iface))) == NULL) + fatal("calloc"); + iface->udpsock = -1; + LIST_INSERT_HEAD(&interfaces, iface, entries); + frontend_imsg_compose_main(IMSG_OPEN_UDPSOCK, 0, + &if_index, sizeof(if_index)); + } else { + if (iface->ifinfo.rdomain != ifinfo.rdomain && + iface->udpsock != -1) { + close(iface->udpsock); + iface->udpsock = -1; + } + } + + if (memcmp(&iface->ifinfo, &ifinfo, sizeof(iface->ifinfo)) != 0) { + memcpy(&iface->ifinfo, &ifinfo, sizeof(iface->ifinfo)); + frontend_imsg_compose_main(IMSG_UPDATE_IF, 0, &iface->ifinfo, + sizeof(iface->ifinfo)); + } +#endif +} + +void +frontend_startup(void) +{ + if (!event_initialized(&ev_route)) + fatalx("%s: did not receive a route socket from the main " + "process", __func__); + + if (pledge("stdio unix recvfd", NULL) == -1) + fatal("pledge"); + event_add(&ev_route, NULL); +} + +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 sockaddr_dl *sdl = NULL; + struct if_announcemsghdr *ifan; + uint32_t if_index; + + switch (rtm->rtm_type) { + case RTM_IFINFO: + if (rtm->rtm_addrs & RTA_IFP && rti_info[RTAX_IFP]->sa_family + == AF_LINK) + sdl = (struct sockaddr_dl *)rti_info[RTAX_IFP]; + rtsock_update_iface((struct if_msghdr *)rtm, sdl); + break; + case RTM_IFANNOUNCE: + ifan = (struct if_announcemsghdr *)rtm; + if_index = ifan->ifan_index; + if (ifan->ifan_what == IFAN_DEPARTURE) { + frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0, + &if_index, sizeof(if_index)); + remove_iface(if_index); + } + 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 +udp_receive(int fd, short events, void *arg) +{ + struct imsg_dhcp imsg_dhcp; + struct iface *iface; + ssize_t len; + + iface = (struct iface *)arg; + memset(&imsg_dhcp, 0, sizeof(imsg_dhcp)); + + if ((len = read(fd, imsg_dhcp.packet, 1500)) == -1) { + log_warn("%s: read", __func__); + return; + } + + if (len == 0) + fatal("%s len == 0", __func__); + + imsg_dhcp.if_index = iface->ifinfo.if_index; + imsg_dhcp.len = len; + frontend_imsg_compose_engine(IMSG_DHCP, 0, 0, &imsg_dhcp, + sizeof(imsg_dhcp)); +} + +void +iface_data_from_imsg(struct iface* iface, struct imsg_req_dhcp *imsg) +{ + memcpy(iface->xid, imsg->xid, sizeof(iface->xid)); + iface->elapsed_time = imsg->elapsed_time; + iface->serverid_len = imsg->serverid_len; + memcpy(iface->serverid, imsg->serverid, SERVERID_SIZE); + memcpy(iface->pds, imsg->pds, sizeof(iface->pds)); +} + +ssize_t +build_packet(uint8_t message_type, struct iface *iface, char *if_name) +{ + struct iface_conf *iface_conf; + struct iface_ia_conf *ia_conf; + struct dhcp_hdr hdr; + struct dhcp_option_hdr opt_hdr; + struct dhcp_iapd iapd; + struct dhcp_iaprefix iaprefix; + struct dhcp_vendor_class vendor_class; + ssize_t len; + uint16_t request_option_code, elapsed_time; + uint8_t *p; + + switch(message_type) { + case DHCPSOLICIT: + case DHCPREQUEST: + break; + default: + fatalx("%s: %s not implemented", __func__, + dhcp_message_type2str(message_type)); + } + + iface_conf = find_iface_conf(&frontend_conf->iface_list, if_name); + + memset(dhcp_packet, 0, sizeof(dhcp_packet)); + + p = dhcp_packet; + hdr.msg_type = message_type; + memcpy(hdr.xid, iface->xid, sizeof(hdr.xid)); + memcpy(p, &hdr, sizeof(struct dhcp_hdr)); + p += sizeof(struct dhcp_hdr); + + opt_hdr.code = htons(DHO_CLIENTID); + opt_hdr.len = htons(sizeof(struct dhcp_duid)); + memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr)); + p += sizeof(struct dhcp_option_hdr); + memcpy(p, &duid, sizeof(struct dhcp_duid)); + p += sizeof(struct dhcp_duid); + + if (message_type == DHCPREQUEST) { + opt_hdr.code = htons(DHO_SERVERID); + opt_hdr.len = htons(iface->serverid_len); + memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr)); + p += sizeof(struct dhcp_option_hdr); + memcpy(p, iface->serverid, iface->serverid_len); + p += iface->serverid_len; + } + SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list, entry) { + struct prefix *pd; + + opt_hdr.code = htons(DHO_IA_PD); + opt_hdr.len = htons(sizeof(struct dhcp_iapd) + + sizeof(struct dhcp_option_hdr) + + sizeof(struct dhcp_iaprefix)); + memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr)); + p += sizeof(struct dhcp_option_hdr); + iapd.iaid = htonl(ia_conf->id); + iapd.t1 = 0; + iapd.t2 = 0; + memcpy(p, &iapd, sizeof(struct dhcp_iapd)); + p += sizeof(struct dhcp_iapd); + + opt_hdr.code = htons(DHO_IA_PREFIX); + opt_hdr.len = htons(sizeof(struct dhcp_iaprefix)); + memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr)); + p += sizeof(struct dhcp_option_hdr); + + memset(&iaprefix, 0, sizeof(struct dhcp_iaprefix)); + + switch(message_type) { + case DHCPSOLICIT: + iaprefix.prefix_len = ia_conf->prefix_len; + break; + case DHCPREQUEST: + pd = &iface->pds[ia_conf->id - 1]; + iaprefix.prefix_len = pd->prefix_len; + memcpy(&iaprefix.prefix, &pd->prefix, + sizeof(struct in6_addr)); + break; + default: + fatalx("%s: %s not implemented", __func__, + dhcp_message_type2str(message_type)); + } + memcpy(p, &iaprefix, sizeof(struct dhcp_iaprefix)); + p += sizeof(struct dhcp_iaprefix); + } + + opt_hdr.code = htons(DHO_ORO); + opt_hdr.len = htons(2 * 2); + memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr)); + p += sizeof(struct dhcp_option_hdr); + request_option_code = htons(DHO_SOL_MAX_RT); + memcpy(p, &request_option_code, sizeof(uint16_t)); + p += sizeof(uint16_t); + request_option_code = htons(DHO_INF_MAX_RT); + memcpy(p, &request_option_code, sizeof(uint16_t)); + p += sizeof(uint16_t); + + opt_hdr.code = htons(DHO_ELAPSED_TIME); + opt_hdr.len = htons(2); + memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr)); + p += sizeof(struct dhcp_option_hdr); + elapsed_time = htons(iface->elapsed_time); + memcpy(p, &elapsed_time, sizeof(uint16_t)); + p += sizeof(uint16_t); + +#ifdef notyet + opt_hdr.code = htons(DHO_RAPID_COMMIT); + opt_hdr.len = htons(0); + memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr)); + p += sizeof(struct dhcp_option_hdr); +#endif + + opt_hdr.code = htons(DHO_VENDOR_CLASS); + opt_hdr.len = htons(sizeof(struct dhcp_vendor_class) + + vendor_class_len); + memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr)); + p += sizeof(struct dhcp_option_hdr); + vendor_class.enterprise_number = htonl(OPENBSD_ENTERPRISENO); + vendor_class.vendor_class_len = htons(vendor_class_len); + memcpy(p, &vendor_class, sizeof(struct dhcp_vendor_class)); + p += sizeof(struct dhcp_vendor_class); + /* Not a C-string, leave out \0 */ + memcpy(p, vendor_class_data, vendor_class_len); + p += vendor_class_len; + + len = p - dhcp_packet; + return (len); +} + +void +send_packet(uint8_t message_type, struct iface *iface) +{ + ssize_t pkt_len; + char ifnamebuf[IF_NAMESIZE], *if_name; + + if (!event_initialized(&iface->udpev)) { + iface->send_solicit = 1; + return; + } + + iface->send_solicit = 0; + + if ((if_name = if_indextoname(iface->ifinfo.if_index, ifnamebuf)) + == NULL) + return; /* iface went away, nothing to do */ + + log_debug("%s on %s", message_type == DHCPSOLICIT ? "DHCPSOLICIT" : + "DHCPREQUEST", if_name); + + pkt_len = build_packet(message_type, iface, if_name); + udp_send_packet(iface, dhcp_packet, pkt_len); +} + +void +udp_send_packet(struct iface *iface, uint8_t *packet, ssize_t len) +{ + struct sockaddr_in6 to; + + memset(&to, 0, sizeof(to)); + to.sin6_family = AF_INET6; + if (inet_pton(AF_INET6, ALL_DHCP_RELAY_AGENTS_AND_SERVERS, + &to.sin6_addr.s6_addr) != 1) + fatal("inet_pton"); + + to.sin6_port = ntohs(SERVER_PORT); + to.sin6_scope_id = iface->ifinfo.if_index; + + if (sendto(EVENT_FD(&iface->udpev), packet, len, 0, + (struct sockaddr *)&to, sizeof(to)) == -1) + log_warn("sendto"); +} + +struct iface* +get_iface_by_id(uint32_t if_index) +{ + struct iface *iface; + + LIST_FOREACH (iface, &interfaces, entries) { + if (iface->ifinfo.if_index == if_index) + return (iface); + } + + return (NULL); +} + +struct iface* +get_iface_by_name(const char *if_name) +{ + uint32_t ifidx = if_nametoindex(if_name); + + if (ifidx == 0) + return (NULL); + return get_iface_by_id(ifidx); +} + +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); + if (event_initialized(&iface->udpev)) { + event_del(&iface->udpev); + close(EVENT_FD(&iface->udpev)); + } + free(iface); +} + +void +set_udpsock(int udpsock, uint32_t if_index) +{ + struct iface *iface; + + iface = get_iface_by_id(if_index); + + if (iface == NULL) { + /* + * The interface disappeared while we were waiting for the + * parent process to open the udp socket. + */ + close(udpsock); + } else if (event_initialized(&iface->udpev)) { + /* + * XXX + * The autoconf flag is flapping and we have multiple udp + * sockets in flight. We don't need this one because we already + * got one. + */ + close(udpsock); + } else { + event_set(&iface->udpev, udpsock, EV_READ | + EV_PERSIST, udp_receive, iface); + event_add(&iface->udpev, NULL); + if (iface->send_solicit) + send_packet(DHCPSOLICIT, iface); + } +} + +struct iface_conf* +find_iface_conf(struct iface_conf_head *head, char *if_name) +{ + struct iface_conf *iface_conf; + + if (if_name == NULL) + return (NULL); + + SIMPLEQ_FOREACH(iface_conf, head, entry) { + if (strcmp(iface_conf->name, if_name) == 0) + return iface_conf; + } + return (NULL); +} + +int* +changed_ifaces(struct dhcp6leased_conf *oconf, struct dhcp6leased_conf *nconf) +{ + struct iface_conf *iface_conf, *oiface_conf; + int *ret, if_index, count = 0, i = 0; + + /* + * Worst case: All old interfaces replaced with new interfaces. + * This should still be a small number + */ + SIMPLEQ_FOREACH(iface_conf, &oconf->iface_list, entry) + count++; + SIMPLEQ_FOREACH(iface_conf, &nconf->iface_list, entry) + count++; + + ret = calloc(count + 1, sizeof(int)); + + SIMPLEQ_FOREACH(iface_conf, &nconf->iface_list, entry) { + if ((if_index = if_nametoindex(iface_conf->name)) == 0) + continue; + oiface_conf = find_iface_conf(&oconf->iface_list, + iface_conf->name); + if (oiface_conf == NULL) { + /* new interface added to config */ + ret[i++] = if_index; + } else if (iface_conf_cmp(iface_conf, oiface_conf) != 0) { + /* interface conf changed */ + ret[i++] = if_index; + } + } + SIMPLEQ_FOREACH(oiface_conf, &oconf->iface_list, entry) { + if ((if_index = if_nametoindex(oiface_conf->name)) == 0) + continue; + if (find_iface_conf(&nconf->iface_list, oiface_conf->name) == + NULL) { + /* interface removed from config */ + ret[i++] = if_index; + } + } + return ret; +} + +int +iface_conf_cmp(struct iface_conf *a, struct iface_conf *b) +{ + return 0; +} diff --git a/sbin/dhcp6leased/frontend.h b/sbin/dhcp6leased/frontend.h new file mode 100644 index 00000000000..7694907cda2 --- /dev/null +++ b/sbin/dhcp6leased/frontend.h @@ -0,0 +1,24 @@ +/* $OpenBSD: frontend.h,v 1.1 2024/06/02 12:28:05 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/dhcp6leased/log.c b/sbin/dhcp6leased/log.c new file mode 100644 index 00000000000..8cbf4162f10 --- /dev/null +++ b/sbin/dhcp6leased/log.c @@ -0,0 +1,199 @@ +/* $OpenBSD: log.c,v 1.1 2024/06/02 12:28:05 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/dhcp6leased/log.h b/sbin/dhcp6leased/log.h new file mode 100644 index 00000000000..18db5fd2d06 --- /dev/null +++ b/sbin/dhcp6leased/log.h @@ -0,0 +1,46 @@ +/* $OpenBSD: log.h,v 1.1 2024/06/02 12:28:05 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 + +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))); + +#endif /* LOG_H */ diff --git a/sbin/dhcp6leased/parse.y b/sbin/dhcp6leased/parse.y new file mode 100644 index 00000000000..8fb7778fa3b --- /dev/null +++ b/sbin/dhcp6leased/parse.y @@ -0,0 +1,917 @@ +/* $OpenBSD: parse.y,v 1.1 2024/06/02 12:28:05 florian Exp $ */ + +/* + * Copyright (c) 2018, 2024 Florian Obser + * Copyright (c) 2004, 2005 Esben Norby + * Copyright (c) 2004 Ryan McBride + * Copyright (c) 2002, 2003, 2004 Henning Brauer + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * 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 "log.h" +#include "dhcp6leased.h" +#include "frontend.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + size_t ungetpos; + size_t ungetsize; + u_char *ungetbuf; + int eof_reached; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *, int); +int popfile(void); +int check_file_secrecy(int, const char *); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +int kw_cmp(const void *, const void *); +int lookup(char *); +int igetc(void); +int lgetc(int); +void lungetc(int); +int findeol(void); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; + +int symset(const char *, const char *, int); +char *symget(const char *); + +static struct dhcp6leased_conf *conf; +static int errors; + +static struct iface_conf *iface_conf; +static struct iface_ia_conf *iface_ia_conf; + +struct iface_conf *conf_get_iface(char *); +struct iface_pd_conf *conf_get_pd_iface(char *, int); +void addressing_plan(struct iface_ia_conf *); +int fls64(uint64_t); + +typedef struct { + union { + int64_t number; + char *string; + } v; + int lineno; +} YYSTYPE; + +%} + +%token ERROR DELEGATION FOR ON PREFIX REQUEST + +%token STRING +%token NUMBER +%type string + +%% + +grammar : /* empty */ + | grammar '\n' + | grammar varset '\n' + | grammar ia_pd '\n' + | grammar error '\n' { file->errors++; } + ; + +string : string STRING { + if (asprintf(&$$, "%s %s", $1, $2) == -1) { + free($1); + free($2); + yyerror("string: asprintf"); + YYERROR; + } + free($1); + free($2); + } + | STRING + ; + +varset : STRING '=' string { + char *s = $1; + if (log_getverbose() == 1) + printf("%s = \"%s\"\n", $1, $3); + while (*s++) { + if (isspace((unsigned char)*s)) { + yyerror("macro name cannot contain " + "whitespace"); + free($1); + free($3); + YYERROR; + } + } + if (symset($1, $3, 0) == -1) + fatal("cannot store variable"); + free($1); + free($3); + } + ; + +optnl : '\n' optnl /* zero or more newlines */ + | /*empty*/ + ; + +nl : '\n' optnl /* one or more newlines */ + ; + +ia_pd : REQUEST PREFIX DELEGATION ON STRING FOR { + iface_conf = conf_get_iface($5); + iface_ia_conf = calloc(1, sizeof(*iface_ia_conf)); + if (iface_ia_conf == NULL) + err(1, "%s: calloc", __func__); + iface_ia_conf->id = ++iface_conf->ia_count; + if (iface_ia_conf->id > MAX_IA) { + yyerror("Too many prefix delegation requests"); + YYERROR; + } + SIMPLEQ_INIT(&iface_ia_conf->iface_pd_list); + SIMPLEQ_INSERT_TAIL(&iface_conf->iface_ia_list, + iface_ia_conf, entry); + } '{' iface_block '}' { + iface_conf = NULL; + iface_ia_conf = NULL; + } + ; + +iface_block : optnl ifaceopts_l + | optnl + ; + +ifaceopts_l : ifaceopts_l ifaceoptsl nl + | ifaceoptsl optnl + ; + +ifaceoptsl : STRING { + struct iface_pd_conf *iface_pd_conf; + int prefixlen; + char *p; + const char *errstr; + + p = strchr($1, '/'); + if (p != NULL) { + *p++ = '\0'; + prefixlen = strtonum(p, 0, 128, &errstr); + if (errstr != NULL) { + yyerror("error parsing interface " + "\"%s/%s\"", $1, p); + free($1); + YYERROR; + } + } else + prefixlen = 64; + if ((iface_pd_conf = conf_get_pd_iface($1, prefixlen)) + == NULL) { + yyerror("duplicate interface %s", $1); + free($1); + YYERROR; + } + } + ; +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + + file->errors++; + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) + fatalx("yyerror vasprintf"); + va_end(ap); + logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); + free(msg); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* This has to be sorted always. */ + static const struct keywords keywords[] = { + {"delegation", DELEGATION}, + {"for", FOR}, + {"on", ON}, + {"prefix", PREFIX}, + {"request", REQUEST}, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define START_EXPAND 1 +#define DONE_EXPAND 2 + +static int expanding; + +int +igetc(void) +{ + int c; + + while (1) { + if (file->ungetpos > 0) + c = file->ungetbuf[--file->ungetpos]; + else + c = getc(file->stream); + + if (c == START_EXPAND) + expanding = 1; + else if (c == DONE_EXPAND) + expanding = 0; + else + break; + } + return (c); +} + +int +lgetc(int quotec) +{ + int c, next; + + if (quotec) { + if ((c = igetc()) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = igetc()) == '\\') { + next = igetc(); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + if (c == EOF) { + /* + * Fake EOL when hit EOF for the first time. This gets line + * count right if last line in included file is syntactically + * invalid and has no newline. + */ + if (file->eof_reached == 0) { + file->eof_reached = 1; + return ('\n'); + } + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = igetc(); + } + } + return (c); +} + +void +lungetc(int c) +{ + if (c == EOF) + return; + + if (file->ungetpos >= file->ungetsize) { + void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); + if (p == NULL) + err(1, "lungetc"); + file->ungetbuf = p; + file->ungetsize *= 2; + } + file->ungetbuf[file->ungetpos++] = c; +} + +int +findeol(void) +{ + int c; + + /* Skip to either EOF or the first real EOL. */ + while (1) { + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + char buf[8096]; + char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && !expanding) { + while (1) { + if ((c = lgetc(0)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + p = val + strlen(val) - 1; + lungetc(DONE_EXPAND); + while (p >= val) { + lungetc((unsigned char)*p); + p--; + } + lungetc(START_EXPAND); + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || next == ' ' || + next == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "yylex: strdup"); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc((unsigned char)*--p); + c = (unsigned char)*--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) { + log_warn("cannot stat %s", fname); + return (-1); + } + if (st.st_uid != 0 && st.st_uid != getuid()) { + log_warnx("%s: owner not root or current user", fname); + return (-1); + } + if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { + log_warnx("%s: group writable or world read/writable", fname); + return (-1); + } + return (0); +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + log_warn("calloc"); + return (NULL); + } + if ((nfile->name = strdup(name)) == NULL) { + log_warn("strdup"); + free(nfile); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + free(nfile->name); + free(nfile); + return (NULL); + } else if (secret && + check_file_secrecy(fileno(nfile->stream), nfile->name)) { + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; + nfile->ungetsize = 16; + nfile->ungetbuf = malloc(nfile->ungetsize); + if (nfile->ungetbuf == NULL) { + log_warn("malloc"); + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file->ungetbuf); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +struct dhcp6leased_conf * +parse_config(const char *filename) +{ + extern const char default_conffile[]; + struct sym *sym, *next; + struct iface_conf *iface; + struct iface_ia_conf *ia_conf; + + conf = config_new_empty(); + + file = pushfile(filename, 0); + if (file == NULL) { + free(conf); + return (NULL); + } + topfile = file; + + yyparse(); + errors = file->errors; + popfile(); + + /* Free macros and check which have not been used. */ + TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { + if ((log_getverbose() == 2) && !sym->used) + fprintf(stderr, "warning: macro '%s' not used\n", + sym->nam); + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + if (errors) { + config_clear(conf); + return (NULL); + } + + SIMPLEQ_FOREACH(iface, &conf->iface_list, entry) { + SIMPLEQ_FOREACH(ia_conf, &iface->iface_ia_list, entry) { + addressing_plan(ia_conf); + } + } + return (conf); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) + break; + } + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + sym = strndup(s, val - s); + if (sym == NULL) + errx(1, "%s: strndup", __func__); + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + } + return (NULL); +} + +struct iface_conf * +conf_get_iface(char *name) +{ + struct iface_conf *iface; + size_t n; + + SIMPLEQ_FOREACH(iface, &conf->iface_list, entry) { + if (strcmp(name, iface->name) == 0) + return (iface); + } + + iface = calloc(1, sizeof(*iface)); + if (iface == NULL) + errx(1, "%s: calloc", __func__); + n = strlcpy(iface->name, name, sizeof(iface->name)); + if (n >= sizeof(iface->name)) + errx(1, "%s: name too long", __func__); + SIMPLEQ_INIT(&iface->iface_ia_list); + + SIMPLEQ_INSERT_TAIL(&conf->iface_list, iface, entry); + + return (iface); +} + +struct iface_pd_conf * +conf_get_pd_iface(char *name, int prefixlen) +{ + struct iface_ia_conf *iface_ia; + struct iface_pd_conf *iface_pd; + size_t n; + + if (strcmp(name, "reserve") != 0) { + SIMPLEQ_FOREACH(iface_ia, &iface_conf->iface_ia_list, + entry) { + SIMPLEQ_FOREACH(iface_pd, &iface_ia->iface_pd_list, + entry) { + if (strcmp(name, iface_pd->name) == 0) + return NULL; + } + } + } + + iface_pd = calloc(1, sizeof(*iface_pd)); + if (iface_pd == NULL) + err(1, "%s: calloc", __func__); + n = strlcpy(iface_pd->name, name, sizeof(iface_pd->name)); + if (n >= sizeof(iface_pd->name)) + errx(1, "%s: name too long", __func__); + iface_pd->prefix_len = prefixlen; + + SIMPLEQ_INSERT_TAIL(&iface_ia_conf->iface_pd_list, iface_pd, entry); + + return (iface_pd); +} + +static inline uint64_t +get_shift(int plen) +{ + if (plen > 64) + plen -= 64; + + return 1ULL << (64 - plen); +} + +void +addressing_plan(struct iface_ia_conf *ia_conf) +{ + struct iface_pd_conf *pd_conf; + uint64_t *p, lo_counter, hi_counter, lo_shift, hi_shift; + int prev_plen; + + lo_counter = hi_counter = 0; + + SIMPLEQ_FOREACH(pd_conf, &ia_conf->iface_pd_list, entry) { + /* not the first prefix */ + if (ia_conf->prefix_len != 0) { + lo_shift = hi_shift = 0; + if (prev_plen > pd_conf->prefix_len) { + if (pd_conf->prefix_len > 64) + lo_shift = + get_shift(pd_conf->prefix_len); + else + hi_shift = + get_shift(pd_conf->prefix_len); + } else { + if (prev_plen > 64) + lo_shift = get_shift(prev_plen); + else + hi_shift = get_shift(prev_plen); + } + + if (lo_shift != 0) { + if (lo_counter > UINT64_MAX - lo_shift) { + /* overflow */ + hi_counter++; + lo_counter = 0; + } else { + lo_counter += lo_shift; + /* remove all lower bits */ + lo_counter &= ~(lo_shift - 1); + } + } else { + hi_counter += hi_shift; + /* remove all lower bits */ + hi_counter &= ~(hi_shift - 1); + lo_counter = 0; + } + + } else + ia_conf->prefix_len = pd_conf->prefix_len; + + p = (uint64_t *)&pd_conf->prefix_mask.s6_addr; + *p |= htobe64(hi_counter); + + p = (uint64_t *)&pd_conf->prefix_mask.s6_addr[8]; + *p |= htobe64(lo_counter); + + prev_plen = pd_conf->prefix_len; + } + + if (hi_counter != 0) + ia_conf->prefix_len = 64 - fls64(hi_counter); + else if (lo_counter != 0) + ia_conf->prefix_len = 128 - fls64(lo_counter); +} + +/* from NetBSD's sys/sys/bitops.h */ +/*- + * Copyright (c) 2007, 2010 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas and Joerg Sonnenberger. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + */ +int +fls64(uint64_t _n) +{ + int _v; + + if (!_n) + return 0; + + _v = 64; + if ((_n & 0xFFFFFFFF00000000ULL) == 0) { + _n <<= 32; + _v -= 32; + } + if ((_n & 0xFFFF000000000000ULL) == 0) { + _n <<= 16; + _v -= 16; + } + if ((_n & 0xFF00000000000000ULL) == 0) { + _n <<= 8; + _v -= 8; + } + if ((_n & 0xF000000000000000ULL) == 0) { + _n <<= 4; + _v -= 4; + } + if ((_n & 0xC000000000000000ULL) == 0) { + _n <<= 2; + _v -= 2; + } + if ((_n & 0x8000000000000000ULL) == 0) { + //_n <<= 1; + _v -= 1; + } + return _v; +} diff --git a/sbin/dhcp6leased/printconf.c b/sbin/dhcp6leased/printconf.c new file mode 100644 index 00000000000..a38a652658e --- /dev/null +++ b/sbin/dhcp6leased/printconf.c @@ -0,0 +1,106 @@ +/* $OpenBSD: printconf.c,v 1.1 2024/06/02 12:28:05 florian Exp $ */ + +/* + * Copyright (c) 2024 Florian Obser + * 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. + */ + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "dhcp6leased.h" +#include "log.h" + +void print_iface_conf(struct iface_conf *, int); +void print_iface_ia_conf(struct iface_ia_conf *, int); +void print_iface_pd_conf(char *, struct iface_pd_conf *, int); + +void +print_iface_pd_conf(char *indent, struct iface_pd_conf *pd_conf, int verbose) +{ + if (verbose > 1) { + struct in6_addr ia6; + int i; + char ntopbuf[INET6_ADDRSTRLEN]; + + memset(&ia6, 0, sizeof(ia6)); + inet_pton(AF_INET6, "2001:db8::", &ia6); + + for (i = 0; i < 16; i++) + ia6.s6_addr[i] |= pd_conf->prefix_mask.s6_addr[i]; + + inet_ntop(AF_INET6, &ia6, ntopbuf, INET6_ADDRSTRLEN); + printf("%s%s/%d\t# %s/%d\n", indent, pd_conf->name, + pd_conf->prefix_len, ntopbuf, pd_conf->prefix_len); + } else + printf("%s%s/%d\n", indent, pd_conf->name, pd_conf->prefix_len); +} + +void +print_iface_ia_conf(struct iface_ia_conf *ia_conf, int verbose) +{ + struct iface_pd_conf *pd_conf; + + SIMPLEQ_FOREACH(pd_conf, &ia_conf->iface_pd_list, entry) + print_iface_pd_conf("\t", pd_conf, + ia_conf->prefix_len >= 32 ? verbose : 1); +} + +void +print_iface_conf(struct iface_conf *iface, int verbose) +{ + struct iface_ia_conf *ia_conf; + int first = 1; + + SIMPLEQ_FOREACH(ia_conf, &iface->iface_ia_list, entry) { + if (!first) + printf("\n"); + first = 0; + + if (verbose > 1) { + printf("request prefix delegation on %s for {" + "\t# prefix length = %d\n", iface->name, + ia_conf->prefix_len); + } else { + printf("request prefix delegation on %s for {\n", + iface->name); + } + print_iface_ia_conf(ia_conf, verbose); + printf("}\n"); + } +} +void +print_config(struct dhcp6leased_conf *conf, int verbose) +{ + struct iface_conf *iface; + + SIMPLEQ_FOREACH(iface, &conf->iface_list, entry) + print_iface_conf(iface, verbose); +}