Subject: Re: [PATCH bpf v8 5/5] selftests/bpf: add strparser test for bpf

From: Jakub Sitnicki <[email-masked]>
Date: Tue, 21 Jan 2025 16:06:54 +0100

Thanks for expanding tests.

On Tue, Jan 21, 2025 at 01:07 PM +08, Jiayuan Chen wrote:
> Add test cases for bpf + strparser and separated them from
> sockmap_basic, as strparser has more encapsulation and parsing
> capabilities compared to sockmap.
>
> Signed-off-by: Jiayuan Chen <[email-masked]>
> ---
>  .../selftests/bpf/prog_tests/sockmap_basic.c  |  53 --
>  .../selftests/bpf/prog_tests/sockmap_strp.c   | 452 ++++++++++++++++++
>  .../selftests/bpf/progs/test_sockmap_strp.c   |  53 ++
>  3 files changed, 505 insertions(+), 53 deletions(-)
>  create mode 100644 tools/testing/selftests/bpf/prog_tests/sockmap_strp.c
>  create mode 100644 tools/testing/selftests/bpf/progs/test_sockmap_strp.c
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
> index 0c51b7288978..f8953455db29 100644
> --- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
> +++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
> @@ -531,57 +531,6 @@ static void test_sockmap_skb_verdict_shutdown(void)
>  	test_sockmap_pass_prog__destroy(skel);
>  }
>  
> -static void test_sockmap_stream_pass(void)
> -{
> -	int zero = 0, sent, recvd;
> -	int verdict, parser;
> -	int err, map;
> -	int c = -1, p = -1;
> -	struct test_sockmap_pass_prog *pass = NULL;
> -	char snd[256] = "0123456789";
> -	char rcv[256] = "0";
> -
> -	pass = test_sockmap_pass_prog__open_and_load();
> -	verdict = bpf_program__fd(pass->progs.prog_skb_verdict);
> -	parser = bpf_program__fd(pass->progs.prog_skb_parser);
> -	map = bpf_map__fd(pass->maps.sock_map_rx);
> -
> -	err = bpf_prog_attach(parser, map, BPF_SK_SKB_STREAM_PARSER, 0);
> -	if (!ASSERT_OK(err, "bpf_prog_attach stream parser"))
> -		goto out;
> -
> -	err = bpf_prog_attach(verdict, map, BPF_SK_SKB_STREAM_VERDICT, 0);
> -	if (!ASSERT_OK(err, "bpf_prog_attach stream verdict"))
> -		goto out;
> -
> -	err = create_pair(AF_INET, SOCK_STREAM, &c, &p);
> -	if (err)
> -		goto out;
> -
> -	/* sk_data_ready of 'p' will be replaced by strparser handler */
> -	err = bpf_map_update_elem(map, &zero, &p, BPF_NOEXIST);
> -	if (!ASSERT_OK(err, "bpf_map_update_elem(p)"))
> -		goto out_close;
> -
> -	/*
> -	 * as 'prog_skb_parser' return the original skb len and
> -	 * 'prog_skb_verdict' return SK_PASS, the kernel will just
> -	 * pass it through to original socket 'p'
> -	 */
> -	sent = xsend(c, snd, sizeof(snd), 0);
> -	ASSERT_EQ(sent, sizeof(snd), "xsend(c)");
> -
> -	recvd = recv_timeout(p, rcv, sizeof(rcv), SOCK_NONBLOCK,
> -			     IO_TIMEOUT_SEC);
> -	ASSERT_EQ(recvd, sizeof(rcv), "recv_timeout(p)");
> -
> -out_close:
> -	close(c);
> -	close(p);
> -
> -out:
> -	test_sockmap_pass_prog__destroy(pass);
> -}
>  
>  static void test_sockmap_skb_verdict_fionread(bool pass_prog)
>  {
> @@ -1101,8 +1050,6 @@ void test_sockmap_basic(void)
>  		test_sockmap_progs_query(BPF_SK_SKB_VERDICT);
>  	if (test__start_subtest("sockmap skb_verdict shutdown"))
>  		test_sockmap_skb_verdict_shutdown();
> -	if (test__start_subtest("sockmap stream parser and verdict pass"))
> -		test_sockmap_stream_pass();
>  	if (test__start_subtest("sockmap skb_verdict fionread"))
>  		test_sockmap_skb_verdict_fionread(true);
>  	if (test__start_subtest("sockmap skb_verdict fionread on drop"))
> diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_strp.c b/tools/testing/selftests/bpf/prog_tests/sockmap_strp.c
> new file mode 100644
> index 000000000000..01ed1fca1d9c
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/sockmap_strp.c
> @@ -0,0 +1,452 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <error.h>
> +#include <netinet/tcp.h>
> +#include <test_progs.h>
> +#include "sockmap_helpers.h"
> +#include "test_skmsg_load_helpers.skel.h"
> +#include "test_sockmap_strp.skel.h"

Nit: add new line to separate cpp defines visually

> +#define STRP_PKT_HEAD_LEN 4
> +#define STRP_PKT_BODY_LEN 6
> +#define STRP_PKT_FULL_LEN (STRP_PKT_HEAD_LEN + STRP_PKT_BODY_LEN)

Nit: add new line to constants visually

> +static const char packet[STRP_PKT_FULL_LEN] = "head+body\0";
> +static const int test_packet_num = 100;
> +
> +/* current implementation of tcp_bpf_recvmsg_parser() invoke

Nit: grammar, "invoke*s*"

> + * data_ready with sk held if skb exist in sk_receive_queue.

Nit: grammar, "exist*s*"

> + * Then for data_ready implementation of strparser, it will
> + * delay the read operation if sk was held and EAGAIN is returned.
> + */
> +static int sockmap_strp_consume_pre_data(int p)
> +{
> +	int recvd;
> +	bool retried = false;
> +	char rcv[10];
> +
> +retry:
> +	errno = 0;
> +	recvd = recv_timeout(p, rcv, sizeof(rcv), 0, 1);
> +	if (recvd < 0 && errno == EAGAIN && retried == false) {
> +		/* On the first call, EAGAIN will certainly be returned.
> +		 * Waiting 1 second is pretty enough wait workqueue finish.

Nit: style, "Wait for workqueue to finish."

> +		 */
> +		sleep(1);
> +		retried = true;
> +		goto retry;
> +	}
> +
> +	if (!ASSERT_EQ(recvd, STRP_PKT_FULL_LEN, "recv_timeout(pre-data)") ||

Meaningful error message like "recv error or truncated data" would be
better. ASSERT_EQ / CHECK macros print the function name, so
"(pre-data)" tag is redundant.

> +	    !ASSERT_OK(memcmp(packet, rcv, STRP_PKT_FULL_LEN),
> +				"memcmp pre-data"))

Suggested error message: "data mismatch".

> +		return -1;
> +	return 0;
> +}
> +
> +static struct test_sockmap_strp *sockmap_strp_init(int *out_map, bool pass,
> +						   bool need_parser)
> +{
> +	struct test_sockmap_strp *strp = NULL;
> +	int verdict, parser;
> +	int err;
> +
> +	strp = test_sockmap_strp__open_and_load();
> +	*out_map = bpf_map__fd(strp->maps.sock_map);
> +
> +	if (need_parser)
> +		parser = bpf_program__fd(strp->progs.prog_skb_parser_partial);
> +	else
> +		parser = bpf_program__fd(strp->progs.prog_skb_parser);
> +
> +	if (pass)
> +		verdict = bpf_program__fd(strp->progs.prog_skb_verdict_pass);
> +	else
> +		verdict = bpf_program__fd(strp->progs.prog_skb_verdict);
> +
> +	err = bpf_prog_attach(parser, *out_map, BPF_SK_SKB_STREAM_PARSER, 0);
> +	if (!ASSERT_OK(err, "bpf_prog_attach stream parser"))
> +		goto err;
> +
> +	err = bpf_prog_attach(verdict, *out_map, BPF_SK_SKB_STREAM_VERDICT, 0);
> +	if (!ASSERT_OK(err, "bpf_prog_attach stream verdict"))
> +		goto err;
> +
> +	return strp;
> +err:
> +	test_sockmap_strp__destroy(strp);
> +	return NULL;
> +}
> +
> +/* Dispatch packets to different socket by packet size:
> + *
> + *                      ------  ------
> + *                     | pkt4 || pkt1 |... > remote socket
> + *  ------ ------     / ------  ------
> + * | pkt8 | pkt7 |...
> + *  ------ ------     \ ------  ------
> + *                     | pkt3 || pkt2 |... > local socket
> + *                      ------  ------
> + */
> +static void test_sockmap_strp_dispatch_pkt(int family, int sotype)
> +{
> +	int i, j, zero = 0, one = 1, recvd;
> +	int err, map;
> +	int c0 = -1, p0 = -1, c1 = -1, p1 = -1;
> +	struct test_sockmap_strp *strp = NULL;
> +	int test_cnt = 6;
> +	char rcv[10];
> +	struct {
> +		char	data[7];
> +		int	data_len;
> +		int	send_cnt;
> +		int	*receiver;
> +	} send_dir[2] = {
> +		/* data expected to deliver to local */
> +		{"llllll", 6, 0, &p0},
> +		/* data expected to deliver to remote */
> +		{"rrrrr",  5, 0, &c1}
> +	};
> +
> +	strp = sockmap_strp_init(&map, false, false);
> +	if (!ASSERT_TRUE(strp, "sockmap_strp_init"))
> +		return;
> +
> +	err = create_socket_pairs(family, sotype, &c0, &c1, &p0, &p1);
> +	if (!ASSERT_OK(err, "create_socket_pairs()"))
> +		goto out;
> +
> +	err = bpf_map_update_elem(map, &zero, &p0, BPF_NOEXIST);
> +	if (!ASSERT_OK(err, "bpf_map_update_elem(p0)"))
> +		goto out_close;
> +
> +	err = bpf_map_update_elem(map, &one, &p1, BPF_NOEXIST);
> +	if (!ASSERT_OK(err, "bpf_map_update_elem(p1)"))
> +		goto out_close;
> +
> +	err = setsockopt(c1, IPPROTO_TCP, TCP_NODELAY, &zero, sizeof(zero));
> +	if (!ASSERT_OK(err, "setsockopt(TCP_NODELAY)"))
> +		goto out_close;
> +
> +	/* deliver data with data size greater than 5 to local */
> +	strp->data->verdict_max_size = 5;
> +
> +	for (i = 0; i < test_cnt; i++) {
> +		int d = i % 2;
> +
> +		xsend(c0, send_dir[d].data, send_dir[d].data_len, 0);
> +		send_dir[d].send_cnt++;
> +	}
> +
> +	for (i = 0; i < 2; i++) {
> +		for (j = 0; j < send_dir[i].send_cnt; j++) {
> +			int expected = send_dir[i].data_len;
> +
> +			recvd = recv_timeout(*send_dir[i].receiver, rcv,
> +					     expected, MSG_DONTWAIT,
> +					     IO_TIMEOUT_SEC);
> +			if (!ASSERT_EQ(recvd, expected, "recv_timeout()"))
> +				goto out_close;
> +			if (!ASSERT_OK(memcmp(send_dir[i].data, rcv, recvd),
> +				       "memcmp(rcv)"))
> +				goto out_close;
> +		}
> +	}
> +out_close:
> +	close(c0);
> +	close(c1);
> +	close(p0);
> +	close(p1);
> +out:
> +	test_sockmap_strp__destroy(strp);
> +}
> +
> +/* We have multiple packets in one skb
> + * ------------ ------------ ------------
> + * |  packet1  |   packet2  |  ...
> + * ------------ ------------ ------------
> + */
> +static void test_sockmap_strp_multiple_pkt(int family, int sotype)
> +{
> +	int i, zero = 0;
> +	int sent, recvd, total;
> +	int err, map;
> +	int c = -1, p = -1;
> +	struct test_sockmap_strp *strp = NULL;
> +	char *snd = NULL, *rcv = NULL;
> +
> +	strp = sockmap_strp_init(&map, true, true);
> +	if (!ASSERT_TRUE(strp, "sockmap_strp_init"))
> +		return;
> +
> +	err = create_pair(family, sotype, &c, &p);
> +	if (err)
> +		goto out;
> +
> +	err = bpf_map_update_elem(map, &zero, &p, BPF_NOEXIST);
> +	if (!ASSERT_OK(err, "bpf_map_update_elem(zero, p)"))
> +		goto out_close;
> +
> +	/* construct multiple packets in one buffer */
> +	total = test_packet_num * STRP_PKT_FULL_LEN;
> +	snd = malloc(total);
> +	rcv = malloc(total + 1);
> +	if (!ASSERT_TRUE(snd, "malloc(snd)") ||
> +	    !ASSERT_TRUE(rcv, "malloc(rcv)"))
> +		goto out_close;
> +
> +	for (i = 0; i < test_packet_num; i++) {
> +		memcpy(snd + i * STRP_PKT_FULL_LEN,
> +		       packet, STRP_PKT_FULL_LEN);
> +	}
> +
> +	sent = xsend(c, snd, total, 0);
> +	if (!ASSERT_EQ(sent, total, "xsend(c)"))
> +		goto out_close;
> +
> +	/* try to recv one more byte to avoid truncation check */
> +	recvd = recv_timeout(p, rcv, total + 1, MSG_DONTWAIT, IO_TIMEOUT_SEC);
> +	if (!ASSERT_EQ(recvd, total, "recv(rcv)"))
> +		goto out_close;
> +
> +	/* we sent TCP segment with multiple encapsulation
> +	 * then check whether packets are handled correctly
> +	 */
> +	if (!ASSERT_OK(memcmp(snd, rcv, total), "memcmp(snd, rcv)"))
> +		goto out_close;
> +
> +out_close:
> +	close(c);
> +	close(p);
> +	if (snd)
> +		free(snd);
> +	if (rcv)
> +		free(rcv);
> +out:
> +	test_sockmap_strp__destroy(strp);
> +}
> +
> +/* Test strparser with partial read */
> +static void test_sockmap_strp_partial_read(int family, int sotype)
> +{
> +	int zero = 0, recvd, off;
> +	int err, map;
> +	int c = -1, p = -1;
> +	struct test_sockmap_strp *strp = NULL;
> +	char rcv[STRP_PKT_FULL_LEN + 1] = "0";
> +
> +	strp = sockmap_strp_init(&map, true, true);
> +	if (!ASSERT_TRUE(strp, "sockmap_strp_init"))
> +		return;
> +
> +	err = create_pair(family, sotype, &c, &p);
> +	if (err)
> +		goto out;
> +
> +	/* sk_data_ready of 'p' will be replaced by strparser handler */
> +	err = bpf_map_update_elem(map, &zero, &p, BPF_NOEXIST);
> +	if (!ASSERT_OK(err, "bpf_map_update_elem(zero, p)"))
> +		goto out_close;
> +
> +	/* 1.1 send partial head, 1 byte header left*/

Nit: missing space before comment-close tag, "left */".

> +	off = STRP_PKT_HEAD_LEN - 1;
> +	xsend(c, packet, off, 0);
> +	recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT, 1);
> +	if (!ASSERT_EQ(-1, recvd, "insufficient head, should no data recvd"))

"partial head sent, expected no data"

> +		goto out_close;
> +
> +	/* 1.2 send remaining head and body */
> +	xsend(c, packet + off, STRP_PKT_FULL_LEN - off, 0);
> +	recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT, IO_TIMEOUT_SEC);
> +	if (!ASSERT_EQ(recvd, STRP_PKT_FULL_LEN, "should full data recvd"))

"expected full data"

> +		goto out_close;
> +
> +	/* 2.1 send partial head, 1 byte header left */
> +	off = STRP_PKT_HEAD_LEN - 1;
> +	xsend(c, packet, off, 0);
> +
> +	/* 2.2 send remaining head and partial body, 1 byte body left */
> +	xsend(c, packet + off, STRP_PKT_FULL_LEN - off - 1, 0);
> +	off = STRP_PKT_FULL_LEN - 1;
> +	recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT, 1);
> +	if (!ASSERT_EQ(-1, recvd, "insufficient body, should no data read"))

"partial body sent, expected no data"

> +		goto out_close;
> +
> +	/* 2.3 send remaining body */
> +	xsend(c, packet + off, STRP_PKT_FULL_LEN - off, 0);
> +	recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT, IO_TIMEOUT_SEC);
> +	if (!ASSERT_EQ(recvd, STRP_PKT_FULL_LEN, "should full data recvd"))

"expected full data"

> +		goto out_close;
> +
> +out_close:
> +	close(c);
> +	close(p);
> +
> +out:
> +	test_sockmap_strp__destroy(strp);
> +}
> +
> +/* Test simple socket read/write with strparser + FIONREAD */
> +static void test_sockmap_strp_pass(int family, int sotype, bool fionread)
> +{
> +	int zero = 0, pkt_size = STRP_PKT_FULL_LEN, sent, recvd, avail;
> +	int err, map;
> +	int c = -1, p = -1;
> +	int test_cnt = 10, i;
> +	struct test_sockmap_strp *strp = NULL;
> +	char rcv[STRP_PKT_FULL_LEN + 1] = "0";
> +
> +	strp = sockmap_strp_init(&map, true, true);
> +	if (!ASSERT_TRUE(strp, "sockmap_strp_init"))
> +		return;
> +
> +	err = create_pair(family, sotype, &c, &p);
> +	if (err)
> +		goto out;
> +
> +	/* inject some data before bpf process, it should be read
> +	 * correctly because we check sk_receive_queue in
> +	 * tcp_bpf_recvmsg_parser()
> +	 */
> +	sent = xsend(c, packet, pkt_size, 0);
> +	if (!ASSERT_EQ(sent, pkt_size, "xsend(pre-data)"))
> +		goto out_close;
> +
> +	/* sk_data_ready of 'p' will be replaced by strparser handler */
> +	err = bpf_map_update_elem(map, &zero, &p, BPF_NOEXIST);
> +	if (!ASSERT_OK(err, "bpf_map_update_elem(p)"))
> +		goto out_close;
> +
> +	/* consume previous data we injected */
> +	if (sockmap_strp_consume_pre_data(p))
> +		goto out_close;
> +
> +	/* Previously, we encountered issues such as deadlocks and
> +	 * sequence errors that resulted in the inability to read
> +	 * continuously. Therefore, we perform multiple iterations
> +	 * of testing here.
> +	 */
> +	for (i = 0; i < test_cnt; i++) {
> +		sent = xsend(c, packet, pkt_size, 0);
> +		if (!ASSERT_EQ(sent, pkt_size, "xsend(c)"))
> +			goto out_close;
> +
> +		recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT,
> +				     IO_TIMEOUT_SEC);
> +		if (!ASSERT_EQ(recvd, pkt_size, "recv_timeout(p)") ||
> +		    !ASSERT_OK(memcmp(packet, rcv, pkt_size),
> +				  "memcmp"))
> +			goto out_close;
> +	}
> +
> +	if (fionread) {
> +		sent = xsend(c, packet, pkt_size, 0);
> +		if (!ASSERT_EQ(sent, pkt_size, "second xsend(c)"))
> +			goto out_close;
> +
> +		err = ioctl(p, FIONREAD, &avail);
> +		if (!ASSERT_OK(err, "ioctl(FIONREAD) error") ||
> +		    !ASSERT_EQ(avail, pkt_size, "ioctl(FIONREAD)"))
> +			goto out_close;
> +
> +		recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT,
> +				     IO_TIMEOUT_SEC);
> +		if (!ASSERT_EQ(recvd, pkt_size, "second recv_timeout(p)") ||
> +		    !ASSERT_OK(memcmp(packet, rcv, pkt_size),
> +			      "second memcmp"))
> +			goto out_close;
> +	}
> +
> +out_close:
> +	close(c);
> +	close(p);
> +
> +out:
> +	test_sockmap_strp__destroy(strp);
> +}
> +
> +/* Test strparser with verdict mode */
> +static void test_sockmap_strp_verdict(int family, int sotype)
> +{
> +	int zero = 0, one = 1, sent, recvd, off;
> +	int err, map;
> +	int c0 = -1, p0 = -1, c1 = -1, p1 = -1;
> +	struct test_sockmap_strp *strp = NULL;
> +	char rcv[STRP_PKT_FULL_LEN + 1] = "0";
> +
> +	strp = sockmap_strp_init(&map, false, true);
> +	if (!ASSERT_TRUE(strp, "sockmap_strp_init"))
> +		return;
> +
> +	/* We simulate a reverse proxy server.
> +	 * When p0 receives data from c0, we forward it to c1.
> +	 * From c1's perspective, it will consider this data
> +	 * as being sent by p1.
> +	 */
> +	err = create_socket_pairs(family, sotype, &c0, &c1, &p0, &p1);
> +	if (!ASSERT_OK(err, "create_socket_pairs()"))
> +		goto out;
> +
> +	err = bpf_map_update_elem(map, &zero, &p0, BPF_NOEXIST);
> +	if (!ASSERT_OK(err, "bpf_map_update_elem(p0)"))
> +		goto out_close;
> +
> +	err = bpf_map_update_elem(map, &one, &p1, BPF_NOEXIST);
> +	if (!ASSERT_OK(err, "bpf_map_update_elem(c1)"))
> +		goto out_close;
> +
> +	sent = xsend(c0, packet, STRP_PKT_FULL_LEN, 0);
> +	if (!ASSERT_EQ(sent, STRP_PKT_FULL_LEN, "xsend(c0)"))
> +		goto out_close;
> +
> +	recvd = recv_timeout(c1, rcv, sizeof(rcv), MSG_DONTWAIT,
> +			     IO_TIMEOUT_SEC);
> +	if (!ASSERT_EQ(recvd, STRP_PKT_FULL_LEN, "recv_timeout(p1)") ||
> +	    !ASSERT_OK(memcmp(packet, rcv, STRP_PKT_FULL_LEN),
> +			  "received data does not match the sent data"))
> +		goto out_close;
> +
> +	/* send again to ensure the stream is functioning correctly. */
> +	sent = xsend(c0, packet, STRP_PKT_FULL_LEN, 0);
> +	if (!ASSERT_EQ(sent, STRP_PKT_FULL_LEN, "second xsend(c0)"))
> +		goto out_close;
> +
> +	/* partial read */
> +	off = STRP_PKT_FULL_LEN / 2;
> +	recvd = recv_timeout(c1, rcv, off, MSG_DONTWAIT,
> +			     IO_TIMEOUT_SEC);
> +	recvd += recv_timeout(c1, rcv + off, sizeof(rcv) - off, MSG_DONTWAIT,
> +			      IO_TIMEOUT_SEC);
> +
> +	if (!ASSERT_EQ(recvd, STRP_PKT_FULL_LEN, "partial recv_timeout(c1)") ||
> +	    !ASSERT_OK(memcmp(packet, rcv, STRP_PKT_FULL_LEN),
> +			  "partial received data does not match the sent data"))
> +		goto out_close;
> +
> +out_close:
> +	close(c0);
> +	close(c1);
> +	close(p0);
> +	close(p1);
> +out:
> +	test_sockmap_strp__destroy(strp);
> +}
> +
> +void test_sockmap_strp(void)
> +{
> +	if (test__start_subtest("sockmap strp tcp pass"))
> +		test_sockmap_strp_pass(AF_INET, SOCK_STREAM, false);
> +	if (test__start_subtest("sockmap strp tcp v6 pass"))
> +		test_sockmap_strp_pass(AF_INET6, SOCK_STREAM, false);
> +	if (test__start_subtest("sockmap strp tcp pass fionread"))
> +		test_sockmap_strp_pass(AF_INET, SOCK_STREAM, true);
> +	if (test__start_subtest("sockmap strp tcp v6 pass fionread"))
> +		test_sockmap_strp_pass(AF_INET6, SOCK_STREAM, true);
> +	if (test__start_subtest("sockmap strp tcp verdict"))
> +		test_sockmap_strp_verdict(AF_INET, SOCK_STREAM);
> +	if (test__start_subtest("sockmap strp tcp v6 verdict"))
> +		test_sockmap_strp_verdict(AF_INET6, SOCK_STREAM);
> +	if (test__start_subtest("sockmap strp tcp partial read"))
> +		test_sockmap_strp_partial_read(AF_INET, SOCK_STREAM);
> +	if (test__start_subtest("sockmap strp tcp multiple packets"))
> +		test_sockmap_strp_multiple_pkt(AF_INET, SOCK_STREAM);
> +	if (test__start_subtest("sockmap strp tcp dispatch"))
> +		test_sockmap_strp_dispatch_pkt(AF_INET, SOCK_STREAM);
> +}
> diff --git a/tools/testing/selftests/bpf/progs/test_sockmap_strp.c b/tools/testing/selftests/bpf/progs/test_sockmap_strp.c
> new file mode 100644
> index 000000000000..dde3d5bec515
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/test_sockmap_strp.c
> @@ -0,0 +1,53 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <linux/bpf.h>
> +#include <bpf/bpf_helpers.h>
> +#include <bpf/bpf_endian.h>
> +int verdict_max_size = 10000;
> +struct {
> +	__uint(type, BPF_MAP_TYPE_SOCKMAP);
> +	__uint(max_entries, 20);
> +	__type(key, int);
> +	__type(value, int);
> +} sock_map SEC(".maps");
> +
> +SEC("sk_skb/stream_verdict")
> +int prog_skb_verdict(struct __sk_buff *skb)
> +{
> +	__u32 one = 1;
> +
> +	if (skb->len > verdict_max_size)
> +		return SK_PASS;
> +
> +	return bpf_sk_redirect_map(skb, &sock_map, one, 0);
> +}
> +
> +SEC("sk_skb/stream_verdict")
> +int prog_skb_verdict_pass(struct __sk_buff *skb)
> +{
> +	return SK_PASS;
> +}
> +
> +SEC("sk_skb/stream_parser")
> +int prog_skb_parser(struct __sk_buff *skb)
> +{
> +	return skb->len;
> +}
> +
> +SEC("sk_skb/stream_parser")
> +int prog_skb_parser_partial(struct __sk_buff *skb)
> +{
> +	/* agreement with the test program on a 4-byte size header
> +	 * and 6-byte body.
> +	 */
> +	if (skb->len < 4) {
> +		/* need more header to determine full length */
> +		return 0;
> +	}
> +	/* return full length decoded from header.
> +	 * the return value may be larger than skb->len which
> +	 * means framework must wait body coming.
> +	 */
> +	return 10;
> +}
> +
> +char _license[] SEC("license") = "GPL";