]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
selftests: drv-net: Pull data before parsing headers
authorAmery Hung <ameryhung@gmail.com>
Mon, 22 Sep 2025 23:33:56 +0000 (16:33 -0700)
committerMartin KaFai Lau <martin.lau@kernel.org>
Tue, 23 Sep 2025 22:21:26 +0000 (15:21 -0700)
It is possible for drivers to generate xdp packets with data residing
entirely in fragments. To keep parsing headers using direct packet
access, call bpf_xdp_pull_data() to pull headers into the linear data
area.

Signed-off-by: Amery Hung <ameryhung@gmail.com>
Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>
Link: https://patch.msgid.link/20250922233356.3356453-9-ameryhung@gmail.com
tools/testing/selftests/net/lib/xdp_native.bpf.c

index 521ba38f2dddad656db63446bd9050261ef0b909..df4eea5c192b3b45339ca0435193e3be0c5c38b2 100644 (file)
@@ -14,6 +14,8 @@
 #define MAX_PAYLOAD_LEN 5000
 #define MAX_HDR_LEN 64
 
+extern int bpf_xdp_pull_data(struct xdp_md *xdp, __u32 len) __ksym __weak;
+
 enum {
        XDP_MODE = 0,
        XDP_PORT = 1,
@@ -68,30 +70,57 @@ static void record_stats(struct xdp_md *ctx, __u32 stat_type)
 
 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;
+       void *data, *data_end;
+       struct ethhdr *eth;
+       int err;
+
+       err = bpf_xdp_pull_data(ctx, sizeof(*eth));
+       if (err)
+               return NULL;
+
+       data_end = (void *)(long)ctx->data_end;
+       data = eth = (void *)(long)ctx->data;
 
        if (data + sizeof(*eth) > data_end)
                return NULL;
 
        if (eth->h_proto == bpf_htons(ETH_P_IP)) {
-               struct iphdr *iph = data + sizeof(*eth);
+               struct iphdr *iph;
+
+               err = bpf_xdp_pull_data(ctx, sizeof(*eth) + sizeof(*iph) +
+                                            sizeof(*udph));
+               if (err)
+                       return NULL;
+
+               data_end = (void *)(long)ctx->data_end;
+               data = (void *)(long)ctx->data;
+
+               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);
+               udph = data + sizeof(*iph) + sizeof(*eth);
+       } else if (eth->h_proto == bpf_htons(ETH_P_IPV6)) {
+               struct ipv6hdr *ipv6h;
+
+               err = bpf_xdp_pull_data(ctx, sizeof(*eth) + sizeof(*ipv6h) +
+                                            sizeof(*udph));
+               if (err)
+                       return NULL;
+
+               data_end = (void *)(long)ctx->data_end;
+               data = (void *)(long)ctx->data;
+
+               ipv6h = data + sizeof(*eth);
 
                if (ipv6h + 1 > (struct ipv6hdr *)data_end ||
                    ipv6h->nexthdr != IPPROTO_UDP)
                        return NULL;
 
-               udph = (void *)eth + sizeof(*ipv6h) + sizeof(*eth);
+               udph = data + sizeof(*ipv6h) + sizeof(*eth);
        } else {
                return NULL;
        }
@@ -145,17 +174,34 @@ static void swap_machdr(void *data)
 
 static int xdp_mode_tx_handler(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;
+       void *data, *data_end;
+       struct ethhdr *eth;
+       int err;
+
+       err = bpf_xdp_pull_data(ctx, sizeof(*eth));
+       if (err)
+               return XDP_PASS;
+
+       data_end = (void *)(long)ctx->data_end;
+       data = eth = (void *)(long)ctx->data;
 
        if (data + sizeof(*eth) > data_end)
                return XDP_PASS;
 
        if (eth->h_proto == bpf_htons(ETH_P_IP)) {
-               struct iphdr *iph = data + sizeof(*eth);
-               __be32 tmp_ip = iph->saddr;
+               struct iphdr *iph;
+               __be32 tmp_ip;
+
+               err = bpf_xdp_pull_data(ctx, sizeof(*eth) + sizeof(*iph) +
+                                            sizeof(*udph));
+               if (err)
+                       return XDP_PASS;
+
+               data_end = (void *)(long)ctx->data_end;
+               data = (void *)(long)ctx->data;
+
+               iph = data + sizeof(*eth);
 
                if (iph + 1 > (struct iphdr *)data_end ||
                    iph->protocol != IPPROTO_UDP)
@@ -169,8 +215,10 @@ static int xdp_mode_tx_handler(struct xdp_md *ctx, __u16 port)
                        return XDP_PASS;
 
                record_stats(ctx, STATS_RX);
+               eth = data;
                swap_machdr((void *)eth);
 
+               tmp_ip = iph->saddr;
                iph->saddr = iph->daddr;
                iph->daddr = tmp_ip;
 
@@ -178,9 +226,19 @@ static int xdp_mode_tx_handler(struct xdp_md *ctx, __u16 port)
 
                return XDP_TX;
 
-       } else if (eth->h_proto  == bpf_htons(ETH_P_IPV6)) {
-               struct ipv6hdr *ipv6h = data + sizeof(*eth);
+       } else if (eth->h_proto == bpf_htons(ETH_P_IPV6)) {
                struct in6_addr tmp_ipv6;
+               struct ipv6hdr *ipv6h;
+
+               err = bpf_xdp_pull_data(ctx, sizeof(*eth) + sizeof(*ipv6h) +
+                                            sizeof(*udph));
+               if (err)
+                       return XDP_PASS;
+
+               data_end = (void *)(long)ctx->data_end;
+               data = (void *)(long)ctx->data;
+
+               ipv6h = data + sizeof(*eth);
 
                if (ipv6h + 1 > (struct ipv6hdr *)data_end ||
                    ipv6h->nexthdr != IPPROTO_UDP)
@@ -194,6 +252,7 @@ static int xdp_mode_tx_handler(struct xdp_md *ctx, __u16 port)
                        return XDP_PASS;
 
                record_stats(ctx, STATS_RX);
+               eth = data;
                swap_machdr((void *)eth);
 
                __builtin_memcpy(&tmp_ipv6, &ipv6h->saddr, sizeof(tmp_ipv6));