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:
parent
4d88bf3ecb
commit
f42348e18f
17 changed files with 179 additions and 106 deletions
|
@ -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 {
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -15,4 +15,5 @@ type Settings struct {
|
|||
Bridge string
|
||||
PortMapping map[string]map[string]string // Deprecated
|
||||
Ports nat.PortMap
|
||||
HairpinMode bool
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <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)
|
||||
if !strings.Contains(out, "port is already allocated") {
|
||||
c.Fatalf("Out must be about \"port is already allocated\", got %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in a new issue