diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 19:05:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 19:05:44 +0000 |
commit | b045529c40c83601909dca7b76a53498e9a70f33 (patch) | |
tree | 88371572105933fd950676c07b3a12163a0c9de0 /tests/libknot/test_xdp_tcp.c | |
parent | Initial commit. (diff) | |
download | knot-b045529c40c83601909dca7b76a53498e9a70f33.tar.xz knot-b045529c40c83601909dca7b76a53498e9a70f33.zip |
Adding upstream version 3.3.4.upstream/3.3.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/libknot/test_xdp_tcp.c')
-rw-r--r-- | tests/libknot/test_xdp_tcp.c | 638 |
1 files changed, 638 insertions, 0 deletions
diff --git a/tests/libknot/test_xdp_tcp.c b/tests/libknot/test_xdp_tcp.c new file mode 100644 index 0000000..3e366b1 --- /dev/null +++ b/tests/libknot/test_xdp_tcp.c @@ -0,0 +1,638 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <unistd.h> + +#include "tap/basic.h" +#include "libknot/error.h" +#include "libknot/xdp/msg_init.h" +#include "libknot/xdp/tcp.c" +#include "libknot/xdp/tcp_iobuf.c" +#include "libknot/xdp/bpf-user.h" + +#define INFTY INT32_MAX + +knot_tcp_table_t *test_table = NULL; +knot_tcp_table_t *test_syn_table = NULL; +#define TEST_TABLE_SIZE 100 + +size_t sent_acks = 0; +size_t sent_rsts = 0; +size_t sent_syns = 0; +size_t sent_fins = 0; +uint32_t sent_seqno = 0; +uint32_t sent_ackno = 0; +size_t sent2_data = 0; +size_t send2_mss = 0; + +knot_xdp_socket_t *test_sock = NULL; + +struct sockaddr_in test_addr = { AF_INET, 0, { 127 + (1 << 24) }, { 0 } }; + +knot_tcp_conn_t *test_conn = NULL; + +/*! + * \brief Length of timeout-watching list. + */ +static size_t tcp_table_timeout_length(knot_tcp_table_t *table) +{ + return list_size(tcp_table_timeout(table)); +} + +/*! + * \brief Clean up old TCP connection w/o sending RST or FIN. + * + * \param tcp_table TCP connection table to clean up. + * \param timeout Remove connections older than this (usecs). + * \param at_least Remove at least this number of connections. + */ +static void tcp_cleanup(knot_tcp_table_t *tcp_table, uint32_t timeout, + uint32_t at_least) +{ + uint32_t now = get_timestamp(), i = 0; + knot_tcp_conn_t *conn, *next; + WALK_LIST_DELSAFE(conn, next, *tcp_table_timeout(tcp_table)) { + if (i++ < at_least || now - conn->last_active >= timeout) { + tcp_table_remove(tcp_table_re_lookup(conn, tcp_table), tcp_table); + del_conn(conn); + } + } +} + +/*! + * \brief Find connection related to incoming message. + */ +static knot_tcp_conn_t *tcp_table_find(knot_tcp_table_t *table, knot_xdp_msg_t *msg_recv) +{ + uint64_t unused = 0; + return *tcp_table_lookup(&msg_recv->ip_from, &msg_recv->ip_to, &unused, table); +} + +static int mock_send(_unused_ knot_xdp_socket_t *sock, const knot_xdp_msg_t msgs[], + uint32_t n_msgs, _unused_ uint32_t *sent) +{ + ok(n_msgs <= 20, "send: not too many at once"); + for (uint32_t i = 0; i < n_msgs; i++) { + const knot_xdp_msg_t *msg = msgs + i; + + ok(msg->flags & KNOT_XDP_MSG_TCP, "send: is TCP message"); + ok(msg->payload.iov_len == 0, "send: is empty payload"); + + if (msg->flags & KNOT_XDP_MSG_RST) { + ok(!(msg->flags & KNOT_XDP_MSG_ACK), "send: no RST+ACK"); + sent_rsts++; + } else if (msg->flags & KNOT_XDP_MSG_SYN) { + ok(msg->flags & KNOT_XDP_MSG_ACK, "send: is SYN+ACK"); + sent_syns++; + } else if (msg->flags & KNOT_XDP_MSG_FIN) { + ok(msg->flags & KNOT_XDP_MSG_ACK, "send: FIN has always ACK"); + sent_fins++; + } else { + ok(msg->flags & KNOT_XDP_MSG_ACK, "send: is ACK"); + sent_acks++; + } + + sent_seqno = msg->seqno; + sent_ackno = msg->ackno; + } + return KNOT_EOK; +} + +static int mock_send_nocheck(_unused_ knot_xdp_socket_t *sock, const knot_xdp_msg_t msgs[], + uint32_t n_msgs, _unused_ uint32_t *sent) +{ + for (uint32_t i = 0; i < n_msgs; i++) { + const knot_xdp_msg_t *msg = msgs + i; + if (msg->flags & KNOT_XDP_MSG_RST) { + sent_rsts++; + } else if (msg->flags & KNOT_XDP_MSG_SYN) { + sent_syns++; + } else if (msg->flags & KNOT_XDP_MSG_FIN) { + sent_fins++; + } else { + sent_acks++; + } + sent_seqno = msg->seqno; + sent_ackno = msg->ackno; + } + return KNOT_EOK; +} + +static int mock_send2(_unused_ knot_xdp_socket_t *sock, const knot_xdp_msg_t msgs[], + uint32_t n_msgs, _unused_ uint32_t *sent) +{ + ok(n_msgs <= 20, "send2: not too many at once"); + for (uint32_t i = 0; i < n_msgs; i++) { + const knot_xdp_msg_t *msg = msgs + i; + ok(msg->flags & KNOT_XDP_MSG_TCP, "send2: is TCP message"); + ok(msg->flags & KNOT_XDP_MSG_ACK, "send2: has ACK"); + ok(msg->payload.iov_len <= send2_mss, "send2: fulfilled MSS"); + sent2_data += msg->payload.iov_len; + + sent_seqno = msg->seqno; + sent_ackno = msg->ackno; + } + return KNOT_EOK; +} + +static void clean_table(void) +{ + (void)tcp_cleanup(test_table, 0, INFTY); +} + +static void clean_sent(void) +{ + sent_acks = 0; + sent_rsts = 0; + sent_syns = 0; + sent_fins = 0; +} + +static void check_sent(size_t expect_acks, size_t expect_rsts, size_t expect_syns, size_t expect_fins) +{ + is_int(expect_acks, sent_acks, "sent ACKs"); + is_int(expect_rsts, sent_rsts, "sent RSTs"); + is_int(expect_syns, sent_syns, "sent SYNs"); + is_int(expect_fins, sent_fins, "sent FINs"); + clean_sent(); +} + +static void prepare_msg(knot_xdp_msg_t *msg, int flags, uint16_t sport, uint16_t dport) +{ + msg_init(msg, flags | KNOT_XDP_MSG_TCP); + memcpy(&msg->ip_from, &test_addr, sizeof(test_addr)); + memcpy(&msg->ip_to, &test_addr, sizeof(test_addr)); + msg->ip_from.sin6_port = htobe16(sport); + msg->ip_to.sin6_port = htobe16(dport); +} + +static void prepare_seqack(knot_xdp_msg_t *msg, int seq_shift, int ack_shift) +{ + msg->seqno = sent_ackno + seq_shift; + msg->ackno = sent_seqno + ack_shift; +} + +static void prepare_data(knot_xdp_msg_t *msg, const char *bytes, size_t n) +{ + msg->payload.iov_len = n; + msg->payload.iov_base = (void *)bytes; +} + +static void fix_seqack(knot_xdp_msg_t *msg) +{ + knot_tcp_conn_t *conn = tcp_table_find(test_table, msg); + if (conn == NULL) { + conn = tcp_table_find(test_syn_table, msg); + } + assert(conn != NULL); + msg->seqno = conn->seqno; + msg->ackno = conn->ackno; +} + +static void fix_seqacks(knot_xdp_msg_t *msgs, size_t count) +{ + for (size_t i = 0; i < count; i++) { + fix_seqack(&msgs[i]); + } +} + +void test_syn(void) +{ + knot_xdp_msg_t msg; + knot_tcp_relay_t rl = { 0 }; + prepare_msg(&msg, KNOT_XDP_MSG_SYN, 1, 2); + int ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); + is_int(KNOT_EOK, ret, "SYN: relay OK"); + ret = knot_tcp_send(test_sock, &rl, 1, 1); + is_int(KNOT_EOK, ret, "SYN: send OK"); + is_int(msg.seqno + 1, sent_ackno, "SYN: ackno"); + check_sent(0, 0, 1, 0); + is_int(XDP_TCP_SYN, rl.action, "SYN: relay action"); + is_int(XDP_TCP_NOOP, rl.answer, "SYN: relay answer"); + ok(NULL == rl.inbf, "SYN: no payload"); + is_int(0, test_table->usage, "SYN: no connection in normal table"); + is_int(1, test_syn_table->usage, "SYN: one connection in SYN table"); + knot_tcp_conn_t *conn = tcp_table_find(test_syn_table, &msg); + ok(conn != NULL, "SYN: connection present"); + assert(conn); + ok(conn == rl.conn, "SYN: relay points to connection"); + is_int(XDP_TCP_ESTABLISHING, conn->state, "SYN: connection state"); + ok(memcmp(&conn->ip_rem, &msg.ip_from, sizeof(msg.ip_from)) == 0, "SYN: conn IP from"); + ok(memcmp(&conn->ip_loc, &msg.ip_to, sizeof(msg.ip_to)) == 0, "SYN: conn IP to"); + + knot_tcp_cleanup(test_syn_table, &rl, 1); + test_conn = conn; +} + +void test_establish(void) +{ + knot_xdp_msg_t msg; + knot_tcp_relay_t rl = { 0 }; + prepare_msg(&msg, KNOT_XDP_MSG_ACK, 1, 2); + prepare_seqack(&msg, 0, 1); + int ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); + is_int(KNOT_EOK, ret, "establish: relay OK"); + is_int(0, test_syn_table->usage, "SYN: no connection in SYN table"); + is_int(1, test_table->usage, "SYN: one connection in normal table"); + ret = knot_tcp_send(test_sock, &rl, 1, 1); + is_int(KNOT_EOK, ret, "establish: send OK"); + check_sent(0, 0, 0, 0); + is_int(0, rl.auto_answer, "establish: no auto answer"); + + knot_tcp_cleanup(test_table, &rl, 1); + clean_table(); +} + +void test_syn_ack(void) +{ + knot_xdp_msg_t msg; + knot_tcp_relay_t rl = { 0 }; + prepare_msg(&msg, KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK, 1000, 2000); + int ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); + is_int(KNOT_EOK, ret, "SYN+ACK: relay OK"); + ret = knot_tcp_send(test_sock, &rl, 1, 1); + is_int(KNOT_EOK, ret, "SYN+ACK: send OK"); + is_int(msg.seqno + 1, sent_ackno, "SYN+ACK: ackno"); + check_sent(1, 0, 0, 0); + is_int(XDP_TCP_ESTABLISH, rl.action, "SYN+ACK: relay action"); + ok(rl.conn != NULL, "SYN+ACK: connection present"); + + test_conn = rl.conn; + knot_tcp_cleanup(test_table, &rl, 1); +} + +void test_data_fragments(void) +{ + const size_t CONNS = 4; + knot_xdp_msg_t msgs[CONNS]; + knot_tcp_relay_t rls[CONNS]; + memset(rls, 0, CONNS * sizeof(*rls)); + + // first msg contains one whole payload and one fragment + prepare_msg(&msgs[0], KNOT_XDP_MSG_ACK, 1000, 2000); + prepare_seqack(&msgs[0], 0, 0); + prepare_data(&msgs[0], "\x00\x03""xyz""\x00\x04""ab", 9); + + // second msg contains just fragment not completing anything + prepare_msg(&msgs[1], KNOT_XDP_MSG_ACK, 1000, 2000); + prepare_seqack(&msgs[1], 9, 0); + prepare_data(&msgs[1], "c", 1); + + // third msg finishes fragment, contains one whole, and starts new fragment by just half of length info + prepare_msg(&msgs[2], KNOT_XDP_MSG_ACK, 1000, 2000); + prepare_seqack(&msgs[2], 10, 0); + prepare_data(&msgs[2], "d""\x00\x01""i""\x00", 5); + + // fourth msg completes fragment and starts never-finishing one + prepare_msg(&msgs[3], KNOT_XDP_MSG_ACK, 1000, 2000); + prepare_seqack(&msgs[3], 15, 0); + prepare_data(&msgs[3], "\x02""AB""\xff\xff""abcdefghijklmnopqrstuvwxyz...", 34); + + assert(test_table); + int ret = knot_tcp_recv(rls, msgs, CONNS, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); + is_int(KNOT_EOK, ret, "fragments: relay OK"); + assert(test_sock); + ret = knot_tcp_send(test_sock, rls, CONNS, CONNS); + is_int(KNOT_EOK, ret, "fragments: send OK"); + is_int(msgs[3].ackno, sent_seqno, "fragments: seqno"); + is_int(msgs[3].seqno + msgs[3].payload.iov_len, sent_ackno, "fragments: ackno"); + check_sent(4, 0, 0, 0); + + is_int(KNOT_XDP_MSG_ACK, rls[0].auto_answer, "fragments[0]: auto answer"); + ok(rls[0].conn != NULL, "fragments0: connection present"); + ok(rls[0].conn == test_conn, "fragments0: same connection"); + is_int(1, rls[0].inbf->n_inbufs, "fragments0: inbufs count"); + struct iovec *inbufs = rls[0].inbf->inbufs; + is_int(3, inbufs[0].iov_len, "fragments0: data length"); + is_int(0, memcmp("xyz", inbufs[0].iov_base, inbufs[0].iov_len), "fragments0: data"); + + is_int(KNOT_XDP_MSG_ACK, rls[1].auto_answer, "fragments[1]: auto answer"); + is_int(XDP_TCP_NOOP, rls[1].action, "fragments[1]: action"); // NOTE: NOOP + ok(rls[0].conn != NULL, "fragments1: connection present"); + ok(rls[0].conn == test_conn, "fragments1: same connection"); + ok(NULL == rls[1].inbf, "fragments1: inbufs count"); + + is_int(KNOT_XDP_MSG_ACK, rls[2].auto_answer, "fragments[2]: auto answer"); + ok(rls[0].conn != NULL, "fragments2: connection present"); + ok(rls[0].conn == test_conn, "fragments2: same connection"); + is_int(2, rls[2].inbf->n_inbufs, "fragments2: inbufs count"); + inbufs = rls[2].inbf->inbufs; + is_int(4, inbufs[0].iov_len, "fragments2-0: data length"); + is_int(0, memcmp("abcd", inbufs[0].iov_base, inbufs[0].iov_len), "fragments2-0: data"); + is_int(1, inbufs[1].iov_len, "fragments2-1: data length"); + is_int(0, memcmp("i", inbufs[1].iov_base, inbufs[1].iov_len), "fragments2-1: data"); + + is_int(KNOT_XDP_MSG_ACK, rls[3].auto_answer, "fragments[3]: auto answer"); + ok(rls[0].conn != NULL, "fragments3: connection present"); + ok(rls[0].conn == test_conn, "fragments3: same connection"); + is_int(1, rls[3].inbf->n_inbufs, "fragments3: inbufs count"); + inbufs = rls[3].inbf->inbufs; + is_int(2, inbufs[0].iov_len, "fragments3: data length"); + is_int(0, memcmp("AB", inbufs[0].iov_base, inbufs[0].iov_len), "fragments3: data"); + + knot_tcp_cleanup(test_table, rls, 4); +} + +void test_close(void) +{ + size_t conns_pre = test_table->usage; + + knot_xdp_msg_t msg; + knot_tcp_relay_t rl = { 0 }; + prepare_msg(&msg, KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_ACK, + be16toh(test_conn->ip_rem.sin6_port), + be16toh(test_conn->ip_loc.sin6_port)); + prepare_seqack(&msg, 0, 0); + + // test wrong ackno synack, shall reply with RST with same + knot_xdp_msg_t wrong = msg; + wrong.seqno += INT32_MAX; + wrong.ackno += INT32_MAX; + int ret = knot_tcp_recv(&rl, &wrong, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); + is_int(KNOT_EOK, ret, "close: relay 0 OK"); + is_int(KNOT_XDP_MSG_RST, rl.auto_answer, "close: reset wrong ackno"); + is_int(rl.auto_seqno, wrong.ackno, "close: reset seqno"); + ret = knot_tcp_send(test_sock, &rl, 1, 1); + is_int(KNOT_EOK, ret, "close: send 0 OK"); + check_sent(0, 1, 0, 0); + is_int(sent_seqno, wrong.ackno, "close: reset seqno sent"); + + ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); + is_int(KNOT_EOK, ret, "close: relay 1 OK"); + ret = knot_tcp_send(test_sock, &rl, 1, 1); + is_int(KNOT_EOK, ret, "close: send OK"); + check_sent(0, 0, 0, 1); + is_int(XDP_TCP_CLOSE, rl.action, "close: relay action"); + assert(rl.conn); + ok(rl.conn == test_conn, "close: same connection"); + is_int(XDP_TCP_CLOSING2, rl.conn->state, "close: conn state"); + + msg.flags &= ~KNOT_XDP_MSG_FIN; + prepare_seqack(&msg, 0, 0); + ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); + is_int(KNOT_EOK, ret, "close: relay 2 OK"); + ret = knot_tcp_send(test_sock, &rl, 1, 1); + is_int(KNOT_EOK, ret, "close: send 2 OK"); + check_sent(0, 0, 0, 0); + is_int(conns_pre - 1, test_table->usage, "close: connection removed"); + is_int(conns_pre - 1, tcp_table_timeout_length(test_table), "close: timeout list size"); + knot_tcp_cleanup(test_table, &rl, 1); +} + +void test_many(void) +{ + size_t CONNS = test_table->size * test_table->size; + size_t i_survive = CONNS / 2; + uint32_t timeout_time = 1000000; + + knot_xdp_msg_t *msgs = malloc(CONNS * sizeof(*msgs)); + assert(msgs != NULL); + for (size_t i = 0; i < CONNS; i++) { + prepare_msg(&msgs[i], KNOT_XDP_MSG_SYN, i + 2, 1); + } + knot_tcp_relay_t *rls = malloc(CONNS * sizeof(*rls)); + + int ret = knot_tcp_recv(rls, msgs, CONNS, test_table, NULL, XDP_TCP_IGNORE_NONE); + is_int(KNOT_EOK, ret, "many: relay OK"); + ret = knot_tcp_send(test_sock, rls, CONNS, CONNS); + is_int(KNOT_EOK, ret, "many: relay send OK"); + check_sent(0, 0, CONNS, 0); + is_int(CONNS, test_table->usage, "many: table usage"); + + knot_tcp_cleanup(test_table, rls, CONNS); + memset(rls, 0, CONNS * sizeof(*rls)); + usleep(timeout_time); + knot_xdp_msg_t *survive = &msgs[i_survive]; + knot_tcp_relay_t surv_rl = { 0 }; + survive->flags = (KNOT_XDP_MSG_TCP | KNOT_XDP_MSG_ACK); + knot_tcp_conn_t *surv_conn = tcp_table_find(test_table, survive); + fix_seqack(survive); + prepare_data(survive, "\x00\x00", 2); + assert(test_table); + ret = knot_tcp_recv(&surv_rl, survive, 1, test_table, NULL, XDP_TCP_IGNORE_NONE); + is_int(KNOT_EOK, ret, "many/survivor: OK"); + clean_sent(); + + knot_sweep_stats_t stats = { 0 }; + ret = knot_tcp_sweep(test_table, timeout_time, INFTY, INFTY, INFTY, INFTY, + INFTY, rls, CONNS, &stats); + is_int(KNOT_EOK, ret, "many/timeout1: OK"); + is_int(CONNS - 1, stats.counters[KNOT_SWEEP_CTR_TIMEOUT], "many/timeout1: close count"); + is_int(0, stats.counters[KNOT_SWEEP_CTR_LIMIT_CONN], "may/timeout1: reset count"); + ret = knot_tcp_send(test_sock, rls, CONNS, CONNS); + is_int(KNOT_EOK, ret, "many/timeout1: send OK"); + check_sent(0, 0, 0, CONNS - 1); + + knot_sweep_stats_reset(&stats); + ret = knot_tcp_sweep(test_table, INFTY, timeout_time, INFTY, INFTY, INFTY, + INFTY, rls, CONNS, &stats); + is_int(KNOT_EOK, ret, "many/timeout2: OK"); + is_int(0, stats.counters[KNOT_SWEEP_CTR_TIMEOUT], "many/timeout2: close count"); + is_int(CONNS - 1, stats.counters[KNOT_SWEEP_CTR_TIMEOUT_RST], "may/timeout2: reset count"); + ret = knot_tcp_send(test_sock, rls, CONNS, CONNS); + is_int(KNOT_EOK, ret, "many/timeout2: send OK"); + check_sent(0, CONNS - 1, 0, 0); + knot_tcp_cleanup(test_table, rls, CONNS); + is_int(1, test_table->usage, "many/timeout: one survivor"); + is_int(1, tcp_table_timeout_length(test_table), "many/timeout: one survivor in timeout list"); + ok(surv_conn != NULL, "many/timeout: survivor connection present"); + ok(surv_conn == surv_rl.conn, "many/timeout: same connection"); + knot_tcp_cleanup(test_table, &surv_rl, 1); + + free(msgs); + free(rls); +} + +void test_ibufs_size(void) +{ + int CONNS = 4; + knot_xdp_msg_t msgs[CONNS]; + knot_tcp_relay_t rls[CONNS]; + + // just open connections + for (int i = 0; i < CONNS; i++) { + prepare_msg(&msgs[i], KNOT_XDP_MSG_SYN, i + 2000, 1); + } + int ret = knot_tcp_recv(rls, msgs, CONNS, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); + is_int(KNOT_EOK, ret, "ibufs: open OK"); + ret = knot_tcp_send(test_sock, rls, CONNS, CONNS); + is_int(KNOT_EOK, ret, "ibufs: first send OK"); + check_sent(0, 0, CONNS, 0); + for (int i = 0; i < CONNS; i++) { + msgs[i].flags = KNOT_XDP_MSG_TCP | KNOT_XDP_MSG_ACK; + } + fix_seqacks(msgs, CONNS); + (void)knot_tcp_recv(rls, msgs, CONNS, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); + + is_int(0, test_table->inbufs_total, "inbufs: initial total zero"); + + // first connection will start a fragment buf then finish it + fix_seqack(&msgs[0]); + prepare_data(&msgs[0], "\x00\x0a""lorem", 7); + ret = knot_tcp_recv(&rls[0], &msgs[0], 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); + is_int(KNOT_EOK, ret, "ibufs: must be OK"); + ret = knot_tcp_send(test_sock, &rls[0], 1, 1); + is_int(KNOT_EOK, ret, "ibufs: must send OK"); + check_sent(1, 0, 0, 0); + is_int(64, test_table->inbufs_total, "inbufs: first inbuf"); + knot_tcp_cleanup(test_table, &rls[0], 1); + + // other connection will just store fragments + fix_seqacks(msgs, CONNS); + prepare_data(&msgs[0], "ipsum", 5); + prepare_data(&msgs[1], "\x00\xff""12345", 7); + prepare_data(&msgs[2], "\xff\xff""abcde", 7); + prepare_data(&msgs[3], "\xff\xff""abcde", 7); + ret = knot_tcp_recv(rls, msgs, CONNS, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); + is_int(KNOT_EOK, ret, "inbufs: relay OK"); + ret = knot_tcp_send(test_sock, rls, CONNS, CONNS); + is_int(KNOT_EOK, ret, "inbufs: send OK"); + check_sent(CONNS, 0, 0, 0); + is_int(192, test_table->inbufs_total, "inbufs: after change"); + is_int(0, rls[1].action, "inbufs: one relay"); + is_int(10, rls[0].inbf->inbufs[0].iov_len, "inbufs: data length"); + knot_tcp_cleanup(test_table, rls, CONNS); + + // now free some + knot_sweep_stats_t stats = { 0 }; + ret = knot_tcp_sweep(test_table, INFTY, INFTY, INFTY, INFTY, + 64, INFTY, rls, + CONNS, &stats); + is_int(KNOT_EOK, ret, "inbufs: timeout OK"); + ret = knot_tcp_send(test_sock, rls, CONNS, CONNS); + is_int(KNOT_EOK, ret, "inbufs: timeout send OK"); + check_sent(0, 2, 0, 0); + is_int(0, stats.counters[KNOT_SWEEP_CTR_TIMEOUT], "inbufs: close count"); + is_int(2, stats.counters[KNOT_SWEEP_CTR_LIMIT_IBUF], "inbufs: reset count"); + knot_tcp_cleanup(test_table, rls, CONNS); + is_int(64, test_table->inbufs_total, "inbufs: final state"); + ok(NULL != tcp_table_find(test_table, &msgs[0]), "inbufs: first conn survived"); + ok(NULL == tcp_table_find(test_table, &msgs[1]), "inbufs: second conn not survived"); + ok(NULL == tcp_table_find(test_table, &msgs[2]), "inbufs: third conn not survived"); + ok(NULL != tcp_table_find(test_table, &msgs[3]), "inbufs: fourth conn survived"); + + clean_table(); +} + +void test_obufs(void) +{ + knot_xdp_msg_t msg; + knot_tcp_relay_t rl = { 0 }; + + prepare_msg(&msg, KNOT_XDP_MSG_SYN, 1, 2); + (void)knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); // SYN + (void)knot_tcp_send(test_sock, &rl, 1, 1); // SYN+ACK + prepare_msg(&msg, KNOT_XDP_MSG_ACK, 1, 2); + prepare_seqack(&msg, 0, 1); + (void)knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); // ACK + + size_t TEST_MSS = 1111; + size_t DATA_LEN = 65535; // with 2-byte len prefix, this is > 64k == window_size + uint8_t *data = calloc(DATA_LEN, 1); + assert(rl.conn); + rl.conn->mss = TEST_MSS; + rl.conn->window_size = 65536; + send2_mss = TEST_MSS; + + int ret = knot_tcp_reply_data(&rl, test_table, false, data, DATA_LEN), i = 0; + is_int(KNOT_EOK, ret, "obufs: fill with data"); + for (knot_tcp_outbuf_t *ob = rl.conn->outbufs; ob != NULL; ob = ob->next, i++) { + if (ob->next == NULL) { + ok(ob->len > 0, "init last ob[%d]: non-trivial", i); + ok(ob->len <= TEST_MSS, "init last ob[%d]: fulfills MSS", i); + } else { + is_int(TEST_MSS, ob->len, "init ob[%d]: exactly MSS", i); + } + ok(!ob->sent, "init ob[%d]: not sent", i); + } + ret = knot_tcp_send(test_sock, &rl, 1, 20), i = 0; + is_int(KNOT_EOK, ret, "obufs: send OK"); + is_int((DATA_LEN + 2) / TEST_MSS * TEST_MSS, sent2_data, "obufs: sent all but one MSS"); + for (knot_tcp_outbuf_t *ob = rl.conn->outbufs; ob != NULL; ob = ob->next, i++) { + if (ob->next == NULL) { + ok(!ob->sent, "last ob[%d]: not sent", i); + } else { + ok(ob->sent, "ob[%d]: sent", i); + if (ob->next->next != NULL) { + is_int(ob->seqno + ob->len, ob->next->seqno, "init ob[%d+1]: seqno", i); + } + } + } + knot_tcp_cleanup(test_table, &rl, 1); + memset(&rl, 0, sizeof(rl)); + + prepare_seqack(&msg, 0, TEST_MSS); + ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); + is_int(KNOT_EOK, ret, "obufs: ACKed data"); + assert(rl.conn); + rl.conn->window_size = 65536; + knot_tcp_outbuf_t *surv_ob = rl.conn->outbufs; + ok(surv_ob != NULL, "obufs: unACKed survived"); + assert(surv_ob); + ok(surv_ob->next == NULL, "obufs: just one survived"); + ok(!surv_ob->sent, "obufs: survivor not sent"); + ret = knot_tcp_send(test_sock, &rl, 1, 20); + is_int(KNOT_EOK, ret, "obufs: send rest OK"); + is_int(DATA_LEN + 2, sent2_data, "obufs: sent all"); + ok(surv_ob->sent, "obufs: survivor sent"); + is_int(sent_seqno, surv_ob->seqno, "obufs: survivor seqno"); + + knot_tcp_cleanup(test_table, &rl, 1); + clean_table(); + free(data); +} + +static void init_mock(knot_xdp_socket_t **socket, void *send_mock) +{ + *socket = calloc(1, sizeof(**socket)); + if (*socket != NULL) { + (*socket)->send_mock = send_mock; + } +} + +int main(int argc, char *argv[]) +{ + plan_lazy(); + + test_table = knot_tcp_table_new(TEST_TABLE_SIZE, NULL); + assert(test_table != NULL); + test_syn_table = knot_tcp_table_new(TEST_TABLE_SIZE, test_table); + + init_mock(&test_sock, mock_send); + + test_syn(); + test_establish(); + + test_syn_ack(); + test_data_fragments(); + test_close(); + + test_ibufs_size(); + + knot_xdp_deinit(test_sock); + init_mock(&test_sock, mock_send_nocheck); + test_many(); + + knot_xdp_deinit(test_sock); + init_mock(&test_sock, mock_send2); + test_obufs(); + + knot_xdp_deinit(test_sock); + knot_tcp_table_free(test_table); + knot_tcp_table_free(test_syn_table); + + return 0; +} |