* [PATCH v3 0/3] bpf: clear stale IPv4 options after LWT encapsulation
@ 2026-09-20 16:32 Weiming Shi
2026-09-20 16:32 ` [PATCH v3 1/3] bpf: propagate cb_access from freplace programs Weiming Shi
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: Weiming Shi @ 2026-09-20 16:32 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan
Cc: linux-kernel, bpf, netdev, linux-kselftest,
Toke Høiland-Jørgensen, Peter Oskolkov, Xiang Mei
This series implements the post-run CB reset suggested by Daniel, reuses the
existing save/restore wrapper, and propagates cb_access from freplace
programs before their activation.
Patch 1 handles freplace cb_access propagation. Patch 2 marks successful LWT
IP header pushes and resets the restored protocol CB after the program runs.
Patch 3 contains the selftests, covering direct and freplace CB access, VRF
ingress, and packets already marked encapsulated before entering LWT.
Changes since v2:
- Replace the LWT state tracking and extra CB copy with a post-run reset after
bpf_prog_run_save_cb() restores the protocol control block.
- Propagate cb_access from freplace programs in a separate prerequisite patch.
- Mark only successful BPF_LWT_ENCAP_IP pushes, covering packets already
marked encapsulated without treating failed SEG6 operations as completed
header replacements.
- Select the reset layout from the protocol callback which next consumes the
packet, preserving ingress interface and L3-slave state across family
changes and initializing the IPv6 network-header offset.
- Save and restore the marker around nested LWT runs.
- Add selftests for direct and freplace CB access, VRF ingress, and packets
already marked encapsulated before entering LWT.
Validation: the full KASAN+BTF kernel build passes. All five selftest cases
pass with none skipped and no KASAN report, Oops, or panic. The new
already-encapsulated case fails on the earlier transition-based implementation
and passes with this series.
Previous version:
https://lore.kernel.org/bpf/20260916170406.1280954-2-bestswngs@gmail.com/
Review discussion:
https://lore.kernel.org/bpf/48990076-414c-4196-99b9-86fce41b8054@iogearbox.net/
https://lore.kernel.org/bpf/97695bef-507a-403a-84ae-c2e222b3dc65@iogearbox.net/
Weiming Shi (3):
bpf: propagate cb_access from freplace programs
bpf: clear stale IPv4 options after LWT encapsulation
selftests/bpf: cover stale CB after LWT IP encapsulation
include/linux/bpf.h | 2 +-
include/linux/filter.h | 8 +-
kernel/bpf/syscall.c | 6 +
net/core/lwt_bpf.c | 47 +++
.../selftests/bpf/prog_tests/lwt_ip_encap.c | 288 ++++++++++++++++++
.../bpf/progs/lwt_ip_encap_stale_cb.c | 100 ++++++
.../progs/lwt_ip_encap_stale_cb_freplace.c | 32 ++
7 files changed, 479 insertions(+), 4 deletions(-)
create mode 100644 tools/testing/selftests/bpf/progs/lwt_ip_encap_stale_cb.c
create mode 100644 tools/testing/selftests/bpf/progs/lwt_ip_encap_stale_cb_freplace.c
base-commit: 6c096bb08de97cdca051fecddad22cac6a1fd275
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v3 1/3] bpf: propagate cb_access from freplace programs
2026-09-20 16:32 [PATCH v3 0/3] bpf: clear stale IPv4 options after LWT encapsulation Weiming Shi
@ 2026-09-20 16:32 ` Weiming Shi
2026-09-20 17:06 ` Alexei Starovoitov
2026-09-20 17:07 ` Alexei Starovoitov
2026-09-20 16:32 ` [PATCH v3 2/3] bpf: clear stale IPv4 options after LWT encapsulation Weiming Shi
2026-09-20 16:32 ` [PATCH v3 3/3] selftests/bpf: cover stale CB after LWT IP encapsulation Weiming Shi
2 siblings, 2 replies; 8+ messages in thread
From: Weiming Shi @ 2026-09-20 16:32 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan
Cc: linux-kernel, bpf, netdev, linux-kselftest,
Toke Høiland-Jørgensen, Peter Oskolkov, Xiang Mei,
stable
bpf_prog_run_save_cb() and bpf_prog_run_clear_cb() use the target
program's cb_access flag to decide whether skb->cb must be saved or
cleared. An extension program can introduce ctx->cb[] access behind a
target which does not access it itself, leaving protocol-owned control
block contents visible to the replacement and preventing the wrapper
from restoring them.
Make cb_access independently addressable and propagate it to the target
before activating a replacement. Wait for wrappers which observed the
old value, and snapshot the flag once per invocation so save and restore
decisions remain paired.
Cc: stable@vger.kernel.org
Fixes: be8704ff07d2 ("bpf: Introduce dynamic program extensions")
Suggested-by: Daniel Borkmann <daniel@iogearbox.net>
Assisted-by: LLM
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
include/linux/bpf.h | 2 +-
include/linux/filter.h | 7 ++++---
kernel/bpf/syscall.c | 6 ++++++
3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index e57af902560c3..606e7cf397af2 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1864,11 +1864,11 @@ struct bpf_prog_aux {
struct bpf_prog {
u16 pages; /* Number of allocated pages */
+ bool cb_access; /* Is control block accessed? */
u32 jited:1, /* Is our filter JIT'ed? */
jit_requested:1,/* archs need to JIT the prog */
jit_required:1, /* program strictly requires JIT compiler */
gpl_compatible:1, /* Is filter GPL compatible? */
- cb_access:1, /* Is control block accessed? */
dst_needed:1, /* Do we need dst entry? */
blinding_requested:1, /* needs constant blinding */
blinded:1, /* Was blinded */
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 39decde7fc730..788c2d625db4a 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1047,16 +1047,17 @@ static inline u32 __bpf_prog_run_save_cb(const struct bpf_prog *prog,
const struct sk_buff *skb = ctx;
u8 *cb_data = bpf_skb_cb(skb);
u8 cb_saved[BPF_SKB_CB_LEN];
+ bool cb_access = READ_ONCE(prog->cb_access);
u32 res;
- if (unlikely(prog->cb_access)) {
+ if (unlikely(cb_access)) {
memcpy(cb_saved, cb_data, sizeof(cb_saved));
memset(cb_data, 0, sizeof(cb_saved));
}
res = bpf_prog_run(prog, skb);
- if (unlikely(prog->cb_access))
+ if (unlikely(cb_access))
memcpy(cb_data, cb_saved, sizeof(cb_saved));
return res;
@@ -1079,7 +1080,7 @@ static inline u32 bpf_prog_run_clear_cb(const struct bpf_prog *prog,
u8 *cb_data = bpf_skb_cb(skb);
u32 res;
- if (unlikely(prog->cb_access))
+ if (unlikely(READ_ONCE(prog->cb_access)))
memset(cb_data, 0, BPF_SKB_CB_LEN);
res = bpf_prog_run_pin_on_cpu(prog, skb);
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index c7bc9ba9b331f..43a29e47c8d01 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -3812,6 +3812,12 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,
if (err)
goto out_unlock;
+ if (prog->type == BPF_PROG_TYPE_EXT && READ_ONCE(prog->cb_access)) {
+ WRITE_ONCE(tgt_prog->cb_access, true);
+ /* Drain runs that observed cb_access=false before enabling freplace. */
+ synchronize_rcu();
+ }
+
err = bpf_trampoline_link_prog(&link->link.node, tr, tgt_prog);
if (err) {
bpf_link_cleanup(&link_primer);
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v3 2/3] bpf: clear stale IPv4 options after LWT encapsulation
2026-09-20 16:32 [PATCH v3 0/3] bpf: clear stale IPv4 options after LWT encapsulation Weiming Shi
2026-09-20 16:32 ` [PATCH v3 1/3] bpf: propagate cb_access from freplace programs Weiming Shi
@ 2026-09-20 16:32 ` Weiming Shi
2026-09-20 17:06 ` Alexei Starovoitov
2026-09-20 16:32 ` [PATCH v3 3/3] selftests/bpf: cover stale CB after LWT IP encapsulation Weiming Shi
2 siblings, 1 reply; 8+ messages in thread
From: Weiming Shi @ 2026-09-20 16:32 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan
Cc: linux-kernel, bpf, netdev, linux-kselftest,
Toke Høiland-Jørgensen, Peter Oskolkov, Xiang Mei,
stable
bpf_lwt_push_ip_encap() rebases the network header after prepending an IP
header, but leaves IPCB(skb)->opt describing the inner IPv4 header. An
ingress LWT route can consequently make an ICMP error use stale option
offsets when constructing its reply.
Mark each completed LWT IP encapsulation in the run's BPF network context
and reset the protocol control block after bpf_prog_run_save_cb() restores
it. This also covers an skb which was already marked encapsulated before
entering LWT. A failed SEG6 encapsulation does not set the marker, so it no
longer causes valid IPv6 metadata to be cleared.
Select the reset layout from the protocol callback which consumes the
packet: the original family for BPF_OK and unsupported redirects, or the
new family for supported reroute and redirect paths. Preserve the ingress
interface and L3-slave state from the restored original control block and
initialize the IPv6 next-header offset when needed. Save and restore the
marker around nested LWT runs.
Cc: stable@vger.kernel.org
Fixes: 52f278774e79 ("bpf: implement BPF_LWT_ENCAP_IP mode in bpf_lwt_push_encap")
Reported-by: Xiang Mei <xmei5@asu.edu>
Link: https://lore.kernel.org/bpf/20260915170147.3943392-2-bestswngs@gmail.com/
Suggested-by: Daniel Borkmann <daniel@iogearbox.net>
Link: https://lore.kernel.org/bpf/48990076-414c-4196-99b9-86fce41b8054@iogearbox.net/
Assisted-by: LLM
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
include/linux/filter.h | 1 +
net/core/lwt_bpf.c | 47 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 48 insertions(+)
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 788c2d625db4a..c8ca526d0661d 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -848,6 +848,7 @@ struct bpf_nh_params {
#define BPF_RI_F_CPU_MAP_INIT BIT(2)
#define BPF_RI_F_DEV_MAP_INIT BIT(3)
#define BPF_RI_F_XSK_MAP_INIT BIT(4)
+#define BPF_RI_F_LWT_IP_ENCAP BIT(5)
struct bpf_redirect_info {
u64 tgt_index;
diff --git a/net/core/lwt_bpf.c b/net/core/lwt_bpf.c
index da49364ec63de..d585484a3a766 100644
--- a/net/core/lwt_bpf.c
+++ b/net/core/lwt_bpf.c
@@ -36,10 +36,44 @@ static inline struct bpf_lwt *bpf_lwt_lwtunnel(struct lwtunnel_state *lwt)
#define NO_REDIRECT false
#define CAN_REDIRECT true
+static void bpf_lwt_reset_cb(struct sk_buff *skb, __be16 orig_proto,
+ bool use_new_proto)
+{
+ __be16 cb_proto = use_new_proto ? skb->protocol : orig_proto;
+ int iif = skb->skb_iif;
+ bool l3slave = false;
+
+ /* VRF may have replaced skb_iif with the master device index. */
+ if (orig_proto == htons(ETH_P_IP)) {
+ iif = IPCB(skb)->iif;
+ l3slave = ipv4_l3mdev_skb(IPCB(skb)->flags);
+ } else if (orig_proto == htons(ETH_P_IPV6)) {
+ iif = IP6CB(skb)->iif;
+ l3slave = ipv6_l3mdev_skb(IP6CB(skb)->flags);
+ }
+
+ if (cb_proto == htons(ETH_P_IP)) {
+ memset(IPCB(skb), 0, sizeof(*IPCB(skb)));
+ IPCB(skb)->iif = iif;
+ if (l3slave)
+ IPCB(skb)->flags |= IPSKB_L3SLAVE;
+ } else if (cb_proto == htons(ETH_P_IPV6)) {
+ memset(IP6CB(skb), 0, sizeof(*IP6CB(skb)));
+ IP6CB(skb)->iif = iif;
+ IP6CB(skb)->nhoff = offsetof(struct ipv6hdr, nexthdr);
+ if (l3slave)
+ IP6CB(skb)->flags |= IP6SKB_L3SLAVE;
+ }
+}
+
static int run_lwt_bpf(struct sk_buff *skb, struct bpf_lwt_prog *lwt,
struct dst_entry *dst, bool can_redirect)
{
struct bpf_net_context __bpf_net_ctx, *bpf_net_ctx;
+ struct bpf_redirect_info *ri;
+ bool lwt_ip_encap, nested_lwt_ip_encap;
+ __be16 orig_proto = skb->protocol;
+ bool use_new_proto;
int ret;
/* Disabling BH is needed to protect per-CPU bpf_redirect_info between
@@ -47,8 +81,20 @@ static int run_lwt_bpf(struct sk_buff *skb, struct bpf_lwt_prog *lwt,
*/
local_bh_disable();
bpf_net_ctx = bpf_net_ctx_set(&__bpf_net_ctx);
+ ri = bpf_net_ctx_get_ri();
+ nested_lwt_ip_encap = ri->kern_flags & BPF_RI_F_LWT_IP_ENCAP;
+ ri->kern_flags &= ~BPF_RI_F_LWT_IP_ENCAP;
bpf_compute_data_pointers(skb);
ret = bpf_prog_run_save_cb(lwt->prog, skb);
+ lwt_ip_encap = ri->kern_flags & BPF_RI_F_LWT_IP_ENCAP;
+ ri->kern_flags &= ~BPF_RI_F_LWT_IP_ENCAP;
+ if (nested_lwt_ip_encap)
+ ri->kern_flags |= BPF_RI_F_LWT_IP_ENCAP;
+ use_new_proto = (ret == BPF_LWT_REROUTE &&
+ lwt->prog->type != BPF_PROG_TYPE_LWT_OUT) ||
+ (ret == BPF_REDIRECT && can_redirect);
+ if (lwt_ip_encap)
+ bpf_lwt_reset_cb(skb, orig_proto, use_new_proto);
switch (ret) {
case BPF_OK:
@@ -668,6 +714,7 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress)
} else {
skb->protocol = htons(ETH_P_IPV6);
}
+ bpf_net_ctx_get_ri()->kern_flags |= BPF_RI_F_LWT_IP_ENCAP;
if (skb_is_gso(skb))
return handle_gso_encap(skb, ipv4, len);
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v3 3/3] selftests/bpf: cover stale CB after LWT IP encapsulation
2026-09-20 16:32 [PATCH v3 0/3] bpf: clear stale IPv4 options after LWT encapsulation Weiming Shi
2026-09-20 16:32 ` [PATCH v3 1/3] bpf: propagate cb_access from freplace programs Weiming Shi
2026-09-20 16:32 ` [PATCH v3 2/3] bpf: clear stale IPv4 options after LWT encapsulation Weiming Shi
@ 2026-09-20 16:32 ` Weiming Shi
2026-09-20 17:06 ` Alexei Starovoitov
2 siblings, 1 reply; 8+ messages in thread
From: Weiming Shi @ 2026-09-20 16:32 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan
Cc: linux-kernel, bpf, netdev, linux-kselftest,
Toke Høiland-Jørgensen, Peter Oskolkov, Xiang Mei
Add regression coverage for stale protocol control-block contents after
bpf_lwt_push_ip_encap(). Inject an IPv4 packet carrying a Record Route
option through an ingress LWT program and verify that the resulting ICMP
Time Exceeded packet has no options copied from the inner header.
Exercise both direct ctx->cb[] access and access introduced by a freplace
program. The latter also verifies that cb_access is propagated to the
target so the replacement observes cleared BPF scratch space.
Run both variants on VRF ingress as well, checking that ICMP source
selection still uses the original ingress slave rather than the VRF
master after the protocol control block is reset.
Add an ingress TC pre-encapsulation case which enters LWT with
skb->encapsulation already set and a compiled outer Record Route option.
This covers the true-to-true transition that an encapsulation-bit edge
check misses.
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
.../selftests/bpf/prog_tests/lwt_ip_encap.c | 288 ++++++++++++++++++
.../bpf/progs/lwt_ip_encap_stale_cb.c | 100 ++++++
.../progs/lwt_ip_encap_stale_cb_freplace.c | 32 ++
3 files changed, 420 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/lwt_ip_encap_stale_cb.c
create mode 100644 tools/testing/selftests/bpf/progs/lwt_ip_encap_stale_cb_freplace.c
diff --git a/tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c b/tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c
index 39e8a3b8b6afb..0cc36b4d75b83 100644
--- a/tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c
+++ b/tools/testing/selftests/bpf/prog_tests/lwt_ip_encap.c
@@ -1,8 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-only
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <linux/icmp.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
#include "network_helpers.h"
#include "test_progs.h"
+#include "lwt_ip_encap_stale_cb.skel.h"
+#include "lwt_ip_encap_stale_cb_freplace.skel.h"
#include "test_lwt_ip_encap.skel.h"
#define BPF_FILE "test_lwt_ip_encap.bpf.o"
@@ -686,3 +695,282 @@ void test_lwt_ip_encap_vxlan_ipv6(void)
{
lwt_ip_encap_vxlan(IPV6_ENCAP);
}
+
+#define STALE_CB_NETNS "lwt-ip-encap-stale-cb"
+#define STALE_CB_DST "10.9.9.0/24"
+#define STALE_CB_PIN_FMT "/sys/fs/bpf/lwt_ip_encap_stale_cb_%d"
+#define STALE_CB_PKT_LEN 64
+
+static __u16 stale_cb_csum(const void *data, size_t len)
+{
+ const __u16 *word = data;
+ __u32 sum = 0;
+
+ while (len > 1) {
+ sum += *word++;
+ len -= sizeof(*word);
+ }
+ if (len)
+ sum += *(const __u8 *)word;
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+
+ return ~sum;
+}
+
+static int stale_cb_get_mac(const char *ifname, __u8 mac[ETH_ALEN])
+{
+ struct ifreq ifr = {};
+ int fd;
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0)
+ return -errno;
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name) - 1);
+ if (ioctl(fd, SIOCGIFHWADDR, &ifr)) {
+ int err = -errno;
+
+ close(fd);
+ return err;
+ }
+ memcpy(mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
+ close(fd);
+ return 0;
+}
+
+static int stale_cb_open_packet_socket(int ifindex)
+{
+ struct sockaddr_ll addr = {
+ .sll_family = AF_PACKET,
+ .sll_protocol = htons(ETH_P_ALL),
+ .sll_ifindex = ifindex,
+ };
+ struct timeval timeout = { .tv_sec = 2 };
+ int fd;
+
+ fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
+ if (fd < 0)
+ return -errno;
+ if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) ||
+ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout))) {
+ int err = -errno;
+
+ close(fd);
+ return err;
+ }
+
+ return fd;
+}
+
+static int stale_cb_send_packet(int fd, const __u8 src_mac[ETH_ALEN],
+ const __u8 dst_mac[ETH_ALEN])
+{
+ __u8 frame[ETH_HLEN + STALE_CB_PKT_LEN] = {};
+ struct ethhdr *eth = (struct ethhdr *)frame;
+ struct iphdr *iph = (struct iphdr *)(frame + ETH_HLEN);
+ __u8 *opt = (__u8 *)(iph + 1);
+
+ memcpy(eth->h_source, src_mac, ETH_ALEN);
+ memcpy(eth->h_dest, dst_mac, ETH_ALEN);
+ eth->h_proto = htons(ETH_P_IP);
+
+ iph->version = 4;
+ iph->ihl = 7;
+ iph->tos = 8;
+ iph->tot_len = htons(STALE_CB_PKT_LEN);
+ iph->id = htons(0x1234);
+ iph->ttl = 64;
+ iph->protocol = IPPROTO_UDP;
+ iph->saddr = inet_addr("10.0.0.2");
+ iph->daddr = inet_addr("10.9.9.9");
+ opt[0] = IPOPT_RR;
+ opt[1] = 8;
+ opt[2] = 4;
+ iph->check = stale_cb_csum(iph, iph->ihl * 4);
+
+ memset(frame + ETH_HLEN + iph->ihl * 4, 0x41,
+ STALE_CB_PKT_LEN - iph->ihl * 4);
+ if (send(fd, frame, sizeof(frame), 0) != sizeof(frame))
+ return -errno;
+
+ return 0;
+}
+
+static int stale_cb_icmp_ihl(int fd, __u32 *saddr)
+{
+ __u8 packet[512];
+ ssize_t len;
+
+ while ((len = recv(fd, packet, sizeof(packet), 0)) >= 0) {
+ const struct ethhdr *eth = (const struct ethhdr *)packet;
+ const struct iphdr *iph;
+ const struct icmphdr *icmph;
+ size_t ip_len;
+
+ if (len < ETH_HLEN + sizeof(*iph) ||
+ eth->h_proto != htons(ETH_P_IP))
+ continue;
+ iph = (const struct iphdr *)(packet + ETH_HLEN);
+ ip_len = iph->ihl * 4;
+ if (iph->ihl < 5 || len < ETH_HLEN + ip_len + sizeof(*icmph) ||
+ iph->protocol != IPPROTO_ICMP)
+ continue;
+ icmph = (const struct icmphdr *)((const __u8 *)iph + ip_len);
+ if (icmph->type == ICMP_TIME_EXCEEDED) {
+ *saddr = iph->saddr;
+ return iph->ihl;
+ }
+ }
+
+ return -errno;
+}
+
+static void lwt_ip_encap_stale_cb(bool use_freplace, bool use_vrf,
+ bool pre_encap)
+{
+ LIBBPF_OPTS(bpf_tc_hook, tc_hook,
+ .attach_point = BPF_TC_INGRESS,
+ );
+ LIBBPF_OPTS(bpf_tc_opts, tc_opts,
+ .handle = 1,
+ .priority = 1,
+ );
+ struct lwt_ip_encap_stale_cb_freplace *freplace_skel = NULL;
+ struct lwt_ip_encap_stale_cb *skel = NULL;
+ struct bpf_program *target, *replacement;
+ struct bpf_link *freplace_link = NULL;
+ struct netns_obj *netns = NULL;
+ char pin_path[128];
+ __u8 mac0[ETH_ALEN], mac1[ETH_ALEN];
+ __u32 saddr = 0;
+ bool tc_hook_created = false;
+ int ifindex, packet_fd = -1, prog_fd, err, ihl;
+
+ skel = lwt_ip_encap_stale_cb__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load target"))
+ goto out;
+ target = use_freplace ? skel->progs.lwt_in_freplace_target :
+ skel->progs.lwt_in_direct;
+ prog_fd = bpf_program__fd(target);
+
+ if (use_freplace) {
+ freplace_skel = lwt_ip_encap_stale_cb_freplace__open();
+ if (!ASSERT_OK_PTR(freplace_skel, "open freplace"))
+ goto out;
+ replacement = freplace_skel->progs.replace_add_ip_encap;
+ err = bpf_program__set_attach_target(replacement, prog_fd,
+ "add_ip_encap");
+ if (!ASSERT_OK(err, "set freplace target"))
+ goto out;
+ err = lwt_ip_encap_stale_cb_freplace__load(freplace_skel);
+ if (!ASSERT_OK(err, "load freplace"))
+ goto out;
+ freplace_link = bpf_program__attach_freplace(replacement, prog_fd,
+ "add_ip_encap");
+ if (!ASSERT_OK_PTR(freplace_link, "attach freplace"))
+ goto out;
+ }
+
+ snprintf(pin_path, sizeof(pin_path), STALE_CB_PIN_FMT, getpid());
+ unlink(pin_path);
+ err = bpf_program__pin(target, pin_path);
+ if (!ASSERT_OK(err, "pin target"))
+ goto out;
+
+ netns = netns_new(STALE_CB_NETNS, true);
+ if (!ASSERT_OK_PTR(netns, "create netns"))
+ goto out_unpin;
+
+ SYS(out_netns, "ip link add vh0 type veth peer name vh1");
+ if (use_vrf) {
+ SYS(out_netns, "ip link add vrf0 type vrf table 1001");
+ SYS(out_netns, "ip link set vrf0 up");
+ SYS(out_netns, "ip link set vh1 master vrf0");
+ SYS(out_netns, "ip addr add 10.1.0.1/32 dev vrf0");
+ SYS(out_netns, "sysctl -wq net.ipv4.conf.vrf0.rp_filter=0");
+ SYS(out_netns, "sysctl -wq net.ipv4.icmp_errors_use_inbound_ifaddr=1");
+ }
+ SYS(out_netns, "ip link set vh0 up");
+ SYS(out_netns, "ip link set vh1 up");
+ SYS(out_netns, "ip addr add 10.0.0.1/24 dev vh1");
+ SYS(out_netns, "sysctl -wq net.ipv4.ip_forward=1");
+ SYS(out_netns, "sysctl -wq net.ipv4.conf.all.rp_filter=0");
+ SYS(out_netns, "sysctl -wq net.ipv4.conf.vh1.rp_filter=0");
+ SYS(out_netns, "sysctl -wq net.ipv4.conf.all.accept_local=1");
+
+ if (pre_encap) {
+ tc_hook.ifindex = if_nametoindex("vh1");
+ if (!ASSERT_GT(tc_hook.ifindex, 0, "vh1 ifindex"))
+ goto out_netns;
+ err = bpf_tc_hook_create(&tc_hook);
+ if (!ASSERT_OK(err, "create vh1 ingress hook"))
+ goto out_netns;
+ tc_hook_created = true;
+ tc_opts.prog_fd = bpf_program__fd(skel->progs.tc_pre_encap);
+ err = bpf_tc_attach(&tc_hook, &tc_opts);
+ if (!ASSERT_OK(err, "attach pre-encapsulation program"))
+ goto out_netns;
+ }
+
+ if (!ASSERT_OK(stale_cb_get_mac("vh0", mac0), "get vh0 mac") ||
+ !ASSERT_OK(stale_cb_get_mac("vh1", mac1), "get vh1 mac"))
+ goto out_netns;
+ SYS(out_netns,
+ "ip neigh replace 10.0.0.2 lladdr %02x:%02x:%02x:%02x:%02x:%02x nud permanent dev vh1",
+ mac0[0], mac0[1], mac0[2], mac0[3], mac0[4], mac0[5]);
+ SYS(out_netns,
+ "ip route add %s encap bpf in pinned %s via 10.0.0.2 dev vh1 %s",
+ STALE_CB_DST, pin_path, use_vrf ? "vrf vrf0" : "");
+
+ ifindex = if_nametoindex("vh0");
+ if (!ASSERT_GT(ifindex, 0, "vh0 ifindex"))
+ goto out_netns;
+ packet_fd = stale_cb_open_packet_socket(ifindex);
+ if (!ASSERT_OK_FD(packet_fd, "open packet socket"))
+ goto out_netns;
+ if (!ASSERT_OK(stale_cb_send_packet(packet_fd, mac0, mac1),
+ "send crafted packet"))
+ goto out_netns;
+
+ ihl = stale_cb_icmp_ihl(packet_fd, &saddr);
+ if (!ASSERT_EQ(ihl, 5, "ICMP IPv4 header length"))
+ goto out_netns;
+ if (use_vrf && !ASSERT_EQ(saddr, inet_addr("10.0.0.1"),
+ "ICMP source is ingress slave address"))
+ goto out_netns;
+ if (use_freplace) {
+ ASSERT_TRUE(freplace_skel->bss->freplace_ran, "freplace ran");
+ ASSERT_TRUE(freplace_skel->bss->freplace_cb_zero,
+ "freplace cb was cleared");
+ } else {
+ ASSERT_TRUE(skel->bss->direct_ran, "direct program ran");
+ ASSERT_TRUE(skel->bss->direct_cb_zero, "direct cb was cleared");
+ }
+
+out_netns:
+ if (packet_fd >= 0)
+ close(packet_fd);
+ if (tc_hook_created)
+ bpf_tc_hook_destroy(&tc_hook);
+ netns_free(netns);
+out_unpin:
+ unlink(pin_path);
+out:
+ bpf_link__destroy(freplace_link);
+ lwt_ip_encap_stale_cb_freplace__destroy(freplace_skel);
+ lwt_ip_encap_stale_cb__destroy(skel);
+}
+
+void test_lwt_ip_encap_stale_cb(void)
+{
+ if (test__start_subtest("direct-cb-access"))
+ lwt_ip_encap_stale_cb(false, false, false);
+ if (test__start_subtest("freplace-cb-access"))
+ lwt_ip_encap_stale_cb(true, false, false);
+ if (test__start_subtest("vrf-direct-cb-access"))
+ lwt_ip_encap_stale_cb(false, true, false);
+ if (test__start_subtest("vrf-freplace-cb-access"))
+ lwt_ip_encap_stale_cb(true, true, false);
+ if (test__start_subtest("already-encapsulated"))
+ lwt_ip_encap_stale_cb(false, false, true);
+}
diff --git a/tools/testing/selftests/bpf/progs/lwt_ip_encap_stale_cb.c b/tools/testing/selftests/bpf/progs/lwt_ip_encap_stale_cb.c
new file mode 100644
index 0000000000000..7638db379118e
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lwt_ip_encap_stale_cb.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_endian.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_tracing_net.h"
+
+#define IPOPT_RR 7
+#define IPOPT_MINOFF 4
+
+bool direct_cb_zero;
+bool direct_ran;
+
+struct tc_outer_ipv4 {
+ struct iphdr iph;
+ __u8 options[8];
+};
+
+static __always_inline __u16 fold_csum(__u64 csum)
+{
+ csum = (csum & 0xffffffff) + (csum >> 32);
+ csum = (csum & 0xffff) + (csum >> 16);
+ csum = (csum & 0xffff) + (csum >> 16);
+
+ return ~csum;
+}
+
+SEC("tc")
+int tc_pre_encap(struct __sk_buff *skb)
+{
+ struct tc_outer_ipv4 outer = {};
+ struct iphdr inner;
+ __s64 csum;
+
+ if (skb->protocol != bpf_htons(ETH_P_IP))
+ return TC_ACT_OK;
+ if (bpf_skb_load_bytes(skb, ETH_HLEN, &inner, sizeof(inner)))
+ return TC_ACT_SHOT;
+
+ outer.iph.version = 4;
+ outer.iph.ihl = sizeof(outer) / 4;
+ outer.iph.tos = 8;
+ outer.iph.tot_len = bpf_htons(bpf_ntohs(inner.tot_len) + sizeof(outer));
+ outer.iph.id = bpf_htons(0x2345);
+ outer.iph.ttl = 64;
+ outer.iph.protocol = IPPROTO_IPIP;
+ outer.iph.saddr = bpf_htonl(0x0a000002); /* 10.0.0.2 */
+ outer.iph.daddr = bpf_htonl(0x0a090909); /* 10.9.9.9 */
+ outer.options[0] = IPOPT_RR;
+ outer.options[1] = sizeof(outer.options);
+ outer.options[2] = IPOPT_MINOFF;
+ csum = bpf_csum_diff(NULL, 0, (__be32 *)&outer, sizeof(outer), 0);
+ if (csum < 0)
+ return TC_ACT_SHOT;
+ outer.iph.check = fold_csum(csum);
+
+ if (bpf_skb_adjust_room(skb, sizeof(outer), BPF_ADJ_ROOM_MAC,
+ BPF_F_ADJ_ROOM_FIXED_GSO |
+ BPF_F_ADJ_ROOM_ENCAP_L3_IPV4))
+ return TC_ACT_SHOT;
+ if (bpf_skb_store_bytes(skb, ETH_HLEN, &outer, sizeof(outer),
+ BPF_F_INVALIDATE_HASH))
+ return TC_ACT_SHOT;
+
+ return TC_ACT_OK;
+}
+
+__noinline int add_ip_encap(struct __sk_buff *skb)
+{
+ struct iphdr iph = {};
+
+ iph.version = 4;
+ iph.ihl = 5;
+ iph.ttl = 1;
+ iph.protocol = 4; /* IPPROTO_IPIP */
+ iph.tot_len = bpf_htons(skb->len + sizeof(iph));
+ iph.saddr = bpf_htonl(0x0a000002); /* 10.0.0.2 */
+ iph.daddr = bpf_htonl(0x0a090909); /* 10.9.9.9 */
+
+ if (bpf_lwt_push_encap(skb, BPF_LWT_ENCAP_IP, &iph, sizeof(iph)))
+ return BPF_DROP;
+
+ return BPF_OK;
+}
+
+SEC("lwt_in")
+int lwt_in_direct(struct __sk_buff *skb)
+{
+ direct_cb_zero = !(skb->cb[0] | skb->cb[1] | skb->cb[2] |
+ skb->cb[3] | skb->cb[4]);
+ direct_ran = true;
+ return add_ip_encap(skb);
+}
+
+SEC("lwt_in")
+int lwt_in_freplace_target(struct __sk_buff *skb)
+{
+ return add_ip_encap(skb);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/lwt_ip_encap_stale_cb_freplace.c b/tools/testing/selftests/bpf/progs/lwt_ip_encap_stale_cb_freplace.c
new file mode 100644
index 0000000000000..6358f76e47c8b
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lwt_ip_encap_stale_cb_freplace.c
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_endian.h>
+#include <bpf/bpf_helpers.h>
+
+bool freplace_cb_zero;
+bool freplace_ran;
+
+SEC("freplace/add_ip_encap")
+int replace_add_ip_encap(struct __sk_buff *skb)
+{
+ struct iphdr iph = {};
+
+ freplace_cb_zero = !(skb->cb[0] | skb->cb[1] | skb->cb[2] |
+ skb->cb[3] | skb->cb[4]);
+ freplace_ran = true;
+
+ iph.version = 4;
+ iph.ihl = 5;
+ iph.ttl = 1;
+ iph.protocol = 4; /* IPPROTO_IPIP */
+ iph.tot_len = bpf_htons(skb->len + sizeof(iph));
+ iph.saddr = bpf_htonl(0x0a000002); /* 10.0.0.2 */
+ iph.daddr = bpf_htonl(0x0a090909); /* 10.9.9.9 */
+
+ if (bpf_lwt_push_encap(skb, BPF_LWT_ENCAP_IP, &iph, sizeof(iph)))
+ return BPF_DROP;
+
+ return BPF_OK;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v3 1/3] bpf: propagate cb_access from freplace programs
2026-09-20 16:32 ` [PATCH v3 1/3] bpf: propagate cb_access from freplace programs Weiming Shi
@ 2026-09-20 17:06 ` Alexei Starovoitov
2026-09-20 17:07 ` Alexei Starovoitov
1 sibling, 0 replies; 8+ messages in thread
From: Alexei Starovoitov @ 2026-09-20 17:06 UTC (permalink / raw)
To: Weiming Shi, Daniel Borkmann, John Fastabend, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Shuah Khan
Cc: linux-kernel, bpf, netdev, linux-kselftest,
Toke Høiland-Jørgensen, Peter Oskolkov, Xiang Mei,
stable
On Mon, Sep 21, 2026 at 12:32 AM Weiming Shi <bestswngs@gmail.com> wrote:
> Cc: stable@vger.kernel.org
> Fixes: be8704ff07d2 ("bpf: Introduce dynamic program extensions")
That shouldn't go to stable. Loading an EXT prog takes CAP_BPF +
CAP_NET_ADMIN + CAP_PERFMON.
[...]
> + if (prog->type == BPF_PROG_TYPE_EXT && READ_ONCE(prog->cb_access)) {
> + WRITE_ONCE(tgt_prog->cb_access, true);
> + /* Drain runs that observed cb_access=false before enabling freplace. */
> + synchronize_rcu();
> + }
tc doesn't look at cb_access at all, yet every tc freplace that touches
cb[] now waits for an RCU grace period in attach.
This is a separate issue from the LWT one. Patch 2 resets the cb after
the run whether the prog has cb_access or not, so it doesn't depend on
this patch. Pls drop it from this series.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v3 2/3] bpf: clear stale IPv4 options after LWT encapsulation
2026-09-20 16:32 ` [PATCH v3 2/3] bpf: clear stale IPv4 options after LWT encapsulation Weiming Shi
@ 2026-09-20 17:06 ` Alexei Starovoitov
0 siblings, 0 replies; 8+ messages in thread
From: Alexei Starovoitov @ 2026-09-20 17:06 UTC (permalink / raw)
To: Weiming Shi, Daniel Borkmann, John Fastabend, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Shuah Khan
Cc: linux-kernel, bpf, netdev, linux-kselftest,
Toke Høiland-Jørgensen, Peter Oskolkov, Xiang Mei,
stable
On Mon, Sep 21, 2026 at 12:32 AM Weiming Shi <bestswngs@gmail.com> wrote:
> Select the reset layout from the protocol callback which consumes the
> packet: the original family for BPF_OK and unsupported redirects, or the
> new family for supported reroute and redirect paths. Preserve the ingress
> interface and L3-slave state from the restored original control block and
> initialize the IPv6 next-header offset when needed. Save and restore the
> marker around nested LWT runs.
What Daniel sketched in v2 was 20 lines. This is still an overkill.
[...]
> +static void bpf_lwt_reset_cb(struct sk_buff *skb, __be16 orig_proto,
> + bool use_new_proto)
> +{
> + __be16 cb_proto = use_new_proto ? skb->protocol : orig_proto;
> + int iif = skb->skb_iif;
> + bool l3slave = false;
> +
> + /* VRF may have replaced skb_iif with the master device index. */
> + if (orig_proto == htons(ETH_P_IP)) {
> + iif = IPCB(skb)->iif;
> + l3slave = ipv4_l3mdev_skb(IPCB(skb)->flags);
When the family doesn't change only IPCB(skb)->opt is stale.
Clear just that like ip_tunnel_xmit() and udp_tunnel_xmit_skb() do.
iif and flags stay as they are and the VRF special casing goes away.
When the family changes do what seg6_do_srh_encap() does.
[...]
> + use_new_proto = (ret == BPF_LWT_REROUTE &&
> + lwt->prog->type != BPF_PROG_TYPE_LWT_OUT) ||
> + (ret == BPF_REDIRECT && can_redirect);
> + if (lwt_ip_encap)
> + bpf_lwt_reset_cb(skb, orig_proto, use_new_proto);
Not needed. BPF_OK after the prog changed the family is broken no
matter which layout the cb has. bpf_xmit() drops such skb and
bpf_input() hands a v6 packet to ip_forward().
Use skb->protocol.
pw-bot: cr
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v3 3/3] selftests/bpf: cover stale CB after LWT IP encapsulation
2026-09-20 16:32 ` [PATCH v3 3/3] selftests/bpf: cover stale CB after LWT IP encapsulation Weiming Shi
@ 2026-09-20 17:06 ` Alexei Starovoitov
0 siblings, 0 replies; 8+ messages in thread
From: Alexei Starovoitov @ 2026-09-20 17:06 UTC (permalink / raw)
To: Weiming Shi, Daniel Borkmann, John Fastabend, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Shuah Khan
Cc: linux-kernel, bpf, netdev, linux-kselftest,
Toke Høiland-Jørgensen, Peter Oskolkov, Xiang Mei
On Mon, Sep 21, 2026 at 12:32 AM Weiming Shi <bestswngs@gmail.com> wrote:
> .../selftests/bpf/prog_tests/lwt_ip_encap.c | 288 ++++++++++++++++++
> .../bpf/progs/lwt_ip_encap_stale_cb.c | 100 ++++++
> .../progs/lwt_ip_encap_stale_cb_freplace.c | 32 ++
> 3 files changed, 420 insertions(+)
This doesn't apply to bpf-next.
And 420 lines of selftest for a 40 line fix ?
That's an overkill.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v3 1/3] bpf: propagate cb_access from freplace programs
2026-09-20 16:32 ` [PATCH v3 1/3] bpf: propagate cb_access from freplace programs Weiming Shi
2026-09-20 17:06 ` Alexei Starovoitov
@ 2026-09-20 17:07 ` Alexei Starovoitov
1 sibling, 0 replies; 8+ messages in thread
From: Alexei Starovoitov @ 2026-09-20 17:07 UTC (permalink / raw)
To: Weiming Shi, Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan
Cc: linux-kernel, bpf, netdev, linux-kselftest,
Toke Høiland-Jørgensen, Peter Oskolkov, Xiang Mei,
stable
On Sun Sep 20, 2026 at 4:32 PM UTC, Weiming Shi wrote:
>
> struct bpf_prog {
> u16 pages; /* Number of allocated pages */
> + bool cb_access; /* Is control block accessed? */
> u32 jited:1, /* Is our filter JIT'ed? */
> jit_requested:1,/* archs need to JIT the prog */
> jit_required:1, /* program strictly requires JIT compiler */
> gpl_compatible:1, /* Is filter GPL compatible? */
> - cb_access:1, /* Is control block accessed? */
No. keep the signle bit.
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-09-20 17:07 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-20 16:32 [PATCH v3 0/3] bpf: clear stale IPv4 options after LWT encapsulation Weiming Shi
2026-09-20 16:32 ` [PATCH v3 1/3] bpf: propagate cb_access from freplace programs Weiming Shi
2026-09-20 17:06 ` Alexei Starovoitov
2026-09-20 17:07 ` Alexei Starovoitov
2026-09-20 16:32 ` [PATCH v3 2/3] bpf: clear stale IPv4 options after LWT encapsulation Weiming Shi
2026-09-20 17:06 ` Alexei Starovoitov
2026-09-20 16:32 ` [PATCH v3 3/3] selftests/bpf: cover stale CB after LWT IP encapsulation Weiming Shi
2026-09-20 17:06 ` Alexei Starovoitov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®