github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/libnetwork/iptables/iptables_test.go (about)

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