diff --git a/libnetwork/drivers/bridge/bridge.go b/libnetwork/drivers/bridge/bridge.go index 6124ccb6cb..2e5a89afb1 100644 --- a/libnetwork/drivers/bridge/bridge.go +++ b/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} diff --git a/libnetwork/drivers/bridge/bridge_test.go b/libnetwork/drivers/bridge/bridge_test.go index 9aafe85d50..71de93de0b 100644 --- a/libnetwork/drivers/bridge/bridge_test.go +++ b/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) { diff --git a/libnetwork/endpoint.go b/libnetwork/endpoint.go index de7b652dc5..02c7ba3316 100644 --- a/libnetwork/endpoint.go +++ b/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 diff --git a/libnetwork/libnetwork_test.go b/libnetwork/libnetwork_test.go index 255261b5b8..f72b8c19ca 100644 --- a/libnetwork/libnetwork_test.go +++ b/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)() diff --git a/libnetwork/network.go b/libnetwork/network.go index 2584602849..00061ea016 100644 --- a/libnetwork/network.go +++ b/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 }