From 6311a967107bf4e97c3474d9d7da6e521fd80365 Mon Sep 17 00:00:00 2001 From: Alessandro Boch Date: Thu, 5 Mar 2015 22:43:14 -0800 Subject: [PATCH] Add implementation and test for SetupIPTables() - Port and refactor docker/damon/driver ip tables setup function into libnetwork. - Taken care of golint guideline for CI to pass - Ran one more time goimports for CI to pass... Signed-off-by: Alessandro Boch --- libnetwork/drivers/bridge/bridge.go | 2 + libnetwork/drivers/bridge/setup.go | 6 - libnetwork/drivers/bridge/setup_ip_tables.go | 150 ++++++++++++++++++ .../drivers/bridge/setup_ip_tables_test.go | 102 ++++++++++++ 4 files changed, 254 insertions(+), 6 deletions(-) create mode 100644 libnetwork/drivers/bridge/setup_ip_tables.go create mode 100644 libnetwork/drivers/bridge/setup_ip_tables_test.go diff --git a/libnetwork/drivers/bridge/bridge.go b/libnetwork/drivers/bridge/bridge.go index 558bbbb460..9c28824e5b 100644 --- a/libnetwork/drivers/bridge/bridge.go +++ b/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 } diff --git a/libnetwork/drivers/bridge/setup.go b/libnetwork/drivers/bridge/setup.go index 2472bac6fa..26a80aab02 100644 --- a/libnetwork/drivers/bridge/setup.go +++ b/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 -} diff --git a/libnetwork/drivers/bridge/setup_ip_tables.go b/libnetwork/drivers/bridge/setup_ip_tables.go new file mode 100644 index 0000000000..392965803b --- /dev/null +++ b/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 +} diff --git a/libnetwork/drivers/bridge/setup_ip_tables_test.go b/libnetwork/drivers/bridge/setup_ip_tables_test.go new file mode 100644 index 0000000000..1f0d967bf9 --- /dev/null +++ b/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) + } +}