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 --- include/net/neighbour.h | 2 +- net/core/neighbour.c | 58 ++++++++++++++++++++++++++++------------- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/include/net/neighbour.h b/include/net/neighbour.h index 3e31eebf8663..762c8e4cdd96 100644 --- a/include/net/neighbour.h +++ b/include/net/neighbour.h @@ -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; diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 36488dbd1512..7dc8f0cdbb45 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -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,6 +1892,21 @@ void neigh_table_init(struct neigh_table *tbl) panic("cannot allocate memory"); } +static void neigh_table_free(struct neigh_table *tbl) +{ + struct neigh_hash_table *nht; + + free_percpu(tbl->stats); + tbl->stats = NULL; + + kfree(tbl->phash_buckets); + tbl->phash_buckets = NULL; + + 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. @@ -1881,26 +1914,15 @@ void neigh_table_init(struct neigh_table *tbl) int neigh_table_clear(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; - - kfree(tbl->phash_buckets); - tbl->phash_buckets = NULL; - - nht = rcu_dereference_protected(tbl->nht, 1); - tbl->nht = NULL; - neigh_hash_free_rcu(&nht->rcu); + 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, -- 2.55.0.691.gc56d675ccc-goog