Skip to content

Commit

Permalink
net: tcp: Fix corner case with closed listener
Browse files Browse the repository at this point in the history
There was a corner case which was not handled well in a scenario, when
listening socket was closed during an active handshake with a new
client.

When a listening socket is closed, the accept callback is cleared on the
TCP context. If this happened during a handshake with a new client, i.
e. before final ACK from the client was processed, this lead to a
context leak, as application did not take ownership of the connection
(i. e. had no means to close it).

Fix this, by proactively closing the connection at the TCP level when no
accept_cb is available. Instead of ignoring the fact that no accept_cb
is available, the TCP stack will now enter TCP_FIN_WAIT_1 state and
proceed with a graceful teardown of the connection.

Signed-off-by: Robert Lubos <[email protected]>
  • Loading branch information
rlubos authored and cfriedt committed Aug 27, 2023
1 parent d5252cb commit 4301503
Showing 1 changed file with 25 additions and 12 deletions.
37 changes: 25 additions & 12 deletions subsys/net/ip/tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -2377,27 +2377,39 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt)
case TCP_SYN_RECEIVED:
if (FL(&fl, &, ACK, th_ack(th) == conn->seq &&
th_seq(th) == conn->ack)) {
net_tcp_accept_cb_t accept_cb = NULL;
struct net_context *context = NULL;

if (conn->accepted_conn != NULL) {
accept_cb = conn->accepted_conn->accept_cb;
context = conn->accepted_conn->context;
}

k_work_cancel_delayable(&conn->establish_timer);
tcp_send_timer_cancel(conn);
next = TCP_ESTABLISHED;
tcp_conn_ref(conn);
net_context_set_state(conn->context,
NET_CONTEXT_CONNECTED);

if (conn->accepted_conn) {
if (conn->accepted_conn->accept_cb) {
conn->accepted_conn->accept_cb(
conn->context,
&conn->accepted_conn->context->remote,
sizeof(struct sockaddr), 0,
conn->accepted_conn->context);
}
/* Make sure the accept_cb is only called once. */
conn->accepted_conn = NULL;

/* Make sure the accept_cb is only called once.
if (accept_cb == NULL) {
/* In case of no accept_cb registered,
* application will not take ownership of the
* connection. To prevent connection leak, unref
* the TCP context and put the connection into
* active close (TCP_FIN_WAIT_1).
*/
conn->accepted_conn = NULL;
net_tcp_put(conn->context);
break;
}

accept_cb(conn->context, &context->remote,
sizeof(struct sockaddr), 0, context);

next = TCP_ESTABLISHED;

tcp_ca_init(conn);

if (len) {
Expand Down Expand Up @@ -2974,7 +2986,8 @@ int net_tcp_put(struct net_context *context)
({ const char *state = net_context_state(context);
state ? state : "<unknown>"; }));

if (conn && conn->state == TCP_ESTABLISHED) {
if (conn && (conn->state == TCP_ESTABLISHED ||
conn->state == TCP_SYN_RECEIVED)) {
/* Send all remaining data if possible. */
if (conn->send_data_total > 0) {
NET_DBG("conn %p pending %zu bytes", conn,
Expand Down

0 comments on commit 4301503

Please sign in to comment.