net: tcp: Avoid double acknowlegding retransmitted data

In the FIN_WAIT_1 any incoming data is dropped, but anknowledged. Add a
check to see if the data is already acknowleged to prevent double
acknowledging of the data and bringing the acknowlegde counter out of
sync. When the acknowledge counter gets out of sync, the connection will
never properly terminate any more.

Signed-off-by: Sjors Hettinga <s.a.hettinga@gmail.com>
This commit is contained in:
Sjors Hettinga 2023-05-03 11:57:43 +02:00 committed by Carles Cufí
parent fd9e7b4e52
commit a28a656aa0
2 changed files with 95 additions and 28 deletions

View file

@ -2476,39 +2476,102 @@ next_state:
do_close = true;
break;
case TCP_FIN_WAIT_1:
/* Acknowledge but drop any data */
conn_ack(conn, + len);
if (th) {
/* Acknowledge but drop any new data */
if (len > 0) {
int32_t new_len = len - net_tcp_seq_cmp(conn->ack, th_seq(th));
/* Cases:
* - Data already received earlier: len > 0 , new_len <= 0
* - Partially new data len > 0, new_len > 0
* - Out of order data len > 0, new_len > 0, ignore the data in
* between
*/
if (new_len > 0) {
conn_ack(conn, + new_len);
}
}
if (th && FL(&fl, ==, (FIN | ACK), th_seq(th) == conn->ack)) {
tcp_send_timer_cancel(conn);
conn_ack(conn, + 1);
tcp_out(conn, ACK);
next = TCP_TIME_WAIT;
verdict = NET_OK;
} else if (th && FL(&fl, ==, FIN, th_seq(th) == conn->ack)) {
tcp_send_timer_cancel(conn);
conn_ack(conn, + 1);
tcp_out(conn, ACK);
next = TCP_CLOSING;
verdict = NET_OK;
} else if (th && FL(&fl, ==, ACK, th_seq(th) == conn->ack)) {
tcp_send_timer_cancel(conn);
next = TCP_FIN_WAIT_2;
verdict = NET_OK;
/*
* Only if our FIN flag has been acknowledged and all the data has been
* received, it can go to the TIME_WAIT state
*/
if (FL(&fl, ==, (FIN | ACK),
(th_ack(th) == conn->seq) && (th_seq(th) == conn->ack))) {
tcp_send_timer_cancel(conn);
conn_ack(conn, + 1);
tcp_out(conn, ACK);
next = TCP_TIME_WAIT;
NET_DBG("FIN acknowledged, going to TIME_WAIT_STATE");
verdict = NET_OK;
} else if (th &&
(FL(&fl, ==, FIN, th_seq(th) == conn->ack) ||
FL(&fl, ==, (FIN | ACK), th_seq(th) == conn->ack))) {
tcp_send_timer_cancel(conn);
conn_ack(conn, + 1);
NET_DBG("FIN not yet acknowleged, going to CLOSING state");
if (th_ack(th) != conn->seq) {
/* Not acknowledged the FIN flag yet, so resend */
tcp_out_ext(conn, (FIN | ACK), NULL, conn->seq - 1);
} else {
tcp_out(conn, ACK);
}
next = TCP_CLOSING;
verdict = NET_OK;
} else if (th && FL(&fl, ==, ACK, th_ack(th) == conn->seq)) {
tcp_send_timer_cancel(conn);
next = TCP_FIN_WAIT_2;
verdict = NET_OK;
NET_DBG("FIN acknowledged, going to FIN_WAIT_2 state seq %u, ack %u"
, conn->seq, conn->ack);
/* Acknowledge any received data */
if (len > 0) {
tcp_out(conn, ACK);
}
} else if (th) {
NET_DBG("Staying in FIN_WAIT_1 flags 0x%x state seq %u, ack %u",
fl, conn->seq, conn->ack);
if (len > 0) {
tcp_send_timer_cancel(conn);
/* Send out a duplicate ACK, with the pending FIN flag */
tcp_out(conn, FIN | ACK);
}
verdict = NET_OK;
}
}
break;
case TCP_FIN_WAIT_2:
if (th && (FL(&fl, ==, FIN, th_seq(th) == conn->ack) ||
FL(&fl, ==, FIN | ACK, th_seq(th) == conn->ack) ||
FL(&fl, ==, FIN | PSH | ACK,
th_seq(th) == conn->ack))) {
/* Received FIN on FIN_WAIT_2, so cancel the timer */
k_work_cancel_delayable(&conn->fin_timer);
/* Acknowledge but drop any data, but subtract any duplicate data */
if (th) {
if (len > 0) {
int32_t new_len = len - net_tcp_seq_cmp(conn->ack, th_seq(th));
/* Cases:
* - Data already received earlier: len > 0 , new_len <= 0
* - Partially new data len > 0, new_len > 0
* - Out of order data len > 0, new_len > 0, ignore the data in
* between
*/
if (new_len > 0) {
conn_ack(conn, + new_len);
}
}
conn_ack(conn, + 1);
tcp_out(conn, ACK);
next = TCP_TIME_WAIT;
verdict = NET_OK;
if (FL(&fl, ==, FIN, th_seq(th) == conn->ack) ||
FL(&fl, ==, FIN | ACK, th_seq(th) == conn->ack) ||
FL(&fl, ==, FIN | PSH | ACK,
th_seq(th) == conn->ack)) {
/* Received FIN on FIN_WAIT_2, so cancel the timer */
k_work_cancel_delayable(&conn->fin_timer);
conn_ack(conn, + 1);
tcp_out(conn, ACK);
next = TCP_TIME_WAIT;
verdict = NET_OK;
} else if (len > 0) {
/* Send out a duplicate ACK */
tcp_out(conn, ACK);
verdict = NET_OK;
}
}
break;
case TCP_CLOSING:

View file

@ -29,6 +29,8 @@ LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL);
/* On QEMU, poll() which waits takes +10ms from the requested time. */
#define FUZZ 10
#define TCP_TEARDOWN_TIMEOUT K_SECONDS(3)
ZTEST(net_socket_poll, test_poll)
{
int res;
@ -171,6 +173,8 @@ ZTEST(net_socket_poll, test_poll)
res = close(s_sock);
zassert_equal(res, 0, "close failed");
k_sleep(TCP_TEARDOWN_TIMEOUT);
}
#define TEST_SNDBUF_SIZE CONFIG_NET_TCP_MAX_RECV_WINDOW_SIZE