* Re: [PATCH net-next 07/15] ixgbe: E610: add ACI dynamic debug
From: Jacob Keller @ 2026-05-04 22:33 UTC (permalink / raw)
To: Piotr Kwapulinski
Cc: przemyslaw.kitszel, andrew+netdev, davem, edumazet, pabeni,
aleksander.lobakin, horms, michal.swiatkowski, jedrzej.jagielski,
aleksandr.loktionov, grzegorz.nitka, ivecera,
arkadiusz.kubalewski, anthony.l.nguyen, stefan.wegrzyn,
piotr.kwapulinski, netdev, linux-kernel, pmenzel,
sunithax.d.mekala
In-Reply-To: <20260503020636.4114758-1-kuba@kernel.org>
On 5/2/2026 7:06 PM, Jakub Kicinski wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> ---
> ixgbe: E610: add ACI dynamic debug
>
> This commit enables dynamic debugging of the Admin Command Interface (ACI)
> for the e610 adapter, allowing ACI commands and descriptor details to be
> printed into the kernel log.
>
>> diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
>> --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
>> +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
>
> [ ... ]
>
>> @@ -106,8 +185,12 @@ static int ixgbe_aci_send_cmd_execute(struct ixgbe_hw *hw,
>> if (desc->flags & cpu_to_le16(LIBIE_AQ_FLAG_RD)) {
>> for (i = 0; i < buf_size / 4; i++)
>> IXGBE_WRITE_REG(hw, IXGBE_PF_HIBA(i), ((u32 *)buf)[i]);
>> - if (buf_tail_size)
>> + ixgbe_aci_debug(hw, desc, buf, buf_size);
>> + if (buf_tail_size) {
>> IXGBE_WRITE_REG(hw, IXGBE_PF_HIBA(i), buf_tail);
>> + ixgbe_aci_debug(hw, desc, &buf_tail,
>> + buf_tail_size);
>> + }
>> }
>> }
>
> Does this result in redundant output being printed to the kernel log?
>
> When buf_tail_size is non-zero, ixgbe_aci_debug() is called twice: once for
> the full buffer, and a second time for just the tail. Since ixgbe_aci_debug()
> prints the full CQ CMD descriptor details each time, this appears to print the
> descriptor headers twice and fragment the buffer output.
>
> [ ... ]
>
>> @@ -183,10 +278,13 @@ static int ixgbe_aci_send_cmd_execute(struct ixgbe_hw *hw,
>> if (valid_buf) {
>> for (i = 0; i < buf_size / 4; i++)
>> ((u32 *)buf)[i] = IXGBE_READ_REG(hw, IXGBE_PF_HIBA(i));
>> + ixgbe_aci_debug(hw, raw_desc, buf, buf_size);
>> if (buf_tail_size) {
>> buf_tail = IXGBE_READ_REG(hw, IXGBE_PF_HIBA(i));
>> memcpy(buf + buf_size - buf_tail_size, &buf_tail,
>> buf_tail_size);
>> + ixgbe_aci_debug(hw, raw_desc, &buf_tail,
>> + buf_tail_size);
>> }
>> }
>
> Can this read uninitialized memory from the buffer?
>
> At the point ixgbe_aci_debug(..., buf, buf_size) is called, the loop has only
> populated the 4-byte aligned chunks of the buffer. The trailing bytes at the
> end of the buffer are not populated until the memcpy() occurs later in the
> block. Because ixgbe_aci_debug() consumes buf_size bytes, it looks like it
> will read and log uninitialized trailing memory (potentially from the kernel
> stack).
>
> Also, similar to the transmit path, this calls ixgbe_aci_debug() a second
> time on &buf_tail, which will print the descriptor headers again.
>
> Could we move a single ixgbe_aci_debug(..., buf, buf_size) call to the end of
> the block, after the entire buffer including the tail has been fully written?
Piotr,
Please review this report and submit a new version of this patch to
Intel Wired LAN.
While you are at it, please review the following report from a run of
sashiko I did myself using claude:
> commit 198ec0389c757125d9079647a6c8aba34ba29048
> Author: Piotr Kwapulinski <piotr.kwapulinski@intel.com>
> ixgbe: E610: add ACI dynamic debug
> This patch enables dynamic debug of the Admin Command Interface (ACI)
> for the E610 adapter, adding descriptor and buffer hex dumps via the
> standard dyndbg infrastructure, plus hw_dbg() calls on error paths.
>> diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
>> index 665a9813e251..b686636beb93 100644
>> --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
>> +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
> [ ... ]
>> @@ -147,6 +230,7 @@ static int ixgbe_aci_send_cmd_execute(struct ixgbe_hw *hw,
>> raw_desc[i] = IXGBE_READ_REG(hw, IXGBE_PF_HIDA(i));
>> raw_desc[i] = raw_desc[i];
>> }
>>+ ixgbe_aci_debug(hw, raw_desc, NULL, 0);
>> }
> [ ... ]
>> /* Handle timeout and invalid state of HICR register */
>>- if (hicr & IXGBE_PF_HICR_C)
>>+ if (hicr & IXGBE_PF_HICR_C) {
>>+ hw_dbg(hw, "error: Admin Command 0x%X command timeout\n",
>>+ le16_to_cpu(desc->opcode));
>> return -ETIME;
>>+ }
> Since raw_desc is declared as a cast of desc:
> u32 *raw_desc = (u32 *)desc;
> and the sync response read loop overwrites raw_desc[] (and thus desc)
> with firmware response data, could le16_to_cpu(desc->opcode) here
> print the response opcode rather than the original command opcode?
> The local variable opcode already holds the original value saved
> earlier via:
> opcode = le16_to_cpu(desc->opcode);
> Would it be more correct to use opcode directly in this hw_dbg() call
> (and the one in the invalid-state check below)?
>> @@ -183,10 +278,13 @@ static int ixgbe_aci_send_cmd_execute(struct ixgbe_hw *hw,
>> if (valid_buf) {
>> for (i = 0; i < buf_size / 4; i++)
>> ((u32 *)buf)[i] = IXGBE_READ_REG(hw, IXGBE_PF_HIBA(i));
>>+ ixgbe_aci_debug(hw, raw_desc, buf, buf_size);
>> if (buf_tail_size) {
>> buf_tail = IXGBE_READ_REG(hw, IXGBE_PF_HIBA(i));
>> memcpy(buf + buf_size - buf_tail_size, &buf_tail,
>> buf_tail_size);
>>+ ixgbe_aci_debug(hw, raw_desc, &buf_tail,
>>+ buf_tail_size);
>> }
>> }
> When buf_size is not 4-byte aligned, ixgbe_aci_debug() is called
> with the full buf_size before the tail bytes have been read from
> hardware and memcpy'd into buf. The hex dump will show stale content
> for the last 1-3 bytes of the buffer.
> Should the ixgbe_aci_debug() call be moved after the tail memcpy so
> that it dumps the complete response?
^ permalink raw reply
* Re: [PATCH net-next v2] declance: Remove IRQF_ONESHOT
From: Maciej W. Rozycki @ 2026-05-04 22:35 UTC (permalink / raw)
To: Sebastian Andrzej Siewior
Cc: netdev, linux-mips, Jakub Kicinski, Andrew Lunn, David S. Miller,
Eric Dumazet, Paolo Abeni
In-Reply-To: <alpine.DEB.2.21.2603292037020.60268@angie.orcam.me.uk>
On Sun, 29 Mar 2026, Maciej W. Rozycki wrote:
> I've now got back to it and while preparing the justification for the
> removal of the IRQF_ONESHOT recommendation and having looked through
> Documentation/core-api/real-time/differences.rst I became stumped and
> need a further clarification after all.
>
> I read in the document that:
>
> "However, on a PREEMPT_RT system, interrupts are forced-threaded and no
> longer run in hard IRQ context."
>
> and:
>
> "All interrupts are forced-threaded in a PREEMPT_RT system. The exceptions
> are interrupts that are requested with the IRQF_NO_THREAD, IRQF_PERCPU, or
> IRQF_ONESHOT flags."
>
> -- do I infer correctly that on a PREEMPT_RT system in the absence of any
> flags passed to request_irq() the handler requested such as one concerned
> here (i.e. lance_dma_merr_int()) will run with interrupts locally enabled
> on the CPU?
No reply, but I've gone through irq_setup_forced_threading() now and my
inference was indeed correct, and the handler does need to be installed
with IRQF_NO_THREAD. I'll send corrective patches shortly.
Maciej
^ permalink raw reply
* Re: [PATCH 09/11] vfio: selftests: Add mlx5 driver - HW init and command interface
From: David Matlack @ 2026-05-04 22:35 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: Alex Williamson, kvm, Leon Romanovsky, linux-kselftest,
linux-rdma, Mark Bloch, netdev, Saeed Mahameed, Shuah Khan,
Tariq Toukan, patches
In-Reply-To: <9-v1-dc5fa250ca1d+3213-mlx5st_jgg@nvidia.com>
On 2026-04-30 09:08 PM, Jason Gunthorpe wrote:
> +/*
> + * Driver state — overlaid on device->driver.region.vaddr.
> + *
> + * Contains both software-only state and HW-visible DMA buffers. HW buffers need
> + * strict IOVA alignment.
> + */
> +struct mlx5st_device {
Can we do s/mlx5st/mlx5/ on the series?
I assume st is for selftests and that is already implied by this file
being under tools/testing/selftests.
> +/*
> + * Probe — match mlx5 devices by PCI vendor/device ID.
> + */
> +
> +#define PCI_VENDOR_ID_MELLANOX 0x15b3
nit: Use #include <linux/pci_ids.h>
> +static int mlx5st_probe(struct vfio_pci_device *device)
> +{
> + static const u16 mlx5st_pci_ids[] = {
> + 0x1011, /* Connect-IB */
> + 0x1012, /* Connect-IB VF */
> + 0x1013, /* ConnectX-4 */
> + 0x1014, /* ConnectX-4 VF */
> + 0x1015, /* ConnectX-4LX */
> + 0x1016, /* ConnectX-4LX VF */
> + 0x1017, /* ConnectX-5 */
> + 0x1018, /* ConnectX-5 VF */
> + 0x1019, /* ConnectX-5 Ex */
> + 0x101a, /* ConnectX-5 Ex VF */
> + 0x101b, /* ConnectX-6 */
> + 0x101c, /* ConnectX-6 VF */
> + 0x101d, /* ConnectX-6 Dx */
> + 0x101e, /* ConnectX-6 Dx VF */
> + 0x101f, /* ConnectX-6 LX */
> + 0x1021, /* ConnectX-7 */
> + 0x1023, /* ConnectX-8 */
> + 0x1025, /* ConnectX-9 */
> + 0x1027, /* ConnectX-10 */
> + 0x2101, /* ConnectX-10 NVLink-C2C */
> + 0xa2d2, /* BlueField integrated ConnectX-5 */
> + 0xa2d3, /* BlueField integrated ConnectX-5 VF */
> + 0xa2d6, /* BlueField-2 integrated ConnectX-6 Dx */
> + 0xa2dc, /* BlueField-3 integrated ConnectX-7 */
> + 0xa2df, /* BlueField-4 integrated ConnectX-8 */
> + };
> + unsigned int i;
> + u16 did;
> +
> + if (vfio_pci_config_readw(device, PCI_VENDOR_ID) !=
> + PCI_VENDOR_ID_MELLANOX)
> + return -ENODEV;
> +
> + did = vfio_pci_config_readw(device, PCI_DEVICE_ID);
> + for (i = 0; i < ARRAY_SIZE(mlx5st_pci_ids); i++) {
> + if (mlx5st_pci_ids[i] == did)
> + return 0;
> + }
> +
> + return -ENODEV;
> +}
^ permalink raw reply
* Re: [PATCH net-next 14/15] ice: dpll: fix rclk pin state get and misplaced header macros
From: Jacob Keller @ 2026-05-04 22:39 UTC (permalink / raw)
To: Vecera, Ivan; +Cc: netdev, linux-kernel
In-Reply-To: <20260430-jk-iwl-net-next-2026-04-30-v1-14-6f27ae1cd073@intel.com>
On 4/30/2026 11:37 PM, Jacob Keller wrote:
> From: Ivan Vecera <ivecera@redhat.com>
>
> Fix two issues introduced in commit ad1df4f2d591 ("ice: dpll: Support
> E825-C SyncE and dynamic pin discovery"):
>
> * The refactoring of ice_dpll_rclk_state_on_pin_get() to use
> ice_dpll_pin_get_parent_idx() omitted the base_rclk_idx adjustment
> that was correctly added in the ice_dpll_rclk_state_on_pin_set() path.
> This breaks E810 devices where base_rclk_idx is non-zero, causing
> the wrong hardware index to be used for pin state lookup and incorrect
> recovered clock state to be reported via the DPLL subsystem. E825C is
> unaffected as its base_rclk_idx is 0.
>
> * Add bounds check against ICE_DPLL_RCLK_NUM_MAX on hw_idx after the
> base_rclk_idx subtraction in both ice_dpll_rclk_state_on_pin_{get,set}()
> to prevent out-of-bounds access on the pin state array.
>
> * The CGU register definitions (ICE_CGU_R10, ICE_CGU_R11 and related field
> masks) were placed after the #endif of the _ICE_DPLL_H_ include guard,
> leaving them unprotected. Move them inside the guard.
>
> Fixes: ad1df4f2d591 ("ice: dpll: Support E825-C SyncE and dynamic pin discovery")
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
> Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
> Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
> ---
Ivan,
Unless you have any objections I will go ahead and rebase this ontop of
net and submit it in my next round of fixes. I might split the changes
to have the CGU register definitions change in a separate patch since it
conceptually isn't the same issue as the other two.
Thanks,
Jake
^ permalink raw reply
* Re: [PATCH 10/11] vfio: selftests: Add mlx5 driver - data path and memcpy ops
From: David Matlack @ 2026-05-04 22:41 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: Alex Williamson, kvm, Leon Romanovsky, linux-kselftest,
linux-rdma, Mark Bloch, netdev, Saeed Mahameed, Shuah Khan,
Tariq Toukan, patches
In-Reply-To: <10-v1-dc5fa250ca1d+3213-mlx5st_jgg@nvidia.com>
On 2026-04-30 09:08 PM, Jason Gunthorpe wrote:
> @@ -1368,6 +1716,11 @@ static void mlx5st_init(struct vfio_pci_device *device)
> mlx5st_alloc_pd(dev);
> mlx5st_create_mkey(dev);
>
> + mlx5st_setup_datapath(dev);
> +
> + device->driver.max_memcpy_size = 1 << 20;
> + device->driver.max_memcpy_count = SQ_WQE_CNT - 1;
What are these limits a function of? e.g. Is the 1MB size a hardware
limit? Can we change SQ_WQE_CNT in the future to increase max count?
I'm interested in this because for Live Update testing we've found it
valuable to keep the device busy for several minutes so that it can do
DMA continuously throughout the Live Update.
> +
> dev_dbg(device, "mlx5 driver initialized\n");
> }
^ permalink raw reply
* Re: [PATCH net-next] net/mlx5: Add MLX5_VXLAN config option
From: Marc Harvey @ 2026-05-04 22:44 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Mark Bloch,
Andrew Lunn, David S. Miller, Eric Dumazet, Paolo Abeni, netdev,
linux-rdma, linux-kernel, Kuniyuki Iwashima
In-Reply-To: <20260429190150.417b0302@kernel.org>
On Wed, Apr 29, 2026 at 7:01 PM Jakub Kicinski <kuba@kernel.org> wrote:
>
> Are you aware of NETIF_F_RX_UDP_TUNNEL_PORT ?
> I haven't checked it does exactly what we need, but I recall there was
> a ethtool feature for this..
Thanks, I didn't know about that feature and mlx5 uses it. However,
mlx5 unconditionally sets the `UDP_TUNNEL_NIC_INFO_STATIC_IANA_VXLAN`
flag, which excludes port 4789 from the entire UDP tunnel core offload
management (see `__udp_tunnel_nic_add_port()`).
So using ethtool to disable `NETIF_F_RX_UDP_TUNNEL_PORT` will not
disable vxlan offload for port 4789.
I think a better approach would be to just remove this static
automatic offloading for port 4789, mlx5 is the only driver using
`UDP_TUNNEL_NIC_INFO_STATIC_IANA_VXLAN` anyway. However, there might
be a reason for this, such as some supported hardware offloading vxlan
on port 4789 by default even without commands from the driver.
If mlx5 continues to use the `UDP_TUNNEL_NIC_INFO_STATIC_IANA_VXLAN`
flag, then some change is required to fully disable vxlan offloading.
^ permalink raw reply
* [RFC PATCH net-next v3 0/2] udp: fix FOU/GUE over multicast
From: Anton Danilov @ 2026-05-04 22:53 UTC (permalink / raw)
To: netdev
Cc: Willem de Bruijn, davem, David Ahern, Eric Dumazet,
Kuniyuki Iwashima, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan, linux-kselftest
In-Reply-To: <ad_dal164gVmImWl@dau-home-pc>
UDP encapsulation (FOU, GUE) has never worked correctly with multicast
destination addresses. When a FOU-encapsulated packet arrives at a
multicast address, it enters __udp4_lib_mcast_deliver() which calls
consume_skb() on packets that need resubmission to the inner protocol
handler, silently dropping them instead.
The unicast delivery path handles this correctly by returning -ret,
but the multicast path was never updated to support UDP encapsulation
resubmit.
This causes silent packet loss for FOU/GRETAP tunnels configured with
multicast remote addresses. The loss ratio depends on the early demux
cache hit rate - packets that hit early demux bypass the multicast path
and work correctly, masking the issue.
Reproducing the issue:
ip netns add ns_a && ip netns add ns_b
ip link add veth0 type veth peer name veth1
ip link set veth0 netns ns_a && ip link set veth1 netns ns_b
ip -n ns_a addr add 10.0.0.1/24 dev veth0 && ip -n ns_a link set veth0 up
ip -n ns_b addr add 10.0.0.2/24 dev veth1 && ip -n ns_b link set veth1 up
# Multicast routes
ip -n ns_a route add 239.0.0.0/8 dev veth0
ip -n ns_b route add 239.0.0.0/8 dev veth1
# Disable early demux to expose the issue (otherwise it's partially masked)
ip netns exec ns_b sysctl -w net.ipv4.ip_early_demux=0
# Join multicast group on receiver
ip -n ns_b addr add 239.0.0.1/32 dev veth1 autojoin
# Sender: GRETAP with FOU encap
ip -n ns_a link add eoudp0 type gretap \
remote 239.0.0.1 local 10.0.0.1 \
encap fou encap-sport 4797 encap-dport 4797 key 239.0.0.1
ip -n ns_a link set eoudp0 up
ip -n ns_a addr add 192.168.99.1/24 dev eoudp0
# Receiver: FOU listener + GRETAP
ip netns exec ns_b ip fou add port 4797 ipproto 47
ip -n ns_b link add eoudp0 type gretap \
remote 239.0.0.1 local 10.0.0.2 \
encap fou encap-sport 4797 encap-dport 4797 key 239.0.0.1
ip -n ns_b link set eoudp0 up
ip -n ns_b addr add 192.168.99.2/24 dev eoudp0
# Static neigh: ARP replies can't traverse unidirectional mcast tunnel
recv_mac=$(ip -n ns_b link show eoudp0 | awk '/ether/{print $2}')
ip -n ns_a neigh add 192.168.99.2 lladdr $recv_mac dev eoudp0
# Test: ping through the FOU/GRETAP tunnel
ip netns exec ns_a ping -c 100 192.168.99.2
# -> without this patch: 0 packets received on eoudp0
# -> with this patch: all packets received on eoudp0
AI assistance (Claude, claude-opus-4-6) was used during root cause
analysis of the kernel source code (tracing the call chain from
udp_queue_rcv_skb through encap_rcv to ip_protocol_deliver_rcu,
comparing unicast/GSO/multicast paths) and during patch and selftest
authoring. The fix approach was identified by observing that the
unicast path (udp_unicast_rcv_skb) already handles encap resubmit
correctly via return -ret, while the multicast path did not.
Changes since v2:
- Use return -ret instead of calling ip_protocol_deliver_rcu()
directly, matching the unicast path and avoiding call stack
growth with nested encapsulations (Eric Dumazet)
- Only change the first-socket path; the clone loop is not
reachable for tunnel sockets (no SO_REUSEADDR/SO_REUSEPORT)
- Replace Python packet generator with ping through a properly
configured FOU/GRETAP tunnel in the selftest
- Add static neighbor entry in the test (ARP replies cannot
traverse the unidirectional multicast tunnel)
Changes since v1 (RFC):
- Moved inline Python packet generator into a separate helper
- Fixed author email typo in Signed-off-by
Anton Danilov (2):
udp: fix encapsulation packet resubmit in multicast deliver
selftests: net: add FOU multicast encapsulation resubmit test
net/ipv4/udp.c | 6 +-
net/ipv6/udp.c | 6 +-
tools/testing/selftests/net/Makefile | 1 +
.../testing/selftests/net/fou_mcast_encap.sh | 112 ++++++++++++++++++
4 files changed, 121 insertions(+), 4 deletions(-)
create mode 100755 tools/testing/selftests/net/fou_mcast_encap.sh
--
2.47.3
^ permalink raw reply
* [RFC PATCH net-next v3 1/2] udp: fix encapsulation packet resubmit in multicast deliver
From: Anton Danilov @ 2026-05-04 22:53 UTC (permalink / raw)
To: netdev
Cc: Willem de Bruijn, davem, David Ahern, Eric Dumazet,
Kuniyuki Iwashima, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan, linux-kselftest
In-Reply-To: <cover.1777934869.git.littlesmilingcloud@gmail.com>
When a UDP encapsulation socket (e.g., FOU) receives a multicast
packet, __udp4_lib_mcast_deliver() and __udp6_lib_mcast_deliver()
call consume_skb() when udp_queue_rcv_skb() returns a positive value.
A positive return value from udp_queue_rcv_skb() indicates that the
encap_rcv handler (e.g., fou_udp_recv) has consumed the UDP header
and wants the packet to be resubmitted to the IP protocol handler
for further processing (e.g., as a GRE packet).
The unicast path in udp_unicast_rcv_skb() handles this correctly by
returning -ret, which propagates up to ip_protocol_deliver_rcu() for
resubmission. However, the multicast path destroys the packet via
consume_skb() instead of resubmitting it, causing silent packet loss.
This affects any UDP encapsulation (FOU, GUE) combined with multicast
destination addresses.
Fix this by returning -ret instead of calling consume_skb() when the
return value is positive, matching the behavior of the unicast path.
This avoids growing the call stack compared to calling
ip_protocol_deliver_rcu() directly.
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
Assisted-by: Claude:claude-opus-4-6
---
net/ipv4/udp.c | 6 ++++--
net/ipv6/udp.c | 6 ++++--
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index e9e2ce9522ef..84a98631b556 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -2467,6 +2467,7 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
struct udp_hslot *hslot;
struct sk_buff *nskb;
bool use_hash2;
+ int ret;
hash2_any = 0;
hash2 = 0;
@@ -2511,8 +2512,9 @@ static int __udp4_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
}
if (first) {
- if (udp_queue_rcv_skb(first, skb) > 0)
- consume_skb(skb);
+ ret = udp_queue_rcv_skb(first, skb);
+ if (ret > 0)
+ return -ret;
} else {
kfree_skb(skb);
__UDP_INC_STATS(net, UDP_MIB_IGNOREDMULTI);
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 15e032194ecc..ff2e389e286b 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -949,6 +949,7 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
struct udp_hslot *hslot;
struct sk_buff *nskb;
bool use_hash2;
+ int ret;
hash2_any = 0;
hash2 = 0;
@@ -998,8 +999,9 @@ static int __udp6_lib_mcast_deliver(struct net *net, struct sk_buff *skb,
}
if (first) {
- if (udpv6_queue_rcv_skb(first, skb) > 0)
- consume_skb(skb);
+ ret = udpv6_queue_rcv_skb(first, skb);
+ if (ret > 0)
+ return -ret;
} else {
kfree_skb(skb);
__UDP6_INC_STATS(net, UDP_MIB_IGNOREDMULTI);
--
2.47.3
^ permalink raw reply related
* [RFC PATCH net-next v3 2/2] selftests: net: add FOU multicast encapsulation resubmit test
From: Anton Danilov @ 2026-05-04 22:53 UTC (permalink / raw)
To: netdev
Cc: Willem de Bruijn, davem, David Ahern, Eric Dumazet,
Kuniyuki Iwashima, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan, linux-kselftest
In-Reply-To: <cover.1777934869.git.littlesmilingcloud@gmail.com>
Add a selftest to verify that FOU-encapsulated packets addressed to a
multicast destination are correctly resubmitted to the inner protocol
handler (GRE) via the UDP multicast delivery path.
The test creates two network namespaces connected by a veth pair with
a FOU/GRETAP tunnel using a multicast remote address (239.0.0.1).
Ping is sent through the tunnel and received packets are counted on
the receiver's tunnel interface.
A static neighbor entry is configured on the sender because ARP
replies from the receiver cannot traverse the unidirectional multicast
tunnel back to the sender.
The early demux optimization (net.ipv4.ip_early_demux) is disabled on
the receiver to force packets through __udp4_lib_mcast_deliver(),
which is the code path being tested.
Signed-off-by: Anton Danilov <littlesmilingcloud@gmail.com>
Assisted-by: Claude:claude-opus-4-6
---
tools/testing/selftests/net/Makefile | 1 +
.../testing/selftests/net/fou_mcast_encap.sh | 112 ++++++++++++++++++
2 files changed, 113 insertions(+)
create mode 100755 tools/testing/selftests/net/fou_mcast_encap.sh
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index a275ed584026..9b2a573e4af2 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -38,6 +38,7 @@ TEST_PROGS := \
fib_rule_tests.sh \
fib_tests.sh \
fin_ack_lat.sh \
+ fou_mcast_encap.sh \
fq_band_pktlimit.sh \
gre_gso.sh \
gre_ipv6_lladdr.sh \
diff --git a/tools/testing/selftests/net/fou_mcast_encap.sh b/tools/testing/selftests/net/fou_mcast_encap.sh
new file mode 100755
index 000000000000..8db9633f4c28
--- /dev/null
+++ b/tools/testing/selftests/net/fou_mcast_encap.sh
@@ -0,0 +1,112 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test that UDP encapsulation (FOU) correctly handles packet resubmit
+# when packets are delivered via the multicast UDP delivery path.
+#
+# When a FOU-encapsulated packet arrives with a multicast destination IP,
+# __udp4_lib_mcast_deliver() must resubmit it to the inner protocol
+# handler (e.g., GRE) rather than consuming it. This test verifies that
+# by creating a FOU/GRETAP tunnel with a multicast remote address and
+# sending ping through it.
+#
+# The early demux optimization can mask this issue by routing packets via
+# the unicast path (udp_unicast_rcv_skb), so we disable it to force
+# packets through __udp4_lib_mcast_deliver().
+
+source lib.sh
+
+NSENDER=""
+NRECV=""
+
+cleanup() {
+ cleanup_all_ns
+}
+
+trap cleanup EXIT
+
+setup() {
+ setup_ns NSENDER NRECV
+
+ ip link add veth_s type veth peer name veth_r
+ ip link set veth_s netns "$NSENDER"
+ ip link set veth_r netns "$NRECV"
+
+ ip -n "$NSENDER" addr add 10.0.0.1/24 dev veth_s
+ ip -n "$NSENDER" link set veth_s up
+
+ ip -n "$NRECV" addr add 10.0.0.2/24 dev veth_r
+ ip -n "$NRECV" link set veth_r up
+
+ # Disable early demux to force multicast delivery path
+ ip netns exec "$NRECV" sysctl -wq net.ipv4.ip_early_demux=0
+
+ # Join multicast group on receiver
+ ip -n "$NRECV" addr add 239.0.0.1/32 dev veth_r autojoin
+
+ # Multicast routes
+ ip -n "$NRECV" route add 239.0.0.0/8 dev veth_r
+ ip -n "$NSENDER" route add 239.0.0.0/8 dev veth_s
+
+ # Sender: GRETAP with FOU encap (no FOU listener needed on TX side)
+ ip -n "$NSENDER" link add eoudp0 type gretap \
+ remote 239.0.0.1 local 10.0.0.1 \
+ encap fou encap-sport 4797 encap-dport 4797 \
+ key 239.0.0.1
+ ip -n "$NSENDER" link set eoudp0 up
+ ip -n "$NSENDER" addr add 192.168.99.1/24 dev eoudp0
+
+ # Receiver: FOU listener + GRETAP
+ ip netns exec "$NRECV" ip fou add port 4797 ipproto 47
+ ip -n "$NRECV" link add eoudp0 type gretap \
+ remote 239.0.0.1 local 10.0.0.2 \
+ encap fou encap-sport 4797 encap-dport 4797 \
+ key 239.0.0.1
+ ip -n "$NRECV" link set eoudp0 up
+ ip -n "$NRECV" addr add 192.168.99.2/24 dev eoudp0
+
+ # Static neigh entry on sender: ARP replies cannot traverse the
+ # multicast tunnel back, so pre-populate the neighbor cache.
+ local recv_mac
+ recv_mac=$(ip -n "$NRECV" link show eoudp0 | awk '/ether/{print $2}')
+ ip -n "$NSENDER" neigh add 192.168.99.2 lladdr "$recv_mac" dev eoudp0
+}
+
+get_rx_packets() {
+ ip -n "$NRECV" -s link show eoudp0 | awk '/RX:/{getline; print $2}'
+}
+
+test_fou_mcast_encap() {
+ local count=100
+ local rx_before
+ local rx_after
+ local rx_delta
+
+ # Warmup: let any initial broadcast/ARP traffic settle
+ ip netns exec "$NSENDER" ping -c 1 -W 1 192.168.99.2 >/dev/null 2>&1
+ sleep 1
+
+ rx_before=$(get_rx_packets)
+ ip netns exec "$NSENDER" ping -c $count -W 1 192.168.99.2 >/dev/null 2>&1
+ sleep 1
+ rx_after=$(get_rx_packets)
+
+ rx_delta=$((rx_after - rx_before))
+
+ if [ "$rx_delta" -ge "$count" ]; then
+ echo "PASS: received $rx_delta/$count packets via multicast FOU/GRETAP"
+ return "$ksft_pass"
+ elif [ "$rx_delta" -gt 0 ]; then
+ echo "FAIL: only $rx_delta/$count packets received (partial delivery)"
+ return "$ksft_fail"
+ else
+ echo "FAIL: 0/$count packets received (multicast encap resubmit broken)"
+ return "$ksft_fail"
+ fi
+}
+
+echo "TEST: FOU/GRETAP multicast encapsulation resubmit"
+
+setup
+test_fou_mcast_encap
+exit $?
--
2.47.3
^ permalink raw reply related
* Re: [PATCH 00/11] mlx5 support for VFIO self test
From: David Matlack @ 2026-05-04 22:54 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: Alex Williamson, kvm, Leon Romanovsky, linux-kselftest,
linux-rdma, Mark Bloch, netdev, Saeed Mahameed, Shuah Khan,
Tariq Toukan, patches, Josh Hilke
In-Reply-To: <20260501164314.GA1381708@nvidia.com>
On 2026-05-01 01:43 PM, Jason Gunthorpe wrote:
> On Fri, May 01, 2026 at 09:11:11AM -0700, David Matlack wrote:
> > On Thu, Apr 30, 2026 at 5:08 PM Jason Gunthorpe <jgg@nvidia.com> wrote:
> > >
> > > Add an mlx5 driver to VFIO self test. This is largely a remix of the
> > > existing VFIO mlx5 driver in rdma-core. It uses an RDMA loopback QP
> > > to issue RDMA WRITE operations which effectively perform memory
> > > copies using DMA. Since mlx5 has a stable programming ABI this
> > > should work on devices from CX5 to current HW. The device FW must
> > > support the QP loopback configuration.
> >
> > > This entire series was coded by Claude Code in about 4 days.
> >
> > Very exciting. Josh Hilke from Google is also working on using AI to
> > create a selftest driver for Intel IGB NICs so VFIO selftests can run
> > in QEMU [1]. So it's encouraging to see you were able to do it with
> > mlx5.
> >
> > [1] https://www.qemu.org/docs/master/system/devices/igb.html
>
> Yes! I would feed DPDK in as well in this case? Combined with the
> kernel driver it should be doable. It is much easier if you understand
> how the NIC works, of course. This worked out significantly because I
> guided it through sufficiently small steps and knew where to find all
> the quality reference material..
>
> > > - Make it work on a PF too (this is surprisingly hard!).
> >
> > Can it work on CX VFs? We're interested in continuously performing
> > memory copies across a Live Update using a VF via selftests to
> > demonstrate SR-IOV preservation (when we eventually get there).
>
> Yes, I started with VF because it is simpler.
Makes sense. I tested it out and was able to get vfio_pci_driver_test
passing with a CX7 VF.
> The PF support flow requires a bunch more complicated stuff.
Do you think it's worth supporting PFs? If anyone with a CX NIC can
enable SR-IOV and run selftests on a VF then we can keep the driver
somewhat simpler.
^ permalink raw reply
* Re: [PATCH 1/2 net] ipv6: addrconf: fix temp address generation after prefix deprecation
From: Fernando Fernandez Mancera @ 2026-05-04 22:58 UTC (permalink / raw)
To: Ido Schimmel
Cc: netdev, linux-kselftest, horms, pabeni, kuba, edumazet, davem,
dsahern, Łukasz Stelmach
In-Reply-To: <2532adc0-262e-433a-9b69-df6d49f662c4@suse.de>
On 5/4/26 9:51 PM, Fernando Fernandez Mancera wrote:
> On 5/4/26 6:35 PM, Ido Schimmel wrote:
>> On Mon, May 04, 2026 at 12:11:40AM +0200, Fernando Fernandez Mancera
>> wrote:
>>> When a router temporarily deprecates an IPv6 prefix (either by sending a
>>> Router Advertisement with Preferred Lifetime = 0 or by letting the
>>> lifetime expire) and later restores it, the kernel permanently loses its
>>> ability to generate temporary privacy addresses (RFC 8981) for that
>>> prefix.
>>>
>>> This happens because the address worker attempts to generate a
>>> replacement temporary address when the current one nears expiration. As
>>> the base prefix is deprecated already, the generation fails, burning the
>>> retry counter for temporary address generation of that prefix.
>>>
>>> When the router eventually restores the prefix, the temporary address
>>> becomes active again. However, once it naturally expires, the kernel
>>> sees the exhausted retry limit and permanently stops generating new
>>> privacy addresses.
>>
>> It's not clear to me to which "retry counter" you are referring to. Are
>> you referring to the counter of the temporary address or to that of the
>> "public address" from which it was generated?
>>
>> AFAICT, in the case of temporary addresses (those w/o "mngtmpaddr") this
>> isn't really a counter, but a boolean that tells you if an address was
>> already spawned from this address.
>>
>>>
>>> Fix this by verifying that the base prefix has sufficient preferred
>>> lifetime remaining before attempting to generate a new temporary
>>> address. This prevents the worker from burning through its retry counter
>>> during temporary network deprecation events like a router reboot.
>>
>> Again, I think that "retry counter" here is confusing. IIUC, what
>> happens is that the kernel marks the temporary address as having spawned
>> an address ('ifp->regen_count++'), then tries to spawn an address by
>> calling ipv6_create_tempaddr(), which fails because the preferred
>> lifetime of the public address is 0.
>>
>
> Yes, you are right. I used the word counter as the variable was ifp-
> >regen_count although for temporary addresses it is just a boolean. Let
> me modify the commit message to make it clear.
>
> Thanks Ido for reviewing!
>
I have checked sashiko feedback and it spotted another race condition.
To fix this altogether I believe we could improve ipv6_create_tempaddr()
to return meaningful return value. This way we could catch a
-ETIME/-ETIMEDOUT and reset the regen_count on the temporary address.
https://sashiko.dev/#/patchset/20260503221139.3742-3-fmancera%40suse.de?part=1
>>>
>>> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
>>> Reported-by: Łukasz Stelmach <steelman@post.pl>
>>> Closes: https://lore.kernel.org/
>>> netdev/87340td30q.fsf%25steelman@post.pl/
>>> Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
>>> ---
>>> net/ipv6/addrconf.c | 4 +++-
>>> 1 file changed, 3 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
>>> index 5476b6536eb7..f6a3d9da3cb1 100644
>>> --- a/net/ipv6/addrconf.c
>>> +++ b/net/ipv6/addrconf.c
>>> @@ -4654,9 +4654,11 @@ static void addrconf_verify_rtnl(struct net *net)
>>> !ifp->regen_count && ifp->ifpub) {
>>> /* This is a non-regenerated temporary addr. */
>>> + unsigned long pub_age = (now - READ_ONCE(ifp->ifpub-
>>> >tstamp)) / HZ;
>>> unsigned long regen_advance =
>>> ipv6_get_regen_advance(ifp->idev);
>>> - if (age + regen_advance >= ifp->prefered_lft) {
>>> + if (age + regen_advance >= ifp->prefered_lft &&
>>> + pub_age + regen_advance < READ_ONCE(ifp->ifpub-
>>> >prefered_lft)) {
>>> struct inet6_ifaddr *ifpub = ifp->ifpub;
>>> if (time_before(ifp->tstamp + ifp->prefered_lft
>>> * HZ, next))
>>> next = ifp->tstamp + ifp->prefered_lft * HZ;
>>> --
>>> 2.53.0
>>>
>
^ permalink raw reply
* [PATCH v13 net-next 0/5] psp: Add support for dev-assoc/disassoc
From: Wei Wang @ 2026-05-04 23:00 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S . Miller, Eric Dumazet, Simon Horman,
Paolo Abeni
Cc: Wei Wang
From: Wei Wang <weibunny@fb.com>
The main purpose of this feature is to associate virtual devices like
veth or netkit with a real PSP device, so we could provide PSP
functionality to the application running with virtual devices.
A typical deployment that works with this feature is as follows:
Host Namespace:
psp_dev_local ←──physically linked──→ psp_dev_peer
(PSP device)
│
│ BPF on psp_dev_local ingress: bpf_redirect_peer() to nk_guest
│
nk_host / veth_host
│
│ BPF on nk_host ingress: bpf_redirect_neigh() to psp_dev_local
│
Guest Namespace (netns):
│
nk_guest / veth_guest
★ PSP application run here
Remote Namespace (_netns):
psp_dev_peer
★ PSP server application runs here
Note:
The general requirement for this feature to work:
For PSP to work correctly, the egress device at validate_xmit_skb()
time must have psp_dev matching the association's psd. Any device
stacking or traffic redirection that changes the egress device will
cause either:
1. TX validation failure (SKB_DROP_REASON_PSP_OUTPUT) - fail-safe
2. RX policy failure after tx-assoc - packets without PSP extension
are rejected by receiver expecting encrypted traffic
Here are a few examples that this feature would not work:
- Bonding with load balancing in round-robin, XOR, 802.3ad mode across
multiple PSP devices, or mixed PSP and non-PSP devices
- Bonding with active-backup mode might work without PSP migration for
failover case.
- ipvlan/macvlan in bridge mode would not work given packets are
loopbacked locally without going through the PSP device.
Changes since v12:
- Rebase
Changes since v11:
- Cap max number of associated devs per psd to 128 to avoid overflowing
GENLMSG_DEFAULT_SIZE for netlink msg in patch 2
- Make common function for getting net in psp_nl_dev_assoc_doit() and
psp_nl_dev_disassoc_doit() in patch 2
- Only allow NSID to be passed in when user is in dev_net(psd->main_netdev)
to avoid manipulation of dev-assoc/dev-disassoc from netns other than
its own in patch 2
- Fixed the race between netdev_unregister() and psp_nl_dev_assoc_doit()
by adding devreg status check in psp_nl_dev_assoc_doit().
Changes since v10:
- Corrected typo on patch 1
- Removed the kdoc style comments, Use goto style in
psp_nl_dev_assoc_doit() clean up code, Resolved "TOCTOU" issue in
psp_assoc_device_get_locked() in patch 2
- Replaced psp_devs_lock with a new mutex in
psp_attach_netdev_notifier(), Fixed kdoc style comments in patch 3
Changes since v9:
- Added comments for psp_device_get_locked(), fixed lint issue, fixed
rcu warning in patch 2
- Return error if register_netdevice_notifier() fails in
psp_device_get_locked_dev_assoc() in patch 3
- Removed psp version and ip version for unnecessary tests cases in
patch 5
Changes since v8:
- Rebase
Changes since v7:
- Refactor in patch 1 to have a common helper for
psp_device_get_locked_admin() and psp_device_get_locked()
- Take psd->lock in psp_assoc_device_get_locked() before
psp_dev_check_access() in patch 2
- Use cmpxchg() for assoc_dev->psp_dev assignment when doing dev-assoc
in patch 2
- Check for err for register_netdevice_notifier() in patch 3
- Call psp_attach_netdev_notifier() in pre_doit handler for dev-assoc to
avoid releasing of psd->lock in patch 3
Changes since v6:
- Remove the unused remote_addr, nk_guest_addr and import cmd in patch 5
Changes since v5:
- Remove module_exit() in patch 3
Changes since v4:
- Address compilation warning in patch 3
- Removed the call to psp_nl_has_listeners_any_ns() and check listeners
when looping through netns in psp_nl_notify_dev() in patch 2. This
makes sure we only send notification to netns that has listeners.
Changes since v3:
- Make nsid optional for dev-assoc/dev-disassoc operation, and use
the ns user is in when it's not specified. Also added a test for this.
- Fix psp_nl_notify_dev() to compute the correct nsid relative to the
listener's netns.
- Only register the new netdev event for psp dev cleanup upon the first
successful dev-assoc operation.
- Change the following in selftest:
- Add CONFIG_NETKIT to driver/net's config
- Fall back to NetDrvEpEnv and run basic test cases if NetDrvContEnv
does not load
- Use ksft_variants instead of psp_ip_ver_test_builder
Changes since v2:
- Change the newly added parameter to psp_device_get_and_lock() to
admin in patch 1. Introduce 2 device check functions:
- psp_device_get_locked_admin() for dev-set and key-rotate
- psp_device_get_locked() for all other operations
Flip the logic for checking the dev_assoc_list accordingly in patch 2.
- Move psp_nl_notify_dev() before removing the dev from assoc_dev_list
in psp_nl_dev_disassoc_doit() and correct the typo in commit msg in
patch 2.
- Remove the threading and subprocess and some comment updates in patch 5.
Changes since v1:
- Update the first 4 patches to reflect the latest changes in
https://lore.kernel.org/netdev/20260302053315.1919859-1-dw@davidwei.uk/
- Update patch 9 to add a param to NetDrvContEnv to control the loading
of the tx forwarding bpf program
Wei Wang (5):
psp: add admin/non-admin version of psp_device_get_locked
psp: add new netlink cmd for dev-assoc and dev-disassoc
psp: add a new netdev event for dev unregister
selftests/net: Add bpf skb forwarding program
selftests/net: psp: Add test for dev-assoc/disassoc
Documentation/netlink/specs/psp.yaml | 71 ++-
include/net/psp/types.h | 23 +
include/uapi/linux/psp.h | 13 +
net/psp/psp-nl-gen.c | 36 +-
net/psp/psp-nl-gen.h | 7 +
net/psp/psp.h | 3 +-
net/psp/psp_main.c | 104 +++-
net/psp/psp_nl.c | 398 ++++++++++++++-
tools/testing/selftests/drivers/net/config | 1 +
.../drivers/net/hw/nk_redirect.bpf.c | 60 +++
.../selftests/drivers/net/lib/py/env.py | 54 ++-
tools/testing/selftests/drivers/net/psp.py | 457 ++++++++++++++++--
12 files changed, 1167 insertions(+), 60 deletions(-)
create mode 100644 tools/testing/selftests/drivers/net/hw/nk_redirect.bpf.c
--
2.52.0
^ permalink raw reply
* [PATCH v13 net-next 1/5] psp: add admin/non-admin version of psp_device_get_locked
From: Wei Wang @ 2026-05-04 23:00 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S . Miller, Eric Dumazet, Simon Horman,
Paolo Abeni
Cc: Wei Wang
In-Reply-To: <20260504230056.917415-1-weibunny.kernel@gmail.com>
From: Wei Wang <weibunny@fb.com>
Introduce 2 versions of psp_device_get_locked:
1. psp_device_get_locked_admin(): This version is used for operations
that would change the status of the psd, and are currently used for
dev-set and key-rotation.
2. psp_device_get_locked(): This is the non-admin version, which are
used for broader user issued operations including: dev-get, rx-assoc,
tx-assoc, get-stats.
Following commit will be implementing both of the checks.
Signed-off-by: Wei Wang <weibunny@fb.com>
Reviewed-by: Daniel Zahka <daniel.zahka@gmail.com>
---
Documentation/netlink/specs/psp.yaml | 4 ++--
net/psp/psp-nl-gen.c | 4 ++--
net/psp/psp-nl-gen.h | 2 ++
net/psp/psp.h | 2 +-
net/psp/psp_main.c | 7 +++++-
net/psp/psp_nl.c | 33 ++++++++++++++++++++--------
6 files changed, 37 insertions(+), 15 deletions(-)
diff --git a/Documentation/netlink/specs/psp.yaml b/Documentation/netlink/specs/psp.yaml
index bfcd6e4ecb85..e3b0fe296e8f 100644
--- a/Documentation/netlink/specs/psp.yaml
+++ b/Documentation/netlink/specs/psp.yaml
@@ -196,7 +196,7 @@ operations:
- psp-versions-ena
reply:
attributes: []
- pre: psp-device-get-locked
+ pre: psp-device-get-locked-admin
post: psp-device-unlock
-
name: dev-change-ntf
@@ -216,7 +216,7 @@ operations:
reply:
attributes:
- id
- pre: psp-device-get-locked
+ pre: psp-device-get-locked-admin
post: psp-device-unlock
-
name: key-rotate-ntf
diff --git a/net/psp/psp-nl-gen.c b/net/psp/psp-nl-gen.c
index 953309952cef..a71dd629aeab 100644
--- a/net/psp/psp-nl-gen.c
+++ b/net/psp/psp-nl-gen.c
@@ -71,7 +71,7 @@ static const struct genl_split_ops psp_nl_ops[] = {
},
{
.cmd = PSP_CMD_DEV_SET,
- .pre_doit = psp_device_get_locked,
+ .pre_doit = psp_device_get_locked_admin,
.doit = psp_nl_dev_set_doit,
.post_doit = psp_device_unlock,
.policy = psp_dev_set_nl_policy,
@@ -80,7 +80,7 @@ static const struct genl_split_ops psp_nl_ops[] = {
},
{
.cmd = PSP_CMD_KEY_ROTATE,
- .pre_doit = psp_device_get_locked,
+ .pre_doit = psp_device_get_locked_admin,
.doit = psp_nl_key_rotate_doit,
.post_doit = psp_device_unlock,
.policy = psp_key_rotate_nl_policy,
diff --git a/net/psp/psp-nl-gen.h b/net/psp/psp-nl-gen.h
index 599c5f1c82f2..977355455395 100644
--- a/net/psp/psp-nl-gen.h
+++ b/net/psp/psp-nl-gen.h
@@ -17,6 +17,8 @@ extern const struct nla_policy psp_keys_nl_policy[PSP_A_KEYS_SPI + 1];
int psp_device_get_locked(const struct genl_split_ops *ops,
struct sk_buff *skb, struct genl_info *info);
+int psp_device_get_locked_admin(const struct genl_split_ops *ops,
+ struct sk_buff *skb, struct genl_info *info);
int psp_assoc_device_get_locked(const struct genl_split_ops *ops,
struct sk_buff *skb, struct genl_info *info);
void
diff --git a/net/psp/psp.h b/net/psp/psp.h
index 9f19137593a0..0f9c4e4e52cb 100644
--- a/net/psp/psp.h
+++ b/net/psp/psp.h
@@ -14,7 +14,7 @@ extern struct xarray psp_devs;
extern struct mutex psp_devs_lock;
void psp_dev_free(struct psp_dev *psd);
-int psp_dev_check_access(struct psp_dev *psd, struct net *net);
+int psp_dev_check_access(struct psp_dev *psd, struct net *net, bool admin);
void psp_nl_notify_dev(struct psp_dev *psd, u32 cmd);
diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c
index 524978dfb8fd..b56a51d524f5 100644
--- a/net/psp/psp_main.c
+++ b/net/psp/psp_main.c
@@ -27,10 +27,15 @@ struct mutex psp_devs_lock;
* psp_dev_check_access() - check if user in a given net ns can access PSP dev
* @psd: PSP device structure user is trying to access
* @net: net namespace user is in
+ * @admin: If true, only allow access from @psd's main device's netns,
+ * for admin operations like config changes and key rotation.
+ * If false, also allow access from network namespaces that have
+ * an associated device with @psd, for read-only and association
+ * management operations.
*
* Return: 0 if PSP device should be visible in @net, errno otherwise.
*/
-int psp_dev_check_access(struct psp_dev *psd, struct net *net)
+int psp_dev_check_access(struct psp_dev *psd, struct net *net, bool admin)
{
if (dev_net(psd->main_netdev) == net)
return 0;
diff --git a/net/psp/psp_nl.c b/net/psp/psp_nl.c
index 0cc744a6e1c9..b4f1b7f9b0c2 100644
--- a/net/psp/psp_nl.c
+++ b/net/psp/psp_nl.c
@@ -41,7 +41,8 @@ static int psp_nl_reply_send(struct sk_buff *rsp, struct genl_info *info)
/* Device stuff */
static struct psp_dev *
-psp_device_get_and_lock(struct net *net, struct nlattr *dev_id)
+psp_device_get_and_lock(struct net *net, struct nlattr *dev_id,
+ bool admin)
{
struct psp_dev *psd;
int err;
@@ -56,7 +57,7 @@ psp_device_get_and_lock(struct net *net, struct nlattr *dev_id)
mutex_lock(&psd->lock);
mutex_unlock(&psp_devs_lock);
- err = psp_dev_check_access(psd, net);
+ err = psp_dev_check_access(psd, net, admin);
if (err) {
mutex_unlock(&psd->lock);
return ERR_PTR(err);
@@ -65,17 +66,31 @@ psp_device_get_and_lock(struct net *net, struct nlattr *dev_id)
return psd;
}
-int psp_device_get_locked(const struct genl_split_ops *ops,
- struct sk_buff *skb, struct genl_info *info)
+static int __psp_device_get_locked(const struct genl_split_ops *ops,
+ struct sk_buff *skb, struct genl_info *info,
+ bool admin)
{
if (GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_ID))
return -EINVAL;
info->user_ptr[0] = psp_device_get_and_lock(genl_info_net(info),
- info->attrs[PSP_A_DEV_ID]);
+ info->attrs[PSP_A_DEV_ID],
+ admin);
return PTR_ERR_OR_ZERO(info->user_ptr[0]);
}
+int psp_device_get_locked_admin(const struct genl_split_ops *ops,
+ struct sk_buff *skb, struct genl_info *info)
+{
+ return __psp_device_get_locked(ops, skb, info, true);
+}
+
+int psp_device_get_locked(const struct genl_split_ops *ops,
+ struct sk_buff *skb, struct genl_info *info)
+{
+ return __psp_device_get_locked(ops, skb, info, false);
+}
+
void
psp_device_unlock(const struct genl_split_ops *ops, struct sk_buff *skb,
struct genl_info *info)
@@ -160,7 +175,7 @@ static int
psp_nl_dev_get_dumpit_one(struct sk_buff *rsp, struct netlink_callback *cb,
struct psp_dev *psd)
{
- if (psp_dev_check_access(psd, sock_net(rsp->sk)))
+ if (psp_dev_check_access(psd, sock_net(rsp->sk), false))
return 0;
return psp_nl_dev_fill(psd, rsp, genl_info_dump(cb));
@@ -310,7 +325,7 @@ int psp_assoc_device_get_locked(const struct genl_split_ops *ops,
*/
mutex_lock(&psd->lock);
if (!psp_dev_is_registered(psd) ||
- psp_dev_check_access(psd, genl_info_net(info))) {
+ psp_dev_check_access(psd, genl_info_net(info), false)) {
mutex_unlock(&psd->lock);
psp_dev_put(psd);
psd = NULL;
@@ -334,7 +349,7 @@ int psp_assoc_device_get_locked(const struct genl_split_ops *ops,
psp_dev_put(psd);
} else {
- psd = psp_device_get_and_lock(genl_info_net(info), id);
+ psd = psp_device_get_and_lock(genl_info_net(info), id, false);
if (IS_ERR(psd)) {
err = PTR_ERR(psd);
goto err_sock_put;
@@ -577,7 +592,7 @@ static int
psp_nl_stats_get_dumpit_one(struct sk_buff *rsp, struct netlink_callback *cb,
struct psp_dev *psd)
{
- if (psp_dev_check_access(psd, sock_net(rsp->sk)))
+ if (psp_dev_check_access(psd, sock_net(rsp->sk), false))
return 0;
return psp_nl_stats_fill(psd, rsp, genl_info_dump(cb));
--
2.52.0
^ permalink raw reply related
* [PATCH v13 net-next 2/5] psp: add new netlink cmd for dev-assoc and dev-disassoc
From: Wei Wang @ 2026-05-04 23:00 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S . Miller, Eric Dumazet, Simon Horman,
Paolo Abeni
Cc: Wei Wang
In-Reply-To: <20260504230056.917415-1-weibunny.kernel@gmail.com>
From: Wei Wang <weibunny@fb.com>
The main purpose of this cmd is to be able to associate a
non-psp-capable device (e.g. veth or netkit) with a psp device.
One use case is if we create a pair of veth/netkit, and assign 1 end
inside a netns, while leaving the other end within the default netns,
with a real PSP device, e.g. netdevsim or a physical PSP-capable NIC.
With this command, we could associate the veth/netkit inside the netns
with PSP device, so the virtual device could act as PSP-capable device
to initiate PSP connections, and performs PSP encryption/decryption on
the real PSP device.
Signed-off-by: Wei Wang <weibunny@fb.com>
Reviewed-by: Daniel Zahka <daniel.zahka@gmail.com>
---
Documentation/netlink/specs/psp.yaml | 67 +++++-
include/net/psp/types.h | 23 ++
include/uapi/linux/psp.h | 13 ++
net/psp/psp-nl-gen.c | 32 +++
net/psp/psp-nl-gen.h | 2 +
net/psp/psp_main.c | 21 ++
net/psp/psp_nl.c | 336 ++++++++++++++++++++++++++-
7 files changed, 483 insertions(+), 11 deletions(-)
diff --git a/Documentation/netlink/specs/psp.yaml b/Documentation/netlink/specs/psp.yaml
index e3b0fe296e8f..ce0a03e2d746 100644
--- a/Documentation/netlink/specs/psp.yaml
+++ b/Documentation/netlink/specs/psp.yaml
@@ -13,6 +13,17 @@ definitions:
hdr0-aes-gmac-128, hdr0-aes-gmac-256]
attribute-sets:
+ -
+ name: assoc-dev-info
+ attributes:
+ -
+ name: ifindex
+ doc: ifindex of an associated network device.
+ type: u32
+ -
+ name: nsid
+ doc: Network namespace ID of the associated device.
+ type: s32
-
name: dev
attributes:
@@ -24,7 +35,9 @@ attribute-sets:
min: 1
-
name: ifindex
- doc: ifindex of the main netdevice linked to the PSP device.
+ doc: |
+ ifindex of the main netdevice linked to the PSP device,
+ or the ifindex to associate with the PSP device.
type: u32
-
name: psp-versions-cap
@@ -38,6 +51,28 @@ attribute-sets:
type: u32
enum: version
enum-as-flags: true
+ -
+ name: assoc-list
+ doc: List of associated virtual devices.
+ type: nest
+ nested-attributes: assoc-dev-info
+ multi-attr: true
+ -
+ name: nsid
+ doc: |
+ Network namespace ID for the device to associate/disassociate.
+ Optional for dev-assoc and dev-disassoc; if not present, the
+ device is looked up in the caller's network namespace.
+ type: s32
+ -
+ name: by-association
+ doc: |
+ Flag indicating the PSP device is an associated device from a
+ different network namespace.
+ Present when in associated namespace, absent when in primary/host
+ namespace.
+ type: flag
+
-
name: assoc
attributes:
@@ -170,6 +205,8 @@ operations:
- ifindex
- psp-versions-cap
- psp-versions-ena
+ - assoc-list
+ - by-association
pre: psp-device-get-locked
post: psp-device-unlock
dump:
@@ -281,6 +318,34 @@ operations:
post: psp-device-unlock
dump:
reply: *stats-all
+ -
+ name: dev-assoc
+ doc: Associate a network device with a PSP device.
+ attribute-set: dev
+ do:
+ request:
+ attributes:
+ - id
+ - ifindex
+ - nsid
+ reply:
+ attributes: []
+ pre: psp-device-get-locked
+ post: psp-device-unlock
+ -
+ name: dev-disassoc
+ doc: Disassociate a network device from a PSP device.
+ attribute-set: dev
+ do:
+ request:
+ attributes:
+ - id
+ - ifindex
+ - nsid
+ reply:
+ attributes: []
+ pre: psp-device-get-locked
+ post: psp-device-unlock
mcast-groups:
list:
diff --git a/include/net/psp/types.h b/include/net/psp/types.h
index 25a9096d4e7d..87991a1ea02d 100644
--- a/include/net/psp/types.h
+++ b/include/net/psp/types.h
@@ -5,6 +5,7 @@
#include <linux/mutex.h>
#include <linux/refcount.h>
+#include <net/net_trackers.h>
struct netlink_ext_ack;
@@ -43,9 +44,29 @@ struct psp_dev_config {
u32 versions;
};
+/* Max number of devices that can be associated with a single PSP device.
+ * Each entry consumes ~24 bytes in the netlink dev-get response, and the
+ * response must fit in GENLMSG_DEFAULT_SIZE (~3.7KB).
+ */
+#define PSP_ASSOC_DEV_MAX 128
+
+/**
+ * struct psp_assoc_dev - wrapper for associated net_device
+ * @dev_list: list node for psp_dev::assoc_dev_list
+ * @assoc_dev: the associated net_device
+ * @dev_tracker: tracker for the net_device reference
+ */
+struct psp_assoc_dev {
+ struct list_head dev_list;
+ struct net_device *assoc_dev;
+ netdevice_tracker dev_tracker;
+};
+
/**
* struct psp_dev - PSP device struct
* @main_netdev: original netdevice of this PSP device
+ * @assoc_dev_list: list of psp_assoc_dev entries associated with this PSP device
+ * @assoc_dev_cnt: number of entries in @assoc_dev_list
* @ops: driver callbacks
* @caps: device capabilities
* @drv_priv: driver priv pointer
@@ -67,6 +88,8 @@ struct psp_dev_config {
*/
struct psp_dev {
struct net_device *main_netdev;
+ struct list_head assoc_dev_list;
+ int assoc_dev_cnt;
struct psp_dev_ops *ops;
struct psp_dev_caps *caps;
diff --git a/include/uapi/linux/psp.h b/include/uapi/linux/psp.h
index a3a336488dc3..1c8899cd4da5 100644
--- a/include/uapi/linux/psp.h
+++ b/include/uapi/linux/psp.h
@@ -17,11 +17,22 @@ enum psp_version {
PSP_VERSION_HDR0_AES_GMAC_256,
};
+enum {
+ PSP_A_ASSOC_DEV_INFO_IFINDEX = 1,
+ PSP_A_ASSOC_DEV_INFO_NSID,
+
+ __PSP_A_ASSOC_DEV_INFO_MAX,
+ PSP_A_ASSOC_DEV_INFO_MAX = (__PSP_A_ASSOC_DEV_INFO_MAX - 1)
+};
+
enum {
PSP_A_DEV_ID = 1,
PSP_A_DEV_IFINDEX,
PSP_A_DEV_PSP_VERSIONS_CAP,
PSP_A_DEV_PSP_VERSIONS_ENA,
+ PSP_A_DEV_ASSOC_LIST,
+ PSP_A_DEV_NSID,
+ PSP_A_DEV_BY_ASSOCIATION,
__PSP_A_DEV_MAX,
PSP_A_DEV_MAX = (__PSP_A_DEV_MAX - 1)
@@ -74,6 +85,8 @@ enum {
PSP_CMD_RX_ASSOC,
PSP_CMD_TX_ASSOC,
PSP_CMD_GET_STATS,
+ PSP_CMD_DEV_ASSOC,
+ PSP_CMD_DEV_DISASSOC,
__PSP_CMD_MAX,
PSP_CMD_MAX = (__PSP_CMD_MAX - 1)
diff --git a/net/psp/psp-nl-gen.c b/net/psp/psp-nl-gen.c
index a71dd629aeab..18a6e8bdb6a1 100644
--- a/net/psp/psp-nl-gen.c
+++ b/net/psp/psp-nl-gen.c
@@ -53,6 +53,20 @@ static const struct nla_policy psp_get_stats_nl_policy[PSP_A_STATS_DEV_ID + 1] =
[PSP_A_STATS_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1),
};
+/* PSP_CMD_DEV_ASSOC - do */
+static const struct nla_policy psp_dev_assoc_nl_policy[PSP_A_DEV_NSID + 1] = {
+ [PSP_A_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1),
+ [PSP_A_DEV_IFINDEX] = { .type = NLA_U32, },
+ [PSP_A_DEV_NSID] = { .type = NLA_S32, },
+};
+
+/* PSP_CMD_DEV_DISASSOC - do */
+static const struct nla_policy psp_dev_disassoc_nl_policy[PSP_A_DEV_NSID + 1] = {
+ [PSP_A_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1),
+ [PSP_A_DEV_IFINDEX] = { .type = NLA_U32, },
+ [PSP_A_DEV_NSID] = { .type = NLA_S32, },
+};
+
/* Ops table for psp */
static const struct genl_split_ops psp_nl_ops[] = {
{
@@ -119,6 +133,24 @@ static const struct genl_split_ops psp_nl_ops[] = {
.dumpit = psp_nl_get_stats_dumpit,
.flags = GENL_CMD_CAP_DUMP,
},
+ {
+ .cmd = PSP_CMD_DEV_ASSOC,
+ .pre_doit = psp_device_get_locked,
+ .doit = psp_nl_dev_assoc_doit,
+ .post_doit = psp_device_unlock,
+ .policy = psp_dev_assoc_nl_policy,
+ .maxattr = PSP_A_DEV_NSID,
+ .flags = GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = PSP_CMD_DEV_DISASSOC,
+ .pre_doit = psp_device_get_locked,
+ .doit = psp_nl_dev_disassoc_doit,
+ .post_doit = psp_device_unlock,
+ .policy = psp_dev_disassoc_nl_policy,
+ .maxattr = PSP_A_DEV_NSID,
+ .flags = GENL_CMD_CAP_DO,
+ },
};
static const struct genl_multicast_group psp_nl_mcgrps[] = {
diff --git a/net/psp/psp-nl-gen.h b/net/psp/psp-nl-gen.h
index 977355455395..4dd0f0f23053 100644
--- a/net/psp/psp-nl-gen.h
+++ b/net/psp/psp-nl-gen.h
@@ -33,6 +33,8 @@ int psp_nl_rx_assoc_doit(struct sk_buff *skb, struct genl_info *info);
int psp_nl_tx_assoc_doit(struct sk_buff *skb, struct genl_info *info);
int psp_nl_get_stats_doit(struct sk_buff *skb, struct genl_info *info);
int psp_nl_get_stats_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
+int psp_nl_dev_assoc_doit(struct sk_buff *skb, struct genl_info *info);
+int psp_nl_dev_disassoc_doit(struct sk_buff *skb, struct genl_info *info);
enum {
PSP_NLGRP_MGMT,
diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c
index b56a51d524f5..792a47105a6c 100644
--- a/net/psp/psp_main.c
+++ b/net/psp/psp_main.c
@@ -37,8 +37,18 @@ struct mutex psp_devs_lock;
*/
int psp_dev_check_access(struct psp_dev *psd, struct net *net, bool admin)
{
+ struct psp_assoc_dev *entry;
+
if (dev_net(psd->main_netdev) == net)
return 0;
+
+ if (!admin) {
+ list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
+ if (dev_net(entry->assoc_dev) == net)
+ return 0;
+ }
+ }
+
return -ENOENT;
}
@@ -74,6 +84,7 @@ psp_dev_create(struct net_device *netdev,
return ERR_PTR(-ENOMEM);
psd->main_netdev = netdev;
+ INIT_LIST_HEAD(&psd->assoc_dev_list);
psd->ops = psd_ops;
psd->caps = psd_caps;
psd->drv_priv = priv_ptr;
@@ -125,6 +136,7 @@ void psp_dev_free(struct psp_dev *psd)
*/
void psp_dev_unregister(struct psp_dev *psd)
{
+ struct psp_assoc_dev *entry, *entry_tmp;
struct psp_assoc *pas, *next;
mutex_lock(&psp_devs_lock);
@@ -144,6 +156,15 @@ void psp_dev_unregister(struct psp_dev *psd)
list_for_each_entry_safe(pas, next, &psd->stale_assocs, assocs_list)
psp_dev_tx_key_del(psd, pas);
+ list_for_each_entry_safe(entry, entry_tmp, &psd->assoc_dev_list,
+ dev_list) {
+ list_del(&entry->dev_list);
+ rcu_assign_pointer(entry->assoc_dev->psp_dev, NULL);
+ netdev_put(entry->assoc_dev, &entry->dev_tracker);
+ kfree(entry);
+ }
+ psd->assoc_dev_cnt = 0;
+
rcu_assign_pointer(psd->main_netdev->psp_dev, NULL);
psd->ops = NULL;
diff --git a/net/psp/psp_nl.c b/net/psp/psp_nl.c
index b4f1b7f9b0c2..8dbe2fe882dd 100644
--- a/net/psp/psp_nl.c
+++ b/net/psp/psp_nl.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/ethtool.h>
+#include <linux/net_namespace.h>
#include <linux/skbuff.h>
#include <linux/xarray.h>
#include <net/genetlink.h>
@@ -38,6 +39,73 @@ static int psp_nl_reply_send(struct sk_buff *rsp, struct genl_info *info)
return genlmsg_reply(rsp, info);
}
+/**
+ * psp_nl_multicast_per_ns() - multicast a notification to each unique netns
+ * @psd: PSP device (must be locked)
+ * @group: multicast group
+ * @build_ntf: callback to build an skb for a given netns, or NULL on failure
+ * @ctx: opaque context passed to @build_ntf
+ *
+ * Iterates all unique network namespaces from the associated device list
+ * plus the main device's netns. For each unique netns, calls @build_ntf
+ * to construct a notification skb and multicasts it.
+ */
+static void psp_nl_multicast_per_ns(struct psp_dev *psd, unsigned int group,
+ struct sk_buff *(*build_ntf)(struct psp_dev *,
+ struct net *,
+ void *),
+ void *ctx)
+{
+ struct psp_assoc_dev *entry;
+ struct xarray sent_nets;
+ struct net *main_net;
+ struct sk_buff *ntf;
+
+ main_net = dev_net(psd->main_netdev);
+ xa_init(&sent_nets);
+
+ list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
+ struct net *assoc_net = dev_net(entry->assoc_dev);
+ int ret;
+
+ if (net_eq(assoc_net, main_net))
+ continue;
+
+ ret = xa_insert(&sent_nets, (unsigned long)assoc_net, assoc_net,
+ GFP_KERNEL);
+ if (ret == -EBUSY)
+ continue;
+
+ ntf = build_ntf(psd, assoc_net, ctx);
+ if (!ntf)
+ continue;
+
+ genlmsg_multicast_netns(&psp_nl_family, assoc_net, ntf, 0,
+ group, GFP_KERNEL);
+ }
+ xa_destroy(&sent_nets);
+
+ /* Send to main device netns */
+ ntf = build_ntf(psd, main_net, ctx);
+ if (!ntf)
+ return;
+ genlmsg_multicast_netns(&psp_nl_family, main_net, ntf, 0, group,
+ GFP_KERNEL);
+}
+
+static struct sk_buff *psp_nl_clone_ntf(struct psp_dev *psd, struct net *net,
+ void *ctx)
+{
+ return skb_clone(ctx, GFP_KERNEL);
+}
+
+static void psp_nl_multicast_all_ns(struct psp_dev *psd, struct sk_buff *ntf,
+ unsigned int group)
+{
+ psp_nl_multicast_per_ns(psd, group, psp_nl_clone_ntf, ntf);
+ nlmsg_free(ntf);
+}
+
/* Device stuff */
static struct psp_dev *
@@ -79,18 +147,58 @@ static int __psp_device_get_locked(const struct genl_split_ops *ops,
return PTR_ERR_OR_ZERO(info->user_ptr[0]);
}
+/*
+ * Admin version of psp_device_get_locked() where it returns psd only if
+ * current netns is the same as psd->main_netdev's netns.
+ */
int psp_device_get_locked_admin(const struct genl_split_ops *ops,
struct sk_buff *skb, struct genl_info *info)
{
return __psp_device_get_locked(ops, skb, info, true);
}
+/*
+ * Non-admin version of psp_device_get_locked() where it returns psd in netns
+ * for not only psd->main_netdev but all netdevs in psd->assoc_dev_list.
+ */
int psp_device_get_locked(const struct genl_split_ops *ops,
struct sk_buff *skb, struct genl_info *info)
{
return __psp_device_get_locked(ops, skb, info, false);
}
+static struct net *psp_nl_resolve_assoc_dev_ns(struct psp_dev *psd,
+ struct genl_info *info)
+{
+ struct net *net;
+ int nsid;
+
+ if (GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_IFINDEX))
+ return ERR_PTR(-EINVAL);
+
+ if (info->attrs[PSP_A_DEV_NSID]) {
+ /* Only callers in the main netns may specify nsid */
+ if (dev_net(psd->main_netdev) != genl_info_net(info)) {
+ NL_SET_BAD_ATTR(info->extack,
+ info->attrs[PSP_A_DEV_NSID]);
+ return ERR_PTR(-EPERM);
+ }
+
+ nsid = nla_get_s32(info->attrs[PSP_A_DEV_NSID]);
+
+ net = get_net_ns_by_id(genl_info_net(info), nsid);
+ if (!net) {
+ NL_SET_BAD_ATTR(info->extack,
+ info->attrs[PSP_A_DEV_NSID]);
+ return ERR_PTR(-EINVAL);
+ }
+ } else {
+ net = get_net(genl_info_net(info));
+ }
+
+ return net;
+}
+
void
psp_device_unlock(const struct genl_split_ops *ops, struct sk_buff *skb,
struct genl_info *info)
@@ -103,11 +211,74 @@ psp_device_unlock(const struct genl_split_ops *ops, struct sk_buff *skb,
sockfd_put(socket);
}
+static bool psp_has_assoc_dev_in_ns(struct psp_dev *psd, struct net *net)
+{
+ struct psp_assoc_dev *entry;
+
+ list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
+ if (dev_net(entry->assoc_dev) == net)
+ return true;
+ }
+
+ return false;
+}
+
+static int psp_nl_fill_assoc_dev_list(struct psp_dev *psd, struct sk_buff *rsp,
+ struct net *cur_net,
+ struct net *filter_net)
+{
+ struct psp_assoc_dev *entry;
+ struct net *dev_net_ns;
+ struct nlattr *nest;
+ int nsid;
+
+ list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
+ dev_net_ns = dev_net(entry->assoc_dev);
+
+ if (filter_net && dev_net_ns != filter_net)
+ continue;
+
+ /* When filtering by namespace, all devices are in the caller's
+ * namespace so nsid is always NETNSA_NSID_NOT_ASSIGNED (-1).
+ * Otherwise, calculate the nsid relative to cur_net.
+ */
+ nsid = filter_net ? NETNSA_NSID_NOT_ASSIGNED :
+ peernet2id_alloc(cur_net, dev_net_ns,
+ GFP_KERNEL);
+
+ nest = nla_nest_start(rsp, PSP_A_DEV_ASSOC_LIST);
+ if (!nest)
+ return -1;
+
+ if (nla_put_u32(rsp, PSP_A_ASSOC_DEV_INFO_IFINDEX,
+ entry->assoc_dev->ifindex) ||
+ nla_put_s32(rsp, PSP_A_ASSOC_DEV_INFO_NSID, nsid)) {
+ nla_nest_cancel(rsp, nest);
+ return -1;
+ }
+
+ nla_nest_end(rsp, nest);
+ }
+
+ return 0;
+}
+
static int
psp_nl_dev_fill(struct psp_dev *psd, struct sk_buff *rsp,
const struct genl_info *info)
{
+ struct net *cur_net;
void *hdr;
+ int err;
+
+ cur_net = genl_info_net(info);
+
+ /* Skip this device if we're in an associated netns but have no
+ * associated devices in cur_net
+ */
+ if (cur_net != dev_net(psd->main_netdev) &&
+ !psp_has_assoc_dev_in_ns(psd, cur_net))
+ return 0;
hdr = genlmsg_iput(rsp, info);
if (!hdr)
@@ -119,6 +290,22 @@ psp_nl_dev_fill(struct psp_dev *psd, struct sk_buff *rsp,
nla_put_u32(rsp, PSP_A_DEV_PSP_VERSIONS_ENA, psd->config.versions))
goto err_cancel_msg;
+ if (cur_net == dev_net(psd->main_netdev)) {
+ /* Primary device - dump assoc list */
+ err = psp_nl_fill_assoc_dev_list(psd, rsp, cur_net, NULL);
+ if (err)
+ goto err_cancel_msg;
+ } else {
+ /* In netns: set by-association flag and dump filtered
+ * assoc list containing only devices in cur_net
+ */
+ if (nla_put_flag(rsp, PSP_A_DEV_BY_ASSOCIATION))
+ goto err_cancel_msg;
+ err = psp_nl_fill_assoc_dev_list(psd, rsp, cur_net, cur_net);
+ if (err)
+ goto err_cancel_msg;
+ }
+
genlmsg_end(rsp, hdr);
return 0;
@@ -127,27 +314,34 @@ psp_nl_dev_fill(struct psp_dev *psd, struct sk_buff *rsp,
return -EMSGSIZE;
}
-void psp_nl_notify_dev(struct psp_dev *psd, u32 cmd)
+static struct sk_buff *psp_nl_build_dev_ntf(struct psp_dev *psd,
+ struct net *net, void *ctx)
{
+ u32 cmd = *(u32 *)ctx;
struct genl_info info;
struct sk_buff *ntf;
- if (!genl_has_listeners(&psp_nl_family, dev_net(psd->main_netdev),
- PSP_NLGRP_MGMT))
- return;
+ if (!genl_has_listeners(&psp_nl_family, net, PSP_NLGRP_MGMT))
+ return NULL;
ntf = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!ntf)
- return;
+ return NULL;
genl_info_init_ntf(&info, &psp_nl_family, cmd);
+ genl_info_net_set(&info, net);
if (psp_nl_dev_fill(psd, ntf, &info)) {
nlmsg_free(ntf);
- return;
+ return NULL;
}
- genlmsg_multicast_netns(&psp_nl_family, dev_net(psd->main_netdev), ntf,
- 0, PSP_NLGRP_MGMT, GFP_KERNEL);
+ return ntf;
+}
+
+void psp_nl_notify_dev(struct psp_dev *psd, u32 cmd)
+{
+ psp_nl_multicast_per_ns(psd, PSP_NLGRP_MGMT,
+ psp_nl_build_dev_ntf, &cmd);
}
int psp_nl_dev_get_doit(struct sk_buff *req, struct genl_info *info)
@@ -281,8 +475,9 @@ int psp_nl_key_rotate_doit(struct sk_buff *skb, struct genl_info *info)
psd->stats.rotations++;
nlmsg_end(ntf, (struct nlmsghdr *)ntf->data);
- genlmsg_multicast_netns(&psp_nl_family, dev_net(psd->main_netdev), ntf,
- 0, PSP_NLGRP_USE, GFP_KERNEL);
+
+ psp_nl_multicast_all_ns(psd, ntf, PSP_NLGRP_USE);
+
return psp_nl_reply_send(rsp, info);
err_free_ntf:
@@ -292,6 +487,127 @@ int psp_nl_key_rotate_doit(struct sk_buff *skb, struct genl_info *info)
return err;
}
+int psp_nl_dev_assoc_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct psp_dev *psd = info->user_ptr[0];
+ struct psp_assoc_dev *psp_assoc_dev;
+ struct net_device *assoc_dev;
+ struct sk_buff *rsp;
+ u32 assoc_ifindex;
+ struct net *net;
+ int err;
+
+ if (psd->assoc_dev_cnt >= PSP_ASSOC_DEV_MAX) {
+ NL_SET_ERR_MSG(info->extack,
+ "Maximum number of associated devices reached");
+ return -ENOSPC;
+ }
+
+ net = psp_nl_resolve_assoc_dev_ns(psd, info);
+ if (IS_ERR(net))
+ return PTR_ERR(net);
+
+ psp_assoc_dev = kzalloc_obj(*psp_assoc_dev, GFP_KERNEL);
+ if (!psp_assoc_dev) {
+ err = -ENOMEM;
+ goto err_put_net;
+ }
+
+ assoc_ifindex = nla_get_u32(info->attrs[PSP_A_DEV_IFINDEX]);
+ assoc_dev = netdev_get_by_index(net, assoc_ifindex,
+ &psp_assoc_dev->dev_tracker,
+ GFP_KERNEL);
+ if (!assoc_dev) {
+ NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_DEV_IFINDEX]);
+ err = -ENODEV;
+ goto assoc_dev_err;
+ }
+
+ /* Check if device is already associated with a PSP device */
+ if (cmpxchg(&assoc_dev->psp_dev, NULL, RCU_INITIALIZER(psd))) {
+ NL_SET_ERR_MSG(info->extack,
+ "Device already associated with a PSP device");
+ err = -EBUSY;
+ goto cmpxchg_err;
+ }
+
+ psp_assoc_dev->assoc_dev = assoc_dev;
+ rsp = psp_nl_reply_new(info);
+ if (!rsp) {
+ err = -ENOMEM;
+ goto rsp_err;
+ }
+
+ list_add_tail(&psp_assoc_dev->dev_list, &psd->assoc_dev_list);
+ psd->assoc_dev_cnt++;
+
+ put_net(net);
+
+ psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF);
+
+ return psp_nl_reply_send(rsp, info);
+
+rsp_err:
+ rcu_assign_pointer(assoc_dev->psp_dev, NULL);
+cmpxchg_err:
+ netdev_put(assoc_dev, &psp_assoc_dev->dev_tracker);
+assoc_dev_err:
+ kfree(psp_assoc_dev);
+err_put_net:
+ put_net(net);
+
+ return err;
+}
+
+int psp_nl_dev_disassoc_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct psp_assoc_dev *entry, *found = NULL;
+ struct psp_dev *psd = info->user_ptr[0];
+ struct sk_buff *rsp;
+ u32 assoc_ifindex;
+ struct net *net;
+
+ net = psp_nl_resolve_assoc_dev_ns(psd, info);
+ if (IS_ERR(net))
+ return PTR_ERR(net);
+
+ assoc_ifindex = nla_get_u32(info->attrs[PSP_A_DEV_IFINDEX]);
+
+ /* Search the association list by ifindex and netns */
+ list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
+ if (entry->assoc_dev->ifindex == assoc_ifindex &&
+ dev_net(entry->assoc_dev) == net) {
+ found = entry;
+ break;
+ }
+ }
+
+ if (!found) {
+ put_net(net);
+ NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_DEV_IFINDEX]);
+ return -ENODEV;
+ }
+
+ rsp = psp_nl_reply_new(info);
+ if (!rsp) {
+ put_net(net);
+ return -ENOMEM;
+ }
+
+ /* Remove from the association list */
+ list_del(&found->dev_list);
+ psd->assoc_dev_cnt--;
+ rcu_assign_pointer(found->assoc_dev->psp_dev, NULL);
+ netdev_put(found->assoc_dev, &found->dev_tracker);
+ kfree(found);
+
+ put_net(net);
+
+ psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF);
+
+ return psp_nl_reply_send(rsp, info);
+}
+
/* Key etc. */
int psp_assoc_device_get_locked(const struct genl_split_ops *ops,
--
2.52.0
^ permalink raw reply related
* [PATCH v13 net-next 3/5] psp: add a new netdev event for dev unregister
From: Wei Wang @ 2026-05-04 23:00 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S . Miller, Eric Dumazet, Simon Horman,
Paolo Abeni
Cc: Wei Wang
In-Reply-To: <20260504230056.917415-1-weibunny.kernel@gmail.com>
From: Wei Wang <weibunny@fb.com>
Add a new netdev event for dev unregister and handle the removal of this
dev from psp->assoc_dev_list, upon the first dev-assoc operation.
Signed-off-by: Wei Wang <weibunny@fb.com>
Reviewed-by: Daniel Zahka <daniel.zahka@gmail.com>
---
Documentation/netlink/specs/psp.yaml | 2 +-
net/psp/psp-nl-gen.c | 2 +-
net/psp/psp-nl-gen.h | 3 ++
net/psp/psp.h | 1 +
net/psp/psp_main.c | 76 ++++++++++++++++++++++++++++
net/psp/psp_nl.c | 29 +++++++++++
6 files changed, 111 insertions(+), 2 deletions(-)
diff --git a/Documentation/netlink/specs/psp.yaml b/Documentation/netlink/specs/psp.yaml
index ce0a03e2d746..a3853caf7206 100644
--- a/Documentation/netlink/specs/psp.yaml
+++ b/Documentation/netlink/specs/psp.yaml
@@ -330,7 +330,7 @@ operations:
- nsid
reply:
attributes: []
- pre: psp-device-get-locked
+ pre: psp-device-get-locked-dev-assoc
post: psp-device-unlock
-
name: dev-disassoc
diff --git a/net/psp/psp-nl-gen.c b/net/psp/psp-nl-gen.c
index 18a6e8bdb6a1..9d0461b7f1f6 100644
--- a/net/psp/psp-nl-gen.c
+++ b/net/psp/psp-nl-gen.c
@@ -135,7 +135,7 @@ static const struct genl_split_ops psp_nl_ops[] = {
},
{
.cmd = PSP_CMD_DEV_ASSOC,
- .pre_doit = psp_device_get_locked,
+ .pre_doit = psp_device_get_locked_dev_assoc,
.doit = psp_nl_dev_assoc_doit,
.post_doit = psp_device_unlock,
.policy = psp_dev_assoc_nl_policy,
diff --git a/net/psp/psp-nl-gen.h b/net/psp/psp-nl-gen.h
index 4dd0f0f23053..24d51bff997f 100644
--- a/net/psp/psp-nl-gen.h
+++ b/net/psp/psp-nl-gen.h
@@ -21,6 +21,9 @@ int psp_device_get_locked_admin(const struct genl_split_ops *ops,
struct sk_buff *skb, struct genl_info *info);
int psp_assoc_device_get_locked(const struct genl_split_ops *ops,
struct sk_buff *skb, struct genl_info *info);
+int psp_device_get_locked_dev_assoc(const struct genl_split_ops *ops,
+ struct sk_buff *skb,
+ struct genl_info *info);
void
psp_device_unlock(const struct genl_split_ops *ops, struct sk_buff *skb,
struct genl_info *info);
diff --git a/net/psp/psp.h b/net/psp/psp.h
index 0f9c4e4e52cb..c82b21bae240 100644
--- a/net/psp/psp.h
+++ b/net/psp/psp.h
@@ -15,6 +15,7 @@ extern struct mutex psp_devs_lock;
void psp_dev_free(struct psp_dev *psd);
int psp_dev_check_access(struct psp_dev *psd, struct net *net, bool admin);
+int psp_attach_netdev_notifier(void);
void psp_nl_notify_dev(struct psp_dev *psd, u32 cmd);
diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c
index 792a47105a6c..546c3611d50a 100644
--- a/net/psp/psp_main.c
+++ b/net/psp/psp_main.c
@@ -391,6 +391,82 @@ int psp_dev_rcv(struct sk_buff *skb, u16 dev_id, u8 generation, bool strip_icv)
}
EXPORT_SYMBOL(psp_dev_rcv);
+static void psp_dev_disassoc_one(struct psp_dev *psd, struct net_device *dev)
+{
+ struct psp_assoc_dev *entry, *tmp;
+
+ list_for_each_entry_safe(entry, tmp, &psd->assoc_dev_list, dev_list) {
+ if (entry->assoc_dev == dev) {
+ list_del(&entry->dev_list);
+ psd->assoc_dev_cnt--;
+ rcu_assign_pointer(entry->assoc_dev->psp_dev, NULL);
+ netdev_put(entry->assoc_dev, &entry->dev_tracker);
+ kfree(entry);
+ return;
+ }
+ }
+}
+
+static int psp_netdev_event(struct notifier_block *nb, unsigned long event,
+ void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct psp_dev *psd;
+
+ if (event != NETDEV_UNREGISTER)
+ return NOTIFY_DONE;
+
+ rcu_read_lock();
+ psd = rcu_dereference(dev->psp_dev);
+ if (psd && psp_dev_tryget(psd)) {
+ rcu_read_unlock();
+ mutex_lock(&psd->lock);
+ psp_dev_disassoc_one(psd, dev);
+ mutex_unlock(&psd->lock);
+ psp_dev_put(psd);
+ } else {
+ rcu_read_unlock();
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block psp_netdev_notifier = {
+ .notifier_call = psp_netdev_event,
+};
+
+static DEFINE_MUTEX(psp_notifier_lock);
+static bool psp_notifier_registered;
+
+/*
+ * psp_attach_netdev_notifier() - register netdev notifier on first use
+ *
+ * Register the netdevice notifier when the first device association
+ * is created. In many installations no associations will be created and
+ * the notifier won't be needed.
+ *
+ * Must be called without psd->lock held, due to lock ordering:
+ * rtnl_lock -> psd->lock (the notifier callback runs under rtnl_lock
+ * and takes psd->lock).
+ */
+int psp_attach_netdev_notifier(void)
+{
+ int err = 0;
+
+ if (READ_ONCE(psp_notifier_registered))
+ return 0;
+
+ mutex_lock(&psp_notifier_lock);
+ if (!psp_notifier_registered) {
+ err = register_netdevice_notifier(&psp_netdev_notifier);
+ if (!err)
+ WRITE_ONCE(psp_notifier_registered, true);
+ }
+ mutex_unlock(&psp_notifier_lock);
+
+ return err;
+}
+
static int __init psp_init(void)
{
mutex_init(&psp_devs_lock);
diff --git a/net/psp/psp_nl.c b/net/psp/psp_nl.c
index 8dbe2fe882dd..2fe7bb13eb24 100644
--- a/net/psp/psp_nl.c
+++ b/net/psp/psp_nl.c
@@ -167,6 +167,22 @@ int psp_device_get_locked(const struct genl_split_ops *ops,
return __psp_device_get_locked(ops, skb, info, false);
}
+/*
+ * Non-admin version of psp_device_get_locked() + psp_attach_netdev_notifier()
+ * only used for dev-assoc.
+ */
+int psp_device_get_locked_dev_assoc(const struct genl_split_ops *ops,
+ struct sk_buff *skb, struct genl_info *info)
+{
+ int err;
+
+ err = psp_attach_netdev_notifier();
+ if (err)
+ return err;
+
+ return __psp_device_get_locked(ops, skb, info, false);
+}
+
static struct net *psp_nl_resolve_assoc_dev_ns(struct psp_dev *psd,
struct genl_info *info)
{
@@ -532,6 +548,19 @@ int psp_nl_dev_assoc_doit(struct sk_buff *skb, struct genl_info *info)
}
psp_assoc_dev->assoc_dev = assoc_dev;
+
+ /* Check for race with NETDEV_UNREGISTER. The cmpxchg above is a
+ * full barrier, and the unregister path has synchronize_net()
+ * between setting NETREG_UNREGISTERING and reading psp_dev in the
+ * notifier. So at least one side would do the clean-up if we are in
+ * the middle of unregitering assoc_dev.
+ * And the clean-up is serialized by psd->lock.
+ */
+ if (READ_ONCE(assoc_dev->reg_state) != NETREG_REGISTERED) {
+ err = -ENODEV;
+ goto rsp_err;
+ }
+
rsp = psp_nl_reply_new(info);
if (!rsp) {
err = -ENOMEM;
--
2.52.0
^ permalink raw reply related
* [PATCH v13 net-next 4/5] selftests/net: Add bpf skb forwarding program
From: Wei Wang @ 2026-05-04 23:00 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S . Miller, Eric Dumazet, Simon Horman,
Paolo Abeni
Cc: Wei Wang, Bobby Eshleman
In-Reply-To: <20260504230056.917415-1-weibunny.kernel@gmail.com>
From: Wei Wang <weibunny@fb.com>
Add nk_redirect.bpf.c, a BPF program that forwards skbs matching some IPv6
prefix received on eth0 ifindex to a specified dev ifindex.
bpf_redirect_neigh() is used to make sure neighbor lookup is performed
and proper MAC addr is being used.
Signed-off-by: Wei Wang <weibunny@fb.com>
Reviewed-by: Bobby Eshleman <bobbyeshleman@meta.com>
Tested-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
.../drivers/net/hw/nk_redirect.bpf.c | 60 +++++++++++++++++++
1 file changed, 60 insertions(+)
create mode 100644 tools/testing/selftests/drivers/net/hw/nk_redirect.bpf.c
diff --git a/tools/testing/selftests/drivers/net/hw/nk_redirect.bpf.c b/tools/testing/selftests/drivers/net/hw/nk_redirect.bpf.c
new file mode 100644
index 000000000000..7ac9ffd50f15
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/nk_redirect.bpf.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BPF program for redirecting traffic using bpf_redirect_neigh().
+ * Unlike bpf_redirect() which preserves L2 headers, bpf_redirect_neigh()
+ * performs neighbor lookup and fills in the correct L2 addresses for the
+ * target interface. This is necessary when redirecting across different
+ * device types (e.g., from netdevsim to netkit).
+ */
+#include <linux/bpf.h>
+#include <linux/pkt_cls.h>
+#include <linux/if_ether.h>
+#include <linux/ipv6.h>
+#include <linux/in6.h>
+#include <bpf/bpf_endian.h>
+#include <bpf/bpf_helpers.h>
+
+#define TC_ACT_OK 0
+#define ETH_P_IPV6 0x86DD
+
+#define ctx_ptr(field) ((void *)(long)(field))
+
+#define v6_p64_equal(a, b) (a.s6_addr32[0] == b.s6_addr32[0] && \
+ a.s6_addr32[1] == b.s6_addr32[1])
+
+volatile __u32 redirect_ifindex;
+volatile __u8 ipv6_prefix[16];
+
+SEC("tc/ingress")
+int tc_redirect(struct __sk_buff *skb)
+{
+ void *data_end = ctx_ptr(skb->data_end);
+ void *data = ctx_ptr(skb->data);
+ struct in6_addr *match_prefix;
+ struct ipv6hdr *ip6h;
+ struct ethhdr *eth;
+
+ match_prefix = (struct in6_addr *)ipv6_prefix;
+
+ if (skb->protocol != bpf_htons(ETH_P_IPV6))
+ return TC_ACT_OK;
+
+ eth = data;
+ if ((void *)(eth + 1) > data_end)
+ return TC_ACT_OK;
+
+ ip6h = data + sizeof(struct ethhdr);
+ if ((void *)(ip6h + 1) > data_end)
+ return TC_ACT_OK;
+
+ if (!v6_p64_equal(ip6h->daddr, (*match_prefix)))
+ return TC_ACT_OK;
+
+ /*
+ * Use bpf_redirect_neigh() to perform neighbor lookup and fill in
+ * correct L2 addresses for the target interface.
+ */
+ return bpf_redirect_neigh(redirect_ifindex, NULL, 0, 0);
+}
+
+char __license[] SEC("license") = "GPL";
--
2.52.0
^ permalink raw reply related
* [PATCH v13 net-next 5/5] selftests/net: psp: Add test for dev-assoc/disassoc
From: Wei Wang @ 2026-05-04 23:00 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S . Miller, Eric Dumazet, Simon Horman,
Paolo Abeni
Cc: Wei Wang
In-Reply-To: <20260504230056.917415-1-weibunny.kernel@gmail.com>
From: Wei Wang <weibunny@fb.com>
Add a new param to NetDrvContEnv to add an additional bpf redirect
program on nk_host to redirect traffic to the psp_dev_local.
The topology looks like this:
Host NS: psp_dev_local <---> nk_host
| |
| | (netkit pair)
| |
Remote NS: psp_dev_peer Guest NS: nk_guest
(responder) (PSP tests)
Add following tests for dev-assoc/dev-disassoc functionality:
1. Test the output of `./tools/net/ynl/pyynl/cli.py --spec
Documentation/netlink/specs/psp.yaml --dump dev-get` in both default and
the guest netns.
2. Test the case where we associate netkit with psp_dev_local, and
send PSP traffic from nk_guest to psp_dev_peer in 2 different netns.
3. Test to make sure the key rotation notification is sent to the netns
for associated dev as well
4. Test to make sure the dev change notification is sent to the netns
for associated dev as well
5. Test for dev-assoc/dev-disassoc without nsid parameter.
6. Test the deletion of nk_guest in client netns, and proper cleanup in
the assoc-list for psp dev.
Signed-off-by: Wei Wang <weibunny@fb.com>
---
tools/testing/selftests/drivers/net/config | 1 +
.../selftests/drivers/net/lib/py/env.py | 54 ++-
tools/testing/selftests/drivers/net/psp.py | 457 ++++++++++++++++--
3 files changed, 478 insertions(+), 34 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config
index fd16994366f4..b8a559b360e4 100644
--- a/tools/testing/selftests/drivers/net/config
+++ b/tools/testing/selftests/drivers/net/config
@@ -8,5 +8,6 @@ CONFIG_NETCONSOLE=m
CONFIG_NETCONSOLE_DYNAMIC=y
CONFIG_NETCONSOLE_EXTENDED_LOG=y
CONFIG_NETDEVSIM=m
+CONFIG_NETKIT=y
CONFIG_VLAN_8021Q=m
CONFIG_XDP_SOCKETS=y
diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py
index 24ce122abd9c..cd3a08cbe968 100644
--- a/tools/testing/selftests/drivers/net/lib/py/env.py
+++ b/tools/testing/selftests/drivers/net/lib/py/env.py
@@ -2,6 +2,7 @@
import ipaddress
import os
+import re
import time
import json
from pathlib import Path
@@ -336,7 +337,7 @@ class NetDrvContEnv(NetDrvEpEnv):
+---------------+
"""
- def __init__(self, src_path, rxqueues=1, **kwargs):
+ def __init__(self, src_path, rxqueues=1, install_tx_redirect_bpf=False, **kwargs):
self.netns = None
self._nk_host_ifname = None
self._nk_guest_ifname = None
@@ -347,6 +348,8 @@ class NetDrvContEnv(NetDrvEpEnv):
self._init_ns_attached = False
self._old_fwd = None
self._old_accept_ra = None
+ self._nk_host_tc_attached = False
+ self._nk_host_bpf_prog_pref = None
super().__init__(src_path, **kwargs)
@@ -397,7 +400,13 @@ class NetDrvContEnv(NetDrvEpEnv):
self._setup_ns()
self._attach_bpf()
+ if install_tx_redirect_bpf:
+ self._attach_tx_redirect_bpf()
+
def __del__(self):
+ if self._nk_host_tc_attached:
+ cmd(f"tc filter del dev {self._nk_host_ifname} ingress pref {self._nk_host_bpf_prog_pref}", fail=False)
+ self._nk_host_tc_attached = False
if self._tc_attached:
cmd(f"tc filter del dev {self.ifname} ingress pref {self._bpf_prog_pref}")
self._tc_attached = False
@@ -505,3 +514,46 @@ class NetDrvContEnv(NetDrvEpEnv):
value = ipv6_bytes + ifindex_bytes
value_hex = ' '.join(f'{b:02x}' for b in value)
bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")
+
+ def _attach_tx_redirect_bpf(self):
+ """
+ Attach BPF program on nk_host ingress to redirect TX traffic.
+
+ Packets from nk_guest destined for the nsim network arrive at nk_host
+ via the netkit pair. This BPF program redirects them to the physical
+ interface so they can reach the remote peer.
+ """
+ bpf_obj = self.test_dir / "nk_redirect.bpf.o"
+ if not bpf_obj.exists():
+ raise KsftSkipEx("BPF prog nk_redirect.bpf.o not found")
+
+ cmd(f"tc qdisc add dev {self._nk_host_ifname} clsact")
+
+ cmd(f"tc filter add dev {self._nk_host_ifname} ingress bpf obj {bpf_obj} sec tc/ingress direct-action")
+ self._nk_host_tc_attached = True
+
+ tc_info = cmd(f"tc filter show dev {self._nk_host_ifname} ingress").stdout
+ match = re.search(r'pref (\d+).*nk_redirect\.bpf.*id (\d+)', tc_info)
+ if not match:
+ raise Exception("Failed to get TX redirect BPF prog ID")
+ self._nk_host_bpf_prog_pref = int(match.group(1))
+ nk_host_bpf_prog_id = int(match.group(2))
+
+ prog_info = bpftool(f"prog show id {nk_host_bpf_prog_id}", json=True)
+ map_ids = prog_info.get("map_ids", [])
+
+ bss_map_id = None
+ for map_id in map_ids:
+ map_info = bpftool(f"map show id {map_id}", json=True)
+ if map_info.get("name").endswith("bss"):
+ bss_map_id = map_id
+
+ if bss_map_id is None:
+ raise Exception("Failed to find TX redirect BPF .bss map")
+
+ ipv6_addr = ipaddress.IPv6Address(self.nsim_v6_pfx)
+ ipv6_bytes = ipv6_addr.packed
+ ifindex_bytes = self.ifindex.to_bytes(4, byteorder='little')
+ value = ipv6_bytes + ifindex_bytes
+ value_hex = ' '.join(f'{b:02x}' for b in value)
+ bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}")
diff --git a/tools/testing/selftests/drivers/net/psp.py b/tools/testing/selftests/drivers/net/psp.py
index 864d9fce1094..79da4d425c50 100755
--- a/tools/testing/selftests/drivers/net/psp.py
+++ b/tools/testing/selftests/drivers/net/psp.py
@@ -5,6 +5,7 @@
import errno
import fcntl
+import os
import socket
import struct
import termios
@@ -14,9 +15,12 @@ from lib.py import defer
from lib.py import ksft_run, ksft_exit, ksft_pr
from lib.py import ksft_true, ksft_eq, ksft_ne, ksft_gt, ksft_raises
from lib.py import ksft_not_none
-from lib.py import KsftSkipEx
-from lib.py import NetDrvEpEnv, PSPFamily, NlError
+from lib.py import ksft_variants, KsftNamedVariant
+from lib.py import KsftSkipEx, KsftFailEx
+from lib.py import NetDrvEpEnv, NetDrvContEnv, PSPFamily, NlError
+from lib.py import NetNSEnter
from lib.py import bkg, rand_port, wait_port_listen
+from lib.py import ip
def _get_outq(s):
@@ -117,11 +121,13 @@ def _get_stat(cfg, key):
# Test case boiler plate
#
-def _init_psp_dev(cfg):
+def _init_psp_dev(cfg, use_psp_ifindex=False):
if not hasattr(cfg, 'psp_dev_id'):
# Figure out which local device we are testing against
+ # For NetDrvContEnv: use psp_ifindex instead of ifindex
+ target_ifindex = cfg.psp_ifindex if use_psp_ifindex else cfg.ifindex
for dev in cfg.pspnl.dev_get({}, dump=True):
- if dev['ifindex'] == cfg.ifindex:
+ if dev['ifindex'] == target_ifindex:
cfg.psp_info = dev
cfg.psp_dev_id = cfg.psp_info['id']
break
@@ -394,6 +400,297 @@ def _data_basic_send(cfg, version, ipver):
_close_psp_conn(cfg, s)
+def _data_basic_send_netkit_psp_assoc(cfg, version, ipver):
+ """
+ Test basic data send with netkit interface associated with PSP dev.
+ """
+
+ _init_psp_dev(cfg, True)
+ psp_dev_id_for_assoc = cfg.psp_dev_id
+
+ # Associate PSP device with nk_guest interface (in guest namespace)
+ nk_guest_dev = ip(f"link show dev {cfg._nk_guest_ifname}", json=True, ns=cfg.netns)[0]
+ nk_guest_ifindex = nk_guest_dev['ifindex']
+
+ cfg.pspnl.dev_assoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+
+ # Check if assoc-list contains nk_guest
+ dev_info = cfg.pspnl.dev_get({'id': psp_dev_id_for_assoc})
+
+ if 'assoc-list' in dev_info:
+ found = False
+ for assoc in dev_info['assoc-list']:
+ if assoc['ifindex'] == nk_guest_ifindex and assoc['nsid'] == cfg.psp_dev_peer_nsid:
+ found = True
+ break
+ ksft_true(found, "Associated device not found in dev_get() response")
+ else:
+ raise RuntimeError("No assoc-list in dev_get() response after association")
+
+ # Enter guest namespace (netns) to run PSP test
+ with NetNSEnter(cfg.netns.name):
+ cfg.pspnl = PSPFamily()
+
+ s = _make_psp_conn(cfg, version, ipver)
+
+ rx_assoc = cfg.pspnl.rx_assoc({"version": version,
+ "dev-id": cfg.psp_dev_id,
+ "sock-fd": s.fileno()})
+ rx = rx_assoc['rx-key']
+ tx = _spi_xchg(s, rx)
+
+ cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
+ "version": version,
+ "tx-key": tx,
+ "sock-fd": s.fileno()})
+
+ data_len = _send_careful(cfg, s, 100)
+ _check_data_rx(cfg, data_len)
+ _close_psp_conn(cfg, s)
+
+ # Clean up - back in host namespace
+ cfg.pspnl = PSPFamily()
+ cfg.pspnl.dev_disassoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+
+ del cfg.psp_dev_id
+ del cfg.psp_info
+
+
+def _key_rotation_notify_multi_ns_netkit(cfg):
+ """ Test key rotation notifications across multiple namespaces using netkit """
+ _init_psp_dev(cfg, True)
+ psp_dev_id_for_assoc = cfg.psp_dev_id
+
+ # Associate PSP device with nk_guest interface (in guest namespace)
+ nk_guest_dev = ip(f"link show dev {cfg._nk_guest_ifname}", json=True, ns=cfg.netns)[0]
+ nk_guest_ifindex = nk_guest_dev['ifindex']
+
+ cfg.pspnl.dev_assoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+
+ # Create listener in guest namespace; socket stays bound to that ns
+ with NetNSEnter(cfg.netns.name):
+ peer_pspnl = PSPFamily()
+ peer_pspnl.ntf_subscribe('use')
+
+ # Create listener in main namespace
+ main_pspnl = PSPFamily()
+ main_pspnl.ntf_subscribe('use')
+
+ # Trigger key rotation on the PSP device
+ cfg.pspnl.key_rotate({"id": psp_dev_id_for_assoc})
+
+ # Poll both sockets from main thread
+ for pspnl, label in [(main_pspnl, "main"), (peer_pspnl, "guest")]:
+ for i in range(100):
+ pspnl.check_ntf()
+
+ try:
+ msg = pspnl.async_msg_queue.get_nowait()
+ break
+ except Exception:
+ pass
+
+ time.sleep(0.1)
+ else:
+ raise KsftFailEx(f"No key rotation notification received in {label} namespace")
+
+ ksft_true(msg['msg'].get('id') == psp_dev_id_for_assoc,
+ f"Key rotation notification for correct device not found in {label} namespace")
+
+ # Clean up
+ cfg.pspnl.dev_disassoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+ del cfg.psp_dev_id
+ del cfg.psp_info
+
+
+def _dev_change_notify_multi_ns_netkit(cfg):
+ """ Test dev_change notifications across multiple namespaces using netkit """
+ _init_psp_dev(cfg, True)
+ psp_dev_id_for_assoc = cfg.psp_dev_id
+
+ # Associate PSP device with nk_guest interface (in guest namespace)
+ nk_guest_dev = ip(f"link show dev {cfg._nk_guest_ifname}", json=True, ns=cfg.netns)[0]
+ nk_guest_ifindex = nk_guest_dev['ifindex']
+
+ cfg.pspnl.dev_assoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+
+ # Create listener in guest namespace; socket stays bound to that ns
+ with NetNSEnter(cfg.netns.name):
+ peer_pspnl = PSPFamily()
+ peer_pspnl.ntf_subscribe('mgmt')
+
+ # Create listener in main namespace
+ main_pspnl = PSPFamily()
+ main_pspnl.ntf_subscribe('mgmt')
+
+ # Trigger dev_change by calling dev_set (notification is always sent)
+ cfg.pspnl.dev_set({'id': psp_dev_id_for_assoc, 'psp-versions-ena': cfg.psp_info['psp-versions-cap']})
+
+ # Poll both sockets from main thread
+ for pspnl, label in [(main_pspnl, "main"), (peer_pspnl, "guest")]:
+ for i in range(100):
+ pspnl.check_ntf()
+
+ try:
+ msg = pspnl.async_msg_queue.get_nowait()
+ break
+ except Exception:
+ pass
+
+ time.sleep(0.1)
+ else:
+ raise KsftFailEx(f"No dev_change notification received in {label} namespace")
+
+ ksft_true(msg['msg'].get('id') == psp_dev_id_for_assoc,
+ f"Dev_change notification for correct device not found in {label} namespace")
+
+ # Clean up
+ cfg.pspnl.dev_disassoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+ del cfg.psp_dev_id
+ del cfg.psp_info
+
+
+def _psp_dev_get_check_netkit_psp_assoc(cfg):
+ """ Check psp dev-get output with netkit interface associated with PSP dev """
+
+ _init_psp_dev(cfg, True)
+ psp_dev_id_for_assoc = cfg.psp_dev_id
+
+ # Associate PSP device with nk_guest interface (in guest namespace)
+ nk_guest_dev = ip(f"link show dev {cfg._nk_guest_ifname}", json=True, ns=cfg.netns)[0]
+ nk_guest_ifindex = nk_guest_dev['ifindex']
+
+ cfg.pspnl.dev_assoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+
+ # Check 1: In default netns, verify dev-get has correct ifindex and assoc-list
+ dev_info = cfg.pspnl.dev_get({'id': psp_dev_id_for_assoc})
+
+ # Verify the PSP device has the correct ifindex
+ ksft_eq(dev_info['ifindex'], cfg.psp_ifindex)
+
+ # Verify assoc-list exists and contains the associated nk_guest with correct ifindex and nsid
+ ksft_true('assoc-list' in dev_info, "No assoc-list in dev_get() response after association")
+ found = False
+ for assoc in dev_info['assoc-list']:
+ if assoc['ifindex'] == nk_guest_ifindex and assoc['nsid'] == cfg.psp_dev_peer_nsid:
+ found = True
+ break
+ ksft_true(found, "Associated device not found in assoc-list with correct ifindex and nsid")
+
+ # Check 2: In guest netns, verify dev-get has assoc-list with nk_guest device
+ with NetNSEnter(cfg.netns.name):
+ peer_pspnl = PSPFamily()
+
+ # Dump all devices in the guest namespace
+ peer_devices = peer_pspnl.dev_get({}, dump=True)
+
+ # Find the device with by-association flag
+ peer_dev = None
+ for dev in peer_devices:
+ if dev.get('by-association'):
+ peer_dev = dev
+ break
+
+ ksft_not_none(peer_dev, "No PSP device found with by-association flag in guest netns")
+
+ # Verify assoc-list contains the nk_guest device
+ ksft_true('assoc-list' in peer_dev and len(peer_dev['assoc-list']) > 0,
+ "Guest device should have assoc-list with local devices")
+
+ # Verify the assoc-list contains nk_guest ifindex with nsid=-1 (same namespace)
+ found = False
+ for assoc in peer_dev['assoc-list']:
+ if assoc['ifindex'] == nk_guest_ifindex:
+ ksft_eq(assoc['nsid'], -1,
+ "nsid should be -1 (NETNSA_NSID_NOT_ASSIGNED) for same-namespace device")
+ found = True
+ break
+ ksft_true(found, "nk_guest ifindex not found in assoc-list")
+
+ # Clean up
+ cfg.pspnl.dev_disassoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+
+ del cfg.psp_dev_id
+ del cfg.psp_info
+
+
+def _dev_assoc_no_nsid(cfg):
+ """ Test dev-assoc and dev-disassoc without nsid attribute """
+ _init_psp_dev(cfg, True)
+ psp_dev_id = cfg.psp_dev_id
+
+ # Get nk_host's ifindex (in host namespace, same as caller)
+ nk_host_dev = ip(f"link show dev {cfg._nk_host_ifname}", json=True)[0]
+ nk_host_ifindex = nk_host_dev['ifindex']
+
+ # Associate without nsid - should look up ifindex in caller's netns
+ cfg.pspnl.dev_assoc({'id': psp_dev_id, 'ifindex': nk_host_ifindex})
+
+ # Verify assoc-list contains the device
+ dev_info = cfg.pspnl.dev_get({'id': psp_dev_id})
+ ksft_true('assoc-list' in dev_info, "No assoc-list after association")
+ found = False
+ for assoc in dev_info['assoc-list']:
+ if assoc['ifindex'] == nk_host_ifindex:
+ found = True
+ break
+ ksft_true(found, "Associated device not found in assoc-list")
+
+ # Disassociate without nsid - should also use caller's netns
+ cfg.pspnl.dev_disassoc({'id': psp_dev_id, 'ifindex': nk_host_ifindex})
+
+ # Verify assoc-list no longer contains the device
+ dev_info = cfg.pspnl.dev_get({'id': psp_dev_id})
+ found = False
+ if 'assoc-list' in dev_info:
+ for assoc in dev_info['assoc-list']:
+ if assoc['ifindex'] == nk_host_ifindex:
+ found = True
+ break
+ ksft_true(not found, "Device should not be in assoc-list after disassociation")
+
+ del cfg.psp_dev_id
+ del cfg.psp_info
+
+
+def _psp_dev_assoc_cleanup_on_netkit_del(cfg):
+ """ Test that assoc-list is cleared when associated netkit interface is deleted """
+ _init_psp_dev(cfg, True)
+ psp_dev_id_for_assoc = cfg.psp_dev_id
+
+ # Associate PSP device with nk_guest interface (in guest namespace)
+ nk_guest_dev = ip(f"link show dev {cfg._nk_guest_ifname}", json=True, ns=cfg.netns)[0]
+ nk_guest_ifindex = nk_guest_dev['ifindex']
+
+ cfg.pspnl.dev_assoc({'id': psp_dev_id_for_assoc, 'ifindex': nk_guest_ifindex, 'nsid': cfg.psp_dev_peer_nsid})
+
+ # Verify assoc-list exists in default netns
+ dev_info = cfg.pspnl.dev_get({'id': psp_dev_id_for_assoc})
+ ksft_true('assoc-list' in dev_info, "No assoc-list after association")
+ found = False
+ for assoc in dev_info['assoc-list']:
+ if assoc['ifindex'] == nk_guest_ifindex and assoc['nsid'] == cfg.psp_dev_peer_nsid:
+ found = True
+ break
+ ksft_true(found, "Associated device not found in assoc-list")
+
+ # Delete the netkit interface in the guest namespace
+ ip(f"link del {cfg._nk_guest_ifname}", ns=cfg.netns)
+
+ # Mark netkit as already deleted so cleanup won't try to delete it again
+ # (deleting nk_guest also removes nk_host since they're a pair)
+ cfg._nk_host_ifname = None
+ cfg._nk_guest_ifname = None
+
+ # Verify assoc-list is gone in default netns after netkit deletion
+ dev_info = cfg.pspnl.dev_get({'id': psp_dev_id_for_assoc})
+ ksft_true('assoc-list' not in dev_info or len(dev_info['assoc-list']) == 0,
+ "assoc-list should be empty after netkit deletion")
+
+ del cfg.psp_dev_id
+ del cfg.psp_info
+
+
def __bad_xfer_do(cfg, s, tx, version='hdr0-aes-gcm-128'):
# Make sure we accept the ACK for the SPI before we seal with the bad assoc
_check_data_outq(s, 0)
@@ -571,33 +868,127 @@ def removal_device_bi(cfg):
_close_conn(cfg, s)
-def psp_ip_ver_test_builder(name, test_func, psp_ver, ipver):
- """Build test cases for each combo of PSP version and IP version"""
- def test_case(cfg):
- cfg.require_ipver(ipver)
- test_func(cfg, psp_ver, ipver)
-
- test_case.__name__ = f"{name}_v{psp_ver}_ip{ipver}"
- return test_case
+@ksft_variants([
+ KsftNamedVariant(f"v{v}_ip{ip}", v, ip)
+ for v in range(4) for ip in ("4", "6")
+])
+def data_basic_send(cfg, version, ipver):
+ cfg.require_ipver(ipver)
+ _data_basic_send(cfg, version, ipver)
+
+
+@ksft_variants([
+ KsftNamedVariant(f"ip{ip}", ip)
+ for ip in ("4", "6")
+])
+def data_mss_adjust(cfg, ipver):
+ cfg.require_ipver(ipver)
+ _data_mss_adjust(cfg, ipver)
+
+
+@ksft_variants([
+ KsftNamedVariant(f"v{v}_ip6", v, "6")
+ for v in range(4)
+])
+def data_basic_send_netkit_psp_assoc(cfg, version, ipver):
+ cfg.require_ipver(ipver)
+ _data_basic_send_netkit_psp_assoc(cfg, version, ipver)
+
+
+
+def _get_nsid(ns_name):
+ """Get the nsid for a namespace."""
+ for entry in ip("netns list-id", json=True):
+ if entry.get("name") == str(ns_name):
+ return entry["nsid"]
+ raise KsftSkipEx(f"nsid not found for namespace {ns_name}")
+
+
+def _setup_psp_attributes(cfg):
+ """
+ Set up PSP-specific attributes on the environment.
+
+ This sets attributes needed for PSP tests based on whether we're using
+ netdevsim or a real NIC.
+ """
+ if cfg._ns is not None:
+ # netdevsim case: PSP device is the local dev (in host namespace)
+ cfg.psp_dev = cfg._ns.nsims[0].dev
+ cfg.psp_ifname = cfg.psp_dev['ifname']
+ cfg.psp_ifindex = cfg.psp_dev['ifindex']
+
+ # PSP peer device is the remote dev (in _netns, where psp_responder runs)
+ cfg.psp_dev_peer = cfg._ns_peer.nsims[0].dev
+ cfg.psp_dev_peer_ifname = cfg.psp_dev_peer['ifname']
+ cfg.psp_dev_peer_ifindex = cfg.psp_dev_peer['ifindex']
+ else:
+ # Real NIC case: PSP device is the local interface
+ cfg.psp_dev = cfg.dev
+ cfg.psp_ifname = cfg.ifname
+ cfg.psp_ifindex = cfg.ifindex
+
+ # PSP peer device is the remote interface
+ cfg.psp_dev_peer = cfg.remote_dev
+ cfg.psp_dev_peer_ifname = cfg.remote_ifname
+ cfg.psp_dev_peer_ifindex = cfg.remote_ifindex
+
+ # Get nsid for the guest namespace (netns) where nk_guest is
+ cfg.psp_dev_peer_nsid = _get_nsid(cfg.netns.name)
+
+
+def _setup_psp_routes(cfg):
+ """
+ Set up routes for cross-namespace connectivity.
+
+ Traffic flows:
+ 1. remote (_netns) -> nk_guest (netns):
+ psp_dev_peer -> psp_dev_local -> BPF redirect -> nk_host -> nk_guest
+ Needs: route in _netns to nk_v6_pfx/64 via psp_dev_local
+
+ 2. nk_guest (netns) -> remote (_netns):
+ nk_guest -> nk_host -> psp_dev_local -> psp_dev_peer
+ Needs: route in netns to dev_v6_pfx/64 via nk_host
+ """
+ # In _netns (remote namespace): add route to nk_guest prefix via psp_dev_local
+ # psp_dev_peer can reach psp_dev_local via the link, then traffic goes through BPF
+ ip(f"-6 route add {cfg.nk_v6_pfx}/64 via {cfg.nsim_v6_pfx}1 dev {cfg.psp_dev_peer_ifname}",
+ ns=cfg._netns)
+
+ # In netns (guest namespace): add route to remote peer prefix
+ # nk_guest default route goes to nk_host, but we need explicit route to dev_v6_pfx/64
+ ip(f"-6 route add {cfg.nsim_v6_pfx}/64 via fe80::1 dev {cfg._nk_guest_ifname}",
+ ns=cfg.netns)
-def ipver_test_builder(name, test_func, ipver):
- """Build test cases for each IP version"""
- def test_case(cfg):
- cfg.require_ipver(ipver)
- test_func(cfg, ipver)
+def main() -> None:
+ """ Ksft boiler plate main """
- test_case.__name__ = f"{name}_ip{ipver}"
- return test_case
+ # Use a different prefix for netkit guest to avoid conflict with dev prefix
+ nk_v6_pfx = "2001:db9::"
+ # Set LOCAL_PREFIX_V6 to a DIFFERENT prefix than the dev prefix to avoid BPF
+ # redirecting psp_responder traffic. The BPF only redirects traffic
+ # matching LOCAL_PREFIX_V6, so dev traffic (2001:db8::) won't be affected.
+ if "LOCAL_PREFIX_V6" not in os.environ:
+ os.environ["LOCAL_PREFIX_V6"] = nk_v6_pfx
-def main() -> None:
- """ Ksft boiler plate main """
+ try:
+ env = NetDrvContEnv(__file__, install_tx_redirect_bpf=True)
+ has_cont = True
+ except KsftSkipEx:
+ env = NetDrvEpEnv(__file__)
+ has_cont = False
- with NetDrvEpEnv(__file__) as cfg:
+ with env as cfg:
cfg.pspnl = PSPFamily()
+ if has_cont:
+ cfg.nk_v6_pfx = nk_v6_pfx
+ _setup_psp_attributes(cfg)
+ _setup_psp_routes(cfg)
+
# Set up responder and communication sock
+ # psp_responder runs in _netns (remote namespace with psp_dev_peer)
responder = cfg.remote.deploy("psp_responder")
cfg.comm_port = rand_port()
@@ -611,17 +1002,17 @@ def main() -> None:
cfg.comm_port),
timeout=1)
- cases = [
- psp_ip_ver_test_builder(
- "data_basic_send", _data_basic_send, version, ipver
- )
- for version in range(0, 4)
- for ipver in ("4", "6")
- ]
- cases += [
- ipver_test_builder("data_mss_adjust", _data_mss_adjust, ipver)
- for ipver in ("4", "6")
- ]
+ cases = [data_basic_send, data_mss_adjust]
+
+ if has_cont:
+ cases += [
+ data_basic_send_netkit_psp_assoc,
+ _key_rotation_notify_multi_ns_netkit,
+ _dev_change_notify_multi_ns_netkit,
+ _psp_dev_get_check_netkit_psp_assoc,
+ _dev_assoc_no_nsid,
+ _psp_dev_assoc_cleanup_on_netkit_del,
+ ]
ksft_run(cases=cases, globs=globals(),
case_pfx={"dev_", "data_", "assoc_", "removal_"},
--
2.52.0
^ permalink raw reply related
* [PATCH net 0/3] pull request: fixes for ovpn 2026-05-04
From: Antonio Quartulli @ 2026-05-04 23:03 UTC (permalink / raw)
To: netdev
Cc: edumazet, sd, davem, kuba, pabeni, ralf, Antonio Quartulli,
Andrew Lunn
Hello netdev team,
Here is a respin of the previous PR, after gathering an extra review
from sashiko. This PR also includes an extra fix for the selftests.
Patch 1 is ensuring that the MAC header offset is reset before
deliering the packet to the upper layer. Not doing so could trick other
parts of the networking stack into wrong calculations.
Patch 1 fixes the inconsistent call context for gro_cells_receive() and
dev_dstats_rx_add().
On a TCP connection, we can end up calling gro_cells_receive() from
process context, which is unexpected (and could potentially trigger
deadlocks). At the same time, also dev_dstats_rx_add() should not be
invoked concurrently on the same CPU (i.e. from a softirq).
For this reason we're wrapping the aforementioned calls within
local_bh_disable/enable().
Ideally all selftests should always succeed now.
Please pull or let me know of any issue.
Thanks,
Antonio
The following changes since commit bd3a4795d5744f59a1f485379f1303e5e606f377:
selftests: tls: add test for data loss on small pipe (2026-05-02 18:27:14 -0700)
are available in the Git repository at:
https://github.com/OpenVPN/ovpn-net-next.git tags/ovpn-net-20260504
for you to fetch changes up to 201ba706318d460a2ea660e3652610be62532a70:
selftests: ovpn: reduce ping count in test.sh (2026-05-05 00:31:11 +0200)
----------------------------------------------------------------
Includes changes:
* ensure MAC header offset is reset before delivering packet
* ensure gro_cells_receive() and dstats_dev_add() are called with BH
disabled
* reduce ping count in selftest to ensure it completes within
timeout
----------------------------------------------------------------
Qingfang Deng (1):
ovpn: reset MAC header before passing skb up
Ralf Lici (2):
ovpn: ensure packet delivery happens with BH disabled
selftests: ovpn: reduce ping count in test.sh
drivers/net/ovpn/io.c | 7 +++++++
tools/testing/selftests/net/ovpn/test.sh | 4 ++--
2 files changed, 9 insertions(+), 2 deletions(-)
^ permalink raw reply
* [PATCH net 1/3] ovpn: reset MAC header before passing skb up
From: Antonio Quartulli @ 2026-05-04 23:03 UTC (permalink / raw)
To: netdev
Cc: edumazet, sd, davem, kuba, pabeni, ralf, Qingfang Deng,
Andrew Lunn, Minqiang Chen, Antonio Quartulli
In-Reply-To: <20260504230305.2681646-1-antonio@openvpn.net>
From: Qingfang Deng <qingfang.deng@linux.dev>
After decapsulating a packet, the skb->mac_header still points to the
outer transport header.
Fix this by calling skb_reset_mac_header() in ovpn_netdev_write() to
ensure the MAC header points to the beginning of
the inner IP/network packet, as expected by the rest of the stack.
Reported-by: Minqiang Chen <ptpt52@gmail.com>
Fixes: 8534731dbf2d ("ovpn: implement packet processing")
Signed-off-by: Qingfang Deng <qingfang.deng@linux.dev>
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/io.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index db43a1f8a07a..d92bb87be2b2 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -85,6 +85,7 @@ static void ovpn_netdev_write(struct ovpn_peer *peer, struct sk_buff *skb)
skb_scrub_packet(skb, true);
/* network header reset in ovpn_decrypt_post() */
+ skb_reset_mac_header(skb);
skb_reset_transport_header(skb);
skb_reset_inner_headers(skb);
--
2.53.0
^ permalink raw reply related
* [PATCH net 2/3] ovpn: ensure packet delivery happens with BH disabled
From: Antonio Quartulli @ 2026-05-04 23:03 UTC (permalink / raw)
To: netdev
Cc: edumazet, sd, davem, kuba, pabeni, ralf, Andrew Lunn,
Antonio Quartulli
In-Reply-To: <20260504230305.2681646-1-antonio@openvpn.net>
From: Ralf Lici <ralf@mandelbit.com>
ovpn injects decrypted packets into the netdev RX path through
ovpn_netdev_write() which invokes gro_cells_receive() and
dev_dstats_rx_add().
ovpn_netdev_write() is normally called in softirq context,
however, in case of TCP connections it may also be invoked
process context.
When this happens gro_cells_receive() will throw a warning:
[ 230.183747][ T12] WARNING: net/core/gro_cells.c:30 at gro_cells_receive+0x708/0xaa0, CPU#1: kworker/u16:0/12
and lockdep will also report a potential inconsistent lock state:
WARNING: inconsistent lock state
7.0.0-rc4+ #246 Tainted: G W
--------------------------------
inconsistent {IN-SOFTIRQ-W} -> {SOFTIRQ-ON-W} usage.
because attempts to acquire gro_cells->bh_lock by both
contexts may lead to a deadlock.
At the same time, dev_dstats_rx_add() does not expect to race
with a softirq (which may happen when invoked in process context),
because the latter may access its per-cpu state and corrupt
it.
Fix all this by invoking local_bh_disable/enable() around
gro_cells_receive() and dev_dstats_rx_add() to ensure that
bottom halves are always disabled before calling both of
them.
Fixes: 11851cbd60ea ("ovpn: implement TCP transport")
Signed-off-by: Ralf Lici <ralf@mandelbit.com>
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
drivers/net/ovpn/io.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
index d92bb87be2b2..22c555dd962e 100644
--- a/drivers/net/ovpn/io.c
+++ b/drivers/net/ovpn/io.c
@@ -91,12 +91,18 @@ static void ovpn_netdev_write(struct ovpn_peer *peer, struct sk_buff *skb)
/* cause packet to be "received" by the interface */
pkt_len = skb->len;
+ /* we may get here in process context in case of TCP connections,
+ * therefore we have to disable BHs to ensure gro_cells_receive()
+ * and dev_dstats_rx_add() do not get corrupted or enter deadlock
+ */
+ local_bh_disable();
ret = gro_cells_receive(&peer->ovpn->gro_cells, skb);
if (likely(ret == NET_RX_SUCCESS)) {
/* update RX stats with the size of decrypted packet */
ovpn_peer_stats_increment_rx(&peer->vpn_stats, pkt_len);
dev_dstats_rx_add(peer->ovpn->dev, pkt_len);
}
+ local_bh_enable();
}
void ovpn_decrypt_post(void *data, int ret)
--
2.53.0
^ permalink raw reply related
* [PATCH net 3/3] selftests: ovpn: reduce ping count in test.sh
From: Antonio Quartulli @ 2026-05-04 23:03 UTC (permalink / raw)
To: netdev
Cc: edumazet, sd, davem, kuba, pabeni, ralf, Andrew Lunn,
Simon Horman, Shuah Khan, linux-kselftest, Antonio Quartulli
In-Reply-To: <20260504230305.2681646-1-antonio@openvpn.net>
From: Ralf Lici <ralf@mandelbit.com>
The second stage of test.sh ("run baseline data traffic") performs a
basic connectivity check with ping -qfc 500 -w 3. On slower CI
instances this is too strict for TCP: the RTT is high enough that 500
echo requests do not reliably complete within 3 seconds, so the stage
flakes and the test fails even though the ovpn setup is healthy.
Reduce the packet count to 100 for both the plain and 3000-byte pings in
that stage. This still verifies peer setup, key exchange, routing, and
data-path traffic, without making the basic connectivity check depend on
timing out under load.
Fixes: 959bc330a439 ("testing/selftests: add test tool and scripts for ovpn module")
Signed-off-by: Ralf Lici <ralf@mandelbit.com>
Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
---
tools/testing/selftests/net/ovpn/test.sh | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selftests/net/ovpn/test.sh
index b50dbe45a4d0..c06e3135fbef 100755
--- a/tools/testing/selftests/net/ovpn/test.sh
+++ b/tools/testing/selftests/net/ovpn/test.sh
@@ -98,10 +98,10 @@ ovpn_run_basic_traffic() {
sleep 0.3
ovpn_cmd_ok "send baseline traffic to peer ${p}" \
ip netns exec ovpn_peer0 \
- ping -qfc 500 -w 3 5.5.5.$((p + 1))
+ ping -qfc 100 -w 3 5.5.5.$((p + 1))
ovpn_cmd_ok "send large-payload traffic to peer ${p}" \
ip netns exec ovpn_peer0 \
- ping -qfc 500 -s 3000 -w 3 5.5.5.$((p + 1))
+ ping -qfc 100 -s 3000 -w 3 5.5.5.$((p + 1))
wait "${tcpdump_pid1}" || return 1
wait "${tcpdump_pid2}" || return 1
--
2.53.0
^ permalink raw reply related
* Re: [PATCH net 06/12] netfilter: nf_conntrack_expect: honor expectation helper field
From: Pablo Neira Ayuso @ 2026-05-04 23:16 UTC (permalink / raw)
To: Ilya Maximets
Cc: netfilter-devel, fw, davem, netdev, kuba, pabeni, edumazet, horms,
Eelco Chaudron, Aaron Conole
In-Reply-To: <ef01005e-d867-4936-b138-b98f37e5f394@ovn.org>
[-- Attachment #1: Type: text/plain, Size: 7383 bytes --]
Hi Ilya,
On Mon, May 04, 2026 at 02:19:20PM +0200, Ilya Maximets wrote:
> On 5/1/26 12:37 PM, Pablo Neira Ayuso wrote:
> > Hi Ilya,
> >
> > On Thu, Apr 30, 2026 at 10:58:38PM +0200, Ilya Maximets wrote:
> >> On 3/26/26 1:51 PM, Pablo Neira Ayuso wrote:
> >>> The expectation helper field is mostly unused. As a result, the
> >>> netfilter codebase relies on accessing the helper through exp->master.
> >>>
> >>> Always set on the expectation helper field so it can be used to reach
> >>> the helper.
> >>>
> >>> nf_ct_expect_init() is called from packet path where the skb owns
> >>> the ct object, therefore accessing exp->master for the newly created
> >>> expectation is safe. This saves a lot of updates in all callsites
> >>> to pass the ct object as parameter to nf_ct_expect_init().
> >>>
> >>> This is a preparation patches for follow up fixes.
> >>>
> >>> Signed-off-by: Florian Westphal <fw@strlen.de>
> >>> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
> >>> ---
> >>
> >> Hi, Pablo and Florian.
> >>
> >> I was investigating FTP test failures in OVS with 7.0 kernel and bisected
> >> the issue down to this commit. AFAIU, with this change all the related
> >> connections over time gain their parents' helpers,. This is causing a change
> >> visible to the userspace, because FTP data connections are now reported to
> >> have helpers in the conntrack dump:
> >>
> >> # conntrack -L
> >> tcp 6 119 TIME_WAIT src=10.1.1.1 dst=10.1.1.2 sport=59534 dport=21 \
> >> src=10.1.1.2 dst=10.1.1.1 sport=21 dport=59534 \
> >> [ASSURED] mark=0 helper=ftp use=2
> >> tcp 6 119 TIME_WAIT src=10.1.1.2 dst=10.1.1.1 sport=52709 dport=52381 \
> >> src=10.1.1.1 dst=10.1.1.2 sport=52381 dport=52709 \
> >> [ASSURED] mark=0 helper=ftp use=1
> >>
> >> Before this commit only the control connection had helper=ftp reported in
> >> the dump. The traffic seems to work fine, but our tests fail because we
> >> do not expect the helper attached.
> >>
> >> AFAIU, it's generally not something that should be happening, as helpers
> >> on data connections do not really make much sense. But I'm just trying to
> >> figure out if you would consider this as a regression and fix in the kernel
> >> or if we should adjust our userspace components for this new dump content,
> >> which would not be very straightforward to do if we want to be able to run
> >> tests on both old and the new versions.
> >>
> >> What do you think?
> >
> > It seems previous behaviour to 9c42bc9db90a was inconsistent, ie. only
> > the h323 helper sets on exp->helper, then it shows helper= in expected
> > connections via ctnetlink. I guess this is for debugging given that
> > h323 is actually a family of helpers.
> >
> > To consistently skip dumping this for expected connections, probably
> > this is the way to do:
> >
> > diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conn
> > index eda5fe4a75c8..9491ae9e080e 100644
> > --- a/net/netfilter/nf_conntrack_netlink.c
> > +++ b/net/netfilter/nf_conntrack_netlink.c
> > @@ -226,7 +226,7 @@ static int ctnetlink_dump_helpinfo(struct sk_buff *sk
> > const struct nf_conn_help *help = nfct_help(ct);
> > struct nf_conntrack_helper *helper;
> >
> > - if (!help)
> > + if (!help || ct->status & IPS_EXPECTED)
> > return 0;
> >
> > rcu_read_lock();
>
> I'm not sure. I tried this change and it fixed one case but broke another.
> Looking at what we're testing, the old behavior (at least for FTP) was:
> "if helper was committed - report it, if not - don't". i.e. it's not really
> about the connection being expected it's about if the user committed the
> helper for the connection or not.
>
> Let me explain a few scenarios that we have in the OVS system tests and what
> I see with the old kernel (6.19), the new (7.0) and the patch above.
>
> A) The first scenario has the following OpenFlow rules (simplified):
>
> table=0,in_port=1,tcp,action=ct(alg=ftp,commit),2
> table=0,in_port=2,tcp,action=ct(table=1)
> table=1,in_port=2,tcp,ct_state=+trk+est,action=1
> table=1,in_port=2,tcp,ct_state=+trk+rel,action=1
>
> This set of rule blindly commits every packet coming from port 1 with the
> helper and sends to port 2. Packets from port 2 are passed through ct and
> only related or established traffic is passed to port 1. This is a very
> rudimentary setup that users can make to allow ftp from port 1 towards port 2,
> but not in the opposite direction.
>
> For this scenario regardless of the kernel version or the patch above I see
> that both the data and the control connections have a helper reported in the
> ctnetlink dump.
This ruleset then is attached the conntrack helper to data connection,
that is, ALG is inspecting the FTP data connection but it will just
find no patterns because it is only the FTP control connection that
creates expectations?
> B) The second scenario:
>
> table=0,in_port=1,tcp,action=ct(table=1)
> table=1,in_port=1,tcp,ct_state=+trk+new,action=ct(commit,alg=ftp),2
> table=1,in_port=1,tcp,ct_state=+trk+est,action=2
>
> table=0,in_port=2,tcp,action=ct(table=1)
> table=1,in_port=2,tcp,ct_state=+trk+new+rel,action=ct(commit),1
> table=1,in_port=2,tcp,ct_state=+trk+est,action=1
>
> This is a more reasonable setup where new connections are committed on the
> way from 1 to 2 and new related connections are committed on the way from
> 2 to 1. This allows port 1 to initiate the control connection and the port
> 2 to initiate the related data connection.
>
> In case of active FTP (port 1 initiates control, port 2 initiates the data):
> - old: control has the helper, data does not.
> - new: both have the helper.
> - patch: control has the helper, data does not.
>
> In case of passive FTP (port 1 initiates both the control and data):
> - old: both have the helper (because both are +new traffic from port 1).
> - new: both have the helper.
> - patch: control has the helper, data does not.
>
> C) We can modify the scenario B to avoid committing the helper on related:
>
> table=0,in_port=1,tcp,action=ct(table=1)
> table=1,in_port=1,tcp,ct_state=+trk+new-rel,action=ct(commit,alg=ftp),2
> table=1,in_port=1,tcp,ct_state=+trk+new+rel,action=ct(commit),2
> table=1,in_port=1,tcp,ct_state=+trk+est,action=2
>
> table=0,in_port=2,tcp,action=ct(table=1)
> table=1,in_port=2,tcp,ct_state=+trk+new+rel,action=ct(commit),1
> table=1,in_port=2,tcp,ct_state=+trk+est,action=1
>
> Here we have (same for active and passive):
> - old: control has the helper, data does not.
> - new: both have the helper.
> - patch: control has the helper, data does not.
>
> I hope that clarifies the situation a little bit.
>
> So, if we want to restore the old behavior, the we'd probably need to track
> how connection gained the helper, i.e. was it via commit or was it inherited.
>
> I'm also not sure why we see the helper with the patch above in scenario A that
> commits established traffic, but not in B or C that only commits new traffic.
Thanks for the detailed report. It seems I changed the semantics of
exp->helper, this used to be use to set a new helper for an expected
connection, which is the case for sip and h323.
Would this patch help address the issue you are observing?
Thanks.
[-- Attachment #2: fix-exp-helper.patch --]
[-- Type: text/x-diff, Size: 5017 bytes --]
commit 39b6894dd511e14450e2a640eeaa83379e0f8eb1
Author: Pablo Neira Ayuso <pablo@netfilter.org>
Date: Tue May 5 01:15:19 2026 +0200
x
diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h
index e9a8350e7ccf..80f50fd0f7ad 100644
--- a/include/net/netfilter/nf_conntrack_expect.h
+++ b/include/net/netfilter/nf_conntrack_expect.h
@@ -45,9 +45,12 @@ struct nf_conntrack_expect {
void (*expectfn)(struct nf_conn *new,
struct nf_conntrack_expect *this);
- /* Helper to assign to new connection */
+ /* Helper that created this expectation */
struct nf_conntrack_helper __rcu *helper;
+ /* Helper to assign to new connection */
+ struct nf_conntrack_helper __rcu *assign_helper;
+
/* The conntrack of the master connection */
struct nf_conn *master;
diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
index b08189226320..656287d16d86 100644
--- a/net/netfilter/nf_conntrack_core.c
+++ b/net/netfilter/nf_conntrack_core.c
@@ -1815,10 +1815,10 @@ init_conntrack(struct net *net, struct nf_conn *tmpl,
__set_bit(IPS_EXPECTED_BIT, &ct->status);
/* exp->master safe, refcnt bumped in nf_ct_find_expectation */
ct->master = exp->master;
- if (exp->helper) {
+ if (exp->assign_helper) {
help = nf_ct_helper_ext_add(ct, GFP_ATOMIC);
if (help)
- rcu_assign_pointer(help->helper, exp->helper);
+ rcu_assign_pointer(help->helper, exp->assign_helper);
}
#ifdef CONFIG_NF_CONNTRACK_MARK
diff --git a/net/netfilter/nf_conntrack_h323_main.c b/net/netfilter/nf_conntrack_h323_main.c
index 3f5c50455b71..b2fe6554b9cf 100644
--- a/net/netfilter/nf_conntrack_h323_main.c
+++ b/net/netfilter/nf_conntrack_h323_main.c
@@ -643,7 +643,7 @@ static int expect_h245(struct sk_buff *skb, struct nf_conn *ct,
&ct->tuplehash[!dir].tuple.src.u3,
&ct->tuplehash[!dir].tuple.dst.u3,
IPPROTO_TCP, NULL, &port);
- rcu_assign_pointer(exp->helper, &nf_conntrack_helper_h245);
+ rcu_assign_pointer(exp->assign_helper, &nf_conntrack_helper_h245);
nathook = rcu_dereference(nfct_h323_nat_hook);
if (memcmp(&ct->tuplehash[dir].tuple.src.u3,
@@ -767,7 +767,7 @@ static int expect_callforwarding(struct sk_buff *skb,
nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, nf_ct_l3num(ct),
&ct->tuplehash[!dir].tuple.src.u3, &addr,
IPPROTO_TCP, NULL, &port);
- rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
+ rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
nathook = rcu_dereference(nfct_h323_nat_hook);
if (memcmp(&ct->tuplehash[dir].tuple.src.u3,
@@ -1234,7 +1234,7 @@ static int expect_q931(struct sk_buff *skb, struct nf_conn *ct,
&ct->tuplehash[!dir].tuple.src.u3 : NULL,
&ct->tuplehash[!dir].tuple.dst.u3,
IPPROTO_TCP, NULL, &port);
- rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
+ rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
exp->flags = NF_CT_EXPECT_PERMANENT; /* Accept multiple calls */
nathook = rcu_dereference(nfct_h323_nat_hook);
@@ -1306,7 +1306,7 @@ static int process_gcf(struct sk_buff *skb, struct nf_conn *ct,
nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, nf_ct_l3num(ct),
&ct->tuplehash[!dir].tuple.src.u3, &addr,
IPPROTO_UDP, NULL, &port);
- rcu_assign_pointer(exp->helper, nf_conntrack_helper_ras);
+ rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_ras);
if (nf_ct_expect_related(exp, 0) == 0) {
pr_debug("nf_ct_ras: expect RAS ");
@@ -1523,7 +1523,7 @@ static int process_acf(struct sk_buff *skb, struct nf_conn *ct,
&ct->tuplehash[!dir].tuple.src.u3, &addr,
IPPROTO_TCP, NULL, &port);
exp->flags = NF_CT_EXPECT_PERMANENT;
- rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
+ rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
if (nf_ct_expect_related(exp, 0) == 0) {
pr_debug("nf_ct_ras: expect Q.931 ");
@@ -1577,7 +1577,7 @@ static int process_lcf(struct sk_buff *skb, struct nf_conn *ct,
&ct->tuplehash[!dir].tuple.src.u3, &addr,
IPPROTO_TCP, NULL, &port);
exp->flags = NF_CT_EXPECT_PERMANENT;
- rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
+ rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
if (nf_ct_expect_related(exp, 0) == 0) {
pr_debug("nf_ct_ras: expect Q.931 ");
diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c
index 1eb55907d470..d24bfa9e8234 100644
--- a/net/netfilter/nf_conntrack_sip.c
+++ b/net/netfilter/nf_conntrack_sip.c
@@ -1383,7 +1383,7 @@ static int process_register_request(struct sk_buff *skb, unsigned int protoff,
nf_ct_expect_init(exp, SIP_EXPECT_SIGNALLING, nf_ct_l3num(ct),
saddr, &daddr, proto, NULL, &port);
exp->timeout.expires = sip_timeout * HZ;
- rcu_assign_pointer(exp->helper, helper);
+ rcu_assign_pointer(exp->assign_helper, helper);
exp->flags = NF_CT_EXPECT_PERMANENT | NF_CT_EXPECT_INACTIVE;
hooks = rcu_dereference(nf_nat_sip_hooks);
^ permalink raw reply related
* Re: [PATCH v2 0/7] seg6: add SRv6 Mobile User Plane (RFC 9433) behaviors
From: Jakub Kicinski @ 2026-05-04 23:39 UTC (permalink / raw)
To: Yuya Kusakabe
Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Andrea Mayer, Shuah Khan, Jonathan Corbet, Shuah Khan,
linux-kernel, netdev, linux-kselftest, linux-doc
In-Reply-To: <20260505-seg6-mobile-v2-0-9e8022bdfdb6@gmail.com>
On Tue, 05 May 2026 01:30:10 +0900 Yuya Kusakabe wrote:
> This series adds the in-kernel data path for the SRv6 Mobile User
> Plane (MUP) architecture defined in RFC 9433. SRv6 MUP integrates
> GTP-U mobile traffic into an SRv6 transport domain by mapping the
> 5-tuple (TEID, QFI, R, U, PDU Session ID) into a single SID, allowing
> operators to replace the GTP-U overlay between the gNB and the
> upstream UPF with native SRv6 forwarding while keeping the radio side
> unchanged.
Could you switch to posting this as an RFC until you gather some review
tags? Our CI require manual intervention to add the necessary iproute2
patches, I suspect there may be some uAPI changes therefore requiring
iproute2 changes here.
^ permalink raw reply
* Re: [PATCH net 06/12] netfilter: nf_conntrack_expect: honor expectation helper field
From: Pablo Neira Ayuso @ 2026-05-04 23:40 UTC (permalink / raw)
To: Ilya Maximets
Cc: netfilter-devel, fw, davem, netdev, kuba, pabeni, edumazet, horms,
Eelco Chaudron, Aaron Conole
In-Reply-To: <afkosr2fDEPA_jX9@chamomile>
[-- Attachment #1: Type: text/plain, Size: 418 bytes --]
On Tue, May 05, 2026 at 01:16:05AM +0200, Pablo Neira Ayuso wrote:
> Thanks for the detailed report. It seems I changed the semantics of
> exp->helper, this used to be use to set a new helper for an expected
> connection, which is the case for sip and h323.
>
> Would this patch help address the issue you are observing?
Actually, this needs to set to NULL the new exp->assign_helper field,
see new patch, untested.
[-- Attachment #2: fix-exp-helper.patch --]
[-- Type: text/x-diff, Size: 5296 bytes --]
diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h
index e9a8350e7ccf..80f50fd0f7ad 100644
--- a/include/net/netfilter/nf_conntrack_expect.h
+++ b/include/net/netfilter/nf_conntrack_expect.h
@@ -45,9 +45,12 @@ struct nf_conntrack_expect {
void (*expectfn)(struct nf_conn *new,
struct nf_conntrack_expect *this);
- /* Helper to assign to new connection */
+ /* Helper that created this expectation */
struct nf_conntrack_helper __rcu *helper;
+ /* Helper to assign to new connection */
+ struct nf_conntrack_helper __rcu *assign_helper;
+
/* The conntrack of the master connection */
struct nf_conn *master;
diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
index b08189226320..656287d16d86 100644
--- a/net/netfilter/nf_conntrack_core.c
+++ b/net/netfilter/nf_conntrack_core.c
@@ -1815,10 +1815,10 @@ init_conntrack(struct net *net, struct nf_conn *tmpl,
__set_bit(IPS_EXPECTED_BIT, &ct->status);
/* exp->master safe, refcnt bumped in nf_ct_find_expectation */
ct->master = exp->master;
- if (exp->helper) {
+ if (exp->assign_helper) {
help = nf_ct_helper_ext_add(ct, GFP_ATOMIC);
if (help)
- rcu_assign_pointer(help->helper, exp->helper);
+ rcu_assign_pointer(help->helper, exp->assign_helper);
}
#ifdef CONFIG_NF_CONNTRACK_MARK
diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c
index 24d0576d84b7..b01d48ab351e 100644
--- a/net/netfilter/nf_conntrack_expect.c
+++ b/net/netfilter/nf_conntrack_expect.c
@@ -308,6 +308,7 @@ struct nf_conntrack_expect *nf_ct_expect_alloc(struct nf_conn *me)
if (!new)
return NULL;
+ new->assign_helper = NULL;
new->master = me;
refcount_set(&new->use, 1);
return new;
diff --git a/net/netfilter/nf_conntrack_h323_main.c b/net/netfilter/nf_conntrack_h323_main.c
index 3f5c50455b71..b2fe6554b9cf 100644
--- a/net/netfilter/nf_conntrack_h323_main.c
+++ b/net/netfilter/nf_conntrack_h323_main.c
@@ -643,7 +643,7 @@ static int expect_h245(struct sk_buff *skb, struct nf_conn *ct,
&ct->tuplehash[!dir].tuple.src.u3,
&ct->tuplehash[!dir].tuple.dst.u3,
IPPROTO_TCP, NULL, &port);
- rcu_assign_pointer(exp->helper, &nf_conntrack_helper_h245);
+ rcu_assign_pointer(exp->assign_helper, &nf_conntrack_helper_h245);
nathook = rcu_dereference(nfct_h323_nat_hook);
if (memcmp(&ct->tuplehash[dir].tuple.src.u3,
@@ -767,7 +767,7 @@ static int expect_callforwarding(struct sk_buff *skb,
nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, nf_ct_l3num(ct),
&ct->tuplehash[!dir].tuple.src.u3, &addr,
IPPROTO_TCP, NULL, &port);
- rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
+ rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
nathook = rcu_dereference(nfct_h323_nat_hook);
if (memcmp(&ct->tuplehash[dir].tuple.src.u3,
@@ -1234,7 +1234,7 @@ static int expect_q931(struct sk_buff *skb, struct nf_conn *ct,
&ct->tuplehash[!dir].tuple.src.u3 : NULL,
&ct->tuplehash[!dir].tuple.dst.u3,
IPPROTO_TCP, NULL, &port);
- rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
+ rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
exp->flags = NF_CT_EXPECT_PERMANENT; /* Accept multiple calls */
nathook = rcu_dereference(nfct_h323_nat_hook);
@@ -1306,7 +1306,7 @@ static int process_gcf(struct sk_buff *skb, struct nf_conn *ct,
nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, nf_ct_l3num(ct),
&ct->tuplehash[!dir].tuple.src.u3, &addr,
IPPROTO_UDP, NULL, &port);
- rcu_assign_pointer(exp->helper, nf_conntrack_helper_ras);
+ rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_ras);
if (nf_ct_expect_related(exp, 0) == 0) {
pr_debug("nf_ct_ras: expect RAS ");
@@ -1523,7 +1523,7 @@ static int process_acf(struct sk_buff *skb, struct nf_conn *ct,
&ct->tuplehash[!dir].tuple.src.u3, &addr,
IPPROTO_TCP, NULL, &port);
exp->flags = NF_CT_EXPECT_PERMANENT;
- rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
+ rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
if (nf_ct_expect_related(exp, 0) == 0) {
pr_debug("nf_ct_ras: expect Q.931 ");
@@ -1577,7 +1577,7 @@ static int process_lcf(struct sk_buff *skb, struct nf_conn *ct,
&ct->tuplehash[!dir].tuple.src.u3, &addr,
IPPROTO_TCP, NULL, &port);
exp->flags = NF_CT_EXPECT_PERMANENT;
- rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
+ rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
if (nf_ct_expect_related(exp, 0) == 0) {
pr_debug("nf_ct_ras: expect Q.931 ");
diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c
index 1eb55907d470..d24bfa9e8234 100644
--- a/net/netfilter/nf_conntrack_sip.c
+++ b/net/netfilter/nf_conntrack_sip.c
@@ -1383,7 +1383,7 @@ static int process_register_request(struct sk_buff *skb, unsigned int protoff,
nf_ct_expect_init(exp, SIP_EXPECT_SIGNALLING, nf_ct_l3num(ct),
saddr, &daddr, proto, NULL, &port);
exp->timeout.expires = sip_timeout * HZ;
- rcu_assign_pointer(exp->helper, helper);
+ rcu_assign_pointer(exp->assign_helper, helper);
exp->flags = NF_CT_EXPECT_PERMANENT | NF_CT_EXPECT_INACTIVE;
hooks = rcu_dereference(nf_nat_sip_hooks);
^ permalink raw reply related
* Re: [Intel-wired-lan] [PATCH iwl-next] ice: add SBQ posted writes with non-posted support for CGU
From: Jacob Keller @ 2026-05-04 23:41 UTC (permalink / raw)
To: Przemyslaw Korba, intel-wired-lan
Cc: netdev, anthony.l.nguyen, przemyslaw.kitszel, Aleksandr Loktionov,
Arkadiusz Kubalewski
In-Reply-To: <20260415112706.1562382-2-przemyslaw.korba@intel.com>
On 4/15/2026 4:27 AM, Przemyslaw Korba wrote:
> From: Karol Kolacinski <karol.kolacinski@intel.com>
>
> Sideband queue (SBQ) is a HW queue with very short completion time. All
> SBQ writes were posted by default, which means that the driver did not
> have to wait for completion from the neighbor device, because there was
> none. This introduced unnecessary delays, where only those delays were
> "ensuring" that the command is "completed" and this was a potential race
> condition.
>
> Add the possibility to perform non-posted writes where it's necessary to
> wait for completion, instead of relying on fake completion from the FW,
> where only the delays are guarding the writes.
>
> Flush the SBQ by reading address 0 from the PHY 0 before issuing SYNC
> command to ensure that writes to all PHYs were completed and skip SBQ
> message completion if it's posted.
>
> To analyze if delays are gone, look for and compare time spent in
> ice_sq_send_cmd — posted writes should return immediately after the wr32.
> That can be done for example by adjusting phc time with phc_ctl on E830
> device, for less than 2 seconds to use this new mechanism. Without it,
> command below will fail.
>
> Reproduction steps:
> phc_ctl eth13 adj 1
> phc_ctl[4478170.994]: adjusted clock by 1.000000 seconds
>
> Check trace for timing for comparisions:
> echo ice_sbq_send_cmd > /sys/kernel/debug/tracing/set_ftrace_filter
> echo function_graph > /sys/kernel/debug/tracing/current_tracer
> cat /sys/kernel/debug/tracing/trace
>
> Tested on:
> - Intel E830 NIC (FW version 1.00)
> - Kernel 6.19.0+
>
> Signed-off-by: Karol Kolacinski <karol.kolacinski@intel.com>
> Signed-off-by: Przemyslaw Korba <przemyslaw.korba@intel.com>
> Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
> Reviewed-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
> ---
This doesn't appear to apply clean to the tip of Intel Wired LAN
dev-queue, nor to net-next/main...
> drivers/net/ethernet/intel/ice/ice_common.c | 18 ++++--
> drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 64 ++++++++++++--------
> drivers/net/ethernet/intel/ice/ice_sbq_cmd.h | 5 +-
> 3 files changed, 53 insertions(+), 34 deletions(-)
>
> diff --git a/drivers/net/ethernet/intel/ice/ice_common.c b/drivers/net/ethernet/intel/ice/ice_common.c
> index f84990996530..2cd3d6d450a9 100644
> --- a/drivers/net/ethernet/intel/ice/ice_common.c
> +++ b/drivers/net/ethernet/intel/ice/ice_common.c
> @@ -1777,23 +1777,29 @@ int ice_sbq_rw_reg(struct ice_hw *hw, struct ice_sbq_msg_input *in, u16 flags)
> msg.msg_addr_low = cpu_to_le16(in->msg_addr_low);
> msg.msg_addr_high = cpu_to_le32(in->msg_addr_high);
>
> - if (in->opcode)
> + switch (in->opcode) {
> + case ice_sbq_msg_wr_p:
> + case ice_sbq_msg_wr_np:
> msg.data = cpu_to_le32(in->data);
> - else
> + break;
> + case ice_sbq_msg_rd:
> /* data read comes back in completion, so shorten the struct by
> * sizeof(msg.data)
> */
> msg_len -= sizeof(msg.data);
> + break;
> + default:
> + return -EINVAL;
> + }
>
> - if (in->opcode == ice_sbq_msg_wr)
> - cd.posted = 1;
It looks like this code in the upstream version doesn't have the cd
structure on this function.
> + cd.posted = in->opcode == ice_sbq_msg_wr_p;
>
It looks like this is based on top of "ice: fix posted write support for
sideband queue operations"? That was dropped from the queue because of
our discussion that you would re-submit a fixed version.
Since that didn't get applied, this won't apply clean either. Do you
still want the part that fixes E830 to go to net? Or do you just want to
implement posted writes all together in one patch?
Either way, could you please re-submit the work either as 2 patches or
as a single combined patch?
Thanks,
Jake
^ 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