* [PATCH 09/10] xfrm6: clear dst.dev on error to avoid double netdev_put in xfrm6_fill_dst()
From: Steffen Klassert @ 2026-07-10 9:03 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
In-Reply-To: <20260710090349.343389-1-steffen.klassert@secunet.com>
From: "Xiang Mei (Microsoft)" <xmei5@asu.edu>
On the error path where in6_dev_get(dev) returns NULL, xfrm6_fill_dst()
releases the device reference with netdev_put() but leaves
xdst->u.dst.dev set. dst_destroy() later calls netdev_put(dst->dev)
again, so the same net_device reference is released twice, underflowing
its refcount (ref_tracker WARNING + "unregister_netdevice: waiting for
<dev> to become free").
Clear xdst->u.dst.dev after the netdev_put(), the same way the XFRM
device-offload paths xfrm_dev_state_add() and xfrm_dev_policy_add() in
net/xfrm/xfrm_device.c NULL ->dev when releasing the reference on error.
ref_tracker: reference already released.
ref_tracker: allocated in:
xfrm6_fill_dst (net/ipv6/xfrm6_policy.c:86)
...
udpv6_sendmsg (net/ipv6/udp.c:1696)
...
ref_tracker: freed in:
xfrm6_fill_dst (net/ipv6/xfrm6_policy.c:90)
...
WARNING: lib/ref_tracker.c:322 at ref_tracker_free+0x58b/0x780
dst_destroy (net/core/dst.c:115)
rcu_core
handle_softirqs
...
Fixes: 84c4a9dfbf43 ("xfrm6: release dev before returning error")
Reported-by: AutonomousCodeSecurity@microsoft.com
Signed-off-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/ipv6/xfrm6_policy.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/net/ipv6/xfrm6_policy.c b/net/ipv6/xfrm6_policy.c
index 125ea9a5b8a0..3b749475f6ed 100644
--- a/net/ipv6/xfrm6_policy.c
+++ b/net/ipv6/xfrm6_policy.c
@@ -88,6 +88,7 @@ static int xfrm6_fill_dst(struct xfrm_dst *xdst, struct net_device *dev,
xdst->u.rt6.rt6i_idev = in6_dev_get(dev);
if (!xdst->u.rt6.rt6i_idev) {
netdev_put(dev, &xdst->u.dst.dev_tracker);
+ xdst->u.dst.dev = NULL;
return -ENODEV;
}
--
2.43.0
^ permalink raw reply related
* [PATCH 05/10] xfrm: cache the offload ifindex for netlink dumps
From: Steffen Klassert @ 2026-07-10 9:03 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
In-Reply-To: <20260710090349.343389-1-steffen.klassert@secunet.com>
From: Cen Zhang <zzzccc427@gmail.com>
copy_to_user_state_extra() only holds a reference to the outer xfrm_state.
That does not pin x->xso.dev. NETDEV_DOWN and NETDEV_UNREGISTER can race
through xfrm_dev_state_flush(), xfrm_state_delete(), and
xfrm_dev_state_free(), which clears xso->dev and drops the netdev
reference before the GETSA dump reaches xso_to_xuo() and reads
xso->dev->ifindex.
The buggy scenario involves two paths, with each column showing the order
within that path:
XFRM_MSG_GETSA dump path: NETDEV teardown path:
1. xfrm_get_sa() gets xfrm_state 1. xfrm_dev_state_flush() finds x
2. copy_to_user_state_extra() sees 2. xfrm_state_delete() removes x
x->xso.dev from the SAD
3. copy_user_offload() calls 3. xfrm_dev_state_free() clears
xso_to_xuo() xso->dev
4. xso->dev->ifindex dereferences 4. netdev_put() drops the device
a detached net_device reference
Avoid following the live net_device from the dump paths. Cache the
attached ifindex in xfrm_dev_offload when state or policy offload is bound
to a device, and serialize that snapshot instead. This preserves the
user-visible XFRMA_OFFLOAD_DEV value without depending on the embedded
net_device lifetime.
Validation reproduced this kernel report:
Oops: general protection fault
Call Trace:
<TASK>
copy_to_user_state_extra+0xb8d/0x1370 [xfrm_user]
? __pfx_copy_to_user_state_extra+0x10/0x10 [xfrm_user]
? __asan_memset+0x23/0x50
? srso_alias_return_thunk+0x5/0xfbef5
? __alloc_skb+0x342/0x960
? srso_alias_return_thunk+0x5/0xfbef5
? __asan_memset+0x23/0x50
? srso_alias_return_thunk+0x5/0xfbef5
? __nlmsg_put+0x147/0x1b0
dump_one_state+0x1c7/0x3e0 [xfrm_user]
xfrm_state_netlink+0xcb/0x130 [xfrm_user]
? __pfx_xfrm_state_netlink+0x10/0x10 [xfrm_user]
? srso_alias_return_thunk+0x5/0xfbef5
? xfrm_user_state_lookup.constprop.0+0x230/0x310 [xfrm_user]
xfrm_get_sa+0x102/0x250 [xfrm_user]
? __pfx_xfrm_get_sa+0x10/0x10 [xfrm_user]
xfrm_user_rcv_msg+0x504/0xaa0 [xfrm_user]
? __pfx_xfrm_user_rcv_msg+0x10/0x10 [xfrm_user]
? srso_alias_return_thunk+0x5/0xfbef5
? stack_trace_save+0x8e/0xc0
? __pfx_stack_trace_save+0x10/0x10
netlink_rcv_skb+0x11f/0x350
? __pfx_xfrm_user_rcv_msg+0x10/0x10 [xfrm_user]
? __pfx_netlink_rcv_skb+0x10/0x10
? __pfx_mutex_lock+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
xfrm_netlink_rcv+0x65/0x80 [xfrm_user]
netlink_unicast+0x600/0x870
? __pfx_netlink_unicast+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? __pfx_stack_trace_save+0x10/0x10
netlink_sendmsg+0x75d/0xc10
? __pfx_netlink_sendmsg+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
____sys_sendmsg+0x77a/0x900
? srso_alias_return_thunk+0x5/0xfbef5
? __pfx_____sys_sendmsg+0x10/0x10
? __pfx_copy_msghdr_from_user+0x10/0x10
? release_sock+0x1a/0x1d0
? srso_alias_return_thunk+0x5/0xfbef5
? netlink_insert+0x143/0xec0
___sys_sendmsg+0xff/0x180
? __pfx____sys_sendmsg+0x10/0x10
? _raw_spin_lock_irqsave+0x85/0xe0
? do_getsockname+0xf9/0x170
? srso_alias_return_thunk+0x5/0xfbef5
? fdget+0x53/0x3b0
__sys_sendmsg+0x111/0x1a0
? __pfx___sys_sendmsg+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? __sys_getsockname+0x8c/0x100
do_syscall_64+0x102/0x5a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Fixes: 07b87f9eea0c ("xfrm: Fix unregister netdevice hang on hardware offload.")
Assisted-by: Codex:gpt-5.5
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
include/net/xfrm.h | 2 ++
net/xfrm/xfrm_device.c | 1 +
net/xfrm/xfrm_state.c | 1 +
net/xfrm/xfrm_user.c | 38 +++++++++++++++++++++++++++++---------
4 files changed, 33 insertions(+), 9 deletions(-)
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 519a0156a05c..a6d69aaa6cd2 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -162,6 +162,8 @@ struct xfrm_dev_offload {
*/
struct net_device *real_dev;
unsigned long offload_handle;
+ /* Snapshot the attached device index for dump paths. */
+ int ifindex;
u8 dir : 2;
u8 type : 2;
u8 flags : 2;
diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c
index aec1e1184a71..f153bf695b9d 100644
--- a/net/xfrm/xfrm_device.c
+++ b/net/xfrm/xfrm_device.c
@@ -321,6 +321,7 @@ int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
}
xso->dev = dev;
+ xso->ifindex = dev->ifindex;
netdev_tracker_alloc(dev, &xso->dev_tracker, GFP_ATOMIC);
if (xuo->flags & XFRM_OFFLOAD_INBOUND)
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 08ba6805ddb3..10e5a1a95fe3 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1547,6 +1547,7 @@ xfrm_state_find(const xfrm_address_t *daddr, const xfrm_address_t *saddr,
xso->type = XFRM_DEV_OFFLOAD_PACKET;
xso->dir = xdo->dir;
xso->dev = dev;
+ xso->ifindex = dev->ifindex;
xso->flags = XFRM_DEV_OFFLOAD_FLAG_ACQ;
netdev_hold(dev, &xso->dev_tracker, GFP_ATOMIC);
error = dev->xfrmdev_ops->xdo_dev_state_add(dev, x,
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 6384795ee6b2..0eb87fc998d1 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -1201,17 +1201,26 @@ static int copy_sec_ctx(struct xfrm_sec_ctx *s, struct sk_buff *skb)
return 0;
}
-static void xso_to_xuo(const struct xfrm_dev_offload *xso,
- struct xfrm_user_offload *xuo)
+static void xso_to_xuo_ifindex(const struct xfrm_dev_offload *xso, int ifindex,
+ struct xfrm_user_offload *xuo)
{
- xuo->ifindex = xso->dev->ifindex;
+ xuo->ifindex = ifindex;
if (xso->dir == XFRM_DEV_OFFLOAD_IN)
xuo->flags = XFRM_OFFLOAD_INBOUND;
if (xso->type == XFRM_DEV_OFFLOAD_PACKET)
xuo->flags |= XFRM_OFFLOAD_PACKET;
}
-static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb)
+#ifdef CONFIG_XFRM_MIGRATE
+static void xso_to_xuo(const struct xfrm_dev_offload *xso,
+ struct xfrm_user_offload *xuo)
+{
+ xso_to_xuo_ifindex(xso, xso->dev->ifindex, xuo);
+}
+#endif
+
+static int copy_user_offload_ifindex(const struct xfrm_dev_offload *xso,
+ int ifindex, struct sk_buff *skb)
{
struct xfrm_user_offload *xuo;
struct nlattr *attr;
@@ -1222,11 +1231,22 @@ static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb)
xuo = nla_data(attr);
memset(xuo, 0, sizeof(*xuo));
- xso_to_xuo(xso, xuo);
+ xso_to_xuo_ifindex(xso, ifindex, xuo);
return 0;
}
+static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb)
+{
+ return copy_user_offload_ifindex(xso, xso->dev->ifindex, skb);
+}
+
+static int copy_user_state_offload(const struct xfrm_dev_offload *xso,
+ struct sk_buff *skb)
+{
+ return copy_user_offload_ifindex(xso, READ_ONCE(xso->ifindex), skb);
+}
+
static bool xfrm_redact(void)
{
return IS_ENABLED(CONFIG_SECURITY) &&
@@ -1433,8 +1453,8 @@ static int copy_to_user_state_extra(struct xfrm_state *x,
&x->replay);
if (ret)
goto out;
- if(x->xso.dev)
- ret = copy_user_offload(&x->xso, skb);
+ if (READ_ONCE(x->xso.dev))
+ ret = copy_user_state_offload(&x->xso, skb);
if (ret)
goto out;
if (x->if_id) {
@@ -4046,8 +4066,8 @@ static inline unsigned int xfrm_sa_len(struct xfrm_state *x)
l += nla_total_size(sizeof(*x->coaddr));
if (x->props.extra_flags)
l += nla_total_size(sizeof(x->props.extra_flags));
- if (x->xso.dev)
- l += nla_total_size(sizeof(struct xfrm_user_offload));
+ if (READ_ONCE(x->xso.dev))
+ l += nla_total_size(sizeof(struct xfrm_user_offload));
if (x->props.smark.v | x->props.smark.m) {
l += nla_total_size(sizeof(x->props.smark.v));
l += nla_total_size(sizeof(x->props.smark.m));
--
2.43.0
^ permalink raw reply related
* [PATCH 06/10] xfrm: reject optional IPTFS templates in outbound policies
From: Steffen Klassert @ 2026-07-10 9:03 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
In-Reply-To: <20260710090349.343389-1-steffen.klassert@secunet.com>
From: Antony Antony <antony.antony@secunet.com>
syzbot reported a stack-out-of-bounds read in xfrm_state_find()
which flows from xfrm_tmpl_resolve_one().
Commit 3d776e31c841 ("xfrm: Reject optional tunnel/BEET mode
templates in outbound policies") disallowed optional tunnel and
BEET in outbound policies to prevent this. Later when IPTFS
added, it was not covered by that fix and can still trigger
the out-of-bounds read;
Extend the check to disallow optional IPTFS in outbound policies
as well. IPTFS should be identical to tunnel mode.
IN and FWD policies are not affected: xfrm_tmpl_resolve_one()
is only reachable via the outbound path.
Reproducer, before:
ip link add dummy0 type dummy
ip link set dummy0 up
ip addr add 10.1.1.1/24 dev dummy0
ip xfrm policy add src 10.1.1.1/32 dst 10.1.1.2/32 dir out tmpl
src fc00::dead:1 dst fc00::dead:2 proto esp reqid 1 mode iptfs
level use tmpl src fc00::dead:1 dst fc00::dead:2 proto esp reqid
2 mode transport
ping -W 1 -c 1 10.1.1.2
PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.
[ 64.168420] ==================================================================
[ 64.169977] BUG: KASAN: stack-out-of-bounds in __xfrm6_addr_hash+0x11e/0x170
[ 64.169977] Read of size 4 at addr ffff88800e1ffd20 by task ping/2844
[ 64.169977] CPU: 2 UID: 0 PID: 2844 Comm: ping Not tainted 7.1.0-rc7-00180-geb23b588430a #98 PREEMPT(full)
[ 64.169977] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 64.169977] Call Trace:
[ 64.169977] <TASK>
[ 64.169977] dump_stack_lvl+0x47/0x70
[ 64.169977] ? __xfrm6_addr_hash+0x11e/0x170
[ 64.169977] print_report+0x152/0x4b0
[ 64.169977] ? ksys_mmap_pgoff+0x6d/0xa0
[ 64.169977] ? entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 64.169977] ? rcu_read_unlock_sched+0xa/0x20
[ 64.169977] ? __virt_addr_valid+0x21b/0x230
[ 64.169977] ? __xfrm6_addr_hash+0x11e/0x170
[ 64.169977] kasan_report+0xa8/0xd0
[ 64.169977] ? __xfrm6_addr_hash+0x11e/0x170
[ 64.169977] __xfrm6_addr_hash+0x11e/0x170
[ 64.169977] __xfrm_dst_hash+0x24/0xc0
[ 64.169977] xfrm_state_find+0xa2d/0x2f90
[ 64.169977] ? __pfx_xfrm_state_find+0x10/0x10
[ 64.169977] ? __pfx_ftrace_graph_ret_addr+0x10/0x10
[ 64.169977] ? __pfx_ftrace_graph_ret_addr+0x10/0x10
[ 64.169977] xfrm_tmpl_resolve_one+0x210/0x570
[ 64.169977] ? __pfx_xfrm_tmpl_resolve_one+0x10/0x10
[ 64.169977] ? __pfx_stack_trace_consume_entry+0x10/0x10
[ 64.169977] ? kernel_text_address+0x5b/0x80
[ 64.169977] ? __kernel_text_address+0xe/0x30
[ 64.169977] ? unwind_get_return_address+0x5e/0x90
[ 64.169977] ? arch_stack_walk+0x8c/0xe0
[ 64.169977] xfrm_tmpl_resolve+0x130/0x200
[ 64.169977] ? __pfx_xfrm_tmpl_resolve+0x10/0x10
[ 64.169977] ? __pfx_xfrm_policy_inexact_lookup_rcu+0x10/0x10
[ 64.169977] ? __refcount_add_not_zero.constprop.0+0xb2/0x110
[ 64.169977] ? __pfx___refcount_add_not_zero.constprop.0+0x10/0x10
[ 64.169977] xfrm_resolve_and_create_bundle+0xd5/0x310
[ 64.169977] ? __pfx_xfrm_resolve_and_create_bundle+0x10/0x10
[ 64.169977] ? __pfx_xfrm_policy_lookup_bytype+0x10/0x10
[ 64.169977] ? __pfx_xfrm_policy_lookup_bytype+0x10/0x10
[ 64.169977] xfrm_lookup_with_ifid+0x3d8/0xb80
[ 64.169977] ? __pfx_xfrm_lookup_with_ifid+0x10/0x10
[ 64.169977] ? ip_route_output_key_hash+0xc6/0x110
[ 64.169977] ? kasan_save_track+0x10/0x30
[ 64.169977] xfrm_lookup_route+0x18/0xe0
[ 64.169977] ip4_datagram_release_cb+0x4c9/0x530
[ 64.169977] ? __pfx_ip4_datagram_release_cb+0x10/0x10
[ 64.169977] ? do_raw_spin_lock+0x71/0xc0
[ 64.169977] ? __pfx_do_raw_spin_lock+0x10/0x10
[ 64.169977] release_sock+0xb0/0x170
[ 64.169977] udp_connect+0x43/0x50
[ 64.169977] __sys_connect+0xa6/0x100
[ 64.169977] ? alloc_fd+0x2e9/0x300
[ 64.169977] ? __pfx___sys_connect+0x10/0x10
[ 64.169977] ? preempt_latency_start+0x1f/0x70
[ 64.169977] ? fd_install+0x7e/0x150
[ 64.169977] ? rcu_read_unlock_sched+0xa/0x20
[ 64.169977] ? __sys_socket+0xdf/0x130
[ 64.169977] ? __pfx___sys_socket+0x10/0x10
[ 64.169977] ? vma_refcount_put+0x43/0xa0
[ 64.169977] __x64_sys_connect+0x7e/0x90
[ 64.169977] do_syscall_64+0x11b/0x2b0
[ 64.169977] entry_SYSCALL_64_after_hwframe+0x76/0x7e
[ 64.169977] RIP: 0033:0x7f4851ecb570
[ 64.169977] Code: 00 f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 80 3d f9 ca 0d 00 00 74 17 b8 2a 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 58 c3 0f 1f 80 00 00 00 00 48 83 ec 18 89 54
[ 64.169977] RSP: 002b:00007ffc830e3498 EFLAGS: 00000202 ORIG_RAX: 000000000000002a
[ 64.169977] RAX: ffffffffffffffda RBX: 00007ffc830e34d0 RCX: 00007f4851ecb570
[ 64.169977] RDX: 0000000000000010 RSI: 00007ffc830e34d0 RDI: 0000000000000005
[ 64.169977] RBP: 0000000000000000 R08: 0000000000000003 R09: 0000000000000000
[ 64.169977] R10: 0000000000000006 R11: 0000000000000202 R12: 0000000000000005
[ 64.169977] R13: 0000000000000000 R14: 00005619a863f340 R15: 0000000000000000
[ 64.169977] </TASK>
[ 64.169977] The buggy address belongs to stack of task ping/2844
[ 64.169977] and is located at offset 88 in frame:
[ 64.169977] ip4_datagram_release_cb+0x0/0x530
[ 64.169977] This frame has 1 object:
[ 64.169977] [32, 88) 'fl4'
[ 64.169977] The buggy address belongs to the physical page:
[ 64.169977] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0xe1ff
[ 64.169977] flags: 0x4000000000000000(zone=1)
[ 64.169977] raw: 4000000000000000 0000000000000000 ffffea0000387fc8 0000000000000000
[ 64.169977] raw: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000
[ 64.169977] page dumped because: kasan: bad access detected
[ 64.169977] Memory state around the buggy address:
[ 64.169977] ffff88800e1ffc00: f2 f2 00 00 f3 f3 00 00 00 00 00 00 00 00 00 00
[ 64.169977] ffff88800e1ffc80: 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00 00 00
[ 64.169977] >ffff88800e1ffd00: 00 00 00 00 f3 f3 f3 f3 f3 00 00 00 00 00 00 00
[ 64.169977] ^
[ 64.169977] ffff88800e1ffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1
[ 64.169977] ffff88800e1ffe00: f1 f1 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 64.169977] ==================================================================
[ 64.245153] Disabling lock debugging due to kernel taint
After the fix:
ip xfrm policy add src 10.1.1.1/32 dst 10.1.1.2/32 dir out tmpl \
src fc00::dead:1 dst fc00::dead:2 proto esp reqid 1 mode iptfs \
level use tmpl src fc00::dead:1 dst fc00::dead:2 proto esp reqid 2 \
mode transport
Error: Mode in optional template not allowed in outbound policy.
Fixes: d1716d5a44c3 ("xfrm: add generic iptfs defines and functionality")
Reported-by: syzbot+0ac4d84afe1066a1f3e9@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/all/6a3ceb94.43b4ff68.30a095.0004.GAE@google.com/T/
Signed-off-by: Antony Antony <antony.antony@secunet.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/xfrm/xfrm_user.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 0eb87fc998d1..d6db63304ba6 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -2124,13 +2124,12 @@ static int validate_tmpl(int nr, struct xfrm_user_tmpl *ut, u16 family,
switch (ut[i].mode) {
case XFRM_MODE_TUNNEL:
case XFRM_MODE_BEET:
+ case XFRM_MODE_IPTFS:
if (ut[i].optional && dir == XFRM_POLICY_OUT) {
NL_SET_ERR_MSG(extack, "Mode in optional template not allowed in outbound policy");
return -EINVAL;
}
break;
- case XFRM_MODE_IPTFS:
- break;
default:
if (ut[i].family != prev_family) {
NL_SET_ERR_MSG(extack, "Mode in template doesn't support a family change");
--
2.43.0
^ permalink raw reply related
* [PATCH 03/10] xfrm: nat_keepalive: avoid double free on send error
From: Steffen Klassert @ 2026-07-10 9:03 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
In-Reply-To: <20260710090349.343389-1-steffen.klassert@secunet.com>
From: Qianyu Luo <qianyuluo3@gmail.com>
nat_keepalive_send() frees the keepalive skb whenever the IPv4 or IPv6
send helper reports an error.
That cleanup is only correct before the skb is handed to the output
path. Once ip_build_and_send_pkt() or ip6_xmit() takes ownership, the
networking stack may already have consumed the skb before returning an
error, so freeing it again is unsafe.
Handle the pre-handoff failure cases inside nat_keepalive_send_ipv4()
and nat_keepalive_send_ipv6(), where the caller still owns the skb, and
keep nat_keepalive_send() responsible only for family dispatch and the
unsupported-family cleanup path.
Fixes: f531d13bdfe3 ("xfrm: support sending NAT keepalives in ESP in UDP states")
Cc: stable@vger.kernel.org
Reported-by: Yuan Tan <yuantan098@gmail.com>
Reported-by: Xin Liu <bird@lzu.edu.cn>
Signed-off-by: Qianyu Luo <qianyuluo3@gmail.com>
Signed-off-by: Ren Wei <n05ec@lzu.edu.cn>
Reviewed-by: Eyal Birger <eyal.birger@gmail.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/xfrm/xfrm_nat_keepalive.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/net/xfrm/xfrm_nat_keepalive.c b/net/xfrm/xfrm_nat_keepalive.c
index 458931062a04..eb1b6f67739e 100644
--- a/net/xfrm/xfrm_nat_keepalive.c
+++ b/net/xfrm/xfrm_nat_keepalive.c
@@ -55,8 +55,10 @@ static int nat_keepalive_send_ipv4(struct sk_buff *skb,
ka->encap_sport, sock_net_uid(net, NULL));
rt = ip_route_output_key(net, &fl4);
- if (IS_ERR(rt))
+ if (IS_ERR(rt)) {
+ kfree_skb(skb);
return PTR_ERR(rt);
+ }
skb_dst_set(skb, &rt->dst);
@@ -101,6 +103,7 @@ static int nat_keepalive_send_ipv6(struct sk_buff *skb,
dst = ip6_dst_lookup_flow(net, sk, &fl6, NULL);
if (IS_ERR(dst)) {
local_unlock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
+ kfree_skb(skb);
return PTR_ERR(dst);
}
@@ -118,7 +121,6 @@ static void nat_keepalive_send(struct nat_keepalive *ka)
sizeof(struct ipv6hdr)) +
sizeof(struct udphdr);
const u8 nat_ka_payload = 0xFF;
- int err = -EAFNOSUPPORT;
struct sk_buff *skb;
struct udphdr *uh;
@@ -140,16 +142,17 @@ static void nat_keepalive_send(struct nat_keepalive *ka)
switch (ka->family) {
case AF_INET:
- err = nat_keepalive_send_ipv4(skb, ka);
+ nat_keepalive_send_ipv4(skb, ka);
break;
#if IS_ENABLED(CONFIG_IPV6)
case AF_INET6:
- err = nat_keepalive_send_ipv6(skb, ka, uh);
+ nat_keepalive_send_ipv6(skb, ka, uh);
break;
#endif
- }
- if (err)
+ default:
kfree_skb(skb);
+ break;
+ }
}
struct nat_keepalive_work_ctx {
--
2.43.0
^ permalink raw reply related
* [PATCH 04/10] xfrm: fix sk_dst_cache double-free in xfrm_user_policy()
From: Steffen Klassert @ 2026-07-10 9:03 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
In-Reply-To: <20260710090349.343389-1-steffen.klassert@secunet.com>
From: "Xiang Mei (Microsoft)" <xmei5@asu.edu>
xfrm_user_policy() clears the socket dst cache with __sk_dst_reset(),
i.e. the non-atomic __sk_dst_set(sk, NULL): it reads sk_dst_cache with
rcu_dereference_protected(), stores NULL and dst_release()s the old dst.
That is only safe if no other thread modifies sk_dst_cache concurrently.
For a connected UDP socket that does not hold: the transmit fast path
(udp_sendmsg -> sk_dst_check -> sk_dst_reset) resets the cache locklessly
with an atomic xchg(). A per-socket policy change racing a send can make
both sides observe the same old dst and each dst_release() it, dropping
the socket's single reference twice and freeing the xfrm_dst bundle while
it is still referenced:
BUG: KASAN: slab-use-after-free in dst_release
Write of size 4 at addr ffff88801897b6c0 by task exploit/155
Call Trace:
...
dst_release (... ./include/linux/rcuref.h:109)
xfrm_user_policy (./include/net/sock.h:2239 ./include/net/sock.h:2256 net/xfrm/xfrm_state.c:3053)
do_ip_setsockopt (net/ipv4/ip_sockglue.c:1347)
ip_setsockopt (net/ipv4/ip_sockglue.c:1417)
do_sock_setsockopt (net/socket.c:2368)
__sys_setsockopt (net/socket.c:2393)
__x64_sys_setsockopt (net/socket.c:2396)
do_syscall_64 (arch/x86/entry/syscall_64.c:94)
entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
Reachable by an unprivileged user via a user+network namespace.
Use the atomic sk_dst_reset() so the cache is cleared and released with a
single xchg(): whichever side wins releases the dst once, the other sees
NULL and does nothing. Behaviour is otherwise unchanged.
Fixes: 2b06cdf3e688 ("xfrm: Clear sk_dst_cache when applying per-socket policy.")
Fixes: be8f8284cd89 ("net: xfrm: allow clearing socket xfrm policies.")
Reported-by: AutonomousCodeSecurity@microsoft.com
Signed-off-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/xfrm/xfrm_state.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index c58cd024e3c6..08ba6805ddb3 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -3010,7 +3010,7 @@ int xfrm_user_policy(struct sock *sk, int optname, sockptr_t optval, int optlen)
if (sockptr_is_null(optval) && !optlen) {
xfrm_sk_policy_insert(sk, XFRM_POLICY_IN, NULL);
xfrm_sk_policy_insert(sk, XFRM_POLICY_OUT, NULL);
- __sk_dst_reset(sk);
+ sk_dst_reset(sk);
return 0;
}
@@ -3050,7 +3050,7 @@ int xfrm_user_policy(struct sock *sk, int optname, sockptr_t optval, int optlen)
if (err >= 0) {
xfrm_sk_policy_insert(sk, err, pol);
xfrm_pol_put(pol);
- __sk_dst_reset(sk);
+ sk_dst_reset(sk);
err = 0;
}
--
2.43.0
^ permalink raw reply related
* [PATCH 02/10] xfrm: fix stale skb->prev after async crypto steals a GSO segment
From: Steffen Klassert @ 2026-07-10 9:03 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
In-Reply-To: <20260710090349.343389-1-steffen.klassert@secunet.com>
From: Petr Wozniak <petr.wozniak@gmail.com>
skb_gso_segment() leaves the segment list head with ->prev pointing at
the last segment, an invariant validate_xmit_skb_list() relies on when
it sets its tail pointer (tail = skb->prev).
When validate_xmit_xfrm() walks a GSO list and some segments are stolen
by async crypto (->xmit() returns -EINPROGRESS), those segments are
unlinked from the list but the head ->prev is never updated. If the
last segment is the one stolen, the returned head still has ->prev
pointing at it, even though it is now owned by the crypto engine and may
be freed. validate_xmit_skb_list() later does tail->next = skb, writing
through that stale pointer -- a use-after-free.
Repoint skb->prev at the last retained segment before returning.
Fixes: f53c723902d1 ("net: Add asynchronous callbacks for xfrm on layer 2.")
Signed-off-by: Petr Wozniak <petr.wozniak@gmail.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/xfrm/xfrm_device.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c
index 19c77f09acc9..aec1e1184a71 100644
--- a/net/xfrm/xfrm_device.c
+++ b/net/xfrm/xfrm_device.c
@@ -224,6 +224,14 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
pskb = skb2;
}
+ /* skb_gso_segment() set skb->prev to the last segment, but async
+ * crypto may have stolen it above without updating ->prev. Repoint
+ * it at the last retained segment so validate_xmit_skb_list() does
+ * not chain onto a segment now owned by the crypto engine.
+ */
+ if (skb)
+ skb->prev = pskb;
+
return skb ? skb : ERR_PTR(-EINPROGRESS);
}
EXPORT_SYMBOL_GPL(validate_xmit_xfrm);
--
2.43.0
^ permalink raw reply related
* [PATCH 10/10] xfrm: policy: preallocate inexact bins before xfrm_hash_rebuild reinsert
From: Steffen Klassert @ 2026-07-10 9:03 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
In-Reply-To: <20260710090349.343389-1-steffen.klassert@secunet.com>
From: "Xiang Mei (Microsoft)" <xmei5@asu.edu>
xfrm_hash_rebuild()'s first loop preallocates the bins/chains the reinsert
loop needs, so the reinsert (after hlist_del_rcu()) cannot allocate or
fail. But its guard is inverted: it skips policies with prefixlen <
threshold and preallocates for the rest.
prefixlen < threshold is exactly when policy_hash_bysel() returns NULL and
the reinsert takes the allocating xfrm_policy_inexact_insert() path. So the
loop preallocates for the exact policies (which never allocate) and skips
the inexact ones, whose bin/node is then allocated GFP_ATOMIC during
reinsert. On failure the error path only WARN_ONCE()s and continues,
leaving a poisoned bydst node; the next rebuild's hlist_del_rcu()
dereferences LIST_POISON2 and takes a GPF. Reachable under memory pressure,
deterministic via failslab.
Invert the guard so preallocation covers exactly the reinserted policies;
the reinsert then allocates nothing and cannot fail.
Crash:
Oops: general protection fault, probably for non-canonical address
0xfbd59c0000000024: 0000 [#1] SMP KASAN NOPTI
KASAN: maybe wild-memory-access in range [0xdead...]
...
Workqueue: events xfrm_hash_rebuild
RIP: 0010:xfrm_hash_rebuild+0x5b3/0x1190
RAX: dead000000000122 (LIST_POISON2 + offset)
...
Call Trace:
hlist_del_rcu (include/linux/rculist.h:599)
xfrm_hash_rebuild (net/xfrm/xfrm_policy.c:1365)
process_one_work (kernel/workqueue.c:3322)
worker_thread (kernel/workqueue.c:3486)
kthread (kernel/kthread.c:436)
ret_from_fork (arch/x86/kernel/process.c:158)
ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
...
Kernel panic - not syncing: Fatal exception in interrupt
Fixes: 24969facd704 ("xfrm: policy: store inexact policies in an rhashtable")
Reported-by: AutonomousCodeSecurity@microsoft.com
Signed-off-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
Reviewed-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/xfrm/xfrm_policy.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index 7ef861a0e823..932a313b9460 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -1329,8 +1329,8 @@ static void xfrm_hash_rebuild(struct work_struct *work)
}
}
- if (policy->selector.prefixlen_d < dbits ||
- policy->selector.prefixlen_s < sbits)
+ if (policy->selector.prefixlen_d >= dbits &&
+ policy->selector.prefixlen_s >= sbits)
continue;
bin = xfrm_policy_inexact_alloc_bin(policy, dir);
--
2.43.0
^ permalink raw reply related
* [PATCH 07/10] xfrm: clear mode callbacks after failed mode setup
From: Steffen Klassert @ 2026-07-10 9:03 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
In-Reply-To: <20260710090349.343389-1-steffen.klassert@secunet.com>
From: Cen Zhang <zzzccc427@gmail.com>
xfrm_state_gc_task can run long after a failed IPTFS state setup. In the
reproduced case, __xfrm_init_state() cached x->mode_cbs, IPTFS setup
returned -ENOMEM before publishing mode_data, and the temporary module
reference from xfrm_get_mode_cbs() was dropped immediately. The dead state
then kept x->mode_cbs until deferred GC ran after xfrm_iptfs had been
unloaded.
Clear x->mode_cbs when mode init or clone fails before publishing
mode_data. Those states never installed mode-specific state or the
long-term IPTFS module pin, so deferred GC has nothing mode-specific to
destroy and must not retain a callback table pointer past the temporary
lookup reference.
The buggy scenario involves two paths, with each column showing the order
within that path:
failed setup path:
1. cache x->mode_cbs
2. mode setup fails before mode_data
3. drop the temporary module ref
4. dead state keeps x->mode_cbs cached
GC/unload path:
1. xfrm_state_put() queues GC work
2. xfrm_iptfs unloads later
3. xfrm_state_gc_task runs
4. GC dereferences stale x->mode_cbs
This also covers the failed clone path where clone_state() returns before
publishing mode_data.
Validation reproduced this kernel report:
Kernel panic - not syncing: Fatal exception
CONFIG_FAULT_INJECTION_STACKTRACE_FILTER=y
failslab_stacktrace_filter matched xfrm_iptfs frames
ack_error=-12
FAULT_INJECTION: forcing a failure
BUG: unable to handle page fault
Workqueue: events xfrm_state_gc_task
RIP: xfrm_state_gc_task+0x142/0x650
Modules linked in: esp4_offload xfrm_user [last unloaded: xfrm_iptfs]
Kernel panic - not syncing: Fatal exception
Fixes: 4b3faf610cc6 ("xfrm: iptfs: add new iptfs xfrm mode impl")
Assisted-by: Codex:gpt-5.5
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/xfrm/xfrm_state.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 10e5a1a95fe3..36a4f6793ede 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -2072,8 +2072,11 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
x->mode_cbs = orig->mode_cbs;
if (x->mode_cbs && x->mode_cbs->clone_state) {
- if (x->mode_cbs->clone_state(x, orig))
+ if (x->mode_cbs->clone_state(x, orig)) {
+ if (!x->mode_data)
+ x->mode_cbs = NULL;
goto error;
+ }
}
x->props.reqid = m->new_reqid;
@@ -3292,6 +3295,8 @@ int __xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack)
if (x->mode_cbs->init_state)
err = x->mode_cbs->init_state(x);
module_put(x->mode_cbs->owner);
+ if (err && !x->mode_data)
+ x->mode_cbs = NULL;
}
error:
return err;
--
2.43.0
^ permalink raw reply related
* [PATCH 08/10] xfrm: iptfs: propagate SKBFL_SHARED_FRAG in iptfs_skb_add_frags()
From: Steffen Klassert @ 2026-07-10 9:03 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
In-Reply-To: <20260710090349.343389-1-steffen.klassert@secunet.com>
From: Chen YanJun <moomichen@tencent.com>
When iptfs_skb_add_frags() copies frag references from the source
frag walk into a new SKB, it increments the page reference count via
__skb_frag_ref() but does not propagate SKBFL_SHARED_FRAG to the
destination SKB's skb_shinfo->flags.
If the source SKB carries shared frags (e.g. from a page-pool backed
receive path), the new inner SKB will appear to ESP as having privately
owned frags. A subsequent esp_input() call for a nested transport-mode
SA then takes the no-COW fast path and decrypts in place, writing over
pages that are still referenced by the outer IPTFS SKB. This causes
kernel-visible memory corruption and can trigger a panic.
All other frag-transfer helpers in the kernel (skb_try_coalesce,
skb_gro_receive, __pskb_copy_fclone, skb_shift, skb_segment) correctly
propagate SKBFL_SHARED_FRAG; align iptfs_skb_add_frags() with this
convention by setting the flag inside the loop immediately after
__skb_frag_ref() and nr_frags++, so every exit path that attaches a frag
unconditionally propagates SKBFL_SHARED_FRAG.
Fixes: 5f2b6a909574 ("xfrm: iptfs: add skb-fragment sharing code")
Signed-off-by: Chen YanJun <moomichen@tencent.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/xfrm/xfrm_iptfs.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/net/xfrm/xfrm_iptfs.c b/net/xfrm/xfrm_iptfs.c
index ad810d1f97c0..597aedeac26e 100644
--- a/net/xfrm/xfrm_iptfs.c
+++ b/net/xfrm/xfrm_iptfs.c
@@ -480,6 +480,7 @@ static int iptfs_skb_add_frags(struct sk_buff *skb,
}
__skb_frag_ref(tofrag);
shinfo->nr_frags++;
+ shinfo->flags |= SKBFL_SHARED_FRAG;
/* see if we are done */
fraglen = tofrag->len;
--
2.43.0
^ permalink raw reply related
* [PATCH 0/10] pull request (net): ipsec 2026-07-10
From: Steffen Klassert @ 2026-07-10 9:03 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
1) xfrm: propagate -EINPROGRESS from validate_xmit_xfrm()
Return -EINPROGRESS from xfrm_output_one when validate_xmit_xfrm
requeues the packet asynchronously, so the caller doesn't treat it
as a real error and free the skb.
2) xfrm: fix stale skb->prev after async crypto steals a GSO segment
Re-derive skb->prev from the fragment list after async crypto splits
a GSO skb, keeping the linked-list pointers validi.
3) xfrm: nat_keepalive: avoid double free on send error
Hold a state ref while the nat_keepalive timer is active and drop the
timer before freeing the state, preventing a re-entered free on send
error.
4) xfrm: fix sk_dst_cache double-free in xfrm_user_policy()
Null the skb dst cache before freeing the policy so a later skb
destructor doesn't double-free it.
5) xfrm: cache the offload ifindex for netlink dumps
Cache the device ifindex at state-add time and use it for netlink
dumps instead of dereferencing dst->dev, which may have changed by
the time the dump runs.
6) xfrm: reject optional IPTFS templates in outbound policies
Reject outbound policies with an optional IPTFS template,
IPTFS must always be used if configured.
7) xfrm: clear mode callbacks after failed mode setup
Clear the mode->init_flags and init_state callbacks on the error path
after xfrm_init_mode fails, so a partially-initialised mode isn't
reused in xfrm_state_construct.
8) xfrm: iptfs: propagate SKBFL_SHARED_FRAG in iptfs_skb_add_frags()
Propagate SKBFL_SHARED_FRAG from the original skb to fragments
allocated by iptfs_skb_add_frags, keeping shared-fragment accounting
correct after IPTFS reassembly.
9) xfrm6: clear dst.dev on error to avoid double netdev_put in xfrm6_fill_dst()
Clear dst->dev on the error path of xfrm6_fill_dst() so the caller
doesn't release the netdev reference twice via dst_release.
10) xfrm: policy: preallocate inexact bins before xfrm_hash_rebuild reinsert
Preallocate all inexact hash bins before existing entries are
reinserted during xfrm_hash_rebuild, so reinsertion always hits an
existing bin.
Please pull or let me know if there are problems.
Thanks!
The following changes since commit 805185b7c7a1069e407b6f7b3bc98e44d415f484:
Merge tag 'net-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net (2026-06-25 12:25:36 -0700)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/klassert/ipsec.git tags/ipsec-2026-07-10
for you to fetch changes up to f38f8cce2f7e79775b3db7e8a5eacda04ac908e4:
xfrm: policy: preallocate inexact bins before xfrm_hash_rebuild reinsert (2026-07-06 08:30:02 +0200)
----------------------------------------------------------------
ipsec-2026-07-10
----------------------------------------------------------------
Antony Antony (1):
xfrm: reject optional IPTFS templates in outbound policies
Cen Zhang (2):
xfrm: cache the offload ifindex for netlink dumps
xfrm: clear mode callbacks after failed mode setup
Chen YanJun (1):
xfrm: iptfs: propagate SKBFL_SHARED_FRAG in iptfs_skb_add_frags()
Petr Wozniak (2):
xfrm: propagate -EINPROGRESS from validate_xmit_xfrm()
xfrm: fix stale skb->prev after async crypto steals a GSO segment
Qianyu Luo (1):
xfrm: nat_keepalive: avoid double free on send error
Xiang Mei (Microsoft) (3):
xfrm: fix sk_dst_cache double-free in xfrm_user_policy()
xfrm6: clear dst.dev on error to avoid double netdev_put in xfrm6_fill_dst()
xfrm: policy: preallocate inexact bins before xfrm_hash_rebuild reinsert
include/net/xfrm.h | 2 ++
net/core/dev.c | 10 ++++++++--
net/ipv6/xfrm6_policy.c | 1 +
net/xfrm/xfrm_device.c | 13 +++++++++++--
net/xfrm/xfrm_iptfs.c | 1 +
net/xfrm/xfrm_nat_keepalive.c | 15 +++++++++------
net/xfrm/xfrm_policy.c | 4 ++--
net/xfrm/xfrm_state.c | 12 +++++++++---
net/xfrm/xfrm_user.c | 41 ++++++++++++++++++++++++++++++-----------
9 files changed, 73 insertions(+), 26 deletions(-)
^ permalink raw reply
* [PATCH 01/10] xfrm: propagate -EINPROGRESS from validate_xmit_xfrm()
From: Steffen Klassert @ 2026-07-10 9:03 UTC (permalink / raw)
To: David Miller, Jakub Kicinski; +Cc: Herbert Xu, Steffen Klassert, netdev
In-Reply-To: <20260710090349.343389-1-steffen.klassert@secunet.com>
From: Petr Wozniak <petr.wozniak@gmail.com>
validate_xmit_xfrm() returns NULL both when a packet is dropped and
when it is stolen by async crypto (-EINPROGRESS from ->xmit()).
Callers cannot distinguish the two cases.
f53c723902d1 ("net: Add asynchronous callbacks for xfrm on layer 2.")
changed the semantics of a NULL return from "dropped" to "stolen or
dropped", but __dev_queue_xmit() was not updated. On virtual/bridge
interfaces (noqueue qdisc) __dev_queue_xmit() initialises rc=-ENOMEM
and jumps to out: when skb is NULL, returning -ENOMEM to the caller
even though the packet will be delivered correctly via xfrm_dev_resume().
Return ERR_PTR(-EINPROGRESS) from validate_xmit_xfrm() for the async
case so callers can tell it apart from a real drop. Update
__dev_queue_xmit() to handle ERR_PTR(-EINPROGRESS) from
validate_xmit_skb() correctly. Update validate_xmit_skb_list() to
use IS_ERR_OR_NULL() so that ERR_PTR(-EINPROGRESS) is not mistakenly
added to the transmitted list.
Fixes: f53c723902d1 ("net: Add asynchronous callbacks for xfrm on layer 2.")
Suggested-by: Sabrina Dubroca <sd@queasysnail.net>
Signed-off-by: Petr Wozniak <petr.wozniak@gmail.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/core/dev.c | 10 ++++++++--
net/xfrm/xfrm_device.c | 4 ++--
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/net/core/dev.c b/net/core/dev.c
index 4b3d5cfdf6e0..5933c5dab09e 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -4018,6 +4018,9 @@ static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb,
return NULL;
}
+/* Returns the skb on success, NULL if dropped, or ERR_PTR(-EINPROGRESS)
+ * if stolen by async xfrm crypto (delivered via xfrm_dev_resume()).
+ */
static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device *dev, bool *again)
{
netdev_features_t features;
@@ -4089,7 +4092,7 @@ struct sk_buff *validate_xmit_skb_list(struct sk_buff *skb, struct net_device *d
skb->prev = skb;
skb = validate_xmit_skb(skb, dev, again);
- if (!skb)
+ if (IS_ERR_OR_NULL(skb))
continue;
if (!head)
@@ -4860,8 +4863,11 @@ int __dev_queue_xmit(struct sk_buff *skb, struct net_device *sb_dev)
goto recursion_alert;
skb = validate_xmit_skb(skb, dev, &again);
- if (!skb)
+ if (IS_ERR_OR_NULL(skb)) {
+ if (PTR_ERR(skb) == -EINPROGRESS)
+ rc = NET_XMIT_SUCCESS;
goto out;
+ }
HARD_TX_LOCK(dev, txq, cpu);
diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c
index 630f3dd31cc5..19c77f09acc9 100644
--- a/net/xfrm/xfrm_device.c
+++ b/net/xfrm/xfrm_device.c
@@ -182,7 +182,7 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
err = x->type_offload->xmit(x, skb, esp_features);
if (err) {
if (err == -EINPROGRESS)
- return NULL;
+ return ERR_PTR(-EINPROGRESS);
XFRM_INC_STATS(xs_net(x), LINUX_MIB_XFRMOUTSTATEPROTOERROR);
kfree_skb(skb);
@@ -224,7 +224,7 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
pskb = skb2;
}
- return skb;
+ return skb ? skb : ERR_PTR(-EINPROGRESS);
}
EXPORT_SYMBOL_GPL(validate_xmit_xfrm);
--
2.43.0
^ permalink raw reply related
* [PATCH RFC net-next 2/2] selftests: seg6: add selftest for End.MAP behavior
From: Yuya Kusakabe @ 2026-07-10 8:58 UTC (permalink / raw)
To: Andrea Mayer, Andrea Mayer, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, David Ahern,
Ido Schimmel, Shuah Khan
Cc: linux-kernel, netdev, linux-kselftest, Yuya Kusakabe
In-Reply-To: <20260710-seg6-mobile-end-map-v1-0-99be02d68143@gmail.com>
Add tools/testing/selftests/net/srv6_end_map_test.sh covering the
End.MAP behavior over a three-node topology (the script header
documents the address plan). Five cases are exercised: SRH-absent,
SRH-present (H.Encaps) and SRH-inline (H.Insert) forwarding verified
by ICMPv6 echo with Icmp6InCsumErrors asserted to stay zero, a
malformed routing header verified to be dropped via the route's own
errors counter, and a Hop Limit expiring at the End.MAP node answered
with an ICMPv6 Time Exceeded. The original and replacement addresses
are chosen with different 16-bit word sums so a transport-checksum
regression cannot cancel out. The malformed packet is crafted by a
small C helper (srv6_mobile_send) added to TEST_GEN_FILES; the test
SKIPs when iproute2 lacks the seg6mobile keyword.
Signed-off-by: Yuya Kusakabe <yuya.kusakabe@gmail.com>
---
tools/testing/selftests/net/Makefile | 2 +
tools/testing/selftests/net/srv6_end_map_test.sh | 360 +++++++++++++++++++++++
tools/testing/selftests/net/srv6_mobile_send.c | 139 +++++++++
3 files changed, 501 insertions(+)
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 708d960ae07d..9acdd4187762 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -91,6 +91,7 @@ TEST_PROGS := \
srv6_end_dx4_netfilter_test.sh \
srv6_end_dx6_netfilter_test.sh \
srv6_end_flavors_test.sh \
+ srv6_end_map_test.sh \
srv6_end_next_csid_l3vpn_test.sh \
srv6_end_x_next_csid_l3vpn_test.sh \
srv6_hencap_red_l3vpn_test.sh \
@@ -161,6 +162,7 @@ TEST_GEN_FILES := \
so_netns_cookie \
so_rcv_listener \
socket \
+ srv6_mobile_send \
stress_reuseport_listen \
tcp_fastopen_backup_key \
tcp_inq \
diff --git a/tools/testing/selftests/net/srv6_end_map_test.sh b/tools/testing/selftests/net/srv6_end_map_test.sh
new file mode 100755
index 000000000000..2a45997b6728
--- /dev/null
+++ b/tools/testing/selftests/net/srv6_end_map_test.sh
@@ -0,0 +1,360 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Selftest for the SRv6 End.MAP behavior (RFC 9433).
+#
+# +------+ 2001:db8:1::/64 +------+ 2001:db8:2::/64 +------+
+# | rt-1 | --------------------- | rt-2 | --------------------- | rt-3 |
+# +------+ veth1 +------+ veth2 +------+
+# (End.MAP)
+#
+# rt-2 holds the End.MAP route for 2001:db8:f::/64 that replaces the
+# IPv6 destination with 2001:db8:3::3 (an address on rt-3's loopback,
+# also used as the final SRv6 segment in the SRH-present scenarios).
+#
+# The original destination 2001:db8:f::1 and the replacement
+# 2001:db8:3::3 have different 16-bit word sums, so any regression in
+# the transport-checksum diff update would corrupt the ICMPv6
+# checksum and bump Icmp6InCsumErrors -- this test asserts that the
+# counter stays at zero across the run.
+#
+# Five cases are exercised:
+#
+# 1. SRH absent -- plain ICMPv6 echo to the End.MAP SID.
+# 2. SRH present -- the destination is reached through an
+# H.Encaps wrapper that carries an SRH with
+# two segments; End.MAP must leave the SRH
+# structurally intact.
+# 3. SRH inline -- the destination is reached through an
+# H.Insert wrapper that inserts an SRH whose
+# first hop is the End.MAP SID; End.MAP must
+# NOT patch the L4 checksum, because the
+# receiver's SRv6 processing restores the
+# destination from segments[0] before the
+# ICMPv6 handler verifies it.
+# 4. SRH malformed -- a C helper sends a packet whose Routing
+# Header type is not 4; End.MAP must drop it.
+# The behavior's own errors counter binds the
+# assertion to the drop.
+# 5. Hop Limit -- an echo whose Hop Limit is 1 on arrival at
+# the End.MAP node must yield an ICMPv6 Time
+# Exceeded from that node, confirming Hop Limit
+# handling is delegated to the ip6_forward path.
+
+source lib.sh
+
+readonly PING_TIMEOUT_SEC=4
+readonly END_MAP_PREFIX="2001:db8:f::/64"
+readonly END_MAP_SID="2001:db8:f::1"
+readonly RT3_SID="2001:db8:3::3"
+HELPER_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
+readonly HELPER_DIR
+readonly HELPER="${HELPER_DIR}/srv6_mobile_send"
+
+ret=0
+nsuccess=0
+nfail=0
+
+PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
+
+log_test()
+{
+ local rc=$1
+ local expected=$2
+ local msg="$3"
+
+ if [ "${rc}" -eq "${expected}" ]; then
+ nsuccess=$((nsuccess + 1))
+ printf "\n TEST: %-60s [ OK ]\n" "${msg}"
+ else
+ ret=1
+ nfail=$((nfail + 1))
+ printf "\n TEST: %-60s [FAIL]\n" "${msg}"
+ if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
+ echo
+ echo "hit enter to continue, 'q' to quit"
+ read -r a
+ [ "$a" = "q" ] && exit 1
+ fi
+ fi
+}
+
+print_log_test_results()
+{
+ printf "\nTests passed: %3d\n" "${nsuccess}"
+ printf "Tests failed: %3d\n" "${nfail}"
+}
+
+cleanup()
+{
+ cleanup_all_ns
+}
+
+trap cleanup EXIT
+
+check_dependencies()
+{
+ if [ "$(id -u)" -ne 0 ]; then
+ echo "SKIP: need root privileges"
+ exit "${ksft_skip}"
+ fi
+
+ for cmd in ip ping; do
+ if ! command -v "$cmd" >/dev/null; then
+ echo "SKIP: ${cmd} is required"
+ exit "${ksft_skip}"
+ fi
+ done
+
+ if [ ! -x "${HELPER}" ]; then
+ echo "SKIP: ${HELPER} not built"
+ exit "${ksft_skip}"
+ fi
+
+ if ! ip route help 2>&1 | grep -qF "seg6mobile"; then
+ echo "SKIP: iproute2 lacks seg6mobile support"
+ exit "${ksft_skip}"
+ fi
+
+ if ! ip route help 2>&1 | grep -qF "End.MAP"; then
+ echo "SKIP: iproute2 lacks End.MAP action"
+ exit "${ksft_skip}"
+ fi
+}
+
+setup()
+{
+ setup_ns rt1 rt2 rt3
+
+ ip -n "$rt1" link set lo up
+ ip -n "$rt2" link set lo up
+ ip -n "$rt3" link set lo up
+
+ ip link add veth1 netns "$rt1" \
+ type veth peer name veth1-rt2 netns "$rt2"
+ ip link add veth2 netns "$rt2" \
+ type veth peer name veth2-rt3 netns "$rt3"
+
+ ip -n "$rt1" addr add 2001:db8:1::1/64 dev veth1 nodad
+ ip -n "$rt2" addr add 2001:db8:1::2/64 dev veth1-rt2 nodad
+ ip -n "$rt2" addr add 2001:db8:2::1/64 dev veth2 nodad
+ ip -n "$rt3" addr add 2001:db8:2::2/64 dev veth2-rt3 nodad
+ # rt-3 also owns the End.MAP replacement SID / SRH endpoint.
+ ip -n "$rt3" addr add "$RT3_SID/128" dev lo nodad
+
+ ip -n "$rt1" link set veth1 up
+ ip -n "$rt2" link set veth1-rt2 up
+ ip -n "$rt2" link set veth2 up
+ ip -n "$rt3" link set veth2-rt3 up
+
+ ip netns exec "$rt2" sysctl -wq net.ipv6.conf.all.forwarding=1
+
+ # rt-3 must accept SRv6 packets so ipv6_srh_rcv lets the
+ # extension header chain through to local delivery.
+ ip netns exec "$rt3" sysctl -wq net.ipv6.conf.all.seg6_enabled=1
+ ip netns exec "$rt3" \
+ sysctl -wq net.ipv6.conf.veth2-rt3.seg6_enabled=1
+ ip netns exec "$rt3" sysctl -wq net.ipv6.conf.lo.seg6_enabled=1
+
+ # Disable HW checksum offload so the kernel software checksum
+ # path runs unconditionally and any csum bug surfaces.
+ ip netns exec "$rt1" ethtool -K veth1 tx off rx off 2>/dev/null
+ ip netns exec "$rt2" ethtool -K veth1-rt2 tx off rx off \
+ 2>/dev/null
+ ip netns exec "$rt2" ethtool -K veth2 tx off rx off 2>/dev/null
+ ip netns exec "$rt3" ethtool -K veth2-rt3 tx off rx off \
+ 2>/dev/null
+
+ # rt-1: route the End.MAP locator into rt-2.
+ ip -n "$rt1" -6 route add "$END_MAP_PREFIX" via 2001:db8:1::2
+
+ # rt-1: a separate H.Encaps route for the SRH-present scenario,
+ # wrapping the inner ICMPv6 echo in an outer IPv6+SRH carrying
+ # [End.MAP_SID, RT3_SID].
+ ip -n "$rt1" -6 route add "$RT3_SID/128" via 2001:db8:1::2 \
+ encap seg6 mode encap \
+ segs "$END_MAP_SID","$RT3_SID" \
+ dev veth1
+
+ # rt-2: End.MAP -- swap DA from the End.MAP SID to RT3_SID
+ # (an address on rt-3) and forward via the IPv6 FIB. "count"
+ # enables the per-behavior counters the malformed-SRH test reads.
+ ip -n "$rt2" -6 route add "$END_MAP_PREFIX" \
+ encap seg6mobile action End.MAP nh6 "$RT3_SID" count \
+ dev veth2
+
+ # rt-2: reach RT3_SID (on rt-3's loopback) through the
+ # directly connected neighbour 2001:db8:2::2.
+ ip -n "$rt2" -6 route add "$RT3_SID/128" via 2001:db8:2::2
+
+ # rt-3: return route for the ICMPv6 echo reply.
+ ip -n "$rt3" -6 route add 2001:db8:1::/64 via 2001:db8:2::1
+}
+
+read_nstat_counter()
+{
+ local ns=$1
+ local name=$2
+
+ # nstat -az reports a counter that has never incremented as 0,
+ # which is what we rely on for a clean before/after delta.
+ ip netns exec "$ns" nstat -az "$name" \
+ | awk -v n="$name" '$1 == n {print $2}'
+}
+
+read_route_errors()
+{
+ # The End.MAP route carries "count", so its errors counter
+ # increments once for every packet the behavior drops. Reading it
+ # binds the negative test to the drop itself rather than to any
+ # unrelated loss on the path to rt-3.
+ ip -n "$rt2" -j -s -6 route show "$END_MAP_PREFIX" \
+ | grep -oE '"errors":[0-9]+' | grep -oE '[0-9]+'
+}
+
+# Test 1: SRH absent.
+test_srh_absent()
+{
+ local before after rc=0
+
+ before=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+
+ if ! ip netns exec "$rt1" \
+ ping -6 -c 1 -W "$PING_TIMEOUT_SEC" "$END_MAP_SID" \
+ >/dev/null 2>&1; then
+ rc=1
+ fi
+
+ if [ "$rc" -eq 0 ]; then
+ after=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+ [ "$before" != "$after" ] && rc=1
+ fi
+
+ log_test "$rc" 0 "End.MAP forwards an ICMPv6 echo without an SRH"
+}
+
+# Test 2: SRH present (H.Encaps from rt-1).
+#
+# The packet rt-1 emits carries an SRH whose only intermediate
+# segment is the End.MAP SID; the final segment is RT3_SID. End.MAP
+# rewrites the outer destination from END_MAP_SID to RT3_SID
+# without touching the SRH, so on rt-3 the SRH processing advances
+# to segments[0] = RT3_SID (local) and the encapsulated inner
+# packet is decapsulated and replied to. A regression that mangles
+# the SRH (e.g. flipping bits in segments_left) would break the chain
+# and the ping would not return.
+test_srh_present()
+{
+ local before after rc=0
+
+ before=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+
+ if ! ip netns exec "$rt1" \
+ ping -6 -c 1 -W "$PING_TIMEOUT_SEC" "$RT3_SID" \
+ >/dev/null 2>&1; then
+ rc=1
+ fi
+
+ if [ "$rc" -eq 0 ]; then
+ after=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+ [ "$before" != "$after" ] && rc=1
+ fi
+
+ log_test "$rc" 0 "End.MAP preserves an SRH carried by H.Encaps"
+}
+
+# Test 3: SRH inserted in-place by H.Insert (mode inline).
+#
+# rt-1 inserts an SRH between the IPv6 header and the L4 payload;
+# segments[0] is the original DA (2001:db8:2::2), segments[1] is the
+# End.MAP SID. End.MAP rewrites the outer DA to RT3_SID but must
+# NOT patch the L4 checksum, because the kernel's standard SRv6
+# processing on rt-3 will decrement Segments Left to 0 and restore
+# the destination to segments[0] before the ICMPv6 handler verifies
+# the checksum against the original pseudo-header. Over-patching the
+# checksum would corrupt it and bump Icmp6InCsumErrors.
+test_srh_inline()
+{
+ local before after rc=0
+
+ ip -n "$rt1" -6 route add 2001:db8:2::2/128 via 2001:db8:1::2 \
+ encap seg6 mode inline segs "$END_MAP_SID" \
+ dev veth1
+
+ before=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+
+ if ! ip netns exec "$rt1" \
+ ping -6 -c 1 -W "$PING_TIMEOUT_SEC" 2001:db8:2::2 \
+ >/dev/null 2>&1; then
+ rc=1
+ fi
+
+ if [ "$rc" -eq 0 ]; then
+ after=$(read_nstat_counter "$rt3" Icmp6InCsumErrors)
+ [ "$before" != "$after" ] && rc=1
+ fi
+
+ log_test "$rc" 0 "End.MAP preserves L4 csum across mode inline SRH"
+}
+
+# Test 4: SRH malformed (negative test).
+#
+# A C helper crafts an IPv6 packet whose Routing Header type is 0
+# rather than 4 (SRH), then sends it from rt-1 toward the End.MAP
+# SID. End.MAP's seg6_mobile_get_and_validate_srh() must return
+# MALFORMED and the handler must drop the packet. The assertion reads
+# the behavior's own errors counter, so it is satisfied only by the
+# End.MAP drop and not by any unrelated loss.
+test_srh_malformed()
+{
+ local before after rc=0
+
+ before=$(read_route_errors)
+
+ ip netns exec "$rt1" "$HELPER" 2001:db8:1::1 "$END_MAP_SID" \
+ >/dev/null 2>&1
+
+ after=$(read_route_errors)
+ [ "$((after - before))" -eq 1 ] || rc=1
+
+ log_test "$rc" 0 "End.MAP drops a packet carrying a malformed SRH"
+}
+
+# Test 5: Hop Limit expiry.
+#
+# End.MAP delegates the Hop Limit check and decrement to the ip6_forward
+# path. An echo whose Hop Limit is 1 when it reaches rt-2 must elicit
+# an ICMPv6 Time Exceeded from rt-2 instead of being forwarded, so
+# rt-2's Icmp6OutTimeExcds increments by exactly one.
+test_hoplimit_expiry()
+{
+ local before after rc=0
+
+ before=$(read_nstat_counter "$rt2" Icmp6OutTimeExcds)
+
+ ip netns exec "$rt1" \
+ ping -6 -c 1 -t 1 -W "$PING_TIMEOUT_SEC" "$END_MAP_SID" \
+ >/dev/null 2>&1
+
+ after=$(read_nstat_counter "$rt2" Icmp6OutTimeExcds)
+ [ "$((after - before))" -eq 1 ] || rc=1
+
+ log_test "$rc" 0 "End.MAP delegates Hop Limit expiry to ip6_forward"
+}
+
+main()
+{
+ check_dependencies
+ setup
+
+ test_srh_absent
+ test_srh_present
+ test_srh_inline
+ test_srh_malformed
+ test_hoplimit_expiry
+
+ print_log_test_results
+ exit "${ret}"
+}
+
+main "$@"
diff --git a/tools/testing/selftests/net/srv6_mobile_send.c b/tools/testing/selftests/net/srv6_mobile_send.c
new file mode 100644
index 000000000000..37d75df3704b
--- /dev/null
+++ b/tools/testing/selftests/net/srv6_mobile_send.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Helper for SRv6 Mobile (RFC 9433) selftests.
+ *
+ * Usage: srv6_mobile_send <src-addr> <dst-addr>
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* RFC 8200 Routing header common fields are 4 bytes; an additional
+ * 4 bytes of type-specific data follow (the Reserved field for the
+ * deprecated type 0, or first_segment/flags/tag for SRH type 4). The
+ * segment list then runs in 16-byte units, giving a total of 24 bytes
+ * for one segment -- which is what ip6r_len = 2 advertises.
+ */
+struct srh {
+ struct ip6_rthdr rthdr;
+ uint32_t type_data;
+ struct in6_addr segments[];
+};
+
+#define SRH_ONE_SEG_LEN (sizeof(struct srh) + sizeof(struct in6_addr))
+
+static uint16_t csum_fold(uint32_t sum)
+{
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+ return ~sum;
+}
+
+static uint32_t csum_partial(const void *buf, size_t len, uint32_t sum)
+{
+ const uint16_t *p = buf;
+
+ while (len > 1) {
+ sum += *p++;
+ len -= 2;
+ }
+ if (len)
+ sum += *(const uint8_t *)p;
+ return sum;
+}
+
+static uint16_t icmpv6_checksum(const struct in6_addr *src,
+ const struct in6_addr *dst,
+ const void *payload, size_t len)
+{
+ uint32_t nexthdr = htonl(IPPROTO_ICMPV6);
+ uint32_t plen = htonl(len);
+ uint32_t sum;
+
+ sum = csum_partial(src, sizeof(*src), 0);
+ sum = csum_partial(dst, sizeof(*dst), sum);
+ sum = csum_partial(&plen, sizeof(plen), sum);
+ sum = csum_partial(&nexthdr, sizeof(nexthdr), sum);
+ sum = csum_partial(payload, len, sum);
+ return csum_fold(sum);
+}
+
+int main(int argc, char **argv)
+{
+ uint8_t frame[sizeof(struct ip6_hdr) + SRH_ONE_SEG_LEN +
+ sizeof(struct icmp6_hdr)];
+ struct sockaddr_in6 dst_addr = {};
+ struct icmp6_hdr *icmp6;
+ struct ip6_hdr *ip6;
+ struct srh *srh;
+ ssize_t res;
+ int fd;
+
+ if (argc != 3) {
+ fprintf(stderr, "usage: %s <src-addr> <dst-addr>\n", argv[0]);
+ return 1;
+ }
+
+ memset(frame, 0, sizeof(frame));
+ ip6 = (struct ip6_hdr *)frame;
+ srh = (struct srh *)(frame + sizeof(*ip6));
+ icmp6 = (struct icmp6_hdr *)(frame + sizeof(*ip6) + SRH_ONE_SEG_LEN);
+
+ ip6->ip6_flow = htonl(6u << 28);
+ ip6->ip6_plen = htons(SRH_ONE_SEG_LEN + sizeof(*icmp6));
+ ip6->ip6_nxt = IPPROTO_ROUTING;
+ ip6->ip6_hops = 64;
+ if (inet_pton(AF_INET6, argv[1], &ip6->ip6_src) != 1) {
+ fprintf(stderr, "invalid src %s\n", argv[1]);
+ return 1;
+ }
+ if (inet_pton(AF_INET6, argv[2], &ip6->ip6_dst) != 1) {
+ fprintf(stderr, "invalid dst %s\n", argv[2]);
+ return 1;
+ }
+
+ srh->rthdr.ip6r_nxt = IPPROTO_ICMPV6;
+ srh->rthdr.ip6r_len = 2; /* (1 + ip6r_len) * 8 = 24 */
+ srh->rthdr.ip6r_type = 0; /* RFC 8754: SRH is type 4 */
+ srh->rthdr.ip6r_segleft = 0;
+ srh->segments[0] = ip6->ip6_dst;
+
+ icmp6->icmp6_type = ICMP6_ECHO_REQUEST;
+ icmp6->icmp6_code = 0;
+ icmp6->icmp6_cksum = 0;
+ icmp6->icmp6_dataun.icmp6_un_data16[0] = htons(0x1234);
+ icmp6->icmp6_dataun.icmp6_un_data16[1] = htons(1);
+ icmp6->icmp6_cksum =
+ icmpv6_checksum(&ip6->ip6_src, &ip6->ip6_dst,
+ icmp6, sizeof(*icmp6));
+
+ fd = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW);
+ if (fd < 0) {
+ perror("socket");
+ return 1;
+ }
+
+ dst_addr.sin6_family = AF_INET6;
+ dst_addr.sin6_addr = ip6->ip6_dst;
+
+ res = sendto(fd, frame, sizeof(frame), 0,
+ (struct sockaddr *)&dst_addr, sizeof(dst_addr));
+ if (res != (ssize_t)sizeof(frame)) {
+ perror("sendto");
+ close(fd);
+ return 1;
+ }
+
+ close(fd);
+ return 0;
+}
--
2.50.1
^ permalink raw reply related
* [PATCH RFC net-next 1/2] seg6: add support for the SRv6 End.MAP behavior
From: Yuya Kusakabe @ 2026-07-10 8:58 UTC (permalink / raw)
To: Andrea Mayer, Andrea Mayer, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, David Ahern,
Ido Schimmel, Shuah Khan
Cc: linux-kernel, netdev, linux-kselftest, Yuya Kusakabe
In-Reply-To: <20260710-seg6-mobile-end-map-v1-0-99be02d68143@gmail.com>
SRv6 End.MAP is defined in RFC 9433 [1].
The SRv6 End.MAP is an SRv6 endpoint that replaces the IPv6
destination address with a configured next SID and forwards the
packet via the IPv6 FIB without consuming the SRH.
The SRv6 End.MAP Linux implementation is the first behavior of the
SRv6 Mobile User Plane and introduces a dedicated
LWTUNNEL_ENCAP_SEG6_MOBILE encap type, a CONFIG_IPV6_SEG6_MOBILE
build option and a net/ipv6/seg6_mobile.c file that hosts the
action dispatch table. The user-space ABI lives in
include/uapi/linux/seg6_mobile.h under a SEG6_MOBILE_* namespace,
kept separate from SEG6_LOCAL_* so that attributes whose semantics
differ between behaviors do not overload the same UAPI table.
The SRv6 End.MAP behavior can be instantiated using a command
similar to the following:
$ ip -6 route add 2001:db8:f::/64 encap seg6mobile action End.MAP \
nh6 2001:db8:2::e dev eth0
We introduce the "seg6mobile" extension in iproute2 in a following
patch.
[1] https://www.rfc-editor.org/rfc/rfc9433.html
Signed-off-by: Yuya Kusakabe <yuya.kusakabe@gmail.com>
---
include/net/seg6.h | 8 +
include/uapi/linux/lwtunnel.h | 1 +
include/uapi/linux/seg6_mobile.h | 58 ++++
net/core/lwtunnel.c | 2 +
net/ipv6/Kconfig | 12 +
net/ipv6/Makefile | 1 +
net/ipv6/seg6.c | 7 +
net/ipv6/seg6_mobile.c | 725 +++++++++++++++++++++++++++++++++++++++
8 files changed, 814 insertions(+)
diff --git a/include/net/seg6.h b/include/net/seg6.h
index 82b3fbbcbb93..789e9bcc4773 100644
--- a/include/net/seg6.h
+++ b/include/net/seg6.h
@@ -64,6 +64,14 @@ static inline int seg6_local_init(void) { return 0; }
static inline void seg6_local_exit(void) {}
#endif
+#ifdef CONFIG_IPV6_SEG6_MOBILE
+extern int seg6_mobile_init(void);
+extern void seg6_mobile_exit(void);
+#else
+static inline int seg6_mobile_init(void) { return 0; }
+static inline void seg6_mobile_exit(void) {}
+#endif
+
extern bool seg6_validate_srh(struct ipv6_sr_hdr *srh, int len, bool reduced);
extern struct ipv6_sr_hdr *seg6_get_srh(struct sk_buff *skb, int flags);
extern void seg6_icmp_srh(struct sk_buff *skb, struct inet6_skb_parm *opt);
diff --git a/include/uapi/linux/lwtunnel.h b/include/uapi/linux/lwtunnel.h
index 229655ef792f..6e48f79c548e 100644
--- a/include/uapi/linux/lwtunnel.h
+++ b/include/uapi/linux/lwtunnel.h
@@ -16,6 +16,7 @@ enum lwtunnel_encap_types {
LWTUNNEL_ENCAP_RPL,
LWTUNNEL_ENCAP_IOAM6,
LWTUNNEL_ENCAP_XFRM,
+ LWTUNNEL_ENCAP_SEG6_MOBILE,
__LWTUNNEL_ENCAP_MAX,
};
diff --git a/include/uapi/linux/seg6_mobile.h b/include/uapi/linux/seg6_mobile.h
new file mode 100644
index 000000000000..bb6fd5189138
--- /dev/null
+++ b/include/uapi/linux/seg6_mobile.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * SRv6 Mobile User Plane implementation
+ */
+#ifndef _UAPI_LINUX_SEG6_MOBILE_H
+#define _UAPI_LINUX_SEG6_MOBILE_H
+
+enum {
+ SEG6_MOBILE_UNSPEC,
+ SEG6_MOBILE_ACTION,
+ SEG6_MOBILE_NH6,
+ SEG6_MOBILE_COUNTERS,
+ __SEG6_MOBILE_MAX,
+};
+
+#define SEG6_MOBILE_MAX (__SEG6_MOBILE_MAX - 1)
+
+enum {
+ SEG6_MOBILE_ACTION_UNSPEC = 0,
+ /* swap IPv6 DA with the next SID, leave SRH untouched */
+ SEG6_MOBILE_ACTION_END_MAP = 1,
+
+ __SEG6_MOBILE_ACTION_MAX,
+};
+
+#define SEG6_MOBILE_ACTION_MAX (__SEG6_MOBILE_ACTION_MAX - 1)
+
+/* SRv6 Mobile Behavior counters are encoded as netlink attributes
+ * guaranteeing the correct alignment.
+ * Each counter is identified by a different attribute type (i.e.
+ * SEG6_MOBILE_CNT_PACKETS).
+ *
+ * - SEG6_MOBILE_CNT_PACKETS: identifies a counter that counts the number
+ * of packets that have been CORRECTLY processed by an SRv6 Behavior
+ * instance (i.e., packets that generate errors or are dropped are NOT
+ * counted).
+ *
+ * - SEG6_MOBILE_CNT_BYTES: identifies a counter that counts the total
+ * amount of traffic in bytes of all packets that have been CORRECTLY
+ * processed by an SRv6 Behavior instance (i.e., packets that generate
+ * errors or are dropped are NOT counted).
+ *
+ * - SEG6_MOBILE_CNT_ERRORS: identifies a counter that counts the number
+ * of packets that have NOT been properly processed by an SRv6 Behavior
+ * instance (i.e., packets that generate errors or are dropped).
+ */
+enum {
+ SEG6_MOBILE_CNT_UNSPEC,
+ SEG6_MOBILE_CNT_PACKETS,
+ SEG6_MOBILE_CNT_BYTES,
+ SEG6_MOBILE_CNT_ERRORS,
+ SEG6_MOBILE_CNT_PAD, /* pad for 64 bits values */
+ __SEG6_MOBILE_CNT_MAX,
+};
+
+#define SEG6_MOBILE_CNT_MAX (__SEG6_MOBILE_CNT_MAX - 1)
+
+#endif /* _UAPI_LINUX_SEG6_MOBILE_H */
diff --git a/net/core/lwtunnel.c b/net/core/lwtunnel.c
index b01a395d9a96..4476293ccb37 100644
--- a/net/core/lwtunnel.c
+++ b/net/core/lwtunnel.c
@@ -53,6 +53,8 @@ static const char *lwtunnel_encap_str(enum lwtunnel_encap_types encap_type)
case LWTUNNEL_ENCAP_XFRM:
/* module autoload not supported for encap type */
return NULL;
+ case LWTUNNEL_ENCAP_SEG6_MOBILE:
+ return "SEG6MOBILE";
case LWTUNNEL_ENCAP_IP6:
case LWTUNNEL_ENCAP_IP:
case LWTUNNEL_ENCAP_NONE:
diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig
index c3806c6ac96f..ba984c8b8fc7 100644
--- a/net/ipv6/Kconfig
+++ b/net/ipv6/Kconfig
@@ -314,6 +314,18 @@ config IPV6_SEG6_BPF
depends on IPV6_SEG6_LWTUNNEL
depends on IPV6 = y
+config IPV6_SEG6_MOBILE
+ bool "IPv6: SRv6 Mobile User Plane (RFC 9433) behaviors"
+ depends on IPV6_SEG6_LWTUNNEL
+ help
+ Support for the SRv6 Mobile User Plane behaviors defined by
+ RFC 9433. These behaviors translate between SRv6 and GTP-U,
+ or operate on SRv6 packets carrying mobile-user-plane traffic,
+ and are exposed via the LWTUNNEL_ENCAP_SEG6_MOBILE lightweight
+ tunnel encapsulation.
+
+ If unsure, say N.
+
config IPV6_RPL_LWTUNNEL
bool "IPv6: RPL Source Routing Header support"
depends on IPV6
diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile
index 5b0cd6488021..515ac8d9d120 100644
--- a/net/ipv6/Makefile
+++ b/net/ipv6/Makefile
@@ -24,6 +24,7 @@ ipv6-$(CONFIG_SYN_COOKIES) += syncookies.o
ipv6-$(CONFIG_NETLABEL) += calipso.o
ipv6-$(CONFIG_IPV6_SEG6_LWTUNNEL) += seg6_iptunnel.o seg6_local.o
ipv6-$(CONFIG_IPV6_SEG6_HMAC) += seg6_hmac.o
+ipv6-$(CONFIG_IPV6_SEG6_MOBILE) += seg6_mobile.o
ipv6-$(CONFIG_IPV6_RPL_LWTUNNEL) += rpl_iptunnel.o
ipv6-$(CONFIG_IPV6_IOAM6_LWTUNNEL) += ioam6_iptunnel.o
diff --git a/net/ipv6/seg6.c b/net/ipv6/seg6.c
index 62a7eb779202..14626b15abd5 100644
--- a/net/ipv6/seg6.c
+++ b/net/ipv6/seg6.c
@@ -525,10 +525,16 @@ int __init seg6_init(void)
if (err)
goto out_unregister_iptun;
+ err = seg6_mobile_init();
+ if (err)
+ goto out_unregister_local;
+
pr_info("Segment Routing with IPv6\n");
out:
return err;
+out_unregister_local:
+ seg6_local_exit();
out_unregister_iptun:
seg6_iptunnel_exit();
out_unregister_genl:
@@ -540,6 +546,7 @@ int __init seg6_init(void)
void seg6_exit(void)
{
+ seg6_mobile_exit();
seg6_local_exit();
seg6_iptunnel_exit();
genl_unregister_family(&seg6_genl_family);
diff --git a/net/ipv6/seg6_mobile.c b/net/ipv6/seg6_mobile.c
new file mode 100644
index 000000000000..b2263c778b00
--- /dev/null
+++ b/net/ipv6/seg6_mobile.c
@@ -0,0 +1,725 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SRv6 Mobile User Plane implementation
+ *
+ * Author:
+ * Yuya Kusakabe <yuya.kusakabe@gmail.com>
+ */
+
+#include <linux/icmpv6.h>
+#include <linux/in6.h>
+#include <linux/ipv6.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <net/checksum.h>
+#include <net/ipv6.h>
+#include <net/lwtunnel.h>
+#include <net/seg6.h>
+#ifdef CONFIG_IPV6_SEG6_HMAC
+#include <net/seg6_hmac.h>
+#endif
+#include <uapi/linux/seg6_mobile.h>
+
+#define SEG6_MOBILE_F_ATTR(i) BIT(i)
+#define SEG6_F_MOBILE_COUNTERS SEG6_MOBILE_F_ATTR(SEG6_MOBILE_COUNTERS)
+
+struct seg6_mobile_lwt;
+
+struct seg6_mobile_action_desc {
+ int action;
+ unsigned long attrs;
+ unsigned long optattrs;
+ int (*input)(struct sk_buff *skb, struct seg6_mobile_lwt *slwt);
+};
+
+struct seg6_mobile_action_param {
+ int (*parse)(struct nlattr **attrs, struct seg6_mobile_lwt *slwt,
+ struct netlink_ext_ack *extack);
+ int (*put)(struct sk_buff *skb, struct seg6_mobile_lwt *slwt);
+ int (*cmp)(struct seg6_mobile_lwt *a, struct seg6_mobile_lwt *b);
+
+ /* optional destroy() callback to release resources acquired in
+ * the corresponding parse() function.
+ */
+ void (*destroy)(struct seg6_mobile_lwt *slwt);
+};
+
+struct pcpu_seg6_mobile_counters {
+ u64_stats_t packets;
+ u64_stats_t bytes;
+ u64_stats_t errors;
+
+ struct u64_stats_sync syncp;
+};
+
+/* User-space aggregate format for the per-CPU counters. Kept private
+ * to the kernel; userspace receives the values through SEG6_MOBILE_CNT_*
+ * nested netlink attributes.
+ */
+struct seg6_mobile_counters {
+ __u64 packets;
+ __u64 bytes;
+ __u64 errors;
+};
+
+#define seg6_mobile_alloc_pcpu_counters(__gfp) \
+ __netdev_alloc_pcpu_stats(struct pcpu_seg6_mobile_counters, \
+ ((__gfp) | __GFP_ZERO))
+
+struct seg6_mobile_lwt {
+ int action;
+ struct in6_addr nh6;
+ const struct seg6_mobile_action_desc *desc;
+ struct pcpu_seg6_mobile_counters __percpu *pcpu_counters;
+
+ /* required attrs are tracked by desc->attrs; optional attrs that
+ * the user actually configured are tracked here so that fill_encap
+ * / cmp / destroy can iterate only over what was parsed.
+ */
+ unsigned long parsed_optattrs;
+};
+
+static struct seg6_mobile_lwt *seg6_mobile_lwtunnel(struct lwtunnel_state *lwt)
+{
+ return (struct seg6_mobile_lwt *)lwt->data;
+}
+
+enum seg6_mobile_srh_state {
+ SEG6_MOBILE_SRH_ABSENT,
+ SEG6_MOBILE_SRH_PRESENT,
+ SEG6_MOBILE_SRH_MALFORMED,
+};
+
+/* Return the SRH if present and valid. @state separates ABSENT from
+ * MALFORMED so End.MAP can forward an SRH-less packet while still
+ * dropping a malformed one.
+ */
+static struct ipv6_sr_hdr *
+seg6_mobile_get_and_validate_srh(struct sk_buff *skb,
+ enum seg6_mobile_srh_state *state)
+{
+ struct ipv6_sr_hdr *srh;
+ unsigned int srhoff = 0;
+ int hdr_proto;
+ int flags = 0;
+
+ srh = seg6_get_srh(skb, 0);
+ if (srh) {
+#ifdef CONFIG_IPV6_SEG6_HMAC
+ if (!seg6_hmac_validate_skb(skb)) {
+ *state = SEG6_MOBILE_SRH_MALFORMED;
+ return NULL;
+ }
+#endif
+ *state = SEG6_MOBILE_SRH_PRESENT;
+ return srh;
+ }
+
+ hdr_proto = ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, &flags);
+ *state = hdr_proto == -ENOENT ? SEG6_MOBILE_SRH_ABSENT
+ : SEG6_MOBILE_SRH_MALFORMED;
+ return NULL;
+}
+
+/* Length of the L4 header that must be made writable so its checksum
+ * field can be patched when the IPv6 DA changes. Returns 0 for L4
+ * protocols whose checksum does not cover the IPv6 pseudo-header.
+ */
+static int seg6_mobile_l4_csum_hlen(u8 nexthdr)
+{
+ switch (nexthdr) {
+ case IPPROTO_TCP:
+ return sizeof(struct tcphdr);
+ case IPPROTO_UDP:
+ return sizeof(struct udphdr);
+ case IPPROTO_ICMPV6:
+ return sizeof(struct icmp6hdr);
+ }
+ return 0;
+}
+
+/* Return a pointer to the L4 checksum field that needs the IPv6 DA
+ * diff applied, or NULL if patching must be skipped. Must be called
+ * after the L4 header has been made writable.
+ */
+static __sum16 *seg6_mobile_l4_csum(struct sk_buff *skb, int l4_off,
+ u8 nexthdr)
+{
+ switch (nexthdr) {
+ case IPPROTO_TCP:
+ return &((struct tcphdr *)(skb->data + l4_off))->check;
+ case IPPROTO_UDP: {
+ struct udphdr *uh = (struct udphdr *)(skb->data + l4_off);
+
+ /* A zero UDPv6 checksum on a fully assembled skb signals
+ * "no checksum" (e.g. tunneled UDP); patching it would
+ * invent a spurious non-zero value.
+ */
+ if (!uh->check && skb->ip_summed != CHECKSUM_PARTIAL)
+ return NULL;
+ return &uh->check;
+ }
+ case IPPROTO_ICMPV6:
+ return &((struct icmp6hdr *)(skb->data + l4_off))->icmp6_cksum;
+ }
+ return NULL;
+}
+
+/* Rewrite the IPv6 destination address with @nh. When @srh_present is
+ * false the packet has no routing header, so the receiver delivers it
+ * straight to the transport: walk any Hop-by-Hop / Destination Options /
+ * Fragment chain to the L4 header and, when that transport uses the IPv6
+ * pseudo-header, patch its checksum by the DA diff. When a routing
+ * header is present the receiver first advances the SID list (SRv6
+ * restores DA to segments[0]) before delivering to L4, so the original
+ * checksum stays valid and only skb->csum needs maintenance for
+ * CHECKSUM_COMPLETE skbs.
+ */
+static int seg6_mobile_advance_da(struct sk_buff *skb,
+ const struct in6_addr *nh, bool srh_present)
+{
+ int l4_off = 0, l4_hlen = 0;
+ struct in6_addr old_da;
+ struct ipv6hdr *ip6h;
+ __be16 frag_off;
+ u8 nexthdr = 0;
+ __sum16 *csum;
+ int write_len;
+
+ if (!pskb_may_pull(skb, sizeof(*ip6h)))
+ return -EINVAL;
+
+ ip6h = ipv6_hdr(skb);
+ write_len = sizeof(*ip6h);
+
+ if (!srh_present) {
+ nexthdr = ip6h->nexthdr;
+ l4_off = ipv6_skip_exthdr(skb, sizeof(*ip6h), &nexthdr,
+ &frag_off);
+ if (l4_off < 0)
+ return -EINVAL;
+
+ /* Non-first fragments carry no L4 header at @l4_off (the
+ * Fragment header reports a non-zero offset); only the
+ * first fragment, which holds the transport header, is
+ * patched.
+ */
+ if (frag_off == 0)
+ l4_hlen = seg6_mobile_l4_csum_hlen(nexthdr);
+ if (l4_hlen)
+ write_len = l4_off + l4_hlen;
+ }
+
+ if (skb_ensure_writable(skb, write_len))
+ return -ENOMEM;
+
+ /* skb_ensure_writable() may change skb pointers; evaluate ip6h again */
+ ip6h = ipv6_hdr(skb);
+ old_da = ip6h->daddr;
+
+ csum = l4_hlen ? seg6_mobile_l4_csum(skb, l4_off, nexthdr) : NULL;
+ if (csum) {
+ inet_proto_csum_replace16(csum, skb, old_da.s6_addr32,
+ nh->s6_addr32, true);
+ /* A real UDPv6 checksum of 0x0000 is illegal, replace it
+ * with 0xffff. inet_proto_csum_replace16() keeps skb->csum
+ * consistent for CHECKSUM_COMPLETE because the IPv6 DA diff
+ * and the L4 csum diff cancel each other.
+ */
+ if (nexthdr == IPPROTO_UDP && !*csum)
+ *csum = CSUM_MANGLED_0;
+ } else if (skb->ip_summed == CHECKSUM_COMPLETE) {
+ update_csum_diff16(skb, old_da.s6_addr32, (__be32 *)nh);
+ }
+
+ ip6h->daddr = *nh;
+ skb_clear_hash(skb);
+
+ return 0;
+}
+
+/* seg6_lookup_nexthop() releases the original dst itself, so no
+ * skb_dst_drop() is needed before the call.
+ */
+static int seg6_mobile_forward(struct sk_buff *skb)
+{
+ seg6_lookup_nexthop(skb, NULL, 0);
+ return dst_input(skb);
+}
+
+static int input_action_end_map(struct sk_buff *skb,
+ struct seg6_mobile_lwt *slwt)
+{
+ enum seg6_mobile_srh_state srh_state;
+
+ seg6_mobile_get_and_validate_srh(skb, &srh_state);
+ if (srh_state == SEG6_MOBILE_SRH_MALFORMED)
+ goto drop;
+
+ if (seg6_mobile_advance_da(skb, &slwt->nh6,
+ srh_state == SEG6_MOBILE_SRH_PRESENT))
+ goto drop;
+
+ return seg6_mobile_forward(skb);
+
+drop:
+ kfree_skb(skb);
+ return -EINVAL;
+}
+
+static int parse_nla_nh6(struct nlattr **attrs, struct seg6_mobile_lwt *slwt,
+ struct netlink_ext_ack *extack)
+{
+ memcpy(&slwt->nh6, nla_data(attrs[SEG6_MOBILE_NH6]),
+ sizeof(struct in6_addr));
+
+ return 0;
+}
+
+static int put_nla_nh6(struct sk_buff *skb, struct seg6_mobile_lwt *slwt)
+{
+ if (nla_put_in6_addr(skb, SEG6_MOBILE_NH6, &slwt->nh6))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int cmp_nla_nh6(struct seg6_mobile_lwt *a, struct seg6_mobile_lwt *b)
+{
+ return memcmp(&a->nh6, &b->nh6, sizeof(struct in6_addr));
+}
+
+static const struct
+nla_policy seg6_mobile_counters_policy[SEG6_MOBILE_CNT_MAX + 1] = {
+ [SEG6_MOBILE_CNT_PACKETS] = { .type = NLA_U64 },
+ [SEG6_MOBILE_CNT_BYTES] = { .type = NLA_U64 },
+ [SEG6_MOBILE_CNT_ERRORS] = { .type = NLA_U64 },
+};
+
+static int parse_nla_counters(struct nlattr **attrs,
+ struct seg6_mobile_lwt *slwt,
+ struct netlink_ext_ack *extack)
+{
+ struct pcpu_seg6_mobile_counters __percpu *pcounters;
+ struct nlattr *tb[SEG6_MOBILE_CNT_MAX + 1];
+ int ret;
+
+ ret = nla_parse_nested_deprecated(tb, SEG6_MOBILE_CNT_MAX,
+ attrs[SEG6_MOBILE_COUNTERS],
+ seg6_mobile_counters_policy, extack);
+ if (ret < 0)
+ return ret;
+
+ /* basic support for SRv6 Behavior counters requires at least:
+ * packets, bytes and errors.
+ */
+ if (!tb[SEG6_MOBILE_CNT_PACKETS] || !tb[SEG6_MOBILE_CNT_BYTES] ||
+ !tb[SEG6_MOBILE_CNT_ERRORS])
+ return -EINVAL;
+
+ /* counters are always zero initialized */
+ pcounters = seg6_mobile_alloc_pcpu_counters(GFP_KERNEL);
+ if (!pcounters)
+ return -ENOMEM;
+
+ slwt->pcpu_counters = pcounters;
+
+ return 0;
+}
+
+static int seg6_mobile_fill_nla_counters(struct sk_buff *skb,
+ struct seg6_mobile_counters *counters)
+{
+ if (nla_put_u64_64bit(skb, SEG6_MOBILE_CNT_PACKETS, counters->packets,
+ SEG6_MOBILE_CNT_PAD))
+ return -EMSGSIZE;
+
+ if (nla_put_u64_64bit(skb, SEG6_MOBILE_CNT_BYTES, counters->bytes,
+ SEG6_MOBILE_CNT_PAD))
+ return -EMSGSIZE;
+
+ if (nla_put_u64_64bit(skb, SEG6_MOBILE_CNT_ERRORS, counters->errors,
+ SEG6_MOBILE_CNT_PAD))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
+static int put_nla_counters(struct sk_buff *skb, struct seg6_mobile_lwt *slwt)
+{
+ struct seg6_mobile_counters counters = { 0, 0, 0 };
+ struct nlattr *nest;
+ int rc, i;
+
+ nest = nla_nest_start(skb, SEG6_MOBILE_COUNTERS);
+ if (!nest)
+ return -EMSGSIZE;
+
+ for_each_possible_cpu(i) {
+ struct pcpu_seg6_mobile_counters *pcounters;
+ u64 packets, bytes, errors;
+ unsigned int start;
+
+ pcounters = per_cpu_ptr(slwt->pcpu_counters, i);
+ do {
+ start = u64_stats_fetch_begin(&pcounters->syncp);
+
+ packets = u64_stats_read(&pcounters->packets);
+ bytes = u64_stats_read(&pcounters->bytes);
+ errors = u64_stats_read(&pcounters->errors);
+
+ } while (u64_stats_fetch_retry(&pcounters->syncp, start));
+
+ counters.packets += packets;
+ counters.bytes += bytes;
+ counters.errors += errors;
+ }
+
+ rc = seg6_mobile_fill_nla_counters(skb, &counters);
+ if (rc < 0) {
+ nla_nest_cancel(skb, nest);
+ return rc;
+ }
+
+ return nla_nest_end(skb, nest);
+}
+
+static int cmp_nla_counters(struct seg6_mobile_lwt *a,
+ struct seg6_mobile_lwt *b)
+{
+ /* tunnels with counters enabled and disabled are different. */
+ return (!!((unsigned long)a->pcpu_counters)) ^
+ (!!((unsigned long)b->pcpu_counters));
+}
+
+static void destroy_attr_counters(struct seg6_mobile_lwt *slwt)
+{
+ free_percpu(slwt->pcpu_counters);
+}
+
+static const struct seg6_mobile_action_desc seg6_mobile_action_table[] = {
+ {
+ .action = SEG6_MOBILE_ACTION_END_MAP,
+ .attrs = SEG6_MOBILE_F_ATTR(SEG6_MOBILE_NH6),
+ .optattrs = SEG6_F_MOBILE_COUNTERS,
+ .input = input_action_end_map,
+ },
+};
+
+static const struct seg6_mobile_action_param
+seg6_mobile_action_params[SEG6_MOBILE_MAX + 1] = {
+ [SEG6_MOBILE_NH6] = {
+ .parse = parse_nla_nh6,
+ .put = put_nla_nh6,
+ .cmp = cmp_nla_nh6,
+ },
+ [SEG6_MOBILE_COUNTERS] = {
+ .parse = parse_nla_counters,
+ .put = put_nla_counters,
+ .cmp = cmp_nla_counters,
+ .destroy = destroy_attr_counters,
+ },
+};
+
+static const struct nla_policy
+seg6_mobile_policy[SEG6_MOBILE_MAX + 1] = {
+ [SEG6_MOBILE_ACTION] = { .type = NLA_U32 },
+ [SEG6_MOBILE_NH6] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)),
+ [SEG6_MOBILE_COUNTERS] = { .type = NLA_NESTED },
+};
+
+static const struct seg6_mobile_action_desc *
+seg6_mobile_get_action_desc(int action)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(seg6_mobile_action_table); i++) {
+ if (seg6_mobile_action_table[i].action == action)
+ return &seg6_mobile_action_table[i];
+ }
+
+ return NULL;
+}
+
+/* call the destroy() callback (if available) for each set attribute in
+ * @parsed_attrs, starting from the first attribute up to the @max_parsed
+ * (excluded) attribute.
+ */
+static void __destroy_attrs(unsigned long parsed_attrs, int max_parsed,
+ struct seg6_mobile_lwt *slwt)
+{
+ const struct seg6_mobile_action_param *param;
+ int i;
+
+ for (i = SEG6_MOBILE_ACTION + 1; i < max_parsed; i++) {
+ if (!(parsed_attrs & SEG6_MOBILE_F_ATTR(i)))
+ continue;
+
+ param = &seg6_mobile_action_params[i];
+ if (param->destroy)
+ param->destroy(slwt);
+ }
+}
+
+static void destroy_attrs(struct seg6_mobile_lwt *slwt)
+{
+ unsigned long attrs = slwt->desc->attrs | slwt->parsed_optattrs;
+
+ __destroy_attrs(attrs, SEG6_MOBILE_MAX + 1, slwt);
+}
+
+static int seg6_mobile_parse_attrs(struct nlattr **attrs,
+ struct seg6_mobile_lwt *slwt,
+ struct netlink_ext_ack *extack)
+{
+ const struct seg6_mobile_action_param *param;
+ const struct seg6_mobile_action_desc *desc;
+ unsigned long parsed_optattrs = 0;
+ int i, err;
+
+ desc = slwt->desc;
+
+ if (WARN_ON_ONCE(desc->attrs & desc->optattrs))
+ return -EINVAL;
+
+ for (i = SEG6_MOBILE_ACTION + 1; i <= SEG6_MOBILE_MAX; i++) {
+ bool required = desc->attrs & SEG6_MOBILE_F_ATTR(i);
+ bool optional = desc->optattrs & SEG6_MOBILE_F_ATTR(i);
+
+ if (!required && !optional)
+ continue;
+
+ if (required && !attrs[i]) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "missing required attribute");
+ err = -EINVAL;
+ goto err;
+ }
+
+ if (!attrs[i])
+ continue;
+
+ param = &seg6_mobile_action_params[i];
+ err = param->parse(attrs, slwt, extack);
+ if (err < 0)
+ goto err;
+
+ if (optional)
+ parsed_optattrs |= SEG6_MOBILE_F_ATTR(i);
+ }
+
+ slwt->parsed_optattrs = parsed_optattrs;
+
+ return 0;
+
+err:
+ __destroy_attrs(desc->attrs | parsed_optattrs, i, slwt);
+ return err;
+}
+
+static bool seg6_mobile_counters_enabled(struct seg6_mobile_lwt *slwt)
+{
+ return slwt->parsed_optattrs & SEG6_F_MOBILE_COUNTERS;
+}
+
+static void seg6_mobile_update_counters(struct seg6_mobile_lwt *slwt,
+ unsigned int len, int err)
+{
+ struct pcpu_seg6_mobile_counters *pcounters;
+
+ pcounters = this_cpu_ptr(slwt->pcpu_counters);
+ u64_stats_update_begin(&pcounters->syncp);
+
+ if (likely(!err)) {
+ u64_stats_inc(&pcounters->packets);
+ u64_stats_add(&pcounters->bytes, len);
+ } else {
+ u64_stats_inc(&pcounters->errors);
+ }
+
+ u64_stats_update_end(&pcounters->syncp);
+}
+
+static int seg6_mobile_input(struct sk_buff *skb)
+{
+ struct dst_entry *orig_dst = skb_dst(skb);
+ struct seg6_mobile_lwt *slwt;
+ unsigned int len = skb->len;
+ int rc;
+
+ if (skb->protocol != htons(ETH_P_IPV6)) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ slwt = seg6_mobile_lwtunnel(orig_dst->lwtstate);
+
+ rc = slwt->desc->input(skb, slwt);
+
+ if (seg6_mobile_counters_enabled(slwt))
+ seg6_mobile_update_counters(slwt, len, rc);
+
+ return rc;
+}
+
+static int seg6_mobile_build_state(struct net *net, struct nlattr *nla,
+ unsigned int family, const void *cfg,
+ struct lwtunnel_state **ts,
+ struct netlink_ext_ack *extack)
+{
+ const struct seg6_mobile_action_desc *desc;
+ struct nlattr *tb[SEG6_MOBILE_MAX + 1];
+ struct lwtunnel_state *newts;
+ struct seg6_mobile_lwt *slwt;
+ int err;
+
+ if (family != AF_INET6)
+ return -EINVAL;
+
+ err = nla_parse_nested_deprecated(tb, SEG6_MOBILE_MAX, nla,
+ seg6_mobile_policy, extack);
+ if (err < 0)
+ return err;
+
+ if (!tb[SEG6_MOBILE_ACTION]) {
+ NL_SET_ERR_MSG_MOD(extack, "missing SEG6_MOBILE_ACTION");
+ return -EINVAL;
+ }
+
+ desc = seg6_mobile_get_action_desc(nla_get_u32(tb[SEG6_MOBILE_ACTION]));
+ if (!desc) {
+ NL_SET_ERR_MSG_MOD(extack, "unknown SRv6 Mobile action");
+ return -EOPNOTSUPP;
+ }
+
+ newts = lwtunnel_state_alloc(sizeof(*slwt));
+ if (!newts)
+ return -ENOMEM;
+
+ slwt = seg6_mobile_lwtunnel(newts);
+ slwt->action = desc->action;
+ slwt->desc = desc;
+
+ err = seg6_mobile_parse_attrs(tb, slwt, extack);
+ if (err < 0) {
+ kfree(newts);
+ return err;
+ }
+
+ newts->type = LWTUNNEL_ENCAP_SEG6_MOBILE;
+ newts->flags = LWTUNNEL_STATE_INPUT_REDIRECT;
+
+ *ts = newts;
+
+ return 0;
+}
+
+static void seg6_mobile_destroy_state(struct lwtunnel_state *lwt)
+{
+ destroy_attrs(seg6_mobile_lwtunnel(lwt));
+}
+
+static int seg6_mobile_fill_encap(struct sk_buff *skb,
+ struct lwtunnel_state *lwt)
+{
+ struct seg6_mobile_lwt *slwt = seg6_mobile_lwtunnel(lwt);
+ const struct seg6_mobile_action_param *param;
+ unsigned long attrs;
+ int i, err;
+
+ if (nla_put_u32(skb, SEG6_MOBILE_ACTION, slwt->action))
+ return -EMSGSIZE;
+
+ attrs = slwt->desc->attrs | slwt->parsed_optattrs;
+ for (i = SEG6_MOBILE_ACTION + 1; i <= SEG6_MOBILE_MAX; i++) {
+ if (!(attrs & SEG6_MOBILE_F_ATTR(i)))
+ continue;
+
+ param = &seg6_mobile_action_params[i];
+ err = param->put(skb, slwt);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int seg6_mobile_get_encap_size(struct lwtunnel_state *lwt)
+{
+ struct seg6_mobile_lwt *slwt = seg6_mobile_lwtunnel(lwt);
+ unsigned long attrs;
+ int nlsize;
+
+ nlsize = nla_total_size(sizeof(u32)); /* SEG6_MOBILE_ACTION */
+
+ attrs = slwt->desc->attrs | slwt->parsed_optattrs;
+ if (attrs & SEG6_MOBILE_F_ATTR(SEG6_MOBILE_NH6))
+ nlsize += nla_total_size(sizeof(struct in6_addr));
+
+ if (attrs & SEG6_F_MOBILE_COUNTERS)
+ nlsize += nla_total_size(0) + /* nest SEG6_MOBILE_COUNTERS */
+ /* SEG6_MOBILE_CNT_PACKETS */
+ nla_total_size_64bit(sizeof(__u64)) +
+ /* SEG6_MOBILE_CNT_BYTES */
+ nla_total_size_64bit(sizeof(__u64)) +
+ /* SEG6_MOBILE_CNT_ERRORS */
+ nla_total_size_64bit(sizeof(__u64));
+
+ return nlsize;
+}
+
+static int seg6_mobile_cmp_encap(struct lwtunnel_state *a,
+ struct lwtunnel_state *b)
+{
+ struct seg6_mobile_lwt *slwt_a = seg6_mobile_lwtunnel(a);
+ struct seg6_mobile_lwt *slwt_b = seg6_mobile_lwtunnel(b);
+ const struct seg6_mobile_action_param *param;
+ unsigned long attrs_a, attrs_b;
+ int i;
+
+ if (slwt_a->action != slwt_b->action)
+ return 1;
+
+ attrs_a = slwt_a->desc->attrs | slwt_a->parsed_optattrs;
+ attrs_b = slwt_b->desc->attrs | slwt_b->parsed_optattrs;
+
+ if (attrs_a != attrs_b)
+ return 1;
+
+ for (i = SEG6_MOBILE_ACTION + 1; i <= SEG6_MOBILE_MAX; i++) {
+ if (!(attrs_a & SEG6_MOBILE_F_ATTR(i)))
+ continue;
+
+ param = &seg6_mobile_action_params[i];
+ if (param->cmp(slwt_a, slwt_b))
+ return 1;
+ }
+
+ return 0;
+}
+
+static const struct lwtunnel_encap_ops seg6_mobile_ops = {
+ .build_state = seg6_mobile_build_state,
+ .destroy_state = seg6_mobile_destroy_state,
+ .input = seg6_mobile_input,
+ .fill_encap = seg6_mobile_fill_encap,
+ .get_encap_size = seg6_mobile_get_encap_size,
+ .cmp_encap = seg6_mobile_cmp_encap,
+ .owner = THIS_MODULE,
+};
+
+int __init seg6_mobile_init(void)
+{
+ BUILD_BUG_ON(SEG6_MOBILE_MAX + 1 > BITS_PER_TYPE(unsigned long));
+
+ return lwtunnel_encap_add_ops(&seg6_mobile_ops,
+ LWTUNNEL_ENCAP_SEG6_MOBILE);
+}
+
+void seg6_mobile_exit(void)
+{
+ lwtunnel_encap_del_ops(&seg6_mobile_ops, LWTUNNEL_ENCAP_SEG6_MOBILE);
+}
--
2.50.1
^ permalink raw reply related
* [PATCH RFC net-next 0/2] seg6: add support for the SRv6 End.MAP behavior
From: Yuya Kusakabe @ 2026-07-10 8:58 UTC (permalink / raw)
To: Andrea Mayer, Andrea Mayer, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, David Ahern,
Ido Schimmel, Shuah Khan
Cc: linux-kernel, netdev, linux-kselftest, Yuya Kusakabe
End.MAP is the simplest of the SRv6 Mobile User Plane (RFC 9433)
behaviors: it replaces the outer IPv6 destination address with a
configured next SID and forwards via the IPv6 FIB without consuming
the SRH. This series is the first per-behavior rebuild of the SRv6
Mobile User Plane v2 submission [1], split one series per behavior
following upstream review.
Because End.MAP is the first behavior to land, this series also
introduces the SRv6 Mobile User Plane lwtunnel framework that the
subsequent per-behavior series plug into:
* a new LWTUNNEL_ENCAP_SEG6_MOBILE encap type with a
CONFIG_IPV6_SEG6_MOBILE build option,
* net/ipv6/seg6_mobile.c, which hosts the action dispatch table,
* include/uapi/linux/seg6_mobile.h, a dedicated SEG6_MOBILE_*
attribute namespace, so that attributes whose semantics differ
between the mobility behaviors and the RFC 8986 behaviors do
not overload the SEG6_LOCAL_* table.
This layout follows the discussion on the v2 thread. The encap type
and the attribute namespace are UAPI that cannot be reduced once
merged, so feedback on this direction from the lwtunnel side is
particularly welcome. Per the same discussion, netfilter hook
integration, VRF support and SRv6-specific drop reasons are left out
of this initial series and will be revisited separately.
A matching iproute2 series adding the "seg6mobile" encap keyword
follows shortly after this posting, since the selftest needs it to
run.
[1] https://lore.kernel.org/netdev/20260505-seg6-mobile-v2-0-9e8022bdfdb6@gmail.com/
Signed-off-by: Yuya Kusakabe <yuya.kusakabe@gmail.com>
---
Yuya Kusakabe (2):
seg6: add support for the SRv6 End.MAP behavior
selftests: seg6: add selftest for End.MAP behavior
include/net/seg6.h | 8 +
include/uapi/linux/lwtunnel.h | 1 +
include/uapi/linux/seg6_mobile.h | 58 ++
net/core/lwtunnel.c | 2 +
net/ipv6/Kconfig | 12 +
net/ipv6/Makefile | 1 +
net/ipv6/seg6.c | 7 +
net/ipv6/seg6_mobile.c | 725 +++++++++++++++++++++++
tools/testing/selftests/net/Makefile | 2 +
tools/testing/selftests/net/srv6_end_map_test.sh | 360 +++++++++++
tools/testing/selftests/net/srv6_mobile_send.c | 139 +++++
11 files changed, 1315 insertions(+)
---
base-commit: 08030ddb87b4c6c6a2c03c82731b5e188f02f5b9
change-id: 20260523-seg6-mobile-end-map-9cec24191817
Best regards,
--
Yuya Kusakabe <yuya.kusakabe@gmail.com>
^ permalink raw reply
* Re: [PATCH net v2] net: stmmac: intel: gate SerDes reconfig on rate
From: Maxime Chevallier @ 2026-07-10 8:50 UTC (permalink / raw)
To: Markus Breitenberger, netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Choong Yong Liang, Markus Breitenberger, stable
In-Reply-To: <20260709190329.124432-1-bre@breiti.cc>
Hi Markus,
On 7/9/26 21:03, Markus Breitenberger wrote:
> From: Markus Breitenberger <bre@keba.com>
>
> intel_mac_finish() is registered as the phylink mac_finish()
> callback for the Elkhart Lake SGMII ports. phylink calls it at
> the end of every major link reconfiguration, including the
> initial one during probe.
>
> The callback selects the PMC ModPHY LCPLL programming for the
> requested MAC-side interface and then power-cycles the SerDes.
> On Elkhart Lake that ModPHY is also used by the on-die AHCI
> SATA PHY. Reapplying the programming during the initial
> boot-time link-up disturbs the shared analog block while it is
> still driving SATA, so the SATA link fails to train:
>
> ata1: SATA link down (SStatus 1 SControl 300)
>
> The disk carrying the root filesystem is never detected and the
> system hangs at rootwait. Ethernet itself comes up normally,
> which makes the failure look unrelated to the network driver.
>
> Before mac_finish() runs, the legacy SerDes power-up path has
> already programmed SERDES_GCR0 for the current interface. The
> 1G and 2.5G ModPHY tables selected by mac_finish() correspond
> to the SerDes lane rate, so read that rate back from SERDES_GCR0
> and skip the PMC reprogramming and SerDes power-cycle when it
> already matches the selected interface.
>
> This keeps the disruptive reprogramming out of the boot path
> when the SerDes is configured correctly, while preserving the
> previous behavior when a real SGMII/1000BASE-X to 2500BASE-X
> rate change is needed. If the register read fails, reconfigure
> as before.
>
> Fixes: a42f6b3f1cc1 ("net: stmmac: configure SerDes according to the interface mode")
> Cc: stable@vger.kernel.org
> Assisted-by: GitHub-Copilot:claude-opus-4.8
> Signed-off-by: Markus Breitenberger <bre@keba.com>
> ---
> v2:
> - Read the current SerDes lane rate from SERDES_GCR0 instead of
> comparing against cached phy_interface state.
> - Rework the commit message to clarify the SerDes power-up path and
> the rate readback check.
> - Keep the previous reconfiguration behavior if the SERDES_GCR0 read
> fails.
>
> v1: https://lore.kernel.org/netdev/20260706061954.94842-1-bre@breiti.cc/
>
> .../net/ethernet/stmicro/stmmac/dwmac-intel.c | 28 +++++++++++++++++++
> 1 file changed, 28 insertions(+)
>
> diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
> index b8d467ba6d72..fa0113597c97 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
> @@ -525,6 +525,31 @@ static int intel_set_reg_access(const struct pmc_serdes_regs *regs, int max_regs
> return ret;
> }
>
> +/* Return true if the SerDes lane rate must change to serve @interface.
> + * If the current rate cannot be determined, reconfigure as before.
> + */
> +static bool intel_serdes_needs_reconfig(struct stmmac_priv *priv,
> + struct intel_priv_data *intel_priv,
> + phy_interface_t interface)
> +{
> + u32 cur_rate, want_rate;
> + int data;
> +
> + if (!intel_priv->mdio_adhoc_addr)
> + return true;
> +
> + data = mdiobus_read(priv->mii, intel_priv->mdio_adhoc_addr,
> + SERDES_GCR0);
> + if (data < 0)
> + return true;
> +
> + cur_rate = (data & SERDES_RATE_MASK) >> SERDES_RATE_PCIE_SHIFT;
> + want_rate = interface == PHY_INTERFACE_MODE_2500BASEX ?
> + SERDES_RATE_PCIE_GEN2 : SERDES_RATE_PCIE_GEN1;
> +
> + return cur_rate != want_rate;
> +}
> +
> static int intel_mac_finish(struct net_device *ndev,
> void *intel_data,
> unsigned int mode,
> @@ -536,6 +561,9 @@ static int intel_mac_finish(struct net_device *ndev,
> int max_regs = 0;
> int ret = 0;
>
> + if (!intel_serdes_needs_reconfig(priv, intel_priv, interface))
> + return 0;
You're returning a bit too early, make sure that you still update
priv->plat->phy_interface as you may be switching between 1000BaseX and
SGMII, so no rate change needed, but an interface change still :)
Maxime
^ permalink raw reply
* [PATCH v3] macvlan: allow source mode devices along with passthru
From: Thomas Martitz @ 2026-07-10 8:36 UTC (permalink / raw)
To: Simon Horman, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, open list:NETWORKING DRIVERS,
open list
Cc: Thomas Martitz, open list:NETWORKING DRIVERS, open list
In-Reply-To: <20260710083604.396560-1-t.martitz@fritz.com>
This allows for configurations where there are a few
known senders in the system (e.g. multiple SoCs on the same
board) along with unlimited external senders.
The source mode devices represent the known senders while
all external senders terminate on passthru device.
Although you can still receive packets on the lower device
without the need for the passthru vlan device, there
are use cases where you need additional packet processing
in the pipeline that hooks via rx_handler. With this the
rx_handler can be attached to the passthru device while
macvlan itself remains attached to the lower device.
We use this to use the same physical link for inter-SoC
networking and external networking. Some of our chips
have no other viable link for inter-SoC traffic.
Signed-off-by: Thomas Martitz <t.martitz@fritz.com>
---
drivers/net/macvlan.c | 105 ++++++++++++++++++++++++++++--------------
1 file changed, 71 insertions(+), 34 deletions(-)
diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c
index 9a4bc99dbf53b..a2ac913f91906 100644
--- a/drivers/net/macvlan.c
+++ b/drivers/net/macvlan.c
@@ -83,6 +83,11 @@ static inline void macvlan_set_passthru(struct macvlan_port *port)
port->flags |= MACVLAN_F_PASSTHRU;
}
+static inline void macvlan_clear_passthru(struct macvlan_port *port)
+{
+ port->flags &= ~MACVLAN_F_PASSTHRU;
+}
+
static inline bool macvlan_addr_change(const struct macvlan_port *port)
{
return port->flags & MACVLAN_F_ADDRCHANGE;
@@ -637,7 +642,7 @@ static int macvlan_open(struct net_device *dev)
struct net_device *lowerdev = vlan->lowerdev;
int err;
- if (macvlan_passthru(vlan->port)) {
+ if (vlan->mode == MACVLAN_MODE_PASSTHRU) {
if (!(vlan->flags & MACVLAN_FLAG_NOPROMISC)) {
err = dev_set_promiscuity(lowerdev, 1);
if (err < 0)
@@ -712,7 +717,7 @@ static int macvlan_stop(struct net_device *dev)
dev_uc_unsync(lowerdev, dev);
dev_mc_unsync(lowerdev, dev);
- if (macvlan_passthru(vlan->port)) {
+ if (vlan->mode == MACVLAN_MODE_PASSTHRU) {
if (!(vlan->flags & MACVLAN_FLAG_NOPROMISC))
dev_set_promiscuity(lowerdev, -1);
goto hash_del;
@@ -968,6 +973,21 @@ static int macvlan_init(struct net_device *dev)
return 0;
}
+static void macvlan_restore_mac(struct macvlan_port *port)
+{
+ /* If the lower device address has been changed by passthru
+ * macvlan, put it back.
+ */
+ if (macvlan_passthru(port) &&
+ !ether_addr_equal(port->dev->dev_addr, port->perm_addr)) {
+ struct sockaddr_storage ss;
+
+ ss.ss_family = port->dev->type;
+ memcpy(&ss.__data, port->perm_addr, port->dev->addr_len);
+ dev_set_mac_address(port->dev, &ss, NULL);
+ }
+}
+
static void macvlan_uninit(struct net_device *dev)
{
struct macvlan_dev *vlan = netdev_priv(dev);
@@ -977,8 +997,18 @@ static void macvlan_uninit(struct net_device *dev)
macvlan_flush_sources(port, vlan);
port->count -= 1;
- if (!port->count)
- macvlan_port_destroy(port->dev);
+ if (port->count) {
+ /* In case of remaining source interfaces undo
+ * passthru-specific properties.
+ */
+ if (vlan->mode == MACVLAN_MODE_PASSTHRU) {
+ macvlan_restore_mac(port);
+ macvlan_clear_passthru(port);
+ }
+ return;
+ }
+
+ macvlan_port_destroy(port->dev);
}
static void macvlan_dev_get_stats64(struct net_device *dev,
@@ -1052,7 +1082,7 @@ static int macvlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
/* Support unicast filter only on passthru devices.
* Multicast filter should be allowed on all devices.
*/
- if (!macvlan_passthru(vlan->port) && is_unicast_ether_addr(addr))
+ if (vlan->mode != MACVLAN_MODE_PASSTHRU && is_unicast_ether_addr(addr))
return -EOPNOTSUPP;
if (flags & NLM_F_REPLACE)
@@ -1077,7 +1107,7 @@ static int macvlan_fdb_del(struct ndmsg *ndm, struct nlattr *tb[],
/* Support unicast filter only on passthru devices.
* Multicast filter should be allowed on all devices.
*/
- if (!macvlan_passthru(vlan->port) && is_unicast_ether_addr(addr))
+ if (vlan->mode != MACVLAN_MODE_PASSTHRU && is_unicast_ether_addr(addr))
return -EOPNOTSUPP;
if (is_unicast_ether_addr(addr))
@@ -1308,17 +1338,7 @@ static void macvlan_port_destroy(struct net_device *dev)
kfree_skb(skb);
}
- /* If the lower device address has been changed by passthru
- * macvlan, put it back.
- */
- if (macvlan_passthru(port) &&
- !ether_addr_equal(port->dev->dev_addr, port->perm_addr)) {
- struct sockaddr_storage ss;
-
- ss.ss_family = port->dev->type;
- memcpy(&ss.__data, port->perm_addr, port->dev->addr_len);
- dev_set_mac_address(port->dev, &ss, NULL);
- }
+ macvlan_restore_mac(port);
kfree(port);
}
@@ -1506,15 +1526,6 @@ int macvlan_common_newlink(struct net_device *dev,
}
port = macvlan_port_get_rtnl(lowerdev);
- /* Only 1 macvlan device can be created in passthru mode */
- if (macvlan_passthru(port)) {
- /* The macvlan port must be not created this time,
- * still goto destroy_macvlan_port for readability.
- */
- err = -EINVAL;
- goto destroy_macvlan_port;
- }
-
vlan->lowerdev = lowerdev;
vlan->dev = dev;
vlan->port = port;
@@ -1527,10 +1538,30 @@ int macvlan_common_newlink(struct net_device *dev,
if (data && data[IFLA_MACVLAN_FLAGS])
vlan->flags = nla_get_u16(data[IFLA_MACVLAN_FLAGS]);
+ /* Only 1 macvlan device can be created in passthru mode. There may be
+ * additional source mode devices but nothing else at the moment.
+ *
+ * First check if adding a source mode device to an existing passthru vlan.
+ */
+ if (macvlan_passthru(port) && vlan->mode != MACVLAN_MODE_SOURCE) {
+ /* The macvlan port must be not created this time,
+ * still goto destroy_macvlan_port for readability.
+ */
+ err = -EINVAL;
+ goto destroy_macvlan_port;
+ }
+
+ /* Now check if adding a passthru device to an existing set of source mode
+ * devices.
+ */
if (vlan->mode == MACVLAN_MODE_PASSTHRU) {
- if (port->count) {
- err = -EINVAL;
- goto destroy_macvlan_port;
+ struct macvlan_dev *p;
+
+ list_for_each_entry(p, &port->vlans, list) {
+ if (p->mode != MACVLAN_MODE_SOURCE) {
+ err = -EINVAL;
+ goto destroy_macvlan_port;
+ }
}
macvlan_set_passthru(port);
eth_hw_addr_inherit(dev, lowerdev);
@@ -1564,7 +1595,11 @@ int macvlan_common_newlink(struct net_device *dev,
if (err)
goto unregister_netdev;
- list_add_tail_rcu(&vlan->list, &port->vlans);
+ /* macvlan_handle_frame expects the (one and only) passthru device first. */
+ if (vlan->mode == MACVLAN_MODE_PASSTHRU)
+ list_add_rcu(&vlan->list, &port->vlans);
+ else
+ list_add_tail_rcu(&vlan->list, &port->vlans);
update_port_bc_queue_len(vlan->port);
netif_stacked_transfer_operstate(lowerdev, dev);
linkwatch_fire_event(dev);
@@ -1627,9 +1662,11 @@ static int macvlan_changelink(struct net_device *dev,
if (data && data[IFLA_MACVLAN_MODE]) {
set_mode = true;
mode = nla_get_u32(data[IFLA_MACVLAN_MODE]);
- /* Passthrough mode can't be set or cleared dynamically */
- if ((mode == MACVLAN_MODE_PASSTHRU) !=
- (vlan->mode == MACVLAN_MODE_PASSTHRU))
+ /* Passthrough mode can't be set or cleared dynamically,
+ * regardless of existing source interfaces. Furthermore, source
+ * interfaces can't switch modes within a passhtrough port.
+ */
+ if (macvlan_passthru(vlan->port) && mode != vlan->mode)
return -EINVAL;
if (vlan->mode == MACVLAN_MODE_SOURCE &&
vlan->mode != mode)
@@ -1639,7 +1676,7 @@ static int macvlan_changelink(struct net_device *dev,
if (data && data[IFLA_MACVLAN_FLAGS]) {
__u16 flags = nla_get_u16(data[IFLA_MACVLAN_FLAGS]);
bool promisc = (flags ^ vlan->flags) & MACVLAN_FLAG_NOPROMISC;
- if (macvlan_passthru(vlan->port) && promisc) {
+ if (vlan->mode == MACVLAN_MODE_PASSTHRU && promisc) {
int err;
if (flags & MACVLAN_FLAG_NOPROMISC)
--
2.54.0
^ permalink raw reply related
* [PATCH v3 0/1] macvlan: allow source mode devices along with passthru
From: Thomas Martitz @ 2026-07-10 8:36 UTC (permalink / raw)
To: Simon Horman, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, open list:NETWORKING DRIVERS,
open list
Cc: Thomas Martitz, open list:NETWORKING DRIVERS, open list
Hello,
we're trying to solve a use case on our devices where two SoC are
connected on the same board, using the only available high-speed interface.
One SoC runs the main Linux system including the full routing stack
(FRITZ!OS) and the other SoC implements most of the GPON ONT side.
The high-speed interface is of course also used for the user traffic.
Therefore we must tell the inter-SoC traffic apart from the user traffic.
We achieve this by matching the well-known MAC address of the ONT SoC.
The user traffic passes through the ONT SoC without modifying MAC headers.
Now we would like to use macvlan (with source mode devices) on the main
SoC side for this but our routing stack requires the rx_handler to be
available. Therefore macvlan is currently not an option.
With this patch macvlan becomes an option because the current limitation
of either "one passthru device" or "any other configuration" is relaxed
for the combination of passthru and any number of source mode devices.
This allows us to configure a source mode device for the other SoC and
register an rx_handler for further processing on the passthru device.
Thanks in advance!
---
Changes in v3
- fix passthru port removal caused by passing the wrong
device to macvlan_port_release_mac(). This was also
detected by syzbot.
- macvlan_port_release_mac() is now named macvlan_restore_mac()
and gets passed a "struct macvlan_port" directly.
- Link to v2: https://lore.kernel.org/netdev/20260709100512.1383421-1-t.martitz@fritz.com/
Changes in v2:
- changed several port-wide checks (macvlan_passthru()) to
per-interface checks (vlan->mode == vlan->mode == MACVLAN_MODE_PASSTHRU)
- correctly handle removing the passthru interface when there are still
source interfaces
- Lnk to initial posting: https://lore.kernel.org/netdev/20260612092345.2352255-1-t.martitz@fritz.com/
---
Thomas Martitz (1):
macvlan: allow source mode devices along with passthru
drivers/net/macvlan.c | 107 ++++++++++++++++++++++++++++--------------
1 file changed, 73 insertions(+), 34 deletions(-)
--
2.54.0
^ permalink raw reply
* Re: [PATCH net v2] sctp: validate stream count in sctp_process_strreset_inreq()
From: David Laight @ 2026-07-10 8:25 UTC (permalink / raw)
To: Cen Zhang (Microsoft)
Cc: marcelo.leitner, lucien.xin, davem, edumazet, kuba, pabeni, horms,
linux-sctp, netdev, linux-kernel, AutonomousCodeSecurity,
tgopinath, kys
In-Reply-To: <20260710010718.20318-1-blbllhy@gmail.com>
On Thu, 9 Jul 2026 21:07:18 -0400
"Cen Zhang (Microsoft)" <blbllhy@gmail.com> wrote:
> When processing a RESET_IN_REQUEST from a peer,
> sctp_process_strreset_inreq() derives the stream count from the
> parameter length but does not check whether the resulting
> RESET_OUT_REQUEST would exceed SCTP_MAX_CHUNK_LEN.
>
> The OUT request header (sctp_strreset_outreq, 16 bytes) is 8 bytes
> larger than the IN request header (sctp_strreset_inreq, 8 bytes).
> Generally, the IP payload is bounded to 65535 bytes, so the stream
> list cannot be large enough to trigger the overflow. However, on
> interfaces with MTU > 65535 (e.g., loopback with IPv6 jumbograms), a
> stream list that fits within the incoming IN parameter can cause a
> __u16 overflow in sctp_make_strreset_req() when computing the OUT
> request size, leading to an undersized skb allocation and a kernel
> BUG:
>
> net/core/skbuff.c:207 skb_panic
> net/core/skbuff.c:2625 skb_put
> net/sctp/sm_make_chunk.c:1535 sctp_addto_chunk
> net/sctp/sm_make_chunk.c:3695 sctp_make_strreset_req
> net/sctp/stream.c:655 sctp_process_strreset_inreq
>
> The local setsockopt path validates the generated reset request size.
> However, for an incoming-only reset, it accounts for the smaller IN
> request even though the peer must generate an OUT request with the same
> stream list. Such a request cannot be completed successfully by the
> peer.
>
> Reject peer IN requests whose corresponding OUT request would exceed
> SCTP_MAX_CHUNK_LEN. Also tighten the local check so it does not send an
> IN request that would require an oversized OUT request from the peer.
>
> Fixes: 7f9d68ac944e ("sctp: implement sender-side procedures for SSN Reset Request Parameter")
> Reported-by: AutonomousCodeSecurity@microsoft.com
> Closes: https://lore.kernel.org/all/20260707203215.2752-1-blbllhy@gmail.com/
> Suggested-by: Xin Long <lucien.xin@gmail.com>
> Signed-off-by: Cen Zhang (Microsoft) <blbllhy@gmail.com>
> ---
> v2: Add the OUT request length check to the send path, as suggested by Xin Long.
>
> net/sctp/stream.c | 6 +++++-
> 1 file changed, 5 insertions(+), 1 deletion(-)
>
> diff --git a/net/sctp/stream.c b/net/sctp/stream.c
> index 5c2fdedea088..34ffe6c945a4 100644
> --- a/net/sctp/stream.c
> +++ b/net/sctp/stream.c
> @@ -308,7 +308,8 @@ int sctp_send_reset_streams(struct sctp_association *asoc,
> goto out;
>
> param_len += str_nums * sizeof(__u16) +
> - sizeof(struct sctp_strreset_inreq);
> + (out ? sizeof(struct sctp_strreset_inreq)
> + : sizeof(struct sctp_strreset_outreq));
Does it really make any sense to have a connection with the 32k streams
that would be needed in order to send a maximal length request?
(Or more likely a user requesting the same streams be reset multiple times.)
So an initial check that str_nums < SOME_CONSTANT_JUST_BELOW_32K would do.
Looking at the code I'm sure the kmalloc() shouldn't be done in the
'str_nums == 0' case either.
In fact it is probably worth doing the kmalloc() earlier to avoid two
scans of the array.
I even wonder if it should be possible to allocate the chunk without filling
in the data and then put the values in afterwards (freeing the chunk if there
is an error).
Then there is the code that reverts the state to OPEN if sctp_send_reconf()
fails - nothing check that is the original state.
David
> }
>
> if (param_len > SCTP_MAX_CHUNK_LEN -
> @@ -639,6 +640,9 @@ struct sctp_chunk *sctp_process_strreset_inreq(
>
> nums = (ntohs(param.p->length) - sizeof(*inreq)) / sizeof(__u16);
> str_p = inreq->list_of_streams;
> + if (nums * sizeof(__u16) + sizeof(struct sctp_strreset_outreq) >
> + SCTP_MAX_CHUNK_LEN - sizeof(struct sctp_reconf_chunk))
> + goto out;
> for (i = 0; i < nums; i++) {
> if (ntohs(str_p[i]) >= stream->outcnt) {
> result = SCTP_STRRESET_ERR_WRONG_SSN;
^ permalink raw reply
* Re: 回复:[PATCH v21 net-next 01/12] net/nebula-matrix: add minimum nbl build framework
From: Breno Leitao @ 2026-07-10 8:20 UTC (permalink / raw)
To: Illusion Wang
Cc: Dimon, Alvin, Sam, netdev, andrew+netdev, corbet, kuba, horms,
linux-doc, pabeni, vadim.fedorenko, lukas.bulwahn, edumazet,
enelsonmoore, skhan, hkallweit1, open list
In-Reply-To: <25d3ffc3-6f45-4baf-9c3c-ff075de14778.Illusion.Wang@nebula-matrix.com>
On Fri, Jul 10, 2026 at 10:45:15AM +0800, Illusion Wang wrote:
> >> From: illusion wang <illusion.wang@nebula-matrix.com>
> >> +Support
> >> +=======
> >> +
> >> +For more information about m18110-NIC/m18000-NIC, please visit the following URL:
> >> +https://www.nebula-matrix.com/
> >
> >Do you have a link for the site in english?
>
> >Thanks
>
> Sorry, I will fix this in the next revision of the patch series.
> Our official English product page is available at:
> https://www.nebula-matrix.com/snic_s1000_en
> The S1000 series products are based on the m18000 chip family,
> and you can find full NIC product introductions on this page.
Thanks, that is what I was looking for. Please use the english like
above.
^ permalink raw reply
* Re: [PATCH net V2] net: phy: motorcomm: read EEE abilities in yt8521_get_features()
From: Breno Leitao @ 2026-07-10 8:19 UTC (permalink / raw)
To: xiaoning.wang
Cc: Frank.Sae, andrew, hkallweit1, linux, davem, edumazet, kuba,
pabeni, netdev, linux-kernel, imx, xiaoning.wang
In-Reply-To: <20260710080018.1070789-1-xiaoning.wang@oss.nxp.com>
On Fri, Jul 10, 2026 at 04:00:18PM +0800, xiaoning.wang@oss.nxp.com wrote:
> From: Clark Wang <xiaoning.wang@nxp.com>
>
> In phy_probe(), genphy_c45_read_eee_abilities() is only called when a
> driver uses phydrv->features. Drivers that implement .get_features are
> responsible for reading the EEE abilities themselves.
>
> yt8521_get_features() does not do this, so phydev->supported_eee stays
> empty for YT8521/YT8531S and "ethtool --show-eee" reports "EEE status:
> not supported", even though the PHY has the standard EEE capability
> registers.
>
> Call genphy_c45_read_eee_abilities() at the end of yt8521_get_features()
> to populate supported_eee.
>
> Fixes: 70479a40954c ("net: phy: Add driver for Motorcomm yt8521 gigabit ethernet phy")
> Signed-off-by: Clark Wang <xiaoning.wang@nxp.com>
Reviewed-by: Breno Leitao <leitao@debian.org>
^ permalink raw reply
* Re: [PATCH net-next v5 0/2] net: dsa: realtek: rtl8365mb: add SGMII/HSGMII support for RTL8367S
From: Mieczyslaw Nalewaj @ 2026-07-10 7:23 UTC (permalink / raw)
To: Johan Alvarado, linusw, alsi, andrew, olteanv, kuba, davem,
edumazet, pabeni, linux
Cc: luizluca, maxime.chevallier, netdev, linux-kernel
In-Reply-To: <0100019f488ec83f-cd82d418-999a-40de-b58b-135b4b2aee51-000000@email.amazonses.com>
On 7/9/2026 10:25 PM, Johan Alvarado wrote:
> The RTL8367S is a 5+2 port switch from the same family as the
> RTL8365MB-VC already supported by this driver. Its chip info table
> entry declares SGMII and HSGMII on external interface 1, but the
> driver so far only implements RGMII, leaving boards that wire the
> switch to the CPU over the SerDes without a working CPU port.
>
> This series implements both modes. The configuration sequence and the
> SerDes tuning parameters are derived from the GPL-licensed Realtek
> rtl8367c vendor driver, as distributed in the Mercusys MR80X GPL code
> drop, and cross-checked against the real register sequence captured at
> runtime by chainloading a custom U-Boot ahead of the stock firmware
> and logging the live SerDes accesses on hardware.
>
> The vendor driver brings up the SerDes by loading firmware into the
> switch's embedded DW8051 microcontroller. Analysis of that firmware
> (by Luiz Angelo Daros de Luca) showed it only performs a SerDes
> data-path reset right after the SerDes reset is deasserted, and then
> runs a link-polling loop that writes the external interface force
> registers -- duplicating, and racing with, the link management phylink
> already performs. This series therefore keeps the DW8051 disabled and
> performs the one necessary action (the data-path reset via the SerDes
> BMCR register) directly in the driver, avoiding both the race and a
> dependency on a redistributable firmware blob.
>
[...]
> ---
> v5:
> - Raise the port 6 ingress and egress rate limiters to their maximum
> at setup time, as the vendor switch init does. The chip resets them
> to 0x1FFFF (~1.048 Gbps in units of 8 Kbps), which caps the
> aggregate HSGMII throughput at roughly 1 Gbps; the cap was
> confirmed on an RTL8367S-based Mercusys MR85X, where raising the
> limiters took multi-client throughput across the HSGMII CPU port
> from ~1.02 Gbps combined to ~2 Gbps (link in patch 2). The related
> HSGMII scheduler line rate (LINE_RATE_HSG_H) is already set to its
> maximum by the common init jam table. Pointed out by Mieczyslaw
> Nalewaj.
I think you should make limiter initiation dependent on whether
the port is assigned to CPU. In a loop, check if the port is CPU port
and set limiters for it.
This way, newly added systems with CPU port 5 or 7 will also work.
Regards
^ permalink raw reply
* Re: [PATCH net 1/2] net: macb: reprogram TBQP after shuffling the TX ring on link-up
From: Sebastian Andrzej Siewior @ 2026-07-10 8:08 UTC (permalink / raw)
To: Taedcke, Christian
Cc: christian.taedcke, Théo Lebrun, Conor Dooley, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Kevin Hao, Simon Horman, Clark Williams, Steven Rostedt,
Robert Hancock, netdev, linux-kernel, linux-rt-devel, stable
In-Reply-To: <4c0570d2-5018-4389-ab63-5f829cc41f32@weidmueller.com>
On 2026-07-07 15:36:24 [+0200], Taedcke, Christian wrote:
> Thank you for the quick review! This is my first Linux kernel
> contribution, so I appreciate your feedback here.
You are doing good.
> On 7/6/2026 5:04 PM, Sebastian Andrzej Siewior wrote:
> > On 2026-07-06 16:02:14 [+0200], Christian Taedcke via B4 Relay wrote:
> >> From: Christian Taedcke <christian.taedcke@weidmueller.com>
> >>
> >> gem_shuffle_tx_one_ring() rotates the software TX ring so that the
> >> tail sits at index 0 and resets queue->tx_tail to 0, but it never
> >> reprograms the hardware transmit buffer queue pointer (TBQP). Other
> >> paths that reset tx_tail to the ring base (macb_init_buffers() and
> >> macb_tx_error_task()) also reprogram TBQP to queue->tx_ring_dma; this
> >> path does not, leaving TBQP pointing at a stale descriptor.
> >>
> >> gem_shuffle_tx_rings() runs on every link-up from
> >> macb_mac_link_up(). After a few link up/down flaps that leave
> >> un-completed descriptors in the ring, the stale TBQP keeps pointing at
> >> a descriptor whose used bit is set. When TX is re-enabled on link-up,
> >> the GEM reads that used descriptor and raises TXUBR. macb_interrupt()
> >> schedules the TX NAPI, macb_tx_poll() makes no progress (work_done ==
> >> 0) and macb_tx_restart() re-issues TSTART, which makes the controller
> >> read the same used descriptor again and re-assert TXUBR. As the MAC
> >> interrupt is level-triggered, it never deasserts and one CPU is pegged
> >> at 100% in the threaded handler, eventually triggering "sched: RT
> >> throttling activated" and a dead network interface.
> >
> > But this should also happen with !RT at which point the interrupt runs
> > at 100% CPU and the softirq has hardly an chance to make progress, no?
>
> Problably yes. I had issues reproducing the issue since it appeared only
> on specific test setups when a lot packets where sent to another network
> device and this device's power was cut. And even then on some test runs
> the issue was not visible after a few hundred iterations. But after a
> restart of the whole test setup (including cold reboot of all devices)
> the issue sometimes appeared after 5 iterations.
> I only metion RT here because it was the only thing i tested. I only ran
> the RT kernel.
> Should I change the description?
It makes a difference if the problem you are facing is limited to
PREEMPT_RT (and so does not trigger on !PREEMPT_RT due to $REASON),
or also effects !PREEMPT_RT but may or may not trigger easily on
PREEMPT_RT.
> >> Fix it by reprogramming TBQP to the ring base on every path of
> >> gem_shuffle_tx_one_ring() that resets tx_tail to 0, mirroring
> >> macb_tx_error_task(). The early return for an already-aligned tail is
> >> left untouched as TBQP is already consistent there. This is safe
> >> because the shuffle runs from macb_mac_link_up() while TE is still
> >> disabled, so the transmitter is halted.
> >>
> >> Fixes: 881a0263d502 ("net: macb: Shuffle the tx ring before enabling tx")
> >
> > This is v7.0-rc4. So that RT tree of yours has some backports or did you
> > run into this while trying to reproduce it upstream?
>
> There were some backports. I ran this on the linux-yocto kernel
> https://git.yoctoproject.org/linux-yocto branch
> v6.6/standard/preempt-rt/base.
> The "Fixes:" commit was backported as 0a47c3889fcd before their version
> of 6.6.130.
>
> The kernel i reproduced the issue on was linux-yocto branch
> v6.6/standard/preempt-rt/base after 6.6.142 was merged into it.
It is usually good to reproduce the issue on vanilla ensuring that the
problem was not introduced by a backport or was solved differently
upstream.
> >> Cc: stable@vger.kernel.org
> >> Assisted-by: Claude:claude-opus-4-8
> >> Signed-off-by: Christian Taedcke <christian.taedcke@weidmueller.com>
> >> ---
> >> drivers/net/ethernet/cadence/macb_main.c | 9 ++++++++-
> >> 1 file changed, 8 insertions(+), 1 deletion(-)
> >>
> >> diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
> >> index fd282a1700fb..b11cb8f068b7 100644
> >> --- a/drivers/net/ethernet/cadence/macb_main.c
> >> +++ b/drivers/net/ethernet/cadence/macb_main.c
> >> @@ -820,7 +820,7 @@ static void gem_shuffle_tx_one_ring(struct macb_queue *queue)
> >> if (!count) {
> >> queue->tx_head = 0;
> >> queue->tx_tail = 0;
> >> - goto unlock;
> >> + goto reset_hw_ptr;
> >
> > This update is even needed for count == 0 case? I kind of do understand
> > that you need to updated if you shuffled the descriptors around.
>
> This was my understanding before researching more because of the email
> from Kevin in this thread: count == 0 may happen anywhere within the ring
> (e.g. when both the tail and the head point to the middle).
> Resetting queue->tx_tail to 0 but not resetting TBQP results in them
> being out-of-sync.
> But as Kevin mentioned in his email TBQP is reset to the original
> value when transmit is disabled (by setting bit 3 in NCR register).
>
> I will investigate this further why my code change fixed the issue for
> me, but according to the documentation in [1] it should be a no-op.
I see.
> [1] https://docs.amd.com/v/u/en-US/ug1085-zynq-ultrascale-trm pg. 1040
>
> Christian
Sebastian
^ permalink raw reply
* [PATCH net V2] net: phy: motorcomm: read EEE abilities in yt8521_get_features()
From: xiaoning.wang @ 2026-07-10 8:00 UTC (permalink / raw)
To: Frank.Sae, andrew, leitao, hkallweit1, linux, davem, edumazet,
kuba, pabeni
Cc: netdev, linux-kernel, imx, xiaoning.wang
From: Clark Wang <xiaoning.wang@nxp.com>
In phy_probe(), genphy_c45_read_eee_abilities() is only called when a
driver uses phydrv->features. Drivers that implement .get_features are
responsible for reading the EEE abilities themselves.
yt8521_get_features() does not do this, so phydev->supported_eee stays
empty for YT8521/YT8531S and "ethtool --show-eee" reports "EEE status:
not supported", even though the PHY has the standard EEE capability
registers.
Call genphy_c45_read_eee_abilities() at the end of yt8521_get_features()
to populate supported_eee.
Fixes: 70479a40954c ("net: phy: Add driver for Motorcomm yt8521 gigabit ethernet phy")
Signed-off-by: Clark Wang <xiaoning.wang@nxp.com>
---
V2 change:
- Since both the YT8521 and YT8531s, which call the yt8521_get_features(),
support EEE, if genphy_c45_read_eee_abilities() returns an error, it
indicates a problem with MDIO bus, and the error should be returned
accordingly.
---
drivers/net/phy/motorcomm.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c
index 5071605a1a11..73b5da937d7f 100644
--- a/drivers/net/phy/motorcomm.c
+++ b/drivers/net/phy/motorcomm.c
@@ -2490,7 +2490,8 @@ static int yt8521_get_features(struct phy_device *phydev)
/* add fiber's features to phydev->supported */
yt8521_prepare_fiber_features(phydev, phydev->supported);
}
- return ret;
+
+ return ret ? ret : genphy_c45_read_eee_abilities(phydev);
}
/**
--
2.34.1
^ permalink raw reply related
* Re: [PATCH net-next 04/10] net: dsa: microchip: add PTP interrupt handling for KSZ8463
From: Bastien Curutchet @ 2026-07-10 7:57 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Richard Cochran, Russell King, Simon Horman, Maxime Chevallier
Cc: Pascal Eberhard, Miquèl Raynal, Thomas Petazzoni, netdev,
linux-kernel
In-Reply-To: <20260709-ksz-new-ptp-v1-4-344f02fe739e@bootlin.com>
Hi all,
On 7/9/26 8:42 AM, Bastien Curutchet (Schneider Electric) wrote:
> KSZ8463 PTP interrupts aren't handled by the driver.
> The interrupt layout in KSZ8463 has nothing to do with the other
> switches:
> - Its global interrupt enable register is 16-bits long and follow an
> 'enable' logic, instead of a 'mask' one
> - all the interrupts of all ports are grouped into one status register
> while others have one interrupt register per port
> - xdelay_req and pdresp timestamps share one single interrupt bit on the
> KSZ8463 while each of them has its own interrupt bit on other switches
>
> Create a KSZ8463-specific set of interrupt domain operations to handle
> the global IRQ layer. To limit code duplication, it uses the same
> interrupt handler than the other switches. Since other switches have
> 8-bits registers, only the high-byte of the interrupt status/enable
> registers are used. This high-byte is where the PTP interrupts are
> located. The low-byte contains the wake-up detection interrupts so if at
> some points these interrupts are needed we'll need a bit of rework here.
>
> Create KSZ8463-specific functions to setup the PTP interrupts. The
> created IRQ domain is tied to the first port of the KSZ8463. Again,
> the same PTP interrupt handler than the others switches is used.
>
> Implement the teardown callback to release the interrupts.
>
> Signed-off-by: Bastien Curutchet (Schneider Electric) <bastien.curutchet@bootlin.com>
> ---
> drivers/net/dsa/microchip/ksz8.c | 93 ++++++++++++++++++++++-
> drivers/net/dsa/microchip/ksz_ptp.c | 129 ++++++++++++++++++++++++++++++++
> drivers/net/dsa/microchip/ksz_ptp.h | 9 +++
> drivers/net/dsa/microchip/ksz_ptp_reg.h | 6 ++
> 4 files changed, 235 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/dsa/microchip/ksz8.c b/drivers/net/dsa/microchip/ksz8.c
> index 3bbca6f9cfc5..c099a7005808 100644
> --- a/drivers/net/dsa/microchip/ksz8.c
> +++ b/drivers/net/dsa/microchip/ksz8.c
> @@ -36,6 +36,13 @@
> #include "ksz8_reg.h"
> #include "ksz8.h"
>
> +/*
> + * We use only the high-byte (so odd addresses) of the 16-bits registers to fit
> + * in the common IRQ framework
> + */
> +#define KSZ8463_REG_ISR 0x191
> +#define KSZ8463_REG_IER 0x193
> +
> /* ksz88x3_drive_strengths - Drive strength mapping for KSZ8863, KSZ8873, ..
> * variants.
> * This values are documented in KSZ8873 and KSZ8863 datasheets.
> @@ -181,6 +188,58 @@ static int ksz8_pme_pwrite8(struct ksz_device *dev, int port, int offset, u8 dat
> return ksz8_ind_write8(dev, table, (u8)(offset), data);
> }
>
> +static void ksz8463_irq_mask(struct irq_data *d)
> +{
> + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d);
> +
> + kirq->masked &= ~BIT(d->hwirq);
> +}
> +
> +static void ksz8463_irq_unmask(struct irq_data *d)
> +{
> + struct ksz_irq *kirq = irq_data_get_irq_chip_data(d);
> +
> + kirq->masked |= BIT(d->hwirq);
> +}
> +
> +static const struct irq_chip ksz8463_irq_chip = {
> + .name = "ksz8463-irq",
> + .irq_mask = ksz8463_irq_mask,
> + .irq_unmask = ksz8463_irq_unmask,
> + .irq_bus_lock = ksz_irq_bus_lock,
> + .irq_bus_sync_unlock = ksz_irq_bus_sync_unlock,
> +};
> +
> +static int ksz8463_irq_domain_map(struct irq_domain *d,
> + unsigned int irq, irq_hw_number_t hwirq)
> +{
> + irq_set_chip_data(irq, d->host_data);
> + irq_set_chip_and_handler(irq, &ksz8463_irq_chip, handle_level_irq);
> + irq_set_noprobe(irq);
> +
> + return 0;
> +}
> +
> +static const struct irq_domain_ops ksz8463_irq_domain_ops = {
> + .map = ksz8463_irq_domain_map,
> + .xlate = irq_domain_xlate_twocell,
> +};
> +
> +static int ksz8463_girq_setup(struct ksz_device *dev)
> +{
> + struct ksz_irq *girq = &dev->girq;
> +
> + girq->nirqs = 8;
> + girq->reg_mask = KSZ8463_REG_IER;
> + girq->reg_status = KSZ8463_REG_ISR;
> + girq->masked = 0;
> + snprintf(girq->name, sizeof(girq->name), "ksz8463-girq");
> +
> + girq->irq_num = dev->irq;
> +
> + return ksz_irq_common_setup(dev, girq, &ksz8463_irq_domain_ops);
> +}
> +
> static int ksz8463_reset_switch(struct ksz_device *dev)
> {
> ksz_cfg(dev, KSZ8463_REG_SW_RESET, KSZ8463_GLOBAL_SOFTWARE_RESET, true);
> @@ -2407,21 +2466,50 @@ static int ksz8463_setup(struct dsa_switch *ds)
> p = &dev->ports[dev->cpu_port];
> p->learning = true;
>
> + if (dev->irq > 0) {
> + ret = ksz8463_girq_setup(dev);
> + if (ret)
> + return ret;
> +
> + ret = ksz8463_ptp_irq_setup(ds);
> + if (ret)
> + goto free_girq;
> + }
> +
> ret = ksz_mdio_register(dev);
> if (ret < 0) {
> dev_err(dev->dev, "failed to register the mdio");
> - return ret;
> + goto free_ptp_irq;
> }
>
> ret = ksz_dcb_init(dev);
> if (ret)
> - return ret;
> + goto free_ptp_irq;
>
> /* start switch */
> regmap_update_bits(ksz_regmap_8(dev), regs[S_START_CTRL],
> SW_START, SW_START);
>
> return 0;
> +
> +free_ptp_irq:
> + if (dev->irq > 0)
> + ksz8463_ptp_irq_free(ds);
> +free_girq:
> + if (dev->irq > 0)
> + ksz_irq_free(&dev->girq);
> +
> + return ret;
> +}
> +
> +static void ksz8463_teardown(struct dsa_switch *ds)
> +{
> + struct ksz_device *dev = ds->priv;
> +
> + if (dev->irq > 0) {
> + ksz8463_ptp_irq_free(ds);
> + ksz_irq_free(&dev->girq);
> + }
> }
>
> /**
> @@ -3010,6 +3098,7 @@ const struct dsa_switch_ops ksz8463_switch_ops = {
> .get_tag_protocol = ksz8463_get_tag_protocol,
> .connect_tag_protocol = ksz8463_connect_tag_protocol,
> .setup = ksz8463_setup,
> + .teardown = ksz8463_teardown,
> .phy_read = ksz8463_phy_read16,
> .phy_write = ksz8463_phy_write16,
> .phylink_get_caps = ksz8_phylink_get_caps,
> diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c
> index 8b98039320ad..7a74befda9ad 100644
> --- a/drivers/net/dsa/microchip/ksz_ptp.c
> +++ b/drivers/net/dsa/microchip/ksz_ptp.c
> @@ -32,6 +32,15 @@
>
> #define KSZ_PTP_INT_START 13
>
> +/*
> + * PTP interrupt bit is the bit 12 of the 16-bits ISR/IER. But ksz_common.c only
> + * accesses the high-byte of these registers so the PTP interrupt bit becomes 4.
> + */
> +#define KSZ8463_SRC_PTP_INT 4
> +#define KSZ8463_PTP_PORT1_INT_START 12
> +#define KSZ8463_PTP_PORT2_INT_START 14
> +#define KSZ8463_PTP_INT_START KSZ8463_PTP_PORT1_INT_START
> +
> static int ksz_ptp_tou_gpio(struct ksz_device *dev)
> {
> int ret;
> @@ -1129,6 +1138,126 @@ static int ksz_ptp_msg_irq_setup(struct ksz_port *port, u8 n)
> return ret;
> }
>
> +static int ksz8463_ptp_port_irq_setup(struct ksz_irq *ptpirq,
> + struct ksz_port *port, int hw_irq)
> +{
> + u16 ts_reg[] = {KSZ8463_REG_PORT_SYNC_TS, KSZ8463_REG_PORT_DREQ_TS};
> + static const char * const name[] = {"sync-msg", "delay-msg"};
> + const struct ksz_dev_ops *ops = port->ksz_dev->dev_ops;
> + struct ksz_ptp_irq *ptpmsg_irq;
> + int ret;
> + int i;
> +
> + init_completion(&port->tstamp_msg_comp);
> +
> + for (i = 0; i < 2; i++) {
> + ptpmsg_irq = &port->ptpmsg_irq[i];
> + ptpmsg_irq->num = irq_create_mapping(ptpirq->domain,
> + hw_irq + i);
> + if (!ptpmsg_irq->num)
> + goto release_msg_irq;
> +
Sashiko says : "Does this code return an uninitialized or incorrect
error code on failure?"
Yes it does, I'll fix it in next iteration
Best regards,
Bastien
^ permalink raw reply
* Re: [PATCH net-next 01/10] net: dsa: microchip: implement ksz8463_setup()
From: Bastien Curutchet @ 2026-07-10 7:55 UTC (permalink / raw)
To: Woojung Huh, UNGLinuxDriver, Andrew Lunn, Vladimir Oltean,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Richard Cochran, Russell King, Simon Horman, Maxime Chevallier
Cc: Pascal Eberhard, Miquèl Raynal, Thomas Petazzoni, netdev,
linux-kernel
In-Reply-To: <20260709-ksz-new-ptp-v1-1-344f02fe739e@bootlin.com>
Hi all,
On 7/9/26 8:42 AM, Bastien Curutchet (Schneider Electric) wrote:
> KSZ8463 uses the ksz8_setup() as setup() callback for its DSA
> operations. Its behavior is quite different than other KSZ8 switches,
> especially its interrupt scheme.
>
> Remove from the ksz8_setup()/ksz8_reset_switch() everything that is
> ksz8463-related.
> Create a dedicated ksz8463_setup() and a ksz8463_reset_switch() function.
> This new ksz8463_setup() is widely inspired from ksz8_setup, it has
> following differences:
> - it doesn't configure drive strength (not supported on KSZ8463)
> - it uses the ksz8463_reset_switch()
> - it doesn't configure IRQs
Sashiko said "Can this cause a probe failure or interrupt corruption
when a hardware interrupt is configured in the device tree?"
It doesn't cause a probe failure on my setup. However, I think it's
right when it says that the IRQ setup could lead to 'erroneously return
an active Linux IRQ belonging to another hardware device'.
I'll change the patch order in next iteration and put this one after the
KSZ8463 interrupt support to avoid this issue.
FYI, Sashiko made two other comments on this patch about bugs that would
be triggered when 'the DSA master netdev is rebound'. I'm not sure to
understand how these bugs can be triggered and the comments start with
'This isn't a bug introduced by this patch' so I don't plan to
investigate it further.
Best regards,
Bastien
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox