1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

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>
This commit is contained in:
Arnaud Porterie 2014-11-10 16:19:16 -08:00
parent 4d88bf3ecb
commit f42348e18f
17 changed files with 179 additions and 106 deletions

View file

@ -79,6 +79,7 @@ func (config *Config) InstallFlags() {
config.Ulimits = make(map[string]*ulimit.Ulimit) config.Ulimits = make(map[string]*ulimit.Ulimit)
opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers") 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.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 { func getDefaultNetworkMtu() int {

View file

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

View file

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

View file

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

View file

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

View file

@ -8,6 +8,7 @@ import (
"net" "net"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -83,6 +84,7 @@ var (
gatewayIPv6 net.IP gatewayIPv6 net.IP
portMapper *portmapper.PortMapper portMapper *portmapper.PortMapper
once sync.Once once sync.Once
hairpinMode bool
defaultBindingIP = net.ParseIP("0.0.0.0") defaultBindingIP = net.ParseIP("0.0.0.0")
currentInterfaces = ifaces{c: make(map[string]*networkInterface)} currentInterfaces = ifaces{c: make(map[string]*networkInterface)}
@ -100,6 +102,7 @@ type Config struct {
EnableIptables bool EnableIptables bool
EnableIpForward bool EnableIpForward bool
EnableIpMasq bool EnableIpMasq bool
EnableUserlandProxy bool
DefaultIp net.IP DefaultIp net.IP
Iface string Iface string
IP string IP string
@ -131,6 +134,8 @@ func InitDriver(config *Config) error {
defaultBindingIP = config.DefaultIp defaultBindingIP = config.DefaultIp
} }
hairpinMode = !config.EnableUserlandProxy
bridgeIface = config.Iface bridgeIface = config.Iface
usingDefaultBridge := false usingDefaultBridge := false
if bridgeIface == "" { if bridgeIface == "" {
@ -243,39 +248,46 @@ func InitDriver(config *Config) error {
if config.EnableIpForward { if config.EnableIpForward {
// Enable IPv4 forwarding // Enable IPv4 forwarding
if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte{'1', '\n'}, 0644); err != nil { 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 != "" { if config.FixedCIDRv6 != "" {
// Enable IPv6 forwarding // Enable IPv6 forwarding
if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/default/forwarding", []byte{'1', '\n'}, 0644); err != nil { 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 { 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 // We can always try removing the iptables
if err := iptables.RemoveExistingChain("DOCKER", iptables.Nat); err != nil { if err := iptables.RemoveExistingChain("DOCKER", iptables.Nat); err != nil {
return err return err
} }
if config.EnableIptables { if config.EnableIptables {
_, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Nat) _, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Nat, hairpinMode)
if err != nil { if err != nil {
return err return err
} }
// call this on Firewalld reload // call this on Firewalld reload
iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Nat) }) iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Nat, hairpinMode) })
chain, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter, hairpinMode)
chain, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter)
if err != nil { if err != nil {
return err return err
} }
// call this on Firewalld reload // 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) 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 // Accept all non-intercontainer outgoing packets
outgoingArgs := []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"} outgoingArgs := []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}
if !iptables.Exists(iptables.Filter, "FORWARD", outgoingArgs...) { if !iptables.Exists(iptables.Filter, "FORWARD", outgoingArgs...) {
@ -637,6 +661,7 @@ func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Set
Bridge: bridgeIface, Bridge: bridgeIface,
IPPrefixLen: maskSize, IPPrefixLen: maskSize,
LinkLocalIPv6Address: localIPv6.String(), LinkLocalIPv6Address: localIPv6.String(),
HairpinMode: hairpinMode,
} }
if globalIPv6Network != nil { if globalIPv6Network != nil {
@ -722,7 +747,7 @@ func AllocatePort(id string, port nat.Port, binding nat.PortBinding) (nat.PortBi
return nat.PortBinding{}, err return nat.PortBinding{}, err
} }
for i := 0; i < MaxAllocatedPortAttempts; i++ { 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 break
} }
// There is no point in immediately retrying to map an explicitly // There is no point in immediately retrying to map an explicitly

View file

@ -177,7 +177,7 @@ func TestLinkContainers(t *testing.T) {
} }
bridgeIface = "lo" 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) t.Fatal(err)
} }

View file

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

View file

@ -44,22 +44,22 @@ func TestMapPorts(t *testing.T) {
return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String()) 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) t.Fatalf("Failed to allocate port: %s", err)
} else if !addrEqual(dstAddr1, host) { } else if !addrEqual(dstAddr1, host) {
t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s", t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
dstAddr1.String(), dstAddr1.Network(), host.String(), host.Network()) 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") 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") 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) t.Fatalf("Failed to allocate port: %s", err)
} }
@ -127,14 +127,14 @@ func TestMapAllPortsSingleInterface(t *testing.T) {
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
start, end := pm.Allocator.Begin, pm.Allocator.End start, end := pm.Allocator.Begin, pm.Allocator.End
for i := start; i < end; i++ { 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) t.Fatal(err)
} }
hosts = append(hosts, host) 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) t.Fatalf("Port %d should be bound but is not", start)
} }

View file

@ -53,6 +53,9 @@ To see the man page for a command run **man docker <command>**.
**-e**, **--exec-driver**="" **-e**, **--exec-driver**=""
Force Docker to use specific exec driver. Default is `native`. Force Docker to use specific exec driver. Default is `native`.
**--exec-opt**=[]
Set exec driver options. See EXEC DRIVER OPTIONS.
**--fixed-cidr**="" **--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) 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**="" **-s**, **--storage-driver**=""
Force the Docker runtime to use a specific 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**=[] **--storage-opt**=[]
Set storage driver options. See STORAGE DRIVER OPTIONS. 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). Use TLS and verify the remote (daemon: verify client, client: verify daemon).
Default is false. 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* **-v**, **--version**=*true*|*false*
Print version information and quit. Default is 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 # COMMANDS
**attach** **attach**
Attach to a running container Attach to a running container

View file

@ -93,6 +93,9 @@ server when it starts up, and cannot be changed once it is running:
* `--mtu=BYTES` — see * `--mtu=BYTES` — see
[Customizing docker0](#docker0) [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 There are two networking options that can be supplied either at startup
or when `docker run` is invoked. When provided at startup, set the 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 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) Chain POSTROUTING (policy ACCEPT)
target prot opt source destination 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 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 option `--ip=IP_ADDRESS`. Remember to restart your Docker server after
editing this setting. 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 Again, this topic is covered without all of these low-level networking
details in the [Docker User Guide](/userguide/dockerlinks/) document if you details in the [Docker User Guide](/userguide/dockerlinks/) document if you
would like to use that as your port redirection reference instead. would like to use that as your port redirection reference instead.

View file

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

View file

@ -4,14 +4,26 @@ import (
"fmt" "fmt"
"net" "net"
"os/exec" "os/exec"
"strconv"
"strings" "strings"
"github.com/go-check/check" "github.com/go-check/check"
) )
func (s *DockerSuite) TestNetworkNat(c *check.C) { func startServerContainer(c *check.C, proto string, port int) string {
testRequires(c, SameHostDaemon, NativeExecDriver) 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") iface, err := net.InterfaceByName("eth0")
if err != nil { if err != nil {
c.Skip(fmt.Sprintf("Test not running with `make test`. Interface eth0 not found: %v", err)) 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) 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) out, _, err := runCommandWithOutput(runCmd)
if err != nil { if err != nil {
c.Fatal(out, err) c.Fatal(out, err)
} }
return strings.Trim(out, "\r\n")
}
cleanedContainerID := strings.TrimSpace(out) func getContainerStatus(c *check.C, containerID string) string {
runCmd := exec.Command(dockerBinary, "inspect", "-f", "{{.State.Running}}", containerID)
runCmd = exec.Command(dockerBinary, "run", "busybox", "sh", "-c", fmt.Sprintf("echo hello world | nc -w 30 %s 8080", ifaceIP)) out, _, err := runCommandWithOutput(runCmd)
out, _, err = runCommandWithOutput(runCmd)
if err != nil { if err != nil {
c.Fatal(out, err) c.Fatal(out, err)
} }
return strings.Trim(out, "\r\n")
runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID) }
out, _, err = runCommandWithOutput(runCmd)
if err != nil { func (s *DockerSuite) TestNetworkNat(c *check.C) {
c.Fatalf("failed to retrieve logs for container: %s, %v", out, err) testRequires(c, SameHostDaemon, NativeExecDriver)
} defer deleteAllContainers()
out = strings.Trim(out, "\r\n") srv := startServerContainer(c, "tcp", 8080)
if expected := "hello world"; out != expected { // Spawn a new container which connects to the server through the
c.Fatalf("Unexpected output. Expected: %q, received: %q for iface %s", expected, out, ifaceIP) // interface address.
} endpoint := getExternalAddress(c)
runCmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", fmt.Sprintf("echo hello world | nc -w 30 %s 8080", endpoint))
killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID) if out, _, err := runCommandWithOutput(runCmd); err != nil {
if out, _, err = runCommandWithOutput(killCmd); err != nil { c.Fatalf("Failed to connect to server: %v (output: %q)", err, string(out))
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)
}
} }

View file

@ -2197,49 +2197,19 @@ func (s *DockerSuite) TestRunPortInUse(c *check.C) {
testRequires(c, SameHostDaemon) testRequires(c, SameHostDaemon)
port := "1234" 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") cmd := exec.Command(dockerBinary, "run", "-d", "-p", port+":80", "busybox", "top")
out, _, err := runCommandWithOutput(cmd) 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 { if err == nil {
c.Fatalf("Binding on used port must fail") c.Fatalf("Binding on used port must fail")
} }
if !strings.Contains(out, "address already in use") { if !strings.Contains(out, "port is already allocated") {
c.Fatalf("Out must be about \"address already in use\", got %s", out) c.Fatalf("Out must be about \"port is already allocated\", 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 <defunct>") {
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)
} }
} }

View file

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

View file

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

View file

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