Add a test that verifies skb->ip_summed is set to CHECKSUM_NONE when a device running in XDP mode creates an skb from a xdp_buff if the attached ebpf program returns an XDP_PASS. The test attaches an XDP program returning XDP_PASS, and a TC ingress program that runs the bpf_skb_rx_checksum() kfunc to inspect the resulting skb. After XDP_PASS the driver must invalidate any previously computed hardware RX checksum since XDP may have modified the packet data. The BPF program counts packets per checksum type in a map, and the test runner verifies that after sending traffic the CHECKSUM_NONE counter is non-zero while CHECKSUM_UNNECESSARY and CHECKSUM_COMPLETE counters are zero.
Signed-off-by: Lorenzo Bianconi <[email protected]> --- Documentation/networking/xdp-rx-metadata.rst | 5 ++ .../selftests/drivers/net/hw/xdp_metadata.py | 55 +++++++++++++++- .../selftests/net/lib/skb_metadata_csum.bpf.c | 73 ++++++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) diff --git a/Documentation/networking/xdp-rx-metadata.rst b/Documentation/networking/xdp-rx-metadata.rst index 93918b3769a3..7434ac98242a 100644 --- a/Documentation/networking/xdp-rx-metadata.rst +++ b/Documentation/networking/xdp-rx-metadata.rst @@ -90,6 +90,11 @@ conversion, and the XDP metadata is not used by the kernel when building ``skbs``. However, TC-BPF programs can access the XDP metadata area using the ``data_meta`` pointer. +If a driver is running in XDP mode, any existing hardware RX checksum +(``CHECKSUM_UNNECESSARY`` or ``CHECKSUM_COMPLETE``) must be invalidated +by setting ``skb->ip_summed`` to ``CHECKSUM_NONE`` before passing the +skb to the kernel, since XDP may have modified the packet data. + In the future, we'd like to support a case where an XDP program can override some of the metadata used for building ``skbs``. diff --git a/tools/testing/selftests/drivers/net/hw/xdp_metadata.py b/tools/testing/selftests/drivers/net/hw/xdp_metadata.py old mode 100644 new mode 100755 index 33a1985356d9..8ccd34b776b4 --- a/tools/testing/selftests/drivers/net/hw/xdp_metadata.py +++ b/tools/testing/selftests/drivers/net/hw/xdp_metadata.py @@ -8,12 +8,13 @@ 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. """ +import json 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 ip, bpftool, defer, ethtool from lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids @@ -130,6 +131,57 @@ def test_xdp_rss_hash(cfg, proto): f"RSS hash type should include L4 for {proto.upper()} traffic") +def test_xdp_pass_rx_csum(cfg): + """Test xdp_pass sets CHECKSUM_NONE on the resulting skb. + + Attaches an XDP program that returns XDP_PASS and a TC ingress + program that checks skb->ip_summed via bpf_skb_rx_checksum(). + Verifies the value is CHECKSUM_NONE. + """ + + bpf_obj = cfg.net_lib_dir / "skb_metadata_csum.bpf.o" + xdp_obj = cfg.net_lib_dir / "xdp_dummy.bpf.o" + + # GRO may overwrite skb->ip_summed after the driver sets it, + # so disable it to preserve the checksum set by the driver. + ethtool(f"-K {cfg.ifname} gro off") + defer(ethtool, f"-K {cfg.ifname} gro on") + ip(f"link set dev {cfg.ifname} xdp obj {xdp_obj} sec xdp") + defer(ip, f"link set dev {cfg.ifname} xdp off") + + qdiscs = json.loads(cmd(f"tc -j qdisc show dev {cfg.ifname}").stdout) + if not any(q['kind'] == 'clsact' for q in qdiscs): + cmd(f"tc qdisc add dev {cfg.ifname} clsact") + defer(cmd, f"tc qdisc del dev {cfg.ifname} clsact") + cmd(f"tc filter add dev {cfg.ifname} ingress bpf da obj {bpf_obj} sec tc") + + progs = bpftool("prog list", json=True) + tc_prog_id = None + for p in progs: + if p.get("name") == "tc_check_csum": + tc_prog_id = p["id"] + break + + if tc_prog_id is None: + raise KsftSkipEx("Could not find tc_check_csum BPF program") + + maps = bpf_prog_map_ids(tc_prog_id) + csum_map_id = maps.get("map_csum_result") + if csum_map_id is None: + raise KsftSkipEx("Could not find map_csum_result map") + + for _ in range(10): + _send_probe(cfg, 12345, proto="udp") + + result = bpf_map_dump(csum_map_id) + csum_none = result.get(0, 0) + csum_unnecessary = result.get(1, 0) + csum_complete = result.get(2, 0) + ksft_ge(csum_none, 1, "skb->ip_summed should be CHECKSUM_NONE after XDP_PASS") + ksft_eq(csum_unnecessary, 0, "CHECKSUM_UNNECESSARY should not be set") + ksft_eq(csum_complete, 0, "CHECKSUM_COMPLETE should not be set") + + def main(): """Run XDP metadata kfunc tests against a real device.""" with NetDrvEpEnv(__file__) as cfg: @@ -137,6 +189,7 @@ def main(): ksft_run( [ test_xdp_rss_hash, + test_xdp_pass_rx_csum, ], args=(cfg,)) ksft_exit() diff --git a/tools/testing/selftests/net/lib/skb_metadata_csum.bpf.c b/tools/testing/selftests/net/lib/skb_metadata_csum.bpf.c new file mode 100644 index 000000000000..6a953c4acfe7 --- /dev/null +++ b/tools/testing/selftests/net/lib/skb_metadata_csum.bpf.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/bpf.h> +#include <linux/if_ether.h> +#include <linux/in.h> +#include <linux/ipv6.h> +#include <linux/pkt_cls.h> +#include <linux/udp.h> +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_endian.h> + +#define UDP_PORT 12345 + +enum skb_csum { + SKB_CSUM_NONE = 0, + SKB_CSUM_UNNECESSARY = 1, + SKB_CSUM_COMPLETE = 2, + SKB_CSUM_PARTIAL = 3, +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, SKB_CSUM_PARTIAL); + __type(key, __u32); + __type(value, __u64); +} map_csum_result SEC(".maps"); + +extern int bpf_skb_rx_checksum(struct __sk_buff *skb, __u32 *ip_summed, + __u32 *csum_meta) __ksym; + +SEC("tc") +int tc_check_csum(struct __sk_buff *skb) +{ + void *data_end = (void *)(long)skb->data_end; + void *data = (void *)(long)skb->data; + __u32 ip_summed, csum_meta; + struct ethhdr *eth = data; + struct ipv6hdr *ip6; + struct udphdr *udp; + + if ((void *)(eth + 1) > data_end) + return TC_ACT_OK; + + if (eth->h_proto != bpf_htons(ETH_P_IPV6)) + return TC_ACT_OK; + + ip6 = (void *)(eth + 1); + if ((void *)(ip6 + 1) > data_end) + return TC_ACT_OK; + + if (ip6->nexthdr != IPPROTO_UDP) + return TC_ACT_OK; + + udp = (void *)(ip6 + 1); + if ((void *)(udp + 1) > data_end) + return TC_ACT_OK; + + if (udp->dest != bpf_htons(UDP_PORT)) + return TC_ACT_OK; + + bpf_skb_rx_checksum(skb, &ip_summed, &csum_meta); + if (ip_summed < SKB_CSUM_PARTIAL) { + __u64 *cnt; + + cnt = bpf_map_lookup_elem(&map_csum_result, &ip_summed); + if (cnt) + __sync_fetch_and_add(cnt, 1); + } + + return TC_ACT_OK; +} + +char _license[] SEC("license") = "GPL"; -- 2.55.0
