Skip to content

Commit

Permalink
implement basic TCP/UDP server support (WebAssembly#481)
Browse files Browse the repository at this point in the history
This adds `bind`, `listen`, and `accept` implementations based on
`wasi-sockets` for the `wasm32-wasip2` target.

Signed-off-by: Joel Dice <[email protected]>
Co-authored-by: Dave Bakker <[email protected]>
  • Loading branch information
dicej and badeend authored Mar 13, 2024
1 parent 684f155 commit f493dc2
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 4 deletions.
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ LIBC_BOTTOM_HALF_OMIT_SOURCES := \
$(LIBC_BOTTOM_HALF_SOURCES)/socket.c \
$(LIBC_BOTTOM_HALF_SOURCES)/send.c \
$(LIBC_BOTTOM_HALF_SOURCES)/recv.c \
$(LIBC_BOTTOM_HALF_SOURCES)/sockets_utils.c
$(LIBC_BOTTOM_HALF_SOURCES)/sockets_utils.c \
$(LIBC_BOTTOM_HALF_SOURCES)/bind.c \
$(LIBC_BOTTOM_HALF_SOURCES)/listen.c \
$(LIBC_BOTTOM_HALF_SOURCES)/accept-wasip2.c
LIBC_BOTTOM_HALF_ALL_SOURCES := $(filter-out $(LIBC_BOTTOM_HALF_OMIT_SOURCES),$(LIBC_BOTTOM_HALF_ALL_SOURCES))
# Omit p2-specific headers from include-all.c test.
INCLUDE_ALL_CLAUSES := -not -name wasip2.h -not -name descriptor_table.h
Expand All @@ -96,7 +99,8 @@ ifeq ($(WASI_SNAPSHOT), p2)
# Omit source files not relevant to WASIp2.
LIBC_BOTTOM_HALF_OMIT_SOURCES := \
$(LIBC_BOTTOM_HALF_CLOUDLIBC_SRC)/libc/sys/socket/send.c \
$(LIBC_BOTTOM_HALF_CLOUDLIBC_SRC)/libc/sys/socket/recv.c
$(LIBC_BOTTOM_HALF_CLOUDLIBC_SRC)/libc/sys/socket/recv.c \
$(LIBC_BOTTOM_HALF_SOURCES)/accept-wasip1.c
LIBC_BOTTOM_HALF_ALL_SOURCES := $(filter-out $(LIBC_BOTTOM_HALF_OMIT_SOURCES),$(LIBC_BOTTOM_HALF_ALL_SOURCES))
endif

Expand Down
8 changes: 8 additions & 0 deletions expected/wasm32-wasip2/defined-symbols.txt
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ atoll
basename
bcmp
bcopy
bind
bsd_signal
bsearch
btowc
Expand Down Expand Up @@ -892,6 +893,7 @@ lgammal
lgammal_r
link
linkat
listen
llabs
lldiv
llrint
Expand Down Expand Up @@ -1236,10 +1238,13 @@ tanh
tanhf
tanhl
tanl
tcp_accept
tcp_bind
tcp_borrow_tcp_socket
tcp_create_socket_create_tcp_socket
tcp_create_socket_result_own_tcp_socket_error_code_free
tcp_ip_socket_address_free
tcp_listen
tcp_method_tcp_socket_accept
tcp_method_tcp_socket_address_family
tcp_method_tcp_socket_finish_bind
Expand Down Expand Up @@ -1320,6 +1325,8 @@ truncf
truncl
tsearch
twalk
udp_accept
udp_bind
udp_borrow_incoming_datagram_stream
udp_borrow_outgoing_datagram_stream
udp_borrow_udp_socket
Expand All @@ -1331,6 +1338,7 @@ udp_incoming_datagram_stream_drop_own
udp_ip_socket_address_free
udp_list_incoming_datagram_free
udp_list_outgoing_datagram_free
udp_listen
udp_method_incoming_datagram_stream_receive
udp_method_incoming_datagram_stream_subscribe
udp_method_outgoing_datagram_stream_check_send
Expand Down
File renamed without changes.
143 changes: 143 additions & 0 deletions libc-bottom-half/sources/accept-wasip2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#include <sys/socket.h>

#include <errno.h>
#include <netinet/in.h>
#include <string.h>

#include <wasi/api.h>
#include <wasi/descriptor_table.h>
#include <wasi/sockets_utils.h>

int tcp_accept(tcp_socket_t *socket, bool client_blocking,
struct sockaddr *addr, socklen_t *addrlen)
{
output_sockaddr_t output_addr;
if (!__wasi_sockets_utils__output_addr_validate(
socket->family, addr, addrlen, &output_addr)) {
errno = EINVAL;
return -1;
}

tcp_socket_state_listening_t listener;
if (socket->state.tag == TCP_SOCKET_STATE_LISTENING) {
listener = socket->state.listening;
} else {
errno = EINVAL;
return -1;
}

tcp_borrow_tcp_socket_t socket_borrow =
tcp_borrow_tcp_socket(socket->socket);

tcp_tuple3_own_tcp_socket_own_input_stream_own_output_stream_t
client_and_io;
network_error_code_t error;
while (!tcp_method_tcp_socket_accept(socket_borrow, &client_and_io,
&error)) {
if (error == NETWORK_ERROR_CODE_WOULD_BLOCK) {
if (socket->blocking) {
poll_borrow_pollable_t pollable_borrow =
poll_borrow_pollable(
socket->socket_pollable);
poll_method_pollable_block(pollable_borrow);
} else {
errno = EWOULDBLOCK;
return -1;
}
} else {
errno = __wasi_sockets_utils__map_error(error);
return -1;
}
}

tcp_own_tcp_socket_t client = client_and_io.f0;
tcp_borrow_tcp_socket_t client_borrow = tcp_borrow_tcp_socket(client);

poll_own_pollable_t client_pollable =
tcp_method_tcp_socket_subscribe(client_borrow);

streams_own_input_stream_t input = client_and_io.f1;
streams_borrow_input_stream_t input_borrow =
streams_borrow_input_stream(input);
poll_own_pollable_t input_pollable =
streams_method_input_stream_subscribe(input_borrow);

streams_own_output_stream_t output = client_and_io.f2;
streams_borrow_output_stream_t output_borrow =
streams_borrow_output_stream(output);
poll_own_pollable_t output_pollable =
streams_method_output_stream_subscribe(output_borrow);

if (output_addr.tag != OUTPUT_SOCKADDR_NULL) {
network_ip_socket_address_t remote_address;
if (!tcp_method_tcp_socket_remote_address(
client_borrow, &remote_address, &error)) {
// TODO wasi-sockets: How to recover from this in a POSIX compatible way?
abort();
}

__wasi_sockets_utils__output_addr_write(remote_address,
&output_addr);
}

descriptor_table_entry_t client_entry = { .tag = DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET, .tcp_socket = {
.socket = client,
.socket_pollable = client_pollable,
.blocking = client_blocking,
.fake_nodelay = socket->fake_nodelay,
.family = socket->family,
.state = { .tag = TCP_SOCKET_STATE_CONNECTED, .connected = {
.input = input,
.input_pollable = input_pollable,
.output = output,
.output_pollable = output_pollable,
} },
} };

int client_fd;
if (!descriptor_table_insert(client_entry, &client_fd)) {
errno = EMFILE;
return -1;
}

return client_fd;
}

int udp_accept(udp_socket_t *socket, bool client_blocking,
struct sockaddr *addr, socklen_t *addrlen)
{
// UDP doesn't support accept
errno = EOPNOTSUPP;
return -1;
}

int accept(int socket, struct sockaddr *restrict addr,
socklen_t *restrict addrlen)
{
return accept4(socket, addr, addrlen, 0);
}

int accept4(int socket, struct sockaddr *restrict addr,
socklen_t *restrict addrlen, int flags)
{
descriptor_table_entry_t *entry;
if (!descriptor_table_get_ref(socket, &entry)) {
errno = EBADF;
return -1;
}

bool client_blocking = (flags & SOCK_NONBLOCK) == 0;
// Ignore SOCK_CLOEXEC flag. That concept does not exist in WASI.

switch (entry->tag) {
case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET:
return tcp_accept(&entry->tcp_socket, client_blocking, addr,
addrlen);
case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET:
return udp_accept(&entry->udp_socket, client_blocking, addr,
addrlen);
default:
errno = EOPNOTSUPP;
return -1;
}
}
53 changes: 53 additions & 0 deletions libc-bottom-half/sources/bind.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include <errno.h>
#include <netinet/in.h>

#include <wasi/api.h>
#include <wasi/descriptor_table.h>
#include <wasi/sockets_utils.h>

int tcp_bind(tcp_socket_t *socket, const struct sockaddr *addr,
socklen_t addrlen)
{
network_ip_socket_address_t local_address;
int parse_err;
if (!__wasi_sockets_utils__parse_address(socket->family, addr, addrlen,
&local_address, &parse_err)) {
errno = parse_err;
return -1;
}

return __wasi_sockets_utils__tcp_bind(socket, &local_address);
}

int udp_bind(udp_socket_t *socket, const struct sockaddr *addr,
socklen_t addrlen)
{
network_ip_socket_address_t local_address;
int parse_err;
if (!__wasi_sockets_utils__parse_address(socket->family, addr, addrlen,
&local_address, &parse_err)) {
errno = parse_err;
return -1;
}

return __wasi_sockets_utils__udp_bind(socket, &local_address);
}

int bind(int socket, const struct sockaddr *addr, socklen_t addrlen)
{
descriptor_table_entry_t *entry;
if (!descriptor_table_get_ref(socket, &entry)) {
errno = EBADF;
return -1;
}

switch (entry->tag) {
case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET:
return tcp_bind(&entry->tcp_socket, addr, addrlen);
case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET:
return udp_bind(&entry->udp_socket, addr, addrlen);
default:
errno = EOPNOTSUPP;
return -1;
}
}
115 changes: 115 additions & 0 deletions libc-bottom-half/sources/listen.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#include <errno.h>
#include <netinet/in.h>

#include <wasi/api.h>
#include <wasi/descriptor_table.h>
#include <wasi/sockets_utils.h>

int tcp_listen(tcp_socket_t *socket, int backlog)
{
network_error_code_t error;
tcp_borrow_tcp_socket_t socket_borrow =
tcp_borrow_tcp_socket(socket->socket);

switch (socket->state.tag) {
case TCP_SOCKET_STATE_UNBOUND: {
// Socket is not explicitly bound by the user. We'll do it for them:

network_ip_socket_address_t any =
__wasi_sockets_utils__any_addr(socket->family);
int result = __wasi_sockets_utils__tcp_bind(socket, &any);
if (result != 0) {
return result;
}

if (socket->state.tag != TCP_SOCKET_STATE_BOUND) {
abort();
}
// Great! We'll continue below.
break;
}
case TCP_SOCKET_STATE_BOUND:
// Great! We'll continue below.
break;
case TCP_SOCKET_STATE_LISTENING:
// We can only update the backlog size.
break;
case TCP_SOCKET_STATE_CONNECTING:
case TCP_SOCKET_STATE_CONNECTED:
case TCP_SOCKET_STATE_CONNECT_FAILED:
default:
errno = EINVAL;
return -1;
}

if (!tcp_method_tcp_socket_set_listen_backlog_size(socket_borrow,
backlog, &error)) {
abort(); // Our own state checks should've prevented this from happening.
}

if (socket->state.tag == TCP_SOCKET_STATE_LISTENING) {
// Updating the backlog is all we had to do.
return 0;
}

network_borrow_network_t network_borrow =
__wasi_sockets_utils__borrow_network();
if (!tcp_method_tcp_socket_start_listen(socket_borrow, &error)) {
errno = __wasi_sockets_utils__map_error(error);
return -1;
}

// Listen has successfully started. Attempt to finish it:
while (!tcp_method_tcp_socket_finish_listen(socket_borrow, &error)) {
if (error == NETWORK_ERROR_CODE_WOULD_BLOCK) {
poll_borrow_pollable_t pollable_borrow =
poll_borrow_pollable(socket->socket_pollable);
poll_method_pollable_block(pollable_borrow);
} else {
errno = __wasi_sockets_utils__map_error(error);
return -1;
}
}

// Listen successful.

socket->state = (tcp_socket_state_t){
.tag = TCP_SOCKET_STATE_LISTENING,
.listening = { /* No additional state */ }
};
return 0;
}

int udp_listen(udp_socket_t *socket, int backlog)
{
// UDP doesn't support listen
errno = EOPNOTSUPP;
return -1;
}

int listen(int socket, int backlog)
{
descriptor_table_entry_t *entry;
if (!descriptor_table_get_ref(socket, &entry)) {
errno = EBADF;
return -1;
}

if (backlog < 0) {
// POSIX:
// > If listen() is called with a backlog argument value that is
// > less than 0, the function behaves as if it had been called
// > with a backlog argument value of 0.
backlog = 0;
}

switch (entry->tag) {
case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET:
return tcp_listen(&entry->tcp_socket, backlog);
case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET:
return udp_listen(&entry->udp_socket, backlog);
default:
errno = EOPNOTSUPP;
return -1;
}
}
2 changes: 0 additions & 2 deletions libc-top-half/musl/include/sys/socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,6 @@ int shutdown (int, int);

#if (defined __wasilibc_unmodified_upstream) || (defined __wasilibc_use_wasip2)
int connect (int, const struct sockaddr *, socklen_t);
#endif
#ifdef __wasilibc_unmodified_upstream /* WASI has no bind/listen */
int bind (int, const struct sockaddr *, socklen_t);
int listen (int, int);
#endif
Expand Down

0 comments on commit f493dc2

Please sign in to comment.