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