* [PATCH net] net: emac: mal: fix W1C write race in ICINTSTAT clearing
From: Rosen Penev @ 2026-07-02 23:49 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Jeff Garzik, David Gibson, open list
The ICINTSTAT register is write-1-to-clear (W1C). The read-modify-write
pattern in both mal_txeob() and mal_rxeob() can lose interrupts: if a bit
that should not be cleared is already asserted when mfdcri() reads the
register, it is included in the read value, retained by the bitwise OR, and
then written back as 1 - inadvertently clearing a pending but unhandled
interrupt.
Fix by writing only the specific bit to clear (ICINTSTAT_ICTX for TXEOB,
ICINTSTAT_ICRX for RXEOB). W1C semantics guarantee that writing 0 to the
other bits has no effect.
Fixes: 1d3bb996481e ("Device tree aware EMAC driver")
Assisted-by: opencode:big-pickle
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/net/ethernet/ibm/emac/mal.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/ibm/emac/mal.c b/drivers/net/ethernet/ibm/emac/mal.c
index 4025bc36ae16..eab7a487bf08 100644
--- a/drivers/net/ethernet/ibm/emac/mal.c
+++ b/drivers/net/ethernet/ibm/emac/mal.c
@@ -282,8 +282,7 @@ static irqreturn_t mal_txeob(int irq, void *dev_instance)
#ifdef CONFIG_PPC_DCR_NATIVE
if (mal_has_feature(mal, MAL_FTR_CLEAR_ICINTSTAT))
- mtdcri(SDR0, DCRN_SDR_ICINTSTAT,
- (mfdcri(SDR0, DCRN_SDR_ICINTSTAT) | ICINTSTAT_ICTX));
+ mtdcri(SDR0, DCRN_SDR_ICINTSTAT, ICINTSTAT_ICTX);
#endif
return IRQ_HANDLED;
@@ -302,8 +301,7 @@ static irqreturn_t mal_rxeob(int irq, void *dev_instance)
#ifdef CONFIG_PPC_DCR_NATIVE
if (mal_has_feature(mal, MAL_FTR_CLEAR_ICINTSTAT))
- mtdcri(SDR0, DCRN_SDR_ICINTSTAT,
- (mfdcri(SDR0, DCRN_SDR_ICINTSTAT) | ICINTSTAT_ICRX));
+ mtdcri(SDR0, DCRN_SDR_ICINTSTAT, ICINTSTAT_ICRX);
#endif
return IRQ_HANDLED;
--
2.55.0
^ permalink raw reply related
* [PATCH] net: emac: mal: replace devm_request_irq with request_irq to fix probe error race
From: Rosen Penev @ 2026-07-02 23:50 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rosen Penev, open list
devm_request_irq() is a managed resource: the IRQ is not freed until
devres_release_all() runs after the probe function returns. In the
probe error path, free_netdev(mal->dummy_dev) and dcr_unmap() execute
while the IRQ is still live. If the shared IRQ fires during cleanup,
the handler accesses unmapped DCR registers (crash) or the already-
freed dummy_dev (use-after-free).
Switch to plain request_irq() with per-IRQ error labels that tear down
only the IRQs that were successfully registered, and add the matching
free_irq() calls in mal_remove().
Fixes: 14f59154ff0b ("net: ibm: emac: mal: use devm for request_irq")
Assisted-by: opencode:big-pickle
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/net/ethernet/ibm/emac/mal.c | 43 +++++++++++++++++++----------
1 file changed, 29 insertions(+), 14 deletions(-)
diff --git a/drivers/net/ethernet/ibm/emac/mal.c b/drivers/net/ethernet/ibm/emac/mal.c
index eab7a487bf08..5c3bf05ca6a5 100644
--- a/drivers/net/ethernet/ibm/emac/mal.c
+++ b/drivers/net/ethernet/ibm/emac/mal.c
@@ -656,26 +656,26 @@ static int mal_probe(struct platform_device *ofdev)
hdlr_rxde = mal_rxde;
}
- err = devm_request_irq(&ofdev->dev, mal->serr_irq, hdlr_serr, irqflags,
- "MAL SERR", mal);
+ err = request_irq(mal->serr_irq, hdlr_serr, irqflags,
+ "MAL SERR", mal);
if (err)
goto fail_dummy;
- err = devm_request_irq(&ofdev->dev, mal->txde_irq, hdlr_txde, irqflags,
- "MAL TX DE", mal);
+ err = request_irq(mal->txde_irq, hdlr_txde, irqflags,
+ "MAL TX DE", mal);
if (err)
- goto fail_dummy;
- err = devm_request_irq(&ofdev->dev, mal->txeob_irq, mal_txeob, 0,
- "MAL TX EOB", mal);
+ goto fail_serr_irq;
+ err = request_irq(mal->txeob_irq, mal_txeob, 0,
+ "MAL TX EOB", mal);
if (err)
- goto fail_dummy;
- err = devm_request_irq(&ofdev->dev, mal->rxde_irq, hdlr_rxde, irqflags,
- "MAL RX DE", mal);
+ goto fail_txde_irq;
+ err = request_irq(mal->rxde_irq, hdlr_rxde, irqflags,
+ "MAL RX DE", mal);
if (err)
- goto fail_dummy;
- err = devm_request_irq(&ofdev->dev, mal->rxeob_irq, mal_rxeob, 0,
- "MAL RX EOB", mal);
+ goto fail_txeob_irq;
+ err = request_irq(mal->rxeob_irq, mal_rxeob, 0,
+ "MAL RX EOB", mal);
if (err)
- goto fail_dummy;
+ goto fail_rxde_irq;
/* Enable all MAL SERR interrupt sources */
set_mal_dcrn(mal, MAL_IER, MAL_IER_EVENTS);
@@ -694,6 +694,14 @@ static int mal_probe(struct platform_device *ofdev)
return 0;
+ fail_rxde_irq:
+ free_irq(mal->rxde_irq, mal);
+ fail_txeob_irq:
+ free_irq(mal->txeob_irq, mal);
+ fail_txde_irq:
+ free_irq(mal->txde_irq, mal);
+ fail_serr_irq:
+ free_irq(mal->serr_irq, mal);
fail_dummy:
free_netdev(mal->dummy_dev);
fail_unmap:
@@ -718,6 +726,13 @@ static void mal_remove(struct platform_device *ofdev)
mal_reset(mal);
+ /* Free IRQs before freeing resources they access */
+ free_irq(mal->serr_irq, mal);
+ free_irq(mal->txde_irq, mal);
+ free_irq(mal->txeob_irq, mal);
+ free_irq(mal->rxde_irq, mal);
+ free_irq(mal->rxeob_irq, mal);
+
free_netdev(mal->dummy_dev);
dcr_unmap(mal->dcr_host, 0x100);
--
2.55.0
^ permalink raw reply related
* [PATCH net] net: emac: mal: replace devm_request_irq with request_irq to fix probe error race
From: Rosen Penev @ 2026-07-02 23:50 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rosen Penev, open list
devm_request_irq() is a managed resource: the IRQ is not freed until
devres_release_all() runs after the probe function returns. In the
probe error path, free_netdev(mal->dummy_dev) and dcr_unmap() execute
while the IRQ is still live. If the shared IRQ fires during cleanup,
the handler accesses unmapped DCR registers (crash) or the already-
freed dummy_dev (use-after-free).
Switch to plain request_irq() with per-IRQ error labels that tear down
only the IRQs that were successfully registered, and add the matching
free_irq() calls in mal_remove().
Fixes: 14f59154ff0b ("net: ibm: emac: mal: use devm for request_irq")
Assisted-by: opencode:big-pickle
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/net/ethernet/ibm/emac/mal.c | 43 +++++++++++++++++++----------
1 file changed, 29 insertions(+), 14 deletions(-)
diff --git a/drivers/net/ethernet/ibm/emac/mal.c b/drivers/net/ethernet/ibm/emac/mal.c
index eab7a487bf08..5c3bf05ca6a5 100644
--- a/drivers/net/ethernet/ibm/emac/mal.c
+++ b/drivers/net/ethernet/ibm/emac/mal.c
@@ -656,26 +656,26 @@ static int mal_probe(struct platform_device *ofdev)
hdlr_rxde = mal_rxde;
}
- err = devm_request_irq(&ofdev->dev, mal->serr_irq, hdlr_serr, irqflags,
- "MAL SERR", mal);
+ err = request_irq(mal->serr_irq, hdlr_serr, irqflags,
+ "MAL SERR", mal);
if (err)
goto fail_dummy;
- err = devm_request_irq(&ofdev->dev, mal->txde_irq, hdlr_txde, irqflags,
- "MAL TX DE", mal);
+ err = request_irq(mal->txde_irq, hdlr_txde, irqflags,
+ "MAL TX DE", mal);
if (err)
- goto fail_dummy;
- err = devm_request_irq(&ofdev->dev, mal->txeob_irq, mal_txeob, 0,
- "MAL TX EOB", mal);
+ goto fail_serr_irq;
+ err = request_irq(mal->txeob_irq, mal_txeob, 0,
+ "MAL TX EOB", mal);
if (err)
- goto fail_dummy;
- err = devm_request_irq(&ofdev->dev, mal->rxde_irq, hdlr_rxde, irqflags,
- "MAL RX DE", mal);
+ goto fail_txde_irq;
+ err = request_irq(mal->rxde_irq, hdlr_rxde, irqflags,
+ "MAL RX DE", mal);
if (err)
- goto fail_dummy;
- err = devm_request_irq(&ofdev->dev, mal->rxeob_irq, mal_rxeob, 0,
- "MAL RX EOB", mal);
+ goto fail_txeob_irq;
+ err = request_irq(mal->rxeob_irq, mal_rxeob, 0,
+ "MAL RX EOB", mal);
if (err)
- goto fail_dummy;
+ goto fail_rxde_irq;
/* Enable all MAL SERR interrupt sources */
set_mal_dcrn(mal, MAL_IER, MAL_IER_EVENTS);
@@ -694,6 +694,14 @@ static int mal_probe(struct platform_device *ofdev)
return 0;
+ fail_rxde_irq:
+ free_irq(mal->rxde_irq, mal);
+ fail_txeob_irq:
+ free_irq(mal->txeob_irq, mal);
+ fail_txde_irq:
+ free_irq(mal->txde_irq, mal);
+ fail_serr_irq:
+ free_irq(mal->serr_irq, mal);
fail_dummy:
free_netdev(mal->dummy_dev);
fail_unmap:
@@ -718,6 +726,13 @@ static void mal_remove(struct platform_device *ofdev)
mal_reset(mal);
+ /* Free IRQs before freeing resources they access */
+ free_irq(mal->serr_irq, mal);
+ free_irq(mal->txde_irq, mal);
+ free_irq(mal->txeob_irq, mal);
+ free_irq(mal->rxde_irq, mal);
+ free_irq(mal->rxeob_irq, mal);
+
free_netdev(mal->dummy_dev);
dcr_unmap(mal->dcr_host, 0x100);
--
2.55.0
^ permalink raw reply related
* Re: [PATCH RFC 0/8] clk: sunxi-ng: Add support for Allwinner A733 CCU and PRCM
From: Enzo Adriano @ 2026-07-02 23:51 UTC (permalink / raw)
To: Junhui Liu
Cc: Andre Przywara, Brian Masney, Michael Turquette, Stephen Boyd,
Chen-Yu Tsai, Philipp Zabel, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Richard Cochran, linux-clk,
linux-riscv, netdev, linux-arm-kernel, linux-sunxi, devicetree,
linux-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jernej Skrabec, Samuel Holland
In-Reply-To: <DJO79318OW0A.2RDQOAXRTHNCW@pigmoral.tech>
Hi Junhui,
Good to hear from you, and glad the series stays in your hands. I hope
the move went smoothly. No rush at all from my side, please take
whatever time you need to settle in.
For whenever you get to v2: I posted register-check notes on five of
the patches earlier today, so they are on the list to pick through at
your convenience. Take from them whatever is useful and ignore the
rest.
One offer, entirely take-it-or-leave-it: I have three Cubie A7S boards
here with serial consoles attached, and I am eager to put them to
use. I can boot v2 the day you post it and report back, if that would
save you time.
On my side I have Cubie A7S board DTS support staged to follow once
the clock and pinctrl pieces land, so whenever v2 arrives it will get
exercised on real hardware right away.
For transparency: I use AI assistance (Claude Code) for the register
comparisons and the test harness, and I check each finding against the
manual and vendor sources before posting.
Thanks,
Enzo
^ permalink raw reply
* Re: [PATCH net] qede: fix off-by-one in BD ring consumption on build_skb failure
From: Jamie Bainbridge @ 2026-07-02 23:52 UTC (permalink / raw)
To: Shigeru Yoshida
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Matvey Kovalev, Pavel Zhigulin, netdev, linux-kernel
In-Reply-To: <20260630164623.3152625-1-syoshida@redhat.com>
On Wed, 1 Jul 2026 at 02:47, Shigeru Yoshida <syoshida@redhat.com> wrote:
>
> qede_rx_build_skb() and qede_tpa_rx_build_skb() do not check for a
> NULL return from qede_build_skb(). When it returns NULL under memory
> pressure, the functions still consume a BD from the ring before
> returning NULL. The callers then recycle additional BDs, resulting in
> one extra BD being consumed (off-by-one). This desynchronizes the BD
> ring, which can corrupt DMA page reference counts and lead to SLUB
> freelist corruption.
Good catch.
Reviewed-by: Jamie Bainbridge <jamie.bainbridge@gmail.com>
> Commit 4e910dbe3650 ("qede: confirm skb is allocated before using")
> added a NULL check inside qede_build_skb() to prevent a NULL pointer
> dereference, but did not address the missing NULL checks in the
> callers, making this off-by-one reachable.
>
> Fix this by adding NULL checks for the return value of
> qede_build_skb() in both qede_rx_build_skb() and
> qede_tpa_rx_build_skb(), returning NULL immediately before any BD ring
> manipulation.
>
> Fixes: 4e910dbe3650 ("qede: confirm skb is allocated before using")
> Signed-off-by: Shigeru Yoshida <syoshida@redhat.com>
> ---
> drivers/net/ethernet/qlogic/qede/qede_fp.c | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/drivers/net/ethernet/qlogic/qede/qede_fp.c b/drivers/net/ethernet/qlogic/qede/qede_fp.c
> index 33e18bb69774..c11e0d8f98aa 100644
> --- a/drivers/net/ethernet/qlogic/qede/qede_fp.c
> +++ b/drivers/net/ethernet/qlogic/qede/qede_fp.c
> @@ -765,6 +765,9 @@ qede_tpa_rx_build_skb(struct qede_dev *edev,
> struct sk_buff *skb;
>
> skb = qede_build_skb(rxq, bd, len, pad);
> + if (unlikely(!skb))
> + return NULL;
> +
> bd->page_offset += rxq->rx_buf_seg_size;
>
> if (bd->page_offset == PAGE_SIZE) {
> @@ -812,6 +815,8 @@ qede_rx_build_skb(struct qede_dev *edev,
> }
>
> skb = qede_build_skb(rxq, bd, len, pad);
> + if (unlikely(!skb))
> + return NULL;
>
> if (unlikely(qede_realloc_rx_buffer(rxq, bd))) {
> /* Incr page ref count to reuse on allocation failure so
> --
> 2.54.0
>
^ permalink raw reply
* Re: [PATCH net] qede: fix off-by-one in BD ring consumption on build_skb failure
From: Jamie Bainbridge @ 2026-07-02 23:55 UTC (permalink / raw)
To: Shigeru Yoshida
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Matvey Kovalev, Pavel Zhigulin, netdev, linux-kernel
In-Reply-To: <CAAvyFNhY2Jp5aH6YPrtfc0mBpwuSeT-8u8bODqq=pootpQffmw@mail.gmail.com>
On Fri, 3 Jul 2026 at 09:52, Jamie Bainbridge
<jamie.bainbridge@gmail.com> wrote:
>
> On Wed, 1 Jul 2026 at 02:47, Shigeru Yoshida <syoshida@redhat.com> wrote:
> >
> > qede_rx_build_skb() and qede_tpa_rx_build_skb() do not check for a
> > NULL return from qede_build_skb(). When it returns NULL under memory
> > pressure, the functions still consume a BD from the ring before
> > returning NULL. The callers then recycle additional BDs, resulting in
> > one extra BD being consumed (off-by-one). This desynchronizes the BD
> > ring, which can corrupt DMA page reference counts and lead to SLUB
> > freelist corruption.
>
> Good catch.
>
> Reviewed-by: Jamie Bainbridge <jamie.bainbridge@gmail.com>
Sorry for the double mail.
I believe the Fixes: should be against the problematic original code:
Fixes: 8a8633978b842 ("qede: Add build_skb() support.")
because that is what you are fixing.
Jamie
> > Commit 4e910dbe3650 ("qede: confirm skb is allocated before using")
> > added a NULL check inside qede_build_skb() to prevent a NULL pointer
> > dereference, but did not address the missing NULL checks in the
> > callers, making this off-by-one reachable.
> >
> > Fix this by adding NULL checks for the return value of
> > qede_build_skb() in both qede_rx_build_skb() and
> > qede_tpa_rx_build_skb(), returning NULL immediately before any BD ring
> > manipulation.
> >
> > Fixes: 4e910dbe3650 ("qede: confirm skb is allocated before using")
> > Signed-off-by: Shigeru Yoshida <syoshida@redhat.com>
> > ---
> > drivers/net/ethernet/qlogic/qede/qede_fp.c | 5 +++++
> > 1 file changed, 5 insertions(+)
> >
> > diff --git a/drivers/net/ethernet/qlogic/qede/qede_fp.c b/drivers/net/ethernet/qlogic/qede/qede_fp.c
> > index 33e18bb69774..c11e0d8f98aa 100644
> > --- a/drivers/net/ethernet/qlogic/qede/qede_fp.c
> > +++ b/drivers/net/ethernet/qlogic/qede/qede_fp.c
> > @@ -765,6 +765,9 @@ qede_tpa_rx_build_skb(struct qede_dev *edev,
> > struct sk_buff *skb;
> >
> > skb = qede_build_skb(rxq, bd, len, pad);
> > + if (unlikely(!skb))
> > + return NULL;
> > +
> > bd->page_offset += rxq->rx_buf_seg_size;
> >
> > if (bd->page_offset == PAGE_SIZE) {
> > @@ -812,6 +815,8 @@ qede_rx_build_skb(struct qede_dev *edev,
> > }
> >
> > skb = qede_build_skb(rxq, bd, len, pad);
> > + if (unlikely(!skb))
> > + return NULL;
> >
> > if (unlikely(qede_realloc_rx_buffer(rxq, bd))) {
> > /* Incr page ref count to reuse on allocation failure so
> > --
> > 2.54.0
> >
^ permalink raw reply
* [PATCH v2 net-next 00/14] net: Support per-netns device unregistration
From: Kuniyuki Iwashima @ 2026-07-03 0:09 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
The biggest blocker to per-netns RTNL is netdev unregistration.
It starts within a single netns, but it can eventually involve
multiple namespaces.
There are three types of such cross-netns devices:
1. Paired devices (e.g., netkit, veth, vxcan)
-> Unregistering one device also deletes its peer, which
may reside in another netns.
2. Tunnel devices (e.g., bareudp, geneve, etc)
-> Destroying a netns removes devices in another netns if
their backend sockets reside in the dying netns
3. Stacked devices (e.g., ipvlan, macvlan, etc)
-> Removing the lower device also removes multiple upper
devices, each of which may reside in different namespaces.
While the first two device types require at most two rtnl_net_lock()s,
the stacked type has no upper limit. This makes it impossible to
freeze all necessary namespaces in advance.
This series introduces per-netns work, initially suggested at
NetConf 2024, to delegate the unregistration of such cross-netns
devices.
https://netdev.bots.linux.dev/netconf/2024/kuniyu.pdf#page=62
The first half of the series wraps NETDEV_UNREGISTER (in core) with
per-netns RTNL, adds a helper for per-netns device unregistration,
and forces per-netns device unregistration in the core code when
CONFIG_DEBUG_NET_SMALL_RTNL=y.
The latter half picks out one from each type (veth, bareudp, ipvlan)
and converts them to support per-netns device unregistration,
although the operations are **still serialised under RTNL** for now.
Please note that this series focuses only on the device unregistration
paths. For example, there are ASSERT_RTNL() left in other paths, and
Sashiko may point it out, but they are out of scope.
This is just the first step, and we need more incremental changes to
completely remove RTNL anyway.
Now, we can see that unregistering a lower device (veth0 below)
removes upper devices (ipvl2, ipvl3) in different namespaces using
per-netns work with a different PID. The lower device (veth0) is
freed only after all upper ipvlan devices have called netdev_put()
in ipvlan_uninit().
# ip netns add ns1
# ip netns add ns2
# ip netns add ns3
# ip -n ns1 link add veth0 type veth peer veth1
# ip -n ns2 link add ipvl2 link veth0 link-netns ns1 type ipvlan mode l2
# ip -n ns3 link add ipvl3 link veth0 link-netns ns1 type ipvlan mode l2
# ip -n ns1 link del veth0
# bpftrace -e '#include <linux/netdevice.h>
kprobe:ipvlan_uninit,
kprobe:veth_dellink,
kprobe:free_netdev {
$dev = (struct net_device *)arg0;
printf("PID: %d | DEV: %s%s\n", pid, $dev->name, kstack());
}'
PID: 2010 | DEV: veth0
veth_dellink+5
rtnl_dellink+1213
rtnetlink_rcv_msg+1791
...
PID: 440 | DEV: ipvl2
ipvlan_uninit+5
unregister_netdevice_many_notify+7129
unregister_netdevice_many_net+1050
rtnl_net_work_func+136
...
PID: 440 | DEV: ipvl2
free_netdev+5
netdev_run_todo+4798
process_scheduled_works+2538
...
PID: 440 | DEV: ipvl3
ipvlan_uninit+5
unregister_netdevice_many_notify+7129
unregister_netdevice_many_net+1050
rtnl_net_work_func+136
process_scheduled_works+2538
...
PID: 2010 | DEV: veth0
free_netdev+5
netdev_run_todo+4798
rtnl_dellink+1507
rtnetlink_rcv_msg+1791
...
PID: 440 | DEV: ipvl3
free_netdev+5
netdev_run_todo+4798
process_scheduled_works+2538
...
Changes:
v2:
* Patch 6
* Use spin_lock_nested() in unregister_netdevice_move_net()
* Add kdoc for dev->unreg_list_net
* Add DEBUG_NET_WARN_ON_ONCE() in unregister_netdevice_queue()
* Patch 13
* Add __ipvtap_dellink_ptr for CONFIG_IPVLAN=y and CONFIG_TAP=m
v1: https://lore.kernel.org/netdev/20260701214334.266991-1-kuniyu@google.com/
Kuniyuki Iwashima (14):
rtnetlink: Lock sock_net(skb->sk) in rtnl_newlink().
rtnetlink: Call unregister_netdevice_many() only once in
rtnl_link_unregister().
rtnetlink: Add per-netns rtnl_work.
net: Wrap default_device_exit_net() with __rtnl_net_lock().
net: Hold __rtnl_net_lock() in netdev_wait_allrefs_any().
net: Add per-netns netdev unregistration infra.
net: Call unregister_netdevice_many() per netns.
veth: Support per-netns device unregistration.
bareudp: Protect bareudp_list with mutex.
bareudp: Support per-netns netdev unregistration.
ipvlan: Convert ipvl_port.count to refcount_t.
ipvlan: Synchronise ipvlan_init() and ipvlan_uninit() for the same
lower dev.
ipvlan: Protect ipvl_port.ipvlans with mutex.
ipvlan: Support per-netns netdev unregistration.
drivers/net/bareudp.c | 43 ++++++++-
drivers/net/ipvlan/ipvlan.h | 12 ++-
drivers/net/ipvlan/ipvlan_main.c | 147 ++++++++++++++++++++++++-------
drivers/net/ipvlan/ipvtap.c | 25 +++++-
drivers/net/veth.c | 34 ++++---
include/linux/netdevice.h | 24 +++++
include/linux/rtnetlink.h | 8 ++
include/net/net_namespace.h | 3 +
net/core/dev.c | 135 +++++++++++++++++++++++++++-
net/core/net_namespace.c | 4 +
net/core/rtnetlink.c | 57 ++++++++++--
11 files changed, 429 insertions(+), 63 deletions(-)
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply
* [PATCH v2 net-next 01/14] rtnetlink: Lock sock_net(skb->sk) in rtnl_newlink().
From: Kuniyuki Iwashima @ 2026-07-03 0:09 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
In-Reply-To: <20260703001009.1572444-1-kuniyu@google.com>
There are a few cases where rtnl_net_lock() is not properly
held in rtnl_newlink().
When either of IFLA_NET_NS_PID / IFLA_NET_NS_FD / IFLA_TARGET_NETNSID
is specified but IFLA_LINK_NETNSID is not, sock_net(skb->sk) is used
as link_net in rtnl_newlink_link_net().
In addition, the do_setlink() path uses sock_net(skb->sk) and one
from the three netns attributes while rtnl_link_get_net_capable()
returns only one of four.
Let's add sock_net(skb->sk) to rtnl_nets in rtnl_newlink().
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
No fixes tag is needed since there is no real bug nor assertion.
---
net/core/rtnetlink.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index 12aa3aa1688b..f39c93e80e20 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -282,10 +282,11 @@ static int rtnl_net_cmp_locks(const struct net *net_a, const struct net *net_b)
#endif
struct rtnl_nets {
- /* ->newlink() needs to freeze 3 netns at most;
- * 2 for the new device, 1 for its peer.
+ /* ->newlink() needs to freeze 4 netns at most;
+ * 2 for the new device, 1 for its peer, 1 for
+ * an existing device (do_setlink() path).
*/
- struct net *net[3];
+ struct net *net[4];
unsigned char len;
};
@@ -4155,6 +4156,8 @@ static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
}
}
+ rtnl_nets_add(&rtnl_nets, get_net(sock_net(skb->sk)));
+
rtnl_nets_lock(&rtnl_nets);
ret = __rtnl_newlink(skb, nlh, ops, tgt_net, link_net, peer_net, tbs, data, extack);
rtnl_nets_unlock(&rtnl_nets);
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net-next 02/14] rtnetlink: Call unregister_netdevice_many() only once in rtnl_link_unregister().
From: Kuniyuki Iwashima @ 2026-07-03 0:09 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
In-Reply-To: <20260703001009.1572444-1-kuniyu@google.com>
When rtnl_link_unregister() is called during module unload, it
calls __rtnl_kill_links() for every netns.
__rtnl_kill_links() collects all devices of the unloaded module
and passes them to unregister_netdevice_many().
Let's move unregister_netdevice_many() to rtnl_link_unregister()
to unregister all devices across netns in a single batch.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
net/core/rtnetlink.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index f39c93e80e20..7207da002fb5 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -637,16 +637,15 @@ int rtnl_link_register(struct rtnl_link_ops *ops)
}
EXPORT_SYMBOL_GPL(rtnl_link_register);
-static void __rtnl_kill_links(struct net *net, struct rtnl_link_ops *ops)
+static void __rtnl_kill_links(struct net *net, struct rtnl_link_ops *ops,
+ struct list_head *dev_kill_list)
{
struct net_device *dev;
- LIST_HEAD(list_kill);
for_each_netdev(net, dev) {
if (dev->rtnl_link_ops == ops)
- ops->dellink(dev, &list_kill);
+ ops->dellink(dev, dev_kill_list);
}
- unregister_netdevice_many(&list_kill);
}
/* Return with the rtnl_lock held when there are no network
@@ -677,6 +676,7 @@ static void rtnl_lock_unregistering_all(void)
*/
void rtnl_link_unregister(struct rtnl_link_ops *ops)
{
+ LIST_HEAD(dev_kill_list);
struct net *net;
mutex_lock(&link_ops_mutex);
@@ -691,7 +691,9 @@ void rtnl_link_unregister(struct rtnl_link_ops *ops)
rtnl_lock_unregistering_all();
for_each_net(net)
- __rtnl_kill_links(net, ops);
+ __rtnl_kill_links(net, ops, &dev_kill_list);
+
+ unregister_netdevice_many(&dev_kill_list);
rtnl_unlock();
up_write(&pernet_ops_rwsem);
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net-next 03/14] rtnetlink: Add per-netns rtnl_work.
From: Kuniyuki Iwashima @ 2026-07-03 0:09 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
In-Reply-To: <20260703001009.1572444-1-kuniyu@google.com>
The biggest blocker to per-netns RTNL is netdev unregistration.
It starts within a single netns (e.g., during a device lookup or
netns dismantle), but it can eventually involve multiple namespaces,
such as when upper ipvlan devices reside in different netns.
This prevents us from acquiring multiple rtnl_net_lock()s beforehand.
When we encounter such a cross-netns device, we must delegate the
unregistration to the work of the netns where the device actually
resides.
Let's add per-netns rtnl_work to support the deferred netdev
unregistration.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
include/linux/rtnetlink.h | 8 ++++++++
include/net/net_namespace.h | 1 +
net/core/net_namespace.c | 1 +
net/core/rtnetlink.c | 26 ++++++++++++++++++++++++++
4 files changed, 36 insertions(+)
diff --git a/include/linux/rtnetlink.h b/include/linux/rtnetlink.h
index ea39dd23a197..95729339e7a5 100644
--- a/include/linux/rtnetlink.h
+++ b/include/linux/rtnetlink.h
@@ -115,6 +115,10 @@ bool rtnl_net_is_locked(struct net *net);
bool lockdep_rtnl_net_is_held(struct net *net);
+void rtnl_net_queue_work(struct net *net);
+void rtnl_net_flush_workqueue(void);
+void rtnl_net_work_func(struct work_struct *work);
+
#define rcu_dereference_rtnl_net(net, p) \
rcu_dereference_check(p, lockdep_rtnl_net_is_held(net))
#define rtnl_net_dereference(net, p) \
@@ -150,6 +154,10 @@ static inline void ASSERT_RTNL_NET(struct net *net)
ASSERT_RTNL();
}
+static inline void rtnl_net_flush_workqueue(void)
+{
+}
+
#define rcu_dereference_rtnl_net(net, p) \
rcu_dereference_rtnl(p)
#define rtnl_net_dereference(net, p) \
diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h
index 80de5e98a66d..a989019af5f7 100644
--- a/include/net/net_namespace.h
+++ b/include/net/net_namespace.h
@@ -197,6 +197,7 @@ struct net {
#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
/* Move to a better place when the config guard is removed. */
struct mutex rtnl_mutex;
+ struct work_struct rtnl_work;
#endif
#if IS_ENABLED(CONFIG_VSOCKETS)
struct netns_vsock vsock;
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index d9dafe24f57e..d1aeff9de580 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -422,6 +422,7 @@ static __net_init int preinit_net(struct net *net, struct user_namespace *user_n
#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
mutex_init(&net->rtnl_mutex);
lock_set_cmp_fn(&net->rtnl_mutex, rtnl_net_lock_cmp_fn, NULL);
+ INIT_WORK(&net->rtnl_work, rtnl_net_work_func);
#endif
INIT_LIST_HEAD(&net->ptype_all);
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index 7207da002fb5..7959519e7375 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -273,6 +273,26 @@ bool lockdep_rtnl_net_is_held(struct net *net)
return lockdep_rtnl_is_held() && lockdep_is_held(&net->rtnl_mutex);
}
EXPORT_SYMBOL(lockdep_rtnl_net_is_held);
+
+static struct workqueue_struct *rtnl_net_wq;
+
+void rtnl_net_queue_work(struct net *net)
+{
+ queue_work(rtnl_net_wq, &net->rtnl_work);
+}
+
+void rtnl_net_flush_workqueue(void)
+{
+ flush_workqueue(rtnl_net_wq);
+}
+
+void rtnl_net_work_func(struct work_struct *work)
+{
+ struct net *net = container_of(work, struct net, rtnl_work);
+
+ rtnl_net_lock(net);
+ rtnl_net_unlock(net);
+}
#else
static int rtnl_net_cmp_locks(const struct net *net_a, const struct net *net_b)
{
@@ -7226,4 +7246,10 @@ void __init rtnetlink_init(void)
register_netdevice_notifier(&rtnetlink_dev_notifier);
rtnl_register_many(rtnetlink_rtnl_msg_handlers);
+
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ rtnl_net_wq = create_workqueue("rtnl_net");
+ if (!rtnl_net_wq)
+ panic("Could not create rtnl_net workq");
+#endif
}
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net-next 04/14] net: Wrap default_device_exit_net() with __rtnl_net_lock().
From: Kuniyuki Iwashima @ 2026-07-03 0:09 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
In-Reply-To: <20260703001009.1572444-1-kuniyu@google.com>
default_device_exit_net() could call dev_change_net_namespace()
to move devices from a dying netns to init_net.
Let's hold the two netns __rtnl_net_lock() around it.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
net/core/dev.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/net/core/dev.c b/net/core/dev.c
index 4b3d5cfdf6e0..c477c4f84ed9 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -13034,7 +13034,7 @@ static void __net_exit default_device_exit_net(struct net *net)
* Push all migratable network devices back to the
* initial network namespace
*/
- ASSERT_RTNL();
+
for_each_netdev_safe(net, dev, aux) {
int err;
char fb_name[IFNAMSIZ];
@@ -13077,11 +13077,19 @@ static void __net_exit default_device_exit_batch(struct list_head *net_list)
LIST_HEAD(dev_kill_list);
rtnl_lock();
+
+ __rtnl_net_lock(&init_net);
+
list_for_each_entry(net, net_list, exit_list) {
+ __rtnl_net_lock(net);
default_device_exit_net(net);
+ __rtnl_net_unlock(net);
+
cond_resched();
}
+ __rtnl_net_unlock(&init_net);
+
list_for_each_entry(net, net_list, exit_list) {
for_each_netdev_reverse(net, dev) {
if (dev->rtnl_link_ops && dev->rtnl_link_ops->dellink)
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net-next 05/14] net: Hold __rtnl_net_lock() in netdev_wait_allrefs_any().
From: Kuniyuki Iwashima @ 2026-07-03 0:09 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
In-Reply-To: <20260703001009.1572444-1-kuniyu@google.com>
Currently, netdev_run_todo() processes pending devices from multiple
namespaces in a batch.
To expand the per-netns RTNL coverage for NETDEV_UNREGISTER, let's
acquire __rtnl_net_lock() in netdev_wait_allrefs_any().
Note that netdev_run_todo() itself will need to be namespacified
before RTNL is removed.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
net/core/dev.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/net/core/dev.c b/net/core/dev.c
index c477c4f84ed9..48818a194fa5 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -11608,8 +11608,13 @@ static struct net_device *netdev_wait_allrefs_any(struct list_head *list)
rtnl_lock();
/* Rebroadcast unregister notification */
- list_for_each_entry(dev, list, todo_list)
+ list_for_each_entry(dev, list, todo_list) {
+ struct net *net = dev_net(dev);
+
+ __rtnl_net_lock(net);
call_netdevice_notifiers(NETDEV_UNREGISTER, dev);
+ __rtnl_net_unlock(net);
+ }
__rtnl_unlock();
rcu_barrier();
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net-next 06/14] net: Add per-netns netdev unregistration infra.
From: Kuniyuki Iwashima @ 2026-07-03 0:09 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
In-Reply-To: <20260703001009.1572444-1-kuniyu@google.com>
When we need to unregister a netdev in a different netns, we will
delegate its unregistration to per-netns work.
There are three types of such cross-netns devices:
1. Paired devices (e.g., netkit, veth, vxcan)
-> Unregistering one device also deletes its peer, which
may reside in another netns.
2. Tunnel devices (e.g., bareudp, geneve, etc)
-> Destroying a netns removes devices in another netns if
their backend sockets reside in the dying netns
3. Stacked devices (e.g., ipvlan, macvlan, etc)
-> Removing the lower device also removes multiple upper
devices, each of which may reside in different namespaces.
In these cases, we will use unregister_netdevice_queue_net() to
queue such potential cross-netns devices for destruction.
Each driver must not call both unregister_netdevice_queue_net()
and unregister_netdevice_queue() for the same device. See the
subsequent veth/bareudp/ipvlan patches for how they avoid double
queueing.
unregister_netdevice_queue_net() takes net and dev. If dev resides
in the net, it simply calls unregister_netdevice_queue().
If dev_net(dev) is different from the net, it enqueues the device
to dev_net(dev)->dev_unreg_head and schedules the per-netns work.
When __rtnl_net_unlock() is called from the per-netns work (or another
thread already holding the lock), unregister_netdevice_many_net()
collects the queued devices and calls unregister_netdevice_many()
to perform the actual unregistration.
During netns dismantle, rtnl_net_flush_workqueue() is called at the
end of default_device_exit_batch() to ensure that cross-netns
devices in the other alive netns are unregistered.
Once RTNL is removed, a device could be moved to another netns while
being queued to net->dev_unreg_head.
__dev_change_net_namespace() handles this race by acquiring
net->dev_unreg_lock of both the old and new netns after dev_set_net()
and moving the device between their dev_unreg_head lists.
Since dev_set_net() and unregister_netdevice_queue_net() are
synchronised by netdev_lock(), the device is either queued to the
old netns's dev_unreg_head and then moved, or queued directly to
the new netns.
Note that unregister_netdevice_move_net() does not need to call
rtnl_net_queue_work() because __dev_change_net_namespace() is
(supposed to be) called with rtnl_net_lock(). (Not all callers
hold it yet, but the race does not happen until all callers
are converted and RTNL is removed.)
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
v2:
* Use spin_lock_nested() in unregister_netdevice_move_net()
* Add kdoc for dev->unreg_list_net
* Add DEBUG_NET_WARN_ON_ONCE() in unregister_netdevice_queue()
---
include/linux/netdevice.h | 18 ++++++++
include/net/net_namespace.h | 2 +
net/core/dev.c | 91 +++++++++++++++++++++++++++++++++++++
net/core/net_namespace.c | 2 +
net/core/rtnetlink.c | 4 ++
5 files changed, 117 insertions(+)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 9981d637f8b5..108d8d7ea75b 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1845,6 +1845,8 @@ enum netdev_reg_state {
* @napi_list: List entry used for polling NAPI devices
* @unreg_list: List entry when we are unregistering the
* device; see the function unregister_netdev
+ * @unreg_list_net:List entry when we are unregistering the cross-netns
+ * device; see the function unregister_netdevice_queue_net()
* @close_list: List entry used when we are closing the device
* @ptype_all: Device-specific packet handlers for all protocols
* @ptype_specific: Device-specific, protocol-specific packet handlers
@@ -2241,6 +2243,9 @@ struct net_device {
struct list_head dev_list;
struct list_head napi_list;
struct list_head unreg_list;
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ struct list_head unreg_list_net;
+#endif
struct list_head close_list;
struct list_head ptype_all;
@@ -3472,6 +3477,19 @@ static inline void unregister_netdevice(struct net_device *dev)
unregister_netdevice_queue(dev, NULL);
}
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+void unregister_netdevice_queue_net(struct net *net, struct net_device *dev,
+ struct list_head *head);
+void unregister_netdevice_many_net(struct net *net);
+#else
+static inline void unregister_netdevice_queue_net(struct net *net,
+ struct net_device *dev,
+ struct list_head *head)
+{
+ unregister_netdevice_queue(dev, head);
+}
+#endif
+
int netdev_refcnt_read(const struct net_device *dev);
void free_netdev(struct net_device *dev);
diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h
index a989019af5f7..501af1999fe8 100644
--- a/include/net/net_namespace.h
+++ b/include/net/net_namespace.h
@@ -198,6 +198,8 @@ struct net {
/* Move to a better place when the config guard is removed. */
struct mutex rtnl_mutex;
struct work_struct rtnl_work;
+ struct list_head dev_unreg_head;
+ spinlock_t dev_unreg_lock;
#endif
#if IS_ENABLED(CONFIG_VSOCKETS)
struct netns_vsock vsock;
diff --git a/net/core/dev.c b/net/core/dev.c
index 48818a194fa5..ed41c704c941 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -12092,6 +12092,9 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
INIT_LIST_HEAD(&dev->napi_list);
INIT_LIST_HEAD(&dev->unreg_list);
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ INIT_LIST_HEAD(&dev->unreg_list_net);
+#endif
INIT_LIST_HEAD(&dev->close_list);
INIT_LIST_HEAD(&dev->link_watch_list);
INIT_LIST_HEAD(&dev->adj_list.upper);
@@ -12309,6 +12312,10 @@ void unregister_netdevice_queue(struct net_device *dev, struct list_head *head)
{
ASSERT_RTNL();
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->unreg_list_net));
+#endif
+
if (head) {
list_move_tail(&dev->unreg_list, head);
} else {
@@ -12485,6 +12492,16 @@ void unregister_netdevice_many_notify(struct list_head *head,
synchronize_net();
list_for_each_entry(dev, head, unreg_list) {
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ struct net *net = dev_net(dev);
+
+ /* spin_lock() can be moved outside of the loop
+ * once the per-netns RTNL conversion completes.
+ */
+ spin_lock(&net->dev_unreg_lock);
+ list_del(&dev->unreg_list_net);
+ spin_unlock(&net->dev_unreg_lock);
+#endif
netdev_put(dev, &dev->dev_registered_tracker);
net_set_todo(dev);
cnt++;
@@ -12507,6 +12524,74 @@ void unregister_netdevice_many(struct list_head *head)
}
EXPORT_SYMBOL(unregister_netdevice_many);
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+void unregister_netdevice_queue_net(struct net *net, struct net_device *dev,
+ struct list_head *head)
+{
+ netdev_lock(dev);
+
+ if (net_eq(dev_net(dev), net)) {
+ netdev_unlock(dev);
+ unregister_netdevice_queue(dev, head);
+ return;
+ }
+
+ net = dev_net(dev);
+
+ spin_lock(&net->dev_unreg_lock);
+
+ DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->unreg_list));
+ DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->unreg_list_net));
+
+ list_add_tail(&dev->unreg_list_net, &net->dev_unreg_head);
+ rtnl_net_queue_work(net);
+
+ spin_unlock(&net->dev_unreg_lock);
+
+ netdev_unlock(dev);
+}
+EXPORT_SYMBOL(unregister_netdevice_queue_net);
+
+static void unregister_netdevice_move_net(struct net *net_old,
+ struct net *net,
+ struct net_device *dev)
+{
+ if (net_old > net) {
+ spin_lock(&net->dev_unreg_lock);
+ spin_lock_nested(&net_old->dev_unreg_lock, SINGLE_DEPTH_NESTING);
+ } else {
+ spin_lock(&net_old->dev_unreg_lock);
+ spin_lock_nested(&net->dev_unreg_lock, SINGLE_DEPTH_NESTING);
+ }
+
+ if (!list_empty(&dev->unreg_list_net)) {
+ list_del(&dev->unreg_list_net);
+ list_add_tail(&dev->unreg_list_net, &net->dev_unreg_head);
+ }
+
+ spin_unlock(&net_old->dev_unreg_lock);
+ spin_unlock(&net->dev_unreg_lock);
+}
+
+void unregister_netdevice_many_net(struct net *net)
+{
+ struct net_device *dev, *tmp;
+ LIST_HEAD(unreg_head_net);
+ LIST_HEAD(unreg_head);
+
+ spin_lock(&net->dev_unreg_lock);
+ list_splice_init(&net->dev_unreg_head, &unreg_head_net);
+ spin_unlock(&net->dev_unreg_lock);
+
+ list_for_each_entry_safe(dev, tmp, &unreg_head_net, unreg_list_net) {
+ list_del_init(&dev->unreg_list_net);
+ list_add_tail(&dev->unreg_list, &unreg_head);
+ }
+
+ unregister_netdevice_many(&unreg_head);
+}
+#endif
+
/**
* unregister_netdev - remove device from the kernel
* @dev: device
@@ -12663,6 +12748,10 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net,
netdev_unlock(dev);
dev->ifindex = new_ifindex;
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ unregister_netdevice_move_net(net_old, net, dev);
+#endif
+
if (new_name[0]) {
/* Rename the netdev to prepared name */
write_seqlock_bh(&netdev_rename_lock);
@@ -13105,6 +13194,8 @@ static void __net_exit default_device_exit_batch(struct list_head *net_list)
}
unregister_netdevice_many(&dev_kill_list);
rtnl_unlock();
+
+ rtnl_net_flush_workqueue();
}
static struct pernet_operations __net_initdata default_device_ops = {
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index d1aeff9de580..578b48cf5318 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -423,6 +423,8 @@ static __net_init int preinit_net(struct net *net, struct user_namespace *user_n
mutex_init(&net->rtnl_mutex);
lock_set_cmp_fn(&net->rtnl_mutex, rtnl_net_lock_cmp_fn, NULL);
INIT_WORK(&net->rtnl_work, rtnl_net_work_func);
+ INIT_LIST_HEAD(&net->dev_unreg_head);
+ spin_lock_init(&net->dev_unreg_lock);
#endif
INIT_LIST_HEAD(&net->ptype_all);
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index 7959519e7375..544498d3c325 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -197,6 +197,7 @@ void __rtnl_net_unlock(struct net *net)
{
ASSERT_RTNL();
+ unregister_netdevice_many_net(net);
mutex_unlock(&net->rtnl_mutex);
}
EXPORT_SYMBOL(__rtnl_net_unlock);
@@ -290,6 +291,9 @@ void rtnl_net_work_func(struct work_struct *work)
{
struct net *net = container_of(work, struct net, rtnl_work);
+ if (list_empty(&net->dev_unreg_head))
+ return;
+
rtnl_net_lock(net);
rtnl_net_unlock(net);
}
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net-next 07/14] net: Call unregister_netdevice_many() per netns.
From: Kuniyuki Iwashima @ 2026-07-03 0:09 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
In-Reply-To: <20260703001009.1572444-1-kuniyu@google.com>
For per-netns device unregistration, the list passed to
unregister_netdevice_many() must contain devices from a single
netns only (once all callers are converted).
Let's move collected devices in the following functions to
net->dev_unreg_head and let __rtnl_net_unlock() pass them to
unregister_netdevice_many().
* default_device_exit_batch()
* ops_exit_rtnl_list()
* __rtnl_kill_links()
This allows incremental conversion of each driver to support
per-netns device unregistration without affecting the normal
kernel where CONFIG_DEBUG_NET_SMALL_RTNL is disabled.
Note that this change unbatches synchronize_rcu() etc in
unregister_netdevice_many(), but we can later split it into
multiple stages to batch them again.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
include/linux/netdevice.h | 6 ++++++
net/core/dev.c | 27 +++++++++++++++++++++++++++
net/core/net_namespace.c | 1 +
net/core/rtnetlink.c | 6 +++++-
4 files changed, 39 insertions(+), 1 deletion(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 108d8d7ea75b..8db25b79573e 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -3481,6 +3481,7 @@ static inline void unregister_netdevice(struct net_device *dev)
void unregister_netdevice_queue_net(struct net *net, struct net_device *dev,
struct list_head *head);
void unregister_netdevice_many_net(struct net *net);
+void unregister_netdevice_queue_many_net(struct net *net, struct list_head *head);
#else
static inline void unregister_netdevice_queue_net(struct net *net,
struct net_device *dev,
@@ -3488,6 +3489,11 @@ static inline void unregister_netdevice_queue_net(struct net *net,
{
unregister_netdevice_queue(dev, head);
}
+
+static inline void unregister_netdevice_queue_many_net(struct net *net,
+ struct list_head *head)
+{
+}
#endif
int netdev_refcnt_read(const struct net_device *dev);
diff --git a/net/core/dev.c b/net/core/dev.c
index ed41c704c941..c43a44fb649f 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -12552,6 +12552,28 @@ void unregister_netdevice_queue_net(struct net *net, struct net_device *dev,
}
EXPORT_SYMBOL(unregister_netdevice_queue_net);
+void unregister_netdevice_queue_many_net(struct net *net, struct list_head *head)
+{
+ struct net_device *dev, *tmp;
+
+ spin_lock(&net->dev_unreg_lock);
+ list_for_each_entry_safe(dev, tmp, head, unreg_list) {
+ /* Once all cross-netns unregister_netdevice_queue() is
+ * converted to _net() (or for debugging), remove this check.
+ */
+ if (!net_eq(dev_net(dev), net))
+ continue;
+
+ DEBUG_NET_WARN_ONCE(!net_eq(dev_net(dev), net),
+ "%s was unregistered from a different netns.\n",
+ dev->name);
+
+ list_del_init(&dev->unreg_list);
+ list_move_tail(&dev->unreg_list_net, &net->dev_unreg_head);
+ }
+ spin_unlock(&net->dev_unreg_lock);
+}
+
static void unregister_netdevice_move_net(struct net *net_old,
struct net *net,
struct net_device *dev)
@@ -13185,12 +13207,17 @@ static void __net_exit default_device_exit_batch(struct list_head *net_list)
__rtnl_net_unlock(&init_net);
list_for_each_entry(net, net_list, exit_list) {
+ __rtnl_net_lock(net);
+
for_each_netdev_reverse(net, dev) {
if (dev->rtnl_link_ops && dev->rtnl_link_ops->dellink)
dev->rtnl_link_ops->dellink(dev, &dev_kill_list);
else
unregister_netdevice_queue(dev, &dev_kill_list);
}
+
+ unregister_netdevice_queue_many_net(net, &dev_kill_list);
+ __rtnl_net_unlock(net);
}
unregister_netdevice_many(&dev_kill_list);
rtnl_unlock();
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index 578b48cf5318..a91d2b58aadd 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -181,6 +181,7 @@ static void ops_exit_rtnl_list(const struct list_head *ops_list,
ops->exit_rtnl(net, &dev_kill_list);
}
+ unregister_netdevice_queue_many_net(net, &dev_kill_list);
__rtnl_net_unlock(net);
}
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index 544498d3c325..b129f793d851 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -714,8 +714,12 @@ void rtnl_link_unregister(struct rtnl_link_ops *ops)
down_write(&pernet_ops_rwsem);
rtnl_lock_unregistering_all();
- for_each_net(net)
+ for_each_net(net) {
+ __rtnl_net_lock(net);
__rtnl_kill_links(net, ops, &dev_kill_list);
+ unregister_netdevice_queue_many_net(net, &dev_kill_list);
+ __rtnl_net_unlock(net);
+ }
unregister_netdevice_many(&dev_kill_list);
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net-next 08/14] veth: Support per-netns device unregistration.
From: Kuniyuki Iwashima @ 2026-07-03 0:09 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
In-Reply-To: <20260703001009.1572444-1-kuniyu@google.com>
Currently, veth_dellink() unregisters both local and peer devices
synchronously under RTNL.
Once RTNL is removed, it can be called concurrently from different
netns.
Let's use xchg() and unregister_netdevice_queue_net() to support
per-netns device unregistration.
This way, each device is queued for destruction only once by
the winner of the race.
Note that the extra netdev_hold() ensures that @peer obtained by
the first xchg() is not freed during the subsequent access to
netdev_priv(peer). The 2nd xchg() overwrites @dev to balance
the refcount.
Tested:
1. Create two veth pairs (veth1-2, veth3-4) between two netns
(ns1 & ns2).
# ip netns add ns1
# ip netns add ns2
# ip -n ns1 link add veth1 type veth peer veth2 netns ns2
# ip -n ns1 link add veth3 type veth peer veth4 netns ns2
2. Run bpftrace to check if the same process does NOT
unregister the paired veth devices
# bpftrace -e '#include <linux/netdevice.h>
kprobe:free_netdev {
$dev = (struct net_device *)arg0;
printf("PID: %d | DEV: %s%s\n", pid, $dev->name, kstack());
}'
3. Remove veth2 in ns2 and check bpftrace output
# ip -n ns2 link del veth2
PID: 2194 | DEV: veth2
free_netdev+5
netdev_run_todo+4798
rtnl_dellink+1507
rtnetlink_rcv_msg+1791
...
PID: 448 | DEV: veth1
free_netdev+5
netdev_run_todo+4798
process_scheduled_works+2538
...
4. Remove ns2 (thus veth4) and check bpftrace output
# ip netns del ns2
PID: 571 | DEV: veth4
free_netdev+5
netdev_run_todo+4798
default_device_exit_batch+2271
ops_undo_list+993
cleanup_net+1122
process_scheduled_works+2538
...
PID: 441 | DEV: veth3
free_netdev+5
netdev_run_todo+4798
process_scheduled_works+2538
...
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
drivers/net/veth.c | 34 +++++++++++++++++++++-------------
1 file changed, 21 insertions(+), 13 deletions(-)
diff --git a/drivers/net/veth.c b/drivers/net/veth.c
index 1c5142149175..8170bf33ccf9 100644
--- a/drivers/net/veth.c
+++ b/drivers/net/veth.c
@@ -77,6 +77,7 @@ struct veth_priv {
struct bpf_prog *_xdp_prog;
struct veth_rq *rq;
unsigned int requested_headroom;
+ netdevice_tracker peer_tracker;
};
struct veth_xdp_tx_bq {
@@ -1901,15 +1902,17 @@ static int veth_newlink(struct net_device *dev,
priv = netdev_priv(dev);
rcu_assign_pointer(priv->peer, peer);
+ netdev_hold(peer, &priv->peer_tracker, GFP_KERNEL);
err = veth_init_queues(dev, tb);
if (err)
goto err_queues;
priv = netdev_priv(peer);
rcu_assign_pointer(priv->peer, dev);
+ netdev_hold(dev, &priv->peer_tracker, GFP_KERNEL);
err = veth_init_queues(peer, tb);
if (err)
- goto err_queues;
+ goto err_peer_queues;
veth_disable_gro(dev);
/* update XDP supported features */
@@ -1918,7 +1921,11 @@ static int veth_newlink(struct net_device *dev,
return 0;
+err_peer_queues:
+ netdev_put(dev, &priv->peer_tracker);
+ priv = netdev_priv(dev);
err_queues:
+ netdev_put(peer, &priv->peer_tracker);
unregister_netdevice(dev);
err_register_dev:
/* nothing to do */
@@ -1933,24 +1940,25 @@ static int veth_newlink(struct net_device *dev,
static void veth_dellink(struct net_device *dev, struct list_head *head)
{
- struct veth_priv *priv;
+ netdevice_tracker *peer_tracker;
struct net_device *peer;
+ struct veth_priv *priv;
priv = netdev_priv(dev);
- peer = rtnl_dereference(priv->peer);
+ peer_tracker = &priv->peer_tracker;
+ peer = unrcu_pointer(xchg(&priv->peer, NULL));
+ if (!peer)
+ return;
- /* Note : dellink() is called from default_device_exit_batch(),
- * before a rcu_synchronize() point. The devices are guaranteed
- * not being freed before one RCU grace period.
- */
- RCU_INIT_POINTER(priv->peer, NULL);
unregister_netdevice_queue(dev, head);
- if (peer) {
- priv = netdev_priv(peer);
- RCU_INIT_POINTER(priv->peer, NULL);
- unregister_netdevice_queue(peer, head);
- }
+ priv = netdev_priv(peer);
+ dev = unrcu_pointer(xchg(&priv->peer, NULL));
+ if (dev)
+ unregister_netdevice_queue_net(dev_net(dev), peer, head);
+
+ netdev_put(peer, peer_tracker);
+ netdev_put(dev, &priv->peer_tracker);
}
static const struct nla_policy veth_policy[VETH_INFO_MAX + 1] = {
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net-next 09/14] bareudp: Protect bareudp_list with mutex.
From: Kuniyuki Iwashima @ 2026-07-03 0:09 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
In-Reply-To: <20260703001009.1572444-1-kuniyu@google.com>
struct bareudp_dev.net is the netns where the backend bareudp
socket resides.
struct bareudp_dev is linked to the bareudp_net.bareudp_list of
the socket's netns.
During netns dismantle or module unload, bareudp_exit_rtnl_net()
iterates the list and queues devices for destruction regardless
of the devices' netns.
Thus, once RTNL is removed, the list can be modified concurrently
from different netns due to device removal.
Let's protect it with per-netns mutex.
bareudp_newlink() is still protected by rtnl_net_lock()s, so
acquiring bn->lock twice in bareudp_find_dev() and
bareudp_configure() is not a problem.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
drivers/net/bareudp.c | 31 +++++++++++++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)
diff --git a/drivers/net/bareudp.c b/drivers/net/bareudp.c
index 5ef841c85526..7dedf4867e7b 100644
--- a/drivers/net/bareudp.c
+++ b/drivers/net/bareudp.c
@@ -36,6 +36,7 @@ static unsigned int bareudp_net_id;
struct bareudp_net {
struct list_head bareudp_list;
+ struct mutex lock;
};
struct bareudp_conf {
@@ -636,10 +637,15 @@ static struct bareudp_dev *bareudp_find_dev(struct bareudp_net *bn,
{
struct bareudp_dev *bareudp, *t = NULL;
+ mutex_lock(&bn->lock);
+
list_for_each_entry(bareudp, &bn->bareudp_list, next) {
if (conf->port == bareudp->port)
t = bareudp;
}
+
+ mutex_unlock(&bn->lock);
+
return t;
}
@@ -675,7 +681,10 @@ static int bareudp_configure(struct net *net, struct net_device *dev,
if (err)
return err;
+ mutex_lock(&bn->lock);
list_add(&bareudp->next, &bn->bareudp_list);
+ mutex_unlock(&bn->lock);
+
return 0;
}
@@ -692,7 +701,7 @@ static int bareudp_link_config(struct net_device *dev,
return 0;
}
-static void bareudp_dellink(struct net_device *dev, struct list_head *head)
+static void __bareudp_dellink(struct net_device *dev, struct list_head *head)
{
struct bareudp_dev *bareudp = netdev_priv(dev);
@@ -700,6 +709,18 @@ static void bareudp_dellink(struct net_device *dev, struct list_head *head)
unregister_netdevice_queue(dev, head);
}
+static void bareudp_dellink(struct net_device *dev, struct list_head *head)
+{
+ struct bareudp_dev *bareudp = netdev_priv(dev);
+ struct bareudp_net *bn;
+
+ bn = net_generic(bareudp->net, bareudp_net_id);
+
+ mutex_lock(&bn->lock);
+ __bareudp_dellink(dev, head);
+ mutex_unlock(&bn->lock);
+}
+
static int bareudp_newlink(struct net_device *dev,
struct rtnl_newlink_params *params,
struct netlink_ext_ack *extack)
@@ -776,6 +797,8 @@ static __net_init int bareudp_init_net(struct net *net)
struct bareudp_net *bn = net_generic(net, bareudp_net_id);
INIT_LIST_HEAD(&bn->bareudp_list);
+ mutex_init(&bn->lock);
+
return 0;
}
@@ -785,8 +808,12 @@ static void __net_exit bareudp_exit_rtnl_net(struct net *net,
struct bareudp_net *bn = net_generic(net, bareudp_net_id);
struct bareudp_dev *bareudp, *next;
+ mutex_lock(&bn->lock);
+
list_for_each_entry_safe(bareudp, next, &bn->bareudp_list, next)
- bareudp_dellink(bareudp->dev, dev_kill_list);
+ __bareudp_dellink(bareudp->dev, dev_kill_list);
+
+ mutex_unlock(&bn->lock);
}
static struct pernet_operations bareudp_net_ops = {
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net-next 10/14] bareudp: Support per-netns netdev unregistration.
From: Kuniyuki Iwashima @ 2026-07-03 0:09 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
In-Reply-To: <20260703001009.1572444-1-kuniyu@google.com>
bareudp_exit_rtnl_net() iterates bareudp devices whose sockets
are in the dying netns and queues them for destruction.
So the devices may reside in different netns.
Let's use unregister_netdevice_queue_net() to support per-netns
device unregistration.
list_del() is changed to list_del_init() to avoid queueing the
same device twice.
Even after bareudp_exit_rtnl_net() queues a cross-netns bareudp
device, bareudp_dellink() could be called concurrently for it
(once RTNL is removed). In such a case, __rtnl_net_unlock() will
perform the unregistration.
Note that bareudp uses register_pernet_subsys() instead of _device(),
so default_device_exit_batch() guarantees that the async per-netns
works are flushed before ->exit().
Tested:
1. Create bareudp device across two netns.
# ip netns add ns1
# ip netns add ns2
# ip -n ns1 link add bareudp0 link-netns ns2 type bareudp \
dstport 9292 ethertype ipv4
2. Run bpftrace to check that bareudp_uninit() is called between
->exit_rtnl() and ->exit().
# bpftrace -e '#include <linux/netdevice.h>
kprobe:bareudp_uninit {
$dev = (struct net_device *)arg0;
printf("PID: %d | DEV: %s%s\n", pid, $dev->name, kstack());
}
kprobe:bareudp_exit_rtnl_net,
kprobe:bareudp_exit_net {
printf("PID: %d%s\n", pid, kstack());
}'
3. Remove the netns where the bareudp socket resides
# ip netns del ns2
Now, we can see bareudp0 is unregistered by per-netns work
instead of cleanup_net() and it finishes before ->exit() to
avoid WARN_ON_ONCE(!list_empty(&bn->bareudp_list)) there.
PID: 576
bareudp_exit_rtnl_net+5
ops_undo_list+702
cleanup_net+1122
process_scheduled_works+2538
...
PID: 470 | DEV: bareudp0
bareudp_uninit+5
unregister_netdevice_many_notify+7129
unregister_netdevice_many_net+1050
rtnl_net_work_func+136
process_scheduled_works+2538
...
PID: 576
bareudp_exit_net+5
ops_undo_list+1064
cleanup_net+1122
process_scheduled_works+2538
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
drivers/net/bareudp.c | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/drivers/net/bareudp.c b/drivers/net/bareudp.c
index 7dedf4867e7b..c3b5ed52d877 100644
--- a/drivers/net/bareudp.c
+++ b/drivers/net/bareudp.c
@@ -701,12 +701,13 @@ static int bareudp_link_config(struct net_device *dev,
return 0;
}
-static void __bareudp_dellink(struct net_device *dev, struct list_head *head)
+static void __bareudp_dellink(struct net *net, struct net_device *dev,
+ struct list_head *head)
{
struct bareudp_dev *bareudp = netdev_priv(dev);
- list_del(&bareudp->next);
- unregister_netdevice_queue(dev, head);
+ list_del_init(&bareudp->next);
+ unregister_netdevice_queue_net(net, dev, head);
}
static void bareudp_dellink(struct net_device *dev, struct list_head *head)
@@ -717,7 +718,8 @@ static void bareudp_dellink(struct net_device *dev, struct list_head *head)
bn = net_generic(bareudp->net, bareudp_net_id);
mutex_lock(&bn->lock);
- __bareudp_dellink(dev, head);
+ if (!list_empty(&bareudp->next))
+ __bareudp_dellink(dev_net(dev), dev, head);
mutex_unlock(&bn->lock);
}
@@ -811,14 +813,22 @@ static void __net_exit bareudp_exit_rtnl_net(struct net *net,
mutex_lock(&bn->lock);
list_for_each_entry_safe(bareudp, next, &bn->bareudp_list, next)
- __bareudp_dellink(bareudp->dev, dev_kill_list);
+ __bareudp_dellink(net, bareudp->dev, dev_kill_list);
mutex_unlock(&bn->lock);
}
+static void __net_exit bareudp_exit_net(struct net *net)
+{
+ struct bareudp_net *bn = net_generic(net, bareudp_net_id);
+
+ WARN_ON_ONCE(!list_empty(&bn->bareudp_list));
+}
+
static struct pernet_operations bareudp_net_ops = {
.init = bareudp_init_net,
.exit_rtnl = bareudp_exit_rtnl_net,
+ .exit = bareudp_exit_net,
.id = &bareudp_net_id,
.size = sizeof(struct bareudp_net),
};
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net-next 11/14] ipvlan: Convert ipvl_port.count to refcount_t.
From: Kuniyuki Iwashima @ 2026-07-03 0:09 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
In-Reply-To: <20260703001009.1572444-1-kuniyu@google.com>
struct ipvl_port is shared between a lower device and its upper
ipvlan devices.
While each upper device can always access ipvl_port safely via
ipvlan_dev.port, the lower device relies on RTNL to access it
via net_device.rx_handler_data.
Once RTNL is removed, the lower device cannot read ipvl_port safely
in ipvlan_device_event() because the port could be freed concurrently
and net_device.rx_handler_data is set to NULL if the last ipvlan
device in another namespace is unregistered.
Let's convert ipvl_port.count to refcount_t and use RCU along with
refcount_inc_not_zero() in ipvlan_device_event().
netdev_put() in ipvlan_port_destroy() is also moved down after
cancel_work_sync(), which is the last user of port->dev.
Note that ipvlan->port is now set in ipvlan_init() so that it can
be used in ipvlan_uninit(), instead of ipvlan_port_get_rtnl()
(rtnl_dereference()).
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
drivers/net/ipvlan/ipvlan.h | 2 +-
drivers/net/ipvlan/ipvlan_main.c | 75 ++++++++++++++++++++++----------
2 files changed, 52 insertions(+), 25 deletions(-)
diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h
index 80f84fc87008..78f9107fa752 100644
--- a/drivers/net/ipvlan/ipvlan.h
+++ b/drivers/net/ipvlan/ipvlan.h
@@ -96,7 +96,7 @@ struct ipvl_port {
u16 dev_id_start;
struct work_struct wq;
struct sk_buff_head backlog;
- int count;
+ refcount_t count;
struct ida ida;
netdevice_tracker dev_tracker;
};
diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c
index ed46439a9f4e..b4906a8d24ef 100644
--- a/drivers/net/ipvlan/ipvlan_main.c
+++ b/drivers/net/ipvlan/ipvlan_main.c
@@ -86,6 +86,7 @@ static int ipvlan_port_create(struct net_device *dev)
goto err;
netdev_hold(dev, &port->dev_tracker, GFP_KERNEL);
+
return 0;
err:
@@ -93,16 +94,18 @@ static int ipvlan_port_create(struct net_device *dev)
return err;
}
-static void ipvlan_port_destroy(struct net_device *dev)
+static void ipvlan_port_destroy(struct ipvl_port *port)
{
- struct ipvl_port *port = ipvlan_port_get_rtnl(dev);
+ struct net_device *dev = port->dev;
struct sk_buff *skb;
- netdev_put(dev, &port->dev_tracker);
if (port->mode == IPVLAN_MODE_L3S)
ipvlan_l3s_unregister(port);
+
netdev_rx_handler_unregister(dev);
cancel_work_sync(&port->wq);
+ netdev_put(dev, &port->dev_tracker);
+
while ((skb = __skb_dequeue(&port->backlog)) != NULL) {
dev_put(skb->dev);
kfree_skb(skb);
@@ -111,6 +114,27 @@ static void ipvlan_port_destroy(struct net_device *dev)
kfree(port);
}
+static void ipvlan_port_put(struct ipvl_port *port)
+{
+ if (refcount_dec_and_test(&port->count))
+ ipvlan_port_destroy(port);
+}
+
+static struct ipvl_port *ipvlan_port_get(struct net_device *dev)
+{
+ struct ipvl_port *port = NULL;
+
+ rcu_read_lock();
+ if (netif_is_ipvlan_port(dev)) {
+ port = ipvlan_port_get_rcu(dev);
+ if (!refcount_inc_not_zero(&port->count))
+ port = NULL;
+ }
+ rcu_read_unlock();
+
+ return port;
+}
+
#define IPVLAN_ALWAYS_ON_OFLOADS \
(NETIF_F_SG | NETIF_F_HW_CSUM | \
NETIF_F_GSO_ROBUST | NETIF_F_GSO_SOFTWARE | NETIF_F_GSO_ENCAP_ALL)
@@ -159,24 +183,24 @@ static int ipvlan_init(struct net_device *dev)
free_percpu(ipvlan->pcpu_stats);
return err;
}
+ port = ipvlan_port_get_rtnl(phy_dev);
+ refcount_set(&port->count, 1);
+ } else {
+ port = ipvlan_port_get_rtnl(phy_dev);
+ refcount_inc(&port->count);
}
- port = ipvlan_port_get_rtnl(phy_dev);
- port->count += 1;
+
+ ipvlan->port = port;
+
return 0;
}
static void ipvlan_uninit(struct net_device *dev)
{
struct ipvl_dev *ipvlan = netdev_priv(dev);
- struct net_device *phy_dev = ipvlan->phy_dev;
- struct ipvl_port *port;
free_percpu(ipvlan->pcpu_stats);
-
- port = ipvlan_port_get_rtnl(phy_dev);
- port->count -= 1;
- if (!port->count)
- ipvlan_port_destroy(port->dev);
+ ipvlan_port_put(ipvlan->port);
}
static int ipvlan_open(struct net_device *dev)
@@ -594,9 +618,7 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,
if (err < 0)
return err;
- /* ipvlan_init() would have created the port, if required */
- port = ipvlan_port_get_rtnl(phy_dev);
- ipvlan->port = port;
+ port = ipvlan->port;
/* If the port-id base is at the MAX value, then wrap it around and
* begin from 0x1 again. This may be due to a busy system where lots
@@ -729,14 +751,13 @@ static int ipvlan_device_event(struct notifier_block *unused,
struct netdev_notifier_pre_changeaddr_info *prechaddr_info;
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct ipvl_dev *ipvlan, *next;
+ int err, ret = NOTIFY_DONE;
struct ipvl_port *port;
LIST_HEAD(lst_kill);
- int err;
-
- if (!netif_is_ipvlan_port(dev))
- return NOTIFY_DONE;
- port = ipvlan_port_get_rtnl(dev);
+ port = ipvlan_port_get(dev);
+ if (!port)
+ return ret;
switch (event) {
case NETDEV_UP:
@@ -788,8 +809,10 @@ static int ipvlan_device_event(struct notifier_block *unused,
err = netif_pre_changeaddr_notify(ipvlan->dev,
prechaddr_info->dev_addr,
extack);
- if (err)
- return notifier_from_errno(err);
+ if (err) {
+ ret = notifier_from_errno(err);
+ break;
+ }
}
break;
@@ -802,7 +825,8 @@ static int ipvlan_device_event(struct notifier_block *unused,
case NETDEV_PRE_TYPE_CHANGE:
/* Forbid underlying device to change its type. */
- return NOTIFY_BAD;
+ ret = NOTIFY_BAD;
+ break;
case NETDEV_NOTIFY_PEERS:
case NETDEV_BONDING_FAILOVER:
@@ -810,7 +834,10 @@ static int ipvlan_device_event(struct notifier_block *unused,
list_for_each_entry(ipvlan, &port->ipvlans, pnode)
call_netdevice_notifiers(event, ipvlan->dev);
}
- return NOTIFY_DONE;
+
+ ipvlan_port_put(port);
+
+ return ret;
}
/* the caller must held the addrs lock */
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net-next 12/14] ipvlan: Synchronise ipvlan_init() and ipvlan_uninit() for the same lower dev.
From: Kuniyuki Iwashima @ 2026-07-03 0:09 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
In-Reply-To: <20260703001009.1572444-1-kuniyu@google.com>
ipvlan_uninit() for the last ipvlan device resets the lower device's
rx_handler_data to NULL.
Once RTNL is removed, ipvlan_init() would race with ipvlan_uninit(),
which could leak a newly allocated ipvl_port.
ipvlan_init() ipvlan_uninit()
| |- if (refcount_dec_and_test(old_port))
... |- ipvlan_port_destroy(old_port)
| '
|- refcount_inc_not_zero(old_port) <-- fails
|- ipvlan_port_create(phy_dev) .
|- new_port = kzalloc() |
|- phy_dev->rx_handler_data = new_port
|- phy_dev->rx_handler_data = NULL
...
`- kfree(old_port);
Let's synchronise the two by holding the lower device's netdev_lock().
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
drivers/net/ipvlan/ipvlan_main.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c
index b4906a8d24ef..7adad781e9b5 100644
--- a/drivers/net/ipvlan/ipvlan_main.c
+++ b/drivers/net/ipvlan/ipvlan_main.c
@@ -177,9 +177,12 @@ static int ipvlan_init(struct net_device *dev)
if (!ipvlan->pcpu_stats)
return -ENOMEM;
+ netdev_lock(phy_dev);
+
if (!netif_is_ipvlan_port(phy_dev)) {
err = ipvlan_port_create(phy_dev);
if (err < 0) {
+ netdev_unlock(phy_dev);
free_percpu(ipvlan->pcpu_stats);
return err;
}
@@ -190,6 +193,8 @@ static int ipvlan_init(struct net_device *dev)
refcount_inc(&port->count);
}
+ netdev_unlock(phy_dev);
+
ipvlan->port = port;
return 0;
@@ -198,9 +203,19 @@ static int ipvlan_init(struct net_device *dev)
static void ipvlan_uninit(struct net_device *dev)
{
struct ipvl_dev *ipvlan = netdev_priv(dev);
+ netdevice_tracker dev_tracker;
+ struct net_device *phy_dev;
free_percpu(ipvlan->pcpu_stats);
+
+ phy_dev = ipvlan->phy_dev;
+ netdev_hold(phy_dev, &dev_tracker, GFP_KERNEL);
+ netdev_lock(phy_dev);
+
ipvlan_port_put(ipvlan->port);
+
+ netdev_unlock(phy_dev);
+ netdev_put(phy_dev, &dev_tracker);
}
static int ipvlan_open(struct net_device *dev)
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net-next 13/14] ipvlan: Protect ipvl_port.ipvlans with mutex.
From: Kuniyuki Iwashima @ 2026-07-03 0:09 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
In-Reply-To: <20260703001009.1572444-1-kuniyu@google.com>
struct ipvl_port is shared between a lower device and its upper
ipvlan devices.
All upper devices are linked to ipvl_port.ipvlans.
Once RTNL is removed, the list can be modified concurrently from
different netns due to device removal.
Let's protect it with a per-port mutex.
NETDEV_PRECHANGEUPPER and NETDEV_CHANGEUPPER are explicitly
skipped to avoid deadlock for netdev_upper_dev_unlink() called
from NETDEV_UNREGISTER.
Note that __ipvtap_dellink_ptr is added for CONFIG_IPVLAN=y
but CONFIG_TAP=m and CONFIG_IPVTAP=m.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
v2: Add __ipvtap_dellink_ptr for CONFIG_IPVLAN=y and CONFIG_TAP=m
---
drivers/net/ipvlan/ipvlan.h | 8 +++++-
drivers/net/ipvlan/ipvlan_main.c | 49 ++++++++++++++++++++++++++++----
drivers/net/ipvlan/ipvtap.c | 23 ++++++++++++---
3 files changed, 70 insertions(+), 10 deletions(-)
diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h
index 78f9107fa752..9d3835c14e5e 100644
--- a/drivers/net/ipvlan/ipvlan.h
+++ b/drivers/net/ipvlan/ipvlan.h
@@ -91,6 +91,7 @@ struct ipvl_port {
struct hlist_head hlhead[IPVLAN_HASH_SIZE];
spinlock_t addrs_lock; /* guards hash-table and addrs */
struct list_head ipvlans;
+ struct mutex pnodes_lock;
u16 mode;
u16 flags;
u16 dev_id_start;
@@ -168,7 +169,7 @@ void ipvlan_count_rx(const struct ipvl_dev *ipvlan,
unsigned int len, bool success, bool mcast);
int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,
struct netlink_ext_ack *extack);
-void ipvlan_link_delete(struct net_device *dev, struct list_head *head);
+void __ipvlan_link_delete(struct net_device *dev, struct list_head *head);
void ipvlan_link_setup(struct net_device *dev);
int ipvlan_link_register(struct rtnl_link_ops *ops);
#ifdef CONFIG_IPVLAN_L3S
@@ -207,4 +208,9 @@ static inline bool netif_is_ipvlan_port(const struct net_device *dev)
return rcu_access_pointer(dev->rx_handler) == ipvlan_handle_frame;
}
+#if IS_ENABLED(CONFIG_IPVTAP)
+extern void (*__ipvtap_dellink_ptr)(struct net_device *dev,
+ struct list_head *head);
+#endif
+
#endif /* __IPVLAN_H */
diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c
index 7adad781e9b5..6d7479a8a9c6 100644
--- a/drivers/net/ipvlan/ipvlan_main.c
+++ b/drivers/net/ipvlan/ipvlan_main.c
@@ -7,6 +7,12 @@
#include "ipvlan.h"
+#if IS_ENABLED(CONFIG_IPVTAP)
+void (*__ipvtap_dellink_ptr)(struct net_device *dev,
+ struct list_head *head);
+EXPORT_SYMBOL(__ipvtap_dellink_ptr);
+#endif
+
static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,
struct netlink_ext_ack *extack)
{
@@ -16,6 +22,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,
ASSERT_RTNL();
if (port->mode != nval) {
+ mutex_lock(&port->pnodes_lock);
+
list_for_each_entry(ipvlan, &port->ipvlans, pnode) {
flags = ipvlan->dev->flags;
if (nval == IPVLAN_MODE_L3 || nval == IPVLAN_MODE_L3S) {
@@ -40,6 +48,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,
ipvlan_l3s_unregister(port);
}
port->mode = nval;
+
+ mutex_unlock(&port->pnodes_lock);
}
return 0;
@@ -56,6 +66,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,
NULL);
}
+ mutex_unlock(&port->pnodes_lock);
+
return err;
}
@@ -76,6 +88,7 @@ static int ipvlan_port_create(struct net_device *dev)
INIT_HLIST_HEAD(&port->hlhead[idx]);
spin_lock_init(&port->addrs_lock);
+ mutex_init(&port->pnodes_lock);
skb_queue_head_init(&port->backlog);
INIT_WORK(&port->wq, ipvlan_process_multicast);
ida_init(&port->ida);
@@ -676,7 +689,10 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,
if (err)
goto unlink_netdev;
+ mutex_lock(&port->pnodes_lock);
list_add_tail_rcu(&ipvlan->pnode, &port->ipvlans);
+ mutex_unlock(&port->pnodes_lock);
+
netif_stacked_transfer_operstate(phy_dev, dev);
return 0;
@@ -690,7 +706,7 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,
}
EXPORT_SYMBOL_GPL(ipvlan_link_new);
-void ipvlan_link_delete(struct net_device *dev, struct list_head *head)
+void __ipvlan_link_delete(struct net_device *dev, struct list_head *head)
{
struct ipvl_dev *ipvlan = netdev_priv(dev);
struct ipvl_addr *addr, *next;
@@ -708,7 +724,16 @@ void ipvlan_link_delete(struct net_device *dev, struct list_head *head)
unregister_netdevice_queue(dev, head);
netdev_upper_dev_unlink(ipvlan->phy_dev, dev);
}
-EXPORT_SYMBOL_GPL(ipvlan_link_delete);
+EXPORT_SYMBOL(__ipvlan_link_delete);
+
+static void ipvlan_link_delete(struct net_device *dev, struct list_head *head)
+{
+ struct ipvl_dev *ipvlan = netdev_priv(dev);
+
+ mutex_lock(&ipvlan->port->pnodes_lock);
+ __ipvlan_link_delete(dev, head);
+ mutex_unlock(&ipvlan->port->pnodes_lock);
+}
void ipvlan_link_setup(struct net_device *dev)
{
@@ -770,10 +795,16 @@ static int ipvlan_device_event(struct notifier_block *unused,
struct ipvl_port *port;
LIST_HEAD(lst_kill);
+ if (event == NETDEV_PRECHANGEUPPER ||
+ event == NETDEV_CHANGEUPPER)
+ return ret;
+
port = ipvlan_port_get(dev);
if (!port)
return ret;
+ mutex_lock(&port->pnodes_lock);
+
switch (event) {
case NETDEV_UP:
case NETDEV_DOWN:
@@ -800,9 +831,15 @@ static int ipvlan_device_event(struct notifier_block *unused,
if (dev->reg_state != NETREG_UNREGISTERING)
break;
- list_for_each_entry_safe(ipvlan, next, &port->ipvlans, pnode)
- ipvlan->dev->rtnl_link_ops->dellink(ipvlan->dev,
- &lst_kill);
+ list_for_each_entry_safe(ipvlan, next, &port->ipvlans, pnode) {
+#if IS_ENABLED(CONFIG_IPVTAP)
+ if (ipvlan->dev->rtnl_link_ops != &ipvlan_link_ops)
+ __ipvtap_dellink_ptr(ipvlan->dev, &lst_kill);
+ else
+#endif
+ __ipvlan_link_delete(ipvlan->dev, &lst_kill);
+ }
+
unregister_netdevice_many(&lst_kill);
break;
@@ -850,6 +887,8 @@ static int ipvlan_device_event(struct notifier_block *unused,
call_netdevice_notifiers(event, ipvlan->dev);
}
+ mutex_unlock(&port->pnodes_lock);
+
ipvlan_port_put(port);
return ret;
diff --git a/drivers/net/ipvlan/ipvtap.c b/drivers/net/ipvlan/ipvtap.c
index 2d6bbddd1edd..99eaa29057b4 100644
--- a/drivers/net/ipvlan/ipvtap.c
+++ b/drivers/net/ipvlan/ipvtap.c
@@ -109,14 +109,24 @@ static int ipvtap_newlink(struct net_device *dev,
return err;
}
+static void __ipvtap_dellink(struct net_device *dev, struct list_head *head)
+{
+ struct ipvtap_dev *vlantap = netdev_priv(dev);
+
+ netdev_rx_handler_unregister(dev);
+ tap_del_queues(&vlantap->tap);
+ __ipvlan_link_delete(dev, head);
+}
+
static void ipvtap_dellink(struct net_device *dev,
struct list_head *head)
{
- struct ipvtap_dev *vlan = netdev_priv(dev);
+ struct ipvtap_dev *vlantap = netdev_priv(dev);
+ struct ipvl_port *port = vlantap->vlan.port;
- netdev_rx_handler_unregister(dev);
- tap_del_queues(&vlan->tap);
- ipvlan_link_delete(dev, head);
+ mutex_lock(&port->pnodes_lock);
+ __ipvtap_dellink(dev, head);
+ mutex_unlock(&port->pnodes_lock);
}
static void ipvtap_setup(struct net_device *dev)
@@ -198,6 +208,8 @@ static int __init ipvtap_init(void)
{
int err;
+ __ipvtap_dellink_ptr = __ipvtap_dellink;
+
err = tap_create_cdev(&ipvtap_cdev, &ipvtap_major, "ipvtap",
THIS_MODULE);
if (err)
@@ -224,6 +236,8 @@ static int __init ipvtap_init(void)
out2:
tap_destroy_cdev(ipvtap_major, &ipvtap_cdev);
out1:
+ __ipvtap_dellink_ptr = NULL;
+
return err;
}
module_init(ipvtap_init);
@@ -234,6 +248,7 @@ static void __exit ipvtap_exit(void)
unregister_netdevice_notifier(&ipvtap_notifier_block);
class_unregister(&ipvtap_class);
tap_destroy_cdev(ipvtap_major, &ipvtap_cdev);
+ __ipvtap_dellink_ptr = NULL;
}
module_exit(ipvtap_exit);
MODULE_ALIAS_RTNL_LINK("ipvtap");
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net-next 14/14] ipvlan: Support per-netns netdev unregistration.
From: Kuniyuki Iwashima @ 2026-07-03 0:09 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Andrew Lunn
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
In-Reply-To: <20260703001009.1572444-1-kuniyu@google.com>
When a lower device is unregistered, its upper ipvlan devices
must also be unregistered. However, these upper devices may
reside in different netns than the lower device.
Let's use unregister_netdevice_queue_net() to support per-netns
device unregistration for ipvlan.
The new dying flag in struct ipvl_dev is used to avoid a race
that ipvlan_link_delete() is called while its lower device is
being removed in ipvlan_device_event().
If dying is true in ipvlan_link_delete(), the ipvlan device is
already destructed but not yet unregistered. In this case,
unregistration will be done in __rtnl_net_unlock() of the
->dellink() caller.
Tested:
1. Create veth in ns1 and two ipvlan devices in ns2 and ns3.
# ip netns add ns1
# ip netns add ns2
# ip netns add ns3
# ip -n ns1 link add veth0 type veth peer veth1
# ip -n ns2 link add ipvl2 link veth0 link-netns ns1 type ipvlan mode l2
# ip -n ns3 link add ipvl3 link veth0 link-netns ns1 type ipvlan mode l2
2. Run bpftrace to check that veth is unregistered first but
wait ipvlan to be unregistered
# bpftrace -e '#include <linux/netdevice.h>
kprobe:ipvlan_uninit,
kprobe:veth_dellink,
kprobe:free_netdev {
$dev = (struct net_device *)arg0;
printf("PID: %d | DEV: %s%s\n", pid, $dev->name, kstack());
}'
3. Remove the lower veth0 in ns1.
# ip -n ns1 link del veth0
We can see that veth0 is freed after unregistering ipvl2 and ipvl3
in per-netns work because ipvl_port holds refcount of veth0.
PID: 2010 | DEV: veth0
veth_dellink+5
rtnl_dellink+1213
rtnetlink_rcv_msg+1791
...
PID: 440 | DEV: ipvl2
ipvlan_uninit+5
unregister_netdevice_many_notify+7129
unregister_netdevice_many_net+1050
rtnl_net_work_func+136
process_scheduled_works+2538
...
PID: 440 | DEV: ipvl2
free_netdev+5
netdev_run_todo+4798
process_scheduled_works+2538
...
PID: 440 | DEV: ipvl3
ipvlan_uninit+5
unregister_netdevice_many_notify+7129
unregister_netdevice_many_net+1050
rtnl_net_work_func+136
process_scheduled_works+2538
...
PID: 2010 | DEV: veth0
free_netdev+5
netdev_run_todo+4798
rtnl_dellink+1507
rtnetlink_rcv_msg+1791
...
PID: 440 | DEV: ipvl3
free_netdev+5
netdev_run_todo+4798
process_scheduled_works+2538
...
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
drivers/net/ipvlan/ipvlan.h | 6 ++++--
drivers/net/ipvlan/ipvlan_main.c | 22 ++++++++++++++--------
drivers/net/ipvlan/ipvtap.c | 8 +++++---
3 files changed, 23 insertions(+), 13 deletions(-)
diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h
index 9d3835c14e5e..8d05ad480438 100644
--- a/drivers/net/ipvlan/ipvlan.h
+++ b/drivers/net/ipvlan/ipvlan.h
@@ -69,6 +69,7 @@ struct ipvl_dev {
DECLARE_BITMAP(mac_filters, IPVLAN_MAC_FILTER_SIZE);
netdev_features_t sfeatures;
u32 msg_enable;
+ bool dying;
};
struct ipvl_addr {
@@ -169,7 +170,8 @@ void ipvlan_count_rx(const struct ipvl_dev *ipvlan,
unsigned int len, bool success, bool mcast);
int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,
struct netlink_ext_ack *extack);
-void __ipvlan_link_delete(struct net_device *dev, struct list_head *head);
+void __ipvlan_link_delete(struct net *net, struct net_device *dev,
+ struct list_head *head);
void ipvlan_link_setup(struct net_device *dev);
int ipvlan_link_register(struct rtnl_link_ops *ops);
#ifdef CONFIG_IPVLAN_L3S
@@ -209,7 +211,7 @@ static inline bool netif_is_ipvlan_port(const struct net_device *dev)
}
#if IS_ENABLED(CONFIG_IPVTAP)
-extern void (*__ipvtap_dellink_ptr)(struct net_device *dev,
+extern void (*__ipvtap_dellink_ptr)(struct net *net, struct net_device *dev,
struct list_head *head);
#endif
diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c
index 6d7479a8a9c6..ee46a55f73d1 100644
--- a/drivers/net/ipvlan/ipvlan_main.c
+++ b/drivers/net/ipvlan/ipvlan_main.c
@@ -8,7 +8,7 @@
#include "ipvlan.h"
#if IS_ENABLED(CONFIG_IPVTAP)
-void (*__ipvtap_dellink_ptr)(struct net_device *dev,
+void (*__ipvtap_dellink_ptr)(struct net *net, struct net_device *dev,
struct list_head *head);
EXPORT_SYMBOL(__ipvtap_dellink_ptr);
#endif
@@ -706,7 +706,8 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,
}
EXPORT_SYMBOL_GPL(ipvlan_link_new);
-void __ipvlan_link_delete(struct net_device *dev, struct list_head *head)
+void __ipvlan_link_delete(struct net *net, struct net_device *dev,
+ struct list_head *head)
{
struct ipvl_dev *ipvlan = netdev_priv(dev);
struct ipvl_addr *addr, *next;
@@ -721,7 +722,7 @@ void __ipvlan_link_delete(struct net_device *dev, struct list_head *head)
ida_free(&ipvlan->port->ida, dev->dev_id);
list_del_rcu(&ipvlan->pnode);
- unregister_netdevice_queue(dev, head);
+ unregister_netdevice_queue_net(net, dev, head);
netdev_upper_dev_unlink(ipvlan->phy_dev, dev);
}
EXPORT_SYMBOL(__ipvlan_link_delete);
@@ -731,7 +732,8 @@ static void ipvlan_link_delete(struct net_device *dev, struct list_head *head)
struct ipvl_dev *ipvlan = netdev_priv(dev);
mutex_lock(&ipvlan->port->pnodes_lock);
- __ipvlan_link_delete(dev, head);
+ if (!ipvlan->dying)
+ __ipvlan_link_delete(dev_net(dev), dev, head);
mutex_unlock(&ipvlan->port->pnodes_lock);
}
@@ -827,22 +829,26 @@ static int ipvlan_device_event(struct notifier_block *unused,
ipvlan_migrate_l3s_hook(oldnet, newnet);
break;
}
- case NETDEV_UNREGISTER:
+ case NETDEV_UNREGISTER: {
+ struct net *net = dev_net(dev);
+
if (dev->reg_state != NETREG_UNREGISTERING)
break;
list_for_each_entry_safe(ipvlan, next, &port->ipvlans, pnode) {
+ ipvlan->dying = true;
+
#if IS_ENABLED(CONFIG_IPVTAP)
if (ipvlan->dev->rtnl_link_ops != &ipvlan_link_ops)
- __ipvtap_dellink_ptr(ipvlan->dev, &lst_kill);
+ __ipvtap_dellink_ptr(net, ipvlan->dev, &lst_kill);
else
#endif
- __ipvlan_link_delete(ipvlan->dev, &lst_kill);
+ __ipvlan_link_delete(net, ipvlan->dev, &lst_kill);
}
unregister_netdevice_many(&lst_kill);
break;
-
+ }
case NETDEV_FEAT_CHANGE:
list_for_each_entry(ipvlan, &port->ipvlans, pnode) {
netif_inherit_tso_max(ipvlan->dev, dev);
diff --git a/drivers/net/ipvlan/ipvtap.c b/drivers/net/ipvlan/ipvtap.c
index 99eaa29057b4..66c949d94261 100644
--- a/drivers/net/ipvlan/ipvtap.c
+++ b/drivers/net/ipvlan/ipvtap.c
@@ -109,13 +109,14 @@ static int ipvtap_newlink(struct net_device *dev,
return err;
}
-static void __ipvtap_dellink(struct net_device *dev, struct list_head *head)
+static void __ipvtap_dellink(struct net *net, struct net_device *dev,
+ struct list_head *head)
{
struct ipvtap_dev *vlantap = netdev_priv(dev);
netdev_rx_handler_unregister(dev);
tap_del_queues(&vlantap->tap);
- __ipvlan_link_delete(dev, head);
+ __ipvlan_link_delete(net, dev, head);
}
static void ipvtap_dellink(struct net_device *dev,
@@ -125,7 +126,8 @@ static void ipvtap_dellink(struct net_device *dev,
struct ipvl_port *port = vlantap->vlan.port;
mutex_lock(&port->pnodes_lock);
- __ipvtap_dellink(dev, head);
+ if (!vlantap->vlan.dying)
+ __ipvtap_dellink(dev_net(dev), dev, head);
mutex_unlock(&port->pnodes_lock);
}
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* Re: [PATCH] net: phy: marvell: Add soft reset for 88E1510
From: Ben Brown @ 2026-07-03 0:14 UTC (permalink / raw)
To: Andrew Lunn
Cc: hkallweit1@gmail.com, linux@armlinux.org.uk, davem@davemloft.net,
edumazet@google.com, kuba@kernel.org, pabeni@redhat.com,
linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
Chris Packham
In-Reply-To: <3536a59f-405c-452a-a7d6-359c0ea1b151@lunn.ch>
On 7/3/26 11:04, Andrew Lunn wrote:
> On Fri, Jul 03, 2026 at 10:50:34AM +1200, Ben Brown wrote:
>> When bringing down then up the link on a 88e1512 phy a link is not
>> getting established. This is because the phy is coming out of reset then
>> immediately getting configured. During configuration the page is
>> unsuccessfully updated causing writes to the wrong registers.
>>
>> Add the soft reset function that does a reset then polling read waiting
>> for the phy to come back online, at which stage the page register can be
>> updated successfully.
>>
>> This was tested on a 88E1512 phy, using ip link to bring up/down the
>> link.
>
> What makes the 88E1512 special that it needs this, but no other
> Marvell PHY does?
>
> Andrew
This may be needed on the other marvell phys, but I only have access to
a 88E1512 phy so I am only updating what I have seen this on.
^ permalink raw reply
* Re: [PATCH net] af_unix: fix listen() succeeding on sockets in the wrong state
From: Cong Wang @ 2026-07-03 0:36 UTC (permalink / raw)
To: John Ericson
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Kuniyuki Iwashima, John Ericson, Simon Horman, Christian Brauner,
David Rheinsberg, Sergei Zimmerman, netdev, linux-kernel
In-Reply-To: <20260702202018.2280336-1-John.Ericson@Obsidian.Systems>
On Thu, Jul 02, 2026 at 04:20:15PM -0400, John Ericson wrote:
> From: John Ericson <mail@johnericson.me>
>
> Commit fd0a109a0f6b ("net, pidfs: prepare for handing out pidfds for
> reaped sk->sk_peer_pid") inserted a `prepare_peercred()` call between
> `err = -EINVAL` and the socket-state check in `unix_listen()`. Since
> `prepare_peercred()` leaves `err` at 0 on success, `listen()` on an
> AF_UNIX socket that is not in `TCP_CLOSE` or `TCP_LISTEN` state (e.g.
> one that is already connected) now silently returns success without
> doing anything, instead of failing with `EINVAL` as it did before.
Do you mind adding a selftest for this?
Thanks,
Cong
^ permalink raw reply
* Re: [PATCH v3] Subject: [PATCH] net: gro: fix double aggregation of flush-marked skbs
From: Shiming Cheng (成诗明) @ 2026-07-03 1:26 UTC (permalink / raw)
To: linux-kernel@vger.kernel.org, dsahern@kernel.org,
imv4bel@gmail.com, linux-mediatek@lists.infradead.org,
alice@isovalent.com, daniel.zahka@gmail.com,
eilaimemedsnaimel@gmail.com, nbd@nbd.name, horms@kernel.org,
kuba@kernel.org, willemb@google.com, pabeni@redhat.com,
edumazet@google.com, netdev@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, matthias.bgg@gmail.com,
davem@davemloft.net, AngeloGioacchino Del Regno,
sd@queasysnail.net
Cc: Lena Wang (王娜), stable@vger.kernel.org
In-Reply-To: <3f540a8a-4167-4727-9516-6fb91335333f@redhat.com>
On Thu, 2026-07-02 at 12:02 +0200, Paolo Abeni wrote:
> Note: the patch subject is quite uncorrected
>
> On 6/30/26 4:35 AM, Shiming Cheng wrote:
> > The new skb_gro_receive_list() function is missing a critical
> > safety check
> > present in the legacy skb_gro_receive() path. Specifically, it does
> > not
> > validate NAPI_GRO_CB(skb)->flush before allowing packet
> > aggregation.
>
> skb_gro_receive_list() is not very "new" and definitely
> skb_gro_receive() is not legacy.
>
The wording here may need to be adjusted. I'm referring to the
chronological order/which one came first.
Updated:
The skb_gro_receive_list() function is missing a critical safety check
that exists in the skb_gro_receive() implementation. Specifically, it
does not validate NAPI_GRO_CB(skb)->flush before allowing packet
aggregation
> > This allows already-GRO'd packets with existing frag_list to be
> > re-aggregated into a new GRO session, corrupting the frag_list
> > chain
> > structure. When skb_segment() attempts to unpack these malformed
> > packets,
> > it encounters invalid state and triggers a kernel panic.
> >
> > Scenario (Tethering/Device forwarding):
> > 1. Driver: Generated aggregated packet P1 via LRO with frag_list
> > 2. Dev A: Receives aggregated fraglist packet and flush flag set
> > 3. Dev A: Re-enters GRO, skb_gro_receive_list() is called
> > 4. Missing flush check allows re-aggregation despite flush flag
> > 5. Frag_list chain becomes corrupted (loops or dangling refs)
> > 6. Dev B: TX path calls skb_segment(), crashes on corrupted
> > frag_list
>
> I can't parse the above. Is this something that can happen with in-
> tree
> drivers or do you need OoT module to trigger it? In any case please
> clarify the actual order and the involved driver. Possibly a stack
> strace leading to the critical aggregation could help.
>
We are hitting a GRO/LRO-related failure in a tethering scenario.
On the RX path, the driver performs an LRO-style aggregation before
handing packets to the stack. When `nfrags` exceeds 17, additional
packets are no longer appended to the frags array, but are attached
through `skb_shared_info(skb)->frag_list`. After that, the driver still
passes the skb into `napi_gro_receive()`, so the same traffic goes
through a second aggregation stage in GRO.
In our tethering case, `NAPI_GRO_CB(skb)->is_flist = !sk`, so
`is_flist` becomes `true`, and the skb follows the `SKB_GSO_FRAGLIST`
path, eventually reaching `skb_gro_receive_list()`. The issue is that
some later skbs may already carry their own `frag_list` as a result of
the first aggregation done by the driver. When GRO links those skbs
again into a new `frag_list` chain, the resulting skb layout becomes
more complex than expected and eventually triggers the kernel
exception.
Actual skb relationships when the issue occurs is as follows.
A->frag_list = B
B->next = C
C->frag_list = D
In the observed layout, A already links `B -> C` through `frag_list`,
while C itself still carries its own `frag_list -> D`. In other words,
when GRO continues chaining skbs in `skb_gro_receive_list()`, the later
skb is no longer a simple standalone packet, but an skb that already
carries `shared_info->frag_list` from the driver-side LRO stage. This
creates a nested `frag_list` layout and eventually triggers the kernel
exception in our case.
> > Fix: Add NAPI_GRO_CB(skb)->flush validation to the early-return
> > check in
> > skb_gro_receive_list(), matching the defensive programming pattern
> > of
> > skb_gro_receive().
> >
> > Fixes: 8928756d53d5 ("net: add fraglist GRO/GSO support")
>
> The fix tag is wrong, should be:
>
> Fixes: 3a1296a38d0c ('net: Support GRO/GSO fraglist chaining.')
>
I will update it in the next patch.
> /P
>
^ permalink raw reply
* [PATCH net-next] net/mlx5e: bound TX CQ poll softirq residency with a time budget
From: Jose Fernandez (Anthropic) @ 2026-07-03 1:36 UTC (permalink / raw)
To: Saeed Mahameed, Tariq Toukan, Mark Bloch, Leon Romanovsky,
Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-rdma, linux-kernel, Jose Fernandez (Anthropic),
Ben Cressey
Under strict IOMMU invalidation (iommu.strict=1), each per-fragment DMA
unmap in the TX completion path issues a synchronous TLB invalidate and
waits for CMD_SYNC, spinning IRQ-off in the SMMU command queue. Under
cross-CPU command-queue contention this per-unmap cost inflates from
microseconds to hundreds of microseconds. mlx5e_poll_tx_cq()'s per-CQE
budget (128) does not bound time in this regime: one CQE can cover a
multi-WQE batch with many fragments, so a single poll invocation can
accumulate seconds of softirq residency and trip the soft-lockup
watchdog on arm64/SMMU-v3 systems.
Bound the invocation by time: check local_clock() every 8 CQEs against
a budget (default 500us; module parameter tx_cq_time_budget_us,
runtime-writable, 0 disables) and break out of the CQE loop when
exceeded, reporting busy exactly like the existing CQE-budget
exhaustion path so NAPI keeps the poll scheduled. Remaining
completions are delayed by one reschedule, never stranded. The inner
WQE walk is never interrupted mid-CQE (sqcc/dma_fifo_cc accounting).
A new ethtool statistic (tx_time_budget_exit) counts early exits.
Also add cond_resched() in mlx5e_free_txqsq_descs(): the teardown path
walks the same per-fragment unmaps in process context.
Tested on arm64 with SMMU-v3 under strict mode: throughput cost is
within run-to-run variance at every measured load shape; under active
invalidation-storm contention, the bounded poll measures 35-50%
faster than unbounded (bounded polling yields cores back to the
transmit path).
Assisted-by: Claude:unspecified
Signed-off-by: Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>
Reviewed-by: Ben Cressey <ben@cressey.dev>
---
drivers/net/ethernet/mellanox/mlx5/core/en_stats.c | 5 ++++
drivers/net/ethernet/mellanox/mlx5/core/en_stats.h | 2 ++
drivers/net/ethernet/mellanox/mlx5/core/en_tx.c | 29 +++++++++++++++++++++-
3 files changed, 35 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c
index 7f33261ba655..b940280af19d 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c
@@ -171,6 +171,7 @@ static const struct counter_desc sw_stats_desc[] = {
{ MLX5E_DECLARE_STAT(struct mlx5e_sw_stats, tx_cqes) },
{ MLX5E_DECLARE_STAT(struct mlx5e_sw_stats, tx_queue_wake) },
{ MLX5E_DECLARE_STAT(struct mlx5e_sw_stats, tx_cqe_err) },
+ { MLX5E_DECLARE_STAT(struct mlx5e_sw_stats, tx_time_budget_exit) },
{ MLX5E_DECLARE_STAT(struct mlx5e_sw_stats, tx_xdp_xmit) },
{ MLX5E_DECLARE_STAT(struct mlx5e_sw_stats, tx_xdp_mpwqe) },
{ MLX5E_DECLARE_STAT(struct mlx5e_sw_stats, tx_xdp_inlnw) },
@@ -426,6 +427,7 @@ static void mlx5e_stats_grp_sw_update_stats_sq(struct mlx5e_sw_stats *s,
s->tx_queue_wake += sq_stats->wake;
s->tx_queue_dropped += sq_stats->dropped;
s->tx_cqe_err += sq_stats->cqe_err;
+ s->tx_time_budget_exit += sq_stats->time_budget_exit;
s->tx_recover += sq_stats->recover;
s->tx_xmit_more += sq_stats->xmit_more;
s->tx_csum_partial_inner += sq_stats->csum_partial_inner;
@@ -2323,6 +2325,7 @@ static const struct counter_desc sq_stats_desc[] = {
{ MLX5E_DECLARE_TX_STAT(struct mlx5e_sq_stats, cqes) },
{ MLX5E_DECLARE_TX_STAT(struct mlx5e_sq_stats, wake) },
{ MLX5E_DECLARE_TX_STAT(struct mlx5e_sq_stats, cqe_err) },
+ { MLX5E_DECLARE_TX_STAT(struct mlx5e_sq_stats, time_budget_exit) },
};
static const struct counter_desc rq_xdpsq_stats_desc[] = {
@@ -2399,6 +2402,7 @@ static const struct counter_desc ptp_sq_stats_desc[] = {
{ MLX5E_DECLARE_PTP_TX_STAT(struct mlx5e_sq_stats, cqes) },
{ MLX5E_DECLARE_PTP_TX_STAT(struct mlx5e_sq_stats, wake) },
{ MLX5E_DECLARE_PTP_TX_STAT(struct mlx5e_sq_stats, cqe_err) },
+ { MLX5E_DECLARE_PTP_TX_STAT(struct mlx5e_sq_stats, time_budget_exit) },
};
static const struct counter_desc ptp_ch_stats_desc[] = {
@@ -2476,6 +2480,7 @@ static const struct counter_desc qos_sq_stats_desc[] = {
{ MLX5E_DECLARE_QOS_TX_STAT(struct mlx5e_sq_stats, cqes) },
{ MLX5E_DECLARE_QOS_TX_STAT(struct mlx5e_sq_stats, wake) },
{ MLX5E_DECLARE_QOS_TX_STAT(struct mlx5e_sq_stats, cqe_err) },
+ { MLX5E_DECLARE_QOS_TX_STAT(struct mlx5e_sq_stats, time_budget_exit) },
};
#define NUM_RQ_STATS ARRAY_SIZE(rq_stats_desc)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.h b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.h
index 09f155acb461..5ba954f42ccd 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.h
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.h
@@ -187,6 +187,7 @@ struct mlx5e_sw_stats {
u64 tx_cqes;
u64 tx_queue_wake;
u64 tx_cqe_err;
+ u64 tx_time_budget_exit;
u64 tx_xdp_xmit;
u64 tx_xdp_mpwqe;
u64 tx_xdp_inlnw;
@@ -445,6 +446,7 @@ struct mlx5e_sq_stats {
u64 cqes ____cacheline_aligned_in_smp;
u64 wake;
u64 cqe_err;
+ u64 time_budget_exit;
};
struct mlx5e_xdpsq_stats {
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_tx.c b/drivers/net/ethernet/mellanox/mlx5/core/en_tx.c
index 0b5e600e4a6a..994df912b765 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_tx.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_tx.c
@@ -43,6 +43,13 @@
#include "en_accel/macsec.h"
#include "en/ptp.h"
#include <net/ipv6.h>
+#include <linux/moduleparam.h>
+#include <linux/sched/clock.h>
+
+static unsigned int mlx5e_tx_cq_time_budget_us = 500;
+module_param_named(tx_cq_time_budget_us, mlx5e_tx_cq_time_budget_us, uint, 0644);
+MODULE_PARM_DESC(tx_cq_time_budget_us,
+ "Max microseconds one TX CQ poll may spend before yielding (0 = unbounded)");
static void mlx5e_dma_unmap_wqe_err(struct mlx5e_txqsq *sq, u8 num_dma)
{
@@ -760,9 +767,12 @@ void mlx5e_txqsq_wake(struct mlx5e_txqsq *sq)
bool mlx5e_poll_tx_cq(struct mlx5e_cq *cq, int napi_budget)
{
struct mlx5e_sq_stats *stats;
+ bool time_exceeded = false;
+ u64 time_budget_end = 0;
struct mlx5e_txqsq *sq;
struct mlx5_cqe64 *cqe;
u32 dma_fifo_cc;
+ u32 budget_us;
u32 nbytes;
u16 npkts;
u16 sqcc;
@@ -790,6 +800,10 @@ bool mlx5e_poll_tx_cq(struct mlx5e_cq *cq, int napi_budget)
/* avoid dirtying sq cache line every cqe */
dma_fifo_cc = sq->dma_fifo_cc;
+ budget_us = READ_ONCE(mlx5e_tx_cq_time_budget_us);
+ if (budget_us)
+ time_budget_end = local_clock() + (u64)budget_us * NSEC_PER_USEC;
+
i = 0;
do {
struct mlx5e_tx_wqe_info *wi;
@@ -842,8 +856,19 @@ bool mlx5e_poll_tx_cq(struct mlx5e_cq *cq, int napi_budget)
stats->cqe_err++;
}
+ /* Check between CQEs only (sqcc/dma_fifo_cc must advance together). */
+ if (unlikely(time_budget_end && (i & 7) == 7 &&
+ local_clock() >= time_budget_end)) {
+ time_exceeded = true;
+ i++;
+ break;
+ }
+
} while ((++i < MLX5E_TX_CQ_POLL_BUDGET) && (cqe = mlx5_cqwq_get_cqe(&cq->wq)));
+ if (unlikely(time_exceeded))
+ stats->time_budget_exit++;
+
stats->cqes += i;
mlx5_cqwq_update_db_record(&cq->wq);
@@ -858,7 +883,7 @@ bool mlx5e_poll_tx_cq(struct mlx5e_cq *cq, int napi_budget)
mlx5e_txqsq_wake(sq);
- return (i == MLX5E_TX_CQ_POLL_BUDGET);
+ return time_exceeded || (i == MLX5E_TX_CQ_POLL_BUDGET);
}
static void mlx5e_tx_wi_kfree_fifo_skbs(struct mlx5e_txqsq *sq, struct mlx5e_tx_wqe_info *wi)
@@ -879,6 +904,8 @@ void mlx5e_free_txqsq_descs(struct mlx5e_txqsq *sq)
dma_fifo_cc = sq->dma_fifo_cc;
while (sqcc != sq->pc) {
+ cond_resched();
+
ci = mlx5_wq_cyc_ctr2ix(&sq->wq, sqcc);
wi = &sq->db.wqe_info[ci];
---
base-commit: 08bc5b2636afcbadc31bb17243eec094e048bd79
change-id: 20260702-mlx5e-tx-cq-time-budget-02cccf37bf54
Best regards,
--
Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>
^ permalink raw reply related
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