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 the next patch, neigh_table_clear() will be called from neigh_table_unregister(), which is called from arp_net_exit() and ndisc_net_exit(). As of the time, all devices are already unregistered and no one can access (p)neigh entry, so neigh_table is freed without waiting RCU grace period. Signed-off-by: Kuniyuki Iwashima --- net/core/neighbour.c | 98 +++++++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 43 deletions(-) diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 34da4cdc813d..83915526eb31 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -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); + RCU_INIT_POINTER(tbl->nht, neigh_hash_alloc(3)); + if (!tbl->nht) + goto err_hash; - 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; + phsize = (PNEIGH_HASHMASK + 1) * sizeof(struct pneigh_entry *); + tbl->phash_buckets = kzalloc(phsize, GFP_KERNEL); + if (!tbl->phash_buckets) + goto err_phash; + + tbl->entry_size = ALIGN(offsetof(struct neighbour, primary_key) + + tbl->key_len, NEIGH_PRIV_ALIGN); tbl->stats = alloc_percpu(struct neigh_statistics); if (!tbl->stats) - panic("cannot create neighbour cache statistics"); + goto err_stats; #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"); + if (!proc_create_seq_data(tbl->id, 0, net->proc_net_stat, + &neigh_stat_seq_ops, tbl)) + goto err_proc; #endif - RCU_INIT_POINTER(tbl->nht, neigh_hash_alloc(3)); - - phsize = (PNEIGH_HASHMASK + 1) * sizeof(struct pneigh_entry *); - tbl->phash_buckets = kzalloc(phsize, GFP_KERNEL); - - if (!tbl->nht || !tbl->phash_buckets) - panic("cannot allocate neighbour cache hashes"); - - 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->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 neigh_hash_table *nht; + struct net *net = &init_net; + 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); + timer_shutdown_sync(&tbl->proxy_timer); + neigh_ifdown(tbl, NULL); - if (atomic_read(&tbl->entries)) - pr_crit("neighbour leakage\n"); + DEBUG_NET_WARN_ON_ONCE(atomic_read(&tbl->entries)); - call_rcu(&rcu_dereference_protected(tbl->nht, 1)->rcu, - neigh_hash_free_rcu); - tbl->nht = NULL; + 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; } -- 2.55.0.679.g6767b8d81c-goog