Skip to content

Commit 70b2403

Browse files
committed
selftests: drv-net-hw: Add a test for symmetric RSS hash
JIRA: https://issues.redhat.com/browse/RHEL-75603 Conflicts: - I added small hunks that needed by the test from missing commit 7147713 ("selftests: drv-net: add a way to wait for a local process"). commit da87cab Author: Gal Pressman <gal@nvidia.com> Date: Mon Feb 24 19:44:16 2025 +0200 selftests: drv-net-hw: Add a test for symmetric RSS hash Add a selftest that verifies symmetric RSS hash is working as intended. The test runs iterations of traffic, swapping the src/dst UDP ports, and verifies that the same RX queue is receiving the traffic in both cases. Reviewed-by: Nimrod Oren <noren@nvidia.com> Signed-off-by: Gal Pressman <gal@nvidia.com> Link: https://patch.msgid.link/20250224174416.499070-5-gal@nvidia.com Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Mohammad Heib <mheib@redhat.com>
1 parent 3220ad1 commit 70b2403

File tree

3 files changed

+98
-2
lines changed

3 files changed

+98
-2
lines changed

tools/testing/selftests/drivers/net/hw/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ TEST_PROGS = \
1212
loopback.sh \
1313
pp_alloc_fail.py \
1414
rss_ctx.py \
15+
rss_input_xfrm.py \
1516
#
1617

1718
TEST_FILES := \
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/env python3
2+
# SPDX-License-Identifier: GPL-2.0
3+
4+
import multiprocessing
5+
import socket
6+
from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge, cmd, fd_read_timeout
7+
from lib.py import NetDrvEpEnv
8+
from lib.py import EthtoolFamily, NetdevFamily
9+
from lib.py import KsftSkipEx, KsftFailEx
10+
from lib.py import rand_port
11+
12+
13+
def traffic(cfg, local_port, remote_port, ipver):
14+
af_inet = socket.AF_INET if ipver == "4" else socket.AF_INET6
15+
sock = socket.socket(af_inet, socket.SOCK_DGRAM)
16+
sock.bind(("", local_port))
17+
sock.connect((cfg.remote_addr_v[ipver], remote_port))
18+
tgt = f"{ipver}:[{cfg.addr_v[ipver]}]:{local_port},sourceport={remote_port}"
19+
cmd("echo a | socat - UDP" + tgt, host=cfg.remote)
20+
fd_read_timeout(sock.fileno(), 5)
21+
return sock.getsockopt(socket.SOL_SOCKET, socket.SO_INCOMING_CPU)
22+
23+
24+
def test_rss_input_xfrm(cfg, ipver):
25+
"""
26+
Test symmetric input_xfrm.
27+
If symmetric RSS hash is configured, send traffic twice, swapping the
28+
src/dst UDP ports, and verify that the same queue is receiving the traffic
29+
in both cases (IPs are constant).
30+
"""
31+
32+
if multiprocessing.cpu_count() < 2:
33+
raise KsftSkipEx("Need at least two CPUs to test symmetric RSS hash")
34+
35+
input_xfrm = cfg.ethnl.rss_get(
36+
{'header': {'dev-name': cfg.ifname}}).get('input_xfrm')
37+
38+
# Check for symmetric xor/or-xor
39+
if not input_xfrm or (input_xfrm != 1 and input_xfrm != 2):
40+
raise KsftSkipEx("Symmetric RSS hash not requested")
41+
42+
cpus = set()
43+
successful = 0
44+
for _ in range(100):
45+
try:
46+
port1 = rand_port(socket.SOCK_DGRAM)
47+
port2 = rand_port(socket.SOCK_DGRAM)
48+
cpu1 = traffic(cfg, port1, port2, ipver)
49+
cpu2 = traffic(cfg, port2, port1, ipver)
50+
cpus.update([cpu1, cpu2])
51+
ksft_eq(
52+
cpu1, cpu2, comment=f"Received traffic on different cpus with ports ({port1 = }, {port2 = }) while symmetric hash is configured")
53+
54+
successful += 1
55+
if successful == 10:
56+
break
57+
except:
58+
continue
59+
else:
60+
raise KsftFailEx("Failed to run traffic")
61+
62+
ksft_ge(len(cpus), 2,
63+
comment=f"Received traffic on less than two cpus {cpus = }")
64+
65+
66+
def test_rss_input_xfrm_ipv4(cfg):
67+
cfg.require_ipver("4")
68+
test_rss_input_xfrm(cfg, "4")
69+
70+
71+
def test_rss_input_xfrm_ipv6(cfg):
72+
cfg.require_ipver("6")
73+
test_rss_input_xfrm(cfg, "6")
74+
75+
76+
def main() -> None:
77+
with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
78+
cfg.ethnl = EthtoolFamily()
79+
cfg.netdevnl = NetdevFamily()
80+
81+
ksft_run([test_rss_input_xfrm_ipv4, test_rss_input_xfrm_ipv6],
82+
args=(cfg, ))
83+
ksft_exit()
84+
85+
86+
if __name__ == "__main__":
87+
main()

tools/testing/selftests/net/lib/py/utils.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import errno
44
import json as _json
5+
import os
56
import random
67
import re
8+
import select
79
import socket
810
import subprocess
911
import time
@@ -14,6 +16,12 @@ def __init__(self, msg, cmd_obj):
1416
super().__init__(msg)
1517
self.cmd = cmd_obj
1618

19+
def fd_read_timeout(fd, timeout):
20+
rlist, _, _ = select.select([fd], [], [], timeout)
21+
if rlist:
22+
return os.read(fd, 1024)
23+
else:
24+
raise TimeoutError("Timeout waiting for fd read")
1725

1826
class cmd:
1927
def __init__(self, comm, shell=True, fail=True, ns=None, background=False, host=None, timeout=5):
@@ -123,11 +131,11 @@ def ethtool(args, json=None, ns=None, host=None):
123131
return tool('ethtool', args, json=json, ns=ns, host=host)
124132

125133

126-
def rand_port():
134+
def rand_port(type=socket.SOCK_STREAM):
127135
"""
128136
Get a random unprivileged port.
129137
"""
130-
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
138+
with socket.socket(socket.AF_INET6, type) as s:
131139
s.bind(("", 0))
132140
return s.getsockname()[1]
133141

0 commit comments

Comments
 (0)