/* $OpenBSD: engine.c,v 1.24 2024/07/11 10:48:51 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 "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", }; enum reconfigure_action { CONFIGURE, DECONFIGURE, }; const char* reconfigure_action_name[] = { "configure", "deconfigure", }; 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; int link_state; uint8_t xid[XID_SIZE]; int serverid_len; uint8_t serverid[SERVERID_SIZE]; struct prefix pds[MAX_IA]; struct prefix new_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 *); int 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 configure_interfaces(struct dhcp6leased_iface *); void deconfigure_interfaces(struct dhcp6leased_iface *); int prefixcmp(struct prefix *, struct prefix *, int); void send_reconfigure_interface(struct iface_pd_conf *, struct prefix *, enum reconfigure_action); int engine_imsg_compose_main(int, pid_t, void *, uint16_t); const char *dhcp_option_type2str(int); const char *dhcp_duid2str(int, uint8_t *); const char *dhcp_status2str(int); 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)); engine_update_iface(&imsg_ifinfo); 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: { 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; memcpy(&cei.pds, &iface->pds, sizeof(cei.pds)); 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; struct iface_conf *iface_conf; int need_refresh = 0; char ifnamebuf[IF_NAMESIZE], *if_name; 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; LIST_INSERT_HEAD(&dhcp6leased_interfaces, iface, entries); need_refresh = 1; } else { 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 ((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; } if (iface->running && LINK_STATE_IS_UP(iface->link_state)) { uint32_t i; int got_lease; if (iface->pds[0].prefix_len == 0) memcpy(iface->pds, imsg_ifinfo->pds, sizeof(iface->pds)); got_lease = 0; for (i = 0; i < iface_conf->ia_count; i++) { if (iface->pds[i].prefix_len > 0) { got_lease = 1; break; } } if (got_lease) state_transition(iface, IF_REBOOTING); else 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; deconfigure_interfaces(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; size_t rem; uint32_t t1, t2, lease_time; int serverid_len, rapid_commit = 0; 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); serverid_len = t1 = t2 = lease_time = 0; memset(iface->new_pds, 0, sizeof(iface->new_pds)); 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) { int status_code; status_code = parse_ia_pd_options(p + sizeof(struct dhcp_iapd), opt_hdr.len - sizeof(struct dhcp_iapd), &iface->new_pds[ntohl(iapd.iaid)]); if (status_code != DHCP_STATUS_SUCCESS && iface->state == IF_RENEWING) { state_transition(iface, IF_REBINDING); goto out; } } break; case DHO_RAPID_COMMIT: if (opt_hdr.len != 0) { log_warnx("%s: invalid rapid commit option", __func__); goto out; } rapid_commit = 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 = &iface->new_pds[ia_conf->id]; 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; } if (lease_time < pd->vltime) lease_time = pd->vltime; 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); memcpy(iface->pds, iface->new_pds, sizeof(iface->pds)); state_transition(iface, IF_REQUESTING); break; case DHCPREPLY: switch (iface->state) { case IF_REQUESTING: case IF_RENEWING: case IF_REBINDING: case IF_REBOOTING: break; case IF_INIT: if (rapid_commit && engine_conf->rapid_commit) break; /* fall through */ default: 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); /* XXX handle t1 = 0 or t2 = 0 */ iface->t1 = t1; iface->t2 = t2; iface->lease_time = lease_time; clock_gettime(CLOCK_MONOTONIC, &iface->request_time); 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: return; } int 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 = DHCP_STATUS_SUCCESS; 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 DHCP_STATUS_UNSPECFAIL; } switch (opt_hdr.code) { case DHO_IA_PREFIX: if (len < sizeof(struct dhcp_iaprefix)) { log_warnx("%s: malformed packet, ignoring", __func__); return DHCP_STATUS_UNSPECFAIL; } 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; } if (ntohl(iaprefix.vltime) == 0) { log_debug("%s: vltime == 0, 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 masked 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 STATUS_CODE can also appear outside of options */ if (len < 2) { log_warnx("%s: malformed packet, ignoring", __func__); return DHCP_STATUS_UNSPECFAIL; } memcpy(&status_code, p, sizeof(uint16_t)); status_code = ntohs(status_code); /* must be at least 4 * srclen + 1 long */ visbuf = calloc(4, opt_hdr.len - 2 + 1); if (visbuf == NULL) { log_warn("%s", __func__); break; } strvisx(visbuf, p + 2, opt_hdr.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; } return status_code; } /* 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: /* * Nothing to do until iface comes up. IP addresses will expire. */ 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 */ deconfigure_interfaces(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; switch (old_state) { case IF_REQUESTING: case IF_RENEWING: case IF_REBINDING: case IF_REBOOTING: configure_interfaces(iface); break; case IF_INIT: if (engine_conf->rapid_commit) configure_interfaces(iface); else fatal("invalid transition Init -> Bound"); break; default: break; } 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; } } /* XXX can this be merged into dhcp_request()? */ 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_SOLICIT, 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: case IF_REQUESTING: case IF_RENEWING: case IF_REBINDING: imsg.serverid_len = iface->serverid_len; memcpy(imsg.serverid, iface->serverid, SERVERID_SIZE); memcpy(imsg.pds, iface->pds, sizeof(iface->pds)); break; } switch (iface->state) { case IF_REQUESTING: engine_imsg_compose_frontend(IMSG_SEND_REQUEST, 0, &imsg, sizeof(imsg)); break; case IF_RENEWING: engine_imsg_compose_frontend(IMSG_SEND_RENEW, 0, &imsg, sizeof(imsg)); break; case IF_REBOOTING: case IF_REBINDING: engine_imsg_compose_frontend(IMSG_SEND_REBIND, 0, &imsg, sizeof(imsg)); break; default: fatalx("%s: wrong state", __func__); } } /* 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; struct imsg_lease_info imsg_lease_info; uint32_t i; char ntopbuf[INET6_ADDRSTRLEN]; 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; } for (i = 0; i < iface_conf->ia_count; i++) { struct prefix *pd = &iface->new_pds[i]; log_info("prefix delegation #%d %s/%d received on %s from " "server %s", i, inet_ntop(AF_INET6, &pd->prefix, ntopbuf, INET6_ADDRSTRLEN), pd->prefix_len, if_name, dhcp_duid2str(iface->serverid_len, iface->serverid)); } SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list, entry) { struct prefix *pd = &iface->new_pds[ia_conf->id]; SIMPLEQ_FOREACH(pd_conf, &ia_conf->iface_pd_list, entry) { send_reconfigure_interface(pd_conf, pd, CONFIGURE); } } if (prefixcmp(iface->pds, iface->new_pds, iface_conf->ia_count) != 0) { log_info("Prefix delegations on %s from server %s changed", if_name, dhcp_duid2str(iface->serverid_len, iface->serverid)); for (i = 0; i < iface_conf->ia_count; i++) { log_debug("%s: iface->pds [%d]: %s/%d", __func__, i, inet_ntop(AF_INET6, &iface->pds[i].prefix, ntopbuf, INET6_ADDRSTRLEN), iface->pds[i].prefix_len); log_debug("%s: pds [%d]: %s/%d", __func__, i, inet_ntop(AF_INET6, &iface->new_pds[i].prefix, ntopbuf, INET6_ADDRSTRLEN), iface->new_pds[i].prefix_len); } deconfigure_interfaces(iface); } memcpy(iface->pds, iface->new_pds, sizeof(iface->pds)); memset(iface->new_pds, 0, sizeof(iface->new_pds)); memset(&imsg_lease_info, 0, sizeof(imsg_lease_info)); imsg_lease_info.if_index = iface->if_index; memcpy(imsg_lease_info.pds, iface->pds, sizeof(iface->pds)); engine_imsg_compose_main(IMSG_WRITE_LEASE, 0, &imsg_lease_info, sizeof(imsg_lease_info)); } void deconfigure_interfaces(struct dhcp6leased_iface *iface) { struct iface_conf *iface_conf; struct iface_ia_conf *ia_conf; struct iface_pd_conf *pd_conf; uint32_t i; char ntopbuf[INET6_ADDRSTRLEN]; 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; } for (i = 0; i < iface_conf->ia_count; i++) { struct prefix *pd = &iface->pds[i]; log_info("Prefix delegation #%d %s/%d expired on %s from " "server %s", i, inet_ntop(AF_INET6, &pd->prefix, ntopbuf, INET6_ADDRSTRLEN), pd->prefix_len, if_name, dhcp_duid2str(iface->serverid_len, iface->serverid)); } SIMPLEQ_FOREACH(ia_conf, &iface_conf->iface_ia_list, entry) { struct prefix *pd = &iface->pds[ia_conf->id]; SIMPLEQ_FOREACH(pd_conf, &ia_conf->iface_pd_list, entry) { send_reconfigure_interface(pd_conf, pd, DECONFIGURE); } } memset(iface->pds, 0, sizeof(iface->pds)); } int prefixcmp(struct prefix *a, struct prefix *b, int count) { int i; for (i = 0; i < count; i++) { if (a[i].prefix_len != b[i].prefix_len) return 1; if (memcmp(&a[i].prefix, &b[i].prefix, sizeof(struct in6_addr)) != 0) return 1; } return 0; } void send_reconfigure_interface(struct iface_pd_conf *pd_conf, struct prefix *pd, enum reconfigure_action action) { struct imsg_configure_address address; uint32_t if_index; int i; char ntopbuf[INET6_ADDRSTRLEN]; if (pd->prefix_len == 0) return; 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: %s/%d", __func__, pd_conf->name, reconfigure_action_name[action], inet_ntop(AF_INET6, &address.addr.sin6_addr, ntopbuf, INET6_ADDRSTRLEN), pd_conf->prefix_len); address.vltime = pd->vltime; address.pltime = pd->pltime; if (action == CONFIGURE) engine_imsg_compose_main(IMSG_CONFIGURE_ADDRESS, 0, &address, sizeof(address)); else engine_imsg_compose_main(IMSG_DECONFIGURE_ADDRESS, 0, &address, sizeof(address)); } const char * dhcp_message_type2str(int 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 & 0xff); return buf; } } const char * dhcp_option_type2str(int 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 &0xffff); 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(int 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 & 0xff); 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]; }