diff --git a/iptables/iptables.go b/iptables/iptables.go new file mode 100644 index 000000000..fca25dd20 --- /dev/null +++ b/iptables/iptables.go @@ -0,0 +1,102 @@ +package iptables + +import ( + "errors" + "fmt" + "net" + "os/exec" + "strconv" + "strings" +) + +type Action string + +const ( + Add Action = "-A" + Delete Action = "-D" +) + +var ( + ErrIptablesNotFound = errors.New("Iptables not found") + nat = []string{"-t", "nat"} +) + +type Chain struct { + Name string + Bridge string +} + +func NewChain(name, bridge string) (*Chain, error) { + if err := Raw("-t", "nat", "-N", name); err != nil { + return nil, err + } + chain := &Chain{ + Name: name, + Bridge: bridge, + } + + if err := chain.Prerouting(Add, "-m", "addrtype", "--dst-type", "LOCAL"); err != nil { + return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err) + } + if err := chain.Output(Add, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8"); err != nil { + return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err) + } + return chain, nil +} + +func RemoveExistingChain(name string) error { + chain := &Chain{ + Name: name, + } + return chain.Remove() +} + +func (c *Chain) Forward(action Action, port int, proto, dest_addr string, dest_port int) error { + return Raw("-t", "nat", fmt.Sprint(action), c.Name, + "-p", proto, "--dport", strconv.Itoa(port), + "!", "-i", c.Bridge, + "-j", "DNAT", "--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))) +} + +func (c *Chain) Prerouting(action Action, args ...string) error { + a := append(nat, fmt.Sprint(action), "PREROUTING") + if len(args) > 0 { + a = append(a, args...) + } + return Raw(append(a, "-j", c.Name)...) +} + +func (c *Chain) Output(action Action, args ...string) error { + a := append(nat, fmt.Sprint(action), "OUTPUT") + if len(args) > 0 { + a = append(a, args...) + } + return Raw(append(a, "-j", c.Name)...) +} + +func (c *Chain) Remove() error { + // Ignore errors - This could mean the chains were never set up + c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL") + c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8") + c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL") // Created in versions <= 0.1.6 + + c.Prerouting(Delete) + c.Output(Delete) + + Raw("-t", "nat", "-F", c.Name) + Raw("-t", "nat", "-X", c.Name) + + return nil +} + +func Raw(args ...string) error { + path, err := exec.LookPath("iptables") + if err != nil { + return ErrIptablesNotFound + } + if err := exec.Command(path, args...).Run(); err != nil { + return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " ")) + } + return nil + +} diff --git a/iptables/iptables_test.go b/iptables/iptables_test.go new file mode 100644 index 000000000..aad8acdb8 --- /dev/null +++ b/iptables/iptables_test.go @@ -0,0 +1,18 @@ +package iptables + +import ( + "os" + "testing" +) + +func TestIptables(t *testing.T) { + if err := Raw("-L"); err != nil { + t.Fatal(err) + } + path := os.Getenv("PATH") + os.Setenv("PATH", "") + defer os.Setenv("PATH", path) + if err := Raw("-L"); err == nil { + t.Fatal("Not finding iptables in the PATH should cause an error") + } +} diff --git a/network.go b/network.go index b55291925..e7973673b 100644 --- a/network.go +++ b/network.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "errors" "fmt" + "github.com/dotcloud/docker/iptables" "github.com/dotcloud/docker/utils" "log" "net" @@ -81,18 +82,6 @@ func ip(args ...string) (string, error) { return string(output), nil } -// Wrapper around the iptables command -func iptables(args ...string) error { - path, err := exec.LookPath("iptables") - if err != nil { - return fmt.Errorf("command not found: iptables") - } - if err := exec.Command(path, args...).Run(); err != nil { - return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " ")) - } - return nil -} - func checkRouteOverlaps(routes string, dockerNetwork *net.IPNet) error { utils.Debugf("Routes:\n\n%s", routes) for _, line := range strings.Split(routes, "\n") { @@ -177,7 +166,7 @@ func CreateBridgeIface(ifaceName string) error { if output, err := ip("link", "set", ifaceName, "up"); err != nil { return fmt.Errorf("Unable to start network bridge: %s (%s)", err, output) } - if err := iptables("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr, + if err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr, "!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil { return fmt.Errorf("Unable to enable network bridge NAT: %s", err) } @@ -219,50 +208,18 @@ type PortMapper struct { tcpProxies map[int]Proxy udpMapping map[int]*net.UDPAddr udpProxies map[int]Proxy -} -func (mapper *PortMapper) cleanup() error { - // Ignore errors - This could mean the chains were never set up - iptables("-t", "nat", "-D", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") - iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER") - iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") // Created in versions <= 0.1.6 - // Also cleanup rules created by older versions, or -X might fail. - iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER") - iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER") - iptables("-t", "nat", "-F", "DOCKER") - iptables("-t", "nat", "-X", "DOCKER") - mapper.tcpMapping = make(map[int]*net.TCPAddr) - mapper.tcpProxies = make(map[int]Proxy) - mapper.udpMapping = make(map[int]*net.UDPAddr) - mapper.udpProxies = make(map[int]Proxy) - return nil -} - -func (mapper *PortMapper) setup() error { - if err := iptables("-t", "nat", "-N", "DOCKER"); err != nil { - return fmt.Errorf("Failed to create DOCKER chain: %s", err) - } - if err := iptables("-t", "nat", "-A", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil { - return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err) - } - if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER"); err != nil { - return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err) - } - return nil -} - -func (mapper *PortMapper) iptablesForward(rule string, port int, proto string, dest_addr string, dest_port int) error { - return iptables("-t", "nat", rule, "DOCKER", "-p", proto, "--dport", strconv.Itoa(port), - "!", "-i", NetworkBridgeIface, - "-j", "DNAT", "--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))) + iptables *iptables.Chain } func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error { if _, isTCP := backendAddr.(*net.TCPAddr); isTCP { backendPort := backendAddr.(*net.TCPAddr).Port backendIP := backendAddr.(*net.TCPAddr).IP - if err := mapper.iptablesForward("-A", port, "tcp", backendIP.String(), backendPort); err != nil { - return err + if mapper.iptables != nil { + if err := mapper.iptables.Forward(iptables.Add, port, "tcp", backendIP.String(), backendPort); err != nil { + return err + } } mapper.tcpMapping[port] = backendAddr.(*net.TCPAddr) proxy, err := NewProxy(&net.TCPAddr{IP: net.IPv4(0, 0, 0, 0), Port: port}, backendAddr) @@ -275,8 +232,10 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error { } else { backendPort := backendAddr.(*net.UDPAddr).Port backendIP := backendAddr.(*net.UDPAddr).IP - if err := mapper.iptablesForward("-A", port, "udp", backendIP.String(), backendPort); err != nil { - return err + if mapper.iptables != nil { + if err := mapper.iptables.Forward(iptables.Add, port, "udp", backendIP.String(), backendPort); err != nil { + return err + } } mapper.udpMapping[port] = backendAddr.(*net.UDPAddr) proxy, err := NewProxy(&net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: port}, backendAddr) @@ -300,8 +259,10 @@ func (mapper *PortMapper) Unmap(port int, proto string) error { proxy.Close() delete(mapper.tcpProxies, port) } - if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { - return err + if mapper.iptables != nil { + if err := mapper.iptables.Forward(iptables.Delete, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { + return err + } } delete(mapper.tcpMapping, port) } else { @@ -313,8 +274,10 @@ func (mapper *PortMapper) Unmap(port int, proto string) error { proxy.Close() delete(mapper.udpProxies, port) } - if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { - return err + if mapper.iptables != nil { + if err := mapper.iptables.Forward(iptables.Delete, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { + return err + } } delete(mapper.udpMapping, port) } @@ -322,12 +285,20 @@ func (mapper *PortMapper) Unmap(port int, proto string) error { } func newPortMapper() (*PortMapper, error) { - mapper := &PortMapper{} - if err := mapper.cleanup(); err != nil { + if err := iptables.RemoveExistingChain("DOCKER"); err != nil { return nil, err } - if err := mapper.setup(); err != nil { - return nil, err + chain, err := iptables.NewChain("DOCKER", NetworkBridgeIface) + if err != nil { + return nil, fmt.Errorf("Failed to create DOCKER chain: %s", err) + } + + mapper := &PortMapper{ + tcpMapping: make(map[int]*net.TCPAddr), + tcpProxies: make(map[int]Proxy), + udpMapping: make(map[int]*net.UDPAddr), + udpProxies: make(map[int]Proxy), + iptables: chain, } return mapper, nil } diff --git a/network_test.go b/network_test.go index bd3a16a1b..e54785092 100644 --- a/network_test.go +++ b/network_test.go @@ -2,22 +2,9 @@ package docker import ( "net" - "os" "testing" ) -func TestIptables(t *testing.T) { - if err := iptables("-L"); err != nil { - t.Fatal(err) - } - path := os.Getenv("PATH") - os.Setenv("PATH", "") - defer os.Setenv("PATH", path) - if err := iptables("-L"); err == nil { - t.Fatal("Not finding iptables in the PATH should cause an error") - } -} - func TestParseNat(t *testing.T) { if nat, err := parseNat("4500"); err == nil { if nat.Frontend != 0 || nat.Backend != 4500 || nat.Proto != "tcp" {