Index: sys/net/if.c =================================================================== --- sys/net/if.c +++ sys/net/if.c @@ -126,7 +126,7 @@ void (*bridge_linkstate_p)(struct ifnet *ifp); void (*ng_ether_link_state_p)(struct ifnet *ifp, int state); -void (*lagg_linkstate_p)(struct ifnet *ifp, int state); +void (*lagg_linkstate_p)(struct ifnet *ifp); /* These are external hooks for CARP. */ void (*carp_linkstate_p)(struct ifnet *ifp); void (*carp_demote_adj_p)(int, char *); @@ -1980,6 +1980,8 @@ if (ifp->if_carp) (*carp_linkstate_p)(ifp); + if (ifp->if_lagg) + (*lagg_linkstate_p)(ifp); rt_ifmsg(ifp); } @@ -2001,6 +2003,8 @@ pfctlinput(PRC_IFUP, ifa->ifa_addr); if (ifp->if_carp) (*carp_linkstate_p)(ifp); + if (ifp->if_lagg) + (*lagg_linkstate_p)(ifp); rt_ifmsg(ifp); #ifdef INET6 in6_if_up(ifp); @@ -2015,17 +2019,27 @@ int (*vlan_setcookie_p)(struct ifnet *, void *); void *(*vlan_cookie_p)(struct ifnet *); +void +if_link_state_change(struct ifnet *ifp, int link_state) +{ + + return if_link_state_change_cond(ifp, link_state, 0); +} + /* * Handle a change in the interface link state. To avoid LORs * between driver lock and upper layer locks, as well as possible * recursions, we post event to taskqueue, and all job * is done in static do_link_state_change(). + * + * If the current link state matches link_state and force isn't + * specified no action is taken. */ void -if_link_state_change(struct ifnet *ifp, int link_state) +if_link_state_change_cond(struct ifnet *ifp, int link_state, int force) { - /* Return if state hasn't changed. */ - if (ifp->if_link_state == link_state) + + if (ifp->if_link_state == link_state && !force) return; ifp->if_link_state = link_state; @@ -2053,7 +2067,7 @@ if (ifp->if_bridge) (*bridge_linkstate_p)(ifp); if (ifp->if_lagg) - (*lagg_linkstate_p)(ifp, link_state); + (*lagg_linkstate_p)(ifp); if (IS_DEFAULT_VNET(curvnet)) devctl_notify("IFNET", ifp->if_xname, Index: sys/net/if_lagg.h =================================================================== --- sys/net/if_lagg.h +++ sys/net/if_lagg.h @@ -281,7 +281,7 @@ #define LAGG_UNLOCK_ASSERT(_sc) rm_assert(&(_sc)->sc_mtx, RA_UNLOCKED) extern struct mbuf *(*lagg_input_p)(struct ifnet *, struct mbuf *); -extern void (*lagg_linkstate_p)(struct ifnet *, int ); +extern void (*lagg_linkstate_p)(struct ifnet *); int lagg_enqueue(struct ifnet *, struct mbuf *); Index: sys/net/if_lagg.c =================================================================== --- sys/net/if_lagg.c +++ sys/net/if_lagg.c @@ -106,7 +106,7 @@ static int lagg_port_destroy(struct lagg_port *, int); static struct mbuf *lagg_input(struct ifnet *, struct mbuf *); static void lagg_linkstate(struct lagg_softc *); -static void lagg_port_state(struct ifnet *, int); +static void lagg_port_state(struct ifnet *); static int lagg_port_ioctl(struct ifnet *, u_long, caddr_t); static int lagg_port_output(struct ifnet *, struct mbuf *, const struct sockaddr *, struct route *); @@ -1779,7 +1779,12 @@ break; } } - if_link_state_change(sc->sc_ifp, new_link); + + /* + * Force state change to ensure ifnet_link_event is generated allowing + * protocols to notify other nodes of potential address move. + */ + if_link_state_change_cond(sc->sc_ifp, new_link, 1); /* Update if_baudrate to reflect the max possible speed */ switch (sc->sc_proto) { @@ -1802,7 +1807,7 @@ } static void -lagg_port_state(struct ifnet *ifp, int state) +lagg_port_state(struct ifnet *ifp) { struct lagg_port *lp = (struct lagg_port *)ifp->if_lagg; struct lagg_softc *sc = NULL; @@ -1818,7 +1823,7 @@ LAGG_WUNLOCK(sc); } -struct lagg_port * +static struct lagg_port * lagg_link_active(struct lagg_softc *sc, struct lagg_port *lp) { struct lagg_port *lp_next, *rval = NULL; Index: sys/net/if_var.h =================================================================== --- sys/net/if_var.h +++ sys/net/if_var.h @@ -492,6 +492,7 @@ void if_free(struct ifnet *); void if_initname(struct ifnet *, const char *, int); void if_link_state_change(struct ifnet *, int); +void if_link_state_change_cond(struct ifnet *, int, int); int if_printf(struct ifnet *, const char *, ...) __printflike(2, 3); void if_ref(struct ifnet *); void if_rele(struct ifnet *); Index: sys/netinet/if_ether.h =================================================================== --- sys/netinet/if_ether.h +++ sys/netinet/if_ether.h @@ -119,7 +119,8 @@ void arprequest(struct ifnet *, const struct in_addr *, const struct in_addr *, u_char *); void arp_ifinit(struct ifnet *, struct ifaddr *); -void arp_announce_ifaddr(struct ifnet *, struct in_addr addr, u_char *); +void arp_announce(struct ifnet *); +void arp_announce_ifaddr(struct ifnet *, const struct in_addr *addr, u_char *); #endif #endif Index: sys/netinet/if_ether.c =================================================================== --- sys/netinet/if_ether.c +++ sys/netinet/if_ether.c @@ -145,6 +145,7 @@ static void arp_iflladdr(void *arg __unused, struct ifnet *ifp); static eventhandler_tag iflladdr_tag; +static eventhandler_tag ifnet_link_event_tag; static const struct netisr_handler arp_nh = { .nh_name = "arp", @@ -1144,43 +1145,96 @@ if (ntohl(dst_in->sin_addr.s_addr) == INADDR_ANY) return; - arp_announce_ifaddr(ifp, dst_in->sin_addr, IF_LLADDR(ifp)); + arp_announce_ifaddr(ifp, &dst_in->sin_addr, IF_LLADDR(ifp)); arp_add_ifa_lle(ifp, dst); } void -arp_announce_ifaddr(struct ifnet *ifp, struct in_addr addr, u_char *enaddr) +arp_announce_ifaddr(struct ifnet *ifp, const struct in_addr *addr, u_char *enaddr) { - if (ntohl(addr.s_addr) != INADDR_ANY) - arprequest(ifp, &addr, &addr, enaddr); + if (ntohl(addr->s_addr) != INADDR_ANY) + arprequest(ifp, addr, addr, enaddr); } /* - * Sends gratuitous ARPs for each ifaddr to notify other - * nodes about the address change. + * Send gratuitous ARPs for all interfaces addresses to notify other nodes of + * changes. + * + * This is a noop if the interface isn't up or has been flagged for no ARP. */ -static __noinline void -arp_handle_ifllchange(struct ifnet *ifp) +void __noinline +arp_announce(struct ifnet *ifp) { + int i, cnt, entries; + u_char *lladdr; struct ifaddr *ifa; + struct in_addr *addr, *head; + + if (!(ifp->if_flags & IFF_UP) || (ifp->if_flags & IFF_NOARP)) + return; + + entries = 8; + cnt = 0; + head = malloc(sizeof(*addr) * entries, M_TEMP, M_NOWAIT); + if (head == NULL) { + log(LOG_INFO, "arp_announce: malloc %d entries failed\n", + entries); + return; + } + /* Take a copy then process to avoid locking issues. */ + IF_ADDR_RLOCK(ifp); TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { - if (ifa->ifa_addr->sa_family == AF_INET) - arp_ifinit(ifp, ifa); + if (ifa->ifa_addr->sa_family != AF_INET) + continue; + + if (cnt == entries) { + addr = (struct in_addr *)realloc(head, sizeof(*addr) * + (entries + 8), M_TEMP, M_NOWAIT); + if (addr == NULL) { + log(LOG_INFO, "arp_announce: realloc to %d " + "entries failed\n", entries + 8); + /* Process what we have. */ + break; + } + entries += 8; + head = addr; + } + + addr = head + cnt; + bcopy(IFA_IN(ifa), addr, sizeof(*addr)); + cnt++; } + IF_ADDR_RUNLOCK(ifp); + + lladdr = IF_LLADDR(ifp); + for (i = 0; i < cnt; i++) { + arp_announce_ifaddr(ifp, head + i, lladdr); + } + free(head, M_TEMP); +} + +/* + * A handler for interface linkstate change events. + */ +static void +arp_ifnet_link_event(void *arg __unused, struct ifnet *ifp, int linkstate) +{ + + if (linkstate == LINK_STATE_UP) + arp_announce(ifp); } /* - * A handler for interface link layer address change event. + * A handler for interface link layer address change events. */ static __noinline void arp_iflladdr(void *arg __unused, struct ifnet *ifp) { - if ((ifp->if_flags & IFF_UP) != 0) - arp_handle_ifllchange(ifp); + arp_announce(ifp); } static void @@ -1188,8 +1242,12 @@ { netisr_register(&arp_nh); - if (IS_DEFAULT_VNET(curvnet)) + + if (IS_DEFAULT_VNET(curvnet)) { iflladdr_tag = EVENTHANDLER_REGISTER(iflladdr_event, arp_iflladdr, NULL, EVENTHANDLER_PRI_ANY); + ifnet_link_event_tag = EVENTHANDLER_REGISTER(ifnet_link_event, + arp_ifnet_link_event, 0, EVENTHANDLER_PRI_ANY); + } } SYSINIT(arp, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY, arp_init, 0); Index: sys/netinet/in_var.h =================================================================== --- sys/netinet/in_var.h +++ sys/netinet/in_var.h @@ -129,6 +129,9 @@ #define IN_IFADDR_WLOCK_ASSERT() rm_assert(&in_ifaddr_lock, RA_WLOCKED) #define IN_IFADDR_WUNLOCK() rm_wunlock(&in_ifaddr_lock) +#define IFA_IN(ifa) \ + (&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr) + /* * Macro for finding the internet address structure (in_ifaddr) * corresponding to one of our IP addresses (in_addr). Index: sys/netinet/ip_carp.c =================================================================== --- sys/netinet/ip_carp.c +++ sys/netinet/ip_carp.c @@ -1009,13 +1009,12 @@ carp_send_arp(struct carp_softc *sc) { struct ifaddr *ifa; - struct in_addr addr; CARP_FOREACH_IFA(sc, ifa) { if (ifa->ifa_addr->sa_family != AF_INET) continue; - addr = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr; - arp_announce_ifaddr(sc->sc_carpdev, addr, LLADDR(&sc->sc_addr)); + arp_announce_ifaddr(sc->sc_carpdev, IFA_IN(ifa), + LLADDR(&sc->sc_addr)); } } @@ -1037,18 +1036,15 @@ static void carp_send_na(struct carp_softc *sc) { - static struct in6_addr mcast = IN6ADDR_LINKLOCAL_ALLNODES_INIT; struct ifaddr *ifa; - struct in6_addr *in6; CARP_FOREACH_IFA(sc, ifa) { if (ifa->ifa_addr->sa_family != AF_INET6) continue; - in6 = IFA_IN6(ifa); - nd6_na_output(sc->sc_carpdev, &mcast, in6, - ND_NA_FLAG_OVERRIDE, 1, NULL); - DELAY(1000); /* XXX */ + nd6_announce_ifaddr(sc->sc_carpdev, IFA_IN6(ifa), + IFA_ND_NA_FLAG_OVERRIDE(ifa)); + nd6_announce_delay(ifa); } } Index: sys/netinet6/in6.c =================================================================== --- sys/netinet6/in6.c +++ sys/netinet6/in6.c @@ -113,7 +113,7 @@ #define V_icmp6_nodeinfo_oldmcprefix VNET(icmp6_nodeinfo_oldmcprefix) /* - * Definitions of some costant IP6 addresses. + * Definitions of some constant IP6 addresses. */ const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT; Index: sys/netinet6/in6_var.h =================================================================== --- sys/netinet6/in6_var.h +++ sys/netinet6/in6_var.h @@ -399,6 +399,11 @@ #define IA6_SIN6(ia) (&((ia)->ia_addr)) #define IA6_DSTSIN6(ia) (&((ia)->ia_dstaddr)) #define IFA_IN6(x) (&((struct sockaddr_in6 *)((x)->ifa_addr))->sin6_addr) +#define IFA_ND_NA_FLAG_OVERRIDE(x) \ + IFA_IN6_ANYCAST(x) ? 0 : ND_NA_FLAG_OVERRIDE +#define IFA_IN6_ANYCAST(x) \ + (((struct in6_ifaddr *)ifa)->ia6_flags & IN6_IFF_ANYCAST) ? 1 : 0 +#define IN6_MAX_ANYCAST_DELAY_TIME_MS 1000000 #define IFA_DSTIN6(x) (&((struct sockaddr_in6 *)((x)->ifa_dstaddr))->sin6_addr) #define IFPR_IN6(x) (&((struct sockaddr_in6 *)((x)->ifpr_prefix))->sin6_addr) Index: sys/netinet6/nd6.h =================================================================== --- sys/netinet6/nd6.h +++ sys/netinet6/nd6.h @@ -398,6 +398,9 @@ #ifdef VIMAGE void nd6_destroy(void); #endif +void nd6_announce(struct ifnet *); +void nd6_announce_ifaddr(struct ifnet *, const struct in6_addr *, u_long); +int nd6_announce_delay(struct ifaddr *); struct nd_ifinfo *nd6_ifattach(struct ifnet *); void nd6_ifdetach(struct nd_ifinfo *); int nd6_is_addr_neighbor(const struct sockaddr_in6 *, struct ifnet *); Index: sys/netinet6/nd6.c =================================================================== --- sys/netinet6/nd6.c +++ sys/netinet6/nd6.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -112,6 +113,7 @@ #endif static eventhandler_tag lle_event_eh; +static eventhandler_tag ifnet_link_event_eh; /* for debugging? */ #if 0 @@ -196,6 +198,105 @@ type == RTM_ADD ? RTF_UP: 0), 0, RT_DEFAULT_FIB); } +static void +nd6_ifnet_link_event(void *arg __unused, struct ifnet *ifp, int linkstate) +{ + + if (linkstate == LINK_STATE_UP) + nd6_announce(ifp); +} + +/* + * Send unsolicited neighbor advertisements for all interface addresses to + * notify other nodes of changes. + * + * This is a noop if the interface isn't up. + */ +void __noinline +nd6_announce(struct ifnet *ifp) +{ + int i, cnt, entries; + struct ifaddr *ifa; + struct ann { + struct in6_addr addr; + u_long flags; + int delay; + } *ann1, *head; + + if (!(ifp->if_flags & IFF_UP)) + return; + + entries = 8; + cnt = 0; + head = malloc(sizeof(struct ann) * entries, M_TEMP, M_WAITOK); + + /* Take a copy then process to avoid locking issues. */ + IF_ADDR_RLOCK(ifp); + TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + + if (cnt == entries) { + ann1 = (struct ann*)realloc(head, sizeof(struct ann) * + (entries + 8), M_TEMP, M_NOWAIT); + if (ann1 == NULL) { + log(LOG_INFO, "nd6_announce: realloc to %d " + "entries failed\n", entries + 8); + /* Process what we have. */ + break; + } + entries += 8; + head = ann1; + } + + ann1 = head + cnt; + bcopy(IFA_IN6(ifa), &ann1->addr, sizeof(ann1->addr)); + ann1->flags = IFA_ND_NA_FLAG_OVERRIDE(ifa); + ann1->delay = nd6_announce_delay(ifa); + cnt++; + } + IF_ADDR_RUNLOCK(ifp); + + for (i = 0; i < cnt;) { + ann1 = head + i; + nd6_announce_ifaddr(ifp, &ann1->addr, ann1->flags); + i++; + if (i == cnt) + break; + DELAY(ann1->delay); + } + free(head, M_TEMP); +} + +/* + * Return the delay required for announcements of the address as per RFC 4861. + */ +int +nd6_announce_delay(struct ifaddr *ifa) +{ + + if (IFA_IN6_ANYCAST(ifa)) { + /* + * Random value between 0 and MAX_ANYCAST_DELAY_TIME + * as per section 7.2.7. + */ + return (random() % IN6_MAX_ANYCAST_DELAY_TIME_MS); + } + + /* Small delay as per section 7.2.6. */ + return (1000); +} + +/* + * Send neighbor advertisement to notify other nodes of changes. + */ +void +nd6_announce_ifaddr(struct ifnet *ifp, const struct in6_addr *addr, u_long flags) +{ + + nd6_na_output(ifp, &in6addr_linklocal_allnodes, addr, flags, 1, NULL); +} + void nd6_init(void) { @@ -211,9 +312,12 @@ nd6_slowtimo, curvnet); nd6_dad_init(); - if (IS_DEFAULT_VNET(curvnet)) + if (IS_DEFAULT_VNET(curvnet)) { lle_event_eh = EVENTHANDLER_REGISTER(lle_event, nd6_lle_event, NULL, EVENTHANDLER_PRI_ANY); + ifnet_link_event_eh = EVENTHANDLER_REGISTER(ifnet_link_event, + nd6_ifnet_link_event, NULL, EVENTHANDLER_PRI_ANY); + } } #ifdef VIMAGE @@ -223,8 +327,10 @@ callout_drain(&V_nd6_slowtimo_ch); callout_drain(&V_nd6_timer_ch); - if (IS_DEFAULT_VNET(curvnet)) + if (IS_DEFAULT_VNET(curvnet)) { EVENTHANDLER_DEREGISTER(lle_event, lle_event_eh); + EVENTHANDLER_DEREGISTER(ifnet_link_event, ifnet_link_event_eh); + } } #endif