The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/4642
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) === Signed-off-by: Christian Brauner <[email protected]>
From 6adb2a803ef2a9575ee25bee5d96bff173608f8a Mon Sep 17 00:00:00 2001 From: Christian Brauner <[email protected]> Date: Tue, 12 Jun 2018 13:04:09 +0200 Subject: [PATCH 01/13] memory: fix format string Signed-off-by: Christian Brauner <[email protected]> --- 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 <[email protected]> Date: Tue, 12 Jun 2018 13:04:50 +0200 Subject: [PATCH 02/13] exec: fix format string Signed-off-by: Christian Brauner <[email protected]> --- 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 <[email protected]> Date: Tue, 12 Jun 2018 13:05:45 +0200 Subject: [PATCH 03/13] images: fix format string Signed-off-by: Christian Brauner <[email protected]> --- 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 <[email protected]> Date: Tue, 12 Jun 2018 13:06:54 +0200 Subject: [PATCH 04/13] migrate: remove debug residuals Signed-off-by: Christian Brauner <[email protected]> --- 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 <[email protected]> Date: Tue, 12 Jun 2018 13:07:41 +0200 Subject: [PATCH 05/13] lvm: fix format string Signed-off-by: Christian Brauner <[email protected]> --- 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 <[email protected]> Date: Tue, 12 Jun 2018 13:08:15 +0200 Subject: [PATCH 06/13] db: fix format string Signed-off-by: Christian Brauner <[email protected]> --- 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 <[email protected]> Date: Tue, 12 Jun 2018 20:03:38 +0200 Subject: [PATCH 07/13] nsexec: prevent fd leak Signed-off-by: Christian Brauner <[email protected]> --- 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 <[email protected]> Date: Fri, 25 May 2018 19:53:23 +0200 Subject: [PATCH 08/13] proxy: unix Closes #4167. Signed-off-by: Christian Brauner <[email protected]> Signed-off-by: Thomas Hipp <[email protected]> --- 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 <[email protected]> Date: Tue, 29 May 2018 20:04:25 +0200 Subject: [PATCH 09/13] api: proxy_unix Closes #4167. Signed-off-by: Thomas Hipp <[email protected]> --- 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 <[email protected]> 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 <[email protected]> Signed-off-by: Christian Brauner <[email protected]> --- 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 <[email protected]> Date: Tue, 12 Jun 2018 15:28:49 +0200 Subject: [PATCH 11/13] proxy: udp Closes #4566. Signed-off-by: Christian Brauner <[email protected]> --- 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 <[email protected]> Date: Tue, 12 Jun 2018 15:31:33 +0200 Subject: [PATCH 12/13] api: proxy_udp Closes #4566. Signed-off-by: Christian Brauner <[email protected]> --- 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 <[email protected]> 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 <[email protected]> --- 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 +}
_______________________________________________ lxc-devel mailing list [email protected] http://lists.linuxcontainers.org/listinfo/lxc-devel
