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