Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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()