neighbour: Convert neigh_table.entries to refcount_t.

We will allocate neigh_table for each netns and free it
when netns is destroyed.

neigh_ifdown() cleans up all neighbour entries during netns
dismantle, but there is no synchronisation between timers because
neigh_del_timer() uses timer_delete() to stop a timer.

If neigh_table were freed while timer were running, neigh_destroy()
would touch the freed table.

If we called timer_delete_sync() in neigh_flush_one() under
tbl->lock, lockdep would complain although it is false-positive.

Let's convert neigh_table.entries to refcount_t and destruct
neigh_table only when the count reaches 0.

Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Link: https://patch.msgid.link/20260902203722.926528-12-kuniyu@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Kuniyuki Iwashima 2026-09-02 20:36:52 +00:00 committed by Jakub Kicinski
parent e265c2c432
commit 1c2b205094
2 changed files with 44 additions and 22 deletions

View File

@ -234,7 +234,7 @@ struct neigh_table {
struct delayed_work managed_work;
struct timer_list proxy_timer;
struct sk_buff_head proxy_queue;
atomic_t entries;
refcount_t entries;
atomic_t gc_entries;
struct list_head gc_list;
struct list_head managed_list;

View File

@ -55,6 +55,23 @@ static void neigh_notify(struct neighbour *n, int type, int flags, u32 pid);
static void __neigh_notify(struct neighbour *n, int type, int flags, u32 pid);
static void pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev,
bool skip_perm);
static void neigh_table_free(struct neigh_table *tbl);
static void neigh_table_get(struct neigh_table *tbl)
{
refcount_inc(&tbl->entries);
}
static void neigh_table_put(struct neigh_table *tbl)
{
if (refcount_dec_and_test(&tbl->entries))
neigh_table_free(tbl);
}
static int neigh_table_entries(struct neigh_table *tbl)
{
return refcount_read(&tbl->entries) - 1;
}
#ifdef CONFIG_PROC_FS
static const struct seq_operations neigh_stat_seq_ops;
@ -522,7 +539,7 @@ static struct neighbour *neigh_alloc(struct neigh_table *tbl,
INIT_LIST_HEAD(&n->gc_list);
INIT_LIST_HEAD(&n->managed_list);
atomic_inc(&tbl->entries);
neigh_table_get(tbl);
out:
return n;
@ -672,7 +689,7 @@ ___neigh_create(struct neigh_table *tbl, const void *pkey,
nht = rcu_dereference_protected(tbl->nht,
lockdep_is_held(&tbl->lock));
if (atomic_read(&tbl->entries) > (1 << nht->hash_shift))
if (neigh_table_entries(tbl) > (1 << nht->hash_shift))
nht = neigh_hash_grow(tbl, nht->hash_shift + 1);
hash_val = tbl->hash(n->primary_key, dev, nht->hash_rnd) >> (32 - nht->hash_shift);
@ -924,7 +941,7 @@ void neigh_destroy(struct neighbour *neigh)
neigh_dbg(2, "neigh %p is destroyed\n", neigh);
atomic_dec(&neigh->tbl->entries);
neigh_table_put(neigh->tbl);
kfree_rcu(neigh, rcu);
}
EXPORT_SYMBOL(neigh_destroy);
@ -979,7 +996,7 @@ static void neigh_periodic_work(struct work_struct *work)
neigh_set_reach_time(p);
}
if (atomic_read(&tbl->entries) < READ_ONCE(tbl->gc_thresh1))
if (neigh_table_entries(tbl) < READ_ONCE(tbl->gc_thresh1))
goto out;
for (i = 0 ; i < (1 << nht->hash_shift); i++) {
@ -1845,6 +1862,7 @@ void neigh_table_init(struct neigh_table *tbl)
tbl->last_flush = now;
tbl->last_rand = now + tbl->parms.reachable_time * 20;
refcount_set(&tbl->entries, 1);
spin_lock_init(&tbl->lock);
mutex_init(&tbl->phash_lock);
skb_queue_head_init_class(&tbl->proxy_queue,
@ -1874,24 +1892,10 @@ void neigh_table_init(struct neigh_table *tbl)
panic("cannot allocate memory");
}
/*
* Only called from ndisc_cleanup(), which means this is dead code
* because we no longer can unload IPv6 module.
*/
int neigh_table_clear(struct neigh_table *tbl)
static void neigh_table_free(struct neigh_table *tbl)
{
struct net *net __maybe_unused = &init_net;
struct neigh_hash_table *nht;
cancel_delayed_work_sync(&tbl->managed_work);
cancel_delayed_work_sync(&tbl->gc_work);
timer_shutdown_sync(&tbl->proxy_timer);
neigh_ifdown(tbl, NULL);
DEBUG_NET_WARN_ON_ONCE(atomic_read(&tbl->entries));
remove_proc_entry(tbl->id, net->proc_net_stat);
free_percpu(tbl->stats);
tbl->stats = NULL;
@ -1901,6 +1905,24 @@ int neigh_table_clear(struct neigh_table *tbl)
nht = rcu_dereference_protected(tbl->nht, 1);
tbl->nht = NULL;
neigh_hash_free_rcu(&nht->rcu);
}
/*
* Only called from ndisc_cleanup(), which means this is dead code
* because we no longer can unload IPv6 module.
*/
int neigh_table_clear(struct neigh_table *tbl)
{
struct net *net __maybe_unused = &init_net;
cancel_delayed_work_sync(&tbl->managed_work);
cancel_delayed_work_sync(&tbl->gc_work);
timer_shutdown_sync(&tbl->proxy_timer);
neigh_ifdown(tbl, NULL);
remove_proc_entry(tbl->id, net->proc_net_stat);
neigh_table_put(tbl);
return 0;
}
@ -2275,7 +2297,7 @@ static int neightbl_fill_info(struct sk_buff *skb, struct neigh_table *tbl,
struct ndt_config ndc = {
.ndtc_key_len = tbl->key_len,
.ndtc_entry_size = tbl->entry_size,
.ndtc_entries = atomic_read(&tbl->entries),
.ndtc_entries = neigh_table_entries(tbl),
.ndtc_last_flush = jiffies_to_msecs(flush_delta),
.ndtc_last_rand = jiffies_to_msecs(rand_delta),
.ndtc_proxy_qlen = READ_ONCE(tbl->proxy_queue.qlen),
@ -3495,7 +3517,7 @@ static int neigh_stat_seq_show(struct seq_file *seq, void *v)
seq_printf(seq, "%08x %08lx %08lx %08lx %08lx %08lx %08lx "
"%08lx %08lx %08lx "
"%08lx %08lx %08lx\n",
atomic_read(&tbl->entries),
neigh_table_entries(tbl),
st->allocs,
st->destroys,