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)
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 {

View file

@ -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":

View file

@ -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

View file

@ -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)

View file

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

View file

@ -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

View file

@ -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)
}

View file

@ -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)

View file

@ -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)
}

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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

View file

@ -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),