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