diff --git a/Makefile b/Makefile index f7dde2740..b2edb95e8 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,9 @@ LIBC_BOTTOM_HALF_OMIT_SOURCES := \ $(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_SOURCES)/accept-wasip2.c \ + $(LIBC_BOTTOM_HALF_SOURCES)/shutdown.c \ + $(LIBC_BOTTOM_HALF_SOURCES)/sockopt.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 @@ -100,6 +102,8 @@ ifeq ($(WASI_SNAPSHOT), p2) 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/shutdown.c \ + $(LIBC_BOTTOM_HALF_CLOUDLIBC_SRC)/libc/sys/socket/getsockopt.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 1f61dd8d5..772a486b9 100644 --- a/expected/wasm32-wasip2/defined-symbols.txt +++ b/expected/wasm32-wasip2/defined-symbols.txt @@ -1,3 +1,4 @@ +NS_PER_S _CLOCK_MONOTONIC _CLOCK_REALTIME _Exit @@ -1111,6 +1112,7 @@ setenv setkey setlinebuf setlocale +setsockopt setstate setvbuf shutdown @@ -1243,6 +1245,7 @@ tcp_bind tcp_borrow_tcp_socket tcp_create_socket_create_tcp_socket tcp_create_socket_result_own_tcp_socket_error_code_free +tcp_getsockopt tcp_ip_socket_address_free tcp_listen tcp_method_tcp_socket_accept @@ -1282,6 +1285,8 @@ 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_shutdown tcp_tcp_socket_drop_borrow tcp_tcp_socket_drop_own tdelete @@ -1332,6 +1337,7 @@ udp_borrow_outgoing_datagram_stream udp_borrow_udp_socket udp_create_socket_create_udp_socket udp_create_socket_result_own_udp_socket_error_code_free +udp_getsockopt udp_incoming_datagram_free udp_incoming_datagram_stream_drop_borrow udp_incoming_datagram_stream_drop_own @@ -1367,6 +1373,8 @@ udp_result_tuple2_own_incoming_datagram_stream_own_outgoing_datagram_stream_erro udp_result_u64_error_code_free udp_result_u8_error_code_free udp_result_void_error_code_free +udp_setsockopt +udp_shutdown udp_udp_socket_drop_borrow udp_udp_socket_drop_own uname diff --git a/libc-bottom-half/cloudlibc/src/libc/sys/ioctl/ioctl.c b/libc-bottom-half/cloudlibc/src/libc/sys/ioctl/ioctl.c index 7d03cc61a..2c968b2a0 100644 --- a/libc-bottom-half/cloudlibc/src/libc/sys/ioctl/ioctl.c +++ b/libc-bottom-half/cloudlibc/src/libc/sys/ioctl/ioctl.c @@ -4,11 +4,66 @@ #include -#include #include #include +#include +#ifdef __wasilibc_use_wasip2 +#include +#endif + int ioctl(int fildes, int request, ...) { +#ifdef __wasilibc_use_wasip2 + descriptor_table_entry_t *entry; + if (descriptor_table_get_ref(fildes, &entry)) { + switch (entry->tag) { + case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET: { + tcp_socket_t *socket = &entry->tcp_socket; + switch (request) { + case FIONBIO: { + va_list ap; + va_start(ap, request); + socket->blocking = *va_arg(ap, const int *) == + 0; + va_end(ap); + + return 0; + } + + default: + // TODO wasi-sockets: anything else we should support? + errno = EINVAL; + return -1; + } + } + + case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET: { + udp_socket_t *socket = &entry->udp_socket; + switch (request) { + case FIONBIO: { + va_list ap; + va_start(ap, request); + socket->blocking = *va_arg(ap, const int *) == + 0; + va_end(ap); + + return 0; + } + + default: + // TODO wasi-sockets: anything else we should support? + errno = EINVAL; + return -1; + } + } + + default: + errno = ENOPROTOOPT; + return -1; + } + } +#endif // __wasilibc_use_wasip2 + switch (request) { case FIONREAD: { // Poll the file descriptor to determine how many bytes can be read. diff --git a/libc-bottom-half/sources/shutdown.c b/libc-bottom-half/sources/shutdown.c new file mode 100644 index 000000000..3ffd479cb --- /dev/null +++ b/libc-bottom-half/sources/shutdown.c @@ -0,0 +1,80 @@ +#include + +#include + +#include +#include +#include + +int tcp_shutdown(tcp_socket_t *socket, int posix_how) +{ + tcp_shutdown_type_t wasi_how; + switch (posix_how) { + case SHUT_RD: + wasi_how = TCP_SHUTDOWN_TYPE_RECEIVE; + break; + case SHUT_WR: + wasi_how = TCP_SHUTDOWN_TYPE_SEND; + break; + case SHUT_RDWR: + wasi_how = TCP_SHUTDOWN_TYPE_BOTH; + break; + default: + errno = EINVAL; + 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; + } + + network_error_code_t error; + tcp_borrow_tcp_socket_t socket_borrow = + tcp_borrow_tcp_socket(socket->socket); + if (!tcp_method_tcp_socket_shutdown(socket_borrow, wasi_how, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + if (posix_how == SHUT_RD || posix_how == SHUT_RDWR) { + // TODO wasi-sockets: drop input stream (if not already). And + // update `recv` to take dropped input streams into account. + } + + if (posix_how == SHUT_WR || posix_how == SHUT_RDWR) { + // TODO wasi-sockets: drop output stream (if not already). And + // update `send` to take dropped output streams into account. + } + + return 0; +} + +int udp_shutdown(udp_socket_t *socket, int posix_how) +{ + // UDP has nothing to shut down. + errno = EOPNOTSUPP; + return -1; +} + +int shutdown(int socket, int how) +{ + 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_shutdown(&entry->tcp_socket, how); + case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET: + return udp_shutdown(&entry->udp_socket, how); + default: + errno = EOPNOTSUPP; + return -1; + } +} diff --git a/libc-bottom-half/sources/sockopt.c b/libc-bottom-half/sources/sockopt.c new file mode 100644 index 000000000..863d1154e --- /dev/null +++ b/libc-bottom-half/sources/sockopt.c @@ -0,0 +1,683 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +const uint64_t NS_PER_S = 1000000000; + +int tcp_getsockopt(tcp_socket_t *socket, int level, int optname, + void *restrict optval, socklen_t *restrict optlen) +{ + 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 = __wasi_sockets_utils__posix_family( + socket->family); + break; + } + case SO_ERROR: { + if (socket->state.tag == + TCP_SOCKET_STATE_CONNECT_FAILED) { + value = __wasi_sockets_utils__map_error( + socket->state.connect_failed.error_code); + socket->state.connect_failed.error_code = 0; + } else { + value = 0; + } + break; + } + case SO_ACCEPTCONN: { + bool is_listening = socket->state.tag == + TCP_SOCKET_STATE_LISTENING; + if (is_listening != + tcp_method_tcp_socket_is_listening( + socket_borrow)) { // Sanity check. + abort(); + } + value = is_listening; + break; + } + case SO_KEEPALIVE: { + bool result; + if (!tcp_method_tcp_socket_keep_alive_enabled( + socket_borrow, &result, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + value = result; + break; + } + case SO_RCVBUF: { + uint64_t result; + if (!tcp_method_tcp_socket_receive_buffer_size( + socket_borrow, &result, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + 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)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + if (result > INT_MAX) { + abort(); + } + + value = result; + break; + } + case SO_REUSEADDR: { + value = socket->fake_reuseaddr; + break; + } + case SO_RCVTIMEO: // TODO wasi-sockets: emulate in wasi-libc itself + case SO_SNDTIMEO: // TODO wasi-sockets: emulate in wasi-libc itself + 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)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + 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)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + 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)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + 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)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + 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)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + if (result > INT_MAX) { + abort(); + } + + value = result; + break; + 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(tcp_socket_t *socket, int level, int optname, + const void *optval, socklen_t optlen) +{ + 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)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + return 0; + } + case SO_RCVBUF: { + if (!tcp_method_tcp_socket_set_receive_buffer_size( + socket_borrow, intval, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + return 0; + } + case SO_SNDBUF: { + if (!tcp_method_tcp_socket_set_send_buffer_size( + socket_borrow, intval, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + 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: // TODO wasi-sockets: emulate in wasi-libc itself + case SO_SNDTIMEO: // TODO wasi-sockets: emulate in wasi-libc itself + 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)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + 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)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + 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)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + 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)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + return 0; + } + case TCP_KEEPCNT: { + if (!tcp_method_tcp_socket_set_keep_alive_count( + socket_borrow, intval, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + return 0; + } + default: + errno = ENOPROTOOPT; + return -1; + } + break; + + default: + errno = ENOPROTOOPT; + return -1; + } +} + +int udp_getsockopt(udp_socket_t *socket, int level, int optname, + void *restrict optval, socklen_t *restrict optlen) +{ + int value; + + network_error_code_t error; + udp_borrow_udp_socket_t socket_borrow = + udp_borrow_udp_socket(socket->socket); + + switch (level) { + case SOL_SOCKET: + switch (optname) { + case SO_TYPE: { + value = SOCK_DGRAM; + break; + } + case SO_PROTOCOL: { + value = IPPROTO_UDP; + break; + } + case SO_DOMAIN: { + value = __wasi_sockets_utils__posix_family( + socket->family); + break; + } + case SO_RCVBUF: { + uint64_t result; + if (!udp_method_udp_socket_receive_buffer_size( + socket_borrow, &result, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + if (result > INT_MAX) { + abort(); + } + + value = result; + break; + } + case SO_SNDBUF: { + uint64_t result; + if (!udp_method_udp_socket_send_buffer_size( + socket_borrow, &result, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + if (result > INT_MAX) { + abort(); + } + + value = result; + break; + } + case SO_RCVTIMEO: // TODO wasi-sockets: emulate in wasi-libc itself + case SO_SNDTIMEO: // TODO wasi-sockets: emulate in wasi-libc itself + 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 (!udp_method_udp_socket_unicast_hop_limit( + socket_borrow, &result, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + 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 (!udp_method_udp_socket_unicast_hop_limit( + socket_borrow, &result, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + 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 udp_setsockopt(udp_socket_t *socket, int level, int optname, + const void *optval, socklen_t optlen) +{ + int intval = *(int *)optval; + + network_error_code_t error; + udp_borrow_udp_socket_t socket_borrow = + udp_borrow_udp_socket(socket->socket); + + switch (level) { + case SOL_SOCKET: + switch (optname) { + case SO_RCVBUF: { + if (!udp_method_udp_socket_set_receive_buffer_size( + socket_borrow, intval, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + return 0; + } + case SO_SNDBUF: { + if (!udp_method_udp_socket_set_send_buffer_size( + socket_borrow, intval, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + return 0; + } + case SO_RCVTIMEO: // TODO wasi-sockets: emulate in wasi-libc itself + case SO_SNDTIMEO: // TODO wasi-sockets: emulate in wasi-libc itself + 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 (!udp_method_udp_socket_set_unicast_hop_limit( + socket_borrow, intval, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + 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 (!udp_method_udp_socket_set_unicast_hop_limit( + socket_borrow, intval, &error)) { + errno = __wasi_sockets_utils__map_error(error); + return -1; + } + + return 0; + } + default: + errno = ENOPROTOOPT; + return -1; + } + break; + + default: + errno = ENOPROTOOPT; + return -1; + } +} + +int getsockopt(int sockfd, int level, int optname, void *restrict optval, + socklen_t *restrict optlen) +{ + descriptor_table_entry_t *entry; + if (!descriptor_table_get_ref(sockfd, &entry)) { + errno = EBADF; + return -1; + } + + if (optval == NULL || optlen == NULL || *optlen < sizeof(int)) { + // FYI, the protocol-specific implementations implicitly depend on these checks. + errno = EINVAL; + return -1; + } + + switch (entry->tag) { + case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET: + return tcp_getsockopt(&entry->tcp_socket, level, optname, + optval, optlen); + case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET: + return udp_getsockopt(&entry->udp_socket, level, optname, + optval, optlen); + default: + errno = ENOPROTOOPT; + return -1; + } +} + +int setsockopt(int sockfd, int level, int optname, const void *optval, + socklen_t optlen) +{ + descriptor_table_entry_t *entry; + if (!descriptor_table_get_ref(sockfd, &entry)) { + errno = EBADF; + return -1; + } + + if (optval == NULL || optlen < sizeof(int)) { + // FYI, the protocol-specific implementations implicitly depend on these checks. + errno = EINVAL; + return -1; + } + + switch (entry->tag) { + case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET: + return tcp_setsockopt(&entry->tcp_socket, level, optname, + optval, optlen); + case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET: + return udp_setsockopt(&entry->udp_socket, level, optname, + optval, optlen); + default: + errno = ENOPROTOOPT; + return -1; + } +}