xref: /linux/tools/testing/selftests/drivers/net/xdp.py (revision 8f7aa3d3c7323f4ca2768a9e74ebbe359c4f8f88)
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