From ddcfab5f8109334e1132fd8b64d168acf39394a3 Mon Sep 17 00:00:00 2001 From: Alessandro Boch Date: Sat, 3 Oct 2015 16:11:50 -0700 Subject: [PATCH] libnetwork <-> ipam driver interaction Signed-off-by: Alessandro Boch --- libnetwork/bitseq/sequence.go | 17 +- libnetwork/cmd/ovrouter/ovrouter.go | 44 ++- libnetwork/controller.go | 72 ++-- libnetwork/driverapi/driverapi.go | 41 +- libnetwork/driverapi/driverapi_test.go | 119 ++++++ libnetwork/driverapi/ipamdata.go | 103 ++++++ libnetwork/drivers/bridge/bridge.go | 32 +- libnetwork/drivers/bridge/bridge_test.go | 93 +++-- libnetwork/drivers/bridge/network_test.go | 34 +- .../drivers/bridge/port_mapping_test.go | 6 +- libnetwork/drivers/host/host.go | 4 +- libnetwork/drivers/host/host_test.go | 4 +- libnetwork/drivers/null/null.go | 4 +- libnetwork/drivers/null/null_test.go | 4 +- libnetwork/drivers/overlay/joinleave.go | 5 +- libnetwork/drivers/overlay/ov_endpoint.go | 39 +- libnetwork/drivers/overlay/ov_network.go | 42 ++- libnetwork/drivers/overlay/overlay.go | 30 -- libnetwork/drivers/remote/api/api.go | 3 + libnetwork/drivers/remote/driver.go | 83 ++--- libnetwork/drivers/remote/driver_test.go | 107 ++++-- libnetwork/drivers/windows/windows.go | 4 +- libnetwork/endpoint.go | 58 ++- libnetwork/endpoint_info.go | 99 +++-- libnetwork/ipamapi/contract.go | 2 - libnetwork/libnetwork_internal_test.go | 266 +++++++++++++ libnetwork/libnetwork_test.go | 23 +- libnetwork/netlabel/labels.go | 3 + libnetwork/network.go | 350 ++++++++++++++++-- libnetwork/sandbox.go | 8 +- libnetwork/types/types.go | 6 + 31 files changed, 1332 insertions(+), 373 deletions(-) create mode 100644 libnetwork/driverapi/driverapi_test.go create mode 100644 libnetwork/driverapi/ipamdata.go diff --git a/libnetwork/bitseq/sequence.go b/libnetwork/bitseq/sequence.go index 0d48f54d15..006fee29a4 100644 --- a/libnetwork/bitseq/sequence.go +++ b/libnetwork/bitseq/sequence.go @@ -307,7 +307,22 @@ func (h *Handle) validateOrdinal(ordinal uint32) error { // Destroy removes from the datastore the data belonging to this handle func (h *Handle) Destroy() error { - return h.deleteFromStore() + 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 ff560d419c..6fdac5e698 100644 --- a/libnetwork/controller.go +++ b/libnetwork/controller.go @@ -301,15 +301,17 @@ func (c *controller) RegisterIpamDriver(name string, driver ipamapi.Ipam) error } c.Lock() - if _, ok := c.ipamDrivers[name]; ok { - c.Unlock() + _, ok := c.ipamDrivers[name] + c.Unlock() + if ok { return driverapi.ErrActiveRegistration(name) } - l, g, err := driver.GetDefaultAddressSpaces() + locAS, glbAS, err := driver.GetDefaultAddressSpaces() if err != nil { return fmt.Errorf("ipam driver %s failed to return default address spaces: %v", name, err) } - c.ipamDrivers[name] = &ipamData{driver: driver, defaultLocalAddressSpace: l, defaultGlobalAddressSpace: g} + c.Lock() + c.ipamDrivers[name] = &ipamData{driver: driver, defaultLocalAddressSpace: locAS, defaultGlobalAddressSpace: glbAS} c.Unlock() log.Debugf("Registering ipam provider: %s", name) @@ -346,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 } @@ -362,28 +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() { @@ -621,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/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/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/ipamapi/contract.go b/libnetwork/ipamapi/contract.go index 47396093b1..6693b5ac82 100644 --- a/libnetwork/ipamapi/contract.go +++ b/libnetwork/ipamapi/contract.go @@ -15,8 +15,6 @@ const ( DefaultIPAM = "default" // PluginEndpointType represents the Endpoint Type used by Plugin system PluginEndpointType = "IPAM" - // Gateway is the key for the gateway option - Gateway = "gateway" ) // Callback provides a Callback interface for registering an IPAM instance into LibNetwork 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 51164a4c2d..416d2c1206 100644 --- a/libnetwork/network.go +++ b/libnetwork/network.go @@ -55,23 +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 - ipamType 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 } @@ -180,10 +251,41 @@ func (n *network) MarshalJSON() ([]byte, error) { 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) } @@ -196,14 +298,35 @@ 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 } @@ -231,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 { @@ -279,6 +412,8 @@ func (n *network) Delete() error { return err } + n.ipamRelease() + return nil } @@ -320,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) } @@ -339,11 +474,20 @@ 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...) + if err = ep.assignAddress(); err != nil { + return nil, err + } + defer func() { + if err != nil { + ep.releaseAddress() + } + }() + ctrlr := n.getController() n.IncEndpointCnt() @@ -439,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 @@ -502,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/types/types.go b/libnetwork/types/types.go index bd6e4c89ef..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