From 66229ae6634d83ee1ad9217c2d030246cee62d1a Mon Sep 17 00:00:00 2001 From: Libor Peltan <libor.peltan@nic.cz> Date: Thu, 11 Nov 2021 21:16:16 +0100 Subject: [PATCH] kxdpgun: allow various modes exploiting left-open connections --- doc/man/kxdpgun.8in | 28 ++++++++++++++++-- doc/man_kxdpgun.rst | 28 ++++++++++++++++-- src/knot/server/xdp-handler.c | 2 +- src/libknot/xdp/tcp.c | 10 +++++-- src/libknot/xdp/tcp.h | 9 +++++- src/utils/kxdpgun/main.c | 54 ++++++++++++++++++++++++++++++++--- tests/libknot/test_xdp_tcp.c | 32 ++++++++++----------- 7 files changed, 135 insertions(+), 28 deletions(-) diff --git a/doc/man/kxdpgun.8in b/doc/man/kxdpgun.8in index 0666be3335..982c94f6e8 100644 --- a/doc/man/kxdpgun.8in +++ b/doc/man/kxdpgun.8in @@ -51,8 +51,8 @@ configured for the network interface. Duration of traffic generation, specified as a decimal number in seconds (default is 5.0). .TP -\fB\-T\fP, \fB\-\-tcp\fP -Send queries over TCP. +\fB\-T\fP, \fB\-\-tcp\fP[=\fIDEBUG_MODE\fP] +Send queries over TCP. Optional debug mode see below. .TP \fB\-Q\fP, \fB\-\-qps\fP \fIqueries\fP Number of queries\-per\-second (approximately) to be sent (default is 1000). @@ -111,6 +111,30 @@ name, and \fIflags\fP is a single character: .sp Sending USR1 signal to a running process triggers current statistics dump to the standard output. +.SS TCP Debug Modes +.INDENT 0.0 +.TP +\fB1\fP +Just send SYN and don\(aqt react to any incomming packets (alias to \fB\-d\fP). +.TP +\fB2\fP +Perform TCP handshake and don\(aqt send anything, allow close initiated by counterpart. +.TP +\fB3\fP +Perform TCP handshake and don\(aqt react further. +.TP +\fB5\fP +Send incomplete query (N\-1 bytes) and don\(aqt react further. +.TP +\fB7\fP +Send query and don\(aqt ACK the response or anthing further. +.TP +\fB8\fP +Don\(aqt close the connection and ignore close by counterpart. +.TP +\fB9\fP +Operate normally except of not ACKing the final FIN+ACK. +.UNINDENT .SH NOTES .sp Linux kernel 4.18+ is required. diff --git a/doc/man_kxdpgun.rst b/doc/man_kxdpgun.rst index c024536776..f293b0121c 100644 --- a/doc/man_kxdpgun.rst +++ b/doc/man_kxdpgun.rst @@ -28,8 +28,8 @@ Options Duration of traffic generation, specified as a decimal number in seconds (default is 5.0). -**-T**, **--tcp** - Send queries over TCP. +**-T**, **--tcp**\[\ =\ *DEBUG_MODE*\] + Send queries over TCP. Optional debug mode see below. **-Q**, **--qps** *queries* Number of queries-per-second (approximately) to be sent (default is 1000). @@ -92,6 +92,30 @@ Signals Sending USR1 signal to a running process triggers current statistics dump to the standard output. +TCP Debug Modes +............... + +**1** + Just send SYN and don't react to any incomming packets (alias to **-d**). + +**2** + Perform TCP handshake and don't send anything, allow close initiated by counterpart. + +**3** + Perform TCP handshake and don't react further. + +**5** + Send incomplete query (N-1 bytes) and don't react further. + +**7** + Send query and don't ACK the response or anthing further. + +**8** + Don't close the connection and ignore close by counterpart. + +**9** + Operate normally except of not ACKing the final FIN+ACK. + Notes ----- diff --git a/src/knot/server/xdp-handler.c b/src/knot/server/xdp-handler.c index f6f3e51f1a..6c0be11087 100644 --- a/src/knot/server/xdp-handler.c +++ b/src/knot/server/xdp-handler.c @@ -196,7 +196,7 @@ static void handle_udp(xdp_handle_ctx_t *ctx, knot_layer_t *layer, static void handle_tcp(xdp_handle_ctx_t *ctx, knot_layer_t *layer, knotd_qdata_params_t *params) { - int ret = knot_tcp_recv(ctx->relays, ctx->msg_recv, ctx->msg_recv_count, ctx->tcp_table, ctx->syn_table); + int ret = knot_tcp_recv(ctx->relays, ctx->msg_recv, ctx->msg_recv_count, ctx->tcp_table, ctx->syn_table, XDP_TCP_IGNORE_NONE); if (ret != KNOT_EOK) { log_notice("TCP, failed to process some packets (%s)", knot_strerror(ret)); return; diff --git a/src/libknot/xdp/tcp.c b/src/libknot/xdp/tcp.c index 938018e150..720836efb2 100644 --- a/src/libknot/xdp/tcp.c +++ b/src/libknot/xdp/tcp.c @@ -269,7 +269,8 @@ static void conn_update(knot_tcp_conn_t *conn, const knot_xdp_msg_t *msg) _public_ int knot_tcp_recv(knot_tcp_relay_t *relays, knot_xdp_msg_t *msgs, uint32_t count, - knot_tcp_table_t *tcp_table, knot_tcp_table_t *syn_table) + knot_tcp_table_t *tcp_table, knot_tcp_table_t *syn_table, + knot_tcp_ignore_t ignore) { if (count == 0) { return KNOT_EOK; @@ -310,7 +311,9 @@ int knot_tcp_recv(knot_tcp_relay_t *relays, knot_xdp_msg_t *msgs, uint32_t count // process incoming data if (seq_ack_match && (msg->flags & KNOT_XDP_MSG_ACK) && msg->payload.iov_len > 0) { - relay->auto_answer = KNOT_XDP_MSG_ACK; + if (!(ignore & XDP_TCP_IGNORE_DATA_ACK)) { + relay->auto_answer = KNOT_XDP_MSG_ACK; + } ret = tcp_inbuf_update(&conn->inbuf, msg->payload, &relay->inbufs, &relay->inbufs_count, &tcp_table->inbufs_total); if (ret != KNOT_EOK) { @@ -384,6 +387,9 @@ int knot_tcp_recv(knot_tcp_relay_t *relays, knot_xdp_msg_t *msgs, uint32_t count } break; case (KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_ACK): + if (ignore & XDP_TCP_IGNORE_FIN) { + break; + } if (!seq_ack_match) { if (conn != NULL) { relay->auto_answer = KNOT_XDP_MSG_RST; diff --git a/src/libknot/xdp/tcp.h b/src/libknot/xdp/tcp.h index 2cdf45cf5c..1e11345c75 100644 --- a/src/libknot/xdp/tcp.h +++ b/src/libknot/xdp/tcp.h @@ -53,6 +53,12 @@ typedef enum { XDP_TCP_FREE_PREFIX, } knot_tcp_relay_free_t; +typedef enum { + XDP_TCP_IGNORE_NONE = 0, + XDP_TCP_IGNORE_DATA_ACK = (1 << 0), + XDP_TCP_IGNORE_FIN = (1 << 1), +} knot_tcp_ignore_t; + typedef struct tcp_outbufs { struct tcp_outbuf *bufs; } tcp_outbufs_t; // this typedef belongs to tcp_iobuf.h, but is here to avoid issues with symbols @@ -152,7 +158,8 @@ void knot_tcp_table_free(knot_tcp_table_t *table); * \return KNOT_E* */ int knot_tcp_recv(knot_tcp_relay_t *relays, knot_xdp_msg_t *msgs, uint32_t count, - knot_tcp_table_t *tcp_table, knot_tcp_table_t *syn_table); + knot_tcp_table_t *tcp_table, knot_tcp_table_t *syn_table, + knot_tcp_ignore_t ignore); /*! * \brief Prepare data (payload) to be sent as a response on specific relay. diff --git a/src/utils/kxdpgun/main.c b/src/utils/kxdpgun/main.c index d8ebf07a5d..db40807569 100644 --- a/src/utils/kxdpgun/main.c +++ b/src/utils/kxdpgun/main.c @@ -88,6 +88,13 @@ typedef struct { static kxdpgun_stats_t global_stats = { 0 }; +typedef enum { + KXDPGUN_IGNORE_NONE = 0, + KXDPGUN_IGNORE_QUERY = (1 << 0), + KXDPGUN_IGNORE_LASTBYTE = (1 << 1), + KXDPGUN_IGNORE_CLOSE = (1 << 2), +} xdp_gun_ignore_t; + typedef struct { char dev[IFNAMSIZ]; uint64_t qps, duration; @@ -99,6 +106,8 @@ typedef struct { uint8_t local_ip_range; bool ipv6; bool tcp; + xdp_gun_ignore_t ignore1; + knot_tcp_ignore_t ignore2; uint16_t target_port; uint32_t listen_port; // KNOT_XDP_LISTEN_PORT_* unsigned n_threads, thread_id; @@ -420,7 +429,7 @@ void *xdp_gun_thread(void *_ctx) } if (ctx->tcp) { knot_tcp_relay_t relays[recvd]; - ret = knot_tcp_recv(relays, pkts, recvd, tcp_table, NULL); + ret = knot_tcp_recv(relays, pkts, recvd, tcp_table, NULL, ctx->ignore2); if (ret != KNOT_EOK) { errors++; break; @@ -432,7 +441,13 @@ void *xdp_gun_thread(void *_ctx) switch (rl->action) { case XDP_TCP_ESTABLISH: local_stats.synack_recv++; + if (ctx->ignore1 & KXDPGUN_IGNORE_QUERY) { + break; + } put_dns_payload(&payl, true, ctx, &payload_ptr); + if (ctx->ignore1 & KXDPGUN_IGNORE_LASTBYTE) { + payl.iov_len--; + } ret = knot_tcp_reply_data(rl, tcp_table, payl.iov_base, payl.iov_len); if (ret != KNOT_EOK) { errors++; @@ -449,7 +464,9 @@ void *xdp_gun_thread(void *_ctx) } for (size_t j = 0; j < rl->inbufs_count; j++) { if (check_dns_payload(&rl->inbufs[j], ctx, &local_stats)) { - rl->answer = XDP_TCP_CLOSE; + if (!(ctx->ignore1 & KXDPGUN_IGNORE_CLOSE)) { + rl->answer = XDP_TCP_CLOSE; + } } } } @@ -658,7 +675,7 @@ static bool get_opts(int argc, char *argv[], xdp_gun_ctx_t *ctx) { "batch", required_argument, NULL, 'b' }, { "drop", no_argument, NULL, 'r' }, { "port", required_argument, NULL, 'p' }, - { "tcp", no_argument, NULL, 'T' }, + { "tcp", optional_argument, NULL, 'T' }, { "affinity", required_argument, NULL, 'F' }, { "interface", required_argument, NULL, 'I' }, { "local", required_argument, NULL, 'l' }, @@ -670,7 +687,7 @@ static bool get_opts(int argc, char *argv[], xdp_gun_ctx_t *ctx) bool default_at_once = true; double argf; char *argcp, *local_ip = NULL; - while ((opt = getopt_long(argc, argv, "hVt:Q:b:rp:TF:I:l:i:", opts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "hVt:Q:b:rp:T::F:I:l:i:", opts, NULL)) != -1) { switch (opt) { case 'h': print_help(); @@ -723,6 +740,35 @@ static bool get_opts(int argc, char *argv[], xdp_gun_ctx_t *ctx) if (default_at_once) { ctx->at_once = 1; } + switch (optarg == NULL ? '0' : optarg[0]) { + case '1': + ctx->listen_port |= KNOT_XDP_LISTEN_PORT_DROP; + break; + case '2': + ctx->ignore1 = KXDPGUN_IGNORE_QUERY; + break; + case '3': + ctx->ignore1 = KXDPGUN_IGNORE_QUERY; + ctx->ignore2 = XDP_TCP_IGNORE_FIN; + break; + case '5': + ctx->ignore1 = KXDPGUN_IGNORE_LASTBYTE; + ctx->ignore2 = XDP_TCP_IGNORE_FIN; + break; + case '7': + ctx->ignore1 = KXDPGUN_IGNORE_CLOSE; + ctx->ignore2 = XDP_TCP_IGNORE_DATA_ACK | XDP_TCP_IGNORE_FIN; + break; + case '8': + ctx->ignore1 = KXDPGUN_IGNORE_CLOSE; + ctx->ignore2 = XDP_TCP_IGNORE_FIN; + break; + case '9': + ctx->ignore2 = XDP_TCP_IGNORE_FIN; + break; + default: + break; + } break; case 'F': if ((arg = atoi(optarg)) > 0) { diff --git a/tests/libknot/test_xdp_tcp.c b/tests/libknot/test_xdp_tcp.c index 98611290ea..a1427c2b54 100644 --- a/tests/libknot/test_xdp_tcp.c +++ b/tests/libknot/test_xdp_tcp.c @@ -212,7 +212,7 @@ void test_syn(void) knot_xdp_msg_t msg; knot_tcp_relay_t rl; prepare_msg(&msg, KNOT_XDP_MSG_SYN, 1, 2); - int ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table); + 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"); @@ -240,7 +240,7 @@ void test_establish(void) knot_tcp_relay_t rl; 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); + 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"); @@ -258,7 +258,7 @@ void test_syn_ack(void) knot_xdp_msg_t msg; knot_tcp_relay_t rl; 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); + 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"); @@ -298,7 +298,7 @@ void test_data_fragments(void) prepare_seqack(&msgs[3], 15, 0); prepare_data(&msgs[3], "\x02""AB""\xff\xff""abcdefghijklmnopqrstuvwxyz...", 34); - int ret = knot_tcp_recv(rls, msgs, CONNS, test_table, test_syn_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"); ret = knot_tcp_send(test_sock, rls, CONNS, CONNS); is_int(KNOT_EOK, ret, "fragments: send OK"); @@ -353,7 +353,7 @@ void test_close(void) 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); + 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"); @@ -362,7 +362,7 @@ void test_close(void) 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); + 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"); @@ -373,7 +373,7 @@ void test_close(void) msg.flags &= ~KNOT_XDP_MSG_FIN; prepare_seqack(&msg, 0, 0); - ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table); + 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"); @@ -395,7 +395,7 @@ void test_many(void) } knot_tcp_relay_t *rls = malloc(CONNS * sizeof(*rls)); - int ret = knot_tcp_recv(rls, msgs, CONNS, test_table, NULL); + 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"); @@ -410,7 +410,7 @@ void test_many(void) knot_tcp_conn_t *surv_conn = tcp_table_find(test_table, survive); fix_seqack(survive); prepare_data(survive, "\x00\x00", 2); - ret = knot_tcp_recv(&surv_rl, survive, 1, test_table, NULL); + 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(); @@ -454,7 +454,7 @@ void test_ibufs_size(void) 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); + 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"); @@ -463,14 +463,14 @@ void test_ibufs_size(void) 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); + (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); + 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"); @@ -484,7 +484,7 @@ void test_ibufs_size(void) 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); + 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"); @@ -521,11 +521,11 @@ void test_obufs(void) knot_tcp_relay_t rl; prepare_msg(&msg, KNOT_XDP_MSG_SYN, 1, 2); - (void)knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table); // SYN + (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); // ACK + (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 @@ -561,7 +561,7 @@ void test_obufs(void) knot_tcp_cleanup(test_table, &rl, 1); prepare_seqack(&msg, 0, TEST_MSS); - ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table); + ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); is_int(KNOT_EOK, ret, "obufs: ACKed data"); rl.conn->window_size = 65536; struct tcp_outbuf *surv_ob = rl.conn->outbufs.bufs; -- GitLab