Skip to content

Commit 61023de

Browse files
committed
fix: srv6 checksum handling
1 parent e5ccdee commit 61023de

File tree

3 files changed

+58
-27
lines changed

3 files changed

+58
-27
lines changed

pkg/coreelf/bpf_bpfeb.o

2.17 KB
Binary file not shown.

pkg/coreelf/bpf_bpfel.o

2.16 KB
Binary file not shown.

src/xdp_prog.c

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,18 @@ static __noinline bool apply_diff(struct xdp_md *ctx, struct diff_value *dv)
137137
return bpf_xdp_store_bytes(ctx, dv->offset, dv->new_value, dv->size) >= 0;
138138
}
139139

140+
// Ensure IPPROTO_ETHERNET is defined (not present in older kernel headers)
141+
#ifndef IPPROTO_ETHERNET
142+
#define IPPROTO_ETHERNET 143
143+
#endif
144+
140145
// Helper: Traverse IPv6 extension headers to find transport layer
141146
// Returns true if transport layer found, outputs protocol and L4 offset
142147
// If final_dst is non-NULL and SRH is found, copies the final destination (segment[LastEntry]) to it
143148
// Supports: Hop-by-Hop (0), Routing (43), Fragment (44), Destination Options (60)
144-
// Also recognizes IPPROTO_ETHERNET (143) as a terminal protocol for L2VPN over SRv6
149+
// Also recognizes IPPROTO_ETHERNET (143), IPPROTO_IPIP (4), IPPROTO_IPV6 (41) as terminal protocols
145150
static __always_inline bool ipv6_find_transport(struct xdp_md *ctx, __u16 l3_offset, __u8 *out_proto, __u16 *out_l4_offset,
146-
__u8 *final_dst)
151+
__u8 *final_dst, bool *has_final_dst)
147152
{
148153
__u8 proto;
149154
if (bpf_xdp_load_bytes(ctx, l3_offset + 6, &proto, 1) < 0)
@@ -154,7 +159,7 @@ static __always_inline bool ipv6_find_transport(struct xdp_md *ctx, __u16 l3_off
154159
#pragma unroll
155160
for (int i = 0; i < 4; i++) { // Max 4 extension headers
156161
if (proto == IPPROTO_UDP || proto == IPPROTO_TCP || proto == IPPROTO_ICMPV6 || proto == IPPROTO_ETHERNET ||
157-
proto == IPPROTO_IPIP) {
162+
proto == IPPROTO_IPIP || proto == IPPROTO_IPV6) {
158163
*out_proto = proto;
159164
*out_l4_offset = l4_offset;
160165
return true;
@@ -169,20 +174,25 @@ static __always_inline bool ipv6_find_transport(struct xdp_md *ctx, __u16 l3_off
169174
l4_offset += (ext_hdr[1] + 1) * 8;
170175
} else if (proto == IPPROTO_ROUTING) {
171176
// Routing header (SRH): extract final destination for pseudo-header
172-
// SRH structure: next_hdr(1) + hdr_ext_len(1) + routing_type(1) + segments_left(1) + ...
177+
// SRH structure: next_hdr(1) + hdr_ext_len(1) + routing_type(1) + segments_left(1)
178+
// + last_entry(1) + flags(1) + tag(2) + segment_list[...]
173179
// Per RFC 8200, when Segments Left > 0, use final destination from SRH
174180
// When Segments Left = 0, use IPv6 Dst (packet has reached final destination)
175-
__u8 srh_hdr[4]; // next_hdr, hdr_ext_len, routing_type, segments_left
176-
if (bpf_xdp_load_bytes(ctx, l4_offset, srh_hdr, 4) < 0)
181+
__u8 srh_hdr[5]; // next_hdr, hdr_ext_len, routing_type, segments_left, last_entry
182+
if (bpf_xdp_load_bytes(ctx, l4_offset, srh_hdr, 5) < 0)
177183
return false;
178184

179185
__u8 segments_left = srh_hdr[3];
186+
__u8 last_entry = srh_hdr[4];
180187

181188
// Only use SRH final destination if Segments Left > 0
182-
if (final_dst && segments_left > 0) {
183-
// Read segment[LastEntry] (first 16 bytes of segment list at SRH + 8)
184-
if (bpf_xdp_load_bytes(ctx, l4_offset + 8, final_dst, 16) < 0)
189+
if (final_dst && has_final_dst && segments_left > 0) {
190+
// segment[LastEntry] is at SRH + 8 + LastEntry * 16
191+
// Segments are stored in reverse order: segment[0] is last hop
192+
__u16 seg_offset = l4_offset + 8 + (__u16)last_entry * 16;
193+
if (bpf_xdp_load_bytes(ctx, seg_offset, final_dst, 16) < 0)
185194
return false;
195+
*has_final_dst = true;
186196
}
187197

188198
proto = srh_hdr[0];
@@ -249,21 +259,15 @@ static __noinline bool recalc_checksum(struct xdp_md *ctx, struct checksum_meta
249259
// IPv6 transport checksum (supports extension headers like SRH)
250260
__u8 proto;
251261
__u16 l4_offset;
252-
__u8 final_dst[16] = {0}; // Final destination for SRv6 pseudo-header (only set if SL > 0)
253-
if (!ipv6_find_transport(ctx, meta->ip_header_offset, &proto, &l4_offset, final_dst))
262+
__u8 final_dst[16] = {0};
263+
bool has_final_dst = false;
264+
if (!ipv6_find_transport(ctx, meta->ip_header_offset, &proto, &l4_offset, final_dst, &has_final_dst))
254265
return false; // No transport layer found
255266

256267
// Use computed l4_offset for both length calculation and data reading
257268
transport_len = pkt_len - l4_offset;
258269

259-
// Check if final_dst was set (SRv6 with SL > 0) - if all zeros, use NULL
260-
__u8 *final_dst_ptr = NULL;
261-
for (int i = 0; i < 16; i++) {
262-
if (final_dst[i] != 0) {
263-
final_dst_ptr = final_dst;
264-
break;
265-
}
266-
}
270+
__u8 *final_dst_ptr = has_final_dst ? final_dst : NULL;
267271
csum = calc_transport_csum_ipv6(ctx, meta->ip_header_offset, l4_offset, transport_len, proto, final_dst_ptr);
268272
if (bpf_xdp_store_bytes(ctx, meta->csum_offset, &csum, 2) < 0)
269273
return false;
@@ -330,6 +334,8 @@ static __noinline bool update_packet_lengths(struct xdp_md *ctx, __u16 target_le
330334
eth_proto = bpf_htons(ETH_P_IPV6);
331335
else {
332336
// L2VPN: inner Ethernet frame after MPLS labels
337+
// TODO: PW Control Word (RFC 4385) not supported - if present (first nibble 0),
338+
// 4 bytes should be skipped before the inner Ethernet header
333339
// Skip inner Ethernet header (14 bytes) and read inner EtherType
334340
__be16 inner_eth_proto;
335341
if (bpf_xdp_load_bytes(ctx, l3_offset + 12, &inner_eth_proto, 2) < 0)
@@ -384,7 +390,7 @@ static __noinline bool update_packet_lengths(struct xdp_md *ctx, __u16 target_le
384390
// Find transport layer (traversing extension headers like SRH)
385391
__u8 proto;
386392
__u16 l4_offset;
387-
if (!ipv6_find_transport(ctx, l3_offset, &proto, &l4_offset, NULL))
393+
if (!ipv6_find_transport(ctx, l3_offset, &proto, &l4_offset, NULL, NULL))
388394
return true; // No transport layer found, but payload_len is updated
389395

390396
if (proto == IPPROTO_UDP) {
@@ -409,16 +415,27 @@ static __noinline bool update_packet_lengths(struct xdp_md *ctx, __u16 target_le
409415
__be16 inner_ip_len_be = bpf_htons(inner_ip_len);
410416
if (bpf_xdp_store_bytes(ctx, inner_l3 + 2, &inner_ip_len_be, 2) < 0)
411417
return false;
412-
// Update inner UDP len (assume IHL=5, offset 9 for protocol)
418+
// Read IHL and protocol from inner IPv4 header
419+
__u8 inner_ver_ihl;
420+
if (bpf_xdp_load_bytes(ctx, inner_l3, &inner_ver_ihl, 1) < 0)
421+
return false;
422+
__u16 inner_ihl = (inner_ver_ihl & 0x0F) * 4;
413423
__u8 inner_proto;
414424
if (bpf_xdp_load_bytes(ctx, inner_l3 + 9, &inner_proto, 1) < 0)
415425
return false;
416-
if (inner_proto == IPPROTO_UDP && target_len > inner_l3 + 20) {
417-
__u16 inner_udp_len = target_len - inner_l3 - 20;
426+
__u16 inner_l4 = inner_l3 + inner_ihl;
427+
if (inner_proto == IPPROTO_UDP && target_len > inner_l4) {
428+
__u16 inner_udp_len = target_len - inner_l4;
418429
__be16 inner_udp_len_be = bpf_htons(inner_udp_len);
419-
if (bpf_xdp_store_bytes(ctx, inner_l3 + 24, &inner_udp_len_be, 2) < 0)
430+
if (bpf_xdp_store_bytes(ctx, inner_l4 + 4, &inner_udp_len_be, 2) < 0)
420431
return false;
421432
}
433+
} else if (inner_eth_proto == bpf_htons(ETH_P_IPV6) && target_len > inner_l3 + sizeof(struct ipv6hdr)) {
434+
// Update inner IPv6 payload_len
435+
__u16 inner_payload_len = target_len - inner_l3 - sizeof(struct ipv6hdr);
436+
__be16 inner_payload_len_be = bpf_htons(inner_payload_len);
437+
if (bpf_xdp_store_bytes(ctx, inner_l3 + 4, &inner_payload_len_be, 2) < 0)
438+
return false;
422439
}
423440
} else if (proto == IPPROTO_IPIP) {
424441
// L3VPN over SRv6: inner IPv4 after SRH
@@ -427,16 +444,30 @@ static __noinline bool update_packet_lengths(struct xdp_md *ctx, __u16 target_le
427444
__be16 inner_ip_len_be = bpf_htons(inner_ip_len);
428445
if (bpf_xdp_store_bytes(ctx, l4_offset + 2, &inner_ip_len_be, 2) < 0)
429446
return false;
447+
// Read IHL and protocol from inner IPv4 header
448+
__u8 inner_ver_ihl;
449+
if (bpf_xdp_load_bytes(ctx, l4_offset, &inner_ver_ihl, 1) < 0)
450+
return false;
451+
__u16 inner_ihl = (inner_ver_ihl & 0x0F) * 4;
430452
__u8 inner_proto;
431453
if (bpf_xdp_load_bytes(ctx, l4_offset + 9, &inner_proto, 1) < 0)
432454
return false;
433-
if (inner_proto == IPPROTO_UDP && target_len > l4_offset + 20) {
434-
__u16 inner_udp_len = target_len - l4_offset - 20;
455+
__u16 inner_l4 = l4_offset + inner_ihl;
456+
if (inner_proto == IPPROTO_UDP && target_len > inner_l4) {
457+
__u16 inner_udp_len = target_len - inner_l4;
435458
__be16 inner_udp_len_be = bpf_htons(inner_udp_len);
436-
if (bpf_xdp_store_bytes(ctx, l4_offset + 24, &inner_udp_len_be, 2) < 0)
459+
if (bpf_xdp_store_bytes(ctx, inner_l4 + 4, &inner_udp_len_be, 2) < 0)
437460
return false;
438461
}
439462
}
463+
} else if (proto == IPPROTO_IPV6) {
464+
// L3VPN over SRv6: inner IPv6 after SRH
465+
if (target_len > l4_offset + sizeof(struct ipv6hdr)) {
466+
__u16 inner_payload_len = target_len - l4_offset - sizeof(struct ipv6hdr);
467+
__be16 inner_payload_len_be = bpf_htons(inner_payload_len);
468+
if (bpf_xdp_store_bytes(ctx, l4_offset + 4, &inner_payload_len_be, 2) < 0)
469+
return false;
470+
}
440471
}
441472
}
442473

0 commit comments

Comments
 (0)