diff --git a/libnetwork/.gitignore b/libnetwork/.gitignore index 0529bde798..0e0d489204 100644 --- a/libnetwork/.gitignore +++ b/libnetwork/.gitignore @@ -29,7 +29,8 @@ cmd/dnet/dnet *.tmp *.coverprofile -# IDE files +# IDE files and folders .project +.settings/ libnetwork-build.created diff --git a/libnetwork/bitseq/sequence.go b/libnetwork/bitseq/sequence.go index 047f4dbaa7..006fee29a4 100644 --- a/libnetwork/bitseq/sequence.go +++ b/libnetwork/bitseq/sequence.go @@ -306,8 +306,23 @@ func (h *Handle) validateOrdinal(ordinal uint32) error { } // Destroy removes from the datastore the data belonging to this handle -func (h *Handle) Destroy() { - h.deleteFromStore() +func (h *Handle) Destroy() error { + for { + if err := h.deleteFromStore(); err != nil { + if _, ok := err.(types.RetryError); !ok { + return fmt.Errorf("internal failure while destroying the sequence: %v", err) + } + // Fetch latest + if err := h.store.GetObject(datastore.Key(h.Key()...), h); err != nil { + if err == datastore.ErrKeyNotFound { // already removed + return nil + } + return fmt.Errorf("failed to fetch from store when destroying the sequence: %v", err) + } + continue + } + return nil + } } // ToByteArray converts this handle's data into a byte array diff --git a/libnetwork/cmd/ovrouter/ovrouter.go b/libnetwork/cmd/ovrouter/ovrouter.go index 787320cf48..21c282edfc 100644 --- a/libnetwork/cmd/ovrouter/ovrouter.go +++ b/libnetwork/cmd/ovrouter/ovrouter.go @@ -10,6 +10,7 @@ import ( "github.com/docker/libnetwork/driverapi" "github.com/docker/libnetwork/drivers/overlay" "github.com/docker/libnetwork/netlabel" + "github.com/docker/libnetwork/types" "github.com/vishvananda/netlink" ) @@ -18,7 +19,7 @@ type router struct { } type endpoint struct { - addr net.IPNet + addr *net.IPNet mac net.HardwareAddr name string } @@ -32,9 +33,40 @@ func (ep *endpoint) Interface() driverapi.InterfaceInfo { return nil } -func (ep *endpoint) AddInterface(mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error { - ep.addr = ipv4 - ep.mac = mac +func (ep *endpoint) SetMacAddress(mac net.HardwareAddr) error { + if ep.mac != nil { + return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", ep.mac, mac) + } + if mac == nil { + return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface") + } + ep.mac = types.GetMacCopy(mac) + return nil +} + +func (ep *endpoint) SetIPAddress(address *net.IPNet) error { + if address.IP == nil { + return types.BadRequestErrorf("tried to set nil IP address to endpoint interface") + } + if address.IP.To4() == nil { + return types.NotImplementedErrorf("do not support ipv6 yet") + } + if ep.addr != nil { + return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with %s.", ep.addr, address) + } + ep.addr = types.GetIPNetCopy(address) + return nil +} + +func (ep *endpoint) MacAddress() net.HardwareAddr { + return types.GetMacCopy(ep.mac) +} + +func (ep *endpoint) Address() *net.IPNet { + return types.GetIPNetCopy(ep.addr) +} + +func (ep *endpoint) AddressIPv6() *net.IPNet { return nil } @@ -86,7 +118,7 @@ func main() { } if err := r.d.CreateNetwork("testnetwork", - map[string]interface{}{}); err != nil { + map[string]interface{}{}, nil, nil); err != nil { fmt.Printf("Failed to create network in the driver: %v\n", err) os.Exit(1) } @@ -111,7 +143,7 @@ func main() { os.Exit(1) } - ipAddr := &netlink.Addr{IPNet: &ep.addr, Label: ""} + ipAddr := &netlink.Addr{IPNet: ep.addr, Label: ""} if err := netlink.AddrAdd(link, ipAddr); err != nil { fmt.Printf("Failed to add address to the interface: %v\n", err) os.Exit(1) diff --git a/libnetwork/controller.go b/libnetwork/controller.go index 9a364c5413..6fdac5e698 100644 --- a/libnetwork/controller.go +++ b/libnetwork/controller.go @@ -58,6 +58,7 @@ import ( "github.com/docker/libnetwork/datastore" "github.com/docker/libnetwork/driverapi" "github.com/docker/libnetwork/hostdiscovery" + "github.com/docker/libnetwork/ipamapi" "github.com/docker/libnetwork/osl" "github.com/docker/libnetwork/types" ) @@ -116,7 +117,14 @@ type driverData struct { capability driverapi.Capability } +type ipamData struct { + driver ipamapi.Ipam + // default address spaces are provided by ipam driver at registration time + defaultLocalAddressSpace, defaultGlobalAddressSpace string +} + type driverTable map[string]*driverData +type ipamTable map[string]*ipamData type networkTable map[string]*network type endpointTable map[string]*endpoint type sandboxTable map[string]*sandbox @@ -125,6 +133,7 @@ type controller struct { id string networks networkTable drivers driverTable + ipamDrivers ipamTable sandboxes sandboxTable cfg *config.Config globalStore, localStore datastore.DataStore @@ -145,11 +154,12 @@ func New(cfgOptions ...config.Option) (NetworkController, error) { cfg.ProcessOptions(cfgOptions...) } c := &controller{ - id: stringid.GenerateRandomID(), - cfg: cfg, - networks: networkTable{}, - sandboxes: sandboxTable{}, - drivers: driverTable{}} + id: stringid.GenerateRandomID(), + cfg: cfg, + networks: networkTable{}, + sandboxes: sandboxTable{}, + drivers: driverTable{}, + ipamDrivers: ipamTable{}} if err := initDrivers(c); err != nil { return nil, err } @@ -160,13 +170,26 @@ func New(cfgOptions ...config.Option) (NetworkController, error) { // But it cannot fail creating the Controller log.Debugf("Failed to Initialize Datastore due to %v. Operating in non-clustered mode", err) } + if err := c.initLocalStore(); err != nil { + log.Debugf("Failed to Initialize LocalDatastore due to %v.", err) + } + } + + if err := initIpams(c, c.localStore, c.globalStore); err != nil { + return nil, err + } + + if cfg != nil { + if err := c.restoreFromGlobalStore(); err != nil { + log.Debugf("Failed to restore from global Datastore due to %v", err) + } if err := c.initDiscovery(cfg.Cluster.Watcher); err != nil { // Failing to initalize discovery is a bad situation to be in. // But it cannot fail creating the Controller log.Debugf("Failed to Initialize Discovery : %v", err) } - if err := c.initLocalStore(); err != nil { - log.Debugf("Failed to Initialize LocalDatastore due to %v.", err) + if err := c.restoreFromLocalStore(); err != nil { + log.Debugf("Failed to restore from local Datastore due to %v", err) } } @@ -272,6 +295,30 @@ func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver, return nil } +func (c *controller) RegisterIpamDriver(name string, driver ipamapi.Ipam) error { + if !config.IsValidName(name) { + return ErrInvalidName(name) + } + + c.Lock() + _, ok := c.ipamDrivers[name] + c.Unlock() + if ok { + return driverapi.ErrActiveRegistration(name) + } + locAS, glbAS, err := driver.GetDefaultAddressSpaces() + if err != nil { + return fmt.Errorf("ipam driver %s failed to return default address spaces: %v", name, err) + } + c.Lock() + c.ipamDrivers[name] = &ipamData{driver: driver, defaultLocalAddressSpace: locAS, defaultGlobalAddressSpace: glbAS} + c.Unlock() + + log.Debugf("Registering ipam provider: %s", name) + + return nil +} + // NewNetwork creates a new network of the specified network type. The options // are network specific and modeled in a generic way. func (c *controller) NewNetwork(networkType, name string, options ...NetworkOption) (Network, error) { @@ -292,6 +339,7 @@ func (c *controller) NewNetwork(networkType, name string, options ...NetworkOpti network := &network{ name: name, networkType: networkType, + ipamType: ipamapi.DefaultIPAM, id: stringid.GenerateRandomID(), ctrlr: c, endpoints: endpointTable{}, @@ -300,14 +348,30 @@ func (c *controller) NewNetwork(networkType, name string, options ...NetworkOpti network.processOptions(options...) - if err := c.addNetwork(network); err != nil { + if _, err := c.loadNetworkDriver(network); err != nil { return nil, err } - if err := c.updateToStore(network); err != nil { + cnfs, err := network.ipamAllocate() + if err != nil { + return nil, err + } + defer func() { + if err != nil { + for _, cn := range cnfs { + cn() + } + } + }() + + if err = c.addNetwork(network); err != nil { + return nil, err + } + + if err = c.updateToStore(network); err != nil { log.Warnf("couldnt create network %s: %v", network.name, err) if e := network.Delete(); e != nil { - log.Warnf("couldnt cleanup network %s: %v", network.name, err) + log.Warnf("couldnt cleanup network %s on network create failure (%v): %v", network.name, err, e) } return nil, err } @@ -316,29 +380,15 @@ func (c *controller) NewNetwork(networkType, name string, options ...NetworkOpti } func (c *controller) addNetwork(n *network) error { - - c.Lock() - // Check if a driver for the specified network type is available - dd, ok := c.drivers[n.networkType] - c.Unlock() - - if !ok { - var err error - dd, err = c.loadDriver(n.networkType) - if err != nil { - return err - } + if _, err := c.loadNetworkDriver(n); err != nil { + return err } - n.Lock() - n.svcRecords = svcMap{} - n.driver = dd.driver - n.dataScope = dd.capability.DataScope d := n.driver n.Unlock() // Create the network - if err := d.CreateNetwork(n.id, n.generic); err != nil { + if err := d.CreateNetwork(n.id, n.generic, n.getIPv4Data(), n.getIPv6Data()); err != nil { return err } if n.isGlobalScoped() { @@ -534,14 +584,39 @@ func (c *controller) loadDriver(networkType string) (*driverData, error) { return dd, nil } -func (c *controller) getDriver(networkType string) (*driverData, error) { - c.Lock() - defer c.Unlock() - dd, ok := c.drivers[networkType] - if !ok { - return nil, types.NotFoundErrorf("driver %s not found", networkType) +func (c *controller) loadIpamDriver(name string) (*ipamData, error) { + if _, err := plugins.Get(name, ipamapi.PluginEndpointType); err != nil { + if err == plugins.ErrNotFound { + return nil, types.NotFoundErrorf(err.Error()) + } + return nil, err } - return dd, nil + c.Lock() + id, ok := c.ipamDrivers[name] + c.Unlock() + if !ok { + return nil, ErrInvalidNetworkDriver(name) + } + return id, nil +} + +func (c *controller) getIPAM(name string) (id *ipamData, err error) { + var ok bool + c.Lock() + id, ok = c.ipamDrivers[name] + c.Unlock() + if !ok { + id, err = c.loadIpamDriver(name) + } + return id, err +} + +func (c *controller) getIpamDriver(name string) (ipamapi.Ipam, error) { + id, err := c.getIPAM(name) + if err != nil { + return nil, err + } + return id.driver, nil } func (c *controller) Stop() { @@ -551,3 +626,24 @@ func (c *controller) Stop() { c.stopExternalKeyListener() osl.GC() } + +func (c *controller) loadNetworkDriver(n *network) (driverapi.Driver, error) { + // Check if a driver for the specified network type is available + c.Lock() + dd, ok := c.drivers[n.networkType] + c.Unlock() + if !ok { + var err error + dd, err = c.loadDriver(n.networkType) + if err != nil { + return nil, err + } + } + + n.Lock() + n.svcRecords = svcMap{} + n.driver = dd.driver + n.dataScope = dd.capability.DataScope + n.Unlock() + return dd.driver, nil +} diff --git a/libnetwork/docs/remote.md b/libnetwork/docs/remote.md index cfd2023ee8..d56eccb9f0 100644 --- a/libnetwork/docs/remote.md +++ b/libnetwork/docs/remote.md @@ -17,7 +17,7 @@ This design ensures that the details of driver registration mechanism are owned The remote driver implementation uses a `plugins.Client` to communicate with the remote driver process. The `driverapi.Driver` methods are implemented as RPCs over the plugin client. -The payloads of these RPCs are mostly direct translations into JSON of the arguments given to the method. There are some exceptions to account for the use of the interfaces `EndpointInfo` and `JoinInfo`, and data types that do not serialise to JSON well (e.g., `net.IPNet`). The protocol is detailed below under "Protocol". +The payloads of these RPCs are mostly direct translations into JSON of the arguments given to the method. There are some exceptions to account for the use of the interfaces `InterfaceInfo` and `JoinInfo`, and data types that do not serialise to JSON well (e.g., `net.IPNet`). The protocol is detailed below under "Protocol". ## Usage @@ -65,12 +65,42 @@ When the proxy is asked to create a network, the remote process shall receive a { "NetworkID": string, + "IPv4Data" : [ + { + "AddressSpace": string, + "Pool": ipv4-cidr-string, + "Gateway" : ipv4-address" + "AuxAddresses": { + "" : "", + "" : "", + ... + } + }, + ], + "IPv6Data" : [ + { + "AddressSpace": string, + "Pool": ipv6-cidr-string, + "Gateway" : ipv6-address" + "AuxAddresses": { + "" : "", + "" : "", + ... + } + }, + ], "Options": { ... } } -The `NetworkID` value is generated by LibNetwork. The `Options` value is the arbitrary map given to the proxy by LibNetwork. +* `NetworkID` value is generated by LibNetwork which represents an unique network. +* `Options` value is the arbitrary map given to the proxy by LibNetwork. +* `IPv4Data` and `IPv6Data` are the ip-addressing data configured by the user and managed by IPAM driver. The network driver is expected to honor the ip-addressing data supplied by IPAM driver. The data include, +* `AddressSpace` : A unique string represents an isolated space for IP Addressing +* `Pool` : A range of IP Addresses represted in CIDR format address/mask. Since, the IPAM driver is responsible for allocating container ip-addresses, the network driver can make use of this information for the network plumbing purposes. +* `Gateway` : Optionally, the IPAM driver may provide a Gateway for the subnet represented by the Pool. the network driver can make use of this information for the network plumbing purposes. +* `AuxAddresses` : A list of pre-allocated ip-addresses with an associated identifier as provided by the user to assist network driver if it requires specific ip-addresses for its operation. The response indicating success is empty: diff --git a/libnetwork/driverapi/driverapi.go b/libnetwork/driverapi/driverapi.go index 1661c3672f..c81e2db34e 100644 --- a/libnetwork/driverapi/driverapi.go +++ b/libnetwork/driverapi/driverapi.go @@ -14,7 +14,7 @@ type Driver interface { // CreateNetwork invokes the driver method to create a network passing // the network id and network specific config. The config mechanism will // eventually be replaced with labels which are yet to be introduced. - CreateNetwork(nid string, options map[string]interface{}) error + CreateNetwork(nid string, options map[string]interface{}, ipV4Data, ipV6Data []IPAMData) error // DeleteNetwork invokes the driver method to delete network passing // the network id. @@ -25,7 +25,7 @@ type Driver interface { // specific config. The endpoint information can be either consumed by // the driver or populated by the driver. The config mechanism will // eventually be replaced with labels which are yet to be introduced. - CreateEndpoint(nid, eid string, epInfo EndpointInfo, options map[string]interface{}) error + CreateEndpoint(nid, eid string, ifInfo InterfaceInfo, options map[string]interface{}) error // DeleteEndpoint invokes the driver method to delete an endpoint // passing the network id and endpoint id. @@ -50,31 +50,26 @@ type Driver interface { Type() string } -// EndpointInfo provides a go interface to fetch or populate endpoint assigned network resources. -type EndpointInfo interface { - // Interface returns the interface bound to the endpoint. - // If the value is not nil the driver is only expected to consume the interface. - // It is an error to try to add interface if the passed down value is non-nil - // If the value is nil the driver is expected to add an interface - Interface() InterfaceInfo - - // AddInterface is used by the driver to add an interface for the endpoint. - // This method will return an error if the driver attempts to add interface - // if the Interface() method returned a non-nil value. - AddInterface(mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error -} - // InterfaceInfo provides a go interface for drivers to retrive // network information to interface resources. type InterfaceInfo interface { + // SetMacAddress allows the driver to set the mac address to the endpoint interface + // during the call to CreateEndpoint, if the mac address is not already set. + SetMacAddress(mac net.HardwareAddr) error + + // SetIPAddress allows the driver to set the ip address to the endpoint interface + // during the call to CreateEndpoint, if the address is not already set. + // The API is to be used to assign both the IPv4 and IPv6 address types. + SetIPAddress(ip *net.IPNet) error + // MacAddress returns the MAC address. MacAddress() net.HardwareAddr // Address returns the IPv4 address. - Address() net.IPNet + Address() *net.IPNet // AddressIPv6 returns the IPv6 address. - AddressIPv6() net.IPNet + AddressIPv6() *net.IPNet } // InterfaceNameInfo provides a go interface for the drivers to assign names @@ -126,3 +121,13 @@ type NodeDiscoveryData struct { Address string Self bool } + +// IPAMData represents the per-network ip related +// operational information libnetwork will send +// to the network driver during CreateNetwork() +type IPAMData struct { + AddressSpace string + Pool *net.IPNet + Gateway *net.IPNet + AuxAddresses map[string]*net.IPNet +} diff --git a/libnetwork/driverapi/driverapi_test.go b/libnetwork/driverapi/driverapi_test.go new file mode 100644 index 0000000000..2de6fcd502 --- /dev/null +++ b/libnetwork/driverapi/driverapi_test.go @@ -0,0 +1,119 @@ +package driverapi + +import ( + "encoding/json" + "net" + "testing" + + _ "github.com/docker/libnetwork/testutils" + "github.com/docker/libnetwork/types" +) + +func TestIPDataMarshalling(t *testing.T) { + i := &IPAMData{ + AddressSpace: "giallo", + Pool: &net.IPNet{IP: net.IP{10, 10, 10, 8}, Mask: net.IPMask{255, 255, 255, 0}}, + Gateway: &net.IPNet{IP: net.IP{10, 10, 10, 254}, Mask: net.IPMask{255, 255, 255, 0}}, + AuxAddresses: map[string]*net.IPNet{ + "ip1": &net.IPNet{IP: net.IP{10, 10, 10, 1}, Mask: net.IPMask{255, 255, 255, 0}}, + "ip2": &net.IPNet{IP: net.IP{10, 10, 10, 2}, Mask: net.IPMask{255, 255, 255, 0}}, + }, + } + + b, err := json.Marshal(i) + if err != nil { + t.Fatal(err) + } + + ii := &IPAMData{} + err = json.Unmarshal(b, &ii) + if err != nil { + t.Fatal(err) + } + + if i.AddressSpace != ii.AddressSpace || !types.CompareIPNet(i.Pool, ii.Pool) || + !types.CompareIPNet(i.Gateway, ii.Gateway) || + !compareAddresses(i.AuxAddresses, ii.AuxAddresses) { + t.Fatalf("JSON marsh/unmarsh failed.\nOriginal:\n%s\nDecoded:\n%s", i, ii) + } +} + +func compareAddresses(a, b map[string]*net.IPNet) bool { + if len(a) != len(b) { + return false + } + if len(a) > 0 { + for k := range a { + if !types.CompareIPNet(a[k], b[k]) { + return false + } + } + } + return true +} + +func TestValidateAndIsV6(t *testing.T) { + var err error + + i := &IPAMData{ + Pool: &net.IPNet{IP: net.IP{10, 10, 10, 8}, Mask: net.IPMask{255, 255, 255, 0}}, + Gateway: &net.IPNet{IP: net.IP{10, 10, 10, 254}, Mask: net.IPMask{255, 255, 255, 0}}, + AuxAddresses: map[string]*net.IPNet{ + "ip1": &net.IPNet{IP: net.IP{10, 10, 10, 1}, Mask: net.IPMask{255, 255, 255, 0}}, + "ip2": &net.IPNet{IP: net.IP{10, 10, 10, 2}, Mask: net.IPMask{255, 255, 255, 0}}, + }, + } + + // Check ip version + if i.IsV6() { + t.Fatalf("incorrect ip version returned") + } + orig := i.Pool + if i.Pool, err = types.ParseCIDR("2003::33/64"); err != nil { + t.Fatal(err) + } + if !i.IsV6() { + t.Fatalf("incorrect ip version returned") + } + i.Pool = orig + + // valid ip data + if err = i.Validate(); err != nil { + t.Fatal(err) + } + + // incongruent gw ver + if i.Gateway, err = types.ParseCIDR("2001::45/65"); err != nil { + t.Fatal(err) + } + if err = i.Validate(); err == nil { + t.Fatalf("expected error but succeded") + } + i.Gateway = nil + + // incongruent secondary ip ver + if i.AuxAddresses["ip2"], err = types.ParseCIDR("2002::44/80"); err != nil { + t.Fatal(err) + } + if err = i.Validate(); err == nil { + t.Fatalf("expected error but succeded") + } + delete(i.AuxAddresses, "ip2") + + // gw outside pool + if i.Gateway, err = types.ParseCIDR("10.10.15.254/24"); err != nil { + t.Fatal(err) + } + if err = i.Validate(); err == nil { + t.Fatalf("expected error but succeded") + } + i.Gateway = nil + + // sec ip outside of pool + if i.AuxAddresses["ip1"], err = types.ParseCIDR("10.10.2.1/24"); err != nil { + t.Fatal(err) + } + if err = i.Validate(); err == nil { + t.Fatalf("expected error but succeded") + } +} diff --git a/libnetwork/driverapi/ipamdata.go b/libnetwork/driverapi/ipamdata.go new file mode 100644 index 0000000000..9a2375bf8a --- /dev/null +++ b/libnetwork/driverapi/ipamdata.go @@ -0,0 +1,103 @@ +package driverapi + +import ( + "encoding/json" + "fmt" + "net" + + "github.com/docker/libnetwork/types" +) + +// MarshalJSON encodes IPAMData into json message +func (i *IPAMData) MarshalJSON() ([]byte, error) { + m := map[string]interface{}{} + m["AddressSpace"] = i.AddressSpace + if i.Pool != nil { + m["Pool"] = i.Pool.String() + } + if i.Gateway != nil { + m["Gateway"] = i.Gateway.String() + } + if i.AuxAddresses != nil { + am := make(map[string]string, len(i.AuxAddresses)) + for k, v := range i.AuxAddresses { + am[k] = v.String() + } + m["AuxAddresses"] = am + } + return json.Marshal(m) +} + +// UnmarshalJSON decodes a json message into IPAMData +func (i *IPAMData) UnmarshalJSON(data []byte) error { + var ( + m map[string]interface{} + err error + ) + if err := json.Unmarshal(data, &m); err != nil { + return err + } + i.AddressSpace = m["AddressSpace"].(string) + if v, ok := m["Pool"]; ok { + if i.Pool, err = types.ParseCIDR(v.(string)); err != nil { + return err + } + } + if v, ok := m["Gateway"]; ok { + if i.Gateway, err = types.ParseCIDR(v.(string)); err != nil { + return err + } + } + if v, ok := m["AuxAddresses"]; ok { + b, _ := json.Marshal(v) + var am map[string]string + if err = json.Unmarshal(b, &am); err != nil { + return err + } + i.AuxAddresses = make(map[string]*net.IPNet, len(am)) + for k, v := range am { + if i.AuxAddresses[k], err = types.ParseCIDR(v); err != nil { + return err + } + } + } + return nil +} + +// Validate checks wheter the IPAMData structure contains congruent data +func (i *IPAMData) Validate() error { + var isV6 bool + if i.Pool == nil { + return types.BadRequestErrorf("invalid pool") + } + if i.Gateway == nil { + return types.BadRequestErrorf("invalid gateway address") + } + isV6 = i.IsV6() + if isV6 && i.Gateway.IP.To4() != nil || !isV6 && i.Gateway.IP.To4() == nil { + return types.BadRequestErrorf("incongruent ip versions for pool and gateway") + } + for k, sip := range i.AuxAddresses { + if isV6 && sip.IP.To4() != nil || !isV6 && sip.IP.To4() == nil { + return types.BadRequestErrorf("incongruent ip versions for pool and secondary ip address %s", k) + } + } + if !i.Pool.Contains(i.Gateway.IP) { + return types.BadRequestErrorf("invalid gateway address (%s) does not belong to the pool (%s)", i.Gateway, i.Pool) + } + for k, sip := range i.AuxAddresses { + if !i.Pool.Contains(sip.IP) { + return types.BadRequestErrorf("invalid secondary address %s (%s) does not belong to the pool (%s)", k, i.Gateway, i.Pool) + } + } + return nil +} + +// IsV6 returns wheter this is an IPv6 IPAMData structure +func (i *IPAMData) IsV6() bool { + return nil == i.Pool.IP.To4() +} + +func (i *IPAMData) String() string { + return fmt.Sprintf("AddressSpace: %s\nPool: %v\nGateway: %v\nAddresses: %v", i.AddressSpace, i.Pool, i.Gateway, i.AuxAddresses) +} diff --git a/libnetwork/drivers.go b/libnetwork/drivers.go index 898bcc4ebe..b50d089b09 100644 --- a/libnetwork/drivers.go +++ b/libnetwork/drivers.go @@ -4,6 +4,9 @@ import ( "strings" "github.com/docker/libnetwork/driverapi" + "github.com/docker/libnetwork/ipamapi" + builtinIpam "github.com/docker/libnetwork/ipams/builtin" + remoteIpam "github.com/docker/libnetwork/ipams/remote" "github.com/docker/libnetwork/netlabel" ) @@ -53,3 +56,15 @@ func makeDriverConfig(c *controller, ntype string) map[string]interface{} { return config } + +func initIpams(ic ipamapi.Callback, lDs, gDs interface{}) error { + for _, fn := range [](func(ipamapi.Callback, interface{}, interface{}) error){ + builtinIpam.Init, + remoteIpam.Init, + } { + if err := fn(ic, lDs, gDs); err != nil { + return err + } + } + return nil +} diff --git a/libnetwork/drivers/bridge/bridge.go b/libnetwork/drivers/bridge/bridge.go index 71cc0e7fce..edcd7250ce 100644 --- a/libnetwork/drivers/bridge/bridge.go +++ b/libnetwork/drivers/bridge/bridge.go @@ -569,7 +569,7 @@ func (d *driver) getNetworks() []*bridgeNetwork { } // Create a new network using bridge plugin -func (d *driver) CreateNetwork(id string, option map[string]interface{}) error { +func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error { var err error defer osl.InitOSContext()() @@ -861,7 +861,7 @@ func setHairpinMode(link netlink.Link, enable bool) error { return nil } -func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error { +func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error { var ( ipv6Addr *net.IPNet err error @@ -869,12 +869,8 @@ func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo, defer osl.InitOSContext()() - if epInfo == nil { - return errors.New("invalid endpoint info passed") - } - - if epInfo.Interface() != nil { - return errors.New("non-nil interface passed to bridge(local) driver") + if ifInfo == nil { + return errors.New("invalid interface info passed") } // Get the network handler and make sure it exists @@ -1060,17 +1056,27 @@ func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo, endpoint.addrv6 = ipv6Addr } - err = epInfo.AddInterface(endpoint.macAddress, *ipv4Addr, *ipv6Addr) - if err != nil { - return err - } - // Program any required port mapping and store them in the endpoint endpoint.portMapping, err = n.allocatePorts(epConfig, endpoint, config.DefaultBindingIP, d.config.EnableUserlandProxy) if err != nil { return err } + err = ifInfo.SetMacAddress(endpoint.macAddress) + if err != nil { + return err + } + err = ifInfo.SetIPAddress(ipv4Addr) + if err != nil { + return err + } + if config.EnableIPv6 { + err = ifInfo.SetIPAddress(ipv6Addr) + if err != nil { + return err + } + } + return nil } diff --git a/libnetwork/drivers/bridge/bridge_test.go b/libnetwork/drivers/bridge/bridge_test.go index 8f53208418..36c6f6338c 100644 --- a/libnetwork/drivers/bridge/bridge_test.go +++ b/libnetwork/drivers/bridge/bridge_test.go @@ -51,15 +51,15 @@ func TestCreateFullOptions(t *testing.T) { netOption := make(map[string]interface{}) netOption[netlabel.GenericData] = netConfig - err := d.CreateNetwork("dummy", netOption) + err := d.CreateNetwork("dummy", netOption, nil, nil) if err != nil { t.Fatalf("Failed to create bridge: %v", err) } // Verify the IP address allocated for the endpoint belongs to the container network epOptions := make(map[string]interface{}) - te := &testEndpoint{} - err = d.CreateEndpoint("dummy", "ep1", te, epOptions) + te := &testEndpoint{iface: &testInterface{}} + err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions) if err != nil { t.Fatalf("Failed to create an endpoint : %s", err.Error()) } @@ -77,7 +77,7 @@ func TestCreateNoConfig(t *testing.T) { genericOption := make(map[string]interface{}) genericOption[netlabel.GenericData] = netconfig - if err := d.CreateNetwork("dummy", genericOption); err != nil { + if err := d.CreateNetwork("dummy", genericOption, nil, nil); err != nil { t.Fatalf("Failed to create bridge: %v", err) } } @@ -94,11 +94,11 @@ func TestCreate(t *testing.T) { genericOption := make(map[string]interface{}) genericOption[netlabel.GenericData] = netconfig - if err := d.CreateNetwork("dummy", genericOption); err != nil { + if err := d.CreateNetwork("dummy", genericOption, nil, nil); err != nil { t.Fatalf("Failed to create bridge: %v", err) } - err := d.CreateNetwork("dummy", genericOption) + err := d.CreateNetwork("dummy", genericOption, nil, nil) if err == nil { t.Fatalf("Expected bridge driver to refuse creation of second network with default name") } @@ -127,7 +127,7 @@ func TestCreateFail(t *testing.T) { genericOption := make(map[string]interface{}) genericOption[netlabel.GenericData] = netconfig - if err := d.CreateNetwork("dummy", genericOption); err == nil { + if err := d.CreateNetwork("dummy", genericOption, nil, nil); err == nil { t.Fatal("Bridge creation was expected to fail") } } @@ -149,19 +149,19 @@ func TestCreateMultipleNetworks(t *testing.T) { config1 := &networkConfiguration{BridgeName: "net_test_1"} genericOption = make(map[string]interface{}) genericOption[netlabel.GenericData] = config1 - if err := d.CreateNetwork("1", genericOption); err != nil { + if err := d.CreateNetwork("1", genericOption, nil, nil); err != nil { t.Fatalf("Failed to create bridge: %v", err) } config2 := &networkConfiguration{BridgeName: "net_test_2"} genericOption[netlabel.GenericData] = config2 - if err := d.CreateNetwork("2", genericOption); err != nil { + if err := d.CreateNetwork("2", genericOption, nil, nil); err != nil { t.Fatalf("Failed to create bridge: %v", err) } config3 := &networkConfiguration{BridgeName: "net_test_3"} genericOption[netlabel.GenericData] = config3 - if err := d.CreateNetwork("3", genericOption); err != nil { + if err := d.CreateNetwork("3", genericOption, nil, nil); err != nil { t.Fatalf("Failed to create bridge: %v", err) } @@ -170,7 +170,7 @@ func TestCreateMultipleNetworks(t *testing.T) { config4 := &networkConfiguration{BridgeName: "net_test_4"} genericOption[netlabel.GenericData] = config4 - if err := d.CreateNetwork("4", genericOption); err != nil { + if err := d.CreateNetwork("4", genericOption, nil, nil); err != nil { t.Fatalf("Failed to create bridge: %v", err) } @@ -208,8 +208,8 @@ func verifyV4INCEntries(networks map[string]*bridgeNetwork, numEntries int, t *t type testInterface struct { mac net.HardwareAddr - addr net.IPNet - addrv6 net.IPNet + addr *net.IPNet + addrv6 *net.IPNet srcName string dstName string } @@ -231,24 +231,47 @@ func (te *testEndpoint) Interface() driverapi.InterfaceInfo { return nil } -func (te *testEndpoint) AddInterface(mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error { - iface := &testInterface{addr: ipv4, addrv6: ipv6} - te.iface = iface - return nil -} - func (i *testInterface) MacAddress() net.HardwareAddr { return i.mac } -func (i *testInterface) Address() net.IPNet { +func (i *testInterface) Address() *net.IPNet { return i.addr } -func (i *testInterface) AddressIPv6() net.IPNet { +func (i *testInterface) AddressIPv6() *net.IPNet { return i.addrv6 } +func (i *testInterface) SetMacAddress(mac net.HardwareAddr) error { + if i.mac != nil { + return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", i.mac, mac) + } + if mac == nil { + return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface") + } + i.mac = types.GetMacCopy(mac) + return nil +} + +func (i *testInterface) SetIPAddress(address *net.IPNet) error { + if address.IP == nil { + return types.BadRequestErrorf("tried to set nil IP address to endpoint interface") + } + if address.IP.To4() == nil { + return setAddress(&i.addrv6, address) + } + return setAddress(&i.addr, address) +} + +func setAddress(ifaceAddr **net.IPNet, address *net.IPNet) error { + if *ifaceAddr != nil { + return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with (%s).", *ifaceAddr, address) + } + *ifaceAddr = types.GetIPNetCopy(address) + return nil +} + func (i *testInterface) SetNames(srcName string, dstName string) error { i.srcName = srcName i.dstName = dstName @@ -308,7 +331,7 @@ func testQueryEndpointInfo(t *testing.T, ulPxyEnabled bool) { genericOption = make(map[string]interface{}) genericOption[netlabel.GenericData] = netconfig - err := d.CreateNetwork("net1", genericOption) + err := d.CreateNetwork("net1", genericOption, nil, nil) if err != nil { t.Fatalf("Failed to create bridge: %v", err) } @@ -317,8 +340,8 @@ func testQueryEndpointInfo(t *testing.T, ulPxyEnabled bool) { epOptions := make(map[string]interface{}) epOptions[netlabel.PortMap] = portMappings - te := &testEndpoint{} - err = d.CreateEndpoint("net1", "ep1", te, epOptions) + te := &testEndpoint{iface: &testInterface{}} + err = d.CreateEndpoint("net1", "ep1", te.Interface(), epOptions) if err != nil { t.Fatalf("Failed to create an endpoint : %s", err.Error()) } @@ -368,7 +391,7 @@ func TestCreateLinkWithOptions(t *testing.T) { netOptions := make(map[string]interface{}) netOptions[netlabel.GenericData] = netconfig - err := d.CreateNetwork("net1", netOptions) + err := d.CreateNetwork("net1", netOptions, nil, nil) if err != nil { t.Fatalf("Failed to create bridge: %v", err) } @@ -377,8 +400,8 @@ func TestCreateLinkWithOptions(t *testing.T) { epOptions := make(map[string]interface{}) epOptions[netlabel.MacAddress] = mac - te := &testEndpoint{} - err = d.CreateEndpoint("net1", "ep", te, epOptions) + te := &testEndpoint{iface: &testInterface{}} + err = d.CreateEndpoint("net1", "ep", te.Interface(), epOptions) if err != nil { t.Fatalf("Failed to create an endpoint: %s", err.Error()) } @@ -437,7 +460,7 @@ func TestLinkContainers(t *testing.T) { genericOption = make(map[string]interface{}) genericOption[netlabel.GenericData] = netconfig - err := d.CreateNetwork("net1", genericOption) + err := d.CreateNetwork("net1", genericOption, nil, nil) if err != nil { t.Fatalf("Failed to create bridge: %v", err) } @@ -446,8 +469,8 @@ func TestLinkContainers(t *testing.T) { epOptions := make(map[string]interface{}) epOptions[netlabel.ExposedPorts] = exposedPorts - te1 := &testEndpoint{} - err = d.CreateEndpoint("net1", "ep1", te1, epOptions) + te1 := &testEndpoint{iface: &testInterface{}} + err = d.CreateEndpoint("net1", "ep1", te1.Interface(), epOptions) if err != nil { t.Fatalf("Failed to create an endpoint : %s", err.Error()) } @@ -457,8 +480,8 @@ func TestLinkContainers(t *testing.T) { t.Fatalf("No Ipv4 address assigned to the endpoint: ep1") } - te2 := &testEndpoint{} - err = d.CreateEndpoint("net1", "ep2", te2, nil) + te2 := &testEndpoint{iface: &testInterface{}} + err = d.CreateEndpoint("net1", "ep2", te2.Interface(), nil) if err != nil { t.Fatalf("Failed to create an endpoint : %s", err.Error()) } @@ -668,13 +691,13 @@ func TestSetDefaultGw(t *testing.T) { genericOption := make(map[string]interface{}) genericOption[netlabel.GenericData] = config - err := d.CreateNetwork("dummy", genericOption) + err := d.CreateNetwork("dummy", genericOption, nil, nil) if err != nil { t.Fatalf("Failed to create bridge: %v", err) } - te := &testEndpoint{} - err = d.CreateEndpoint("dummy", "ep", te, nil) + te := &testEndpoint{iface: &testInterface{}} + err = d.CreateEndpoint("dummy", "ep", te.Interface(), nil) if err != nil { t.Fatalf("Failed to create endpoint: %v", err) } diff --git a/libnetwork/drivers/bridge/network_test.go b/libnetwork/drivers/bridge/network_test.go index 8112dd6dea..ee94e13d6b 100644 --- a/libnetwork/drivers/bridge/network_test.go +++ b/libnetwork/drivers/bridge/network_test.go @@ -26,13 +26,13 @@ func TestLinkCreate(t *testing.T) { genericOption := make(map[string]interface{}) genericOption[netlabel.GenericData] = config - err := d.CreateNetwork("dummy", genericOption) + err := d.CreateNetwork("dummy", genericOption, nil, nil) if err != nil { t.Fatalf("Failed to create bridge: %v", err) } - te := &testEndpoint{} - err = d.CreateEndpoint("dummy", "", te, nil) + te := &testEndpoint{iface: &testInterface{}} + err = d.CreateEndpoint("dummy", "", te.Interface(), nil) if err != nil { if _, ok := err.(InvalidEndpointIDError); !ok { t.Fatalf("Failed with a wrong error :%s", err.Error()) @@ -42,7 +42,7 @@ func TestLinkCreate(t *testing.T) { } // Good endpoint creation - err = d.CreateEndpoint("dummy", "ep", te, nil) + err = d.CreateEndpoint("dummy", "ep", te.Interface(), nil) if err != nil { t.Fatalf("Failed to create a link: %s", err.Error()) } @@ -64,7 +64,7 @@ func TestLinkCreate(t *testing.T) { // then we could check the MTU on hostLnk as well. te1 := &testEndpoint{iface: &testInterface{}} - err = d.CreateEndpoint("dummy", "ep", te1, nil) + err = d.CreateEndpoint("dummy", "ep", te1.Interface(), nil) if err == nil { t.Fatalf("Failed to detect duplicate endpoint id on same network") } @@ -117,19 +117,19 @@ func TestLinkCreateTwo(t *testing.T) { genericOption := make(map[string]interface{}) genericOption[netlabel.GenericData] = config - err := d.CreateNetwork("dummy", genericOption) + err := d.CreateNetwork("dummy", genericOption, nil, nil) if err != nil { t.Fatalf("Failed to create bridge: %v", err) } - te1 := &testEndpoint{} - err = d.CreateEndpoint("dummy", "ep", te1, nil) + te1 := &testEndpoint{iface: &testInterface{}} + err = d.CreateEndpoint("dummy", "ep", te1.Interface(), nil) if err != nil { t.Fatalf("Failed to create a link: %s", err.Error()) } - te2 := &testEndpoint{} - err = d.CreateEndpoint("dummy", "ep", te2, nil) + te2 := &testEndpoint{iface: &testInterface{}} + err = d.CreateEndpoint("dummy", "ep", te2.Interface(), nil) if err != nil { if _, ok := err.(driverapi.ErrEndpointExists); !ok { t.Fatalf("Failed with a wrong error: %s", err.Error()) @@ -152,19 +152,19 @@ func TestLinkCreateNoEnableIPv6(t *testing.T) { genericOption := make(map[string]interface{}) genericOption[netlabel.GenericData] = config - err := d.CreateNetwork("dummy", genericOption) + err := d.CreateNetwork("dummy", genericOption, nil, nil) if err != nil { t.Fatalf("Failed to create bridge: %v", err) } - te := &testEndpoint{} - err = d.CreateEndpoint("dummy", "ep", te, nil) + te := &testEndpoint{iface: &testInterface{}} + err = d.CreateEndpoint("dummy", "ep", te.Interface(), nil) if err != nil { t.Fatalf("Failed to create a link: %s", err.Error()) } iface := te.iface - if iface.addrv6.IP.To16() != nil { + if iface.addrv6 != nil && iface.addrv6.IP.To16() != nil { t.Fatalf("Expectd IPv6 address to be nil when IPv6 is not enabled. Got IPv6 = %s", iface.addrv6.String()) } @@ -187,13 +187,13 @@ func TestLinkDelete(t *testing.T) { genericOption := make(map[string]interface{}) genericOption[netlabel.GenericData] = config - err := d.CreateNetwork("dummy", genericOption) + err := d.CreateNetwork("dummy", genericOption, nil, nil) if err != nil { t.Fatalf("Failed to create bridge: %v", err) } - te := &testEndpoint{} - err = d.CreateEndpoint("dummy", "ep1", te, nil) + te := &testEndpoint{iface: &testInterface{}} + err = d.CreateEndpoint("dummy", "ep1", te.Interface(), nil) if err != nil { t.Fatalf("Failed to create a link: %s", err.Error()) } diff --git a/libnetwork/drivers/bridge/port_mapping_test.go b/libnetwork/drivers/bridge/port_mapping_test.go index 9e7fa07445..9fb03a7b92 100644 --- a/libnetwork/drivers/bridge/port_mapping_test.go +++ b/libnetwork/drivers/bridge/port_mapping_test.go @@ -44,13 +44,13 @@ func TestPortMappingConfig(t *testing.T) { netOptions := make(map[string]interface{}) netOptions[netlabel.GenericData] = netConfig - err := d.CreateNetwork("dummy", netOptions) + err := d.CreateNetwork("dummy", netOptions, nil, nil) if err != nil { t.Fatalf("Failed to create bridge: %v", err) } - te := &testEndpoint{} - err = d.CreateEndpoint("dummy", "ep1", te, epOptions) + te := &testEndpoint{iface: &testInterface{}} + err = d.CreateEndpoint("dummy", "ep1", te.Interface(), epOptions) if err != nil { t.Fatalf("Failed to create the endpoint: %s", err.Error()) } diff --git a/libnetwork/drivers/host/host.go b/libnetwork/drivers/host/host.go index 2549ed866e..340cb2e6f0 100644 --- a/libnetwork/drivers/host/host.go +++ b/libnetwork/drivers/host/host.go @@ -23,7 +23,7 @@ func Init(dc driverapi.DriverCallback, config map[string]interface{}) error { return dc.RegisterDriver(networkType, &driver{}, c) } -func (d *driver) CreateNetwork(id string, option map[string]interface{}) error { +func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error { d.Lock() defer d.Unlock() @@ -40,7 +40,7 @@ func (d *driver) DeleteNetwork(nid string) error { return types.ForbiddenErrorf("network of type \"%s\" cannot be deleted", networkType) } -func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error { +func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error { return nil } diff --git a/libnetwork/drivers/host/host_test.go b/libnetwork/drivers/host/host_test.go index 43481916c1..cc1da885a8 100644 --- a/libnetwork/drivers/host/host_test.go +++ b/libnetwork/drivers/host/host_test.go @@ -14,7 +14,7 @@ func TestDriver(t *testing.T) { t.Fatalf("Unexpected network type returned by driver") } - err := d.CreateNetwork("first", nil) + err := d.CreateNetwork("first", nil, nil, nil) if err != nil { t.Fatal(err) } @@ -23,7 +23,7 @@ func TestDriver(t *testing.T) { t.Fatalf("Unexpected network id stored") } - err = d.CreateNetwork("second", nil) + err = d.CreateNetwork("second", nil, nil, nil) if err == nil { t.Fatalf("Second network creation should fail on this driver") } diff --git a/libnetwork/drivers/null/null.go b/libnetwork/drivers/null/null.go index 670fc68672..5fbdd12956 100644 --- a/libnetwork/drivers/null/null.go +++ b/libnetwork/drivers/null/null.go @@ -23,7 +23,7 @@ func Init(dc driverapi.DriverCallback, config map[string]interface{}) error { return dc.RegisterDriver(networkType, &driver{}, c) } -func (d *driver) CreateNetwork(id string, option map[string]interface{}) error { +func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error { d.Lock() defer d.Unlock() @@ -40,7 +40,7 @@ func (d *driver) DeleteNetwork(nid string) error { return types.ForbiddenErrorf("network of type \"%s\" cannot be deleted", networkType) } -func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error { +func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error { return nil } diff --git a/libnetwork/drivers/null/null_test.go b/libnetwork/drivers/null/null_test.go index 7bc65c6efd..8ea44ccff8 100644 --- a/libnetwork/drivers/null/null_test.go +++ b/libnetwork/drivers/null/null_test.go @@ -14,7 +14,7 @@ func TestDriver(t *testing.T) { t.Fatalf("Unexpected network type returned by driver") } - err := d.CreateNetwork("first", nil) + err := d.CreateNetwork("first", nil, nil, nil) if err != nil { t.Fatal(err) } @@ -23,7 +23,7 @@ func TestDriver(t *testing.T) { t.Fatalf("Unexpected network id stored") } - err = d.CreateNetwork("second", nil) + err = d.CreateNetwork("second", nil, nil, nil) if err == nil { t.Fatalf("Second network creation should fail on this driver") } diff --git a/libnetwork/drivers/overlay/joinleave.go b/libnetwork/drivers/overlay/joinleave.go index 9fa73f1c33..289df3f93f 100644 --- a/libnetwork/drivers/overlay/joinleave.go +++ b/libnetwork/drivers/overlay/joinleave.go @@ -25,8 +25,7 @@ func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, } if err := n.joinSandbox(); err != nil { - return fmt.Errorf("network sandbox join failed: %v", - err) + return fmt.Errorf("network sandbox join failed: %v", err) } sbox := n.sandbox() @@ -63,7 +62,7 @@ func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, } if err := netlink.LinkSetHardwareAddr(veth, ep.mac); err != nil { - return fmt.Errorf("could not set mac address to the container interface: %v", err) + return fmt.Errorf("could not set mac address (%v) to the container interface: %v", ep.mac, err) } if iNames := jinfo.InterfaceName(); iNames != nil { diff --git a/libnetwork/drivers/overlay/ov_endpoint.go b/libnetwork/drivers/overlay/ov_endpoint.go index 71e73c4e0a..408ffb6e2d 100644 --- a/libnetwork/drivers/overlay/ov_endpoint.go +++ b/libnetwork/drivers/overlay/ov_endpoint.go @@ -1,7 +1,6 @@ package overlay import ( - "encoding/binary" "fmt" "net" @@ -36,7 +35,7 @@ func (n *network) deleteEndpoint(eid string) { n.Unlock() } -func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo, +func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error { if err := validateID(nid, eid); err != nil { return err @@ -48,35 +47,20 @@ func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo, } ep := &endpoint{ - id: eid, + id: eid, + addr: ifInfo.Address(), + mac: ifInfo.MacAddress(), } - if epInfo != nil && epInfo.Interface() != nil { - addr := epInfo.Interface().Address() - ep.addr = &addr - ep.mac = epInfo.Interface().MacAddress() - n.addEndpoint(ep) - return nil + if ep.addr == nil { + return fmt.Errorf("create endpoint was not passed interface IP address") } - ipID, err := d.ipAllocator.GetID() - if err != nil { - return fmt.Errorf("could not allocate ip from subnet %s: %v", - bridgeSubnet.String(), err) - } - - ep.addr = &net.IPNet{ - Mask: bridgeSubnet.Mask, - } - ep.addr.IP = make([]byte, 4) - - binary.BigEndian.PutUint32(ep.addr.IP, bridgeSubnetInt+ipID) - - ep.mac = netutils.GenerateMACFromIP(ep.addr.IP) - - err = epInfo.AddInterface(ep.mac, *ep.addr, net.IPNet{}) - if err != nil { - return fmt.Errorf("could not add interface to endpoint info: %v", err) + if ep.mac == nil { + ep.mac = netutils.GenerateMACFromIP(ep.addr.IP) + if err := ifInfo.SetMacAddress(ep.mac); err != nil { + return err + } } n.addEndpoint(ep) @@ -99,7 +83,6 @@ func (d *driver) DeleteEndpoint(nid, eid string) error { return fmt.Errorf("endpoint id %q not found", eid) } - d.ipAllocator.Release(binary.BigEndian.Uint32(ep.addr.IP) - bridgeSubnetInt) n.deleteEndpoint(eid) return nil } diff --git a/libnetwork/drivers/overlay/ov_network.go b/libnetwork/drivers/overlay/ov_network.go index 9e5f9d8909..3f751cb158 100644 --- a/libnetwork/drivers/overlay/ov_network.go +++ b/libnetwork/drivers/overlay/ov_network.go @@ -9,7 +9,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/libnetwork/datastore" - "github.com/docker/libnetwork/ipallocator" + "github.com/docker/libnetwork/driverapi" "github.com/docker/libnetwork/osl" "github.com/vishvananda/netlink" "github.com/vishvananda/netlink/nl" @@ -18,24 +18,24 @@ import ( type networkTable map[string]*network type network struct { - id string - vni uint32 - dbIndex uint64 - dbExists bool - sbox osl.Sandbox - endpoints endpointTable - ipAllocator *ipallocator.IPAllocator - gw net.IP - vxlanName string - driver *driver - joinCnt int - once *sync.Once - initEpoch int - initErr error + id string + vni uint32 + dbIndex uint64 + dbExists bool + sbox osl.Sandbox + endpoints endpointTable + vxlanName string + driver *driver + joinCnt int + once *sync.Once + initEpoch int + initErr error + subnets []*net.IPNet + gateways []*net.IPNet sync.Mutex } -func (d *driver) CreateNetwork(id string, option map[string]interface{}) error { +func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error { if id == "" { return fmt.Errorf("invalid network id") } @@ -51,7 +51,13 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}) error { once: &sync.Once{}, } - n.gw = bridgeIP.IP + n.subnets = make([]*net.IPNet, len(ipV4Data)) + n.gateways = make([]*net.IPNet, len(ipV4Data)) + + for i, ipd := range ipV4Data { + n.subnets[i] = ipd.Pool + n.gateways[i] = ipd.Gateway + } d.addNetwork(n) @@ -151,7 +157,7 @@ func (n *network) initSandbox() error { // Add a bridge inside the namespace if err := sbox.AddInterface("bridge1", "br", - sbox.InterfaceOptions().Address(bridgeIP), + sbox.InterfaceOptions().Address(n.gateways[0]), sbox.InterfaceOptions().Bridge(true)); err != nil { return fmt.Errorf("could not create bridge inside the network sandbox: %v", err) } diff --git a/libnetwork/drivers/overlay/overlay.go b/libnetwork/drivers/overlay/overlay.go index 4a8a6e0137..995b4f194c 100644 --- a/libnetwork/drivers/overlay/overlay.go +++ b/libnetwork/drivers/overlay/overlay.go @@ -1,9 +1,7 @@ package overlay import ( - "encoding/binary" "fmt" - "net" "sync" "github.com/Sirupsen/logrus" @@ -44,36 +42,8 @@ type driver struct { sync.Mutex } -var ( - bridgeSubnet, bridgeIP *net.IPNet - once sync.Once - bridgeSubnetInt uint32 -) - -func onceInit() { - var err error - _, bridgeSubnet, err = net.ParseCIDR("172.21.0.0/16") - if err != nil { - panic("could not parse cid 172.21.0.0/16") - } - - bridgeSubnetInt = binary.BigEndian.Uint32(bridgeSubnet.IP.To4()) - - ip, subnet, err := net.ParseCIDR("172.21.255.254/16") - if err != nil { - panic("could not parse cid 172.21.255.254/16") - } - - bridgeIP = &net.IPNet{ - IP: ip, - Mask: subnet.Mask, - } -} - // Init registers a new instance of overlay driver func Init(dc driverapi.DriverCallback, config map[string]interface{}) error { - once.Do(onceInit) - c := driverapi.Capability{ DataScope: datastore.GlobalScope, } diff --git a/libnetwork/drivers/remote/api/api.go b/libnetwork/drivers/remote/api/api.go index 2345be0a29..1d7742e21a 100644 --- a/libnetwork/drivers/remote/api/api.go +++ b/libnetwork/drivers/remote/api/api.go @@ -34,6 +34,9 @@ type CreateNetworkRequest struct { // A free form map->object interface for communication of options. Options map[string]interface{} + + // IPAMData contains the address pool information for this network + IPv4Data, IPv6Data []driverapi.IPAMData } // CreateNetworkResponse is the response to the CreateNetworkRequest. diff --git a/libnetwork/drivers/remote/driver.go b/libnetwork/drivers/remote/driver.go index c4eb5e95c8..a10698e317 100644 --- a/libnetwork/drivers/remote/driver.go +++ b/libnetwork/drivers/remote/driver.go @@ -82,10 +82,12 @@ func (d *driver) call(methodName string, arg interface{}, retVal maybeError) err return nil } -func (d *driver) CreateNetwork(id string, options map[string]interface{}) error { +func (d *driver) CreateNetwork(id string, options map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error { create := &api.CreateNetworkRequest{ NetworkID: id, Options: options, + IPv4Data: ipV4Data, + IPv6Data: ipV6Data, } return d.call("CreateNetwork", create, &api.CreateNetworkResponse{}) } @@ -95,23 +97,22 @@ func (d *driver) DeleteNetwork(nid string) error { return d.call("DeleteNetwork", delete, &api.DeleteNetworkResponse{}) } -func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error { - var reqIface *api.EndpointInterface - - if epInfo == nil { - return fmt.Errorf("must not be called with nil EndpointInfo") +func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error { + if ifInfo == nil { + return fmt.Errorf("must not be called with nil InterfaceInfo") } - iface := epInfo.Interface() - if iface != nil { - addr4 := iface.Address() - addr6 := iface.AddressIPv6() - reqIface = &api.EndpointInterface{ - Address: addr4.String(), - AddressIPv6: addr6.String(), - MacAddress: iface.MacAddress().String(), - } + reqIface := &api.EndpointInterface{} + if ifInfo.Address() != nil { + reqIface.Address = ifInfo.Address().String() } + if ifInfo.AddressIPv6() != nil { + reqIface.AddressIPv6 = ifInfo.AddressIPv6().String() + } + if ifInfo.MacAddress() != nil { + reqIface.MacAddress = ifInfo.MacAddress().String() + } + create := &api.CreateEndpointRequest{ NetworkID: nid, EndpointID: eid, @@ -127,24 +128,27 @@ func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo, if err != nil { return err } - if reqIface != nil && inIface != nil { - // We're not supposed to add interface if there is already - // one. Attempt to roll back - return errorWithRollback("driver attempted to add interface ignoring the one provided", d.DeleteEndpoint(nid, eid)) + if inIface == nil { + // Remote driver did not set any field + return nil } - if inIface != nil { - var addr4, addr6 net.IPNet - if inIface.Address != nil { - addr4 = *(inIface.Address) - } - if inIface.AddressIPv6 != nil { - addr6 = *(inIface.AddressIPv6) - } - if err := epInfo.AddInterface(inIface.MacAddress, addr4, addr6); err != nil { - return errorWithRollback(fmt.Sprintf("failed to AddInterface %v: %s", inIface, err), d.DeleteEndpoint(nid, eid)) + if inIface.MacAddress != nil { + if err := ifInfo.SetMacAddress(inIface.MacAddress); err != nil { + return errorWithRollback(fmt.Sprintf("driver modified interface MAC address: %v", err), d.DeleteEndpoint(nid, eid)) } } + if inIface.Address != nil { + if err := ifInfo.SetIPAddress(inIface.Address); err != nil { + return errorWithRollback(fmt.Sprintf("driver modified interface address: %v", err), d.DeleteEndpoint(nid, eid)) + } + } + if inIface.AddressIPv6 != nil { + if err := ifInfo.SetIPAddress(inIface.AddressIPv6); err != nil { + return errorWithRollback(fmt.Sprintf("driver modified interface address: %v", err), d.DeleteEndpoint(nid, eid)) + } + } + return nil } @@ -193,11 +197,7 @@ func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, } ifaceName := res.InterfaceName - if jinfo.InterfaceName() != nil && ifaceName == nil { - return fmt.Errorf("no interface name information received while one is expected") - } - - if iface := jinfo.InterfaceName(); iface != nil { + if iface := jinfo.InterfaceName(); iface != nil && ifaceName != nil { if err := iface.SetNames(ifaceName.SrcName, ifaceName.DstPrefix); err != nil { return errorWithRollback(fmt.Sprintf("failed to set interface name: %s", err), d.Leave(nid, eid)) } @@ -278,7 +278,7 @@ func parseStaticRoutes(r api.JoinResponse) ([]*types.StaticRoute, error) { outRoute := &types.StaticRoute{RouteType: inRoute.RouteType} if inRoute.Destination != "" { - if outRoute.Destination, err = toAddr(inRoute.Destination); err != nil { + if outRoute.Destination, err = types.ParseCIDR(inRoute.Destination); err != nil { return nil, err } } @@ -304,12 +304,12 @@ func parseInterface(r api.CreateEndpointResponse) (*api.Interface, error) { var err error outIf = &api.Interface{} if inIf.Address != "" { - if outIf.Address, err = toAddr(inIf.Address); err != nil { + if outIf.Address, err = types.ParseCIDR(inIf.Address); err != nil { return nil, err } } if inIf.AddressIPv6 != "" { - if outIf.AddressIPv6, err = toAddr(inIf.AddressIPv6); err != nil { + if outIf.AddressIPv6, err = types.ParseCIDR(inIf.AddressIPv6); err != nil { return nil, err } } @@ -322,12 +322,3 @@ func parseInterface(r api.CreateEndpointResponse) (*api.Interface, error) { return outIf, nil } - -func toAddr(ipAddr string) (*net.IPNet, error) { - ip, ipnet, err := net.ParseCIDR(ipAddr) - if err != nil { - return nil, err - } - ipnet.IP = ip - return ipnet, nil -} diff --git a/libnetwork/drivers/remote/driver_test.go b/libnetwork/drivers/remote/driver_test.go index 07f76fa9bb..61d46e8dea 100644 --- a/libnetwork/drivers/remote/driver_test.go +++ b/libnetwork/drivers/remote/driver_test.go @@ -80,27 +80,59 @@ type testEndpoint struct { } func (test *testEndpoint) Interface() driverapi.InterfaceInfo { + return test +} + +func (test *testEndpoint) Address() *net.IPNet { + if test.address == "" { + return nil + } + nw, _ := types.ParseCIDR(test.address) + return nw +} + +func (test *testEndpoint) AddressIPv6() *net.IPNet { + if test.addressIPv6 == "" { + return nil + } + nw, _ := types.ParseCIDR(test.addressIPv6) + return nw +} + +func (test *testEndpoint) MacAddress() net.HardwareAddr { + if test.macAddress == "" { + return nil + } + mac, _ := net.ParseMAC(test.macAddress) + return mac +} + +func (test *testEndpoint) SetMacAddress(mac net.HardwareAddr) error { + if test.macAddress != "" { + return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", test.macAddress, mac) + } + if mac == nil { + return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface") + } + test.macAddress = mac.String() return nil } -func (test *testEndpoint) AddInterface(mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error { - ip4, net4, _ := net.ParseCIDR(test.address) - ip6, net6, _ := net.ParseCIDR(test.addressIPv6) - if ip4 != nil { - net4.IP = ip4 - if !types.CompareIPNet(net4, &ipv4) { - test.t.Fatalf("Wrong address given %+v", ipv4) - } +func (test *testEndpoint) SetIPAddress(address *net.IPNet) error { + if address.IP == nil { + return types.BadRequestErrorf("tried to set nil IP address to endpoint interface") } - if ip6 != nil { - net6.IP = ip6 - if !types.CompareIPNet(net6, &ipv6) { - test.t.Fatalf("Wrong address (IPv6) given %+v", ipv6) - } + if address.IP.To4() == nil { + return setAddress(&test.addressIPv6, address) } - if test.macAddress != "" && mac.String() != test.macAddress { - test.t.Fatalf("Wrong MAC address given %v", mac) + return setAddress(&test.address, address) +} + +func setAddress(ifaceAddr *string, address *net.IPNet) error { + if *ifaceAddr != "" { + return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with (%s).", *ifaceAddr, address) } + *ifaceAddr = address.String() return nil } @@ -253,7 +285,7 @@ func TestRemoteDriver(t *testing.T) { dst: "vethdst", address: "192.168.5.7/16", addressIPv6: "2001:DB8::5:7/48", - macAddress: "7a:56:78:34:12:da", + macAddress: "", gateway: "192.168.0.1", gatewayIPv6: "2001:DB8::1", hostsPath: "/here/comes/the/host/path", @@ -289,9 +321,7 @@ func TestRemoteDriver(t *testing.T) { }) handle(t, mux, "CreateEndpoint", func(msg map[string]interface{}) interface{} { iface := map[string]interface{}{ - "Address": ep.address, - "AddressIPv6": ep.addressIPv6, - "MacAddress": ep.macAddress, + "MacAddress": ep.macAddress, } return map[string]interface{}{ "Interface": iface, @@ -360,7 +390,7 @@ func TestRemoteDriver(t *testing.T) { } netID := "dummy-network" - err = d.CreateNetwork(netID, map[string]interface{}{}) + err = d.CreateNetwork(netID, map[string]interface{}{}, nil, nil) if err != nil { t.Fatal(err) } @@ -400,19 +430,6 @@ func TestRemoteDriver(t *testing.T) { } } -type failEndpoint struct { - t *testing.T -} - -func (f *failEndpoint) Interfaces() []*driverapi.InterfaceInfo { - f.t.Fatal("Unexpected call of Interfaces") - return nil -} -func (f *failEndpoint) AddInterface(int, net.HardwareAddr, net.IPNet, net.IPNet) error { - f.t.Fatal("Unexpected call of AddInterface") - return nil -} - func TestDriverError(t *testing.T) { var plugin = "test-net-driver-error" @@ -454,7 +471,7 @@ func TestMissingValues(t *testing.T) { "MacAddress": ep.macAddress, } return map[string]interface{}{ - "Interfaces": []interface{}{iface}, + "Interface": iface, } }) @@ -473,11 +490,27 @@ type rollbackEndpoint struct { } func (r *rollbackEndpoint) Interface() driverapi.InterfaceInfo { + return r +} + +func (r *rollbackEndpoint) MacAddress() net.HardwareAddr { return nil } -func (r *rollbackEndpoint) AddInterface(_ net.HardwareAddr, _ net.IPNet, _ net.IPNet) error { - return fmt.Errorf("fail this to trigger a rollback") +func (r *rollbackEndpoint) Address() *net.IPNet { + return nil +} + +func (r *rollbackEndpoint) AddressIPv6() *net.IPNet { + return nil +} + +func (r *rollbackEndpoint) SetMacAddress(mac net.HardwareAddr) error { + return fmt.Errorf("invalid mac") +} + +func (r *rollbackEndpoint) SetIPAddress(ip *net.IPNet) error { + return fmt.Errorf("invalid ip") } func TestRollback(t *testing.T) { @@ -511,7 +544,7 @@ func TestRollback(t *testing.T) { ep := &rollbackEndpoint{} - if err := driver.CreateEndpoint("dummy", "dummy", ep, map[string]interface{}{}); err == nil { + if err := driver.CreateEndpoint("dummy", "dummy", ep.Interface(), map[string]interface{}{}); err == nil { t.Fatalf("Expected error from driver") } if !rolledback { diff --git a/libnetwork/drivers/windows/windows.go b/libnetwork/drivers/windows/windows.go index 6872486bf4..5464a5f070 100644 --- a/libnetwork/drivers/windows/windows.go +++ b/libnetwork/drivers/windows/windows.go @@ -19,7 +19,7 @@ func Init(dc driverapi.DriverCallback, config map[string]interface{}) error { return dc.RegisterDriver(networkType, &driver{}, c) } -func (d *driver) CreateNetwork(id string, option map[string]interface{}) error { +func (d *driver) CreateNetwork(id string, option map[string]interface{}, ipV4Data, ipV6Data []driverapi.IPAMData) error { return nil } @@ -27,7 +27,7 @@ func (d *driver) DeleteNetwork(nid string) error { return nil } -func (d *driver) CreateEndpoint(nid, eid string, epInfo driverapi.EndpointInfo, epOptions map[string]interface{}) error { +func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error { return nil } diff --git a/libnetwork/endpoint.go b/libnetwork/endpoint.go index 83db4a8e7e..bc398f7a64 100644 --- a/libnetwork/endpoint.go +++ b/libnetwork/endpoint.go @@ -10,6 +10,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/libnetwork/datastore" + "github.com/docker/libnetwork/ipamapi" "github.com/docker/libnetwork/netlabel" "github.com/docker/libnetwork/types" ) @@ -71,7 +72,9 @@ func (ep *endpoint) MarshalJSON() ([]byte, error) { epMap["id"] = ep.id epMap["ep_iface"] = ep.iface epMap["exposed_ports"] = ep.exposedPorts - epMap["generic"] = ep.generic + if ep.generic != nil { + epMap["generic"] = ep.generic + } epMap["sandbox"] = ep.sandboxID return json.Marshal(epMap) } @@ -98,8 +101,8 @@ func (ep *endpoint) UnmarshalJSON(b []byte) (err error) { cb, _ := json.Marshal(epMap["sandbox"]) json.Unmarshal(cb, &ep.sandboxID) - if epMap["generic"] != nil { - ep.generic = epMap["generic"].(map[string]interface{}) + if v, ok := epMap["generic"]; ok { + ep.generic = v.(map[string]interface{}) } return nil } @@ -423,6 +426,8 @@ func (ep *endpoint) Delete() error { return err } + ep.releaseAddress() + return nil } @@ -482,7 +487,7 @@ func (ep *endpoint) getFirstInterfaceAddress() net.IP { ep.Lock() defer ep.Unlock() - if ep.iface != nil { + if ep.iface.addr != nil { return ep.iface.addr.IP } @@ -549,3 +554,48 @@ func (ep *endpoint) DataScope() datastore.DataScope { func (ep *endpoint) isLocalScoped() bool { return ep.DataScope() == datastore.LocalScope } + +func (ep *endpoint) assignAddress() error { + var ( + ipam ipamapi.Ipam + err error + ) + n := ep.getNetwork() + if n.Type() == "host" || n.Type() == "null" || n.Type() == "bridge" { + return nil + } + ipam, err = n.getController().getIpamDriver(n.ipamType) + if err != nil { + return err + } + for _, d := range n.getIPInfo() { + var addr *net.IPNet + addr, _, err = ipam.RequestAddress(d.PoolID, nil, nil) + if err == nil { + ep.Lock() + ep.iface.addr = addr + ep.iface.poolID = d.PoolID + ep.Unlock() + return nil + } + if err != ipamapi.ErrNoAvailableIPs { + return err + } + } + return fmt.Errorf("no available ip addresses on this network address pools: %s (%s)", n.Name(), n.ID()) +} + +func (ep *endpoint) releaseAddress() { + n := ep.getNetwork() + if n.Type() == "host" || n.Type() == "null" || n.Type() == "bridge" { + return + } + ipam, err := n.getController().getIpamDriver(n.ipamType) + if err != nil { + log.Warnf("Failed to retrieve ipam driver to release interface address on delete of endpoint %s (%s): %v", ep.Name(), ep.ID(), err) + return + } + if err := ipam.ReleaseAddress(ep.iface.poolID, ep.iface.addr.IP); err != nil { + log.Warnf("Failed to release ip address %s on delete of endpoint %s (%s): %v", ep.iface.addr.IP, ep.Name(), ep.ID(), err) + } +} diff --git a/libnetwork/endpoint_info.go b/libnetwork/endpoint_info.go index 6d06675ae9..4e25fec3af 100644 --- a/libnetwork/endpoint_info.go +++ b/libnetwork/endpoint_info.go @@ -34,26 +34,33 @@ type InterfaceInfo interface { MacAddress() net.HardwareAddr // Address returns the IPv4 address assigned to the endpoint. - Address() net.IPNet + Address() *net.IPNet // AddressIPv6 returns the IPv6 address assigned to the endpoint. - AddressIPv6() net.IPNet + AddressIPv6() *net.IPNet } type endpointInterface struct { mac net.HardwareAddr - addr net.IPNet - addrv6 net.IPNet + addr *net.IPNet + addrv6 *net.IPNet srcName string dstPrefix string routes []*net.IPNet + poolID string } func (epi *endpointInterface) MarshalJSON() ([]byte, error) { epMap := make(map[string]interface{}) - epMap["mac"] = epi.mac.String() - epMap["addr"] = epi.addr.String() - epMap["addrv6"] = epi.addrv6.String() + if epi.mac != nil { + epMap["mac"] = epi.mac.String() + } + if epi.addr != nil { + epMap["addr"] = epi.addr.String() + } + if epi.addrv6 != nil { + epMap["addrv6"] = epi.addrv6.String() + } epMap["srcName"] = epi.srcName epMap["dstPrefix"] = epi.dstPrefix var routes []string @@ -61,28 +68,32 @@ func (epi *endpointInterface) MarshalJSON() ([]byte, error) { routes = append(routes, route.String()) } epMap["routes"] = routes + epMap["poolID"] = epi.poolID return json.Marshal(epMap) } -func (epi *endpointInterface) UnmarshalJSON(b []byte) (err error) { - var epMap map[string]interface{} - if err := json.Unmarshal(b, &epMap); err != nil { +func (epi *endpointInterface) UnmarshalJSON(b []byte) error { + var ( + err error + epMap map[string]interface{} + ) + if err = json.Unmarshal(b, &epMap); err != nil { return err } - - mac, _ := net.ParseMAC(epMap["mac"].(string)) - epi.mac = mac - - ip, ipnet, _ := net.ParseCIDR(epMap["addr"].(string)) - if ipnet != nil { - ipnet.IP = ip - epi.addr = *ipnet + if v, ok := epMap["mac"]; ok { + if epi.mac, err = net.ParseMAC(v.(string)); err != nil { + return types.InternalErrorf("failed to decode endpoint interface mac address after json unmarshal: %s", v.(string)) + } } - - ip, ipnet, _ = net.ParseCIDR(epMap["addrv6"].(string)) - if ipnet != nil { - ipnet.IP = ip - epi.addrv6 = *ipnet + if v, ok := epMap["addr"]; ok { + if epi.addr, err = types.ParseCIDR(v.(string)); err != nil { + return types.InternalErrorf("failed to decode endpoint interface ipv4 address after json unmarshal: %v", err) + } + } + if v, ok := epMap["addrv6"]; ok { + if epi.addrv6, err = types.ParseCIDR(v.(string)); err != nil { + return types.InternalErrorf("failed to decode endpoint interface ipv6 address after json unmarshal: %v", err) + } } epi.srcName = epMap["srcName"].(string) @@ -99,6 +110,7 @@ func (epi *endpointInterface) UnmarshalJSON(b []byte) (err error) { epi.routes = append(epi.routes, ipr) } } + epi.poolID = epMap["poolID"].(string) return nil } @@ -149,17 +161,32 @@ func (ep *endpoint) Interface() driverapi.InterfaceInfo { return nil } -func (ep *endpoint) AddInterface(mac net.HardwareAddr, ipv4 net.IPNet, ipv6 net.IPNet) error { - ep.Lock() - defer ep.Unlock() - - iface := &endpointInterface{ - addr: *types.GetIPNetCopy(&ipv4), - addrv6: *types.GetIPNetCopy(&ipv6), +func (epi *endpointInterface) SetMacAddress(mac net.HardwareAddr) error { + if epi.mac != nil { + return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", epi.mac, mac) } - iface.mac = types.GetMacCopy(mac) + if mac == nil { + return types.BadRequestErrorf("tried to set nil MAC address to endpoint interface") + } + epi.mac = types.GetMacCopy(mac) + return nil +} - ep.iface = iface +func (epi *endpointInterface) SetIPAddress(address *net.IPNet) error { + if address.IP == nil { + return types.BadRequestErrorf("tried to set nil IP address to endpoint interface") + } + if address.IP.To4() == nil { + return setAddress(&epi.addrv6, address) + } + return setAddress(&epi.addr, address) +} + +func setAddress(ifaceAddr **net.IPNet, address *net.IPNet) error { + if *ifaceAddr != nil { + return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with (%s).", *ifaceAddr, address) + } + *ifaceAddr = types.GetIPNetCopy(address) return nil } @@ -167,12 +194,12 @@ func (epi *endpointInterface) MacAddress() net.HardwareAddr { return types.GetMacCopy(epi.mac) } -func (epi *endpointInterface) Address() net.IPNet { - return (*types.GetIPNetCopy(&epi.addr)) +func (epi *endpointInterface) Address() *net.IPNet { + return types.GetIPNetCopy(epi.addr) } -func (epi *endpointInterface) AddressIPv6() net.IPNet { - return (*types.GetIPNetCopy(&epi.addrv6)) +func (epi *endpointInterface) AddressIPv6() *net.IPNet { + return types.GetIPNetCopy(epi.addrv6) } func (epi *endpointInterface) SetNames(srcName string, dstPrefix string) error { diff --git a/libnetwork/ipam/allocator.go b/libnetwork/ipam/allocator.go index 60c25613c0..85851a0c64 100644 --- a/libnetwork/ipam/allocator.go +++ b/libnetwork/ipam/allocator.go @@ -1,57 +1,58 @@ package ipam import ( + "encoding/json" "fmt" "net" "strings" "sync" log "github.com/Sirupsen/logrus" + "github.com/docker/libkv/store" "github.com/docker/libnetwork/bitseq" "github.com/docker/libnetwork/datastore" + "github.com/docker/libnetwork/ipamapi" + "github.com/docker/libnetwork/netutils" "github.com/docker/libnetwork/types" ) const ( + localAddressSpace = "LocalDefault" + globalAddressSpace = "GlobalDefault" // The biggest configurable host subnets - minNetSize = 8 - minNetSizeV6 = 64 - // The effective network size for v6 + minNetSize = 8 + minNetSizeV6 = 64 minNetSizeV6Eff = 96 - // The size of the host subnet used internally, it's the most granular sequence addresses - defaultInternalHostSize = 16 // datastore keyes for ipam objects - dsConfigKey = "ipam-config" // ipam-config// - dsDataKey = "ipam-data" // ipam-data//// + dsConfigKey = "ipam/" + ipamapi.DefaultIPAM + "/config" + dsDataKey = "ipam/" + ipamapi.DefaultIPAM + "/data" ) // Allocator provides per address space ipv4/ipv6 book keeping type Allocator struct { - // The internal subnets host size - internalHostSize int + // Predefined pools for default address spaces + predefined map[string][]*net.IPNet // Static subnet information - subnets map[subnetKey]*SubnetInfo - // Allocated addresses in each address space's internal subnet - addresses map[subnetKey]*bitseq.Handle + subnets map[SubnetKey]*PoolData + // Allocated addresses in each address space's subnet + addresses map[SubnetKey]*bitseq.Handle // Datastore store datastore.DataStore - App string - ID string dbIndex uint64 dbExists bool sync.Mutex } // NewAllocator returns an instance of libnetwork ipam -func NewAllocator(ds datastore.DataStore) (*Allocator, error) { +func NewAllocator(lcDs, glDs datastore.DataStore) (*Allocator, error) { a := &Allocator{} - a.subnets = make(map[subnetKey]*SubnetInfo) - a.addresses = make(map[subnetKey]*bitseq.Handle) - a.internalHostSize = defaultInternalHostSize - a.store = ds - a.App = "ipam" - a.ID = dsConfigKey + a.subnets = make(map[SubnetKey]*PoolData) + a.addresses = make(map[SubnetKey]*bitseq.Handle) + a.predefined = make(map[string][]*net.IPNet, 2) + a.predefined[localAddressSpace] = initLocalPredefinedPools() + a.predefined[globalAddressSpace] = initGlobalPredefinedPools() + a.store = glDs if a.store == nil { return a, nil @@ -70,18 +71,13 @@ func NewAllocator(ds datastore.DataStore) (*Allocator, error) { } a.subnetConfigFromStore(kvPair) - // Now retrieve the list of small subnets + // Now retrieve the bitmasks for the master pools var inserterList []func() error a.Lock() for k, v := range a.subnets { - inserterList = append(inserterList, - func() error { - subnetList, err := getInternalSubnets(v.Subnet, a.internalHostSize) - if err != nil { - return fmt.Errorf("failed to load address bitmask for configured subnet %s because of %s", v.Subnet.String(), err.Error()) - } - return a.insertAddressMasks(k, subnetList) - }) + if v.Range == nil { + inserterList = append(inserterList, func() error { return a.insertBitMask(k, v.Pool) }) + } } a.Unlock() @@ -98,29 +94,31 @@ func NewAllocator(ds datastore.DataStore) (*Allocator, error) { func (a *Allocator) subnetConfigFromStore(kvPair *store.KVPair) { a.Lock() if a.dbIndex < kvPair.LastIndex { - a.subnets = byteArrayToSubnets(kvPair.Value) + a.SetValue(kvPair.Value) a.dbIndex = kvPair.LastIndex a.dbExists = true } a.Unlock() } -// Pointer to the configured subnets in each address space -type subnetKey struct { - addressSpace AddressSpace - subnet string - childSubnet string +// SubnetKey is the pointer to the configured pools in each address space +type SubnetKey struct { + AddressSpace string + Subnet string + ChildSubnet string } -func (s *subnetKey) String() string { - k := fmt.Sprintf("%s/%s", s.addressSpace, s.subnet) - if s.childSubnet != "" { - k = fmt.Sprintf("%s/%s", k, s.childSubnet) +// String returns the string form of the SubnetKey object +func (s *SubnetKey) String() string { + k := fmt.Sprintf("%s/%s", s.AddressSpace, s.Subnet) + if s.ChildSubnet != "" { + k = fmt.Sprintf("%s/%s", k, s.ChildSubnet) } return k } -func (s *subnetKey) FromString(str string) error { +// FromString populate the SubnetKey object reading it from string +func (s *SubnetKey) FromString(str string) error { if str == "" || !strings.Contains(str, "/") { return fmt.Errorf("invalid string form for subnetkey: %s", str) } @@ -129,26 +127,106 @@ func (s *subnetKey) FromString(str string) error { if len(p) != 3 && len(p) != 5 { return fmt.Errorf("invalid string form for subnetkey: %s", str) } - s.addressSpace = AddressSpace(p[0]) - s.subnet = fmt.Sprintf("%s/%s", p[1], p[2]) + s.AddressSpace = p[0] + s.Subnet = fmt.Sprintf("%s/%s", p[1], p[2]) if len(p) == 5 { - s.childSubnet = fmt.Sprintf("%s/%s", p[1], p[2]) + s.ChildSubnet = fmt.Sprintf("%s/%s", p[3], p[4]) } return nil } -func (s *subnetKey) canonicalSubnet() *net.IPNet { - if _, sub, err := net.ParseCIDR(s.subnet); err == nil { - return sub +// AddressRange specifies first and last ip ordinal which +// identify a range in a a pool of addresses +type AddressRange struct { + Sub *net.IPNet + Start, End uint32 +} + +// String returns the string form of the AddressRange object +func (r *AddressRange) String() string { + return fmt.Sprintf("Sub: %s, range [%d, %d]", r.Sub, r.Start, r.End) +} + +// MarshalJSON returns the JSON encoding of the Range object +func (r *AddressRange) MarshalJSON() ([]byte, error) { + m := map[string]interface{}{ + "Sub": r.Sub.String(), + "Start": r.Start, + "End": r.End, } + return json.Marshal(m) +} + +// UnmarshalJSON decodes data into the Range object +func (r *AddressRange) UnmarshalJSON(data []byte) error { + m := map[string]interface{}{} + err := json.Unmarshal(data, &m) + if err != nil { + return err + } + if r.Sub, err = types.ParseCIDR(m["Sub"].(string)); err != nil { + return err + } + r.Start = uint32(m["Start"].(float64)) + r.End = uint32(m["End"].(float64)) return nil } -func (s *subnetKey) canonicalChildSubnet() *net.IPNet { - if _, sub, err := net.ParseCIDR(s.childSubnet); err == nil { - return sub +// PoolData contains the configured pool data +type PoolData struct { + ParentKey SubnetKey + Pool *net.IPNet + Range *AddressRange `json:",omitempty"` + RefCount int +} + +// String returns the string form of the PoolData object +func (p *PoolData) String() string { + return fmt.Sprintf("ParentKey: %s, Pool: %s, Range: %s, RefCount: %d", + p.ParentKey.String(), p.Pool.String(), p.Range, p.RefCount) +} + +// MarshalJSON returns the JSON encoding of the PoolData object +func (p *PoolData) MarshalJSON() ([]byte, error) { + m := map[string]interface{}{ + "ParentKey": p.ParentKey, + "RefCount": p.RefCount, } + if p.Pool != nil { + m["Pool"] = p.Pool.String() + } + if p.Range != nil { + m["Range"] = p.Range + } + return json.Marshal(m) +} + +// UnmarshalJSON decodes data into the PoolData object +func (p *PoolData) UnmarshalJSON(data []byte) error { + var ( + err error + t struct { + ParentKey SubnetKey + Pool string + Range *AddressRange `json:",omitempty"` + RefCount int + } + ) + + if err = json.Unmarshal(data, &t); err != nil { + return err + } + + p.ParentKey = t.ParentKey + p.Range = t.Range + p.RefCount = t.RefCount + if t.Pool != "" { + if p.Pool, err = types.ParseCIDR(t.Pool); err != nil { + return err + } + } + return nil } @@ -159,88 +237,267 @@ const ( v6 = 6 ) -/******************* - * IPAMConf Contract - ********************/ +// GetDefaultAddressSpaces returns the local and global default address spaces +func (a *Allocator) GetDefaultAddressSpaces() (string, string, error) { + return localAddressSpace, globalAddressSpace, nil +} -// AddSubnet adds a subnet for the specified address space -func (a *Allocator) AddSubnet(addrSpace AddressSpace, subnetInfo *SubnetInfo) error { - // Sanity check - if addrSpace == "" { - return ErrInvalidAddressSpace +// RequestPool returns an address pool along with its unique id. +func (a *Allocator) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) { + k, nw, aw, ipr, err := a.parsePoolRequest(addressSpace, pool, subPool, v6) + if err != nil { + return "", nil, nil, ipamapi.ErrInvalidPool } - if subnetInfo == nil || subnetInfo.Subnet == nil { - return ErrInvalidSubnet +retry: + insert, err := a.updatePoolDBOnAdd(*k, nw, ipr) + if err != nil { + return "", nil, nil, err } - // Convert to smaller internal subnets (if needed) - subnetList, err := getInternalSubnets(subnetInfo.Subnet, a.internalHostSize) + if err := a.writeToStore(); err != nil { + if _, ok := err.(types.RetryError); !ok { + return "", nil, nil, types.InternalErrorf("pool configuration failed because of %s", err.Error()) + } + if erru := a.readFromStore(); erru != nil { + return "", nil, nil, fmt.Errorf("failed to get updated pool config from datastore (%v) after (%v)", erru, err) + } + goto retry + } + return k.String(), aw, nil, insert() +} + +// ReleasePool releases the address pool identified by the passed id +func (a *Allocator) ReleasePool(poolID string) error { + k := SubnetKey{} + if err := k.FromString(poolID); err != nil { + return types.BadRequestErrorf("invalid pool id: %s", poolID) + } + +retry: + remove, err := a.updatePoolDBOnRemoval(k) if err != nil { return err } -retry: - if a.contains(addrSpace, subnetInfo) { - return ErrOverlapSubnet - } - - // Store the configured subnet and sync to datatstore - key := subnetKey{addrSpace, subnetInfo.Subnet.String(), ""} - a.Lock() - a.subnets[key] = subnetInfo - a.Unlock() - err = a.writeToStore() - if err != nil { + if err = a.writeToStore(); err != nil { if _, ok := err.(types.RetryError); !ok { - return types.InternalErrorf("subnet configuration failed because of %s", err.Error()) + return types.InternalErrorf("pool (%s) removal failed because of %v", poolID, err) } - // Update to latest if erru := a.readFromStore(); erru != nil { - // Restore and bail out - a.Lock() - delete(a.addresses, key) - a.Unlock() - return fmt.Errorf("failed to get updated subnets config from datastore (%v) after (%v)", erru, err) + return fmt.Errorf("failed to get updated pool config from datastore (%v) after (%v)", erru, err) } goto retry } - // Insert respective bitmasks for this subnet - a.insertAddressMasks(key, subnetList) + return remove() +} + +func (a *Allocator) parsePoolRequest(addressSpace, pool, subPool string, v6 bool) (*SubnetKey, *net.IPNet, *net.IPNet, *AddressRange, error) { + var ( + nw, aw *net.IPNet + ipr *AddressRange + err error + ) + + if addressSpace == "" { + return nil, nil, nil, nil, ipamapi.ErrInvalidAddressSpace + } + + if pool == "" && subPool != "" { + return nil, nil, nil, nil, ipamapi.ErrInvalidSubPool + } + + if pool != "" { + if _, nw, err = net.ParseCIDR(pool); err != nil { + return nil, nil, nil, nil, ipamapi.ErrInvalidPool + } + if subPool != "" { + if ipr, err = getAddressRange(subPool); err != nil { + return nil, nil, nil, nil, err + } + } + } else { + if nw, err = a.getPredefinedPool(addressSpace, v6); err != nil { + return nil, nil, nil, nil, err + } + + } + if aw, err = adjustAndCheckSubnetSize(nw); err != nil { + return nil, nil, nil, nil, err + } + + return &SubnetKey{AddressSpace: addressSpace, Subnet: nw.String(), ChildSubnet: subPool}, nw, aw, ipr, nil +} + +func (a *Allocator) updatePoolDBOnAdd(k SubnetKey, nw *net.IPNet, ipr *AddressRange) (func() error, error) { + a.Lock() + defer a.Unlock() + + // Check if already allocated + if p, ok := a.subnets[k]; ok { + a.incRefCount(p, 1) + return func() error { return nil }, nil + } + + // If master pool, check for overlap + if ipr == nil { + if a.contains(k.AddressSpace, nw) { + return nil, ipamapi.ErrPoolOverlap + } + // This is a new master pool, add it along with corresponding bitmask + a.subnets[k] = &PoolData{Pool: nw, RefCount: 1} + return func() error { return a.insertBitMask(k, nw) }, nil + } + + // This is a new non-master pool + p := &PoolData{ + ParentKey: SubnetKey{AddressSpace: k.AddressSpace, Subnet: k.Subnet}, + Pool: nw, + Range: ipr, + RefCount: 1, + } + a.subnets[k] = p + + // Look for parent pool + pp, ok := a.subnets[p.ParentKey] + if ok { + a.incRefCount(pp, 1) + return func() error { return nil }, nil + } + + // Parent pool does not exist, add it along with corresponding bitmask + a.subnets[p.ParentKey] = &PoolData{Pool: nw, RefCount: 1} + return func() error { return a.insertBitMask(p.ParentKey, nw) }, nil +} + +func (a *Allocator) updatePoolDBOnRemoval(k SubnetKey) (func() error, error) { + a.Lock() + defer a.Unlock() + + p, ok := a.subnets[k] + if !ok { + return nil, ipamapi.ErrBadPool + } + + a.incRefCount(p, -1) + + c := p + for ok { + if c.RefCount == 0 { + delete(a.subnets, k) + if c.Range == nil { + return func() error { + bm, err := a.retrieveBitmask(k, c.Pool) + if err != nil { + return fmt.Errorf("could not find bitmask in datastore for pool %s removal: %v", k.String(), err) + } + return bm.Destroy() + }, nil + } + } + k = c.ParentKey + c, ok = a.subnets[k] + } + + return func() error { return nil }, nil +} + +func (a *Allocator) incRefCount(p *PoolData, delta int) { + c := p + ok := true + for ok { + c.RefCount += delta + c, ok = a.subnets[c.ParentKey] + } +} + +func (a *Allocator) insertBitMask(key SubnetKey, pool *net.IPNet) error { + log.Debugf("Inserting bitmask (%s, %s)", key.String(), pool.String()) + ipVer := getAddressVersion(pool.IP) + ones, bits := pool.Mask.Size() + numAddresses := uint32(1 << uint(bits-ones)) + + if ipVer == v4 { + // Do not let broadcast address be reserved + numAddresses-- + } + + // Generate the new address masks. AddressMask content may come from datastore + h, err := bitseq.NewHandle(dsDataKey, a.store, key.String(), numAddresses) + if err != nil { + return err + } + + if ipVer == v4 { + // Do not let network identifier address be reserved + h.Set(0) + } + + a.Lock() + a.addresses[key] = h + a.Unlock() return nil } -// Create and insert the internal subnet(s) addresses masks into the address database. Mask data may come from the bitseq datastore. -func (a *Allocator) insertAddressMasks(parentKey subnetKey, internalSubnetList []*net.IPNet) error { - ipVer := getAddressVersion(internalSubnetList[0].IP) - num := len(internalSubnetList) - ones, bits := internalSubnetList[0].Mask.Size() - numAddresses := 1 << uint(bits-ones) - - for i := 0; i < num; i++ { - smallKey := subnetKey{parentKey.addressSpace, parentKey.subnet, internalSubnetList[i].String()} - limit := uint32(numAddresses) - - if ipVer == v4 && i == num-1 { - // Do not let broadcast address be reserved - limit-- +func (a *Allocator) retrieveBitmask(k SubnetKey, n *net.IPNet) (*bitseq.Handle, error) { + a.Lock() + bm, ok := a.addresses[k] + a.Unlock() + if !ok { + log.Debugf("Retrieving bitmask (%s, %s)", k.String(), n.String()) + if err := a.insertBitMask(k, n); err != nil { + return nil, fmt.Errorf("could not find bitmask in datastore for %s", k.String()) } - - // Generate the new address masks. AddressMask content may come from datastore - h, err := bitseq.NewHandle(dsDataKey, a.getStore(), smallKey.String(), limit) - if err != nil { - return err - } - - if ipVer == v4 && i == 0 { - // Do not let network identifier address be reserved - h.Set(0) - } - a.Lock() - a.addresses[smallKey] = h + bm = a.addresses[k] a.Unlock() } - return nil + return bm, nil +} + +func (a *Allocator) getPredefineds(as string) []*net.IPNet { + a.Lock() + defer a.Unlock() + l := make([]*net.IPNet, 0, len(a.predefined[as])) + for _, pool := range a.predefined[as] { + l = append(l, pool) + } + return l +} + +func (a *Allocator) getPredefinedPool(as string, ipV6 bool) (*net.IPNet, error) { + var v ipVersion + v = v4 + if ipV6 { + v = v6 + } + + if as != localAddressSpace && as != globalAddressSpace { + return nil, fmt.Errorf("no default pool availbale for non-default addresss spaces") + } + + for _, nw := range a.getPredefineds(as) { + if v != getAddressVersion(nw.IP) { + continue + } + a.Lock() + _, ok := a.subnets[SubnetKey{AddressSpace: as, Subnet: nw.String()}] + a.Unlock() + if ok { + continue + } + + if !a.contains(as, nw) { + if as == localAddressSpace { + if err := netutils.CheckRouteOverlaps(nw); err == nil { + return nw, nil + } + continue + } + return nw, nil + } + } + + return nil, types.NotFoundErrorf("could not find an available predefined network") } // Check subnets size. In case configured subnet is v6 and host size is @@ -249,7 +506,7 @@ func adjustAndCheckSubnetSize(subnet *net.IPNet) (*net.IPNet, error) { ones, bits := subnet.Mask.Size() if v6 == getAddressVersion(subnet.IP) { if ones < minNetSizeV6 { - return nil, ErrInvalidSubnet + return nil, ipamapi.ErrInvalidPool } if ones < minNetSizeV6Eff { newMask := net.CIDRMask(minNetSizeV6Eff, bits) @@ -257,20 +514,17 @@ func adjustAndCheckSubnetSize(subnet *net.IPNet) (*net.IPNet, error) { } } else { if ones < minNetSize { - return nil, ErrInvalidSubnet + return nil, ipamapi.ErrInvalidPool } } return subnet, nil } // Checks whether the passed subnet is a superset or subset of any of the subset in the db -func (a *Allocator) contains(space AddressSpace, subInfo *SubnetInfo) bool { - a.Lock() - defer a.Unlock() +func (a *Allocator) contains(space string, nw *net.IPNet) bool { for k, v := range a.subnets { - if space == k.addressSpace { - if subInfo.Subnet.Contains(v.Subnet.IP) || - v.Subnet.Contains(subInfo.Subnet.IP) { + if space == k.AddressSpace && k.ChildSubnet == "" { + if nw.Contains(v.Pool.IP) || v.Pool.Contains(nw.IP) { return true } } @@ -278,272 +532,107 @@ func (a *Allocator) contains(space AddressSpace, subInfo *SubnetInfo) bool { return false } -// Splits the passed subnet into N internal subnets with host size equal to internalHostSize. -// If the subnet's host size is equal to or smaller than internalHostSize, there won't be any -// split and the return list will contain only the passed subnet. -func getInternalSubnets(inSubnet *net.IPNet, internalHostSize int) ([]*net.IPNet, error) { - var subnetList []*net.IPNet - - // Sanity check and size adjustment for v6 - subnet, err := adjustAndCheckSubnetSize(inSubnet) - if err != nil { - return subnetList, err +// RequestAddress returns an address from the specified pool ID +func (a *Allocator) RequestAddress(poolID string, prefAddress net.IP, opts map[string]string) (*net.IPNet, map[string]string, error) { + k := SubnetKey{} + if err := k.FromString(poolID); err != nil { + return nil, nil, types.BadRequestErrorf("invalid pool id: %s", poolID) } - // Get network/host subnet information - netBits, bits := subnet.Mask.Size() - hostBits := bits - netBits - - extraBits := hostBits - internalHostSize - if extraBits <= 0 { - subnetList = make([]*net.IPNet, 1) - subnetList[0] = subnet - } else { - // Split in smaller internal subnets - numIntSubs := 1 << uint(extraBits) - subnetList = make([]*net.IPNet, numIntSubs) - - // Construct one copy of the internal subnets's mask - intNetBits := bits - internalHostSize - intMask := net.CIDRMask(intNetBits, bits) - - // Construct the prefix portion for each internal subnet - for i := 0; i < numIntSubs; i++ { - intIP := make([]byte, len(subnet.IP)) - copy(intIP, subnet.IP) // IPv6 is too big, just work on the extra portion - addIntToIP(intIP, uint32(i< len(a.predefined[as])-1-atLeast { + return 0, nil, fmt.Errorf("Not enough non-overlapping networks to run the test") + } + return i, a.predefined[as][i], nil +} + func TestAdjustAndCheckSubnet(t *testing.T) { _, sub6, _ := net.ParseCIDR("1003:1:2:300::/63") _, err := adjustAndCheckSubnetSize(sub6) @@ -172,13 +444,13 @@ func TestAdjustAndCheckSubnet(t *testing.T) { } func TestRemoveSubnet(t *testing.T) { - a, err := NewAllocator(nil) + a, err := NewAllocator(nil, nil) if err != nil { t.Fatal(err) } input := []struct { - addrSpace AddressSpace + addrSpace string subnet string }{ {"default", "192.168.0.0/16"}, @@ -191,119 +463,116 @@ func TestRemoveSubnet(t *testing.T) { {"splane", "2002:1:2:3:4:5:ffff::/112"}, } - for _, i := range input { - _, sub, err := net.ParseCIDR(i.subnet) - if err != nil { - t.Fatalf("Wrong input, Can't proceed: %s", err.Error()) - } - err = a.AddSubnet(i.addrSpace, &SubnetInfo{Subnet: sub}) - if err != nil { + poolIDs := make([]string, len(input)) + + for ind, i := range input { + if poolIDs[ind], _, _, err = a.RequestPool(i.addrSpace, i.subnet, "", nil, false); err != nil { t.Fatalf("Failed to apply input. Can't proceed: %s", err.Error()) } } - _, sub, _ := net.ParseCIDR("172.17.0.0/16") - a.RemoveSubnet("default", sub) - if len(a.subnets) != 7 { - t.Fatalf("Failed to remove subnet info") - } - list := a.getSubnetList("default", v4) - if len(list) != 257 { - t.Fatalf("Failed to effectively remove subnet address space") - } - - _, sub, _ = net.ParseCIDR("2002:1:2:3:4:5:ffff::/112") - a.RemoveSubnet("default", sub) - if len(a.subnets) != 6 { - t.Fatalf("Failed to remove subnet info") - } - list = a.getSubnetList("default", v6) - if len(list) != 0 { - t.Fatalf("Failed to effectively remove subnet address space") - } - - _, sub, _ = net.ParseCIDR("2002:1:2:3:4:5:6::/112") - a.RemoveSubnet("splane", sub) - if len(a.subnets) != 5 { - t.Fatalf("Failed to remove subnet info") - } - list = a.getSubnetList("splane", v6) - if len(list) != 1 { - t.Fatalf("Failed to effectively remove subnet address space") - } -} - -func TestGetInternalSubnets(t *testing.T) { - // This function tests the splitting of a parent subnet in small host subnets. - // The splitting is controlled by the max host size, which is the first parameter - // passed to the function. It basically says if the parent subnet host size is - // greater than the max host size, split the parent subnet into N internal small - // subnets with host size = max host size to cover the same address space. - - input := []struct { - internalHostSize int - parentSubnet string - firstIntSubnet string - lastIntSubnet string - }{ - // Test 8 bits prefix network - {24, "10.0.0.0/8", "10.0.0.0/8", "10.0.0.0/8"}, - {16, "10.0.0.0/8", "10.0.0.0/16", "10.255.0.0/16"}, - {8, "10.0.0.0/8", "10.0.0.0/24", "10.255.255.0/24"}, - // Test 16 bits prefix network - {16, "192.168.0.0/16", "192.168.0.0/16", "192.168.0.0/16"}, - {8, "192.168.0.0/16", "192.168.0.0/24", "192.168.255.0/24"}, - // Test 24 bits prefix network - {16, "192.168.57.0/24", "192.168.57.0/24", "192.168.57.0/24"}, - {8, "192.168.57.0/24", "192.168.57.0/24", "192.168.57.0/24"}, - // Test non byte multiple host size - {24, "10.0.0.0/8", "10.0.0.0/8", "10.0.0.0/8"}, - {20, "10.0.0.0/12", "10.0.0.0/12", "10.0.0.0/12"}, - {20, "10.128.0.0/12", "10.128.0.0/12", "10.128.0.0/12"}, - {12, "10.16.0.0/16", "10.16.0.0/20", "10.16.240.0/20"}, - {13, "10.0.0.0/8", "10.0.0.0/19", "10.255.224.0/19"}, - {15, "10.0.0.0/8", "10.0.0.0/17", "10.255.128.0/17"}, - // Test v6 network - {16, "2002:1:2:3:4:5:6000::/110", "2002:1:2:3:4:5:6000:0/112", "2002:1:2:3:4:5:6003:0/112"}, - {16, "2002:1:2:3:4:5:ff00::/104", "2002:1:2:3:4:5:ff00:0/112", "2002:1:2:3:4:5:ffff:0/112"}, - {12, "2002:1:2:3:4:5:ffff::/112", "2002:1:2:3:4:5:ffff:0/116", "2002:1:2:3:4:5:ffff:f000/116"}, - {11, "2002:1:2:3:4:5:ffff::/112", "2002:1:2:3:4:5:ffff:0/117", "2002:1:2:3:4:5:ffff:f800/117"}, - } - - for _, d := range input { - assertInternalSubnet(t, d.internalHostSize, d.parentSubnet, d.firstIntSubnet, d.lastIntSubnet) + for ind, id := range poolIDs { + if err := a.ReleasePool(id); err != nil { + t.Fatalf("Failed to release poolID %s (%d)", id, ind) + } } } func TestGetSameAddress(t *testing.T) { - a, err := NewAllocator(nil) + a, err := NewAllocator(nil, nil) if err != nil { t.Fatal(err) } - addSpace := AddressSpace("giallo") - _, subnet, _ := net.ParseCIDR("192.168.100.0/24") - if err := a.AddSubnet(addSpace, &SubnetInfo{Subnet: subnet}); err != nil { + pid, _, _, err := a.RequestPool("giallo", "192.168.100.0/24", "", nil, false) + if err != nil { t.Fatal(err) } ip := net.ParseIP("192.168.100.250") - req := &AddressRequest{Subnet: *subnet, Address: ip} - - _, err = a.Request(addSpace, req) + _, _, err = a.RequestAddress(pid, ip, nil) if err != nil { t.Fatal(err) } - _, err = a.Request(addSpace, req) + _, _, err = a.RequestAddress(pid, ip, nil) if err == nil { t.Fatal(err) } } +func TestRequestReleaseAddressFromSubPool(t *testing.T) { + a, err := NewAllocator(nil, nil) + if err != nil { + t.Fatal(err) + } + + poolID, _, _, err := a.RequestPool("rosso", "172.28.0.0/16", "172.28.30.0/24", nil, false) + if err != nil { + t.Fatal(err) + } + + var ip *net.IPNet + expected := &net.IPNet{IP: net.IP{172, 28, 30, 255}, Mask: net.IPMask{255, 255, 0, 0}} + for err == nil { + var c *net.IPNet + if c, _, err = a.RequestAddress(poolID, nil, nil); err == nil { + ip = c + } + } + if err != ipamapi.ErrNoAvailableIPs { + t.Fatal(err) + } + if !types.CompareIPNet(expected, ip) { + t.Fatalf("Unexpected last IP from subpool. Expected: %s. Got: %v.", expected, ip) + } + rp := &net.IPNet{IP: net.IP{172, 28, 30, 97}, Mask: net.IPMask{255, 255, 0, 0}} + if err = a.ReleaseAddress(poolID, rp.IP); err != nil { + t.Fatal(err) + } + if ip, _, err = a.RequestAddress(poolID, nil, nil); err != nil { + t.Fatal(err) + } + if !types.CompareIPNet(rp, ip) { + t.Fatalf("Unexpected IP from subpool. Expected: %s. Got: %v.", rp, ip) + } + + _, _, _, err = a.RequestPool("rosso", "10.0.0.0/8", "10.0.0.0/16", nil, false) + if err != nil { + t.Fatal(err) + } + poolID, _, _, err = a.RequestPool("rosso", "10.0.0.0/16", "10.0.0.0/24", nil, false) + if err != nil { + t.Fatal(err) + } + expected = &net.IPNet{IP: net.IP{10, 0, 0, 255}, Mask: net.IPMask{255, 255, 0, 0}} + for err == nil { + var c *net.IPNet + if c, _, err = a.RequestAddress(poolID, nil, nil); err == nil { + ip = c + } + } + if err != ipamapi.ErrNoAvailableIPs { + t.Fatal(err) + } + if !types.CompareIPNet(expected, ip) { + t.Fatalf("Unexpected last IP from subpool. Expected: %s. Got: %v.", expected, ip) + } + rp = &net.IPNet{IP: net.IP{10, 0, 0, 79}, Mask: net.IPMask{255, 255, 0, 0}} + if err = a.ReleaseAddress(poolID, rp.IP); err != nil { + t.Fatal(err) + } + if ip, _, err = a.RequestAddress(poolID, nil, nil); err != nil { + t.Fatal(err) + } + if !types.CompareIPNet(rp, ip) { + t.Fatalf("Unexpected IP from subpool. Expected: %s. Got: %v.", rp, ip) + } +} + func TestGetAddress(t *testing.T) { input := []string{ - /*"10.0.0.0/8", "10.0.0.0/9", */ "10.0.0.0/10", "10.0.0.0/11", "10.0.0.0/12", "10.0.0.0/13", "10.0.0.0/14", + /*"10.0.0.0/8", "10.0.0.0/9", "10.0.0.0/10",*/ "10.0.0.0/11", "10.0.0.0/12", "10.0.0.0/13", "10.0.0.0/14", "10.0.0.0/15", "10.0.0.0/16", "10.0.0.0/17", "10.0.0.0/18", "10.0.0.0/19", "10.0.0.0/20", "10.0.0.0/21", "10.0.0.0/22", "10.0.0.0/23", "10.0.0.0/24", "10.0.0.0/25", "10.0.0.0/26", "10.0.0.0/27", "10.0.0.0/28", "10.0.0.0/29", "10.0.0.0/30", "10.0.0.0/31"} @@ -313,90 +582,65 @@ func TestGetAddress(t *testing.T) { } } -func TestGetSubnetList(t *testing.T) { - a, err := NewAllocator(nil) - if err != nil { - t.Fatal(err) - } - input := []struct { - addrSpace AddressSpace - subnet string - }{ - {"default", "192.168.0.0/16"}, - {"default", "172.17.0.0/16"}, - {"default", "10.0.0.0/8"}, - {"default", "2002:1:2:3:4:5:6::/112"}, - {"default", "2002:1:2:3:4:5:ffff::/112"}, - {"splane", "172.17.0.0/16"}, - {"splane", "10.0.0.0/8"}, - {"splane", "2002:1:2:3:4:5:ff00::/104"}, - } - - for _, i := range input { - _, sub, err := net.ParseCIDR(i.subnet) - if err != nil { - t.Fatalf("Wrong input, Can't proceed: %s", err.Error()) - } - err = a.AddSubnet(i.addrSpace, &SubnetInfo{Subnet: sub}) - if err != nil { - t.Fatalf("Failed to apply input. Can't proceed: %s", err.Error()) - } - } - - list := a.getSubnetList("default", v4) - if len(list) != 258 { - t.Fatalf("Incorrect number of internal subnets for ipv4 version. Expected 258. Got %d.", len(list)) - } - list = a.getSubnetList("splane", v4) - if len(list) != 257 { - t.Fatalf("Incorrect number of internal subnets for ipv4 version. Expected 257. Got %d.", len(list)) - } - - list = a.getSubnetList("default", v6) - if len(list) != 2 { - t.Fatalf("Incorrect number of internal subnets for ipv6 version. Expected 2. Got %d.", len(list)) - } - list = a.getSubnetList("splane", v6) - if len(list) != 256 { - t.Fatalf("Incorrect number of internal subnets for ipv6 version. Expected 256. Got %d.", len(list)) - } - -} - func TestRequestSyntaxCheck(t *testing.T) { var ( - subnet = "192.168.0.0/16" - addSpace = AddressSpace("green") + pool = "192.168.0.0/16" + subPool = "192.168.0.0/24" + addrSpace = "green" ) - a, err := NewAllocator(nil) - if err != nil { - t.Fatal(err) - } + a, _ := NewAllocator(nil, nil) - // Add subnet and create base request - _, sub, _ := net.ParseCIDR(subnet) - a.AddSubnet(addSpace, &SubnetInfo{Subnet: sub}) - req := &AddressRequest{Subnet: *sub} - - // Empty address space request - _, err = a.Request("", req) + _, _, _, err := a.RequestPool("", pool, "", nil, false) if err == nil { t.Fatalf("Failed to detect wrong request: empty address space") } - // Preferred address from different subnet in request - req.Address = net.ParseIP("172.17.0.23") - _, err = a.Request(addSpace, req) + _, _, _, err = a.RequestPool("", pool, subPool, nil, false) + if err == nil { + t.Fatalf("Failed to detect wrong request: empty address space") + } + + _, _, _, err = a.RequestPool(addrSpace, "", subPool, nil, false) + if err == nil { + t.Fatalf("Failed to detect wrong request: subPool specified and no pool") + } + + pid, _, _, err := a.RequestPool(addrSpace, pool, subPool, nil, false) + if err != nil { + t.Fatalf("Unexpected failure: %v", err) + } + + _, _, err = a.RequestAddress("", nil, nil) + if err == nil { + t.Fatalf("Failed to detect wrong request: no pool id specified") + } + + ip := net.ParseIP("172.17.0.23") + _, _, err = a.RequestAddress(pid, ip, nil) if err == nil { t.Fatalf("Failed to detect wrong request: preferred IP from different subnet") } - // Preferred address specified and nil subnet - req = &AddressRequest{Address: net.ParseIP("172.17.0.23")} - _, err = a.Request(addSpace, req) + ip = net.ParseIP("192.168.0.50") + _, _, err = a.RequestAddress(pid, ip, nil) + if err != nil { + t.Fatalf("Unexpected failure: %v", err) + } + + err = a.ReleaseAddress("", ip) if err == nil { - t.Fatalf("Failed to detect wrong request: subnet not specified but preferred address specified") + t.Fatalf("Failed to detect wrong request: no pool id specified") + } + + err = a.ReleaseAddress(pid, nil) + if err == nil { + t.Fatalf("Failed to detect wrong request: no pool id specified") + } + + err = a.ReleaseAddress(pid, ip) + if err != nil { + t.Fatalf("Unexpected failure: %v", err) } } @@ -433,18 +677,15 @@ func TestRequest(t *testing.T) { func TestRelease(t *testing.T) { var ( err error - req *AddressRequest subnet = "192.168.0.0/16" ) - _, sub, _ := net.ParseCIDR(subnet) - a := getAllocator(t, sub) - req = &AddressRequest{Subnet: *sub} - bm := a.addresses[subnetKey{"default", subnet, subnet}] + a, pid := getAllocator(t, subnet) + bm := a.addresses[SubnetKey{"default", subnet, ""}] // Allocate all addresses - for err != ErrNoAvailableIPs { - _, err = a.Request("default", req) + for err != ipamapi.ErrNoAvailableIPs { + _, _, err = a.RequestAddress(pid, nil, nil) } toRelease := []struct { @@ -475,45 +716,24 @@ func TestRelease(t *testing.T) { } // One by one, relase the address and request again. We should get the same IP - req = &AddressRequest{Subnet: *sub} for i, inp := range toRelease { - address := net.ParseIP(inp.address) - a.Release("default", address) + ip0 := net.ParseIP(inp.address) + a.ReleaseAddress(pid, ip0) if bm.Unselected() != 1 { t.Fatalf("Failed to update free address count after release. Expected %d, Found: %d", i+1, bm.Unselected()) } - rsp, err := a.Request("default", req) + nw, _, err := a.RequestAddress(pid, nil, nil) if err != nil { t.Fatalf("Failed to obtain the address: %s", err.Error()) } - if !address.Equal(rsp.Address) { - t.Fatalf("Failed to obtain the same address. Expected: %s, Got: %s", address, rsp.Address) + ip := nw.IP + if !ip0.Equal(ip) { + t.Fatalf("Failed to obtain the same address. Expected: %s, Got: %s", ip0, ip) } } } -func assertInternalSubnet(t *testing.T, hostSize int, bigSubnet, firstSmall, lastSmall string) { - _, subnet, _ := net.ParseCIDR(bigSubnet) - list, _ := getInternalSubnets(subnet, hostSize) - count := 1 - ones, bits := subnet.Mask.Size() - diff := bits - ones - int(hostSize) - if diff > 0 { - count <<= uint(diff) - } - - if len(list) != count { - t.Fatalf("Wrong small subnets number. Expected: %d, Got: %d", count, len(list)) - } - if firstSmall != list[0].String() { - t.Fatalf("Wrong first small subent. Expected: %v, Got: %v", firstSmall, list[0]) - } - if lastSmall != list[count-1].String() { - t.Fatalf("Wrong last small subent. Expected: %v, Got: %v", lastSmall, list[count-1]) - } -} - func assertGetAddress(t *testing.T, subnet string) { var ( err error @@ -526,15 +746,15 @@ func assertGetAddress(t *testing.T, subnet string) { zeroes := bits - ones numAddresses := 1 << uint(zeroes) - bm, err := bitseq.NewHandle("ipam_test", nil, "default/192.168.0.0/24", uint32(numAddresses)) + bm, err := bitseq.NewHandle("ipam_test", nil, "default/"+subnet, uint32(numAddresses)) if err != nil { t.Fatal(err) } start := time.Now() run := 0 - for err != ErrNoAvailableIPs { - _, err = a.getAddress(sub, bm, nil, v4) + for err != ipamapi.ErrNoAvailableIPs { + _, err = a.getAddress(sub, bm, nil, nil) run++ } if printTime { @@ -554,59 +774,52 @@ func assertGetAddress(t *testing.T, subnet string) { func assertNRequests(t *testing.T, subnet string, numReq int, lastExpectedIP string) { var ( err error - req *AddressRequest - rsp *AddressResponse + nw *net.IPNet printTime = false ) - _, sub, _ := net.ParseCIDR(subnet) lastIP := net.ParseIP(lastExpectedIP) - - a := getAllocator(t, sub) - req = &AddressRequest{Subnet: *sub} + a, pid := getAllocator(t, subnet) i := 0 start := time.Now() for ; i < numReq; i++ { - rsp, err = a.Request("default", req) + nw, _, err = a.RequestAddress(pid, nil, nil) } if printTime { fmt.Printf("\nTaken %v, to allocate %d addresses on %s\n", time.Since(start), numReq, subnet) } - if !lastIP.Equal(rsp.Address) { - t.Fatalf("Wrong last IP. Expected %s. Got: %s (err: %v, ind: %d)", lastExpectedIP, rsp.Address.String(), err, i) + if !lastIP.Equal(nw.IP) { + t.Fatalf("Wrong last IP. Expected %s. Got: %s (err: %v, ind: %d)", lastExpectedIP, nw.IP.String(), err, i) } } -func benchmarkRequest(subnet *net.IPNet) { +func benchmarkRequest(subnet string) { var err error - a, _ := NewAllocator(nil) - a.internalHostSize = 20 - a.AddSubnet("default", &SubnetInfo{Subnet: subnet}) - - req := &AddressRequest{Subnet: *subnet} - for err != ErrNoAvailableIPs { - _, err = a.Request("default", req) + a, _ := NewAllocator(nil, nil) + pid, _, _, _ := a.RequestPool("default", subnet, "", nil, false) + for err != ipamapi.ErrNoAvailableIPs { + _, _, err = a.RequestAddress(pid, nil, nil) } } -func benchMarkRequest(subnet *net.IPNet, b *testing.B) { +func benchMarkRequest(subnet string, b *testing.B) { for n := 0; n < b.N; n++ { benchmarkRequest(subnet) } } func BenchmarkRequest_24(b *testing.B) { - benchmarkRequest(&net.IPNet{IP: []byte{10, 0, 0, 0}, Mask: []byte{255, 255, 255, 0}}) + benchmarkRequest("10.0.0.0/24") } func BenchmarkRequest_16(b *testing.B) { - benchmarkRequest(&net.IPNet{IP: []byte{10, 0, 0, 0}, Mask: []byte{255, 255, 0, 0}}) + benchmarkRequest("10.0.0.0/16") } func BenchmarkRequest_8(b *testing.B) { - benchmarkRequest(&net.IPNet{IP: []byte{10, 0, 0, 0}, Mask: []byte{255, 0xfc, 0, 0}}) + benchmarkRequest("10.0.0.0/8") } diff --git a/libnetwork/ipam/contract.go b/libnetwork/ipam/contract.go deleted file mode 100644 index 618ada2c52..0000000000 --- a/libnetwork/ipam/contract.go +++ /dev/null @@ -1,100 +0,0 @@ -// Package ipam that specifies the contract the IPAM plugin need to satisfy, -// decoupling IPAM interface and implementation. -package ipam - -import ( - "errors" - "net" -) - -/************** - * IPAM Errors - **************/ - -// ErrIpamNotAvailable is returned when the plugin prviding the IPAM service is not available -var ( - ErrInvalidIpamService = errors.New("Invalid IPAM Service") - ErrInvalidIpamConfigService = errors.New("Invalid IPAM Config Service") - ErrIpamNotAvailable = errors.New("IPAM Service not available") - ErrIpamInternalError = errors.New("IPAM Internal Error") - ErrInvalidAddressSpace = errors.New("Invalid Address Space") - ErrInvalidSubnet = errors.New("Invalid Subnet") - ErrInvalidRequest = errors.New("Invalid Request") - ErrSubnetNotFound = errors.New("Subnet not found") - ErrOverlapSubnet = errors.New("Subnet overlaps with existing subnet on this address space") - ErrNoAvailableSubnet = errors.New("No available subnet") - ErrNoAvailableIPs = errors.New("No available addresses on subnet") - ErrIPAlreadyAllocated = errors.New("Address already in use") - ErrIPOutOfRange = errors.New("Requested address is out of range") - ErrSubnetAlreadyRegistered = errors.New("Subnet already registered on this address space") - ErrBadSubnet = errors.New("Address space does not contain specified subnet") -) - -// AddressSpace identifies a unique pool of network addresses -type AddressSpace string - -/******************************* - * IPAM Configuration Interface - *******************************/ - -// Config represents the interface the IPAM service plugins must implement -// in order to allow injection/modification of IPAM database. -// Common key is a addressspace -type Config interface { - // AddSubnet adds a subnet to the specified address space - AddSubnet(AddressSpace, *SubnetInfo) error - // RemoveSubnet removes a subnet from the specified address space - RemoveSubnet(AddressSpace, *net.IPNet) error - // AddVendorInfo adds Vendor specific data - AddVendorInfo([]byte) error -} - -// SubnetInfo contains the information subnet hosts need in order to communicate -type SubnetInfo struct { - Subnet *net.IPNet - Gateway net.IP - OpaqueData []byte // Vendor specific -} - -/************************* - * IPAM Service Interface - *************************/ - -// IPAM defines the interface that needs to be implemented by IPAM service plugin -// Common key is a unique address space identifier -type IPAM interface { - // Request address from the specified address space - Request(AddressSpace, *AddressRequest) (*AddressResponse, error) - // Separate API for IPv6 - RequestV6(AddressSpace, *AddressRequest) (*AddressResponse, error) - // Release the address from the specified address space - Release(AddressSpace, net.IP) -} - -// AddressRequest encloses the information a client -// needs to pass to IPAM when requesting an address -type AddressRequest struct { - Subnet net.IPNet // Preferred subnet pool (Optional) - Address net.IP // Preferred address (Optional) - Endpoint string // For static IP mapping (Optional) - OpaqueData []byte // Vendor specific request data -} - -// Validate runs syntactic validation on this AddressRequest object -func (req *AddressRequest) Validate() error { - var byteArray []byte = req.Address - - // Check preferred address - if byteArray != nil && (&req.Subnet == nil || !req.Subnet.Contains(req.Address)) { - return ErrInvalidRequest - } - - return nil -} - -// AddressResponse represents the IPAM service's -// response to an address request -type AddressResponse struct { - Address net.IP - Subnet SubnetInfo -} diff --git a/libnetwork/ipam/store.go b/libnetwork/ipam/store.go index 8794a3d194..e3d327f1f9 100644 --- a/libnetwork/ipam/store.go +++ b/libnetwork/ipam/store.go @@ -2,7 +2,6 @@ package ipam import ( "encoding/json" - "net" log "github.com/Sirupsen/logrus" "github.com/docker/libnetwork/datastore" @@ -13,14 +12,14 @@ import ( func (a *Allocator) Key() []string { a.Lock() defer a.Unlock() - return []string{a.App, a.ID} + return []string{dsConfigKey} } // KeyPrefix returns the immediate parent key that can be used for tree walk func (a *Allocator) KeyPrefix() []string { a.Lock() defer a.Unlock() - return []string{a.App} + return []string{dsConfigKey} } // Value marshals the data to be stored in the KV store @@ -31,9 +30,14 @@ func (a *Allocator) Value() []byte { if a.subnets == nil { return []byte{} } + m := map[string]interface{}{} + for k, v := range a.subnets { + m[k.String()] = v + } - b, err := subnetsToByteArray(a.subnets) + b, err := json.Marshal(m) if err != nil { + log.Warnf("Failed to marshal ipam configured subnets") return nil } return b @@ -41,52 +45,19 @@ func (a *Allocator) Value() []byte { // SetValue unmarshalls the data from the KV store. func (a *Allocator) SetValue(value []byte) error { - a.subnets = byteArrayToSubnets(value) - return nil -} - -func subnetsToByteArray(m map[subnetKey]*SubnetInfo) ([]byte, error) { - if m == nil { - return nil, nil - } - - mm := make(map[string]string, len(m)) - for k, v := range m { - mm[k.String()] = v.Subnet.String() - } - - return json.Marshal(mm) -} - -func byteArrayToSubnets(ba []byte) map[subnetKey]*SubnetInfo { - m := map[subnetKey]*SubnetInfo{} - - if ba == nil || len(ba) == 0 { - return m - } - - var mm map[string]string - err := json.Unmarshal(ba, &mm) + var m map[string]*PoolData + err := json.Unmarshal(value, &m) if err != nil { - log.Warnf("Failed to decode subnets byte array: %v", err) - return m + return err } - for ks, vs := range mm { - sk := subnetKey{} - if err := sk.FromString(ks); err != nil { - log.Warnf("Failed to decode subnets map entry: (%s, %s)", ks, vs) - continue + for ks, d := range m { + k := SubnetKey{} + if err := k.FromString(ks); err != nil { + return err } - si := &SubnetInfo{} - _, nw, err := net.ParseCIDR(vs) - if err != nil { - log.Warnf("Failed to decode subnets map entry value: (%s, %s)", ks, vs) - continue - } - si.Subnet = nw - m[sk] = si + a.subnets[k] = d } - return m + return nil } // Index returns the latest DB Index as seen by this object @@ -130,7 +101,6 @@ func (a *Allocator) watchForChanges() error { select { case kvPair := <-kvpChan: if kvPair != nil { - log.Debugf("Got notification for key %v: %v", kvPair.Key, kvPair.Value) a.subnetConfigFromStore(kvPair) } } diff --git a/libnetwork/ipamapi/contract.go b/libnetwork/ipamapi/contract.go new file mode 100644 index 0000000000..6693b5ac82 --- /dev/null +++ b/libnetwork/ipamapi/contract.go @@ -0,0 +1,72 @@ +// Package ipamapi specifies the contract the IPAM service (built-in or remote) needs to satisfy. +package ipamapi + +import ( + "errors" + "net" +) + +/******************** + * IPAM plugin types + ********************/ + +const ( + // DefaultIPAM is the name of the built-in default ipam driver + DefaultIPAM = "default" + // PluginEndpointType represents the Endpoint Type used by Plugin system + PluginEndpointType = "IPAM" +) + +// Callback provides a Callback interface for registering an IPAM instance into LibNetwork +type Callback interface { + // RegisterDriver provides a way for Remote drivers to dynamically register new NetworkType and associate with a ipam instance + RegisterIpamDriver(name string, driver Ipam) error +} + +/************** + * IPAM Errors + **************/ + +// Weel-known errors returned by IPAM +var ( + ErrInvalidIpamService = errors.New("Invalid IPAM Service") + ErrInvalidIpamConfigService = errors.New("Invalid IPAM Config Service") + ErrIpamNotAvailable = errors.New("IPAM Service not available") + ErrIpamInternalError = errors.New("IPAM Internal Error") + ErrInvalidAddressSpace = errors.New("Invalid Address Space") + ErrInvalidPool = errors.New("Invalid Address Pool") + ErrInvalidSubPool = errors.New("Invalid Address SubPool") + ErrInvalidRequest = errors.New("Invalid Request") + ErrPoolNotFound = errors.New("Address Pool not found") + ErrOverlapPool = errors.New("Address pool overlaps with existing pool on this address space") + ErrNoAvailablePool = errors.New("No available pool") + ErrNoAvailableIPs = errors.New("No available addresses on this pool") + ErrIPAlreadyAllocated = errors.New("Address already in use") + ErrIPOutOfRange = errors.New("Requested address is out of range") + ErrPoolOverlap = errors.New("Pool overlaps with other one on this address space") + ErrBadPool = errors.New("Address space does not contain specified address pool") +) + +/******************************* + * IPAM Service Interface + *******************************/ + +// Ipam represents the interface the IPAM service plugins must implement +// in order to allow injection/modification of IPAM database. +type Ipam interface { + // GetDefaultAddressSpaces returns the default local and global address spaces for this ipam + GetDefaultAddressSpaces() (string, string, error) + // RequestPool returns an address pool along with its unique id. Address space is a mandatory field + // which denotes a set of non-overlapping pools. pool describes the pool of addrresses in CIDR notation. + // subpool indicates a smaller range of addresses from the pool, for now it is specified in CIDR notation. + // Both pool and subpool are non mandatory fields. When they are not specified, Ipam driver may choose to + // return a self chosen pool for this request. In such case the v6 flag needs to be set appropriately so + // that the driver would return the expected ip version pool. + RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) + // ReleasePool releases the address pool identified by the passed id + ReleasePool(poolID string) error + // Request address from the specified pool ID. Input options or preferred IP can be passed. + RequestAddress(string, net.IP, map[string]string) (*net.IPNet, map[string]string, error) + // Release the address from the specified pool ID + ReleaseAddress(string, net.IP) error +} diff --git a/libnetwork/ipams/builtin/builtin.go b/libnetwork/ipams/builtin/builtin.go new file mode 100644 index 0000000000..707e001f73 --- /dev/null +++ b/libnetwork/ipams/builtin/builtin.go @@ -0,0 +1,35 @@ +package builtin + +import ( + "fmt" + + "github.com/docker/libnetwork/datastore" + "github.com/docker/libnetwork/ipam" + "github.com/docker/libnetwork/ipamapi" +) + +// Init registers the built-in ipam service with libnetwork +func Init(ic ipamapi.Callback, l, g interface{}) error { + var ( + ok bool + localDs, globalDs datastore.DataStore + ) + + if l != nil { + if localDs, ok = l.(datastore.DataStore); !ok { + return fmt.Errorf("incorrect local datastore passed to built-in ipam init") + } + } + + if g != nil { + if globalDs, ok = g.(datastore.DataStore); !ok { + return fmt.Errorf("incorrect global datastore passed to built-in ipam init") + } + } + a, err := ipam.NewAllocator(localDs, globalDs) + if err != nil { + return err + } + + return ic.RegisterIpamDriver(ipamapi.DefaultIPAM, a) +} diff --git a/libnetwork/ipams/remote/api/api.go b/libnetwork/ipams/remote/api/api.go new file mode 100644 index 0000000000..7d60eb311d --- /dev/null +++ b/libnetwork/ipams/remote/api/api.go @@ -0,0 +1,81 @@ +// Package api defines the data structure to be used in the request/response +// messages between libnetwork and the remote ipam plugin +package api + +import ( + "net" +) + +// Response is the basic response structure used in all responses +type Response struct { + Error string +} + +// IsSuccess returns wheter the plugin response is successful +func (r *Response) IsSuccess() bool { + return r.Error == "" +} + +// GetError returns the error from the response, if any. +func (r *Response) GetError() string { + return r.Error +} + +// GetAddressSpacesResponse is the response to the ``get default address spaces`` request message +type GetAddressSpacesResponse struct { + Response + LocalDefaultAddressSpace string + GlobalDefaultAddressSpace string +} + +// RequestPoolRequest represents the expected data in a ``request address pool`` request message +type RequestPoolRequest struct { + AddressSpace string + Pool string + SubPool string + Options map[string]string + V6 bool +} + +// RequestPoolResponse represents the response message to a ``request address pool`` request +type RequestPoolResponse struct { + Response + PoolID string + Pool *net.IPNet + Data map[string]string +} + +// ReleasePoolRequest represents the expected data in a ``release address pool`` request message +type ReleasePoolRequest struct { + PoolID string +} + +// ReleasePoolResponse represents the response message to a ``release address pool`` request +type ReleasePoolResponse struct { + Response +} + +// RequestAddressRequest represents the expected data in a ``request address`` request message +type RequestAddressRequest struct { + PoolID string + Address net.IP + Options map[string]string +} + +// RequestAddressResponse represents the expected data in the response message to a ``request address`` request +type RequestAddressResponse struct { + Response + Address *net.IPNet + Data map[string]string +} + +// ReleaseAddressRequest represents the expected data in a ``release address`` request message +type ReleaseAddressRequest struct { + PoolID string + Address net.IP +} + +// ReleaseAddressResponse represents the response message to a ``release address`` request +type ReleaseAddressResponse struct { + Response +} diff --git a/libnetwork/ipams/remote/remote.go b/libnetwork/ipams/remote/remote.go new file mode 100644 index 0000000000..f5ddbd4764 --- /dev/null +++ b/libnetwork/ipams/remote/remote.go @@ -0,0 +1,92 @@ +package remote + +import ( + "fmt" + "net" + + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/plugins" + "github.com/docker/libnetwork/ipamapi" + "github.com/docker/libnetwork/ipams/remote/api" +) + +type allocator struct { + endpoint *plugins.Client + name string +} + +// PluginResponse is the interface for the plugin request responses +type PluginResponse interface { + IsSuccess() bool + GetError() string +} + +func newAllocator(name string, client *plugins.Client) ipamapi.Ipam { + a := &allocator{name: name, endpoint: client} + return a +} + +// Init registers a remote ipam when its plugin is activated +func Init(cb ipamapi.Callback, l, g interface{}) error { + plugins.Handle(ipamapi.PluginEndpointType, func(name string, client *plugins.Client) { + if err := cb.RegisterIpamDriver(name, newAllocator(name, client)); err != nil { + log.Errorf("error registering remote ipam %s due to %v", name, err) + } + }) + return nil +} + +func (a *allocator) call(methodName string, arg interface{}, retVal PluginResponse) error { + method := ipamapi.PluginEndpointType + "." + methodName + err := a.endpoint.Call(method, arg, retVal) + if err != nil { + return err + } + if !retVal.IsSuccess() { + return fmt.Errorf("remote: %s", retVal.GetError()) + } + return nil +} + +// GetDefaultAddressSpaces returns the local and global default address spaces +func (a *allocator) GetDefaultAddressSpaces() (string, string, error) { + res := &api.GetAddressSpacesResponse{} + if err := a.call("GetDefaultAddressSpaces", nil, res); err != nil { + return "", "", err + } + return res.LocalDefaultAddressSpace, res.GlobalDefaultAddressSpace, nil +} + +// RequestPool requests an address pool in the specified address space +func (a *allocator) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) { + req := &api.RequestPoolRequest{AddressSpace: addressSpace, Pool: pool, SubPool: subPool, Options: options, V6: v6} + res := &api.RequestPoolResponse{} + if err := a.call("RequestPool", req, res); err != nil { + return "", nil, nil, err + } + return res.PoolID, res.Pool, res.Data, nil +} + +// ReleasePool removes an address pool from the specified address space +func (a *allocator) ReleasePool(poolID string) error { + req := &api.ReleasePoolRequest{PoolID: poolID} + res := &api.ReleasePoolResponse{} + return a.call("ReleasePool", req, res) +} + +// RequestAddress requests an address from the address pool +func (a *allocator) RequestAddress(poolID string, address net.IP, options map[string]string) (*net.IPNet, map[string]string, error) { + req := &api.RequestAddressRequest{PoolID: poolID, Address: address, Options: options} + res := &api.RequestAddressResponse{} + if err := a.call("RequestAddress", req, res); err != nil { + return nil, nil, err + } + return res.Address, res.Data, nil +} + +// ReleaseAddress releases the address from the specified address pool +func (a *allocator) ReleaseAddress(poolID string, address net.IP) error { + req := &api.ReleaseAddressRequest{PoolID: poolID, Address: address} + res := &api.ReleaseAddressResponse{} + return a.call("ReleaseAddress", req, res) +} diff --git a/libnetwork/libnetwork_internal_test.go b/libnetwork/libnetwork_internal_test.go index 21f1fae88c..2b5e20dd8d 100644 --- a/libnetwork/libnetwork_internal_test.go +++ b/libnetwork/libnetwork_internal_test.go @@ -1,10 +1,15 @@ package libnetwork import ( + "encoding/json" + "fmt" + "net" "testing" "github.com/docker/libnetwork/datastore" "github.com/docker/libnetwork/driverapi" + "github.com/docker/libnetwork/netlabel" + "github.com/docker/libnetwork/types" ) func TestDriverRegistration(t *testing.T) { @@ -31,3 +36,264 @@ func SetTestDataStore(c NetworkController, custom datastore.DataStore) { con := c.(*controller) con.globalStore = custom } + +func TestNetworkMarshalling(t *testing.T) { + n := &network{ + name: "Miao", + id: "abccba", + ipamType: "default", + addrSpace: "viola", + networkType: "bridge", + endpointCnt: 27, + enableIPv6: true, + persist: true, + ipamV4Config: []*IpamConf{ + &IpamConf{ + PreferredPool: "10.2.0.0/16", + SubPool: "10.2.0.0/24", + Options: map[string]string{ + netlabel.MacAddress: "a:b:c:d:e:f", + }, + Gateway: "", + AuxAddresses: nil, + }, + &IpamConf{ + PreferredPool: "10.2.0.0/16", + SubPool: "10.2.1.0/24", + Options: nil, + Gateway: "10.2.1.254", + }, + }, + ipamV6Config: []*IpamConf{ + &IpamConf{ + PreferredPool: "abcd::/64", + SubPool: "abcd:abcd:abcd:abcd:abcd::/80", + Gateway: "abcd::29/64", + AuxAddresses: nil, + }, + }, + ipamV4Info: []*IpamInfo{ + &IpamInfo{ + PoolID: "ipoolverde123", + Meta: map[string]string{ + netlabel.Gateway: "10.2.1.255/16", + }, + IPAMData: driverapi.IPAMData{ + AddressSpace: "viola", + Pool: &net.IPNet{ + IP: net.IP{10, 2, 0, 0}, + Mask: net.IPMask{255, 255, 255, 0}, + }, + Gateway: nil, + AuxAddresses: nil, + }, + }, + &IpamInfo{ + PoolID: "ipoolblue345", + Meta: map[string]string{ + netlabel.Gateway: "10.2.1.255/16", + }, + IPAMData: driverapi.IPAMData{ + AddressSpace: "viola", + Pool: &net.IPNet{ + IP: net.IP{10, 2, 1, 0}, + Mask: net.IPMask{255, 255, 255, 0}, + }, + Gateway: &net.IPNet{IP: net.IP{10, 2, 1, 254}, Mask: net.IPMask{255, 255, 255, 0}}, + AuxAddresses: map[string]*net.IPNet{ + "ip3": &net.IPNet{IP: net.IP{10, 2, 1, 3}, Mask: net.IPMask{255, 255, 255, 0}}, + "ip5": &net.IPNet{IP: net.IP{10, 2, 1, 55}, Mask: net.IPMask{255, 255, 255, 0}}, + }, + }, + }, + &IpamInfo{ + PoolID: "weirdinfo", + IPAMData: driverapi.IPAMData{ + Gateway: &net.IPNet{ + IP: net.IP{11, 2, 1, 255}, + Mask: net.IPMask{255, 0, 0, 0}, + }, + }, + }, + }, + ipamV6Info: []*IpamInfo{ + &IpamInfo{ + PoolID: "ipoolv6", + IPAMData: driverapi.IPAMData{ + AddressSpace: "viola", + Pool: &net.IPNet{ + IP: net.IP{0xab, 0xcd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Mask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0}, + }, + Gateway: &net.IPNet{ + IP: net.IP{0xab, 0xcd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29}, + Mask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0}, + }, + AuxAddresses: nil, + }, + }, + }, + } + + b, err := json.Marshal(n) + if err != nil { + t.Fatal(err) + } + + nn := &network{} + err = json.Unmarshal(b, nn) + if err != nil { + t.Fatal(err) + } + + if n.name != nn.name || n.id != nn.id || n.networkType != nn.networkType || n.ipamType != nn.ipamType || + n.addrSpace != nn.addrSpace || n.endpointCnt != nn.endpointCnt || n.enableIPv6 != nn.enableIPv6 || + n.persist != nn.persist || !compareIpamConfList(n.ipamV4Config, nn.ipamV4Config) || + !compareIpamInfoList(n.ipamV4Info, nn.ipamV4Info) || !compareIpamConfList(n.ipamV6Config, nn.ipamV6Config) || + !compareIpamInfoList(n.ipamV6Info, nn.ipamV6Info) { + t.Fatalf("JSON marsh/unmarsh failed."+ + "\nOriginal:\n%#v\nDecoded:\n%#v"+ + "\nOriginal ipamV4Conf: %#v\n\nDecoded ipamV4Conf: %#v"+ + "\nOriginal ipamV4Info: %s\n\nDecoded ipamV4Info: %s"+ + "\nOriginal ipamV6Conf: %#v\n\nDecoded ipamV6Conf: %#v"+ + "\nOriginal ipamV6Info: %s\n\nDecoded ipamV6Info: %s", + n, nn, printIpamConf(n.ipamV4Config), printIpamConf(nn.ipamV4Config), + printIpamInfo(n.ipamV4Info), printIpamInfo(nn.ipamV4Info), + printIpamConf(n.ipamV6Config), printIpamConf(nn.ipamV6Config), + printIpamInfo(n.ipamV6Info), printIpamInfo(nn.ipamV6Info)) + } +} + +func printIpamConf(list []*IpamConf) string { + s := fmt.Sprintf("\n[]*IpamConfig{") + for _, i := range list { + s = fmt.Sprintf("%s %v,", s, i) + } + s = fmt.Sprintf("%s}", s) + return s +} + +func printIpamInfo(list []*IpamInfo) string { + s := fmt.Sprintf("\n[]*IpamInfo{") + for _, i := range list { + s = fmt.Sprintf("%s\n{\n%s\n}", s, i) + } + s = fmt.Sprintf("%s\n}", s) + return s +} + +func TestEndpointMarshalling(t *testing.T) { + ip, nw6, err := net.ParseCIDR("2001:3002:4003::122/64") + if err != nil { + t.Fatal(err) + } + nw6.IP = ip + + e := &endpoint{ + name: "Bau", + id: "efghijklmno", + sandboxID: "ambarabaciccicocco", + iface: &endpointInterface{ + mac: []byte{11, 12, 13, 14, 15, 16}, + addr: &net.IPNet{ + IP: net.IP{10, 0, 1, 23}, + Mask: net.IPMask{255, 255, 255, 0}, + }, + addrv6: nw6, + srcName: "veth12ab1314", + dstPrefix: "eth", + poolID: "poolpool", + }, + } + + b, err := json.Marshal(e) + if err != nil { + t.Fatal(err) + } + + ee := &endpoint{} + err = json.Unmarshal(b, ee) + if err != nil { + t.Fatal(err) + } + + if e.name != ee.name || e.id != ee.id || e.sandboxID != ee.sandboxID || !compareEndpointInterface(e.iface, ee.iface) { + t.Fatalf("JSON marsh/unmarsh failed.\nOriginal:\n%#v\nDecoded:\n%#v\nOriginal iface: %#v\nDecodediface:\n%#v", e, ee, e.iface, ee.iface) + } +} + +func compareEndpointInterface(a, b *endpointInterface) bool { + if a == b { + return true + } + if a == nil || b == nil { + return false + } + return a.srcName == b.srcName && a.dstPrefix == b.dstPrefix && a.poolID == b.poolID && + types.CompareIPNet(a.addr, b.addr) && types.CompareIPNet(a.addrv6, b.addrv6) +} + +func compareIpamConfList(listA, listB []*IpamConf) bool { + var a, b *IpamConf + if len(listA) != len(listB) { + return false + } + for i := 0; i < len(listA); i++ { + a = listA[i] + b = listB[i] + if a.PreferredPool != b.PreferredPool || + a.SubPool != b.SubPool || a.IsV6 != b.IsV6 || + !compareStringMaps(a.Options, b.Options) || + a.Gateway != b.Gateway || !compareStringMaps(a.AuxAddresses, b.AuxAddresses) { + return false + } + } + return true +} + +func compareIpamInfoList(listA, listB []*IpamInfo) bool { + var a, b *IpamInfo + if len(listA) != len(listB) { + return false + } + for i := 0; i < len(listA); i++ { + a = listA[i] + b = listB[i] + if a.PoolID != b.PoolID || !compareStringMaps(a.Meta, b.Meta) || + !types.CompareIPNet(a.Gateway, b.Gateway) || + a.AddressSpace != b.AddressSpace || + !types.CompareIPNet(a.Pool, b.Pool) || + !compareAddresses(a.AuxAddresses, b.AuxAddresses) { + return false + } + } + return true +} + +func compareStringMaps(a, b map[string]string) bool { + if len(a) != len(b) { + return false + } + if len(a) > 0 { + for k := range a { + if a[k] != b[k] { + return false + } + } + } + return true +} + +func compareAddresses(a, b map[string]*net.IPNet) bool { + if len(a) != len(b) { + return false + } + if len(a) > 0 { + for k := range a { + if !types.CompareIPNet(a[k], b[k]) { + return false + } + } + } + return true +} diff --git a/libnetwork/libnetwork_test.go b/libnetwork/libnetwork_test.go index a5fa7c14d0..87436835d4 100644 --- a/libnetwork/libnetwork_test.go +++ b/libnetwork/libnetwork_test.go @@ -6,7 +6,6 @@ import ( "flag" "fmt" "io/ioutil" - "net" "net/http" "net/http/httptest" "os" @@ -268,23 +267,20 @@ func TestBridge(t *testing.T) { defer testutils.SetupTestOSContext(t)() } - ip, subnet, err := net.ParseCIDR("192.168.100.1/24") + subnet, err := types.ParseCIDR("192.168.100.1/24") if err != nil { t.Fatal(err) } - subnet.IP = ip - ip, cidr, err := net.ParseCIDR("192.168.100.2/28") + cidr, err := types.ParseCIDR("192.168.100.2/28") if err != nil { t.Fatal(err) } - cidr.IP = ip - ip, cidrv6, err := net.ParseCIDR("fe90::1/96") + cidrv6, err := types.ParseCIDR("fe90::1/96") if err != nil { t.Fatal(err) } - cidrv6.IP = ip log.Debug("Adding a bridge") @@ -549,11 +545,10 @@ func TestUnknownEndpoint(t *testing.T) { defer testutils.SetupTestOSContext(t)() } - ip, subnet, err := net.ParseCIDR("192.168.100.1/24") + subnet, err := types.ParseCIDR("192.168.100.1/24") if err != nil { t.Fatal(err) } - subnet.IP = ip netOption := options.Generic{ "BridgeName": "testnetwork", @@ -1026,10 +1021,9 @@ func TestEndpointJoin(t *testing.T) { // Validate if ep.Info() only gives me IP address info and not names and gateway during CreateEndpoint() info := ep1.Info() - if iface := info.Iface(); iface != nil { - if iface.Address().IP.To4() == nil { - t.Fatalf("Invalid IP address returned: %v", iface.Address()) - } + iface := info.Iface() + if iface.Address() != nil && iface.Address().IP.To4() == nil { + t.Fatalf("Invalid IP address returned: %v", iface.Address()) } if info.Gateway().To4() != nil { @@ -1711,11 +1705,10 @@ func TestEnableIPv6(t *testing.T) { } }() - ip, cidrv6, err := net.ParseCIDR("fe80::1/64") + cidrv6, err := types.ParseCIDR("fe80::1/64") if err != nil { t.Fatal(err) } - cidrv6.IP = ip netOption := options.Generic{ netlabel.EnableIPv6: true, diff --git a/libnetwork/netlabel/labels.go b/libnetwork/netlabel/labels.go index d2db2403ed..12065525d4 100644 --- a/libnetwork/netlabel/labels.go +++ b/libnetwork/netlabel/labels.go @@ -38,6 +38,9 @@ const ( // OverlayNeighborIP constant represents overlay driver neighbor IP OverlayNeighborIP = DriverPrefix + ".overlay.neighbor_ip" + + // Gateway represents the gateway for the network + Gateway = Prefix + ".gateway" ) // Key extracts the key portion of the label diff --git a/libnetwork/network.go b/libnetwork/network.go index ea7d4ea600..416d2c1206 100644 --- a/libnetwork/network.go +++ b/libnetwork/network.go @@ -55,22 +55,94 @@ type EndpointWalker func(ep Endpoint) bool type svcMap map[string]net.IP +// IpamConf contains all the ipam related configurations for a network +type IpamConf struct { + PreferredPool string + SubPool string + Options map[string]string // IPAM input options + IsV6 bool + Gateway string + AuxAddresses map[string]string +} + +// Validate checks whether the configuration is valid +func (c *IpamConf) Validate() error { + if c.Gateway != "" && nil == net.ParseIP(c.Gateway) { + return types.BadRequestErrorf("invalid gateway address %s in IpamConf dtructure", c.Gateway) + } + return nil +} + +// IpamInfo contains all the ipam related operational info for a network +type IpamInfo struct { + PoolID string + Meta map[string]string + driverapi.IPAMData +} + +// MarshalJSON encodes IpamInfo into json message +func (i *IpamInfo) MarshalJSON() ([]byte, error) { + m := map[string]interface{}{ + "PoolID": i.PoolID, + } + v, err := json.Marshal(&i.IPAMData) + if err != nil { + return nil, err + } + m["IPAMData"] = string(v) + + if i.Meta != nil { + m["Meta"] = i.Meta + } + return json.Marshal(m) +} + +// UnmarshalJSON decodes json message into PoolData +func (i *IpamInfo) UnmarshalJSON(data []byte) error { + var ( + m map[string]interface{} + err error + ) + if err = json.Unmarshal(data, &m); err != nil { + return err + } + i.PoolID = m["PoolID"].(string) + if v, ok := m["Meta"]; ok { + b, _ := json.Marshal(v) + if err = json.Unmarshal(b, &i.Meta); err != nil { + return err + } + } + if v, ok := m["IPAMData"]; ok { + if err = json.Unmarshal([]byte(v.(string)), &i.IPAMData); err != nil { + return err + } + } + return nil +} + type network struct { - ctrlr *controller - name string - networkType string - id string - driver driverapi.Driver - enableIPv6 bool - endpointCnt uint64 - endpoints endpointTable - generic options.Generic - dbIndex uint64 - svcRecords svcMap - dbExists bool - persist bool - stopWatchCh chan struct{} - dataScope datastore.DataScope + ctrlr *controller + name string + networkType string + id string + ipamType string + driver driverapi.Driver + addrSpace string + ipamV4Config []*IpamConf + ipamV6Config []*IpamConf + ipamV4Info []*IpamInfo + ipamV6Info []*IpamInfo + enableIPv6 bool + endpointCnt uint64 + endpoints endpointTable + generic options.Generic + dbIndex uint64 + svcRecords svcMap + dbExists bool + persist bool + stopWatchCh chan struct{} + dataScope datastore.DataScope sync.Mutex } @@ -178,10 +250,42 @@ func (n *network) MarshalJSON() ([]byte, error) { netMap["name"] = n.name netMap["id"] = n.id netMap["networkType"] = n.networkType + netMap["ipamType"] = n.ipamType + netMap["addrSpace"] = n.addrSpace netMap["endpointCnt"] = n.endpointCnt netMap["enableIPv6"] = n.enableIPv6 - netMap["generic"] = n.generic + if n.generic != nil { + netMap["generic"] = n.generic + } netMap["persist"] = n.persist + if len(n.ipamV4Config) > 0 { + ics, err := json.Marshal(n.ipamV4Config) + if err != nil { + return nil, err + } + netMap["ipamV4Config"] = string(ics) + } + if len(n.ipamV4Info) > 0 { + iis, err := json.Marshal(n.ipamV4Info) + if err != nil { + return nil, err + } + netMap["ipamV4Info"] = string(iis) + } + if len(n.ipamV6Config) > 0 { + ics, err := json.Marshal(n.ipamV6Config) + if err != nil { + return nil, err + } + netMap["ipamV6Config"] = string(ics) + } + if len(n.ipamV6Info) > 0 { + iis, err := json.Marshal(n.ipamV6Info) + if err != nil { + return nil, err + } + netMap["ipamV6Info"] = string(iis) + } return json.Marshal(netMap) } @@ -193,14 +297,36 @@ func (n *network) UnmarshalJSON(b []byte) (err error) { } n.name = netMap["name"].(string) n.id = netMap["id"].(string) + n.ipamType = netMap["ipamType"].(string) + n.addrSpace = netMap["addrSpace"].(string) n.networkType = netMap["networkType"].(string) n.endpointCnt = uint64(netMap["endpointCnt"].(float64)) n.enableIPv6 = netMap["enableIPv6"].(bool) - if netMap["generic"] != nil { - n.generic = netMap["generic"].(map[string]interface{}) + if v, ok := netMap["generic"]; ok { + n.generic = v.(map[string]interface{}) } - if netMap["persist"] != nil { - n.persist = netMap["persist"].(bool) + if v, ok := netMap["persist"]; ok { + n.persist = v.(bool) + } + if v, ok := netMap["ipamV4Config"]; ok { + if err := json.Unmarshal([]byte(v.(string)), &n.ipamV4Config); err != nil { + return err + } + } + if v, ok := netMap["ipamV4Info"]; ok { + if err := json.Unmarshal([]byte(v.(string)), &n.ipamV4Info); err != nil { + return err + } + } + if v, ok := netMap["ipamV6Config"]; ok { + if err := json.Unmarshal([]byte(v.(string)), &n.ipamV6Config); err != nil { + return err + } + } + if v, ok := netMap["ipamV6Info"]; ok { + if err := json.Unmarshal([]byte(v.(string)), &n.ipamV6Info); err != nil { + return err + } } return nil } @@ -228,6 +354,16 @@ func NetworkOptionPersist(persist bool) NetworkOption { } } +// NetworkOptionIpam function returns an option setter for the ipam configuration for this network +func NetworkOptionIpam(ipamDriver string, addrSpace string, ipV4 []*IpamConf, ipV6 []*IpamConf) NetworkOption { + return func(n *network) { + n.ipamType = ipamDriver + n.addrSpace = addrSpace + n.ipamV4Config = ipV4 + n.ipamV6Config = ipV6 + } +} + func (n *network) processOptions(options ...NetworkOption) { for _, opt := range options { if opt != nil { @@ -239,9 +375,7 @@ func (n *network) processOptions(options ...NetworkOption) { func (n *network) Delete() error { var err error - n.Lock() - ctrlr := n.ctrlr - n.Unlock() + ctrlr := n.getController() ctrlr.Lock() _, ok := ctrlr.networks[n.id] @@ -278,6 +412,8 @@ func (n *network) Delete() error { return err } + n.ipamRelease() + return nil } @@ -319,7 +455,7 @@ func (n *network) addEndpoint(ep *endpoint) error { } }() - err = d.CreateEndpoint(n.id, ep.id, ep, ep.generic) + err = d.CreateEndpoint(n.id, ep.id, ep.Interface(), ep.generic) if err != nil { return types.InternalErrorf("failed to create endpoint %s on network %s: %v", ep.Name(), n.Name(), err) } @@ -338,15 +474,21 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi return nil, types.ForbiddenErrorf("service endpoint with name %s already exists", name) } - ep := &endpoint{name: name, - generic: make(map[string]interface{})} + ep := &endpoint{name: name, generic: make(map[string]interface{}), iface: &endpointInterface{}} ep.id = stringid.GenerateRandomID() ep.network = n ep.processOptions(options...) - n.Lock() - ctrlr := n.ctrlr - n.Unlock() + if err = ep.assignAddress(); err != nil { + return nil, err + } + defer func() { + if err != nil { + ep.releaseAddress() + } + }() + + ctrlr := n.getController() n.IncEndpointCnt() if err = ctrlr.updateToStore(n); err != nil { @@ -441,7 +583,7 @@ func (n *network) isGlobalScoped() bool { func (n *network) updateSvcRecord(ep *endpoint, isAdd bool) { n.Lock() var recs []etchosts.Record - if iface := ep.Iface(); iface != nil { + if iface := ep.Iface(); iface.Address() != nil { if isAdd { n.svcRecords[ep.Name()] = iface.Address().IP n.svcRecords[ep.Name()+"."+n.name] = iface.Address().IP @@ -504,3 +646,161 @@ func (n *network) getController() *controller { defer n.Unlock() return n.ctrlr } + +func (n *network) ipamAllocate() ([]func(), error) { + var ( + cnl []func() + err error + ) + + // For now also exclude bridge from using new ipam + if n.Type() == "host" || n.Type() == "null" || n.Type() == "bridge" { + return cnl, nil + } + + ipam, err := n.getController().getIpamDriver(n.ipamType) + if err != nil { + return nil, err + } + + if n.addrSpace == "" { + if n.addrSpace, err = n.deriveAddressSpace(); err != nil { + return nil, err + } + } + + if n.ipamV4Config == nil { + n.ipamV4Config = []*IpamConf{&IpamConf{}} + } + + n.ipamV4Info = make([]*IpamInfo, len(n.ipamV4Config)) + + for i, cfg := range n.ipamV4Config { + if err = cfg.Validate(); err != nil { + return nil, err + } + d := &IpamInfo{} + n.ipamV4Info[i] = d + + d.PoolID, d.Pool, d.Meta, err = ipam.RequestPool(n.addrSpace, cfg.PreferredPool, cfg.SubPool, cfg.Options, cfg.IsV6) + if err != nil { + return nil, err + } + + defer func() { + if err != nil { + if err := ipam.ReleasePool(d.PoolID); err != nil { + log.Warnf("Failed to release address pool %s after failure to create network %s (%s)", d.PoolID, n.Name(), n.ID()) + } + } + }() + + if gws, ok := d.Meta[netlabel.Gateway]; ok { + if d.Gateway, err = types.ParseCIDR(gws); err != nil { + return nil, types.BadRequestErrorf("failed to parse gateway address (%v) returned by ipam driver: %v", gws, err) + } + } + + // If user requested a specific gateway, libnetwork will allocate it + // irrespective of whether ipam driver returned a gateway already. + // If none of the above is true, libnetwork will allocate one. + if cfg.Gateway != "" || d.Gateway == nil { + if d.Gateway, _, err = ipam.RequestAddress(d.PoolID, net.ParseIP(cfg.Gateway), nil); err != nil { + return nil, types.InternalErrorf("failed to allocate gateway (%v): %v", cfg.Gateway, err) + } + } + + cnl = append(cnl, func() { + if err := ipam.ReleaseAddress(d.PoolID, d.Gateway.IP); err != nil { + log.Warnf("Failed to release gw address %s after failure to create network %s (%s)", d.Gateway, n.Name(), n.ID()) + } + }) + if cfg.AuxAddresses != nil { + var ip net.IP + d.IPAMData.AuxAddresses = make(map[string]*net.IPNet, len(cfg.AuxAddresses)) + for k, v := range cfg.AuxAddresses { + if ip = net.ParseIP(v); ip == nil { + return nil, types.BadRequestErrorf("non parsable secondary ip address %s (%s) passed for network %s", k, v, n.Name()) + } + if d.IPAMData.AuxAddresses[k], _, err = ipam.RequestAddress(d.PoolID, net.ParseIP(cfg.Gateway), nil); err != nil { + return nil, types.InternalErrorf("failed to allocate secondary ip address %s(%s): %v", k, v, err) + } + } + } + } + + return cnl, nil +} + +func (n *network) ipamRelease() { + // For now also exclude bridge from using new ipam + if n.Type() == "host" || n.Type() == "null" || n.Type() == "bridge" { + return + } + ipam, err := n.getController().getIpamDriver(n.ipamType) + if err != nil { + log.Warnf("Failed to retrieve ipam driver to release address pool(s) on delete of network %s (%s): %v", n.Name(), n.ID(), err) + return + } + for _, d := range n.ipamV4Info { + if d.Gateway != nil { + if err := ipam.ReleaseAddress(d.PoolID, d.Gateway.IP); err != nil { + log.Warnf("Failed to release gateway ip address %s on delete of network %s (%s): %v", d.Gateway.IP, n.Name(), n.ID(), err) + } + } + if d.IPAMData.AuxAddresses != nil { + for k, nw := range d.IPAMData.AuxAddresses { + if err := ipam.ReleaseAddress(d.PoolID, nw.IP); err != nil { + log.Warnf("Failed to release secondary ip address %s (%v) on delete of network %s (%s): %v", k, nw.IP, n.Name(), n.ID(), err) + } + } + } + if err := ipam.ReleasePool(d.PoolID); err != nil { + log.Warnf("Failed to release address pool %s on delete of network %s (%s): %v", d.PoolID, n.Name(), n.ID(), err) + } + } +} + +func (n *network) getIPInfo() []*IpamInfo { + n.Lock() + defer n.Unlock() + l := make([]*IpamInfo, 0, len(n.ipamV4Info)) + for _, d := range n.ipamV4Info { + l = append(l, d) + } + return l +} + +func (n *network) getIPv4Data() []driverapi.IPAMData { + l := make([]driverapi.IPAMData, 0, len(n.ipamV4Info)) + n.Lock() + for _, d := range n.ipamV4Info { + l = append(l, d.IPAMData) + } + n.Unlock() + return l +} + +func (n *network) getIPv6Data() []driverapi.IPAMData { + l := make([]driverapi.IPAMData, 0, len(n.ipamV6Info)) + n.Lock() + for _, d := range n.ipamV6Info { + l = append(l, d.IPAMData) + } + n.Unlock() + return l +} + +func (n *network) deriveAddressSpace() (string, error) { + c := n.getController() + c.Lock() + ipd, ok := c.ipamDrivers[n.ipamType] + c.Unlock() + if !ok { + return "", types.NotFoundErrorf("could not find ipam driver %s to get default address space", n.ipamType) + } + if n.isGlobalScoped() { + return ipd.defaultGlobalAddressSpace, nil + } + return ipd.defaultLocalAddressSpace, nil +} diff --git a/libnetwork/sandbox.go b/libnetwork/sandbox.go index 40fc7f6de6..303daa8d91 100644 --- a/libnetwork/sandbox.go +++ b/libnetwork/sandbox.go @@ -324,12 +324,12 @@ func (sb *sandbox) populateNetworkResources(ep *endpoint) error { i := ep.iface ep.Unlock() - if i != nil { + if i.srcName != "" { var ifaceOptions []osl.IfaceOption - ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().Address(&i.addr), sb.osSbox.InterfaceOptions().Routes(i.routes)) - if i.addrv6.IP.To16() != nil { - ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().AddressIPv6(&i.addrv6)) + ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().Address(i.addr), sb.osSbox.InterfaceOptions().Routes(i.routes)) + if i.addrv6 != nil && i.addrv6.IP.To16() != nil { + ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().AddressIPv6(i.addrv6)) } if err := sb.osSbox.AddInterface(i.srcName, i.dstPrefix, ifaceOptions...); err != nil { diff --git a/libnetwork/store.go b/libnetwork/store.go index 1b70cc22be..9cbbfc5a73 100644 --- a/libnetwork/store.go +++ b/libnetwork/store.go @@ -45,14 +45,7 @@ func (c *controller) initGlobalStore() error { c.Lock() c.globalStore = store c.Unlock() - - nws, err := c.getNetworksFromStore(true) - if err == nil { - c.processNetworkUpdate(nws, nil) - } else if err != datastore.ErrKeyNotFound { - log.Warnf("failed to read networks from globalstore during init : %v", err) - } - return c.watchNetworks() + return nil } func (c *controller) initLocalStore() error { @@ -66,14 +59,37 @@ func (c *controller) initLocalStore() error { c.Lock() c.localStore = localStore c.Unlock() + return nil +} - nws, err := c.getNetworksFromStore(false) +func (c *controller) restoreFromGlobalStore() error { + c.Lock() + s := c.globalStore + c.Unlock() + if s == nil { + return nil + } + c.restore("global") + return c.watchNetworks() +} + +func (c *controller) restoreFromLocalStore() error { + c.Lock() + s := c.localStore + c.Unlock() + if s != nil { + c.restore("local") + } + return nil +} + +func (c *controller) restore(store string) { + nws, err := c.getNetworksFromStore(store == "global") if err == nil { c.processNetworkUpdate(nws, nil) } else if err != datastore.ErrKeyNotFound { - log.Warnf("failed to read networks from localstore during init : %v", err) + log.Warnf("failed to read networks from %s store during init : %v", store, err) } - return nil } func (c *controller) getNetworksFromStore(global bool) ([]*store.KVPair, error) { diff --git a/libnetwork/store_test.go b/libnetwork/store_test.go index ec1da287ba..63777c001e 100644 --- a/libnetwork/store_test.go +++ b/libnetwork/store_test.go @@ -52,7 +52,6 @@ func testLocalBackend(t *testing.T, provider, url string, storeConfig *store.Con genericOption[netlabel.GenericData] = driverOptions cfgOptions = append(cfgOptions, config.OptionDriverConfig("host", genericOption)) - fmt.Printf("URL : %s\n", url) ctrl, err := New(cfgOptions...) if err != nil { t.Fatalf("Error new controller: %v", err) @@ -139,7 +138,7 @@ func TestLocalStoreLockTimeout(t *testing.T) { } defer ctrl1.Stop() // Use the same boltdb file without closing the previous controller - ctrl2, _ := New(cfgOptions...) + ctrl2, err := New(cfgOptions...) if err != nil { t.Fatalf("Error new controller: %v", err) } diff --git a/libnetwork/types/types.go b/libnetwork/types/types.go index 9df1af50b1..59842e68bc 100644 --- a/libnetwork/types/types.go +++ b/libnetwork/types/types.go @@ -154,6 +154,9 @@ func ParseProtocol(s string) Protocol { // GetMacCopy returns a copy of the passed MAC address func GetMacCopy(from net.HardwareAddr) net.HardwareAddr { + if from == nil { + return nil + } to := make(net.HardwareAddr, len(from)) copy(to, from) return to @@ -161,6 +164,9 @@ func GetMacCopy(from net.HardwareAddr) net.HardwareAddr { // GetIPCopy returns a copy of the passed IP address func GetIPCopy(from net.IP) net.IP { + if from == nil { + return nil + } to := make(net.IP, len(from)) copy(to, from) return to @@ -222,23 +228,32 @@ func GetMinimalIPNet(nw *net.IPNet) *net.IPNet { var v4inV6MaskPrefix = []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} -// GetHostPartIP returns the host portion of the ip address identified by the mask. -// IP address representation is not modified. If address and mask are not compatible -// an error is returned. -func GetHostPartIP(ip net.IP, mask net.IPMask) (net.IP, error) { +// compareIPMask checks if the passed ip and mask are semantically compatible. +// It returns the byte indexes for the address and mask so that caller can +// do bitwise operations without modifying address representation. +func compareIPMask(ip net.IP, mask net.IPMask) (is int, ms int, err error) { // Find the effective starting of address and mask - is := 0 - ms := 0 if len(ip) == net.IPv6len && ip.To4() != nil { is = 12 } if len(ip[is:]) == net.IPv4len && len(mask) == net.IPv6len && bytes.Equal(mask[:12], v4inV6MaskPrefix) { ms = 12 } - // Check if address and mask are semantically compatible if len(ip[is:]) != len(mask[ms:]) { - return nil, fmt.Errorf("cannot compute host portion ip address as ip and mask are not compatible: (%#v, %#v)", ip, mask) + err = fmt.Errorf("ip and mask are not compatible: (%#v, %#v)", ip, mask) + } + return +} + +// GetHostPartIP returns the host portion of the ip address identified by the mask. +// IP address representation is not modified. If address and mask are not compatible +// an error is returned. +func GetHostPartIP(ip net.IP, mask net.IPMask) (net.IP, error) { + // Find the effective starting of address and mask + is, ms, err := compareIPMask(ip, mask) + if err != nil { + return nil, fmt.Errorf("cannot compute host portion ip address because %s", err) } // Compute host portion @@ -250,6 +265,34 @@ func GetHostPartIP(ip net.IP, mask net.IPMask) (net.IP, error) { return out, nil } +// GetBroadcastIP returns the broadcast ip address for the passed network (ip and mask). +// IP address representation is not modified. If address and mask are not compatible +// an error is returned. +func GetBroadcastIP(ip net.IP, mask net.IPMask) (net.IP, error) { + // Find the effective starting of address and mask + is, ms, err := compareIPMask(ip, mask) + if err != nil { + return nil, fmt.Errorf("cannot compute broadcast ip address because %s", err) + } + + // Compute broadcast address + out := GetIPCopy(ip) + for i := 0; i < len(mask[ms:]); i++ { + out[is+i] |= ^mask[ms+i] + } + + return out, nil +} + +// ParseCIDR returns the *net.IPNet represented by the passed CIDR notation +func ParseCIDR(cidr string) (n *net.IPNet, e error) { + var i net.IP + if i, n, e = net.ParseCIDR(cidr); e == nil { + n.IP = i + } + return +} + const ( // NEXTHOP indicates a StaticRoute with an IP next hop. NEXTHOP = iota diff --git a/libnetwork/types/types_test.go b/libnetwork/types/types_test.go index ed90915b01..e5a94d22c2 100644 --- a/libnetwork/types/types_test.go +++ b/libnetwork/types/types_test.go @@ -111,6 +111,75 @@ func TestErrorConstructors(t *testing.T) { } } +func TestCompareIPMask(t *testing.T) { + input := []struct { + ip net.IP + mask net.IPMask + is int + ms int + isErr bool + }{ + { // ip in v4Inv6 representation, mask in v4 representation + ip: net.IPv4(172, 28, 30, 1), + mask: []byte{0xff, 0xff, 0xff, 0}, + is: 12, + ms: 0, + }, + { // ip and mask in v4Inv6 representation + ip: net.IPv4(172, 28, 30, 2), + mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0}, + is: 12, + ms: 12, + }, + { // ip in v4 representation, mask in v4Inv6 representation + ip: net.IPv4(172, 28, 30, 3)[12:], + mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0}, + is: 0, + ms: 12, + }, + { // ip and mask in v4 representation + ip: net.IPv4(172, 28, 30, 4)[12:], + mask: []byte{0xff, 0xff, 0xff, 0}, + is: 0, + ms: 0, + }, + { // ip and mask as v6 + ip: net.ParseIP("2005:2004:2002:2001:FFFF:ABCD:EEAB:00CD"), + mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0}, + is: 0, + ms: 0, + }, + { + ip: net.ParseIP("2005:2004:2002:2001:FFFF:ABCD:EEAB:00CD"), + mask: []byte{0xff, 0xff, 0xff, 0}, + isErr: true, + }, + { + ip: net.ParseIP("173.32.4.5"), + mask: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0}, + isErr: true, + }, + { + ip: net.ParseIP("173.32.4.5"), + mask: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0}, + isErr: true, + }, + } + + for ind, i := range input { + is, ms, err := compareIPMask(i.ip, i.mask) + if i.isErr { + if err == nil { + t.Fatalf("Incorrect error condition for element %d. is: %d, ms: %d, err: %v", ind, is, ms, err) + } + } else { + if i.is != is || i.ms != ms { + t.Fatalf("expected is: %d, ms: %d. Got is: %d, ms: %d for element %d", i.is, i.ms, is, ms, ind) + } + } + } +} + func TestUtilGetHostPortionIP(t *testing.T) { input := []struct { ip net.IP @@ -168,3 +237,106 @@ func TestUtilGetHostPortionIP(t *testing.T) { t.Fatalf("Unexpected success") } } + +func TestUtilGetBroadcastIP(t *testing.T) { + input := []struct { + ip net.IP + mask net.IPMask + bcast net.IP + err error + }{ + // ip in v4Inv6 representation, mask in v4 representation + { + ip: net.IPv4(172, 28, 30, 1), + mask: []byte{0xff, 0xff, 0xff, 0}, + bcast: net.IPv4(172, 28, 30, 255), + }, + { + ip: net.IPv4(10, 28, 30, 1), + mask: []byte{0xff, 0, 0, 0}, + bcast: net.IPv4(10, 255, 255, 255), + }, + // ip and mask in v4Inv6 representation + { + ip: net.IPv4(172, 28, 30, 2), + mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0}, + bcast: net.IPv4(172, 28, 30, 255), + }, + { + ip: net.IPv4(172, 28, 30, 2), + mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0}, + bcast: net.IPv4(172, 28, 255, 255), + }, + // ip in v4 representation, mask in v4Inv6 representation + { + ip: net.IPv4(172, 28, 30, 3)[12:], + mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0}, + bcast: net.IPv4(172, 28, 30, 255)[12:], + }, + { + ip: net.IPv4(172, 28, 30, 3)[12:], + mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0}, + bcast: net.IPv4(172, 255, 255, 255)[12:], + }, + // ip and mask in v4 representation + { + ip: net.IPv4(172, 28, 30, 4)[12:], + mask: []byte{0xff, 0xff, 0xff, 0}, + bcast: net.IPv4(172, 28, 30, 255)[12:], + }, + { + ip: net.IPv4(172, 28, 30, 4)[12:], + mask: []byte{0xff, 0xff, 0, 0}, + bcast: net.IPv4(172, 28, 255, 255)[12:], + }, + { // ip and mask as v6 + ip: net.ParseIP("2005:2004:2002:2001:FFFF:ABCD:EEAB:00CD"), + mask: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0}, + bcast: net.ParseIP("2005:2004:2002:2001:FFFF:ABCD:EEFF:FFFF"), + }, + } + + for _, i := range input { + h, err := GetBroadcastIP(i.ip, i.mask) + if err != nil { + t.Fatal(err) + } + if !i.bcast.Equal(h) { + t.Fatalf("Failed to return expected host ip. Expected: %s. Got: %s", i.bcast, h) + } + } + + // ip as v6 and mask as v4 are not compatible + if _, err := GetBroadcastIP(net.ParseIP("2005:2004:2002:2001:FFFF:ABCD:EEAB:00CD"), []byte{0xff, 0xff, 0xff, 0}); err == nil { + t.Fatalf("Unexpected success") + } + // ip as v4 and non conventional mask + if _, err := GetBroadcastIP(net.ParseIP("173.32.4.5"), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0}); err == nil { + t.Fatalf("Unexpected success") + } + // ip as v4 and non conventional mask + if _, err := GetBroadcastIP(net.ParseIP("173.32.4.5"), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0}); err == nil { + t.Fatalf("Unexpected success") + } +} + +func TestParseCIDR(t *testing.T) { + input := []struct { + cidr string + ipnw *net.IPNet + }{ + {"192.168.22.44/16", &net.IPNet{IP: net.IP{192, 168, 22, 44}, Mask: net.IPMask{255, 255, 0, 0}}}, + {"10.10.2.0/24", &net.IPNet{IP: net.IP{10, 10, 2, 0}, Mask: net.IPMask{255, 255, 255, 0}}}, + {"10.0.0.100/17", &net.IPNet{IP: net.IP{10, 0, 0, 100}, Mask: net.IPMask{255, 255, 128, 0}}}, + } + + for _, i := range input { + nw, err := ParseCIDR(i.cidr) + if err != nil { + t.Fatal(err) + } + if !CompareIPNet(nw, i.ipnw) { + t.Fatalf("network differ. Expected %v. Got: %v", i.ipnw, nw) + } + } +}