1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
"""
Tests for RSS hashing on IPv6 Flow Label.
"""
import glob
import os
import socket
from lib.py import CmdExitFailure
from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge, ksft_in, \
ksft_not_in, ksft_raises, KsftSkipEx
from lib.py import bkg, cmd, defer, fd_read_timeout, rand_port
from lib.py import NetDrvEpEnv
def _check_system(cfg):
if not hasattr(socket, "SO_INCOMING_CPU"):
raise KsftSkipEx("socket.SO_INCOMING_CPU was added in Python 3.11")
qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))
if qcnt < 2:
raise KsftSkipEx(f"Local has only {qcnt} queues")
for f in [f"/sys/class/net/{cfg.ifname}/queues/rx-0/rps_flow_cnt",
f"/sys/class/net/{cfg.ifname}/queues/rx-0/rps_cpus"]:
try:
with open(f, 'r') as fp:
setting = fp.read().strip()
# CPU mask will be zeros and commas
if setting.replace("0", "").replace(",", ""):
raise KsftSkipEx(f"RPS/RFS is configured: {f}: {setting}")
except FileNotFoundError:
pass
# 1 is the default, if someone changed it we probably shouldn"t mess with it
af = cmd("cat /proc/sys/net/ipv6/auto_flowlabels", host=cfg.remote).stdout
if af.strip() != "1":
raise KsftSkipEx("Remote does not have auto_flowlabels enabled")
def _ethtool_get_cfg(cfg, fl_type):
descr = cmd(f"ethtool -n {cfg.ifname} rx-flow-hash {fl_type}").stdout
converter = {
"IP SA": "s",
"IP DA": "d",
"L3 proto": "t",
"L4 bytes 0 & 1 [TCP/UDP src port]": "f",
"L4 bytes 2 & 3 [TCP/UDP dst port]": "n",
"IPv6 Flow Label": "l",
}
ret = ""
for line in descr.split("\n")[1:-2]:
# if this raises we probably need to add more keys to converter above
ret += converter[line]
return ret
def _traffic(cfg, one_sock, one_cpu):
local_port = rand_port(socket.SOCK_DGRAM)
remote_port = rand_port(socket.SOCK_DGRAM)
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
sock.bind(("", local_port))
sock.connect((cfg.remote_addr_v["6"], 0))
if one_sock:
send = f"exec 5<>/dev/udp/{cfg.addr_v['6']}/{local_port}; " \
"for i in `seq 20`; do echo a >&5; sleep 0.02; done; exec 5>&-"
else:
send = "for i in `seq 20`; do echo a | socat -t0.02 - UDP6:" \
f"[{cfg.addr_v['6']}]:{local_port},sourceport={remote_port}; done"
cpus = set()
with bkg(send, shell=True, host=cfg.remote, exit_wait=True):
for _ in range(20):
fd_read_timeout(sock.fileno(), 1)
cpu = sock.getsockopt(socket.SOL_SOCKET, socket.SO_INCOMING_CPU)
cpus.add(cpu)
if one_cpu:
ksft_eq(len(cpus), 1,
f"{one_sock=} - expected one CPU, got traffic on: {cpus=}")
else:
ksft_ge(len(cpus), 2,
f"{one_sock=} - expected many CPUs, got traffic on: {cpus=}")
def test_rss_flow_label(cfg):
"""
Test hashing on IPv6 flow label. Send traffic over a single socket
and over multiple sockets. Depend on the remote having auto-label
enabled so that it randomizes the label per socket.
"""
cfg.require_ipver("6")
cfg.require_cmd("socat", remote=True)
_check_system(cfg)
# Enable flow label hashing for UDP6
initial = _ethtool_get_cfg(cfg, "udp6")
no_lbl = initial.replace("l", "")
if "l" not in initial:
try:
cmd(f"ethtool -N {cfg.ifname} rx-flow-hash udp6 l{no_lbl}")
except CmdExitFailure as exc:
raise KsftSkipEx("Device doesn't support Flow Label for UDP6") from exc
defer(cmd, f"ethtool -N {cfg.ifname} rx-flow-hash udp6 {initial}")
_traffic(cfg, one_sock=True, one_cpu=True)
_traffic(cfg, one_sock=False, one_cpu=False)
# Disable it, we should see no hashing (reset was already defer()ed)
cmd(f"ethtool -N {cfg.ifname} rx-flow-hash udp6 {no_lbl}")
_traffic(cfg, one_sock=False, one_cpu=True)
def _check_v4_flow_types(cfg):
for fl_type in ["tcp4", "udp4", "ah4", "esp4", "sctp4"]:
try:
cur = cmd(f"ethtool -n {cfg.ifname} rx-flow-hash {fl_type}").stdout
ksft_not_in("Flow Label", cur,
comment=f"{fl_type=} has Flow Label:" + cur)
except CmdExitFailure:
# Probably does not support this flow type
pass
def test_rss_flow_label_6only(cfg):
"""
Test interactions with IPv4 flow types. It should not be possible to set
IPv6 Flow Label hashing for an IPv4 flow type. The Flow Label should also
not appear in the IPv4 "current config".
"""
with ksft_raises(CmdExitFailure) as cm:
cmd(f"ethtool -N {cfg.ifname} rx-flow-hash tcp4 sdfnl")
ksft_in("Invalid argument", cm.exception.cmd.stderr)
_check_v4_flow_types(cfg)
# Try to enable Flow Labels and check again, in case it leaks thru
initial = _ethtool_get_cfg(cfg, "udp6")
changed = initial.replace("l", "") if "l" in initial else initial + "l"
cmd(f"ethtool -N {cfg.ifname} rx-flow-hash udp6 {changed}")
restore = defer(cmd, f"ethtool -N {cfg.ifname} rx-flow-hash udp6 {initial}")
_check_v4_flow_types(cfg)
restore.exec()
_check_v4_flow_types(cfg)
def main() -> None:
with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
ksft_run([test_rss_flow_label,
test_rss_flow_label_6only],
args=(cfg, ))
ksft_exit()
if __name__ == "__main__":
main()
|