* [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:02 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.