From 4651a150f8718ab35da9dee60e2b41d57e8620f4 Mon Sep 17 00:00:00 2001 From: Ian Campbell Date: Wed, 16 Mar 2016 17:28:08 +0000 Subject: [PATCH] Add nc-vsock utility (for testing AF_VSOCK) Signed-off-by: Ian Campbell --- alpine/Dockerfile | 1 + alpine/packages/Makefile | 3 + alpine/packages/nc-vsock/.gitignore | 1 + alpine/packages/nc-vsock/COMMIT.txt | 20 ++ alpine/packages/nc-vsock/Dockerfile | 10 + alpine/packages/nc-vsock/Makefile | 15 + .../nc-vsock/include/uapi/linux/vm_sockets.h | 161 ++++++++++ alpine/packages/nc-vsock/nc-vsock.c | 302 ++++++++++++++++++ 8 files changed, 513 insertions(+) create mode 100644 alpine/packages/nc-vsock/.gitignore create mode 100644 alpine/packages/nc-vsock/COMMIT.txt create mode 100644 alpine/packages/nc-vsock/Dockerfile create mode 100644 alpine/packages/nc-vsock/Makefile create mode 100644 alpine/packages/nc-vsock/include/uapi/linux/vm_sockets.h create mode 100644 alpine/packages/nc-vsock/nc-vsock.c diff --git a/alpine/Dockerfile b/alpine/Dockerfile index 92a035405..50eabcba1 100644 --- a/alpine/Dockerfile +++ b/alpine/Dockerfile @@ -52,6 +52,7 @@ COPY packages/hvtools/hv_set_ifconfig /sbin/ COPY packages/userns/etc /etc/ COPY packages/userns/groupadd /usr/sbin COPY packages/userns/useradd /usr/sbin +COPY packages/nc-vsock/nc-vsock /usr/bin RUN \ rc-update add swap boot && \ diff --git a/alpine/packages/Makefile b/alpine/packages/Makefile index 309752afc..7931d1709 100644 --- a/alpine/packages/Makefile +++ b/alpine/packages/Makefile @@ -5,6 +5,7 @@ all: $(MAKE) -C hupper OS=linux $(MAKE) -C hvtools OS=linux $(MAKE) -C docker OS=Linux + $(MAKE) -C nc-vsock OS=linux arm: $(MAKE) -C proxy OS=linux ARCH=arm @@ -12,6 +13,7 @@ arm: $(MAKE) -C mdnstool OS=linux ARCH=arm $(MAKE) -C hupper OS=linux ARCH=arm $(MAKE) -C docker OS=Linux ARCH=arm + $(MAKE) -C nc-vsock OS=linux ARCH=arm clean: $(MAKE) -C proxy clean @@ -20,3 +22,4 @@ clean: $(MAKE) -C docker clean $(MAKE) -C hupper clean $(MAKE) -C hvtools clean + $(MAKE) -C nc-vsock clean diff --git a/alpine/packages/nc-vsock/.gitignore b/alpine/packages/nc-vsock/.gitignore new file mode 100644 index 000000000..41edd196d --- /dev/null +++ b/alpine/packages/nc-vsock/.gitignore @@ -0,0 +1 @@ +nc-vsock diff --git a/alpine/packages/nc-vsock/COMMIT.txt b/alpine/packages/nc-vsock/COMMIT.txt new file mode 100644 index 000000000..6c7352f5c --- /dev/null +++ b/alpine/packages/nc-vsock/COMMIT.txt @@ -0,0 +1,20 @@ +From https://github.com/stefanha/linux vsock-extras +f442d29db40a1999d139dccf110f3b590a0ed5b8:nc-vsock.c + +including include/uapi/linux/vm_sockets.h, patched with: + +--- /home/ijc/x 2016-03-17 09:31:23.288944691 +0000 ++++ alpine/packages/nc-vsock/include/uapi/linux/vm_sockets.h 2016-03-17 09:30:07.916069307 +0000 +@@ -16,7 +16,12 @@ + #ifndef _UAPI_VM_SOCKETS_H + #define _UAPI_VM_SOCKETS_H + ++#ifdef __KERNEL__ + #include ++#else ++#define __kernel_sa_family_t sa_family_t ++#include ++#endif + + /* Option name for STREAM socket buffer size. Use as the option name in + * setsockopt(3) or getsockopt(3) to set or get an unsigned long long that diff --git a/alpine/packages/nc-vsock/Dockerfile b/alpine/packages/nc-vsock/Dockerfile new file mode 100644 index 000000000..9a86af443 --- /dev/null +++ b/alpine/packages/nc-vsock/Dockerfile @@ -0,0 +1,10 @@ +FROM alpine:3.3 + +RUN apk update && apk upgrade && apk add alpine-sdk + +RUN mkdir -p /nc-vsock +WORKDIR /nc-vsock + +COPY . /nc-vsock + +RUN make nc-vsock diff --git a/alpine/packages/nc-vsock/Makefile b/alpine/packages/nc-vsock/Makefile new file mode 100644 index 000000000..f58fab3ec --- /dev/null +++ b/alpine/packages/nc-vsock/Makefile @@ -0,0 +1,15 @@ +.PHONY: all + +DEPS=nc-vsock.c + +all: Dockerfile $(DEPS) + docker build -t nc-vsock:build . + docker run --rm nc-vsock:build cat nc-vsock > nc-vsock + chmod 755 nc-vsock + +nc-vsock: $(DEPS) + gcc -Wall -Werror -o nc-vsock nc-vsock.c + +clean: + rm -f nc-vsock + docker images -q nc-vsock:build | xargs docker rmi -f diff --git a/alpine/packages/nc-vsock/include/uapi/linux/vm_sockets.h b/alpine/packages/nc-vsock/include/uapi/linux/vm_sockets.h new file mode 100644 index 000000000..41934a185 --- /dev/null +++ b/alpine/packages/nc-vsock/include/uapi/linux/vm_sockets.h @@ -0,0 +1,161 @@ +/* + * VMware vSockets Driver + * + * Copyright (C) 2007-2013 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#ifndef _UAPI_VM_SOCKETS_H +#define _UAPI_VM_SOCKETS_H + +#ifdef __KERNEL__ +#include +#else +#define __kernel_sa_family_t sa_family_t +#include +#endif + +/* Option name for STREAM socket buffer size. Use as the option name in + * setsockopt(3) or getsockopt(3) to set or get an unsigned long long that + * specifies the size of the buffer underlying a vSockets STREAM socket. + * Value is clamped to the MIN and MAX. + */ + +#define SO_VM_SOCKETS_BUFFER_SIZE 0 + +/* Option name for STREAM socket minimum buffer size. Use as the option name + * in setsockopt(3) or getsockopt(3) to set or get an unsigned long long that + * specifies the minimum size allowed for the buffer underlying a vSockets + * STREAM socket. + */ + +#define SO_VM_SOCKETS_BUFFER_MIN_SIZE 1 + +/* Option name for STREAM socket maximum buffer size. Use as the option name + * in setsockopt(3) or getsockopt(3) to set or get an unsigned long long + * that specifies the maximum size allowed for the buffer underlying a + * vSockets STREAM socket. + */ + +#define SO_VM_SOCKETS_BUFFER_MAX_SIZE 2 + +/* Option name for socket peer's host-specific VM ID. Use as the option name + * in getsockopt(3) to get a host-specific identifier for the peer endpoint's + * VM. The identifier is a signed integer. + * Only available for hypervisor endpoints. + */ + +#define SO_VM_SOCKETS_PEER_HOST_VM_ID 3 + +/* Option name for determining if a socket is trusted. Use as the option name + * in getsockopt(3) to determine if a socket is trusted. The value is a + * signed integer. + */ + +#define SO_VM_SOCKETS_TRUSTED 5 + +/* Option name for STREAM socket connection timeout. Use as the option name + * in setsockopt(3) or getsockopt(3) to set or get the connection + * timeout for a STREAM socket. + */ + +#define SO_VM_SOCKETS_CONNECT_TIMEOUT 6 + +/* Option name for using non-blocking send/receive. Use as the option name + * for setsockopt(3) or getsockopt(3) to set or get the non-blocking + * transmit/receive flag for a STREAM socket. This flag determines whether + * send() and recv() can be called in non-blocking contexts for the given + * socket. The value is a signed integer. + * + * This option is only relevant to kernel endpoints, where descheduling the + * thread of execution is not allowed, for example, while holding a spinlock. + * It is not to be confused with conventional non-blocking socket operations. + * + * Only available for hypervisor endpoints. + */ + +#define SO_VM_SOCKETS_NONBLOCK_TXRX 7 + +/* The vSocket equivalent of INADDR_ANY. This works for the svm_cid field of + * sockaddr_vm and indicates the context ID of the current endpoint. + */ + +#define VMADDR_CID_ANY -1U + +/* Bind to any available port. Works for the svm_port field of + * sockaddr_vm. + */ + +#define VMADDR_PORT_ANY -1U + +/* Use this as the destination CID in an address when referring to the + * hypervisor. VMCI relies on it being 0, but this would be useful for other + * transports too. + */ + +#define VMADDR_CID_HYPERVISOR 0 + +/* This CID is specific to VMCI and can be considered reserved (even VMCI + * doesn't use it anymore, it's a legacy value from an older release). + */ + +#define VMADDR_CID_RESERVED 1 + +/* Use this as the destination CID in an address when referring to the host + * (any process other than the hypervisor). VMCI relies on it being 2, but + * this would be useful for other transports too. + */ + +#define VMADDR_CID_HOST 2 + +/* Invalid vSockets version. */ + +#define VM_SOCKETS_INVALID_VERSION -1U + +/* The epoch (first) component of the vSockets version. A single byte + * representing the epoch component of the vSockets version. + */ + +#define VM_SOCKETS_VERSION_EPOCH(_v) (((_v) & 0xFF000000) >> 24) + +/* The major (second) component of the vSockets version. A single byte + * representing the major component of the vSockets version. Typically + * changes for every major release of a product. + */ + +#define VM_SOCKETS_VERSION_MAJOR(_v) (((_v) & 0x00FF0000) >> 16) + +/* The minor (third) component of the vSockets version. Two bytes representing + * the minor component of the vSockets version. + */ + +#define VM_SOCKETS_VERSION_MINOR(_v) (((_v) & 0x0000FFFF)) + +/* Address structure for vSockets. The address family should be set to + * AF_VSOCK. The structure members should all align on their natural + * boundaries without resorting to compiler packing directives. The total size + * of this structure should be exactly the same as that of struct sockaddr. + */ + +struct sockaddr_vm { + __kernel_sa_family_t svm_family; + unsigned short svm_reserved1; + unsigned int svm_port; + unsigned int svm_cid; + unsigned char svm_zero[sizeof(struct sockaddr) - + sizeof(sa_family_t) - + sizeof(unsigned short) - + sizeof(unsigned int) - sizeof(unsigned int)]; +}; + +#define IOCTL_VM_SOCKETS_GET_LOCAL_CID _IO(7, 0xb9) + +#endif /* _UAPI_VM_SOCKETS_H */ diff --git a/alpine/packages/nc-vsock/nc-vsock.c b/alpine/packages/nc-vsock/nc-vsock.c new file mode 100644 index 000000000..3dd86dda1 --- /dev/null +++ b/alpine/packages/nc-vsock/nc-vsock.c @@ -0,0 +1,302 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "include/uapi/linux/vm_sockets.h" + +static int parse_cid(const char *cid_str) +{ + char *end = NULL; + long cid = strtol(cid_str, &end, 10); + if (cid_str != end && *end == '\0') { + return cid; + } else { + fprintf(stderr, "invalid cid: %s\n", cid_str); + return -1; + } +} + +static int parse_port(const char *port_str) +{ + char *end = NULL; + long port = strtol(port_str, &end, 10); + if (port_str != end && *end == '\0') { + return port; + } else { + fprintf(stderr, "invalid port number: %s\n", port_str); + return -1; + } +} + +static int vsock_listen(const char *port_str) +{ + int listen_fd; + int client_fd; + struct sockaddr_vm sa_listen = { + .svm_family = AF_VSOCK, + .svm_cid = VMADDR_CID_ANY, + }; + struct sockaddr_vm sa_client; + socklen_t socklen_client = sizeof(sa_client); + int port = parse_port(port_str); + if (port < 0) { + return -1; + } + + sa_listen.svm_port = port; + + listen_fd = socket(AF_VSOCK, SOCK_STREAM, 0); + if (listen_fd < 0) { + perror("socket"); + return -1; + } + + if (bind(listen_fd, (struct sockaddr*)&sa_listen, sizeof(sa_listen)) != 0) { + perror("bind"); + close(listen_fd); + return -1; + } + + if (listen(listen_fd, 1) != 0) { + perror("listen"); + close(listen_fd); + return -1; + } + + client_fd = accept(listen_fd, (struct sockaddr*)&sa_client, &socklen_client); + if (client_fd < 0) { + perror("accept"); + close(listen_fd); + return -1; + } + + fprintf(stderr, "Connection from cid %u port %u...\n", sa_client.svm_cid, sa_client.svm_port); + + close(listen_fd); + return client_fd; +} + +static int tcp_connect(const char *node, const char *service) +{ + int fd; + int ret; + const struct addrinfo hints = { + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + }; + struct addrinfo *res = NULL; + struct addrinfo *addrinfo; + + ret = getaddrinfo(node, service, &hints, &res); + if (ret != 0) { + fprintf(stderr, "getaddrinfo failed: %s\n", gai_strerror(ret)); + return -1; + } + + for (addrinfo = res; addrinfo; addrinfo = addrinfo->ai_next) { + fd = socket(addrinfo->ai_family, addrinfo->ai_socktype, addrinfo->ai_protocol); + if (fd < 0) { + perror("socket"); + continue; + } + + if (connect(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) != 0) { + perror("connect"); + close(fd); + continue; + } + + break; + } + + freeaddrinfo(res); + return fd; +} + +static int vsock_connect(const char *cid_str, const char *port_str) +{ + int fd; + int cid; + int port; + struct sockaddr_vm sa = { + .svm_family = AF_VSOCK, + }; + + cid = parse_cid(cid_str); + if (cid < 0) { + return -1; + } + sa.svm_cid = cid; + + port = parse_port(port_str); + if (port < 0) { + return -1; + } + sa.svm_port = port; + + fd = socket(AF_VSOCK, SOCK_STREAM, 0); + if (fd < 0) { + perror("socket"); + return -1; + } + + if (connect(fd, (struct sockaddr*)&sa, sizeof(sa)) != 0) { + perror("connect"); + close(fd); + return -1; + } + + return fd; +} + +static int get_fds(int argc, char **argv, int fds[2]) +{ + fds[0] = STDIN_FILENO; + fds[1] = -1; + + if (argc >= 3 && strcmp(argv[1], "-l") == 0) { + fds[1] = vsock_listen(argv[2]); + if (fds[1] < 0) { + return -1; + } + + if (argc == 6 && strcmp(argv[3], "-t") == 0) { + fds[0] = tcp_connect(argv[4], argv[5]); + if (fds[0] < 0) { + return -1; + } + } + return 0; + } else if (argc == 3) { + fds[1] = vsock_connect(argv[1], argv[2]); + if (fds[1] < 0) { + return -1; + } + return 0; + } else { + fprintf(stderr, "usage: %s [-l [-t ] | ]\n", argv[0]); + return -1; + } +} + +static void set_nonblock(int fd, bool enable) +{ + int ret; + int flags; + + ret = fcntl(fd, F_GETFL); + if (ret < 0) { + perror("fcntl"); + return; + } + + flags = ret & ~O_NONBLOCK; + if (enable) { + flags |= O_NONBLOCK; + } + + fcntl(fd, F_SETFL, flags); +} + +static int xfer_data(int in_fd, int out_fd) +{ + char buf[4096]; + char *send_ptr = buf; + ssize_t nbytes; + ssize_t remaining; + + nbytes = read(in_fd, buf, sizeof(buf)); + if (nbytes <= 0) { + return -1; + } + + remaining = nbytes; + while (remaining > 0) { + nbytes = write(out_fd, send_ptr, remaining); + if (nbytes < 0 && errno == EAGAIN) { + nbytes = 0; + } else if (nbytes <= 0) { + return -1; + } + + if (remaining > nbytes) { + /* Wait for fd to become writeable again */ + for (;;) { + fd_set wfds; + FD_ZERO(&wfds); + FD_SET(out_fd, &wfds); + if (select(out_fd + 1, NULL, &wfds, NULL, NULL) < 0) { + if (errno == EINTR) { + continue; + } else { + perror("select"); + return -1; + } + } + + if (FD_ISSET(out_fd, &wfds)) { + break; + } + } + } + + send_ptr += nbytes; + remaining -= nbytes; + } + return 0; +} + +static void main_loop(int fds[2]) +{ + fd_set rfds; + int nfds = fds[fds[0] > fds[1] ? 0 : 1] + 1; + + set_nonblock(fds[0], true); + set_nonblock(fds[1], true); + + for (;;) { + FD_ZERO(&rfds); + FD_SET(fds[0], &rfds); + FD_SET(fds[1], &rfds); + + if (select(nfds, &rfds, NULL, NULL, NULL) < 0) { + if (errno == EINTR) { + continue; + } else { + perror("select"); + return; + } + } + + if (FD_ISSET(fds[0], &rfds)) { + if (xfer_data(fds[0], fds[1]) < 0) { + return; + } + } + + if (FD_ISSET(fds[1], &rfds)) { + if (xfer_data(fds[1], fds[0]) < 0) { + return; + } + } + } +} + +int main(int argc, char **argv) +{ + int fds[2]; + + if (get_fds(argc, argv, fds) < 0) { + return EXIT_FAILURE; + } + + main_loop(fds); + return EXIT_SUCCESS; +}