tests: net: tcp: Add test for FIN,ACK received after final data
Add a test case for a scenario where the final data sent by one peer is
acknowledged in the FIN,ACK response from the other peer. Verify that
the acknowledgment is handled correctly, and a consecutive sequence
number sent by the TCP stack in such case is set correctly.
This complements the other existing test for FIN packet handling, which
verified that data received in a FIN packet is handled correctly. With
those tests in place it should be safer to update any logic related to
FIN packet handling.
Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
(cherry picked from commit 99b435788e66bdb520964468505ef1f881d06e5b)
diff --git a/tests/net/tcp/src/main.c b/tests/net/tcp/src/main.c
index 0ab1dc6..7029b9c 100644
--- a/tests/net/tcp/src/main.c
+++ b/tests/net/tcp/src/main.c
@@ -116,6 +116,7 @@
TEST_CLIENT_FIN_ACK_WITH_DATA = 18,
TEST_CLIENT_SEQ_VALIDATION = 19,
TEST_SERVER_ACK_VALIDATION = 20,
+ TEST_SERVER_FIN_ACK_AFTER_DATA = 21,
} test_case_no;
static enum test_state t_state;
@@ -142,6 +143,7 @@
static void handle_client_fin_ack_with_data_test(sa_family_t af, struct tcphdr *th);
static void handle_client_seq_validation_test(sa_family_t af, struct tcphdr *th);
static void handle_server_ack_validation_test(struct net_pkt *pkt);
+static void handle_server_fin_ack_after_data_test(sa_family_t af, struct tcphdr *th);
static void verify_flags(struct tcphdr *th, uint8_t flags,
const char *fun, int line)
@@ -494,6 +496,9 @@
case TEST_SERVER_ACK_VALIDATION:
handle_server_ack_validation_test(pkt);
break;
+ case TEST_SERVER_FIN_ACK_AFTER_DATA:
+ handle_server_fin_ack_after_data_test(net_pkt_family(pkt), &th);
+ break;
default:
zassert_true(false, "Undefined test case");
}
@@ -3002,4 +3007,204 @@
net_context_put(accepted_ctx);
}
+#define TEST_FIN_ACK_AFTER_DATA_REQ "request"
+#define TEST_FIN_ACK_AFTER_DATA_RSP "test data response"
+
+/* In this test we check that FIN,ACK packet acknowledging latest data is
+ * handled correctly by the TCP stack.
+ */
+static void handle_server_fin_ack_after_data_test(sa_family_t af, struct tcphdr *th)
+{
+ struct net_pkt *reply = NULL;
+
+ zassert_false(th == NULL && t_state != T_SYN,
+ "NULL pkt only expected in T_SYN state");
+
+ switch (t_state) {
+ case T_SYN:
+ reply = prepare_syn_packet(af, htons(MY_PORT), htons(PEER_PORT));
+ seq++;
+ t_state = T_SYN_ACK;
+ break;
+ case T_SYN_ACK:
+ test_verify_flags(th, SYN | ACK);
+ zassert_equal(ntohl(th->th_ack), seq,
+ "Unexpected ACK in T_SYN_ACK, got %d, expected %d",
+ ntohl(th->th_ack), seq);
+ device_initial_seq = ntohl(th->th_seq);
+ ack = ntohl(th->th_seq) + 1U;
+ t_state = T_DATA_ACK;
+
+ /* Dummy "request" packet */
+ reply = prepare_data_packet(af, htons(MY_PORT), htons(PEER_PORT),
+ TEST_FIN_ACK_AFTER_DATA_REQ,
+ sizeof(TEST_FIN_ACK_AFTER_DATA_REQ) - 1);
+ seq += sizeof(TEST_FIN_ACK_AFTER_DATA_REQ) - 1;
+ break;
+ case T_DATA_ACK:
+ test_verify_flags(th, ACK);
+ t_state = T_DATA;
+ zassert_equal(ntohl(th->th_seq), ack,
+ "Unexpected SEQ in T_DATA_ACK, got %d, expected %d",
+ get_rel_seq(th), ack);
+ zassert_equal(ntohl(th->th_ack), seq,
+ "Unexpected ACK in T_DATA_ACK, got %d, expected %d",
+ ntohl(th->th_ack), seq);
+ break;
+ case T_DATA:
+ test_verify_flags(th, PSH | ACK);
+ zassert_equal(ntohl(th->th_seq), ack,
+ "Unexpected SEQ in T_DATA, got %d, expected %d",
+ get_rel_seq(th), ack);
+ zassert_equal(ntohl(th->th_ack), seq,
+ "Unexpected ACK in T_DATA, got %d, expected %d",
+ ntohl(th->th_ack), seq);
+ ack += sizeof(TEST_FIN_ACK_AFTER_DATA_RSP) - 1;
+ t_state = T_FIN_ACK;
+
+ reply = prepare_fin_ack_packet(af, htons(MY_PORT), htons(PEER_PORT));
+ seq++;
+ break;
+ case T_FIN_ACK:
+ test_verify_flags(th, FIN | ACK);
+ zassert_equal(ntohl(th->th_seq), ack,
+ "Unexpected SEQ in T_FIN_ACK, got %d, expected %d",
+ get_rel_seq(th), ack);
+ zassert_equal(ntohl(th->th_ack), seq,
+ "Unexpected ACK in T_FIN_ACK, got %d, expected %d",
+ ntohl(th->th_ack), seq);
+
+ ack++;
+ t_state = T_CLOSING;
+
+ reply = prepare_ack_packet(af, htons(MY_PORT), htons(PEER_PORT));
+ seq++;
+ break;
+ case T_CLOSING:
+ zassert_true(false, "Should not receive anything after final ACK");
+ break;
+ default:
+ zassert_true(false, "%s unexpected state", __func__);
+ return;
+ }
+
+ if (reply != NULL) {
+ zassert_ok(net_recv_data(net_iface, reply), "%s failed", __func__);
+ }
+}
+
+/* Receive callback to be installed in the accept handler */
+static void test_fin_ack_after_data_recv_cb(struct net_context *context,
+ struct net_pkt *pkt,
+ union net_ip_header *ip_hdr,
+ union net_proto_header *proto_hdr,
+ int status,
+ void *user_data)
+{
+ zassert_ok(status, "failed to recv the data");
+
+ if (pkt != NULL) {
+ uint8_t buf[sizeof(TEST_FIN_ACK_AFTER_DATA_REQ)] = { 0 };
+ int data_len = net_pkt_remaining_data(pkt);
+
+ zassert_equal(data_len, sizeof(TEST_FIN_ACK_AFTER_DATA_REQ) - 1,
+ "Invalid packet length, %d", data_len);
+ zassert_ok(net_pkt_read(pkt, buf, data_len));
+ zassert_mem_equal(buf, TEST_FIN_ACK_AFTER_DATA_REQ, data_len);
+
+ net_pkt_unref(pkt);
+ }
+
+ test_sem_give();
+}
+
+static void test_fin_ack_after_data_accept_cb(struct net_context *ctx,
+ struct sockaddr *addr,
+ socklen_t addrlen,
+ int status,
+ void *user_data)
+{
+ int ret;
+
+ zassert_ok(status, "failed to accept the conn");
+
+ /* set callback on newly created context */
+ accepted_ctx = ctx;
+ ret = net_context_recv(ctx, test_fin_ack_after_data_recv_cb,
+ K_NO_WAIT, NULL);
+ zassert_ok(ret, "Failed to recv data from peer");
+
+ /* Ref the context on the app behalf. */
+ net_context_ref(ctx);
+}
+
+/* Verify that the TCP stack replies with a valid FIN,ACK after the peer
+ * acknowledges the latest data in the FIN packet.
+ * Test case scenario IPv4
+ * send SYN,
+ * expect SYN ACK,
+ * send ACK with Data,
+ * expect ACK,
+ * expect Data,
+ * send FIN,ACK
+ * expect FIN,ACK
+ * send ACK
+ * any failures cause test case to fail.
+ */
+ZTEST(net_tcp, test_server_fin_ack_after_data)
+{
+ struct net_context *ctx;
+ int ret;
+
+ test_case_no = TEST_SERVER_FIN_ACK_AFTER_DATA;
+
+ t_state = T_SYN;
+ seq = ack = 0;
+
+ ret = net_context_get(AF_INET, SOCK_STREAM, IPPROTO_TCP, &ctx);
+ zassert_ok(ret, "Failed to get net_context");
+
+ net_context_ref(ctx);
+
+ ret = net_context_bind(ctx, (struct sockaddr *)&my_addr_s,
+ sizeof(struct sockaddr_in));
+ zassert_ok(ret, "Failed to bind net_context");
+
+ /* Put context into listening mode and install accept cb */
+ ret = net_context_listen(ctx, 1);
+ zassert_ok(ret, "Failed to listen on net_context");
+
+ ret = net_context_accept(ctx, test_fin_ack_after_data_accept_cb,
+ K_NO_WAIT, NULL);
+ zassert_ok(ret, "Failed to set accept on net_context");
+
+ /* Trigger the peer to send SYN */
+ handle_server_fin_ack_after_data_test(AF_INET, NULL);
+
+ /* test_fin_ack_after_data_recv_cb will release the semaphore after
+ * dummy request is read.
+ */
+ test_sem_take(K_MSEC(100), __LINE__);
+
+ /* Send dummy "response" */
+ ret = net_context_send(accepted_ctx, TEST_FIN_ACK_AFTER_DATA_RSP,
+ sizeof(TEST_FIN_ACK_AFTER_DATA_RSP) - 1, NULL,
+ K_NO_WAIT, NULL);
+ zassert_equal(ret, sizeof(TEST_FIN_ACK_AFTER_DATA_RSP) - 1,
+ "Failed to send data to peer %d", ret);
+
+ /* test_fin_ack_after_data_recv_cb will release the semaphore after
+ * the connection is marked closed.
+ */
+ test_sem_take(K_MSEC(100), __LINE__);
+
+ net_context_put(ctx);
+ net_context_put(accepted_ctx);
+
+ /* Connection is in TIME_WAIT state, context will be released
+ * after K_MSEC(CONFIG_NET_TCP_TIME_WAIT_DELAY), so wait for it.
+ */
+ k_sleep(K_MSEC(CONFIG_NET_TCP_TIME_WAIT_DELAY));
+}
+
ZTEST_SUITE(net_tcp, NULL, presetup, NULL, NULL, NULL);