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
26295 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
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
# First two queues get less traffic than all the rest
182
ksft_lt(sum(cnts[:2]), sum(cnts[2:]), "traffic distributed: " + str(cnts))
183
184
185
def test_rss_queue_reconfigure(cfg, main_ctx=True):
186
"""Make sure queue changes can't override requested RSS config.
187
188
By default main RSS table should change to include all queues.
189
When user sets a specific RSS config the driver should preserve it,
190
even when queue count changes. Driver should refuse to deactivate
191
queues used in the user-set RSS config.
192
"""
193
194
if not main_ctx:
195
require_ntuple(cfg)
196
197
# Start with 4 queues, an arbitrary known number.
198
try:
199
qcnt = len(_get_rx_cnts(cfg))
200
ethtool(f"-L {cfg.ifname} combined 4")
201
defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
202
except:
203
raise KsftSkipEx("Not enough queues for the test or qstat not supported")
204
205
if main_ctx:
206
ctx_id = 0
207
ctx_ref = ""
208
else:
209
ctx_id = ethtool_create(cfg, "-X", "context new")
210
ctx_ref = f"context {ctx_id}"
211
defer(ethtool, f"-X {cfg.ifname} {ctx_ref} delete")
212
213
# Indirection table should be distributing to all queues.
214
data = get_rss(cfg, context=ctx_id)
215
ksft_eq(0, min(data['rss-indirection-table']))
216
ksft_eq(3, max(data['rss-indirection-table']))
217
218
# Increase queues, indirection table should be distributing to all queues.
219
# It's unclear whether tables of additional contexts should be reset, too.
220
if main_ctx:
221
ethtool(f"-L {cfg.ifname} combined 5")
222
data = get_rss(cfg)
223
ksft_eq(0, min(data['rss-indirection-table']))
224
ksft_eq(4, max(data['rss-indirection-table']))
225
ethtool(f"-L {cfg.ifname} combined 4")
226
227
# Configure the table explicitly
228
port = rand_port()
229
ethtool(f"-X {cfg.ifname} {ctx_ref} weight 1 0 0 1")
230
if main_ctx:
231
other_key = 'empty'
232
defer(ethtool, f"-X {cfg.ifname} default")
233
else:
234
other_key = 'noise'
235
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"
236
ntuple = ethtool_create(cfg, "-N", flow)
237
defer(ethtool, f"-N {cfg.ifname} delete {ntuple}")
238
239
_send_traffic_check(cfg, port, ctx_ref, { 'target': (0, 3),
240
other_key: (1, 2) })
241
242
# We should be able to increase queues, but table should be left untouched
243
ethtool(f"-L {cfg.ifname} combined 5")
244
data = get_rss(cfg, context=ctx_id)
245
ksft_eq({0, 3}, set(data['rss-indirection-table']))
246
247
_send_traffic_check(cfg, port, ctx_ref, { 'target': (0, 3),
248
other_key: (1, 2, 4) })
249
250
# Setting queue count to 3 should fail, queue 3 is used
251
try:
252
ethtool(f"-L {cfg.ifname} combined 3")
253
except CmdExitFailure:
254
pass
255
else:
256
raise Exception(f"Driver didn't prevent us from deactivating a used queue (context {ctx_id})")
257
258
if not main_ctx:
259
ethtool(f"-L {cfg.ifname} combined 4")
260
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id} action 1"
261
try:
262
# this targets queue 4, which doesn't exist
263
ntuple2 = ethtool_create(cfg, "-N", flow)
264
defer(ethtool, f"-N {cfg.ifname} delete {ntuple2}")
265
except CmdExitFailure:
266
pass
267
else:
268
raise Exception(f"Driver didn't prevent us from targeting a nonexistent queue (context {ctx_id})")
269
# change the table to target queues 0 and 2
270
ethtool(f"-X {cfg.ifname} {ctx_ref} weight 1 0 1 0")
271
# ntuple rule therefore targets queues 1 and 3
272
try:
273
ntuple2 = ethtool_create(cfg, "-N", flow)
274
except CmdExitFailure:
275
ksft_pr("Driver does not support rss + queue offset")
276
return
277
278
defer(ethtool, f"-N {cfg.ifname} delete {ntuple2}")
279
# should replace existing filter
280
ksft_eq(ntuple, ntuple2)
281
_send_traffic_check(cfg, port, ctx_ref, { 'target': (1, 3),
282
'noise' : (0, 2) })
283
# Setting queue count to 3 should fail, queue 3 is used
284
try:
285
ethtool(f"-L {cfg.ifname} combined 3")
286
except CmdExitFailure:
287
pass
288
else:
289
raise Exception(f"Driver didn't prevent us from deactivating a used queue (context {ctx_id})")
290
291
292
def test_rss_resize(cfg):
293
"""Test resizing of the RSS table.
294
295
Some devices dynamically increase and decrease the size of the RSS
296
indirection table based on the number of enabled queues.
297
When that happens driver must maintain the balance of entries
298
(preferably duplicating the smaller table).
299
"""
300
301
channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
302
ch_max = channels['combined-max']
303
qcnt = channels['combined-count']
304
305
if ch_max < 2:
306
raise KsftSkipEx(f"Not enough queues for the test: {ch_max}")
307
308
ethtool(f"-L {cfg.ifname} combined 2")
309
defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
310
311
ethtool(f"-X {cfg.ifname} weight 1 7")
312
defer(ethtool, f"-X {cfg.ifname} default")
313
314
ethtool(f"-L {cfg.ifname} combined {ch_max}")
315
data = get_rss(cfg)
316
ksft_eq(0, min(data['rss-indirection-table']))
317
ksft_eq(1, max(data['rss-indirection-table']))
318
319
ksft_eq(7,
320
data['rss-indirection-table'].count(1) /
321
data['rss-indirection-table'].count(0),
322
f"Table imbalance after resize: {data['rss-indirection-table']}")
323
324
325
def test_hitless_key_update(cfg):
326
"""Test that flows may be rehashed without impacting traffic.
327
328
Some workloads may want to rehash the flows in response to an imbalance.
329
Most effective way to do that is changing the RSS key. Check that changing
330
the key does not cause link flaps or traffic disruption.
331
332
Disrupting traffic for key update is not a bug, but makes the key
333
update unusable for rehashing under load.
334
"""
335
data = get_rss(cfg)
336
key_len = len(data['rss-hash-key'])
337
338
key = _rss_key_rand(key_len)
339
340
tgen = GenerateTraffic(cfg)
341
try:
342
errors0, carrier0 = get_drop_err_sum(cfg)
343
t0 = datetime.datetime.now()
344
ethtool(f"-X {cfg.ifname} hkey " + _rss_key_str(key))
345
t1 = datetime.datetime.now()
346
errors1, carrier1 = get_drop_err_sum(cfg)
347
finally:
348
tgen.wait_pkts_and_stop(5000)
349
350
ksft_lt((t1 - t0).total_seconds(), 0.2)
351
ksft_eq(errors1 - errors1, 0)
352
ksft_eq(carrier1 - carrier0, 0)
353
354
355
def test_rss_context_dump(cfg):
356
"""
357
Test dumping RSS contexts. This tests mostly exercises the kernel APIs.
358
"""
359
360
# Get a random key of the right size
361
data = get_rss(cfg)
362
if 'rss-hash-key' in data:
363
key_data = _rss_key_rand(len(data['rss-hash-key']))
364
key = _rss_key_str(key_data)
365
else:
366
key_data = []
367
key = "ba:ad"
368
369
ids = []
370
try:
371
ids.append(ethtool_create(cfg, "-X", f"context new"))
372
defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
373
374
ids.append(ethtool_create(cfg, "-X", f"context new weight 1 1"))
375
defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
376
377
ids.append(ethtool_create(cfg, "-X", f"context new hkey {key}"))
378
defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete")
379
except CmdExitFailure:
380
if not ids:
381
raise KsftSkipEx("Unable to add any contexts")
382
ksft_pr(f"Added only {len(ids)} out of 3 contexts")
383
384
expect_tuples = set([(cfg.ifname, -1)] + [(cfg.ifname, ctx_id) for ctx_id in ids])
385
386
# Dump all
387
ctxs = cfg.ethnl.rss_get({}, dump=True)
388
tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
389
ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump")
390
ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname])
391
ksft_eq(expect_tuples, ctx_tuples)
392
393
# Sanity-check the results
394
for data in ctxs:
395
ksft_ne(set(data.get('indir', [1])), {0}, "indir table is all zero")
396
ksft_ne(set(data.get('hkey', [1])), {0}, "key is all zero")
397
398
# More specific checks
399
if len(ids) > 1 and data.get('context') == ids[1]:
400
ksft_eq(set(data['indir']), {0, 1},
401
"ctx1 - indir table mismatch")
402
if len(ids) > 2 and data.get('context') == ids[2]:
403
ksft_eq(data['hkey'], bytes(key_data), "ctx2 - key mismatch")
404
405
# Ifindex filter
406
ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}}, dump=True)
407
tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
408
ctx_tuples = set(tuples)
409
ksft_eq(len(tuples), len(ctx_tuples), "duplicates in context dump")
410
ksft_eq(expect_tuples, ctx_tuples)
411
412
# Skip ctx 0
413
expect_tuples.remove((cfg.ifname, -1))
414
415
ctxs = cfg.ethnl.rss_get({'start-context': 1}, dump=True)
416
tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]
417
ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump")
418
ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname])
419
ksft_eq(expect_tuples, ctx_tuples)
420
421
# And finally both with ifindex and skip main
422
ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}, 'start-context': 1}, dump=True)
423
ctx_tuples = set([(c['header']['dev-name'], c.get('context', -1)) for c in ctxs])
424
ksft_eq(expect_tuples, ctx_tuples)
425
426
427
def test_rss_context(cfg, ctx_cnt=1, create_with_cfg=None):
428
"""
429
Test separating traffic into RSS contexts.
430
The queues will be allocated 2 for each context:
431
ctx0 ctx1 ctx2 ctx3
432
[0 1] [2 3] [4 5] [6 7] ...
433
"""
434
435
require_ntuple(cfg)
436
437
requested_ctx_cnt = ctx_cnt
438
439
# Try to allocate more queues when necessary
440
qcnt = len(_get_rx_cnts(cfg))
441
if qcnt < 2 + 2 * ctx_cnt:
442
try:
443
ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}")
444
ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}")
445
defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
446
except:
447
raise KsftSkipEx("Not enough queues for the test")
448
449
ports = []
450
451
# Use queues 0 and 1 for normal traffic
452
ethtool(f"-X {cfg.ifname} equal 2")
453
defer(ethtool, f"-X {cfg.ifname} default")
454
455
for i in range(ctx_cnt):
456
want_cfg = f"start {2 + i * 2} equal 2"
457
create_cfg = want_cfg if create_with_cfg else ""
458
459
try:
460
ctx_id = ethtool_create(cfg, "-X", f"context new {create_cfg}")
461
defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
462
except CmdExitFailure:
463
# try to carry on and skip at the end
464
if i == 0:
465
raise
466
ksft_pr(f"Failed to create context {i + 1}, trying to test what we got")
467
ctx_cnt = i
468
if cfg.context_cnt is None:
469
cfg.context_cnt = ctx_cnt
470
break
471
472
_rss_key_check(cfg, context=ctx_id)
473
474
if not create_with_cfg:
475
ethtool(f"-X {cfg.ifname} context {ctx_id} {want_cfg}")
476
_rss_key_check(cfg, context=ctx_id)
477
478
# Sanity check the context we just created
479
data = get_rss(cfg, ctx_id)
480
ksft_eq(min(data['rss-indirection-table']), 2 + i * 2, "Unexpected context cfg: " + str(data))
481
ksft_eq(max(data['rss-indirection-table']), 2 + i * 2 + 1, "Unexpected context cfg: " + str(data))
482
483
ports.append(rand_port())
484
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {ports[i]} context {ctx_id}"
485
ntuple = ethtool_create(cfg, "-N", flow)
486
defer(ethtool, f"-N {cfg.ifname} delete {ntuple}")
487
488
_ntuple_rule_check(cfg, ntuple, ctx_id)
489
490
for i in range(ctx_cnt):
491
_send_traffic_check(cfg, ports[i], f"context {i}",
492
{ 'target': (2+i*2, 3+i*2),
493
'noise': (0, 1),
494
'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt)) })
495
496
if requested_ctx_cnt != ctx_cnt:
497
raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}")
498
499
500
def test_rss_context4(cfg):
501
test_rss_context(cfg, 4)
502
503
504
def test_rss_context32(cfg):
505
test_rss_context(cfg, 32)
506
507
508
def test_rss_context4_create_with_cfg(cfg):
509
test_rss_context(cfg, 4, create_with_cfg=True)
510
511
512
def test_rss_context_queue_reconfigure(cfg):
513
test_rss_queue_reconfigure(cfg, main_ctx=False)
514
515
516
def test_rss_context_out_of_order(cfg, ctx_cnt=4):
517
"""
518
Test separating traffic into RSS contexts.
519
Contexts are removed in semi-random order, and steering re-tested
520
to make sure removal doesn't break steering to surviving contexts.
521
Test requires 3 contexts to work.
522
"""
523
524
require_ntuple(cfg)
525
require_context_cnt(cfg, 4)
526
527
# Try to allocate more queues when necessary
528
qcnt = len(_get_rx_cnts(cfg))
529
if qcnt < 2 + 2 * ctx_cnt:
530
try:
531
ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}")
532
ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}")
533
defer(ethtool, f"-L {cfg.ifname} combined {qcnt}")
534
except:
535
raise KsftSkipEx("Not enough queues for the test")
536
537
ntuple = []
538
ctx = []
539
ports = []
540
541
def remove_ctx(idx):
542
ntuple[idx].exec()
543
ntuple[idx] = None
544
ctx[idx].exec()
545
ctx[idx] = None
546
547
def check_traffic():
548
for i in range(ctx_cnt):
549
if ctx[i]:
550
expected = {
551
'target': (2+i*2, 3+i*2),
552
'noise': (0, 1),
553
'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt))
554
}
555
else:
556
expected = {
557
'target': (0, 1),
558
'empty': range(2, 2+2*ctx_cnt)
559
}
560
561
_send_traffic_check(cfg, ports[i], f"context {i}", expected)
562
563
# Use queues 0 and 1 for normal traffic
564
ethtool(f"-X {cfg.ifname} equal 2")
565
defer(ethtool, f"-X {cfg.ifname} default")
566
567
for i in range(ctx_cnt):
568
ctx_id = ethtool_create(cfg, "-X", f"context new start {2 + i * 2} equal 2")
569
ctx.append(defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete"))
570
571
ports.append(rand_port())
572
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {ports[i]} context {ctx_id}"
573
ntuple_id = ethtool_create(cfg, "-N", flow)
574
ntuple.append(defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}"))
575
576
check_traffic()
577
578
# Remove middle context
579
remove_ctx(ctx_cnt // 2)
580
check_traffic()
581
582
# Remove first context
583
remove_ctx(0)
584
check_traffic()
585
586
# Remove last context
587
remove_ctx(-1)
588
check_traffic()
589
590
591
def test_rss_context_overlap(cfg, other_ctx=0):
592
"""
593
Test contexts overlapping with each other.
594
Use 4 queues for the main context, but only queues 2 and 3 for context 1.
595
"""
596
597
require_ntuple(cfg)
598
if other_ctx:
599
require_context_cnt(cfg, 2)
600
601
queue_cnt = len(_get_rx_cnts(cfg))
602
if queue_cnt < 4:
603
try:
604
ksft_pr(f"Increasing queue count {queue_cnt} -> 4")
605
ethtool(f"-L {cfg.ifname} combined 4")
606
defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")
607
except:
608
raise KsftSkipEx("Not enough queues for the test")
609
610
if other_ctx == 0:
611
ethtool(f"-X {cfg.ifname} equal 4")
612
defer(ethtool, f"-X {cfg.ifname} default")
613
else:
614
other_ctx = ethtool_create(cfg, "-X", "context new")
615
ethtool(f"-X {cfg.ifname} context {other_ctx} equal 4")
616
defer(ethtool, f"-X {cfg.ifname} context {other_ctx} delete")
617
618
ctx_id = ethtool_create(cfg, "-X", "context new")
619
ethtool(f"-X {cfg.ifname} context {ctx_id} start 2 equal 2")
620
defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
621
622
port = rand_port()
623
if other_ctx:
624
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {other_ctx}"
625
ntuple_id = ethtool_create(cfg, "-N", flow)
626
ntuple = defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
627
628
# Test the main context
629
cnts = _get_rx_cnts(cfg)
630
GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
631
cnts = _get_rx_cnts(cfg, prev=cnts)
632
633
ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts))
634
ksft_ge(sum(cnts[ :2]), 7000, "traffic on main context (1/2): " + str(cnts))
635
ksft_ge(sum(cnts[2:4]), 7000, "traffic on main context (2/2): " + str(cnts))
636
if other_ctx == 0:
637
ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts))
638
639
# Now create a rule for context 1 and make sure traffic goes to a subset
640
if other_ctx:
641
ntuple.exec()
642
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"
643
ntuple_id = ethtool_create(cfg, "-N", flow)
644
defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
645
646
cnts = _get_rx_cnts(cfg)
647
GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000)
648
cnts = _get_rx_cnts(cfg, prev=cnts)
649
650
directed = sum(cnts[2:4])
651
ksft_lt(sum(cnts[ :2]), directed / 2, "traffic on main context: " + str(cnts))
652
ksft_ge(directed, 20000, "traffic on extra context: " + str(cnts))
653
if other_ctx == 0:
654
ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts))
655
656
657
def test_rss_context_overlap2(cfg):
658
test_rss_context_overlap(cfg, True)
659
660
661
def test_flow_add_context_missing(cfg):
662
"""
663
Test that we are not allowed to add a rule pointing to an RSS context
664
which was never created.
665
"""
666
667
require_ntuple(cfg)
668
669
# Find a context which doesn't exist
670
for ctx_id in range(1, 100):
671
try:
672
get_rss(cfg, context=ctx_id)
673
except CmdExitFailure:
674
break
675
676
with ksft_raises(CmdExitFailure) as cm:
677
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port 1234 context {ctx_id}"
678
ntuple_id = ethtool_create(cfg, "-N", flow)
679
ethtool(f"-N {cfg.ifname} delete {ntuple_id}")
680
if cm.exception:
681
ksft_in('Invalid argument', cm.exception.cmd.stderr)
682
683
684
def test_delete_rss_context_busy(cfg):
685
"""
686
Test that deletion returns -EBUSY when an rss context is being used
687
by an ntuple filter.
688
"""
689
690
require_ntuple(cfg)
691
692
# create additional rss context
693
ctx_id = ethtool_create(cfg, "-X", "context new")
694
ctx_deleter = defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
695
696
# utilize context from ntuple filter
697
port = rand_port()
698
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id}"
699
ntuple_id = ethtool_create(cfg, "-N", flow)
700
defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
701
702
# attempt to delete in-use context
703
try:
704
ctx_deleter.exec_only()
705
ctx_deleter.cancel()
706
raise KsftFailEx(f"deleted context {ctx_id} used by rule {ntuple_id}")
707
except CmdExitFailure:
708
pass
709
710
711
def test_rss_ntuple_addition(cfg):
712
"""
713
Test that the queue offset (ring_cookie) of an ntuple rule is added
714
to the queue number read from the indirection table.
715
"""
716
717
require_ntuple(cfg)
718
719
queue_cnt = len(_get_rx_cnts(cfg))
720
if queue_cnt < 4:
721
try:
722
ksft_pr(f"Increasing queue count {queue_cnt} -> 4")
723
ethtool(f"-L {cfg.ifname} combined 4")
724
defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")
725
except:
726
raise KsftSkipEx("Not enough queues for the test")
727
728
# Use queue 0 for normal traffic
729
ethtool(f"-X {cfg.ifname} equal 1")
730
defer(ethtool, f"-X {cfg.ifname} default")
731
732
# create additional rss context
733
ctx_id = ethtool_create(cfg, "-X", "context new equal 2")
734
defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
735
736
# utilize context from ntuple filter
737
port = rand_port()
738
flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id} action 2"
739
try:
740
ntuple_id = ethtool_create(cfg, "-N", flow)
741
except CmdExitFailure:
742
raise KsftSkipEx("Ntuple filter with RSS and nonzero action not supported")
743
defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")
744
745
_send_traffic_check(cfg, port, f"context {ctx_id}", { 'target': (2, 3),
746
'empty' : (1,),
747
'noise' : (0,) })
748
749
750
def test_rss_default_context_rule(cfg):
751
"""
752
Allocate a port, direct this port to context 0, then create a new RSS
753
context and steer all TCP traffic to it (context 1). Verify that:
754
* Traffic to the specific port continues to use queues of the main
755
context (0/1).
756
* Traffic to any other TCP port is redirected to the new context
757
(queues 2/3).
758
"""
759
760
require_ntuple(cfg)
761
762
queue_cnt = len(_get_rx_cnts(cfg))
763
if queue_cnt < 4:
764
try:
765
ksft_pr(f"Increasing queue count {queue_cnt} -> 4")
766
ethtool(f"-L {cfg.ifname} combined 4")
767
defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}")
768
except Exception as exc:
769
raise KsftSkipEx("Not enough queues for the test") from exc
770
771
# Use queues 0 and 1 for the main context
772
ethtool(f"-X {cfg.ifname} equal 2")
773
defer(ethtool, f"-X {cfg.ifname} default")
774
775
# Create a new RSS context that uses queues 2 and 3
776
ctx_id = ethtool_create(cfg, "-X", "context new start 2 equal 2")
777
defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
778
779
# Generic low-priority rule: redirect all TCP traffic to the new context.
780
# Give it an explicit higher location number (lower priority).
781
flow_generic = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} context {ctx_id} loc 1"
782
ethtool(f"-N {cfg.ifname} {flow_generic}")
783
defer(ethtool, f"-N {cfg.ifname} delete 1")
784
785
# Specific high-priority rule for a random port that should stay on context 0.
786
# Assign loc 0 so it is evaluated before the generic rule.
787
port_main = rand_port()
788
flow_main = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port_main} context 0 loc 0"
789
ethtool(f"-N {cfg.ifname} {flow_main}")
790
defer(ethtool, f"-N {cfg.ifname} delete 0")
791
792
_ntuple_rule_check(cfg, 1, ctx_id)
793
794
# Verify that traffic matching the specific rule still goes to queues 0/1
795
_send_traffic_check(cfg, port_main, "context 0",
796
{ 'target': (0, 1),
797
'empty' : (2, 3) })
798
799
# And that traffic for any other port is steered to the new context
800
port_other = rand_port()
801
_send_traffic_check(cfg, port_other, f"context {ctx_id}",
802
{ 'target': (2, 3),
803
'noise' : (0, 1) })
804
805
806
def main() -> None:
807
with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
808
cfg.context_cnt = None
809
cfg.ethnl = EthtoolFamily()
810
cfg.netdevnl = NetdevFamily()
811
812
ksft_run([test_rss_key_indir, test_rss_queue_reconfigure,
813
test_rss_resize, test_hitless_key_update,
814
test_rss_context, test_rss_context4, test_rss_context32,
815
test_rss_context_dump, test_rss_context_queue_reconfigure,
816
test_rss_context_overlap, test_rss_context_overlap2,
817
test_rss_context_out_of_order, test_rss_context4_create_with_cfg,
818
test_flow_add_context_missing,
819
test_delete_rss_context_busy, test_rss_ntuple_addition,
820
test_rss_default_context_rule],
821
args=(cfg, ))
822
ksft_exit()
823
824
825
if __name__ == "__main__":
826
main()
827
828