1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Improve scalabiltiy of bridge network isolation rules

- This reduces complexity from O(N^2) to O(2N)

Signed-off-by: Alessandro Boch <aboch@docker.com>
Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
This commit is contained in:
Alessandro Boch 2016-11-02 10:14:09 -07:00 committed by Akihiro Suda
parent aa612217b5
commit ed6d70c0c1
4 changed files with 124 additions and 102 deletions

View file

@ -141,7 +141,8 @@ type driver struct {
network *bridgeNetwork network *bridgeNetwork
natChain *iptables.ChainInfo natChain *iptables.ChainInfo
filterChain *iptables.ChainInfo filterChain *iptables.ChainInfo
isolationChain *iptables.ChainInfo isolationChain1 *iptables.ChainInfo
isolationChain2 *iptables.ChainInfo
networks map[string]*bridgeNetwork networks map[string]*bridgeNetwork
store datastore.DataStore store datastore.DataStore
nlh *netlink.Handle nlh *netlink.Handle
@ -266,15 +267,15 @@ func (n *bridgeNetwork) registerIptCleanFunc(clean iptableCleanFunc) {
n.iptCleanFuncs = append(n.iptCleanFuncs, clean) n.iptCleanFuncs = append(n.iptCleanFuncs, clean)
} }
func (n *bridgeNetwork) getDriverChains() (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) { func (n *bridgeNetwork) getDriverChains() (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) {
n.Lock() n.Lock()
defer n.Unlock() defer n.Unlock()
if n.driver == nil { if n.driver == nil {
return nil, nil, nil, types.BadRequestErrorf("no driver found") return nil, nil, nil, nil, types.BadRequestErrorf("no driver found")
} }
return n.driver.natChain, n.driver.filterChain, n.driver.isolationChain, nil return n.driver.natChain, n.driver.filterChain, n.driver.isolationChain1, n.driver.isolationChain2, nil
} }
func (n *bridgeNetwork) getNetworkBridgeName() string { func (n *bridgeNetwork) getNetworkBridgeName() string {
@ -311,22 +312,10 @@ func (n *bridgeNetwork) isolateNetwork(others []*bridgeNetwork, enable bool) err
return nil return nil
} }
// Install the rules to isolate this networks against each of the other networks // Install the rules to isolate this network against each of the other networks
for _, o := range others { if err := setINC(thisConfig.BridgeName, enable); err != nil {
o.Lock()
otherConfig := o.config
o.Unlock()
if otherConfig.Internal {
continue
}
if thisConfig.BridgeName != otherConfig.BridgeName {
if err := setINC(thisConfig.BridgeName, otherConfig.BridgeName, enable); err != nil {
return err return err
} }
}
}
return nil return nil
} }
@ -337,7 +326,8 @@ func (d *driver) configure(option map[string]interface{}) error {
err error err error
natChain *iptables.ChainInfo natChain *iptables.ChainInfo
filterChain *iptables.ChainInfo filterChain *iptables.ChainInfo
isolationChain *iptables.ChainInfo isolationChain1 *iptables.ChainInfo
isolationChain2 *iptables.ChainInfo
) )
genericData, ok := option[netlabel.GenericData] genericData, ok := option[netlabel.GenericData]
@ -365,7 +355,7 @@ func (d *driver) configure(option map[string]interface{}) error {
} }
} }
removeIPChains() removeIPChains()
natChain, filterChain, isolationChain, err = setupIPChains(config) natChain, filterChain, isolationChain1, isolationChain2, err = setupIPChains(config)
if err != nil { if err != nil {
return err return err
} }
@ -384,7 +374,8 @@ func (d *driver) configure(option map[string]interface{}) error {
d.Lock() d.Lock()
d.natChain = natChain d.natChain = natChain
d.filterChain = filterChain d.filterChain = filterChain
d.isolationChain = isolationChain d.isolationChain1 = isolationChain1
d.isolationChain2 = isolationChain2
d.config = config d.config = config
d.Unlock() d.Unlock()

View file

@ -425,14 +425,15 @@ func TestCreateMultipleNetworks(t *testing.T) {
t.Fatalf("Failed to create bridge: %v", err) t.Fatalf("Failed to create bridge: %v", err)
} }
verifyV4INCEntries(d.networks, t)
config2 := &networkConfiguration{BridgeName: "net_test_2"} config2 := &networkConfiguration{BridgeName: "net_test_2"}
genericOption[netlabel.GenericData] = config2 genericOption[netlabel.GenericData] = config2
if err := d.CreateNetwork("2", genericOption, nil, getIPv4Data(t, ""), nil); err != nil { if err := d.CreateNetwork("2", genericOption, nil, getIPv4Data(t, ""), nil); err != nil {
t.Fatalf("Failed to create bridge: %v", err) t.Fatalf("Failed to create bridge: %v", err)
} }
// Verify the network isolation rules are installed, each network subnet should appear 2 times verifyV4INCEntries(d.networks, t)
verifyV4INCEntries(d.networks, 2, t)
config3 := &networkConfiguration{BridgeName: "net_test_3"} config3 := &networkConfiguration{BridgeName: "net_test_3"}
genericOption[netlabel.GenericData] = config3 genericOption[netlabel.GenericData] = config3
@ -440,8 +441,7 @@ func TestCreateMultipleNetworks(t *testing.T) {
t.Fatalf("Failed to create bridge: %v", err) t.Fatalf("Failed to create bridge: %v", err)
} }
// Verify the network isolation rules are installed, each network subnet should appear 4 times verifyV4INCEntries(d.networks, t)
verifyV4INCEntries(d.networks, 6, t)
config4 := &networkConfiguration{BridgeName: "net_test_4"} config4 := &networkConfiguration{BridgeName: "net_test_4"}
genericOption[netlabel.GenericData] = config4 genericOption[netlabel.GenericData] = config4
@ -449,46 +449,44 @@ func TestCreateMultipleNetworks(t *testing.T) {
t.Fatalf("Failed to create bridge: %v", err) t.Fatalf("Failed to create bridge: %v", err)
} }
// Now 6 times verifyV4INCEntries(d.networks, t)
verifyV4INCEntries(d.networks, 12, t)
d.DeleteNetwork("1") d.DeleteNetwork("1")
verifyV4INCEntries(d.networks, 6, t) verifyV4INCEntries(d.networks, t)
d.DeleteNetwork("2") d.DeleteNetwork("2")
verifyV4INCEntries(d.networks, 2, t) verifyV4INCEntries(d.networks, t)
d.DeleteNetwork("3") d.DeleteNetwork("3")
verifyV4INCEntries(d.networks, 0, t) verifyV4INCEntries(d.networks, t)
d.DeleteNetwork("4") d.DeleteNetwork("4")
verifyV4INCEntries(d.networks, 0, t) verifyV4INCEntries(d.networks, t)
} }
func verifyV4INCEntries(networks map[string]*bridgeNetwork, numEntries int, t *testing.T) { // Verify the network isolation rules are installed for each network
out, err := iptables.Raw("-nvL", IsolationChain) func verifyV4INCEntries(networks map[string]*bridgeNetwork, t *testing.T) {
out1, err := iptables.Raw("-S", IsolationChain1)
if err != nil {
t.Fatal(err)
}
out2, err := iptables.Raw("-S", IsolationChain2)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
found := 0 for _, n := range networks {
for _, x := range networks { re := regexp.MustCompile(fmt.Sprintf("-i %s ! -o %s -j %s", n.config.BridgeName, n.config.BridgeName, IsolationChain2))
for _, y := range networks { matches := re.FindAllString(string(out1[:]), -1)
if x == y {
continue
}
re := regexp.MustCompile(x.config.BridgeName + " " + y.config.BridgeName)
matches := re.FindAllString(string(out[:]), -1)
if len(matches) != 1 { if len(matches) != 1 {
t.Fatalf("Cannot find expected inter-network isolation rules in IP Tables:\n%s", string(out[:])) t.Fatalf("Cannot find expected inter-network isolation rules in IP Tables for network %s:\n%s.", n.id, string(out1[:]))
} }
found++ re = regexp.MustCompile(fmt.Sprintf("-o %s -j DROP", n.config.BridgeName))
matches = re.FindAllString(string(out2[:]), -1)
if len(matches) != 1 {
t.Fatalf("Cannot find expected inter-network isolation rules in IP Tables for network %s:\n%s.", n.id, string(out2[:]))
} }
} }
if found != numEntries {
t.Fatalf("Cannot find expected number (%d) of inter-network isolation rules in IP Tables:\n%s\nFound %d", numEntries, string(out[:]), found)
}
} }
type testInterface struct { type testInterface struct {
@ -996,9 +994,9 @@ func TestCleanupIptableRules(t *testing.T) {
bridgeChain := []iptables.ChainInfo{ bridgeChain := []iptables.ChainInfo{
{Name: DockerChain, Table: iptables.Nat}, {Name: DockerChain, Table: iptables.Nat},
{Name: DockerChain, Table: iptables.Filter}, {Name: DockerChain, Table: iptables.Filter},
{Name: IsolationChain, Table: iptables.Filter}, {Name: IsolationChain1, Table: iptables.Filter},
} }
if _, _, _, err := setupIPChains(&configuration{EnableIPTables: true}); err != nil { if _, _, _, _, err := setupIPChains(&configuration{EnableIPTables: true}); err != nil {
t.Fatalf("Error setting up ip chains: %v", err) t.Fatalf("Error setting up ip chains: %v", err)
} }
for _, chainInfo := range bridgeChain { for _, chainInfo := range bridgeChain {

View file

@ -13,20 +13,30 @@ import (
// DockerChain: DOCKER iptable chain name // DockerChain: DOCKER iptable chain name
const ( const (
DockerChain = "DOCKER" DockerChain = "DOCKER"
IsolationChain = "DOCKER-ISOLATION" // Isolation between bridge networks is achieved in two stages by means
// of the following two chains in the filter table. The first chain matches
// on the source interface being a bridge network's bridge and the
// destination being a different interface. A positive match leads to the
// second isolation chain. No match returns to the parent chain. The second
// isolation chain matches on destination interface being a bridge network's
// bridge. A positive match identifies a packet originated from one bridge
// network's bridge destined to another bridge network's bridge and will
// result in the packet being dropped. No match returns to the parent chain.
IsolationChain1 = "DOCKER-ISOLATION-STAGE-1"
IsolationChain2 = "DOCKER-ISOLATION-STAGE-2"
) )
func setupIPChains(config *configuration) (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) { func setupIPChains(config *configuration) (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) {
// Sanity check. // Sanity check.
if config.EnableIPTables == false { if config.EnableIPTables == false {
return nil, nil, nil, errors.New("cannot create new chains, EnableIPTable is disabled") return nil, nil, nil, nil, errors.New("cannot create new chains, EnableIPTable is disabled")
} }
hairpinMode := !config.EnableUserlandProxy hairpinMode := !config.EnableUserlandProxy
natChain, err := iptables.NewChain(DockerChain, iptables.Nat, hairpinMode) natChain, err := iptables.NewChain(DockerChain, iptables.Nat, hairpinMode)
if err != nil { if err != nil {
return nil, nil, nil, fmt.Errorf("failed to create NAT chain: %v", err) return nil, nil, nil, nil, fmt.Errorf("failed to create NAT chain: %v", err)
} }
defer func() { defer func() {
if err != nil { if err != nil {
@ -38,7 +48,7 @@ func setupIPChains(config *configuration) (*iptables.ChainInfo, *iptables.ChainI
filterChain, err := iptables.NewChain(DockerChain, iptables.Filter, false) filterChain, err := iptables.NewChain(DockerChain, iptables.Filter, false)
if err != nil { if err != nil {
return nil, nil, nil, fmt.Errorf("failed to create FILTER chain: %v", err) return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER chain: %v", err)
} }
defer func() { defer func() {
if err != nil { if err != nil {
@ -48,16 +58,25 @@ func setupIPChains(config *configuration) (*iptables.ChainInfo, *iptables.ChainI
} }
}() }()
isolationChain, err := iptables.NewChain(IsolationChain, iptables.Filter, false) isolationChain1, err := iptables.NewChain(IsolationChain1, iptables.Filter, false)
if err != nil { if err != nil {
return nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err) return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err)
} }
if err := iptables.AddReturnRule(IsolationChain); err != nil { isolationChain2, err := iptables.NewChain(IsolationChain2, iptables.Filter, false)
return nil, nil, nil, err if err != nil {
return nil, nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err)
} }
return natChain, filterChain, isolationChain, nil if err := iptables.AddReturnRule(IsolationChain1); err != nil {
return nil, nil, nil, nil, err
}
if err := iptables.AddReturnRule(IsolationChain2); err != nil {
return nil, nil, nil, nil, err
}
return natChain, filterChain, isolationChain1, isolationChain2, nil
} }
func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInterface) error { func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInterface) error {
@ -94,7 +113,7 @@ func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInt
n.registerIptCleanFunc(func() error { n.registerIptCleanFunc(func() error {
return setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false) return setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false)
}) })
natChain, filterChain, _, err := n.getDriverChains() natChain, filterChain, _, _, err := n.getDriverChains()
if err != nil { if err != nil {
return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %s", err.Error()) return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %s", err.Error())
} }
@ -117,7 +136,7 @@ func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInt
} }
d.Lock() d.Lock()
err = iptables.EnsureJumpRule("FORWARD", IsolationChain) err = iptables.EnsureJumpRule("FORWARD", IsolationChain1)
d.Unlock() d.Unlock()
if err != nil { if err != nil {
return err return err
@ -245,42 +264,56 @@ func setIcc(bridgeIface string, iccEnable, insert bool) error {
return nil return nil
} }
// Control Inter Network Communication. Install/remove only if it is not/is present. // Control Inter Network Communication. Install[Remove] only if it is [not] present.
func setINC(iface1, iface2 string, enable bool) error { func setINC(iface string, enable bool) error {
var ( var (
table = iptables.Filter action = iptables.Insert
chain = IsolationChain actionMsg = "add"
args = [2][]string{{"-i", iface1, "-o", iface2, "-j", "DROP"}, {"-i", iface2, "-o", iface1, "-j", "DROP"}} chains = []string{IsolationChain1, IsolationChain2}
rules = [][]string{
{"-i", iface, "!", "-o", iface, "-j", IsolationChain2},
{"-o", iface, "-j", "DROP"},
}
) )
if !enable {
action = iptables.Delete
actionMsg = "remove"
}
for i, chain := range chains {
if err := iptables.ProgramRule(iptables.Filter, chain, action, rules[i]); err != nil {
msg := fmt.Sprintf("unable to %s inter-network communication rule: %v", actionMsg, err)
if enable { if enable {
for i := 0; i < 2; i++ { if i == 1 {
if iptables.Exists(table, chain, args[i]...) { // Rollback the rule installed on first chain
continue if err2 := iptables.ProgramRule(iptables.Filter, chains[0], iptables.Delete, rules[0]); err2 != nil {
} logrus.Warn("Failed to rollback iptables rule after failure (%v): %v", err, err2)
if err := iptables.RawCombinedOutput(append([]string{"-I", chain}, args[i]...)...); err != nil {
return fmt.Errorf("unable to add inter-network communication rule: %v", err)
} }
} }
} else { return fmt.Errorf(msg)
for i := 0; i < 2; i++ {
if !iptables.Exists(table, chain, args[i]...) {
continue
}
if err := iptables.RawCombinedOutput(append([]string{"-D", chain}, args[i]...)...); err != nil {
return fmt.Errorf("unable to remove inter-network communication rule: %v", err)
} }
logrus.Warn(msg)
} }
} }
return nil return nil
} }
// Obsolete chain from previous docker versions
const oldIsolationChain = "DOCKER-ISOLATION"
func removeIPChains() { func removeIPChains() {
// Remove obsolete rules from default chains
iptables.ProgramRule(iptables.Filter, "FORWARD", iptables.Delete, []string{"-j", oldIsolationChain})
// Remove chains
for _, chainInfo := range []iptables.ChainInfo{ for _, chainInfo := range []iptables.ChainInfo{
{Name: DockerChain, Table: iptables.Nat}, {Name: DockerChain, Table: iptables.Nat},
{Name: DockerChain, Table: iptables.Filter}, {Name: DockerChain, Table: iptables.Filter},
{Name: IsolationChain, Table: iptables.Filter}, {Name: IsolationChain1, Table: iptables.Filter},
{Name: IsolationChain2, Table: iptables.Filter},
{Name: oldIsolationChain, Table: iptables.Filter},
} { } {
if err := chainInfo.Remove(); err != nil { if err := chainInfo.Remove(); err != nil {
logrus.Warnf("Failed to remove existing iptables entries in table %s chain %s : %v", chainInfo.Table, chainInfo.Name, err) logrus.Warnf("Failed to remove existing iptables entries in table %s chain %s : %v", chainInfo.Table, chainInfo.Name, err)
@ -290,8 +323,8 @@ func removeIPChains() {
func setupInternalNetworkRules(bridgeIface string, addr net.Addr, icc, insert bool) error { func setupInternalNetworkRules(bridgeIface string, addr net.Addr, icc, insert bool) error {
var ( var (
inDropRule = iptRule{table: iptables.Filter, chain: IsolationChain, args: []string{"-i", bridgeIface, "!", "-d", addr.String(), "-j", "DROP"}} inDropRule = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-i", bridgeIface, "!", "-d", addr.String(), "-j", "DROP"}}
outDropRule = iptRule{table: iptables.Filter, chain: IsolationChain, args: []string{"-o", bridgeIface, "!", "-s", addr.String(), "-j", "DROP"}} outDropRule = iptRule{table: iptables.Filter, chain: IsolationChain1, args: []string{"-o", bridgeIface, "!", "-s", addr.String(), "-j", "DROP"}}
) )
if err := programChainRule(inDropRule, "DROP INCOMING", insert); err != nil { if err := programChainRule(inDropRule, "DROP INCOMING", insert); err != nil {
return err return err

View file

@ -116,7 +116,7 @@ func assertIPTableChainProgramming(rule iptRule, descr string, t *testing.T) {
func assertChainConfig(d *driver, t *testing.T) { func assertChainConfig(d *driver, t *testing.T) {
var err error var err error
d.natChain, d.filterChain, d.isolationChain, err = setupIPChains(d.config) d.natChain, d.filterChain, d.isolationChain1, d.isolationChain2, err = setupIPChains(d.config)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }