[PATCH v3 net-next 06/15] neighbour: Store arp_tbl and nd_tbl in net->neigh_tables[].
From: Kuniyuki Iwashima <kuniyu@google.com>
Date: 2026-08-11 02:24:57
Subsystem:
networking [general], networking [ipv4/ipv6], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Ahern, Ido Schimmel, Linus Torvalds
We will remove RTNL for neigh_add() and neigh_delete(), but they are still serialised by per-protocol neigh_table.lock. We can avoid contention by converting neigh_tables[] to per-netns, but arp_tbl and nd_tbl are directly used in many places. As a prep, let's store &arp_tbl and &nd_tbl in net->neigh_tables[]. We will replace such users with arp_table(net) and nd_table(net) and then allocate per-netns neigh_table. Note that nd_table() still returns &nd_tbl in case disable_ipv6_mod is 1 because some buggy drivers use nd_tbl without checking it. Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com> --- include/net/arp.h | 6 +++++- include/net/ndisc.h | 9 +++++++++ include/net/neighbour.h | 2 ++ include/net/net_namespace.h | 4 ++++ net/core/neighbour.c | 12 ++++++++++++ net/ipv4/arp.c | 23 +++++++++++++++++++++-- net/ipv6/ndisc.c | 12 +++++++++++- 7 files changed, 64 insertions(+), 4 deletions(-)
diff --git a/include/net/arp.h b/include/net/arp.h
index e8747e0713c7..f8d18b1f8b28 100644
--- a/include/net/arp.h
+++ b/include/net/arp.h@@ -7,9 +7,13 @@ #include <linux/hash.h> #include <net/neighbour.h> - extern struct neigh_table arp_tbl; +static inline struct neigh_table *arp_table(struct net *net) +{ + return net->neigh_tables[NEIGH_ARP_TABLE]; +} + static inline u32 arp_hashfn(const void *pkey, const struct net_device *dev, u32 *hash_rnd) { u32 key = *(const u32 *)pkey;
diff --git a/include/net/ndisc.h b/include/net/ndisc.h
index 9e5379ad2d8e..8516c7ff6e92 100644
--- a/include/net/ndisc.h
+++ b/include/net/ndisc.h@@ -67,6 +67,15 @@ struct prefix_info; extern struct neigh_table nd_tbl; +static inline struct neigh_table *nd_table(struct net *net) +{ +#if IS_ENABLED(CONFIG_IPV6) + if (disable_ipv6_mod) + return &nd_tbl; +#endif + return net->neigh_tables[NEIGH_ND_TABLE]; +} + struct nd_msg { struct icmp6hdr icmph; struct in6_addr target;
diff --git a/include/net/neighbour.h b/include/net/neighbour.h
index 7847c29496ae..700d62605fab 100644
--- a/include/net/neighbour.h
+++ b/include/net/neighbour.h@@ -339,6 +339,8 @@ static inline void neigh_confirm(struct neighbour *n) } } +int neigh_table_register(struct net *net, struct neigh_table *tbl, int index); +void neigh_table_unregister(struct net *net, int index); void neigh_table_init(int index, struct neigh_table *tbl); int neigh_table_clear(int index, struct neigh_table *tbl); struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h
index 501af1999fe8..f96a390ae536 100644
--- a/include/net/net_namespace.h
+++ b/include/net/net_namespace.h@@ -39,6 +39,7 @@ #include <net/netns/mctp.h> #include <net/netns/vsock.h> #include <net/net_trackers.h> +#include <net/neighbour_tables.h> #include <linux/ns_common.h> #include <linux/idr.h> #include <linux/skbuff.h>
@@ -47,6 +48,7 @@ struct user_namespace; struct proc_dir_entry; +struct neigh_table; struct net_device; struct sock; struct ctl_table_header;
@@ -103,6 +105,8 @@ struct net { struct proc_dir_entry *proc_net; struct proc_dir_entry *proc_net_stat; + struct neigh_table *neigh_tables[NEIGH_NR_TABLES]; + #ifdef CONFIG_SYSCTL struct ctl_table_set sysctls; #endif
diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index caa52bf64a2d..286acb7cd504 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c@@ -1899,6 +1899,18 @@ int neigh_table_clear(int index, struct neigh_table *tbl) return 0; } +int neigh_table_register(struct net *net, struct neigh_table *tbl, int index) +{ + net->neigh_tables[index] = tbl; + + return 0; +} + +void neigh_table_unregister(struct net *net, int index) +{ + net->neigh_tables[index] = NULL; +} + static struct neigh_table *neigh_find_table(int family) { struct neigh_table *tbl = NULL;
diff --git a/net/ipv4/arp.c b/net/ipv4/arp.c
index d409f606aec0..bad17d5aeafc 100644
--- a/net/ipv4/arp.c
+++ b/net/ipv4/arp.c@@ -1495,15 +1495,34 @@ static const struct seq_operations arp_seq_ops = { static int __net_init arp_net_init(struct net *net) { + int err; + + err = neigh_table_register(net, &arp_tbl, NEIGH_ARP_TABLE); + if (err) + goto err; + +#ifdef CONFIG_PROC_FS if (!proc_create_net("arp", 0444, net->proc_net, &arp_seq_ops, - sizeof(struct neigh_seq_state))) - return -ENOMEM; + sizeof(struct neigh_seq_state))) { + err = -ENOMEM; + goto err_proc_create; + } +#endif + return 0; + +#ifdef CONFIG_PROC_FS +err_proc_create: + neigh_table_unregister(net, NEIGH_ARP_TABLE); +#endif +err: + return err; } static void __net_exit arp_net_exit(struct net *net) { remove_proc_entry("arp", net->proc_net); + neigh_table_unregister(net, NEIGH_ARP_TABLE); } static struct pernet_operations arp_net_ops = {
diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index 2ceb655c4229..7af1ba5084bd 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c@@ -1972,12 +1972,16 @@ static int __net_init ndisc_net_init(struct net *net) struct sock *sk; int err; + err = neigh_table_register(net, &nd_tbl, NEIGH_ND_TABLE); + if (err) + goto err; + err = inet_ctl_sock_create(&sk, PF_INET6, SOCK_RAW, IPPROTO_ICMPV6, net); if (err < 0) { net_err_ratelimited("NDISC: Failed to initialize the control socket (err %d)\n", err); - return err; + goto err_sock_create; } net->ipv6.ndisc_sk = sk;
@@ -1988,11 +1992,17 @@ static int __net_init ndisc_net_init(struct net *net) inet6_clear_bit(MC6_LOOP, sk); return 0; + +err_sock_create: + neigh_table_unregister(net, NEIGH_ND_TABLE); +err: + return err; } static void __net_exit ndisc_net_exit(struct net *net) { inet_ctl_sock_destroy(net->ipv6.ndisc_sk); + neigh_table_unregister(net, NEIGH_ND_TABLE); } static struct pernet_operations ndisc_net_ops = {
--
2.55.0.691.gc56d675ccc-goog