mirror of
https://git.proxmox.com/git/wasi-libc
synced 2025-07-26 00:39:53 +00:00

* implement basic TCP/UDP client support This implements `socket`, `connect`, `recv`, `send`, etc. in terms of `wasi-sockets` for the `wasm32-wasip2` target. I've introduced a new public header file: `__wasi_snapshot.h`, which will define a preprocessor symbol `__wasilibc_use_wasip2` if using the `wasm32-wasip2` version of the header, in which case we provide features only available for that target. Co-authored-by: Dave Bakker <github@davebakker.io> Signed-off-by: Joel Dice <joel.dice@fermyon.com> * fix grammar in __wasi_snapshot.h comment Co-authored-by: Dan Gohman <dev@sunfishcode.online> Signed-off-by: Joel Dice <joel.dice@fermyon.com> --------- Signed-off-by: Joel Dice <joel.dice@fermyon.com> Co-authored-by: Dave Bakker <github@davebakker.io> Co-authored-by: Dan Gohman <dev@sunfishcode.online>
198 lines
5.3 KiB
C
198 lines
5.3 KiB
C
#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;
|
|
}
|
|
}
|