/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2012 Roy Marples * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 #ifdef __linux__ # define _LINUX_IN6_H # include #endif #define ELOOP_QUEUE 1 #include "bind.h" #include "common.h" #include "configure.h" #include "dhcpcd.h" #include "eloop.h" #include "ipv6rs.h" #define ALLROUTERS "ff02::2" #define HOPLIMIT 255 #define ROUNDUP8(a) (1 + (((a) - 1) | 7)) #define RTR_SOLICITATION_INTERVAL 4 /* seconds */ #define MAX_RTR_SOLICITATIONS 3 /* times */ #ifndef ND_OPT_RDNSS #define ND_OPT_RDNSS 25 struct nd_opt_rdnss { /* RDNSS option RFC 6106 */ uint8_t nd_opt_rdnss_type; uint8_t nd_opt_rdnss_len; uint16_t nd_opt_rdnss_reserved; uint32_t nd_opt_rdnss_lifetime; /* followed by list of IP prefixes */ } _packed; #endif #ifndef ND_OPT_DNSSL #define ND_OPT_DNSSL 31 struct nd_opt_dnssl { /* DNSSL option RFC 6106 */ uint8_t nd_opt_dnssl_type; uint8_t nd_opt_dnssl_len; uint16_t nd_opt_dnssl_reserved; uint32_t nd_opt_dnssl_lifetime; /* followed by list of DNS servers */ } _packed; #endif static int sock; static struct sockaddr_in6 allrouters, from; static struct msghdr sndhdr; static struct iovec sndiov[2]; static unsigned char *sndbuf; static struct msghdr rcvhdr; static struct iovec rcviov[2]; static unsigned char *rcvbuf; static unsigned char ansbuf[1500]; static char ntopbuf[INET6_ADDRSTRLEN]; #if DEBUG_MEMORY static void ipv6rs_cleanup(void) { free(sndbuf); free(rcvbuf); } #endif int ipv6rs_open(void) { int on; int len; struct icmp6_filter filt; memset(&allrouters, 0, sizeof(allrouters)); allrouters.sin6_family = AF_INET6; #ifdef SIN6_LEN allrouters.sin6_len = sizeof(allrouters); #endif if (inet_pton(AF_INET6, ALLROUTERS, &allrouters.sin6_addr.s6_addr) != 1) return -1; sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (sock == -1) return -1; on = 1; if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)) == -1) return -1; on = 1; if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)) == -1) return -1; ICMP6_FILTER_SETBLOCKALL(&filt); ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt); if (setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt)) == -1) return -1; #if DEBUG_MEMORY atexit(ipv6rs_cleanup); #endif len = CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int)); sndbuf = xzalloc(len); if (sndbuf == NULL) return -1; sndhdr.msg_namelen = sizeof(struct sockaddr_in6); sndhdr.msg_iov = sndiov; sndhdr.msg_iovlen = 1; sndhdr.msg_control = sndbuf; sndhdr.msg_controllen = len; rcvbuf = xzalloc(len); if (rcvbuf == NULL) return -1; rcvhdr.msg_name = &from; rcvhdr.msg_namelen = sizeof(from); rcvhdr.msg_iov = rcviov; rcvhdr.msg_iovlen = 1; rcvhdr.msg_control = rcvbuf; rcvhdr.msg_controllen = len; rcviov[0].iov_base = ansbuf; rcviov[0].iov_len = sizeof(ansbuf); return sock; } static int ipv6rs_makeprobe(struct interface *ifp) { struct nd_router_solicit *rs; struct nd_opt_hdr *nd; free(ifp->rs); ifp->rslen = sizeof(*rs) + ROUNDUP8(ifp->hwlen + 2); ifp->rs = xzalloc(ifp->rslen); if (ifp->rs == NULL) return -1; rs = (struct nd_router_solicit *)ifp->rs; rs->nd_rs_type = ND_ROUTER_SOLICIT; rs->nd_rs_code = 0; rs->nd_rs_cksum = 0; rs->nd_rs_reserved = 0; nd = (struct nd_opt_hdr *)(ifp->rs + sizeof(*rs)); nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR; nd->nd_opt_len = (ROUNDUP8(ifp->hwlen + 2)) >> 3; memcpy(nd + 1, ifp->hwaddr, ifp->hwlen); return 0; } static void ipv6rs_sendprobe(void *arg) { struct interface *ifp = arg; struct sockaddr_in6 dst; struct cmsghdr *cm; struct in6_pktinfo pi; int hoplimit = HOPLIMIT; dst = allrouters; //dst.sin6_scope_id = ifp->linkid; ipv6rs_makeprobe(ifp); sndhdr.msg_name = (caddr_t)&dst; sndhdr.msg_iov[0].iov_base = ifp->rs; sndhdr.msg_iov[0].iov_len = ifp->rslen; /* Set the outbound interface */ cm = CMSG_FIRSTHDR(&sndhdr); cm->cmsg_level = IPPROTO_IPV6; cm->cmsg_type = IPV6_PKTINFO; cm->cmsg_len = CMSG_LEN(sizeof(pi)); memset(&pi, 0, sizeof(pi)); pi.ipi6_ifindex = if_nametoindex(ifp->name); memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); /* Hop limit */ cm = CMSG_NXTHDR(&sndhdr, cm); cm->cmsg_level = IPPROTO_IPV6; cm->cmsg_type = IPV6_HOPLIMIT; cm->cmsg_len = CMSG_LEN(sizeof(hoplimit)); memcpy(CMSG_DATA(cm), &hoplimit, sizeof(hoplimit)); syslog(LOG_INFO, "%s: sending IPv6 Router Solicitation", ifp->name); if (sendmsg(sock, &sndhdr, 0) == -1) syslog(LOG_ERR, "%s: sendmsg: %m", ifp->name); if (ifp->rsprobes++ < MAX_RTR_SOLICITATIONS) add_timeout_sec(RTR_SOLICITATION_INTERVAL, ipv6rs_sendprobe, ifp); else syslog(LOG_INFO, "%s: no IPv6 Routers available", ifp->name); } static void ipv6rs_sort(struct interface *ifp) { struct ra *rap, *sorted, *ran, *rat; if (ifp->ras == NULL || ifp->ras->next == NULL) return; /* Sort our RA's - most recent first */ sorted = ifp->ras; ifp->ras = ifp->ras->next; sorted->next = NULL; for (rap = ifp->ras; rap && (ran = rap->next, 1); rap = ran) { /* Are we the new head? */ if (timercmp(&rap->received, &sorted->received, <)) { rap->next = sorted; sorted = rap; continue; } /* Do we fit in the middle? */ for (rat = sorted; rat->next; rat = rat->next) { if (timercmp(&rap->received, &rat->next->received, <)) { rap->next = rat->next; rat->next = rap; break; } } /* We must be at the end */ if (!rat->next) { rat->next = rap; rap->next = NULL; } } } void ipv6rs_handledata(_unused void *arg) { ssize_t len, l, n, olen; struct cmsghdr *cm; int hoplimit; struct in6_pktinfo pkt; struct icmp6_hdr *icp; struct interface *ifp; const char *sfrom; struct nd_router_advert *nd_ra; struct nd_opt_prefix_info *pi; struct nd_opt_mtu *mtu; struct nd_opt_rdnss *rdnss; struct nd_opt_dnssl *dnssl; uint32_t lifetime; uint8_t *p, *op; struct in6_addr addr; char buf[INET6_ADDRSTRLEN]; const char *cbp; struct ra *rap; struct nd_opt_hdr *ndo; struct ra_opt *rao, *raol; char *opt; struct timeval expire; int has_dns; len = recvmsg(sock, &rcvhdr, 0); if (len == -1) { syslog(LOG_ERR, "recvmsg: %m"); return; } sfrom = inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf, INET6_ADDRSTRLEN); if ((size_t)len < sizeof(struct nd_router_advert)) { syslog(LOG_ERR, "IPv6 RA packet too short from %s", sfrom); return; } pkt.ipi6_ifindex = hoplimit = 0; for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&rcvhdr); cm; cm = (struct cmsghdr *)CMSG_NXTHDR(&rcvhdr, cm)) { if (cm->cmsg_level != IPPROTO_IPV6) continue; switch(cm->cmsg_type) { case IPV6_PKTINFO: if (cm->cmsg_len == CMSG_LEN(sizeof(pkt))) memcpy(&pkt, CMSG_DATA(cm), sizeof(pkt)); break; case IPV6_HOPLIMIT: if (cm->cmsg_len == CMSG_LEN(sizeof(int))) memcpy(&hoplimit, CMSG_DATA(cm), sizeof(int)); break; } } if (pkt.ipi6_ifindex == 0 || hoplimit == 0) { syslog(LOG_ERR, "IPv6 RA did not contain index or hop limit from %s", sfrom); return; } icp = (struct icmp6_hdr *)rcvhdr.msg_iov[0].iov_base; if (icp->icmp6_type != ND_ROUTER_ADVERT || icp->icmp6_code != 0) { syslog(LOG_ERR, "invalid IPv6 type or code from %s", sfrom); return; } if (!IN6_IS_ADDR_LINKLOCAL(&from.sin6_addr)) { syslog(LOG_ERR, "RA recieved from non local IPv6 address %s", sfrom); return; } for (ifp = ifaces; ifp; ifp = ifp->next) if (if_nametoindex(ifp->name) == (unsigned int)pkt.ipi6_ifindex) break; if (ifp == NULL) { syslog(LOG_ERR,"received RA for unexpected interface from %s", sfrom); return; } for (rap = ifp->ras; rap; rap = rap->next) { if (memcmp(rap->from.s6_addr, from.sin6_addr.s6_addr, sizeof(rap->from.s6_addr)) == 0) break; } /* We don't want to spam the log with the fact we got an RA every * 30 seconds or so, so only spam the log if it's different. */ if (options & DHCPCD_DEBUG || rap == NULL || (rap->expired || rap->data_len != len || memcmp(rap->data, (unsigned char *)icp, rap->data_len) != 0)) { if (rap) { free(rap->data); rap->data_len = 0; } syslog(LOG_INFO, "%s: Router Advertisement from %s", ifp->name, sfrom); } if (rap == NULL) { rap = xmalloc(sizeof(*rap)); rap->next = ifp->ras; rap->options = NULL; ifp->ras = rap; memcpy(rap->from.s6_addr, from.sin6_addr.s6_addr, sizeof(rap->from.s6_addr)); strlcpy(rap->sfrom, sfrom, sizeof(rap->sfrom)); rap->data_len = 0; } if (rap->data_len == 0) { rap->data = xmalloc(len); memcpy(rap->data, icp, len); rap->data_len = len; } get_monotonic(&rap->received); nd_ra = (struct nd_router_advert *)icp; rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime); rap->expired = 0; len -= sizeof(struct nd_router_advert); p = ((uint8_t *)icp) + sizeof(struct nd_router_advert); olen = 0; lifetime = ~0U; has_dns = 0; for (olen = 0; len > 0; p += olen, len -= olen) { if ((size_t)len < sizeof(struct nd_opt_hdr)) { syslog(LOG_ERR, "%s: Short option", ifp->name); break; } ndo = (struct nd_opt_hdr *)p; olen = ndo->nd_opt_len * 8 ; if (olen == 0) { syslog(LOG_ERR, "%s: zero length option", ifp->name); break; } if (olen > len) { syslog(LOG_ERR, "%s: Option length exceeds message", ifp->name); break; } opt = NULL; switch (ndo->nd_opt_type) { case ND_OPT_PREFIX_INFORMATION: pi = (struct nd_opt_prefix_info *)ndo; if (pi->nd_opt_pi_len != 4) { syslog(LOG_ERR, "%s: invalid option len for prefix", ifp->name); break; } if (pi->nd_opt_pi_prefix_len > 128) { syslog(LOG_ERR, "%s: invalid prefix len", ifp->name); break; } if (IN6_IS_ADDR_MULTICAST(&pi->nd_opt_pi_prefix) || IN6_IS_ADDR_LINKLOCAL(&pi->nd_opt_pi_prefix)) { syslog(LOG_ERR, "%s: invalid prefix in RA", ifp->name); break; } opt = xstrdup(inet_ntop(AF_INET6, pi->nd_opt_pi_prefix.s6_addr, ntopbuf, INET6_ADDRSTRLEN)); if (opt) { rap->prefix_len = pi->nd_opt_pi_prefix_len; rap->prefix_vltime = ntohl(pi->nd_opt_pi_valid_time); rap->prefix_pltime = ntohl(pi->nd_opt_pi_preferred_time); } break; case ND_OPT_MTU: mtu = (struct nd_opt_mtu *)p; snprintf(buf, sizeof(buf), "%d", ntohl(mtu->nd_opt_mtu_mtu)); opt = xstrdup(buf); break; case ND_OPT_RDNSS: rdnss = (struct nd_opt_rdnss *)p; lifetime = ntohl(rdnss->nd_opt_rdnss_lifetime); op = (uint8_t *)ndo; op += offsetof(struct nd_opt_rdnss, nd_opt_rdnss_lifetime); op += sizeof(rdnss->nd_opt_rdnss_lifetime); l = 0; for (n = ndo->nd_opt_len - 1; n > 1; n -= 2) { memcpy(&addr.s6_addr, op, sizeof(addr.s6_addr)); cbp = inet_ntop(AF_INET6, &addr, ntopbuf, INET6_ADDRSTRLEN); if (cbp == NULL) { syslog(LOG_ERR, "%s: invalid RDNSS address", ifp->name); } else { if (opt) { l = strlen(opt); opt = xrealloc(opt, l + strlen(cbp) + 2); opt[l] = ' '; strcpy(opt + l + 1, cbp); } else opt = xstrdup(cbp); if (lifetime > 0) has_dns = 1; } op += sizeof(addr.s6_addr); } break; case ND_OPT_DNSSL: dnssl = (struct nd_opt_dnssl *)p; lifetime = ntohl(dnssl->nd_opt_dnssl_lifetime); op = p + offsetof(struct nd_opt_dnssl, nd_opt_dnssl_lifetime); op += sizeof(dnssl->nd_opt_dnssl_lifetime); n = (dnssl->nd_opt_dnssl_len - 1) * 8; l = decode_rfc3397(NULL, 0, n, op); if (l < 1) { syslog(LOG_ERR, "%s: invalid DNSSL option", ifp->name); } else { opt = xmalloc(l); decode_rfc3397(opt, l, n, op); } break; } if (opt == NULL) continue; for (raol = NULL, rao = rap->options; rao; raol = rao, rao = rao->next) { if (rao->type == ndo->nd_opt_type && strcmp(rao->option, opt) == 0) break; } if (lifetime == 0) { if (rao) { if (raol) raol->next = rao->next; else rap->options = rao->next; free(rao->option); free(rao); } continue; } if (rao == NULL) { rao = xmalloc(sizeof(*rao)); rao->next = rap->options; rap->options = rao; rao->type = ndo->nd_opt_type; rao->option = opt; } else free(opt); if (lifetime == ~0U) timerclear(&rao->expire); else { expire.tv_sec = lifetime; expire.tv_usec = 0; timeradd(&rap->received, &expire, &rao->expire); } } ipv6rs_sort(ifp); run_script_reason(ifp, options & DHCPCD_TEST ? "TEST" : "ROUTERADVERT"); if (options & DHCPCD_TEST) exit(EXIT_SUCCESS); /* If we don't require RDNSS then set has_dns = 1 so we fork */ if (!(ifp->state->options->options & DHCPCD_IPV6RA_REQRDNSS)) has_dns = 1; if (has_dns) delete_q_timeout(0, handle_exit_timeout, NULL); delete_timeout(NULL, ifp); ipv6rs_expire(ifp); if (has_dns) daemonise(); else if (options & DHCPCD_DAEMONISE && !(options & DHCPCD_DAEMONISED)) syslog(LOG_WARNING, "%s: did not fork due to an absent RDNSS option in the RA", ifp->name); } ssize_t ipv6rs_env(char **env, const char *prefix, const struct interface *ifp) { ssize_t l; struct timeval now; const struct ra *rap; const struct ra_opt *rao; int i; char buffer[32], buffer2[32]; const char *optn; l = 0; get_monotonic(&now); for (rap = ifp->ras, i = 1; rap; rap = rap->next, i++) { if (env) { snprintf(buffer, sizeof(buffer), "ra%d_from", i); setvar(&env, prefix, buffer, rap->sfrom); } l++; for (rao = rap->options; rao; rao = rao->next) { if (rao->option == NULL) continue; if (env == NULL) { switch (rao->type) { case ND_OPT_PREFIX_INFORMATION: l += 4; break; default: l++; } continue; } switch (rao->type) { case ND_OPT_PREFIX_INFORMATION: optn = "prefix"; break; case ND_OPT_MTU: optn = "mtu"; break; case ND_OPT_RDNSS: optn = "rdnss"; break; case ND_OPT_DNSSL: optn = "dnssl"; break; default: continue; } snprintf(buffer, sizeof(buffer), "ra%d_%s", i, optn); setvar(&env, prefix, buffer, rao->option); l++; switch (rao->type) { case ND_OPT_PREFIX_INFORMATION: snprintf(buffer, sizeof(buffer), "ra%d_prefix_len", i); snprintf(buffer2, sizeof(buffer2), "%d", rap->prefix_len); setvar(&env, prefix, buffer, buffer2); snprintf(buffer, sizeof(buffer), "ra%d_prefix_vltime", i); snprintf(buffer2, sizeof(buffer2), "%d", rap->prefix_vltime); setvar(&env, prefix, buffer, buffer2); snprintf(buffer, sizeof(buffer), "ra%d_prefix_pltime", i); snprintf(buffer2, sizeof(buffer2), "%d", rap->prefix_pltime); setvar(&env, prefix, buffer, buffer2); l += 3; break; } } } if (env) setvard(&env, prefix, "ra_count", i - 1); l++; return l; } static void ipv6rs_free_opts(struct ra *rap) { struct ra_opt *rao, *raon; for (rao = rap->options; rao && (raon = rao->next, 1); rao = raon) { free(rao->option); free(rao); } } void ipv6rs_free(struct interface *ifp) { struct ra *rap, *ran; free(ifp->rs); ifp->rs = NULL; for (rap = ifp->ras; rap && (ran = rap->next, 1); rap = ran) { ipv6rs_free_opts(rap); free(rap->data); free(rap); } ifp->ras = NULL; } void ipv6rs_expire(void *arg) { struct interface *ifp; struct ra *rap, *ran, *ral; struct ra_opt *rao, *raol, *raon; struct timeval now, lt, expire, next; int expired; uint32_t expire_secs; ifp = arg; get_monotonic(&now); expired = 0; expire_secs = ~0U; timerclear(&next); for (rap = ifp->ras, ral = NULL; rap && (ran = rap->next, 1); ral = rap, rap = ran) { lt.tv_sec = rap->lifetime; lt.tv_usec = 0; timeradd(&rap->received, <, &expire); if (timercmp(&now, &expire, >)) { syslog(LOG_INFO, "%s: %s: expired Router Advertisement", ifp->name, rap->sfrom); rap->expired = expired = 1; if (ral) ral->next = ran; else ifp->ras = ran; ipv6rs_free_opts(rap); free(rap); continue; } timersub(&expire, &now, <); if (!timerisset(&next) || timercmp(&next, <, >)) next = lt; for (rao = rap->options, raol = NULL; rao && (raon = rao->next); raol = rao, rao = raon) { if (!timerisset(&rao->expire)) continue; if (timercmp(&now, &rao->expire, >)) { syslog(LOG_INFO, "%s: %s: expired option %d", ifp->name, rap->sfrom, rao->type); rap->expired = expired = 1; if (raol) raol = raon; else rap->options = raon; continue; } timersub(&rao->expire, &now, <); if (!timerisset(&next) || timercmp(&next, <, >)) next = lt; } } if (timerisset(&next)) add_timeout_tv(&next, ipv6rs_expire, ifp); if (expired) run_script_reason(ifp, "ROUTERADVERT"); } int ipv6rs_start(struct interface *ifp) { delete_timeout(NULL, ifp); /* Always make a new probe as the underlying hardware * address could have changed. */ ipv6rs_makeprobe(ifp); if (ifp->rs == NULL) return -1; ifp->rsprobes = 0; ipv6rs_sendprobe(ifp); return 0; }