Commit 46ba523b authored by Shrek Wang's avatar Shrek Wang Committed by Benjamin Cabé
Browse files

net: tcp: Align TCP seqence validation with Spec



According to RFC 793, the seqnum test includes 4 cases when STATE >
TCP_SYN_SENT:

      Seg-len Recv-win    Test
      ------- --------    ---------------------------------------
        0       0         SEG.SEQ = RCV.NXT
        0      >0         RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND
       >0       0         not acceptable
       >0      >0         RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND
                       or RCV.NXT =< SEG.SEQ+SEG.LEN-1 <RCV.NXT+RCV.WND

After the seq validation, the 'send duplicated ACK' code in FIN_WAIT1/
2/CLOSING/TIMEWAIT state processing is duplicated, so remove them.

Added TEST_CLIENT_SEQ_VALIDATION ztest case in tests/net/tcp.

Signed-off-by: default avatarShrek Wang <inet_eman@outlook.com>
parent f8b70ee1
Loading
Loading
Loading
Loading
+44 −45
Original line number Diff line number Diff line
@@ -2428,10 +2428,38 @@ err:
	return conn;
}

static bool tcp_validate_seq(struct tcp *conn, struct tcphdr *hdr)
/* According to RFC 793, the seqnum test includes 4 cases when STATE > TCP_SYN_SENT
 *
 *	Seg-len	Recv-win	Test
 *	-------	--------	-----------------------------------------------
 *	  0	  0		SEG.SEQ = RCV.NXT
 *	  0	 >0		RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND
 *	 >0	  0		not acceptable
 *	 >0	 >0		RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND
 *			     or RCV.NXT =< SEG.SEQ+SEG.LEN-1 <RCV.NXT+RCV.WND
 */
static bool tcp_validate_seq(struct tcp *conn, struct tcphdr *hdr, size_t len)
{
	return (net_tcp_seq_cmp(th_seq(hdr), conn->ack) >= 0) &&
		(net_tcp_seq_cmp(th_seq(hdr), conn->ack + conn->recv_win) < 0);
	if ((conn->state == TCP_LISTEN) || (conn->state == TCP_SYN_SENT)) {
		return true;
	}

	if (conn->recv_win > 0) {
		if (len == 0) {
			return ((net_tcp_seq_cmp(th_seq(hdr), conn->ack) >= 0) &&
				(net_tcp_seq_cmp(th_seq(hdr), conn->ack + conn->recv_win) < 0));
		}
		return (((net_tcp_seq_cmp(th_seq(hdr), conn->ack) >= 0) &&
			 (net_tcp_seq_cmp(th_seq(hdr), conn->ack + conn->recv_win) < 0)) ||
			((net_tcp_seq_cmp(th_seq(hdr) + len - 1, conn->ack) >= 0) &&
			 (net_tcp_seq_cmp(th_seq(hdr) + len - 1, conn->ack + conn->recv_win) < 0)));
	}

	if (len == 0) {
		return (net_tcp_seq_cmp(th_seq(hdr), conn->ack) == 0);
	}

	return false;
}

static int32_t tcp_compute_new_length(struct tcp *conn, struct tcphdr *hdr, size_t len,
@@ -2769,14 +2797,21 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt)
		goto out;
	}

	len = tcp_data_len(pkt);

	/* first validate the seqnum */
	if (!tcp_validate_seq(conn, th, len)) {
		/* send ACK for non-RST packet */
		if (FL(&fl, &, RST)) {
		/* We only accept RST packet that has valid seq field. */
		if (!tcp_validate_seq(conn, th)) {
			net_stats_update_tcp_seg_rsterr(net_pkt_iface(pkt));
		} else if ((len > 0) || FL(&fl, &, FIN)) {
			tcp_out(conn, ACK);
		}
		k_mutex_unlock(&conn->lock);
		return NET_DROP;
	}

	if (FL(&fl, &, RST)) {
		/* Valid RST received. */
		verdict = NET_OK;
		net_stats_update_tcp_seg_rst(net_pkt_iface(pkt));
@@ -2814,8 +2849,7 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt)
		goto out;
	}

	if ((conn->state != TCP_LISTEN) && (conn->state != TCP_SYN_SENT) &&
	    tcp_validate_seq(conn, th) && FL(&fl, &, SYN)) {
	if ((conn->state != TCP_LISTEN) && (conn->state != TCP_SYN_SENT) && FL(&fl, &, SYN)) {
		/* According to RFC 793, ch 3.9 Event Processing, receiving SYN
		 * once the connection has been established is an error
		 * condition, reset should be sent and connection closed.
@@ -2851,8 +2885,6 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt)
		k_sem_give(&conn->tx_sem);
	}

	len = tcp_data_len(pkt);

	switch (conn->state) {
	case TCP_LISTEN:
		if (FL(&fl, ==, SYN)) {
@@ -3296,11 +3328,6 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt)
			}
			tcp_out(conn, ACK);
			verdict = NET_OK;
		} else {
			if (len > 0) {
				tcp_out(conn, ACK);
				verdict = NET_OK;
			}
		}
	}
	break;
@@ -3337,12 +3364,6 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt)

			verdict = NET_OK;
			tcp_out(conn, ACK);
		} else {
			if (len > 0) {
				/* Send out a duplicate ACK */
				tcp_out(conn, ACK);
				verdict = NET_OK;
			}
		}
		break;
	case TCP_CLOSING: {
@@ -3380,19 +3401,6 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt)

			verdict = NET_OK;
		}

		/*
		 * There can also be data in the message, so compute with the length
		 * of the packet to check with the ack
		 * Since the conn->ack was already incremented in TCP_FIN_WAIT_1
		 * add 1 in the comparison sequence
		 */
		if ((FL(&fl, &, FIN, net_tcp_seq_cmp(th_seq(th) + len + 1, conn->ack) == 0)) ||
		    (len > 0)) {
			/* Send out a duplicate ACK */
			tcp_out(conn, ACK);
			verdict = NET_OK;
		}
	}
	break;
	case TCP_TIME_WAIT: {
@@ -3408,15 +3416,6 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt)
			net_stats_update_tcp_seg_drop(conn->iface);

			net_tcp_reply_rst(pkt);
		} else {
			/* Acknowledge any FIN attempts, in case retransmission took
			 * place.
			 */
			if ((FL(&fl, &, FIN, net_tcp_seq_cmp(th_seq(th) + 1, conn->ack) == 0)) ||
			    (len > 0)) {
				tcp_out(conn, ACK);
				verdict = NET_OK;
			}
		}
	}
	break;
+250 −1
Original line number Diff line number Diff line
@@ -114,6 +114,7 @@ static enum test_case_no {
	TEST_CLIENT_CLOSING_FAILURE_IPV6 = 16,
	TEST_CLIENT_FIN_WAIT_2_IPV4_FAILURE = 17,
	TEST_CLIENT_FIN_ACK_WITH_DATA = 18,
	TEST_CLIENT_SEQ_VALIDATION = 19,
} test_case_no;

static enum test_state t_state;
@@ -138,6 +139,7 @@ static void handle_server_rst_on_closed_port(sa_family_t af, struct tcphdr *th);
static void handle_server_rst_on_listening_port(sa_family_t af, struct tcphdr *th);
static void handle_syn_invalid_ack(sa_family_t af, struct tcphdr *th);
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 verify_flags(struct tcphdr *th, uint8_t flags,
			 const char *fun, int line)
@@ -484,7 +486,9 @@ static int tester_send(const struct device *dev, struct net_pkt *pkt)
	case TEST_CLIENT_FIN_ACK_WITH_DATA:
		handle_client_fin_ack_with_data_test(net_pkt_family(pkt), &th);
		break;

	case TEST_CLIENT_SEQ_VALIDATION:
		handle_client_seq_validation_test(net_pkt_family(pkt), &th);
		break;
	default:
		zassert_true(false, "Undefined test case");
	}
@@ -2084,6 +2088,8 @@ static struct out_of_order_check_struct out_of_order_check_list[] = {
	{ 10,  9, 0, 0}, /* Section with a gap */
	{ 0,  10, 10, 0},
	{ 10, 10, 40, 0}, /* First sequence complete */
	{ 32,  6, 40, 0}, /* Invalid seqnum (old) */
	{ 30, 16, 40, 0}, /* Partial data valid (not supported yet) */
	{ 50,  6, 40, 0},
	{ 50,  3, 40, 0}, /* Discardable packet */
	{ 55,  5, 40, 0},
@@ -2607,4 +2613,247 @@ ZTEST(net_tcp, test_client_fin_ack_with_data)
	}
}

static struct k_work_delayable test_seq_val_work;

static void handle_client_seq_validation_test(sa_family_t af, struct tcphdr *th)
{
	static uint16_t peer_port;
	struct net_pkt *reply = NULL;
	static uint8_t round;
	static uint16_t wnd;
	static int rsterr;
	const uint8_t *data = lorem_ipsum;
	size_t len = 500;

	switch (t_state) {
	case T_SYN:
		test_verify_flags(th, SYN);
		device_initial_seq = ntohl(th->th_seq);
		seq = 0U;
		ack = ntohl(th->th_seq) + 1U;
		peer_port = th->th_sport;
		reply = prepare_syn_ack_packet(af, htons(MY_PORT), peer_port);
		seq++;
		t_state = T_SYN_ACK;
		break;
	case T_SYN_ACK:
		test_verify_flags(th, ACK);
		t_state = T_DATA;
		wnd = ntohs(th_win(th));
		round = 0;

		k_work_reschedule(&test_seq_val_work, K_MSEC(1));
		return;
	case T_DATA:
		/* Test seqnum on case 'recv_win > 0'. Some scenarios
		 * have been covered by out-of-order test. Here we only
		 * cover the remaining ones.
		 */

		switch (round) {
		case 0: {
			uint32_t t_seq = seq;

			/* len == 0, with seqnum bigger than the scope. */
			seq = 66000;
			reply = prepare_ack_packet(af, htons(MY_PORT), peer_port);
			seq = t_seq;
			round++;

			/* The ACK with an invalid seqnum would be silently dropped */
			k_work_reschedule(&test_seq_val_work, K_MSEC(1));
		}
		break;
		case 1: {
			uint32_t t_seq = seq;

			/* len == 0, with seqnum bigger than the scope. */
			seq = 66000;
			reply = prepare_data_packet(af, htons(MY_PORT), peer_port, data, 10);
			seq = t_seq;
			round++;
		}
		break;
		case 2: {
			uint32_t t_seq = seq;

			test_verify_flags(th, ACK);
			zassert_equal(get_rel_seq(th), 1,
				      "Unexpected SEQ number in T_DATA_ACK round %d, got %d",
				      round, get_rel_seq(th));
			zassert_equal(ntohl(th->th_ack), seq,
				      "Unexpected ACK in T_DATA_ACK round %d, got %d",
				      round, ntohl(th->th_ack));

			seq = wnd + 10;
			reply = prepare_data_packet(af, htons(MY_PORT), peer_port, data, 1);
			seq = t_seq;
			round++;
		}
		break;
		case 3:
			test_verify_flags(th, ACK);
			zassert_equal(get_rel_seq(th), 1,
				      "Unexpected SEQ number in T_DATA round %d, got %d",
				      round, get_rel_seq(th));
			zassert_equal(ntohl(th->th_ack), seq,
				      "Unexpected ACK in T_DATA round %d, got %d",
				      round, ntohl(th->th_ack));
			zassert_equal(ntohs(th_win(th)), wnd,
				      "Unexpected recv_win in T_DATA round %d, got %d",
				      round, ntohs(th_win(th)));

			len = len > wnd ? wnd : len;
			reply = prepare_data_packet(af, htons(MY_PORT), peer_port, data, len);
			seq += len;
			wnd -= len;
			if (wnd == 0) {
				t_state = T_DATA_ACK;
				round++;
			}
			break;
		default:
			zassert_true(false, "T_DATA, %d round should never happen", round);
			break;
		}
		break;
	case T_DATA_ACK:
		if (th) {
			test_verify_flags(th, ACK);
			zassert_equal(get_rel_seq(th), 1,
				      "Unexpected SEQ number in T_DATA_ACK round %d, got %d",
				      round, get_rel_seq(th));
			zassert_equal(ntohl(th->th_ack), seq,
				      "Unexpected ACK in T_DATA_ACK round %d, got %d",
				      round, ntohl(th->th_ack));
			zassert_equal(ntohs(th_win(th)), wnd,
				      "Unexpected recv_win in T_DATA_ACK round %d, got %d",
				      round, ntohs(th_win(th)));
		}

		switch (round) {
		case 4:
			zassert_true(wnd == 0, "Peer's recv_win is not zero");
			/* test data in 0 window, expect duplicate ACK */
			reply = prepare_data_packet(af, htons(MY_PORT), peer_port, data, 1);
			round++;
			break;
		case 5:
			/* test RST with different seqnum, expect no reply */
			seq--;
			reply = prepare_rst_ack_packet(af, htons(MY_PORT), peer_port);
			seq++;
			round++;

			rsterr = GET_STAT(net_iface, tcp.rsterr);

			/* reschedule thread due to no reply */
			k_work_reschedule(&test_seq_val_work, K_MSEC(1));
			break;
		case 6: {
			int rsterr_after = GET_STAT(net_iface, tcp.rsterr);

			zassert_equal(rsterr + 1, rsterr_after,
				      "RST packet not skipped (before %d, after %d)",
				      rsterr, rsterr_after);

			/* test RST with correct seqnum, expect ERSET in recv_cb. */
			reply = prepare_rst_ack_packet(af, htons(MY_PORT), peer_port);

		}
		break;
		default:
			zassert_true(false, "T_DATA_ACK, %d round should never happen", round);
			break;
		}
		break;
	default:
		zassert_true(false, "%s unexpected state", __func__);
		return;
	}

	zassert_ok(net_recv_data(net_iface, reply), "%s failed", __func__);
}

static void test_seq_val_handler(struct k_work *work)
{
	ARG_UNUSED(work);

	handle_client_seq_validation_test(AF_INET, NULL);
}

static void test_seq_val_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)
{
	if (status) {
		zassert_equal(status, -ECONNRESET, "failed to recv data");
		test_sem_give();
	}

	if (pkt) {
		static int i;

		net_pkt_unref(pkt);

		if (i == 0) {
			test_sem_give();
		}
		i++;
	}
}

/* Test case scenario IPv4
 *   expect SYN,
 *   send SYN ACK,
 *   expect ACK,
 *   send Data with seq valid,
 *   expect ACK,
 *   send Data with seq invalid,
 *   expect same ACK,
 *   send Data to make server 0 recv_wnd,
 *   expect ACK with 0 window,
 *   send Data, valid RST,
 *   expect duplicate ACK on Data, check RST stat
 *   any failures cause test case to fail.
 */
ZTEST(net_tcp, test_client_seq_validation)
{
	struct net_context *ctx;

	test_case_no = TEST_CLIENT_SEQ_VALIDATION;

	k_work_init_delayable(&test_seq_val_work, test_seq_val_handler);

	t_state = T_SYN;
	seq = ack = 0;

	zassert_ok(net_context_get(AF_INET, SOCK_STREAM, IPPROTO_TCP, &ctx),
		   "Failed to get net_context");

	net_context_ref(ctx);

	zassert_ok(net_context_connect(ctx, (struct sockaddr *)&peer_addr_s,
				       sizeof(struct sockaddr_in), NULL,
				       K_MSEC(1000), NULL),
		   "Failed to connect to peer");
	zassert_ok(net_context_recv(ctx, test_seq_val_recv_cb,
				    K_NO_WAIT, NULL),
		   "Failed to recv data from peer");

	/* Take sem twice, one for data packet, second for conn reset. */
	test_sem_take(K_MSEC(300), __LINE__);
	test_sem_take(K_MSEC(300), __LINE__);

	net_context_put(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);