/* $OpenBSD: frontend.c,v 1.14 2024/07/11 13:38:03 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 "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 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 *); 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; struct sockaddr_in6 dst; 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"); memset(&dst, 0, sizeof(dst)); dst.sin6_family = AF_INET6; if (inet_pton(AF_INET6, ALL_DHCP_RELAY_AGENTS_AND_SERVERS, &dst.sin6_addr.s6_addr) != 1) fatal("inet_pton"); dst.sin6_port = ntohs(SERVER_PORT); 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 (IMSG_DATA_SIZE(imsg) != sizeof(struct dhcp6leased_conf)) fatalx("%s: IMSG_RECONF_CONF wrong length: %lu", __func__, IMSG_DATA_SIZE(imsg)); if ((nconf = malloc(sizeof(struct dhcp6leased_conf))) == NULL) fatal(NULL); memcpy(nconf, imsg.data, sizeof(struct dhcp6leased_conf)); 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_ia_conf->id = 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_SOLICIT: case IMSG_SEND_REQUEST: case IMSG_SEND_RENEW: case IMSG_SEND_REBIND: { 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); switch (imsg.hdr.type) { case IMSG_SEND_SOLICIT: send_packet(DHCPSOLICIT, iface); break; case IMSG_SEND_REQUEST: send_packet(DHCPREQUEST, iface); break; case IMSG_SEND_RENEW: send_packet(DHCPRENEW, iface); break; case IMSG_SEND_REBIND: send_packet(DHCPREBIND, iface); break; } 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; if (find_iface_conf(&frontend_conf->iface_list, if_name) == NULL) 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; 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: freeifaddrs(ifap); iface = get_iface_by_id(if_index); if (iface == NULL) { if ((iface = calloc(1, sizeof(*iface))) == NULL) fatal("calloc"); 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 frontend_startup(void) { if (!event_initialized(&ev_route)) fatalx("%s: did not receive a route socket from the main " "process", __func__); 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 if_announcemsghdr *ifan; uint32_t if_index; switch (rtm->rtm_type) { case RTM_IFINFO: if_index = ((struct if_msghdr *)rtm)->ifm_index; update_iface(if_index); 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; size_t i; ssize_t len; uint16_t request_option_code, elapsed_time; const uint16_t options[] = {DHO_SOL_MAX_RT, DHO_INF_MAX_RT}; uint8_t *p; switch (message_type) { case DHCPSOLICIT: case DHCPREQUEST: case DHCPRENEW: case DHCPREBIND: 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); switch (message_type) { case DHCPSOLICIT: case DHCPREBIND: break; case DHCPREQUEST: case DHCPRENEW: 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; break; default: fatalx("%s: %s not implemented", __func__, dhcp_message_type2str(message_type)); } 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: case DHCPRENEW: case DHCPREBIND: pd = &iface->pds[ia_conf->id]; if (pd->prefix_len > 0) { iaprefix.prefix_len = pd->prefix_len; memcpy(&iaprefix.prefix, &pd->prefix, sizeof(struct in6_addr)); } else iaprefix.prefix_len = ia_conf->prefix_len; 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(sizeof(request_option_code) * nitems(options)); memcpy(p, &opt_hdr, sizeof(struct dhcp_option_hdr)); p += sizeof(struct dhcp_option_hdr); for (i = 0; i < nitems(options); i++) { request_option_code = htons(options[i]); 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); if (message_type == DHCPSOLICIT && frontend_conf->rapid_commit) { 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); } 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, *message_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 */ switch (message_type) { case DHCPSOLICIT: message_name = "Soliciting"; break; case DHCPREQUEST: message_name = "Requesting"; break; case DHCPRENEW: message_name = "Renewing"; break; case DHCPREBIND: message_name = "Rebinding"; break; default: message_name = NULL; break; } if (message_name) log_info("%s lease on %s", message_name, if_name); pkt_len = build_packet(message_type, iface, if_name); dst.sin6_scope_id = iface->ifinfo.if_index; if (sendto(EVENT_FD(&iface->udpev), dhcp_packet, pkt_len, 0, (struct sockaddr *)&dst, sizeof(dst)) == -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; }