xref: /linux/tools/testing/selftests/drivers/net/xdp.py (revision 4ce06406958b67fdddcc2e6948237dd6ff6ba112)
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, 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}"
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
175def test_xdp_native_pass_sb(cfg):
176    """
177    Tests the XDP_PASS action for single buffer case.
178
179    Args:
180        cfg: Configuration object containing network settings.
181    """
182    bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500)
183
184    _test_pass(cfg, bpf_info, 256)
185
186
187def test_xdp_native_pass_mb(cfg):
188    """
189    Tests the XDP_PASS action for a multi-buff size.
190
191    Args:
192        cfg: Configuration object containing network settings.
193    """
194    bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000)
195
196    _test_pass(cfg, bpf_info, 8000)
197
198
199def _test_drop(cfg, bpf_info, msg_sz):
200    """
201    Tests the XDP_DROP action by exchanging UDP packets.
202
203    Args:
204        cfg: Configuration object containing network settings.
205        bpf_info: BPFProgInfo object containing information about the BPF program.
206        msg_sz: Size of the test message to send.
207    """
208
209    prog_info = _load_xdp_prog(cfg, bpf_info)
210    port = rand_port()
211
212    bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.DROP.value)
213    bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port)
214
215    ksft_eq(_test_udp(cfg, port, msg_sz), False, "UDP packet exchange should fail")
216    stats = _get_stats(prog_info["maps"]["map_xdp_stats"])
217
218    ksft_ne(stats[XDPStats.RX.value], 0, "RX stats should be zero")
219    ksft_eq(stats[XDPStats.RX.value], stats[XDPStats.DROP.value], "RX and DROP stats mismatch")
220
221
222def test_xdp_native_drop_sb(cfg):
223    """
224    Tests the XDP_DROP action for a signle-buff case.
225
226    Args:
227        cfg: Configuration object containing network settings.
228    """
229    bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500)
230
231    _test_drop(cfg, bpf_info, 256)
232
233
234def test_xdp_native_drop_mb(cfg):
235    """
236    Tests the XDP_DROP action for a multi-buff case.
237
238    Args:
239        cfg: Configuration object containing network settings.
240    """
241    bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000)
242
243    _test_drop(cfg, bpf_info, 8000)
244
245
246def _test_xdp_native_tx(cfg, bpf_info, payload_lens):
247    """
248    Tests the XDP_TX action.
249
250    Args:
251        cfg: Configuration object containing network settings.
252        bpf_info: BPFProgInfo object containing the BPF program metadata.
253        payload_lens: Array of packet lengths to send.
254    """
255    cfg.require_cmd("socat", remote=True)
256    prog_info = _load_xdp_prog(cfg, bpf_info)
257    port = rand_port()
258
259    bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.TX.value)
260    bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port)
261
262    expected_pkts = 0
263    for payload_len in payload_lens:
264        test_string = "".join(
265            random.choice(string.ascii_lowercase) for _ in range(payload_len)
266        )
267
268        rx_udp = f"socat -{cfg.addr_ipver} -T 2 " + \
269                 f"-u UDP-RECV:{port},reuseport STDOUT"
270
271        # Writing zero bytes to stdin gets ignored by socat,
272        # but with the shut-null flag socat generates a zero sized packet
273        # when the socket is closed.
274        tx_cmd_suffix = ",shut-null" if payload_len == 0 else ""
275        tx_udp = f"echo -n {test_string} | socat -t 2 " + \
276                 f"-u STDIN UDP:{cfg.baddr}:{port}{tx_cmd_suffix}"
277
278        with bkg(rx_udp, host=cfg.remote, exit_wait=True) as rnc:
279            wait_port_listen(port, proto="udp", host=cfg.remote)
280            cmd(tx_udp, host=cfg.remote, shell=True)
281
282        ksft_eq(rnc.stdout.strip(), test_string, "UDP packet exchange failed")
283
284        expected_pkts += 1
285        stats = _get_stats(prog_info["maps"]["map_xdp_stats"])
286        ksft_eq(stats[XDPStats.RX.value], expected_pkts, "RX stats mismatch")
287        ksft_eq(stats[XDPStats.TX.value], expected_pkts, "TX stats mismatch")
288
289
290def test_xdp_native_tx_sb(cfg):
291    """
292    Tests the XDP_TX action for a single-buff case.
293
294    Args:
295        cfg: Configuration object containing network settings.
296    """
297    bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500)
298
299    # Ensure there's enough room for an ETH / IP / UDP header
300    pkt_hdr_len = 42 if cfg.addr_ipver == "4" else 62
301
302    _test_xdp_native_tx(cfg, bpf_info, [0, 1500 // 2, 1500 - pkt_hdr_len])
303
304
305def test_xdp_native_tx_mb(cfg):
306    """
307    Tests the XDP_TX action for a multi-buff case.
308
309    Args:
310        cfg: Configuration object containing network settings.
311    """
312    bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o",
313                           "xdp.frags", 9000)
314    # The first packet ensures we exercise the fragmented code path.
315    # And the subsequent 0-sized packet ensures the driver
316    # reinitializes xdp_buff correctly.
317    _test_xdp_native_tx(cfg, bpf_info, [8000, 0])
318
319
320def _validate_res(res, offset_lst, pkt_sz_lst):
321    """
322    Validates the result of a test.
323
324    Args:
325        res: The result of the test, which should be a dictionary with a "status" key.
326
327    Raises:
328        KsftFailEx: If the test fails to pass any combination of offset and packet size.
329    """
330    if "status" not in res:
331        raise KsftFailEx("Missing 'status' key in result dictionary")
332
333    # Validate that not a single case was successful
334    if res["status"] == "fail":
335        if res["offset"] == offset_lst[0] and res["pkt_sz"] == pkt_sz_lst[0]:
336            raise KsftFailEx(f"{res['reason']}")
337
338        # Get the previous offset and packet size to report the successful run
339        tmp_idx = offset_lst.index(res["offset"])
340        prev_offset = offset_lst[tmp_idx - 1]
341        if tmp_idx == 0:
342            tmp_idx = pkt_sz_lst.index(res["pkt_sz"])
343            prev_pkt_sz = pkt_sz_lst[tmp_idx - 1]
344        else:
345            prev_pkt_sz = res["pkt_sz"]
346
347        # Use these values for error reporting
348        ksft_pr(
349        f"Failed run: pkt_sz {res['pkt_sz']}, offset {res['offset']}. "
350        f"Last successful run: pkt_sz {prev_pkt_sz}, offset {prev_offset}. "
351        f"Reason: {res['reason']}"
352        )
353
354
355def _check_for_failures(recvd_str, stats):
356    """
357    Checks for common failures while adjusting headroom or tailroom.
358
359    Args:
360        recvd_str: The string received from the remote host after sending a test string.
361        stats: A dictionary containing formatted packet statistics for various XDP actions.
362
363    Returns:
364        str: A string describing the failure reason if a failure is detected, otherwise None.
365    """
366
367    # Any adjustment failure result in an abort hence, we track this counter
368    if stats[XDPStats.ABORT.value] != 0:
369        return "Adjustment failed"
370
371    # Since we are using aggregate stats for a single test across all offsets and packet sizes
372    # we can't use RX stats only to track data exchange failure without taking a previous
373    # snapshot. An easier way is to simply check for non-zero length of received string.
374    if len(recvd_str) == 0:
375        return "Data exchange failed"
376
377    # Check for RX and PASS stats mismatch. Ideally, they should be equal for a successful run
378    if stats[XDPStats.RX.value] != stats[XDPStats.PASS.value]:
379        return "RX stats mismatch"
380
381    return None
382
383
384def _test_xdp_native_tail_adjst(cfg, pkt_sz_lst, offset_lst):
385    """
386    Tests the XDP tail adjustment functionality.
387
388    This function loads the appropriate XDP program based on the provided
389    program name and configures the XDP map for tail adjustment. It then
390    validates the tail adjustment by sending and receiving UDP packets
391    with specified packet sizes and offsets.
392
393    Args:
394        cfg: Configuration object containing network settings.
395        prog: Name of the XDP program to load.
396        pkt_sz_lst: List of packet sizes to test.
397        offset_lst: List of offsets to validate support for tail adjustment.
398
399    Returns:
400        dict: A dictionary with test status and failure details if applicable.
401    """
402    port = rand_port()
403    bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000)
404
405    prog_info = _load_xdp_prog(cfg, bpf_info)
406
407    # Configure the XDP map for tail adjustment
408    bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.TAIL_ADJST.value)
409    bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port)
410
411    for offset in offset_lst:
412        tag = format(random.randint(65, 90), "02x")
413
414        bpf_map_set("map_xdp_setup", TestConfig.ADJST_OFFSET.value, offset)
415        if offset > 0:
416            bpf_map_set("map_xdp_setup", TestConfig.ADJST_TAG.value, int(tag, 16))
417
418        for pkt_sz in pkt_sz_lst:
419            test_str = "".join(random.choice(string.ascii_lowercase) for _ in range(pkt_sz))
420            recvd_str = _exchg_udp(cfg, port, test_str)
421            stats = _get_stats(prog_info["maps"]["map_xdp_stats"])
422
423            failure = _check_for_failures(recvd_str, stats)
424            if failure is not None:
425                return {
426                    "status": "fail",
427                    "reason": failure,
428                    "offset": offset,
429                    "pkt_sz": pkt_sz,
430                }
431
432            # Validate data content based on offset direction
433            expected_data = None
434            if offset > 0:
435                expected_data = test_str + (offset * chr(int(tag, 16)))
436            else:
437                expected_data = test_str[0:pkt_sz + offset]
438
439            if recvd_str != expected_data:
440                return {
441                    "status": "fail",
442                    "reason": "Data mismatch",
443                    "offset": offset,
444                    "pkt_sz": pkt_sz,
445                }
446
447    return {"status": "pass"}
448
449
450def test_xdp_native_adjst_tail_grow_data(cfg):
451    """
452    Tests the XDP tail adjustment by growing packet data.
453
454    Args:
455        cfg: Configuration object containing network settings.
456    """
457    pkt_sz_lst = [512, 1024, 2048]
458    offset_lst = [1, 16, 32, 64, 128, 256]
459    res = _test_xdp_native_tail_adjst(
460        cfg,
461        pkt_sz_lst,
462        offset_lst,
463    )
464
465    _validate_res(res, offset_lst, pkt_sz_lst)
466
467
468def test_xdp_native_adjst_tail_shrnk_data(cfg):
469    """
470    Tests the XDP tail adjustment by shrinking packet data.
471
472    Args:
473        cfg: Configuration object containing network settings.
474    """
475    pkt_sz_lst = [512, 1024, 2048]
476    offset_lst = [-16, -32, -64, -128, -256]
477    res = _test_xdp_native_tail_adjst(
478        cfg,
479        pkt_sz_lst,
480        offset_lst,
481    )
482
483    _validate_res(res, offset_lst, pkt_sz_lst)
484
485
486def get_hds_thresh(cfg):
487    """
488    Retrieves the header data split (HDS) threshold for a network interface.
489
490    Args:
491        cfg: Configuration object containing network settings.
492
493    Returns:
494        The HDS threshold value. If the threshold is not supported or an error occurs,
495        a default value of 1500 is returned.
496    """
497    ethnl = cfg.ethnl
498    hds_thresh = 1500
499
500    try:
501        rings = ethnl.rings_get({'header': {'dev-index': cfg.ifindex}})
502        if 'hds-thresh' not in rings:
503            ksft_pr(f'hds-thresh not supported. Using default: {hds_thresh}')
504            return hds_thresh
505        hds_thresh = rings['hds-thresh']
506    except NlError as e:
507        ksft_pr(f"Failed to get rings: {e}. Using default: {hds_thresh}")
508
509    return hds_thresh
510
511
512def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst):
513    """
514    Tests the XDP head adjustment action for a multi-buffer case.
515
516    Args:
517        cfg: Configuration object containing network settings.
518        ethnl: Network namespace or link object (not used in this function).
519
520    This function sets up the packet size and offset lists, then performs
521    the head adjustment test by sending and receiving UDP packets.
522    """
523    cfg.require_cmd("socat", remote=True)
524
525    prog_info = _load_xdp_prog(cfg, BPFProgInfo(prog, "xdp_native.bpf.o", "xdp.frags", 9000))
526    port = rand_port()
527
528    bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.HEAD_ADJST.value)
529    bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port)
530
531    hds_thresh = get_hds_thresh(cfg)
532    for offset in offset_lst:
533        for pkt_sz in pkt_sz_lst:
534            # The "head" buffer must contain at least the Ethernet header
535            # after we eat into it. We send large-enough packets, but if HDS
536            # is enabled head will only contain headers. Don't try to eat
537            # more than 28 bytes (UDPv4 + eth hdr left: (14 + 20 + 8) - 14)
538            l2_cut_off = 28 if cfg.addr_ipver == 4 else 48
539            if pkt_sz > hds_thresh and offset > l2_cut_off:
540                ksft_pr(
541                f"Failed run: pkt_sz ({pkt_sz}) > HDS threshold ({hds_thresh}) and "
542                f"offset {offset} > {l2_cut_off}"
543                )
544                return {"status": "pass"}
545
546            test_str = ''.join(random.choice(string.ascii_lowercase) for _ in range(pkt_sz))
547            tag = format(random.randint(65, 90), '02x')
548
549            bpf_map_set("map_xdp_setup", TestConfig.ADJST_OFFSET.value, offset)
550            bpf_map_set("map_xdp_setup", TestConfig.ADJST_TAG.value, int(tag, 16))
551
552            recvd_str = _exchg_udp(cfg, port, test_str)
553
554            # Check for failures around adjustment and data exchange
555            failure = _check_for_failures(recvd_str, _get_stats(prog_info['maps']['map_xdp_stats']))
556            if failure is not None:
557                return {
558                    "status": "fail",
559                    "reason": failure,
560                    "offset": offset,
561                    "pkt_sz": pkt_sz
562                }
563
564            # Validate data content based on offset direction
565            expected_data = None
566            if offset < 0:
567                expected_data = chr(int(tag, 16)) * (0 - offset) + test_str
568            else:
569                expected_data = test_str[offset:]
570
571            if recvd_str != expected_data:
572                return {
573                    "status": "fail",
574                    "reason": "Data mismatch",
575                    "offset": offset,
576                    "pkt_sz": pkt_sz
577                }
578
579    return {"status": "pass"}
580
581
582def test_xdp_native_adjst_head_grow_data(cfg):
583    """
584    Tests the XDP headroom growth support.
585
586    Args:
587        cfg: Configuration object containing network settings.
588
589    This function sets up the packet size and offset lists, then calls the
590    _test_xdp_native_head_adjst_mb function to perform the actual test. The
591    test is passed if the headroom is successfully extended for given packet
592    sizes and offsets.
593    """
594    pkt_sz_lst = [512, 1024, 2048]
595
596    # Negative values result in headroom shrinking, resulting in growing of payload
597    offset_lst = [-16, -32, -64, -128, -256]
598    res = _test_xdp_native_head_adjst(cfg, "xdp_prog_frags", pkt_sz_lst, offset_lst)
599
600    _validate_res(res, offset_lst, pkt_sz_lst)
601
602
603def test_xdp_native_adjst_head_shrnk_data(cfg):
604    """
605    Tests the XDP headroom shrinking support.
606
607    Args:
608        cfg: Configuration object containing network settings.
609
610    This function sets up the packet size and offset lists, then calls the
611    _test_xdp_native_head_adjst_mb function to perform the actual test. The
612    test is passed if the headroom is successfully shrunk for given packet
613    sizes and offsets.
614    """
615    pkt_sz_lst = [512, 1024, 2048]
616
617    # Positive values result in headroom growing, resulting in shrinking of payload
618    offset_lst = [16, 32, 64, 128, 256]
619    res = _test_xdp_native_head_adjst(cfg, "xdp_prog_frags", pkt_sz_lst, offset_lst)
620
621    _validate_res(res, offset_lst, pkt_sz_lst)
622
623
624@ksft_variants([
625    KsftNamedVariant("pass", XDPAction.PASS),
626    KsftNamedVariant("drop", XDPAction.DROP),
627    KsftNamedVariant("tx", XDPAction.TX),
628])
629def test_xdp_native_qstats(cfg, act):
630    """
631    Send 1000 messages. Expect XDP action specified in @act.
632    Make sure the packets were counted to interface level qstats
633    (Rx, and Tx if act is TX).
634    """
635
636    cfg.require_cmd("socat")
637
638    bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500)
639    prog_info = _load_xdp_prog(cfg, bpf_info)
640    port = rand_port()
641
642    bpf_map_set("map_xdp_setup", TestConfig.MODE.value, act.value)
643    bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port)
644
645    # Discard the input, but we need a listener to avoid ICMP errors
646    rx_udp = f"socat -{cfg.addr_ipver} -T 2 -u UDP-RECV:{port},reuseport " + \
647        "/dev/null"
648    # Listener runs on "remote" in case of XDP_TX
649    rx_host = cfg.remote if act == XDPAction.TX else None
650    # We want to spew 1000 packets quickly, bash seems to do a good enough job
651    # Each reopening of the socket gives us a differenot local port (for RSS)
652    tx_udp = "for _ in `seq 20`; do " \
653        f"exec 5<>/dev/udp/{cfg.addr}/{port}; " \
654        "for i in `seq 50`; do echo a >&5; done; " \
655        "exec 5>&-; done"
656
657    cfg.wait_hw_stats_settle()
658    # Qstats have more clearly defined semantics than rtnetlink.
659    # XDP is the "first layer of the stack" so XDP packets should be counted
660    # as received and sent as if the decision was made in the routing layer.
661    before = cfg.netnl.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0]
662
663    with bkg(rx_udp, host=rx_host, exit_wait=True):
664        wait_port_listen(port, proto="udp", host=rx_host)
665        cmd(tx_udp, host=cfg.remote, shell=True)
666
667    cfg.wait_hw_stats_settle()
668    after = cfg.netnl.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0]
669
670    expected_pkts = 1000
671    ksft_ge(after['rx-packets'] - before['rx-packets'], expected_pkts)
672    if act == XDPAction.TX:
673        ksft_ge(after['tx-packets'] - before['tx-packets'], expected_pkts)
674
675    stats = _get_stats(prog_info["maps"]["map_xdp_stats"])
676    ksft_eq(stats[XDPStats.RX.value], expected_pkts, "XDP RX stats mismatch")
677    if act == XDPAction.TX:
678        ksft_eq(stats[XDPStats.TX.value], expected_pkts, "XDP TX stats mismatch")
679
680    # Flip the ring count back and forth to make sure the stats from XDP rings
681    # don't get lost.
682    chans = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
683    if chans.get('combined-count', 0) > 1:
684        cfg.ethnl.channels_set({'header': {'dev-index': cfg.ifindex},
685                                'combined-count': 1})
686        cfg.ethnl.channels_set({'header': {'dev-index': cfg.ifindex},
687                                'combined-count': chans['combined-count']})
688        before = after
689        after = cfg.netnl.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0]
690
691        ksft_ge(after['rx-packets'], before['rx-packets'])
692        if act == XDPAction.TX:
693            ksft_ge(after['tx-packets'], before['tx-packets'])
694
695
696def main():
697    """
698    Main function to execute the XDP tests.
699
700    This function runs a series of tests to validate the XDP support for
701    both the single and multi-buffer. It uses the NetDrvEpEnv context
702    manager to manage the network driver environment and the ksft_run
703    function to execute the tests.
704    """
705    with NetDrvEpEnv(__file__) as cfg:
706        cfg.ethnl = EthtoolFamily()
707        cfg.netnl = NetdevFamily()
708        ksft_run(
709            [
710                test_xdp_native_pass_sb,
711                test_xdp_native_pass_mb,
712                test_xdp_native_drop_sb,
713                test_xdp_native_drop_mb,
714                test_xdp_native_tx_sb,
715                test_xdp_native_tx_mb,
716                test_xdp_native_adjst_tail_grow_data,
717                test_xdp_native_adjst_tail_shrnk_data,
718                test_xdp_native_adjst_head_grow_data,
719                test_xdp_native_adjst_head_shrnk_data,
720                test_xdp_native_qstats,
721            ],
722            args=(cfg,))
723    ksft_exit()
724
725
726if __name__ == "__main__":
727    main()
728