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:
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)
|
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 {
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
|
|
Loading…
Add table
Reference in a new issue