github.com/moby/docker@v26.1.3+incompatible/libnetwork/iptables/iptables_test.go (about)

     1  //go:build linux
     2  
     3  package iptables
     4  
     5  import (
     6  	"net"
     7  	"os/exec"
     8  	"strconv"
     9  	"strings"
    10  	"testing"
    11  
    12  	"golang.org/x/sync/errgroup"
    13  )
    14  
    15  const (
    16  	chainName  = "DOCKEREST"
    17  	bridgeName = "lo"
    18  )
    19  
    20  func createNewChain(t *testing.T) (*IPTable, *ChainInfo, *ChainInfo) {
    21  	t.Helper()
    22  	iptable := GetIptable(IPv4)
    23  
    24  	natChain, err := iptable.NewChain(chainName, Nat, false)
    25  	if err != nil {
    26  		t.Fatal(err)
    27  	}
    28  	err = iptable.ProgramChain(natChain, bridgeName, false, true)
    29  	if err != nil {
    30  		t.Fatal(err)
    31  	}
    32  
    33  	filterChain, err := iptable.NewChain(chainName, Filter, false)
    34  	if err != nil {
    35  		t.Fatal(err)
    36  	}
    37  	err = iptable.ProgramChain(filterChain, bridgeName, false, true)
    38  	if err != nil {
    39  		t.Fatal(err)
    40  	}
    41  
    42  	return iptable, natChain, filterChain
    43  }
    44  
    45  func TestNewChain(t *testing.T) {
    46  	createNewChain(t)
    47  }
    48  
    49  func TestForward(t *testing.T) {
    50  	iptable, natChain, filterChain := createNewChain(t)
    51  
    52  	ip := net.ParseIP("192.168.1.1")
    53  	port := 1234
    54  	dstAddr := "172.17.0.1"
    55  	dstPort := 4321
    56  	proto := "tcp"
    57  
    58  	err := natChain.Forward(Insert, ip, port, proto, dstAddr, dstPort, bridgeName)
    59  	if err != nil {
    60  		t.Fatal(err)
    61  	}
    62  
    63  	dnatRule := []string{
    64  		"-d", ip.String(),
    65  		"-p", proto,
    66  		"--dport", strconv.Itoa(port),
    67  		"-j", "DNAT",
    68  		"--to-destination", dstAddr + ":" + strconv.Itoa(dstPort),
    69  		"!", "-i", bridgeName,
    70  	}
    71  
    72  	if !iptable.Exists(natChain.Table, natChain.Name, dnatRule...) {
    73  		t.Fatal("DNAT rule does not exist")
    74  	}
    75  
    76  	filterRule := []string{
    77  		"!", "-i", bridgeName,
    78  		"-o", bridgeName,
    79  		"-d", dstAddr,
    80  		"-p", proto,
    81  		"--dport", strconv.Itoa(dstPort),
    82  		"-j", "ACCEPT",
    83  	}
    84  
    85  	if !iptable.Exists(filterChain.Table, filterChain.Name, filterRule...) {
    86  		t.Fatal("filter rule does not exist")
    87  	}
    88  
    89  	masqRule := []string{
    90  		"-d", dstAddr,
    91  		"-s", dstAddr,
    92  		"-p", proto,
    93  		"--dport", strconv.Itoa(dstPort),
    94  		"-j", "MASQUERADE",
    95  	}
    96  
    97  	if !iptable.Exists(natChain.Table, "POSTROUTING", masqRule...) {
    98  		t.Fatal("MASQUERADE rule does not exist")
    99  	}
   100  }
   101  
   102  func TestLink(t *testing.T) {
   103  	iptable, _, filterChain := createNewChain(t)
   104  	ip1 := net.ParseIP("192.168.1.1")
   105  	ip2 := net.ParseIP("192.168.1.2")
   106  	port := 1234
   107  	proto := "tcp"
   108  
   109  	err := filterChain.Link(Append, ip1, ip2, port, proto, bridgeName)
   110  	if err != nil {
   111  		t.Fatal(err)
   112  	}
   113  
   114  	rule1 := []string{
   115  		"-i", bridgeName,
   116  		"-o", bridgeName,
   117  		"-p", proto,
   118  		"-s", ip1.String(),
   119  		"-d", ip2.String(),
   120  		"--dport", strconv.Itoa(port),
   121  		"-j", "ACCEPT",
   122  	}
   123  
   124  	if !iptable.Exists(filterChain.Table, filterChain.Name, rule1...) {
   125  		t.Fatal("rule1 does not exist")
   126  	}
   127  
   128  	rule2 := []string{
   129  		"-i", bridgeName,
   130  		"-o", bridgeName,
   131  		"-p", proto,
   132  		"-s", ip2.String(),
   133  		"-d", ip1.String(),
   134  		"--sport", strconv.Itoa(port),
   135  		"-j", "ACCEPT",
   136  	}
   137  
   138  	if !iptable.Exists(filterChain.Table, filterChain.Name, rule2...) {
   139  		t.Fatal("rule2 does not exist")
   140  	}
   141  }
   142  
   143  func TestPrerouting(t *testing.T) {
   144  	iptable, natChain, _ := createNewChain(t)
   145  
   146  	args := []string{"-i", "lo", "-d", "192.168.1.1"}
   147  	err := natChain.Prerouting(Insert, args...)
   148  	if err != nil {
   149  		t.Fatal(err)
   150  	}
   151  
   152  	if !iptable.Exists(natChain.Table, "PREROUTING", args...) {
   153  		t.Fatal("rule does not exist")
   154  	}
   155  
   156  	delRule := append([]string{"-D", "PREROUTING", "-t", string(Nat)}, args...)
   157  	if _, err = iptable.Raw(delRule...); err != nil {
   158  		t.Fatal(err)
   159  	}
   160  }
   161  
   162  func TestOutput(t *testing.T) {
   163  	iptable, natChain, _ := createNewChain(t)
   164  
   165  	args := []string{"-o", "lo", "-d", "192.168.1.1"}
   166  	err := natChain.Output(Insert, args...)
   167  	if err != nil {
   168  		t.Fatal(err)
   169  	}
   170  
   171  	if !iptable.Exists(natChain.Table, "OUTPUT", args...) {
   172  		t.Fatal("rule does not exist")
   173  	}
   174  
   175  	delRule := append([]string{
   176  		"-D", "OUTPUT", "-t",
   177  		string(natChain.Table),
   178  	}, args...)
   179  	if _, err = iptable.Raw(delRule...); err != nil {
   180  		t.Fatal(err)
   181  	}
   182  }
   183  
   184  func TestConcurrencyWithWait(t *testing.T) {
   185  	RunConcurrencyTest(t, true)
   186  }
   187  
   188  func TestConcurrencyNoWait(t *testing.T) {
   189  	RunConcurrencyTest(t, false)
   190  }
   191  
   192  // Runs 10 concurrent rule additions. This will fail if iptables
   193  // is actually invoked simultaneously without --wait.
   194  // Note that if iptables does not support the xtable lock on this
   195  // system, then allowXlock has no effect -- it will always be off.
   196  func RunConcurrencyTest(t *testing.T, allowXlock bool) {
   197  	_, natChain, _ := createNewChain(t)
   198  
   199  	if !allowXlock && supportsXlock {
   200  		supportsXlock = false
   201  		defer func() { supportsXlock = true }()
   202  	}
   203  
   204  	ip := net.ParseIP("192.168.1.1")
   205  	port := 1234
   206  	dstAddr := "172.17.0.1"
   207  	dstPort := 4321
   208  	proto := "tcp"
   209  
   210  	group := new(errgroup.Group)
   211  	for i := 0; i < 10; i++ {
   212  		group.Go(func() error {
   213  			return natChain.Forward(Append, ip, port, proto, dstAddr, dstPort, "lo")
   214  		})
   215  	}
   216  	if err := group.Wait(); err != nil {
   217  		t.Fatal(err)
   218  	}
   219  }
   220  
   221  func TestCleanup(t *testing.T) {
   222  	iptable, _, filterChain := createNewChain(t)
   223  
   224  	var rules []byte
   225  
   226  	// Cleanup filter/FORWARD first otherwise output of iptables-save is dirty
   227  	link := []string{
   228  		"-t", string(filterChain.Table),
   229  		string(Delete), "FORWARD",
   230  		"-o", bridgeName,
   231  		"-j", filterChain.Name,
   232  	}
   233  
   234  	if _, err := iptable.Raw(link...); err != nil {
   235  		t.Fatal(err)
   236  	}
   237  	filterChain.Remove()
   238  
   239  	err := iptable.RemoveExistingChain(chainName, Nat)
   240  	if err != nil {
   241  		t.Fatal(err)
   242  	}
   243  
   244  	rules, err = exec.Command("iptables-save").Output()
   245  	if err != nil {
   246  		t.Fatal(err)
   247  	}
   248  	if strings.Contains(string(rules), chainName) {
   249  		t.Fatalf("Removing chain failed. %s found in iptables-save", chainName)
   250  	}
   251  }
   252  
   253  func TestExistsRaw(t *testing.T) {
   254  	const testChain1 = "ABCD"
   255  	const testChain2 = "EFGH"
   256  
   257  	iptable := GetIptable(IPv4)
   258  
   259  	_, err := iptable.NewChain(testChain1, Filter, false)
   260  	if err != nil {
   261  		t.Fatal(err)
   262  	}
   263  	defer func() {
   264  		iptable.RemoveExistingChain(testChain1, Filter)
   265  	}()
   266  
   267  	_, err = iptable.NewChain(testChain2, Filter, false)
   268  	if err != nil {
   269  		t.Fatal(err)
   270  	}
   271  	defer func() {
   272  		iptable.RemoveExistingChain(testChain2, Filter)
   273  	}()
   274  
   275  	// Test detection over full and truncated rule string
   276  	input := []struct{ rule []string }{
   277  		{[]string{"-s", "172.8.9.9/32", "-j", "ACCEPT"}},
   278  		{[]string{"-d", "172.8.9.0/24", "-j", "DROP"}},
   279  		{[]string{"-s", "172.0.3.0/24", "-d", "172.17.0.0/24", "-p", "tcp", "-m", "tcp", "--dport", "80", "-j", testChain2}},
   280  		{[]string{"-j", "RETURN"}},
   281  	}
   282  
   283  	for i, r := range input {
   284  		ruleAdd := append([]string{"-t", string(Filter), "-A", testChain1}, r.rule...)
   285  		err = iptable.RawCombinedOutput(ruleAdd...)
   286  		if err != nil {
   287  			t.Fatalf("i=%d, err: %v", i, err)
   288  		}
   289  		if !iptable.exists(true, Filter, testChain1, r.rule...) {
   290  			t.Fatalf("Failed to detect rule. i=%d", i)
   291  		}
   292  		// Truncate the rule
   293  		trg := r.rule[len(r.rule)-1]
   294  		trg = trg[:len(trg)-2]
   295  		r.rule[len(r.rule)-1] = trg
   296  		if iptable.exists(true, Filter, testChain1, r.rule...) {
   297  			t.Fatalf("Invalid detection. i=%d", i)
   298  		}
   299  	}
   300  }