Move per-container forward rules to DOCKER chain
| ... | ... |
@@ -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 |
+} |