-/* $OpenBSD: nd6.c,v 1.274 2023/05/03 11:43:31 bluhm Exp $ */
+/* $OpenBSD: nd6.c,v 1.275 2023/05/04 06:56:56 bluhm Exp $ */
/* $KAME: nd6.c,v 1.280 2002/06/08 19:52:07 itojun Exp $ */
/*
#include <netinet6/nd6.h>
#include <netinet/icmp6.h>
+/*
+ * Locks used to protect struct members in this file:
+ * a atomic operations
+ * I immutable after creation
+ * K kernel lock
+ * m nd6 mutex, needed when net lock is shared
+ * N net lock
+ */
+
#define ND6_SLOWTIMER_INTERVAL (60 * 60) /* 1 hour */
#define ND6_RECALC_REACHTM_INTERVAL (60 * 120) /* 2 hours */
int nd6_debug = 0;
#endif
-TAILQ_HEAD(llinfo_nd6_head, llinfo_nd6) nd6_list;
-struct pool nd6_pool; /* pool for llinfo_nd6 structures */
-int nd6_inuse;
+/* llinfo_nd6 live time, rt_llinfo and RTF_LLINFO are protected by nd6_mtx */
+struct mutex nd6_mtx = MUTEX_INITIALIZER(IPL_SOFTNET);
+
+TAILQ_HEAD(llinfo_nd6_head, llinfo_nd6) nd6_list =
+ TAILQ_HEAD_INITIALIZER(nd6_list); /* [mN] list of llinfo_nd6 structures */
+struct pool nd6_pool; /* [I] pool for llinfo_nd6 structures */
+int nd6_inuse; /* [m] limit neigbor discovery routes */
unsigned int ln_hold_total; /* [a] packets currently in the nd6 queue */
void nd6_timer(void *);
void
nd6_init(void)
{
- TAILQ_INIT(&nd6_list);
pool_init(&nd6_pool, sizeof(struct llinfo_nd6), 0,
IPL_SOFTNET, 0, "nd6", NULL);
uptime = getuptime();
expire = uptime + nd6_gctimer;
+ /* Net lock is exclusive, no nd6 mutex needed for nd6_list here. */
TAILQ_FOREACH_SAFE(ln, &nd6_list, ln_list, nln) {
struct rtentry *rt = ln->ln_rt;
{
struct llinfo_nd6 *ln, *nln;
- NET_ASSERT_LOCKED();
+ NET_ASSERT_LOCKED_EXCLUSIVE();
/*
* Nuke neighbor cache entries for the ifp.
void
nd6_invalidate(struct rtentry *rt)
{
- struct llinfo_nd6 *ln = (struct llinfo_nd6 *)rt->rt_llinfo;
+ struct llinfo_nd6 *ln;
struct sockaddr_dl *sdl = satosdl(rt->rt_gateway);
+ mtx_enter(&nd6_mtx);
+ ln = (struct llinfo_nd6 *)rt->rt_llinfo;
+ if (ln == NULL) {
+ mtx_leave(&nd6_mtx);
+ return;
+ }
atomic_sub_int(&ln_hold_total, mq_purge(&ln->ln_mq));
sdl->sdl_alen = 0;
ln->ln_state = ND6_LLINFO_INCOMPLETE;
ln->ln_asked = 0;
+ mtx_leave(&nd6_mtx);
}
/*
}
satosdl(gate)->sdl_type = ifp->if_type;
satosdl(gate)->sdl_index = ifp->if_index;
- if (ln != NULL)
- break; /* This happens on a route change */
/*
* Case 2: This route may come from cloning, or a manual route
* add with a LL address.
*/
ln = pool_get(&nd6_pool, PR_NOWAIT | PR_ZERO);
- rt->rt_llinfo = (caddr_t)ln;
if (ln == NULL) {
log(LOG_DEBUG, "%s: pool get failed\n", __func__);
break;
}
- mq_init(&ln->ln_mq, LN_HOLD_QUEUE, IPL_SOFTNET);
+
+ mtx_enter(&nd6_mtx);
+ if (rt->rt_llinfo != NULL) {
+ /* we lost the race, another thread has entered it */
+ mtx_leave(&nd6_mtx);
+ pool_put(&nd6_pool, ln);
+ break;
+ }
nd6_inuse++;
+ mq_init(&ln->ln_mq, LN_HOLD_QUEUE, IPL_SOFTNET);
+ rt->rt_llinfo = (caddr_t)ln;
ln->ln_rt = rt;
/* this is required for "ndp" command. - shin */
if (req == RTM_ADD) {
}
rt->rt_flags |= RTF_LLINFO;
TAILQ_INSERT_HEAD(&nd6_list, ln, ln_list);
+ mtx_leave(&nd6_mtx);
/*
* If we have too many cache entries, initiate immediate
nd6_inuse >= ip6_neighborgcthresh) {
int i;
+ mtx_enter(&nd6_mtx);
for (i = 0; i < 10; i++) {
struct llinfo_nd6 *ln_end;
ln_end->ln_state = ND6_LLINFO_PURGE;
nd6_llinfo_settimer(ln_end, 0);
}
+ mtx_leave(&nd6_mtx);
}
/*
llsol.s6_addr32[2] = htonl(1);
llsol.s6_addr8[12] = 0xff;
+ KERNEL_LOCK();
if (in6_addmulti(&llsol, ifp, &error)) {
char addr[INET6_ADDRSTRLEN];
nd6log((LOG_ERR, "%s: failed to join "
addr, sizeof(addr)),
error));
}
+ KERNEL_UNLOCK();
}
}
break;
case RTM_DELETE:
- if (ln == NULL)
+ mtx_enter(&nd6_mtx);
+ ln = (struct llinfo_nd6 *)rt->rt_llinfo;
+ if (ln == NULL) {
+ /* we lost the race, another thread has removed it */
+ mtx_leave(&nd6_mtx);
break;
+ }
+ nd6_inuse--;
+ TAILQ_REMOVE(&nd6_list, ln, ln_list);
+ rt->rt_expire = 0;
+ rt->rt_llinfo = NULL;
+ rt->rt_flags &= ~RTF_LLINFO;
+ atomic_sub_int(&ln_hold_total, mq_purge(&ln->ln_mq));
+ mtx_leave(&nd6_mtx);
+
+ pool_put(&nd6_pool, ln);
+
/* leave from solicited node multicast for proxy ND */
if ((rt->rt_flags & RTF_ANNOUNCE) != 0 &&
(ifp->if_flags & IFF_MULTICAST) != 0) {
llsol.s6_addr32[2] = htonl(1);
llsol.s6_addr8[12] = 0xff;
+ KERNEL_LOCK();
IN6_LOOKUP_MULTI(llsol, ifp, in6m);
if (in6m)
in6_delmulti(in6m);
+ KERNEL_UNLOCK();
}
- nd6_inuse--;
- TAILQ_REMOVE(&nd6_list, ln, ln_list);
- rt->rt_expire = 0;
- rt->rt_llinfo = NULL;
- rt->rt_flags &= ~RTF_LLINFO;
- atomic_sub_int(&ln_hold_total, mq_purge(&ln->ln_mq));
- pool_put(&nd6_pool, ln);
break;
case RTM_INVALIDATE:
- if (ln == NULL)
- break;
if (!ISSET(rt->rt_flags, RTF_LOCAL))
nd6_invalidate(rt);
break;
* for this entry to be a target of forced garbage collection (see
* nd6_rtrequest()).
*/
+ mtx_enter(&nd6_mtx);
TAILQ_REMOVE(&nd6_list, ln, ln_list);
TAILQ_INSERT_HEAD(&nd6_list, ln, ln_list);
+ mtx_leave(&nd6_mtx);
/*
* The first time we send a packet to a neighbor whose entry is