diff --git a/expected/wasm32-wasip2/defined-symbols.txt b/expected/wasm32-wasip2/defined-symbols.txt index bd947b2cc..9922dfb78 100644 --- a/expected/wasm32-wasip2/defined-symbols.txt +++ b/expected/wasm32-wasip2/defined-symbols.txt @@ -1,4 +1,3 @@ -NS_PER_S _CLOCK_MONOTONIC _CLOCK_REALTIME _Exit @@ -343,6 +342,8 @@ __wasilibc_rmdirat __wasilibc_sockaddr_to_wasi __wasilibc_sockaddr_validate __wasilibc_stat +__wasilibc_tcp_getsockopt +__wasilibc_tcp_setsockopt __wasilibc_tell __wasilibc_unlinkat __wasilibc_unspecified_addr @@ -1384,7 +1385,6 @@ tcp_result_u32_error_code_free tcp_result_u64_error_code_free tcp_result_u8_error_code_free tcp_result_void_error_code_free -tcp_setsockopt tcp_tcp_socket_drop_own tdelete tdestroy diff --git a/expected/wasm32-wasip3/defined-symbols.txt b/expected/wasm32-wasip3/defined-symbols.txt index 02a9aafa9..34149271a 100644 --- a/expected/wasm32-wasip3/defined-symbols.txt +++ b/expected/wasm32-wasip3/defined-symbols.txt @@ -339,6 +339,8 @@ __wasilibc_rmdirat __wasilibc_sockaddr_to_wasi __wasilibc_sockaddr_validate __wasilibc_stat +__wasilibc_tcp_getsockopt +__wasilibc_tcp_setsockopt __wasilibc_tell __wasilibc_unlinkat __wasilibc_unspecified_addr diff --git a/libc-bottom-half/CMakeLists.txt b/libc-bottom-half/CMakeLists.txt index c7c8013fe..497f207a4 100644 --- a/libc-bottom-half/CMakeLists.txt +++ b/libc-bottom-half/CMakeLists.txt @@ -145,6 +145,7 @@ else() sources/socket.c sources/sockets_utils.c sources/sockopt.c + sources/tcp_sockopt.c ) endif() diff --git a/libc-bottom-half/headers/private/wasi/tcp.h b/libc-bottom-half/headers/private/wasi/tcp.h index daf443dd1..297ce8917 100644 --- a/libc-bottom-half/headers/private/wasi/tcp.h +++ b/libc-bottom-half/headers/private/wasi/tcp.h @@ -6,6 +6,86 @@ #ifndef __wasip1__ #include +#include + +// Normalize names on WASIp2 to the WASIp3-based names +#ifdef __wasip2__ +typedef network_error_code_t sockets_error_code_t; +typedef tcp_own_tcp_socket_t sockets_own_tcp_socket_t; +typedef tcp_borrow_tcp_socket_t sockets_borrow_tcp_socket_t; +typedef network_ip_address_family_t sockets_ip_address_family_t; +#define sockets_borrow_tcp_socket tcp_borrow_tcp_socket +#endif // __wasip2__ + +typedef struct { + int dummy; +} tcp_socket_state_unbound_t; +typedef struct { + int dummy; +} tcp_socket_state_bound_t; +typedef struct { + int dummy; +} tcp_socket_state_connecting_t; +typedef struct { + int dummy; +} tcp_socket_state_listening_t; + +// Pollables here are lazily initialized on-demand. +typedef struct { +#ifdef __wasip2__ + streams_own_input_stream_t input; + poll_own_pollable_t input_pollable; + streams_own_output_stream_t output; + poll_own_pollable_t output_pollable; +#else + int dummy; // TODO(wasip3) +#endif +} tcp_socket_state_connected_t; + +typedef struct { + sockets_error_code_t error_code; +} tcp_socket_state_connect_failed_t; + +// This is a tagged union. When adding/removing/renaming cases, be sure to keep +// the tag and union definitions in sync. +typedef struct { + enum { + TCP_SOCKET_STATE_UNBOUND, + TCP_SOCKET_STATE_BOUND, + TCP_SOCKET_STATE_CONNECTING, + TCP_SOCKET_STATE_CONNECTED, + TCP_SOCKET_STATE_CONNECT_FAILED, + TCP_SOCKET_STATE_LISTENING, + } tag; + union { + tcp_socket_state_unbound_t unbound; + tcp_socket_state_bound_t bound; + tcp_socket_state_connecting_t connecting; + tcp_socket_state_connected_t connected; + tcp_socket_state_connect_failed_t connect_failed; + tcp_socket_state_listening_t listening; + }; +} tcp_socket_state_t; + +typedef struct { + sockets_own_tcp_socket_t socket; + tcp_socket_state_t state; +#ifdef __wasip2__ + // This pollable is lazily initialized on-demand. + poll_own_pollable_t socket_pollable; +#endif + bool blocking; + bool fake_nodelay; + bool fake_reuseaddr; + sockets_ip_address_family_t family; + monotonic_clock_duration_t send_timeout; + monotonic_clock_duration_t recv_timeout; +} tcp_socket_t; + +int __wasilibc_tcp_getsockopt(void *data, int level, int optname, + void *restrict optval, socklen_t *restrict optlen); +int __wasilibc_tcp_setsockopt(void *data, int level, int optname, + const void *optval, socklen_t optlen); /// Adds the provided TCP socket to the descriptor table, returning the /// corresponding file descriptor. diff --git a/libc-bottom-half/sources/tcp_sockopt.c b/libc-bottom-half/sources/tcp_sockopt.c new file mode 100644 index 000000000..d4f3d6a3a --- /dev/null +++ b/libc-bottom-half/sources/tcp_sockopt.c @@ -0,0 +1,421 @@ +#include + +#ifndef __wasip1__ + +#include +#include +#include +#include +#include +#include +#include + +// Normalize names on WASIp2 to the WASIp3-based names +#ifdef __wasip2__ +#define sockets_method_tcp_socket_get_is_listening \ + tcp_method_tcp_socket_is_listening +#define sockets_method_tcp_socket_get_keep_alive_enabled \ + tcp_method_tcp_socket_keep_alive_enabled +#define sockets_method_tcp_socket_get_receive_buffer_size \ + tcp_method_tcp_socket_receive_buffer_size +#define sockets_method_tcp_socket_get_send_buffer_size \ + tcp_method_tcp_socket_send_buffer_size +#define sockets_method_tcp_socket_set_keep_alive_enabled \ + tcp_method_tcp_socket_set_keep_alive_enabled +#define sockets_method_tcp_socket_set_receive_buffer_size \ + tcp_method_tcp_socket_set_receive_buffer_size +#define sockets_method_tcp_socket_set_send_buffer_size \ + tcp_method_tcp_socket_set_send_buffer_size +#define sockets_method_tcp_socket_get_hop_limit tcp_method_tcp_socket_hop_limit +#define sockets_method_tcp_socket_set_hop_limit \ + tcp_method_tcp_socket_set_hop_limit +#define sockets_method_tcp_socket_get_keep_alive_idle_time \ + tcp_method_tcp_socket_keep_alive_idle_time +#define sockets_method_tcp_socket_get_keep_alive_interval \ + tcp_method_tcp_socket_keep_alive_interval +#define sockets_method_tcp_socket_get_keep_alive_count \ + tcp_method_tcp_socket_keep_alive_count +#define sockets_method_tcp_socket_set_keep_alive_idle_time \ + tcp_method_tcp_socket_set_keep_alive_idle_time +#define sockets_method_tcp_socket_set_keep_alive_interval \ + tcp_method_tcp_socket_set_keep_alive_interval +#define sockets_method_tcp_socket_set_keep_alive_count \ + tcp_method_tcp_socket_set_keep_alive_count + +#define SOCKETS_IP_ADDRESS_FAMILY_IPV4 NETWORK_IP_ADDRESS_FAMILY_IPV4 +#define SOCKETS_IP_ADDRESS_FAMILY_IPV6 NETWORK_IP_ADDRESS_FAMILY_IPV6 +typedef tcp_duration_t sockets_duration_t; +#endif // __wasip2__ + +static const uint64_t NS_PER_S = 1000000000; + +int __wasilibc_tcp_getsockopt(void *data, int level, int optname, + void *restrict optval, + socklen_t *restrict optlen) { + tcp_socket_t *socket = (tcp_socket_t *)data; + int value = 0; + + sockets_error_code_t error; + sockets_borrow_tcp_socket_t socket_borrow = + sockets_borrow_tcp_socket(socket->socket); + + switch (level) { + case SOL_SOCKET: + switch (optname) { + case SO_TYPE: + value = SOCK_STREAM; + break; + case SO_PROTOCOL: + value = IPPROTO_TCP; + break; + + case SO_DOMAIN: + value = __wasilibc_wasi_family_to_libc(socket->family); + break; + + case SO_ERROR: + if (socket->state.tag == TCP_SOCKET_STATE_CONNECT_FAILED) { + value = __wasilibc_map_socket_error( + socket->state.connect_failed.error_code); + socket->state.connect_failed.error_code = 0; + } else if (socket->state.tag == TCP_SOCKET_STATE_CONNECTING) { + value = EINPROGRESS; + } else { + value = 0; + } + break; + + case SO_ACCEPTCONN: { + bool is_listening = socket->state.tag == TCP_SOCKET_STATE_LISTENING; + // Sanity check. + if (is_listening != + sockets_method_tcp_socket_get_is_listening(socket_borrow)) { + abort(); + } + value = is_listening; + break; + } + case SO_KEEPALIVE: { + bool result; + if (!sockets_method_tcp_socket_get_keep_alive_enabled(socket_borrow, + &result, &error)) + return __wasilibc_socket_error_to_errno(error); + value = result; + break; + } + case SO_RCVBUF: { + uint64_t result; + if (!sockets_method_tcp_socket_get_receive_buffer_size(socket_borrow, + &result, &error)) + return __wasilibc_socket_error_to_errno(error); + + value = result > INT_MAX ? INT_MAX : result; + break; + } + case SO_SNDBUF: { + uint64_t result; + if (!sockets_method_tcp_socket_get_send_buffer_size(socket_borrow, + &result, &error)) + return __wasilibc_socket_error_to_errno(error); + + value = result > INT_MAX ? INT_MAX : result; + break; + } + case SO_REUSEADDR: + value = socket->fake_reuseaddr; + break; + case SO_RCVTIMEO: { + struct timeval tv = duration_to_timeval(socket->recv_timeout); + memcpy(optval, &tv, + *optlen < sizeof(struct timeval) ? *optlen + : sizeof(struct timeval)); + *optlen = sizeof(struct timeval); + return 0; + } + case SO_SNDTIMEO: { + struct timeval tv = duration_to_timeval(socket->send_timeout); + memcpy(optval, &tv, + *optlen < sizeof(struct timeval) ? *optlen + : sizeof(struct timeval)); + *optlen = sizeof(struct timeval); + return 0; + } + default: + errno = ENOPROTOOPT; + return -1; + } + break; + + case SOL_IP: + switch (optname) { + case IP_TTL: { + if (socket->family != SOCKETS_IP_ADDRESS_FAMILY_IPV4) { + errno = EAFNOSUPPORT; + return -1; + } + + uint8_t result; + if (!sockets_method_tcp_socket_get_hop_limit(socket_borrow, &result, + &error)) + return __wasilibc_socket_error_to_errno(error); + + value = result; + break; + } + default: + errno = ENOPROTOOPT; + return -1; + } + break; + + case SOL_IPV6: + switch (optname) { + case IPV6_UNICAST_HOPS: { + if (socket->family != SOCKETS_IP_ADDRESS_FAMILY_IPV6) { + errno = EAFNOSUPPORT; + return -1; + } + + uint8_t result; + if (!sockets_method_tcp_socket_get_hop_limit(socket_borrow, &result, + &error)) + return __wasilibc_socket_error_to_errno(error); + + value = result; + break; + } + default: + errno = ENOPROTOOPT; + return -1; + } + break; + + case SOL_TCP: + switch (optname) { + case TCP_NODELAY: { + value = socket->fake_nodelay; + break; + } + case TCP_KEEPIDLE: { + sockets_duration_t result_ns; + if (!sockets_method_tcp_socket_get_keep_alive_idle_time( + socket_borrow, &result_ns, &error)) + return __wasilibc_socket_error_to_errno(error); + + uint64_t result_s = result_ns / NS_PER_S; + if (result_s == 0) { + result_s = 1; // Value was rounded down to zero. Round it up instead, + // because 0 is an invalid value for this socket option. + } + + value = result_s > INT_MAX ? INT_MAX : result_s; + break; + } + case TCP_KEEPINTVL: { + sockets_duration_t result_ns; + if (!sockets_method_tcp_socket_get_keep_alive_interval( + socket_borrow, &result_ns, &error)) + return __wasilibc_socket_error_to_errno(error); + + uint64_t result_s = result_ns / NS_PER_S; + if (result_s == 0) { + result_s = 1; // Value was rounded down to zero. Round it up instead, + // because 0 is an invalid value for this socket option. + } + + value = result_s > INT_MAX ? INT_MAX : result_s; + break; + } + case TCP_KEEPCNT: { + uint32_t result; + if (!sockets_method_tcp_socket_get_keep_alive_count(socket_borrow, + &result, &error)) + return __wasilibc_socket_error_to_errno(error); + + value = result > INT_MAX ? INT_MAX : result; + break; + } + default: + errno = ENOPROTOOPT; + return -1; + } + break; + + default: + errno = ENOPROTOOPT; + return -1; + } + + // Copy out integer value. + memcpy(optval, &value, *optlen < sizeof(int) ? *optlen : sizeof(int)); + *optlen = sizeof(int); + return 0; +} + +int __wasilibc_tcp_setsockopt(void *data, int level, int optname, + const void *optval, socklen_t optlen) { + tcp_socket_t *socket = (tcp_socket_t *)data; + int intval = *(int *)optval; + + sockets_error_code_t error; + sockets_borrow_tcp_socket_t socket_borrow = + sockets_borrow_tcp_socket(socket->socket); + + switch (level) { + case SOL_SOCKET: + switch (optname) { + case SO_KEEPALIVE: { + if (!sockets_method_tcp_socket_set_keep_alive_enabled( + socket_borrow, intval != 0, &error)) + return __wasilibc_socket_error_to_errno(error); + + return 0; + } + case SO_RCVBUF: { + if (!sockets_method_tcp_socket_set_receive_buffer_size(socket_borrow, + intval, &error)) + return __wasilibc_socket_error_to_errno(error); + + return 0; + } + case SO_SNDBUF: { + if (!sockets_method_tcp_socket_set_send_buffer_size(socket_borrow, intval, + &error)) + return __wasilibc_socket_error_to_errno(error); + + return 0; + } + case SO_REUSEADDR: { + // As of this writing, WASI has no support for changing SO_REUSEADDR + // -- it's enabled by default and cannot be disabled. To keep + // applications happy, we pretend to support enabling and disabling + // it. + socket->fake_reuseaddr = (intval != 0); + return 0; + } + case SO_RCVTIMEO: { + struct timeval *tv = (struct timeval *)optval; + if (!timeval_to_duration(tv, &socket->recv_timeout)) { + errno = EINVAL; + return -1; + } + return 0; + } + case SO_SNDTIMEO: { + struct timeval *tv = (struct timeval *)optval; + if (!timeval_to_duration(tv, &socket->send_timeout)) { + errno = EINVAL; + return -1; + } + return 0; + } + default: + errno = ENOPROTOOPT; + return -1; + } + break; + + case SOL_IP: + switch (optname) { + case IP_TTL: { + if (socket->family != SOCKETS_IP_ADDRESS_FAMILY_IPV4) { + errno = EAFNOSUPPORT; + return -1; + } + + if (intval < 0 || intval > UINT8_MAX) { + errno = EINVAL; + return -1; + } + + if (!sockets_method_tcp_socket_set_hop_limit(socket_borrow, intval, + &error)) + return __wasilibc_socket_error_to_errno(error); + + return 0; + } + default: + errno = ENOPROTOOPT; + return -1; + } + break; + + case SOL_IPV6: + switch (optname) { + case IPV6_UNICAST_HOPS: { + if (socket->family != SOCKETS_IP_ADDRESS_FAMILY_IPV6) { + errno = EAFNOSUPPORT; + return -1; + } + + if (intval < 0 || intval > UINT8_MAX) { + errno = EINVAL; + return -1; + } + + if (!sockets_method_tcp_socket_set_hop_limit(socket_borrow, intval, + &error)) + return __wasilibc_socket_error_to_errno(error); + + return 0; + } + default: + errno = ENOPROTOOPT; + return -1; + } + break; + + case SOL_TCP: + switch (optname) { + case TCP_NODELAY: { + // At the time of writing, WASI has no support for TCP_NODELAY. + // Yet, many applications expect this option to be implemented. + // To ensure those applications can run on WASI at all, we fake + // support for it by recording the value, but not doing anything + // with it. + // If/when WASI adds true support, we can remove this workaround + // and implement it properly. From the application's perspective + // the "worst" thing that can then happen is that it automagically + // becomes faster. + socket->fake_nodelay = (intval != 0); + return 0; + } + case TCP_KEEPIDLE: { + sockets_duration_t duration = intval * NS_PER_S; + if (!sockets_method_tcp_socket_set_keep_alive_idle_time(socket_borrow, + duration, &error)) + return __wasilibc_socket_error_to_errno(error); + + return 0; + } + case TCP_KEEPINTVL: { + sockets_duration_t duration = intval * NS_PER_S; + if (!sockets_method_tcp_socket_set_keep_alive_interval(socket_borrow, + duration, &error)) + return __wasilibc_socket_error_to_errno(error); + + return 0; + } + case TCP_KEEPCNT: { + if (!sockets_method_tcp_socket_set_keep_alive_count(socket_borrow, intval, + &error)) + return __wasilibc_socket_error_to_errno(error); + + return 0; + } + default: + errno = ENOPROTOOPT; + return -1; + } + break; + + default: + errno = ENOPROTOOPT; + return -1; + } + + // should not be reachable, all cases above should return + abort(); +} + +#endif // not(__wasip1__) diff --git a/libc-bottom-half/sources/wasip2_tcp.c b/libc-bottom-half/sources/wasip2_tcp.c index f1a66c17a..becf511fb 100644 --- a/libc-bottom-half/sources/wasip2_tcp.c +++ b/libc-bottom-half/sources/wasip2_tcp.c @@ -12,67 +12,6 @@ #ifdef __wasip2__ -const uint64_t NS_PER_S = 1000000000; - -typedef struct { - int dummy; -} tcp_socket_state_unbound_t; -typedef struct { - int dummy; -} tcp_socket_state_bound_t; -typedef struct { - int dummy; -} tcp_socket_state_connecting_t; -typedef struct { - int dummy; -} tcp_socket_state_listening_t; - -// Pollables here are lazily initialized on-demand. -typedef struct { - streams_own_input_stream_t input; - poll_own_pollable_t input_pollable; - streams_own_output_stream_t output; - poll_own_pollable_t output_pollable; -} tcp_socket_state_connected_t; - -typedef struct { - network_error_code_t error_code; -} tcp_socket_state_connect_failed_t; - -// This is a tagged union. When adding/removing/renaming cases, be sure to keep -// the tag and union definitions in sync. -typedef struct { - enum { - TCP_SOCKET_STATE_UNBOUND, - TCP_SOCKET_STATE_BOUND, - TCP_SOCKET_STATE_CONNECTING, - TCP_SOCKET_STATE_CONNECTED, - TCP_SOCKET_STATE_CONNECT_FAILED, - TCP_SOCKET_STATE_LISTENING, - } tag; - union { - tcp_socket_state_unbound_t unbound; - tcp_socket_state_bound_t bound; - tcp_socket_state_connecting_t connecting; - tcp_socket_state_connected_t connected; - tcp_socket_state_connect_failed_t connect_failed; - tcp_socket_state_listening_t listening; - }; -} tcp_socket_state_t; - -typedef struct { - tcp_own_tcp_socket_t socket; - // This pollable is lazily initialized on-demand. - poll_own_pollable_t socket_pollable; - bool blocking; - bool fake_nodelay; - bool fake_reuseaddr; - network_ip_address_family_t family; - tcp_socket_state_t state; - monotonic_clock_duration_t send_timeout; - monotonic_clock_duration_t recv_timeout; -} tcp_socket_t; - static descriptor_vtable_t tcp_vtable; static int tcp_add(tcp_own_tcp_socket_t socket, @@ -725,400 +664,6 @@ static int tcp_shutdown(void *data, int posix_how) { return 0; } -static int tcp_getsockopt(void *data, int level, int optname, - void *restrict optval, socklen_t *restrict optlen) { - tcp_socket_t *socket = (tcp_socket_t *)data; - int value = 0; - - network_error_code_t error; - tcp_borrow_tcp_socket_t socket_borrow = tcp_borrow_tcp_socket(socket->socket); - - switch (level) { - case SOL_SOCKET: - switch (optname) { - case SO_TYPE: - value = SOCK_STREAM; - break; - case SO_PROTOCOL: - value = IPPROTO_TCP; - break; - - case SO_DOMAIN: - value = __wasilibc_wasi_family_to_libc(socket->family); - break; - - case SO_ERROR: - if (socket->state.tag == TCP_SOCKET_STATE_CONNECT_FAILED) { - value = __wasilibc_map_socket_error( - socket->state.connect_failed.error_code); - socket->state.connect_failed.error_code = 0; - } else if (socket->state.tag == TCP_SOCKET_STATE_CONNECTING) { - value = EINPROGRESS; - } else { - value = 0; - } - break; - - case SO_ACCEPTCONN: { - bool is_listening = socket->state.tag == TCP_SOCKET_STATE_LISTENING; - // Sanity check. - if (is_listening != tcp_method_tcp_socket_is_listening(socket_borrow)) { - abort(); - } - value = is_listening; - break; - } - case SO_KEEPALIVE: { - bool result; - if (!tcp_method_tcp_socket_keep_alive_enabled(socket_borrow, &result, - &error)) { - return __wasilibc_socket_error_to_errno(error); - } - value = result; - break; - } - case SO_RCVBUF: { - uint64_t result; - if (!tcp_method_tcp_socket_receive_buffer_size(socket_borrow, &result, - &error)) { - return __wasilibc_socket_error_to_errno(error); - } - - if (result > INT_MAX) { - abort(); - } - - value = result; - break; - } - case SO_SNDBUF: { - uint64_t result; - if (!tcp_method_tcp_socket_send_buffer_size(socket_borrow, &result, - &error)) { - return __wasilibc_socket_error_to_errno(error); - } - - if (result > INT_MAX) { - abort(); - } - - value = result; - break; - } - case SO_REUSEADDR: - value = socket->fake_reuseaddr; - break; - case SO_RCVTIMEO: { - struct timeval tv = duration_to_timeval(socket->recv_timeout); - memcpy(optval, &tv, - *optlen < sizeof(struct timeval) ? *optlen - : sizeof(struct timeval)); - *optlen = sizeof(struct timeval); - return 0; - } - case SO_SNDTIMEO: { - struct timeval tv = duration_to_timeval(socket->send_timeout); - memcpy(optval, &tv, - *optlen < sizeof(struct timeval) ? *optlen - : sizeof(struct timeval)); - *optlen = sizeof(struct timeval); - return 0; - } - default: - errno = ENOPROTOOPT; - return -1; - } - break; - - case SOL_IP: - switch (optname) { - case IP_TTL: { - if (socket->family != NETWORK_IP_ADDRESS_FAMILY_IPV4) { - errno = EAFNOSUPPORT; - return -1; - } - - uint8_t result; - if (!tcp_method_tcp_socket_hop_limit(socket_borrow, &result, &error)) { - return __wasilibc_socket_error_to_errno(error); - } - - value = result; - break; - } - default: - errno = ENOPROTOOPT; - return -1; - } - break; - - case SOL_IPV6: - switch (optname) { - case IPV6_UNICAST_HOPS: { - if (socket->family != NETWORK_IP_ADDRESS_FAMILY_IPV6) { - errno = EAFNOSUPPORT; - return -1; - } - - uint8_t result; - if (!tcp_method_tcp_socket_hop_limit(socket_borrow, &result, &error)) { - return __wasilibc_socket_error_to_errno(error); - } - - value = result; - break; - } - default: - errno = ENOPROTOOPT; - return -1; - } - break; - - case SOL_TCP: - switch (optname) { - case TCP_NODELAY: { - value = socket->fake_nodelay; - break; - } - case TCP_KEEPIDLE: { - tcp_duration_t result_ns; - if (!tcp_method_tcp_socket_keep_alive_idle_time(socket_borrow, &result_ns, - &error)) { - return __wasilibc_socket_error_to_errno(error); - } - - uint64_t result_s = result_ns / NS_PER_S; - if (result_s == 0) { - result_s = 1; // Value was rounded down to zero. Round it up instead, - // because 0 is an invalid value for this socket option. - } - - if (result_s > INT_MAX) { - abort(); - } - - value = result_s; - break; - } - case TCP_KEEPINTVL: { - tcp_duration_t result_ns; - if (!tcp_method_tcp_socket_keep_alive_interval(socket_borrow, &result_ns, - &error)) { - return __wasilibc_socket_error_to_errno(error); - } - - uint64_t result_s = result_ns / NS_PER_S; - if (result_s == 0) { - result_s = 1; // Value was rounded down to zero. Round it up instead, - // because 0 is an invalid value for this socket option. - } - - if (result_s > INT_MAX) { - abort(); - } - - value = result_s; - break; - } - case TCP_KEEPCNT: { - uint32_t result; - if (!tcp_method_tcp_socket_keep_alive_count(socket_borrow, &result, - &error)) { - return __wasilibc_socket_error_to_errno(error); - } - - if (result > INT_MAX) { - abort(); - } - - value = result; - break; - } - default: - errno = ENOPROTOOPT; - return -1; - } - break; - - default: - errno = ENOPROTOOPT; - return -1; - } - - // Copy out integer value. - memcpy(optval, &value, *optlen < sizeof(int) ? *optlen : sizeof(int)); - *optlen = sizeof(int); - return 0; -} - -int tcp_setsockopt(void *data, int level, int optname, const void *optval, - socklen_t optlen) { - tcp_socket_t *socket = (tcp_socket_t *)data; - int intval = *(int *)optval; - - network_error_code_t error; - tcp_borrow_tcp_socket_t socket_borrow = tcp_borrow_tcp_socket(socket->socket); - - switch (level) { - case SOL_SOCKET: - switch (optname) { - case SO_KEEPALIVE: { - if (!tcp_method_tcp_socket_set_keep_alive_enabled(socket_borrow, - intval != 0, &error)) { - return __wasilibc_socket_error_to_errno(error); - } - - return 0; - } - case SO_RCVBUF: { - if (!tcp_method_tcp_socket_set_receive_buffer_size(socket_borrow, intval, - &error)) { - return __wasilibc_socket_error_to_errno(error); - } - - return 0; - } - case SO_SNDBUF: { - if (!tcp_method_tcp_socket_set_send_buffer_size(socket_borrow, intval, - &error)) { - return __wasilibc_socket_error_to_errno(error); - } - - return 0; - } - case SO_REUSEADDR: { - // As of this writing, WASI has no support for changing SO_REUSEADDR - // -- it's enabled by default and cannot be disabled. To keep - // applications happy, we pretend to support enabling and disabling - // it. - socket->fake_reuseaddr = (intval != 0); - return 0; - } - case SO_RCVTIMEO: { - struct timeval *tv = (struct timeval *)optval; - if (!timeval_to_duration(tv, &socket->recv_timeout)) { - errno = EINVAL; - return -1; - } - return 0; - } - case SO_SNDTIMEO: { - struct timeval *tv = (struct timeval *)optval; - if (!timeval_to_duration(tv, &socket->send_timeout)) { - errno = EINVAL; - return -1; - } - return 0; - } - default: - errno = ENOPROTOOPT; - return -1; - } - break; - - case SOL_IP: - switch (optname) { - case IP_TTL: { - if (socket->family != NETWORK_IP_ADDRESS_FAMILY_IPV4) { - errno = EAFNOSUPPORT; - return -1; - } - - if (intval < 0 || intval > UINT8_MAX) { - errno = EINVAL; - return -1; - } - - if (!tcp_method_tcp_socket_set_hop_limit(socket_borrow, intval, &error)) { - return __wasilibc_socket_error_to_errno(error); - } - - return 0; - } - default: - errno = ENOPROTOOPT; - return -1; - } - break; - - case SOL_IPV6: - switch (optname) { - case IPV6_UNICAST_HOPS: { - if (socket->family != NETWORK_IP_ADDRESS_FAMILY_IPV6) { - errno = EAFNOSUPPORT; - return -1; - } - - if (intval < 0 || intval > UINT8_MAX) { - errno = EINVAL; - return -1; - } - - if (!tcp_method_tcp_socket_set_hop_limit(socket_borrow, intval, &error)) { - return __wasilibc_socket_error_to_errno(error); - } - - return 0; - } - default: - errno = ENOPROTOOPT; - return -1; - } - break; - - case SOL_TCP: - switch (optname) { - case TCP_NODELAY: { - // At the time of writing, WASI has no support for TCP_NODELAY. - // Yet, many applications expect this option to be implemented. - // To ensure those applications can run on WASI at all, we fake - // support for it by recording the value, but not doing anything - // with it. - // If/when WASI adds true support, we can remove this workaround - // and implement it properly. From the application's perspective - // the "worst" thing that can then happen is that it automagically - // becomes faster. - socket->fake_nodelay = (intval != 0); - return 0; - } - case TCP_KEEPIDLE: { - tcp_duration_t duration = intval * NS_PER_S; - if (!tcp_method_tcp_socket_set_keep_alive_idle_time(socket_borrow, - duration, &error)) { - return __wasilibc_socket_error_to_errno(error); - } - - return 0; - } - case TCP_KEEPINTVL: { - tcp_duration_t duration = intval * NS_PER_S; - if (!tcp_method_tcp_socket_set_keep_alive_interval(socket_borrow, - duration, &error)) { - return __wasilibc_socket_error_to_errno(error); - } - - return 0; - } - case TCP_KEEPCNT: { - if (!tcp_method_tcp_socket_set_keep_alive_count(socket_borrow, intval, - &error)) { - return __wasilibc_socket_error_to_errno(error); - } - - return 0; - } - default: - errno = ENOPROTOOPT; - return -1; - } - break; - - default: - errno = ENOPROTOOPT; - return -1; - } -} - static int tcp_poll_register(void *data, poll_state_t *state, short events) { tcp_socket_t *socket = (tcp_socket_t *)data; switch (socket->state.tag) { @@ -1236,8 +781,8 @@ static descriptor_vtable_t tcp_vtable = { .recvfrom = tcp_recvfrom, .sendto = tcp_sendto, .shutdown = tcp_shutdown, - .getsockopt = tcp_getsockopt, - .setsockopt = tcp_setsockopt, + .getsockopt = __wasilibc_tcp_getsockopt, + .setsockopt = __wasilibc_tcp_setsockopt, .poll_register = tcp_poll_register, .poll_finish = tcp_poll_finish, diff --git a/libc-bottom-half/sources/wasip3_tcp.c b/libc-bottom-half/sources/wasip3_tcp.c index 8861cbbbf..5dda276dd 100644 --- a/libc-bottom-half/sources/wasip3_tcp.c +++ b/libc-bottom-half/sources/wasip3_tcp.c @@ -1,13 +1,52 @@ -#include #include #ifdef __wasip3__ +#include +#include +#include +#include + +static void tcp_free(void *data) { + tcp_socket_t *tcp = (tcp_socket_t *)data; + + sockets_tcp_socket_drop_own(tcp->socket); + + free(tcp); +} + +static descriptor_vtable_t tcp_vtable = { + .free = tcp_free, + .getsockopt = __wasilibc_tcp_getsockopt, + .setsockopt = __wasilibc_tcp_setsockopt, +}; + +static int tcp_add(sockets_own_tcp_socket_t socket, + sockets_ip_address_family_t family, bool blocking, + tcp_socket_t **out) { + tcp_socket_t *tcp = calloc(1, sizeof(tcp_socket_t)); + if (!tcp) { + sockets_tcp_socket_drop_own(socket); + errno = ENOMEM; + return -1; + } + tcp->state.tag = TCP_SOCKET_STATE_UNBOUND; + tcp->socket = socket; + tcp->family = family; + tcp->blocking = blocking; + + descriptor_table_entry_t entry; + entry.vtable = &tcp_vtable; + entry.data = tcp; + if (out) + *out = tcp; + return descriptor_table_insert(entry); +} + int __wasilibc_add_tcp_socket(sockets_own_tcp_socket_t socket, sockets_ip_address_family_t family, bool blocking) { - // TODO(wasip3) - errno = ENOTSUP; - return -1; + return tcp_add(socket, family, blocking, NULL); } + #endif // __wasip3__ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 39bc3ec83..bb51b2c05 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -313,7 +313,7 @@ endif() if (NOT (WASI STREQUAL "p1")) add_wasilibc_test(poll-connect.c NETWORK FAILP3) add_wasilibc_test(poll-nonblocking-socket.c NETWORK FAILP3) - add_wasilibc_test(setsockopt.c NETWORK FAILP3) + add_wasilibc_test(setsockopt.c NETWORK) add_wasilibc_test(sockets-nonblocking-udp.c NETWORK FAILP3) add_wasilibc_test(sockets-nonblocking-multiple.c NETWORK FAILP3) add_wasilibc_test(sockets-nonblocking-udp-multiple.c NETWORK FAILP3)