1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3 4""" 5This file contains tests to verify native XDP support in network drivers. 6The tests utilize the BPF program `xdp_native.bpf.o` from the `selftests.net.lib` 7directory, with each test focusing on a specific aspect of XDP functionality. 8""" 9import random 10import string 11from dataclasses import dataclass 12from enum import Enum 13 14from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge, ksft_ne, ksft_pr 15from lib.py import KsftNamedVariant, ksft_variants 16from lib.py import KsftFailEx, NetDrvEpEnv 17from lib.py import EthtoolFamily, NetdevFamily, NlError 18from lib.py import bkg, cmd, rand_port, wait_port_listen 19from lib.py import ip, bpftool, defer 20 21 22class TestConfig(Enum): 23 """Enum for XDP configuration options.""" 24 MODE = 0 # Configures the BPF program for a specific test 25 PORT = 1 # Port configuration to communicate with the remote host 26 ADJST_OFFSET = 2 # Tail/Head adjustment offset for extension/shrinking 27 ADJST_TAG = 3 # Adjustment tag to annotate the start and end of extension 28 29 30class XDPAction(Enum): 31 """Enum for XDP actions.""" 32 PASS = 0 # Pass the packet up to the stack 33 DROP = 1 # Drop the packet 34 TX = 2 # Route the packet to the remote host 35 TAIL_ADJST = 3 # Adjust the tail of the packet 36 HEAD_ADJST = 4 # Adjust the head of the packet 37 38 39class XDPStats(Enum): 40 """Enum for XDP statistics.""" 41 RX = 0 # Count of valid packets received for testing 42 PASS = 1 # Count of packets passed up to the stack 43 DROP = 2 # Count of packets dropped 44 TX = 3 # Count of incoming packets routed to the remote host 45 ABORT = 4 # Count of packets that were aborted 46 47 48@dataclass 49class BPFProgInfo: 50 """Data class to store information about a BPF program.""" 51 name: str # Name of the BPF program 52 file: str # BPF program object file 53 xdp_sec: str = "xdp" # XDP section name (e.g., "xdp" or "xdp.frags") 54 mtu: int = 1500 # Maximum Transmission Unit, default is 1500 55 56 57def _exchg_udp(cfg, port, test_string): 58 """ 59 Exchanges UDP packets between a local and remote host using the socat tool. 60 61 Args: 62 cfg: Configuration object containing network settings. 63 port: Port number to use for the UDP communication. 64 test_string: String that the remote host will send. 65 66 Returns: 67 The string received by the test host. 68 """ 69 cfg.require_cmd("socat", remote=True) 70 71 rx_udp_cmd = f"socat -{cfg.addr_ipver} -T 2 -u UDP-RECV:{port},reuseport STDOUT" 72 tx_udp_cmd = f"echo -n {test_string} | socat -t 2 -u STDIN UDP:{cfg.baddr}:{port}" 73 74 with bkg(rx_udp_cmd, exit_wait=True) as nc: 75 wait_port_listen(port, proto="udp") 76 cmd(tx_udp_cmd, host=cfg.remote, shell=True) 77 78 return nc.stdout.strip() 79 80 81def _test_udp(cfg, port, size=256): 82 """ 83 Tests UDP packet exchange between a local and remote host. 84 85 Args: 86 cfg: Configuration object containing network settings. 87 port: Port number to use for the UDP communication. 88 size: The length of the test string to be exchanged, default is 256 characters. 89 90 Returns: 91 bool: True if the received string matches the sent string, False otherwise. 92 """ 93 test_str = "".join(random.choice(string.ascii_lowercase) for _ in range(size)) 94 recvd_str = _exchg_udp(cfg, port, test_str) 95 96 return recvd_str == test_str 97 98 99def _load_xdp_prog(cfg, bpf_info): 100 """ 101 Loads an XDP program onto a network interface. 102 103 Args: 104 cfg: Configuration object containing network settings. 105 bpf_info: BPFProgInfo object containing information about the BPF program. 106 107 Returns: 108 dict: A dictionary containing the XDP program ID, name, and associated map IDs. 109 """ 110 abs_path = cfg.net_lib_dir / bpf_info.file 111 prog_info = {} 112 113 cmd(f"ip link set dev {cfg.remote_ifname} mtu {bpf_info.mtu}", shell=True, host=cfg.remote) 114 defer(ip, f"link set dev {cfg.remote_ifname} mtu 1500", host=cfg.remote) 115 116 cmd( 117 f"ip link set dev {cfg.ifname} mtu {bpf_info.mtu} xdpdrv obj {abs_path} sec {bpf_info.xdp_sec}", 118 shell=True 119 ) 120 defer(ip, f"link set dev {cfg.ifname} mtu 1500 xdpdrv off") 121 122 xdp_info = ip(f"-d link show dev {cfg.ifname}", json=True)[0] 123 prog_info["id"] = xdp_info["xdp"]["prog"]["id"] 124 prog_info["name"] = xdp_info["xdp"]["prog"]["name"] 125 prog_id = prog_info["id"] 126 127 map_ids = bpftool(f"prog show id {prog_id}", json=True)["map_ids"] 128 prog_info["maps"] = {} 129 for map_id in map_ids: 130 name = bpftool(f"map show id {map_id}", json=True)["name"] 131 prog_info["maps"][name] = map_id 132 133 return prog_info 134 135 136def format_hex_bytes(value): 137 """ 138 Helper function that converts an integer into a formatted hexadecimal byte string. 139 140 Args: 141 value: An integer representing the number to be converted. 142 143 Returns: 144 A string representing hexadecimal equivalent of value, with bytes separated by spaces. 145 """ 146 hex_str = value.to_bytes(4, byteorder='little', signed=True) 147 return ' '.join(f'{byte:02x}' for byte in hex_str) 148 149 150def _set_xdp_map(map_name, key, value): 151 """ 152 Updates an XDP map with a given key-value pair using bpftool. 153 154 Args: 155 map_name: The name of the XDP map to update. 156 key: The key to update in the map, formatted as a hexadecimal string. 157 value: The value to associate with the key, formatted as a hexadecimal string. 158 """ 159 key_formatted = format_hex_bytes(key) 160 value_formatted = format_hex_bytes(value) 161 bpftool( 162 f"map update name {map_name} key hex {key_formatted} value hex {value_formatted}" 163 ) 164 165 166def _get_stats(xdp_map_id): 167 """ 168 Retrieves and formats statistics from an XDP map. 169 170 Args: 171 xdp_map_id: The ID of the XDP map from which to retrieve statistics. 172 173 Returns: 174 A dictionary containing formatted packet statistics for various XDP actions. 175 The keys are based on the XDPStats Enum values. 176 177 Raises: 178 KsftFailEx: If the stats retrieval fails. 179 """ 180 stats_dump = bpftool(f"map dump id {xdp_map_id}", json=True) 181 if not stats_dump: 182 raise KsftFailEx(f"Failed to get stats for map {xdp_map_id}") 183 184 stats_formatted = {} 185 for key in range(0, 5): 186 val = stats_dump[key]["formatted"]["value"] 187 if stats_dump[key]["formatted"]["key"] == XDPStats.RX.value: 188 stats_formatted[XDPStats.RX.value] = val 189 elif stats_dump[key]["formatted"]["key"] == XDPStats.PASS.value: 190 stats_formatted[XDPStats.PASS.value] = val 191 elif stats_dump[key]["formatted"]["key"] == XDPStats.DROP.value: 192 stats_formatted[XDPStats.DROP.value] = val 193 elif stats_dump[key]["formatted"]["key"] == XDPStats.TX.value: 194 stats_formatted[XDPStats.TX.value] = val 195 elif stats_dump[key]["formatted"]["key"] == XDPStats.ABORT.value: 196 stats_formatted[XDPStats.ABORT.value] = val 197 198 return stats_formatted 199 200 201def _test_pass(cfg, bpf_info, msg_sz): 202 """ 203 Tests the XDP_PASS action by exchanging UDP packets. 204 205 Args: 206 cfg: Configuration object containing network settings. 207 bpf_info: BPFProgInfo object containing information about the BPF program. 208 msg_sz: Size of the test message to send. 209 """ 210 211 prog_info = _load_xdp_prog(cfg, bpf_info) 212 port = rand_port() 213 214 _set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.PASS.value) 215 _set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port) 216 217 ksft_eq(_test_udp(cfg, port, msg_sz), True, "UDP packet exchange failed") 218 stats = _get_stats(prog_info["maps"]["map_xdp_stats"]) 219 220 ksft_ne(stats[XDPStats.RX.value], 0, "RX stats should not be zero") 221 ksft_eq(stats[XDPStats.RX.value], stats[XDPStats.PASS.value], "RX and PASS stats mismatch") 222 223 224def test_xdp_native_pass_sb(cfg): 225 """ 226 Tests the XDP_PASS action for single buffer case. 227 228 Args: 229 cfg: Configuration object containing network settings. 230 """ 231 bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) 232 233 _test_pass(cfg, bpf_info, 256) 234 235 236def test_xdp_native_pass_mb(cfg): 237 """ 238 Tests the XDP_PASS action for a multi-buff size. 239 240 Args: 241 cfg: Configuration object containing network settings. 242 """ 243 bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000) 244 245 _test_pass(cfg, bpf_info, 8000) 246 247 248def _test_drop(cfg, bpf_info, msg_sz): 249 """ 250 Tests the XDP_DROP action by exchanging UDP packets. 251 252 Args: 253 cfg: Configuration object containing network settings. 254 bpf_info: BPFProgInfo object containing information about the BPF program. 255 msg_sz: Size of the test message to send. 256 """ 257 258 prog_info = _load_xdp_prog(cfg, bpf_info) 259 port = rand_port() 260 261 _set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.DROP.value) 262 _set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port) 263 264 ksft_eq(_test_udp(cfg, port, msg_sz), False, "UDP packet exchange should fail") 265 stats = _get_stats(prog_info["maps"]["map_xdp_stats"]) 266 267 ksft_ne(stats[XDPStats.RX.value], 0, "RX stats should be zero") 268 ksft_eq(stats[XDPStats.RX.value], stats[XDPStats.DROP.value], "RX and DROP stats mismatch") 269 270 271def test_xdp_native_drop_sb(cfg): 272 """ 273 Tests the XDP_DROP action for a signle-buff case. 274 275 Args: 276 cfg: Configuration object containing network settings. 277 """ 278 bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) 279 280 _test_drop(cfg, bpf_info, 256) 281 282 283def test_xdp_native_drop_mb(cfg): 284 """ 285 Tests the XDP_DROP action for a multi-buff case. 286 287 Args: 288 cfg: Configuration object containing network settings. 289 """ 290 bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000) 291 292 _test_drop(cfg, bpf_info, 8000) 293 294 295def _test_xdp_native_tx(cfg, bpf_info, payload_lens): 296 """ 297 Tests the XDP_TX action. 298 299 Args: 300 cfg: Configuration object containing network settings. 301 bpf_info: BPFProgInfo object containing the BPF program metadata. 302 payload_lens: Array of packet lengths to send. 303 """ 304 cfg.require_cmd("socat", remote=True) 305 prog_info = _load_xdp_prog(cfg, bpf_info) 306 port = rand_port() 307 308 _set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.TX.value) 309 _set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port) 310 311 expected_pkts = 0 312 for payload_len in payload_lens: 313 test_string = "".join( 314 random.choice(string.ascii_lowercase) for _ in range(payload_len) 315 ) 316 317 rx_udp = f"socat -{cfg.addr_ipver} -T 2 " + \ 318 f"-u UDP-RECV:{port},reuseport STDOUT" 319 320 # Writing zero bytes to stdin gets ignored by socat, 321 # but with the shut-null flag socat generates a zero sized packet 322 # when the socket is closed. 323 tx_cmd_suffix = ",shut-null" if payload_len == 0 else "" 324 tx_udp = f"echo -n {test_string} | socat -t 2 " + \ 325 f"-u STDIN UDP:{cfg.baddr}:{port}{tx_cmd_suffix}" 326 327 with bkg(rx_udp, host=cfg.remote, exit_wait=True) as rnc: 328 wait_port_listen(port, proto="udp", host=cfg.remote) 329 cmd(tx_udp, host=cfg.remote, shell=True) 330 331 ksft_eq(rnc.stdout.strip(), test_string, "UDP packet exchange failed") 332 333 expected_pkts += 1 334 stats = _get_stats(prog_info["maps"]["map_xdp_stats"]) 335 ksft_eq(stats[XDPStats.RX.value], expected_pkts, "RX stats mismatch") 336 ksft_eq(stats[XDPStats.TX.value], expected_pkts, "TX stats mismatch") 337 338 339def test_xdp_native_tx_sb(cfg): 340 """ 341 Tests the XDP_TX action for a single-buff case. 342 343 Args: 344 cfg: Configuration object containing network settings. 345 """ 346 bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) 347 348 # Ensure there's enough room for an ETH / IP / UDP header 349 pkt_hdr_len = 42 if cfg.addr_ipver == "4" else 62 350 351 _test_xdp_native_tx(cfg, bpf_info, [0, 1500 // 2, 1500 - pkt_hdr_len]) 352 353 354def test_xdp_native_tx_mb(cfg): 355 """ 356 Tests the XDP_TX action for a multi-buff case. 357 358 Args: 359 cfg: Configuration object containing network settings. 360 """ 361 bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", 362 "xdp.frags", 9000) 363 # The first packet ensures we exercise the fragmented code path. 364 # And the subsequent 0-sized packet ensures the driver 365 # reinitializes xdp_buff correctly. 366 _test_xdp_native_tx(cfg, bpf_info, [8000, 0]) 367 368 369def _validate_res(res, offset_lst, pkt_sz_lst): 370 """ 371 Validates the result of a test. 372 373 Args: 374 res: The result of the test, which should be a dictionary with a "status" key. 375 376 Raises: 377 KsftFailEx: If the test fails to pass any combination of offset and packet size. 378 """ 379 if "status" not in res: 380 raise KsftFailEx("Missing 'status' key in result dictionary") 381 382 # Validate that not a single case was successful 383 if res["status"] == "fail": 384 if res["offset"] == offset_lst[0] and res["pkt_sz"] == pkt_sz_lst[0]: 385 raise KsftFailEx(f"{res['reason']}") 386 387 # Get the previous offset and packet size to report the successful run 388 tmp_idx = offset_lst.index(res["offset"]) 389 prev_offset = offset_lst[tmp_idx - 1] 390 if tmp_idx == 0: 391 tmp_idx = pkt_sz_lst.index(res["pkt_sz"]) 392 prev_pkt_sz = pkt_sz_lst[tmp_idx - 1] 393 else: 394 prev_pkt_sz = res["pkt_sz"] 395 396 # Use these values for error reporting 397 ksft_pr( 398 f"Failed run: pkt_sz {res['pkt_sz']}, offset {res['offset']}. " 399 f"Last successful run: pkt_sz {prev_pkt_sz}, offset {prev_offset}. " 400 f"Reason: {res['reason']}" 401 ) 402 403 404def _check_for_failures(recvd_str, stats): 405 """ 406 Checks for common failures while adjusting headroom or tailroom. 407 408 Args: 409 recvd_str: The string received from the remote host after sending a test string. 410 stats: A dictionary containing formatted packet statistics for various XDP actions. 411 412 Returns: 413 str: A string describing the failure reason if a failure is detected, otherwise None. 414 """ 415 416 # Any adjustment failure result in an abort hence, we track this counter 417 if stats[XDPStats.ABORT.value] != 0: 418 return "Adjustment failed" 419 420 # Since we are using aggregate stats for a single test across all offsets and packet sizes 421 # we can't use RX stats only to track data exchange failure without taking a previous 422 # snapshot. An easier way is to simply check for non-zero length of received string. 423 if len(recvd_str) == 0: 424 return "Data exchange failed" 425 426 # Check for RX and PASS stats mismatch. Ideally, they should be equal for a successful run 427 if stats[XDPStats.RX.value] != stats[XDPStats.PASS.value]: 428 return "RX stats mismatch" 429 430 return None 431 432 433def _test_xdp_native_tail_adjst(cfg, pkt_sz_lst, offset_lst): 434 """ 435 Tests the XDP tail adjustment functionality. 436 437 This function loads the appropriate XDP program based on the provided 438 program name and configures the XDP map for tail adjustment. It then 439 validates the tail adjustment by sending and receiving UDP packets 440 with specified packet sizes and offsets. 441 442 Args: 443 cfg: Configuration object containing network settings. 444 prog: Name of the XDP program to load. 445 pkt_sz_lst: List of packet sizes to test. 446 offset_lst: List of offsets to validate support for tail adjustment. 447 448 Returns: 449 dict: A dictionary with test status and failure details if applicable. 450 """ 451 port = rand_port() 452 bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000) 453 454 prog_info = _load_xdp_prog(cfg, bpf_info) 455 456 # Configure the XDP map for tail adjustment 457 _set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.TAIL_ADJST.value) 458 _set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port) 459 460 for offset in offset_lst: 461 tag = format(random.randint(65, 90), "02x") 462 463 _set_xdp_map("map_xdp_setup", TestConfig.ADJST_OFFSET.value, offset) 464 if offset > 0: 465 _set_xdp_map("map_xdp_setup", TestConfig.ADJST_TAG.value, int(tag, 16)) 466 467 for pkt_sz in pkt_sz_lst: 468 test_str = "".join(random.choice(string.ascii_lowercase) for _ in range(pkt_sz)) 469 recvd_str = _exchg_udp(cfg, port, test_str) 470 stats = _get_stats(prog_info["maps"]["map_xdp_stats"]) 471 472 failure = _check_for_failures(recvd_str, stats) 473 if failure is not None: 474 return { 475 "status": "fail", 476 "reason": failure, 477 "offset": offset, 478 "pkt_sz": pkt_sz, 479 } 480 481 # Validate data content based on offset direction 482 expected_data = None 483 if offset > 0: 484 expected_data = test_str + (offset * chr(int(tag, 16))) 485 else: 486 expected_data = test_str[0:pkt_sz + offset] 487 488 if recvd_str != expected_data: 489 return { 490 "status": "fail", 491 "reason": "Data mismatch", 492 "offset": offset, 493 "pkt_sz": pkt_sz, 494 } 495 496 return {"status": "pass"} 497 498 499def test_xdp_native_adjst_tail_grow_data(cfg): 500 """ 501 Tests the XDP tail adjustment by growing packet data. 502 503 Args: 504 cfg: Configuration object containing network settings. 505 """ 506 pkt_sz_lst = [512, 1024, 2048] 507 offset_lst = [1, 16, 32, 64, 128, 256] 508 res = _test_xdp_native_tail_adjst( 509 cfg, 510 pkt_sz_lst, 511 offset_lst, 512 ) 513 514 _validate_res(res, offset_lst, pkt_sz_lst) 515 516 517def test_xdp_native_adjst_tail_shrnk_data(cfg): 518 """ 519 Tests the XDP tail adjustment by shrinking packet data. 520 521 Args: 522 cfg: Configuration object containing network settings. 523 """ 524 pkt_sz_lst = [512, 1024, 2048] 525 offset_lst = [-16, -32, -64, -128, -256] 526 res = _test_xdp_native_tail_adjst( 527 cfg, 528 pkt_sz_lst, 529 offset_lst, 530 ) 531 532 _validate_res(res, offset_lst, pkt_sz_lst) 533 534 535def get_hds_thresh(cfg): 536 """ 537 Retrieves the header data split (HDS) threshold for a network interface. 538 539 Args: 540 cfg: Configuration object containing network settings. 541 542 Returns: 543 The HDS threshold value. If the threshold is not supported or an error occurs, 544 a default value of 1500 is returned. 545 """ 546 ethnl = cfg.ethnl 547 hds_thresh = 1500 548 549 try: 550 rings = ethnl.rings_get({'header': {'dev-index': cfg.ifindex}}) 551 if 'hds-thresh' not in rings: 552 ksft_pr(f'hds-thresh not supported. Using default: {hds_thresh}') 553 return hds_thresh 554 hds_thresh = rings['hds-thresh'] 555 except NlError as e: 556 ksft_pr(f"Failed to get rings: {e}. Using default: {hds_thresh}") 557 558 return hds_thresh 559 560 561def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst): 562 """ 563 Tests the XDP head adjustment action for a multi-buffer case. 564 565 Args: 566 cfg: Configuration object containing network settings. 567 ethnl: Network namespace or link object (not used in this function). 568 569 This function sets up the packet size and offset lists, then performs 570 the head adjustment test by sending and receiving UDP packets. 571 """ 572 cfg.require_cmd("socat", remote=True) 573 574 prog_info = _load_xdp_prog(cfg, BPFProgInfo(prog, "xdp_native.bpf.o", "xdp.frags", 9000)) 575 port = rand_port() 576 577 _set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.HEAD_ADJST.value) 578 _set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port) 579 580 hds_thresh = get_hds_thresh(cfg) 581 for offset in offset_lst: 582 for pkt_sz in pkt_sz_lst: 583 # The "head" buffer must contain at least the Ethernet header 584 # after we eat into it. We send large-enough packets, but if HDS 585 # is enabled head will only contain headers. Don't try to eat 586 # more than 28 bytes (UDPv4 + eth hdr left: (14 + 20 + 8) - 14) 587 l2_cut_off = 28 if cfg.addr_ipver == 4 else 48 588 if pkt_sz > hds_thresh and offset > l2_cut_off: 589 ksft_pr( 590 f"Failed run: pkt_sz ({pkt_sz}) > HDS threshold ({hds_thresh}) and " 591 f"offset {offset} > {l2_cut_off}" 592 ) 593 return {"status": "pass"} 594 595 test_str = ''.join(random.choice(string.ascii_lowercase) for _ in range(pkt_sz)) 596 tag = format(random.randint(65, 90), '02x') 597 598 _set_xdp_map("map_xdp_setup", 599 TestConfig.ADJST_OFFSET.value, 600 offset) 601 _set_xdp_map("map_xdp_setup", TestConfig.ADJST_TAG.value, int(tag, 16)) 602 _set_xdp_map("map_xdp_setup", TestConfig.ADJST_OFFSET.value, offset) 603 604 recvd_str = _exchg_udp(cfg, port, test_str) 605 606 # Check for failures around adjustment and data exchange 607 failure = _check_for_failures(recvd_str, _get_stats(prog_info['maps']['map_xdp_stats'])) 608 if failure is not None: 609 return { 610 "status": "fail", 611 "reason": failure, 612 "offset": offset, 613 "pkt_sz": pkt_sz 614 } 615 616 # Validate data content based on offset direction 617 expected_data = None 618 if offset < 0: 619 expected_data = chr(int(tag, 16)) * (0 - offset) + test_str 620 else: 621 expected_data = test_str[offset:] 622 623 if recvd_str != expected_data: 624 return { 625 "status": "fail", 626 "reason": "Data mismatch", 627 "offset": offset, 628 "pkt_sz": pkt_sz 629 } 630 631 return {"status": "pass"} 632 633 634def test_xdp_native_adjst_head_grow_data(cfg): 635 """ 636 Tests the XDP headroom growth support. 637 638 Args: 639 cfg: Configuration object containing network settings. 640 641 This function sets up the packet size and offset lists, then calls the 642 _test_xdp_native_head_adjst_mb function to perform the actual test. The 643 test is passed if the headroom is successfully extended for given packet 644 sizes and offsets. 645 """ 646 pkt_sz_lst = [512, 1024, 2048] 647 648 # Negative values result in headroom shrinking, resulting in growing of payload 649 offset_lst = [-16, -32, -64, -128, -256] 650 res = _test_xdp_native_head_adjst(cfg, "xdp_prog_frags", pkt_sz_lst, offset_lst) 651 652 _validate_res(res, offset_lst, pkt_sz_lst) 653 654 655def test_xdp_native_adjst_head_shrnk_data(cfg): 656 """ 657 Tests the XDP headroom shrinking support. 658 659 Args: 660 cfg: Configuration object containing network settings. 661 662 This function sets up the packet size and offset lists, then calls the 663 _test_xdp_native_head_adjst_mb function to perform the actual test. The 664 test is passed if the headroom is successfully shrunk for given packet 665 sizes and offsets. 666 """ 667 pkt_sz_lst = [512, 1024, 2048] 668 669 # Positive values result in headroom growing, resulting in shrinking of payload 670 offset_lst = [16, 32, 64, 128, 256] 671 res = _test_xdp_native_head_adjst(cfg, "xdp_prog_frags", pkt_sz_lst, offset_lst) 672 673 _validate_res(res, offset_lst, pkt_sz_lst) 674 675 676@ksft_variants([ 677 KsftNamedVariant("pass", XDPAction.PASS), 678 KsftNamedVariant("drop", XDPAction.DROP), 679 KsftNamedVariant("tx", XDPAction.TX), 680]) 681def test_xdp_native_qstats(cfg, act): 682 """ 683 Send 1000 messages. Expect XDP action specified in @act. 684 Make sure the packets were counted to interface level qstats 685 (Rx, and Tx if act is TX). 686 """ 687 688 cfg.require_cmd("socat") 689 690 bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500) 691 prog_info = _load_xdp_prog(cfg, bpf_info) 692 port = rand_port() 693 694 _set_xdp_map("map_xdp_setup", TestConfig.MODE.value, act.value) 695 _set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port) 696 697 # Discard the input, but we need a listener to avoid ICMP errors 698 rx_udp = f"socat -{cfg.addr_ipver} -T 2 -u UDP-RECV:{port},reuseport " + \ 699 "/dev/null" 700 # Listener runs on "remote" in case of XDP_TX 701 rx_host = cfg.remote if act == XDPAction.TX else None 702 # We want to spew 1000 packets quickly, bash seems to do a good enough job 703 # Each reopening of the socket gives us a differenot local port (for RSS) 704 tx_udp = "for _ in `seq 20`; do " \ 705 f"exec 5<>/dev/udp/{cfg.addr}/{port}; " \ 706 "for i in `seq 50`; do echo a >&5; done; " \ 707 "exec 5>&-; done" 708 709 cfg.wait_hw_stats_settle() 710 # Qstats have more clearly defined semantics than rtnetlink. 711 # XDP is the "first layer of the stack" so XDP packets should be counted 712 # as received and sent as if the decision was made in the routing layer. 713 before = cfg.netnl.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0] 714 715 with bkg(rx_udp, host=rx_host, exit_wait=True): 716 wait_port_listen(port, proto="udp", host=rx_host) 717 cmd(tx_udp, host=cfg.remote, shell=True) 718 719 cfg.wait_hw_stats_settle() 720 after = cfg.netnl.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0] 721 722 expected_pkts = 1000 723 ksft_ge(after['rx-packets'] - before['rx-packets'], expected_pkts) 724 if act == XDPAction.TX: 725 ksft_ge(after['tx-packets'] - before['tx-packets'], expected_pkts) 726 727 stats = _get_stats(prog_info["maps"]["map_xdp_stats"]) 728 ksft_eq(stats[XDPStats.RX.value], expected_pkts, "XDP RX stats mismatch") 729 if act == XDPAction.TX: 730 ksft_eq(stats[XDPStats.TX.value], expected_pkts, "XDP TX stats mismatch") 731 732 # Flip the ring count back and forth to make sure the stats from XDP rings 733 # don't get lost. 734 chans = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}}) 735 if chans.get('combined-count', 0) > 1: 736 cfg.ethnl.channels_set({'header': {'dev-index': cfg.ifindex}, 737 'combined-count': 1}) 738 cfg.ethnl.channels_set({'header': {'dev-index': cfg.ifindex}, 739 'combined-count': chans['combined-count']}) 740 before = after 741 after = cfg.netnl.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0] 742 743 ksft_ge(after['rx-packets'], before['rx-packets']) 744 if act == XDPAction.TX: 745 ksft_ge(after['tx-packets'], before['tx-packets']) 746 747 748def main(): 749 """ 750 Main function to execute the XDP tests. 751 752 This function runs a series of tests to validate the XDP support for 753 both the single and multi-buffer. It uses the NetDrvEpEnv context 754 manager to manage the network driver environment and the ksft_run 755 function to execute the tests. 756 """ 757 with NetDrvEpEnv(__file__) as cfg: 758 cfg.ethnl = EthtoolFamily() 759 cfg.netnl = NetdevFamily() 760 ksft_run( 761 [ 762 test_xdp_native_pass_sb, 763 test_xdp_native_pass_mb, 764 test_xdp_native_drop_sb, 765 test_xdp_native_drop_mb, 766 test_xdp_native_tx_sb, 767 test_xdp_native_tx_mb, 768 test_xdp_native_adjst_tail_grow_data, 769 test_xdp_native_adjst_tail_shrnk_data, 770 test_xdp_native_adjst_head_grow_data, 771 test_xdp_native_adjst_head_shrnk_data, 772 test_xdp_native_qstats, 773 ], 774 args=(cfg,)) 775 ksft_exit() 776 777 778if __name__ == "__main__": 779 main() 780