Implement part of Berkeley Socket API for libc-wasi (#1036)

Refer to [Networking API design](https://github.com/WebAssembly/WASI/issues/370)
and [feat(socket): berkeley socket API v2](https://github.com/WebAssembly/WASI/pull/459):

- Support the socket API of synchronous mode, including `socket/bind/listen/accept/send/recv/close/shutdown`,
    the asynchronous mode isn't supported yet.
- Support adding `--addr-pool=<pool1,pool2,..>` argument for command line to identify the valid ip address range
- Add socket-api sample and update the document
This commit is contained in:
Wenyong Huang
2022-03-10 15:13:38 +08:00
committed by GitHub
parent 0065743075
commit 9c87a1ee17
28 changed files with 2211 additions and 214 deletions

View File

@ -0,0 +1,410 @@
/*
* Copyright (C) 2019 Intel Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
#ifndef _WASI_SOCKET_EXT_H_
#define _WASI_SOCKET_EXT_H_
#include <stddef.h>
#include <stdint.h>
/*Be a part of <wasi/api.h>*/
typedef enum {
SOCKET_DGRAM = 0,
SOCKET_STREAM,
} __wasi_sock_type_t;
typedef uint16_t __wasi_ip_port_t;
typedef enum { IPv4 = 0, IPv6 } __wasi_addr_type_t;
/* n0.n1.n2.n3 */
typedef struct __wasi_addr_ip4_t {
uint8_t n0;
uint8_t n1;
uint8_t n2;
uint8_t n3;
} __wasi_addr_ip4_t;
typedef struct __wasi_addr_ip4_port_t {
__wasi_addr_ip4_t addr;
__wasi_ip_port_t port;
} __wasi_addr_ip4_port_t;
typedef struct __wasi_addr_ip6_t {
uint16_t n0;
uint16_t n1;
uint16_t n2;
uint16_t n3;
uint16_t h0;
uint16_t h1;
uint16_t h2;
uint16_t h3;
} __wasi_addr_ip6_t;
typedef struct __wasi_addr_ip6_port_t {
__wasi_addr_ip6_t addr;
__wasi_ip_port_t port;
} __wasi_addr_ip6_port_t;
typedef struct __wasi_addr_t {
__wasi_addr_type_t kind;
union {
__wasi_addr_ip4_port_t ip4;
__wasi_addr_ip6_port_t ip6;
} addr;
} __wasi_addr_t;
typedef enum { INET4 = 0, INET6 } __wasi_address_family_t;
#ifdef __wasi__
/**
* Reimplement below POSIX APIs with __wasi_sock_XXX functions.
*
* Keep sync with
* <sys/socket.h>
* <sys/types.h>
*/
int
accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int
bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int
connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int
listen(int sockfd, int backlog);
int
socket(int domain, int type, int protocol);
#endif
/**
* Accept a connection on a socket
* Note: This is similar to `accept`
*/
int32_t
__imported_wasi_snapshot_preview1_sock_accept(int32_t arg0, int32_t arg1)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_accept")));
static inline __wasi_errno_t
__wasi_sock_accept(__wasi_fd_t fd, __wasi_fd_t *fd_new)
{
return (__wasi_errno_t)__imported_wasi_snapshot_preview1_sock_accept(
(int32_t)fd, (int32_t)fd_new);
}
/**
* Returns the local address to which the socket is bound.
*
* Note: This is similar to `getsockname` in POSIX
*
* When successful, the contents of the output buffer consist of an IP address,
* either IP4 or IP6.
*/
int32_t
__imported_wasi_snapshot_preview1_sock_addr_local(int32_t arg0, int32_t arg1,
int32_t arg2)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_addr_local")));
static inline __wasi_errno_t
__wasi_sock_addr_local(__wasi_fd_t fd, uint8_t *buf, __wasi_size_t buf_len)
{
return (__wasi_errno_t)__imported_wasi_snapshot_preview1_sock_addr_local(
(int32_t)fd, (int32_t)buf, (int32_t)buf_len);
}
/**
* Returns the remote address to which the socket is connected to.
*
* Note: This is similar to `getpeername` in POSIX
*
* When successful, the contents of the output buffer consist of an IP address,
* either IP4 or IP6.
*/
int32_t
__imported_wasi_snapshot_preview1_sock_addr_remote(int32_t arg0, int32_t arg1,
int32_t arg2)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_addr_remote")));
static inline __wasi_errno_t
__wasi_sock_addr_remote(__wasi_fd_t fd, uint8_t *buf, __wasi_size_t buf_len)
{
return (__wasi_errno_t)__imported_wasi_snapshot_preview1_sock_addr_remote(
(int32_t)fd, (int32_t)buf, (int32_t)buf_len);
}
/**
* Resolves a hostname and a port to one or more IP addresses. Port is optional
* and you can pass 0 (zero) in most cases, it is used a hint for protocol.
*
* Note: This is similar to `getaddrinfo` in POSIX
*
* When successful, the contents of the output buffer consist of a sequence of
* IPv4 and/or IPv6 addresses. Each address entry consists of a addr_t object.
*
* This function fills the output buffer as much as possible, potentially
* truncating the last address entry. It is advisable that the buffer is
*/
int32_t
__imported_wasi_snapshot_preview1_addr_resolve(int32_t arg0, int32_t arg1,
int32_t arg2, int32_t arg3,
int32_t arg4)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("addr_resolve")));
static inline __wasi_errno_t
__wasi_addr_resolve(__wasi_fd_t fd, const char *host, __wasi_ip_port_t port,
uint8_t *buf, __wasi_size_t size)
{
return (__wasi_errno_t)__imported_wasi_snapshot_preview1_addr_resolve(
(int32_t)fd, (int32_t)host, (int32_t)port, (int32_t)buf, (int32_t)size);
}
/**
* Bind a socket
* Note: This is similar to `bind` in POSIX using PF_INET
*/
int32_t
__imported_wasi_snapshot_preview1_sock_bind(int32_t arg0, int32_t arg1)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_bind")));
static inline __wasi_errno_t
__wasi_sock_bind(__wasi_fd_t fd, __wasi_addr_t *addr)
{
return (__wasi_errno_t)__imported_wasi_snapshot_preview1_sock_bind(
(int32_t)fd, (int32_t)addr);
}
/**
* Close a socket (this is an alias for `fd_close`)
* Note: This is similar to `close` in POSIX.
*/
int32_t
__imported_wasi_snapshot_preview1_sock_close(int32_t arg0)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_close")));
static inline __wasi_errno_t
__wasi_sock_close(__wasi_fd_t fd)
{
return (__wasi_errno_t)__imported_wasi_snapshot_preview1_sock_close(
(int32_t)fd);
}
/**
* Initiate a connection on a socket to the specified address
* Note: This is similar to `connect` in POSIX
*/
int32_t
__imported_wasi_snapshot_preview1_sock_connect(int32_t arg0, int32_t arg1)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_connect")));
static inline __wasi_errno_t
__wasi_sock_connect(__wasi_fd_t fd, __wasi_addr_t *addr)
{
return (__wasi_errno_t)__imported_wasi_snapshot_preview1_sock_connect(
(int32_t)fd, (int32_t)addr);
}
/**
* Retrieve the size of the receive buffer
* Note: This is similar to `getsockopt` in POSIX for SO_RCVBUF
*/
int32_t
__imported_wasi_snapshot_preview1_sock_get_recv_buf_size(int32_t arg0,
int32_t arg1)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_get_recv_buf_size")));
static inline __wasi_errno_t
__wasi_sock_get_recv_buf_size(__wasi_fd_t fd, __wasi_size_t *size)
{
return (__wasi_errno_t)
__imported_wasi_snapshot_preview1_sock_get_recv_buf_size((int32_t)fd,
(int32_t)size);
}
/**
* Retrieve status of address reuse on a socket
* Note: This is similar to `getsockopt` in POSIX for SO_REUSEADDR
*/
int32_t
__imported_wasi_snapshot_preview1_sock_get_reuse_addr(int32_t arg0,
int32_t arg1)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_get_reuse_addr")));
static inline __wasi_errno_t
__wasi_sock_get_reuse_addr(__wasi_fd_t fd, uint8_t *reuse)
{
return (__wasi_errno_t)
__imported_wasi_snapshot_preview1_sock_get_reuse_addr((int32_t)fd,
(int32_t)reuse);
}
/**
* Retrieve status of port reuse on a socket
* Note: This is similar to `getsockopt` in POSIX for SO_REUSEPORT
*/
int32_t
__imported_wasi_snapshot_preview1_sock_get_reuse_port(int32_t arg0,
int32_t arg1)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_get_reuse_port")));
static inline __wasi_errno_t
__wasi_sock_get_reuse_port(__wasi_fd_t fd, int8_t *reuse)
{
return (__wasi_errno_t)
__imported_wasi_snapshot_preview1_sock_get_reuse_port((int32_t)fd,
(int32_t)reuse);
}
/**
* Retrieve the size of the send buffer
* Note: This is similar to `getsockopt` in POSIX for SO_SNDBUF
*/
int32_t
__imported_wasi_snapshot_preview1_sock_get_send_buf_size(int32_t arg0,
int32_t arg1)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_get_send_buf_size")));
static inline __wasi_errno_t
__wasi_sock_get_send_buf_size(__wasi_fd_t fd, __wasi_size_t *size)
{
return (__wasi_errno_t)
__imported_wasi_snapshot_preview1_sock_get_send_buf_size((int32_t)fd,
(int32_t)size);
}
/**
* Listen for connections on a socket
* Note: This is similar to `listen`
*/
int32_t
__imported_wasi_snapshot_preview1_sock_listen(int32_t arg0, int32_t arg1)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_listen")));
static inline __wasi_errno_t
__wasi_sock_listen(__wasi_fd_t fd, __wasi_size_t backlog)
{
return (__wasi_errno_t)__imported_wasi_snapshot_preview1_sock_listen(
(int32_t)fd, (int32_t)backlog);
}
/**
* Open a socket
* The first argument to this function is a handle to an
* address pool. The address pool determines what actions can
* be performed and at which addresses they can be performed to.
* The address pool cannot be re-assigned. You will need to close
* the socket and open a new one to use a different address pool.
* Note: This is similar to `socket` in POSIX using PF_INET
*/
int32_t
__imported_wasi_snapshot_preview1_sock_open(int32_t arg0, int32_t arg1,
int32_t arg2, int32_t arg3)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_open")));
static inline __wasi_errno_t
__wasi_sock_open(__wasi_fd_t fd, __wasi_address_family_t af,
__wasi_sock_type_t socktype, __wasi_fd_t *sockfd)
{
return (__wasi_errno_t)__imported_wasi_snapshot_preview1_sock_open(
(int32_t)fd, (int32_t)af, (int32_t)socktype, (int32_t)sockfd);
}
/**
* Set size of receive buffer
* Note: This is similar to `setsockopt` in POSIX for SO_RCVBUF
*/
int32_t
__imported_wasi_snapshot_preview1_sock_set_recv_buf_size(int32_t arg0,
int32_t arg1)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_set_recv_buf_size")));
static inline __wasi_errno_t
__wasi_sock_set_recv_buf_size(__wasi_fd_t fd, __wasi_size_t size)
{
return (__wasi_errno_t)
__imported_wasi_snapshot_preview1_sock_set_recv_buf_size((int32_t)fd,
(int32_t)size);
}
/**
* Enable/disable address reuse on a socket
* Note: This is similar to `setsockopt` in POSIX for SO_REUSEADDR
*/
int32_t
__imported_wasi_snapshot_preview1_sock_set_reuse_addr(int32_t arg0,
int32_t arg1)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_set_reuse_addr")));
static inline __wasi_errno_t
__wasi_sock_set_reuse_addr(__wasi_fd_t fd, uint8_t reuse)
{
return (__wasi_errno_t)
__imported_wasi_snapshot_preview1_sock_set_reuse_addr((int32_t)fd,
(int32_t)reuse);
}
/**
* Enable port reuse on a socket
* Note: This is similar to `setsockopt` in POSIX for SO_REUSEPORT
*/
int32_t
__imported_wasi_snapshot_preview1_sock_set_reuse_port(int32_t arg0,
int32_t arg1)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_set_reuse_port")));
static inline __wasi_errno_t
__wasi_sock_set_reuse_port(__wasi_fd_t fd, uint8_t reuse)
{
return (__wasi_errno_t)
__imported_wasi_snapshot_preview1_sock_set_reuse_port((int32_t)fd,
(int32_t)reuse);
}
/**
* Set size of send buffer
* Note: This is similar to `setsockopt` in POSIX for SO_SNDBUF
*/
int32_t
__imported_wasi_snapshot_preview1_sock_set_send_buf_size(int32_t arg0)
__attribute__((__import_module__("wasi_snapshot_preview1"),
__import_name__("sock_set_send_buf_size")));
static inline __wasi_errno_t
__wasi_sock_set_send_buf_size(__wasi_fd_t fd)
{
return (__wasi_errno_t)
__imported_wasi_snapshot_preview1_sock_set_send_buf_size((int32_t)fd);
}
/**
* TODO: modify recv() and send()
* since don't want to re-compile the wasi-libc,
* we tend to keep original implentations of recv() and send().
*/
#endif

View File

@ -0,0 +1,9 @@
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
cmake_minimum_required (VERSION 2.8...3.16)
project(socket_wasi_ext)
add_library(${PROJECT_NAME} STATIC ${CMAKE_CURRENT_LIST_DIR}/src/wasi/wasi_socket_ext.c)
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/inc/)

View File

@ -0,0 +1,180 @@
/*
* Copyright (C) 2019 Intel Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/socket.h>
#include <wasi/api.h>
#include <wasi_socket_ext.h>
#define HANDLE_ERROR(error) \
if (error != __WASI_ERRNO_SUCCESS) { \
errno = error; \
return -1; \
}
/* addr_num and port are in network order */
static void
ipv4_addr_to_wasi_addr(uint32_t addr_num, uint16_t port, __wasi_addr_t *out)
{
out->kind = IPv4;
out->addr.ip4.port = ntohs(port);
out->addr.ip4.addr.n3 = (addr_num & 0xFF000000) >> 24;
out->addr.ip4.addr.n2 = (addr_num & 0x00FF0000) >> 16;
out->addr.ip4.addr.n1 = (addr_num & 0x0000FF00) >> 8;
out->addr.ip4.addr.n0 = (addr_num & 0x000000FF);
}
static __wasi_errno_t
sockaddr_to_wasi_addr(const struct sockaddr *sock_addr, socklen_t addrlen,
__wasi_addr_t *wasi_addr)
{
__wasi_errno_t ret = __WASI_ERRNO_SUCCESS;
if (AF_INET == sock_addr->sa_family) {
assert(sizeof(struct sockaddr_in) == addrlen);
ipv4_addr_to_wasi_addr(
((struct sockaddr_in *)sock_addr)->sin_addr.s_addr,
((struct sockaddr_in *)sock_addr)->sin_port, wasi_addr);
}
else if (AF_INET6 == sock_addr->sa_family) {
// TODO: IPV6
ret = __WASI_ERRNO_AFNOSUPPORT;
}
else {
ret = __WASI_ERRNO_AFNOSUPPORT;
}
return ret;
}
static __wasi_errno_t
sock_addr_remote(__wasi_fd_t fd, struct sockaddr *sock_addr, socklen_t *addrlen)
{
__wasi_addr_t wasi_addr = { 0 };
__wasi_errno_t error;
error =
__wasi_sock_addr_remote(fd, (uint8_t *)&wasi_addr, sizeof(wasi_addr));
if (__WASI_ERRNO_SUCCESS != error) {
return error;
}
if (IPv4 == wasi_addr.kind) {
struct sockaddr_in sock_addr_in = { 0 };
sock_addr_in.sin_family = AF_INET;
sock_addr_in.sin_addr.s_addr = (wasi_addr.addr.ip4.addr.n3 << 24)
| (wasi_addr.addr.ip4.addr.n2 << 16)
| (wasi_addr.addr.ip4.addr.n1 << 8)
| wasi_addr.addr.ip4.addr.n0;
sock_addr_in.sin_port = htons(wasi_addr.addr.ip4.port);
memcpy(sock_addr, &sock_addr_in, sizeof(sock_addr_in));
*addrlen = sizeof(sock_addr_in);
}
else if (IPv6 == wasi_addr.kind) {
// TODO: IPV6
return __WASI_ERRNO_AFNOSUPPORT;
}
else {
return __WASI_ERRNO_AFNOSUPPORT;
}
return __WASI_ERRNO_SUCCESS;
}
int
accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
__wasi_addr_t wasi_addr = { 0 };
__wasi_fd_t new_sockfd;
__wasi_errno_t error;
error = __wasi_sock_accept(sockfd, &new_sockfd);
HANDLE_ERROR(error)
// error = sock_addr_remote(new_sockfd, addr, addrlen);
// HANDLE_ERROR(error)
*addrlen = 0;
return new_sockfd;
}
int
bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
__wasi_addr_t wasi_addr = { 0 };
__wasi_errno_t error;
error = sockaddr_to_wasi_addr(addr, addrlen, &wasi_addr);
HANDLE_ERROR(error)
error = __wasi_sock_bind(sockfd, &wasi_addr);
HANDLE_ERROR(error)
return __WASI_ERRNO_SUCCESS;
}
int
connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
__wasi_addr_t wasi_addr = { 0 };
__wasi_errno_t error;
error = sockaddr_to_wasi_addr(addr, addrlen, &wasi_addr);
HANDLE_ERROR(error)
error = __wasi_sock_connect(sockfd, &wasi_addr);
HANDLE_ERROR(error)
return __WASI_ERRNO_SUCCESS;
}
int
listen(int sockfd, int backlog)
{
__wasi_errno_t error = __wasi_sock_listen(sockfd, backlog);
HANDLE_ERROR(error)
return __WASI_ERRNO_SUCCESS;
}
int
socket(int domain, int type, int protocol)
{
// the stub of address pool fd
__wasi_fd_t poolfd = -1;
__wasi_fd_t sockfd;
__wasi_errno_t error;
__wasi_address_family_t af;
__wasi_sock_type_t socktype;
if (AF_INET == domain) {
af = INET4;
}
else if (AF_INET6 == domain) {
af = INET6;
}
else {
return __WASI_ERRNO_NOPROTOOPT;
}
if (SOCK_DGRAM == type) {
socktype = SOCKET_DGRAM;
}
else if (SOCK_STREAM == type) {
socktype = SOCKET_STREAM;
}
else {
return __WASI_ERRNO_NOPROTOOPT;
}
error = __wasi_sock_open(poolfd, af, socktype, &sockfd);
HANDLE_ERROR(error)
return sockfd;
}