root/tools/testing/selftests/bpf/prog_tests/sockmap_strp.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);
}

static void test_sockmap_strp_parser_reject(void)
{
        struct test_sockmap_strp *strp = NULL;
        int parser_mod, parser_ro, link;
        int err, map;

        strp = test_sockmap_strp__open_and_load();
        if (!ASSERT_OK_PTR(strp, "test_sockmap_strp__open_and_load"))
                return;

        map = bpf_map__fd(strp->maps.sock_map);
        parser_mod = bpf_program__fd(strp->progs.prog_skb_parser_resize);
        parser_ro = bpf_program__fd(strp->progs.prog_skb_parser);

        err = bpf_prog_attach(parser_mod, map, BPF_SK_SKB_STREAM_PARSER, 0);
        ASSERT_ERR(err, "bpf_prog_attach parser_mod");

        link = bpf_link_create(parser_ro, map, BPF_SK_SKB_STREAM_PARSER, NULL);
        if (!ASSERT_GE(link, 0, "bpf_link_create parser_ro"))
                goto out;

        err = bpf_link_update(link, parser_mod, NULL);
        ASSERT_ERR(err, "bpf_link_update parser_mod");
out:
        if (link >= 0)
                close(link);
        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);
        if (test__start_subtest("sockmap strp parser reject pkt mod"))
                test_sockmap_strp_parser_reject();
}