123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- package bridge
- import (
- "net"
- "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)()
- nh, err := netlink.NewHandle()
- if err != nil {
- t.Fatal(err)
- }
- createTestBridge(getBasicTestConfig(), &bridgeInterface{nlh: nh}, t)
- // Store various iptables chain rules we care for.
- rules := []struct {
- rule iptRule
- descr string
- }{
- {iptRule{ipv: iptables.IPv4, table: iptables.Filter, chain: "FORWARD", args: []string{"-d", "127.1.2.3", "-i", "lo", "-o", "lo", "-j", "DROP"}}, "Test Loopback"},
- {iptRule{ipv: iptables.IPv4, table: iptables.Nat, chain: "POSTROUTING", args: []string{"-s", iptablesTestBridgeIP, "!", "-o", DefaultBridgeName, "-j", "MASQUERADE"}}, "NAT Test"},
- {iptRule{ipv: iptables.IPv4, table: iptables.Filter, chain: "FORWARD", args: []string{"-o", DefaultBridgeName, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}}, "Test ACCEPT INCOMING"},
- {iptRule{ipv: iptables.IPv4, table: iptables.Filter, chain: "FORWARD", args: []string{"-i", DefaultBridgeName, "!", "-o", DefaultBridgeName, "-j", "ACCEPT"}}, "Test ACCEPT NON_ICC OUTGOING"},
- {iptRule{ipv: iptables.IPv4, table: iptables.Filter, chain: "FORWARD", args: []string{"-i", DefaultBridgeName, "-o", DefaultBridgeName, "-j", "ACCEPT"}}, "Test enable ICC"},
- {iptRule{ipv: iptables.IPv4, table: iptables.Filter, chain: "FORWARD", args: []string{"-i", DefaultBridgeName, "-o", DefaultBridgeName, "-j", "DROP"}}, "Test disable ICC"},
- }
- // Assert the chain rules' insertion and removal.
- for _, c := range rules {
- assertIPTableChainProgramming(c.rule, c.descr, t)
- }
- }
- func TestSetupIPChains(t *testing.T) {
- // Create a test bridge with a basic bridge configuration (name + IPv4).
- defer netnsutils.SetupTestOSContext(t)()
- nh, err := netlink.NewHandle()
- if err != nil {
- t.Fatal(err)
- }
- driverconfig := configuration{
- EnableIPTables: true,
- }
- d := &driver{
- config: driverconfig,
- }
- assertChainConfig(d, t)
- config := getBasicTestConfig()
- br := &bridgeInterface{nlh: nh}
- createTestBridge(config, br, t)
- assertBridgeConfig(config, br, d, t)
- config.EnableIPMasquerade = true
- assertBridgeConfig(config, br, d, t)
- config.EnableICC = true
- assertBridgeConfig(config, br, d, t)
- config.EnableIPMasquerade = false
- assertBridgeConfig(config, br, d, t)
- }
- func getBasicTestConfig() *networkConfiguration {
- config := &networkConfiguration{
- BridgeName: DefaultBridgeName,
- AddressIPv4: &net.IPNet{IP: net.ParseIP(iptablesTestBridgeIP), Mask: net.CIDRMask(16, 32)},
- }
- return config
- }
- func createTestBridge(config *networkConfiguration, br *bridgeInterface, t *testing.T) {
- if err := setupDevice(config, br); err != nil {
- t.Fatalf("Failed to create the testing Bridge: %s", err.Error())
- }
- if err := setupBridgeIPv4(config, br); err != nil {
- t.Fatalf("Failed to bring up the testing Bridge: %s", err.Error())
- }
- if config.EnableIPv6 {
- if err := setupBridgeIPv6(config, br); err != nil {
- t.Fatalf("Failed to bring up the testing Bridge: %s", err.Error())
- }
- }
- }
- // Assert base function which pushes iptables chain rules on insertion and removal.
- func assertIPTableChainProgramming(rule iptRule, descr string, t *testing.T) {
- // Add
- if err := programChainRule(rule, descr, true); err != nil {
- t.Fatalf("Failed to program iptable rule %s: %s", descr, err.Error())
- }
- if !rule.Exists() {
- t.Fatalf("Failed to effectively program iptable rule: %s", descr)
- }
- // Remove
- if err := programChainRule(rule, descr, false); err != nil {
- t.Fatalf("Failed to remove iptable rule %s: %s", descr, err.Error())
- }
- if rule.Exists() {
- t.Fatalf("Failed to effectively remove iptable rule: %s", descr)
- }
- }
- // Assert function which create chains.
- func assertChainConfig(d *driver, t *testing.T) {
- var err error
- d.natChain, d.filterChain, d.isolationChain1, d.isolationChain2, err = setupIPChains(d.config, iptables.IPv4)
- if err != nil {
- t.Fatal(err)
- }
- if d.config.EnableIP6Tables {
- d.natChainV6, d.filterChainV6, d.isolationChain1V6, d.isolationChain2V6, err = setupIPChains(d.config, iptables.IPv6)
- if err != nil {
- t.Fatal(err)
- }
- }
- }
- // Assert function which pushes chains based on bridge config parameters.
- func assertBridgeConfig(config *networkConfiguration, br *bridgeInterface, d *driver, t *testing.T) {
- nw := bridgeNetwork{
- portMapper: portmapper.New(),
- portMapperV6: portmapper.New(),
- config: config,
- }
- nw.driver = d
- // Attempt programming of ip tables.
- err := nw.setupIP4Tables(config, br)
- if err != nil {
- t.Fatalf("%v", err)
- }
- if d.config.EnableIP6Tables {
- if err := nw.setupIP6Tables(config, br); err != nil {
- t.Fatalf("%v", err)
- }
- }
- }
- // Regression test for https://github.com/moby/moby/issues/46445
- func TestSetupIP6TablesWithHostIPv4(t *testing.T) {
- defer netnsutils.SetupTestOSContext(t)()
- d := newDriver()
- dc := &configuration{
- EnableIPTables: true,
- EnableIP6Tables: true,
- }
- if err := d.configure(map[string]interface{}{netlabel.GenericData: dc}); err != nil {
- t.Fatal(err)
- }
- nc := &networkConfiguration{
- BridgeName: DefaultBridgeName,
- AddressIPv4: &net.IPNet{IP: net.ParseIP(iptablesTestBridgeIP), Mask: net.CIDRMask(16, 32)},
- EnableIPMasquerade: true,
- EnableIPv6: true,
- AddressIPv6: &net.IPNet{IP: net.ParseIP("2001:db8::1"), Mask: net.CIDRMask(64, 128)},
- HostIPv4: net.ParseIP("192.0.2.2"),
- }
- nh, err := netlink.NewHandle()
- if err != nil {
- t.Fatal(err)
- }
- br := &bridgeInterface{nlh: nh}
- 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")
- hostIPv6 := net.ParseIP("2001:db8:1::1")
- for _, tc := range []struct {
- desc string
- enableIPTables bool
- enableIP6Tables bool
- 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
- // are needed even if outgoing NAT is disabled. Hairpin NAT tests belong with the port
- // forwarding DNAT tests.
- wantIPv4Masq bool
- wantIPv4Snat bool
- wantIPv6Masq bool
- wantIPv6Snat 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,
- hostIPv6: hostIPv6,
- },
- {
- 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,
- hostIPv6: hostIPv6,
- },
- {
- 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 masquerade, IPv6 SNAT",
- enableIPTables: true,
- enableIP6Tables: true,
- enableIPv6: true,
- enableIPMasquerade: true,
- hostIPv6: hostIPv6,
- wantIPv4Masq: true,
- wantIPv6Snat: true,
- },
- {
- desc: "IPv4 SNAT, IPv6 masquerade",
- enableIPTables: true,
- enableIP6Tables: true,
- enableIPv6: true,
- enableIPMasquerade: true,
- hostIPv4: hostIPv4,
- 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)()
- 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,
- HostIPv6: tc.hostIPv6,
- }
- 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"}}},
- {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)
- }
- })
- }
- }
|