mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
349 lines
8.3 KiB
Go
349 lines
8.3 KiB
Go
//go:build linux
|
|
|
|
package iptables
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/moby/moby/v2/internal/testutil/netnsutils"
|
|
"golang.org/x/sync/errgroup"
|
|
"gotest.tools/v3/assert"
|
|
"gotest.tools/v3/skip"
|
|
)
|
|
|
|
const (
|
|
chainName = "DOCKEREST"
|
|
bridgeName = "lo"
|
|
)
|
|
|
|
func createNewChain(t *testing.T) (*IPTable, *ChainInfo, *ChainInfo) {
|
|
t.Helper()
|
|
iptable := GetIptable(IPv4)
|
|
|
|
natChain, err := iptable.NewChain(chainName, Nat)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
filterChain, err := iptable.NewChain(chainName, Filter)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return iptable, natChain, filterChain
|
|
}
|
|
|
|
func TestNewChain(t *testing.T) {
|
|
createNewChain(t)
|
|
}
|
|
|
|
func TestLink(t *testing.T) {
|
|
iptable, _, filterChain := createNewChain(t)
|
|
ip1 := netip.MustParseAddr("192.168.1.1")
|
|
ip2 := netip.MustParseAddr("192.168.1.2")
|
|
port := 1234
|
|
proto := "tcp"
|
|
|
|
err := filterChain.Link(Append, ip1, ip2, port, proto, bridgeName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rule1 := []string{
|
|
"-i", bridgeName,
|
|
"-o", bridgeName,
|
|
"-p", proto,
|
|
"-s", ip1.String(),
|
|
"-d", ip2.String(),
|
|
"--dport", strconv.Itoa(port),
|
|
"-j", "ACCEPT",
|
|
}
|
|
|
|
if !iptable.Exists(filterChain.Table, filterChain.Name, rule1...) {
|
|
t.Fatal("rule1 does not exist")
|
|
}
|
|
|
|
rule2 := []string{
|
|
"-i", bridgeName,
|
|
"-o", bridgeName,
|
|
"-p", proto,
|
|
"-s", ip2.String(),
|
|
"-d", ip1.String(),
|
|
"--sport", strconv.Itoa(port),
|
|
"-j", "ACCEPT",
|
|
}
|
|
|
|
if !iptable.Exists(filterChain.Table, filterChain.Name, rule2...) {
|
|
t.Fatal("rule2 does not exist")
|
|
}
|
|
}
|
|
|
|
func TestPrerouting(t *testing.T) {
|
|
iptable, natChain, _ := createNewChain(t)
|
|
|
|
args := []string{"-i", "lo", "-d", "192.168.1.1"}
|
|
err := natChain.Prerouting(Insert, args...)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !iptable.Exists(natChain.Table, "PREROUTING", args...) {
|
|
t.Fatal("rule does not exist")
|
|
}
|
|
|
|
delRule := append([]string{"-D", "PREROUTING", "-t", string(Nat)}, args...)
|
|
if _, err = iptable.Raw(delRule...); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestOutput(t *testing.T) {
|
|
iptable, natChain, _ := createNewChain(t)
|
|
|
|
args := []string{"-o", "lo", "-d", "192.168.1.1"}
|
|
err := natChain.Output(Insert, args...)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !iptable.Exists(natChain.Table, "OUTPUT", args...) {
|
|
t.Fatal("rule does not exist")
|
|
}
|
|
|
|
delRule := append([]string{
|
|
"-D", "OUTPUT", "-t",
|
|
string(natChain.Table),
|
|
}, args...)
|
|
if _, err = iptable.Raw(delRule...); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Runs 10 concurrent rule additions. This will fail if iptables
|
|
// is actually invoked simultaneously without --wait.
|
|
func TestConcurrencyWithWait(t *testing.T) {
|
|
_, natChain, _ := createNewChain(t)
|
|
|
|
ip := net.ParseIP("192.168.1.1")
|
|
port := 1234
|
|
dstAddr := "172.17.0.1"
|
|
dstPort := 4321
|
|
proto := "tcp"
|
|
|
|
group := new(errgroup.Group)
|
|
for range 10 {
|
|
group.Go(func() error {
|
|
return addSomeRules(natChain, ip, port, proto, dstAddr, dstPort)
|
|
})
|
|
}
|
|
if err := group.Wait(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// addSomeRules adds arbitrary iptable rules. RunConcurrencyTest previously used
|
|
// iptables.Forward to create rules, that function has been removed. To preserve
|
|
// the test, this function creates similar rules.
|
|
func addSomeRules(c *ChainInfo, ip net.IP, port int, proto, destAddr string, destPort int) error {
|
|
iptable := GetIptable(c.IPVersion)
|
|
daddr := ip.String()
|
|
|
|
args := []string{
|
|
"-p", proto,
|
|
"-d", daddr,
|
|
"--dport", strconv.Itoa(port),
|
|
"-j", "DNAT",
|
|
"--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort)),
|
|
}
|
|
if err := iptable.ProgramRule(Nat, c.Name, Append, args); err != nil {
|
|
return err
|
|
}
|
|
|
|
args = []string{
|
|
"!", "-i", "lo",
|
|
"-o", "lo",
|
|
"-p", proto,
|
|
"-d", destAddr,
|
|
"--dport", strconv.Itoa(destPort),
|
|
"-j", "ACCEPT",
|
|
}
|
|
if err := iptable.ProgramRule(Filter, c.Name, Append, args); err != nil {
|
|
return err
|
|
}
|
|
|
|
args = []string{
|
|
"-p", proto,
|
|
"-s", destAddr,
|
|
"-d", destAddr,
|
|
"--dport", strconv.Itoa(destPort),
|
|
"-j", "MASQUERADE",
|
|
}
|
|
if err := iptable.ProgramRule(Nat, "POSTROUTING", Append, args); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func TestCleanup(t *testing.T) {
|
|
iptable, _, filterChain := createNewChain(t)
|
|
|
|
filterChain.Remove()
|
|
|
|
err := iptable.RemoveExistingChain(chainName, Nat)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rules, err := exec.Command("iptables-save").Output()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(string(rules), chainName) {
|
|
t.Fatalf("Removing chain failed. %s found in iptables-save", chainName)
|
|
}
|
|
}
|
|
|
|
func TestExistsRaw(t *testing.T) {
|
|
const testChain1 = "ABCD"
|
|
const testChain2 = "EFGH"
|
|
|
|
iptable := GetIptable(IPv4)
|
|
|
|
_, err := iptable.NewChain(testChain1, Filter)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() {
|
|
iptable.RemoveExistingChain(testChain1, Filter)
|
|
}()
|
|
|
|
_, err = iptable.NewChain(testChain2, Filter)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() {
|
|
iptable.RemoveExistingChain(testChain2, Filter)
|
|
}()
|
|
|
|
// Test detection over full and truncated rule string
|
|
input := []struct{ rule []string }{
|
|
{[]string{"-s", "172.8.9.9/32", "-j", "ACCEPT"}},
|
|
{[]string{"-d", "172.8.9.0/24", "-j", "DROP"}},
|
|
{[]string{"-s", "172.0.3.0/24", "-d", "172.17.0.0/24", "-p", "tcp", "-m", "tcp", "--dport", "80", "-j", testChain2}},
|
|
{[]string{"-j", "RETURN"}},
|
|
}
|
|
|
|
for i, r := range input {
|
|
ruleAdd := append([]string{"-t", string(Filter), "-A", testChain1}, r.rule...)
|
|
err = iptable.RawCombinedOutput(ruleAdd...)
|
|
if err != nil {
|
|
t.Fatalf("i=%d, err: %v", i, err)
|
|
}
|
|
if !iptable.exists(true, Filter, testChain1, r.rule...) {
|
|
t.Fatalf("Failed to detect rule. i=%d", i)
|
|
}
|
|
// Truncate the rule
|
|
trg := r.rule[len(r.rule)-1]
|
|
trg = trg[:len(trg)-2]
|
|
r.rule[len(r.rule)-1] = trg
|
|
if iptable.exists(true, Filter, testChain1, r.rule...) {
|
|
t.Fatalf("Invalid detection. i=%d", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRule(t *testing.T) {
|
|
skip.If(t, UsingFirewalld(), "firewalld is running in the host netns, it can't modify rules in the test's netns")
|
|
defer netnsutils.SetupTestOSContext(t)()
|
|
|
|
assert.NilError(t, exec.Command("iptables", "-N", "TESTCHAIN").Run())
|
|
|
|
rule := Rule{IPVer: IPv4, Table: Filter, Chain: "TESTCHAIN", Args: []string{"-j", "RETURN"}}
|
|
assert.NilError(t, rule.Insert())
|
|
assert.Equal(t, rule.Exists(), true)
|
|
assert.Equal(t, mustDumpChain(t, Filter, "TESTCHAIN"), `-N TESTCHAIN
|
|
-A TESTCHAIN -j RETURN
|
|
`)
|
|
|
|
// Test that Insert is idempotent
|
|
assert.NilError(t, rule.Insert())
|
|
assert.Equal(t, mustDumpChain(t, Filter, "TESTCHAIN"), `-N TESTCHAIN
|
|
-A TESTCHAIN -j RETURN
|
|
`)
|
|
|
|
rule = Rule{IPVer: IPv4, Table: Filter, Chain: "TESTCHAIN", Args: []string{"-j", "ACCEPT"}}
|
|
assert.NilError(t, rule.Append())
|
|
assert.Equal(t, rule.Exists(), true)
|
|
assert.Equal(t, mustDumpChain(t, Filter, "TESTCHAIN"), `-N TESTCHAIN
|
|
-A TESTCHAIN -j RETURN
|
|
-A TESTCHAIN -j ACCEPT
|
|
`)
|
|
|
|
// Test that Append is idempotent
|
|
assert.NilError(t, rule.Append())
|
|
assert.Equal(t, mustDumpChain(t, Filter, "TESTCHAIN"), `-N TESTCHAIN
|
|
-A TESTCHAIN -j RETURN
|
|
-A TESTCHAIN -j ACCEPT
|
|
`)
|
|
|
|
assert.NilError(t, rule.Delete())
|
|
assert.Equal(t, rule.Exists(), false)
|
|
assert.Equal(t, mustDumpChain(t, Filter, "TESTCHAIN"), `-N TESTCHAIN
|
|
-A TESTCHAIN -j RETURN
|
|
`)
|
|
|
|
// Test that Delete is idempotent
|
|
assert.NilError(t, rule.Delete())
|
|
}
|
|
|
|
func mustDumpChain(t *testing.T, table Table, chain string) string {
|
|
t.Helper()
|
|
out, err := exec.Command("iptables", "-t", string(table), "-S", chain).CombinedOutput()
|
|
assert.NilError(t, err, "output:\n%s", out)
|
|
return string(out)
|
|
}
|
|
|
|
func TestFlushChain(t *testing.T) {
|
|
_ = firewalldInit()
|
|
if UsingFirewalld() {
|
|
t.Skip("firewalld in host netns cannot create rules in the test's netns")
|
|
}
|
|
defer netnsutils.SetupTestOSContext(t)()
|
|
|
|
iptable := GetIptable(IPv4)
|
|
chain := "TESTFLUSHCHAIN"
|
|
table := Filter
|
|
|
|
// Ensure the chain exists
|
|
assert.NilError(t, iptable.RemoveExistingChain(chain, table))
|
|
_, err := iptable.NewChain(chain, table)
|
|
assert.NilError(t, err)
|
|
|
|
// Add a rule to the chain
|
|
rule := Rule{
|
|
IPVer: IPv4, Table: table, Chain: chain,
|
|
Args: []string{"-j", "ACCEPT"},
|
|
}
|
|
assert.NilError(t, rule.Insert())
|
|
|
|
// Flush the chain
|
|
assert.NilError(t, iptable.FlushChain(table, chain))
|
|
|
|
// Check that the chain exists and is empty (only the chain definition remains)
|
|
out, err := exec.Command("iptables", "-t", string(table), "-S", chain).CombinedOutput()
|
|
assert.NilError(t, err)
|
|
|
|
rulesCount := strings.Count(string(out), fmt.Sprintf("-A %s ", chain))
|
|
assert.Check(t, rulesCount == 0)
|
|
|
|
// Cleanup
|
|
_ = iptable.RemoveExistingChain(chain, table)
|
|
}
|