mirror of
https://github.com/torvalds/linux.git
synced 2026-07-28 01:55:51 +02:00
selftests: drv-net: Test XDP_PASS/DROP support
Test XDP_PASS/DROP in single buffer and multi buffer mode when XDP native support is available. ./drivers/net/xdp.py TAP version 13 1..4 ok 1 xdp.test_xdp_native_pass_sb ok 2 xdp.test_xdp_native_pass_mb ok 3 xdp.test_xdp_native_drop_sb ok 4 xdp.test_xdp_native_drop_mb \# Totals: pass:4 fail:0 xfail:0 xpass:0 skip:0 error:0 Signed-off-by: Mohsin Bashir <mohsin.bashr@gmail.com> Link: https://patch.msgid.link/20250719083059.3209169-3-mohsin.bashr@gmail.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
be09f0d1ac
commit
1cbcb1b28b
|
|
@ -22,6 +22,7 @@ TEST_PROGS := \
|
||||||
stats.py \
|
stats.py \
|
||||||
shaper.py \
|
shaper.py \
|
||||||
hds.py \
|
hds.py \
|
||||||
|
xdp.py \
|
||||||
# end of TEST_PROGS
|
# end of TEST_PROGS
|
||||||
|
|
||||||
include ../../lib.mk
|
include ../../lib.mk
|
||||||
|
|
|
||||||
303
tools/testing/selftests/drivers/net/xdp.py
Executable file
303
tools/testing/selftests/drivers/net/xdp.py
Executable file
|
|
@ -0,0 +1,303 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file contains tests to verify native XDP support in network drivers.
|
||||||
|
The tests utilize the BPF program `xdp_native.bpf.o` from the `selftests.net.lib`
|
||||||
|
directory, with each test focusing on a specific aspect of XDP functionality.
|
||||||
|
"""
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ne
|
||||||
|
from lib.py import KsftFailEx, NetDrvEpEnv
|
||||||
|
from lib.py import bkg, cmd, rand_port
|
||||||
|
from lib.py import ip, bpftool, defer
|
||||||
|
|
||||||
|
|
||||||
|
class TestConfig(Enum):
|
||||||
|
"""Enum for XDP configuration options."""
|
||||||
|
MODE = 0 # Configures the BPF program for a specific test
|
||||||
|
PORT = 1 # Port configuration to communicate with the remote host
|
||||||
|
|
||||||
|
|
||||||
|
class XDPAction(Enum):
|
||||||
|
"""Enum for XDP actions."""
|
||||||
|
PASS = 0 # Pass the packet up to the stack
|
||||||
|
DROP = 1 # Drop the packet
|
||||||
|
|
||||||
|
|
||||||
|
class XDPStats(Enum):
|
||||||
|
"""Enum for XDP statistics."""
|
||||||
|
RX = 0 # Count of valid packets received for testing
|
||||||
|
PASS = 1 # Count of packets passed up to the stack
|
||||||
|
DROP = 2 # Count of packets dropped
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BPFProgInfo:
|
||||||
|
"""Data class to store information about a BPF program."""
|
||||||
|
name: str # Name of the BPF program
|
||||||
|
file: str # BPF program object file
|
||||||
|
xdp_sec: str = "xdp" # XDP section name (e.g., "xdp" or "xdp.frags")
|
||||||
|
mtu: int = 1500 # Maximum Transmission Unit, default is 1500
|
||||||
|
|
||||||
|
|
||||||
|
def _exchg_udp(cfg, port, test_string):
|
||||||
|
"""
|
||||||
|
Exchanges UDP packets between a local and remote host using the socat tool.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cfg: Configuration object containing network settings.
|
||||||
|
port: Port number to use for the UDP communication.
|
||||||
|
test_string: String that the remote host will send.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The string received by the test host.
|
||||||
|
"""
|
||||||
|
cfg.require_cmd("socat", remote=True)
|
||||||
|
|
||||||
|
rx_udp_cmd = f"socat -{cfg.addr_ipver} -T 2 -u UDP-RECV:{port},reuseport STDOUT"
|
||||||
|
tx_udp_cmd = f"echo -n {test_string} | socat -t 2 -u STDIN UDP:{cfg.baddr}:{port}"
|
||||||
|
|
||||||
|
with bkg(rx_udp_cmd, exit_wait=True) as nc:
|
||||||
|
cmd(tx_udp_cmd, host=cfg.remote, shell=True)
|
||||||
|
|
||||||
|
return nc.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _test_udp(cfg, port, size=256):
|
||||||
|
"""
|
||||||
|
Tests UDP packet exchange between a local and remote host.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cfg: Configuration object containing network settings.
|
||||||
|
port: Port number to use for the UDP communication.
|
||||||
|
size: The length of the test string to be exchanged, default is 256 characters.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the received string matches the sent string, False otherwise.
|
||||||
|
"""
|
||||||
|
test_str = "".join(random.choice(string.ascii_lowercase) for _ in range(size))
|
||||||
|
recvd_str = _exchg_udp(cfg, port, test_str)
|
||||||
|
|
||||||
|
return recvd_str == test_str
|
||||||
|
|
||||||
|
|
||||||
|
def _load_xdp_prog(cfg, bpf_info):
|
||||||
|
"""
|
||||||
|
Loads an XDP program onto a network interface.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cfg: Configuration object containing network settings.
|
||||||
|
bpf_info: BPFProgInfo object containing information about the BPF program.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary containing the XDP program ID, name, and associated map IDs.
|
||||||
|
"""
|
||||||
|
abs_path = cfg.net_lib_dir / bpf_info.file
|
||||||
|
prog_info = {}
|
||||||
|
|
||||||
|
cmd(f"ip link set dev {cfg.remote_ifname} mtu {bpf_info.mtu}", shell=True, host=cfg.remote)
|
||||||
|
defer(ip, f"link set dev {cfg.remote_ifname} mtu 1500", host=cfg.remote)
|
||||||
|
|
||||||
|
cmd(
|
||||||
|
f"ip link set dev {cfg.ifname} mtu {bpf_info.mtu} xdp obj {abs_path} sec {bpf_info.xdp_sec}",
|
||||||
|
shell=True
|
||||||
|
)
|
||||||
|
defer(ip, f"link set dev {cfg.ifname} mtu 1500 xdp off")
|
||||||
|
|
||||||
|
xdp_info = ip(f"-d link show dev {cfg.ifname}", json=True)[0]
|
||||||
|
prog_info["id"] = xdp_info["xdp"]["prog"]["id"]
|
||||||
|
prog_info["name"] = xdp_info["xdp"]["prog"]["name"]
|
||||||
|
prog_id = prog_info["id"]
|
||||||
|
|
||||||
|
map_ids = bpftool(f"prog show id {prog_id}", json=True)["map_ids"]
|
||||||
|
prog_info["maps"] = {}
|
||||||
|
for map_id in map_ids:
|
||||||
|
name = bpftool(f"map show id {map_id}", json=True)["name"]
|
||||||
|
prog_info["maps"][name] = map_id
|
||||||
|
|
||||||
|
return prog_info
|
||||||
|
|
||||||
|
|
||||||
|
def format_hex_bytes(value):
|
||||||
|
"""
|
||||||
|
Helper function that converts an integer into a formatted hexadecimal byte string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: An integer representing the number to be converted.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A string representing hexadecimal equivalent of value, with bytes separated by spaces.
|
||||||
|
"""
|
||||||
|
hex_str = value.to_bytes(4, byteorder='little', signed=True)
|
||||||
|
return ' '.join(f'{byte:02x}' for byte in hex_str)
|
||||||
|
|
||||||
|
|
||||||
|
def _set_xdp_map(map_name, key, value):
|
||||||
|
"""
|
||||||
|
Updates an XDP map with a given key-value pair using bpftool.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
map_name: The name of the XDP map to update.
|
||||||
|
key: The key to update in the map, formatted as a hexadecimal string.
|
||||||
|
value: The value to associate with the key, formatted as a hexadecimal string.
|
||||||
|
"""
|
||||||
|
key_formatted = format_hex_bytes(key)
|
||||||
|
value_formatted = format_hex_bytes(value)
|
||||||
|
bpftool(
|
||||||
|
f"map update name {map_name} key hex {key_formatted} value hex {value_formatted}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_stats(xdp_map_id):
|
||||||
|
"""
|
||||||
|
Retrieves and formats statistics from an XDP map.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
xdp_map_id: The ID of the XDP map from which to retrieve statistics.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A dictionary containing formatted packet statistics for various XDP actions.
|
||||||
|
The keys are based on the XDPStats Enum values.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
KsftFailEx: If the stats retrieval fails.
|
||||||
|
"""
|
||||||
|
stats_dump = bpftool(f"map dump id {xdp_map_id}", json=True)
|
||||||
|
if not stats_dump:
|
||||||
|
raise KsftFailEx(f"Failed to get stats for map {xdp_map_id}")
|
||||||
|
|
||||||
|
stats_formatted = {}
|
||||||
|
for key in range(0, 4):
|
||||||
|
val = stats_dump[key]["formatted"]["value"]
|
||||||
|
if stats_dump[key]["formatted"]["key"] == XDPStats.RX.value:
|
||||||
|
stats_formatted[XDPStats.RX.value] = val
|
||||||
|
elif stats_dump[key]["formatted"]["key"] == XDPStats.PASS.value:
|
||||||
|
stats_formatted[XDPStats.PASS.value] = val
|
||||||
|
elif stats_dump[key]["formatted"]["key"] == XDPStats.DROP.value:
|
||||||
|
stats_formatted[XDPStats.DROP.value] = val
|
||||||
|
|
||||||
|
return stats_formatted
|
||||||
|
|
||||||
|
|
||||||
|
def _test_pass(cfg, bpf_info, msg_sz):
|
||||||
|
"""
|
||||||
|
Tests the XDP_PASS action by exchanging UDP packets.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cfg: Configuration object containing network settings.
|
||||||
|
bpf_info: BPFProgInfo object containing information about the BPF program.
|
||||||
|
msg_sz: Size of the test message to send.
|
||||||
|
"""
|
||||||
|
|
||||||
|
prog_info = _load_xdp_prog(cfg, bpf_info)
|
||||||
|
port = rand_port()
|
||||||
|
|
||||||
|
_set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.PASS.value)
|
||||||
|
_set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port)
|
||||||
|
|
||||||
|
ksft_eq(_test_udp(cfg, port, msg_sz), True, "UDP packet exchange failed")
|
||||||
|
stats = _get_stats(prog_info["maps"]["map_xdp_stats"])
|
||||||
|
|
||||||
|
ksft_ne(stats[XDPStats.RX.value], 0, "RX stats should not be zero")
|
||||||
|
ksft_eq(stats[XDPStats.RX.value], stats[XDPStats.PASS.value], "RX and PASS stats mismatch")
|
||||||
|
|
||||||
|
|
||||||
|
def test_xdp_native_pass_sb(cfg):
|
||||||
|
"""
|
||||||
|
Tests the XDP_PASS action for single buffer case.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cfg: Configuration object containing network settings.
|
||||||
|
"""
|
||||||
|
bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500)
|
||||||
|
|
||||||
|
_test_pass(cfg, bpf_info, 256)
|
||||||
|
|
||||||
|
|
||||||
|
def test_xdp_native_pass_mb(cfg):
|
||||||
|
"""
|
||||||
|
Tests the XDP_PASS action for a multi-buff size.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cfg: Configuration object containing network settings.
|
||||||
|
"""
|
||||||
|
bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000)
|
||||||
|
|
||||||
|
_test_pass(cfg, bpf_info, 8000)
|
||||||
|
|
||||||
|
|
||||||
|
def _test_drop(cfg, bpf_info, msg_sz):
|
||||||
|
"""
|
||||||
|
Tests the XDP_DROP action by exchanging UDP packets.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cfg: Configuration object containing network settings.
|
||||||
|
bpf_info: BPFProgInfo object containing information about the BPF program.
|
||||||
|
msg_sz: Size of the test message to send.
|
||||||
|
"""
|
||||||
|
|
||||||
|
prog_info = _load_xdp_prog(cfg, bpf_info)
|
||||||
|
port = rand_port()
|
||||||
|
|
||||||
|
_set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.DROP.value)
|
||||||
|
_set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port)
|
||||||
|
|
||||||
|
ksft_eq(_test_udp(cfg, port, msg_sz), False, "UDP packet exchange should fail")
|
||||||
|
stats = _get_stats(prog_info["maps"]["map_xdp_stats"])
|
||||||
|
|
||||||
|
ksft_ne(stats[XDPStats.RX.value], 0, "RX stats should be zero")
|
||||||
|
ksft_eq(stats[XDPStats.RX.value], stats[XDPStats.DROP.value], "RX and DROP stats mismatch")
|
||||||
|
|
||||||
|
|
||||||
|
def test_xdp_native_drop_sb(cfg):
|
||||||
|
"""
|
||||||
|
Tests the XDP_DROP action for a signle-buff case.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cfg: Configuration object containing network settings.
|
||||||
|
"""
|
||||||
|
bpf_info = BPFProgInfo("xdp_prog", "xdp_native.bpf.o", "xdp", 1500)
|
||||||
|
|
||||||
|
_test_drop(cfg, bpf_info, 256)
|
||||||
|
|
||||||
|
|
||||||
|
def test_xdp_native_drop_mb(cfg):
|
||||||
|
"""
|
||||||
|
Tests the XDP_DROP action for a multi-buff case.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cfg: Configuration object containing network settings.
|
||||||
|
"""
|
||||||
|
bpf_info = BPFProgInfo("xdp_prog_frags", "xdp_native.bpf.o", "xdp.frags", 9000)
|
||||||
|
|
||||||
|
_test_drop(cfg, bpf_info, 8000)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
Main function to execute the XDP tests.
|
||||||
|
|
||||||
|
This function runs a series of tests to validate the XDP support for
|
||||||
|
both the single and multi-buffer. It uses the NetDrvEpEnv context
|
||||||
|
manager to manage the network driver environment and the ksft_run
|
||||||
|
function to execute the tests.
|
||||||
|
"""
|
||||||
|
with NetDrvEpEnv(__file__) as cfg:
|
||||||
|
ksft_run(
|
||||||
|
[
|
||||||
|
test_xdp_native_pass_sb,
|
||||||
|
test_xdp_native_pass_mb,
|
||||||
|
test_xdp_native_drop_sb,
|
||||||
|
test_xdp_native_drop_mb,
|
||||||
|
],
|
||||||
|
args=(cfg,))
|
||||||
|
ksft_exit()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
158
tools/testing/selftests/net/lib/xdp_native.bpf.c
Normal file
158
tools/testing/selftests/net/lib/xdp_native.bpf.c
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <linux/bpf.h>
|
||||||
|
#include <linux/in.h>
|
||||||
|
#include <linux/if_ether.h>
|
||||||
|
#include <linux/ip.h>
|
||||||
|
#include <linux/ipv6.h>
|
||||||
|
#include <linux/udp.h>
|
||||||
|
#include <bpf/bpf_endian.h>
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
XDP_MODE = 0,
|
||||||
|
XDP_PORT = 1,
|
||||||
|
} xdp_map_setup_keys;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
XDP_MODE_PASS = 0,
|
||||||
|
XDP_MODE_DROP = 1,
|
||||||
|
} xdp_map_modes;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
STATS_RX = 0,
|
||||||
|
STATS_PASS = 1,
|
||||||
|
STATS_DROP = 2,
|
||||||
|
} xdp_stats;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||||
|
__uint(max_entries, 2);
|
||||||
|
__type(key, __u32);
|
||||||
|
__type(value, __s32);
|
||||||
|
} map_xdp_setup SEC(".maps");
|
||||||
|
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||||
|
__uint(max_entries, 4);
|
||||||
|
__type(key, __u32);
|
||||||
|
__type(value, __u64);
|
||||||
|
} map_xdp_stats SEC(".maps");
|
||||||
|
|
||||||
|
static void record_stats(struct xdp_md *ctx, __u32 stat_type)
|
||||||
|
{
|
||||||
|
__u64 *count;
|
||||||
|
|
||||||
|
count = bpf_map_lookup_elem(&map_xdp_stats, &stat_type);
|
||||||
|
|
||||||
|
if (count)
|
||||||
|
__sync_fetch_and_add(count, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct udphdr *filter_udphdr(struct xdp_md *ctx, __u16 port)
|
||||||
|
{
|
||||||
|
void *data_end = (void *)(long)ctx->data_end;
|
||||||
|
void *data = (void *)(long)ctx->data;
|
||||||
|
struct udphdr *udph = NULL;
|
||||||
|
struct ethhdr *eth = data;
|
||||||
|
|
||||||
|
if (data + sizeof(*eth) > data_end)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (eth->h_proto == bpf_htons(ETH_P_IP)) {
|
||||||
|
struct iphdr *iph = data + sizeof(*eth);
|
||||||
|
|
||||||
|
if (iph + 1 > (struct iphdr *)data_end ||
|
||||||
|
iph->protocol != IPPROTO_UDP)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
udph = (void *)eth + sizeof(*iph) + sizeof(*eth);
|
||||||
|
} else if (eth->h_proto == bpf_htons(ETH_P_IPV6)) {
|
||||||
|
struct ipv6hdr *ipv6h = data + sizeof(*eth);
|
||||||
|
|
||||||
|
if (ipv6h + 1 > (struct ipv6hdr *)data_end ||
|
||||||
|
ipv6h->nexthdr != IPPROTO_UDP)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
udph = (void *)eth + sizeof(*ipv6h) + sizeof(*eth);
|
||||||
|
} else {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (udph + 1 > (struct udphdr *)data_end)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (udph->dest != bpf_htons(port))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
record_stats(ctx, STATS_RX);
|
||||||
|
|
||||||
|
return udph;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xdp_mode_pass(struct xdp_md *ctx, __u16 port)
|
||||||
|
{
|
||||||
|
struct udphdr *udph = NULL;
|
||||||
|
|
||||||
|
udph = filter_udphdr(ctx, port);
|
||||||
|
if (!udph)
|
||||||
|
return XDP_PASS;
|
||||||
|
|
||||||
|
record_stats(ctx, STATS_PASS);
|
||||||
|
|
||||||
|
return XDP_PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xdp_mode_drop_handler(struct xdp_md *ctx, __u16 port)
|
||||||
|
{
|
||||||
|
struct udphdr *udph = NULL;
|
||||||
|
|
||||||
|
udph = filter_udphdr(ctx, port);
|
||||||
|
if (!udph)
|
||||||
|
return XDP_PASS;
|
||||||
|
|
||||||
|
record_stats(ctx, STATS_DROP);
|
||||||
|
|
||||||
|
return XDP_DROP;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xdp_prog_common(struct xdp_md *ctx)
|
||||||
|
{
|
||||||
|
__u32 key, *port;
|
||||||
|
__s32 *mode;
|
||||||
|
|
||||||
|
key = XDP_MODE;
|
||||||
|
mode = bpf_map_lookup_elem(&map_xdp_setup, &key);
|
||||||
|
if (!mode)
|
||||||
|
return XDP_PASS;
|
||||||
|
|
||||||
|
key = XDP_PORT;
|
||||||
|
port = bpf_map_lookup_elem(&map_xdp_setup, &key);
|
||||||
|
if (!port)
|
||||||
|
return XDP_PASS;
|
||||||
|
|
||||||
|
switch (*mode) {
|
||||||
|
case XDP_MODE_PASS:
|
||||||
|
return xdp_mode_pass(ctx, (__u16)(*port));
|
||||||
|
case XDP_MODE_DROP:
|
||||||
|
return xdp_mode_drop_handler(ctx, (__u16)(*port));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Default action is to simple pass */
|
||||||
|
return XDP_PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("xdp")
|
||||||
|
int xdp_prog(struct xdp_md *ctx)
|
||||||
|
{
|
||||||
|
return xdp_prog_common(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC("xdp.frags")
|
||||||
|
int xdp_prog_frags(struct xdp_md *ctx)
|
||||||
|
{
|
||||||
|
return xdp_prog_common(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
char _license[] SEC("license") = "GPL";
|
||||||
Loading…
Reference in New Issue
Block a user