forked from WebAssembly/wasi-libc
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implement basic TCP/UDP server support (WebAssembly#481)
This adds `bind`, `listen`, and `accept` implementations based on `wasi-sockets` for the `wasm32-wasip2` target. Signed-off-by: Joel Dice <[email protected]> Co-authored-by: Dave Bakker <[email protected]>
- Loading branch information
Showing
7 changed files
with
325 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters