From: Roy Marples Date: Fri, 3 May 2013 12:11:49 +0000 (+0000) Subject: Add IPv6 DAD detection. X-Git-Tag: v5.99.6~8 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=66fd5d67f0598b77081d36c1025f70bf604a4bde;p=thirdparty%2Fdhcpcd.git Add IPv6 DAD detection. Sadly, it seems that userland cannot send from the unspecified address. This means that for RFC conformancy, we have to rely on the kernel performing DAD. --- diff --git a/dhcp6.c b/dhcp6.c index c8fc8605..1adeb456 100644 --- a/dhcp6.c +++ b/dhcp6.c @@ -25,6 +25,8 @@ * SUCH DAMAGE. */ +/* TODO: We should decline dupliate addresses detected */ + #include #include @@ -50,6 +52,7 @@ #include "dhcp6.h" #include "duid.h" #include "eloop.h" +#include "ipv6ns.h" #include "ipv6rs.h" #include "platform.h" #include "script.h" @@ -1030,6 +1033,24 @@ dhcp6_addrexists(const struct ipv6_addr *a) return 0; } +static void +dhcp6_dadcallback(void *arg) +{ + struct ipv6_addr *ap = arg; + + ipv6ns_cancelprobeaddr(ap); + ap->dadcompleted = 1; + if (ap->dad) + /* XXX FIXME + * We should decline the address */ + syslog(LOG_WARNING, "%s: DAD detected %s", + ap->iface->name, ap->saddr); +#ifdef IPV6_SEND_DAD + else + ipv6_addaddr(ap); +#endif +} + static int dhcp6_findna(struct interface *ifp, const uint8_t *iaid, const uint8_t *d, size_t l) @@ -1063,8 +1084,10 @@ dhcp6_findna(struct interface *ifp, const uint8_t *iaid, syslog(LOG_ERR, "%s: %m", __func__); break; } + a->iface = ifp; a->new = 1; a->onlink = 1; /* XXX: suprised no DHCP opt for this */ + a->dadcallback = dhcp6_dadcallback; memcpy(a->iaid, iaid, sizeof(a->iaid)); p = D6_COPTION_DATA(o); memcpy(&a->addr.s6_addr, p, @@ -1135,8 +1158,10 @@ dhcp6_findpd(struct interface *ifp, const uint8_t *iaid, syslog(LOG_ERR, "%s: %m", __func__); break; } + a->iface = ifp; a->new = 1; a->onlink = 0; + a->dadcallback = dhcp6_dadcallback; memcpy(a->iaid, iaid, sizeof(a->iaid)); p = D6_COPTION_DATA(o); memcpy(&u32, p, sizeof(u32)); @@ -1410,9 +1435,10 @@ dhcp6_delegate_addr(struct interface *ifp, const struct ipv6_addr *prefix, syslog(LOG_ERR, "%s: %m", __func__); return NULL; } - + a->iface = ifp; a->new = 1; a->onlink = 1; + a->dadcallback = dhcp6_dadcallback; a->delegating_iface = ifs; memcpy(&a->iaid, &prefix->iaid, sizeof(a->iaid)); a->prefix_pltime = prefix->prefix_pltime; @@ -1486,7 +1512,7 @@ dhcp6_delegate_prefix(struct interface *ifp) } if (k) { ifd_state = D6_STATE(ifd); - ipv6_addaddrs(ifd, &ifd_state->addrs); + ipv6ns_probeaddrs(&ifd_state->addrs); } } } @@ -1531,7 +1557,7 @@ dhcp6_find_delegates(struct interface *ifp) if (k) { syslog(LOG_INFO, "%s: adding delegated prefixes", ifp->name); state = D6_STATE(ifp); - ipv6_addaddrs(ifp, &state->addrs); + ipv6ns_probeaddrs(&state->addrs); ipv6_buildroutes(); } return k; @@ -1783,7 +1809,7 @@ recv: dhcp6_startexpire, ifp); if (ifp->options->ia_type == D6_OPTION_IA_PD) dhcp6_delegate_prefix(ifp); - ipv6_addaddrs(ifp, &state->addrs); + ipv6ns_probeaddrs(&state->addrs); if (state->renew || state->rebind) syslog(LOG_INFO, "%s: renew in %u seconds, rebind in %u seconds", diff --git a/if-bsd.c b/if-bsd.c index 6367be9d..78425d0f 100644 --- a/if-bsd.c +++ b/if-bsd.c @@ -308,6 +308,11 @@ if_address6(const struct interface *ifp, const struct ipv6_addr *a, int action) return -1; memset(&ifa, 0, sizeof(ifa)); strlcpy(ifa.ifra_name, ifp->name, sizeof(ifa.ifra_name)); + ifa.ifra_flags = IN6_IFF_TENTATIVE; +#if 0 + if (a->autoconf) + ifa.ifra_flags |= IN6_IFF_AUTOCONF; +#endif #define ADDADDR(v, addr) { \ (v)->sin6_family = AF_INET6; \ diff --git a/if-options.c b/if-options.c index 33d85af4..8d83cbd3 100644 --- a/if-options.c +++ b/if-options.c @@ -1091,6 +1091,7 @@ read_config(const char *file, #endif #ifdef INET6 ifo->options |= DHCPCD_IPV6 | DHCPCD_IPV6RS | DHCPCD_IPV6RA_REQRDNSS; + ifo->dadtransmits = ipv6_dadtransmits(ifname); #endif ifo->timeout = DEFAULT_TIMEOUT; ifo->reboot = DEFAULT_REBOOT; diff --git a/if-options.h b/if-options.h index 9a4a835b..b4c03041 100644 --- a/if-options.h +++ b/if-options.h @@ -146,6 +146,7 @@ struct if_options { uint16_t ia_type; size_t iaid_len; struct if_iaid *iaid; + int dadtransmits; #endif }; diff --git a/ipv6.c b/ipv6.c index 042c2a52..e8752815 100644 --- a/ipv6.c +++ b/ipv6.c @@ -247,8 +247,28 @@ ipv6_prefixlen(const struct in6_addr *mask) return x * NBBY + y; } +int +ipv6_addaddr(struct ipv6_addr *ap) +{ + + syslog(ap->new ? LOG_INFO : LOG_DEBUG, + "%s: adding address %s", ap->iface->name, ap->saddr); + if (add_address6(ap->iface, ap) == -1) { + syslog(LOG_ERR, "add_address6 %m"); + return -1; + } + ap->new = 0; + ap->added = 1; + if (ipv6_removesubnet(ap->iface, ap) == -1) + syslog(LOG_ERR,"ipv6_removesubnet %m"); + syslog(LOG_DEBUG, + "%s: pltime %d seconds, vltime %d seconds", + ap->iface->name, ap->prefix_pltime, ap->prefix_vltime); + return 0; +} + ssize_t -ipv6_addaddrs(const struct interface *ifp, struct ipv6_addrhead *addrs) +ipv6_addaddrs(struct ipv6_addrhead *addrs) { struct ipv6_addr *ap; ssize_t i; @@ -258,20 +278,8 @@ ipv6_addaddrs(const struct interface *ifp, struct ipv6_addrhead *addrs) if (ap->prefix_vltime == 0 || IN6_IS_ADDR_UNSPECIFIED(&ap->addr)) continue; - syslog(ap->new ? LOG_INFO : LOG_DEBUG, - "%s: adding address %s", - ifp->name, ap->saddr); - if (add_address6(ifp, ap) == -1) - syslog(LOG_ERR, "add_address6 %m"); - else { + if (ipv6_addaddr(ap) == 0) i++; - if (ipv6_removesubnet(ifp, ap) == -1) - syslog(LOG_ERR,"ipv6_removesubnet %m"); - syslog(LOG_DEBUG, - "%s: pltime %d seconds, vltime %d seconds", - ifp->name, ap->prefix_pltime, - ap->prefix_vltime); - } } return i; diff --git a/ipv6.h b/ipv6.h index 67e8a366..ad4b91a8 100644 --- a/ipv6.h +++ b/ipv6.h @@ -39,6 +39,7 @@ struct ipv6_addr { TAILQ_ENTRY(ipv6_addr) next; + struct interface *iface; struct in6_addr prefix; int prefix_len; uint32_t prefix_vltime; @@ -47,8 +48,17 @@ struct ipv6_addr { uint8_t onlink; uint8_t new; char saddr[INET6_ADDRSTRLEN]; + uint8_t added; + uint8_t autoconf; uint8_t iaid[4]; struct interface *delegating_iface; + + void (*dadcallback)(void *); + uint8_t dad; + uint8_t dadcompleted; + uint8_t *ns; + size_t nslen; + int nsprobes; }; TAILQ_HEAD(ipv6_addrhead, ipv6_addr); @@ -72,7 +82,8 @@ int ipv6_makeaddr(struct in6_addr *, const char *, const struct in6_addr *, int) int ipv6_makeprefix(struct in6_addr *, const struct in6_addr *, int); int ipv6_mask(struct in6_addr *, int); int ipv6_prefixlen(const struct in6_addr *); -ssize_t ipv6_addaddrs(const struct interface *, struct ipv6_addrhead *); +int ipv6_addaddr(struct ipv6_addr *); +ssize_t ipv6_addaddrs(struct ipv6_addrhead *); int ipv6_removesubnet(const struct interface *, struct ipv6_addr *); void ipv6_buildroutes(void); void ipv6_drop(struct interface *); diff --git a/ipv6ns.c b/ipv6ns.c index 0541ca53..09a37e55 100644 --- a/ipv6ns.c +++ b/ipv6ns.c @@ -49,18 +49,41 @@ #define ELOOP_QUEUE 1 #include "common.h" #include "dhcpcd.h" +#include "dhcp6.h" #include "eloop.h" #include "ipv6.h" #include "ipv6ns.h" #include "script.h" -#define MIN_RANDOM_FACTOR (500 * 1000) /* milliseconds in usecs */ -#define MAX_RANDOM_FACTOR (1500 * 1000) /* milliseconds in usecs */ +#define MIN_RANDOM_FACTOR 500 /* milliseconds */ +#define MAX_RANDOM_FACTOR 1500 /* milliseconds */ +#define MIN_RANDOM_FACTOR_U MIN_RANDOM_FACTOR * 1000 /* usecs */ +#define MAX_RANDOM_FACTOR_U MAX_RANDOM_FACTOR * 1000 /* usecs */ + +#if BYTE_ORDER == BIG_ENDIAN +#define IPV6_ADDR_INT32_ONE 1 +#define IPV6_ADDR_INT16_MLL 0xff02 +#elif BYTE_ORDER == LITTLE_ENDIAN +#define IPV6_ADDR_INT32_ONE 0x01000000 +#define IPV6_ADDR_INT16_MLL 0x02ff +#endif /* Debugging Neighbor Solicitations is a lot of spam, so disable it */ //#define DEBUG_NS +// + +/* Currently, no known kernel allows us to send from the unspecified address + * which is required for DAD to work. This isn't that much of a problem as + * the kernel will do DAD for us correctly, however we don't know the exact + * randomness the kernel applies to the timeouts. So we just follow the same + * logic and have a little faith. + * This define is purely for completeness */ +// #define IPV6_SEND_DAD static int sock = -1; +#ifdef IPV6_SEND_DAD +static int unspec_sock = -1; +#endif static struct sockaddr_in6 from; static struct msghdr sndhdr; static struct iovec sndiov[2]; @@ -89,6 +112,15 @@ ipv6ns_open(void) int on; int len; struct icmp6_filter filt; +#ifdef IPV6_SEND_DAD + union { + struct sockaddr sa; + struct sockaddr_in6 sin; + } su; +#endif + + if (sock != -1) + return sock; sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (sock == -1) @@ -104,6 +136,24 @@ ipv6ns_open(void) goto eexit; ICMP6_FILTER_SETBLOCKALL(&filt); + +#ifdef IPV6_SEND_DAD + /* We send DAD requests from the unspecified address. */ + unspec_sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + if (unspec_sock == -1) + goto eexit; + if (setsockopt(unspec_sock, IPPROTO_ICMPV6, ICMP6_FILTER, + &filt, sizeof(filt)) == -1) + goto eexit; + memset(&su, 0, sizeof(su)); + su.sin.sin6_family = AF_INET6; +#ifdef SIN6_LEN + su.sin.sin6_len = sizeof(su.sin); +#endif + if (bind(unspec_sock, &su.sa, sizeof(su.sin)) == -1) + goto eexit; +#endif + ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filt); if (setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt)) == -1) @@ -134,9 +184,12 @@ ipv6ns_open(void) rcvhdr.msg_controllen = len; rcviov[0].iov_base = ansbuf; rcviov[0].iov_len = sizeof(ansbuf); + + eloop_event_add(sock, ipv6ns_handledata, NULL); return sock; eexit: + syslog(LOG_ERR, "%s: %m", __func__); close(sock); sock = -1; free(sndbuf); @@ -146,30 +199,6 @@ eexit: return -1; } -static int -ipv6ns_makeprobe(struct ra *rap) -{ - struct nd_neighbor_solicit *ns; - struct nd_opt_hdr *nd; - - free(rap->ns); - rap->nslen = sizeof(*ns) + ROUNDUP8(rap->iface->hwlen + 2); - rap->ns = calloc(1, rap->nslen); - if (rap->ns == NULL) - return -1; - ns = (struct nd_neighbor_solicit *)(void *)rap->ns; - ns->nd_ns_type = ND_NEIGHBOR_SOLICIT; - ns->nd_ns_cksum = 0; - ns->nd_ns_code = 0; - ns->nd_ns_reserved = 0; - ns->nd_ns_target = rap->from; - nd = (struct nd_opt_hdr *)(rap->ns + sizeof(*ns)); - nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR; - nd->nd_opt_len = (ROUNDUP8(rap->iface->hwlen + 2)) >> 3; - memcpy(nd + 1, rap->iface->hwaddr, rap->iface->hwlen); - return 0; -} - static void ipv6ns_unreachable(void *arg) { @@ -185,26 +214,194 @@ ipv6ns_unreachable(void *arg) } void -ipv6ns_sendprobe(void *arg) +ipv6ns_cancelprobeaddr(struct ipv6_addr *ap) { - struct ra *rap = arg; + + eloop_timeout_delete(ipv6ns_probeaddr, ap); + if (ap->dadcallback) + eloop_timeout_delete(ap->dadcallback, ap); +} + +void +ipv6ns_probeaddr(void *arg) +{ + struct ipv6_addr *ap = arg; +#ifdef IPV6_SEND_DAD + struct nd_neighbor_solicit *ns; + struct nd_opt_hdr *nd; struct sockaddr_in6 dst; struct cmsghdr *cm; struct in6_pktinfo pi; int hoplimit = HOPLIMIT; +#else + struct timeval mtv; + int i; +#endif struct timeval tv, rtv; - if (sock == -1) { - if (ipv6ns_open() == -1) { - syslog(LOG_ERR, "%s: ipv6ns_open: %m", __func__); + if (ap->dadcallback && + (ap->new == 0 || ap->nsprobes >= ap->iface->options->dadtransmits)) + { +#ifdef IPV6_SEND_DAD + ap->dadcallback(ap); +#else + ipv6_addaddr(ap); +#endif + return; + } + + if (ipv6ns_open() == -1) + return; + + ap->dadcompleted = 0; + +#ifdef IPV6_SEND_DAD + if (!ap->ns) { + ap->nslen = sizeof(*ns) + ROUNDUP8(ap->iface->hwlen + 2); + ap->ns = calloc(1, ap->nslen); + if (ap->ns == NULL) { + syslog(LOG_ERR, "%s: %m", __func__); return; } - eloop_event_add(sock, ipv6ns_handledata, NULL); + ns = (struct nd_neighbor_solicit *)(void *)ap->ns; + ns->nd_ns_type = ND_NEIGHBOR_SOLICIT; + //ns->nd_ns_cksum = 0; + //ns->nd_ns_code = 0; + //ns->nd_ns_reserved = 0; + ns->nd_ns_target = ap->addr; + nd = (struct nd_opt_hdr *)(ap->ns + sizeof(*ns)); + nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR; + nd->nd_opt_len = (ROUNDUP8(ap->iface->hwlen + 2)) >> 3; + memcpy(nd + 1, ap->iface->hwaddr, ap->iface->hwlen); } - if (!rap->ns && ipv6ns_makeprobe(rap) == -1) { - syslog(LOG_ERR, "%s: ipv6ns_makeprobe: %m", __func__); + memset(&dst, 0, sizeof(dst)); + dst.sin6_family = AF_INET6; +#ifdef SIN6_LEN + dst.sin6_len = sizeof(dst); +#endif + dst.sin6_addr.s6_addr16[0] = IPV6_ADDR_INT16_MLL; + dst.sin6_addr.s6_addr16[1] = 0; + dst.sin6_addr.s6_addr32[1] = 0; + dst.sin6_addr.s6_addr32[2] = IPV6_ADDR_INT32_ONE; + dst.sin6_addr.s6_addr32[3] = ap->addr.s6_addr32[3]; + dst.sin6_addr.s6_addr[12] = 0xff; + + //memcpy(&dst.sin6_addr, &ap->addr, sizeof(dst.sin6_addr)); + //dst.sin6_scope_id = ap->iface->index; + + sndhdr.msg_name = (caddr_t)&dst; + sndhdr.msg_iov[0].iov_base = ap->ns; + sndhdr.msg_iov[0].iov_len = ap->nslen; + + /* 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 = ap->iface->index; + 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)); + +#ifdef DEBUG_NS + syslog(LOG_INFO, "%s: sending IPv6 NS for %s", + ap->iface->name, ap->saddr); + if (ap->dadcallback == NULL) + syslog(LOG_WARNING, "%s: no callback!", ap->iface->name); +#endif + if (sendmsg(unspec_sock, &sndhdr, 0) == -1) + syslog(LOG_ERR, "%s: %s: sendmsg: %m", + __func__, ap->iface->name); + + if (ap->dadcallback) { + ms_to_tv(&tv, RETRANS_TIMER); + ms_to_tv(&rtv, MIN_RANDOM_FACTOR); + timeradd(&tv, &rtv, &tv); + rtv.tv_sec = 0; + rtv.tv_usec = arc4random() % + (MAX_RANDOM_FACTOR_U - MIN_RANDOM_FACTOR_U); + timeradd(&tv, &rtv, &tv); + + eloop_timeout_add_tv(&tv, + ++(ap->nsprobes) < ap->iface->options->dadtransmits ? + ipv6ns_probeaddr : ap->dadcallback, + ap); + } +#else /* IPV6_SEND_DAD */ + /* Let the kernel handle DAD. + * We don't know the timings, so just wait for the max */ + ipv6_addaddr(ap); + if (ap->dadcallback) { + mtv.tv_sec = 0; + mtv.tv_usec = 0; + for (i = 0; i < ap->iface->options->dadtransmits; i++) { + ms_to_tv(&tv, RETRANS_TIMER); + ms_to_tv(&rtv, MAX_RANDOM_FACTOR); + timeradd(&tv, &rtv, &tv); + timeradd(&mtv, &tv, &mtv); + } + eloop_timeout_add_tv(&mtv, ap->dadcallback, ap); + } +#endif /* IPV6_SEND_DAD */ +} + +ssize_t +ipv6ns_probeaddrs(struct ipv6_addrhead *addrs) +{ + struct ipv6_addr *ap; + ssize_t i; + + i = 0; + TAILQ_FOREACH(ap, addrs, next) { + if (ap->prefix_vltime == 0 || + IN6_IS_ADDR_UNSPECIFIED(&ap->addr)) + continue; + ipv6ns_probeaddr(ap); + i++; + } + + return i; +} + +void +ipv6ns_proberouter(void *arg) +{ + struct ra *rap = arg; + struct nd_neighbor_solicit *ns; + struct nd_opt_hdr *nd; + struct sockaddr_in6 dst; + struct cmsghdr *cm; + struct in6_pktinfo pi; + int hoplimit = HOPLIMIT; + struct timeval tv, rtv; + + if (ipv6ns_open() == -1) return; + + if (!rap->ns) { + rap->nslen = sizeof(*ns) + ROUNDUP8(rap->iface->hwlen + 2); + rap->ns = calloc(1, rap->nslen); + if (rap->ns == NULL) { + syslog(LOG_ERR, "%s: %m", __func__); + return; + } + ns = (struct nd_neighbor_solicit *)(void *)rap->ns; + ns->nd_ns_type = ND_NEIGHBOR_SOLICIT; + //ns->nd_ns_cksum = 0; + //ns->nd_ns_code = 0; + //ns->nd_ns_reserved = 0; + ns->nd_ns_target = rap->from; + nd = (struct nd_opt_hdr *)(rap->ns + sizeof(*ns)); + nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR; + nd->nd_opt_len = (ROUNDUP8(rap->iface->hwlen + 2)) >> 3; + memcpy(nd + 1, rap->iface->hwaddr, rap->iface->hwlen); } memset(&dst, 0, sizeof(dst)); @@ -248,9 +445,9 @@ ipv6ns_sendprobe(void *arg) ms_to_tv(&rtv, MIN_RANDOM_FACTOR); timeradd(&tv, &rtv, &tv); rtv.tv_sec = 0; - rtv.tv_usec = arc4random() % (MAX_RANDOM_FACTOR - MIN_RANDOM_FACTOR); + rtv.tv_usec = arc4random() % (MAX_RANDOM_FACTOR_U - MIN_RANDOM_FACTOR_U); timeradd(&tv, &rtv, &tv); - eloop_timeout_add_tv(&tv, ipv6ns_sendprobe, rap); + eloop_timeout_add_tv(&tv, ipv6ns_proberouter, rap); if (rap->nsprobes++ == 0) eloop_timeout_add_sec(DELAY_FIRST_PROBE_TIME, @@ -271,7 +468,12 @@ ipv6ns_handledata(__unused void *arg) struct nd_neighbor_advert *nd_na; struct ra *rap; int is_router, is_solicited; +#ifdef DEBUG_NS + int found; +#endif struct timeval tv; + struct dhcp6_state *d6state; + struct ipv6_addr *ap; len = recvmsg(sock, &rcvhdr, 0); if (len == -1) { @@ -340,16 +542,51 @@ ipv6ns_handledata(__unused void *arg) return; } +#ifdef DEBUG_NS + found = 0; +#endif TAILQ_FOREACH(rap, &ipv6_routers, next) { - if (rap->iface == ifp && - memcmp(rap->from.s6_addr, from.sin6_addr.s6_addr, + if (rap->iface != ifp) + continue; + if (memcmp(rap->from.s6_addr, nd_na->nd_na_target.s6_addr, sizeof(rap->from.s6_addr)) == 0) break; + TAILQ_FOREACH(ap, &rap->addrs, next) { + if (memcmp(ap->addr.s6_addr, + nd_na->nd_na_target.s6_addr, + sizeof(ap->addr.s6_addr)) == 0) + { + ap->dad = 1; + if (ap->dadcallback) + ap->dadcallback(ap); +#ifdef DEBUG_NS + found++; +#endif + } + } } if (rap == NULL) { + d6state = D6_STATE(ifp); + if (d6state) { + TAILQ_FOREACH(ap, &d6state->addrs, next) { + if (memcmp(ap->addr.s6_addr, + nd_na->nd_na_target.s6_addr, + sizeof(ap->addr.s6_addr)) == 0) + { + ap->dad = 1; + if (ap->dadcallback) + ap->dadcallback(ap); #ifdef DEBUG_NS - syslog(LOG_DEBUG, "%s: unexpected NA from %s", - ifp->name, sfrom); + found++; +#endif + } + } + } + +#ifdef DEBUG_NS + if (found == 0) + syslog(LOG_DEBUG, "%s: unexpected NA from %s", + ifp->name, sfrom); #endif return; } @@ -377,21 +614,7 @@ ipv6ns_handledata(__unused void *arg) tv.tv_sec = REACHABLE_TIME; tv.tv_usec = 0; } - eloop_timeout_add_tv(&tv, ipv6ns_sendprobe, rap); + eloop_timeout_add_tv(&tv, ipv6ns_proberouter, rap); eloop_timeout_delete(ipv6ns_unreachable, rap); } } - -int -ipv6ns_init(void) -{ - int fd; - - fd = ipv6ns_open(); - if (fd == -1) { - syslog(LOG_ERR, "ipv6ns_open: %m"); - return -1; - } - eloop_event_add(fd, ipv6ns_handledata, NULL); - return 0; -} diff --git a/ipv6ns.h b/ipv6ns.h index e14856b2..7b0a9e56 100644 --- a/ipv6ns.h +++ b/ipv6ns.h @@ -33,9 +33,11 @@ #define MAX_REACHABLE_TIME 3600 /* seconds */ #define REACHABLE_TIME 30 /* seconds */ -#define RETRANS_TIMER 1 /* second */ +#define RETRANS_TIMER 1000 /* milliseconds */ #define DELAY_FIRST_PROBE_TIME 5 /* seconds */ -int ipv6ns_init(void); -void ipv6ns_sendprobe(void *); +void ipv6ns_probeaddr(void *); +ssize_t ipv6ns_probeaddrs(struct ipv6_addrhead *); +void ipv6ns_cancelprobeaddr(struct ipv6_addr *); +void ipv6ns_proberouter(void *); #endif diff --git a/ipv6rs.c b/ipv6rs.c index 363aaf87..79c646e5 100644 --- a/ipv6rs.c +++ b/ipv6rs.c @@ -308,7 +308,7 @@ ipv6rs_freedrop_addrs(struct ra *rap, int drop) /* Only drop the address if no other RAs have assigned it. * This is safe because the RA is removed from the list * before we are called. */ - if (drop && (options & DHCPCD_IPV6RA_OWN) && + if (drop && ap->added && !IN6_IS_ADDR_UNSPECIFIED(&ap->addr) && !ipv6rs_addrexists(ap) && !dhcp6_addrexists(ap)) { @@ -393,6 +393,23 @@ add_router(struct ra *router) TAILQ_INSERT_HEAD(&ipv6_routers, router, next); } +static void +ipv6rs_dadcallback(void *arg) +{ + struct ipv6_addr *ap = arg; + + ipv6ns_cancelprobeaddr(ap); + ap->dadcompleted = 1; + if (ap->dad) + /* No idea what how to try and make another address :( */ + syslog(LOG_WARNING, "%s: DAD detected %s", + ap->iface->name, ap->saddr); +#ifdef IPV6_SEND_DAD + else + ipv6_addaddr(ap); +#endif +} + /* ARGSUSED */ static void ipv6rs_handledata(__unused void *arg) @@ -620,13 +637,15 @@ ipv6rs_handledata(__unused void *arg) !(pi->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK)) break; - ap = malloc(sizeof(*ap)); + ap = calloc(1, sizeof(*ap)); if (ap == NULL) { syslog(LOG_ERR, "%s: %m", __func__); break; } + ap->iface = rap->iface; ap->new = 1; ap->onlink = 0; + ap->autoconf = 1; ap->prefix_len = pi->nd_opt_pi_prefix_len; memcpy(ap->prefix.s6_addr, pi->nd_opt_pi_prefix.s6_addr, @@ -651,14 +670,16 @@ ipv6rs_handledata(__unused void *arg) memset(&ap->addr, 0, sizeof(ap->addr)); ap->saddr[0] = '\0'; } + ap->dadcallback = ipv6rs_dadcallback; TAILQ_INSERT_TAIL(&rap->addrs, ap, next); } else if (ap->prefix_vltime != ntohl(pi->nd_opt_pi_valid_time) || ap->prefix_pltime != - ntohl(pi->nd_opt_pi_preferred_time)) + ntohl(pi->nd_opt_pi_preferred_time) || + ap->dad) + { ap->new = 1; - else - ap->new = 0; + } if (pi->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) ap->onlink = 1; @@ -666,6 +687,8 @@ ipv6rs_handledata(__unused void *arg) ntohl(pi->nd_opt_pi_valid_time); ap->prefix_pltime = ntohl(pi->nd_opt_pi_preferred_time); + ap->nsprobes = 0; + ap->dad = 0; if (opt) { l = strlen(opt); tmp = realloc(opt, @@ -806,12 +829,12 @@ ipv6rs_handledata(__unused void *arg) if (new_rap) add_router(rap); - if (options & DHCPCD_IPV6RA_OWN && !(options & DHCPCD_TEST)) - ipv6_addaddrs(ifp, &rap->addrs); if (options & DHCPCD_TEST) { script_runreason(ifp, "TEST"); goto handle_flag; } + if (options & DHCPCD_IPV6RA_OWN) + ipv6ns_probeaddrs(&rap->addrs); ipv6_buildroutes(); /* We will get run by the expire function */ if (rap->lifetime) @@ -837,7 +860,7 @@ ipv6rs_handledata(__unused void *arg) ifp->options->options & DHCPCD_IPV6RA_OWN_DEFAULT) { rap->nsprobes = 0; - ipv6ns_sendprobe(rap); + ipv6ns_proberouter(rap); } handle_flag: @@ -1074,6 +1097,7 @@ ipv6rs_expire(void *arg) syslog(LOG_INFO, "%s: %s: expired address", ifp->name, ap->saddr); + eloop_timeout_delete(NULL, ap); TAILQ_REMOVE(&rap->addrs, ap, next); free(ap); /* No need to delete it as the kernel @@ -1200,26 +1224,3 @@ ipv6rs_drop(struct interface *ifp) script_runreason(ifp, "ROUTERADVERT"); } } - -int -ipv6rs_init(void) -{ - int fd; - - fd = ipv6rs_open(); - if (fd == -1) { - syslog(LOG_ERR, "ipv6rs: %m"); - options &= ~(DHCPCD_IPV6RS | - DHCPCD_IPV6RA_OWN | DHCPCD_IPV6RA_OWN_DEFAULT); - return -1; - } - - eloop_event_add(fd, ipv6rs_handledata, NULL); - // atexit(restore_rtadv); - - if (options & DHCPCD_IPV6RA_OWN || - options & DHCPCD_IPV6RA_OWN_DEFAULT) - return ipv6ns_init(); - - return 0; -} diff --git a/ipv6rs.h b/ipv6rs.h index 5cdb9410..2b53d0cf 100644 --- a/ipv6rs.h +++ b/ipv6rs.h @@ -57,7 +57,7 @@ struct ra { uint32_t mtu; struct ipv6_addrhead addrs; TAILQ_HEAD(, ra_opt) options; - + unsigned char *ns; size_t nslen; int nsprobes; @@ -76,7 +76,6 @@ struct rs_state { #define RS_STATE(a) ((struct rs_state *)(ifp)->if_data[IF_DATA_IPV6RS]) #ifdef INET6 -int ipv6rs_init(void); int ipv6rs_start(struct interface *); ssize_t ipv6rs_env(char **, const char *, const struct interface *); const struct ipv6_addr * ipv6rs_findprefix(const struct ipv6_addr *); @@ -89,7 +88,6 @@ void ipv6rs_expire(void *arg); int ipv6rs_has_ra(const struct interface *); void ipv6rs_drop(struct interface *); #else -#define ipv6rs_init() {} #define ipv6rs_start(a) {} #define ipv6rs_free(a) #define ipv6rs_has_ra(a) 0 diff --git a/platform-bsd.c b/platform-bsd.c index 93048fc1..19e4fed7 100644 --- a/platform-bsd.c +++ b/platform-bsd.c @@ -134,3 +134,12 @@ check_ipv6(const char *ifname) return 1; } + +int +ipv6_dadtransmits(__unused const char *ifname) +{ + int r; + + r = get_inet6_sysctl(IPV6CTL_DAD_COUNT); + return r < 0 ? 0 : r; +} diff --git a/platform-linux.c b/platform-linux.c index 323a3bcb..a8a6195f 100644 --- a/platform-linux.c +++ b/platform-linux.c @@ -245,3 +245,17 @@ forward: } return 1; } + +int +ipv6_dadtransmits(const char *ifname) +{ + char path[256]; + int r; + + if (ifname == NULL) + ifname = "default"; + + snprintf(path, sizeof(path), "%s/%s/dad_transmits", prefix, ifname); + r = check_proc_int(path); + return r < 0 ? 0 : r; +} diff --git a/platform.h b/platform.h index 2c738797..38e90991 100644 --- a/platform.h +++ b/platform.h @@ -30,5 +30,6 @@ char *hardware_platform(void); int check_ipv6(const char *); +int ipv6_dadtransmits(const char *); #endif