diff --git a/src/ipv6.c b/src/ipv6.c index 8877381a..52a3c88a 100644 --- a/src/ipv6.c +++ b/src/ipv6.c @@ -1832,16 +1832,19 @@ ipv6_startstatic(struct interface *ifp) int ipv6_start(struct interface *ifp) { -#ifdef IPV6_POLLADDRFLAG +#if defined(ND6_ADVERTISE) || defined(IPV6_POLLADDRFLAG) struct ipv6_state *state; /* We need to update the address flags. */ if ((state = IPV6_STATE(ifp)) != NULL) { struct ipv6_addr *ia; +#ifdef IPV6_POLLADDRFLAG const char *alias; int flags; +#endif TAILQ_FOREACH(ia, &state->addrs, next) { +#ifdef IPV6_POLLADDRFLAG #ifdef ALIAS_ADDR alias = ia->alias; #else @@ -1850,6 +1853,9 @@ ipv6_start(struct interface *ifp) flags = if_addrflags6(ia->iface, &ia->addr, alias); if (flags != -1) ia->addr_flags = flags; +#endif + /* hwaddr could have changed */ + ia->flags &= ~IPV6_AF_ADVERTISED; } } #endif diff --git a/src/ipv6.h b/src/ipv6.h index a2989858..2a93727a 100644 --- a/src/ipv6.h +++ b/src/ipv6.h @@ -160,7 +160,8 @@ /* * ND6 Advertising is only used for IP address sharing to prefer - * the address on a specific interface. + * the address on a specific interface or when the hardware address + * of the interface changes. * This just fails to work on OpenBSD and causes erroneous duplicate * address messages on BSD's other then DragonFly and NetBSD. */ @@ -227,8 +228,9 @@ struct ipv6_addr { #define IPV6_AF_EXTENDED (1U << 13) #define IPV6_AF_REGEN (1U << 14) #define IPV6_AF_ROUTER (1U << 15) +#define IPV6_AF_ADVERTISED (1U << 16) #ifdef IPV6_MANAGETEMPADDR -#define IPV6_AF_TEMPORARY (1U << 16) +#define IPV6_AF_TEMPORARY (1U << 17) #endif struct ll_callback { diff --git a/src/ipv6nd.c b/src/ipv6nd.c index 71b1230c..49f425b7 100644 --- a/src/ipv6nd.c +++ b/src/ipv6nd.c @@ -514,6 +514,7 @@ ipv6nd_advertise(struct ipv6_addr *ia) struct interface *ifp; struct ipv6_state *state; struct ipv6_addr *iap, *iaf; + bool found_another = false; struct nd_neighbor_advert *na; if (IN6_IS_ADDR_MULTICAST(&ia->addr)) @@ -529,20 +530,20 @@ ipv6nd_advertise(struct ipv6_addr *ia) iaf = NULL; TAILQ_FOREACH(ifp, ctx->ifaces, next) { state = IPV6_STATE(ifp); - if (state == NULL || !if_is_link_up(ifp)) + if (state == NULL) 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); + if (iaf != NULL) + found_another = true; /* Don't advertise what we can't use. */ if (iap->prefix_vltime == 0 || - iap->addr_flags & IN6_IFF_NOTUSEABLE) + iap->addr_flags & IN6_IFF_NOTUSEABLE || + !if_is_link_up(ifp)) continue; if (iaf == NULL || @@ -550,9 +551,32 @@ ipv6nd_advertise(struct ipv6_addr *ia) iaf = iap; } } - if (iaf == NULL) + + /* If we have already advertised the address, return. */ + if (iaf == NULL || iaf->flags & IPV6_AF_ADVERTISED) return; + /* Now cancel any other advertisements for the same address. */ + if (found_another) { + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + state = IPV6_STATE(ifp); + if (state == NULL) + continue; + + TAILQ_FOREACH(iap, &state->addrs, next) { + if (!IN6_ARE_ADDR_EQUAL(&iap->addr, &ia->addr)) + continue; + + iap->flags &= ~IPV6_AF_ADVERTISED; + eloop_timeout_delete(ctx->eloop, + ipv6nd_sendadvertisement, iap); + } + } + } else { + eloop_timeout_delete(ctx->eloop, + ipv6nd_sendadvertisement, iaf); + } + /* Make the packet. */ ifp = iaf->iface; iaf->na_len = sizeof(*na); @@ -588,7 +612,7 @@ ipv6nd_advertise(struct ipv6_addr *ia) iaf->na_count = 0; free(iaf->na); iaf->na = na; - eloop_timeout_delete(ctx->eloop, ipv6nd_sendadvertisement, iaf); + iaf->flags |= IPV6_AF_ADVERTISED; ipv6nd_sendadvertisement(iaf); } #elif !defined(SMALL)