/* $OpenBSD: frontend.c,v 1.68 2024/08/24 09:44:41 florian Exp $ */ /* * Copyright (c) 2017 Florian Obser * Copyright (c) 2005 Claudio Jeker * Copyright (c) 2004 Esben Norby * Copyright (c) 2003, 2004 Henning Brauer * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "slaacd.h" #include "frontend.h" #include "control.h" #define ROUTE_SOCKET_BUF_SIZE 16384 #define ALLROUTER "ff02::2" struct icmp6_ev { struct event ev; uint8_t answer[1500]; struct msghdr rcvmhdr; struct iovec rcviov[1]; struct sockaddr_in6 from; int refcnt; }; struct iface { LIST_ENTRY(iface) entries; struct icmp6_ev *icmp6ev; struct ether_addr hw_address; uint32_t if_index; int rdomain; int send_solicitation; int ll_tentative; }; __dead void frontend_shutdown(void); void frontend_sig_handler(int, short, void *); void update_iface(uint32_t, char*); void frontend_startup(void); void route_receive(int, short, void *); void handle_route_message(struct rt_msghdr *, struct sockaddr **); void get_rtaddrs(int, struct sockaddr *, struct sockaddr **); void icmp6_receive(int, short, void *); int get_flags(char *); int get_xflags(char *); int get_ifrdomain(char *); struct iface *get_iface_by_id(uint32_t); void remove_iface(uint32_t); struct icmp6_ev *get_icmp6ev_by_rdomain(int); void unref_icmp6ev(struct iface *); void set_icmp6sock(int, int); void send_solicitation(uint32_t); #ifndef SMALL const char *flags_to_str(int); #endif /* SMALL */ LIST_HEAD(, iface) interfaces; static struct imsgev *iev_main; static struct imsgev *iev_engine; struct event ev_route; struct msghdr sndmhdr; struct iovec sndiov[4]; struct nd_router_solicit rs; struct nd_opt_hdr nd_opt_hdr; struct ether_addr nd_opt_source_link_addr; struct sockaddr_in6 dst; int ioctlsock; void frontend_sig_handler(int sig, short event, void *bula) { /* * Normal signal handler rules don't apply because libevent * decouples for us. */ switch (sig) { case SIGINT: case SIGTERM: frontend_shutdown(); default: fatalx("unexpected signal"); } } void frontend(int debug, int verbose) { struct event ev_sigint, ev_sigterm; struct passwd *pw; struct in6_pktinfo *pi; struct cmsghdr *cm; size_t sndcmsglen; int hoplimit = 255; uint8_t *sndcmsgbuf; 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", "frontend"); log_procinit("frontend"); if ((ioctlsock = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0)) == -1) fatal("socket"); if (setgroups(1, &pw->pw_gid) || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) fatal("can't drop privileges"); if (pledge("stdio unix recvfd route", NULL) == -1) fatal("pledge"); event_init(); /* Setup signal handler. */ signal_set(&ev_sigint, SIGINT, frontend_sig_handler, NULL); signal_set(&ev_sigterm, SIGTERM, frontend_sig_handler, NULL); signal_add(&ev_sigint, NULL); signal_add(&ev_sigterm, NULL); signal(SIGPIPE, SIG_IGN); signal(SIGHUP, SIG_IGN); /* Setup pipe and event handler to the parent process. */ if ((iev_main = malloc(sizeof(struct imsgev))) == NULL) fatal(NULL); imsg_init(&iev_main->ibuf, 3); iev_main->handler = frontend_dispatch_main; iev_main->events = EV_READ; event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events, iev_main->handler, iev_main); event_add(&iev_main->ev, NULL); sndcmsglen = CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int)); if ((sndcmsgbuf = malloc(sndcmsglen)) == NULL) fatal("malloc"); rs.nd_rs_type = ND_ROUTER_SOLICIT; rs.nd_rs_code = 0; rs.nd_rs_cksum = 0; rs.nd_rs_reserved = 0; nd_opt_hdr.nd_opt_type = ND_OPT_SOURCE_LINKADDR; nd_opt_hdr.nd_opt_len = 1; memset(&dst, 0, sizeof(dst)); dst.sin6_family = AF_INET6; if (inet_pton(AF_INET6, ALLROUTER, &dst.sin6_addr.s6_addr) != 1) fatal("inet_pton"); sndmhdr.msg_namelen = sizeof(struct sockaddr_in6); sndmhdr.msg_iov = sndiov; sndmhdr.msg_iovlen = 3; sndmhdr.msg_control = (caddr_t)sndcmsgbuf; sndmhdr.msg_controllen = sndcmsglen; sndmhdr.msg_name = (caddr_t)&dst; sndmhdr.msg_iov[0].iov_base = (caddr_t)&rs; sndmhdr.msg_iov[0].iov_len = sizeof(rs); sndmhdr.msg_iov[1].iov_base = (caddr_t)&nd_opt_hdr; sndmhdr.msg_iov[1].iov_len = sizeof(nd_opt_hdr); sndmhdr.msg_iov[2].iov_base = (caddr_t)&nd_opt_source_link_addr; sndmhdr.msg_iov[2].iov_len = sizeof(nd_opt_source_link_addr); cm = CMSG_FIRSTHDR(&sndmhdr); cm->cmsg_level = IPPROTO_IPV6; cm->cmsg_type = IPV6_PKTINFO; cm->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); pi = (struct in6_pktinfo *)CMSG_DATA(cm); memset(&pi->ipi6_addr, 0, sizeof(pi->ipi6_addr)); pi->ipi6_ifindex = 0; cm = CMSG_NXTHDR(&sndmhdr, cm); cm->cmsg_level = IPPROTO_IPV6; cm->cmsg_type = IPV6_HOPLIMIT; cm->cmsg_len = CMSG_LEN(sizeof(int)); memcpy(CMSG_DATA(cm), &hoplimit, sizeof(int)); LIST_INIT(&interfaces); event_dispatch(); frontend_shutdown(); } __dead void frontend_shutdown(void) { /* Close pipes. */ msgbuf_write(&iev_engine->ibuf.w); msgbuf_clear(&iev_engine->ibuf.w); close(iev_engine->ibuf.fd); msgbuf_write(&iev_main->ibuf.w); msgbuf_clear(&iev_main->ibuf.w); close(iev_main->ibuf.fd); free(iev_engine); free(iev_main); log_info("frontend exiting"); exit(0); } int frontend_imsg_compose_main(int type, pid_t pid, void *data, uint16_t datalen) { return (imsg_compose_event(iev_main, type, 0, pid, -1, data, datalen)); } int frontend_imsg_compose_engine(int type, uint32_t peerid, pid_t pid, void *data, uint16_t datalen) { return (imsg_compose_event(iev_engine, type, peerid, pid, -1, data, datalen)); } void frontend_dispatch_main(int fd, short event, void *bula) { struct imsg imsg; struct imsgev *iev = bula; struct imsgbuf *ibuf = &iev->ibuf; ssize_t n; uint32_t type; int shut = 0, icmp6sock, rdomain; 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 engine * process. */ if (iev_engine) fatalx("%s: received unexpected imsg fd " "to frontend", __func__); if ((fd = imsg_get_fd(&imsg)) == -1) fatalx("%s: expected to receive imsg fd to " "frontend but didn't receive any", __func__); iev_engine = malloc(sizeof(struct imsgev)); if (iev_engine == NULL) fatal(NULL); imsg_init(&iev_engine->ibuf, fd); iev_engine->handler = frontend_dispatch_engine; iev_engine->events = EV_READ; event_set(&iev_engine->ev, iev_engine->ibuf.fd, iev_engine->events, iev_engine->handler, iev_engine); event_add(&iev_engine->ev, NULL); break; case IMSG_ICMP6SOCK: if ((icmp6sock = imsg_get_fd(&imsg)) == -1) fatalx("%s: expected to receive imsg " "ICMPv6 fd but didn't receive any", __func__); if (imsg_get_data(&imsg, &rdomain, sizeof(rdomain)) == -1) fatalx("%s: invalid %s", __func__, i2s(type)); set_icmp6sock(icmp6sock, rdomain); break; case IMSG_ROUTESOCK: if ((fd = imsg_get_fd(&imsg)) == -1) fatalx("%s: expected to receive imsg " "routesocket fd but didn't receive any", __func__); event_set(&ev_route, fd, EV_READ | EV_PERSIST, route_receive, NULL); break; case IMSG_STARTUP: frontend_startup(); break; #ifndef SMALL case IMSG_CONTROLFD: if ((fd = imsg_get_fd(&imsg)) == -1) fatalx("%s: expected to receive imsg " "control fd but didn't receive any", __func__); /* Listen on control socket. */ control_listen(fd); break; case IMSG_CTL_END: control_imsg_relay(&imsg); break; #endif /* SMALL */ default: log_debug("%s: error handling 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 frontend_dispatch_engine(int fd, short event, void *bula) { struct imsgev *iev = bula; struct imsgbuf *ibuf = &iev->ibuf; struct imsg imsg; ssize_t n; int shut = 0; 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_END: case IMSG_CTL_SHOW_INTERFACE_INFO: case IMSG_CTL_SHOW_INTERFACE_INFO_RA: case IMSG_CTL_SHOW_INTERFACE_INFO_RA_PREFIX: case IMSG_CTL_SHOW_INTERFACE_INFO_RA_RDNS: case IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSALS: case IMSG_CTL_SHOW_INTERFACE_INFO_ADDR_PROPOSAL: case IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSALS: case IMSG_CTL_SHOW_INTERFACE_INFO_DFR_PROPOSAL: case IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSALS: case IMSG_CTL_SHOW_INTERFACE_INFO_RDNS_PROPOSAL: control_imsg_relay(&imsg); break; #endif /* SMALL */ case IMSG_CTL_SEND_SOLICITATION: if (imsg_get_data(&imsg, &if_index, sizeof(if_index)) == -1) fatalx("%s: invalid %s", __func__, i2s(type)); send_solicitation(if_index); break; default: log_debug("%s: error handling 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); } } int get_flags(char *if_name) { struct ifreq ifr; strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name)); if (ioctl(ioctlsock, SIOCGIFFLAGS, (caddr_t)&ifr) == -1) { log_warn("SIOCGIFFLAGS"); return -1; } return ifr.ifr_flags; } int get_xflags(char *if_name) { struct ifreq ifr; strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name)); if (ioctl(ioctlsock, SIOCGIFXFLAGS, (caddr_t)&ifr) == -1) { log_warn("SIOCGIFXFLAGS"); return -1; } return ifr.ifr_flags; } int get_ifrdomain(char *if_name) { struct ifreq ifr; strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name)); if (ioctl(ioctlsock, SIOCGIFRDOMAIN, (caddr_t)&ifr) == -1) { log_warn("SIOCGIFRDOMAIN"); return -1; } return ifr.ifr_rdomainid; } void update_iface(uint32_t if_index, char* if_name) { struct iface *iface; struct ifaddrs *ifap, *ifa; struct imsg_ifinfo imsg_ifinfo; struct sockaddr_dl *sdl; struct sockaddr_in6 *sin6; struct in6_ifreq ifr6; int flags, xflags, ifrdomain; if ((flags = get_flags(if_name)) == -1 || (xflags = get_xflags(if_name)) == -1) return; if (!(xflags & (IFXF_AUTOCONF6 | IFXF_AUTOCONF6TEMP))) return; if ((ifrdomain = get_ifrdomain(if_name)) == -1) return; iface = get_iface_by_id(if_index); if (iface != NULL) { if (iface->rdomain != ifrdomain) { unref_icmp6ev(iface); iface->rdomain = ifrdomain; iface->icmp6ev = get_icmp6ev_by_rdomain(ifrdomain); } } else { if ((iface = calloc(1, sizeof(*iface))) == NULL) fatal("calloc"); iface->if_index = if_index; iface->rdomain = ifrdomain; iface->icmp6ev = get_icmp6ev_by_rdomain(ifrdomain); iface->ll_tentative = 1; LIST_INSERT_HEAD(&interfaces, iface, entries); } memset(&imsg_ifinfo, 0, sizeof(imsg_ifinfo)); imsg_ifinfo.if_index = if_index; imsg_ifinfo.rdomain = ifrdomain; imsg_ifinfo.running = (flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP | IFF_RUNNING); imsg_ifinfo.autoconf = (xflags & IFXF_AUTOCONF6); imsg_ifinfo.temporary = (xflags & IFXF_AUTOCONF6TEMP); imsg_ifinfo.soii = !(xflags & IFXF_INET6_NOSOII); if (getifaddrs(&ifap) != 0) fatal("getifaddrs"); for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { if (strcmp(if_name, ifa->ifa_name) != 0) continue; if (ifa->ifa_addr == NULL) continue; switch(ifa->ifa_addr->sa_family) { case AF_LINK: imsg_ifinfo.link_state = ((struct if_data *)ifa->ifa_data)->ifi_link_state; sdl = (struct sockaddr_dl *)ifa->ifa_addr; if (sdl->sdl_type != IFT_ETHER || sdl->sdl_alen != ETHER_ADDR_LEN) continue; memcpy(iface->hw_address.ether_addr_octet, LLADDR(sdl), ETHER_ADDR_LEN); memcpy(imsg_ifinfo.hw_address.ether_addr_octet, LLADDR(sdl), ETHER_ADDR_LEN); case AF_INET6: sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; #ifdef __KAME__ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) && sin6->sin6_scope_id == 0) { sin6->sin6_scope_id = ntohs(*(u_int16_t *) &sin6->sin6_addr.s6_addr[2]); sin6->sin6_addr.s6_addr[2] = sin6->sin6_addr.s6_addr[3] = 0; } #endif if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { memcpy(&imsg_ifinfo.ll_address, sin6, sizeof(imsg_ifinfo.ll_address)); if (!iface->ll_tentative) break; memset(&ifr6, 0, sizeof(ifr6)); strlcpy(ifr6.ifr_name, if_name, sizeof(ifr6.ifr_name)); memcpy(&ifr6.ifr_addr, sin6, sizeof(ifr6.ifr_addr)); if (ioctl(ioctlsock, SIOCGIFAFLAG_IN6, (caddr_t)&ifr6) == -1) { log_warn("SIOCGIFAFLAG_IN6"); break; } if (!(ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_TENTATIVE)) { iface->ll_tentative = 0; if (iface->send_solicitation) send_solicitation( iface->if_index); } } break; default: break; } } freeifaddrs(ifap); frontend_imsg_compose_main(IMSG_UPDATE_IF, 0, &imsg_ifinfo, sizeof(imsg_ifinfo)); } #ifndef SMALL const char* flags_to_str(int flags) { static char buf[sizeof(" anycast tentative duplicated detached " "deprecated autoconf temporary")]; buf[0] = '\0'; if (flags & IN6_IFF_ANYCAST) strlcat(buf, " anycast", sizeof(buf)); if (flags & IN6_IFF_TENTATIVE) strlcat(buf, " tentative", sizeof(buf)); if (flags & IN6_IFF_DUPLICATED) strlcat(buf, " duplicated", sizeof(buf)); if (flags & IN6_IFF_DETACHED) strlcat(buf, " detached", sizeof(buf)); if (flags & IN6_IFF_DEPRECATED) strlcat(buf, " deprecated", sizeof(buf)); if (flags & IN6_IFF_AUTOCONF) strlcat(buf, " autoconf", sizeof(buf)); if (flags & IN6_IFF_TEMPORARY) strlcat(buf, " temporary", sizeof(buf)); return (buf); } #endif /* SMALL */ void frontend_startup(void) { struct if_nameindex *ifnidxp, *ifnidx; if (!event_initialized(&ev_route)) fatalx("%s: did not receive a route socket from the main " "process", __func__); event_add(&ev_route, NULL); if ((ifnidxp = if_nameindex()) == NULL) fatalx("if_nameindex"); for(ifnidx = ifnidxp; ifnidx->if_index !=0 && ifnidx->if_name != NULL; ifnidx++) update_iface(ifnidx->if_index, ifnidx->if_name); if_freenameindex(ifnidxp); } void route_receive(int fd, short events, void *arg) { static uint8_t *buf; struct rt_msghdr *rtm; struct sockaddr *sa, *rti_info[RTAX_MAX]; ssize_t n; if (buf == NULL) { buf = malloc(ROUTE_SOCKET_BUF_SIZE); if (buf == NULL) fatal("malloc"); } rtm = (struct rt_msghdr *)buf; if ((n = read(fd, buf, ROUTE_SOCKET_BUF_SIZE)) == -1) { if (errno == EAGAIN || errno == EINTR) return; log_warn("dispatch_rtmsg: read error"); return; } if (n == 0) fatal("routing socket closed"); if (n < (ssize_t)sizeof(rtm->rtm_msglen) || n < rtm->rtm_msglen) { log_warnx("partial rtm of %zd in buffer", n); return; } if (rtm->rtm_version != RTM_VERSION) return; sa = (struct sockaddr *)(buf + rtm->rtm_hdrlen); get_rtaddrs(rtm->rtm_addrs, sa, rti_info); handle_route_message(rtm, rti_info); } void handle_route_message(struct rt_msghdr *rtm, struct sockaddr **rti_info) { struct if_msghdr *ifm; struct if_announcemsghdr *ifan; struct imsg_del_addr del_addr; struct imsg_del_route del_route; struct imsg_dup_addr dup_addr; struct sockaddr_rtlabel *rl; struct sockaddr_in6 *sin6; struct in6_ifreq ifr6; struct in6_addr *in6; int xflags, if_index; char ifnamebuf[IFNAMSIZ]; char *if_name; switch (rtm->rtm_type) { case RTM_IFINFO: ifm = (struct if_msghdr *)rtm; if_index = ifm->ifm_index; if_name = if_indextoname(if_index, ifnamebuf); if (if_name == NULL) { log_debug("RTM_IFINFO: lost if %d", if_index); frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0, &if_index, sizeof(if_index)); remove_iface(if_index); break; } xflags = get_xflags(if_name); if (xflags == -1 || !(xflags & (IFXF_AUTOCONF6 | IFXF_AUTOCONF6TEMP))) { log_debug("RTM_IFINFO: %s(%d) no(longer) autoconf6", if_name, if_index); frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0, &if_index, sizeof(if_index)); remove_iface(if_index); } else { update_iface(if_index, if_name); } break; case RTM_IFANNOUNCE: ifan = (struct if_announcemsghdr *)rtm; if_index = ifan->ifan_index; if (ifan->ifan_what == IFAN_DEPARTURE) { frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0, &if_index, sizeof(if_index)); remove_iface(if_index); } break; case RTM_NEWADDR: ifm = (struct if_msghdr *)rtm; if_index = ifm->ifm_index; if_name = if_indextoname(if_index, ifnamebuf); if (if_name == NULL) { log_debug("RTM_NEWADDR: lost if %d", if_index); frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0, &if_index, sizeof(if_index)); remove_iface(if_index); break; } log_debug("RTM_NEWADDR: %s[%u]", if_name, if_index); update_iface(if_index, if_name); break; case RTM_DELADDR: ifm = (struct if_msghdr *)rtm; if_index = ifm->ifm_index; if_name = if_indextoname(if_index, ifnamebuf); if (if_name == NULL) { log_debug("RTM_DELADDR: lost if %d", if_index); frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0, &if_index, sizeof(if_index)); remove_iface(if_index); break; } if (rtm->rtm_addrs & RTA_IFA && rti_info[RTAX_IFA]->sa_family == AF_INET6) { del_addr.if_index = if_index; memcpy(&del_addr.addr, rti_info[RTAX_IFA], sizeof( del_addr.addr)); frontend_imsg_compose_engine(IMSG_DEL_ADDRESS, 0, 0, &del_addr, sizeof(del_addr)); log_debug("RTM_DELADDR: %s[%u]", if_name, if_index); } break; case RTM_CHGADDRATTR: ifm = (struct if_msghdr *)rtm; if_index = ifm->ifm_index; if_name = if_indextoname(if_index, ifnamebuf); if (if_name == NULL) { log_debug("RTM_CHGADDRATTR: lost if %d", if_index); frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0, &if_index, sizeof(if_index)); remove_iface(if_index); break; } if (rtm->rtm_addrs & RTA_IFA && rti_info[RTAX_IFA]->sa_family == AF_INET6) { sin6 = (struct sockaddr_in6 *) rti_info[RTAX_IFA]; if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { update_iface(if_index, if_name); break; } memset(&ifr6, 0, sizeof(ifr6)); strlcpy(ifr6.ifr_name, if_name, sizeof(ifr6.ifr_name)); memcpy(&ifr6.ifr_addr, sin6, sizeof(ifr6.ifr_addr)); if (ioctl(ioctlsock, SIOCGIFAFLAG_IN6, (caddr_t)&ifr6) == -1) { log_warn("SIOCGIFAFLAG_IN6"); break; } #ifndef SMALL log_debug("RTM_CHGADDRATTR: %s - %s", sin6_to_str(sin6), flags_to_str(ifr6.ifr_ifru.ifru_flags6)); #endif /* SMALL */ if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_DUPLICATED) { dup_addr.if_index = if_index; dup_addr.addr = *sin6; frontend_imsg_compose_engine(IMSG_DUP_ADDRESS, 0, 0, &dup_addr, sizeof(dup_addr)); } } break; case RTM_DELETE: ifm = (struct if_msghdr *)rtm; if ((rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY | RTA_LABEL)) != (RTA_DST | RTA_GATEWAY | RTA_LABEL)) break; if (rti_info[RTAX_DST]->sa_family != AF_INET6) break; if (!IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *) rti_info[RTAX_DST])->sin6_addr)) break; if (rti_info[RTAX_GATEWAY]->sa_family != AF_INET6) break; if (rti_info[RTAX_LABEL]->sa_len != sizeof(struct sockaddr_rtlabel)) break; rl = (struct sockaddr_rtlabel *)rti_info[RTAX_LABEL]; if (strcmp(rl->sr_label, SLAACD_RTA_LABEL) != 0) break; if_index = ifm->ifm_index; if_name = if_indextoname(if_index, ifnamebuf); if (if_name == NULL) { log_debug("RTM_DELETE: lost if %d", if_index); frontend_imsg_compose_engine(IMSG_REMOVE_IF, 0, 0, &if_index, sizeof(if_index)); remove_iface(if_index); break; } del_route.if_index = if_index; memcpy(&del_route.gw, rti_info[RTAX_GATEWAY], sizeof(del_route.gw)); in6 = &del_route.gw.sin6_addr; #ifdef __KAME__ /* XXX from route(8) p_sockaddr() */ if ((IN6_IS_ADDR_LINKLOCAL(in6) || IN6_IS_ADDR_MC_LINKLOCAL(in6) || IN6_IS_ADDR_MC_INTFACELOCAL(in6)) && del_route.gw.sin6_scope_id == 0) { del_route.gw.sin6_scope_id = (u_int32_t)ntohs(*(u_short *) &in6->s6_addr[2]); *(u_short *)&in6->s6_addr[2] = 0; } #endif frontend_imsg_compose_engine(IMSG_DEL_ROUTE, 0, 0, &del_route, sizeof(del_route)); log_debug("RTM_DELETE: %s[%u]", if_name, ifm->ifm_index); break; #ifndef SMALL case RTM_PROPOSAL: if (rtm->rtm_priority == RTP_PROPOSAL_SOLICIT) { log_debug("RTP_PROPOSAL_SOLICIT"); frontend_imsg_compose_engine(IMSG_REPROPOSE_RDNS, 0, 0, NULL, 0); } break; #endif /* SMALL */ default: log_debug("unexpected RTM: %d", rtm->rtm_type); break; } } #define ROUNDUP(a) \ ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) void get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info) { int i; for (i = 0; i < RTAX_MAX; i++) { if (addrs & (1 << i)) { rti_info[i] = sa; sa = (struct sockaddr *)((char *)(sa) + ROUNDUP(sa->sa_len)); } else rti_info[i] = NULL; } } void icmp6_receive(int fd, short events, void *arg) { struct imsg_ra ra; struct icmp6_hdr *icmp6_hdr; struct icmp6_ev *icmp6ev; struct in6_pktinfo *pi = NULL; struct cmsghdr *cm; ssize_t len; int if_index = 0, *hlimp = NULL; char ntopbuf[INET6_ADDRSTRLEN]; #ifndef SMALL char ifnamebuf[IFNAMSIZ]; #endif /* SMALL */ icmp6ev = arg; if ((len = recvmsg(fd, &icmp6ev->rcvmhdr, 0)) == -1) { log_warn("recvmsg"); return; } if ((size_t)len < sizeof(struct icmp6_hdr)) return; icmp6_hdr = (struct icmp6_hdr *)icmp6ev->answer; if (icmp6_hdr->icmp6_type != ND_ROUTER_ADVERT) return; /* extract optional information via Advanced API */ for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&icmp6ev->rcvmhdr); cm; cm = (struct cmsghdr *)CMSG_NXTHDR(&icmp6ev->rcvmhdr, cm)) { if (cm->cmsg_level == IPPROTO_IPV6 && cm->cmsg_type == IPV6_PKTINFO && cm->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) { pi = (struct in6_pktinfo *)(CMSG_DATA(cm)); if_index = pi->ipi6_ifindex; } if (cm->cmsg_level == IPPROTO_IPV6 && cm->cmsg_type == IPV6_HOPLIMIT && cm->cmsg_len == CMSG_LEN(sizeof(int))) hlimp = (int *)CMSG_DATA(cm); } if (if_index == 0) { log_warnx("failed to get receiving interface"); return; } if (hlimp == NULL) { log_warnx("failed to get receiving hop limit"); return; } if (*hlimp != 255) { log_warnx("invalid RA with hop limit of %d from %s on %s", *hlimp, inet_ntop(AF_INET6, &icmp6ev->from.sin6_addr, ntopbuf, INET6_ADDRSTRLEN), if_indextoname(if_index, ifnamebuf)); return; } if ((size_t)len > sizeof(ra.packet)) { log_warnx("invalid RA with size %ld from %s on %s", len, inet_ntop(AF_INET6, &icmp6ev->from.sin6_addr, ntopbuf, INET6_ADDRSTRLEN), if_indextoname(if_index, ifnamebuf)); return; } ra.if_index = if_index; memcpy(&ra.from, &icmp6ev->from, sizeof(ra.from)); ra.len = len; memcpy(ra.packet, icmp6ev->answer, len); frontend_imsg_compose_engine(IMSG_RA, 0, 0, &ra, sizeof(ra)); } void send_solicitation(uint32_t if_index) { struct in6_pktinfo *pi; struct cmsghdr *cm; struct iface *iface; log_debug("%s(%u)", __func__, if_index); if ((iface = get_iface_by_id(if_index)) == NULL) return; if (!event_initialized(&iface->icmp6ev->ev)) { iface->send_solicitation = 1; return; } else if (iface->ll_tentative) { iface->send_solicitation = 1; return; } iface->send_solicitation = 0; dst.sin6_scope_id = if_index; cm = CMSG_FIRSTHDR(&sndmhdr); pi = (struct in6_pktinfo *)CMSG_DATA(cm); pi->ipi6_ifindex = if_index; memcpy(&nd_opt_source_link_addr, &iface->hw_address, sizeof(nd_opt_source_link_addr)); if (sendmsg(EVENT_FD(&iface->icmp6ev->ev), &sndmhdr, 0) != sizeof(rs) + sizeof(nd_opt_hdr) + sizeof(nd_opt_source_link_addr)) log_warn("sendmsg"); } struct iface* get_iface_by_id(uint32_t if_index) { struct iface *iface; LIST_FOREACH (iface, &interfaces, entries) { if (iface->if_index == if_index) return (iface); } return (NULL); } void remove_iface(uint32_t if_index) { struct iface *iface; iface = get_iface_by_id(if_index); if (iface == NULL) return; LIST_REMOVE(iface, entries); unref_icmp6ev(iface); free(iface); } struct icmp6_ev* get_icmp6ev_by_rdomain(int rdomain) { struct iface *iface; struct icmp6_ev *icmp6ev = NULL; LIST_FOREACH (iface, &interfaces, entries) { if (iface->rdomain == rdomain) { icmp6ev = iface->icmp6ev; break; } } if (icmp6ev == NULL) { if ((icmp6ev = calloc(1, sizeof(*icmp6ev))) == NULL) fatal("calloc"); icmp6ev->rcviov[0].iov_base = (caddr_t)icmp6ev->answer; icmp6ev->rcviov[0].iov_len = sizeof(icmp6ev->answer); icmp6ev->rcvmhdr.msg_name = (caddr_t)&icmp6ev->from; icmp6ev->rcvmhdr.msg_namelen = sizeof(icmp6ev->from); icmp6ev->rcvmhdr.msg_iov = icmp6ev->rcviov; icmp6ev->rcvmhdr.msg_iovlen = 1; icmp6ev->rcvmhdr.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int)); if ((icmp6ev->rcvmhdr.msg_control = malloc(icmp6ev-> rcvmhdr.msg_controllen)) == NULL) fatal("malloc"); frontend_imsg_compose_main(IMSG_OPEN_ICMP6SOCK, 0, &rdomain, sizeof(rdomain)); } icmp6ev->refcnt++; return (icmp6ev); } void unref_icmp6ev(struct iface *iface) { struct icmp6_ev *icmp6ev = iface->icmp6ev; iface->icmp6ev = NULL; if (icmp6ev != NULL) { icmp6ev->refcnt--; if (icmp6ev->refcnt == 0) { event_del(&icmp6ev->ev); close(EVENT_FD(&icmp6ev->ev)); free(icmp6ev); } } } void set_icmp6sock(int icmp6sock, int rdomain) { struct iface *iface; LIST_FOREACH (iface, &interfaces, entries) { if (!event_initialized(&iface->icmp6ev->ev) && iface->rdomain == rdomain) { event_set(&iface->icmp6ev->ev, icmp6sock, EV_READ | EV_PERSIST, icmp6_receive, iface->icmp6ev); event_add(&iface->icmp6ev->ev, NULL); icmp6sock = -1; break; } } if (icmp6sock != -1) { /* * The interface disappeared or changed rdomain while we were * waiting for the parent process to open the raw socket. */ close(icmp6sock); return; } LIST_FOREACH (iface, &interfaces, entries) { if (event_initialized(&iface->icmp6ev->ev) && iface->send_solicitation) send_solicitation(iface->if_index); } }