diff --git a/daemon/config.go b/daemon/config.go index 43b08531b5..5fe01e5c7c 100644 --- a/daemon/config.go +++ b/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 { diff --git a/daemon/container.go b/daemon/container.go index 5c7d3a4e52..82446a8d1b 100644 --- a/daemon/container.go +++ b/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": diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index 7827baa266..2f301149c8 100644 --- a/daemon/execdriver/driver.go +++ b/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 diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index ddb7b9bdcc..fbad9f1301 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -112,6 +112,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) diff --git a/daemon/network/settings.go b/daemon/network/settings.go index f3841f09b1..91d61160a6 100644 --- a/daemon/network/settings.go +++ b/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 } diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index 2fe04d2065..a324d6b6e4 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/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 diff --git a/daemon/networkdriver/bridge/driver_test.go b/daemon/networkdriver/bridge/driver_test.go index d18882e664..c82acb86a2 100644 --- a/daemon/networkdriver/bridge/driver_test.go +++ b/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) } diff --git a/daemon/networkdriver/portmapper/mapper.go b/daemon/networkdriver/portmapper/mapper.go index 09952ba35b..f0c7a507df 100644 --- a/daemon/networkdriver/portmapper/mapper.go +++ b/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) diff --git a/daemon/networkdriver/portmapper/mapper_test.go b/daemon/networkdriver/portmapper/mapper_test.go index 729fe56075..b908db2811 100644 --- a/daemon/networkdriver/portmapper/mapper_test.go +++ b/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) } diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index 4e7cafe466..8b7b06709e 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -53,6 +53,9 @@ To see the man page for a command run **man docker **. **-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 diff --git a/docs/sources/articles/networking.md b/docs/sources/articles/networking.md index 823b450c75..5ee1d7e6ba 100644 --- a/docs/sources/articles/networking.md +++ b/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. diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 5a92739529..227f971494 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/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. diff --git a/hack/make.sh b/hack/make.sh index 699bedb379..b9c2aef6a8 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -207,6 +207,7 @@ test_env() { DEST="$DEST" \ DOCKER_EXECDRIVER="$DOCKER_EXECDRIVER" \ DOCKER_GRAPHDRIVER="$DOCKER_GRAPHDRIVER" \ + DOCKER_USERLANDPROXY="$DOCKER_USERLANDPROXY" \ DOCKER_HOST="$DOCKER_HOST" \ GOPATH="$GOPATH" \ HOME="$DEST/fake-HOME" \ diff --git a/hack/make/.integration-daemon-start b/hack/make/.integration-daemon-start index 57fd525028..937979df3e 100644 --- a/hack/make/.integration-daemon-start +++ b/hack/make/.integration-daemon-start @@ -14,6 +14,7 @@ exec 41>&1 42>&2 export DOCKER_GRAPHDRIVER=${DOCKER_GRAPHDRIVER:-vfs} export DOCKER_EXECDRIVER=${DOCKER_EXECDRIVER:-native} +export DOCKER_USERLANDPROXY=${DOCKER_USERLANDPROXY:-true} if [ -z "$DOCKER_TEST_HOST" ]; then export DOCKER_HOST="unix://$(cd "$DEST" && pwd)/docker.sock" # "pwd" tricks to make sure $DEST is an absolute path, not a relative one @@ -23,6 +24,7 @@ if [ -z "$DOCKER_TEST_HOST" ]; then --storage-driver "$DOCKER_GRAPHDRIVER" \ --exec-driver "$DOCKER_EXECDRIVER" \ --pidfile "$DEST/docker.pid" \ + --userland-proxy="$DOCKER_USERLANDPROXY" \ &> "$DEST/docker.log" ) & trap "source '${MAKEDIR}/.integration-daemon-stop'" EXIT # make sure that if the script exits unexpectedly, we stop this daemon we just started diff --git a/integration-cli/docker_cli_nat_test.go b/integration-cli/docker_cli_nat_test.go index 875b6540ab..14237042ac 100644 --- a/integration-cli/docker_cli_nat_test.go +++ b/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) } - - 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) - } - - out = strings.Trim(out, "\r\n") - - if expected := "hello world"; out != expected { - c.Fatalf("Unexpected output. Expected: %q, received: %q for iface %s", expected, out, ifaceIP) - } - - killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID) - if out, _, err = runCommandWithOutput(killCmd); err != nil { - c.Fatalf("failed to kill container: %s, %v", out, err) - } - + return strings.Trim(out, "\r\n") +} + +func (s *DockerSuite) TestNetworkNat(c *check.C) { + testRequires(c, SameHostDaemon, NativeExecDriver) + defer deleteAllContainers() + + srv := startServerContainer(c, "tcp", 8080) + + // 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)) + } + + 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) + } } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index cc4c9988ab..626cc3583a 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/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("Fail to run listening container") + } + + 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)) - } - - 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 ") { - c.Errorf("Unexpected defunct docker process") - } - 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) } } diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index a1a845baff..3500d2f750 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -37,6 +37,16 @@ type Daemon struct { storageDriver string execDriver string wait chan error + userlandProxy bool +} + +func enableUserlandProxy() bool { + if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" { + if val, err := strconv.ParseBool(env); err != nil { + return val + } + } + return true } // NewDaemon returns a Daemon instance to be used for testing. @@ -58,11 +68,19 @@ func NewDaemon(c *check.C) *Daemon { c.Fatalf("Could not create %s/graph directory", daemonFolder) } + userlandProxy := true + if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" { + if val, err := strconv.ParseBool(env); err != nil { + userlandProxy = val + } + } + return &Daemon{ c: c, folder: daemonFolder, storageDriver: os.Getenv("DOCKER_GRAPHDRIVER"), execDriver: os.Getenv("DOCKER_EXECDRIVER"), + userlandProxy: userlandProxy, } } @@ -79,6 +97,7 @@ func (d *Daemon) Start(arg ...string) error { "--daemon", "--graph", fmt.Sprintf("%s/graph", d.folder), "--pidfile", fmt.Sprintf("%s/docker.pid", d.folder), + fmt.Sprintf("--userland-proxy=%t", d.userlandProxy), } // If we don't explicitly set the log-level or debug flag(-D) then diff --git a/pkg/iptables/firewalld_test.go b/pkg/iptables/firewalld_test.go index 3896007d64..ff92657b18 100644 --- a/pkg/iptables/firewalld_test.go +++ b/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) } diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index 9983ec61ff..64a45db992 100644 --- a/pkg/iptables/iptables.go +++ b/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 diff --git a/pkg/iptables/iptables_test.go b/pkg/iptables/iptables_test.go index ced4262ce2..3539bd5cf1 100644 --- a/pkg/iptables/iptables_test.go +++ b/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),