diff --git a/libnetwork/drivers/bridge/bridge.go b/libnetwork/drivers/bridge/bridge.go index b617ea7bc4..d67b1fbb8f 100644 --- a/libnetwork/drivers/bridge/bridge.go +++ b/libnetwork/drivers/bridge/bridge.go @@ -71,6 +71,7 @@ type networkConfiguration struct { Mtu int DefaultBindingIP net.IP DefaultBridge bool + HostIP net.IP ContainerIfacePrefix string // Internal fields set after ipam data parsing AddressIPv4 *net.IPNet @@ -253,6 +254,10 @@ func (c *networkConfiguration) fromLabels(labels map[string]string) error { } case netlabel.ContainerIfacePrefix: c.ContainerIfacePrefix = value + case netlabel.HostIP: + if c.HostIP = net.ParseIP(value); c.HostIP == nil { + return parseErr(label, value, "nil ip") + } } } diff --git a/libnetwork/drivers/bridge/bridge_store.go b/libnetwork/drivers/bridge/bridge_store.go index 2988c34fa5..08ed258e63 100644 --- a/libnetwork/drivers/bridge/bridge_store.go +++ b/libnetwork/drivers/bridge/bridge_store.go @@ -141,6 +141,7 @@ func (ncfg *networkConfiguration) MarshalJSON() ([]byte, error) { nMap["Internal"] = ncfg.Internal nMap["DefaultBridge"] = ncfg.DefaultBridge nMap["DefaultBindingIP"] = ncfg.DefaultBindingIP.String() + nMap["HostIP"] = ncfg.HostIP.String() nMap["DefaultGatewayIPv4"] = ncfg.DefaultGatewayIPv4.String() nMap["DefaultGatewayIPv6"] = ncfg.DefaultGatewayIPv6.String() nMap["ContainerIfacePrefix"] = ncfg.ContainerIfacePrefix @@ -183,6 +184,10 @@ func (ncfg *networkConfiguration) UnmarshalJSON(b []byte) error { ncfg.ContainerIfacePrefix = v.(string) } + if v, ok := nMap["HostIP"]; ok { + ncfg.HostIP = net.ParseIP(v.(string)) + } + ncfg.DefaultBridge = nMap["DefaultBridge"].(bool) ncfg.DefaultBindingIP = net.ParseIP(nMap["DefaultBindingIP"].(string)) ncfg.DefaultGatewayIPv4 = net.ParseIP(nMap["DefaultGatewayIPv4"].(string)) diff --git a/libnetwork/drivers/bridge/bridge_test.go b/libnetwork/drivers/bridge/bridge_test.go index 945e2bd2ec..52e1f5c1ab 100644 --- a/libnetwork/drivers/bridge/bridge_test.go +++ b/libnetwork/drivers/bridge/bridge_test.go @@ -264,6 +264,7 @@ func TestCreateFullOptionsLabels(t *testing.T) { } bndIPs := "127.0.0.1" + testHostIP := "1.2.3.4" nwV6s := "2001:db8:2600:2700:2800::/80" gwV6s := "2001:db8:2600:2700:2800::25/80" nwV6, _ := types.ParseCIDR(nwV6s) @@ -275,6 +276,7 @@ func TestCreateFullOptionsLabels(t *testing.T) { EnableICC: "true", EnableIPMasquerade: "true", DefaultBindingIP: bndIPs, + netlabel.HostIP: testHostIP, } netOption := make(map[string]interface{}) @@ -322,6 +324,11 @@ func TestCreateFullOptionsLabels(t *testing.T) { t.Fatalf("Unexpected: %v", nw.config.DefaultBindingIP) } + hostIP := net.ParseIP(testHostIP) + if !hostIP.Equal(nw.config.HostIP) { + t.Fatalf("Unexpected: %v", nw.config.HostIP) + } + if !types.CompareIPNet(nw.config.AddressIPv6, nwV6) { t.Fatalf("Unexpected: %v", nw.config.AddressIPv6) } diff --git a/libnetwork/drivers/bridge/setup_ip_tables.go b/libnetwork/drivers/bridge/setup_ip_tables.go index 438b842a82..e59b9ca828 100644 --- a/libnetwork/drivers/bridge/setup_ip_tables.go +++ b/libnetwork/drivers/bridge/setup_ip_tables.go @@ -121,11 +121,11 @@ func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInt return setupInternalNetworkRules(config.BridgeName, maskedAddrv4, config.EnableICC, false) }) } else { - if err = setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, true); err != nil { + if err = setupIPTablesInternal(config.HostIP, config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, true); err != nil { return fmt.Errorf("Failed to Setup IP tables: %s", err.Error()) } n.registerIptCleanFunc(func() error { - return setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false) + return setupIPTablesInternal(config.HostIP, config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false) }) natChain, filterChain, _, _, err := n.getDriverChains() if err != nil { @@ -166,15 +166,28 @@ type iptRule struct { args []string } -func setupIPTablesInternal(bridgeIface string, addr net.Addr, icc, ipmasq, hairpin, enable bool) error { +func setupIPTablesInternal(hostIP net.IP, bridgeIface string, addr net.Addr, icc, ipmasq, hairpin, enable bool) error { var ( address = addr.String() - natRule = iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-s", address, "!", "-o", bridgeIface, "-j", "MASQUERADE"}} - hpNatRule = iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "MASQUERADE"}} skipDNAT = iptRule{table: iptables.Nat, chain: DockerChain, preArgs: []string{"-t", "nat"}, args: []string{"-i", bridgeIface, "-j", "RETURN"}} outRule = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}} + natArgs []string + hpNatArgs []string ) + // if hostIP is set use this address as the src-ip during SNAT + if hostIP != nil { + hostAddr := hostIP.String() + natArgs = []string{"-s", address, "!", "-o", bridgeIface, "-j", "SNAT", "--to-source", hostAddr} + hpNatArgs = []string{"-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "SNAT", "--to-source", hostAddr} + // Else use MASQUERADE which picks the src-ip based on NH from the route table + } else { + natArgs = []string{"-s", address, "!", "-o", bridgeIface, "-j", "MASQUERADE"} + hpNatArgs = []string{"-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "MASQUERADE"} + } + + natRule := iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: natArgs} + hpNatRule := iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: hpNatArgs} // Set NAT. if ipmasq { diff --git a/libnetwork/netlabel/labels.go b/libnetwork/netlabel/labels.go index 1594556ec7..f5075a6c34 100644 --- a/libnetwork/netlabel/labels.go +++ b/libnetwork/netlabel/labels.go @@ -53,6 +53,9 @@ const ( // ContainerIfacePrefix can be used to override the interface prefix used inside the container ContainerIfacePrefix = Prefix + ".container_iface_prefix" + + // HostIP is the Source-IP Address used to SNAT container traffic + HostIP = Prefix + ".host_ipv4" ) var (