mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-27 15:40:10 -04:00
This test loads xdp_metadata.bpf which calls bpf_xdp_metadata_rx_hash() on incoming packets. The metadata from that packet is then sent to a BPF map for validation. It borrows structure from xdp.py, reusing common functions. The test checks the device's xdp-rx-metadata-features via netlink before running and skips on devices that do not advertise hash support. This can be run on veth devices as well as real hardware. The test is fairly simple and just verifies that a TCP or UDP packet can be identified as an L4 flow. This minimal test also passes if run on a veth device. Signed-off-by: Chris J Arges <carges@cloudflare.com> Link: https://patch.msgid.link/20260325201139.2501937-7-carges@cloudflare.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
164 lines
3.7 KiB
C
164 lines
3.7 KiB
C
// 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";
|