Netdev List
 help / color / mirror / Atom feed
* [PATCH net] selftests: openvswitch: Test SET on later IPv6 fragments
@ 2026-10-09  9:06 Sahaj Chaudhari
  2026-10-10  9:06 ` netdev-bot+sashiko
  0 siblings, 1 reply; 2+ messages in thread
From: Sahaj Chaudhari @ 2026-10-09  9:06 UTC (permalink / raw)
  To: netdev
  Cc: aconole, echaudro, i.maximets, davem, edumazet, kuba, pabeni,
	horms, shuah, dev, linux-kselftest

Add regression coverage for OVS IPv6 SET actions on later IPv6
fragments. A later fragment has no transport header, so the test sends
a crafted noninitial fragment through a source-address SET flow, checks
that the flow counter advances, and looks for a set_ipv6_addr warning.

This covers the later-fragment case handled by the upstream fix.

Signed-off-by: Sahaj Chaudhari <sahaj123.sc@gmail.com>

---
 tools/testing/selftests/net/openvswitch/Makefile   |  2 +-
 tools/testing/selftests/net/openvswitch/config     |  1 +
 .../selftests/net/openvswitch/openvswitch.sh       | 43 +++++++++++++++++++
 .../selftests/net/openvswitch/ovs-ipv6-frag.py     | 48 ++++++++++++++++++++++
 4 files changed, 93 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/net/openvswitch/Makefile b/tools/testing/selftests/net/openvswitch/Makefile
index 3fd1da2ec07d5..b3260cd354419 100644
--- a/tools/testing/selftests/net/openvswitch/Makefile
+++ b/tools/testing/selftests/net/openvswitch/Makefile
@@ -6,7 +6,7 @@ CFLAGS += -Wall -Wl,--no-as-needed -O2 -g -I$(top_srcdir)/usr/include $(KHDR_INC
 
 TEST_PROGS := openvswitch.sh
 
-TEST_FILES := ovs-dpctl.py
+TEST_FILES := ovs-dpctl.py ovs-ipv6-frag.py
 
 EXTRA_CLEAN := test_netlink_checks
 
diff --git a/tools/testing/selftests/net/openvswitch/config b/tools/testing/selftests/net/openvswitch/config
index a825e0b5c88e5..b062ebbce0f00 100644
--- a/tools/testing/selftests/net/openvswitch/config
+++ b/tools/testing/selftests/net/openvswitch/config
@@ -3,6 +3,7 @@ CONFIG_INET_DIAG=y
 CONFIG_IP_SCTP=y
 CONFIG_IPV6=y
 CONFIG_NETFILTER=y
+CONFIG_DEBUG_NET=y
 CONFIG_NET_IPGRE=m
 CONFIG_NET_IPGRE_DEMUX=m
 CONFIG_NF_CONNTRACK=m
diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh
index a31f7fb6882dc..b9a4c4bf40672 100755
--- a/tools/testing/selftests/net/openvswitch/openvswitch.sh
+++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh
@@ -31,6 +31,7 @@ tests="
 	dec_ttl					ttl: dec_ttl decrements IP TTL
 	flow_set				flow-set: Flow modify
 	action_set				set: SET action rewrites fields
+	ipv6_later_frag				ipv6-frag: SET on a later IPv6 fragment
 	trunc					trunc: output truncation
 	icmpv6					icmpv6: ICMPv6 echo type match
 	sctp_connect_v4				sctp: SCTP flow key matching
@@ -66,6 +67,11 @@ ovs_wait() {
 	return 1
 }
 
+ovs_flow_has_packets() {
+	python3 "$ovs_base/ovs-dpctl.py" dump-flows "$1" |
+		grep -Eq 'packets:[1-9][0-9]*,'
+}
+
 ovs_base=`pwd`
 sbxs=
 sbx_add () {
@@ -445,6 +451,43 @@ test_action_set() {
 	return 0
 }
 
+test_ipv6_later_frag() {
+	local t="test_ipv6_later_frag"
+	local warning="WARNING:.*set_ipv6_addr"
+	local before after
+
+	if ! dmesg >/dev/null 2>&1; then
+		info "dmesg unavailable - cannot check for kernel warning"
+		return $ksft_skip
+	fi
+
+	before=$(dmesg | grep -c "$warning" || true)
+	if [ "$before" -ne 0 ]; then
+		info "set_ipv6_addr warning already present - skipping"
+		return $ksft_skip
+	fi
+
+	sbx_add "$t" || return 1
+	ovs_add_dp "$t" fragtest || return 1
+	ovs_add_netns_and_veths "$t" fragtest client c0 c1 || return 1
+
+	ovs_add_flow "$t" fragtest \
+		'in_port(1),eth(),eth_type(0x86dd),ipv6(proto=44)' \
+		'set(ipv6(src=2001:db8::99,proto=44)),drop' || return 1
+
+	ip netns exec client python3 "$ovs_base/ovs-ipv6-frag.py" c1 || \
+		return 1
+	ovs_wait ovs_flow_has_packets fragtest || return 1
+
+	after=$(dmesg | grep -c "$warning" || true)
+	if [ "$after" -ne "$before" ]; then
+		info "set_ipv6_addr warning emitted for later IPv6 fragment"
+		return 1
+	fi
+
+	return 0
+}
+
 # trunc test
 # - trunc(14): truncate to ETH_HLEN, strips IP payload, ping fails
 # - trunc(1) and trunc(13): kernel rejects below ETH_HLEN (EINVAL)
diff --git a/tools/testing/selftests/net/openvswitch/ovs-ipv6-frag.py b/tools/testing/selftests/net/openvswitch/ovs-ipv6-frag.py
new file mode 100644
index 0000000000000..869f9d6503ef4
--- /dev/null
+++ b/tools/testing/selftests/net/openvswitch/ovs-ipv6-frag.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+import ipaddress
+import socket
+import struct
+import sys
+
+
+def main():
+    if len(sys.argv) != 2:
+        print(f"Usage: {sys.argv[0]} INTERFACE", file=sys.stderr)
+        return 2
+
+    interface = sys.argv[1]
+    payload = bytes(range(8))
+    # Offset 1 and M=1 make this a noninitial fragment.
+    fragment = struct.pack("!BBHI", socket.IPPROTO_TCP, 0, 0x0009, 1)
+    ipv6 = struct.pack(
+        "!IHBB16s16s",
+        6 << 28,
+        len(fragment) + len(payload),
+        socket.IPPROTO_FRAGMENT,
+        64,
+        ipaddress.IPv6Address("2001:db8::1").packed,
+        ipaddress.IPv6Address("2001:db8::2").packed,
+    )
+    ethernet = struct.pack(
+        "!6s6sH",
+        b"\xff" * 6,
+        bytes.fromhex("020000000001"),
+        0x86DD,
+    )
+    packet = ethernet + ipv6 + fragment + payload
+
+    with socket.socket(
+        socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0003)
+    ) as sock:
+        sock.bind((interface, 0))
+        sent = sock.send(packet)
+
+    if sent != len(packet):
+        raise OSError(f"short packet send: {sent} of {len(packet)} bytes")
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())

^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH net] selftests: openvswitch: Test SET on later IPv6 fragments
  2026-10-09  9:06 [PATCH net] selftests: openvswitch: Test SET on later IPv6 fragments Sahaj Chaudhari
@ 2026-10-10  9:06 ` netdev-bot+sashiko
  0 siblings, 0 replies; 2+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10  9:06 UTC (permalink / raw)
  To: sahaj123.sc
  Cc: netdev, aconole, echaudro, i.maximets, davem, edumazet, kuba,
	pabeni, horms, shuah, dev, linux-kselftest

Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider.

Critical: 0 · High: 1 · Medium: 3 · Low: 2

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] The test sends its fragment in a way that can never trigger the
  bug it is meant to catch, so it passes with or without commit…
- [Medium] test_ipv6_later_frag looks for the regression with `grep -c
  'WARNING:.*set_ipv6_addr'`.
- [Medium] test_ipv6_later_frag never checks that the running kernel has
  CONFIG_DEBUG_NET, even though the patch adds CONFIG_DEBUG_NET=y to the…
- [Medium] The new test's packet-counter check (ovs_flow_has_packets)
  runs `ovs-dpctl.py dump-flows` on a flow whose action is a SET, but…
- [Low] The commit message says the test 'covers the later-fragment case
  handled by the upstream fix' but does not name it: commit 3c6a4b11330f…
- [Low] The fragment adds CONFIG_DEBUG_NET=y but not its prerequisite
  CONFIG_DEBUG_KERNEL=y.

--

selftests: openvswitch: Test SET on later IPv6 fragments

This adds an openvswitch selftest that sends a crafted noninitial IPv6
fragment through a flow with an IPv6 source-address SET action. The test
checks that the flow counter advances and that dmesg shows no
set_ipv6_addr warning. It also adds CONFIG_DEBUG_NET=y to the openvswitch
config fragment.

> This covers the later-fragment case handled by the upstream fix.

[Severity: Low]
Could this name the fix? It appears to be commit 3c6a4b11330f ("net:
openvswitch: validate transport header presence in set_ipv6_addr").

The commit message also doesn't say that the patch adds CONFIG_DEBUG_NET=y
to tools/testing/selftests/net/openvswitch/config. That change applies to
every OVS selftest built from the fragment. It turns on all
DEBUG_NET_WARN_ON_ONCE() checks and turns off the transport header reset
in __netif_receive_skb_core().

Should that be mentioned too?

> diff --git a/tools/testing/selftests/net/openvswitch/config b/tools/testing/selftests/net/openvswitch/config
> index a825e0b5c88e5..b062ebbce0f00 100644
> --- a/tools/testing/selftests/net/openvswitch/config
> +++ b/tools/testing/selftests/net/openvswitch/config
> @@ -3,6 +3,7 @@ CONFIG_INET_DIAG=y
>  CONFIG_IP_SCTP=y
>  CONFIG_IPV6=y
>  CONFIG_NETFILTER=y
> +CONFIG_DEBUG_NET=y

[Severity: Low]
Does this fragment also need CONFIG_DEBUG_KERNEL=y? From net/Kconfig.debug:

config DEBUG_NET
	bool "Add generic networking debug"
	depends on DEBUG_KERNEL && NET

DEBUG_KERNEL has no default. If this fragment is merged into a base config
that lacks DEBUG_KERNEL, DEBUG_NET is silently dropped and the warning this
test looks for can't fire.

x86_64_defconfig and i386_defconfig already set CONFIG_DEBUG_KERNEL=y, so
the common case isn't affected.

[ ... ]

> diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh
> index a31f7fb6882dc..b9a4c4bf40672 100755
> --- a/tools/testing/selftests/net/openvswitch/openvswitch.sh
> +++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh

[ ... ]

> @@ -66,6 +67,11 @@ ovs_wait() {
>  	return 1
>  }
>  
> +ovs_flow_has_packets() {
> +	python3 "$ovs_base/ovs-dpctl.py" dump-flows "$1" |
> +		grep -Eq 'packets:[1-9][0-9]*,'
> +}

[Severity: Medium]
Can dump-flows format the SET action that this test installs? The SET
branch of ovsactions.dpstr() in ovs-dpctl.py does this:

    field = datum
    mask = None
    if field[0] == "OVS_ACTION_ATTR_SET_MASKED":

Here datum is the decoded ovskey nla returned by
self.get_attr("OVS_ACTION_ATTR_SET"). So field[0] indexes an nla object
with the integer 0 instead of checking the action name. This branch is not
inside the try/except that covers the other actions.

ovsflow.dpstr() puts the "packets:N," text in the same string as the
actions, and dump-flows prints nothing until that whole string is built.

If indexing the pyroute2 nla with 0 raises KeyError, dump-flows exits with
a traceback and prints nothing. Then ovs_flow_has_packets never matches,
ovs_wait times out, and the test fails even on a fixed kernel.

Whether it raises depends on how pyroute2's nlmsg_base implements
__getitem__. The formatter itself isn't new. However, no existing test
dumps a flow with a SET action, so this test seems to be the first to
depend on it.

> @@ -445,6 +451,43 @@ test_action_set() {
>  	return 0
>  }
>  
> +test_ipv6_later_frag() {
> +	local t="test_ipv6_later_frag"
> +	local warning="WARNING:.*set_ipv6_addr"

[Severity: Medium]
Will this pattern match the warning on every build? __warn() in
kernel/panic.c prints the headline like this:

	pr_warn("WARNING: %s:%d at %pS, CPU#%d: %s/%d\n",
		file, line, caller,

For the DEBUG_NET_WARN_ON_ONCE() in skb_transport_header(), %s:%d is the
include/linux/skbuff.h location. %pS names the out-of-line function that
contains the WARN. The [inline] frames in the syzbot trace come from
offline symbolization and don't appear in raw dmesg.

set_ipv6_addr() is a static helper with two call sites, both in
set_ipv6(). In the syzbot build, set_ipv6() was already inlined into
do_execute_actions(). If the compiler also inlines set_ipv6_addr(), the
headline names do_execute_actions or set_ipv6 instead. The test would then
pass even though the warning fired.

Would it be more reliable to match on the skbuff.h WARN location or on the
skb_transport_header_was_set() condition?

> +	local before after
> +
> +	if ! dmesg >/dev/null 2>&1; then
> +		info "dmesg unavailable - cannot check for kernel warning"
> +		return $ksft_skip
> +	fi

[Severity: Medium]
Should the test also skip when the running kernel doesn't have
CONFIG_DEBUG_NET? Without it, include/net/net_debug.h has:

#define DEBUG_NET_WARN_ON_ONCE(cond) BUILD_BUG_ON_INVALID(cond)

and __netif_receive_skb_core() also resets an unset transport header:

#if !defined(CONFIG_DEBUG_NET)
	if (!skb_transport_header_was_set(skb))
		skb_reset_transport_header(skb);
#endif

The test only skips when dmesg can't be read or when a matching warning is
already present. On distro or CI kernels not built from the config
fragment, it returns 0 once the flow counter advances instead of returning
$ksft_skip.

[ ... ]

> diff --git a/tools/testing/selftests/net/openvswitch/ovs-ipv6-frag.py b/tools/testing/selftests/net/openvswitch/ovs-ipv6-frag.py
> new file mode 100644
> index 0000000000000..869f9d6503ef4
> --- /dev/null
> +++ b/tools/testing/selftests/net/openvswitch/ovs-ipv6-frag.py

[ ... ]

> +    packet = ethernet + ipv6 + fragment + payload
> +
> +    with socket.socket(
> +        socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0003)
> +    ) as sock:
> +        sock.bind((interface, 0))
> +        sent = sock.send(packet)

[Severity: High]
Can a fragment sent this way trigger the warning at all? It looks like the
test passes both with and without commit 3c6a4b11330f.

The socket is bound with ETH_P_ALL, so packet_snd() leaves skb->protocol at
0. Then this happens:

packet_snd()
  packet_parse_headers()
    skb->protocol = dev_parse_header_protocol(skb);  /* ETH_P_IPV6 */
    skb_probe_transport_header()
      __skb_flow_dissect()
        /* NEXTHDR_FRAGMENT, non-first fragment */
        fdret = FLOW_DISSECT_RET_OUT_GOOD;
      skb_set_transport_header(skb, keys.control.thoff);

Nothing on the way from veth to OVS clears it. __dev_forward_skb2(),
skb_scrub_packet() and __netif_receive_skb_core() all leave a set
transport header in place.

parse_ipv6hdr() in net/openvswitch/flow.c also returns early for a later
fragment without touching it:

		if (frag_off) {
			key->ip.frag = OVS_FRAG_TYPE_LATER;
			key->ip.proto = NEXTHDR_FRAGMENT;
			return 0;
		}

So on an unfixed kernel, set_ipv6()->set_ipv6_addr()->
update_ipv6_checksum() calls skb_transport_offset() while the transport
header is already set. The check in skb_transport_header() doesn't fire:

	DEBUG_NET_WARN_ON_ONCE(!skb_transport_header_was_set(skb));

As a result, after equals before and test_ipv6_later_frag() returns 0.

The syzbot trace quoted in 3c6a4b11330f goes through
ovs_packet_cmd_execute(). There the skb is built from netlink with no
transport header. Does the test need to inject the packet through
OVS_PACKET_CMD_EXECUTE to cover this regression?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009090629.1694991-1-sahaj123.sc%40gmail.com

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-10-10  9:06 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-09  9:06 [PATCH net] selftests: openvswitch: Test SET on later IPv6 fragments Sahaj Chaudhari
2026-10-10  9:06 ` netdev-bot+sashiko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox