kern/158185: IPv6 does not manage the NIC's route when doing ifconfig down/up

Doug Ambrisko ambrisko at ambrisko.com
Wed Jun 22 23:50:10 UTC 2011


>Number:         158185
>Category:       kern
>Synopsis:       IPv6 does not manage the NIC's route when doing ifconfig down/up
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Wed Jun 22 23:50:09 UTC 2011
>Closed-Date:
>Last-Modified:
>Originator:     Doug Ambrisko
>Release:        FreeBSD 7.2
>Organization:
>Environment:
FreeBSD one.mfg 7.2-RELEASE-p2 FreeBSD 7.2-RELEASE-p2 #1: Fri Jul 22 20:32:52 PDT 2005     fake at fake:/usr/obj/usr/src/sys/FAKE i386
>Description:
Recently, I ran into a problem in which IPv4 works fine but IPv6 doesn't.
I use a trick to "fail-over" NICs via ifconfig down the bad link and then 
ifconfig up the good link.  Both NICs have the same IP.  The 2nd IP is 
assigned when the 1st NIC is down.  This works fine with IPv4 since on down 
it removes the NIC's local route that was set when the IP was assigned ie. 
the /x network.  In the IPv4 stack it calls if_up and if_down to purge the 
route and if_up to recreate the NIC's route.  Only when you delete the
IP does the route go away.

This doesn't happen with IPv6, instead the route is created when
the NIC is first assigned an IP and persists when the NIC is down
and even when another is up.

You can see this with route get or netstat -r pointing to a NIC that
isn't valid.

I have a prototype/first hack to fix it by modeling the IPv4 paradigm and
deal with the routes/multicast groups.  I'd like someone to take a look 
and tell me what I messed up and how it should be done better.

This appears to be a fairly simple bug in FreeBSD that was just never
looked at before since it is a bit of a corner case. 

>How-To-Repeat:
A lot of this problem can be seen tested via
	netstat -r
	ndp -a
	ifmcstat

Assign an IPv6 address via ifconfig, then ifconfig down it and the route
is still there.
>Fix:
Here is the hacky patch I came up with.

diff -upr ../src/sys/net/if.c sys/net/if.c
--- ../src/sys/net/if.c	2009-07-27 12:48:10.000000000 -0700
+++ sys/net/if.c	2011-05-17 12:51:38.000000000 -0700
@@ -1449,6 +1449,9 @@ if_unroute(struct ifnet *ifp, int flag, 
 		carp_carpdev_state(ifp->if_carp);
 #endif
 	rt_ifmsg(ifp);
+#ifdef INET6
+	in6_if_down(ifp);
+#endif
 }
 
 /*
diff -upr ../src/sys/netinet6/in6.c sys/netinet6/in6.c
--- ../src/sys/netinet6/in6.c	2009-07-27 12:48:10.000000000 -0700
+++ sys/netinet6/in6.c	2011-05-17 12:59:27.000000000 -0700
@@ -132,6 +132,9 @@ static void in6_unlink_ifa(struct in6_if
 
 struct in6_multihead in6_multihead;	/* XXX BSS initialization */
 int	(*faithprefix_p)(struct in6_addr *);
+static int in6_join_multicast_groups(struct ifnet *ifp,
+	struct in6_aliasreq *ifra, struct in6_ifaddr *ia, int flags,
+	int hostIsNew);
 
 /*
  * Subroutine for in6_ifaddloop() and in6_ifremloop().
@@ -788,6 +791,7 @@ in6_control(struct socket *so, u_long cm
 	return (0);
 }
 
+
 /*
  * Update parameters of an IPv6 interface address.
  * If necessary, a new entry is created and linked into address chains.
@@ -802,10 +806,6 @@ in6_update_ifa(struct ifnet *ifp, struct
 	struct in6_ifaddr *oia;
 	struct sockaddr_in6 dst6;
 	struct in6_addrlifetime *lt;
-	struct in6_multi_mship *imm;
-	struct in6_multi *in6m_sol;
-	struct rtentry *rt;
-	int delay;
 	char ip6buf[INET6_ADDRSTRLEN];
 
 	/* Validate parameters */
@@ -1049,6 +1049,41 @@ in6_update_ifa(struct ifnet *ifp, struct
 	 * not just go to unlink.
 	 */
 
+	if ((error = in6_join_multicast_groups(ifp, ifra, ia, flags, hostIsNew))) {
+		goto cleanup;
+	}
+
+
+	return (error);
+
+  unlink:
+	/*
+	 * XXX: if a change of an existing address failed, keep the entry
+	 * anyway.
+	 */
+	if (hostIsNew)
+		in6_unlink_ifa(ia, ifp);
+	return (error);
+
+  cleanup:
+	in6_purgeaddr(&ia->ia_ifa);
+	return error;
+}
+
+static int
+in6_join_multicast_groups(struct ifnet *ifp, struct in6_aliasreq *ifra,
+			  struct in6_ifaddr *ia, int flags, int hostIsNew)
+{
+	int error = 0;
+	struct in6_multi_mship *imm;
+	struct rtentry *rt;
+	struct in6_multi *in6m_sol;
+	int delay;
+	char ip6buf[INET6_ADDRSTRLEN];
+
+/* Up the interface */
+ifp->if_flags |= IFF_UP;
+
 	/* Join necessary multicast groups */
 	in6m_sol = NULL;
 	if ((ifp->if_flags & IFF_MULTICAST) != 0) {
@@ -1263,7 +1295,7 @@ in6_update_ifa(struct ifnet *ifp, struct
 			    ip6_sprintf(ip6buf, &mltaddr.sin6_addr),
 			    if_name(ifp), error));
 			goto cleanup;
-		}
+	}
 		LIST_INSERT_HEAD(&ia->ia6_memberships, imm, i6mm_chain);
 #undef	MLTMASK_LEN
 	}
@@ -1306,20 +1338,8 @@ in6_update_ifa(struct ifnet *ifp, struct
 		nd6_dad_start((struct ifaddr *)ia, delay);
 	}
 
+ cleanup:
 	return (error);
-
-  unlink:
-	/*
-	 * XXX: if a change of an existing address failed, keep the entry
-	 * anyway.
-	 */
-	if (hostIsNew)
-		in6_unlink_ifa(ia, ifp);
-	return (error);
-
-  cleanup:
-	in6_purgeaddr(&ia->ia_ifa);
-	return error;
 }
 
 void
@@ -1727,7 +1747,7 @@ in6_ifinit(struct ifnet *ifp, struct in6
 
 	/* we could do in(6)_socktrim here, but just omit it at this moment. */
 
-	if (newhost && nd6_need_cache(ifp) != 0) {
+	if (newhost) {
 		/*
 		 * set the rtrequest function to create llinfo.  It also
 		 * adjust outgoing interface of the route for the local
@@ -2120,6 +2140,93 @@ in6_ifawithifp(struct ifnet *ifp, struct
 	return NULL;
 }
 
+extern struct inpcbinfo udbinfo;
+extern struct inpcbinfo ripcbinfo;
+/*static*/ void in6_purgemaddrs(struct ifnet *);
+
+void
+in6_if_down(struct ifnet *ifp)
+{
+	struct in6_ifaddr *ia /*, *oia*/;
+	struct ifaddr *ifa, *next;
+	struct rtentry *rt;
+	short rtflags;
+	struct sockaddr_in6 sin6;
+	struct in6_multi_mship *imm;
+
+	/* remove neighbor management table */
+	nd6_purge(ifp);
+
+	/* undo everything done by in6_ifattach(), just in case */
+	for (ifa = ifp->if_addrlist.tqh_first; ifa; ifa = next) {
+		next = ifa->ifa_list.tqe_next;
+
+		if (ifa->ifa_addr->sa_family != AF_INET6
+		 /* DJA || !IN6_IS_ADDR_LINKLOCAL(&satosin6(&ifa->ifa_addr)->sin6_addr) */) {
+			continue;
+		}
+
+		ia = (struct in6_ifaddr *)ifa;
+
+		/*
+		 * leave from multicast groups we have joined for the interface
+		 */
+		while ((imm = ia->ia6_memberships.lh_first) != NULL) {
+			LIST_REMOVE(imm, i6mm_chain);
+			in6_leavegroup(imm);
+		}
+
+#define rtinitflags(x) \
+        ((((x)->if_flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) != 0) \
+            ? RTF_HOST : RTF_UP)
+		/* remove from the routing table */
+		if ((ia->ia_flags & IFA_ROUTE) &&
+		    (rt = rtalloc1((struct sockaddr *)&ia->ia_addr, 0, 0UL))) {
+			rtflags = rt->rt_flags;
+			rtfree(rt);
+			if(ifa->ifa_addr->sa_family == AF_INET6)
+			rtinit(ifa, (int)RTM_DELETE,
+			    rtinitflags(ifp));
+		}
+
+	}
+
+	in6_pcbpurgeif0(&udbinfo, ifp);
+	in6_pcbpurgeif0(&ripcbinfo, ifp);
+	/* leave from all multicast groups joined */
+	in6_purgemaddrs(ifp);
+
+	/*
+	 * remove neighbor management table.  we call it twice just to make
+	 * sure we nuke everything.  maybe we need just one call.
+	 * XXX: since the first call did not release addresses, some prefixes
+	 * might remain.  We should call nd6_purge() again to release the
+	 * prefixes after removing all addresses above.
+	 * (Or can we just delay calling nd6_purge until at this point?)
+	 */
+	nd6_purge(ifp);
+
+	/* remove route to link-local allnodes multicast (ff02::1) */
+	bzero(&sin6, sizeof(sin6));
+	sin6.sin6_len = sizeof(struct sockaddr_in6);
+	sin6.sin6_family = AF_INET6;
+	sin6.sin6_addr = in6addr_linklocal_allnodes;
+	if (in6_setscope(&sin6.sin6_addr, ifp, NULL))
+		/* XXX: should not fail */
+		return;
+	/* XXX grab lock first to avoid LOR */
+	if (rt_tables[0][AF_INET6] != NULL) {
+		RADIX_NODE_HEAD_LOCK(rt_tables[0][AF_INET6]);
+		rt = rtalloc1((struct sockaddr *)&sin6, 0, RTF_RNH_LOCKED);
+		if (rt) {
+			if (rt->rt_ifp == ifp)
+				rtexpunge(rt);
+			RTFREE_LOCKED(rt);
+		}
+		RADIX_NODE_HEAD_UNLOCK(rt_tables[0][AF_INET6]);
+	}
+}
+
 /*
  * perform DAD when interface becomes IFF_UP.
  */
@@ -2128,20 +2235,92 @@ in6_if_up(struct ifnet *ifp)
 {
 	struct ifaddr *ifa;
 	struct in6_ifaddr *ia;
+	struct in6_aliasreq ifra_store, *ifra;
 
+	/* DJA */
 	TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) {
 		if (ifa->ifa_addr->sa_family != AF_INET6)
 			continue;
 		ia = (struct in6_ifaddr *)ifa;
-		if (ia->ia6_flags & IN6_IFF_TENTATIVE) {
+
+
+		in6_ifinit(ifp, ia, &ia->ia_addr, 1);
+
+		ifra = &ifra_store;
+		bzero(&ifra_store, sizeof(ifra_store));
+		bcopy(if_name(ifp), ifra_store.ifra_name,
+		      sizeof(ifra_store.ifra_name));
+		bcopy(&ia->ia_addr, &ifra_store.ifra_addr,
+		      sizeof(ifra_store.ifra_addr));
+		bcopy(&ia->ia_dstaddr, &ifra_store.ifra_dstaddr,
+		      sizeof(ifra_store.ifra_dstaddr));
+		bcopy(&ia->ia_prefixmask, &ifra_store.ifra_prefixmask,
+		      sizeof(ifra_store.ifra_prefixmask));
+		ifra_store.ifra_flags = ia->ia6_flags;
+		bcopy(&ia->ia6_lifetime, &ifra_store.ifra_lifetime,
+		      sizeof(ifra->ifra_lifetime));
+
+		/* Join necessary multicast groups */
+		if (in6_join_multicast_groups(ifp, ifra, ia, 0, 0)) {
+			in6_purgeaddr(&ia->ia_ifa);
+			continue;
+		}
+
+		int i, error = 0;
+		struct nd_prefixctl pr0;
+		struct nd_prefix *pr;
+		/*
+		 * then, make the prefix on-link on the interface.
+		 * XXX: we'd rather create the prefix before the address, but
+		 * we need at least one address to install the corresponding
+		 * interface route, so we configure the address first.
+		 */
+
+		/*
+		 * convert mask to prefix length (prefixmask has already
+		 * been validated in in6_update_ifa().
+		 */
+		bzero(&pr0, sizeof(pr0));
+		pr0.ndpr_ifp = ifp;
+		pr0.ndpr_plen = in6_mask2len(&ifra->ifra_prefixmask.sin6_addr,
+		    NULL);
+
+		if (pr0.ndpr_plen == 128) {
+			continue;	/* we don't need to install a host route. */
+			}
+		pr0.ndpr_prefix = ifra->ifra_addr;
+		/* apply the mask for safety. */
+		for (i = 0; i < 4; i++) {
+			pr0.ndpr_prefix.sin6_addr.s6_addr32[i] &=
+				ifra->ifra_prefixmask.sin6_addr.s6_addr32[i];
+		}
+
+		/*
+		 * XXX: since we don't have an API to set prefix (not address)
+		 * lifetimes, we just use the same lifetimes as addresses.
+		 * The (temporarily) installed lifetimes can be overridden by
+		 * later advertised RAs (when accept_rtadv is non 0), which is
+		 * an intended behavior.
+		 */
+		pr0.ndpr_raf_onlink = 1; /* should be configurable? */
+		pr0.ndpr_raf_auto =
+		    ((ifra->ifra_flags & IN6_IFF_AUTOCONF) != 0);
+		pr0.ndpr_vltime = ifra->ifra_lifetime.ia6t_vltime;
+		pr0.ndpr_pltime = ifra->ifra_lifetime.ia6t_pltime;
+
+		/* add the prefix if not yet. */
+		if ((pr = nd6_prefix_lookup(&pr0)) == NULL) {
 			/*
-			 * The TENTATIVE flag was likely set by hand
-			 * beforehand, implicitly indicating the need for DAD.
-			 * We may be able to skip the random delay in this
-			 * case, but we impose delays just in case.
+			 * nd6_prelist_add will install the corresponding
+			 * interface route.
 			 */
-			nd6_dad_start(ifa,
-			    arc4random() % (MAX_RTR_SOLICITATION_DELAY * hz));
+			if ((error = nd6_prelist_add(&pr0, NULL, &pr)) != 0)
+				return;
+			if (pr == NULL) {
+				log(LOG_ERR, "nd6_prelist_add succeeded but "
+				    "no prefix\n");
+				return; /* XXX panic here? */
+			}
 		}
 	}
 
Only in sys/netinet6: in6.c.orig
Only in sys/netinet6: in6.c~
diff -upr ../src/sys/netinet6/in6.h sys/netinet6/in6.h
--- ../src/sys/netinet6/in6.h	2009-04-14 20:14:26.000000000 -0700
+++ sys/netinet6/in6.h	2011-04-19 09:41:23.000000000 -0700
@@ -620,6 +620,7 @@ int	in6_localaddr __P((struct in6_addr *
 int	in6_addrscope __P((struct in6_addr *));
 struct	in6_ifaddr *in6_ifawithifp __P((struct ifnet *, struct in6_addr *));
 extern void in6_if_up __P((struct ifnet *));
+extern void in6_if_down __P((struct ifnet *));
 struct sockaddr;
 extern	u_char	ip6_protox[];
 
diff -upr ../src/sys/netinet6/in6_ifattach.c sys/netinet6/in6_ifattach.c
--- ../src/sys/netinet6/in6_ifattach.c	2009-04-14 20:14:26.000000000 -0700
+++ sys/netinet6/in6_ifattach.c	2011-04-19 10:29:31.000000000 -0700
@@ -78,7 +78,7 @@ static int generate_tmp_ifid(u_int8_t *,
 static int get_ifid(struct ifnet *, struct ifnet *, struct in6_addr *);
 static int in6_ifattach_linklocal(struct ifnet *, struct ifnet *);
 static int in6_ifattach_loopback(struct ifnet *);
-static void in6_purgemaddrs(struct ifnet *);
+/*static*/ void in6_purgemaddrs(struct ifnet *);
 
 #define EUI64_GBIT	0x01
 #define EUI64_UBIT	0x02
@@ -886,7 +886,7 @@ in6_tmpaddrtimer(void *ignored_arg)
 	splx(s);
 }
 
-static void
+/*static*/ void
 in6_purgemaddrs(struct ifnet *ifp)
 {
 	struct in6_multi *in6m;

>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list