* Re: [PATCH iwl-next v1 3/6] ixgbe: E610: do not disable LSE on driver down/remove
From: Simon Horman @ 2026-07-08 17:01 UTC (permalink / raw)
To: jedrzej.jagielski; +Cc: Simon Horman, intel-wired-lan, anthony.l.nguyen, netdev
In-Reply-To: <20260702091553.57112-4-jedrzej.jagielski@intel.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev
---
ixgbe: E610: do not disable LSE on driver down/remove
This patch removes the calls to ixgbe_disable_link_status_events() in
ixgbe_down() and ixgbe_remove() for the E610 MAC. The intent is to keep
Link Status Events (LSE) enabled when the interface is down or unloading.
> diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
> index 882021c436d0f..83098bf522e42 100644
> --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
> +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
[ ... ]
> @@ -6733,8 +6720,6 @@ void ixgbe_down(struct ixgbe_adapter *adapter)
>
> ixgbe_clean_all_tx_rings(adapter);
> ixgbe_clean_all_rx_rings(adapter);
> - if (adapter->hw.mac.type == ixgbe_mac_e610)
> - ixgbe_disable_link_status_events(adapter);
[Severity: High]
Does removing this call allow unhandled asynchronous LSE events to permanently
block the ACI mailbox?
If a link event occurs while the driver is down, the firmware can queue an
event in the ACI queue. Upon the next driver load, before the service task
can clear events, commands like ixgbe_get_flash_data() are called.
In ixgbe_aci_send_cmd_execute(), if the EV bit is asserted, the synchronous
response is overwritten by the unhandled asynchronous event from HIDA_2:
if ((hicr & IXGBE_PF_HICR_EV) && !(hicr & IXGBE_PF_HICR_C))
for (i = 0; i < IXGBE_ACI_DESC_SIZE_IN_DWORDS; i++)
raw_desc[i] = IXGBE_READ_REG(hw, IXGBE_PF_HIDA_2(i));
if (desc->opcode != cpu_to_le16(opcode) &&
opcode != ixgbe_aci_opc_get_fw_event)
return -EIO;
Could this result in an opcode mismatch and return -EIO for all future
commands, causing device initialization to fail?
> }
>
> /**
[ ... ]
> @@ -12080,9 +12065,6 @@ static void ixgbe_remove(struct pci_dev *pdev)
> set_bit(__IXGBE_REMOVING, &adapter->state);
> cancel_work_sync(&adapter->service_task);
>
> - if (adapter->hw.mac.type == ixgbe_mac_e610)
> - ixgbe_disable_link_status_events(adapter);
> -
[Severity: High]
Can the same mailbox blockage happen here during driver unload?
If LSE is not disabled on remove, a link state change (such as a cable
unplug) after unload could leave the ACI queue with pending events.
Would this cause the same -EIO failure during the subsequent ixgbe_probe()
when the driver is loaded again?
> if (adapter->mii_bus)
> mdiobus_unregister(adapter->mii_bus);
>
^ permalink raw reply
* Re: [PATCH net-next v5 1/4] dt-bindings: net: pse-pd: add bindings for Realtek/Broadcom PSE MCU
From: Conor Dooley @ 2026-07-08 16:56 UTC (permalink / raw)
To: Jonas Jelonek
Cc: Oleksij Rempel, Kory Maincent, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, netdev, devicetree,
linux-kernel, Daniel Golle, Bjørn Mork
In-Reply-To: <2afc9c9a-eacc-46ca-b965-4cabee8f7094@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 5761 bytes --]
On Tue, Jul 07, 2026 at 10:50:21PM +0200, Jonas Jelonek wrote:
> Hi Conor,
>
> On 07.07.26 19:25, Conor Dooley wrote:
> > On Mon, Jul 06, 2026 at 10:30:00PM +0200, Jonas Jelonek wrote:
> >> [...]
> >> The protocol and firmware on the MCU, most likely the whole "solution",
> >> is from Realtek. The setup is always the same on most Realtek-based
> >> switches (saying most because a few counterexamples use completely
> >> different setups, not even Broadcom or Realtek PSE silicon). The host
> >> interface is always the same (except for I2C vs. SMBus vs. UART, which
> >> is likely just a config in the MCU firmware). Therefore "realtek," is the
> >> right prefix for all of these.
> >>
> >> Broadcom is not really involved here except for their PSE silicon being
> >> used. Maybe Realtek modeled their MCU host protocol after the one that
> >> Broadcom PSE silicon uses as host interface, but this is rather guessing.
> >>
> >> Maybe a historical view might help. Older RTL83xx-based switches with
> >> PoE shipped with this setup using Broadcom PSE silicon. From what I know,
> >> at this point Realtek didn't design their own PSE silicon. They used the
> >> Broadcom silicon, put a MCU as a manager in front of it with their firmware
> >> and a host protocol based on what Broadcom PSE itself uses. At some
> >> point Realtek started to design their own PSE silicon which then was
> >> used in newer switches instead of Broadcom PSE.
> > Right, in that case it does make sense to use a realtek prefix, since
> > the software and mcu solution is all theirs.
> >
> >> [...]
> >> Only one at a time is used, but not combined in any way. All switches
> >> I've seen so far always have a single management MCU for PoE, not
> >> multiple. Thus, only a single variant is used. Which variant is used
> >> likely depends on the board vendor which then tells Realtek "I want your
> >> PoE solution, I can attach it via (I2C/SMBus/UART)". At least for UART vs.
> >> I2C/SMBus there are sometimes valid reasons to use UART over the other.
> >>
> >> There is only a single switch (from Linksys) where the MCU expects raw
> >> I2C messages. SMBus transaction fail actually. But I don't see the reason
> >> why Linksys did it that way. The reason can't be that the MCU is attached
> >> on a bit-banged I2C because another switch uses SMBus transaction on
> >> a bit-banged I2C.
> > Reading this, it feels like you "should" have compatibles that uniquely
> > identify the protocol used.
>
> Ok, I hope I put this together correctly. A concrete proposal:
>
> "realtek,pse-mcu-gen1" (Protocol Gen 1, UART)
> "realtek,pse-mcu-gen1-smbus" (Protocol Gen 1, SMBus)
> "realtek,pse-mcu-gen2" (Protocol Gen 2, UART)
> "realtek,pse-mcu-gen2-i2c" (Protocol Gen 2, raw I2C)
> "realtek,pse-mcu-gen2-smbus" (Protocol Gen 2, SMBus)
>
> This uniquely identifies the protocol used: first generation and second
> generation. As Rob mentioned before [1], this also pulls in the raw I2C
> vs. SMBus framing in contrast to having it in a property. The framing
> suffix appears only on I2C attachments because it doesn't apply to
> UART transport, and this is given by the parent serial@ node.
>
> Though I'm still open for suggestions regarding the protocol
> identification if "-gen1"/"-gen2" is not acceptable.
This seems reasonable enough.
> > Looking at the devices below, it seems like it
> > would be possible to use compatibles based on the switches themselves, e.g.
> > zyxel,xs1930-pse etc. If there are other devices that use the same
> > protocol, they could fall back to the ones below.
> >
> > It'd be good to have the net developers weigh in though, as to whether
> > using compatibles based on the switches is suitable.
>
> I'd lean against, but happy to defer to you and the net maintainers. The
> node describes the MCU with its Realtek firmware — the firmware/protocol
> defines the device. Everything that differs between instances on the
> controller level would be captured by the compatibles proposed above, so
> a board compatible would encode nothing there the gen+framing string
> doesn't.
>
> Observed variation lives on another level. For instance, some boards have
> heterogeneous per-port caps (e.g. 16 ports at 60W, 8 ports at 30W). This
> is clearly something that should be expressed per-pse-pi, not in a
> switch-specific compatible.
>
> It would also be an exception to the other PSE-PD bindings. They describe
> controllers used across many switches too, yet none encode the
The difference is those cases (for what few pse-psd bindings there are)
the compatibles correspond to individual devices. Here you have
compatibles you're going to use to cover multiple devices (with device
corresponding to a combination of mcu/firmware/hardware behind the mcu).
That lack of a 1:1 mapping is why I'm asking for something different from
you than you see with the existing pse-pd devices. The switch the device
is integrated on seems to be the only thing that reasonably makes sense
to use.
> switch/enclosure. Board-specific compatibles might still be added later in
> case a device really has a variation or quirk that genuinely needs its own
> compatible.
And in doing so, have to retrofit that compatible to all devicetrees
that use it. This is one of the reasons that we generally demand
device-specific compatibles.
You could add switch-specific compatibles that fall back to the ones you
provide above, with the driver only using the ones above unless
something crops up in the future?
Cheers,
Conor.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* [PATCH bpf v3 2/2] selftests/bpf: Test FIONREAD on a sockmap socket without a verdict program
From: Mattia Meleleo via B4 Relay @ 2026-07-08 16:55 UTC (permalink / raw)
To: bpf
Cc: netdev, John Fastabend, Jakub Sitnicki, Jiayuan Chen,
Kumar Kartikeya Dwivedi, Emil Tsalapatis, Mattia Meleleo
In-Reply-To: <20260708-fionread-no-verdict-v3-0-b4ee31b3af53@coralogix.com>
From: Mattia Meleleo <mattia.meleleo@coralogix.com>
Add a test validating that FIONREAD on a TCP socket in a sockmap
without a verdict program reports data pending in sk_receive_queue.
Reviewed-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Signed-off-by: Mattia Meleleo <mattia.meleleo@coralogix.com>
---
.../selftests/bpf/prog_tests/sockmap_basic.c | 39 ++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
index cb3229711..f0f368201 100644
--- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
+++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
@@ -1373,6 +1373,43 @@ static void test_sockmap_multi_channels(int sotype)
test_sockmap_pass_prog__destroy(skel);
}
+/* A socket in a sockmap without a verdict program keeps its ingress data
+ * in sk_receive_queue: FIONREAD must account for it.
+ */
+static void test_sockmap_no_verdict_fionread(void)
+{
+ int err, map, zero = 0, sent, avail;
+ int c0 = -1, c1 = -1, p0 = -1, p1 = -1;
+ struct test_sockmap_pass_prog *skel;
+ char buf[256] = "0123456789";
+
+ skel = test_sockmap_pass_prog__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+ map = bpf_map__fd(skel->maps.sock_map_rx);
+
+ err = create_socket_pairs(AF_INET, SOCK_STREAM, &c0, &c1, &p0, &p1);
+ if (!ASSERT_OK(err, "create_socket_pairs()"))
+ goto out;
+
+ err = bpf_map_update_elem(map, &zero, &c1, BPF_NOEXIST);
+ if (!ASSERT_OK(err, "bpf_map_update_elem(c1)"))
+ goto out_close;
+
+ sent = xsend(p1, &buf, sizeof(buf), 0);
+ ASSERT_EQ(sent, sizeof(buf), "xsend(p1)");
+ avail = wait_for_fionread(c1, sizeof(buf), IO_TIMEOUT_SEC);
+ ASSERT_EQ(avail, sizeof(buf), "ioctl(FIONREAD)");
+
+out_close:
+ close(c0);
+ close(p0);
+ close(c1);
+ close(p1);
+out:
+ test_sockmap_pass_prog__destroy(skel);
+}
+
void test_sockmap_basic(void)
{
if (test__start_subtest("sockmap create_update_free"))
@@ -1415,6 +1452,8 @@ void test_sockmap_basic(void)
test_sockmap_skb_verdict_shutdown();
if (test__start_subtest("sockmap skb_verdict fionread"))
test_sockmap_skb_verdict_fionread(true);
+ if (test__start_subtest("sockmap no_verdict fionread"))
+ test_sockmap_no_verdict_fionread();
if (test__start_subtest("sockmap skb_verdict fionread on drop"))
test_sockmap_skb_verdict_fionread(false);
if (test__start_subtest("sockmap skb_verdict change tail"))
--
Git-155)
^ permalink raw reply related
* [PATCH bpf v3 1/2] bpf, sockmap: Account for receive queue in FIONREAD without a verdict program
From: Mattia Meleleo via B4 Relay @ 2026-07-08 16:55 UTC (permalink / raw)
To: bpf
Cc: netdev, John Fastabend, Jakub Sitnicki, Jiayuan Chen,
Kumar Kartikeya Dwivedi, Emil Tsalapatis, Mattia Meleleo
In-Reply-To: <20260708-fionread-no-verdict-v3-0-b4ee31b3af53@coralogix.com>
From: Mattia Meleleo <mattia.meleleo@coralogix.com>
tcp_bpf_ioctl() answers SIOCINQ from psock->msg_tot_len, which only
counts bytes in ingress_msg. Without a stream/skb verdict program
nothing is diverted there: data stays in sk_receive_queue, so FIONREAD
returns 0 even though read() returns data.
Add tcp_inq() to the reported value when the psock has no verdict
program. The two queues are disjoint, so bytes redirected into
ingress_msg from other sockets stay correctly accounted through
msg_tot_len.
Remove unused sk_psock_msg_inq().
Fixes: 929e30f93125 ("bpf, sockmap: Fix FIONREAD for sockmap")
Reviewed-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Signed-off-by: Mattia Meleleo <mattia.meleleo@coralogix.com>
---
include/linux/skmsg.h | 14 --------------
net/ipv4/tcp_bpf.c | 17 ++++++++++++++++-
2 files changed, 16 insertions(+), 15 deletions(-)
diff --git a/include/linux/skmsg.h b/include/linux/skmsg.h
index a8553401b..d5e35f247 100644
--- a/include/linux/skmsg.h
+++ b/include/linux/skmsg.h
@@ -551,20 +551,6 @@ static inline void psock_progs_drop(struct sk_psock_progs *progs)
psock_set_prog(&progs->skb_verdict, NULL);
}
-/* for tcp only, sk is locked */
-static inline ssize_t sk_psock_msg_inq(struct sock *sk)
-{
- struct sk_psock *psock;
- ssize_t inq = 0;
-
- psock = sk_psock_get(sk);
- if (likely(psock)) {
- inq = sk_psock_get_msg_len_nolock(psock);
- sk_psock_put(sk, psock);
- }
- return inq;
-}
-
/* for udp only, sk is not locked */
static inline ssize_t sk_msg_first_len(struct sock *sk)
{
diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
index cc0bd73f3..8e905b50d 100644
--- a/net/ipv4/tcp_bpf.c
+++ b/net/ipv4/tcp_bpf.c
@@ -334,6 +334,7 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk,
static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)
{
+ struct sk_psock *psock;
bool slow;
if (cmd != SIOCINQ)
@@ -344,7 +345,21 @@ static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)
return -EINVAL;
slow = lock_sock_fast(sk);
- *karg = sk_psock_msg_inq(sk);
+ psock = sk_psock_get(sk);
+ if (unlikely(!psock)) {
+ unlock_sock_fast(sk, slow);
+ return tcp_ioctl(sk, cmd, karg);
+ }
+ *karg = sk_psock_get_msg_len_nolock(psock);
+ /* Without a verdict program, ingress data is never diverted to
+ * ingress_msg: it stays in sk_receive_queue and is read through
+ * the fallback to tcp_recvmsg(), so account for it like
+ * tcp_ioctl() does.
+ */
+ if (!READ_ONCE(psock->progs.stream_verdict) &&
+ !READ_ONCE(psock->progs.skb_verdict))
+ *karg += tcp_inq(sk);
+ sk_psock_put(sk, psock);
unlock_sock_fast(sk, slow);
return 0;
--
Git-155)
^ permalink raw reply related
* [PATCH bpf v3 0/2] bpf, sockmap: Fix FIONREAD for sockets without a verdict program
From: Mattia Meleleo via B4 Relay @ 2026-07-08 16:54 UTC (permalink / raw)
To: bpf
Cc: netdev, John Fastabend, Jakub Sitnicki, Jiayuan Chen,
Kumar Kartikeya Dwivedi, Emil Tsalapatis, Mattia Meleleo
Sockets added to a sockmap/sockhash with no stream/skb verdict program
attached answer FIONREAD with 0 even when unread data is pending in
sk_receive_queue. Fix tcp_bpf_ioctl() to account for the receive queue
in that case, and add a selftest.
Changes in v3:
- Remove unused sk_psock_msg_inq()
- Link to v2: https://patch.msgid.link/20260708-fionread-no-verdict-v2-0-29dd293621c7@coralogix.com
Changes in v2:
- Split the fix and the selftest into separate patches
- Use READ_ONCE() to read the verdict program pointers
- Link to v1: https://patch.msgid.link/20260707-fionread-no-verdict-v1-1-ce94a72357ec@coralogix.com
Signed-off-by: Mattia Meleleo <mattia.meleleo@coralogix.com>
---
Mattia Meleleo (2):
bpf, sockmap: Account for receive queue in FIONREAD without a verdict program
selftests/bpf: Test FIONREAD on a sockmap socket without a verdict program
include/linux/skmsg.h | 14 --------
net/ipv4/tcp_bpf.c | 17 +++++++++-
.../selftests/bpf/prog_tests/sockmap_basic.c | 39 ++++++++++++++++++++++
3 files changed, 55 insertions(+), 15 deletions(-)
---
base-commit: d2c9a99135da931377240942d44f3dea104cedb8
change-id: 20260707-fionread-no-verdict-a4f8697ac9f9
Best regards,
--
Mattia Meleleo <mattia.meleleo@coralogix.com>
^ permalink raw reply
* Re: [PATCH net] tipc: fix u16 MTU truncation in media and bearer MTU validation
From: Cen Zhang (Microsoft) @ 2026-07-08 16:53 UTC (permalink / raw)
To: vadim.fedorenko
Cc: AutonomousCodeSecurity, blbllhy, davem, edumazet, horms, jmaloy,
kuba, kys, linux-kernel, netdev, pabeni, tgopinath,
tipc-discussion
In-Reply-To: <04feb35c-ab30-4a12-ae19-a160b451c14a@linux.dev>
Thanks for the suggestion. I'll use NLA_POLICY_MAX(NLA_U32, U16_MAX)
in tipc_nl_prop_policy and drop the manual upper bound checks in v3.
^ permalink raw reply
* Re: [PATCH net] vhost-net: fix TX stall when vhost owns virtio-net header
From: Michael S. Tsirkin @ 2026-07-08 16:50 UTC (permalink / raw)
To: enrico.zanda
Cc: jasowangio, virtualization, netdev, kuba, kvm, linux-kernel,
eperezma, nd
In-Reply-To: <20260708152242.2268848-1-enrico.zanda@arm.com>
On Wed, Jul 08, 2026 at 04:22:42PM +0100, enrico.zanda@arm.com wrote:
> From: Enrico Zanda <enrico.zanda@arm.com>
>
> When vhost owns the virtio-net header, i.e. when
> VHOST_NET_F_VIRTIO_NET_HDR is negotiated, sock_hlen is 0,
> meaning that no header will be forwarded to the TAP device.
>
> In the current vhost_net_build_xdp() implementation,
> when sock_hlen == 0, the gso pointer can point at the start of the
> Ethernet frame instead of a virtio-net header.
> This results in a wrong interpretation of the destination MAC address
> bytes as struct virtio_net_hdr fields.
>
> This can, for some MAC addresses, trigger -EINVAL and return early
> before the TX descriptor is completed, which can stall vhost-net TX.
>
> Before 97b2409f28e0, the gso pointer was set to the zeroed padding area,
> using it as a synthetic virtio-net header. Restore that behavior.
>
> Fixes: 97b2409f28e0 ("vhost-net: reduce one userspace copy when building XDP buff")
> Signed-off-by: Enrico Zanda <enrico.zanda@arm.com>
The fix looks good:
Acked-by: Michael S. Tsirkin <mst@redhat.com>
Sashiko thinks there's something something security here, but I think
it is misguided. It's just guest hurting itself. driver breaks the
device it gets to keep both pieces.
> ---
> drivers/vhost/net.c | 8 +++++---
> 1 file changed, 5 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c
> index 77b59f49bddb..3e72b9c6af0c 100644
> --- a/drivers/vhost/net.c
> +++ b/drivers/vhost/net.c
> @@ -731,10 +731,12 @@ static int vhost_net_build_xdp(struct vhost_net_virtqueue *nvq,
> goto err;
> }
>
> - gso = buf + pad - sock_hlen;
> -
> - if (!sock_hlen)
> + if (!sock_hlen) {
> memset(buf, 0, pad);
> + gso = buf;
> + } else {
> + gso = buf + pad - sock_hlen;
> + }
>
> if ((gso->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) &&
> vhost16_to_cpu(vq, gso->csum_start) +
> --
> 2.43.0
^ permalink raw reply
* Re: [PATCH net] net/iucv: take a reference on the socket found in afiucv_hs_rcv()
From: Hidayathulla Khan I @ 2026-07-08 16:45 UTC (permalink / raw)
To: hexlabsecurity, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S. Miller, Thorsten Winkler, Alexandra Winter, pasic
Cc: Nagamani PV, Simon Horman, netdev, linux-kernel, linux-s390,
aswin
In-Reply-To: <20260705-b4-disp-fc79c0dc-v1-1-d2cdcb57afa9@proton.me>
Hi Bryam,
Patch looks correct. The fix properly pins the socket found in
afiucv_hs_rcv()
before dropping iucv_sk_list.lock, preventing a concurrent close from
freeing
it before the handlers run.
The sock_hold() and sock_put() placement and guards are correct.
Reviewed-by: Hidayath Khan <hidayath@linux.ibm.com>
On 06/07/26 8:54 am, Bryam Vargas via B4 Relay wrote:
> From: Bryam Vargas <hexlabsecurity@proton.me>
>
> afiucv_hs_rcv() looks up the destination socket under iucv_sk_list.lock,
> drops the lock, and then passes the socket to the afiucv_hs_callback_*()
> handlers without holding a reference. AF_IUCV sockets are not
> RCU-protected and are freed synchronously by iucv_sock_kill() ->
> sock_put(), so a concurrent close can free the socket in the window
> between read_unlock() and the handler, which then dereferences freed
> memory (for example sk->sk_data_ready() in afiucv_hs_callback_syn()).
>
> Take a reference with sock_hold() while the socket is still on the list
> and release it with sock_put() once the handler has run.
>
> Fixes: 3881ac441f64 ("af_iucv: add HiperSockets transport")
> Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
> ---
> afiucv_netdev_event() has the same iucv_sk_list use-after-free and is
> being fixed separately by Nagamani PV:
> https://lore.kernel.org/all/20260508170534.2208812-1-nagamani@linux.ibm.com/
> This patch covers the receive path, afiucv_hs_rcv(), which that fix does
> not touch.
>
> Verified with an LKMM/herd7 litmus (the missing sock_hold() is the only
> delta between a flagged data race and none) and with an in-kernel KASAN
> model that reproduces the afiucv_hs_rcv() lookup-without-hold path (the
> freed socket's sk_data_ready pointer is read after the callback runs);
> adding the reference clears it. af_iucv is s390-only, so this is the
> model rather than the driver. Reproducer available on request.
> ---
> net/iucv/af_iucv.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c
> index fed240b453bd..b85fb9767dec 100644
> --- a/net/iucv/af_iucv.c
> +++ b/net/iucv/af_iucv.c
> @@ -2089,6 +2089,8 @@ static int afiucv_hs_rcv(struct sk_buff *skb, struct net_device *dev,
> }
> }
> }
> + if (sk)
> + sock_hold(sk);
> read_unlock(&iucv_sk_list.lock);
> if (!iucv)
> sk = NULL;
> @@ -2138,6 +2140,8 @@ static int afiucv_hs_rcv(struct sk_buff *skb, struct net_device *dev,
> kfree_skb(skb);
> }
>
> + if (sk)
> + sock_put(sk);
> return err;
> }
>
>
> ---
> base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
> change-id: 20260705-b4-disp-fc79c0dc-019670262472
>
> Best regards,
^ permalink raw reply
* Re: [PATCH RESEND 1/1] macvlan: allow source mode devices along with passthru
From: Simon Horman @ 2026-07-08 16:43 UTC (permalink / raw)
To: t.martitz
Cc: Simon Horman, andrew+netdev, davem, edumazet, kuba, pabeni,
netdev, linux-kernel
In-Reply-To: <20260702065642.1783764-2-t.martitz@fritz.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev
---
macvlan: allow source mode devices along with passthru
This allows for configurations where there are a few
known senders in the system (e.g. multiple SoCs on the same
board) along with unlimited external senders.
> diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c
> index c40fa331836bb..d28f9d905a84d 100644
> --- a/drivers/net/macvlan.c
> +++ b/drivers/net/macvlan.c
[ ... ]
> @@ -1523,10 +1514,30 @@ int macvlan_common_newlink(struct net_device *dev,
> if (data && data[IFLA_MACVLAN_FLAGS])
> vlan->flags = nla_get_u16(data[IFLA_MACVLAN_FLAGS]);
>
> + /* Only 1 macvlan device can be created in passthru mode. There may be
> + * additional source mode devices but nothing else at the moment.
> + *
> + * First check if adding a source mode device to an existing passthru vlan.
> + */
> + if (macvlan_passthru(port) && vlan->mode != MACVLAN_MODE_SOURCE) {
> + /* The macvlan port must be not created this time,
> + * still goto destroy_macvlan_port for readability.
> + */
> + err = -EINVAL;
> + goto destroy_macvlan_port;
> + }
[Severity: High]
While macvlan_common_newlink() prevents creating non-SOURCE devices on a
passthru port, macvlan_changelink() does not seem to have a similar check.
Can a user bypass this restriction by creating a SOURCE device and then using
changelink to modify its mode to VEPA, bypassing MAC isolation?
[Severity: High]
With source devices and passthru devices now coexisting on the same port,
functions like macvlan_open(), macvlan_stop(), and macvlan_sync_address()
check the port-wide macvlan_passthru(vlan->port) flag instead of the device
mode.
If a user brings down a SOURCE device on a passthru port, will macvlan_stop()
incorrectly take the passthru teardown path? This appears to skip dev_uc_del()
and call dev_set_promiscuity(..., -1) for a device that never incremented it.
> +
> + /* Now check if adding a passthru device to an existing set of source mode
> + * devices.
> + */
> if (vlan->mode == MACVLAN_MODE_PASSTHRU) {
> - if (port->count) {
> - err = -EINVAL;
> - goto destroy_macvlan_port;
> + struct macvlan_dev *p;
> +
> + list_for_each_entry(p, &port->vlans, list) {
> + if (p->mode != MACVLAN_MODE_SOURCE) {
> + err = -EINVAL;
> + goto destroy_macvlan_port;
> + }
> }
> macvlan_set_passthru(port);
[Severity: High]
If a PASSTHRU device is deleted while SOURCE devices still exist on the port,
macvlan_uninit() decrements port->count but does not clear MACVLAN_F_PASSTHRU.
Does this leave the port in a permanently broken state where new PASSTHRU
devices cannot be created, and remaining SOURCE devices continue to be
mistakenly treated as passthru?
> eth_hw_addr_inherit(dev, lowerdev);
^ permalink raw reply
* Re: [PATCH V3 3/4] net: xilinx: axienet: Derive RX frame length from DMA residue
From: Pandey, Radhey Shyam @ 2026-07-08 16:41 UTC (permalink / raw)
To: Srinivas Neeli, Vinod Koul, Radhey Shyam Pandey
Cc: Frank Li, Michal Simek, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Suraj Gupta,
Marek Vasut, Tomi Valkeinen, Alex Bereza, Folker Schwesinger,
dmaengine, netdev, linux-arm-kernel, linux-kernel, git
In-Reply-To: <20260708100652.603074-4-srinivas.neeli@amd.com>
rephrase to - derive RX frame length from residue in dmaengine path
> The dmaengine RX path determined the received frame length by reading APP
> word 4 of the DMA descriptor metadata, masking the lower 16 bits of
> app_metadata[LEN_APP].
Avoid above explanation and make commit description concise.>
> This relies on the optional AXI4-Stream status/control interface being
> present in the design. The descriptor APP fields are only populated by the
> hardware when that interface is enabled. On designs without it the APP
> fields are not updated, so the length read back is invalid.
>
> The AXI DMA engine already reports how many bytes it wrote into the buffer
> through the standard dmaengine residue mechanism
> (dmaengine_result.residue). The received frame length is therefore the
> posted buffer length minus the residue, which is independent of the
> status/control interface and correct across all designs, including
> multi-descriptor frames where the residue is summed over the chain.
>
> Use result->residue to compute the RX frame length and drop the descriptor
> metadata lookup, which was only used for this purpose. The error path now
> uses the standard dmaengine_result.result status instead of the metadata
> pointer return value, and the now-unused LEN_APP macro is removed.
now unused>
> The transmit path is unaffected. It still passes APP metadata for checksum
> offload and derives its length from the skb.
>
Switching to dmaengine residue is better alternative but consider it as
an enhancement. Drop the fixes tag. The non-dmaengine axienet RX path
still derives frame length from APP field and it's a design assumption.
> Fixes: 6a91b846af85 ("net: axienet: Introduce dmaengine support")
> Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
> ---
> Changes in V3:
> - New patch in this series.
> - This patch enables axienet to work on designs where the AXI4-Stream
> status/control interface is not present. By using the standard
> dmaengine residue mechanism, the driver no longer depends on APP
> fields being populated by hardware.
> - This approach replaces the V2 xferred_bytes mechanism (V2 patch 5/5),
> making the dt-bindings patch (V2 patch 4/5) for xlnx,include-stscntrl-strm
> also unnecessary. Both V2 patches are dropped in this series.
> ---
> drivers/net/ethernet/xilinx/xilinx_axienet_main.c | 14 +++++---------
> 1 file changed, 5 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
> index fcf517069d16..67d1b8e91d68 100644
> --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
> +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c
> @@ -53,7 +53,6 @@
> #define TX_BD_NUM_MAX 4096
> #define RX_BD_NUM_MAX 4096
> #define DMA_NUM_APP_WORDS 5
> -#define LEN_APP 4
> #define RX_BUF_NUM_DEFAULT 128
>
> /* Must be shorter than length of ethtool_drvinfo.driver field to fit */
> @@ -1159,29 +1158,26 @@ axienet_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> static void axienet_dma_rx_cb(void *data, const struct dmaengine_result *result)
> {
> struct skbuf_dma_descriptor *skbuf_dma;
> - size_t meta_len, meta_max_len, rx_len;
> struct axienet_local *lp = data;
> struct sk_buff *skb;
> - u32 *app_metadata;
> + size_t rx_len;
> int i;
>
> skbuf_dma = axienet_get_rx_desc(lp, lp->rx_ring_tail++);
> skb = skbuf_dma->skb;
> - app_metadata = dmaengine_desc_get_metadata_ptr(skbuf_dma->desc, &meta_len,
> - &meta_max_len);
> dma_unmap_single(lp->dev, skbuf_dma->dma_address, lp->max_frm_size,
> DMA_FROM_DEVICE);
>
> - if (IS_ERR(app_metadata)) {
> + if (result->result != DMA_TRANS_NOERROR) {
> if (net_ratelimit())
> - netdev_err(lp->ndev, "Failed to get RX metadata pointer\n");
> + netdev_err(lp->ndev, "RX DMA transfer failed\n");
> dev_kfree_skb_any(skb);
> lp->ndev->stats.rx_dropped++;
> goto rx_submit;
> }
>
> - /* TODO: Derive app word index programmatically */
> - rx_len = (app_metadata[LEN_APP] & 0xFFFF);
> + /* Actual length = posted buffer length - residue. */
> + rx_len = lp->max_frm_size - result->residue;
> skb_put(skb, rx_len);
> skb->protocol = eth_type_trans(skb, lp->ndev);
> skb->ip_summed = CHECKSUM_NONE;
^ permalink raw reply
* [PATCH net] pds_core: reject component parameter in legacy firmware update
From: Nikhil P. Rao @ 2026-07-08 16:36 UTC (permalink / raw)
To: netdev
Cc: kuba, brett.creeley, eric.joyner, andrew+netdev, davem, edumazet,
pabeni, Nikhil P. Rao
The legacy firmware update path does not support per-component updates.
If a user specifies a component parameter with devlink flash, reject
the request with -EOPNOTSUPP rather than silently ignoring the component
parameter and flashing the entire firmware image.
Fixes: 49ce92fbee0b ("pds_core: add FW update feature to devlink")
Signed-off-by: Nikhil P. Rao <nikhil.rao@amd.com>
---
drivers/net/ethernet/amd/pds_core/devlink.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c
index 2ea97e1c5939..8adae7b18898 100644
--- a/drivers/net/ethernet/amd/pds_core/devlink.c
+++ b/drivers/net/ethernet/amd/pds_core/devlink.c
@@ -90,6 +90,12 @@ int pdsc_dl_flash_update(struct devlink *dl,
{
struct pdsc *pdsc = devlink_priv(dl);
+ if (params->component) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Component update not supported by this device");
+ return -EOPNOTSUPP;
+ }
+
return pdsc_firmware_update(pdsc, params->fw, extack);
}
--
2.43.0
^ permalink raw reply related
* Re: [PATCH] net: use sync wakeups for socket error reports
From: Eric Dumazet @ 2026-07-08 16:32 UTC (permalink / raw)
To: Usama Arif
Cc: Breno Leitao, davem, horms, kuba, kuniyu, linux-kernel, netdev,
pabeni, willemb, shakeel.butt, hannes, riel, kernel-team
In-Reply-To: <bc16e45e-32ef-45d8-99af-14f088733ab3@linux.dev>
On Wed, Jul 8, 2026 at 6:09 PM Usama Arif <usama.arif@linux.dev> wrote:
>
>
>
> On 08/07/2026 16:25, Breno Leitao wrote:
> > On Wed, Jul 08, 2026 at 06:38:15AM -0700, Usama Arif wrote:
> >> Measured on a 176-core EPYC 9D64 host running a Meta production
> >> workload, bpftrace on tracepoint:ipi:ipi_send_cpu with a kstack filter
> >> attributed the sock_def_error_report -> ep_poll_callback ->
> >> try_to_wake_up -> ttwu_queue_wakelist -> __smp_call_single_queue
> >> chain to 16,326 IPIs/min.
> >
> > I am interested in why so many sock_def_error_report().
> >
> > That's seems a lot for genuine socket errors (RST/ICMP) on a healthy
> > host, so I suspect these aren't errors at all?
> >
> > Can you share the full stack above sock_def_error_report()?
>
>
> I ran this bpftrace script the host now (results added at the end):
>
> sudo bpftrace -e '
> kprobe:sock_def_error_report
> {
> @wake_src[kstack()] = count();
> }
> interval:s:60
> {
> print(@wake_src, 5);
> exit();
> }'
>
>
> The biggest source is tcp_sendmsg -> __skb_tstamp_tx, which as you said
> is not an actual error. __skb_tstamp_tx clones the outgoing skb, tags it
> with ee_origin = SO_EE_ORIGIN_TIMESTAMPING and ee_errno = ENOMSG, enqueues
> it on sk->sk_error_queue via sock_queue_err_skb, and calls sk_error_report
> so epoll raises EPOLLERR. Userspace then reads it with recvmsg(MSG_ERRQUEUE)
> to get the SND/ACK timestamp.
>
> So the workload has SO_TIMESTAMPING enabled on its TCP sockets, and every
> packet completion and every ACK triggers a timestamp delivery through the
> error-queue path, which is why sock_def_error_report fires.
>
It seems we can not please everyone.
https://lore.kernel.org/netdev/20260526063650.952-1-xuewen.yan@unisoc.com/
Perhaps this SYNC heuristic should be a per-socket choice so that
applications can decide what is best for them.
^ permalink raw reply
* Re: [PATCH bpf] bpf, sockmap: Account for receive queue in FIONREAD without a verdict program
From: Emil Tsalapatis @ 2026-07-08 16:19 UTC (permalink / raw)
To: Kumar Kartikeya Dwivedi, Emil Tsalapatis, mattia.meleleo,
John Fastabend, Jakub Sitnicki, Jiayuan Chen
Cc: netdev, bpf
In-Reply-To: <DJSWX1VY6XXI.1XRGI8HMHT6JT@gmail.com>
On Wed Jul 8, 2026 at 12:41 AM EDT, Kumar Kartikeya Dwivedi wrote:
> On Tue Jul 7, 2026 at 7:19 PM CEST, Emil Tsalapatis wrote:
>> On Tue Jul 7, 2026 at 12:15 PM EDT, Mattia Meleleo via B4 Relay wrote:
>>> From: Mattia Meleleo <mattia.meleleo@coralogix.com>
>>>
>>> tcp_bpf_ioctl() answers SIOCINQ from psock->msg_tot_len, which only
>>> counts bytes in ingress_msg. Without a stream/skb verdict program
>>> nothing is diverted there: data stays in sk_receive_queue, so FIONREAD
>>> returns 0 even though read() returns data.
>>>
>>> Add tcp_inq() to the reported value when the psock has no verdict
>>> program. The two queues are disjoint, so bytes redirected into
>>> ingress_msg from other sockets stay correctly accounted through
>>> msg_tot_len.
>>>
>>> Add a selftest covering FIONREAD without a verdict program.
>>>
>>> Fixes: 929e30f93125 ("bpf, sockmap: Fix FIONREAD for sockmap")
>>> Signed-off-by: Mattia Meleleo <mattia.meleleo@coralogix.com>
>>
>> Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
>>
>> I don't think the Sashiko READ_ONCE() recommendation is that important,
>> we're just checking for the pointers' existence and I don't see how the
>> reads can be moved/optimized out/merged in a way that breaks this code.
>>
>
> It would be necessary if the xchg() can happen even when the lock is held, at
> the very least, to suppress potential KCSAN warnings, I think. Even for the
> theoretical load tearing that causes false positive, it would be benign due to
> wrong accounting.
>
Fair enough, nothing wrong with adding the READ_ONCE since the set already
needed respinning.
>>> ---
>>> net/ipv4/tcp_bpf.c | 16 ++++++++-
>>> .../selftests/bpf/prog_tests/sockmap_basic.c | 39 ++++++++++++++++++++++
>>> 2 files changed, 54 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
>>> index cc0bd73f3..a001b1fff 100644
>>> --- a/net/ipv4/tcp_bpf.c
>>> +++ b/net/ipv4/tcp_bpf.c
>>> @@ -334,6 +334,7 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk,
>>>
>>> static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)
>>> {
>>> + struct sk_psock *psock;
>>> bool slow;
>>>
>>> if (cmd != SIOCINQ)
>>> @@ -344,7 +345,20 @@ static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)
>>> return -EINVAL;
>>>
>>> slow = lock_sock_fast(sk);
>>> - *karg = sk_psock_msg_inq(sk);
>>> + psock = sk_psock_get(sk);
>>> + if (unlikely(!psock)) {
>>> + unlock_sock_fast(sk, slow);
>>> + return tcp_ioctl(sk, cmd, karg);
>>> + }
>>> + *karg = sk_psock_get_msg_len_nolock(psock);
>>> + /* Without a verdict program, ingress data is never diverted to
>>> + * ingress_msg: it stays in sk_receive_queue and is read through
>>> + * the fallback to tcp_recvmsg(), so account for it like
>>> + * tcp_ioctl() does.
>>> + */
>>> + if (!psock->progs.stream_verdict && !psock->progs.skb_verdict)
>>> + *karg += tcp_inq(sk);
>>> + sk_psock_put(sk, psock);
>>> unlock_sock_fast(sk, slow);
>>>
>>> return 0;
>>> diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
>>> index cb3229711..f0f368201 100644
>>> --- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
>>> +++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
>>> @@ -1373,6 +1373,43 @@ static void test_sockmap_multi_channels(int sotype)
>>> test_sockmap_pass_prog__destroy(skel);
>>> }
>>>
>>> +/* A socket in a sockmap without a verdict program keeps its ingress data
>>> + * in sk_receive_queue: FIONREAD must account for it.
>>> + */
>>> +static void test_sockmap_no_verdict_fionread(void)
>>> +{
>>> + int err, map, zero = 0, sent, avail;
>>> + int c0 = -1, c1 = -1, p0 = -1, p1 = -1;
>>> + struct test_sockmap_pass_prog *skel;
>>> + char buf[256] = "0123456789";
>>> +
>>> + skel = test_sockmap_pass_prog__open_and_load();
>>> + if (!ASSERT_OK_PTR(skel, "open_and_load"))
>>> + return;
>>> + map = bpf_map__fd(skel->maps.sock_map_rx);
>>> +
>>> + err = create_socket_pairs(AF_INET, SOCK_STREAM, &c0, &c1, &p0, &p1);
>>> + if (!ASSERT_OK(err, "create_socket_pairs()"))
>>> + goto out;
>>> +
>>> + err = bpf_map_update_elem(map, &zero, &c1, BPF_NOEXIST);
>>> + if (!ASSERT_OK(err, "bpf_map_update_elem(c1)"))
>>> + goto out_close;
>>> +
>>> + sent = xsend(p1, &buf, sizeof(buf), 0);
>>> + ASSERT_EQ(sent, sizeof(buf), "xsend(p1)");
>>> + avail = wait_for_fionread(c1, sizeof(buf), IO_TIMEOUT_SEC);
>>> + ASSERT_EQ(avail, sizeof(buf), "ioctl(FIONREAD)");
>>> +
>>> +out_close:
>>> + close(c0);
>>> + close(p0);
>>> + close(c1);
>>> + close(p1);
>>> +out:
>>> + test_sockmap_pass_prog__destroy(skel);
>>> +}
>>> +
>>> void test_sockmap_basic(void)
>>> {
>>> if (test__start_subtest("sockmap create_update_free"))
>>> @@ -1415,6 +1452,8 @@ void test_sockmap_basic(void)
>>> test_sockmap_skb_verdict_shutdown();
>>> if (test__start_subtest("sockmap skb_verdict fionread"))
>>> test_sockmap_skb_verdict_fionread(true);
>>> + if (test__start_subtest("sockmap no_verdict fionread"))
>>> + test_sockmap_no_verdict_fionread();
>>> if (test__start_subtest("sockmap skb_verdict fionread on drop"))
>>> test_sockmap_skb_verdict_fionread(false);
>>> if (test__start_subtest("sockmap skb_verdict change tail"))
>>>
>>> ---
>>> base-commit: d2c9a99135da931377240942d44f3dea104cedb8
>>> change-id: 20260707-fionread-no-verdict-a4f8697ac9f9
>>>
>>> Best regards,
>>> --
>>> Mattia Meleleo <mattia.meleleo@coralogix.com>
^ permalink raw reply
* [PATCH net] tipc: guard against empty buffer list in tipc_node_xmit()
From: Weiming Shi @ 2026-07-08 16:13 UTC (permalink / raw)
To: Jon Maloy, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David S . Miller, Simon Horman
Cc: Xiang Mei, netdev, tipc-discussion, Weiming Shi
tipc_node_xmit() dispatches a buffer list either to the bearer path via
tipc_link_xmit() or, when the destination node lives in a sibling network
namespace on the same host (n->peer_net set), to tipc_lxc_xmit(). The
bearer path returns early on an empty list, but tipc_node_xmit() does not,
and tipc_lxc_xmit() dereferences the first buffer without checking:
struct tipc_msg *hdr = buf_msg(skb_peek(list));
named_distribute() can hand tipc_node_xmit() an empty list. It bails out
early when named_prepare_buf() fails its GFP_ATOMIC allocation, leaving
the queue empty, and tipc_named_node_up() then calls tipc_node_xmit() on
it unconditionally. On the intra-host container path skb_peek() returns
NULL and msg_user() reads through it.
The TIPC configuration ops are flagged GENL_UNS_ADMIN_PERM, so an
unprivileged user can reach this via unshare(CLONE_NEWUSER|CLONE_NEWNET).
Oops: general protection fault, probably for non-canonical address 0xdffffc000000001b
KASAN: null-ptr-deref in range [0x00000000000000d8-0x00000000000000df]
CPU: 0 Comm: ksoftirqd/0
RIP: 0010:tipc_lxc_xmit (net/tipc/msg.h:202 net/tipc/node.c:1629)
Call Trace:
tipc_node_xmit (net/tipc/node.c:1721)
tipc_named_node_up (net/tipc/name_distr.c:223)
tipc_node_write_unlock (net/tipc/node.c:428)
tipc_rcv (net/tipc/node.c:2189)
tipc_l2_rcv_msg (net/tipc/bearer.c:670)
Return early from tipc_node_xmit() when the list is empty.
Fixes: f73b12812a3d ("tipc: improve throughput between nodes in netns")
Reported-by: Xiang Mei <xmei5@asu.edu>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
net/tipc/node.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/net/tipc/node.c b/net/tipc/node.c
index 8e4ef2630ae4..cd05269e5335 100644
--- a/net/tipc/node.c
+++ b/net/tipc/node.c
@@ -1695,6 +1695,9 @@ int tipc_node_xmit(struct net *net, struct sk_buff_head *list,
int bearer_id;
int rc;
+ if (skb_queue_empty(list))
+ return 0;
+
if (in_own_node(net, dnode)) {
tipc_loopback_trace(net, list);
spin_lock_init(&list->lock);
--
2.43.0
^ permalink raw reply related
* Re: [PATCH net v2] tun/tap & vhost-net: make qdisc backpressure opt-in via IFF_BACKPRESSURE
From: Michael S. Tsirkin @ 2026-07-08 16:10 UTC (permalink / raw)
To: Simon Schippers
Cc: Brett A C Sheffield, Willem de Bruijn, Jason Wang,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
netdev, Simon Horman, Jonathan Corbet, Shuah Khan, Andrew Lunn,
Tim Gebauer, linux-doc, linux-kernel
In-Reply-To: <c743935b-7b6f-49f4-b1dd-3fe1cc528464@tu-dortmund.de>
On Wed, Jul 08, 2026 at 05:12:19PM +0200, Simon Schippers wrote:
> On 7/7/26 22:05, Brett A C Sheffield wrote:
> > On 2026-07-07 08:52, Simon Schippers wrote:
> >> Brett, can you try the two attached patches here with iperf3?
> >> I think testing with 8 and 16 threads is enough, so where there is a
> >> regression.
> >>
> >> The two patches are about time when to wake:
> >> Currently we wake after consuming half the internal ring buffer.
> >> One of the attached patches wakes after 2 cachelines (128 of 1000
> >> packets) and the other one just wakes once the ring buffer is empty.
> >>
> >> This would really help :)
> >
> > Sure...
> >
> >
>
> The reverted results for reference:
> 7.2.0-rc1 reverted:
>
> threads 1
> [ 5] 0.00-10.00 sec 15.3 GBytes 13.1 Gbits/sec 368 sender
> [ 5] 0.00-10.00 sec 2.00 GBytes 1.72 Gbits/sec receiver
>
> threads 2
> [SUM] 0.00-10.00 sec 15.9 GBytes 13.7 Gbits/sec 1567 sender
> [SUM] 0.00-10.00 sec 4.00 GBytes 3.43 Gbits/sec receiver
>
> threads 4
> [SUM] 0.00-10.00 sec 16.5 GBytes 14.1 Gbits/sec 6701 sender
> [SUM] 0.00-10.00 sec 8.00 GBytes 6.87 Gbits/sec receiver
>
> threads 8
> [SUM] 0.00-10.00 sec 16.2 GBytes 14.0 Gbits/sec 19319 sender
> [SUM] 0.00-10.00 sec 15.7 GBytes 13.5 Gbits/sec receiver
>
> threads 16
> [SUM] 0.00-10.00 sec 14.4 GBytes 12.4 Gbits/sec 43593 sender
> [SUM] 0.00-10.00 sec 14.4 GBytes 12.4 Gbits/sec receiver
>
>
> > 7.2.0-rc2 (unpatched)
> >
> > threads 1
> > [ 5] 0.00-10.00 sec 20.4 GBytes 17.5 Gbits/sec 0 sender
> > [ 5] 0.00-10.00 sec 2.00 GBytes 1.72 Gbits/sec receiver
> >
> > threads 2
> > [SUM] 0.00-10.00 sec 12.7 GBytes 10.9 Gbits/sec 0 sender
> > [SUM] 0.00-10.00 sec 4.00 GBytes 3.43 Gbits/sec receiver
> >
> > threads 8
> > [SUM] 0.00-10.00 sec 11.5 GBytes 9.85 Gbits/sec 0 sender
> > [SUM] 0.00-10.01 sec 11.4 GBytes 9.83 Gbits/sec receiver
> >
> > threads 16
> > [SUM] 0.00-10.00 sec 11.6 GBytes 9.95 Gbits/sec 0 sender
> > [SUM] 0.00-10.01 sec 11.5 GBytes 9.91 Gbits/sec receiver
> >
> >
> > 7.2.0-rc2 with 0001-tun-set-waking-threshold-to-ptr_ring_empty.patch
> >
> > threads 1
> > [ 5] 0.00-10.00 sec 19.6 GBytes 16.8 Gbits/sec 0 sender
> > [ 5] 0.00-10.00 sec 2.00 GBytes 1.72 Gbits/sec receiver
> >
> > threads 2
> > [SUM] 0.00-10.00 sec 11.1 GBytes 9.50 Gbits/sec 0 sender
> > [SUM] 0.00-10.00 sec 4.00 GBytes 3.43 Gbits/sec receiver
> >
> > threads 8
> > [SUM] 0.00-10.00 sec 10.8 GBytes 9.25 Gbits/sec 0 sender
> > [SUM] 0.00-10.01 sec 10.7 GBytes 9.23 Gbits/sec receiver
> >
> > threads 16
> > [SUM] 0.00-10.00 sec 10.9 GBytes 9.34 Gbits/sec 0 sender
> > [SUM] 0.00-10.01 sec 10.8 GBytes 9.30 Gbits/sec receiver
> >
> >
> > 7.2.0-rc2 with 0001-tun-set-waking-threshold-to-tx_ring.batch.patch
> >
> > threads 1
> > [ 5] 0.00-10.00 sec 19.6 GBytes 16.9 Gbits/sec 2 sender
> > [ 5] 0.00-10.00 sec 2.00 GBytes 1.72 Gbits/sec receiver
> >
> > threads 2
> > [SUM] 0.00-10.00 sec 13.9 GBytes 11.9 Gbits/sec 0 sender
> > [SUM] 0.00-10.00 sec 4.00 GBytes 3.43 Gbits/sec receiver
> >
> > threads 8
> > [SUM] 0.00-10.00 sec 12.7 GBytes 10.9 Gbits/sec 0 sender
> > [SUM] 0.00-10.01 sec 12.3 GBytes 10.6 Gbits/sec receiver
> >
> > threads 16
> > [SUM] 0.00-10.00 sec 12.5 GBytes 10.7 Gbits/sec 0 sender
> > [SUM] 0.00-10.00 sec 12.4 GBytes 10.7 Gbits/sec receiver
> >
> >
> >
> > HTH,
> >
> >
> > Brett
>
> Thank you again!
>
> These results show that waking earlier performs better in this case:
> For 8 threads we get 10.6 Gbit/s instead of 9.83 Gbit/s when waking early
> but we are still *far* from the 13.5 Gbit/s when we revert everything
> (see above I pasted the reverted results there).
>
> So I come to the conclusion:
>
> 1. Waking earlier/later will not fix the performance issues. It may
> improve the performance slightly.
> Apart from that we introduce smp_mb__after_atomic() for the re-check
> on producer side (noop on x86) and a full smp_mb() for the re-check on
> consumer side.
> Nothing else stands out to me apart from the overheads of
> netif_tx_stop_queue() and netif_tx_wake_queue(). But we must use
> those 2 functions one way or another.
>
> 2. Users may depend on the dropping. Without backpressure tun/tap acts
> like a pfifo qdisc which tail-drops. Some users may require this.
>
> --> I would merge this opt-in patchset, it should be fine (it is not
> flagged by Sashiko anymore btw). I would not be mad if it would not
> get merged but I am out of ideas on how to improve.
>
> Thanks,
> Simon
I am fine with it being opt in, but we IMHO, need to be more clear, for users,
opt in to what this is.
--
MST
^ permalink raw reply
* Re: [PATCH] net: use sync wakeups for socket error reports
From: Usama Arif @ 2026-07-08 16:08 UTC (permalink / raw)
To: Breno Leitao
Cc: davem, edumazet, horms, kuba, kuniyu, linux-kernel, netdev,
pabeni, willemb, shakeel.butt, hannes, riel, kernel-team
In-Reply-To: <ak5rH-SWjiqR6MVe@gmail.com>
On 08/07/2026 16:25, Breno Leitao wrote:
> On Wed, Jul 08, 2026 at 06:38:15AM -0700, Usama Arif wrote:
>> Measured on a 176-core EPYC 9D64 host running a Meta production
>> workload, bpftrace on tracepoint:ipi:ipi_send_cpu with a kstack filter
>> attributed the sock_def_error_report -> ep_poll_callback ->
>> try_to_wake_up -> ttwu_queue_wakelist -> __smp_call_single_queue
>> chain to 16,326 IPIs/min.
>
> I am interested in why so many sock_def_error_report().
>
> That's seems a lot for genuine socket errors (RST/ICMP) on a healthy
> host, so I suspect these aren't errors at all?
>
> Can you share the full stack above sock_def_error_report()?
I ran this bpftrace script the host now (results added at the end):
sudo bpftrace -e '
kprobe:sock_def_error_report
{
@wake_src[kstack()] = count();
}
interval:s:60
{
print(@wake_src, 5);
exit();
}'
The biggest source is tcp_sendmsg -> __skb_tstamp_tx, which as you said
is not an actual error. __skb_tstamp_tx clones the outgoing skb, tags it
with ee_origin = SO_EE_ORIGIN_TIMESTAMPING and ee_errno = ENOMSG, enqueues
it on sk->sk_error_queue via sock_queue_err_skb, and calls sk_error_report
so epoll raises EPOLLERR. Userspace then reads it with recvmsg(MSG_ERRQUEUE)
to get the SND/ACK timestamp.
So the workload has SO_TIMESTAMPING enabled on its TCP sockets, and every
packet completion and every ACK triggers a timestamp delivery through the
error-queue path, which is why sock_def_error_report fires.
Attached 2 probes
@wake_src[
sock_def_error_report+1
sk_error_report+17
sock_queue_err_skb+285
__skb_tstamp_tx+903
tcp_ack+3399
tcp_rcv_established+1630
tcp_v6_do_rcv+372
tcp_v6_rcv+4748
ip6_protocol_deliver_rcu+653
ip6_input_finish+79
ip6_input+43
ipv6_list_rcv+4339
__netif_receive_skb_list_core+244
netif_receive_skb_list_internal+433
napi_complete_done+149
bnxt_poll_p5+499
net_rx_action+513
irq_exit_rcu+312
common_interrupt+62
asm_common_interrupt+34
]: 1812
@wake_src[
sock_def_error_report+1
sk_error_report+17
sock_dequeue_err_skb+194
ipv6_recv_error+74
bpf_trampoline_6442598004+73
____sys_recvmsg.llvm.18251018526254450710+168
___sys_recvmsg+312
__x64_sys_recvmsg+95
do_syscall_64+316
entry_SYSCALL_64_after_hwframe+75
]: 8045
@wake_src[
sock_def_error_report+1
sk_error_report+17
sock_queue_err_skb+285
__skb_tstamp_tx+903
tcp_ack+3399
tcp_rcv_established+1258
tcp_v6_do_rcv+372
tcp_v6_rcv+4748
ip6_protocol_deliver_rcu+653
ip6_input_finish+79
ip6_input+43
ipv6_list_rcv+4339
__netif_receive_skb_list_core+244
netif_receive_skb_list_internal+433
napi_complete_done+149
bnxt_poll_p5+499
net_rx_action+513
irq_exit_rcu+312
common_interrupt+125
asm_common_interrupt+34
cpuidle_enter_state+202
cpuidle_enter+40
cpu_startup_entry+497
ap_starting+0
common_startup_64+318
]: 12603
@wake_src[
sock_def_error_report+1
sk_error_report+17
sock_queue_err_skb+285
__skb_tstamp_tx+903
tcp_ack+3399
tcp_rcv_established+1258
tcp_v6_do_rcv+372
tcp_v6_rcv+4748
ip6_protocol_deliver_rcu+653
ip6_input_finish+79
ip6_input+43
ipv6_list_rcv+4339
__netif_receive_skb_list_core+244
netif_receive_skb_list_internal+433
napi_complete_done+149
bnxt_poll_p5+499
net_rx_action+513
irq_exit_rcu+312
common_interrupt+62
asm_common_interrupt+34
]: 19314
@wake_src[
sock_def_error_report+1
sk_error_report+17
sock_queue_err_skb+285
__skb_tstamp_tx+903
bnxt_start_xmit+1769
dev_hard_start_xmit+160
sch_direct_xmit+165
__qdisc_run+714
__dev_queue_xmit+2052
skb_do_redirect+2531
netkit_xmit+715
dev_hard_start_xmit+160
__dev_queue_xmit+1049
ip6_finish_output2+848
ip6_finish_output+213
ip6_output+86
ip6_xmit+933
inet6_csk_xmit+163
__tcp_transmit_skb+2733
tcp_write_xmit+2948
__tcp_push_pending_frames+46
tcp_sendmsg_locked+4187
tcp_sendmsg+40
__x64_sys_sendmsg+567
do_syscall_64+316
entry_SYSCALL_64_after_hwframe+75
]: 33514
^ permalink raw reply
* Re: [PATCH net v5] net: airoha: fix MIB stats collection to be lossless
From: Lorenzo Bianconi @ 2026-07-08 16:07 UTC (permalink / raw)
To: Aniket Negi
Cc: netdev, matthias.bgg, angelogioacchino.delregno, aniket.negi,
Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Christian Marangi, linux-arm-kernel,
linux-mediatek, linux-kernel
In-Reply-To: <20260707152639.105628-1-aniket.negi03@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 16369 bytes --]
> REG_FE_GDM_MIB_CLEAR after every read creates a race window where
> packets arriving between read and clear are lost from statistics.
>
> Switch to a delta-based approach instead:
>
> - 64-bit H+L registers (ok pkts/bytes, E64..L1023): read absolute
> hardware total directly into a local variable; clamp with max(new, old)
> to prevent torn-read regression when the counter carries between the
> two reads.
>
> - 32-bit registers (drops, bc, mc, errors, runt, long): accumulate
> (u32)(curr - prev) into a 64-bit software counter; unsigned
> subtraction handles wrap-around transparently.
>
> - tx/rx_len[0] ([0,64] bucket): combines RUNT_CNT (32-bit, delta via
> tx_runt/rx_runt) and E64_CNT (64-bit, absolute) into a single local
> accumulator; max(new, old) applied here too to guard against a torn
> read of E64 when the RUNT accumulator is unchanged between polls.
>
> MIB counters are zeroed by the SCU FE reset (EN7581_FE_RST) asserted
> in airoha_hw_init() at module load, so no explicit MIB clear is needed
> in airoha_fe_init().
>
> Merge airoha_dev_get_hw_stats() into airoha_update_hw_stats() and
> move stats_lock inside. Plain spin_lock() is correct: the function
> is only called from ndo_get_stats64() in process context. Each dev
> refreshes only its own MIB counters; sibling devs on a shared GDM3/4
> port are polled when their own netdev is queried.
>
> Fixes: 8f4695fb67b2 ("net: airoha: better handle MIBs for GDM ports with multiple devs attached")
> Signed-off-by: Aniket Negi <aniket.negi03@gmail.com>
Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
> ---
>
> Changes in v5:
> - Link to V4: https://lore.kernel.org/20260706154730.36949-1-aniket.negi03@gmail.com
> - Drop MIB clear loop from airoha_fe_init(): SCU FE reset (EN7581_FE_RST)
> resets MIB counters at module load, making the explicit clear redundant
> - Rename local variable tmp -> data; drop prev variable; use + instead
> of | for H+L combination; inline max(data, dev->stats.x) per
> maintainer nits (Lorenzo Bianconi)
> - Extend max() clamping to the hybrid tx/rx_len[0] (RUNT+E64) bucket to
> guard against E64 torn reads when the RUNT accumulator is stable
> - Fix undefined behaviour: split i++ out of expressions that also read
> i as an array subscript in the same statement
> - Retain REG_FE_GDM_MIB_CLEAR/FE_GDM_MIB_{RX,TX}_CLEAR_MASK definitions
> in airoha_regs.h as register documentation (per Lorenzo Bianconi)
>
> Changes in v4:
> - Add max(new, old) clamping for 64-bit H+L register pairs to ensure
> monotonically non-decreasing stats despite torn reads between H and L
> - Use local variable for all 64-bit H+L computations to prevent lockless
> readers from seeing intermediate values during piecewise write
> - Add one-shot MIB counter clear in airoha_fe_init() to establish a
> clean baseline (kexec, driver rebind, warm reboot)
> - Document sibling dev polling design in commit message
>
> Changes in v3:
> - Link to V2: https://lore.kernel.org/20260701173941.314795-1-aniket.negi03@gmail.com/
> - Add Acked-by tag from Lorenzo
> - Rename from tx_runt_cnt to tx_runt, tx_long_cnt to tx_long,
> tx_runt_accum64 to tx_runt64
> - Rename from rx_runt_cnt to rx_runt, rx_long_cnt to rx_long,
> rx_runt_accum64 to rx_runt64
> - Condense the marked comments in V2, remove new line after comment
>
> Changes in v2:
> - Link to V1: https://lore.kernel.org/20260630111834.233643-1-aniket.negi03@gmail.com
> - Store _CNT_L register reads in val before adding to stats
> - Fix double-counting bug in the RUNT+E64 combined bucket
> - Replace 7-element tx_len[]/rx_len[] shadow arrays with focused fields
> - Rename inner struct hw_prev_stats to mib_prev
> - Rename airoha_dev_get_hw_stats() to airoha_update_hw_stats() and
> move the port spin_lock inside, removing the separate wrapper
> ---
> drivers/net/ethernet/airoha/airoha_eth.c | 171 ++++++++++++++---------
> drivers/net/ethernet/airoha/airoha_eth.h | 27 ++++
> 2 files changed, 132 insertions(+), 66 deletions(-)
>
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
> index 59001fd4b6f7..90aa8b0210bd 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.c
> +++ b/drivers/net/ethernet/airoha/airoha_eth.c
> @@ -1686,11 +1686,14 @@ static void airoha_qdma_stop_napi(struct airoha_qdma *qdma)
> }
> }
>
> -static void airoha_dev_get_hw_stats(struct airoha_gdm_dev *dev)
> +static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
> {
> struct airoha_gdm_port *port = dev->port;
> struct airoha_eth *eth = dev->eth;
> u32 val, i = 0;
> + u64 data;
> +
> + spin_lock(&port->stats_lock);
>
> /* Read relevant MIB for GDM with multiple port attached */
> if (port->id == AIROHA_GDM3_IDX || port->id == AIROHA_GDM4_IDX)
> @@ -1701,152 +1704,188 @@ static void airoha_dev_get_hw_stats(struct airoha_gdm_dev *dev)
>
> u64_stats_update_begin(&dev->stats.syncp);
>
> - /* TX */
> + /* TX - 64-bit H+L registers: hw accumulates the total, read directly.
> + * Use local variable to prevent readers from seeing intermediate values.
> + * Clamp to prevent regression from torn reads between H and L.
> + */
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_H(port->id));
> - dev->stats.tx_ok_pkts += ((u64)val << 32);
> + data = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_PKT_CNT_L(port->id));
> - dev->stats.tx_ok_pkts += val;
> + data += val;
> + dev->stats.tx_ok_pkts = max(data, dev->stats.tx_ok_pkts);
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_H(port->id));
> - dev->stats.tx_ok_bytes += ((u64)val << 32);
> + data = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_OK_BYTE_CNT_L(port->id));
> - dev->stats.tx_ok_bytes += val;
> + data += val;
> + dev->stats.tx_ok_bytes = max(data, dev->stats.tx_ok_bytes);
>
> + /* TX - 32-bit registers: accumulate delta to handle wrap-around. */
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_DROP_CNT(port->id));
> - dev->stats.tx_drops += val;
> + dev->stats.tx_drops += (u32)(val - dev->stats.mib_prev.tx_drops);
> + dev->stats.mib_prev.tx_drops = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_BC_CNT(port->id));
> - dev->stats.tx_broadcast += val;
> + dev->stats.tx_broadcast += (u32)(val - dev->stats.mib_prev.tx_broadcast);
> + dev->stats.mib_prev.tx_broadcast = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_MC_CNT(port->id));
> - dev->stats.tx_multicast += val;
> + dev->stats.tx_multicast += (u32)(val - dev->stats.mib_prev.tx_multicast);
> + dev->stats.mib_prev.tx_multicast = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_RUNT_CNT(port->id));
> - dev->stats.tx_len[i] += val;
> + dev->stats.mib_prev.tx_runt64 +=
> + (u32)(val - dev->stats.mib_prev.tx_runt);
> + dev->stats.mib_prev.tx_runt = val;
>
> + /* tx_len[0]: RUNT (32-bit, delta) + E64 (64-bit, absolute). */
> + data = dev->stats.mib_prev.tx_runt64;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> + data += (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_E64_CNT_L(port->id));
> - dev->stats.tx_len[i++] += val;
> + data += val;
> + dev->stats.tx_len[i] = max(data, dev->stats.tx_len[i]);
> + i++;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> + data = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L64_CNT_L(port->id));
> - dev->stats.tx_len[i++] += val;
> + data += val;
> + dev->stats.tx_len[i] = max(data, dev->stats.tx_len[i]);
> + i++;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> + data = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L127_CNT_L(port->id));
> - dev->stats.tx_len[i++] += val;
> + data += val;
> + dev->stats.tx_len[i] = max(data, dev->stats.tx_len[i]);
> + i++;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> + data = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L255_CNT_L(port->id));
> - dev->stats.tx_len[i++] += val;
> + data += val;
> + dev->stats.tx_len[i] = max(data, dev->stats.tx_len[i]);
> + i++;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> + data = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L511_CNT_L(port->id));
> - dev->stats.tx_len[i++] += val;
> + data += val;
> + dev->stats.tx_len[i] = max(data, dev->stats.tx_len[i]);
> + i++;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_H(port->id));
> - dev->stats.tx_len[i] += ((u64)val << 32);
> + data = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_L1023_CNT_L(port->id));
> - dev->stats.tx_len[i++] += val;
> + data += val;
> + dev->stats.tx_len[i] = max(data, dev->stats.tx_len[i]);
> + i++;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_TX_ETH_LONG_CNT(port->id));
> - dev->stats.tx_len[i++] += val;
> + dev->stats.tx_len[i++] += (u32)(val - dev->stats.mib_prev.tx_long);
> + dev->stats.mib_prev.tx_long = val;
>
> /* RX */
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_H(port->id));
> - dev->stats.rx_ok_pkts += ((u64)val << 32);
> + data = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_PKT_CNT_L(port->id));
> - dev->stats.rx_ok_pkts += val;
> + data += val;
> + dev->stats.rx_ok_pkts = max(data, dev->stats.rx_ok_pkts);
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_H(port->id));
> - dev->stats.rx_ok_bytes += ((u64)val << 32);
> + data = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_OK_BYTE_CNT_L(port->id));
> - dev->stats.rx_ok_bytes += val;
> + data += val;
> + dev->stats.rx_ok_bytes = max(data, dev->stats.rx_ok_bytes);
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_DROP_CNT(port->id));
> - dev->stats.rx_drops += val;
> + dev->stats.rx_drops += (u32)(val - dev->stats.mib_prev.rx_drops);
> + dev->stats.mib_prev.rx_drops = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_BC_CNT(port->id));
> - dev->stats.rx_broadcast += val;
> + dev->stats.rx_broadcast += (u32)(val - dev->stats.mib_prev.rx_broadcast);
> + dev->stats.mib_prev.rx_broadcast = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_MC_CNT(port->id));
> - dev->stats.rx_multicast += val;
> + dev->stats.rx_multicast += (u32)(val - dev->stats.mib_prev.rx_multicast);
> + dev->stats.mib_prev.rx_multicast = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ERROR_DROP_CNT(port->id));
> - dev->stats.rx_errors += val;
> + dev->stats.rx_errors += (u32)(val - dev->stats.mib_prev.rx_errors);
> + dev->stats.mib_prev.rx_errors = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_CRC_ERR_CNT(port->id));
> - dev->stats.rx_crc_error += val;
> + dev->stats.rx_crc_error += (u32)(val - dev->stats.mib_prev.rx_crc_error);
> + dev->stats.mib_prev.rx_crc_error = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_OVERFLOW_DROP_CNT(port->id));
> - dev->stats.rx_over_errors += val;
> + dev->stats.rx_over_errors += (u32)(val - dev->stats.mib_prev.rx_over_errors);
> + dev->stats.mib_prev.rx_over_errors = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_FRAG_CNT(port->id));
> - dev->stats.rx_fragment += val;
> + dev->stats.rx_fragment += (u32)(val - dev->stats.mib_prev.rx_fragment);
> + dev->stats.mib_prev.rx_fragment = val;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_JABBER_CNT(port->id));
> - dev->stats.rx_jabber += val;
> + dev->stats.rx_jabber += (u32)(val - dev->stats.mib_prev.rx_jabber);
> + dev->stats.mib_prev.rx_jabber = val;
>
> i = 0;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_RUNT_CNT(port->id));
> - dev->stats.rx_len[i] += val;
> + dev->stats.mib_prev.rx_runt64 +=
> + (u32)(val - dev->stats.mib_prev.rx_runt);
> + dev->stats.mib_prev.rx_runt = val;
>
> + /* rx_len[0]: RUNT (32-bit, delta) + E64 (64-bit, absolute). */
> + data = dev->stats.mib_prev.rx_runt64;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> + data += (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_E64_CNT_L(port->id));
> - dev->stats.rx_len[i++] += val;
> + data += val;
> + dev->stats.rx_len[i] = max(data, dev->stats.rx_len[i]);
> + i++;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> + data = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L64_CNT_L(port->id));
> - dev->stats.rx_len[i++] += val;
> + data += val;
> + dev->stats.rx_len[i] = max(data, dev->stats.rx_len[i]);
> + i++;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> + data = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L127_CNT_L(port->id));
> - dev->stats.rx_len[i++] += val;
> + data += val;
> + dev->stats.rx_len[i] = max(data, dev->stats.rx_len[i]);
> + i++;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> + data = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L255_CNT_L(port->id));
> - dev->stats.rx_len[i++] += val;
> + data += val;
> + dev->stats.rx_len[i] = max(data, dev->stats.rx_len[i]);
> + i++;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> + data = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L511_CNT_L(port->id));
> - dev->stats.rx_len[i++] += val;
> + data += val;
> + dev->stats.rx_len[i] = max(data, dev->stats.rx_len[i]);
> + i++;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_H(port->id));
> - dev->stats.rx_len[i] += ((u64)val << 32);
> + data = (u64)val << 32;
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_L1023_CNT_L(port->id));
> - dev->stats.rx_len[i++] += val;
> + data += val;
> + dev->stats.rx_len[i] = max(data, dev->stats.rx_len[i]);
> + i++;
>
> val = airoha_fe_rr(eth, REG_FE_GDM_RX_ETH_LONG_CNT(port->id));
> - dev->stats.rx_len[i++] += val;
> + dev->stats.rx_len[i] += (u32)(val - dev->stats.mib_prev.rx_long);
> + dev->stats.mib_prev.rx_long = val;
>
> u64_stats_update_end(&dev->stats.syncp);
> -}
> -
> -static void airoha_update_hw_stats(struct airoha_gdm_dev *dev)
> -{
> - struct airoha_gdm_port *port = dev->port;
> - int i;
> -
> - spin_lock(&port->stats_lock);
> -
> - for (i = 0; i < ARRAY_SIZE(port->devs); i++) {
> - if (port->devs[i])
> - airoha_dev_get_hw_stats(port->devs[i]);
> - }
> -
> - /* Reset MIB counters */
> - airoha_fe_set(dev->eth, REG_FE_GDM_MIB_CLEAR(port->id),
> - FE_GDM_MIB_RX_CLEAR_MASK | FE_GDM_MIB_TX_CLEAR_MASK);
>
> spin_unlock(&port->stats_lock);
> }
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
> index f6d01a8e8da1..fe934f9ffe8a 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.h
> +++ b/drivers/net/ethernet/airoha/airoha_eth.h
> @@ -245,6 +245,33 @@ struct airoha_hw_stats {
> u64 rx_fragment;
> u64 rx_jabber;
> u64 rx_len[7];
> +
> + struct {
> + /* Previous HW register values for 32-bit counter delta
> + * tracking. Storing the last seen value and accumulating
> + * (u32)(curr - prev) into the 64-bit software counter
> + * handles wrap-around transparently via unsigned arithmetic.
> + * tx_runt64/rx_runt64 hold the running sum of runt deltas.
> + * These fields are never reported to userspace.
> + */
> + u32 tx_drops;
> + u32 tx_broadcast;
> + u32 tx_multicast;
> + u32 tx_runt;
> + u32 tx_long;
> + u64 tx_runt64;
> + u32 rx_drops;
> + u32 rx_broadcast;
> + u32 rx_multicast;
> + u32 rx_errors;
> + u32 rx_crc_error;
> + u32 rx_over_errors;
> + u32 rx_fragment;
> + u32 rx_jabber;
> + u32 rx_runt;
> + u32 rx_long;
> + u64 rx_runt64;
> + } mib_prev;
> };
>
> enum {
>
> base-commit: 60444706aa17616efc03190d099ac347e28b3d0a
> --
> 2.43.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH net] net/iucv: take a reference on the socket found in afiucv_hs_rcv()
From: Hidayathulla Khan I @ 2026-07-08 16:06 UTC (permalink / raw)
To: hexlabsecurity, Paolo Abeni, Jakub Kicinski, Eric Dumazet,
David S. Miller, Thorsten Winkler, Alexandra Winter
Cc: Nagamani PV, Simon Horman, netdev, linux-kernel, linux-s390,
pasic, aswin
In-Reply-To: <20260705-b4-disp-fc79c0dc-v1-1-d2cdcb57afa9@proton.me>
Hi Bryam,
Addressing Sashiko's findings on this patch: [High] NULL Pointer Dereference in `afiucv_hs_callback_syn` error path.
This is already fixed by my patch currently under review.
[PATCH net] net/af_iucv: fix NULL deref in afiucv_hs_callback_syn()
The fix guards iucv_sock_kill(nsk) with if (nsk).
Regards,
Hidayath Khan
On 06/07/26 8:54 am, Bryam Vargas via B4 Relay wrote:
> From: Bryam Vargas <hexlabsecurity@proton.me>
>
> afiucv_hs_rcv() looks up the destination socket under iucv_sk_list.lock,
> drops the lock, and then passes the socket to the afiucv_hs_callback_*()
> handlers without holding a reference. AF_IUCV sockets are not
> RCU-protected and are freed synchronously by iucv_sock_kill() ->
> sock_put(), so a concurrent close can free the socket in the window
> between read_unlock() and the handler, which then dereferences freed
> memory (for example sk->sk_data_ready() in afiucv_hs_callback_syn()).
>
> Take a reference with sock_hold() while the socket is still on the list
> and release it with sock_put() once the handler has run.
>
> Fixes: 3881ac441f64 ("af_iucv: add HiperSockets transport")
> Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
> ---
> afiucv_netdev_event() has the same iucv_sk_list use-after-free and is
> being fixed separately by Nagamani PV:
> https://lore.kernel.org/all/20260508170534.2208812-1-nagamani@linux.ibm.com/
> This patch covers the receive path, afiucv_hs_rcv(), which that fix does
> not touch.
>
> Verified with an LKMM/herd7 litmus (the missing sock_hold() is the only
> delta between a flagged data race and none) and with an in-kernel KASAN
> model that reproduces the afiucv_hs_rcv() lookup-without-hold path (the
> freed socket's sk_data_ready pointer is read after the callback runs);
> adding the reference clears it. af_iucv is s390-only, so this is the
> model rather than the driver. Reproducer available on request.
> ---
> net/iucv/af_iucv.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c
> index fed240b453bd..b85fb9767dec 100644
> --- a/net/iucv/af_iucv.c
> +++ b/net/iucv/af_iucv.c
> @@ -2089,6 +2089,8 @@ static int afiucv_hs_rcv(struct sk_buff *skb, struct net_device *dev,
> }
> }
> }
> + if (sk)
> + sock_hold(sk);
> read_unlock(&iucv_sk_list.lock);
> if (!iucv)
> sk = NULL;
> @@ -2138,6 +2140,8 @@ static int afiucv_hs_rcv(struct sk_buff *skb, struct net_device *dev,
> kfree_skb(skb);
> }
>
> + if (sk)
> + sock_put(sk);
> return err;
> }
>
>
> ---
> base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
> change-id: 20260705-b4-disp-fc79c0dc-019670262472
>
> Best regards,
^ permalink raw reply
* [PATCH net-next 3/3] vxlan: no longer rely on RTNL in vxlan_fill_info()
From: Eric Dumazet @ 2026-07-08 16:04 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, Andrew Lunn,
netdev, eric.dumazet, Eric Dumazet
In-Reply-To: <20260708160411.1355008-1-edumazet@google.com>
Now that vxlan->cfg is RCU-protected, we can update vxlan_fill_info()
to run under RCU read lock instead of relying on RTNL.
This completes the transition to RTNL-less link info dumping for VXLAN.
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
drivers/net/vxlan/vxlan_core.c | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c
index 96ac74fb1eb064d4e280af8ffa68cff37aacbca7..86b853d7739639b93b6468bc64a217c562b4804c 100644
--- a/drivers/net/vxlan/vxlan_core.c
+++ b/drivers/net/vxlan/vxlan_core.c
@@ -4674,10 +4674,16 @@ static int vxlan_fill_info(struct sk_buff *skb, const struct net_device *dev)
struct ifla_vxlan_port_range ports;
const struct vxlan_config *cfg;
const struct vxlan_rdst *dst;
+ int err = 0;
dst = &vxlan->default_dst;
- cfg = rtnl_dereference(vxlan->cfg);
+ rcu_read_lock();
+ cfg = rcu_dereference(vxlan->cfg);
+ if (!cfg) {
+ err = -ENODEV;
+ goto out;
+ }
ports.low = htons(cfg->port_min);
ports.high = htons(cfg->port_max);
@@ -4777,9 +4783,12 @@ static int vxlan_fill_info(struct sk_buff *skb, const struct net_device *dev)
&cfg->reserved_bits))
goto nla_put_failure;
- return 0;
+out:
+ rcu_read_unlock();
+ return err;
nla_put_failure:
+ rcu_read_unlock();
return -EMSGSIZE;
}
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply related
* [PATCH net-next 2/3] vxlan: convert configuration to RCU protection
From: Eric Dumazet @ 2026-07-08 16:04 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, Andrew Lunn,
netdev, eric.dumazet, Eric Dumazet
In-Reply-To: <20260708160411.1355008-1-edumazet@google.com>
In order to allow lockless readers in future patches, dynamically allocate
'struct vxlan_config' and convert 'vxlan->cfg' to an RCU protected pointer.
Updating configuration via vxlan_changelink() or __vxlan_dev_create()
now allocates a new struct vxlan_config, initializes it, and uses
rcu_assign_pointer() to publish it, freeing the previous config with
kfree_rcu().
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
.../mellanox/mlxsw/spectrum_nve_vxlan.c | 14 +-
.../mellanox/mlxsw/spectrum_switchdev.c | 57 ++-
drivers/net/vxlan/vxlan_core.c | 336 +++++++++++-------
drivers/net/vxlan/vxlan_mdb.c | 29 +-
drivers/net/vxlan/vxlan_multicast.c | 12 +-
drivers/net/vxlan/vxlan_vnifilter.c | 21 +-
include/net/vxlan.h | 3 +-
net/openvswitch/vport-vxlan.c | 31 +-
8 files changed, 331 insertions(+), 172 deletions(-)
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_nve_vxlan.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_nve_vxlan.c
index 52c2fe3644d4b9b27f1d589d9f7f597748339782..50cea39323f570e04067f2f98aff4d97e4a409fc 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_nve_vxlan.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_nve_vxlan.c
@@ -59,8 +59,11 @@ static bool mlxsw_sp_nve_vxlan_can_offload(const struct mlxsw_sp_nve *nve,
const struct mlxsw_sp_nve_params *params,
struct netlink_ext_ack *extack)
{
- struct vxlan_dev *vxlan = netdev_priv(params->dev);
- struct vxlan_config *cfg = &vxlan->cfg;
+ const struct vxlan_config *cfg;
+ struct vxlan_dev *vxlan;
+
+ vxlan = netdev_priv(params->dev);
+ cfg = rtnl_dereference(vxlan->cfg);
if (vxlan_addr_multicast(&cfg->remote_ip)) {
NL_SET_ERR_MSG_MOD(extack, "VxLAN: Multicast destination IP is not supported");
@@ -148,8 +151,11 @@ static void mlxsw_sp_nve_vxlan_config(const struct mlxsw_sp_nve *nve,
const struct mlxsw_sp_nve_params *params,
struct mlxsw_sp_nve_config *config)
{
- struct vxlan_dev *vxlan = netdev_priv(params->dev);
- struct vxlan_config *cfg = &vxlan->cfg;
+ const struct vxlan_config *cfg;
+ struct vxlan_dev *vxlan;
+
+ vxlan = netdev_priv(params->dev);
+ cfg = rtnl_dereference(vxlan->cfg);
config->type = MLXSW_SP_NVE_TYPE_VXLAN;
config->ttl = cfg->ttl;
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c
index fe45e533a4b2efb532b85960009c586a53ade340..f25105ceb3f1bff87ec6addcae70c606bbe048ba 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c
@@ -2513,15 +2513,17 @@ mlxsw_sp_bridge_vlan_aware_vxlan_join(struct mlxsw_sp_bridge_device *bridge_devi
{
struct mlxsw_sp *mlxsw_sp = mlxsw_sp_lower_get(bridge_device->dev);
struct vxlan_dev *vxlan = netdev_priv(vxlan_dev);
- struct mlxsw_sp_nve_params params = {
- .type = MLXSW_SP_NVE_TYPE_VXLAN,
- .vni = vxlan->cfg.vni,
- .dev = vxlan_dev,
- .ethertype = ethertype,
- };
+ struct mlxsw_sp_nve_params params;
+ const struct vxlan_config *cfg;
struct mlxsw_sp_fid *fid;
int err;
+ cfg = rtnl_dereference(vxlan->cfg);
+ params.type = MLXSW_SP_NVE_TYPE_VXLAN;
+ params.vni = cfg->vni;
+ params.dev = vxlan_dev;
+ params.ethertype = ethertype;
+
/* If the VLAN is 0, we need to find the VLAN that is configured as
* PVID and egress untagged on the bridge port of the VxLAN device.
* It is possible no such VLAN exists
@@ -2704,15 +2706,17 @@ mlxsw_sp_bridge_8021d_vxlan_join(struct mlxsw_sp_bridge_device *bridge_device,
{
struct mlxsw_sp *mlxsw_sp = mlxsw_sp_lower_get(bridge_device->dev);
struct vxlan_dev *vxlan = netdev_priv(vxlan_dev);
- struct mlxsw_sp_nve_params params = {
- .type = MLXSW_SP_NVE_TYPE_VXLAN,
- .vni = vxlan->cfg.vni,
- .dev = vxlan_dev,
- .ethertype = ETH_P_8021Q,
- };
+ struct mlxsw_sp_nve_params params;
+ const struct vxlan_config *cfg;
struct mlxsw_sp_fid *fid;
int err;
+ cfg = rtnl_dereference(vxlan->cfg);
+ params.type = MLXSW_SP_NVE_TYPE_VXLAN;
+ params.vni = cfg->vni;
+ params.dev = vxlan_dev;
+ params.ethertype = ETH_P_8021Q;
+
fid = mlxsw_sp_fid_8021d_get(mlxsw_sp, bridge_device->dev->ifindex);
if (IS_ERR(fid)) {
NL_SET_ERR_MSG_MOD(extack, "Failed to create 802.1D FID");
@@ -2933,10 +2937,13 @@ static void __mlxsw_sp_bridge_vxlan_leave(struct mlxsw_sp *mlxsw_sp,
const struct net_device *vxlan_dev)
{
struct vxlan_dev *vxlan = netdev_priv(vxlan_dev);
+ const struct vxlan_config *cfg;
struct mlxsw_sp_fid *fid;
+ cfg = rtnl_dereference(vxlan->cfg);
+
/* If the VxLAN device is down, then the FID does not have a VNI */
- fid = mlxsw_sp_fid_lookup_by_vni(mlxsw_sp, vxlan->cfg.vni);
+ fid = mlxsw_sp_fid_lookup_by_vni(mlxsw_sp, cfg->vni);
if (!fid)
return;
@@ -3029,11 +3036,13 @@ static void mlxsw_sp_fdb_vxlan_call_notifiers(struct net_device *dev,
struct switchdev_notifier_vxlan_fdb_info info;
struct vxlan_dev *vxlan = netdev_priv(dev);
enum switchdev_notifier_type type;
+ const struct vxlan_config *cfg;
+ cfg = rtnl_dereference(vxlan->cfg);
type = adding ? SWITCHDEV_VXLAN_FDB_ADD_TO_BRIDGE :
SWITCHDEV_VXLAN_FDB_DEL_TO_BRIDGE;
mlxsw_sp_switchdev_addr_vxlan_convert(proto, addr, &info.remote_ip);
- info.remote_port = vxlan->cfg.dst_port;
+ info.remote_port = cfg->dst_port;
info.remote_vni = vni;
info.remote_ifindex = 0;
ether_addr_copy(info.eth_addr, mac);
@@ -3236,8 +3245,10 @@ __mlxsw_sp_fdb_notify_mac_uc_tunnel_process(struct mlxsw_sp *mlxsw_sp,
if (adding && netif_is_vxlan(dev)) {
struct vxlan_dev *vxlan = netdev_priv(dev);
+ const struct vxlan_config *cfg;
- if (!(vxlan->cfg.flags & VXLAN_F_LEARN))
+ cfg = rtnl_dereference(vxlan->cfg);
+ if (!(cfg->flags & VXLAN_F_LEARN))
return -EINVAL;
}
@@ -3722,9 +3733,11 @@ mlxsw_sp_switchdev_vxlan_work_prepare(struct mlxsw_sp_switchdev_event_work *
{
struct vxlan_dev *vxlan = netdev_priv(switchdev_work->dev);
struct switchdev_notifier_vxlan_fdb_info *vxlan_fdb_info;
- struct vxlan_config *cfg = &vxlan->cfg;
+ const struct vxlan_config *cfg;
struct netlink_ext_ack *extack;
+ cfg = rtnl_dereference(vxlan->cfg);
+
extack = switchdev_notifier_info_to_extack(info);
vxlan_fdb_info = container_of(info,
struct switchdev_notifier_vxlan_fdb_info,
@@ -3851,11 +3864,15 @@ mlxsw_sp_switchdev_vxlan_vlan_add(struct mlxsw_sp *mlxsw_sp,
struct netlink_ext_ack *extack)
{
struct vxlan_dev *vxlan = netdev_priv(vxlan_dev);
- __be32 vni = vxlan->cfg.vni;
+ const struct vxlan_config *cfg;
struct mlxsw_sp_fid *fid;
u16 old_vid;
+ __be32 vni;
int err;
+ cfg = rtnl_dereference(vxlan->cfg);
+ vni = cfg->vni;
+
/* We cannot have the same VLAN as PVID and egress untagged on multiple
* VxLAN devices. Note that we get this notification before the VLAN is
* actually added to the bridge's database, so it is not possible for
@@ -3935,12 +3952,16 @@ mlxsw_sp_switchdev_vxlan_vlan_del(struct mlxsw_sp *mlxsw_sp,
const struct net_device *vxlan_dev, u16 vid)
{
struct vxlan_dev *vxlan = netdev_priv(vxlan_dev);
- __be32 vni = vxlan->cfg.vni;
+ const struct vxlan_config *cfg;
struct mlxsw_sp_fid *fid;
+ __be32 vni;
if (!netif_running(vxlan_dev))
return;
+ cfg = rtnl_dereference(vxlan->cfg);
+ vni = cfg->vni;
+
fid = mlxsw_sp_fid_lookup_by_vni(mlxsw_sp, vni);
if (!fid)
return;
diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c
index eb2608fb7139a18d905d9b1a5140f22a880818d6..96ac74fb1eb064d4e280af8ffa68cff37aacbca7 100644
--- a/drivers/net/vxlan/vxlan_core.c
+++ b/drivers/net/vxlan/vxlan_core.c
@@ -110,20 +110,23 @@ static struct vxlan_dev *vxlan_vs_find_vni(struct vxlan_sock *vs,
vni = 0;
hlist_for_each_entry_rcu(node, vni_head(vs, vni), hlist) {
+ const struct vxlan_config *cfg;
+
if (!node->vxlan)
continue;
+
+ cfg = rcu_dereference(node->vxlan->cfg);
+
vnode = NULL;
- if (node->vxlan->cfg.flags & VXLAN_F_VNIFILTER) {
+ if (cfg->flags & VXLAN_F_VNIFILTER) {
vnode = vxlan_vnifilter_lookup(node->vxlan, vni);
if (!vnode)
continue;
- } else if (node->vxlan->default_dst.remote_vni != vni) {
+ } else if (cfg->vni != vni) {
continue;
}
if (IS_ENABLED(CONFIG_IPV6)) {
- const struct vxlan_config *cfg = &node->vxlan->cfg;
-
if ((cfg->flags & VXLAN_F_IPV6_LINKLOCAL) &&
cfg->remote_ifindex != ifindex)
continue;
@@ -157,6 +160,7 @@ static int vxlan_fdb_info(struct sk_buff *skb, struct vxlan_dev *vxlan,
u32 portid, u32 seq, int type, unsigned int flags,
const struct vxlan_rdst *rdst)
{
+ const struct vxlan_config *cfg = rcu_dereference_rtnl(vxlan->cfg);
unsigned long now = jiffies;
struct nda_cacheinfo ci;
bool send_ip, send_eth;
@@ -216,10 +220,10 @@ static int vxlan_fdb_info(struct sk_buff *skb, struct vxlan_dev *vxlan,
goto nla_put_failure;
if (rdst->remote_port &&
- rdst->remote_port != vxlan->cfg.dst_port &&
+ rdst->remote_port != cfg->dst_port &&
nla_put_be16(skb, NDA_PORT, rdst->remote_port))
goto nla_put_failure;
- if (rdst->remote_vni != vxlan->default_dst.remote_vni &&
+ if (rdst->remote_vni != cfg->vni &&
nla_put_u32(skb, NDA_VNI, be32_to_cpu(rdst->remote_vni)))
goto nla_put_failure;
if (rdst->remote_ifindex &&
@@ -227,7 +231,7 @@ static int vxlan_fdb_info(struct sk_buff *skb, struct vxlan_dev *vxlan,
goto nla_put_failure;
}
- if ((vxlan->cfg.flags & VXLAN_F_COLLECT_METADATA) && fdb->key.vni &&
+ if ((cfg->flags & VXLAN_F_COLLECT_METADATA) && fdb->key.vni &&
nla_put_u32(skb, NDA_SRC_VNI,
be32_to_cpu(fdb->key.vni)))
goto nla_put_failure;
@@ -418,7 +422,7 @@ static struct vxlan_fdb *vxlan_find_mac(struct vxlan_dev *vxlan,
lockdep_assert_held_once(&vxlan->hash_lock);
rcu_read_lock();
- f = vxlan_find_mac_rcu(vxlan, &vxlan->cfg, mac, vni);
+ f = vxlan_find_mac_rcu(vxlan, rcu_dereference(vxlan->cfg), mac, vni);
rcu_read_unlock();
return f;
@@ -459,7 +463,7 @@ int vxlan_fdb_find_uc(struct net_device *dev, const u8 *mac, __be32 vni,
rcu_read_lock();
- f = vxlan_find_mac_rcu(vxlan, &vxlan->cfg, eth_addr, vni);
+ f = vxlan_find_mac_rcu(vxlan, rcu_dereference(vxlan->cfg), eth_addr, vni);
if (f)
rdst = first_remote_rcu(f);
if (!rdst) {
@@ -865,12 +869,13 @@ int vxlan_fdb_create(struct vxlan_dev *vxlan,
u32 nhid, struct vxlan_fdb **fdb,
struct netlink_ext_ack *extack)
{
+ const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg);
struct vxlan_rdst *rd = NULL;
struct vxlan_fdb *f;
int rc;
- if (vxlan->cfg.addrmax &&
- vxlan->addrcnt >= vxlan->cfg.addrmax)
+ if (cfg->addrmax &&
+ vxlan->addrcnt >= cfg->addrmax)
return -ENOSPC;
netdev_dbg(vxlan->dev, "add %pM -> %pIS\n", mac, ip);
@@ -1150,6 +1155,7 @@ static int vxlan_fdb_parse(struct nlattr *tb[], struct vxlan_dev *vxlan,
__be32 *vni, u32 *ifindex, u32 *nhid,
struct netlink_ext_ack *extack)
{
+ const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg);
struct net *net = dev_net(vxlan->dev);
int err;
@@ -1166,7 +1172,7 @@ static int vxlan_fdb_parse(struct nlattr *tb[], struct vxlan_dev *vxlan,
return err;
}
} else {
- union vxlan_addr *remote = &vxlan->default_dst.remote_ip;
+ const union vxlan_addr *remote = &cfg->remote_ip;
if (remote->sa.sa_family == AF_INET) {
ip->sin.sin_addr.s_addr = htonl(INADDR_ANY);
@@ -1186,7 +1192,7 @@ static int vxlan_fdb_parse(struct nlattr *tb[], struct vxlan_dev *vxlan,
}
*port = nla_get_be16(tb[NDA_PORT]);
} else {
- *port = vxlan->cfg.dst_port;
+ *port = cfg->dst_port;
}
if (tb[NDA_VNI]) {
@@ -1196,7 +1202,7 @@ static int vxlan_fdb_parse(struct nlattr *tb[], struct vxlan_dev *vxlan,
}
*vni = cpu_to_be32(nla_get_u32(tb[NDA_VNI]));
} else {
- *vni = vxlan->default_dst.remote_vni;
+ *vni = cfg->vni;
}
if (tb[NDA_SRC_VNI]) {
@@ -1206,7 +1212,7 @@ static int vxlan_fdb_parse(struct nlattr *tb[], struct vxlan_dev *vxlan,
}
*src_vni = cpu_to_be32(nla_get_u32(tb[NDA_SRC_VNI]));
} else {
- *src_vni = vxlan->default_dst.remote_vni;
+ *src_vni = cfg->vni;
}
if (tb[NDA_IFINDEX]) {
@@ -1396,18 +1402,23 @@ static int vxlan_fdb_get(struct sk_buff *skb,
struct netlink_ext_ack *extack)
{
struct vxlan_dev *vxlan = netdev_priv(dev);
+ const struct vxlan_config *cfg;
struct vxlan_fdb *f;
__be32 vni;
int err;
+ cfg = rtnl_dereference(vxlan->cfg);
+
if (tb[NDA_VNI])
vni = cpu_to_be32(nla_get_u32(tb[NDA_VNI]));
+ else if (cfg)
+ vni = cfg->vni;
else
- vni = vxlan->default_dst.remote_vni;
+ return -ENODEV;
rcu_read_lock();
- f = vxlan_find_mac_rcu(vxlan, &vxlan->cfg, addr, vni);
+ f = vxlan_find_mac_rcu(vxlan, cfg, addr, vni);
if (!f) {
NL_SET_ERR_MSG(extack, "Fdb entry not found");
err = -ENOENT;
@@ -1510,6 +1521,7 @@ static bool __vxlan_sock_release_prep(struct vxlan_sock *vs)
static void vxlan_sock_release(struct vxlan_dev *vxlan)
{
+ const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg);
struct vxlan_sock *sock4 = rtnl_dereference(vxlan->vn4_sock);
#if IS_ENABLED(CONFIG_IPV6)
struct vxlan_sock *sock6 = rtnl_dereference(vxlan->vn6_sock);
@@ -1519,7 +1531,7 @@ static void vxlan_sock_release(struct vxlan_dev *vxlan)
RCU_INIT_POINTER(vxlan->vn4_sock, NULL);
- if (vxlan->cfg.flags & VXLAN_F_VNIFILTER)
+ if (cfg && (cfg->flags & VXLAN_F_VNIFILTER))
vxlan_vs_del_vnigrp(vxlan);
else
vxlan_vs_del_dev(vxlan);
@@ -1650,6 +1662,7 @@ static bool vxlan_ecn_decapsulate(struct vxlan_sock *vs, void *oiph,
static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
{
struct vxlan_vni_node *vninode = NULL;
+ const struct vxlan_config *cfg;
__be16 protocol = htons(ETH_P_TEB);
enum skb_drop_reason reason;
const struct vxlanhdr *vh;
@@ -1691,8 +1704,10 @@ static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
goto drop;
}
- if (vh->vx_flags & vxlan->cfg.reserved_bits.vx_flags ||
- vh->vx_vni & vxlan->cfg.reserved_bits.vx_vni) {
+ cfg = rcu_dereference(vxlan->cfg);
+
+ if (vh->vx_flags & cfg->reserved_bits.vx_flags ||
+ vh->vx_vni & cfg->reserved_bits.vx_vni) {
/* If the header uses bits besides those enabled by the
* netdevice configuration, treat this as a malformed packet.
* This behavior diverges from VXLAN RFC (RFC7348) which
@@ -1704,12 +1719,12 @@ static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
reason = SKB_DROP_REASON_VXLAN_INVALID_HDR;
DEV_STATS_INC(vxlan->dev, rx_frame_errors);
DEV_STATS_INC(vxlan->dev, rx_errors);
- vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, vninode,
+ vxlan_vnifilter_count(vxlan, cfg, vni, vninode,
VXLAN_VNI_STATS_RX_ERRORS, 0);
goto drop;
}
- if (vxlan->cfg.flags & VXLAN_F_GPE) {
+ if (cfg->flags & VXLAN_F_GPE) {
if (!vxlan_parse_gpe_proto(vh, &protocol))
goto drop;
raw_proto = true;
@@ -1721,8 +1736,8 @@ static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
goto drop;
}
- if (vxlan->cfg.flags & VXLAN_F_REMCSUM_RX) {
- reason = vxlan_remcsum(skb, vxlan->cfg.flags);
+ if (cfg->flags & VXLAN_F_REMCSUM_RX) {
+ reason = vxlan_remcsum(skb, cfg->flags);
if (unlikely(reason))
goto drop;
}
@@ -1747,14 +1762,14 @@ static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
memset(md, 0, sizeof(*md));
}
- if (vxlan->cfg.flags & VXLAN_F_GBP)
- vxlan_parse_gbp_hdr(skb, vxlan->cfg.flags, md);
+ if (cfg->flags & VXLAN_F_GBP)
+ vxlan_parse_gbp_hdr(skb, cfg->flags, md);
/* Note that GBP and GPE can never be active together. This is
* ensured in vxlan_dev_configure.
*/
if (!raw_proto) {
- reason = vxlan_set_mac(vxlan, &vxlan->cfg, vs, skb, vni);
+ reason = vxlan_set_mac(vxlan, cfg, vs, skb, vni);
if (reason)
goto drop;
} else {
@@ -1775,7 +1790,7 @@ static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
if (reason) {
DEV_STATS_INC(vxlan->dev, rx_length_errors);
DEV_STATS_INC(vxlan->dev, rx_errors);
- vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, vninode,
+ vxlan_vnifilter_count(vxlan, cfg, vni, vninode,
VXLAN_VNI_STATS_RX_ERRORS, 0);
goto drop;
}
@@ -1787,7 +1802,7 @@ static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
reason = SKB_DROP_REASON_IP_TUNNEL_ECN;
DEV_STATS_INC(vxlan->dev, rx_frame_errors);
DEV_STATS_INC(vxlan->dev, rx_errors);
- vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, vninode,
+ vxlan_vnifilter_count(vxlan, cfg, vni, vninode,
VXLAN_VNI_STATS_RX_ERRORS, 0);
goto drop;
}
@@ -1797,14 +1812,14 @@ static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
if (unlikely(!(vxlan->dev->flags & IFF_UP))) {
rcu_read_unlock();
dev_dstats_rx_dropped(vxlan->dev);
- vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, vninode,
+ vxlan_vnifilter_count(vxlan, cfg, vni, vninode,
VXLAN_VNI_STATS_RX_DROPS, 0);
reason = SKB_DROP_REASON_DEV_READY;
goto drop;
}
dev_dstats_rx_add(vxlan->dev, skb->len);
- vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, vninode, VXLAN_VNI_STATS_RX, skb->len);
+ vxlan_vnifilter_count(vxlan, cfg, vni, vninode, VXLAN_VNI_STATS_RX, skb->len);
gro_cells_receive(&vxlan->gro_cells, skb);
rcu_read_unlock();
@@ -1848,17 +1863,20 @@ static int vxlan_err_lookup(struct sock *sk, struct sk_buff *skb)
static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni, u32 flags)
{
struct vxlan_dev *vxlan = netdev_priv(dev);
+ const struct vxlan_config *cfg;
struct arphdr *parp;
u8 *arpptr, *sha;
__be32 sip, tip;
struct neighbour *n;
+ cfg = rcu_dereference(vxlan->cfg);
+
if (dev->flags & IFF_NOARP)
goto out;
if (!pskb_may_pull(skb, arp_hdr_len(dev))) {
dev_dstats_tx_dropped(dev);
- vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, NULL,
+ vxlan_vnifilter_count(vxlan, cfg, vni, NULL,
VXLAN_VNI_STATS_TX_DROPS, 0);
goto out;
}
@@ -1896,7 +1914,7 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni, u
}
rcu_read_lock();
- f = vxlan_find_mac_tx(vxlan, &vxlan->cfg, n->ha, vni);
+ f = vxlan_find_mac_tx(vxlan, cfg, n->ha, vni);
if (f)
rdst = first_remote_rcu(f);
if (rdst && vxlan_addr_any(&rdst->remote_ip)) {
@@ -1922,7 +1940,7 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni, u
if (netif_rx(reply) == NET_RX_DROP) {
dev_dstats_rx_dropped(dev);
- vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, NULL,
+ vxlan_vnifilter_count(vxlan, cfg, vni, NULL,
VXLAN_VNI_STATS_RX_DROPS, 0);
}
@@ -2036,6 +2054,7 @@ static struct sk_buff *vxlan_na_create(struct sk_buff *request,
static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni, u32 flags)
{
struct vxlan_dev *vxlan = netdev_priv(dev);
+ const struct vxlan_config *cfg;
const struct in6_addr *daddr;
const struct ipv6hdr *iphdr;
struct neighbour *n;
@@ -2045,6 +2064,8 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni,
if (unlikely(!ipv6_mod_enabled()))
goto out;
+ cfg = rcu_dereference(vxlan->cfg);
+
iphdr = ipv6_hdr(skb);
daddr = &iphdr->daddr;
msg = (struct nd_msg *)(iphdr + 1);
@@ -2065,7 +2086,7 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni,
goto out;
}
- f = vxlan_find_mac_tx(vxlan, &vxlan->cfg, n->ha, vni);
+ f = vxlan_find_mac_tx(vxlan, cfg, n->ha, vni);
if (f)
rdst = first_remote_rcu(f);
if (rdst && vxlan_addr_any(&rdst->remote_ip)) {
@@ -2084,7 +2105,7 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni,
if (netif_rx(reply) == NET_RX_DROP) {
dev_dstats_rx_dropped(dev);
- vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, NULL,
+ vxlan_vnifilter_count(vxlan, cfg, vni, NULL,
VXLAN_VNI_STATS_RX_DROPS, 0);
}
} else if (flags & VXLAN_F_L3MISS) {
@@ -2749,10 +2770,12 @@ static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev)
u32 nhid = 0;
u32 flags;
- cfg = &vxlan->cfg;
+ rcu_read_lock();
+ cfg = rcu_dereference(vxlan->cfg);
flags = cfg->flags;
default_vni = cfg->vni;
saddr_family = cfg->saddr.sa.sa_family;
+ rcu_read_unlock();
info = skb_tunnel_info(skb);
@@ -2867,12 +2890,17 @@ static void vxlan_cleanup(struct timer_list *t)
{
struct vxlan_dev *vxlan = timer_container_of(vxlan, t, age_timer);
unsigned long next_timer = jiffies + FDB_AGE_INTERVAL;
+ const struct vxlan_config *cfg;
struct vxlan_fdb *f;
if (!netif_running(vxlan->dev))
return;
rcu_read_lock();
+ cfg = rcu_dereference(vxlan->cfg);
+ if (!cfg)
+ goto out;
+
hlist_for_each_entry_rcu(f, &vxlan->fdb_list, fdb_node) {
unsigned long timeout;
@@ -2882,7 +2910,7 @@ static void vxlan_cleanup(struct timer_list *t)
if (f->flags & NTF_EXT_LEARNED)
continue;
- timeout = READ_ONCE(f->updated) + vxlan->cfg.age_interval * HZ;
+ timeout = READ_ONCE(f->updated) + cfg->age_interval * HZ;
if (time_before_eq(timeout, jiffies)) {
spin_lock(&vxlan->hash_lock);
if (!hlist_unhashed(&f->fdb_node)) {
@@ -2896,6 +2924,7 @@ static void vxlan_cleanup(struct timer_list *t)
next_timer = timeout;
}
}
+out:
rcu_read_unlock();
mod_timer(&vxlan->age_timer, next_timer);
@@ -2926,13 +2955,16 @@ static void vxlan_vs_add_dev(struct vxlan_sock *vs, struct vxlan_dev *vxlan,
static int vxlan_init(struct net_device *dev)
{
struct vxlan_dev *vxlan = netdev_priv(dev);
+ const struct vxlan_config *cfg;
int err;
+ cfg = rtnl_dereference(vxlan->cfg);
+
err = rhashtable_init(&vxlan->fdb_hash_tbl, &vxlan_fdb_rht_params);
if (err)
return err;
- if (vxlan->cfg.flags & VXLAN_F_VNIFILTER) {
+ if (cfg->flags & VXLAN_F_VNIFILTER) {
err = vxlan_vnigroup_init(vxlan);
if (err)
goto err_rhashtable_destroy;
@@ -2952,7 +2984,7 @@ static int vxlan_init(struct net_device *dev)
err_gro_cells_destroy:
gro_cells_destroy(&vxlan->gro_cells);
err_vnigroup_uninit:
- if (vxlan->cfg.flags & VXLAN_F_VNIFILTER)
+ if (cfg->flags & VXLAN_F_VNIFILTER)
vxlan_vnigroup_uninit(vxlan);
err_rhashtable_destroy:
rhashtable_destroy(&vxlan->fdb_hash_tbl);
@@ -2962,10 +2994,13 @@ static int vxlan_init(struct net_device *dev)
static void vxlan_uninit(struct net_device *dev)
{
struct vxlan_dev *vxlan = netdev_priv(dev);
+ const struct vxlan_config *cfg;
+
+ cfg = rtnl_dereference(vxlan->cfg);
vxlan_mdb_fini(vxlan);
- if (vxlan->cfg.flags & VXLAN_F_VNIFILTER)
+ if (cfg && (cfg->flags & VXLAN_F_VNIFILTER))
vxlan_vnigroup_uninit(vxlan);
gro_cells_destroy(&vxlan->gro_cells);
@@ -2977,6 +3012,7 @@ static void vxlan_uninit(struct net_device *dev)
static int vxlan_open(struct net_device *dev)
{
struct vxlan_dev *vxlan = netdev_priv(dev);
+ const struct vxlan_config *cfg;
int ret;
ret = vxlan_sock_add(vxlan);
@@ -2989,7 +3025,8 @@ static int vxlan_open(struct net_device *dev)
return ret;
}
- if (vxlan->cfg.age_interval)
+ cfg = rtnl_dereference(vxlan->cfg);
+ if (cfg && cfg->age_interval)
mod_timer(&vxlan->age_timer, jiffies + FDB_AGE_INTERVAL);
return ret;
@@ -3011,8 +3048,10 @@ struct vxlan_fdb_flush_desc {
static bool vxlan_fdb_is_default_entry(const struct vxlan_fdb *f,
const struct vxlan_dev *vxlan)
{
+ const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg);
+
return is_zero_ether_addr(f->key.eth_addr) &&
- f->key.vni == vxlan->cfg.vni;
+ f->key.vni == cfg->vni;
}
static bool vxlan_fdb_nhid_matches(const struct vxlan_fdb *f, u32 nhid)
@@ -3229,14 +3268,18 @@ static int vxlan_change_mtu(struct net_device *dev, int new_mtu)
{
struct vxlan_dev *vxlan = netdev_priv(dev);
struct vxlan_rdst *dst = &vxlan->default_dst;
- struct net_device *lowerdev = __dev_get_by_index(vxlan->net,
- dst->remote_ifindex);
+ const struct vxlan_config *cfg;
+ struct net_device *lowerdev;
+
+ cfg = rtnl_dereference(vxlan->cfg);
+
+ lowerdev = __dev_get_by_index(vxlan->net, dst->remote_ifindex);
/* This check is different than dev->max_mtu, because it looks at
* the lowerdev->mtu, rather than the static dev->max_mtu
*/
if (lowerdev) {
- int max_mtu = lowerdev->mtu - vxlan_headroom(vxlan->cfg.flags);
+ int max_mtu = lowerdev->mtu - vxlan_headroom(cfg->flags);
if (new_mtu > max_mtu)
return -EINVAL;
}
@@ -3249,11 +3292,14 @@ static int vxlan_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb)
{
struct vxlan_dev *vxlan = netdev_priv(dev);
struct ip_tunnel_info *info = skb_tunnel_info(skb);
+ const struct vxlan_config *cfg;
__be16 sport, dport;
- sport = udp_flow_src_port(dev_net(dev), skb, vxlan->cfg.port_min,
- vxlan->cfg.port_max, true);
- dport = info->key.tp_dst ? : vxlan->cfg.dst_port;
+ cfg = rcu_dereference(vxlan->cfg);
+
+ sport = udp_flow_src_port(dev_net(dev), skb, cfg->port_min,
+ cfg->port_max, true);
+ dport = info->key.tp_dst ? : cfg->dst_port;
if (ip_tunnel_info_af(info) == AF_INET) {
struct vxlan_sock *sock4 = rcu_dereference(vxlan->vn4_sock);
@@ -3363,6 +3409,14 @@ static void vxlan_offload_rx_ports(struct net_device *dev, bool push)
}
}
+static void vxlan_free_dev(struct net_device *dev)
+{
+ struct vxlan_dev *vxlan = netdev_priv(dev);
+ struct vxlan_config *cfg = rcu_dereference_protected(vxlan->cfg, 1);
+
+ kfree(cfg);
+}
+
/* Initialize the device structure. */
static void vxlan_setup(struct net_device *dev)
{
@@ -3371,6 +3425,8 @@ static void vxlan_setup(struct net_device *dev)
eth_hw_addr_random(dev);
ether_setup(dev);
+ dev->priv_destructor = vxlan_free_dev;
+
dev->needs_free_netdev = true;
SET_NETDEV_DEVTYPE(dev, &vxlan_type);
@@ -3651,21 +3707,22 @@ static struct vxlan_sock *vxlan_socket_create(struct net *net, bool ipv6,
static int __vxlan_sock_add(struct vxlan_dev *vxlan, bool ipv6)
{
- bool metadata = vxlan->cfg.flags & VXLAN_F_COLLECT_METADATA;
+ const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg);
+ bool metadata = cfg->flags & VXLAN_F_COLLECT_METADATA;
struct vxlan_sock *vs = NULL;
struct vxlan_dev_node *node;
int l3mdev_index = 0;
ASSERT_RTNL();
- if (vxlan->cfg.remote_ifindex)
+ if (cfg->remote_ifindex)
l3mdev_index = l3mdev_master_upper_ifindex_by_index(
- vxlan->net, vxlan->cfg.remote_ifindex);
+ vxlan->net, cfg->remote_ifindex);
- if (!vxlan->cfg.no_share) {
+ if (!cfg->no_share) {
rcu_read_lock();
vs = vxlan_find_sock(vxlan->net, ipv6 ? AF_INET6 : AF_INET,
- vxlan->cfg.dst_port, vxlan->cfg.flags,
+ cfg->dst_port, cfg->flags,
l3mdev_index);
if (vs && !refcount_inc_not_zero(&vs->refcnt)) {
rcu_read_unlock();
@@ -3675,7 +3732,7 @@ static int __vxlan_sock_add(struct vxlan_dev *vxlan, bool ipv6)
}
if (!vs)
vs = vxlan_socket_create(vxlan->net, ipv6,
- vxlan->cfg.dst_port, vxlan->cfg.flags,
+ cfg->dst_port, cfg->flags,
l3mdev_index);
if (IS_ERR(vs))
return PTR_ERR(vs);
@@ -3690,7 +3747,7 @@ static int __vxlan_sock_add(struct vxlan_dev *vxlan, bool ipv6)
node = &vxlan->hlist4;
}
- if (metadata && (vxlan->cfg.flags & VXLAN_F_VNIFILTER))
+ if (metadata && (cfg->flags & VXLAN_F_VNIFILTER))
vxlan_vs_add_vnigrp(vxlan, vs, ipv6);
else
vxlan_vs_add_dev(vs, vxlan, node);
@@ -3700,11 +3757,14 @@ static int __vxlan_sock_add(struct vxlan_dev *vxlan, bool ipv6)
static int vxlan_sock_add(struct vxlan_dev *vxlan)
{
- bool metadata = vxlan->cfg.flags & VXLAN_F_COLLECT_METADATA;
- bool ipv6 = vxlan->cfg.flags & VXLAN_F_IPV6 || metadata;
- bool ipv4 = !ipv6 || metadata;
+ const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg);
+ bool metadata, ipv6, ipv4;
int ret = 0;
+ metadata = cfg->flags & VXLAN_F_COLLECT_METADATA;
+ ipv6 = (cfg->flags & VXLAN_F_IPV6) || metadata;
+ ipv4 = !ipv6 || metadata;
+
RCU_INIT_POINTER(vxlan->vn4_sock, NULL);
#if IS_ENABLED(CONFIG_IPV6)
RCU_INIT_POINTER(vxlan->vn6_sock, NULL);
@@ -3728,22 +3788,27 @@ int vxlan_vni_in_use(struct net *src_net, struct vxlan_dev *vxlan,
struct vxlan_dev *tmp;
list_for_each_entry(tmp, &vn->vxlan_list, next) {
+ const struct vxlan_config *tmp_cfg;
+
if (tmp == vxlan)
continue;
- if (tmp->cfg.flags & VXLAN_F_VNIFILTER) {
+
+ tmp_cfg = rtnl_dereference(tmp->cfg);
+
+ if (tmp_cfg->flags & VXLAN_F_VNIFILTER) {
if (!vxlan_vnifilter_lookup(tmp, vni))
continue;
- } else if (tmp->cfg.vni != vni) {
+ } else if (tmp_cfg->vni != vni) {
continue;
}
- if (tmp->cfg.dst_port != conf->dst_port)
+ if (tmp_cfg->dst_port != conf->dst_port)
continue;
- if ((tmp->cfg.flags & (VXLAN_F_RCV_FLAGS | VXLAN_F_IPV6)) !=
+ if ((tmp_cfg->flags & (VXLAN_F_RCV_FLAGS | VXLAN_F_IPV6)) !=
(conf->flags & (VXLAN_F_RCV_FLAGS | VXLAN_F_IPV6)))
continue;
if ((conf->flags & VXLAN_F_IPV6_LINKLOCAL) &&
- tmp->cfg.remote_ifindex != conf->remote_ifindex)
+ tmp_cfg->remote_ifindex != conf->remote_ifindex)
continue;
return -EEXIST;
@@ -3905,7 +3970,7 @@ static int vxlan_config_validate(struct net *src_net, struct vxlan_config *conf,
}
static void vxlan_config_apply(struct net_device *dev,
- struct vxlan_config *conf,
+ struct vxlan_config *new_cfg,
struct net_device *lowerdev,
struct net *src_net,
bool changelink)
@@ -3913,8 +3978,9 @@ static void vxlan_config_apply(struct net_device *dev,
struct vxlan_dev *vxlan = netdev_priv(dev);
struct vxlan_rdst *dst = &vxlan->default_dst;
unsigned short needed_headroom = ETH_HLEN;
+ struct vxlan_config *old_cfg;
int max_mtu = ETH_MAX_MTU;
- u32 flags = conf->flags;
+ u32 flags = new_cfg->flags;
if (!changelink) {
if (flags & VXLAN_F_GPE)
@@ -3922,18 +3988,18 @@ static void vxlan_config_apply(struct net_device *dev,
else
vxlan_ether_setup(dev);
- if (conf->mtu)
- dev->mtu = conf->mtu;
+ if (new_cfg->mtu)
+ dev->mtu = new_cfg->mtu;
vxlan->net = src_net;
}
- dst->remote_vni = conf->vni;
+ dst->remote_vni = new_cfg->vni;
- memcpy(&dst->remote_ip, &conf->remote_ip, sizeof(conf->remote_ip));
+ memcpy(&dst->remote_ip, &new_cfg->remote_ip, sizeof(new_cfg->remote_ip));
if (lowerdev) {
- dst->remote_ifindex = conf->remote_ifindex;
+ dst->remote_ifindex = new_cfg->remote_ifindex;
netif_inherit_tso_max(dev, lowerdev);
@@ -3946,7 +4012,7 @@ static void vxlan_config_apply(struct net_device *dev,
if (max_mtu < ETH_MIN_MTU)
max_mtu = ETH_MIN_MTU;
- if (!changelink && !conf->mtu)
+ if (!changelink && !new_cfg->mtu)
dev->mtu = max_mtu;
}
@@ -3958,7 +4024,10 @@ static void vxlan_config_apply(struct net_device *dev,
needed_headroom += vxlan_headroom(flags);
dev->needed_headroom = needed_headroom;
- memcpy(&vxlan->cfg, conf, sizeof(*conf));
+ old_cfg = rtnl_dereference(vxlan->cfg);
+ rcu_assign_pointer(vxlan->cfg, new_cfg);
+ if (old_cfg)
+ kfree_rcu(old_cfg, rcu);
}
static int vxlan_dev_configure(struct net *src_net, struct net_device *dev,
@@ -3967,13 +4036,18 @@ static int vxlan_dev_configure(struct net *src_net, struct net_device *dev,
{
struct vxlan_dev *vxlan = netdev_priv(dev);
struct net_device *lowerdev;
+ struct vxlan_config *new_cfg;
int ret;
ret = vxlan_config_validate(src_net, conf, &lowerdev, vxlan, extack);
if (ret)
return ret;
- vxlan_config_apply(dev, conf, lowerdev, src_net, false);
+ new_cfg = kmemdup(conf, sizeof(*conf), GFP_KERNEL);
+ if (!new_cfg)
+ return -ENOMEM;
+
+ vxlan_config_apply(dev, new_cfg, lowerdev, src_net, false);
return 0;
}
@@ -3985,6 +4059,7 @@ static int __vxlan_dev_create(struct net *net, struct net_device *dev,
struct vxlan_net *vn = net_generic(net, vxlan_net_id);
struct vxlan_dev *vxlan = netdev_priv(dev);
struct net_device *remote_dev = NULL;
+ const struct vxlan_config *cfg;
struct vxlan_rdst *dst;
int err;
@@ -3993,6 +4068,8 @@ static int __vxlan_dev_create(struct net *net, struct net_device *dev,
if (err)
return err;
+ cfg = rtnl_dereference(vxlan->cfg);
+
dev->ethtool_ops = &vxlan_ethtool_ops;
err = register_netdevice(dev);
@@ -4024,7 +4101,7 @@ static int __vxlan_dev_create(struct net *net, struct net_device *dev,
&dst->remote_ip,
NUD_REACHABLE | NUD_PERMANENT,
NLM_F_EXCL | NLM_F_CREATE,
- vxlan->cfg.dst_port,
+ cfg->dst_port,
dst->remote_vni,
dst->remote_vni,
dst->remote_ifindex,
@@ -4088,8 +4165,12 @@ static int vxlan_nl2conf(struct nlattr *tb[], struct nlattr *data[],
memset(conf, 0, sizeof(*conf));
/* if changelink operation, start with old existing cfg */
- if (changelink)
- memcpy(conf, &vxlan->cfg, sizeof(*conf));
+ if (changelink) {
+ const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg);
+
+ if (cfg)
+ memcpy(conf, cfg, sizeof(*conf));
+ }
if (data[IFLA_VXLAN_ID]) {
__be32 vni = cpu_to_be32(nla_get_u32(data[IFLA_VXLAN_ID]));
@@ -4436,9 +4517,11 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[],
struct netlink_ext_ack *extack)
{
struct vxlan_dev *vxlan = netdev_priv(dev);
+ const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg);
bool rem_ip_changed, change_igmp;
struct net_device *lowerdev;
struct vxlan_config conf;
+ struct vxlan_config *new_cfg;
struct vxlan_rdst *dst;
int err;
@@ -4452,13 +4535,19 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[],
if (err)
return err;
+ new_cfg = kmemdup(&conf, sizeof(conf), GFP_KERNEL);
+ if (!new_cfg)
+ return -ENOMEM;
+
if (dst->remote_dev == lowerdev)
lowerdev = NULL;
err = netdev_adjacent_change_prepare(dst->remote_dev, lowerdev, dev,
extack);
- if (err)
+ if (err) {
+ kfree(new_cfg);
return err;
+ }
rem_ip_changed = !vxlan_addr_equal(&conf.remote_ip, &dst->remote_ip);
change_igmp = vxlan->dev->flags & IFF_UP &&
@@ -4473,7 +4562,7 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[],
&conf.remote_ip,
NUD_REACHABLE | NUD_PERMANENT,
NLM_F_APPEND | NLM_F_CREATE,
- vxlan->cfg.dst_port,
+ cfg->dst_port,
conf.vni, conf.vni,
conf.remote_ifindex,
NTF_SELF, 0, true, extack);
@@ -4481,13 +4570,14 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[],
spin_unlock_bh(&vxlan->hash_lock);
netdev_adjacent_change_abort(dst->remote_dev,
lowerdev, dev);
+ kfree(new_cfg);
return err;
}
}
if (!vxlan_addr_any(&dst->remote_ip))
__vxlan_fdb_delete(vxlan, all_zeros_mac,
dst->remote_ip,
- vxlan->cfg.dst_port,
+ cfg->dst_port,
dst->remote_vni,
dst->remote_vni,
dst->remote_ifindex,
@@ -4497,12 +4587,13 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[],
/* If vni filtering device, also update fdb entries of
* all vnis that were using default remote ip
*/
- if (vxlan->cfg.flags & VXLAN_F_VNIFILTER) {
+ if (cfg->flags & VXLAN_F_VNIFILTER) {
err = vxlan_vnilist_update_group(vxlan, &dst->remote_ip,
&conf.remote_ip, extack);
if (err) {
netdev_adjacent_change_abort(dst->remote_dev,
lowerdev, dev);
+ kfree(new_cfg);
return err;
}
}
@@ -4511,13 +4602,13 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[],
if (change_igmp && vxlan_addr_multicast(&dst->remote_ip))
err = vxlan_multicast_leave(vxlan);
- if (conf.age_interval != vxlan->cfg.age_interval)
+ if (conf.age_interval != cfg->age_interval)
mod_timer(&vxlan->age_timer, jiffies);
netdev_adjacent_change_commit(dst->remote_dev, lowerdev, dev);
if (lowerdev && lowerdev != dst->remote_dev)
dst->remote_dev = lowerdev;
- vxlan_config_apply(dev, &conf, lowerdev, vxlan->net, true);
+ vxlan_config_apply(dev, new_cfg, lowerdev, vxlan->net, true);
if (!err && change_igmp &&
vxlan_addr_multicast(&dst->remote_ip))
@@ -4580,11 +4671,16 @@ static size_t vxlan_get_size(const struct net_device *dev)
static int vxlan_fill_info(struct sk_buff *skb, const struct net_device *dev)
{
const struct vxlan_dev *vxlan = netdev_priv(dev);
- const struct vxlan_rdst *dst = &vxlan->default_dst;
- struct ifla_vxlan_port_range ports = {
- .low = htons(vxlan->cfg.port_min),
- .high = htons(vxlan->cfg.port_max),
- };
+ struct ifla_vxlan_port_range ports;
+ const struct vxlan_config *cfg;
+ const struct vxlan_rdst *dst;
+
+ dst = &vxlan->default_dst;
+
+ cfg = rtnl_dereference(vxlan->cfg);
+
+ ports.low = htons(cfg->port_min);
+ ports.high = htons(cfg->port_max);
if (nla_put_u32(skb, IFLA_VXLAN_ID, be32_to_cpu(dst->remote_vni)))
goto nla_put_failure;
@@ -4606,79 +4702,79 @@ static int vxlan_fill_info(struct sk_buff *skb, const struct net_device *dev)
if (dst->remote_ifindex && nla_put_u32(skb, IFLA_VXLAN_LINK, dst->remote_ifindex))
goto nla_put_failure;
- if (!vxlan_addr_any(&vxlan->cfg.saddr)) {
- if (vxlan->cfg.saddr.sa.sa_family == AF_INET) {
+ if (!vxlan_addr_any(&cfg->saddr)) {
+ if (cfg->saddr.sa.sa_family == AF_INET) {
if (nla_put_in_addr(skb, IFLA_VXLAN_LOCAL,
- vxlan->cfg.saddr.sin.sin_addr.s_addr))
+ cfg->saddr.sin.sin_addr.s_addr))
goto nla_put_failure;
#if IS_ENABLED(CONFIG_IPV6)
} else {
if (nla_put_in6_addr(skb, IFLA_VXLAN_LOCAL6,
- &vxlan->cfg.saddr.sin6.sin6_addr))
+ &cfg->saddr.sin6.sin6_addr))
goto nla_put_failure;
#endif
}
}
- if (nla_put_u8(skb, IFLA_VXLAN_TTL, vxlan->cfg.ttl) ||
+ if (nla_put_u8(skb, IFLA_VXLAN_TTL, cfg->ttl) ||
nla_put_u8(skb, IFLA_VXLAN_TTL_INHERIT,
- !!(vxlan->cfg.flags & VXLAN_F_TTL_INHERIT)) ||
- nla_put_u8(skb, IFLA_VXLAN_TOS, vxlan->cfg.tos) ||
- nla_put_u8(skb, IFLA_VXLAN_DF, vxlan->cfg.df) ||
- nla_put_be32(skb, IFLA_VXLAN_LABEL, vxlan->cfg.label) ||
- nla_put_u32(skb, IFLA_VXLAN_LABEL_POLICY, vxlan->cfg.label_policy) ||
+ !!(cfg->flags & VXLAN_F_TTL_INHERIT)) ||
+ nla_put_u8(skb, IFLA_VXLAN_TOS, cfg->tos) ||
+ nla_put_u8(skb, IFLA_VXLAN_DF, cfg->df) ||
+ nla_put_be32(skb, IFLA_VXLAN_LABEL, cfg->label) ||
+ nla_put_u32(skb, IFLA_VXLAN_LABEL_POLICY, cfg->label_policy) ||
nla_put_u8(skb, IFLA_VXLAN_LEARNING,
- !!(vxlan->cfg.flags & VXLAN_F_LEARN)) ||
+ !!(cfg->flags & VXLAN_F_LEARN)) ||
nla_put_u8(skb, IFLA_VXLAN_PROXY,
- !!(vxlan->cfg.flags & VXLAN_F_PROXY)) ||
+ !!(cfg->flags & VXLAN_F_PROXY)) ||
nla_put_u8(skb, IFLA_VXLAN_RSC,
- !!(vxlan->cfg.flags & VXLAN_F_RSC)) ||
+ !!(cfg->flags & VXLAN_F_RSC)) ||
nla_put_u8(skb, IFLA_VXLAN_L2MISS,
- !!(vxlan->cfg.flags & VXLAN_F_L2MISS)) ||
+ !!(cfg->flags & VXLAN_F_L2MISS)) ||
nla_put_u8(skb, IFLA_VXLAN_L3MISS,
- !!(vxlan->cfg.flags & VXLAN_F_L3MISS)) ||
+ !!(cfg->flags & VXLAN_F_L3MISS)) ||
nla_put_u8(skb, IFLA_VXLAN_COLLECT_METADATA,
- !!(vxlan->cfg.flags & VXLAN_F_COLLECT_METADATA)) ||
- nla_put_u32(skb, IFLA_VXLAN_AGEING, vxlan->cfg.age_interval) ||
- nla_put_u32(skb, IFLA_VXLAN_LIMIT, vxlan->cfg.addrmax) ||
- nla_put_be16(skb, IFLA_VXLAN_PORT, vxlan->cfg.dst_port) ||
+ !!(cfg->flags & VXLAN_F_COLLECT_METADATA)) ||
+ nla_put_u32(skb, IFLA_VXLAN_AGEING, cfg->age_interval) ||
+ nla_put_u32(skb, IFLA_VXLAN_LIMIT, cfg->addrmax) ||
+ nla_put_be16(skb, IFLA_VXLAN_PORT, cfg->dst_port) ||
nla_put_u8(skb, IFLA_VXLAN_UDP_CSUM,
- !(vxlan->cfg.flags & VXLAN_F_UDP_ZERO_CSUM_TX)) ||
+ !(cfg->flags & VXLAN_F_UDP_ZERO_CSUM_TX)) ||
nla_put_u8(skb, IFLA_VXLAN_UDP_ZERO_CSUM6_TX,
- !!(vxlan->cfg.flags & VXLAN_F_UDP_ZERO_CSUM6_TX)) ||
+ !!(cfg->flags & VXLAN_F_UDP_ZERO_CSUM6_TX)) ||
nla_put_u8(skb, IFLA_VXLAN_UDP_ZERO_CSUM6_RX,
- !!(vxlan->cfg.flags & VXLAN_F_UDP_ZERO_CSUM6_RX)) ||
+ !!(cfg->flags & VXLAN_F_UDP_ZERO_CSUM6_RX)) ||
nla_put_u8(skb, IFLA_VXLAN_REMCSUM_TX,
- !!(vxlan->cfg.flags & VXLAN_F_REMCSUM_TX)) ||
+ !!(cfg->flags & VXLAN_F_REMCSUM_TX)) ||
nla_put_u8(skb, IFLA_VXLAN_REMCSUM_RX,
- !!(vxlan->cfg.flags & VXLAN_F_REMCSUM_RX)) ||
+ !!(cfg->flags & VXLAN_F_REMCSUM_RX)) ||
nla_put_u8(skb, IFLA_VXLAN_LOCALBYPASS,
- !!(vxlan->cfg.flags & VXLAN_F_LOCALBYPASS)))
+ !!(cfg->flags & VXLAN_F_LOCALBYPASS)))
goto nla_put_failure;
if (nla_put(skb, IFLA_VXLAN_PORT_RANGE, sizeof(ports), &ports))
goto nla_put_failure;
- if (vxlan->cfg.flags & VXLAN_F_GBP &&
+ if (cfg->flags & VXLAN_F_GBP &&
nla_put_flag(skb, IFLA_VXLAN_GBP))
goto nla_put_failure;
- if (vxlan->cfg.flags & VXLAN_F_GPE &&
+ if (cfg->flags & VXLAN_F_GPE &&
nla_put_flag(skb, IFLA_VXLAN_GPE))
goto nla_put_failure;
- if (vxlan->cfg.flags & VXLAN_F_REMCSUM_NOPARTIAL &&
+ if (cfg->flags & VXLAN_F_REMCSUM_NOPARTIAL &&
nla_put_flag(skb, IFLA_VXLAN_REMCSUM_NOPARTIAL))
goto nla_put_failure;
- if (vxlan->cfg.flags & VXLAN_F_VNIFILTER &&
+ if (cfg->flags & VXLAN_F_VNIFILTER &&
nla_put_u8(skb, IFLA_VXLAN_VNIFILTER,
- !!(vxlan->cfg.flags & VXLAN_F_VNIFILTER)))
+ !!(cfg->flags & VXLAN_F_VNIFILTER)))
goto nla_put_failure;
if (nla_put(skb, IFLA_VXLAN_RESERVED_BITS,
- sizeof(vxlan->cfg.reserved_bits),
- &vxlan->cfg.reserved_bits))
+ sizeof(cfg->reserved_bits),
+ &cfg->reserved_bits))
goto nla_put_failure;
return 0;
diff --git a/drivers/net/vxlan/vxlan_mdb.c b/drivers/net/vxlan/vxlan_mdb.c
index c92217906d5ee90c92f3da50ca9a276793ed9aca..eb15bb052a6a874a61c9a3306e1132cca3a98fdf 100644
--- a/drivers/net/vxlan/vxlan_mdb.c
+++ b/drivers/net/vxlan/vxlan_mdb.c
@@ -164,7 +164,7 @@ static int vxlan_mdb_entry_info_fill(const struct vxlan_dev *vxlan,
const struct vxlan_mdb_entry *mdb_entry,
const struct vxlan_mdb_remote *remote)
{
- const struct vxlan_config *cfg = &vxlan->cfg;
+ const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg);
struct vxlan_rdst *rd = rtnl_dereference(remote->rd);
struct br_mdb_entry e;
struct nlattr *nest;
@@ -605,7 +605,9 @@ static int vxlan_mdb_config_init(struct vxlan_mdb_config *cfg,
{
struct br_mdb_entry *entry = nla_data(tb[MDBA_SET_ENTRY]);
struct vxlan_dev *vxlan = netdev_priv(dev);
- const struct vxlan_config *vcfg = &vxlan->cfg;
+ const struct vxlan_config *vcfg;
+
+ vcfg = rtnl_dereference(vxlan->cfg);
memset(cfg, 0, sizeof(*cfg));
cfg->vxlan = vxlan;
@@ -940,12 +942,12 @@ vxlan_mdb_nlmsg_remote_size(const struct vxlan_dev *vxlan,
const struct vxlan_mdb_entry *mdb_entry,
const struct vxlan_mdb_remote *remote)
{
- const struct vxlan_config *cfg = &vxlan->cfg;
+ const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg);
const struct vxlan_mdb_entry_key *group = &mdb_entry->key;
struct vxlan_rdst *rd = rtnl_dereference(remote->rd);
size_t nlmsg_size;
- /* MDBA_MDB_ENTRY_INFO */
+ /* MDBA_MDB_ENTRY_INFO */
nlmsg_size = nla_total_size(sizeof(struct br_mdb_entry)) +
/* MDBA_MDB_EATTR_TIMER */
nla_total_size(sizeof(u32));
@@ -1205,7 +1207,7 @@ vxlan_mdb_entry_get(struct vxlan_dev *vxlan,
goto err_free_entry;
if (hlist_is_singular_node(&mdb_entry->mdb_node, &vxlan->mdb_list))
- vxlan->cfg.flags |= VXLAN_F_MDB;
+ rtnl_dereference(vxlan->cfg)->flags |= VXLAN_F_MDB;
return mdb_entry;
@@ -1222,7 +1224,7 @@ static void vxlan_mdb_entry_put(struct vxlan_dev *vxlan,
return;
if (hlist_is_singular_node(&mdb_entry->mdb_node, &vxlan->mdb_list))
- vxlan->cfg.flags &= ~VXLAN_F_MDB;
+ rtnl_dereference(vxlan->cfg)->flags &= ~VXLAN_F_MDB;
rhashtable_remove_fast(&vxlan->mdb_tbl, &mdb_entry->rhnode,
vxlan_mdb_rht_params);
@@ -1610,7 +1612,7 @@ struct vxlan_mdb_entry *vxlan_mdb_entry_skb_get(struct vxlan_dev *vxlan,
struct sk_buff *skb,
__be32 src_vni)
{
- const struct vxlan_config *cfg = &vxlan->cfg;
+ const struct vxlan_config *cfg;
struct vxlan_mdb_entry *mdb_entry;
struct vxlan_mdb_entry_key group;
@@ -1618,6 +1620,10 @@ struct vxlan_mdb_entry *vxlan_mdb_entry_skb_get(struct vxlan_dev *vxlan,
is_broadcast_ether_addr(eth_hdr(skb)->h_dest))
return NULL;
+ cfg = rcu_dereference(vxlan->cfg);
+ if (!cfg)
+ return NULL;
+
/* When not in collect metadata mode, 'src_vni' is zero, but MDB
* entries are stored with the VNI of the VXLAN device.
*/
@@ -1691,8 +1697,11 @@ netdev_tx_t vxlan_mdb_xmit(struct vxlan_dev *vxlan,
struct sk_buff *skb)
{
struct vxlan_mdb_remote *remote, *fremote = NULL;
+ const struct vxlan_config *cfg;
__be32 src_vni = mdb_entry->key.vni;
+ cfg = rcu_dereference(vxlan->cfg);
+
list_for_each_entry_rcu(remote, &mdb_entry->remotes, list) {
struct sk_buff *skb1;
@@ -1708,12 +1717,12 @@ netdev_tx_t vxlan_mdb_xmit(struct vxlan_dev *vxlan,
skb1 = skb_clone(skb, GFP_ATOMIC);
if (skb1)
- vxlan_xmit_one(skb1, vxlan->dev, &vxlan->cfg, src_vni,
+ vxlan_xmit_one(skb1, vxlan->dev, cfg, src_vni,
rcu_dereference(remote->rd), false);
}
if (fremote)
- vxlan_xmit_one(skb, vxlan->dev, &vxlan->cfg, src_vni,
+ vxlan_xmit_one(skb, vxlan->dev, cfg, src_vni,
rcu_dereference(fremote->rd), false);
else
kfree_skb_reason(skb, SKB_DROP_REASON_NO_TX_TARGET);
@@ -1744,7 +1753,7 @@ void vxlan_mdb_fini(struct vxlan_dev *vxlan)
struct vxlan_mdb_flush_desc desc = {};
vxlan_mdb_flush(vxlan, &desc);
- WARN_ON_ONCE(vxlan->cfg.flags & VXLAN_F_MDB);
+ WARN_ON_ONCE(rtnl_dereference(vxlan->cfg)->flags & VXLAN_F_MDB);
rhashtable_free_and_destroy(&vxlan->mdb_tbl, vxlan_mdb_check_empty,
NULL);
}
diff --git a/drivers/net/vxlan/vxlan_multicast.c b/drivers/net/vxlan/vxlan_multicast.c
index 3b75b48dc726df40cebb233095a8a046ee274c30..e2cf10da274f1b608d8bb5020d2b87ebfedeff46 100644
--- a/drivers/net/vxlan/vxlan_multicast.c
+++ b/drivers/net/vxlan/vxlan_multicast.c
@@ -147,6 +147,8 @@ bool vxlan_group_used(struct vxlan_net *vn, struct vxlan_dev *dev,
#endif
list_for_each_entry(vxlan, &vn->vxlan_list, next) {
+ const struct vxlan_config *cfg;
+
if (!netif_running(vxlan->dev) || vxlan == dev)
continue;
@@ -158,7 +160,9 @@ bool vxlan_group_used(struct vxlan_net *vn, struct vxlan_dev *dev,
rtnl_dereference(vxlan->vn6_sock) != sock6)
continue;
#endif
- if (vxlan->cfg.flags & VXLAN_F_VNIFILTER) {
+ cfg = rtnl_dereference(vxlan->cfg);
+
+ if (cfg->flags & VXLAN_F_VNIFILTER) {
if (!vxlan_group_used_by_vnifilter(vxlan, ip, ifindex))
continue;
} else {
@@ -233,6 +237,7 @@ static int vxlan_multicast_leave_vnigrp(struct vxlan_dev *vxlan)
int vxlan_multicast_join(struct vxlan_dev *vxlan)
{
+ const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg);
int ret = 0;
if (vxlan_addr_multicast(&vxlan->default_dst.remote_ip)) {
@@ -244,7 +249,7 @@ int vxlan_multicast_join(struct vxlan_dev *vxlan)
return ret;
}
- if (vxlan->cfg.flags & VXLAN_F_VNIFILTER)
+ if (cfg->flags & VXLAN_F_VNIFILTER)
return vxlan_multicast_join_vnigrp(vxlan);
return 0;
@@ -252,6 +257,7 @@ int vxlan_multicast_join(struct vxlan_dev *vxlan)
int vxlan_multicast_leave(struct vxlan_dev *vxlan)
{
+ const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg);
struct vxlan_net *vn = net_generic(vxlan->net, vxlan_net_id);
int ret = 0;
@@ -263,7 +269,7 @@ int vxlan_multicast_leave(struct vxlan_dev *vxlan)
return ret;
}
- if (vxlan->cfg.flags & VXLAN_F_VNIFILTER)
+ if (cfg->flags & VXLAN_F_VNIFILTER)
return vxlan_multicast_leave_vnigrp(vxlan);
return 0;
diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c
index 3674dec5a59ae4cdb94319c8789826fbb9f0104c..f3dda671b4e45677944fd1f2db2afc453061e3a5 100644
--- a/drivers/net/vxlan/vxlan_vnifilter.c
+++ b/drivers/net/vxlan/vxlan_vnifilter.c
@@ -178,7 +178,7 @@ void vxlan_vnifilter_count(struct vxlan_dev *vxlan,
{
struct vxlan_vni_node *vnode;
- if (!cfg || !(cfg->flags & VXLAN_F_VNIFILTER))
+ if (!(cfg->flags & VXLAN_F_VNIFILTER))
return;
if (vninode) {
@@ -337,13 +337,15 @@ static int vxlan_vnifilter_dump_dev(const struct net_device *dev,
struct vxlan_vni_node *tmp, *v, *vbegin = NULL, *vend = NULL;
struct vxlan_dev *vxlan = netdev_priv(dev);
struct tunnel_msg *new_tmsg, *tmsg;
+ const struct vxlan_config *cfg;
int idx = 0, s_idx = cb->args[1];
struct vxlan_vni_group *vg;
struct nlmsghdr *nlh;
bool dump_stats;
int err = 0;
- if (!(vxlan->cfg.flags & VXLAN_F_VNIFILTER))
+ cfg = rcu_dereference(vxlan->cfg);
+ if (!(cfg->flags & VXLAN_F_VNIFILTER))
return -EINVAL;
/* RCU needed because of the vni locking rules (rcu || rtnl) */
@@ -478,6 +480,7 @@ static int vxlan_update_default_fdb_entry(struct vxlan_dev *vxlan, __be32 vni,
union vxlan_addr *remote_ip,
struct netlink_ext_ack *extack)
{
+ const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg);
struct vxlan_rdst *dst = &vxlan->default_dst;
int err = 0;
@@ -487,7 +490,7 @@ static int vxlan_update_default_fdb_entry(struct vxlan_dev *vxlan, __be32 vni,
remote_ip,
NUD_REACHABLE | NUD_PERMANENT,
NLM_F_APPEND | NLM_F_CREATE,
- vxlan->cfg.dst_port,
+ cfg->dst_port,
vni,
vni,
dst->remote_ifindex,
@@ -501,7 +504,7 @@ static int vxlan_update_default_fdb_entry(struct vxlan_dev *vxlan, __be32 vni,
if (old_remote_ip && !vxlan_addr_any(old_remote_ip)) {
__vxlan_fdb_delete(vxlan, all_zeros_mac,
*old_remote_ip,
- vxlan->cfg.dst_port,
+ cfg->dst_port,
vni, vni,
dst->remote_ifindex,
true);
@@ -615,6 +618,7 @@ static void vxlan_vni_delete_group(struct vxlan_dev *vxlan,
struct vxlan_vni_node *vninode)
{
struct vxlan_net *vn = net_generic(vxlan->net, vxlan_net_id);
+ const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg);
struct vxlan_rdst *dst = &vxlan->default_dst;
/* if per vni remote_ip not present, delete the
@@ -626,7 +630,7 @@ static void vxlan_vni_delete_group(struct vxlan_dev *vxlan,
__vxlan_fdb_delete(vxlan, all_zeros_mac,
(vxlan_addr_any(&vninode->remote_ip) ?
dst->remote_ip : vninode->remote_ip),
- vxlan->cfg.dst_port,
+ cfg->dst_port,
vninode->vni, vninode->vni,
dst->remote_ifindex,
true);
@@ -726,6 +730,7 @@ static int vxlan_vni_add(struct vxlan_dev *vxlan,
u32 vni, union vxlan_addr *group,
struct netlink_ext_ack *extack)
{
+ const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg);
struct vxlan_vni_node *vninode;
__be32 v = cpu_to_be32(vni);
bool changed = false;
@@ -734,7 +739,7 @@ static int vxlan_vni_add(struct vxlan_dev *vxlan,
if (vxlan_vnifilter_lookup(vxlan, v))
return vxlan_vni_update(vxlan, vg, v, group, &changed, extack);
- err = vxlan_vni_in_use(vxlan->net, vxlan, &vxlan->cfg, v);
+ err = vxlan_vni_in_use(vxlan->net, vxlan, cfg, v);
if (err) {
NL_SET_ERR_MSG(extack, "VNI in use");
return err;
@@ -943,6 +948,7 @@ static int vxlan_vnifilter_process(struct sk_buff *skb, struct nlmsghdr *nlh,
struct netlink_ext_ack *extack)
{
struct net *net = sock_net(skb->sk);
+ const struct vxlan_config *cfg;
struct tunnel_msg *tmsg;
struct vxlan_dev *vxlan;
struct net_device *dev;
@@ -967,8 +973,9 @@ static int vxlan_vnifilter_process(struct sk_buff *skb, struct nlmsghdr *nlh,
}
vxlan = netdev_priv(dev);
+ cfg = rtnl_dereference(vxlan->cfg);
- if (!(vxlan->cfg.flags & VXLAN_F_VNIFILTER))
+ if (!(cfg->flags & VXLAN_F_VNIFILTER))
return -EOPNOTSUPP;
nlmsg_for_each_attr_type(attr, VXLAN_VNIFILTER_ENTRY, nlh,
diff --git a/include/net/vxlan.h b/include/net/vxlan.h
index dfba89695efcfa46cbc56f005763e1086ce67b23..7321768b0f2c1e81467d90153c422ee576aaefa9 100644
--- a/include/net/vxlan.h
+++ b/include/net/vxlan.h
@@ -229,6 +229,7 @@ struct vxlan_config {
bool no_share;
enum ifla_vxlan_df df;
struct vxlanhdr reserved_bits;
+ struct rcu_head rcu;
};
enum {
@@ -301,7 +302,7 @@ struct vxlan_dev {
unsigned int addrcnt;
struct gro_cells gro_cells;
- struct vxlan_config cfg;
+ struct vxlan_config __rcu *cfg;
struct vxlan_vni_group __rcu *vnigrp;
diff --git a/net/openvswitch/vport-vxlan.c b/net/openvswitch/vport-vxlan.c
index b0a084864a858453715d8e51b3e95cc7b2d9370f..9b1101f6ce1e1ceee3032ce94b0886846e6e1f84 100644
--- a/net/openvswitch/vport-vxlan.c
+++ b/net/openvswitch/vport-vxlan.c
@@ -22,27 +22,40 @@ static struct vport_ops ovs_vxlan_netdev_vport_ops;
static int vxlan_get_options(const struct vport *vport, struct sk_buff *skb)
{
struct vxlan_dev *vxlan = netdev_priv(vport->dev);
- const struct vxlan_config *cfg = &vxlan->cfg;
- __be16 dst_port = cfg->dst_port;
+ const struct vxlan_config *cfg;
+ __be16 dst_port;
+ int err = 0;
- if (nla_put_u16(skb, OVS_TUNNEL_ATTR_DST_PORT, ntohs(dst_port)))
- return -EMSGSIZE;
+ rcu_read_lock();
+ cfg = rcu_dereference(vxlan->cfg);
+ dst_port = cfg->dst_port;
+
+ if (nla_put_u16(skb, OVS_TUNNEL_ATTR_DST_PORT, ntohs(dst_port))) {
+ err = -EMSGSIZE;
+ goto out;
+ }
if (cfg->flags & VXLAN_F_GBP) {
struct nlattr *exts;
exts = nla_nest_start_noflag(skb, OVS_TUNNEL_ATTR_EXTENSION);
- if (!exts)
- return -EMSGSIZE;
+ if (!exts) {
+ err = -EMSGSIZE;
+ goto out;
+ }
if (cfg->flags & VXLAN_F_GBP &&
- nla_put_flag(skb, OVS_VXLAN_EXT_GBP))
- return -EMSGSIZE;
+ nla_put_flag(skb, OVS_VXLAN_EXT_GBP)) {
+ err = -EMSGSIZE;
+ goto out;
+ }
nla_nest_end(skb, exts);
}
- return 0;
+out:
+ rcu_read_unlock();
+ return err;
}
static const struct nla_policy exts_policy[OVS_VXLAN_EXT_MAX + 1] = {
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply related
* [PATCH net-next 1/3] vxlan: pass vxlan_config pointer to helper functions
From: Eric Dumazet @ 2026-07-08 16:04 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, Andrew Lunn,
netdev, eric.dumazet, Eric Dumazet
In-Reply-To: <20260708160411.1355008-1-edumazet@google.com>
In preparation for converting vxlan->cfg to an RCU-protected pointer,
refactor internal helper functions in the RX, TX, MDB, VNIFILTER, and
OVS paths to accept a pointer to struct vxlan_config (or pass flags/
saddr_family where appropriate) rather than directly accessing
vxlan->cfg.
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
drivers/net/vxlan/vxlan_core.c | 249 +++++++++++++++-------------
drivers/net/vxlan/vxlan_mdb.c | 20 ++-
drivers/net/vxlan/vxlan_private.h | 6 +-
drivers/net/vxlan/vxlan_vnifilter.c | 5 +-
net/openvswitch/vport-vxlan.c | 7 +-
5 files changed, 158 insertions(+), 129 deletions(-)
diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c
index 67c367cc566233e809b0f70e0d939dd1c1ac0d9f..eb2608fb7139a18d905d9b1a5140f22a880818d6 100644
--- a/drivers/net/vxlan/vxlan_core.c
+++ b/drivers/net/vxlan/vxlan_core.c
@@ -377,14 +377,15 @@ static void vxlan_fdb_miss(struct vxlan_dev *vxlan, const u8 eth_addr[ETH_ALEN])
/* Look up Ethernet address in forwarding table */
static struct vxlan_fdb *vxlan_find_mac_rcu(struct vxlan_dev *vxlan,
+ const struct vxlan_config *cfg,
const u8 *mac, __be32 vni)
{
struct vxlan_fdb_key key;
memset(&key, 0, sizeof(key));
memcpy(key.eth_addr, mac, sizeof(key.eth_addr));
- if (!(vxlan->cfg.flags & VXLAN_F_COLLECT_METADATA))
- key.vni = vxlan->default_dst.remote_vni;
+ if (!(cfg->flags & VXLAN_F_COLLECT_METADATA))
+ key.vni = cfg->vni;
else
key.vni = vni;
@@ -393,11 +394,12 @@ static struct vxlan_fdb *vxlan_find_mac_rcu(struct vxlan_dev *vxlan,
}
static struct vxlan_fdb *vxlan_find_mac_tx(struct vxlan_dev *vxlan,
+ const struct vxlan_config *cfg,
const u8 *mac, __be32 vni)
{
struct vxlan_fdb *f;
- f = vxlan_find_mac_rcu(vxlan, mac, vni);
+ f = vxlan_find_mac_rcu(vxlan, cfg, mac, vni);
if (f) {
unsigned long now = jiffies;
@@ -416,7 +418,7 @@ static struct vxlan_fdb *vxlan_find_mac(struct vxlan_dev *vxlan,
lockdep_assert_held_once(&vxlan->hash_lock);
rcu_read_lock();
- f = vxlan_find_mac_rcu(vxlan, mac, vni);
+ f = vxlan_find_mac_rcu(vxlan, &vxlan->cfg, mac, vni);
rcu_read_unlock();
return f;
@@ -457,7 +459,7 @@ int vxlan_fdb_find_uc(struct net_device *dev, const u8 *mac, __be32 vni,
rcu_read_lock();
- f = vxlan_find_mac_rcu(vxlan, eth_addr, vni);
+ f = vxlan_find_mac_rcu(vxlan, &vxlan->cfg, eth_addr, vni);
if (f)
rdst = first_remote_rcu(f);
if (!rdst) {
@@ -1405,7 +1407,7 @@ static int vxlan_fdb_get(struct sk_buff *skb,
rcu_read_lock();
- f = vxlan_find_mac_rcu(vxlan, addr, vni);
+ f = vxlan_find_mac_rcu(vxlan, &vxlan->cfg, addr, vni);
if (!f) {
NL_SET_ERR_MSG(extack, "Fdb entry not found");
err = -ENOENT;
@@ -1423,6 +1425,7 @@ static int vxlan_fdb_get(struct sk_buff *skb,
* and Tunnel endpoint.
*/
static enum skb_drop_reason vxlan_snoop(struct net_device *dev,
+ const struct vxlan_config *cfg,
union vxlan_addr *src_ip,
const u8 *src_mac, u32 src_ifindex,
__be32 vni)
@@ -1441,7 +1444,7 @@ static enum skb_drop_reason vxlan_snoop(struct net_device *dev,
ifindex = src_ifindex;
#endif
- f = vxlan_find_mac_rcu(vxlan, src_mac, vni);
+ f = vxlan_find_mac_rcu(vxlan, cfg, src_mac, vni);
if (likely(f)) {
struct vxlan_rdst *rdst = first_remote_rcu(f);
unsigned long now = jiffies;
@@ -1477,9 +1480,9 @@ static enum skb_drop_reason vxlan_snoop(struct net_device *dev,
vxlan_fdb_update(vxlan, src_mac, src_ip,
NUD_REACHABLE,
NLM_F_EXCL|NLM_F_CREATE,
- vxlan->cfg.dst_port,
+ cfg->dst_port,
vni,
- vxlan->default_dst.remote_vni,
+ cfg->vni,
ifindex, NTF_SELF, 0, true, NULL);
spin_unlock(&vxlan->hash_lock);
}
@@ -1587,6 +1590,7 @@ static void vxlan_parse_gbp_hdr(struct sk_buff *skb, u32 vxflags,
}
static enum skb_drop_reason vxlan_set_mac(struct vxlan_dev *vxlan,
+ const struct vxlan_config *cfg,
struct vxlan_sock *vs,
struct sk_buff *skb, __be32 vni)
{
@@ -1612,10 +1616,10 @@ static enum skb_drop_reason vxlan_set_mac(struct vxlan_dev *vxlan,
#endif
}
- if (!(vxlan->cfg.flags & VXLAN_F_LEARN))
+ if (!(cfg->flags & VXLAN_F_LEARN))
return SKB_NOT_DROPPED_YET;
- return vxlan_snoop(skb->dev, &saddr, eth_hdr(skb)->h_source,
+ return vxlan_snoop(skb->dev, cfg, &saddr, eth_hdr(skb)->h_source,
ifindex, vni);
}
@@ -1646,18 +1650,20 @@ static bool vxlan_ecn_decapsulate(struct vxlan_sock *vs, void *oiph,
static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
{
struct vxlan_vni_node *vninode = NULL;
- const struct vxlanhdr *vh;
- struct vxlan_dev *vxlan;
- struct vxlan_sock *vs;
- struct vxlan_metadata _md;
- struct vxlan_metadata *md = &_md;
__be16 protocol = htons(ETH_P_TEB);
enum skb_drop_reason reason;
+ const struct vxlanhdr *vh;
+ struct vxlan_metadata *md;
+ struct vxlan_metadata _md;
+ struct vxlan_dev *vxlan;
bool raw_proto = false;
- void *oiph;
+ struct vxlan_sock *vs;
__be32 vni = 0;
+ void *oiph;
int nh;
+ md = &_md;
+
/* Need UDP and VXLAN header to be present */
reason = pskb_may_pull_reason(skb, VXLAN_HLEN);
if (reason)
@@ -1698,7 +1704,7 @@ static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
reason = SKB_DROP_REASON_VXLAN_INVALID_HDR;
DEV_STATS_INC(vxlan->dev, rx_frame_errors);
DEV_STATS_INC(vxlan->dev, rx_errors);
- vxlan_vnifilter_count(vxlan, vni, vninode,
+ vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, vninode,
VXLAN_VNI_STATS_RX_ERRORS, 0);
goto drop;
}
@@ -1748,7 +1754,7 @@ static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
*/
if (!raw_proto) {
- reason = vxlan_set_mac(vxlan, vs, skb, vni);
+ reason = vxlan_set_mac(vxlan, &vxlan->cfg, vs, skb, vni);
if (reason)
goto drop;
} else {
@@ -1769,7 +1775,7 @@ static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
if (reason) {
DEV_STATS_INC(vxlan->dev, rx_length_errors);
DEV_STATS_INC(vxlan->dev, rx_errors);
- vxlan_vnifilter_count(vxlan, vni, vninode,
+ vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, vninode,
VXLAN_VNI_STATS_RX_ERRORS, 0);
goto drop;
}
@@ -1781,7 +1787,7 @@ static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
reason = SKB_DROP_REASON_IP_TUNNEL_ECN;
DEV_STATS_INC(vxlan->dev, rx_frame_errors);
DEV_STATS_INC(vxlan->dev, rx_errors);
- vxlan_vnifilter_count(vxlan, vni, vninode,
+ vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, vninode,
VXLAN_VNI_STATS_RX_ERRORS, 0);
goto drop;
}
@@ -1791,14 +1797,14 @@ static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
if (unlikely(!(vxlan->dev->flags & IFF_UP))) {
rcu_read_unlock();
dev_dstats_rx_dropped(vxlan->dev);
- vxlan_vnifilter_count(vxlan, vni, vninode,
+ vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, vninode,
VXLAN_VNI_STATS_RX_DROPS, 0);
reason = SKB_DROP_REASON_DEV_READY;
goto drop;
}
dev_dstats_rx_add(vxlan->dev, skb->len);
- vxlan_vnifilter_count(vxlan, vni, vninode, VXLAN_VNI_STATS_RX, skb->len);
+ vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, vninode, VXLAN_VNI_STATS_RX, skb->len);
gro_cells_receive(&vxlan->gro_cells, skb);
rcu_read_unlock();
@@ -1839,7 +1845,7 @@ static int vxlan_err_lookup(struct sock *sk, struct sk_buff *skb)
return 0;
}
-static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
+static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni, u32 flags)
{
struct vxlan_dev *vxlan = netdev_priv(dev);
struct arphdr *parp;
@@ -1852,7 +1858,7 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
if (!pskb_may_pull(skb, arp_hdr_len(dev))) {
dev_dstats_tx_dropped(dev);
- vxlan_vnifilter_count(vxlan, vni, NULL,
+ vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, NULL,
VXLAN_VNI_STATS_TX_DROPS, 0);
goto out;
}
@@ -1890,7 +1896,7 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
}
rcu_read_lock();
- f = vxlan_find_mac_tx(vxlan, n->ha, vni);
+ f = vxlan_find_mac_tx(vxlan, &vxlan->cfg, n->ha, vni);
if (f)
rdst = first_remote_rcu(f);
if (rdst && vxlan_addr_any(&rdst->remote_ip)) {
@@ -1916,11 +1922,11 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
if (netif_rx(reply) == NET_RX_DROP) {
dev_dstats_rx_dropped(dev);
- vxlan_vnifilter_count(vxlan, vni, NULL,
+ vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, NULL,
VXLAN_VNI_STATS_RX_DROPS, 0);
}
- } else if (vxlan->cfg.flags & VXLAN_F_L3MISS) {
+ } else if (flags & VXLAN_F_L3MISS) {
union vxlan_addr ipa = {
.sin.sin_addr.s_addr = tip,
.sin.sin_family = AF_INET,
@@ -2027,7 +2033,7 @@ static struct sk_buff *vxlan_na_create(struct sk_buff *request,
return reply;
}
-static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
+static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni, u32 flags)
{
struct vxlan_dev *vxlan = netdev_priv(dev);
const struct in6_addr *daddr;
@@ -2059,7 +2065,7 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
goto out;
}
- f = vxlan_find_mac_tx(vxlan, n->ha, vni);
+ f = vxlan_find_mac_tx(vxlan, &vxlan->cfg, n->ha, vni);
if (f)
rdst = first_remote_rcu(f);
if (rdst && vxlan_addr_any(&rdst->remote_ip)) {
@@ -2078,10 +2084,10 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
if (netif_rx(reply) == NET_RX_DROP) {
dev_dstats_rx_dropped(dev);
- vxlan_vnifilter_count(vxlan, vni, NULL,
+ vxlan_vnifilter_count(vxlan, &vxlan->cfg, vni, NULL,
VXLAN_VNI_STATS_RX_DROPS, 0);
}
- } else if (vxlan->cfg.flags & VXLAN_F_L3MISS) {
+ } else if (flags & VXLAN_F_L3MISS) {
union vxlan_addr ipa = {
.sin6.sin6_addr = msg->target,
.sin6.sin6_family = AF_INET6,
@@ -2097,9 +2103,9 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
}
#endif
-static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)
+static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb,
+ const struct vxlan_config *cfg)
{
- struct vxlan_dev *vxlan = netdev_priv(dev);
struct neighbour *n;
if (is_multicast_ether_addr(eth_hdr(skb)->h_dest))
@@ -2115,7 +2121,7 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)
return false;
pip = ip_hdr(skb);
n = neigh_lookup(&arp_tbl, &pip->daddr, dev);
- if (!n && (vxlan->cfg.flags & VXLAN_F_L3MISS)) {
+ if (!n && (cfg->flags & VXLAN_F_L3MISS)) {
union vxlan_addr ipa = {
.sin.sin_addr.s_addr = pip->daddr,
.sin.sin_family = AF_INET,
@@ -2141,7 +2147,7 @@ static bool route_shortcircuit(struct net_device *dev, struct sk_buff *skb)
return false;
pip6 = ipv6_hdr(skb);
n = neigh_lookup(&nd_tbl, &pip6->daddr, dev);
- if (!n && (vxlan->cfg.flags & VXLAN_F_L3MISS)) {
+ if (!n && (cfg->flags & VXLAN_F_L3MISS)) {
union vxlan_addr ipa = {
.sin6.sin6_addr = pip6->daddr,
.sin6.sin6_family = AF_INET6,
@@ -2253,20 +2259,20 @@ static int vxlan_build_skb(struct sk_buff *skb, struct dst_entry *dst,
/* Bypass encapsulation if the destination is local */
static void vxlan_encap_bypass(struct sk_buff *skb, struct vxlan_dev *src_vxlan,
- struct vxlan_dev *dst_vxlan, __be32 vni,
- bool snoop)
+ struct vxlan_dev *dst_vxlan,
+ const struct vxlan_config *cfg,
+ __be32 vni, bool snoop)
{
union vxlan_addr loopback;
- union vxlan_addr *remote_ip = &dst_vxlan->default_dst.remote_ip;
unsigned int len = skb->len;
- struct net_device *dev;
+ struct net_device *dev = dst_vxlan->dev;
skb->pkt_type = PACKET_HOST;
skb->encapsulation = 0;
- skb->dev = dst_vxlan->dev;
+ skb->dev = dev;
__skb_pull(skb, skb_network_offset(skb));
- if (remote_ip->sa.sa_family == AF_INET) {
+ if (cfg->remote_ip.sa.sa_family == AF_INET) {
loopback.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
loopback.sa.sa_family = AF_INET;
#if IS_ENABLED(CONFIG_IPV6)
@@ -2277,26 +2283,25 @@ static void vxlan_encap_bypass(struct sk_buff *skb, struct vxlan_dev *src_vxlan,
}
rcu_read_lock();
- dev = skb->dev;
if (unlikely(!(dev->flags & IFF_UP))) {
kfree_skb_reason(skb, SKB_DROP_REASON_DEV_READY);
goto drop;
}
- if ((dst_vxlan->cfg.flags & VXLAN_F_LEARN) && snoop)
- vxlan_snoop(dev, &loopback, eth_hdr(skb)->h_source, 0, vni);
+ if ((cfg->flags & VXLAN_F_LEARN) && snoop)
+ vxlan_snoop(dev, cfg, &loopback, eth_hdr(skb)->h_source, 0, vni);
dev_dstats_tx_add(src_vxlan->dev, len);
- vxlan_vnifilter_count(src_vxlan, vni, NULL, VXLAN_VNI_STATS_TX, len);
+ vxlan_vnifilter_count(src_vxlan, cfg, vni, NULL, VXLAN_VNI_STATS_TX, len);
if (__netif_rx(skb) == NET_RX_SUCCESS) {
dev_dstats_rx_add(dst_vxlan->dev, len);
- vxlan_vnifilter_count(dst_vxlan, vni, NULL, VXLAN_VNI_STATS_RX,
+ vxlan_vnifilter_count(dst_vxlan, cfg, vni, NULL, VXLAN_VNI_STATS_RX,
len);
} else {
drop:
dev_dstats_rx_dropped(dev);
- vxlan_vnifilter_count(dst_vxlan, vni, NULL,
+ vxlan_vnifilter_count(dst_vxlan, cfg, vni, NULL,
VXLAN_VNI_STATS_RX_DROPS, 0);
}
rcu_read_unlock();
@@ -2304,6 +2309,7 @@ static void vxlan_encap_bypass(struct sk_buff *skb, struct vxlan_dev *src_vxlan,
static int encap_bypass_if_local(struct sk_buff *skb, struct net_device *dev,
struct vxlan_dev *vxlan,
+ const struct vxlan_config *cfg,
int addr_family,
__be16 dst_port, int dst_ifindex, __be32 vni,
struct dst_entry *dst,
@@ -2319,22 +2325,22 @@ static int encap_bypass_if_local(struct sk_buff *skb, struct net_device *dev,
/* Bypass encapsulation if the destination is local */
if (rt_flags & RTCF_LOCAL &&
!(rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) &&
- vxlan->cfg.flags & VXLAN_F_LOCALBYPASS) {
+ cfg->flags & VXLAN_F_LOCALBYPASS) {
struct vxlan_dev *dst_vxlan;
dst_release(dst);
dst_vxlan = vxlan_find_vni(vxlan->net, dst_ifindex, vni,
addr_family, dst_port,
- vxlan->cfg.flags);
+ cfg->flags);
if (!dst_vxlan) {
DEV_STATS_INC(dev, tx_errors);
- vxlan_vnifilter_count(vxlan, vni, NULL,
+ vxlan_vnifilter_count(vxlan, cfg, vni, NULL,
VXLAN_VNI_STATS_TX_ERRORS, 0);
kfree_skb_reason(skb, SKB_DROP_REASON_VXLAN_VNI_NOT_FOUND);
return -ENOENT;
}
- vxlan_encap_bypass(skb, vxlan, dst_vxlan, vni, true);
+ vxlan_encap_bypass(skb, vxlan, dst_vxlan, cfg, vni, true);
return 1;
}
@@ -2342,30 +2348,35 @@ static int encap_bypass_if_local(struct sk_buff *skb, struct net_device *dev,
}
void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
+ const struct vxlan_config *cfg,
__be32 default_vni, struct vxlan_rdst *rdst, bool did_rsc)
{
+ unsigned int pkt_len = skb->len;
+ __be16 src_port = 0, dst_port;
+ struct dst_entry *ndst = NULL;
+ enum skb_drop_reason reason;
+ struct vxlan_dev *vxlan;
struct dst_cache *dst_cache;
+ const struct iphdr *old_iph;
struct ip_tunnel_info *info;
struct ip_tunnel_key *pkey;
- struct ip_tunnel_key key;
- struct vxlan_dev *vxlan = netdev_priv(dev);
- const struct iphdr *old_iph;
+ struct vxlan_metadata *md;
struct vxlan_metadata _md;
- struct vxlan_metadata *md = &_md;
- unsigned int pkt_len = skb->len;
- __be16 src_port = 0, dst_port;
- struct dst_entry *ndst = NULL;
+ struct ip_tunnel_key key;
+ u32 flags = cfg->flags;
+ bool udp_sum = false;
+ bool no_eth_encap;
int addr_family;
+ bool use_cache;
+ __be32 vni = 0;
+ bool xnet;
__u8 tos, ttl;
int ifindex;
int err = 0;
- u32 flags = vxlan->cfg.flags;
- bool use_cache;
- bool udp_sum = false;
- bool xnet = !net_eq(vxlan->net, dev_net(vxlan->dev));
- enum skb_drop_reason reason;
- bool no_eth_encap;
- __be32 vni = 0;
+
+ vxlan = netdev_priv(dev);
+ xnet = !net_eq(vxlan->net, dev_net(vxlan->dev));
+ md = &_md;
no_eth_encap = flags & VXLAN_F_GPE && skb->protocol != htons(ETH_P_TEB);
reason = skb_vlan_inet_prepare(skb, no_eth_encap);
@@ -2385,23 +2396,23 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
if (vxlan_addr_any(&rdst->remote_ip)) {
if (did_rsc) {
/* short-circuited back to local bridge */
- vxlan_encap_bypass(skb, vxlan, vxlan,
+ vxlan_encap_bypass(skb, vxlan, vxlan, cfg,
default_vni, true);
return;
}
goto drop;
}
- addr_family = vxlan->cfg.saddr.sa.sa_family;
- dst_port = rdst->remote_port ? rdst->remote_port : vxlan->cfg.dst_port;
+ addr_family = cfg->saddr.sa.sa_family;
+ dst_port = rdst->remote_port ? rdst->remote_port : cfg->dst_port;
vni = (rdst->remote_vni) ? : default_vni;
ifindex = rdst->remote_ifindex;
if (addr_family == AF_INET) {
- key.u.ipv4.src = vxlan->cfg.saddr.sin.sin_addr.s_addr;
+ key.u.ipv4.src = cfg->saddr.sin.sin_addr.s_addr;
key.u.ipv4.dst = rdst->remote_ip.sin.sin_addr.s_addr;
} else {
- key.u.ipv6.src = vxlan->cfg.saddr.sin6.sin6_addr;
+ key.u.ipv6.src = cfg->saddr.sin6.sin6_addr;
key.u.ipv6.dst = rdst->remote_ip.sin6.sin6_addr;
}
@@ -2410,11 +2421,11 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
if (flags & VXLAN_F_TTL_INHERIT) {
ttl = ip_tunnel_get_ttl(old_iph, skb);
} else {
- ttl = vxlan->cfg.ttl;
+ ttl = cfg->ttl;
if (!ttl && vxlan_addr_multicast(&rdst->remote_ip))
ttl = 1;
}
- tos = vxlan->cfg.tos;
+ tos = cfg->tos;
if (tos == 1)
tos = ip_tunnel_get_dsfield(old_iph, skb);
if (tos && !info)
@@ -2425,9 +2436,9 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
else
udp_sum = !(flags & VXLAN_F_UDP_ZERO_CSUM6_TX);
#if IS_ENABLED(CONFIG_IPV6)
- switch (vxlan->cfg.label_policy) {
+ switch (cfg->label_policy) {
case VXLAN_LABEL_FIXED:
- key.label = vxlan->cfg.label;
+ key.label = cfg->label;
break;
case VXLAN_LABEL_INHERIT:
key.label = ip_tunnel_get_flowlabel(old_iph, skb);
@@ -2445,7 +2456,7 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
}
pkey = &info->key;
addr_family = ip_tunnel_info_af(info);
- dst_port = info->key.tp_dst ? : vxlan->cfg.dst_port;
+ dst_port = info->key.tp_dst ? : cfg->dst_port;
vni = tunnel_id_to_key32(info->key.tun_id);
ifindex = 0;
dst_cache = &info->dst_cache;
@@ -2458,8 +2469,8 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
tos = info->key.tos;
udp_sum = test_bit(IP_TUNNEL_CSUM_BIT, info->key.tun_flags);
}
- src_port = udp_flow_src_port(dev_net(dev), skb, vxlan->cfg.port_min,
- vxlan->cfg.port_max, true);
+ src_port = udp_flow_src_port(dev_net(dev), skb, cfg->port_min,
+ cfg->port_max, true);
rcu_read_lock();
if (addr_family == AF_INET) {
@@ -2492,15 +2503,15 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
if (!info) {
/* Bypass encapsulation if the destination is local */
- err = encap_bypass_if_local(skb, dev, vxlan, AF_INET,
+ err = encap_bypass_if_local(skb, dev, vxlan, cfg, AF_INET,
dst_port, ifindex, vni,
&rt->dst, rt->rt_flags);
if (err)
goto out_unlock;
- if (vxlan->cfg.df == VXLAN_DF_SET) {
+ if (cfg->df == VXLAN_DF_SET) {
df = htons(IP_DF);
- } else if (vxlan->cfg.df == VXLAN_DF_INHERIT) {
+ } else if (cfg->df == VXLAN_DF_INHERIT) {
struct ethhdr *eth = eth_hdr(skb);
if (ntohs(eth->h_proto) == ETH_P_IPV6 ||
@@ -2529,7 +2540,7 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
unclone->key.u.ipv4.src = pkey->u.ipv4.dst;
unclone->key.u.ipv4.dst = saddr;
}
- vxlan_encap_bypass(skb, vxlan, vxlan, vni, false);
+ vxlan_encap_bypass(skb, vxlan, vxlan, cfg, vni, false);
dst_release(ndst);
goto out_unlock;
}
@@ -2579,7 +2590,7 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
if (!info) {
u32 rt6i_flags = dst_rt6_info(ndst)->rt6i_flags;
- err = encap_bypass_if_local(skb, dev, vxlan, AF_INET6,
+ err = encap_bypass_if_local(skb, dev, vxlan, cfg, AF_INET6,
dst_port, ifindex, vni,
ndst, rt6i_flags);
if (err)
@@ -2603,7 +2614,7 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
unclone->key.u.ipv6.dst = saddr;
}
- vxlan_encap_bypass(skb, vxlan, vxlan, vni, false);
+ vxlan_encap_bypass(skb, vxlan, vxlan, cfg, vni, false);
dst_release(ndst);
goto out_unlock;
}
@@ -2624,14 +2635,14 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
ip6cb_flags);
#endif
}
- vxlan_vnifilter_count(vxlan, vni, NULL, VXLAN_VNI_STATS_TX, pkt_len);
+ vxlan_vnifilter_count(vxlan, cfg, vni, NULL, VXLAN_VNI_STATS_TX, pkt_len);
out_unlock:
rcu_read_unlock();
return;
drop:
dev_dstats_tx_dropped(dev);
- vxlan_vnifilter_count(vxlan, vni, NULL, VXLAN_VNI_STATS_TX_DROPS, 0);
+ vxlan_vnifilter_count(vxlan, cfg, vni, NULL, VXLAN_VNI_STATS_TX_DROPS, 0);
kfree_skb_reason(skb, reason);
return;
@@ -2643,11 +2654,12 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
DEV_STATS_INC(dev, tx_carrier_errors);
dst_release(ndst);
DEV_STATS_INC(dev, tx_errors);
- vxlan_vnifilter_count(vxlan, vni, NULL, VXLAN_VNI_STATS_TX_ERRORS, 0);
+ vxlan_vnifilter_count(vxlan, cfg, vni, NULL, VXLAN_VNI_STATS_TX_ERRORS, 0);
kfree_skb_reason(skb, reason);
}
static void vxlan_xmit_nh(struct sk_buff *skb, struct net_device *dev,
+ const struct vxlan_config *cfg,
struct vxlan_fdb *f, __be32 vni, bool did_rsc)
{
struct vxlan_rdst nh_rdst;
@@ -2664,7 +2676,7 @@ static void vxlan_xmit_nh(struct sk_buff *skb, struct net_device *dev,
do_xmit = vxlan_fdb_nh_path_select(nh, hash, &nh_rdst);
if (likely(do_xmit))
- vxlan_xmit_one(skb, dev, vni, &nh_rdst, did_rsc);
+ vxlan_xmit_one(skb, dev, cfg, vni, &nh_rdst, did_rsc);
else
goto drop;
@@ -2672,15 +2684,15 @@ static void vxlan_xmit_nh(struct sk_buff *skb, struct net_device *dev,
drop:
dev_dstats_tx_dropped(dev);
- vxlan_vnifilter_count(netdev_priv(dev), vni, NULL,
+ vxlan_vnifilter_count(netdev_priv(dev), cfg, vni, NULL,
VXLAN_VNI_STATS_TX_DROPS, 0);
dev_kfree_skb(skb);
}
static netdev_tx_t vxlan_xmit_nhid(struct sk_buff *skb, struct net_device *dev,
- u32 nhid, __be32 vni)
+ u32 nhid, __be32 vni, int saddr_family,
+ const struct vxlan_config *cfg)
{
- struct vxlan_dev *vxlan = netdev_priv(dev);
struct vxlan_rdst nh_rdst;
struct nexthop *nh;
bool do_xmit;
@@ -2698,11 +2710,11 @@ static netdev_tx_t vxlan_xmit_nhid(struct sk_buff *skb, struct net_device *dev,
do_xmit = vxlan_fdb_nh_path_select(nh, hash, &nh_rdst);
rcu_read_unlock();
- if (vxlan->cfg.saddr.sa.sa_family != nh_rdst.remote_ip.sa.sa_family)
+ if (saddr_family != nh_rdst.remote_ip.sa.sa_family)
goto drop;
if (likely(do_xmit))
- vxlan_xmit_one(skb, dev, vni, &nh_rdst, false);
+ vxlan_xmit_one(skb, dev, cfg, vni, &nh_rdst, false);
else
goto drop;
@@ -2710,7 +2722,7 @@ static netdev_tx_t vxlan_xmit_nhid(struct sk_buff *skb, struct net_device *dev,
drop:
dev_dstats_tx_dropped(dev);
- vxlan_vnifilter_count(netdev_priv(dev), vni, NULL,
+ vxlan_vnifilter_count(netdev_priv(dev), cfg, vni, NULL,
VXLAN_VNI_STATS_TX_DROPS, 0);
dev_kfree_skb(skb);
return NETDEV_TX_OK;
@@ -2727,34 +2739,43 @@ static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev)
struct vxlan_dev *vxlan = netdev_priv(dev);
struct vxlan_rdst *rdst, *fdst = NULL;
const struct ip_tunnel_info *info;
+ const struct vxlan_config *cfg;
+ __be32 default_vni;
struct vxlan_fdb *f;
struct ethhdr *eth;
+ int saddr_family;
__be32 vni = 0;
- u32 nhid = 0;
bool did_rsc;
+ u32 nhid = 0;
+ u32 flags;
+
+ cfg = &vxlan->cfg;
+ flags = cfg->flags;
+ default_vni = cfg->vni;
+ saddr_family = cfg->saddr.sa.sa_family;
info = skb_tunnel_info(skb);
skb_reset_mac_header(skb);
- if (vxlan->cfg.flags & VXLAN_F_COLLECT_METADATA) {
+ if (flags & VXLAN_F_COLLECT_METADATA) {
if (info && info->mode & IP_TUNNEL_INFO_BRIDGE &&
info->mode & IP_TUNNEL_INFO_TX) {
vni = tunnel_id_to_key32(info->key.tun_id);
nhid = info->key.nhid;
} else {
if (info && info->mode & IP_TUNNEL_INFO_TX)
- vxlan_xmit_one(skb, dev, vni, NULL, false);
+ vxlan_xmit_one(skb, dev, cfg, vni, NULL, false);
else
kfree_skb_reason(skb, SKB_DROP_REASON_TUNNEL_TXINFO);
return NETDEV_TX_OK;
}
}
- if (vxlan->cfg.flags & VXLAN_F_PROXY) {
+ if (flags & VXLAN_F_PROXY) {
eth = eth_hdr(skb);
if (ntohs(eth->h_proto) == ETH_P_ARP)
- return arp_reduce(dev, skb, vni);
+ return arp_reduce(dev, skb, vni, flags);
#if IS_ENABLED(CONFIG_IPV6)
else if (ntohs(eth->h_proto) == ETH_P_IPV6 &&
pskb_may_pull(skb, sizeof(struct ipv6hdr) +
@@ -2764,15 +2785,15 @@ static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev)
if (m->icmph.icmp6_code == 0 &&
m->icmph.icmp6_type == NDISC_NEIGHBOUR_SOLICITATION)
- return neigh_reduce(dev, skb, vni);
+ return neigh_reduce(dev, skb, vni, flags);
}
#endif
}
if (nhid)
- return vxlan_xmit_nhid(skb, dev, nhid, vni);
+ return vxlan_xmit_nhid(skb, dev, nhid, vni, saddr_family, cfg);
- if (vxlan->cfg.flags & VXLAN_F_MDB) {
+ if (flags & VXLAN_F_MDB) {
struct vxlan_mdb_entry *mdb_entry;
rcu_read_lock();
@@ -2789,26 +2810,26 @@ static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev)
eth = eth_hdr(skb);
rcu_read_lock();
- f = vxlan_find_mac_tx(vxlan, eth->h_dest, vni);
+ f = vxlan_find_mac_tx(vxlan, cfg, eth->h_dest, vni);
did_rsc = false;
- if (f && (f->flags & NTF_ROUTER) && (vxlan->cfg.flags & VXLAN_F_RSC) &&
+ if (f && (f->flags & NTF_ROUTER) && (flags & VXLAN_F_RSC) &&
(ntohs(eth->h_proto) == ETH_P_IP ||
ntohs(eth->h_proto) == ETH_P_IPV6)) {
- did_rsc = route_shortcircuit(dev, skb);
+ did_rsc = route_shortcircuit(dev, skb, cfg);
if (did_rsc)
- f = vxlan_find_mac_tx(vxlan, eth->h_dest, vni);
+ f = vxlan_find_mac_tx(vxlan, cfg, eth->h_dest, vni);
}
if (f == NULL) {
- f = vxlan_find_mac_tx(vxlan, all_zeros_mac, vni);
+ f = vxlan_find_mac_tx(vxlan, cfg, all_zeros_mac, vni);
if (f == NULL) {
- if ((vxlan->cfg.flags & VXLAN_F_L2MISS) &&
+ if ((flags & VXLAN_F_L2MISS) &&
!is_multicast_ether_addr(eth->h_dest))
vxlan_fdb_miss(vxlan, eth->h_dest);
dev_dstats_tx_dropped(dev);
- vxlan_vnifilter_count(vxlan, vni, NULL,
+ vxlan_vnifilter_count(vxlan, cfg, vni, NULL,
VXLAN_VNI_STATS_TX_DROPS, 0);
kfree_skb_reason(skb, SKB_DROP_REASON_NO_TX_TARGET);
goto out;
@@ -2816,8 +2837,8 @@ static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev)
}
if (rcu_access_pointer(f->nh)) {
- vxlan_xmit_nh(skb, dev, f,
- (vni ? : vxlan->default_dst.remote_vni), did_rsc);
+ vxlan_xmit_nh(skb, dev, cfg, f,
+ (vni ? : default_vni), did_rsc);
} else {
list_for_each_entry_rcu(rdst, &f->remotes, list) {
struct sk_buff *skb1;
@@ -2828,10 +2849,10 @@ static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev)
}
skb1 = skb_clone(skb, GFP_ATOMIC);
if (skb1)
- vxlan_xmit_one(skb1, dev, vni, rdst, did_rsc);
+ vxlan_xmit_one(skb1, dev, cfg, vni, rdst, did_rsc);
}
if (fdst)
- vxlan_xmit_one(skb, dev, vni, fdst, did_rsc);
+ vxlan_xmit_one(skb, dev, cfg, vni, fdst, did_rsc);
else
kfree_skb_reason(skb, SKB_DROP_REASON_NO_TX_TARGET);
}
@@ -3701,7 +3722,7 @@ static int vxlan_sock_add(struct vxlan_dev *vxlan)
}
int vxlan_vni_in_use(struct net *src_net, struct vxlan_dev *vxlan,
- struct vxlan_config *conf, __be32 vni)
+ const struct vxlan_config *conf, __be32 vni)
{
struct vxlan_net *vn = net_generic(src_net, vxlan_net_id);
struct vxlan_dev *tmp;
diff --git a/drivers/net/vxlan/vxlan_mdb.c b/drivers/net/vxlan/vxlan_mdb.c
index 055a4969f593c9a949399242298d3a1e44e43988..c92217906d5ee90c92f3da50ca9a276793ed9aca 100644
--- a/drivers/net/vxlan/vxlan_mdb.c
+++ b/drivers/net/vxlan/vxlan_mdb.c
@@ -164,6 +164,7 @@ static int vxlan_mdb_entry_info_fill(const struct vxlan_dev *vxlan,
const struct vxlan_mdb_entry *mdb_entry,
const struct vxlan_mdb_remote *remote)
{
+ const struct vxlan_config *cfg = &vxlan->cfg;
struct vxlan_rdst *rd = rtnl_dereference(remote->rd);
struct br_mdb_entry e;
struct nlattr *nest;
@@ -188,7 +189,7 @@ static int vxlan_mdb_entry_info_fill(const struct vxlan_dev *vxlan,
vxlan_nla_put_addr(skb, MDBA_MDB_EATTR_DST, &rd->remote_ip))
goto nest_err;
- if (rd->remote_port && rd->remote_port != vxlan->cfg.dst_port &&
+ if (rd->remote_port && rd->remote_port != cfg->dst_port &&
nla_put_u16(skb, MDBA_MDB_EATTR_DST_PORT,
be16_to_cpu(rd->remote_port)))
goto nest_err;
@@ -201,7 +202,7 @@ static int vxlan_mdb_entry_info_fill(const struct vxlan_dev *vxlan,
nla_put_u32(skb, MDBA_MDB_EATTR_IFINDEX, rd->remote_ifindex))
goto nest_err;
- if ((vxlan->cfg.flags & VXLAN_F_COLLECT_METADATA) &&
+ if ((cfg->flags & VXLAN_F_COLLECT_METADATA) &&
mdb_entry->key.vni && nla_put_u32(skb, MDBA_MDB_EATTR_SRC_VNI,
be32_to_cpu(mdb_entry->key.vni)))
goto nest_err;
@@ -604,6 +605,7 @@ static int vxlan_mdb_config_init(struct vxlan_mdb_config *cfg,
{
struct br_mdb_entry *entry = nla_data(tb[MDBA_SET_ENTRY]);
struct vxlan_dev *vxlan = netdev_priv(dev);
+ const struct vxlan_config *vcfg = &vxlan->cfg;
memset(cfg, 0, sizeof(*cfg));
cfg->vxlan = vxlan;
@@ -613,7 +615,7 @@ static int vxlan_mdb_config_init(struct vxlan_mdb_config *cfg,
cfg->filter_mode = MCAST_EXCLUDE;
cfg->rt_protocol = RTPROT_STATIC;
cfg->remote_vni = vxlan->default_dst.remote_vni;
- cfg->remote_port = vxlan->cfg.dst_port;
+ cfg->remote_port = vcfg->dst_port;
if (entry->ifindex != dev->ifindex) {
NL_SET_ERR_MSG_MOD(extack, "Port net device must be the VXLAN net device");
@@ -938,6 +940,7 @@ vxlan_mdb_nlmsg_remote_size(const struct vxlan_dev *vxlan,
const struct vxlan_mdb_entry *mdb_entry,
const struct vxlan_mdb_remote *remote)
{
+ const struct vxlan_config *cfg = &vxlan->cfg;
const struct vxlan_mdb_entry_key *group = &mdb_entry->key;
struct vxlan_rdst *rd = rtnl_dereference(remote->rd);
size_t nlmsg_size;
@@ -959,7 +962,7 @@ vxlan_mdb_nlmsg_remote_size(const struct vxlan_dev *vxlan,
/* MDBA_MDB_EATTR_DST */
nlmsg_size += nla_total_size(vxlan_addr_size(&rd->remote_ip));
/* MDBA_MDB_EATTR_DST_PORT */
- if (rd->remote_port && rd->remote_port != vxlan->cfg.dst_port)
+ if (rd->remote_port && rd->remote_port != cfg->dst_port)
nlmsg_size += nla_total_size(sizeof(u16));
/* MDBA_MDB_EATTR_VNI */
if (rd->remote_vni != vxlan->default_dst.remote_vni)
@@ -968,7 +971,7 @@ vxlan_mdb_nlmsg_remote_size(const struct vxlan_dev *vxlan,
if (rd->remote_ifindex)
nlmsg_size += nla_total_size(sizeof(u32));
/* MDBA_MDB_EATTR_SRC_VNI */
- if ((vxlan->cfg.flags & VXLAN_F_COLLECT_METADATA) && group->vni)
+ if ((cfg->flags & VXLAN_F_COLLECT_METADATA) && group->vni)
nlmsg_size += nla_total_size(sizeof(u32));
return nlmsg_size;
@@ -1607,6 +1610,7 @@ struct vxlan_mdb_entry *vxlan_mdb_entry_skb_get(struct vxlan_dev *vxlan,
struct sk_buff *skb,
__be32 src_vni)
{
+ const struct vxlan_config *cfg = &vxlan->cfg;
struct vxlan_mdb_entry *mdb_entry;
struct vxlan_mdb_entry_key group;
@@ -1617,7 +1621,7 @@ struct vxlan_mdb_entry *vxlan_mdb_entry_skb_get(struct vxlan_dev *vxlan,
/* When not in collect metadata mode, 'src_vni' is zero, but MDB
* entries are stored with the VNI of the VXLAN device.
*/
- if (!(vxlan->cfg.flags & VXLAN_F_COLLECT_METADATA))
+ if (!(cfg->flags & VXLAN_F_COLLECT_METADATA))
src_vni = vxlan->default_dst.remote_vni;
memset(&group, 0, sizeof(group));
@@ -1704,12 +1708,12 @@ netdev_tx_t vxlan_mdb_xmit(struct vxlan_dev *vxlan,
skb1 = skb_clone(skb, GFP_ATOMIC);
if (skb1)
- vxlan_xmit_one(skb1, vxlan->dev, src_vni,
+ vxlan_xmit_one(skb1, vxlan->dev, &vxlan->cfg, src_vni,
rcu_dereference(remote->rd), false);
}
if (fremote)
- vxlan_xmit_one(skb, vxlan->dev, src_vni,
+ vxlan_xmit_one(skb, vxlan->dev, &vxlan->cfg, src_vni,
rcu_dereference(fremote->rd), false);
else
kfree_skb_reason(skb, SKB_DROP_REASON_NO_TX_TARGET);
diff --git a/drivers/net/vxlan/vxlan_private.h b/drivers/net/vxlan/vxlan_private.h
index b1eec221636088aa1c1674221d5ef0f13698b53f..3d5f21c11ab4ff55ae6c47f8dabdc21331d37290 100644
--- a/drivers/net/vxlan/vxlan_private.h
+++ b/drivers/net/vxlan/vxlan_private.h
@@ -195,9 +195,10 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan,
__u32 ifindex, __u16 ndm_flags, u32 nhid,
bool swdev_notify, struct netlink_ext_ack *extack);
void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
+ const struct vxlan_config *cfg,
__be32 default_vni, struct vxlan_rdst *rdst, bool did_rsc);
int vxlan_vni_in_use(struct net *src_net, struct vxlan_dev *vxlan,
- struct vxlan_config *conf, __be32 vni);
+ const struct vxlan_config *conf, __be32 vni);
/* vxlan_vnifilter.c */
int vxlan_vnigroup_init(struct vxlan_dev *vxlan);
@@ -205,7 +206,8 @@ void vxlan_vnigroup_uninit(struct vxlan_dev *vxlan);
int vxlan_vnifilter_init(void);
void vxlan_vnifilter_uninit(void);
-void vxlan_vnifilter_count(struct vxlan_dev *vxlan, __be32 vni,
+void vxlan_vnifilter_count(struct vxlan_dev *vxlan,
+ const struct vxlan_config *cfg, __be32 vni,
struct vxlan_vni_node *vninode,
int type, unsigned int len);
diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c
index 3e76f4e210944ffecd35e872c7b7ce36923117b5..3674dec5a59ae4cdb94319c8789826fbb9f0104c 100644
--- a/drivers/net/vxlan/vxlan_vnifilter.c
+++ b/drivers/net/vxlan/vxlan_vnifilter.c
@@ -171,13 +171,14 @@ static void vxlan_vnifilter_stats_add(struct vxlan_vni_node *vninode,
u64_stats_update_end(&pstats->syncp);
}
-void vxlan_vnifilter_count(struct vxlan_dev *vxlan, __be32 vni,
+void vxlan_vnifilter_count(struct vxlan_dev *vxlan,
+ const struct vxlan_config *cfg, __be32 vni,
struct vxlan_vni_node *vninode,
int type, unsigned int len)
{
struct vxlan_vni_node *vnode;
- if (!(vxlan->cfg.flags & VXLAN_F_VNIFILTER))
+ if (!cfg || !(cfg->flags & VXLAN_F_VNIFILTER))
return;
if (vninode) {
diff --git a/net/openvswitch/vport-vxlan.c b/net/openvswitch/vport-vxlan.c
index c1b37b50d29e15540f22787d0dcdb12da181a5fd..b0a084864a858453715d8e51b3e95cc7b2d9370f 100644
--- a/net/openvswitch/vport-vxlan.c
+++ b/net/openvswitch/vport-vxlan.c
@@ -22,19 +22,20 @@ static struct vport_ops ovs_vxlan_netdev_vport_ops;
static int vxlan_get_options(const struct vport *vport, struct sk_buff *skb)
{
struct vxlan_dev *vxlan = netdev_priv(vport->dev);
- __be16 dst_port = vxlan->cfg.dst_port;
+ const struct vxlan_config *cfg = &vxlan->cfg;
+ __be16 dst_port = cfg->dst_port;
if (nla_put_u16(skb, OVS_TUNNEL_ATTR_DST_PORT, ntohs(dst_port)))
return -EMSGSIZE;
- if (vxlan->cfg.flags & VXLAN_F_GBP) {
+ if (cfg->flags & VXLAN_F_GBP) {
struct nlattr *exts;
exts = nla_nest_start_noflag(skb, OVS_TUNNEL_ATTR_EXTENSION);
if (!exts)
return -EMSGSIZE;
- if (vxlan->cfg.flags & VXLAN_F_GBP &&
+ if (cfg->flags & VXLAN_F_GBP &&
nla_put_flag(skb, OVS_VXLAN_EXT_GBP))
return -EMSGSIZE;
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply related
* [PATCH net-next 0/3] vxlan: RCU protect vxlan_config and enable RTNL-less fill_info
From: Eric Dumazet @ 2026-07-08 16:04 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, Andrew Lunn,
netdev, eric.dumazet, Eric Dumazet
This patch series converts VXLAN configuration (`struct vxlan_config`)
to be RCU-protected, allowing lockless readers and enabling RTNL-less
`vxlan_fill_info()` netlink link info dumping.
Work has been split into three distinct parts:
1. Refactor helper functions in RX, TX, MDB, VNIFILTER, and OVS paths to
accept a `const struct vxlan_config *cfg` pointer (or pass specific
fields such as `flags`/`saddr_family`) rather than accessing
`vxlan->cfg` directly.
2. Convert `vxlan->cfg` from an embedded struct into an RCU-protected
pointer (`struct vxlan_config __rcu *cfg`), dynamically allocated on
device creation or changelink, and freed via `kfree_rcu()`. Update
offload drivers (`mlxsw`) and OVS (`vport-vxlan.c`) accordingly.
3. Update `vxlan_fill_info()` to run under `rcu_read_lock()` instead of
relying on RTNL lock, completing the transition to lockless link info
dumping for VXLAN devices.
Eric Dumazet (3):
vxlan: pass vxlan_config pointer to helper functions
vxlan: convert configuration to RCU protection
vxlan: no longer rely on RTNL in vxlan_fill_info()
.../mellanox/mlxsw/spectrum_nve_vxlan.c | 14 +-
.../mellanox/mlxsw/spectrum_switchdev.c | 57 +-
drivers/net/vxlan/vxlan_core.c | 566 +++++++++++-------
drivers/net/vxlan/vxlan_mdb.c | 37 +-
drivers/net/vxlan/vxlan_multicast.c | 12 +-
drivers/net/vxlan/vxlan_private.h | 6 +-
drivers/net/vxlan/vxlan_vnifilter.c | 24 +-
include/net/vxlan.h | 3 +-
net/openvswitch/vport-vxlan.c | 34 +-
9 files changed, 475 insertions(+), 278 deletions(-)
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply
* Re: [PATCH net-next v11 2/2] net: mana: force full-page RX buffers via ethtool private flag
From: Simon Horman @ 2026-07-08 15:57 UTC (permalink / raw)
To: dipayanroy
Cc: Simon Horman, kys, haiyangz, wei.liu, decui, andrew+netdev, davem,
edumazet, kuba, pabeni, leon, longli, kotaranov, shradhagupta,
ssengar, ernis, shirazsaleem, linux-hyperv, netdev, linux-kernel,
linux-rdma, stephen, jacob.e.keller, dipayanroy, leitao, kees,
john.fastabend, hawk, bpf, daniel, ast, sdf, yury.norov,
pavan.chebbi
In-Reply-To: <20260701141808.461554-3-dipayanroy@linux.microsoft.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
net: mana: force full-page RX buffers via ethtool private flag
This adds an ethtool private flag "full-page-rx" that forces one RX buffer
per page to avoid a page_pool fragment path throughput regression on some
4K PAGE_SIZE ARM64 platforms. Toggling the flag while the port is up
triggers a mana_detach() + mana_attach() reconfiguration to apply the new
buffer layout.
A few questions inline.
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
> index 7438ea6b3f26..38f50b4eef87 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
[ ... mana_use_single_rxbuf_per_page() and mana_get_rxbuf_cfg() hunks
unrelated to the comments below ... ]
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> index 3531abfdaf43..24f6ce63b031 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
[ ... ]
> @@ -611,6 +630,88 @@ static int mana_get_link_ksettings(struct net_device *ndev,
> return 0;
> }
>
> +static u32 mana_get_priv_flags(struct net_device *ndev)
> +{
> + struct mana_port_context *apc = netdev_priv(ndev);
> +
> + return apc->priv_flags;
> +}
> +
> +static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
> +{
> + struct mana_port_context *apc = netdev_priv(ndev);
> + u32 changed = apc->priv_flags ^ priv_flags;
> + u32 old_priv_flags = apc->priv_flags;
> + bool schedule_port_reset = false;
> + int err = 0;
> +
> + if (!changed)
> + return 0;
> +
> + /* Reject unknown bits */
> + if (priv_flags & ~GENMASK(MANA_PRIV_FLAG_MAX - 1, 0))
> + return -EINVAL;
> +
> + if (changed & BIT(MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF)) {
> + apc->priv_flags = priv_flags;
[Low]
Is the storage of priv_flags intentionally nested inside the per-flag
branch? With only one flag today this is fine, but when a second bit is
added to enum mana_priv_flag_bits, a user toggling only the new bit
passes the !changed and unknown-bits checks, the outer if is skipped,
apc->priv_flags is never updated, and mana_set_priv_flags() returns 0
while mana_get_priv_flags() keeps reporting the old state.
Would it be cleaner to store priv_flags unconditionally after validation
and use per-flag if (changed & BIT(...)) blocks only for the
flag-specific reconfiguration?
> +
> + if (!apc->port_is_up) {
> + /* Port is down, flag updated to apply on next up
> + * so just return.
> + */
> + return 0;
> + }
> +
> + /* Pre-allocate buffers to prevent failure in mana_attach
> + * later
> + */
> + err = mana_pre_alloc_rxbufs(apc, ndev->mtu, apc->num_queues);
[Low]
When the MTU is already jumbo (mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2) or
XDP is attached, mana_use_single_rxbuf_per_page() already returns true
regardless of the new priv flag:
if (apc->priv_flags & BIT(MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF))
return true;
/* For xdp and jumbo frames make sure only one packet fits per page. */
if (mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 || mana_xdp_get(apc))
return true;
In that case mana_get_rxbuf_cfg() produces identical output whether the
new flag is on or off, yet toggling the flag here always performs a full
mana_pre_alloc_rxbufs() + mana_detach() + mana_attach() cycle and flaps
the link.
Would a pre/post comparison of mana_get_rxbuf_cfg() output be worth
adding to skip the reconfiguration when nothing actually changes?
[High]
mana_set_priv_flags() drives the same detach/attach sequence used by
mana_set_channels() and mana_per_port_queue_reset_work_handler(), but
does not take apc->vport_mutex and set apc->channel_changing = true
across the window.
The struct comment on channel_changing spells out the invariant:
/* Set by mana_set_channels() under vport_mutex to block RDMA
* from grabbing the vport during the detach/attach window.
* Checked by mana_cfg_vport() when called from the RDMA path.
*/
bool channel_changing;
Without it, after mana_detach() uncfg's the vport an RDMA client can
call mana_cfg_vport() and bump vport_use_count, and then mana_attach()
will fail with -EBUSY when it re-cfg's the vport.
There is also no equivalent of the '!port_is_up && vport_use_count'
early -EBUSY check that mana_set_channels() performs, so is the flag
allowed to be changed silently while an RDMA client is using the vport?
> + if (err) {
> + netdev_err(ndev,
> + "Insufficient memory for new allocations\n");
> + apc->priv_flags = old_priv_flags;
> + return err;
> + }
> +
> + err = mana_detach(ndev, false);
[High]
mana_detach() begins with ASSERT_RTNL(), and so does mana_attach()
called below. Is RTNL guaranteed to be held on the SET priv-flags path?
mana registers net_shaper_ops in mana_devops, which makes it an
ops-locked driver, so netdev_need_ops_lock() is true and the ethtool
core acquires rtnl only when the driver opts in via
ETHTOOL_OP_NEEDS_RTNL_* for that specific command.
Looking at mana_ethtool_ops.op_needs_rtnl:
.op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS |
ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM |
ETHTOOL_OP_NEEDS_RTNL_GLINK,
ETHTOOL_OP_NEEDS_RTNL_SPFLAGS is not listed, so ASSERT_RTNL() in the
detach/attach helpers below will WARN, and the code races with
RTNL-serialized mutators (mana_open, mana_close, mana_change_mtu,
mana_tx_timeout) that read and write apc->port_is_up, apc->rxqs,
apc->tx_qp and apc->port_st_save.
Should ETHTOOL_OP_NEEDS_RTNL_SPFLAGS be added to op_needs_rtnl, matching
what is already done for SET channels?
> + if (err) {
> + netdev_err(ndev, "mana_detach failed: %d\n", err);
> + apc->priv_flags = old_priv_flags;
> +
> + /* Port is in an inconsistent state. Restore
> + * 'port_is_up' so that queue reset work handler
> + * can properly detach and re-attach.
> + */
> + apc->port_is_up = true;
> + schedule_port_reset = true;
> + goto out;
> + }
> +
> + err = mana_attach(ndev);
> + if (err) {
> + netdev_err(ndev, "mana_attach failed: %d\n", err);
> + apc->priv_flags = old_priv_flags;
> +
> + /* Restore 'port_is_up' so the reset work handler
> + * can properly detach/attach. Without this,
> + * the handler sees port_is_up=false and skips
> + * queue allocation, leaving the port dead.
> + */
> + apc->port_is_up = true;
> + schedule_port_reset = true;
[High]
On this branch, mana_detach() has already succeeded and run
mana_cleanup_port_context(), so apc->rxqs is NULL and queues are torn
down. Setting apc->port_is_up = true here between now and when
queue_reset_work runs opens a window where callers gate on port_is_up
as their "safe to access rxqs/tx_qp" predicate.
For example, mana_get_ethtool_stats() does:
if (!apc->port_is_up)
return;
...
rxq = apc->rxqs[q];
...
A concurrent ethtool -S invocation during that window will pass the
port_is_up gate and dereference apc->rxqs[q]->stats on a NULL rxqs.
Is the port_is_up restore actually needed for the reset work handler's
mana_detach() call? On the reset path, mana_detach() takes its early
return when !netif_device_present(ndev):
if (!from_close && !netif_device_present(ndev))
return 0;
That early return does not touch apc->port_st_save, so the saved state
from the earlier successful mana_detach() should already be intact for
the follow-up mana_attach().
There is also no smp_wmb() paired with this write, unlike the pattern
used inside mana_detach()/mana_attach() proper.
> + }
> + }
> +
> +out:
> + mana_pre_dealloc_rxbufs(apc);
> +
> + if (schedule_port_reset)
> + queue_work(apc->ac->per_port_queue_reset_wq,
> + &apc->queue_reset_work);
> +
> + return err;
> +}
> +
[ ... remaining hunks unrelated to the comments above ... ]
^ permalink raw reply
* Re: [PATCH V3 2/4] dmaengine: xilinx_dma: Move descriptors to done list based on completion bit
From: Pandey, Radhey Shyam @ 2026-07-08 15:56 UTC (permalink / raw)
To: Srinivas Neeli, Vinod Koul, Radhey Shyam Pandey
Cc: Frank Li, Michal Simek, Andrew Lunn, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Suraj Gupta,
Marek Vasut, Tomi Valkeinen, Alex Bereza, Folker Schwesinger,
dmaengine, netdev, linux-arm-kernel, linux-kernel, git
In-Reply-To: <20260708100652.603074-3-srinivas.neeli@amd.com>
> In AXI MCDMA scatter-gather mode, xilinx_dma_complete_descriptor() walks
avoid "scatter-gather" mode.
> the channel's active_list and unconditionally moves every entry to the
> done_list. The MCDMA IOC interrupt handler invokes this function on
> every interrupt-on-completion, but with interrupt coalescing
> (IRQThreshold > 1) an IOC interrupt may fire after only a subset of the
> queued descriptors have actually been processed by the hardware. As a
> result, descriptors whose completion bit is not yet set in the BD status
> were being reported as completed to client drivers.
>
> Add a check for the descriptor completion bit before moving entries from
> the active list to the done list, using the appropriate direction-
> specific status field (s2mm_status for DMA_DEV_TO_MEM, mm2s_status for
> DMA_MEM_TO_DEV).
>
> The MCDMA completion check is intentionally not guarded by chan->has_sg,
> unlike the AXIDMA branch above. AXI MCDMA only operates in scatter-gather
> mode (has_sg is always true), so the guard would always pass and is
> omitted. The completion bit is therefore checked unconditionally.
Not need to talk about has_sg check and compare it with AXIDMA but if
you still prefer make it precise.
Once addressed , feel free to add.
Reviewed-by: Radhey Shyam Pandey <radhey.shyam.pandey@amd.com>
Thanks!
Just a note that this change aligns with changes that were done in past
for AXIDMA in commit 7bcdaa658102 dmaengine: xilinx_dma: Freeup active
list based on descriptor completion bit
>
> Fixes: 6ccd692bfb7f ("dmaengine: xilinx_dma: Add Xilinx AXI MCDMA Engine driver support")
> Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
> ---
> Changes in V3:
> - Added Fixes tag.
> - Expanded commit message to explain the interrupt coalescing scenario
> and why the has_sg guard is omitted for MCDMA.
> - Changed local variable from 'bool completed' to 'u32 status' for
> cleaner status field access.
> - Simplified completion check logic.
>
> Changes in V2:
> - No change.
> ---
> drivers/dma/xilinx/xilinx_dma.c | 11 +++++++++++
> 1 file changed, 11 insertions(+)
>
> diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
> index ff5b29a808e9..1b5b00f08c5f 100644
> --- a/drivers/dma/xilinx/xilinx_dma.c
> +++ b/drivers/dma/xilinx/xilinx_dma.c
> @@ -1784,6 +1784,17 @@ static void xilinx_dma_complete_descriptor(struct xilinx_dma_chan *chan)
> struct xilinx_axidma_tx_segment, node);
> if (!(seg->hw.status & XILINX_DMA_BD_COMP_MASK) && chan->has_sg)
> break;
> + } else if (chan->xdev->dma_config->dmatype == XDMA_TYPE_AXIMCDMA) {
> + struct xilinx_aximcdma_tx_segment *seg;
> + u32 status;
> +
> + seg = list_last_entry(&desc->segments,
> + struct xilinx_aximcdma_tx_segment,
> + node);
> + status = (chan->direction == DMA_DEV_TO_MEM) ?
> + seg->hw.s2mm_status : seg->hw.mm2s_status;
> + if (!(status & XILINX_DMA_BD_COMP_MASK))
> + break;
> }
> if (chan->has_sg && chan->xdev->dma_config->dmatype !=
> XDMA_TYPE_VDMA)
^ permalink raw reply
* Re: [PATCH nf] ipvs: make destination flags atomic
From: Julian Anastasov @ 2026-07-08 15:53 UTC (permalink / raw)
To: Yizhou Zhao
Cc: Simon Horman, David Ahern, Ido Schimmel, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Pablo Neira Ayuso,
Florian Westphal, Phil Sutter, Alexander Frolkin, netdev,
lvs-devel, linux-kernel, netfilter-devel, coreteam, stable,
Yuxiang Yang, Ao Wang, Xuewei Feng, Qi Li, Ke Xu
In-Reply-To: <91509A0C-9E4A-4F0E-A45C-ABD29396067E@mails.tsinghua.edu.cn>
Hello,
On Wed, 8 Jul 2026, Yizhou Zhao wrote:
> > On Jul 8, 2026, at 03:18, Julian Anastasov <ja@ssi.bg> wrote:
> >
> > On Tue, 7 Jul 2026, Yizhou Zhao wrote:
> >
>
> We have posted a v2 patch at:
> https://lore.kernel.org/netfilter-devel/20260708060454.20534-1-zhaoyz24@mails.tsinghua.edu.cn/
>
> The v2 patch updates the commit message with more conservative
> wording, and fixes the checkpatch logical-continuation warnings.
After looking again at the code, I think we can
do it in different way:
- IP_VS_DEST_F_AVAILABLE and IP_VS_DEST_F_OVERLOAD are defined
in include/uapi/linux/ip_vs.h but we never export them to user
space. So, we are free to change them. We can move them to
include/net/ip_vs.h, see below...
- IP_VS_DEST_F_AVAILABLE is changed only under service_mutex,
so we can keep its usage
- IP_VS_DEST_F_OVERLOAD needs different access methods.
We can add 'unsigned long flags2;', may be after l_threshold.
And to switch to such usage (F_OVERLOAD -> FL_OVERLOAD):
- test_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2)
- set_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2)
Sometimes if (test_bit()) clear_bit() can avoid
full memory barrier in ip_vs_dest_update_overload()
- clear_bit(IP_VS_DEST_FL_OVERLOAD, &dest->flags2)
test_bit() guard can help here too
As there are other races involved, something like
this can be a starting point for such change. It tries harder
to update the overload flag on dest edit/add but it does not
include the proposed bitops:
diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index 49297fec448a..b34631270e24 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -1906,6 +1906,8 @@ static inline void ip_vs_dest_put_and_free(struct ip_vs_dest *dest)
kfree(dest);
}
+void ip_vs_dest_update_overload(struct ip_vs_dest *dest);
+
/* IPVS sync daemon data and function prototypes
* (from ip_vs_sync.c)
*/
diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c
index d19caf66afeb..3fd221996e6e 100644
--- a/net/netfilter/ipvs/ip_vs_conn.c
+++ b/net/netfilter/ipvs/ip_vs_conn.c
@@ -1087,6 +1087,26 @@ static inline int ip_vs_dest_totalconns(struct ip_vs_dest *dest)
+ atomic_read(&dest->inactconns);
}
+__always_inline void ip_vs_dest_update_overload(struct ip_vs_dest *dest)
+{
+ int conns, l, u;
+
+ u = READ_ONCE(dest->u_threshold);
+ if (!u)
+ goto unset;
+ conns = ip_vs_dest_totalconns(dest);
+ if (conns >= u) {
+ dest->flags |= IP_VS_DEST_F_OVERLOAD;
+ return;
+ }
+ l = READ_ONCE(dest->l_threshold) ? : (u * 3 / 4);
+ if (conns >= l && l)
+ return;
+
+unset:
+ dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
+}
+
/*
* Bind a connection entry with a virtual service destination
* Called just after a new connection entry is created.
@@ -1161,9 +1181,7 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)
atomic_inc(&dest->persistconns);
}
- if (dest->u_threshold != 0 &&
- ip_vs_dest_totalconns(dest) >= dest->u_threshold)
- dest->flags |= IP_VS_DEST_F_OVERLOAD;
+ ip_vs_dest_update_overload(dest);
}
@@ -1257,16 +1275,8 @@ static inline void ip_vs_unbind_dest(struct ip_vs_conn *cp)
atomic_dec(&dest->persistconns);
}
- if (dest->l_threshold != 0) {
- if (ip_vs_dest_totalconns(dest) < dest->l_threshold)
- dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
- } else if (dest->u_threshold != 0) {
- if (ip_vs_dest_totalconns(dest) * 4 < dest->u_threshold * 3)
- dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
- } else {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
- dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
- }
+ if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ ip_vs_dest_update_overload(dest);
ip_vs_dest_put(dest);
}
diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c
index bcf40b8c41cf..2871116e46ec 100644
--- a/net/netfilter/ipvs/ip_vs_ctl.c
+++ b/net/netfilter/ipvs/ip_vs_ctl.c
@@ -1315,6 +1315,7 @@ __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest,
struct ip_vs_service *old_svc;
struct ip_vs_scheduler *sched;
int conn_flags;
+ bool upd_thresh;
/* We cannot modify an address and change the address family */
BUG_ON(!add && udest->af != dest->af);
@@ -1370,10 +1371,12 @@ __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest,
/* set the dest status flags */
dest->flags |= IP_VS_DEST_F_AVAILABLE;
- if (udest->u_threshold == 0 || udest->u_threshold > dest->u_threshold)
- dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
- dest->u_threshold = udest->u_threshold;
- dest->l_threshold = udest->l_threshold;
+ upd_thresh = READ_ONCE(dest->u_threshold) != udest->u_threshold ||
+ READ_ONCE(dest->l_threshold) != udest->l_threshold;
+ WRITE_ONCE(dest->u_threshold, udest->u_threshold);
+ WRITE_ONCE(dest->l_threshold, udest->l_threshold);
+ if (upd_thresh)
+ ip_vs_dest_update_overload(dest);
dest->af = udest->af;
@@ -3667,8 +3670,8 @@ __ip_vs_get_dest_entries(struct netns_ipvs *ipvs, const struct ip_vs_get_dests *
entry.port = dest->port;
entry.conn_flags = atomic_read(&dest->conn_flags);
entry.weight = atomic_read(&dest->weight);
- entry.u_threshold = dest->u_threshold;
- entry.l_threshold = dest->l_threshold;
+ entry.u_threshold = READ_ONCE(dest->u_threshold);
+ entry.l_threshold = READ_ONCE(dest->l_threshold);
entry.activeconns = atomic_read(&dest->activeconns);
entry.inactconns = atomic_read(&dest->inactconns);
entry.persistconns = atomic_read(&dest->persistconns);
@@ -4277,8 +4280,10 @@ static int ip_vs_genl_fill_dest(struct sk_buff *skb, struct ip_vs_dest *dest)
dest->tun_port) ||
nla_put_u16(skb, IPVS_DEST_ATTR_TUN_FLAGS,
dest->tun_flags) ||
- nla_put_u32(skb, IPVS_DEST_ATTR_U_THRESH, dest->u_threshold) ||
- nla_put_u32(skb, IPVS_DEST_ATTR_L_THRESH, dest->l_threshold) ||
+ nla_put_u32(skb, IPVS_DEST_ATTR_U_THRESH,
+ READ_ONCE(dest->u_threshold)) ||
+ nla_put_u32(skb, IPVS_DEST_ATTR_L_THRESH,
+ READ_ONCE(dest->l_threshold)) ||
nla_put_u32(skb, IPVS_DEST_ATTR_ACTIVE_CONNS,
atomic_read(&dest->activeconns)) ||
nla_put_u32(skb, IPVS_DEST_ATTR_INACT_CONNS,
Regards
--
Julian Anastasov <ja@ssi.bg>
^ 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;
as well as URLs for NNTP newsgroup(s).