Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/testing/selftests/drivers/net/hw/rss_ctx.py
29271 views
1
#!/usr/bin/env python3
2
# SPDX-License-Identifier: GPL-2.0
3
4
import datetime
5
import random
6
import re
7
from lib.py import ksft_run, ksft_pr, ksft_exit
8
from lib.py import ksft_eq, ksft_ne, ksft_ge, ksft_in, ksft_lt, ksft_true, ksft_raises
9
from lib.py import NetDrvEpEnv
10
from lib.py import EthtoolFamily, NetdevFamily
11
from lib.py import KsftSkipEx, KsftFailEx
12
from lib.py import rand_port
13
from lib.py import ethtool, ip, defer, GenerateTraffic, CmdExitFailure
14
15
16
def _rss_key_str(key):
17
return ":".join(["{:02x}".format(x) for x in key])
18
19
20
def _rss_key_rand(length):
21
return [random.randint(0, 255) for _ in range(length)]
22
23
24
def _rss_key_check(cfg, data=None, context=0):
25
if data is None:
26
data = get_rss(cfg, context=context)
27
if 'rss-hash-key' not in data:
28
return
29
non_zero = [x for x in data['rss-hash-key'] if x != 0]
30
ksft_eq(bool(non_zero), True, comment=f"RSS key is all zero {data['rss-hash-key']}")
31
32
33
def get_rss(cfg, context=0):
34
return ethtool(f"-x {cfg.ifname} context {context}", json=True)[0]
35
36
37
def get_drop_err_sum(cfg):
38
stats = ip("-s -s link show dev " + cfg.ifname, json=True)[0]
39
cnt = 0
40
for key in ['errors', 'dropped', 'over_errors', 'fifo_errors',
41
'length_errors', 'crc_errors', 'missed_errors',
42
'frame_errors']:
43
cnt += stats["stats64"]["rx"][key]
44
return cnt, stats["stats64"]["tx"]["carrier_changes"]
45
46
47
def ethtool_create(cfg, act, opts):
48
output = ethtool(f"{act} {cfg.ifname} {opts}").stdout
49
# Output will be something like: "New RSS context is 1" or
50
# "Added rule with ID 7", we want the integer from the end
51
return int(output.split()[-1])
52
53
54
def require_ntuple(cfg):
55
features = ethtool(f"-k {cfg.ifname}", json=True)[0]
56
if not features["ntuple-filters"]["active"]:
57
# ntuple is more of a capability than a config knob, don't bother
58
# trying to enable it (until some driver actually needs it).
59
raise KsftSkipEx("Ntuple filters not enabled on the device: " + str(features["ntuple-filters"]))
60
61
62
def require_context_cnt(cfg, need_cnt):
63
# There's no good API to get the context count, so the tests
64
# which try to add a lot opportunisitically set the count they
65
# discovered. Careful with test ordering!
66
if need_cnt and cfg.context_cnt and cfg.context_cnt < need_cnt:
67
raise KsftSkipEx(f"Test requires at least {need_cnt} contexts, but device only has {cfg.context_cnt}")
68
69
70
# Get Rx packet counts for all queues, as a simple list of integers
71
# if @prev is specified the prev counts will be subtracted
72
def _get_rx_cnts(cfg, prev=None):
73
cfg.wait_hw_stats_settle()
74
data = cfg.netdevnl.qstats_get({"ifindex": cfg.ifindex, "scope": ["queue"]}, dump=True)
75
data = [x for x in data if x['queue-type'] == "rx"]
76
max_q = max([x["queue-id"] for x in data])
77
queue_stats = [0] * (max_q + 1)
78
for q in data:
79
queue_stats[q["queue-id"]] = q["rx-packets"]
80
if prev and q["queue-id"] < len(prev):
81
queue_stats[q["queue-id"]] -= prev[q["queue-id"]]
82
return queue_stats
83
84
85
def _send_traffic_check(cfg, port, name, params):
86
# params is a dict with 3 possible keys:
87
# - "target": required, which queues we expect to get iperf traffic
88
# - "empty": optional, which queues should see no traffic at all
89
# - "noise": optional, which queues we expect to see low traffic;
90
# used for queues of the main context, since some background
91
# OS activity may use those queues while we're testing
92
# the value for each is a list, or some other iterable containing queue ids.
93
94
cnts = _get_rx_cnts(cfg)
95
GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
96
cnts = _get_rx_cnts(cfg, prev=cnts)
97
98
directed = sum(cnts[i] for i in params['target'])
99
100
ksft_ge(directed, 20000, f"traffic on {name}: " + str(cnts))
101
if params.get('noise'):
102
ksft_lt(sum(cnts[i] for i in params['noise']), directed / 2,
103
f"traffic on other queues ({name})':" + str(cnts))
104
if params.get('empty'):
105
ksft_eq(sum(cnts[i] for i in params['empty']), 0,
106
f"traffic on inactive queues ({name}): " + str(cnts))
107
108
109
def _ntuple_rule_check(cfg, rule_id, ctx_id):
110
"""Check that ntuple rule references RSS context ID"""
111
text = ethtool(f"-n {cfg.ifname} rule {rule_id}").stdout
112
pattern = f"RSS Context (ID: )?{ctx_id}"
113
ksft_true(re.search(pattern, text), "RSS context not referenced in ntuple rule")
114
115
116
def test_rss_key_indir(cfg):
117
"""Test basics like updating the main RSS key and indirection table."""
118
119
qcnt = len(_get_rx_cnts(cfg))
120
if qcnt < 3:
121
raise KsftSkipEx("Device has fewer than 3 queues (or doesn't support queue stats)")
122
123
data = get_rss(cfg)
124
want_keys = ['rss-hash-key', 'rss-hash-function', 'rss-indirection-table']
125
for k in want_keys:
126
if k not in data:
127
raise KsftFailEx("ethtool results missing key: " + k)
128
if not data[k]:
129
raise KsftFailEx(f"ethtool results empty for '{k}': {data[k]}")
130
131
_rss_key_check(cfg, data=data)
132
key_len = len(data['rss-hash-key'])
133
134
# Set the key
135
key = _rss_key_rand(key_len)
136
ethtool(f"-X {cfg.ifname} hkey " + _rss_key_str(key))
137
138
data = get_rss(cfg)
139
ksft_eq(key, data['rss-hash-key'])
140
141
# Set the indirection table and the key together
142
key = _rss_key_rand(key_len)
143
ethtool(f"-X {cfg.ifname} equal 3 hkey " + _rss_key_str(key))
144
reset_indir = defer(ethtool, f"-X {cfg.ifname} default")
145
146
data = get_rss(cfg)
147
_rss_key_check(cfg, data=data)
148
ksft_eq(0, min(data['rss-indirection-table']))
149
ksft_eq(2, max(data['rss-indirection-table']))
150
151
# Reset indirection table and set the key
152
key = _rss_key_rand(key_len)
153
ethtool(f"-X {cfg.ifname} default hkey " + _rss_key_str(key))
154
data = get_rss(cfg)
155
_rss_key_check(cfg, data=data)
156
ksft_eq(0, min(data['rss-indirection-table']))
157
ksft_eq(qcnt - 1, max(data['rss-indirection-table']))
158
159
# Set the indirection table
160
ethtool(f"-X {cfg.ifname} equal 2")
161
data = get_rss(cfg)
162
ksft_eq(0, min(data['rss-indirection-table']))
163
ksft_eq(1, max(data['rss-indirection-table']))
164
165
# Check we only get traffic on the first 2 queues
166
cnts = _get_rx_cnts(cfg)
167
GenerateTraffic(cfg).wait_pkts_and_stop(20000)
168
cnts = _get_rx_cnts(cfg, prev=cnts)
169
# 2 queues, 20k packets, must be at least 5k per queue
170
ksft_ge(cnts[0], 5000, "traffic on main context (1/2): " + str(cnts))
171
ksft_ge(cnts[1], 5000, "traffic on main context (2/2): " + str(cnts))
172
# The other queues should be unused
173
ksft_eq(sum(cnts[2:]), 0, "traffic on unused queues: " + str(cnts))
174
175
# Restore, and check traffic gets spread again
176
reset_indir.exec()
177
178
cnts = _get_rx_cnts(cfg)
179
GenerateTraffic(cfg).wait_pkts_and_stop(20000)
180
cnts = _get_rx_cnts(cfg, prev=cnts)
181
if qcnt > 4:
182
# First two queues get less traffic than all the rest
183
ksft_lt(sum(cnts[:2]), sum(cnts[2:]),
184
"traffic distributed: " + str(cnts))
185
else:
186
# When queue count is low make sure third queue got significant pkts
187
ksft_ge(cnts[2], 3500, "traffic distributed: " + str(cnts))
188
189
190
def test_rss_queue_reconfigure(cfg, main_ctx=True):
191
"""Make sure queue changes can't override requested RSS config.
192
193
By default main RSS table should change to include all queues.
194
When user sets a specific RSS config the driver should preserve it,
195
even when queue count changes. Driver should refuse to deactivate
196
queues used in the user-set RSS config.
197
"""
198
199
if not main_ctx:
200
require_ntuple(cfg)
201
202
# Start with 4 queues, an arbitrary known number.
203
try:
204
qcnt = len(_get_rx_cnts(cfg))
205
ethtool(f"-L {cfg.ifname} combined 4")
206
defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
207
except:
208
raise KsftSkipEx("Not enough queues for the test or qstat not supported")
209
210
if main_ctx:
211
ctx_id = 0
212
ctx_ref = ""
213
else:
214
ctx_id = ethtool_create(cfg, "-X", "context new")
215
ctx_ref = f"context {ctx_id}"
216
defer(ethtool, f"-X {cfg.ifname} {ctx_ref} delete")
217
218
# Indirection table should be distributing to all queues.
219
data = get_rss(cfg, context=ctx_id)
220
ksft_eq(0, min(data['rss-indirection-table']))
221
ksft_eq(3, max(data['rss-indirection-table']))
222
223
# Increase queues, indirection table should be distributing to all queues.
224
# It's unclear whether tables of additional contexts should be reset, too.
225
if main_ctx:
226
ethtool(f"-L {cfg.ifname} combined 5")
227
data = get_rss(cfg)
228
ksft_eq(0, min(data['rss-indirection-table']))
229
ksft_eq(4, max(data['rss-indirection-table']))
230
ethtool(f"-L {cfg.ifname} combined 4")
231
232
# Configure the table explicitly
233
port = rand_port()
234
ethtool(f"-X {cfg.ifname} {ctx_ref} weight 1 0 0 1")
235
if main_ctx:
236
other_key = 'empty'
237
defer(ethtool, f"-X {cfg.ifname} default")
238
else:
239
other_key = 'noise'
240
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"
241
ntuple = ethtool_create(cfg, "-N", flow)
242
defer(ethtool, f"-N {cfg.ifname} delete {ntuple}")
243
244
_send_traffic_check(cfg, port, ctx_ref, { 'target': (0, 3),
245
other_key: (1, 2) })
246
247
# We should be able to increase queues, but table should be left untouched
248
ethtool(f"-L {cfg.ifname} combined 5")
249
data = get_rss(cfg, context=ctx_id)
250
ksft_eq({0, 3}, set(data['rss-indirection-table']))
251
252
_send_traffic_check(cfg, port, ctx_ref, { 'target': (0, 3),
253
other_key: (1, 2, 4) })
254
255
# Setting queue count to 3 should fail, queue 3 is used
256
try:
257
ethtool(f"-L {cfg.ifname} combined 3")
258
except CmdExitFailure:
259
pass
260
else:
261
raise Exception(f"Driver didn't prevent us from deactivating a used queue (context {ctx_id})")
262
263
if not main_ctx:
264
ethtool(f"-L {cfg.ifname} combined 4")
265
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id} action 1"
266
try:
267
# this targets queue 4, which doesn't exist
268
ntuple2 = ethtool_create(cfg, "-N", flow)
269
defer(ethtool, f"-N {cfg.ifname} delete {ntuple2}")
270
except CmdExitFailure:
271
pass
272
else:
273
raise Exception(f"Driver didn't prevent us from targeting a nonexistent queue (context {ctx_id})")
274
# change the table to target queues 0 and 2
275
ethtool(f"-X {cfg.ifname} {ctx_ref} weight 1 0 1 0")
276
# ntuple rule therefore targets queues 1 and 3
277
try:
278
ntuple2 = ethtool_create(cfg, "-N", flow)
279
except CmdExitFailure:
280
ksft_pr("Driver does not support rss + queue offset")
281
return
282
283
defer(ethtool, f"-N {cfg.ifname} delete {ntuple2}")
284
# should replace existing filter
285
ksft_eq(ntuple, ntuple2)
286
_send_traffic_check(cfg, port, ctx_ref, { 'target': (1, 3),
287
'noise' : (0, 2) })
288
# Setting queue count to 3 should fail, queue 3 is used
289
try:
290
ethtool(f"-L {cfg.ifname} combined 3")
291
except CmdExitFailure:
292
pass
293
else:
294
raise Exception(f"Driver didn't prevent us from deactivating a used queue (context {ctx_id})")
295
296
297
def test_rss_resize(cfg):
298
"""Test resizing of the RSS table.
299
300
Some devices dynamically increase and decrease the size of the RSS
301
indirection table based on the number of enabled queues.
302
When that happens driver must maintain the balance of entries
303
(preferably duplicating the smaller table).
304
"""
305
306
channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
307
ch_max = channels['combined-max']
308
qcnt = channels['combined-count']
309
310
if ch_max < 2:
311
raise KsftSkipEx(f"Not enough queues for the test: {ch_max}")
312
313
ethtool(f"-L {cfg.ifname} combined 2")
314
defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
315
316
ethtool(f"-X {cfg.ifname} weight 1 7")
317
defer(ethtool, f"-X {cfg.ifname} default")
318
319
ethtool(f"-L {cfg.ifname} combined {ch_max}")
320
data = get_rss(cfg)
321
ksft_eq(0, min(data['rss-indirection-table']))
322
ksft_eq(1, max(data['rss-indirection-table']))
323
324
ksft_eq(7,
325
data['rss-indirection-table'].count(1) /
326
data['rss-indirection-table'].count(0),
327
f"Table imbalance after resize: {data['rss-indirection-table']}")
328
329
330
def test_hitless_key_update(cfg):
331
"""Test that flows may be rehashed without impacting traffic.
332
333
Some workloads may want to rehash the flows in response to an imbalance.
334
Most effective way to do that is changing the RSS key. Check that changing
335
the key does not cause link flaps or traffic disruption.
336
337
Disrupting traffic for key update is not a bug, but makes the key
338
update unusable for rehashing under load.
339
"""
340
data = get_rss(cfg)
341
key_len = len(data['rss-hash-key'])
342
343
ethnl = EthtoolFamily()
344
key = random.randbytes(key_len)
345
346
tgen = GenerateTraffic(cfg)
347
try:
348
errors0, carrier0 = get_drop_err_sum(cfg)
349
t0 = datetime.datetime.now()
350
ethnl.rss_set({"header": {"dev-index": cfg.ifindex}, "hkey": key})
351
t1 = datetime.datetime.now()
352
errors1, carrier1 = get_drop_err_sum(cfg)
353
finally:
354
tgen.wait_pkts_and_stop(5000)
355
356
ksft_lt((t1 - t0).total_seconds(), 0.15)
357
ksft_eq(errors1 - errors1, 0)
358
ksft_eq(carrier1 - carrier0, 0)
359
360
361
def test_rss_context_dump(cfg):
362
"""
363
Test dumping RSS contexts. This tests mostly exercises the kernel APIs.
364
"""
365
366
# Get a random key of the right size
367
data = get_rss(cfg)
368
if 'rss-hash-key' in data:
369
key_data = _rss_key_rand(len(data['rss-hash-key']))
370
key = _rss_key_str(key_data)
371
else:
372
key_data = []
373
key = "ba:ad"
374
375
ids = []
376
try:
377
ids.append(ethtool_create(cfg, "-X", f"context new"))
378
defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
379
380
ids.append(ethtool_create(cfg, "-X", f"context new weight 1 1"))
381
defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
382
383
ids.append(ethtool_create(cfg, "-X", f"context new hkey {key}"))
384
defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
385
except CmdExitFailure:
386
if not ids:
387
raise KsftSkipEx("Unable to add any contexts")
388
ksft_pr(f"Added only {len(ids)} out of 3 contexts")
389
390
expect_tuples = set([(cfg.ifname, -1)] + [(cfg.ifname, ctx_id) for ctx_id in ids])
391
392
# Dump all
393
ctxs = cfg.ethnl.rss_get({}, dump=True)
394
tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
395
ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump")
396
ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname])
397
ksft_eq(expect_tuples, ctx_tuples)
398
399
# Sanity-check the results
400
for data in ctxs:
401
ksft_ne(set(data.get('indir', [1])), {0}, "indir table is all zero")
402
ksft_ne(set(data.get('hkey', [1])), {0}, "key is all zero")
403
404
# More specific checks
405
if len(ids) > 1 and data.get('context') == ids[1]:
406
ksft_eq(set(data['indir']), {0, 1},
407
"ctx1 - indir table mismatch")
408
if len(ids) > 2 and data.get('context') == ids[2]:
409
ksft_eq(data['hkey'], bytes(key_data), "ctx2 - key mismatch")
410
411
# Ifindex filter
412
ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}}, dump=True)
413
tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
414
ctx_tuples = set(tuples)
415
ksft_eq(len(tuples), len(ctx_tuples), "duplicates in context dump")
416
ksft_eq(expect_tuples, ctx_tuples)
417
418
# Skip ctx 0
419
expect_tuples.remove((cfg.ifname, -1))
420
421
ctxs = cfg.ethnl.rss_get({'start-context': 1}, dump=True)
422
tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
423
ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump")
424
ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname])
425
ksft_eq(expect_tuples, ctx_tuples)
426
427
# And finally both with ifindex and skip main
428
ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}, 'start-context': 1}, dump=True)
429
ctx_tuples = set([(c['header']['dev-name'], c.get('context', -1)) for c in ctxs])
430
ksft_eq(expect_tuples, ctx_tuples)
431
432
433
def test_rss_context(cfg, ctx_cnt=1, create_with_cfg=None):
434
"""
435
Test separating traffic into RSS contexts.
436
The queues will be allocated 2 for each context:
437
ctx0 ctx1 ctx2 ctx3
438
[0 1] [2 3] [4 5] [6 7] ...
439
"""
440
441
require_ntuple(cfg)
442
443
requested_ctx_cnt = ctx_cnt
444
445
# Try to allocate more queues when necessary
446
qcnt = len(_get_rx_cnts(cfg))
447
if qcnt < 2 + 2 * ctx_cnt:
448
try:
449
ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}")
450
ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}")
451
defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
452
except:
453
raise KsftSkipEx("Not enough queues for the test")
454
455
ports = []
456
457
# Use queues 0 and 1 for normal traffic
458
ethtool(f"-X {cfg.ifname} equal 2")
459
defer(ethtool, f"-X {cfg.ifname} default")
460
461
for i in range(ctx_cnt):
462
want_cfg = f"start {2 + i * 2} equal 2"
463
create_cfg = want_cfg if create_with_cfg else ""
464
465
try:
466
ctx_id = ethtool_create(cfg, "-X", f"context new {create_cfg}")
467
defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
468
except CmdExitFailure:
469
# try to carry on and skip at the end
470
if i == 0:
471
raise
472
ksft_pr(f"Failed to create context {i + 1}, trying to test what we got")
473
ctx_cnt = i
474
if cfg.context_cnt is None:
475
cfg.context_cnt = ctx_cnt
476
break
477
478
_rss_key_check(cfg, context=ctx_id)
479
480
if not create_with_cfg:
481
ethtool(f"-X {cfg.ifname} context {ctx_id} {want_cfg}")
482
_rss_key_check(cfg, context=ctx_id)
483
484
# Sanity check the context we just created
485
data = get_rss(cfg, ctx_id)
486
ksft_eq(min(data['rss-indirection-table']), 2 + i * 2, "Unexpected context cfg: " + str(data))
487
ksft_eq(max(data['rss-indirection-table']), 2 + i * 2 + 1, "Unexpected context cfg: " + str(data))
488
489
ports.append(rand_port())
490
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {ports[i]} context {ctx_id}"
491
ntuple = ethtool_create(cfg, "-N", flow)
492
defer(ethtool, f"-N {cfg.ifname} delete {ntuple}")
493
494
_ntuple_rule_check(cfg, ntuple, ctx_id)
495
496
for i in range(ctx_cnt):
497
_send_traffic_check(cfg, ports[i], f"context {i}",
498
{ 'target': (2+i*2, 3+i*2),
499
'noise': (0, 1),
500
'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt)) })
501
502
if requested_ctx_cnt != ctx_cnt:
503
raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}")
504
505
506
def test_rss_context4(cfg):
507
test_rss_context(cfg, 4)
508
509
510
def test_rss_context32(cfg):
511
test_rss_context(cfg, 32)
512
513
514
def test_rss_context4_create_with_cfg(cfg):
515
test_rss_context(cfg, 4, create_with_cfg=True)
516
517
518
def test_rss_context_queue_reconfigure(cfg):
519
test_rss_queue_reconfigure(cfg, main_ctx=False)
520
521
522
def test_rss_context_out_of_order(cfg, ctx_cnt=4):
523
"""
524
Test separating traffic into RSS contexts.
525
Contexts are removed in semi-random order, and steering re-tested
526
to make sure removal doesn't break steering to surviving contexts.
527
Test requires 3 contexts to work.
528
"""
529
530
require_ntuple(cfg)
531
require_context_cnt(cfg, 4)
532
533
# Try to allocate more queues when necessary
534
qcnt = len(_get_rx_cnts(cfg))
535
if qcnt < 2 + 2 * ctx_cnt:
536
try:
537
ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}")
538
ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}")
539
defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
540
except:
541
raise KsftSkipEx("Not enough queues for the test")
542
543
ntuple = []
544
ctx = []
545
ports = []
546
547
def remove_ctx(idx):
548
ntuple[idx].exec()
549
ntuple[idx] = None
550
ctx[idx].exec()
551
ctx[idx] = None
552
553
def check_traffic():
554
for i in range(ctx_cnt):
555
if ctx[i]:
556
expected = {
557
'target': (2+i*2, 3+i*2),
558
'noise': (0, 1),
559
'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt))
560
}
561
else:
562
expected = {
563
'target': (0, 1),
564
'empty': range(2, 2+2*ctx_cnt)
565
}
566
567
_send_traffic_check(cfg, ports[i], f"context {i}", expected)
568
569
# Use queues 0 and 1 for normal traffic
570
ethtool(f"-X {cfg.ifname} equal 2")
571
defer(ethtool, f"-X {cfg.ifname} default")
572
573
for i in range(ctx_cnt):
574
ctx_id = ethtool_create(cfg, "-X", f"context new start {2 + i * 2} equal 2")
575
ctx.append(defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete"))
576
577
ports.append(rand_port())
578
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {ports[i]} context {ctx_id}"
579
ntuple_id = ethtool_create(cfg, "-N", flow)
580
ntuple.append(defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}"))
581
582
check_traffic()
583
584
# Remove middle context
585
remove_ctx(ctx_cnt // 2)
586
check_traffic()
587
588
# Remove first context
589
remove_ctx(0)
590
check_traffic()
591
592
# Remove last context
593
remove_ctx(-1)
594
check_traffic()
595
596
597
def test_rss_context_overlap(cfg, other_ctx=0):
598
"""
599
Test contexts overlapping with each other.
600
Use 4 queues for the main context, but only queues 2 and 3 for context 1.
601
"""
602
603
require_ntuple(cfg)
604
if other_ctx:
605
require_context_cnt(cfg, 2)
606
607
queue_cnt = len(_get_rx_cnts(cfg))
608
if queue_cnt < 4:
609
try:
610
ksft_pr(f"Increasing queue count {queue_cnt} -> 4")
611
ethtool(f"-L {cfg.ifname} combined 4")
612
defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")
613
except:
614
raise KsftSkipEx("Not enough queues for the test")
615
616
if other_ctx == 0:
617
ethtool(f"-X {cfg.ifname} equal 4")
618
defer(ethtool, f"-X {cfg.ifname} default")
619
else:
620
other_ctx = ethtool_create(cfg, "-X", "context new")
621
ethtool(f"-X {cfg.ifname} context {other_ctx} equal 4")
622
defer(ethtool, f"-X {cfg.ifname} context {other_ctx} delete")
623
624
ctx_id = ethtool_create(cfg, "-X", "context new")
625
ethtool(f"-X {cfg.ifname} context {ctx_id} start 2 equal 2")
626
defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
627
628
port = rand_port()
629
if other_ctx:
630
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {other_ctx}"
631
ntuple_id = ethtool_create(cfg, "-N", flow)
632
ntuple = defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
633
634
# Test the main context
635
cnts = _get_rx_cnts(cfg)
636
GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
637
cnts = _get_rx_cnts(cfg, prev=cnts)
638
639
ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts))
640
ksft_ge(sum(cnts[ :2]), 7000, "traffic on main context (1/2): " + str(cnts))
641
ksft_ge(sum(cnts[2:4]), 7000, "traffic on main context (2/2): " + str(cnts))
642
if other_ctx == 0:
643
ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts))
644
645
# Now create a rule for context 1 and make sure traffic goes to a subset
646
if other_ctx:
647
ntuple.exec()
648
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"
649
ntuple_id = ethtool_create(cfg, "-N", flow)
650
defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
651
652
cnts = _get_rx_cnts(cfg)
653
GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
654
cnts = _get_rx_cnts(cfg, prev=cnts)
655
656
directed = sum(cnts[2:4])
657
ksft_lt(sum(cnts[ :2]), directed / 2, "traffic on main context: " + str(cnts))
658
ksft_ge(directed, 20000, "traffic on extra context: " + str(cnts))
659
if other_ctx == 0:
660
ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts))
661
662
663
def test_rss_context_overlap2(cfg):
664
test_rss_context_overlap(cfg, True)
665
666
667
def test_flow_add_context_missing(cfg):
668
"""
669
Test that we are not allowed to add a rule pointing to an RSS context
670
which was never created.
671
"""
672
673
require_ntuple(cfg)
674
675
# Find a context which doesn't exist
676
for ctx_id in range(1, 100):
677
try:
678
get_rss(cfg, context=ctx_id)
679
except CmdExitFailure:
680
break
681
682
with ksft_raises(CmdExitFailure) as cm:
683
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port 1234 context {ctx_id}"
684
ntuple_id = ethtool_create(cfg, "-N", flow)
685
ethtool(f"-N {cfg.ifname} delete {ntuple_id}")
686
if cm.exception:
687
ksft_in('Invalid argument', cm.exception.cmd.stderr)
688
689
690
def test_delete_rss_context_busy(cfg):
691
"""
692
Test that deletion returns -EBUSY when an rss context is being used
693
by an ntuple filter.
694
"""
695
696
require_ntuple(cfg)
697
698
# create additional rss context
699
ctx_id = ethtool_create(cfg, "-X", "context new")
700
ctx_deleter = defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
701
702
# utilize context from ntuple filter
703
port = rand_port()
704
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"
705
ntuple_id = ethtool_create(cfg, "-N", flow)
706
defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
707
708
# attempt to delete in-use context
709
try:
710
ctx_deleter.exec_only()
711
ctx_deleter.cancel()
712
raise KsftFailEx(f"deleted context {ctx_id} used by rule {ntuple_id}")
713
except CmdExitFailure:
714
pass
715
716
717
def test_rss_ntuple_addition(cfg):
718
"""
719
Test that the queue offset (ring_cookie) of an ntuple rule is added
720
to the queue number read from the indirection table.
721
"""
722
723
require_ntuple(cfg)
724
725
queue_cnt = len(_get_rx_cnts(cfg))
726
if queue_cnt < 4:
727
try:
728
ksft_pr(f"Increasing queue count {queue_cnt} -> 4")
729
ethtool(f"-L {cfg.ifname} combined 4")
730
defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")
731
except:
732
raise KsftSkipEx("Not enough queues for the test")
733
734
# Use queue 0 for normal traffic
735
ethtool(f"-X {cfg.ifname} equal 1")
736
defer(ethtool, f"-X {cfg.ifname} default")
737
738
# create additional rss context
739
ctx_id = ethtool_create(cfg, "-X", "context new equal 2")
740
defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
741
742
# utilize context from ntuple filter
743
port = rand_port()
744
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id} action 2"
745
try:
746
ntuple_id = ethtool_create(cfg, "-N", flow)
747
except CmdExitFailure:
748
raise KsftSkipEx("Ntuple filter with RSS and nonzero action not supported")
749
defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
750
751
_send_traffic_check(cfg, port, f"context {ctx_id}", { 'target': (2, 3),
752
'empty' : (1,),
753
'noise' : (0,) })
754
755
756
def test_rss_default_context_rule(cfg):
757
"""
758
Allocate a port, direct this port to context 0, then create a new RSS
759
context and steer all TCP traffic to it (context 1). Verify that:
760
* Traffic to the specific port continues to use queues of the main
761
context (0/1).
762
* Traffic to any other TCP port is redirected to the new context
763
(queues 2/3).
764
"""
765
766
require_ntuple(cfg)
767
768
queue_cnt = len(_get_rx_cnts(cfg))
769
if queue_cnt < 4:
770
try:
771
ksft_pr(f"Increasing queue count {queue_cnt} -> 4")
772
ethtool(f"-L {cfg.ifname} combined 4")
773
defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")
774
except Exception as exc:
775
raise KsftSkipEx("Not enough queues for the test") from exc
776
777
# Use queues 0 and 1 for the main context
778
ethtool(f"-X {cfg.ifname} equal 2")
779
defer(ethtool, f"-X {cfg.ifname} default")
780
781
# Create a new RSS context that uses queues 2 and 3
782
ctx_id = ethtool_create(cfg, "-X", "context new start 2 equal 2")
783
defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
784
785
# Generic low-priority rule: redirect all TCP traffic to the new context.
786
# Give it an explicit higher location number (lower priority).
787
flow_generic = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} context {ctx_id} loc 1"
788
ethtool(f"-N {cfg.ifname} {flow_generic}")
789
defer(ethtool, f"-N {cfg.ifname} delete 1")
790
791
# Specific high-priority rule for a random port that should stay on context 0.
792
# Assign loc 0 so it is evaluated before the generic rule.
793
port_main = rand_port()
794
flow_main = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port_main} context 0 loc 0"
795
ethtool(f"-N {cfg.ifname} {flow_main}")
796
defer(ethtool, f"-N {cfg.ifname} delete 0")
797
798
_ntuple_rule_check(cfg, 1, ctx_id)
799
800
# Verify that traffic matching the specific rule still goes to queues 0/1
801
_send_traffic_check(cfg, port_main, "context 0",
802
{ 'target': (0, 1),
803
'empty' : (2, 3) })
804
805
# And that traffic for any other port is steered to the new context
806
port_other = rand_port()
807
_send_traffic_check(cfg, port_other, f"context {ctx_id}",
808
{ 'target': (2, 3),
809
'noise' : (0, 1) })
810
811
812
def main() -> None:
813
with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
814
cfg.context_cnt = None
815
cfg.ethnl = EthtoolFamily()
816
cfg.netdevnl = NetdevFamily()
817
818
ksft_run([test_rss_key_indir, test_rss_queue_reconfigure,
819
test_rss_resize, test_hitless_key_update,
820
test_rss_context, test_rss_context4, test_rss_context32,
821
test_rss_context_dump, test_rss_context_queue_reconfigure,
822
test_rss_context_overlap, test_rss_context_overlap2,
823
test_rss_context_out_of_order, test_rss_context4_create_with_cfg,
824
test_flow_add_context_missing,
825
test_delete_rss_context_busy, test_rss_ntuple_addition,
826
test_rss_default_context_rule],
827
args=(cfg, ))
828
ksft_exit()
829
830
831
if __name__ == "__main__":
832
main()
833
834