diff --git a/README.md b/README.md index 7db0395..425dba9 100644 --- a/README.md +++ b/README.md @@ -48,13 +48,15 @@ There are many networking solutions available to suit a broad range of use-cases // For each new container: allocate IP and interfaces. The returned network // settings will be used for container infos (inspect and such), as well as - // iptables rules for port publishing. - _, sinfo, err := network.CreateEndpoint("Endpoint1", networkNamespace.Key(), "") + // iptables rules for port publishing. This info is contained or accessible + // from the returned endpoint. + ep, err := network.CreateEndpoint("Endpoint1", networkNamespace.Key(), "") if err != nil { return } - + // Add interfaces to the namespace. + sinfo := ep.SandboxInfo() for _, iface := range sinfo.Interfaces { if err := networkNamespace.AddInterface(iface); err != nil { return diff --git a/cmd/readme_test/readme.go b/cmd/readme_test/readme.go index 90e2409..ea55f27 100644 --- a/cmd/readme_test/readme.go +++ b/cmd/readme_test/readme.go @@ -33,13 +33,15 @@ func main() { // For each new container: allocate IP and interfaces. The returned network // settings will be used for container infos (inspect and such), as well as - // iptables rules for port publishing. - _, sinfo, err := network.CreateEndpoint("Endpoint1", networkNamespace.Key(), "") + // iptables rules for port publishing. This info is contained or accessible + // from the returned endpoint. + ep, err := network.CreateEndpoint("Endpoint1", networkNamespace.Key(), "") if err != nil { return } // Add interfaces to the namespace. + sinfo := ep.SandboxInfo() for _, iface := range sinfo.Interfaces { if err := networkNamespace.AddInterface(iface); err != nil { return diff --git a/driverapi/driverapi.go b/driverapi/driverapi.go index 267cd31..b93d6a3 100644 --- a/driverapi/driverapi.go +++ b/driverapi/driverapi.go @@ -3,6 +3,8 @@ package driverapi import ( "errors" "net" + + "github.com/docker/libnetwork/netutils" ) var ( @@ -56,10 +58,10 @@ type Interface struct { DstName string // IPv4 address for the interface. - Address net.IPNet + Address *net.IPNet // IPv6 address for the interface. - AddressIPv6 net.IPNet + AddressIPv6 *net.IPNet } // SandboxInfo represents all possible information that @@ -76,3 +78,85 @@ type SandboxInfo struct { // TODO: Add routes and ip tables etc. } + +// GetCopy returns a copy of this Interface structure +func (i *Interface) GetCopy() *Interface { + return &Interface{ + SrcName: i.SrcName, + DstName: i.DstName, + Address: netutils.GetIPNetCopy(i.Address), + AddressIPv6: netutils.GetIPNetCopy(i.AddressIPv6), + } +} + +// Equal checks if this instance of Interface is equal to the passed one +func (i *Interface) Equal(o *Interface) bool { + if i == o { + return true + } + + if o == nil { + return false + } + + if i.SrcName != o.SrcName || i.DstName != o.DstName { + return false + } + + if !netutils.CompareIPNet(i.Address, o.Address) { + return false + } + + if !netutils.CompareIPNet(i.AddressIPv6, o.AddressIPv6) { + return false + } + + return true +} + +// GetCopy returns a copy of this SandboxInfo structure +func (s *SandboxInfo) GetCopy() *SandboxInfo { + list := make([]*Interface, len(s.Interfaces)) + for i, iface := range s.Interfaces { + list[i] = iface.GetCopy() + } + gw := netutils.GetIPCopy(s.Gateway) + gw6 := netutils.GetIPCopy(s.GatewayIPv6) + + return &SandboxInfo{Interfaces: list, Gateway: gw, GatewayIPv6: gw6} +} + +// Equal checks if this instance of SandboxInfo is equal to the passed one +func (s *SandboxInfo) Equal(o *SandboxInfo) bool { + if s == o { + return true + } + + if o == nil { + return false + } + + if !s.Gateway.Equal(o.Gateway) { + return false + } + + if !s.GatewayIPv6.Equal(o.GatewayIPv6) { + return false + } + + if (s.Interfaces == nil && o.Interfaces != nil) || + (s.Interfaces != nil && o.Interfaces == nil) || + (len(s.Interfaces) != len(o.Interfaces)) { + return false + } + + // Note: At the moment, the two lists must be in the same order + for i := 0; i < len(s.Interfaces); i++ { + if !s.Interfaces[i].Equal(o.Interfaces[i]) { + return false + } + } + + return true + +} diff --git a/driverapi/driverapi_test.go b/driverapi/driverapi_test.go new file mode 100644 index 0000000..e9f7089 --- /dev/null +++ b/driverapi/driverapi_test.go @@ -0,0 +1,71 @@ +package driverapi + +import ( + "net" + "testing" +) + +func TestInterfaceEqual(t *testing.T) { + list := getInterfaceList() + + if !list[0].Equal(list[0]) { + t.Fatalf("Interface.Equal() returned false negative") + } + + if list[0].Equal(list[1]) { + t.Fatalf("Interface.Equal() returned false positive") + } + + if list[0].Equal(list[1]) != list[1].Equal(list[0]) { + t.Fatalf("Interface.Equal() failed commutative check") + } +} + +func TestInterfaceCopy(t *testing.T) { + for _, iface := range getInterfaceList() { + cp := iface.GetCopy() + + if !iface.Equal(cp) { + t.Fatalf("Failed to return a copy of Interface") + } + + if iface == cp { + t.Fatalf("Failed to return a true copy of Interface") + } + } +} + +func TestSandboxInfoCopy(t *testing.T) { + si := SandboxInfo{Interfaces: getInterfaceList(), Gateway: net.ParseIP("192.168.1.254"), GatewayIPv6: net.ParseIP("2001:2345::abcd:8889")} + cp := si.GetCopy() + + if !si.Equal(cp) { + t.Fatalf("Failed to return a copy of SandboxInfo") + } + + if &si == cp { + t.Fatalf("Failed to return a true copy of SanboxInfo") + } +} + +func getInterfaceList() []*Interface { + _, netv4a, _ := net.ParseCIDR("192.168.30.1/24") + _, netv4b, _ := net.ParseCIDR("172.18.255.2/23") + _, netv6a, _ := net.ParseCIDR("2001:2345::abcd:8888/80") + _, netv6b, _ := net.ParseCIDR("2001:2345::abcd:8889/80") + + return []*Interface{ + &Interface{ + SrcName: "veth1234567", + DstName: "eth0", + Address: netv4a, + AddressIPv6: netv6a, + }, + &Interface{ + SrcName: "veth7654321", + DstName: "eth1", + Address: netv4b, + AddressIPv6: netv6b, + }, + } +} diff --git a/drivers/bridge/bridge.go b/drivers/bridge/bridge.go index 48b2f83..92e58bb 100644 --- a/drivers/bridge/bridge.go +++ b/drivers/bridge/bridge.go @@ -213,7 +213,8 @@ func (d *driver) DeleteNetwork(nid driverapi.UUID) error { func (d *driver) CreateEndpoint(nid, eid driverapi.UUID, sboxKey string, epOption interface{}) (*driverapi.SandboxInfo, error) { var ( - ipv6Addr net.IPNet + ipv6Addr *net.IPNet + ip6 net.IP err error ) @@ -292,14 +293,14 @@ func (d *driver) CreateEndpoint(nid, eid driverapi.UUID, sboxKey string, epOptio if err != nil { return nil, err } - ipv4Addr := net.IPNet{IP: ip4, Mask: n.bridge.bridgeIPv4.Mask} + ipv4Addr := &net.IPNet{IP: ip4, Mask: n.bridge.bridgeIPv4.Mask} if config.EnableIPv6 { ip6, err := ipAllocator.RequestIP(n.bridge.bridgeIPv6, nil) if err != nil { return nil, err } - ipv6Addr = net.IPNet{IP: ip6, Mask: n.bridge.bridgeIPv6.Mask} + ipv6Addr = &net.IPNet{IP: ip6, Mask: n.bridge.bridgeIPv6.Mask} } var interfaces []*driverapi.Interface @@ -316,7 +317,7 @@ func (d *driver) CreateEndpoint(nid, eid driverapi.UUID, sboxKey string, epOptio } n.endpoint.addressIPv4 = ip4 - n.endpoint.addressIPv6 = ipv6Addr.IP + n.endpoint.addressIPv6 = ip6 interfaces = append(interfaces, intf) sinfo.Interfaces = interfaces return sinfo, nil diff --git a/drivers/bridge/network_test.go b/drivers/bridge/network_test.go index 02258fa..4a5e303 100644 --- a/drivers/bridge/network_test.go +++ b/drivers/bridge/network_test.go @@ -118,7 +118,7 @@ func TestLinkCreateNoEnableIPv6(t *testing.T) { } interfaces := sinfo.Interfaces - if len(interfaces[0].AddressIPv6.IP) != 0 { + if interfaces[0].AddressIPv6 != nil { t.Fatalf("Expectd IPv6 address to be nil when IPv6 is not enabled. Got IPv6 = %s", interfaces[0].AddressIPv6.String()) } diff --git a/libnetwork_test.go b/libnetwork_test.go index 4237932..6bc19ec 100644 --- a/libnetwork_test.go +++ b/libnetwork_test.go @@ -67,7 +67,7 @@ func TestSimplebridge(t *testing.T) { t.Fatal(err) } - ep, _, err := network.CreateEndpoint("testep", "", "") + ep, err := network.CreateEndpoint("testep", "", "") if err != nil { t.Fatal(err) } @@ -196,7 +196,7 @@ func TestDeleteNetworkWithActiveEndpoints(t *testing.T) { t.Fatal(err) } - ep, _, err := network.CreateEndpoint("testep", "", "") + ep, err := network.CreateEndpoint("testep", "", "") if err != nil { t.Fatal(err) } @@ -264,7 +264,7 @@ func TestUnknownEndpoint(t *testing.T) { t.Fatal(err) } - ep, _, err := network.CreateEndpoint("testep", "", "") + ep, err := network.CreateEndpoint("testep", "", "") if err != nil { t.Fatal(err) } diff --git a/netutils/utils.go b/netutils/utils.go index eff88df..c3a32a1 100644 --- a/netutils/utils.go +++ b/netutils/utils.go @@ -3,6 +3,7 @@ package netutils import ( + "bytes" "crypto/rand" "encoding/hex" "errors" @@ -145,3 +146,31 @@ func GenerateRandomName(prefix string, size int) (string, error) { } return prefix + hex.EncodeToString(id)[:size], nil } + +// GetIPCopy returns a copy of the passed IP address +func GetIPCopy(from net.IP) net.IP { + to := make(net.IP, len(from)) + copy(to, from) + return to +} + +// GetIPNetCopy returns a copy of the passed IP Network +func GetIPNetCopy(from *net.IPNet) *net.IPNet { + if from == nil { + return nil + } + bm := make(net.IPMask, len(from.Mask)) + copy(bm, from.Mask) + return &net.IPNet{IP: GetIPCopy(from.IP), Mask: bm} +} + +// CompareIPNet returns equal if the two IP Networks are equal +func CompareIPNet(a, b *net.IPNet) bool { + if a == b { + return true + } + if a == nil || b == nil { + return false + } + return a.IP.Equal(b.IP) && bytes.Equal(a.Mask, b.Mask) +} diff --git a/netutils/utils_test.go b/netutils/utils_test.go index 78de626..12ff40a 100644 --- a/netutils/utils_test.go +++ b/netutils/utils_test.go @@ -209,3 +209,55 @@ func TestUtilGenerateRandomMAC(t *testing.T) { t.Fatalf("mac1 %s should not equal mac2 %s", mac1, mac2) } } + +func TestCompareIPNet(t *testing.T) { + if CompareIPNet(nil, nil) == false { + t.Fatalf("Failed to detect two nil net.IPNets are equal") + } + + _, net1, _ := net.ParseCIDR("192.168.30.22/24") + if CompareIPNet(net1, net1) == false { + t.Fatalf("Failed to detect same net.IPNet pointers equality") + } + + _, net2, _ := net.ParseCIDR("192.168.30.22/24") + if CompareIPNet(net1, net2) == false { + t.Fatalf("Failed to detect same net.IPNet object equality") + } + + _, net3, _ := net.ParseCIDR("192.168.30.33/24") + if CompareIPNet(net1, net3) == false { + t.Fatalf("Failed to detect semantically equivalent net.IPNets") + } + + _, net3, _ = net.ParseCIDR("192.168.31.33/24") + if CompareIPNet(net2, net3) == true { + t.Fatalf("Failed to detect different net.IPNets") + } +} + +func TestIPCopyFunctions(t *testing.T) { + ip := net.ParseIP("172.28.30.134") + cp := GetIPCopy(ip) + + if !ip.Equal(cp) { + t.Fatalf("Failed to return a copy of net.IP") + } + + if &ip == &cp { + t.Fatalf("Failed to return a true copy of net.IP") + } +} + +func TestNetIPCopyFunctions(t *testing.T) { + _, net, _ := net.ParseCIDR("192.168.30.23/24") + cp := GetIPNetCopy(net) + + if CompareIPNet(net, cp) == false { + t.Fatalf("Failed to return a copy of net.IPNet") + } + + if net == cp { + t.Fatalf("Failed to return a true copy of net.IPNet") + } +} diff --git a/network.go b/network.go index 4d11d20..7a08143 100644 --- a/network.go +++ b/network.go @@ -48,8 +48,9 @@ import ( // NetworkController provides the interface for controller instance which manages // networks. type NetworkController interface { + // NOTE: This method will go away when moving to plugin infrastructure NewNetworkDriver(networkType string, options interface{}) (*NetworkDriver, error) - // Create a new network. The options parameter carry driver specific options. + // Create a new network. The options parameter carries network specific options. // Labels support will be added in the near future. NewNetwork(d *NetworkDriver, name string, options interface{}) (Network, error) } @@ -69,7 +70,7 @@ type Network interface { // Create a new endpoint to this network symbolically identified by the // specified unique name. The options parameter carry driver specific options. // Labels support will be added in the near future. - CreateEndpoint(name string, sboxKey string, options interface{}) (Endpoint, *driverapi.SandboxInfo, error) + CreateEndpoint(name string, sboxKey string, options interface{}) (Endpoint, error) // Delete the network. Delete() error @@ -77,10 +78,19 @@ type Network interface { // Endpoint represents a logical connection between a network and a sandbox. type Endpoint interface { - // A system generated id for this network. + // A system generated id for this endpoint. ID() string - // Delete endpoint. + // Name returns the name of this endpoint. + Name() string + + // Network returns the name of the network to which this endpoint is attached. + Network() string + + // SandboxInfo returns the sandbox information for this endpoint. + SandboxInfo() *driverapi.SandboxInfo + + // Delete and detaches this endpoint from the network. Delete() error } @@ -222,7 +232,7 @@ func (n *network) Delete() error { return err } -func (n *network) CreateEndpoint(name string, sboxKey string, options interface{}) (Endpoint, *driverapi.SandboxInfo, error) { +func (n *network) CreateEndpoint(name string, sboxKey string, options interface{}) (Endpoint, error) { ep := &endpoint{name: name} ep.id = driverapi.UUID(stringid.GenerateRandomID()) ep.network = n @@ -230,20 +240,35 @@ func (n *network) CreateEndpoint(name string, sboxKey string, options interface{ d := n.driver.internalDriver sinfo, err := d.CreateEndpoint(n.id, ep.id, sboxKey, options) if err != nil { - return nil, nil, err + return nil, err } ep.sandboxInfo = sinfo n.Lock() n.endpoints[ep.id] = ep n.Unlock() - return ep, sinfo, nil + return ep, nil } func (ep *endpoint) ID() string { return string(ep.id) } +func (ep *endpoint) Name() string { + return ep.name +} + +func (ep *endpoint) Network() string { + return ep.network.name +} + +func (ep *endpoint) SandboxInfo() *driverapi.SandboxInfo { + if ep.sandboxInfo == nil { + return nil + } + return ep.sandboxInfo.GetCopy() +} + func (ep *endpoint) Delete() error { var err error diff --git a/sandbox/configure_linux.go b/sandbox/configure_linux.go index cec2662..066954f 100644 --- a/sandbox/configure_linux.go +++ b/sandbox/configure_linux.go @@ -35,12 +35,12 @@ func setGatewayIP(gw net.IP) error { } func setInterfaceIP(iface netlink.Link, settings *driverapi.Interface) error { - ipAddr := &netlink.Addr{IPNet: &settings.Address, Label: ""} + ipAddr := &netlink.Addr{IPNet: settings.Address, Label: ""} return netlink.AddrAdd(iface, ipAddr) } func setInterfaceIPv6(iface netlink.Link, settings *driverapi.Interface) error { - ipAddr := &netlink.Addr{IPNet: &settings.Address, Label: ""} + ipAddr := &netlink.Addr{IPNet: settings.Address, Label: ""} return netlink.AddrAdd(iface, ipAddr) }