Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next v6 0/2] selftests/net/openvswitch: add SCTP flow key test
@ 2026-07-24 15:06 Minxi Hou
  2026-07-24 15:06 ` [PATCH net-next v6 1/2] selftests/net/openvswitch: add SCTP flow key parsing to ovs-dpctl.py Minxi Hou
  2026-07-24 15:06 ` [PATCH net-next v6 2/2] selftests/net/openvswitch: add SCTP flow key test Minxi Hou
  0 siblings, 2 replies; 4+ messages in thread
From: Minxi Hou @ 2026-07-24 15:06 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, kuba, pabeni, aconole, echaudro, i.maximets,
	i.maximets, dev, linux-kselftest, shuah, horms, linux-kernel,
	Minxi Hou

v5 failed in NIPA: the kernel rejected the SCTP-keyed flow with
EINVAL(22) in add_flow. Root cause: v5 installed flows containing
sctp() via ovs-dpctl.py without registering OVS_KEY_ATTR_SCTP in its
flow-string parse table, so the parser silently dropped the L4 key;
the kernel mandates the SCTP key when ipv4.proto == IPPROTO_SCTP
(match_validate() in flow_netlink.c) and returns -EINVAL.

The parse-table entry was present in v1-v4 and was accidentally
dropped in the v4->v5 respin. v6 splits the fix from the test so each
patch stands on its own: patch 1 restores SCTP parsing in ovs-dpctl.py
(the NIPA fix) with unit tests, patch 2 adds the SCTP flow key test
(same goal as v5).

Tested with vng on x86_64: all OVS selftests pass, including the new
sctp_connect_v4.

v6:
  - split into 2 patches: ovs-dpctl.py parse fix + shell test
  - restore OVS_KEY_ATTR_SCTP parse-table entry + unit tests (NIPA fix)
  - listener readiness via /proc/net/sctp/eps (ss needs sctp_diag)
  - add timeout 3 to socat clients (unidirectional connect hangs)
  - restart listeners with kill -TERM + kill -0 poll (wait can hang)
v5: https://lore.kernel.org/netdev/20260723084203.3483560-1-houminxi@gmail.com/
  - switch to socat per Kicinski; NIPA FAIL: EINVAL in add_flow
    (parse entry accidentally dropped in this respin)
v4: https://lore.kernel.org/netdev/20260719162657.3263089-1-houminxi@gmail.com/
  - rebase after ICMPv6 test merged; nc --sctp detection nit
v3: https://lore.kernel.org/netdev/20260715015446.530018-1-houminxi@gmail.com/
  - switch ncat -> nc with skip if nc lacks SCTP support
v2: https://lore.kernel.org/netdev/20260707034718.2717982-1-houminxi@gmail.com/
  - replace sleep after spawn with ovs_wait on listener stderr
v1: https://lore.kernel.org/netdev/20260702090908.1253688-1-houminxi@gmail.com/

Minxi Hou (2):
  selftests/net/openvswitch: add SCTP flow key parsing to ovs-dpctl.py
  selftests/net/openvswitch: add SCTP flow key test

 .../selftests/net/openvswitch/openvswitch.sh  | 121 ++++++++++++++++
 .../selftests/net/openvswitch/ovs-dpctl.py    | 129 ++++++++++++++++++
 2 files changed, 250 insertions(+)

-- 
2.55.0


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

* [PATCH net-next v6 1/2] selftests/net/openvswitch: add SCTP flow key parsing to ovs-dpctl.py
  2026-07-24 15:06 [PATCH net-next v6 0/2] selftests/net/openvswitch: add SCTP flow key test Minxi Hou
@ 2026-07-24 15:06 ` Minxi Hou
  2026-07-24 19:02   ` [net-next,v6,1/2] " Aaron Conole
  2026-07-24 15:06 ` [PATCH net-next v6 2/2] selftests/net/openvswitch: add SCTP flow key test Minxi Hou
  1 sibling, 1 reply; 4+ messages in thread
From: Minxi Hou @ 2026-07-24 15:06 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, kuba, pabeni, aconole, echaudro, i.maximets,
	i.maximets, dev, linux-kselftest, shuah, horms, linux-kernel,
	Minxi Hou

The ovskey flow-string parser has no OVS_KEY_ATTR_SCTP entry, so a
flow string containing sctp(src=.../dst=...) parses without error but
silently drops the L4 key. The resulting flow carries only
ipv4(proto=132), and the kernel rejects it: match_validate() in
flow_netlink.c requires OVS_KEY_ATTR_SCTP when the IP protocol is
IPPROTO_SCTP and returns -EINVAL for the missing key.

Register OVS_KEY_ATTR_SCTP in the parse table and add unit tests
(runnable via ovs-dpctl.py --test) covering SCTP and the neighboring
TCP/UDP/ICMP/IPv4 key parsers.

Signed-off-by: Minxi Hou <houminxi@gmail.com>
---
 .../selftests/net/openvswitch/ovs-dpctl.py    | 129 ++++++++++++++++++
 1 file changed, 129 insertions(+)

diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
index f3edd198223f..633af10d7e8a 100644
--- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
+++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
@@ -15,6 +15,7 @@ import struct
 import sys
 import time
 import types
+import unittest
 import uuid
 
 try:
@@ -1984,6 +1985,11 @@ class ovskey(nla):
                 "udp",
                 ovskey.ovs_key_udp,
             ),
+            (
+                "OVS_KEY_ATTR_SCTP",
+                "sctp",
+                ovskey.ovs_key_sctp,
+            ),
             (
                 "OVS_KEY_ATTR_ICMP",
                 "icmp",
@@ -3165,5 +3171,128 @@ def main(argv):
     return 0
 
 
+def _init_ovskey_nlas():
+    """Initialize required NLA classes for ovskey parsing."""
+    nlmsg_atoms.encap_ovskey = encap_ovskey
+    nlmsg_atoms.ovskey = ovskey
+    nlmsg_atoms.ovsactions = ovsactions
+
+
+def _parse_flow(flowstr):
+    """Parse a flow string and return the key."""
+    key = ovskey()
+    key["attrs"] = []
+    key.parse(flowstr)
+    return key
+
+
+def _find_attr(key, attr_name):
+    """Find an attribute in parsed key."""
+    for attr in key["attrs"]:
+        if attr[0] == attr_name:
+            return attr[1]
+    return None
+
+
+class TestOvsKeyParse(unittest.TestCase):
+    """Unit tests for ovskey.parse() flow string parsing."""
+
+    @classmethod
+    def setUpClass(cls):
+        _init_ovskey_nlas()
+
+    def test_sctp_dst(self):
+        """Test SCTP destination port parsing."""
+        key = _parse_flow("sctp(dst=4443)")
+        attr = _find_attr(key, "OVS_KEY_ATTR_SCTP")
+        self.assertIsNotNone(attr, "SCTP key not found")
+        self.assertEqual(attr["dst"], 4443)
+
+    def test_sctp_src(self):
+        """Test SCTP source port parsing."""
+        key = _parse_flow("sctp(src=4443)")
+        attr = _find_attr(key, "OVS_KEY_ATTR_SCTP")
+        self.assertIsNotNone(attr, "SCTP key not found")
+        self.assertEqual(attr["src"], 4443)
+
+    def test_sctp_src_and_dst(self):
+        """Test SCTP source and destination port parsing."""
+        key = _parse_flow("sctp(src=1234,dst=5678)")
+        attr = _find_attr(key, "OVS_KEY_ATTR_SCTP")
+        self.assertIsNotNone(attr, "SCTP key not found")
+        self.assertEqual(attr["src"], 1234)
+        self.assertEqual(attr["dst"], 5678)
+
+    def test_tcp_dst(self):
+        """Test TCP destination port parsing."""
+        key = _parse_flow("tcp(dst=80)")
+        attr = _find_attr(key, "OVS_KEY_ATTR_TCP")
+        self.assertIsNotNone(attr, "TCP key not found")
+        self.assertEqual(attr["dst"], 80)
+
+    def test_udp_dst(self):
+        """Test UDP destination port parsing."""
+        key = _parse_flow("udp(dst=53)")
+        attr = _find_attr(key, "OVS_KEY_ATTR_UDP")
+        self.assertIsNotNone(attr, "UDP key not found")
+        self.assertEqual(attr["dst"], 53)
+
+    def test_icmp_type_code(self):
+        """Test ICMP type and code parsing."""
+        key = _parse_flow("icmp(type=8,code=0)")
+        attr = _find_attr(key, "OVS_KEY_ATTR_ICMP")
+        self.assertIsNotNone(attr, "ICMP key not found")
+        self.assertEqual(attr["type"], 8)
+        self.assertEqual(attr["code"], 0)
+
+    def test_ipv4_proto(self):
+        """Test IPv4 protocol parsing."""
+        key = _parse_flow("eth_type(0x0800),ipv4(proto=132)")
+        attr = _find_attr(key, "OVS_KEY_ATTR_IPV4")
+        self.assertIsNotNone(attr, "IPv4 key not found")
+        self.assertEqual(attr["proto"], 132)
+
+    def test_eth_type(self):
+        """Test Ethernet type parsing."""
+        key = _parse_flow("eth_type(0x0800)")
+        attr = _find_attr(key, "OVS_KEY_ATTR_ETHERTYPE")
+        self.assertIsNotNone(attr, "EthType key not found")
+        self.assertEqual(attr, 0x0800)
+
+    def test_full_sctp_flow(self):
+        """Test complete SCTP flow string parsing."""
+        flowstr = (
+            "in_port(1),eth(),eth_type(0x0800),"
+            "ipv4(proto=132),sctp(dst=4443)"
+        )
+        key = _parse_flow(flowstr)
+        in_port = _find_attr(key, "OVS_KEY_ATTR_IN_PORT")
+        self.assertEqual(in_port, 1)
+        sctp = _find_attr(key, "OVS_KEY_ATTR_SCTP")
+        self.assertIsNotNone(sctp, "SCTP key not found")
+        self.assertEqual(sctp["dst"], 4443)
+
+    def test_sctp_roundtrip(self):
+        """Test parse -> dpstr -> parse keeps the SCTP key."""
+        key = _parse_flow("sctp(dst=4443)")
+        key2 = ovskey()
+        key2["attrs"] = []
+        remainder = key2.parse(key.dpstr())
+        self.assertEqual(remainder, "", "round-trip left unparsed text")
+        attr = _find_attr(key2, "OVS_KEY_ATTR_SCTP")
+        self.assertIsNotNone(attr, "SCTP key lost in round-trip")
+        self.assertEqual(attr["dst"], 4443)
+
+
+def test_ovskey_parse():
+    """Test ovskey.parse() method for various flow key types."""
+    suite = unittest.TestLoader().loadTestsFromTestCase(TestOvsKeyParse)
+    runner = unittest.TextTestRunner(verbosity=2)
+    result = runner.run(suite)
+    return 0 if result.wasSuccessful() else 1
+
+
 if __name__ == "__main__":
+    if len(sys.argv) > 1 and sys.argv[1] == "--test":
+        sys.exit(test_ovskey_parse())
     sys.exit(main(sys.argv))
-- 
2.55.0


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

* [PATCH net-next v6 2/2] selftests/net/openvswitch: add SCTP flow key test
  2026-07-24 15:06 [PATCH net-next v6 0/2] selftests/net/openvswitch: add SCTP flow key test Minxi Hou
  2026-07-24 15:06 ` [PATCH net-next v6 1/2] selftests/net/openvswitch: add SCTP flow key parsing to ovs-dpctl.py Minxi Hou
@ 2026-07-24 15:06 ` Minxi Hou
  1 sibling, 0 replies; 4+ messages in thread
From: Minxi Hou @ 2026-07-24 15:06 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, kuba, pabeni, aconole, echaudro, i.maximets,
	i.maximets, dev, linux-kselftest, shuah, horms, linux-kernel,
	Minxi Hou

Add test_sctp_connect_v4() to verify OVS can match on SCTP flow keys
(sctp src/dst port).

The test sets up client and server namespaces connected through an
OVS bridge, installs port-keyed flows, and verifies:
  - sctp(dst=4443) matches client-to-server INIT
  - sctp(src=4443) matches server-to-client INIT-ACK
  - removing flows drops the connection
  - reinstalling flows restores connectivity

The listener readiness probe reads /proc/net/sctp/eps instead of ss:
ss requires the sctp_diag interface, which is not enabled on all
kernels, while /proc/net/sctp/eps exists whenever SCTP is loaded.

Signed-off-by: Minxi Hou <houminxi@gmail.com>
---
 .../selftests/net/openvswitch/openvswitch.sh  | 121 ++++++++++++++++++
 1 file changed, 121 insertions(+)

diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh
index 853dbc1b00d7..b448324527d8 100755
--- a/tools/testing/selftests/net/openvswitch/openvswitch.sh
+++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh
@@ -34,6 +34,7 @@ tests="
 	action_set				set: SET action rewrites fields
 	trunc					trunc: output truncation
 	icmpv6					icmpv6: ICMPv6 echo type match
+	sctp_connect_v4				sctp: SCTP flow key matching
 	psample					psample: Sampling packets with psample"
 
 info() {
@@ -611,6 +612,126 @@ test_icmpv6() {
 	return 0
 }
 
+# Check for an SCTP endpoint via /proc, which works without sctp_diag.
+sctp_eps_has() {
+	ip netns exec "$1" awk -v p="$2" '$6==p' /proc/net/sctp/eps | grep -q .
+}
+
+# sctp_connect_v4 test
+# - sctp(dst=4443) matches client-to-server INIT
+# - sctp(src=4443) matches server-to-client INIT-ACK
+# - remove flows and verify connection fails, reinstall and recover
+test_sctp_connect_v4() {
+	local t="test_sctp_connect_v4"
+	local srv_ip=172.31.110.20
+
+	modprobe -q sctp 2>/dev/null || return "$ksft_skip"
+	socat -V 2>&1 | grep -q "define WITH_SCTP" || return "$ksft_skip"
+
+	sbx_add "$t" || return $?
+	ovs_add_dp "$t" sctp4 || return 1
+
+	info "create namespaces"
+	for ns in client server; do
+		ovs_add_netns_and_veths "$t" "sctp4" "$ns" \
+		    "${ns:0:1}0" "${ns:0:1}1" || return 1
+	done
+
+	ip netns exec client ip addr add 172.31.110.10/24 dev c1
+	ip netns exec client ip link set c1 up
+	ip netns exec server ip addr add "${srv_ip}/24" dev s1
+	ip netns exec server ip link set s1 up
+
+	# Probe: check if kernel supports sctp flow key.
+	ovs_add_flow "$t" sctp4 \
+	    'in_port(1),eth(),eth_type(0x0800),ipv4(proto=132),sctp(dst=4443)' \
+	    '2' &>/dev/null
+	if [ $? -ne 0 ]; then
+		info "no support for sctp key - skipping"
+		ovs_exit_sig
+		return $ksft_skip
+	fi
+	ovs_del_flows "$t" sctp4
+
+	# ARP forwarding
+	ovs_add_flow "$t" sctp4 \
+	    'in_port(1),eth(),eth_type(0x0806),arp()' \
+	    '2' || return 1
+	ovs_add_flow "$t" sctp4 \
+	    'in_port(2),eth(),eth_type(0x0806),arp()' \
+	    '1' || return 1
+
+	# SCTP port matching: dst for request, src for reply
+	ovs_add_flow "$t" sctp4 \
+	    'in_port(1),eth(),eth_type(0x0800),ipv4(proto=132),sctp(dst=4443)' \
+	    '2' || return 1
+	ovs_add_flow "$t" sctp4 \
+	    'in_port(2),eth(),eth_type(0x0800),ipv4(proto=132),sctp(src=4443)' \
+	    '1' || return 1
+
+	ovs_netns_spawn_daemon "$t" "server" \
+	    socat -u SCTP4-LISTEN:4443 STDOUT
+	local server_pid="$pid"
+	ovs_wait sctp_eps_has server 4443 || return 1
+
+	info "verify SCTP association with port-keyed flows"
+	ovs_sbx "$t" ip netns exec client \
+	    timeout 3 socat -u STDIN "SCTP4-CONNECT:${srv_ip}:4443" </dev/null \
+	    || return 1
+
+	ovs_del_flows "$t" sctp4
+
+	info "verify connection fails without flows"
+	ovs_add_flow "$t" sctp4 \
+	    'in_port(1),eth(),eth_type(0x0806),arp()' \
+	    '2' || return 1
+	ovs_add_flow "$t" sctp4 \
+	    'in_port(2),eth(),eth_type(0x0806),arp()' \
+	    '1' || return 1
+
+	kill -TERM "$server_pid" 2>/dev/null
+	local i=0
+	while kill -0 "$server_pid" 2>/dev/null && [ "$i" -lt 5 ]; do
+	    sleep 0.2
+	    i=$((i + 1))
+	done
+	ovs_netns_spawn_daemon "$t" "server" \
+	    socat -u SCTP4-LISTEN:4443 STDOUT
+	server_pid="$pid"
+	ovs_wait sctp_eps_has server 4443 || return 1
+
+	ovs_sbx "$t" ip netns exec client \
+	    timeout 3 socat -u STDIN "SCTP4-CONNECT:${srv_ip}:4443" </dev/null \
+	    >/dev/null 2>&1 \
+	    && { info "connection should fail without flows"
+	         return 1; }
+
+	info "reinstall flows and verify recovery"
+	ovs_add_flow "$t" sctp4 \
+	    'in_port(1),eth(),eth_type(0x0800),ipv4(proto=132),sctp(dst=4443)' \
+	    '2' || return 1
+	ovs_add_flow "$t" sctp4 \
+	    'in_port(2),eth(),eth_type(0x0800),ipv4(proto=132),sctp(src=4443)' \
+	    '1' || return 1
+
+	kill -TERM "$server_pid" 2>/dev/null
+	i=0
+	while kill -0 "$server_pid" 2>/dev/null && [ "$i" -lt 5 ]; do
+	    sleep 0.2
+	    i=$((i + 1))
+	done
+	ovs_netns_spawn_daemon "$t" "server" \
+	    socat -u SCTP4-LISTEN:4443 STDOUT
+	server_pid="$pid"
+	ovs_wait sctp_eps_has server 4443 || return 1
+
+	ovs_sbx "$t" ip netns exec client \
+	    timeout 3 socat -u STDIN "SCTP4-CONNECT:${srv_ip}:4443" </dev/null \
+	    || return 1
+
+	return 0
+}
+
 # psample test
 # - use psample to observe packets
 test_psample() {
-- 
2.55.0


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

* Re: [net-next,v6,1/2] selftests/net/openvswitch: add SCTP flow key parsing to ovs-dpctl.py
  2026-07-24 15:06 ` [PATCH net-next v6 1/2] selftests/net/openvswitch: add SCTP flow key parsing to ovs-dpctl.py Minxi Hou
@ 2026-07-24 19:02   ` Aaron Conole
  0 siblings, 0 replies; 4+ messages in thread
From: Aaron Conole @ 2026-07-24 19:02 UTC (permalink / raw)
  To: houminxi
  Cc: netdev, davem, edumazet, kuba, pabeni, echaudro, i.maximets,
	i.maximets, dev, linux-kselftest, shuah, horms, linux-kernel

Minxi Hou <houminxi@gmail.com> writes:

> The ovskey flow-string parser has no OVS_KEY_ATTR_SCTP entry, so a
> flow string containing sctp(src=.../dst=...) parses without error but
> silently drops the L4 key. The resulting flow carries only
> ipv4(proto=132), and the kernel rejects it: match_validate() in
> flow_netlink.c requires OVS_KEY_ATTR_SCTP when the IP protocol is
> IPPROTO_SCTP and returns -EINVAL for the missing key.
> 
> Register OVS_KEY_ATTR_SCTP in the parse table and add unit tests
> (runnable via ovs-dpctl.py --test) covering SCTP and the neighboring
> TCP/UDP/ICMP/IPv4 key parsers.
> 
> Signed-off-by: Minxi Hou <houminxi@gmail.com>
> ---
>  .../selftests/net/openvswitch/ovs-dpctl.py    | 129 ++++++++++++++++++
>  1 file changed, 129 insertions(+)

[...]

> @@ -3165,5 +3171,128 @@ def main(argv):
>      return 0
>
>
> +def _init_ovskey_nlas():
> +    """Initialize required NLA classes for ovskey parsing."""
> +    nlmsg_atoms.encap_ovskey = encap_ovskey
> +    nlmsg_atoms.ovskey = ovskey
> +    nlmsg_atoms.ovsactions = ovsactions
> +
> +
> +def _parse_flow(flowstr):
> +    """Parse a flow string and return the key."""
> +    key = ovskey()
> +    key["attrs"] = []
> +    key.parse(flowstr)
> +    return key
> +
> +
> +def _find_attr(key, attr_name):
> +    """Find an attribute in parsed key."""
> +    for attr in key["attrs"]:
> +        if attr[0] == attr_name:
> +            return attr[1]
> +    return None
> +
> +
> +class TestOvsKeyParse(unittest.TestCase):
> +    """Unit tests for ovskey.parse() flow string parsing."""

[...]

I don't think we need unit tests for the unit test... That seems like
extra working making tests all the way down.

Maybe someone disagrees, but to me a test case here feels a bit
excessive.


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

end of thread, other threads:[~2026-07-24 19:03 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-24 15:06 [PATCH net-next v6 0/2] selftests/net/openvswitch: add SCTP flow key test Minxi Hou
2026-07-24 15:06 ` [PATCH net-next v6 1/2] selftests/net/openvswitch: add SCTP flow key parsing to ovs-dpctl.py Minxi Hou
2026-07-24 19:02   ` [net-next,v6,1/2] " Aaron Conole
2026-07-24 15:06 ` [PATCH net-next v6 2/2] selftests/net/openvswitch: add SCTP flow key test Minxi Hou

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