github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/libnetwork/iptables/iptables_test.go (about)

     1  package iptables
     2  
     3  import (
     4  	"net"
     5  	"os/exec"
     6  	"strconv"
     7  	"strings"
     8  	"sync"
     9  	"testing"
    10  
    11  	_ "github.com/docker/libnetwork/testutils"
    12  )
    13  
    14  const chainName = "DOCKEREST"
    15  
    16  var natChain *ChainInfo
    17  var filterChain *ChainInfo
    18  var bridgeName string
    19  
    20  func TestNewChain(t *testing.T) {
    21  	var err error
    22  
    23  	bridgeName = "lo"
    24  	iptable := GetIptable(IPv4)
    25  
    26  	natChain, err = iptable.NewChain(chainName, Nat, false)
    27  	if err != nil {
    28  		t.Fatal(err)
    29  	}
    30  	err = iptable.ProgramChain(natChain, bridgeName, false, true)
    31  	if err != nil {
    32  		t.Fatal(err)
    33  	}
    34  
    35  	filterChain, err = iptable.NewChain(chainName, Filter, false)
    36  	if err != nil {
    37  		t.Fatal(err)
    38  	}
    39  	err = iptable.ProgramChain(filterChain, bridgeName, false, true)
    40  	if err != nil {
    41  		t.Fatal(err)
    42  	}
    43  }
    44  
    45  func TestForward(t *testing.T) {
    46  	ip := net.ParseIP("192.168.1.1")
    47  	port := 1234
    48  	dstAddr := "172.17.0.1"
    49  	dstPort := 4321
    50  	proto := "tcp"
    51  
    52  	bridgeName := "lo"
    53  	iptable := GetIptable(IPv4)
    54  
    55  	err := natChain.Forward(Insert, ip, port, proto, dstAddr, dstPort, bridgeName)
    56  	if err != nil {
    57  		t.Fatal(err)
    58  	}
    59  
    60  	dnatRule := []string{
    61  		"-d", ip.String(),
    62  		"-p", proto,
    63  		"--dport", strconv.Itoa(port),
    64  		"-j", "DNAT",
    65  		"--to-destination", dstAddr + ":" + strconv.Itoa(dstPort),
    66  		"!", "-i", bridgeName,
    67  	}
    68  
    69  	if !iptable.Exists(natChain.Table, natChain.Name, dnatRule...) {
    70  		t.Fatal("DNAT rule does not exist")
    71  	}
    72  
    73  	filterRule := []string{
    74  		"!", "-i", bridgeName,
    75  		"-o", bridgeName,
    76  		"-d", dstAddr,
    77  		"-p", proto,
    78  		"--dport", strconv.Itoa(dstPort),
    79  		"-j", "ACCEPT",
    80  	}
    81  
    82  	if !iptable.Exists(filterChain.Table, filterChain.Name, filterRule...) {
    83  		t.Fatal("filter rule does not exist")
    84  	}
    85  
    86  	masqRule := []string{
    87  		"-d", dstAddr,
    88  		"-s", dstAddr,
    89  		"-p", proto,
    90  		"--dport", strconv.Itoa(dstPort),
    91  		"-j", "MASQUERADE",
    92  	}
    93  
    94  	if !iptable.Exists(natChain.Table, "POSTROUTING", masqRule...) {
    95  		t.Fatal("MASQUERADE rule does not exist")
    96  	}
    97  }
    98  
    99  func TestLink(t *testing.T) {
   100  	var err error
   101  
   102  	bridgeName := "lo"
   103  	iptable := GetIptable(IPv4)
   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  	if !iptable.Exists(filterChain.Table, filterChain.Name, rule1...) {
   124  		t.Fatal("rule1 does not exist")
   125  	}
   126  
   127  	rule2 := []string{
   128  		"-i", bridgeName,
   129  		"-o", bridgeName,
   130  		"-p", proto,
   131  		"-s", ip2.String(),
   132  		"-d", ip1.String(),
   133  		"--sport", strconv.Itoa(port),
   134  		"-j", "ACCEPT"}
   135  
   136  	if !iptable.Exists(filterChain.Table, filterChain.Name, rule2...) {
   137  		t.Fatal("rule2 does not exist")
   138  	}
   139  }
   140  
   141  func TestPrerouting(t *testing.T) {
   142  	args := []string{
   143  		"-i", "lo",
   144  		"-d", "192.168.1.1"}
   145  	iptable := GetIptable(IPv4)
   146  
   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  	args := []string{
   164  		"-o", "lo",
   165  		"-d", "192.168.1.1"}
   166  	iptable := GetIptable(IPv4)
   167  
   168  	err := natChain.Output(Insert, args...)
   169  	if err != nil {
   170  		t.Fatal(err)
   171  	}
   172  
   173  	if !iptable.Exists(natChain.Table, "OUTPUT", args...) {
   174  		t.Fatal("rule does not exist")
   175  	}
   176  
   177  	delRule := append([]string{"-D", "OUTPUT", "-t",
   178  		string(natChain.Table)}, 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  	var wg sync.WaitGroup
   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  	for i := 0; i < 10; i++ {
   211  		wg.Add(1)
   212  		go func() {
   213  			defer wg.Done()
   214  			err := natChain.Forward(Append, ip, port, proto, dstAddr, dstPort, "lo")
   215  			if err != nil {
   216  				t.Fatal(err)
   217  			}
   218  		}()
   219  	}
   220  	wg.Wait()
   221  }
   222  
   223  func TestCleanup(t *testing.T) {
   224  	var err error
   225  	var rules []byte
   226  
   227  	// Cleanup filter/FORWARD first otherwise output of iptables-save is dirty
   228  	link := []string{"-t", string(filterChain.Table),
   229  		string(Delete), "FORWARD",
   230  		"-o", bridgeName,
   231  		"-j", filterChain.Name}
   232  	iptable := GetIptable(IPv4)
   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  	testChain1 := "ABCD"
   255  	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.existsRaw(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.existsRaw(Filter, testChain1, r.rule...) {
   297  			t.Fatalf("Invalid detection. i=%d", i)
   298  		}
   299  	}
   300  }
   301  
   302  func TestGetVersion(t *testing.T) {
   303  	mj, mn, mc := parseVersionNumbers("iptables v1.4.19.1-alpha")
   304  	if mj != 1 || mn != 4 || mc != 19 {
   305  		t.Fatal("Failed to parse version numbers")
   306  	}
   307  }
   308  
   309  func TestSupportsCOption(t *testing.T) {
   310  	input := []struct {
   311  		mj int
   312  		mn int
   313  		mc int
   314  		ok bool
   315  	}{
   316  		{1, 4, 11, true},
   317  		{1, 4, 12, true},
   318  		{1, 5, 0, true},
   319  		{0, 4, 11, false},
   320  		{0, 5, 12, false},
   321  		{1, 3, 12, false},
   322  		{1, 4, 10, false},
   323  	}
   324  	for ind, inp := range input {
   325  		if inp.ok != supportsCOption(inp.mj, inp.mn, inp.mc) {
   326  			t.Fatalf("Incorrect check: %d", ind)
   327  		}
   328  	}
   329  }