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