Provide isolation b/w bridge networks

Signed-off-by: Alessandro Boch <aboch@docker.com>
This commit is contained in:
Alessandro Boch 2015-06-02 18:40:55 -07:00
parent d861b7ec70
commit e27586de4e
3 changed files with 238 additions and 31 deletions

View File

@ -330,6 +330,79 @@ func (n *bridgeNetwork) getEndpoint(eid types.UUID) (*bridgeEndpoint, error) {
return nil, nil
}
// Install/Removes the iptables rules needed to isolate this network
// from each of the other networks
func (n *bridgeNetwork) isolateNetwork(others []*bridgeNetwork, enable bool) error {
n.Lock()
thisV4 := n.bridge.bridgeIPv4
thisV6 := getV6Network(n.config, n.bridge)
n.Unlock()
// Install the rules to isolate this networks against each of the other networks
for _, o := range others {
o.Lock()
otherV4 := o.bridge.bridgeIPv4
otherV6 := getV6Network(o.config, o.bridge)
o.Unlock()
if !types.CompareIPNet(thisV4, otherV4) {
// It's ok to pass a.b.c.d/x, iptables will ignore the host subnet bits
if err := setINC(thisV4.String(), otherV4.String(), enable); err != nil {
return err
}
}
if thisV6 != nil && otherV6 != nil && !types.CompareIPNet(thisV6, otherV6) {
if err := setINC(thisV6.String(), otherV6.String(), enable); err != nil {
return err
}
}
}
return nil
}
// Checks whether this network conflicts with any of the passed networks
func (n *bridgeNetwork) conflicts(others []*bridgeNetwork) error {
n.Lock()
id := n.id
config := n.config
n.Unlock()
for _, nw := range others {
nw.Lock()
oid := nw.id
oconfig := nw.config
obridge := nw.bridge
nw.Unlock()
if oid == id {
continue
}
// Verify the name (which may have been set by newInterface()) does not conflict with
// existing bridge interfaces. Ironically the system chosen name gets stored in the config...
// Basically we are checking if the two original configs were both empty.
if oconfig.BridgeName == config.BridgeName {
return types.ForbiddenErrorf("conflicts with network %s (%s) by bridge name", oid, oconfig.BridgeName)
}
// If this network config specifies the AddressIPv4, we need
// to make sure it does not conflict with any previously allocated
// bridges. This could not be completely caught by the config conflict
// check, because networks which config does not specify the AddressIPv4
// get their address and subnet selected by the driver (see electBridgeIPv4())
if config.AddressIPv4 != nil {
if obridge.bridgeIPv4.Contains(config.AddressIPv4.IP) ||
config.AddressIPv4.Contains(obridge.bridgeIPv4.IP) {
return types.ForbiddenErrorf("conflicts with network %s (%s) by ip network", nw.id, nw.config.BridgeName)
}
}
}
return nil
}
func (d *driver) Config(option map[string]interface{}) error {
var config *configuration
@ -434,28 +507,51 @@ func parseNetworkOptions(option options.Generic) (*networkConfiguration, error)
return config, nil
}
// Returns the non link-local IPv6 subnet for the containers attached to this bridge if found, nil otherwise
func getV6Network(config *networkConfiguration, i *bridgeInterface) *net.IPNet {
if config.FixedCIDRv6 != nil {
return config.FixedCIDRv6
}
if i.bridgeIPv6 != nil && i.bridgeIPv6.IP != nil && !i.bridgeIPv6.IP.IsLinkLocalUnicast() {
return i.bridgeIPv6
}
return nil
}
// Return a slice of networks over which caller can iterate safely
func (d *driver) getNetworks() []*bridgeNetwork {
d.Lock()
defer d.Unlock()
ls := make([]*bridgeNetwork, 0, len(d.networks))
for _, nw := range d.networks {
ls = append(ls, nw)
}
return ls
}
// Create a new network using bridge plugin
func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) error {
var err error
// Driver must be configured
d.Lock()
// Sanity checks
d.Lock()
if _, ok := d.networks[id]; ok {
d.Unlock()
return types.ForbiddenErrorf("network %s exists", id)
}
d.Unlock()
// Parse and validate the config. It should not conflict with existing networks' config
config, err := parseNetworkOptions(option)
if err != nil {
d.Unlock()
return err
}
for _, nw := range d.networks {
networkList := d.getNetworks()
for _, nw := range networkList {
if nw.config.Conflict(config) {
d.Unlock()
return types.ForbiddenErrorf("conflicts with network %s (%s)", nw.id, nw.config.BridgeName)
}
}
@ -467,6 +563,8 @@ func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) err
config: config,
portMapper: portmapper.New(),
}
d.Lock()
d.networks[id] = network
d.Unlock()
@ -485,32 +583,22 @@ func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) err
// Verify network does not conflict with previously configured networks
// on parameters that were chosen by the driver.
d.Lock()
for _, nw := range d.networks {
if nw.id == id {
continue
}
// Verify the name (which may have been set by newInterface()) does not conflict with
// existing bridge interfaces. Ironically the system chosen name gets stored in the config...
// Basically we are checking if the two original configs were both empty.
if nw.config.BridgeName == config.BridgeName {
d.Unlock()
return types.ForbiddenErrorf("conflicts with network %s (%s) by bridge name", nw.id, nw.config.BridgeName)
}
// If this network config specifies the AddressIPv4, we need
// to make sure it does not conflict with any previously allocated
// bridges. This could not be completely caught by the config conflict
// check, because networks which config does not specify the AddressIPv4
// get their address and subnet selected by the driver (see electBridgeIPv4())
if config.AddressIPv4 != nil {
if nw.bridge.bridgeIPv4.Contains(config.AddressIPv4.IP) ||
config.AddressIPv4.Contains(nw.bridge.bridgeIPv4.IP) {
d.Unlock()
return types.ForbiddenErrorf("conflicts with network %s (%s) by ip network", nw.id, nw.config.BridgeName)
}
}
if err := network.conflicts(networkList); err != nil {
return err
}
setupNetworkIsolationRules := func(config *networkConfiguration, i *bridgeInterface) error {
defer func() {
if err != nil {
if err := network.isolateNetwork(networkList, false); err != nil {
logrus.Warnf("Failed on removing the inter-network iptables rules on cleanup: %v", err)
}
}
}()
err := network.isolateNetwork(networkList, true)
return err
}
d.Unlock()
// Prepare the bridge setup configuration
bridgeSetup := newBridgeSetup(config, bridgeIface)
@ -567,6 +655,9 @@ func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) err
// Setup DefaultGatewayIPv6
{config.DefaultGatewayIPv6 != nil, setupGatewayIPv6},
// Add inter-network communication rules.
{config.EnableIPTables, setupNetworkIsolationRules},
} {
if step.Condition {
bridgeSetup.queueStep(step.Fn)
@ -627,6 +718,22 @@ func (d *driver) DeleteNetwork(nid types.UUID) error {
return err
}
// In case of failures after this point, restore the network isolation rules
nwList := d.getNetworks()
defer func() {
if err != nil {
if err := n.isolateNetwork(nwList, true); err != nil {
logrus.Warnf("Failed on restoring the inter-network iptables rules on cleanup: %v", err)
}
}
}()
// Remove inter-network communication rules.
err = n.isolateNetwork(nwList, false)
if err != nil {
return err
}
// Programming
err = netlink.LinkDel(n.bridge.Link)

View File

@ -88,6 +88,71 @@ func TestCreateFail(t *testing.T) {
}
}
func TestCreateMultipleNetworks(t *testing.T) {
defer netutils.SetupTestNetNS(t)()
d := newDriver()
dd, _ := d.(*driver)
config1 := &networkConfiguration{BridgeName: "net_test_1", AllowNonDefaultBridge: true, EnableIPTables: true}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = config1
if err := d.CreateNetwork("1", genericOption); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
config2 := &networkConfiguration{BridgeName: "net_test_2", AllowNonDefaultBridge: true, EnableIPTables: true}
genericOption[netlabel.GenericData] = config2
if err := d.CreateNetwork("2", genericOption); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
config3 := &networkConfiguration{BridgeName: "net_test_3", AllowNonDefaultBridge: true, EnableIPTables: true}
genericOption[netlabel.GenericData] = config3
if err := d.CreateNetwork("3", genericOption); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
// Verify the network isolation rules are installed, each network subnet should appear 4 times
verifyV4INCEntries(dd.networks, 4, t)
config4 := &networkConfiguration{BridgeName: "net_test_4", AllowNonDefaultBridge: true, EnableIPTables: true}
genericOption[netlabel.GenericData] = config4
if err := d.CreateNetwork("4", genericOption); err != nil {
t.Fatalf("Failed to create bridge: %v", err)
}
// Now 6 times
verifyV4INCEntries(dd.networks, 6, t)
d.DeleteNetwork("1")
verifyV4INCEntries(dd.networks, 4, t)
d.DeleteNetwork("2")
verifyV4INCEntries(dd.networks, 2, t)
d.DeleteNetwork("3")
verifyV4INCEntries(dd.networks, 0, t)
d.DeleteNetwork("4")
verifyV4INCEntries(dd.networks, 0, t)
}
func verifyV4INCEntries(networks map[types.UUID]*bridgeNetwork, numEntries int, t *testing.T) {
out, err := iptables.Raw("-L", "FORWARD")
if err != nil {
t.Fatal(err)
}
for _, nw := range networks {
nt := types.GetIPNetCopy(nw.bridge.bridgeIPv4)
nt.IP = nt.IP.Mask(nt.Mask)
re := regexp.MustCompile(nt.String())
matches := re.FindAllString(string(out[:]), -1)
if len(matches) != numEntries {
t.Fatalf("Cannot find expected inter-network isolation rules in IP Tables:\n%s", string(out[:]))
}
}
}
type testInterface struct {
id int
mac net.HardwareAddr

View File

@ -171,3 +171,38 @@ func setIcc(bridgeIface string, iccEnable, insert bool) error {
return nil
}
// Control Inter Network Communication. Install/remove only if it is not/is present.
func setINC(network1, network2 string, enable bool) error {
var (
table = iptables.Filter
chain = "FORWARD"
args = [2][]string{{"-s", network1, "-d", network2, "-j", "DROP"}, {"-s", network2, "-d", network1, "-j", "DROP"}}
)
if enable {
for i := 0; i < 2; i++ {
if iptables.Exists(table, chain, args[i]...) {
continue
}
if output, err := iptables.Raw(append([]string{"-I", chain}, args[i]...)...); err != nil {
return fmt.Errorf("unable to add inter-network communication rule: %s", err.Error())
} else if len(output) != 0 {
return fmt.Errorf("error adding inter-network communication rule: %s", string(output))
}
}
} else {
for i := 0; i < 2; i++ {
if !iptables.Exists(table, chain, args[i]...) {
continue
}
if output, err := iptables.Raw(append([]string{"-D", chain}, args[i]...)...); err != nil {
return fmt.Errorf("unable to remove inter-network communication rule: %s", err.Error())
} else if len(output) != 0 {
return fmt.Errorf("error removing inter-network communication rule: %s", string(output))
}
}
}
return nil
}