Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/testing/selftests/drivers/net/hw/rss_api.py
26295 views
1
#!/usr/bin/env python3
2
# SPDX-License-Identifier: GPL-2.0
3
4
"""
5
API level tests for RSS (mostly Netlink vs IOCTL).
6
"""
7
8
import errno
9
import glob
10
import random
11
from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_is, ksft_ne, ksft_raises
12
from lib.py import KsftSkipEx, KsftFailEx
13
from lib.py import defer, ethtool, CmdExitFailure
14
from lib.py import EthtoolFamily, NlError
15
from lib.py import NetDrvEnv
16
17
18
def _require_2qs(cfg):
19
qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))
20
if qcnt < 2:
21
raise KsftSkipEx(f"Local has only {qcnt} queues")
22
return qcnt
23
24
25
def _ethtool_create(cfg, act, opts):
26
output = ethtool(f"{act} {cfg.ifname} {opts}").stdout
27
# Output will be something like: "New RSS context is 1" or
28
# "Added rule with ID 7", we want the integer from the end
29
return int(output.split()[-1])
30
31
32
def _ethtool_get_cfg(cfg, fl_type, to_nl=False):
33
descr = ethtool(f"-n {cfg.ifname} rx-flow-hash {fl_type}").stdout
34
35
if to_nl:
36
converter = {
37
"IP SA": "ip-src",
38
"IP DA": "ip-dst",
39
"L4 bytes 0 & 1 [TCP/UDP src port]": "l4-b-0-1",
40
"L4 bytes 2 & 3 [TCP/UDP dst port]": "l4-b-2-3",
41
}
42
43
ret = set()
44
else:
45
converter = {
46
"IP SA": "s",
47
"IP DA": "d",
48
"L3 proto": "t",
49
"L4 bytes 0 & 1 [TCP/UDP src port]": "f",
50
"L4 bytes 2 & 3 [TCP/UDP dst port]": "n",
51
}
52
53
ret = ""
54
55
for line in descr.split("\n")[1:-2]:
56
# if this raises we probably need to add more keys to converter above
57
if to_nl:
58
ret.add(converter[line])
59
else:
60
ret += converter[line]
61
return ret
62
63
64
def test_rxfh_nl_set_fail(cfg):
65
"""
66
Test error path of Netlink SET.
67
"""
68
_require_2qs(cfg)
69
70
ethnl = EthtoolFamily()
71
ethnl.ntf_subscribe("monitor")
72
73
with ksft_raises(NlError):
74
ethnl.rss_set({"header": {"dev-name": "lo"},
75
"indir": None})
76
77
with ksft_raises(NlError):
78
ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
79
"indir": [100000]})
80
ntf = next(ethnl.poll_ntf(duration=0.2), None)
81
ksft_is(ntf, None)
82
83
84
def test_rxfh_nl_set_indir(cfg):
85
"""
86
Test setting indirection table via Netlink.
87
"""
88
qcnt = _require_2qs(cfg)
89
90
# Test some SETs with a value
91
reset = defer(cfg.ethnl.rss_set,
92
{"header": {"dev-index": cfg.ifindex}, "indir": None})
93
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
94
"indir": [1]})
95
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
96
ksft_eq(set(rss.get("indir", [-1])), {1})
97
98
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
99
"indir": [0, 1]})
100
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
101
ksft_eq(set(rss.get("indir", [-1])), {0, 1})
102
103
# Make sure we can't set the queue count below max queue used
104
with ksft_raises(CmdExitFailure):
105
ethtool(f"-L {cfg.ifname} combined 0 rx 1")
106
with ksft_raises(CmdExitFailure):
107
ethtool(f"-L {cfg.ifname} combined 1 rx 0")
108
109
# Test reset back to default
110
reset.exec()
111
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
112
ksft_eq(set(rss.get("indir", [-1])), set(range(qcnt)))
113
114
115
def test_rxfh_nl_set_indir_ctx(cfg):
116
"""
117
Test setting indirection table for a custom context via Netlink.
118
"""
119
_require_2qs(cfg)
120
121
# Get setting for ctx 0, we'll make sure they don't get clobbered
122
dflt = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
123
124
# Create context
125
ctx_id = _ethtool_create(cfg, "-X", "context new")
126
defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
127
128
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
129
"context": ctx_id, "indir": [1]})
130
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex},
131
"context": ctx_id})
132
ksft_eq(set(rss.get("indir", [-1])), {1})
133
134
ctx0 = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
135
ksft_eq(ctx0, dflt)
136
137
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
138
"context": ctx_id, "indir": [0, 1]})
139
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex},
140
"context": ctx_id})
141
ksft_eq(set(rss.get("indir", [-1])), {0, 1})
142
143
ctx0 = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
144
ksft_eq(ctx0, dflt)
145
146
# Make sure we can't set the queue count below max queue used
147
with ksft_raises(CmdExitFailure):
148
ethtool(f"-L {cfg.ifname} combined 0 rx 1")
149
with ksft_raises(CmdExitFailure):
150
ethtool(f"-L {cfg.ifname} combined 1 rx 0")
151
152
153
def test_rxfh_indir_ntf(cfg):
154
"""
155
Check that Netlink notifications are generated when RSS indirection
156
table was modified.
157
"""
158
_require_2qs(cfg)
159
160
ethnl = EthtoolFamily()
161
ethnl.ntf_subscribe("monitor")
162
163
ethtool(f"--disable-netlink -X {cfg.ifname} weight 0 1")
164
reset = defer(ethtool, f"-X {cfg.ifname} default")
165
166
ntf = next(ethnl.poll_ntf(duration=0.2), None)
167
if ntf is None:
168
raise KsftFailEx("No notification received")
169
ksft_eq(ntf["name"], "rss-ntf")
170
ksft_eq(set(ntf["msg"]["indir"]), {1})
171
172
reset.exec()
173
ntf = next(ethnl.poll_ntf(duration=0.2), None)
174
if ntf is None:
175
raise KsftFailEx("No notification received after reset")
176
ksft_eq(ntf["name"], "rss-ntf")
177
ksft_is(ntf["msg"].get("context"), None)
178
ksft_ne(set(ntf["msg"]["indir"]), {1})
179
180
181
def test_rxfh_indir_ctx_ntf(cfg):
182
"""
183
Check that Netlink notifications are generated when RSS indirection
184
table was modified on an additional RSS context.
185
"""
186
_require_2qs(cfg)
187
188
ctx_id = _ethtool_create(cfg, "-X", "context new")
189
defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
190
191
ethnl = EthtoolFamily()
192
ethnl.ntf_subscribe("monitor")
193
194
ethtool(f"--disable-netlink -X {cfg.ifname} context {ctx_id} weight 0 1")
195
196
ntf = next(ethnl.poll_ntf(duration=0.2), None)
197
if ntf is None:
198
raise KsftFailEx("No notification received")
199
ksft_eq(ntf["name"], "rss-ntf")
200
ksft_eq(ntf["msg"].get("context"), ctx_id)
201
ksft_eq(set(ntf["msg"]["indir"]), {1})
202
203
204
def test_rxfh_nl_set_key(cfg):
205
"""
206
Test setting hashing key via Netlink.
207
"""
208
209
dflt = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
210
defer(cfg.ethnl.rss_set,
211
{"header": {"dev-index": cfg.ifindex},
212
"hkey": dflt["hkey"], "indir": None})
213
214
# Empty key should error out
215
with ksft_raises(NlError) as cm:
216
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
217
"hkey": None})
218
ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.hkey')
219
220
# Set key to random
221
mod = random.randbytes(len(dflt["hkey"]))
222
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
223
"hkey": mod})
224
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
225
ksft_eq(rss.get("hkey", [-1]), mod)
226
227
# Set key to random and indir tbl to something at once
228
mod = random.randbytes(len(dflt["hkey"]))
229
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
230
"indir": [0, 1], "hkey": mod})
231
rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
232
ksft_eq(rss.get("hkey", [-1]), mod)
233
ksft_eq(set(rss.get("indir", [-1])), {0, 1})
234
235
236
def test_rxfh_fields(cfg):
237
"""
238
Test reading Rx Flow Hash over Netlink.
239
"""
240
241
flow_types = ["tcp4", "tcp6", "udp4", "udp6"]
242
ethnl = EthtoolFamily()
243
244
cfg_nl = ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
245
for fl_type in flow_types:
246
one = _ethtool_get_cfg(cfg, fl_type, to_nl=True)
247
ksft_eq(one, cfg_nl["flow-hash"][fl_type],
248
comment="Config for " + fl_type)
249
250
251
def test_rxfh_fields_set(cfg):
252
""" Test configuring Rx Flow Hash over Netlink. """
253
254
flow_types = ["tcp4", "tcp6", "udp4", "udp6"]
255
256
# Collect current settings
257
cfg_old = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
258
# symmetric hashing is config-order-sensitive make sure we leave
259
# symmetric mode, or make the flow-hash sym-compatible first
260
changes = [{"flow-hash": cfg_old["flow-hash"],},
261
{"input-xfrm": cfg_old.get("input-xfrm", {}),}]
262
if cfg_old.get("input-xfrm"):
263
changes = list(reversed(changes))
264
for old in changes:
265
defer(cfg.ethnl.rss_set, {"header": {"dev-index": cfg.ifindex},} | old)
266
267
# symmetric hashing prevents some of the configs below
268
if cfg_old.get("input-xfrm"):
269
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
270
"input-xfrm": {}})
271
272
for fl_type in flow_types:
273
cur = _ethtool_get_cfg(cfg, fl_type)
274
if cur == "sdfn":
275
change_nl = {"ip-src", "ip-dst"}
276
change_ic = "sd"
277
else:
278
change_nl = {"l4-b-0-1", "l4-b-2-3", "ip-src", "ip-dst"}
279
change_ic = "sdfn"
280
281
cfg.ethnl.rss_set({
282
"header": {"dev-index": cfg.ifindex},
283
"flow-hash": {fl_type: change_nl}
284
})
285
reset = defer(ethtool, f"--disable-netlink -N {cfg.ifname} "
286
f"rx-flow-hash {fl_type} {cur}")
287
288
cfg_nl = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
289
ksft_eq(change_nl, cfg_nl["flow-hash"][fl_type],
290
comment=f"Config for {fl_type} over Netlink")
291
cfg_ic = _ethtool_get_cfg(cfg, fl_type)
292
ksft_eq(change_ic, cfg_ic,
293
comment=f"Config for {fl_type} over IOCTL")
294
295
reset.exec()
296
cfg_nl = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
297
ksft_eq(cfg_old["flow-hash"][fl_type], cfg_nl["flow-hash"][fl_type],
298
comment=f"Un-config for {fl_type} over Netlink")
299
cfg_ic = _ethtool_get_cfg(cfg, fl_type)
300
ksft_eq(cur, cfg_ic, comment=f"Un-config for {fl_type} over IOCTL")
301
302
# Try to set multiple at once, the defer was already installed at the start
303
change = {"ip-src"}
304
if change == cfg_old["flow-hash"]["tcp4"]:
305
change = {"ip-dst"}
306
cfg.ethnl.rss_set({
307
"header": {"dev-index": cfg.ifindex},
308
"flow-hash": {x: change for x in flow_types}
309
})
310
311
cfg_nl = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
312
for fl_type in flow_types:
313
ksft_eq(change, cfg_nl["flow-hash"][fl_type],
314
comment=f"multi-config for {fl_type} over Netlink")
315
316
317
def test_rxfh_fields_set_xfrm(cfg):
318
""" Test changing Rx Flow Hash vs xfrm_input at once. """
319
320
def set_rss(cfg, xfrm, fh):
321
cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
322
"input-xfrm": xfrm, "flow-hash": fh})
323
324
# Install the reset handler
325
cfg_old = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
326
# symmetric hashing is config-order-sensitive make sure we leave
327
# symmetric mode, or make the flow-hash sym-compatible first
328
changes = [{"flow-hash": cfg_old["flow-hash"],},
329
{"input-xfrm": cfg_old.get("input-xfrm", {}),}]
330
if cfg_old.get("input-xfrm"):
331
changes = list(reversed(changes))
332
for old in changes:
333
defer(cfg.ethnl.rss_set, {"header": {"dev-index": cfg.ifindex},} | old)
334
335
# Make sure we start with input-xfrm off, and tcp4 config non-sym
336
set_rss(cfg, {}, {})
337
set_rss(cfg, {}, {"tcp4": {"ip-src"}})
338
339
# Setting sym and fixing tcp4 config not expected to pass right now
340
with ksft_raises(NlError):
341
set_rss(cfg, {"sym-xor"}, {"tcp4": {"ip-src", "ip-dst"}})
342
# One at a time should work, hopefully
343
set_rss(cfg, 0, {"tcp4": {"ip-src", "ip-dst"}})
344
no_support = False
345
try:
346
set_rss(cfg, {"sym-xor"}, {})
347
except NlError:
348
try:
349
set_rss(cfg, {"sym-or-xor"}, {})
350
except NlError:
351
no_support = True
352
if no_support:
353
raise KsftSkipEx("no input-xfrm supported")
354
# Disabling two at once should not work either without kernel changes
355
with ksft_raises(NlError):
356
set_rss(cfg, {}, {"tcp4": {"ip-src"}})
357
358
359
def test_rxfh_fields_ntf(cfg):
360
""" Test Rx Flow Hash notifications. """
361
362
cur = _ethtool_get_cfg(cfg, "tcp4")
363
if cur == "sdfn":
364
change = {"ip-src", "ip-dst"}
365
else:
366
change = {"l4-b-0-1", "l4-b-2-3", "ip-src", "ip-dst"}
367
368
ethnl = EthtoolFamily()
369
ethnl.ntf_subscribe("monitor")
370
371
ethnl.rss_set({
372
"header": {"dev-index": cfg.ifindex},
373
"flow-hash": {"tcp4": change}
374
})
375
reset = defer(ethtool,
376
f"--disable-netlink -N {cfg.ifname} rx-flow-hash tcp4 {cur}")
377
378
ntf = next(ethnl.poll_ntf(duration=0.2), None)
379
if ntf is None:
380
raise KsftFailEx("No notification received after IOCTL change")
381
ksft_eq(ntf["name"], "rss-ntf")
382
ksft_eq(ntf["msg"]["flow-hash"]["tcp4"], change)
383
ksft_eq(next(ethnl.poll_ntf(duration=0.01), None), None)
384
385
reset.exec()
386
ntf = next(ethnl.poll_ntf(duration=0.2), None)
387
if ntf is None:
388
raise KsftFailEx("No notification received after Netlink change")
389
ksft_eq(ntf["name"], "rss-ntf")
390
ksft_ne(ntf["msg"]["flow-hash"]["tcp4"], change)
391
ksft_eq(next(ethnl.poll_ntf(duration=0.01), None), None)
392
393
394
def test_rss_ctx_add(cfg):
395
""" Test creating an additional RSS context via Netlink """
396
397
_require_2qs(cfg)
398
399
# Test basic creation
400
ctx = cfg.ethnl.rss_create_act({"header": {"dev-index": cfg.ifindex}})
401
d = defer(ethtool, f"-X {cfg.ifname} context {ctx.get('context')} delete")
402
ksft_ne(ctx.get("context", 0), 0)
403
ksft_ne(set(ctx.get("indir", [0])), {0},
404
comment="Driver should init the indirection table")
405
406
# Try requesting the ID we just got allocated
407
with ksft_raises(NlError) as cm:
408
ctx = cfg.ethnl.rss_create_act({
409
"header": {"dev-index": cfg.ifindex},
410
"context": ctx.get("context"),
411
})
412
ethtool(f"-X {cfg.ifname} context {ctx.get('context')} delete")
413
d.exec()
414
ksft_eq(cm.exception.nl_msg.error, -errno.EBUSY)
415
416
# Test creating with a specified RSS table, and context ID
417
ctx_id = ctx.get("context")
418
ctx = cfg.ethnl.rss_create_act({
419
"header": {"dev-index": cfg.ifindex},
420
"context": ctx_id,
421
"indir": [1],
422
})
423
ethtool(f"-X {cfg.ifname} context {ctx.get('context')} delete")
424
ksft_eq(ctx.get("context"), ctx_id)
425
ksft_eq(set(ctx.get("indir", [0])), {1})
426
427
428
def test_rss_ctx_ntf(cfg):
429
""" Test notifications for creating additional RSS contexts """
430
431
ethnl = EthtoolFamily()
432
ethnl.ntf_subscribe("monitor")
433
434
# Create / delete via Netlink
435
ctx = cfg.ethnl.rss_create_act({"header": {"dev-index": cfg.ifindex}})
436
cfg.ethnl.rss_delete_act({
437
"header": {"dev-index": cfg.ifindex},
438
"context": ctx["context"],
439
})
440
441
ntf = next(ethnl.poll_ntf(duration=0.2), None)
442
if ntf is None:
443
raise KsftFailEx("[NL] No notification after context creation")
444
ksft_eq(ntf["name"], "rss-create-ntf")
445
ksft_eq(ctx, ntf["msg"])
446
447
ntf = next(ethnl.poll_ntf(duration=0.2), None)
448
if ntf is None:
449
raise KsftFailEx("[NL] No notification after context deletion")
450
ksft_eq(ntf["name"], "rss-delete-ntf")
451
452
# Create / deleve via IOCTL
453
ctx_id = _ethtool_create(cfg, "--disable-netlink -X", "context new")
454
ethtool(f"--disable-netlink -X {cfg.ifname} context {ctx_id} delete")
455
ntf = next(ethnl.poll_ntf(duration=0.2), None)
456
if ntf is None:
457
raise KsftFailEx("[IOCTL] No notification after context creation")
458
ksft_eq(ntf["name"], "rss-create-ntf")
459
460
ntf = next(ethnl.poll_ntf(duration=0.2), None)
461
if ntf is None:
462
raise KsftFailEx("[IOCTL] No notification after context deletion")
463
ksft_eq(ntf["name"], "rss-delete-ntf")
464
465
466
def main() -> None:
467
""" Ksft boiler plate main """
468
469
with NetDrvEnv(__file__, nsim_test=False) as cfg:
470
cfg.ethnl = EthtoolFamily()
471
ksft_run(globs=globals(), case_pfx={"test_"}, args=(cfg, ))
472
ksft_exit()
473
474
475
if __name__ == "__main__":
476
main()
477
478