Index: head/sys/net/if.c =================================================================== --- head/sys/net/if.c +++ head/sys/net/if.c @@ -3314,8 +3314,10 @@ * * At this time we only support certain types of interfaces, * and we don't allow the length of the address to change. + * + * Set noinline to be dtrace-friendly */ -int +__noinline int if_setlladdr(struct ifnet *ifp, const u_char *lladdr, int len) { struct sockaddr_dl *sdl; Index: head/sys/net/if_lagg.h =================================================================== --- head/sys/net/if_lagg.h +++ head/sys/net/if_lagg.h @@ -201,11 +201,16 @@ SLIST_ENTRY(lagg_mc) mc_entries; }; +typedef enum { + LAGG_LLQTYPE_PHYS = 0, /* Task related to physical (underlying) port */ + LAGG_LLQTYPE_VIRT, /* Task related to lagg interface itself */ +} lagg_llqtype; + /* List of interfaces to have the MAC address modified */ struct lagg_llq { struct ifnet *llq_ifp; uint8_t llq_lladdr[ETHER_ADDR_LEN]; - uint8_t llq_primary; + lagg_llqtype llq_type; SLIST_ENTRY(lagg_llq) llq_entries; }; @@ -273,6 +278,7 @@ #define LAGG_WUNLOCK(_sc) rm_wunlock(&(_sc)->sc_mtx) #define LAGG_RLOCK_ASSERT(_sc) rm_assert(&(_sc)->sc_mtx, RA_RLOCKED) #define LAGG_WLOCK_ASSERT(_sc) rm_assert(&(_sc)->sc_mtx, RA_WLOCKED) +#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 ); Index: head/sys/net/if_lagg.c =================================================================== --- head/sys/net/if_lagg.c +++ head/sys/net/if_lagg.c @@ -100,7 +100,7 @@ static void lagg_lladdr(struct lagg_softc *, uint8_t *); static void lagg_capabilities(struct lagg_softc *); -static void lagg_port_lladdr(struct lagg_port *, uint8_t *); +static void lagg_port_lladdr(struct lagg_port *, uint8_t *, lagg_llqtype); static void lagg_port_setlladdr(void *, int); static int lagg_port_create(struct lagg_softc *, struct ifnet *); static int lagg_port_destroy(struct lagg_port *, int); @@ -543,6 +543,7 @@ lagg_port_destroy(lp, 1); /* Unhook the aggregation protocol */ lagg_proto_detach(sc); + LAGG_UNLOCK_ASSERT(sc); ifmedia_removeall(&sc->sc_media); ether_ifdetach(ifp); @@ -557,7 +558,12 @@ free(sc, M_DEVBUF); } -static void +/* + * Set link-layer address on the lagg interface itself. + * + * Set noinline to be dtrace-friendly + */ +static __noinline void lagg_lladdr(struct lagg_softc *sc, uint8_t *lladdr) { struct ifnet *ifp = sc->sc_ifp; @@ -577,11 +583,16 @@ bcopy(lladdr, IF_LLADDR(ifp), ETHER_ADDR_LEN); lagg_proto_lladdr(sc); + /* + * Send notification request for lagg interface + * itself. Note that new lladdr is already set. + */ bzero(&lp, sizeof(lp)); lp.lp_ifp = sc->sc_ifp; lp.lp_softc = sc; - lagg_port_lladdr(&lp, lladdr); + /* Do not request lladdr change */ + lagg_port_lladdr(&lp, lladdr, LAGG_LLQTYPE_VIRT); } static void @@ -622,51 +633,58 @@ } } -static void -lagg_port_lladdr(struct lagg_port *lp, uint8_t *lladdr) +/* + * Enqueue interface lladdr notification. + * If request is already queued, it is updated. + * If setting lladdr is also desired, @do_change has to be set to 1. + * + * Set noinline to be dtrace-friendly + */ +static __noinline void +lagg_port_lladdr(struct lagg_port *lp, uint8_t *lladdr, lagg_llqtype llq_type) { struct lagg_softc *sc = lp->lp_softc; struct ifnet *ifp = lp->lp_ifp; struct lagg_llq *llq; - int pending = 0; - int primary; LAGG_WLOCK_ASSERT(sc); - primary = (sc->sc_primary->lp_ifp == ifp) ? 1 : 0; - if (primary == 0 && (lp->lp_detaching || - memcmp(lladdr, IF_LLADDR(ifp), ETHER_ADDR_LEN) == 0)) + /* + * Do not enqueue requests where lladdr is the same for + * "physical" interfaces (e.g. ports in lagg) + */ + if (llq_type == LAGG_LLQTYPE_PHYS && + memcmp(IF_LLADDR(ifp), lladdr, ETHER_ADDR_LEN) == 0) return; /* Check to make sure its not already queued to be changed */ SLIST_FOREACH(llq, &sc->sc_llq_head, llq_entries) { - if (llq->llq_ifp == ifp && llq->llq_primary == primary) { - pending = 1; - break; + if (llq->llq_ifp == ifp) { + /* Update lladdr, it may have changed */ + bcopy(lladdr, llq->llq_lladdr, ETHER_ADDR_LEN); + return; } } - if (!pending) { - llq = malloc(sizeof(struct lagg_llq), M_DEVBUF, M_NOWAIT); - if (llq == NULL) /* XXX what to do */ - return; - } + llq = malloc(sizeof(struct lagg_llq), M_DEVBUF, M_NOWAIT | M_ZERO); + if (llq == NULL) /* XXX what to do */ + return; - /* Update the lladdr even if pending, it may have changed */ llq->llq_ifp = ifp; - llq->llq_primary = primary; + llq->llq_type = llq_type; bcopy(lladdr, llq->llq_lladdr, ETHER_ADDR_LEN); - - if (!pending) - SLIST_INSERT_HEAD(&sc->sc_llq_head, llq, llq_entries); + /* XXX: We should insert to tail */ + SLIST_INSERT_HEAD(&sc->sc_llq_head, llq, llq_entries); taskqueue_enqueue(taskqueue_swi, &sc->sc_lladdr_task); } /* * Set the interface MAC address from a taskqueue to avoid a LOR. + * + * Set noinline to be dtrace-friendly */ -static void +static __noinline void lagg_port_setlladdr(void *arg, int pending) { struct lagg_softc *sc = (struct lagg_softc *)arg; @@ -688,17 +706,20 @@ ifp = llq->llq_ifp; CURVNET_SET(ifp->if_vnet); - if (llq->llq_primary == 0) { - /* - * Set the link layer address on the laggport interface. - * if_setlladdr() triggers gratuitous ARPs for INET. - */ + error = 0; + + /* + * Set the link layer address on the laggport interface. + * Note that if_setlladdr() or iflladdr_event handler + * may result in arp transmission / lltable updates. + */ + if (llq->llq_type == LAGG_LLQTYPE_PHYS) error = if_setlladdr(ifp, llq->llq_lladdr, ETHER_ADDR_LEN); - if (error) - printf("%s: setlladdr failed on %s\n", __func__, - ifp->if_xname); - } else + if (error) + printf("%s: setlladdr failed on %s\n", __func__, + ifp->if_xname); + else EVENTHANDLER_INVOKE(iflladdr_event, ifp); CURVNET_RESTORE(); head = SLIST_NEXT(llq, llq_entries); @@ -730,7 +751,7 @@ } /* XXX Disallow non-ethernet interfaces (this should be any of 802) */ - if (ifp->if_type != IFT_ETHER) + if (ifp->if_type != IFT_ETHER && ifp->if_type != IFT_L2VLAN) return (EPROTONOSUPPORT); /* Allow the first Ethernet member to define the MTU */ @@ -784,10 +805,15 @@ if (SLIST_EMPTY(&sc->sc_ports)) { sc->sc_primary = lp; + /* First port in lagg. Update/notify lagg lladdress */ lagg_lladdr(sc, IF_LLADDR(ifp)); } else { - /* Update link layer address for this port */ - lagg_port_lladdr(lp, IF_LLADDR(sc->sc_ifp)); + + /* + * Update link layer address for this port and + * send notifications to other subsystems. + */ + lagg_port_lladdr(lp, IF_LLADDR(sc->sc_ifp), LAGG_LLQTYPE_PHYS); } /* @@ -873,7 +899,7 @@ if (!lp->lp_detaching) { lagg_ether_cmdmulti(lp, 0); lagg_setflags(lp, 0); - lagg_port_lladdr(lp, lp->lp_lladdr); + lagg_port_lladdr(lp, lp->lp_lladdr, LAGG_LLQTYPE_PHYS); } /* Restore interface */ @@ -905,19 +931,16 @@ } lagg_lladdr(sc, lladdr); + /* Mark lp0 as new primary */ + sc->sc_primary = lp0; + /* - * Update link layer address for each port. No port is - * marked as primary at this moment. + * Enqueue lladdr update/notification for each port + * (new primary needs update as well, to switch from + * old lladdr to its 'real' one). */ SLIST_FOREACH(lp_ptr, &sc->sc_ports, lp_entries) - lagg_port_lladdr(lp_ptr, lladdr); - /* - * Mark lp0 as the new primary. This invokes an - * iflladdr_event. - */ - sc->sc_primary = lp0; - if (lp0 != NULL) - lagg_port_lladdr(lp0, lladdr); + lagg_port_lladdr(lp_ptr, lladdr, LAGG_LLQTYPE_PHYS); } /* Remove any pending lladdr changes from the queue */ @@ -1149,8 +1172,8 @@ lagg_init(void *xsc) { struct lagg_softc *sc = (struct lagg_softc *)xsc; - struct lagg_port *lp; struct ifnet *ifp = sc->sc_ifp; + struct lagg_port *lp; if (ifp->if_drv_flags & IFF_DRV_RUNNING) return; @@ -1158,9 +1181,14 @@ LAGG_WLOCK(sc); ifp->if_drv_flags |= IFF_DRV_RUNNING; - /* Update the port lladdrs */ + + /* + * Update the port lladdrs if needed. + * This might be if_setlladdr() notification + * that lladdr has been changed. + */ SLIST_FOREACH(lp, &sc->sc_ports, lp_entries) - lagg_port_lladdr(lp, IF_LLADDR(ifp)); + lagg_port_lladdr(lp, IF_LLADDR(ifp), LAGG_LLQTYPE_PHYS); lagg_proto_init(sc); @@ -1244,6 +1272,7 @@ LAGG_WLOCK(sc); lagg_proto_detach(sc); + LAGG_UNLOCK_ASSERT(sc); lagg_proto_attach(sc, ra->ra_proto); break; case SIOCGLAGGOPTS: