Skip to content

Commit

Permalink
Disable bridge n/w masquerading for IPv4, IPv6, or both.
Browse files Browse the repository at this point in the history
Signed-off-by: Rob Murray <rob.murray@docker.com>
  • Loading branch information
robmry committed May 1, 2024
1 parent 9d07820 commit 43b5f91
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 78 deletions.
106 changes: 106 additions & 0 deletions integration/networking/bridge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package networking

import (
"context"
"errors"
"fmt"
"os/exec"
"regexp"
"strings"
"testing"
Expand All @@ -12,6 +14,7 @@ import (
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/integration/internal/network"
"github.com/docker/docker/libnetwork/drivers/bridge"
"github.com/docker/docker/testutil"
"github.com/docker/docker/testutil/daemon"
"github.com/google/go-cmp/cmp/cmpopts"
Expand Down Expand Up @@ -828,3 +831,106 @@ func TestReadOnlySlashProc(t *testing.T) {
})
}
}

// Check that masquerading can be disabled for IPv4/IPv6/both on a bridge
// network, and that the choice is preserved over a daemon restart.
func TestDisableMasquerade(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "no iptables on Windows")
skip.If(t, testEnv.IsRootless(), "can't see iptables in rootless mode")

ctx := setupTest(t)
d := daemon.New(t)
d.StartWithBusybox(ctx, t, "--experimental", "--ip6tables")
defer d.Stop(t)

c := d.NewClientT(t)
defer c.Close()

testcases := []struct {
name string
dOpt string
exp4 bool
exp6 bool
}{
{
name: "default",
exp4: true,
exp6: true,
},
{
name: "disable both",
dOpt: "com.docker.network.bridge.enable_ip_masquerade",
},
{
name: "disable 4",
dOpt: "com.docker.network.bridge.enable_ip4_masquerade",
exp6: true,
},
{
name: "disable 6",
dOpt: "com.docker.network.bridge.enable_ip6_masquerade",
exp4: true,
},
}

// Generate test config - a bridge network per test, subnet number is the test's index.
type ipam struct {
netName string
subnet4, gw4 string
subnet6, gw6 string
}
td := make([]*ipam, len(testcases))
for i := range testcases {
td[i] = &ipam{
netName: fmt.Sprintf("testnet%d", i),
subnet4: fmt.Sprintf("172.24.%d.0/24", i),
gw4: fmt.Sprintf("172.24.%d.1", i),
subnet6: fmt.Sprintf("fdc3:8049:23e9:%d::/64", i),
gw6: fmt.Sprintf("fdc3:8049:23e9:%d::1", i),
}
}

// Create networks.
for i, tc := range testcases {
nOpts := []func(*types.NetworkCreate){
network.WithIPv6(),
network.WithIPAM(td[i].subnet4, td[i].gw4),
network.WithIPAM(td[i].subnet6, td[i].gw6),
network.WithOption(bridge.BridgeName, td[i].netName),
}
if tc.dOpt != "" {
nOpts = append(nOpts, network.WithOption(tc.dOpt, "0"))
}
network.CreateNoError(ctx, t, c, td[i].netName, nOpts...)
defer network.RemoveNoError(ctx, t, c, td[i].netName)
}

// Function to check for expected presence/absence of masquerade rules for each network.
checkAllNetworks := func(when string) {
for i, tc := range testcases {
t.Run(when+"/"+tc.name, func(t *testing.T) {
checkRule := func(cmd, netName, subnet, ipv string, expRule bool) {
t.Helper()
args := []string{
"-t", "nat", "-C",
"POSTROUTING", "-s", subnet, "!", "-o", netName, "-j", "MASQUERADE",
}
err := exec.Command(cmd, args...).Run()
if expRule {
assert.Check(t, err, ipv+" masquerade rule expected: "+strings.Join(args, " "))
} else {
var ee *exec.ExitError
assert.Check(t, errors.As(err, &ee) && ee.ExitCode() == 1,
ipv+" masquerade rule unexpected: "+strings.Join(args, " "))
}
}
checkRule("iptables", td[i].netName, td[i].subnet4, "IPv4", tc.exp4)
checkRule("ip6tables", td[i].netName, td[i].subnet6, "IPv6", tc.exp6)
})
}
}

checkAllNetworks("initial")
d.Restart(t, "--experimental", "--ip6tables")
checkAllNetworks("restarted")
}
18 changes: 15 additions & 3 deletions libnetwork/drivers/bridge/bridge_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ type networkConfiguration struct {
ID string
BridgeName string
EnableIPv6 bool
EnableIPMasquerade bool
EnableIPMasquerade bool // false => no v4 or v6 masquerading
EnableIP4Masquerade bool
EnableIP6Masquerade bool
EnableICC bool
InhibitIPv4 bool
Mtu int
Expand Down Expand Up @@ -290,6 +292,14 @@ func (c *networkConfiguration) fromLabels(labels map[string]string) error {
if c.EnableIPMasquerade, err = strconv.ParseBool(value); err != nil {
return parseErr(label, value, err.Error())
}
case EnableIP4Masquerade:
if c.EnableIP4Masquerade, err = strconv.ParseBool(value); err != nil {
return parseErr(label, value, err.Error())
}
case EnableIP6Masquerade:
if c.EnableIP6Masquerade, err = strconv.ParseBool(value); err != nil {
return parseErr(label, value, err.Error())
}
case EnableICC:
if c.EnableICC, err = strconv.ParseBool(value); err != nil {
return parseErr(label, value, err.Error())
Expand Down Expand Up @@ -512,8 +522,10 @@ func parseNetworkGenericOptions(data interface{}) (*networkConfiguration, error)
config = opt
case map[string]string:
config = &networkConfiguration{
EnableICC: true,
EnableIPMasquerade: true,
EnableICC: true,
EnableIPMasquerade: true,
EnableIP4Masquerade: true,
EnableIP6Masquerade: true,
}
err = config.fromLabels(opt)
case options.Generic:
Expand Down
10 changes: 10 additions & 0 deletions libnetwork/drivers/bridge/bridge_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ func (ncfg *networkConfiguration) MarshalJSON() ([]byte, error) {
nMap["BridgeName"] = ncfg.BridgeName
nMap["EnableIPv6"] = ncfg.EnableIPv6
nMap["EnableIPMasquerade"] = ncfg.EnableIPMasquerade
nMap["EnableIP4Masquerade"] = ncfg.EnableIP4Masquerade
nMap["EnableIP6Masquerade"] = ncfg.EnableIP6Masquerade
nMap["EnableICC"] = ncfg.EnableICC
nMap["InhibitIPv4"] = ncfg.InhibitIPv4
nMap["Mtu"] = ncfg.Mtu
Expand Down Expand Up @@ -190,6 +192,14 @@ func (ncfg *networkConfiguration) UnmarshalJSON(b []byte) error {
ncfg.BridgeName = nMap["BridgeName"].(string)
ncfg.EnableIPv6 = nMap["EnableIPv6"].(bool)
ncfg.EnableIPMasquerade = nMap["EnableIPMasquerade"].(bool)
ncfg.EnableIP4Masquerade = true
if v, ok := nMap["EnableIP4Masquerade"]; ok {
ncfg.EnableIP4Masquerade = v.(bool)
}
ncfg.EnableIP6Masquerade = true
if v, ok := nMap["EnableIP6Masquerade"]; ok {
ncfg.EnableIP6Masquerade = v.(bool)
}
ncfg.EnableICC = nMap["EnableICC"].(bool)
if v, ok := nMap["InhibitIPv4"]; ok {
ncfg.InhibitIPv4 = v.(bool)
Expand Down
4 changes: 3 additions & 1 deletion libnetwork/drivers/bridge/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ const (
BridgeName = "com.docker.network.bridge.name"

// EnableIPMasquerade label for bridge driver
EnableIPMasquerade = "com.docker.network.bridge.enable_ip_masquerade"
EnableIPMasquerade = "com.docker.network.bridge.enable_ip_masquerade"
EnableIP4Masquerade = "com.docker.network.bridge.enable_ip4_masquerade"
EnableIP6Masquerade = "com.docker.network.bridge.enable_ip6_masquerade"

// EnableICC label
EnableICC = "com.docker.network.bridge.enable_icc"
Expand Down
20 changes: 10 additions & 10 deletions libnetwork/drivers/bridge/setup_ip_tables_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,15 @@ func setupIPTablesInternal(ipVer iptables.IPVersion, config *networkConfiguratio
outRule = iptRule{ipv: ipVer, table: iptables.Filter, chain: "FORWARD", args: []string{"-i", config.BridgeName, "!", "-o", config.BridgeName, "-j", "ACCEPT"}}
natArgs []string
hpNatArgs []string
hostIP net.IP
masq bool
)
hostIP := config.HostIPv4
if ipVer == iptables.IPv6 {
hostIP = config.HostIPv6
masq = config.EnableIPMasquerade && config.EnableIP6Masquerade
} else {
hostIP = config.HostIPv4
masq = config.EnableIPMasquerade && config.EnableIP4Masquerade
}
// If hostIP is set, the user wants IPv4/IPv6 SNAT with the given address.
if hostIP != nil {
Expand All @@ -278,16 +283,11 @@ func setupIPTablesInternal(ipVer iptables.IPVersion, config *networkConfiguratio
hpNatRule := iptRule{ipv: ipVer, table: iptables.Nat, chain: "POSTROUTING", args: hpNatArgs}

// Set NAT.
if config.EnableIPMasquerade {
if err := programChainRule(natRule, "NAT", enable); err != nil {
return err
}
if err := programChainRule(natRule, "NAT", masq && enable); err != nil {
return err
}

if config.EnableIPMasquerade && !hairpin {
if err := programChainRule(skipDNAT, "SKIP DNAT", enable); err != nil {
return err
}
if err := programChainRule(skipDNAT, "SKIP DNAT", masq && !hairpin && enable); err != nil {
return err
}

// In hairpin mode, masquerade traffic from localhost. If hairpin is disabled or if we're tearing down
Expand Down

0 comments on commit 43b5f91

Please sign in to comment.