* [PATCH v1 net-next 0/8] neighbour: Add IPv4/IPv6-specific wrappers for neigh_lookup() and friends.
@ 2026-09-30 20:03 Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 1/8] ipv4: Rename ipv4_neigh_lookup() to ipv4_dst_neigh_lookup() Kuniyuki Iwashima
` (7 more replies)
0 siblings, 8 replies; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-30 20:03 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
syzbot reported that ip6_finish_output2() could pass tbl and dev
from different netns to neigh_create().
When namespacifying neigh_table, I chose to resolve neigh_table
in callers to minimise changes, but it turned out to be error-prone.
This series adds IPv4/IPv6-specific helpers for neigh_lookup()
and neigh_create() that always fetch the netns from dev to fix
the problem.
Along the way, the confusing and now mostly redundant helpers
__neigh_lookup() and __neigh_lookup_errno() are converted and
removed.
Kuniyuki Iwashima (8):
ipv4: Rename ipv4_neigh_lookup() to ipv4_dst_neigh_lookup().
ipv6: Rename ip6_neigh_lookup() to __ip6_dst_neigh_lookup().
ipv4: Add wrappers for neigh_lookup() and neigh_create().
ipv6: Add wrappers for neigh_lookup() and (__)?neigh_create().
mlxsw: spectrum: Resolve neigh_table right before neigh_lookup().
ipv6: Use ipv6_neigh_lookup() and friends.
ipv4: Use ipv4_neigh_lookup() and friends.
neighbour: Remove __neigh_lookup() and __neigh_lookup_errno().
.../marvell/prestera/prestera_router.c | 14 ++--
.../mellanox/mlx5/core/en/tc_tun_encap.c | 14 ++--
.../mellanox/mlx5/core/en_accel/ipsec.c | 6 +-
.../ethernet/mellanox/mlxsw/spectrum_router.c | 77 +++++++++++--------
.../ethernet/mellanox/mlxsw/spectrum_span.c | 42 ++++++----
.../netronome/nfp/flower/tunnel_conf.c | 8 +-
drivers/net/ethernet/rocker/rocker_ofdpa.c | 2 +-
drivers/net/ethernet/sfc/tc_counters.c | 11 +--
drivers/net/vrf.c | 4 +-
drivers/net/vxlan/vxlan_core.c | 14 +---
include/net/arp.h | 16 ++++
include/net/ip6_route.h | 6 +-
include/net/ndisc.h | 31 ++++++--
include/net/neighbour.h | 24 ------
net/bridge/br_arp_nd_proxy.c | 4 +-
net/core/neighbour.c | 9 ++-
net/ieee802154/6lowpan/tx.c | 3 +-
net/ipv4/arp.c | 26 ++++---
net/ipv4/fib_semantics.c | 5 +-
net/ipv4/route.c | 18 ++---
net/ipv6/ip6_output.c | 2 +-
net/ipv6/ndisc.c | 48 +++++++-----
net/ipv6/route.c | 23 +++---
net/sched/sch_teql.c | 4 +-
24 files changed, 222 insertions(+), 189 deletions(-)
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v1 net-next 1/8] ipv4: Rename ipv4_neigh_lookup() to ipv4_dst_neigh_lookup().
2026-09-30 20:03 [PATCH v1 net-next 0/8] neighbour: Add IPv4/IPv6-specific wrappers for neigh_lookup() and friends Kuniyuki Iwashima
@ 2026-09-30 20:03 ` Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 2/8] ipv6: Rename ip6_neigh_lookup() to __ip6_dst_neigh_lookup() Kuniyuki Iwashima
` (6 subsequent siblings)
7 siblings, 0 replies; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-30 20:03 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
Later, we will add ipv4_neigh_lookup() as a wrapper for
neigh_lookup(arp_table(net), ...).
The existing ipv4_neigh_lookup() is more like ip6_dst_neigh_lookup().
Let's rename ipv4_neigh_lookup() to ipv4_dst_neigh_lookup().
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
net/ipv4/route.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index d7da2f1acbb5..50c842617e45 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -146,9 +146,9 @@ static u32 *ipv4_cow_metrics(struct dst_entry *dst, unsigned long old)
return NULL;
}
-static struct neighbour *ipv4_neigh_lookup(const struct dst_entry *dst,
- struct sk_buff *skb,
- const void *daddr);
+static struct neighbour *ipv4_dst_neigh_lookup(const struct dst_entry *dst,
+ struct sk_buff *skb,
+ const void *daddr);
static void ipv4_confirm_neigh(const struct dst_entry *dst, const void *daddr);
static struct dst_ops ipv4_dst_ops = {
@@ -163,7 +163,7 @@ static struct dst_ops ipv4_dst_ops = {
.update_pmtu = ip_rt_update_pmtu,
.redirect = ip_do_redirect,
.local_out = __ip_local_out,
- .neigh_lookup = ipv4_neigh_lookup,
+ .neigh_lookup = ipv4_dst_neigh_lookup,
.confirm_neigh = ipv4_confirm_neigh,
};
@@ -409,9 +409,9 @@ void rt_cache_flush(struct net *net)
rt_genid_bump_ipv4(net);
}
-static struct neighbour *ipv4_neigh_lookup(const struct dst_entry *dst,
- struct sk_buff *skb,
- const void *daddr)
+static struct neighbour *ipv4_dst_neigh_lookup(const struct dst_entry *dst,
+ struct sk_buff *skb,
+ const void *daddr)
{
const struct rtable *rt = container_of(dst, struct rtable, dst);
struct net_device *dev;
@@ -2910,7 +2910,7 @@ struct rtable *ip_route_output_key_hash_rcu(struct net *net, struct flowi4 *fl4,
static struct dst_ops ipv4_dst_blackhole_ops = {
.family = AF_INET,
.default_advmss = ipv4_default_advmss,
- .neigh_lookup = ipv4_neigh_lookup,
+ .neigh_lookup = ipv4_dst_neigh_lookup,
.check = dst_blackhole_check,
.cow_metrics = dst_blackhole_cow_metrics,
.update_pmtu = dst_blackhole_update_pmtu,
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v1 net-next 2/8] ipv6: Rename ip6_neigh_lookup() to __ip6_dst_neigh_lookup().
2026-09-30 20:03 [PATCH v1 net-next 0/8] neighbour: Add IPv4/IPv6-specific wrappers for neigh_lookup() and friends Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 1/8] ipv4: Rename ipv4_neigh_lookup() to ipv4_dst_neigh_lookup() Kuniyuki Iwashima
@ 2026-09-30 20:03 ` Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 3/8] ipv4: Add wrappers for neigh_lookup() and neigh_create() Kuniyuki Iwashima
` (5 subsequent siblings)
7 siblings, 0 replies; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-30 20:03 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
Later, we will add ipv6_neigh_lookup() as a wrapper for
neigh_lookup(nd_table(net), ...).
The existing ip6_neigh_lookup() is called from ip6_dst_neigh_lookup()
with a gateway address.
Let's rename ip6_neigh_lookup() to __ip6_dst_neigh_lookup().
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
include/net/ip6_route.h | 6 +++---
net/ipv6/ndisc.c | 12 ++++++------
net/ipv6/route.c | 12 ++++++------
3 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index 0f9b7a260d25..5a21ed4b6e1c 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -430,7 +430,7 @@ u32 ip6_mtu_from_fib6(const struct fib6_result *res,
const struct in6_addr *daddr,
const struct in6_addr *saddr);
-struct neighbour *ip6_neigh_lookup(const struct in6_addr *gw,
- struct net_device *dev, struct sk_buff *skb,
- const void *daddr);
+struct neighbour *__ip6_dst_neigh_lookup(const struct in6_addr *gw,
+ struct net_device *dev, struct sk_buff *skb,
+ const void *daddr);
#endif
diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index 12d85d7f8234..e1cbffaa0ad0 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -1359,9 +1359,9 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)
/* routes added from RAs do not use nexthop objects */
rt = rt6_get_dflt_router(net, &ipv6_hdr(skb)->saddr, skb->dev);
if (rt) {
- neigh = ip6_neigh_lookup(&rt->fib6_nh->fib_nh_gw6,
- rt->fib6_nh->fib_nh_dev, NULL,
- &ipv6_hdr(skb)->saddr);
+ neigh = __ip6_dst_neigh_lookup(&rt->fib6_nh->fib_nh_gw6,
+ rt->fib6_nh->fib_nh_dev, NULL,
+ &ipv6_hdr(skb)->saddr);
if (!neigh) {
net_err_ratelimited("RA: %s got default router without neighbour\n",
__func__);
@@ -1395,9 +1395,9 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)
return reason;
}
- neigh = ip6_neigh_lookup(&rt->fib6_nh->fib_nh_gw6,
- rt->fib6_nh->fib_nh_dev, NULL,
- &ipv6_hdr(skb)->saddr);
+ neigh = __ip6_dst_neigh_lookup(&rt->fib6_nh->fib_nh_gw6,
+ rt->fib6_nh->fib_nh_dev, NULL,
+ &ipv6_hdr(skb)->saddr);
if (!neigh) {
net_err_ratelimited("RA: %s got default router without neighbour\n",
__func__);
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index a76869ff87cd..8baac4d85251 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -206,10 +206,10 @@ static inline const void *choose_neigh_daddr(const struct in6_addr *p,
return daddr;
}
-struct neighbour *ip6_neigh_lookup(const struct in6_addr *gw,
- struct net_device *dev,
- struct sk_buff *skb,
- const void *daddr)
+struct neighbour *__ip6_dst_neigh_lookup(const struct in6_addr *gw,
+ struct net_device *dev,
+ struct sk_buff *skb,
+ const void *daddr)
{
struct neighbour *n;
@@ -228,8 +228,8 @@ static struct neighbour *ip6_dst_neigh_lookup(const struct dst_entry *dst,
{
const struct rt6_info *rt = dst_rt6_info(dst);
- return ip6_neigh_lookup(rt6_nexthop(rt, &in6addr_any),
- dst_dev(dst), skb, daddr);
+ return __ip6_dst_neigh_lookup(rt6_nexthop(rt, &in6addr_any),
+ dst_dev(dst), skb, daddr);
}
static void ip6_confirm_neigh(const struct dst_entry *dst, const void *daddr)
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v1 net-next 3/8] ipv4: Add wrappers for neigh_lookup() and neigh_create().
2026-09-30 20:03 [PATCH v1 net-next 0/8] neighbour: Add IPv4/IPv6-specific wrappers for neigh_lookup() and friends Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 1/8] ipv4: Rename ipv4_neigh_lookup() to ipv4_dst_neigh_lookup() Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 2/8] ipv6: Rename ip6_neigh_lookup() to __ip6_dst_neigh_lookup() Kuniyuki Iwashima
@ 2026-09-30 20:03 ` Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 4/8] ipv6: Add wrappers for neigh_lookup() and (__)?neigh_create() Kuniyuki Iwashima
` (4 subsequent siblings)
7 siblings, 0 replies; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-30 20:03 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
neigh_lookup() accepts struct neigh_table *tbl and struct
net_device *dev, and both of them must exist in the same
netns.
It is error-prone to require callers to fetch a netns and
get neigh_table, which might belong to a different netns
than dev_net(dev).
Let's add IPv4-specific wrappers for neigh_lookup() and
neigh_create() that always fetch netns from dev.
Note that we do not add wrappers for __neigh_lookup() and
__neigh_lookup_errno(), which we will remove later.
__neigh_lookup_errno() is a simple combo of neigh_lookup()
and neigh_create(). __neigh_lookup() is almost the same
but converts errno from neigh_create() to NULL.
So both names are confusing.
For IPv4, __neigh_lookup_errno() is only used in arp_req_set()
and is not worth a new wrapper. Instead, it should be inlined.
arp_process() uses __neigh_lookup(create=0) but it should
simply be neigh_lookup(), and then __neigh_lookup(create=1)
is only used in arp_process(), so this should be inlined too.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
include/net/arp.h | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/include/net/arp.h b/include/net/arp.h
index e932def63d62..329a5456987a 100644
--- a/include/net/arp.h
+++ b/include/net/arp.h
@@ -51,6 +51,22 @@ static inline struct neighbour *__ipv4_neigh_lookup(struct net_device *dev, u32
return n;
}
+static inline struct neighbour *ipv4_neigh_lookup(struct net_device *dev,
+ const void *pkey)
+{
+ struct neigh_table *tbl = arp_table(dev_net(dev));
+
+ return neigh_lookup(tbl, pkey, dev);
+}
+
+static inline struct neighbour *ipv4_neigh_create(struct net_device *dev,
+ const void *pkey)
+{
+ struct neigh_table *tbl = arp_table(dev_net(dev));
+
+ return neigh_create(tbl, pkey, dev);
+}
+
static inline void __ipv4_confirm_neigh(struct net_device *dev, u32 key)
{
struct neighbour *n;
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v1 net-next 4/8] ipv6: Add wrappers for neigh_lookup() and (__)?neigh_create().
2026-09-30 20:03 [PATCH v1 net-next 0/8] neighbour: Add IPv4/IPv6-specific wrappers for neigh_lookup() and friends Kuniyuki Iwashima
` (2 preceding siblings ...)
2026-09-30 20:03 ` [PATCH v1 net-next 3/8] ipv4: Add wrappers for neigh_lookup() and neigh_create() Kuniyuki Iwashima
@ 2026-09-30 20:03 ` Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 5/8] mlxsw: spectrum: Resolve neigh_table right before neigh_lookup() Kuniyuki Iwashima
` (3 subsequent siblings)
7 siblings, 0 replies; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-30 20:03 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
neigh_lookup() accepts struct neigh_table *tbl and struct
net_device *dev, and both of them must exist in the same
netns.
It is error-prone to require callers to fetch a netns and
get neigh_table, which might belong to a different netns
than dev_net(dev).
Let's add IPv6-specific wrappers for neigh_lookup() and
(__)?neigh_create() that always fetch netns from dev.
Similar to IPv4, we do not add wrappers for __neigh_lookup()
since 3 out of 4 users call it with create=1, which should be
simply written as neigh_lookup() and neigh_create().
IPv6 has 3 callers of __neigh_create(want_ref=false), so
ipv6_neigh_create_noref() is also added.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
include/net/ndisc.h | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/include/net/ndisc.h b/include/net/ndisc.h
index 96e3bb6e83af..2fce6fccf55a 100644
--- a/include/net/ndisc.h
+++ b/include/net/ndisc.h
@@ -381,6 +381,30 @@ static inline struct neighbour *__ipv6_neigh_lookup(struct net_device *dev, cons
return n;
}
+static inline struct neighbour *ipv6_neigh_lookup(struct net_device *dev,
+ const void *pkey)
+{
+ struct neigh_table *tbl = nd_table(dev_net(dev));
+
+ return neigh_lookup(tbl, pkey, dev);
+}
+
+static inline struct neighbour *ipv6_neigh_create(struct net_device *dev,
+ const void *pkey)
+{
+ struct neigh_table *tbl = nd_table(dev_net(dev));
+
+ return neigh_create(tbl, pkey, dev);
+}
+
+static inline struct neighbour *ipv6_neigh_create_noref(struct net_device *dev,
+ const void *pkey)
+{
+ struct neigh_table *tbl = nd_table(dev_net(dev));
+
+ return __neigh_create(tbl, pkey, dev, false);
+}
+
static inline void __ipv6_confirm_neigh(struct net_device *dev,
const void *pkey)
{
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v1 net-next 5/8] mlxsw: spectrum: Resolve neigh_table right before neigh_lookup().
2026-09-30 20:03 [PATCH v1 net-next 0/8] neighbour: Add IPv4/IPv6-specific wrappers for neigh_lookup() and friends Kuniyuki Iwashima
` (3 preceding siblings ...)
2026-09-30 20:03 ` [PATCH v1 net-next 4/8] ipv6: Add wrappers for neigh_lookup() and (__)?neigh_create() Kuniyuki Iwashima
@ 2026-09-30 20:03 ` Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 6/8] ipv6: Use ipv6_neigh_lookup() and friends Kuniyuki Iwashima
` (2 subsequent siblings)
7 siblings, 0 replies; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-30 20:03 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev,
Petr Machata
We will replace neigh_lookup() and neigh_create() with
IPv4/IPv6-specific helpers.
mlxsw has two paths where neigh_table is fetched in advance
and passed down to a function which calls neigh_lookup() and
neigh_create().
To keep the changes simple, let's prepare them beforehand
by delaying the neigh_table resolution until right before
neigh_lookup().
struct mlxsw_sp_nexthop caches neigh_tbl, so it is replaced with
family, and a new helper, mlxsw_sp_nexthop_neigh_lookup(),
resolves neigh_table and calls neigh_lookup() and neigh_create().
Similarly, mlxsw_sp_span_entry_gretap[46]_parms() now pass family
to mlxsw_sp_span_dmac() to resolve neigh_table there just before
neigh_lookup() and neigh_create().
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
Cc: Petr Machata <petrm@nvidia.com>
---
.../ethernet/mellanox/mlxsw/spectrum_router.c | 65 ++++++++++++-------
.../ethernet/mellanox/mlxsw/spectrum_span.c | 31 +++++----
2 files changed, 58 insertions(+), 38 deletions(-)
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
index f4a435885ba4..ba8a7a44ce9e 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
@@ -3072,9 +3072,9 @@ struct mlxsw_sp_nexthop {
* this nexthop belongs to
*/
struct rhash_head ht_node;
- struct neigh_table *neigh_tbl;
struct mlxsw_sp_nexthop_key key;
unsigned char gw_addr[sizeof(struct in6_addr)];
+ int family;
int ifindex;
int nh_weight;
int norm_nh_weight;
@@ -4300,28 +4300,49 @@ static void __mlxsw_sp_nexthop_neigh_update(struct mlxsw_sp_nexthop *nh,
nh->update = 1;
}
+static struct neighbour *
+mlxsw_sp_nexthop_neigh_lookup(struct mlxsw_sp_nexthop *nh)
+{
+ struct neigh_table *tbl;
+ struct net_device *dev;
+ struct neighbour *n;
+ struct net *net;
+
+ dev = mlxsw_sp_nexthop_dev(nh);
+ net = dev_net(dev);
+
+#if IS_ENABLED(CONFIG_IPV6)
+ if (nh->family == AF_INET6)
+ tbl = nd_table(net);
+ else
+#endif
+ tbl = arp_table(net);
+
+ n = neigh_lookup(tbl, &nh->gw_addr, dev);
+ if (!n) {
+ n = neigh_create(tbl, &nh->gw_addr, dev);
+ if (!IS_ERR(n))
+ neigh_event_send(n, NULL);
+ }
+
+ return n;
+}
+
static int
mlxsw_sp_nexthop_dead_neigh_replace(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_neigh_entry *neigh_entry)
{
struct neighbour *n, *old_n = neigh_entry->key.n;
struct mlxsw_sp_nexthop *nh;
- struct net_device *dev;
bool entry_connected;
u8 nud_state, dead;
int err;
nh = list_first_entry(&neigh_entry->nexthop_list,
struct mlxsw_sp_nexthop, neigh_list_node);
- dev = mlxsw_sp_nexthop_dev(nh);
-
- n = neigh_lookup(nh->neigh_tbl, &nh->gw_addr, dev);
- if (!n) {
- n = neigh_create(nh->neigh_tbl, &nh->gw_addr, dev);
- if (IS_ERR(n))
- return PTR_ERR(n);
- neigh_event_send(n, NULL);
- }
+ n = mlxsw_sp_nexthop_neigh_lookup(nh);
+ if (IS_ERR(n))
+ return PTR_ERR(n);
mlxsw_sp_neigh_entry_remove(mlxsw_sp, neigh_entry);
neigh_entry->key.n = n;
@@ -4402,7 +4423,6 @@ static int mlxsw_sp_nexthop_neigh_init(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_nexthop *nh)
{
struct mlxsw_sp_neigh_entry *neigh_entry;
- struct net_device *dev;
struct neighbour *n;
u8 nud_state, dead;
int err;
@@ -4412,20 +4432,16 @@ static int mlxsw_sp_nexthop_neigh_init(struct mlxsw_sp *mlxsw_sp,
if (!nh->nhgi->gateway || nh->neigh_entry)
return 0;
- dev = mlxsw_sp_nexthop_dev(nh);
/* Take a reference of neigh here ensuring that neigh would
* not be destructed before the nexthop entry is finished.
* The reference is taken either in neigh_lookup() or
* in neigh_create() in case n is not found.
*/
- n = neigh_lookup(nh->neigh_tbl, &nh->gw_addr, dev);
- if (!n) {
- n = neigh_create(nh->neigh_tbl, &nh->gw_addr, dev);
- if (IS_ERR(n))
- return PTR_ERR(n);
- neigh_event_send(n, NULL);
- }
+ n = mlxsw_sp_nexthop_neigh_lookup(nh);
+ if (IS_ERR(n))
+ return PTR_ERR(n);
+
neigh_entry = mlxsw_sp_neigh_entry_lookup(mlxsw_sp, n);
if (!neigh_entry) {
neigh_entry = mlxsw_sp_neigh_entry_create(mlxsw_sp, n);
@@ -4630,7 +4646,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_table(mlxsw_sp_net(mlxsw_sp));
+ nh->family = AF_INET;
err = mlxsw_sp_nexthop_insert(mlxsw_sp, nh);
if (err)
return err;
@@ -5120,7 +5136,6 @@ 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;
@@ -5129,12 +5144,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_table(net);
+ nh->family = AF_INET;
break;
case AF_INET6:
memcpy(&nh->gw_addr, &nh_obj->ipv6, sizeof(nh_obj->ipv6));
#if IS_ENABLED(CONFIG_IPV6)
- nh->neigh_tbl = nd_table(net);
+ nh->family = AF_INET6;
#endif
break;
}
@@ -6990,7 +7005,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_table(mlxsw_sp_net(mlxsw_sp));
+ nh->family = AF_INET6;
#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 1cd8347c274d..d34d2040177b 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
@@ -224,14 +224,24 @@ struct mlxsw_sp_span_entry_ops mlxsw_sp_span_entry_ops_phys = {
.deconfigure = mlxsw_sp_span_entry_phys_deconfigure,
};
-static int mlxsw_sp_span_dmac(struct neigh_table *tbl,
+static int mlxsw_sp_span_dmac(int family,
const void *pkey,
struct net_device *dev,
unsigned char dmac[ETH_ALEN])
{
- struct neighbour *neigh = neigh_lookup(tbl, pkey, dev);
+ struct net *net = dev_net(dev);
+ struct neigh_table *tbl;
+ struct neighbour *neigh;
int err = 0;
+#if IS_ENABLED(CONFIG_IPV6_GRE)
+ if (family == AF_INET6)
+ tbl = nd_table(net);
+ else
+#endif
+ tbl = arp_table(net);
+
+ neigh = neigh_lookup(tbl, pkey, dev);
if (!neigh) {
neigh = neigh_create(tbl, pkey, dev);
if (IS_ERR(neigh))
@@ -355,8 +365,7 @@ mlxsw_sp_span_entry_tunnel_parms_common(struct net_device *edev,
union mlxsw_sp_l3addr saddr,
union mlxsw_sp_l3addr daddr,
union mlxsw_sp_l3addr gw,
- __u8 ttl,
- struct neigh_table *tbl,
+ __u8 ttl, int family,
struct mlxsw_sp_span_parms *sparmsp)
{
unsigned char dmac[ETH_ALEN];
@@ -365,7 +374,7 @@ mlxsw_sp_span_entry_tunnel_parms_common(struct net_device *edev,
if (mlxsw_sp_l3addr_is_zero(gw))
gw = daddr;
- if (!edev || mlxsw_sp_span_dmac(tbl, &gw, edev, dmac))
+ if (!edev || mlxsw_sp_span_dmac(family, &gw, edev, dmac))
goto unoffloadable;
if (is_vlan_dev(edev))
@@ -456,7 +465,6 @@ 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) ||
@@ -470,11 +478,10 @@ 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,
- tbl, sparmsp);
+ AF_INET, sparmsp);
}
static int
@@ -564,7 +571,6 @@ 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) ||
@@ -578,11 +584,10 @@ 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,
- tbl, sparmsp);
+ AF_INET6, sparmsp);
}
static int
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v1 net-next 6/8] ipv6: Use ipv6_neigh_lookup() and friends.
2026-09-30 20:03 [PATCH v1 net-next 0/8] neighbour: Add IPv4/IPv6-specific wrappers for neigh_lookup() and friends Kuniyuki Iwashima
` (4 preceding siblings ...)
2026-09-30 20:03 ` [PATCH v1 net-next 5/8] mlxsw: spectrum: Resolve neigh_table right before neigh_lookup() Kuniyuki Iwashima
@ 2026-09-30 20:03 ` Kuniyuki Iwashima
2026-10-02 2:03 ` netdev-bot+sashiko
2026-09-30 20:03 ` [PATCH v1 net-next 7/8] ipv4: Use ipv4_neigh_lookup() " Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 8/8] neighbour: Remove __neigh_lookup() and __neigh_lookup_errno() Kuniyuki Iwashima
7 siblings, 1 reply; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-30 20:03 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev,
syzbot+5a8857f0b4a0a7b12c62, Saeed Mahameed, Leon Romanovsky,
Tariq Toukan, Mark Bloch, Petr Machata, Edward Cree,
Nikolay Aleksandrov, Alexander Aring, Stefan Schmidt,
Miquel Raynal
syzbot reported the splat below in neigh_mark_dead() [0]
where tbl->lock was not held while neigh_ifdown() should
have acquired one.
This only happens when a neigh_table accidentally has a
neighbour from a different netns.
In ip6_finish_output2(), the net argument is the caller's and
does not always match dev_net(dev) fetched from dst. (e.g. xfrm)
It is error-prone to require callers to fetch netns and
neigh_table and pass it with dev to neigh_lookup(), etc.
Let's replace neigh_lookup() and friends with IPv6 helpers.
Note that __neigh_lookup() is split into ipv6_neigh_lookup()
and ipv6_neigh_create().
[0]:
debug_locks && !(lock_is_held(&(&n->tbl->lock)->dep_map) != 0)
WARNING: net/core/neighbour.c:154 at neigh_mark_dead+0x2fb/0x340 net/core/neighbour.c:154, CPU#0: syz.0.857/9765
Modules linked in:
CPU: 0 UID: 0 PID: 9765 Comm: syz.0.857 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/24/2026
RIP: 0010:neigh_mark_dead+0x2fb/0x340 net/core/neighbour.c:154
Code: e8 03 42 80 3c 28 00 74 08 48 89 df e8 ee 9a 80 f8 4c 89 33 5b 41 5c 41 5d 41 5e 41 5f 5d c3 cc cc cc cc cc e8 86 95 10 f8 90 <0f> 0b 90 e9 8f fd ff ff 48 c7 c1 80 9a 7a 90 80 e1 07 80 c1 03 38
RSP: 0018:ffffc90003726600 EFLAGS: 00010287
RAX: ffffffff89b7467a RBX: ffff888022e86000 RCX: 0000000000080000
RDX: ffffc900047fa000 RSI: 0000000000003e08 RDI: 0000000000003e09
RBP: 0000000000000000 R08: 0000000000000003 R09: 0000000000000004
R10: dffffc0000000000 R11: fffff520006e4cb8 R12: ffff888022e8613c
R13: dffffc0000000000 R14: ffff888022e86020 R15: ffff888022e86000
FS: 00007f9c30f046c0(0000) GS:ffff888124cc6000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000000000000 CR3: 0000000076c56000 CR4: 00000000003526f0
Call Trace:
<TASK>
neigh_flush_one+0x27c/0x5a0 net/core/neighbour.c:388
neigh_flush_dev net/core/neighbour.c:432 [inline]
__neigh_ifdown+0x1fb/0xc70 net/core/neighbour.c:465
neigh_ifdown+0x1f/0x30 net/core/neighbour.c:487
rt6_disable_ip+0x751/0x7e0 net/ipv6/route.c:5058
addrconf_ifdown+0x155/0x1ad0 net/ipv6/addrconf.c:3892
addrconf_notify+0x1bc/0x1050 net/ipv6/addrconf.c:-1
notifier_call_chain+0x1a5/0x3d0 kernel/notifier.c:85
call_netdevice_notifiers_mtu net/core/dev.c:2350 [inline]
netif_set_mtu_ext+0x5ac/0x830 net/core/dev.c:9963
do_setlink+0xa81/0x47a0 net/core/rtnetlink.c:3247
rtnl_setlink+0x578/0x820 net/core/rtnetlink.c:3623
rtnetlink_rcv_msg+0x802/0xc00 net/core/rtnetlink.c:7159
netlink_rcv_skb+0x226/0x4a0 net/netlink/af_netlink.c:2572
netlink_unicast_kernel net/netlink/af_netlink.c:1335 [inline]
netlink_unicast+0x7bd/0x940 net/netlink/af_netlink.c:1361
netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1916
sock_sendmsg_nosec+0x13a/0x180 net/socket.c:800
__sock_sendmsg net/socket.c:815 [inline]
sock_write_iter+0x2de/0x3e0 net/socket.c:1266
do_iter_readv_writev+0x612/0x8c0 fs/read_write.c:-1
vfs_writev+0x343/0x990 fs/read_write.c:1058
do_writev+0x154/0x2e0 fs/read_write.c:1104
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f9c2ff9e159
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f9c30f04028 EFLAGS: 00000246 ORIG_RAX: 0000000000000014
RAX: ffffffffffffffda RBX: 00007f9c30225fa0 RCX: 00007f9c2ff9e159
RDX: 0000000000000001 RSI: 00002000000003c0 RDI: 0000000000000004
RBP: 00007f9c3003506b R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f9c30226038 R14: 00007f9c30225fa0 R15: 00007ffed3c74808
</TASK>
Fixes: df5f4f372de5 ("ipv6: Replace &nd_tbl with nd_table(net).")
Reported-by: syzbot+5a8857f0b4a0a7b12c62@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/netdev/6abd69a9.80e1c6cc.22483f.0005.GAE@google.com/
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
Cc: Saeed Mahameed <saeedm@nvidia.com>
Cc: Leon Romanovsky <leon@kernel.org>
Cc: Tariq Toukan <tariqt@nvidia.com>
Cc: Mark Bloch <mbloch@nvidia.com>
Cc: Petr Machata <petrm@nvidia.com>
Cc: Edward Cree <ecree.xilinx@gmail.com>
Cc: Nikolay Aleksandrov <razor@blackwall.org>
Cc: Alexander Aring <alex.aring@gmail.com>
Cc: Stefan Schmidt <stefan@datenfreihafen.org>
Cc: Miquel Raynal <miquel.raynal@bootlin.com>
---
.../mellanox/mlx5/core/en/tc_tun_encap.c | 13 +++----
.../ethernet/mellanox/mlxsw/spectrum_router.c | 29 ++++++++-------
.../ethernet/mellanox/mlxsw/spectrum_span.c | 24 ++++++++-----
.../netronome/nfp/flower/tunnel_conf.c | 4 +--
drivers/net/ethernet/sfc/tc_counters.c | 5 ++-
drivers/net/vrf.c | 4 +--
drivers/net/vxlan/vxlan_core.c | 6 ++--
include/net/ndisc.h | 7 ++--
net/bridge/br_arp_nd_proxy.c | 2 +-
net/ieee802154/6lowpan/tx.c | 3 +-
net/ipv4/fib_semantics.c | 2 +-
net/ipv6/ip6_output.c | 2 +-
net/ipv6/ndisc.c | 36 ++++++++++++-------
net/ipv6/route.c | 11 +++---
14 files changed, 79 insertions(+), 69 deletions(-)
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 67c12ca19d59..fe0258375ef6 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
@@ -439,21 +439,18 @@ void mlx5e_tc_update_neigh_used_value(struct mlx5e_neigh_hash_entry *nhe)
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;
+ /* find the relevant neigh according to the cached device and
+ * dst ip pair
+ */
#if IS_ENABLED(CONFIG_IPV6)
if (m_neigh->family != AF_INET)
- tbl = nd_table(net);
+ n = ipv6_neigh_lookup(dev, &m_neigh->dst_ip);
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, dev);
+ n = neigh_lookup(arp_table(net), &m_neigh->dst_ip, dev);
if (!n)
return;
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
index ba8a7a44ce9e..1f4753213b9c 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
@@ -2449,8 +2449,6 @@ 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;
@@ -2464,9 +2462,8 @@ 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(tbl, &dip, dev);
+ n = ipv6_neigh_lookup(dev, &dip);
if (!n)
return;
@@ -4312,17 +4309,23 @@ mlxsw_sp_nexthop_neigh_lookup(struct mlxsw_sp_nexthop *nh)
net = dev_net(dev);
#if IS_ENABLED(CONFIG_IPV6)
- if (nh->family == AF_INET6)
- tbl = nd_table(net);
- else
+ if (nh->family == AF_INET6) {
+ n = ipv6_neigh_lookup(dev, &nh->gw_addr);
+ if (!n) {
+ n = ipv6_neigh_create(dev, &nh->gw_addr);
+ if (!IS_ERR(n))
+ neigh_event_send(n, NULL);
+ }
+ } else
#endif
+ {
tbl = arp_table(net);
-
- n = neigh_lookup(tbl, &nh->gw_addr, dev);
- if (!n) {
- n = neigh_create(tbl, &nh->gw_addr, dev);
- if (!IS_ERR(n))
- neigh_event_send(n, NULL);
+ n = neigh_lookup(tbl, &nh->gw_addr, dev);
+ if (!n) {
+ n = neigh_create(tbl, &nh->gw_addr, dev);
+ if (!IS_ERR(n))
+ neigh_event_send(n, NULL);
+ }
}
return n;
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
index d34d2040177b..79947f68b10d 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
@@ -235,17 +235,23 @@ static int mlxsw_sp_span_dmac(int family,
int err = 0;
#if IS_ENABLED(CONFIG_IPV6_GRE)
- if (family == AF_INET6)
- tbl = nd_table(net);
- else
+ if (family == AF_INET6) {
+ neigh = ipv6_neigh_lookup(dev, pkey);
+ if (!neigh) {
+ neigh = ipv6_neigh_create(dev, pkey);
+ if (IS_ERR(neigh))
+ return PTR_ERR(neigh);
+ }
+ } else
#endif
+ {
tbl = arp_table(net);
-
- neigh = neigh_lookup(tbl, pkey, dev);
- if (!neigh) {
- neigh = neigh_create(tbl, pkey, dev);
- if (IS_ERR(neigh))
- return PTR_ERR(neigh);
+ neigh = neigh_lookup(tbl, pkey, dev);
+ if (!neigh) {
+ neigh = neigh_create(tbl, pkey, dev);
+ if (IS_ERR(neigh))
+ return PTR_ERR(neigh);
+ }
}
neigh_event_send(neigh, NULL);
diff --git a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
index d650d33e3709..27d80ec8e895 100644
--- a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
+++ b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
@@ -253,7 +253,6 @@ 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;
@@ -280,8 +279,7 @@ void nfp_tunnel_keep_alive_v6(struct nfp_app *app, struct sk_buff *skb)
if (!netdev)
continue;
- tbl = nd_table(dev_net(netdev));
- n = neigh_lookup(tbl, ipv6_add, netdev);
+ n = ipv6_neigh_lookup(netdev, ipv6_add);
if (!n)
continue;
diff --git a/drivers/net/ethernet/sfc/tc_counters.c b/drivers/net/ethernet/sfc/tc_counters.c
index 793a562fc1f7..ae6cc6b93864 100644
--- a/drivers/net/ethernet/sfc/tc_counters.c
+++ b/drivers/net/ethernet/sfc/tc_counters.c
@@ -116,9 +116,8 @@ static void efx_tc_counter_work(struct work_struct *work)
encap->neigh->egdev);
else
#if IS_ENABLED(CONFIG_IPV6)
- n = neigh_lookup(nd_table(net),
- &encap->neigh->dst_ip6,
- encap->neigh->egdev);
+ n = ipv6_neigh_lookup(encap->neigh->egdev,
+ &encap->neigh->dst_ip6);
#else
n = NULL;
#endif
diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c
index 320f3584a43b..79d433f49f80 100644
--- a/drivers/net/vrf.c
+++ b/drivers/net/vrf.c
@@ -614,9 +614,9 @@ static int vrf_finish_output6(struct net *net, struct sock *sk,
rcu_read_lock();
nexthop = rt6_nexthop(dst_rt6_info(dst), &ipv6_hdr(skb)->daddr);
- neigh = __ipv6_neigh_lookup_noref(dst->dev, nexthop);
+ neigh = __ipv6_neigh_lookup_noref(dev, nexthop);
if (unlikely(!neigh))
- neigh = __neigh_create(nd_table(net), nexthop, dst->dev, false);
+ neigh = ipv6_neigh_create_noref(dev, nexthop);
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 27b0b6567d52..513779c07621 100644
--- a/drivers/net/vxlan/vxlan_core.c
+++ b/drivers/net/vxlan/vxlan_core.c
@@ -2091,8 +2091,7 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb,
ipv6_addr_is_multicast(&msg->target))
goto out;
- n = neigh_lookup(nd_table(dev_net(dev)), &msg->target, dev);
-
+ n = ipv6_neigh_lookup(dev, &msg->target);
if (n) {
struct vxlan_rdst *rdst = NULL;
u8 ha[ETH_ALEN] __aligned(2);
@@ -2189,9 +2188,8 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb,
if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr)))
return false;
- tbl = nd_table(dev_net(dev));
pip6 = ipv6_hdr(skb);
- n = neigh_lookup(tbl, &pip6->daddr, dev);
+ n = ipv6_neigh_lookup(dev, &pip6->daddr);
if (!n && (cfg->flags & VXLAN_F_L3MISS)) {
union vxlan_addr ipa = {
.sin6.sin6_addr = pip6->daddr,
diff --git a/include/net/ndisc.h b/include/net/ndisc.h
index 2fce6fccf55a..91898a2475e7 100644
--- a/include/net/ndisc.h
+++ b/include/net/ndisc.h
@@ -423,11 +423,8 @@ static inline struct neighbour *ip_neigh_gw6(struct net_device *dev,
struct neighbour *neigh;
neigh = __ipv6_neigh_lookup_noref(dev, addr);
- if (unlikely(!neigh)) {
- struct neigh_table *tbl = nd_table(dev_net(dev));
-
- neigh = __neigh_create(tbl, addr, dev, false);
- }
+ if (unlikely(!neigh))
+ neigh = ipv6_neigh_create_noref(dev, addr);
return neigh;
#else
diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
index da15f4d7c1ae..a303bf897f11 100644
--- a/net/bridge/br_arp_nd_proxy.c
+++ b/net/bridge/br_arp_nd_proxy.c
@@ -487,7 +487,7 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
return;
}
- n = neigh_lookup(nd_table(dev_net(vlandev)), &msg->target, vlandev);
+ n = ipv6_neigh_lookup(vlandev, &msg->target);
if (n) {
struct net_bridge_fdb_entry *f;
u8 ha[ETH_ALEN] __aligned(2);
diff --git a/net/ieee802154/6lowpan/tx.c b/net/ieee802154/6lowpan/tx.c
index 4f511476b992..b261daedc290 100644
--- a/net/ieee802154/6lowpan/tx.c
+++ b/net/ieee802154/6lowpan/tx.c
@@ -58,9 +58,8 @@ 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(tbl, &hdr->daddr, ldev);
+ n = ipv6_neigh_lookup(ldev, &hdr->daddr);
if (n) {
llneigh = lowpan_802154_neigh(neighbour_priv(n));
read_lock_bh(&n->lock);
diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c
index e3bcc25229b0..a98c7670d2ce 100644
--- a/net/ipv4/fib_semantics.c
+++ b/net/ipv4/fib_semantics.c
@@ -617,7 +617,7 @@ static int fib_detect_death(struct fib_info *fi, int order,
if (likely(nhc->nhc_gw_family == AF_INET))
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_table(net), &nhc->nhc_gw.ipv6, nhc->nhc_dev);
+ n = ipv6_neigh_lookup(nhc->nhc_dev, &nhc->nhc_gw.ipv6);
else
n = NULL;
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index 10146a57b69e..25fe0ba98b1a 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -127,7 +127,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_table(net), nexthop, dev, false);
+ neigh = ipv6_neigh_create_noref(dev, nexthop);
if (IS_ERR(neigh)) {
IP6_INC_STATS(net, idev, IPSTATS_MIB_OUTNOROUTES);
kfree_skb_reason(skb, SKB_DROP_REASON_NEIGH_CREATEFAIL);
diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index e1cbffaa0ad0..0ed1a619372b 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -947,8 +947,12 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)
* update / create cache entry
* for the source address
*/
- neigh = __neigh_lookup(tbl, saddr, dev,
- !inc || lladdr || !dev->addr_len);
+ neigh = ipv6_neigh_lookup(dev, saddr);
+ if (!neigh && (!inc || lladdr || !dev->addr_len)) {
+ neigh = ipv6_neigh_create(dev, saddr);
+ if (IS_ERR(neigh))
+ neigh = NULL;
+ }
if (neigh)
ndisc_update(dev, neigh, lladdr, NUD_STALE,
NEIGH_UPDATE_F_WEAK_OVERRIDE|
@@ -998,7 +1002,6 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
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;
@@ -1063,8 +1066,7 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
return reason;
}
- tbl = nd_table(net);
- neigh = neigh_lookup(tbl, &msg->target, dev);
+ neigh = ipv6_neigh_lookup(dev, &msg->target);
/* RFC 9131 updates original Neighbour Discovery RFC 4861.
* NAs with Target LL Address option can now create a STALE neighbor
@@ -1093,7 +1095,7 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
return reason;
}
if (!neigh)
- neigh = neigh_create(tbl, &msg->target, dev);
+ neigh = ipv6_neigh_create(dev, &msg->target);
new_state = NUD_STALE;
}
@@ -1108,7 +1110,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(tbl, &msg->target, dev)) {
+ pneigh_lookup(nd_table(dev_net(dev)), &msg->target, dev)) {
/* XXX: idev->cnf.proxy_ndp */
goto out;
}
@@ -1141,7 +1143,6 @@ static enum skb_drop_reason ndisc_recv_rs(struct sk_buff *skb)
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;
u8 *lladdr = NULL;
@@ -1177,8 +1178,12 @@ static enum skb_drop_reason ndisc_recv_rs(struct sk_buff *skb)
goto out;
}
- tbl = nd_table(dev_net(dev));
- neigh = __neigh_lookup(tbl, saddr, dev, 1);
+ neigh = ipv6_neigh_lookup(dev, saddr);
+ if (!neigh) {
+ neigh = ipv6_neigh_create(dev, saddr);
+ if (IS_ERR(neigh))
+ goto out;
+ }
if (neigh) {
ndisc_update(dev, neigh, lladdr, NUD_STALE,
NEIGH_UPDATE_F_WEAK_OVERRIDE|
@@ -1478,9 +1483,14 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)
* Process options.
*/
- if (!neigh)
- neigh = __neigh_lookup(nd_table(net), &ipv6_hdr(skb)->saddr,
- skb->dev, 1);
+ if (!neigh) {
+ neigh = ipv6_neigh_lookup(skb->dev, &ipv6_hdr(skb)->saddr);
+ if (!neigh) {
+ neigh = ipv6_neigh_create(skb->dev, &ipv6_hdr(skb)->saddr);
+ if (IS_ERR(neigh))
+ neigh = NULL;
+ }
+ }
if (neigh) {
u8 *lladdr = NULL;
if (ndopts.nd_opts_src_lladdr) {
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 8baac4d85251..ea9f0a4c34f9 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -218,7 +218,7 @@ struct neighbour *__ip6_dst_neigh_lookup(const struct in6_addr *gw,
if (n)
return n;
- n = neigh_create(nd_table(dev_net(dev)), daddr, dev);
+ n = ipv6_neigh_create(dev, daddr);
return IS_ERR(n) ? NULL : n;
}
@@ -4277,9 +4277,12 @@ 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_table(dev_net(dev)), &msg->target, dev, 1);
- if (!neigh)
- return;
+ neigh = ipv6_neigh_lookup(dev, &msg->target);
+ if (!neigh) {
+ neigh = ipv6_neigh_create(dev, &msg->target);
+ if (IS_ERR(neigh))
+ return;
+ }
/*
* We have finally decided to accept it.
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v1 net-next 7/8] ipv4: Use ipv4_neigh_lookup() and friends.
2026-09-30 20:03 [PATCH v1 net-next 0/8] neighbour: Add IPv4/IPv6-specific wrappers for neigh_lookup() and friends Kuniyuki Iwashima
` (5 preceding siblings ...)
2026-09-30 20:03 ` [PATCH v1 net-next 6/8] ipv6: Use ipv6_neigh_lookup() and friends Kuniyuki Iwashima
@ 2026-09-30 20:03 ` Kuniyuki Iwashima
2026-10-02 2:03 ` netdev-bot+sashiko
2026-09-30 20:03 ` [PATCH v1 net-next 8/8] neighbour: Remove __neigh_lookup() and __neigh_lookup_errno() Kuniyuki Iwashima
7 siblings, 1 reply; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-30 20:03 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev,
Elad Nachman, Saeed Mahameed, Leon Romanovsky, Tariq Toukan,
Mark Bloch, Boris Pismenny, Petr Machata, Jiri Pirko, Edward Cree,
Nikolay Aleksandrov
It is error-prone to require callers to fetch netns and
neigh_table and pass it with dev to neigh_lookup(), etc.
Let's replace neigh_lookup() and friends with IPv4 helpers.
Note that __neigh_lookup(create=false) is replaced with
ipv4_neigh_lookup() and __neigh_lookup(create=true) and
__neigh_lookup_errno() are split into ipv4_neigh_lookup()
and ipv4_neigh_create().
Fixes: 2430df635a59 ("ipv4: Replace &arp_tbl with arp_table(net).")
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
Cc: Elad Nachman <enachman@marvell.com>
Cc: Saeed Mahameed <saeedm@nvidia.com>
Cc: Leon Romanovsky <leon@kernel.org>
Cc: Tariq Toukan <tariqt@nvidia.com>
Cc: Mark Bloch <mbloch@nvidia.com>
Cc: Boris Pismenny <borisp@nvidia.com>
Cc: Petr Machata <petrm@nvidia.com>
Cc: Jiri Pirko <jiri@resnulli.us>
Cc: Edward Cree <ecree.xilinx@gmail.com>
Cc: Nikolay Aleksandrov <razor@blackwall.org>
---
.../marvell/prestera/prestera_router.c | 14 ++++------
.../mellanox/mlx5/core/en/tc_tun_encap.c | 3 +--
.../mellanox/mlx5/core/en_accel/ipsec.c | 6 ++---
.../ethernet/mellanox/mlxsw/spectrum_router.c | 13 +++-------
.../ethernet/mellanox/mlxsw/spectrum_span.c | 7 ++---
.../netronome/nfp/flower/tunnel_conf.c | 4 +--
drivers/net/ethernet/rocker/rocker_ofdpa.c | 2 +-
drivers/net/ethernet/sfc/tc_counters.c | 6 ++---
drivers/net/vxlan/vxlan_core.c | 8 ++----
net/bridge/br_arp_nd_proxy.c | 2 +-
net/ipv4/arp.c | 26 ++++++++++++-------
net/ipv4/fib_semantics.c | 3 +--
net/ipv4/route.c | 2 +-
13 files changed, 38 insertions(+), 58 deletions(-)
diff --git a/drivers/net/ethernet/marvell/prestera/prestera_router.c b/drivers/net/ethernet/marvell/prestera/prestera_router.c
index ba45b61b09bb..44c9c1fa3189 100644
--- a/drivers/net/ethernet/marvell/prestera/prestera_router.c
+++ b/drivers/net/ethernet/marvell/prestera/prestera_router.c
@@ -683,8 +683,7 @@ __prestera_k_arb_n_offload_set(struct prestera_switch *sw,
{
struct neighbour *n;
- n = neigh_lookup(arp_table(&init_net), &nc->key.addr.u.ipv4,
- nc->key.dev);
+ n = ipv4_neigh_lookup(nc->key.dev, &nc->key.addr.u.ipv4);
if (!n)
return;
@@ -790,7 +789,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_table(&init_net), &nc->key.addr.u.ipv4, nc->key.dev);
+ n = ipv4_neigh_lookup(nc->key.dev, &nc->key.addr.u.ipv4);
if (!n)
goto out;
@@ -1052,13 +1051,10 @@ 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) {
- struct neigh_table *tbl = arp_table(&init_net);
-
- n = neigh_lookup(tbl, &nc->key.addr.u.ipv4,
- nc->key.dev);
+ n = ipv4_neigh_lookup(nc->key.dev, &nc->key.addr.u.ipv4);
if (!n)
- n = neigh_create(tbl, &nc->key.addr.u.ipv4,
- nc->key.dev);
+ n = ipv4_neigh_create(nc->key.dev,
+ &nc->key.addr.u.ipv4);
} else {
n = NULL;
}
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 fe0258375ef6..5e40107efa26 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
@@ -438,7 +438,6 @@ void mlx5e_tc_update_neigh_used_value(struct mlx5e_neigh_hash_entry *nhe)
if (neigh_used) {
struct net_device *dev = READ_ONCE(nhe->neigh_dev);
- struct net *net = dev_net(dev);
nhe->reported_lastuse = jiffies;
@@ -450,7 +449,7 @@ void mlx5e_tc_update_neigh_used_value(struct mlx5e_neigh_hash_entry *nhe)
n = ipv6_neigh_lookup(dev, &m_neigh->dst_ip);
else
#endif
- n = neigh_lookup(arp_table(net), &m_neigh->dst_ip, dev);
+ n = ipv4_neigh_lookup(dev, &m_neigh->dst_ip);
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 37a8ddee3ea1..16edd3263aed 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c
@@ -262,7 +262,6 @@ 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;
@@ -365,10 +364,9 @@ static void mlx5e_ipsec_init_macs(struct mlx5e_ipsec_sa_entry *sa_entry,
return;
neigh:
- tbl = arp_table(dev_net(netdev));
- n = neigh_lookup(tbl, pkey, netdev);
+ n = ipv4_neigh_lookup(netdev, pkey);
if (!n) {
- n = neigh_create(tbl, pkey, netdev);
+ n = ipv4_neigh_create(netdev, pkey);
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 1f4753213b9c..07d22257b703 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
@@ -2415,8 +2415,6 @@ 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;
@@ -2432,10 +2430,9 @@ 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(tbl, &dipn, dev);
+ n = ipv4_neigh_lookup(dev, &dipn);
if (!n)
return;
@@ -4300,13 +4297,10 @@ static void __mlxsw_sp_nexthop_neigh_update(struct mlxsw_sp_nexthop *nh,
static struct neighbour *
mlxsw_sp_nexthop_neigh_lookup(struct mlxsw_sp_nexthop *nh)
{
- struct neigh_table *tbl;
struct net_device *dev;
struct neighbour *n;
- struct net *net;
dev = mlxsw_sp_nexthop_dev(nh);
- net = dev_net(dev);
#if IS_ENABLED(CONFIG_IPV6)
if (nh->family == AF_INET6) {
@@ -4319,10 +4313,9 @@ mlxsw_sp_nexthop_neigh_lookup(struct mlxsw_sp_nexthop *nh)
} else
#endif
{
- tbl = arp_table(net);
- n = neigh_lookup(tbl, &nh->gw_addr, dev);
+ n = ipv4_neigh_lookup(dev, &nh->gw_addr);
if (!n) {
- n = neigh_create(tbl, &nh->gw_addr, dev);
+ n = ipv4_neigh_create(dev, &nh->gw_addr);
if (!IS_ERR(n))
neigh_event_send(n, NULL);
}
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
index 79947f68b10d..0b84a25e2ea8 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_span.c
@@ -229,8 +229,6 @@ static int mlxsw_sp_span_dmac(int family,
struct net_device *dev,
unsigned char dmac[ETH_ALEN])
{
- struct net *net = dev_net(dev);
- struct neigh_table *tbl;
struct neighbour *neigh;
int err = 0;
@@ -245,10 +243,9 @@ static int mlxsw_sp_span_dmac(int family,
} else
#endif
{
- tbl = arp_table(net);
- neigh = neigh_lookup(tbl, pkey, dev);
+ neigh = ipv4_neigh_lookup(dev, pkey);
if (!neigh) {
- neigh = neigh_create(tbl, pkey, dev);
+ neigh = ipv4_neigh_create(dev, pkey);
if (IS_ERR(neigh))
return PTR_ERR(neigh);
}
diff --git a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
index 27d80ec8e895..3b47e9fe64e1 100644
--- a/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
+++ b/drivers/net/ethernet/netronome/nfp/flower/tunnel_conf.c
@@ -209,7 +209,6 @@ 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;
@@ -236,8 +235,7 @@ void nfp_tunnel_keep_alive(struct nfp_app *app, struct sk_buff *skb)
if (!netdev)
continue;
- tbl = arp_table(dev_net(netdev));
- n = neigh_lookup(tbl, &ipv4_addr, netdev);
+ n = ipv4_neigh_lookup(netdev, &ipv4_addr);
if (!n)
continue;
diff --git a/drivers/net/ethernet/rocker/rocker_ofdpa.c b/drivers/net/ethernet/rocker/rocker_ofdpa.c
index ead8b447b89c..ace69a1d5275 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_table(&init_net), &ip_addr, dev);
+ n = ipv4_neigh_create(dev, &ip_addr);
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 ae6cc6b93864..fcb2be6abb20 100644
--- a/drivers/net/ethernet/sfc/tc_counters.c
+++ b/drivers/net/ethernet/sfc/tc_counters.c
@@ -91,7 +91,6 @@ 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);
@@ -106,14 +105,13 @@ static void efx_tc_counter_work(struct work_struct *work)
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_table(net), &encap->neigh->dst_ip,
- encap->neigh->egdev);
+ n = ipv4_neigh_lookup(encap->neigh->egdev,
+ &encap->neigh->dst_ip);
else
#if IS_ENABLED(CONFIG_IPV6)
n = ipv6_neigh_lookup(encap->neigh->egdev,
diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c
index 513779c07621..35bd92f5e460 100644
--- a/drivers/net/vxlan/vxlan_core.c
+++ b/drivers/net/vxlan/vxlan_core.c
@@ -1877,7 +1877,6 @@ static int vxlan_err_lookup(struct sock *sk, struct sk_buff *skb)
static int arp_reduce(struct net_device *dev, struct sk_buff *skb,
const struct vxlan_config *cfg, __be32 vni)
{
- struct neigh_table *tbl = arp_table(dev_net(dev));
struct vxlan_dev *vxlan = netdev_priv(dev);
struct neighbour *n;
struct arphdr *parp;
@@ -1914,8 +1913,7 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb,
ipv4_is_multicast(tip))
goto out;
- n = neigh_lookup(tbl, &tip, dev);
-
+ n = ipv4_neigh_lookup(dev, &tip);
if (n) {
struct vxlan_rdst *rdst = NULL;
u8 ha[ETH_ALEN] __aligned(2);
@@ -2145,7 +2143,6 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb,
static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb,
const struct vxlan_config *cfg)
{
- struct neigh_table *tbl;
struct neighbour *n;
if (is_multicast_ether_addr(eth_hdr(skb)->h_dest))
@@ -2160,9 +2157,8 @@ 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(tbl, &pip->daddr, dev);
+ n = ipv4_neigh_lookup(dev, &pip->daddr);
if (!n && (cfg->flags & VXLAN_F_L3MISS)) {
union vxlan_addr ipa = {
.sin.sin_addr.s_addr = pip->daddr,
diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
index a303bf897f11..e3dbd71b30c5 100644
--- a/net/bridge/br_arp_nd_proxy.c
+++ b/net/bridge/br_arp_nd_proxy.c
@@ -210,7 +210,7 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,
return;
}
- n = neigh_lookup(arp_table(dev_net(vlandev)), &tip, vlandev);
+ n = ipv4_neigh_lookup(vlandev, &tip);
if (n) {
struct net_bridge_fdb_entry *f;
u8 ha[ETH_ALEN] __aligned(2);
diff --git a/net/ipv4/arp.c b/net/ipv4/arp.c
index 95f3359d0f92..1ca4ced280ae 100644
--- a/net/ipv4/arp.c
+++ b/net/ipv4/arp.c
@@ -892,7 +892,7 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
/* Update our ARP tables */
- n = __neigh_lookup(tbl, &sip, dev, 0);
+ n = ipv4_neigh_lookup(dev, &sip);
addr_type = -1;
if (n || arp_accept(in_dev, sip)) {
@@ -911,9 +911,14 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
(addr_type == RTN_UNICAST ||
(addr_type < 0 &&
/* postpone calculation to as late as possible */
- inet_addr_type_dev_table(net, dev, sip) ==
- RTN_UNICAST)))))
- n = __neigh_lookup(tbl, &sip, dev, 1);
+ inet_addr_type_dev_table(net, dev, sip) == RTN_UNICAST))))) {
+ n = ipv4_neigh_lookup(dev, &sip);
+ if (!n) {
+ n = ipv4_neigh_create(dev, &sip);
+ if (IS_ERR(n))
+ n = NULL;
+ }
+ }
}
if (n) {
@@ -1102,7 +1107,6 @@ 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;
@@ -1138,7 +1142,9 @@ 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(tbl, &ip, dev);
+ neigh = ipv4_neigh_lookup(dev, &ip);
+ if (!neigh)
+ neigh = ipv4_neigh_create(dev, &ip);
err = PTR_ERR(neigh);
if (!IS_ERR(neigh)) {
unsigned int state = NUD_STALE;
@@ -1174,7 +1180,6 @@ 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;
@@ -1185,7 +1190,7 @@ static int arp_req_get(struct net *net, struct arpreq *r)
if (IS_ERR(dev))
return PTR_ERR(dev);
- neigh = neigh_lookup(tbl, &ip, dev);
+ neigh = ipv4_neigh_lookup(dev, &ip);
if (!neigh)
return -ENXIO;
@@ -1210,12 +1215,13 @@ static int arp_req_get(struct net *net, struct arpreq *r)
int arp_invalidate(struct net_device *dev, __be32 ip, bool force)
{
- struct neigh_table *tbl = arp_table(dev_net(dev));
struct neighbour *neigh;
int err = -ENXIO;
- neigh = neigh_lookup(tbl, &ip, dev);
+ neigh = ipv4_neigh_lookup(dev, &ip);
if (neigh) {
+ struct neigh_table *tbl = neigh->tbl;
+
if ((READ_ONCE(neigh->nud_state) & NUD_VALID) && !force) {
neigh_release(neigh);
return 0;
diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c
index a98c7670d2ce..235c51a6f8e5 100644
--- a/net/ipv4/fib_semantics.c
+++ b/net/ipv4/fib_semantics.c
@@ -610,12 +610,11 @@ 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 net *net = fi->fib_net;
int state = NUD_NONE;
struct neighbour *n;
if (likely(nhc->nhc_gw_family == AF_INET))
- n = neigh_lookup(arp_table(net), &nhc->nhc_gw.ipv4, nhc->nhc_dev);
+ n = ipv4_neigh_lookup(nhc->nhc_dev, &nhc->nhc_gw.ipv4);
else if (IS_ENABLED(CONFIG_IPV6) && nhc->nhc_gw_family == AF_INET6)
n = ipv6_neigh_lookup(nhc->nhc_dev, &nhc->nhc_gw.ipv6);
else
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index 50c842617e45..18b588dfbea7 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -817,7 +817,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_table(net), &new_gw, rt->dst.dev);
+ n = ipv4_neigh_create(rt->dst.dev, &new_gw);
if (!IS_ERR(n)) {
if (!(READ_ONCE(n->nud_state) & NUD_VALID)) {
neigh_event_send(n, NULL);
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v1 net-next 8/8] neighbour: Remove __neigh_lookup() and __neigh_lookup_errno().
2026-09-30 20:03 [PATCH v1 net-next 0/8] neighbour: Add IPv4/IPv6-specific wrappers for neigh_lookup() and friends Kuniyuki Iwashima
` (6 preceding siblings ...)
2026-09-30 20:03 ` [PATCH v1 net-next 7/8] ipv4: Use ipv4_neigh_lookup() " Kuniyuki Iwashima
@ 2026-09-30 20:03 ` Kuniyuki Iwashima
7 siblings, 0 replies; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-30 20:03 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
Both __neigh_lookup() and __neigh_lookup_errno() not only
look up but also create a new neighbour entry.
The names were misleading and there was __neigh_lookup(..., 0),
which should have been simply written as neigh_lookup().
Now, each helper has only 1 user left.
Let's convert the last users to neigh_lookup() and neigh_create()
and remove __neigh_lookup() and __neigh_lookup_errno().
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
include/net/neighbour.h | 24 ------------------------
net/core/neighbour.c | 9 +++++++--
net/sched/sch_teql.c | 4 +++-
3 files changed, 10 insertions(+), 27 deletions(-)
diff --git a/include/net/neighbour.h b/include/net/neighbour.h
index ed9d9ae6d3a6..79a5a548a129 100644
--- a/include/net/neighbour.h
+++ b/include/net/neighbour.h
@@ -554,30 +554,6 @@ static inline int neigh_output(struct neighbour *n, struct sk_buff *skb,
return READ_ONCE(n->output)(n, skb);
}
-static inline struct neighbour *
-__neigh_lookup(struct neigh_table *tbl, const void *pkey, struct net_device *dev, int creat)
-{
- struct neighbour *n = neigh_lookup(tbl, pkey, dev);
-
- if (n || !creat)
- return n;
-
- n = neigh_create(tbl, pkey, dev);
- return IS_ERR(n) ? NULL : n;
-}
-
-static inline struct neighbour *
-__neigh_lookup_errno(struct neigh_table *tbl, const void *pkey,
- struct net_device *dev)
-{
- struct neighbour *n = neigh_lookup(tbl, pkey, dev);
-
- if (n)
- return n;
-
- return neigh_create(tbl, pkey, dev);
-}
-
struct neighbour_cb {
unsigned long sched_next;
unsigned int flags;
diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index 6ed8eb075146..0d883c043956 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c
@@ -1569,8 +1569,13 @@ struct neighbour *neigh_event_ns(struct neigh_table *tbl,
u8 *lladdr, void *saddr,
struct net_device *dev)
{
- struct neighbour *neigh = __neigh_lookup(tbl, saddr, dev,
- lladdr || !dev->addr_len);
+ struct neighbour *neigh = neigh_lookup(tbl, saddr, dev);
+
+ if (!neigh && (lladdr || !dev->addr_len)) {
+ neigh = neigh_create(tbl, saddr, dev);
+ if (IS_ERR(neigh))
+ neigh = NULL;
+ }
if (neigh)
neigh_update(neigh, lladdr, NUD_STALE,
NEIGH_UPDATE_F_OVERRIDE, 0);
diff --git a/net/sched/sch_teql.c b/net/sched/sch_teql.c
index 409ce50cc0db..7054265fa8dd 100644
--- a/net/sched/sch_teql.c
+++ b/net/sched/sch_teql.c
@@ -257,7 +257,9 @@ __teql_resolve(struct sk_buff *skb, struct sk_buff *skb_res,
if (dst->dev != dev) {
struct neighbour *mn;
- mn = __neigh_lookup_errno(n->tbl, n->primary_key, dev);
+ mn = neigh_lookup(n->tbl, n->primary_key, dev);
+ if (!mn)
+ mn = neigh_create(n->tbl, n->primary_key, dev);
neigh_release(n);
if (IS_ERR(mn))
return PTR_ERR(mn);
--
2.56.0.rc1.315.gc6ed9934b7-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v1 net-next 6/8] ipv6: Use ipv6_neigh_lookup() and friends.
2026-09-30 20:03 ` [PATCH v1 net-next 6/8] ipv6: Use ipv6_neigh_lookup() and friends Kuniyuki Iwashima
@ 2026-10-02 2:03 ` netdev-bot+sashiko
2026-10-02 17:01 ` Kuniyuki Iwashima
0 siblings, 1 reply; 12+ messages in thread
From: netdev-bot+sashiko @ 2026-10-02 2:03 UTC (permalink / raw)
To: kuniyu
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, dsahern, idosch,
horms, kuni1840, netdev, enachman, saeedm, leon, tariqt, mbloch,
borisp, petrm, jiri, ecree.xilinx, razor
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 1 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The patch's bug class is still reachable through sch_teql.
- [Medium] ipv6_neigh_lookup(), ipv6_neigh_create() and
ipv6_neigh_create_noref() choose the table with an unsynchronized…
- [Low] In mlxsw_sp_router_neigh_ent_ipv6_process() the dev comes from
mlxsw_sp_rif_dev(rifs[rif]) and is passed to ipv6_neigh_lookup(dev,…
--
ipv6: Use ipv6_neigh_lookup() and friends.
This commit replaces neigh_lookup(), neigh_create() and __neigh_lookup()
callers that pass nd_table(net) with IPv6 helpers. The helpers take the
neighbour table from dev_net(dev). It fixes a syzbot lockdep splat in
neigh_mark_dead(): ip6_finish_output2() used the caller's net (for example
under xfrm), so a neighbour was inserted into another netns's table.
Closes: https://lore.kernel.org/netdev/6abd69a9.80e1c6cc.22483f.0005.GAE@google.com/
> This only happens when a neigh_table accidentally has a
> neighbour from a different netns.
[ ... ]
> It is error-prone to require callers to fetch netns and
> neigh_table and pass it with dev to neigh_lookup(), etc.
[Severity: High]
Can a neighbour from a different netns still end up in a table through
sch_teql? This patch does not touch net/sched/sch_teql.c, and
__teql_resolve() still pairs the master's table with the slave device:
net/sched/sch_teql.c:__teql_resolve() {
...
n = dst_neigh_lookup_skb(dst, skb);
if (!n)
return -ENOENT;
if (dst->dev != dev) {
struct neighbour *mn;
mn = __neigh_lookup_errno(n->tbl, n->primary_key, dev);
...
}
dst->dev is the teql master, so n->tbl is the table of the master's netns,
while dev is the slave.
Nothing seems to require the two to share a netns. teql_qdisc_init()
checks only hard_header_len, m->dev == dev and TC_H_ROOT. The teql Qdisc_ops
are registered globally. teql_master_setup() also does not make the master
netns-immutable.
With the master and slave in different netns, ___neigh_create() links the
new neighbour into the master-netns table's hash. It also adds it to the
slave's dev->neighbours[] through neigh_get_dev_table(). For IPv6,
ndisc_constructor() takes n->parms from the slave's in6_dev, so the
parms->dead check passes.
When the slave later goes down in its own netns, this path runs:
neigh_ifdown(nd_table(dev_net(slave)), slave)
neigh_flush_dev()
neigh_flush_one()
hlist_del_rcu(&n->hash)
neigh_mark_dead()
lockdep_assert_held(&n->tbl->lock)
Only the slave-netns tbl->lock is held there. Would that give the same
splat as the syzbot report, along with unlocked changes to the other
netns's hash and gc lists?
The later commit "neighbour: Remove __neigh_lookup() and
__neigh_lookup_errno()." in this series appears to keep
neigh_lookup(n->tbl, ..., dev) and neigh_create(n->tbl, ..., dev) here.
Three possible directions:
- resolve the table from dev_net(dev) by family
- reject cross-netns slaves in teql_qdisc_init() with
net_eq(dev_net(dev), dev_net(m->dev)) and make the master
netns-immutable
- reject n->parms->tbl != tbl under tbl->lock in ___neigh_create()
> diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
> index ba8a7a44ce9ed..1f4753213b9cb 100644
> --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
> +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
[ ... ]
> @@ -2464,9 +2462,8 @@ 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(tbl, &dip, dev);
> + n = ipv6_neigh_lookup(dev, &dip);
[Severity: Low]
Is dev guaranteed to be non-NULL here? mlxsw_sp_rif_dev() can return NULL:
if (!rif->crif)
return NULL;
return rif->crif->key.dev;
The loopback RIF's crif is allocated with mlxsw_sp_crif_alloc(NULL), so its
key.dev is NULL.
Before this change, neigh_lookup(tbl, &dip, NULL) only hashed and compared
the dev pointer. ipv6_neigh_lookup() evaluates nd_table(dev_net(dev)), so a
NULL dev would now be dereferenced.
In practice, host entries are only programmed on RIFs found by
mlxsw_sp_rif_find_by_dev() from n->dev. __mlxsw_sp_router_neighs_update_rauhtd()
also holds router->lock for the whole dump. So this looks reachable only if
firmware returns a record the driver never programmed.
Would a NULL check on dev before the lookup be worth adding for robustness?
The IPv4 counterpart in "ipv4: Use ipv4_neigh_lookup() and friends." appears
to follow the same pattern.
> if (!n)
> return;
>
[ ... ]
> diff --git a/net/ipv6/route.c b/net/ipv6/route.c
> index 8baac4d852518..ea9f0a4c34f97 100644
> --- a/net/ipv6/route.c
> +++ b/net/ipv6/route.c
> @@ -218,7 +218,7 @@ struct neighbour *__ip6_dst_neigh_lookup(const struct in6_addr *gw,
> if (n)
> return n;
>
> - n = neigh_create(nd_table(dev_net(dev)), daddr, dev);
> + n = ipv6_neigh_create(dev, daddr);
[Severity: Medium]
Can the table chosen inside ipv6_neigh_create() go stale before the
neighbour is inserted? The helper reads dev_net(dev) without
synchronization:
struct neigh_table *tbl = nd_table(dev_net(dev));
return neigh_create(tbl, pkey, dev);
Under tbl->lock, ___neigh_create() only checks the following. It does not
check that dev is still in tbl's netns or that n->parms belongs to tbl:
if (n->parms->dead) {
rc = ERR_PTR(-EINVAL);
goto out_tbl_unlock;
}
Consider a preemptible process-context creator, such as a dst_neigh_lookup()
caller that reaches __ip6_dst_neigh_lookup() outside RCU:
creator
ipv6_neigh_create(dev, ...)
tbl = nd_table(dev_net(dev)) <- old netns
<preempted>
__dev_change_net_namespace()
NETDEV_UNREGISTER
rcu_barrier()
dev_net_set(dev, new_net)
NETDEV_REGISTER <- new in6_dev
creator resumes
neigh_create(old_tbl, ...)
ndisc_constructor()
in6_dev_get(dev) <- new in6_dev, live parms
The new parms are live, so the dead check passes. The neighbour is then
linked into the old netns's table and onto dev->neighbours[]. A later
neigh_ifdown(new_tbl, dev) would change old_tbl's hash and gc lists without
old_tbl->lock. That is the same state described in the commit message.
The rcu_barrier() in __dev_change_net_namespace() does not exclude a
non-RCU process-context reader like this. The window is narrow and there is
no reproducer.
Would a check under tbl->lock in ___neigh_create(), for example rejecting
n->parms->tbl != tbl with -EINVAL, close this window? It would also cover
the sch_teql case above.
> return IS_ERR(n) ? NULL : n;
> }
>
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930200326.718459-1-kuniyu%40google.com
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v1 net-next 7/8] ipv4: Use ipv4_neigh_lookup() and friends.
2026-09-30 20:03 ` [PATCH v1 net-next 7/8] ipv4: Use ipv4_neigh_lookup() " Kuniyuki Iwashima
@ 2026-10-02 2:03 ` netdev-bot+sashiko
0 siblings, 0 replies; 12+ messages in thread
From: netdev-bot+sashiko @ 2026-10-02 2:03 UTC (permalink / raw)
To: kuniyu
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, dsahern, idosch,
horms, kuni1840, netdev, enachman, saeedm, leon, tariqt, mbloch,
borisp, petrm, jiri, ecree.xilinx, razor
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 0 (1) · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] mlxsw: kernel panic via NULL pointer dereference in hw neighbour
events
Pre-existing issues:
- [Medium] In mlx5e_ipsec_init_macs()
(drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c), the…
--
ipv4: Use ipv4_neigh_lookup() and friends.
This commit replaces open-coded neigh_lookup(), neigh_create(),
__neigh_lookup() and __neigh_lookup_errno() calls on the ARP table with
the ipv4_neigh_lookup() and ipv4_neigh_create() helpers. Callers no
longer need to fetch the netns and neigh_table themselves. It touches
several drivers as well as net/ipv4 and the bridge.
> 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 37a8ddee3ea1e..16edd3263aedf 100644
> --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c
> +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec.c
[ ... ]
> @@ -365,10 +364,9 @@ static void mlx5e_ipsec_init_macs(struct mlx5e_ipsec_sa_entry *sa_entry,
> return;
>
> neigh:
> - tbl = arp_table(dev_net(netdev));
> - n = neigh_lookup(tbl, pkey, netdev);
> + n = ipv4_neigh_lookup(netdev, pkey);
> if (!n) {
> - n = neigh_create(tbl, pkey, netdev);
> + n = ipv4_neigh_create(netdev, pkey);
[Severity: Medium]
This patch didn't introduce this issue, but the new helper names make
it easier to see. Can this fallback put IPv6 keys into the ARP table?
For AF_INET6 SAs, pkey points at the 16-byte addrs->saddr.a6 or
addrs->daddr.a6. Earlier in mlx5e_ipsec_init_macs(), the IPv6 case
jumps here when CONFIG_IPV6 is disabled or the route lookup fails:
case AF_INET6:
if (!IS_ENABLED(CONFIG_IPV6) ||
ip6_dst_lookup(dev_net(netdev), NULL, &rt_dst_entry, &fl6))
goto neigh;
The shared path also jumps here when dst_neigh_lookup() returns NULL.
ipv4_neigh_lookup() and ipv4_neigh_create() always use
arp_table(dev_net(dev)), and its key length is 4. That means the first
32 bits of the IPv6 address are treated as an IPv4 address:
- On a miss, an ARP entry for an unrelated IPv4 address is created on
netdev, neigh_event_send() sends an ARP request for it, and
attrs->drop is set.
- On a hit, neigh_ha_snapshot() copies the MAC of an unrelated IPv4
neighbour into the IPv6 SA's dmac.
The old code used the ARP table here too. The patch only changes how
the table is obtained.
Should this fallback check addrs->family and use ipv6_neigh_lookup()
and ipv6_neigh_create() for AF_INET6, or skip the neighbour fallback
for IPv6?
> 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
> --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
> +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
> @@ -2432,10 +2430,9 @@ 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(tbl, &dipn, dev);
> + n = ipv4_neigh_lookup(dev, &dipn);
> if (!n)
> return;
>
[Severity: High]
Can dev be NULL here?
mlxsw_sp_rif_dev() returns NULL when the RIF has no crif. That is the
case for the IP-in-IP underlay RIFs, which mlxsw_sp_ul_rif_create()
allocates without a crif. The only check above is that
mlxsw_sp->router->rifs[rif] is non-NULL. If the device reports
neighbour activity on such a RIF through RAUHTD, the delayed neighbour
update work reaches this point with dev == NULL.
Before this patch that case was harmless. The netns came from
mlxsw_sp_net(mlxsw_sp), not from dev, and neigh_lookup() with a NULL
dev simply matched nothing and returned NULL.
ipv4_neigh_lookup() now derives the table itself via
arp_table(dev_net(dev)). That dereferences the NULL dev, so the kernel
oopses in the router's periodic neighbour work instead of skipping the
entry. Routed IP-in-IP setups on Spectrum would hit this during
normal traffic.
The IPv6 counterpart, mlxsw_sp_router_neigh_ent_ipv6_process(),
passes the same possibly-NULL dev to ipv6_neigh_lookup(). It looks
like it has the same problem.
Should both functions return early when dev is NULL, before the
lookup? Or should the new helpers tolerate a NULL dev the way
neigh_lookup() effectively did? Checking in the callers seems simpler
and keeps the helpers strict.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930200326.718459-1-kuniyu%40google.com
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v1 net-next 6/8] ipv6: Use ipv6_neigh_lookup() and friends.
2026-10-02 2:03 ` netdev-bot+sashiko
@ 2026-10-02 17:01 ` Kuniyuki Iwashima
0 siblings, 0 replies; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-10-02 17:01 UTC (permalink / raw)
To: netdev-bot+sashiko
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, dsahern, idosch,
horms, kuni1840, netdev, enachman, saeedm, leon, tariqt, mbloch,
borisp, petrm, jiri, ecree.xilinx, razor
On Thu, Oct 1, 2026 at 7:03 PM <netdev-bot+sashiko@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 3 potential
> issue(s) to consider.
>
> Critical: 0 · High: 1 · Medium: 1 · Low: 1
>
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
>
> - [High] The patch's bug class is still reachable through sch_teql.
> - [Medium] ipv6_neigh_lookup(), ipv6_neigh_create() and
> ipv6_neigh_create_noref() choose the table with an unsynchronized…
> - [Low] In mlxsw_sp_router_neigh_ent_ipv6_process() the dev comes from
> mlxsw_sp_rif_dev(rifs[rif]) and is passed to ipv6_neigh_lookup(dev,…
>
> --
>
> ipv6: Use ipv6_neigh_lookup() and friends.
>
> This commit replaces neigh_lookup(), neigh_create() and __neigh_lookup()
> callers that pass nd_table(net) with IPv6 helpers. The helpers take the
> neighbour table from dev_net(dev). It fixes a syzbot lockdep splat in
> neigh_mark_dead(): ip6_finish_output2() used the caller's net (for example
> under xfrm), so a neighbour was inserted into another netns's table.
>
> Closes: https://lore.kernel.org/netdev/6abd69a9.80e1c6cc.22483f.0005.GAE@google.com/
>
> > This only happens when a neigh_table accidentally has a
> > neighbour from a different netns.
>
> [ ... ]
>
> > It is error-prone to require callers to fetch netns and
> > neigh_table and pass it with dev to neigh_lookup(), etc.
>
> [Severity: High]
> Can a neighbour from a different netns still end up in a table through
> sch_teql? This patch does not touch net/sched/sch_teql.c, and
> __teql_resolve() still pairs the master's table with the slave device:
>
> net/sched/sch_teql.c:__teql_resolve() {
> ...
> n = dst_neigh_lookup_skb(dst, skb);
> if (!n)
> return -ENOENT;
>
> if (dst->dev != dev) {
> struct neighbour *mn;
>
> mn = __neigh_lookup_errno(n->tbl, n->primary_key, dev);
> ...
> }
>
> dst->dev is the teql master, so n->tbl is the table of the master's netns,
> while dev is the slave.
>
> Nothing seems to require the two to share a netns. teql_qdisc_init()
> checks only hard_header_len, m->dev == dev and TC_H_ROOT. The teql Qdisc_ops
> are registered globally. teql_master_setup() also does not make the master
> netns-immutable.
>
> With the master and slave in different netns, ___neigh_create() links the
> new neighbour into the master-netns table's hash. It also adds it to the
> slave's dev->neighbours[] through neigh_get_dev_table(). For IPv6,
> ndisc_constructor() takes n->parms from the slave's in6_dev, so the
> parms->dead check passes.
>
> When the slave later goes down in its own netns, this path runs:
>
> neigh_ifdown(nd_table(dev_net(slave)), slave)
> neigh_flush_dev()
> neigh_flush_one()
> hlist_del_rcu(&n->hash)
> neigh_mark_dead()
> lockdep_assert_held(&n->tbl->lock)
>
> Only the slave-netns tbl->lock is held there. Would that give the same
> splat as the syzbot report, along with unlocked changes to the other
> netns's hash and gc lists?
>
> The later commit "neighbour: Remove __neigh_lookup() and
> __neigh_lookup_errno()." in this series appears to keep
> neigh_lookup(n->tbl, ..., dev) and neigh_create(n->tbl, ..., dev) here.
>
> Three possible directions:
>
> - resolve the table from dev_net(dev) by family
> - reject cross-netns slaves in teql_qdisc_init() with
> net_eq(dev_net(dev), dev_net(m->dev)) and make the master
> netns-immutable
> - reject n->parms->tbl != tbl under tbl->lock in ___neigh_create()
>
> > diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
> > index ba8a7a44ce9ed..1f4753213b9cb 100644
> > --- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
> > +++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
>
> [ ... ]
>
> > @@ -2464,9 +2462,8 @@ 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(tbl, &dip, dev);
> > + n = ipv6_neigh_lookup(dev, &dip);
>
> [Severity: Low]
> Is dev guaranteed to be non-NULL here? mlxsw_sp_rif_dev() can return NULL:
>
> if (!rif->crif)
> return NULL;
> return rif->crif->key.dev;
>
> The loopback RIF's crif is allocated with mlxsw_sp_crif_alloc(NULL), so its
> key.dev is NULL.
>
> Before this change, neigh_lookup(tbl, &dip, NULL) only hashed and compared
> the dev pointer. ipv6_neigh_lookup() evaluates nd_table(dev_net(dev)), so a
> NULL dev would now be dereferenced.
>
> In practice, host entries are only programmed on RIFs found by
> mlxsw_sp_rif_find_by_dev() from n->dev. __mlxsw_sp_router_neighs_update_rauhtd()
> also holds router->lock for the whole dump. So this looks reachable only if
> firmware returns a record the driver never programmed.
>
> Would a NULL check on dev before the lookup be worth adding for robustness?
> The IPv4 counterpart in "ipv4: Use ipv4_neigh_lookup() and friends." appears
> to follow the same pattern.
>
> > if (!n)
> > return;
> >
>
> [ ... ]
>
> > diff --git a/net/ipv6/route.c b/net/ipv6/route.c
> > index 8baac4d852518..ea9f0a4c34f97 100644
> > --- a/net/ipv6/route.c
> > +++ b/net/ipv6/route.c
> > @@ -218,7 +218,7 @@ struct neighbour *__ip6_dst_neigh_lookup(const struct in6_addr *gw,
> > if (n)
> > return n;
> >
> > - n = neigh_create(nd_table(dev_net(dev)), daddr, dev);
> > + n = ipv6_neigh_create(dev, daddr);
>
> [Severity: Medium]
> Can the table chosen inside ipv6_neigh_create() go stale before the
> neighbour is inserted? The helper reads dev_net(dev) without
> synchronization:
>
> struct neigh_table *tbl = nd_table(dev_net(dev));
>
> return neigh_create(tbl, pkey, dev);
>
> Under tbl->lock, ___neigh_create() only checks the following. It does not
> check that dev is still in tbl's netns or that n->parms belongs to tbl:
>
> if (n->parms->dead) {
> rc = ERR_PTR(-EINVAL);
> goto out_tbl_unlock;
> }
>
> Consider a preemptible process-context creator, such as a dst_neigh_lookup()
> caller that reaches __ip6_dst_neigh_lookup() outside RCU:
>
> creator
> ipv6_neigh_create(dev, ...)
> tbl = nd_table(dev_net(dev)) <- old netns
> <preempted>
>
> __dev_change_net_namespace()
> NETDEV_UNREGISTER
> rcu_barrier()
> dev_net_set(dev, new_net)
> NETDEV_REGISTER <- new in6_dev
>
> creator resumes
> neigh_create(old_tbl, ...)
> ndisc_constructor()
> in6_dev_get(dev) <- new in6_dev, live parms
>
> The new parms are live, so the dead check passes. The neighbour is then
> linked into the old netns's table and onto dev->neighbours[]. A later
> neigh_ifdown(new_tbl, dev) would change old_tbl's hash and gc lists without
> old_tbl->lock. That is the same state described in the commit message.
>
> The rcu_barrier() in __dev_change_net_namespace() does not exclude a
> non-RCU process-context reader like this. The window is narrow and there is
> no reproducer.
>
> Would a check under tbl->lock in ___neigh_create(), for example rejecting
> n->parms->tbl != tbl with -EINVAL, close this window? It would also cover
> the sch_teql case above.
Good point, will update v2.
---
pw-bot: cr
>
> > return IS_ERR(n) ? NULL : n;
> > }
> >
>
> --
> Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930200326.718459-1-kuniyu%40google.com
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-10-02 17:01 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-30 20:03 [PATCH v1 net-next 0/8] neighbour: Add IPv4/IPv6-specific wrappers for neigh_lookup() and friends Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 1/8] ipv4: Rename ipv4_neigh_lookup() to ipv4_dst_neigh_lookup() Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 2/8] ipv6: Rename ip6_neigh_lookup() to __ip6_dst_neigh_lookup() Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 3/8] ipv4: Add wrappers for neigh_lookup() and neigh_create() Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 4/8] ipv6: Add wrappers for neigh_lookup() and (__)?neigh_create() Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 5/8] mlxsw: spectrum: Resolve neigh_table right before neigh_lookup() Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 6/8] ipv6: Use ipv6_neigh_lookup() and friends Kuniyuki Iwashima
2026-10-02 2:03 ` netdev-bot+sashiko
2026-10-02 17:01 ` Kuniyuki Iwashima
2026-09-30 20:03 ` [PATCH v1 net-next 7/8] ipv4: Use ipv4_neigh_lookup() " Kuniyuki Iwashima
2026-10-02 2:03 ` netdev-bot+sashiko
2026-09-30 20:03 ` [PATCH v1 net-next 8/8] neighbour: Remove __neigh_lookup() and __neigh_lookup_errno() Kuniyuki Iwashima
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox