From: Roy Marples Date: Sat, 5 Jan 2019 11:42:12 +0000 (+0000) Subject: ip6: Implement IPv6 address sharing X-Git-Tag: v7.1.0~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=cd09e583c76755545396676833fad09313611f72;p=thirdparty%2Fdhcpcd.git ip6: Implement IPv6 address sharing This allows the same IPv6 address to exist on more than one interface. Whenever dhcpcd address an IPv6 address, it will advertise it along with the hardware address of the preferred interface. This is heavliy reliant on the kernel supporting this as it's the kernel that handle the Duplicate Address Detection. In a nutshell it needs to support RFC 7527 and ignore NA packets from any hardware address the host owns. Currently the only known kernel that fully supports this is NetBSD-8.99.27 --- diff --git a/src/dhcp6.c b/src/dhcp6.c index 502d58f0..6fef9899 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -1516,6 +1516,7 @@ dhcp6_dadcallback(void *arg) if (valid) dhcpcd_daemonise(ifp->ctx); } + ipv6nd_advertise(ia); } } } @@ -3952,6 +3953,21 @@ dhcp6_dropnondelegates(struct interface *ifp) dhcp6_drop(ifp, "EXPIRE6"); } +void +dhcp6_abort(struct interface *ifp) +{ + struct dhcp6_state *state; + struct ipv6_addr *ia; + + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_start1, ifp); + state = D6_STATE(ifp); + if (state == NULL) + return; + TAILQ_FOREACH(ia, &state->addrs, next) { + ipv6nd_advertise(ia); + } +} + void dhcp6_handleifa(int cmd, struct ipv6_addr *ia, pid_t pid) { diff --git a/src/dhcp6.h b/src/dhcp6.h index 5211dae4..53f4a567 100644 --- a/src/dhcp6.h +++ b/src/dhcp6.h @@ -232,6 +232,7 @@ ssize_t dhcp6_env(char **, const char *, const struct interface *, void dhcp6_free(struct interface *); void dhcp6_handleifa(int, struct ipv6_addr *, pid_t); int dhcp6_dadcompleted(const struct interface *); +void dhcp6_abort(struct interface *); void dhcp6_drop(struct interface *, const char *); void dhcp6_dropnondelegates(struct interface *ifp); int dhcp6_dump(struct interface *); diff --git a/src/dhcpcd.c b/src/dhcpcd.c index d41a5dd1..6c46820b 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -735,6 +735,7 @@ dhcpcd_handlecarrier(struct dhcpcd_ctx *ctx, int carrier, unsigned int flags, #endif dhcp_abort(ifp); ipv6nd_expire(ifp, 0); + dhcp6_abort(ifp); #else dhcpcd_drop(ifp, 0); #endif diff --git a/src/if-bsd.c b/src/if-bsd.c index 7df647a2..5f76b544 100644 --- a/src/if-bsd.c +++ b/src/if-bsd.c @@ -1271,7 +1271,8 @@ if_machinearch(char *str, size_t len) #ifdef INET6 #if (defined(IPV6CTL_ACCEPT_RTADV) && !defined(ND6_IFF_ACCEPT_RTADV)) || \ - defined(IPV6CTL_USETEMPADDR) || defined(IPV6CTL_TEMPVLTIME) + defined(IPV6CTL_USETEMPADDR) || defined(IPV6CTL_TEMPVLTIME) || \ + defined(IPV6CTL_FORWARDING) #define get_inet6_sysctl(code) inet6_sysctl(code, 0, 0) #define set_inet6_sysctl(code, val) inet6_sysctl(code, val, 1) static int @@ -1355,6 +1356,19 @@ ip6_temp_valid_lifetime(__unused const char *ifname) } #endif +int +ip6_forwarding(__unused const char *ifname) +{ + int val; + +#ifdef IPV6CTL_FORWARDING + val = get_inet6_sysctl(IPV6CTL_FORWARDING); +#else + val = get_inet6_sysctlbyname("net.inet6.ip6.forwarding"); +#endif + return val < 0 ? 0 : val; +} + #ifdef SIOCIFAFATTACH static int af_attach(int s, const struct interface *ifp, int af) diff --git a/src/if-linux.c b/src/if-linux.c index e9223e58..1dd0bab7 100644 --- a/src/if-linux.c +++ b/src/if-linux.c @@ -1756,4 +1756,18 @@ ip6_temp_valid_lifetime(const char *ifname) return val < 0 ? TEMP_VALID_LIFETIME : val; } #endif /* IPV6_MANAGETEMPADDR */ + +int +ip6_forwarding(const char *ifname) +{ + char path[256]; + int val; + + if (ifname == NULL) + ifname = "all"; + snprintf(path, sizeof(path), "%s/%s/forwarding", prefix, ifname); + val = check_proc_int(path); + return val == -1 ? 0 : val; +} + #endif /* INET6 */ diff --git a/src/if.h b/src/if.h index 51ef75c0..d260d2d1 100644 --- a/src/if.h +++ b/src/if.h @@ -197,6 +197,7 @@ int ip6_temp_valid_lifetime(const char *ifname); #else #define ip6_use_tempaddr(a) (0) #endif +int ip6_forwarding(const char *ifname); int if_address6(unsigned char, const struct ipv6_addr *); int if_addrflags6(const struct interface *, const struct in6_addr *, diff --git a/src/ipv6.c b/src/ipv6.c index 49315d14..38cb06d8 100644 --- a/src/ipv6.c +++ b/src/ipv6.c @@ -626,32 +626,19 @@ ipv6_deleteaddr(struct ipv6_addr *ia) break; } } + + /* Advertise the address if it exists on another interface. */ + ipv6nd_advertise(ia); } static int ipv6_addaddr1(struct ipv6_addr *ia, const struct timespec *now) { struct interface *ifp; - struct ipv6_state *state; - struct ipv6_addr *ia2; uint32_t pltime, vltime; + bool vltime_was_zero; __printflike(1, 2) void (*logfunc)(const char *, ...); - /* Ensure no other interface has this address */ - TAILQ_FOREACH(ifp, ia->iface->ctx->ifaces, next) { - if (ifp == ia->iface) - continue; - state = IPV6_STATE(ifp); - if (state == NULL) - continue; - TAILQ_FOREACH(ia2, &state->addrs, next) { - if (IN6_ARE_ADDR_EQUAL(&ia2->addr, &ia->addr)) { - ipv6_deleteaddr(ia2); - break; - } - } - } - /* Remember the interface of the address. */ ifp = ia->iface; @@ -714,7 +701,7 @@ ipv6_addaddr1(struct ipv6_addr *ia, const struct timespec *now) " seconds", ifp->name, ia->prefix_pltime, ia->prefix_vltime); - + vltime_was_zero = ia->prefix_vltime == 0; if (if_address6(RTM_NEWADDR, ia) == -1) { logerr(__func__); /* Restore real pltime and vltime */ @@ -778,6 +765,10 @@ ipv6_addaddr1(struct ipv6_addr *ia, const struct timespec *now) } #endif + /* Re-advertise the preferred address to be safe. */ + if (!vltime_was_zero) + ipv6nd_advertise(ia); + return 0; } @@ -909,7 +900,7 @@ ipv6_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr, unsigned int ssize_t ipv6_addaddrs(struct ipv6_addrhead *addrs) { - struct ipv6_addr *ap, *apn, *apf; + struct ipv6_addr *ap, *apn; ssize_t i; struct timespec now; @@ -935,27 +926,6 @@ ipv6_addaddrs(struct ipv6_addrhead *addrs) } else if (!(ap->flags & IPV6_AF_STALE) && !IN6_IS_ADDR_UNSPECIFIED(&ap->addr)) { - apf = ipv6_findaddr(ap->iface->ctx, - &ap->addr, IPV6_AF_ADDED); - if (apf && apf->iface != ap->iface) { - if (apf->iface->metric <= ap->iface->metric) { - loginfox("%s: preferring %s on %s", - ap->iface->name, - ap->saddr, - apf->iface->name); - continue; - } - loginfox("%s: preferring %s on %s", - apf->iface->name, - ap->saddr, - ap->iface->name); - if (if_address6(RTM_DELADDR, apf) == -1 && - errno != EADDRNOTAVAIL && errno != ENXIO) - logerr(__func__); - apf->flags &= - ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED); - } else if (apf) - apf->flags &= ~IPV6_AF_ADDED; if (ap->flags & IPV6_AF_NEW) i++; if (!timespecisset(&now)) @@ -989,6 +959,7 @@ ipv6_freeaddr(struct ipv6_addr *ia) } eloop_q_timeout_delete(ia->iface->ctx->eloop, 0, NULL, ia); + free(ia->na); free(ia); } @@ -1108,6 +1079,9 @@ ipv6_handleifa(struct dhcpcd_ctx *ctx, case RTM_DELADDR: if (ia != NULL) { TAILQ_REMOVE(&state->addrs, ia, next); + /* Advertise the address if it exists on + * another interface. */ + ipv6nd_advertise(ia); /* We'll free it at the end of the function. */ } break; diff --git a/src/ipv6.h b/src/ipv6.h index 73f6538b..b6ed17b7 100644 --- a/src/ipv6.h +++ b/src/ipv6.h @@ -44,7 +44,8 @@ # endif #endif -#define ALLROUTERS "ff02::2" +#define ALLNODES "ff02::1" +#define ALLROUTERS "ff02::2" #define EUI64_GBIT 0x01 #define EUI64_UBIT 0x02 @@ -169,6 +170,10 @@ struct ipv6_addr { void (*dadcallback)(void *); int dadcounter; + struct nd_neighbor_advert *na; + size_t na_len; + int na_count; + #ifdef ALIAS_ADDR char alias[IF_NAMESIZE]; #endif diff --git a/src/ipv6nd.c b/src/ipv6nd.c index 8b446e9c..d6dc749d 100644 --- a/src/ipv6nd.c +++ b/src/ipv6nd.c @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -331,6 +332,135 @@ ipv6nd_sendrsprobe(void *arg) } } +static void +ipv6nd_sendadvertisement(void *arg) +{ + struct ipv6_addr *ia = arg; + struct interface *ifp = ia->iface; + struct dhcpcd_ctx *ctx = ifp->ctx; + struct sockaddr_in6 dst; + struct cmsghdr *cm; + struct in6_pktinfo pi; + const struct rs_state *state = RS_CSTATE(ifp); + + if (state == NULL || ifp->carrier == LINK_DOWN) + goto freeit; + + memset(&dst, 0, sizeof(dst)); + dst.sin6_family = AF_INET6; +#ifdef SIN6_LEN + dst.sin6_len = sizeof(dst); +#endif + dst.sin6_scope_id = ifp->index; + if (inet_pton(AF_INET6, ALLNODES, &dst.sin6_addr) != 1) { + logerr(__func__); + return; + } + + ctx->sndhdr.msg_name = (void *)&dst; + ctx->sndhdr.msg_iov[0].iov_base = ia->na; + ctx->sndhdr.msg_iov[0].iov_len = ia->na_len; + + /* Set the outbound interface. */ + cm = CMSG_FIRSTHDR(&ctx->sndhdr); + assert(cm != NULL); + 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 = ifp->index; + memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); + + logdebugx("%s: sending NA for %s", ifp->name, ia->saddr); + if (sendmsg(ctx->nd_fd, &ctx->sndhdr, 0) == -1) + logerr(__func__); + + if (++ia->na_count < MAX_NEIGHBOR_ADVERTISEMENT) { + eloop_timeout_add_sec(ctx->eloop, + state->retrans / 1000, ipv6nd_sendadvertisement, ia); + return; + } + +freeit: + free(ia->na); + ia->na = NULL; + ia->na_count = 0; +} + +void +ipv6nd_advertise(struct ipv6_addr *ia) +{ + struct dhcpcd_ctx *ctx; + struct interface *ifp; + struct ipv6_state *state; + struct ipv6_addr *iap, *iaf; + struct nd_neighbor_advert *na; + + if (IN6_IS_ADDR_MULTICAST(&ia->addr)) + return; + + ctx = ia->iface->ctx; + if_sortinterfaces(ctx); + /* Find the most preferred address to advertise. */ + iaf = NULL; + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + state = IPV6_STATE(ifp); + if (state == NULL || ifp->carrier == LINK_DOWN) + continue; + + TAILQ_FOREACH(iap, &state->addrs, next) { + if (!IN6_ARE_ADDR_EQUAL(&iap->addr, &ia->addr)) + continue; + + /* Cancel any current advertisement. */ + eloop_timeout_delete(ctx->eloop, + ipv6nd_sendadvertisement, iap); + + /* Don't advertise what we can't use. */ + if (iap->prefix_vltime == 0 || + iap->addr_flags & IN6_IFF_NOTUSEABLE) + continue; + + if (iaf == NULL) + iaf = iap; + } + } + if (iaf == NULL) + return; + + /* Make the packet. */ + ifp = iaf->iface; + iaf->na_len = sizeof(*na); + if (ifp->hwlen != 0) + iaf->na_len += (size_t)ROUNDUP8(ifp->hwlen + 2); + na = calloc(1, iaf->na_len); + if (na == NULL) { + logerr(__func__); + return; + } + + na->nd_na_type = ND_NEIGHBOR_ADVERT; + na->nd_na_flags_reserved = ND_NA_FLAG_OVERRIDE; + if (ip6_forwarding(ifp->name) == 1) + na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; + na->nd_na_target = ia->addr; + + if (ifp->hwlen != 0) { + struct nd_opt_hdr *opt; + + opt = (struct nd_opt_hdr *)(na + 1); + opt->nd_opt_type = ND_OPT_TARGET_LINKADDR; + opt->nd_opt_len = (uint8_t)((ROUNDUP8(ifp->hwlen + 2)) >> 3); + memcpy(opt + 1, ifp->hwaddr, ifp->hwlen); + } + + iaf->na_count = 0; + free(iaf->na); + iaf->na = na; + eloop_timeout_delete(ctx->eloop, ipv6nd_sendadvertisement, iaf); + ipv6nd_sendadvertisement(iaf); +} + void ipv6nd_expire(struct interface *ifp, uint32_t seconds) { @@ -718,6 +848,7 @@ try_script: return; } } + ipv6nd_advertise(ia); } } @@ -869,8 +1000,11 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx, struct interface *ifp, if (rap->reachable > MAX_REACHABLE_TIME) rap->reachable = 0; } - if (nd_ra->nd_ra_retransmit) - rap->retrans = ntohl(nd_ra->nd_ra_retransmit); + if (nd_ra->nd_ra_retransmit) { + struct rs_state *state = RS_STATE(ifp); + + state->retrans = rap->retrans = ntohl(nd_ra->nd_ra_retransmit); + } if (rap->lifetime) rap->expired = 0; rap->hasdns = 0; @@ -1628,6 +1762,7 @@ ipv6nd_startrs1(void *arg) return; } + state->retrans = RETRANS_TIMER; state->rsprobes = 0; ipv6nd_sendrsprobe(ifp); } diff --git a/src/ipv6nd.h b/src/ipv6nd.h index e6a11d68..449a4dc9 100644 --- a/src/ipv6nd.h +++ b/src/ipv6nd.h @@ -58,9 +58,11 @@ struct rs_state { struct nd_router_solicit *rs; size_t rslen; int rsprobes; + uint32_t retrans; }; #define RS_STATE(a) ((struct rs_state *)(ifp)->if_data[IF_DATA_IPV6ND]) +#define RS_CSTATE(a) ((const struct rs_state *)(ifp)->if_data[IF_DATA_IPV6ND]) #define RS_STATE_RUNNING(a) (ipv6nd_hasra((a)) && ipv6nd_dadcompleted((a))) #ifndef MAX_RTR_SOLICITATION_DELAY @@ -68,6 +70,7 @@ struct rs_state { #define MAX_UNICAST_SOLICIT 3 /* 3 transmissions */ #define RTR_SOLICITATION_INTERVAL 4 /* seconds */ #define MAX_RTR_SOLICITATIONS 3 /* times */ +#define MAX_NEIGHBOR_ADVERTISEMENT 3 /* 3 transmissions */ #endif /* On carrier up, expire known routers after RTR_CARRIER_EXPIRE seconds. */ @@ -99,6 +102,7 @@ int ipv6nd_hasra(const struct interface *); int ipv6nd_hasradhcp(const struct interface *); void ipv6nd_handleifa(int, struct ipv6_addr *, pid_t); int ipv6nd_dadcompleted(const struct interface *); +void ipv6nd_advertise(struct ipv6_addr *); void ipv6nd_expire(struct interface *, uint32_t); void ipv6nd_drop(struct interface *); void ipv6nd_neighbour(struct dhcpcd_ctx *, struct in6_addr *, int);