tech-net archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

fix for agr(4) and gre(4) (kern/39940)



Here is a fix for kern/39940.  Please review.  It postpones adding
the IPv6 loopback and link-local addresses to a new interface to
a workqueue(9), so that the kernel will not potentially call the
interface's if_ioctl routine from inside its if_ioctl routine, which
leads to deadlocks and LOCKDEBUG errors on interfaces such as agr(4)
and gre(4) that do their own locking.  I've tested the patch on gre(4),
where it prevents the deadlock.

Dave

-- 
David Young             OJC Technologies
dyoung%ojctech.com@localhost      Urbana, IL * (217) 278-3933
Index: in6.c
===================================================================
RCS file: /cvsroot/src/sys/netinet6/in6.c,v
retrieving revision 1.151
diff -p -u -u -p -r1.151 in6.c
--- in6.c       12 May 2009 23:01:26 -0000      1.151
+++ in6.c       4 Aug 2009 22:02:13 -0000
@@ -76,6 +76,7 @@ __KERNEL_RCSID(0, "$NetBSD: in6.c,v 1.15
 #include <sys/socketvar.h>
 #include <sys/sockio.h>
 #include <sys/systm.h>
+#include <sys/once.h>
 #include <sys/proc.h>
 #include <sys/time.h>
 #include <sys/kernel.h>
@@ -2230,8 +2231,11 @@ in6_if2idlen(struct ifnet *ifp)
 void *
 in6_domifattach(struct ifnet *ifp)
 {
+       static ONCE_DECL(ifwqest);
        struct in6_ifextra *ext;
 
+       RUN_ONCE(&ifwqest, in6_ifaddrs_wq_establish);
+
        ext = malloc(sizeof(*ext), M_IFADDR, M_WAITOK|M_ZERO);
 
        ext->in6_ifstat = malloc(sizeof(struct in6_ifstat),
Index: in6_ifattach.c
===================================================================
RCS file: /cvsroot/src/sys/netinet6/in6_ifattach.c,v
retrieving revision 1.82
diff -p -u -u -p -r1.82 in6_ifattach.c
--- in6_ifattach.c      30 Jul 2009 17:28:36 -0000      1.82
+++ in6_ifattach.c      4 Aug 2009 22:02:13 -0000
@@ -35,6 +35,7 @@ __KERNEL_RCSID(0, "$NetBSD: in6_ifattach
 
 #include <sys/param.h>
 #include <sys/systm.h>
+#include <sys/kmem.h>
 #include <sys/malloc.h>
 #include <sys/socket.h>
 #include <sys/sockio.h>
@@ -42,6 +43,7 @@ __KERNEL_RCSID(0, "$NetBSD: in6_ifattach
 #include <sys/syslog.h>
 #include <sys/md5.h>
 #include <sys/socketvar.h>
+#include <sys/workqueue.h>
 
 #include <net/if.h>
 #include <net/if_dl.h>
@@ -60,12 +62,27 @@ __KERNEL_RCSID(0, "$NetBSD: in6_ifattach
 
 #include <net/net_osdep.h>
 
+struct in6_ifaddr_work {
+       struct work     iw_work;
+       struct ifindexgen {
+               int     ig_idx;
+               int     ig_gen;
+       }               iw_idxgen,
+                       iw_alt_idxgen;
+       bool            iw_alt_present;
+};
+
 unsigned long in6_maxmtu = 0;
 
 int ip6_auto_linklocal = 1;    /* enable by default */
 
 callout_t in6_tmpaddrtimer_ch;
 
+static struct workqueue *in6_ifaddrs_wq = NULL;
+
+static void in6_ifaddrs_schedule(struct ifnet *, struct ifnet *);
+static void in6_ifaddrs_init(struct ifnet *, struct ifnet *);
+static void in6_ifaddrs_worker(struct work *, void *);
 
 #if 0
 static int get_hostid_ifid(struct ifnet *, struct in6_addr *);
@@ -751,8 +768,6 @@ in6_nigroup(struct ifnet *ifp, const cha
 void
 in6_ifattach(struct ifnet *ifp, struct ifnet *altifp)
 {
-       struct in6_ifaddr *ia;
-       struct in6_addr in6;
 
        /* some of the interfaces are inherently not IPv6 capable */
        switch (ifp->if_type) {
@@ -811,6 +826,15 @@ in6_ifattach(struct ifnet *ifp, struct i
                return;
        }
 
+       in6_ifaddrs_schedule(ifp, altifp);
+}
+
+void
+in6_ifaddrs_init(struct ifnet *ifp, struct ifnet *altifp)
+{
+       struct in6_addr in6;
+       struct in6_ifaddr *ia;
+
        /*
         * assign loopback address for loopback interface.
         * XXX multiple loopback interface case.
@@ -835,6 +859,80 @@ in6_ifattach(struct ifnet *ifp, struct i
        }
 }
 
+static struct ifnet *
+getifp(struct ifindexgen *ig)
+{
+       int ifindex = ig->ig_idx;
+       uint64_t ifindex_gen = ig->ig_gen;
+       struct ifnet *ifp;
+
+       if (ifindex2ifnet == NULL)
+               printf("%s: no ifindices in use\n", __func__);
+       else if (ifindex >= if_indexlim) {
+               printf("%s: ifindex %d >= limit %d\n", __func__, ifindex,
+                   if_indexlim);
+       } else if ((ifp = ifindex2ifnet[ifindex]) == NULL)
+               printf("%s: ifindex %d not in use\n", __func__, ifindex);
+       else if (ifp->if_index_gen != ifindex_gen)
+               printf("%s: ifindex %d recycled\n", __func__, ifindex);
+       else
+               return ifp;
+       return NULL;
+}
+
+static void
+in6_ifaddrs_worker(struct work *wk, void *arg)
+{
+       struct in6_ifaddr_work *iw = (struct in6_ifaddr_work *)wk;
+       struct ifnet *altifp = NULL, *ifp;
+
+       if ((ifp = getifp(&iw->iw_idxgen)) != NULL &&
+           (!iw->iw_alt_present ||
+            (altifp = getifp(&iw->iw_alt_idxgen)) != NULL))
+               in6_ifaddrs_init(ifp, altifp);
+
+       kmem_free(iw, sizeof(*iw));
+}
+
+int
+in6_ifaddrs_wq_establish(void)
+{
+       int rc;
+
+       rc = workqueue_create(&in6_ifaddrs_wq, "in6ifaddr", in6_ifaddrs_worker,
+           NULL, PRI_KERNEL, IPL_NET, 0);
+
+       if (rc != 0) {
+               printf("%s: could not create inet6 ifaddrs workqueue.\n",
+                   __func__);
+       }
+       return rc;
+}
+
+void
+in6_ifaddrs_schedule(struct ifnet *ifp, struct ifnet *altifp)
+{
+       struct in6_ifaddr_work *iw;
+       struct ifindexgen *ig, *alt_ig;
+
+       iw = kmem_zalloc(sizeof(*iw), KM_SLEEP);
+
+       ig = &iw->iw_idxgen;
+       alt_ig = &iw->iw_alt_idxgen;
+
+       KASSERT(ifp->if_index < if_indexlim);
+       ig->ig_idx = ifp->if_index;
+       ig->ig_gen = ifp->if_index_gen;
+       if (altifp != NULL) {
+               KASSERT(altifp->if_index < if_indexlim);
+               alt_ig->ig_idx = altifp->if_index;
+               alt_ig->ig_gen = altifp->if_index_gen;
+               iw->iw_alt_present = true;
+       }
+
+       workqueue_enqueue(in6_ifaddrs_wq, &iw->iw_work, NULL);
+}
+
 /*
  * NOTE: in6_ifdetach() does not support loopback if at this moment.
  * We don't need this function in bsdi, because interfaces are never removed
Index: in6_ifattach.h
===================================================================
RCS file: /cvsroot/src/sys/netinet6/in6_ifattach.h,v
retrieving revision 1.11
diff -p -u -u -p -r1.11 in6_ifattach.h
--- in6_ifattach.h      1 Nov 2007 20:33:57 -0000       1.11
+++ in6_ifattach.h      4 Aug 2009 22:02:13 -0000
@@ -40,6 +40,7 @@ int in6_get_tmpifid(struct ifnet *, u_in
 void in6_tmpaddrtimer(void *);
 int in6_get_hw_ifid(struct ifnet *, struct in6_addr *);
 int in6_nigroup(struct ifnet *, const char *, int, struct sockaddr_in6 *);
+int in6_ifaddrs_wq_establish(void);
 #endif /* _KERNEL */
 
 #endif /* !_NETINET6_IN6_IFATTACH_H_ */


Home | Main Index | Thread Index | Old Index