- Controlled by network option
Signed-off-by: Alessandro Boch <aboch@docker.com>
| ... | ... |
@@ -450,6 +450,8 @@ func (c *networkConfiguration) processIPAM(id string, ipamV4Data, ipamV6Data []d |
| 450 | 450 |
} |
| 451 | 451 |
|
| 452 | 452 |
if len(ipamV6Data) > 0 {
|
| 453 |
+ c.AddressIPv6 = ipamV6Data[0].Pool |
|
| 454 |
+ |
|
| 453 | 455 |
if ipamV6Data[0].Gateway != nil {
|
| 454 | 456 |
c.AddressIPv6 = types.GetIPNetCopy(ipamV6Data[0].Gateway) |
| 455 | 457 |
} |
| ... | ... |
@@ -959,13 +961,20 @@ func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, |
| 959 | 959 |
if endpoint.addrv6 == nil && config.EnableIPv6 {
|
| 960 | 960 |
var ip6 net.IP |
| 961 | 961 |
network := n.bridge.bridgeIPv6 |
| 962 |
+ if config.AddressIPv6 != nil {
|
|
| 963 |
+ network = config.AddressIPv6 |
|
| 964 |
+ } |
|
| 965 |
+ |
|
| 962 | 966 |
ones, _ := network.Mask.Size() |
| 963 |
- if ones <= 80 {
|
|
| 964 |
- ip6 = make(net.IP, len(network.IP)) |
|
| 965 |
- copy(ip6, network.IP) |
|
| 966 |
- for i, h := range endpoint.macAddress {
|
|
| 967 |
- ip6[i+10] = h |
|
| 968 |
- } |
|
| 967 |
+ if ones > 80 {
|
|
| 968 |
+ err = types.ForbiddenErrorf("Cannot self generate an IPv6 address on network %v: At least 48 host bits are needed.", network)
|
|
| 969 |
+ return err |
|
| 970 |
+ } |
|
| 971 |
+ |
|
| 972 |
+ ip6 = make(net.IP, len(network.IP)) |
|
| 973 |
+ copy(ip6, network.IP) |
|
| 974 |
+ for i, h := range endpoint.macAddress {
|
|
| 975 |
+ ip6[i+10] = h |
|
| 969 | 976 |
} |
| 970 | 977 |
|
| 971 | 978 |
endpoint.addrv6 = &net.IPNet{IP: ip6, Mask: network.Mask}
|
| ... | ... |
@@ -111,18 +111,35 @@ func TestCreateFullOptionsLabels(t *testing.T) {
|
| 111 | 111 |
t.Fatalf("Failed to setup driver config: %v", err)
|
| 112 | 112 |
} |
| 113 | 113 |
|
| 114 |
+ bndIPs := "127.0.0.1" |
|
| 115 |
+ nwV6s := "2100:2400:2600:2700:2800::/80" |
|
| 116 |
+ gwV6s := "2100:2400:2600:2700:2800::25/80" |
|
| 117 |
+ nwV6, _ := types.ParseCIDR(nwV6s) |
|
| 118 |
+ gwV6, _ := types.ParseCIDR(gwV6s) |
|
| 119 |
+ |
|
| 114 | 120 |
labels := map[string]string{
|
| 115 |
- BridgeName: "cu", |
|
| 121 |
+ BridgeName: DefaultBridgeName, |
|
| 122 |
+ DefaultBridge: "true", |
|
| 116 | 123 |
netlabel.EnableIPv6: "true", |
| 117 | 124 |
EnableICC: "true", |
| 118 | 125 |
EnableIPMasquerade: "true", |
| 119 |
- DefaultBindingIP: "127.0.0.1", |
|
| 126 |
+ DefaultBindingIP: bndIPs, |
|
| 120 | 127 |
} |
| 121 | 128 |
|
| 122 | 129 |
netOption := make(map[string]interface{})
|
| 123 | 130 |
netOption[netlabel.GenericData] = labels |
| 124 | 131 |
|
| 125 |
- err := d.CreateNetwork("dummy", netOption, getIPv4Data(t), nil)
|
|
| 132 |
+ ipdList := getIPv4Data(t) |
|
| 133 |
+ ipd6List := []driverapi.IPAMData{
|
|
| 134 |
+ driverapi.IPAMData{
|
|
| 135 |
+ Pool: nwV6, |
|
| 136 |
+ AuxAddresses: map[string]*net.IPNet{
|
|
| 137 |
+ DefaultGatewayV6AuxKey: gwV6, |
|
| 138 |
+ }, |
|
| 139 |
+ }, |
|
| 140 |
+ } |
|
| 141 |
+ |
|
| 142 |
+ err := d.CreateNetwork("dummy", netOption, ipdList, ipd6List)
|
|
| 126 | 143 |
if err != nil {
|
| 127 | 144 |
t.Fatalf("Failed to create bridge: %v", err)
|
| 128 | 145 |
} |
| ... | ... |
@@ -132,7 +149,7 @@ func TestCreateFullOptionsLabels(t *testing.T) {
|
| 132 | 132 |
t.Fatalf("Cannot find dummy network in bridge driver")
|
| 133 | 133 |
} |
| 134 | 134 |
|
| 135 |
- if nw.config.BridgeName != "cu" {
|
|
| 135 |
+ if nw.config.BridgeName != DefaultBridgeName {
|
|
| 136 | 136 |
t.Fatalf("incongruent name in bridge network")
|
| 137 | 137 |
} |
| 138 | 138 |
|
| ... | ... |
@@ -147,6 +164,36 @@ func TestCreateFullOptionsLabels(t *testing.T) {
|
| 147 | 147 |
if !nw.config.EnableIPMasquerade {
|
| 148 | 148 |
t.Fatalf("incongruent EnableIPMasquerade in bridge network")
|
| 149 | 149 |
} |
| 150 |
+ |
|
| 151 |
+ bndIP := net.ParseIP(bndIPs) |
|
| 152 |
+ if !bndIP.Equal(nw.config.DefaultBindingIP) {
|
|
| 153 |
+ t.Fatalf("Unexpected: %v", nw.config.DefaultBindingIP)
|
|
| 154 |
+ } |
|
| 155 |
+ |
|
| 156 |
+ if !types.CompareIPNet(nw.config.AddressIPv6, nwV6) {
|
|
| 157 |
+ t.Fatalf("Unexpected: %v", nw.config.AddressIPv6)
|
|
| 158 |
+ } |
|
| 159 |
+ |
|
| 160 |
+ if !gwV6.IP.Equal(nw.config.DefaultGatewayIPv6) {
|
|
| 161 |
+ t.Fatalf("Unexpected: %v", nw.config.DefaultGatewayIPv6)
|
|
| 162 |
+ } |
|
| 163 |
+ |
|
| 164 |
+ // In short here we are testing --fixed-cidr-v6 daemon option |
|
| 165 |
+ // plus --mac-address run option |
|
| 166 |
+ mac, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff")
|
|
| 167 |
+ epOptions := map[string]interface{}{netlabel.MacAddress: mac}
|
|
| 168 |
+ te := newTestEndpoint(ipdList[0].Pool, 20) |
|
| 169 |
+ err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions)
|
|
| 170 |
+ if err != nil {
|
|
| 171 |
+ t.Fatal(err) |
|
| 172 |
+ } |
|
| 173 |
+ |
|
| 174 |
+ if !nwV6.Contains(te.Interface().AddressIPv6().IP) {
|
|
| 175 |
+ t.Fatalf("endpoint got assigned address outside of container network(%s): %s", nwV6.String(), te.Interface().AddressIPv6())
|
|
| 176 |
+ } |
|
| 177 |
+ if te.Interface().AddressIPv6().IP.String() != "2100:2400:2600:2700:2800:aabb:ccdd:eeff" {
|
|
| 178 |
+ t.Fatalf("Unexpected endpoint IPv6 address: %v", te.Interface().AddressIPv6().IP)
|
|
| 179 |
+ } |
|
| 150 | 180 |
} |
| 151 | 181 |
|
| 152 | 182 |
func TestCreate(t *testing.T) {
|
| ... | ... |
@@ -737,7 +737,7 @@ func (ep *endpoint) DataScope() string {
|
| 737 | 737 |
return ep.getNetwork().DataScope() |
| 738 | 738 |
} |
| 739 | 739 |
|
| 740 |
-func (ep *endpoint) assignAddress() error {
|
|
| 740 |
+func (ep *endpoint) assignAddress(assignIPv4, assignIPv6 bool) error {
|
|
| 741 | 741 |
var ( |
| 742 | 742 |
ipam ipamapi.Ipam |
| 743 | 743 |
err error |
| ... | ... |
@@ -754,11 +754,18 @@ func (ep *endpoint) assignAddress() error {
|
| 754 | 754 |
if err != nil {
|
| 755 | 755 |
return err |
| 756 | 756 |
} |
| 757 |
- err = ep.assignAddressVersion(4, ipam) |
|
| 758 |
- if err != nil {
|
|
| 759 |
- return err |
|
| 757 |
+ |
|
| 758 |
+ if assignIPv4 {
|
|
| 759 |
+ if err = ep.assignAddressVersion(4, ipam); err != nil {
|
|
| 760 |
+ return err |
|
| 761 |
+ } |
|
| 760 | 762 |
} |
| 761 |
- return ep.assignAddressVersion(6, ipam) |
|
| 763 |
+ |
|
| 764 |
+ if assignIPv6 {
|
|
| 765 |
+ err = ep.assignAddressVersion(6, ipam) |
|
| 766 |
+ } |
|
| 767 |
+ |
|
| 768 |
+ return err |
|
| 762 | 769 |
} |
| 763 | 770 |
|
| 764 | 771 |
func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
|
| ... | ... |
@@ -787,7 +794,11 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
|
| 787 | 787 |
} |
| 788 | 788 |
|
| 789 | 789 |
for _, d := range ipInfo {
|
| 790 |
- addr, _, err := ipam.RequestAddress(d.PoolID, nil, nil) |
|
| 790 |
+ var prefIP net.IP |
|
| 791 |
+ if *address != nil {
|
|
| 792 |
+ prefIP = (*address).IP |
|
| 793 |
+ } |
|
| 794 |
+ addr, _, err := ipam.RequestAddress(d.PoolID, prefIP, nil) |
|
| 791 | 795 |
if err == nil {
|
| 792 | 796 |
ep.Lock() |
| 793 | 797 |
*address = addr |
| ... | ... |
@@ -6,6 +6,7 @@ import ( |
| 6 | 6 |
"flag" |
| 7 | 7 |
"fmt" |
| 8 | 8 |
"io/ioutil" |
| 9 |
+ "net" |
|
| 9 | 10 |
"net/http" |
| 10 | 11 |
"net/http/httptest" |
| 11 | 12 |
"os" |
| ... | ... |
@@ -313,6 +314,59 @@ func TestBridge(t *testing.T) {
|
| 313 | 313 |
} |
| 314 | 314 |
} |
| 315 | 315 |
|
| 316 |
+// Testing IPV6 from MAC address |
|
| 317 |
+func TestBridgeIpv6FromMac(t *testing.T) {
|
|
| 318 |
+ if !testutils.IsRunningInContainer() {
|
|
| 319 |
+ defer testutils.SetupTestOSContext(t)() |
|
| 320 |
+ } |
|
| 321 |
+ |
|
| 322 |
+ netOption := options.Generic{
|
|
| 323 |
+ netlabel.GenericData: options.Generic{
|
|
| 324 |
+ "BridgeName": "testipv6mac", |
|
| 325 |
+ "EnableIPv6": true, |
|
| 326 |
+ "EnableICC": true, |
|
| 327 |
+ "EnableIPMasquerade": true, |
|
| 328 |
+ }, |
|
| 329 |
+ } |
|
| 330 |
+ ipamV4ConfList := []*libnetwork.IpamConf{&libnetwork.IpamConf{PreferredPool: "192.168.100.0/24", Gateway: "192.168.100.1"}}
|
|
| 331 |
+ ipamV6ConfList := []*libnetwork.IpamConf{&libnetwork.IpamConf{PreferredPool: "fe90::/64", Gateway: "fe90::22"}}
|
|
| 332 |
+ |
|
| 333 |
+ network, err := controller.NewNetwork(bridgeNetType, "testipv6mac", |
|
| 334 |
+ libnetwork.NetworkOptionGeneric(netOption), |
|
| 335 |
+ libnetwork.NetworkOptionIpam(ipamapi.DefaultIPAM, "", ipamV4ConfList, ipamV6ConfList), |
|
| 336 |
+ libnetwork.NetworkOptionDeferIPv6Alloc(true)) |
|
| 337 |
+ if err != nil {
|
|
| 338 |
+ t.Fatal(err) |
|
| 339 |
+ } |
|
| 340 |
+ |
|
| 341 |
+ mac := net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
|
|
| 342 |
+ epOption := options.Generic{netlabel.MacAddress: mac}
|
|
| 343 |
+ |
|
| 344 |
+ ep, err := network.CreateEndpoint("testep", libnetwork.EndpointOptionGeneric(epOption))
|
|
| 345 |
+ if err != nil {
|
|
| 346 |
+ t.Fatal(err) |
|
| 347 |
+ } |
|
| 348 |
+ |
|
| 349 |
+ iface := ep.Info().Iface() |
|
| 350 |
+ if !bytes.Equal(iface.MacAddress(), mac) {
|
|
| 351 |
+ t.Fatalf("Unexpected mac address: %v", iface.MacAddress())
|
|
| 352 |
+ } |
|
| 353 |
+ |
|
| 354 |
+ ip, expIP, _ := net.ParseCIDR("fe90::aabb:ccdd:eeff/64")
|
|
| 355 |
+ expIP.IP = ip |
|
| 356 |
+ if !types.CompareIPNet(expIP, iface.AddressIPv6()) {
|
|
| 357 |
+ t.Fatalf("Expected %v. Got: %v", expIP, iface.AddressIPv6())
|
|
| 358 |
+ } |
|
| 359 |
+ |
|
| 360 |
+ if err := ep.Delete(); err != nil {
|
|
| 361 |
+ t.Fatal(err) |
|
| 362 |
+ } |
|
| 363 |
+ |
|
| 364 |
+ if err := network.Delete(); err != nil {
|
|
| 365 |
+ t.Fatal(err) |
|
| 366 |
+ } |
|
| 367 |
+} |
|
| 368 |
+ |
|
| 316 | 369 |
func TestUnknownDriver(t *testing.T) {
|
| 317 | 370 |
if !testutils.IsRunningInContainer() {
|
| 318 | 371 |
defer testutils.SetupTestOSContext(t)() |
| ... | ... |
@@ -152,6 +152,7 @@ type network struct {
|
| 152 | 152 |
ipamV4Info []*IpamInfo |
| 153 | 153 |
ipamV6Info []*IpamInfo |
| 154 | 154 |
enableIPv6 bool |
| 155 |
+ postIPv6 bool |
|
| 155 | 156 |
epCnt *endpointCnt |
| 156 | 157 |
generic options.Generic |
| 157 | 158 |
dbIndex uint64 |
| ... | ... |
@@ -298,6 +299,7 @@ func (n *network) CopyTo(o datastore.KVObject) error {
|
| 298 | 298 |
dstN.ipamType = n.ipamType |
| 299 | 299 |
dstN.enableIPv6 = n.enableIPv6 |
| 300 | 300 |
dstN.persist = n.persist |
| 301 |
+ dstN.postIPv6 = n.postIPv6 |
|
| 301 | 302 |
dstN.dbIndex = n.dbIndex |
| 302 | 303 |
dstN.dbExists = n.dbExists |
| 303 | 304 |
dstN.drvOnce = n.drvOnce |
| ... | ... |
@@ -358,6 +360,7 @@ func (n *network) MarshalJSON() ([]byte, error) {
|
| 358 | 358 |
netMap["generic"] = n.generic |
| 359 | 359 |
} |
| 360 | 360 |
netMap["persist"] = n.persist |
| 361 |
+ netMap["postIPv6"] = n.postIPv6 |
|
| 361 | 362 |
if len(n.ipamV4Config) > 0 {
|
| 362 | 363 |
ics, err := json.Marshal(n.ipamV4Config) |
| 363 | 364 |
if err != nil {
|
| ... | ... |
@@ -418,6 +421,9 @@ func (n *network) UnmarshalJSON(b []byte) (err error) {
|
| 418 | 418 |
if v, ok := netMap["persist"]; ok {
|
| 419 | 419 |
n.persist = v.(bool) |
| 420 | 420 |
} |
| 421 |
+ if v, ok := netMap["postIPv6"]; ok {
|
|
| 422 |
+ n.postIPv6 = v.(bool) |
|
| 423 |
+ } |
|
| 421 | 424 |
if v, ok := netMap["ipamType"]; ok {
|
| 422 | 425 |
n.ipamType = v.(string) |
| 423 | 426 |
} else {
|
| ... | ... |
@@ -505,6 +511,16 @@ func NetworkOptionDriverOpts(opts map[string]string) NetworkOption {
|
| 505 | 505 |
} |
| 506 | 506 |
} |
| 507 | 507 |
|
| 508 |
+// NetworkOptionDeferIPv6Alloc instructs the network to defer the IPV6 address allocation until after the endpoint has been created |
|
| 509 |
+// It is being provided to support the specific docker daemon flags where user can deterministically assign an IPv6 address |
|
| 510 |
+// to a container as combination of fixed-cidr-v6 + mac-address |
|
| 511 |
+// TODO: Remove this option setter once we support endpoint ipam options |
|
| 512 |
+func NetworkOptionDeferIPv6Alloc(enable bool) NetworkOption {
|
|
| 513 |
+ return func(n *network) {
|
|
| 514 |
+ n.postIPv6 = enable |
|
| 515 |
+ } |
|
| 516 |
+} |
|
| 517 |
+ |
|
| 508 | 518 |
func (n *network) processOptions(options ...NetworkOption) {
|
| 509 | 519 |
for _, opt := range options {
|
| 510 | 520 |
if opt != nil {
|
| ... | ... |
@@ -655,7 +671,7 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi |
| 655 | 655 |
|
| 656 | 656 |
ep.processOptions(options...) |
| 657 | 657 |
|
| 658 |
- if err = ep.assignAddress(); err != nil {
|
|
| 658 |
+ if err = ep.assignAddress(true, !n.postIPv6); err != nil {
|
|
| 659 | 659 |
return nil, err |
| 660 | 660 |
} |
| 661 | 661 |
defer func() {
|
| ... | ... |
@@ -675,6 +691,10 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi |
| 675 | 675 |
} |
| 676 | 676 |
}() |
| 677 | 677 |
|
| 678 |
+ if err = ep.assignAddress(false, n.postIPv6); err != nil {
|
|
| 679 |
+ return nil, err |
|
| 680 |
+ } |
|
| 681 |
+ |
|
| 678 | 682 |
if err = n.getController().updateToStore(ep); err != nil {
|
| 679 | 683 |
return nil, err |
| 680 | 684 |
} |