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

Reply via email to