Module Name:    src
Committed By:   ozaki-r
Date:           Tue Jan 16 08:13:47 UTC 2018

Modified Files:
        src/sys/netinet: if_arp.c
        src/sys/netinet6: nd6_nbr.c

Log Message:
Make DAD destructions (MP-)safe with callout_stop

arp_dad_stoptimer and nd6_dad_stoptimer can be called with or without
softnet_lock held and unfortunately we have no easy way to statically know 
which.
So it is hard to use callout_halt there.

To address the situation, we use callout_stop to make the code safe. The new
approach copes with the issue by delegating the destruction of a callout to
callout itself, which allows us to not wait the callout to finish. This can be
done thanks to that DAD objects are separated from other data such as ifa.

The approach is suggested by riastradh@
Proposed on tech-kern@ and tech-net@


To generate a diff of this commit:
cvs rdiff -u -r1.255 -r1.256 src/sys/netinet/if_arp.c
cvs rdiff -u -r1.143 -r1.144 src/sys/netinet6/nd6_nbr.c

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

Modified files:

Index: src/sys/netinet/if_arp.c
diff -u src/sys/netinet/if_arp.c:1.255 src/sys/netinet/if_arp.c:1.256
--- src/sys/netinet/if_arp.c:1.255	Fri Nov 17 07:37:12 2017
+++ src/sys/netinet/if_arp.c	Tue Jan 16 08:13:47 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_arp.c,v 1.255 2017/11/17 07:37:12 ozaki-r Exp $	*/
+/*	$NetBSD: if_arp.c,v 1.256 2018/01/16 08:13:47 ozaki-r Exp $	*/
 
 /*-
  * Copyright (c) 1998, 2000, 2008 The NetBSD Foundation, Inc.
@@ -68,7 +68,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: if_arp.c,v 1.255 2017/11/17 07:37:12 ozaki-r Exp $");
+__KERNEL_RCSID(0, "$NetBSD: if_arp.c,v 1.256 2018/01/16 08:13:47 ozaki-r Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_ddb.h"
@@ -173,7 +173,8 @@ static	void revarprequest(struct ifnet *
 
 static	void arp_drainstub(void);
 
-static void arp_dad_timer(struct ifaddr *);
+struct dadq;
+static void arp_dad_timer(struct dadq *);
 static void arp_dad_start(struct ifaddr *);
 static void arp_dad_stop(struct ifaddr *);
 static void arp_dad_duplicated(struct ifaddr *, const char *);
@@ -1534,18 +1535,18 @@ arp_dad_starttimer(struct dadq *dp, int 
 {
 
 	callout_reset(&dp->dad_timer_ch, ticks,
-	    (void (*)(void *))arp_dad_timer, (void *)dp->dad_ifa);
+	    (void (*)(void *))arp_dad_timer, dp);
 }
 
 static void
-arp_dad_stoptimer(struct dadq *dp)
+arp_dad_destroytimer(struct dadq *dp)
 {
 
-#ifdef NET_MPSAFE
-	callout_halt(&dp->dad_timer_ch, NULL);
-#else
-	callout_halt(&dp->dad_timer_ch, softnet_lock);
-#endif
+	TAILQ_REMOVE(&dadq, dp, dad_list);
+	/* Request the timer to destroy dp. */
+	dp->dad_ifa = NULL;
+	callout_reset(&dp->dad_timer_ch, 0,
+	    (void (*)(void *))arp_dad_timer, dp);
 }
 
 static void
@@ -1665,36 +1666,39 @@ arp_dad_stop(struct ifaddr *ifa)
 	}
 
 	/* Prevent the timer from running anymore. */
-	TAILQ_REMOVE(&dadq, dp, dad_list);
-	mutex_exit(&arp_dad_lock);
+	arp_dad_destroytimer(dp);
 
-	arp_dad_stoptimer(dp);
+	mutex_exit(&arp_dad_lock);
 
-	kmem_intr_free(dp, sizeof(*dp));
 	ifafree(ifa);
 }
 
 static void
-arp_dad_timer(struct ifaddr *ifa)
+arp_dad_timer(struct dadq *dp)
 {
-	struct in_ifaddr *ia = (struct in_ifaddr *)ifa;
-	struct dadq *dp;
+	struct ifaddr *ifa;
+	struct in_ifaddr *ia;
 	char ipbuf[INET_ADDRSTRLEN];
 	bool need_free = false;
 
 	SOFTNET_KERNEL_LOCK_UNLESS_NET_MPSAFE();
 	mutex_enter(&arp_dad_lock);
 
-	/* Sanity check */
-	if (ia == NULL) {
-		log(LOG_ERR, "%s: called with null parameter\n", __func__);
-		goto done;
-	}
-	dp = arp_dad_find(ifa);
-	if (dp == NULL) {
-		/* DAD seems to be stopping, so do nothing. */
+	ifa = dp->dad_ifa;
+	if (ifa == NULL) {
+		/* ifa is being deleted. DAD should be freed too. */
+		if (callout_pending(&dp->dad_timer_ch)) {
+			/*
+			 * The callout is scheduled again, so we cannot destroy
+			 * dp in this run.
+			 */
+			goto done;
+		}
+		need_free = true;
 		goto done;
 	}
+
+	ia = (struct in_ifaddr *)ifa;
 	if (ia->ia4_flags & IN_IFF_DUPLICATED) {
 		log(LOG_ERR, "%s: called with duplicate address %s(%s)\n",
 		    __func__, IN_PRINT(ipbuf, &ia->ia_addr.sin_addr),
@@ -1769,8 +1773,10 @@ done:
 	mutex_exit(&arp_dad_lock);
 
 	if (need_free) {
+		callout_destroy(&dp->dad_timer_ch);
 		kmem_intr_free(dp, sizeof(*dp));
-		ifafree(ifa);
+		if (ifa != NULL)
+			ifafree(ifa);
 	}
 
 	SOFTNET_KERNEL_UNLOCK_UNLESS_NET_MPSAFE();

Index: src/sys/netinet6/nd6_nbr.c
diff -u src/sys/netinet6/nd6_nbr.c:1.143 src/sys/netinet6/nd6_nbr.c:1.144
--- src/sys/netinet6/nd6_nbr.c:1.143	Tue Jan 16 07:56:55 2018
+++ src/sys/netinet6/nd6_nbr.c	Tue Jan 16 08:13:47 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: nd6_nbr.c,v 1.143 2018/01/16 07:56:55 ozaki-r Exp $	*/
+/*	$NetBSD: nd6_nbr.c,v 1.144 2018/01/16 08:13:47 ozaki-r Exp $	*/
 /*	$KAME: nd6_nbr.c,v 1.61 2001/02/10 16:06:14 jinmei Exp $	*/
 
 /*
@@ -31,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: nd6_nbr.c,v 1.143 2018/01/16 07:56:55 ozaki-r Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nd6_nbr.c,v 1.144 2018/01/16 08:13:47 ozaki-r Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_inet.h"
@@ -79,8 +79,8 @@ __KERNEL_RCSID(0, "$NetBSD: nd6_nbr.c,v 
 struct dadq;
 static struct dadq *nd6_dad_find(struct ifaddr *);
 static void nd6_dad_starttimer(struct dadq *, int);
-static void nd6_dad_stoptimer(struct dadq *);
-static void nd6_dad_timer(struct ifaddr *);
+static void nd6_dad_destroytimer(struct dadq *);
+static void nd6_dad_timer(struct dadq *);
 static void nd6_dad_ns_output(struct dadq *, struct ifaddr *);
 static void nd6_dad_ns_input(struct ifaddr *);
 static void nd6_dad_na_input(struct ifaddr *);
@@ -1087,18 +1087,18 @@ nd6_dad_starttimer(struct dadq *dp, int 
 {
 
 	callout_reset(&dp->dad_timer_ch, ticks,
-	    (void (*)(void *))nd6_dad_timer, (void *)dp->dad_ifa);
+	    (void (*)(void *))nd6_dad_timer, dp);
 }
 
 static void
-nd6_dad_stoptimer(struct dadq *dp)
+nd6_dad_destroytimer(struct dadq *dp)
 {
 
-#ifdef NET_MPSAFE
-	callout_halt(&dp->dad_timer_ch, NULL);
-#else
-	callout_halt(&dp->dad_timer_ch, softnet_lock);
-#endif
+	TAILQ_REMOVE(&dadq, dp, dad_list);
+	/* Request the timer to destroy dp. */
+	dp->dad_ifa = NULL;
+	callout_reset(&dp->dad_timer_ch, 0,
+	    (void (*)(void *))nd6_dad_timer, dp);
 }
 
 /*
@@ -1210,20 +1210,18 @@ nd6_dad_stop(struct ifaddr *ifa)
 	}
 
 	/* Prevent the timer from running anymore. */
-	TAILQ_REMOVE(&dadq, dp, dad_list);
-	mutex_exit(&nd6_dad_lock);
+	nd6_dad_destroytimer(dp);
 
-	nd6_dad_stoptimer(dp);
+	mutex_exit(&nd6_dad_lock);
 
-	kmem_intr_free(dp, sizeof(*dp));
 	ifafree(ifa);
 }
 
 static void
-nd6_dad_timer(struct ifaddr *ifa)
+nd6_dad_timer(struct dadq *dp)
 {
-	struct in6_ifaddr *ia = (struct in6_ifaddr *)ifa;
-	struct dadq *dp;
+	struct ifaddr *ifa;
+	struct in6_ifaddr *ia;
 	int duplicate = 0;
 	char ip6buf[INET6_ADDRSTRLEN];
 	bool need_free = false;
@@ -1231,16 +1229,21 @@ nd6_dad_timer(struct ifaddr *ifa)
 	SOFTNET_KERNEL_LOCK_UNLESS_NET_MPSAFE();
 	mutex_enter(&nd6_dad_lock);
 
-	/* Sanity check */
-	if (ia == NULL) {
-		log(LOG_ERR, "nd6_dad_timer: called with null parameter\n");
-		goto done;
-	}
-	dp = nd6_dad_find(ifa);
-	if (dp == NULL) {
-		/* DAD seems to be stopping, so do nothing. */
+	ifa = dp->dad_ifa;
+	if (ifa == NULL) {
+		/* ifa is being deleted. DAD should be freed too. */
+		if (callout_pending(&dp->dad_timer_ch)) {
+			/*
+			 * The callout is scheduled again, so we cannot destroy
+			 * dp in this run.
+			 */
+			goto done;
+		}
+		need_free = true;
 		goto done;
 	}
+
+	ia = (struct in6_ifaddr *)ifa;
 	if (ia->ia6_flags & IN6_IFF_DUPLICATED) {
 		log(LOG_ERR, "nd6_dad_timer: called with duplicate address "
 			"%s(%s)\n",
@@ -1316,9 +1319,10 @@ done:
 	mutex_exit(&nd6_dad_lock);
 
 	if (need_free) {
+		callout_destroy(&dp->dad_timer_ch);
 		kmem_intr_free(dp, sizeof(*dp));
-		ifafree(ifa);
-		ifa = NULL;
+		if (ifa != NULL)
+			ifafree(ifa);
 	}
 
 	if (duplicate)
@@ -1392,13 +1396,11 @@ nd6_dad_duplicated(struct ifaddr *ifa)
 		}
 	}
 
-	TAILQ_REMOVE(&dadq, dp, dad_list);
-	mutex_exit(&nd6_dad_lock);
-
 	/* We are done with DAD, with duplicated address found. (failure) */
-	nd6_dad_stoptimer(dp);
+	nd6_dad_destroytimer(dp);
+
+	mutex_exit(&nd6_dad_lock);
 
-	kmem_intr_free(dp, sizeof(*dp));
 	ifafree(ifa);
 }
 

Reply via email to