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