diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-26 16:08:03 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-26 16:08:03 +0000 |
commit | f1db79e6e5c383cf76f3bf0dd42115d19591a72b (patch) | |
tree | 3f9509008e8a130c45b7e31b1520d66d720493ec /libc-bottom-half/sources/sockets_utils.c | |
parent | Adding upstream version 0.0~git20230821.ec4566b. (diff) | |
download | wasi-libc-upstream.tar.xz wasi-libc-upstream.zip |
Adding upstream version 0.0~git20240411.9e8c542.upstream/0.0_git20240411.9e8c542upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libc-bottom-half/sources/sockets_utils.c')
-rw-r--r-- | libc-bottom-half/sources/sockets_utils.c | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/libc-bottom-half/sources/sockets_utils.c b/libc-bottom-half/sources/sockets_utils.c new file mode 100644 index 0000000..4f5658a --- /dev/null +++ b/libc-bottom-half/sources/sockets_utils.c @@ -0,0 +1,462 @@ +#include <errno.h> + +#include <wasi/sockets_utils.h> + +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; +} |