Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/testing/selftests/drivers/net/xdp.py
49639 views
1
#!/usr/bin/env python3
2
# SPDX-License-Identifier: GPL-2.0
3
4
"""
5
This file contains tests to verify native XDP support in network drivers.
6
The tests utilize the BPF program `xdp_native.bpf.o` from the `selftests.net.lib`
7
directory, with each test focusing on a specific aspect of XDP functionality.
8
"""
9
import random
10
import string
11
from dataclasses import dataclass
12
from enum import Enum
13
14
from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge, ksft_ne, ksft_pr
15
from lib.py import KsftNamedVariant, ksft_variants
16
from lib.py import KsftFailEx, NetDrvEpEnv
17
from lib.py import EthtoolFamily, NetdevFamily, NlError
18
from lib.py import bkg, cmd, rand_port, wait_port_listen
19
from lib.py import ip, bpftool, defer
20
21
22
class 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
30
class 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
39
class 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
49
class 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
57
def _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
81
def _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
99
def _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
136
def 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
150
def _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
166
def _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
201
def _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
224
def 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
236
def 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
248
def _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
271
def 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
283
def 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
295
def _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
339
def 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
354
def 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
369
def _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
404
def _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
433
def _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
499
def 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
517
def 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
535
def 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
561
def _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
634
def 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
655
def 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
])
681
def 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
748
def 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
778
if __name__ == "__main__":
779
main()
780
781