diff --git a/Makefile b/Makefile index 4e2beb01..f7dde274 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -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 diff --git a/expected/wasm32-wasip2/defined-symbols.txt b/expected/wasm32-wasip2/defined-symbols.txt index c4058893..1f61dd8d 100644 --- a/expected/wasm32-wasip2/defined-symbols.txt +++ b/expected/wasm32-wasip2/defined-symbols.txt @@ -411,6 +411,7 @@ atoll basename bcmp bcopy +bind bsd_signal bsearch btowc @@ -892,6 +893,7 @@ lgammal lgammal_r link linkat +listen llabs lldiv llrint @@ -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 @@ -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 @@ -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 diff --git a/libc-bottom-half/sources/accept.c b/libc-bottom-half/sources/accept-wasip1.c similarity index 100% rename from libc-bottom-half/sources/accept.c rename to libc-bottom-half/sources/accept-wasip1.c diff --git a/libc-bottom-half/sources/accept-wasip2.c b/libc-bottom-half/sources/accept-wasip2.c new file mode 100644 index 00000000..36759581 --- /dev/null +++ b/libc-bottom-half/sources/accept-wasip2.c @@ -0,0 +1,143 @@ +#include + +#include +#include +#include + +#include +#include +#include + +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; + } +} diff --git a/libc-bottom-half/sources/bind.c b/libc-bottom-half/sources/bind.c new file mode 100644 index 00000000..62046914 --- /dev/null +++ b/libc-bottom-half/sources/bind.c @@ -0,0 +1,53 @@ +#include +#include + +#include +#include +#include + +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; + } +} diff --git a/libc-bottom-half/sources/listen.c b/libc-bottom-half/sources/listen.c new file mode 100644 index 00000000..29a064a5 --- /dev/null +++ b/libc-bottom-half/sources/listen.c @@ -0,0 +1,115 @@ +#include +#include + +#include +#include +#include + +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; + } +} diff --git a/libc-top-half/musl/include/sys/socket.h b/libc-top-half/musl/include/sys/socket.h index 668a0d7d..4d6a139d 100644 --- a/libc-top-half/musl/include/sys/socket.h +++ b/libc-top-half/musl/include/sys/socket.h @@ -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