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/connect.c | |
parent | Adding upstream version 0.0~git20230821.ec4566b. (diff) | |
download | wasi-libc-f1db79e6e5c383cf76f3bf0dd42115d19591a72b.tar.xz wasi-libc-f1db79e6e5c383cf76f3bf0dd42115d19591a72b.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 '')
-rw-r--r-- | libc-bottom-half/sources/connect.c | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/libc-bottom-half/sources/connect.c b/libc-bottom-half/sources/connect.c new file mode 100644 index 0000000..7ef6808 --- /dev/null +++ b/libc-bottom-half/sources/connect.c @@ -0,0 +1,197 @@ +#include <errno.h> +#include <netinet/in.h> + +#include <wasi/descriptor_table.h> +#include <wasi/sockets_utils.h> + +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; + } +} |