Bladeren bron

Merge pull request #23 from aboch/iptables

Add implementation and test for setupIPTables()
Arnaud Porterie 10 jaren geleden
bovenliggende
commit
38173fa22c

+ 2 - 0
libnetwork/drivers/bridge/bridge.go

@@ -19,6 +19,8 @@ type Configuration struct {
 	FixedCIDRv6        *net.IPNet
 	EnableIPv6         bool
 	EnableIPTables     bool
+	EnableIPMasquerade bool
+	EnableICC          bool
 	EnableIPForwarding bool
 }
 

+ 0 - 6
libnetwork/drivers/bridge/setup.go

@@ -23,9 +23,3 @@ func (b *bridgeSetup) apply() error {
 func (b *bridgeSetup) queueStep(step setupStep) {
 	b.steps = append(b.steps, step)
 }
-
-//---------------------------------------------------------------------------//
-
-func setupIPTables(i *bridgeInterface) error {
-	return nil
-}

+ 150 - 0
libnetwork/drivers/bridge/setup_ip_tables.go

@@ -0,0 +1,150 @@
+package bridge
+
+import (
+	"fmt"
+	"net"
+
+	"github.com/docker/docker/daemon/networkdriver"
+	"github.com/docker/docker/daemon/networkdriver/portmapper"
+	"github.com/docker/docker/pkg/iptables"
+)
+
+// DockerChain: DOCKER iptable chain name
+const (
+	DockerChain = "DOCKER"
+)
+
+func setupIPTables(i *bridgeInterface) error {
+	// Sanity check.
+	if i.Config.EnableIPTables == false {
+		return fmt.Errorf("Unexpected request to set IP tables for interface: %s", i.Config.BridgeName)
+	}
+
+	addrv4, _, err := networkdriver.GetIfaceAddr(i.Config.BridgeName)
+	if err != nil {
+		return fmt.Errorf("Failed to setup IP tables, cannot acquire Interface address: %s", err.Error())
+	}
+	if err = setupIPTablesInternal(i.Config.BridgeName, addrv4, i.Config.EnableICC, i.Config.EnableIPMasquerade, true); err != nil {
+		return fmt.Errorf("Failed to Setup IP tables: %s", err.Error())
+	}
+
+	_, err = iptables.NewChain(DockerChain, i.Config.BridgeName, iptables.Nat)
+	if err != nil {
+		return fmt.Errorf("Failed to create NAT chain: %s", err.Error())
+	}
+
+	chain, err := iptables.NewChain(DockerChain, i.Config.BridgeName, iptables.Filter)
+	if err != nil {
+		return fmt.Errorf("Failed to create FILTER chain: %s", err.Error())
+	}
+
+	portmapper.SetIptablesChain(chain)
+
+	return nil
+}
+
+func setupIPTablesInternal(bridgeIface string, addr net.Addr, icc, ipmasq, enable bool) error {
+	var (
+		address = addr.String()
+		natRule = []string{"POSTROUTING", "-t", "nat", "-s", address, "!", "-o", bridgeIface, "-j", "MASQUERADE"}
+		outRule = []string{"FORWARD", "-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}
+		inRule  = []string{"FORWARD", "-o", bridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}
+	)
+
+	// Set NAT.
+	if ipmasq {
+		if err := programChainRule(natRule, "NAT", enable); err != nil {
+			return err
+		}
+	}
+
+	// Set Inter Container Communication.
+	if err := setIcc(bridgeIface, icc, enable); err != nil {
+		return err
+	}
+
+	// Set Accept on all non-intercontainer outgoing packets.
+	if err := programChainRule(outRule, "ACCEPT NON_ICC OUTGOING", enable); err != nil {
+		return err
+	}
+
+	// Set Accept on incoming packets for existing connections.
+	if err := programChainRule(inRule, "ACCEPT INCOMING", enable); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func programChainRule(ruleArgs []string, ruleDescr string, insert bool) error {
+	var (
+		prefix    []string
+		operation string
+		condition bool
+	)
+
+	if insert {
+		condition = !iptables.Exists(ruleArgs...)
+		prefix = []string{"-I"}
+		operation = "enable"
+	} else {
+		condition = iptables.Exists(ruleArgs...)
+		prefix = []string{"-D"}
+		operation = "disable"
+	}
+
+	if condition {
+		if output, err := iptables.Raw(append(prefix, ruleArgs...)...); err != nil {
+			return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error())
+		} else if len(output) != 0 {
+			return &iptables.ChainError{Chain: ruleDescr, Output: output}
+		}
+	}
+
+	return nil
+}
+
+func setIcc(bridgeIface string, iccEnable, insert bool) error {
+	var (
+		args       = []string{"FORWARD", "-i", bridgeIface, "-o", bridgeIface, "-j"}
+		acceptArgs = append(args, "ACCEPT")
+		dropArgs   = append(args, "DROP")
+	)
+
+	if insert {
+		if !iccEnable {
+			iptables.Raw(append([]string{"-D"}, acceptArgs...)...)
+
+			if !iptables.Exists(dropArgs...) {
+				if output, err := iptables.Raw(append([]string{"-I"}, dropArgs...)...); err != nil {
+					return fmt.Errorf("Unable to prevent intercontainer communication: %s", err.Error())
+				} else if len(output) != 0 {
+					return fmt.Errorf("Error disabling intercontainer communication: %s", output)
+				}
+			}
+		} else {
+			iptables.Raw(append([]string{"-D"}, dropArgs...)...)
+
+			if !iptables.Exists(acceptArgs...) {
+				if output, err := iptables.Raw(append([]string{"-I"}, acceptArgs...)...); err != nil {
+					return fmt.Errorf("Unable to allow intercontainer communication: %s", err.Error())
+				} else if len(output) != 0 {
+					return fmt.Errorf("Error enabling intercontainer communication: %s", output)
+				}
+			}
+		}
+	} else {
+		// Remove any ICC rule.
+		if !iccEnable {
+			if iptables.Exists(dropArgs...) {
+				iptables.Raw(append([]string{"-D"}, dropArgs...)...)
+			}
+		} else {
+			if iptables.Exists(acceptArgs...) {
+				iptables.Raw(append([]string{"-D"}, acceptArgs...)...)
+			}
+		}
+	}
+
+	return nil
+}

+ 102 - 0
libnetwork/drivers/bridge/setup_ip_tables_test.go

@@ -0,0 +1,102 @@
+package bridge
+
+import (
+	"net"
+	"testing"
+
+	"github.com/docker/docker/pkg/iptables"
+	"github.com/docker/libnetwork"
+)
+
+const (
+	iptablesTestBridgeIP = "192.168.42.1"
+)
+
+func TestProgramIPTable(t *testing.T) {
+	// Create a test bridge with a basic bridge configuration (name + IPv4).
+	defer libnetwork.SetupTestNetNS(t)()
+	createTestBridge(getBasicTestConfig(), t)
+
+	// Store various iptables chain rules we care for.
+	rules := []struct {
+		ruleArgs []string
+		descr    string
+	}{{[]string{"FORWARD", "-d", "127.1.2.3", "-i", "lo", "-o", "lo", "-j", "DROP"}, "Test Loopback"},
+		{[]string{"POSTROUTING", "-t", "nat", "-s", iptablesTestBridgeIP, "!", "-o", DefaultBridgeName, "-j", "MASQUERADE"}, "NAT Test"},
+		{[]string{"FORWARD", "-i", DefaultBridgeName, "!", "-o", DefaultBridgeName, "-j", "ACCEPT"}, "Test ACCEPT NON_ICC OUTGOING"},
+		{[]string{"FORWARD", "-o", DefaultBridgeName, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}, "Test ACCEPT INCOMING"},
+		{[]string{"FORWARD", "-i", DefaultBridgeName, "-o", DefaultBridgeName, "-j", "ACCEPT"}, "Test enable ICC"},
+		{[]string{"FORWARD", "-i", DefaultBridgeName, "-o", DefaultBridgeName, "-j", "DROP"}, "Test disable ICC"},
+	}
+
+	// Assert the chain rules' insertion and removal.
+	for _, c := range rules {
+		assertIPTableChainProgramming(c.ruleArgs, c.descr, t)
+	}
+}
+
+func TestSetupIPTables(t *testing.T) {
+	// Create a test bridge with a basic bridge configuration (name + IPv4).
+	defer libnetwork.SetupTestNetNS(t)()
+	br := getBasicTestConfig()
+	createTestBridge(br, t)
+
+	// Modify iptables params in base configuration and apply them.
+	br.Config.EnableIPTables = true
+	assertBridgeConfig(br, t)
+
+	br.Config.EnableIPMasquerade = true
+	assertBridgeConfig(br, t)
+
+	br.Config.EnableICC = true
+	assertBridgeConfig(br, t)
+
+	br.Config.EnableIPMasquerade = false
+	assertBridgeConfig(br, t)
+}
+
+func getBasicTestConfig() *bridgeInterface {
+	return &bridgeInterface{
+		Config: &Configuration{
+			BridgeName:  DefaultBridgeName,
+			AddressIPv4: &net.IPNet{IP: net.ParseIP(iptablesTestBridgeIP), Mask: net.CIDRMask(16, 32)},
+		},
+	}
+}
+
+func createTestBridge(br *bridgeInterface, t *testing.T) {
+	if err := setupDevice(br); err != nil {
+		t.Fatalf("Failed to create the testing Bridge: %s", err.Error())
+	}
+	if err := setupBridgeIPv4(br); err != nil {
+		t.Fatalf("Failed to bring up the testing Bridge: %s", err.Error())
+	}
+}
+
+// Assert base function which pushes iptables chain rules on insertion and removal.
+func assertIPTableChainProgramming(args []string, descr string, t *testing.T) {
+	// Add
+	if err := programChainRule(args, descr, true); err != nil {
+		t.Fatalf("Failed to program iptable rule %s: %s", descr, err.Error())
+	}
+	if iptables.Exists(args...) == false {
+		t.Fatalf("Failed to effectively program iptable rule: %s", descr)
+	}
+
+	// Remove
+	if err := programChainRule(args, descr, false); err != nil {
+		t.Fatalf("Failed to remove iptable rule %s: %s", descr, err.Error())
+	}
+	if iptables.Exists(args...) == true {
+		t.Fatalf("Failed to effectively remove iptable rule: %s", descr)
+	}
+}
+
+// Assert function which pushes chains based on bridge config parameters.
+func assertBridgeConfig(br *bridgeInterface, t *testing.T) {
+	// Attempt programming of ip tables.
+	err := setupIPTables(br)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+}