Added socket send and recv timeout options (#1419)

Added socket send and recv timeout options with implementation for posix platform.

This is part of a extending support for sockets in WASI. #1336.

Also add sample that sets and reads back the send and receive timeouts using
the native function binding.
This commit is contained in:
Callum Macmillan 2022-09-03 01:35:23 +01:00 committed by GitHub
parent 3c4e980c9c
commit 72367f47eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 526 additions and 1 deletions

View File

@ -94,6 +94,9 @@ typedef struct __wasi_addr_info_hints_t {
* <sys/types.h>
*/
#define SO_RCVTIMEO 20
#define SO_SNDTIMEO 21
struct addrinfo {
int ai_flags; /* Input flags. */
int ai_family; /* Protocol family for socket. */
@ -132,6 +135,14 @@ getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int
getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int
getsockopt(int sockfd, int level, int optname, void *__restrict optval,
socklen_t *__restrict optlen);
int
setsockopt(int sockfd, int level, int optname, const void *optval,
socklen_t optlen);
int
getaddrinfo(const char *node, const char *service, const struct addrinfo *hints,
struct addrinfo **res);
@ -463,6 +474,62 @@ __wasi_sock_set_send_buf_size(__wasi_fd_t fd)
__imported_wasi_snapshot_preview1_sock_set_send_buf_size((int32_t)fd);
}
int32_t
__imported_wasi_snapshot_preview1_sock_get_recv_timeout(int32_t arg0,
int32_t arg1)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_get_recv_timeout")));
static inline __wasi_errno_t
__wasi_sock_get_recv_timeout(__wasi_fd_t fd, uint64_t *timeout_us)
{
return (__wasi_errno_t)
__imported_wasi_snapshot_preview1_sock_get_recv_timeout(
(int32_t)fd, (int32_t)timeout_us);
}
int32_t
__imported_wasi_snapshot_preview1_sock_set_recv_timeout(int32_t arg0,
int64_t arg1)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_set_recv_timeout")));
static inline __wasi_errno_t
__wasi_sock_set_recv_timeout(__wasi_fd_t fd, uint64_t timeout_us)
{
return (__wasi_errno_t)
__imported_wasi_snapshot_preview1_sock_set_recv_timeout(
(int32_t)fd, (int64_t)timeout_us);
}
int32_t
__imported_wasi_snapshot_preview1_sock_get_send_timeout(int32_t arg0,
int32_t arg1)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_get_send_timeout")));
static inline __wasi_errno_t
__wasi_sock_get_send_timeout(__wasi_fd_t fd, uint64_t *timeout_us)
{
return (__wasi_errno_t)
__imported_wasi_snapshot_preview1_sock_get_send_timeout(
(int32_t)fd, (int32_t)timeout_us);
}
int32_t
__imported_wasi_snapshot_preview1_sock_set_send_timeout(int32_t arg0,
int64_t arg1)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_set_send_timeout")));
static inline __wasi_errno_t
__wasi_sock_set_send_timeout(__wasi_fd_t fd, uint64_t timeout_us)
{
return (__wasi_errno_t)
__imported_wasi_snapshot_preview1_sock_set_send_timeout(
(int32_t)fd, (int64_t)timeout_us);
}
/**
* TODO: modify recv() and send()
* since don't want to re-compile the wasi-libc,

View File

@ -470,3 +470,92 @@ freeaddrinfo(struct addrinfo *res)
* frees the memory of the entire array. */
free(res);
}
struct timeval
time_us_to_timeval(uint64_t time_us)
{
struct timeval tv;
tv.tv_sec = time_us / 1000000UL;
tv.tv_usec = time_us % 1000000UL;
return tv;
}
uint64_t
timeval_to_time_us(struct timeval tv)
{
return (tv.tv_sec * 1000000UL) + tv.tv_usec;
}
int
get_sol_socket_option(int sockfd, int optname, void *__restrict optval,
socklen_t *__restrict optlen)
{
__wasi_errno_t error;
uint64_t timeout_us;
switch (optname) {
case SO_RCVTIMEO:
assert(*optlen == sizeof(struct timeval));
error = __wasi_sock_get_recv_timeout(sockfd, &timeout_us);
HANDLE_ERROR(error);
*(struct timeval *)optval = time_us_to_timeval(timeout_us);
return error;
case SO_SNDTIMEO:
assert(*optlen == sizeof(struct timeval));
error = __wasi_sock_get_send_timeout(sockfd, &timeout_us);
HANDLE_ERROR(error);
*(struct timeval *)optval = time_us_to_timeval(timeout_us);
return error;
}
HANDLE_ERROR(__WASI_ERRNO_NOTSUP);
}
int
getsockopt(int sockfd, int level, int optname, void *__restrict optval,
socklen_t *__restrict optlen)
{
switch (level) {
case SOL_SOCKET:
return get_sol_socket_option(sockfd, optname, optval, optlen);
}
HANDLE_ERROR(__WASI_ERRNO_NOTSUP);
}
int
set_sol_socket_option(int sockfd, int optname, const void *optval,
socklen_t optlen)
{
__wasi_errno_t error;
uint64_t timeout_us;
switch (optname) {
case SO_RCVTIMEO:
assert(optlen == sizeof(struct timeval));
timeout_us = timeval_to_time_us(*(struct timeval *)optval);
error = __wasi_sock_set_recv_timeout(sockfd, timeout_us);
HANDLE_ERROR(error);
return error;
case SO_SNDTIMEO:
assert(optlen == sizeof(struct timeval));
timeout_us = timeval_to_time_us(*(struct timeval *)optval);
error = __wasi_sock_set_send_timeout(sockfd, timeout_us);
HANDLE_ERROR(error);
return error;
}
HANDLE_ERROR(__WASI_ERRNO_NOTSUP);
}
int
setsockopt(int sockfd, int level, int optname, const void *optval,
socklen_t optlen)
{
switch (level) {
case SOL_SOCKET:
return set_sol_socket_option(sockfd, optname, optval, optlen);
}
HANDLE_ERROR(__WASI_ERRNO_NOTSUP);
}

View File

@ -1126,6 +1126,25 @@ wasi_sock_get_recv_buf_size(wasm_exec_env_t exec_env, wasi_fd_t fd,
return __WASI_ENOSYS;
}
static wasi_errno_t
wasi_sock_get_recv_timeout(wasm_exec_env_t exec_env, wasi_fd_t fd,
uint64_t *timeout_us)
{
wasm_module_inst_t module_inst = get_module_inst(exec_env);
wasi_ctx_t wasi_ctx = get_wasi_ctx(module_inst);
struct fd_table *curfds = NULL;
if (!wasi_ctx)
return __WASI_EACCES;
if (!validate_native_addr(timeout_us, sizeof(uint64_t)))
return __WASI_EINVAL;
curfds = wasi_ctx_get_curfds(module_inst, wasi_ctx);
return wasmtime_ssp_sock_get_recv_timeout(curfds, fd, timeout_us);
}
static wasi_errno_t
wasi_sock_get_reuse_addr(wasm_exec_env_t exec_env, wasi_fd_t fd, uint8 *reuse)
{
@ -1145,6 +1164,25 @@ wasi_sock_get_send_buf_size(wasm_exec_env_t exec_env, wasi_fd_t fd,
return __WASI_ENOSYS;
}
static wasi_errno_t
wasi_sock_get_send_timeout(wasm_exec_env_t exec_env, wasi_fd_t fd,
uint64_t *timeout_us)
{
wasm_module_inst_t module_inst = get_module_inst(exec_env);
wasi_ctx_t wasi_ctx = get_wasi_ctx(module_inst);
struct fd_table *curfds = NULL;
if (!wasi_ctx)
return __WASI_EACCES;
if (!validate_native_addr(timeout_us, sizeof(uint64_t)))
return __WASI_EINVAL;
curfds = wasi_ctx_get_curfds(module_inst, wasi_ctx);
return wasmtime_ssp_sock_get_send_timeout(curfds, fd, timeout_us);
}
static wasi_errno_t
wasi_sock_listen(wasm_exec_env_t exec_env, wasi_fd_t fd, uint32 backlog)
{
@ -1184,6 +1222,22 @@ wasi_sock_set_recv_buf_size(wasm_exec_env_t exec_env, wasi_fd_t fd,
return __WASI_ENOSYS;
}
static wasi_errno_t
wasi_sock_set_recv_timeout(wasm_exec_env_t exec_env, wasi_fd_t fd,
uint64_t timeout_us)
{
wasm_module_inst_t module_inst = get_module_inst(exec_env);
wasi_ctx_t wasi_ctx = get_wasi_ctx(module_inst);
struct fd_table *curfds = NULL;
if (!wasi_ctx)
return __WASI_EACCES;
curfds = wasi_ctx_get_curfds(module_inst, wasi_ctx);
return wasmtime_ssp_sock_set_recv_timeout(curfds, fd, timeout_us);
}
static wasi_errno_t
wasi_sock_set_reuse_addr(wasm_exec_env_t exec_env, wasi_fd_t fd, uint8 reuse)
{
@ -1203,6 +1257,22 @@ wasi_sock_set_send_buf_size(wasm_exec_env_t exec_env, wasi_fd_t fd,
return __WASI_ENOSYS;
}
static wasi_errno_t
wasi_sock_set_send_timeout(wasm_exec_env_t exec_env, wasi_fd_t fd,
uint64_t timeout_us)
{
wasm_module_inst_t module_inst = get_module_inst(exec_env);
wasi_ctx_t wasi_ctx = get_wasi_ctx(module_inst);
struct fd_table *curfds = NULL;
if (!wasi_ctx)
return __WASI_EACCES;
curfds = wasi_ctx_get_curfds(module_inst, wasi_ctx);
return wasmtime_ssp_sock_set_send_timeout(curfds, fd, timeout_us);
}
static wasi_errno_t
wasi_sock_recv(wasm_exec_env_t exec_env, wasi_fd_t sock, iovec_app_t *ri_data,
uint32 ri_data_len, wasi_riflags_t ri_flags, uint32 *ro_data_len,
@ -1427,17 +1497,21 @@ static NativeSymbol native_symbols_libc_wasi[] = {
REG_NATIVE_FUNC(sock_close, "(i)i"),
REG_NATIVE_FUNC(sock_connect, "(i*)i"),
REG_NATIVE_FUNC(sock_get_recv_buf_size, "(i*)i"),
REG_NATIVE_FUNC(sock_get_recv_timeout, "(i*)i"),
REG_NATIVE_FUNC(sock_get_reuse_addr, "(i*)i"),
REG_NATIVE_FUNC(sock_get_reuse_port, "(i*)i"),
REG_NATIVE_FUNC(sock_get_send_buf_size, "(i*)i"),
REG_NATIVE_FUNC(sock_get_send_timeout, "(i*)i"),
REG_NATIVE_FUNC(sock_listen, "(ii)i"),
REG_NATIVE_FUNC(sock_open, "(iii*)i"),
REG_NATIVE_FUNC(sock_recv, "(i*ii**)i"),
REG_NATIVE_FUNC(sock_send, "(i*ii*)i"),
REG_NATIVE_FUNC(sock_set_recv_buf_size, "(ii)i"),
REG_NATIVE_FUNC(sock_set_recv_timeout, "(iI)i"),
REG_NATIVE_FUNC(sock_set_reuse_addr, "(ii)i"),
REG_NATIVE_FUNC(sock_set_reuse_port, "(ii)i"),
REG_NATIVE_FUNC(sock_set_send_buf_size, "(ii)i"),
REG_NATIVE_FUNC(sock_set_send_timeout, "(iI)i"),
REG_NATIVE_FUNC(sock_shutdown, "(ii)i"),
REG_NATIVE_FUNC(sched_yield, "()i"),
};

View File

@ -1088,6 +1088,38 @@ __wasi_errno_t wasmtime_ssp_sock_shutdown(
__wasi_fd_t sock
) WASMTIME_SSP_SYSCALL_NAME(sock_shutdown) __attribute__((__warn_unused_result__));
__wasi_errno_t wasmtime_ssp_sock_set_recv_timeout(
#if !defined(WASMTIME_SSP_STATIC_CURFDS)
struct fd_table *curfds,
#endif
__wasi_fd_t sock,
uint64_t timeout_us
) WASMTIME_SSP_SYSCALL_NAME(sock_set_recv_timeout) __attribute__((__warn_unused_result__));
__wasi_errno_t wasmtime_ssp_sock_get_recv_timeout(
#if !defined(WASMTIME_SSP_STATIC_CURFDS)
struct fd_table *curfds,
#endif
__wasi_fd_t sock,
uint64_t *timeout_us
) WASMTIME_SSP_SYSCALL_NAME(sock_get_recv_timeout) __attribute__((__warn_unused_result__));
__wasi_errno_t wasmtime_ssp_sock_set_send_timeout(
#if !defined(WASMTIME_SSP_STATIC_CURFDS)
struct fd_table *curfds,
#endif
__wasi_fd_t sock,
uint64_t timeout_us
) WASMTIME_SSP_SYSCALL_NAME(sock_set_send_timeout) __attribute__((__warn_unused_result__));
__wasi_errno_t wasmtime_ssp_sock_get_send_timeout(
#if !defined(WASMTIME_SSP_STATIC_CURFDS)
struct fd_table *curfds,
#endif
__wasi_fd_t sock,
uint64_t *timeout_us
) WASMTIME_SSP_SYSCALL_NAME(sock_get_send_timeout) __attribute__((__warn_unused_result__));
__wasi_errno_t wasmtime_ssp_sched_yield(void)
WASMTIME_SSP_SYSCALL_NAME(sched_yield) __attribute__((__warn_unused_result__));

View File

@ -3556,3 +3556,37 @@ addr_pool_destroy(struct addr_pool *addr_pool)
cur = next;
}
}
#ifndef WASMTIME_SSP_STATIC_CURFDS
#define WASMTIME_SSP_PASSTHROUGH_FD_TABLE struct fd_table *curfds,
#else
#define WASMTIME_SSP_PASSTHROUGH_FD_TABLE
#endif
// Defines a function that passes through the socket option to the OS
// implementation
#define WASMTIME_SSP_PASSTHROUGH_SOCKET_OPTION(FUNC_NAME, OPTION_TYPE) \
__wasi_errno_t wasmtime_ssp_sock_##FUNC_NAME( \
WASMTIME_SSP_PASSTHROUGH_FD_TABLE __wasi_fd_t sock, \
OPTION_TYPE option) \
{ \
struct fd_object *fo; \
__wasi_errno_t error; \
int ret; \
error = fd_object_get(curfds, &fo, sock, 0, 0); \
if (error != 0) \
return error; \
ret = os_socket_##FUNC_NAME(fd_number(fo), option); \
fd_object_release(fo); \
if (BHT_OK != ret) \
return convert_errno(errno); \
return __WASI_ESUCCESS; \
}
WASMTIME_SSP_PASSTHROUGH_SOCKET_OPTION(set_send_timeout, uint64)
WASMTIME_SSP_PASSTHROUGH_SOCKET_OPTION(get_send_timeout, uint64 *)
WASMTIME_SSP_PASSTHROUGH_SOCKET_OPTION(set_recv_timeout, uint64)
WASMTIME_SSP_PASSTHROUGH_SOCKET_OPTION(get_recv_timeout, uint64 *)
#undef WASMTIME_SSP_PASSTHROUGH_FD_TABLE
#undef WASMTIME_SSP_PASSTHROUGH_SOCKET_OPTION

View File

@ -351,6 +351,54 @@ os_socket_convert_sockaddr(struct sockaddr *addr, uint8_t *buf, size_t buflen,
return BHT_OK;
}
int
os_socket_set_send_timeout(bh_socket_t socket, uint64 timeout_us)
{
struct timeval tv;
tv.tv_sec = timeout_us / 1000000UL;
tv.tv_usec = timeout_us % 1000000UL;
if (setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) != 0) {
return BHT_ERROR;
}
return BHT_OK;
}
int
os_socket_get_send_timeout(bh_socket_t socket, uint64 *timeout_us)
{
struct timeval tv;
socklen_t tv_len = sizeof(tv);
if (getsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, &tv, &tv_len) != 0) {
return BHT_ERROR;
}
*timeout_us = (tv.tv_sec * 1000000UL) + tv.tv_usec;
return BHT_OK;
}
int
os_socket_set_recv_timeout(bh_socket_t socket, uint64 timeout_us)
{
struct timeval tv;
tv.tv_sec = timeout_us / 1000000UL;
tv.tv_usec = timeout_us % 1000000UL;
if (setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0) {
return BHT_ERROR;
}
return BHT_OK;
}
int
os_socket_get_recv_timeout(bh_socket_t socket, uint64 *timeout_us)
{
struct timeval tv;
socklen_t tv_len = sizeof(tv);
if (getsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &tv, &tv_len) != 0) {
return BHT_ERROR;
}
*timeout_us = (tv.tv_sec * 1000000UL) + tv.tv_usec;
return BHT_OK;
}
int
os_socket_addr_local(bh_socket_t socket, uint8_t *buf, size_t buflen,
uint16_t *port, uint8_t *is_ipv4)

View File

@ -511,6 +511,50 @@ int
os_socket_addr_remote(bh_socket_t socket, uint8_t *buf, size_t buflen,
uint16_t *port, uint8_t *is_ipv4);
/**
* Set the send timeout until reporting an error
*
* @param socket the socket to set
* @param time_us microseconds until timeout
*
* @return 0 if success, -1 otherwise
*/
int
os_socket_set_send_timeout(bh_socket_t socket, uint64 timeout_us);
/**
* Get the send timeout until reporting an error
*
* @param socket the socket to set
* @param time_us the returned microseconds until timeout
*
* @return 0 if success, -1 otherwise
*/
int
os_socket_get_send_timeout(bh_socket_t socket, uint64 *timeout_us);
/**
* Set the recv timeout until reporting an error
*
* @param socket the socket to set
* @param time_us microseconds until timeout
*
* @return 0 if success, -1 otherwise
*/
int
os_socket_set_recv_timeout(bh_socket_t socket, uint64 timeout_us);
/**
* Get the recv timeout until reporting an error
*
* @param socket the socket to set
* @param time_us the returned microseconds until timeout
*
* @return 0 if success, -1 otherwise
*/
int
os_socket_get_recv_timeout(bh_socket_t socket, uint64 *timeout_us);
#ifdef __cplusplus
}
#endif

View File

@ -713,4 +713,36 @@ os_socket_addr_remote(bh_socket_t socket, uint8_t *buf, size_t buflen,
return BHT_ERROR;
}
int
os_socket_set_send_timeout(bh_socket_t socket, uint64 timeout_us)
{
errno = ENOSYS;
return BHT_ERROR;
}
int
os_socket_get_send_timeout(bh_socket_t socket, uint64 *timeout_us)
{
errno = ENOSYS;
return BHT_ERROR;
}
int
os_socket_set_recv_timeout(bh_socket_t socket, uint64 timeout_us)
{
errno = ENOSYS;
return BHT_ERROR;
}
int
os_socket_get_recv_timeout(bh_socket_t socket, uint64 *timeout_us)
{
errno = ENOSYS;
return BHT_ERROR;
}
#endif

View File

@ -209,6 +209,38 @@ os_socket_addr_local(bh_socket_t socket, uint8_t *buf, size_t buflen,
return BHT_ERROR;
}
int
os_socket_set_send_timeout(bh_socket_t socket, uint64 timeout_us)
{
errno = ENOSYS;
return BHT_ERROR;
}
int
os_socket_get_send_timeout(bh_socket_t socket, uint64 *timeout_us)
{
errno = ENOSYS;
return BHT_ERROR;
}
int
os_socket_set_recv_timeout(bh_socket_t socket, uint64 timeout_us)
{
errno = ENOSYS;
return BHT_ERROR;
}
int
os_socket_get_recv_timeout(bh_socket_t socket, uint64 *timeout_us)
{
errno = ENOSYS;
return BHT_ERROR;
}
int
os_socket_addr_remote(bh_socket_t socket, uint8_t *buf, size_t buflen,
uint16_t *port, uint8_t *is_ipv4)

View File

@ -90,6 +90,7 @@ ExternalProject_Add(wasm-app
tcp_client.wasm ${CMAKE_CURRENT_SOURCE_DIR}/build
tcp_server.wasm ${CMAKE_CURRENT_SOURCE_DIR}/build
send_recv.wasm ${CMAKE_CURRENT_SOURCE_DIR}/build
socket_opts.wasm ${CMAKE_CURRENT_SOURCE_DIR}/build
)
add_executable(tcp_server ${CMAKE_CURRENT_SOURCE_DIR}/wasm-src/tcp_server.c)
@ -102,6 +103,8 @@ target_link_libraries(send_recv pthread)
add_executable(addr_resolve ${CMAKE_CURRENT_SOURCE_DIR}/wasm-src/addr_resolve.c)
add_executable(socket_opts ${CMAKE_CURRENT_SOURCE_DIR}/wasm-src/socket_opts.c)
############################################
## Build iwasm with wasi and pthread support
############################################

View File

@ -82,4 +82,17 @@ Data:
And mourns for us
```
`socket_opts.wasm` shows an example of getting and setting various supported socket options
```bash
$ ./iwasm ./socket_opts.wasm
```
The output is:
```bash
[Client] Create socket
recv_timeout is expected
send_timeout is expected
[Client] Close socket
```
Refer to [socket api document](../../doc/socket_api.md) for more details.

View File

@ -80,3 +80,4 @@ compile_with_clang(tcp_server.c)
compile_with_clang(tcp_client.c)
compile_with_clang(send_recv.c)
compile_with_clang(addr_resolve.c)
compile_with_clang(socket_opts.c)

View File

@ -0,0 +1,56 @@
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#ifdef __wasi__
#include <wasi_socket_ext.h>
#endif
#define OPTION_ASSERT(A, B, OPTION) \
if (A == B) { \
printf("%s is expected\n", OPTION); \
} \
else { \
printf("%s is unexpected\n", OPTION); \
return EXIT_FAILURE; \
}
struct timeval
to_timeval(time_t tv_sec, suseconds_t tv_usec)
{
struct timeval tv = { tv_sec, tv_usec };
return tv;
}
int
main(int argc, char *argv[])
{
int socket_fd = 0;
struct timeval tv;
socklen_t tv_len = sizeof(tv);
printf("[Client] Create socket\n");
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd == -1) {
perror("Create socket failed");
return EXIT_FAILURE;
}
tv = to_timeval(123, 1000);
setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
tv = to_timeval(0, 0);
getsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, &tv_len);
OPTION_ASSERT(tv.tv_sec, 123, "SO_RCVTIMEO tv_sec");
OPTION_ASSERT(tv.tv_usec, 1000, "SO_RCVTIMEO tv_usec");
tv = to_timeval(456, 2000);
setsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
tv = to_timeval(0, 0);
getsockopt(socket_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, &tv_len);
OPTION_ASSERT(tv.tv_sec, 456, "SO_SNDTIMEO tv_sec");
OPTION_ASSERT(tv.tv_usec, 2000, "SO_SNDTIMEO tv_usec");
printf("[Client] Close socket\n");
close(socket_fd);
return EXIT_SUCCESS;
}