The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/4775
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) ===
From 1f9752260266a8c1b8ed02014284c6a5b792cae4 Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Mon, 9 Jul 2018 15:03:49 +0200 Subject: [PATCH 1/2] lxd: Optimized UDP/TCP proxying (NAT) Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- lxd/container_lxc.go | 101 ++++++++++++++++++++++++++++++ lxd/{networks_iptables.go => iptables.go} | 35 +++++++++-- 2 files changed, 130 insertions(+), 6 deletions(-) rename lxd/{networks_iptables.go => iptables.go} (62%) diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go index 2cee8ad1d..afad5b06d 100644 --- a/lxd/container_lxc.go +++ b/lxd/container_lxc.go @@ -6850,6 +6850,10 @@ func (c *containerLXC) insertProxyDevice(devName string, m types.Device) error { return fmt.Errorf("Can't add proxy device to stopped container") } + if c.tryNat(devName, m) { + return nil + } + proxyValues, err := setupProxyProcInfo(c, m) if err != nil { return err @@ -6881,13 +6885,107 @@ func (c *containerLXC) insertProxyDevice(devName string, m types.Device) error { return nil } +func (c *containerLXC) tryNat(proxy string, device types.Device) bool { + listenAddr, err := parseAddr(device["listen"]) + if err != nil { + return false + } + + connectAddr, err := parseAddr(device["connect"]) + if err != nil { + return false + } + + cAddrFields := strings.SplitN(connectAddr.addr[0], ":", 2) + validIP := false + + for _, name := range c.expandedDevices.DeviceNames() { + m := c.expandedDevices[name] + if m["type"] == "nic" { + if m["nictype"] != "bridged" { + continue + } + + // Check whether the NIC has a static IP + ip, ok := m["ipv4.address"] + if ok && ip == cAddrFields[0] { + validIP = true + break + } + } + } + + if !validIP { + logger.Info("NAT unavailable: NIC IP doesn't match proxy target IP") + return false + } + + if len(connectAddr.addr) > len(listenAddr.addr) { + // Cannot support single port -> multiple port + return false + } + + // Support TCP <-> TCP and UDP <-> UDP + if listenAddr.connType == "unix" || connectAddr.connType == "unix" || + listenAddr.connType != connectAddr.connType { + logger.Info(fmt.Sprintf("NAT unavailable: %s <-> %s not supported", + listenAddr.connType, connectAddr.connType)) + return false + } + + iptablesComment := fmt.Sprintf("%s (%s)", c.Name(), proxy) + + for i, lAddr := range listenAddr.addr { + listenFields := strings.SplitN(lAddr, ":", 2) + var cAddr string + if len(connectAddr.addr) == 1 { + cAddr = connectAddr.addr[0] + } else { + cAddr = connectAddr.addr[i] + } + + // outbound <-> container + err := containerIptablesPrepend("ipv4", iptablesComment, "nat", + "PREROUTING", "-p", listenAddr.connType, "--destination", + listenFields[0], "--dport", listenFields[1], "-j", "DNAT", + "--to-destination", cAddr) + if err != nil { + goto fail + } + + // host <-> container + err = containerIptablesPrepend("ipv4", iptablesComment, "nat", + "OUTPUT", "-p", listenAddr.connType, "--destination", + listenFields[0], "--dport", listenFields[1], "-j", "DNAT", + "--to-destination", cAddr) + if err != nil { + goto fail + } + } + + logger.Info("Using NAT for proxy device '%s'", proxy) + return true + +fail: + containerIptablesClear("ipv4", iptablesComment, "nat") + return false +} + func (c *containerLXC) removeProxyDevice(devName string) error { if !c.IsRunning() { return fmt.Errorf("Can't remove proxy device from stopped container") } + // Remove possible iptables entries + containerIptablesClear("ipv4", fmt.Sprintf("%s (%s)", c.Name(), devName), "nat") + devFileName := fmt.Sprintf("proxy.%s", devName) devPath := filepath.Join(c.DevicesPath(), devFileName) + if !shared.PathExists(devPath) { + // There's no proxy process if NAT is enabled + return nil + } + err := killProxyProc(devPath) if err != nil { return err @@ -6897,6 +6995,9 @@ func (c *containerLXC) removeProxyDevice(devName string) error { } func (c *containerLXC) removeProxyDevices() error { + // Remove possible iptables entries + containerIptablesClear("ipv4", fmt.Sprintf("%s", c.Name()), "nat") + // Check that we actually have devices to remove if !shared.PathExists(c.DevicesPath()) { return nil diff --git a/lxd/networks_iptables.go b/lxd/iptables.go similarity index 62% rename from lxd/networks_iptables.go rename to lxd/iptables.go index 1c0c2bc88..313a48014 100644 --- a/lxd/networks_iptables.go +++ b/lxd/iptables.go @@ -8,7 +8,8 @@ import ( "github.com/lxc/lxd/shared" ) -func networkIptablesPrepend(protocol string, netName string, table string, chain string, rule ...string) error { +func iptablesPrepend(protocol string, comment string, table string, chain string, + rule ...string) error { cmd := "iptables" if protocol == "ipv6" { cmd = "ip6tables" @@ -28,7 +29,7 @@ func networkIptablesPrepend(protocol string, netName string, table string, chain // Check for an existing entry args := append(baseArgs, []string{"-C", chain}...) args = append(args, rule...) - args = append(args, "-m", "comment", "--comment", fmt.Sprintf("generated for LXD network %s", netName)) + args = append(args, "-m", "comment", "--comment", fmt.Sprintf("generated for %s", comment)) _, err = shared.RunCommand(cmd, args...) if err == nil { return nil @@ -37,7 +38,7 @@ func networkIptablesPrepend(protocol string, netName string, table string, chain // Add the rule args = append(baseArgs, []string{"-I", chain}...) args = append(args, rule...) - args = append(args, "-m", "comment", "--comment", fmt.Sprintf("generated for LXD network %s", netName)) + args = append(args, "-m", "comment", "--comment", fmt.Sprintf("generated for %s", comment)) _, err = shared.TryRunCommand(cmd, args...) if err != nil { @@ -47,7 +48,7 @@ func networkIptablesPrepend(protocol string, netName string, table string, chain return nil } -func networkIptablesClear(protocol string, netName string, table string) error { +func iptablesClear(protocol string, comment string, table string) error { // Detect kernels that lack IPv6 support if !shared.PathExists("/proc/sys/net/ipv6") && protocol == "ipv6" { return nil @@ -73,11 +74,11 @@ func networkIptablesClear(protocol string, netName string, table string) error { args := append(baseArgs, "-S") output, err := shared.TryRunCommand(cmd, args...) if err != nil { - return fmt.Errorf("Failed to list %s rules for %s (table %s)", protocol, netName, table) + return fmt.Errorf("Failed to list %s rules for %s (table %s)", protocol, comment, table) } for _, line := range strings.Split(output, "\n") { - if !strings.Contains(line, fmt.Sprintf("generated for LXD network %s", netName)) { + if !strings.Contains(line, fmt.Sprintf("generated for %s", comment)) { continue } @@ -94,3 +95,25 @@ func networkIptablesClear(protocol string, netName string, table string) error { return nil } + +func networkIptablesPrepend(protocol string, comment string, table string, chain string, + rule ...string) error { + return iptablesPrepend(protocol, fmt.Sprintf("LXD network %s", comment), + table, chain, rule...) +} + +func networkIptablesClear(protocol string, comment string, table string) error { + return iptablesClear(protocol, fmt.Sprintf("LXD network %s", comment), + table) +} + +func containerIptablesPrepend(protocol string, comment string, table string, + chain string, rule ...string) error { + return iptablesPrepend(protocol, fmt.Sprintf("LXD container %s", comment), + table, chain, rule...) +} + +func containerIptablesClear(protocol string, comment string, table string) error { + return iptablesClear(protocol, fmt.Sprintf("LXD container %s", comment), + table) +} From 4259015631623ca27fabc491f51f904d07002ac9 Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Wed, 11 Jul 2018 15:39:12 +0200 Subject: [PATCH 2/2] test: Add NAT tests Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- test/suites/proxy.sh | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/suites/proxy.sh b/test/suites/proxy.sh index 9a75d9084..94ddf6ec5 100755 --- a/test/suites/proxy.sh +++ b/test/suites/proxy.sh @@ -92,6 +92,42 @@ test_proxy_device_tcp() { # Cleanup lxc delete -f proxyTester + + # Try NAT + lxc init testimage nattest + + lxc network create lxdt$$ dns.domain=test dns.mode=managed + lxc network attach lxdt$$ nattest eth0 + v4_addr="$(lxc network get lxdt$$ ipv4.address | cut -d/ -f1)0" + lxc config device set nattest eth0 ipv4.address "${v4_addr}" + + lxc start nattest + [ "$(iptables -t nat -S | grep -c "generated for LXD container nattest (proxyDev)")" -eq 0 ] + + lxc config device add nattest validNAT proxy listen="tcp:127.0.0.1:1234" connect="tcp:${v4_addr}:1234" + [ "$(iptables -t nat -S | grep -c "generated for LXD container nattest (validNAT)")" -eq 2 ] + + lxc config device remove nattest validNAT + [ "$(iptables -t nat -S | grep -c "generated for LXD container nattest (validNAT)")" -eq 0 ] + + lxc config device add nattest validNAT proxy listen="tcp:127.0.0.1:1234-1235" connect="tcp:${v4_addr}:1234" + [ "$(iptables -t nat -S | grep -c "generated for LXD container nattest (validNAT)")" -eq 4 ] + + lxc config device remove nattest validNAT + [ "$(iptables -t nat -S | grep -c "generated for LXD container nattest (validNAT)")" -eq 0 ] + + lxc config device add nattest validNAT proxy listen="tcp:127.0.0.1:1234-1235" connect="tcp:${v4_addr}:1234-1235" + [ "$(iptables -t nat -S | grep -c "generated for LXD container nattest (validNAT)")" -eq 4 ] + + # This won't enable NAT + lxc config device add nattest invalidNAT proxy listen="tcp:127.0.0.1:1234" connect="udp:${v4_addr}:1234" + [ "$(iptables -t nat -S | grep -c "generated for LXD container nattest (invalidNAT)")" -eq 0 ] + [ "$(iptables -t nat -S | grep -c "generated for LXD container nattest (validNAT)")" -eq 4 ] + + lxc delete -f nattest + [ "$(iptables -t nat -S | grep -c "generated for LXD container nattest (validNAT)")" -eq 0 ] + + lxc network delete lxdt$$ } test_proxy_device_unix() {
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel