When a vlan interface is torn down in vlan_unconfig(), it removes references
to any multicast addresses from the parent device.  If any of those attempts
fail, then vlan_unconfig() fails and doesn't tear down the device.  However,
when a "normal" ifnet is detached, it destroys all its multicast addresses in
if_detach() before it invokes the ifnet_departure eventhandler.  This means
that when the vlan eventhandler tries to call vlan_unconfig(), it will fail
if multicast has ever been used on the vlan interface as the attempts to
release a reference on the multicast address on the parent interface will
fail with ENOENT.  However, the code does not expect vlan_unconfig() to ever
fail, and in fact it will loop forever here:

restart:
        for (i = 0; i < (1 << ifp->if_vlantrunk->hwidth); i++)
                if ((ifv = LIST_FIRST(&ifp->if_vlantrunk->hash[i]))) {
                        vlan_unconfig_locked(ifv->ifv_ifp);
                        if (ifp->if_vlantrunk)
                                goto restart;   /* trunk->hwidth can change */
                        else
                                break;
                }

due to the 'goto restart'.

The fix I came up with was to make vlan_unconfig() simply ignore errors from
removing a multicast reference from the parent device and always succeed.  I
think this is probably more robust anyway.  None of the callers of
vlan_unconfig() ever check the return value to handle failure anyway.  You
can trigger this hang by kldunload'ing a network driver where at least one
instance has a sub-interface with a multicast address registered.  Thoughts?

Index: if_vlan.c
===================================================================
--- if_vlan.c   (revision 207329)
+++ if_vlan.c   (working copy)
@@ -187,8 +187,8 @@
     int (*func)(struct ifnet *, int));
 static int vlan_setflags(struct ifnet *ifp, int status);
 static int vlan_setmulti(struct ifnet *ifp);
-static int vlan_unconfig(struct ifnet *ifp);
-static int vlan_unconfig_locked(struct ifnet *ifp);
+static void vlan_unconfig(struct ifnet *ifp);
+static void vlan_unconfig_locked(struct ifnet *ifp);
 static int vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t tag);
 static void vlan_link_state(struct ifnet *ifp);
 static void vlan_capabilities(struct ifvlan *ifv);
@@ -1128,25 +1128,22 @@
        return (error);
 }
 
-static int
+static void
 vlan_unconfig(struct ifnet *ifp)
 {
-       int ret;
 
        VLAN_LOCK();
-       ret = vlan_unconfig_locked(ifp);
+       vlan_unconfig_locked(ifp);
        VLAN_UNLOCK();
-       return (ret);
 }
 
-static int
+static void
 vlan_unconfig_locked(struct ifnet *ifp)
 {
        struct ifvlantrunk *trunk;
        struct vlan_mc_entry *mc;
        struct ifvlan *ifv;
        struct ifnet  *parent;
-       int error;
 
        VLAN_LOCK_ASSERT();
 
@@ -1175,9 +1172,15 @@
                while ((mc = SLIST_FIRST(&ifv->vlan_mc_listhead)) != NULL) {
                        bcopy((char *)&mc->mc_addr, LLADDR(&sdl),
                            ETHER_ADDR_LEN);
-                       error = if_delmulti(parent, (struct sockaddr *)&sdl);
-                       if (error)
-                               return (error);
+
+                       /*
+                        * This may fail if the parent interface is
+                        * being detached.  Regardless, we should do a
+                        * best effort to free this interface as much
+                        * as possible as all callers expect vlan
+                        * destruction to succeed.
+                        */
+                       (void)if_delmulti(parent, (struct sockaddr *)&sdl);
                        SLIST_REMOVE_HEAD(&ifv->vlan_mc_listhead, mc_entries);
                        free(mc, M_VLAN);
                }
@@ -1223,8 +1226,6 @@
         */
        if (parent != NULL)
                EVENTHANDLER_INVOKE(vlan_unconfig, parent, ifv->ifv_tag);
-
-       return (0);
 }
 
 /* Handle a reference counted flag that should be set on the parent as well */


-- 
John Baldwin
_______________________________________________
freebsd-net@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/freebsd-net
To unsubscribe, send any mail to "freebsd-net-unsubscr...@freebsd.org"

Reply via email to