Skip to content

Commit 1590774

Browse files
authored
Add sockets tests for hangups on both sides, and implement timeouts for TCP sockets (#636)
The new tests are based on the previous simple test where the server accepts one connection and echoes back a message received from the client. Scenarios tested: * Client hangs up after connecting * Client hangs up while sending * Client hangs up after sending * Client hangs up while receiving * Server hangs up after connecting but before receiving data from client * Server hangs up while receiving data * Server hangs up after receiving all data but before sending data back to client * Server hangs up while sending data back to client As part of this testing, I implemented timeouts for TCP sockets. There are no tests for UDP sockets yet, so timeouts are not implemented yet for them.
1 parent 25827cc commit 1590774

20 files changed

+883
-13
lines changed

libc-bottom-half/cloudlibc/src/common/time.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <time.h>
1919

2020
#define NSEC_PER_SEC 1000000000
21+
#define USEC_PER_SEC 1000000
2122

2223
static inline bool timespec_to_timestamp_exact(
2324
#ifdef __wasilibc_use_wasip2
@@ -117,7 +118,30 @@ static inline struct timeval instant_to_timeval(
117118
monotonic_clock_instant_t ns) {
118119
// Decompose instant into seconds and microoseconds
119120
return (struct timeval){.tv_sec = ns / NSEC_PER_SEC,
120-
.tv_usec = (ns % NSEC_PER_SEC) / 1000};
121+
.tv_usec = (ns % NSEC_PER_SEC) / 1000};
122+
}
123+
124+
static inline struct timeval duration_to_timeval(
125+
monotonic_clock_duration_t ns) {
126+
// Decompose duration into seconds and microoseconds
127+
return (struct timeval){.tv_sec = ns / NSEC_PER_SEC,
128+
.tv_usec = (ns % NSEC_PER_SEC) / 1000};
129+
}
130+
131+
static inline bool timeval_to_duration(
132+
struct timeval* timeval, monotonic_clock_duration_t* duration) {
133+
// Invalid microseconds field
134+
if (timeval->tv_usec < 0 || timeval->tv_usec >= USEC_PER_SEC)
135+
return false;
136+
if (timeval->tv_sec < 0) {
137+
// Timestamps before the Epoch are not supported
138+
*duration = 0;
139+
} else if (__builtin_mul_overflow(timeval->tv_sec, NSEC_PER_SEC, duration) ||
140+
__builtin_add_overflow(*duration, timeval->tv_usec * 1000, duration)) {
141+
// Make sure our duration does not overflow
142+
*duration = NUMERIC_MAX(monotonic_clock_instant_t);
143+
}
144+
return true;
121145
}
122146

123147
static inline struct timeval timestamp_to_timeval(

libc-bottom-half/headers/private/wasi/descriptor_table.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ typedef struct {
5555
bool fake_reuseaddr;
5656
network_ip_address_family_t family;
5757
tcp_socket_state_t state;
58+
monotonic_clock_duration_t send_timeout;
59+
monotonic_clock_duration_t recv_timeout;
5860
} tcp_socket_t;
5961

6062
typedef struct {

libc-bottom-half/sources/accept-wasip2.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ int tcp_accept(tcp_socket_t *socket, bool client_blocking,
9292
.output = output,
9393
.output_pollable = output_pollable,
9494
} },
95+
.send_timeout = 0, // Use 0 to represent no timeout
96+
.recv_timeout = 0,
9597
} };
9698

9799
int client_fd;

libc-bottom-half/sources/recv.c

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,35 @@ static ssize_t tcp_recvfrom(tcp_socket_t *socket, uint8_t *buffer,
6161
wasip2_list_u8_free(&result);
6262
return result.len;
6363
} else if (should_block) {
64-
poll_borrow_pollable_t pollable_borrow =
65-
poll_borrow_pollable(connection.input_pollable);
66-
poll_method_pollable_block(pollable_borrow);
64+
poll_borrow_pollable_t pollable_borrow =
65+
poll_borrow_pollable(connection.input_pollable);
66+
if (socket->recv_timeout != 0) {
67+
monotonic_clock_own_pollable_t timeout_pollable =
68+
monotonic_clock_subscribe_duration(socket->recv_timeout);
69+
poll_list_borrow_pollable_t pollables;
70+
pollables.ptr = malloc(sizeof(poll_borrow_pollable_t) * 2);
71+
if (!pollables.ptr) {
72+
errno = ENOMEM;
73+
return -1;
74+
}
75+
pollables.ptr[0] = pollable_borrow;
76+
pollables.ptr[1] = poll_borrow_pollable(timeout_pollable);
77+
pollables.len = 2;
78+
wasip2_list_u32_t ret;
79+
poll_poll(&pollables, &ret);
80+
poll_pollable_drop_own(timeout_pollable);
81+
poll_list_borrow_pollable_free(&pollables);
82+
for (size_t i = 0; i < ret.len; i++) {
83+
if (ret.ptr[i] == 1) {
84+
// Timed out
85+
errno = EWOULDBLOCK;
86+
wasip2_list_u32_free(&ret);
87+
return -1;
88+
}
89+
}
90+
wasip2_list_u32_free(&ret);
91+
} else
92+
poll_method_pollable_block(pollable_borrow);
6793
} else {
6894
errno = EWOULDBLOCK;
6995
return -1;

libc-bottom-half/sources/send.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,34 @@ static ssize_t tcp_sendto(tcp_socket_t *socket, const uint8_t *buffer,
6868
poll_borrow_pollable_t pollable_borrow =
6969
poll_borrow_pollable(
7070
connection.output_pollable);
71+
if (socket->send_timeout != 0) {
72+
monotonic_clock_own_pollable_t timeout_pollable =
73+
monotonic_clock_subscribe_duration(socket->send_timeout);
74+
poll_list_borrow_pollable_t pollables;
75+
pollables.ptr = malloc(sizeof(poll_borrow_pollable_t) * 2);
76+
if (!pollables.ptr) {
77+
errno = ENOMEM;
78+
return -1;
79+
}
80+
pollables.ptr[0] = pollable_borrow;
81+
pollables.ptr[1] = poll_borrow_pollable(timeout_pollable);
82+
pollables.len = 2;
83+
wasip2_list_u32_t ret;
84+
poll_poll(&pollables, &ret);
85+
poll_pollable_drop_own(timeout_pollable);
86+
poll_list_borrow_pollable_free(&pollables);
87+
for (size_t i = 0; i < ret.len; i++) {
88+
if (ret.ptr[i] == 1) {
89+
// Timed out
90+
errno = EWOULDBLOCK;
91+
wasip2_list_u32_free(&ret);
92+
return -1;
93+
}
94+
}
95+
wasip2_list_u32_free(&ret);
96+
} else
97+
poll_method_pollable_block(pollable_borrow);
98+
7199
poll_method_pollable_block(pollable_borrow);
72100
} else {
73101
errno = EWOULDBLOCK;

libc-bottom-half/sources/socket.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ static int tcp_socket(network_ip_address_family_t family, bool blocking)
2828
.state = { .tag = TCP_SOCKET_STATE_UNBOUND,
2929
.unbound = {
3030
/* No additional state. */ } },
31+
.send_timeout = 0, // Use 0 to represent no timeout
32+
.recv_timeout = 0,
3133
} };
3234

3335
int fd;

libc-bottom-half/sources/sockopt.c

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <errno.h>
66
#include <limits.h>
77
#include <string.h>
8+
#include "common/time.h"
89

910
#include <wasi/api.h>
1011
#include <wasi/descriptor_table.h>
@@ -104,8 +105,18 @@ int tcp_getsockopt(tcp_socket_t *socket, int level, int optname,
104105
value = socket->fake_reuseaddr;
105106
break;
106107
}
107-
case SO_RCVTIMEO: // TODO wasi-sockets: emulate in wasi-libc itself
108-
case SO_SNDTIMEO: // TODO wasi-sockets: emulate in wasi-libc itself
108+
case SO_RCVTIMEO: {
109+
struct timeval tv = duration_to_timeval(socket->recv_timeout);
110+
memcpy(optval, &tv, *optlen < sizeof(struct timeval) ? *optlen : sizeof(struct timeval));
111+
*optlen = sizeof(struct timeval);
112+
return 0;
113+
}
114+
case SO_SNDTIMEO: {
115+
struct timeval tv = duration_to_timeval(socket->send_timeout);
116+
memcpy(optval, &tv, *optlen < sizeof(struct timeval) ? *optlen : sizeof(struct timeval));
117+
*optlen = sizeof(struct timeval);
118+
return 0;
119+
}
109120
default:
110121
errno = ENOPROTOOPT;
111122
return -1;
@@ -288,8 +299,22 @@ int tcp_setsockopt(tcp_socket_t *socket, int level, int optname,
288299
socket->fake_reuseaddr = (intval != 0);
289300
return 0;
290301
}
291-
case SO_RCVTIMEO: // TODO wasi-sockets: emulate in wasi-libc itself
292-
case SO_SNDTIMEO: // TODO wasi-sockets: emulate in wasi-libc itself
302+
case SO_RCVTIMEO: {
303+
struct timeval* tv = (struct timeval*) optval;
304+
if (!timeval_to_duration(tv, &socket->recv_timeout)) {
305+
errno = EINVAL;
306+
return -1;
307+
}
308+
return 0;
309+
}
310+
case SO_SNDTIMEO: {
311+
struct timeval* tv = (struct timeval*) optval;
312+
if (!timeval_to_duration(tv, &socket->send_timeout)) {
313+
errno = EINVAL;
314+
return -1;
315+
}
316+
return 0;
317+
}
293318
default:
294319
errno = ENOPROTOOPT;
295320
return -1;

test/scripts/run-test.sh

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ ENGINE="${ENGINE:-wasmtime}"
1414

1515
# Run the client/server sockets test, which requires a client and server
1616
# running in separate processes
17-
run_sockets_test_simple() {
17+
# Takes the name of the .wasm file for the server as an argument
18+
run_sockets_test() {
1819
# Args are the same for client and server
1920
cd $DIR
20-
server_wasm=`echo $WASM | sed -e 's/client/server/g'`
21+
server_wasm=$1
2122
echo "$ENV $ENGINE $RUN $server_wasm $ARGS" > server_cmd.sh
2223
chmod +x server_cmd.sh
2324
# Start the server
@@ -44,7 +45,7 @@ CLIENTS=10
4445
run_sockets_test_multiple() {
4546
# Args are the same for client and server
4647
cd $DIR
47-
server_wasm=`echo $WASM | sed -e 's/client/server/g'`
48+
server_wasm=$1
4849
echo "$ENV $ENGINE $RUN $server_wasm $ARGS" > server_cmd.sh
4950
chmod +x server_cmd.sh
5051
# Start the server
@@ -76,20 +77,60 @@ run_sockets_test_multiple() {
7677
}
7778

7879
testname=$(basename $WASM)
80+
parent=$(dirname $WASM)
7981
if [ $testname == "sockets-server.component.wasm" ]; then
8082
exit 0
8183
fi
8284
if [ $testname == "sockets-multiple-server.component.wasm" ]; then
8385
exit 0
8486
fi
8587
if [ $testname == "sockets-client.component.wasm" ]; then
86-
run_sockets_test_simple
88+
run_sockets_test $parent/"sockets-server.component.wasm"
8789
exit $?
8890
fi
8991
if [ $testname == "sockets-multiple-client.component.wasm" ]; then
90-
run_sockets_test_multiple
92+
run_sockets_test_multiple $parent/"sockets-multiple-server.component.wasm"
9193
exit $?
9294
fi
95+
if [ $testname = "sockets-client-hangup-after-connect.component.wasm" ]; then
96+
run_sockets_test $parent/"sockets-server-handle-hangups.component.wasm"
97+
exit $?
98+
fi
99+
if [ $testname = "sockets-client-hangup-while-sending.component.wasm" ]; then
100+
run_sockets_test $parent/"sockets-server-handle-hangups.component.wasm"
101+
exit $?
102+
fi
103+
if [ $testname = "sockets-client-hangup-after-sending.component.wasm" ]; then
104+
run_sockets_test $parent/"sockets-server-handle-hangups.component.wasm"
105+
exit $?
106+
fi
107+
if [ $testname = "sockets-client-hangup-while-receiving.component.wasm" ]; then
108+
run_sockets_test $parent/"sockets-server-handle-hangups.component.wasm"
109+
exit $?
110+
fi
111+
if [ $testname == "sockets-server-hangup-before-send.component.wasm" ]; then
112+
exit 0
113+
fi
114+
if [ $testname == "sockets-server-hangup-during-send.component.wasm" ]; then
115+
exit 0
116+
fi
117+
if [ $testname == "sockets-server-hangup-before-recv.component.wasm" ]; then
118+
exit 0
119+
fi
120+
if [ $testname == "sockets-server-hangup-during-recv.component.wasm" ]; then
121+
exit 0
122+
fi
123+
if [ $testname == "sockets-server-handle-hangups.component.wasm" ]; then
124+
exit 0
125+
fi
126+
if [ $testname == "sockets-client-handle-hangups.component.wasm" ]; then
127+
run_sockets_test $parent/"sockets-server-hangup-before-send.component.wasm"
128+
run_sockets_test $parent/"sockets-server-hangup-during-send.component.wasm"
129+
run_sockets_test $parent/"sockets-server-hangup-before-recv.component.wasm"
130+
run_sockets_test $parent/"sockets-server-hangup-during-recv.component.wasm"
131+
# Deliberately fall through so that we can run this test without a server
132+
# (to test the behavior when connect() fails)
133+
fi
93134
cd $DIR
94135
mkdir -p fs
95136
echo "$ENV $ENGINE $RUN $WASM $ARGS" > cmd.sh

test/src/misc/setsockopt.c

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//! filter.py(TARGET_TRIPLE): wasm32-wasip2
2+
//! add-flags.py(RUN): --wasi=inherit-network=y
3+
#include <errno.h>
4+
#include <fcntl.h>
5+
#include <stdio.h>
6+
#include <stdlib.h>
7+
#include <string.h>
8+
#include <unistd.h>
9+
#include <netdb.h>
10+
#include <sys/socket.h>
11+
#include <netinet/in.h>
12+
#include <arpa/inet.h>
13+
#include "test.h"
14+
15+
#define TEST(c) do { \
16+
errno = 0; \
17+
if (!(c)) \
18+
t_error("%s failed (errno = %d)\n", #c, errno); \
19+
} while(0)
20+
21+
int BUFSIZE = 256;
22+
23+
// See sockets-server.c -- must be running already as a separate executable
24+
void test_tcp_client() {
25+
// Prepare client socket
26+
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
27+
TEST(socket_fd != -1);
28+
29+
// Set send timeout to 3 and let recv timeout default to 0; test that getsockopt() returns the right results
30+
struct timeval tv = { .tv_sec = 3, .tv_usec = 0 };
31+
socklen_t len = sizeof(tv);
32+
TEST(setsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv))==0);
33+
TEST(getsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, &len)==0);
34+
TEST(tv.tv_sec == 0);
35+
TEST(tv.tv_usec == 0);
36+
TEST(len == sizeof(tv));
37+
TEST(getsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, &len)==0);
38+
TEST(tv.tv_sec == 3);
39+
TEST(tv.tv_usec == 0);
40+
TEST(len == sizeof(tv));
41+
42+
// Set send timeout to 0 and recv timeout to 3; test that getsockopt() returns the right results
43+
tv.tv_sec = 0;
44+
TEST(setsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv))==0);
45+
tv.tv_sec = 3;
46+
TEST(setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv))==0);
47+
TEST(getsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, &len)==0);
48+
TEST(tv.tv_sec == 3);
49+
TEST(tv.tv_usec == 0);
50+
TEST(len == sizeof(tv));
51+
TEST(getsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, &len)==0);
52+
TEST(tv.tv_sec == 0);
53+
TEST(tv.tv_usec == 0);
54+
TEST(len == sizeof(tv));
55+
56+
// Set send timeout to 1 and recv timeout to 2; test that getsockopt() returns the right results
57+
tv.tv_sec = 1;
58+
TEST(setsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv))==0);
59+
tv.tv_sec = 2;
60+
TEST(setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv))==0);
61+
TEST(getsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, &len)==0);
62+
TEST(tv.tv_sec == 2);
63+
TEST(tv.tv_usec == 0);
64+
TEST(len == sizeof(tv));
65+
TEST(getsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, &len)==0);
66+
TEST(tv.tv_sec == 1);
67+
TEST(tv.tv_usec == 0);
68+
TEST(len == sizeof(tv));
69+
70+
// Shut down client
71+
close(socket_fd);
72+
}
73+
74+
int main(void)
75+
{
76+
test_tcp_client();
77+
78+
return t_status;
79+
}

0 commit comments

Comments
 (0)