Module Name:    src
Committed By:   dyoung
Date:           Thu Aug 13 00:34:05 UTC 2009

Modified Files:
        src/sys/netinet6: in6.c in6_ifattach.c in6_ifattach.h

Log Message:
Postpone to a workqueue adding link-local and loopback IPv6 addresses
to an interface.  This keeps the kernel from entering ifp->if_ioctl
recursively, which can deadlock if if_ioctl takes locks.  This will
fix deadlocks & LOCKDEBUG errors in agr(4) (kern/39940) and in
gre(4).


To generate a diff of this commit:
cvs rdiff -u -r1.151 -r1.152 src/sys/netinet6/in6.c
cvs rdiff -u -r1.82 -r1.83 src/sys/netinet6/in6_ifattach.c
cvs rdiff -u -r1.11 -r1.12 src/sys/netinet6/in6_ifattach.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/netinet6/in6.c
diff -u src/sys/netinet6/in6.c:1.151 src/sys/netinet6/in6.c:1.152
--- src/sys/netinet6/in6.c:1.151	Tue May 12 23:01:26 2009
+++ src/sys/netinet6/in6.c	Thu Aug 13 00:34:04 2009
@@ -1,4 +1,4 @@
-/*	$NetBSD: in6.c,v 1.151 2009/05/12 23:01:26 elad Exp $	*/
+/*	$NetBSD: in6.c,v 1.152 2009/08/13 00:34:04 dyoung Exp $	*/
 /*	$KAME: in6.c,v 1.198 2001/07/18 09:12:38 itojun Exp $	*/
 
 /*
@@ -62,7 +62,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: in6.c,v 1.151 2009/05/12 23:01:26 elad Exp $");
+__KERNEL_RCSID(0, "$NetBSD: in6.c,v 1.152 2009/08/13 00:34:04 dyoung Exp $");
 
 #include "opt_inet.h"
 #include "opt_pfil_hooks.h"
@@ -76,6 +76,7 @@
 #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 @@
 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: src/sys/netinet6/in6_ifattach.c
diff -u src/sys/netinet6/in6_ifattach.c:1.82 src/sys/netinet6/in6_ifattach.c:1.83
--- src/sys/netinet6/in6_ifattach.c:1.82	Thu Jul 30 17:28:36 2009
+++ src/sys/netinet6/in6_ifattach.c	Thu Aug 13 00:34:04 2009
@@ -1,4 +1,4 @@
-/*	$NetBSD: in6_ifattach.c,v 1.82 2009/07/30 17:28:36 dyoung Exp $	*/
+/*	$NetBSD: in6_ifattach.c,v 1.83 2009/08/13 00:34:04 dyoung Exp $	*/
 /*	$KAME: in6_ifattach.c,v 1.124 2001/07/18 08:32:51 jinmei Exp $	*/
 
 /*
@@ -31,10 +31,11 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: in6_ifattach.c,v 1.82 2009/07/30 17:28:36 dyoung Exp $");
+__KERNEL_RCSID(0, "$NetBSD: in6_ifattach.c,v 1.83 2009/08/13 00:34:04 dyoung Exp $");
 
 #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 @@
 #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,41 @@
 
 #include <net/net_osdep.h>
 
+/* Record of an interface to add a link-local and possibly a loopback
+ * IPv6 address to, processed on a workqueue(9) by in6_ifaddrs_worker.
+ */
+struct in6_ifaddr_work {
+	struct work	iw_work;
+	struct ifindexgen {
+		int	ig_idx;		/* Interface index */
+		int	ig_gen;		/* Interface index generation */
+	}		iw_idxgen,	/* Identify of the interface
+					 * to receive a link-local and
+					 * possibly a loopback address.
+					 */
+			iw_alt_idxgen;	/* Optional identity of a second
+					 * interface. If iw_alt_present
+					 * is true, this field
+					 * identifies a second interface
+					 * whose EUI64 we use to derive
+					 * the link-local address for
+					 * the interface indicated by
+					 * iw_idxgen.
+					 */
+	bool		iw_alt_present;	/* iff true, iw_alt_idxgen is valid. */ 
+};
+
 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 +782,6 @@
 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 +840,26 @@
 		return;
 	}
 
+	/* Assign addresses to ifp in another thread in order to
+	 * avoid re-entering ifp->if_ioctl().
+	 */
+	in6_ifaddrs_schedule(ifp, altifp);
+}
+
+/* in6_ifaddrs_init
+ *
+ * Add a link-local address to ifp, and if ifp is a loopback address,
+ * add a loopback address to it, too.
+ *
+ * If altifp is not NULL, derive the link-local address of ifp from the
+ * EUI64 of 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 +884,95 @@
 	}
 }
 
+/* getifp: helper for in6_ifaddrs_worker().
+ *
+ * Lookup an ifnet by ifindexgen, a pair consisting of an interface
+ * index (if_index) and an interface-index generation number.
+ */
+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;
+}
+
+/* in6_ifaddrs_schedule
+ *
+ * Schedule ifp for a kernel thread to add addresses.
+ *
+ * The kernel thread will assign a link-local address to ifp, and if ifp
+ * is a loopback address, add a loopback address to it, too.
+ *
+ * If altifp is not NULL, derive the link-local address of ifp from the
+ * EUI64 of altifp.
+ */
+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: src/sys/netinet6/in6_ifattach.h
diff -u src/sys/netinet6/in6_ifattach.h:1.11 src/sys/netinet6/in6_ifattach.h:1.12
--- src/sys/netinet6/in6_ifattach.h:1.11	Thu Nov  1 20:33:57 2007
+++ src/sys/netinet6/in6_ifattach.h	Thu Aug 13 00:34:05 2009
@@ -1,4 +1,4 @@
-/*	$NetBSD: in6_ifattach.h,v 1.11 2007/11/01 20:33:57 dyoung Exp $	*/
+/*	$NetBSD: in6_ifattach.h,v 1.12 2009/08/13 00:34:05 dyoung Exp $	*/
 /*	$KAME: in6_ifattach.h,v 1.8 2000/04/12 03:51:30 itojun Exp $	*/
 
 /*
@@ -40,6 +40,7 @@
 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_ */

Reply via email to