Skip to content

Commit f493dc2

Browse files
dicejbadeend
andauthored
implement basic TCP/UDP server support (#481)
This adds `bind`, `listen`, and `accept` implementations based on `wasi-sockets` for the `wasm32-wasip2` target. Signed-off-by: Joel Dice <joel.dice@fermyon.com> Co-authored-by: Dave Bakker <github@davebakker.io>
1 parent 684f155 commit f493dc2

7 files changed

Lines changed: 325 additions & 4 deletions

File tree

Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,10 @@ LIBC_BOTTOM_HALF_OMIT_SOURCES := \
8686
$(LIBC_BOTTOM_HALF_SOURCES)/socket.c \
8787
$(LIBC_BOTTOM_HALF_SOURCES)/send.c \
8888
$(LIBC_BOTTOM_HALF_SOURCES)/recv.c \
89-
$(LIBC_BOTTOM_HALF_SOURCES)/sockets_utils.c
89+
$(LIBC_BOTTOM_HALF_SOURCES)/sockets_utils.c \
90+
$(LIBC_BOTTOM_HALF_SOURCES)/bind.c \
91+
$(LIBC_BOTTOM_HALF_SOURCES)/listen.c \
92+
$(LIBC_BOTTOM_HALF_SOURCES)/accept-wasip2.c
9093
LIBC_BOTTOM_HALF_ALL_SOURCES := $(filter-out $(LIBC_BOTTOM_HALF_OMIT_SOURCES),$(LIBC_BOTTOM_HALF_ALL_SOURCES))
9194
# Omit p2-specific headers from include-all.c test.
9295
INCLUDE_ALL_CLAUSES := -not -name wasip2.h -not -name descriptor_table.h
@@ -96,7 +99,8 @@ ifeq ($(WASI_SNAPSHOT), p2)
9699
# Omit source files not relevant to WASIp2.
97100
LIBC_BOTTOM_HALF_OMIT_SOURCES := \
98101
$(LIBC_BOTTOM_HALF_CLOUDLIBC_SRC)/libc/sys/socket/send.c \
99-
$(LIBC_BOTTOM_HALF_CLOUDLIBC_SRC)/libc/sys/socket/recv.c
102+
$(LIBC_BOTTOM_HALF_CLOUDLIBC_SRC)/libc/sys/socket/recv.c \
103+
$(LIBC_BOTTOM_HALF_SOURCES)/accept-wasip1.c
100104
LIBC_BOTTOM_HALF_ALL_SOURCES := $(filter-out $(LIBC_BOTTOM_HALF_OMIT_SOURCES),$(LIBC_BOTTOM_HALF_ALL_SOURCES))
101105
endif
102106

expected/wasm32-wasip2/defined-symbols.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ atoll
411411
basename
412412
bcmp
413413
bcopy
414+
bind
414415
bsd_signal
415416
bsearch
416417
btowc
@@ -892,6 +893,7 @@ lgammal
892893
lgammal_r
893894
link
894895
linkat
896+
listen
895897
llabs
896898
lldiv
897899
llrint
@@ -1236,10 +1238,13 @@ tanh
12361238
tanhf
12371239
tanhl
12381240
tanl
1241+
tcp_accept
1242+
tcp_bind
12391243
tcp_borrow_tcp_socket
12401244
tcp_create_socket_create_tcp_socket
12411245
tcp_create_socket_result_own_tcp_socket_error_code_free
12421246
tcp_ip_socket_address_free
1247+
tcp_listen
12431248
tcp_method_tcp_socket_accept
12441249
tcp_method_tcp_socket_address_family
12451250
tcp_method_tcp_socket_finish_bind
@@ -1320,6 +1325,8 @@ truncf
13201325
truncl
13211326
tsearch
13221327
twalk
1328+
udp_accept
1329+
udp_bind
13231330
udp_borrow_incoming_datagram_stream
13241331
udp_borrow_outgoing_datagram_stream
13251332
udp_borrow_udp_socket
@@ -1331,6 +1338,7 @@ udp_incoming_datagram_stream_drop_own
13311338
udp_ip_socket_address_free
13321339
udp_list_incoming_datagram_free
13331340
udp_list_outgoing_datagram_free
1341+
udp_listen
13341342
udp_method_incoming_datagram_stream_receive
13351343
udp_method_incoming_datagram_stream_subscribe
13361344
udp_method_outgoing_datagram_stream_check_send
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#include <sys/socket.h>
2+
3+
#include <errno.h>
4+
#include <netinet/in.h>
5+
#include <string.h>
6+
7+
#include <wasi/api.h>
8+
#include <wasi/descriptor_table.h>
9+
#include <wasi/sockets_utils.h>
10+
11+
int tcp_accept(tcp_socket_t *socket, bool client_blocking,
12+
struct sockaddr *addr, socklen_t *addrlen)
13+
{
14+
output_sockaddr_t output_addr;
15+
if (!__wasi_sockets_utils__output_addr_validate(
16+
socket->family, addr, addrlen, &output_addr)) {
17+
errno = EINVAL;
18+
return -1;
19+
}
20+
21+
tcp_socket_state_listening_t listener;
22+
if (socket->state.tag == TCP_SOCKET_STATE_LISTENING) {
23+
listener = socket->state.listening;
24+
} else {
25+
errno = EINVAL;
26+
return -1;
27+
}
28+
29+
tcp_borrow_tcp_socket_t socket_borrow =
30+
tcp_borrow_tcp_socket(socket->socket);
31+
32+
tcp_tuple3_own_tcp_socket_own_input_stream_own_output_stream_t
33+
client_and_io;
34+
network_error_code_t error;
35+
while (!tcp_method_tcp_socket_accept(socket_borrow, &client_and_io,
36+
&error)) {
37+
if (error == NETWORK_ERROR_CODE_WOULD_BLOCK) {
38+
if (socket->blocking) {
39+
poll_borrow_pollable_t pollable_borrow =
40+
poll_borrow_pollable(
41+
socket->socket_pollable);
42+
poll_method_pollable_block(pollable_borrow);
43+
} else {
44+
errno = EWOULDBLOCK;
45+
return -1;
46+
}
47+
} else {
48+
errno = __wasi_sockets_utils__map_error(error);
49+
return -1;
50+
}
51+
}
52+
53+
tcp_own_tcp_socket_t client = client_and_io.f0;
54+
tcp_borrow_tcp_socket_t client_borrow = tcp_borrow_tcp_socket(client);
55+
56+
poll_own_pollable_t client_pollable =
57+
tcp_method_tcp_socket_subscribe(client_borrow);
58+
59+
streams_own_input_stream_t input = client_and_io.f1;
60+
streams_borrow_input_stream_t input_borrow =
61+
streams_borrow_input_stream(input);
62+
poll_own_pollable_t input_pollable =
63+
streams_method_input_stream_subscribe(input_borrow);
64+
65+
streams_own_output_stream_t output = client_and_io.f2;
66+
streams_borrow_output_stream_t output_borrow =
67+
streams_borrow_output_stream(output);
68+
poll_own_pollable_t output_pollable =
69+
streams_method_output_stream_subscribe(output_borrow);
70+
71+
if (output_addr.tag != OUTPUT_SOCKADDR_NULL) {
72+
network_ip_socket_address_t remote_address;
73+
if (!tcp_method_tcp_socket_remote_address(
74+
client_borrow, &remote_address, &error)) {
75+
// TODO wasi-sockets: How to recover from this in a POSIX compatible way?
76+
abort();
77+
}
78+
79+
__wasi_sockets_utils__output_addr_write(remote_address,
80+
&output_addr);
81+
}
82+
83+
descriptor_table_entry_t client_entry = { .tag = DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET, .tcp_socket = {
84+
.socket = client,
85+
.socket_pollable = client_pollable,
86+
.blocking = client_blocking,
87+
.fake_nodelay = socket->fake_nodelay,
88+
.family = socket->family,
89+
.state = { .tag = TCP_SOCKET_STATE_CONNECTED, .connected = {
90+
.input = input,
91+
.input_pollable = input_pollable,
92+
.output = output,
93+
.output_pollable = output_pollable,
94+
} },
95+
} };
96+
97+
int client_fd;
98+
if (!descriptor_table_insert(client_entry, &client_fd)) {
99+
errno = EMFILE;
100+
return -1;
101+
}
102+
103+
return client_fd;
104+
}
105+
106+
int udp_accept(udp_socket_t *socket, bool client_blocking,
107+
struct sockaddr *addr, socklen_t *addrlen)
108+
{
109+
// UDP doesn't support accept
110+
errno = EOPNOTSUPP;
111+
return -1;
112+
}
113+
114+
int accept(int socket, struct sockaddr *restrict addr,
115+
socklen_t *restrict addrlen)
116+
{
117+
return accept4(socket, addr, addrlen, 0);
118+
}
119+
120+
int accept4(int socket, struct sockaddr *restrict addr,
121+
socklen_t *restrict addrlen, int flags)
122+
{
123+
descriptor_table_entry_t *entry;
124+
if (!descriptor_table_get_ref(socket, &entry)) {
125+
errno = EBADF;
126+
return -1;
127+
}
128+
129+
bool client_blocking = (flags & SOCK_NONBLOCK) == 0;
130+
// Ignore SOCK_CLOEXEC flag. That concept does not exist in WASI.
131+
132+
switch (entry->tag) {
133+
case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET:
134+
return tcp_accept(&entry->tcp_socket, client_blocking, addr,
135+
addrlen);
136+
case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET:
137+
return udp_accept(&entry->udp_socket, client_blocking, addr,
138+
addrlen);
139+
default:
140+
errno = EOPNOTSUPP;
141+
return -1;
142+
}
143+
}

libc-bottom-half/sources/bind.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include <errno.h>
2+
#include <netinet/in.h>
3+
4+
#include <wasi/api.h>
5+
#include <wasi/descriptor_table.h>
6+
#include <wasi/sockets_utils.h>
7+
8+
int tcp_bind(tcp_socket_t *socket, const struct sockaddr *addr,
9+
socklen_t addrlen)
10+
{
11+
network_ip_socket_address_t local_address;
12+
int parse_err;
13+
if (!__wasi_sockets_utils__parse_address(socket->family, addr, addrlen,
14+
&local_address, &parse_err)) {
15+
errno = parse_err;
16+
return -1;
17+
}
18+
19+
return __wasi_sockets_utils__tcp_bind(socket, &local_address);
20+
}
21+
22+
int udp_bind(udp_socket_t *socket, const struct sockaddr *addr,
23+
socklen_t addrlen)
24+
{
25+
network_ip_socket_address_t local_address;
26+
int parse_err;
27+
if (!__wasi_sockets_utils__parse_address(socket->family, addr, addrlen,
28+
&local_address, &parse_err)) {
29+
errno = parse_err;
30+
return -1;
31+
}
32+
33+
return __wasi_sockets_utils__udp_bind(socket, &local_address);
34+
}
35+
36+
int bind(int socket, const struct sockaddr *addr, socklen_t addrlen)
37+
{
38+
descriptor_table_entry_t *entry;
39+
if (!descriptor_table_get_ref(socket, &entry)) {
40+
errno = EBADF;
41+
return -1;
42+
}
43+
44+
switch (entry->tag) {
45+
case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET:
46+
return tcp_bind(&entry->tcp_socket, addr, addrlen);
47+
case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET:
48+
return udp_bind(&entry->udp_socket, addr, addrlen);
49+
default:
50+
errno = EOPNOTSUPP;
51+
return -1;
52+
}
53+
}

libc-bottom-half/sources/listen.c

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#include <errno.h>
2+
#include <netinet/in.h>
3+
4+
#include <wasi/api.h>
5+
#include <wasi/descriptor_table.h>
6+
#include <wasi/sockets_utils.h>
7+
8+
int tcp_listen(tcp_socket_t *socket, int backlog)
9+
{
10+
network_error_code_t error;
11+
tcp_borrow_tcp_socket_t socket_borrow =
12+
tcp_borrow_tcp_socket(socket->socket);
13+
14+
switch (socket->state.tag) {
15+
case TCP_SOCKET_STATE_UNBOUND: {
16+
// Socket is not explicitly bound by the user. We'll do it for them:
17+
18+
network_ip_socket_address_t any =
19+
__wasi_sockets_utils__any_addr(socket->family);
20+
int result = __wasi_sockets_utils__tcp_bind(socket, &any);
21+
if (result != 0) {
22+
return result;
23+
}
24+
25+
if (socket->state.tag != TCP_SOCKET_STATE_BOUND) {
26+
abort();
27+
}
28+
// Great! We'll continue below.
29+
break;
30+
}
31+
case TCP_SOCKET_STATE_BOUND:
32+
// Great! We'll continue below.
33+
break;
34+
case TCP_SOCKET_STATE_LISTENING:
35+
// We can only update the backlog size.
36+
break;
37+
case TCP_SOCKET_STATE_CONNECTING:
38+
case TCP_SOCKET_STATE_CONNECTED:
39+
case TCP_SOCKET_STATE_CONNECT_FAILED:
40+
default:
41+
errno = EINVAL;
42+
return -1;
43+
}
44+
45+
if (!tcp_method_tcp_socket_set_listen_backlog_size(socket_borrow,
46+
backlog, &error)) {
47+
abort(); // Our own state checks should've prevented this from happening.
48+
}
49+
50+
if (socket->state.tag == TCP_SOCKET_STATE_LISTENING) {
51+
// Updating the backlog is all we had to do.
52+
return 0;
53+
}
54+
55+
network_borrow_network_t network_borrow =
56+
__wasi_sockets_utils__borrow_network();
57+
if (!tcp_method_tcp_socket_start_listen(socket_borrow, &error)) {
58+
errno = __wasi_sockets_utils__map_error(error);
59+
return -1;
60+
}
61+
62+
// Listen has successfully started. Attempt to finish it:
63+
while (!tcp_method_tcp_socket_finish_listen(socket_borrow, &error)) {
64+
if (error == NETWORK_ERROR_CODE_WOULD_BLOCK) {
65+
poll_borrow_pollable_t pollable_borrow =
66+
poll_borrow_pollable(socket->socket_pollable);
67+
poll_method_pollable_block(pollable_borrow);
68+
} else {
69+
errno = __wasi_sockets_utils__map_error(error);
70+
return -1;
71+
}
72+
}
73+
74+
// Listen successful.
75+
76+
socket->state = (tcp_socket_state_t){
77+
.tag = TCP_SOCKET_STATE_LISTENING,
78+
.listening = { /* No additional state */ }
79+
};
80+
return 0;
81+
}
82+
83+
int udp_listen(udp_socket_t *socket, int backlog)
84+
{
85+
// UDP doesn't support listen
86+
errno = EOPNOTSUPP;
87+
return -1;
88+
}
89+
90+
int listen(int socket, int backlog)
91+
{
92+
descriptor_table_entry_t *entry;
93+
if (!descriptor_table_get_ref(socket, &entry)) {
94+
errno = EBADF;
95+
return -1;
96+
}
97+
98+
if (backlog < 0) {
99+
// POSIX:
100+
// > If listen() is called with a backlog argument value that is
101+
// > less than 0, the function behaves as if it had been called
102+
// > with a backlog argument value of 0.
103+
backlog = 0;
104+
}
105+
106+
switch (entry->tag) {
107+
case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET:
108+
return tcp_listen(&entry->tcp_socket, backlog);
109+
case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET:
110+
return udp_listen(&entry->udp_socket, backlog);
111+
default:
112+
errno = EOPNOTSUPP;
113+
return -1;
114+
}
115+
}

libc-top-half/musl/include/sys/socket.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,6 @@ int shutdown (int, int);
410410

411411
#if (defined __wasilibc_unmodified_upstream) || (defined __wasilibc_use_wasip2)
412412
int connect (int, const struct sockaddr *, socklen_t);
413-
#endif
414-
#ifdef __wasilibc_unmodified_upstream /* WASI has no bind/listen */
415413
int bind (int, const struct sockaddr *, socklen_t);
416414
int listen (int, int);
417415
#endif

0 commit comments

Comments
 (0)