Netdev List
 help / color / mirror / Atom feed
From: ThisSeanZhang <thisseanzhang@gmail.com>
To: bpf@vger.kernel.org
Cc: Alexei Starovoitov <ast@kernel.org>,
	Daniel Borkmann <daniel@iogearbox.net>,
	Andrii Nakryiko <andrii@kernel.org>,
	Eduard Zingerman <eddyz87@gmail.com>,
	Kumar Kartikeya Dwivedi <memxor@gmail.com>,
	Martin KaFai Lau <martin.lau@linux.dev>,
	netdev@vger.kernel.org, Nick Hudson <nhudson@akamai.com>,
	Felix Fietkau <nbd@nbd.name>
Subject: [RFC bpf-next v3 3/3] selftests/bpf: Add a test for the PPPoE encap/decap kfuncs
Date: Tue, 10 Nov 2026 22:21:41 -0500	[thread overview]
Message-ID: <20261111032141.525444-4-thisseanzhang@gmail.com> (raw)
In-Reply-To: <20261111032141.525444-1-thisseanzhang@gmail.com>

Add a tc test that exercises the bpf_skb_pppoe_encap() and
bpf_skb_pppoe_decap() kfuncs.

The encap program is run over plain Ethernet/IPv4 and Ethernet/IPv6
packets. The test checks that the kernel writes a correct PPPoE
session header between the Ethernet and network headers (version,
type, code, session id, length and PPP protocol), that skb->protocol
becomes ETH_P_PPP_SES, and that the payload is preserved. It then
runs the decap program and checks that the original packet and
protocol metadata are restored.

The rejection paths are driven by the packet input: encapsulation of
a non-IP packet (ARP), decapsulation of a packet whose protocol is
not ETH_P_PPP_SES (plain IPv4, and an IPv4 packet whose bytes at the
PPP protocol offset decode as PPP_IP), decapsulation of a PPPoE
packet with an unsupported PPP protocol (IPCP), and decapsulation of
a packet too short to still hold an IP header.

A separate program that holds a packet pointer across a call to
bpf_skb_pppoe_encap() must fail verification, covering the
KF_CHANGES_PKT invalidation from the first patch.

Signed-off-by: ThisSeanZhang <thisseanzhang@gmail.com>
---
 .../selftests/bpf/prog_tests/tc_pppoe.c       | 269 ++++++++++++++++++
 tools/testing/selftests/bpf/progs/tc_pppoe.c  |  77 +++++
 .../selftests/bpf/progs/tc_pppoe_fail.c       |  36 +++
 3 files changed, 382 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/tc_pppoe.c
 create mode 100644 tools/testing/selftests/bpf/progs/tc_pppoe.c
 create mode 100644 tools/testing/selftests/bpf/progs/tc_pppoe_fail.c

diff --git a/tools/testing/selftests/bpf/prog_tests/tc_pppoe.c b/tools/testing/selftests/bpf/prog_tests/tc_pppoe.c
new file mode 100644
index 000000000..5420d3bf3
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/tc_pppoe.c
@@ -0,0 +1,269 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 ThisSeanZhang */
+
+#include <arpa/inet.h>
+#include <test_progs.h>
+#include "tc_pppoe.skel.h"
+#include "tc_pppoe_fail.skel.h"
+
+/* Ethernet + IPv4 + TCP, 54 bytes in total. */
+static const __u8 ip4_pkt[] = {
+	/* Ethernet header. */
+	0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+	0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
+	0x08, 0x00,
+	/* IPv4 header. */
+	0x45, 0x00, 0x00, 0x28,
+	0x12, 0x34, 0x40, 0x00,
+	0x40, 0x06, 0x00, 0x00,
+	0xc0, 0xa8, 0x01, 0x01,
+	0xc0, 0xa8, 0x01, 0x02,
+	/* TCP header. */
+	0x00, 0x50, 0x1f, 0x90,
+	0x00, 0x00, 0x00, 0x01,
+	0x00, 0x00, 0x00, 0x00,
+	0x50, 0x02, 0x10, 0x00,
+	0x00, 0x00, 0x00, 0x00,
+};
+
+/* Ethernet + IPv6 + TCP, 74 bytes in total. */
+static const __u8 ip6_pkt[] = {
+	/* Ethernet header. */
+	0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+	0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
+	0x86, 0xdd,
+	/* IPv6 header. */
+	0x60, 0x00, 0x00, 0x00,
+	0x00, 0x14, 0x06, 0x40,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+	/* TCP header. */
+	0x00, 0x50, 0x1f, 0x90,
+	0x00, 0x00, 0x00, 0x01,
+	0x00, 0x00, 0x00, 0x00,
+	0x50, 0x02, 0x10, 0x00,
+	0x00, 0x00, 0x00, 0x00,
+};
+
+/* Ethernet + ARP request, 42 bytes in total. */
+static const __u8 arp_pkt[] = {
+	/* Ethernet header. */
+	0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+	0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
+	0x08, 0x06,
+	/* ARP payload. */
+	0x00, 0x01,
+	0x08, 0x00,
+	0x06, 0x04, 0x00, 0x01,
+	0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
+	0xc0, 0xa8, 0x01, 0x01,
+	0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+	0xc0, 0xa8, 0x01, 0x02,
+};
+
+#define PPPOE_SES_HLEN	8
+#define TC_ACT_SHOT	2
+
+static int run_prog(int prog_fd, const void *data_in, __u32 size_in,
+		    void *data_out, __u32 size_out, __u32 *retval,
+		    __u32 *size_out_actual)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, opts,
+		    .data_in = (void *)data_in,
+		    .data_size_in = size_in,
+		    .data_out = data_out,
+		    .data_size_out = size_out,
+	);
+	int ret;
+
+	ret = bpf_prog_test_run_opts(prog_fd, &opts);
+	if (!ret && retval)
+		*retval = opts.retval;
+	if (!ret && size_out_actual)
+		*size_out_actual = opts.data_size_out;
+
+	return ret;
+}
+
+static void test_encap_decap(struct tc_pppoe *skel, const char *subtest,
+			     const __u8 *pkt, __u32 pkt_len, int ethertype,
+			     __u8 ppp_proto)
+{
+	__u8 encap_pkt[128];
+	__u8 decap_pkt[128];
+	__u32 retval, out_len;
+	int ret;
+
+	if (!test__start_subtest(subtest))
+		return;
+
+	skel->bss->encap_proto = 0;
+	ret = run_prog(bpf_program__fd(skel->progs.tc_pppoe_encap),
+		       pkt, pkt_len, encap_pkt, sizeof(encap_pkt),
+		       &retval, &out_len);
+	ASSERT_OK(ret, "encap test_run");
+	ASSERT_OK(retval, "encap retval");
+	ASSERT_EQ(out_len, pkt_len + PPPOE_SES_HLEN, "encap pkt len");
+	ASSERT_EQ(skel->bss->encap_proto, htons(0x8864),
+		  "encap skb protocol");
+
+	/* Ethernet header, with the ethertype changed to PPPoE session. */
+	ASSERT_EQ(encap_pkt[12], 0x88, "encap eth h_proto");
+	ASSERT_EQ(encap_pkt[13], 0x64, "encap eth h_proto");
+	/* PPPoE session header written by the kernel. */
+	ASSERT_EQ(encap_pkt[14], 0x11, "encap pppoe ver/type");
+	ASSERT_EQ(encap_pkt[15], 0x00, "encap pppoe code");
+	ASSERT_EQ(encap_pkt[16], 0xde, "encap pppoe sid");
+	ASSERT_EQ(encap_pkt[17], 0xad, "encap pppoe sid");
+	ASSERT_EQ(encap_pkt[18], 0x00, "encap pppoe length");
+	ASSERT_EQ(encap_pkt[19], pkt_len - 14 + 2, "encap pppoe length");
+	ASSERT_EQ(encap_pkt[20], 0x00, "encap ppp proto");
+	ASSERT_EQ(encap_pkt[21], ppp_proto, "encap ppp proto");
+	/* The original packet moves behind the new header unchanged. */
+	ASSERT_MEMEQ(encap_pkt + 14 + PPPOE_SES_HLEN, pkt + 14,
+		     pkt_len - 14, "encap payload");
+
+	skel->bss->decap_proto = 0;
+	ret = run_prog(bpf_program__fd(skel->progs.tc_pppoe_decap),
+		       encap_pkt, pkt_len + PPPOE_SES_HLEN, decap_pkt,
+		       sizeof(decap_pkt), &retval, &out_len);
+	ASSERT_OK(ret, "decap test_run");
+	ASSERT_OK(retval, "decap retval");
+	ASSERT_EQ(out_len, pkt_len, "decap pkt len");
+	ASSERT_EQ(skel->bss->decap_proto, ethertype, "decap skb protocol");
+	ASSERT_MEMEQ(decap_pkt, pkt, pkt_len, "decap packet");
+}
+
+static void test_reject_input(struct tc_pppoe *skel, const char *prog_name,
+			      const __u8 *pkt, __u32 pkt_len,
+			      const char *subtest)
+{
+	struct bpf_program *prog;
+	__u8 out[128];
+	__u32 retval = 0;
+	int ret;
+
+	if (!test__start_subtest(subtest))
+		return;
+
+	prog = bpf_object__find_program_by_name(skel->obj, prog_name);
+	if (!ASSERT_OK_PTR(prog, "find program"))
+		return;
+
+	skel->bss->encap_proto = 0;
+	skel->bss->decap_proto = 0;
+	ret = run_prog(bpf_program__fd(prog), pkt, pkt_len, out,
+		       sizeof(out), &retval, NULL);
+	ASSERT_OK(ret, "reject test_run");
+	ASSERT_EQ(retval, TC_ACT_SHOT, "kfunc rejected the call");
+	ASSERT_EQ(skel->bss->encap_proto, 0, "skb protocol unchanged");
+	ASSERT_EQ(skel->bss->decap_proto, 0, "skb protocol unchanged");
+}
+
+static void test_kfunc_flags(struct tc_pppoe *skel, const char *prog_name,
+			     int expected, const char *subtest)
+{
+	struct bpf_program *prog;
+	__u8 out[128];
+	__u32 retval = 0;
+	int ret;
+
+	if (!test__start_subtest(subtest))
+		return;
+
+	prog = bpf_object__find_program_by_name(skel->obj, prog_name);
+	if (!ASSERT_OK_PTR(prog, "find program"))
+		return;
+
+	ret = run_prog(bpf_program__fd(prog), ip4_pkt, sizeof(ip4_pkt), out,
+		       sizeof(out), &retval, NULL);
+	ASSERT_OK(ret, "flags test_run");
+	ASSERT_EQ((int)retval, expected, "kfunc retval");
+}
+
+void test_tc_pppoe(void)
+{
+	/* PPPoE packet with PPP protocol 0x8021 (IPCP), neither IPv4 nor IPv6. */
+	static const __u8 bad_ppp_pkt[] = {
+		0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+		0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
+		0x88, 0x64,
+		0x11, 0x00, 0x00, 0x00, 0x00, 0x28, 0x80, 0x21,
+		0x45, 0x00, 0x00, 0x28,
+		0x12, 0x34, 0x40, 0x00,
+		0x40, 0x06, 0x00, 0x00,
+		0xc0, 0xa8, 0x01, 0x01,
+		0xc0, 0xa8, 0x01, 0x02,
+		0x00, 0x50, 0x1f, 0x90,
+		0x00, 0x00, 0x00, 0x01,
+		0x00, 0x00, 0x00, 0x00,
+		0x50, 0x02, 0x10, 0x00,
+		0x00, 0x00, 0x00, 0x00,
+	};
+	/* PPPoE packet too short for a full inner IP header. */
+	static const __u8 truncated_pkt[] = {
+		0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+		0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
+		0x88, 0x64,
+		0x11, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x21,
+		0x45, 0x00,
+	};
+	/* Non-PPPoE IPv4 packet whose bytes 20/21 decode as PPP_IP. */
+	static const __u8 fake_ppp_pkt[] = {
+		0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+		0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
+		0x08, 0x00,
+		0x45, 0x00, 0x00, 0x28,
+		0x12, 0x34, 0x00, 0x21,
+		0x40, 0x06, 0x00, 0x00,
+		0xc0, 0xa8, 0x01, 0x01,
+		0xc0, 0xa8, 0x01, 0x02,
+		0x00, 0x50, 0x1f, 0x90,
+		0x00, 0x00, 0x00, 0x01,
+		0x00, 0x00, 0x00, 0x00,
+		0x50, 0x02, 0x10, 0x00,
+		0x00, 0x00, 0x00, 0x00,
+	};
+	struct tc_pppoe_fail *fail_skel;
+	struct tc_pppoe *skel;
+
+	skel = tc_pppoe__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "skel open_and_load"))
+		return;
+
+	test_encap_decap(skel, "encap-decap-v4", ip4_pkt, sizeof(ip4_pkt),
+			 htons(0x0800), 0x21);
+	test_encap_decap(skel, "encap-decap-v6", ip6_pkt, sizeof(ip6_pkt),
+			 htons(0x86dd), 0x57);
+
+	/* BPF_F_ADJ_ROOM_FIXED_GSO is accepted, unknown flags are not. */
+	test_kfunc_flags(skel, "tc_pppoe_encap_fixed_gso", 0,
+			 "encap-fixed-gso");
+	test_kfunc_flags(skel, "tc_pppoe_encap_bad_flags", -EINVAL,
+			 "encap-bad-flags");
+
+	/* Encapsulation of a non-IP packet must be rejected. */
+	test_reject_input(skel, "tc_pppoe_encap", arp_pkt, sizeof(arp_pkt),
+			  "encap-arp-reject");
+	/* Decapsulation of a packet whose protocol is not PPPoE. */
+	test_reject_input(skel, "tc_pppoe_decap", ip4_pkt, sizeof(ip4_pkt),
+			  "decap-non-pppoe");
+	test_reject_input(skel, "tc_pppoe_decap", bad_ppp_pkt,
+			  sizeof(bad_ppp_pkt), "decap-bad-ppp-proto");
+	test_reject_input(skel, "tc_pppoe_decap", truncated_pkt,
+			  sizeof(truncated_pkt), "decap-truncated");
+	test_reject_input(skel, "tc_pppoe_decap", fake_ppp_pkt,
+			  sizeof(fake_ppp_pkt), "decap-non-pppoe-fake-proto");
+
+	tc_pppoe__destroy(skel);
+
+	/*
+	 * KF_CHANGES_PKT must make the verifier reject a program that
+	 * keeps a packet pointer across the call.
+	 */
+	fail_skel = tc_pppoe_fail__open_and_load();
+	if (!ASSERT_ERR_PTR(fail_skel, "stale packet pointer rejected"))
+		tc_pppoe_fail__destroy(fail_skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/tc_pppoe.c b/tools/testing/selftests/bpf/progs/tc_pppoe.c
new file mode 100644
index 000000000..880abfbd5
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tc_pppoe.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 ThisSeanZhang */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#define ETH_P_PPP_SES_TEST	0x8864
+
+#define TC_ACT_OK_TEST		0
+#define TC_ACT_SHOT_TEST	2
+
+extern int bpf_skb_pppoe_encap(struct __sk_buff *skb,
+			       __be16 sid, u64 flags) __ksym;
+extern int bpf_skb_pppoe_decap(struct __sk_buff *skb, u64 flags) __ksym;
+
+/* skb->protocol as observed after the kfunc call. */
+int encap_proto;
+int decap_proto;
+
+SEC("tc")
+int tc_pppoe_encap(struct __sk_buff *skb)
+{
+	struct ethhdr eth;
+
+	if (bpf_skb_pppoe_encap(skb, bpf_htons(0xdead), 0))
+		return TC_ACT_SHOT_TEST;
+
+	encap_proto = skb->protocol;
+
+	if (bpf_skb_load_bytes(skb, 0, &eth, sizeof(eth)))
+		return TC_ACT_SHOT_TEST;
+	eth.h_proto = bpf_htons(ETH_P_PPP_SES_TEST);
+	if (bpf_skb_store_bytes(skb, 0, &eth, sizeof(eth), 0))
+		return TC_ACT_SHOT_TEST;
+
+	return TC_ACT_OK_TEST;
+}
+
+SEC("tc")
+int tc_pppoe_decap(struct __sk_buff *skb)
+{
+	struct ethhdr eth;
+
+	if (bpf_skb_pppoe_decap(skb, 0))
+		return TC_ACT_SHOT_TEST;
+
+	decap_proto = skb->protocol;
+
+	/*
+	 * Restore the ethertype to the protocol of the decapsulated
+	 * payload, as picked by the kernel from the PPP protocol field.
+	 */
+	if (bpf_skb_load_bytes(skb, 0, &eth, sizeof(eth)))
+		return TC_ACT_SHOT_TEST;
+	eth.h_proto = (__be16)skb->protocol;
+	if (bpf_skb_store_bytes(skb, 0, &eth, sizeof(eth), 0))
+		return TC_ACT_SHOT_TEST;
+
+	return TC_ACT_OK_TEST;
+}
+
+/* BPF_F_ADJ_ROOM_FIXED_GSO is accepted; any other flag is rejected. */
+SEC("tc")
+int tc_pppoe_encap_fixed_gso(struct __sk_buff *skb)
+{
+	return bpf_skb_pppoe_encap(skb, bpf_htons(0xdead),
+				   BPF_F_ADJ_ROOM_FIXED_GSO);
+}
+
+SEC("tc")
+int tc_pppoe_encap_bad_flags(struct __sk_buff *skb)
+{
+	return bpf_skb_pppoe_encap(skb, bpf_htons(0xdead), 1ULL << 32);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/tc_pppoe_fail.c b/tools/testing/selftests/bpf/progs/tc_pppoe_fail.c
new file mode 100644
index 000000000..f816c7169
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tc_pppoe_fail.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 ThisSeanZhang */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#define TC_ACT_SHOT_TEST	2
+
+extern int bpf_skb_pppoe_encap(struct __sk_buff *skb,
+			       __be16 sid, u64 flags) __ksym;
+
+/*
+ * bpf_skb_pppoe_encap() may reallocate the skb head, so the packet
+ * pointer checked before the call is stale afterwards: this program
+ * has to fail verification.
+ */
+SEC("tc")
+int tc_pppoe_stale_ptr(struct __sk_buff *skb)
+{
+	char *data = (char *)(long)skb->data;
+	char *data_end = (char *)(long)skb->data_end;
+	__u8 b = 0;
+
+	if (data + 1 > data_end)
+		return TC_ACT_SHOT_TEST;
+
+	if (bpf_skb_pppoe_encap(skb, bpf_htons(0xdead), 0))
+		return TC_ACT_SHOT_TEST;
+
+	b = *data;
+
+	return b;
+}
+
+char _license[] SEC("license") = "GPL";
-- 
2.47.3


      parent reply	other threads:[~2026-11-11  3:22 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-11  3:21 [RFC bpf-next v3 0/3] bpf: Add PPPoE encap/decap kfuncs ThisSeanZhang
2026-11-11  3:21 ` [RFC bpf-next v3 1/3] bpf: Add KF_CHANGES_PKT kfunc flag ThisSeanZhang
2026-11-11  3:21 ` [RFC bpf-next v3 2/3] bpf: Add PPPoE encap/decap kfuncs ThisSeanZhang
2026-11-11  3:21 ` ThisSeanZhang [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261111032141.525444-4-thisseanzhang@gmail.com \
    --to=thisseanzhang@gmail.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=martin.lau@linux.dev \
    --cc=memxor@gmail.com \
    --cc=nbd@nbd.name \
    --cc=netdev@vger.kernel.org \
    --cc=nhudson@akamai.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox