[lxc-devel] [lxd/master] [TESTING] proxy: udp support

brauner on Github lxc-bot at linuxcontainers.org
Wed Jun 13 00:07:58 UTC 2018


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 364 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20180613/e007a64c/attachment.bin>
-------------- next part --------------
From 6adb2a803ef2a9575ee25bee5d96bff173608f8a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 12 Jun 2018 13:04:09 +0200
Subject: [PATCH 01/13] memory: fix format string

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/debug/memory.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/debug/memory.go b/lxd/debug/memory.go
index c7d6d3908..9055495d2 100644
--- a/lxd/debug/memory.go
+++ b/lxd/debug/memory.go
@@ -50,7 +50,7 @@ func memoryWatcher(ctx context.Context, signals <-chan os.Signal, filename strin
 func memoryDump(filename string) {
 	f, err := os.Create(filename)
 	if err != nil {
-		logger.Debugf("Error opening memory profile file '%s': %s", err)
+		logger.Debugf("Error opening memory profile file '%s': %s", filename, err)
 		return
 	}
 	pprof.WriteHeapProfile(f)

From e99cd9dfec5d6c609e4847ac53bef7045e17d52f Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 12 Jun 2018 13:04:50 +0200
Subject: [PATCH 02/13] exec: fix format string

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_exec.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 0807c3cb7..6b1ec1917 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -218,10 +218,10 @@ func (s *execWs) Do(op *operation) error {
 					}
 				} else if command.Command == "signal" {
 					if err := syscall.Kill(attachedChildPid, syscall.Signal(command.Signal)); err != nil {
-						logger.Debugf("Failed forwarding signal '%s' to PID %d.", command.Signal, attachedChildPid)
+						logger.Debugf("Failed forwarding signal '%d' to PID %d", command.Signal, attachedChildPid)
 						continue
 					}
-					logger.Debugf("Forwarded signal '%d' to PID %d.", command.Signal, attachedChildPid)
+					logger.Debugf("Forwarded signal '%d' to PID %d", command.Signal, attachedChildPid)
 				}
 			}
 		}()

From b86d7d5db158429f4539f563c10c4f47045ae926 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 12 Jun 2018 13:05:45 +0200
Subject: [PATCH 03/13] images: fix format string

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/images.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/images.go b/lxd/images.go
index adf3a28b3..ac4128d58 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -1040,7 +1040,7 @@ func pruneExpiredImages(ctx context.Context, d *Daemon) {
 		for _, pool := range poolNames {
 			err := doDeleteImageFromPool(d.State(), fp, pool)
 			if err != nil {
-				logger.Debugf("Error deleting image %s from storage pool %: %s", fp, pool, err)
+				logger.Debugf("Error deleting image %s from storage pool %s: %s", fp, pool, err)
 				continue
 			}
 		}

From 23fab32473c338a166b8bdf95c6be1460a39b8df Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 12 Jun 2018 13:06:54 +0200
Subject: [PATCH 04/13] migrate: remove debug residuals

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/migrate_container.go | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/lxd/migrate_container.go b/lxd/migrate_container.go
index 2d583fc28..9129ce1fb 100644
--- a/lxd/migrate_container.go
+++ b/lxd/migrate_container.go
@@ -150,7 +150,6 @@ func (s *migrationSourceWs) checkForPreDumpSupport() (bool, int) {
 	if tmp != "" {
 		use_pre_dumps = shared.IsTrue(tmp)
 	}
-	logger.Debugf("migration.incremental.memory %d", use_pre_dumps)
 
 	var max_iterations int
 
@@ -910,7 +909,6 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 			}
 
 			if resp.GetPredump() {
-				logger.Debugf("Before the receive loop %s", sync.GetFinalPreDump())
 				for !sync.GetFinalPreDump() {
 					logger.Debugf("About to receive rsync")
 					// Transfer a CRIU pre-dump
@@ -940,7 +938,6 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 						restore <- err
 						return
 					}
-					logger.Debugf("At the end of the receive loop %s", sync.GetFinalPreDump())
 				}
 			}
 

From 70fbb7406dd847304fc0aca0ceb3ae9d4af3cee9 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 12 Jun 2018 13:07:41 +0200
Subject: [PATCH 05/13] lvm: fix format string

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_lvm.go | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index c4662a466..72e2b9b5a 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -770,10 +770,7 @@ func (s *storageLvm) StoragePoolUpdate(writable *api.StoragePoolPut, changedConf
 
 			err := lvmVGRename(newName, oldPoolName)
 			if err != nil {
-				logger.Warnf(`Failed to rename LVM volume `+
-					`group from "%s" to "%s": %s. Manual `+
-					`intervention needed`, newName,
-					oldPoolName)
+				logger.Warnf(`Failed to rename LVM volume group from "%s" to "%s": %s. Manual intervention needed`, newName, oldPoolName, err)
 			}
 			s.setOnDiskPoolName(oldPoolName)
 		}()

From e6d4d6bbeeffe2f7bfaee94eb973ff04b77da55a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 12 Jun 2018 13:08:15 +0200
Subject: [PATCH 06/13] db: fix format string

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/db/query/dump.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/db/query/dump.go b/lxd/db/query/dump.go
index 33ce6105f..3aa12933c 100644
--- a/lxd/db/query/dump.go
+++ b/lxd/db/query/dump.go
@@ -102,7 +102,7 @@ func dumpTable(tx *sql.Tx, table, schema string) (string, error) {
 		}
 		err := rows.Scan(row...)
 		if err != nil {
-			return "", errors.Wrapf(err, "failed to scan row %d")
+			return "", errors.Wrapf(err, "failed to scan row %d", i)
 		}
 		values := make([]string, len(columns))
 		for j, v := range raw {

From f73a963bd12a0c6e525c4ff6ef33bc4c8a3b5858 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 12 Jun 2018 20:03:38 +0200
Subject: [PATCH 07/13] nsexec: prevent fd leak

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/main_nsexec.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/main_nsexec.go b/lxd/main_nsexec.go
index a04658c56..4c6837cbf 100644
--- a/lxd/main_nsexec.go
+++ b/lxd/main_nsexec.go
@@ -183,6 +183,7 @@ void attach_userns(int pid) {
 	}
 
 	ret = setns(userns_fd, CLONE_NEWUSER);
+	close(userns_fd);
 	if (ret < 0) {
 		fprintf(stderr, "Failed setns to container user namespace: %s\n", strerror(errno));
 		_exit(EXIT_FAILURE);

From 55dfa3dd89ee7ae45be6df1730fa7c099b70397b Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 25 May 2018 19:53:23 +0200
Subject: [PATCH 08/13] proxy: unix

Closes #4167.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/container_lxc.go      |  45 ++--
 lxd/main_forkproxy.go     | 519 ++++++++++++++++++++++++++++++++++++----------
 lxd/main_nsexec.go        |   2 +-
 lxd/proxy_device_utils.go |   4 +-
 shared/util_linux.go      | 111 ++++++++++
 5 files changed, 535 insertions(+), 146 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index aadde3e56..f9ea10ed1 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2488,7 +2488,13 @@ func (c *containerLXC) Start(stateful bool) error {
 		return err
 	}
 
-	c.restartProxyDevices()
+	// Start proxy devices
+	err = c.restartProxyDevices()
+	if err != nil {
+		// Attempt to stop the container
+		c.Stop(false)
+		return err
+	}
 
 	logger.Info("Started container", ctxMap)
 	eventSendLifecycle("container-started",
@@ -6798,8 +6804,6 @@ func (c *containerLXC) insertProxyDevice(devName string, m types.Device) error {
 		proxyValues.listenAddr,
 		proxyValues.connectPid,
 		proxyValues.connectAddr,
-		"0",
-		"0",
 		logPath,
 		pidPath)
 	if err != nil {
@@ -6858,49 +6862,28 @@ func (c *containerLXC) updateProxyDevice(devName string, m types.Device) error {
 		return fmt.Errorf("Can't update proxy device in stopped container")
 	}
 
-	proxyValues, err := setupProxyProcInfo(c, m)
-	if err != nil {
-		return err
-	}
-
 	devFileName := fmt.Sprintf("proxy.%s", devName)
 	pidPath := filepath.Join(c.DevicesPath(), devFileName)
-	logFileName := fmt.Sprintf("proxy.%s.log", devName)
-	logPath := filepath.Join(c.LogPath(), logFileName)
-
-	err = killProxyProc(pidPath)
-	if err != nil {
-		return fmt.Errorf("Error occurred when removing old proxy device")
-	}
-
-	_, err = shared.RunCommand(
-		c.state.OS.ExecPath,
-		"forkproxy",
-		proxyValues.listenPid,
-		proxyValues.listenAddr,
-		proxyValues.connectPid,
-		proxyValues.connectAddr,
-		"0",
-		"0",
-		logPath,
-		pidPath)
+	err := killProxyProc(pidPath)
 	if err != nil {
-		return fmt.Errorf("Error occurred when starting new proxy device")
+		return fmt.Errorf("Error occurred when removing old proxy device: %v", err)
 	}
 
-	return nil
+	return c.insertProxyDevice(devName, m)
 }
 
-func (c *containerLXC) restartProxyDevices() {
+func (c *containerLXC) restartProxyDevices() error {
 	for _, name := range c.expandedDevices.DeviceNames() {
 		m := c.expandedDevices[name]
 		if m["type"] == "proxy" {
 			err := c.insertProxyDevice(name, m)
 			if err != nil {
-				fmt.Printf("Error when starting proxy device '%s' for container %s: %s\n", name, c.name, err)
+				return fmt.Errorf("Error when starting proxy device '%s' for container %s: %v\n", name, c.name, err)
 			}
 		}
 	}
+
+	return nil
 }
 
 // Network device handling
diff --git a/lxd/main_forkproxy.go b/lxd/main_forkproxy.go
index 0c02199f9..167db12ef 100644
--- a/lxd/main_forkproxy.go
+++ b/lxd/main_forkproxy.go
@@ -5,13 +5,13 @@ import (
 	"io"
 	"net"
 	"os"
-	"strconv"
+	"os/signal"
 	"strings"
 	"syscall"
+	"time"
 
 	"github.com/spf13/cobra"
 
-	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/eagain"
 )
@@ -19,115 +19,251 @@ import (
 /*
 #define _GNU_SOURCE
 #include <errno.h>
+#include <fcntl.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
 #include <unistd.h>
 
 extern char* advance_arg(bool required);
+extern void attach_userns(int pid);
 extern int dosetns(int pid, char *nstype);
 
-void forkproxy() {
-	char *cur = NULL;
+int whoami = -ESRCH;
 
-	int cmdline, listen_pid, connect_pid, fdnum, forked, childPid, ret;
-	char *logPath = NULL, *pidPath = NULL;
-	FILE *logFile = NULL, *pidFile = NULL;
+#define FORKPROXY_CHILD 1
+#define FORKPROXY_PARENT 0
+#define FORKPROXY_UDS_SOCK_FD_NUM 200
 
-	// /proc/self/fd/<num> (14 (path) + 21 (int64) + 1 (null))
-	char fdpath[36];
+int wait_for_pid(pid_t pid)
+{
+	int status, ret;
+
+again:
+	ret = waitpid(pid, &status, 0);
+	if (ret == -1) {
+		if (errno == EINTR)
+			goto again;
+		return -1;
+	}
+	if (ret != pid)
+		goto again;
+	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+		return -1;
+	return 0;
+}
+
+ssize_t lxc_read_nointr(int fd, void* buf, size_t count)
+{
+	ssize_t ret;
+again:
+	ret = read(fd, buf, count);
+	if (ret < 0 && errno == EINTR)
+		goto again;
+	return ret;
+}
+
+void forkproxy()
+{
+	int connect_pid, listen_pid, log_fd;
+	ssize_t ret;
+	pid_t pid;
+	char *connect_addr, *cur, *listen_addr, *log_path, *pid_path;
+	int sk_fds[2] = {-EBADF, -EBADF};
+	FILE *pid_file;
 
 	// Get the pid
 	cur = advance_arg(false);
-	if (cur == NULL || (strcmp(cur, "--help") == 0 || strcmp(cur, "--version") == 0 || strcmp(cur, "-h") == 0)) {
-		return;
-	}
-	listen_pid = atoi(cur);
+	if (cur == NULL ||
+	    (strcmp(cur, "--help") == 0 || strcmp(cur, "--version") == 0 ||
+	     strcmp(cur, "-h") == 0))
+		_exit(EXIT_FAILURE);
 
-	// Get the arguments
-	advance_arg(true);
+	listen_pid = atoi(cur);
+	listen_addr = advance_arg(true);
 	connect_pid = atoi(advance_arg(true));
-	advance_arg(true);
-	fdnum = atoi(advance_arg(true));
-	forked = atoi(advance_arg(true));
-	logPath = advance_arg(true);
-	pidPath = advance_arg(true);
-
-	// Check if proxy daemon already forked
-	if (forked == 0) {
-		logFile = fopen(logPath, "w+");
-		if (logFile == NULL) {
-			_exit(1);
-		}
+	connect_addr = advance_arg(true);
+	log_path = advance_arg(true);
+	pid_path = advance_arg(true);
+
+	close(STDIN_FILENO);
+	log_fd = open(log_path, O_WRONLY | O_CREAT | O_CLOEXEC | O_TRUNC, 0600);
+	if (log_fd < 0)
+		_exit(EXIT_FAILURE);
+
+	ret = dup3(log_fd, STDOUT_FILENO, O_CLOEXEC);
+	if (ret < 0)
+		_exit(EXIT_FAILURE);
+
+	ret = dup3(log_fd, STDERR_FILENO, O_CLOEXEC);
+	if (ret < 0)
+		_exit(EXIT_FAILURE);
+
+	pid_file = fopen(pid_path, "w+");
+	if (!pid_file) {
+		fprintf(stderr,
+			"%s - Failed to create pid file for proxy daemon\n",
+			strerror(errno));
+		_exit(EXIT_FAILURE);
+	}
 
-		if (dup2(fileno(logFile), STDOUT_FILENO) < 0) {
-			fprintf(logFile, "Failed to redirect STDOUT to logfile: %s\n", strerror(errno));
-			_exit(1);
+	ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sk_fds);
+	if (ret < 0) {
+		fprintf(stderr,
+			"%s - Failed to create anonymous unix socket pair\n",
+			strerror(errno));
+		_exit(EXIT_FAILURE);
+	}
+	fprintf(stderr, "Created anonymous pair {%d,%d} of unix sockets\n",
+		sk_fds[0], sk_fds[1]);
+
+	pid = fork();
+	if (pid < 0) {
+		fprintf(stderr, "%s - Failed to create new process\n",
+			strerror(errno));
+		_exit(EXIT_FAILURE);
+	}
+
+	if (pid == 0) {
+		whoami = FORKPROXY_CHILD;
+
+		fclose(pid_file);
+		close(sk_fds[0]);
+
+		// Attach to the user namespace of the listener
+		attach_userns(listen_pid);
+
+		// Attach to the network namespace of the listener
+		ret = dosetns(listen_pid, "net");
+		if (ret < 0) {
+			fprintf(stderr, "Failed setns to listener network namespace: %s\n",
+				strerror(errno));
+			_exit(EXIT_FAILURE);
 		}
-		if (dup2(fileno(logFile), STDERR_FILENO) < 0) {
-			fprintf(logFile, "Failed to redirect STDERR to logfile: %s\n", strerror(errno));
-			_exit(1);
+
+		// Attach to the mount namespace of the listener
+		ret = dosetns(listen_pid, "mnt");
+		if (ret < 0) {
+			fprintf(stderr, "Failed setns to listener mount namespace: %s\n",
+				strerror(errno));
+			_exit(EXIT_FAILURE);
 		}
-		fclose(logFile);
 
-		pidFile = fopen(pidPath, "w+");
-		if (pidFile == NULL) {
-			fprintf(stderr, "Failed to create pid file for proxy daemon: %s\n", strerror(errno));
+		ret = dup3(sk_fds[1], FORKPROXY_UDS_SOCK_FD_NUM, O_CLOEXEC);
+		if (ret < 0) {
+			fprintf(stderr,
+				"%s - Failed to duplicate fd %d to fd 200\n",
+				strerror(errno), sk_fds[1]);
 			_exit(1);
 		}
 
-		childPid = fork();
-		if (childPid < 0) {
-			fprintf(stderr, "Failed to fork proxy daemon: %s\n", strerror(errno));
+		ret = close(sk_fds[1]);
+		if (ret < 0) {
+			fprintf(stderr, "%s - Failed to close socket fd %d\n",
+				strerror(errno), sk_fds[1]);
 			_exit(1);
-		} else if (childPid != 0) {
-			fprintf(pidFile, "%d", childPid);
-			fclose(pidFile);
-			fclose(stdin);
-			fclose(stdout);
-			fclose(stderr);
-			_exit(0);
-		} else {
-			ret = setsid();
-			if (ret < 0) {
-				fprintf(stderr, "Failed to setsid in proxy daemon: %s\n", strerror(errno));
-				_exit(1);
-			}
 		}
-	}
+	} else {
+		whoami = FORKPROXY_PARENT;
 
-	// Cannot pass through -1 to runCommand since it is interpreted as a flag
-	fdnum = fdnum == 0 ? -1 : fdnum;
+		close(sk_fds[1]);
 
-	ret = snprintf(fdpath, sizeof(fdpath), "/proc/self/fd/%d", fdnum);
-	if (ret < 0 || (size_t)ret >= sizeof(fdpath)) {
-		fprintf(stderr, "Failed to format file descriptor path\n");
-		_exit(1);
-	}
+		// Attach to the user namespace of the listener
+		attach_userns(connect_pid);
 
-	// Join the listener ns if not already setup
-	if (access(fdpath, F_OK) < 0) {
 		// Attach to the network namespace of the listener
-		if (dosetns(listen_pid, "net") < 0) {
-			fprintf(stderr, "Failed setns to listener network namespace: %s\n", strerror(errno));
+		ret = dosetns(connect_pid, "net");
+		if (ret < 0) {
+			fprintf(stderr, "Failed setns to listener network namespace: %s\n",
+				strerror(errno));
+			_exit(EXIT_FAILURE);
+		}
+
+		// Attach to the mount namespace of the listener
+		ret = dosetns(connect_pid, "mnt");
+		if (ret < 0) {
+			fprintf(stderr, "Failed setns to listener mount namespace: %s\n",
+				strerror(errno));
+			_exit(EXIT_FAILURE);
+		}
+
+		ret = dup3(sk_fds[0], FORKPROXY_UDS_SOCK_FD_NUM, O_CLOEXEC);
+		if (ret < 0) {
+			fprintf(stderr,
+				"%s - Failed to duplicate fd %d to fd 200\n",
+				strerror(errno), sk_fds[1]);
 			_exit(1);
 		}
-	} else {
-		// Join the connector ns now
-		if (dosetns(connect_pid, "net") < 0) {
-			fprintf(stderr, "Failed setns to connector network namespace: %s\n", strerror(errno));
+
+		ret = close(sk_fds[0]);
+		if (ret < 0) {
+			fprintf(stderr, "%s - Failed to close socket fd %d\n",
+				strerror(errno), sk_fds[1]);
 			_exit(1);
 		}
+
+		ret = wait_for_pid(pid);
+		if (ret < 0) {
+			fprintf(stderr, "Failed to start listener\n");
+			_exit(EXIT_FAILURE);
+		}
+
+		// daemonize
+		pid = fork();
+		if (pid < 0)
+			_exit(EXIT_FAILURE);
+
+		if (pid != 0) {
+			ret = wait_for_pid(pid);
+			if (ret < 0)
+				_exit(EXIT_FAILURE);
+
+			_exit(EXIT_SUCCESS);
+		}
+
+		pid = fork();
+		if (pid < 0)
+			_exit(EXIT_FAILURE);
+
+		if (pid != 0) {
+			ret = fprintf(pid_file, "%d", pid);
+			fclose(pid_file);
+			if (ret < 0) {
+				fprintf(stderr, "Failed to write proxy daemon pid %d to \"%s\"\n",
+					pid, pid_path);
+				ret = EXIT_FAILURE;
+			}
+			close(STDOUT_FILENO);
+			close(STDERR_FILENO);
+			_exit(EXIT_SUCCESS);
+		}
+
+		ret = setsid();
+		if (ret < 0)
+			fprintf(stderr, "%s - Failed to setsid in proxy daemon\n",
+				strerror(errno));
 	}
 }
 */
 import "C"
 
+const forkproxyUDSSockFDNum int = C.FORKPROXY_UDS_SOCK_FD_NUM
+
 type cmdForkproxy struct {
 	global *cmdGlobal
 }
 
+type proxyAddress struct {
+	connType string
+	addr     string
+	abstract bool
+}
+
 func (c *cmdForkproxy) Command() *cobra.Command {
 	// Main subcommand
 	cmd := &cobra.Command{}
@@ -147,8 +283,13 @@ func (c *cmdForkproxy) Command() *cobra.Command {
 }
 
 func (c *cmdForkproxy) Run(cmd *cobra.Command, args []string) error {
+	// Only root should run this
+	if os.Geteuid() != 0 {
+		return fmt.Errorf("This must be run as root")
+	}
+
 	// Sanity checks
-	if len(args) != 8 {
+	if len(args) != 6 {
 		cmd.Help()
 
 		if len(args) == 0 {
@@ -158,64 +299,75 @@ func (c *cmdForkproxy) Run(cmd *cobra.Command, args []string) error {
 		return fmt.Errorf("Missing required arguments")
 	}
 
-	// Only root should run this
-	if os.Geteuid() != 0 {
-		return fmt.Errorf("This must be run as root")
-	}
-
 	// Get all our arguments
-	listenPid := args[0]
 	listenAddr := args[1]
-	connectPid := args[2]
-	connectAddr := args[3]
 
-	fd := -1
-	if args[4] != "0" {
-		fd, _ = strconv.Atoi(args[4])
+	// Check where we are in initialization
+	if C.whoami != C.FORKPROXY_PARENT && C.whoami != C.FORKPROXY_CHILD {
+		return fmt.Errorf("Failed to call forkproxy constructor")
 	}
 
-	// At this point we have already forked and should set this flag to 1
-	args[5] = "1"
+	lAddr := parseAddr(listenAddr)
 
-	// Check where we are in initialization
-	if !shared.PathExists(fmt.Sprintf("/proc/self/fd/%d", fd)) {
-		fmt.Printf("Listening on %s in %s, forwarding to %s from %s\n", listenAddr, listenPid, connectAddr, connectPid)
+	if C.whoami == C.FORKPROXY_CHILD {
+		err := os.Remove(lAddr.addr)
+		if err != nil && !os.IsNotExist(err) {
+			return err
+		}
 
 		file, err := getListenerFile(listenAddr)
 		if err != nil {
 			return err
 		}
-		defer file.Close()
 
-		listenerFd := file.Fd()
-		if err != nil {
-			return fmt.Errorf("Failed to duplicate the listener fd: %v", err)
-		}
+		err = shared.AbstractUnixSendFd(forkproxyUDSSockFDNum, int(file.Fd()))
+		syscall.Close(forkproxyUDSSockFDNum)
+		file.Close()
+		return err
+	}
 
-		newFd, err := syscall.Dup(int(listenerFd))
-		if err != nil {
-			return fmt.Errorf("Failed to dup fd: %v", err)
-		}
+	file, err := shared.AbstractUnixReceiveFd(forkproxyUDSSockFDNum)
+	syscall.Close(forkproxyUDSSockFDNum)
+	if err != nil {
+		fmt.Printf("Failed to receive fd from listener process: %v\n", err)
+		return err
+	}
 
-		fmt.Printf("Re-executing proxy process\n")
+	listener, err := net.FileListener(file)
+	if err != nil {
+		fmt.Printf("Failed to re-assemble listener: %v", err)
+		return err
+	}
+
+	// Handle SIGTERM which is sent when the proxy is to be removed
+	terminate := false
+	sigs := make(chan os.Signal, 1)
+	signal.Notify(sigs, syscall.SIGTERM)
 
-		args[4] = strconv.Itoa(int(newFd))
-		execArgs := append([]string{"lxd", "forkproxy"}, args...)
+	// Wait for SIGTERM and close the listener in order to exit the loop below
+	go func() {
+		<-sigs
+		terminate = true
+		listener.Close()
+	}()
+
+	connectAddr := args[3]
+	cAddr := parseAddr(connectAddr)
 
-		err = syscall.Exec(util.GetExecPath(), execArgs, os.Environ())
+	if cAddr.connType == "unix" && !cAddr.abstract {
+		// Create socket
+		file, err := getListenerFile(fmt.Sprintf("unix:%s", cAddr.addr))
 		if err != nil {
-			return fmt.Errorf("Failed to re-exec: %v", err)
+			return err
 		}
-	}
+		file.Close()
 
-	// Re-create listener from fd
-	listenFile := os.NewFile(uintptr(fd), "listener")
-	listener, err := net.FileListener(listenFile)
-	if err != nil {
-		return fmt.Errorf("Failed to re-assemble listener: %v", err)
+		defer os.Remove(cAddr.addr)
 	}
 
-	defer listener.Close()
+	if lAddr.connType == "unix" && !lAddr.abstract {
+		defer os.Remove(lAddr.addr)
+	}
 
 	fmt.Printf("Starting to proxy\n")
 
@@ -224,6 +376,10 @@ func (c *cmdForkproxy) Run(cmd *cobra.Command, args []string) error {
 		// Accept a new client
 		srcConn, err := listener.Accept()
 		if err != nil {
+			if terminate {
+				break
+			}
+
 			fmt.Printf("error: Failed to accept new connection: %v\n", err)
 			continue
 		}
@@ -237,18 +393,149 @@ func (c *cmdForkproxy) Run(cmd *cobra.Command, args []string) error {
 			continue
 		}
 
-		go io.Copy(eagain.Writer{Writer: srcConn}, eagain.Reader{Reader: dstConn})
-		go io.Copy(eagain.Writer{Writer: dstConn}, eagain.Reader{Reader: srcConn})
+		if cAddr.connType == "unix" && lAddr.connType == "unix" {
+			// Handle OOB if both src and dst are using unix sockets
+			go unixRelay(srcConn, dstConn)
+		} else {
+			go genericRelay(srcConn, dstConn)
+		}
+	}
+
+	fmt.Printf("Stopping proxy\n")
+
+	return nil
+}
+
+func genericRelay(dst io.ReadWriteCloser, src io.ReadWriteCloser) {
+	relayer := func(dst io.Writer, src io.Reader, ch chan bool) {
+		io.Copy(eagain.Writer{Writer: dst}, eagain.Reader{Reader: src})
+		ch <- true
 	}
+
+	chSend := make(chan bool)
+	go relayer(dst, src, chSend)
+
+	chRecv := make(chan bool)
+	go relayer(src, dst, chRecv)
+
+	<-chSend
+	<-chRecv
+
+	src.Close()
+	dst.Close()
+}
+
+func unixRelayer(src *net.UnixConn, dst *net.UnixConn, ch chan bool) {
+	dataBuf := make([]byte, 4096)
+	oobBuf := make([]byte, 4096)
+
+	for {
+		// Read from the source
+	readAgain:
+		sData, sOob, _, _, err := src.ReadMsgUnix(dataBuf, oobBuf)
+		if err != nil {
+			errno, ok := shared.GetErrno(err)
+			if ok && errno == syscall.EAGAIN {
+				goto readAgain
+			}
+			fmt.Printf("Disconnected during read: %v\n", err)
+			ch <- true
+			return
+		}
+
+		var fds []int
+		if sOob > 0 {
+			entries, err := syscall.ParseSocketControlMessage(oobBuf[:sOob])
+			if err != nil {
+				fmt.Printf("Failed to parse control message: %v\n", err)
+				ch <- true
+				return
+			}
+
+			for _, msg := range entries {
+				fds, err = syscall.ParseUnixRights(&msg)
+				if err != nil {
+					fmt.Printf("Failed to get fd list for control message: %v\n", err)
+					ch <- true
+					return
+				}
+			}
+		}
+
+		// Send to the destination
+	writeAgain:
+		tData, tOob, err := dst.WriteMsgUnix(dataBuf[:sData], oobBuf[:sOob], nil)
+		if err != nil {
+			errno, ok := shared.GetErrno(err)
+			if ok && errno == syscall.EAGAIN {
+				goto writeAgain
+			}
+			fmt.Printf("Disconnected during write: %v\n", err)
+			ch <- true
+			return
+		}
+
+		if sData != tData || sOob != tOob {
+			fmt.Printf("Some data got lost during transfer, disconnecting.")
+			ch <- true
+			return
+		}
+
+		// Close those fds we received
+		if fds != nil {
+			for _, fd := range fds {
+				err := syscall.Close(fd)
+				if err != nil {
+					fmt.Printf("Failed to close fd %d: %v\n", fd, err)
+					ch <- true
+					return
+				}
+			}
+		}
+	}
+}
+
+func unixRelay(dst io.ReadWriteCloser, src io.ReadWriteCloser) {
+	chSend := make(chan bool)
+	go unixRelayer(dst.(*net.UnixConn), src.(*net.UnixConn), chSend)
+
+	chRecv := make(chan bool)
+	go unixRelayer(src.(*net.UnixConn), dst.(*net.UnixConn), chRecv)
+
+	<-chSend
+	<-chRecv
+
+	src.Close()
+	dst.Close()
+}
+
+func tryListen(protocol string, addr string) (net.Listener, error) {
+	var listener net.Listener
+	var err error
+
+	for i := 0; i < 10; i++ {
+		listener, err = net.Listen(protocol, addr)
+		if err == nil {
+			break
+		}
+
+		time.Sleep(500 * time.Millisecond)
+	}
+
+	if err != nil {
+		return nil, err
+	}
+
+	return listener, nil
 }
 
 func getListenerFile(listenAddr string) (os.File, error) {
 	fields := strings.SplitN(listenAddr, ":", 2)
 	addr := strings.Join(fields[1:], "")
 
-	listener, err := net.Listen(fields[0], addr)
+	listener, err := tryListen(fields[0], addr)
 	if err != nil {
-		return os.File{}, err
+		return os.File{}, fmt.Errorf("Failed to listen on %s: %v", addr, err)
 	}
 
 	file := &os.File{}
@@ -260,7 +547,6 @@ func getListenerFile(listenAddr string) (os.File, error) {
 		unixListener := listener.(*net.UnixListener)
 		file, err = unixListener.File()
 	}
-
 	if err != nil {
 		return os.File{}, fmt.Errorf("Failed to get file from listener: %v", err)
 	}
@@ -273,3 +559,12 @@ func getDestConn(connectAddr string) (net.Conn, error) {
 	addr := strings.Join(fields[1:], "")
 	return net.Dial(fields[0], addr)
 }
+
+func parseAddr(addr string) *proxyAddress {
+	fields := strings.SplitN(addr, ":", 2)
+	return &proxyAddress{
+		connType: fields[0],
+		addr:     fields[1],
+		abstract: strings.HasPrefix(fields[1], "@"),
+	}
+}
diff --git a/lxd/main_nsexec.go b/lxd/main_nsexec.go
index 4c6837cbf..881a47109 100644
--- a/lxd/main_nsexec.go
+++ b/lxd/main_nsexec.go
@@ -177,7 +177,7 @@ void attach_userns(int pid) {
 	userns_fd = in_same_namespace(getpid(), pid, "user");
 	if (userns_fd < 0) {
 		if (userns_fd == -EINVAL)
-			_exit(EXIT_SUCCESS);
+			return;
 
 		_exit(EXIT_FAILURE);
 	}
diff --git a/lxd/proxy_device_utils.go b/lxd/proxy_device_utils.go
index 1984062b5..63d815dfd 100644
--- a/lxd/proxy_device_utils.go
+++ b/lxd/proxy_device_utils.go
@@ -31,10 +31,10 @@ func setupProxyProcInfo(c container, device map[string]string) (*proxyProcInfo,
 	connectionType := strings.SplitN(connectAddr, ":", 2)[0]
 	listenerType := strings.SplitN(listenAddr, ":", 2)[0]
 
-	if connectionType != "tcp" {
+	if !shared.StringInSlice(connectionType, []string{"tcp", "unix"}) {
 		return nil, fmt.Errorf("Proxy device doesn't support the connection type: %s", connectionType)
 	}
-	if listenerType != "tcp" {
+	if !shared.StringInSlice(listenerType, []string{"tcp", "unix"}) {
 		return nil, fmt.Errorf("Proxy device doesn't support the listener type: %s", listenerType)
 	}
 
diff --git a/shared/util_linux.go b/shared/util_linux.go
index 88613479a..a6582ba87 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -35,6 +35,7 @@ import (
 #include <string.h>
 #include <unistd.h>
 #include <sys/stat.h>
+#include <sys/socket.h>
 #include <sys/types.h>
 #include <sys/un.h>
 
@@ -149,6 +150,93 @@ again:
 
 	return ret;
 }
+
+int lxc_abstract_unix_send_fds(int fd, int *sendfds, int num_sendfds,
+			       void *data, size_t size)
+{
+	int ret;
+	struct msghdr msg;
+	struct iovec iov;
+	struct cmsghdr *cmsg = NULL;
+	char buf[1] = {0};
+	char *cmsgbuf;
+	size_t cmsgbufsize = CMSG_SPACE(num_sendfds * sizeof(int));
+
+	memset(&msg, 0, sizeof(msg));
+	memset(&iov, 0, sizeof(iov));
+
+	cmsgbuf = malloc(cmsgbufsize);
+	if (!cmsgbuf)
+		return -1;
+
+	msg.msg_control = cmsgbuf;
+	msg.msg_controllen = cmsgbufsize;
+
+	cmsg = CMSG_FIRSTHDR(&msg);
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	cmsg->cmsg_len = CMSG_LEN(num_sendfds * sizeof(int));
+
+	msg.msg_controllen = cmsg->cmsg_len;
+
+	memcpy(CMSG_DATA(cmsg), sendfds, num_sendfds * sizeof(int));
+
+	iov.iov_base = data ? data : buf;
+	iov.iov_len = data ? size : sizeof(buf);
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+
+	ret = sendmsg(fd, &msg, MSG_NOSIGNAL);
+	if (ret < 0)
+		fprintf(stderr, "%s - Failed to send file descriptor\n", strerror(errno));
+	free(cmsgbuf);
+	return ret;
+}
+
+int lxc_abstract_unix_recv_fds(int fd, int *recvfds, int num_recvfds,
+			       void *data, size_t size)
+{
+	int ret;
+	struct msghdr msg;
+	struct iovec iov;
+	struct cmsghdr *cmsg = NULL;
+	char buf[1] = {0};
+	char *cmsgbuf;
+	size_t cmsgbufsize = CMSG_SPACE(num_recvfds * sizeof(int));
+
+	memset(&msg, 0, sizeof(msg));
+	memset(&iov, 0, sizeof(iov));
+
+	cmsgbuf = malloc(cmsgbufsize);
+	if (!cmsgbuf)
+		return -1;
+
+	msg.msg_control = cmsgbuf;
+	msg.msg_controllen = cmsgbufsize;
+
+	iov.iov_base = data ? data : buf;
+	iov.iov_len = data ? size : sizeof(buf);
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+
+	ret = recvmsg(fd, &msg, 0);
+	if (ret <= 0) {
+		fprintf(stderr, "%s - Failed to receive file descriptor\n", strerror(errno));
+		goto out;
+	}
+
+	cmsg = CMSG_FIRSTHDR(&msg);
+
+	memset(recvfds, -1, num_recvfds * sizeof(int));
+	if (cmsg && cmsg->cmsg_len == CMSG_LEN(num_recvfds * sizeof(int)) &&
+	    cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
+		memcpy(recvfds, CMSG_DATA(cmsg), num_recvfds * sizeof(int));
+	}
+
+out:
+	free(cmsgbuf);
+	return ret;
+}
 */
 import "C"
 
@@ -174,6 +262,29 @@ func GetPollRevents(fd int, timeout int, flags int) (int, int, error) {
 	return int(ret), int(revents), err
 }
 
+func AbstractUnixSendFd(sockFD int, sendFD int) error {
+	fd := C.int(sendFD)
+	sk_fd := C.int(sockFD)
+	ret := C.lxc_abstract_unix_send_fds(sk_fd, &fd, C.int(1), nil, C.size_t(0))
+	if ret < 0 {
+		return fmt.Errorf("Failed to send file descriptor via abstract unix socket")
+	}
+
+	return nil
+}
+
+func AbstractUnixReceiveFd(sockFD int) (*os.File, error) {
+	fd := C.int(-1)
+	sk_fd := C.int(sockFD)
+	ret := C.lxc_abstract_unix_recv_fds(sk_fd, &fd, C.int(1), nil, C.size_t(0))
+	if ret < 0 {
+		return nil, fmt.Errorf("Failed to receive file descriptor via abstract unix socket")
+	}
+
+	file := os.NewFile(uintptr(fd), "")
+	return file, nil
+}
+
 func OpenPty(uid, gid int64) (master *os.File, slave *os.File, err error) {
 	fd_master := C.int(-1)
 	fd_slave := C.int(-1)

From b811673d0aa2c3537c1eac6eaa4dbbfec771a9fd Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Tue, 29 May 2018 20:04:25 +0200
Subject: [PATCH 09/13] api: proxy_unix

Closes #4167.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 doc/api-extensions.md | 12 ++++++++++++
 doc/containers.md     |  5 ++++-
 shared/version/api.go |  1 +
 3 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 868838750..fe2e3f41e 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -486,3 +486,15 @@ images from the host.
 ## container\_local\_cross\_pool\_handling
 This enables copying or moving containers between storage pools on the same LXD
 instance.
+
+## proxy_unix
+Add support for both unix sockets and abstract unix sockets in proxy devices.
+They can be used by specifying the address as `unix:/path/to/unix.sock` (normal
+socket) or `unix:@/tmp/unix.sock` (abstract socket).
+
+Supported connections are now:
+
+* `TCP <-> TCP`
+* `UNIX <-> UNIX`
+* `TCP <-> UNIX`
+* `UNIX <-> TCP`
diff --git a/doc/containers.md b/doc/containers.md
index 7d059f606..2f716eaa3 100644
--- a/doc/containers.md
+++ b/doc/containers.md
@@ -421,7 +421,10 @@ addresses to an address inside the container or to do the reverse and
 have an address in the container connect through the host.
 
 The supported connection types are:
- - `TCP - TCP`
+* `TCP <-> TCP`
+* `UNIX <-> UNIX`
+* `TCP <-> UNIX`
+* `UNIX <-> TCP`
 
 Key         | Type      | Default           | Required  | Description
 :--         | :--       | :--               | :--       | :--
diff --git a/shared/version/api.go b/shared/version/api.go
index 2a3391393..e0208a277 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -106,6 +106,7 @@ var APIExtensions = []string{
 	"container_backup",
 	"devlxd_images",
 	"container_local_cross_pool_handling",
+	"proxy_unix",
 }
 
 // APIExtensionsCount returns the number of available API extensions.

From a32e680e2c2ffe92d101f4a00beff6289fc699cf Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Wed, 30 May 2018 11:38:14 +0200
Subject: [PATCH 10/13] tests: add tcp proxy tests

Closes #4167.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go |   2 +-
 test/suites/proxy.sh | 230 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 227 insertions(+), 5 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index f9ea10ed1..2f9b3bdb5 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -6850,7 +6850,7 @@ func (c *containerLXC) removeProxyDevices() error {
 		devicePath := filepath.Join(c.DevicesPath(), f.Name())
 		err = killProxyProc(devicePath)
 		if err != nil {
-			logger.Error("failed removing proxy device", log.Ctx{"err": err, "path": devicePath})
+			logger.Error("Failed removing proxy device", log.Ctx{"err": err, "path": devicePath})
 		}
 	}
 
diff --git a/test/suites/proxy.sh b/test/suites/proxy.sh
index a7bb0d949..9685b2601 100755
--- a/test/suites/proxy.sh
+++ b/test/suites/proxy.sh
@@ -1,49 +1,271 @@
 test_proxy_device() {
+  test_proxy_device_tcp
+  test_proxy_device_unix
+  test_proxy_device_tcp_unix
+  test_proxy_device_unix_tcp
+}
+
+test_proxy_device_tcp() {
   ensure_import_testimage
   ensure_has_localhost_remote "${LXD_ADDR}"
 
+  # Setup
   MESSAGE="Proxy device test string"
   HOST_TCP_PORT=$(local_tcp_port)
-
   lxc launch testimage proxyTester
+
+  # Initial test
   lxc config device add proxyTester proxyDev proxy "listen=tcp:127.0.0.1:$HOST_TCP_PORT" connect=tcp:127.0.0.1:4321 bind=host
-  nsenter -n -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -6 -l 4321 > proxyTest.out &
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -6 -l 4321 > proxyTest.out &
   sleep 2
 
   echo "${MESSAGE}" | nc -w1 127.0.0.1 "${HOST_TCP_PORT}"
 
   if [ "$(cat proxyTest.out)" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
     echo "Proxy device did not properly send data from host to container"
     false
   fi
 
   rm -f proxyTest.out
 
+  # Restart the container
   lxc restart -f proxyTester
-  nsenter -n -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -6 -l 4321 > proxyTest.out &
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -6 -l 4321 > proxyTest.out &
   sleep 2
 
   echo "${MESSAGE}" | nc -w1 127.0.0.1 "${HOST_TCP_PORT}"
 
   if [ "$(cat proxyTest.out)" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
     echo "Proxy device did not properly restart on container restart"
     false
   fi
 
   rm -f proxyTest.out
 
+  # Change the port
   lxc config device set proxyTester proxyDev connect tcp:127.0.0.1:1337
-  nsenter -n -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -6 -l 1337 > proxyTest.out &
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -6 -l 1337 > proxyTest.out &
   sleep 2
 
   echo "${MESSAGE}" | nc -w1 127.0.0.1 "${HOST_TCP_PORT}"
 
   if [ "$(cat proxyTest.out)" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
     echo "Proxy device did not properly restart when config was updated"
     false
   fi
 
   rm -f proxyTest.out
+
+  # Cleanup
+  lxc delete -f proxyTester
+}
+
+test_proxy_device_unix() {
+  ensure_import_testimage
+  ensure_has_localhost_remote "${LXD_ADDR}"
+
+  # Setup
+  MESSAGE="Proxy device test string"
+  OUTFILE="${TEST_DIR}/proxyTest.out"
+  HOST_SOCK="${TEST_DIR}/lxdtest-$(basename "${LXD_DIR}")-host.sock"
+  lxc launch testimage proxyTester
+
+  # Initial test
+  lxc config device add proxyTester proxyDev proxy "listen=unix:${HOST_SOCK}" connect=unix:/tmp/"lxdtest-$(basename "${LXD_DIR}").sock" bind=host
+  (
+    cd "${LXD_DIR}/containers/proxyTester/rootfs/tmp/" || exit
+    umask 0000
+    rm -f "lxdtest-$(basename "${LXD_DIR}").sock"
+    exec nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -l -U "lxdtest-$(basename "${LXD_DIR}").sock" > "${OUTFILE}"
+  ) &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -U "${HOST_SOCK}"
+
+  if [ "$(cat "${OUTFILE}")" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly send data from host to container"
+    false
+  fi
+
+  rm -f "${OUTFILE}" "${HOST_SOCK}"
+
+  # Restart the container
+  lxc restart -f proxyTester
+  (
+    cd "${LXD_DIR}/containers/proxyTester/rootfs/tmp/" || exit
+    umask 0000
+    rm -f "lxdtest-$(basename "${LXD_DIR}").sock"
+    exec nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -l -U "lxdtest-$(basename "${LXD_DIR}").sock" > "${OUTFILE}"
+  ) &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -U "${HOST_SOCK}"
+
+  if [ "$(cat "${OUTFILE}")" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart on container restart"
+    false
+  fi
+
+  rm -f "${OUTFILE}" "${HOST_SOCK}"
+
+  # Change the socket
+  lxc config device set proxyTester proxyDev connect unix:/tmp/"lxdtest-$(basename "${LXD_DIR}")-2.sock"
+  (
+    cd "${LXD_DIR}/containers/proxyTester/rootfs/tmp/" || exit
+    umask 0000
+    rm -f "lxdtest-$(basename "${LXD_DIR}")-2.sock"
+    exec nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -l -U "lxdtest-$(basename "${LXD_DIR}")-2.sock" > "${OUTFILE}"
+  ) &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -U "${HOST_SOCK}"
+
+  if [ "$(cat "${OUTFILE}")" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart when config was updated"
+    false
+  fi
+
+  rm -f "${OUTFILE}" "${HOST_SOCK}"
+
+  # Cleanup
   lxc delete -f proxyTester
 }
 
+test_proxy_device_tcp_unix() {
+  ensure_import_testimage
+  ensure_has_localhost_remote "${LXD_ADDR}"
+
+  # Setup
+  MESSAGE="Proxy device test string"
+  HOST_TCP_PORT=$(local_tcp_port)
+  OUTFILE="${TEST_DIR}/proxyTest.out"
+  lxc launch testimage proxyTester
+
+  # Initial test
+  lxc config device add proxyTester proxyDev proxy "listen=tcp:127.0.0.1:${HOST_TCP_PORT}" connect=unix:/tmp/"lxdtest-$(basename "${LXD_DIR}").sock" bind=host
+  (
+    cd "${LXD_DIR}/containers/proxyTester/rootfs/tmp/" || exit
+    umask 0000
+    rm -f "lxdtest-$(basename "${LXD_DIR}").sock"
+    exec nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -l -U "lxdtest-$(basename "${LXD_DIR}").sock" > "${OUTFILE}"
+  ) &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 127.0.0.1 "${HOST_TCP_PORT}"
+
+  if [ "$(cat "${OUTFILE}")" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly send data from host to container"
+    false
+  fi
+
+  rm -f "${OUTFILE}"
+
+  # Restart the container
+  lxc restart -f proxyTester
+  (
+    cd "${LXD_DIR}/containers/proxyTester/rootfs/tmp/" || exit
+    umask 0000
+    rm -f "lxdtest-$(basename "${LXD_DIR}").sock"
+    exec nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -l -U "lxdtest-$(basename "${LXD_DIR}").sock" > "${OUTFILE}"
+  ) &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 127.0.0.1 "${HOST_TCP_PORT}"
+
+  if [ "$(cat "${OUTFILE}")" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart on container restart"
+    false
+  fi
+
+  rm -f "${OUTFILE}"
+
+  # Change the socket
+  lxc config device set proxyTester proxyDev connect unix:/tmp/"lxdtest-$(basename "${LXD_DIR}")-2.sock"
+  (
+    cd "${LXD_DIR}/containers/proxyTester/rootfs/tmp/" || exit
+    umask 0000
+    rm -f "lxdtest-$(basename "${LXD_DIR}")-2.sock"
+    exec nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -l -U "lxdtest-$(basename "${LXD_DIR}")-2.sock" > "${OUTFILE}"
+  ) &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 127.0.0.1 "${HOST_TCP_PORT}"
+
+  if [ "$(cat "${OUTFILE}")" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart when config was updated"
+    false
+  fi
+
+  rm -f "${OUTFILE}"
+
+  # Cleanup
+  lxc delete -f proxyTester
+}
+
+test_proxy_device_unix_tcp() {
+  ensure_import_testimage
+  ensure_has_localhost_remote "${LXD_ADDR}"
+
+  # Setup
+  MESSAGE="Proxy device test string"
+  OUTFILE="${TEST_DIR}/proxyTest.out"
+  HOST_SOCK="${TEST_DIR}/lxdtest-$(basename "${LXD_DIR}")-host.sock"
+  lxc launch testimage proxyTester
+
+  # Initial test
+  lxc config device add proxyTester proxyDev proxy "listen=unix:${HOST_SOCK}" connect=tcp:127.0.0.1:4321 bind=host
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -6 -l 4321 > "${OUTFILE}" &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -U "${HOST_SOCK#$(pwd)/}"
+
+  if [ "$(cat "${OUTFILE}")" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly send data from host to container"
+    false
+  fi
+
+  rm -f "${OUTFILE}" "${HOST_SOCK}"
+
+  # Restart the container
+  lxc restart -f proxyTester
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -6 -l 4321 > "${OUTFILE}" &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -U "${HOST_SOCK#$(pwd)/}"
+
+  if [ "$(cat "${OUTFILE}")" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart on container restart"
+    false
+  fi
+
+  rm -f "${OUTFILE}" "${HOST_SOCK}"
+
+  # Change the port
+  lxc config device set proxyTester proxyDev connect tcp:127.0.0.1:1337
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -6 -l 1337 > "${OUTFILE}" &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -U "${HOST_SOCK#$(pwd)/}"
+
+  if [ "$(cat "${OUTFILE}")" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart when config was updated"
+    false
+  fi
+
+  rm -f "${OUTFILE}" "${HOST_SOCK}"
+
+  # Cleanup
+  lxc delete -f proxyTester
+}

From bd54d4c7f9a6e603f5af6d9350a75f3bcf41be12 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 12 Jun 2018 15:28:49 +0200
Subject: [PATCH 11/13] proxy: udp

Closes #4566.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/main_forkproxy.go     | 166 +++++++++++++++++++++++++++++++++++-----------
 lxd/proxy_device_utils.go |   4 +-
 2 files changed, 130 insertions(+), 40 deletions(-)

diff --git a/lxd/main_forkproxy.go b/lxd/main_forkproxy.go
index 167db12ef..50ff0508b 100644
--- a/lxd/main_forkproxy.go
+++ b/lxd/main_forkproxy.go
@@ -333,7 +333,16 @@ func (c *cmdForkproxy) Run(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
-	listener, err := net.FileListener(file)
+	var srcConn net.Conn
+	var listener net.Listener
+
+	udpFD := -1
+	if lAddr.connType == "udp" {
+		udpFD = int(file.Fd())
+		srcConn, err = net.FileConn(file)
+	} else {
+		listener, err = net.FileListener(file)
+	}
 	if err != nil {
 		fmt.Printf("Failed to re-assemble listener: %v", err)
 		return err
@@ -348,7 +357,12 @@ func (c *cmdForkproxy) Run(cmd *cobra.Command, args []string) error {
 	go func() {
 		<-sigs
 		terminate = true
-		listener.Close()
+		if lAddr.connType == "udp" {
+			srcConn.Close()
+		} else {
+			listener.Close()
+		}
+		file.Close()
 	}()
 
 	connectAddr := args[3]
@@ -369,35 +383,65 @@ func (c *cmdForkproxy) Run(cmd *cobra.Command, args []string) error {
 		defer os.Remove(lAddr.addr)
 	}
 
-	fmt.Printf("Starting to proxy\n")
+	fmt.Printf("Starting %s <-> %s proxy\n", lAddr.connType, cAddr.connType)
+	if lAddr.connType == "udp" {
+		for {
+			ret, revents, err := shared.GetPollRevents(udpFD, -1, (shared.POLLIN | shared.POLLPRI | shared.POLLERR | shared.POLLHUP | shared.POLLRDHUP | shared.POLLNVAL))
+			if ret < 0 {
+				fmt.Printf("Failed to poll on file descriptor: %s\n", err)
+				srcConn.Close()
+				return err
+			}
 
-	// begin proxying
-	for {
-		// Accept a new client
-		srcConn, err := listener.Accept()
-		if err != nil {
-			if terminate {
-				break
+			if (revents & (shared.POLLERR | shared.POLLHUP | shared.POLLRDHUP | shared.POLLNVAL)) > 0 {
+				err := fmt.Errorf("Invalid UDP socket file descriptor")
+				fmt.Printf("%s\n", err)
+				srcConn.Close()
+				return err
 			}
 
-			fmt.Printf("error: Failed to accept new connection: %v\n", err)
-			continue
-		}
-		fmt.Printf("Accepted a new connection\n")
+			// Connect to the target
+			dstConn, err := getDestConn(connectAddr)
+			if err != nil {
+				fmt.Printf("error: Failed to connect to target: %v\n", err)
+				srcConn.Close()
+				return err
+			}
 
-		// Connect to the target
-		dstConn, err := getDestConn(connectAddr)
-		if err != nil {
-			fmt.Printf("error: Failed to connect to target: %v\n", err)
-			srcConn.Close()
-			continue
+			genericRelay(srcConn, dstConn, false)
 		}
+	} else {
+		// begin proxying
+		for {
+			// Accept a new client
+			srcConn, err = listener.Accept()
+			if err != nil {
+				if terminate {
+					break
+				}
 
-		if cAddr.connType == "unix" && lAddr.connType == "unix" {
-			// Handle OOB if both src and dst are using unix sockets
-			go unixRelay(srcConn, dstConn)
-		} else {
-			go genericRelay(srcConn, dstConn)
+				fmt.Printf("error: Failed to accept new connection: %v\n", err)
+				continue
+			}
+			fmt.Printf("Accepted a new connection\n")
+
+			// Connect to the target
+			dstConn, err := getDestConn(connectAddr)
+			if err != nil {
+				fmt.Printf("error: Failed to connect to target: %v\n", err)
+				if lAddr.connType != "udp" {
+					srcConn.Close()
+				}
+
+				continue
+			}
+
+			if cAddr.connType == "unix" && lAddr.connType == "unix" {
+				// Handle OOB if both src and dst are using unix sockets
+				go unixRelay(srcConn, dstConn)
+			} else {
+				go genericRelay(srcConn, dstConn, true)
+			}
 		}
 	}
 
@@ -406,23 +450,33 @@ func (c *cmdForkproxy) Run(cmd *cobra.Command, args []string) error {
 	return nil
 }
 
-func genericRelay(dst io.ReadWriteCloser, src io.ReadWriteCloser) {
-	relayer := func(dst io.Writer, src io.Reader, ch chan bool) {
-		io.Copy(eagain.Writer{Writer: dst}, eagain.Reader{Reader: src})
-		ch <- true
+func genericRelay(dst io.ReadWriteCloser, src io.ReadWriteCloser, closeDst bool) {
+	relayer := func(dst io.Writer, src io.Reader, ch chan error) {
+		_, err := io.Copy(eagain.Writer{Writer: dst}, eagain.Reader{Reader: src})
+		ch <- err
 	}
 
-	chSend := make(chan bool)
+	chSend := make(chan error)
 	go relayer(dst, src, chSend)
 
-	chRecv := make(chan bool)
+	chRecv := make(chan error)
 	go relayer(src, dst, chRecv)
 
-	<-chSend
-	<-chRecv
+	errSnd := <-chSend
+	errRcv := <-chRecv
 
 	src.Close()
-	dst.Close()
+	if closeDst {
+		dst.Close()
+	}
+
+	if errSnd != nil {
+		fmt.Printf("Error while sending data %s\n", errSnd)
+	}
+
+	if errRcv != nil {
+		fmt.Printf("Error while reading data %s\n", errRcv)
+	}
 }
 
 func unixRelayer(src *net.UnixConn, dst *net.UnixConn, ch chan bool) {
@@ -529,13 +583,49 @@ func tryListen(protocol string, addr string) (net.Listener, error) {
 	return listener, nil
 }
 
-func getListenerFile(listenAddr string) (os.File, error) {
+func tryListenUDP(protocol string, addr string) (*os.File, error) {
+	var UDPConn *net.UDPConn
+	var err error
+
+	udpAddr, err := net.ResolveUDPAddr(protocol, addr)
+	if err != nil {
+		return nil, err
+	}
+
+	for i := 0; i < 10; i++ {
+		UDPConn, err = net.ListenUDP(protocol, udpAddr)
+		if err == nil {
+			file, err := UDPConn.File()
+			UDPConn.Close()
+			return file, err
+		}
+
+		time.Sleep(500 * time.Millisecond)
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	if UDPConn == nil {
+		return nil, fmt.Errorf("Failed to setup UDP listener")
+	}
+
+	file, err := UDPConn.File()
+	UDPConn.Close()
+	return file, err
+}
+
+func getListenerFile(listenAddr string) (*os.File, error) {
 	fields := strings.SplitN(listenAddr, ":", 2)
 	addr := strings.Join(fields[1:], "")
 
+	if fields[0] == "udp" {
+		return tryListenUDP(fields[0], addr)
+	}
+
 	listener, err := tryListen(fields[0], addr)
 	if err != nil {
-		return os.File{}, fmt.Errorf("Failed to listen on %s: %v", addr, err)
+		return nil, fmt.Errorf("Failed to listen on %s: %v", addr, err)
 	}
 
 	file := &os.File{}
@@ -548,10 +638,10 @@ func getListenerFile(listenAddr string) (os.File, error) {
 		file, err = unixListener.File()
 	}
 	if err != nil {
-		return os.File{}, fmt.Errorf("Failed to get file from listener: %v", err)
+		return nil, fmt.Errorf("Failed to get file from listener: %v", err)
 	}
 
-	return *file, nil
+	return file, nil
 }
 
 func getDestConn(connectAddr string) (net.Conn, error) {
diff --git a/lxd/proxy_device_utils.go b/lxd/proxy_device_utils.go
index 63d815dfd..995d0b42a 100644
--- a/lxd/proxy_device_utils.go
+++ b/lxd/proxy_device_utils.go
@@ -31,10 +31,10 @@ func setupProxyProcInfo(c container, device map[string]string) (*proxyProcInfo,
 	connectionType := strings.SplitN(connectAddr, ":", 2)[0]
 	listenerType := strings.SplitN(listenAddr, ":", 2)[0]
 
-	if !shared.StringInSlice(connectionType, []string{"tcp", "unix"}) {
+	if !shared.StringInSlice(connectionType, []string{"tcp", "udp", "unix"}) {
 		return nil, fmt.Errorf("Proxy device doesn't support the connection type: %s", connectionType)
 	}
-	if !shared.StringInSlice(listenerType, []string{"tcp", "unix"}) {
+	if !shared.StringInSlice(listenerType, []string{"tcp", "udp", "unix"}) {
 		return nil, fmt.Errorf("Proxy device doesn't support the listener type: %s", listenerType)
 	}
 

From ff8ee52dc0ebffb633655c17368fd067f47100d8 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 12 Jun 2018 15:31:33 +0200
Subject: [PATCH 12/13] api: proxy_udp

Closes #4566.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 doc/api-extensions.md | 15 +++++++++++++++
 doc/containers.md     |  5 +++++
 shared/version/api.go |  1 +
 3 files changed, 21 insertions(+)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index fe2e3f41e..f767b0186 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -498,3 +498,18 @@ Supported connections are now:
 * `UNIX <-> UNIX`
 * `TCP <-> UNIX`
 * `UNIX <-> TCP`
+
+## proxy_udp
+Add support for udp in proxy devices.
+
+Supported connections are now:
+
+* `TCP <-> TCP`
+* `UNIX <-> UNIX`
+* `TCP <-> UNIX`
+* `UNIX <-> TCP`
+* `UDP <-> UDP`
+* `UDP <-> TCP`
+* `TCP <-> UDP`
+* `UDP <-> UNIX`
+* `UNIX <-> UDP`
diff --git a/doc/containers.md b/doc/containers.md
index 2f716eaa3..26107e113 100644
--- a/doc/containers.md
+++ b/doc/containers.md
@@ -422,9 +422,14 @@ have an address in the container connect through the host.
 
 The supported connection types are:
 * `TCP <-> TCP`
+* `UDP <-> UDP`
 * `UNIX <-> UNIX`
 * `TCP <-> UNIX`
 * `UNIX <-> TCP`
+* `UDP <-> TCP`
+* `TCP <-> UDP`
+* `UDP <-> UNIX`
+* `UNIX <-> UDP`
 
 Key         | Type      | Default           | Required  | Description
 :--         | :--       | :--               | :--       | :--
diff --git a/shared/version/api.go b/shared/version/api.go
index e0208a277..b4a3e0e93 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -107,6 +107,7 @@ var APIExtensions = []string{
 	"devlxd_images",
 	"container_local_cross_pool_handling",
 	"proxy_unix",
+	"proxy_udp",
 }
 
 // APIExtensionsCount returns the number of available API extensions.

From 0d198c6e076cd0dcf80ced1f5985372357f03907 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 12 Jun 2018 15:54:37 +0200
Subject: [PATCH 13/13] tests: add udp proxy tests

Closes #4566.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 test/suites/proxy.sh | 312 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 312 insertions(+)

diff --git a/test/suites/proxy.sh b/test/suites/proxy.sh
index 9685b2601..57f2684c5 100755
--- a/test/suites/proxy.sh
+++ b/test/suites/proxy.sh
@@ -1,8 +1,13 @@
 test_proxy_device() {
   test_proxy_device_tcp
+  test_proxy_device_udp
+  test_proxy_device_udp_unix
+  test_proxy_device_unix_udp
   test_proxy_device_unix
   test_proxy_device_tcp_unix
   test_proxy_device_unix_tcp
+  test_proxy_device_udp_tcp
+  test_proxy_device_tcp_udp
 }
 
 test_proxy_device_tcp() {
@@ -269,3 +274,310 @@ test_proxy_device_unix_tcp() {
   # Cleanup
   lxc delete -f proxyTester
 }
+
+test_proxy_device_udp() {
+  ensure_import_testimage
+  ensure_has_localhost_remote "${LXD_ADDR}"
+
+  # Setup
+  MESSAGE="Proxy device test string"
+  HOST_UDP_PORT=$(local_tcp_port)
+  lxc launch testimage proxyTester
+
+  # Initial test
+  lxc config device add proxyTester proxyDev proxy "listen=udp:127.0.0.1:$HOST_UDP_PORT" connect=udp:127.0.0.1:4321 bind=host
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -u -l 4321 > proxyTest.out &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -u 127.0.0.1 "${HOST_UDP_PORT}"
+
+  if [ "$(cat proxyTest.out)" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly send data from host to container"
+    false
+  fi
+
+  rm -f proxyTest.out
+
+  # Restart the container
+  lxc restart -f proxyTester
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -u -l 4321 > proxyTest.out &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -u 127.0.0.1 "${HOST_UDP_PORT}"
+
+  if [ "$(cat proxyTest.out)" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart on container restart"
+    false
+  fi
+
+  rm -f proxyTest.out
+
+  # Change the port
+  lxc config device set proxyTester proxyDev connect udp:127.0.0.1:1337
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -u -l 1337 > proxyTest.out &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -u 127.0.0.1 "${HOST_UDP_PORT}"
+
+  if [ "$(cat proxyTest.out)" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart when config was updated"
+    false
+  fi
+
+  rm -f proxyTest.out
+
+  # Cleanup
+  lxc delete -f proxyTester
+}
+
+test_proxy_device_udp_unix() {
+  ensure_import_testimage
+  ensure_has_localhost_remote "${LXD_ADDR}"
+
+  # Setup
+  MESSAGE="Proxy device test string"
+  HOST_UDP_PORT=$(local_tcp_port)
+  OUTFILE="${TEST_DIR}/proxyTest.out"
+  lxc launch testimage proxyTester
+
+  # Initial test
+  lxc config device add proxyTester proxyDev proxy "listen=udp:127.0.0.1:${HOST_UDP_PORT}" connect=unix:/tmp/"lxdtest-$(basename "${LXD_DIR}").sock" bind=host
+  (
+    cd "${LXD_DIR}/containers/proxyTester/rootfs/tmp/" || exit
+    umask 0000
+    rm -f "lxdtest-$(basename "${LXD_DIR}").sock"
+    exec nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -l -U "lxdtest-$(basename "${LXD_DIR}").sock" > "${OUTFILE}"
+  ) &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -u 127.0.0.1 "${HOST_UDP_PORT}"
+
+  if [ "$(cat "${OUTFILE}")" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly send data from host to container"
+    false
+  fi
+
+  rm -f "${OUTFILE}"
+
+  # Restart the container
+  lxc restart -f proxyTester
+  (
+    cd "${LXD_DIR}/containers/proxyTester/rootfs/tmp/" || exit
+    umask 0000
+    rm -f "lxdtest-$(basename "${LXD_DIR}").sock"
+    exec nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -l -U "lxdtest-$(basename "${LXD_DIR}").sock" > "${OUTFILE}"
+  ) &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -u 127.0.0.1 "${HOST_UDP_PORT}"
+
+  if [ "$(cat "${OUTFILE}")" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart on container restart"
+    false
+  fi
+
+  rm -f "${OUTFILE}"
+
+  # Change the socket
+  lxc config device set proxyTester proxyDev connect unix:/tmp/"lxdtest-$(basename "${LXD_DIR}")-2.sock"
+  (
+    cd "${LXD_DIR}/containers/proxyTester/rootfs/tmp/" || exit
+    umask 0000
+    rm -f "lxdtest-$(basename "${LXD_DIR}")-2.sock"
+    exec nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -l -U "lxdtest-$(basename "${LXD_DIR}")-2.sock" > "${OUTFILE}"
+  ) &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -u 127.0.0.1 "${HOST_UDP_PORT}"
+
+  if [ "$(cat "${OUTFILE}")" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart when config was updated"
+    false
+  fi
+
+  rm -f "${OUTFILE}"
+
+  # Cleanup
+  lxc delete -f proxyTester
+}
+
+test_proxy_device_unix_udp() {
+  ensure_import_testimage
+  ensure_has_localhost_remote "${LXD_ADDR}"
+
+  # Setup
+  MESSAGE="Proxy device test string"
+  OUTFILE="${TEST_DIR}/proxyTest.out"
+  HOST_SOCK="${TEST_DIR}/lxdtest-$(basename "${LXD_DIR}")-host.sock"
+  lxc launch testimage proxyTester
+
+  # Initial test
+  lxc config device add proxyTester proxyDev proxy "listen=unix:${HOST_SOCK}" connect=udp:127.0.0.1:4321 bind=host
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -u -l 4321 > "${OUTFILE}" &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -U "${HOST_SOCK#$(pwd)/}"
+
+  if [ "$(cat "${OUTFILE}")" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly send data from host to container"
+    false
+  fi
+
+  rm -f "${OUTFILE}" "${HOST_SOCK}"
+
+  # Restart the container
+  lxc restart -f proxyTester
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -u -l 4321 > "${OUTFILE}" &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -U "${HOST_SOCK#$(pwd)/}"
+
+  if [ "$(cat "${OUTFILE}")" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart on container restart"
+    false
+  fi
+
+  rm -f "${OUTFILE}" "${HOST_SOCK}"
+
+  # Change the port
+  lxc config device set proxyTester proxyDev connect udp:127.0.0.1:1337
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -u -l 1337 > "${OUTFILE}" &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -U "${HOST_SOCK#$(pwd)/}"
+
+  if [ "$(cat "${OUTFILE}")" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart when config was updated"
+    false
+  fi
+
+  rm -f "${OUTFILE}" "${HOST_SOCK}"
+
+  # Cleanup
+  lxc delete -f proxyTester
+}
+
+test_proxy_device_udp_tcp() {
+  ensure_import_testimage
+  ensure_has_localhost_remote "${LXD_ADDR}"
+
+  # Setup
+  MESSAGE="Proxy device test string"
+  HOST_UDP_PORT=$(local_tcp_port)
+  lxc launch testimage proxyTester
+
+  # Initial test
+  lxc config device add proxyTester proxyDev proxy "listen=udp:127.0.0.1:$HOST_UDP_PORT" connect=tcp:127.0.0.1:4321 bind=host
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -6 -l 4321 > proxyTest.out &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -u 127.0.0.1 "${HOST_UDP_PORT}"
+
+  if [ "$(cat proxyTest.out)" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly send data from host to container"
+    false
+  fi
+
+  rm -f proxyTest.out
+
+  # Restart the container
+  lxc restart -f proxyTester
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -6 -l 4321 > proxyTest.out &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -u 127.0.0.1 "${HOST_UDP_PORT}"
+
+  if [ "$(cat proxyTest.out)" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart on container restart"
+    false
+  fi
+
+  rm -f proxyTest.out
+
+  # Change the port
+  lxc config device set proxyTester proxyDev connect tcp:127.0.0.1:1337
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -6 -l 1337 > proxyTest.out &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 -u 127.0.0.1 "${HOST_UDP_PORT}"
+
+  if [ "$(cat proxyTest.out)" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart when config was updated"
+    false
+  fi
+
+  rm -f proxyTest.out
+
+  # Cleanup
+  lxc delete -f proxyTester
+}
+
+test_proxy_device_tcp_udp() {
+  ensure_import_testimage
+  ensure_has_localhost_remote "${LXD_ADDR}"
+
+  # Setup
+  MESSAGE="Proxy device test string"
+  HOST_TCP_PORT=$(local_tcp_port)
+  lxc launch testimage proxyTester
+
+  # Initial test
+  lxc config device add proxyTester proxyDev proxy "listen=tcp:127.0.0.1:$HOST_TCP_PORT" connect=udp:127.0.0.1:4321 bind=host
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -u -l 4321 > proxyTest.out &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 127.0.0.1 "${HOST_TCP_PORT}"
+
+  if [ "$(cat proxyTest.out)" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly send data from host to container"
+    false
+  fi
+
+  rm -f proxyTest.out
+
+  # Restart the container
+  lxc restart -f proxyTester
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -u -l 4321 > proxyTest.out &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 127.0.0.1 "${HOST_TCP_PORT}"
+
+  if [ "$(cat proxyTest.out)" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart on container restart"
+    false
+  fi
+
+  rm -f proxyTest.out
+
+  # Change the port
+  lxc config device set proxyTester proxyDev connect udp:127.0.0.1:1337
+  nsenter -n -U -t "$(lxc query /1.0/containers/proxyTester/state | jq .pid)" -- nc -u -l 1337 > proxyTest.out &
+  sleep 2
+
+  echo "${MESSAGE}" | nc -w1 127.0.0.1 "${HOST_TCP_PORT}"
+
+  if [ "$(cat proxyTest.out)" != "${MESSAGE}" ]; then
+    cat "${LXD_DIR}/logs/proxyTester/proxy.proxyDev.log"
+    echo "Proxy device did not properly restart when config was updated"
+    false
+  fi
+
+  rm -f proxyTest.out
+
+  # Cleanup
+  lxc delete -f proxyTester
+}


More information about the lxc-devel mailing list