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>
250 lines
5.9 KiB
C
250 lines
5.9 KiB
C
#include <sys/socket.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <wasi/api.h>
|
|
#include <wasi/descriptor_table.h>
|
|
#include <wasi/sockets_utils.h>
|
|
|
|
static ssize_t tcp_sendto(tcp_socket_t *socket, const uint8_t *buffer,
|
|
size_t length, int flags, const struct sockaddr *addr,
|
|
socklen_t addrlen)
|
|
{
|
|
const int supported_flags = MSG_DONTWAIT | MSG_NOSIGNAL;
|
|
if ((flags & supported_flags) != flags) {
|
|
errno = EOPNOTSUPP;
|
|
return -1;
|
|
}
|
|
|
|
if (addr != NULL || addrlen != 0) {
|
|
errno = EISCONN;
|
|
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;
|
|
}
|
|
|
|
bool should_block = socket->blocking;
|
|
if ((flags & MSG_DONTWAIT) != 0) {
|
|
should_block = false;
|
|
}
|
|
|
|
if ((flags & MSG_NOSIGNAL) != 0) {
|
|
// Ignore it. WASI has no Unix-style signals. So effectively,
|
|
// MSG_NOSIGNAL is always the case, whether it was explicitly
|
|
// requested or not.
|
|
}
|
|
|
|
streams_borrow_output_stream_t tx_borrow =
|
|
streams_borrow_output_stream(connection.output);
|
|
while (true) {
|
|
streams_stream_error_t error;
|
|
uint64_t count;
|
|
if (!streams_method_output_stream_check_write(tx_borrow, &count,
|
|
&error)) {
|
|
// TODO wasi-sockets: wasi-sockets has no way to recover stream errors yet.
|
|
errno = EPIPE;
|
|
return -1;
|
|
}
|
|
|
|
if (count) {
|
|
count = count < length ? count : length;
|
|
wasip2_list_u8_t list = { .ptr = (uint8_t *)buffer,
|
|
.len = count };
|
|
if (!streams_method_output_stream_write(
|
|
tx_borrow, &list, &error)) {
|
|
// TODO wasi-sockets: wasi-sockets has no way to recover TCP stream errors yet.
|
|
errno = EPIPE;
|
|
return -1;
|
|
} else {
|
|
return count;
|
|
}
|
|
} else if (should_block) {
|
|
poll_borrow_pollable_t pollable_borrow =
|
|
poll_borrow_pollable(
|
|
connection.output_pollable);
|
|
poll_method_pollable_block(pollable_borrow);
|
|
} else {
|
|
errno = EWOULDBLOCK;
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static ssize_t udp_sendto(udp_socket_t *socket, const uint8_t *buffer,
|
|
size_t length, int flags, const struct sockaddr *addr,
|
|
socklen_t addrlen)
|
|
{
|
|
const int supported_flags = MSG_DONTWAIT;
|
|
if ((flags & supported_flags) != flags) {
|
|
errno = EOPNOTSUPP;
|
|
return -1;
|
|
}
|
|
|
|
network_ip_socket_address_t remote_address;
|
|
bool has_remote_address = (addr != NULL);
|
|
|
|
if (has_remote_address) {
|
|
if (socket->state.tag == UDP_SOCKET_STATE_CONNECTED) {
|
|
errno = EISCONN;
|
|
return -1;
|
|
}
|
|
|
|
int parse_err;
|
|
if (!__wasi_sockets_utils__parse_address(
|
|
socket->family, addr, addrlen, &remote_address,
|
|
&parse_err)) {
|
|
errno = parse_err;
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (addrlen != 0) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (socket->state.tag != UDP_SOCKET_STATE_CONNECTED) {
|
|
errno = EDESTADDRREQ;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
network_error_code_t error;
|
|
udp_borrow_udp_socket_t socket_borrow =
|
|
udp_borrow_udp_socket(socket->socket);
|
|
|
|
udp_socket_streams_t streams;
|
|
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;
|
|
}
|
|
|
|
if (!__wasi_sockets_utils__stream(socket, NULL, &streams,
|
|
&error)) {
|
|
errno = __wasi_sockets_utils__map_error(error);
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
case UDP_SOCKET_STATE_BOUND_NOSTREAMS: {
|
|
if (!__wasi_sockets_utils__stream(socket, NULL, &streams,
|
|
&error)) {
|
|
errno = __wasi_sockets_utils__map_error(error);
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
case UDP_SOCKET_STATE_BOUND_STREAMING:
|
|
streams = socket->state.bound_streaming.streams;
|
|
break;
|
|
|
|
case UDP_SOCKET_STATE_CONNECTED:
|
|
streams = socket->state.connected.streams;
|
|
break;
|
|
|
|
default: /* unreachable */
|
|
abort();
|
|
}
|
|
|
|
bool should_block = socket->blocking;
|
|
if ((flags & MSG_DONTWAIT) != 0) {
|
|
should_block = false;
|
|
}
|
|
|
|
udp_outgoing_datagram_t datagrams[1] = {{
|
|
.remote_address = {
|
|
.is_some = has_remote_address,
|
|
.val = remote_address,
|
|
},
|
|
.data = {
|
|
.len = length,
|
|
.ptr = (uint8_t*)buffer,
|
|
},
|
|
}};
|
|
udp_list_outgoing_datagram_t list = {
|
|
.len = 1,
|
|
.ptr = datagrams,
|
|
};
|
|
|
|
udp_borrow_outgoing_datagram_stream_t outgoing_borrow =
|
|
udp_borrow_outgoing_datagram_stream(streams.outgoing);
|
|
while (true) {
|
|
uint64_t allowed;
|
|
if (!udp_method_outgoing_datagram_stream_check_send(
|
|
outgoing_borrow, &allowed, &error)) {
|
|
errno = __wasi_sockets_utils__map_error(error);
|
|
return -1;
|
|
}
|
|
|
|
if (allowed) {
|
|
uint64_t datagrams_sent;
|
|
if (!udp_method_outgoing_datagram_stream_send(
|
|
outgoing_borrow, &list, &datagrams_sent,
|
|
&error)) {
|
|
errno = __wasi_sockets_utils__map_error(error);
|
|
return -1;
|
|
}
|
|
|
|
if (datagrams_sent != 0 && datagrams_sent != 1) {
|
|
abort();
|
|
}
|
|
|
|
if (datagrams_sent == 1) {
|
|
return length;
|
|
}
|
|
}
|
|
|
|
if (should_block) {
|
|
poll_borrow_pollable_t pollable_borrow =
|
|
poll_borrow_pollable(streams.outgoing_pollable);
|
|
poll_method_pollable_block(pollable_borrow);
|
|
} else {
|
|
errno = EWOULDBLOCK;
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
ssize_t send(int socket, const void *buffer, size_t length, int flags)
|
|
{
|
|
return sendto(socket, buffer, length, flags, NULL, 0);
|
|
}
|
|
|
|
ssize_t sendto(int socket, const void *buffer, size_t length, int flags,
|
|
const struct sockaddr *addr, socklen_t addrlen)
|
|
{
|
|
descriptor_table_entry_t *entry;
|
|
if (!descriptor_table_get_ref(socket, &entry)) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
|
|
if (buffer == NULL) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
switch (entry->tag) {
|
|
case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET:
|
|
return tcp_sendto(&entry->tcp_socket, buffer, length, flags,
|
|
addr, addrlen);
|
|
case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET:
|
|
return udp_sendto(&entry->udp_socket, buffer, length, flags,
|
|
addr, addrlen);
|
|
default:
|
|
errno = EOPNOTSUPP;
|
|
return -1;
|
|
}
|
|
}
|