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

Support for com.docker.network.host_ipv4 driver label

This commit allows a user to specify a Host IP via the
com.docker.network.host_ipv4 label which is used as the
Source IP during SNAT for bridge networks .

The use case is for hosts with multiple interfaces and
this label can dictate which IP will be used as Source IP
for North-South traffic

In the absence of this label, MASQUERADE is used which picks the Source IP
based on Next Hop from the Route Table

Addresses: https://github.com/moby/moby/issues/30053

Signed-off-by: Arko Dasgupta <arko.dasgupta@docker.com>
This commit is contained in:
Arko Dasgupta 2019-09-24 22:08:25 -07:00
parent 141b53c77a
commit 8c8a25d524
5 changed files with 38 additions and 5 deletions

View file

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

View file

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

View file

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

View file

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

View file

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