bpf: Update transport_header when encapsulating UDP tunnel in lwt
Subsystems (5)
v3 cover bpf: Update transport_header when encapsulating UDP tunnel in lwt
Currently, bpf_lwt_push_ip_encap() does not update skb->transport_header. When a driver, e.g. ice, reuses the stale skb->transport_header to offload checksum computation to NIC hardware, VxLAN packets encapsulated by bpf_lwt_push_encap() helper may be dropped due to incorrect checksum.
Update skb->transport_header in bpf_lwt_push_ip_encap() whenever the encapsulated packet uses UDP, so checksum offload works correctly.
Changes:
v2 -> v3:
* Drop patch #1 and #2 of v2 that aim to resolve potential issues reported by sashiko (per Alexei).
* Check target IP version and UDP tunnel in test (per sashiko).
* v2: https://lore.kernel.org/bpf/[email protected]/
v1 -> v2:
* Address sashiko's reviews:
* Fix TOCTOU issue in lwt to avoid changing hdr after checks.
* Add check iph->ihl < 5 in lwt to avoid infinite-loop in MIPS driver.
* Update comment style in selftests with BPF comment style.
* v1: https://lore.kernel.org/bpf/[email protected]/
Leon Hwang (2):
bpf: Update transport_header when encapsulating UDP tunnel in lwt selftests/bpf: Add tests to verify the fix of encapsulating VxLAN in
lwt
net/core/lwt_bpf.c | 11 ++ .../selftests/bpf/prog_tests/lwt_ip_encap.c | 164 ++++++++++++++++++ .../selftests/bpf/progs/test_lwt_ip_encap.c | 112 ++++++++++++ .../bpf/progs/test_lwt_ip_encap_fix.c | 44 +++++ 4 files changed, 331 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/test_lwt_ip_encap_fix.c
Signature · 1 line
2.54.0
PATCH v3 1/2 bpf: Update transport_header when encapsulating UDP tunnel in lwt
Currently, bpf_lwt_push_ip_encap() does not update skb->transport_header. When a driver, e.g. ice, reuses the stale skb->transport_header to offload checksum computation to NIC hardware, VxLAN packets encapsulated by bpf_lwt_push_encap() helper may be dropped due to incorrect checksum.
Update skb->transport_header in bpf_lwt_push_ip_encap() whenever the encapsulated packet uses UDP, so checksum offload works correctly.
--- net/core/lwt_bpf.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
net/core/lwt_bpf.c +11 -0
@@ -599,6 +599,7 @@ static int handle_gso_encap(struct sk_buff *skb, bool ipv4, int encap_len)int bpf_lwt_push_ip_encap (struct sk_buff *skb,void *hdr,u32 len,bool ingress) { +bool is_udp_tunnel;struct iphdr *iph;bool ipv4;int err;
@@ -612,10 +613,16 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) ipv4 = true;if (unlikely (len < iph->ihl *4 ))return -EINVAL ; + is_udp_tunnel = iph->protocol ==IPPROTO_UDP ; +if (unlikely (is_udp_tunnel && len < iph->ihl *4 +sizeof (struct udphdr ))) +return -EINVAL ; }else if (iph->version ==6 ) { ipv4 = false;if (unlikely (len <sizeof (struct ipv6hdr )))return -EINVAL ; + is_udp_tunnel = ((struct ipv6hdr *)iph)->nexthdr ==NEXTHDR_UDP ; +if (unlikely (is_udp_tunnel && len <sizeof (struct ipv6hdr ) +sizeof (struct udphdr ))) +return -EINVAL ; }else {return -EINVAL ; }
@@ -637,6 +644,10 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress)if (ingress)skb_postpush_rcsum (skb, iph, len);skb_reset_network_header (skb); +if (ipv4 && is_udp_tunnel) +skb_set_transport_header (skb,skb_network_offset (skb) + iph->ihl *4 ); +else if (!ipv4 && is_udp_tunnel) +skb_set_transport_header (skb,skb_network_offset (skb) +sizeof (struct ipv6hdr ));memcpy (skb_network_header (skb), hdr, len);bpf_compute_data_pointers (skb);skb_clear_hash (skb);
Signature · 1 line
2.54.0
[email protected]
AI reviewThank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Time-of-Check to Time-of-Use (TOCTOU) vulnerability in transport header offset calculation leads to out-of-bounds write.
Pre-existing issues:
- [Critical] Missing validation of iph->ihl >= 5 allows unprivileged BPF programs to trigger a kernel panic in ip_fast_csum. --
commit 0be2d8b388e17b2000afd2e38ff89f5f956084ae
Author: Leon Hwang <[email protected]>
bpf: Update transport_header when encapsulating UDP tunnel in lwt
This commit updates bpf_lwt_push_ip_encap() to set the skb transport header whenever the encapsulated packet uses UDP. This ensures that drivers relying on the transport header for hardware checksum offload do not drop packets due to incorrect checksums.
net/core/lwt_bpf.c int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) · line 615
Quoted 8 lines
net/core/lwt_bpf.c +0
-0@@ -612,10 +613,16 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) ipv4 = true;if (unlikely (len < iph->ihl *4 ))return -EINVAL ;
[Severity: Critical]
This is a pre-existing issue, but does this code properly validate the minimum IPv4 header length?
Because len is an unsigned u32, if a BPF program provides a buffer where iph->ihl is 0, iph->ihl * 4 will be 0. Since len < 0 is never true, the check trivially passes without returning -EINVAL.
Later in the function, ip_fast_csum() is called with iph->ihl. On some architectures (like PowerPC and MIPS), the ip_fast_csum() implementation assumes ihl >= 5 and subtracts from it, causing an integer underflow in the loop counter that leads to a deterministic kernel panic.
Should there be an explicit check that enforces iph->ihl >= 5 before the length calculation?
Quoted 4 lines
net/core/lwt_bpf.c +3
-0+ is_udp_tunnel = iph->protocol ==IPPROTO_UDP ; +if (unlikely (is_udp_tunnel && len < iph->ihl *4 +sizeof (struct udphdr ))) +return -EINVAL ; }else if (iph->version ==6 ) {
[ ... ]
net/core/lwt_bpf.c int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) · line 648
Quoted 6 lines
net/core/lwt_bpf.c +2
-0@@ -637,6 +644,10 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress)if (ingress)skb_postpush_rcsum (skb, iph, len);skb_reset_network_header (skb); +if (ipv4 && is_udp_tunnel) +skb_set_transport_header (skb,skb_network_offset (skb) + iph->ihl *4 );
[Severity: High]
Can iph->ihl change between the initial length validation and this call?
The hdr pointer (and thus iph) points to a buffer provided by the BPF program, which might be concurrently modified by userspace via a shared BPF map. Because skb_cow_head() acts as a compiler barrier, the compiler will reload iph->ihl from this untrusted memory here.
If it is concurrently changed to a larger value (like 15) after the initial validation, the transport header offset could be set beyond the end of the SKB data (skb->tail).
Could this lead to out-of-bounds memory writes when network drivers later dereference this transport header to update checksums?
Quoted 3 lines
net/core/lwt_bpf.c +2
-0+else if (!ipv4 && is_udp_tunnel) +skb_set_transport_header (skb,skb_network_offset (skb) +sizeof (struct ipv6hdr ));memcpy (skb_network_header (skb), hdr, len);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Quoted 46 lines · Leon Hwang
Currently,
bpf_lwt_push_ip_encap()does not updateskb->transport_header. When a driver, e.g. ice, reuses the staleskb->transport_headerto offload checksum computation to NIC hardware, VxLAN packets encapsulated bybpf_lwt_push_encap()helper may be dropped due to incorrect checksum.Update
skb->transport_headerinbpf_lwt_push_ip_encap()whenever the encapsulated packet uses UDP, so checksum offload works correctly.Fixes 52f278774e79 ("bpf: implement BPF_LWT_ENCAP_IP mode in bpf_lwt_push_encap")Cc Leon Hwang <[email protected]>Signed-off-by Leon Hwang <[email protected]>--- net/core/lwt_bpf.c | 11 +++++++++++ 1 file changed, 11 insertions(+)net/core/lwt_bpf.c +7
-0@@ -599,6 +599,7 @@ static int handle_gso_encap(struct sk_buff *skb, bool ipv4, int encap_len)int bpf_lwt_push_ip_encap (struct sk_buff *skb,void *hdr,u32 len,bool ingress) { +bool is_udp_tunnel;struct iphdr *iph;bool ipv4;int err;@@ -612,10 +613,16 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) ipv4 = true;if (unlikely (len < iph->ihl *4 ))return -EINVAL ; + is_udp_tunnel = iph->protocol ==IPPROTO_UDP ; +if (unlikely (is_udp_tunnel && len < iph->ihl *4 +sizeof (struct udphdr ))) +return -EINVAL ; }else if (iph->version ==6 ) { ipv4 = false;if (unlikely (len <sizeof (struct ipv6hdr )))return -EINVAL ; + is_udp_tunnel = ((struct ipv6hdr *)iph)->nexthdr ==NEXTHDR_UDP ; +if (unlikely (is_udp_tunnel && len <sizeof (struct ipv6hdr ) +sizeof (struct udphdr ))) +return -EINVAL ; }else {return -EINVAL ; }
net/core/lwt_bpf.c int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) · line 646
Quoted 3 lines
net/core/lwt_bpf.c +0
-0@@ -637,6 +644,10 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress)if (ingress)skb_postpush_rcsum (skb, iph, len);skb_reset_network_header (skb);
iph might point to freed memory after skb_cow_head(), leading to UAF in iph->ihl
This issue is already there in the skb_postpush_rcsum(skb, iph, len) call above.
Quoted 10 lines
net/core/lwt_bpf.c +4
-1+if (ipv4 && is_udp_tunnel) +skb_set_transport_header (skb,skb_network_offset (skb) + iph->ihl *4 ); +else if (!ipv4 && is_udp_tunnel) +skb_set_transport_header (skb,skb_network_offset (skb) +sizeof (struct ipv6hdr ));memcpy (skb_network_header (skb), hdr, len);bpf_compute_data_pointers (skb);skb_clear_hash (skb); --2.54.0
Quoted 54 lines · Eric Dumazet
Quoted 46 lines · Leon Hwang
Currently,
bpf_lwt_push_ip_encap()does not updateskb->transport_header. When a driver, e.g. ice, reuses the staleskb->transport_headerto offload checksum computation to NIC hardware, VxLAN packets encapsulated bybpf_lwt_push_encap()helper may be dropped due to incorrect checksum.Update
skb->transport_headerinbpf_lwt_push_ip_encap()whenever the encapsulated packet uses UDP, so checksum offload works correctly.Fixes 52f278774e79 ("bpf: implement BPF_LWT_ENCAP_IP mode in bpf_lwt_push_encap")Cc Leon Hwang <[email protected]>Signed-off-by Leon Hwang <[email protected]>--- net/core/lwt_bpf.c | 11 +++++++++++ 1 file changed, 11 insertions(+)net/core/lwt_bpf.c +7
-0@@ -599,6 +599,7 @@ static int handle_gso_encap(struct sk_buff *skb, bool ipv4, int encap_len)int bpf_lwt_push_ip_encap (struct sk_buff *skb,void *hdr,u32 len,bool ingress) { +bool is_udp_tunnel;struct iphdr *iph;bool ipv4;int err;@@ -612,10 +613,16 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) ipv4 = true;if (unlikely (len < iph->ihl *4 ))return -EINVAL ; + is_udp_tunnel = iph->protocol ==IPPROTO_UDP ; +if (unlikely (is_udp_tunnel && len < iph->ihl *4 +sizeof (struct udphdr ))) +return -EINVAL ; }else if (iph->version ==6 ) { ipv4 = false;if (unlikely (len <sizeof (struct ipv6hdr )))return -EINVAL ; + is_udp_tunnel = ((struct ipv6hdr *)iph)->nexthdr ==NEXTHDR_UDP ; +if (unlikely (is_udp_tunnel && len <sizeof (struct ipv6hdr ) +sizeof (struct udphdr ))) +return -EINVAL ; }else {return -EINVAL ; }net/core/lwt_bpf.c int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) · line 646
Quoted 3 lines
net/core/lwt_bpf.c +0
-0@@ -637,6 +644,10 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress)if (ingress)skb_postpush_rcsum (skb, iph, len);skb_reset_network_header (skb);iph might point to freed memory after
skb_cow_head(), leading to UAF iniph->ihl
Quoted 1 line
This issue is already there in the
skb_postpush_rcsum(skb, iph, len)call above.
Ignore this, I missed the context about iph, which does not point to skb->head
[email protected]
AI reviewSince len is a u32, if iph->ihl is 0 then iph->ihl * 4 is 0 and the "len < iph->ihl * 4" check is never true, so it passes without returning -EINVAL.
net/core/lwt_bpf.c int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) · line 615
Quoted 8 lines
net/core/lwt_bpf.c +0
-0@@ -612,10 +613,16 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) ipv4 = true;if (unlikely (len < iph->ihl *4 ))return -EINVAL ;
Since len is a u32, if iph->ihl is 0 then iph->ihl * 4 is 0 and the "len < iph->ihl * 4" check is never true, so it passes without returning -EINVAL.
Later ip_fast_csum() is called with iph->ihl, and on some architectures (PowerPC, MIPS) that implementation assumes ihl >= 5 and decrements the loop counter, which underflows when ihl is 0.
Should there be an explicit iph->ihl >= 5 check here, for example:
if (unlikely(iph->ihl < 5 || len < iph->ihl * 4)) return -EINVAL;
This was raised in review of v1 and v2 added the guard, but v3 appears to have dropped it again. The earlier discussion is here:
Quoted 13 lines
net/core/lwt_bpf.c +6
-0+ is_udp_tunnel = iph->protocol ==IPPROTO_UDP ; +if (unlikely (is_udp_tunnel && len < iph->ihl *4 +sizeof (struct udphdr ))) +return -EINVAL ; }else if (iph->version ==6 ) { ipv4 = false;if (unlikely (len <sizeof (struct ipv6hdr )))return -EINVAL ; + is_udp_tunnel = ((struct ipv6hdr *)iph)->nexthdr ==NEXTHDR_UDP ; +if (unlikely (is_udp_tunnel && len <sizeof (struct ipv6hdr ) +sizeof (struct udphdr ))) +return -EINVAL ; }else {return -EINVAL ; }
[ ... ]
net/core/lwt_bpf.c int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) · line 651
Quoted 9 lines
net/core/lwt_bpf.c +4
-0@@ -637,6 +644,10 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress)if (ingress)skb_postpush_rcsum (skb, iph, len);skb_reset_network_header (skb); +if (ipv4 && is_udp_tunnel) +skb_set_transport_header (skb,skb_network_offset (skb) + iph->ihl *4 ); +else if (!ipv4 && is_udp_tunnel) +skb_set_transport_header (skb,skb_network_offset (skb) +sizeof (struct ipv6hdr ));memcpy (skb_network_header (skb), hdr, len);
Can iph->ihl change between the initial length validation and this call?
The hdr pointer (and therefore iph) refers to a buffer provided by the BPF program, which can be concurrently modified by userspace through a shared BPF map. Because skb_cow_head() acts as a compiler barrier, the compiler reloads iph->ihl from that untrusted memory here.
This read also happens before the memcpy() into the skb:
skb_set_transport_header(skb, skb_network_offset(skb) + iph->ihl * 4); memcpy(skb_network_header(skb), hdr, len);
If iph->ihl is concurrently changed to a larger value (such as 15) after the initial validation, can the transport header offset be set beyond skb->tail, leading to out-of-bounds writes when drivers later dereference the transport header for checksum updates?
In the v1 discussion the suggestion was to move skb_set_transport_header() after the memcpy() so it reads from the copied-in header, but the current code still reads iph->ihl before the memcpy(). See:
https://lore.kernel.org/bpf/[email protected]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26763883406
Emil Tsalapatis
reviewerQuoted 45 lines · Leon Hwang
Currently,
bpf_lwt_push_ip_encap()does not updateskb->transport_header. When a driver, e.g. ice, reuses the staleskb->transport_headerto offload checksum computation to NIC hardware, VxLAN packets encapsulated bybpf_lwt_push_encap()helper may be dropped due to incorrect checksum.Update
skb->transport_headerinbpf_lwt_push_ip_encap()whenever the encapsulated packet uses UDP, so checksum offload works correctly.Fixes 52f278774e79 ("bpf: implement BPF_LWT_ENCAP_IP mode in bpf_lwt_push_encap")Cc Leon Hwang <[email protected]>Signed-off-by Leon Hwang <[email protected]>--- net/core/lwt_bpf.c | 11 +++++++++++ 1 file changed, 11 insertions(+)net/core/lwt_bpf.c +7
-0@@ -599,6 +599,7 @@ static int handle_gso_encap(struct sk_buff *skb, bool ipv4, int encap_len)int bpf_lwt_push_ip_encap (struct sk_buff *skb,void *hdr,u32 len,bool ingress) { +bool is_udp_tunnel;struct iphdr *iph;bool ipv4;int err;@@ -612,10 +613,16 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) ipv4 = true;if (unlikely (len < iph->ihl *4 ))return -EINVAL ; + is_udp_tunnel = iph->protocol ==IPPROTO_UDP ; +if (unlikely (is_udp_tunnel && len < iph->ihl *4 +sizeof (struct udphdr ))) +return -EINVAL ; }else if (iph->version ==6 ) { ipv4 = false;if (unlikely (len <sizeof (struct ipv6hdr )))return -EINVAL ; + is_udp_tunnel = ((struct ipv6hdr *)iph)->nexthdr ==NEXTHDR_UDP ; +if (unlikely (is_udp_tunnel && len <sizeof (struct ipv6hdr ) +sizeof (struct udphdr ))) +return -EINVAL ; }else {return -EINVAL ; }
net/core/lwt_bpf.c int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) · line 650
Quoted 7 lines
net/core/lwt_bpf.c +4
-0@@ -637,6 +644,10 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress)if (ingress)skb_postpush_rcsum (skb, iph, len);skb_reset_network_header (skb); +if (ipv4 && is_udp_tunnel) +skb_set_transport_header (skb,skb_network_offset (skb) + iph->ihl *4 ); +else if (!ipv4 && is_udp_tunnel) +skb_set_transport_header (skb,skb_network_offset (skb) +sizeof (struct ipv6hdr ));
Minor nit: Could we do something like
if (is_udp_tunnel) {
size_t hrdsz = ipv4 ? iph->ihl * 4 : sizeof(struct ipv6hdr);
skb_set_transport_header(skb9, skb_network_offset(skb)) + hdrsz;
}to make the logic clearer?
Quoted 3 lines
net/core/lwt_bpf.c +0
-0memcpy (skb_network_header (skb), hdr, len);bpf_compute_data_pointers (skb);skb_clear_hash (skb);
Quoted 1 line · Emil Tsalapatis
On Mon Jun 1, 2026 at 11:02 AM EDT, Leon Hwang wrote:
[...]
Quoted 11 lines
net/core/lwt_bpf.c int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) · line 650
Quoted 8 lines
net/core/lwt_bpf.c +4
-0@@ -637,6 +644,10 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress)if (ingress)skb_postpush_rcsum (skb, iph, len);skb_reset_network_header (skb); +if (ipv4 && is_udp_tunnel) +skb_set_transport_header (skb,skb_network_offset (skb) + iph->ihl *4 ); +else if (!ipv4 && is_udp_tunnel) +skb_set_transport_header (skb,skb_network_offset (skb) +sizeof (struct ipv6hdr ));Minor nit: Could we do something like
Quoted 7 lines
if (is_udp_tunnel) { size_t hrdsz = ipv4 ? iph->ihl * 4 : sizeof(struct ipv6hdr); skb_set_transport_header(skb9, skb_network_offset(skb)) + hdrsz; }to make the logic clearer?
Make sense.
Thanks,
Leon
Quoted 4 lines
Quoted 3 lines
net/core/lwt_bpf.c +0
-0memcpy (skb_network_header (skb), hdr, len);bpf_compute_data_pointers (skb);skb_clear_hash (skb);
Quoted 45 lines · Leon Hwang
Currently,
bpf_lwt_push_ip_encap()does not updateskb->transport_header. When a driver, e.g. ice, reuses the staleskb->transport_headerto offload checksum computation to NIC hardware, VxLAN packets encapsulated bybpf_lwt_push_encap()helper may be dropped due to incorrect checksum.Update
skb->transport_headerinbpf_lwt_push_ip_encap()whenever the encapsulated packet uses UDP, so checksum offload works correctly.Fixes 52f278774e79 ("bpf: implement BPF_LWT_ENCAP_IP mode in bpf_lwt_push_encap")Cc Leon Hwang <[email protected]>Signed-off-by Leon Hwang <[email protected]>--- net/core/lwt_bpf.c | 11 +++++++++++ 1 file changed, 11 insertions(+)net/core/lwt_bpf.c +7
-0@@ -599,6 +599,7 @@ static int handle_gso_encap(struct sk_buff *skb, bool ipv4, int encap_len)int bpf_lwt_push_ip_encap (struct sk_buff *skb,void *hdr,u32 len,bool ingress) { +bool is_udp_tunnel;struct iphdr *iph;bool ipv4;int err;@@ -612,10 +613,16 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) ipv4 = true;if (unlikely (len < iph->ihl *4 ))return -EINVAL ; + is_udp_tunnel = iph->protocol ==IPPROTO_UDP ; +if (unlikely (is_udp_tunnel && len < iph->ihl *4 +sizeof (struct udphdr ))) +return -EINVAL ; }else if (iph->version ==6 ) { ipv4 = false;if (unlikely (len <sizeof (struct ipv6hdr )))return -EINVAL ; + is_udp_tunnel = ((struct ipv6hdr *)iph)->nexthdr ==NEXTHDR_UDP ; +if (unlikely (is_udp_tunnel && len <sizeof (struct ipv6hdr ) +sizeof (struct udphdr ))) +return -EINVAL ; }else {return -EINVAL ; }
net/core/lwt_bpf.c int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) · line 650
Quoted 7 lines
net/core/lwt_bpf.c +4
-0@@ -637,6 +644,10 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress)if (ingress)skb_postpush_rcsum (skb, iph, len);skb_reset_network_header (skb); +if (ipv4 && is_udp_tunnel) +skb_set_transport_header (skb,skb_network_offset (skb) + iph->ihl *4 ); +else if (!ipv4 && is_udp_tunnel) +skb_set_transport_header (skb,skb_network_offset (skb) +sizeof (struct ipv6hdr ));
I think GRE is also affected, why not unconditionally set transport header to network_offset + outer_ip_ihl regardless of outer protocol??
Quoted 3 lines
net/core/lwt_bpf.c +0
-0memcpy (skb_network_header (skb), hdr, len);bpf_compute_data_pointers (skb);skb_clear_hash (skb);
Quoted 2 lines · Jiayuan Chen
On 6/1/26 11:02 PM, Leon Hwang wrote:
[...]
Quoted 7 lines
Quoted 7 lines
net/core/lwt_bpf.c +2
-0@@ -637,6 +644,10 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) if (ingress) skb_postpush_rcsum(skb, iph, len); skb_reset_network_header(skb); + if (ipv4 && is_udp_tunnel) + skb_set_transport_header(skb, skb_network_offset(skb) + iph-
Quoted 9 lines
Review int bpf_lwt_push_ip_encap(struct sk_buff *skb, · line 653
Quoted 4 lines
(quoted diff) +2
-0>ihl * 4); + else if (!ipv4 && is_udp_tunnel) + skb_set_transport_header(skb, skb_network_offset(skb) + sizeof(struct ipv6hdr));I think GRE is also affected, why not unconditionally set transport header to
network_offset+outer_ip_ihlregardless of outer protocol??
Probably, yes. But, I'm not sure about this.
At Shopee, we encountered the issue that the VxLAN packets encapsulated with lwt-bpf were dropped on ice driver. And, it is to be fixed by this patch.
IOW, if someone encounter the same issue for GRE packets, he can fix the issue by the same way.
Thanks,
Leon
Quoted 3 lines
Quoted 3 lines
memcpy(skb_network_header(skb), hdr, len);
bpf_compute_data_pointers(skb);
skb_clear_hash(skb);
PATCH v3 2/2 selftests/bpf: Add tests to verify the fix of encapsulating VxLAN in lwt
In reply to Leon HwangAdd two tests to verify the transport header of skb has been set when encapsulate VxLAN using bpf_lwt_push_encap() helper.
1. VxLAN over IPv4.
2. VxLAN over IPv6.
Without the fix, the tests would fail:
lwt_ip_encap_vxlan:FAIL:transport_hdr offset unexpected transport_hdr offset: actual 70 != expected 20
#208 lwt_ip_encap_vxlan_ipv4:FAIL
lwt_ip_encap_vxlan:FAIL:transport_hdr offset unexpected transport_hdr offset: actual 110 != expected 40
#209 lwt_ip_encap_vxlan_ipv6:FAIL
--- .../selftests/bpf/prog_tests/lwt_ip_encap.c | 164 ++++++++++++++++++ .../selftests/bpf/progs/test_lwt_ip_encap.c | 112 ++++++++++++ .../bpf/progs/test_lwt_ip_encap_fix.c | 44 +++++ 3 files changed, 320 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/test_lwt_ip_encap_fix.c
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c +164 -0
@@ -1,8 +1,11 @@// SPDX-License-Identifier: GPL-2.0-only +#include <linux/ip.h> +#include <linux/ipv6.h> #include <netinet/in.h> #include "network_helpers.h" #include "test_progs.h" +#include "test_lwt_ip_encap_fix.skel.h" #define BPF_FILE "test_lwt_ip_encap.bpf.o"
@@ -35,6 +38,10 @@#define IP6_ADDR_SRC IP6_ADDR_1#define IP6_ADDR_DST IP6_ADDR_4 +/* VxLAN tunnel endpoints, reachable via the bottom route (veth5/6/7/8). */ +#define IP4_ADDR_VXLAN "172.16.17.100" +#define IP6_ADDR_VXLAN "fb20::1" +/* Setup/topology: * * NS1 NS2 NS3
@@ -538,3 +545,160 @@ void test_lwt_ip_encap_ipv4(void)if (test__start_subtest ("ingress" ))lwt_ip_encap (IPV4_ENCAP ,INGRESS ,"" ); } + +/* +* VxLAN Setup/topology: +* +* NS1 (IP*_ADDR_1) NS2 NS3 (IP*_ADDR_4) +* [ping src] +* | top route +* veth1 (LWT encap) <<-- veth2 veth3 <<-- veth4 (ping dst) +* | ^ +* (bottom route) | (inner pkt) +* v bottom route | +* veth5 -->> veth6 veth7 -->> veth8 (vxlan decap) +* (IP*_ADDR_VXLAN) +* +* Add the VxLAN endpoint addresses to NS3's veth8, create standard +* VxLAN decap devices bound to those addresses, and install routes so +* NS1/NS2 can reach the endpoints via the bottom route. +*/ +static int setup_vxlan_routes (const char *ns3,const char *ns1,const char *ns2) +{ +struct nstoken *nstoken; + + nstoken =open_netns (ns3); +if (!ASSERT_OK_PTR (nstoken,"open ns3 for vxlan" )) +return -1 ; + +SYS (fail_close,"ip a add %s/32 dev veth8" ,IP4_ADDR_VXLAN ); +SYS (fail_close,"ip -6 a add %s/128 dev veth8" ,IP6_ADDR_VXLAN ); +/* +* Standard VxLAN devices to decap the encapsulated packets. The inner +* Ethernet frame uses a broadcast dst MAC so the IP stack accepts it +* without ARP or FDB configuration. +*/ +SYS (fail_close,"ip link add vxlan4 type vxlan id 1 dstport 4789 local %s dev veth8 nolearning noudpcsum" , +IP4_ADDR_VXLAN ); +SYS (fail_close,"ip link set vxlan4 up" ); +SYS (fail_close,"ip link add vxlan6 type vxlan id 1 dstport 4789 local %s dev veth8 nolearning udp6zerocsumrx" , +IP6_ADDR_VXLAN ); +SYS (fail_close,"ip link set vxlan6 up" ); +close_netns (nstoken); + +SYS (fail,"ip -n %s route add %s/32 dev veth5 via %s" , + ns1,IP4_ADDR_VXLAN ,IP4_ADDR_6 ); +SYS (fail,"ip -n %s route add %s/32 dev veth7 via %s" , + ns2,IP4_ADDR_VXLAN ,IP4_ADDR_8 ); +SYS (fail,"ip -n %s -6 route add %s/128 dev veth5 via %s" , + ns1,IP6_ADDR_VXLAN ,IP6_ADDR_6 ); +SYS (fail,"ip -n %s -6 route add %s/128 dev veth7 via %s" , + ns2,IP6_ADDR_VXLAN ,IP6_ADDR_8 ); +return 0 ; + +fail_close: +close_netns (nstoken); +fail: +return -1 ; +} + +/* +* VxLAN encap tests (IPv4-outer and IPv6-outer variants). +* +* Test 1 - functional: the BPF LWT xmit program encapsulates the packet +* (protocol=UDP, port=4789) and re-routes it without dropping it. +* Verified by ping success. +* +* Test 2 - fix verification: after bpf_lwt_push_ip_encap() the +* skb->transport_header must point at the outer UDP header, i.e. +* transport_header - network_header == sizeof(outer IP header). +* Without the fix the transport_header still points at the inner +* transport layer, giving a wrong (larger) offset. +*/ +static void lwt_ip_encap_vxlan (bool ipv4_encap) +{ +char ns1[NETNS_NAME_SIZE ] =NETNS_BASE "-1-" ; +char ns2[NETNS_NAME_SIZE ] =NETNS_BASE "-2-" ; +char ns3[NETNS_NAME_SIZE ] =NETNS_BASE "-3-" ; +const char *sec = ipv4_encap ?"encap_vxlan" :"encap_vxlan6" ; +int expected_offset = ipv4_encap ? (int )sizeof (struct iphdr ) + : (int )sizeof (struct ipv6hdr ); +struct test_lwt_ip_encap_fix *skel =NULL ; +int thdr_offset, err; + +if (!ASSERT_OK (create_ns (ns1,NETNS_NAME_SIZE ),"create ns1" )) + goto out; +if (!ASSERT_OK (create_ns (ns2,NETNS_NAME_SIZE ),"create ns2" )) + goto out; +if (!ASSERT_OK (create_ns (ns3,NETNS_NAME_SIZE ),"create ns3" )) + goto out; + +if (!ASSERT_OK (setup_network (ns1, ns2, ns3,"" ),"setup network" )) + goto out; + +if (!ASSERT_OK (setup_vxlan_routes (ns3, ns1, ns2),"setup vxlan routes" )) + goto out; + +/* +* Attach fexit to bpf_lwt_push_ip_encap() before installing the +* LWT route so we don't miss the first encap call. +*/ + skel =test_lwt_ip_encap_fix__open (); +if (!ASSERT_OK_PTR (skel,"test_lwt_ip_encap_fix__open" )) + goto out; + + skel->rodata->tgt_ip_version = ipv4_encap ?4 :6 ; + + err =test_lwt_ip_encap_fix__load (skel); +if (!ASSERT_OK (err,"test_lwt_ip_encap_fix__load" )) + goto out; + + err =test_lwt_ip_encap_fix__attach (skel); +if (!ASSERT_OK (err,"test_lwt_ip_encap_fix__attach" )) + goto out; + +/* Remove the direct NS2->DST route so packets must go via LWT encap. */ +SYS (out,"ip -n %s route del %s/32 dev veth3" , ns2,IP4_ADDR_DST ); +SYS (out,"ip -n %s -6 route del %s/128 dev veth3" , ns2,IP6_ADDR_DST ); + +/* Install the VxLAN BPF LWT xmit route. */ +if (ipv4_encap) +SYS (out,"ip -n %s route add %s encap bpf xmit obj %s sec %s dev veth1" , + ns1,IP4_ADDR_DST ,BPF_FILE , sec); +else +SYS (out,"ip -n %s -6 route add %s encap bpf xmit obj %s sec %s dev veth1" , + ns1,IP6_ADDR_DST ,BPF_FILE , sec); + + skel->bss->fexit_triggered = false; +if (ipv4_encap) +SYS (out,"ip netns exec %s ping -c 1 -W1 %s" , ns1,IP4_ADDR_DST ); +else +SYS (out,"ip netns exec %s ping6 -c 1 -W1 %s" , ns1,IP6_ADDR_DST ); + +/* Test 1: fexit triggered means bpf_lwt_push_ip_encap() succeeded. */ +if (!ASSERT_TRUE (skel->bss->fexit_triggered,"fexit_triggered" )) + goto out; + +/* +* Test 2: transport_header must sit immediately after the outer IP +* header, pointing at the UDP header of the VxLAN encap. +*/ + thdr_offset = (int )skel->bss->transport_hdr - (int )skel->bss->network_hdr; +ASSERT_EQ (thdr_offset, expected_offset,"transport_hdr offset" ); + +out: +test_lwt_ip_encap_fix__destroy (skel); +SYS_NOFAIL ("ip netns del %s" , ns1); +SYS_NOFAIL ("ip netns del %s" , ns2); +SYS_NOFAIL ("ip netns del %s" , ns3); +} + +void test_lwt_ip_encap_vxlan_ipv4 (void ) +{ +lwt_ip_encap_vxlan (IPV4_ENCAP ); +} + +void test_lwt_ip_encap_vxlan_ipv6 (void ) +{ +lwt_ip_encap_vxlan (IPV6_ENCAP ); +}
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c +112 -0
124 lines not on this page · raw message
tools/testing/selftests/bpf/progs/test_lwt_ip_encap_fix.c new file +44 -0
44 lines not on this page · raw message
Signature · 1 line
2.54.0
Emil Tsalapatis
reviewerRe: [PATCH bpf v3 2/2] selftests/bpf: Add tests to verify the fix of encapsulating VxLAN in lwt
Quoted 40 lines · Leon Hwang
Add two tests to verify the transport header of skb has been set when encapsulate VxLAN using
bpf_lwt_push_encap()helper.1. VxLAN over IPv4.
2. VxLAN over IPv6.Without the fix, the tests would fail:
lwt_ip_encap_vxlan:FAIL:transport_hdr offset unexpected
transport_hdroffset: actual 70 != expected 20
#208 lwt_ip_encap_vxlan_ipv4:FAIL
lwt_ip_encap_vxlan:FAIL:transport_hdr offset unexpectedtransport_hdroffset: actual 110 != expected 40
#209 lwt_ip_encap_vxlan_ipv6:FAILAssisted-by Claude:claude-sonnet-4-6Cc Leon Hwang <[email protected]>Signed-off-by Leon Hwang <[email protected]>--- .../selftests/bpf/prog_tests/lwt_ip_encap.c | 164 ++++++++++++++++++ .../selftests/bpf/progs/test_lwt_ip_encap.c | 112 ++++++++++++ .../bpf/progs/test_lwt_ip_encap_fix.c | 44 +++++ 3 files changed, 320 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/test_lwt_ip_encap_fix.ctools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c +3
-0@@ -1,8 +1,11 @@// SPDX-License-Identifier: GPL-2.0-only +#include <linux/ip.h> +#include <linux/ipv6.h> #include <netinet/in.h> #include "network_helpers.h" #include "test_progs.h" +#include "test_lwt_ip_encap_fix.skel.h" #define BPF_FILE "test_lwt_ip_encap.bpf.o"
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c line 43
Quoted 6 lines
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c +3
-0@@ -35,6 +38,10 @@#define IP6_ADDR_SRC IP6_ADDR_1#define IP6_ADDR_DST IP6_ADDR_4 +/* VxLAN tunnel endpoints, reachable via the bottom route (veth5/6/7/8). */ +#define IP4_ADDR_VXLAN "172.16.17.100" +#define IP6_ADDR_VXLAN "fb20::1"
There's a whole series of existing IP definitions at the top of the file, we should put those new ones right below them. And fb20 -> fb11 to keep with the pattern imo.
Quoted 12 lines
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c +5
-0+/* Setup/topology: * * NS1 NS2 NS3 @@ -538,3 +545,160 @@ void test_lwt_ip_encap_ipv4(void)if (test__start_subtest ("ingress" ))lwt_ip_encap (IPV4_ENCAP ,INGRESS ,"" ); } + +/* +* VxLAN Setup/topology: +*
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c void test_lwt_ip_encap_ipv4(void) · line 561
Quoted 10 lines
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c +10
-0+* NS1 (IP*_ADDR_1) NS2 NS3 (IP*_ADDR_4) +* [ping src] +* | top route +* veth1 (LWT encap) <<-- veth2 veth3 <<-- veth4 (ping dst) +* | ^ +* (bottom route) | (inner pkt) +* v bottom route | +* veth5 -->> veth6 veth7 -->> veth8 (vxlan decap) +* (IP*_ADDR_VXLAN) +*
Not sure if this is rendering weird for me but NS2 could be tabbed over once more for clarity.
Quoted 46 lines
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c +46
-0+* Add the VxLAN endpoint addresses to NS3's veth8, create standard +* VxLAN decap devices bound to those addresses, and install routes so +* NS1/NS2 can reach the endpoints via the bottom route. +*/ +static int setup_vxlan_routes (const char *ns3,const char *ns1,const char *ns2) +{ +struct nstoken *nstoken; + + nstoken =open_netns (ns3); +if (!ASSERT_OK_PTR (nstoken,"open ns3 for vxlan" )) +return -1 ; + +SYS (fail_close,"ip a add %s/32 dev veth8" ,IP4_ADDR_VXLAN ); +SYS (fail_close,"ip -6 a add %s/128 dev veth8" ,IP6_ADDR_VXLAN ); +/* +* Standard VxLAN devices to decap the encapsulated packets. The inner +* Ethernet frame uses a broadcast dst MAC so the IP stack accepts it +* without ARP or FDB configuration. +*/ +SYS (fail_close,"ip link add vxlan4 type vxlan id 1 dstport 4789 local %s dev veth8 nolearning noudpcsum" , +IP4_ADDR_VXLAN ); +SYS (fail_close,"ip link set vxlan4 up" ); +SYS (fail_close,"ip link add vxlan6 type vxlan id 1 dstport 4789 local %s dev veth8 nolearning udp6zerocsumrx" , +IP6_ADDR_VXLAN ); +SYS (fail_close,"ip link set vxlan6 up" ); +close_netns (nstoken); + +SYS (fail,"ip -n %s route add %s/32 dev veth5 via %s" , + ns1,IP4_ADDR_VXLAN ,IP4_ADDR_6 ); +SYS (fail,"ip -n %s route add %s/32 dev veth7 via %s" , + ns2,IP4_ADDR_VXLAN ,IP4_ADDR_8 ); +SYS (fail,"ip -n %s -6 route add %s/128 dev veth5 via %s" , + ns1,IP6_ADDR_VXLAN ,IP6_ADDR_6 ); +SYS (fail,"ip -n %s -6 route add %s/128 dev veth7 via %s" , + ns2,IP6_ADDR_VXLAN ,IP6_ADDR_8 ); +return 0 ; + +fail_close: +close_netns (nstoken); +fail: +return -1 ; +} + +/* +* VxLAN encap tests (IPv4-outer and IPv6-outer variants). +*
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c void test_lwt_ip_encap_ipv4(void) · line 617
Quoted 10 lines
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c +10
-0+* Test 1 - functional: the BPF LWT xmit program encapsulates the packet +* (protocol=UDP, port=4789) and re-routes it without dropping it. +* Verified by ping success. +* +* Test 2 - fix verification: after bpf_lwt_push_ip_encap() the +* skb->transport_header must point at the outer UDP header, i.e. +* transport_header - network_header == sizeof(outer IP header). +* Without the fix the transport_header still points at the inner +* transport layer, giving a wrong (larger) offset. +*/
The Test 2 bullet regurgitates the AI's context, can you rephrase it so that it's not framed as a fix but as a test?
Quoted 109 lines
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c +87
-0+static void lwt_ip_encap_vxlan (bool ipv4_encap) +{ +char ns1[NETNS_NAME_SIZE ] =NETNS_BASE "-1-" ; +char ns2[NETNS_NAME_SIZE ] =NETNS_BASE "-2-" ; +char ns3[NETNS_NAME_SIZE ] =NETNS_BASE "-3-" ; +const char *sec = ipv4_encap ?"encap_vxlan" :"encap_vxlan6" ; +int expected_offset = ipv4_encap ? (int )sizeof (struct iphdr ) + : (int )sizeof (struct ipv6hdr ); +struct test_lwt_ip_encap_fix *skel =NULL ; +int thdr_offset, err; + +if (!ASSERT_OK (create_ns (ns1,NETNS_NAME_SIZE ),"create ns1" )) + goto out; +if (!ASSERT_OK (create_ns (ns2,NETNS_NAME_SIZE ),"create ns2" )) + goto out; +if (!ASSERT_OK (create_ns (ns3,NETNS_NAME_SIZE ),"create ns3" )) + goto out; + +if (!ASSERT_OK (setup_network (ns1, ns2, ns3,"" ),"setup network" )) + goto out; + +if (!ASSERT_OK (setup_vxlan_routes (ns3, ns1, ns2),"setup vxlan routes" )) + goto out; + +/* +* Attach fexit to bpf_lwt_push_ip_encap() before installing the +* LWT route so we don't miss the first encap call. +*/ + skel =test_lwt_ip_encap_fix__open (); +if (!ASSERT_OK_PTR (skel,"test_lwt_ip_encap_fix__open" )) + goto out; + + skel->rodata->tgt_ip_version = ipv4_encap ?4 :6 ; + + err =test_lwt_ip_encap_fix__load (skel); +if (!ASSERT_OK (err,"test_lwt_ip_encap_fix__load" )) + goto out; + + err =test_lwt_ip_encap_fix__attach (skel); +if (!ASSERT_OK (err,"test_lwt_ip_encap_fix__attach" )) + goto out; + +/* Remove the direct NS2->DST route so packets must go via LWT encap. */ +SYS (out,"ip -n %s route del %s/32 dev veth3" , ns2,IP4_ADDR_DST ); +SYS (out,"ip -n %s -6 route del %s/128 dev veth3" , ns2,IP6_ADDR_DST ); + +/* Install the VxLAN BPF LWT xmit route. */ +if (ipv4_encap) +SYS (out,"ip -n %s route add %s encap bpf xmit obj %s sec %s dev veth1" , + ns1,IP4_ADDR_DST ,BPF_FILE , sec); +else +SYS (out,"ip -n %s -6 route add %s encap bpf xmit obj %s sec %s dev veth1" , + ns1,IP6_ADDR_DST ,BPF_FILE , sec); + + skel->bss->fexit_triggered = false; +if (ipv4_encap) +SYS (out,"ip netns exec %s ping -c 1 -W1 %s" , ns1,IP4_ADDR_DST ); +else +SYS (out,"ip netns exec %s ping6 -c 1 -W1 %s" , ns1,IP6_ADDR_DST ); + +/* Test 1: fexit triggered means bpf_lwt_push_ip_encap() succeeded. */ +if (!ASSERT_TRUE (skel->bss->fexit_triggered,"fexit_triggered" )) + goto out; + +/* +* Test 2: transport_header must sit immediately after the outer IP +* header, pointing at the UDP header of the VxLAN encap. +*/ + thdr_offset = (int )skel->bss->transport_hdr - (int )skel->bss->network_hdr; +ASSERT_EQ (thdr_offset, expected_offset,"transport_hdr offset" ); + +out: +test_lwt_ip_encap_fix__destroy (skel); +SYS_NOFAIL ("ip netns del %s" , ns1); +SYS_NOFAIL ("ip netns del %s" , ns2); +SYS_NOFAIL ("ip netns del %s" , ns3); +} + +void test_lwt_ip_encap_vxlan_ipv4 (void ) +{ +lwt_ip_encap_vxlan (IPV4_ENCAP ); +} + +void test_lwt_ip_encap_vxlan_ipv6 (void ) +{ +lwt_ip_encap_vxlan (IPV6_ENCAP ); +}tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c +5
-0@@ -2,8 +2,10 @@#include <stddef.h> #include <string.h> #include <linux/bpf.h> +#include <linux/if_ether.h> #include <linux/ip.h> #include <linux/ipv6.h> +#include <linux/udp.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_endian.h> @@ -82,4 +84,114 @@ int bpf_lwt_encap_gre6(struct __sk_buff *skb)return BPF_LWT_REROUTE ; } +struct vxlanhdr { +__be32 vx_flags;/* I flag = 0x08000000 (valid VNI) */ +__be32 vx_vni;/* VNI in top 24 bits */
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c int bpf_lwt_encap_gre6(struct __sk_buff *skb) · line 99
Quoted 10 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c +10
-0+}; + +#define VXLAN_PORT 4789 +#define VXLAN_FLAGS 0x08000000 +#define VXLAN_VNI 1 + +static const __u8 bcast[ETH_ALEN ] = { +0xff ,0xff ,0xff ,0xff ,0xff ,0xff , +}; +
q +static const __u8 srcmac[ETH_ALEN] = {
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c int bpf_lwt_encap_gre6(struct __sk_buff *skb) · line 112
Quoted 12 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c +12
-0+0x02 ,0x00 ,0x00 ,0x00 ,0x00 ,0x01 , +}; + +SEC("encap_vxlan") +int bpf_lwt_encap_vxlan (struct __sk_buff *skb) +{ +struct encap_hdr { +struct iphdr iph; +struct udphdr udph; +struct vxlanhdr vxh; +struct ethhdr eth; + } __attribute__((__packed__ ))/* packed is required to avoid padding */ hdr;
Comment is unnecessary here.
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c int bpf_lwt_encap_gre6(struct __sk_buff *skb) · line 124
Quoted 12 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c +12
-0+int err; + +memset (&hdr,0 ,sizeof (hdr)); + + hdr.iph.ihl =5 ; + hdr.iph.version =4 ; + hdr.iph.ttl =0x40 ; + hdr.iph.protocol =17 ;/* IPPROTO_UDP */ + hdr.iph.tot_len =bpf_htons (skb->len +sizeof (hdr)); +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + hdr.iph.saddr =0x640510ac ;/* 172.16.5.100 */ + hdr.iph.daddr =0x641110ac ;/* 172.16.17.100 */
Ideally want to keep the addresses we are hardcoding here and the the addresses we're declaring in the userspace part in sync. Here we've hardcoded the addresses three multiple ways (be, le, and string in the userspace part).
tools/testing/selftests/bpf/progs/test_lwt_ip_encap_fix.c
Quoted 74 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c +72
-0+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + hdr.iph.saddr =0xac100564 ;/* 172.16.5.100 */ + hdr.iph.daddr =0xac101164 ;/* 172.16.17.100 */ +#else +#error "Fix your compiler's __BYTE_ORDER__?!" +#endif + + hdr.udph.source =bpf_htons (VXLAN_PORT ); + hdr.udph.dest =bpf_htons (VXLAN_PORT ); + hdr.udph.len =bpf_htons (skb->len +sizeof (hdr.udph) +sizeof (hdr.vxh) + +sizeof (hdr.eth)); + + hdr.vxh.vx_flags =bpf_htonl (VXLAN_FLAGS ); + hdr.vxh.vx_vni =bpf_htonl (VXLAN_VNI <<8 ); + +__builtin_memcpy (hdr.eth.h_dest, bcast,ETH_ALEN ); +__builtin_memcpy (hdr.eth.h_source, srcmac,ETH_ALEN ); + hdr.eth.h_proto =bpf_htons (ETH_P_IP ); + + err =bpf_lwt_push_encap (skb,BPF_LWT_ENCAP_IP , &hdr,sizeof (hdr)); +if (err) +return BPF_DROP ; + +return BPF_LWT_REROUTE ; +} + +SEC("encap_vxlan6") +int bpf_lwt_encap_vxlan6 (struct __sk_buff *skb) +{ +struct encap_hdr { +struct ipv6hdr ip6hdr; +struct udphdr udph; +struct vxlanhdr vxh; +struct ethhdr eth; + } __attribute__((__packed__ ))/* packed is required to avoid padding */ hdr; +int err; + +memset (&hdr,0 ,sizeof (hdr)); + + hdr.ip6hdr.version =6 ; + hdr.ip6hdr.nexthdr =17 ;/* IPPROTO_UDP */ + hdr.ip6hdr.hop_limit =0x40 ; + hdr.ip6hdr.payload_len =bpf_htons (skb->len +sizeof (hdr.udph) +sizeof (hdr.vxh) + +sizeof (hdr.eth)); +/* fb05::1 */ + hdr.ip6hdr.saddr.s6_addr[0 ] =0xfb ; + hdr.ip6hdr.saddr.s6_addr[1 ] =0x05 ; + hdr.ip6hdr.saddr.s6_addr[15 ] =1 ; +/* fb20::1 */ + hdr.ip6hdr.daddr.s6_addr[0 ] =0xfb ; + hdr.ip6hdr.daddr.s6_addr[1 ] =0x20 ; + hdr.ip6hdr.daddr.s6_addr[15 ] =1 ; + + hdr.udph.source =bpf_htons (VXLAN_PORT ); + hdr.udph.dest =bpf_htons (VXLAN_PORT ); + hdr.udph.len =bpf_htons (skb->len +sizeof (hdr.udph) +sizeof (hdr.vxh) + +sizeof (hdr.eth)); + + hdr.vxh.vx_flags =bpf_htonl (VXLAN_FLAGS ); + hdr.vxh.vx_vni =bpf_htonl (VXLAN_VNI <<8 ); + +__builtin_memcpy (hdr.eth.h_dest, bcast,ETH_ALEN ); +__builtin_memcpy (hdr.eth.h_source, srcmac,ETH_ALEN ); + hdr.eth.h_proto =bpf_htons (ETH_P_IPV6 ); + + err =bpf_lwt_push_encap (skb,BPF_LWT_ENCAP_IP , &hdr,sizeof (hdr)); +if (err) +return BPF_DROP ; + +return BPF_LWT_REROUTE ; +} + char _license[] SEC("license") = "GPL";tools/testing/selftests/bpf/progs/test_lwt_ip_encap_fix.c +0
-0
We definitely need to change the name here this is straight from the AI.
tools/testing/selftests/bpf/progs/test_lwt_ip_encap_fix.c line 7
Quoted 12 lines
new file mode 100644
index 000000000000..6945f83b94f2tools/testing/selftests/bpf/progs/test_lwt_ip_encap_fix.c new file +7
-0@@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> +#include <bpf/bpf_core_read.h> + +#define NEXTHDR_UDP 17/* UDP message. */
Unnecessary, we hardcode 17 in the other test.
Quoted 37 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap_fix.c new file +37
-0+ +volatile const int tgt_ip_version; + +__u16 transport_hdr =0 ; +__u16 network_hdr =0 ; +bool fexit_triggered = false; + +/* +* bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) +* +* After a successful push the transport_header must point at the outer +* transport header (UDP for VxLAN), i.e. +* transport_header - network_header == sizeof(outer IP header) +*/ +SEC("fexit/bpf_lwt_push_ip_encap") +int BPF_PROG (fexit_lwt_push_ip_encap ,struct sk_buff *skb,void *hdr,u32 len,bool ingress, +int retval) +{ +struct iphdr *iph; + +if (retval || fexit_triggered) +return 0 ; + + iph = (typeof (iph)) (skb->head + skb->network_header); +if (iph->version != tgt_ip_version) +return 0 ; + +if ((iph->version ==4 && iph->protocol ==IPPROTO_UDP ) || + (iph->version ==6 && ((struct ipv6hdr *)iph)->nexthdr ==NEXTHDR_UDP )) { + fexit_triggered = true; + transport_hdr =BPF_CORE_READ (skb, transport_header); + network_hdr =BPF_CORE_READ (skb, network_header); + } +return 0 ; +} + +char _license[] SEC("license") = "GPL";
Re: [PATCH bpf v3 2/2] selftests/bpf: Add tests to verify the fix of encapsulating VxLAN in lwt
Quoted 1 line · Emil Tsalapatis
On Mon Jun 1, 2026 at 11:02 AM EDT, Leon Hwang wrote:
[...]
Quoted 4 lines
Quoted 4 lines
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c +0
-0@@ -35,6 +38,10 @@#define IP6_ADDR_SRC IP6_ADDR_1#define IP6_ADDR_DST IP6_ADDR_4
Quoted 8 lines
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c line 43
Quoted 3 lines
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c +3
-0+/* VxLAN tunnel endpoints, reachable via the bottom route (veth5/6/7/8). */ +#define IP4_ADDR_VXLAN "172.16.17.100" +#define IP6_ADDR_VXLAN "fb20::1"There's a whole series of existing IP definitions at the top of the file, we should put those new ones right below them. And fb20 -> fb11 to keep with the pattern imo.
Ack.
Quoted 16 lines
Quoted 16 lines
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c +9
-0+/* Setup/topology: * * NS1 NS2 NS3 @@ -538,3 +545,160 @@ void test_lwt_ip_encap_ipv4(void)if (test__start_subtest ("ingress" ))lwt_ip_encap (IPV4_ENCAP ,INGRESS ,"" ); } + +/* +* VxLAN Setup/topology: +* +* NS1 (IP*_ADDR_1) NS2 NS3 (IP*_ADDR_4) +* [ping src] +* | top route +* veth1 (LWT encap) <<-- veth2 veth3 <<-- veth4 (ping dst)
Quoted 9 lines
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c void test_lwt_ip_encap_ipv4(void) · line 561
Quoted 6 lines
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c +6
-0+* | ^ +* (bottom route) | (inner pkt) +* v bottom route | +* veth5 -->> veth6 veth7 -->> veth8 (vxlan decap) +* (IP*_ADDR_VXLAN) +* Not sure if this is rendering weird for me but NS2 could be tabbed over once more for clarity.
NS2 here is to make sure the LWT-encap VxLAN packets can be routed correctly like other lwt tests.
Quoted 4 lines
Quoted 4 lines
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c +4
-0+* Add the VxLAN endpoint addresses to NS3's veth8, create standard +* VxLAN decap devices bound to those addresses, and install routes so +* NS1/NS2 can reach the endpoints via the bottom route. +*/
[...]
Quoted 14 lines
Quoted 14 lines
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c +14
-0+fail_close: +close_netns (nstoken); +fail: +return -1 ; +} + +/* +* VxLAN encap tests (IPv4-outer and IPv6-outer variants). +* +* Test 1 - functional: the BPF LWT xmit program encapsulates the packet +* (protocol=UDP, port=4789) and re-routes it without dropping it. +* Verified by ping success. +* +* Test 2 - fix verification: after bpf_lwt_push_ip_encap() the
Quoted 9 lines
Quoted 5 lines
+ * skb->transport_header must point at the outer UDP header, i.e. + * transport_header - network_header == sizeof(outer IP header). + * Without the fix the transport_header still points at the inner + * transport layer, giving a wrong (larger) offset.+ */
The Test 2 bullet regurgitates the AI's context, can you rephrase it so that it's not framed as a fix but as a test?
Hmm, better to drop this comment, I think.
Quoted 6 lines
Quoted 6 lines
tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c +6
-0+static void lwt_ip_encap_vxlan (bool ipv4_encap) +{ +char ns1[NETNS_NAME_SIZE ] =NETNS_BASE "-1-" ; +char ns2[NETNS_NAME_SIZE ] =NETNS_BASE "-2-" ; +char ns3[NETNS_NAME_SIZE ] =NETNS_BASE "-3-" ; +const char *sec = ipv4_encap ?"encap_vxlan" :"encap_vxlan6" ;
[...]
Quoted 4 lines
Quoted 4 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c +4
-0+ +SEC("encap_vxlan") +int bpf_lwt_encap_vxlan (struct __sk_buff *skb) +{
Quoted 9 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c int bpf_lwt_encap_gre6(struct __sk_buff *skb) · line 112
Quoted 6 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c +6
-0+struct encap_hdr { +struct iphdr iph; +struct udphdr udph; +struct vxlanhdr vxh; +struct ethhdr eth; + } __attribute__((__packed__ ))/* packed is required to avoid padding */ hdr;Comment is unnecessary here.
Ack.
Quoted 9 lines
Quoted 9 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c +9
-0+int err; + +memset (&hdr,0 ,sizeof (hdr)); + + hdr.iph.ihl =5 ; + hdr.iph.version =4 ; + hdr.iph.ttl =0x40 ; + hdr.iph.protocol =17 ;/* IPPROTO_UDP */ + hdr.iph.tot_len =bpf_htons (skb->len +sizeof (hdr));
Quoted 9 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c int bpf_lwt_encap_gre6(struct __sk_buff *skb) · line 124
Quoted 3 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c +3
-0+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + hdr.iph.saddr =0x640510ac ;/* 172.16.5.100 */ + hdr.iph.daddr =0x641110ac ;/* 172.16.17.100 */ Ideally want to keep the addresses we are hardcoding here and the the addresses we're declaring in the userspace part in sync. Here we've hardcoded the addresses three multiple ways (be, le, and string in the userspace part).
I think it's OK to hardcode them here like the gre tests.
Quoted 27 lines
Quoted 27 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c +27
-0+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + hdr.iph.saddr =0xac100564 ;/* 172.16.5.100 */ + hdr.iph.daddr =0xac101164 ;/* 172.16.17.100 */ +#else +#error "Fix your compiler's __BYTE_ORDER__?!" +#endif + + hdr.udph.source =bpf_htons (VXLAN_PORT ); + hdr.udph.dest =bpf_htons (VXLAN_PORT ); + hdr.udph.len =bpf_htons (skb->len +sizeof (hdr.udph) +sizeof (hdr.vxh) + +sizeof (hdr.eth)); + + hdr.vxh.vx_flags =bpf_htonl (VXLAN_FLAGS ); + hdr.vxh.vx_vni =bpf_htonl (VXLAN_VNI <<8 ); + +__builtin_memcpy (hdr.eth.h_dest, bcast,ETH_ALEN ); +__builtin_memcpy (hdr.eth.h_source, srcmac,ETH_ALEN ); + hdr.eth.h_proto =bpf_htons (ETH_P_IP ); + + err =bpf_lwt_push_encap (skb,BPF_LWT_ENCAP_IP , &hdr,sizeof (hdr)); +if (err) +return BPF_DROP ; + +return BPF_LWT_REROUTE ; +} + +SEC ("encap_vxlan6" )
Quoted 8 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c int bpf_lwt_encap_gre6(struct __sk_buff *skb) · line 159
Quoted 8 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c +8
-0+int bpf_lwt_encap_vxlan6 (struct __sk_buff *skb) +{ +struct encap_hdr { +struct ipv6hdr ip6hdr; +struct udphdr udph; +struct vxlanhdr vxh; +struct ethhdr eth; + } __attribute__((__packed__ ))/* packed is required to avoid padding */ hdr;
Will drop the comment.
Quoted 2 lines
Quoted 2 lines
(quoted diff) +2
-0+ int err; +
[...]
Quoted 8 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap_fix.c
Quoted 5 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap.c +3
-0+return BPF_LWT_REROUTE ; +} + char _license[] SEC("license") = "GPL";tools/testing/selftests/bpf/progs/test_lwt_ip_encap_fix.c +0
-0We definitely need to change the name here this is straight from the AI.
Will fold it into test_lwt_ip_encap.c to avoid creating a new file.
Quoted 6 lines
Quoted 6 lines
new file mode 100644
index 000000000000..6945f83b94f2tools/testing/selftests/bpf/progs/test_lwt_ip_encap_fix.c new file +1
-0@@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0
Quoted 9 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap_fix.c line 7
Quoted 6 lines
tools/testing/selftests/bpf/progs/test_lwt_ip_encap_fix.c new file +6
-0+#include "vmlinux.h" +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> +#include <bpf/bpf_core_read.h> + +#define NEXTHDR_UDP 17/* UDP message. */ Unnecessary, we hardcode 17 in the other test.
Ack.
Thanks,
Leon
[...]