summaryrefslogtreecommitdiffstats
path: root/libc-bottom-half/sources/sockets_utils.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-26 16:08:03 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-26 16:08:03 +0000
commitf1db79e6e5c383cf76f3bf0dd42115d19591a72b (patch)
tree3f9509008e8a130c45b7e31b1520d66d720493ec /libc-bottom-half/sources/sockets_utils.c
parentAdding upstream version 0.0~git20230821.ec4566b. (diff)
downloadwasi-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.c462
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;
+}