mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

Add test cases for bpf + strparser and separated them from sockmap_basic, as strparser has more encapsulation and parsing capabilities compared to standard sockmap. Signed-off-by: Jiayuan Chen <mrpre@163.com> Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org> Acked-by: Jakub Sitnicki <jakub@cloudflare.com> Acked-by: John Fastabend <john.fastabend@gmail.com> Link: https://patch.msgid.link/20250122100917.49845-6-mrpre@163.com
454 lines
13 KiB
C
454 lines
13 KiB
C
// 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"
|
|
|
|
#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)
|
|
|
|
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() invokes data_ready
|
|
* with sk held if an skb exists in sk_receive_queue. Then for the
|
|
* data_ready implementation of strparser, it will delay the read
|
|
* operation if sk is 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.
|
|
* A 1-second wait is enough for the workqueue to finish.
|
|
*/
|
|
sleep(1);
|
|
retried = true;
|
|
goto retry;
|
|
}
|
|
|
|
if (!ASSERT_EQ(recvd, STRP_PKT_FULL_LEN, "recv error or truncated data") ||
|
|
!ASSERT_OK(memcmp(packet, rcv, STRP_PKT_FULL_LEN),
|
|
"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),
|
|
"data mismatch"))
|
|
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), "data mismatch"))
|
|
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 */
|
|
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, "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, "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, "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, "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, data mismatch"))
|
|
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, data mismatch"))
|
|
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(p1)"))
|
|
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(c1)") ||
|
|
!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);
|
|
}
|