Skip to content

Commit

Permalink
add initial vsock support
Browse files Browse the repository at this point in the history
This adds the ability to create and use vsock sockets, which can serve
as a communication mechanism between multiple VMs or VM and host.

VSOCK are not available everywhere, so this feature is guarded by the
USE_VSOCK flags and test to check vsock functionality requires the
local CID feature, available on Linux 5.6+ with the vsock_loopback
driver.

Signed-off-by: Petre Eftime <[email protected]>
  • Loading branch information
Petre Eftime committed Jul 16, 2020
1 parent 75ae77f commit 687a558
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 6 deletions.
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ if (WIN32)
set(TLS_STACK_DETERMINED ON)

elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Android")
option(USE_VSOCK
"Build in support for VSOCK sockets"
OFF)

file(GLOB AWS_IO_OS_HEADERS
)

Expand Down Expand Up @@ -194,6 +198,10 @@ if (BUILD_RELOCATABLE_BINARIES)
target_compile_definitions(${PROJECT_NAME} PRIVATE "-DCOMPAT_MODE")
endif()

if (USE_VSOCK)
target_compile_definitions(${PROJECT_NAME} PUBLIC "-DUSE_VSOCK")
endif()

target_include_directories(${PROJECT_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,7 @@ notifications.
AWS_SOCKET_IPV4,
AWS_SOCKET_IPV6,
AWS_SOCKET_LOCAL,
AWS_SOCKET_VSOCK,
} aws_socket_domain;

`AWS_SOCKET_IPV4` means an IPv4 address will be used.
Expand All @@ -681,6 +682,7 @@ notifications.
AWS_SOCKET_DGRAM
} aws_socket_type;

`AWS_SOCKET_VSOCK` means a CID address will be used. Note: VSOCK is currently only available on Linux with an appropriate VSOCK kernel driver installed. `-DUSE_VSOCK` needs to be passed during compilation to enable VSOCK support.

`AWS_SOCKET_STREAM` is TCP or a connection oriented socket.

Expand Down Expand Up @@ -709,7 +711,7 @@ with it.
char port[10];
};

`address` can be either an IPv4 or IPv6 address. This can be used for UDP or TCP.
`address` can be either an IPv4, IPv6 or VSOCK CID address. This can be used for UDP or TCP.
`socket_name` is only used in LOCAL mode.
`port` can be used for TCP or UDP.

Expand Down
12 changes: 7 additions & 5 deletions include/aws/io/socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ enum aws_socket_domain {
AWS_SOCKET_IPV6,
/* Unix domain sockets (or at least something like them) */
AWS_SOCKET_LOCAL,
/* VSOCK used in inter-VM communication */
AWS_SOCKET_VSOCK,
};

enum aws_socket_type {
Expand All @@ -22,7 +24,7 @@ enum aws_socket_type {
AWS_SOCKET_STREAM,
/* A datagram socket is connectionless and sends unreliable messages.
* This means UDP when used with IPV4/6.
* LOCAL sockets are not compatible with DGRAM.*/
* LOCAL and VSOCK sockets are not compatible with DGRAM.*/
AWS_SOCKET_DGRAM,
};

Expand Down Expand Up @@ -152,7 +154,7 @@ AWS_IO_API void aws_socket_clean_up(struct aws_socket *socket);
* Connects to a remote endpoint. In UDP, this simply binds the socket to a remote address for use with
* `aws_socket_write()`, and if the operation is successful, the socket can immediately be used for write operations.
*
* In TCP amd LOCAL, this function will not block. If the return value is successful, then you must wait on the
* In TCP, LOCAL and VSOCK this function will not block. If the return value is successful, then you must wait on the
* `on_connection_result()` callback to be invoked before using the socket.
*
* If an event_loop is provided for UDP sockets, a notification will be sent on
Expand All @@ -175,12 +177,12 @@ AWS_IO_API int aws_socket_connect(
AWS_IO_API int aws_socket_bind(struct aws_socket *socket, const struct aws_socket_endpoint *local_endpoint);

/**
* TCP and LOCAL only. Sets up the socket to listen on the address bound to in `aws_socket_bind()`.
* TCP, LOCAL and VSOCK only. Sets up the socket to listen on the address bound to in `aws_socket_bind()`.
*/
AWS_IO_API int aws_socket_listen(struct aws_socket *socket, int backlog_size);

/**
* TCP and LOCAL only. The socket will begin accepting new connections. This is an asynchronous operation. New
* TCP, LOCAL and VSOCK only. The socket will begin accepting new connections. This is an asynchronous operation. New
* connections or errors will arrive via the `on_accept_result` callback.
*
* aws_socket_bind() and aws_socket_listen() must be called before calling this function.
Expand All @@ -192,7 +194,7 @@ AWS_IO_API int aws_socket_start_accept(
void *user_data);

/**
* TCP and LOCAL only. The listening socket will stop accepting new connections.
* TCP, LOCAL and VSOCK only. The listening socket will stop accepting new connections.
* It is safe to call `aws_socket_start_accept()` again after
* this operation. This can be called from any thread but be aware,
* on some platforms, if you call this from outside of the current event loop's thread, it will block
Expand Down
63 changes: 63 additions & 0 deletions source/posix/socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@
# define O_CLOEXEC 02000000
#endif

#ifdef USE_VSOCK
# if defined(__linux__) && defined(AF_VSOCK)
# include <linux/vm_sockets.h>
# else
# error "USE_VSOCK not supported on current platform"
# endif
#endif

/* other than CONNECTED_READ | CONNECTED_WRITE
* a socket is only in one of these states at a time. */
enum socket_state {
Expand All @@ -61,6 +69,10 @@ static int s_convert_domain(enum aws_socket_domain domain) {
return AF_INET6;
case AWS_SOCKET_LOCAL:
return AF_UNIX;
#ifdef USE_VSOCK
case AWS_SOCKET_VSOCK:
return AF_VSOCK;
#endif
default:
AWS_ASSERT(0);
return AF_INET;
Expand Down Expand Up @@ -498,9 +510,46 @@ struct socket_address {
struct sockaddr_in addr_in;
struct sockaddr_in6 addr_in6;
struct sockaddr_un un_addr;
#ifdef USE_VSOCK
struct sockaddr_vm vm_addr;
#endif
} sock_addr_types;
};

#ifdef USE_VSOCK
/** Convert a string to a VSOCK CID. Respects the calling convetion of inet_pton:
* 0 on error, 1 on success. */
static int parse_cid(const char *cid_str, unsigned int *value) {
if (cid_str == NULL || value == NULL) {
errno = EINVAL;
return 0;
}
/* strtoll returns 0 as both error and correct value */
errno = 0;
/* unsigned long long to handle edge cases in convention explicitly */
long long cid = strtoll(cid_str, NULL, 10);
if (errno != 0) {
return 0;
}

/* -1U means any, so it's a valid value, but it needs to be converted to
* unsigned int. */
if (cid == -1) {
*value = VMADDR_CID_ANY;
return 1;
}

if (cid < 0 || cid > UINT_MAX) {
errno = ERANGE;
return 0;
}

/* cast is safe here, edge cases already checked */
*value = (unsigned int)cid;
return 1;
}
#endif

int aws_socket_connect(
struct aws_socket *socket,
const struct aws_socket_endpoint *remote_endpoint,
Expand Down Expand Up @@ -551,6 +600,13 @@ int aws_socket_connect(
address.sock_addr_types.un_addr.sun_family = AF_UNIX;
strncpy(address.sock_addr_types.un_addr.sun_path, remote_endpoint->address, AWS_ADDRESS_MAX_LEN);
sock_size = sizeof(address.sock_addr_types.un_addr);
#ifdef USE_VSOCK
} else if (socket->options.domain == AWS_SOCKET_VSOCK) {
pton_err = parse_cid(remote_endpoint->address, &address.sock_addr_types.vm_addr.svm_cid);
address.sock_addr_types.vm_addr.svm_family = AF_VSOCK;
address.sock_addr_types.vm_addr.svm_port = (unsigned int)remote_endpoint->port;
sock_size = sizeof(address.sock_addr_types.vm_addr);
#endif
} else {
AWS_ASSERT(0);
return aws_raise_error(AWS_IO_SOCKET_UNSUPPORTED_ADDRESS_FAMILY);
Expand Down Expand Up @@ -718,6 +774,13 @@ int aws_socket_bind(struct aws_socket *socket, const struct aws_socket_endpoint
address.sock_addr_types.un_addr.sun_family = AF_UNIX;
strncpy(address.sock_addr_types.un_addr.sun_path, local_endpoint->address, AWS_ADDRESS_MAX_LEN);
sock_size = sizeof(address.sock_addr_types.un_addr);
#ifdef USE_VSOCK
} else if (socket->options.domain == AWS_SOCKET_VSOCK) {
pton_err = parse_cid(local_endpoint->address, &address.sock_addr_types.vm_addr.svm_cid);
address.sock_addr_types.vm_addr.svm_family = AF_VSOCK;
address.sock_addr_types.vm_addr.svm_port = (unsigned int)local_endpoint->port;
sock_size = sizeof(address.sock_addr_types.vm_addr);
#endif
} else {
AWS_ASSERT(0);
return aws_raise_error(AWS_IO_SOCKET_UNSUPPORTED_ADDRESS_FAMILY);
Expand Down
3 changes: 3 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ add_test_case(udp_socket_communication)
add_test_case(udp_bind_connect_communication)
add_net_test_case(connect_timeout)
add_net_test_case(connect_timeout_cancelation)
if (USE_VSOCK)
add_test_case(vsock_loopback_socket_communication)
endif ()

add_test_case(outgoing_local_sock_errors)
add_test_case(outgoing_tcp_sock_error)
Expand Down
28 changes: 28 additions & 0 deletions tests/socket_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
# pragma warning(disable : 4996) /* sprintf */
#endif

#if USE_VSOCK
# include <linux/vm_sockets.h>
#endif

struct local_listener_args {
struct aws_socket *incoming;
struct aws_mutex *mutex;
Expand Down Expand Up @@ -432,6 +436,30 @@ static int s_test_tcp_socket_communication(struct aws_allocator *allocator, void

AWS_TEST_CASE(tcp_socket_communication, s_test_tcp_socket_communication)

#if defined(USE_VSOCK)
static int s_test_vsock_loopback_socket_communication(struct aws_allocator *allocator, void *ctx) {
/* Without vsock loopback it's difficult to test vsock functionality.
* Also note that having this defined does not guarantee that it's available
* for use and there's no path to figure out dynamically if it can be used. */
# if defined(VMADDR_CID_LOCAL)
(void)ctx;

struct aws_socket_options options;
AWS_ZERO_STRUCT(options);
options.connect_timeout_ms = 3000;
options.type = AWS_SOCKET_STREAM;
options.domain = AWS_SOCKET_VSOCK;

struct aws_socket_endpoint endpoint = {.address = "1" /* VMADDR_CID_LOCAL */, .port = 8127};

return s_test_socket(allocator, &options, &endpoint);
# endif
return 0;
}

AWS_TEST_CASE(vsock_loopback_socket_communication, s_test_vsock_loopback_socket_communication)
#endif

static int s_test_udp_socket_communication(struct aws_allocator *allocator, void *ctx) {
(void)ctx;

Expand Down

0 comments on commit 687a558

Please sign in to comment.