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