mirror of
https://git.proxmox.com/git/wasi-libc
synced 2025-08-04 23:56:26 +00:00
implement basic TCP/UDP server support (#481)
This adds `bind`, `listen`, and `accept` implementations based on `wasi-sockets` for the `wasm32-wasip2` target. Signed-off-by: Joel Dice <joel.dice@fermyon.com> Co-authored-by: Dave Bakker <github@davebakker.io>
This commit is contained in:
parent
684f155664
commit
f493dc284d
8
Makefile
8
Makefile
@ -86,7 +86,10 @@ LIBC_BOTTOM_HALF_OMIT_SOURCES := \
|
||||
$(LIBC_BOTTOM_HALF_SOURCES)/socket.c \
|
||||
$(LIBC_BOTTOM_HALF_SOURCES)/send.c \
|
||||
$(LIBC_BOTTOM_HALF_SOURCES)/recv.c \
|
||||
$(LIBC_BOTTOM_HALF_SOURCES)/sockets_utils.c
|
||||
$(LIBC_BOTTOM_HALF_SOURCES)/sockets_utils.c \
|
||||
$(LIBC_BOTTOM_HALF_SOURCES)/bind.c \
|
||||
$(LIBC_BOTTOM_HALF_SOURCES)/listen.c \
|
||||
$(LIBC_BOTTOM_HALF_SOURCES)/accept-wasip2.c
|
||||
LIBC_BOTTOM_HALF_ALL_SOURCES := $(filter-out $(LIBC_BOTTOM_HALF_OMIT_SOURCES),$(LIBC_BOTTOM_HALF_ALL_SOURCES))
|
||||
# Omit p2-specific headers from include-all.c test.
|
||||
INCLUDE_ALL_CLAUSES := -not -name wasip2.h -not -name descriptor_table.h
|
||||
@ -96,7 +99,8 @@ ifeq ($(WASI_SNAPSHOT), p2)
|
||||
# Omit source files not relevant to WASIp2.
|
||||
LIBC_BOTTOM_HALF_OMIT_SOURCES := \
|
||||
$(LIBC_BOTTOM_HALF_CLOUDLIBC_SRC)/libc/sys/socket/send.c \
|
||||
$(LIBC_BOTTOM_HALF_CLOUDLIBC_SRC)/libc/sys/socket/recv.c
|
||||
$(LIBC_BOTTOM_HALF_CLOUDLIBC_SRC)/libc/sys/socket/recv.c \
|
||||
$(LIBC_BOTTOM_HALF_SOURCES)/accept-wasip1.c
|
||||
LIBC_BOTTOM_HALF_ALL_SOURCES := $(filter-out $(LIBC_BOTTOM_HALF_OMIT_SOURCES),$(LIBC_BOTTOM_HALF_ALL_SOURCES))
|
||||
endif
|
||||
|
||||
|
@ -411,6 +411,7 @@ atoll
|
||||
basename
|
||||
bcmp
|
||||
bcopy
|
||||
bind
|
||||
bsd_signal
|
||||
bsearch
|
||||
btowc
|
||||
@ -892,6 +893,7 @@ lgammal
|
||||
lgammal_r
|
||||
link
|
||||
linkat
|
||||
listen
|
||||
llabs
|
||||
lldiv
|
||||
llrint
|
||||
@ -1236,10 +1238,13 @@ tanh
|
||||
tanhf
|
||||
tanhl
|
||||
tanl
|
||||
tcp_accept
|
||||
tcp_bind
|
||||
tcp_borrow_tcp_socket
|
||||
tcp_create_socket_create_tcp_socket
|
||||
tcp_create_socket_result_own_tcp_socket_error_code_free
|
||||
tcp_ip_socket_address_free
|
||||
tcp_listen
|
||||
tcp_method_tcp_socket_accept
|
||||
tcp_method_tcp_socket_address_family
|
||||
tcp_method_tcp_socket_finish_bind
|
||||
@ -1320,6 +1325,8 @@ truncf
|
||||
truncl
|
||||
tsearch
|
||||
twalk
|
||||
udp_accept
|
||||
udp_bind
|
||||
udp_borrow_incoming_datagram_stream
|
||||
udp_borrow_outgoing_datagram_stream
|
||||
udp_borrow_udp_socket
|
||||
@ -1331,6 +1338,7 @@ udp_incoming_datagram_stream_drop_own
|
||||
udp_ip_socket_address_free
|
||||
udp_list_incoming_datagram_free
|
||||
udp_list_outgoing_datagram_free
|
||||
udp_listen
|
||||
udp_method_incoming_datagram_stream_receive
|
||||
udp_method_incoming_datagram_stream_subscribe
|
||||
udp_method_outgoing_datagram_stream_check_send
|
||||
|
143
libc-bottom-half/sources/accept-wasip2.c
Normal file
143
libc-bottom-half/sources/accept-wasip2.c
Normal file
@ -0,0 +1,143 @@
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <netinet/in.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <wasi/api.h>
|
||||
#include <wasi/descriptor_table.h>
|
||||
#include <wasi/sockets_utils.h>
|
||||
|
||||
int tcp_accept(tcp_socket_t *socket, bool client_blocking,
|
||||
struct sockaddr *addr, socklen_t *addrlen)
|
||||
{
|
||||
output_sockaddr_t output_addr;
|
||||
if (!__wasi_sockets_utils__output_addr_validate(
|
||||
socket->family, addr, addrlen, &output_addr)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
tcp_socket_state_listening_t listener;
|
||||
if (socket->state.tag == TCP_SOCKET_STATE_LISTENING) {
|
||||
listener = socket->state.listening;
|
||||
} else {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
tcp_borrow_tcp_socket_t socket_borrow =
|
||||
tcp_borrow_tcp_socket(socket->socket);
|
||||
|
||||
tcp_tuple3_own_tcp_socket_own_input_stream_own_output_stream_t
|
||||
client_and_io;
|
||||
network_error_code_t error;
|
||||
while (!tcp_method_tcp_socket_accept(socket_borrow, &client_and_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 = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
errno = __wasi_sockets_utils__map_error(error);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
tcp_own_tcp_socket_t client = client_and_io.f0;
|
||||
tcp_borrow_tcp_socket_t client_borrow = tcp_borrow_tcp_socket(client);
|
||||
|
||||
poll_own_pollable_t client_pollable =
|
||||
tcp_method_tcp_socket_subscribe(client_borrow);
|
||||
|
||||
streams_own_input_stream_t input = client_and_io.f1;
|
||||
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 = client_and_io.f2;
|
||||
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);
|
||||
|
||||
if (output_addr.tag != OUTPUT_SOCKADDR_NULL) {
|
||||
network_ip_socket_address_t remote_address;
|
||||
if (!tcp_method_tcp_socket_remote_address(
|
||||
client_borrow, &remote_address, &error)) {
|
||||
// TODO wasi-sockets: How to recover from this in a POSIX compatible way?
|
||||
abort();
|
||||
}
|
||||
|
||||
__wasi_sockets_utils__output_addr_write(remote_address,
|
||||
&output_addr);
|
||||
}
|
||||
|
||||
descriptor_table_entry_t client_entry = { .tag = DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET, .tcp_socket = {
|
||||
.socket = client,
|
||||
.socket_pollable = client_pollable,
|
||||
.blocking = client_blocking,
|
||||
.fake_nodelay = socket->fake_nodelay,
|
||||
.family = socket->family,
|
||||
.state = { .tag = TCP_SOCKET_STATE_CONNECTED, .connected = {
|
||||
.input = input,
|
||||
.input_pollable = input_pollable,
|
||||
.output = output,
|
||||
.output_pollable = output_pollable,
|
||||
} },
|
||||
} };
|
||||
|
||||
int client_fd;
|
||||
if (!descriptor_table_insert(client_entry, &client_fd)) {
|
||||
errno = EMFILE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return client_fd;
|
||||
}
|
||||
|
||||
int udp_accept(udp_socket_t *socket, bool client_blocking,
|
||||
struct sockaddr *addr, socklen_t *addrlen)
|
||||
{
|
||||
// UDP doesn't support accept
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int accept(int socket, struct sockaddr *restrict addr,
|
||||
socklen_t *restrict addrlen)
|
||||
{
|
||||
return accept4(socket, addr, addrlen, 0);
|
||||
}
|
||||
|
||||
int accept4(int socket, struct sockaddr *restrict addr,
|
||||
socklen_t *restrict addrlen, int flags)
|
||||
{
|
||||
descriptor_table_entry_t *entry;
|
||||
if (!descriptor_table_get_ref(socket, &entry)) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool client_blocking = (flags & SOCK_NONBLOCK) == 0;
|
||||
// Ignore SOCK_CLOEXEC flag. That concept does not exist in WASI.
|
||||
|
||||
switch (entry->tag) {
|
||||
case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET:
|
||||
return tcp_accept(&entry->tcp_socket, client_blocking, addr,
|
||||
addrlen);
|
||||
case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET:
|
||||
return udp_accept(&entry->udp_socket, client_blocking, addr,
|
||||
addrlen);
|
||||
default:
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
}
|
53
libc-bottom-half/sources/bind.c
Normal file
53
libc-bottom-half/sources/bind.c
Normal file
@ -0,0 +1,53 @@
|
||||
#include <errno.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <wasi/api.h>
|
||||
#include <wasi/descriptor_table.h>
|
||||
#include <wasi/sockets_utils.h>
|
||||
|
||||
int tcp_bind(tcp_socket_t *socket, const struct sockaddr *addr,
|
||||
socklen_t addrlen)
|
||||
{
|
||||
network_ip_socket_address_t local_address;
|
||||
int parse_err;
|
||||
if (!__wasi_sockets_utils__parse_address(socket->family, addr, addrlen,
|
||||
&local_address, &parse_err)) {
|
||||
errno = parse_err;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return __wasi_sockets_utils__tcp_bind(socket, &local_address);
|
||||
}
|
||||
|
||||
int udp_bind(udp_socket_t *socket, const struct sockaddr *addr,
|
||||
socklen_t addrlen)
|
||||
{
|
||||
network_ip_socket_address_t local_address;
|
||||
int parse_err;
|
||||
if (!__wasi_sockets_utils__parse_address(socket->family, addr, addrlen,
|
||||
&local_address, &parse_err)) {
|
||||
errno = parse_err;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return __wasi_sockets_utils__udp_bind(socket, &local_address);
|
||||
}
|
||||
|
||||
int bind(int socket, const struct sockaddr *addr, socklen_t addrlen)
|
||||
{
|
||||
descriptor_table_entry_t *entry;
|
||||
if (!descriptor_table_get_ref(socket, &entry)) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (entry->tag) {
|
||||
case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET:
|
||||
return tcp_bind(&entry->tcp_socket, addr, addrlen);
|
||||
case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET:
|
||||
return udp_bind(&entry->udp_socket, addr, addrlen);
|
||||
default:
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
}
|
115
libc-bottom-half/sources/listen.c
Normal file
115
libc-bottom-half/sources/listen.c
Normal file
@ -0,0 +1,115 @@
|
||||
#include <errno.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <wasi/api.h>
|
||||
#include <wasi/descriptor_table.h>
|
||||
#include <wasi/sockets_utils.h>
|
||||
|
||||
int tcp_listen(tcp_socket_t *socket, int backlog)
|
||||
{
|
||||
network_error_code_t error;
|
||||
tcp_borrow_tcp_socket_t socket_borrow =
|
||||
tcp_borrow_tcp_socket(socket->socket);
|
||||
|
||||
switch (socket->state.tag) {
|
||||
case TCP_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__tcp_bind(socket, &any);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (socket->state.tag != TCP_SOCKET_STATE_BOUND) {
|
||||
abort();
|
||||
}
|
||||
// Great! We'll continue below.
|
||||
break;
|
||||
}
|
||||
case TCP_SOCKET_STATE_BOUND:
|
||||
// Great! We'll continue below.
|
||||
break;
|
||||
case TCP_SOCKET_STATE_LISTENING:
|
||||
// We can only update the backlog size.
|
||||
break;
|
||||
case TCP_SOCKET_STATE_CONNECTING:
|
||||
case TCP_SOCKET_STATE_CONNECTED:
|
||||
case TCP_SOCKET_STATE_CONNECT_FAILED:
|
||||
default:
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!tcp_method_tcp_socket_set_listen_backlog_size(socket_borrow,
|
||||
backlog, &error)) {
|
||||
abort(); // Our own state checks should've prevented this from happening.
|
||||
}
|
||||
|
||||
if (socket->state.tag == TCP_SOCKET_STATE_LISTENING) {
|
||||
// Updating the backlog is all we had to do.
|
||||
return 0;
|
||||
}
|
||||
|
||||
network_borrow_network_t network_borrow =
|
||||
__wasi_sockets_utils__borrow_network();
|
||||
if (!tcp_method_tcp_socket_start_listen(socket_borrow, &error)) {
|
||||
errno = __wasi_sockets_utils__map_error(error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Listen has successfully started. Attempt to finish it:
|
||||
while (!tcp_method_tcp_socket_finish_listen(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;
|
||||
}
|
||||
}
|
||||
|
||||
// Listen successful.
|
||||
|
||||
socket->state = (tcp_socket_state_t){
|
||||
.tag = TCP_SOCKET_STATE_LISTENING,
|
||||
.listening = { /* No additional state */ }
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
|
||||
int udp_listen(udp_socket_t *socket, int backlog)
|
||||
{
|
||||
// UDP doesn't support listen
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int listen(int socket, int backlog)
|
||||
{
|
||||
descriptor_table_entry_t *entry;
|
||||
if (!descriptor_table_get_ref(socket, &entry)) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (backlog < 0) {
|
||||
// POSIX:
|
||||
// > If listen() is called with a backlog argument value that is
|
||||
// > less than 0, the function behaves as if it had been called
|
||||
// > with a backlog argument value of 0.
|
||||
backlog = 0;
|
||||
}
|
||||
|
||||
switch (entry->tag) {
|
||||
case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET:
|
||||
return tcp_listen(&entry->tcp_socket, backlog);
|
||||
case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET:
|
||||
return udp_listen(&entry->udp_socket, backlog);
|
||||
default:
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
}
|
@ -410,8 +410,6 @@ int shutdown (int, int);
|
||||
|
||||
#if (defined __wasilibc_unmodified_upstream) || (defined __wasilibc_use_wasip2)
|
||||
int connect (int, const struct sockaddr *, socklen_t);
|
||||
#endif
|
||||
#ifdef __wasilibc_unmodified_upstream /* WASI has no bind/listen */
|
||||
int bind (int, const struct sockaddr *, socklen_t);
|
||||
int listen (int, int);
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user