xdp: Fix L4 header bounds checking and IPv6 extension header parsing in l3fwd

Fix critical bounds checking issues and add proper IPv6 extension header
support in XDP L3 forwarding program.

Issues fixed:

1. L4 Header Bounds Checking
   - Previous code checked bounds for udphdr size (8 bytes) but then
     accessed tcphdr struct (20+ bytes), causing potential out-of-bounds
     reads and verifier rejection
   - Now each protocol (TCP/UDP) has separate, proper bounds validation
     before accessing headers

2. IPv6 Extension Header Parsing
   - Added skip_ip6hdrext() function to parse IPv6 extension headers
     (Hop-by-Hop, Routing, Fragment, Destination Options, AH, MH)
   - Ensures accurate L4 protocol identification and flow tracking for
     IPv6 packets with extension headers
   - Supports up to 6 chained extension headers with fallback

3. PERCPU Map Performance
   - Changed bpf_map_update_elem() flag from BPF_NOEXIST to BPF_ANY
   - Eliminates unnecessary overhead since PERCPU maps have no
     contention and lookup-update race window is negligible

4. L4 Offset Calculation
   - Properly calculate L4 header offset separately for bounds checking
   - Use offset-based addressing instead of pointer arithmetic for
     verifier compliance

These fixes ensure the program passes BPF verifier checks, handles all
packet types correctly, and only records flow statistics for packets
that successfully pass FIB lookup and port validation.

Tested with: IPv4/IPv6 traffic, VLAN-tagged packets, IPv6 with extension
headers (fragmentation, routing headers), TCP/UDP flows.
This commit is contained in:
2025-10-28 13:05:26 +00:00
parent 268615b9a4
commit 8f988b0a0f
+74 -17
View File
@@ -23,6 +23,7 @@
#define IPV6_FLOWINFO_MASK bpf_htonl(0x0FFFFFFF)
#define VLAN_MAX_DEPTH 2 /* Support double-tagged VLANs */
#define IPV6_EXT_MAX_CHAIN 6 /* Max IPv6 extension headers to parse */
/* Forwarding ports */
struct {
@@ -91,7 +92,8 @@ static __always_inline void record_stats(struct xdp_md *ctx, struct flow_key *ke
.packets = 1,
.bytes = bytes,
};
bpf_map_update_elem(&xdp_flow_stats, key, &new_stats, BPF_NOEXIST);
/* Use BPF_ANY instead of BPF_NOEXIST for better performance */
bpf_map_update_elem(&xdp_flow_stats, key, &new_stats, BPF_ANY);
}
}
@@ -125,6 +127,47 @@ static __always_inline int parse_vlan(void *data, void *data_end, __u64 *nh_off,
return 0;
}
/* Skip IPv6 extension headers to find the actual L4 protocol */
static __always_inline int skip_ip6hdrext(void *data, void *data_end, __u64 *nh_off, __u8 next_hdr_type)
{
struct ipv6_opt_hdr {
__u8 nexthdr;
__u8 hdrlen;
} *hdr;
int i;
#pragma unroll
for (i = 0; i < IPV6_EXT_MAX_CHAIN; i++) {
hdr = data + *nh_off;
if ((void *)(hdr + 1) > data_end)
return -1;
switch (next_hdr_type) {
case IPPROTO_HOPOPTS:
case IPPROTO_DSTOPTS:
case IPPROTO_ROUTING:
case IPPROTO_MH:
*nh_off += (hdr->hdrlen + 1) * 8;
next_hdr_type = hdr->nexthdr;
break;
case IPPROTO_AH:
*nh_off += (hdr->hdrlen + 2) * 4;
next_hdr_type = hdr->nexthdr;
break;
case IPPROTO_FRAGMENT:
*nh_off += 8;
next_hdr_type = hdr->nexthdr;
break;
default:
/* Found a header that is not an IPv6 extension header */
return next_hdr_type;
}
}
return -1;
}
static __always_inline int xdp_l3fwd_flags(struct xdp_md *ctx, __u32 flags)
{
void *data_end = (void *)(long)ctx->data_end;
@@ -165,14 +208,17 @@ static __always_inline int xdp_l3fwd_flags(struct xdp_md *ctx, __u32 flags)
key.ipv4_src = iph->saddr;
key.ipv4_dst = iph->daddr;
void *l4_hdr = (void *)iph + (iph->ihl * 4);
if (l4_hdr + sizeof(struct udphdr) <= data_end) {
if (iph->protocol == IPPROTO_TCP) {
struct tcphdr *tcph = l4_hdr;
/* FIXED: Proper bounds checking for L4 headers */
__u64 l4_off = nh_off + (iph->ihl * 4);
if (iph->protocol == IPPROTO_TCP) {
struct tcphdr *tcph = data + l4_off;
if ((void *)(tcph + 1) <= data_end) {
key.sport = tcph->source;
key.dport = tcph->dest;
} else if (iph->protocol == IPPROTO_UDP) {
struct udphdr *udph = l4_hdr;
}
} else if (iph->protocol == IPPROTO_UDP) {
struct udphdr *udph = data + l4_off;
if ((void *)(udph + 1) <= data_end) {
key.sport = udph->source;
key.dport = udph->dest;
}
@@ -193,18 +239,28 @@ static __always_inline int xdp_l3fwd_flags(struct xdp_md *ctx, __u32 flags)
if (ip6h->hop_limit <= 1)
return XDP_PASS;
key.proto = ip6h->nexthdr;
__builtin_memcpy(key.ipv6_src, &ip6h->saddr, 16);
__builtin_memcpy(key.ipv6_dst, &ip6h->daddr, 16);
void *l4_hdr = (void *)(ip6h + 1);
if (l4_hdr + sizeof(struct udphdr) <= data_end) {
if (ip6h->nexthdr == IPPROTO_TCP) {
struct tcphdr *tcph = l4_hdr;
/* FIXED: Parse IPv6 extension headers to get actual L4 protocol */
__u64 l4_off = nh_off + sizeof(*ip6h);
int l4_proto = skip_ip6hdrext(data, data_end, &l4_off, ip6h->nexthdr);
if (l4_proto < 0)
l4_proto = ip6h->nexthdr; /* Fallback if parsing fails */
key.proto = l4_proto;
/* FIXED: Proper bounds checking for L4 headers */
if (l4_proto == IPPROTO_TCP) {
struct tcphdr *tcph = data + l4_off;
if ((void *)(tcph + 1) <= data_end) {
key.sport = tcph->source;
key.dport = tcph->dest;
} else if (ip6h->nexthdr == IPPROTO_UDP) {
struct udphdr *udph = l4_hdr;
}
} else if (l4_proto == IPPROTO_UDP) {
struct udphdr *udph = data + l4_off;
if ((void *)(udph + 1) <= data_end) {
key.sport = udph->source;
key.dport = udph->dest;
}
@@ -212,7 +268,7 @@ static __always_inline int xdp_l3fwd_flags(struct xdp_md *ctx, __u32 flags)
fib_params.family = AF_INET6;
fib_params.flowinfo = *(__be32 *)ip6h & IPV6_FLOWINFO_MASK;
fib_params.l4_protocol = ip6h->nexthdr;
fib_params.l4_protocol = l4_proto;
fib_params.tot_len = bpf_ntohs(ip6h->payload_len);
__builtin_memcpy(fib_params.ipv6_src, &ip6h->saddr, 16);
__builtin_memcpy(fib_params.ipv6_dst, &ip6h->daddr, 16);
@@ -227,9 +283,10 @@ static __always_inline int xdp_l3fwd_flags(struct xdp_md *ctx, __u32 flags)
if (!bpf_map_lookup_elem(&xdp_l3fwd_ports, &fib_params.ifindex))
return XDP_PASS;
/* Record stats AFTER fib lookup */
/* Record stats AFTER fib lookup and port validation */
record_stats(ctx, &key, bytes);
/* Update TTL/hop limit and MAC addresses */
if (h_proto == bpf_htons(ETH_P_IP))
ip_decrease_ttl(iph);
else if (h_proto == bpf_htons(ETH_P_IPV6))
@@ -255,4 +312,4 @@ int xdp_l3fwd_direct_prog(struct xdp_md *ctx)
return xdp_l3fwd_flags(ctx, BPF_FIB_LOOKUP_DIRECT);
}
char _license[] SEC("license") = "GPL";
char _license[] SEC("license") = "GPL";