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