New host_ipv6
bridge option to SNAT IPv6 connections
Add a new `com.docker.network.host_ipv6` bridge option to compliment the existing `com.docker.network.host_ipv4` option. When set to an IPv6 address, this causes the bridge to insert `SNAT` rules instead of `MASQUERADE` rules (assuming `ip6tables` is enabled). `SNAT` makes it possible for users to control the source IP address used for outgoing connections. Signed-off-by: Richard Hansen <rhansen@rhansen.org>
This commit is contained in:
parent
fc4d035e7a
commit
808120e5b8
5 changed files with 47 additions and 3 deletions
|
@ -74,6 +74,7 @@ type networkConfiguration struct {
|
|||
DefaultBindingIP net.IP
|
||||
DefaultBridge bool
|
||||
HostIPv4 net.IP
|
||||
HostIPv6 net.IP
|
||||
ContainerIfacePrefix string
|
||||
// Internal fields set after ipam data parsing
|
||||
AddressIPv4 *net.IPNet
|
||||
|
@ -270,6 +271,10 @@ func (c *networkConfiguration) fromLabels(labels map[string]string) error {
|
|||
if c.HostIPv4 = net.ParseIP(value); c.HostIPv4 == nil {
|
||||
return parseErr(label, value, "nil ip")
|
||||
}
|
||||
case netlabel.HostIPv6:
|
||||
if c.HostIPv6 = net.ParseIP(value); c.HostIPv6 == nil {
|
||||
return parseErr(label, value, "nil ip")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -148,6 +148,7 @@ func (ncfg *networkConfiguration) MarshalJSON() ([]byte, error) {
|
|||
nMap["DefaultBindingIP"] = ncfg.DefaultBindingIP.String()
|
||||
// This key is "HostIP" instead of "HostIPv4" to preserve compatibility with the on-disk format.
|
||||
nMap["HostIP"] = ncfg.HostIPv4.String()
|
||||
nMap["HostIPv6"] = ncfg.HostIPv6.String()
|
||||
nMap["DefaultGatewayIPv4"] = ncfg.DefaultGatewayIPv4.String()
|
||||
nMap["DefaultGatewayIPv6"] = ncfg.DefaultGatewayIPv6.String()
|
||||
nMap["ContainerIfacePrefix"] = ncfg.ContainerIfacePrefix
|
||||
|
@ -194,6 +195,9 @@ func (ncfg *networkConfiguration) UnmarshalJSON(b []byte) error {
|
|||
if v, ok := nMap["HostIP"]; ok {
|
||||
ncfg.HostIPv4 = net.ParseIP(v.(string))
|
||||
}
|
||||
if v, ok := nMap["HostIPv6"]; ok {
|
||||
ncfg.HostIPv6 = net.ParseIP(v.(string))
|
||||
}
|
||||
|
||||
ncfg.DefaultBridge = nMap["DefaultBridge"].(bool)
|
||||
ncfg.DefaultBindingIP = net.ParseIP(nMap["DefaultBindingIP"].(string))
|
||||
|
|
|
@ -258,9 +258,13 @@ func setupIPTablesInternal(ipVer iptables.IPVersion, config *networkConfiguratio
|
|||
natArgs []string
|
||||
hpNatArgs []string
|
||||
)
|
||||
// If config.HostIPv4 is set, the user wants IPv4 SNAT with the given address.
|
||||
if config.HostIPv4 != nil && ipVer == iptables.IPv4 {
|
||||
hostAddr := config.HostIPv4.String()
|
||||
hostIP := config.HostIPv4
|
||||
if ipVer == iptables.IPv6 {
|
||||
hostIP = config.HostIPv6
|
||||
}
|
||||
// If hostIP is set, the user wants IPv4/IPv6 SNAT with the given address.
|
||||
if hostIP != nil {
|
||||
hostAddr := hostIP.String()
|
||||
natArgs = []string{"-s", address, "!", "-o", config.BridgeName, "-j", "SNAT", "--to-source", hostAddr}
|
||||
hpNatArgs = []string{"-m", "addrtype", "--src-type", "LOCAL", "-o", config.BridgeName, "-j", "SNAT", "--to-source", hostAddr}
|
||||
// Else use MASQUERADE which picks the src-ip based on NH from the route table
|
||||
|
|
|
@ -212,6 +212,7 @@ func TestOutgoingNATRules(t *testing.T) {
|
|||
maskedBrIPv4 := &net.IPNet{IP: brIPv4.IP.Mask(brIPv4.Mask), Mask: brIPv4.Mask}
|
||||
maskedBrIPv6 := &net.IPNet{IP: brIPv6.IP.Mask(brIPv6.Mask), Mask: brIPv6.Mask}
|
||||
hostIPv4 := net.ParseIP("192.0.2.2")
|
||||
hostIPv6 := net.ParseIP("2001:db8:1::1")
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
enableIPTables bool
|
||||
|
@ -219,6 +220,7 @@ func TestOutgoingNATRules(t *testing.T) {
|
|||
enableIPv6 bool
|
||||
enableIPMasquerade bool
|
||||
hostIPv4 net.IP
|
||||
hostIPv6 net.IP
|
||||
// Hairpin NAT rules are not tested here because they are orthogonal to outgoing NAT. They
|
||||
// exist to support the port forwarding DNAT rules: without any port forwarding there would be
|
||||
// no need for any hairpin NAT rules, and when there is port forwarding then hairpin NAT rules
|
||||
|
@ -227,6 +229,7 @@ func TestOutgoingNATRules(t *testing.T) {
|
|||
wantIPv4Masq bool
|
||||
wantIPv4Snat bool
|
||||
wantIPv6Masq bool
|
||||
wantIPv6Snat bool
|
||||
}{
|
||||
{
|
||||
desc: "everything disabled",
|
||||
|
@ -241,6 +244,7 @@ func TestOutgoingNATRules(t *testing.T) {
|
|||
enableIPv6: true,
|
||||
enableIPMasquerade: true,
|
||||
hostIPv4: hostIPv4,
|
||||
hostIPv6: hostIPv6,
|
||||
},
|
||||
{
|
||||
desc: "masquerade disabled, no host IP",
|
||||
|
@ -254,6 +258,7 @@ func TestOutgoingNATRules(t *testing.T) {
|
|||
enableIP6Tables: true,
|
||||
enableIPv6: true,
|
||||
hostIPv4: hostIPv4,
|
||||
hostIPv6: hostIPv6,
|
||||
},
|
||||
{
|
||||
desc: "IPv4 masquerade, IPv6 disabled",
|
||||
|
@ -277,6 +282,16 @@ func TestOutgoingNATRules(t *testing.T) {
|
|||
wantIPv4Masq: true,
|
||||
wantIPv6Masq: true,
|
||||
},
|
||||
{
|
||||
desc: "IPv4 masquerade, IPv6 SNAT",
|
||||
enableIPTables: true,
|
||||
enableIP6Tables: true,
|
||||
enableIPv6: true,
|
||||
enableIPMasquerade: true,
|
||||
hostIPv6: hostIPv6,
|
||||
wantIPv4Masq: true,
|
||||
wantIPv6Snat: true,
|
||||
},
|
||||
{
|
||||
desc: "IPv4 SNAT, IPv6 masquerade",
|
||||
enableIPTables: true,
|
||||
|
@ -287,6 +302,17 @@ func TestOutgoingNATRules(t *testing.T) {
|
|||
wantIPv4Snat: true,
|
||||
wantIPv6Masq: true,
|
||||
},
|
||||
{
|
||||
desc: "IPv4 SNAT, IPv6 SNAT",
|
||||
enableIPTables: true,
|
||||
enableIP6Tables: true,
|
||||
enableIPv6: true,
|
||||
enableIPMasquerade: true,
|
||||
hostIPv4: hostIPv4,
|
||||
hostIPv6: hostIPv6,
|
||||
wantIPv4Snat: true,
|
||||
wantIPv6Snat: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
defer netnsutils.SetupTestOSContext(t)()
|
||||
|
@ -308,6 +334,7 @@ func TestOutgoingNATRules(t *testing.T) {
|
|||
EnableIPv6: tc.enableIPv6,
|
||||
EnableIPMasquerade: tc.enableIPMasquerade,
|
||||
HostIPv4: tc.hostIPv4,
|
||||
HostIPv6: tc.hostIPv6,
|
||||
}
|
||||
ipv4Data := []driverapi.IPAMData{{Pool: maskedBrIPv4, Gateway: brIPv4}}
|
||||
ipv6Data := []driverapi.IPAMData{{Pool: maskedBrIPv6, Gateway: brIPv6}}
|
||||
|
@ -343,6 +370,7 @@ func TestOutgoingNATRules(t *testing.T) {
|
|||
{tc.wantIPv4Masq, iptRule{iptables.IPv4, iptables.Nat, "POSTROUTING", []string{"-s", maskedBrIPv4.String(), "!", "-o", br, "-j", "MASQUERADE"}}},
|
||||
{tc.wantIPv4Snat, iptRule{iptables.IPv4, iptables.Nat, "POSTROUTING", []string{"-s", maskedBrIPv4.String(), "!", "-o", br, "-j", "SNAT", "--to-source", hostIPv4.String()}}},
|
||||
{tc.wantIPv6Masq, iptRule{iptables.IPv6, iptables.Nat, "POSTROUTING", []string{"-s", maskedBrIPv6.String(), "!", "-o", br, "-j", "MASQUERADE"}}},
|
||||
{tc.wantIPv6Snat, iptRule{iptables.IPv6, iptables.Nat, "POSTROUTING", []string{"-s", maskedBrIPv6.String(), "!", "-o", br, "-j", "SNAT", "--to-source", hostIPv6.String()}}},
|
||||
} {
|
||||
assert.Equal(t, rc.rule.Exists(), rc.want)
|
||||
}
|
||||
|
|
|
@ -47,6 +47,9 @@ const (
|
|||
// HostIPv4 is the Source-IPv4 Address used to SNAT IPv4 container traffic
|
||||
HostIPv4 = Prefix + ".host_ipv4"
|
||||
|
||||
// HostIPv6 is the Source-IPv6 Address used to SNAT IPv6 container traffic
|
||||
HostIPv6 = Prefix + ".host_ipv6"
|
||||
|
||||
// LocalKVClient constants represents the local kv store client
|
||||
LocalKVClient = DriverPrivatePrefix + "localkv_client"
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue