moby/libnetwork/iptables/iptables_test.go
Albin Kerouanton 6a0a2c4f79
Always use iptables -C to look for rules
iptables -C flag was introduced in v1.4.11, which was released ten
years ago. Thus, there're no more Linux distributions supported by
Docker using this version. As such, this commit removes the old way of
checking if an iptables rule exists (by using substring matching).

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
(cherry picked from commit 799cc143c9)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-12-31 18:17:38 +01:00

301 lines
6.5 KiB
Go

//go:build linux
// +build linux
package iptables
import (
"net"
"os/exec"
"strconv"
"strings"
"testing"
"golang.org/x/sync/errgroup"
)
const (
chainName = "DOCKEREST"
bridgeName = "lo"
)
func createNewChain(t *testing.T) (*IPTable, *ChainInfo, *ChainInfo) {
t.Helper()
iptable := GetIptable(IPv4)
natChain, err := iptable.NewChain(chainName, Nat, false)
if err != nil {
t.Fatal(err)
}
err = iptable.ProgramChain(natChain, bridgeName, false, true)
if err != nil {
t.Fatal(err)
}
filterChain, err := iptable.NewChain(chainName, Filter, false)
if err != nil {
t.Fatal(err)
}
err = iptable.ProgramChain(filterChain, bridgeName, false, true)
if err != nil {
t.Fatal(err)
}
return iptable, natChain, filterChain
}
func TestNewChain(t *testing.T) {
createNewChain(t)
}
func TestForward(t *testing.T) {
iptable, natChain, filterChain := createNewChain(t)
ip := net.ParseIP("192.168.1.1")
port := 1234
dstAddr := "172.17.0.1"
dstPort := 4321
proto := "tcp"
err := natChain.Forward(Insert, ip, port, proto, dstAddr, dstPort, bridgeName)
if err != nil {
t.Fatal(err)
}
dnatRule := []string{
"-d", ip.String(),
"-p", proto,
"--dport", strconv.Itoa(port),
"-j", "DNAT",
"--to-destination", dstAddr + ":" + strconv.Itoa(dstPort),
"!", "-i", bridgeName,
}
if !iptable.Exists(natChain.Table, natChain.Name, dnatRule...) {
t.Fatal("DNAT rule does not exist")
}
filterRule := []string{
"!", "-i", bridgeName,
"-o", bridgeName,
"-d", dstAddr,
"-p", proto,
"--dport", strconv.Itoa(dstPort),
"-j", "ACCEPT",
}
if !iptable.Exists(filterChain.Table, filterChain.Name, filterRule...) {
t.Fatal("filter rule does not exist")
}
masqRule := []string{
"-d", dstAddr,
"-s", dstAddr,
"-p", proto,
"--dport", strconv.Itoa(dstPort),
"-j", "MASQUERADE",
}
if !iptable.Exists(natChain.Table, "POSTROUTING", masqRule...) {
t.Fatal("MASQUERADE rule does not exist")
}
}
func TestLink(t *testing.T) {
iptable, _, filterChain := createNewChain(t)
ip1 := net.ParseIP("192.168.1.1")
ip2 := net.ParseIP("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)
}
}
func TestConcurrencyWithWait(t *testing.T) {
RunConcurrencyTest(t, true)
}
func TestConcurrencyNoWait(t *testing.T) {
RunConcurrencyTest(t, false)
}
// Runs 10 concurrent rule additions. This will fail if iptables
// is actually invoked simultaneously without --wait.
// Note that if iptables does not support the xtable lock on this
// system, then allowXlock has no effect -- it will always be off.
func RunConcurrencyTest(t *testing.T, allowXlock bool) {
_, natChain, _ := createNewChain(t)
if !allowXlock && supportsXlock {
supportsXlock = false
defer func() { supportsXlock = true }()
}
ip := net.ParseIP("192.168.1.1")
port := 1234
dstAddr := "172.17.0.1"
dstPort := 4321
proto := "tcp"
group := new(errgroup.Group)
for i := 0; i < 10; i++ {
group.Go(func() error {
return natChain.Forward(Append, ip, port, proto, dstAddr, dstPort, "lo")
})
}
if err := group.Wait(); err != nil {
t.Fatal(err)
}
}
func TestCleanup(t *testing.T) {
iptable, _, filterChain := createNewChain(t)
var rules []byte
// Cleanup filter/FORWARD first otherwise output of iptables-save is dirty
link := []string{"-t", string(filterChain.Table),
string(Delete), "FORWARD",
"-o", bridgeName,
"-j", filterChain.Name}
if _, err := iptable.Raw(link...); err != nil {
t.Fatal(err)
}
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) {
testChain1 := "ABCD"
testChain2 := "EFGH"
iptable := GetIptable(IPv4)
_, err := iptable.NewChain(testChain1, Filter, false)
if err != nil {
t.Fatal(err)
}
defer func() {
iptable.RemoveExistingChain(testChain1, Filter)
}()
_, err = iptable.NewChain(testChain2, Filter, false)
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)
}
}
}