neighbour: Clean up neigh_table_init() and neigh_table_clear().

Currently, neigh_table_init() and neigh_table_clear() are called
once at the boot time, but we will call them for each netns.

Let's clean up them.

For neigh_table_init(),

  * Allocate all memory first and unwind on failure
  * Remove !tbl->entry_size check since it is always true
     for arp_tbl and nd_tbl
  * Initialise everything before starting delayed works

For neigh_table_clear(),

  * Reorder function to match neigh_table_init()
  * Use timer_shutdown_sync() instead of timer_delete_sync()
  * Remove pneigh_queue_purge() since it is called
     via neigh_ifdown()
  * Replace pr_crit() with DEBUG_NET_WARN_ON_ONCE()
  * Call neigh_hash_free_rcu() directly without call_rcu()

In a later patch, neigh_table_clear() will be split so that
tbl->{stats,phash_buckets,nht} will be freed only when all
neighbour entries are freed.

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-11-kuniyu@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Kuniyuki Iwashima 2026-09-02 20:36:51 +00:00 committed by Jakub Kicinski
parent df5f4f372d
commit e265c2c432

View File

@ -1810,57 +1810,68 @@ static struct lock_class_key neigh_table_proxy_queue_class;
void neigh_table_init(struct neigh_table *tbl)
{
unsigned long now = jiffies;
struct net *net = &init_net;
unsigned long phsize;
INIT_LIST_HEAD(&tbl->parms_list);
INIT_LIST_HEAD(&tbl->gc_list);
INIT_LIST_HEAD(&tbl->managed_list);
list_add(&tbl->parms.list, &tbl->parms_list);
write_pnet(&tbl->parms.net, &init_net);
refcount_set(&tbl->parms.refcnt, 1);
neigh_set_reach_time(&tbl->parms);
tbl->parms.qlen = 0;
tbl->stats = alloc_percpu(struct neigh_statistics);
if (!tbl->stats)
panic("cannot create neighbour cache statistics");
#ifdef CONFIG_PROC_FS
if (!proc_create_seq_data(tbl->id, 0, init_net.proc_net_stat,
&neigh_stat_seq_ops, tbl))
panic("cannot create neighbour proc dir entry");
#endif
RCU_INIT_POINTER(tbl->nht, neigh_hash_alloc(3));
if (!tbl->nht)
goto err_hash;
phsize = (PNEIGH_HASHMASK + 1) * sizeof(struct pneigh_entry *);
tbl->phash_buckets = kzalloc(phsize, GFP_KERNEL);
if (!tbl->phash_buckets)
goto err_phash;
if (!tbl->nht || !tbl->phash_buckets)
panic("cannot allocate neighbour cache hashes");
tbl->entry_size = ALIGN(offsetof(struct neighbour, primary_key) +
tbl->key_len, NEIGH_PRIV_ALIGN);
if (!tbl->entry_size)
tbl->entry_size = ALIGN(offsetof(struct neighbour, primary_key) +
tbl->key_len, NEIGH_PRIV_ALIGN);
else
WARN_ON(tbl->entry_size % NEIGH_PRIV_ALIGN);
tbl->stats = alloc_percpu(struct neigh_statistics);
if (!tbl->stats)
goto err_stats;
#ifdef CONFIG_PROC_FS
if (!proc_create_seq_data(tbl->id, 0, net->proc_net_stat,
&neigh_stat_seq_ops, tbl))
goto err_proc;
#endif
tbl->parms.tbl = tbl;
tbl->parms.qlen = 0;
INIT_LIST_HEAD(&tbl->parms_list);
list_add(&tbl->parms.list, &tbl->parms_list);
write_pnet(&tbl->parms.net, net);
refcount_set(&tbl->parms.refcnt, 1);
neigh_set_reach_time(&tbl->parms);
tbl->last_flush = now;
tbl->last_rand = now + tbl->parms.reachable_time * 20;
spin_lock_init(&tbl->lock);
mutex_init(&tbl->phash_lock);
skb_queue_head_init_class(&tbl->proxy_queue,
&neigh_table_proxy_queue_class);
timer_setup(&tbl->proxy_timer, neigh_proxy_process, 0);
INIT_LIST_HEAD(&tbl->gc_list);
INIT_DEFERRABLE_WORK(&tbl->gc_work, neigh_periodic_work);
queue_delayed_work(system_power_efficient_wq, &tbl->gc_work,
tbl->parms.reachable_time);
tbl->parms.reachable_time);
INIT_LIST_HEAD(&tbl->managed_list);
INIT_DEFERRABLE_WORK(&tbl->managed_work, neigh_managed_work);
queue_delayed_work(system_power_efficient_wq, &tbl->managed_work, 0);
timer_setup(&tbl->proxy_timer, neigh_proxy_process, 0);
skb_queue_head_init_class(&tbl->proxy_queue,
&neigh_table_proxy_queue_class);
return;
tbl->last_flush = now;
tbl->last_rand = now + tbl->parms.reachable_time * 20;
#ifdef CONFIG_PROC_FS
err_proc:
free_percpu(tbl->stats);
#endif
err_stats:
kfree(tbl->phash_buckets);
err_phash:
neigh_hash_free_rcu(&rcu_dereference_protected(tbl->nht, 1)->rcu);
err_hash:
panic("cannot allocate memory");
}
/*
@ -1869,26 +1880,27 @@ void neigh_table_init(struct neigh_table *tbl)
*/
int neigh_table_clear(struct neigh_table *tbl)
{
/* It is not clean... Fix it to unload IPv6 module safely */
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_delete_sync(&tbl->proxy_timer);
pneigh_queue_purge(&tbl->proxy_queue, NULL, tbl->family);
neigh_ifdown(tbl, NULL);
if (atomic_read(&tbl->entries))
pr_crit("neighbour leakage\n");
timer_shutdown_sync(&tbl->proxy_timer);
call_rcu(&rcu_dereference_protected(tbl->nht, 1)->rcu,
neigh_hash_free_rcu);
tbl->nht = NULL;
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;
remove_proc_entry(tbl->id, init_net.proc_net_stat);
free_percpu(tbl->stats);
tbl->stats = NULL;
nht = rcu_dereference_protected(tbl->nht, 1);
tbl->nht = NULL;
neigh_hash_free_rcu(&nht->rcu);
return 0;
}