/* $OpenBSD: engine.c,v 1.93 2024/08/24 16:35:05 florian Exp $ */ /* * Copyright (c) 2017 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. */ /* * 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. */ #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 "slaacd.h" #include "engine.h" #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) #define MAX_RTR_SOLICITATION_DELAY 1 #define MAX_RTR_SOLICITATION_DELAY_USEC MAX_RTR_SOLICITATION_DELAY * 1000000 #define RTR_SOLICITATION_INTERVAL 4 #define MAX_RTR_SOLICITATIONS 3 /* * Constants for RFC 8981 temporary address extensions * * PRIV_PREFERRED_LIFETIME > (PRIV_MAX_DESYNC_FACTOR + PRIV_REGEN_ADVANCE) */ #define PRIV_VALID_LIFETIME 172800 /* 2 days */ #define PRIV_PREFERRED_LIFETIME 86400 /* 1 day */ #define PRIV_MAX_DESYNC_FACTOR 34560 /* PRIV_PREFERRED_LIFETIME * 0.4 */ #define PRIV_REGEN_ADVANCE 5 /* 5 seconds */ enum if_state { IF_DOWN, IF_INIT, IF_BOUND, }; enum proposal_state { PROPOSAL_IF_DOWN, PROPOSAL_NOT_CONFIGURED, PROPOSAL_CONFIGURED, PROPOSAL_NEARLY_EXPIRED, PROPOSAL_WITHDRAWN, PROPOSAL_DUPLICATED, PROPOSAL_STALE, }; const char* rpref_name[] = { "Low", "Medium", "High", }; struct radv_prefix { LIST_ENTRY(radv_prefix) entries; struct in6_addr prefix; uint8_t prefix_len; /*XXX int */ int onlink; int autonomous; uint32_t vltime; uint32_t pltime; int dad_counter; }; struct radv_rdns { LIST_ENTRY(radv_rdns) entries; struct in6_addr rdns; }; struct radv { LIST_ENTRY(radv) entries; struct sockaddr_in6 from; struct timespec when; struct timespec uptime; struct event timer; uint32_t min_lifetime; uint8_t curhoplimit; int managed; int other; enum rpref rpref; uint16_t router_lifetime; /* in seconds */ uint32_t reachable_time; /* in milliseconds */ uint32_t retrans_time; /* in milliseconds */ LIST_HEAD(, radv_prefix) prefixes; uint32_t rdns_lifetime; LIST_HEAD(, radv_rdns) rdns_servers; uint32_t mtu; }; struct address_proposal { LIST_ENTRY(address_proposal) entries; struct event timer; int64_t id; enum proposal_state state; struct timeval timo; struct timespec created; struct timespec when; struct timespec uptime; uint32_t if_index; struct ether_addr hw_address; struct sockaddr_in6 from; struct sockaddr_in6 addr; struct in6_addr mask; struct in6_addr prefix; int temporary; uint8_t prefix_len; uint32_t vltime; uint32_t pltime; uint32_t desync_factor; uint8_t soiikey[SLAACD_SOIIKEY_LEN]; uint32_t mtu; }; struct dfr_proposal { LIST_ENTRY(dfr_proposal) entries; struct event timer; int64_t id; enum proposal_state state; struct timeval timo; struct timespec when; struct timespec uptime; uint32_t if_index; int rdomain; struct sockaddr_in6 addr; uint32_t router_lifetime; enum rpref rpref; }; struct rdns_proposal { LIST_ENTRY(rdns_proposal) entries; struct event timer; int64_t id; enum proposal_state state; struct timeval timo; struct timespec when; struct timespec uptime; uint32_t if_index; int rdomain; struct sockaddr_in6 from; int rdns_count; struct in6_addr rdns[MAX_RDNS_COUNT]; uint32_t rdns_lifetime; }; struct slaacd_iface { LIST_ENTRY(slaacd_iface) entries; enum if_state state; struct event timer; struct timeval timo; struct timespec last_sol; int probes; uint32_t if_index; uint32_t rdomain; int running; int autoconf; int temporary; int soii; struct ether_addr hw_address; struct sockaddr_in6 ll_address; uint8_t soiikey[SLAACD_SOIIKEY_LEN]; int link_state; uint32_t cur_mtu; LIST_HEAD(, radv) radvs; LIST_HEAD(, address_proposal) addr_proposals; LIST_HEAD(, dfr_proposal) dfr_proposals; LIST_HEAD(, rdns_proposal) rdns_proposals; }; LIST_HEAD(, slaacd_iface) slaacd_interfaces; __dead void engine_shutdown(void); void engine_sig_handler(int sig, short, void *); void engine_dispatch_frontend(int, short, void *); void engine_dispatch_main(int, short, void *); #ifndef SMALL void send_interface_info(struct slaacd_iface *, pid_t); void engine_showinfo_ctl(pid_t, uint32_t); void debug_log_ra(struct imsg_ra *); int in6_mask2prefixlen(struct in6_addr *); #endif /* SMALL */ struct slaacd_iface *get_slaacd_iface_by_id(uint32_t); void remove_slaacd_iface(uint32_t); void free_ra(struct radv *); void iface_state_transition(struct slaacd_iface *, enum if_state); void addr_proposal_state_transition(struct address_proposal *, enum proposal_state); void dfr_proposal_state_transition(struct dfr_proposal *, enum proposal_state); void rdns_proposal_state_transition(struct rdns_proposal *, enum proposal_state); void engine_update_iface(struct imsg_ifinfo *); void request_solicitation(struct slaacd_iface *); void parse_ra(struct slaacd_iface *, struct imsg_ra *); void gen_addr(struct slaacd_iface *, struct radv_prefix *, struct address_proposal *, int); void gen_address_proposal(struct slaacd_iface *, struct radv *, struct radv_prefix *, int); void free_address_proposal(struct address_proposal *); void withdraw_addr(struct address_proposal *); void configure_address(struct address_proposal *); void in6_prefixlen2mask(struct in6_addr *, int len); void gen_dfr_proposal(struct slaacd_iface *, struct radv *); void configure_dfr(struct dfr_proposal *); void free_dfr_proposal(struct dfr_proposal *); void withdraw_dfr(struct dfr_proposal *); void update_iface_ra_rdns(struct slaacd_iface *, struct radv *); void gen_rdns_proposal(struct slaacd_iface *, struct radv *); void free_rdns_proposal(struct rdns_proposal *); void withdraw_rdns(struct rdns_proposal *); void compose_rdns_proposal(uint32_t, int); void update_iface_ra(struct slaacd_iface *, struct radv *); void update_iface_ra_dfr(struct slaacd_iface *, struct radv *); void update_iface_ra_prefix(struct slaacd_iface *, struct radv *, struct radv_prefix *prefix); void address_proposal_timeout(int, short, void *); void dfr_proposal_timeout(int, short, void *); void rdns_proposal_timeout(int, short, void *); void iface_timeout(int, short, void *); struct radv *find_ra(struct slaacd_iface *, struct sockaddr_in6 *); struct address_proposal *find_address_proposal_by_addr(struct slaacd_iface *, struct sockaddr_in6 *); struct dfr_proposal *find_dfr_proposal_by_gw(struct slaacd_iface *, struct sockaddr_in6 *); struct rdns_proposal *find_rdns_proposal_by_gw(struct slaacd_iface *, struct sockaddr_in6 *); struct radv_prefix *find_prefix(struct radv *, struct in6_addr *, uint8_t); int engine_imsg_compose_main(int, pid_t, void *, uint16_t); uint32_t real_lifetime(struct timespec *, uint32_t); void merge_dad_couters(struct radv *, struct radv *); static struct imsgev *iev_frontend; static struct imsgev *iev_main; int64_t proposal_id; #define CASE(x) case x : return #x #ifndef SMALL static const char* if_state_name(enum if_state ifs) { switch (ifs) { CASE(IF_DOWN); CASE(IF_INIT); CASE(IF_BOUND); } } static const char* proposal_state_name(enum proposal_state ps) { switch (ps) { CASE(PROPOSAL_IF_DOWN); CASE(PROPOSAL_NOT_CONFIGURED); CASE(PROPOSAL_CONFIGURED); CASE(PROPOSAL_NEARLY_EXPIRED); CASE(PROPOSAL_WITHDRAWN); CASE(PROPOSAL_DUPLICATED); CASE(PROPOSAL_STALE); } } #endif void engine_sig_handler(int sig, short event, void *arg) { /* * Normal signal handler rules don't apply because libevent * decouples for us. */ switch (sig) { case SIGINT: case SIGTERM: engine_shutdown(); default: fatalx("unexpected signal"); } } void engine(int debug, int verbose) { struct event ev_sigint, ev_sigterm; struct passwd *pw; log_init(debug, LOG_DAEMON); log_setverbose(verbose); if ((pw = getpwnam(SLAACD_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(&slaacd_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 slaacd_iface *iface; struct imsg_ra ra; struct address_proposal *addr_proposal = NULL; struct dfr_proposal *dfr_proposal = NULL; struct imsg_del_addr del_addr; struct imsg_del_route del_route; struct imsg_dup_addr dup_addr; ssize_t n; int shut = 0; #ifndef SMALL int verbose; #endif /* SMALL */ uint32_t if_index, type; 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; type = imsg_get_type(&imsg); switch (type) { #ifndef SMALL case IMSG_CTL_LOG_VERBOSE: if (imsg_get_data(&imsg, &verbose, sizeof(verbose)) == -1) fatalx("%s: invalid %s", __func__, i2s(type)); log_setverbose(verbose); break; case IMSG_CTL_SHOW_INTERFACE_INFO: if (imsg_get_data(&imsg, &if_index, sizeof(if_index)) == -1) fatalx("%s: invalid %s", __func__, i2s(type)); engine_showinfo_ctl(imsg_get_pid(&imsg), if_index); break; #endif /* SMALL */ case IMSG_REMOVE_IF: if (imsg_get_data(&imsg, &if_index, sizeof(if_index)) == -1) fatalx("%s: invalid %s", __func__, i2s(type)); remove_slaacd_iface(if_index); break; case IMSG_RA: if (imsg_get_data(&imsg, &ra, sizeof(ra)) == -1) fatalx("%s: invalid %s", __func__, i2s(type)); iface = get_slaacd_iface_by_id(ra.if_index); /* * Ignore unsolicitated router advertisements * if we think the interface is still down. * Otherwise we confuse the state machine. */ if (iface != NULL && iface->state != IF_DOWN) parse_ra(iface, &ra); break; case IMSG_CTL_SEND_SOLICITATION: if (imsg_get_data(&imsg, &if_index, sizeof(if_index)) == -1) fatalx("%s: invalid %s", __func__, i2s(type)); iface = get_slaacd_iface_by_id(if_index); if (iface == NULL) log_warnx("requested to send solicitation on " "non-autoconf interface: %u", if_index); else { iface->last_sol.tv_sec = 0; /* no rate limit */ request_solicitation(iface); } break; case IMSG_DEL_ADDRESS: if (imsg_get_data(&imsg, &del_addr, sizeof(del_addr)) == -1) fatalx("%s: invalid %s", __func__, i2s(type)); iface = get_slaacd_iface_by_id(del_addr.if_index); if (iface == NULL) { log_debug("IMSG_DEL_ADDRESS: unknown interface" ", ignoring"); break; } addr_proposal = find_address_proposal_by_addr(iface, &del_addr.addr); /* * If it's in state PROPOSAL_WITHDRAWN we just * deleted it ourself but want to keep it around * so we can renew it */ if (addr_proposal && addr_proposal->state != PROPOSAL_WITHDRAWN) free_address_proposal(addr_proposal); break; case IMSG_DEL_ROUTE: if (imsg_get_data(&imsg, &del_route, sizeof(del_route)) == -1) fatalx("%s: invalid %s", __func__, i2s(type)); iface = get_slaacd_iface_by_id(del_route.if_index); if (iface == NULL) { log_debug("IMSG_DEL_ROUTE: unknown interface" ", ignoring"); break; } dfr_proposal = find_dfr_proposal_by_gw(iface, &del_route.gw); if (dfr_proposal) { dfr_proposal->state = PROPOSAL_WITHDRAWN; free_dfr_proposal(dfr_proposal); } break; case IMSG_DUP_ADDRESS: if (imsg_get_data(&imsg, &dup_addr, sizeof(dup_addr)) == -1) fatalx("%s: invalid %s", __func__, i2s(type)); iface = get_slaacd_iface_by_id(dup_addr.if_index); if (iface == NULL) { log_debug("IMSG_DUP_ADDRESS: unknown interface" ", ignoring"); break; } addr_proposal = find_address_proposal_by_addr(iface, &dup_addr.addr); if (addr_proposal) addr_proposal_state_transition(addr_proposal, PROPOSAL_DUPLICATED); break; case IMSG_REPROPOSE_RDNS: LIST_FOREACH (iface, &slaacd_interfaces, entries) compose_rdns_proposal(iface->if_index, iface->rdomain); break; default: log_debug("%s: unexpected imsg %d", __func__, type); break; } imsg_free(&imsg); } if (!shut) imsg_event_add(iev); else { /* This pipe is dead. Remove its event handler. */ event_del(&iev->ev); event_loopexit(NULL); } } void engine_dispatch_main(int fd, short event, void *bula) { struct imsg imsg; struct imsgev *iev = bula; struct imsgbuf *ibuf = &iev->ibuf; struct imsg_ifinfo imsg_ifinfo; ssize_t n; uint32_t type; 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; type = imsg_get_type(&imsg); switch (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_UPDATE_IF: if (imsg_get_data(&imsg, &imsg_ifinfo, sizeof(imsg_ifinfo)) == -1) fatalx("%s: invalid %s", __func__, i2s(type)); engine_update_iface(&imsg_ifinfo); break; default: log_debug("%s: unexpected imsg %d", __func__, type); break; } imsg_free(&imsg); } if (!shut) imsg_event_add(iev); else { /* This pipe is dead. Remove its event handler. */ event_del(&iev->ev); event_loopexit(NULL); } } #ifndef SMALL void send_interface_info(struct slaacd_iface *iface, pid_t pid) { struct ctl_engine_info cei; struct ctl_engine_info_ra cei_ra; struct ctl_engine_info_ra_prefix cei_ra_prefix; struct ctl_engine_info_ra_rdns cei_ra_rdns; struct ctl_engine_info_address_proposal cei_addr_proposal; struct ctl_engine_info_dfr_proposal cei_dfr_proposal; struct ctl_engine_info_rdns_proposal cei_rdns_proposal; struct radv *ra; struct radv_prefix *prefix; struct radv_rdns *rdns; struct address_proposal *addr_proposal; struct dfr_proposal *dfr_proposal; struct rdns_proposal *rdns_proposal; memset(&cei, 0, sizeof(cei)); cei.if_index = iface->if_index; cei.running = iface->running; cei.autoconf = iface->autoconf; cei.temporary = iface->temporary; cei.soii = iface->soii; memcpy(&cei.hw_address, &iface->hw_address, sizeof(struct ether_addr)); memcpy(&cei.ll_address, &iface->ll_address, sizeof(struct sockaddr_in6)); engine_imsg_compose_frontend(IMSG_CTL_SHOW_INTERFACE_INFO, pid, &cei, sizeof(cei)); LIST_FOREACH(ra, &iface->radvs, entries) { memset(&cei_ra, 0, sizeof(cei_ra)); memcpy(&cei_ra.from, &ra->from, sizeof(cei_ra.from)); memcpy(&cei_ra.when, &ra->when, sizeof(cei_ra.when)); memcpy(&cei_ra.uptime, &ra->uptime, sizeof(cei_ra.uptime)); cei_ra.curhoplimit = ra->curhoplimit; cei_ra.managed = ra->managed; cei_ra.other = ra->other; if (strlcpy(cei_ra.rpref, rpref_name[ra->rpref], sizeof( cei_ra.rpref)) >= sizeof(cei_ra.rpref)) log_warnx("truncated router preference"); cei_ra.router_lifetime = ra->router_lifetime; cei_ra.reachable_time = ra->reachable_time; cei_ra.retrans_time = ra->retrans_time; cei_ra.mtu = ra->mtu; engine_imsg_compose_frontend(IMSG_CTL_SHOW_INTERFACE_INFO_RA, pid, &cei_ra, sizeof(cei_ra)); LIST_FOREACH(prefix, &ra->prefixes, entries) { memset(&cei_ra_prefix, 0, sizeof(cei_ra_prefix)); cei_ra_prefix.prefix = prefix->prefix; cei_ra_prefix.prefix_len = prefix->prefix_len; cei_ra_prefix.onlink = prefix->onlink; cei_ra_prefix.autonomous = prefix->autonomous; cei_ra_prefix.vltime = prefix->vltime; cei_ra_prefix.pltime = prefix->pltime; engine_imsg_compose_frontend( IMSG_CTL_SHOW_INTERFACE_INFO_RA_PREFIX, pid, &cei_ra_prefix, sizeof(cei_ra_prefix)); } LIST_FOREACH(rdns, &ra->rdns_servers, entries) { memset(&cei_ra_rdns, 0, sizeof(cei_ra_rdns)); memcpy(&cei_ra_rdns.rdns, &rdns->rdns, sizeof(cei_ra_rdns.rdns)); cei_ra_rdns.lifetime = ra->rdns_lifetime; engine_imsg_compose_frontend( IMSG_CTL_SHOW_INTERFACE_INFO_RA_RDNS, pid, &cei_ra_rdns, sizeof(cei_ra_rdns)); } } if (!LIST_EMPTY(&iface->addr_proposals)) engine_imsg_compose_frontend( IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSALS, pid, NULL, 0); LIST_FOREACH(addr_proposal, &iface->addr_proposals, entries) { memset(&cei_addr_proposal, 0, sizeof(cei_addr_proposal)); cei_addr_proposal.id = addr_proposal->id; if (strlcpy(cei_addr_proposal.state, proposal_state_name(addr_proposal->state), sizeof(cei_addr_proposal.state)) >= sizeof(cei_addr_proposal.state)) log_warnx("truncated state name"); cei_addr_proposal.next_timeout = addr_proposal->timo.tv_sec; cei_addr_proposal.when = addr_proposal->when; cei_addr_proposal.uptime = addr_proposal->uptime; memcpy(&cei_addr_proposal.addr, &addr_proposal->addr, sizeof( cei_addr_proposal.addr)); memcpy(&cei_addr_proposal.prefix, &addr_proposal->prefix, sizeof(cei_addr_proposal.prefix)); cei_addr_proposal.prefix_len = addr_proposal->prefix_len; cei_addr_proposal.temporary = addr_proposal->temporary; cei_addr_proposal.vltime = addr_proposal->vltime; cei_addr_proposal.pltime = addr_proposal->pltime; engine_imsg_compose_frontend( IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSAL, pid, &cei_addr_proposal, sizeof(cei_addr_proposal)); } if (!LIST_EMPTY(&iface->dfr_proposals)) engine_imsg_compose_frontend( IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSALS, pid, NULL, 0); LIST_FOREACH(dfr_proposal, &iface->dfr_proposals, entries) { memset(&cei_dfr_proposal, 0, sizeof(cei_dfr_proposal)); cei_dfr_proposal.id = dfr_proposal->id; if (strlcpy(cei_dfr_proposal.state, proposal_state_name(dfr_proposal->state), sizeof(cei_dfr_proposal.state)) >= sizeof(cei_dfr_proposal.state)) log_warnx("truncated state name"); cei_dfr_proposal.next_timeout = dfr_proposal->timo.tv_sec; cei_dfr_proposal.when = dfr_proposal->when; cei_dfr_proposal.uptime = dfr_proposal->uptime; memcpy(&cei_dfr_proposal.addr, &dfr_proposal->addr, sizeof( cei_dfr_proposal.addr)); cei_dfr_proposal.router_lifetime = dfr_proposal->router_lifetime; if (strlcpy(cei_dfr_proposal.rpref, rpref_name[dfr_proposal->rpref], sizeof(cei_dfr_proposal.rpref)) >= sizeof(cei_dfr_proposal.rpref)) log_warnx("truncated router preference"); engine_imsg_compose_frontend( IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSAL, pid, &cei_dfr_proposal, sizeof(cei_dfr_proposal)); } if (!LIST_EMPTY(&iface->rdns_proposals)) engine_imsg_compose_frontend( IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSALS, pid, NULL, 0); LIST_FOREACH(rdns_proposal, &iface->rdns_proposals, entries) { memset(&cei_rdns_proposal, 0, sizeof(cei_rdns_proposal)); cei_rdns_proposal.id = rdns_proposal->id; if (strlcpy(cei_rdns_proposal.state, proposal_state_name(rdns_proposal->state), sizeof(cei_rdns_proposal.state)) >= sizeof(cei_rdns_proposal.state)) log_warnx("truncated state name"); cei_rdns_proposal.next_timeout = rdns_proposal->timo.tv_sec; cei_rdns_proposal.when = rdns_proposal->when; cei_rdns_proposal.uptime = rdns_proposal->uptime; memcpy(&cei_rdns_proposal.from, &rdns_proposal->from, sizeof( cei_rdns_proposal.from)); cei_rdns_proposal.rdns_count = rdns_proposal->rdns_count; memcpy(&cei_rdns_proposal.rdns, &rdns_proposal->rdns, sizeof(cei_rdns_proposal.rdns)); cei_rdns_proposal.rdns_lifetime = rdns_proposal->rdns_lifetime; engine_imsg_compose_frontend( IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSAL, pid, &cei_rdns_proposal, sizeof(cei_rdns_proposal)); } } void engine_showinfo_ctl(pid_t pid, uint32_t if_index) { struct slaacd_iface *iface; if (if_index == 0) { LIST_FOREACH (iface, &slaacd_interfaces, entries) send_interface_info(iface, pid); } else { if ((iface = get_slaacd_iface_by_id(if_index)) != NULL) send_interface_info(iface, pid); } engine_imsg_compose_frontend(IMSG_CTL_END, pid, NULL, 0); } #endif /* SMALL */ struct slaacd_iface* get_slaacd_iface_by_id(uint32_t if_index) { struct slaacd_iface *iface; LIST_FOREACH (iface, &slaacd_interfaces, entries) { if (iface->if_index == if_index) return (iface); } return (NULL); } void remove_slaacd_iface(uint32_t if_index) { struct slaacd_iface *iface; struct radv *ra; struct address_proposal *addr_proposal; struct dfr_proposal *dfr_proposal; struct rdns_proposal *rdns_proposal; iface = get_slaacd_iface_by_id(if_index); if (iface == NULL) return; LIST_REMOVE(iface, entries); while(!LIST_EMPTY(&iface->radvs)) { ra = LIST_FIRST(&iface->radvs); LIST_REMOVE(ra, entries); free_ra(ra); } while(!LIST_EMPTY(&iface->addr_proposals)) { addr_proposal = LIST_FIRST(&iface->addr_proposals); free_address_proposal(addr_proposal); } while(!LIST_EMPTY(&iface->dfr_proposals)) { dfr_proposal = LIST_FIRST(&iface->dfr_proposals); free_dfr_proposal(dfr_proposal); } while(!LIST_EMPTY(&iface->rdns_proposals)) { rdns_proposal = LIST_FIRST(&iface->rdns_proposals); free_rdns_proposal(rdns_proposal); } compose_rdns_proposal(iface->if_index, iface->rdomain); evtimer_del(&iface->timer); free(iface); } void free_ra(struct radv *ra) { struct radv_prefix *prefix; struct radv_rdns *rdns; if (ra == NULL) return; evtimer_del(&ra->timer); while (!LIST_EMPTY(&ra->prefixes)) { prefix = LIST_FIRST(&ra->prefixes); LIST_REMOVE(prefix, entries); free(prefix); } while (!LIST_EMPTY(&ra->rdns_servers)) { rdns = LIST_FIRST(&ra->rdns_servers); LIST_REMOVE(rdns, entries); free(rdns); } free(ra); } void iface_state_transition(struct slaacd_iface *iface, enum if_state new_state) { enum if_state old_state = iface->state; struct address_proposal *addr_proposal; struct dfr_proposal *dfr_proposal; struct rdns_proposal *rdns_proposal; iface->state = new_state; switch (new_state) { case IF_DOWN: if (old_state != IF_DOWN) { LIST_FOREACH (addr_proposal, &iface->addr_proposals, entries) addr_proposal_state_transition(addr_proposal, PROPOSAL_IF_DOWN); LIST_FOREACH (dfr_proposal, &iface->dfr_proposals, entries) dfr_proposal_state_transition(dfr_proposal, PROPOSAL_IF_DOWN); LIST_FOREACH (rdns_proposal, &iface->rdns_proposals, entries) rdns_proposal_state_transition(rdns_proposal, PROPOSAL_IF_DOWN); } /* nothing else to do until interface comes back up */ iface->timo.tv_sec = -1; break; case IF_INIT: switch (old_state) { case IF_INIT: iface->probes++; break; case IF_DOWN: LIST_FOREACH (addr_proposal, &iface->addr_proposals, entries) addr_proposal_state_transition(addr_proposal, PROPOSAL_WITHDRAWN); LIST_FOREACH (dfr_proposal, &iface->dfr_proposals, entries) dfr_proposal_state_transition(dfr_proposal, PROPOSAL_WITHDRAWN); LIST_FOREACH (rdns_proposal, &iface->rdns_proposals, entries) rdns_proposal_state_transition(rdns_proposal, PROPOSAL_WITHDRAWN); default: iface->probes = 0; } if (iface->probes < MAX_RTR_SOLICITATIONS) { iface->timo.tv_sec = RTR_SOLICITATION_INTERVAL; request_solicitation(iface); } else /* no router available, stop probing */ iface->timo.tv_sec = -1; break; case IF_BOUND: iface->timo.tv_sec = -1; break; } if (log_getverbose()) { char ifnamebuf[IF_NAMESIZE], *if_name; 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 addr_proposal_state_transition(struct address_proposal *addr_proposal, enum proposal_state new_state) { enum proposal_state old_state = addr_proposal->state; struct slaacd_iface *iface; uint32_t lifetime; addr_proposal->state = new_state; if ((iface = get_slaacd_iface_by_id(addr_proposal->if_index)) == NULL) return; switch (addr_proposal->state) { case PROPOSAL_IF_DOWN: if (old_state == PROPOSAL_IF_DOWN) { withdraw_addr(addr_proposal); addr_proposal->timo.tv_sec = -1; } else { addr_proposal->timo.tv_sec = real_lifetime(&addr_proposal->uptime, addr_proposal->vltime); } break; case PROPOSAL_NOT_CONFIGURED: break; case PROPOSAL_CONFIGURED: lifetime = real_lifetime(&addr_proposal->uptime, addr_proposal->pltime); if (lifetime == 0) lifetime = real_lifetime(&addr_proposal->uptime, addr_proposal->vltime); if (lifetime > MAX_RTR_SOLICITATIONS * (RTR_SOLICITATION_INTERVAL + 1)) addr_proposal->timo.tv_sec = lifetime - MAX_RTR_SOLICITATIONS * RTR_SOLICITATION_INTERVAL; else addr_proposal->timo.tv_sec = RTR_SOLICITATION_INTERVAL; break; case PROPOSAL_NEARLY_EXPIRED: lifetime = real_lifetime(&addr_proposal->uptime, addr_proposal->pltime); if (lifetime == 0) lifetime = real_lifetime(&addr_proposal->uptime, addr_proposal->vltime); if (lifetime > MAX_RTR_SOLICITATIONS * (RTR_SOLICITATION_INTERVAL + 1)) addr_proposal->timo.tv_sec = lifetime - MAX_RTR_SOLICITATIONS * RTR_SOLICITATION_INTERVAL; else addr_proposal->timo.tv_sec = RTR_SOLICITATION_INTERVAL; request_solicitation(iface); break; case PROPOSAL_WITHDRAWN: withdraw_addr(addr_proposal); addr_proposal->timo.tv_sec = MAX_RTR_SOLICITATIONS * RTR_SOLICITATION_INTERVAL; break; case PROPOSAL_DUPLICATED: addr_proposal->timo.tv_sec = 0; break; case PROPOSAL_STALE: addr_proposal->timo.tv_sec = 0; /* remove immediately */ break; } if (log_getverbose()) { char ifnamebuf[IF_NAMESIZE], *if_name; if_name = if_indextoname(addr_proposal->if_index, ifnamebuf); log_debug("%s[%s] %s -> %s, timo: %lld", __func__, if_name == NULL ? "?" : if_name, proposal_state_name(old_state), proposal_state_name(new_state), addr_proposal->timo.tv_sec); } if (addr_proposal->timo.tv_sec == -1) { if (evtimer_pending(&addr_proposal->timer, NULL)) evtimer_del(&addr_proposal->timer); } else evtimer_add(&addr_proposal->timer, &addr_proposal->timo); } void dfr_proposal_state_transition(struct dfr_proposal *dfr_proposal, enum proposal_state new_state) { enum proposal_state old_state = dfr_proposal->state; struct slaacd_iface *iface; uint32_t lifetime; dfr_proposal->state = new_state; if ((iface = get_slaacd_iface_by_id(dfr_proposal->if_index)) == NULL) return; switch (dfr_proposal->state) { case PROPOSAL_IF_DOWN: if (old_state == PROPOSAL_IF_DOWN) { withdraw_dfr(dfr_proposal); dfr_proposal->timo.tv_sec = -1; } else { dfr_proposal->timo.tv_sec = real_lifetime(&dfr_proposal->uptime, dfr_proposal->router_lifetime); } break; case PROPOSAL_NOT_CONFIGURED: break; case PROPOSAL_CONFIGURED: lifetime = real_lifetime(&dfr_proposal->uptime, dfr_proposal->router_lifetime); if (lifetime > MAX_RTR_SOLICITATIONS * (RTR_SOLICITATION_INTERVAL + 1)) dfr_proposal->timo.tv_sec = lifetime - MAX_RTR_SOLICITATIONS * RTR_SOLICITATION_INTERVAL; else dfr_proposal->timo.tv_sec = RTR_SOLICITATION_INTERVAL; break; case PROPOSAL_NEARLY_EXPIRED: lifetime = real_lifetime(&dfr_proposal->uptime, dfr_proposal->router_lifetime); if (lifetime > MAX_RTR_SOLICITATIONS * (RTR_SOLICITATION_INTERVAL + 1)) dfr_proposal->timo.tv_sec = lifetime - MAX_RTR_SOLICITATIONS * RTR_SOLICITATION_INTERVAL; else dfr_proposal->timo.tv_sec = RTR_SOLICITATION_INTERVAL; request_solicitation(iface); break; case PROPOSAL_WITHDRAWN: withdraw_dfr(dfr_proposal); dfr_proposal->timo.tv_sec = MAX_RTR_SOLICITATIONS * RTR_SOLICITATION_INTERVAL; break; case PROPOSAL_STALE: dfr_proposal->timo.tv_sec = 0; /* remove immediately */ break; case PROPOSAL_DUPLICATED: fatalx("invalid dfr state: PROPOSAL_DUPLICATED"); break; } if (log_getverbose()) { char ifnamebuf[IF_NAMESIZE], *if_name; if_name = if_indextoname(dfr_proposal->if_index, ifnamebuf); log_debug("%s[%s] %s -> %s, timo: %lld", __func__, if_name == NULL ? "?" : if_name, proposal_state_name(old_state), proposal_state_name(new_state), dfr_proposal->timo.tv_sec); } if (dfr_proposal->timo.tv_sec == -1) { if (evtimer_pending(&dfr_proposal->timer, NULL)) evtimer_del(&dfr_proposal->timer); } else evtimer_add(&dfr_proposal->timer, &dfr_proposal->timo); } void rdns_proposal_state_transition(struct rdns_proposal *rdns_proposal, enum proposal_state new_state) { enum proposal_state old_state = rdns_proposal->state; struct slaacd_iface *iface; uint32_t lifetime; rdns_proposal->state = new_state; if ((iface = get_slaacd_iface_by_id(rdns_proposal->if_index)) == NULL) return; switch (rdns_proposal->state) { case PROPOSAL_IF_DOWN: if (old_state == PROPOSAL_IF_DOWN) { withdraw_rdns(rdns_proposal); rdns_proposal->timo.tv_sec = -1; } else { rdns_proposal->timo.tv_sec = real_lifetime(&rdns_proposal->uptime, rdns_proposal->rdns_lifetime); } break; case PROPOSAL_NOT_CONFIGURED: break; case PROPOSAL_CONFIGURED: lifetime = real_lifetime(&rdns_proposal->uptime, rdns_proposal->rdns_lifetime); if (lifetime > MAX_RTR_SOLICITATIONS * (RTR_SOLICITATION_INTERVAL + 1)) rdns_proposal->timo.tv_sec = lifetime - MAX_RTR_SOLICITATIONS * RTR_SOLICITATION_INTERVAL; else rdns_proposal->timo.tv_sec = RTR_SOLICITATION_INTERVAL; break; case PROPOSAL_NEARLY_EXPIRED: lifetime = real_lifetime(&rdns_proposal->uptime, rdns_proposal->rdns_lifetime); if (lifetime > MAX_RTR_SOLICITATIONS * (RTR_SOLICITATION_INTERVAL + 1)) rdns_proposal->timo.tv_sec = lifetime - MAX_RTR_SOLICITATIONS * RTR_SOLICITATION_INTERVAL; else rdns_proposal->timo.tv_sec = RTR_SOLICITATION_INTERVAL; request_solicitation(iface); break; case PROPOSAL_WITHDRAWN: withdraw_rdns(rdns_proposal); rdns_proposal->timo.tv_sec = MAX_RTR_SOLICITATIONS * RTR_SOLICITATION_INTERVAL; break; case PROPOSAL_STALE: rdns_proposal->timo.tv_sec = 0; /* remove immediately */ break; case PROPOSAL_DUPLICATED: fatalx("invalid rdns state: PROPOSAL_DUPLICATED"); break; } if (log_getverbose()) { char ifnamebuf[IF_NAMESIZE], *if_name; if_name = if_indextoname(rdns_proposal->if_index, ifnamebuf); log_debug("%s[%s] %s -> %s, timo: %lld", __func__, if_name == NULL ? "?" : if_name, proposal_state_name(old_state), proposal_state_name(new_state), rdns_proposal->timo.tv_sec); } if (rdns_proposal->timo.tv_sec == -1) { if (evtimer_pending(&rdns_proposal->timer, NULL)) evtimer_del(&rdns_proposal->timer); } else evtimer_add(&rdns_proposal->timer, &rdns_proposal->timo); } void request_solicitation(struct slaacd_iface *iface) { struct timespec now, diff, sol_delay = {RTR_SOLICITATION_INTERVAL, 0}; clock_gettime(CLOCK_MONOTONIC, &now); timespecsub(&now, &iface->last_sol, &diff); if (timespeccmp(&diff, &sol_delay, <)) { log_debug("last solicitation less than %d seconds ago", RTR_SOLICITATION_INTERVAL); return; } iface->last_sol = now; engine_imsg_compose_frontend(IMSG_CTL_SEND_SOLICITATION, 0, &iface->if_index, sizeof(iface->if_index)); } void engine_update_iface(struct imsg_ifinfo *imsg_ifinfo) { struct slaacd_iface *iface; int need_refresh = 0; iface = get_slaacd_iface_by_id(imsg_ifinfo->if_index); if (iface == NULL) { if ((iface = calloc(1, sizeof(*iface))) == NULL) fatal("calloc"); iface->state = IF_DOWN; iface->timo.tv_usec = arc4random_uniform(1000000); evtimer_set(&iface->timer, iface_timeout, iface); iface->if_index = imsg_ifinfo->if_index; iface->rdomain = imsg_ifinfo->rdomain; iface->running = imsg_ifinfo->running; iface->link_state = imsg_ifinfo->link_state; iface->autoconf = imsg_ifinfo->autoconf; iface->temporary = imsg_ifinfo->temporary; iface->soii = imsg_ifinfo->soii; memcpy(&iface->hw_address, &imsg_ifinfo->hw_address, sizeof(struct ether_addr)); memcpy(&iface->ll_address, &imsg_ifinfo->ll_address, sizeof(struct sockaddr_in6)); memcpy(iface->soiikey, imsg_ifinfo->soiikey, sizeof(iface->soiikey)); LIST_INIT(&iface->radvs); LIST_INSERT_HEAD(&slaacd_interfaces, iface, entries); LIST_INIT(&iface->addr_proposals); LIST_INIT(&iface->dfr_proposals); LIST_INIT(&iface->rdns_proposals); need_refresh = 1; } else { memcpy(&iface->ll_address, &imsg_ifinfo->ll_address, sizeof(struct sockaddr_in6)); if (iface->autoconf != imsg_ifinfo->autoconf) { iface->autoconf = imsg_ifinfo->autoconf; need_refresh = 1; } if (iface->temporary != imsg_ifinfo->temporary) { iface->temporary = imsg_ifinfo->temporary; need_refresh = 1; } if (iface->soii != imsg_ifinfo->soii) { iface->soii = imsg_ifinfo->soii; need_refresh = 1; } 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 (memcmp(iface->soiikey, imsg_ifinfo->soiikey, sizeof(iface->soiikey)) != 0) { memcpy(iface->soiikey, imsg_ifinfo->soiikey, sizeof(iface->soiikey)); 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)) iface_state_transition(iface, IF_INIT); else iface_state_transition(iface, IF_DOWN); } void parse_ra(struct slaacd_iface *iface, struct imsg_ra *ra) { struct icmp6_hdr *icmp6_hdr; struct nd_router_advert *nd_ra; struct radv *radv; struct radv_prefix *prefix; struct radv_rdns *rdns; ssize_t len = ra->len; const char *hbuf; uint8_t *p; #ifndef SMALL if (log_getverbose() > 1) debug_log_ra(ra); #endif /* SMALL */ hbuf = sin6_to_str(&ra->from); if ((size_t)len < sizeof(struct icmp6_hdr)) { log_warnx("received too short message (%ld) from %s", len, hbuf); return; } p = ra->packet; icmp6_hdr = (struct icmp6_hdr *)p; if (icmp6_hdr->icmp6_type != ND_ROUTER_ADVERT) return; if (!IN6_IS_ADDR_LINKLOCAL(&ra->from.sin6_addr)) { log_debug("RA from non link local address %s", hbuf); return; } if ((size_t)len < sizeof(struct nd_router_advert)) { log_warnx("received too short message (%ld) from %s", len, hbuf); return; } if ((radv = calloc(1, sizeof(*radv))) == NULL) fatal("calloc"); LIST_INIT(&radv->prefixes); LIST_INIT(&radv->rdns_servers); radv->min_lifetime = UINT32_MAX; nd_ra = (struct nd_router_advert *)p; len -= sizeof(struct nd_router_advert); p += sizeof(struct nd_router_advert); log_debug("ICMPv6 type(%d), code(%d) from %s of length %ld", nd_ra->nd_ra_type, nd_ra->nd_ra_code, hbuf, len); if (nd_ra->nd_ra_code != 0) { log_warnx("invalid ICMPv6 code (%d) from %s", nd_ra->nd_ra_code, hbuf); goto err; } memcpy(&radv->from, &ra->from, sizeof(ra->from)); if (clock_gettime(CLOCK_REALTIME, &radv->when)) fatal("clock_gettime"); if (clock_gettime(CLOCK_MONOTONIC, &radv->uptime)) fatal("clock_gettime"); radv->curhoplimit = nd_ra->nd_ra_curhoplimit; radv->managed = nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED; radv->other = nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER; switch (nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_RTPREF_MASK) { case ND_RA_FLAG_RTPREF_HIGH: radv->rpref=HIGH; break; case ND_RA_FLAG_RTPREF_LOW: radv->rpref=LOW; break; case ND_RA_FLAG_RTPREF_MEDIUM: /* fallthrough */ default: radv->rpref=MEDIUM; break; } radv->router_lifetime = ntohs(nd_ra->nd_ra_router_lifetime); if (radv->router_lifetime != 0) radv->min_lifetime = radv->router_lifetime; radv->reachable_time = ntohl(nd_ra->nd_ra_reachable); radv->retrans_time = ntohl(nd_ra->nd_ra_retransmit); while ((size_t)len >= sizeof(struct nd_opt_hdr)) { struct nd_opt_hdr *nd_opt_hdr = (struct nd_opt_hdr *)p; struct nd_opt_prefix_info *prf; struct nd_opt_rdnss *rdnss; struct nd_opt_mtu *mtu; struct in6_addr *in6; int i; len -= sizeof(struct nd_opt_hdr); p += sizeof(struct nd_opt_hdr); if (nd_opt_hdr->nd_opt_len * 8 - 2 > len) { log_warnx("invalid option len: %u > %ld", nd_opt_hdr->nd_opt_len, len); goto err; } switch (nd_opt_hdr->nd_opt_type) { case ND_OPT_PREFIX_INFORMATION: if (nd_opt_hdr->nd_opt_len != 4) { log_warnx("invalid ND_OPT_PREFIX_INFORMATION: " "len != 4"); goto err; } if ((prefix = calloc(1, sizeof(*prefix))) == NULL) fatal("calloc"); prf = (struct nd_opt_prefix_info*) nd_opt_hdr; prefix->prefix = prf->nd_opt_pi_prefix; prefix->prefix_len = prf->nd_opt_pi_prefix_len; prefix->onlink = prf->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK; prefix->autonomous = prf->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO; prefix->vltime = ntohl(prf->nd_opt_pi_valid_time); prefix->pltime = ntohl(prf->nd_opt_pi_preferred_time); if (radv->min_lifetime > prefix->pltime) radv->min_lifetime = prefix->pltime; LIST_INSERT_HEAD(&radv->prefixes, prefix, entries); break; case ND_OPT_RDNSS: if (nd_opt_hdr->nd_opt_len < 3) { log_warnx("invalid ND_OPT_RDNSS: len < 24"); goto err; } if ((nd_opt_hdr->nd_opt_len - 1) % 2 != 0) { log_warnx("invalid ND_OPT_RDNSS: length with" "out header is not multiply of 16: %d", (nd_opt_hdr->nd_opt_len - 1) * 8); goto err; } rdnss = (struct nd_opt_rdnss*) nd_opt_hdr; radv->rdns_lifetime = ntohl( rdnss->nd_opt_rdnss_lifetime); if (radv->min_lifetime > radv->rdns_lifetime) radv->min_lifetime = radv->rdns_lifetime; in6 = (struct in6_addr*) (p + 6); for (i=0; i < (nd_opt_hdr->nd_opt_len - 1)/2; i++, in6++) { if ((rdns = calloc(1, sizeof(*rdns))) == NULL) fatal("calloc"); memcpy(&rdns->rdns, in6, sizeof(rdns->rdns)); LIST_INSERT_HEAD(&radv->rdns_servers, rdns, entries); } break; case ND_OPT_MTU: if (nd_opt_hdr->nd_opt_len != 1) { log_warnx("invalid ND_OPT_MTU: len != 1"); goto err; } mtu = (struct nd_opt_mtu*) nd_opt_hdr; radv->mtu = ntohl(mtu->nd_opt_mtu_mtu); /* path MTU cannot be less than IPV6_MMTU */ if (radv->mtu < IPV6_MMTU) { radv->mtu = 0; log_warnx("invalid advertised MTU"); } break; case ND_OPT_DNSSL: case ND_OPT_REDIRECTED_HEADER: case ND_OPT_SOURCE_LINKADDR: case ND_OPT_TARGET_LINKADDR: case ND_OPT_ROUTE_INFO: #if 0 log_debug("\tOption: %u (len: %u) not implemented", nd_opt_hdr->nd_opt_type, nd_opt_hdr->nd_opt_len * 8); #endif break; default: log_debug("\t\tUNKNOWN: %d", nd_opt_hdr->nd_opt_type); break; } len -= nd_opt_hdr->nd_opt_len * 8 - 2; p += nd_opt_hdr->nd_opt_len * 8 - 2; } update_iface_ra(iface, radv); return; err: free_ra(radv); } void gen_addr(struct slaacd_iface *iface, struct radv_prefix *prefix, struct address_proposal *addr_proposal, int temporary) { SHA2_CTX ctx; struct in6_addr iid; int i; u_int8_t digest[SHA512_DIGEST_LENGTH]; memset(&iid, 0, sizeof(iid)); /* from in6_ifadd() in nd6_rtr.c */ /* XXX from in6.h, guarded by #ifdef _KERNEL XXX nonstandard */ #define s6_addr32 __u6_addr.__u6_addr32 in6_prefixlen2mask(&addr_proposal->mask, addr_proposal->prefix_len); memset(&addr_proposal->addr, 0, sizeof(addr_proposal->addr)); addr_proposal->addr.sin6_family = AF_INET6; addr_proposal->addr.sin6_len = sizeof(addr_proposal->addr); memcpy(&addr_proposal->addr.sin6_addr, &prefix->prefix, sizeof(addr_proposal->addr.sin6_addr)); for (i = 0; i < 4; i++) addr_proposal->addr.sin6_addr.s6_addr32[i] &= addr_proposal->mask.s6_addr32[i]; if (temporary) { arc4random_buf(&iid.s6_addr, sizeof(iid.s6_addr)); } else if (iface->soii) { SHA512Init(&ctx); SHA512Update(&ctx, &prefix->prefix, sizeof(prefix->prefix)); SHA512Update(&ctx, &iface->hw_address, sizeof(iface->hw_address)); SHA512Update(&ctx, &prefix->dad_counter, sizeof(prefix->dad_counter)); SHA512Update(&ctx, addr_proposal->soiikey, sizeof(addr_proposal->soiikey)); SHA512Final(digest, &ctx); memcpy(&iid.s6_addr, digest + (sizeof(digest) - sizeof(iid.s6_addr)), sizeof(iid.s6_addr)); } else { /* This is safe, because we have a 64 prefix len */ memcpy(&iid.s6_addr, &iface->ll_address.sin6_addr, sizeof(iid.s6_addr)); } for (i = 0; i < 4; i++) addr_proposal->addr.sin6_addr.s6_addr32[i] |= (iid.s6_addr32[i] & ~addr_proposal->mask.s6_addr32[i]); #undef s6_addr32 } /* from sys/netinet6/in6.c */ 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]; } #ifndef SMALL /* from kame via ifconfig, where it's called prefix() */ int in6_mask2prefixlen(struct in6_addr *in6) { u_char *nam = (u_char *)in6; int byte, bit, plen = 0, size = sizeof(struct in6_addr); for (byte = 0; byte < size; byte++, plen += 8) if (nam[byte] != 0xff) break; if (byte == size) return (plen); for (bit = 7; bit != 0; bit--, plen++) if (!(nam[byte] & (1 << bit))) break; for (; bit != 0; bit--) if (nam[byte] & (1 << bit)) return (0); byte++; for (; byte < size; byte++) if (nam[byte]) return (0); return (plen); } void debug_log_ra(struct imsg_ra *ra) { struct nd_router_advert *nd_ra; ssize_t len = ra->len; char ntopbuf[INET6_ADDRSTRLEN]; const char *hbuf; uint8_t *p; hbuf = sin6_to_str(&ra->from); if (!IN6_IS_ADDR_LINKLOCAL(&ra->from.sin6_addr)) { log_warnx("RA from non link local address %s", hbuf); return; } if ((size_t)len < sizeof(struct nd_router_advert)) { log_warnx("received too short message (%ld) from %s", len, hbuf); return; } p = ra->packet; nd_ra = (struct nd_router_advert *)p; len -= sizeof(struct nd_router_advert); p += sizeof(struct nd_router_advert); log_debug("ICMPv6 type(%d), code(%d) from %s of length %ld", nd_ra->nd_ra_type, nd_ra->nd_ra_code, hbuf, len); if (nd_ra->nd_ra_type != ND_ROUTER_ADVERT) { log_warnx("invalid ICMPv6 type (%d) from %s", nd_ra->nd_ra_type, hbuf); return; } if (nd_ra->nd_ra_code != 0) { log_warnx("invalid ICMPv6 code (%d) from %s", nd_ra->nd_ra_code, hbuf); return; } log_debug("---"); log_debug("RA from %s", hbuf); log_debug("\tCur Hop Limit: %u", nd_ra->nd_ra_curhoplimit); log_debug("\tManaged address configuration: %d", (nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) ? 1 : 0); log_debug("\tOther configuration: %d", (nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) ? 1 : 0); switch (nd_ra->nd_ra_flags_reserved & ND_RA_FLAG_RTPREF_MASK) { case ND_RA_FLAG_RTPREF_HIGH: log_debug("\tRouter Preference: high"); break; case ND_RA_FLAG_RTPREF_MEDIUM: log_debug("\tRouter Preference: medium"); break; case ND_RA_FLAG_RTPREF_LOW: log_debug("\tRouter Preference: low"); break; case ND_RA_FLAG_RTPREF_RSV: log_debug("\tRouter Preference: reserved"); break; } log_debug("\tRouter Lifetime: %hds", ntohs(nd_ra->nd_ra_router_lifetime)); log_debug("\tReachable Time: %ums", ntohl(nd_ra->nd_ra_reachable)); log_debug("\tRetrans Timer: %ums", ntohl(nd_ra->nd_ra_retransmit)); while ((size_t)len >= sizeof(struct nd_opt_hdr)) { struct nd_opt_hdr *nd_opt_hdr = (struct nd_opt_hdr *)p; struct nd_opt_mtu *mtu; struct nd_opt_prefix_info *prf; struct nd_opt_rdnss *rdnss; struct in6_addr *in6; int i; len -= sizeof(struct nd_opt_hdr); p += sizeof(struct nd_opt_hdr); if (nd_opt_hdr->nd_opt_len * 8 - 2 > len) { log_warnx("invalid option len: %u > %ld", nd_opt_hdr->nd_opt_len, len); return; } log_debug("\tOption: %u (len: %u)", nd_opt_hdr->nd_opt_type, nd_opt_hdr->nd_opt_len * 8); switch (nd_opt_hdr->nd_opt_type) { case ND_OPT_SOURCE_LINKADDR: if (nd_opt_hdr->nd_opt_len == 1) log_debug("\t\tND_OPT_SOURCE_LINKADDR: " "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); else log_debug("\t\tND_OPT_SOURCE_LINKADDR"); break; case ND_OPT_TARGET_LINKADDR: if (nd_opt_hdr->nd_opt_len == 1) log_debug("\t\tND_OPT_TARGET_LINKADDR: " "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); else log_debug("\t\tND_OPT_TARGET_LINKADDR"); break; case ND_OPT_PREFIX_INFORMATION: if (nd_opt_hdr->nd_opt_len != 4) { log_warnx("invalid ND_OPT_PREFIX_INFORMATION: " "len != 4"); return; } prf = (struct nd_opt_prefix_info*) nd_opt_hdr; log_debug("\t\tND_OPT_PREFIX_INFORMATION: %s/%u", inet_ntop(AF_INET6, &prf->nd_opt_pi_prefix, ntopbuf, INET6_ADDRSTRLEN), prf->nd_opt_pi_prefix_len); log_debug("\t\t\tOn-link: %d", prf->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK ? 1:0); log_debug("\t\t\tAutonomous address-configuration: %d", prf->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO ? 1 : 0); log_debug("\t\t\tvltime: %u", ntohl(prf->nd_opt_pi_valid_time)); log_debug("\t\t\tpltime: %u", ntohl(prf->nd_opt_pi_preferred_time)); break; case ND_OPT_REDIRECTED_HEADER: log_debug("\t\tND_OPT_REDIRECTED_HEADER"); break; case ND_OPT_MTU: if (nd_opt_hdr->nd_opt_len != 1) { log_warnx("invalid ND_OPT_MTU: len != 1"); return; } mtu = (struct nd_opt_mtu*) nd_opt_hdr; log_debug("\t\tND_OPT_MTU: %u", ntohl(mtu->nd_opt_mtu_mtu)); break; case ND_OPT_ROUTE_INFO: log_debug("\t\tND_OPT_ROUTE_INFO"); break; case ND_OPT_RDNSS: if (nd_opt_hdr->nd_opt_len < 3) { log_warnx("invalid ND_OPT_RDNSS: len < 24"); return; } if ((nd_opt_hdr->nd_opt_len - 1) % 2 != 0) { log_warnx("invalid ND_OPT_RDNSS: length with" "out header is not multiply of 16: %d", (nd_opt_hdr->nd_opt_len - 1) * 8); return; } rdnss = (struct nd_opt_rdnss*) nd_opt_hdr; log_debug("\t\tND_OPT_RDNSS: lifetime: %u", ntohl( rdnss->nd_opt_rdnss_lifetime)); in6 = (struct in6_addr*) (p + 6); for (i=0; i < (nd_opt_hdr->nd_opt_len - 1)/2; i++, in6++) { log_debug("\t\t\t%s", inet_ntop(AF_INET6, in6, ntopbuf, INET6_ADDRSTRLEN)); } break; default: log_debug("\t\tUNKNOWN: %d", nd_opt_hdr->nd_opt_type); break; } len -= nd_opt_hdr->nd_opt_len * 8 - 2; p += nd_opt_hdr->nd_opt_len * 8 - 2; } } #endif /* SMALL */ void update_iface_ra(struct slaacd_iface *iface, struct radv *ra) { struct radv *old_ra; struct radv_prefix *prefix; if ((old_ra = find_ra(iface, &ra->from)) == NULL) LIST_INSERT_HEAD(&iface->radvs, ra, entries); else { LIST_REPLACE(old_ra, ra, entries); merge_dad_couters(old_ra, ra); free_ra(old_ra); } update_iface_ra_dfr(iface, ra); LIST_FOREACH(prefix, &ra->prefixes, entries) { if (!prefix->autonomous || prefix->vltime == 0 || prefix->pltime > prefix->vltime || IN6_IS_ADDR_LINKLOCAL(&prefix->prefix)) continue; update_iface_ra_prefix(iface, ra, prefix); } update_iface_ra_rdns(iface, ra); } void update_iface_ra_dfr(struct slaacd_iface *iface, struct radv *ra) { struct dfr_proposal *dfr_proposal; dfr_proposal = find_dfr_proposal_by_gw(iface, &ra->from); if (ra->router_lifetime == 0) { free_dfr_proposal(dfr_proposal); return; } if (!dfr_proposal) { /* new proposal */ gen_dfr_proposal(iface, ra); return; } dfr_proposal->when = ra->when; dfr_proposal->uptime = ra->uptime; dfr_proposal->router_lifetime = ra->router_lifetime; log_debug("%s, dfr state: %s, rl: %d", __func__, proposal_state_name(dfr_proposal->state), real_lifetime(&dfr_proposal->uptime, dfr_proposal->router_lifetime)); switch (dfr_proposal->state) { case PROPOSAL_CONFIGURED: case PROPOSAL_NEARLY_EXPIRED: /* routes do not expire in the kernel, update timeout */ dfr_proposal_state_transition(dfr_proposal, PROPOSAL_CONFIGURED); break; case PROPOSAL_IF_DOWN: case PROPOSAL_WITHDRAWN: log_debug("updating dfr"); configure_dfr(dfr_proposal); break; default: log_debug("%s: iface %d: %s", __func__, iface->if_index, sin6_to_str(&dfr_proposal->addr)); break; } } void update_iface_ra_prefix(struct slaacd_iface *iface, struct radv *ra, struct radv_prefix *prefix) { struct address_proposal *addr_proposal; uint32_t pltime, vltime; int found, found_temporary, duplicate_found; found = found_temporary = duplicate_found = 0; if (!!iface->autoconf != !!iface->temporary) { struct address_proposal *tmp; /* * If only the autoconf or temporary flag is set, check if we * have the "other kind" of address configured and delete it. */ LIST_FOREACH_SAFE (addr_proposal, &iface->addr_proposals, entries, tmp) { if ((!addr_proposal->temporary && !iface->autoconf) || (addr_proposal->temporary && !iface->temporary)) free_address_proposal(addr_proposal); } } LIST_FOREACH(addr_proposal, &iface->addr_proposals, entries) { if (prefix->prefix_len == addr_proposal-> prefix_len && memcmp(&prefix->prefix, &addr_proposal->prefix, sizeof(struct in6_addr)) != 0) continue; if (memcmp(&addr_proposal->hw_address, &iface->hw_address, sizeof(addr_proposal->hw_address)) != 0) continue; if (memcmp(&addr_proposal->soiikey, &iface->soiikey, sizeof(addr_proposal->soiikey)) != 0) continue; if (addr_proposal->state == PROPOSAL_DUPLICATED) { duplicate_found = 1; continue; } vltime = prefix->vltime; if (addr_proposal->temporary) { struct timespec now; int64_t ltime, mtime; if (clock_gettime(CLOCK_MONOTONIC, &now)) fatal("clock_gettime"); mtime = addr_proposal->created.tv_sec + PRIV_PREFERRED_LIFETIME - addr_proposal->desync_factor; ltime = MINIMUM(mtime, now.tv_sec + prefix->pltime) - now.tv_sec; pltime = ltime > 0 ? ltime : 0; ltime = MINIMUM(addr_proposal->created.tv_sec + PRIV_VALID_LIFETIME, now.tv_sec + vltime) - now.tv_sec; vltime = ltime > 0 ? ltime : 0; if ((mtime - now.tv_sec) > PRIV_REGEN_ADVANCE) found_temporary = 1; } else { pltime = prefix->pltime; found = 1; } addr_proposal->from = ra->from; addr_proposal->when = ra->when; addr_proposal->uptime = ra->uptime; addr_proposal->vltime = vltime; addr_proposal->pltime = pltime; if (ra->mtu == iface->cur_mtu) addr_proposal->mtu = 0; else { addr_proposal->mtu = ra->mtu; iface->cur_mtu = ra->mtu; } log_debug("%s, addr state: %s", __func__, proposal_state_name(addr_proposal->state)); switch (addr_proposal->state) { case PROPOSAL_CONFIGURED: case PROPOSAL_NEARLY_EXPIRED: case PROPOSAL_IF_DOWN: case PROPOSAL_WITHDRAWN: log_debug("updating address"); configure_address(addr_proposal); break; default: log_debug("%s: iface %d: %s", __func__, iface->if_index, sin6_to_str(&addr_proposal->addr)); break; } } if (!found && iface->autoconf && duplicate_found && iface->soii) { prefix->dad_counter++; log_debug("%s dad_counter: %d", __func__, prefix->dad_counter); gen_address_proposal(iface, ra, prefix, 0); } else if (!found && iface->autoconf && (iface->soii || prefix->prefix_len <= 64)) /* new proposal */ gen_address_proposal(iface, ra, prefix, 0); /* temporary addresses do not depend on eui64 */ if (!found_temporary && iface->temporary) { if (prefix->pltime >= PRIV_REGEN_ADVANCE) { /* new temporary proposal */ gen_address_proposal(iface, ra, prefix, 1); } else if (prefix->pltime > 0) { log_warnx("%s: pltime from %s is too small: %d < %d; " "not generating temporary address", __func__, sin6_to_str(&ra->from), prefix->pltime, PRIV_REGEN_ADVANCE); } } } void update_iface_ra_rdns(struct slaacd_iface *iface, struct radv *ra) { struct rdns_proposal *rdns_proposal; struct radv_rdns *radv_rdns; struct in6_addr rdns[MAX_RDNS_COUNT]; int rdns_count; rdns_proposal = find_rdns_proposal_by_gw(iface, &ra->from); if (!rdns_proposal) { /* new proposal */ if (!LIST_EMPTY(&ra->rdns_servers)) gen_rdns_proposal(iface, ra); return; } rdns_count = 0; memset(&rdns, 0, sizeof(rdns)); LIST_FOREACH(radv_rdns, &ra->rdns_servers, entries) { memcpy(&rdns[rdns_count++], &radv_rdns->rdns, sizeof(struct in6_addr)); if (rdns_proposal->rdns_count == MAX_RDNS_COUNT) break; } if (rdns_count == 0) { free_rdns_proposal(rdns_proposal); return; } if (rdns_proposal->rdns_count != rdns_count || memcmp(&rdns_proposal->rdns, &rdns, sizeof(rdns)) != 0) { memcpy(&rdns_proposal->rdns, &rdns, sizeof(rdns)); rdns_proposal->rdns_count = rdns_count; rdns_proposal->state = PROPOSAL_NOT_CONFIGURED; } rdns_proposal->when = ra->when; rdns_proposal->uptime = ra->uptime; rdns_proposal->rdns_lifetime = ra->rdns_lifetime; log_debug("%s, rdns state: %s, rl: %d", __func__, proposal_state_name(rdns_proposal->state), real_lifetime(&rdns_proposal->uptime, rdns_proposal->rdns_lifetime)); switch (rdns_proposal->state) { case PROPOSAL_CONFIGURED: case PROPOSAL_NEARLY_EXPIRED: /* rdns are not expired by the kernel, update timeout */ rdns_proposal_state_transition(rdns_proposal, PROPOSAL_CONFIGURED); break; case PROPOSAL_IF_DOWN: case PROPOSAL_WITHDRAWN: case PROPOSAL_NOT_CONFIGURED: log_debug("updating rdns"); rdns_proposal_state_transition(rdns_proposal, PROPOSAL_CONFIGURED); compose_rdns_proposal(rdns_proposal->if_index, rdns_proposal->rdomain); break; default: log_debug("%s: iface %d: %s", __func__, iface->if_index, sin6_to_str(&rdns_proposal->from)); break; } } void configure_address(struct address_proposal *addr_proposal) { struct imsg_configure_address address; struct slaacd_iface *iface; log_debug("%s: %d", __func__, addr_proposal->if_index); address.if_index = addr_proposal->if_index; memcpy(&address.addr, &addr_proposal->addr, sizeof(address.addr)); memcpy(&address.gw, &addr_proposal->from, sizeof(address.gw)); memcpy(&address.mask, &addr_proposal->mask, sizeof(address.mask)); address.vltime = addr_proposal->vltime; address.pltime = addr_proposal->pltime; address.temporary = addr_proposal->temporary; address.mtu = addr_proposal->mtu; engine_imsg_compose_main(IMSG_CONFIGURE_ADDRESS, 0, &address, sizeof(address)); if ((iface = get_slaacd_iface_by_id(addr_proposal->if_index)) != NULL) iface_state_transition(iface, IF_BOUND); addr_proposal_state_transition(addr_proposal, PROPOSAL_CONFIGURED); } void gen_address_proposal(struct slaacd_iface *iface, struct radv *ra, struct radv_prefix *prefix, int temporary) { struct address_proposal *addr_proposal; const char *hbuf; if ((addr_proposal = calloc(1, sizeof(*addr_proposal))) == NULL) fatal("calloc"); addr_proposal->id = ++proposal_id; evtimer_set(&addr_proposal->timer, address_proposal_timeout, addr_proposal); addr_proposal->timo.tv_sec = 1; addr_proposal->timo.tv_usec = arc4random_uniform(1000000); addr_proposal->state = PROPOSAL_NOT_CONFIGURED; if (clock_gettime(CLOCK_MONOTONIC, &addr_proposal->created)) fatal("clock_gettime"); addr_proposal->when = ra->when; addr_proposal->uptime = ra->uptime; addr_proposal->if_index = iface->if_index; memcpy(&addr_proposal->from, &ra->from, sizeof(addr_proposal->from)); memcpy(&addr_proposal->hw_address, &iface->hw_address, sizeof(addr_proposal->hw_address)); memcpy(&addr_proposal->soiikey, &iface->soiikey, sizeof(addr_proposal->soiikey)); addr_proposal->temporary = temporary; memcpy(&addr_proposal->prefix, &prefix->prefix, sizeof(addr_proposal->prefix)); addr_proposal->prefix_len = prefix->prefix_len; if (temporary) { addr_proposal->vltime = MINIMUM(prefix->vltime, PRIV_VALID_LIFETIME); addr_proposal->desync_factor = arc4random_uniform(PRIV_MAX_DESYNC_FACTOR); addr_proposal->pltime = MINIMUM(prefix->pltime, PRIV_PREFERRED_LIFETIME - addr_proposal->desync_factor); } else { addr_proposal->vltime = prefix->vltime; addr_proposal->pltime = prefix->pltime; } if (ra->mtu == iface->cur_mtu) addr_proposal->mtu = 0; else { addr_proposal->mtu = ra->mtu; iface->cur_mtu = ra->mtu; } gen_addr(iface, prefix, addr_proposal, temporary); LIST_INSERT_HEAD(&iface->addr_proposals, addr_proposal, entries); configure_address(addr_proposal); hbuf = sin6_to_str(&addr_proposal->addr); log_debug("%s: iface %d: %s", __func__, iface->if_index, hbuf); } void free_address_proposal(struct address_proposal *addr_proposal) { if (addr_proposal == NULL) return; LIST_REMOVE(addr_proposal, entries); evtimer_del(&addr_proposal->timer); switch (addr_proposal->state) { case PROPOSAL_CONFIGURED: case PROPOSAL_NEARLY_EXPIRED: case PROPOSAL_STALE: withdraw_addr(addr_proposal); break; default: break; } free(addr_proposal); } void withdraw_addr(struct address_proposal *addr_proposal) { struct imsg_configure_address address; log_debug("%s: %d", __func__, addr_proposal->if_index); memset(&address, 0, sizeof(address)); address.if_index = addr_proposal->if_index; memcpy(&address.addr, &addr_proposal->addr, sizeof(address.addr)); engine_imsg_compose_main(IMSG_WITHDRAW_ADDRESS, 0, &address, sizeof(address)); } void gen_dfr_proposal(struct slaacd_iface *iface, struct radv *ra) { struct dfr_proposal *dfr_proposal; const char *hbuf; if ((dfr_proposal = calloc(1, sizeof(*dfr_proposal))) == NULL) fatal("calloc"); dfr_proposal->id = ++proposal_id; evtimer_set(&dfr_proposal->timer, dfr_proposal_timeout, dfr_proposal); dfr_proposal->timo.tv_sec = 1; dfr_proposal->timo.tv_usec = arc4random_uniform(1000000); dfr_proposal->state = PROPOSAL_NOT_CONFIGURED; dfr_proposal->when = ra->when; dfr_proposal->uptime = ra->uptime; dfr_proposal->if_index = iface->if_index; dfr_proposal->rdomain = iface->rdomain; memcpy(&dfr_proposal->addr, &ra->from, sizeof(dfr_proposal->addr)); dfr_proposal->router_lifetime = ra->router_lifetime; dfr_proposal->rpref = ra->rpref; LIST_INSERT_HEAD(&iface->dfr_proposals, dfr_proposal, entries); configure_dfr(dfr_proposal); hbuf = sin6_to_str(&dfr_proposal->addr); log_debug("%s: iface %d: %s", __func__, iface->if_index, hbuf); } void configure_dfr(struct dfr_proposal *dfr_proposal) { struct imsg_configure_dfr dfr; log_debug("%s: %d", __func__, dfr_proposal->if_index); dfr.if_index = dfr_proposal->if_index; dfr.rdomain = dfr_proposal->rdomain; memcpy(&dfr.addr, &dfr_proposal->addr, sizeof(dfr.addr)); dfr.router_lifetime = dfr_proposal->router_lifetime; engine_imsg_compose_main(IMSG_CONFIGURE_DFR, 0, &dfr, sizeof(dfr)); dfr_proposal_state_transition(dfr_proposal, PROPOSAL_CONFIGURED); } void withdraw_dfr(struct dfr_proposal *dfr_proposal) { struct imsg_configure_dfr dfr; log_debug("%s: %d", __func__, dfr_proposal->if_index); dfr.if_index = dfr_proposal->if_index; dfr.rdomain = dfr_proposal->rdomain; memcpy(&dfr.addr, &dfr_proposal->addr, sizeof(dfr.addr)); dfr.router_lifetime = dfr_proposal->router_lifetime; engine_imsg_compose_main(IMSG_WITHDRAW_DFR, 0, &dfr, sizeof(dfr)); } void free_dfr_proposal(struct dfr_proposal *dfr_proposal) { if (dfr_proposal == NULL) return; LIST_REMOVE(dfr_proposal, entries); evtimer_del(&dfr_proposal->timer); switch (dfr_proposal->state) { case PROPOSAL_CONFIGURED: case PROPOSAL_NEARLY_EXPIRED: case PROPOSAL_STALE: withdraw_dfr(dfr_proposal); break; default: break; } free(dfr_proposal); } void gen_rdns_proposal(struct slaacd_iface *iface, struct radv *ra) { struct rdns_proposal *rdns_proposal; struct radv_rdns *rdns; const char *hbuf; if ((rdns_proposal = calloc(1, sizeof(*rdns_proposal))) == NULL) fatal("calloc"); rdns_proposal->id = ++proposal_id; evtimer_set(&rdns_proposal->timer, rdns_proposal_timeout, rdns_proposal); rdns_proposal->timo.tv_sec = 1; rdns_proposal->timo.tv_usec = arc4random_uniform(1000000); rdns_proposal->state = PROPOSAL_NOT_CONFIGURED; rdns_proposal->when = ra->when; rdns_proposal->uptime = ra->uptime; rdns_proposal->if_index = iface->if_index; rdns_proposal->rdomain = iface->rdomain; memcpy(&rdns_proposal->from, &ra->from, sizeof(rdns_proposal->from)); rdns_proposal->rdns_lifetime = ra->rdns_lifetime; LIST_FOREACH(rdns, &ra->rdns_servers, entries) { memcpy(&rdns_proposal->rdns[rdns_proposal->rdns_count++], &rdns->rdns, sizeof(struct in6_addr)); if (rdns_proposal->rdns_count == MAX_RDNS_COUNT) break; } LIST_INSERT_HEAD(&iface->rdns_proposals, rdns_proposal, entries); compose_rdns_proposal(iface->if_index, iface->rdomain); hbuf = sin6_to_str(&rdns_proposal->from); log_debug("%s: iface %d: %s", __func__, iface->if_index, hbuf); } void compose_rdns_proposal(uint32_t if_index, int rdomain) { struct imsg_propose_rdns rdns; struct slaacd_iface *iface; struct rdns_proposal *rdns_proposal; int i; memset(&rdns, 0, sizeof(rdns)); rdns.if_index = if_index; rdns.rdomain = rdomain; if ((iface = get_slaacd_iface_by_id(if_index)) != NULL) { LIST_FOREACH(rdns_proposal, &iface->rdns_proposals, entries) { if (rdns_proposal->state == PROPOSAL_WITHDRAWN || rdns_proposal->state == PROPOSAL_STALE) continue; rdns_proposal_state_transition(rdns_proposal, PROPOSAL_CONFIGURED); for (i = 0; i < rdns_proposal->rdns_count && rdns.rdns_count < MAX_RDNS_COUNT; i++) { rdns.rdns[rdns.rdns_count++] = rdns_proposal->rdns[i]; } } } engine_imsg_compose_main(IMSG_PROPOSE_RDNS, 0, &rdns, sizeof(rdns)); } void free_rdns_proposal(struct rdns_proposal *rdns_proposal) { if (rdns_proposal == NULL) return; LIST_REMOVE(rdns_proposal, entries); evtimer_del(&rdns_proposal->timer); switch (rdns_proposal->state) { case PROPOSAL_CONFIGURED: case PROPOSAL_NEARLY_EXPIRED: case PROPOSAL_STALE: withdraw_rdns(rdns_proposal); break; default: break; } free(rdns_proposal); } void withdraw_rdns(struct rdns_proposal *rdns_proposal) { log_debug("%s: %d", __func__, rdns_proposal->if_index); rdns_proposal->state = PROPOSAL_WITHDRAWN; /* we have to re-propose all rdns servers, minus one */ compose_rdns_proposal(rdns_proposal->if_index, rdns_proposal->rdomain); } void address_proposal_timeout(int fd, short events, void *arg) { struct address_proposal *addr_proposal; struct slaacd_iface *iface = NULL; struct radv *ra = NULL; struct radv_prefix *prefix = NULL; const char *hbuf; addr_proposal = (struct address_proposal *)arg; hbuf = sin6_to_str(&addr_proposal->addr); log_debug("%s: iface %d: %s [%s], priv: %s", __func__, addr_proposal->if_index, hbuf, proposal_state_name(addr_proposal->state), addr_proposal->temporary ? "y" : "n"); switch (addr_proposal->state) { case PROPOSAL_IF_DOWN: addr_proposal_state_transition(addr_proposal, PROPOSAL_STALE); break; case PROPOSAL_CONFIGURED: addr_proposal_state_transition(addr_proposal, PROPOSAL_NEARLY_EXPIRED); break; case PROPOSAL_NEARLY_EXPIRED: if (real_lifetime(&addr_proposal->uptime, addr_proposal->vltime) > 0) addr_proposal_state_transition(addr_proposal, PROPOSAL_NEARLY_EXPIRED); else addr_proposal_state_transition(addr_proposal, PROPOSAL_STALE); break; case PROPOSAL_DUPLICATED: iface = get_slaacd_iface_by_id(addr_proposal->if_index); if (iface != NULL) ra = find_ra(iface, &addr_proposal->from); if (ra != NULL) prefix = find_prefix(ra, &addr_proposal->prefix, addr_proposal->prefix_len); if (prefix != NULL) { if (!addr_proposal->temporary) { prefix->dad_counter++; gen_address_proposal(iface, ra, prefix, 0); } else gen_address_proposal(iface, ra, prefix, 1); } addr_proposal_state_transition(addr_proposal, PROPOSAL_STALE); break; case PROPOSAL_STALE: free_address_proposal(addr_proposal); addr_proposal = NULL; break; case PROPOSAL_WITHDRAWN: free_address_proposal(addr_proposal); addr_proposal = NULL; break; default: log_debug("%s: unhandled state: %s", __func__, proposal_state_name(addr_proposal->state)); } } void dfr_proposal_timeout(int fd, short events, void *arg) { struct dfr_proposal *dfr_proposal; const char *hbuf; dfr_proposal = (struct dfr_proposal *)arg; hbuf = sin6_to_str(&dfr_proposal->addr); log_debug("%s: iface %d: %s [%s]", __func__, dfr_proposal->if_index, hbuf, proposal_state_name(dfr_proposal->state)); switch (dfr_proposal->state) { case PROPOSAL_IF_DOWN: dfr_proposal_state_transition(dfr_proposal, PROPOSAL_STALE); break; case PROPOSAL_CONFIGURED: dfr_proposal_state_transition(dfr_proposal, PROPOSAL_NEARLY_EXPIRED); break; case PROPOSAL_NEARLY_EXPIRED: if (real_lifetime(&dfr_proposal->uptime, dfr_proposal->router_lifetime) > 0) dfr_proposal_state_transition(dfr_proposal, PROPOSAL_NEARLY_EXPIRED); else dfr_proposal_state_transition(dfr_proposal, PROPOSAL_STALE); break; case PROPOSAL_STALE: free_dfr_proposal(dfr_proposal); dfr_proposal = NULL; break; case PROPOSAL_WITHDRAWN: free_dfr_proposal(dfr_proposal); dfr_proposal = NULL; break; default: log_debug("%s: unhandled state: %s", __func__, proposal_state_name(dfr_proposal->state)); } } void rdns_proposal_timeout(int fd, short events, void *arg) { struct rdns_proposal *rdns_proposal; const char *hbuf; rdns_proposal = (struct rdns_proposal *)arg; hbuf = sin6_to_str(&rdns_proposal->from); log_debug("%s: iface %d: %s [%s]", __func__, rdns_proposal->if_index, hbuf, proposal_state_name(rdns_proposal->state)); switch (rdns_proposal->state) { case PROPOSAL_IF_DOWN: rdns_proposal_state_transition(rdns_proposal, PROPOSAL_STALE); break; case PROPOSAL_CONFIGURED: rdns_proposal_state_transition(rdns_proposal, PROPOSAL_NEARLY_EXPIRED); break; case PROPOSAL_NEARLY_EXPIRED: if (real_lifetime(&rdns_proposal->uptime, rdns_proposal->rdns_lifetime) > 0) rdns_proposal_state_transition(rdns_proposal, PROPOSAL_NEARLY_EXPIRED); else rdns_proposal_state_transition(rdns_proposal, PROPOSAL_STALE); break; case PROPOSAL_STALE: free_rdns_proposal(rdns_proposal); rdns_proposal = NULL; break; case PROPOSAL_WITHDRAWN: free_rdns_proposal(rdns_proposal); rdns_proposal = NULL; break; default: log_debug("%s: unhandled state: %s", __func__, proposal_state_name(rdns_proposal->state)); } } void iface_timeout(int fd, short events, void *arg) { struct slaacd_iface *iface = (struct slaacd_iface *)arg; log_debug("%s[%d]: %s", __func__, iface->if_index, if_state_name(iface->state)); switch (iface->state) { case IF_DOWN: fatalx("%s: timeout in wrong state IF_DOWN", __func__); break; case IF_INIT: iface_state_transition(iface, IF_INIT); break; default: break; } } struct radv* find_ra(struct slaacd_iface *iface, struct sockaddr_in6 *from) { struct radv *ra; LIST_FOREACH (ra, &iface->radvs, entries) { if (memcmp(&ra->from.sin6_addr, &from->sin6_addr, sizeof(from->sin6_addr)) == 0) return (ra); } return (NULL); } struct address_proposal* find_address_proposal_by_addr(struct slaacd_iface *iface, struct sockaddr_in6 *addr) { struct address_proposal *addr_proposal; LIST_FOREACH (addr_proposal, &iface->addr_proposals, entries) { if (memcmp(&addr_proposal->addr, addr, sizeof(*addr)) == 0) return (addr_proposal); } return (NULL); } struct dfr_proposal* find_dfr_proposal_by_gw(struct slaacd_iface *iface, struct sockaddr_in6 *addr) { struct dfr_proposal *dfr_proposal; LIST_FOREACH (dfr_proposal, &iface->dfr_proposals, entries) { if (memcmp(&dfr_proposal->addr, addr, sizeof(*addr)) == 0) return (dfr_proposal); } return (NULL); } struct rdns_proposal* find_rdns_proposal_by_gw(struct slaacd_iface *iface, struct sockaddr_in6 *from) { struct rdns_proposal *rdns_proposal; LIST_FOREACH (rdns_proposal, &iface->rdns_proposals, entries) { if (memcmp(&rdns_proposal->from, from, sizeof(*from)) == 0) return (rdns_proposal); } return (NULL); } struct radv_prefix * find_prefix(struct radv *ra, struct in6_addr *prefix, uint8_t prefix_len) { struct radv_prefix *result; LIST_FOREACH(result, &ra->prefixes, entries) { if (memcmp(&result->prefix, prefix, sizeof(result->prefix)) == 0 && result->prefix_len == prefix_len) return (result); } return (NULL); } uint32_t real_lifetime(struct timespec *received_uptime, uint32_t ltime) { struct timespec now, diff; int64_t remaining; if (clock_gettime(CLOCK_MONOTONIC, &now)) fatal("clock_gettime"); timespecsub(&now, received_uptime, &diff); remaining = ((int64_t)ltime) - diff.tv_sec; if (remaining < 0) remaining = 0; return (remaining); } void merge_dad_couters(struct radv *old_ra, struct radv *new_ra) { struct radv_prefix *old_prefix, *new_prefix; LIST_FOREACH(old_prefix, &old_ra->prefixes, entries) { if (!old_prefix->dad_counter) continue; if ((new_prefix = find_prefix(new_ra, &old_prefix->prefix, old_prefix->prefix_len)) != NULL) new_prefix->dad_counter = old_prefix->dad_counter; } }