Merge branch 'bnxt_en-add-xdp-rss-hash-metadata-support'

Chris J Arges says:

====================
bnxt_en: Add XDP RSS hash metadata support

This series adds XDP RSS hash metadata extraction support for the bnxt_en
driver and includes selftests to validate the functionality. I was able
to test this on a BCM57414 NIC.
====================

Link: https://patch.msgid.link/20260325201139.2501937-1-carges@cloudflare.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-03-29 14:09:10 -07:00
commit 22e49419d4
12 changed files with 516 additions and 101 deletions

View File

@ -2077,23 +2077,6 @@ static struct sk_buff *bnxt_rx_vlan(struct sk_buff *skb, u8 cmp_type,
return NULL;
}
static enum pkt_hash_types bnxt_rss_ext_op(struct bnxt *bp,
struct rx_cmp *rxcmp)
{
u8 ext_op;
ext_op = RX_CMP_V3_HASH_TYPE(bp, rxcmp);
switch (ext_op) {
case EXT_OP_INNER_4:
case EXT_OP_OUTER_4:
case EXT_OP_INNFL_3:
case EXT_OP_OUTFL_3:
return PKT_HASH_TYPE_L4;
default:
return PKT_HASH_TYPE_L3;
}
}
/* returns the following:
* 1 - 1 packet successfully received
* 0 - successful TPA_START, packet not completed yet
@ -2112,13 +2095,13 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,
u32 tmp_raw_cons = *raw_cons;
u16 cons, prod, cp_cons = RING_CMP(tmp_raw_cons);
struct skb_shared_info *sinfo;
struct bnxt_xdp_buff bnxt_xdp;
struct bnxt_sw_rx_bd *rx_buf;
unsigned int len;
u8 *data_ptr, agg_bufs, cmp_type;
bool xdp_active = false;
dma_addr_t dma_addr;
struct sk_buff *skb;
struct xdp_buff xdp;
u32 flags, misc;
u32 cmpl_ts;
void *data;
@ -2231,9 +2214,14 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,
dma_addr = rx_buf->mapping;
if (bnxt_xdp_attached(bp, rxr)) {
bnxt_xdp_buff_init(bp, rxr, cons, data_ptr, len, &xdp);
bnxt_xdp.rxcmp = rxcmp;
bnxt_xdp.rxcmp1 = rxcmp1;
bnxt_xdp.cmp_type = cmp_type;
bnxt_xdp_buff_init(bp, rxr, cons, data_ptr, len, &bnxt_xdp.xdp);
if (agg_bufs) {
u32 frag_len = bnxt_rx_agg_netmems_xdp(bp, cpr, &xdp,
u32 frag_len = bnxt_rx_agg_netmems_xdp(bp, cpr,
&bnxt_xdp.xdp,
cp_cons,
agg_bufs,
false);
@ -2245,12 +2233,13 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,
}
if (xdp_active) {
if (bnxt_rx_xdp(bp, rxr, cons, &xdp, data, &data_ptr, &len, event)) {
if (bnxt_rx_xdp(bp, rxr, cons, &bnxt_xdp.xdp, data, &data_ptr,
&len, event)) {
rc = 1;
goto next_rx;
}
if (xdp_buff_has_frags(&xdp)) {
sinfo = xdp_get_shared_info_from_buff(&xdp);
if (xdp_buff_has_frags(&bnxt_xdp.xdp)) {
sinfo = xdp_get_shared_info_from_buff(&bnxt_xdp.xdp);
agg_bufs = sinfo->nr_frags;
} else {
agg_bufs = 0;
@ -2261,7 +2250,8 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,
if (!xdp_active)
skb = bnxt_copy_skb(bnapi, data_ptr, len, dma_addr);
else
skb = bnxt_copy_xdp(bnapi, &xdp, len, dma_addr);
skb = bnxt_copy_xdp(bnapi, &bnxt_xdp.xdp, len,
dma_addr);
bnxt_reuse_rx_data(rxr, cons, data);
if (!skb) {
if (agg_bufs) {
@ -2269,7 +2259,8 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,
bnxt_reuse_rx_agg_bufs(cpr, cp_cons, 0,
agg_bufs, false);
else
bnxt_xdp_buff_frags_free(rxr, &xdp);
bnxt_xdp_buff_frags_free(rxr,
&bnxt_xdp.xdp);
}
goto oom_next_rx;
}
@ -2293,10 +2284,11 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,
if (!skb)
goto oom_next_rx;
} else {
skb = bnxt_xdp_build_skb(bp, skb, agg_bufs, rxr, &xdp);
skb = bnxt_xdp_build_skb(bp, skb, agg_bufs,
rxr, &bnxt_xdp.xdp);
if (!skb) {
/* we should be able to free the old skb here */
bnxt_xdp_buff_frags_free(rxr, &xdp);
bnxt_xdp_buff_frags_free(rxr, &bnxt_xdp.xdp);
goto oom_next_rx;
}
}
@ -15902,6 +15894,10 @@ static const struct net_device_ops bnxt_netdev_ops = {
.ndo_hwtstamp_set = bnxt_hwtstamp_set,
};
static const struct xdp_metadata_ops bnxt_xdp_metadata_ops = {
.xmo_rx_hash = bnxt_xdp_rx_hash,
};
static void bnxt_get_queue_stats_rx(struct net_device *dev, int i,
struct netdev_queue_stats_rx *stats)
{
@ -16786,6 +16782,7 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
goto init_err_free;
dev->netdev_ops = &bnxt_netdev_ops;
dev->xdp_metadata_ops = &bnxt_xdp_metadata_ops;
dev->stat_ops = &bnxt_stat_ops;
dev->watchdog_timeo = BNXT_TX_TIMEOUT;
dev->ethtool_ops = &bnxt_ethtool_ops;

View File

@ -232,6 +232,7 @@ struct rx_cmp {
#define RX_CMP_FLAGS_ITYPE_UDP (3 << 12)
#define RX_CMP_FLAGS_ITYPE_FCOE (4 << 12)
#define RX_CMP_FLAGS_ITYPE_ROCE (5 << 12)
#define RX_CMP_FLAGS_ITYPE_ICMP (7 << 12)
#define RX_CMP_FLAGS_ITYPE_PTP_WO_TS (8 << 12)
#define RX_CMP_FLAGS_ITYPE_PTP_W_TS (9 << 12)
#define RX_CMP_LEN (0xffff << 16)
@ -311,6 +312,7 @@ struct rx_cmp_ext {
#define RX_CMP_FLAGS2_T_IP_CS_CALC (0x1 << 2)
#define RX_CMP_FLAGS2_T_L4_CS_CALC (0x1 << 3)
#define RX_CMP_FLAGS2_META_FORMAT_VLAN (0x1 << 4)
#define RX_CMP_FLAGS2_IP_TYPE (0x1 << 8)
__le32 rx_cmp_meta_data;
#define RX_CMP_FLAGS2_METADATA_TCI_MASK 0xffff
#define RX_CMP_FLAGS2_METADATA_VID_MASK 0xfff
@ -2900,6 +2902,23 @@ static inline bool bnxt_sriov_cfg(struct bnxt *bp)
#endif
}
static inline enum pkt_hash_types bnxt_rss_ext_op(struct bnxt *bp,
const struct rx_cmp *rxcmp)
{
u8 ext_op;
ext_op = RX_CMP_V3_HASH_TYPE(bp, rxcmp);
switch (ext_op) {
case EXT_OP_INNER_4:
case EXT_OP_OUTER_4:
case EXT_OP_INNFL_3:
case EXT_OP_OUTFL_3:
return PKT_HASH_TYPE_L4;
default:
return PKT_HASH_TYPE_L3;
}
}
extern const u16 bnxt_bstore_to_trace[];
extern const u16 bnxt_lhint_arr[];

View File

@ -472,3 +472,60 @@ bnxt_xdp_build_skb(struct bnxt *bp, struct sk_buff *skb, u8 num_frags,
xdp_buff_get_skb_flags(xdp));
return skb;
}
int bnxt_xdp_rx_hash(const struct xdp_md *ctx, u32 *hash,
enum xdp_rss_hash_type *rss_type)
{
const struct bnxt_xdp_buff *xdp = (void *)ctx;
const struct rx_cmp_ext *rxcmp1 = xdp->rxcmp1;
const struct rx_cmp *rxcmp = xdp->rxcmp;
enum xdp_rss_hash_type hash_type = 0;
u32 itypes;
if (!rxcmp || !RX_CMP_HASH_VALID(rxcmp))
return -ENODATA;
*hash = le32_to_cpu(rxcmp->rx_cmp_rss_hash);
if (!rxcmp1) {
*rss_type = XDP_RSS_TYPE_L2;
return 0;
}
if (xdp->cmp_type == CMP_TYPE_RX_L2_CMP) {
itypes = RX_CMP_ITYPES(rxcmp);
if (rxcmp1->rx_cmp_flags2 &
cpu_to_le32(RX_CMP_FLAGS2_IP_TYPE)) {
hash_type |= XDP_RSS_TYPE_L3_IPV6;
} else {
hash_type |= XDP_RSS_TYPE_L3_IPV4;
}
switch (itypes) {
case RX_CMP_FLAGS_ITYPE_TCP:
hash_type |= XDP_RSS_L4 | XDP_RSS_L4_TCP;
break;
case RX_CMP_FLAGS_ITYPE_UDP:
hash_type |= XDP_RSS_L4 | XDP_RSS_L4_UDP;
break;
case RX_CMP_FLAGS_ITYPE_ICMP:
hash_type |= XDP_RSS_L4 | XDP_RSS_L4_ICMP;
break;
default:
break;
}
} else if (xdp->cmp_type == CMP_TYPE_RX_L2_V3_CMP) {
struct bnxt *bp = netdev_priv(xdp->xdp.rxq->dev);
if (rxcmp1->rx_cmp_flags2 & cpu_to_le32(RX_CMP_FLAGS2_IP_TYPE))
hash_type |= XDP_RSS_TYPE_L3_IPV6;
else
hash_type |= XDP_RSS_TYPE_L3_IPV4;
if (bnxt_rss_ext_op(bp, rxcmp) == PKT_HASH_TYPE_L4)
hash_type |= XDP_RSS_L4;
}
*rss_type = hash_type;
return 0;
}

View File

@ -12,6 +12,13 @@
DECLARE_STATIC_KEY_FALSE(bnxt_xdp_locking_key);
struct bnxt_xdp_buff {
struct xdp_buff xdp;
struct rx_cmp *rxcmp;
struct rx_cmp_ext *rxcmp1;
u8 cmp_type;
};
struct bnxt_sw_tx_bd *bnxt_xmit_bd(struct bnxt *bp,
struct bnxt_tx_ring_info *txr,
dma_addr_t mapping, u32 len,
@ -34,4 +41,7 @@ void bnxt_xdp_buff_frags_free(struct bnxt_rx_ring_info *rxr,
struct sk_buff *bnxt_xdp_build_skb(struct bnxt *bp, struct sk_buff *skb,
u8 num_frags, struct bnxt_rx_ring_info *rxr,
struct xdp_buff *xdp);
int bnxt_xdp_rx_hash(const struct xdp_md *ctx, u32 *hash,
enum xdp_rss_hash_type *rss_type);
#endif

View File

@ -42,6 +42,7 @@ TEST_PROGS = \
rss_input_xfrm.py \
toeplitz.py \
tso.py \
xdp_metadata.py \
xsk_reconfig.py \
#

View File

@ -25,6 +25,7 @@ try:
from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, \
fd_read_timeout, ip, rand_port, rand_ports, wait_port_listen, \
wait_file, tool
from net.lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx
from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \
ksft_setup, ksft_variants, KsftNamedVariant
@ -40,6 +41,7 @@ try:
"bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool",
"fd_read_timeout", "ip", "rand_port", "rand_ports",
"wait_port_listen", "wait_file", "tool",
"bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
"KsftSkipEx", "KsftFailEx", "KsftXfailEx",
"ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run",
"ksft_setup", "ksft_variants", "KsftNamedVariant",

View File

@ -0,0 +1,146 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
"""
Tests for XDP metadata kfuncs (e.g. bpf_xdp_metadata_rx_hash).
These tests load device-bound XDP programs from xdp_metadata.bpf.o
that call metadata kfuncs, send traffic, and verify the extracted
metadata via BPF maps.
"""
from lib.py import ksft_run, ksft_eq, ksft_exit, ksft_ge, ksft_ne, ksft_pr
from lib.py import KsftNamedVariant, ksft_variants
from lib.py import CmdExitFailure, KsftSkipEx, NetDrvEpEnv
from lib.py import NetdevFamily
from lib.py import bkg, cmd, rand_port, wait_port_listen
from lib.py import ip, bpftool, defer
from lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
def _load_xdp_metadata_prog(cfg, prog_name, bpf_file="xdp_metadata.bpf.o"):
"""Load a device-bound XDP metadata program and return prog/map info.
Returns:
dict with 'id', 'name', and 'maps' (name -> map_id).
"""
abs_path = cfg.net_lib_dir / bpf_file
pin_dir = "/sys/fs/bpf/xdp_metadata_test"
cmd(f"rm -rf {pin_dir}", shell=True, fail=False)
cmd(f"mkdir -p {pin_dir}", shell=True)
try:
bpftool(f"prog loadall {abs_path} {pin_dir} type xdp "
f"xdpmeta_dev {cfg.ifname}")
except CmdExitFailure as e:
cmd(f"rm -rf {pin_dir}", shell=True, fail=False)
raise KsftSkipEx(
f"Failed to load device-bound XDP program '{prog_name}'"
) from e
defer(cmd, f"rm -rf {pin_dir}", shell=True, fail=False)
pin_path = f"{pin_dir}/{prog_name}"
ip(f"link set dev {cfg.ifname} xdpdrv pinned {pin_path}")
defer(ip, f"link set dev {cfg.ifname} xdpdrv off")
xdp_info = ip(f"-d link show dev {cfg.ifname}", json=True)[0]
prog_id = xdp_info["xdp"]["prog"]["id"]
return {"id": prog_id,
"name": xdp_info["xdp"]["prog"]["name"],
"maps": bpf_prog_map_ids(prog_id)}
def _send_probe(cfg, port, proto="tcp"):
"""Send a single payload from the remote end using socat.
Args:
cfg: Configuration object containing network settings.
port: Port number for the exchange.
proto: Protocol to use, either "tcp" or "udp".
"""
cfg.require_cmd("socat", remote=True)
if proto == "tcp":
rx_cmd = f"socat -{cfg.addr_ipver} -T 2 TCP-LISTEN:{port},reuseport STDOUT"
tx_cmd = f"echo -n rss_hash_test | socat -t 2 -u STDIN TCP:{cfg.baddr}:{port}"
else:
rx_cmd = f"socat -{cfg.addr_ipver} -T 2 -u UDP-RECV:{port},reuseport STDOUT"
tx_cmd = f"echo -n rss_hash_test | socat -t 2 -u STDIN UDP:{cfg.baddr}:{port}"
with bkg(rx_cmd, exit_wait=True):
wait_port_listen(port, proto=proto)
cmd(tx_cmd, host=cfg.remote, shell=True)
# BPF map keys matching the enums in xdp_metadata.bpf.c
_SETUP_KEY_PORT = 1
_RSS_KEY_HASH = 0
_RSS_KEY_TYPE = 1
_RSS_KEY_PKT_CNT = 2
_RSS_KEY_ERR_CNT = 3
XDP_RSS_L4 = 0x8 # BIT(3) from enum xdp_rss_hash_type
@ksft_variants([
KsftNamedVariant("tcp", "tcp"),
KsftNamedVariant("udp", "udp"),
])
def test_xdp_rss_hash(cfg, proto):
"""Test RSS hash metadata extraction via bpf_xdp_metadata_rx_hash().
This test will only run on devices that support xdp-rx-metadata-features.
Loads the xdp_rss_hash program from xdp_metadata, sends a packet using
the specified protocol, and verifies that the program extracted a non-zero
hash with an L4 hash type.
"""
dev_info = cfg.netnl.dev_get({"ifindex": cfg.ifindex})
rx_meta = dev_info.get("xdp-rx-metadata-features", [])
if "hash" not in rx_meta:
raise KsftSkipEx("device does not support XDP rx hash metadata")
prog_info = _load_xdp_metadata_prog(cfg, "xdp_rss_hash")
port = rand_port()
bpf_map_set("map_xdp_setup", _SETUP_KEY_PORT, port)
rss_map_id = prog_info["maps"]["map_rss"]
_send_probe(cfg, port, proto=proto)
rss = bpf_map_dump(rss_map_id)
pkt_cnt = rss.get(_RSS_KEY_PKT_CNT, 0)
err_cnt = rss.get(_RSS_KEY_ERR_CNT, 0)
hash_val = rss.get(_RSS_KEY_HASH, 0)
hash_type = rss.get(_RSS_KEY_TYPE, 0)
ksft_ge(pkt_cnt, 1, comment="should have received at least one packet")
ksft_eq(err_cnt, 0, comment=f"RSS hash error count: {err_cnt}")
ksft_ne(hash_val, 0,
f"RSS hash should be non-zero for {proto.upper()} traffic")
ksft_pr(f" RSS hash: {hash_val:#010x}")
ksft_pr(f" RSS hash type: {hash_type:#06x}")
ksft_ne(hash_type & XDP_RSS_L4, 0,
f"RSS hash type should include L4 for {proto.upper()} traffic")
def main():
"""Run XDP metadata kfunc tests against a real device."""
with NetDrvEpEnv(__file__) as cfg:
cfg.netnl = NetdevFamily()
ksft_run(
[
test_xdp_rss_hash,
],
args=(cfg,))
ksft_exit()
if __name__ == "__main__":
main()

View File

@ -24,6 +24,7 @@ try:
from net.lib.py import CmdExitFailure
from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, \
fd_read_timeout, ip, rand_port, rand_ports, wait_port_listen, wait_file
from net.lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx
from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \
ksft_setup, ksft_variants, KsftNamedVariant
@ -37,6 +38,7 @@ try:
"bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool",
"fd_read_timeout", "ip", "rand_port", "rand_ports",
"wait_port_listen", "wait_file",
"bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
"KsftSkipEx", "KsftFailEx", "KsftXfailEx",
"ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run",
"ksft_setup", "ksft_variants", "KsftNamedVariant",

View File

@ -16,7 +16,8 @@ from lib.py import KsftNamedVariant, ksft_variants
from lib.py import KsftFailEx, NetDrvEpEnv
from lib.py import EthtoolFamily, NetdevFamily, NlError
from lib.py import bkg, cmd, rand_port, wait_port_listen
from lib.py import ip, bpftool, defer
from lib.py import ip, defer
from lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
class TestConfig(Enum):
@ -122,47 +123,11 @@ def _load_xdp_prog(cfg, bpf_info):
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
prog_info["maps"] = bpf_prog_map_ids(prog_info["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.
@ -177,25 +142,11 @@ def _get_stats(xdp_map_id):
Raises:
KsftFailEx: If the stats retrieval fails.
"""
stats_dump = bpftool(f"map dump id {xdp_map_id}", json=True)
if not stats_dump:
stats = bpf_map_dump(xdp_map_id)
if not stats:
raise KsftFailEx(f"Failed to get stats for map {xdp_map_id}")
stats_formatted = {}
for key in range(0, 5):
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
elif stats_dump[key]["formatted"]["key"] == XDPStats.TX.value:
stats_formatted[XDPStats.TX.value] = val
elif stats_dump[key]["formatted"]["key"] == XDPStats.ABORT.value:
stats_formatted[XDPStats.ABORT.value] = val
return stats_formatted
return stats
def _test_pass(cfg, bpf_info, msg_sz):
@ -211,8 +162,8 @@ def _test_pass(cfg, bpf_info, msg_sz):
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)
bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.PASS.value)
bpf_map_set("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"])
@ -258,8 +209,8 @@ def _test_drop(cfg, bpf_info, msg_sz):
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)
bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.DROP.value)
bpf_map_set("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"])
@ -305,8 +256,8 @@ def _test_xdp_native_tx(cfg, bpf_info, payload_lens):
prog_info = _load_xdp_prog(cfg, bpf_info)
port = rand_port()
_set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.TX.value)
_set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port)
bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.TX.value)
bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port)
expected_pkts = 0
for payload_len in payload_lens:
@ -454,15 +405,15 @@ def _test_xdp_native_tail_adjst(cfg, pkt_sz_lst, offset_lst):
prog_info = _load_xdp_prog(cfg, bpf_info)
# Configure the XDP map for tail adjustment
_set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.TAIL_ADJST.value)
_set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port)
bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.TAIL_ADJST.value)
bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port)
for offset in offset_lst:
tag = format(random.randint(65, 90), "02x")
_set_xdp_map("map_xdp_setup", TestConfig.ADJST_OFFSET.value, offset)
bpf_map_set("map_xdp_setup", TestConfig.ADJST_OFFSET.value, offset)
if offset > 0:
_set_xdp_map("map_xdp_setup", TestConfig.ADJST_TAG.value, int(tag, 16))
bpf_map_set("map_xdp_setup", TestConfig.ADJST_TAG.value, int(tag, 16))
for pkt_sz in pkt_sz_lst:
test_str = "".join(random.choice(string.ascii_lowercase) for _ in range(pkt_sz))
@ -574,8 +525,8 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst):
prog_info = _load_xdp_prog(cfg, BPFProgInfo(prog, "xdp_native.bpf.o", "xdp.frags", 9000))
port = rand_port()
_set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.HEAD_ADJST.value)
_set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port)
bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.HEAD_ADJST.value)
bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port)
hds_thresh = get_hds_thresh(cfg)
for offset in offset_lst:
@ -595,11 +546,8 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, offset_lst):
test_str = ''.join(random.choice(string.ascii_lowercase) for _ in range(pkt_sz))
tag = format(random.randint(65, 90), '02x')
_set_xdp_map("map_xdp_setup",
TestConfig.ADJST_OFFSET.value,
offset)
_set_xdp_map("map_xdp_setup", TestConfig.ADJST_TAG.value, int(tag, 16))
_set_xdp_map("map_xdp_setup", TestConfig.ADJST_OFFSET.value, offset)
bpf_map_set("map_xdp_setup", TestConfig.ADJST_OFFSET.value, offset)
bpf_map_set("map_xdp_setup", TestConfig.ADJST_TAG.value, int(tag, 16))
recvd_str = _exchg_udp(cfg, port, test_str)
@ -691,8 +639,8 @@ def test_xdp_native_qstats(cfg, act):
prog_info = _load_xdp_prog(cfg, bpf_info)
port = rand_port()
_set_xdp_map("map_xdp_setup", TestConfig.MODE.value, act.value)
_set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port)
bpf_map_set("map_xdp_setup", TestConfig.MODE.value, act.value)
bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port)
# Discard the input, but we need a listener to avoid ICMP errors
rx_udp = f"socat -{cfg.addr_ipver} -T 2 -u UDP-RECV:{port},reuseport " + \

View File

@ -15,6 +15,7 @@ from .nsim import NetdevSim, NetdevSimDev
from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \
bpftool, ip, ethtool, bpftrace, rand_port, rand_ports, wait_port_listen, \
wait_file, tool
from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
from .ynl import NlError, NlctrlFamily, YnlFamily, \
EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily
from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink
@ -29,6 +30,7 @@ __all__ = ["KSRC",
"CmdExitFailure", "fd_read_timeout", "cmd", "bkg", "defer",
"bpftool", "ip", "ethtool", "bpftrace", "rand_port", "rand_ports",
"wait_port_listen", "wait_file", "tool",
"bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids",
"NetdevSim", "NetdevSimDev",
"NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError",
"YnlFamily", "EthtoolFamily", "NetdevFamily", "RtnlFamily",

View File

@ -0,0 +1,68 @@
# SPDX-License-Identifier: GPL-2.0
"""
BPF helper utilities for kernel selftests.
Provides common operations for interacting with BPF maps and programs
via bpftool, used by XDP and other BPF-based test files.
"""
from .utils import bpftool
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 bpf_map_set(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 bpf_map_dump(map_id):
"""Dump all entries of a BPF array map.
Args:
map_id: Numeric map ID (as returned by bpftool prog show).
Returns:
A dict mapping formatted key (int) to formatted value (int).
"""
raw = bpftool(f"map dump id {map_id}", json=True)
return {e["formatted"]["key"]: e["formatted"]["value"] for e in raw}
def bpf_prog_map_ids(prog_id):
"""Get the map name-to-ID mapping for a loaded BPF program.
Args:
prog_id: Numeric program ID.
Returns:
A dict mapping map name (str) to map ID (int).
"""
map_ids = bpftool(f"prog show id {prog_id}", json=True)["map_ids"]
maps = {}
for mid in map_ids:
name = bpftool(f"map show id {mid}", json=True)["name"]
maps[name] = mid
return maps

View File

@ -0,0 +1,163 @@
// 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 <linux/tcp.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
enum {
XDP_PORT = 1,
XDP_PROTO = 4,
} xdp_map_setup_keys;
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 5);
__type(key, __u32);
__type(value, __s32);
} map_xdp_setup SEC(".maps");
/* RSS hash results: key 0 = hash, key 1 = hash type,
* key 2 = packet count, key 3 = error count.
*/
enum {
RSS_KEY_HASH = 0,
RSS_KEY_TYPE = 1,
RSS_KEY_PKT_CNT = 2,
RSS_KEY_ERR_CNT = 3,
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, __u32);
__type(value, __u32);
__uint(max_entries, 4);
} map_rss SEC(".maps");
/* Mirror of enum xdp_rss_hash_type from include/net/xdp.h.
* Needed because the enum is not part of UAPI headers.
*/
enum xdp_rss_hash_type {
XDP_RSS_L3_IPV4 = 1U << 0,
XDP_RSS_L3_IPV6 = 1U << 1,
XDP_RSS_L3_DYNHDR = 1U << 2,
XDP_RSS_L4 = 1U << 3,
XDP_RSS_L4_TCP = 1U << 4,
XDP_RSS_L4_UDP = 1U << 5,
XDP_RSS_L4_SCTP = 1U << 6,
XDP_RSS_L4_IPSEC = 1U << 7,
XDP_RSS_L4_ICMP = 1U << 8,
};
extern int bpf_xdp_metadata_rx_hash(const struct xdp_md *ctx, __u32 *hash,
enum xdp_rss_hash_type *rss_type) __ksym;
static __always_inline __u16 get_dest_port(void *l4, void *data_end,
__u8 protocol)
{
if (protocol == IPPROTO_UDP) {
struct udphdr *udp = l4;
if ((void *)(udp + 1) > data_end)
return 0;
return udp->dest;
} else if (protocol == IPPROTO_TCP) {
struct tcphdr *tcp = l4;
if ((void *)(tcp + 1) > data_end)
return 0;
return tcp->dest;
}
return 0;
}
SEC("xdp")
int xdp_rss_hash(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
enum xdp_rss_hash_type rss_type = 0;
struct ethhdr *eth = data;
__u8 l4_proto = 0;
__u32 hash = 0;
__u32 key, val;
void *l4 = NULL;
__u32 *cnt;
int ret;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
if (eth->h_proto == bpf_htons(ETH_P_IP)) {
struct iphdr *iph = (void *)(eth + 1);
if ((void *)(iph + 1) > data_end)
return XDP_PASS;
l4_proto = iph->protocol;
l4 = (void *)(iph + 1);
} else if (eth->h_proto == bpf_htons(ETH_P_IPV6)) {
struct ipv6hdr *ip6h = (void *)(eth + 1);
if ((void *)(ip6h + 1) > data_end)
return XDP_PASS;
l4_proto = ip6h->nexthdr;
l4 = (void *)(ip6h + 1);
}
if (!l4)
return XDP_PASS;
/* Filter on the configured protocol (map_xdp_setup key XDP_PROTO).
* When set, only process packets matching the requested L4 protocol.
*/
key = XDP_PROTO;
__s32 *proto_cfg = bpf_map_lookup_elem(&map_xdp_setup, &key);
if (proto_cfg && *proto_cfg != 0 && l4_proto != (__u8)*proto_cfg)
return XDP_PASS;
/* Filter on the configured port (map_xdp_setup key XDP_PORT).
* Only applies to protocols with ports (UDP, TCP).
*/
key = XDP_PORT;
__s32 *port_cfg = bpf_map_lookup_elem(&map_xdp_setup, &key);
if (port_cfg && *port_cfg != 0) {
__u16 dest = get_dest_port(l4, data_end, l4_proto);
if (!dest || bpf_ntohs(dest) != (__u16)*port_cfg)
return XDP_PASS;
}
ret = bpf_xdp_metadata_rx_hash(ctx, &hash, &rss_type);
if (ret < 0) {
key = RSS_KEY_ERR_CNT;
cnt = bpf_map_lookup_elem(&map_rss, &key);
if (cnt)
__sync_fetch_and_add(cnt, 1);
return XDP_PASS;
}
key = RSS_KEY_HASH;
bpf_map_update_elem(&map_rss, &key, &hash, BPF_ANY);
key = RSS_KEY_TYPE;
val = (__u32)rss_type;
bpf_map_update_elem(&map_rss, &key, &val, BPF_ANY);
key = RSS_KEY_PKT_CNT;
cnt = bpf_map_lookup_elem(&map_rss, &key);
if (cnt)
__sync_fetch_and_add(cnt, 1);
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";