* [PATCH net-next 00/14] skb extension for BPF metadata
@ 2026-08-14 8:14 Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 01/14] bpf: Introduce per-packet metadata storage for BPF programs Jakub Sitnicki
` (13 more replies)
0 siblings, 14 replies; 16+ messages in thread
From: Jakub Sitnicki @ 2026-08-14 8:14 UTC (permalink / raw)
To: netdev, Alexei Starovoitov, Jakub Kicinski, Kuniyuki Iwashima,
Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
[I realize net-next closes soon. Posting only to collect more feedback.]
Please see the RFC cover letter for the overview [1].
I will focus here just on the latest developments.
1) Based on feedback from the RFC - the BPF skb extension is *no longer
scrubbed* on tunnel encap/decap or veth traversal. There is also *no
mechanism* to enable scrubbing as we don't seem to have a use case for it.
2) Since the RFC I've enabled access to BPF skb extension for the remaining
BPF program types which operate on skbs, namely netfilter, lwt family, and
- everyone's favorite - sk_skb.
Outside the patch set, two things happened:
3) At NetConf 2026 Kuniyuki presented effectively the same idea with a use
case in mind to attach metadata to packets on egress (IIRC). Hoping for
feedback if this meets your needs as well.
4) At BPF Summit 2026, Alexei asked - I'm paraphrasing: What percentage of
skbs will carry metadata in our workload? This determines if the cost of
attaching a tracing prog to consume_skb gets amortized. We've run
experiments and have some answers.
If bpf skb ext existed today, we would adopt it to attach metadata to
incoming TCP connections and ingress UDP packets to CDN to identify flows
that have been forwarded from one PoP to another.
Based on stats from a production node where we've been testing this patch
set, the fraction of skbs that would carry the metadata is <1% (~0.7%):
```
923m16$ sudo perf stat -a -r 10 \
-e skb:consume_skb -e skb:kfree_skb \
-e probe:skb_ext_add -- sleep 1
Performance counter stats for 'system wide' (10 runs):
334,256 skb:consume_skb ( +- 1.63% )
1,525 skb:kfree_skb ( +- 4.09% )
2,218 probe:skb_ext_add ( +- 18.58% )
1.02305 +- 0.00243 seconds time elapsed ( +- 0.24% )
```
I expect that even if we adopted bpf skb ext for other use cases we have in
mind, like labeling egress CDN packets with a customer identifier, we would
attach metadata only to around 5% of all skbs in flight.
We've run an experiment to evaluate this patch set - bpf skb extension -
against the alternative - using a BPF map keyed by &skb (LRU_HASH) to stash
metadata plus consume/kfree_skb tracepoint programs to clean up the
entries.
To do that we've modified the CDN component that labels the incoming
connections forwarded from another PoP:
- with bpf skb ext, BPF progs involved in processing consumed 7.5..10 CPU%,
- with BPF map + consume/kfree_skb tp, the overhead was between 15..20 CPU%
Not surprising considering the low fraction of skbs we attach metadata to.
(For graphs see slides 30 & 40 from our presentation at Netdev 0x1A [2].)
Note that we have not tried using RHASH as a BPF metadata stash, to see if
that could bridge the overhead gap, as that would involve deploying a
non-LTS kernel ATM.
Thanks,
-jkbs
[1] https://patch.msgid.link/20260714-bpf-meta-inside-skb-ext-v1-0-5871c07a8dd6@cloudflare.com
[2] https://github.com/jsitnicki/talks/blob/5d64c151f4dc0c38b2832fdcbce7284ec93fdca2/Netdev%200x1A%20-%20Thrice%20the%20charm%20-%20an%20skb%20extension%20for%20BPF%20metadata.pdf
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
---
Changes in v1:
- Don't scrub BPF skb extension. Remove F_NO_SCRUB flag. (Stan)
- Allow calling bpf_dynptr_from_skb_ext from NETFILTER, LWT_*, SK_SKB progs.
- Reorg tests into smaller commits. Add missing coverage.
- Link to RFC: https://patch.msgid.link/20260714-bpf-meta-inside-skb-ext-v1-0-5871c07a8dd6@cloudflare.com
---
Jakub Sitnicki (14):
bpf: Introduce per-packet metadata storage for BPF programs
bpf: Allow access to bpf_sock_ops_kern->skb
bpf: Make BPF skb extension survive packet scrubbing
selftests/bpf: Add tests for bpf_dynptr_from_skb_ext
selftests/bpf: Test skb_ext on cloned skbs
selftests/bpf: Test skb_ext survival across veth and GRE
selftests/bpf: Test skb_ext read from cgroup_skb and sk_filter hooks
selftests/bpf: Test skb_ext read from sock_ops and LSM hooks
selftests/bpf: Test skb_ext read from kfree_skb tracepoint
selftests/bpf: Test skb_ext read from netfilter hook
selftests/bpf: Test skb_ext from LWT in, out, and xmit hooks
selftests/bpf: Test skb_ext read from seg6local End.BPF hook
selftests/bpf: Test skb_ext read from sk_skb stream verdict hook
selftests/bpf: Use non-trivial test payload in xdp_context tests
include/linux/bpf.h | 10 +
include/linux/filter.h | 26 +
include/linux/skbuff.h | 5 +
include/uapi/linux/bpf.h | 5 +
kernel/bpf/helpers.c | 7 +
kernel/bpf/log.c | 2 +
kernel/bpf/verifier.c | 15 +-
net/Kconfig | 20 +
net/core/filter.c | 135 +++
net/core/skbuff.c | 81 +-
net/ipv4/udp.c | 9 +-
tools/testing/selftests/bpf/config | 1 +
.../selftests/bpf/prog_tests/socket_helpers.h | 1 +
.../bpf/prog_tests/xdp_context_test_run.c | 988 ++++++++++++++++++++-
tools/testing/selftests/bpf/progs/test_xdp_meta.c | 489 +++++++++-
15 files changed, 1772 insertions(+), 22 deletions(-)
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH net-next 01/14] bpf: Introduce per-packet metadata storage for BPF programs
2026-08-14 8:14 [PATCH net-next 00/14] skb extension for BPF metadata Jakub Sitnicki
@ 2026-08-14 8:14 ` Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 02/14] bpf: Allow access to bpf_sock_ops_kern->skb Jakub Sitnicki
` (12 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jakub Sitnicki @ 2026-08-14 8:14 UTC (permalink / raw)
To: netdev, Alexei Starovoitov, Jakub Kicinski, Kuniyuki Iwashima,
Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
BPF programs attached at different points in the network stack have no way
to pass data between each other on a per-packet basis, other than by
stashing it into a shared BPF map. xdp/skb->data_meta works for XDP-to-TC
handoff, but is not available to programs running at later hooks like
cgroup/skb, sock_ops, socket filters, tracing or LSM.
Add a new skb extension (struct bpf_skb_ext) that provides up to 256 bytes
of per-packet storage. Size is configurable at build time though the
CONFIG_BPF_SKB_EXT_SIZE option. The storage is embedded inside the
extension chunk itself.
Expose the storage to BPF programs via bpf_dynptr_from_skb_ext() kfunc.
The caller passes BPF_SKB_EXT_F_CREATE to allocate or COW (unshare) the
extension and get a read-write dynptr. Without the flag, it gets a
read-only dynptr to the existing extension, or -ENOENT if none exists.
Guard the feature behind a new CONFIG_BPF_SKB_EXT option.
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
---
include/linux/bpf.h | 10 ++++
include/linux/filter.h | 26 +++++++++
include/linux/skbuff.h | 3 ++
include/uapi/linux/bpf.h | 5 ++
kernel/bpf/helpers.c | 7 +++
kernel/bpf/log.c | 2 +
kernel/bpf/verifier.c | 10 +++-
net/Kconfig | 20 +++++++
net/core/filter.c | 135 +++++++++++++++++++++++++++++++++++++++++++++++
net/core/skbuff.c | 3 ++
10 files changed, 220 insertions(+), 1 deletion(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 7719f6528445..6b918a5b61bf 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1484,6 +1484,8 @@ enum bpf_dynptr_type {
BPF_DYNPTR_TYPE_SKB_META,
/* Underlying data is a file */
BPF_DYNPTR_TYPE_FILE,
+ /* Underlying data is a bpf_skb_ext chunk */
+ BPF_DYNPTR_TYPE_SKB_EXT,
};
int bpf_dynptr_check_size(u64 size);
@@ -4209,4 +4211,12 @@ static inline int bpf_map_check_op_flags(struct bpf_map *map, u64 flags, u64 all
return 0;
}
+#ifdef CONFIG_BPF_SKB_EXT
+
+struct bpf_skb_ext {
+ u8 buf[CONFIG_BPF_SKB_EXT_SIZE] __aligned(8);
+};
+
+#endif /* CONFIG_BPF_SKB_EXT */
+
#endif /* _LINUX_BPF_H */
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 14acb2455746..6a2955487504 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1914,4 +1914,30 @@ static inline void *bpf_skb_meta_pointer(struct sk_buff *skb, u32 offset)
}
#endif /* CONFIG_NET */
+#ifdef CONFIG_BPF_SKB_EXT
+void *bpf_skb_ext_pointer(struct sk_buff *skb, u32 offset);
+int __bpf_skb_ext_load_bytes(const struct sk_buff *skb, u32 offset, void *to,
+ u32 len);
+int __bpf_skb_ext_store_bytes(struct sk_buff *skb, u32 offset, const void *from,
+ u32 len, u64 flags);
+#else /* CONFIG_BPF_SKB_EXT */
+static inline void *bpf_skb_ext_pointer(struct sk_buff *skb, u32 offset)
+{
+ return NULL;
+}
+
+static inline int __bpf_skb_ext_load_bytes(const struct sk_buff *skb,
+ u32 offset, void *to, u32 len)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int __bpf_skb_ext_store_bytes(struct sk_buff *skb, u32 offset,
+ const void *from, u32 len,
+ u64 flags)
+{
+ return -EOPNOTSUPP;
+}
+#endif /* CONFIG_BPF_SKB_EXT */
+
#endif /* __LINUX_FILTER_H__ */
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 22eda1d54a0e..a4f88107c2d5 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -5031,6 +5031,9 @@ enum skb_ext_id {
#endif
#if IS_ENABLED(CONFIG_CAN)
SKB_EXT_CAN,
+#endif
+#if IS_ENABLED(CONFIG_BPF_SKB_EXT)
+ SKB_EXT_BPF,
#endif
SKB_EXT_NUM, /* must be last */
};
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index 89b36de5fdbb..3eee4467422d 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -7732,4 +7732,9 @@ struct bpf_insn_array_value {
__u32 :32;
};
+/* Flags to control bpf_dynptr_from_skb_ext() behavior. */
+enum {
+ BPF_SKB_EXT_F_CREATE = (1ULL << 0),
+};
+
#endif /* _UAPI__LINUX_BPF_H__ */
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index c18f1e16edee..aa28cc6a75f7 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -1926,6 +1926,8 @@ static int __bpf_dynptr_read(void *dst, u64 len, const struct bpf_dynptr_kern *s
return 0;
case BPF_DYNPTR_TYPE_FILE:
return bpf_file_fetch_bytes(src->data, offset, dst, len);
+ case BPF_DYNPTR_TYPE_SKB_EXT:
+ return __bpf_skb_ext_load_bytes(src->data, src->offset + offset, dst, len);
default:
WARN_ONCE(true, "bpf_dynptr_read: unknown dynptr type %d\n", type);
return -EFAULT;
@@ -1985,6 +1987,8 @@ int __bpf_dynptr_write(const struct bpf_dynptr_kern *dst, u64 offset, void *src,
case BPF_DYNPTR_TYPE_SKB_META:
return __bpf_skb_meta_store_bytes(dst->data, dst->offset + offset, src,
len, flags);
+ case BPF_DYNPTR_TYPE_SKB_EXT:
+ return __bpf_skb_ext_store_bytes(dst->data, dst->offset + offset, src, len, flags);
default:
WARN_ONCE(true, "bpf_dynptr_write: unknown dynptr type %d\n", type);
return -EFAULT;
@@ -2032,6 +2036,7 @@ BPF_CALL_3(bpf_dynptr_data, const struct bpf_dynptr_kern *, ptr, u64, offset, u6
case BPF_DYNPTR_TYPE_SKB:
case BPF_DYNPTR_TYPE_XDP:
case BPF_DYNPTR_TYPE_SKB_META:
+ case BPF_DYNPTR_TYPE_SKB_EXT:
/* skb and xdp dynptrs should use bpf_dynptr_slice / bpf_dynptr_slice_rdwr */
return 0;
default:
@@ -3087,6 +3092,8 @@ __bpf_kfunc void *bpf_dynptr_slice(const struct bpf_dynptr *p, u64 offset,
}
case BPF_DYNPTR_TYPE_SKB_META:
return bpf_skb_meta_pointer(ptr->data, ptr->offset + offset);
+ case BPF_DYNPTR_TYPE_SKB_EXT:
+ return bpf_skb_ext_pointer(ptr->data, ptr->offset + offset);
case BPF_DYNPTR_TYPE_FILE:
err = bpf_file_fetch_bytes(ptr->data, offset, buffer__nullable, buffer__szk);
return err ? NULL : buffer__nullable;
diff --git a/kernel/bpf/log.c b/kernel/bpf/log.c
index b740fa73ee26..d66e154649e0 100644
--- a/kernel/bpf/log.c
+++ b/kernel/bpf/log.c
@@ -462,6 +462,8 @@ const char *dynptr_type_str(enum bpf_dynptr_type type)
return "skb_meta";
case BPF_DYNPTR_TYPE_FILE:
return "file";
+ case BPF_DYNPTR_TYPE_SKB_EXT:
+ return "skb_ext";
case BPF_DYNPTR_TYPE_INVALID:
return "<invalid>";
default:
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 7aa47342dc65..1fab69fec478 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -635,6 +635,7 @@ static enum bpf_type_flag get_dynptr_type_flag(enum bpf_dynptr_type type)
case BPF_DYNPTR_TYPE_XDP:
return DYNPTR_TYPE_XDP;
case BPF_DYNPTR_TYPE_SKB_META:
+ case BPF_DYNPTR_TYPE_SKB_EXT:
return DYNPTR_TYPE_SKB_META;
case BPF_DYNPTR_TYPE_FILE:
return DYNPTR_TYPE_FILE;
@@ -11036,6 +11037,7 @@ enum special_kfunc_type {
KF_bpf_dynptr_from_xdp,
KF_bpf_dynptr_from_skb_meta,
KF_bpf_xdp_pull_data,
+ KF_bpf_dynptr_from_skb_ext,
KF_bpf_dynptr_slice,
KF_bpf_dynptr_slice_rdwr,
KF_bpf_dynptr_clone,
@@ -11116,6 +11118,11 @@ BTF_ID_UNUSED
BTF_ID_UNUSED
BTF_ID_UNUSED
#endif
+#ifdef CONFIG_BPF_SKB_EXT
+BTF_ID(func, bpf_dynptr_from_skb_ext)
+#else
+BTF_ID_UNUSED
+#endif
BTF_ID(func, bpf_dynptr_slice)
BTF_ID(func, bpf_dynptr_slice_rdwr)
BTF_ID(func, bpf_dynptr_clone)
@@ -12219,7 +12226,8 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
dynptr_arg_type |= DYNPTR_TYPE_SKB;
} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_xdp]) {
dynptr_arg_type |= DYNPTR_TYPE_XDP;
- } else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_skb_meta]) {
+ } else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_skb_meta] ||
+ meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_skb_ext]) {
dynptr_arg_type |= DYNPTR_TYPE_SKB_META;
} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_file]) {
dynptr_arg_type |= DYNPTR_TYPE_FILE;
diff --git a/net/Kconfig b/net/Kconfig
index e38477393551..6d57320dfea3 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -540,4 +540,24 @@ config NET_TEST
If unsure, say N.
+config BPF_SKB_EXT
+ bool "skb extension for BPF metadata"
+ depends on BPF_SYSCALL
+ select SKB_EXTENSIONS
+ help
+ Enable an sk_buff extension for storing BPF metadata. This allows BPF
+ programs to associate arbitrary data with individual packets as they
+ traverse the network stack. The storage is automatically freed when
+ the sk_buff is freed.
+
+config BPF_SKB_EXT_SIZE
+ int "Size of the BPF skb extension metadata buffer"
+ depends on BPF_SKB_EXT
+ range 1 256
+ default 64
+ help
+ Configures the size of the inline metadata buffer in struct
+ bpf_skb_ext, which is the maximum amount of data a BPF program can
+ store or retrieve with bpf_dynptr_from_skb_ext().
+
endif # if NET
diff --git a/net/core/filter.c b/net/core/filter.c
index 11bb0d236822..a9a164738389 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -12259,6 +12259,48 @@ int __bpf_skb_meta_store_bytes(struct sk_buff *skb, u32 offset,
return 0;
}
+#ifdef CONFIG_BPF_SKB_EXT
+void *bpf_skb_ext_pointer(struct sk_buff *skb, u32 offset)
+{
+ struct bpf_skb_ext *ext;
+
+ ext = skb_ext_find(skb, SKB_EXT_BPF);
+ if (!ext)
+ return NULL;
+
+ return ext->buf + offset;
+}
+
+int __bpf_skb_ext_load_bytes(const struct sk_buff *skb, u32 offset, void *to,
+ u32 len)
+{
+ struct bpf_skb_ext *ext;
+
+ ext = skb_ext_find(skb, SKB_EXT_BPF);
+ if (!ext)
+ return -ENOENT;
+
+ memmove(to, ext->buf + offset, len);
+ return 0;
+}
+
+int __bpf_skb_ext_store_bytes(struct sk_buff *skb, u32 offset,
+ const void *from, u32 len, u64 flags)
+{
+ struct bpf_skb_ext *ext;
+
+ if (unlikely(flags))
+ return -EINVAL;
+
+ ext = skb_ext_find(skb, SKB_EXT_BPF);
+ if (!ext)
+ return -ENOENT;
+
+ memmove(ext->buf + offset, from, len);
+ return 0;
+}
+#endif /* CONFIG_BPF_SKB_EXT */
+
__bpf_kfunc_start_defs();
__bpf_kfunc int bpf_dynptr_from_skb(struct __sk_buff *s, u64 flags,
struct bpf_dynptr *ptr__uninit)
@@ -12276,6 +12318,71 @@ __bpf_kfunc int bpf_dynptr_from_skb(struct __sk_buff *s, u64 flags,
return 0;
}
+#ifdef CONFIG_BPF_SKB_EXT
+/**
+ * bpf_dynptr_from_skb_ext() - Initialize a dynptr to the skb_ext BPF area.
+ * @skb_: socket buffer to attach the extension to
+ * @size: dynptr size in bytes, 0 for maximum (CONFIG_BPF_SKB_EXT_SIZE)
+ * @flags: BPF_SKB_EXT_F_CREATE to create/COW (read-write), 0 to find (read-only)
+ * @ptr__uninit: dynptr to initialize
+ *
+ * Return:
+ * * %0 - dynptr ready to use
+ * * %-ENOENT - extension not found (when not creating)
+ * * %-ENOMEM - allocation failed
+ * * %-EINVAL - invalid flags
+ * * %-E2BIG - size exceeds CONFIG_BPF_SKB_EXT_SIZE
+ */
+__bpf_kfunc int bpf_dynptr_from_skb_ext(struct __sk_buff *skb_, u32 size,
+ u64 flags,
+ struct bpf_dynptr *ptr__uninit)
+{
+ struct bpf_dynptr_kern *ptr = (struct bpf_dynptr_kern *)ptr__uninit;
+ struct sk_buff *skb = (struct sk_buff *)skb_;
+ bool create = flags & BPF_SKB_EXT_F_CREATE;
+ struct bpf_skb_ext *ext;
+ bool exists;
+ int err;
+
+ if (flags & ~BPF_SKB_EXT_F_CREATE) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (size > ARRAY_SIZE(ext->buf)) {
+ err = -E2BIG;
+ goto error;
+ }
+ if (!size)
+ size = ARRAY_SIZE(ext->buf);
+
+ exists = skb_ext_exist(skb, SKB_EXT_BPF);
+ if (!create) {
+ if (!exists) {
+ err = -ENOENT;
+ goto error;
+ }
+ goto out;
+ }
+
+ ext = skb_ext_add(skb, SKB_EXT_BPF);
+ if (!ext) {
+ err = -ENOMEM;
+ goto error;
+ }
+ if (!exists)
+ memset(ext, 0, sizeof(*ext));
+out:
+ bpf_dynptr_init(ptr, skb, BPF_DYNPTR_TYPE_SKB_EXT, 0, size);
+ if (!create)
+ bpf_dynptr_set_rdonly(ptr);
+ return 0;
+error:
+ bpf_dynptr_set_null(ptr);
+ return err;
+}
+#endif /* CONFIG_BPF_SKB_EXT */
+
/**
* bpf_dynptr_from_skb_meta() - Initialize a dynptr to the skb metadata area.
* @skb_: socket buffer carrying the metadata
@@ -12576,6 +12683,12 @@ BTF_KFUNCS_START(bpf_kfunc_check_set_skb_meta)
BTF_ID_FLAGS(func, bpf_dynptr_from_skb_meta)
BTF_KFUNCS_END(bpf_kfunc_check_set_skb_meta)
+#ifdef CONFIG_BPF_SKB_EXT
+BTF_KFUNCS_START(bpf_kfunc_check_set_skb_ext)
+BTF_ID_FLAGS(func, bpf_dynptr_from_skb_ext)
+BTF_KFUNCS_END(bpf_kfunc_check_set_skb_ext)
+#endif
+
BTF_KFUNCS_START(bpf_kfunc_check_set_xdp)
BTF_ID_FLAGS(func, bpf_dynptr_from_xdp)
BTF_ID_FLAGS(func, bpf_xdp_pull_data)
@@ -12603,6 +12716,13 @@ static const struct btf_kfunc_id_set bpf_kfunc_set_skb_meta = {
.set = &bpf_kfunc_check_set_skb_meta,
};
+#ifdef CONFIG_BPF_SKB_EXT
+static const struct btf_kfunc_id_set bpf_kfunc_set_skb_ext = {
+ .owner = THIS_MODULE,
+ .set = &bpf_kfunc_check_set_skb_ext,
+};
+#endif
+
static const struct btf_kfunc_id_set bpf_kfunc_set_xdp = {
.owner = THIS_MODULE,
.set = &bpf_kfunc_check_set_xdp,
@@ -12640,6 +12760,21 @@ static int __init bpf_kfunc_init(void)
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &bpf_kfunc_set_skb);
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_CLS, &bpf_kfunc_set_skb_meta);
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_ACT, &bpf_kfunc_set_skb_meta);
+#ifdef CONFIG_BPF_SKB_EXT
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_CLS, &bpf_kfunc_set_skb_ext);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_ACT, &bpf_kfunc_set_skb_ext);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_CGROUP_SKB, &bpf_kfunc_set_skb_ext);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SOCK_OPS, &bpf_kfunc_set_skb_ext);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SK_SKB, &bpf_kfunc_set_skb_ext);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SOCKET_FILTER, &bpf_kfunc_set_skb_ext);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LWT_OUT, &bpf_kfunc_set_skb_ext);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LWT_IN, &bpf_kfunc_set_skb_ext);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LWT_XMIT, &bpf_kfunc_set_skb_ext);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LWT_SEG6LOCAL, &bpf_kfunc_set_skb_ext);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_NETFILTER, &bpf_kfunc_set_skb_ext);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &bpf_kfunc_set_skb_ext);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &bpf_kfunc_set_skb_ext);
+#endif
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_XDP, &bpf_kfunc_set_xdp);
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
&bpf_kfunc_set_sock_addr);
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index c82a1472a5ea..9947d2ed6635 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -5155,6 +5155,9 @@ static const u8 skb_ext_type_len[] = {
#if IS_ENABLED(CONFIG_CAN)
[SKB_EXT_CAN] = SKB_EXT_CHUNKSIZEOF(struct can_skb_ext),
#endif
+#if IS_ENABLED(CONFIG_BPF_SKB_EXT)
+ [SKB_EXT_BPF] = SKB_EXT_CHUNKSIZEOF(struct bpf_skb_ext),
+#endif
};
static __always_inline __no_profile unsigned int skb_ext_total_length(void)
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 02/14] bpf: Allow access to bpf_sock_ops_kern->skb
2026-08-14 8:14 [PATCH net-next 00/14] skb extension for BPF metadata Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 01/14] bpf: Introduce per-packet metadata storage for BPF programs Jakub Sitnicki
@ 2026-08-14 8:14 ` Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 03/14] bpf: Make BPF skb extension survive packet scrubbing Jakub Sitnicki
` (11 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jakub Sitnicki @ 2026-08-14 8:14 UTC (permalink / raw)
To: netdev, Alexei Starovoitov, Jakub Kicinski, Kuniyuki Iwashima,
Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
sock_ops programs receive bpf_sock_ops_kern as their kernel context, which
holds a pointer to the sk_buff being processed. Mark bpf_sock_ops_kern->skb
as BTF_TYPE_SAFE_TRUSTED_OR_NULL so that BPF programs can dereference it
and pass it to kfuncs expecting a trusted sk_buff pointer, such as
bpf_skb_storage_get().
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
---
kernel/bpf/verifier.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 1fab69fec478..b0ddaccf61b3 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -5684,6 +5684,10 @@ BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct vm_area_struct) {
struct file *vm_file;
};
+BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct bpf_sock_ops_kern) {
+ struct sk_buff *skb;
+};
+
static bool type_is_rcu(struct bpf_verifier_env *env,
struct bpf_reg_state *reg,
const char *field_name, u32 btf_id)
@@ -5726,6 +5730,7 @@ static bool type_is_trusted_or_null(struct bpf_verifier_env *env,
BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct socket));
BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct dentry));
BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct vm_area_struct));
+ BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct bpf_sock_ops_kern));
return btf_nested_type_is_trusted(&env->log, reg, field_name, btf_id,
"__safe_trusted_or_null");
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 03/14] bpf: Make BPF skb extension survive packet scrubbing
2026-08-14 8:14 [PATCH net-next 00/14] skb extension for BPF metadata Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 01/14] bpf: Introduce per-packet metadata storage for BPF programs Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 02/14] bpf: Allow access to bpf_sock_ops_kern->skb Jakub Sitnicki
@ 2026-08-14 8:14 ` Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 04/14] selftests/bpf: Add tests for bpf_dynptr_from_skb_ext Jakub Sitnicki
` (10 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jakub Sitnicki @ 2026-08-14 8:14 UTC (permalink / raw)
To: netdev, Alexei Starovoitov, Jakub Kicinski, Kuniyuki Iwashima,
Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
skb_scrub_packet() drops all skb extensions unconditionally via
skb_ext_reset(). It runs on tunnel encap/decap (ip_tunnel_rcv,
vxlan_rcv, etc.) and cross-netns forwarding (dev_forward_skb).
This makes it impossible for a BPF program to pass metadata via
bpf_skb_ext through a tunnel or across a netns boundary. The extension
is always lost at the scrub point.
Introduce skb_ext_scrub() which consults each active extension before
discarding it. Extensions that request preservation are kept while the
rest are torn down. When the extension slab is shared with clones, COW
ensures isolation. Replace the skb_ext_reset() call in
skb_scrub_packet() with skb_ext_scrub().
Switch udp_try_make_stateless() to skb_ext_scrub() as well, so that the
metadata stays readable until the packet is queued on a UDP socket
receive queue (e.g. for a sockmap verdict program). Only mark the skb
stateless when no extension survives the scrub. Otherwise
skb_consume_udp() would take the __consume_stateless_skb() fast path,
which skips skb_release_head_state(), and leak the extension slab.
Finally, make BPF skb extension survive skb scrubbing.
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
---
include/linux/skbuff.h | 2 ++
net/core/skbuff.c | 78 +++++++++++++++++++++++++++++++++++++++++++-------
net/ipv4/udp.c | 9 ++++--
3 files changed, 76 insertions(+), 13 deletions(-)
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index a4f88107c2d5..286f2e4cdbb1 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -5061,6 +5061,7 @@ void *__skb_ext_set(struct sk_buff *skb, enum skb_ext_id id,
void *skb_ext_add(struct sk_buff *skb, enum skb_ext_id id);
void __skb_ext_del(struct sk_buff *skb, enum skb_ext_id id);
void __skb_ext_put(struct skb_ext *ext);
+void skb_ext_scrub(struct sk_buff *skb);
static inline void skb_ext_put(struct sk_buff *skb)
{
@@ -5130,6 +5131,7 @@ static inline bool skb_has_extensions(struct sk_buff *skb)
static inline void __skb_ext_put(struct skb_ext *ext) {}
static inline void skb_ext_put(struct sk_buff *skb) {}
static inline void skb_ext_reset(struct sk_buff *skb) {}
+static inline void skb_ext_scrub(struct sk_buff *skb) {}
static inline void skb_ext_del(struct sk_buff *skb, int unused) {}
static inline void __skb_ext_copy(struct sk_buff *d, const struct sk_buff *s) {}
static inline void skb_ext_copy(struct sk_buff *dst, const struct sk_buff *s) {}
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 9947d2ed6635..700bee045b5c 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -83,6 +83,7 @@
#include <net/page_pool/helpers.h>
#include <net/psp/types.h>
#include <net/dropreason.h>
+#include <linux/bpf.h>
#include <net/xdp_sock.h>
#include <linux/uaccess.h>
@@ -6284,7 +6285,7 @@ void skb_scrub_packet(struct sk_buff *skb, bool xnet)
skb->skb_iif = 0;
skb->ignore_df = 0;
skb_dst_drop(skb);
- skb_ext_reset(skb);
+ skb_ext_scrub(skb);
nf_reset_ct(skb);
nf_reset_trace(skb);
@@ -7258,6 +7259,20 @@ void __skb_ext_del(struct sk_buff *skb, enum skb_ext_id id)
}
EXPORT_SYMBOL(__skb_ext_del);
+static void skb_ext_put_each(struct skb_ext *ext, unsigned int skip)
+{
+#ifdef CONFIG_XFRM
+ if (!(skip & (1 << SKB_EXT_SEC_PATH)) &&
+ __skb_ext_exist(ext, SKB_EXT_SEC_PATH))
+ skb_ext_put_sp(skb_ext_get_ptr(ext, SKB_EXT_SEC_PATH));
+#endif
+#ifdef CONFIG_MCTP_FLOWS
+ if (!(skip & (1 << SKB_EXT_MCTP)) &&
+ __skb_ext_exist(ext, SKB_EXT_MCTP))
+ skb_ext_put_mctp(skb_ext_get_ptr(ext, SKB_EXT_MCTP));
+#endif
+}
+
void __skb_ext_put(struct skb_ext *ext)
{
/* If this is last clone, nothing can increment
@@ -7269,18 +7284,61 @@ void __skb_ext_put(struct skb_ext *ext)
if (!refcount_dec_and_test(&ext->refcnt))
return;
free_now:
-#ifdef CONFIG_XFRM
- if (__skb_ext_exist(ext, SKB_EXT_SEC_PATH))
- skb_ext_put_sp(skb_ext_get_ptr(ext, SKB_EXT_SEC_PATH));
-#endif
-#ifdef CONFIG_MCTP_FLOWS
- if (__skb_ext_exist(ext, SKB_EXT_MCTP))
- skb_ext_put_mctp(skb_ext_get_ptr(ext, SKB_EXT_MCTP));
-#endif
-
+ skb_ext_put_each(ext, 0);
kmem_cache_free(skbuff_ext_cache, ext);
}
EXPORT_SYMBOL(__skb_ext_put);
+
+static unsigned int skb_ext_no_scrub(struct skb_ext *ext)
+{
+ unsigned int keep = 0;
+
+#if IS_ENABLED(CONFIG_BPF_SKB_EXT)
+ if (__skb_ext_exist(ext, SKB_EXT_BPF))
+ keep |= (1 << SKB_EXT_BPF);
+#endif
+ return keep;
+}
+
+static int __skb_ext_scrub(struct sk_buff *skb, unsigned int keep)
+{
+ struct skb_ext *old = skb->extensions;
+ struct skb_ext *ext;
+ int i;
+
+ if (refcount_read(&old->refcnt) == 1) {
+ skb_ext_put_each(old, keep);
+ ext = old;
+ } else {
+ ext = skb_ext_maybe_cow(old, keep);
+ if (!ext)
+ return -ENOMEM;
+ skb->extensions = ext;
+ }
+
+ for (i = 0; i < SKB_EXT_NUM; i++) {
+ if (!(keep & (1 << i)))
+ ext->offset[i] = 0;
+ }
+ skb->active_extensions = keep;
+ return 0;
+}
+
+void skb_ext_scrub(struct sk_buff *skb)
+{
+ unsigned int keep;
+
+ if (likely(!skb->active_extensions))
+ return;
+
+ keep = skb_ext_no_scrub(skb->extensions);
+ if (keep && !__skb_ext_scrub(skb, keep))
+ return;
+
+ skb_ext_reset(skb);
+}
+EXPORT_SYMBOL(skb_ext_scrub);
+
#endif /* CONFIG_SKB_EXTENSIONS */
static void kfree_skb_napi_cache(struct sk_buff *skb)
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index af9603217444..865655db2185 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -1530,7 +1530,10 @@ void udp_splice_eof(struct socket *sock)
* We need to preserve secpath, if present, to eventually process
* IP_CMSG_PASSSEC at recvmsg() time.
*
- * Other extensions can be cleared.
+ * Other extensions get scrubbed. If any extension survives scrubbing
+ * (e.g. SKB_EXT_BPF), the skb must stay stateful, so that
+ * skb_consume_udp() releases it via skb_release_head_state() instead of
+ * leaking the extension slab on the __consume_stateless_skb() path.
*/
static bool udp_try_make_stateless(struct sk_buff *skb)
{
@@ -1538,8 +1541,8 @@ static bool udp_try_make_stateless(struct sk_buff *skb)
return true;
if (!secpath_exists(skb)) {
- skb_ext_reset(skb);
- return true;
+ skb_ext_scrub(skb);
+ return !skb_has_extensions(skb);
}
return false;
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 04/14] selftests/bpf: Add tests for bpf_dynptr_from_skb_ext
2026-08-14 8:14 [PATCH net-next 00/14] skb extension for BPF metadata Jakub Sitnicki
` (2 preceding siblings ...)
2026-08-14 8:14 ` [PATCH net-next 03/14] bpf: Make BPF skb extension survive packet scrubbing Jakub Sitnicki
@ 2026-08-14 8:14 ` Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 05/14] selftests/bpf: Test skb_ext on cloned skbs Jakub Sitnicki
` (9 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jakub Sitnicki @ 2026-08-14 8:14 UTC (permalink / raw)
To: netdev, Alexei Starovoitov, Jakub Kicinski, Kuniyuki Iwashima,
Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
Cover the bpf_dynptr_from_skb_ext() kfunc and the dynptr interface
to skb_ext with TC-to-TC tests on a tuntap device:
- write/read via bpf_dynptr_read and bpf_dynptr_write
- write/read via bpf_dynptr_slice and bpf_dynptr_slice_rdwr
- clone read via bpf_dynptr_clone
- error paths: no allocation without F_CREATE, invalid flags, read-only
enforcement without F_CREATE
- double allocation: data from first alloc survives second skb_ext_add
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
---
tools/testing/selftests/bpf/config | 1 +
.../bpf/prog_tests/xdp_context_test_run.c | 52 ++++++
tools/testing/selftests/bpf/progs/test_xdp_meta.c | 185 +++++++++++++++++++++
3 files changed, 238 insertions(+)
diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
index ea7044f30adc..502f4504bdba 100644
--- a/tools/testing/selftests/bpf/config
+++ b/tools/testing/selftests/bpf/config
@@ -7,6 +7,7 @@ CONFIG_BPF_JIT=y
CONFIG_BPF_KPROBE_OVERRIDE=y
CONFIG_BPF_LIRC_MODE2=y
CONFIG_BPF_LSM=y
+CONFIG_BPF_SKB_EXT=y
CONFIG_BPF_STREAM_PARSER=y
CONFIG_BPF_SYSCALL=y
# CONFIG_BPF_UNPRIV_DEFAULT_OFF is not set
diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
index 448807676176..fd340e5538a3 100644
--- a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
+++ b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
@@ -693,3 +693,55 @@ void test_xdp_context_lwt_encap(void)
test_xdp_meta__destroy(skel);
}
+
+void test_skb_ext_basic(void)
+{
+ struct test_xdp_meta *skel = NULL;
+
+ skel = test_xdp_meta__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open and load skeleton"))
+ return;
+
+ if (test__start_subtest("tc_write_read"))
+ test_tuntap(NULL, /* xdp */
+ skel->progs.tc_skb_ext_write,
+ skel->progs.tc_skb_ext_read,
+ &skel->bss->test_pass);
+ if (test__start_subtest("tc_write_clone_read"))
+ test_tuntap(NULL, /* xdp */
+ skel->progs.tc_skb_ext_write,
+ skel->progs.tc_skb_ext_clone_read,
+ &skel->bss->test_pass);
+ if (test__start_subtest("tc_write_slice_read"))
+ test_tuntap(NULL, /* xdp */
+ skel->progs.tc_skb_ext_write,
+ skel->progs.tc_skb_ext_slice_read,
+ &skel->bss->test_pass);
+ if (test__start_subtest("tc_slice_write_read"))
+ test_tuntap(NULL, /* xdp */
+ skel->progs.tc_skb_ext_slice_write,
+ skel->progs.tc_skb_ext_read,
+ &skel->bss->test_pass);
+ if (test__start_subtest("tc_no_alloc"))
+ test_tuntap(NULL, /* xdp */
+ skel->progs.tc_skb_ext_no_alloc,
+ NULL, /* tc prio 2 */
+ &skel->bss->test_pass);
+ if (test__start_subtest("tc_invalid_flags"))
+ test_tuntap(NULL, /* xdp */
+ skel->progs.tc_skb_ext_invalid_flags,
+ NULL, /* tc prio 2 */
+ &skel->bss->test_pass);
+ if (test__start_subtest("tc_rdonly"))
+ test_tuntap(NULL, /* xdp */
+ skel->progs.tc_skb_ext_rdonly,
+ NULL, /* tc prio 2 */
+ &skel->bss->test_pass);
+ if (test__start_subtest("tc_double_alloc"))
+ test_tuntap(NULL, /* xdp */
+ skel->progs.tc_skb_ext_double_alloc,
+ NULL, /* tc prio 2 */
+ &skel->bss->test_pass);
+
+ test_xdp_meta__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/test_xdp_meta.c b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
index 08b03be0b891..43840ee32d35 100644
--- a/tools/testing/selftests/bpf/progs/test_xdp_meta.c
+++ b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
@@ -6,6 +6,7 @@
#include <errno.h>
#include "bpf_kfuncs.h"
+#include "bpf_misc.h"
#include "bpf_tracing_net.h"
#define META_SIZE 32
@@ -689,4 +690,188 @@ int helper_skb_change_proto(struct __sk_buff *ctx)
return TC_ACT_SHOT;
}
+/* Write to skb_ext using bpf_dynptr_write helper */
+SEC("tc")
+int tc_skb_ext_write(struct __sk_buff *ctx)
+{
+ struct bpf_dynptr meta;
+
+ if (!is_test_packet_tc(ctx))
+ return TC_ACT_SHOT;
+ if (bpf_dynptr_from_skb_ext(ctx, 0, BPF_SKB_EXT_F_CREATE, &meta))
+ return TC_ACT_SHOT;
+ if (bpf_dynptr_write(&meta, 0, (void *)meta_want, ARRAY_SIZE(meta_want), 0))
+ return TC_ACT_SHOT;
+
+ return TC_ACT_UNSPEC;
+}
+
+/* Read from skb-ext metadata using bpf_dynptr_read helper */
+SEC("tc")
+int tc_skb_ext_read(struct __sk_buff *ctx)
+{
+ __u8 meta_have[META_SIZE];
+ struct bpf_dynptr meta;
+
+ if (bpf_dynptr_from_skb_ext(ctx, 0, 0, &meta))
+ return TC_ACT_SHOT;
+ if (bpf_dynptr_read(meta_have, ARRAY_SIZE(meta_have), &meta, 0, 0))
+ return TC_ACT_SHOT;
+ if (!check_metadata(meta_have))
+ return TC_ACT_SHOT;
+
+ test_pass = true;
+ return TC_ACT_UNSPEC;
+}
+
+/* Read from a cloned skb_ext dynptr */
+SEC("tc")
+int tc_skb_ext_clone_read(struct __sk_buff *ctx)
+{
+ struct bpf_dynptr meta, clone;
+ __u8 meta_have[META_SIZE];
+
+ if (bpf_dynptr_from_skb_ext(ctx, 0, 0, &meta))
+ return TC_ACT_SHOT;
+ if (bpf_dynptr_clone(&meta, &clone))
+ return TC_ACT_SHOT;
+ if (bpf_dynptr_read(meta_have, ARRAY_SIZE(meta_have), &clone, 0, 0))
+ return TC_ACT_SHOT;
+ if (!check_metadata(meta_have))
+ return TC_ACT_SHOT;
+
+ test_pass = true;
+ return TC_ACT_UNSPEC;
+}
+
+/* Read from skb_ext using bpf_dynptr_slice */
+SEC("tc")
+int tc_skb_ext_slice_read(struct __sk_buff *ctx)
+{
+ struct bpf_dynptr meta;
+ __u8 *meta_have;
+
+ if (bpf_dynptr_from_skb_ext(ctx, 0, 0, &meta))
+ return TC_ACT_SHOT;
+ meta_have = bpf_dynptr_slice(&meta, 0, NULL, META_SIZE);
+ if (!meta_have)
+ return TC_ACT_SHOT;
+ if (!check_metadata(meta_have))
+ return TC_ACT_SHOT;
+
+ test_pass = true;
+ return TC_ACT_UNSPEC;
+}
+
+/* Write to skb_ext using bpf_dynptr_slice_rdwr */
+SEC("tc")
+int tc_skb_ext_slice_write(struct __sk_buff *ctx)
+{
+ struct bpf_dynptr meta;
+ __u8 *dst;
+
+ if (!is_test_packet_tc(ctx))
+ return TC_ACT_SHOT;
+ if (bpf_dynptr_from_skb_ext(ctx, 0, BPF_SKB_EXT_F_CREATE, &meta))
+ return TC_ACT_SHOT;
+ dst = bpf_dynptr_slice_rdwr(&meta, 0, NULL, META_SIZE);
+ if (!dst)
+ return TC_ACT_SHOT;
+ __builtin_memcpy(dst, meta_want, META_SIZE);
+
+ return TC_ACT_UNSPEC;
+}
+
+/* Opening skb_ext without F_CREATE on a fresh skb should fail */
+SEC("tc")
+int tc_skb_ext_no_alloc(struct __sk_buff *ctx)
+{
+ struct bpf_dynptr meta;
+
+ if (!is_test_packet_tc(ctx))
+ return TC_ACT_SHOT;
+ if (bpf_dynptr_from_skb_ext(ctx, 0, 0, &meta) != -ENOENT)
+ return TC_ACT_SHOT;
+
+ test_pass = true;
+ return TC_ACT_UNSPEC;
+}
+
+/* Invalid flags are rejected */
+SEC("tc")
+int tc_skb_ext_invalid_flags(struct __sk_buff *ctx)
+{
+ struct bpf_dynptr meta;
+
+ if (!is_test_packet_tc(ctx))
+ return TC_ACT_SHOT;
+ if (bpf_dynptr_from_skb_ext(ctx, 0, ~0ULL, &meta) != -EINVAL)
+ return TC_ACT_SHOT;
+
+ test_pass = true;
+ return TC_ACT_UNSPEC;
+}
+
+/* Without F_CREATE the dynptr is read-only */
+SEC("tc")
+int tc_skb_ext_rdonly(struct __sk_buff *ctx)
+{
+ __u8 meta_have[META_SIZE];
+ struct bpf_dynptr meta;
+
+ if (!is_test_packet_tc(ctx))
+ return TC_ACT_SHOT;
+
+ /* Create and populate the ext */
+ if (bpf_dynptr_from_skb_ext(ctx, 0, BPF_SKB_EXT_F_CREATE, &meta))
+ return TC_ACT_SHOT;
+ if (bpf_dynptr_write(&meta, 0, (void *)meta_want, META_SIZE, 0))
+ return TC_ACT_SHOT;
+
+ /* Reopen without F_CREATE -- should be read-only */
+ if (bpf_dynptr_from_skb_ext(ctx, 0, 0, &meta))
+ return TC_ACT_SHOT;
+
+ /* Verify read-only: writes must fail, reads must work */
+ if (!bpf_dynptr_is_rdonly(&meta))
+ return TC_ACT_SHOT;
+ if (!bpf_dynptr_write(&meta, 0, (void *)meta_want, META_SIZE, 0))
+ return TC_ACT_SHOT;
+ if (bpf_dynptr_read(meta_have, META_SIZE, &meta, 0, 0))
+ return TC_ACT_SHOT;
+ if (!check_metadata(meta_have))
+ return TC_ACT_SHOT;
+
+ test_pass = true;
+ return TC_ACT_UNSPEC;
+}
+
+/* Double alloc: data from first alloc survives second skb_ext_add */
+SEC("tc")
+int tc_skb_ext_double_alloc(struct __sk_buff *ctx)
+{
+ __u8 meta_have[META_SIZE];
+ struct bpf_dynptr meta;
+
+ if (!is_test_packet_tc(ctx))
+ return TC_ACT_SHOT;
+
+ /* First alloc + write */
+ if (bpf_dynptr_from_skb_ext(ctx, 0, BPF_SKB_EXT_F_CREATE, &meta))
+ return TC_ACT_SHOT;
+ if (bpf_dynptr_write(&meta, 0, (void *)meta_want, META_SIZE, 0))
+ return TC_ACT_SHOT;
+
+ /* Second alloc -- skb_ext_add returns existing ext */
+ if (bpf_dynptr_from_skb_ext(ctx, 0, BPF_SKB_EXT_F_CREATE, &meta))
+ return TC_ACT_SHOT;
+ if (bpf_dynptr_read(meta_have, META_SIZE, &meta, 0, 0))
+ return TC_ACT_SHOT;
+ if (!check_metadata(meta_have))
+ return TC_ACT_SHOT;
+
+ test_pass = true;
+ return TC_ACT_UNSPEC;
+}
+
char _license[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 05/14] selftests/bpf: Test skb_ext on cloned skbs
2026-08-14 8:14 [PATCH net-next 00/14] skb extension for BPF metadata Jakub Sitnicki
` (3 preceding siblings ...)
2026-08-14 8:14 ` [PATCH net-next 04/14] selftests/bpf: Add tests for bpf_dynptr_from_skb_ext Jakub Sitnicki
@ 2026-08-14 8:14 ` Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 06/14] selftests/bpf: Test skb_ext survival across veth and GRE Jakub Sitnicki
` (8 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jakub Sitnicki @ 2026-08-14 8:14 UTC (permalink / raw)
To: netdev, Alexei Starovoitov, Jakub Kicinski, Kuniyuki Iwashima,
Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
Cover skb_ext behavior when an skb is cloned by TC mirred
(mirror to a dummy device):
- clone_ext_read: the extension written at tap ingress is readable
from the mirred clone at dummy ingress -- the clone shares the
extension with the original
- clone_ext_cow: opening the extension with F_CREATE on the clone
triggers copy-on-write, so overwriting the clone's data does not
affect the original -- verified by a tp_btf/kfree_skb probe that
checks the original skb still carries meta_want
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
---
.../bpf/prog_tests/xdp_context_test_run.c | 93 ++++++++++++++++++++++
tools/testing/selftests/bpf/progs/test_xdp_meta.c | 46 +++++++++++
2 files changed, 139 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
index fd340e5538a3..58b793cf14e5 100644
--- a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
+++ b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
@@ -694,6 +694,95 @@ void test_xdp_context_lwt_encap(void)
test_xdp_meta__destroy(skel);
}
+/* Test if skb_ext survives skb clone (via tc mirred).
+ * dummy_prog runs on the clone (dummy ingress).
+ */
+static void test_mirred_clone_ext(struct test_xdp_meta *skel,
+ struct bpf_program *dummy_prog)
+{
+ LIBBPF_OPTS(bpf_tc_hook, tc_hook, .attach_point = BPF_TC_INGRESS);
+ LIBBPF_OPTS(bpf_tc_opts, tc_opts, .handle = 1, .priority = 1);
+ struct netns_obj *ns = NULL;
+ int dummy_ifindex;
+ int tap_ifindex;
+ int tap_fd = -1;
+ int ret;
+
+ skel->bss->test_pass = false;
+
+ ns = netns_new("mirred_clone", true);
+ if (!ASSERT_OK_PTR(ns, "netns_new"))
+ return;
+
+ /* Dummy dev: attach reader */
+ SYS(close, "ip link add name " DUMMY_NAME " type dummy");
+ SYS(close, "ip link set dev " DUMMY_NAME " up");
+
+ dummy_ifindex = if_nametoindex(DUMMY_NAME);
+ if (!ASSERT_GE(dummy_ifindex, 0, "dummy_ifindex"))
+ goto close;
+
+ tc_hook.ifindex = dummy_ifindex;
+ ret = bpf_tc_hook_create(&tc_hook);
+ if (!ASSERT_OK(ret, "dummy_hook_create"))
+ goto close;
+
+ tc_opts.prog_fd = bpf_program__fd(dummy_prog);
+ ret = bpf_tc_attach(&tc_hook, &tc_opts);
+ if (!ASSERT_OK(ret, "dummy_attach"))
+ goto close;
+
+ /* TAP dev: attach writer + mirred to dummy */
+ tap_fd = open_tuntap(TAP_NAME, true);
+ if (!ASSERT_GE(tap_fd, 0, "open_tuntap"))
+ goto close;
+
+ SYS(close, "ip link set dev " TAP_NAME " up");
+
+ tap_ifindex = if_nametoindex(TAP_NAME);
+ if (!ASSERT_GE(tap_ifindex, 0, "tap_ifindex"))
+ goto close;
+
+ tc_hook.ifindex = tap_ifindex;
+ ret = bpf_tc_hook_create(&tc_hook);
+ if (!ASSERT_OK(ret, "tap_hook_create"))
+ goto close;
+
+ tc_opts.prog_id = 0;
+ tc_opts.prog_fd = bpf_program__fd(skel->progs.tc_skb_ext_write);
+ ret = bpf_tc_attach(&tc_hook, &tc_opts);
+ if (!ASSERT_OK(ret, "tap_attach"))
+ goto close;
+
+ SYS(close, "tc filter add dev " TAP_NAME " ingress "
+ "protocol all matchall "
+ "action mirred ingress mirror dev " DUMMY_NAME);
+
+ ret = write_test_packet(tap_fd);
+ if (!ASSERT_OK(ret, "write_test_packet"))
+ goto close;
+
+ ASSERT_TRUE(skel->bss->test_pass, "test_pass");
+
+close:
+ if (tap_fd >= 0)
+ close(tap_fd);
+ netns_free(ns);
+}
+
+static void test_mirred_clone_ext_cow(struct test_xdp_meta *skel)
+{
+ struct bpf_link *tp_link;
+
+ skel->bss->clone_cow_done = false;
+ tp_link = bpf_program__attach(skel->progs.tp_kfree_skb_cow_check);
+ if (!ASSERT_OK_PTR(tp_link, "attach_tp"))
+ return;
+
+ test_mirred_clone_ext(skel, skel->progs.tc_skb_ext_clone_redir_cow);
+ bpf_link__destroy(tp_link);
+}
+
void test_skb_ext_basic(void)
{
struct test_xdp_meta *skel = NULL;
@@ -742,6 +831,10 @@ void test_skb_ext_basic(void)
skel->progs.tc_skb_ext_double_alloc,
NULL, /* tc prio 2 */
&skel->bss->test_pass);
+ if (test__start_subtest("clone_ext_read"))
+ test_mirred_clone_ext(skel, skel->progs.tc_skb_ext_read);
+ if (test__start_subtest("clone_ext_cow"))
+ test_mirred_clone_ext_cow(skel);
test_xdp_meta__destroy(skel);
}
diff --git a/tools/testing/selftests/bpf/progs/test_xdp_meta.c b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
index 43840ee32d35..1b6bb20ee7e8 100644
--- a/tools/testing/selftests/bpf/progs/test_xdp_meta.c
+++ b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
@@ -3,6 +3,7 @@
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
#include <errno.h>
#include "bpf_kfuncs.h"
@@ -874,4 +875,49 @@ int tc_skb_ext_double_alloc(struct __sk_buff *ctx)
return TC_ACT_UNSPEC;
}
+static const __u8 meta_zero[META_SIZE] = {};
+
+volatile bool clone_cow_done;
+
+/* Overwrite skb_ext on the clone via F_CREATE (COW) -- must not affect original.
+ * Runs on the dummy ingress (clone side), synchronously during tc mirred.
+ */
+SEC("tc")
+int tc_skb_ext_clone_redir_cow(struct __sk_buff *ctx)
+{
+ struct bpf_dynptr meta;
+
+ if (bpf_dynptr_from_skb_ext(ctx, 0, BPF_SKB_EXT_F_CREATE, &meta))
+ return TC_ACT_SHOT;
+
+ /* Zero out the clone's ext -- must not affect original */
+ bpf_dynptr_write(&meta, 0, (void *)meta_zero, META_SIZE, 0);
+
+ clone_cow_done = true;
+ return TC_ACT_SHOT;
+}
+
+/* Verify COW isolation at kfree_skb time: once clone_cow_done is set,
+ * check that the original skb still has meta_want.
+ */
+SEC("tp_btf/kfree_skb")
+int BPF_PROG(tp_kfree_skb_cow_check, struct sk_buff *skb)
+{
+ __u8 meta_have[META_SIZE];
+ struct bpf_dynptr meta;
+
+ if (!clone_cow_done)
+ return 0;
+
+ if (bpf_dynptr_from_skb_ext((struct __sk_buff *)skb, 0, 0, &meta))
+ return 0;
+ if (bpf_dynptr_read(meta_have, META_SIZE, &meta, 0, 0))
+ return 0;
+ if (!check_metadata(meta_have))
+ return 0;
+
+ test_pass = true;
+ return 0;
+}
+
char _license[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 06/14] selftests/bpf: Test skb_ext survival across veth and GRE
2026-08-14 8:14 [PATCH net-next 00/14] skb extension for BPF metadata Jakub Sitnicki
` (4 preceding siblings ...)
2026-08-14 8:14 ` [PATCH net-next 05/14] selftests/bpf: Test skb_ext on cloned skbs Jakub Sitnicki
@ 2026-08-14 8:14 ` Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 07/14] selftests/bpf: Test skb_ext read from cgroup_skb and sk_filter hooks Jakub Sitnicki
` (7 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jakub Sitnicki @ 2026-08-14 8:14 UTC (permalink / raw)
To: netdev, Alexei Starovoitov, Jakub Kicinski, Kuniyuki Iwashima,
Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
Write skb_ext at TC egress and read it back at TC ingress after the
packet crosses device boundaries:
- survives_veth: veth forwarding between two netns
- survives_gre: GRE encap/decap between two gretap endpoints over
loopback in a single netns
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
---
.../bpf/prog_tests/xdp_context_test_run.c | 154 +++++++++++++++++++++
1 file changed, 154 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
index 58b793cf14e5..de6030302536 100644
--- a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
+++ b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
@@ -783,6 +783,156 @@ static void test_mirred_clone_ext_cow(struct test_xdp_meta *skel)
bpf_link__destroy(tp_link);
}
+/* Test if skb_ext survives veth cross-netns forward */
+static void test_skb_ext_scrub_veth(struct test_xdp_meta *skel)
+{
+ LIBBPF_OPTS(bpf_tc_hook, tx_hook, .attach_point = BPF_TC_EGRESS);
+ LIBBPF_OPTS(bpf_tc_opts, tx_opts, .handle = 1, .priority = 1);
+ LIBBPF_OPTS(bpf_tc_hook, rx_hook, .attach_point = BPF_TC_INGRESS);
+ LIBBPF_OPTS(bpf_tc_opts, rx_opts, .handle = 1, .priority = 1);
+ struct netns_obj *rx_ns = NULL, *tx_ns = NULL;
+ struct nstoken *nstoken = NULL;
+ int rx_ifindex, tx_ifindex;
+ int ret;
+
+ tx_ns = netns_new(TX_NETNS, false);
+ if (!ASSERT_OK_PTR(tx_ns, "create tx_ns"))
+ return;
+
+ rx_ns = netns_new(RX_NETNS, false);
+ if (!ASSERT_OK_PTR(rx_ns, "create rx_ns"))
+ goto close;
+
+ SYS(close, "ip link add " RX_NAME " netns " RX_NETNS
+ " type veth peer name " TX_NAME " netns " TX_NETNS);
+
+ /* Setup RX side: TC ingress reader */
+ nstoken = open_netns(RX_NETNS);
+ if (!ASSERT_OK_PTR(nstoken, "setns rx_ns"))
+ goto close;
+
+ SYS(close, "ip link set dev " RX_NAME " up");
+
+ rx_ifindex = if_nametoindex(RX_NAME);
+ if (!ASSERT_GE(rx_ifindex, 0, "if_nametoindex rx"))
+ goto close;
+
+ rx_hook.ifindex = rx_ifindex;
+ ret = bpf_tc_hook_create(&rx_hook);
+ if (!ASSERT_OK(ret, "bpf_tc_hook_create rx"))
+ goto close;
+
+ rx_opts.prog_fd = bpf_program__fd(skel->progs.tc_skb_ext_read);
+ ret = bpf_tc_attach(&rx_hook, &rx_opts);
+ if (!ASSERT_OK(ret, "bpf_tc_attach rx"))
+ goto close;
+
+ close_netns(nstoken);
+
+ /* Setup TX side: TC egress writer */
+ nstoken = open_netns(TX_NETNS);
+ if (!ASSERT_OK_PTR(nstoken, "setns tx_ns"))
+ goto close;
+
+ SYS(close, "ip link set dev " TX_NAME " up");
+
+ tx_ifindex = if_nametoindex(TX_NAME);
+ if (!ASSERT_GE(tx_ifindex, 0, "if_nametoindex tx"))
+ goto close;
+
+ tx_hook.ifindex = tx_ifindex;
+ ret = bpf_tc_hook_create(&tx_hook);
+ if (!ASSERT_OK(ret, "bpf_tc_hook_create tx"))
+ goto close;
+
+ tx_opts.prog_fd = bpf_program__fd(skel->progs.tc_skb_ext_write);
+ ret = bpf_tc_attach(&tx_hook, &tx_opts);
+ if (!ASSERT_OK(ret, "bpf_tc_attach tx"))
+ goto close;
+
+ skel->bss->test_pass = false;
+
+ ret = send_test_packet(tx_ifindex);
+ if (!ASSERT_OK(ret, "send_test_packet"))
+ goto close;
+
+ ASSERT_TRUE(skel->bss->test_pass, "test_pass");
+
+close:
+ close_netns(nstoken);
+ netns_free(rx_ns);
+ netns_free(tx_ns);
+}
+
+/* Test if skb_ext survives GRE tunnel encap+decap */
+static void test_skb_ext_scrub_gre(struct test_xdp_meta *skel)
+{
+ LIBBPF_OPTS(bpf_tc_hook, tx_hook, .attach_point = BPF_TC_EGRESS);
+ LIBBPF_OPTS(bpf_tc_opts, tx_opts, .handle = 1, .priority = 1);
+ LIBBPF_OPTS(bpf_tc_hook, rx_hook, .attach_point = BPF_TC_INGRESS);
+ LIBBPF_OPTS(bpf_tc_opts, rx_opts, .handle = 1, .priority = 1);
+ struct netns_obj *ns = NULL;
+ int tx_ifindex;
+ int rx_ifindex;
+ int ret;
+
+ skel->bss->test_pass = false;
+
+ ns = netns_new("gre_test", true);
+ if (!ASSERT_OK_PTR(ns, "netns_new"))
+ return;
+
+ /* Setup: gre_tx -> lo -> gre_rx */
+ SYS(close, "ip link set lo up");
+ SYS(close, "ip link add gre_tx type gretap"
+ " local 127.0.0.1 remote 127.0.0.2");
+ SYS(close, "ip link set gre_tx up");
+ SYS(close, "ip addr add 127.0.0.2/8 dev lo");
+ SYS(close, "ip link add gre_rx type gretap"
+ " local 127.0.0.2 remote 127.0.0.1");
+ SYS(close, "ip link set gre_rx up");
+
+ /* Write skb_ext on TC egress on GRE tx */
+ tx_ifindex = if_nametoindex("gre_tx");
+ if (!ASSERT_GE(tx_ifindex, 0, "tx_ifindex"))
+ goto close;
+
+ tx_hook.ifindex = tx_ifindex;
+ ret = bpf_tc_hook_create(&tx_hook);
+ if (!ASSERT_OK(ret, "tx_hook_create"))
+ goto close;
+
+ tx_opts.prog_fd = bpf_program__fd(skel->progs.tc_skb_ext_write);
+ ret = bpf_tc_attach(&tx_hook, &tx_opts);
+ if (!ASSERT_OK(ret, "tx_attach"))
+ goto close;
+
+ /* Read skb_ext on TC ingress on GRE rx */
+ rx_ifindex = if_nametoindex("gre_rx");
+ if (!ASSERT_GE(rx_ifindex, 0, "rx_ifindex"))
+ goto close;
+
+ rx_hook.ifindex = rx_ifindex;
+ ret = bpf_tc_hook_create(&rx_hook);
+ if (!ASSERT_OK(ret, "rx_hook_create"))
+ goto close;
+
+ rx_opts.prog_fd = bpf_program__fd(skel->progs.tc_skb_ext_read);
+ ret = bpf_tc_attach(&rx_hook, &rx_opts);
+ if (!ASSERT_OK(ret, "rx_attach"))
+ goto close;
+
+ /* Then use send_test_packet on GRE tx */
+ ret = send_test_packet(tx_ifindex);
+ if (!ASSERT_OK(ret, "send_test_packet"))
+ goto close;
+
+ ASSERT_TRUE(skel->bss->test_pass, "test_pass");
+
+close:
+ netns_free(ns);
+}
+
void test_skb_ext_basic(void)
{
struct test_xdp_meta *skel = NULL;
@@ -835,6 +985,10 @@ void test_skb_ext_basic(void)
test_mirred_clone_ext(skel, skel->progs.tc_skb_ext_read);
if (test__start_subtest("clone_ext_cow"))
test_mirred_clone_ext_cow(skel);
+ if (test__start_subtest("survives_veth"))
+ test_skb_ext_scrub_veth(skel);
+ if (test__start_subtest("survives_gre"))
+ test_skb_ext_scrub_gre(skel);
test_xdp_meta__destroy(skel);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 07/14] selftests/bpf: Test skb_ext read from cgroup_skb and sk_filter hooks
2026-08-14 8:14 [PATCH net-next 00/14] skb extension for BPF metadata Jakub Sitnicki
` (5 preceding siblings ...)
2026-08-14 8:14 ` [PATCH net-next 06/14] selftests/bpf: Test skb_ext survival across veth and GRE Jakub Sitnicki
@ 2026-08-14 8:14 ` Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 08/14] selftests/bpf: Test skb_ext read from sock_ops and LSM hooks Jakub Sitnicki
` (6 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jakub Sitnicki @ 2026-08-14 8:14 UTC (permalink / raw)
To: netdev, Alexei Starovoitov, Jakub Kicinski, Kuniyuki Iwashima,
Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
Extend skb_ext cross-hook coverage to UDP readers: attach a TC
ingress program on loopback that writes the extension, send a UDP
datagram over loopback, and read the extension back from a
cgroup_skb/ingress program and from a socket filter attached with
SO_ATTACH_BPF.
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
---
.../bpf/prog_tests/xdp_context_test_run.c | 121 +++++++++++++++++++++
tools/testing/selftests/bpf/progs/test_xdp_meta.c | 37 +++++++
2 files changed, 158 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
index de6030302536..808eb158fe0f 100644
--- a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
+++ b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
@@ -992,3 +992,124 @@ void test_skb_ext_basic(void)
test_xdp_meta__destroy(skel);
}
+
+/* Send test_payload over loopback UDP to recv_fd */
+static int send_loopback_udp(int recv_fd)
+{
+ struct sockaddr_in addr = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+ };
+ char buf[TEST_PAYLOAD_LEN];
+ int ret = -1;
+ int fd = -1;
+ __be16 port;
+
+ port = get_socket_local_port(recv_fd);
+ if (!ASSERT_GE(port, 0, "get_port"))
+ goto out;
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (!ASSERT_GE(fd, 0, "socket"))
+ goto out;
+
+ addr.sin_port = port;
+ sendto(fd, test_payload, TEST_PAYLOAD_LEN, 0,
+ (void *)&addr, sizeof(addr));
+ recvfrom(recv_fd, buf, sizeof(buf), 0, NULL, NULL);
+ ret = 0;
+out:
+ if (fd >= 0)
+ close(fd);
+ return ret;
+}
+
+enum udp_reader_type {
+ READER_CGRP_SKB,
+ READER_SK_FILTER,
+};
+
+/* Test skb_ext survival across TC ingress -> UDP reader hook */
+static void test_skb_ext_udp(struct test_xdp_meta *skel, const char *name,
+ enum udp_reader_type reader)
+{
+ LIBBPF_OPTS(bpf_tc_hook, tc_hook,
+ .ifindex = 1 /* IFINDEX_LO */,
+ .attach_point = BPF_TC_INGRESS);
+ LIBBPF_OPTS(bpf_tc_opts, tc_opts, .handle = 1, .priority = 1);
+ struct bpf_link *reader_link = NULL;
+ struct netns_obj *ns = NULL;
+ int server_fd = -1;
+ int cgroup_fd = -1;
+ int filter_fd;
+ int ret;
+
+ ns = netns_new(name, true);
+ if (!ASSERT_OK_PTR(ns, "netns_new"))
+ return;
+
+ cgroup_fd = test__join_cgroup(name);
+ if (!ASSERT_GE(cgroup_fd, 0, "join_cgroup"))
+ goto cleanup;
+
+ server_fd = start_server(AF_INET, SOCK_DGRAM, "127.0.0.1", 0, 0);
+ if (!ASSERT_GE(server_fd, 0, "start_server"))
+ goto cleanup;
+
+ skel->bss->test_pass = false;
+
+ ret = bpf_tc_hook_create(&tc_hook);
+ if (!ASSERT_OK(ret, "bpf_tc_hook_create"))
+ goto cleanup;
+
+ tc_opts.prog_fd = bpf_program__fd(skel->progs.tc_skb_ext_write);
+ ret = bpf_tc_attach(&tc_hook, &tc_opts);
+ if (!ASSERT_OK(ret, "bpf_tc_attach"))
+ goto cleanup;
+
+ switch (reader) {
+ case READER_CGRP_SKB:
+ reader_link = bpf_program__attach_cgroup(skel->progs.cgrp_skb_ext_read,
+ cgroup_fd);
+ if (!ASSERT_OK_PTR(reader_link, "attach_cgroup"))
+ goto cleanup;
+ break;
+ case READER_SK_FILTER:
+ filter_fd = bpf_program__fd(skel->progs.sk_filter_skb_ext_read);
+ ret = setsockopt(server_fd, SOL_SOCKET, SO_ATTACH_BPF,
+ &filter_fd, sizeof(filter_fd));
+ if (!ASSERT_OK(ret, "attach_socket_filter"))
+ goto cleanup;
+ break;
+ }
+
+ if (send_loopback_udp(server_fd))
+ goto cleanup;
+
+ ASSERT_TRUE(skel->bss->test_pass, "test_pass");
+
+cleanup:
+ bpf_link__destroy(reader_link);
+ bpf_tc_hook_destroy(&tc_hook);
+ if (server_fd >= 0)
+ close(server_fd);
+ if (cgroup_fd >= 0)
+ close(cgroup_fd);
+ netns_free(ns);
+}
+
+void test_skb_ext_cross_hook(void)
+{
+ struct test_xdp_meta *skel = NULL;
+
+ skel = test_xdp_meta__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open and load skeleton"))
+ return;
+
+ if (test__start_subtest("tc_to_cgrp_ingress"))
+ test_skb_ext_udp(skel, "tc_to_cgrp_ingress", READER_CGRP_SKB);
+ if (test__start_subtest("tc_to_sk_filter"))
+ test_skb_ext_udp(skel, "tc_to_sk_filter", READER_SK_FILTER);
+
+ test_xdp_meta__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/test_xdp_meta.c b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
index 1b6bb20ee7e8..0a3afb5c3461 100644
--- a/tools/testing/selftests/bpf/progs/test_xdp_meta.c
+++ b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
@@ -920,4 +920,41 @@ int BPF_PROG(tp_kfree_skb_cow_check, struct sk_buff *skb)
return 0;
}
+/* Read skb_ext from cgroup/skb ingress -- tests cross-hook survival */
+SEC("cgroup_skb/ingress")
+int cgrp_skb_ext_read(struct __sk_buff *ctx)
+{
+ __u8 meta_have[META_SIZE];
+ struct bpf_dynptr meta;
+
+ if (bpf_dynptr_from_skb_ext(ctx, 0, 0, &meta))
+ return 1;
+ if (bpf_dynptr_read(meta_have, META_SIZE, &meta, 0, 0))
+ return 1;
+ if (!check_metadata(meta_have))
+ return 1;
+
+ test_pass = true;
+ return 1;
+}
+
+/* Read skb_ext from socket filter -- tests TC -> sk_filter path */
+SEC("socket")
+int sk_filter_skb_ext_read(struct __sk_buff *ctx)
+{
+ __u8 meta_have[META_SIZE];
+ struct bpf_dynptr meta;
+
+ if (bpf_dynptr_from_skb_ext(ctx, 0, 0, &meta))
+ goto out;
+ if (bpf_dynptr_read(meta_have, META_SIZE, &meta, 0, 0))
+ goto out;
+ if (!check_metadata(meta_have))
+ goto out;
+
+ test_pass = true;
+out:
+ return ctx->len;
+}
+
char _license[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 08/14] selftests/bpf: Test skb_ext read from sock_ops and LSM hooks
2026-08-14 8:14 [PATCH net-next 00/14] skb extension for BPF metadata Jakub Sitnicki
` (6 preceding siblings ...)
2026-08-14 8:14 ` [PATCH net-next 07/14] selftests/bpf: Test skb_ext read from cgroup_skb and sk_filter hooks Jakub Sitnicki
@ 2026-08-14 8:14 ` Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 09/14] selftests/bpf: Test skb_ext read from kfree_skb tracepoint Jakub Sitnicki
` (5 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jakub Sitnicki @ 2026-08-14 8:14 UTC (permalink / raw)
To: netdev, Alexei Starovoitov, Jakub Kicinski, Kuniyuki Iwashima,
Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
Extend skb_ext test coverage to hook on TCP ingress path: attach a TC
ingress program on loopback that writes the metadata to packets, and read
it back from a sock_ops program (BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB,
reaching the skb via bpf_sock_ops_kern->skb) and from the LSM
inet_conn_established hook.
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
---
.../bpf/prog_tests/xdp_context_test_run.c | 92 ++++++++++++++++++++++
tools/testing/selftests/bpf/progs/test_xdp_meta.c | 75 ++++++++++++++++++
2 files changed, 167 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
index 808eb158fe0f..e0ab0fccf9d7 100644
--- a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
+++ b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
@@ -1098,6 +1098,94 @@ static void test_skb_ext_udp(struct test_xdp_meta *skel, const char *name,
netns_free(ns);
}
+enum tcp_reader_type {
+ READER_SKOPS,
+ READER_LSM,
+};
+
+/* Test skb_ext survival across TC ingress -> TCP reader hook */
+static void test_skb_ext_tcp(struct test_xdp_meta *skel, const char *name,
+ enum tcp_reader_type reader)
+{
+ LIBBPF_OPTS(bpf_tc_hook, tc_hook,
+ .ifindex = 1 /* IFINDEX_LO */,
+ .attach_point = BPF_TC_INGRESS);
+ LIBBPF_OPTS(bpf_tc_opts, tc_opts, .handle = 1, .priority = 1);
+ struct bpf_link *reader_link = NULL;
+ struct netns_obj *ns = NULL;
+ int server_fd = -1;
+ int cgroup_fd = -1;
+ int client_fd = -1;
+ int conn_fd = -1;
+ __be16 port;
+ int ret;
+
+ ns = netns_new(name, true);
+ if (!ASSERT_OK_PTR(ns, "netns_new"))
+ return;
+
+ cgroup_fd = test__join_cgroup(name);
+ if (!ASSERT_GE(cgroup_fd, 0, "join_cgroup"))
+ goto cleanup;
+
+ server_fd = start_server(AF_INET, SOCK_STREAM, "127.0.0.1", 0, 0);
+ if (!ASSERT_GE(server_fd, 0, "start_server"))
+ goto cleanup;
+
+ port = get_socket_local_port(server_fd);
+ if (!ASSERT_GE(port, 0, "get_port"))
+ goto cleanup;
+
+ skel->bss->target_port = port;
+ skel->bss->test_pass = false;
+
+ ret = bpf_tc_hook_create(&tc_hook);
+ if (!ASSERT_OK(ret, "bpf_tc_hook_create"))
+ goto cleanup;
+
+ tc_opts.prog_fd = bpf_program__fd(skel->progs.tc_skb_ext_write_port);
+ ret = bpf_tc_attach(&tc_hook, &tc_opts);
+ if (!ASSERT_OK(ret, "bpf_tc_attach"))
+ goto cleanup;
+
+ switch (reader) {
+ case READER_SKOPS:
+ reader_link = bpf_program__attach_cgroup(skel->progs.skops_skb_ext_read,
+ cgroup_fd);
+ if (!ASSERT_OK_PTR(reader_link, "attach_skops"))
+ goto cleanup;
+ break;
+ case READER_LSM:
+ reader_link = bpf_program__attach_lsm(skel->progs.lsm_skb_ext_read);
+ if (!ASSERT_OK_PTR(reader_link, "attach_lsm"))
+ goto cleanup;
+ break;
+ }
+
+ client_fd = connect_to_fd(server_fd, 0);
+ if (!ASSERT_GE(client_fd, 0, "connect"))
+ goto cleanup;
+
+ conn_fd = accept(server_fd, NULL, NULL);
+ if (!ASSERT_GE(conn_fd, 0, "accept"))
+ goto cleanup;
+
+ ASSERT_TRUE(skel->bss->test_pass, "test_pass");
+
+cleanup:
+ if (conn_fd >= 0)
+ close(conn_fd);
+ if (client_fd >= 0)
+ close(client_fd);
+ bpf_link__destroy(reader_link);
+ bpf_tc_hook_destroy(&tc_hook);
+ if (server_fd >= 0)
+ close(server_fd);
+ if (cgroup_fd >= 0)
+ close(cgroup_fd);
+ netns_free(ns);
+}
+
void test_skb_ext_cross_hook(void)
{
struct test_xdp_meta *skel = NULL;
@@ -1110,6 +1198,10 @@ void test_skb_ext_cross_hook(void)
test_skb_ext_udp(skel, "tc_to_cgrp_ingress", READER_CGRP_SKB);
if (test__start_subtest("tc_to_sk_filter"))
test_skb_ext_udp(skel, "tc_to_sk_filter", READER_SK_FILTER);
+ if (test__start_subtest("tc_to_lsm"))
+ test_skb_ext_tcp(skel, "tc_to_lsm", READER_LSM);
+ if (test__start_subtest("tc_to_skops"))
+ test_skb_ext_tcp(skel, "tc_to_skops", READER_SKOPS);
test_xdp_meta__destroy(skel);
}
diff --git a/tools/testing/selftests/bpf/progs/test_xdp_meta.c b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
index 0a3afb5c3461..8f0a0fea5d84 100644
--- a/tools/testing/selftests/bpf/progs/test_xdp_meta.c
+++ b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
@@ -938,6 +938,81 @@ int cgrp_skb_ext_read(struct __sk_buff *ctx)
return 1;
}
+volatile __be16 target_port;
+
+#define TCPV4_HDR_OFF (sizeof(struct ethhdr) + sizeof(struct iphdr))
+#define TCPV4_SPORT_OFF (TCPV4_HDR_OFF + offsetof(struct tcphdr, source))
+#define TCPV4_DPORT_OFF (TCPV4_HDR_OFF + offsetof(struct tcphdr, dest))
+
+/* Write skb_ext on TCP packets to/from target_port */
+SEC("tc")
+int tc_skb_ext_write_port(struct __sk_buff *ctx)
+{
+ struct bpf_dynptr meta;
+ __be16 sport, dport;
+
+ if (ctx->protocol != __bpf_constant_htons(ETH_P_IP))
+ return TC_ACT_UNSPEC;
+ if (bpf_skb_load_bytes(ctx, TCPV4_SPORT_OFF, &sport, sizeof(sport)))
+ return TC_ACT_UNSPEC;
+ if (bpf_skb_load_bytes(ctx, TCPV4_DPORT_OFF, &dport, sizeof(dport)))
+ return TC_ACT_UNSPEC;
+ if (sport != target_port && dport != target_port)
+ return TC_ACT_UNSPEC;
+
+ if (bpf_dynptr_from_skb_ext(ctx, 0, BPF_SKB_EXT_F_CREATE, &meta))
+ return TC_ACT_UNSPEC;
+ bpf_dynptr_write(&meta, 0, (void *)meta_want, META_SIZE, 0);
+
+ return TC_ACT_UNSPEC;
+}
+
+/* Read skb_ext from sock_ops passive established -- tests TC -> sock_ops path */
+SEC("sockops")
+int skops_skb_ext_read(struct bpf_sock_ops *ctx)
+{
+ struct bpf_sock_ops_kern *kctx;
+ __u8 meta_have[META_SIZE];
+ struct bpf_dynptr meta;
+ struct sk_buff *skb;
+
+ if (ctx->op != BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB)
+ return 1;
+
+ kctx = bpf_cast_to_kern_ctx(ctx);
+ skb = kctx->skb;
+ if (!skb)
+ return 1;
+
+ if (bpf_dynptr_from_skb_ext((struct __sk_buff *)skb, 0, 0, &meta))
+ return 1;
+ if (bpf_dynptr_read(meta_have, META_SIZE, &meta, 0, 0))
+ return 1;
+ if (!check_metadata(meta_have))
+ return 1;
+
+ test_pass = true;
+ return 1;
+}
+
+/* Read skb_ext from LSM inet_conn_established -- tests TC -> LSM path */
+SEC("lsm/inet_conn_established")
+int BPF_PROG(lsm_skb_ext_read, struct sock *sk, struct sk_buff *skb)
+{
+ __u8 meta_have[META_SIZE];
+ struct bpf_dynptr meta;
+
+ if (bpf_dynptr_from_skb_ext((struct __sk_buff *)skb, 0, 0, &meta))
+ return 0;
+ if (bpf_dynptr_read(meta_have, META_SIZE, &meta, 0, 0))
+ return 0;
+ if (!check_metadata(meta_have))
+ return 0;
+
+ test_pass = true;
+ return 0;
+}
+
/* Read skb_ext from socket filter -- tests TC -> sk_filter path */
SEC("socket")
int sk_filter_skb_ext_read(struct __sk_buff *ctx)
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 09/14] selftests/bpf: Test skb_ext read from kfree_skb tracepoint
2026-08-14 8:14 [PATCH net-next 00/14] skb extension for BPF metadata Jakub Sitnicki
` (7 preceding siblings ...)
2026-08-14 8:14 ` [PATCH net-next 08/14] selftests/bpf: Test skb_ext read from sock_ops and LSM hooks Jakub Sitnicki
@ 2026-08-14 8:14 ` Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 10/14] selftests/bpf: Test skb_ext read from netfilter hook Jakub Sitnicki
` (4 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jakub Sitnicki @ 2026-08-14 8:14 UTC (permalink / raw)
To: netdev, Alexei Starovoitov, Jakub Kicinski, Kuniyuki Iwashima,
Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
Write skb_ext at cgroup/skb egress, send UDP to a closed loopback port so
the packet is dropped, and read the extension back from a tp_btf/kfree_skb
program.
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
---
.../bpf/prog_tests/xdp_context_test_run.c | 65 ++++++++++++++++++++++
tools/testing/selftests/bpf/progs/test_xdp_meta.c | 34 +++++++++++
2 files changed, 99 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
index e0ab0fccf9d7..1e4e12d2b041 100644
--- a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
+++ b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
@@ -1186,6 +1186,69 @@ static void test_skb_ext_tcp(struct test_xdp_meta *skel, const char *name,
netns_free(ns);
}
+/* Test skb_ext survival until skb free: cgroup/skb egress -> kfree_skb.
+ * Send UDP to a closed port. The packet is dropped, triggering kfree_skb.
+ */
+static void test_cgrp_egress_to_kfree_skb(struct test_xdp_meta *skel)
+{
+ struct sockaddr_in addr = {
+ .sin_family = AF_INET,
+ .sin_port = htons(4321),
+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+ };
+ struct bpf_link *cg_link = NULL;
+ struct bpf_link *tp_link = NULL;
+ struct netns_obj *ns = NULL;
+ int cgroup_fd = -1;
+ char buf[1];
+ int fd = -1;
+ int ret;
+
+ cgroup_fd = test__join_cgroup("/cgrp_to_kfree");
+ if (!ASSERT_GE(cgroup_fd, 0, "join_cgroup"))
+ return;
+
+ ns = netns_new("cgrp_to_kfree", true);
+ if (!ASSERT_OK_PTR(ns, "netns_new"))
+ goto cleanup;
+
+ skel->bss->test_pass = false;
+
+ cg_link = bpf_program__attach_cgroup(skel->progs.cgrp_skb_ext_write,
+ cgroup_fd);
+ if (!ASSERT_OK_PTR(cg_link, "attach_cgroup"))
+ goto cleanup;
+
+ tp_link = bpf_program__attach_trace(skel->progs.tp_kfree_skb_ext_read);
+ if (!ASSERT_OK_PTR(tp_link, "attach_tp"))
+ goto cleanup;
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (!ASSERT_GE(fd, 0, "socket"))
+ goto cleanup;
+
+ ret = connect(fd, (void *)&addr, sizeof(addr));
+ if (!ASSERT_OK(ret, "connect"))
+ goto cleanup;
+
+ send(fd, test_payload, TEST_PAYLOAD_LEN, 0);
+
+ /* Wait for ICMP error -- confirms the packet was freed */
+ ret = recv(fd, buf, sizeof(buf), 0);
+ ASSERT_EQ(errno, ECONNREFUSED, "recv_econnrefused");
+
+ ASSERT_TRUE(skel->bss->test_pass, "test_pass");
+
+cleanup:
+ if (fd >= 0)
+ close(fd);
+ bpf_link__destroy(tp_link);
+ bpf_link__destroy(cg_link);
+ netns_free(ns);
+ if (cgroup_fd >= 0)
+ close(cgroup_fd);
+}
+
void test_skb_ext_cross_hook(void)
{
struct test_xdp_meta *skel = NULL;
@@ -1202,6 +1265,8 @@ void test_skb_ext_cross_hook(void)
test_skb_ext_tcp(skel, "tc_to_lsm", READER_LSM);
if (test__start_subtest("tc_to_skops"))
test_skb_ext_tcp(skel, "tc_to_skops", READER_SKOPS);
+ if (test__start_subtest("cgrp_egress_to_kfree_skb"))
+ test_cgrp_egress_to_kfree_skb(skel);
test_xdp_meta__destroy(skel);
}
diff --git a/tools/testing/selftests/bpf/progs/test_xdp_meta.c b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
index 8f0a0fea5d84..415b33b32ef5 100644
--- a/tools/testing/selftests/bpf/progs/test_xdp_meta.c
+++ b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
@@ -1032,4 +1032,38 @@ int sk_filter_skb_ext_read(struct __sk_buff *ctx)
return ctx->len;
}
+/* Write skb_ext from cgroup/skb egress */
+SEC("cgroup_skb/egress")
+int cgrp_skb_ext_write(struct __sk_buff *ctx)
+{
+ struct bpf_dynptr meta;
+
+ if (!is_test_packet_tc(ctx))
+ return 1;
+
+ if (bpf_dynptr_from_skb_ext(ctx, 0, BPF_SKB_EXT_F_CREATE, &meta))
+ return 1;
+ bpf_dynptr_write(&meta, 0, (void *)meta_want, META_SIZE, 0);
+
+ return 1;
+}
+
+/* Read skb_ext from tp_btf/kfree_skb -- tests survival until skb free */
+SEC("tp_btf/kfree_skb")
+int BPF_PROG(tp_kfree_skb_ext_read, struct sk_buff *skb)
+{
+ __u8 meta_have[META_SIZE];
+ struct bpf_dynptr meta;
+
+ if (bpf_dynptr_from_skb_ext((struct __sk_buff *)skb, 0, 0, &meta))
+ return 0;
+ if (bpf_dynptr_read(meta_have, META_SIZE, &meta, 0, 0))
+ return 0;
+ if (!check_metadata(meta_have))
+ return 0;
+
+ test_pass = true;
+ return 0;
+}
+
char _license[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 10/14] selftests/bpf: Test skb_ext read from netfilter hook
2026-08-14 8:14 [PATCH net-next 00/14] skb extension for BPF metadata Jakub Sitnicki
` (8 preceding siblings ...)
2026-08-14 8:14 ` [PATCH net-next 09/14] selftests/bpf: Test skb_ext read from kfree_skb tracepoint Jakub Sitnicki
@ 2026-08-14 8:14 ` Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 11/14] selftests/bpf: Test skb_ext from LWT in, out, and xmit hooks Jakub Sitnicki
` (3 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jakub Sitnicki @ 2026-08-14 8:14 UTC (permalink / raw)
To: netdev, Alexei Starovoitov, Jakub Kicinski, Kuniyuki Iwashima,
Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
Add a TC -> netfilter cross-hook test for bpf_dynptr_from_skb_ext(). A TC
ingress program writes skb_ext on loopback, and a netfilter program at
NF_INET_LOCAL_IN reads it back.
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
---
.../bpf/prog_tests/xdp_context_test_run.c | 56 ++++++++++++++++++++++
tools/testing/selftests/bpf/progs/test_xdp_meta.c | 23 +++++++++
2 files changed, 79 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
index 1e4e12d2b041..82c57ea3957e 100644
--- a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
+++ b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
@@ -2,6 +2,7 @@
#include <test_progs.h>
#include <network_helpers.h>
#include <linux/ipv6.h>
+#include <linux/netfilter.h>
#include <arpa/inet.h>
#include "test_xdp_context_test_run.skel.h"
#include "test_xdp_meta.skel.h"
@@ -1249,6 +1250,59 @@ static void test_cgrp_egress_to_kfree_skb(struct test_xdp_meta *skel)
close(cgroup_fd);
}
+/* Test skb_ext survival across TC ingress -> netfilter hook */
+static void test_skb_ext_nf(struct test_xdp_meta *skel, const char *name)
+{
+ LIBBPF_OPTS(bpf_tc_hook, tc_hook,
+ .ifindex = 1 /* IFINDEX_LO */,
+ .attach_point = BPF_TC_INGRESS);
+ LIBBPF_OPTS(bpf_tc_opts, tc_opts, .handle = 1, .priority = 1);
+ LIBBPF_OPTS(bpf_netfilter_opts, nf_opts,
+ .pf = NFPROTO_IPV4,
+ .hooknum = NF_INET_LOCAL_IN,
+ .priority = 1);
+ struct bpf_link *reader_link = NULL;
+ struct netns_obj *ns = NULL;
+ int server_fd = -1;
+ int ret;
+
+ ns = netns_new(name, true);
+ if (!ASSERT_OK_PTR(ns, "netns_new"))
+ return;
+
+ server_fd = start_server(AF_INET, SOCK_DGRAM, "127.0.0.1", 0, 0);
+ if (!ASSERT_GE(server_fd, 0, "start_server"))
+ goto cleanup;
+
+ skel->bss->test_pass = false;
+
+ ret = bpf_tc_hook_create(&tc_hook);
+ if (!ASSERT_OK(ret, "bpf_tc_hook_create"))
+ goto cleanup;
+
+ tc_opts.prog_fd = bpf_program__fd(skel->progs.tc_skb_ext_write);
+ ret = bpf_tc_attach(&tc_hook, &tc_opts);
+ if (!ASSERT_OK(ret, "bpf_tc_attach"))
+ goto cleanup;
+
+ reader_link = bpf_program__attach_netfilter(skel->progs.nf_skb_ext_read,
+ &nf_opts);
+ if (!ASSERT_OK_PTR(reader_link, "attach_nf"))
+ goto cleanup;
+
+ if (send_loopback_udp(server_fd))
+ goto cleanup;
+
+ ASSERT_TRUE(skel->bss->test_pass, "test_pass");
+
+cleanup:
+ bpf_link__destroy(reader_link);
+ bpf_tc_hook_destroy(&tc_hook);
+ if (server_fd >= 0)
+ close(server_fd);
+ netns_free(ns);
+}
+
void test_skb_ext_cross_hook(void)
{
struct test_xdp_meta *skel = NULL;
@@ -1267,6 +1321,8 @@ void test_skb_ext_cross_hook(void)
test_skb_ext_tcp(skel, "tc_to_skops", READER_SKOPS);
if (test__start_subtest("cgrp_egress_to_kfree_skb"))
test_cgrp_egress_to_kfree_skb(skel);
+ if (test__start_subtest("tc_to_nf"))
+ test_skb_ext_nf(skel, "tc_to_nf");
test_xdp_meta__destroy(skel);
}
diff --git a/tools/testing/selftests/bpf/progs/test_xdp_meta.c b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
index 415b33b32ef5..0688120f3d40 100644
--- a/tools/testing/selftests/bpf/progs/test_xdp_meta.c
+++ b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
@@ -1066,4 +1066,27 @@ int BPF_PROG(tp_kfree_skb_ext_read, struct sk_buff *skb)
return 0;
}
+#define NF_ACCEPT 1
+
+SEC("netfilter")
+int nf_skb_ext_read(struct bpf_nf_ctx *ctx)
+{
+ struct __sk_buff *skb = (struct __sk_buff *)ctx->skb;
+ __u8 meta_have[META_SIZE];
+ struct bpf_dynptr meta;
+
+ if (!skb)
+ return NF_ACCEPT;
+
+ if (bpf_dynptr_from_skb_ext(skb, 0, 0, &meta))
+ return NF_ACCEPT;
+ if (bpf_dynptr_read(meta_have, META_SIZE, &meta, 0, 0))
+ return NF_ACCEPT;
+ if (!check_metadata(meta_have))
+ return NF_ACCEPT;
+
+ test_pass = true;
+ return NF_ACCEPT;
+}
+
char _license[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 11/14] selftests/bpf: Test skb_ext from LWT in, out, and xmit hooks
2026-08-14 8:14 [PATCH net-next 00/14] skb extension for BPF metadata Jakub Sitnicki
` (9 preceding siblings ...)
2026-08-14 8:14 ` [PATCH net-next 10/14] selftests/bpf: Test skb_ext read from netfilter hook Jakub Sitnicki
@ 2026-08-14 8:14 ` Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 12/14] selftests/bpf: Test skb_ext read from seg6local End.BPF hook Jakub Sitnicki
` (2 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Jakub Sitnicki @ 2026-08-14 8:14 UTC (permalink / raw)
To: netdev, Alexei Starovoitov, Jakub Kicinski, Kuniyuki Iwashima,
Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
Add cross-hook tests for bpf_dynptr_from_skb_ext() covering all
generic LWT program types. For lwt_in, a TC ingress program writes
skb_ext on loopback and the LWT program reads it back. For lwt_out
and lwt_xmit, the LWT programs write skb_ext on the output path and a
TC ingress program reads it back after loopback.
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
---
.../bpf/prog_tests/xdp_context_test_run.c | 98 ++++++++++++++++++++++
tools/testing/selftests/bpf/progs/test_xdp_meta.c | 45 ++++++++++
2 files changed, 143 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
index 82c57ea3957e..7a7537ef80e7 100644
--- a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
+++ b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
@@ -1303,6 +1303,95 @@ static void test_skb_ext_nf(struct test_xdp_meta *skel, const char *name)
netns_free(ns);
}
+#define LWT_EXT_PIN_PATH "/sys/fs/bpf/skb_ext_lwt"
+
+/* Test skb_ext across LWT hooks on loopback.
+ *
+ * @lwt_prog: BPF program to pin and attach via ip route encap
+ * @encap_dir: "in", "out", or "xmit"
+ * @writer: true if lwt_prog writes skb_ext (reader on TC ingress),
+ * false if lwt_prog reads skb_ext (writer on TC ingress)
+ */
+static void test_skb_ext_lwt(struct test_xdp_meta *skel, const char *name,
+ struct bpf_program *lwt_prog,
+ const char *encap_dir, bool writer)
+{
+ LIBBPF_OPTS(bpf_tc_hook, tc_hook,
+ .ifindex = 1 /* IFINDEX_LO */,
+ .attach_point = BPF_TC_INGRESS);
+ LIBBPF_OPTS(bpf_tc_opts, tc_opts, .handle = 1, .priority = 1);
+ struct sockaddr_in addr = {
+ .sin_family = AF_INET,
+ };
+ struct bpf_program *tc_prog;
+ char buf[TEST_PAYLOAD_LEN];
+ struct netns_obj *ns = NULL;
+ bool pinned = false;
+ int server_fd = -1;
+ int fd = -1;
+ __be16 port;
+ int ret;
+
+ unlink(LWT_EXT_PIN_PATH);
+ ret = bpf_program__pin(lwt_prog, LWT_EXT_PIN_PATH);
+ if (!ASSERT_OK(ret, "pin lwt"))
+ return;
+ pinned = true;
+
+ ns = netns_new(name, true);
+ if (!ASSERT_OK_PTR(ns, "netns_new"))
+ goto cleanup;
+
+ SYS(cleanup, "ip addr add 10.0.0.1/32 dev lo");
+ SYS(cleanup, "ip route replace table local local 10.0.0.1 "
+ "encap bpf %s pinned " LWT_EXT_PIN_PATH " dev lo",
+ encap_dir);
+
+ server_fd = start_server(AF_INET, SOCK_DGRAM, "10.0.0.1", 0, 0);
+ if (!ASSERT_GE(server_fd, 0, "start_server"))
+ goto cleanup;
+
+ skel->bss->test_pass = false;
+
+ ret = bpf_tc_hook_create(&tc_hook);
+ if (!ASSERT_OK(ret, "bpf_tc_hook_create"))
+ goto cleanup;
+
+ /* When LWT writes, TC ingress reads; when LWT reads, TC ingress writes */
+ tc_prog = writer ? skel->progs.tc_skb_ext_read
+ : skel->progs.tc_skb_ext_write;
+ tc_opts.prog_fd = bpf_program__fd(tc_prog);
+ ret = bpf_tc_attach(&tc_hook, &tc_opts);
+ if (!ASSERT_OK(ret, "bpf_tc_attach"))
+ goto cleanup;
+
+ port = get_socket_local_port(server_fd);
+ if (!ASSERT_GE(port, 0, "get_port"))
+ goto cleanup;
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (!ASSERT_GE(fd, 0, "socket"))
+ goto cleanup;
+
+ inet_pton(AF_INET, "10.0.0.1", &addr.sin_addr);
+ addr.sin_port = port;
+ sendto(fd, test_payload, TEST_PAYLOAD_LEN, 0,
+ (void *)&addr, sizeof(addr));
+ recvfrom(server_fd, buf, sizeof(buf), 0, NULL, NULL);
+
+ ASSERT_TRUE(skel->bss->test_pass, "test_pass");
+
+cleanup:
+ if (fd >= 0)
+ close(fd);
+ bpf_tc_hook_destroy(&tc_hook);
+ if (server_fd >= 0)
+ close(server_fd);
+ netns_free(ns);
+ if (pinned)
+ unlink(LWT_EXT_PIN_PATH);
+}
+
void test_skb_ext_cross_hook(void)
{
struct test_xdp_meta *skel = NULL;
@@ -1323,6 +1412,15 @@ void test_skb_ext_cross_hook(void)
test_cgrp_egress_to_kfree_skb(skel);
if (test__start_subtest("tc_to_nf"))
test_skb_ext_nf(skel, "tc_to_nf");
+ if (test__start_subtest("tc_to_lwt_in"))
+ test_skb_ext_lwt(skel, "tc_to_lwt_in",
+ skel->progs.lwt_in_skb_ext_read, "in", false);
+ if (test__start_subtest("lwt_out_to_tc"))
+ test_skb_ext_lwt(skel, "lwt_out_to_tc",
+ skel->progs.lwt_out_skb_ext_write, "out", true);
+ if (test__start_subtest("lwt_xmit_to_tc"))
+ test_skb_ext_lwt(skel, "lwt_xmit_to_tc",
+ skel->progs.lwt_xmit_skb_ext_write, "xmit", true);
test_xdp_meta__destroy(skel);
}
diff --git a/tools/testing/selftests/bpf/progs/test_xdp_meta.c b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
index 0688120f3d40..97c1760fbd6d 100644
--- a/tools/testing/selftests/bpf/progs/test_xdp_meta.c
+++ b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
@@ -1089,4 +1089,49 @@ int nf_skb_ext_read(struct bpf_nf_ctx *ctx)
return NF_ACCEPT;
}
+SEC("lwt_in")
+int lwt_in_skb_ext_read(struct __sk_buff *ctx)
+{
+ __u8 meta_have[META_SIZE];
+ struct bpf_dynptr meta;
+
+ if (bpf_dynptr_from_skb_ext(ctx, 0, 0, &meta))
+ return BPF_OK;
+ if (bpf_dynptr_read(meta_have, META_SIZE, &meta, 0, 0))
+ return BPF_OK;
+ if (!check_metadata(meta_have))
+ return BPF_OK;
+
+ test_pass = true;
+ return BPF_OK;
+}
+
+SEC("lwt_out")
+int lwt_out_skb_ext_write(struct __sk_buff *ctx)
+{
+ struct bpf_dynptr meta;
+
+ if (!is_test_packet_tc(ctx))
+ return BPF_OK;
+ if (bpf_dynptr_from_skb_ext(ctx, 0, BPF_SKB_EXT_F_CREATE, &meta))
+ return BPF_OK;
+ bpf_dynptr_write(&meta, 0, (void *)meta_want, ARRAY_SIZE(meta_want), 0);
+
+ return BPF_OK;
+}
+
+SEC("lwt_xmit")
+int lwt_xmit_skb_ext_write(struct __sk_buff *ctx)
+{
+ struct bpf_dynptr meta;
+
+ if (!is_test_packet_tc(ctx))
+ return BPF_OK;
+ if (bpf_dynptr_from_skb_ext(ctx, 0, BPF_SKB_EXT_F_CREATE, &meta))
+ return BPF_OK;
+ bpf_dynptr_write(&meta, 0, (void *)meta_want, ARRAY_SIZE(meta_want), 0);
+
+ return BPF_OK;
+}
+
char _license[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 12/14] selftests/bpf: Test skb_ext read from seg6local End.BPF hook
2026-08-14 8:14 [PATCH net-next 00/14] skb extension for BPF metadata Jakub Sitnicki
` (10 preceding siblings ...)
2026-08-14 8:14 ` [PATCH net-next 11/14] selftests/bpf: Test skb_ext from LWT in, out, and xmit hooks Jakub Sitnicki
@ 2026-08-14 8:14 ` Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 13/14] selftests/bpf: Test skb_ext read from sk_skb stream verdict hook Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 14/14] selftests/bpf: Use non-trivial test payload in xdp_context tests Jakub Sitnicki
13 siblings, 0 replies; 16+ messages in thread
From: Jakub Sitnicki @ 2026-08-14 8:14 UTC (permalink / raw)
To: netdev, Alexei Starovoitov, Jakub Kicinski, Kuniyuki Iwashima,
Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
Test bpf_dynptr_from_skb_ext() from lwt_seg6local context:
- TC writes skb_ext on the encapped packet leaving NS1,
- NS2 runs End.BPF for fd01::1 which reads it back,
- NS3 receives the packet and decapsulates it.
Note that End.BPF cannot deliver locally since seg6_lookup_nexthop rejects
loopback routes, hence a three netns setup is necessary.
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
---
.../bpf/prog_tests/xdp_context_test_run.c | 170 +++++++++++++++++++++
tools/testing/selftests/bpf/progs/test_xdp_meta.c | 18 +++
2 files changed, 188 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
index 7a7537ef80e7..5f9247941aa0 100644
--- a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
+++ b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
@@ -1392,6 +1392,174 @@ static void test_skb_ext_lwt(struct test_xdp_meta *skel, const char *name,
unlink(LWT_EXT_PIN_PATH);
}
+#define SEG6_PIN_PATH "/sys/fs/bpf/skb_ext_seg6local"
+
+/*
+ * Test skb_ext survival across TC egress -> seg6local End.BPF hook.
+ *
+ * Topology:
+ *
+ * NS1 NS2 NS3
+ * lo veth1 <-> veth2 veth3 <-> veth4 lo
+ *
+ * NS1 encaps fb01::2 with SRH (segments fd01::1,fd01::2) on output and
+ * writes skb_ext at TC egress on veth1. NS2 runs End.BPF for fd01::1,
+ * which reads skb_ext, then forwards to NS3 (seg6_lookup_nexthop
+ * rejects loopback routes with local_delivery=false, so End.BPF
+ * cannot deliver locally). NS3 has fd01::2 local, decapsulates, and
+ * delivers the inner UDP (dst=fb01::2) to a local socket for test
+ * synchronization.
+ *
+ * The TC program drops non-test packets, so NDP is bypassed with a
+ * static neighbor entry on the NS1->NS2 hop.
+ */
+static void test_skb_ext_seg6local(struct test_xdp_meta *skel)
+{
+ LIBBPF_OPTS(bpf_tc_hook, tc_hook, .attach_point = BPF_TC_EGRESS);
+ LIBBPF_OPTS(bpf_tc_opts, tc_opts, .handle = 1, .priority = 1);
+ struct nstoken *nstoken = NULL;
+ struct sockaddr_in6 dst = {};
+ char buf[TEST_PAYLOAD_LEN];
+ bool pinned = false;
+ int client_fd = -1;
+ int server_fd = -1;
+ ssize_t bytes;
+ int ret;
+
+ unlink(SEG6_PIN_PATH);
+ ret = bpf_program__pin(skel->progs.seg6local_skb_ext_read,
+ SEG6_PIN_PATH);
+ if (!ASSERT_OK(ret, "pin seg6local"))
+ return;
+ pinned = true;
+
+ skel->bss->test_pass = false;
+
+ SYS(cleanup, "ip netns add seg6_1");
+ SYS(cleanup, "ip netns add seg6_2");
+ SYS(cleanup, "ip netns add seg6_3");
+
+ /* NS1 <-> NS2 veth pair; static NDP since TC drops NS/NA */
+ SYS(cleanup, "ip -n seg6_1 link add veth1 type veth peer name veth2 netns seg6_2");
+ SYS(cleanup, "ip -n seg6_1 link set dev veth1 up");
+ SYS(cleanup, "ip -n seg6_2 link set dev veth2 address 02:00:00:00:00:02");
+ SYS(cleanup, "ip -n seg6_2 link set dev veth2 up");
+ SYS(cleanup, "ip -n seg6_1 -6 addr add fb00::12/16 dev veth1 scope link nodad");
+ SYS(cleanup, "ip -n seg6_2 -6 addr add fb00::21/16 dev veth2 scope link nodad");
+ SYS(cleanup, "ip -n seg6_1 -6 neigh add fb00::21 lladdr 02:00:00:00:00:02 "
+ "nud permanent dev veth1");
+
+ /* NS2 <-> NS3 veth pair */
+ SYS(cleanup, "ip -n seg6_2 link add veth3 type veth peer name veth4 netns seg6_3");
+ SYS(cleanup, "ip -n seg6_2 link set dev veth3 up");
+ SYS(cleanup, "ip -n seg6_3 link set dev veth4 up");
+ SYS(cleanup, "ip -n seg6_2 -6 addr add fb00::34/16 dev veth3 scope link nodad");
+ SYS(cleanup, "ip -n seg6_3 -6 addr add fb00::43/16 dev veth4 scope link nodad");
+
+ /* NS1: source address + SRH encap on output + route for fd01::/16 */
+ SYS(cleanup, "ip -n seg6_1 link set dev lo up");
+ SYS(cleanup, "ip -n seg6_1 -6 addr add fb01::1/128 dev lo nodad");
+ SYS(cleanup, "ip -n seg6_1 -6 route add fb01::2 encap seg6 mode encap "
+ "segs fd01::1,fd01::2 dev veth1 via fb00::21");
+ SYS(cleanup, "ip -n seg6_1 -6 route add fd01::/16 dev veth1 via fb00::21");
+
+ /* NS2: seg6local End.BPF for fd01::1, then forward to NS3.
+ * Use open_netns so the pinned prog on bpffs is accessible.
+ */
+ nstoken = open_netns("seg6_2");
+ if (!ASSERT_OK_PTR(nstoken, "open seg6_2"))
+ goto cleanup;
+
+ SYS(cleanup, "ip link set dev lo up");
+ SYS(cleanup, "sysctl -wq net.ipv6.conf.all.forwarding=1");
+ SYS(cleanup, "ip -6 route add fd01::2/128 dev veth3 via fb00::43");
+ SYS(cleanup, "ip -6 route add fd01::1 encap seg6local "
+ "action End.BPF endpoint pinned " SEG6_PIN_PATH " dev veth2");
+
+ close_netns(nstoken);
+ nstoken = NULL;
+
+ /* NS3: decap point + UDP server.
+ * fd01::2 local triggers SRH decap (segments_left=0).
+ * fb01::2 local delivers the inner UDP to the socket.
+ */
+ nstoken = open_netns("seg6_3");
+ if (!ASSERT_OK_PTR(nstoken, "open seg6_3"))
+ goto cleanup;
+
+ SYS(cleanup, "ip link set dev lo up");
+ SYS(cleanup, "sysctl -wq net.ipv6.conf.all.seg6_enabled=1");
+ SYS(cleanup, "sysctl -wq net.ipv6.conf.lo.seg6_enabled=1");
+ SYS(cleanup, "sysctl -wq net.ipv6.conf.veth4.seg6_enabled=1");
+ SYS(cleanup, "ip -6 addr add fd01::2/128 dev lo nodad");
+ SYS(cleanup, "ip -6 addr add fb01::2/128 dev lo nodad");
+
+ server_fd = start_server(AF_INET6, SOCK_DGRAM, "fb01::2", 1234, 0);
+ if (!ASSERT_GE(server_fd, 0, "start_server"))
+ goto cleanup;
+
+ close_netns(nstoken);
+ nstoken = NULL;
+
+ /* NS1: write skb_ext at TC egress on veth1, then send.
+ * TC egress fires after seg6 encap, on the encapped packet.
+ */
+ nstoken = open_netns("seg6_1");
+ if (!ASSERT_OK_PTR(nstoken, "open seg6_1"))
+ goto cleanup;
+
+ tc_hook.ifindex = if_nametoindex("veth1");
+ if (!ASSERT_GE(tc_hook.ifindex, 0, "ifindex veth1"))
+ goto cleanup;
+
+ ret = bpf_tc_hook_create(&tc_hook);
+ if (!ASSERT_OK(ret, "bpf_tc_hook_create"))
+ goto cleanup;
+
+ tc_opts.prog_fd = bpf_program__fd(skel->progs.tc_skb_ext_write);
+ ret = bpf_tc_attach(&tc_hook, &tc_opts);
+ if (!ASSERT_OK(ret, "bpf_tc_attach"))
+ goto cleanup;
+
+ client_fd = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (!ASSERT_GE(client_fd, 0, "socket"))
+ goto cleanup;
+
+ dst.sin6_family = AF_INET6;
+ dst.sin6_port = htons(1234);
+ inet_pton(AF_INET6, "fb01::2", &dst.sin6_addr);
+
+ bytes = sendto(client_fd, test_payload, TEST_PAYLOAD_LEN, 0,
+ (void *)&dst, sizeof(dst));
+
+ close_netns(nstoken);
+ nstoken = NULL;
+
+ if (!ASSERT_EQ(bytes, TEST_PAYLOAD_LEN, "sendto"))
+ goto cleanup;
+
+ /* Receive decapsulated packet -- synchronizes with seg6local */
+ nstoken = open_netns("seg6_3");
+ if (!ASSERT_OK_PTR(nstoken, "open seg6_3"))
+ goto cleanup;
+
+ bytes = recvfrom(server_fd, buf, sizeof(buf), 0, NULL, NULL);
+ ASSERT_EQ(bytes, TEST_PAYLOAD_LEN, "recvfrom");
+ ASSERT_TRUE(skel->bss->test_pass, "test_pass");
+
+cleanup:
+ close_netns(nstoken);
+ if (server_fd >= 0)
+ close(server_fd);
+ if (client_fd >= 0)
+ close(client_fd);
+ SYS_NOFAIL("ip netns del seg6_3");
+ SYS_NOFAIL("ip netns del seg6_2");
+ SYS_NOFAIL("ip netns del seg6_1");
+ if (pinned)
+ unlink(SEG6_PIN_PATH);
+}
+
void test_skb_ext_cross_hook(void)
{
struct test_xdp_meta *skel = NULL;
@@ -1421,6 +1589,8 @@ void test_skb_ext_cross_hook(void)
if (test__start_subtest("lwt_xmit_to_tc"))
test_skb_ext_lwt(skel, "lwt_xmit_to_tc",
skel->progs.lwt_xmit_skb_ext_write, "xmit", true);
+ if (test__start_subtest("tc_to_seg6local"))
+ test_skb_ext_seg6local(skel);
test_xdp_meta__destroy(skel);
}
diff --git a/tools/testing/selftests/bpf/progs/test_xdp_meta.c b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
index 97c1760fbd6d..c4ea38d79533 100644
--- a/tools/testing/selftests/bpf/progs/test_xdp_meta.c
+++ b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
@@ -1134,4 +1134,22 @@ int lwt_xmit_skb_ext_write(struct __sk_buff *ctx)
return BPF_OK;
}
+/* Read skb_ext from seg6local End.BPF -- tests TC -> seg6local path */
+SEC("lwt_seg6local")
+int seg6local_skb_ext_read(struct __sk_buff *ctx)
+{
+ __u8 meta_have[META_SIZE];
+ struct bpf_dynptr meta;
+
+ if (bpf_dynptr_from_skb_ext(ctx, 0, 0, &meta))
+ return BPF_OK;
+ if (bpf_dynptr_read(meta_have, META_SIZE, &meta, 0, 0))
+ return BPF_OK;
+ if (!check_metadata(meta_have))
+ return BPF_OK;
+
+ test_pass = true;
+ return BPF_OK;
+}
+
char _license[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 13/14] selftests/bpf: Test skb_ext read from sk_skb stream verdict hook
2026-08-14 8:14 [PATCH net-next 00/14] skb extension for BPF metadata Jakub Sitnicki
` (11 preceding siblings ...)
2026-08-14 8:14 ` [PATCH net-next 12/14] selftests/bpf: Test skb_ext read from seg6local End.BPF hook Jakub Sitnicki
@ 2026-08-14 8:14 ` Jakub Sitnicki
2026-08-16 14:11 ` Jack Wang
2026-08-14 8:14 ` [PATCH net-next 14/14] selftests/bpf: Use non-trivial test payload in xdp_context tests Jakub Sitnicki
13 siblings, 1 reply; 16+ messages in thread
From: Jakub Sitnicki @ 2026-08-14 8:14 UTC (permalink / raw)
To: netdev, Alexei Starovoitov, Jakub Kicinski, Kuniyuki Iwashima,
Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
Test bpf_dynptr_from_skb_ext() from sk_skb context. Write to skb_ext from
TC on loopback; read from skb_ext from the sockmap verdict program.
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
---
.../selftests/bpf/prog_tests/socket_helpers.h | 1 +
.../bpf/prog_tests/xdp_context_test_run.c | 79 ++++++++++++++++++++++
tools/testing/selftests/bpf/progs/test_xdp_meta.c | 18 +++++
3 files changed, 98 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/socket_helpers.h b/tools/testing/selftests/bpf/prog_tests/socket_helpers.h
index 0d59503a0c73..eb114a6ea6f9 100644
--- a/tools/testing/selftests/bpf/prog_tests/socket_helpers.h
+++ b/tools/testing/selftests/bpf/prog_tests/socket_helpers.h
@@ -3,6 +3,7 @@
#ifndef __SOCKET_HELPERS__
#define __SOCKET_HELPERS__
+#include <error.h>
#include <sys/un.h>
#include <linux/vm_sockets.h>
diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
index 5f9247941aa0..f5fbc19757e3 100644
--- a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
+++ b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
@@ -4,6 +4,7 @@
#include <linux/ipv6.h>
#include <linux/netfilter.h>
#include <arpa/inet.h>
+#include "socket_helpers.h"
#include "test_xdp_context_test_run.skel.h"
#include "test_xdp_meta.skel.h"
@@ -1560,6 +1561,82 @@ static void test_skb_ext_seg6local(struct test_xdp_meta *skel)
unlink(SEG6_PIN_PATH);
}
+/* Test skb_ext survival across TC ingress -> sk_skb verdict hook.
+ * TC ingress writes skb_ext on loopback; the verdict program reads it
+ * on skb delivery to a socket in the sockmap.
+ */
+static void test_skb_ext_sk_skb(struct test_xdp_meta *skel)
+{
+ LIBBPF_OPTS(bpf_tc_hook, tc_hook,
+ .ifindex = 1 /* IFINDEX_LO */,
+ .attach_point = BPF_TC_INGRESS);
+ LIBBPF_OPTS(bpf_tc_opts, tc_opts, .handle = 1, .priority = 1);
+ struct bpf_link *verdict_link = NULL;
+ struct sockaddr_in addr;
+ socklen_t addr_len = sizeof(addr);
+ struct netns_obj *ns = NULL;
+ char buf[TEST_PAYLOAD_LEN];
+ int c1 = -1, p1 = -1;
+ int map = -1, zero = 0;
+ ssize_t n;
+ int ret;
+
+ ns = netns_new("tc_to_sk_skb", true);
+ if (!ASSERT_OK_PTR(ns, "netns_new"))
+ return;
+
+ skel->bss->test_pass = false;
+
+ ret = bpf_tc_hook_create(&tc_hook);
+ if (!ASSERT_OK(ret, "bpf_tc_hook_create"))
+ goto cleanup;
+
+ tc_opts.prog_fd = bpf_program__fd(skel->progs.tc_skb_ext_write_port);
+ ret = bpf_tc_attach(&tc_hook, &tc_opts);
+ if (!ASSERT_OK(ret, "bpf_tc_attach"))
+ goto cleanup;
+
+ map = bpf_map_create(BPF_MAP_TYPE_SOCKMAP, NULL, sizeof(int),
+ sizeof(int), 1, NULL);
+ if (!ASSERT_GE(map, 0, "bpf_map_create"))
+ goto cleanup;
+
+ verdict_link = bpf_program__attach_sockmap(skel->progs.sk_skb_skb_ext_read,
+ map);
+ if (!ASSERT_OK_PTR(verdict_link, "attach_sockmap"))
+ goto cleanup;
+
+ if (!ASSERT_OK(create_pair(AF_INET, SOCK_STREAM, &c1, &p1), "create_pair"))
+ goto cleanup;
+
+ if (!ASSERT_OK(bpf_map_update_elem(map, &zero, &c1, BPF_NOEXIST), "map_update"))
+ goto cleanup;
+
+ if (!ASSERT_OK(getsockname(c1, (struct sockaddr *)&addr, &addr_len),
+ "getsockname"))
+ goto cleanup;
+ skel->bss->target_port = addr.sin_port;
+
+ n = send(p1, test_payload, TEST_PAYLOAD_LEN, 0);
+ if (!ASSERT_EQ(n, TEST_PAYLOAD_LEN, "send"))
+ goto cleanup;
+
+ n = recv(c1, buf, sizeof(buf), 0);
+ ASSERT_EQ(n, TEST_PAYLOAD_LEN, "recv");
+ ASSERT_TRUE(skel->bss->test_pass, "test_pass");
+
+cleanup:
+ if (c1 >= 0)
+ close(c1);
+ if (p1 >= 0)
+ close(p1);
+ bpf_link__destroy(verdict_link);
+ if (map >= 0)
+ close(map);
+ bpf_tc_hook_destroy(&tc_hook);
+ netns_free(ns);
+}
+
void test_skb_ext_cross_hook(void)
{
struct test_xdp_meta *skel = NULL;
@@ -1591,6 +1668,8 @@ void test_skb_ext_cross_hook(void)
skel->progs.lwt_xmit_skb_ext_write, "xmit", true);
if (test__start_subtest("tc_to_seg6local"))
test_skb_ext_seg6local(skel);
+ if (test__start_subtest("tc_to_sk_skb"))
+ test_skb_ext_sk_skb(skel);
test_xdp_meta__destroy(skel);
}
diff --git a/tools/testing/selftests/bpf/progs/test_xdp_meta.c b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
index c4ea38d79533..4c224d5fe1b2 100644
--- a/tools/testing/selftests/bpf/progs/test_xdp_meta.c
+++ b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
@@ -1152,4 +1152,22 @@ int seg6local_skb_ext_read(struct __sk_buff *ctx)
return BPF_OK;
}
+/* Read skb_ext from sk_skb stream verdict hook */
+SEC("sk_skb/stream_verdict")
+int sk_skb_skb_ext_read(struct __sk_buff *ctx)
+{
+ __u8 meta_have[META_SIZE];
+ struct bpf_dynptr meta;
+
+ if (bpf_dynptr_from_skb_ext(ctx, 0, 0, &meta))
+ return SK_PASS;
+ if (bpf_dynptr_read(meta_have, META_SIZE, &meta, 0, 0))
+ return SK_PASS;
+ if (!check_metadata(meta_have))
+ return SK_PASS;
+
+ test_pass = true;
+ return SK_PASS;
+}
+
char _license[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH net-next 14/14] selftests/bpf: Use non-trivial test payload in xdp_context tests
2026-08-14 8:14 [PATCH net-next 00/14] skb extension for BPF metadata Jakub Sitnicki
` (12 preceding siblings ...)
2026-08-14 8:14 ` [PATCH net-next 13/14] selftests/bpf: Test skb_ext read from sk_skb stream verdict hook Jakub Sitnicki
@ 2026-08-14 8:14 ` Jakub Sitnicki
13 siblings, 0 replies; 16+ messages in thread
From: Jakub Sitnicki @ 2026-08-14 8:14 UTC (permalink / raw)
To: netdev, Alexei Starovoitov, Jakub Kicinski, Kuniyuki Iwashima,
Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
Replace the sequential test payload pattern (0x01..0x38) with
higher-entropy values that don't alias common protocol header fields
such as IP version/IHL or ethertype LSBs. This reduces the chance of
false positives from stray packets.
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
---
tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c | 8 ++++----
tools/testing/selftests/bpf/progs/test_xdp_meta.c | 8 ++++----
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
index f5fbc19757e3..8bf586b5af32 100644
--- a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
+++ b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
@@ -21,10 +21,10 @@
#define TEST_PAYLOAD_LEN 32
static const __u8 test_payload[TEST_PAYLOAD_LEN] = {
- 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
- 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
- 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
- 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
+ 0x49, 0x20, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x65,
+ 0x64, 0x20, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x20,
+ 0x26, 0x20, 0x6c, 0x69, 0x76, 0x65, 0x64, 0x20,
+ 0x74, 0x6f, 0x20, 0x74, 0x65, 0x6c, 0x6c, 0x21,
};
void test_xdp_context_error(int prog_fd, struct bpf_test_run_opts opts,
diff --git a/tools/testing/selftests/bpf/progs/test_xdp_meta.c b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
index 4c224d5fe1b2..b0ab7f4f7a1b 100644
--- a/tools/testing/selftests/bpf/progs/test_xdp_meta.c
+++ b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
@@ -24,10 +24,10 @@
bool test_pass;
static const __u8 meta_want[META_SIZE] = {
- 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
- 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
- 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
- 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
+ 0x49, 0x20, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x65,
+ 0x64, 0x20, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x20,
+ 0x26, 0x20, 0x6c, 0x69, 0x76, 0x65, 0x64, 0x20,
+ 0x74, 0x6f, 0x20, 0x74, 0x65, 0x6c, 0x6c, 0x21,
};
static bool check_metadata(const char *file, int line, __u8 *meta_have)
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH net-next 13/14] selftests/bpf: Test skb_ext read from sk_skb stream verdict hook
2026-08-14 8:14 ` [PATCH net-next 13/14] selftests/bpf: Test skb_ext read from sk_skb stream verdict hook Jakub Sitnicki
@ 2026-08-16 14:11 ` Jack Wang
0 siblings, 0 replies; 16+ messages in thread
From: Jack Wang @ 2026-08-16 14:11 UTC (permalink / raw)
To: Jakub Sitnicki, netdev, Alexei Starovoitov, Jakub Kicinski,
Kuniyuki Iwashima, Paolo Abeni, Stanislav Fomichev
Cc: bpf, kernel-team, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, David S. Miller, Eric Dumazet, Simon Horman,
Jesper Dangaard Brouer, Willem de Bruijn
On Fri Aug 14, 2026 at 4:14 PM CST, Jakub Sitnicki wrote:
> + n = send(p1, test_payload, TEST_PAYLOAD_LEN, 0);
> + if (!ASSERT_EQ(n, TEST_PAYLOAD_LEN, "send"))
> + goto cleanup;
> +
> + n = recv(c1, buf, sizeof(buf), 0);
send() and recv() are stream sockets,so either operation may return a short count
even though this will usually not happen on loopback. Maybe use send/receive helpers
or loop til TEST_PAYLOAD_LEN bytes is a better choice,the blocking recv() can leave
the test stuck instead of reporting a timeout when data path regresses.
cheers
Jack Wang
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-08-16 14:11 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-14 8:14 [PATCH net-next 00/14] skb extension for BPF metadata Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 01/14] bpf: Introduce per-packet metadata storage for BPF programs Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 02/14] bpf: Allow access to bpf_sock_ops_kern->skb Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 03/14] bpf: Make BPF skb extension survive packet scrubbing Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 04/14] selftests/bpf: Add tests for bpf_dynptr_from_skb_ext Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 05/14] selftests/bpf: Test skb_ext on cloned skbs Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 06/14] selftests/bpf: Test skb_ext survival across veth and GRE Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 07/14] selftests/bpf: Test skb_ext read from cgroup_skb and sk_filter hooks Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 08/14] selftests/bpf: Test skb_ext read from sock_ops and LSM hooks Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 09/14] selftests/bpf: Test skb_ext read from kfree_skb tracepoint Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 10/14] selftests/bpf: Test skb_ext read from netfilter hook Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 11/14] selftests/bpf: Test skb_ext from LWT in, out, and xmit hooks Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 12/14] selftests/bpf: Test skb_ext read from seg6local End.BPF hook Jakub Sitnicki
2026-08-14 8:14 ` [PATCH net-next 13/14] selftests/bpf: Test skb_ext read from sk_skb stream verdict hook Jakub Sitnicki
2026-08-16 14:11 ` Jack Wang
2026-08-14 8:14 ` [PATCH net-next 14/14] selftests/bpf: Use non-trivial test payload in xdp_context tests Jakub Sitnicki
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox