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