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