Index: sys/netinet6/in6.c =================================================================== RCS file: /cvsroot/src/sys/netinet6/in6.c,v retrieving revision 1.221 diff -p -u -r1.221 in6.c --- sys/netinet6/in6.c 18 Oct 2016 07:30:31 -0000 1.221 +++ sys/netinet6/in6.c 3 Nov 2016 02:07:26 -0000 @@ -1326,11 +1326,11 @@ in6_update_ifa(struct ifnet *ifp, struct return rc; } -void -in6_purgeaddr(struct ifaddr *ifa) +static void +in6_purgeaddr1(struct in6_ifaddr *ia) { + struct ifaddr *ifa = &ia->ia_ifa; struct ifnet *ifp = ifa->ifa_ifp; - struct in6_ifaddr *ia = (struct in6_ifaddr *) ifa; struct in6_multi_mship *imm; /* stop DAD processing */ @@ -1353,6 +1353,108 @@ in6_purgeaddr(struct ifaddr *ifa) in6_unlink_ifa(ia, ifp); } +static struct in6_purge { + kmutex_t lock; + kcondvar_t cv; + struct workqueue *wq; +} in6_purge __cacheline_aligned; + +static bool +in6_purge_acquire_wait(struct in6_ifaddr *ia) +{ + bool acquired = false; + + mutex_enter(&in6_purge.lock); + if (ia->ia6_purging) { + do { + cv_wait(&in6_purge.cv, &in6_purge.lock); + } while (ia->ia6_purging); + goto out; + } + ia->ia6_purging = true; + acquired = true; +out: mutex_exit(&in6_purge.lock); + + return acquired; +} + +static bool +in6_purge_tryacquire(struct in6_ifaddr *ia) +{ + bool acquired = false; + + mutex_enter(&in6_purge.lock); + if (ia->ia6_purging) + goto out; + ia->ia6_purging = true; + acquired = true; +out: mutex_exit(&in6_purge.lock); + + return acquired; +} + +static void +in6_purge_release(struct in6_ifaddr *ia) +{ + + mutex_enter(&in6_purge.lock); + KASSERTMSG(ia->ia6_purging, + "in6_ifaddr %p queued but not marked for purge", ia); + ia->ia6_purging = false; + cv_broadcast(&in6_purge.cv); + mutex_exit(&in6_purge.lock); +} + +static void +in6_purgeaddr_work(struct work *wk, void *cookie __unused) +{ + struct in6_ifaddr *ia = container_of(wk, struct in6_ifaddr, + ia6_purge_work); + + KASSERT(ia->ia6_purging); + in6_purgeaddr1(ia); + in6_purge_release(ia); +} + +int +in6_purge_init(void) +{ + int error; + + error = workqueue_create(&in6_purge.wq, "in6purgeq", + &in6_purgeaddr_work, NULL, PRI_NONE, IPL_SOFTNET, 0); + if (error) + return error; + + mutex_init(&in6_purge.lock, MUTEX_DEFAULT, IPL_SOFTNET); + cv_init(&in6_purge.cv, "in6purge"); + + return 0; +} + +void +in6_purgeaddr(struct ifaddr *ifa) +{ + struct in6_ifaddr *ia = (struct in6_ifaddr *) ifa; + + if (!in6_purge_acquire_wait(ia)) + /* Someone else just purged it for us. */ + return; + in6_purgeaddr1(ia); + in6_purge_release(ia); +} + +void +in6_purgeaddr_async(struct ifaddr *ifa) +{ + struct in6_ifaddr *ia = (struct in6_ifaddr *) ifa; + + if (!in6_purge_tryacquire(ia)) + /* Already queued up for purging. */ + return; + workqueue_enqueue(in6_purge.wq, &ia->ia6_purge_work, NULL); +} + static void in6_unlink_ifa(struct in6_ifaddr *ia, struct ifnet *ifp) { Index: sys/netinet6/in6_proto.c =================================================================== RCS file: /cvsroot/src/sys/netinet6/in6_proto.c,v retrieving revision 1.113 diff -p -u -r1.113 in6_proto.c --- sys/netinet6/in6_proto.c 6 Jul 2016 10:49:49 -0000 1.113 +++ sys/netinet6/in6_proto.c 3 Nov 2016 02:07:26 -0000 @@ -423,6 +423,11 @@ bool in6_present = false; static void in6_dom_init(void) { + int error; + + error = in6_purge_init(); + if (error) + panic("failed to initialize in6 purge async: error=%d", error); in6_present = true; } Index: sys/netinet6/in6_var.h =================================================================== RCS file: /cvsroot/src/sys/netinet6/in6_var.h,v retrieving revision 1.87 diff -p -u -r1.87 in6_var.h --- sys/netinet6/in6_var.h 14 Sep 2016 16:17:17 -0000 1.87 +++ sys/netinet6/in6_var.h 3 Nov 2016 02:07:26 -0000 @@ -67,6 +67,10 @@ #include #include +#ifdef _KERNEL +#include +#endif + /* * Interface address, Internet version. One of these structures * is allocated for each interface with an Internet address. @@ -131,6 +135,10 @@ struct in6_ifaddr { #ifdef _KERNEL struct pslist_entry ia6_pslist_entry; + + /* asynchronous purge work, locked by in6_purge.lock */ + struct work ia6_purge_work; + bool ia6_purging; #endif }; @@ -525,38 +533,66 @@ MALLOC_DECLARE(M_IP6OPT); extern struct pslist_head in6_ifaddr_list; extern kmutex_t in6_ifaddr_lock; +static inline struct in6_ifaddr * +_in6_ifaddr_skip_reader(struct in6_ifaddr *ia6) +{ + + for (; + ia6 != NULL; + ia6 = PSLIST_READER_NEXT(ia6, struct in6_ifaddr, + ia6_pslist_entry)) { + if (!ia6->ia6_purging) + return ia6; + } + return NULL; +} + +static inline struct in6_ifaddr * +_in6_ifaddr_skip_writer(struct in6_ifaddr *ia6) +{ + + for (; + ia6 != NULL; + ia6 = PSLIST_WRITER_NEXT(ia6, struct in6_ifaddr, + ia6_pslist_entry)) { + if (!ia6->ia6_purging) + return ia6; + } + return NULL; +} + #define IN6_ADDRLIST_ENTRY_INIT(__ia) \ PSLIST_ENTRY_INIT((__ia), ia6_pslist_entry) #define IN6_ADDRLIST_ENTRY_DESTROY(__ia) \ PSLIST_ENTRY_DESTROY((__ia), ia6_pslist_entry) -#define IN6_ADDRLIST_READER_EMPTY() \ - (PSLIST_READER_FIRST(&in6_ifaddr_list, struct in6_ifaddr, \ - ia6_pslist_entry) == NULL) +#define IN6_ADDRLIST_READER_EMPTY() (IN6_ADDRLIST_READER_FIRST() == NULL) #define IN6_ADDRLIST_READER_FIRST() \ - PSLIST_READER_FIRST(&in6_ifaddr_list, struct in6_ifaddr, \ - ia6_pslist_entry) + _in6_ifaddr_skip_reader(PSLIST_READER_FIRST(&in6_ifaddr_list, \ + struct in6_ifaddr, ia6_pslist_entry)) #define IN6_ADDRLIST_READER_NEXT(__ia) \ - PSLIST_READER_NEXT((__ia), struct in6_ifaddr, ia6_pslist_entry) + _in6_ifaddr_skip_reader(PSLIST_READER_NEXT((__ia), struct in6_ifaddr, \ + ia6_pslist_entry)) #define IN6_ADDRLIST_READER_FOREACH(__ia) \ PSLIST_READER_FOREACH((__ia), &in6_ifaddr_list, \ - struct in6_ifaddr, ia6_pslist_entry) + struct in6_ifaddr, ia6_pslist_entry) \ + if (!(__ia)->ia6_purging) #define IN6_ADDRLIST_WRITER_INSERT_HEAD(__ia) \ PSLIST_WRITER_INSERT_HEAD(&in6_ifaddr_list, (__ia), ia6_pslist_entry) #define IN6_ADDRLIST_WRITER_REMOVE(__ia) \ PSLIST_WRITER_REMOVE((__ia), ia6_pslist_entry) #define IN6_ADDRLIST_WRITER_FOREACH(__ia) \ PSLIST_WRITER_FOREACH((__ia), &in6_ifaddr_list, struct in6_ifaddr, \ - ia6_pslist_entry) + ia6_pslist_entry) \ + if (!(__ia)->ia6_purging) #define IN6_ADDRLIST_WRITER_FIRST() \ - PSLIST_WRITER_FIRST(&in6_ifaddr_list, struct in6_ifaddr, \ - ia6_pslist_entry) + _in6_ifaddr_skip_writer(PSLIST_WRITER_FIRST(&in6_ifaddr_list, \ + struct in6_ifaddr, ia6_pslist_entry)) #define IN6_ADDRLIST_WRITER_NEXT(__ia) \ - PSLIST_WRITER_NEXT((__ia), struct in6_ifaddr, ia6_pslist_entry) + _in6_ifaddr_skip_writer(PSLIST_WRITER_NEXT((__ia), struct in6_ifaddr, \ + ia6_pslist_entry)) #define IN6_ADDRLIST_WRITER_INSERT_AFTER(__ia, __new) \ PSLIST_WRITER_INSERT_AFTER((__ia), (__new), ia6_pslist_entry) -#define IN6_ADDRLIST_WRITER_EMPTY() \ - (PSLIST_WRITER_FIRST(&in6_ifaddr_list, struct in6_ifaddr, \ - ia6_pslist_entry) == NULL) +#define IN6_ADDRLIST_WRITER_EMPTY() (IN6_ADDRLIST_WRITER_FIRST() == NULL) #define IN6_ADDRLIST_WRITER_INSERT_TAIL(__new) \ do { \ if (IN6_ADDRLIST_WRITER_EMPTY()) { \ @@ -785,7 +821,9 @@ int in6_mask2len(struct in6_addr *, u_ch int in6_control(struct socket *, u_long, void *, struct ifnet *); int in6_update_ifa(struct ifnet *, struct in6_aliasreq *, struct in6_ifaddr *, int); +int in6_purge_init(void); void in6_purgeaddr(struct ifaddr *); +void in6_purgeaddr_async(struct ifaddr *); void in6_purgeif(struct ifnet *); void in6_savemkludge(struct in6_ifaddr *); void in6_setmaxmtu (void); Index: sys/netinet6/nd6.c =================================================================== RCS file: /cvsroot/src/sys/netinet6/nd6.c,v retrieving revision 1.210 diff -p -u -r1.210 nd6.c --- sys/netinet6/nd6.c 2 Nov 2016 03:43:27 -0000 1.210 +++ sys/netinet6/nd6.c 3 Nov 2016 02:07:27 -0000 @@ -630,7 +630,7 @@ nd6_timer_work(struct work *wk, void *ar } ia6_release(ia6, &psref); - in6_purgeaddr(&ia6->ia_ifa); + in6_purgeaddr_async(&ia6->ia_ifa); ia6 = NULL; if (regen)