Sfoglia il codice sorgente

Allow IPv6 allocation post endpoint create

- Controlled by network option

Signed-off-by: Alessandro Boch <aboch@docker.com>
Alessandro Boch 9 anni fa
parent
commit
af7bc494f9

+ 15 - 6
libnetwork/drivers/bridge/bridge.go

@@ -450,6 +450,8 @@ func (c *networkConfiguration) processIPAM(id string, ipamV4Data, ipamV6Data []d
 	}
 
 	if len(ipamV6Data) > 0 {
+		c.AddressIPv6 = ipamV6Data[0].Pool
+
 		if ipamV6Data[0].Gateway != nil {
 			c.AddressIPv6 = types.GetIPNetCopy(ipamV6Data[0].Gateway)
 		}
@@ -959,13 +961,20 @@ func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo,
 	if endpoint.addrv6 == nil && config.EnableIPv6 {
 		var ip6 net.IP
 		network := n.bridge.bridgeIPv6
+		if config.AddressIPv6 != nil {
+			network = config.AddressIPv6
+		}
+
 		ones, _ := network.Mask.Size()
-		if ones <= 80 {
-			ip6 = make(net.IP, len(network.IP))
-			copy(ip6, network.IP)
-			for i, h := range endpoint.macAddress {
-				ip6[i+10] = h
-			}
+		if ones > 80 {
+			err = types.ForbiddenErrorf("Cannot self generate an IPv6 address on network %v: At least 48 host bits are needed.", network)
+			return err
+		}
+
+		ip6 = make(net.IP, len(network.IP))
+		copy(ip6, network.IP)
+		for i, h := range endpoint.macAddress {
+			ip6[i+10] = h
 		}
 
 		endpoint.addrv6 = &net.IPNet{IP: ip6, Mask: network.Mask}

+ 51 - 4
libnetwork/drivers/bridge/bridge_test.go

@@ -111,18 +111,35 @@ func TestCreateFullOptionsLabels(t *testing.T) {
 		t.Fatalf("Failed to setup driver config: %v", err)
 	}
 
+	bndIPs := "127.0.0.1"
+	nwV6s := "2100:2400:2600:2700:2800::/80"
+	gwV6s := "2100:2400:2600:2700:2800::25/80"
+	nwV6, _ := types.ParseCIDR(nwV6s)
+	gwV6, _ := types.ParseCIDR(gwV6s)
+
 	labels := map[string]string{
-		BridgeName:          "cu",
+		BridgeName:          DefaultBridgeName,
+		DefaultBridge:       "true",
 		netlabel.EnableIPv6: "true",
 		EnableICC:           "true",
 		EnableIPMasquerade:  "true",
-		DefaultBindingIP:    "127.0.0.1",
+		DefaultBindingIP:    bndIPs,
 	}
 
 	netOption := make(map[string]interface{})
 	netOption[netlabel.GenericData] = labels
 
-	err := d.CreateNetwork("dummy", netOption, getIPv4Data(t), nil)
+	ipdList := getIPv4Data(t)
+	ipd6List := []driverapi.IPAMData{
+		driverapi.IPAMData{
+			Pool: nwV6,
+			AuxAddresses: map[string]*net.IPNet{
+				DefaultGatewayV6AuxKey: gwV6,
+			},
+		},
+	}
+
+	err := d.CreateNetwork("dummy", netOption, ipdList, ipd6List)
 	if err != nil {
 		t.Fatalf("Failed to create bridge: %v", err)
 	}
@@ -132,7 +149,7 @@ func TestCreateFullOptionsLabels(t *testing.T) {
 		t.Fatalf("Cannot find dummy network in bridge driver")
 	}
 
-	if nw.config.BridgeName != "cu" {
+	if nw.config.BridgeName != DefaultBridgeName {
 		t.Fatalf("incongruent name in bridge network")
 	}
 
@@ -147,6 +164,36 @@ func TestCreateFullOptionsLabels(t *testing.T) {
 	if !nw.config.EnableIPMasquerade {
 		t.Fatalf("incongruent EnableIPMasquerade in bridge network")
 	}
+
+	bndIP := net.ParseIP(bndIPs)
+	if !bndIP.Equal(nw.config.DefaultBindingIP) {
+		t.Fatalf("Unexpected: %v", nw.config.DefaultBindingIP)
+	}
+
+	if !types.CompareIPNet(nw.config.AddressIPv6, nwV6) {
+		t.Fatalf("Unexpected: %v", nw.config.AddressIPv6)
+	}
+
+	if !gwV6.IP.Equal(nw.config.DefaultGatewayIPv6) {
+		t.Fatalf("Unexpected: %v", nw.config.DefaultGatewayIPv6)
+	}
+
+	// In short here we are testing --fixed-cidr-v6 daemon option
+	// plus --mac-address run option
+	mac, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff")
+	epOptions := map[string]interface{}{netlabel.MacAddress: mac}
+	te := newTestEndpoint(ipdList[0].Pool, 20)
+	err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !nwV6.Contains(te.Interface().AddressIPv6().IP) {
+		t.Fatalf("endpoint got assigned address outside of container network(%s): %s", nwV6.String(), te.Interface().AddressIPv6())
+	}
+	if te.Interface().AddressIPv6().IP.String() != "2100:2400:2600:2700:2800:aabb:ccdd:eeff" {
+		t.Fatalf("Unexpected endpoint IPv6 address: %v", te.Interface().AddressIPv6().IP)
+	}
 }
 
 func TestCreate(t *testing.T) {

+ 17 - 6
libnetwork/endpoint.go

@@ -737,7 +737,7 @@ func (ep *endpoint) DataScope() string {
 	return ep.getNetwork().DataScope()
 }
 
-func (ep *endpoint) assignAddress() error {
+func (ep *endpoint) assignAddress(assignIPv4, assignIPv6 bool) error {
 	var (
 		ipam ipamapi.Ipam
 		err  error
@@ -754,11 +754,18 @@ func (ep *endpoint) assignAddress() error {
 	if err != nil {
 		return err
 	}
-	err = ep.assignAddressVersion(4, ipam)
-	if err != nil {
-		return err
+
+	if assignIPv4 {
+		if err = ep.assignAddressVersion(4, ipam); err != nil {
+			return err
+		}
 	}
-	return ep.assignAddressVersion(6, ipam)
+
+	if assignIPv6 {
+		err = ep.assignAddressVersion(6, ipam)
+	}
+
+	return err
 }
 
 func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
@@ -787,7 +794,11 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
 	}
 
 	for _, d := range ipInfo {
-		addr, _, err := ipam.RequestAddress(d.PoolID, nil, nil)
+		var prefIP net.IP
+		if *address != nil {
+			prefIP = (*address).IP
+		}
+		addr, _, err := ipam.RequestAddress(d.PoolID, prefIP, nil)
 		if err == nil {
 			ep.Lock()
 			*address = addr

+ 54 - 0
libnetwork/libnetwork_test.go

@@ -6,6 +6,7 @@ import (
 	"flag"
 	"fmt"
 	"io/ioutil"
+	"net"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -313,6 +314,59 @@ func TestBridge(t *testing.T) {
 	}
 }
 
+// Testing IPV6 from MAC address
+func TestBridgeIpv6FromMac(t *testing.T) {
+	if !testutils.IsRunningInContainer() {
+		defer testutils.SetupTestOSContext(t)()
+	}
+
+	netOption := options.Generic{
+		netlabel.GenericData: options.Generic{
+			"BridgeName":         "testipv6mac",
+			"EnableIPv6":         true,
+			"EnableICC":          true,
+			"EnableIPMasquerade": true,
+		},
+	}
+	ipamV4ConfList := []*libnetwork.IpamConf{&libnetwork.IpamConf{PreferredPool: "192.168.100.0/24", Gateway: "192.168.100.1"}}
+	ipamV6ConfList := []*libnetwork.IpamConf{&libnetwork.IpamConf{PreferredPool: "fe90::/64", Gateway: "fe90::22"}}
+
+	network, err := controller.NewNetwork(bridgeNetType, "testipv6mac",
+		libnetwork.NetworkOptionGeneric(netOption),
+		libnetwork.NetworkOptionIpam(ipamapi.DefaultIPAM, "", ipamV4ConfList, ipamV6ConfList),
+		libnetwork.NetworkOptionDeferIPv6Alloc(true))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	mac := net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
+	epOption := options.Generic{netlabel.MacAddress: mac}
+
+	ep, err := network.CreateEndpoint("testep", libnetwork.EndpointOptionGeneric(epOption))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	iface := ep.Info().Iface()
+	if !bytes.Equal(iface.MacAddress(), mac) {
+		t.Fatalf("Unexpected mac address: %v", iface.MacAddress())
+	}
+
+	ip, expIP, _ := net.ParseCIDR("fe90::aabb:ccdd:eeff/64")
+	expIP.IP = ip
+	if !types.CompareIPNet(expIP, iface.AddressIPv6()) {
+		t.Fatalf("Expected %v. Got: %v", expIP, iface.AddressIPv6())
+	}
+
+	if err := ep.Delete(); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := network.Delete(); err != nil {
+		t.Fatal(err)
+	}
+}
+
 func TestUnknownDriver(t *testing.T) {
 	if !testutils.IsRunningInContainer() {
 		defer testutils.SetupTestOSContext(t)()

+ 21 - 1
libnetwork/network.go

@@ -152,6 +152,7 @@ type network struct {
 	ipamV4Info   []*IpamInfo
 	ipamV6Info   []*IpamInfo
 	enableIPv6   bool
+	postIPv6     bool
 	epCnt        *endpointCnt
 	generic      options.Generic
 	dbIndex      uint64
@@ -298,6 +299,7 @@ func (n *network) CopyTo(o datastore.KVObject) error {
 	dstN.ipamType = n.ipamType
 	dstN.enableIPv6 = n.enableIPv6
 	dstN.persist = n.persist
+	dstN.postIPv6 = n.postIPv6
 	dstN.dbIndex = n.dbIndex
 	dstN.dbExists = n.dbExists
 	dstN.drvOnce = n.drvOnce
@@ -358,6 +360,7 @@ func (n *network) MarshalJSON() ([]byte, error) {
 		netMap["generic"] = n.generic
 	}
 	netMap["persist"] = n.persist
+	netMap["postIPv6"] = n.postIPv6
 	if len(n.ipamV4Config) > 0 {
 		ics, err := json.Marshal(n.ipamV4Config)
 		if err != nil {
@@ -418,6 +421,9 @@ func (n *network) UnmarshalJSON(b []byte) (err error) {
 	if v, ok := netMap["persist"]; ok {
 		n.persist = v.(bool)
 	}
+	if v, ok := netMap["postIPv6"]; ok {
+		n.postIPv6 = v.(bool)
+	}
 	if v, ok := netMap["ipamType"]; ok {
 		n.ipamType = v.(string)
 	} else {
@@ -505,6 +511,16 @@ func NetworkOptionDriverOpts(opts map[string]string) NetworkOption {
 	}
 }
 
+// NetworkOptionDeferIPv6Alloc instructs the network to defer the IPV6 address allocation until after the endpoint has been created
+// It is being provided to support the specific docker daemon flags where user can deterministically assign an IPv6 address
+// to a container as combination of fixed-cidr-v6 + mac-address
+// TODO: Remove this option setter once we support endpoint ipam options
+func NetworkOptionDeferIPv6Alloc(enable bool) NetworkOption {
+	return func(n *network) {
+		n.postIPv6 = enable
+	}
+}
+
 func (n *network) processOptions(options ...NetworkOption) {
 	for _, opt := range options {
 		if opt != nil {
@@ -655,7 +671,7 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi
 
 	ep.processOptions(options...)
 
-	if err = ep.assignAddress(); err != nil {
+	if err = ep.assignAddress(true, !n.postIPv6); err != nil {
 		return nil, err
 	}
 	defer func() {
@@ -675,6 +691,10 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi
 		}
 	}()
 
+	if err = ep.assignAddress(false, n.postIPv6); err != nil {
+		return nil, err
+	}
+
 	if err = n.getController().updateToStore(ep); err != nil {
 		return nil, err
 	}