Pārlūkot izejas kodu

Issue #88: Handle default v4/v6 gw setting

- Basically this is porting docker PR #9381 to libnetwork
- Added a Config.Validate() method where to consolidate
  a priori validation of bridge configuration
- Have bridgeInterface store the current v4/v6 default gateways
- Introduced two setupStep functions to set the requested def gateways

Signed-off-by: Alessandro Boch <aboch@docker.com>
Alessandro Boch 10 gadi atpakaļ
vecāks
revīzija
35693a1a47

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

@@ -40,6 +40,8 @@ type Configuration struct {
 	EnableIPForwarding    bool
 	AllowNonDefaultBridge bool
 	Mtu                   int
+	DefaultGatewayIPv4    net.IP
+	DefaultGatewayIPv6    net.IP
 }
 
 // EndpointConfiguration represents the user specified configuration for the sandbox endpoint
@@ -76,6 +78,46 @@ func New() (string, driverapi.Driver) {
 	return networkType, &driver{}
 }
 
+// Validate performs a static validation on the configuration parameters.
+// Whatever can be assessed a priori before attempting any programming.
+func (c *Configuration) Validate() error {
+	if c.Mtu < 0 {
+		return ErrInvalidMtu
+	}
+
+	// If bridge v4 subnet is specified
+	if c.AddressIPv4 != nil {
+		// If Container restricted subnet is specified, it must be a subset of bridge subnet
+		if c.FixedCIDR != nil {
+			// Check Network address
+			if !c.AddressIPv4.Contains(c.FixedCIDR.IP) {
+				return ErrInvalidContainerSubnet
+			}
+			// Check it is effectively a subset
+			brNetLen, _ := c.AddressIPv4.Mask.Size()
+			cnNetLen, _ := c.FixedCIDR.Mask.Size()
+			if brNetLen > cnNetLen {
+				return ErrInvalidContainerSubnet
+			}
+		}
+		// If default gw is specified, it must be part of bridge subnet
+		if c.DefaultGatewayIPv4 != nil {
+			if !c.AddressIPv4.Contains(c.DefaultGatewayIPv4) {
+				return ErrInvalidGateway
+			}
+		}
+	}
+
+	// If default v6 gw is specified, FixedCIDRv6 must be specified and gw must belong to FixedCIDRv6 subnet
+	if c.EnableIPv6 && c.DefaultGatewayIPv6 != nil {
+		if c.FixedCIDRv6 == nil || !c.FixedCIDRv6.Contains(c.DefaultGatewayIPv6) {
+			return ErrInvalidGateway
+		}
+	}
+
+	return nil
+}
+
 func (n *bridgeNetwork) getEndpoint(eid types.UUID) (string, *bridgeEndpoint, error) {
 	n.Lock()
 	defer n.Unlock()
@@ -114,7 +156,12 @@ func (d *driver) Config(option interface{}) error {
 		config = opt
 	}
 
+	if err := config.Validate(); err != nil {
+		return err
+	}
+
 	d.config = config
+
 	return nil
 }
 
@@ -192,6 +239,12 @@ func (d *driver) CreateNetwork(id types.UUID, option interface{}) error {
 
 		// Setup IP forwarding.
 		{config.EnableIPForwarding, setupIPForwarding},
+
+		// Setup DefaultGatewayIPv4
+		{config.DefaultGatewayIPv4 != nil, setupGatewayIPv4},
+
+		// Setup DefaultGatewayIPv6
+		{config.DefaultGatewayIPv6 != nil, setupGatewayIPv6},
 	} {
 		if step.Condition {
 			bridgeSetup.queueStep(step.Fn)
@@ -295,6 +348,7 @@ func (d *driver) CreateEndpoint(nid, eid types.UUID, sboxKey string, epOptions i
 	// Try to convert the options to endpoint configuration
 	epConfig, err := parseEndpointOptions(epOptions)
 	if err != nil {
+		n.Unlock()
 		return nil, err
 	}
 
@@ -408,10 +462,12 @@ func (d *driver) CreateEndpoint(nid, eid types.UUID, sboxKey string, epOptions i
 
 	// Generate the sandbox info to return
 	sinfo := &sandbox.Info{Interfaces: []*sandbox.Interface{intf}}
-	sinfo.Gateway = n.bridge.bridgeIPv4.IP
+
+	// Set the default gateway(s) for the sandbox
+	sinfo.Gateway = n.bridge.gatewayIPv4
 	if config.EnableIPv6 {
 		intf.AddressIPv6 = ipv6Addr
-		sinfo.GatewayIPv6 = n.bridge.bridgeIPv6.IP
+		sinfo.GatewayIPv6 = n.bridge.gatewayIPv6
 	}
 
 	return sinfo, nil

+ 129 - 0
libnetwork/drivers/bridge/bridge_test.go

@@ -91,3 +91,132 @@ func TestCreateLinkWithOptions(t *testing.T) {
 		t.Fatalf("Failed to parse and program endpoint configuration")
 	}
 }
+
+func TestValidateConfig(t *testing.T) {
+
+	// Test mtu
+	c := Configuration{Mtu: -2}
+	err := c.Validate()
+	if err == nil {
+		t.Fatalf("Failed to detect invalid MTU number")
+	}
+
+	c.Mtu = 9000
+	err = c.Validate()
+	if err != nil {
+		t.Fatalf("unexpected validation error on MTU number")
+	}
+
+	// Bridge network
+	_, network, _ := net.ParseCIDR("172.28.0.0/16")
+
+	// Test FixedCIDR
+	_, containerSubnet, _ := net.ParseCIDR("172.27.0.0/16")
+	c = Configuration{
+		AddressIPv4: network,
+		FixedCIDR:   containerSubnet,
+	}
+
+	err = c.Validate()
+	if err == nil {
+		t.Fatalf("Failed to detect invalid FixedCIDR network")
+	}
+
+	_, containerSubnet, _ = net.ParseCIDR("172.28.0.0/16")
+	c.FixedCIDR = containerSubnet
+	err = c.Validate()
+	if err != nil {
+		t.Fatalf("Unexpected validation error on FixedCIDR network")
+	}
+
+	_, containerSubnet, _ = net.ParseCIDR("172.28.0.0/15")
+	c.FixedCIDR = containerSubnet
+	err = c.Validate()
+	if err == nil {
+		t.Fatalf("Failed to detect invalid FixedCIDR network")
+	}
+
+	_, containerSubnet, _ = net.ParseCIDR("172.28.0.0/17")
+	c.FixedCIDR = containerSubnet
+	err = c.Validate()
+	if err != nil {
+		t.Fatalf("Unexpected validation error on FixedCIDR network")
+	}
+
+	// Test v4 gw
+	c.DefaultGatewayIPv4 = net.ParseIP("172.27.30.234")
+	err = c.Validate()
+	if err == nil {
+		t.Fatalf("Failed to detect invalid default gateway")
+	}
+
+	c.DefaultGatewayIPv4 = net.ParseIP("172.28.30.234")
+	err = c.Validate()
+	if err != nil {
+		t.Fatalf("Unexpected validation error on default gateway")
+	}
+
+	// Test v6 gw
+	_, containerSubnet, _ = net.ParseCIDR("2001:1234:ae:b004::/64")
+	c = Configuration{
+		EnableIPv6:         true,
+		FixedCIDRv6:        containerSubnet,
+		DefaultGatewayIPv6: net.ParseIP("2001:1234:ac:b004::bad:a55"),
+	}
+	err = c.Validate()
+	if err == nil {
+		t.Fatalf("Failed to detect invalid v6 default gateway")
+	}
+
+	c.DefaultGatewayIPv6 = net.ParseIP("2001:1234:ae:b004::bad:a55")
+	err = c.Validate()
+	if err != nil {
+		t.Fatalf("Unexpected validation error on v6 default gateway")
+	}
+
+	c.FixedCIDRv6 = nil
+	err = c.Validate()
+	if err == nil {
+		t.Fatalf("Failed to detect invalid v6 default gateway")
+	}
+}
+
+func TestSetDefaultGw(t *testing.T) {
+	defer netutils.SetupTestNetNS(t)()
+	_, d := New()
+
+	_, subnetv6, _ := net.ParseCIDR("2001:db8:ea9:9abc:b0c4::/80")
+	gw4 := bridgeNetworks[0].IP.To4()
+	gw4[3] = 254
+	gw6 := net.ParseIP("2001:db8:ea9:9abc:b0c4::254")
+
+	config := &Configuration{
+		BridgeName:         DefaultBridgeName,
+		EnableIPv6:         true,
+		FixedCIDRv6:        subnetv6,
+		DefaultGatewayIPv4: gw4,
+		DefaultGatewayIPv6: gw6,
+	}
+
+	if err := d.Config(config); err != nil {
+		t.Fatalf("Failed to setup driver config: %v", err)
+	}
+
+	err := d.CreateNetwork("dummy", nil)
+	if err != nil {
+		t.Fatalf("Failed to create bridge: %v", err)
+	}
+
+	sinfo, err := d.CreateEndpoint("dummy", "ep", "sb2", nil)
+	if err != nil {
+		t.Fatalf("Failed to create endpoint: %v", err)
+	}
+
+	if !gw4.Equal(sinfo.Gateway) {
+		t.Fatalf("Failed to configure default gateway. Expected %v. Found %v", gw4, sinfo.Gateway)
+	}
+
+	if !gw6.Equal(sinfo.GatewayIPv6) {
+		t.Fatalf("Failed to configure default gateway. Expected %v. Found %v", gw6, sinfo.GatewayIPv6)
+	}
+}

+ 9 - 0
libnetwork/drivers/bridge/error.go

@@ -24,6 +24,15 @@ var (
 
 	// ErrNoIPAddr error is returned when bridge has no IPv4 address configured.
 	ErrNoIPAddr = errors.New("bridge has no IPv4 address configured")
+
+	// ErrInvalidGateway is returned when the user provided default gateway (v4/v6) is not not valid.
+	ErrInvalidGateway = errors.New("default gateway ip must be part of the network")
+
+	// ErrInvalidContainerSubnet is returned when the container subnet (FixedCIDR) is not valid.
+	ErrInvalidContainerSubnet = errors.New("container subnet must be a subset of bridge network")
+
+	// ErrInvalidMtu is returned when the user provided MTU is not valid
+	ErrInvalidMtu = errors.New("invalid MTU number")
 )
 
 // ActiveEndpointsError is returned when there are

+ 5 - 3
libnetwork/drivers/bridge/interface.go

@@ -14,9 +14,11 @@ const (
 
 // Interface models the bridge network device.
 type bridgeInterface struct {
-	Link       netlink.Link
-	bridgeIPv4 *net.IPNet
-	bridgeIPv6 *net.IPNet
+	Link        netlink.Link
+	bridgeIPv4  *net.IPNet
+	bridgeIPv6  *net.IPNet
+	gatewayIPv4 net.IP
+	gatewayIPv6 net.IP
 }
 
 // newInterface creates a new bridge interface structure. It attempts to find

+ 4 - 3
libnetwork/drivers/bridge/network_test.go

@@ -17,7 +17,8 @@ func TestLinkCreate(t *testing.T) {
 	config := &Configuration{
 		BridgeName: DefaultBridgeName,
 		Mtu:        mtu,
-		EnableIPv6: true}
+		EnableIPv6: true,
+	}
 	if err := d.Config(config); err != nil {
 		t.Fatalf("Failed to setup driver config: %v", err)
 	}
@@ -97,12 +98,12 @@ func TestLinkCreate(t *testing.T) {
 		t.Fatalf("IP %s is not a valid ip in the subnet %s", ip6.String(), bridgeIPv6.String())
 	}
 
-	if sinfo.Gateway.String() != n.bridge.bridgeIPv4.IP.String() {
+	if !sinfo.Gateway.Equal(n.bridge.bridgeIPv4.IP) {
 		t.Fatalf("Invalid default gateway. Expected %s. Got %s", n.bridge.bridgeIPv4.IP.String(),
 			sinfo.Gateway.String())
 	}
 
-	if sinfo.GatewayIPv6.String() != n.bridge.bridgeIPv6.IP.String() {
+	if !sinfo.GatewayIPv6.Equal(n.bridge.bridgeIPv6.IP) {
 		t.Fatalf("Invalid default gateway for IPv6. Expected %s. Got %s", n.bridge.bridgeIPv6.IP.String(),
 			sinfo.GatewayIPv6.String())
 	}

+ 16 - 0
libnetwork/drivers/bridge/setup_ipv4.go

@@ -51,7 +51,9 @@ func setupBridgeIPv4(config *Configuration, i *bridgeInterface) error {
 		return &IPv4AddrAddError{ip: bridgeIPv4, err: err}
 	}
 
+	// Store bridge network and default gateway
 	i.bridgeIPv4 = bridgeIPv4
+	i.gatewayIPv4 = i.bridgeIPv4.IP
 
 	return nil
 }
@@ -81,3 +83,17 @@ func electBridgeIPv4(config *Configuration) (*net.IPNet, error) {
 
 	return nil, IPv4AddrRangeError(config.BridgeName)
 }
+
+func setupGatewayIPv4(config *Configuration, i *bridgeInterface) error {
+	if !i.bridgeIPv4.Contains(config.DefaultGatewayIPv4) {
+		return ErrInvalidGateway
+	}
+	if _, err := ipAllocator.RequestIP(i.bridgeIPv4, config.DefaultGatewayIPv4); err != nil {
+		return err
+	}
+
+	// Store requested default gateway
+	i.gatewayIPv4 = config.DefaultGatewayIPv4
+
+	return nil
+}

+ 22 - 0
libnetwork/drivers/bridge/setup_ipv4_test.go

@@ -76,3 +76,25 @@ func TestSetupBridgeIPv4Auto(t *testing.T) {
 		t.Fatalf("Bridge device does not have the automatic IPv4 address %v", bridgeNetworks[0].String())
 	}
 }
+
+func TestSetupGatewayIPv4(t *testing.T) {
+	defer netutils.SetupTestNetNS(t)()
+
+	ip, nw, _ := net.ParseCIDR("192.168.0.24/16")
+	nw.IP = ip
+	gw := net.ParseIP("192.168.0.254")
+
+	config := &Configuration{
+		BridgeName:         DefaultBridgeName,
+		DefaultGatewayIPv4: gw}
+
+	br := &bridgeInterface{bridgeIPv4: nw}
+
+	if err := setupGatewayIPv4(config, br); err != nil {
+		t.Fatalf("Set Default Gateway failed: %v", err)
+	}
+
+	if !gw.Equal(br.gatewayIPv4) {
+		t.Fatalf("Set Default Gateway failed. Expected %v, Found %v", gw, br.gatewayIPv4)
+	}
+}

+ 19 - 0
libnetwork/drivers/bridge/setup_ipv6.go

@@ -33,7 +33,26 @@ func setupBridgeIPv6(config *Configuration, i *bridgeInterface) error {
 		return &IPv6AddrAddError{ip: bridgeIPv6, err: err}
 	}
 
+	// Store bridge network and default gateway
 	i.bridgeIPv6 = bridgeIPv6
+	i.gatewayIPv6 = i.bridgeIPv6.IP
+
+	return nil
+}
+
+func setupGatewayIPv6(config *Configuration, i *bridgeInterface) error {
+	if config.FixedCIDRv6 == nil {
+		return ErrInvalidContainerSubnet
+	}
+	if !config.FixedCIDRv6.Contains(config.DefaultGatewayIPv6) {
+		return ErrInvalidGateway
+	}
+	if _, err := ipAllocator.RequestIP(config.FixedCIDRv6, config.DefaultGatewayIPv6); err != nil {
+		return err
+	}
+
+	// Store requested default gateway
+	i.gatewayIPv6 = config.DefaultGatewayIPv6
 
 	return nil
 }

+ 23 - 0
libnetwork/drivers/bridge/setup_ipv6_test.go

@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"fmt"
 	"io/ioutil"
+	"net"
 	"testing"
 
 	"github.com/docker/libnetwork/netutils"
@@ -45,3 +46,25 @@ func TestSetupIPv6(t *testing.T) {
 	}
 
 }
+
+func TestSetupGatewayIPv6(t *testing.T) {
+	defer netutils.SetupTestNetNS(t)()
+
+	_, nw, _ := net.ParseCIDR("2001:db8:ea9:9abc:ffff::/80")
+	gw := net.ParseIP("2001:db8:ea9:9abc:ffff::254")
+
+	config := &Configuration{
+		BridgeName:         DefaultBridgeName,
+		FixedCIDRv6:        nw,
+		DefaultGatewayIPv6: gw}
+
+	br := &bridgeInterface{}
+
+	if err := setupGatewayIPv6(config, br); err != nil {
+		t.Fatalf("Set Default Gateway failed: %v", err)
+	}
+
+	if !gw.Equal(br.gatewayIPv6) {
+		t.Fatalf("Set Default Gateway failed. Expected %v, Found %v", gw, br.gatewayIPv6)
+	}
+}