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, KsftFailEx 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-ip {cfg.addr} 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 if not main_ctx: 242 ethtool(f"-L {cfg.ifname} combined 4") 243 flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id} action 1" 244 try: 245 # this targets queue 4, which doesn't exist 246 ntuple2 = ethtool_create(cfg, "-N", flow) 247 except CmdExitFailure: 248 pass 249 else: 250 raise Exception(f"Driver didn't prevent us from targeting a nonexistent queue (context {ctx_id})") 251 # change the table to target queues 0 and 2 252 ethtool(f"-X {cfg.ifname} {ctx_ref} weight 1 0 1 0") 253 # ntuple rule therefore targets queues 1 and 3 254 ntuple2 = ethtool_create(cfg, "-N", flow) 255 # should replace existing filter 256 ksft_eq(ntuple, ntuple2) 257 _send_traffic_check(cfg, port, ctx_ref, { 'target': (1, 3), 258 'noise' : (0, 2) }) 259 # Setting queue count to 3 should fail, queue 3 is used 260 try: 261 ethtool(f"-L {cfg.ifname} combined 3") 262 except CmdExitFailure: 263 pass 264 else: 265 raise Exception(f"Driver didn't prevent us from deactivating a used queue (context {ctx_id})") 266 267 268def test_rss_resize(cfg): 269 """Test resizing of the RSS table. 270 271 Some devices dynamically increase and decrease the size of the RSS 272 indirection table based on the number of enabled queues. 273 When that happens driver must maintain the balance of entries 274 (preferably duplicating the smaller table). 275 """ 276 277 channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}}) 278 ch_max = channels['combined-max'] 279 qcnt = channels['combined-count'] 280 281 if ch_max < 2: 282 raise KsftSkipEx(f"Not enough queues for the test: {ch_max}") 283 284 ethtool(f"-L {cfg.ifname} combined 2") 285 defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") 286 287 ethtool(f"-X {cfg.ifname} weight 1 7") 288 defer(ethtool, f"-X {cfg.ifname} default") 289 290 ethtool(f"-L {cfg.ifname} combined {ch_max}") 291 data = get_rss(cfg) 292 ksft_eq(0, min(data['rss-indirection-table'])) 293 ksft_eq(1, max(data['rss-indirection-table'])) 294 295 ksft_eq(7, 296 data['rss-indirection-table'].count(1) / 297 data['rss-indirection-table'].count(0), 298 f"Table imbalance after resize: {data['rss-indirection-table']}") 299 300 301def test_hitless_key_update(cfg): 302 """Test that flows may be rehashed without impacting traffic. 303 304 Some workloads may want to rehash the flows in response to an imbalance. 305 Most effective way to do that is changing the RSS key. Check that changing 306 the key does not cause link flaps or traffic disruption. 307 308 Disrupting traffic for key update is not a bug, but makes the key 309 update unusable for rehashing under load. 310 """ 311 data = get_rss(cfg) 312 key_len = len(data['rss-hash-key']) 313 314 key = _rss_key_rand(key_len) 315 316 tgen = GenerateTraffic(cfg) 317 try: 318 errors0, carrier0 = get_drop_err_sum(cfg) 319 t0 = datetime.datetime.now() 320 ethtool(f"-X {cfg.ifname} hkey " + _rss_key_str(key)) 321 t1 = datetime.datetime.now() 322 errors1, carrier1 = get_drop_err_sum(cfg) 323 finally: 324 tgen.wait_pkts_and_stop(5000) 325 326 ksft_lt((t1 - t0).total_seconds(), 0.2) 327 ksft_eq(errors1 - errors1, 0) 328 ksft_eq(carrier1 - carrier0, 0) 329 330 331def test_rss_context_dump(cfg): 332 """ 333 Test dumping RSS contexts. This tests mostly exercises the kernel APIs. 334 """ 335 336 # Get a random key of the right size 337 data = get_rss(cfg) 338 if 'rss-hash-key' in data: 339 key_data = _rss_key_rand(len(data['rss-hash-key'])) 340 key = _rss_key_str(key_data) 341 else: 342 key_data = [] 343 key = "ba:ad" 344 345 ids = [] 346 try: 347 ids.append(ethtool_create(cfg, "-X", f"context new")) 348 defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete") 349 350 ids.append(ethtool_create(cfg, "-X", f"context new weight 1 1")) 351 defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete") 352 353 ids.append(ethtool_create(cfg, "-X", f"context new hkey {key}")) 354 defer(ethtool, f"-X {cfg.ifname} context {ids[-1]} delete") 355 except CmdExitFailure: 356 if not ids: 357 raise KsftSkipEx("Unable to add any contexts") 358 ksft_pr(f"Added only {len(ids)} out of 3 contexts") 359 360 expect_tuples = set([(cfg.ifname, -1)] + [(cfg.ifname, ctx_id) for ctx_id in ids]) 361 362 # Dump all 363 ctxs = cfg.ethnl.rss_get({}, dump=True) 364 tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs] 365 ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump") 366 ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname]) 367 ksft_eq(expect_tuples, ctx_tuples) 368 369 # Sanity-check the results 370 for data in ctxs: 371 ksft_ne(set(data['indir']), {0}, "indir table is all zero") 372 ksft_ne(set(data.get('hkey', [1])), {0}, "key is all zero") 373 374 # More specific checks 375 if len(ids) > 1 and data.get('context') == ids[1]: 376 ksft_eq(set(data['indir']), {0, 1}, 377 "ctx1 - indir table mismatch") 378 if len(ids) > 2 and data.get('context') == ids[2]: 379 ksft_eq(data['hkey'], bytes(key_data), "ctx2 - key mismatch") 380 381 # Ifindex filter 382 ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}}, dump=True) 383 tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs] 384 ctx_tuples = set(tuples) 385 ksft_eq(len(tuples), len(ctx_tuples), "duplicates in context dump") 386 ksft_eq(expect_tuples, ctx_tuples) 387 388 # Skip ctx 0 389 expect_tuples.remove((cfg.ifname, -1)) 390 391 ctxs = cfg.ethnl.rss_get({'start-context': 1}, dump=True) 392 tuples = [(c['header']['dev-name'], c.get('context', -1)) for c in ctxs] 393 ksft_eq(len(tuples), len(set(tuples)), "duplicates in context dump") 394 ctx_tuples = set([ctx for ctx in tuples if ctx[0] == cfg.ifname]) 395 ksft_eq(expect_tuples, ctx_tuples) 396 397 # And finally both with ifindex and skip main 398 ctxs = cfg.ethnl.rss_get({'header': {'dev-name': cfg.ifname}, 'start-context': 1}, dump=True) 399 ctx_tuples = set([(c['header']['dev-name'], c.get('context', -1)) for c in ctxs]) 400 ksft_eq(expect_tuples, ctx_tuples) 401 402 403def test_rss_context(cfg, ctx_cnt=1, create_with_cfg=None): 404 """ 405 Test separating traffic into RSS contexts. 406 The queues will be allocated 2 for each context: 407 ctx0 ctx1 ctx2 ctx3 408 [0 1] [2 3] [4 5] [6 7] ... 409 """ 410 411 require_ntuple(cfg) 412 413 requested_ctx_cnt = ctx_cnt 414 415 # Try to allocate more queues when necessary 416 qcnt = len(_get_rx_cnts(cfg)) 417 if qcnt < 2 + 2 * ctx_cnt: 418 try: 419 ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}") 420 ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}") 421 defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") 422 except: 423 raise KsftSkipEx("Not enough queues for the test") 424 425 ports = [] 426 427 # Use queues 0 and 1 for normal traffic 428 ethtool(f"-X {cfg.ifname} equal 2") 429 defer(ethtool, f"-X {cfg.ifname} default") 430 431 for i in range(ctx_cnt): 432 want_cfg = f"start {2 + i * 2} equal 2" 433 create_cfg = want_cfg if create_with_cfg else "" 434 435 try: 436 ctx_id = ethtool_create(cfg, "-X", f"context new {create_cfg}") 437 defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete") 438 except CmdExitFailure: 439 # try to carry on and skip at the end 440 if i == 0: 441 raise 442 ksft_pr(f"Failed to create context {i + 1}, trying to test what we got") 443 ctx_cnt = i 444 break 445 446 _rss_key_check(cfg, context=ctx_id) 447 448 if not create_with_cfg: 449 ethtool(f"-X {cfg.ifname} context {ctx_id} {want_cfg}") 450 _rss_key_check(cfg, context=ctx_id) 451 452 # Sanity check the context we just created 453 data = get_rss(cfg, ctx_id) 454 ksft_eq(min(data['rss-indirection-table']), 2 + i * 2, "Unexpected context cfg: " + str(data)) 455 ksft_eq(max(data['rss-indirection-table']), 2 + i * 2 + 1, "Unexpected context cfg: " + str(data)) 456 457 ports.append(rand_port()) 458 flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {ports[i]} context {ctx_id}" 459 ntuple = ethtool_create(cfg, "-N", flow) 460 defer(ethtool, f"-N {cfg.ifname} delete {ntuple}") 461 462 for i in range(ctx_cnt): 463 _send_traffic_check(cfg, ports[i], f"context {i}", 464 { 'target': (2+i*2, 3+i*2), 465 'noise': (0, 1), 466 'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt)) }) 467 468 if requested_ctx_cnt != ctx_cnt: 469 raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}") 470 471 472def test_rss_context4(cfg): 473 test_rss_context(cfg, 4) 474 475 476def test_rss_context32(cfg): 477 test_rss_context(cfg, 32) 478 479 480def test_rss_context4_create_with_cfg(cfg): 481 test_rss_context(cfg, 4, create_with_cfg=True) 482 483 484def test_rss_context_queue_reconfigure(cfg): 485 test_rss_queue_reconfigure(cfg, main_ctx=False) 486 487 488def test_rss_context_out_of_order(cfg, ctx_cnt=4): 489 """ 490 Test separating traffic into RSS contexts. 491 Contexts are removed in semi-random order, and steering re-tested 492 to make sure removal doesn't break steering to surviving contexts. 493 Test requires 3 contexts to work. 494 """ 495 496 require_ntuple(cfg) 497 498 requested_ctx_cnt = ctx_cnt 499 500 # Try to allocate more queues when necessary 501 qcnt = len(_get_rx_cnts(cfg)) 502 if qcnt < 2 + 2 * ctx_cnt: 503 try: 504 ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}") 505 ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}") 506 defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") 507 except: 508 raise KsftSkipEx("Not enough queues for the test") 509 510 ntuple = [] 511 ctx = [] 512 ports = [] 513 514 def remove_ctx(idx): 515 ntuple[idx].exec() 516 ntuple[idx] = None 517 ctx[idx].exec() 518 ctx[idx] = None 519 520 def check_traffic(): 521 for i in range(ctx_cnt): 522 if ctx[i]: 523 expected = { 524 'target': (2+i*2, 3+i*2), 525 'noise': (0, 1), 526 'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt)) 527 } 528 else: 529 expected = { 530 'target': (0, 1), 531 'empty': range(2, 2+2*ctx_cnt) 532 } 533 534 _send_traffic_check(cfg, ports[i], f"context {i}", expected) 535 536 # Use queues 0 and 1 for normal traffic 537 ethtool(f"-X {cfg.ifname} equal 2") 538 defer(ethtool, f"-X {cfg.ifname} default") 539 540 for i in range(ctx_cnt): 541 ctx_id = ethtool_create(cfg, "-X", f"context new start {2 + i * 2} equal 2") 542 ctx.append(defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")) 543 544 ports.append(rand_port()) 545 flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {ports[i]} context {ctx_id}" 546 ntuple_id = ethtool_create(cfg, "-N", flow) 547 ntuple.append(defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")) 548 549 check_traffic() 550 551 # Remove middle context 552 remove_ctx(ctx_cnt // 2) 553 check_traffic() 554 555 # Remove first context 556 remove_ctx(0) 557 check_traffic() 558 559 # Remove last context 560 remove_ctx(-1) 561 check_traffic() 562 563 if requested_ctx_cnt != ctx_cnt: 564 raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}") 565 566 567def test_rss_context_overlap(cfg, other_ctx=0): 568 """ 569 Test contexts overlapping with each other. 570 Use 4 queues for the main context, but only queues 2 and 3 for context 1. 571 """ 572 573 require_ntuple(cfg) 574 575 queue_cnt = len(_get_rx_cnts(cfg)) 576 if queue_cnt < 4: 577 try: 578 ksft_pr(f"Increasing queue count {queue_cnt} -> 4") 579 ethtool(f"-L {cfg.ifname} combined 4") 580 defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}") 581 except: 582 raise KsftSkipEx("Not enough queues for the test") 583 584 if other_ctx == 0: 585 ethtool(f"-X {cfg.ifname} equal 4") 586 defer(ethtool, f"-X {cfg.ifname} default") 587 else: 588 other_ctx = ethtool_create(cfg, "-X", "context new") 589 ethtool(f"-X {cfg.ifname} context {other_ctx} equal 4") 590 defer(ethtool, f"-X {cfg.ifname} context {other_ctx} delete") 591 592 ctx_id = ethtool_create(cfg, "-X", "context new") 593 ethtool(f"-X {cfg.ifname} context {ctx_id} start 2 equal 2") 594 defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete") 595 596 port = rand_port() 597 if other_ctx: 598 flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {other_ctx}" 599 ntuple_id = ethtool_create(cfg, "-N", flow) 600 ntuple = defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}") 601 602 # Test the main context 603 cnts = _get_rx_cnts(cfg) 604 GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) 605 cnts = _get_rx_cnts(cfg, prev=cnts) 606 607 ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts)) 608 ksft_ge(sum(cnts[ :2]), 7000, "traffic on main context (1/2): " + str(cnts)) 609 ksft_ge(sum(cnts[2:4]), 7000, "traffic on main context (2/2): " + str(cnts)) 610 if other_ctx == 0: 611 ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts)) 612 613 # Now create a rule for context 1 and make sure traffic goes to a subset 614 if other_ctx: 615 ntuple.exec() 616 flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id}" 617 ntuple_id = ethtool_create(cfg, "-N", flow) 618 defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}") 619 620 cnts = _get_rx_cnts(cfg) 621 GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) 622 cnts = _get_rx_cnts(cfg, prev=cnts) 623 624 directed = sum(cnts[2:4]) 625 ksft_lt(sum(cnts[ :2]), directed / 2, "traffic on main context: " + str(cnts)) 626 ksft_ge(directed, 20000, "traffic on extra context: " + str(cnts)) 627 if other_ctx == 0: 628 ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts)) 629 630 631def test_rss_context_overlap2(cfg): 632 test_rss_context_overlap(cfg, True) 633 634 635def test_delete_rss_context_busy(cfg): 636 """ 637 Test that deletion returns -EBUSY when an rss context is being used 638 by an ntuple filter. 639 """ 640 641 require_ntuple(cfg) 642 643 # create additional rss context 644 ctx_id = ethtool_create(cfg, "-X", "context new") 645 ctx_deleter = defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete") 646 647 # utilize context from ntuple filter 648 port = rand_port() 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 # attempt to delete in-use context 654 try: 655 ctx_deleter.exec_only() 656 ctx_deleter.cancel() 657 raise KsftFailEx(f"deleted context {ctx_id} used by rule {ntuple_id}") 658 except CmdExitFailure: 659 pass 660 661 662def test_rss_ntuple_addition(cfg): 663 """ 664 Test that the queue offset (ring_cookie) of an ntuple rule is added 665 to the queue number read from the indirection table. 666 """ 667 668 require_ntuple(cfg) 669 670 queue_cnt = len(_get_rx_cnts(cfg)) 671 if queue_cnt < 4: 672 try: 673 ksft_pr(f"Increasing queue count {queue_cnt} -> 4") 674 ethtool(f"-L {cfg.ifname} combined 4") 675 defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}") 676 except: 677 raise KsftSkipEx("Not enough queues for the test") 678 679 # Use queue 0 for normal traffic 680 ethtool(f"-X {cfg.ifname} equal 1") 681 defer(ethtool, f"-X {cfg.ifname} default") 682 683 # create additional rss context 684 ctx_id = ethtool_create(cfg, "-X", "context new equal 2") 685 defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete") 686 687 # utilize context from ntuple filter 688 port = rand_port() 689 flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} context {ctx_id} action 2" 690 try: 691 ntuple_id = ethtool_create(cfg, "-N", flow) 692 except CmdExitFailure: 693 raise KsftSkipEx("Ntuple filter with RSS and nonzero action not supported") 694 defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}") 695 696 _send_traffic_check(cfg, port, f"context {ctx_id}", { 'target': (2, 3), 697 'empty' : (1,), 698 'noise' : (0,) }) 699 700 701def main() -> None: 702 with NetDrvEpEnv(__file__, nsim_test=False) as cfg: 703 cfg.ethnl = EthtoolFamily() 704 cfg.netdevnl = NetdevFamily() 705 706 ksft_run([test_rss_key_indir, test_rss_queue_reconfigure, 707 test_rss_resize, test_hitless_key_update, 708 test_rss_context, test_rss_context4, test_rss_context32, 709 test_rss_context_dump, test_rss_context_queue_reconfigure, 710 test_rss_context_overlap, test_rss_context_overlap2, 711 test_rss_context_out_of_order, test_rss_context4_create_with_cfg, 712 test_delete_rss_context_busy, test_rss_ntuple_addition], 713 args=(cfg, )) 714 ksft_exit() 715 716 717if __name__ == "__main__": 718 main() 719