Browse code

Merge pull request #7003 from porjo/6034-fowardChain

Move per-container forward rules to DOCKER chain

Alexander Morozov authored on 2014/12/23 07:40:42
Showing 9 changed files
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"io/ioutil"
6 6
 	"net"
7 7
 	"os"
8
-	"strconv"
9 8
 	"sync"
10 9
 
11 10
 	log "github.com/Sirupsen/logrus"
... ...
@@ -144,12 +143,16 @@ func InitDriver(job *engine.Job) engine.Status {
144 144
 	}
145 145
 
146 146
 	// We can always try removing the iptables
147
-	if err := iptables.RemoveExistingChain("DOCKER"); err != nil {
147
+	if err := iptables.RemoveExistingChain("DOCKER", iptables.Nat); err != nil {
148 148
 		return job.Error(err)
149 149
 	}
150 150
 
151 151
 	if enableIPTables {
152
-		chain, err := iptables.NewChain("DOCKER", bridgeIface)
152
+		_, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Nat)
153
+		if err != nil {
154
+			return job.Error(err)
155
+		}
156
+		chain, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter)
153 157
 		if err != nil {
154 158
 			return job.Error(err)
155 159
 		}
... ...
@@ -501,35 +504,38 @@ func AllocatePort(job *engine.Job) engine.Status {
501 501
 func LinkContainers(job *engine.Job) engine.Status {
502 502
 	var (
503 503
 		action       = job.Args[0]
504
+		nfAction     iptables.Action
504 505
 		childIP      = job.Getenv("ChildIP")
505 506
 		parentIP     = job.Getenv("ParentIP")
506 507
 		ignoreErrors = job.GetenvBool("IgnoreErrors")
507 508
 		ports        = job.GetenvList("Ports")
508 509
 	)
509
-	for _, value := range ports {
510
-		port := nat.Port(value)
511
-		if output, err := iptables.Raw(action, "FORWARD",
512
-			"-i", bridgeIface, "-o", bridgeIface,
513
-			"-p", port.Proto(),
514
-			"-s", parentIP,
515
-			"--dport", strconv.Itoa(port.Int()),
516
-			"-d", childIP,
517
-			"-j", "ACCEPT"); !ignoreErrors && err != nil {
518
-			return job.Error(err)
519
-		} else if len(output) != 0 {
520
-			return job.Errorf("Error toggle iptables forward: %s", output)
521
-		}
522 510
 
523
-		if output, err := iptables.Raw(action, "FORWARD",
524
-			"-i", bridgeIface, "-o", bridgeIface,
525
-			"-p", port.Proto(),
526
-			"-s", childIP,
527
-			"--sport", strconv.Itoa(port.Int()),
528
-			"-d", parentIP,
529
-			"-j", "ACCEPT"); !ignoreErrors && err != nil {
511
+	switch action {
512
+	case "-A":
513
+		nfAction = iptables.Append
514
+	case "-I":
515
+		nfAction = iptables.Insert
516
+	case "-D":
517
+		nfAction = iptables.Delete
518
+	default:
519
+		return job.Errorf("Invalid action '%s' specified", action)
520
+	}
521
+
522
+	ip1 := net.ParseIP(parentIP)
523
+	if ip1 == nil {
524
+		return job.Errorf("parent IP '%s' is invalid", parentIP)
525
+	}
526
+	ip2 := net.ParseIP(childIP)
527
+	if ip2 == nil {
528
+		return job.Errorf("child IP '%s' is invalid", childIP)
529
+	}
530
+
531
+	chain := iptables.Chain{Name: "DOCKER", Bridge: bridgeIface}
532
+	for _, p := range ports {
533
+		port := nat.Port(p)
534
+		if err := chain.Link(nfAction, ip1, ip2, port.Int(), port.Proto()); !ignoreErrors && err != nil {
530 535
 			return job.Error(err)
531
-		} else if len(output) != 0 {
532
-			return job.Errorf("Error toggle iptables forward: %s", output)
533 536
 		}
534 537
 	}
535 538
 	return engine.StatusOK
... ...
@@ -7,6 +7,7 @@ import (
7 7
 
8 8
 	"github.com/docker/docker/daemon/networkdriver/portmapper"
9 9
 	"github.com/docker/docker/engine"
10
+	"github.com/docker/docker/pkg/iptables"
10 11
 )
11 12
 
12 13
 func init() {
... ...
@@ -118,3 +119,43 @@ func TestMacAddrGeneration(t *testing.T) {
118 118
 		t.Fatal("Non-unique MAC address")
119 119
 	}
120 120
 }
121
+
122
+func TestLinkContainers(t *testing.T) {
123
+	eng := engine.New()
124
+	eng.Logging = false
125
+
126
+	// Init driver
127
+	job := eng.Job("initdriver")
128
+	if res := InitDriver(job); res != engine.StatusOK {
129
+		t.Fatal("Failed to initialize network driver")
130
+	}
131
+
132
+	// Allocate interface
133
+	job = eng.Job("allocate_interface", "container_id")
134
+	if res := Allocate(job); res != engine.StatusOK {
135
+		t.Fatal("Failed to allocate network interface")
136
+	}
137
+
138
+	job.Args[0] = "-I"
139
+
140
+	job.Setenv("ChildIP", "172.17.0.2")
141
+	job.Setenv("ParentIP", "172.17.0.1")
142
+	job.SetenvBool("IgnoreErrors", false)
143
+	job.SetenvList("Ports", []string{"1234"})
144
+
145
+	bridgeIface = "lo"
146
+	_, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter)
147
+	if err != nil {
148
+		t.Fatal(err)
149
+	}
150
+
151
+	if res := LinkContainers(job); res != engine.StatusOK {
152
+		t.Fatalf("LinkContainers failed")
153
+	}
154
+
155
+	// flush rules
156
+	if _, err = iptables.Raw([]string{"-F", "DOCKER"}...); err != nil {
157
+		t.Fatal(err)
158
+	}
159
+
160
+}
... ...
@@ -93,7 +93,7 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err er
93 93
 	}
94 94
 
95 95
 	containerIP, containerPort := getIPAndPort(m.container)
96
-	if err := forward(iptables.Add, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
96
+	if err := forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
97 97
 		return nil, err
98 98
 	}
99 99
 
... ...
@@ -184,30 +184,46 @@ running.  The options then modify this default configuration.
184 184
 
185 185
 <a name="the-world"></a>
186 186
 
187
-Whether a container can talk to the world is governed by one main factor.
187
+Whether a container can talk to the world is governed by two factors.
188 188
 
189
-Is the host machine willing to forward IP packets?  This is governed
190
-by the `ip_forward` system parameter.  Packets can only pass between
191
-containers if this parameter is `1`.  Usually you will simply leave
192
-the Docker server at its default setting `--ip-forward=true` and
193
-Docker will go set `ip_forward` to `1` for you when the server
194
-starts up.  To check the setting or turn it on manually:
195
-
196
-    # Usually not necessary: turning on forwarding,
197
-    # on the host where your Docker server is running
189
+1.  Is the host machine willing to forward IP packets?  This is governed
190
+    by the `ip_forward` system parameter.  Packets can only pass between
191
+    containers if this parameter is `1`.  Usually you will simply leave
192
+    the Docker server at its default setting `--ip-forward=true` and
193
+    Docker will go set `ip_forward` to `1` for you when the server
194
+    starts up.  To check the setting or turn it on manually:
198 195
 
196
+    ```
199 197
     $ cat /proc/sys/net/ipv4/ip_forward
200 198
     0
201
-    $ sudo echo 1 > /proc/sys/net/ipv4/ip_forward
199
+    $ echo 1 > /proc/sys/net/ipv4/ip_forward
202 200
     $ cat /proc/sys/net/ipv4/ip_forward
203 201
     1
202
+    ```
203
+
204
+    Many using Docker will want `ip_forward` to be on, to at
205
+    least make communication *possible* between containers and
206
+    the wider world.
207
+
208
+    May also be needed for inter-container communication if you are
209
+    in a multiple bridge setup.
210
+
211
+2.  Do your `iptables` allow this particular connection? Docker will
212
+    never make changes to your system `iptables` rules if you set
213
+    `--iptables=false` when the daemon starts.  Otherwise the Docker
214
+    server will append forwarding rules to the `DOCKER` filter chain.
215
+
216
+Docker will not delete or modify any pre-existing rules from the `DOCKER`
217
+filter chain. This allows the user to create in advance any rules required
218
+to further restrict access to the containers.
204 219
 
205
-Many using Docker will want `ip_forward` to be on, to at
206
-least make communication *possible* between containers and
207
-the wider world.
220
+Docker's forward rules permit all external source IPs by default. To allow
221
+only a specific IP or network to access the containers, insert a negated
222
+rule at the top of the `DOCKER` filter chain. For example, to restrict
223
+external access such that *only* source IP 8.8.8.8 can access the
224
+containers, the following rule could be added:
208 225
 
209
-May also be needed for inter-container communication if you are
210
-in a multiple bridge setup.
226
+    $ iptables -I DOCKER -i ext_if ! -s 8.8.8.8 -j DROP
211 227
 
212 228
 ## Communication between containers
213 229
 
... ...
@@ -222,12 +238,12 @@ system level, by two factors.
222 222
     between them.  See the later sections of this document for other
223 223
     possible topologies.
224 224
 
225
-2.  Do your `iptables` allow this particular connection to be made?
226
-    Docker will never make changes to your system `iptables` rules if
227
-    you set `--iptables=false` when the daemon starts.  Otherwise the
228
-    Docker server will add a default rule to the `FORWARD` chain with a
229
-    blanket `ACCEPT` policy if you retain the default `--icc=true`, or
230
-    else will set the policy to `DROP` if `--icc=false`.
225
+2.  Do your `iptables` allow this particular connection? Docker will never
226
+    make changes to your system `iptables` rules if you set
227
+    `--iptables=false` when the daemon starts.  Otherwise the Docker server
228
+    will add a default rule to the `FORWARD` chain with a blanket `ACCEPT`
229
+    policy if you retain the default `--icc=true`, or else will set the
230
+    policy to `DROP` if `--icc=false`.
231 231
 
232 232
 It is a strategic question whether to leave `--icc=true` or change it to
233 233
 `--icc=false` (on Ubuntu, by editing the `DOCKER_OPTS` variable in
... ...
@@ -267,6 +283,7 @@ the `FORWARD` chain has a default policy of `ACCEPT` or `DROP`:
267 267
     ...
268 268
     Chain FORWARD (policy ACCEPT)
269 269
     target     prot opt source               destination
270
+    DOCKER     all  --  0.0.0.0/0            0.0.0.0/0
270 271
     DROP       all  --  0.0.0.0/0            0.0.0.0/0
271 272
     ...
272 273
 
... ...
@@ -278,9 +295,13 @@ the `FORWARD` chain has a default policy of `ACCEPT` or `DROP`:
278 278
     ...
279 279
     Chain FORWARD (policy ACCEPT)
280 280
     target     prot opt source               destination
281
+    DOCKER     all  --  0.0.0.0/0            0.0.0.0/0
282
+    DROP       all  --  0.0.0.0/0            0.0.0.0/0
283
+
284
+    Chain DOCKER (1 references)
285
+    target     prot opt source               destination
281 286
     ACCEPT     tcp  --  172.17.0.2           172.17.0.3           tcp spt:80
282 287
     ACCEPT     tcp  --  172.17.0.3           172.17.0.2           tcp dpt:80
283
-    DROP       all  --  0.0.0.0/0            0.0.0.0/0
284 288
 
285 289
 > **Note**:
286 290
 > Docker is careful that its host-wide `iptables` rules fully expose
... ...
@@ -83,8 +83,8 @@ func TestLinksIpTablesRulesWhenLinkAndUnlink(t *testing.T) {
83 83
 	childIP := findContainerIP(t, "child")
84 84
 	parentIP := findContainerIP(t, "parent")
85 85
 
86
-	sourceRule := []string{"FORWARD", "-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", childIP, "--sport", "80", "-d", parentIP, "-j", "ACCEPT"}
87
-	destinationRule := []string{"FORWARD", "-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", parentIP, "--dport", "80", "-d", childIP, "-j", "ACCEPT"}
86
+	sourceRule := []string{"DOCKER", "-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", childIP, "--sport", "80", "-d", parentIP, "-j", "ACCEPT"}
87
+	destinationRule := []string{"DOCKER", "-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", parentIP, "--dport", "80", "-d", childIP, "-j", "ACCEPT"}
88 88
 	if !iptables.Exists(sourceRule...) || !iptables.Exists(destinationRule...) {
89 89
 		t.Fatal("Iptables rules not found")
90 90
 	}
... ...
@@ -2029,7 +2029,7 @@ func TestRunDeallocatePortOnMissingIptablesRule(t *testing.T) {
2029 2029
 	if err != nil {
2030 2030
 		t.Fatal(err)
2031 2031
 	}
2032
-	iptCmd := exec.Command("iptables", "-D", "FORWARD", "-d", fmt.Sprintf("%s/32", ip),
2032
+	iptCmd := exec.Command("iptables", "-D", "DOCKER", "-d", fmt.Sprintf("%s/32", ip),
2033 2033
 		"!", "-i", "docker0", "-o", "docker0", "-p", "tcp", "-m", "tcp", "--dport", "23", "-j", "ACCEPT")
2034 2034
 	out, _, err = runCommandWithOutput(iptCmd)
2035 2035
 	if err != nil {
... ...
@@ -138,7 +138,8 @@ func (l *Link) getDefaultPort() *nat.Port {
138 138
 }
139 139
 
140 140
 func (l *Link) Enable() error {
141
-	if err := l.toggle("-I", false); err != nil {
141
+	// -A == iptables append flag
142
+	if err := l.toggle("-A", false); err != nil {
142 143
 		return err
143 144
 	}
144 145
 	l.IsEnabled = true
... ...
@@ -148,6 +149,7 @@ func (l *Link) Enable() error {
148 148
 func (l *Link) Disable() {
149 149
 	// We do not care about errors here because the link may not
150 150
 	// exist in iptables
151
+	// -D == iptables delete flag
151 152
 	l.toggle("-D", true)
152 153
 
153 154
 	l.IsEnabled = false
... ...
@@ -13,14 +13,17 @@ import (
13 13
 )
14 14
 
15 15
 type Action string
16
+type Table string
16 17
 
17 18
 const (
18
-	Add    Action = "-A"
19
+	Append Action = "-A"
19 20
 	Delete Action = "-D"
21
+	Insert Action = "-I"
22
+	Nat    Table  = "nat"
23
+	Filter Table  = "filter"
20 24
 )
21 25
 
22 26
 var (
23
-	nat                 = []string{"-t", "nat"}
24 27
 	supportsXlock       = false
25 28
 	ErrIptablesNotFound = errors.New("Iptables not found")
26 29
 )
... ...
@@ -28,6 +31,7 @@ var (
28 28
 type Chain struct {
29 29
 	Name   string
30 30
 	Bridge string
31
+	Table  Table
31 32
 }
32 33
 
33 34
 type ChainError struct {
... ...
@@ -43,34 +47,74 @@ func init() {
43 43
 	supportsXlock = exec.Command("iptables", "--wait", "-L", "-n").Run() == nil
44 44
 }
45 45
 
46
-func NewChain(name, bridge string) (*Chain, error) {
47
-	if output, err := Raw("-t", "nat", "-N", name); err != nil {
48
-		return nil, err
49
-	} else if len(output) != 0 {
50
-		return nil, fmt.Errorf("Error creating new iptables chain: %s", output)
51
-	}
52
-	chain := &Chain{
46
+func NewChain(name, bridge string, table Table) (*Chain, error) {
47
+	c := &Chain{
53 48
 		Name:   name,
54 49
 		Bridge: bridge,
50
+		Table:  table,
51
+	}
52
+
53
+	if string(c.Table) == "" {
54
+		c.Table = Filter
55 55
 	}
56 56
 
57
-	if err := chain.Prerouting(Add, "-m", "addrtype", "--dst-type", "LOCAL"); err != nil {
58
-		return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
57
+	// Add chain if it doesn't exist
58
+	if _, err := Raw("-t", string(c.Table), "-n", "-L", c.Name); err != nil {
59
+		if output, err := Raw("-t", string(c.Table), "-N", c.Name); err != nil {
60
+			return nil, err
61
+		} else if len(output) != 0 {
62
+			return nil, fmt.Errorf("Could not create %s/%s chain: %s", c.Table, c.Name, output)
63
+		}
59 64
 	}
60
-	if err := chain.Output(Add, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8"); err != nil {
61
-		return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
65
+
66
+	switch table {
67
+	case Nat:
68
+		preroute := []string{
69
+			"-m", "addrtype",
70
+			"--dst-type", "LOCAL"}
71
+		if !Exists(preroute...) {
72
+			if err := c.Prerouting(Append, preroute...); err != nil {
73
+				return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
74
+			}
75
+		}
76
+		output := []string{
77
+			"-m", "addrtype",
78
+			"--dst-type", "LOCAL",
79
+			"!", "--dst", "127.0.0.0/8"}
80
+		if !Exists(output...) {
81
+			if err := c.Output(Append, output...); err != nil {
82
+				return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
83
+			}
84
+		}
85
+	case Filter:
86
+		link := []string{"FORWARD",
87
+			"-o", c.Bridge,
88
+			"-j", c.Name}
89
+		if !Exists(link...) {
90
+			insert := append([]string{string(Insert)}, link...)
91
+			if output, err := Raw(insert...); err != nil {
92
+				return nil, err
93
+			} else if len(output) != 0 {
94
+				return nil, fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output)
95
+			}
96
+		}
62 97
 	}
63
-	return chain, nil
98
+	return c, nil
64 99
 }
65 100
 
66
-func RemoveExistingChain(name string) error {
67
-	chain := &Chain{
68
-		Name: name,
101
+func RemoveExistingChain(name string, table Table) error {
102
+	c := &Chain{
103
+		Name:  name,
104
+		Table: table,
105
+	}
106
+	if string(c.Table) == "" {
107
+		c.Table = Filter
69 108
 	}
70
-	return chain.Remove()
109
+	return c.Remove()
71 110
 }
72 111
 
73
-func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr string, dest_port int) error {
112
+// Add forwarding rule to 'filter' table and corresponding nat rule to 'nat' table
113
+func (c *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr string, destPort int) error {
74 114
 	daddr := ip.String()
75 115
 	if ip.IsUnspecified() {
76 116
 		// iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we
... ...
@@ -78,39 +122,75 @@ func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr str
78 78
 		// value" by both iptables and ip6tables.
79 79
 		daddr = "0/0"
80 80
 	}
81
-	if output, err := Raw("-t", "nat", fmt.Sprint(action), c.Name,
81
+	if output, err := Raw("-t", string(Nat), string(action), c.Name,
82 82
 		"-p", proto,
83 83
 		"-d", daddr,
84 84
 		"--dport", strconv.Itoa(port),
85 85
 		"!", "-i", c.Bridge,
86 86
 		"-j", "DNAT",
87
-		"--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))); err != nil {
87
+		"--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))); err != nil {
88 88
 		return err
89 89
 	} else if len(output) != 0 {
90 90
 		return &ChainError{Chain: "FORWARD", Output: output}
91 91
 	}
92 92
 
93
-	fAction := action
94
-	if fAction == Add {
95
-		fAction = "-I"
96
-	}
97
-	if output, err := Raw(string(fAction), "FORWARD",
93
+	if output, err := Raw("-t", string(Filter), string(action), c.Name,
98 94
 		"!", "-i", c.Bridge,
99 95
 		"-o", c.Bridge,
100 96
 		"-p", proto,
101
-		"-d", dest_addr,
102
-		"--dport", strconv.Itoa(dest_port),
97
+		"-d", destAddr,
98
+		"--dport", strconv.Itoa(destPort),
103 99
 		"-j", "ACCEPT"); err != nil {
104 100
 		return err
105 101
 	} else if len(output) != 0 {
106 102
 		return &ChainError{Chain: "FORWARD", Output: output}
107 103
 	}
108 104
 
105
+	if output, err := Raw("-t", string(Nat), string(action), "POSTROUTING",
106
+		"-p", proto,
107
+		"-s", destAddr,
108
+		"-d", destAddr,
109
+		"--dport", strconv.Itoa(destPort),
110
+		"-j", "MASQUERADE"); err != nil {
111
+		return err
112
+	} else if len(output) != 0 {
113
+		return &ChainError{Chain: "FORWARD", Output: output}
114
+	}
115
+
116
+	return nil
117
+}
118
+
119
+// Add reciprocal ACCEPT rule for two supplied IP addresses.
120
+// Traffic is allowed from ip1 to ip2 and vice-versa
121
+func (c *Chain) Link(action Action, ip1, ip2 net.IP, port int, proto string) error {
122
+	if output, err := Raw("-t", string(Filter), string(action), c.Name,
123
+		"-i", c.Bridge, "-o", c.Bridge,
124
+		"-p", proto,
125
+		"-s", ip1.String(),
126
+		"-d", ip2.String(),
127
+		"--dport", strconv.Itoa(port),
128
+		"-j", "ACCEPT"); err != nil {
129
+		return err
130
+	} else if len(output) != 0 {
131
+		return fmt.Errorf("Error iptables forward: %s", output)
132
+	}
133
+	if output, err := Raw("-t", string(Filter), string(action), c.Name,
134
+		"-i", c.Bridge, "-o", c.Bridge,
135
+		"-p", proto,
136
+		"-s", ip2.String(),
137
+		"-d", ip1.String(),
138
+		"--sport", strconv.Itoa(port),
139
+		"-j", "ACCEPT"); err != nil {
140
+		return err
141
+	} else if len(output) != 0 {
142
+		return fmt.Errorf("Error iptables forward: %s", output)
143
+	}
109 144
 	return nil
110 145
 }
111 146
 
147
+// Add linking rule to nat/PREROUTING chain.
112 148
 func (c *Chain) Prerouting(action Action, args ...string) error {
113
-	a := append(nat, fmt.Sprint(action), "PREROUTING")
149
+	a := []string{"-t", string(Nat), string(action), "PREROUTING"}
114 150
 	if len(args) > 0 {
115 151
 		a = append(a, args...)
116 152
 	}
... ...
@@ -122,8 +202,9 @@ func (c *Chain) Prerouting(action Action, args ...string) error {
122 122
 	return nil
123 123
 }
124 124
 
125
+// Add linking rule to an OUTPUT chain
125 126
 func (c *Chain) Output(action Action, args ...string) error {
126
-	a := append(nat, fmt.Sprint(action), "OUTPUT")
127
+	a := []string{"-t", string(c.Table), string(action), "OUTPUT"}
127 128
 	if len(args) > 0 {
128 129
 		a = append(a, args...)
129 130
 	}
... ...
@@ -137,20 +218,20 @@ func (c *Chain) Output(action Action, args ...string) error {
137 137
 
138 138
 func (c *Chain) Remove() error {
139 139
 	// Ignore errors - This could mean the chains were never set up
140
-	c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL")
141
-	c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8")
142
-	c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL") // Created in versions <= 0.1.6
143
-
144
-	c.Prerouting(Delete)
145
-	c.Output(Delete)
146
-
147
-	Raw("-t", "nat", "-F", c.Name)
148
-	Raw("-t", "nat", "-X", c.Name)
140
+	if c.Table == Nat {
141
+		c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL")
142
+		c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8")
143
+		c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL") // Created in versions <= 0.1.6
149 144
 
145
+		c.Prerouting(Delete)
146
+		c.Output(Delete)
147
+	}
148
+	Raw("-t", string(c.Table), "-F", c.Name)
149
+	Raw("-t", string(c.Table), "-X", c.Name)
150 150
 	return nil
151 151
 }
152 152
 
153
-// Check if an existing rule exists
153
+// Check if a rule exists
154 154
 func Exists(args ...string) bool {
155 155
 	// iptables -C, --check option was added in v.1.4.11
156 156
 	// http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt
... ...
@@ -175,6 +256,7 @@ func Exists(args ...string) bool {
175 175
 	)
176 176
 }
177 177
 
178
+// Call 'iptables' system command, passing supplied arguments
178 179
 func Raw(args ...string) ([]byte, error) {
179 180
 	path, err := exec.LookPath("iptables")
180 181
 	if err != nil {
181 182
new file mode 100644
... ...
@@ -0,0 +1,204 @@
0
+package iptables
1
+
2
+import (
3
+	"net"
4
+	"os/exec"
5
+	"strconv"
6
+	"strings"
7
+	"testing"
8
+)
9
+
10
+const chainName = "DOCKERTEST"
11
+
12
+var natChain *Chain
13
+var filterChain *Chain
14
+
15
+func TestNewChain(t *testing.T) {
16
+	var err error
17
+
18
+	natChain, err = NewChain(chainName, "lo", Nat)
19
+	if err != nil {
20
+		t.Fatal(err)
21
+	}
22
+
23
+	filterChain, err = NewChain(chainName, "lo", Filter)
24
+	if err != nil {
25
+		t.Fatal(err)
26
+	}
27
+}
28
+
29
+func TestForward(t *testing.T) {
30
+	ip := net.ParseIP("192.168.1.1")
31
+	port := 1234
32
+	dstAddr := "172.17.0.1"
33
+	dstPort := 4321
34
+	proto := "tcp"
35
+
36
+	err := natChain.Forward(Insert, ip, port, proto, dstAddr, dstPort)
37
+	if err != nil {
38
+		t.Fatal(err)
39
+	}
40
+
41
+	dnatRule := []string{natChain.Name,
42
+		"-t", string(natChain.Table),
43
+		"!", "-i", filterChain.Bridge,
44
+		"-d", ip.String(),
45
+		"-p", proto,
46
+		"--dport", strconv.Itoa(port),
47
+		"-j", "DNAT",
48
+		"--to-destination", dstAddr + ":" + strconv.Itoa(dstPort),
49
+	}
50
+
51
+	if !Exists(dnatRule...) {
52
+		t.Fatalf("DNAT rule does not exist")
53
+	}
54
+
55
+	filterRule := []string{filterChain.Name,
56
+		"-t", string(filterChain.Table),
57
+		"!", "-i", filterChain.Bridge,
58
+		"-o", filterChain.Bridge,
59
+		"-d", dstAddr,
60
+		"-p", proto,
61
+		"--dport", strconv.Itoa(dstPort),
62
+		"-j", "ACCEPT",
63
+	}
64
+
65
+	if !Exists(filterRule...) {
66
+		t.Fatalf("filter rule does not exist")
67
+	}
68
+
69
+	masqRule := []string{"POSTROUTING",
70
+		"-t", string(natChain.Table),
71
+		"-d", dstAddr,
72
+		"-s", dstAddr,
73
+		"-p", proto,
74
+		"--dport", strconv.Itoa(dstPort),
75
+		"-j", "MASQUERADE",
76
+	}
77
+
78
+	if !Exists(masqRule...) {
79
+		t.Fatalf("MASQUERADE rule does not exist")
80
+	}
81
+}
82
+
83
+func TestLink(t *testing.T) {
84
+	var err error
85
+
86
+	ip1 := net.ParseIP("192.168.1.1")
87
+	ip2 := net.ParseIP("192.168.1.2")
88
+	port := 1234
89
+	proto := "tcp"
90
+
91
+	err = filterChain.Link(Append, ip1, ip2, port, proto)
92
+	if err != nil {
93
+		t.Fatal(err)
94
+	}
95
+
96
+	rule1 := []string{filterChain.Name,
97
+		"-t", string(filterChain.Table),
98
+		"-i", filterChain.Bridge,
99
+		"-o", filterChain.Bridge,
100
+		"-p", proto,
101
+		"-s", ip1.String(),
102
+		"-d", ip2.String(),
103
+		"--dport", strconv.Itoa(port),
104
+		"-j", "ACCEPT"}
105
+
106
+	if !Exists(rule1...) {
107
+		t.Fatalf("rule1 does not exist")
108
+	}
109
+
110
+	rule2 := []string{filterChain.Name,
111
+		"-t", string(filterChain.Table),
112
+		"-i", filterChain.Bridge,
113
+		"-o", filterChain.Bridge,
114
+		"-p", proto,
115
+		"-s", ip2.String(),
116
+		"-d", ip1.String(),
117
+		"--sport", strconv.Itoa(port),
118
+		"-j", "ACCEPT"}
119
+
120
+	if !Exists(rule2...) {
121
+		t.Fatalf("rule2 does not exist")
122
+	}
123
+}
124
+
125
+func TestPrerouting(t *testing.T) {
126
+	args := []string{
127
+		"-i", "lo",
128
+		"-d", "192.168.1.1"}
129
+
130
+	err := natChain.Prerouting(Insert, args...)
131
+	if err != nil {
132
+		t.Fatal(err)
133
+	}
134
+
135
+	rule := []string{"PREROUTING",
136
+		"-t", string(Nat),
137
+		"-j", natChain.Name}
138
+
139
+	rule = append(rule, args...)
140
+
141
+	if !Exists(rule...) {
142
+		t.Fatalf("rule does not exist")
143
+	}
144
+
145
+	delRule := append([]string{"-D"}, rule...)
146
+	if _, err = Raw(delRule...); err != nil {
147
+		t.Fatal(err)
148
+	}
149
+}
150
+
151
+func TestOutput(t *testing.T) {
152
+	args := []string{
153
+		"-o", "lo",
154
+		"-d", "192.168.1.1"}
155
+
156
+	err := natChain.Output(Insert, args...)
157
+	if err != nil {
158
+		t.Fatal(err)
159
+	}
160
+
161
+	rule := []string{"OUTPUT",
162
+		"-t", string(natChain.Table),
163
+		"-j", natChain.Name}
164
+
165
+	rule = append(rule, args...)
166
+
167
+	if !Exists(rule...) {
168
+		t.Fatalf("rule does not exist")
169
+	}
170
+
171
+	delRule := append([]string{"-D"}, rule...)
172
+	if _, err = Raw(delRule...); err != nil {
173
+		t.Fatal(err)
174
+	}
175
+}
176
+
177
+func TestCleanup(t *testing.T) {
178
+	var err error
179
+	var rules []byte
180
+
181
+	// Cleanup filter/FORWARD first otherwise output of iptables-save is dirty
182
+	link := []string{"-t", string(filterChain.Table),
183
+		string(Delete), "FORWARD",
184
+		"-o", filterChain.Bridge,
185
+		"-j", filterChain.Name}
186
+	if _, err = Raw(link...); err != nil {
187
+		t.Fatal(err)
188
+	}
189
+	filterChain.Remove()
190
+
191
+	err = RemoveExistingChain(chainName, Nat)
192
+	if err != nil {
193
+		t.Fatal(err)
194
+	}
195
+
196
+	rules, err = exec.Command("iptables-save").Output()
197
+	if err != nil {
198
+		t.Fatal(err)
199
+	}
200
+	if strings.Contains(string(rules), chainName) {
201
+		t.Fatalf("Removing chain failed. %s found in iptables-save", chainName)
202
+	}
203
+}