Merge pull request #46677 from rhansen/nat-test
bridge: Add unit tests for outgoing NAT rules
This commit is contained in:
commit
fc4d035e7a
2 changed files with 174 additions and 0 deletions
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/docker/libnetwork/iptables"
|
||||
|
@ -241,6 +242,14 @@ func (r iptRule) Delete() error {
|
|||
return r.exec(iptables.Delete)
|
||||
}
|
||||
|
||||
func (r iptRule) String() string {
|
||||
cmd := append([]string{"iptables"}, r.cmdArgs("-A")...)
|
||||
if r.ipv == iptables.IPv6 {
|
||||
cmd[0] = "ip6tables"
|
||||
}
|
||||
return strings.Join(cmd, " ")
|
||||
}
|
||||
|
||||
func setupIPTablesInternal(ipVer iptables.IPVersion, config *networkConfiguration, addr *net.IPNet, hairpin, enable bool) error {
|
||||
var (
|
||||
address = addr.String()
|
||||
|
|
|
@ -5,16 +5,36 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/docker/docker/internal/testutils/netnsutils"
|
||||
"github.com/docker/docker/libnetwork/driverapi"
|
||||
"github.com/docker/docker/libnetwork/iptables"
|
||||
"github.com/docker/docker/libnetwork/netlabel"
|
||||
"github.com/docker/docker/libnetwork/portmapper"
|
||||
"github.com/vishvananda/netlink"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
iptablesTestBridgeIP = "192.168.42.1"
|
||||
)
|
||||
|
||||
// A testRegisterer implements the driverapi.Registerer interface.
|
||||
type testRegisterer struct {
|
||||
t *testing.T
|
||||
d *driver
|
||||
}
|
||||
|
||||
func (r *testRegisterer) RegisterDriver(name string, di driverapi.Driver, _ driverapi.Capability) error {
|
||||
if got, want := name, "bridge"; got != want {
|
||||
r.t.Fatalf("got driver name %s, want %s", got, want)
|
||||
}
|
||||
d, ok := di.(*driver)
|
||||
if !ok {
|
||||
r.t.Fatalf("got driver type %T, want %T", di, &driver{})
|
||||
}
|
||||
r.d = d
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestProgramIPTable(t *testing.T) {
|
||||
// Create a test bridge with a basic bridge configuration (name + IPv4).
|
||||
defer netnsutils.SetupTestOSContext(t)()
|
||||
|
@ -184,3 +204,148 @@ func TestSetupIP6TablesWithHostIPv4(t *testing.T) {
|
|||
createTestBridge(nc, br, t)
|
||||
assertBridgeConfig(nc, br, d, t)
|
||||
}
|
||||
|
||||
func TestOutgoingNATRules(t *testing.T) {
|
||||
br := "br-nattest"
|
||||
brIPv4 := &net.IPNet{IP: net.ParseIP(iptablesTestBridgeIP), Mask: net.CIDRMask(16, 32)}
|
||||
brIPv6 := &net.IPNet{IP: net.ParseIP("2001:db8::1"), Mask: net.CIDRMask(64, 128)}
|
||||
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")
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
enableIPTables bool
|
||||
enableIP6Tables bool
|
||||
enableIPv6 bool
|
||||
enableIPMasquerade bool
|
||||
hostIPv4 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
|
||||
// are needed even if outgoing NAT is disabled. Hairpin NAT tests belong with the port
|
||||
// forwarding DNAT tests.
|
||||
wantIPv4Masq bool
|
||||
wantIPv4Snat bool
|
||||
wantIPv6Masq bool
|
||||
}{
|
||||
{
|
||||
desc: "everything disabled",
|
||||
},
|
||||
{
|
||||
desc: "iptables/ip6tables disabled",
|
||||
enableIPv6: true,
|
||||
enableIPMasquerade: true,
|
||||
},
|
||||
{
|
||||
desc: "host IP with iptables/ip6tables disabled",
|
||||
enableIPv6: true,
|
||||
enableIPMasquerade: true,
|
||||
hostIPv4: hostIPv4,
|
||||
},
|
||||
{
|
||||
desc: "masquerade disabled, no host IP",
|
||||
enableIPTables: true,
|
||||
enableIP6Tables: true,
|
||||
enableIPv6: true,
|
||||
},
|
||||
{
|
||||
desc: "masquerade disabled, with host IP",
|
||||
enableIPTables: true,
|
||||
enableIP6Tables: true,
|
||||
enableIPv6: true,
|
||||
hostIPv4: hostIPv4,
|
||||
},
|
||||
{
|
||||
desc: "IPv4 masquerade, IPv6 disabled",
|
||||
enableIPTables: true,
|
||||
enableIPMasquerade: true,
|
||||
wantIPv4Masq: true,
|
||||
},
|
||||
{
|
||||
desc: "IPv4 SNAT, IPv6 disabled",
|
||||
enableIPTables: true,
|
||||
enableIPMasquerade: true,
|
||||
hostIPv4: hostIPv4,
|
||||
wantIPv4Snat: true,
|
||||
},
|
||||
{
|
||||
desc: "IPv4 masquerade, IPv6 masquerade",
|
||||
enableIPTables: true,
|
||||
enableIP6Tables: true,
|
||||
enableIPv6: true,
|
||||
enableIPMasquerade: true,
|
||||
wantIPv4Masq: true,
|
||||
wantIPv6Masq: true,
|
||||
},
|
||||
{
|
||||
desc: "IPv4 SNAT, IPv6 masquerade",
|
||||
enableIPTables: true,
|
||||
enableIP6Tables: true,
|
||||
enableIPv6: true,
|
||||
enableIPMasquerade: true,
|
||||
hostIPv4: hostIPv4,
|
||||
wantIPv4Snat: true,
|
||||
wantIPv6Masq: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
defer netnsutils.SetupTestOSContext(t)()
|
||||
dc := &configuration{
|
||||
EnableIPTables: tc.enableIPTables,
|
||||
EnableIP6Tables: tc.enableIP6Tables,
|
||||
}
|
||||
r := &testRegisterer{t: t}
|
||||
if err := Register(r, map[string]interface{}{netlabel.GenericData: dc}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r.d == nil {
|
||||
t.Fatal("testRegisterer.RegisterDriver never called")
|
||||
}
|
||||
nc := &networkConfiguration{
|
||||
BridgeName: br,
|
||||
AddressIPv4: brIPv4,
|
||||
AddressIPv6: brIPv6,
|
||||
EnableIPv6: tc.enableIPv6,
|
||||
EnableIPMasquerade: tc.enableIPMasquerade,
|
||||
HostIPv4: tc.hostIPv4,
|
||||
}
|
||||
ipv4Data := []driverapi.IPAMData{{Pool: maskedBrIPv4, Gateway: brIPv4}}
|
||||
ipv6Data := []driverapi.IPAMData{{Pool: maskedBrIPv6, Gateway: brIPv6}}
|
||||
if !nc.EnableIPv6 {
|
||||
nc.AddressIPv6 = nil
|
||||
ipv6Data = nil
|
||||
}
|
||||
if err := r.d.CreateNetwork("nattest", map[string]interface{}{netlabel.GenericData: nc}, nil, ipv4Data, ipv6Data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := r.d.DeleteNetwork("nattest"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
// Log the contents of all chains to aid troubleshooting.
|
||||
for _, ipv := range []iptables.IPVersion{iptables.IPv4, iptables.IPv6} {
|
||||
ipt := iptables.GetIptable(ipv)
|
||||
for _, table := range []iptables.Table{iptables.Nat, iptables.Filter, iptables.Mangle} {
|
||||
out, err := ipt.Raw("-t", string(table), "-S")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Logf("%s: %s %s table rules:\n%s", tc.desc, ipv, table, string(out))
|
||||
}
|
||||
}
|
||||
for _, rc := range []struct {
|
||||
want bool
|
||||
rule iptRule
|
||||
}{
|
||||
// Rule order doesn't matter: At most one of the following IPv4 rules will exist, and the
|
||||
// same goes for the IPv6 rules.
|
||||
{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"}}},
|
||||
} {
|
||||
assert.Equal(t, rc.rule.Exists(), rc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue