From 684f155664244825f27e3fb52be1518d0d0fbe51 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 13 Mar 2024 11:59:27 -0600 Subject: [PATCH] implement basic TCP/UDP client support (#477) * implement basic TCP/UDP client support This implements `socket`, `connect`, `recv`, `send`, etc. in terms of `wasi-sockets` for the `wasm32-wasip2` target. I've introduced a new public header file: `__wasi_snapshot.h`, which will define a preprocessor symbol `__wasilibc_use_wasip2` if using the `wasm32-wasip2` version of the header, in which case we provide features only available for that target. Co-authored-by: Dave Bakker Signed-off-by: Joel Dice * fix grammar in __wasi_snapshot.h comment Co-authored-by: Dan Gohman Signed-off-by: Joel Dice --------- Signed-off-by: Joel Dice Co-authored-by: Dave Bakker Co-authored-by: Dan Gohman --- Makefile | 19 +- expected/wasm32-wasip1-threads/include-all.c | 1 + expected/wasm32-wasip1/include-all.c | 1 + expected/wasm32-wasip2/defined-symbols.txt | 16 + expected/wasm32-wasip2/include-all.c | 1 + expected/wasm32-wasip2/predefined-macros.txt | 24 +- .../headers/private/wasi/sockets_utils.h | 53 ++ .../headers/public/__header_sys_socket.h | 34 ++ .../headers/public/__wasi_snapshot.h | 5 + libc-bottom-half/sources/connect.c | 197 ++++++++ libc-bottom-half/sources/descriptor_table.c | 2 - libc-bottom-half/sources/recv.c | 198 ++++++++ libc-bottom-half/sources/send.c | 249 ++++++++++ libc-bottom-half/sources/socket.c | 107 ++++ libc-bottom-half/sources/sockets_utils.c | 462 ++++++++++++++++++ libc-top-half/musl/include/sys/socket.h | 16 +- test/Makefile | 6 +- 17 files changed, 1377 insertions(+), 14 deletions(-) create mode 100644 libc-bottom-half/headers/private/wasi/sockets_utils.h create mode 100644 libc-bottom-half/headers/public/__wasi_snapshot.h create mode 100644 libc-bottom-half/sources/connect.c create mode 100644 libc-bottom-half/sources/recv.c create mode 100644 libc-bottom-half/sources/send.c create mode 100644 libc-bottom-half/sources/socket.c create mode 100644 libc-bottom-half/sources/sockets_utils.c diff --git a/Makefile b/Makefile index 9137d996d..4e2beb01d 100644 --- a/Makefile +++ b/Makefile @@ -81,12 +81,25 @@ ifeq ($(WASI_SNAPSHOT), p1) # this list. LIBC_BOTTOM_HALF_OMIT_SOURCES := \ $(LIBC_BOTTOM_HALF_SOURCES)/wasip2.c \ - $(LIBC_BOTTOM_HALF_SOURCES)/descriptor_table.c + $(LIBC_BOTTOM_HALF_SOURCES)/descriptor_table.c \ + $(LIBC_BOTTOM_HALF_SOURCES)/connect.c \ + $(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_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 endif +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_ALL_SOURCES := $(filter-out $(LIBC_BOTTOM_HALF_OMIT_SOURCES),$(LIBC_BOTTOM_HALF_ALL_SOURCES)) +endif + # FIXME(https://reviews.llvm.org/D85567) - due to a bug in LLD the weak # references to a function defined in `chdir.c` only work if `chdir.c` is at the # end of the archive, but once that LLD review lands and propagates into LLVM @@ -687,6 +700,10 @@ include_dirs: # Remove selected header files. $(RM) $(patsubst %,$(SYSROOT_INC)/%,$(MUSL_OMIT_HEADERS)) +ifeq ($(WASI_SNAPSHOT), p2) + printf '#ifndef __wasilibc_use_wasip2\n#define __wasilibc_use_wasip2\n#endif\n' \ + > "$(SYSROOT_INC)/__wasi_snapshot.h" +endif startup_files: include_dirs $(LIBC_BOTTOM_HALF_CRT_OBJS) # diff --git a/expected/wasm32-wasip1-threads/include-all.c b/expected/wasm32-wasip1-threads/include-all.c index d9536e09f..507337430 100644 --- a/expected/wasm32-wasip1-threads/include-all.c +++ b/expected/wasm32-wasip1-threads/include-all.c @@ -60,6 +60,7 @@ #include <__typedef_suseconds_t.h> #include <__typedef_time_t.h> #include <__typedef_uid_t.h> +#include <__wasi_snapshot.h> #include #include #include diff --git a/expected/wasm32-wasip1/include-all.c b/expected/wasm32-wasip1/include-all.c index 1040097ba..cd3b81720 100644 --- a/expected/wasm32-wasip1/include-all.c +++ b/expected/wasm32-wasip1/include-all.c @@ -60,6 +60,7 @@ #include <__typedef_suseconds_t.h> #include <__typedef_time_t.h> #include <__typedef_uid_t.h> +#include <__wasi_snapshot.h> #include #include #include diff --git a/expected/wasm32-wasip2/defined-symbols.txt b/expected/wasm32-wasip2/defined-symbols.txt index bf4b07a6f..c4058893d 100644 --- a/expected/wasm32-wasip2/defined-symbols.txt +++ b/expected/wasm32-wasip2/defined-symbols.txt @@ -300,6 +300,18 @@ __wasi_sock_accept __wasi_sock_recv __wasi_sock_send __wasi_sock_shutdown +__wasi_sockets_utils__any_addr +__wasi_sockets_utils__borrow_network +__wasi_sockets_utils__create_streams +__wasi_sockets_utils__drop_streams +__wasi_sockets_utils__map_error +__wasi_sockets_utils__output_addr_validate +__wasi_sockets_utils__output_addr_write +__wasi_sockets_utils__parse_address +__wasi_sockets_utils__posix_family +__wasi_sockets_utils__stream +__wasi_sockets_utils__tcp_bind +__wasi_sockets_utils__udp_bind __wasilibc_access __wasilibc_cwd __wasilibc_deinitialize_environ @@ -470,6 +482,7 @@ confstr conj conjf conjl +connect copysign copysignf copysignl @@ -1047,6 +1060,7 @@ realloc reallocarray realpath recv +recvfrom regcomp regerror regexec @@ -1088,6 +1102,7 @@ seed48 seekdir select send +sendto setbuf setbuffer setenv @@ -1112,6 +1127,7 @@ sinhl sinl sleep snprintf +socket sprintf sqrt sqrtf diff --git a/expected/wasm32-wasip2/include-all.c b/expected/wasm32-wasip2/include-all.c index db324a96e..045fd1ef7 100644 --- a/expected/wasm32-wasip2/include-all.c +++ b/expected/wasm32-wasip2/include-all.c @@ -60,6 +60,7 @@ #include <__typedef_suseconds_t.h> #include <__typedef_time_t.h> #include <__typedef_uid_t.h> +#include <__wasi_snapshot.h> #include #include #include diff --git a/expected/wasm32-wasip2/predefined-macros.txt b/expected/wasm32-wasip2/predefined-macros.txt index 961a38a3b..20add244c 100644 --- a/expected/wasm32-wasip2/predefined-macros.txt +++ b/expected/wasm32-wasip2/predefined-macros.txt @@ -1162,10 +1162,12 @@ #define MOREDATA 2 #define MSG_ANY 0x02 #define MSG_BAND 0x04 +#define MSG_DONTWAIT 0x0040 #define MSG_HIPRI 0x01 -#define MSG_PEEK __WASI_RIFLAGS_RECV_PEEK -#define MSG_TRUNC __WASI_ROFLAGS_RECV_DATA_TRUNCATED -#define MSG_WAITALL __WASI_RIFLAGS_RECV_WAITALL +#define MSG_NOSIGNAL 0x4000 +#define MSG_PEEK 0x0002 +#define MSG_TRUNC 0x0020 +#define MSG_WAITALL 0x0100 #define MUXID_ALL (-1) #define M_1_PI 0.31830988618379067154 #define M_2_PI 0.63661977236758134308 @@ -1701,9 +1703,22 @@ #define SOCK_DGRAM __WASI_FILETYPE_SOCKET_DGRAM #define SOCK_NONBLOCK (0x00004000) #define SOCK_STREAM __WASI_FILETYPE_SOCKET_STREAM +#define SOL_IP 0 +#define SOL_IPV6 41 #define SOL_SOCKET 0x7fffffff #define SOL_TCP 6 #define SOL_UDP 17 +#define SOMAXCONN 128 +#define SO_ACCEPTCONN 30 +#define SO_DOMAIN 39 +#define SO_ERROR 4 +#define SO_KEEPALIVE 9 +#define SO_PROTOCOL 38 +#define SO_RCVBUF 8 +#define SO_RCVTIMEO 66 +#define SO_REUSEADDR 2 +#define SO_SNDBUF 7 +#define SO_SNDTIMEO 67 #define SO_TYPE 3 #define SSIZE_MAX LONG_MAX #define STATUS ns_o_status @@ -3110,7 +3125,7 @@ #define __tg_real_remquo(x,y,z) (__RETCAST_2(x, y)( __FLT(x) && __FLT(y) ? remquof(x, y, z) : __LDBL((x)+(y)) ? remquol(x, y, z) : remquo(x, y, z) )) #define __tm_gmtoff tm_gmtoff #define __tm_zone tm_zone -#define __va_copy(d,s) __builtin_va_copy(d,s) +#define __va_copy(d,s) __builtin_va_copy(d, s) #define __wasi__ 1 #define __wasi_api_h #define __wasi_libc_environ_h @@ -3179,6 +3194,7 @@ #define __wasilibc___typedef_suseconds_t_h #define __wasilibc___typedef_time_t_h #define __wasilibc___typedef_uid_t_h +#define __wasilibc_use_wasip2 #define __wasm 1 #define __wasm32 1 #define __wasm32__ 1 diff --git a/libc-bottom-half/headers/private/wasi/sockets_utils.h b/libc-bottom-half/headers/private/wasi/sockets_utils.h new file mode 100644 index 000000000..93cf1f45d --- /dev/null +++ b/libc-bottom-half/headers/private/wasi/sockets_utils.h @@ -0,0 +1,53 @@ +#ifndef __wasi_sockets_utils_h +#define __wasi_sockets_utils_h + +#include + +#include + +typedef struct { + enum { + OUTPUT_SOCKADDR_NULL, + OUTPUT_SOCKADDR_V4, + OUTPUT_SOCKADDR_V6, + } tag; + union { + struct { + int dummy; + } null; + struct { + struct sockaddr_in *addr; + socklen_t *addrlen; + } v4; + struct { + struct sockaddr_in6 *addr; + socklen_t *addrlen; + } v6; + }; +} output_sockaddr_t; + +network_borrow_network_t __wasi_sockets_utils__borrow_network(); +int __wasi_sockets_utils__map_error(network_error_code_t wasi_error); +bool __wasi_sockets_utils__parse_address( + network_ip_address_family_t expected_family, + const struct sockaddr *address, socklen_t len, + network_ip_socket_address_t *result, int *error); +bool __wasi_sockets_utils__output_addr_validate( + network_ip_address_family_t expected_family, struct sockaddr *addr, + socklen_t *addrlen, output_sockaddr_t *result); +void __wasi_sockets_utils__output_addr_write( + const network_ip_socket_address_t input, output_sockaddr_t *output); +int __wasi_sockets_utils__posix_family(network_ip_address_family_t wasi_family); +network_ip_socket_address_t +__wasi_sockets_utils__any_addr(network_ip_address_family_t family); +int __wasi_sockets_utils__tcp_bind(tcp_socket_t *socket, + network_ip_socket_address_t *address); +int __wasi_sockets_utils__udp_bind(udp_socket_t *socket, + network_ip_socket_address_t *address); +bool __wasi_sockets_utils__stream(udp_socket_t *socket, + network_ip_socket_address_t *remote_address, + udp_socket_streams_t *result, + network_error_code_t *error); +void __wasi_sockets_utils__drop_streams(udp_socket_streams_t streams); + +#endif diff --git a/libc-bottom-half/headers/public/__header_sys_socket.h b/libc-bottom-half/headers/public/__header_sys_socket.h index 8ba4eff4a..fa98ccac2 100644 --- a/libc-bottom-half/headers/public/__header_sys_socket.h +++ b/libc-bottom-half/headers/public/__header_sys_socket.h @@ -1,6 +1,7 @@ #ifndef __wasilibc___header_sys_socket_h #define __wasilibc___header_sys_socket_h +#include <__wasi_snapshot.h> #include <__struct_msghdr.h> #include <__struct_sockaddr.h> #include <__struct_sockaddr_storage.h> @@ -11,9 +12,42 @@ #define SHUT_WR __WASI_SDFLAGS_WR #define SHUT_RDWR (SHUT_RD | SHUT_WR) +#ifdef __wasilibc_use_wasip2 +#define MSG_DONTWAIT 0x0040 +#define MSG_NOSIGNAL 0x4000 +#define MSG_PEEK 0x0002 +#define MSG_WAITALL 0x0100 +#define MSG_TRUNC 0x0020 + +#define SOL_IP 0 +#define SOL_TCP 6 +#define SOL_UDP 17 +#define SOL_IPV6 41 + +#define SOMAXCONN 128 + +#define SO_REUSEADDR 2 +#define SO_ERROR 4 +#define SO_SNDBUF 7 +#define SO_RCVBUF 8 +#define SO_KEEPALIVE 9 +#define SO_ACCEPTCONN 30 +#define SO_PROTOCOL 38 +#define SO_DOMAIN 39 + +#if __LONG_MAX == 0x7fffffff +#define SO_RCVTIMEO 66 +#define SO_SNDTIMEO 67 +#else +#define SO_RCVTIMEO 20 +#define SO_SNDTIMEO 21 +#endif + +#else // __wasilibc_use_wasip2 #define MSG_PEEK __WASI_RIFLAGS_RECV_PEEK #define MSG_WAITALL __WASI_RIFLAGS_RECV_WAITALL #define MSG_TRUNC __WASI_ROFLAGS_RECV_DATA_TRUNCATED +#endif // __wasilibc_use_wasip2 #define SOCK_DGRAM __WASI_FILETYPE_SOCKET_DGRAM #define SOCK_STREAM __WASI_FILETYPE_SOCKET_STREAM diff --git a/libc-bottom-half/headers/public/__wasi_snapshot.h b/libc-bottom-half/headers/public/__wasi_snapshot.h new file mode 100644 index 000000000..9a1007805 --- /dev/null +++ b/libc-bottom-half/headers/public/__wasi_snapshot.h @@ -0,0 +1,5 @@ +/* This file is (practically) empty by default. The Makefile will replace it + with a non-empty version that defines `__wasilibc_use_wasip2` if targeting + `wasm32-wasip2`. + */ + diff --git a/libc-bottom-half/sources/connect.c b/libc-bottom-half/sources/connect.c new file mode 100644 index 000000000..7ef6808ed --- /dev/null +++ b/libc-bottom-half/sources/connect.c @@ -0,0 +1,197 @@ +#include +#include + +#include +#include + +static int tcp_connect(tcp_socket_t *socket, const struct sockaddr *addr, + socklen_t addrlen) +{ + network_ip_socket_address_t remote_address; + int parse_err; + if (!__wasi_sockets_utils__parse_address(socket->family, addr, addrlen, + &remote_address, &parse_err)) { + errno = parse_err; + return -1; + } + + switch (socket->state.tag) { + case TCP_SOCKET_STATE_UNBOUND: + case TCP_SOCKET_STATE_BOUND: + // These can initiate a connect. + break; + case TCP_SOCKET_STATE_CONNECTING: + errno = EALREADY; + return -1; + case TCP_SOCKET_STATE_CONNECTED: + errno = EISCONN; + return -1; + case TCP_SOCKET_STATE_CONNECT_FAILED: // POSIX: "If connect() fails, the state of the socket is unspecified. Conforming applications should close the file descriptor and create a new socket before attempting to reconnect." + case TCP_SOCKET_STATE_LISTENING: + default: + errno = EOPNOTSUPP; + return -1; + } + + network_error_code_t error; + network_borrow_network_t network_borrow = + __wasi_sockets_utils__borrow_network(); + tcp_borrow_tcp_socket_t socket_borrow = + tcp_borrow_tcp_socket(socket->socket); + + if (!tcp_method_tcp_socket_start_connect(socket_borrow, network_borrow, + &remote_address, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + // Connect has successfully started. + socket->state = (tcp_socket_state_t){ + .tag = TCP_SOCKET_STATE_CONNECTING, + .connecting = { /* No additional state */ } + }; + + // Attempt to finish it: + tcp_tuple2_own_input_stream_own_output_stream_t io; + while (!tcp_method_tcp_socket_finish_connect(socket_borrow, &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 = EINPROGRESS; + return -1; + } + } else { + socket->state = + (tcp_socket_state_t){ .tag = TCP_SOCKET_STATE_CONNECT_FAILED, + .connect_failed = { + .error_code = + error, + } }; + + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + } + + // Connect successful. + + streams_own_input_stream_t input = io.f0; + 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 = io.f1; + 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); + + socket->state = + (tcp_socket_state_t){ .tag = TCP_SOCKET_STATE_CONNECTED, + .connected = { + .input = input, + .input_pollable = input_pollable, + .output = output, + .output_pollable = + output_pollable, + } }; + return 0; +} + +// When `connect` is called on a UDP socket with an AF_UNSPEC address, it is actually a "disconnect" request. +static int udp_connect(udp_socket_t *socket, const struct sockaddr *addr, + socklen_t addrlen) +{ + if (addr == NULL || addrlen < sizeof(struct sockaddr)) { + errno = EINVAL; + return -1; + } + + network_ip_socket_address_t remote_address; + bool has_remote_address = (addr->sa_family != AF_UNSPEC); + if (has_remote_address) { + int parse_err; + if (!__wasi_sockets_utils__parse_address( + socket->family, addr, addrlen, &remote_address, + &parse_err)) { + errno = parse_err; + return -1; + } + } + + // Prepare the socket; binding it if not bound yet, and disconnecting it if connected. + switch (socket->state.tag) { + case UDP_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__udp_bind(socket, &any); + if (result != 0) { + return result; + } + break; + } + case UDP_SOCKET_STATE_BOUND_NOSTREAMS: { + // This is the state we want to be in. + break; + } + case UDP_SOCKET_STATE_BOUND_STREAMING: { + __wasi_sockets_utils__drop_streams( + socket->state.bound_streaming.streams); + socket->state = (udp_socket_state_t){ + .tag = UDP_SOCKET_STATE_BOUND_NOSTREAMS, + .bound_nostreams = {} + }; + break; + } + case UDP_SOCKET_STATE_CONNECTED: { + __wasi_sockets_utils__drop_streams( + socket->state.connected.streams); + socket->state = (udp_socket_state_t){ + .tag = UDP_SOCKET_STATE_BOUND_NOSTREAMS, + .bound_nostreams = {} + }; + break; + } + default: /* unreachable */ + abort(); + } + + network_error_code_t error; + udp_socket_streams_t streams; + + if (!__wasi_sockets_utils__stream( + socket, has_remote_address ? &remote_address : NULL, + &streams, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + return 0; +} + +int connect(int fd, const struct sockaddr *addr, socklen_t addrlen) +{ + descriptor_table_entry_t *entry; + if (!descriptor_table_get_ref(fd, &entry)) { + errno = EBADF; + return -1; + } + + switch (entry->tag) { + case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET: + return tcp_connect(&entry->tcp_socket, addr, addrlen); + case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET: + return udp_connect(&entry->udp_socket, addr, addrlen); + default: + errno = EOPNOTSUPP; + return -1; + } +} diff --git a/libc-bottom-half/sources/descriptor_table.c b/libc-bottom-half/sources/descriptor_table.c index 166bb84df..d45e7ce58 100644 --- a/libc-bottom-half/sources/descriptor_table.c +++ b/libc-bottom-half/sources/descriptor_table.c @@ -22,8 +22,6 @@ * will be managed exclusively in this table. */ -#include - #include __attribute__((__import_module__("wasi_snapshot_preview1"), diff --git a/libc-bottom-half/sources/recv.c b/libc-bottom-half/sources/recv.c new file mode 100644 index 000000000..b9c3f4c05 --- /dev/null +++ b/libc-bottom-half/sources/recv.c @@ -0,0 +1,198 @@ +#include + +#include +#include + +#include +#include +#include + +static ssize_t tcp_recvfrom(tcp_socket_t *socket, uint8_t *buffer, + size_t length, int flags, struct sockaddr *addr, + socklen_t *addrlen) +{ + // TODO wasi-sockets: flags: + // - MSG_WAITALL: we can probably support these relatively easy. + // - MSG_OOB: could be shimmed by always responding that no OOB data is available. + // - MSG_PEEK: could be shimmed by performing the receive into a local socket-specific buffer. And on subsequent receives first check that buffer. + + const int supported_flags = MSG_DONTWAIT; + if ((flags & supported_flags) != flags) { + errno = EOPNOTSUPP; + return -1; + } + + if (addr != NULL || addrlen != NULL) { + errno = EISCONN; + return -1; + } + + tcp_socket_state_connected_t connection; + if (socket->state.tag == TCP_SOCKET_STATE_CONNECTED) { + connection = socket->state.connected; + } else { + errno = ENOTCONN; + return -1; + } + + bool should_block = socket->blocking; + if ((flags & MSG_DONTWAIT) != 0) { + should_block = false; + } + + streams_borrow_input_stream_t rx_borrow = + streams_borrow_input_stream(connection.input); + while (true) { + wasip2_list_u8_t result; + streams_stream_error_t error; + if (!streams_method_input_stream_read(rx_borrow, length, + &result, &error)) { + if (error.tag == STREAMS_STREAM_ERROR_CLOSED) { + return 0; + } else { + // TODO wasi-sockets: wasi-sockets has no way to recover TCP stream errors yet. + errno = EPIPE; + return -1; + } + } + + if (result.len) { + memcpy(buffer, result.ptr, result.len); + wasip2_list_u8_free(&result); + return result.len; + } else if (should_block) { + poll_borrow_pollable_t pollable_borrow = + poll_borrow_pollable(connection.input_pollable); + poll_method_pollable_block(pollable_borrow); + } else { + errno = EWOULDBLOCK; + return -1; + } + } +} + +static ssize_t udp_recvfrom(udp_socket_t *socket, uint8_t *buffer, + size_t length, int flags, struct sockaddr *addr, + socklen_t *addrlen) +{ + // TODO wasi-sockets: flags: + // - MSG_PEEK: could be shimmed by performing the receive into a local socket-specific buffer. And on subsequent receives first check that buffer. + + const int supported_flags = MSG_DONTWAIT | MSG_TRUNC; + if ((flags & supported_flags) != flags) { + errno = EOPNOTSUPP; + return -1; + } + + output_sockaddr_t output_addr; + if (!__wasi_sockets_utils__output_addr_validate( + socket->family, addr, addrlen, &output_addr)) { + errno = EINVAL; + return -1; + } + + network_error_code_t error; + udp_borrow_udp_socket_t socket_borrow = + udp_borrow_udp_socket(socket->socket); + + udp_socket_streams_t streams; + switch (socket->state.tag) { + case UDP_SOCKET_STATE_UNBOUND: { + // Unlike `send`, `recv` should _not_ perform an implicit bind. + errno = EINVAL; + return -1; + } + case UDP_SOCKET_STATE_BOUND_NOSTREAMS: { + if (!__wasi_sockets_utils__stream(socket, NULL, &streams, + &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + break; + } + case UDP_SOCKET_STATE_BOUND_STREAMING: + streams = socket->state.bound_streaming.streams; + break; + + case UDP_SOCKET_STATE_CONNECTED: + streams = socket->state.connected.streams; + break; + + default: /* unreachable */ + abort(); + } + + bool return_real_size = (flags & MSG_TRUNC) != 0; + bool should_block = socket->blocking; + if ((flags & MSG_DONTWAIT) != 0) { + should_block = false; + } + + udp_borrow_incoming_datagram_stream_t incoming_borrow = + udp_borrow_incoming_datagram_stream(streams.incoming); + while (true) { + udp_list_incoming_datagram_t datagrams; + if (!udp_method_incoming_datagram_stream_receive( + incoming_borrow, 1, &datagrams, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + if (datagrams.len) { + udp_incoming_datagram_t datagram = datagrams.ptr[0]; + size_t datagram_size = datagram.data.len; + size_t bytes_to_copy = + datagram_size < length ? datagram_size : length; + + if (output_addr.tag != OUTPUT_SOCKADDR_NULL) { + __wasi_sockets_utils__output_addr_write( + datagram.remote_address, &output_addr); + } + + memcpy(buffer, datagram.data.ptr, bytes_to_copy); + udp_list_incoming_datagram_free(&datagrams); + return return_real_size ? datagram_size : bytes_to_copy; + + } else if (should_block) { + poll_borrow_pollable_t pollable_borrow = + poll_borrow_pollable(streams.incoming_pollable); + poll_method_pollable_block(pollable_borrow); + } else { + errno = EWOULDBLOCK; + return -1; + } + } +} + +ssize_t recv(int socket, void *restrict buffer, size_t length, int flags) +{ + return recvfrom(socket, buffer, length, flags, NULL, NULL); +} + +ssize_t recvfrom(int socket, void *__restrict buffer, size_t length, int flags, + struct sockaddr *__restrict addr, + socklen_t *__restrict addrlen) +{ + descriptor_table_entry_t *entry; + if (!descriptor_table_get_ref(socket, &entry)) { + errno = EBADF; + return -1; + } + + if (buffer == NULL) { + errno = EINVAL; + return -1; + } + + switch (entry->tag) { + case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET: + return tcp_recvfrom(&entry->tcp_socket, buffer, length, flags, + addr, addrlen); + case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET: + return udp_recvfrom(&entry->udp_socket, buffer, length, flags, + addr, addrlen); + default: + errno = EOPNOTSUPP; + return -1; + } +} diff --git a/libc-bottom-half/sources/send.c b/libc-bottom-half/sources/send.c new file mode 100644 index 000000000..42f653b90 --- /dev/null +++ b/libc-bottom-half/sources/send.c @@ -0,0 +1,249 @@ +#include + +#include + +#include +#include +#include + +static ssize_t tcp_sendto(tcp_socket_t *socket, const uint8_t *buffer, + size_t length, int flags, const struct sockaddr *addr, + socklen_t addrlen) +{ + const int supported_flags = MSG_DONTWAIT | MSG_NOSIGNAL; + if ((flags & supported_flags) != flags) { + errno = EOPNOTSUPP; + return -1; + } + + if (addr != NULL || addrlen != 0) { + errno = EISCONN; + return -1; + } + + tcp_socket_state_connected_t connection; + if (socket->state.tag == TCP_SOCKET_STATE_CONNECTED) { + connection = socket->state.connected; + } else { + errno = ENOTCONN; + return -1; + } + + bool should_block = socket->blocking; + if ((flags & MSG_DONTWAIT) != 0) { + should_block = false; + } + + if ((flags & MSG_NOSIGNAL) != 0) { + // Ignore it. WASI has no Unix-style signals. So effectively, + // MSG_NOSIGNAL is always the case, whether it was explicitly + // requested or not. + } + + streams_borrow_output_stream_t tx_borrow = + streams_borrow_output_stream(connection.output); + while (true) { + streams_stream_error_t error; + uint64_t count; + if (!streams_method_output_stream_check_write(tx_borrow, &count, + &error)) { + // TODO wasi-sockets: wasi-sockets has no way to recover stream errors yet. + errno = EPIPE; + return -1; + } + + if (count) { + count = count < length ? count : length; + wasip2_list_u8_t list = { .ptr = (uint8_t *)buffer, + .len = count }; + if (!streams_method_output_stream_write( + tx_borrow, &list, &error)) { + // TODO wasi-sockets: wasi-sockets has no way to recover TCP stream errors yet. + errno = EPIPE; + return -1; + } else { + return count; + } + } else if (should_block) { + poll_borrow_pollable_t pollable_borrow = + poll_borrow_pollable( + connection.output_pollable); + poll_method_pollable_block(pollable_borrow); + } else { + errno = EWOULDBLOCK; + return -1; + } + } +} + +static ssize_t udp_sendto(udp_socket_t *socket, const uint8_t *buffer, + size_t length, int flags, const struct sockaddr *addr, + socklen_t addrlen) +{ + const int supported_flags = MSG_DONTWAIT; + if ((flags & supported_flags) != flags) { + errno = EOPNOTSUPP; + return -1; + } + + network_ip_socket_address_t remote_address; + bool has_remote_address = (addr != NULL); + + if (has_remote_address) { + if (socket->state.tag == UDP_SOCKET_STATE_CONNECTED) { + errno = EISCONN; + return -1; + } + + int parse_err; + if (!__wasi_sockets_utils__parse_address( + socket->family, addr, addrlen, &remote_address, + &parse_err)) { + errno = parse_err; + return -1; + } + } else { + if (addrlen != 0) { + errno = EINVAL; + return -1; + } + + if (socket->state.tag != UDP_SOCKET_STATE_CONNECTED) { + errno = EDESTADDRREQ; + return -1; + } + } + + network_error_code_t error; + udp_borrow_udp_socket_t socket_borrow = + udp_borrow_udp_socket(socket->socket); + + udp_socket_streams_t streams; + switch (socket->state.tag) { + case UDP_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__udp_bind(socket, &any); + if (result != 0) { + return result; + } + + if (!__wasi_sockets_utils__stream(socket, NULL, &streams, + &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + break; + } + case UDP_SOCKET_STATE_BOUND_NOSTREAMS: { + if (!__wasi_sockets_utils__stream(socket, NULL, &streams, + &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + break; + } + case UDP_SOCKET_STATE_BOUND_STREAMING: + streams = socket->state.bound_streaming.streams; + break; + + case UDP_SOCKET_STATE_CONNECTED: + streams = socket->state.connected.streams; + break; + + default: /* unreachable */ + abort(); + } + + bool should_block = socket->blocking; + if ((flags & MSG_DONTWAIT) != 0) { + should_block = false; + } + + udp_outgoing_datagram_t datagrams[1] = {{ + .remote_address = { + .is_some = has_remote_address, + .val = remote_address, + }, + .data = { + .len = length, + .ptr = (uint8_t*)buffer, + }, + }}; + udp_list_outgoing_datagram_t list = { + .len = 1, + .ptr = datagrams, + }; + + udp_borrow_outgoing_datagram_stream_t outgoing_borrow = + udp_borrow_outgoing_datagram_stream(streams.outgoing); + while (true) { + uint64_t allowed; + if (!udp_method_outgoing_datagram_stream_check_send( + outgoing_borrow, &allowed, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + if (allowed) { + uint64_t datagrams_sent; + if (!udp_method_outgoing_datagram_stream_send( + outgoing_borrow, &list, &datagrams_sent, + &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + if (datagrams_sent != 0 && datagrams_sent != 1) { + abort(); + } + + if (datagrams_sent == 1) { + return length; + } + } + + if (should_block) { + poll_borrow_pollable_t pollable_borrow = + poll_borrow_pollable(streams.outgoing_pollable); + poll_method_pollable_block(pollable_borrow); + } else { + errno = EWOULDBLOCK; + return -1; + } + } +} + +ssize_t send(int socket, const void *buffer, size_t length, int flags) +{ + return sendto(socket, buffer, length, flags, NULL, 0); +} + +ssize_t sendto(int socket, const void *buffer, size_t length, int flags, + const struct sockaddr *addr, socklen_t addrlen) +{ + descriptor_table_entry_t *entry; + if (!descriptor_table_get_ref(socket, &entry)) { + errno = EBADF; + return -1; + } + + if (buffer == NULL) { + errno = EINVAL; + return -1; + } + + switch (entry->tag) { + case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET: + return tcp_sendto(&entry->tcp_socket, buffer, length, flags, + addr, addrlen); + case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET: + return udp_sendto(&entry->udp_socket, buffer, length, flags, + addr, addrlen); + default: + errno = EOPNOTSUPP; + return -1; + } +} diff --git a/libc-bottom-half/sources/socket.c b/libc-bottom-half/sources/socket.c new file mode 100644 index 000000000..3e61638b5 --- /dev/null +++ b/libc-bottom-half/sources/socket.c @@ -0,0 +1,107 @@ +#include +#include + +#include +#include + +static int tcp_socket(network_ip_address_family_t family, bool blocking) +{ + tcp_create_socket_error_code_t error; + tcp_own_tcp_socket_t socket; + if (!tcp_create_socket_create_tcp_socket(family, &socket, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + tcp_borrow_tcp_socket_t socket_borrow = tcp_borrow_tcp_socket(socket); + poll_own_pollable_t socket_pollable = + tcp_method_tcp_socket_subscribe(socket_borrow); + + descriptor_table_entry_t + entry = { .tag = DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET, + .tcp_socket = { + .socket = socket, + .socket_pollable = socket_pollable, + .blocking = blocking, + .fake_nodelay = false, + .family = family, + .state = { .tag = TCP_SOCKET_STATE_UNBOUND, + .unbound = { + /* No additional state. */ } }, + } }; + + int fd; + if (!descriptor_table_insert(entry, &fd)) { + errno = EMFILE; + return -1; + } + return fd; +} + +static int udp_socket(network_ip_address_family_t family, bool blocking) +{ + udp_create_socket_error_code_t error; + udp_own_udp_socket_t socket; + if (!udp_create_socket_create_udp_socket(family, &socket, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + udp_borrow_udp_socket_t socket_borrow = udp_borrow_udp_socket(socket); + poll_own_pollable_t socket_pollable = + udp_method_udp_socket_subscribe(socket_borrow); + + descriptor_table_entry_t + entry = { .tag = DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET, + .udp_socket = { + .socket = socket, + .socket_pollable = socket_pollable, + .blocking = blocking, + .family = family, + .state = { .tag = UDP_SOCKET_STATE_UNBOUND, + .unbound = { + /* No additional state. */ } }, + } }; + + int fd; + if (!descriptor_table_insert(entry, &fd)) { + errno = EMFILE; + return -1; + } + return fd; +} + +int socket(int domain, int type, int protocol) +{ + network_ip_address_family_t family; + switch (domain) { + case PF_INET: + family = NETWORK_IP_ADDRESS_FAMILY_IPV4; + break; + + case PF_INET6: + family = NETWORK_IP_ADDRESS_FAMILY_IPV6; + break; + + default: + errno = EAFNOSUPPORT; + return -1; + } + + int real_type = type & ~(SOCK_NONBLOCK | SOCK_CLOEXEC); + bool blocking = (type & SOCK_NONBLOCK) == 0; + // Ignore SOCK_CLOEXEC flag. That concept does not exist in WASI. + + if (real_type == SOCK_STREAM && + (protocol == 0 || protocol == IPPROTO_TCP)) { + return tcp_socket(family, blocking); + + } else if (real_type == SOCK_DGRAM && + (protocol == 0 || protocol == IPPROTO_UDP)) { + return udp_socket(family, blocking); + + } else { + errno = EPROTONOSUPPORT; + return -1; + } +} diff --git a/libc-bottom-half/sources/sockets_utils.c b/libc-bottom-half/sources/sockets_utils.c new file mode 100644 index 000000000..4f5658a5e --- /dev/null +++ b/libc-bottom-half/sources/sockets_utils.c @@ -0,0 +1,462 @@ +#include + +#include + +static network_own_network_t global_network; +static bool global_network_initialized = false; + +network_borrow_network_t __wasi_sockets_utils__borrow_network() +{ + if (!global_network_initialized) { + global_network = instance_network_instance_network(); + global_network_initialized = true; + } + + return network_borrow_network(global_network); +} + +int __wasi_sockets_utils__map_error(network_error_code_t wasi_error) +{ + switch (wasi_error) { + case NETWORK_ERROR_CODE_ACCESS_DENIED: + return EACCES; + case NETWORK_ERROR_CODE_NOT_SUPPORTED: + return EOPNOTSUPP; + case NETWORK_ERROR_CODE_INVALID_ARGUMENT: + return EINVAL; + case NETWORK_ERROR_CODE_OUT_OF_MEMORY: + return ENOMEM; + case NETWORK_ERROR_CODE_TIMEOUT: + return ETIMEDOUT; + case NETWORK_ERROR_CODE_CONCURRENCY_CONFLICT: + return EALREADY; + case NETWORK_ERROR_CODE_WOULD_BLOCK: + return EWOULDBLOCK; + case NETWORK_ERROR_CODE_NEW_SOCKET_LIMIT: + return EMFILE; + case NETWORK_ERROR_CODE_ADDRESS_NOT_BINDABLE: + return EADDRNOTAVAIL; + case NETWORK_ERROR_CODE_ADDRESS_IN_USE: + return EADDRINUSE; + case NETWORK_ERROR_CODE_REMOTE_UNREACHABLE: + return EHOSTUNREACH; + case NETWORK_ERROR_CODE_CONNECTION_REFUSED: + return ECONNREFUSED; + case NETWORK_ERROR_CODE_CONNECTION_RESET: + return ECONNRESET; + case NETWORK_ERROR_CODE_CONNECTION_ABORTED: + return ECONNABORTED; + case NETWORK_ERROR_CODE_DATAGRAM_TOO_LARGE: + return EMSGSIZE; + + case NETWORK_ERROR_CODE_INVALID_STATE: + case NETWORK_ERROR_CODE_NOT_IN_PROGRESS: + abort(); // If our internal state checks are working right, these errors should never show up. + break; + + case NETWORK_ERROR_CODE_NAME_UNRESOLVABLE: + case NETWORK_ERROR_CODE_TEMPORARY_RESOLVER_FAILURE: + case NETWORK_ERROR_CODE_PERMANENT_RESOLVER_FAILURE: + abort(); // These errors are specific to getaddrinfo, which should have filtered these errors out before calling this generic method + break; + + case NETWORK_ERROR_CODE_UNKNOWN: + default: + return EOPNOTSUPP; + } +} + +bool __wasi_sockets_utils__parse_address( + network_ip_address_family_t expected_family, + const struct sockaddr *address, socklen_t len, + network_ip_socket_address_t *result, int *error) +{ + if (address == NULL || len < sizeof(struct sockaddr)) { + *error = EINVAL; + return false; + } + + switch (expected_family) { + case NETWORK_IP_ADDRESS_FAMILY_IPV4: { + if (address->sa_family != AF_INET) { + *error = EAFNOSUPPORT; + return false; + } + + if (len < sizeof(struct sockaddr_in)) { + *error = EINVAL; + return false; + } + + struct sockaddr_in *ipv4 = (struct sockaddr_in *)address; + unsigned ip = ipv4->sin_addr.s_addr; + unsigned short port = ipv4->sin_port; + *result = (network_ip_socket_address_t){ + .tag = NETWORK_IP_SOCKET_ADDRESS_IPV4, + .val = { .ipv4 = { + .port = ntohs(port), // (port << 8) | (port >> 8), + .address = { ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, ip >> 24 }, + } }, + }; + return true; + } + case NETWORK_IP_ADDRESS_FAMILY_IPV6: { + if (address->sa_family != AF_INET6) { + *error = EAFNOSUPPORT; + return false; + } + + if (len < sizeof(struct sockaddr_in6)) { + *error = EINVAL; + return false; + } + + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)address; + unsigned char *ip = (unsigned char *)&(ipv6->sin6_addr.s6_addr); + unsigned short port = ipv6->sin6_port; + *result = (network_ip_socket_address_t){ + .tag = NETWORK_IP_SOCKET_ADDRESS_IPV6, + .val = { .ipv6 = { + .port = ntohs(port), + .address = { + (((unsigned short)ip[0]) << 8) | ip[1], + (((unsigned short)ip[2]) << 8) | ip[3], + (((unsigned short)ip[4]) << 8) | ip[5], + (((unsigned short)ip[6]) << 8) | ip[7], + (((unsigned short)ip[8]) << 8) | ip[9], + (((unsigned short)ip[10]) << 8) | ip[11], + (((unsigned short)ip[12]) << 8) | ip[13], + (((unsigned short)ip[14]) << 8) | ip[15], + }, + // TODO wasi-sockets: do these need to be endian-reversed? + .flow_info = ipv6->sin6_flowinfo, + .scope_id = ipv6->sin6_scope_id, + } } + }; + return true; + } + default: /* unreachable */ + abort(); + } +} + +bool __wasi_sockets_utils__output_addr_validate( + network_ip_address_family_t expected_family, struct sockaddr *addr, + socklen_t *addrlen, output_sockaddr_t *result) +{ + // The address parameters must be either both null or both _not_ null. + + if (addr == NULL && addrlen == NULL) { + *result = (output_sockaddr_t){ .tag = OUTPUT_SOCKADDR_NULL, + .null = {} }; + return true; + + } else if (addr != NULL && addrlen != NULL) { + if (expected_family == NETWORK_IP_ADDRESS_FAMILY_IPV4) { + if (*addrlen < sizeof(struct sockaddr_in)) { + return false; + } + + *result = + (output_sockaddr_t){ .tag = OUTPUT_SOCKADDR_V4, + .v4 = { + .addr = (struct sockaddr_in + *) + addr, + .addrlen = addrlen, + } }; + return true; + + } else if (expected_family == NETWORK_IP_ADDRESS_FAMILY_IPV6) { + if (*addrlen < sizeof(struct sockaddr_in6)) { + return false; + } + + *result = + (output_sockaddr_t){ .tag = OUTPUT_SOCKADDR_V6, + .v6 = { + .addr = (struct sockaddr_in6 + *) + addr, + .addrlen = addrlen, + } }; + return true; + + } else { + abort(); + } + + } else { + return false; + } +} + +void __wasi_sockets_utils__output_addr_write( + const network_ip_socket_address_t input, output_sockaddr_t *output) +{ + switch (input.tag) { + case NETWORK_IP_SOCKET_ADDRESS_IPV4: { + if (output->tag != OUTPUT_SOCKADDR_V4) { + abort(); + } + + network_ipv4_socket_address_t input_v4 = input.val.ipv4; + network_ipv4_address_t ip = input_v4.address; + + *output->v4.addrlen = sizeof(struct sockaddr_in); + *output->v4.addr = (struct sockaddr_in){ + .sin_family = AF_INET, + .sin_port = htons(input_v4.port), + .sin_addr = { .s_addr = ip.f0 | (ip.f1 << 8) | + (ip.f2 << 16) | (ip.f3 << 24) }, + }; + return; + } + case NETWORK_IP_SOCKET_ADDRESS_IPV6: { + if (output->tag != OUTPUT_SOCKADDR_V6) { + abort(); + } + + network_ipv6_socket_address_t input_v6 = input.val.ipv6; + network_ipv6_address_t ip = input_v6.address; + + *output->v6.addrlen = sizeof(struct sockaddr_in6); + *output->v6.addr = (struct sockaddr_in6) { + .sin6_family = AF_INET6, + .sin6_port = htons(input_v6.port), + .sin6_addr = { .s6_addr = { + ip.f0 >> 8, ip.f0 & 0xFF, + ip.f1 >> 8, ip.f1 & 0xFF, + ip.f2 >> 8, ip.f2 & 0xFF, + ip.f3 >> 8, ip.f3 & 0xFF, + ip.f4 >> 8, ip.f4 & 0xFF, + ip.f5 >> 8, ip.f5 & 0xFF, + ip.f6 >> 8, ip.f6 & 0xFF, + ip.f7 >> 8, ip.f7 & 0xFF, + } }, + // TODO wasi-sockets: do these need to be endian-reversed? + .sin6_flowinfo = input_v6.flow_info, + .sin6_scope_id = input_v6.scope_id, + }; + return; + } + default: /* unreachable */ + abort(); + } +} + +int __wasi_sockets_utils__posix_family(network_ip_address_family_t wasi_family) +{ + switch (wasi_family) { + case NETWORK_IP_ADDRESS_FAMILY_IPV4: + return AF_INET; + case NETWORK_IP_ADDRESS_FAMILY_IPV6: + return AF_INET6; + default: /* unreachable */ + abort(); + } +} + +network_ip_socket_address_t +__wasi_sockets_utils__any_addr(network_ip_address_family_t family) +{ + switch (family) { + case NETWORK_IP_ADDRESS_FAMILY_IPV4: + return (network_ip_socket_address_t){ .tag = NETWORK_IP_SOCKET_ADDRESS_IPV4, + .val = { + .ipv4 = { + .port = 0, + .address = { 0, + 0, + 0, + 0 }, + } } }; + case NETWORK_IP_ADDRESS_FAMILY_IPV6: + return (network_ip_socket_address_t){ .tag = NETWORK_IP_SOCKET_ADDRESS_IPV6, + .val = { + .ipv6 = { + .port = 0, + .address = { 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 }, + .flow_info = + 0, + .scope_id = + 0, + } } }; + default: /* unreachable */ + abort(); + } +} + +int __wasi_sockets_utils__tcp_bind(tcp_socket_t *socket, + network_ip_socket_address_t *address) +{ + tcp_socket_state_unbound_t unbound; + if (socket->state.tag == TCP_SOCKET_STATE_UNBOUND) { + unbound = socket->state.unbound; + } else { + errno = EINVAL; + return -1; + } + + network_error_code_t error; + network_borrow_network_t network_borrow = + __wasi_sockets_utils__borrow_network(); + tcp_borrow_tcp_socket_t socket_borrow = + tcp_borrow_tcp_socket(socket->socket); + + if (!tcp_method_tcp_socket_start_bind(socket_borrow, network_borrow, + address, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + // Bind has successfully started. Attempt to finish it: + while (!tcp_method_tcp_socket_finish_bind(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; + } + } + + // Bind successful. + + socket->state = + (tcp_socket_state_t){ .tag = TCP_SOCKET_STATE_BOUND, + .bound = { /* No additional state */ } }; + return 0; +} + +int __wasi_sockets_utils__udp_bind(udp_socket_t *socket, + network_ip_socket_address_t *address) +{ + udp_socket_state_unbound_t unbound; + if (socket->state.tag == UDP_SOCKET_STATE_UNBOUND) { + unbound = socket->state.unbound; + } else { + errno = EINVAL; + return -1; + } + + network_error_code_t error; + network_borrow_network_t network_borrow = + __wasi_sockets_utils__borrow_network(); + udp_borrow_udp_socket_t socket_borrow = + udp_borrow_udp_socket(socket->socket); + + if (!udp_method_udp_socket_start_bind(socket_borrow, network_borrow, + address, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + // Bind has successfully started. Attempt to finish it: + while (!udp_method_udp_socket_finish_bind(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; + } + } + + // Bind successful. + + socket->state = + (udp_socket_state_t){ .tag = UDP_SOCKET_STATE_BOUND_NOSTREAMS, + .bound_nostreams = {} }; + return 0; +} + +bool __wasi_sockets_utils__create_streams( + udp_borrow_udp_socket_t socket_borrow, + network_ip_socket_address_t *remote_address, + udp_socket_streams_t *result, network_error_code_t *error) +{ + udp_tuple2_own_incoming_datagram_stream_own_outgoing_datagram_stream_t + io; + if (!udp_method_udp_socket_stream(socket_borrow, remote_address, &io, + error)) { + return false; + } + + udp_own_incoming_datagram_stream_t incoming = io.f0; + udp_borrow_incoming_datagram_stream_t incoming_borrow = + udp_borrow_incoming_datagram_stream(incoming); + poll_own_pollable_t incoming_pollable = + udp_method_incoming_datagram_stream_subscribe(incoming_borrow); + + udp_own_outgoing_datagram_stream_t outgoing = io.f1; + udp_borrow_outgoing_datagram_stream_t outgoing_borrow = + udp_borrow_outgoing_datagram_stream(outgoing); + poll_own_pollable_t outgoing_pollable = + udp_method_outgoing_datagram_stream_subscribe(outgoing_borrow); + + *result = (udp_socket_streams_t){ + .incoming = incoming, + .incoming_pollable = incoming_pollable, + .outgoing = outgoing, + .outgoing_pollable = outgoing_pollable, + }; + return true; +} + +void __wasi_sockets_utils__drop_streams(udp_socket_streams_t streams) +{ + poll_pollable_drop_own(streams.incoming_pollable); + poll_pollable_drop_own(streams.outgoing_pollable); + udp_incoming_datagram_stream_drop_own(streams.incoming); + udp_outgoing_datagram_stream_drop_own(streams.outgoing); +} + +bool __wasi_sockets_utils__stream( + udp_socket_t *socket, + network_ip_socket_address_t + *remote_address, // May be null to "disconnect" + udp_socket_streams_t *result, network_error_code_t *error) +{ + // Assert that: + // - We're already bound. This is required by WASI. + // - We have no active streams. From WASI: + // > Implementations may trap if the streams returned by a previous + // > invocation haven't been dropped yet before calling `stream` again. + if (socket->state.tag != UDP_SOCKET_STATE_BOUND_NOSTREAMS) { + abort(); + } + + udp_borrow_udp_socket_t socket_borrow = + udp_borrow_udp_socket(socket->socket); + + if (!__wasi_sockets_utils__create_streams(socket_borrow, remote_address, + result, error)) { + return false; + } + + if (remote_address != NULL) { + socket->state = + (udp_socket_state_t){ .tag = UDP_SOCKET_STATE_CONNECTED, + .connected = { + .streams = *result, + } }; + } else { + socket->state = + (udp_socket_state_t){ .tag = UDP_SOCKET_STATE_BOUND_STREAMING, + .bound_streaming = { + .streams = *result, + } }; + } + + return true; +} diff --git a/libc-top-half/musl/include/sys/socket.h b/libc-top-half/musl/include/sys/socket.h index 4d574c662..668a0d7d3 100644 --- a/libc-top-half/musl/include/sys/socket.h +++ b/libc-top-half/musl/include/sys/socket.h @@ -1,5 +1,8 @@ #ifndef _SYS_SOCKET_H #define _SYS_SOCKET_H + +#include <__wasi_snapshot.h> + #ifdef __wasilibc_unmodified_upstream /* Use alternate WASI libc headers */ #else #include <__header_sys_socket.h> @@ -395,16 +398,21 @@ struct sockaddr_storage { #include <__struct_sockaddr_storage.h> #endif -#ifdef __wasilibc_unmodified_upstream /* WASI has no socket/socketpair */ +#if (defined __wasilibc_unmodified_upstream) || (defined __wasilibc_use_wasip2) int socket (int, int, int); +#endif + +#ifdef __wasilibc_unmodified_upstream /* WASI has no socketpair */ int socketpair (int, int, int, int [2]); #endif int shutdown (int, int); -#ifdef __wasilibc_unmodified_upstream /* WASI has no bind/connect/listen/accept */ -int bind (int, const struct sockaddr *, socklen_t); +#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 @@ -418,7 +426,7 @@ int getpeername (int, struct sockaddr *__restrict, socklen_t *__restrict); ssize_t send (int, const void *, size_t, int); ssize_t recv (int, void *, size_t, int); -#ifdef __wasilibc_unmodified_upstream /* WASI has no sendto/recvfrom */ +#if (defined __wasilibc_unmodified_upstream) || (defined __wasilibc_use_wasip2) ssize_t sendto (int, const void *, size_t, int, const struct sockaddr *, socklen_t); ssize_t recvfrom (int, void *__restrict, size_t, int, struct sockaddr *__restrict, socklen_t *__restrict); #endif diff --git a/test/Makefile b/test/Makefile index ff0799e8e..02b436afb 100644 --- a/test/Makefile +++ b/test/Makefile @@ -32,7 +32,7 @@ ADAPTER_URL ?= https://github.com/bytecodealliance/wasmtime/releases/download/v1 ADAPTER = $(DOWNDIR)/wasi_snapshot_preview1.command.wasm TO_DOWNLOAD = $(LIBC_TEST) $(LIBRT) $(WASMTIME) -ifeq ($(TARGET_TRIPLE), wasm32-wasi-preview2) +ifeq ($(TARGET_TRIPLE), wasm32-wasip2) TO_DOWNLOAD += $(ADAPTER) $(WASM_TOOLS) endif @@ -152,7 +152,7 @@ $(WASMS): | $(OBJDIRS) $(OBJDIR)/%.core.wasm: $(OBJDIR)/%.wasm.o $(INFRA_WASM_OBJS) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ -ifeq ($(TARGET_TRIPLE), wasm32-wasi-preview2) +ifeq ($(TARGET_TRIPLE), wasm32-wasip2) $(OBJDIR)/%.wasm: $(OBJDIR)/%.core.wasm $(WASM_TOOLS) component new --adapt $(ADAPTER) $< -o $@ endif @@ -179,7 +179,7 @@ run: build $(ERRS) $(ERRS): | $(OBJDIRS) -ifeq ($(TARGET_TRIPLE), wasm32-wasi-preview2) +ifeq ($(TARGET_TRIPLE), wasm32-wasip2) %.wasm.err: %.wasm $(ENGINE) --wasm component-model $< >$@ else