@@ -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
145150static __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