diff --git a/tools/testing/selftests/bpf/prog_tests/dynptr.c b/tools/testing/selftests/bpf/prog_tests/dynptr.c
index bcf80b9f7c27..c40631f33c7b 100644
--- a/tools/testing/selftests/bpf/prog_tests/dynptr.c
+++ b/tools/testing/selftests/bpf/prog_tests/dynptr.c
@@ -2,6 +2,7 @@
 /* Copyright (c) 2022 Facebook */
 
 #include <test_progs.h>
+#include <network_helpers.h>
 #include "dynptr_fail.skel.h"
 #include "dynptr_success.skel.h"
 
@@ -11,8 +12,8 @@ static char obj_log_buf[1048576];
 static struct {
 	const char *prog_name;
 	const char *expected_err_msg;
-} dynptr_tests[] = {
-	/* failure cases */
+} verifier_error_tests[] = {
+	/* these cases should trigger a verifier error */
 	{"ringbuf_missing_release1", "Unreleased reference id=1"},
 	{"ringbuf_missing_release2", "Unreleased reference id=2"},
 	{"ringbuf_missing_release_callback", "Unreleased reference id"},
@@ -42,11 +43,25 @@ static struct {
 	{"release_twice_callback", "arg 1 is an unacquired reference"},
 	{"dynptr_from_mem_invalid_api",
 		"Unsupported reg type fp for bpf_dynptr_from_mem data"},
+	{"skb_invalid_data_slice", "invalid mem access 'scalar'"},
+	{"xdp_invalid_data_slice", "invalid mem access 'scalar'"},
+	{"skb_invalid_ctx", "unknown func bpf_dynptr_from_skb"},
+	{"xdp_invalid_ctx", "unknown func bpf_dynptr_from_xdp"},
+};
+
+enum test_setup_type {
+	SETUP_SYSCALL_SLEEP,
+	SETUP_SKB_PROG,
+};
 
-	/* success cases */
-	{"test_read_write", NULL},
-	{"test_data_slice", NULL},
-	{"test_ringbuf", NULL},
+static struct {
+	const char *prog_name;
+	enum test_setup_type type;
+} runtime_tests[] = {
+	{"test_read_write", SETUP_SYSCALL_SLEEP},
+	{"test_data_slice", SETUP_SYSCALL_SLEEP},
+	{"test_ringbuf", SETUP_SYSCALL_SLEEP},
+	{"test_skb_readonly", SETUP_SKB_PROG},
 };
 
 static void verify_fail(const char *prog_name, const char *expected_err_msg)
@@ -85,7 +100,7 @@ static void verify_fail(const char *prog_name, const char *expected_err_msg)
 	dynptr_fail__destroy(skel);
 }
 
-static void verify_success(const char *prog_name)
+static void run_tests(const char *prog_name, enum test_setup_type setup_type)
 {
 	struct dynptr_success *skel;
 	struct bpf_program *prog;
@@ -107,15 +122,42 @@ static void verify_success(const char *prog_name)
 	if (!ASSERT_OK_PTR(prog, "bpf_object__find_program_by_name"))
 		goto cleanup;
 
-	link = bpf_program__attach(prog);
-	if (!ASSERT_OK_PTR(link, "bpf_program__attach"))
-		goto cleanup;
+	switch (setup_type) {
+	case SETUP_SYSCALL_SLEEP:
+		link = bpf_program__attach(prog);
+		if (!ASSERT_OK_PTR(link, "bpf_program__attach"))
+			goto cleanup;
 
-	usleep(1);
+		usleep(1);
 
-	ASSERT_EQ(skel->bss->err, 0, "err");
+		bpf_link__destroy(link);
+		break;
+	case SETUP_SKB_PROG:
+	{
+		int prog_fd, err;
+		char buf[64];
+
+		prog_fd = bpf_program__fd(prog);
+		if (CHECK_FAIL(prog_fd < 0))
+			goto cleanup;
+
+		LIBBPF_OPTS(bpf_test_run_opts, topts,
+			    .data_in = &pkt_v4,
+			    .data_size_in = sizeof(pkt_v4),
+			    .data_out = buf,
+			    .data_size_out = sizeof(buf),
+			    .repeat = 1,
+		);
 
-	bpf_link__destroy(link);
+		err = bpf_prog_test_run_opts(prog_fd, &topts);
+
+		if (!ASSERT_OK(err, "test_run"))
+			goto cleanup;
+
+		break;
+	}
+	}
+	ASSERT_EQ(skel->bss->err, 0, "err");
 
 cleanup:
 	dynptr_success__destroy(skel);
@@ -125,14 +167,17 @@ void test_dynptr(void)
 {
 	int i;
 
-	for (i = 0; i < ARRAY_SIZE(dynptr_tests); i++) {
-		if (!test__start_subtest(dynptr_tests[i].prog_name))
+	for (i = 0; i < ARRAY_SIZE(verifier_error_tests); i++) {
+		if (!test__start_subtest(verifier_error_tests[i].prog_name))
+			continue;
+
+		verify_fail(verifier_error_tests[i].prog_name,
+			    verifier_error_tests[i].expected_err_msg);
+	}
+	for (i = 0; i < ARRAY_SIZE(runtime_tests); i++) {
+		if (!test__start_subtest(runtime_tests[i].prog_name))
 			continue;
 
-		if (dynptr_tests[i].expected_err_msg)
-			verify_fail(dynptr_tests[i].prog_name,
-				    dynptr_tests[i].expected_err_msg);
-		else
-			verify_success(dynptr_tests[i].prog_name);
+		run_tests(runtime_tests[i].prog_name, runtime_tests[i].type);
 	}
 }
diff --git a/tools/testing/selftests/bpf/prog_tests/dynptr_xdp.c b/tools/testing/selftests/bpf/prog_tests/dynptr_xdp.c
new file mode 100644
index 000000000000..ca775d126b60
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/dynptr_xdp.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <network_helpers.h>
+#include "test_dynptr_xdp.skel.h"
+#include "test_tcp_hdr_options.h"
+
+struct test_pkt {
+	struct ipv6_packet pk6_v6;
+	u8 options[16];
+} __packed;
+
+void test_dynptr_xdp(void)
+{
+	struct test_dynptr_xdp *skel;
+	char buf[128];
+	int err;
+
+	skel = test_dynptr_xdp__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
+		return;
+
+	struct test_pkt pkt = {
+		.pk6_v6.eth.h_proto = __bpf_constant_htons(ETH_P_IPV6),
+		.pk6_v6.iph.nexthdr = IPPROTO_TCP,
+		.pk6_v6.iph.payload_len = __bpf_constant_htons(MAGIC_BYTES),
+		.pk6_v6.tcp.urg_ptr = 123,
+		.pk6_v6.tcp.doff = 9, /* 16 bytes of options */
+
+		.options = {
+			TCPOPT_MSS, 4, 0x05, 0xB4, TCPOPT_NOP, TCPOPT_NOP,
+			skel->rodata->tcp_hdr_opt_kind_tpr, 6, 0, 0, 0, 9, TCPOPT_EOL
+		},
+	};
+
+	LIBBPF_OPTS(bpf_test_run_opts, topts,
+		    .data_in = &pkt,
+		    .data_size_in = sizeof(pkt),
+		    .data_out = buf,
+		    .data_size_out = sizeof(buf),
+		    .repeat = 3,
+	);
+
+	err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.xdp_ingress_v6), &topts);
+	ASSERT_OK(err, "ipv6 test_run");
+	ASSERT_EQ(skel->bss->server_id, 0x9000000, "server id");
+	ASSERT_EQ(topts.retval, XDP_PASS, "ipv6 test_run retval");
+
+	test_dynptr_xdp__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c
index c1814938a5fd..4e3f853b2d02 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c
@@ -5,6 +5,7 @@
 #include <string.h>
 #include <linux/bpf.h>
 #include <bpf/bpf_helpers.h>
+#include <linux/if_ether.h>
 #include "bpf_misc.h"
 
 char _license[] SEC("license") = "GPL";
@@ -622,3 +623,78 @@ int dynptr_from_mem_invalid_api(void *ctx)
 
 	return 0;
 }
+
+/* The data slice is invalidated whenever a helper changes packet data */
+SEC("?tc")
+int skb_invalid_data_slice(struct __sk_buff *skb)
+{
+	struct bpf_dynptr ptr;
+	struct ethhdr *hdr;
+
+	bpf_dynptr_from_skb(skb, 0, &ptr);
+	hdr = bpf_dynptr_data(&ptr, 0, sizeof(*hdr));
+	if (!hdr)
+		return SK_DROP;
+
+	hdr->h_proto = 12;
+
+	if (bpf_skb_pull_data(skb, skb->len))
+		return SK_DROP;
+
+	/* this should fail */
+	hdr->h_proto = 1;
+
+	return SK_PASS;
+}
+
+/* The data slice is invalidated whenever a helper changes packet data */
+SEC("?xdp")
+int xdp_invalid_data_slice(struct xdp_md *xdp)
+{
+	struct bpf_dynptr ptr;
+	struct ethhdr *hdr1, *hdr2;
+
+	bpf_dynptr_from_xdp(xdp, 0, &ptr);
+	hdr1 = bpf_dynptr_data(&ptr, 0, sizeof(*hdr1));
+	if (!hdr1)
+		return XDP_DROP;
+
+	hdr2 = bpf_dynptr_data(&ptr, 0, sizeof(*hdr2));
+	if (!hdr2)
+		return XDP_DROP;
+
+	hdr1->h_proto = 12;
+	hdr2->h_proto = 12;
+
+	if (bpf_xdp_adjust_head(xdp, 0 - (int)sizeof(*hdr1)))
+		return XDP_DROP;
+
+	/* this should fail */
+	hdr2->h_proto = 1;
+
+	return XDP_PASS;
+}
+
+/* Only supported prog type can create skb-type dynptrs */
+SEC("?raw_tp/sys_nanosleep")
+int skb_invalid_ctx(void *ctx)
+{
+	struct bpf_dynptr ptr;
+
+	/* this should fail */
+	bpf_dynptr_from_skb(ctx, 0, &ptr);
+
+	return 0;
+}
+
+/* Only supported prog type can create xdp-type dynptrs */
+SEC("?raw_tp/sys_nanosleep")
+int xdp_invalid_ctx(void *ctx)
+{
+	struct bpf_dynptr ptr;
+
+	/* this should fail */
+	bpf_dynptr_from_xdp(ctx, 0, &ptr);
+
+	return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/dynptr_success.c b/tools/testing/selftests/bpf/progs/dynptr_success.c
index a3a6103c8569..ffddd6ddc7fb 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_success.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_success.c
@@ -162,3 +162,35 @@ int test_ringbuf(void *ctx)
 	bpf_ringbuf_discard_dynptr(&ptr, 0);
 	return 0;
 }
+
+SEC("cgroup_skb/egress")
+int test_skb_readonly(void *ctx)
+{
+	__u8 write_data[2] = {1, 2};
+	struct bpf_dynptr ptr;
+	void *data;
+	int ret;
+
+	err = 1;
+
+	if (bpf_dynptr_from_skb(ctx, 0, &ptr))
+		return 0;
+	err++;
+
+	data = bpf_dynptr_data(&ptr, 0, 1);
+	if (data)
+		/* it's an error if data is not NULL since cgroup skbs
+		 * are read only
+		 */
+		return 0;
+	err++;
+
+	ret = bpf_dynptr_write(&ptr, 0, write_data, sizeof(write_data), 0);
+	/* since cgroup skbs are read only, writes should fail */
+	if (ret != -EINVAL)
+		return 0;
+
+	err = 0;
+
+	return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/test_dynptr_xdp.c b/tools/testing/selftests/bpf/progs/test_dynptr_xdp.c
new file mode 100644
index 000000000000..c879dfb6370a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_dynptr_xdp.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* This logic is lifted from a real-world use case of packet parsing, used in
+ * the open source library katran, a layer 4 load balancer.
+ *
+ * This test demonstrates how to parse packet contents using dynptrs.
+ *
+ * https://github.com/facebookincubator/katran/blob/main/katran/lib/bpf/pckt_parsing.h
+ */
+
+#include <string.h>
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <linux/tcp.h>
+#include <stdbool.h>
+#include <linux/ipv6.h>
+#include <linux/if_ether.h>
+#include "test_tcp_hdr_options.h"
+
+char _license[] SEC("license") = "GPL";
+
+/* Arbitrarily picked unused value from IANA TCP Option Kind Numbers */
+const __u32 tcp_hdr_opt_kind_tpr = 0xB7;
+/* Length of the tcp header option */
+const __u32 tcp_hdr_opt_len_tpr = 6;
+/* maximum number of header options to check to lookup server_id */
+const __u32 tcp_hdr_opt_max_opt_checks = 15;
+
+__u32 server_id;
+
+static int parse_hdr_opt(struct bpf_dynptr *ptr, __u32 *off, __u8 *hdr_bytes_remaining,
+			 __u32 *server_id)
+{
+	__u8 *tcp_opt, kind, hdr_len;
+	__u8 *data;
+
+	data = bpf_dynptr_data(ptr, *off, sizeof(kind) + sizeof(hdr_len) +
+			       sizeof(*server_id));
+	if (!data)
+		return -1;
+
+	kind = data[0];
+
+	if (kind == TCPOPT_EOL)
+		return -1;
+
+	if (kind == TCPOPT_NOP) {
+		*off += 1;
+		/* continue to the next option */
+		*hdr_bytes_remaining -= 1;
+
+		return 0;
+	}
+
+	if (*hdr_bytes_remaining < 2)
+		return -1;
+
+	hdr_len = data[1];
+	if (hdr_len > *hdr_bytes_remaining)
+		return -1;
+
+	if (kind == tcp_hdr_opt_kind_tpr) {
+		if (hdr_len != tcp_hdr_opt_len_tpr)
+			return -1;
+
+		memcpy(server_id, (__u32 *)(data + 2), sizeof(*server_id));
+		return 1;
+	}
+
+	*off += hdr_len;
+	*hdr_bytes_remaining -= hdr_len;
+
+	return 0;
+}
+
+SEC("xdp")
+int xdp_ingress_v6(struct xdp_md *xdp)
+{
+	__u8 hdr_bytes_remaining;
+	struct tcphdr *tcp_hdr;
+	__u8 tcp_hdr_opt_len;
+	int err = 0;
+	__u32 off;
+
+	struct bpf_dynptr ptr;
+
+	bpf_dynptr_from_xdp(xdp, 0, &ptr);
+
+	off = sizeof(struct ethhdr) + sizeof(struct ipv6hdr);
+
+	tcp_hdr = bpf_dynptr_data(&ptr, off, sizeof(*tcp_hdr));
+	if (!tcp_hdr)
+		return XDP_DROP;
+
+	tcp_hdr_opt_len = (tcp_hdr->doff * 4) - sizeof(struct tcphdr);
+	if (tcp_hdr_opt_len < tcp_hdr_opt_len_tpr)
+		return XDP_DROP;
+
+	hdr_bytes_remaining = tcp_hdr_opt_len;
+
+	off += sizeof(struct tcphdr);
+
+	/* max number of bytes of options in tcp header is 40 bytes */
+	for (int i = 0; i < tcp_hdr_opt_max_opt_checks; i++) {
+		err = parse_hdr_opt(&ptr, &off, &hdr_bytes_remaining, &server_id);
+
+		if (err || !hdr_bytes_remaining)
+			break;
+	}
+
+	if (!server_id)
+		return XDP_DROP;
+
+	return XDP_PASS;
+}
diff --git a/tools/testing/selftests/bpf/progs/test_l4lb_noinline.c b/tools/testing/selftests/bpf/progs/test_l4lb_noinline.c
index c8bc0c6947aa..1fef7868ea8b 100644
--- a/tools/testing/selftests/bpf/progs/test_l4lb_noinline.c
+++ b/tools/testing/selftests/bpf/progs/test_l4lb_noinline.c
@@ -230,21 +230,18 @@ static __noinline bool get_packet_dst(struct real_definition **real,
 	return true;
 }
 
-static __noinline int parse_icmpv6(void *data, void *data_end, __u64 off,
+static __noinline int parse_icmpv6(struct bpf_dynptr *skb_ptr, __u64 off,
 				   struct packet_description *pckt)
 {
 	struct icmp6hdr *icmp_hdr;
 	struct ipv6hdr *ip6h;
 
-	icmp_hdr = data + off;
-	if (icmp_hdr + 1 > data_end)
+	icmp_hdr = bpf_dynptr_data(skb_ptr, off, sizeof(*icmp_hdr) + sizeof(*ip6h));
+	if (!icmp_hdr)
 		return TC_ACT_SHOT;
 	if (icmp_hdr->icmp6_type != ICMPV6_PKT_TOOBIG)
 		return TC_ACT_OK;
-	off += sizeof(struct icmp6hdr);
-	ip6h = data + off;
-	if (ip6h + 1 > data_end)
-		return TC_ACT_SHOT;
+	ip6h = (struct ipv6hdr *)(icmp_hdr + 1);
 	pckt->proto = ip6h->nexthdr;
 	pckt->flags |= F_ICMP;
 	memcpy(pckt->srcv6, ip6h->daddr.s6_addr32, 16);
@@ -252,22 +249,19 @@ static __noinline int parse_icmpv6(void *data, void *data_end, __u64 off,
 	return TC_ACT_UNSPEC;
 }
 
-static __noinline int parse_icmp(void *data, void *data_end, __u64 off,
+static __noinline int parse_icmp(struct bpf_dynptr *skb_ptr, __u64 off,
 				 struct packet_description *pckt)
 {
 	struct icmphdr *icmp_hdr;
 	struct iphdr *iph;
 
-	icmp_hdr = data + off;
-	if (icmp_hdr + 1 > data_end)
+	icmp_hdr = bpf_dynptr_data(skb_ptr, off, sizeof(*icmp_hdr) + sizeof(*iph));
+	if (!icmp_hdr)
 		return TC_ACT_SHOT;
 	if (icmp_hdr->type != ICMP_DEST_UNREACH ||
 	    icmp_hdr->code != ICMP_FRAG_NEEDED)
 		return TC_ACT_OK;
-	off += sizeof(struct icmphdr);
-	iph = data + off;
-	if (iph + 1 > data_end)
-		return TC_ACT_SHOT;
+	iph = (struct iphdr *)(icmp_hdr + 1);
 	if (iph->ihl != 5)
 		return TC_ACT_SHOT;
 	pckt->proto = iph->protocol;
@@ -277,13 +271,13 @@ static __noinline int parse_icmp(void *data, void *data_end, __u64 off,
 	return TC_ACT_UNSPEC;
 }
 
-static __noinline bool parse_udp(void *data, __u64 off, void *data_end,
+static __noinline bool parse_udp(struct bpf_dynptr *skb_ptr, __u64 off,
 				 struct packet_description *pckt)
 {
 	struct udphdr *udp;
-	udp = data + off;
 
-	if (udp + 1 > data_end)
+	udp = bpf_dynptr_data(skb_ptr, off, sizeof(*udp));
+	if (!udp)
 		return false;
 
 	if (!(pckt->flags & F_ICMP)) {
@@ -296,13 +290,13 @@ static __noinline bool parse_udp(void *data, __u64 off, void *data_end,
 	return true;
 }
 
-static __noinline bool parse_tcp(void *data, __u64 off, void *data_end,
+static __noinline bool parse_tcp(struct bpf_dynptr *skb_ptr, __u64 off,
 				 struct packet_description *pckt)
 {
 	struct tcphdr *tcp;
 
-	tcp = data + off;
-	if (tcp + 1 > data_end)
+	tcp = bpf_dynptr_data(skb_ptr, off, sizeof(*tcp));
+	if (!tcp)
 		return false;
 
 	if (tcp->syn)
@@ -318,12 +312,11 @@ static __noinline bool parse_tcp(void *data, __u64 off, void *data_end,
 	return true;
 }
 
-static __noinline int process_packet(void *data, __u64 off, void *data_end,
+static __noinline int process_packet(struct bpf_dynptr *skb_ptr,
+				     struct eth_hdr *eth, __u64 off,
 				     bool is_ipv6, struct __sk_buff *skb)
 {
-	void *pkt_start = (void *)(long)skb->data;
 	struct packet_description pckt = {};
-	struct eth_hdr *eth = pkt_start;
 	struct bpf_tunnel_key tkey = {};
 	struct vip_stats *data_stats;
 	struct real_definition *dst;
@@ -344,8 +337,8 @@ static __noinline int process_packet(void *data, __u64 off, void *data_end,
 
 	tkey.tunnel_ttl = 64;
 	if (is_ipv6) {
-		ip6h = data + off;
-		if (ip6h + 1 > data_end)
+		ip6h = bpf_dynptr_data(skb_ptr, off, sizeof(*ip6h));
+		if (!ip6h)
 			return TC_ACT_SHOT;
 
 		iph_len = sizeof(struct ipv6hdr);
@@ -356,7 +349,7 @@ static __noinline int process_packet(void *data, __u64 off, void *data_end,
 		if (protocol == IPPROTO_FRAGMENT) {
 			return TC_ACT_SHOT;
 		} else if (protocol == IPPROTO_ICMPV6) {
-			action = parse_icmpv6(data, data_end, off, &pckt);
+			action = parse_icmpv6(skb_ptr, off, &pckt);
 			if (action >= 0)
 				return action;
 			off += IPV6_PLUS_ICMP_HDR;
@@ -365,10 +358,8 @@ static __noinline int process_packet(void *data, __u64 off, void *data_end,
 			memcpy(pckt.dstv6, ip6h->daddr.s6_addr32, 16);
 		}
 	} else {
-		iph = data + off;
-		if (iph + 1 > data_end)
-			return TC_ACT_SHOT;
-		if (iph->ihl != 5)
+		iph = bpf_dynptr_data(skb_ptr, off, sizeof(*iph));
+		if (!iph || iph->ihl != 5)
 			return TC_ACT_SHOT;
 
 		protocol = iph->protocol;
@@ -379,7 +370,7 @@ static __noinline int process_packet(void *data, __u64 off, void *data_end,
 		if (iph->frag_off & PCKT_FRAGMENTED)
 			return TC_ACT_SHOT;
 		if (protocol == IPPROTO_ICMP) {
-			action = parse_icmp(data, data_end, off, &pckt);
+			action = parse_icmp(skb_ptr, off, &pckt);
 			if (action >= 0)
 				return action;
 			off += IPV4_PLUS_ICMP_HDR;
@@ -391,10 +382,10 @@ static __noinline int process_packet(void *data, __u64 off, void *data_end,
 	protocol = pckt.proto;
 
 	if (protocol == IPPROTO_TCP) {
-		if (!parse_tcp(data, off, data_end, &pckt))
+		if (!parse_tcp(skb_ptr, off, &pckt))
 			return TC_ACT_SHOT;
 	} else if (protocol == IPPROTO_UDP) {
-		if (!parse_udp(data, off, data_end, &pckt))
+		if (!parse_udp(skb_ptr, off, &pckt))
 			return TC_ACT_SHOT;
 	} else {
 		return TC_ACT_SHOT;
@@ -450,20 +441,22 @@ static __noinline int process_packet(void *data, __u64 off, void *data_end,
 SEC("tc")
 int balancer_ingress(struct __sk_buff *ctx)
 {
-	void *data_end = (void *)(long)ctx->data_end;
-	void *data = (void *)(long)ctx->data;
-	struct eth_hdr *eth = data;
+	struct bpf_dynptr ptr;
+	struct eth_hdr *eth;
 	__u32 eth_proto;
 	__u32 nh_off;
 
 	nh_off = sizeof(struct eth_hdr);
-	if (data + nh_off > data_end)
+
+	bpf_dynptr_from_skb(ctx, 0, &ptr);
+	eth = bpf_dynptr_data(&ptr, 0, sizeof(*eth));
+	if (!eth)
 		return TC_ACT_SHOT;
 	eth_proto = eth->eth_proto;
 	if (eth_proto == bpf_htons(ETH_P_IP))
-		return process_packet(data, nh_off, data_end, false, ctx);
+		return process_packet(&ptr, eth, nh_off, false, ctx);
 	else if (eth_proto == bpf_htons(ETH_P_IPV6))
-		return process_packet(data, nh_off, data_end, true, ctx);
+		return process_packet(&ptr, eth, nh_off, true, ctx);
 	else
 		return TC_ACT_SHOT;
 }
diff --git a/tools/testing/selftests/bpf/progs/test_xdp.c b/tools/testing/selftests/bpf/progs/test_xdp.c
index d7a9a74b7245..2272b56a8777 100644
--- a/tools/testing/selftests/bpf/progs/test_xdp.c
+++ b/tools/testing/selftests/bpf/progs/test_xdp.c
@@ -20,6 +20,12 @@
 #include <bpf/bpf_endian.h>
 #include "test_iptunnel_common.h"
 
+const size_t tcphdr_sz = sizeof(struct tcphdr);
+const size_t udphdr_sz = sizeof(struct udphdr);
+const size_t ethhdr_sz = sizeof(struct ethhdr);
+const size_t iphdr_sz = sizeof(struct iphdr);
+const size_t ipv6hdr_sz = sizeof(struct ipv6hdr);
+
 struct {
 	__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
 	__uint(max_entries, 256);
@@ -43,8 +49,7 @@ static __always_inline void count_tx(__u32 protocol)
 		*rxcnt_count += 1;
 }
 
-static __always_inline int get_dport(void *trans_data, void *data_end,
-				     __u8 protocol)
+static __always_inline int get_dport(void *trans_data, __u8 protocol)
 {
 	struct tcphdr *th;
 	struct udphdr *uh;
@@ -52,13 +57,9 @@ static __always_inline int get_dport(void *trans_data, void *data_end,
 	switch (protocol) {
 	case IPPROTO_TCP:
 		th = (struct tcphdr *)trans_data;
-		if (th + 1 > data_end)
-			return -1;
 		return th->dest;
 	case IPPROTO_UDP:
 		uh = (struct udphdr *)trans_data;
-		if (uh + 1 > data_end)
-			return -1;
 		return uh->dest;
 	default:
 		return 0;
@@ -75,14 +76,13 @@ static __always_inline void set_ethhdr(struct ethhdr *new_eth,
 	new_eth->h_proto = h_proto;
 }
 
-static __always_inline int handle_ipv4(struct xdp_md *xdp)
+static __always_inline int handle_ipv4(struct xdp_md *xdp, struct bpf_dynptr *xdp_ptr)
 {
-	void *data_end = (void *)(long)xdp->data_end;
-	void *data = (void *)(long)xdp->data;
+	struct bpf_dynptr new_xdp_ptr;
 	struct iptnl_info *tnl;
 	struct ethhdr *new_eth;
 	struct ethhdr *old_eth;
-	struct iphdr *iph = data + sizeof(struct ethhdr);
+	struct iphdr *iph;
 	__u16 *next_iph;
 	__u16 payload_len;
 	struct vip vip = {};
@@ -90,10 +90,12 @@ static __always_inline int handle_ipv4(struct xdp_md *xdp)
 	__u32 csum = 0;
 	int i;
 
-	if (iph + 1 > data_end)
+	iph = bpf_dynptr_data(xdp_ptr, ethhdr_sz,
+			      iphdr_sz + (tcphdr_sz > udphdr_sz ? tcphdr_sz : udphdr_sz));
+	if (!iph)
 		return XDP_DROP;
 
-	dport = get_dport(iph + 1, data_end, iph->protocol);
+	dport = get_dport(iph + 1, iph->protocol);
 	if (dport == -1)
 		return XDP_DROP;
 
@@ -108,37 +110,33 @@ static __always_inline int handle_ipv4(struct xdp_md *xdp)
 	if (!tnl || tnl->family != AF_INET)
 		return XDP_PASS;
 
-	if (bpf_xdp_adjust_head(xdp, 0 - (int)sizeof(struct iphdr)))
+	if (bpf_xdp_adjust_head(xdp, 0 - (int)iphdr_sz))
 		return XDP_DROP;
 
-	data = (void *)(long)xdp->data;
-	data_end = (void *)(long)xdp->data_end;
-
-	new_eth = data;
-	iph = data + sizeof(*new_eth);
-	old_eth = data + sizeof(*iph);
-
-	if (new_eth + 1 > data_end ||
-	    old_eth + 1 > data_end ||
-	    iph + 1 > data_end)
+	bpf_dynptr_from_xdp(xdp, 0, &new_xdp_ptr);
+	new_eth = bpf_dynptr_data(&new_xdp_ptr, 0, ethhdr_sz + iphdr_sz + ethhdr_sz);
+	if (!new_eth)
 		return XDP_DROP;
 
+	iph = (struct iphdr *)(new_eth + 1);
+	old_eth = (struct ethhdr *)(iph + 1);
+
 	set_ethhdr(new_eth, old_eth, tnl, bpf_htons(ETH_P_IP));
 
 	iph->version = 4;
-	iph->ihl = sizeof(*iph) >> 2;
+	iph->ihl = iphdr_sz >> 2;
 	iph->frag_off =	0;
 	iph->protocol = IPPROTO_IPIP;
 	iph->check = 0;
 	iph->tos = 0;
-	iph->tot_len = bpf_htons(payload_len + sizeof(*iph));
+	iph->tot_len = bpf_htons(payload_len + iphdr_sz);
 	iph->daddr = tnl->daddr.v4;
 	iph->saddr = tnl->saddr.v4;
 	iph->ttl = 8;
 
 	next_iph = (__u16 *)iph;
 #pragma clang loop unroll(full)
-	for (i = 0; i < sizeof(*iph) >> 1; i++)
+	for (i = 0; i < iphdr_sz >> 1; i++)
 		csum += *next_iph++;
 
 	iph->check = ~((csum & 0xffff) + (csum >> 16));
@@ -148,22 +146,23 @@ static __always_inline int handle_ipv4(struct xdp_md *xdp)
 	return XDP_TX;
 }
 
-static __always_inline int handle_ipv6(struct xdp_md *xdp)
+static __always_inline int handle_ipv6(struct xdp_md *xdp, struct bpf_dynptr *xdp_ptr)
 {
-	void *data_end = (void *)(long)xdp->data_end;
-	void *data = (void *)(long)xdp->data;
+	struct bpf_dynptr new_xdp_ptr;
 	struct iptnl_info *tnl;
 	struct ethhdr *new_eth;
 	struct ethhdr *old_eth;
-	struct ipv6hdr *ip6h = data + sizeof(struct ethhdr);
+	struct ipv6hdr *ip6h;
 	__u16 payload_len;
 	struct vip vip = {};
 	int dport;
 
-	if (ip6h + 1 > data_end)
+	ip6h = bpf_dynptr_data(xdp_ptr, ethhdr_sz,
+			       ipv6hdr_sz + (tcphdr_sz > udphdr_sz ? tcphdr_sz : udphdr_sz));
+	if (!ip6h)
 		return XDP_DROP;
 
-	dport = get_dport(ip6h + 1, data_end, ip6h->nexthdr);
+	dport = get_dport(ip6h + 1, ip6h->nexthdr);
 	if (dport == -1)
 		return XDP_DROP;
 
@@ -178,26 +177,23 @@ static __always_inline int handle_ipv6(struct xdp_md *xdp)
 	if (!tnl || tnl->family != AF_INET6)
 		return XDP_PASS;
 
-	if (bpf_xdp_adjust_head(xdp, 0 - (int)sizeof(struct ipv6hdr)))
+	if (bpf_xdp_adjust_head(xdp, 0 - (int)ipv6hdr_sz))
 		return XDP_DROP;
 
-	data = (void *)(long)xdp->data;
-	data_end = (void *)(long)xdp->data_end;
-
-	new_eth = data;
-	ip6h = data + sizeof(*new_eth);
-	old_eth = data + sizeof(*ip6h);
-
-	if (new_eth + 1 > data_end || old_eth + 1 > data_end ||
-	    ip6h + 1 > data_end)
+	bpf_dynptr_from_xdp(xdp, 0, &new_xdp_ptr);
+	new_eth = bpf_dynptr_data(&new_xdp_ptr, 0, ethhdr_sz + ipv6hdr_sz + ethhdr_sz);
+	if (!new_eth)
 		return XDP_DROP;
 
+	ip6h = (struct ipv6hdr *)(new_eth + 1);
+	old_eth = (struct ethhdr *)(ip6h + 1);
+
 	set_ethhdr(new_eth, old_eth, tnl, bpf_htons(ETH_P_IPV6));
 
 	ip6h->version = 6;
 	ip6h->priority = 0;
 	memset(ip6h->flow_lbl, 0, sizeof(ip6h->flow_lbl));
-	ip6h->payload_len = bpf_htons(bpf_ntohs(payload_len) + sizeof(*ip6h));
+	ip6h->payload_len = bpf_htons(bpf_ntohs(payload_len) + ipv6hdr_sz);
 	ip6h->nexthdr = IPPROTO_IPV6;
 	ip6h->hop_limit = 8;
 	memcpy(ip6h->saddr.s6_addr32, tnl->saddr.v6, sizeof(tnl->saddr.v6));
@@ -211,21 +207,22 @@ static __always_inline int handle_ipv6(struct xdp_md *xdp)
 SEC("xdp")
 int _xdp_tx_iptunnel(struct xdp_md *xdp)
 {
-	void *data_end = (void *)(long)xdp->data_end;
-	void *data = (void *)(long)xdp->data;
-	struct ethhdr *eth = data;
+	struct bpf_dynptr ptr;
+	struct ethhdr *eth;
 	__u16 h_proto;
 
-	if (eth + 1 > data_end)
+	bpf_dynptr_from_xdp(xdp, 0, &ptr);
+	eth = bpf_dynptr_data(&ptr, 0, ethhdr_sz);
+	if (!eth)
 		return XDP_DROP;
 
 	h_proto = eth->h_proto;
 
 	if (h_proto == bpf_htons(ETH_P_IP))
-		return handle_ipv4(xdp);
+		return handle_ipv4(xdp, &ptr);
 	else if (h_proto == bpf_htons(ETH_P_IPV6))
 
-		return handle_ipv6(xdp);
+		return handle_ipv6(xdp, &ptr);
 	else
 		return XDP_DROP;
 }
diff --git a/tools/testing/selftests/bpf/test_tcp_hdr_options.h b/tools/testing/selftests/bpf/test_tcp_hdr_options.h
index 6118e3ab61fc..56c9f8a3ad3d 100644
--- a/tools/testing/selftests/bpf/test_tcp_hdr_options.h
+++ b/tools/testing/selftests/bpf/test_tcp_hdr_options.h
@@ -50,6 +50,7 @@ struct linum_err {
 
 #define TCPOPT_EOL		0
 #define TCPOPT_NOP		1
+#define TCPOPT_MSS		2
 #define TCPOPT_WINDOW		3
 #define TCPOPT_EXP		254
 
