* Re: [PATCH net-next v11 7/7] selftests: netconsole: validate target resume
From: Matthieu Baerts @ 2026-07-08 15:50 UTC (permalink / raw)
To: Andre Carvalho, Breno Leitao
Cc: netdev, linux-kernel, linux-kselftest, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Shuah Khan, Simon Horman
In-Reply-To: <20260118-netcons-retrigger-v11-7-4de36aebcf48@gmail.com>
Hi Andre, Breno,
On 18/01/2026 12:00, Andre Carvalho wrote:
> Introduce a new netconsole selftest to validate that netconsole is able
> to resume a deactivated target when the low level interface comes back.
>
> The test setups the network using netdevsim, creates a netconsole target
> and then remove/add netdevsim in order to bring the same interfaces
> back. Afterwards, the test validates that the target works as expected.
>
> Targets are created via cmdline parameters to the module to ensure that
> we are able to resume targets that were bound by mac and interface name.
I'm sorry to react on this "old" patch, but I have some troubles running
this netcons_resume.sh test in a new environment with containers.
> diff --git a/tools/testing/selftests/drivers/net/netcons_resume.sh b/tools/testing/selftests/drivers/net/netcons_resume.sh
> new file mode 100755
> index 000000000000..fc5e5e3ad3d4
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/netcons_resume.sh
(...)
> +function trigger_reactivation() {
> + # Add back low level module
> + modprobe netdevsim
> + # Recreate namespace and two interfaces
> + set_network
> + # Restore MACs
> + ip netns exec "${NAMESPACE}" ip link set "${DSTIF}" \
> + address "${SAVED_DSTMAC}"
> + if [ "${BINDMODE}" == "mac" ]; then
> + ip link set dev "${SRCIF}" down
> + ip link set dev "${SRCIF}" address "${SAVED_SRCMAC}"
> + # Rename device in order to trigger target resume, as initial
> + # when device was recreated it didn't have correct mac address.
> + ip link set dev "${SRCIF}" name "${TARGET}"
When I execute the test, the "ifname" bind mode works without issues,
but the "mac" one not. From what I see, the socat process doesn't get
any UDP packet when expected. I wonder if the problem might not come
from here: the interface is disabled before changing the MAC address and
renaming the interface, but not re-enabled at the end. Is it normal?
If I add 'up' at the end of this last line here, or if I remove the
whole if-statement block, the test passes.
In the console, I can see these messages, with or without re-enabling
the interface:
netdevsim netdevsim642 eni642np1: renamed from eth0
netdevsim netdevsim387 eni387np1: renamed from eth1
netconsole: netconsole: local port 1514
netconsole: netconsole: local IPv4 address 192.0.2.1
netconsole: netconsole: interface name ''
netconsole: netconsole: local ethernet address '1e:95:b7:ae:ab:dc'
netconsole: netconsole: remote port 6666
netconsole: netconsole: remote IPv4 address 192.0.2.2
netconsole: netconsole: remote ethernet address e6:12:42:f8:4c:b2
printk: console [netcon_ext0] enabled
netconsole: network logging started
netconsole: network logging stopped on interface eni387np1 as it
unregistered
netdevsim netdevsim642 eni642np1: renamed from eth0
netdevsim netdevsim387 eni387np1: renamed from eth1
netconsole: Process resuming (mac: 1e:95:b7:ae:ab:dc), s:2, r:-1
netpoll: netconsole: device 1e:95:b7:ae:ab:dc not up yet, forcing it
netconsole: network logging resumed on interface eni387np1
netdevsim netdevsim387 netcons_LFLQP: renamed from eni387np1
netconsole selftest: netcons_LFLQP
The "network logging resumed on interface" seems to suggest that the
previous patch of this series here, commit 220dbe3c76ed ("netconsole:
resume previously deactivated target"), managed to resume the previously
activated target, but not in my case.
What I don't understand is why is it working on the Netdev CI, and not
on my side. The main difference is that I might be missing some
userspace packages -- but I don't see what can be missing here, all
other netconsole tests pass -- a specific kernel config, or not applied
patch. Or something different on the host -- in a container on my side
-- but there shouldn't be any interactions with the host here,
everything is happening in the VM. Any ideas? :)
> + fi
> +}
> +
> +function trigger_deactivation() {
> + # Start by storing mac addresses so we can be restored in reactivate
> + SAVED_DSTMAC=$(ip netns exec "${NAMESPACE}" \
> + cat /sys/class/net/"$DSTIF"/address)
> + SAVED_SRCMAC=$(mac_get "${SRCIF}")
> + # Remove low level module
> + rmmod netdevsim
> +}
> +
> +trap cleanup EXIT
> +
> +# Run the test twice, with different cmdline parameters
> +for BINDMODE in "ifname" "mac"
> +do
> + echo "Running with bind mode: ${BINDMODE}" >&2
> + # Set current loglevel to KERN_INFO(6), and default to KERN_NOTICE(5)
> + echo "6 5" > /proc/sys/kernel/printk
Can we remove this? Without that, it is hard to understand what went
wrong in case of issues.
> +
> + # Create one namespace and two interfaces
> + set_network
> +
> + # Create the command line for netconsole, with the configuration from
> + # the function above
> + CMDLINE=$(create_cmdline_str "${BINDMODE}")
> +
> + # The content of kmsg will be save to the following file
> + OUTPUT_FILE="/tmp/${TARGET}-${BINDMODE}"
> +
> + # Load the module, with the cmdline set
> + modprobe netconsole "${CMDLINE}"
> + # Expose cmdline target in configfs
> + mkdir "${NETCONS_CONFIGFS}/cmdline0"
> +
> + # Target should be enabled
> + wait_target_state "cmdline0" "enabled"
> +
> + # Trigger deactivation by unloading netdevsim module. Target should be
> + # disabled.
> + trigger_deactivation
> + wait_target_state "cmdline0" "disabled"
> +
> + # Trigger reactivation by loading netdevsim, recreating the network and
> + # restoring mac addresses. Target should be re-enabled.
> + trigger_reactivation
> + wait_target_state "cmdline0" "enabled"
> +
> + # Listen for netconsole port inside the namespace and destination
> + # interface
> + listen_port_and_save_to "${OUTPUT_FILE}" &
> + # Wait for socat to start and listen to the port.
> + wait_local_port_listen "${NAMESPACE}" "${PORT}" udp
> + # Send the message
> + echo "${MSG}: ${TARGET}" > /dev/kmsg
> + # Wait until socat saves the file to disk
> + busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}"
In my case, the script was stopping here, without any message. I can
send a patch adding "|| true" to go to the next instruction, and display
"FAIL: File was not generated.".
> + # Make sure the message was received in the dst part
> + # and exit
> + validate_msg "${OUTPUT_FILE}"
> +
> + # kill socat in case it is still running
> + pkill_socat
> + # Cleanup & unload the module
> + cleanup
> +
> + echo "${BINDMODE} : Test passed" >&2
> +done
> +
> +trap - EXIT
> +exit "${EXIT_STATUS}"
>
Cheers,
Matt
^ 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
* 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 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
* [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
* [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 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 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
* 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
* 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: 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 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
* [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 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
* 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
* [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 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
* 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 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 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] 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
* [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
* [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 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
* 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
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