package libnetwork import ( "context" "fmt" "regexp" "strconv" "strings" "testing" "github.com/moby/moby/v2/daemon/libnetwork/config" "github.com/moby/moby/v2/daemon/libnetwork/drivers/bridge" "github.com/moby/moby/v2/daemon/libnetwork/internal/nftables" "github.com/moby/moby/v2/daemon/libnetwork/iptables" "github.com/moby/moby/v2/internal/testutil/netnsutils" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/golden" "gotest.tools/v3/icmd" "gotest.tools/v3/skip" ) const ( fwdChainName = "FORWARD" usrChainName = userChain ) func TestUserChain(t *testing.T) { const testName = "TestUserChain" iptable4 := iptables.GetIptable(iptables.IPv4) iptable6 := iptables.GetIptable(iptables.IPv6) res := icmd.RunCommand("iptables", "--version") assert.NilError(t, res.Error) noChainErr := "No chain/target/match by that name" if strings.Contains(res.Combined(), "nf_tables") && versionLt(t, res.Combined(), 1, 8, 10) { // Prior to v1.8.10, iptables-nft "-S " reports the following for a non-existent chain: // // ip6tables v1.8.9 (nf_tables): chain `' in table `filter' is incompatible, use 'nft' tool. // // This was fixed in this commit: https://git.netfilter.org/iptables/commit/?id=82ccfb488eeac5507471099b9b4e6d136cc06e3b noChainErr = "incompatible, use 'nft' tool" } tests := []struct { iptables bool append bool // append other rules to FORWARD }{ { iptables: true, append: false, }, { iptables: true, append: true, }, { iptables: false, append: false, }, } for _, tc := range tests { t.Run(fmt.Sprintf("iptables=%v,append=%v", tc.iptables, tc.append), func(t *testing.T) { defer netnsutils.SetupTestOSContext(t)() defer resetIptables(t) c, err := New( context.Background(), config.OptionDataDir(t.TempDir()), config.OptionBridgeConfig(bridge.Configuration{ EnableIPTables: tc.iptables, EnableIP6Tables: tc.iptables, })) assert.NilError(t, err) defer c.Stop() skip.If(t, nftables.Enabled(), "nftables is enabled, skipping iptables test") // init. condition golden.Assert(t, getRules(t, iptable4, fwdChainName), fmt.Sprintf("%s/iptables-%v_append-%v_fwdinit4.golden", testName, tc.iptables, tc.append)) golden.Assert(t, getRules(t, iptable6, fwdChainName), fmt.Sprintf("%s/iptables-%v_append-%v_fwdinit6.golden", testName, tc.iptables, tc.append)) if tc.iptables { golden.Assert(t, getRules(t, iptable4, bridge.DockerForwardChain), fmt.Sprintf("%s/iptables-%v_append-%v_dockerfwdinit4.golden", testName, tc.iptables, tc.append)) golden.Assert(t, getRules(t, iptable6, bridge.DockerForwardChain), fmt.Sprintf("%s/iptables-%v_append-%v_dockerfwdinit6.golden", testName, tc.iptables, tc.append)) } else { assert.Check(t, !iptables.GetIptable(iptables.IPv4).ExistChain(bridge.DockerForwardChain, fwdChainName), "Chain %s should not exist", bridge.DockerForwardChain) } if tc.append { _, err := iptable4.Raw("-A", fwdChainName, "-j", "DROP") assert.Check(t, err) _, err = iptable6.Raw("-A", fwdChainName, "-j", "DROP") assert.Check(t, err) } c.setupUserChains() golden.Assert(t, getRules(t, iptable4, fwdChainName), fmt.Sprintf("%s/iptables-%v_append-%v_fwdafter4.golden", testName, tc.iptables, tc.append)) golden.Assert(t, getRules(t, iptable6, fwdChainName), fmt.Sprintf("%s/iptables-%v_append-%v_fwdafter6.golden", testName, tc.iptables, tc.append)) if tc.iptables { golden.Assert(t, getRules(t, iptable4, bridge.DockerForwardChain), fmt.Sprintf("%s/iptables-%v_append-%v_dockerfwdafter4.golden", testName, tc.iptables, tc.append)) golden.Assert(t, getRules(t, iptable6, bridge.DockerForwardChain), fmt.Sprintf("%s/iptables-%v_append-%v_dockerfwdafter6.golden", testName, tc.iptables, tc.append)) } else { assert.Check(t, !iptables.GetIptable(iptables.IPv4).ExistChain(bridge.DockerForwardChain, fwdChainName), "Chain %s should not exist", bridge.DockerForwardChain) } if tc.iptables { golden.Assert(t, getRules(t, iptable4, usrChainName), fmt.Sprintf("%s/iptables-%v_append-%v_usrafter4.golden", testName, tc.iptables, tc.append)) golden.Assert(t, getRules(t, iptable6, usrChainName), fmt.Sprintf("%s/iptables-%v_append-%v_usrafter6.golden", testName, tc.iptables, tc.append)) } else { _, err := iptable4.Raw("-S", usrChainName) assert.Check(t, is.ErrorContains(err, noChainErr), "ipv4 chain %v: created unexpectedly", usrChainName) _, err = iptable6.Raw("-S", usrChainName) assert.Check(t, is.ErrorContains(err, noChainErr), "ipv6 chain %v: created unexpectedly", usrChainName) } }) } } func getRules(t *testing.T, iptable *iptables.IPTable, chain string) string { t.Helper() output, err := iptable.Raw("-S", chain) assert.NilError(t, err, "chain %s: failed to get rules", chain) return string(output) } func resetIptables(t *testing.T) { t.Helper() for _, ipVer := range []iptables.IPVersion{iptables.IPv4, iptables.IPv6} { iptable := iptables.GetIptable(ipVer) _, err := iptable.Raw("-F", fwdChainName) assert.Check(t, err) _ = iptable.RemoveExistingChain(usrChainName, iptables.Filter) } } // versionLt returns true if the iptables version returned by `iptables --version` // is less than the `..` version passed in as argument. func versionLt(t *testing.T, ver string, major, minor, patch int) bool { t.Helper() matches := regexp.MustCompile(`iptables v([0-9]+)\.([0-9]+)\.([0-9]+)`).FindStringSubmatch(ver) assert.Assert(t, len(matches) == 4, "could not determine iptables version from %q", ver) parsedMajor, err := strconv.Atoi(matches[1]) assert.NilError(t, err) parsedMinor, err := strconv.Atoi(matches[2]) assert.NilError(t, err) parsedPatch, err := strconv.Atoi(matches[3]) assert.NilError(t, err) return parsedMajor < major || (parsedMajor == major && parsedMinor < minor) || (parsedMajor == major && parsedMinor == minor && parsedPatch < patch) }