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