Browse Source

Add `--userland-proxy` daemon flag

The `--userland-proxy` daemon flag makes it possible to rely on hairpin
NAT and additional iptables routes instead of userland proxy for port
publishing and inter-container communication.

Usage of the userland proxy remains the default as hairpin NAT is
unsupported by older kernels.

Signed-off-by: Arnaud Porterie <arnaud.porterie@docker.com>
Arnaud Porterie 10 years ago
parent
commit
f42348e18f

+ 1 - 0
daemon/config.go

@@ -79,6 +79,7 @@ func (config *Config) InstallFlags() {
 	config.Ulimits = make(map[string]*ulimit.Ulimit)
 	opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers")
 	flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Default driver for container logs")
+	flag.BoolVar(&config.Bridge.EnableUserlandProxy, []string{"-userland-proxy"}, true, "Use userland proxy for loopback traffic")
 }
 
 func getDefaultNetworkMtu() int {

+ 1 - 0
daemon/container.go

@@ -307,6 +307,7 @@ func populateCommand(c *Container, env []string) error {
 				GlobalIPv6Address:    network.GlobalIPv6Address,
 				GlobalIPv6PrefixLen:  network.GlobalIPv6PrefixLen,
 				IPv6Gateway:          network.IPv6Gateway,
+				HairpinMode:          network.HairpinMode,
 			}
 		}
 	case "container":

+ 1 - 0
daemon/execdriver/driver.go

@@ -96,6 +96,7 @@ type NetworkInterface struct {
 	LinkLocalIPv6Address string `json:"link_local_ipv6"`
 	GlobalIPv6PrefixLen  int    `json:"global_ipv6_prefix_len"`
 	IPv6Gateway          string `json:"ipv6_gateway"`
+	HairpinMode          bool   `json:"hairpin_mode"`
 }
 
 // TODO Windows: Factor out ulimit.Rlimit

+ 1 - 0
daemon/execdriver/native/create.go

@@ -114,6 +114,7 @@ func (d *driver) createNetwork(container *configs.Config, c *execdriver.Command)
 			Gateway:           c.Network.Interface.Gateway,
 			Type:              "veth",
 			Bridge:            c.Network.Interface.Bridge,
+			HairpinMode:       c.Network.Interface.HairpinMode,
 		}
 		if c.Network.Interface.GlobalIPv6Address != "" {
 			vethNetwork.IPv6Address = fmt.Sprintf("%s/%d", c.Network.Interface.GlobalIPv6Address, c.Network.Interface.GlobalIPv6PrefixLen)

+ 1 - 0
daemon/network/settings.go

@@ -15,4 +15,5 @@ type Settings struct {
 	Bridge                 string
 	PortMapping            map[string]map[string]string // Deprecated
 	Ports                  nat.PortMap
+	HairpinMode            bool
 }

+ 34 - 9
daemon/networkdriver/bridge/driver.go

@@ -8,6 +8,7 @@ import (
 	"net"
 	"os"
 	"os/exec"
+	"path/filepath"
 	"strconv"
 	"strings"
 	"sync"
@@ -83,6 +84,7 @@ var (
 	gatewayIPv6       net.IP
 	portMapper        *portmapper.PortMapper
 	once              sync.Once
+	hairpinMode       bool
 
 	defaultBindingIP  = net.ParseIP("0.0.0.0")
 	currentInterfaces = ifaces{c: make(map[string]*networkInterface)}
@@ -100,6 +102,7 @@ type Config struct {
 	EnableIptables              bool
 	EnableIpForward             bool
 	EnableIpMasq                bool
+	EnableUserlandProxy         bool
 	DefaultIp                   net.IP
 	Iface                       string
 	IP                          string
@@ -131,6 +134,8 @@ func InitDriver(config *Config) error {
 		defaultBindingIP = config.DefaultIp
 	}
 
+	hairpinMode = !config.EnableUserlandProxy
+
 	bridgeIface = config.Iface
 	usingDefaultBridge := false
 	if bridgeIface == "" {
@@ -243,39 +248,46 @@ func InitDriver(config *Config) error {
 	if config.EnableIpForward {
 		// Enable IPv4 forwarding
 		if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte{'1', '\n'}, 0644); err != nil {
-			logrus.Warnf("WARNING: unable to enable IPv4 forwarding: %s\n", err)
+			logrus.Warnf("Unable to enable IPv4 forwarding: %v", err)
 		}
 
 		if config.FixedCIDRv6 != "" {
 			// Enable IPv6 forwarding
 			if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/default/forwarding", []byte{'1', '\n'}, 0644); err != nil {
-				logrus.Warnf("WARNING: unable to enable IPv6 default forwarding: %s\n", err)
+				logrus.Warnf("Unable to enable IPv6 default forwarding: %v", err)
 			}
 			if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/all/forwarding", []byte{'1', '\n'}, 0644); err != nil {
-				logrus.Warnf("WARNING: unable to enable IPv6 all forwarding: %s\n", err)
+				logrus.Warnf("Unable to enable IPv6 all forwarding: %v", err)
 			}
 		}
 	}
 
+	if hairpinMode {
+		// Enable loopback adresses routing
+		sysPath := filepath.Join("/proc/sys/net/ipv4/conf", bridgeIface, "route_localnet")
+		if err := ioutil.WriteFile(sysPath, []byte{'1', '\n'}, 0644); err != nil {
+			logrus.Warnf("Unable to enable local routing for hairpin mode: %v", err)
+		}
+	}
+
 	// We can always try removing the iptables
 	if err := iptables.RemoveExistingChain("DOCKER", iptables.Nat); err != nil {
 		return err
 	}
 
 	if config.EnableIptables {
-		_, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Nat)
+		_, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Nat, hairpinMode)
 		if err != nil {
 			return err
 		}
 		// call this on Firewalld reload
-		iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Nat) })
-
-		chain, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter)
+		iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Nat, hairpinMode) })
+		chain, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter, hairpinMode)
 		if err != nil {
 			return err
 		}
 		// call this on Firewalld reload
-		iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Filter) })
+		iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Filter, hairpinMode) })
 
 		portMapper.SetIptablesChain(chain)
 	}
@@ -374,6 +386,18 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error {
 		}
 	}
 
+	// In hairpin mode, masquerade traffic from localhost
+	if hairpinMode {
+		masqueradeArgs := []string{"-t", "nat", "-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "MASQUERADE"}
+		if !iptables.Exists(iptables.Filter, "POSTROUTING", masqueradeArgs...) {
+			if output, err := iptables.Raw(append([]string{"-I", "POSTROUTING"}, masqueradeArgs...)...); err != nil {
+				return fmt.Errorf("Unable to masquerade local traffic: %s", err)
+			} else if len(output) != 0 {
+				return fmt.Errorf("Error iptables masquerade local traffic: %s", output)
+			}
+		}
+	}
+
 	// Accept all non-intercontainer outgoing packets
 	outgoingArgs := []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}
 	if !iptables.Exists(iptables.Filter, "FORWARD", outgoingArgs...) {
@@ -637,6 +661,7 @@ func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Set
 		Bridge:               bridgeIface,
 		IPPrefixLen:          maskSize,
 		LinkLocalIPv6Address: localIPv6.String(),
+		HairpinMode:          hairpinMode,
 	}
 
 	if globalIPv6Network != nil {
@@ -722,7 +747,7 @@ func AllocatePort(id string, port nat.Port, binding nat.PortBinding) (nat.PortBi
 		return nat.PortBinding{}, err
 	}
 	for i := 0; i < MaxAllocatedPortAttempts; i++ {
-		if host, err = portMapper.Map(container, ip, hostPort); err == nil {
+		if host, err = portMapper.Map(container, ip, hostPort, !hairpinMode); err == nil {
 			break
 		}
 		// There is no point in immediately retrying to map an explicitly

+ 1 - 1
daemon/networkdriver/bridge/driver_test.go

@@ -177,7 +177,7 @@ func TestLinkContainers(t *testing.T) {
 	}
 
 	bridgeIface = "lo"
-	if _, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter); err != nil {
+	if _, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter, false); err != nil {
 		t.Fatal(err)
 	}
 

+ 20 - 11
daemon/networkdriver/portmapper/mapper.go

@@ -51,7 +51,7 @@ func (pm *PortMapper) SetIptablesChain(c *iptables.Chain) {
 	pm.chain = c
 }
 
-func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err error) {
+func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, useProxy bool) (host net.Addr, err error) {
 	pm.lock.Lock()
 	defer pm.lock.Unlock()
 
@@ -59,7 +59,6 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host
 		m                 *mapping
 		proto             string
 		allocatedHostPort int
-		proxy             UserlandProxy
 	)
 
 	switch container.(type) {
@@ -75,7 +74,9 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host
 			container: container,
 		}
 
-		proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
+		if useProxy {
+			m.userlandProxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
+		}
 	case *net.UDPAddr:
 		proto = "udp"
 		if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
@@ -88,7 +89,9 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host
 			container: container,
 		}
 
-		proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port)
+		if useProxy {
+			m.userlandProxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port)
+		}
 	default:
 		return nil, ErrUnknownBackendAddressType
 	}
@@ -112,7 +115,9 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host
 
 	cleanup := func() error {
 		// need to undo the iptables rules before we return
-		proxy.Stop()
+		if m.userlandProxy != nil {
+			m.userlandProxy.Stop()
+		}
 		pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
 		if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
 			return err
@@ -121,13 +126,15 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host
 		return nil
 	}
 
-	if err := proxy.Start(); err != nil {
-		if err := cleanup(); err != nil {
-			return nil, fmt.Errorf("Error during port allocation cleanup: %v", err)
+	if m.userlandProxy != nil {
+		if err := m.userlandProxy.Start(); err != nil {
+			if err := cleanup(); err != nil {
+				return nil, fmt.Errorf("Error during port allocation cleanup: %v", err)
+			}
+			return nil, err
 		}
-		return nil, err
 	}
-	m.userlandProxy = proxy
+
 	pm.currentMappings[key] = m
 	return m.host, nil
 }
@@ -154,7 +161,9 @@ func (pm *PortMapper) Unmap(host net.Addr) error {
 		return ErrPortNotMapped
 	}
 
-	data.userlandProxy.Stop()
+	if data.userlandProxy != nil {
+		data.userlandProxy.Stop()
+	}
 
 	delete(pm.currentMappings, key)
 

+ 6 - 6
daemon/networkdriver/portmapper/mapper_test.go

@@ -44,22 +44,22 @@ func TestMapPorts(t *testing.T) {
 		return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String())
 	}
 
-	if host, err := pm.Map(srcAddr1, dstIp1, 80); err != nil {
+	if host, err := pm.Map(srcAddr1, dstIp1, 80, true); err != nil {
 		t.Fatalf("Failed to allocate port: %s", err)
 	} else if !addrEqual(dstAddr1, host) {
 		t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
 			dstAddr1.String(), dstAddr1.Network(), host.String(), host.Network())
 	}
 
-	if _, err := pm.Map(srcAddr1, dstIp1, 80); err == nil {
+	if _, err := pm.Map(srcAddr1, dstIp1, 80, true); err == nil {
 		t.Fatalf("Port is in use - mapping should have failed")
 	}
 
-	if _, err := pm.Map(srcAddr2, dstIp1, 80); err == nil {
+	if _, err := pm.Map(srcAddr2, dstIp1, 80, true); err == nil {
 		t.Fatalf("Port is in use - mapping should have failed")
 	}
 
-	if _, err := pm.Map(srcAddr2, dstIp2, 80); err != nil {
+	if _, err := pm.Map(srcAddr2, dstIp2, 80, true); err != nil {
 		t.Fatalf("Failed to allocate port: %s", err)
 	}
 
@@ -127,14 +127,14 @@ func TestMapAllPortsSingleInterface(t *testing.T) {
 	for i := 0; i < 10; i++ {
 		start, end := pm.Allocator.Begin, pm.Allocator.End
 		for i := start; i < end; i++ {
-			if host, err = pm.Map(srcAddr1, dstIp1, 0); err != nil {
+			if host, err = pm.Map(srcAddr1, dstIp1, 0, true); err != nil {
 				t.Fatal(err)
 			}
 
 			hosts = append(hosts, host)
 		}
 
-		if _, err := pm.Map(srcAddr1, dstIp1, start); err == nil {
+		if _, err := pm.Map(srcAddr1, dstIp1, start, true); err == nil {
 			t.Fatalf("Port %d should be bound but is not", start)
 		}
 

+ 9 - 6
docs/man/docker.1.md

@@ -53,6 +53,9 @@ To see the man page for a command run **man docker <command>**.
 **-e**, **--exec-driver**=""
   Force Docker to use specific exec driver. Default is `native`.
 
+**--exec-opt**=[]
+  Set exec driver options. See EXEC DRIVER OPTIONS.
+
 **--fixed-cidr**=""
   IPv4 subnet for fixed IPs (e.g., 10.20.0.0/16); this subnet must be nested in the bridge subnet (which is defined by \-b or \-\-bip)
 
@@ -111,6 +114,9 @@ unix://[/path/to/socket] to use.
 **-s**, **--storage-driver**=""
   Force the Docker runtime to use a specific storage driver.
 
+**--selinux-enabled**=*true*|*false*
+  Enable selinux support. Default is false. SELinux does not presently support the BTRFS storage driver.
+
 **--storage-opt**=[]
   Set storage driver options. See STORAGE DRIVER OPTIONS.
 
@@ -121,15 +127,12 @@ unix://[/path/to/socket] to use.
   Use TLS and verify the remote (daemon: verify client, client: verify daemon).
   Default is false.
 
+**--userland-proxy**=*true*|*false*
+    Rely on a userland proxy implementation for inter-container and outside-to-container loopback communications. Default is true.
+
 **-v**, **--version**=*true*|*false*
   Print version information and quit. Default is false.
 
-**--exec-opt**=[]
-  Set exec driver options. See EXEC DRIVER OPTIONS.
-
-**--selinux-enabled**=*true*|*false*
-  Enable selinux support. Default is false. SELinux does not presently support the BTRFS storage driver.
-
 # COMMANDS
 **attach**
   Attach to a running container

+ 19 - 1
docs/sources/articles/networking.md

@@ -93,6 +93,9 @@ server when it starts up, and cannot be changed once it is running:
  *  `--mtu=BYTES` — see
     [Customizing docker0](#docker0)
 
+ *  `--userland-proxy=true|false` — see
+    [Binding container ports](#binding-ports)
+
 There are two networking options that can be supplied either at startup
 or when `docker run` is invoked.  When provided at startup, set the
 default value that `docker run` will later use if the options are not
@@ -399,7 +402,7 @@ machine that the Docker server creates when it starts:
     ...
     Chain POSTROUTING (policy ACCEPT)
     target     prot opt source               destination
-    MASQUERADE  all  --  172.17.0.0/16       !172.17.0.0/16
+    MASQUERADE  all  --  172.17.0.0/16       0.0.0.0/0
     ...
 
 But if you want containers to accept incoming connections, you will need
@@ -452,6 +455,21 @@ address, you can edit your system-wide Docker server settings and add the
 option `--ip=IP_ADDRESS`.  Remember to restart your Docker server after
 editing this setting.
 
+> **Note**:
+> With hairpin NAT enabled (`--userland-proxy=false`), containers port exposure
+> is achieved purely through iptables rules, and no attempt to bind the exposed
+> port is ever made. This means that nothing prevents shadowing a previously
+> listening service outside of Docker through exposing the same port for a
+> container. In such conflicting situation, Docker created iptables rules will
+> take precedence and route to the container.
+
+The `--userland-proxy` parameter, true by default, provides a userland
+implementation for inter-container and outside-to-container communication. When
+disabled, Docker uses both an additional `MASQUERADE` iptable rule and the
+`net.ipv4.route_localnet` kernel parameter which allow the host machine to
+connect to a local container exposed port through the commonly used loopback
+address: this alternative is preferred for performance reason.
+
 Again, this topic is covered without all of these low-level networking
 details in the [Docker User Guide](/userguide/dockerlinks/) document if you
 would like to use that as your port redirection reference instead.

+ 2 - 1
docs/sources/reference/commandline/cli.md

@@ -149,6 +149,7 @@ expect an integer, and they can only be specified once.
       --default-gateway-v6=""                Container default gateway IPv6 address
       --dns=[]                               DNS server to use
       --dns-search=[]                        DNS search domains to use
+      --default-ulimit=[]                    Set default ulimit settings for containers
       -e, --exec-driver="native"             Exec driver to use
       --fixed-cidr=""                        IPv4 subnet for fixed IPs
       --fixed-cidr-v6=""                     IPv6 subnet for fixed IPs
@@ -177,8 +178,8 @@ expect an integer, and they can only be specified once.
       --tlscert="~/.docker/cert.pem"         Path to TLS certificate file
       --tlskey="~/.docker/key.pem"           Path to TLS key file
       --tlsverify=false                      Use TLS and verify the remote
+      --userland-proxy=true                  Use userland proxy for loopback traffic
       -v, --version=false                    Print version information and quit
-      --default-ulimit=[]                    Set default ulimit settings for containers.
 
 Options with [] may be specified multiple times.
 

+ 60 - 18
integration-cli/docker_cli_nat_test.go

@@ -4,14 +4,26 @@ import (
 	"fmt"
 	"net"
 	"os/exec"
+	"strconv"
 	"strings"
 
 	"github.com/go-check/check"
 )
 
-func (s *DockerSuite) TestNetworkNat(c *check.C) {
-	testRequires(c, SameHostDaemon, NativeExecDriver)
+func startServerContainer(c *check.C, proto string, port int) string {
+	cmd := []string{"-d", "-p", fmt.Sprintf("%d:%d", port, port), "busybox", "nc", "-lp", strconv.Itoa(port)}
+	if proto == "udp" {
+		cmd = append(cmd, "-u")
+	}
 
+	name := "server"
+	if err := waitForContainer(name, cmd...); err != nil {
+		c.Fatalf("Failed to launch server container: %v", err)
+	}
+	return name
+}
+
+func getExternalAddress(c *check.C) net.IP {
 	iface, err := net.InterfaceByName("eth0")
 	if err != nil {
 		c.Skip(fmt.Sprintf("Test not running with `make test`. Interface eth0 not found: %v", err))
@@ -27,35 +39,65 @@ func (s *DockerSuite) TestNetworkNat(c *check.C) {
 		c.Fatalf("Error retrieving the up for eth0: %s", err)
 	}
 
-	runCmd := exec.Command(dockerBinary, "run", "-dt", "-p", "8080:8080", "busybox", "nc", "-lp", "8080")
+	return ifaceIP
+}
+
+func getContainerLogs(c *check.C, containerID string) string {
+	runCmd := exec.Command(dockerBinary, "logs", containerID)
 	out, _, err := runCommandWithOutput(runCmd)
 	if err != nil {
 		c.Fatal(out, err)
 	}
+	return strings.Trim(out, "\r\n")
+}
 
-	cleanedContainerID := strings.TrimSpace(out)
-
-	runCmd = exec.Command(dockerBinary, "run", "busybox", "sh", "-c", fmt.Sprintf("echo hello world | nc -w 30 %s 8080", ifaceIP))
-	out, _, err = runCommandWithOutput(runCmd)
+func getContainerStatus(c *check.C, containerID string) string {
+	runCmd := exec.Command(dockerBinary, "inspect", "-f", "{{.State.Running}}", containerID)
+	out, _, err := runCommandWithOutput(runCmd)
 	if err != nil {
 		c.Fatal(out, err)
 	}
+	return strings.Trim(out, "\r\n")
+}
 
-	runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID)
-	out, _, err = runCommandWithOutput(runCmd)
-	if err != nil {
-		c.Fatalf("failed to retrieve logs for container: %s, %v", out, err)
-	}
+func (s *DockerSuite) TestNetworkNat(c *check.C) {
+	testRequires(c, SameHostDaemon, NativeExecDriver)
+	defer deleteAllContainers()
 
-	out = strings.Trim(out, "\r\n")
+	srv := startServerContainer(c, "tcp", 8080)
 
-	if expected := "hello world"; out != expected {
-		c.Fatalf("Unexpected output. Expected: %q, received: %q for iface %s", expected, out, ifaceIP)
+	// Spawn a new container which connects to the server through the
+	// interface address.
+	endpoint := getExternalAddress(c)
+	runCmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", fmt.Sprintf("echo hello world | nc -w 30 %s 8080", endpoint))
+	if out, _, err := runCommandWithOutput(runCmd); err != nil {
+		c.Fatalf("Failed to connect to server: %v (output: %q)", err, string(out))
 	}
 
-	killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID)
-	if out, _, err = runCommandWithOutput(killCmd); err != nil {
-		c.Fatalf("failed to kill container: %s, %v", out, err)
+	result := getContainerLogs(c, srv)
+	if expected := "hello world"; result != expected {
+		c.Fatalf("Unexpected output. Expected: %q, received: %q", expected, result)
+	}
+}
+
+func (s *DockerSuite) TestNetworkLocalhostTCPNat(c *check.C) {
+	testRequires(c, SameHostDaemon, NativeExecDriver)
+	defer deleteAllContainers()
+
+	srv := startServerContainer(c, "tcp", 8081)
+
+	// Attempt to connect from the host to the listening container.
+	conn, err := net.Dial("tcp", "localhost:8081")
+	if err != nil {
+		c.Fatalf("Failed to connect to container (%v)", err)
+	}
+	if _, err := conn.Write([]byte("hello world\n")); err != nil {
+		c.Fatal(err)
 	}
+	conn.Close()
 
+	result := getContainerLogs(c, srv)
+	if expected := "hello world"; result != expected {
+		c.Fatalf("Unexpected output. Expected: %q, received: %q", expected, result)
+	}
 }

+ 7 - 37
integration-cli/docker_cli_run_test.go

@@ -2197,49 +2197,19 @@ func (s *DockerSuite) TestRunPortInUse(c *check.C) {
 	testRequires(c, SameHostDaemon)
 
 	port := "1234"
-	l, err := net.Listen("tcp", ":"+port)
-	if err != nil {
-		c.Fatal(err)
-	}
-	defer l.Close()
-	cmd := exec.Command(dockerBinary, "run", "-d", "-p", port+":80", "busybox", "top")
-	out, _, err := runCommandWithOutput(cmd)
-	if err == nil {
-		c.Fatalf("Binding on used port must fail")
-	}
-	if !strings.Contains(out, "address already in use") {
-		c.Fatalf("Out must be about \"address already in use\", got %s", out)
-	}
-}
-
-// https://github.com/docker/docker/issues/8428
-func (s *DockerSuite) TestRunPortProxy(c *check.C) {
-	testRequires(c, SameHostDaemon)
-
-	port := "12345"
 	cmd := exec.Command(dockerBinary, "run", "-d", "-p", port+":80", "busybox", "top")
-
 	out, _, err := runCommandWithOutput(cmd)
 	if err != nil {
-		c.Fatalf("Failed to run and bind port %s, output: %s, error: %s", port, out, err)
-	}
-
-	// connett for 10 times here. This will trigger 10 EPIPES in the child
-	// process and kill it when it writes to a closed stdout/stderr
-	for i := 0; i < 10; i++ {
-		net.Dial("tcp", fmt.Sprintf("0.0.0.0:%s", port))
+		c.Fatalf("Fail to run listening container")
 	}
 
-	listPs := exec.Command("sh", "-c", "ps ax | grep docker")
-	out, _, err = runCommandWithOutput(listPs)
-	if err != nil {
-		c.Errorf("list docker process failed with output %s, error %s", out, err)
-	}
-	if strings.Contains(out, "docker <defunct>") {
-		c.Errorf("Unexpected defunct docker process")
+	cmd = exec.Command(dockerBinary, "run", "-d", "-p", port+":80", "busybox", "top")
+	out, _, err = runCommandWithOutput(cmd)
+	if err == nil {
+		c.Fatalf("Binding on used port must fail")
 	}
-	if !strings.Contains(out, "docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 12345") {
-		c.Errorf("Failed to find docker-proxy process, got %s", out)
+	if !strings.Contains(out, "port is already allocated") {
+		c.Fatalf("Out must be about \"port is already allocated\", got %s", out)
 	}
 }
 

+ 1 - 1
pkg/iptables/firewalld_test.go

@@ -14,7 +14,7 @@ func TestReloaded(t *testing.T) {
 	var err error
 	var fwdChain *Chain
 
-	fwdChain, err = NewChain("FWD", "lo", Filter)
+	fwdChain, err = NewChain("FWD", "lo", Filter, false)
 	if err != nil {
 		t.Fatal(err)
 	}

+ 5 - 4
pkg/iptables/iptables.go

@@ -58,7 +58,7 @@ func initCheck() error {
 	return nil
 }
 
-func NewChain(name, bridge string, table Table) (*Chain, error) {
+func NewChain(name, bridge string, table Table, hairpinMode bool) (*Chain, error) {
 	c := &Chain{
 		Name:   name,
 		Bridge: bridge,
@@ -90,8 +90,10 @@ func NewChain(name, bridge string, table Table) (*Chain, error) {
 		}
 		output := []string{
 			"-m", "addrtype",
-			"--dst-type", "LOCAL",
-			"!", "--dst", "127.0.0.0/8"}
+			"--dst-type", "LOCAL"}
+		if !hairpinMode {
+			output = append(output, "!", "--dst", "127.0.0.0/8")
+		}
 		if !Exists(Nat, "OUTPUT", output...) {
 			if err := c.Output(Append, output...); err != nil {
 				return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
@@ -137,7 +139,6 @@ func (c *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr stri
 		"-p", proto,
 		"-d", daddr,
 		"--dport", strconv.Itoa(port),
-		"!", "-i", c.Bridge,
 		"-j", "DNAT",
 		"--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))); err != nil {
 		return err

+ 2 - 3
pkg/iptables/iptables_test.go

@@ -16,12 +16,12 @@ var filterChain *Chain
 func TestNewChain(t *testing.T) {
 	var err error
 
-	natChain, err = NewChain(chainName, "lo", Nat)
+	natChain, err = NewChain(chainName, "lo", Nat, false)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	filterChain, err = NewChain(chainName, "lo", Filter)
+	filterChain, err = NewChain(chainName, "lo", Filter, false)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -40,7 +40,6 @@ func TestForward(t *testing.T) {
 	}
 
 	dnatRule := []string{
-		"!", "-i", filterChain.Bridge,
 		"-d", ip.String(),
 		"-p", proto,
 		"--dport", strconv.Itoa(port),