AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/13 08:33 flow patch-triage
5m Args:
null

Results:
{
  "EnableConfigs": [
    "NET_NS",
    "IPV6"
  ],
  "FocusSymbols": [
    "neigh_table_register",
    "neigh_table_unregister",
    "pneigh_lookup",
    "pneigh_create",
    "pneigh_delete",
    "neigh_parms_alloc",
    "neigh_parms_release",
    "neigh_xmit"
  ],
  "KMSANReasoning": "The patch series refactors the neighbor discovery subsystem (ARP and IPv6 ND) to make neighbor tables (`arp_tbl` and `nd_tbl`) per-network-namespace instead of global variables. It does this by allocating a new `struct neigh_table` for each network namespace, copying the initial values from a template, and initializing the dynamic fields (locks, lists, workqueues, etc.). \n\nThere are no new structures or fields copied to user space. The `struct neigh_table` itself is an internal kernel structure and is never copied directly to user space. Information from the table is exposed to user space via netlink messages (e.g., `neightbl_fill_info`, `neightbl_fill_param_info`), which construct the messages using `nla_put_*` functions and designated initializers for structures like `ndt_config` and `ndt_stats`. All memory allocations for the tables, parameters, and hash buckets use zero-initializing functions (`kzalloc`, `alloc_percpu`, `__GFP_ZERO`) or `kmemdup` from fully initialized static templates.\n\nSince the patch does not introduce any uninitialized memory risks, info-leaks, or complex data structures sent to user space without proper initialization, a dedicated KMSAN fuzzing session is not justified. Standard KASAN, LOCKDEP, and other bug detectors are sufficient to catch any potential memory corruption, out-of-bounds access, or use-after-free bugs introduced by this refactoring.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch changes the neighbor tables (arp_tbl and nd_tbl) from being global to being per-network namespace (net-\u003eneigh_tables). This is a significant functional change in the core networking stack, affecting IPv4 ARP and IPv6 Neighbor Discovery, especially in environments using network namespaces. It modifies how neighbor entries are looked up, created, and garbage collected across different namespaces.",
  "WorthFuzzing": true
}

1/1 2026/08/13 08:33 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit ec0d7c39eae66ddc2d69aed3897e829969ad40bd\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Aug 13 08:33:02 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst\nindex e586e17fc7a58..fe43e85959583 100644\n--- a/Documentation/admin-guide/sysctl/net.rst\n+++ b/Documentation/admin-guide/sysctl/net.rst\n@@ -413,6 +413,20 @@ new netns has been created.\n \n Default : 0  (for compatibility reasons)\n \n+neigh_inherit_init_net\n+----------------------\n+\n+Controls if a new network namespace should inherit all current\n+settings under /proc/sys/net/ipv{4,6}/neigh/default/.\n+\n+By default, the value is set to 1, and all settings are inherited\n+from init_net.\n+\n+If set to 0, both IPv4 and IPv6 settings are reset to their default\n+values.\n+\n+Default : 1  (for compatibility reasons)\n+\n txrehash\n --------\n \ndiff --git a/drivers/infiniband/ulp/ipoib/ipoib_main.c b/drivers/infiniband/ulp/ipoib/ipoib_main.c\nindex 16a015b672063..895a3e3f7310b 100644\n--- a/drivers/infiniband/ulp/ipoib/ipoib_main.c\n+++ b/drivers/infiniband/ulp/ipoib/ipoib_main.c\n@@ -1378,7 +1378,8 @@ struct ipoib_neigh *ipoib_neigh_get(struct net_device *dev, u8 *daddr)\n \treturn neigh;\n }\n \n-static void __ipoib_reap_neigh(struct ipoib_dev_priv *priv)\n+static void __ipoib_reap_neigh(struct ipoib_dev_priv *priv,\n+\t\t\t       int gc_interval)\n {\n \tstruct ipoib_neigh_table *ntbl = \u0026priv-\u003entbl;\n \tstruct ipoib_neigh_hash *htbl;\n@@ -1397,7 +1398,7 @@ static void __ipoib_reap_neigh(struct ipoib_dev_priv *priv)\n \t\tgoto out_unlock;\n \n \t/* neigh is obsolete if it was idle for two GC periods */\n-\tdt = 2 * arp_tbl.gc_interval;\n+\tdt = 2 * gc_interval;\n \tneigh_obsolete = jiffies - dt;\n \n \tfor (i = 0; i \u003c htbl-\u003esize; i++) {\n@@ -1433,11 +1434,16 @@ static void ipoib_reap_neigh(struct work_struct *work)\n {\n \tstruct ipoib_dev_priv *priv =\n \t\tcontainer_of(work, struct ipoib_dev_priv, neigh_reap_task.work);\n+\tstruct net_device *dev = priv-\u003edev;\n+\tstruct net *net = dev_net(dev);\n+\tstruct neigh_table *tbl;\n+\tint gc_interval;\n \n-\t__ipoib_reap_neigh(priv);\n+\ttbl = arp_table(net);\n+\tgc_interval = tbl-\u003egc_interval;\n+\t__ipoib_reap_neigh(priv, gc_interval);\n \n-\tqueue_delayed_work(priv-\u003ewq, \u0026priv-\u003eneigh_reap_task,\n-\t\t\t   arp_tbl.gc_interval);\n+\tqueue_delayed_work(priv-\u003ewq, \u0026priv-\u003eneigh_reap_task, gc_interval);\n }\n \n \n@@ -1590,8 +1596,11 @@ void ipoib_neigh_free(struct ipoib_neigh *neigh)\n static int ipoib_neigh_hash_init(struct ipoib_dev_priv *priv)\n {\n \tstruct ipoib_neigh_table *ntbl = \u0026priv-\u003entbl;\n-\tstruct ipoib_neigh_hash *htbl;\n \tstruct ipoib_neigh __rcu **buckets;\n+\tstruct net_device *dev = priv-\u003edev;\n+\tstruct net *net = dev_net(dev);\n+\tstruct ipoib_neigh_hash *htbl;\n+\tstruct neigh_table *tbl;\n \tu32 size;\n \n \tclear_bit(IPOIB_NEIGH_TBL_FLUSH, \u0026priv-\u003eflags);\n@@ -1599,7 +1608,9 @@ static int ipoib_neigh_hash_init(struct ipoib_dev_priv *priv)\n \thtbl = kzalloc_obj(*htbl);\n \tif (!htbl)\n \t\treturn -ENOMEM;\n-\tsize = roundup_pow_of_two(arp_tbl.gc_thresh3);\n+\n+\ttbl = arp_table(net);\n+\tsize = roundup_pow_of_two(tbl-\u003egc_thresh3);\n \tbuckets = kvzalloc_objs(*buckets, size);\n \tif (!buckets) {\n \t\tkfree(htbl);\n@@ -1614,7 +1625,7 @@ static int ipoib_neigh_hash_init(struct ipoib_dev_priv *priv)\n \n \t/* start garbage collection */\n \tqueue_delayed_work(priv-\u003ewq, \u0026priv-\u003eneigh_reap_task,\n-\t\t\t   arp_tbl.gc_interval);\n+\t\t\t   tbl-\u003egc_interval);\n \n \treturn 0;\n }\ndiff --git a/drivers/net/ethernet/marvell/prestera/prestera_router.c b/drivers/net/ethernet/marvell/prestera/prestera_router.c\nindex 0c4f462baa6ef..ba45b61b09bb0 100644\n--- a/drivers/net/ethernet/marvell/prestera/prestera_router.c\n+++ b/drivers/net/ethernet/marvell/prestera/prestera_router.c\n@@ -683,7 +683,7 @@ __prestera_k_arb_n_offload_set(struct prestera_switch *sw,\n {\n \tstruct neighbour *n;\n \n-\tn = neigh_lookup(\u0026arp_tbl, \u0026nc-\u003ekey.addr.u.ipv4,\n+\tn = neigh_lookup(arp_table(\u0026init_net), \u0026nc-\u003ekey.addr.u.ipv4,\n \t\t\t nc-\u003ekey.dev);\n \tif (!n)\n \t\treturn;\n@@ -790,7 +790,7 @@ __prestera_k_arb_nc_kern_n_fetch(struct prestera_switch *sw,\n \tint err;\n \n \tmemset(\u0026nc-\u003enh_neigh_info, 0, sizeof(nc-\u003enh_neigh_info));\n-\tn = neigh_lookup(\u0026arp_tbl, \u0026nc-\u003ekey.addr.u.ipv4, nc-\u003ekey.dev);\n+\tn = neigh_lookup(arp_table(\u0026init_net), \u0026nc-\u003ekey.addr.u.ipv4, nc-\u003ekey.dev);\n \tif (!n)\n \t\tgoto out;\n \n@@ -1052,10 +1052,12 @@ static void __prestera_k_arb_hw_state_upd(struct prestera_switch *sw,\n #endif /* PRESTERA_IMPLICITY_RESOLVE_DEAD_NEIGH */\n \n \tif (nc-\u003ekey.addr.v == PRESTERA_IPV4) {\n-\t\tn = neigh_lookup(\u0026arp_tbl, \u0026nc-\u003ekey.addr.u.ipv4,\n+\t\tstruct neigh_table *tbl = arp_table(\u0026init_net);\n+\n+\t\tn = neigh_lookup(tbl, \u0026nc-\u003ekey.addr.u.ipv4,\n \t\t\t\t nc-\u003ekey.dev);\n \t\tif (!n)\n-\t\t\tn = neigh_create(\u0026arp_tbl, \u0026nc-\u003ekey.addr.u.ipv4,\n+\t\t\tn = neigh_create(tbl, \u0026nc-\u003ekey.addr.u.ipv4,\n \t\t\t\t\t nc-\u003ekey.dev);\n \t} else {\n \t\tn = NULL;\ndiff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c b/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c\nindex 648f4521c0964..6a8d8aac7d16f 100644\n--- a/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c\n+++ b/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c\n@@ -17,20 +17,27 @@\n #include \"fs_core.h\"\n #include \"diag/en_rep_tracepoint.h\"\n \n-static unsigned long mlx5e_rep_ipv6_interval(void)\n+static unsigned long mlx5e_rep_ipv6_interval(struct net *net)\n {\n \tif (IS_ENABLED(CONFIG_IPV6) \u0026\u0026 ipv6_mod_enabled())\n-\t\treturn NEIGH_VAR(\u0026nd_tbl.parms, DELAY_PROBE_TIME);\n+\t\treturn NEIGH_VAR(\u0026nd_table(net)-\u003eparms, DELAY_PROBE_TIME);\n \n \treturn ~0UL;\n }\n \n static void mlx5e_rep_neigh_update_init_interval(struct mlx5e_rep_priv *rpriv)\n {\n-\tunsigned long ipv4_interval = NEIGH_VAR(\u0026arp_tbl.parms, DELAY_PROBE_TIME);\n-\tunsigned long ipv6_interval = mlx5e_rep_ipv6_interval();\n \tstruct net_device *netdev = rpriv-\u003enetdev;\n-\tstruct mlx5e_priv *priv = netdev_priv(netdev);\n+\tstruct net *net = dev_net(netdev);\n+\tunsigned long ipv4_interval;\n+\tunsigned long ipv6_interval;\n+\tstruct neigh_table *tbl;\n+\tstruct mlx5e_priv *priv;\n+\n+\tpriv = netdev_priv(netdev);\n+\ttbl = arp_table(net);\n+\tipv4_interval = NEIGH_VAR(\u0026tbl-\u003eparms, DELAY_PROBE_TIME);\n+\tipv6_interval = mlx5e_rep_ipv6_interval(net);\n \n \trpriv-\u003eneigh_update.min_interval = min_t(unsigned long, ipv6_interval, ipv4_interval);\n \tmlx5_fc_update_sampling_interval(priv-\u003emdev, rpriv-\u003eneigh_update.min_interval);\n@@ -217,12 +224,6 @@ static int mlx5e_rep_netevent_event(struct notifier_block *nb,\n \tswitch (event) {\n \tcase NETEVENT_NEIGH_UPDATE:\n \t\tn = ptr;\n-#if IS_ENABLED(CONFIG_IPV6)\n-\t\tif (n-\u003etbl != \u0026nd_tbl \u0026\u0026 n-\u003etbl != \u0026arp_tbl)\n-#else\n-\t\tif (n-\u003etbl != \u0026arp_tbl)\n-#endif\n-\t\t\treturn NOTIFY_DONE;\n \n \t\tupdate_work = mlx5e_alloc_neigh_update_work(priv, n);\n \t\tif (!update_work)\n@@ -238,11 +239,7 @@ static int mlx5e_rep_netevent_event(struct notifier_block *nb,\n \t\t * changes in the default table, we only care about changes\n \t\t * done per device delay prob time parameter.\n \t\t */\n-#if IS_ENABLED(CONFIG_IPV6)\n-\t\tif (!p-\u003edev || (p-\u003etbl != \u0026nd_tbl \u0026\u0026 p-\u003etbl != \u0026arp_tbl))\n-#else\n-\t\tif (!p-\u003edev || p-\u003etbl != \u0026arp_tbl)\n-#endif\n+\t\tif (!p-\u003edev)\n \t\t\treturn NOTIFY_DONE;\n \n \t\trcu_read_lock();\ndiff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c\nindex 8b827201935eb..67c12ca19d59d 100644\n--- a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c\n+++ b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c\n@@ -393,20 +393,10 @@ void mlx5e_tc_update_neigh_used_value(struct mlx5e_neigh_hash_entry *nhe)\n \tstruct mlx5e_encap_entry *e = NULL;\n \tstruct mlx5e_tc_flow *flow;\n \tstruct mlx5_fc *counter;\n-\tstruct neigh_table *tbl;\n \tbool neigh_used = false;\n \tstruct neighbour *n;\n \tu64 lastuse;\n \n-\tif (m_neigh-\u003efamily == AF_INET)\n-\t\ttbl = \u0026arp_tbl;\n-#if IS_ENABLED(CONFIG_IPV6)\n-\telse if (m_neigh-\u003efamily == AF_INET6)\n-\t\ttbl = \u0026nd_tbl;\n-#endif\n-\telse\n-\t\treturn;\n-\n \t/* mlx5e_get_next_valid_encap() releases previous encap before returning\n \t * next one.\n \t */\n@@ -447,12 +437,23 @@ void mlx5e_tc_update_neigh_used_value(struct mlx5e_neigh_hash_entry *nhe)\n \ttrace_mlx5e_tc_update_neigh_used_value(nhe, neigh_used);\n \n \tif (neigh_used) {\n+\t\tstruct net_device *dev = READ_ONCE(nhe-\u003eneigh_dev);\n+\t\tstruct net *net = dev_net(dev);\n+\t\tstruct neigh_table *tbl;\n+\n \t\tnhe-\u003ereported_lastuse = jiffies;\n \n+#if IS_ENABLED(CONFIG_IPV6)\n+\t\tif (m_neigh-\u003efamily != AF_INET)\n+\t\t\ttbl = nd_table(net);\n+\t\telse\n+#endif\n+\t\t\ttbl = arp_table(net);\n+\n \t\t/* find the relevant neigh according to the cached device and\n \t\t * dst ip pair\n \t\t */\n-\t\tn = neigh_lookup(tbl, \u0026m_neigh-\u003edst_ip, READ_ONCE(nhe-\u003eneigh_dev));\n+\t\tn = neigh_lookup(tbl, \u0026m_neigh-\u003edst_ip, dev);\n \t\tif (!n)\n \t\t\treturn;\n \ndiff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c\nindex db260e3d1412f..37a8ddee3ea1e 100644\n--- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c\n+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c\n@@ -262,6 +262,7 @@ static void mlx5e_ipsec_init_macs(struct mlx5e_ipsec_sa_entry *sa_entry,\n \tstruct net_device *netdev = sa_entry-\u003edev;\n \tstruct xfrm_state *x = sa_entry-\u003ex;\n \tstruct dst_entry *rt_dst_entry;\n+\tstruct neigh_table *tbl;\n \tstruct flowi4 fl4 = {};\n \tstruct flowi6 fl6 = {};\n \tstruct neighbour *n;\n@@ -364,9 +365,10 @@ static void mlx5e_ipsec_init_macs(struct mlx5e_ipsec_sa_entry *sa_entry,\n \treturn;\n \n neigh:\n-\tn = neigh_lookup(\u0026arp_tbl, pkey, netdev);\n+\ttbl = arp_table(dev_net(netdev));\n+\tn = neigh_lookup(tbl, pkey, netdev);\n \tif (!n) {\n-\t\tn = neigh_create(\u0026arp_tbl, pkey, netdev);\n+\t\tn = neigh_create(tbl, pkey, netdev);\n \t\tif (IS_ERR(n))\n \t\t\treturn;\n \t\tneigh_event_send(n, NULL);\ndiff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c\nindex 3d6fdbab05e01..f4a435885ba43 100644\n--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c\n+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c\n@@ -2397,14 +2397,15 @@ mlxsw_sp_neigh_entry_lookup(struct mlxsw_sp *mlxsw_sp, struct neighbour *n)\n static void\n mlxsw_sp_router_neighs_update_interval_init(struct mlxsw_sp *mlxsw_sp)\n {\n+\tstruct net *net = mlxsw_sp_net(mlxsw_sp);\n \tunsigned long interval;\n \n #if IS_ENABLED(CONFIG_IPV6)\n \tinterval = min_t(unsigned long,\n-\t\t\t NEIGH_VAR(\u0026arp_tbl.parms, DELAY_PROBE_TIME),\n-\t\t\t NEIGH_VAR(\u0026nd_tbl.parms, DELAY_PROBE_TIME));\n+\t\t\t NEIGH_VAR(\u0026arp_table(net)-\u003eparms, DELAY_PROBE_TIME),\n+\t\t\t NEIGH_VAR(\u0026nd_table(net)-\u003eparms, DELAY_PROBE_TIME));\n #else\n-\tinterval = NEIGH_VAR(\u0026arp_tbl.parms, DELAY_PROBE_TIME);\n+\tinterval = NEIGH_VAR(\u0026arp_table(net)-\u003eparms, DELAY_PROBE_TIME);\n #endif\n \tmlxsw_sp-\u003erouter-\u003eneighs_update.interval = jiffies_to_msecs(interval);\n }\n@@ -2414,6 +2415,8 @@ static void mlxsw_sp_router_neigh_ent_ipv4_process(struct mlxsw_sp *mlxsw_sp,\n \t\t\t\t\t\t   int ent_index)\n {\n \tu64 max_rifs = MLXSW_CORE_RES_GET(mlxsw_sp-\u003ecore, MAX_RIFS);\n+\tstruct net *net = mlxsw_sp_net(mlxsw_sp);\n+\tstruct neigh_table *tbl;\n \tstruct net_device *dev;\n \tstruct neighbour *n;\n \t__be32 dipn;\n@@ -2429,9 +2432,10 @@ static void mlxsw_sp_router_neigh_ent_ipv4_process(struct mlxsw_sp *mlxsw_sp,\n \t\treturn;\n \t}\n \n+\ttbl = arp_table(net);\n \tdipn = htonl(dip);\n \tdev = mlxsw_sp_rif_dev(mlxsw_sp-\u003erouter-\u003erifs[rif]);\n-\tn = neigh_lookup(\u0026arp_tbl, \u0026dipn, dev);\n+\tn = neigh_lookup(tbl, \u0026dipn, dev);\n \tif (!n)\n \t\treturn;\n \n@@ -2445,6 +2449,8 @@ static void mlxsw_sp_router_neigh_ent_ipv6_process(struct mlxsw_sp *mlxsw_sp,\n \t\t\t\t\t\t   char *rauhtd_pl,\n \t\t\t\t\t\t   int rec_index)\n {\n+\tstruct net *net = mlxsw_sp_net(mlxsw_sp);\n+\tstruct neigh_table *tbl;\n \tstruct net_device *dev;\n \tstruct neighbour *n;\n \tstruct in6_addr dip;\n@@ -2458,8 +2464,9 @@ static void mlxsw_sp_router_neigh_ent_ipv6_process(struct mlxsw_sp *mlxsw_sp,\n \t\treturn;\n \t}\n \n+\ttbl = nd_table(net);\n \tdev = mlxsw_sp_rif_dev(mlxsw_sp-\u003erouter-\u003erifs[rif]);\n-\tn = neigh_lookup(\u0026nd_tbl, \u0026dip, dev);\n+\tn = neigh_lookup(tbl, \u0026dip, dev);\n \tif (!n)\n \t\treturn;\n \n@@ -3014,16 +3021,17 @@ static int mlxsw_sp_neigh_rif_made_sync(struct mlxsw_sp *mlxsw_sp,\n \t\t.mlxsw_sp = mlxsw_sp,\n \t\t.rif = rif,\n \t};\n+\tstruct net *net = mlxsw_sp_net(mlxsw_sp);\n \n \tif (!mlxsw_sp_dev_lower_is_port(mlxsw_sp_rif_dev(rif)))\n \t\treturn 0;\n \n-\tneigh_for_each(\u0026arp_tbl, mlxsw_sp_neigh_rif_made_sync_each, \u0026rms);\n+\tneigh_for_each(arp_table(net), mlxsw_sp_neigh_rif_made_sync_each, \u0026rms);\n \tif (rms.err)\n \t\tgoto err_arp;\n \n #if IS_ENABLED(CONFIG_IPV6)\n-\tneigh_for_each(\u0026nd_tbl, mlxsw_sp_neigh_rif_made_sync_each, \u0026rms);\n+\tneigh_for_each(nd_table(net), mlxsw_sp_neigh_rif_made_sync_each, \u0026rms);\n #endif\n \tif (rms.err)\n \t\tgoto err_nd;\n@@ -4622,7 +4630,7 @@ static int mlxsw_sp_nexthop4_init(struct mlxsw_sp *mlxsw_sp,\n \tnh-\u003enh_weight = 1;\n #endif\n \tmemcpy(\u0026nh-\u003egw_addr, \u0026fib_nh-\u003efib_nh_gw4, sizeof(fib_nh-\u003efib_nh_gw4));\n-\tnh-\u003eneigh_tbl = \u0026arp_tbl;\n+\tnh-\u003eneigh_tbl = arp_table(mlxsw_sp_net(mlxsw_sp));\n \terr = mlxsw_sp_nexthop_insert(mlxsw_sp, nh);\n \tif (err)\n \t\treturn err;\n@@ -5112,6 +5120,7 @@ mlxsw_sp_nexthop_obj_init(struct mlxsw_sp *mlxsw_sp,\n \t\t\t  struct nh_notifier_single_info *nh_obj, int weight)\n {\n \tstruct net_device *dev = nh_obj-\u003edev;\n+\tstruct net *net = dev_net(dev);\n \tint err;\n \n \tnh-\u003enhgi = nh_grp-\u003enhgi;\n@@ -5120,12 +5129,12 @@ mlxsw_sp_nexthop_obj_init(struct mlxsw_sp *mlxsw_sp,\n \tswitch (nh_obj-\u003egw_family) {\n \tcase AF_INET:\n \t\tmemcpy(\u0026nh-\u003egw_addr, \u0026nh_obj-\u003eipv4, sizeof(nh_obj-\u003eipv4));\n-\t\tnh-\u003eneigh_tbl = \u0026arp_tbl;\n+\t\tnh-\u003eneigh_tbl = arp_table(net);\n \t\tbreak;\n \tcase AF_INET6:\n \t\tmemcpy(\u0026nh-\u003egw_addr, \u0026nh_obj-\u003eipv6, sizeof(nh_obj-\u003eipv6));\n #if IS_ENABLED(CONFIG_IPV6)\n-\t\tnh-\u003eneigh_tbl = \u0026nd_tbl;\n+\t\tnh-\u003eneigh_tbl = nd_table(net);\n #endif\n \t\tbreak;\n \t}\n@@ -6981,7 +6990,7 @@ static int mlxsw_sp_nexthop6_init(struct mlxsw_sp *mlxsw_sp,\n \tnh-\u003enh_weight = rt-\u003efib6_nh-\u003efib_nh_weight;\n \tmemcpy(\u0026nh-\u003egw_addr, \u0026rt-\u003efib6_nh-\u003efib_nh_gw6, sizeof(nh-\u003egw_addr));\n #if IS_ENABLED(CONFIG_IPV6)\n-\tnh-\u003eneigh_tbl = \u0026nd_tbl;\n+\tnh-\u003eneigh_tbl = nd_table(mlxsw_sp_net(mlxsw_sp));\n #endif\n \n \terr = mlxsw_sp_nexthop_counter_enable(mlxsw_sp, nh);\ndiff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c\nindex ae63d549b5429..1cd8347c274d4 100644\n--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c\n+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c\n@@ -456,6 +456,7 @@ mlxsw_sp_span_entry_gretap4_parms(struct mlxsw_sp *mlxsw_sp,\n \tbool inherit_tos = tparm.iph.tos \u0026 0x1;\n \tbool inherit_ttl = !tparm.iph.ttl;\n \tunion mlxsw_sp_l3addr gw = daddr;\n+\tstruct neigh_table *tbl = NULL;\n \tstruct net_device *l3edev;\n \n \tif (!(to_dev-\u003eflags \u0026 IFF_UP) ||\n@@ -469,9 +470,11 @@ mlxsw_sp_span_entry_gretap4_parms(struct mlxsw_sp *mlxsw_sp,\n \t\treturn mlxsw_sp_span_entry_unoffloadable(sparmsp);\n \n \tl3edev = mlxsw_sp_span_gretap4_route(to_dev, \u0026saddr.addr4, \u0026gw.addr4);\n+\tif (l3edev)\n+\t\ttbl = arp_table(dev_net(l3edev));\n \treturn mlxsw_sp_span_entry_tunnel_parms_common(l3edev, saddr, daddr, gw,\n \t\t\t\t\t\t       tparm.iph.ttl,\n-\t\t\t\t\t\t       \u0026arp_tbl, sparmsp);\n+\t\t\t\t\t\t       tbl, sparmsp);\n }\n \n static int\n@@ -561,6 +564,7 @@ mlxsw_sp_span_entry_gretap6_parms(struct mlxsw_sp *mlxsw_sp,\n \tunion mlxsw_sp_l3addr daddr = { .addr6 = tparm.raddr };\n \tbool inherit_ttl = !tparm.hop_limit;\n \tunion mlxsw_sp_l3addr gw = daddr;\n+\tstruct neigh_table *tbl = NULL;\n \tstruct net_device *l3edev;\n \n \tif (!(to_dev-\u003eflags \u0026 IFF_UP) ||\n@@ -574,9 +578,11 @@ mlxsw_sp_span_entry_gretap6_parms(struct mlxsw_sp *mlxsw_sp,\n \t\treturn mlxsw_sp_span_entry_unoffloadable(sparmsp);\n \n \tl3edev = mlxsw_sp_span_gretap6_route(to_dev, \u0026saddr.addr6, \u0026gw.addr6);\n+\tif (l3edev)\n+\t\ttbl = nd_table(dev_net(l3edev));\n \treturn mlxsw_sp_span_entry_tunnel_parms_common(l3edev, saddr, daddr, gw,\n \t\t\t\t\t\t       tparm.hop_limit,\n-\t\t\t\t\t\t       \u0026nd_tbl, sparmsp);\n+\t\t\t\t\t\t       tbl, sparmsp);\n }\n \n static int\ndiff --git a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c\nindex ca30702f88780..d650d33e3709c 100644\n--- a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c\n+++ b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c\n@@ -209,6 +209,7 @@ void nfp_tunnel_keep_alive(struct nfp_app *app, struct sk_buff *skb)\n {\n \tstruct nfp_tun_active_tuns *payload;\n \tstruct net_device *netdev;\n+\tstruct neigh_table *tbl;\n \tint count, i, pay_len;\n \tstruct neighbour *n;\n \t__be32 ipv4_addr;\n@@ -235,7 +236,8 @@ void nfp_tunnel_keep_alive(struct nfp_app *app, struct sk_buff *skb)\n \t\tif (!netdev)\n \t\t\tcontinue;\n \n-\t\tn = neigh_lookup(\u0026arp_tbl, \u0026ipv4_addr, netdev);\n+\t\ttbl = arp_table(dev_net(netdev));\n+\t\tn = neigh_lookup(tbl, \u0026ipv4_addr, netdev);\n \t\tif (!n)\n \t\t\tcontinue;\n \n@@ -251,6 +253,7 @@ void nfp_tunnel_keep_alive_v6(struct nfp_app *app, struct sk_buff *skb)\n #if IS_ENABLED(CONFIG_IPV6)\n \tstruct nfp_tun_active_tuns_v6 *payload;\n \tstruct net_device *netdev;\n+\tstruct neigh_table *tbl;\n \tint count, i, pay_len;\n \tstruct neighbour *n;\n \tvoid *ipv6_add;\n@@ -277,7 +280,8 @@ void nfp_tunnel_keep_alive_v6(struct nfp_app *app, struct sk_buff *skb)\n \t\tif (!netdev)\n \t\t\tcontinue;\n \n-\t\tn = neigh_lookup(\u0026nd_tbl, ipv6_add, netdev);\n+\t\ttbl = nd_table(dev_net(netdev));\n+\t\tn = neigh_lookup(tbl, ipv6_add, netdev);\n \t\tif (!n)\n \t\t\tcontinue;\n \n@@ -729,12 +733,6 @@ nfp_tun_neigh_event_handler(struct notifier_block *nb, unsigned long event,\n \tdefault:\n \t\treturn NOTIFY_DONE;\n \t}\n-#if IS_ENABLED(CONFIG_IPV6)\n-\tif (n-\u003etbl != \u0026nd_tbl \u0026\u0026 n-\u003etbl != \u0026arp_tbl)\n-#else\n-\tif (n-\u003etbl != \u0026arp_tbl)\n-#endif\n-\t\treturn NOTIFY_DONE;\n \n \tapp_priv = container_of(nb, struct nfp_flower_priv, tun.neigh_nb);\n \tapp = app_priv-\u003eapp;\ndiff --git a/drivers/net/ethernet/rocker/rocker_main.c b/drivers/net/ethernet/rocker/rocker_main.c\nindex 84a55f2b48ffe..0fcd65fc0af7f 100644\n--- a/drivers/net/ethernet/rocker/rocker_main.c\n+++ b/drivers/net/ethernet/rocker/rocker_main.c\n@@ -3134,7 +3134,7 @@ static int rocker_netevent_event(struct notifier_block *unused,\n \n \tswitch (event) {\n \tcase NETEVENT_NEIGH_UPDATE:\n-\t\tif (n-\u003etbl != \u0026arp_tbl)\n+\t\tif (n-\u003etbl != arp_table(\u0026init_net))\n \t\t\treturn NOTIFY_DONE;\n \t\tdev = n-\u003edev;\n \t\tif (!rocker_port_dev_check(dev))\ndiff --git a/drivers/net/ethernet/rocker/rocker_ofdpa.c b/drivers/net/ethernet/rocker/rocker_ofdpa.c\nindex 15d19a8a17101..ead8b447b89c4 100644\n--- a/drivers/net/ethernet/rocker/rocker_ofdpa.c\n+++ b/drivers/net/ethernet/rocker/rocker_ofdpa.c\n@@ -1336,7 +1336,7 @@ static int ofdpa_port_ipv4_resolve(struct ofdpa_port *ofdpa_port,\n \tint err = 0;\n \n \tif (!n) {\n-\t\tn = neigh_create(\u0026arp_tbl, \u0026ip_addr, dev);\n+\t\tn = neigh_create(arp_table(\u0026init_net), \u0026ip_addr, dev);\n \t\tif (IS_ERR(n))\n \t\t\treturn PTR_ERR(n);\n \t}\ndiff --git a/drivers/net/ethernet/sfc/tc_counters.c b/drivers/net/ethernet/sfc/tc_counters.c\nindex b84235e93ffe9..793a562fc1f7c 100644\n--- a/drivers/net/ethernet/sfc/tc_counters.c\n+++ b/drivers/net/ethernet/sfc/tc_counters.c\n@@ -91,6 +91,7 @@ static void efx_tc_counter_work(struct work_struct *work)\n \tstruct efx_tc_action_set *act;\n \tunsigned long touched;\n \tstruct neighbour *n;\n+\tstruct net *net;\n \n \tspin_lock_bh(\u0026cnt-\u003elock);\n \ttouched = READ_ONCE(cnt-\u003etouched);\n@@ -103,16 +104,19 @@ static void efx_tc_counter_work(struct work_struct *work)\n \t\t\tcontinue;\n \t\tif (time_after_eq(encap-\u003eneigh-\u003eused, touched))\n \t\t\tcontinue;\n+\n \t\tencap-\u003eneigh-\u003eused = touched;\n+\t\tnet = encap-\u003eneigh-\u003enet;\n+\n \t\t/* We have passed traffic using this ARP entry, so\n \t\t * indicate to the ARP cache that it's still active\n \t\t */\n \t\tif (encap-\u003eneigh-\u003edst_ip)\n-\t\t\tn = neigh_lookup(\u0026arp_tbl, \u0026encap-\u003eneigh-\u003edst_ip,\n+\t\t\tn = neigh_lookup(arp_table(net), \u0026encap-\u003eneigh-\u003edst_ip,\n \t\t\t\t\t encap-\u003eneigh-\u003eegdev);\n \t\telse\n #if IS_ENABLED(CONFIG_IPV6)\n-\t\t\tn = neigh_lookup(\u0026nd_tbl,\n+\t\t\tn = neigh_lookup(nd_table(net),\n \t\t\t\t\t \u0026encap-\u003eneigh-\u003edst_ip6,\n \t\t\t\t\t encap-\u003eneigh-\u003eegdev);\n #else\ndiff --git a/drivers/net/ethernet/sfc/tc_encap_actions.c b/drivers/net/ethernet/sfc/tc_encap_actions.c\nindex c2ad3a358d201..152c4639ecc6f 100644\n--- a/drivers/net/ethernet/sfc/tc_encap_actions.c\n+++ b/drivers/net/ethernet/sfc/tc_encap_actions.c\n@@ -527,10 +527,10 @@ static int efx_neigh_event(struct efx_nic *efx, struct neighbour *n)\n \tif (WARN_ON(!efx-\u003etc))\n \t\treturn NOTIFY_DONE;\n \n-\tif (n-\u003etbl == \u0026arp_tbl) {\n+\tif (n-\u003etbl-\u003efamily == AF_INET) {\n \t\tkeysize = sizeof(keys.dst_ip);\n #if IS_ENABLED(CONFIG_IPV6)\n-\t} else if (n-\u003etbl == \u0026nd_tbl) {\n+\t} else if (n-\u003etbl-\u003efamily == AF_INET6) {\n \t\tipv6 = true;\n \t\tkeysize = sizeof(keys.dst_ip6);\n #endif\ndiff --git a/drivers/net/vrf.c b/drivers/net/vrf.c\nindex a0557a3a70260..50ec04cc0213f 100644\n--- a/drivers/net/vrf.c\n+++ b/drivers/net/vrf.c\n@@ -616,7 +616,7 @@ static int vrf_finish_output6(struct net *net, struct sock *sk,\n \tnexthop = rt6_nexthop(dst_rt6_info(dst), \u0026ipv6_hdr(skb)-\u003edaddr);\n \tneigh = __ipv6_neigh_lookup_noref(dst-\u003edev, nexthop);\n \tif (unlikely(!neigh))\n-\t\tneigh = __neigh_create(\u0026nd_tbl, nexthop, dst-\u003edev, false);\n+\t\tneigh = __neigh_create(nd_table(net), nexthop, dst-\u003edev, false);\n \tif (!IS_ERR(neigh)) {\n \t\tsock_confirm_neigh(skb, neigh);\n \t\tret = neigh_output(neigh, skb, false);\ndiff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c\nindex fd5505153d0b6..345d1883d993e 100644\n--- a/drivers/net/vxlan/vxlan_core.c\n+++ b/drivers/net/vxlan/vxlan_core.c\n@@ -1841,11 +1841,12 @@ static int vxlan_err_lookup(struct sock *sk, struct sk_buff *skb)\n \n static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)\n {\n+\tstruct neigh_table *tbl = arp_table(dev_net(dev));\n \tstruct vxlan_dev *vxlan = netdev_priv(dev);\n+\tstruct neighbour *n;\n \tstruct arphdr *parp;\n \tu8 *arpptr, *sha;\n \t__be32 sip, tip;\n-\tstruct neighbour *n;\n \n \tif (dev-\u003eflags \u0026 IFF_NOARP)\n \t\tgoto out;\n@@ -1877,7 +1878,7 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)\n \t    ipv4_is_multicast(tip))\n \t\tgoto out;\n \n-\tn = neigh_lookup(\u0026arp_tbl, \u0026tip, dev);\n+\tn = neigh_lookup(tbl, \u0026tip, dev);\n \n \tif (n) {\n \t\tstruct vxlan_rdst *rdst = NULL;\n@@ -2047,7 +2048,7 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)\n \t    ipv6_addr_is_multicast(\u0026msg-\u003etarget))\n \t\tgoto out;\n \n-\tn = neigh_lookup(\u0026nd_tbl, \u0026msg-\u003etarget, dev);\n+\tn = neigh_lookup(nd_table(dev_net(dev)), \u0026msg-\u003etarget, dev);\n \n \tif (n) {\n \t\tstruct vxlan_rdst *rdst = NULL;\n@@ -2100,6 +2101,7 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)\n static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)\n {\n \tstruct vxlan_dev *vxlan = netdev_priv(dev);\n+\tstruct neigh_table *tbl;\n \tstruct neighbour *n;\n \n \tif (is_multicast_ether_addr(eth_hdr(skb)-\u003eh_dest))\n@@ -2113,8 +2115,10 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)\n \n \t\tif (!pskb_network_may_pull(skb, sizeof(struct iphdr)))\n \t\t\treturn false;\n+\n+\t\ttbl = arp_table(dev_net(dev));\n \t\tpip = ip_hdr(skb);\n-\t\tn = neigh_lookup(\u0026arp_tbl, \u0026pip-\u003edaddr, dev);\n+\t\tn = neigh_lookup(tbl, \u0026pip-\u003edaddr, dev);\n \t\tif (!n \u0026\u0026 (vxlan-\u003ecfg.flags \u0026 VXLAN_F_L3MISS)) {\n \t\t\tunion vxlan_addr ipa = {\n \t\t\t\t.sin.sin_addr.s_addr = pip-\u003edaddr,\n@@ -2139,8 +2143,10 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)\n \t\t\treturn false;\n \t\tif (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr)))\n \t\t\treturn false;\n+\n+\t\ttbl = nd_table(dev_net(dev));\n \t\tpip6 = ipv6_hdr(skb);\n-\t\tn = neigh_lookup(\u0026nd_tbl, \u0026pip6-\u003edaddr, dev);\n+\t\tn = neigh_lookup(tbl, \u0026pip6-\u003edaddr, dev);\n \t\tif (!n \u0026\u0026 (vxlan-\u003ecfg.flags \u0026 VXLAN_F_L3MISS)) {\n \t\t\tunion vxlan_addr ipa = {\n \t\t\t\t.sin6.sin6_addr = pip6-\u003edaddr,\ndiff --git a/include/net/arp.h b/include/net/arp.h\nindex e8747e0713c79..e932def63d62f 100644\n--- a/include/net/arp.h\n+++ b/include/net/arp.h\n@@ -7,8 +7,10 @@\n #include \u003clinux/hash.h\u003e\n #include \u003cnet/neighbour.h\u003e\n \n-\n-extern struct neigh_table arp_tbl;\n+static inline struct neigh_table *arp_table(struct net *net)\n+{\n+\treturn net-\u003eneigh_tables[NEIGH_ARP_TABLE];\n+}\n \n static inline u32 arp_hashfn(const void *pkey, const struct net_device *dev, u32 *hash_rnd)\n {\n@@ -21,10 +23,12 @@ static inline u32 arp_hashfn(const void *pkey, const struct net_device *dev, u32\n #ifdef CONFIG_INET\n static inline struct neighbour *__ipv4_neigh_lookup_noref(struct net_device *dev, u32 key)\n {\n+\tstruct neigh_table *tbl = arp_table(dev_net(dev));\n+\n \tif (dev-\u003eflags \u0026 (IFF_LOOPBACK | IFF_POINTOPOINT))\n \t\tkey = INADDR_ANY;\n \n-\treturn ___neigh_lookup_noref(\u0026arp_tbl, neigh_key_eq32, arp_hashfn, \u0026key, dev);\n+\treturn ___neigh_lookup_noref(tbl, neigh_key_eq32, arp_hashfn, \u0026key, dev);\n }\n #else\n static inline\ndiff --git a/include/net/ndisc.h b/include/net/ndisc.h\nindex 9e5379ad2d8e0..96e3bb6e83afd 100644\n--- a/include/net/ndisc.h\n+++ b/include/net/ndisc.h\n@@ -67,6 +67,15 @@ struct prefix_info;\n \n extern struct neigh_table nd_tbl;\n \n+static inline struct neigh_table *nd_table(struct net *net)\n+{\n+#if IS_ENABLED(CONFIG_IPV6)\n+\tif (disable_ipv6_mod)\n+\t\treturn \u0026nd_tbl;\n+#endif\n+\treturn net-\u003eneigh_tables[NEIGH_ND_TABLE];\n+}\n+\n struct nd_msg {\n         struct icmp6hdr\ticmph;\n         struct in6_addr\ttarget;\n@@ -354,7 +363,9 @@ static inline u32 ndisc_hashfn(const void *pkey, const struct net_device *dev, _\n \n static inline struct neighbour *__ipv6_neigh_lookup_noref(struct net_device *dev, const void *pkey)\n {\n-\treturn ___neigh_lookup_noref(\u0026nd_tbl, neigh_key_eq128, ndisc_hashfn, pkey, dev);\n+\tstruct neigh_table *tbl = nd_table(dev_net(dev));\n+\n+\treturn ___neigh_lookup_noref(tbl, neigh_key_eq128, ndisc_hashfn, pkey, dev);\n }\n \n static inline struct neighbour *__ipv6_neigh_lookup(struct net_device *dev, const void *pkey)\n@@ -388,8 +399,11 @@ static inline struct neighbour *ip_neigh_gw6(struct net_device *dev,\n \tstruct neighbour *neigh;\n \n \tneigh = __ipv6_neigh_lookup_noref(dev, addr);\n-\tif (unlikely(!neigh))\n-\t\tneigh = __neigh_create(\u0026nd_tbl, addr, dev, false);\n+\tif (unlikely(!neigh)) {\n+\t\tstruct neigh_table *tbl = nd_table(dev_net(dev));\n+\n+\t\tneigh = __neigh_create(tbl, addr, dev, false);\n+\t}\n \n \treturn neigh;\n #else\ndiff --git a/include/net/neighbour.h b/include/net/neighbour.h\nindex 8860cc2175fc1..ed9d9ae6d3a65 100644\n--- a/include/net/neighbour.h\n+++ b/include/net/neighbour.h\n@@ -31,6 +31,8 @@\n #include \u003cnet/rtnetlink.h\u003e\n #include \u003cnet/neighbour_tables.h\u003e\n \n+extern int sysctl_neigh_inherit_init_net;\n+\n /*\n  * NUD stands for \"neighbor unreachability detection\"\n  */\n@@ -179,7 +181,6 @@ struct neigh_ops {\n \n struct pneigh_entry {\n \tstruct pneigh_entry\t__rcu *next;\n-\tpossible_net_t\t\tnet;\n \tstruct net_device\t*dev;\n \tnetdevice_tracker\tdev_tracker;\n \tunion {\n@@ -234,7 +235,7 @@ struct neigh_table {\n \tstruct delayed_work\tmanaged_work;\n \tstruct timer_list \tproxy_timer;\n \tstruct sk_buff_head\tproxy_queue;\n-\tatomic_t\t\tentries;\n+\trefcount_t\t\tentries;\n \tatomic_t\t\tgc_entries;\n \tstruct list_head\tgc_list;\n \tstruct list_head\tmanaged_list;\n@@ -339,8 +340,8 @@ static inline void neigh_confirm(struct neighbour *n)\n \t}\n }\n \n-void neigh_table_init(int index, struct neigh_table *tbl);\n-int neigh_table_clear(int index, struct neigh_table *tbl);\n+int neigh_table_register(struct net *net, struct neigh_table *tbl, int index);\n+void neigh_table_unregister(struct net *net, int index);\n struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,\n \t\t\t       struct net_device *dev);\n struct neighbour *__neigh_create(struct neigh_table *tbl, const void *pkey,\n@@ -389,24 +390,17 @@ static inline void neigh_set_reach_time(struct neigh_parms *p)\n \n void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,\n \t\t    struct sk_buff *skb);\n-struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl, struct net *net,\n+struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl,\n \t\t\t\t   const void *key, struct net_device *dev);\n-int pneigh_create(struct neigh_table *tbl, struct net *net, const void *key,\n+int pneigh_create(struct neigh_table *tbl, const void *key,\n \t\t  struct net_device *dev, u32 flags, u8 protocol,\n \t\t  bool permanent);\n-int pneigh_delete(struct neigh_table *tbl, struct net *net, const void *key,\n+int pneigh_delete(struct neigh_table *tbl, const void *key,\n \t\t  struct net_device *dev);\n \n-static inline struct net *pneigh_net(const struct pneigh_entry *pneigh)\n-{\n-\treturn read_pnet(\u0026pneigh-\u003enet);\n-}\n-\n void neigh_app_ns(struct neighbour *n);\n void neigh_for_each(struct neigh_table *tbl,\n \t\t    void (*cb)(struct neighbour *, void *), void *cookie);\n-void __neigh_for_each_release(struct neigh_table *tbl,\n-\t\t\t      int (*cb)(struct neighbour *));\n int neigh_xmit(int fam, struct net_device *, const void *, struct sk_buff *);\n \n struct neigh_seq_state {\ndiff --git a/include/net/net_namespace.h b/include/net/net_namespace.h\nindex 501af1999fe83..f96a390ae5364 100644\n--- a/include/net/net_namespace.h\n+++ b/include/net/net_namespace.h\n@@ -39,6 +39,7 @@\n #include \u003cnet/netns/mctp.h\u003e\n #include \u003cnet/netns/vsock.h\u003e\n #include \u003cnet/net_trackers.h\u003e\n+#include \u003cnet/neighbour_tables.h\u003e\n #include \u003clinux/ns_common.h\u003e\n #include \u003clinux/idr.h\u003e\n #include \u003clinux/skbuff.h\u003e\n@@ -47,6 +48,7 @@\n \n struct user_namespace;\n struct proc_dir_entry;\n+struct neigh_table;\n struct net_device;\n struct sock;\n struct ctl_table_header;\n@@ -103,6 +105,8 @@ struct net {\n \tstruct proc_dir_entry \t*proc_net;\n \tstruct proc_dir_entry \t*proc_net_stat;\n \n+\tstruct neigh_table\t*neigh_tables[NEIGH_NR_TABLES];\n+\n #ifdef CONFIG_SYSCTL\n \tstruct ctl_table_set\tsysctls;\n #endif\ndiff --git a/include/net/route.h b/include/net/route.h\nindex f90106f383c56..38a23f28ee14a 100644\n--- a/include/net/route.h\n+++ b/include/net/route.h\n@@ -403,8 +403,11 @@ static inline struct neighbour *ip_neigh_gw4(struct net_device *dev,\n \tstruct neighbour *neigh;\n \n \tneigh = __ipv4_neigh_lookup_noref(dev, (__force u32)daddr);\n-\tif (unlikely(!neigh))\n-\t\tneigh = __neigh_create(\u0026arp_tbl, \u0026daddr, dev, false);\n+\tif (unlikely(!neigh)) {\n+\t\tstruct neigh_table *tbl = arp_table(dev_net(dev));\n+\n+\t\tneigh = __neigh_create(tbl, \u0026daddr, dev, false);\n+\t}\n \n \treturn neigh;\n }\ndiff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c\nindex b6e5a86b6a92a..0df84d3ccd906 100644\n--- a/net/bridge/br_arp_nd_proxy.c\n+++ b/net/bridge/br_arp_nd_proxy.c\n@@ -193,7 +193,7 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,\n \t\treturn;\n \t}\n \n-\tn = neigh_lookup(\u0026arp_tbl, \u0026tip, vlandev);\n+\tn = neigh_lookup(arp_table(dev_net(vlandev)), \u0026tip, vlandev);\n \tif (n) {\n \t\tstruct net_bridge_fdb_entry *f;\n \n@@ -468,7 +468,7 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,\n \t\treturn;\n \t}\n \n-\tn = neigh_lookup(\u0026nd_tbl, \u0026msg-\u003etarget, vlandev);\n+\tn = neigh_lookup(nd_table(dev_net(vlandev)), \u0026msg-\u003etarget, vlandev);\n \tif (n) {\n \t\tstruct net_bridge_fdb_entry *f;\n \ndiff --git a/net/core/neighbour.c b/net/core/neighbour.c\nindex 1349c0eedb642..9b28f40b019aa 100644\n--- a/net/core/neighbour.c\n+++ b/net/core/neighbour.c\n@@ -55,6 +55,23 @@ static void neigh_notify(struct neighbour *n, int type, int flags, u32 pid);\n static void __neigh_notify(struct neighbour *n, int type, int flags, u32 pid);\n static void pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev,\n \t\t\t  bool skip_perm);\n+static void neigh_table_free(struct neigh_table *tbl);\n+\n+static void neigh_table_get(struct neigh_table *tbl)\n+{\n+\trefcount_inc(\u0026tbl-\u003eentries);\n+}\n+\n+static void neigh_table_put(struct neigh_table *tbl)\n+{\n+\tif (refcount_dec_and_test(\u0026tbl-\u003eentries))\n+\t\tneigh_table_free(tbl);\n+}\n+\n+static int neigh_table_entries(struct neigh_table *tbl)\n+{\n+\treturn refcount_read(\u0026tbl-\u003eentries) - 1;\n+}\n \n #ifdef CONFIG_PROC_FS\n static const struct seq_operations neigh_stat_seq_ops;\n@@ -131,10 +148,11 @@ unsigned long neigh_rand_reach_time(unsigned long base)\n {\n \treturn base ? get_random_u32_below(base) + (base \u003e\u003e 1) : 0;\n }\n-EXPORT_SYMBOL(neigh_rand_reach_time);\n \n static void neigh_mark_dead(struct neighbour *n)\n {\n+\tlockdep_assert_held(\u0026n-\u003etbl-\u003elock);\n+\n \tn-\u003edead = 1;\n \tif (!list_empty(\u0026n-\u003egc_list)) {\n \t\tlist_del_init(\u0026n-\u003egc_list);\n@@ -148,11 +166,6 @@ static void neigh_update_gc_list(struct neighbour *n)\n {\n \tbool on_gc_list, exempt_from_gc;\n \n-\tspin_lock_bh(\u0026n-\u003etbl-\u003elock);\n-\twrite_lock(\u0026n-\u003elock);\n-\tif (n-\u003edead)\n-\t\tgoto out;\n-\n \t/* remove from the gc list if new state is permanent or if neighbor is\n \t * externally learned / validated; otherwise entry should be on the gc\n \t * list\n@@ -169,20 +182,12 @@ static void neigh_update_gc_list(struct neighbour *n)\n \t\tlist_add_tail(\u0026n-\u003egc_list, \u0026n-\u003etbl-\u003egc_list);\n \t\tatomic_inc(\u0026n-\u003etbl-\u003egc_entries);\n \t}\n-out:\n-\twrite_unlock(\u0026n-\u003elock);\n-\tspin_unlock_bh(\u0026n-\u003etbl-\u003elock);\n }\n \n static void neigh_update_managed_list(struct neighbour *n)\n {\n \tbool on_managed_list, add_to_managed;\n \n-\tspin_lock_bh(\u0026n-\u003etbl-\u003elock);\n-\twrite_lock(\u0026n-\u003elock);\n-\tif (n-\u003edead)\n-\t\tgoto out;\n-\n \tadd_to_managed = n-\u003eflags \u0026 NTF_MANAGED;\n \ton_managed_list = !list_empty(\u0026n-\u003emanaged_list);\n \n@@ -190,9 +195,6 @@ static void neigh_update_managed_list(struct neighbour *n)\n \t\tlist_del_init(\u0026n-\u003emanaged_list);\n \telse if (add_to_managed \u0026\u0026 !on_managed_list)\n \t\tlist_add_tail(\u0026n-\u003emanaged_list, \u0026n-\u003etbl-\u003emanaged_list);\n-out:\n-\twrite_unlock(\u0026n-\u003elock);\n-\tspin_unlock_bh(\u0026n-\u003etbl-\u003elock);\n }\n \n static void neigh_update_flags(struct neighbour *neigh, u32 flags, int *notify,\n@@ -349,8 +351,7 @@ static void neigh_parms_qlen_dec(struct net_device *dev, int family)\n \trcu_read_unlock();\n }\n \n-static void pneigh_queue_purge(struct sk_buff_head *list, struct net *net,\n-\t\t\t       int family)\n+static void pneigh_queue_purge(struct sk_buff_head *list, int family)\n {\n \tstruct sk_buff_head tmp;\n \tunsigned long flags;\n@@ -361,13 +362,11 @@ static void pneigh_queue_purge(struct sk_buff_head *list, struct net *net,\n \tskb = skb_peek(list);\n \twhile (skb != NULL) {\n \t\tstruct sk_buff *skb_next = skb_peek_next(skb, list);\n-\t\tstruct net_device *dev = skb-\u003edev;\n \n-\t\tif (net == NULL || net_eq(dev_net(dev), net)) {\n-\t\t\tneigh_parms_qlen_dec(dev, family);\n-\t\t\t__skb_unlink(skb, list);\n-\t\t\t__skb_queue_tail(\u0026tmp, skb);\n-\t\t}\n+\t\tneigh_parms_qlen_dec(skb-\u003edev, family);\n+\t\t__skb_unlink(skb, list);\n+\t\t__skb_queue_tail(\u0026tmp, skb);\n+\n \t\tskb = skb_next;\n \t}\n \tspin_unlock_irqrestore(\u0026list-\u003elock, flags);\n@@ -471,8 +470,7 @@ static int __neigh_ifdown(struct neigh_table *tbl, struct net_device *dev,\n \tspin_unlock_bh(\u0026tbl-\u003elock);\n \n \tpneigh_ifdown(tbl, dev, skip_perm);\n-\tpneigh_queue_purge(\u0026tbl-\u003eproxy_queue, dev ? dev_net(dev) : NULL,\n-\t\t\t   tbl-\u003efamily);\n+\tpneigh_queue_purge(\u0026tbl-\u003eproxy_queue, tbl-\u003efamily);\n \tif (skb_queue_empty_lockless(\u0026tbl-\u003eproxy_queue))\n \t\ttimer_delete_sync(\u0026tbl-\u003eproxy_timer);\n \treturn 0;\n@@ -537,7 +535,7 @@ static struct neighbour *neigh_alloc(struct neigh_table *tbl,\n \tINIT_LIST_HEAD(\u0026n-\u003egc_list);\n \tINIT_LIST_HEAD(\u0026n-\u003emanaged_list);\n \n-\tatomic_inc(\u0026tbl-\u003eentries);\n+\tneigh_table_get(tbl);\n out:\n \treturn n;\n \n@@ -687,7 +685,7 @@ ___neigh_create(struct neigh_table *tbl, const void *pkey,\n \tnht = rcu_dereference_protected(tbl-\u003enht,\n \t\t\t\t\tlockdep_is_held(\u0026tbl-\u003elock));\n \n-\tif (atomic_read(\u0026tbl-\u003eentries) \u003e (1 \u003c\u003c nht-\u003ehash_shift))\n+\tif (neigh_table_entries(tbl) \u003e (1 \u003c\u003c nht-\u003ehash_shift))\n \t\tnht = neigh_hash_grow(tbl, nht-\u003ehash_shift + 1);\n \n \thash_val = tbl-\u003ehash(n-\u003eprimary_key, dev, nht-\u003ehash_rnd) \u003e\u003e (32 - nht-\u003ehash_shift);\n@@ -752,8 +750,7 @@ static u32 pneigh_hash(const void *pkey, unsigned int key_len)\n }\n \n struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl,\n-\t\t\t\t   struct net *net, const void *pkey,\n-\t\t\t\t   struct net_device *dev)\n+\t\t\t\t   const void *pkey, struct net_device *dev)\n {\n \tstruct pneigh_entry *n;\n \tunsigned int key_len;\n@@ -766,7 +763,6 @@ struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl,\n \n \twhile (n) {\n \t\tif (!memcmp(n-\u003ekey, pkey, key_len) \u0026\u0026\n-\t\t    net_eq(pneigh_net(n), net) \u0026\u0026\n \t\t    (n-\u003edev == dev || !n-\u003edev))\n \t\t\treturn n;\n \n@@ -776,7 +772,7 @@ struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl,\n \treturn NULL;\n }\n \n-int pneigh_create(struct neigh_table *tbl, struct net *net,\n+int pneigh_create(struct neigh_table *tbl,\n \t\t  const void *pkey, struct net_device *dev,\n \t\t  u32 flags, u8 protocol, bool permanent)\n {\n@@ -787,7 +783,7 @@ int pneigh_create(struct neigh_table *tbl, struct net *net,\n \n \tmutex_lock(\u0026tbl-\u003ephash_lock);\n \n-\tn = pneigh_lookup(tbl, net, pkey, dev);\n+\tn = pneigh_lookup(tbl, pkey, dev);\n \tif (n)\n \t\tgoto update;\n \n@@ -798,7 +794,6 @@ int pneigh_create(struct neigh_table *tbl, struct net *net,\n \t\tgoto out;\n \t}\n \n-\twrite_pnet(\u0026n-\u003enet, net);\n \tmemcpy(n-\u003ekey, pkey, key_len);\n \tn-\u003edev = dev;\n \tnetdev_hold(dev, \u0026n-\u003edev_tracker, GFP_KERNEL);\n@@ -831,7 +826,7 @@ static void pneigh_destroy(struct rcu_head *rcu)\n \tkfree(n);\n }\n \n-int pneigh_delete(struct neigh_table *tbl, struct net *net, const void *pkey,\n+int pneigh_delete(struct neigh_table *tbl, const void *pkey,\n \t\t  struct net_device *dev)\n {\n \tstruct pneigh_entry *n, __rcu **np;\n@@ -846,8 +841,7 @@ int pneigh_delete(struct neigh_table *tbl, struct net *net, const void *pkey,\n \tfor (np = \u0026tbl-\u003ephash_buckets[hash_val];\n \t     (n = rcu_dereference_protected(*np, 1)) != NULL;\n \t     np = \u0026n-\u003enext) {\n-\t\tif (!memcmp(n-\u003ekey, pkey, key_len) \u0026\u0026 n-\u003edev == dev \u0026\u0026\n-\t\t    net_eq(pneigh_net(n), net)) {\n+\t\tif (!memcmp(n-\u003ekey, pkey, key_len) \u0026\u0026 n-\u003edev == dev) {\n \t\t\trcu_assign_pointer(*np, n-\u003enext);\n \n \t\t\tmutex_unlock(\u0026tbl-\u003ephash_lock);\n@@ -939,7 +933,7 @@ void neigh_destroy(struct neighbour *neigh)\n \n \tneigh_dbg(2, \"neigh %p is destroyed\\n\", neigh);\n \n-\tatomic_dec(\u0026neigh-\u003etbl-\u003eentries);\n+\tneigh_table_put(neigh-\u003etbl);\n \tkfree_rcu(neigh, rcu);\n }\n EXPORT_SYMBOL(neigh_destroy);\n@@ -994,7 +988,7 @@ static void neigh_periodic_work(struct work_struct *work)\n \t\t\tneigh_set_reach_time(p);\n \t}\n \n-\tif (atomic_read(\u0026tbl-\u003eentries) \u003c READ_ONCE(tbl-\u003egc_thresh1))\n+\tif (neigh_table_entries(tbl) \u003c READ_ONCE(tbl-\u003egc_thresh1))\n \t\tgoto out;\n \n \tfor (i = 0 ; i \u003c (1 \u003c\u003c nht-\u003ehash_shift); i++) {\n@@ -1523,10 +1517,23 @@ static int __neigh_update(struct neighbour *neigh, const u8 *lladdr,\n \n \twrite_unlock_bh(\u0026neigh-\u003elock);\n \n-\tif (((new ^ old) \u0026 NUD_PERMANENT) || gc_update)\n-\t\tneigh_update_gc_list(neigh);\n-\tif (managed_update)\n-\t\tneigh_update_managed_list(neigh);\n+\tgc_update |= !!((new ^ old) \u0026 NUD_PERMANENT);\n+\tif (gc_update || managed_update) {\n+\t\tspin_lock_bh(\u0026neigh-\u003etbl-\u003elock);\n+\n+\t\tif (!neigh-\u003edead) {\n+\t\t\twrite_lock(\u0026neigh-\u003elock);\n+\n+\t\t\tif (gc_update)\n+\t\t\t\tneigh_update_gc_list(neigh);\n+\t\t\tif (managed_update)\n+\t\t\t\tneigh_update_managed_list(neigh);\n+\n+\t\t\twrite_unlock(\u0026neigh-\u003elock);\n+\t\t}\n+\n+\t\tspin_unlock_bh(\u0026neigh-\u003etbl-\u003elock);\n+\t}\n \n \tif (notify)\n \t\tcall_netevent_notifiers(NETEVENT_NEIGH_UPDATE, neigh);\n@@ -1540,7 +1547,6 @@ int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,\n {\n \treturn __neigh_update(neigh, lladdr, new, flags, nlmsg_pid, NULL);\n }\n-EXPORT_SYMBOL(neigh_update);\n \n /* Update the neigh to listen temporarily for probe responses, even if it is\n  * in a NUD_FAILED state. The caller has to hold neigh-\u003elock for writing.\n@@ -1558,7 +1564,6 @@ void __neigh_set_probe_once(struct neighbour *neigh)\n \t\t\tjiffies + max(NEIGH_VAR(neigh-\u003eparms, RETRANS_TIME),\n \t\t\t\t      HZ/100));\n }\n-EXPORT_SYMBOL(__neigh_set_probe_once);\n \n struct neighbour *neigh_event_ns(struct neigh_table *tbl,\n \t\t\t\t u8 *lladdr, void *saddr,\n@@ -1571,7 +1576,6 @@ struct neighbour *neigh_event_ns(struct neigh_table *tbl,\n \t\t\t     NEIGH_UPDATE_F_OVERRIDE, 0);\n \treturn neigh;\n }\n-EXPORT_SYMBOL(neigh_event_ns);\n \n /* called with read_lock_bh(\u0026n-\u003elock); */\n static void neigh_hh_init(struct neighbour *n)\n@@ -1624,7 +1628,6 @@ int neigh_resolve_output(struct neighbour *neigh, struct sk_buff *skb)\n \tkfree_skb_reason(skb, SKB_DROP_REASON_NEIGH_HH_FILLFAIL);\n \tgoto out;\n }\n-EXPORT_SYMBOL(neigh_resolve_output);\n \n /* As fast as possible without hh cache */\n \n@@ -1741,16 +1744,15 @@ void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,\n \tmod_timer(\u0026tbl-\u003eproxy_timer, sched_next);\n \tspin_unlock(\u0026tbl-\u003eproxy_queue.lock);\n }\n-EXPORT_SYMBOL(pneigh_enqueue);\n \n static inline struct neigh_parms *lookup_neigh_parms(struct neigh_table *tbl,\n-\t\t\t\t\t\t      struct net *net, int ifindex)\n+\t\t\t\t\t\t     int ifindex)\n {\n \tstruct neigh_parms *p;\n \n \tlist_for_each_entry(p, \u0026tbl-\u003eparms_list, list) {\n-\t\tif ((p-\u003edev \u0026\u0026 p-\u003edev-\u003eifindex == ifindex \u0026\u0026 net_eq(neigh_parms_net(p), net)) ||\n-\t\t    (!p-\u003edev \u0026\u0026 !ifindex \u0026\u0026 net_eq(net, \u0026init_net)))\n+\t\tif ((p-\u003edev \u0026\u0026 p-\u003edev-\u003eifindex == ifindex) ||\n+\t\t    (!p-\u003edev \u0026\u0026 !ifindex))\n \t\t\treturn p;\n \t}\n \n@@ -1789,7 +1791,6 @@ struct neigh_parms *neigh_parms_alloc(struct net_device *dev,\n \t}\n \treturn p;\n }\n-EXPORT_SYMBOL(neigh_parms_alloc);\n \n static void neigh_rcu_free_parms(struct rcu_head *head)\n {\n@@ -1812,113 +1813,169 @@ void neigh_parms_release(struct neigh_table *tbl, struct neigh_parms *parms)\n \tnetdev_put(parms-\u003edev, \u0026parms-\u003edev_tracker);\n \tcall_rcu(\u0026parms-\u003ercu_head, neigh_rcu_free_parms);\n }\n-EXPORT_SYMBOL(neigh_parms_release);\n \n static struct lock_class_key neigh_table_proxy_queue_class;\n \n-static struct neigh_table __rcu *neigh_tables[NEIGH_NR_TABLES] __read_mostly;\n-\n-void neigh_table_init(int index, struct neigh_table *tbl)\n+static int neigh_table_init(struct net *net, struct neigh_table *tbl)\n {\n \tunsigned long now = jiffies;\n \tunsigned long phsize;\n \n-\tINIT_LIST_HEAD(\u0026tbl-\u003eparms_list);\n-\tINIT_LIST_HEAD(\u0026tbl-\u003egc_list);\n-\tINIT_LIST_HEAD(\u0026tbl-\u003emanaged_list);\n+\tRCU_INIT_POINTER(tbl-\u003enht, neigh_hash_alloc(3));\n+\tif (!tbl-\u003enht)\n+\t\tgoto err_hash;\n \n-\tlist_add(\u0026tbl-\u003eparms.list, \u0026tbl-\u003eparms_list);\n-\twrite_pnet(\u0026tbl-\u003eparms.net, \u0026init_net);\n-\trefcount_set(\u0026tbl-\u003eparms.refcnt, 1);\n-\tneigh_set_reach_time(\u0026tbl-\u003eparms);\n-\ttbl-\u003eparms.qlen = 0;\n+\tphsize = (PNEIGH_HASHMASK + 1) * sizeof(struct pneigh_entry *);\n+\ttbl-\u003ephash_buckets = kzalloc(phsize, GFP_KERNEL);\n+\tif (!tbl-\u003ephash_buckets)\n+\t\tgoto err_phash;\n+\n+\ttbl-\u003eentry_size = ALIGN(offsetof(struct neighbour, primary_key) +\n+\t\t\t\ttbl-\u003ekey_len, NEIGH_PRIV_ALIGN);\n \n \ttbl-\u003estats = alloc_percpu(struct neigh_statistics);\n \tif (!tbl-\u003estats)\n-\t\tpanic(\"cannot create neighbour cache statistics\");\n+\t\tgoto err_stats;\n \n #ifdef CONFIG_PROC_FS\n-\tif (!proc_create_seq_data(tbl-\u003eid, 0, init_net.proc_net_stat,\n-\t\t\t      \u0026neigh_stat_seq_ops, tbl))\n-\t\tpanic(\"cannot create neighbour proc dir entry\");\n+\tif (!proc_create_net_data(tbl-\u003eid, 0, net-\u003eproc_net_stat,\n+\t\t\t\t  \u0026neigh_stat_seq_ops,\n+\t\t\t\t  sizeof(struct seq_net_private), tbl))\n+\t\tgoto err_proc;\n #endif\n \n-\tRCU_INIT_POINTER(tbl-\u003enht, neigh_hash_alloc(3));\n-\n-\tphsize = (PNEIGH_HASHMASK + 1) * sizeof(struct pneigh_entry *);\n-\ttbl-\u003ephash_buckets = kzalloc(phsize, GFP_KERNEL);\n-\n-\tif (!tbl-\u003enht || !tbl-\u003ephash_buckets)\n-\t\tpanic(\"cannot allocate neighbour cache hashes\");\n-\n-\tif (!tbl-\u003eentry_size)\n-\t\ttbl-\u003eentry_size = ALIGN(offsetof(struct neighbour, primary_key) +\n-\t\t\t\t\ttbl-\u003ekey_len, NEIGH_PRIV_ALIGN);\n-\telse\n-\t\tWARN_ON(tbl-\u003eentry_size % NEIGH_PRIV_ALIGN);\n+\ttbl-\u003eparms.tbl = tbl;\n+\ttbl-\u003eparms.qlen = 0;\n+\tINIT_LIST_HEAD(\u0026tbl-\u003eparms_list);\n+\tlist_add(\u0026tbl-\u003eparms.list, \u0026tbl-\u003eparms_list);\n+\twrite_pnet(\u0026tbl-\u003eparms.net, net);\n+\trefcount_set(\u0026tbl-\u003eparms.refcnt, 1);\n+\tneigh_set_reach_time(\u0026tbl-\u003eparms);\n+\ttbl-\u003elast_flush = now;\n+\ttbl-\u003elast_rand\t= now + tbl-\u003eparms.reachable_time * 20;\n \n+\trefcount_set(\u0026tbl-\u003eentries, 1);\n \tspin_lock_init(\u0026tbl-\u003elock);\n \tmutex_init(\u0026tbl-\u003ephash_lock);\n+\tskb_queue_head_init_class(\u0026tbl-\u003eproxy_queue,\n+\t\t\t\t  \u0026neigh_table_proxy_queue_class);\n+\ttimer_setup(\u0026tbl-\u003eproxy_timer, neigh_proxy_process, 0);\n \n+\tINIT_LIST_HEAD(\u0026tbl-\u003egc_list);\n \tINIT_DEFERRABLE_WORK(\u0026tbl-\u003egc_work, neigh_periodic_work);\n \tqueue_delayed_work(system_power_efficient_wq, \u0026tbl-\u003egc_work,\n-\t\t\ttbl-\u003eparms.reachable_time);\n+\t\t\t   tbl-\u003eparms.reachable_time);\n+\n+\tINIT_LIST_HEAD(\u0026tbl-\u003emanaged_list);\n \tINIT_DEFERRABLE_WORK(\u0026tbl-\u003emanaged_work, neigh_managed_work);\n \tqueue_delayed_work(system_power_efficient_wq, \u0026tbl-\u003emanaged_work, 0);\n \n-\ttimer_setup(\u0026tbl-\u003eproxy_timer, neigh_proxy_process, 0);\n-\tskb_queue_head_init_class(\u0026tbl-\u003eproxy_queue,\n-\t\t\t\u0026neigh_table_proxy_queue_class);\n-\n-\ttbl-\u003elast_flush = now;\n-\ttbl-\u003elast_rand\t= now + tbl-\u003eparms.reachable_time * 20;\n+\treturn 0;\n \n-\trcu_assign_pointer(neigh_tables[index], tbl);\n+#ifdef CONFIG_PROC_FS\n+err_proc:\n+\tfree_percpu(tbl-\u003estats);\n+#endif\n+err_stats:\n+\tkfree(tbl-\u003ephash_buckets);\n+err_phash:\n+\tneigh_hash_free_rcu(\u0026rcu_dereference_protected(tbl-\u003enht, 1)-\u003ercu);\n+err_hash:\n+\treturn -ENOMEM;\n }\n \n-/*\n- * Only called from ndisc_cleanup(), which means this is dead code\n- * because we no longer can unload IPv6 module.\n- */\n-int neigh_table_clear(int index, struct neigh_table *tbl)\n+static void neigh_table_free(struct neigh_table *tbl)\n {\n-\tRCU_INIT_POINTER(neigh_tables[index], NULL);\n-\tsynchronize_rcu();\n+\tstruct neigh_hash_table *nht;\n+\n+\tfree_percpu(tbl-\u003estats);\n+\ttbl-\u003estats = NULL;\n+\n+\tkfree(tbl-\u003ephash_buckets);\n+\ttbl-\u003ephash_buckets = NULL;\n+\n+\tnht = rcu_dereference_protected(tbl-\u003enht, 1);\n+\ttbl-\u003enht = NULL;\n+\tneigh_hash_free_rcu(\u0026nht-\u003ercu);\n \n-\t/* It is not clean... Fix it to unload IPv6 module safely */\n+\tkfree(tbl);\n+}\n+\n+static void neigh_table_clear(struct net *net, struct neigh_table *tbl)\n+{\n \tcancel_delayed_work_sync(\u0026tbl-\u003emanaged_work);\n \tcancel_delayed_work_sync(\u0026tbl-\u003egc_work);\n-\ttimer_delete_sync(\u0026tbl-\u003eproxy_timer);\n-\tpneigh_queue_purge(\u0026tbl-\u003eproxy_queue, NULL, tbl-\u003efamily);\n+\ttimer_shutdown_sync(\u0026tbl-\u003eproxy_timer);\n+\n \tneigh_ifdown(tbl, NULL);\n-\tif (atomic_read(\u0026tbl-\u003eentries))\n-\t\tpr_crit(\"neighbour leakage\\n\");\n+\tremove_proc_entry(tbl-\u003eid, net-\u003eproc_net_stat);\n \n-\tcall_rcu(\u0026rcu_dereference_protected(tbl-\u003enht, 1)-\u003ercu,\n-\t\t neigh_hash_free_rcu);\n-\ttbl-\u003enht = NULL;\n+\tneigh_table_put(tbl);\n+}\n \n-\tkfree(tbl-\u003ephash_buckets);\n-\ttbl-\u003ephash_buckets = NULL;\n+static void neigh_table_inherit(struct net *net, struct neigh_table *tbl,\n+\t\t\t\tint index)\n+{\n+\tconst struct neigh_table *init_tbl = init_net.neigh_tables[index];\n+\tbool inherit = READ_ONCE(sysctl_neigh_inherit_init_net);\n+\tint i;\n \n-\tremove_proc_entry(tbl-\u003eid, init_net.proc_net_stat);\n+\tif (net_eq(net, \u0026init_net) || !inherit)\n+\t\treturn;\n \n-\tfree_percpu(tbl-\u003estats);\n-\ttbl-\u003estats = NULL;\n+\ttbl-\u003egc_interval = READ_ONCE(init_tbl-\u003egc_interval);\n+\ttbl-\u003egc_thresh1 = READ_ONCE(init_tbl-\u003egc_thresh1);\n+\ttbl-\u003egc_thresh2 = READ_ONCE(init_tbl-\u003egc_thresh2);\n+\ttbl-\u003egc_thresh3 = READ_ONCE(init_tbl-\u003egc_thresh3);\n+\n+\tfor (i = 0; i \u003c NEIGH_VAR_DATA_MAX; i++)\n+\t\ttbl-\u003eparms.data[i] = READ_ONCE(init_tbl-\u003eparms.data[i]);\n+}\n+\n+int neigh_table_register(struct net *net, struct neigh_table *tbl, int index)\n+{\n+\tint err;\n+\n+\ttbl = kmemdup(tbl, sizeof(*tbl), GFP_KERNEL);\n+\tif (!tbl) {\n+\t\terr = -ENOMEM;\n+\t\tgoto err;\n+\t}\n+\n+\tneigh_table_inherit(net, tbl, index);\n+\n+\terr = neigh_table_init(net, tbl);\n+\tif (err)\n+\t\tgoto free_table;\n+\n+\tnet-\u003eneigh_tables[index] = tbl;\n \n \treturn 0;\n+\n+free_table:\n+\tkfree(tbl);\n+err:\n+\treturn err;\n+}\n+\n+void neigh_table_unregister(struct net *net, int index)\n+{\n+\tstruct neigh_table *tbl = net-\u003eneigh_tables[index];\n+\n+\tnet-\u003eneigh_tables[index] = NULL;\n+\tneigh_table_clear(net, tbl);\n }\n \n-static struct neigh_table *neigh_find_table(int family)\n+static struct neigh_table *neigh_find_table(struct net *net, int family)\n {\n \tstruct neigh_table *tbl = NULL;\n \n \tswitch (family) {\n \tcase AF_INET:\n-\t\ttbl = rcu_dereference_rtnl(neigh_tables[NEIGH_ARP_TABLE]);\n+\t\ttbl = net-\u003eneigh_tables[NEIGH_ARP_TABLE];\n \t\tbreak;\n \tcase AF_INET6:\n-\t\ttbl = rcu_dereference_rtnl(neigh_tables[NEIGH_ND_TABLE]);\n+\t\ttbl = net-\u003eneigh_tables[NEIGH_ND_TABLE];\n \t\tbreak;\n \t}\n \n@@ -1972,7 +2029,7 @@ static int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh,\n \t\t}\n \t}\n \n-\ttbl = neigh_find_table(ndm-\u003endm_family);\n+\ttbl = neigh_find_table(net, ndm-\u003endm_family);\n \tif (tbl == NULL)\n \t\treturn -EAFNOSUPPORT;\n \n@@ -1982,7 +2039,7 @@ static int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh,\n \t}\n \n \tif (ndm-\u003endm_flags \u0026 NTF_PROXY) {\n-\t\terr = pneigh_delete(tbl, net, nla_data(dst_attr), dev);\n+\t\terr = pneigh_delete(tbl, nla_data(dst_attr), dev);\n \t\tgoto out;\n \t}\n \n@@ -2058,7 +2115,7 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh,\n \t\t}\n \t}\n \n-\ttbl = neigh_find_table(ndm-\u003endm_family);\n+\ttbl = neigh_find_table(net, ndm-\u003endm_family);\n \tif (tbl == NULL)\n \t\treturn -EAFNOSUPPORT;\n \n@@ -2078,7 +2135,7 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh,\n \t\t\tgoto out;\n \t\t}\n \n-\t\terr = pneigh_create(tbl, net, dst, dev, ndm_flags, protocol,\n+\t\terr = pneigh_create(tbl, dst, dev, ndm_flags, protocol,\n \t\t\t\t    !!(ndm-\u003endm_state \u0026 NUD_PERMANENT));\n \t\tgoto out;\n \t}\n@@ -2267,7 +2324,7 @@ static int neightbl_fill_info(struct sk_buff *skb, struct neigh_table *tbl,\n \t\tstruct ndt_config ndc = {\n \t\t\t.ndtc_key_len\t\t= tbl-\u003ekey_len,\n \t\t\t.ndtc_entry_size\t= tbl-\u003eentry_size,\n-\t\t\t.ndtc_entries\t\t= atomic_read(\u0026tbl-\u003eentries),\n+\t\t\t.ndtc_entries\t\t= neigh_table_entries(tbl),\n \t\t\t.ndtc_last_flush\t= jiffies_to_msecs(flush_delta),\n \t\t\t.ndtc_last_rand\t\t= jiffies_to_msecs(rand_delta),\n \t\t\t.ndtc_proxy_qlen\t= READ_ONCE(tbl-\u003eproxy_queue.qlen),\n@@ -2400,10 +2457,8 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,\n \n \tndtmsg = nlmsg_data(nlh);\n \n-\trcu_read_lock();\n-\n \tfor (tidx = 0; tidx \u003c NEIGH_NR_TABLES; tidx++) {\n-\t\ttbl = rcu_dereference(neigh_tables[tidx]);\n+\t\ttbl = net-\u003eneigh_tables[tidx];\n \t\tif (!tbl)\n \t\t\tcontinue;\n \n@@ -2417,7 +2472,6 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,\n \t}\n \n \tif (!found) {\n-\t\trcu_read_unlock();\n \t\terr = -ENOENT;\n \t\tgoto errout;\n \t}\n@@ -2442,8 +2496,8 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,\n \t\tif (tbp[NDTPA_IFINDEX])\n \t\t\tifindex = nla_get_u32(tbp[NDTPA_IFINDEX]);\n \n-\t\tp = lookup_neigh_parms(tbl, net, ifindex);\n-\t\tif (p == NULL) {\n+\t\tp = lookup_neigh_parms(tbl, ifindex);\n+\t\tif (!p) {\n \t\t\terr = -ENOENT;\n \t\t\tgoto errout_tbl_lock;\n \t\t}\n@@ -2524,12 +2578,6 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,\n \t\t}\n \t}\n \n-\terr = -ENOENT;\n-\tif ((tb[NDTA_THRESH1] || tb[NDTA_THRESH2] ||\n-\t     tb[NDTA_THRESH3] || tb[NDTA_GC_INTERVAL]) \u0026\u0026\n-\t    !net_eq(net, \u0026init_net))\n-\t\tgoto errout_tbl_lock;\n-\n \tif (tb[NDTA_THRESH1])\n \t\tWRITE_ONCE(tbl-\u003egc_thresh1, nla_get_u32(tb[NDTA_THRESH1]));\n \n@@ -2546,7 +2594,6 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,\n \n errout_tbl_lock:\n \tspin_unlock_bh(\u0026tbl-\u003elock);\n-\trcu_read_unlock();\n errout:\n \treturn err;\n }\n@@ -2598,7 +2645,7 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb)\n \tfor (tidx = 0; tidx \u003c NEIGH_NR_TABLES; tidx++) {\n \t\tstruct neigh_parms *p;\n \n-\t\ttbl = rcu_dereference(neigh_tables[tidx]);\n+\t\ttbl = net-\u003eneigh_tables[tidx];\n \t\tif (!tbl)\n \t\t\tcontinue;\n \n@@ -2613,9 +2660,6 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb)\n \t\tnidx = 0;\n \t\tp = list_next_entry(\u0026tbl-\u003eparms, list);\n \t\tlist_for_each_entry_from_rcu(p, \u0026tbl-\u003eparms_list, list) {\n-\t\t\tif (!net_eq(neigh_parms_net(p), net))\n-\t\t\t\tcontinue;\n-\n \t\t\tif (nidx \u003c neigh_skip)\n \t\t\t\tgoto next;\n \n@@ -2793,12 +2837,11 @@ static int neigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,\n \t\t\t    struct netlink_callback *cb,\n \t\t\t    struct neigh_dump_filter *filter)\n {\n-\tstruct net *net = sock_net(skb-\u003esk);\n-\tstruct neighbour *n;\n-\tint err = 0, h, s_h = cb-\u003eargs[1];\n \tint idx, s_idx = idx = cb-\u003eargs[2];\n-\tstruct neigh_hash_table *nht;\n+\tint err = 0, h, s_h = cb-\u003eargs[1];\n \tunsigned int flags = NLM_F_MULTI;\n+\tstruct neigh_hash_table *nht;\n+\tstruct neighbour *n;\n \n \tif (filter-\u003edev_idx || filter-\u003emaster_idx)\n \t\tflags |= NLM_F_DUMP_FILTERED;\n@@ -2810,7 +2853,7 @@ static int neigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,\n \t\t\ts_idx = 0;\n \t\tidx = 0;\n \t\tneigh_for_each_in_bucket_rcu(n, \u0026nht-\u003ehash_heads[h]) {\n-\t\t\tif (idx \u003c s_idx || !net_eq(dev_net(n-\u003edev), net))\n+\t\t\tif (idx \u003c s_idx)\n \t\t\t\tgoto next;\n \t\t\tif (neigh_ifindex_filtered(n-\u003edev, filter-\u003edev_idx) ||\n \t\t\t    neigh_master_filtered(n-\u003edev, filter-\u003emaster_idx))\n@@ -2834,11 +2877,10 @@ static int pneigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,\n \t\t\t     struct netlink_callback *cb,\n \t\t\t     struct neigh_dump_filter *filter)\n {\n-\tstruct pneigh_entry *n;\n-\tstruct net *net = sock_net(skb-\u003esk);\n-\tint err = 0, h, s_h = cb-\u003eargs[3];\n \tint idx, s_idx = idx = cb-\u003eargs[4];\n+\tint err = 0, h, s_h = cb-\u003eargs[3];\n \tunsigned int flags = NLM_F_MULTI;\n+\tstruct pneigh_entry *n;\n \n \tif (filter-\u003edev_idx || filter-\u003emaster_idx)\n \t\tflags |= NLM_F_DUMP_FILTERED;\n@@ -2849,7 +2891,7 @@ static int pneigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,\n \t\tfor (n = rcu_dereference(tbl-\u003ephash_buckets[h]), idx = 0;\n \t\t     n;\n \t\t     n = rcu_dereference(n-\u003enext)) {\n-\t\t\tif (idx \u003c s_idx || pneigh_net(n) != net)\n+\t\t\tif (idx \u003c s_idx)\n \t\t\t\tgoto next;\n \t\t\tif (neigh_ifindex_filtered(n-\u003edev, filter-\u003edev_idx) ||\n \t\t\t    neigh_master_filtered(n-\u003edev, filter-\u003emaster_idx))\n@@ -2935,6 +2977,7 @@ static int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb)\n {\n \tconst struct nlmsghdr *nlh = cb-\u003enlh;\n \tstruct neigh_dump_filter filter = {};\n+\tstruct net *net = sock_net(skb-\u003esk);\n \tstruct neigh_table *tbl;\n \tint t, family, s_t;\n \tint proxy = 0;\n@@ -2958,8 +3001,7 @@ static int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb)\n \n \trcu_read_lock();\n \tfor (t = 0; t \u003c NEIGH_NR_TABLES; t++) {\n-\t\ttbl = rcu_dereference(neigh_tables[t]);\n-\n+\t\ttbl = net-\u003eneigh_tables[t];\n \t\tif (!tbl)\n \t\t\tcontinue;\n \t\tif (t \u003c s_t || (family \u0026\u0026 tbl-\u003efamily != family))\n@@ -3081,7 +3123,7 @@ static int neigh_get(struct sk_buff *in_skb, struct nlmsghdr *nlh,\n \n \trcu_read_lock();\n \n-\ttbl = neigh_find_table(ndm-\u003endm_family);\n+\ttbl = neigh_find_table(net, ndm-\u003endm_family);\n \tif (!tbl) {\n \t\tNL_SET_ERR_MSG(extack, \"Unsupported family in header for neighbor get request\");\n \t\terr = -EAFNOSUPPORT;\n@@ -3108,7 +3150,7 @@ static int neigh_get(struct sk_buff *in_skb, struct nlmsghdr *nlh,\n \tif (ndm-\u003endm_flags \u0026 NTF_PROXY) {\n \t\tstruct pneigh_entry *pn;\n \n-\t\tpn = pneigh_lookup(tbl, net, dst, dev);\n+\t\tpn = pneigh_lookup(tbl, dst, dev);\n \t\tif (!pn) {\n \t\t\tNL_SET_ERR_MSG(extack, \"Proxy neighbour entry not found\");\n \t\t\terr = -ENOENT;\n@@ -3161,37 +3203,6 @@ void neigh_for_each(struct neigh_table *tbl, void (*cb)(struct neighbour *, void\n }\n EXPORT_SYMBOL(neigh_for_each);\n \n-/* The tbl-\u003elock must be held as a writer and BH disabled. */\n-void __neigh_for_each_release(struct neigh_table *tbl,\n-\t\t\t      int (*cb)(struct neighbour *))\n-{\n-\tstruct neigh_hash_table *nht;\n-\tint chain;\n-\n-\tnht = rcu_dereference_protected(tbl-\u003enht,\n-\t\t\t\t\tlockdep_is_held(\u0026tbl-\u003elock));\n-\tfor (chain = 0; chain \u003c (1 \u003c\u003c nht-\u003ehash_shift); chain++) {\n-\t\tstruct hlist_node *tmp;\n-\t\tstruct neighbour *n;\n-\n-\t\tneigh_for_each_in_bucket_safe(n, tmp, \u0026nht-\u003ehash_heads[chain]) {\n-\t\t\tint release;\n-\n-\t\t\twrite_lock(\u0026n-\u003elock);\n-\t\t\trelease = cb(n);\n-\t\t\tif (release) {\n-\t\t\t\thlist_del_rcu(\u0026n-\u003ehash);\n-\t\t\t\thlist_del_rcu(\u0026n-\u003edev_list);\n-\t\t\t\tneigh_mark_dead(n);\n-\t\t\t}\n-\t\t\twrite_unlock(\u0026n-\u003elock);\n-\t\t\tif (release)\n-\t\t\t\tneigh_cleanup_and_release(n);\n-\t\t}\n-\t}\n-}\n-EXPORT_SYMBOL(__neigh_for_each_release);\n-\n int neigh_xmit(int index, struct net_device *dev,\n \t       const void *addr, struct sk_buff *skb)\n {\n@@ -3200,9 +3211,11 @@ int neigh_xmit(int index, struct net_device *dev,\n \tif (likely(index \u003c NEIGH_NR_TABLES)) {\n \t\tstruct neigh_table *tbl;\n \t\tstruct neighbour *neigh;\n+\t\tstruct net *net;\n \n \t\trcu_read_lock();\n-\t\ttbl = rcu_dereference(neigh_tables[index]);\n+\t\tnet = dev_net_rcu(dev);\n+\t\ttbl = net-\u003eneigh_tables[index];\n \t\tif (!tbl) {\n \t\t\trcu_read_unlock();\n \t\t\tgoto out_kfree_skb;\n@@ -3245,10 +3258,6 @@ static struct neighbour *neigh_get_valid(struct seq_file *seq,\n \t\t\t\t\t loff_t *pos)\n {\n \tstruct neigh_seq_state *state = seq-\u003eprivate;\n-\tstruct net *net = seq_file_net(seq);\n-\n-\tif (!net_eq(dev_net(n-\u003edev), net))\n-\t\treturn NULL;\n \n \tif (state-\u003eneigh_sub_iter) {\n \t\tloff_t fakep = 0;\n@@ -3337,7 +3346,6 @@ static struct neighbour *neigh_get_idx(struct seq_file *seq, loff_t *pos)\n static struct pneigh_entry *pneigh_get_first(struct seq_file *seq)\n {\n \tstruct neigh_seq_state *state = seq-\u003eprivate;\n-\tstruct net *net = seq_file_net(seq);\n \tstruct neigh_table *tbl = state-\u003etbl;\n \tstruct pneigh_entry *pn = NULL;\n \tint bucket;\n@@ -3345,9 +3353,6 @@ static struct pneigh_entry *pneigh_get_first(struct seq_file *seq)\n \tstate-\u003eflags |= NEIGH_SEQ_IS_PNEIGH;\n \tfor (bucket = 0; bucket \u003c= PNEIGH_HASHMASK; bucket++) {\n \t\tpn = rcu_dereference(tbl-\u003ephash_buckets[bucket]);\n-\n-\t\twhile (pn \u0026\u0026 !net_eq(pneigh_net(pn), net))\n-\t\t\tpn = rcu_dereference(pn-\u003enext);\n \t\tif (pn)\n \t\t\tbreak;\n \t}\n@@ -3361,21 +3366,15 @@ static struct pneigh_entry *pneigh_get_next(struct seq_file *seq,\n \t\t\t\t\t    loff_t *pos)\n {\n \tstruct neigh_seq_state *state = seq-\u003eprivate;\n-\tstruct net *net = seq_file_net(seq);\n \tstruct neigh_table *tbl = state-\u003etbl;\n \n-\tdo {\n-\t\tpn = rcu_dereference(pn-\u003enext);\n-\t} while (pn \u0026\u0026 !net_eq(pneigh_net(pn), net));\n+\tpn = rcu_dereference(pn-\u003enext);\n \n \twhile (!pn) {\n \t\tif (++state-\u003ebucket \u003e PNEIGH_HASHMASK)\n \t\t\tbreak;\n \n \t\tpn = rcu_dereference(tbl-\u003ephash_buckets[state-\u003ebucket]);\n-\n-\t\twhile (pn \u0026\u0026 !net_eq(pneigh_net(pn), net))\n-\t\t\tpn = rcu_dereference(pn-\u003enext);\n \t\tif (pn)\n \t\t\tbreak;\n \t}\n@@ -3430,7 +3429,6 @@ void *neigh_seq_start(struct seq_file *seq, loff_t *pos, struct neigh_table *tbl\n \n \treturn *pos ? neigh_get_idx_any(seq, pos) : SEQ_START_TOKEN;\n }\n-EXPORT_SYMBOL(neigh_seq_start);\n \n void *neigh_seq_next(struct seq_file *seq, void *v, loff_t *pos)\n {\n@@ -3457,7 +3455,6 @@ void *neigh_seq_next(struct seq_file *seq, void *v, loff_t *pos)\n \t++(*pos);\n \treturn rc;\n }\n-EXPORT_SYMBOL(neigh_seq_next);\n \n void neigh_seq_stop(struct seq_file *seq, void *v)\n \t__releases(tbl-\u003elock)\n@@ -3469,7 +3466,6 @@ void neigh_seq_stop(struct seq_file *seq, void *v)\n \tspin_unlock_bh(\u0026tbl-\u003elock);\n \trcu_read_unlock();\n }\n-EXPORT_SYMBOL(neigh_seq_stop);\n \n /* statistics via seq_file */\n \n@@ -3523,7 +3519,7 @@ static int neigh_stat_seq_show(struct seq_file *seq, void *v)\n \tseq_printf(seq, \"%08x %08lx %08lx %08lx   %08lx %08lx %08lx   \"\n \t\t\t\"%08lx         %08lx         %08lx         \"\n \t\t\t\"%08lx       %08lx            %08lx\\n\",\n-\t\t   atomic_read(\u0026tbl-\u003eentries),\n+\t\t   neigh_table_entries(tbl),\n \n \t\t   st-\u003eallocs,\n \t\t   st-\u003edestroys,\n@@ -3593,7 +3589,6 @@ void neigh_app_ns(struct neighbour *n)\n {\n \tneigh_notify(n, RTM_GETNEIGH, NLM_F_REQUEST, 0);\n }\n-EXPORT_SYMBOL(neigh_app_ns);\n \n #ifdef CONFIG_SYSCTL\n static int unres_qlen_max = INT_MAX / SKB_TRUESIZE(ETH_FRAME_LEN);\n@@ -3689,7 +3684,6 @@ int neigh_proc_dointvec(const struct ctl_table *ctl, int write, void *buffer,\n \tneigh_proc_update(ctl, write);\n \treturn ret;\n }\n-EXPORT_SYMBOL(neigh_proc_dointvec);\n \n int neigh_proc_dointvec_jiffies(const struct ctl_table *ctl, int write, void *buffer,\n \t\t\t\tsize_t *lenp, loff_t *ppos)\n@@ -3699,7 +3693,6 @@ int neigh_proc_dointvec_jiffies(const struct ctl_table *ctl, int write, void *bu\n \tneigh_proc_update(ctl, write);\n \treturn ret;\n }\n-EXPORT_SYMBOL(neigh_proc_dointvec_jiffies);\n \n static int neigh_proc_dointvec_userhz_jiffies(const struct ctl_table *ctl, int write,\n \t\t\t\t\t      void *buffer, size_t *lenp,\n@@ -3719,7 +3712,6 @@ int neigh_proc_dointvec_ms_jiffies(const struct ctl_table *ctl, int write,\n \tneigh_proc_update(ctl, write);\n \treturn ret;\n }\n-EXPORT_SYMBOL(neigh_proc_dointvec_ms_jiffies);\n \n static int neigh_proc_dointvec_unres_qlen(const struct ctl_table *ctl, int write,\n \t\t\t\t\t  void *buffer, size_t *lenp,\n@@ -3926,7 +3918,6 @@ int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p,\n err:\n \treturn -ENOBUFS;\n }\n-EXPORT_SYMBOL(neigh_sysctl_register);\n \n void neigh_sysctl_unregister(struct neigh_parms *p)\n {\n@@ -3937,7 +3928,6 @@ void neigh_sysctl_unregister(struct neigh_parms *p)\n \t\tkfree(t);\n \t}\n }\n-EXPORT_SYMBOL(neigh_sysctl_unregister);\n \n #endif\t/* CONFIG_SYSCTL */\n \ndiff --git a/net/core/sysctl_net_core.c b/net/core/sysctl_net_core.c\nindex b508618bfc123..9777b9ef58949 100644\n--- a/net/core/sysctl_net_core.c\n+++ b/net/core/sysctl_net_core.c\n@@ -21,6 +21,7 @@\n \n #include \u003cnet/ip.h\u003e\n #include \u003cnet/sock.h\u003e\n+#include \u003cnet/neighbour.h\u003e\n #include \u003cnet/net_ratelimit.h\u003e\n #include \u003cnet/busy_poll.h\u003e\n #include \u003cnet/pkt_sched.h\u003e\n@@ -53,6 +54,8 @@ EXPORT_SYMBOL(sysctl_fb_tunnels_only_for_init_net);\n int sysctl_devconf_inherit_init_net __read_mostly;\n EXPORT_SYMBOL(sysctl_devconf_inherit_init_net);\n \n+int sysctl_neigh_inherit_init_net __read_mostly = 1;\n+\n #if IS_ENABLED(CONFIG_NET_FLOW_LIMIT) || IS_ENABLED(CONFIG_RPS)\n static int dump_cpumask(void *buffer, size_t *lenp, loff_t *ppos,\n \t\t\tstruct cpumask *mask)\n@@ -676,6 +679,15 @@ static struct ctl_table net_core_table[] = {\n \t\t.proc_handler\t= proc_do_skb_defer_max,\n \t\t.extra1\t\t= SYSCTL_ZERO,\n \t},\n+\t{\n+\t\t.procname\t= \"neigh_inherit_init_net\",\n+\t\t.data\t\t= \u0026sysctl_neigh_inherit_init_net,\n+\t\t.maxlen\t\t= sizeof(int),\n+\t\t.mode\t\t= 0644,\n+\t\t.proc_handler\t= proc_dointvec_minmax,\n+\t\t.extra1\t\t= SYSCTL_ZERO,\n+\t\t.extra2\t\t= SYSCTL_ONE,\n+\t},\n };\n \n static struct ctl_table netns_core_table[] = {\ndiff --git a/net/ieee802154/6lowpan/tx.c b/net/ieee802154/6lowpan/tx.c\nindex 4df76ff50699e..4f511476b992c 100644\n--- a/net/ieee802154/6lowpan/tx.c\n+++ b/net/ieee802154/6lowpan/tx.c\n@@ -58,8 +58,9 @@ int lowpan_header_create(struct sk_buff *skb, struct net_device *ldev,\n \t\tinfo-\u003edaddr.mode = IEEE802154_ADDR_SHORT;\n \t} else {\n \t\t__le16 short_addr = cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC);\n+\t\tstruct neigh_table *tbl = nd_table(dev_net(ldev));\n \n-\t\tn = neigh_lookup(\u0026nd_tbl, \u0026hdr-\u003edaddr, ldev);\n+\t\tn = neigh_lookup(tbl, \u0026hdr-\u003edaddr, ldev);\n \t\tif (n) {\n \t\t\tllneigh = lowpan_802154_neigh(neighbour_priv(n));\n \t\t\tread_lock_bh(\u0026n-\u003elock);\ndiff --git a/net/ipv4/arp.c b/net/ipv4/arp.c\nindex d409f606aec0e..90bc53fb80905 100644\n--- a/net/ipv4/arp.c\n+++ b/net/ipv4/arp.c\n@@ -149,7 +149,7 @@ static const struct neigh_ops arp_direct_ops = {\n \t.connected_output =\tneigh_direct_output,\n };\n \n-struct neigh_table arp_tbl = {\n+static struct neigh_table arp_tbl = {\n \t.family\t\t= AF_INET,\n \t.key_len\t= 4,\n \t.protocol\t= cpu_to_be16(ETH_P_IP),\n@@ -182,7 +182,6 @@ struct neigh_table arp_tbl = {\n \t.gc_thresh2\t= 512,\n \t.gc_thresh3\t= 1024,\n };\n-EXPORT_SYMBOL(arp_tbl);\n \n int arp_mc_map(__be32 addr, u8 *haddr, struct net_device *dev, int dir)\n {\n@@ -222,14 +221,18 @@ static bool arp_key_eq(const struct neighbour *neigh, const void *pkey)\n \n static int arp_constructor(struct neighbour *neigh)\n {\n-\t__be32 addr;\n \tstruct net_device *dev = neigh-\u003edev;\n-\tstruct in_device *in_dev;\n-\tstruct neigh_parms *parms;\n+\tstruct net *net = dev_net(dev);\n \tu32 inaddr_any = INADDR_ANY;\n+\tstruct neigh_parms *parms;\n+\tstruct in_device *in_dev;\n+\tstruct neigh_table *tbl;\n+\t__be32 addr;\n+\n+\ttbl = arp_table(net);\n \n \tif (dev-\u003eflags \u0026 (IFF_LOOPBACK | IFF_POINTOPOINT))\n-\t\tmemcpy(neigh-\u003eprimary_key, \u0026inaddr_any, arp_tbl.key_len);\n+\t\tmemcpy(neigh-\u003eprimary_key, \u0026inaddr_any, tbl-\u003ekey_len);\n \n \taddr = *(__be32 *)neigh-\u003eprimary_key;\n \trcu_read_lock();\n@@ -239,7 +242,7 @@ static int arp_constructor(struct neighbour *neigh)\n \t\treturn -EINVAL;\n \t}\n \n-\tneigh-\u003etype = inet_addr_type_dev_table(dev_net(dev), dev, addr);\n+\tneigh-\u003etype = inet_addr_type_dev_table(net, dev, addr);\n \n \tparms = in_dev-\u003earp_parms;\n \t__neigh_parms_put(neigh-\u003eparms);\n@@ -701,24 +704,24 @@ static bool arp_is_garp(struct net *net, struct net_device *dev,\n \n static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)\n {\n+\tstruct neigh_table *tbl = arp_table(net);\n+\tstruct dst_entry *reply_dst = NULL;\n \tstruct net_device *dev = skb-\u003edev;\n-\tstruct in_device *in_dev = __in_dev_get_rcu(dev);\n-\tstruct arphdr *arp;\n+\tunsigned char *sha, *tha = NULL;\n+\tstruct in_device *in_dev;\n+\tu16 dev_type = dev-\u003etype;\n \tunsigned char *arp_ptr;\n+\tbool is_garp = false;\n+\tstruct neighbour *n;\n+\tstruct arphdr *arp;\n \tstruct rtable *rt;\n-\tunsigned char *sha;\n-\tunsigned char *tha = NULL;\n \t__be32 sip, tip;\n-\tu16 dev_type = dev-\u003etype;\n \tint addr_type;\n-\tstruct neighbour *n;\n-\tstruct dst_entry *reply_dst = NULL;\n-\tbool is_garp = false;\n \n \t/* arp_rcv below verifies the ARP header and verifies the device\n \t * is ARP'able.\n \t */\n-\n+\tin_dev = __in_dev_get_rcu(dev);\n \tif (!in_dev)\n \t\tgoto out_free_skb;\n \n@@ -850,7 +853,7 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)\n \t\t\tif (!dont_send \u0026\u0026 IN_DEV_ARPFILTER(in_dev))\n \t\t\t\tdont_send = arp_filter(sip, tip, dev);\n \t\t\tif (!dont_send) {\n-\t\t\t\tn = neigh_event_ns(\u0026arp_tbl, sha, \u0026sip, dev);\n+\t\t\t\tn = neigh_event_ns(tbl, sha, \u0026sip, dev);\n \t\t\t\tif (n) {\n \t\t\t\t\tarp_send_dst(ARPOP_REPLY, ETH_P_ARP,\n \t\t\t\t\t\t     sip, dev, tip, sha,\n@@ -865,8 +868,8 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)\n \t\t\t    (arp_fwd_proxy(in_dev, dev, rt) ||\n \t\t\t     arp_fwd_pvlan(in_dev, dev, rt, sip, tip) ||\n \t\t\t     (rt-\u003edst.dev != dev \u0026\u0026\n-\t\t\t      pneigh_lookup(\u0026arp_tbl, net, \u0026tip, dev)))) {\n-\t\t\t\tn = neigh_event_ns(\u0026arp_tbl, sha, \u0026sip, dev);\n+\t\t\t      pneigh_lookup(tbl, \u0026tip, dev)))) {\n+\t\t\t\tn = neigh_event_ns(tbl, sha, \u0026sip, dev);\n \t\t\t\tif (n)\n \t\t\t\t\tneigh_release(n);\n \n@@ -878,7 +881,7 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)\n \t\t\t\t\t\t     dev-\u003edev_addr, sha,\n \t\t\t\t\t\t     reply_dst);\n \t\t\t\t} else {\n-\t\t\t\t\tpneigh_enqueue(\u0026arp_tbl,\n+\t\t\t\t\tpneigh_enqueue(tbl,\n \t\t\t\t\t\t       in_dev-\u003earp_parms, skb);\n \t\t\t\t\tgoto out_free_dst;\n \t\t\t\t}\n@@ -889,7 +892,7 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)\n \n \t/* Update our ARP tables */\n \n-\tn = __neigh_lookup(\u0026arp_tbl, \u0026sip, dev, 0);\n+\tn = __neigh_lookup(tbl, \u0026sip, dev, 0);\n \n \taddr_type = -1;\n \tif (n || arp_accept(in_dev, sip)) {\n@@ -910,7 +913,7 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)\n \t\t\t/* postpone calculation to as late as possible */\n \t\t\tinet_addr_type_dev_table(net, dev, sip) ==\n \t\t\t\tRTN_UNICAST)))))\n-\t\t\tn = __neigh_lookup(\u0026arp_tbl, \u0026sip, dev, 1);\n+\t\t\tn = __neigh_lookup(tbl, \u0026sip, dev, 1);\n \t}\n \n \tif (n) {\n@@ -1077,9 +1080,10 @@ static int arp_req_set_proxy(struct net *net, struct net_device *dev, int on)\n }\n \n static int arp_req_set_public(struct net *net, struct arpreq *r,\n-\t\tstruct net_device *dev)\n+\t\t\t      struct net_device *dev)\n {\n \t__be32 mask = ((struct sockaddr_in *)\u0026r-\u003earp_netmask)-\u003esin_addr.s_addr;\n+\tstruct neigh_table *tbl = arp_table(net);\n \n \tif (!dev \u0026\u0026 (r-\u003earp_flags \u0026 ATF_COM)) {\n \t\tdev = dev_getbyhwaddr(net, r-\u003earp_ha.sa_family,\n@@ -1090,7 +1094,7 @@ static int arp_req_set_public(struct net *net, struct arpreq *r,\n \tif (mask) {\n \t\t__be32 ip = ((struct sockaddr_in *)\u0026r-\u003earp_pa)-\u003esin_addr.s_addr;\n \n-\t\treturn pneigh_create(\u0026arp_tbl, net, \u0026ip, dev, 0, 0, false);\n+\t\treturn pneigh_create(tbl, \u0026ip, dev, 0, 0, false);\n \t}\n \n \treturn arp_req_set_proxy(net, dev, 1);\n@@ -1098,6 +1102,7 @@ static int arp_req_set_public(struct net *net, struct arpreq *r,\n \n static int arp_req_set(struct net *net, struct arpreq *r)\n {\n+\tstruct neigh_table *tbl = arp_table(net);\n \tstruct neighbour *neigh;\n \tstruct net_device *dev;\n \t__be32 ip;\n@@ -1133,7 +1138,7 @@ static int arp_req_set(struct net *net, struct arpreq *r)\n \n \tip = ((struct sockaddr_in *)\u0026r-\u003earp_pa)-\u003esin_addr.s_addr;\n \n-\tneigh = __neigh_lookup_errno(\u0026arp_tbl, \u0026ip, dev);\n+\tneigh = __neigh_lookup_errno(tbl, \u0026ip, dev);\n \terr = PTR_ERR(neigh);\n \tif (!IS_ERR(neigh)) {\n \t\tunsigned int state = NUD_STALE;\n@@ -1169,6 +1174,7 @@ static unsigned int arp_state_to_flags(struct neighbour *neigh)\n static int arp_req_get(struct net *net, struct arpreq *r)\n {\n \t__be32 ip = ((struct sockaddr_in *) \u0026r-\u003earp_pa)-\u003esin_addr.s_addr;\n+\tstruct neigh_table *tbl = arp_table(net);\n \tstruct neighbour *neigh;\n \tstruct net_device *dev;\n \n@@ -1179,7 +1185,7 @@ static int arp_req_get(struct net *net, struct arpreq *r)\n \tif (IS_ERR(dev))\n \t\treturn PTR_ERR(dev);\n \n-\tneigh = neigh_lookup(\u0026arp_tbl, \u0026ip, dev);\n+\tneigh = neigh_lookup(tbl, \u0026ip, dev);\n \tif (!neigh)\n \t\treturn -ENXIO;\n \n@@ -1204,10 +1210,11 @@ static int arp_req_get(struct net *net, struct arpreq *r)\n \n int arp_invalidate(struct net_device *dev, __be32 ip, bool force)\n {\n-\tstruct neighbour *neigh = neigh_lookup(\u0026arp_tbl, \u0026ip, dev);\n+\tstruct neigh_table *tbl = arp_table(dev_net(dev));\n+\tstruct neighbour *neigh;\n \tint err = -ENXIO;\n-\tstruct neigh_table *tbl = \u0026arp_tbl;\n \n+\tneigh = neigh_lookup(tbl, \u0026ip, dev);\n \tif (neigh) {\n \t\tif ((READ_ONCE(neigh-\u003enud_state) \u0026 NUD_VALID) \u0026\u0026 !force) {\n \t\t\tneigh_release(neigh);\n@@ -1228,14 +1235,15 @@ int arp_invalidate(struct net_device *dev, __be32 ip, bool force)\n }\n \n static int arp_req_delete_public(struct net *net, struct arpreq *r,\n-\t\tstruct net_device *dev)\n+\t\t\t\t struct net_device *dev)\n {\n \t__be32 mask = ((struct sockaddr_in *)\u0026r-\u003earp_netmask)-\u003esin_addr.s_addr;\n+\tstruct neigh_table *tbl = arp_table(net);\n \n \tif (mask) {\n \t\t__be32 ip = ((struct sockaddr_in *)\u0026r-\u003earp_pa)-\u003esin_addr.s_addr;\n \n-\t\treturn pneigh_delete(\u0026arp_tbl, net, \u0026ip, dev);\n+\t\treturn pneigh_delete(tbl, \u0026ip, dev);\n \t}\n \n \treturn arp_req_set_proxy(net, dev, 0);\n@@ -1325,18 +1333,22 @@ static int arp_netdev_event(struct notifier_block *this, unsigned long event,\n {\n \tstruct net_device *dev = netdev_notifier_info_to_dev(ptr);\n \tstruct netdev_notifier_change_info *change_info;\n+\tstruct net *net = dev_net(dev);\n \tstruct in_device *in_dev;\n+\tstruct neigh_table *tbl;\n \tbool evict_nocarrier;\n \n+\ttbl = arp_table(net);\n+\n \tswitch (event) {\n \tcase NETDEV_CHANGEADDR:\n-\t\tneigh_changeaddr(\u0026arp_tbl, dev);\n-\t\trt_cache_flush(dev_net(dev));\n+\t\tneigh_changeaddr(tbl, dev);\n+\t\trt_cache_flush(net);\n \t\tbreak;\n \tcase NETDEV_CHANGE:\n \t\tchange_info = ptr;\n \t\tif (change_info-\u003eflags_changed \u0026 IFF_NOARP)\n-\t\t\tneigh_changeaddr(\u0026arp_tbl, dev);\n+\t\t\tneigh_changeaddr(tbl, dev);\n \n \t\tin_dev = __in_dev_get_rtnl(dev);\n \t\tif (!in_dev)\n@@ -1345,7 +1357,7 @@ static int arp_netdev_event(struct notifier_block *this, unsigned long event,\n \t\t\tevict_nocarrier = IN_DEV_ARP_EVICT_NOCARRIER(in_dev);\n \n \t\tif (evict_nocarrier \u0026\u0026 !netif_carrier_ok(dev))\n-\t\t\tneigh_carrier_down(\u0026arp_tbl, dev);\n+\t\t\tneigh_carrier_down(tbl, dev);\n \t\tbreak;\n \tdefault:\n \t\tbreak;\n@@ -1364,7 +1376,9 @@ static struct notifier_block arp_netdev_notifier = {\n  */\n void arp_ifdown(struct net_device *dev)\n {\n-\tneigh_ifdown(\u0026arp_tbl, dev);\n+\tstruct neigh_table *tbl = arp_table(dev_net(dev));\n+\n+\tneigh_ifdown(tbl, dev);\n }\n \n \n@@ -1479,10 +1493,12 @@ static int arp_seq_show(struct seq_file *seq, void *v)\n \n static void *arp_seq_start(struct seq_file *seq, loff_t *pos)\n {\n+\tstruct neigh_table *tbl = arp_table(seq_file_net(seq));\n+\n \t/* Don't want to confuse \"arp -a\" w/ magic entries,\n \t * so we tell the generic iterator to skip NUD_NOARP.\n \t */\n-\treturn neigh_seq_start(seq, pos, \u0026arp_tbl, NEIGH_SEQ_SKIP_NOARP);\n+\treturn neigh_seq_start(seq, pos, tbl, NEIGH_SEQ_SKIP_NOARP);\n }\n \n static const struct seq_operations arp_seq_ops = {\n@@ -1495,15 +1511,47 @@ static const struct seq_operations arp_seq_ops = {\n \n static int __net_init arp_net_init(struct net *net)\n {\n+\tint err;\n+\n+\terr = neigh_table_register(net, \u0026arp_tbl, NEIGH_ARP_TABLE);\n+\tif (err)\n+\t\tgoto err;\n+\n+#ifdef CONFIG_PROC_FS\n+#ifdef CONFIG_SYSCTL\n+\terr = neigh_sysctl_register(NULL, \u0026arp_table(net)-\u003eparms, NULL);\n+\tif (err)\n+\t\tgoto err_sysctl;\n+#endif\n+\n \tif (!proc_create_net(\"arp\", 0444, net-\u003eproc_net, \u0026arp_seq_ops,\n-\t\t\tsizeof(struct neigh_seq_state)))\n-\t\treturn -ENOMEM;\n+\t\t\t     sizeof(struct neigh_seq_state))) {\n+\t\terr = -ENOMEM;\n+\t\tgoto err_proc_create;\n+\t}\n+#endif\n+\n \treturn 0;\n+\n+#ifdef CONFIG_PROC_FS\n+err_proc_create:\n+#ifdef CONFIG_SYSCTL\n+\tneigh_sysctl_unregister(\u0026arp_table(net)-\u003eparms);\n+err_sysctl:\n+#endif\n+\tneigh_table_unregister(net, NEIGH_ARP_TABLE);\n+#endif\n+err:\n+\treturn err;\n }\n \n static void __net_exit arp_net_exit(struct net *net)\n {\n \tremove_proc_entry(\"arp\", net-\u003eproc_net);\n+#ifdef CONFIG_SYSCTL\n+\tneigh_sysctl_unregister(\u0026arp_table(net)-\u003eparms);\n+#endif\n+\tneigh_table_unregister(net, NEIGH_ARP_TABLE);\n }\n \n static struct pernet_operations arp_net_ops = {\n@@ -1513,12 +1561,9 @@ static struct pernet_operations arp_net_ops = {\n \n void __init arp_init(void)\n {\n-\tneigh_table_init(NEIGH_ARP_TABLE, \u0026arp_tbl);\n+\tif (register_pernet_subsys(\u0026arp_net_ops))\n+\t\tpanic(\"Cannot allocate arp table\\n\");\n \n \tdev_add_pack(\u0026arp_packet_type);\n-\tregister_pernet_subsys(\u0026arp_net_ops);\n-#ifdef CONFIG_SYSCTL\n-\tneigh_sysctl_register(NULL, \u0026arp_tbl.parms, NULL);\n-#endif\n \tregister_netdevice_notifier(\u0026arp_netdev_notifier);\n }\ndiff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c\nindex 47ded0f607d4b..05de597ce086f 100644\n--- a/net/ipv4/devinet.c\n+++ b/net/ipv4/devinet.c\n@@ -265,7 +265,9 @@ EXPORT_SYMBOL(in_dev_finish_destroy);\n \n static struct in_device *inetdev_init(struct net_device *dev)\n {\n+\tstruct net *net = dev_net(dev);\n \tstruct in_device *in_dev;\n+\tstruct neigh_table *tbl;\n \tint err = -ENOMEM;\n \n \tASSERT_RTNL();\n@@ -273,11 +275,12 @@ static struct in_device *inetdev_init(struct net_device *dev)\n \tin_dev = kzalloc_obj(*in_dev);\n \tif (!in_dev)\n \t\tgoto out;\n-\tmemcpy(\u0026in_dev-\u003ecnf, dev_net(dev)-\u003eipv4.devconf_dflt,\n-\t\t\tsizeof(in_dev-\u003ecnf));\n+\n+\ttbl = arp_table(net);\n+\tmemcpy(\u0026in_dev-\u003ecnf, net-\u003eipv4.devconf_dflt, sizeof(in_dev-\u003ecnf));\n \tin_dev-\u003ecnf.sysctl = NULL;\n \tin_dev-\u003edev = dev;\n-\tin_dev-\u003earp_parms = neigh_parms_alloc(dev, \u0026arp_tbl);\n+\tin_dev-\u003earp_parms = neigh_parms_alloc(dev, tbl);\n \tif (!in_dev-\u003earp_parms)\n \t\tgoto out_kfree;\n \tif (IPV4_DEVCONF(in_dev-\u003ecnf, FORWARDING))\n@@ -291,7 +294,7 @@ static struct in_device *inetdev_init(struct net_device *dev)\n \t\terr = devinet_sysctl_register(in_dev);\n \t\tif (err) {\n \t\t\tin_dev-\u003edead = 1;\n-\t\t\tneigh_parms_release(\u0026arp_tbl, in_dev-\u003earp_parms);\n+\t\t\tneigh_parms_release(tbl, in_dev-\u003earp_parms);\n \t\t\tin_dev_put(in_dev);\n \t\t\tin_dev = NULL;\n \t\t\tgoto out;\n@@ -313,13 +316,12 @@ static struct in_device *inetdev_init(struct net_device *dev)\n \n static void inetdev_destroy(struct in_device *in_dev)\n {\n-\tstruct net_device *dev;\n+\tstruct net_device *dev = in_dev-\u003edev;\n+\tstruct net *net = dev_net(dev);\n \tstruct in_ifaddr *ifa;\n \n \tASSERT_RTNL();\n \n-\tdev = in_dev-\u003edev;\n-\n \tin_dev-\u003edead = 1;\n \n \tRCU_INIT_POINTER(dev-\u003eip_ptr, NULL);\n@@ -332,7 +334,7 @@ static void inetdev_destroy(struct in_device *in_dev)\n \t}\n \n \tdevinet_sysctl_unregister(in_dev);\n-\tneigh_parms_release(\u0026arp_tbl, in_dev-\u003earp_parms);\n+\tneigh_parms_release(arp_table(net), in_dev-\u003earp_parms);\n \tarp_ifdown(dev);\n \n \tin_dev_put(in_dev);\ndiff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c\nindex 78f84ae3ee120..cda150309dc6b 100644\n--- a/net/ipv4/fib_semantics.c\n+++ b/net/ipv4/fib_semantics.c\n@@ -610,13 +610,14 @@ static int fib_detect_death(struct fib_info *fi, int order,\n \t\t\t    int dflt)\n {\n \tconst struct fib_nh_common *nhc = fib_info_nhc(fi, 0);\n-\tstruct neighbour *n;\n+\tstruct net *net = fi-\u003efib_net;\n \tint state = NUD_NONE;\n+\tstruct neighbour *n;\n \n \tif (likely(nhc-\u003enhc_gw_family == AF_INET))\n-\t\tn = neigh_lookup(\u0026arp_tbl, \u0026nhc-\u003enhc_gw.ipv4, nhc-\u003enhc_dev);\n+\t\tn = neigh_lookup(arp_table(net), \u0026nhc-\u003enhc_gw.ipv4, nhc-\u003enhc_dev);\n \telse if (IS_ENABLED(CONFIG_IPV6) \u0026\u0026 nhc-\u003enhc_gw_family == AF_INET6)\n-\t\tn = neigh_lookup(\u0026nd_tbl, \u0026nhc-\u003enhc_gw.ipv6, nhc-\u003enhc_dev);\n+\t\tn = neigh_lookup(nd_table(net), \u0026nhc-\u003enhc_gw.ipv6, nhc-\u003enhc_dev);\n \telse\n \t\tn = NULL;\n \ndiff --git a/net/ipv4/route.c b/net/ipv4/route.c\nindex fd688e1f879f5..b8b6b5d991294 100644\n--- a/net/ipv4/route.c\n+++ b/net/ipv4/route.c\n@@ -788,7 +788,7 @@ static void __ip_do_redirect(struct rtable *rt, struct sk_buff *skb, struct flow\n \n \tn = __ipv4_neigh_lookup(rt-\u003edst.dev, (__force u32)new_gw);\n \tif (!n)\n-\t\tn = neigh_create(\u0026arp_tbl, \u0026new_gw, rt-\u003edst.dev);\n+\t\tn = neigh_create(arp_table(net), \u0026new_gw, rt-\u003edst.dev);\n \tif (!IS_ERR(n)) {\n \t\tif (!(READ_ONCE(n-\u003enud_state) \u0026 NUD_VALID)) {\n \t\t\tneigh_event_send(n, NULL);\ndiff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c\nindex f6fa2715b450b..f3be7a8f996dd 100644\n--- a/net/ipv6/addrconf.c\n+++ b/net/ipv6/addrconf.c\n@@ -376,6 +376,8 @@ static int snmp6_alloc_dev(struct inet6_dev *idev)\n \n static struct inet6_dev *ipv6_add_dev(struct net_device *dev)\n {\n+\tstruct net *net = dev_net(dev);\n+\tstruct neigh_table *tbl;\n \tstruct inet6_dev *ndev;\n \tint err = -ENOMEM;\n \n@@ -393,14 +395,15 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev)\n \tndev-\u003edev = dev;\n \tINIT_LIST_HEAD(\u0026ndev-\u003eaddr_list);\n \ttimer_setup(\u0026ndev-\u003ers_timer, addrconf_rs_timer, 0);\n-\tmemcpy(\u0026ndev-\u003ecnf, dev_net(dev)-\u003eipv6.devconf_dflt, sizeof(ndev-\u003ecnf));\n+\tmemcpy(\u0026ndev-\u003ecnf, net-\u003eipv6.devconf_dflt, sizeof(ndev-\u003ecnf));\n \n \tif (ndev-\u003ecnf.stable_secret.initialized)\n \t\tndev-\u003ecnf.addr_gen_mode = IN6_ADDR_GEN_MODE_STABLE_PRIVACY;\n \n+\ttbl = nd_table(net);\n \tndev-\u003ecnf.mtu6 = dev-\u003emtu;\n \tndev-\u003era_mtu = 0;\n-\tndev-\u003end_parms = neigh_parms_alloc(dev, \u0026nd_tbl);\n+\tndev-\u003end_parms = neigh_parms_alloc(dev, tbl);\n \tif (!ndev-\u003end_parms) {\n \t\tkfree(ndev);\n \t\treturn ERR_PTR(err);\n@@ -413,7 +416,7 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev)\n \tif (snmp6_alloc_dev(ndev) \u003c 0) {\n \t\tnetdev_dbg(dev, \"%s: cannot allocate memory for statistics\\n\",\n \t\t\t   __func__);\n-\t\tneigh_parms_release(\u0026nd_tbl, ndev-\u003end_parms);\n+\t\tneigh_parms_release(tbl, ndev-\u003end_parms);\n \t\tnetdev_put(dev, \u0026ndev-\u003edev_tracker);\n \t\tkfree(ndev);\n \t\treturn ERR_PTR(err);\n@@ -481,7 +484,7 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev)\n \treturn ndev;\n \n err_release:\n-\tneigh_parms_release(\u0026nd_tbl, ndev-\u003end_parms);\n+\tneigh_parms_release(tbl, ndev-\u003end_parms);\n \tndev-\u003edead = 1;\n \tin6_dev_finish_destroy(ndev);\n \treturn ERR_PTR(err);\n@@ -4038,9 +4041,11 @@ static int addrconf_ifdown(struct net_device *dev, bool unregister)\n \n \t/* Last: Shot the device (if unregistered) */\n \tif (unregister) {\n+\t\tstruct neigh_table *tbl = nd_table(net);\n+\n \t\taddrconf_sysctl_unregister(idev);\n-\t\tneigh_parms_release(\u0026nd_tbl, idev-\u003end_parms);\n-\t\tneigh_ifdown(\u0026nd_tbl, dev);\n+\t\tneigh_parms_release(tbl, idev-\u003end_parms);\n+\t\tneigh_ifdown(tbl, dev);\n \t\tin6_dev_put(idev);\n \t}\n \treturn 0;\ndiff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c\nindex 2c44e5ed61716..25fc57e52b5fa 100644\n--- a/net/ipv6/ip6_output.c\n+++ b/net/ipv6/ip6_output.c\n@@ -125,7 +125,7 @@ static int ip6_finish_output2(struct net *net, struct sock *sk, struct sk_buff *\n \n \tif (IS_ERR_OR_NULL(neigh)) {\n \t\tif (unlikely(!neigh))\n-\t\t\tneigh = __neigh_create(\u0026nd_tbl, nexthop, dev, false);\n+\t\t\tneigh = __neigh_create(nd_table(net), nexthop, dev, false);\n \t\tif (IS_ERR(neigh)) {\n \t\t\tIP6_INC_STATS(net, idev, IPSTATS_MIB_OUTNOROUTES);\n \t\t\tkfree_skb_reason(skb, SKB_DROP_REASON_NEIGH_CREATEFAIL);\n@@ -581,7 +581,7 @@ int ip6_forward(struct sk_buff *skb)\n \n \t/* XXX: idev-\u003ecnf.proxy_ndp? */\n \tif (READ_ONCE(net-\u003eipv6.devconf_all-\u003eproxy_ndp) \u0026\u0026\n-\t    pneigh_lookup(\u0026nd_tbl, net, \u0026hdr-\u003edaddr, skb-\u003edev)) {\n+\t    pneigh_lookup(nd_table(net), \u0026hdr-\u003edaddr, skb-\u003edev)) {\n \t\tint proxied = ip6_forward_proxy_check(skb);\n \n \t\thdr = ipv6_hdr(skb);\ndiff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c\nindex 2ceb655c4229e..022c2296807a3 100644\n--- a/net/ipv6/ndisc.c\n+++ b/net/ipv6/ndisc.c\n@@ -767,10 +767,11 @@ static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb)\n static int pndisc_is_router(const void *pkey,\n \t\t\t    struct net_device *dev)\n {\n+\tstruct net *net = dev_net(dev);\n \tstruct pneigh_entry *n;\n \tint ret = -1;\n \n-\tn = pneigh_lookup(\u0026nd_tbl, dev_net(dev), pkey, dev);\n+\tn = pneigh_lookup(nd_table(net), pkey, dev);\n \tif (n)\n \t\tret = !!(READ_ONCE(n-\u003eflags) \u0026 NTF_ROUTER);\n \n@@ -788,19 +789,21 @@ void ndisc_update(const struct net_device *dev, struct neighbour *neigh,\n \n static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)\n {\n+\tu32 ndoptlen = skb_tail_pointer(skb) - (skb_transport_header(skb) +\n+\t\t\t\t    offsetof(struct nd_msg, opt));\n \tstruct nd_msg *msg = (struct nd_msg *)skb_transport_header(skb);\n \tconst struct in6_addr *saddr = \u0026ipv6_hdr(skb)-\u003esaddr;\n \tconst struct in6_addr *daddr = \u0026ipv6_hdr(skb)-\u003edaddr;\n-\tu8 *lladdr = NULL;\n-\tu32 ndoptlen = skb_tail_pointer(skb) - (skb_transport_header(skb) +\n-\t\t\t\t    offsetof(struct nd_msg, opt));\n-\tstruct ndisc_options ndopts;\n \tstruct net_device *dev = skb-\u003edev;\n-\tstruct inet6_ifaddr *ifp;\n+\tstruct net *net = dev_net(dev);\n+\tint dad = ipv6_addr_any(saddr);\n \tstruct inet6_dev *idev = NULL;\n+\tstruct ndisc_options ndopts;\n+\tstruct inet6_ifaddr *ifp;\n+\tstruct neigh_table *tbl;\n \tstruct neighbour *neigh;\n-\tint dad = ipv6_addr_any(saddr);\n \tint is_router = -1;\n+\tu8 *lladdr = NULL;\n \tSKB_DR(reason);\n \tu64 nonce = 0;\n \tbool inc;\n@@ -845,9 +848,10 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)\n \tif (ndopts.nd_opts_nonce \u0026\u0026 ndopts.nd_opts_nonce-\u003end_opt_len == 1)\n \t\tmemcpy(\u0026nonce, (u8 *)(ndopts.nd_opts_nonce + 1), 6);\n \n-\tinc = ipv6_addr_is_multicast(daddr);\n+\ttbl = nd_table(net);\n \n-\tifp = ipv6_get_ifaddr(dev_net(dev), \u0026msg-\u003etarget, dev, 1);\n+\tinc = ipv6_addr_is_multicast(daddr);\n+\tifp = ipv6_get_ifaddr(net, \u0026msg-\u003etarget, dev, 1);\n \tif (ifp) {\n have_ifp:\n \t\tif (ifp-\u003eflags \u0026 (IFA_F_TENTATIVE|IFA_F_OPTIMISTIC)) {\n@@ -880,8 +884,6 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)\n \n \t\tidev = ifp-\u003eidev;\n \t} else {\n-\t\tstruct net *net = dev_net(dev);\n-\n \t\t/* perhaps an address on the master device */\n \t\tif (netif_is_l3_slave(dev)) {\n \t\t\tstruct net_device *mdev;\n@@ -918,7 +920,7 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)\n \t\t\t\t */\n \t\t\t\tstruct sk_buff *n = skb_clone(skb, GFP_ATOMIC);\n \t\t\t\tif (n)\n-\t\t\t\t\tpneigh_enqueue(\u0026nd_tbl, idev-\u003end_parms, n);\n+\t\t\t\t\tpneigh_enqueue(tbl, idev-\u003end_parms, n);\n \t\t\t\tgoto out;\n \t\t\t}\n \t\t} else {\n@@ -937,15 +939,15 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)\n \t}\n \n \tif (inc)\n-\t\tNEIGH_CACHE_STAT_INC(\u0026nd_tbl, rcv_probes_mcast);\n+\t\tNEIGH_CACHE_STAT_INC(tbl, rcv_probes_mcast);\n \telse\n-\t\tNEIGH_CACHE_STAT_INC(\u0026nd_tbl, rcv_probes_ucast);\n+\t\tNEIGH_CACHE_STAT_INC(tbl, rcv_probes_ucast);\n \n \t/*\n \t *\tupdate / create cache entry\n \t *\tfor the source address\n \t */\n-\tneigh = __neigh_lookup(\u0026nd_tbl, saddr, dev,\n+\tneigh = __neigh_lookup(tbl, saddr, dev,\n \t\t\t       !inc || lladdr || !dev-\u003eaddr_len);\n \tif (neigh)\n \t\tndisc_update(dev, neigh, lladdr, NUD_STALE,\n@@ -987,17 +989,19 @@ static int accept_untracked_na(struct inet6_dev *idev, struct in6_addr *saddr)\n \n static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)\n {\n-\tstruct nd_msg *msg = (struct nd_msg *)skb_transport_header(skb);\n-\tstruct in6_addr *saddr = \u0026ipv6_hdr(skb)-\u003esaddr;\n-\tconst struct in6_addr *daddr = \u0026ipv6_hdr(skb)-\u003edaddr;\n-\tu8 *lladdr = NULL;\n \tu32 ndoptlen = skb_tail_pointer(skb) - (skb_transport_header(skb) +\n \t\t\t\t    offsetof(struct nd_msg, opt));\n-\tstruct ndisc_options ndopts;\n+\tstruct nd_msg *msg = (struct nd_msg *)skb_transport_header(skb);\n+\tconst struct in6_addr *daddr = \u0026ipv6_hdr(skb)-\u003edaddr;\n+\tstruct in6_addr *saddr = \u0026ipv6_hdr(skb)-\u003esaddr;\n \tstruct net_device *dev = skb-\u003edev;\n-\tstruct inet6_dev *idev = __in6_dev_get(dev);\n+\tstruct net *net = dev_net(dev);\n+\tstruct ndisc_options ndopts;\n \tstruct inet6_ifaddr *ifp;\n+\tstruct neigh_table *tbl;\n \tstruct neighbour *neigh;\n+\tstruct inet6_dev *idev;\n+\tu8 *lladdr = NULL;\n \tSKB_DR(reason);\n \tu8 new_state;\n \n@@ -1020,6 +1024,7 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)\n \t * and thus should not be accepted.\n \t * drop_unsolicited_na takes precedence over accept_untracked_na\n \t */\n+\tidev = __in6_dev_get(dev);\n \tif (!msg-\u003eicmph.icmp6_solicited \u0026\u0026 idev \u0026\u0026\n \t    READ_ONCE(idev-\u003ecnf.drop_unsolicited_na))\n \t\treturn reason;\n@@ -1034,7 +1039,7 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)\n \t\t\treturn reason;\n \t\t}\n \t}\n-\tifp = ipv6_get_ifaddr(dev_net(dev), \u0026msg-\u003etarget, dev, 1);\n+\tifp = ipv6_get_ifaddr(net, \u0026msg-\u003etarget, dev, 1);\n \tif (ifp) {\n \t\tif (skb-\u003epkt_type != PACKET_LOOPBACK\n \t\t    \u0026\u0026 (ifp-\u003eflags \u0026 IFA_F_TENTATIVE)) {\n@@ -1058,7 +1063,8 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)\n \t\treturn reason;\n \t}\n \n-\tneigh = neigh_lookup(\u0026nd_tbl, \u0026msg-\u003etarget, dev);\n+\ttbl = nd_table(net);\n+\tneigh = neigh_lookup(tbl, \u0026msg-\u003etarget, dev);\n \n \t/* RFC 9131 updates original Neighbour Discovery RFC 4861.\n \t * NAs with Target LL Address option without a corresponding\n@@ -1078,14 +1084,13 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)\n \tnew_state = msg-\u003eicmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE;\n \tif (!neigh \u0026\u0026 lladdr \u0026\u0026 idev \u0026\u0026 READ_ONCE(idev-\u003ecnf.forwarding)) {\n \t\tif (accept_untracked_na(idev, saddr)) {\n-\t\t\tneigh = neigh_create(\u0026nd_tbl, \u0026msg-\u003etarget, dev);\n+\t\t\tneigh = neigh_create(tbl, \u0026msg-\u003etarget, dev);\n \t\t\tnew_state = NUD_STALE;\n \t\t}\n \t}\n \n \tif (neigh \u0026\u0026 !IS_ERR(neigh)) {\n \t\tu8 old_flags = neigh-\u003eflags;\n-\t\tstruct net *net = dev_net(dev);\n \n \t\tif (READ_ONCE(neigh-\u003enud_state) \u0026 NUD_FAILED)\n \t\t\tgoto out;\n@@ -1098,7 +1103,7 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)\n \t\tif (lladdr \u0026\u0026 !memcmp(lladdr, dev-\u003edev_addr, dev-\u003eaddr_len) \u0026\u0026\n \t\t    READ_ONCE(net-\u003eipv6.devconf_all-\u003eforwarding) \u0026\u0026\n \t\t    READ_ONCE(net-\u003eipv6.devconf_all-\u003eproxy_ndp) \u0026\u0026\n-\t\t    pneigh_lookup(\u0026nd_tbl, net, \u0026msg-\u003etarget, dev)) {\n+\t\t    pneigh_lookup(tbl, \u0026msg-\u003etarget, dev)) {\n \t\t\t/* XXX: idev-\u003ecnf.proxy_ndp */\n \t\t\tgoto out;\n \t\t}\n@@ -1127,18 +1132,20 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)\n static enum skb_drop_reason ndisc_recv_rs(struct sk_buff *skb)\n {\n \tstruct rs_msg *rs_msg = (struct rs_msg *)skb_transport_header(skb);\n+\tconst struct in6_addr *saddr = \u0026ipv6_hdr(skb)-\u003esaddr;\n \tunsigned long ndoptlen = skb-\u003elen - sizeof(*rs_msg);\n+\tstruct net_device *dev = skb-\u003edev;\n+\tstruct ndisc_options ndopts;\n+\tstruct neigh_table *tbl;\n \tstruct neighbour *neigh;\n \tstruct inet6_dev *idev;\n-\tconst struct in6_addr *saddr = \u0026ipv6_hdr(skb)-\u003esaddr;\n-\tstruct ndisc_options ndopts;\n \tu8 *lladdr = NULL;\n \tSKB_DR(reason);\n \n \tif (skb-\u003elen \u003c sizeof(*rs_msg))\n \t\treturn SKB_DROP_REASON_PKT_TOO_SMALL;\n \n-\tidev = __in6_dev_get(skb-\u003edev);\n+\tidev = __in6_dev_get(dev);\n \tif (!idev) {\n \t\tnet_err_ratelimited(\"RS: can't find in6 device\\n\");\n \t\treturn reason;\n@@ -1156,19 +1163,19 @@ static enum skb_drop_reason ndisc_recv_rs(struct sk_buff *skb)\n \t\tgoto out;\n \n \t/* Parse ND options */\n-\tif (!ndisc_parse_options(skb-\u003edev, rs_msg-\u003eopt, ndoptlen, \u0026ndopts))\n+\tif (!ndisc_parse_options(dev, rs_msg-\u003eopt, ndoptlen, \u0026ndopts))\n \t\treturn SKB_DROP_REASON_IPV6_NDISC_BAD_OPTIONS;\n \n \tif (ndopts.nd_opts_src_lladdr) {\n-\t\tlladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr,\n-\t\t\t\t\t     skb-\u003edev);\n+\t\tlladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr, dev);\n \t\tif (!lladdr)\n \t\t\tgoto out;\n \t}\n \n-\tneigh = __neigh_lookup(\u0026nd_tbl, saddr, skb-\u003edev, 1);\n+\ttbl = nd_table(dev_net(dev));\n+\tneigh = __neigh_lookup(tbl, saddr, dev, 1);\n \tif (neigh) {\n-\t\tndisc_update(skb-\u003edev, neigh, lladdr, NUD_STALE,\n+\t\tndisc_update(dev, neigh, lladdr, NUD_STALE,\n \t\t\t     NEIGH_UPDATE_F_WEAK_OVERRIDE|\n \t\t\t     NEIGH_UPDATE_F_OVERRIDE|\n \t\t\t     NEIGH_UPDATE_F_OVERRIDE_ISROUTER,\n@@ -1232,6 +1239,7 @@ static void ndisc_ra_useropt(struct sk_buff *ra, struct nd_opt_hdr *opt)\n static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)\n {\n \tstruct ra_msg *ra_msg = (struct ra_msg *)skb_transport_header(skb);\n+\tstruct net *net = dev_net(skb-\u003edev);\n \tbool send_ifinfo_notify = false;\n \tstruct neighbour *neigh = NULL;\n \tstruct ndisc_options ndopts;\n@@ -1241,7 +1249,6 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)\n \tu32 defrtr_usr_metric;\n \tunsigned int pref = 0;\n \t__u32 old_if_flags;\n-\tstruct net *net;\n \tSKB_DR(reason);\n \tint lifetime;\n \tint optlen;\n@@ -1330,7 +1337,6 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)\n \t/* Do not accept RA with source-addr found on local machine unless\n \t * accept_ra_from_local is set to true.\n \t */\n-\tnet = dev_net(in6_dev-\u003edev);\n \tif (!READ_ONCE(in6_dev-\u003ecnf.accept_ra_from_local) \u0026\u0026\n \t    ipv6_chk_addr(net, \u0026ipv6_hdr(skb)-\u003esaddr, in6_dev-\u003edev, 0)) {\n \t\tnet_dbg_ratelimited(\"RA from local address detected on dev: %s: default router ignored\\n\",\n@@ -1466,7 +1472,7 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)\n \t */\n \n \tif (!neigh)\n-\t\tneigh = __neigh_lookup(\u0026nd_tbl, \u0026ipv6_hdr(skb)-\u003esaddr,\n+\t\tneigh = __neigh_lookup(nd_table(net), \u0026ipv6_hdr(skb)-\u003esaddr,\n \t\t\t\t       skb-\u003edev, 1);\n \tif (neigh) {\n \t\tu8 *lladdr = NULL;\n@@ -1859,12 +1865,15 @@ static int ndisc_netdev_event(struct notifier_block *this, unsigned long event,\n \tstruct net_device *dev = netdev_notifier_info_to_dev(ptr);\n \tstruct netdev_notifier_change_info *change_info;\n \tstruct net *net = dev_net(dev);\n+\tstruct neigh_table *tbl;\n \tstruct inet6_dev *idev;\n \tbool evict_nocarrier;\n \n+\ttbl = nd_table(net);\n+\n \tswitch (event) {\n \tcase NETDEV_CHANGEADDR:\n-\t\tneigh_changeaddr(\u0026nd_tbl, dev);\n+\t\tneigh_changeaddr(tbl, dev);\n \t\tfib6_run_gc(0, net, false);\n \t\tfallthrough;\n \tcase NETDEV_UP:\n@@ -1888,12 +1897,12 @@ static int ndisc_netdev_event(struct notifier_block *this, unsigned long event,\n \n \t\tchange_info = ptr;\n \t\tif (change_info-\u003eflags_changed \u0026 IFF_NOARP)\n-\t\t\tneigh_changeaddr(\u0026nd_tbl, dev);\n+\t\t\tneigh_changeaddr(tbl, dev);\n \t\tif (evict_nocarrier \u0026\u0026 !netif_carrier_ok(dev))\n-\t\t\tneigh_carrier_down(\u0026nd_tbl, dev);\n+\t\t\tneigh_carrier_down(tbl, dev);\n \t\tbreak;\n \tcase NETDEV_DOWN:\n-\t\tneigh_ifdown(\u0026nd_tbl, dev);\n+\t\tneigh_ifdown(tbl, dev);\n \t\tfib6_run_gc(0, net, false);\n \t\tbreak;\n \tcase NETDEV_NOTIFY_PEERS:\n@@ -1972,12 +1981,23 @@ static int __net_init ndisc_net_init(struct net *net)\n \tstruct sock *sk;\n \tint err;\n \n+\terr = neigh_table_register(net, \u0026nd_tbl, NEIGH_ND_TABLE);\n+\tif (err)\n+\t\tgoto err;\n+\n+#ifdef CONFIG_SYSCTL\n+\terr = neigh_sysctl_register(NULL, \u0026nd_table(net)-\u003eparms,\n+\t\t\t\t    ndisc_ifinfo_sysctl_change);\n+\tif (err)\n+\t\tgoto err_sysctl;\n+#endif\n+\n \terr = inet_ctl_sock_create(\u0026sk, PF_INET6,\n \t\t\t\t   SOCK_RAW, IPPROTO_ICMPV6, net);\n \tif (err \u003c 0) {\n \t\tnet_err_ratelimited(\"NDISC: Failed to initialize the control socket (err %d)\\n\",\n \t\t\t\t    err);\n-\t\treturn err;\n+\t\tgoto err_sock_create;\n \t}\n \n \tnet-\u003eipv6.ndisc_sk = sk;\n@@ -1988,11 +2008,24 @@ static int __net_init ndisc_net_init(struct net *net)\n \tinet6_clear_bit(MC6_LOOP, sk);\n \n \treturn 0;\n+\n+err_sock_create:\n+#ifdef CONFIG_SYSCTL\n+\tneigh_sysctl_unregister(\u0026nd_table(net)-\u003eparms);\n+err_sysctl:\n+#endif\n+\tneigh_table_unregister(net, NEIGH_ND_TABLE);\n+err:\n+\treturn err;\n }\n \n static void __net_exit ndisc_net_exit(struct net *net)\n {\n \tinet_ctl_sock_destroy(net-\u003eipv6.ndisc_sk);\n+#ifdef CONFIG_SYSCTL\n+\tneigh_sysctl_unregister(\u0026nd_table(net)-\u003eparms);\n+#endif\n+\tneigh_table_unregister(net, NEIGH_ND_TABLE);\n }\n \n static struct pernet_operations ndisc_net_ops = {\n@@ -2002,30 +2035,7 @@ static struct pernet_operations ndisc_net_ops = {\n \n int __init ndisc_init(void)\n {\n-\tint err;\n-\n-\terr = register_pernet_subsys(\u0026ndisc_net_ops);\n-\tif (err)\n-\t\treturn err;\n-\t/*\n-\t * Initialize the neighbour table\n-\t */\n-\tneigh_table_init(NEIGH_ND_TABLE, \u0026nd_tbl);\n-\n-#ifdef CONFIG_SYSCTL\n-\terr = neigh_sysctl_register(NULL, \u0026nd_tbl.parms,\n-\t\t\t\t    ndisc_ifinfo_sysctl_change);\n-\tif (err)\n-\t\tgoto out_unregister_pernet;\n-out:\n-#endif\n-\treturn err;\n-\n-#ifdef CONFIG_SYSCTL\n-out_unregister_pernet:\n-\tunregister_pernet_subsys(\u0026ndisc_net_ops);\n-\tgoto out;\n-#endif\n+\treturn register_pernet_subsys(\u0026ndisc_net_ops);\n }\n \n int __init ndisc_late_init(void)\n@@ -2040,9 +2050,5 @@ void ndisc_late_cleanup(void)\n \n void ndisc_cleanup(void)\n {\n-#ifdef CONFIG_SYSCTL\n-\tneigh_sysctl_unregister(\u0026nd_tbl.parms);\n-#endif\n-\tneigh_table_clear(NEIGH_ND_TABLE, \u0026nd_tbl);\n \tunregister_pernet_subsys(\u0026ndisc_net_ops);\n }\ndiff --git a/net/ipv6/route.c b/net/ipv6/route.c\nindex 5968ce5ad1508..cea1cdc2b8e8e 100644\n--- a/net/ipv6/route.c\n+++ b/net/ipv6/route.c\n@@ -217,7 +217,7 @@ struct neighbour *ip6_neigh_lookup(const struct in6_addr *gw,\n \tif (n)\n \t\treturn n;\n \n-\tn = neigh_create(\u0026nd_tbl, daddr, dev);\n+\tn = neigh_create(nd_table(dev_net(dev)), daddr, dev);\n \treturn IS_ERR(n) ? NULL : n;\n }\n \n@@ -4229,6 +4229,7 @@ static int ip6_route_del(struct fib6_config *cfg,\n static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_buff *skb)\n {\n \tstruct netevent_redirect netevent;\n+\tstruct net_device *dev = skb-\u003edev;\n \tstruct rt6_info *rt, *nrt = NULL;\n \tstruct fib6_result res = {};\n \tstruct ndisc_options ndopts;\n@@ -4262,7 +4263,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu\n \t\treturn;\n \t}\n \n-\tin6_dev = __in6_dev_get(skb-\u003edev);\n+\tin6_dev = __in6_dev_get(dev);\n \tif (!in6_dev)\n \t\treturn;\n \tif (READ_ONCE(in6_dev-\u003ecnf.forwarding) ||\n@@ -4274,15 +4275,14 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu\n \t *\tfirst-hop router for the specified ICMP Destination Address.\n \t */\n \n-\tif (!ndisc_parse_options(skb-\u003edev, msg-\u003eopt, optlen, \u0026ndopts)) {\n+\tif (!ndisc_parse_options(dev, msg-\u003eopt, optlen, \u0026ndopts)) {\n \t\tnet_dbg_ratelimited(\"rt6_redirect: invalid ND options\\n\");\n \t\treturn;\n \t}\n \n \tlladdr = NULL;\n \tif (ndopts.nd_opts_tgt_lladdr) {\n-\t\tlladdr = ndisc_opt_addr_data(ndopts.nd_opts_tgt_lladdr,\n-\t\t\t\t\t     skb-\u003edev);\n+\t\tlladdr = ndisc_opt_addr_data(ndopts.nd_opts_tgt_lladdr, dev);\n \t\tif (!lladdr) {\n \t\t\tnet_dbg_ratelimited(\"rt6_redirect: invalid link-layer address length\\n\");\n \t\t\treturn;\n@@ -4301,7 +4301,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu\n \t */\n \tdst_confirm_neigh(\u0026rt-\u003edst, \u0026ipv6_hdr(skb)-\u003esaddr);\n \n-\tneigh = __neigh_lookup(\u0026nd_tbl, \u0026msg-\u003etarget, skb-\u003edev, 1);\n+\tneigh = __neigh_lookup(nd_table(dev_net(dev)), \u0026msg-\u003etarget, dev, 1);\n \tif (!neigh)\n \t\treturn;\n \n@@ -4309,7 +4309,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu\n \t *\tWe have finally decided to accept it.\n \t */\n \n-\tndisc_update(skb-\u003edev, neigh, lladdr, NUD_STALE,\n+\tndisc_update(dev, neigh, lladdr, NUD_STALE,\n \t\t     NEIGH_UPDATE_F_WEAK_OVERRIDE|\n \t\t     NEIGH_UPDATE_F_OVERRIDE|\n \t\t     (on_link ? 0 : (NEIGH_UPDATE_F_OVERRIDE_ISROUTER|\n@@ -5030,9 +5030,11 @@ void rt6_sync_down_dev(struct net_device *dev, unsigned long event)\n \n void rt6_disable_ip(struct net_device *dev, unsigned long event)\n {\n+\tstruct net *net = dev_net(dev);\n+\n \trt6_sync_down_dev(dev, event);\n \trt6_uncached_list_flush_dev(dev);\n-\tneigh_ifdown(\u0026nd_tbl, dev);\n+\tneigh_ifdown(nd_table(net), dev);\n }\n \n struct rt6_mtu_change_arg {\ndiff --git a/tools/testing/selftests/net/test_neigh.sh b/tools/testing/selftests/net/test_neigh.sh\nindex 7c594bf6ead0d..e70e31edf6337 100755\n--- a/tools/testing/selftests/net/test_neigh.sh\n+++ b/tools/testing/selftests/net/test_neigh.sh\n@@ -63,6 +63,15 @@ exit_cleanup_all()\n \texit \"${EXIT_STATUS}\"\n }\n \n+get_periodic_gc_runs()\n+{\n+\tlocal ns=$1\n+\tlocal tbl_name=$2\n+\n+\tip -n \"$ns\" -j -s ntable show name \"$tbl_name\" | \\\n+\t\tjq '.[] | select(has(\"periodic_gc_runs\")) | .[\"periodic_gc_runs\"]'\n+}\n+\n ################################################################################\n # Tests\n \n@@ -231,9 +240,6 @@ extern_valid_common()\n \t# Check that an \"extern_valid\" entry survives a forced garbage\n \t# collection. Add an entry, wait 5 seconds and add more entries than\n \t# \"thresh3\" so that forced garbage collection will run.\n-\t#\n-\t# Note that the garbage collection thresholds are global resources and\n-\t# that changes in the initial namespace affect all the namespaces.\n \tlocal forced_gc_runs_t0\n \tlocal forced_gc_runs_t1\n \tlocal orig_thresh1\n@@ -241,18 +247,23 @@ extern_valid_common()\n \tlocal orig_thresh3\n \n \trun_cmd \"ip -n $ns1 neigh flush dev veth0\"\n-\torig_thresh1=$(ip -j ntable show name \"$tbl_name\" | jq '.[] | select(has(\"thresh1\")) | .[\"thresh1\"]')\n-\torig_thresh2=$(ip -j ntable show name \"$tbl_name\" | jq '.[] | select(has(\"thresh2\")) | .[\"thresh2\"]')\n-\torig_thresh3=$(ip -j ntable show name \"$tbl_name\" | jq '.[] | select(has(\"thresh3\")) | .[\"thresh3\"]')\n-\trun_cmd \"ip ntable change name $tbl_name thresh3 10 thresh2 9 thresh1 8\"\n+\torig_thresh1=$(ip -n \"$ns1\" -j ntable show name \"$tbl_name\" | \\\n+\t\t\tjq '.[] | select(has(\"thresh1\")) | .[\"thresh1\"]')\n+\torig_thresh2=$(ip -n \"$ns1\" -j ntable show name \"$tbl_name\" | \\\n+\t\t\tjq '.[] | select(has(\"thresh2\")) | .[\"thresh2\"]')\n+\torig_thresh3=$(ip -n \"$ns1\" -j ntable show name \"$tbl_name\" | \\\n+\t\t\tjq '.[] | select(has(\"thresh3\")) | .[\"thresh3\"]')\n+\trun_cmd \"ip -n $ns1 ntable change name $tbl_name thresh3 10 thresh2 9 thresh1 8\"\n \trun_cmd \"ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid\"\n \trun_cmd \"ip -n $ns1 neigh add ${subnet}3 lladdr $mac nud stale dev veth0\"\n \trun_cmd \"sleep 5\"\n-\tforced_gc_runs_t0=$(ip -j -s ntable show name \"$tbl_name\" | jq '.[] | select(has(\"forced_gc_runs\")) | .[\"forced_gc_runs\"]')\n+\tforced_gc_runs_t0=$(ip -n \"$ns1\" -j -s ntable show name \"$tbl_name\" | \\\n+\t\t\t\tjq '.[] | select(has(\"forced_gc_runs\")) | .[\"forced_gc_runs\"]')\n \tfor i in {1..20}; do\n \t\trun_cmd \"ip -n $ns1 neigh add ${subnet}$((i + 4)) nud none dev veth0\"\n \tdone\n-\tforced_gc_runs_t1=$(ip -j -s ntable show name \"$tbl_name\" | jq '.[] | select(has(\"forced_gc_runs\")) | .[\"forced_gc_runs\"]')\n+\tforced_gc_runs_t1=$(ip -n \"$ns1\" -j -s ntable show name \"$tbl_name\" | \\\n+\t\t\t\tjq '.[] | select(has(\"forced_gc_runs\")) | .[\"forced_gc_runs\"]')\n \tif [[ $forced_gc_runs_t1 -eq $forced_gc_runs_t0 ]]; then\n \t\tcheck_err 1 \"Forced garbage collection did not run\"\n \tfi\n@@ -263,7 +274,8 @@ extern_valid_common()\n \n \tlog_test \"$af_str \\\"extern_valid\\\" flag: Forced garbage collection\"\n \n-\trun_cmd \"ip ntable change name $tbl_name thresh3 $orig_thresh3 thresh2 $orig_thresh2 thresh1 $orig_thresh1\"\n+\trun_cmd \"ip -n $ns1 ntable change name $tbl_name \\\n+\t\tthresh3 $orig_thresh3 thresh2 $orig_thresh2 thresh1 $orig_thresh1\"\n \n \tRET=0\n \n@@ -275,31 +287,36 @@ extern_valid_common()\n \t# collection. Add an \"extern_valid\" entry, add more than \"thresh1\"\n \t# regular entries, wait \"base_reachable\" (longer than \"gc_stale\")\n \t# seconds and check that the \"extern_valid\" entry was not deleted.\n-\t#\n-\t# Note that the garbage collection thresholds and \"base_reachable\" are\n-\t# global resources and that changes in the initial namespace affect all\n-\t# the namespaces.\n \tlocal periodic_gc_runs_t0\n \tlocal periodic_gc_runs_t1\n \tlocal orig_base_reachable\n+\tlocal base_reachable=10000\n \tlocal orig_gc_stale\n+\tlocal timeout\n \n \trun_cmd \"ip -n $ns1 neigh flush dev veth0\"\n-\torig_thresh1=$(ip -j ntable show name \"$tbl_name\" | jq '.[] | select(has(\"thresh1\")) | .[\"thresh1\"]')\n-\torig_base_reachable=$(ip -j ntable show name \"$tbl_name\" | jq '.[] | select(has(\"thresh1\")) | .[\"base_reachable\"]')\n-\trun_cmd \"ip ntable change name $tbl_name thresh1 10 base_reachable 10000\"\n-\torig_gc_stale=$(ip -n \"$ns1\" -j ntable show name \"$tbl_name\" dev veth0 | jq '.[][\"gc_stale\"]')\n+\torig_thresh1=$(ip -n \"$ns1\" -j ntable show name \"$tbl_name\" | \\\n+\t\t\tjq '.[] | select(has(\"thresh1\")) | .[\"thresh1\"]')\n+\torig_base_reachable=$(ip -n \"$ns1\" -j ntable show name \"$tbl_name\" | \\\n+\t\t\t\tjq '.[] | select(has(\"thresh1\")) | .[\"base_reachable\"]')\n+\trun_cmd \"ip -n $ns1 ntable change name $tbl_name thresh1 10 base_reachable $base_reachable\"\n+\torig_gc_stale=$(ip -n \"$ns1\" -j ntable show name \"$tbl_name\" dev veth0 | \\\n+\t\t\tjq '.[][\"gc_stale\"]')\n \trun_cmd \"ip -n $ns1 ntable change name $tbl_name dev veth0 gc_stale 1000\"\n \trun_cmd \"ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid\"\n \trun_cmd \"ip -n $ns1 neigh add ${subnet}3 lladdr $mac nud stale dev veth0\"\n-\t# Wait orig_base_reachable/2 for the new interval to take effect.\n-\trun_cmd \"sleep $(((orig_base_reachable / 1000) / 2 + 2))\"\n+\t# Wait up to neigh_set_reach_time(BASE_REACHABLE_TIME) (~45s) + 5s\n+\t# for the new interval to take effect, see neigh_table_init().\n+\ttimeout=$(((orig_base_reachable / 1000) * 3 / 2 + 5))\n+\tslowwait_for_counter \"$timeout\" 1 get_periodic_gc_runs \"$ns1\" \"$tbl_name\" \u003e/dev/null\n \tfor i in {1..20}; do\n \t\trun_cmd \"ip -n $ns1 neigh add ${subnet}$((i + 4)) nud none dev veth0\"\n \tdone\n-\tperiodic_gc_runs_t0=$(ip -j -s ntable show name \"$tbl_name\" | jq '.[] | select(has(\"periodic_gc_runs\")) | .[\"periodic_gc_runs\"]')\n-\trun_cmd \"sleep 10\"\n-\tperiodic_gc_runs_t1=$(ip -j -s ntable show name \"$tbl_name\" | jq '.[] | select(has(\"periodic_gc_runs\")) | .[\"periodic_gc_runs\"]')\n+\tperiodic_gc_runs_t0=$(get_periodic_gc_runs \"$ns1\" \"$tbl_name\")\n+\t# Wait up to BASE_REACHABLE_TIME / 2 + 5s, see neigh_periodic_work().\n+\ttimeout=$(((base_reachable / 1000) / 2 + 5))\n+\tslowwait_for_counter \"$timeout\" 1 get_periodic_gc_runs \"$ns1\" \"$tbl_name\" \u003e/dev/null\n+\tperiodic_gc_runs_t1=$(get_periodic_gc_runs \"$ns1\" \"$tbl_name\")\n \t[[ $periodic_gc_runs_t1 -ne $periodic_gc_runs_t0 ]]\n \tcheck_err $? \"Periodic garbage collection did not run\"\n \trun_cmd \"ip -n $ns1 neigh get $ip_addr dev veth0 | grep \\\"extern_valid\\\"\"\n@@ -310,7 +327,8 @@ extern_valid_common()\n \tlog_test \"$af_str \\\"extern_valid\\\" flag: Periodic garbage collection\"\n \n \trun_cmd \"ip -n $ns1 ntable change name $tbl_name dev veth0 gc_stale $orig_gc_stale\"\n-\trun_cmd \"ip ntable change name $tbl_name thresh1 $orig_thresh1 base_reachable $orig_base_reachable\"\n+\trun_cmd \"ip -n $ns1 ntable change name $tbl_name \\\n+\t\tthresh1 $orig_thresh1 base_reachable $orig_base_reachable\"\n }\n \n extern_valid_ipv4()\n"
}

2/1 2026/08/13 08:33 agent patch-evaluator
2m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "NET_NS",
    "IPV6"
  ],
  "FocusSymbols": [
    "neigh_table_register",
    "neigh_table_unregister",
    "pneigh_lookup",
    "pneigh_create",
    "pneigh_delete",
    "neigh_parms_alloc",
    "neigh_parms_release",
    "neigh_xmit"
  ],
  "Reasoning": "The patch changes the neighbor tables (arp_tbl and nd_tbl) from being global to being per-network namespace (net-\u003eneigh_tables). This is a significant functional change in the core networking stack, affecting IPv4 ARP and IPv6 Neighbor Discovery, especially in environments using network namespaces. It modifies how neighbor entries are looked up, created, and garbage collected across different namespaces.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit ec0d7c39eae66ddc2d69aed3897e829969ad40bd
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Aug 13 08:33:02 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
index e586e17fc7a58..fe43e85959583 100644
--- a/Documentation/admin-guide/sysctl/net.rst
+++ b/Documentation/admin-guide/sysctl/net.rst
@@ -413,6 +413,20 @@ new netns has been created.
 
 Default : 0  (for compatibility reasons)
 
+neigh_inherit_init_net
+----------------------
+
+Controls if a new network namespace should inherit all current
+settings under /proc/sys/net/ipv{4,6}/neigh/default/.
+
+By default, the value is set to 1, and all settings are inherited
+from init_net.
+
+If set to 0, both IPv4 and IPv6 settings are reset to their default
+values.
+
+Default : 1  (for compatibility reasons)
+
 txrehash
 --------
 
diff --git a/drivers/infiniband/ulp/ipoib/ipoib_main.c b/drivers/infiniband/ulp/ipoib/ipoib_main.c
index 16a015b672063..895a3e3f7310b 100644
--- a/drivers/infiniband/ulp/ipoib/ipoib_main.c
+++ b/drivers/infiniband/ulp/ipoib/ipoib_main.c
@@ -1378,7 +1378,8 @@ struct ipoib_neigh *ipoib_neigh_get(struct net_device *dev, u8 *daddr)
 	return neigh;
 }
 
-static void __ipoib_reap_neigh(struct ipoib_dev_priv *priv)
+static void __ipoib_reap_neigh(struct ipoib_dev_priv *priv,
+			       int gc_interval)
 {
 	struct ipoib_neigh_table *ntbl = &priv->ntbl;
 	struct ipoib_neigh_hash *htbl;
@@ -1397,7 +1398,7 @@ static void __ipoib_reap_neigh(struct ipoib_dev_priv *priv)
 		goto out_unlock;
 
 	/* neigh is obsolete if it was idle for two GC periods */
-	dt = 2 * arp_tbl.gc_interval;
+	dt = 2 * gc_interval;
 	neigh_obsolete = jiffies - dt;
 
 	for (i = 0; i < htbl->size; i++) {
@@ -1433,11 +1434,16 @@ static void ipoib_reap_neigh(struct work_struct *work)
 {
 	struct ipoib_dev_priv *priv =
 		container_of(work, struct ipoib_dev_priv, neigh_reap_task.work);
+	struct net_device *dev = priv->dev;
+	struct net *net = dev_net(dev);
+	struct neigh_table *tbl;
+	int gc_interval;
 
-	__ipoib_reap_neigh(priv);
+	tbl = arp_table(net);
+	gc_interval = tbl->gc_interval;
+	__ipoib_reap_neigh(priv, gc_interval);
 
-	queue_delayed_work(priv->wq, &priv->neigh_reap_task,
-			   arp_tbl.gc_interval);
+	queue_delayed_work(priv->wq, &priv->neigh_reap_task, gc_interval);
 }
 
 
@@ -1590,8 +1596,11 @@ void ipoib_neigh_free(struct ipoib_neigh *neigh)
 static int ipoib_neigh_hash_init(struct ipoib_dev_priv *priv)
 {
 	struct ipoib_neigh_table *ntbl = &priv->ntbl;
-	struct ipoib_neigh_hash *htbl;
 	struct ipoib_neigh __rcu **buckets;
+	struct net_device *dev = priv->dev;
+	struct net *net = dev_net(dev);
+	struct ipoib_neigh_hash *htbl;
+	struct neigh_table *tbl;
 	u32 size;
 
 	clear_bit(IPOIB_NEIGH_TBL_FLUSH, &priv->flags);
@@ -1599,7 +1608,9 @@ static int ipoib_neigh_hash_init(struct ipoib_dev_priv *priv)
 	htbl = kzalloc_obj(*htbl);
 	if (!htbl)
 		return -ENOMEM;
-	size = roundup_pow_of_two(arp_tbl.gc_thresh3);
+
+	tbl = arp_table(net);
+	size = roundup_pow_of_two(tbl->gc_thresh3);
 	buckets = kvzalloc_objs(*buckets, size);
 	if (!buckets) {
 		kfree(htbl);
@@ -1614,7 +1625,7 @@ static int ipoib_neigh_hash_init(struct ipoib_dev_priv *priv)
 
 	/* start garbage collection */
 	queue_delayed_work(priv->wq, &priv->neigh_reap_task,
-			   arp_tbl.gc_interval);
+			   tbl->gc_interval);
 
 	return 0;
 }
diff --git a/drivers/net/ethernet/marvell/prestera/prestera_router.c b/drivers/net/ethernet/marvell/prestera/prestera_router.c
index 0c4f462baa6ef..ba45b61b09bb0 100644
--- a/drivers/net/ethernet/marvell/prestera/prestera_router.c
+++ b/drivers/net/ethernet/marvell/prestera/prestera_router.c
@@ -683,7 +683,7 @@ __prestera_k_arb_n_offload_set(struct prestera_switch *sw,
 {
 	struct neighbour *n;
 
-	n = neigh_lookup(&arp_tbl, &nc->key.addr.u.ipv4,
+	n = neigh_lookup(arp_table(&init_net), &nc->key.addr.u.ipv4,
 			 nc->key.dev);
 	if (!n)
 		return;
@@ -790,7 +790,7 @@ __prestera_k_arb_nc_kern_n_fetch(struct prestera_switch *sw,
 	int err;
 
 	memset(&nc->nh_neigh_info, 0, sizeof(nc->nh_neigh_info));
-	n = neigh_lookup(&arp_tbl, &nc->key.addr.u.ipv4, nc->key.dev);
+	n = neigh_lookup(arp_table(&init_net), &nc->key.addr.u.ipv4, nc->key.dev);
 	if (!n)
 		goto out;
 
@@ -1052,10 +1052,12 @@ static void __prestera_k_arb_hw_state_upd(struct prestera_switch *sw,
 #endif /* PRESTERA_IMPLICITY_RESOLVE_DEAD_NEIGH */
 
 	if (nc->key.addr.v == PRESTERA_IPV4) {
-		n = neigh_lookup(&arp_tbl, &nc->key.addr.u.ipv4,
+		struct neigh_table *tbl = arp_table(&init_net);
+
+		n = neigh_lookup(tbl, &nc->key.addr.u.ipv4,
 				 nc->key.dev);
 		if (!n)
-			n = neigh_create(&arp_tbl, &nc->key.addr.u.ipv4,
+			n = neigh_create(tbl, &nc->key.addr.u.ipv4,
 					 nc->key.dev);
 	} else {
 		n = NULL;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c b/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c
index 648f4521c0964..6a8d8aac7d16f 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c
@@ -17,20 +17,27 @@
 #include "fs_core.h"
 #include "diag/en_rep_tracepoint.h"
 
-static unsigned long mlx5e_rep_ipv6_interval(void)
+static unsigned long mlx5e_rep_ipv6_interval(struct net *net)
 {
 	if (IS_ENABLED(CONFIG_IPV6) && ipv6_mod_enabled())
-		return NEIGH_VAR(&nd_tbl.parms, DELAY_PROBE_TIME);
+		return NEIGH_VAR(&nd_table(net)->parms, DELAY_PROBE_TIME);
 
 	return ~0UL;
 }
 
 static void mlx5e_rep_neigh_update_init_interval(struct mlx5e_rep_priv *rpriv)
 {
-	unsigned long ipv4_interval = NEIGH_VAR(&arp_tbl.parms, DELAY_PROBE_TIME);
-	unsigned long ipv6_interval = mlx5e_rep_ipv6_interval();
 	struct net_device *netdev = rpriv->netdev;
-	struct mlx5e_priv *priv = netdev_priv(netdev);
+	struct net *net = dev_net(netdev);
+	unsigned long ipv4_interval;
+	unsigned long ipv6_interval;
+	struct neigh_table *tbl;
+	struct mlx5e_priv *priv;
+
+	priv = netdev_priv(netdev);
+	tbl = arp_table(net);
+	ipv4_interval = NEIGH_VAR(&tbl->parms, DELAY_PROBE_TIME);
+	ipv6_interval = mlx5e_rep_ipv6_interval(net);
 
 	rpriv->neigh_update.min_interval = min_t(unsigned long, ipv6_interval, ipv4_interval);
 	mlx5_fc_update_sampling_interval(priv->mdev, rpriv->neigh_update.min_interval);
@@ -217,12 +224,6 @@ static int mlx5e_rep_netevent_event(struct notifier_block *nb,
 	switch (event) {
 	case NETEVENT_NEIGH_UPDATE:
 		n = ptr;
-#if IS_ENABLED(CONFIG_IPV6)
-		if (n->tbl != &nd_tbl && n->tbl != &arp_tbl)
-#else
-		if (n->tbl != &arp_tbl)
-#endif
-			return NOTIFY_DONE;
 
 		update_work = mlx5e_alloc_neigh_update_work(priv, n);
 		if (!update_work)
@@ -238,11 +239,7 @@ static int mlx5e_rep_netevent_event(struct notifier_block *nb,
 		 * changes in the default table, we only care about changes
 		 * done per device delay prob time parameter.
 		 */
-#if IS_ENABLED(CONFIG_IPV6)
-		if (!p->dev || (p->tbl != &nd_tbl && p->tbl != &arp_tbl))
-#else
-		if (!p->dev || p->tbl != &arp_tbl)
-#endif
+		if (!p->dev)
 			return NOTIFY_DONE;
 
 		rcu_read_lock();
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c
index 8b827201935eb..67c12ca19d59d 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c
@@ -393,20 +393,10 @@ void mlx5e_tc_update_neigh_used_value(struct mlx5e_neigh_hash_entry *nhe)
 	struct mlx5e_encap_entry *e = NULL;
 	struct mlx5e_tc_flow *flow;
 	struct mlx5_fc *counter;
-	struct neigh_table *tbl;
 	bool neigh_used = false;
 	struct neighbour *n;
 	u64 lastuse;
 
-	if (m_neigh->family == AF_INET)
-		tbl = &arp_tbl;
-#if IS_ENABLED(CONFIG_IPV6)
-	else if (m_neigh->family == AF_INET6)
-		tbl = &nd_tbl;
-#endif
-	else
-		return;
-
 	/* mlx5e_get_next_valid_encap() releases previous encap before returning
 	 * next one.
 	 */
@@ -447,12 +437,23 @@ void mlx5e_tc_update_neigh_used_value(struct mlx5e_neigh_hash_entry *nhe)
 	trace_mlx5e_tc_update_neigh_used_value(nhe, neigh_used);
 
 	if (neigh_used) {
+		struct net_device *dev = READ_ONCE(nhe->neigh_dev);
+		struct net *net = dev_net(dev);
+		struct neigh_table *tbl;
+
 		nhe->reported_lastuse = jiffies;
 
+#if IS_ENABLED(CONFIG_IPV6)
+		if (m_neigh->family != AF_INET)
+			tbl = nd_table(net);
+		else
+#endif
+			tbl = arp_table(net);
+
 		/* find the relevant neigh according to the cached device and
 		 * dst ip pair
 		 */
-		n = neigh_lookup(tbl, &m_neigh->dst_ip, READ_ONCE(nhe->neigh_dev));
+		n = neigh_lookup(tbl, &m_neigh->dst_ip, dev);
 		if (!n)
 			return;
 
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c
index db260e3d1412f..37a8ddee3ea1e 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c
@@ -262,6 +262,7 @@ static void mlx5e_ipsec_init_macs(struct mlx5e_ipsec_sa_entry *sa_entry,
 	struct net_device *netdev = sa_entry->dev;
 	struct xfrm_state *x = sa_entry->x;
 	struct dst_entry *rt_dst_entry;
+	struct neigh_table *tbl;
 	struct flowi4 fl4 = {};
 	struct flowi6 fl6 = {};
 	struct neighbour *n;
@@ -364,9 +365,10 @@ static void mlx5e_ipsec_init_macs(struct mlx5e_ipsec_sa_entry *sa_entry,
 	return;
 
 neigh:
-	n = neigh_lookup(&arp_tbl, pkey, netdev);
+	tbl = arp_table(dev_net(netdev));
+	n = neigh_lookup(tbl, pkey, netdev);
 	if (!n) {
-		n = neigh_create(&arp_tbl, pkey, netdev);
+		n = neigh_create(tbl, pkey, netdev);
 		if (IS_ERR(n))
 			return;
 		neigh_event_send(n, NULL);
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
index 3d6fdbab05e01..f4a435885ba43 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
@@ -2397,14 +2397,15 @@ mlxsw_sp_neigh_entry_lookup(struct mlxsw_sp *mlxsw_sp, struct neighbour *n)
 static void
 mlxsw_sp_router_neighs_update_interval_init(struct mlxsw_sp *mlxsw_sp)
 {
+	struct net *net = mlxsw_sp_net(mlxsw_sp);
 	unsigned long interval;
 
 #if IS_ENABLED(CONFIG_IPV6)
 	interval = min_t(unsigned long,
-			 NEIGH_VAR(&arp_tbl.parms, DELAY_PROBE_TIME),
-			 NEIGH_VAR(&nd_tbl.parms, DELAY_PROBE_TIME));
+			 NEIGH_VAR(&arp_table(net)->parms, DELAY_PROBE_TIME),
+			 NEIGH_VAR(&nd_table(net)->parms, DELAY_PROBE_TIME));
 #else
-	interval = NEIGH_VAR(&arp_tbl.parms, DELAY_PROBE_TIME);
+	interval = NEIGH_VAR(&arp_table(net)->parms, DELAY_PROBE_TIME);
 #endif
 	mlxsw_sp->router->neighs_update.interval = jiffies_to_msecs(interval);
 }
@@ -2414,6 +2415,8 @@ static void mlxsw_sp_router_neigh_ent_ipv4_process(struct mlxsw_sp *mlxsw_sp,
 						   int ent_index)
 {
 	u64 max_rifs = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_RIFS);
+	struct net *net = mlxsw_sp_net(mlxsw_sp);
+	struct neigh_table *tbl;
 	struct net_device *dev;
 	struct neighbour *n;
 	__be32 dipn;
@@ -2429,9 +2432,10 @@ static void mlxsw_sp_router_neigh_ent_ipv4_process(struct mlxsw_sp *mlxsw_sp,
 		return;
 	}
 
+	tbl = arp_table(net);
 	dipn = htonl(dip);
 	dev = mlxsw_sp_rif_dev(mlxsw_sp->router->rifs[rif]);
-	n = neigh_lookup(&arp_tbl, &dipn, dev);
+	n = neigh_lookup(tbl, &dipn, dev);
 	if (!n)
 		return;
 
@@ -2445,6 +2449,8 @@ static void mlxsw_sp_router_neigh_ent_ipv6_process(struct mlxsw_sp *mlxsw_sp,
 						   char *rauhtd_pl,
 						   int rec_index)
 {
+	struct net *net = mlxsw_sp_net(mlxsw_sp);
+	struct neigh_table *tbl;
 	struct net_device *dev;
 	struct neighbour *n;
 	struct in6_addr dip;
@@ -2458,8 +2464,9 @@ static void mlxsw_sp_router_neigh_ent_ipv6_process(struct mlxsw_sp *mlxsw_sp,
 		return;
 	}
 
+	tbl = nd_table(net);
 	dev = mlxsw_sp_rif_dev(mlxsw_sp->router->rifs[rif]);
-	n = neigh_lookup(&nd_tbl, &dip, dev);
+	n = neigh_lookup(tbl, &dip, dev);
 	if (!n)
 		return;
 
@@ -3014,16 +3021,17 @@ static int mlxsw_sp_neigh_rif_made_sync(struct mlxsw_sp *mlxsw_sp,
 		.mlxsw_sp = mlxsw_sp,
 		.rif = rif,
 	};
+	struct net *net = mlxsw_sp_net(mlxsw_sp);
 
 	if (!mlxsw_sp_dev_lower_is_port(mlxsw_sp_rif_dev(rif)))
 		return 0;
 
-	neigh_for_each(&arp_tbl, mlxsw_sp_neigh_rif_made_sync_each, &rms);
+	neigh_for_each(arp_table(net), mlxsw_sp_neigh_rif_made_sync_each, &rms);
 	if (rms.err)
 		goto err_arp;
 
 #if IS_ENABLED(CONFIG_IPV6)
-	neigh_for_each(&nd_tbl, mlxsw_sp_neigh_rif_made_sync_each, &rms);
+	neigh_for_each(nd_table(net), mlxsw_sp_neigh_rif_made_sync_each, &rms);
 #endif
 	if (rms.err)
 		goto err_nd;
@@ -4622,7 +4630,7 @@ static int mlxsw_sp_nexthop4_init(struct mlxsw_sp *mlxsw_sp,
 	nh->nh_weight = 1;
 #endif
 	memcpy(&nh->gw_addr, &fib_nh->fib_nh_gw4, sizeof(fib_nh->fib_nh_gw4));
-	nh->neigh_tbl = &arp_tbl;
+	nh->neigh_tbl = arp_table(mlxsw_sp_net(mlxsw_sp));
 	err = mlxsw_sp_nexthop_insert(mlxsw_sp, nh);
 	if (err)
 		return err;
@@ -5112,6 +5120,7 @@ mlxsw_sp_nexthop_obj_init(struct mlxsw_sp *mlxsw_sp,
 			  struct nh_notifier_single_info *nh_obj, int weight)
 {
 	struct net_device *dev = nh_obj->dev;
+	struct net *net = dev_net(dev);
 	int err;
 
 	nh->nhgi = nh_grp->nhgi;
@@ -5120,12 +5129,12 @@ mlxsw_sp_nexthop_obj_init(struct mlxsw_sp *mlxsw_sp,
 	switch (nh_obj->gw_family) {
 	case AF_INET:
 		memcpy(&nh->gw_addr, &nh_obj->ipv4, sizeof(nh_obj->ipv4));
-		nh->neigh_tbl = &arp_tbl;
+		nh->neigh_tbl = arp_table(net);
 		break;
 	case AF_INET6:
 		memcpy(&nh->gw_addr, &nh_obj->ipv6, sizeof(nh_obj->ipv6));
 #if IS_ENABLED(CONFIG_IPV6)
-		nh->neigh_tbl = &nd_tbl;
+		nh->neigh_tbl = nd_table(net);
 #endif
 		break;
 	}
@@ -6981,7 +6990,7 @@ static int mlxsw_sp_nexthop6_init(struct mlxsw_sp *mlxsw_sp,
 	nh->nh_weight = rt->fib6_nh->fib_nh_weight;
 	memcpy(&nh->gw_addr, &rt->fib6_nh->fib_nh_gw6, sizeof(nh->gw_addr));
 #if IS_ENABLED(CONFIG_IPV6)
-	nh->neigh_tbl = &nd_tbl;
+	nh->neigh_tbl = nd_table(mlxsw_sp_net(mlxsw_sp));
 #endif
 
 	err = mlxsw_sp_nexthop_counter_enable(mlxsw_sp, nh);
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
index ae63d549b5429..1cd8347c274d4 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
@@ -456,6 +456,7 @@ mlxsw_sp_span_entry_gretap4_parms(struct mlxsw_sp *mlxsw_sp,
 	bool inherit_tos = tparm.iph.tos & 0x1;
 	bool inherit_ttl = !tparm.iph.ttl;
 	union mlxsw_sp_l3addr gw = daddr;
+	struct neigh_table *tbl = NULL;
 	struct net_device *l3edev;
 
 	if (!(to_dev->flags & IFF_UP) ||
@@ -469,9 +470,11 @@ mlxsw_sp_span_entry_gretap4_parms(struct mlxsw_sp *mlxsw_sp,
 		return mlxsw_sp_span_entry_unoffloadable(sparmsp);
 
 	l3edev = mlxsw_sp_span_gretap4_route(to_dev, &saddr.addr4, &gw.addr4);
+	if (l3edev)
+		tbl = arp_table(dev_net(l3edev));
 	return mlxsw_sp_span_entry_tunnel_parms_common(l3edev, saddr, daddr, gw,
 						       tparm.iph.ttl,
-						       &arp_tbl, sparmsp);
+						       tbl, sparmsp);
 }
 
 static int
@@ -561,6 +564,7 @@ mlxsw_sp_span_entry_gretap6_parms(struct mlxsw_sp *mlxsw_sp,
 	union mlxsw_sp_l3addr daddr = { .addr6 = tparm.raddr };
 	bool inherit_ttl = !tparm.hop_limit;
 	union mlxsw_sp_l3addr gw = daddr;
+	struct neigh_table *tbl = NULL;
 	struct net_device *l3edev;
 
 	if (!(to_dev->flags & IFF_UP) ||
@@ -574,9 +578,11 @@ mlxsw_sp_span_entry_gretap6_parms(struct mlxsw_sp *mlxsw_sp,
 		return mlxsw_sp_span_entry_unoffloadable(sparmsp);
 
 	l3edev = mlxsw_sp_span_gretap6_route(to_dev, &saddr.addr6, &gw.addr6);
+	if (l3edev)
+		tbl = nd_table(dev_net(l3edev));
 	return mlxsw_sp_span_entry_tunnel_parms_common(l3edev, saddr, daddr, gw,
 						       tparm.hop_limit,
-						       &nd_tbl, sparmsp);
+						       tbl, sparmsp);
 }
 
 static int
diff --git a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
index ca30702f88780..d650d33e3709c 100644
--- a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
+++ b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
@@ -209,6 +209,7 @@ void nfp_tunnel_keep_alive(struct nfp_app *app, struct sk_buff *skb)
 {
 	struct nfp_tun_active_tuns *payload;
 	struct net_device *netdev;
+	struct neigh_table *tbl;
 	int count, i, pay_len;
 	struct neighbour *n;
 	__be32 ipv4_addr;
@@ -235,7 +236,8 @@ void nfp_tunnel_keep_alive(struct nfp_app *app, struct sk_buff *skb)
 		if (!netdev)
 			continue;
 
-		n = neigh_lookup(&arp_tbl, &ipv4_addr, netdev);
+		tbl = arp_table(dev_net(netdev));
+		n = neigh_lookup(tbl, &ipv4_addr, netdev);
 		if (!n)
 			continue;
 
@@ -251,6 +253,7 @@ void nfp_tunnel_keep_alive_v6(struct nfp_app *app, struct sk_buff *skb)
 #if IS_ENABLED(CONFIG_IPV6)
 	struct nfp_tun_active_tuns_v6 *payload;
 	struct net_device *netdev;
+	struct neigh_table *tbl;
 	int count, i, pay_len;
 	struct neighbour *n;
 	void *ipv6_add;
@@ -277,7 +280,8 @@ void nfp_tunnel_keep_alive_v6(struct nfp_app *app, struct sk_buff *skb)
 		if (!netdev)
 			continue;
 
-		n = neigh_lookup(&nd_tbl, ipv6_add, netdev);
+		tbl = nd_table(dev_net(netdev));
+		n = neigh_lookup(tbl, ipv6_add, netdev);
 		if (!n)
 			continue;
 
@@ -729,12 +733,6 @@ nfp_tun_neigh_event_handler(struct notifier_block *nb, unsigned long event,
 	default:
 		return NOTIFY_DONE;
 	}
-#if IS_ENABLED(CONFIG_IPV6)
-	if (n->tbl != &nd_tbl && n->tbl != &arp_tbl)
-#else
-	if (n->tbl != &arp_tbl)
-#endif
-		return NOTIFY_DONE;
 
 	app_priv = container_of(nb, struct nfp_flower_priv, tun.neigh_nb);
 	app = app_priv->app;
diff --git a/drivers/net/ethernet/rocker/rocker_main.c b/drivers/net/ethernet/rocker/rocker_main.c
index 84a55f2b48ffe..0fcd65fc0af7f 100644
--- a/drivers/net/ethernet/rocker/rocker_main.c
+++ b/drivers/net/ethernet/rocker/rocker_main.c
@@ -3134,7 +3134,7 @@ static int rocker_netevent_event(struct notifier_block *unused,
 
 	switch (event) {
 	case NETEVENT_NEIGH_UPDATE:
-		if (n->tbl != &arp_tbl)
+		if (n->tbl != arp_table(&init_net))
 			return NOTIFY_DONE;
 		dev = n->dev;
 		if (!rocker_port_dev_check(dev))
diff --git a/drivers/net/ethernet/rocker/rocker_ofdpa.c b/drivers/net/ethernet/rocker/rocker_ofdpa.c
index 15d19a8a17101..ead8b447b89c4 100644
--- a/drivers/net/ethernet/rocker/rocker_ofdpa.c
+++ b/drivers/net/ethernet/rocker/rocker_ofdpa.c
@@ -1336,7 +1336,7 @@ static int ofdpa_port_ipv4_resolve(struct ofdpa_port *ofdpa_port,
 	int err = 0;
 
 	if (!n) {
-		n = neigh_create(&arp_tbl, &ip_addr, dev);
+		n = neigh_create(arp_table(&init_net), &ip_addr, dev);
 		if (IS_ERR(n))
 			return PTR_ERR(n);
 	}
diff --git a/drivers/net/ethernet/sfc/tc_counters.c b/drivers/net/ethernet/sfc/tc_counters.c
index b84235e93ffe9..793a562fc1f7c 100644
--- a/drivers/net/ethernet/sfc/tc_counters.c
+++ b/drivers/net/ethernet/sfc/tc_counters.c
@@ -91,6 +91,7 @@ static void efx_tc_counter_work(struct work_struct *work)
 	struct efx_tc_action_set *act;
 	unsigned long touched;
 	struct neighbour *n;
+	struct net *net;
 
 	spin_lock_bh(&cnt->lock);
 	touched = READ_ONCE(cnt->touched);
@@ -103,16 +104,19 @@ static void efx_tc_counter_work(struct work_struct *work)
 			continue;
 		if (time_after_eq(encap->neigh->used, touched))
 			continue;
+
 		encap->neigh->used = touched;
+		net = encap->neigh->net;
+
 		/* We have passed traffic using this ARP entry, so
 		 * indicate to the ARP cache that it's still active
 		 */
 		if (encap->neigh->dst_ip)
-			n = neigh_lookup(&arp_tbl, &encap->neigh->dst_ip,
+			n = neigh_lookup(arp_table(net), &encap->neigh->dst_ip,
 					 encap->neigh->egdev);
 		else
 #if IS_ENABLED(CONFIG_IPV6)
-			n = neigh_lookup(&nd_tbl,
+			n = neigh_lookup(nd_table(net),
 					 &encap->neigh->dst_ip6,
 					 encap->neigh->egdev);
 #else
diff --git a/drivers/net/ethernet/sfc/tc_encap_actions.c b/drivers/net/ethernet/sfc/tc_encap_actions.c
index c2ad3a358d201..152c4639ecc6f 100644
--- a/drivers/net/ethernet/sfc/tc_encap_actions.c
+++ b/drivers/net/ethernet/sfc/tc_encap_actions.c
@@ -527,10 +527,10 @@ static int efx_neigh_event(struct efx_nic *efx, struct neighbour *n)
 	if (WARN_ON(!efx->tc))
 		return NOTIFY_DONE;
 
-	if (n->tbl == &arp_tbl) {
+	if (n->tbl->family == AF_INET) {
 		keysize = sizeof(keys.dst_ip);
 #if IS_ENABLED(CONFIG_IPV6)
-	} else if (n->tbl == &nd_tbl) {
+	} else if (n->tbl->family == AF_INET6) {
 		ipv6 = true;
 		keysize = sizeof(keys.dst_ip6);
 #endif
diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c
index a0557a3a70260..50ec04cc0213f 100644
--- a/drivers/net/vrf.c
+++ b/drivers/net/vrf.c
@@ -616,7 +616,7 @@ static int vrf_finish_output6(struct net *net, struct sock *sk,
 	nexthop = rt6_nexthop(dst_rt6_info(dst), &ipv6_hdr(skb)->daddr);
 	neigh = __ipv6_neigh_lookup_noref(dst->dev, nexthop);
 	if (unlikely(!neigh))
-		neigh = __neigh_create(&nd_tbl, nexthop, dst->dev, false);
+		neigh = __neigh_create(nd_table(net), nexthop, dst->dev, false);
 	if (!IS_ERR(neigh)) {
 		sock_confirm_neigh(skb, neigh);
 		ret = neigh_output(neigh, skb, false);
diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c
index fd5505153d0b6..345d1883d993e 100644
--- a/drivers/net/vxlan/vxlan_core.c
+++ b/drivers/net/vxlan/vxlan_core.c
@@ -1841,11 +1841,12 @@ static int vxlan_err_lookup(struct sock *sk, struct sk_buff *skb)
 
 static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 {
+	struct neigh_table *tbl = arp_table(dev_net(dev));
 	struct vxlan_dev *vxlan = netdev_priv(dev);
+	struct neighbour *n;
 	struct arphdr *parp;
 	u8 *arpptr, *sha;
 	__be32 sip, tip;
-	struct neighbour *n;
 
 	if (dev->flags & IFF_NOARP)
 		goto out;
@@ -1877,7 +1878,7 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 	    ipv4_is_multicast(tip))
 		goto out;
 
-	n = neigh_lookup(&arp_tbl, &tip, dev);
+	n = neigh_lookup(tbl, &tip, dev);
 
 	if (n) {
 		struct vxlan_rdst *rdst = NULL;
@@ -2047,7 +2048,7 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 	    ipv6_addr_is_multicast(&msg->target))
 		goto out;
 
-	n = neigh_lookup(&nd_tbl, &msg->target, dev);
+	n = neigh_lookup(nd_table(dev_net(dev)), &msg->target, dev);
 
 	if (n) {
 		struct vxlan_rdst *rdst = NULL;
@@ -2100,6 +2101,7 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)
 {
 	struct vxlan_dev *vxlan = netdev_priv(dev);
+	struct neigh_table *tbl;
 	struct neighbour *n;
 
 	if (is_multicast_ether_addr(eth_hdr(skb)->h_dest))
@@ -2113,8 +2115,10 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)
 
 		if (!pskb_network_may_pull(skb, sizeof(struct iphdr)))
 			return false;
+
+		tbl = arp_table(dev_net(dev));
 		pip = ip_hdr(skb);
-		n = neigh_lookup(&arp_tbl, &pip->daddr, dev);
+		n = neigh_lookup(tbl, &pip->daddr, dev);
 		if (!n && (vxlan->cfg.flags & VXLAN_F_L3MISS)) {
 			union vxlan_addr ipa = {
 				.sin.sin_addr.s_addr = pip->daddr,
@@ -2139,8 +2143,10 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)
 			return false;
 		if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr)))
 			return false;
+
+		tbl = nd_table(dev_net(dev));
 		pip6 = ipv6_hdr(skb);
-		n = neigh_lookup(&nd_tbl, &pip6->daddr, dev);
+		n = neigh_lookup(tbl, &pip6->daddr, dev);
 		if (!n && (vxlan->cfg.flags & VXLAN_F_L3MISS)) {
 			union vxlan_addr ipa = {
 				.sin6.sin6_addr = pip6->daddr,
diff --git a/include/net/arp.h b/include/net/arp.h
index e8747e0713c79..e932def63d62f 100644
--- a/include/net/arp.h
+++ b/include/net/arp.h
@@ -7,8 +7,10 @@
 #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)
 {
@@ -21,10 +23,12 @@ static inline u32 arp_hashfn(const void *pkey, const struct net_device *dev, u32
 #ifdef CONFIG_INET
 static inline struct neighbour *__ipv4_neigh_lookup_noref(struct net_device *dev, u32 key)
 {
+	struct neigh_table *tbl = arp_table(dev_net(dev));
+
 	if (dev->flags & (IFF_LOOPBACK | IFF_POINTOPOINT))
 		key = INADDR_ANY;
 
-	return ___neigh_lookup_noref(&arp_tbl, neigh_key_eq32, arp_hashfn, &key, dev);
+	return ___neigh_lookup_noref(tbl, neigh_key_eq32, arp_hashfn, &key, dev);
 }
 #else
 static inline
diff --git a/include/net/ndisc.h b/include/net/ndisc.h
index 9e5379ad2d8e0..96e3bb6e83afd 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;
@@ -354,7 +363,9 @@ static inline u32 ndisc_hashfn(const void *pkey, const struct net_device *dev, _
 
 static inline struct neighbour *__ipv6_neigh_lookup_noref(struct net_device *dev, const void *pkey)
 {
-	return ___neigh_lookup_noref(&nd_tbl, neigh_key_eq128, ndisc_hashfn, pkey, dev);
+	struct neigh_table *tbl = nd_table(dev_net(dev));
+
+	return ___neigh_lookup_noref(tbl, neigh_key_eq128, ndisc_hashfn, pkey, dev);
 }
 
 static inline struct neighbour *__ipv6_neigh_lookup(struct net_device *dev, const void *pkey)
@@ -388,8 +399,11 @@ static inline struct neighbour *ip_neigh_gw6(struct net_device *dev,
 	struct neighbour *neigh;
 
 	neigh = __ipv6_neigh_lookup_noref(dev, addr);
-	if (unlikely(!neigh))
-		neigh = __neigh_create(&nd_tbl, addr, dev, false);
+	if (unlikely(!neigh)) {
+		struct neigh_table *tbl = nd_table(dev_net(dev));
+
+		neigh = __neigh_create(tbl, addr, dev, false);
+	}
 
 	return neigh;
 #else
diff --git a/include/net/neighbour.h b/include/net/neighbour.h
index 8860cc2175fc1..ed9d9ae6d3a65 100644
--- a/include/net/neighbour.h
+++ b/include/net/neighbour.h
@@ -31,6 +31,8 @@
 #include <net/rtnetlink.h>
 #include <net/neighbour_tables.h>
 
+extern int sysctl_neigh_inherit_init_net;
+
 /*
  * NUD stands for "neighbor unreachability detection"
  */
@@ -179,7 +181,6 @@ struct neigh_ops {
 
 struct pneigh_entry {
 	struct pneigh_entry	__rcu *next;
-	possible_net_t		net;
 	struct net_device	*dev;
 	netdevice_tracker	dev_tracker;
 	union {
@@ -234,7 +235,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;
@@ -339,8 +340,8 @@ static inline void neigh_confirm(struct neighbour *n)
 	}
 }
 
-void neigh_table_init(int index, struct neigh_table *tbl);
-int neigh_table_clear(int index, struct neigh_table *tbl);
+int neigh_table_register(struct net *net, struct neigh_table *tbl, int index);
+void neigh_table_unregister(struct net *net, int index);
 struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
 			       struct net_device *dev);
 struct neighbour *__neigh_create(struct neigh_table *tbl, const void *pkey,
@@ -389,24 +390,17 @@ static inline void neigh_set_reach_time(struct neigh_parms *p)
 
 void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,
 		    struct sk_buff *skb);
-struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl, struct net *net,
+struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl,
 				   const void *key, struct net_device *dev);
-int pneigh_create(struct neigh_table *tbl, struct net *net, const void *key,
+int pneigh_create(struct neigh_table *tbl, const void *key,
 		  struct net_device *dev, u32 flags, u8 protocol,
 		  bool permanent);
-int pneigh_delete(struct neigh_table *tbl, struct net *net, const void *key,
+int pneigh_delete(struct neigh_table *tbl, const void *key,
 		  struct net_device *dev);
 
-static inline struct net *pneigh_net(const struct pneigh_entry *pneigh)
-{
-	return read_pnet(&pneigh->net);
-}
-
 void neigh_app_ns(struct neighbour *n);
 void neigh_for_each(struct neigh_table *tbl,
 		    void (*cb)(struct neighbour *, void *), void *cookie);
-void __neigh_for_each_release(struct neigh_table *tbl,
-			      int (*cb)(struct neighbour *));
 int neigh_xmit(int fam, struct net_device *, const void *, struct sk_buff *);
 
 struct neigh_seq_state {
diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h
index 501af1999fe83..f96a390ae5364 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/include/net/route.h b/include/net/route.h
index f90106f383c56..38a23f28ee14a 100644
--- a/include/net/route.h
+++ b/include/net/route.h
@@ -403,8 +403,11 @@ static inline struct neighbour *ip_neigh_gw4(struct net_device *dev,
 	struct neighbour *neigh;
 
 	neigh = __ipv4_neigh_lookup_noref(dev, (__force u32)daddr);
-	if (unlikely(!neigh))
-		neigh = __neigh_create(&arp_tbl, &daddr, dev, false);
+	if (unlikely(!neigh)) {
+		struct neigh_table *tbl = arp_table(dev_net(dev));
+
+		neigh = __neigh_create(tbl, &daddr, dev, false);
+	}
 
 	return neigh;
 }
diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
index b6e5a86b6a92a..0df84d3ccd906 100644
--- a/net/bridge/br_arp_nd_proxy.c
+++ b/net/bridge/br_arp_nd_proxy.c
@@ -193,7 +193,7 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,
 		return;
 	}
 
-	n = neigh_lookup(&arp_tbl, &tip, vlandev);
+	n = neigh_lookup(arp_table(dev_net(vlandev)), &tip, vlandev);
 	if (n) {
 		struct net_bridge_fdb_entry *f;
 
@@ -468,7 +468,7 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
 		return;
 	}
 
-	n = neigh_lookup(&nd_tbl, &msg->target, vlandev);
+	n = neigh_lookup(nd_table(dev_net(vlandev)), &msg->target, vlandev);
 	if (n) {
 		struct net_bridge_fdb_entry *f;
 
diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index 1349c0eedb642..9b28f40b019aa 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;
@@ -131,10 +148,11 @@ unsigned long neigh_rand_reach_time(unsigned long base)
 {
 	return base ? get_random_u32_below(base) + (base >> 1) : 0;
 }
-EXPORT_SYMBOL(neigh_rand_reach_time);
 
 static void neigh_mark_dead(struct neighbour *n)
 {
+	lockdep_assert_held(&n->tbl->lock);
+
 	n->dead = 1;
 	if (!list_empty(&n->gc_list)) {
 		list_del_init(&n->gc_list);
@@ -148,11 +166,6 @@ static void neigh_update_gc_list(struct neighbour *n)
 {
 	bool on_gc_list, exempt_from_gc;
 
-	spin_lock_bh(&n->tbl->lock);
-	write_lock(&n->lock);
-	if (n->dead)
-		goto out;
-
 	/* remove from the gc list if new state is permanent or if neighbor is
 	 * externally learned / validated; otherwise entry should be on the gc
 	 * list
@@ -169,20 +182,12 @@ static void neigh_update_gc_list(struct neighbour *n)
 		list_add_tail(&n->gc_list, &n->tbl->gc_list);
 		atomic_inc(&n->tbl->gc_entries);
 	}
-out:
-	write_unlock(&n->lock);
-	spin_unlock_bh(&n->tbl->lock);
 }
 
 static void neigh_update_managed_list(struct neighbour *n)
 {
 	bool on_managed_list, add_to_managed;
 
-	spin_lock_bh(&n->tbl->lock);
-	write_lock(&n->lock);
-	if (n->dead)
-		goto out;
-
 	add_to_managed = n->flags & NTF_MANAGED;
 	on_managed_list = !list_empty(&n->managed_list);
 
@@ -190,9 +195,6 @@ static void neigh_update_managed_list(struct neighbour *n)
 		list_del_init(&n->managed_list);
 	else if (add_to_managed && !on_managed_list)
 		list_add_tail(&n->managed_list, &n->tbl->managed_list);
-out:
-	write_unlock(&n->lock);
-	spin_unlock_bh(&n->tbl->lock);
 }
 
 static void neigh_update_flags(struct neighbour *neigh, u32 flags, int *notify,
@@ -349,8 +351,7 @@ static void neigh_parms_qlen_dec(struct net_device *dev, int family)
 	rcu_read_unlock();
 }
 
-static void pneigh_queue_purge(struct sk_buff_head *list, struct net *net,
-			       int family)
+static void pneigh_queue_purge(struct sk_buff_head *list, int family)
 {
 	struct sk_buff_head tmp;
 	unsigned long flags;
@@ -361,13 +362,11 @@ static void pneigh_queue_purge(struct sk_buff_head *list, struct net *net,
 	skb = skb_peek(list);
 	while (skb != NULL) {
 		struct sk_buff *skb_next = skb_peek_next(skb, list);
-		struct net_device *dev = skb->dev;
 
-		if (net == NULL || net_eq(dev_net(dev), net)) {
-			neigh_parms_qlen_dec(dev, family);
-			__skb_unlink(skb, list);
-			__skb_queue_tail(&tmp, skb);
-		}
+		neigh_parms_qlen_dec(skb->dev, family);
+		__skb_unlink(skb, list);
+		__skb_queue_tail(&tmp, skb);
+
 		skb = skb_next;
 	}
 	spin_unlock_irqrestore(&list->lock, flags);
@@ -471,8 +470,7 @@ static int __neigh_ifdown(struct neigh_table *tbl, struct net_device *dev,
 	spin_unlock_bh(&tbl->lock);
 
 	pneigh_ifdown(tbl, dev, skip_perm);
-	pneigh_queue_purge(&tbl->proxy_queue, dev ? dev_net(dev) : NULL,
-			   tbl->family);
+	pneigh_queue_purge(&tbl->proxy_queue, tbl->family);
 	if (skb_queue_empty_lockless(&tbl->proxy_queue))
 		timer_delete_sync(&tbl->proxy_timer);
 	return 0;
@@ -537,7 +535,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;
 
@@ -687,7 +685,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);
@@ -752,8 +750,7 @@ static u32 pneigh_hash(const void *pkey, unsigned int key_len)
 }
 
 struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl,
-				   struct net *net, const void *pkey,
-				   struct net_device *dev)
+				   const void *pkey, struct net_device *dev)
 {
 	struct pneigh_entry *n;
 	unsigned int key_len;
@@ -766,7 +763,6 @@ struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl,
 
 	while (n) {
 		if (!memcmp(n->key, pkey, key_len) &&
-		    net_eq(pneigh_net(n), net) &&
 		    (n->dev == dev || !n->dev))
 			return n;
 
@@ -776,7 +772,7 @@ struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl,
 	return NULL;
 }
 
-int pneigh_create(struct neigh_table *tbl, struct net *net,
+int pneigh_create(struct neigh_table *tbl,
 		  const void *pkey, struct net_device *dev,
 		  u32 flags, u8 protocol, bool permanent)
 {
@@ -787,7 +783,7 @@ int pneigh_create(struct neigh_table *tbl, struct net *net,
 
 	mutex_lock(&tbl->phash_lock);
 
-	n = pneigh_lookup(tbl, net, pkey, dev);
+	n = pneigh_lookup(tbl, pkey, dev);
 	if (n)
 		goto update;
 
@@ -798,7 +794,6 @@ int pneigh_create(struct neigh_table *tbl, struct net *net,
 		goto out;
 	}
 
-	write_pnet(&n->net, net);
 	memcpy(n->key, pkey, key_len);
 	n->dev = dev;
 	netdev_hold(dev, &n->dev_tracker, GFP_KERNEL);
@@ -831,7 +826,7 @@ static void pneigh_destroy(struct rcu_head *rcu)
 	kfree(n);
 }
 
-int pneigh_delete(struct neigh_table *tbl, struct net *net, const void *pkey,
+int pneigh_delete(struct neigh_table *tbl, const void *pkey,
 		  struct net_device *dev)
 {
 	struct pneigh_entry *n, __rcu **np;
@@ -846,8 +841,7 @@ int pneigh_delete(struct neigh_table *tbl, struct net *net, const void *pkey,
 	for (np = &tbl->phash_buckets[hash_val];
 	     (n = rcu_dereference_protected(*np, 1)) != NULL;
 	     np = &n->next) {
-		if (!memcmp(n->key, pkey, key_len) && n->dev == dev &&
-		    net_eq(pneigh_net(n), net)) {
+		if (!memcmp(n->key, pkey, key_len) && n->dev == dev) {
 			rcu_assign_pointer(*np, n->next);
 
 			mutex_unlock(&tbl->phash_lock);
@@ -939,7 +933,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);
@@ -994,7 +988,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++) {
@@ -1523,10 +1517,23 @@ static int __neigh_update(struct neighbour *neigh, const u8 *lladdr,
 
 	write_unlock_bh(&neigh->lock);
 
-	if (((new ^ old) & NUD_PERMANENT) || gc_update)
-		neigh_update_gc_list(neigh);
-	if (managed_update)
-		neigh_update_managed_list(neigh);
+	gc_update |= !!((new ^ old) & NUD_PERMANENT);
+	if (gc_update || managed_update) {
+		spin_lock_bh(&neigh->tbl->lock);
+
+		if (!neigh->dead) {
+			write_lock(&neigh->lock);
+
+			if (gc_update)
+				neigh_update_gc_list(neigh);
+			if (managed_update)
+				neigh_update_managed_list(neigh);
+
+			write_unlock(&neigh->lock);
+		}
+
+		spin_unlock_bh(&neigh->tbl->lock);
+	}
 
 	if (notify)
 		call_netevent_notifiers(NETEVENT_NEIGH_UPDATE, neigh);
@@ -1540,7 +1547,6 @@ int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
 {
 	return __neigh_update(neigh, lladdr, new, flags, nlmsg_pid, NULL);
 }
-EXPORT_SYMBOL(neigh_update);
 
 /* Update the neigh to listen temporarily for probe responses, even if it is
  * in a NUD_FAILED state. The caller has to hold neigh->lock for writing.
@@ -1558,7 +1564,6 @@ void __neigh_set_probe_once(struct neighbour *neigh)
 			jiffies + max(NEIGH_VAR(neigh->parms, RETRANS_TIME),
 				      HZ/100));
 }
-EXPORT_SYMBOL(__neigh_set_probe_once);
 
 struct neighbour *neigh_event_ns(struct neigh_table *tbl,
 				 u8 *lladdr, void *saddr,
@@ -1571,7 +1576,6 @@ struct neighbour *neigh_event_ns(struct neigh_table *tbl,
 			     NEIGH_UPDATE_F_OVERRIDE, 0);
 	return neigh;
 }
-EXPORT_SYMBOL(neigh_event_ns);
 
 /* called with read_lock_bh(&n->lock); */
 static void neigh_hh_init(struct neighbour *n)
@@ -1624,7 +1628,6 @@ int neigh_resolve_output(struct neighbour *neigh, struct sk_buff *skb)
 	kfree_skb_reason(skb, SKB_DROP_REASON_NEIGH_HH_FILLFAIL);
 	goto out;
 }
-EXPORT_SYMBOL(neigh_resolve_output);
 
 /* As fast as possible without hh cache */
 
@@ -1741,16 +1744,15 @@ void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,
 	mod_timer(&tbl->proxy_timer, sched_next);
 	spin_unlock(&tbl->proxy_queue.lock);
 }
-EXPORT_SYMBOL(pneigh_enqueue);
 
 static inline struct neigh_parms *lookup_neigh_parms(struct neigh_table *tbl,
-						      struct net *net, int ifindex)
+						     int ifindex)
 {
 	struct neigh_parms *p;
 
 	list_for_each_entry(p, &tbl->parms_list, list) {
-		if ((p->dev && p->dev->ifindex == ifindex && net_eq(neigh_parms_net(p), net)) ||
-		    (!p->dev && !ifindex && net_eq(net, &init_net)))
+		if ((p->dev && p->dev->ifindex == ifindex) ||
+		    (!p->dev && !ifindex))
 			return p;
 	}
 
@@ -1789,7 +1791,6 @@ struct neigh_parms *neigh_parms_alloc(struct net_device *dev,
 	}
 	return p;
 }
-EXPORT_SYMBOL(neigh_parms_alloc);
 
 static void neigh_rcu_free_parms(struct rcu_head *head)
 {
@@ -1812,113 +1813,169 @@ void neigh_parms_release(struct neigh_table *tbl, struct neigh_parms *parms)
 	netdev_put(parms->dev, &parms->dev_tracker);
 	call_rcu(&parms->rcu_head, neigh_rcu_free_parms);
 }
-EXPORT_SYMBOL(neigh_parms_release);
 
 static struct lock_class_key neigh_table_proxy_queue_class;
 
-static struct neigh_table __rcu *neigh_tables[NEIGH_NR_TABLES] __read_mostly;
-
-void neigh_table_init(int index, struct neigh_table *tbl)
+static int neigh_table_init(struct net *net, struct neigh_table *tbl)
 {
 	unsigned long now = jiffies;
 	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_net_data(tbl->id, 0, net->proc_net_stat,
+				  &neigh_stat_seq_ops,
+				  sizeof(struct seq_net_private), 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;
 
+	refcount_set(&tbl->entries, 1);
 	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);
-
-	tbl->last_flush = now;
-	tbl->last_rand	= now + tbl->parms.reachable_time * 20;
+	return 0;
 
-	rcu_assign_pointer(neigh_tables[index], tbl);
+#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:
+	return -ENOMEM;
 }
 
-/*
- * Only called from ndisc_cleanup(), which means this is dead code
- * because we no longer can unload IPv6 module.
- */
-int neigh_table_clear(int index, struct neigh_table *tbl)
+static void neigh_table_free(struct neigh_table *tbl)
 {
-	RCU_INIT_POINTER(neigh_tables[index], NULL);
-	synchronize_rcu();
+	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);
 
-	/* It is not clean... Fix it to unload IPv6 module safely */
+	kfree(tbl);
+}
+
+static void neigh_table_clear(struct net *net, struct neigh_table *tbl)
+{
 	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");
+	remove_proc_entry(tbl->id, net->proc_net_stat);
 
-	call_rcu(&rcu_dereference_protected(tbl->nht, 1)->rcu,
-		 neigh_hash_free_rcu);
-	tbl->nht = NULL;
+	neigh_table_put(tbl);
+}
 
-	kfree(tbl->phash_buckets);
-	tbl->phash_buckets = NULL;
+static void neigh_table_inherit(struct net *net, struct neigh_table *tbl,
+				int index)
+{
+	const struct neigh_table *init_tbl = init_net.neigh_tables[index];
+	bool inherit = READ_ONCE(sysctl_neigh_inherit_init_net);
+	int i;
 
-	remove_proc_entry(tbl->id, init_net.proc_net_stat);
+	if (net_eq(net, &init_net) || !inherit)
+		return;
 
-	free_percpu(tbl->stats);
-	tbl->stats = NULL;
+	tbl->gc_interval = READ_ONCE(init_tbl->gc_interval);
+	tbl->gc_thresh1 = READ_ONCE(init_tbl->gc_thresh1);
+	tbl->gc_thresh2 = READ_ONCE(init_tbl->gc_thresh2);
+	tbl->gc_thresh3 = READ_ONCE(init_tbl->gc_thresh3);
+
+	for (i = 0; i < NEIGH_VAR_DATA_MAX; i++)
+		tbl->parms.data[i] = READ_ONCE(init_tbl->parms.data[i]);
+}
+
+int neigh_table_register(struct net *net, struct neigh_table *tbl, int index)
+{
+	int err;
+
+	tbl = kmemdup(tbl, sizeof(*tbl), GFP_KERNEL);
+	if (!tbl) {
+		err = -ENOMEM;
+		goto err;
+	}
+
+	neigh_table_inherit(net, tbl, index);
+
+	err = neigh_table_init(net, tbl);
+	if (err)
+		goto free_table;
+
+	net->neigh_tables[index] = tbl;
 
 	return 0;
+
+free_table:
+	kfree(tbl);
+err:
+	return err;
+}
+
+void neigh_table_unregister(struct net *net, int index)
+{
+	struct neigh_table *tbl = net->neigh_tables[index];
+
+	net->neigh_tables[index] = NULL;
+	neigh_table_clear(net, tbl);
 }
 
-static struct neigh_table *neigh_find_table(int family)
+static struct neigh_table *neigh_find_table(struct net *net, int family)
 {
 	struct neigh_table *tbl = NULL;
 
 	switch (family) {
 	case AF_INET:
-		tbl = rcu_dereference_rtnl(neigh_tables[NEIGH_ARP_TABLE]);
+		tbl = net->neigh_tables[NEIGH_ARP_TABLE];
 		break;
 	case AF_INET6:
-		tbl = rcu_dereference_rtnl(neigh_tables[NEIGH_ND_TABLE]);
+		tbl = net->neigh_tables[NEIGH_ND_TABLE];
 		break;
 	}
 
@@ -1972,7 +2029,7 @@ static int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh,
 		}
 	}
 
-	tbl = neigh_find_table(ndm->ndm_family);
+	tbl = neigh_find_table(net, ndm->ndm_family);
 	if (tbl == NULL)
 		return -EAFNOSUPPORT;
 
@@ -1982,7 +2039,7 @@ static int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh,
 	}
 
 	if (ndm->ndm_flags & NTF_PROXY) {
-		err = pneigh_delete(tbl, net, nla_data(dst_attr), dev);
+		err = pneigh_delete(tbl, nla_data(dst_attr), dev);
 		goto out;
 	}
 
@@ -2058,7 +2115,7 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh,
 		}
 	}
 
-	tbl = neigh_find_table(ndm->ndm_family);
+	tbl = neigh_find_table(net, ndm->ndm_family);
 	if (tbl == NULL)
 		return -EAFNOSUPPORT;
 
@@ -2078,7 +2135,7 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh,
 			goto out;
 		}
 
-		err = pneigh_create(tbl, net, dst, dev, ndm_flags, protocol,
+		err = pneigh_create(tbl, dst, dev, ndm_flags, protocol,
 				    !!(ndm->ndm_state & NUD_PERMANENT));
 		goto out;
 	}
@@ -2267,7 +2324,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),
@@ -2400,10 +2457,8 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,
 
 	ndtmsg = nlmsg_data(nlh);
 
-	rcu_read_lock();
-
 	for (tidx = 0; tidx < NEIGH_NR_TABLES; tidx++) {
-		tbl = rcu_dereference(neigh_tables[tidx]);
+		tbl = net->neigh_tables[tidx];
 		if (!tbl)
 			continue;
 
@@ -2417,7 +2472,6 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,
 	}
 
 	if (!found) {
-		rcu_read_unlock();
 		err = -ENOENT;
 		goto errout;
 	}
@@ -2442,8 +2496,8 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,
 		if (tbp[NDTPA_IFINDEX])
 			ifindex = nla_get_u32(tbp[NDTPA_IFINDEX]);
 
-		p = lookup_neigh_parms(tbl, net, ifindex);
-		if (p == NULL) {
+		p = lookup_neigh_parms(tbl, ifindex);
+		if (!p) {
 			err = -ENOENT;
 			goto errout_tbl_lock;
 		}
@@ -2524,12 +2578,6 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,
 		}
 	}
 
-	err = -ENOENT;
-	if ((tb[NDTA_THRESH1] || tb[NDTA_THRESH2] ||
-	     tb[NDTA_THRESH3] || tb[NDTA_GC_INTERVAL]) &&
-	    !net_eq(net, &init_net))
-		goto errout_tbl_lock;
-
 	if (tb[NDTA_THRESH1])
 		WRITE_ONCE(tbl->gc_thresh1, nla_get_u32(tb[NDTA_THRESH1]));
 
@@ -2546,7 +2594,6 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,
 
 errout_tbl_lock:
 	spin_unlock_bh(&tbl->lock);
-	rcu_read_unlock();
 errout:
 	return err;
 }
@@ -2598,7 +2645,7 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
 	for (tidx = 0; tidx < NEIGH_NR_TABLES; tidx++) {
 		struct neigh_parms *p;
 
-		tbl = rcu_dereference(neigh_tables[tidx]);
+		tbl = net->neigh_tables[tidx];
 		if (!tbl)
 			continue;
 
@@ -2613,9 +2660,6 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
 		nidx = 0;
 		p = list_next_entry(&tbl->parms, list);
 		list_for_each_entry_from_rcu(p, &tbl->parms_list, list) {
-			if (!net_eq(neigh_parms_net(p), net))
-				continue;
-
 			if (nidx < neigh_skip)
 				goto next;
 
@@ -2793,12 +2837,11 @@ static int neigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,
 			    struct netlink_callback *cb,
 			    struct neigh_dump_filter *filter)
 {
-	struct net *net = sock_net(skb->sk);
-	struct neighbour *n;
-	int err = 0, h, s_h = cb->args[1];
 	int idx, s_idx = idx = cb->args[2];
-	struct neigh_hash_table *nht;
+	int err = 0, h, s_h = cb->args[1];
 	unsigned int flags = NLM_F_MULTI;
+	struct neigh_hash_table *nht;
+	struct neighbour *n;
 
 	if (filter->dev_idx || filter->master_idx)
 		flags |= NLM_F_DUMP_FILTERED;
@@ -2810,7 +2853,7 @@ static int neigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,
 			s_idx = 0;
 		idx = 0;
 		neigh_for_each_in_bucket_rcu(n, &nht->hash_heads[h]) {
-			if (idx < s_idx || !net_eq(dev_net(n->dev), net))
+			if (idx < s_idx)
 				goto next;
 			if (neigh_ifindex_filtered(n->dev, filter->dev_idx) ||
 			    neigh_master_filtered(n->dev, filter->master_idx))
@@ -2834,11 +2877,10 @@ static int pneigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,
 			     struct netlink_callback *cb,
 			     struct neigh_dump_filter *filter)
 {
-	struct pneigh_entry *n;
-	struct net *net = sock_net(skb->sk);
-	int err = 0, h, s_h = cb->args[3];
 	int idx, s_idx = idx = cb->args[4];
+	int err = 0, h, s_h = cb->args[3];
 	unsigned int flags = NLM_F_MULTI;
+	struct pneigh_entry *n;
 
 	if (filter->dev_idx || filter->master_idx)
 		flags |= NLM_F_DUMP_FILTERED;
@@ -2849,7 +2891,7 @@ static int pneigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,
 		for (n = rcu_dereference(tbl->phash_buckets[h]), idx = 0;
 		     n;
 		     n = rcu_dereference(n->next)) {
-			if (idx < s_idx || pneigh_net(n) != net)
+			if (idx < s_idx)
 				goto next;
 			if (neigh_ifindex_filtered(n->dev, filter->dev_idx) ||
 			    neigh_master_filtered(n->dev, filter->master_idx))
@@ -2935,6 +2977,7 @@ static int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
 {
 	const struct nlmsghdr *nlh = cb->nlh;
 	struct neigh_dump_filter filter = {};
+	struct net *net = sock_net(skb->sk);
 	struct neigh_table *tbl;
 	int t, family, s_t;
 	int proxy = 0;
@@ -2958,8 +3001,7 @@ static int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
 
 	rcu_read_lock();
 	for (t = 0; t < NEIGH_NR_TABLES; t++) {
-		tbl = rcu_dereference(neigh_tables[t]);
-
+		tbl = net->neigh_tables[t];
 		if (!tbl)
 			continue;
 		if (t < s_t || (family && tbl->family != family))
@@ -3081,7 +3123,7 @@ static int neigh_get(struct sk_buff *in_skb, struct nlmsghdr *nlh,
 
 	rcu_read_lock();
 
-	tbl = neigh_find_table(ndm->ndm_family);
+	tbl = neigh_find_table(net, ndm->ndm_family);
 	if (!tbl) {
 		NL_SET_ERR_MSG(extack, "Unsupported family in header for neighbor get request");
 		err = -EAFNOSUPPORT;
@@ -3108,7 +3150,7 @@ static int neigh_get(struct sk_buff *in_skb, struct nlmsghdr *nlh,
 	if (ndm->ndm_flags & NTF_PROXY) {
 		struct pneigh_entry *pn;
 
-		pn = pneigh_lookup(tbl, net, dst, dev);
+		pn = pneigh_lookup(tbl, dst, dev);
 		if (!pn) {
 			NL_SET_ERR_MSG(extack, "Proxy neighbour entry not found");
 			err = -ENOENT;
@@ -3161,37 +3203,6 @@ void neigh_for_each(struct neigh_table *tbl, void (*cb)(struct neighbour *, void
 }
 EXPORT_SYMBOL(neigh_for_each);
 
-/* The tbl->lock must be held as a writer and BH disabled. */
-void __neigh_for_each_release(struct neigh_table *tbl,
-			      int (*cb)(struct neighbour *))
-{
-	struct neigh_hash_table *nht;
-	int chain;
-
-	nht = rcu_dereference_protected(tbl->nht,
-					lockdep_is_held(&tbl->lock));
-	for (chain = 0; chain < (1 << nht->hash_shift); chain++) {
-		struct hlist_node *tmp;
-		struct neighbour *n;
-
-		neigh_for_each_in_bucket_safe(n, tmp, &nht->hash_heads[chain]) {
-			int release;
-
-			write_lock(&n->lock);
-			release = cb(n);
-			if (release) {
-				hlist_del_rcu(&n->hash);
-				hlist_del_rcu(&n->dev_list);
-				neigh_mark_dead(n);
-			}
-			write_unlock(&n->lock);
-			if (release)
-				neigh_cleanup_and_release(n);
-		}
-	}
-}
-EXPORT_SYMBOL(__neigh_for_each_release);
-
 int neigh_xmit(int index, struct net_device *dev,
 	       const void *addr, struct sk_buff *skb)
 {
@@ -3200,9 +3211,11 @@ int neigh_xmit(int index, struct net_device *dev,
 	if (likely(index < NEIGH_NR_TABLES)) {
 		struct neigh_table *tbl;
 		struct neighbour *neigh;
+		struct net *net;
 
 		rcu_read_lock();
-		tbl = rcu_dereference(neigh_tables[index]);
+		net = dev_net_rcu(dev);
+		tbl = net->neigh_tables[index];
 		if (!tbl) {
 			rcu_read_unlock();
 			goto out_kfree_skb;
@@ -3245,10 +3258,6 @@ static struct neighbour *neigh_get_valid(struct seq_file *seq,
 					 loff_t *pos)
 {
 	struct neigh_seq_state *state = seq->private;
-	struct net *net = seq_file_net(seq);
-
-	if (!net_eq(dev_net(n->dev), net))
-		return NULL;
 
 	if (state->neigh_sub_iter) {
 		loff_t fakep = 0;
@@ -3337,7 +3346,6 @@ static struct neighbour *neigh_get_idx(struct seq_file *seq, loff_t *pos)
 static struct pneigh_entry *pneigh_get_first(struct seq_file *seq)
 {
 	struct neigh_seq_state *state = seq->private;
-	struct net *net = seq_file_net(seq);
 	struct neigh_table *tbl = state->tbl;
 	struct pneigh_entry *pn = NULL;
 	int bucket;
@@ -3345,9 +3353,6 @@ static struct pneigh_entry *pneigh_get_first(struct seq_file *seq)
 	state->flags |= NEIGH_SEQ_IS_PNEIGH;
 	for (bucket = 0; bucket <= PNEIGH_HASHMASK; bucket++) {
 		pn = rcu_dereference(tbl->phash_buckets[bucket]);
-
-		while (pn && !net_eq(pneigh_net(pn), net))
-			pn = rcu_dereference(pn->next);
 		if (pn)
 			break;
 	}
@@ -3361,21 +3366,15 @@ static struct pneigh_entry *pneigh_get_next(struct seq_file *seq,
 					    loff_t *pos)
 {
 	struct neigh_seq_state *state = seq->private;
-	struct net *net = seq_file_net(seq);
 	struct neigh_table *tbl = state->tbl;
 
-	do {
-		pn = rcu_dereference(pn->next);
-	} while (pn && !net_eq(pneigh_net(pn), net));
+	pn = rcu_dereference(pn->next);
 
 	while (!pn) {
 		if (++state->bucket > PNEIGH_HASHMASK)
 			break;
 
 		pn = rcu_dereference(tbl->phash_buckets[state->bucket]);
-
-		while (pn && !net_eq(pneigh_net(pn), net))
-			pn = rcu_dereference(pn->next);
 		if (pn)
 			break;
 	}
@@ -3430,7 +3429,6 @@ void *neigh_seq_start(struct seq_file *seq, loff_t *pos, struct neigh_table *tbl
 
 	return *pos ? neigh_get_idx_any(seq, pos) : SEQ_START_TOKEN;
 }
-EXPORT_SYMBOL(neigh_seq_start);
 
 void *neigh_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 {
@@ -3457,7 +3455,6 @@ void *neigh_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 	++(*pos);
 	return rc;
 }
-EXPORT_SYMBOL(neigh_seq_next);
 
 void neigh_seq_stop(struct seq_file *seq, void *v)
 	__releases(tbl->lock)
@@ -3469,7 +3466,6 @@ void neigh_seq_stop(struct seq_file *seq, void *v)
 	spin_unlock_bh(&tbl->lock);
 	rcu_read_unlock();
 }
-EXPORT_SYMBOL(neigh_seq_stop);
 
 /* statistics via seq_file */
 
@@ -3523,7 +3519,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,
@@ -3593,7 +3589,6 @@ void neigh_app_ns(struct neighbour *n)
 {
 	neigh_notify(n, RTM_GETNEIGH, NLM_F_REQUEST, 0);
 }
-EXPORT_SYMBOL(neigh_app_ns);
 
 #ifdef CONFIG_SYSCTL
 static int unres_qlen_max = INT_MAX / SKB_TRUESIZE(ETH_FRAME_LEN);
@@ -3689,7 +3684,6 @@ int neigh_proc_dointvec(const struct ctl_table *ctl, int write, void *buffer,
 	neigh_proc_update(ctl, write);
 	return ret;
 }
-EXPORT_SYMBOL(neigh_proc_dointvec);
 
 int neigh_proc_dointvec_jiffies(const struct ctl_table *ctl, int write, void *buffer,
 				size_t *lenp, loff_t *ppos)
@@ -3699,7 +3693,6 @@ int neigh_proc_dointvec_jiffies(const struct ctl_table *ctl, int write, void *bu
 	neigh_proc_update(ctl, write);
 	return ret;
 }
-EXPORT_SYMBOL(neigh_proc_dointvec_jiffies);
 
 static int neigh_proc_dointvec_userhz_jiffies(const struct ctl_table *ctl, int write,
 					      void *buffer, size_t *lenp,
@@ -3719,7 +3712,6 @@ int neigh_proc_dointvec_ms_jiffies(const struct ctl_table *ctl, int write,
 	neigh_proc_update(ctl, write);
 	return ret;
 }
-EXPORT_SYMBOL(neigh_proc_dointvec_ms_jiffies);
 
 static int neigh_proc_dointvec_unres_qlen(const struct ctl_table *ctl, int write,
 					  void *buffer, size_t *lenp,
@@ -3926,7 +3918,6 @@ int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p,
 err:
 	return -ENOBUFS;
 }
-EXPORT_SYMBOL(neigh_sysctl_register);
 
 void neigh_sysctl_unregister(struct neigh_parms *p)
 {
@@ -3937,7 +3928,6 @@ void neigh_sysctl_unregister(struct neigh_parms *p)
 		kfree(t);
 	}
 }
-EXPORT_SYMBOL(neigh_sysctl_unregister);
 
 #endif	/* CONFIG_SYSCTL */
 
diff --git a/net/core/sysctl_net_core.c b/net/core/sysctl_net_core.c
index b508618bfc123..9777b9ef58949 100644
--- a/net/core/sysctl_net_core.c
+++ b/net/core/sysctl_net_core.c
@@ -21,6 +21,7 @@
 
 #include <net/ip.h>
 #include <net/sock.h>
+#include <net/neighbour.h>
 #include <net/net_ratelimit.h>
 #include <net/busy_poll.h>
 #include <net/pkt_sched.h>
@@ -53,6 +54,8 @@ EXPORT_SYMBOL(sysctl_fb_tunnels_only_for_init_net);
 int sysctl_devconf_inherit_init_net __read_mostly;
 EXPORT_SYMBOL(sysctl_devconf_inherit_init_net);
 
+int sysctl_neigh_inherit_init_net __read_mostly = 1;
+
 #if IS_ENABLED(CONFIG_NET_FLOW_LIMIT) || IS_ENABLED(CONFIG_RPS)
 static int dump_cpumask(void *buffer, size_t *lenp, loff_t *ppos,
 			struct cpumask *mask)
@@ -676,6 +679,15 @@ static struct ctl_table net_core_table[] = {
 		.proc_handler	= proc_do_skb_defer_max,
 		.extra1		= SYSCTL_ZERO,
 	},
+	{
+		.procname	= "neigh_inherit_init_net",
+		.data		= &sysctl_neigh_inherit_init_net,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_minmax,
+		.extra1		= SYSCTL_ZERO,
+		.extra2		= SYSCTL_ONE,
+	},
 };
 
 static struct ctl_table netns_core_table[] = {
diff --git a/net/ieee802154/6lowpan/tx.c b/net/ieee802154/6lowpan/tx.c
index 4df76ff50699e..4f511476b992c 100644
--- a/net/ieee802154/6lowpan/tx.c
+++ b/net/ieee802154/6lowpan/tx.c
@@ -58,8 +58,9 @@ int lowpan_header_create(struct sk_buff *skb, struct net_device *ldev,
 		info->daddr.mode = IEEE802154_ADDR_SHORT;
 	} else {
 		__le16 short_addr = cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC);
+		struct neigh_table *tbl = nd_table(dev_net(ldev));
 
-		n = neigh_lookup(&nd_tbl, &hdr->daddr, ldev);
+		n = neigh_lookup(tbl, &hdr->daddr, ldev);
 		if (n) {
 			llneigh = lowpan_802154_neigh(neighbour_priv(n));
 			read_lock_bh(&n->lock);
diff --git a/net/ipv4/arp.c b/net/ipv4/arp.c
index d409f606aec0e..90bc53fb80905 100644
--- a/net/ipv4/arp.c
+++ b/net/ipv4/arp.c
@@ -149,7 +149,7 @@ static const struct neigh_ops arp_direct_ops = {
 	.connected_output =	neigh_direct_output,
 };
 
-struct neigh_table arp_tbl = {
+static struct neigh_table arp_tbl = {
 	.family		= AF_INET,
 	.key_len	= 4,
 	.protocol	= cpu_to_be16(ETH_P_IP),
@@ -182,7 +182,6 @@ struct neigh_table arp_tbl = {
 	.gc_thresh2	= 512,
 	.gc_thresh3	= 1024,
 };
-EXPORT_SYMBOL(arp_tbl);
 
 int arp_mc_map(__be32 addr, u8 *haddr, struct net_device *dev, int dir)
 {
@@ -222,14 +221,18 @@ static bool arp_key_eq(const struct neighbour *neigh, const void *pkey)
 
 static int arp_constructor(struct neighbour *neigh)
 {
-	__be32 addr;
 	struct net_device *dev = neigh->dev;
-	struct in_device *in_dev;
-	struct neigh_parms *parms;
+	struct net *net = dev_net(dev);
 	u32 inaddr_any = INADDR_ANY;
+	struct neigh_parms *parms;
+	struct in_device *in_dev;
+	struct neigh_table *tbl;
+	__be32 addr;
+
+	tbl = arp_table(net);
 
 	if (dev->flags & (IFF_LOOPBACK | IFF_POINTOPOINT))
-		memcpy(neigh->primary_key, &inaddr_any, arp_tbl.key_len);
+		memcpy(neigh->primary_key, &inaddr_any, tbl->key_len);
 
 	addr = *(__be32 *)neigh->primary_key;
 	rcu_read_lock();
@@ -239,7 +242,7 @@ static int arp_constructor(struct neighbour *neigh)
 		return -EINVAL;
 	}
 
-	neigh->type = inet_addr_type_dev_table(dev_net(dev), dev, addr);
+	neigh->type = inet_addr_type_dev_table(net, dev, addr);
 
 	parms = in_dev->arp_parms;
 	__neigh_parms_put(neigh->parms);
@@ -701,24 +704,24 @@ static bool arp_is_garp(struct net *net, struct net_device *dev,
 
 static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
 {
+	struct neigh_table *tbl = arp_table(net);
+	struct dst_entry *reply_dst = NULL;
 	struct net_device *dev = skb->dev;
-	struct in_device *in_dev = __in_dev_get_rcu(dev);
-	struct arphdr *arp;
+	unsigned char *sha, *tha = NULL;
+	struct in_device *in_dev;
+	u16 dev_type = dev->type;
 	unsigned char *arp_ptr;
+	bool is_garp = false;
+	struct neighbour *n;
+	struct arphdr *arp;
 	struct rtable *rt;
-	unsigned char *sha;
-	unsigned char *tha = NULL;
 	__be32 sip, tip;
-	u16 dev_type = dev->type;
 	int addr_type;
-	struct neighbour *n;
-	struct dst_entry *reply_dst = NULL;
-	bool is_garp = false;
 
 	/* arp_rcv below verifies the ARP header and verifies the device
 	 * is ARP'able.
 	 */
-
+	in_dev = __in_dev_get_rcu(dev);
 	if (!in_dev)
 		goto out_free_skb;
 
@@ -850,7 +853,7 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
 			if (!dont_send && IN_DEV_ARPFILTER(in_dev))
 				dont_send = arp_filter(sip, tip, dev);
 			if (!dont_send) {
-				n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
+				n = neigh_event_ns(tbl, sha, &sip, dev);
 				if (n) {
 					arp_send_dst(ARPOP_REPLY, ETH_P_ARP,
 						     sip, dev, tip, sha,
@@ -865,8 +868,8 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
 			    (arp_fwd_proxy(in_dev, dev, rt) ||
 			     arp_fwd_pvlan(in_dev, dev, rt, sip, tip) ||
 			     (rt->dst.dev != dev &&
-			      pneigh_lookup(&arp_tbl, net, &tip, dev)))) {
-				n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
+			      pneigh_lookup(tbl, &tip, dev)))) {
+				n = neigh_event_ns(tbl, sha, &sip, dev);
 				if (n)
 					neigh_release(n);
 
@@ -878,7 +881,7 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
 						     dev->dev_addr, sha,
 						     reply_dst);
 				} else {
-					pneigh_enqueue(&arp_tbl,
+					pneigh_enqueue(tbl,
 						       in_dev->arp_parms, skb);
 					goto out_free_dst;
 				}
@@ -889,7 +892,7 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
 
 	/* Update our ARP tables */
 
-	n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
+	n = __neigh_lookup(tbl, &sip, dev, 0);
 
 	addr_type = -1;
 	if (n || arp_accept(in_dev, sip)) {
@@ -910,7 +913,7 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
 			/* postpone calculation to as late as possible */
 			inet_addr_type_dev_table(net, dev, sip) ==
 				RTN_UNICAST)))))
-			n = __neigh_lookup(&arp_tbl, &sip, dev, 1);
+			n = __neigh_lookup(tbl, &sip, dev, 1);
 	}
 
 	if (n) {
@@ -1077,9 +1080,10 @@ static int arp_req_set_proxy(struct net *net, struct net_device *dev, int on)
 }
 
 static int arp_req_set_public(struct net *net, struct arpreq *r,
-		struct net_device *dev)
+			      struct net_device *dev)
 {
 	__be32 mask = ((struct sockaddr_in *)&r->arp_netmask)->sin_addr.s_addr;
+	struct neigh_table *tbl = arp_table(net);
 
 	if (!dev && (r->arp_flags & ATF_COM)) {
 		dev = dev_getbyhwaddr(net, r->arp_ha.sa_family,
@@ -1090,7 +1094,7 @@ static int arp_req_set_public(struct net *net, struct arpreq *r,
 	if (mask) {
 		__be32 ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
 
-		return pneigh_create(&arp_tbl, net, &ip, dev, 0, 0, false);
+		return pneigh_create(tbl, &ip, dev, 0, 0, false);
 	}
 
 	return arp_req_set_proxy(net, dev, 1);
@@ -1098,6 +1102,7 @@ static int arp_req_set_public(struct net *net, struct arpreq *r,
 
 static int arp_req_set(struct net *net, struct arpreq *r)
 {
+	struct neigh_table *tbl = arp_table(net);
 	struct neighbour *neigh;
 	struct net_device *dev;
 	__be32 ip;
@@ -1133,7 +1138,7 @@ static int arp_req_set(struct net *net, struct arpreq *r)
 
 	ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
 
-	neigh = __neigh_lookup_errno(&arp_tbl, &ip, dev);
+	neigh = __neigh_lookup_errno(tbl, &ip, dev);
 	err = PTR_ERR(neigh);
 	if (!IS_ERR(neigh)) {
 		unsigned int state = NUD_STALE;
@@ -1169,6 +1174,7 @@ static unsigned int arp_state_to_flags(struct neighbour *neigh)
 static int arp_req_get(struct net *net, struct arpreq *r)
 {
 	__be32 ip = ((struct sockaddr_in *) &r->arp_pa)->sin_addr.s_addr;
+	struct neigh_table *tbl = arp_table(net);
 	struct neighbour *neigh;
 	struct net_device *dev;
 
@@ -1179,7 +1185,7 @@ static int arp_req_get(struct net *net, struct arpreq *r)
 	if (IS_ERR(dev))
 		return PTR_ERR(dev);
 
-	neigh = neigh_lookup(&arp_tbl, &ip, dev);
+	neigh = neigh_lookup(tbl, &ip, dev);
 	if (!neigh)
 		return -ENXIO;
 
@@ -1204,10 +1210,11 @@ static int arp_req_get(struct net *net, struct arpreq *r)
 
 int arp_invalidate(struct net_device *dev, __be32 ip, bool force)
 {
-	struct neighbour *neigh = neigh_lookup(&arp_tbl, &ip, dev);
+	struct neigh_table *tbl = arp_table(dev_net(dev));
+	struct neighbour *neigh;
 	int err = -ENXIO;
-	struct neigh_table *tbl = &arp_tbl;
 
+	neigh = neigh_lookup(tbl, &ip, dev);
 	if (neigh) {
 		if ((READ_ONCE(neigh->nud_state) & NUD_VALID) && !force) {
 			neigh_release(neigh);
@@ -1228,14 +1235,15 @@ int arp_invalidate(struct net_device *dev, __be32 ip, bool force)
 }
 
 static int arp_req_delete_public(struct net *net, struct arpreq *r,
-		struct net_device *dev)
+				 struct net_device *dev)
 {
 	__be32 mask = ((struct sockaddr_in *)&r->arp_netmask)->sin_addr.s_addr;
+	struct neigh_table *tbl = arp_table(net);
 
 	if (mask) {
 		__be32 ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
 
-		return pneigh_delete(&arp_tbl, net, &ip, dev);
+		return pneigh_delete(tbl, &ip, dev);
 	}
 
 	return arp_req_set_proxy(net, dev, 0);
@@ -1325,18 +1333,22 @@ static int arp_netdev_event(struct notifier_block *this, unsigned long event,
 {
 	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
 	struct netdev_notifier_change_info *change_info;
+	struct net *net = dev_net(dev);
 	struct in_device *in_dev;
+	struct neigh_table *tbl;
 	bool evict_nocarrier;
 
+	tbl = arp_table(net);
+
 	switch (event) {
 	case NETDEV_CHANGEADDR:
-		neigh_changeaddr(&arp_tbl, dev);
-		rt_cache_flush(dev_net(dev));
+		neigh_changeaddr(tbl, dev);
+		rt_cache_flush(net);
 		break;
 	case NETDEV_CHANGE:
 		change_info = ptr;
 		if (change_info->flags_changed & IFF_NOARP)
-			neigh_changeaddr(&arp_tbl, dev);
+			neigh_changeaddr(tbl, dev);
 
 		in_dev = __in_dev_get_rtnl(dev);
 		if (!in_dev)
@@ -1345,7 +1357,7 @@ static int arp_netdev_event(struct notifier_block *this, unsigned long event,
 			evict_nocarrier = IN_DEV_ARP_EVICT_NOCARRIER(in_dev);
 
 		if (evict_nocarrier && !netif_carrier_ok(dev))
-			neigh_carrier_down(&arp_tbl, dev);
+			neigh_carrier_down(tbl, dev);
 		break;
 	default:
 		break;
@@ -1364,7 +1376,9 @@ static struct notifier_block arp_netdev_notifier = {
  */
 void arp_ifdown(struct net_device *dev)
 {
-	neigh_ifdown(&arp_tbl, dev);
+	struct neigh_table *tbl = arp_table(dev_net(dev));
+
+	neigh_ifdown(tbl, dev);
 }
 
 
@@ -1479,10 +1493,12 @@ static int arp_seq_show(struct seq_file *seq, void *v)
 
 static void *arp_seq_start(struct seq_file *seq, loff_t *pos)
 {
+	struct neigh_table *tbl = arp_table(seq_file_net(seq));
+
 	/* Don't want to confuse "arp -a" w/ magic entries,
 	 * so we tell the generic iterator to skip NUD_NOARP.
 	 */
-	return neigh_seq_start(seq, pos, &arp_tbl, NEIGH_SEQ_SKIP_NOARP);
+	return neigh_seq_start(seq, pos, tbl, NEIGH_SEQ_SKIP_NOARP);
 }
 
 static const struct seq_operations arp_seq_ops = {
@@ -1495,15 +1511,47 @@ 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
+#ifdef CONFIG_SYSCTL
+	err = neigh_sysctl_register(NULL, &arp_table(net)->parms, NULL);
+	if (err)
+		goto err_sysctl;
+#endif
+
 	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:
+#ifdef CONFIG_SYSCTL
+	neigh_sysctl_unregister(&arp_table(net)->parms);
+err_sysctl:
+#endif
+	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);
+#ifdef CONFIG_SYSCTL
+	neigh_sysctl_unregister(&arp_table(net)->parms);
+#endif
+	neigh_table_unregister(net, NEIGH_ARP_TABLE);
 }
 
 static struct pernet_operations arp_net_ops = {
@@ -1513,12 +1561,9 @@ static struct pernet_operations arp_net_ops = {
 
 void __init arp_init(void)
 {
-	neigh_table_init(NEIGH_ARP_TABLE, &arp_tbl);
+	if (register_pernet_subsys(&arp_net_ops))
+		panic("Cannot allocate arp table\n");
 
 	dev_add_pack(&arp_packet_type);
-	register_pernet_subsys(&arp_net_ops);
-#ifdef CONFIG_SYSCTL
-	neigh_sysctl_register(NULL, &arp_tbl.parms, NULL);
-#endif
 	register_netdevice_notifier(&arp_netdev_notifier);
 }
diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c
index 47ded0f607d4b..05de597ce086f 100644
--- a/net/ipv4/devinet.c
+++ b/net/ipv4/devinet.c
@@ -265,7 +265,9 @@ EXPORT_SYMBOL(in_dev_finish_destroy);
 
 static struct in_device *inetdev_init(struct net_device *dev)
 {
+	struct net *net = dev_net(dev);
 	struct in_device *in_dev;
+	struct neigh_table *tbl;
 	int err = -ENOMEM;
 
 	ASSERT_RTNL();
@@ -273,11 +275,12 @@ static struct in_device *inetdev_init(struct net_device *dev)
 	in_dev = kzalloc_obj(*in_dev);
 	if (!in_dev)
 		goto out;
-	memcpy(&in_dev->cnf, dev_net(dev)->ipv4.devconf_dflt,
-			sizeof(in_dev->cnf));
+
+	tbl = arp_table(net);
+	memcpy(&in_dev->cnf, net->ipv4.devconf_dflt, sizeof(in_dev->cnf));
 	in_dev->cnf.sysctl = NULL;
 	in_dev->dev = dev;
-	in_dev->arp_parms = neigh_parms_alloc(dev, &arp_tbl);
+	in_dev->arp_parms = neigh_parms_alloc(dev, tbl);
 	if (!in_dev->arp_parms)
 		goto out_kfree;
 	if (IPV4_DEVCONF(in_dev->cnf, FORWARDING))
@@ -291,7 +294,7 @@ static struct in_device *inetdev_init(struct net_device *dev)
 		err = devinet_sysctl_register(in_dev);
 		if (err) {
 			in_dev->dead = 1;
-			neigh_parms_release(&arp_tbl, in_dev->arp_parms);
+			neigh_parms_release(tbl, in_dev->arp_parms);
 			in_dev_put(in_dev);
 			in_dev = NULL;
 			goto out;
@@ -313,13 +316,12 @@ static struct in_device *inetdev_init(struct net_device *dev)
 
 static void inetdev_destroy(struct in_device *in_dev)
 {
-	struct net_device *dev;
+	struct net_device *dev = in_dev->dev;
+	struct net *net = dev_net(dev);
 	struct in_ifaddr *ifa;
 
 	ASSERT_RTNL();
 
-	dev = in_dev->dev;
-
 	in_dev->dead = 1;
 
 	RCU_INIT_POINTER(dev->ip_ptr, NULL);
@@ -332,7 +334,7 @@ static void inetdev_destroy(struct in_device *in_dev)
 	}
 
 	devinet_sysctl_unregister(in_dev);
-	neigh_parms_release(&arp_tbl, in_dev->arp_parms);
+	neigh_parms_release(arp_table(net), in_dev->arp_parms);
 	arp_ifdown(dev);
 
 	in_dev_put(in_dev);
diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c
index 78f84ae3ee120..cda150309dc6b 100644
--- a/net/ipv4/fib_semantics.c
+++ b/net/ipv4/fib_semantics.c
@@ -610,13 +610,14 @@ static int fib_detect_death(struct fib_info *fi, int order,
 			    int dflt)
 {
 	const struct fib_nh_common *nhc = fib_info_nhc(fi, 0);
-	struct neighbour *n;
+	struct net *net = fi->fib_net;
 	int state = NUD_NONE;
+	struct neighbour *n;
 
 	if (likely(nhc->nhc_gw_family == AF_INET))
-		n = neigh_lookup(&arp_tbl, &nhc->nhc_gw.ipv4, nhc->nhc_dev);
+		n = neigh_lookup(arp_table(net), &nhc->nhc_gw.ipv4, nhc->nhc_dev);
 	else if (IS_ENABLED(CONFIG_IPV6) && nhc->nhc_gw_family == AF_INET6)
-		n = neigh_lookup(&nd_tbl, &nhc->nhc_gw.ipv6, nhc->nhc_dev);
+		n = neigh_lookup(nd_table(net), &nhc->nhc_gw.ipv6, nhc->nhc_dev);
 	else
 		n = NULL;
 
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index fd688e1f879f5..b8b6b5d991294 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -788,7 +788,7 @@ static void __ip_do_redirect(struct rtable *rt, struct sk_buff *skb, struct flow
 
 	n = __ipv4_neigh_lookup(rt->dst.dev, (__force u32)new_gw);
 	if (!n)
-		n = neigh_create(&arp_tbl, &new_gw, rt->dst.dev);
+		n = neigh_create(arp_table(net), &new_gw, rt->dst.dev);
 	if (!IS_ERR(n)) {
 		if (!(READ_ONCE(n->nud_state) & NUD_VALID)) {
 			neigh_event_send(n, NULL);
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index f6fa2715b450b..f3be7a8f996dd 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -376,6 +376,8 @@ static int snmp6_alloc_dev(struct inet6_dev *idev)
 
 static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
 {
+	struct net *net = dev_net(dev);
+	struct neigh_table *tbl;
 	struct inet6_dev *ndev;
 	int err = -ENOMEM;
 
@@ -393,14 +395,15 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
 	ndev->dev = dev;
 	INIT_LIST_HEAD(&ndev->addr_list);
 	timer_setup(&ndev->rs_timer, addrconf_rs_timer, 0);
-	memcpy(&ndev->cnf, dev_net(dev)->ipv6.devconf_dflt, sizeof(ndev->cnf));
+	memcpy(&ndev->cnf, net->ipv6.devconf_dflt, sizeof(ndev->cnf));
 
 	if (ndev->cnf.stable_secret.initialized)
 		ndev->cnf.addr_gen_mode = IN6_ADDR_GEN_MODE_STABLE_PRIVACY;
 
+	tbl = nd_table(net);
 	ndev->cnf.mtu6 = dev->mtu;
 	ndev->ra_mtu = 0;
-	ndev->nd_parms = neigh_parms_alloc(dev, &nd_tbl);
+	ndev->nd_parms = neigh_parms_alloc(dev, tbl);
 	if (!ndev->nd_parms) {
 		kfree(ndev);
 		return ERR_PTR(err);
@@ -413,7 +416,7 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
 	if (snmp6_alloc_dev(ndev) < 0) {
 		netdev_dbg(dev, "%s: cannot allocate memory for statistics\n",
 			   __func__);
-		neigh_parms_release(&nd_tbl, ndev->nd_parms);
+		neigh_parms_release(tbl, ndev->nd_parms);
 		netdev_put(dev, &ndev->dev_tracker);
 		kfree(ndev);
 		return ERR_PTR(err);
@@ -481,7 +484,7 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
 	return ndev;
 
 err_release:
-	neigh_parms_release(&nd_tbl, ndev->nd_parms);
+	neigh_parms_release(tbl, ndev->nd_parms);
 	ndev->dead = 1;
 	in6_dev_finish_destroy(ndev);
 	return ERR_PTR(err);
@@ -4038,9 +4041,11 @@ static int addrconf_ifdown(struct net_device *dev, bool unregister)
 
 	/* Last: Shot the device (if unregistered) */
 	if (unregister) {
+		struct neigh_table *tbl = nd_table(net);
+
 		addrconf_sysctl_unregister(idev);
-		neigh_parms_release(&nd_tbl, idev->nd_parms);
-		neigh_ifdown(&nd_tbl, dev);
+		neigh_parms_release(tbl, idev->nd_parms);
+		neigh_ifdown(tbl, dev);
 		in6_dev_put(idev);
 	}
 	return 0;
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 2c44e5ed61716..25fc57e52b5fa 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -125,7 +125,7 @@ static int ip6_finish_output2(struct net *net, struct sock *sk, struct sk_buff *
 
 	if (IS_ERR_OR_NULL(neigh)) {
 		if (unlikely(!neigh))
-			neigh = __neigh_create(&nd_tbl, nexthop, dev, false);
+			neigh = __neigh_create(nd_table(net), nexthop, dev, false);
 		if (IS_ERR(neigh)) {
 			IP6_INC_STATS(net, idev, IPSTATS_MIB_OUTNOROUTES);
 			kfree_skb_reason(skb, SKB_DROP_REASON_NEIGH_CREATEFAIL);
@@ -581,7 +581,7 @@ int ip6_forward(struct sk_buff *skb)
 
 	/* XXX: idev->cnf.proxy_ndp? */
 	if (READ_ONCE(net->ipv6.devconf_all->proxy_ndp) &&
-	    pneigh_lookup(&nd_tbl, net, &hdr->daddr, skb->dev)) {
+	    pneigh_lookup(nd_table(net), &hdr->daddr, skb->dev)) {
 		int proxied = ip6_forward_proxy_check(skb);
 
 		hdr = ipv6_hdr(skb);
diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index 2ceb655c4229e..022c2296807a3 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -767,10 +767,11 @@ static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb)
 static int pndisc_is_router(const void *pkey,
 			    struct net_device *dev)
 {
+	struct net *net = dev_net(dev);
 	struct pneigh_entry *n;
 	int ret = -1;
 
-	n = pneigh_lookup(&nd_tbl, dev_net(dev), pkey, dev);
+	n = pneigh_lookup(nd_table(net), pkey, dev);
 	if (n)
 		ret = !!(READ_ONCE(n->flags) & NTF_ROUTER);
 
@@ -788,19 +789,21 @@ void ndisc_update(const struct net_device *dev, struct neighbour *neigh,
 
 static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)
 {
+	u32 ndoptlen = skb_tail_pointer(skb) - (skb_transport_header(skb) +
+				    offsetof(struct nd_msg, opt));
 	struct nd_msg *msg = (struct nd_msg *)skb_transport_header(skb);
 	const struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
 	const struct in6_addr *daddr = &ipv6_hdr(skb)->daddr;
-	u8 *lladdr = NULL;
-	u32 ndoptlen = skb_tail_pointer(skb) - (skb_transport_header(skb) +
-				    offsetof(struct nd_msg, opt));
-	struct ndisc_options ndopts;
 	struct net_device *dev = skb->dev;
-	struct inet6_ifaddr *ifp;
+	struct net *net = dev_net(dev);
+	int dad = ipv6_addr_any(saddr);
 	struct inet6_dev *idev = NULL;
+	struct ndisc_options ndopts;
+	struct inet6_ifaddr *ifp;
+	struct neigh_table *tbl;
 	struct neighbour *neigh;
-	int dad = ipv6_addr_any(saddr);
 	int is_router = -1;
+	u8 *lladdr = NULL;
 	SKB_DR(reason);
 	u64 nonce = 0;
 	bool inc;
@@ -845,9 +848,10 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)
 	if (ndopts.nd_opts_nonce && ndopts.nd_opts_nonce->nd_opt_len == 1)
 		memcpy(&nonce, (u8 *)(ndopts.nd_opts_nonce + 1), 6);
 
-	inc = ipv6_addr_is_multicast(daddr);
+	tbl = nd_table(net);
 
-	ifp = ipv6_get_ifaddr(dev_net(dev), &msg->target, dev, 1);
+	inc = ipv6_addr_is_multicast(daddr);
+	ifp = ipv6_get_ifaddr(net, &msg->target, dev, 1);
 	if (ifp) {
 have_ifp:
 		if (ifp->flags & (IFA_F_TENTATIVE|IFA_F_OPTIMISTIC)) {
@@ -880,8 +884,6 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)
 
 		idev = ifp->idev;
 	} else {
-		struct net *net = dev_net(dev);
-
 		/* perhaps an address on the master device */
 		if (netif_is_l3_slave(dev)) {
 			struct net_device *mdev;
@@ -918,7 +920,7 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)
 				 */
 				struct sk_buff *n = skb_clone(skb, GFP_ATOMIC);
 				if (n)
-					pneigh_enqueue(&nd_tbl, idev->nd_parms, n);
+					pneigh_enqueue(tbl, idev->nd_parms, n);
 				goto out;
 			}
 		} else {
@@ -937,15 +939,15 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)
 	}
 
 	if (inc)
-		NEIGH_CACHE_STAT_INC(&nd_tbl, rcv_probes_mcast);
+		NEIGH_CACHE_STAT_INC(tbl, rcv_probes_mcast);
 	else
-		NEIGH_CACHE_STAT_INC(&nd_tbl, rcv_probes_ucast);
+		NEIGH_CACHE_STAT_INC(tbl, rcv_probes_ucast);
 
 	/*
 	 *	update / create cache entry
 	 *	for the source address
 	 */
-	neigh = __neigh_lookup(&nd_tbl, saddr, dev,
+	neigh = __neigh_lookup(tbl, saddr, dev,
 			       !inc || lladdr || !dev->addr_len);
 	if (neigh)
 		ndisc_update(dev, neigh, lladdr, NUD_STALE,
@@ -987,17 +989,19 @@ static int accept_untracked_na(struct inet6_dev *idev, struct in6_addr *saddr)
 
 static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
 {
-	struct nd_msg *msg = (struct nd_msg *)skb_transport_header(skb);
-	struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
-	const struct in6_addr *daddr = &ipv6_hdr(skb)->daddr;
-	u8 *lladdr = NULL;
 	u32 ndoptlen = skb_tail_pointer(skb) - (skb_transport_header(skb) +
 				    offsetof(struct nd_msg, opt));
-	struct ndisc_options ndopts;
+	struct nd_msg *msg = (struct nd_msg *)skb_transport_header(skb);
+	const struct in6_addr *daddr = &ipv6_hdr(skb)->daddr;
+	struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
 	struct net_device *dev = skb->dev;
-	struct inet6_dev *idev = __in6_dev_get(dev);
+	struct net *net = dev_net(dev);
+	struct ndisc_options ndopts;
 	struct inet6_ifaddr *ifp;
+	struct neigh_table *tbl;
 	struct neighbour *neigh;
+	struct inet6_dev *idev;
+	u8 *lladdr = NULL;
 	SKB_DR(reason);
 	u8 new_state;
 
@@ -1020,6 +1024,7 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
 	 * and thus should not be accepted.
 	 * drop_unsolicited_na takes precedence over accept_untracked_na
 	 */
+	idev = __in6_dev_get(dev);
 	if (!msg->icmph.icmp6_solicited && idev &&
 	    READ_ONCE(idev->cnf.drop_unsolicited_na))
 		return reason;
@@ -1034,7 +1039,7 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
 			return reason;
 		}
 	}
-	ifp = ipv6_get_ifaddr(dev_net(dev), &msg->target, dev, 1);
+	ifp = ipv6_get_ifaddr(net, &msg->target, dev, 1);
 	if (ifp) {
 		if (skb->pkt_type != PACKET_LOOPBACK
 		    && (ifp->flags & IFA_F_TENTATIVE)) {
@@ -1058,7 +1063,8 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
 		return reason;
 	}
 
-	neigh = neigh_lookup(&nd_tbl, &msg->target, dev);
+	tbl = nd_table(net);
+	neigh = neigh_lookup(tbl, &msg->target, dev);
 
 	/* RFC 9131 updates original Neighbour Discovery RFC 4861.
 	 * NAs with Target LL Address option without a corresponding
@@ -1078,14 +1084,13 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
 	new_state = msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE;
 	if (!neigh && lladdr && idev && READ_ONCE(idev->cnf.forwarding)) {
 		if (accept_untracked_na(idev, saddr)) {
-			neigh = neigh_create(&nd_tbl, &msg->target, dev);
+			neigh = neigh_create(tbl, &msg->target, dev);
 			new_state = NUD_STALE;
 		}
 	}
 
 	if (neigh && !IS_ERR(neigh)) {
 		u8 old_flags = neigh->flags;
-		struct net *net = dev_net(dev);
 
 		if (READ_ONCE(neigh->nud_state) & NUD_FAILED)
 			goto out;
@@ -1098,7 +1103,7 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
 		if (lladdr && !memcmp(lladdr, dev->dev_addr, dev->addr_len) &&
 		    READ_ONCE(net->ipv6.devconf_all->forwarding) &&
 		    READ_ONCE(net->ipv6.devconf_all->proxy_ndp) &&
-		    pneigh_lookup(&nd_tbl, net, &msg->target, dev)) {
+		    pneigh_lookup(tbl, &msg->target, dev)) {
 			/* XXX: idev->cnf.proxy_ndp */
 			goto out;
 		}
@@ -1127,18 +1132,20 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
 static enum skb_drop_reason ndisc_recv_rs(struct sk_buff *skb)
 {
 	struct rs_msg *rs_msg = (struct rs_msg *)skb_transport_header(skb);
+	const struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
 	unsigned long ndoptlen = skb->len - sizeof(*rs_msg);
+	struct net_device *dev = skb->dev;
+	struct ndisc_options ndopts;
+	struct neigh_table *tbl;
 	struct neighbour *neigh;
 	struct inet6_dev *idev;
-	const struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
-	struct ndisc_options ndopts;
 	u8 *lladdr = NULL;
 	SKB_DR(reason);
 
 	if (skb->len < sizeof(*rs_msg))
 		return SKB_DROP_REASON_PKT_TOO_SMALL;
 
-	idev = __in6_dev_get(skb->dev);
+	idev = __in6_dev_get(dev);
 	if (!idev) {
 		net_err_ratelimited("RS: can't find in6 device\n");
 		return reason;
@@ -1156,19 +1163,19 @@ static enum skb_drop_reason ndisc_recv_rs(struct sk_buff *skb)
 		goto out;
 
 	/* Parse ND options */
-	if (!ndisc_parse_options(skb->dev, rs_msg->opt, ndoptlen, &ndopts))
+	if (!ndisc_parse_options(dev, rs_msg->opt, ndoptlen, &ndopts))
 		return SKB_DROP_REASON_IPV6_NDISC_BAD_OPTIONS;
 
 	if (ndopts.nd_opts_src_lladdr) {
-		lladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr,
-					     skb->dev);
+		lladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr, dev);
 		if (!lladdr)
 			goto out;
 	}
 
-	neigh = __neigh_lookup(&nd_tbl, saddr, skb->dev, 1);
+	tbl = nd_table(dev_net(dev));
+	neigh = __neigh_lookup(tbl, saddr, dev, 1);
 	if (neigh) {
-		ndisc_update(skb->dev, neigh, lladdr, NUD_STALE,
+		ndisc_update(dev, neigh, lladdr, NUD_STALE,
 			     NEIGH_UPDATE_F_WEAK_OVERRIDE|
 			     NEIGH_UPDATE_F_OVERRIDE|
 			     NEIGH_UPDATE_F_OVERRIDE_ISROUTER,
@@ -1232,6 +1239,7 @@ static void ndisc_ra_useropt(struct sk_buff *ra, struct nd_opt_hdr *opt)
 static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)
 {
 	struct ra_msg *ra_msg = (struct ra_msg *)skb_transport_header(skb);
+	struct net *net = dev_net(skb->dev);
 	bool send_ifinfo_notify = false;
 	struct neighbour *neigh = NULL;
 	struct ndisc_options ndopts;
@@ -1241,7 +1249,6 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)
 	u32 defrtr_usr_metric;
 	unsigned int pref = 0;
 	__u32 old_if_flags;
-	struct net *net;
 	SKB_DR(reason);
 	int lifetime;
 	int optlen;
@@ -1330,7 +1337,6 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)
 	/* Do not accept RA with source-addr found on local machine unless
 	 * accept_ra_from_local is set to true.
 	 */
-	net = dev_net(in6_dev->dev);
 	if (!READ_ONCE(in6_dev->cnf.accept_ra_from_local) &&
 	    ipv6_chk_addr(net, &ipv6_hdr(skb)->saddr, in6_dev->dev, 0)) {
 		net_dbg_ratelimited("RA from local address detected on dev: %s: default router ignored\n",
@@ -1466,7 +1472,7 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)
 	 */
 
 	if (!neigh)
-		neigh = __neigh_lookup(&nd_tbl, &ipv6_hdr(skb)->saddr,
+		neigh = __neigh_lookup(nd_table(net), &ipv6_hdr(skb)->saddr,
 				       skb->dev, 1);
 	if (neigh) {
 		u8 *lladdr = NULL;
@@ -1859,12 +1865,15 @@ static int ndisc_netdev_event(struct notifier_block *this, unsigned long event,
 	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
 	struct netdev_notifier_change_info *change_info;
 	struct net *net = dev_net(dev);
+	struct neigh_table *tbl;
 	struct inet6_dev *idev;
 	bool evict_nocarrier;
 
+	tbl = nd_table(net);
+
 	switch (event) {
 	case NETDEV_CHANGEADDR:
-		neigh_changeaddr(&nd_tbl, dev);
+		neigh_changeaddr(tbl, dev);
 		fib6_run_gc(0, net, false);
 		fallthrough;
 	case NETDEV_UP:
@@ -1888,12 +1897,12 @@ static int ndisc_netdev_event(struct notifier_block *this, unsigned long event,
 
 		change_info = ptr;
 		if (change_info->flags_changed & IFF_NOARP)
-			neigh_changeaddr(&nd_tbl, dev);
+			neigh_changeaddr(tbl, dev);
 		if (evict_nocarrier && !netif_carrier_ok(dev))
-			neigh_carrier_down(&nd_tbl, dev);
+			neigh_carrier_down(tbl, dev);
 		break;
 	case NETDEV_DOWN:
-		neigh_ifdown(&nd_tbl, dev);
+		neigh_ifdown(tbl, dev);
 		fib6_run_gc(0, net, false);
 		break;
 	case NETDEV_NOTIFY_PEERS:
@@ -1972,12 +1981,23 @@ 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;
+
+#ifdef CONFIG_SYSCTL
+	err = neigh_sysctl_register(NULL, &nd_table(net)->parms,
+				    ndisc_ifinfo_sysctl_change);
+	if (err)
+		goto err_sysctl;
+#endif
+
 	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 +2008,24 @@ static int __net_init ndisc_net_init(struct net *net)
 	inet6_clear_bit(MC6_LOOP, sk);
 
 	return 0;
+
+err_sock_create:
+#ifdef CONFIG_SYSCTL
+	neigh_sysctl_unregister(&nd_table(net)->parms);
+err_sysctl:
+#endif
+	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);
+#ifdef CONFIG_SYSCTL
+	neigh_sysctl_unregister(&nd_table(net)->parms);
+#endif
+	neigh_table_unregister(net, NEIGH_ND_TABLE);
 }
 
 static struct pernet_operations ndisc_net_ops = {
@@ -2002,30 +2035,7 @@ static struct pernet_operations ndisc_net_ops = {
 
 int __init ndisc_init(void)
 {
-	int err;
-
-	err = register_pernet_subsys(&ndisc_net_ops);
-	if (err)
-		return err;
-	/*
-	 * Initialize the neighbour table
-	 */
-	neigh_table_init(NEIGH_ND_TABLE, &nd_tbl);
-
-#ifdef CONFIG_SYSCTL
-	err = neigh_sysctl_register(NULL, &nd_tbl.parms,
-				    ndisc_ifinfo_sysctl_change);
-	if (err)
-		goto out_unregister_pernet;
-out:
-#endif
-	return err;
-
-#ifdef CONFIG_SYSCTL
-out_unregister_pernet:
-	unregister_pernet_subsys(&ndisc_net_ops);
-	goto out;
-#endif
+	return register_pernet_subsys(&ndisc_net_ops);
 }
 
 int __init ndisc_late_init(void)
@@ -2040,9 +2050,5 @@ void ndisc_late_cleanup(void)
 
 void ndisc_cleanup(void)
 {
-#ifdef CONFIG_SYSCTL
-	neigh_sysctl_unregister(&nd_tbl.parms);
-#endif
-	neigh_table_clear(NEIGH_ND_TABLE, &nd_tbl);
 	unregister_pernet_subsys(&ndisc_net_ops);
 }
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 5968ce5ad1508..cea1cdc2b8e8e 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -217,7 +217,7 @@ struct neighbour *ip6_neigh_lookup(const struct in6_addr *gw,
 	if (n)
 		return n;
 
-	n = neigh_create(&nd_tbl, daddr, dev);
+	n = neigh_create(nd_table(dev_net(dev)), daddr, dev);
 	return IS_ERR(n) ? NULL : n;
 }
 
@@ -4229,6 +4229,7 @@ static int ip6_route_del(struct fib6_config *cfg,
 static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_buff *skb)
 {
 	struct netevent_redirect netevent;
+	struct net_device *dev = skb->dev;
 	struct rt6_info *rt, *nrt = NULL;
 	struct fib6_result res = {};
 	struct ndisc_options ndopts;
@@ -4262,7 +4263,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
 		return;
 	}
 
-	in6_dev = __in6_dev_get(skb->dev);
+	in6_dev = __in6_dev_get(dev);
 	if (!in6_dev)
 		return;
 	if (READ_ONCE(in6_dev->cnf.forwarding) ||
@@ -4274,15 +4275,14 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
 	 *	first-hop router for the specified ICMP Destination Address.
 	 */
 
-	if (!ndisc_parse_options(skb->dev, msg->opt, optlen, &ndopts)) {
+	if (!ndisc_parse_options(dev, msg->opt, optlen, &ndopts)) {
 		net_dbg_ratelimited("rt6_redirect: invalid ND options\n");
 		return;
 	}
 
 	lladdr = NULL;
 	if (ndopts.nd_opts_tgt_lladdr) {
-		lladdr = ndisc_opt_addr_data(ndopts.nd_opts_tgt_lladdr,
-					     skb->dev);
+		lladdr = ndisc_opt_addr_data(ndopts.nd_opts_tgt_lladdr, dev);
 		if (!lladdr) {
 			net_dbg_ratelimited("rt6_redirect: invalid link-layer address length\n");
 			return;
@@ -4301,7 +4301,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
 	 */
 	dst_confirm_neigh(&rt->dst, &ipv6_hdr(skb)->saddr);
 
-	neigh = __neigh_lookup(&nd_tbl, &msg->target, skb->dev, 1);
+	neigh = __neigh_lookup(nd_table(dev_net(dev)), &msg->target, dev, 1);
 	if (!neigh)
 		return;
 
@@ -4309,7 +4309,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
 	 *	We have finally decided to accept it.
 	 */
 
-	ndisc_update(skb->dev, neigh, lladdr, NUD_STALE,
+	ndisc_update(dev, neigh, lladdr, NUD_STALE,
 		     NEIGH_UPDATE_F_WEAK_OVERRIDE|
 		     NEIGH_UPDATE_F_OVERRIDE|
 		     (on_link ? 0 : (NEIGH_UPDATE_F_OVERRIDE_ISROUTER|
@@ -5030,9 +5030,11 @@ void rt6_sync_down_dev(struct net_device *dev, unsigned long event)
 
 void rt6_disable_ip(struct net_device *dev, unsigned long event)
 {
+	struct net *net = dev_net(dev);
+
 	rt6_sync_down_dev(dev, event);
 	rt6_uncached_list_flush_dev(dev);
-	neigh_ifdown(&nd_tbl, dev);
+	neigh_ifdown(nd_table(net), dev);
 }
 
 struct rt6_mtu_change_arg {
diff --git a/tools/testing/selftests/net/test_neigh.sh b/tools/testing/selftests/net/test_neigh.sh
index 7c594bf6ead0d..e70e31edf6337 100755
--- a/tools/testing/selftests/net/test_neigh.sh
+++ b/tools/testing/selftests/net/test_neigh.sh
@@ -63,6 +63,15 @@ exit_cleanup_all()
 	exit "${EXIT_STATUS}"
 }
 
+get_periodic_gc_runs()
+{
+	local ns=$1
+	local tbl_name=$2
+
+	ip -n "$ns" -j -s ntable show name "$tbl_name" | \
+		jq '.[] | select(has("periodic_gc_runs")) | .["periodic_gc_runs"]'
+}
+
 ################################################################################
 # Tests
 
@@ -231,9 +240,6 @@ extern_valid_common()
 	# Check that an "extern_valid" entry survives a forced garbage
 	# collection. Add an entry, wait 5 seconds and add more entries than
 	# "thresh3" so that forced garbage collection will run.
-	#
-	# Note that the garbage collection thresholds are global resources and
-	# that changes in the initial namespace affect all the namespaces.
 	local forced_gc_runs_t0
 	local forced_gc_runs_t1
 	local orig_thresh1
@@ -241,18 +247,23 @@ extern_valid_common()
 	local orig_thresh3
 
 	run_cmd "ip -n $ns1 neigh flush dev veth0"
-	orig_thresh1=$(ip -j ntable show name "$tbl_name" | jq '.[] | select(has("thresh1")) | .["thresh1"]')
-	orig_thresh2=$(ip -j ntable show name "$tbl_name" | jq '.[] | select(has("thresh2")) | .["thresh2"]')
-	orig_thresh3=$(ip -j ntable show name "$tbl_name" | jq '.[] | select(has("thresh3")) | .["thresh3"]')
-	run_cmd "ip ntable change name $tbl_name thresh3 10 thresh2 9 thresh1 8"
+	orig_thresh1=$(ip -n "$ns1" -j ntable show name "$tbl_name" | \
+			jq '.[] | select(has("thresh1")) | .["thresh1"]')
+	orig_thresh2=$(ip -n "$ns1" -j ntable show name "$tbl_name" | \
+			jq '.[] | select(has("thresh2")) | .["thresh2"]')
+	orig_thresh3=$(ip -n "$ns1" -j ntable show name "$tbl_name" | \
+			jq '.[] | select(has("thresh3")) | .["thresh3"]')
+	run_cmd "ip -n $ns1 ntable change name $tbl_name thresh3 10 thresh2 9 thresh1 8"
 	run_cmd "ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid"
 	run_cmd "ip -n $ns1 neigh add ${subnet}3 lladdr $mac nud stale dev veth0"
 	run_cmd "sleep 5"
-	forced_gc_runs_t0=$(ip -j -s ntable show name "$tbl_name" | jq '.[] | select(has("forced_gc_runs")) | .["forced_gc_runs"]')
+	forced_gc_runs_t0=$(ip -n "$ns1" -j -s ntable show name "$tbl_name" | \
+				jq '.[] | select(has("forced_gc_runs")) | .["forced_gc_runs"]')
 	for i in {1..20}; do
 		run_cmd "ip -n $ns1 neigh add ${subnet}$((i + 4)) nud none dev veth0"
 	done
-	forced_gc_runs_t1=$(ip -j -s ntable show name "$tbl_name" | jq '.[] | select(has("forced_gc_runs")) | .["forced_gc_runs"]')
+	forced_gc_runs_t1=$(ip -n "$ns1" -j -s ntable show name "$tbl_name" | \
+				jq '.[] | select(has("forced_gc_runs")) | .["forced_gc_runs"]')
 	if [[ $forced_gc_runs_t1 -eq $forced_gc_runs_t0 ]]; then
 		check_err 1 "Forced garbage collection did not run"
 	fi
@@ -263,7 +274,8 @@ extern_valid_common()
 
 	log_test "$af_str \"extern_valid\" flag: Forced garbage collection"
 
-	run_cmd "ip ntable change name $tbl_name thresh3 $orig_thresh3 thresh2 $orig_thresh2 thresh1 $orig_thresh1"
+	run_cmd "ip -n $ns1 ntable change name $tbl_name \
+		thresh3 $orig_thresh3 thresh2 $orig_thresh2 thresh1 $orig_thresh1"
 
 	RET=0
 
@@ -275,31 +287,36 @@ extern_valid_common()
 	# collection. Add an "extern_valid" entry, add more than "thresh1"
 	# regular entries, wait "base_reachable" (longer than "gc_stale")
 	# seconds and check that the "extern_valid" entry was not deleted.
-	#
-	# Note that the garbage collection thresholds and "base_reachable" are
-	# global resources and that changes in the initial namespace affect all
-	# the namespaces.
 	local periodic_gc_runs_t0
 	local periodic_gc_runs_t1
 	local orig_base_reachable
+	local base_reachable=10000
 	local orig_gc_stale
+	local timeout
 
 	run_cmd "ip -n $ns1 neigh flush dev veth0"
-	orig_thresh1=$(ip -j ntable show name "$tbl_name" | jq '.[] | select(has("thresh1")) | .["thresh1"]')
-	orig_base_reachable=$(ip -j ntable show name "$tbl_name" | jq '.[] | select(has("thresh1")) | .["base_reachable"]')
-	run_cmd "ip ntable change name $tbl_name thresh1 10 base_reachable 10000"
-	orig_gc_stale=$(ip -n "$ns1" -j ntable show name "$tbl_name" dev veth0 | jq '.[]["gc_stale"]')
+	orig_thresh1=$(ip -n "$ns1" -j ntable show name "$tbl_name" | \
+			jq '.[] | select(has("thresh1")) | .["thresh1"]')
+	orig_base_reachable=$(ip -n "$ns1" -j ntable show name "$tbl_name" | \
+				jq '.[] | select(has("thresh1")) | .["base_reachable"]')
+	run_cmd "ip -n $ns1 ntable change name $tbl_name thresh1 10 base_reachable $base_reachable"
+	orig_gc_stale=$(ip -n "$ns1" -j ntable show name "$tbl_name" dev veth0 | \
+			jq '.[]["gc_stale"]')
 	run_cmd "ip -n $ns1 ntable change name $tbl_name dev veth0 gc_stale 1000"
 	run_cmd "ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid"
 	run_cmd "ip -n $ns1 neigh add ${subnet}3 lladdr $mac nud stale dev veth0"
-	# Wait orig_base_reachable/2 for the new interval to take effect.
-	run_cmd "sleep $(((orig_base_reachable / 1000) / 2 + 2))"
+	# Wait up to neigh_set_reach_time(BASE_REACHABLE_TIME) (~45s) + 5s
+	# for the new interval to take effect, see neigh_table_init().
+	timeout=$(((orig_base_reachable / 1000) * 3 / 2 + 5))
+	slowwait_for_counter "$timeout" 1 get_periodic_gc_runs "$ns1" "$tbl_name" >/dev/null
 	for i in {1..20}; do
 		run_cmd "ip -n $ns1 neigh add ${subnet}$((i + 4)) nud none dev veth0"
 	done
-	periodic_gc_runs_t0=$(ip -j -s ntable show name "$tbl_name" | jq '.[] | select(has("periodic_gc_runs")) | .["periodic_gc_runs"]')
-	run_cmd "sleep 10"
-	periodic_gc_runs_t1=$(ip -j -s ntable show name "$tbl_name" | jq '.[] | select(has("periodic_gc_runs")) | .["periodic_gc_runs"]')
+	periodic_gc_runs_t0=$(get_periodic_gc_runs "$ns1" "$tbl_name")
+	# Wait up to BASE_REACHABLE_TIME / 2 + 5s, see neigh_periodic_work().
+	timeout=$(((base_reachable / 1000) / 2 + 5))
+	slowwait_for_counter "$timeout" 1 get_periodic_gc_runs "$ns1" "$tbl_name" >/dev/null
+	periodic_gc_runs_t1=$(get_periodic_gc_runs "$ns1" "$tbl_name")
 	[[ $periodic_gc_runs_t1 -ne $periodic_gc_runs_t0 ]]
 	check_err $? "Periodic garbage collection did not run"
 	run_cmd "ip -n $ns1 neigh get $ip_addr dev veth0 | grep \"extern_valid\""
@@ -310,7 +327,8 @@ extern_valid_common()
 	log_test "$af_str \"extern_valid\" flag: Periodic garbage collection"
 
 	run_cmd "ip -n $ns1 ntable change name $tbl_name dev veth0 gc_stale $orig_gc_stale"
-	run_cmd "ip ntable change name $tbl_name thresh1 $orig_thresh1 base_reachable $orig_base_reachable"
+	run_cmd "ip -n $ns1 ntable change name $tbl_name \
+		thresh1 $orig_thresh1 base_reachable $orig_base_reachable"
 }
 
 extern_valid_ipv4()

3/2 2026/08/13 08:33 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 44855
							output: 58
							thoughts: 1746
						

Thoughts:
**Fuzzing Target Identification: Neighbor Table Namespaces**

Okay, let's break this down systematically. My initial assessment is that this patch introduces functional changes significant enough to warrant fuzzing. The key indicator is the modification to how neighbor tables are handled, specifically the transition from a global to a per-network namespace model.

The code touches a wide swathe of the kernel networking stack, including the admin guide and core drivers. The fact that it modifies `Documentation/admin-guide/sysctl/net.rst` suggests changes that affect user configuration and possibly behavior that can be controlled from user space.

The core of the change appears centered around `arp_tbl` and `nd_tbl`. These are now accessed via `arp_table(net)` and `nd_table(net)`, which immediately signifies a fundamental shift in how the kernel manages ARP and IPv6 neighbor discovery tables. This is a core networking change. This affects core parts of the network stack: ARP and IPv6 neighbor discovery, and therefore demands close attention.

Looking at `net/core/neighbour.c`, `neigh_table_init` now takes `struct net *net` and `struct neigh_table *tbl`. This is a clear indicator of the namespace change. The addition of `neigh_table_register`, `neigh_table_unregister`, `neigh_table_clear`, and `neigh_table_inherit` are all strong functional changes. This means we must consider these for fuzzing.

My list of focus symbols will be centered around the registration and unregistration of the neighbor tables, and their interaction with ARP and NDP:
*   `neigh_table_register`
*   `neigh_table_unregister`
*   `neigh_table_init`
*   `arp_table`
*   `nd_table`
*   `neigh_table_inherit`
*   `neigh_table_clear`
*   `neigh_table_free`
*   `neigh_table_entries`
*   `neigh_table_get`
*   `neigh_table_put`

Checking `neigh_table_register`, it's not directly exported, but is called within `arp_net_init` and `ndisc_net_init`. This is a clue, these are pernet operations meaning network namespace creation and destruction will trigger the register and unregister functions. These become primary targets.

So, while `arp_net_init` and `ndisc_net_init` look like potential focus symbols, they're static. However, `neigh_table_register` and `neigh_table_unregister`, even though not exported, are global functions. We can use them. Looking at the code in `net/core/neighbour.c`, there are many functions that were either changed, or added to handle the network namespaces:

*   `neigh_table_register`
*   `neigh_table_unregister`
*   `pneigh_lookup`
*   `pneigh_create`
*   `pneigh_delete`
*   `neigh_parms_alloc`
*   `neigh_parms_release`
*   `neigh_xmit`
*   `neigh_dump_info`
*   `neightbl_set`
*   `neigh_add`
*   `neigh_delete`
*   `neigh_get`

Looks like the functions `neightbl_set`, `neigh_add`, `neigh_delete`, and `neigh_get` are static and will be harder to target directly. `neigh_table_register`, and `neigh_table_unregister` are excellent. The others are worth considering, but we will have to test how they are called.



4/2 2026/08/13 08:33 tool grepper
0m Args:
{
  "Expression": "^(int|void|struct [a-zA-Z0-9_]+ \\*) [a-zA-Z0-9_]+\\(",
  "PathPrefix": "net/core/neighbour.c"
}

Results:
{
  "Output": "net/core/neighbour.c=436=static void neigh_flush_table(struct neigh_table *tbl)\n--\nnet/core/neighbour.c-452-\nnet/core/neighbour.c:453:void neigh_changeaddr(struct neigh_table *tbl, struct net_device *dev)\nnet/core/neighbour.c-454-{\n--\nnet/core/neighbour.c=460=static int __neigh_ifdown(struct neigh_table *tbl, struct net_device *dev,\n--\nnet/core/neighbour.c-478-\nnet/core/neighbour.c:479:int neigh_carrier_down(struct neigh_table *tbl, struct net_device *dev)\nnet/core/neighbour.c-480-{\n--\nnet/core/neighbour.c-484-\nnet/core/neighbour.c:485:int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev)\nnet/core/neighbour.c-486-{\n--\nnet/core/neighbour.c=752=struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl,\n--\nnet/core/neighbour.c-774-\nnet/core/neighbour.c:775:int pneigh_create(struct neigh_table *tbl,\nnet/core/neighbour.c-776-\t\t  const void *pkey, struct net_device *dev,\n--\nnet/core/neighbour.c=821=static void pneigh_destroy(struct rcu_head *rcu)\n--\nnet/core/neighbour.c-828-\nnet/core/neighbour.c:829:int pneigh_delete(struct neigh_table *tbl, const void *pkey,\nnet/core/neighbour.c-830-\t\t  struct net_device *dev)\n--\nnet/core/neighbour.c=898=static inline void neigh_parms_put(struct neigh_parms *parms)\n--\nnet/core/neighbour.c-907- */\nnet/core/neighbour.c:908:void neigh_destroy(struct neighbour *neigh)\nnet/core/neighbour.c-909-{\n--\nnet/core/neighbour.c=1094=static void neigh_timer_handler(struct timer_list *t)\n--\nnet/core/neighbour.c-1195-\nnet/core/neighbour.c:1196:int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb,\nnet/core/neighbour.c-1197-\t\t       const bool immediate_ok)\n--\nnet/core/neighbour.c=1365=static int __neigh_update(struct neighbour *neigh, const u8 *lladdr,\n--\nnet/core/neighbour.c-1544-\nnet/core/neighbour.c:1545:int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,\nnet/core/neighbour.c-1546-\t\t u32 flags, u32 nlmsg_pid)\n--\nnet/core/neighbour.c-1553- */\nnet/core/neighbour.c:1554:void __neigh_set_probe_once(struct neighbour *neigh)\nnet/core/neighbour.c-1555-{\n--\nnet/core/neighbour.c=1581=static void neigh_hh_init(struct neighbour *n)\n--\nnet/core/neighbour.c-1599-\nnet/core/neighbour.c:1600:int neigh_resolve_output(struct neighbour *neigh, struct sk_buff *skb)\nnet/core/neighbour.c-1601-{\n--\nnet/core/neighbour.c-1633-\nnet/core/neighbour.c:1634:int neigh_connected_output(struct neighbour *neigh, struct sk_buff *skb)\nnet/core/neighbour.c-1635-{\n--\nnet/core/neighbour.c-1655-\nnet/core/neighbour.c:1656:int neigh_direct_output(struct neighbour *neigh, struct sk_buff *skb)\nnet/core/neighbour.c-1657-{\n--\nnet/core/neighbour.c=1711=static unsigned long neigh_proxy_delay(struct neigh_parms *p)\n--\nnet/core/neighbour.c-1721-\nnet/core/neighbour.c:1722:void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,\nnet/core/neighbour.c-1723-\t\t    struct sk_buff *skb)\n--\nnet/core/neighbour.c=1795=static void neigh_rcu_free_parms(struct rcu_head *head)\n--\nnet/core/neighbour.c-1802-\nnet/core/neighbour.c:1803:void neigh_parms_release(struct neigh_table *tbl, struct neigh_parms *parms)\nnet/core/neighbour.c-1804-{\n--\nnet/core/neighbour.c=1916=static void neigh_table_inherit(struct net *net, struct neigh_table *tbl,\n--\nnet/core/neighbour.c-1934-\nnet/core/neighbour.c:1935:int neigh_table_register(struct net *net, struct neigh_table *tbl, int index)\nnet/core/neighbour.c-1936-{\n--\nnet/core/neighbour.c-1960-\nnet/core/neighbour.c:1961:void neigh_table_unregister(struct net *net, int index)\nnet/core/neighbour.c-1962-{\n--\nnet/core/neighbour.c=3098=static int neigh_get(struct sk_buff *in_skb, struct nlmsghdr *nlh,\n--\nnet/core/neighbour.c-3185-\nnet/core/neighbour.c:3186:void neigh_for_each(struct neigh_table *tbl, void (*cb)(struct neighbour *, void *), void *cookie)\nnet/core/neighbour.c-3187-{\n--\nnet/core/neighbour.c=3204=EXPORT_SYMBOL(neigh_for_each);\nnet/core/neighbour.c-3205-\nnet/core/neighbour.c:3206:int neigh_xmit(int index, struct net_device *dev,\nnet/core/neighbour.c-3207-\t       const void *addr, struct sk_buff *skb)\n--\nnet/core/neighbour.c=3433=void *neigh_seq_next(struct seq_file *seq, void *v, loff_t *pos)\n--\nnet/core/neighbour.c-3458-\nnet/core/neighbour.c:3459:void neigh_seq_stop(struct seq_file *seq, void *v)\nnet/core/neighbour.c-3460-\t__releases(tbl-\u003elock)\n--\nnet/core/neighbour.c=3581=static void neigh_notify(struct neighbour *neigh, int type, int flags, u32 pid)\n--\nnet/core/neighbour.c-3587-\nnet/core/neighbour.c:3588:void neigh_app_ns(struct neighbour *n)\nnet/core/neighbour.c-3589-{\n--\nnet/core/neighbour.c=3663=static int neigh_proc_dointvec_ms_jiffies_positive(const struct ctl_table *ctl, int write,\n--\nnet/core/neighbour.c-3678-\nnet/core/neighbour.c:3679:int neigh_proc_dointvec(const struct ctl_table *ctl, int write, void *buffer,\nnet/core/neighbour.c-3680-\t\t\tsize_t *lenp, loff_t *ppos)\n--\nnet/core/neighbour.c-3687-\nnet/core/neighbour.c:3688:int neigh_proc_dointvec_jiffies(const struct ctl_table *ctl, int write, void *buffer,\nnet/core/neighbour.c-3689-\t\t\t\tsize_t *lenp, loff_t *ppos)\n--\nnet/core/neighbour.c=3697=static int neigh_proc_dointvec_userhz_jiffies(const struct ctl_table *ctl, int write,\n--\nnet/core/neighbour.c-3706-\nnet/core/neighbour.c:3707:int neigh_proc_dointvec_ms_jiffies(const struct ctl_table *ctl, int write,\nnet/core/neighbour.c-3708-\t\t\t\t   void *buffer, size_t *lenp, loff_t *ppos)\n--\nnet/core/neighbour.c=3780=static struct neigh_sysctl_table {\n--\nnet/core/neighbour.c-3835-\nnet/core/neighbour.c:3836:int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p,\nnet/core/neighbour.c-3837-\t\t\t  proc_handler *handler)\n--\nnet/core/neighbour.c-3921-\nnet/core/neighbour.c:3922:void neigh_sysctl_unregister(struct neigh_parms *p)\nnet/core/neighbour.c-3923-{\n"
}

5/2 2026/08/13 08:33 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 49809
							output: 36
							thoughts: 186
						

Thoughts:
**Investigating the Kernel's Neighbor Table Registration**

Okay, so I'm looking into how the network neighbor table is handled, and I want to understand the relationship between registering and unregistering entries, specifically within the kernel. My first instinct is to check `net/core/neighbour.c`, focusing on the functions `neigh_table_register` and `neigh_table_unregister`. Are these functions exposed as symbols? Nope, they're not explicitly exported with `EXPORT_SYMBOL`, which is a bit of a wrinkle. I know `arp.c` and `ndisc.c` in particular use these, so I need to understand the implications of this apparent lack of export.

Now, I need to understand the build configuration: Are `arp.c` and `ndisc.c` built-in, or are they modules? A quick look at the makefiles shows that `arp.c` is built-in (`obj-y += arp.o`). `ndisc.c`, on the other hand, is part of the IPv6 stack, and that can potentially be a module (`obj-$(CONFIG_IPV6) += ipv6.o`). If `ndisc.c` is a module, this raises a question of how it can use non-exported symbols.

Wait a moment... If `neigh_table_register` isn't exported, that would seem to preclude `ipv6` from being a module if it's dependent on that function. That's a critical point to clarify. So, I need to go back and double-check, because my initial assessment could be wrong, the situation could be more complex, or the kernel may be dealing with this in another way. Let's confirm again whether `neigh_table_register` is *truly* not exported.



6/2 2026/08/13 08:33 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "EXPORT_SYMBOL.*neigh_table_register",
  "PathPrefix": "net/core/neighbour.c"
}

Results:
{
  "error": "no matches"
}

7/2 2026/08/13 08:33 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 50425
							output: 36
							thoughts: 25
						

Thoughts:
Here's a summary of my immediate thought process:

**Initial Investigation into `neigh_table_init` Export**

Okay, hold on a second... was `neigh_table_init` actually exported? That's not what I remembered, but memory can be tricky, especially with these low-level routines. I need to quickly verify that. The first thing I'm going to do is double-check the export status of the `neigh_table_init` function. I'll need to confirm if it's visible outside its intended scope, as I recall there were some access control mechanisms in place. The implications of its export are significant, potentially affecting security or intended functionality if it's being used improperly. This requires a rapid, targeted investigation.



8/2 2026/08/13 08:33 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "EXPORT_SYMBOL.*neigh_table_init",
  "PathPrefix": "net/core/neighbour.c"
}

Results:
{
  "error": "no matches"
}

9/2 2026/08/13 08:33 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 50659
							output: 35
							thoughts: 70
						

Thoughts:
**Examining the Dependencies of `neighbour.c`**

Okay, so I'm looking at `neighbour.c` and immediately thinking, "Right, this is part of the `core` components, meaning it's likely built-in." That's good to know, at least initially. Now, the next question is about `ipv6`. Is `ipv6` also built-in, or is it a module? It makes a big difference in how I need to approach this. If `ipv6` is modular, then it *has* to be relying on exported symbols. So, I need to check something quickly. I need to see if `neigh_table_register` - the function in question, which is located inside `net/core/neighbour.c` - is actually exported for external use. That will give me the clearest indication of how this all fits together.



10/2 2026/08/13 08:33 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "EXPORT_SYMBOL.*neigh_table_",
  "PathPrefix": "net/core/neighbour.c"
}

Results:
{
  "error": "no matches"
}

11/2 2026/08/13 08:33 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 50964
							output: 30
							thoughts: 43
						

Thoughts:
Okay, here's my interpretation of those thoughts, summarized from my perspective as someone familiar with network internals:

**Investigating IPv6 Namespace Changes**

Alright, so I'm troubleshooting something, and I'm wondering if the way we access `ipv6` has shifted. Maybe the kernel's module structure has changed, and it's no longer a standalone module, or perhaps `neighbour.c` is now responsible for exporting that functionality, or at least a portion of it. My first instinct, given what I'm looking at, is to check the `net/core/neighbour.c` file itself to see if the interface for this functionality has changed and if there is a new spot. I'll start by checking the end of the file; that seems like a logical place for potential export symbols or initialization related to `ipv6`, considering how kernel code often works.



12/2 2026/08/13 08:33 tool grepper
0m Args:
{
  "Expression": "EXPORT_SYMBOL",
  "PathPrefix": "net/core/neighbour.c"
}

Results:
{
  "Output": "net/core/neighbour.c=620=struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,\n--\nnet/core/neighbour.c-637-}\nnet/core/neighbour.c:638:EXPORT_SYMBOL(neigh_lookup);\nnet/core/neighbour.c-639-\n--\nnet/core/neighbour.c=733=struct neighbour *__neigh_create(struct neigh_table *tbl, const void *pkey,\n--\nnet/core/neighbour.c-739-}\nnet/core/neighbour.c:740:EXPORT_SYMBOL(__neigh_create);\nnet/core/neighbour.c-741-\n--\nnet/core/neighbour.c=908=void neigh_destroy(struct neighbour *neigh)\n--\nnet/core/neighbour.c-938-}\nnet/core/neighbour.c:939:EXPORT_SYMBOL(neigh_destroy);\nnet/core/neighbour.c-940-\n--\nnet/core/neighbour.c=1196=int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb,\n--\nnet/core/neighbour.c-1281-}\nnet/core/neighbour.c:1282:EXPORT_SYMBOL(__neigh_event_send);\nnet/core/neighbour.c-1283-\n--\nnet/core/neighbour.c=3186=void neigh_for_each(struct neigh_table *tbl, void (*cb)(struct neighbour *, void *), void *cookie)\n--\nnet/core/neighbour.c-3203-}\nnet/core/neighbour.c:3204:EXPORT_SYMBOL(neigh_for_each);\nnet/core/neighbour.c-3205-\nnet/core/neighbour.c=3206=int neigh_xmit(int index, struct net_device *dev,\n--\nnet/core/neighbour.c-3251-}\nnet/core/neighbour.c:3252:EXPORT_SYMBOL(neigh_xmit);\nnet/core/neighbour.c-3253-\n"
}

13/2 2026/08/13 08:33 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 51744
							output: 32
							thoughts: 34
						

Thoughts:
Here's my attempt at summarizing the thought process, as if I were having them:

**Debugging a Missing Symbol**

Okay, hold on a second. I'm getting a linker error, and it looks like `neigh_table_register` is the culprit. That function *should* be available, but the build process is clearly saying it isn't exported. Before I go too far down a rabbit hole, let's just confirm it's even *declared* somewhere. I'll quickly check `include/net/neighbour.h`. Maybe there's a typo in my code, or perhaps it's defined conditionally. Gotta make sure it's actually supposed to be there at all before I start looking at makefiles or linker flags. This should be a quick sanity check before digging deeper.



14/2 2026/08/13 08:33 tool grepper
0m Args:
{
  "Expression": "neigh_table_register",
  "PathPrefix": "include/net/neighbour.h"
}

Results:
{
  "Output": "include/net/neighbour.h=332=static inline void neigh_confirm(struct neighbour *n)\n--\ninclude/net/neighbour.h-342-\ninclude/net/neighbour.h:343:int neigh_table_register(struct net *net, struct neigh_table *tbl, int index);\ninclude/net/neighbour.h-344-void neigh_table_unregister(struct net *net, int index);\n"
}

15/2 2026/08/13 08:33 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 52094
							output: 34
							thoughts: 107
						

Thoughts:
**Initial Investigation of New Function: `neigh_table_register`**

Alright, so `neigh_table_register` is definitely a new function I haven't come across before. It's time to dig in and see where it fits into the broader picture. My initial thought is to check `net/ipv4/arp.c` and `net/ipv6/ndisc.c`. These seem like logical places to start, given the nature of neighbor discovery and address resolution protocols. I'm hoping to find some related initialization or usage examples there. Okay, so I've found `arp_net_init` and `ndisc_net_init` and they're marked as static...hmm, that's not as helpful as I'd hoped. Let me take a second look, wait... *aha*! `arp_init` and `ndisc_init` are not static. Okay, let's explore those now. That seems like the right path to follow to understand how this new function ties in.



16/2 2026/08/13 08:33 tool grepper
0m Args:
{
  "Expression": "void __init arp_init",
  "PathPrefix": "net/ipv4/arp.c"
}

Results:
{
  "Output": "net/ipv4/arp.c=1557=static struct pernet_operations arp_net_ops = {\n--\nnet/ipv4/arp.c-1561-\nnet/ipv4/arp.c:1562:void __init arp_init(void)\nnet/ipv4/arp.c-1563-{\n"
}

17/2 2026/08/13 08:33 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 52549
							output: 37
							thoughts: 243
						

Thoughts:
**Analyzing ARP and Neighbor Discovery Initialization**

Okay, so `arp_init` is essentially the initialization routine, called only during boot. But the real action for per-network namespace initialization happens with `arp_net_init` and `ndisc_net_init`. The thing is, since these are static, they won't make the best focus symbols, or at least, if precision is important, they won't. I'd rather focus on the globally accessible parts. Ah! `neigh_table_register` and `neigh_table_unregister` are global, and those *are* relevant. And, yeah, `neigh_table_register` certainly qualifies as a focus symbol.

Let's dig a bit deeper; are there any other global functions that might be modified or are relevant to the larger picture? Let's see... `pneigh_lookup`, `pneigh_create`, `pneigh_delete`, `neigh_parms_alloc`, `neigh_parms_release`, `neigh_xmit`, `neigh_dump_info`... Ok, those seem promising. Then we've got a bunch of static ones, `neightbl_set`, `neigh_add`, `neigh_delete`, `neigh_get` ... those are important, but aren't globally accessible.

I should probably pull up `net/core/neighbour.c` and take a look at the implementation of `pneigh_lookup`. That seems like a good starting point.



18/2 2026/08/13 08:33 tool grepper
0m Args:
{
  "Expression": "struct pneigh_entry \\*pneigh_lookup",
  "PathPrefix": "net/core/neighbour.c"
}

Results:
{
  "Output": "net/core/neighbour.c=742=static u32 pneigh_hash(const void *pkey, unsigned int key_len)\n--\nnet/core/neighbour.c-751-\nnet/core/neighbour.c:752:struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl,\nnet/core/neighbour.c-753-\t\t\t\t   const void *pkey, struct net_device *dev)\n"
}

19/2 2026/08/13 08:33 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 53261
							output: 30
							thoughts: 131
						

Thoughts:
**Analyzing the Scope of Neighbor Table Access Changes**

Okay, so I see this patch is making some pretty fundamental changes to how we interact with the neighbor tables. My initial concern, as always, is impact. Specifically, it's shifting from these globally accessible functions and data structures like `pneigh_lookup`, `pneigh_create`, `pneigh_delete`, `neigh_parms_alloc`, `neigh_parms_release`, and `neigh_xmit` to network namespace-specific ones: `arp_table(net)` and `nd_table(net)`. That screams "widespread changes" to me.

I need to quickly assess the fallout. Where will this need to be addressed? Obviously, the use of `arp_tbl` and `nd_tbl` is going to have to be updated. It's time to check what has changed. Let's start with `include/net/arp.h`. That seems like the logical place to start looking for definitions and usage of that `arp_table` function to get a grasp of its new architecture.



20/2 2026/08/13 08:34 tool grepper
0m Args:
{
  "Expression": "arp_table",
  "PathPrefix": "include/net/arp.h"
}

Results:
{
  "Output": "include/net/arp.h-9-\ninclude/net/arp.h:10:static inline struct neigh_table *arp_table(struct net *net)\ninclude/net/arp.h-11-{\n--\ninclude/net/arp.h=24=static inline struct neighbour *__ipv4_neigh_lookup_noref(struct net_device *dev, u32 key)\ninclude/net/arp.h-25-{\ninclude/net/arp.h:26:\tstruct neigh_table *tbl = arp_table(dev_net(dev));\ninclude/net/arp.h-27-\n"
}

21/2 2026/08/13 08:34 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 53809
							output: 31
							thoughts: 20
						

Thoughts:
Here's a summary of my thought process, as requested:

**Navigating the Network Neighbor Discovery Landscape**

Okay, so I'm thinking, where would the definition for this `nd_table` structure likely reside? Given its name, and its relation to network discovery, my first instinct is to look within the network header files. Specifically, I'd want to check the core network headers, rather than any protocol-specific ones. It's a fundamental concept, so it probably exists in a central location. `include/net/` seems like the correct starting point, and I'm going to quickly check `ndisc.h`. It's a good bet for neighbor discovery related structures and definitions. Let's start there.



22/2 2026/08/13 08:34 tool grepper
0m Args:
{
  "Expression": "nd_table",
  "PathPrefix": "include/net/ndisc.h"
}

Results:
{
  "Output": "include/net/ndisc.h=68=extern struct neigh_table nd_tbl;\ninclude/net/ndisc.h-69-\ninclude/net/ndisc.h:70:static inline struct neigh_table *nd_table(struct net *net)\ninclude/net/ndisc.h-71-{\n--\ninclude/net/ndisc.h=364=static inline struct neighbour *__ipv6_neigh_lookup_noref(struct net_device *dev, const void *pkey)\ninclude/net/ndisc.h-365-{\ninclude/net/ndisc.h:366:\tstruct neigh_table *tbl = nd_table(dev_net(dev));\ninclude/net/ndisc.h-367-\n--\ninclude/net/ndisc.h=395=static inline struct neighbour *ip_neigh_gw6(struct net_device *dev,\n--\ninclude/net/ndisc.h-402-\tif (unlikely(!neigh)) {\ninclude/net/ndisc.h:403:\t\tstruct neigh_table *tbl = nd_table(dev_net(dev));\ninclude/net/ndisc.h-404-\n"
}

23/2 2026/08/13 08:34 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 54314
							output: 29
							thoughts: 171
						

Thoughts:
**Fuzzing This Core Networking Change**

Okay, so this is a significant change: neighbor tables are now per-network namespace. That immediately screams "fuzzing target" to me. The potential for unexpected interactions and corner cases is high, especially with the increased complexity of managing these tables across namespaces.

My initial focus needs to be on these key symbols in `net/core/neighbour.c`: `neigh_table_register`, `neigh_table_unregister`, `pneigh_lookup`, `pneigh_create`, `pneigh_delete`, `neigh_parms_alloc`, `neigh_parms_release`, and of course `neigh_xmit`. These are the functions directly involved in table management and neighbor operations, and where fuzzing will have the most impact. I’ll need to target them with inputs that exploit any potential vulnerabilities when used in conjunction with different netns configurations.

First, I need to verify any prerequisites. Let's see... the code is in `net/core/neighbour.c`, so it's always compiled as long as `CONFIG_NET` is defined – that's expected. Ah, here's the critical one: `CONFIG_NET_NS` is essential for testing the per-netns behavior. I'll need to make sure that config is enabled in my test environment to thoroughly exercise this new functionality. Let me confirm that `CONFIG_NET_NS` is indeed active. That’s crucial for covering the targeted feature.



24/2 2026/08/13 08:34 tool grepper
0m Args:
{
  "Expression": "CONFIG_NET_NS",
  "PathPrefix": "net/core/"
}

Results:
{
  "Output": "net/core/dev.c=2177=static void rtnl_net_dev_lock(struct net_device *dev)\n--\nnet/core/dev.c-2193-\nnet/core/dev.c:2194:#ifdef CONFIG_NET_NS\nnet/core/dev.c-2195-\t\t/* dev might have been moved to another netns. */\n--\nnet/core/dev.c=10860=static bool from_cleanup_net(void)\nnet/core/dev.c-10861-{\nnet/core/dev.c:10862:#ifdef CONFIG_NET_NS\nnet/core/dev.c-10863-\treturn current == READ_ONCE(cleanup_net_task);\n--\nnet/core/net-sysfs.c=2167=static struct ns_common *net_grab_current_ns(void)\n--\nnet/core/net-sysfs.c-2169-\tstruct net *net = current-\u003ensproxy-\u003enet_ns;\nnet/core/net-sysfs.c:2170:#ifdef CONFIG_NET_NS\nnet/core/net-sysfs.c-2171-\tif (net)\n--\nnet/core/net_namespace.c=440=static __net_init int setup_net(struct net *net)\n--\nnet/core/net_namespace.c-470-\nnet/core/net_namespace.c:471:#ifdef CONFIG_NET_NS\nnet/core/net_namespace.c-472-static struct ucounts *inc_net_namespaces(struct user_namespace *ns)\n--\nnet/core/net_namespace.c=813=EXPORT_SYMBOL_GPL(get_net_ns_by_pid);\nnet/core/net_namespace.c-814-\nnet/core/net_namespace.c:815:#ifdef CONFIG_NET_NS_REFCNT_TRACKER\nnet/core/net_namespace.c-816-static void net_ns_net_debugfs(struct net *net)\n--\nnet/core/net_namespace.c=1183=static void rtnl_net_notifyid(struct net *net, int cmd, int id, u32 portid,\n--\nnet/core/net_namespace.c-1211-\nnet/core/net_namespace.c:1212:#ifdef CONFIG_NET_NS\nnet/core/net_namespace.c-1213-static void __init netns_ipv4_struct_check(void)\n--\nnet/core/net_namespace.c=1263=void __init net_ns_init(void)\n--\nnet/core/net_namespace.c-1266-\nnet/core/net_namespace.c:1267:#ifdef CONFIG_NET_NS\nnet/core/net_namespace.c-1268-\tnetns_ipv4_struct_check();\n--\nnet/core/net_namespace.c-1307-\nnet/core/net_namespace.c:1308:#ifdef CONFIG_NET_NS\nnet/core/net_namespace.c-1309-static int __register_pernet_operations(struct list_head *list,\n--\nnet/core/net_namespace.c=1363=static void __unregister_pernet_operations(struct pernet_operations *ops)\n--\nnet/core/net_namespace.c-1374-\nnet/core/net_namespace.c:1375:#endif /* CONFIG_NET_NS */\nnet/core/net_namespace.c-1376-\n--\nnet/core/net_namespace.c=1511=EXPORT_SYMBOL_GPL(unregister_pernet_device);\nnet/core/net_namespace.c-1512-\nnet/core/net_namespace.c:1513:#ifdef CONFIG_NET_NS\nnet/core/net_namespace.c-1514-static struct ns_common *netns_get(struct task_struct *task)\n"
}

25/2 2026/08/13 08:34 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 55836
							output: 32
							thoughts: 877
						

Thoughts:
**Focusing on Network Namespace and Neighbor Table Interactions**

Okay, so I've been looking at this patch and, yes, `CONFIG_NET_NS` is definitely a requirement here. I mean, the whole point of these changes, especially with `nd_tbl`, hinges on network namespace support. And to properly handle `nd_tbl` changes, `CONFIG_IPV6` is also going to be needed. Now, let's see, what other configurations might be relevant here.

The patch touches a bunch of drivers: `ipoib_main.c`, `prestera_router.c`, `mlx5/en/rep/neigh.c`, `spectrum_router.c`, `tunnel_conf.c`, `rocker_main.c`, `tc_counters.c`, `vrf.c`, and `vxlan_core.c`. But the core stuff is in `neighbour.c`, `arp.c`, and `ndisc.c`. So really, `NET_NS` and `IPV6` are the big ones to pay attention to.

Let's revisit `neigh_table_register`. It's called from `arp_net_init` and `ndisc_net_init`. These are pernet operations, so they're triggered when a new network namespace is created. So fuzzing this means we *need* to create network namespaces. That solidifies the `NET_NS` requirement.

Alright, let's list my focus symbols, the functions that are most likely to be exposed:
*   `neigh_table_register`
*   `neigh_table_unregister`
*   `pneigh_lookup`
*   `pneigh_create`
*   `pneigh_delete`
*   `neigh_parms_alloc`
*   `neigh_parms_release`
*   `neigh_xmit`

Wait, `neigh_table_register` isn't static, although it's not exported. It's a global function. So, is this reachable from syscalls? Yeah, creating a new network namespace with something like `unshare(CLONE_NEWNET)` will call `setup_net`. This calls the registered pernet operations, which includes `arp_net_init` and `ndisc_net_init`, which in turn call `neigh_table_register`. So, yes, it's definitely reachable.

Now, let's look at `pneigh_create`. It's called from `arp_req_set_public` and `ndisc_netdev_event`. Wait, it's also called from `neigh_add` (netlink RTM_NEWNEIGH). So, it's reachable via netlink.

`pneigh_delete` is called from `neigh_delete` (netlink RTM_DELNEIGH).

`neigh_parms_alloc` is called from `inetdev_init` (adding an IPv4 address) and `ipv6_add_dev` (adding an IPv6 address).

`neigh_parms_release` is called from `inetdev_destroy` and `addrconf_ifdown`.

And `neigh_xmit` is called from `mpls_xmit` and `bpf_lwt_xmit`.

Okay, those look like solid focus symbols for deeper analysis.

Let me double-check if I missed any key functions. Is `neigh_table_init` now static? Yep, let's check `net/core/neighbour.c` for that.



26/2 2026/08/13 08:34 tool grepper
0m Args:
{
  "Expression": "neigh_table_init",
  "PathPrefix": "net/core/neighbour.c"
}

Results:
{
  "Output": "net/core/neighbour.c=1817=static struct lock_class_key neigh_table_proxy_queue_class;\nnet/core/neighbour.c-1818-\nnet/core/neighbour.c:1819:static int neigh_table_init(struct net *net, struct neigh_table *tbl)\nnet/core/neighbour.c-1820-{\n--\nnet/core/neighbour.c=1935=int neigh_table_register(struct net *net, struct neigh_table *tbl, int index)\n--\nnet/core/neighbour.c-1946-\nnet/core/neighbour.c:1947:\terr = neigh_table_init(net, tbl);\nnet/core/neighbour.c-1948-\tif (err)\n"
}

27/2 2026/08/13 08:34 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 57714
							output: 32
							thoughts: 24
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Considering the `neigh_table` Initialization and Clearing Functions**

Alright, so `neigh_table_init` is officially static, which makes sense. We're keeping that initialization routine internal to the module, as expected. It's a good practice to encapsulate those kinds of functions.

But now, what about its counterpart, `neigh_table_clear`? Should it *also* be static? We should give that some thought, and make sure that follows the same logic. Let's make sure our design is consistent. If we're keeping `init` internal, `clear` likely should follow suit, depending on the role the neighboring table serves within the larger context.



28/2 2026/08/13 08:34 tool grepper
0m Args:
{
  "Expression": "neigh_table_clear",
  "PathPrefix": "net/core/neighbour.c"
}

Results:
{
  "Output": "net/core/neighbour.c=1887=static void neigh_table_free(struct neigh_table *tbl)\n--\nnet/core/neighbour.c-1903-\nnet/core/neighbour.c:1904:static void neigh_table_clear(struct net *net, struct neigh_table *tbl)\nnet/core/neighbour.c-1905-{\n--\nnet/core/neighbour.c=1961=void neigh_table_unregister(struct net *net, int index)\n--\nnet/core/neighbour.c-1965-\tnet-\u003eneigh_tables[index] = NULL;\nnet/core/neighbour.c:1966:\tneigh_table_clear(net, tbl);\nnet/core/neighbour.c-1967-}\n"
}

29/2 2026/08/13 08:34 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 58137
							output: 32
							thoughts: 23
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Considering `neigh_table` Functions**

Right, so `neigh_table_clear` is definitely a static function. That's straightforward and as expected. Now, my mind immediately jumps to `neigh_table_inherit`. I need to think about *that* one. What's its scope, its access modifiers, and its purpose in relation to the overall `neigh_table` structure? Is it also static? And if not, what are the implications of it being non-static in this particular context? I need to analyze how it's used and how it interacts with the rest of the code. This will be key to understanding the overall design and any potential areas of concern. I'll need to double-check my assumptions and trace its usage to be sure I'm not missing something subtle. I need to figure out if there's any particular reason it's *not* static, or if it's an oversight.



30/2 2026/08/13 08:34 tool grepper
0m Args:
{
  "Expression": "neigh_table_inherit",
  "PathPrefix": "net/core/neighbour.c"
}

Results:
{
  "Output": "net/core/neighbour.c=1904=static void neigh_table_clear(struct net *net, struct neigh_table *tbl)\n--\nnet/core/neighbour.c-1915-\nnet/core/neighbour.c:1916:static void neigh_table_inherit(struct net *net, struct neigh_table *tbl,\nnet/core/neighbour.c-1917-\t\t\t\tint index)\n--\nnet/core/neighbour.c=1935=int neigh_table_register(struct net *net, struct neigh_table *tbl, int index)\n--\nnet/core/neighbour.c-1944-\nnet/core/neighbour.c:1945:\tneigh_table_inherit(net, tbl, index);\nnet/core/neighbour.c-1946-\n"
}

31/2 2026/08/13 08:34 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 58619
							output: 35
							thoughts: 176
						

Thoughts:
**My Analysis of Potential Focus Symbols**

Okay, so I've confirmed that `neigh_table_inherit` is indeed a static function. That eliminates it from consideration as a focus symbol.

Moving on, I need to identify the non-static functions within the neighborhood subsystem that might serve as good candidates. Based on my existing knowledge, here's what comes to mind: `neigh_table_register`, `neigh_table_unregister`, `pneigh_lookup`, `pneigh_create`, `pneigh_delete`, `neigh_parms_alloc`, `neigh_parms_release`, and `neigh_xmit`. These seem like core functions I should investigate further.

Now, let's just quickly double check those inline definitions for the ARP and neighbor discovery tables. Ah, yes, `arp_table` and `nd_table` are defined as `static inline` in their respective header files. Therefore, they won't appear as symbols, which means I can ignore them for this particular analysis.

Finally, I should check `arp_req_set_public` and `arp_req_delete_public` in `net/ipv4/arp.c` to see if they might be useful.



32/2 2026/08/13 08:34 tool grepper
0m Args:
{
  "Expression": "arp_req_set_public",
  "PathPrefix": "net/ipv4/arp.c"
}

Results:
{
  "Output": "net/ipv4/arp.c=1069=static int arp_req_set_proxy(struct net *net, struct net_device *dev, int on)\n--\nnet/ipv4/arp.c-1081-\nnet/ipv4/arp.c:1082:static int arp_req_set_public(struct net *net, struct arpreq *r,\nnet/ipv4/arp.c-1083-\t\t\t      struct net_device *dev)\n--\nnet/ipv4/arp.c=1103=static int arp_req_set(struct net *net, struct arpreq *r)\n--\nnet/ipv4/arp.c-1115-\tif (r-\u003earp_flags \u0026 ATF_PUBL)\nnet/ipv4/arp.c:1116:\t\treturn arp_req_set_public(net, r, dev);\nnet/ipv4/arp.c-1117-\n"
}

33/2 2026/08/13 08:34 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 59331
							output: 31
							thoughts: 25
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:

**Analyzing a Static State in the ARP Process**

Hmm, things feel static. This suggests a potential hang-up or a state where things aren't transitioning as expected. My gut tells me this is likely related to the Address Resolution Protocol (ARP) process, given its critical role in IPv4 network connectivity and its potential for creating unexpected blocks.

Let's cut right to the chase and examine the `arp_process` function. It's the core of the ARP handling logic within the Linux kernel, specifically within `net/ipv4/arp.c`. I need to understand what's happening at the lowest level to diagnose this static behavior. I'll need to look at the interactions there for the conditions of the state. I expect to be able to follow the data at that level.



34/2 2026/08/13 08:34 tool grepper
0m Args:
{
  "Expression": "arp_process",
  "PathPrefix": "net/ipv4/arp.c"
}

Results:
{
  "Output": "net/ipv4/arp.c=675=static bool arp_is_garp(struct net *net, struct net_device *dev,\n--\nnet/ipv4/arp.c-704-\nnet/ipv4/arp.c:705:static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)\nnet/ipv4/arp.c-706-{\n--\nnet/ipv4/arp.c=956=static void parp_redo(struct sk_buff *skb)\nnet/ipv4/arp.c-957-{\nnet/ipv4/arp.c:958:\tarp_process(dev_net(skb-\u003edev), NULL, skb);\nnet/ipv4/arp.c-959-}\n--\nnet/ipv4/arp.c=970=static int arp_rcv(struct sk_buff *skb, struct net_device *dev,\n--\nnet/ipv4/arp.c-1000-\t\t       dev_net(dev), NULL, skb, dev, NULL,\nnet/ipv4/arp.c:1001:\t\t       arp_process);\nnet/ipv4/arp.c-1002-\n"
}

35/2 2026/08/13 08:34 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 59892
							output: 35
							thoughts: 57
						

Thoughts:
Here's my thinking process:

**Static `arp_process` and Related IPv6 Neighbor Discovery Functions**

Okay, so `arp_process` is defined as a static function. That means it's only visible within its compilation unit, likely for a very specific internal purpose. Let's delve deeper into understanding where it fits within the larger network stack context.

Given that `arp_process` is static, my next step is to examine the related IPv6 Neighbor Discovery functions in `net/ipv6/ndisc.c`. Specifically, I'll focus on `ndisc_recv_ns`, `ndisc_recv_na`, `ndisc_recv_rs`, and `ndisc_router_discovery`. These seem like the logical places to look for any interaction with, or potential analogous functionality to, the ARP (Address Resolution Protocol) process, but within the IPv6 realm. I want to see how the code handles neighbor solicitation, neighbor advertisement, router solicitation, and router discovery, to understand if `arp_process` might have a IPv6 analogue that is more visible to the kernel.



36/2 2026/08/13 08:34 tool grepper
0m Args:
{
  "Expression": "ndisc_recv_ns",
  "PathPrefix": "net/ipv6/ndisc.c"
}

Results:
{
  "Output": "net/ipv6/ndisc.c=781=void ndisc_update(const struct net_device *dev, struct neighbour *neigh,\n--\nnet/ipv6/ndisc.c-789-\nnet/ipv6/ndisc.c:790:static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)\nnet/ipv6/ndisc.c-791-{\n--\nnet/ipv6/ndisc.c=1786=static void pndisc_redo(struct sk_buff *skb)\nnet/ipv6/ndisc.c-1787-{\nnet/ipv6/ndisc.c:1788:\tenum skb_drop_reason reason = ndisc_recv_ns(skb);\nnet/ipv6/ndisc.c-1789-\n--\nnet/ipv6/ndisc.c=1812=enum skb_drop_reason ndisc_rcv(struct sk_buff *skb)\n--\nnet/ipv6/ndisc.c-1839-\t\tmemset(NEIGH_CB(skb), 0, sizeof(struct neighbour_cb));\nnet/ipv6/ndisc.c:1840:\t\treason = ndisc_recv_ns(skb);\nnet/ipv6/ndisc.c-1841-\t\tbreak;\n"
}

37/2 2026/08/13 08:34 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 60559
							output: 34
							thoughts: 15
						

Thoughts:
Okay, here's my summary, presented as a stream of thought:

**Initial Assessment: Stasis and the Unknown**

Right, these thoughts, they're… static. Kind of a dead end. Not much to work with, conceptually. I need a trigger, a direction to pull. What's the core question here? What could inject some dynamism? Let's see... what about `ndisc_rcv`? That's a good starting point. I should be able to make something useful out of that...



38/2 2026/08/13 08:34 tool grepper
0m Args:
{
  "Expression": "ndisc_rcv",
  "PathPrefix": "net/ipv6/ndisc.c"
}

Results:
{
  "Output": "net/ipv6/ndisc.c=1798=static bool ndisc_suppress_frag_ndisc(struct sk_buff *skb)\n--\nnet/ipv6/ndisc.c-1811-\nnet/ipv6/ndisc.c:1812:enum skb_drop_reason ndisc_rcv(struct sk_buff *skb)\nnet/ipv6/ndisc.c-1813-{\n"
}

39/2 2026/08/13 08:34 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 60840
							output: 32
							thoughts: 28
						

Thoughts:
Okay, here's my interpretation of those thoughts, presented as a personal thought process, as if I were a networking expert considering this:

**Considering Network Packet Reception Focus**

Right, let's see. `ndisc_rcv`... That's a decent one to zero in on. It's not a static element; that's important. It's dynamic enough to be a good focal point when digging into network packet reception. Now, what about the venerable `arp_rcv`? I need to consider that as a complementary focus. Let's see how those two function together.



40/2 2026/08/13 08:34 tool grepper
0m Args:
{
  "Expression": "arp_rcv",
  "PathPrefix": "net/ipv4/arp.c"
}

Results:
{
  "Output": "net/ipv4/arp.c-17- *\t\tAlan Cox\t:\tMake ARP add its own protocol entry\nnet/ipv4/arp.c:18: *\t\tRoss Martin     :       Rewrote arp_rcv() and arp_get_info()\nnet/ipv4/arp.c-19- *\t\tStephen Henson\t:\tAdd AX25 support to arp_get_info()\n--\nnet/ipv4/arp.c-36- *\t\tCraig Schlenter :\tDon't modify permanent entry\nnet/ipv4/arp.c:37: *\t\t\t\t\tduring arp_rcv.\nnet/ipv4/arp.c-38- *\t\tRuss Nelson\t:\tTidied up a few bits.\n--\nnet/ipv4/arp.c=705=static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)\n--\nnet/ipv4/arp.c-720-\nnet/ipv4/arp.c:721:\t/* arp_rcv below verifies the ARP header and verifies the device\nnet/ipv4/arp.c-722-\t * is ARP'able.\n--\nnet/ipv4/arp.c=961=static int arp_is_multicast(const void *pkey)\n--\nnet/ipv4/arp.c-969-\nnet/ipv4/arp.c:970:static int arp_rcv(struct sk_buff *skb, struct net_device *dev,\nnet/ipv4/arp.c-971-\t\t   struct packet_type *pt, struct net_device *orig_dev)\n--\nnet/ipv4/arp.c=1389=static struct packet_type arp_packet_type __read_mostly = {\nnet/ipv4/arp.c-1390-\t.type =\tcpu_to_be16(ETH_P_ARP),\nnet/ipv4/arp.c:1391:\t.func =\tarp_rcv,\nnet/ipv4/arp.c-1392-};\n"
}

41/2 2026/08/13 08:34 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 61538
							output: 31
							thoughts: 26
						

Thoughts:
Here's my thinking process:

**Diving into the ARP Codebase**

Okay, so `arp_rcv` is static. That's a good initial observation; it's likely a core, internal function. Now, where does it fit within the overall ARP implementation? The next logical step is to trace the call chain or interactions. Let me see what I can find out about `arp_send` and `arp_solicit`. These are likely candidates for functions that would trigger the reception or resolution of ARP requests, which is obviously important because `arp_rcv` handles the responses. Understanding how they work together will paint a clearer picture of the ARP protocol's data path. I need to figure out the interactions between them all.



42/2 2026/08/13 08:35 tool grepper
0m Args:
{
  "Expression": "arp_send",
  "PathPrefix": "net/ipv4/arp.c"
}

Results:
{
  "Output": "net/ipv4/arp.c-63- *\t\tArnaldo C. Melo :\tconvert /proc/net/arp to seq_file\nnet/ipv4/arp.c:64: *\t\tShmulik Hen:\t\tSplit arp_send to arp_create and\nnet/ipv4/arp.c-65- *\t\t\t\t\tarp_xmit so intermediate drivers like\n--\nnet/ipv4/arp.c=297=static void arp_error_report(struct neighbour *neigh, struct sk_buff *skb)\n--\nnet/ipv4/arp.c-303-/* Create and send an arp packet. */\nnet/ipv4/arp.c:304:static void arp_send_dst(int type, int ptype, __be32 dest_ip,\nnet/ipv4/arp.c-305-\t\t\t struct net_device *dev, __be32 src_ip,\n--\nnet/ipv4/arp.c-325-\nnet/ipv4/arp.c:326:void arp_send(int type, int ptype, __be32 dest_ip,\nnet/ipv4/arp.c-327-\t      struct net_device *dev, __be32 src_ip,\n--\nnet/ipv4/arp.c-330-{\nnet/ipv4/arp.c:331:\tarp_send_dst(type, ptype, dest_ip, dev, src_ip, dest_hw, src_hw,\nnet/ipv4/arp.c-332-\t\t     target_hw, NULL);\nnet/ipv4/arp.c-333-}\nnet/ipv4/arp.c:334:EXPORT_SYMBOL(arp_send);\nnet/ipv4/arp.c-335-\nnet/ipv4/arp.c=336=static void arp_solicit(struct neighbour *neigh, struct sk_buff *skb)\n--\nnet/ipv4/arp.c-394-\t\tdst = skb_dst(skb);\nnet/ipv4/arp.c:395:\tarp_send_dst(ARPOP_REQUEST, ETH_P_ARP, target, dev, saddr,\nnet/ipv4/arp.c-396-\t\t     dst_hw, dev-\u003edev_addr, NULL, dst);\n--\nnet/ipv4/arp.c=705=static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)\n--\nnet/ipv4/arp.c-837-\t\t    !arp_ignore(in_dev, sip, tip))\nnet/ipv4/arp.c:838:\t\t\tarp_send_dst(ARPOP_REPLY, ETH_P_ARP, sip, dev, tip,\nnet/ipv4/arp.c-839-\t\t\t\t     sha, dev-\u003edev_addr, sha, reply_dst);\n--\nnet/ipv4/arp.c-857-\t\t\t\tif (n) {\nnet/ipv4/arp.c:858:\t\t\t\t\tarp_send_dst(ARPOP_REPLY, ETH_P_ARP,\nnet/ipv4/arp.c-859-\t\t\t\t\t\t     sip, dev, tip, sha,\n--\nnet/ipv4/arp.c-878-\t\t\t\t    NEIGH_VAR(in_dev-\u003earp_parms, PROXY_DELAY) == 0) {\nnet/ipv4/arp.c:879:\t\t\t\t\tarp_send_dst(ARPOP_REPLY, ETH_P_ARP,\nnet/ipv4/arp.c-880-\t\t\t\t\t\t     sip, dev, tip, sha,\n"
}

43/2 2026/08/13 08:35 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 62641
							output: 35
							thoughts: 40
						

Thoughts:
Here's a breakdown of my thought process:

**ARP & NDISC Export Status**

Okay, so `arp_send` is explicitly exported, meaning it's intended for broader use. That's good to know. But, `arp_solicit` is static, which tells me it's likely only used internally within its own compilation unit, which might mean the functionality is more tightly coupled to a specific module or purpose. Now, let's turn our attention to the Neighbor Discovery Protocol (NDISC) functions. I need to understand the export status, and potential coupling of `ndisc_send_ns` and `ndisc_send_na`. Are these also intended for general use, or are they likewise limited to certain aspects of the kernel? Understanding their scope will help determine how they fit into the bigger picture.



44/2 2026/08/13 08:35 tool grepper
0m Args:
{
  "Expression": "ndisc_send_ns",
  "PathPrefix": "net/ipv6/ndisc.c"
}

Results:
{
  "Output": "net/ipv6/ndisc.c=653=EXPORT_SYMBOL(ndisc_ns_create);\nnet/ipv6/ndisc.c-654-\nnet/ipv6/ndisc.c:655:void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit,\nnet/ipv6/ndisc.c-656-\t\t   const struct in6_addr *daddr, const struct in6_addr *saddr,\n--\nnet/ipv6/ndisc.c=740=static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb)\n--\nnet/ipv6/ndisc.c-757-\t\t}\nnet/ipv6/ndisc.c:758:\t\tndisc_send_ns(dev, target, target, saddr, 0);\nnet/ipv6/ndisc.c-759-\t} else if ((probes -= NEIGH_VAR(neigh-\u003eparms, APP_PROBES)) \u003c 0) {\n--\nnet/ipv6/ndisc.c-762-\t\taddrconf_addr_solict_mult(target, \u0026mcaddr);\nnet/ipv6/ndisc.c:763:\t\tndisc_send_ns(dev, target, \u0026mcaddr, saddr, 0);\nnet/ipv6/ndisc.c-764-\t}\n"
}

45/2 2026/08/13 08:35 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 63244
							output: 35
							thoughts: 24
						

Thoughts:
Here's my take on those thoughts, tailored for an expert audience:

**Analyzing the NDP Control Plane**

Okay, so `ndisc_send_ns` is global, which means it's a potential choke point, and something I need to keep in mind from a performance and security perspective. I need to understand its impact and where it's called from. I'm wondering if it's over-utilized or if a more targeted approach might be better in specific scenarios. That also means it can impact all instances, making it a critical point to review.

Given that, I should dig deeper. Let's see how `ndisc_send_na` behaves. Understanding its interplay with `ndisc_send_ns` will illuminate the overall NDP control plane behavior, as NA responses are the immediate response to NS. Are they correctly and efficiently handled? And, depending on the implementation details of both functions, what kind of locking is happening? Are there any obvious race conditions or contention points? That will give me a better understanding of the interaction with the overall system. I'm thinking about the potential impact of high network traffic and the implications for both single-threaded and multi-threaded systems. Time to dive into the code.



46/2 2026/08/13 08:35 tool grepper
0m Args:
{
  "Expression": "ndisc_send_na",
  "PathPrefix": "net/ipv6/ndisc.c"
}

Results:
{
  "Output": "net/ipv6/ndisc.c=523=EXPORT_SYMBOL(ndisc_send_skb);\nnet/ipv6/ndisc.c-524-\nnet/ipv6/ndisc.c:525:void ndisc_send_na(struct net_device *dev, const struct in6_addr *daddr,\nnet/ipv6/ndisc.c-526-\t\t   const struct in6_addr *solicited_addr,\n--\nnet/ipv6/ndisc.c-579-}\nnet/ipv6/ndisc.c:580:EXPORT_SYMBOL_GPL(ndisc_send_na);\nnet/ipv6/ndisc.c-581-\nnet/ipv6/ndisc.c=582=static void ndisc_send_unsol_na(struct net_device *dev)\n--\nnet/ipv6/ndisc.c-597-\nnet/ipv6/ndisc.c:598:\t\tndisc_send_na(dev, \u0026in6addr_linklocal_allnodes, \u0026ifa-\u003eaddr,\nnet/ipv6/ndisc.c-599-\t\t\t      /*router=*/ !!idev-\u003ecnf.forwarding,\n--\nnet/ipv6/ndisc.c=790=static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)\n--\nnet/ipv6/ndisc.c-935-\tif (dad) {\nnet/ipv6/ndisc.c:936:\t\tndisc_send_na(dev, \u0026in6addr_linklocal_allnodes, \u0026msg-\u003etarget,\nnet/ipv6/ndisc.c-937-\t\t\t      !!is_router, false, (ifp != NULL), true);\n--\nnet/ipv6/ndisc.c-957-\tif (neigh || !dev-\u003eheader_ops) {\nnet/ipv6/ndisc.c:958:\t\tndisc_send_na(dev, saddr, \u0026msg-\u003etarget, !!is_router,\nnet/ipv6/ndisc.c-959-\t\t\t      true, (ifp != NULL \u0026\u0026 inc), inc);\n"
}

47/2 2026/08/13 08:35 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 64098
							output: 32
							thoughts: 452
						

Thoughts:
**Analyzing the Impact of this Patch**

Okay, so `ndisc_send_na` is definitely exported, good to know. Now, let's see, we have a list of all these functions related to neighbor discovery: `neigh_table_register`, `neigh_table_unregister`, `pneigh_lookup`, `pneigh_create`, `pneigh_delete`, `neigh_parms_alloc`, `neigh_parms_release`, `neigh_xmit`, `arp_send`, `ndisc_send_ns`, and `ndisc_send_na`. Hang on a second, though; `arp_send`, `ndisc_send_ns`, and `ndisc_send_na` weren't touched in this patch. So, what *did* change?

Right, the patch is focused on these modified functions: `neigh_table_register`, `neigh_table_unregister`, `pneigh_lookup`, `pneigh_create`, `pneigh_delete`, `neigh_parms_alloc`, `neigh_parms_release`, `neigh_xmit`, `neigh_dump_info`, `neigh_dump_table`, `pneigh_dump_table`, `neigh_get`, `neigh_add`, `neigh_delete`, `neightbl_set`, `neightbl_dump_info`, `neigh_seq_start`, `neigh_seq_next`, and `neigh_seq_stop`. That's a lot!

I want to focus on the core functionality, so I'll create a list of focus symbols of the modified functions: `neigh_table_register`, `neigh_table_unregister`, `pneigh_lookup`, `pneigh_create`, `pneigh_delete`, `neigh_parms_alloc`, and `neigh_parms_release`. `neigh_xmit` will be of interest too, clearly.

Hmm, `neigh_dump_info` is static. I'd better double-check where that's used, let me look in `net/core/neighbour.c`.



48/2 2026/08/13 08:35 tool grepper
0m Args:
{
  "Expression": "neigh_dump_info",
  "PathPrefix": "net/core/neighbour.c"
}

Results:
{
  "Output": "net/core/neighbour.c=2915=static int neigh_valid_dump_req(const struct nlmsghdr *nlh,\n--\nnet/core/neighbour.c-2975-\nnet/core/neighbour.c:2976:static int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb)\nnet/core/neighbour.c-2977-{\n--\nnet/core/neighbour.c=3934=static const struct rtnl_msg_handler neigh_rtnl_msg_handlers[] __initconst = {\n--\nnet/core/neighbour.c-3936-\t{.msgtype = RTM_DELNEIGH, .doit = neigh_delete},\nnet/core/neighbour.c:3937:\t{.msgtype = RTM_GETNEIGH, .doit = neigh_get, .dumpit = neigh_dump_info,\nnet/core/neighbour.c-3938-\t .flags = RTNL_FLAG_DOIT_UNLOCKED | RTNL_FLAG_DUMP_UNLOCKED},\n"
}

49/2 2026/08/13 08:35 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 65301
							output: 42
							thoughts: 209
						

Thoughts:
**Analyzing the Neighborhood Subsystem**

Okay, so I'm looking at this code and it's immediately apparent that a lot of it is static. I need to get a handle on what's accessible and relevant here, so let's start with the basics. `neigh_add`, `neigh_delete`, and `neigh_get` – those are static. Fine, not the entry points I'm looking for. What about `neigh_table_init`? Yep, static as well. Okay, so those aren't going to be the major focus points.

Given that, I think my best bets for significant symbols are likely going to be: `neigh_table_register`, `neigh_table_unregister`, `pneigh_lookup`, `pneigh_create`, `pneigh_delete`, `neigh_parms_alloc`, `neigh_parms_release`, and finally, `neigh_xmit`. Those are the functions that sound like the key control points.

Alright, let's confirm the definitions. I remember seeing `neigh_table_register` in `include/net/neighbour.h` earlier, so I'll double check that. Yep, it's there. Good. Now, I should make sure I understand the conditional compilation of this. I should investigate `CONFIG_NET_NS` and `CONFIG_IPV6`, because these are important, and make sure I don't miss anything. The patch also introduced `sysctl_neigh_inherit_init_net`, which I should look into. I should check `net/core/sysctl_net_core.c` for this, I can learn more about its behavior.



50/2 2026/08/13 08:35 tool grepper
0m Args:
{
  "Expression": "sysctl_neigh_inherit_init_net",
  "PathPrefix": "net/core/sysctl_net_core.c"
}

Results:
{
  "Output": "net/core/sysctl_net_core.c=55=EXPORT_SYMBOL(sysctl_devconf_inherit_init_net);\nnet/core/sysctl_net_core.c-56-\nnet/core/sysctl_net_core.c:57:int sysctl_neigh_inherit_init_net __read_mostly = 1;\nnet/core/sysctl_net_core.c-58-\n--\nnet/core/sysctl_net_core.c=433=static struct ctl_table net_core_table[] = {\n--\nnet/core/sysctl_net_core.c-683-\t\t.procname\t= \"neigh_inherit_init_net\",\nnet/core/sysctl_net_core.c:684:\t\t.data\t\t= \u0026sysctl_neigh_inherit_init_net,\nnet/core/sysctl_net_core.c-685-\t\t.maxlen\t\t= sizeof(int),\n"
}

51/2 2026/08/13 08:35 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 66151
							output: 36
							thoughts: 47
						

Thoughts:
Okay, here's my interpretation of those thoughts, summarized from my perspective as a networking expert:

**Investigating a VxLAN Sysctl Setting**

Right, so I'm looking at this sysctl setting. The implication is that it's generally available, assuming the kernel is built in a relatively standard configuration. The fact that `CONFIG_SYSCTL` is enabled is practically a given in most modern kernel builds. It's a fundamental feature.

Now, my focus is on how this plays out within the VxLAN implementation. I need to dive into the changes within the `drivers/net/vxlan/vxlan_core.c` file. That's where I expect to find the specifics regarding this particular sysctl - how it's defined, how it's used to control VxLAN behavior, and what parameters it might expose. Let's see what's changed and figure out what we can control.



52/2 2026/08/13 08:35 tool grepper
0m Args:
{
  "Expression": "arp_table",
  "PathPrefix": "drivers/net/vxlan/vxlan_core.c"
}

Results:
{
  "Output": "drivers/net/vxlan/vxlan_core.c=1842=static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)\ndrivers/net/vxlan/vxlan_core.c-1843-{\ndrivers/net/vxlan/vxlan_core.c:1844:\tstruct neigh_table *tbl = arp_table(dev_net(dev));\ndrivers/net/vxlan/vxlan_core.c-1845-\tstruct vxlan_dev *vxlan = netdev_priv(dev);\n--\ndrivers/net/vxlan/vxlan_core.c=2101=static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)\n--\ndrivers/net/vxlan/vxlan_core.c-2118-\ndrivers/net/vxlan/vxlan_core.c:2119:\t\ttbl = arp_table(dev_net(dev));\ndrivers/net/vxlan/vxlan_core.c-2120-\t\tpip = ip_hdr(skb);\n"
}

53/2 2026/08/13 08:35 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 66712
							output: 180
							thoughts: 407
						

Thoughts:
**Analyzing the Network Namespace Neighbor Table Changes**

Okay, so I'm looking at this code and it's clear this patch is about adapting to the new per-network namespace neighbor table structure. It uses `arp_table(dev_net(dev))` and that makes perfect sense, just adapting to the new API.

The big picture here is that they've moved the neighbor tables, `arp_tbl` and `nd_tbl`, from being global to being specific to each network namespace (`net->neigh_tables`). This has pretty significant ramifications, especially for scenarios like multi-tenancy and containerization where you have multiple isolated network environments. It fundamentally alters how we look up, create, and manage neighbor entries across these different namespaces for both IPv4 ARP and IPv6 Neighbor Discovery.

I'm seeing that `NET_NS` and `IPV6` are relevant configuration options, which makes sense. I need to make sure I understand the key functions that have been impacted. Let's make sure I have all the key symbols. I'll focus on these: `neigh_table_register`, `neigh_table_unregister`, `pneigh_lookup`, `pneigh_create`, `pneigh_delete`, `neigh_parms_alloc`, and `neigh_xmit`.

Let's quickly verify the function names. Yep, `neigh_table_register` is indeed the correct name and the correct signature: `neigh_table_register(struct net *net, struct neigh_table *tbl, int index)`. And `neigh_table_unregister` is `neigh_table_unregister(struct net *net, int index)`.

Now, let's confirm the usage changes. I can see that `neigh_xmit` now correctly uses `net->neigh_tables[index]`. Good, that's what I expected. And `neigh_parms_alloc` now takes `struct neigh_table *tbl` which is also as expected, since it's no longer using the global tables. Also, `pneigh_lookup` is modified and now takes a `struct neigh_table *tbl` and drops the `struct net *net` parameter since the table is now per-net.

Alright, everything seems to be correctly adapted to the per-namespace approach. The core functions I checked all show the expected modifications. I'm satisfied that the changes are implemented correctly based on this review.



54/2 2026/08/13 08:35 tool set-results
0m Args:
{
  "EnableConfigs": [
    "NET_NS",
    "IPV6"
  ],
  "FocusSymbols": [
    "neigh_table_register",
    "neigh_table_unregister",
    "pneigh_lookup",
    "pneigh_create",
    "pneigh_delete",
    "neigh_parms_alloc",
    "neigh_parms_release",
    "neigh_xmit"
  ],
  "Reasoning": "The patch changes the neighbor tables (arp_tbl and nd_tbl) from being global to being per-network namespace (net-\u003eneigh_tables). This is a significant functional change in the core networking stack, affecting IPv4 ARP and IPv6 Neighbor Discovery, especially in environments using network namespaces. It modifies how neighbor entries are looked up, created, and garbage collected across different namespaces.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "NET_NS",
    "IPV6"
  ],
  "FocusSymbols": [
    "neigh_table_register",
    "neigh_table_unregister",
    "pneigh_lookup",
    "pneigh_create",
    "pneigh_delete",
    "neigh_parms_alloc",
    "neigh_parms_release",
    "neigh_xmit"
  ],
  "Reasoning": "The patch changes the neighbor tables (arp_tbl and nd_tbl) from being global to being per-network namespace (net-\u003eneigh_tables). This is a significant functional change in the core networking stack, affecting IPv4 ARP and IPv6 Neighbor Discovery, especially in environments using network namespaces. It modifies how neighbor entries are looked up, created, and garbage collected across different namespaces.",
  "WorthFuzzing": true
}

55/1 2026/08/13 08:35 action If
2m Args:
{
  "WorthFuzzing": true
}

Results:
null

56/2 2026/08/13 08:35 agent kmsan-evaluator
2m Model:
best-expensive

Args:
null

Results:
{
  "KMSANReasoning": "The patch series refactors the neighbor discovery subsystem (ARP and IPv6 ND) to make neighbor tables (`arp_tbl` and `nd_tbl`) per-network-namespace instead of global variables. It does this by allocating a new `struct neigh_table` for each network namespace, copying the initial values from a template, and initializing the dynamic fields (locks, lists, workqueues, etc.). \n\nThere are no new structures or fields copied to user space. The `struct neigh_table` itself is an internal kernel structure and is never copied directly to user space. Information from the table is exposed to user space via netlink messages (e.g., `neightbl_fill_info`, `neightbl_fill_param_info`), which construct the messages using `nla_put_*` functions and designated initializers for structures like `ndt_config` and `ndt_stats`. All memory allocations for the tables, parameters, and hash buckets use zero-initializing functions (`kzalloc`, `alloc_percpu`, `__GFP_ZERO`) or `kmemdup` from fully initialized static templates.\n\nSince the patch does not introduce any uninitialized memory risks, info-leaks, or complex data structures sent to user space without proper initialization, a dedicated KMSAN fuzzing session is not justified. Standard KASAN, LOCKDEP, and other bug detectors are sufficient to catch any potential memory corruption, out-of-bounds access, or use-after-free bugs introduced by this refactoring.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit ec0d7c39eae66ddc2d69aed3897e829969ad40bd
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Aug 13 08:33:02 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
index e586e17fc7a58..fe43e85959583 100644
--- a/Documentation/admin-guide/sysctl/net.rst
+++ b/Documentation/admin-guide/sysctl/net.rst
@@ -413,6 +413,20 @@ new netns has been created.
 
 Default : 0  (for compatibility reasons)
 
+neigh_inherit_init_net
+----------------------
+
+Controls if a new network namespace should inherit all current
+settings under /proc/sys/net/ipv{4,6}/neigh/default/.
+
+By default, the value is set to 1, and all settings are inherited
+from init_net.
+
+If set to 0, both IPv4 and IPv6 settings are reset to their default
+values.
+
+Default : 1  (for compatibility reasons)
+
 txrehash
 --------
 
diff --git a/drivers/infiniband/ulp/ipoib/ipoib_main.c b/drivers/infiniband/ulp/ipoib/ipoib_main.c
index 16a015b672063..895a3e3f7310b 100644
--- a/drivers/infiniband/ulp/ipoib/ipoib_main.c
+++ b/drivers/infiniband/ulp/ipoib/ipoib_main.c
@@ -1378,7 +1378,8 @@ struct ipoib_neigh *ipoib_neigh_get(struct net_device *dev, u8 *daddr)
 	return neigh;
 }
 
-static void __ipoib_reap_neigh(struct ipoib_dev_priv *priv)
+static void __ipoib_reap_neigh(struct ipoib_dev_priv *priv,
+			       int gc_interval)
 {
 	struct ipoib_neigh_table *ntbl = &priv->ntbl;
 	struct ipoib_neigh_hash *htbl;
@@ -1397,7 +1398,7 @@ static void __ipoib_reap_neigh(struct ipoib_dev_priv *priv)
 		goto out_unlock;
 
 	/* neigh is obsolete if it was idle for two GC periods */
-	dt = 2 * arp_tbl.gc_interval;
+	dt = 2 * gc_interval;
 	neigh_obsolete = jiffies - dt;
 
 	for (i = 0; i < htbl->size; i++) {
@@ -1433,11 +1434,16 @@ static void ipoib_reap_neigh(struct work_struct *work)
 {
 	struct ipoib_dev_priv *priv =
 		container_of(work, struct ipoib_dev_priv, neigh_reap_task.work);
+	struct net_device *dev = priv->dev;
+	struct net *net = dev_net(dev);
+	struct neigh_table *tbl;
+	int gc_interval;
 
-	__ipoib_reap_neigh(priv);
+	tbl = arp_table(net);
+	gc_interval = tbl->gc_interval;
+	__ipoib_reap_neigh(priv, gc_interval);
 
-	queue_delayed_work(priv->wq, &priv->neigh_reap_task,
-			   arp_tbl.gc_interval);
+	queue_delayed_work(priv->wq, &priv->neigh_reap_task, gc_interval);
 }
 
 
@@ -1590,8 +1596,11 @@ void ipoib_neigh_free(struct ipoib_neigh *neigh)
 static int ipoib_neigh_hash_init(struct ipoib_dev_priv *priv)
 {
 	struct ipoib_neigh_table *ntbl = &priv->ntbl;
-	struct ipoib_neigh_hash *htbl;
 	struct ipoib_neigh __rcu **buckets;
+	struct net_device *dev = priv->dev;
+	struct net *net = dev_net(dev);
+	struct ipoib_neigh_hash *htbl;
+	struct neigh_table *tbl;
 	u32 size;
 
 	clear_bit(IPOIB_NEIGH_TBL_FLUSH, &priv->flags);
@@ -1599,7 +1608,9 @@ static int ipoib_neigh_hash_init(struct ipoib_dev_priv *priv)
 	htbl = kzalloc_obj(*htbl);
 	if (!htbl)
 		return -ENOMEM;
-	size = roundup_pow_of_two(arp_tbl.gc_thresh3);
+
+	tbl = arp_table(net);
+	size = roundup_pow_of_two(tbl->gc_thresh3);
 	buckets = kvzalloc_objs(*buckets, size);
 	if (!buckets) {
 		kfree(htbl);
@@ -1614,7 +1625,7 @@ static int ipoib_neigh_hash_init(struct ipoib_dev_priv *priv)
 
 	/* start garbage collection */
 	queue_delayed_work(priv->wq, &priv->neigh_reap_task,
-			   arp_tbl.gc_interval);
+			   tbl->gc_interval);
 
 	return 0;
 }
diff --git a/drivers/net/ethernet/marvell/prestera/prestera_router.c b/drivers/net/ethernet/marvell/prestera/prestera_router.c
index 0c4f462baa6ef..ba45b61b09bb0 100644
--- a/drivers/net/ethernet/marvell/prestera/prestera_router.c
+++ b/drivers/net/ethernet/marvell/prestera/prestera_router.c
@@ -683,7 +683,7 @@ __prestera_k_arb_n_offload_set(struct prestera_switch *sw,
 {
 	struct neighbour *n;
 
-	n = neigh_lookup(&arp_tbl, &nc->key.addr.u.ipv4,
+	n = neigh_lookup(arp_table(&init_net), &nc->key.addr.u.ipv4,
 			 nc->key.dev);
 	if (!n)
 		return;
@@ -790,7 +790,7 @@ __prestera_k_arb_nc_kern_n_fetch(struct prestera_switch *sw,
 	int err;
 
 	memset(&nc->nh_neigh_info, 0, sizeof(nc->nh_neigh_info));
-	n = neigh_lookup(&arp_tbl, &nc->key.addr.u.ipv4, nc->key.dev);
+	n = neigh_lookup(arp_table(&init_net), &nc->key.addr.u.ipv4, nc->key.dev);
 	if (!n)
 		goto out;
 
@@ -1052,10 +1052,12 @@ static void __prestera_k_arb_hw_state_upd(struct prestera_switch *sw,
 #endif /* PRESTERA_IMPLICITY_RESOLVE_DEAD_NEIGH */
 
 	if (nc->key.addr.v == PRESTERA_IPV4) {
-		n = neigh_lookup(&arp_tbl, &nc->key.addr.u.ipv4,
+		struct neigh_table *tbl = arp_table(&init_net);
+
+		n = neigh_lookup(tbl, &nc->key.addr.u.ipv4,
 				 nc->key.dev);
 		if (!n)
-			n = neigh_create(&arp_tbl, &nc->key.addr.u.ipv4,
+			n = neigh_create(tbl, &nc->key.addr.u.ipv4,
 					 nc->key.dev);
 	} else {
 		n = NULL;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c b/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c
index 648f4521c0964..6a8d8aac7d16f 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en/rep/neigh.c
@@ -17,20 +17,27 @@
 #include "fs_core.h"
 #include "diag/en_rep_tracepoint.h"
 
-static unsigned long mlx5e_rep_ipv6_interval(void)
+static unsigned long mlx5e_rep_ipv6_interval(struct net *net)
 {
 	if (IS_ENABLED(CONFIG_IPV6) && ipv6_mod_enabled())
-		return NEIGH_VAR(&nd_tbl.parms, DELAY_PROBE_TIME);
+		return NEIGH_VAR(&nd_table(net)->parms, DELAY_PROBE_TIME);
 
 	return ~0UL;
 }
 
 static void mlx5e_rep_neigh_update_init_interval(struct mlx5e_rep_priv *rpriv)
 {
-	unsigned long ipv4_interval = NEIGH_VAR(&arp_tbl.parms, DELAY_PROBE_TIME);
-	unsigned long ipv6_interval = mlx5e_rep_ipv6_interval();
 	struct net_device *netdev = rpriv->netdev;
-	struct mlx5e_priv *priv = netdev_priv(netdev);
+	struct net *net = dev_net(netdev);
+	unsigned long ipv4_interval;
+	unsigned long ipv6_interval;
+	struct neigh_table *tbl;
+	struct mlx5e_priv *priv;
+
+	priv = netdev_priv(netdev);
+	tbl = arp_table(net);
+	ipv4_interval = NEIGH_VAR(&tbl->parms, DELAY_PROBE_TIME);
+	ipv6_interval = mlx5e_rep_ipv6_interval(net);
 
 	rpriv->neigh_update.min_interval = min_t(unsigned long, ipv6_interval, ipv4_interval);
 	mlx5_fc_update_sampling_interval(priv->mdev, rpriv->neigh_update.min_interval);
@@ -217,12 +224,6 @@ static int mlx5e_rep_netevent_event(struct notifier_block *nb,
 	switch (event) {
 	case NETEVENT_NEIGH_UPDATE:
 		n = ptr;
-#if IS_ENABLED(CONFIG_IPV6)
-		if (n->tbl != &nd_tbl && n->tbl != &arp_tbl)
-#else
-		if (n->tbl != &arp_tbl)
-#endif
-			return NOTIFY_DONE;
 
 		update_work = mlx5e_alloc_neigh_update_work(priv, n);
 		if (!update_work)
@@ -238,11 +239,7 @@ static int mlx5e_rep_netevent_event(struct notifier_block *nb,
 		 * changes in the default table, we only care about changes
 		 * done per device delay prob time parameter.
 		 */
-#if IS_ENABLED(CONFIG_IPV6)
-		if (!p->dev || (p->tbl != &nd_tbl && p->tbl != &arp_tbl))
-#else
-		if (!p->dev || p->tbl != &arp_tbl)
-#endif
+		if (!p->dev)
 			return NOTIFY_DONE;
 
 		rcu_read_lock();
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c
index 8b827201935eb..67c12ca19d59d 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_tun_encap.c
@@ -393,20 +393,10 @@ void mlx5e_tc_update_neigh_used_value(struct mlx5e_neigh_hash_entry *nhe)
 	struct mlx5e_encap_entry *e = NULL;
 	struct mlx5e_tc_flow *flow;
 	struct mlx5_fc *counter;
-	struct neigh_table *tbl;
 	bool neigh_used = false;
 	struct neighbour *n;
 	u64 lastuse;
 
-	if (m_neigh->family == AF_INET)
-		tbl = &arp_tbl;
-#if IS_ENABLED(CONFIG_IPV6)
-	else if (m_neigh->family == AF_INET6)
-		tbl = &nd_tbl;
-#endif
-	else
-		return;
-
 	/* mlx5e_get_next_valid_encap() releases previous encap before returning
 	 * next one.
 	 */
@@ -447,12 +437,23 @@ void mlx5e_tc_update_neigh_used_value(struct mlx5e_neigh_hash_entry *nhe)
 	trace_mlx5e_tc_update_neigh_used_value(nhe, neigh_used);
 
 	if (neigh_used) {
+		struct net_device *dev = READ_ONCE(nhe->neigh_dev);
+		struct net *net = dev_net(dev);
+		struct neigh_table *tbl;
+
 		nhe->reported_lastuse = jiffies;
 
+#if IS_ENABLED(CONFIG_IPV6)
+		if (m_neigh->family != AF_INET)
+			tbl = nd_table(net);
+		else
+#endif
+			tbl = arp_table(net);
+
 		/* find the relevant neigh according to the cached device and
 		 * dst ip pair
 		 */
-		n = neigh_lookup(tbl, &m_neigh->dst_ip, READ_ONCE(nhe->neigh_dev));
+		n = neigh_lookup(tbl, &m_neigh->dst_ip, dev);
 		if (!n)
 			return;
 
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c
index db260e3d1412f..37a8ddee3ea1e 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c
@@ -262,6 +262,7 @@ static void mlx5e_ipsec_init_macs(struct mlx5e_ipsec_sa_entry *sa_entry,
 	struct net_device *netdev = sa_entry->dev;
 	struct xfrm_state *x = sa_entry->x;
 	struct dst_entry *rt_dst_entry;
+	struct neigh_table *tbl;
 	struct flowi4 fl4 = {};
 	struct flowi6 fl6 = {};
 	struct neighbour *n;
@@ -364,9 +365,10 @@ static void mlx5e_ipsec_init_macs(struct mlx5e_ipsec_sa_entry *sa_entry,
 	return;
 
 neigh:
-	n = neigh_lookup(&arp_tbl, pkey, netdev);
+	tbl = arp_table(dev_net(netdev));
+	n = neigh_lookup(tbl, pkey, netdev);
 	if (!n) {
-		n = neigh_create(&arp_tbl, pkey, netdev);
+		n = neigh_create(tbl, pkey, netdev);
 		if (IS_ERR(n))
 			return;
 		neigh_event_send(n, NULL);
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
index 3d6fdbab05e01..f4a435885ba43 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
@@ -2397,14 +2397,15 @@ mlxsw_sp_neigh_entry_lookup(struct mlxsw_sp *mlxsw_sp, struct neighbour *n)
 static void
 mlxsw_sp_router_neighs_update_interval_init(struct mlxsw_sp *mlxsw_sp)
 {
+	struct net *net = mlxsw_sp_net(mlxsw_sp);
 	unsigned long interval;
 
 #if IS_ENABLED(CONFIG_IPV6)
 	interval = min_t(unsigned long,
-			 NEIGH_VAR(&arp_tbl.parms, DELAY_PROBE_TIME),
-			 NEIGH_VAR(&nd_tbl.parms, DELAY_PROBE_TIME));
+			 NEIGH_VAR(&arp_table(net)->parms, DELAY_PROBE_TIME),
+			 NEIGH_VAR(&nd_table(net)->parms, DELAY_PROBE_TIME));
 #else
-	interval = NEIGH_VAR(&arp_tbl.parms, DELAY_PROBE_TIME);
+	interval = NEIGH_VAR(&arp_table(net)->parms, DELAY_PROBE_TIME);
 #endif
 	mlxsw_sp->router->neighs_update.interval = jiffies_to_msecs(interval);
 }
@@ -2414,6 +2415,8 @@ static void mlxsw_sp_router_neigh_ent_ipv4_process(struct mlxsw_sp *mlxsw_sp,
 						   int ent_index)
 {
 	u64 max_rifs = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_RIFS);
+	struct net *net = mlxsw_sp_net(mlxsw_sp);
+	struct neigh_table *tbl;
 	struct net_device *dev;
 	struct neighbour *n;
 	__be32 dipn;
@@ -2429,9 +2432,10 @@ static void mlxsw_sp_router_neigh_ent_ipv4_process(struct mlxsw_sp *mlxsw_sp,
 		return;
 	}
 
+	tbl = arp_table(net);
 	dipn = htonl(dip);
 	dev = mlxsw_sp_rif_dev(mlxsw_sp->router->rifs[rif]);
-	n = neigh_lookup(&arp_tbl, &dipn, dev);
+	n = neigh_lookup(tbl, &dipn, dev);
 	if (!n)
 		return;
 
@@ -2445,6 +2449,8 @@ static void mlxsw_sp_router_neigh_ent_ipv6_process(struct mlxsw_sp *mlxsw_sp,
 						   char *rauhtd_pl,
 						   int rec_index)
 {
+	struct net *net = mlxsw_sp_net(mlxsw_sp);
+	struct neigh_table *tbl;
 	struct net_device *dev;
 	struct neighbour *n;
 	struct in6_addr dip;
@@ -2458,8 +2464,9 @@ static void mlxsw_sp_router_neigh_ent_ipv6_process(struct mlxsw_sp *mlxsw_sp,
 		return;
 	}
 
+	tbl = nd_table(net);
 	dev = mlxsw_sp_rif_dev(mlxsw_sp->router->rifs[rif]);
-	n = neigh_lookup(&nd_tbl, &dip, dev);
+	n = neigh_lookup(tbl, &dip, dev);
 	if (!n)
 		return;
 
@@ -3014,16 +3021,17 @@ static int mlxsw_sp_neigh_rif_made_sync(struct mlxsw_sp *mlxsw_sp,
 		.mlxsw_sp = mlxsw_sp,
 		.rif = rif,
 	};
+	struct net *net = mlxsw_sp_net(mlxsw_sp);
 
 	if (!mlxsw_sp_dev_lower_is_port(mlxsw_sp_rif_dev(rif)))
 		return 0;
 
-	neigh_for_each(&arp_tbl, mlxsw_sp_neigh_rif_made_sync_each, &rms);
+	neigh_for_each(arp_table(net), mlxsw_sp_neigh_rif_made_sync_each, &rms);
 	if (rms.err)
 		goto err_arp;
 
 #if IS_ENABLED(CONFIG_IPV6)
-	neigh_for_each(&nd_tbl, mlxsw_sp_neigh_rif_made_sync_each, &rms);
+	neigh_for_each(nd_table(net), mlxsw_sp_neigh_rif_made_sync_each, &rms);
 #endif
 	if (rms.err)
 		goto err_nd;
@@ -4622,7 +4630,7 @@ static int mlxsw_sp_nexthop4_init(struct mlxsw_sp *mlxsw_sp,
 	nh->nh_weight = 1;
 #endif
 	memcpy(&nh->gw_addr, &fib_nh->fib_nh_gw4, sizeof(fib_nh->fib_nh_gw4));
-	nh->neigh_tbl = &arp_tbl;
+	nh->neigh_tbl = arp_table(mlxsw_sp_net(mlxsw_sp));
 	err = mlxsw_sp_nexthop_insert(mlxsw_sp, nh);
 	if (err)
 		return err;
@@ -5112,6 +5120,7 @@ mlxsw_sp_nexthop_obj_init(struct mlxsw_sp *mlxsw_sp,
 			  struct nh_notifier_single_info *nh_obj, int weight)
 {
 	struct net_device *dev = nh_obj->dev;
+	struct net *net = dev_net(dev);
 	int err;
 
 	nh->nhgi = nh_grp->nhgi;
@@ -5120,12 +5129,12 @@ mlxsw_sp_nexthop_obj_init(struct mlxsw_sp *mlxsw_sp,
 	switch (nh_obj->gw_family) {
 	case AF_INET:
 		memcpy(&nh->gw_addr, &nh_obj->ipv4, sizeof(nh_obj->ipv4));
-		nh->neigh_tbl = &arp_tbl;
+		nh->neigh_tbl = arp_table(net);
 		break;
 	case AF_INET6:
 		memcpy(&nh->gw_addr, &nh_obj->ipv6, sizeof(nh_obj->ipv6));
 #if IS_ENABLED(CONFIG_IPV6)
-		nh->neigh_tbl = &nd_tbl;
+		nh->neigh_tbl = nd_table(net);
 #endif
 		break;
 	}
@@ -6981,7 +6990,7 @@ static int mlxsw_sp_nexthop6_init(struct mlxsw_sp *mlxsw_sp,
 	nh->nh_weight = rt->fib6_nh->fib_nh_weight;
 	memcpy(&nh->gw_addr, &rt->fib6_nh->fib_nh_gw6, sizeof(nh->gw_addr));
 #if IS_ENABLED(CONFIG_IPV6)
-	nh->neigh_tbl = &nd_tbl;
+	nh->neigh_tbl = nd_table(mlxsw_sp_net(mlxsw_sp));
 #endif
 
 	err = mlxsw_sp_nexthop_counter_enable(mlxsw_sp, nh);
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
index ae63d549b5429..1cd8347c274d4 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
@@ -456,6 +456,7 @@ mlxsw_sp_span_entry_gretap4_parms(struct mlxsw_sp *mlxsw_sp,
 	bool inherit_tos = tparm.iph.tos & 0x1;
 	bool inherit_ttl = !tparm.iph.ttl;
 	union mlxsw_sp_l3addr gw = daddr;
+	struct neigh_table *tbl = NULL;
 	struct net_device *l3edev;
 
 	if (!(to_dev->flags & IFF_UP) ||
@@ -469,9 +470,11 @@ mlxsw_sp_span_entry_gretap4_parms(struct mlxsw_sp *mlxsw_sp,
 		return mlxsw_sp_span_entry_unoffloadable(sparmsp);
 
 	l3edev = mlxsw_sp_span_gretap4_route(to_dev, &saddr.addr4, &gw.addr4);
+	if (l3edev)
+		tbl = arp_table(dev_net(l3edev));
 	return mlxsw_sp_span_entry_tunnel_parms_common(l3edev, saddr, daddr, gw,
 						       tparm.iph.ttl,
-						       &arp_tbl, sparmsp);
+						       tbl, sparmsp);
 }
 
 static int
@@ -561,6 +564,7 @@ mlxsw_sp_span_entry_gretap6_parms(struct mlxsw_sp *mlxsw_sp,
 	union mlxsw_sp_l3addr daddr = { .addr6 = tparm.raddr };
 	bool inherit_ttl = !tparm.hop_limit;
 	union mlxsw_sp_l3addr gw = daddr;
+	struct neigh_table *tbl = NULL;
 	struct net_device *l3edev;
 
 	if (!(to_dev->flags & IFF_UP) ||
@@ -574,9 +578,11 @@ mlxsw_sp_span_entry_gretap6_parms(struct mlxsw_sp *mlxsw_sp,
 		return mlxsw_sp_span_entry_unoffloadable(sparmsp);
 
 	l3edev = mlxsw_sp_span_gretap6_route(to_dev, &saddr.addr6, &gw.addr6);
+	if (l3edev)
+		tbl = nd_table(dev_net(l3edev));
 	return mlxsw_sp_span_entry_tunnel_parms_common(l3edev, saddr, daddr, gw,
 						       tparm.hop_limit,
-						       &nd_tbl, sparmsp);
+						       tbl, sparmsp);
 }
 
 static int
diff --git a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
index ca30702f88780..d650d33e3709c 100644
--- a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
+++ b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
@@ -209,6 +209,7 @@ void nfp_tunnel_keep_alive(struct nfp_app *app, struct sk_buff *skb)
 {
 	struct nfp_tun_active_tuns *payload;
 	struct net_device *netdev;
+	struct neigh_table *tbl;
 	int count, i, pay_len;
 	struct neighbour *n;
 	__be32 ipv4_addr;
@@ -235,7 +236,8 @@ void nfp_tunnel_keep_alive(struct nfp_app *app, struct sk_buff *skb)
 		if (!netdev)
 			continue;
 
-		n = neigh_lookup(&arp_tbl, &ipv4_addr, netdev);
+		tbl = arp_table(dev_net(netdev));
+		n = neigh_lookup(tbl, &ipv4_addr, netdev);
 		if (!n)
 			continue;
 
@@ -251,6 +253,7 @@ void nfp_tunnel_keep_alive_v6(struct nfp_app *app, struct sk_buff *skb)
 #if IS_ENABLED(CONFIG_IPV6)
 	struct nfp_tun_active_tuns_v6 *payload;
 	struct net_device *netdev;
+	struct neigh_table *tbl;
 	int count, i, pay_len;
 	struct neighbour *n;
 	void *ipv6_add;
@@ -277,7 +280,8 @@ void nfp_tunnel_keep_alive_v6(struct nfp_app *app, struct sk_buff *skb)
 		if (!netdev)
 			continue;
 
-		n = neigh_lookup(&nd_tbl, ipv6_add, netdev);
+		tbl = nd_table(dev_net(netdev));
+		n = neigh_lookup(tbl, ipv6_add, netdev);
 		if (!n)
 			continue;
 
@@ -729,12 +733,6 @@ nfp_tun_neigh_event_handler(struct notifier_block *nb, unsigned long event,
 	default:
 		return NOTIFY_DONE;
 	}
-#if IS_ENABLED(CONFIG_IPV6)
-	if (n->tbl != &nd_tbl && n->tbl != &arp_tbl)
-#else
-	if (n->tbl != &arp_tbl)
-#endif
-		return NOTIFY_DONE;
 
 	app_priv = container_of(nb, struct nfp_flower_priv, tun.neigh_nb);
 	app = app_priv->app;
diff --git a/drivers/net/ethernet/rocker/rocker_main.c b/drivers/net/ethernet/rocker/rocker_main.c
index 84a55f2b48ffe..0fcd65fc0af7f 100644
--- a/drivers/net/ethernet/rocker/rocker_main.c
+++ b/drivers/net/ethernet/rocker/rocker_main.c
@@ -3134,7 +3134,7 @@ static int rocker_netevent_event(struct notifier_block *unused,
 
 	switch (event) {
 	case NETEVENT_NEIGH_UPDATE:
-		if (n->tbl != &arp_tbl)
+		if (n->tbl != arp_table(&init_net))
 			return NOTIFY_DONE;
 		dev = n->dev;
 		if (!rocker_port_dev_check(dev))
diff --git a/drivers/net/ethernet/rocker/rocker_ofdpa.c b/drivers/net/ethernet/rocker/rocker_ofdpa.c
index 15d19a8a17101..ead8b447b89c4 100644
--- a/drivers/net/ethernet/rocker/rocker_ofdpa.c
+++ b/drivers/net/ethernet/rocker/rocker_ofdpa.c
@@ -1336,7 +1336,7 @@ static int ofdpa_port_ipv4_resolve(struct ofdpa_port *ofdpa_port,
 	int err = 0;
 
 	if (!n) {
-		n = neigh_create(&arp_tbl, &ip_addr, dev);
+		n = neigh_create(arp_table(&init_net), &ip_addr, dev);
 		if (IS_ERR(n))
 			return PTR_ERR(n);
 	}
diff --git a/drivers/net/ethernet/sfc/tc_counters.c b/drivers/net/ethernet/sfc/tc_counters.c
index b84235e93ffe9..793a562fc1f7c 100644
--- a/drivers/net/ethernet/sfc/tc_counters.c
+++ b/drivers/net/ethernet/sfc/tc_counters.c
@@ -91,6 +91,7 @@ static void efx_tc_counter_work(struct work_struct *work)
 	struct efx_tc_action_set *act;
 	unsigned long touched;
 	struct neighbour *n;
+	struct net *net;
 
 	spin_lock_bh(&cnt->lock);
 	touched = READ_ONCE(cnt->touched);
@@ -103,16 +104,19 @@ static void efx_tc_counter_work(struct work_struct *work)
 			continue;
 		if (time_after_eq(encap->neigh->used, touched))
 			continue;
+
 		encap->neigh->used = touched;
+		net = encap->neigh->net;
+
 		/* We have passed traffic using this ARP entry, so
 		 * indicate to the ARP cache that it's still active
 		 */
 		if (encap->neigh->dst_ip)
-			n = neigh_lookup(&arp_tbl, &encap->neigh->dst_ip,
+			n = neigh_lookup(arp_table(net), &encap->neigh->dst_ip,
 					 encap->neigh->egdev);
 		else
 #if IS_ENABLED(CONFIG_IPV6)
-			n = neigh_lookup(&nd_tbl,
+			n = neigh_lookup(nd_table(net),
 					 &encap->neigh->dst_ip6,
 					 encap->neigh->egdev);
 #else
diff --git a/drivers/net/ethernet/sfc/tc_encap_actions.c b/drivers/net/ethernet/sfc/tc_encap_actions.c
index c2ad3a358d201..152c4639ecc6f 100644
--- a/drivers/net/ethernet/sfc/tc_encap_actions.c
+++ b/drivers/net/ethernet/sfc/tc_encap_actions.c
@@ -527,10 +527,10 @@ static int efx_neigh_event(struct efx_nic *efx, struct neighbour *n)
 	if (WARN_ON(!efx->tc))
 		return NOTIFY_DONE;
 
-	if (n->tbl == &arp_tbl) {
+	if (n->tbl->family == AF_INET) {
 		keysize = sizeof(keys.dst_ip);
 #if IS_ENABLED(CONFIG_IPV6)
-	} else if (n->tbl == &nd_tbl) {
+	} else if (n->tbl->family == AF_INET6) {
 		ipv6 = true;
 		keysize = sizeof(keys.dst_ip6);
 #endif
diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c
index a0557a3a70260..50ec04cc0213f 100644
--- a/drivers/net/vrf.c
+++ b/drivers/net/vrf.c
@@ -616,7 +616,7 @@ static int vrf_finish_output6(struct net *net, struct sock *sk,
 	nexthop = rt6_nexthop(dst_rt6_info(dst), &ipv6_hdr(skb)->daddr);
 	neigh = __ipv6_neigh_lookup_noref(dst->dev, nexthop);
 	if (unlikely(!neigh))
-		neigh = __neigh_create(&nd_tbl, nexthop, dst->dev, false);
+		neigh = __neigh_create(nd_table(net), nexthop, dst->dev, false);
 	if (!IS_ERR(neigh)) {
 		sock_confirm_neigh(skb, neigh);
 		ret = neigh_output(neigh, skb, false);
diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c
index fd5505153d0b6..345d1883d993e 100644
--- a/drivers/net/vxlan/vxlan_core.c
+++ b/drivers/net/vxlan/vxlan_core.c
@@ -1841,11 +1841,12 @@ static int vxlan_err_lookup(struct sock *sk, struct sk_buff *skb)
 
 static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 {
+	struct neigh_table *tbl = arp_table(dev_net(dev));
 	struct vxlan_dev *vxlan = netdev_priv(dev);
+	struct neighbour *n;
 	struct arphdr *parp;
 	u8 *arpptr, *sha;
 	__be32 sip, tip;
-	struct neighbour *n;
 
 	if (dev->flags & IFF_NOARP)
 		goto out;
@@ -1877,7 +1878,7 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 	    ipv4_is_multicast(tip))
 		goto out;
 
-	n = neigh_lookup(&arp_tbl, &tip, dev);
+	n = neigh_lookup(tbl, &tip, dev);
 
 	if (n) {
 		struct vxlan_rdst *rdst = NULL;
@@ -2047,7 +2048,7 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 	    ipv6_addr_is_multicast(&msg->target))
 		goto out;
 
-	n = neigh_lookup(&nd_tbl, &msg->target, dev);
+	n = neigh_lookup(nd_table(dev_net(dev)), &msg->target, dev);
 
 	if (n) {
 		struct vxlan_rdst *rdst = NULL;
@@ -2100,6 +2101,7 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)
 {
 	struct vxlan_dev *vxlan = netdev_priv(dev);
+	struct neigh_table *tbl;
 	struct neighbour *n;
 
 	if (is_multicast_ether_addr(eth_hdr(skb)->h_dest))
@@ -2113,8 +2115,10 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)
 
 		if (!pskb_network_may_pull(skb, sizeof(struct iphdr)))
 			return false;
+
+		tbl = arp_table(dev_net(dev));
 		pip = ip_hdr(skb);
-		n = neigh_lookup(&arp_tbl, &pip->daddr, dev);
+		n = neigh_lookup(tbl, &pip->daddr, dev);
 		if (!n && (vxlan->cfg.flags & VXLAN_F_L3MISS)) {
 			union vxlan_addr ipa = {
 				.sin.sin_addr.s_addr = pip->daddr,
@@ -2139,8 +2143,10 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)
 			return false;
 		if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr)))
 			return false;
+
+		tbl = nd_table(dev_net(dev));
 		pip6 = ipv6_hdr(skb);
-		n = neigh_lookup(&nd_tbl, &pip6->daddr, dev);
+		n = neigh_lookup(tbl, &pip6->daddr, dev);
 		if (!n && (vxlan->cfg.flags & VXLAN_F_L3MISS)) {
 			union vxlan_addr ipa = {
 				.sin6.sin6_addr = pip6->daddr,
diff --git a/include/net/arp.h b/include/net/arp.h
index e8747e0713c79..e932def63d62f 100644
--- a/include/net/arp.h
+++ b/include/net/arp.h
@@ -7,8 +7,10 @@
 #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)
 {
@@ -21,10 +23,12 @@ static inline u32 arp_hashfn(const void *pkey, const struct net_device *dev, u32
 #ifdef CONFIG_INET
 static inline struct neighbour *__ipv4_neigh_lookup_noref(struct net_device *dev, u32 key)
 {
+	struct neigh_table *tbl = arp_table(dev_net(dev));
+
 	if (dev->flags & (IFF_LOOPBACK | IFF_POINTOPOINT))
 		key = INADDR_ANY;
 
-	return ___neigh_lookup_noref(&arp_tbl, neigh_key_eq32, arp_hashfn, &key, dev);
+	return ___neigh_lookup_noref(tbl, neigh_key_eq32, arp_hashfn, &key, dev);
 }
 #else
 static inline
diff --git a/include/net/ndisc.h b/include/net/ndisc.h
index 9e5379ad2d8e0..96e3bb6e83afd 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;
@@ -354,7 +363,9 @@ static inline u32 ndisc_hashfn(const void *pkey, const struct net_device *dev, _
 
 static inline struct neighbour *__ipv6_neigh_lookup_noref(struct net_device *dev, const void *pkey)
 {
-	return ___neigh_lookup_noref(&nd_tbl, neigh_key_eq128, ndisc_hashfn, pkey, dev);
+	struct neigh_table *tbl = nd_table(dev_net(dev));
+
+	return ___neigh_lookup_noref(tbl, neigh_key_eq128, ndisc_hashfn, pkey, dev);
 }
 
 static inline struct neighbour *__ipv6_neigh_lookup(struct net_device *dev, const void *pkey)
@@ -388,8 +399,11 @@ static inline struct neighbour *ip_neigh_gw6(struct net_device *dev,
 	struct neighbour *neigh;
 
 	neigh = __ipv6_neigh_lookup_noref(dev, addr);
-	if (unlikely(!neigh))
-		neigh = __neigh_create(&nd_tbl, addr, dev, false);
+	if (unlikely(!neigh)) {
+		struct neigh_table *tbl = nd_table(dev_net(dev));
+
+		neigh = __neigh_create(tbl, addr, dev, false);
+	}
 
 	return neigh;
 #else
diff --git a/include/net/neighbour.h b/include/net/neighbour.h
index 8860cc2175fc1..ed9d9ae6d3a65 100644
--- a/include/net/neighbour.h
+++ b/include/net/neighbour.h
@@ -31,6 +31,8 @@
 #include <net/rtnetlink.h>
 #include <net/neighbour_tables.h>
 
+extern int sysctl_neigh_inherit_init_net;
+
 /*
  * NUD stands for "neighbor unreachability detection"
  */
@@ -179,7 +181,6 @@ struct neigh_ops {
 
 struct pneigh_entry {
 	struct pneigh_entry	__rcu *next;
-	possible_net_t		net;
 	struct net_device	*dev;
 	netdevice_tracker	dev_tracker;
 	union {
@@ -234,7 +235,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;
@@ -339,8 +340,8 @@ static inline void neigh_confirm(struct neighbour *n)
 	}
 }
 
-void neigh_table_init(int index, struct neigh_table *tbl);
-int neigh_table_clear(int index, struct neigh_table *tbl);
+int neigh_table_register(struct net *net, struct neigh_table *tbl, int index);
+void neigh_table_unregister(struct net *net, int index);
 struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
 			       struct net_device *dev);
 struct neighbour *__neigh_create(struct neigh_table *tbl, const void *pkey,
@@ -389,24 +390,17 @@ static inline void neigh_set_reach_time(struct neigh_parms *p)
 
 void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,
 		    struct sk_buff *skb);
-struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl, struct net *net,
+struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl,
 				   const void *key, struct net_device *dev);
-int pneigh_create(struct neigh_table *tbl, struct net *net, const void *key,
+int pneigh_create(struct neigh_table *tbl, const void *key,
 		  struct net_device *dev, u32 flags, u8 protocol,
 		  bool permanent);
-int pneigh_delete(struct neigh_table *tbl, struct net *net, const void *key,
+int pneigh_delete(struct neigh_table *tbl, const void *key,
 		  struct net_device *dev);
 
-static inline struct net *pneigh_net(const struct pneigh_entry *pneigh)
-{
-	return read_pnet(&pneigh->net);
-}
-
 void neigh_app_ns(struct neighbour *n);
 void neigh_for_each(struct neigh_table *tbl,
 		    void (*cb)(struct neighbour *, void *), void *cookie);
-void __neigh_for_each_release(struct neigh_table *tbl,
-			      int (*cb)(struct neighbour *));
 int neigh_xmit(int fam, struct net_device *, const void *, struct sk_buff *);
 
 struct neigh_seq_state {
diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h
index 501af1999fe83..f96a390ae5364 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/include/net/route.h b/include/net/route.h
index f90106f383c56..38a23f28ee14a 100644
--- a/include/net/route.h
+++ b/include/net/route.h
@@ -403,8 +403,11 @@ static inline struct neighbour *ip_neigh_gw4(struct net_device *dev,
 	struct neighbour *neigh;
 
 	neigh = __ipv4_neigh_lookup_noref(dev, (__force u32)daddr);
-	if (unlikely(!neigh))
-		neigh = __neigh_create(&arp_tbl, &daddr, dev, false);
+	if (unlikely(!neigh)) {
+		struct neigh_table *tbl = arp_table(dev_net(dev));
+
+		neigh = __neigh_create(tbl, &daddr, dev, false);
+	}
 
 	return neigh;
 }
diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
index b6e5a86b6a92a..0df84d3ccd906 100644
--- a/net/bridge/br_arp_nd_proxy.c
+++ b/net/bridge/br_arp_nd_proxy.c
@@ -193,7 +193,7 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,
 		return;
 	}
 
-	n = neigh_lookup(&arp_tbl, &tip, vlandev);
+	n = neigh_lookup(arp_table(dev_net(vlandev)), &tip, vlandev);
 	if (n) {
 		struct net_bridge_fdb_entry *f;
 
@@ -468,7 +468,7 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
 		return;
 	}
 
-	n = neigh_lookup(&nd_tbl, &msg->target, vlandev);
+	n = neigh_lookup(nd_table(dev_net(vlandev)), &msg->target, vlandev);
 	if (n) {
 		struct net_bridge_fdb_entry *f;
 
diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index 1349c0eedb642..9b28f40b019aa 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;
@@ -131,10 +148,11 @@ unsigned long neigh_rand_reach_time(unsigned long base)
 {
 	return base ? get_random_u32_below(base) + (base >> 1) : 0;
 }
-EXPORT_SYMBOL(neigh_rand_reach_time);
 
 static void neigh_mark_dead(struct neighbour *n)
 {
+	lockdep_assert_held(&n->tbl->lock);
+
 	n->dead = 1;
 	if (!list_empty(&n->gc_list)) {
 		list_del_init(&n->gc_list);
@@ -148,11 +166,6 @@ static void neigh_update_gc_list(struct neighbour *n)
 {
 	bool on_gc_list, exempt_from_gc;
 
-	spin_lock_bh(&n->tbl->lock);
-	write_lock(&n->lock);
-	if (n->dead)
-		goto out;
-
 	/* remove from the gc list if new state is permanent or if neighbor is
 	 * externally learned / validated; otherwise entry should be on the gc
 	 * list
@@ -169,20 +182,12 @@ static void neigh_update_gc_list(struct neighbour *n)
 		list_add_tail(&n->gc_list, &n->tbl->gc_list);
 		atomic_inc(&n->tbl->gc_entries);
 	}
-out:
-	write_unlock(&n->lock);
-	spin_unlock_bh(&n->tbl->lock);
 }
 
 static void neigh_update_managed_list(struct neighbour *n)
 {
 	bool on_managed_list, add_to_managed;
 
-	spin_lock_bh(&n->tbl->lock);
-	write_lock(&n->lock);
-	if (n->dead)
-		goto out;
-
 	add_to_managed = n->flags & NTF_MANAGED;
 	on_managed_list = !list_empty(&n->managed_list);
 
@@ -190,9 +195,6 @@ static void neigh_update_managed_list(struct neighbour *n)
 		list_del_init(&n->managed_list);
 	else if (add_to_managed && !on_managed_list)
 		list_add_tail(&n->managed_list, &n->tbl->managed_list);
-out:
-	write_unlock(&n->lock);
-	spin_unlock_bh(&n->tbl->lock);
 }
 
 static void neigh_update_flags(struct neighbour *neigh, u32 flags, int *notify,
@@ -349,8 +351,7 @@ static void neigh_parms_qlen_dec(struct net_device *dev, int family)
 	rcu_read_unlock();
 }
 
-static void pneigh_queue_purge(struct sk_buff_head *list, struct net *net,
-			       int family)
+static void pneigh_queue_purge(struct sk_buff_head *list, int family)
 {
 	struct sk_buff_head tmp;
 	unsigned long flags;
@@ -361,13 +362,11 @@ static void pneigh_queue_purge(struct sk_buff_head *list, struct net *net,
 	skb = skb_peek(list);
 	while (skb != NULL) {
 		struct sk_buff *skb_next = skb_peek_next(skb, list);
-		struct net_device *dev = skb->dev;
 
-		if (net == NULL || net_eq(dev_net(dev), net)) {
-			neigh_parms_qlen_dec(dev, family);
-			__skb_unlink(skb, list);
-			__skb_queue_tail(&tmp, skb);
-		}
+		neigh_parms_qlen_dec(skb->dev, family);
+		__skb_unlink(skb, list);
+		__skb_queue_tail(&tmp, skb);
+
 		skb = skb_next;
 	}
 	spin_unlock_irqrestore(&list->lock, flags);
@@ -471,8 +470,7 @@ static int __neigh_ifdown(struct neigh_table *tbl, struct net_device *dev,
 	spin_unlock_bh(&tbl->lock);
 
 	pneigh_ifdown(tbl, dev, skip_perm);
-	pneigh_queue_purge(&tbl->proxy_queue, dev ? dev_net(dev) : NULL,
-			   tbl->family);
+	pneigh_queue_purge(&tbl->proxy_queue, tbl->family);
 	if (skb_queue_empty_lockless(&tbl->proxy_queue))
 		timer_delete_sync(&tbl->proxy_timer);
 	return 0;
@@ -537,7 +535,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;
 
@@ -687,7 +685,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);
@@ -752,8 +750,7 @@ static u32 pneigh_hash(const void *pkey, unsigned int key_len)
 }
 
 struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl,
-				   struct net *net, const void *pkey,
-				   struct net_device *dev)
+				   const void *pkey, struct net_device *dev)
 {
 	struct pneigh_entry *n;
 	unsigned int key_len;
@@ -766,7 +763,6 @@ struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl,
 
 	while (n) {
 		if (!memcmp(n->key, pkey, key_len) &&
-		    net_eq(pneigh_net(n), net) &&
 		    (n->dev == dev || !n->dev))
 			return n;
 
@@ -776,7 +772,7 @@ struct pneigh_entry *pneigh_lookup(struct neigh_table *tbl,
 	return NULL;
 }
 
-int pneigh_create(struct neigh_table *tbl, struct net *net,
+int pneigh_create(struct neigh_table *tbl,
 		  const void *pkey, struct net_device *dev,
 		  u32 flags, u8 protocol, bool permanent)
 {
@@ -787,7 +783,7 @@ int pneigh_create(struct neigh_table *tbl, struct net *net,
 
 	mutex_lock(&tbl->phash_lock);
 
-	n = pneigh_lookup(tbl, net, pkey, dev);
+	n = pneigh_lookup(tbl, pkey, dev);
 	if (n)
 		goto update;
 
@@ -798,7 +794,6 @@ int pneigh_create(struct neigh_table *tbl, struct net *net,
 		goto out;
 	}
 
-	write_pnet(&n->net, net);
 	memcpy(n->key, pkey, key_len);
 	n->dev = dev;
 	netdev_hold(dev, &n->dev_tracker, GFP_KERNEL);
@@ -831,7 +826,7 @@ static void pneigh_destroy(struct rcu_head *rcu)
 	kfree(n);
 }
 
-int pneigh_delete(struct neigh_table *tbl, struct net *net, const void *pkey,
+int pneigh_delete(struct neigh_table *tbl, const void *pkey,
 		  struct net_device *dev)
 {
 	struct pneigh_entry *n, __rcu **np;
@@ -846,8 +841,7 @@ int pneigh_delete(struct neigh_table *tbl, struct net *net, const void *pkey,
 	for (np = &tbl->phash_buckets[hash_val];
 	     (n = rcu_dereference_protected(*np, 1)) != NULL;
 	     np = &n->next) {
-		if (!memcmp(n->key, pkey, key_len) && n->dev == dev &&
-		    net_eq(pneigh_net(n), net)) {
+		if (!memcmp(n->key, pkey, key_len) && n->dev == dev) {
 			rcu_assign_pointer(*np, n->next);
 
 			mutex_unlock(&tbl->phash_lock);
@@ -939,7 +933,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);
@@ -994,7 +988,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++) {
@@ -1523,10 +1517,23 @@ static int __neigh_update(struct neighbour *neigh, const u8 *lladdr,
 
 	write_unlock_bh(&neigh->lock);
 
-	if (((new ^ old) & NUD_PERMANENT) || gc_update)
-		neigh_update_gc_list(neigh);
-	if (managed_update)
-		neigh_update_managed_list(neigh);
+	gc_update |= !!((new ^ old) & NUD_PERMANENT);
+	if (gc_update || managed_update) {
+		spin_lock_bh(&neigh->tbl->lock);
+
+		if (!neigh->dead) {
+			write_lock(&neigh->lock);
+
+			if (gc_update)
+				neigh_update_gc_list(neigh);
+			if (managed_update)
+				neigh_update_managed_list(neigh);
+
+			write_unlock(&neigh->lock);
+		}
+
+		spin_unlock_bh(&neigh->tbl->lock);
+	}
 
 	if (notify)
 		call_netevent_notifiers(NETEVENT_NEIGH_UPDATE, neigh);
@@ -1540,7 +1547,6 @@ int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
 {
 	return __neigh_update(neigh, lladdr, new, flags, nlmsg_pid, NULL);
 }
-EXPORT_SYMBOL(neigh_update);
 
 /* Update the neigh to listen temporarily for probe responses, even if it is
  * in a NUD_FAILED state. The caller has to hold neigh->lock for writing.
@@ -1558,7 +1564,6 @@ void __neigh_set_probe_once(struct neighbour *neigh)
 			jiffies + max(NEIGH_VAR(neigh->parms, RETRANS_TIME),
 				      HZ/100));
 }
-EXPORT_SYMBOL(__neigh_set_probe_once);
 
 struct neighbour *neigh_event_ns(struct neigh_table *tbl,
 				 u8 *lladdr, void *saddr,
@@ -1571,7 +1576,6 @@ struct neighbour *neigh_event_ns(struct neigh_table *tbl,
 			     NEIGH_UPDATE_F_OVERRIDE, 0);
 	return neigh;
 }
-EXPORT_SYMBOL(neigh_event_ns);
 
 /* called with read_lock_bh(&n->lock); */
 static void neigh_hh_init(struct neighbour *n)
@@ -1624,7 +1628,6 @@ int neigh_resolve_output(struct neighbour *neigh, struct sk_buff *skb)
 	kfree_skb_reason(skb, SKB_DROP_REASON_NEIGH_HH_FILLFAIL);
 	goto out;
 }
-EXPORT_SYMBOL(neigh_resolve_output);
 
 /* As fast as possible without hh cache */
 
@@ -1741,16 +1744,15 @@ void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,
 	mod_timer(&tbl->proxy_timer, sched_next);
 	spin_unlock(&tbl->proxy_queue.lock);
 }
-EXPORT_SYMBOL(pneigh_enqueue);
 
 static inline struct neigh_parms *lookup_neigh_parms(struct neigh_table *tbl,
-						      struct net *net, int ifindex)
+						     int ifindex)
 {
 	struct neigh_parms *p;
 
 	list_for_each_entry(p, &tbl->parms_list, list) {
-		if ((p->dev && p->dev->ifindex == ifindex && net_eq(neigh_parms_net(p), net)) ||
-		    (!p->dev && !ifindex && net_eq(net, &init_net)))
+		if ((p->dev && p->dev->ifindex == ifindex) ||
+		    (!p->dev && !ifindex))
 			return p;
 	}
 
@@ -1789,7 +1791,6 @@ struct neigh_parms *neigh_parms_alloc(struct net_device *dev,
 	}
 	return p;
 }
-EXPORT_SYMBOL(neigh_parms_alloc);
 
 static void neigh_rcu_free_parms(struct rcu_head *head)
 {
@@ -1812,113 +1813,169 @@ void neigh_parms_release(struct neigh_table *tbl, struct neigh_parms *parms)
 	netdev_put(parms->dev, &parms->dev_tracker);
 	call_rcu(&parms->rcu_head, neigh_rcu_free_parms);
 }
-EXPORT_SYMBOL(neigh_parms_release);
 
 static struct lock_class_key neigh_table_proxy_queue_class;
 
-static struct neigh_table __rcu *neigh_tables[NEIGH_NR_TABLES] __read_mostly;
-
-void neigh_table_init(int index, struct neigh_table *tbl)
+static int neigh_table_init(struct net *net, struct neigh_table *tbl)
 {
 	unsigned long now = jiffies;
 	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_net_data(tbl->id, 0, net->proc_net_stat,
+				  &neigh_stat_seq_ops,
+				  sizeof(struct seq_net_private), 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;
 
+	refcount_set(&tbl->entries, 1);
 	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);
-
-	tbl->last_flush = now;
-	tbl->last_rand	= now + tbl->parms.reachable_time * 20;
+	return 0;
 
-	rcu_assign_pointer(neigh_tables[index], tbl);
+#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:
+	return -ENOMEM;
 }
 
-/*
- * Only called from ndisc_cleanup(), which means this is dead code
- * because we no longer can unload IPv6 module.
- */
-int neigh_table_clear(int index, struct neigh_table *tbl)
+static void neigh_table_free(struct neigh_table *tbl)
 {
-	RCU_INIT_POINTER(neigh_tables[index], NULL);
-	synchronize_rcu();
+	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);
 
-	/* It is not clean... Fix it to unload IPv6 module safely */
+	kfree(tbl);
+}
+
+static void neigh_table_clear(struct net *net, struct neigh_table *tbl)
+{
 	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");
+	remove_proc_entry(tbl->id, net->proc_net_stat);
 
-	call_rcu(&rcu_dereference_protected(tbl->nht, 1)->rcu,
-		 neigh_hash_free_rcu);
-	tbl->nht = NULL;
+	neigh_table_put(tbl);
+}
 
-	kfree(tbl->phash_buckets);
-	tbl->phash_buckets = NULL;
+static void neigh_table_inherit(struct net *net, struct neigh_table *tbl,
+				int index)
+{
+	const struct neigh_table *init_tbl = init_net.neigh_tables[index];
+	bool inherit = READ_ONCE(sysctl_neigh_inherit_init_net);
+	int i;
 
-	remove_proc_entry(tbl->id, init_net.proc_net_stat);
+	if (net_eq(net, &init_net) || !inherit)
+		return;
 
-	free_percpu(tbl->stats);
-	tbl->stats = NULL;
+	tbl->gc_interval = READ_ONCE(init_tbl->gc_interval);
+	tbl->gc_thresh1 = READ_ONCE(init_tbl->gc_thresh1);
+	tbl->gc_thresh2 = READ_ONCE(init_tbl->gc_thresh2);
+	tbl->gc_thresh3 = READ_ONCE(init_tbl->gc_thresh3);
+
+	for (i = 0; i < NEIGH_VAR_DATA_MAX; i++)
+		tbl->parms.data[i] = READ_ONCE(init_tbl->parms.data[i]);
+}
+
+int neigh_table_register(struct net *net, struct neigh_table *tbl, int index)
+{
+	int err;
+
+	tbl = kmemdup(tbl, sizeof(*tbl), GFP_KERNEL);
+	if (!tbl) {
+		err = -ENOMEM;
+		goto err;
+	}
+
+	neigh_table_inherit(net, tbl, index);
+
+	err = neigh_table_init(net, tbl);
+	if (err)
+		goto free_table;
+
+	net->neigh_tables[index] = tbl;
 
 	return 0;
+
+free_table:
+	kfree(tbl);
+err:
+	return err;
+}
+
+void neigh_table_unregister(struct net *net, int index)
+{
+	struct neigh_table *tbl = net->neigh_tables[index];
+
+	net->neigh_tables[index] = NULL;
+	neigh_table_clear(net, tbl);
 }
 
-static struct neigh_table *neigh_find_table(int family)
+static struct neigh_table *neigh_find_table(struct net *net, int family)
 {
 	struct neigh_table *tbl = NULL;
 
 	switch (family) {
 	case AF_INET:
-		tbl = rcu_dereference_rtnl(neigh_tables[NEIGH_ARP_TABLE]);
+		tbl = net->neigh_tables[NEIGH_ARP_TABLE];
 		break;
 	case AF_INET6:
-		tbl = rcu_dereference_rtnl(neigh_tables[NEIGH_ND_TABLE]);
+		tbl = net->neigh_tables[NEIGH_ND_TABLE];
 		break;
 	}
 
@@ -1972,7 +2029,7 @@ static int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh,
 		}
 	}
 
-	tbl = neigh_find_table(ndm->ndm_family);
+	tbl = neigh_find_table(net, ndm->ndm_family);
 	if (tbl == NULL)
 		return -EAFNOSUPPORT;
 
@@ -1982,7 +2039,7 @@ static int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh,
 	}
 
 	if (ndm->ndm_flags & NTF_PROXY) {
-		err = pneigh_delete(tbl, net, nla_data(dst_attr), dev);
+		err = pneigh_delete(tbl, nla_data(dst_attr), dev);
 		goto out;
 	}
 
@@ -2058,7 +2115,7 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh,
 		}
 	}
 
-	tbl = neigh_find_table(ndm->ndm_family);
+	tbl = neigh_find_table(net, ndm->ndm_family);
 	if (tbl == NULL)
 		return -EAFNOSUPPORT;
 
@@ -2078,7 +2135,7 @@ static int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh,
 			goto out;
 		}
 
-		err = pneigh_create(tbl, net, dst, dev, ndm_flags, protocol,
+		err = pneigh_create(tbl, dst, dev, ndm_flags, protocol,
 				    !!(ndm->ndm_state & NUD_PERMANENT));
 		goto out;
 	}
@@ -2267,7 +2324,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),
@@ -2400,10 +2457,8 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,
 
 	ndtmsg = nlmsg_data(nlh);
 
-	rcu_read_lock();
-
 	for (tidx = 0; tidx < NEIGH_NR_TABLES; tidx++) {
-		tbl = rcu_dereference(neigh_tables[tidx]);
+		tbl = net->neigh_tables[tidx];
 		if (!tbl)
 			continue;
 
@@ -2417,7 +2472,6 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,
 	}
 
 	if (!found) {
-		rcu_read_unlock();
 		err = -ENOENT;
 		goto errout;
 	}
@@ -2442,8 +2496,8 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,
 		if (tbp[NDTPA_IFINDEX])
 			ifindex = nla_get_u32(tbp[NDTPA_IFINDEX]);
 
-		p = lookup_neigh_parms(tbl, net, ifindex);
-		if (p == NULL) {
+		p = lookup_neigh_parms(tbl, ifindex);
+		if (!p) {
 			err = -ENOENT;
 			goto errout_tbl_lock;
 		}
@@ -2524,12 +2578,6 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,
 		}
 	}
 
-	err = -ENOENT;
-	if ((tb[NDTA_THRESH1] || tb[NDTA_THRESH2] ||
-	     tb[NDTA_THRESH3] || tb[NDTA_GC_INTERVAL]) &&
-	    !net_eq(net, &init_net))
-		goto errout_tbl_lock;
-
 	if (tb[NDTA_THRESH1])
 		WRITE_ONCE(tbl->gc_thresh1, nla_get_u32(tb[NDTA_THRESH1]));
 
@@ -2546,7 +2594,6 @@ static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh,
 
 errout_tbl_lock:
 	spin_unlock_bh(&tbl->lock);
-	rcu_read_unlock();
 errout:
 	return err;
 }
@@ -2598,7 +2645,7 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
 	for (tidx = 0; tidx < NEIGH_NR_TABLES; tidx++) {
 		struct neigh_parms *p;
 
-		tbl = rcu_dereference(neigh_tables[tidx]);
+		tbl = net->neigh_tables[tidx];
 		if (!tbl)
 			continue;
 
@@ -2613,9 +2660,6 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
 		nidx = 0;
 		p = list_next_entry(&tbl->parms, list);
 		list_for_each_entry_from_rcu(p, &tbl->parms_list, list) {
-			if (!net_eq(neigh_parms_net(p), net))
-				continue;
-
 			if (nidx < neigh_skip)
 				goto next;
 
@@ -2793,12 +2837,11 @@ static int neigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,
 			    struct netlink_callback *cb,
 			    struct neigh_dump_filter *filter)
 {
-	struct net *net = sock_net(skb->sk);
-	struct neighbour *n;
-	int err = 0, h, s_h = cb->args[1];
 	int idx, s_idx = idx = cb->args[2];
-	struct neigh_hash_table *nht;
+	int err = 0, h, s_h = cb->args[1];
 	unsigned int flags = NLM_F_MULTI;
+	struct neigh_hash_table *nht;
+	struct neighbour *n;
 
 	if (filter->dev_idx || filter->master_idx)
 		flags |= NLM_F_DUMP_FILTERED;
@@ -2810,7 +2853,7 @@ static int neigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,
 			s_idx = 0;
 		idx = 0;
 		neigh_for_each_in_bucket_rcu(n, &nht->hash_heads[h]) {
-			if (idx < s_idx || !net_eq(dev_net(n->dev), net))
+			if (idx < s_idx)
 				goto next;
 			if (neigh_ifindex_filtered(n->dev, filter->dev_idx) ||
 			    neigh_master_filtered(n->dev, filter->master_idx))
@@ -2834,11 +2877,10 @@ static int pneigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,
 			     struct netlink_callback *cb,
 			     struct neigh_dump_filter *filter)
 {
-	struct pneigh_entry *n;
-	struct net *net = sock_net(skb->sk);
-	int err = 0, h, s_h = cb->args[3];
 	int idx, s_idx = idx = cb->args[4];
+	int err = 0, h, s_h = cb->args[3];
 	unsigned int flags = NLM_F_MULTI;
+	struct pneigh_entry *n;
 
 	if (filter->dev_idx || filter->master_idx)
 		flags |= NLM_F_DUMP_FILTERED;
@@ -2849,7 +2891,7 @@ static int pneigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,
 		for (n = rcu_dereference(tbl->phash_buckets[h]), idx = 0;
 		     n;
 		     n = rcu_dereference(n->next)) {
-			if (idx < s_idx || pneigh_net(n) != net)
+			if (idx < s_idx)
 				goto next;
 			if (neigh_ifindex_filtered(n->dev, filter->dev_idx) ||
 			    neigh_master_filtered(n->dev, filter->master_idx))
@@ -2935,6 +2977,7 @@ static int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
 {
 	const struct nlmsghdr *nlh = cb->nlh;
 	struct neigh_dump_filter filter = {};
+	struct net *net = sock_net(skb->sk);
 	struct neigh_table *tbl;
 	int t, family, s_t;
 	int proxy = 0;
@@ -2958,8 +3001,7 @@ static int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
 
 	rcu_read_lock();
 	for (t = 0; t < NEIGH_NR_TABLES; t++) {
-		tbl = rcu_dereference(neigh_tables[t]);
-
+		tbl = net->neigh_tables[t];
 		if (!tbl)
 			continue;
 		if (t < s_t || (family && tbl->family != family))
@@ -3081,7 +3123,7 @@ static int neigh_get(struct sk_buff *in_skb, struct nlmsghdr *nlh,
 
 	rcu_read_lock();
 
-	tbl = neigh_find_table(ndm->ndm_family);
+	tbl = neigh_find_table(net, ndm->ndm_family);
 	if (!tbl) {
 		NL_SET_ERR_MSG(extack, "Unsupported family in header for neighbor get request");
 		err = -EAFNOSUPPORT;
@@ -3108,7 +3150,7 @@ static int neigh_get(struct sk_buff *in_skb, struct nlmsghdr *nlh,
 	if (ndm->ndm_flags & NTF_PROXY) {
 		struct pneigh_entry *pn;
 
-		pn = pneigh_lookup(tbl, net, dst, dev);
+		pn = pneigh_lookup(tbl, dst, dev);
 		if (!pn) {
 			NL_SET_ERR_MSG(extack, "Proxy neighbour entry not found");
 			err = -ENOENT;
@@ -3161,37 +3203,6 @@ void neigh_for_each(struct neigh_table *tbl, void (*cb)(struct neighbour *, void
 }
 EXPORT_SYMBOL(neigh_for_each);
 
-/* The tbl->lock must be held as a writer and BH disabled. */
-void __neigh_for_each_release(struct neigh_table *tbl,
-			      int (*cb)(struct neighbour *))
-{
-	struct neigh_hash_table *nht;
-	int chain;
-
-	nht = rcu_dereference_protected(tbl->nht,
-					lockdep_is_held(&tbl->lock));
-	for (chain = 0; chain < (1 << nht->hash_shift); chain++) {
-		struct hlist_node *tmp;
-		struct neighbour *n;
-
-		neigh_for_each_in_bucket_safe(n, tmp, &nht->hash_heads[chain]) {
-			int release;
-
-			write_lock(&n->lock);
-			release = cb(n);
-			if (release) {
-				hlist_del_rcu(&n->hash);
-				hlist_del_rcu(&n->dev_list);
-				neigh_mark_dead(n);
-			}
-			write_unlock(&n->lock);
-			if (release)
-				neigh_cleanup_and_release(n);
-		}
-	}
-}
-EXPORT_SYMBOL(__neigh_for_each_release);
-
 int neigh_xmit(int index, struct net_device *dev,
 	       const void *addr, struct sk_buff *skb)
 {
@@ -3200,9 +3211,11 @@ int neigh_xmit(int index, struct net_device *dev,
 	if (likely(index < NEIGH_NR_TABLES)) {
 		struct neigh_table *tbl;
 		struct neighbour *neigh;
+		struct net *net;
 
 		rcu_read_lock();
-		tbl = rcu_dereference(neigh_tables[index]);
+		net = dev_net_rcu(dev);
+		tbl = net->neigh_tables[index];
 		if (!tbl) {
 			rcu_read_unlock();
 			goto out_kfree_skb;
@@ -3245,10 +3258,6 @@ static struct neighbour *neigh_get_valid(struct seq_file *seq,
 					 loff_t *pos)
 {
 	struct neigh_seq_state *state = seq->private;
-	struct net *net = seq_file_net(seq);
-
-	if (!net_eq(dev_net(n->dev), net))
-		return NULL;
 
 	if (state->neigh_sub_iter) {
 		loff_t fakep = 0;
@@ -3337,7 +3346,6 @@ static struct neighbour *neigh_get_idx(struct seq_file *seq, loff_t *pos)
 static struct pneigh_entry *pneigh_get_first(struct seq_file *seq)
 {
 	struct neigh_seq_state *state = seq->private;
-	struct net *net = seq_file_net(seq);
 	struct neigh_table *tbl = state->tbl;
 	struct pneigh_entry *pn = NULL;
 	int bucket;
@@ -3345,9 +3353,6 @@ static struct pneigh_entry *pneigh_get_first(struct seq_file *seq)
 	state->flags |= NEIGH_SEQ_IS_PNEIGH;
 	for (bucket = 0; bucket <= PNEIGH_HASHMASK; bucket++) {
 		pn = rcu_dereference(tbl->phash_buckets[bucket]);
-
-		while (pn && !net_eq(pneigh_net(pn), net))
-			pn = rcu_dereference(pn->next);
 		if (pn)
 			break;
 	}
@@ -3361,21 +3366,15 @@ static struct pneigh_entry *pneigh_get_next(struct seq_file *seq,
 					    loff_t *pos)
 {
 	struct neigh_seq_state *state = seq->private;
-	struct net *net = seq_file_net(seq);
 	struct neigh_table *tbl = state->tbl;
 
-	do {
-		pn = rcu_dereference(pn->next);
-	} while (pn && !net_eq(pneigh_net(pn), net));
+	pn = rcu_dereference(pn->next);
 
 	while (!pn) {
 		if (++state->bucket > PNEIGH_HASHMASK)
 			break;
 
 		pn = rcu_dereference(tbl->phash_buckets[state->bucket]);
-
-		while (pn && !net_eq(pneigh_net(pn), net))
-			pn = rcu_dereference(pn->next);
 		if (pn)
 			break;
 	}
@@ -3430,7 +3429,6 @@ void *neigh_seq_start(struct seq_file *seq, loff_t *pos, struct neigh_table *tbl
 
 	return *pos ? neigh_get_idx_any(seq, pos) : SEQ_START_TOKEN;
 }
-EXPORT_SYMBOL(neigh_seq_start);
 
 void *neigh_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 {
@@ -3457,7 +3455,6 @@ void *neigh_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 	++(*pos);
 	return rc;
 }
-EXPORT_SYMBOL(neigh_seq_next);
 
 void neigh_seq_stop(struct seq_file *seq, void *v)
 	__releases(tbl->lock)
@@ -3469,7 +3466,6 @@ void neigh_seq_stop(struct seq_file *seq, void *v)
 	spin_unlock_bh(&tbl->lock);
 	rcu_read_unlock();
 }
-EXPORT_SYMBOL(neigh_seq_stop);
 
 /* statistics via seq_file */
 
@@ -3523,7 +3519,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,
@@ -3593,7 +3589,6 @@ void neigh_app_ns(struct neighbour *n)
 {
 	neigh_notify(n, RTM_GETNEIGH, NLM_F_REQUEST, 0);
 }
-EXPORT_SYMBOL(neigh_app_ns);
 
 #ifdef CONFIG_SYSCTL
 static int unres_qlen_max = INT_MAX / SKB_TRUESIZE(ETH_FRAME_LEN);
@@ -3689,7 +3684,6 @@ int neigh_proc_dointvec(const struct ctl_table *ctl, int write, void *buffer,
 	neigh_proc_update(ctl, write);
 	return ret;
 }
-EXPORT_SYMBOL(neigh_proc_dointvec);
 
 int neigh_proc_dointvec_jiffies(const struct ctl_table *ctl, int write, void *buffer,
 				size_t *lenp, loff_t *ppos)
@@ -3699,7 +3693,6 @@ int neigh_proc_dointvec_jiffies(const struct ctl_table *ctl, int write, void *bu
 	neigh_proc_update(ctl, write);
 	return ret;
 }
-EXPORT_SYMBOL(neigh_proc_dointvec_jiffies);
 
 static int neigh_proc_dointvec_userhz_jiffies(const struct ctl_table *ctl, int write,
 					      void *buffer, size_t *lenp,
@@ -3719,7 +3712,6 @@ int neigh_proc_dointvec_ms_jiffies(const struct ctl_table *ctl, int write,
 	neigh_proc_update(ctl, write);
 	return ret;
 }
-EXPORT_SYMBOL(neigh_proc_dointvec_ms_jiffies);
 
 static int neigh_proc_dointvec_unres_qlen(const struct ctl_table *ctl, int write,
 					  void *buffer, size_t *lenp,
@@ -3926,7 +3918,6 @@ int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p,
 err:
 	return -ENOBUFS;
 }
-EXPORT_SYMBOL(neigh_sysctl_register);
 
 void neigh_sysctl_unregister(struct neigh_parms *p)
 {
@@ -3937,7 +3928,6 @@ void neigh_sysctl_unregister(struct neigh_parms *p)
 		kfree(t);
 	}
 }
-EXPORT_SYMBOL(neigh_sysctl_unregister);
 
 #endif	/* CONFIG_SYSCTL */
 
diff --git a/net/core/sysctl_net_core.c b/net/core/sysctl_net_core.c
index b508618bfc123..9777b9ef58949 100644
--- a/net/core/sysctl_net_core.c
+++ b/net/core/sysctl_net_core.c
@@ -21,6 +21,7 @@
 
 #include <net/ip.h>
 #include <net/sock.h>
+#include <net/neighbour.h>
 #include <net/net_ratelimit.h>
 #include <net/busy_poll.h>
 #include <net/pkt_sched.h>
@@ -53,6 +54,8 @@ EXPORT_SYMBOL(sysctl_fb_tunnels_only_for_init_net);
 int sysctl_devconf_inherit_init_net __read_mostly;
 EXPORT_SYMBOL(sysctl_devconf_inherit_init_net);
 
+int sysctl_neigh_inherit_init_net __read_mostly = 1;
+
 #if IS_ENABLED(CONFIG_NET_FLOW_LIMIT) || IS_ENABLED(CONFIG_RPS)
 static int dump_cpumask(void *buffer, size_t *lenp, loff_t *ppos,
 			struct cpumask *mask)
@@ -676,6 +679,15 @@ static struct ctl_table net_core_table[] = {
 		.proc_handler	= proc_do_skb_defer_max,
 		.extra1		= SYSCTL_ZERO,
 	},
+	{
+		.procname	= "neigh_inherit_init_net",
+		.data		= &sysctl_neigh_inherit_init_net,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_minmax,
+		.extra1		= SYSCTL_ZERO,
+		.extra2		= SYSCTL_ONE,
+	},
 };
 
 static struct ctl_table netns_core_table[] = {
diff --git a/net/ieee802154/6lowpan/tx.c b/net/ieee802154/6lowpan/tx.c
index 4df76ff50699e..4f511476b992c 100644
--- a/net/ieee802154/6lowpan/tx.c
+++ b/net/ieee802154/6lowpan/tx.c
@@ -58,8 +58,9 @@ int lowpan_header_create(struct sk_buff *skb, struct net_device *ldev,
 		info->daddr.mode = IEEE802154_ADDR_SHORT;
 	} else {
 		__le16 short_addr = cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC);
+		struct neigh_table *tbl = nd_table(dev_net(ldev));
 
-		n = neigh_lookup(&nd_tbl, &hdr->daddr, ldev);
+		n = neigh_lookup(tbl, &hdr->daddr, ldev);
 		if (n) {
 			llneigh = lowpan_802154_neigh(neighbour_priv(n));
 			read_lock_bh(&n->lock);
diff --git a/net/ipv4/arp.c b/net/ipv4/arp.c
index d409f606aec0e..90bc53fb80905 100644
--- a/net/ipv4/arp.c
+++ b/net/ipv4/arp.c
@@ -149,7 +149,7 @@ static const struct neigh_ops arp_direct_ops = {
 	.connected_output =	neigh_direct_output,
 };
 
-struct neigh_table arp_tbl = {
+static struct neigh_table arp_tbl = {
 	.family		= AF_INET,
 	.key_len	= 4,
 	.protocol	= cpu_to_be16(ETH_P_IP),
@@ -182,7 +182,6 @@ struct neigh_table arp_tbl = {
 	.gc_thresh2	= 512,
 	.gc_thresh3	= 1024,
 };
-EXPORT_SYMBOL(arp_tbl);
 
 int arp_mc_map(__be32 addr, u8 *haddr, struct net_device *dev, int dir)
 {
@@ -222,14 +221,18 @@ static bool arp_key_eq(const struct neighbour *neigh, const void *pkey)
 
 static int arp_constructor(struct neighbour *neigh)
 {
-	__be32 addr;
 	struct net_device *dev = neigh->dev;
-	struct in_device *in_dev;
-	struct neigh_parms *parms;
+	struct net *net = dev_net(dev);
 	u32 inaddr_any = INADDR_ANY;
+	struct neigh_parms *parms;
+	struct in_device *in_dev;
+	struct neigh_table *tbl;
+	__be32 addr;
+
+	tbl = arp_table(net);
 
 	if (dev->flags & (IFF_LOOPBACK | IFF_POINTOPOINT))
-		memcpy(neigh->primary_key, &inaddr_any, arp_tbl.key_len);
+		memcpy(neigh->primary_key, &inaddr_any, tbl->key_len);
 
 	addr = *(__be32 *)neigh->primary_key;
 	rcu_read_lock();
@@ -239,7 +242,7 @@ static int arp_constructor(struct neighbour *neigh)
 		return -EINVAL;
 	}
 
-	neigh->type = inet_addr_type_dev_table(dev_net(dev), dev, addr);
+	neigh->type = inet_addr_type_dev_table(net, dev, addr);
 
 	parms = in_dev->arp_parms;
 	__neigh_parms_put(neigh->parms);
@@ -701,24 +704,24 @@ static bool arp_is_garp(struct net *net, struct net_device *dev,
 
 static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
 {
+	struct neigh_table *tbl = arp_table(net);
+	struct dst_entry *reply_dst = NULL;
 	struct net_device *dev = skb->dev;
-	struct in_device *in_dev = __in_dev_get_rcu(dev);
-	struct arphdr *arp;
+	unsigned char *sha, *tha = NULL;
+	struct in_device *in_dev;
+	u16 dev_type = dev->type;
 	unsigned char *arp_ptr;
+	bool is_garp = false;
+	struct neighbour *n;
+	struct arphdr *arp;
 	struct rtable *rt;
-	unsigned char *sha;
-	unsigned char *tha = NULL;
 	__be32 sip, tip;
-	u16 dev_type = dev->type;
 	int addr_type;
-	struct neighbour *n;
-	struct dst_entry *reply_dst = NULL;
-	bool is_garp = false;
 
 	/* arp_rcv below verifies the ARP header and verifies the device
 	 * is ARP'able.
 	 */
-
+	in_dev = __in_dev_get_rcu(dev);
 	if (!in_dev)
 		goto out_free_skb;
 
@@ -850,7 +853,7 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
 			if (!dont_send && IN_DEV_ARPFILTER(in_dev))
 				dont_send = arp_filter(sip, tip, dev);
 			if (!dont_send) {
-				n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
+				n = neigh_event_ns(tbl, sha, &sip, dev);
 				if (n) {
 					arp_send_dst(ARPOP_REPLY, ETH_P_ARP,
 						     sip, dev, tip, sha,
@@ -865,8 +868,8 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
 			    (arp_fwd_proxy(in_dev, dev, rt) ||
 			     arp_fwd_pvlan(in_dev, dev, rt, sip, tip) ||
 			     (rt->dst.dev != dev &&
-			      pneigh_lookup(&arp_tbl, net, &tip, dev)))) {
-				n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
+			      pneigh_lookup(tbl, &tip, dev)))) {
+				n = neigh_event_ns(tbl, sha, &sip, dev);
 				if (n)
 					neigh_release(n);
 
@@ -878,7 +881,7 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
 						     dev->dev_addr, sha,
 						     reply_dst);
 				} else {
-					pneigh_enqueue(&arp_tbl,
+					pneigh_enqueue(tbl,
 						       in_dev->arp_parms, skb);
 					goto out_free_dst;
 				}
@@ -889,7 +892,7 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
 
 	/* Update our ARP tables */
 
-	n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
+	n = __neigh_lookup(tbl, &sip, dev, 0);
 
 	addr_type = -1;
 	if (n || arp_accept(in_dev, sip)) {
@@ -910,7 +913,7 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
 			/* postpone calculation to as late as possible */
 			inet_addr_type_dev_table(net, dev, sip) ==
 				RTN_UNICAST)))))
-			n = __neigh_lookup(&arp_tbl, &sip, dev, 1);
+			n = __neigh_lookup(tbl, &sip, dev, 1);
 	}
 
 	if (n) {
@@ -1077,9 +1080,10 @@ static int arp_req_set_proxy(struct net *net, struct net_device *dev, int on)
 }
 
 static int arp_req_set_public(struct net *net, struct arpreq *r,
-		struct net_device *dev)
+			      struct net_device *dev)
 {
 	__be32 mask = ((struct sockaddr_in *)&r->arp_netmask)->sin_addr.s_addr;
+	struct neigh_table *tbl = arp_table(net);
 
 	if (!dev && (r->arp_flags & ATF_COM)) {
 		dev = dev_getbyhwaddr(net, r->arp_ha.sa_family,
@@ -1090,7 +1094,7 @@ static int arp_req_set_public(struct net *net, struct arpreq *r,
 	if (mask) {
 		__be32 ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
 
-		return pneigh_create(&arp_tbl, net, &ip, dev, 0, 0, false);
+		return pneigh_create(tbl, &ip, dev, 0, 0, false);
 	}
 
 	return arp_req_set_proxy(net, dev, 1);
@@ -1098,6 +1102,7 @@ static int arp_req_set_public(struct net *net, struct arpreq *r,
 
 static int arp_req_set(struct net *net, struct arpreq *r)
 {
+	struct neigh_table *tbl = arp_table(net);
 	struct neighbour *neigh;
 	struct net_device *dev;
 	__be32 ip;
@@ -1133,7 +1138,7 @@ static int arp_req_set(struct net *net, struct arpreq *r)
 
 	ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
 
-	neigh = __neigh_lookup_errno(&arp_tbl, &ip, dev);
+	neigh = __neigh_lookup_errno(tbl, &ip, dev);
 	err = PTR_ERR(neigh);
 	if (!IS_ERR(neigh)) {
 		unsigned int state = NUD_STALE;
@@ -1169,6 +1174,7 @@ static unsigned int arp_state_to_flags(struct neighbour *neigh)
 static int arp_req_get(struct net *net, struct arpreq *r)
 {
 	__be32 ip = ((struct sockaddr_in *) &r->arp_pa)->sin_addr.s_addr;
+	struct neigh_table *tbl = arp_table(net);
 	struct neighbour *neigh;
 	struct net_device *dev;
 
@@ -1179,7 +1185,7 @@ static int arp_req_get(struct net *net, struct arpreq *r)
 	if (IS_ERR(dev))
 		return PTR_ERR(dev);
 
-	neigh = neigh_lookup(&arp_tbl, &ip, dev);
+	neigh = neigh_lookup(tbl, &ip, dev);
 	if (!neigh)
 		return -ENXIO;
 
@@ -1204,10 +1210,11 @@ static int arp_req_get(struct net *net, struct arpreq *r)
 
 int arp_invalidate(struct net_device *dev, __be32 ip, bool force)
 {
-	struct neighbour *neigh = neigh_lookup(&arp_tbl, &ip, dev);
+	struct neigh_table *tbl = arp_table(dev_net(dev));
+	struct neighbour *neigh;
 	int err = -ENXIO;
-	struct neigh_table *tbl = &arp_tbl;
 
+	neigh = neigh_lookup(tbl, &ip, dev);
 	if (neigh) {
 		if ((READ_ONCE(neigh->nud_state) & NUD_VALID) && !force) {
 			neigh_release(neigh);
@@ -1228,14 +1235,15 @@ int arp_invalidate(struct net_device *dev, __be32 ip, bool force)
 }
 
 static int arp_req_delete_public(struct net *net, struct arpreq *r,
-		struct net_device *dev)
+				 struct net_device *dev)
 {
 	__be32 mask = ((struct sockaddr_in *)&r->arp_netmask)->sin_addr.s_addr;
+	struct neigh_table *tbl = arp_table(net);
 
 	if (mask) {
 		__be32 ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
 
-		return pneigh_delete(&arp_tbl, net, &ip, dev);
+		return pneigh_delete(tbl, &ip, dev);
 	}
 
 	return arp_req_set_proxy(net, dev, 0);
@@ -1325,18 +1333,22 @@ static int arp_netdev_event(struct notifier_block *this, unsigned long event,
 {
 	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
 	struct netdev_notifier_change_info *change_info;
+	struct net *net = dev_net(dev);
 	struct in_device *in_dev;
+	struct neigh_table *tbl;
 	bool evict_nocarrier;
 
+	tbl = arp_table(net);
+
 	switch (event) {
 	case NETDEV_CHANGEADDR:
-		neigh_changeaddr(&arp_tbl, dev);
-		rt_cache_flush(dev_net(dev));
+		neigh_changeaddr(tbl, dev);
+		rt_cache_flush(net);
 		break;
 	case NETDEV_CHANGE:
 		change_info = ptr;
 		if (change_info->flags_changed & IFF_NOARP)
-			neigh_changeaddr(&arp_tbl, dev);
+			neigh_changeaddr(tbl, dev);
 
 		in_dev = __in_dev_get_rtnl(dev);
 		if (!in_dev)
@@ -1345,7 +1357,7 @@ static int arp_netdev_event(struct notifier_block *this, unsigned long event,
 			evict_nocarrier = IN_DEV_ARP_EVICT_NOCARRIER(in_dev);
 
 		if (evict_nocarrier && !netif_carrier_ok(dev))
-			neigh_carrier_down(&arp_tbl, dev);
+			neigh_carrier_down(tbl, dev);
 		break;
 	default:
 		break;
@@ -1364,7 +1376,9 @@ static struct notifier_block arp_netdev_notifier = {
  */
 void arp_ifdown(struct net_device *dev)
 {
-	neigh_ifdown(&arp_tbl, dev);
+	struct neigh_table *tbl = arp_table(dev_net(dev));
+
+	neigh_ifdown(tbl, dev);
 }
 
 
@@ -1479,10 +1493,12 @@ static int arp_seq_show(struct seq_file *seq, void *v)
 
 static void *arp_seq_start(struct seq_file *seq, loff_t *pos)
 {
+	struct neigh_table *tbl = arp_table(seq_file_net(seq));
+
 	/* Don't want to confuse "arp -a" w/ magic entries,
 	 * so we tell the generic iterator to skip NUD_NOARP.
 	 */
-	return neigh_seq_start(seq, pos, &arp_tbl, NEIGH_SEQ_SKIP_NOARP);
+	return neigh_seq_start(seq, pos, tbl, NEIGH_SEQ_SKIP_NOARP);
 }
 
 static const struct seq_operations arp_seq_ops = {
@@ -1495,15 +1511,47 @@ 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
+#ifdef CONFIG_SYSCTL
+	err = neigh_sysctl_register(NULL, &arp_table(net)->parms, NULL);
+	if (err)
+		goto err_sysctl;
+#endif
+
 	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:
+#ifdef CONFIG_SYSCTL
+	neigh_sysctl_unregister(&arp_table(net)->parms);
+err_sysctl:
+#endif
+	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);
+#ifdef CONFIG_SYSCTL
+	neigh_sysctl_unregister(&arp_table(net)->parms);
+#endif
+	neigh_table_unregister(net, NEIGH_ARP_TABLE);
 }
 
 static struct pernet_operations arp_net_ops = {
@@ -1513,12 +1561,9 @@ static struct pernet_operations arp_net_ops = {
 
 void __init arp_init(void)
 {
-	neigh_table_init(NEIGH_ARP_TABLE, &arp_tbl);
+	if (register_pernet_subsys(&arp_net_ops))
+		panic("Cannot allocate arp table\n");
 
 	dev_add_pack(&arp_packet_type);
-	register_pernet_subsys(&arp_net_ops);
-#ifdef CONFIG_SYSCTL
-	neigh_sysctl_register(NULL, &arp_tbl.parms, NULL);
-#endif
 	register_netdevice_notifier(&arp_netdev_notifier);
 }
diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c
index 47ded0f607d4b..05de597ce086f 100644
--- a/net/ipv4/devinet.c
+++ b/net/ipv4/devinet.c
@@ -265,7 +265,9 @@ EXPORT_SYMBOL(in_dev_finish_destroy);
 
 static struct in_device *inetdev_init(struct net_device *dev)
 {
+	struct net *net = dev_net(dev);
 	struct in_device *in_dev;
+	struct neigh_table *tbl;
 	int err = -ENOMEM;
 
 	ASSERT_RTNL();
@@ -273,11 +275,12 @@ static struct in_device *inetdev_init(struct net_device *dev)
 	in_dev = kzalloc_obj(*in_dev);
 	if (!in_dev)
 		goto out;
-	memcpy(&in_dev->cnf, dev_net(dev)->ipv4.devconf_dflt,
-			sizeof(in_dev->cnf));
+
+	tbl = arp_table(net);
+	memcpy(&in_dev->cnf, net->ipv4.devconf_dflt, sizeof(in_dev->cnf));
 	in_dev->cnf.sysctl = NULL;
 	in_dev->dev = dev;
-	in_dev->arp_parms = neigh_parms_alloc(dev, &arp_tbl);
+	in_dev->arp_parms = neigh_parms_alloc(dev, tbl);
 	if (!in_dev->arp_parms)
 		goto out_kfree;
 	if (IPV4_DEVCONF(in_dev->cnf, FORWARDING))
@@ -291,7 +294,7 @@ static struct in_device *inetdev_init(struct net_device *dev)
 		err = devinet_sysctl_register(in_dev);
 		if (err) {
 			in_dev->dead = 1;
-			neigh_parms_release(&arp_tbl, in_dev->arp_parms);
+			neigh_parms_release(tbl, in_dev->arp_parms);
 			in_dev_put(in_dev);
 			in_dev = NULL;
 			goto out;
@@ -313,13 +316,12 @@ static struct in_device *inetdev_init(struct net_device *dev)
 
 static void inetdev_destroy(struct in_device *in_dev)
 {
-	struct net_device *dev;
+	struct net_device *dev = in_dev->dev;
+	struct net *net = dev_net(dev);
 	struct in_ifaddr *ifa;
 
 	ASSERT_RTNL();
 
-	dev = in_dev->dev;
-
 	in_dev->dead = 1;
 
 	RCU_INIT_POINTER(dev->ip_ptr, NULL);
@@ -332,7 +334,7 @@ static void inetdev_destroy(struct in_device *in_dev)
 	}
 
 	devinet_sysctl_unregister(in_dev);
-	neigh_parms_release(&arp_tbl, in_dev->arp_parms);
+	neigh_parms_release(arp_table(net), in_dev->arp_parms);
 	arp_ifdown(dev);
 
 	in_dev_put(in_dev);
diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c
index 78f84ae3ee120..cda150309dc6b 100644
--- a/net/ipv4/fib_semantics.c
+++ b/net/ipv4/fib_semantics.c
@@ -610,13 +610,14 @@ static int fib_detect_death(struct fib_info *fi, int order,
 			    int dflt)
 {
 	const struct fib_nh_common *nhc = fib_info_nhc(fi, 0);
-	struct neighbour *n;
+	struct net *net = fi->fib_net;
 	int state = NUD_NONE;
+	struct neighbour *n;
 
 	if (likely(nhc->nhc_gw_family == AF_INET))
-		n = neigh_lookup(&arp_tbl, &nhc->nhc_gw.ipv4, nhc->nhc_dev);
+		n = neigh_lookup(arp_table(net), &nhc->nhc_gw.ipv4, nhc->nhc_dev);
 	else if (IS_ENABLED(CONFIG_IPV6) && nhc->nhc_gw_family == AF_INET6)
-		n = neigh_lookup(&nd_tbl, &nhc->nhc_gw.ipv6, nhc->nhc_dev);
+		n = neigh_lookup(nd_table(net), &nhc->nhc_gw.ipv6, nhc->nhc_dev);
 	else
 		n = NULL;
 
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index fd688e1f879f5..b8b6b5d991294 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -788,7 +788,7 @@ static void __ip_do_redirect(struct rtable *rt, struct sk_buff *skb, struct flow
 
 	n = __ipv4_neigh_lookup(rt->dst.dev, (__force u32)new_gw);
 	if (!n)
-		n = neigh_create(&arp_tbl, &new_gw, rt->dst.dev);
+		n = neigh_create(arp_table(net), &new_gw, rt->dst.dev);
 	if (!IS_ERR(n)) {
 		if (!(READ_ONCE(n->nud_state) & NUD_VALID)) {
 			neigh_event_send(n, NULL);
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index f6fa2715b450b..f3be7a8f996dd 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -376,6 +376,8 @@ static int snmp6_alloc_dev(struct inet6_dev *idev)
 
 static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
 {
+	struct net *net = dev_net(dev);
+	struct neigh_table *tbl;
 	struct inet6_dev *ndev;
 	int err = -ENOMEM;
 
@@ -393,14 +395,15 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
 	ndev->dev = dev;
 	INIT_LIST_HEAD(&ndev->addr_list);
 	timer_setup(&ndev->rs_timer, addrconf_rs_timer, 0);
-	memcpy(&ndev->cnf, dev_net(dev)->ipv6.devconf_dflt, sizeof(ndev->cnf));
+	memcpy(&ndev->cnf, net->ipv6.devconf_dflt, sizeof(ndev->cnf));
 
 	if (ndev->cnf.stable_secret.initialized)
 		ndev->cnf.addr_gen_mode = IN6_ADDR_GEN_MODE_STABLE_PRIVACY;
 
+	tbl = nd_table(net);
 	ndev->cnf.mtu6 = dev->mtu;
 	ndev->ra_mtu = 0;
-	ndev->nd_parms = neigh_parms_alloc(dev, &nd_tbl);
+	ndev->nd_parms = neigh_parms_alloc(dev, tbl);
 	if (!ndev->nd_parms) {
 		kfree(ndev);
 		return ERR_PTR(err);
@@ -413,7 +416,7 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
 	if (snmp6_alloc_dev(ndev) < 0) {
 		netdev_dbg(dev, "%s: cannot allocate memory for statistics\n",
 			   __func__);
-		neigh_parms_release(&nd_tbl, ndev->nd_parms);
+		neigh_parms_release(tbl, ndev->nd_parms);
 		netdev_put(dev, &ndev->dev_tracker);
 		kfree(ndev);
 		return ERR_PTR(err);
@@ -481,7 +484,7 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
 	return ndev;
 
 err_release:
-	neigh_parms_release(&nd_tbl, ndev->nd_parms);
+	neigh_parms_release(tbl, ndev->nd_parms);
 	ndev->dead = 1;
 	in6_dev_finish_destroy(ndev);
 	return ERR_PTR(err);
@@ -4038,9 +4041,11 @@ static int addrconf_ifdown(struct net_device *dev, bool unregister)
 
 	/* Last: Shot the device (if unregistered) */
 	if (unregister) {
+		struct neigh_table *tbl = nd_table(net);
+
 		addrconf_sysctl_unregister(idev);
-		neigh_parms_release(&nd_tbl, idev->nd_parms);
-		neigh_ifdown(&nd_tbl, dev);
+		neigh_parms_release(tbl, idev->nd_parms);
+		neigh_ifdown(tbl, dev);
 		in6_dev_put(idev);
 	}
 	return 0;
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 2c44e5ed61716..25fc57e52b5fa 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -125,7 +125,7 @@ static int ip6_finish_output2(struct net *net, struct sock *sk, struct sk_buff *
 
 	if (IS_ERR_OR_NULL(neigh)) {
 		if (unlikely(!neigh))
-			neigh = __neigh_create(&nd_tbl, nexthop, dev, false);
+			neigh = __neigh_create(nd_table(net), nexthop, dev, false);
 		if (IS_ERR(neigh)) {
 			IP6_INC_STATS(net, idev, IPSTATS_MIB_OUTNOROUTES);
 			kfree_skb_reason(skb, SKB_DROP_REASON_NEIGH_CREATEFAIL);
@@ -581,7 +581,7 @@ int ip6_forward(struct sk_buff *skb)
 
 	/* XXX: idev->cnf.proxy_ndp? */
 	if (READ_ONCE(net->ipv6.devconf_all->proxy_ndp) &&
-	    pneigh_lookup(&nd_tbl, net, &hdr->daddr, skb->dev)) {
+	    pneigh_lookup(nd_table(net), &hdr->daddr, skb->dev)) {
 		int proxied = ip6_forward_proxy_check(skb);
 
 		hdr = ipv6_hdr(skb);
diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index 2ceb655c4229e..022c2296807a3 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -767,10 +767,11 @@ static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb)
 static int pndisc_is_router(const void *pkey,
 			    struct net_device *dev)
 {
+	struct net *net = dev_net(dev);
 	struct pneigh_entry *n;
 	int ret = -1;
 
-	n = pneigh_lookup(&nd_tbl, dev_net(dev), pkey, dev);
+	n = pneigh_lookup(nd_table(net), pkey, dev);
 	if (n)
 		ret = !!(READ_ONCE(n->flags) & NTF_ROUTER);
 
@@ -788,19 +789,21 @@ void ndisc_update(const struct net_device *dev, struct neighbour *neigh,
 
 static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)
 {
+	u32 ndoptlen = skb_tail_pointer(skb) - (skb_transport_header(skb) +
+				    offsetof(struct nd_msg, opt));
 	struct nd_msg *msg = (struct nd_msg *)skb_transport_header(skb);
 	const struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
 	const struct in6_addr *daddr = &ipv6_hdr(skb)->daddr;
-	u8 *lladdr = NULL;
-	u32 ndoptlen = skb_tail_pointer(skb) - (skb_transport_header(skb) +
-				    offsetof(struct nd_msg, opt));
-	struct ndisc_options ndopts;
 	struct net_device *dev = skb->dev;
-	struct inet6_ifaddr *ifp;
+	struct net *net = dev_net(dev);
+	int dad = ipv6_addr_any(saddr);
 	struct inet6_dev *idev = NULL;
+	struct ndisc_options ndopts;
+	struct inet6_ifaddr *ifp;
+	struct neigh_table *tbl;
 	struct neighbour *neigh;
-	int dad = ipv6_addr_any(saddr);
 	int is_router = -1;
+	u8 *lladdr = NULL;
 	SKB_DR(reason);
 	u64 nonce = 0;
 	bool inc;
@@ -845,9 +848,10 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)
 	if (ndopts.nd_opts_nonce && ndopts.nd_opts_nonce->nd_opt_len == 1)
 		memcpy(&nonce, (u8 *)(ndopts.nd_opts_nonce + 1), 6);
 
-	inc = ipv6_addr_is_multicast(daddr);
+	tbl = nd_table(net);
 
-	ifp = ipv6_get_ifaddr(dev_net(dev), &msg->target, dev, 1);
+	inc = ipv6_addr_is_multicast(daddr);
+	ifp = ipv6_get_ifaddr(net, &msg->target, dev, 1);
 	if (ifp) {
 have_ifp:
 		if (ifp->flags & (IFA_F_TENTATIVE|IFA_F_OPTIMISTIC)) {
@@ -880,8 +884,6 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)
 
 		idev = ifp->idev;
 	} else {
-		struct net *net = dev_net(dev);
-
 		/* perhaps an address on the master device */
 		if (netif_is_l3_slave(dev)) {
 			struct net_device *mdev;
@@ -918,7 +920,7 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)
 				 */
 				struct sk_buff *n = skb_clone(skb, GFP_ATOMIC);
 				if (n)
-					pneigh_enqueue(&nd_tbl, idev->nd_parms, n);
+					pneigh_enqueue(tbl, idev->nd_parms, n);
 				goto out;
 			}
 		} else {
@@ -937,15 +939,15 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)
 	}
 
 	if (inc)
-		NEIGH_CACHE_STAT_INC(&nd_tbl, rcv_probes_mcast);
+		NEIGH_CACHE_STAT_INC(tbl, rcv_probes_mcast);
 	else
-		NEIGH_CACHE_STAT_INC(&nd_tbl, rcv_probes_ucast);
+		NEIGH_CACHE_STAT_INC(tbl, rcv_probes_ucast);
 
 	/*
 	 *	update / create cache entry
 	 *	for the source address
 	 */
-	neigh = __neigh_lookup(&nd_tbl, saddr, dev,
+	neigh = __neigh_lookup(tbl, saddr, dev,
 			       !inc || lladdr || !dev->addr_len);
 	if (neigh)
 		ndisc_update(dev, neigh, lladdr, NUD_STALE,
@@ -987,17 +989,19 @@ static int accept_untracked_na(struct inet6_dev *idev, struct in6_addr *saddr)
 
 static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
 {
-	struct nd_msg *msg = (struct nd_msg *)skb_transport_header(skb);
-	struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
-	const struct in6_addr *daddr = &ipv6_hdr(skb)->daddr;
-	u8 *lladdr = NULL;
 	u32 ndoptlen = skb_tail_pointer(skb) - (skb_transport_header(skb) +
 				    offsetof(struct nd_msg, opt));
-	struct ndisc_options ndopts;
+	struct nd_msg *msg = (struct nd_msg *)skb_transport_header(skb);
+	const struct in6_addr *daddr = &ipv6_hdr(skb)->daddr;
+	struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
 	struct net_device *dev = skb->dev;
-	struct inet6_dev *idev = __in6_dev_get(dev);
+	struct net *net = dev_net(dev);
+	struct ndisc_options ndopts;
 	struct inet6_ifaddr *ifp;
+	struct neigh_table *tbl;
 	struct neighbour *neigh;
+	struct inet6_dev *idev;
+	u8 *lladdr = NULL;
 	SKB_DR(reason);
 	u8 new_state;
 
@@ -1020,6 +1024,7 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
 	 * and thus should not be accepted.
 	 * drop_unsolicited_na takes precedence over accept_untracked_na
 	 */
+	idev = __in6_dev_get(dev);
 	if (!msg->icmph.icmp6_solicited && idev &&
 	    READ_ONCE(idev->cnf.drop_unsolicited_na))
 		return reason;
@@ -1034,7 +1039,7 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
 			return reason;
 		}
 	}
-	ifp = ipv6_get_ifaddr(dev_net(dev), &msg->target, dev, 1);
+	ifp = ipv6_get_ifaddr(net, &msg->target, dev, 1);
 	if (ifp) {
 		if (skb->pkt_type != PACKET_LOOPBACK
 		    && (ifp->flags & IFA_F_TENTATIVE)) {
@@ -1058,7 +1063,8 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
 		return reason;
 	}
 
-	neigh = neigh_lookup(&nd_tbl, &msg->target, dev);
+	tbl = nd_table(net);
+	neigh = neigh_lookup(tbl, &msg->target, dev);
 
 	/* RFC 9131 updates original Neighbour Discovery RFC 4861.
 	 * NAs with Target LL Address option without a corresponding
@@ -1078,14 +1084,13 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
 	new_state = msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE;
 	if (!neigh && lladdr && idev && READ_ONCE(idev->cnf.forwarding)) {
 		if (accept_untracked_na(idev, saddr)) {
-			neigh = neigh_create(&nd_tbl, &msg->target, dev);
+			neigh = neigh_create(tbl, &msg->target, dev);
 			new_state = NUD_STALE;
 		}
 	}
 
 	if (neigh && !IS_ERR(neigh)) {
 		u8 old_flags = neigh->flags;
-		struct net *net = dev_net(dev);
 
 		if (READ_ONCE(neigh->nud_state) & NUD_FAILED)
 			goto out;
@@ -1098,7 +1103,7 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
 		if (lladdr && !memcmp(lladdr, dev->dev_addr, dev->addr_len) &&
 		    READ_ONCE(net->ipv6.devconf_all->forwarding) &&
 		    READ_ONCE(net->ipv6.devconf_all->proxy_ndp) &&
-		    pneigh_lookup(&nd_tbl, net, &msg->target, dev)) {
+		    pneigh_lookup(tbl, &msg->target, dev)) {
 			/* XXX: idev->cnf.proxy_ndp */
 			goto out;
 		}
@@ -1127,18 +1132,20 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
 static enum skb_drop_reason ndisc_recv_rs(struct sk_buff *skb)
 {
 	struct rs_msg *rs_msg = (struct rs_msg *)skb_transport_header(skb);
+	const struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
 	unsigned long ndoptlen = skb->len - sizeof(*rs_msg);
+	struct net_device *dev = skb->dev;
+	struct ndisc_options ndopts;
+	struct neigh_table *tbl;
 	struct neighbour *neigh;
 	struct inet6_dev *idev;
-	const struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
-	struct ndisc_options ndopts;
 	u8 *lladdr = NULL;
 	SKB_DR(reason);
 
 	if (skb->len < sizeof(*rs_msg))
 		return SKB_DROP_REASON_PKT_TOO_SMALL;
 
-	idev = __in6_dev_get(skb->dev);
+	idev = __in6_dev_get(dev);
 	if (!idev) {
 		net_err_ratelimited("RS: can't find in6 device\n");
 		return reason;
@@ -1156,19 +1163,19 @@ static enum skb_drop_reason ndisc_recv_rs(struct sk_buff *skb)
 		goto out;
 
 	/* Parse ND options */
-	if (!ndisc_parse_options(skb->dev, rs_msg->opt, ndoptlen, &ndopts))
+	if (!ndisc_parse_options(dev, rs_msg->opt, ndoptlen, &ndopts))
 		return SKB_DROP_REASON_IPV6_NDISC_BAD_OPTIONS;
 
 	if (ndopts.nd_opts_src_lladdr) {
-		lladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr,
-					     skb->dev);
+		lladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr, dev);
 		if (!lladdr)
 			goto out;
 	}
 
-	neigh = __neigh_lookup(&nd_tbl, saddr, skb->dev, 1);
+	tbl = nd_table(dev_net(dev));
+	neigh = __neigh_lookup(tbl, saddr, dev, 1);
 	if (neigh) {
-		ndisc_update(skb->dev, neigh, lladdr, NUD_STALE,
+		ndisc_update(dev, neigh, lladdr, NUD_STALE,
 			     NEIGH_UPDATE_F_WEAK_OVERRIDE|
 			     NEIGH_UPDATE_F_OVERRIDE|
 			     NEIGH_UPDATE_F_OVERRIDE_ISROUTER,
@@ -1232,6 +1239,7 @@ static void ndisc_ra_useropt(struct sk_buff *ra, struct nd_opt_hdr *opt)
 static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)
 {
 	struct ra_msg *ra_msg = (struct ra_msg *)skb_transport_header(skb);
+	struct net *net = dev_net(skb->dev);
 	bool send_ifinfo_notify = false;
 	struct neighbour *neigh = NULL;
 	struct ndisc_options ndopts;
@@ -1241,7 +1249,6 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)
 	u32 defrtr_usr_metric;
 	unsigned int pref = 0;
 	__u32 old_if_flags;
-	struct net *net;
 	SKB_DR(reason);
 	int lifetime;
 	int optlen;
@@ -1330,7 +1337,6 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)
 	/* Do not accept RA with source-addr found on local machine unless
 	 * accept_ra_from_local is set to true.
 	 */
-	net = dev_net(in6_dev->dev);
 	if (!READ_ONCE(in6_dev->cnf.accept_ra_from_local) &&
 	    ipv6_chk_addr(net, &ipv6_hdr(skb)->saddr, in6_dev->dev, 0)) {
 		net_dbg_ratelimited("RA from local address detected on dev: %s: default router ignored\n",
@@ -1466,7 +1472,7 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)
 	 */
 
 	if (!neigh)
-		neigh = __neigh_lookup(&nd_tbl, &ipv6_hdr(skb)->saddr,
+		neigh = __neigh_lookup(nd_table(net), &ipv6_hdr(skb)->saddr,
 				       skb->dev, 1);
 	if (neigh) {
 		u8 *lladdr = NULL;
@@ -1859,12 +1865,15 @@ static int ndisc_netdev_event(struct notifier_block *this, unsigned long event,
 	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
 	struct netdev_notifier_change_info *change_info;
 	struct net *net = dev_net(dev);
+	struct neigh_table *tbl;
 	struct inet6_dev *idev;
 	bool evict_nocarrier;
 
+	tbl = nd_table(net);
+
 	switch (event) {
 	case NETDEV_CHANGEADDR:
-		neigh_changeaddr(&nd_tbl, dev);
+		neigh_changeaddr(tbl, dev);
 		fib6_run_gc(0, net, false);
 		fallthrough;
 	case NETDEV_UP:
@@ -1888,12 +1897,12 @@ static int ndisc_netdev_event(struct notifier_block *this, unsigned long event,
 
 		change_info = ptr;
 		if (change_info->flags_changed & IFF_NOARP)
-			neigh_changeaddr(&nd_tbl, dev);
+			neigh_changeaddr(tbl, dev);
 		if (evict_nocarrier && !netif_carrier_ok(dev))
-			neigh_carrier_down(&nd_tbl, dev);
+			neigh_carrier_down(tbl, dev);
 		break;
 	case NETDEV_DOWN:
-		neigh_ifdown(&nd_tbl, dev);
+		neigh_ifdown(tbl, dev);
 		fib6_run_gc(0, net, false);
 		break;
 	case NETDEV_NOTIFY_PEERS:
@@ -1972,12 +1981,23 @@ 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;
+
+#ifdef CONFIG_SYSCTL
+	err = neigh_sysctl_register(NULL, &nd_table(net)->parms,
+				    ndisc_ifinfo_sysctl_change);
+	if (err)
+		goto err_sysctl;
+#endif
+
 	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 +2008,24 @@ static int __net_init ndisc_net_init(struct net *net)
 	inet6_clear_bit(MC6_LOOP, sk);
 
 	return 0;
+
+err_sock_create:
+#ifdef CONFIG_SYSCTL
+	neigh_sysctl_unregister(&nd_table(net)->parms);
+err_sysctl:
+#endif
+	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);
+#ifdef CONFIG_SYSCTL
+	neigh_sysctl_unregister(&nd_table(net)->parms);
+#endif
+	neigh_table_unregister(net, NEIGH_ND_TABLE);
 }
 
 static struct pernet_operations ndisc_net_ops = {
@@ -2002,30 +2035,7 @@ static struct pernet_operations ndisc_net_ops = {
 
 int __init ndisc_init(void)
 {
-	int err;
-
-	err = register_pernet_subsys(&ndisc_net_ops);
-	if (err)
-		return err;
-	/*
-	 * Initialize the neighbour table
-	 */
-	neigh_table_init(NEIGH_ND_TABLE, &nd_tbl);
-
-#ifdef CONFIG_SYSCTL
-	err = neigh_sysctl_register(NULL, &nd_tbl.parms,
-				    ndisc_ifinfo_sysctl_change);
-	if (err)
-		goto out_unregister_pernet;
-out:
-#endif
-	return err;
-
-#ifdef CONFIG_SYSCTL
-out_unregister_pernet:
-	unregister_pernet_subsys(&ndisc_net_ops);
-	goto out;
-#endif
+	return register_pernet_subsys(&ndisc_net_ops);
 }
 
 int __init ndisc_late_init(void)
@@ -2040,9 +2050,5 @@ void ndisc_late_cleanup(void)
 
 void ndisc_cleanup(void)
 {
-#ifdef CONFIG_SYSCTL
-	neigh_sysctl_unregister(&nd_tbl.parms);
-#endif
-	neigh_table_clear(NEIGH_ND_TABLE, &nd_tbl);
 	unregister_pernet_subsys(&ndisc_net_ops);
 }
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 5968ce5ad1508..cea1cdc2b8e8e 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -217,7 +217,7 @@ struct neighbour *ip6_neigh_lookup(const struct in6_addr *gw,
 	if (n)
 		return n;
 
-	n = neigh_create(&nd_tbl, daddr, dev);
+	n = neigh_create(nd_table(dev_net(dev)), daddr, dev);
 	return IS_ERR(n) ? NULL : n;
 }
 
@@ -4229,6 +4229,7 @@ static int ip6_route_del(struct fib6_config *cfg,
 static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_buff *skb)
 {
 	struct netevent_redirect netevent;
+	struct net_device *dev = skb->dev;
 	struct rt6_info *rt, *nrt = NULL;
 	struct fib6_result res = {};
 	struct ndisc_options ndopts;
@@ -4262,7 +4263,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
 		return;
 	}
 
-	in6_dev = __in6_dev_get(skb->dev);
+	in6_dev = __in6_dev_get(dev);
 	if (!in6_dev)
 		return;
 	if (READ_ONCE(in6_dev->cnf.forwarding) ||
@@ -4274,15 +4275,14 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
 	 *	first-hop router for the specified ICMP Destination Address.
 	 */
 
-	if (!ndisc_parse_options(skb->dev, msg->opt, optlen, &ndopts)) {
+	if (!ndisc_parse_options(dev, msg->opt, optlen, &ndopts)) {
 		net_dbg_ratelimited("rt6_redirect: invalid ND options\n");
 		return;
 	}
 
 	lladdr = NULL;
 	if (ndopts.nd_opts_tgt_lladdr) {
-		lladdr = ndisc_opt_addr_data(ndopts.nd_opts_tgt_lladdr,
-					     skb->dev);
+		lladdr = ndisc_opt_addr_data(ndopts.nd_opts_tgt_lladdr, dev);
 		if (!lladdr) {
 			net_dbg_ratelimited("rt6_redirect: invalid link-layer address length\n");
 			return;
@@ -4301,7 +4301,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
 	 */
 	dst_confirm_neigh(&rt->dst, &ipv6_hdr(skb)->saddr);
 
-	neigh = __neigh_lookup(&nd_tbl, &msg->target, skb->dev, 1);
+	neigh = __neigh_lookup(nd_table(dev_net(dev)), &msg->target, dev, 1);
 	if (!neigh)
 		return;
 
@@ -4309,7 +4309,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
 	 *	We have finally decided to accept it.
 	 */
 
-	ndisc_update(skb->dev, neigh, lladdr, NUD_STALE,
+	ndisc_update(dev, neigh, lladdr, NUD_STALE,
 		     NEIGH_UPDATE_F_WEAK_OVERRIDE|
 		     NEIGH_UPDATE_F_OVERRIDE|
 		     (on_link ? 0 : (NEIGH_UPDATE_F_OVERRIDE_ISROUTER|
@@ -5030,9 +5030,11 @@ void rt6_sync_down_dev(struct net_device *dev, unsigned long event)
 
 void rt6_disable_ip(struct net_device *dev, unsigned long event)
 {
+	struct net *net = dev_net(dev);
+
 	rt6_sync_down_dev(dev, event);
 	rt6_uncached_list_flush_dev(dev);
-	neigh_ifdown(&nd_tbl, dev);
+	neigh_ifdown(nd_table(net), dev);
 }
 
 struct rt6_mtu_change_arg {
diff --git a/tools/testing/selftests/net/test_neigh.sh b/tools/testing/selftests/net/test_neigh.sh
index 7c594bf6ead0d..e70e31edf6337 100755
--- a/tools/testing/selftests/net/test_neigh.sh
+++ b/tools/testing/selftests/net/test_neigh.sh
@@ -63,6 +63,15 @@ exit_cleanup_all()
 	exit "${EXIT_STATUS}"
 }
 
+get_periodic_gc_runs()
+{
+	local ns=$1
+	local tbl_name=$2
+
+	ip -n "$ns" -j -s ntable show name "$tbl_name" | \
+		jq '.[] | select(has("periodic_gc_runs")) | .["periodic_gc_runs"]'
+}
+
 ################################################################################
 # Tests
 
@@ -231,9 +240,6 @@ extern_valid_common()
 	# Check that an "extern_valid" entry survives a forced garbage
 	# collection. Add an entry, wait 5 seconds and add more entries than
 	# "thresh3" so that forced garbage collection will run.
-	#
-	# Note that the garbage collection thresholds are global resources and
-	# that changes in the initial namespace affect all the namespaces.
 	local forced_gc_runs_t0
 	local forced_gc_runs_t1
 	local orig_thresh1
@@ -241,18 +247,23 @@ extern_valid_common()
 	local orig_thresh3
 
 	run_cmd "ip -n $ns1 neigh flush dev veth0"
-	orig_thresh1=$(ip -j ntable show name "$tbl_name" | jq '.[] | select(has("thresh1")) | .["thresh1"]')
-	orig_thresh2=$(ip -j ntable show name "$tbl_name" | jq '.[] | select(has("thresh2")) | .["thresh2"]')
-	orig_thresh3=$(ip -j ntable show name "$tbl_name" | jq '.[] | select(has("thresh3")) | .["thresh3"]')
-	run_cmd "ip ntable change name $tbl_name thresh3 10 thresh2 9 thresh1 8"
+	orig_thresh1=$(ip -n "$ns1" -j ntable show name "$tbl_name" | \
+			jq '.[] | select(has("thresh1")) | .["thresh1"]')
+	orig_thresh2=$(ip -n "$ns1" -j ntable show name "$tbl_name" | \
+			jq '.[] | select(has("thresh2")) | .["thresh2"]')
+	orig_thresh3=$(ip -n "$ns1" -j ntable show name "$tbl_name" | \
+			jq '.[] | select(has("thresh3")) | .["thresh3"]')
+	run_cmd "ip -n $ns1 ntable change name $tbl_name thresh3 10 thresh2 9 thresh1 8"
 	run_cmd "ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid"
 	run_cmd "ip -n $ns1 neigh add ${subnet}3 lladdr $mac nud stale dev veth0"
 	run_cmd "sleep 5"
-	forced_gc_runs_t0=$(ip -j -s ntable show name "$tbl_name" | jq '.[] | select(has("forced_gc_runs")) | .["forced_gc_runs"]')
+	forced_gc_runs_t0=$(ip -n "$ns1" -j -s ntable show name "$tbl_name" | \
+				jq '.[] | select(has("forced_gc_runs")) | .["forced_gc_runs"]')
 	for i in {1..20}; do
 		run_cmd "ip -n $ns1 neigh add ${subnet}$((i + 4)) nud none dev veth0"
 	done
-	forced_gc_runs_t1=$(ip -j -s ntable show name "$tbl_name" | jq '.[] | select(has("forced_gc_runs")) | .["forced_gc_runs"]')
+	forced_gc_runs_t1=$(ip -n "$ns1" -j -s ntable show name "$tbl_name" | \
+				jq '.[] | select(has("forced_gc_runs")) | .["forced_gc_runs"]')
 	if [[ $forced_gc_runs_t1 -eq $forced_gc_runs_t0 ]]; then
 		check_err 1 "Forced garbage collection did not run"
 	fi
@@ -263,7 +274,8 @@ extern_valid_common()
 
 	log_test "$af_str \"extern_valid\" flag: Forced garbage collection"
 
-	run_cmd "ip ntable change name $tbl_name thresh3 $orig_thresh3 thresh2 $orig_thresh2 thresh1 $orig_thresh1"
+	run_cmd "ip -n $ns1 ntable change name $tbl_name \
+		thresh3 $orig_thresh3 thresh2 $orig_thresh2 thresh1 $orig_thresh1"
 
 	RET=0
 
@@ -275,31 +287,36 @@ extern_valid_common()
 	# collection. Add an "extern_valid" entry, add more than "thresh1"
 	# regular entries, wait "base_reachable" (longer than "gc_stale")
 	# seconds and check that the "extern_valid" entry was not deleted.
-	#
-	# Note that the garbage collection thresholds and "base_reachable" are
-	# global resources and that changes in the initial namespace affect all
-	# the namespaces.
 	local periodic_gc_runs_t0
 	local periodic_gc_runs_t1
 	local orig_base_reachable
+	local base_reachable=10000
 	local orig_gc_stale
+	local timeout
 
 	run_cmd "ip -n $ns1 neigh flush dev veth0"
-	orig_thresh1=$(ip -j ntable show name "$tbl_name" | jq '.[] | select(has("thresh1")) | .["thresh1"]')
-	orig_base_reachable=$(ip -j ntable show name "$tbl_name" | jq '.[] | select(has("thresh1")) | .["base_reachable"]')
-	run_cmd "ip ntable change name $tbl_name thresh1 10 base_reachable 10000"
-	orig_gc_stale=$(ip -n "$ns1" -j ntable show name "$tbl_name" dev veth0 | jq '.[]["gc_stale"]')
+	orig_thresh1=$(ip -n "$ns1" -j ntable show name "$tbl_name" | \
+			jq '.[] | select(has("thresh1")) | .["thresh1"]')
+	orig_base_reachable=$(ip -n "$ns1" -j ntable show name "$tbl_name" | \
+				jq '.[] | select(has("thresh1")) | .["base_reachable"]')
+	run_cmd "ip -n $ns1 ntable change name $tbl_name thresh1 10 base_reachable $base_reachable"
+	orig_gc_stale=$(ip -n "$ns1" -j ntable show name "$tbl_name" dev veth0 | \
+			jq '.[]["gc_stale"]')
 	run_cmd "ip -n $ns1 ntable change name $tbl_name dev veth0 gc_stale 1000"
 	run_cmd "ip -n $ns1 neigh add $ip_addr lladdr $mac nud stale dev veth0 extern_valid"
 	run_cmd "ip -n $ns1 neigh add ${subnet}3 lladdr $mac nud stale dev veth0"
-	# Wait orig_base_reachable/2 for the new interval to take effect.
-	run_cmd "sleep $(((orig_base_reachable / 1000) / 2 + 2))"
+	# Wait up to neigh_set_reach_time(BASE_REACHABLE_TIME) (~45s) + 5s
+	# for the new interval to take effect, see neigh_table_init().
+	timeout=$(((orig_base_reachable / 1000) * 3 / 2 + 5))
+	slowwait_for_counter "$timeout" 1 get_periodic_gc_runs "$ns1" "$tbl_name" >/dev/null
 	for i in {1..20}; do
 		run_cmd "ip -n $ns1 neigh add ${subnet}$((i + 4)) nud none dev veth0"
 	done
-	periodic_gc_runs_t0=$(ip -j -s ntable show name "$tbl_name" | jq '.[] | select(has("periodic_gc_runs")) | .["periodic_gc_runs"]')
-	run_cmd "sleep 10"
-	periodic_gc_runs_t1=$(ip -j -s ntable show name "$tbl_name" | jq '.[] | select(has("periodic_gc_runs")) | .["periodic_gc_runs"]')
+	periodic_gc_runs_t0=$(get_periodic_gc_runs "$ns1" "$tbl_name")
+	# Wait up to BASE_REACHABLE_TIME / 2 + 5s, see neigh_periodic_work().
+	timeout=$(((base_reachable / 1000) / 2 + 5))
+	slowwait_for_counter "$timeout" 1 get_periodic_gc_runs "$ns1" "$tbl_name" >/dev/null
+	periodic_gc_runs_t1=$(get_periodic_gc_runs "$ns1" "$tbl_name")
 	[[ $periodic_gc_runs_t1 -ne $periodic_gc_runs_t0 ]]
 	check_err $? "Periodic garbage collection did not run"
 	run_cmd "ip -n $ns1 neigh get $ip_addr dev veth0 | grep \"extern_valid\""
@@ -310,7 +327,8 @@ extern_valid_common()
 	log_test "$af_str \"extern_valid\" flag: Periodic garbage collection"
 
 	run_cmd "ip -n $ns1 ntable change name $tbl_name dev veth0 gc_stale $orig_gc_stale"
-	run_cmd "ip ntable change name $tbl_name thresh1 $orig_thresh1 base_reachable $orig_base_reachable"
+	run_cmd "ip -n $ns1 ntable change name $tbl_name \
+		thresh1 $orig_thresh1 base_reachable $orig_base_reachable"
 }
 
 extern_valid_ipv4()

57/3 2026/08/13 08:35 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 44852
							output: 39
							thoughts: 2691
						

Thoughts:
**Analysis of Neighbor Discovery Subsystem Modification for Per-Network Namespace Support**

Okay, so this patch series fundamentally alters the Linux kernel's neighbor discovery (ARP and IPv6 ND) subsystem. The core change is migrating the neighbor tables, `arp_tbl` and `nd_tbl`, from global variables to being per-network-namespace via `net->neigh_tables`. This necessitates passing the `struct net *net` pointer to many functions, changing existing calls like `neigh_lookup(&arp_tbl, ...)` to `neigh_lookup(arp_table(net), ...)`, and the same for the IPv6 table. A new sysctl, `neigh_inherit_init_net`, is also introduced to control whether new network namespaces inherit settings from the initial network namespace.

My immediate concern is always memory safety, specifically uninitialized memory. Let's dig in. The patch modifies `neigh_table_init` and `neigh_table_register`. `neigh_table_register` uses `kmemdup` to duplicate a `struct neigh_table`. This duplication stems from the template, which is a static variable (either `arp_tbl` or `nd_tbl`).  It's crucial that `kmemdup` doesn't introduce uninitialized memory. `kmemdup` copies the *entire* `struct neigh_table` template. Thankfully, the template is initialized, either explicitly, or by the compiler, with zeros. So, no problem here. Then `neigh_table_init` is called, and `kzalloc` is used.  `kzalloc` guarantees zero-initialization, so no problems.

I also need to check what structures are exposed to userspace. The kernel provides info to userspace via netlink, specifically using functions like `neightbl_fill_info` and `neightbl_dump_info`. I need to ensure there are no uninitialized fields when these structures are communicated to userspace. In `neightbl_fill_info`, a `struct ndt_config` is constructed and populated. I need to make sure that structure has no padding, and its fields are initialized. After checking `include/uapi/linux/neighbour.h`, I can see that `struct ndt_config` doesn't have any padding. The other new sysctl variable `sysctl_neigh_inherit_init_net` looks straightforward and doesn't introduce any complexities.

The patch modifies `struct neigh_table` to change `atomic_t entries` to `refcount_t entries` and removes `possible_net_t net;` from `struct pneigh_entry`. A `struct neigh_table *neigh_tables[NEIGH_NR_TABLES];` is added to `struct net`. I also see that `net` is removed from `pneigh_lookup`, which now uses the per-net `neigh_table`. That's expected, as the per-net tables mean no net parameter is needed.

I've got to revisit the `kmemdup` in `neigh_table_register`.  `kmemdup` duplicates the static template, which is good.  `neigh_table_init` uses `kzalloc`, which is safe.

Next, I look at `neigh_alloc` and `pneigh_create`. No changes in `neigh_alloc` except `neigh_table_get(tbl)`. The patch modifies `pneigh_create` to use `kmalloc` to create a `struct pneigh_entry`. Does userspace see this structure? It looks like `pneigh_dump_table` calls `neigh_fill_info`, which takes a `struct neighbour` pointer. However, `pneigh_dump_table` is passing what appears to be a `struct pneigh_entry *` (cast to `struct neighbour *`) to `neigh_fill_info`. This is concerning, I need to check how the memory is laid out in memory, but this should be okay. The padding and members in `struct pneigh_entry` can be safely used.



58/3 2026/08/13 08:36 tool grepper
0m Args:
{
  "Expression": "neigh_fill_info.*RTM_NEWNEIGH",
  "PathPrefix": "net/core/neighbour.c"
}

Results:
{
  "Output": "net/core/neighbour.c=3098=static int neigh_get(struct sk_buff *in_skb, struct nlmsghdr *nlh,\n--\nnet/core/neighbour.c-3159-\nnet/core/neighbour.c:3160:\t\terr = pneigh_fill_info(skb, pn, pid, seq, RTM_NEWNEIGH, 0, tbl);\nnet/core/neighbour.c-3161-\t\tif (err)\n--\nnet/core/neighbour.c-3170-\nnet/core/neighbour.c:3171:\t\terr = neigh_fill_info(skb, neigh, pid, seq, RTM_NEWNEIGH, 0);\nnet/core/neighbour.c-3172-\t\tneigh_release(neigh);\n"
}

59/3 2026/08/13 08:36 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 48651
							output: 33
							thoughts: 32
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:

**Working Through the pneigh_entry Relationship**

Right, so I'm thinking about `pneigh_fill_info` here. It seems like we're using it in conjunction with `pneigh_entry`, and that immediately flags a potential area for closer inspection. Given my understanding of the networking stack, I know these data structures are critical for neighbor discovery and management. Therefore, my next step is clear: I need to dig into the definition and usage of `pneigh_fill_info`. I need to understand exactly what information `pneigh_fill_info` is providing, how it's being used within the `pneigh_entry` context, and if the data flow is as expected. This will give me the information I need.



60/3 2026/08/13 08:36 tool grepper
0m Args:
{
  "Expression": "pneigh_fill_info",
  "PathPrefix": "net/core/neighbour.c"
}

Results:
{
  "Output": "net/core/neighbour.c=2745=static int neigh_fill_info(struct sk_buff *skb, struct neighbour *neigh,\n--\nnet/core/neighbour.c-2758-\nnet/core/neighbour.c:2759:static int pneigh_fill_info(struct sk_buff *skb, struct pneigh_entry *pn,\nnet/core/neighbour.c-2760-\t\t\t    u32 pid, u32 seq, int type, unsigned int flags,\n--\nnet/core/neighbour.c=2876=static int pneigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb,\n--\nnet/core/neighbour.c-2898-\t\t\t\tgoto next;\nnet/core/neighbour.c:2899:\t\t\terr = pneigh_fill_info(skb, n, NETLINK_CB(cb-\u003eskb).portid,\nnet/core/neighbour.c-2900-\t\t\t\t\t       cb-\u003enlh-\u003enlmsg_seq,\n--\nnet/core/neighbour.c=3098=static int neigh_get(struct sk_buff *in_skb, struct nlmsghdr *nlh,\n--\nnet/core/neighbour.c-3159-\nnet/core/neighbour.c:3160:\t\terr = pneigh_fill_info(skb, pn, pid, seq, RTM_NEWNEIGH, 0, tbl);\nnet/core/neighbour.c-3161-\t\tif (err)\n"
}

61/3 2026/08/13 08:36 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 49281
							output: 36
							thoughts: 14
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Analyzing the `pneigh_fill_info` Implementation**

Okay, time to dive into this `pneigh_fill_info` implementation. I need to refresh my understanding of how this is supposed to work and quickly identify any potential issues or areas for optimization. First, I’ll need to understand the data structures and assumptions underlying this function – how does it interact with the persistent neighbor cache? What specific information is it meant to fill, and what does that info represent? Is this an attempt to populate all neighbor entries at once, or are there selective fills involved? Is it doing more work than it needs to?

I'll be looking for signs of potential race conditions or contention issues, especially if this function is called concurrently. Memory allocation is always a concern, too, so I need to make sure the implementation doesn't have any hidden memory leaks or unnecessary allocations inside of performance-sensitive code paths. I need to think about cases where this code could fail and verify the error handling. I'll need to look specifically for any uses of synchronization primitives – locks, atomic operations – and make sure they're used correctly. I'm also going to examine the code for any potential buffer overflows or integer overflows, considering the kind of data this function is dealing with. I know I have a bunch of unit tests, so I need to check how comprehensive they are to see if any obvious edge cases were overlooked. I also need to assess if this part of the code is the right place for what it's doing, or if it has some undesirable side effects that could be moved elsewhere.



62/3 2026/08/13 08:36 tool read-file
0m Args:
{
  "File": "net/core/neighbour.c",
  "FirstLine": 2759,
  "LineCount": 50
}

Results:
{
  "Contents": "2759:\tstatic int pneigh_fill_info(struct sk_buff *skb, struct pneigh_entry *pn,\n2760:\t\t\t\t    u32 pid, u32 seq, int type, unsigned int flags,\n2761:\t\t\t\t    struct neigh_table *tbl)\n2762:\t{\n2763:\t\tu32 neigh_flags, neigh_flags_ext;\n2764:\t\tstruct nlmsghdr *nlh;\n2765:\t\tstruct ndmsg *ndm;\n2766:\t\tu8 protocol;\n2767:\t\n2768:\t\tnlh = nlmsg_put(skb, pid, seq, type, sizeof(*ndm), flags);\n2769:\t\tif (nlh == NULL)\n2770:\t\t\treturn -EMSGSIZE;\n2771:\t\n2772:\t\tneigh_flags = READ_ONCE(pn-\u003eflags);\n2773:\t\tneigh_flags_ext = neigh_flags \u003e\u003e NTF_EXT_SHIFT;\n2774:\t\tneigh_flags \u0026= NTF_OLD_MASK;\n2775:\t\n2776:\t\tndm = nlmsg_data(nlh);\n2777:\t\tndm-\u003endm_family\t = tbl-\u003efamily;\n2778:\t\tndm-\u003endm_pad1    = 0;\n2779:\t\tndm-\u003endm_pad2    = 0;\n2780:\t\tndm-\u003endm_flags\t = neigh_flags | NTF_PROXY;\n2781:\t\tndm-\u003endm_type\t = RTN_UNICAST;\n2782:\t\tndm-\u003endm_ifindex = pn-\u003edev ? pn-\u003edev-\u003eifindex : 0;\n2783:\t\tndm-\u003endm_state\t = NUD_NONE;\n2784:\t\n2785:\t\tif (nla_put(skb, NDA_DST, tbl-\u003ekey_len, pn-\u003ekey))\n2786:\t\t\tgoto nla_put_failure;\n2787:\t\n2788:\t\tprotocol = READ_ONCE(pn-\u003eprotocol);\n2789:\t\tif (protocol \u0026\u0026 nla_put_u8(skb, NDA_PROTOCOL, protocol))\n2790:\t\t\tgoto nla_put_failure;\n2791:\t\tif (neigh_flags_ext \u0026\u0026 nla_put_u32(skb, NDA_FLAGS_EXT, neigh_flags_ext))\n2792:\t\t\tgoto nla_put_failure;\n2793:\t\n2794:\t\tnlmsg_end(skb, nlh);\n2795:\t\treturn 0;\n2796:\t\n2797:\tnla_put_failure:\n2798:\t\tnlmsg_cancel(skb, nlh);\n2799:\t\treturn -EMSGSIZE;\n2800:\t}\n2801:\t\n2802:\tstatic bool neigh_master_filtered(struct net_device *dev, int master_idx)\n2803:\t{\n2804:\t\tstruct net_device *master;\n2805:\t\n2806:\t\tif (!master_idx)\n2807:\t\t\treturn false;\n2808:\t\n"
}

63/3 2026/08/13 08:36 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 50511
							output: 36
							thoughts: 1312
						

Thoughts:
**Analysis of Network Neighbor Table Initialization and Data Exposure**

Okay, let's break this down. My initial concern was about data corruption or padding issues related to network neighbor entries. The primary area of interest appears to be how the `pn->key` is handled in the `pneigh_fill_info` function when using `nla_put`. This function relies on data that is initially allocated via `kmalloc` in `pneigh_create`. I noted that the `pn->key` is fully initialized with `memcpy`, which should be fine.

Now, I needed to ascertain whether any potential padding within the `struct ndmsg` structure used for neighbor discovery messages could lead to issues. I took a closer look at `ndmsg`. Specifically, I checked the sizes and padding. `ndm_pad1` and `ndm_pad2` are explicitly zeroed. The individual bitfields are 8-bit, 8-bit, 16-bit for padding, then a 32-bit field, and finally another 16-bit and two 8-bit fields for the rest of the members. There's no padding within the structure itself, so that's not a concern.

Next, I reviewed the `neigh_table_init` function. The table, `tbl`, is allocated with `kmemdup` in `neigh_table_register`. The copy template is either `arp_tbl` or `nd_tbl`. I immediately considered potential padding in `struct neigh_table`, but since it's an internal kernel structure, padding wouldn't be directly exposed to userspace.

However, I remembered `neigh_table_inherit`. This function copies certain fields from `init_tbl`. Specifically, `gc_interval`, `gc_thresh1`, `gc_thresh2`, `gc_thresh3`, and the `parms.data` array are copied.

Then, I investigated `neigh_parms_alloc`, which uses `kmemdup` to allocate from `tbl->parms`. `tbl->parms` gets initialized in `neigh_table_init`. Now the important question: Is `struct neigh_parms` exposed to userspace? Yes, it is dumped via `neightbl_dump_info`, which in turn calls `neightbl_fill_param_info`. I'm starting to zero in on areas that could potentially be problematic because they expose data to user space. It is important to know if any of the structs here have padding. I need to review what data is copied and how it's handled during the `kmemdup` operations to make sure nothing unexpected is exposed to user space.



64/3 2026/08/13 08:36 tool grepper
0m Args:
{
  "Expression": "neightbl_fill_param_info",
  "PathPrefix": "net/core/neighbour.c"
}

Results:
{
  "Output": "net/core/neighbour.c=2297=static int neightbl_fill_info(struct sk_buff *skb, struct neigh_table *tbl,\n--\nnet/core/neighbour.c-2380-\nnet/core/neighbour.c:2381:static int neightbl_fill_param_info(struct sk_buff *skb,\nnet/core/neighbour.c-2382-\t\t\t\t    struct neigh_table *tbl,\n--\nnet/core/neighbour.c=2625=static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb)\n--\nnet/core/neighbour.c-2665-\nnet/core/neighbour.c:2666:\t\t\tif (neightbl_fill_param_info(skb, tbl, p,\nnet/core/neighbour.c-2667-\t\t\t\t\t\t     NETLINK_CB(cb-\u003eskb).portid,\n"
}

65/3 2026/08/13 08:36 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 52668
							output: 36
							thoughts: 15
						

Thoughts:
Okay, here's my attempt at summarizing that thought process, keeping in mind I'm supposed to speak as an expert:

**Investigating `neightbl_fill_param_info`**

Alright, time to dig in. The name `neightbl_fill_param_info` immediately suggests a function or routine dealing with the population and dissemination of parameter information related to something called "neightbl," likely an acronym for something I'm intimately familiar with in this context. I'm going to want to understand exactly what parameters it deals with, the context of their collection or computation, and how this information is finally organized and stored. Is it a look-up table, structure, or something more complex? I need to quickly establish the intended purpose and scope before I can evaluate its performance or troubleshoot any issues. I'll need to remember that details about the data, like its type, source, and destination, are important. I know where to start looking to get the answers I need, hopefully it's straightforward enough.



66/3 2026/08/13 08:36 tool read-file
0m Args:
{
  "File": "net/core/neighbour.c",
  "FirstLine": 2381,
  "LineCount": 50
}

Results:
{
  "Contents": "2381:\tstatic int neightbl_fill_param_info(struct sk_buff *skb,\n2382:\t\t\t\t\t    struct neigh_table *tbl,\n2383:\t\t\t\t\t    struct neigh_parms *parms,\n2384:\t\t\t\t\t    u32 pid, u32 seq, int type,\n2385:\t\t\t\t\t    unsigned int flags)\n2386:\t{\n2387:\t\tstruct ndtmsg *ndtmsg;\n2388:\t\tstruct nlmsghdr *nlh;\n2389:\t\n2390:\t\tnlh = nlmsg_put(skb, pid, seq, type, sizeof(*ndtmsg), flags);\n2391:\t\tif (nlh == NULL)\n2392:\t\t\treturn -EMSGSIZE;\n2393:\t\n2394:\t\tndtmsg = nlmsg_data(nlh);\n2395:\t\tndtmsg-\u003endtm_family = tbl-\u003efamily;\n2396:\t\tndtmsg-\u003endtm_pad1   = 0;\n2397:\t\tndtmsg-\u003endtm_pad2   = 0;\n2398:\t\n2399:\t\tif (nla_put_string(skb, NDTA_NAME, tbl-\u003eid) \u003c 0 ||\n2400:\t\t    neightbl_fill_parms(skb, parms) \u003c 0)\n2401:\t\t\tgoto errout;\n2402:\t\n2403:\t\tnlmsg_end(skb, nlh);\n2404:\t\treturn 0;\n2405:\terrout:\n2406:\t\tnlmsg_cancel(skb, nlh);\n2407:\t\treturn -EMSGSIZE;\n2408:\t}\n2409:\t\n2410:\tstatic const struct nla_policy nl_neightbl_policy[NDTA_MAX+1] = {\n2411:\t\t[NDTA_NAME]\t\t= { .type = NLA_STRING },\n2412:\t\t[NDTA_THRESH1]\t\t= { .type = NLA_U32 },\n2413:\t\t[NDTA_THRESH2]\t\t= { .type = NLA_U32 },\n2414:\t\t[NDTA_THRESH3]\t\t= { .type = NLA_U32 },\n2415:\t\t[NDTA_GC_INTERVAL]\t= { .type = NLA_U64 },\n2416:\t\t[NDTA_PARMS]\t\t= { .type = NLA_NESTED },\n2417:\t};\n2418:\t\n2419:\tstatic const struct nla_policy nl_ntbl_parm_policy[NDTPA_MAX+1] = {\n2420:\t\t[NDTPA_IFINDEX]\t\t\t= { .type = NLA_U32 },\n2421:\t\t[NDTPA_QUEUE_LEN]\t\t= { .type = NLA_U32 },\n2422:\t\t[NDTPA_QUEUE_LENBYTES]\t\t= { .type = NLA_U32 },\n2423:\t\t[NDTPA_PROXY_QLEN]\t\t= { .type = NLA_U32 },\n2424:\t\t[NDTPA_APP_PROBES]\t\t= { .type = NLA_U32 },\n2425:\t\t[NDTPA_UCAST_PROBES]\t\t= { .type = NLA_U32 },\n2426:\t\t[NDTPA_MCAST_PROBES]\t\t= { .type = NLA_U32 },\n2427:\t\t[NDTPA_MCAST_REPROBES]\t\t= { .type = NLA_U32 },\n2428:\t\t[NDTPA_BASE_REACHABLE_TIME]\t= { .type = NLA_U64 },\n2429:\t\t[NDTPA_GC_STALETIME]\t\t= { .type = NLA_U64 },\n2430:\t\t[NDTPA_DELAY_PROBE_TIME]\t= { .type = NLA_U64 },\n"
}

67/3 2026/08/13 08:36 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 53973
							output: 36
							thoughts: 62
						

Thoughts:
Here's my summary, as though I'm thinking through this process:

**Diving into the `neigh_parms` Structure**

Okay, so `neightbl_fill_parms` is key here. It looks like it's specifically using `nla_put_u32` and other similar `nla_put_` functions to populate *individual* fields within the `neigh_parms` structure. That means it's *not* simply copying the entire `struct neigh_parms` wholesale into userspace, which is a good thing for control and potential optimization.

Given that understanding, I need to revisit `neightbl_fill_info` to get a complete picture. It's the logical next step to confirm this segmented approach and see how the information is being built and exposed to the user. I want to make sure I understand the complete process.



68/3 2026/08/13 08:36 tool read-file
0m Args:
{
  "File": "net/core/neighbour.c",
  "FirstLine": 2324,
  "LineCount": 50
}

Results:
{
  "Contents": "2324:\t\t\tstruct ndt_config ndc = {\n2325:\t\t\t\t.ndtc_key_len\t\t= tbl-\u003ekey_len,\n2326:\t\t\t\t.ndtc_entry_size\t= tbl-\u003eentry_size,\n2327:\t\t\t\t.ndtc_entries\t\t= neigh_table_entries(tbl),\n2328:\t\t\t\t.ndtc_last_flush\t= jiffies_to_msecs(flush_delta),\n2329:\t\t\t\t.ndtc_last_rand\t\t= jiffies_to_msecs(rand_delta),\n2330:\t\t\t\t.ndtc_proxy_qlen\t= READ_ONCE(tbl-\u003eproxy_queue.qlen),\n2331:\t\t\t};\n2332:\t\n2333:\t\t\tnht = rcu_dereference(tbl-\u003enht);\n2334:\t\t\tndc.ndtc_hash_rnd = nht-\u003ehash_rnd[0];\n2335:\t\t\tndc.ndtc_hash_mask = ((1 \u003c\u003c nht-\u003ehash_shift) - 1);\n2336:\t\n2337:\t\t\tif (nla_put(skb, NDTA_CONFIG, sizeof(ndc), \u0026ndc))\n2338:\t\t\t\tgoto nla_put_failure;\n2339:\t\t}\n2340:\t\n2341:\t\t{\n2342:\t\t\tint cpu;\n2343:\t\t\tstruct ndt_stats ndst;\n2344:\t\n2345:\t\t\tmemset(\u0026ndst, 0, sizeof(ndst));\n2346:\t\n2347:\t\t\tfor_each_possible_cpu(cpu) {\n2348:\t\t\t\tstruct neigh_statistics\t*st;\n2349:\t\n2350:\t\t\t\tst = per_cpu_ptr(tbl-\u003estats, cpu);\n2351:\t\t\t\tndst.ndts_allocs\t\t+= READ_ONCE(st-\u003eallocs);\n2352:\t\t\t\tndst.ndts_destroys\t\t+= READ_ONCE(st-\u003edestroys);\n2353:\t\t\t\tndst.ndts_hash_grows\t\t+= READ_ONCE(st-\u003ehash_grows);\n2354:\t\t\t\tndst.ndts_res_failed\t\t+= READ_ONCE(st-\u003eres_failed);\n2355:\t\t\t\tndst.ndts_lookups\t\t+= READ_ONCE(st-\u003elookups);\n2356:\t\t\t\tndst.ndts_hits\t\t\t+= READ_ONCE(st-\u003ehits);\n2357:\t\t\t\tndst.ndts_rcv_probes_mcast\t+= READ_ONCE(st-\u003ercv_probes_mcast);\n2358:\t\t\t\tndst.ndts_rcv_probes_ucast\t+= READ_ONCE(st-\u003ercv_probes_ucast);\n2359:\t\t\t\tndst.ndts_periodic_gc_runs\t+= READ_ONCE(st-\u003eperiodic_gc_runs);\n2360:\t\t\t\tndst.ndts_forced_gc_runs\t+= READ_ONCE(st-\u003eforced_gc_runs);\n2361:\t\t\t\tndst.ndts_table_fulls\t\t+= READ_ONCE(st-\u003etable_fulls);\n2362:\t\t\t}\n2363:\t\n2364:\t\t\tif (nla_put_64bit(skb, NDTA_STATS, sizeof(ndst), \u0026ndst,\n2365:\t\t\t\t\t  NDTA_PAD))\n2366:\t\t\t\tgoto nla_put_failure;\n2367:\t\t}\n2368:\t\n2369:\t\tBUG_ON(tbl-\u003eparms.dev);\n2370:\t\tif (neightbl_fill_parms(skb, \u0026tbl-\u003eparms) \u003c 0)\n2371:\t\t\tgoto nla_put_failure;\n2372:\t\n2373:\t\tnlmsg_end(skb, nlh);\n"
}

69/3 2026/08/13 08:36 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 55259
							output: 35
							thoughts: 47
						

Thoughts:
**Initial Assessment of `ndc` Initialization**

Okay, so I see `ndc` is being initialized using designated initializers, which is generally good practice for clarity. But hang on... something's not right. The `ndtc_hash_chain_gc` field isn't being initialized within `ndc` during this process. That's a potential oversight that could lead to undefined behavior or, at the very least, unpredictable garbage collection behavior later on. I need to verify what's going on with that struct `ndt_config` definition to determine if there's an implicit initialization happening somewhere else, or if this is a genuine bug. I need to be sure this crucial element is properly managed, and the documentation for that struct is not the cause for concern.



70/3 2026/08/13 08:36 tool grepper
0m Args:
{
  "Expression": "struct ndt_config",
  "PathPrefix": "include/uapi/linux/neighbour.h"
}

Results:
{
  "Output": "include/uapi/linux/neighbour.h=173=struct ndtmsg {\n--\ninclude/uapi/linux/neighbour.h-178-\ninclude/uapi/linux/neighbour.h:179:struct ndt_config {\ninclude/uapi/linux/neighbour.h-180-\t__u16\t\tndtc_key_len;\n--\ninclude/uapi/linux/neighbour.h=191=enum {\n--\ninclude/uapi/linux/neighbour.h-196-\tNDTA_THRESH3,\t\t\t/* u32 */\ninclude/uapi/linux/neighbour.h:197:\tNDTA_CONFIG,\t\t\t/* struct ndt_config, read-only */\ninclude/uapi/linux/neighbour.h-198-\tNDTA_PARMS,\t\t\t/* nested TLV NDTPA_* */\n"
}

71/3 2026/08/13 08:36 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 55724
							output: 38
							thoughts: 15
						

Thoughts:
Here's my take on the thought process, as requested:

**Diving into `struct ndt_config`**

Alright, let's get down to brass tacks. I need to fully understand `struct ndt_config`. I can't just glance at it; I need the *full* definition, all the fields, every bit. Context is crucial, and this is where it begins. I need to see what this struct is actually *comprised* of. What variables are in there? What data types? Are there any nested structures or pointers? Is it defined within another structure or union?

Knowing the complete picture will allow me to immediately evaluate its relevance to the task at hand. This is the foundation upon which any further understanding will be built. No shortcuts here. Get it right, get it done, move on. Let's see what we're working with.



72/3 2026/08/13 08:37 tool read-file
0m Args:
{
  "File": "include/uapi/linux/neighbour.h",
  "FirstLine": 179,
  "LineCount": 20
}

Results:
{
  "Contents": " 179:\tstruct ndt_config {\n 180:\t\t__u16\t\tndtc_key_len;\n 181:\t\t__u16\t\tndtc_entry_size;\n 182:\t\t__u32\t\tndtc_entries;\n 183:\t\t__u32\t\tndtc_last_flush;\t/* delta to now in msecs */\n 184:\t\t__u32\t\tndtc_last_rand;\t\t/* delta to now in msecs */\n 185:\t\t__u32\t\tndtc_hash_rnd;\n 186:\t\t__u32\t\tndtc_hash_mask;\n 187:\t\t__u32\t\tndtc_hash_chain_gc;\n 188:\t\t__u32\t\tndtc_proxy_qlen;\n 189:\t};\n 190:\t\n 191:\tenum {\n 192:\t\tNDTA_UNSPEC,\n 193:\t\tNDTA_NAME,\t\t\t/* char *, unchangeable */\n 194:\t\tNDTA_THRESH1,\t\t\t/* u32 */\n 195:\t\tNDTA_THRESH2,\t\t\t/* u32 */\n 196:\t\tNDTA_THRESH3,\t\t\t/* u32 */\n 197:\t\tNDTA_CONFIG,\t\t\t/* struct ndt_config, read-only */\n 198:\t\tNDTA_PARMS,\t\t\t/* nested TLV NDTPA_* */\n"
}

73/3 2026/08/13 08:37 llm kmsan-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 56341
							output: 341
							thoughts: 12078
						

Thoughts:
**Analysis of Potential Uninitialized Memory Exposure in Network Neighbor Table Refactoring**

Alright, let's break this down. My primary concern here is ensuring this refactor doesn't introduce any new security vulnerabilities due to uninitialized memory being exposed to userspace. I've gone through the code with a fine-toothed comb, tracing how `struct neigh_table` and related structures are initialized, used, and, critically, *exposed* to userspace.

First, I zeroed in on the `neightbl_fill_info` function, as it's a key point where information is conveyed to userspace. Initially, I was concerned about the removal (or lack thereof) of the `ndtc_hash_chain_gc` field within the `ndt_config` structure. But designated initializers in C, as I recall, guarantee zero-initialization of any unspecified fields. The patch just happened to show context lines, not an actual removal, and the existing code *also* didn't explicitly initialize that field. It's safe.

I then expanded my search, examining `neigh_table_register`, `neigh_table_init`, `neigh_alloc`, `pneigh_create`, and every function that potentially interfaces with user space through netlink messages. I paid close attention to where memory is allocated (`kmemdup`, `kzalloc`, `kmalloc`) and if the content is then copied. For example, the use of `kmemdup` in `neigh_table_register` concerned me at first, but ultimately it's alright; the original `neigh_table` is fully initialized, and kmemdup copies from it. The kernel itself consumes it internally, and the data is never directly exposed. Instead, individual pieces are passed via `nla_put` calls in functions like `neightbl_fill_parms`. I checked all these places and verified the same, with individual initialized variables being passed to userspace via `nla_put` or the structures are fully initialized before usage.

I meticulously traced all paths in those functions as well as `neigh_table_unregister`, `neigh_table_inherit`, and the various helper functions to see if any uninitialized data might be lurking. The key here is not *where* data is allocated but *how* and *when* it’s exposed, if at all. For example, `neigh_parms_alloc`, which copies the content of `neigh_table->parms`, seemed to need a closer look, but it uses `kmemdup` after that structure is initialized in `neigh_table_init`.

The removal of the `net` field from `struct pneigh_entry` also got my attention. However, after going through the code, I realized that that structure would implicitly belong to the network namespace. That structure also gets fully initialized during neighbor resolution. So the removal is an improvement as it simplifies the structure without introducing vulnerabilities.

I've also verified that the neighbor entry retrieval functions, such as `arp_req_get`, and dump functions, such as `neigh_stat_seq_show` and `arp_seq_show`, do *not* directly expose uninitialized memory. They only present valid, initialized data.

I'm now confident that this refactoring does not introduce any new exposure of uninitialized memory. The code consistently employs appropriate initialization techniques, and the mechanisms for transferring data to userspace (`nla_put_*`) are safe in this context. While I remain vigilant about potential memory safety issues, this particular patch appears sound from a KMSAN perspective. Standard KASAN fuzzing is sufficient for now.



74/3 2026/08/13 08:38 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch series refactors the neighbor discovery subsystem (ARP and IPv6 ND) to make neighbor tables (`arp_tbl` and `nd_tbl`) per-network-namespace instead of global variables. It does this by allocating a new `struct neigh_table` for each network namespace, copying the initial values from a template, and initializing the dynamic fields (locks, lists, workqueues, etc.). \n\nThere are no new structures or fields copied to user space. The `struct neigh_table` itself is an internal kernel structure and is never copied directly to user space. Information from the table is exposed to user space via netlink messages (e.g., `neightbl_fill_info`, `neightbl_fill_param_info`), which construct the messages using `nla_put_*` functions and designated initializers for structures like `ndt_config` and `ndt_stats`. All memory allocations for the tables, parameters, and hash buckets use zero-initializing functions (`kzalloc`, `alloc_percpu`, `__GFP_ZERO`) or `kmemdup` from fully initialized static templates.\n\nSince the patch does not introduce any uninitialized memory risks, info-leaks, or complex data structures sent to user space without proper initialization, a dedicated KMSAN fuzzing session is not justified. Standard KASAN, LOCKDEP, and other bug detectors are sufficient to catch any potential memory corruption, out-of-bounds access, or use-after-free bugs introduced by this refactoring.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch series refactors the neighbor discovery subsystem (ARP and IPv6 ND) to make neighbor tables (`arp_tbl` and `nd_tbl`) per-network-namespace instead of global variables. It does this by allocating a new `struct neigh_table` for each network namespace, copying the initial values from a template, and initializing the dynamic fields (locks, lists, workqueues, etc.). \n\nThere are no new structures or fields copied to user space. The `struct neigh_table` itself is an internal kernel structure and is never copied directly to user space. Information from the table is exposed to user space via netlink messages (e.g., `neightbl_fill_info`, `neightbl_fill_param_info`), which construct the messages using `nla_put_*` functions and designated initializers for structures like `ndt_config` and `ndt_stats`. All memory allocations for the tables, parameters, and hash buckets use zero-initializing functions (`kzalloc`, `alloc_percpu`, `__GFP_ZERO`) or `kmemdup` from fully initialized static templates.\n\nSince the patch does not introduce any uninitialized memory risks, info-leaks, or complex data structures sent to user space without proper initialization, a dedicated KMSAN fuzzing session is not justified. Standard KASAN, LOCKDEP, and other bug detectors are sufficient to catch any potential memory corruption, out-of-bounds access, or use-after-free bugs introduced by this refactoring.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)