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 mrpre@163.com
.../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"))
if (test__start_subtest("sockmap skb_verdict fionread")) test_sockmap_skb_verdict_fionread(true); if (test__start_subtest("sockmap skb_verdict fionread on drop"))test_sockmap_stream_pass();
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";