summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2021-03-19 03:17:13 +0000
committerAlyssa Ross <hi@alyssa.is>2021-03-21 14:37:33 +0000
commit20a27f13713bb1c7be9bd82ac659e346f5384a51 (patch)
tree43f5c216753b0031a74d5949c0bc7befeca0b25a
parent38559b5aa5a6e371a5fd8a20367fc142e02945c3 (diff)
downloaducspi-vsock-20a27f13713bb1c7be9bd82ac659e346f5384a51.tar
ucspi-vsock-20a27f13713bb1c7be9bd82ac659e346f5384a51.tar.gz
ucspi-vsock-20a27f13713bb1c7be9bd82ac659e346f5384a51.tar.bz2
ucspi-vsock-20a27f13713bb1c7be9bd82ac659e346f5384a51.tar.lz
ucspi-vsock-20a27f13713bb1c7be9bd82ac659e346f5384a51.tar.xz
ucspi-vsock-20a27f13713bb1c7be9bd82ac659e346f5384a51.tar.zst
ucspi-vsock-20a27f13713bb1c7be9bd82ac659e346f5384a51.zip
Extract vsockserver-socketbinder and vsockserverd
vsockserver-socketbinder creates and listens on a socket, and
vsockserverd accepts connections and sets up file descriptors.

vsockserver previously did both of these things in one big program,
but now it just sets up a command line to run vsockserver-socketbinder
followed by vsockserverd.  Having two seperate programs allows one
program to be used in situations where the other is not
suitable (e.g. using vsockserver-socketbinder to create a socket in
situations where accept behaviour more complex than vsockserverd can
provide is required).

This design is taken from s6[1], which uses the same design for its
s6-ipcserver, s6-ipcserver-socketbinder, and s6-ipcserverd programs.

[1]: https://skarnet.org/software/s6/

Message-Id: <20210319031713.23600-1-hi@alyssa.is>
Reviewed-by: Cole Helbling <cole.e.helbling@outlook.com>
-rw-r--r--.gitignore2
-rw-r--r--Makefile.in14
-rw-r--r--vsockserver-socketbinder.c88
-rw-r--r--vsockserver.c157
-rw-r--r--vsockserverd.c120
5 files changed, 287 insertions, 94 deletions
diff --git a/.gitignore b/.gitignore
index 89a0408..d29518a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,8 @@
 *.o
 *.tmp
 vsockclient
+vsockserver-socketbinder
+vsockserverd
 vsockserver
 config.h
 Makefile
diff --git a/Makefile.in b/Makefile.in
index 3260e85..0e65f1f 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -10,7 +10,7 @@ INSTALL_PROGRAM = $(INSTALL)
 prefix = @PREFIX@
 bindir = @BINDIR@
 
-PROGRAMS = vsockclient vsockserver
+PROGRAMS = vsockclient vsockserver-socketbinder vsockserverd vsockserver
 
 all: $(PROGRAMS)
 .PHONY: all
@@ -26,11 +26,17 @@ config.h: configure
 
 vsockclient: vsockclient.o env.o log.o num.o vsock.o
 	$(CC) $(LDFLAGS) -o $@ $@.o env.o log.o num.o vsock.o $(LDLIBS)
-vsockserver: vsockserver.o env.o log.o num.o vsock.o
-	$(CC) $(LDFLAGS) -o $@ $@.o env.o log.o num.o vsock.o $(LDLIBS)
+vsockserver-socketbinder: vsockserver-socketbinder.o log.o num.o vsock.o
+	$(CC) $(LDFLAGS) -o $@ $@.o log.o num.o vsock.o $(LDLIBS)
+vsockserverd: vsockserverd.o env.o log.o vsock.o
+	$(CC) $(LDFLAGS) -o $@ $@.o env.o log.o vsock.o $(LDLIBS)
+vsockserver: vsockserver.o exec.o log.o
+	$(CC) $(LDFLAGS) -o $@ $@.o exec.o log.o $(LDLIBS)
 
 vsockclient.o: env.h log.h num.h vsock.h
-vsockserver.o: env.h log.h num.h vsock.h
+vsockserver-socketbinder.o: log.h num.h vsock.h
+vsockserverd.o: env.h log.h vsock.h
+vsockserver.o: config.h exec.h log.h
 
 clean:
 	rm -f $(PROGRAMS) *.o
diff --git a/vsockserver-socketbinder.c b/vsockserver-socketbinder.c
new file mode 100644
index 0000000..fdcdfa8
--- /dev/null
+++ b/vsockserver-socketbinder.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2020-2021 Alyssa Ross <hi@alyssa.is>
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdnoreturn.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <linux/vm_sockets.h>
+
+#include "log.h"
+#include "num.h"
+#include "vsock.h"
+
+static const int LISTEN_BACKLOG = 40;
+
+noreturn static void ex_usage(void)
+{
+	if (verbosity)
+		fprintf(stderr, "Usage: %s cid port prog...\n",
+			program_invocation_short_name);
+	exit(EX_USAGE);
+}
+
+int main(int argc, char *argv[])
+{
+	int opt, fd;
+	uint32_t cid, port;
+
+	// A skeleton of an option parser to reject any options that
+	// are given, so we can add options in the future without
+	// worrying about breaking backwards compatibility because
+	// they were previously interpreted as a first argument.
+	while ((opt = getopt(argc, argv, "+")) != -1) {
+		switch (opt) {
+		default:
+			ex_usage();
+		}
+	}
+
+	// Check there are enough positional arguments (two for the
+	// address and at least one to exec into).
+	if (optind > argc - 3)
+		ex_usage();
+
+	// Parse the `cid' argument.
+	if (!strcmp(argv[optind], "-1"))
+		cid = VMADDR_CID_ANY;
+	else if (getu32(argv[optind], 0,  UINT32_MAX, &cid))
+		ex_usage();
+	optind++;
+
+	// Parse the `port' argument.
+	if (!strcmp(argv[optind], "-1"))
+		port = VMADDR_PORT_ANY;
+	else if (getu32(argv[optind], 0, UINT32_MAX, &port))
+		ex_usage();
+	optind++;
+
+	// Set up the socket.
+	fd = socket(AF_VSOCK, SOCK_STREAM, 0);
+	if (fd == -1)
+		diee(EX_OSERR, "socket");
+	if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1)
+		diee(EX_OSERR, "fcntl");
+	if (vsock_bind(fd, cid, port) == -1)
+		diee(EX_OSERR, "bind");
+	if (listen(fd, LISTEN_BACKLOG) == -1)
+		diee(EX_OSERR, "listen");
+
+	// Place the socket at stdout.
+	if (dup2(fd, STDIN_FILENO) == -1)
+		diee(EX_OSERR, "dup2");
+	if (fd != STDIN_FILENO)
+		close(fd);
+
+	// Finally, exec into `prog'.
+	execvp(argv[optind], &argv[optind]);
+	diee(EX_OSERR, "execvp");
+}
diff --git a/vsockserver.c b/vsockserver.c
index 43307d2..f740a8a 100644
--- a/vsockserver.c
+++ b/vsockserver.c
@@ -1,28 +1,20 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
-// SPDX-FileCopyrightText: 2020-2021 Alyssa Ross <hi@alyssa.is>
+// SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
 
 #define _GNU_SOURCE
 
+#include <argz.h>
 #include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <poll.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdnoreturn.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/wait.h>
 #include <sysexits.h>
 #include <unistd.h>
 
-#include <linux/vm_sockets.h>
-
-#include "env.h"
+#include "config.h"
+#include "exec.h"
 #include "log.h"
-#include "num.h"
-#include "vsock.h"
 
 noreturn static void ex_usage(void)
 {
@@ -34,102 +26,87 @@ noreturn static void ex_usage(void)
 
 int main(int argc, char *argv[])
 {
-	bool notify = false;
 	int opt;
-	pid_t child;
-	uint32_t lcid, lport, rcid, rport;
+	bool alloc_failed = false;
+	size_t binder_opts_len = 0, daemon_opts_len = 0;
+	char *binder_opts = NULL, *daemon_opts = NULL;
+
+	// The heavy lifting here is done by vsockserver-socketbinder
+	// and vsockserverd.  All this program does is munge argv
+	// appropriately to set the right options for each of those
+	// programs, and then exec into vsockserver-socketbinder.
+
+	// If allocation fails, we need to keep going until after
+	// we've parsed the arguments, so we know what our verbosity
+	// setting is, and consequently whether we should print an
+	// error message about the allocation failure.
+	alloc_failed |=
+		argz_add(&binder_opts, &binder_opts_len, BINDIR "/vsockserver-socketbinder") ||
+		argz_add(&daemon_opts, &daemon_opts_len, BINDIR "/vsockserverd");
 
 	while ((opt = getopt(argc, argv, "+1qQv")) != -1) {
+		char *arg = NULL;
+
 		switch (opt) {
 		case '1':
-			notify = true;
-			break;
 		case 'q':
 		case 'Q':
 		case 'v':
 			set_verbosity(opt);
+			alloc_failed |=
+				asprintf(&arg, "-%c", opt) == -1 ||
+				argz_add(&daemon_opts, &daemon_opts_len, arg);
+			free(arg);
 			break;
 		default:
 			ex_usage();
 		}
 	}
 
-	// Check there are enough positional arguments (two for the
-	// address and at least one to exec into).
+	// Now that verbosity is set, we can whether we were sitting
+	// on an allocation failure.
+	if (alloc_failed)
+		diee(EX_OSERR, "malloc");
+	// Now we don't have to keep checking alloc_failed before
+	// doing anything, and we can deal with allocation failures
+	// after this point by just terminating immediately.
+
+	// Check there are `cid' and `port' arguments to pass to
+	// vsockserver-socketbinder, and at least one `prog' argument
+	// to pass to vsockserverd.
 	if (optind > argc - 3)
 		ex_usage();
 
-	if (!strcmp(argv[optind], "-1"))
-		lcid = VMADDR_CID_ANY;
-	else if (getu32(argv[optind], 0, UINT32_MAX, &lcid))
-		ex_usage();
-	optind++;
-
-	if (!strcmp(argv[optind], "-1"))
-		lport = VMADDR_PORT_ANY;
-	else if (getu32(argv[optind], 0, UINT32_MAX, &lport))
-		ex_usage();
-	optind++;
-
-	int fd = socket(AF_VSOCK, SOCK_STREAM, 0);
-	if (fd == -1)
-		diee(EX_OSERR, "socket");
-
-	if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1)
-		diee(EX_OSERR, "fcntl");
-
-	if (vsock_bind(fd, lcid, lport) == -1)
-		diee(EX_OSERR, "bind");
-
-	if (listen(fd, 40) == -1)
-		diee(EX_OSERR, "listen");
-
-	if (lport == VMADDR_PORT_ANY)
-		if (vsock_get_cid_and_port(fd, NULL, &lport) == -1)
-			diee(EX_OSERR, "getsockname");
-
-	if (notify) {
-		printf("%" PRIu32 "\n", lport);
-		fclose(stdout);
+	// Add `cid' and `port' arguments to binder options.
+	if (argz_add(&binder_opts, &binder_opts_len, "--") ||
+	    argz_add(&binder_opts, &binder_opts_len, argv[optind++]) ||
+	    argz_add(&binder_opts, &binder_opts_len, argv[optind++]))
+		diee(EX_OSERR, "malloc");
+
+	// Add all of daemon_opts onto the end of binder_opts.  It's
+	// okay to multiply to find the size because if it would
+	// overflow calloc would have failed earlier.
+	if (argz_append(&binder_opts, &binder_opts_len, daemon_opts, daemon_opts_len))
+		diee(EX_OSERR, "malloc");
+	free(daemon_opts);
+
+	// Append `prog' to binder_opts.  Technically this should be
+	// part of daemon_opts, but then we'd just be copying it into
+	// one place to immediately copy it elsewhere.
+	for (int i = optind; i < argc; i++)
+		if (argz_add(&binder_opts, &binder_opts_len, argv[i]))
+			diee(EX_OSERR, "malloc");
+
+	if (verbosity == all) {
+		char *opt;
+
+		// Log the full argv before we exec it.
+		fprintf(stderr, "%s: executing", program_invocation_short_name);
+		while ((opt = argz_next(binder_opts, binder_opts_len, opt)))
+			fprintf(stderr, " %s", opt);
+		fputc('\n', stderr);
 	}
 
-	setenvf("VSOCKLOCALCID", 1, "%" PRIu32, lcid);
-	setenvf("VSOCKLOCALPORT", 1, "%" PRIu32, lport);
-
-	ilog("listening as %" PRIu32 " on port %" PRIu32, lcid, lport);
-
-	struct pollfd poll_fd = { .fd = fd, .events = POLL_IN, .revents = 0 };
-	while (poll(&poll_fd, 1, -1) != -1) {
-		// On Linux, conn will be blocking.  On other
-		// platforms, this may not be the case.  If other
-		// platforms are to be supported, we'd probably want
-		// to set O_NONBLOCK here.
-		int conn = vsock_accept(fd, &rcid, &rport);
-		if (conn == -1)
-			diee(EX_OSERR, "accept");
-
-		setenvf("VSOCKREMOTECID", 1, "%" PRIu32, rcid);
-		setenvf("VSOCKREMOTEPORT", 1, "%" PRIu32, rport);
-
-		ilog("connection from %" PRIu32 " port %" PRIu32, rcid, rport);
-
-		switch (child = fork()) {
-		case -1: diee(EX_OSERR, "fork");
-		case 0:
-			if (dup2(conn, STDIN_FILENO) == -1)
-				diee(EX_OSERR, "dup2");
-			if (dup2(conn, STDOUT_FILENO) == -1)
-				diee(EX_OSERR, "dup2");
-			if (conn != STDIN_FILENO && conn != STDOUT_FILENO)
-				close(conn);
-			execvp(argv[optind], &argv[optind]);
-			diee(EX_OSERR, "exec");
-		}
-
-		if (waitpid(child, NULL, 0) == -1)
-			diee(EX_OSERR, "waitpid");
-
-		close(conn);
-	}
-	diee(EX_OSERR, "poll");
+	execzp(binder_opts, binder_opts, binder_opts_len);
+	diee(EX_OSERR, "execvp");
 }
diff --git a/vsockserverd.c b/vsockserverd.c
new file mode 100644
index 0000000..1abfff9
--- /dev/null
+++ b/vsockserverd.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2020-2021 Alyssa Ross <hi@alyssa.is>
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <inttypes.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdnoreturn.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "env.h"
+#include "log.h"
+#include "vsock.h"
+
+noreturn static void ex_usage(void)
+{
+	if (verbosity)
+		fprintf(stderr, "Usage: %s [ -1 ] [ -q | -Q | -v ] prog...\n",
+			program_invocation_short_name);
+	exit(EX_USAGE);
+}
+
+int main(int argc, char *argv[])
+{
+	bool notify = false;
+	int opt;
+	pid_t child;
+	uint32_t lcid, lport, rcid, rport;
+	struct pollfd poll_fd =
+		{ .fd = STDIN_FILENO, .events = POLLIN, .revents = 0 };
+
+	while ((opt = getopt(argc, argv, "+1qQv")) != -1) {
+		switch (opt) {
+		case '1':
+			notify = true;
+			break;
+		case 'q':
+		case 'Q':
+		case 'v':
+			set_verbosity(opt);
+			break;
+		default:
+			ex_usage();
+		}
+	}
+
+	// Check that there is something for us to exec into.
+	if (optind > argc - 1)
+		ex_usage();
+
+	// Find out the CID and port of the socket.
+	if (vsock_get_cid_and_port(STDIN_FILENO, &lcid, &lport)) {
+		if (errno == ENOTSOCK || errno == EPROTOTYPE)
+			die(EX_USAGE, "stdin is not an AF_VSOCK socket");
+		diee(EX_OSERR, "getsockname");
+	}
+
+	// Notify the user of our port and indicate readiness.
+	if (notify) {
+		printf("%" PRIu32 "\n", lport);
+		fclose(stdout);
+	}
+
+	// Set our CID and port as environment variables so `prog' can
+	// know them.
+	setenvf("VSOCKLOCALCID", 1, "%" PRIu32, lcid);
+	setenvf("VSOCKLOCALPORT", 1, "%" PRIu32, lport);
+
+	ilog("listening as %" PRIu32 " on port %" PRIu32, lcid, lport);
+
+	// Wait for a connection.
+	while (poll(&poll_fd, 1, -1) != -1) {
+		// On Linux, conn will be blocking.  On other
+		// platforms, this may not be the case.  If other
+		// platforms are to be supported, we'd probably want
+		// to set O_NONBLOCK here.
+		int conn = vsock_accept(STDIN_FILENO, &rcid, &rport);
+		if (conn == -1)
+			diee(EX_OSERR, "accept");
+
+		// Set the client's CID and port as environment
+		// variables so `prog' can know them.
+		setenvf("VSOCKREMOTECID", 1, "%" PRIu32, rcid);
+		setenvf("VSOCKREMOTEPORT", 1, "%" PRIu32, rport);
+
+		ilog("connection from %" PRIu32 " port %" PRIu32, rcid, rport);
+
+		switch (child = fork()) {
+		case -1: diee(EX_OSERR, "fork");
+		case 0:
+			// Set up the connection socket on prog's
+			// stdin and stdout.  This has the happy side
+			// effect of closing the listening socket in
+			// the child, since it's also on stdin.
+			if (dup2(conn, STDIN_FILENO) == -1)
+				diee(EX_OSERR, "dup2");
+			if (dup2(conn, STDOUT_FILENO) == -1)
+				diee(EX_OSERR, "dup2");
+			if (conn != STDIN_FILENO && conn != STDOUT_FILENO)
+				close(conn);
+
+			// exec into `prog'.
+			execvp(argv[optind], &argv[optind]);
+			diee(EX_OSERR, "exec");
+		}
+
+		if (waitpid(child, NULL, 0) == -1)
+			diee(EX_OSERR, "waitpid");
+
+		close(conn);
+	}
+	diee(EX_OSERR, "poll");
+}