Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport v3.5-branch] Keep tcp context valid for the whole connection duration #64286

Merged
merged 5 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion subsys/net/ip/tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -2341,6 +2341,18 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt)
net_stats_update_tcp_seg_rst(net_pkt_iface(pkt));
do_close = true;
close_status = -ECONNRESET;

/* If we receive RST and ACK for the sent SYN, it means
* that there is no socket listening the port we are trying
* to connect to. Set the errno properly in this case.
*/
if (conn->in_connect) {
fl = th_flags(th);
if (FL(&fl, ==, RST | ACK)) {
close_status = -ECONNREFUSED;
}
}

goto out;
}

Expand Down Expand Up @@ -2421,6 +2433,7 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt)
conn->send_options.mss_found = false;
conn_seq(conn, + 1);
next = TCP_SYN_SENT;
tcp_conn_ref(conn);
}
break;
case TCP_SYN_RECEIVED:
Expand Down Expand Up @@ -2496,7 +2509,6 @@ static enum net_verdict tcp_in(struct tcp *conn, struct net_pkt *pkt)
}

next = TCP_ESTABLISHED;
tcp_conn_ref(conn);
net_context_set_state(conn->context,
NET_CONTEXT_CONNECTED);
tcp_ca_init(conn);
Expand Down
11 changes: 11 additions & 0 deletions subsys/net/lib/sockets/sockets.c
Original file line number Diff line number Diff line change
Expand Up @@ -1938,6 +1938,17 @@ int zsock_getsockopt_ctx(struct net_context *ctx, int level, int optname,
switch (level) {
case SOL_SOCKET:
switch (optname) {
case SO_ERROR: {
if (*optlen != sizeof(int)) {
errno = EINVAL;
return -1;
}

*(int *)optval = POINTER_TO_INT(ctx->user_data);

return 0;
}

case SO_TYPE: {
int type = (int)net_context_get_type(ctx);

Expand Down
5 changes: 5 additions & 0 deletions subsys/net/lib/sockets/sockets_select.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,11 @@ int z_impl_zsock_select(int nfds, zsock_fd_set *readfds, zsock_fd_set *writefds,
ZSOCK_FD_SET(fd, exceptfds);
num_selects++;
}

if (writefds != NULL) {
ZSOCK_FD_SET(fd, writefds);
num_selects++;
}
}

res--;
Expand Down
141 changes: 140 additions & 1 deletion tests/net/socket/tcp/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -933,11 +933,18 @@ ZTEST(net_socket_tcp, test_connect_timeout)

test_close(c_sock);

/* If we have preemptive option set, then sleep here in order to allow
* other part of the system to run and update itself.
*/
if (IS_ENABLED(CONFIG_NET_TC_THREAD_PREEMPTIVE)) {
k_sleep(K_MSEC(10));
}

/* After the client socket closing, the context count should be 0 */
net_context_foreach(calc_net_context, &count_after);

zassert_equal(count_after, 0,
"net_context still in use");
"net_context %d still in use", count_after);

restore_packet_loss_ratio();
}
Expand Down Expand Up @@ -2068,6 +2075,138 @@ ZTEST(net_socket_tcp, test_ioctl_fionread_v6)
test_ioctl_fionread_common(AF_INET6);
}

/* Connect to peer which is not listening the test port and
* make sure select() returns proper error for the closed
* connection.
*/
ZTEST(net_socket_tcp, test_connect_and_wait_for_v4_select)
{
struct sockaddr_in addr = { 0 };
struct in_addr v4addr;
int fd, flags, ret, optval;
socklen_t optlen = sizeof(optval);

fd = socket(AF_INET, SOCK_STREAM, 0);

flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

inet_pton(AF_INET, "127.0.0.1", (void *)&v4addr);

addr.sin_family = AF_INET;
net_ipaddr_copy(&addr.sin_addr, &v4addr);

/* There should be nobody serving this port */
addr.sin_port = htons(8088);

ret = connect(fd, (const struct sockaddr *)&addr, sizeof(addr));
zassert_equal(ret, -1, "connect succeed, %d", errno);
zassert_equal(errno, EINPROGRESS, "connect succeed, %d", errno);

/* Wait for the connection (this should fail eventually) */
while (1) {
fd_set wfds;
struct timeval tv = {
.tv_sec = 1,
.tv_usec = 0
};

FD_ZERO(&wfds);
FD_SET(fd, &wfds);

/* Check if the connection is there, this should timeout */
ret = select(fd + 1, NULL, &wfds, NULL, &tv);
if (ret < 0) {
break;
}

if (ret > 0) {
if (FD_ISSET(fd, &wfds)) {
break;
}
}
}

zassert_true(ret > 0, "select failed, %d", errno);

/* Get the reason for the connect */
ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen);
zassert_equal(ret, 0, "getsockopt failed, %d", errno);

/* If SO_ERROR is 0, then it means that connect succeed. Any
* other value (errno) means that it failed.
*/
zassert_equal(optval, ECONNREFUSED, "unexpected connect status, %d", optval);

ret = close(fd);
zassert_equal(ret, 0, "close failed, %d", errno);
}

/* Connect to peer which is not listening the test port and
* make sure poll() returns proper error for the closed
* connection.
*/
ZTEST(net_socket_tcp, test_connect_and_wait_for_v4_poll)
{
struct sockaddr_in addr = { 0 };
struct pollfd fds[1];
struct in_addr v4addr;
int fd, flags, ret, optval;
bool closed = false;
socklen_t optlen = sizeof(optval);

fd = socket(AF_INET, SOCK_STREAM, 0);

flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

inet_pton(AF_INET, "127.0.0.1", (void *)&v4addr);

addr.sin_family = AF_INET;
net_ipaddr_copy(&addr.sin_addr, &v4addr);

/* There should be nobody serving this port */
addr.sin_port = htons(8088);

ret = connect(fd, (const struct sockaddr *)&addr, sizeof(addr));
zassert_equal(ret, -1, "connect succeed, %d", errno);
zassert_equal(errno, EINPROGRESS, "connect succeed, %d", errno);

/* Wait for the connection (this should fail eventually) */
while (1) {
memset(fds, 0, sizeof(fds));
fds[0].fd = fd;
fds[0].events = POLLOUT;

/* Check if the connection is there, this should timeout */
ret = poll(fds, 1, 10);
if (ret < 0) {
break;
}

if (fds[0].revents > 0) {
if (fds[0].revents & POLLERR) {
closed = true;
break;
}
}
}

zassert_true(closed, "poll failed, %d", errno);

/* Get the reason for the connect */
ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen);
zassert_equal(ret, 0, "getsockopt failed, %d", errno);

/* If SO_ERROR is 0, then it means that connect succeed. Any
* other value (errno) means that it failed.
*/
zassert_equal(optval, ECONNREFUSED, "unexpected connect status, %d", optval);

ret = close(fd);
zassert_equal(ret, 0, "close failed, %d", errno);
}

static void after(void *arg)
{
ARG_UNUSED(arg);
Expand Down