Browse Source

Merge pull request #102 from aboch/pm

Bridge to handle port mapping
Jana Radhakrishnan 10 years ago
parent
commit
284c1713e8

+ 3 - 3
libnetwork/Godeps/Godeps.json

@@ -1,5 +1,5 @@
 {
-	"ImportPath": "github.com/docker/docker/vendor/src/github.com/docker/libnetwork",
+	"ImportPath": "github.com/docker/libnetwork",
 	"GoVersion": "go1.4.2",
 	"Packages": [
 		"./..."
@@ -52,8 +52,8 @@
 		},
 		{
 			"ImportPath": "github.com/docker/libcontainer/user",
-			"Comment": "v1.4.0",
-			"Rev": "53eca435e63db58b06cf796d3a9326db5fd42253"
+			"Comment": "v1.4.0-495-g3e66118",
+			"Rev": "3e661186ba24f259d3860f067df052c7f6904bee"
 		},
 		{
 			"ImportPath": "github.com/vishvananda/netlink",

+ 0 - 93
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel.go

@@ -1,93 +0,0 @@
-package kernel
-
-import (
-	"bytes"
-	"errors"
-	"fmt"
-)
-
-type KernelVersionInfo struct {
-	Kernel int
-	Major  int
-	Minor  int
-	Flavor string
-}
-
-func (k *KernelVersionInfo) String() string {
-	return fmt.Sprintf("%d.%d.%d%s", k.Kernel, k.Major, k.Minor, k.Flavor)
-}
-
-// Compare two KernelVersionInfo struct.
-// Returns -1 if a < b, 0 if a == b, 1 it a > b
-func CompareKernelVersion(a, b *KernelVersionInfo) int {
-	if a.Kernel < b.Kernel {
-		return -1
-	} else if a.Kernel > b.Kernel {
-		return 1
-	}
-
-	if a.Major < b.Major {
-		return -1
-	} else if a.Major > b.Major {
-		return 1
-	}
-
-	if a.Minor < b.Minor {
-		return -1
-	} else if a.Minor > b.Minor {
-		return 1
-	}
-
-	return 0
-}
-
-func GetKernelVersion() (*KernelVersionInfo, error) {
-	var (
-		err error
-	)
-
-	uts, err := uname()
-	if err != nil {
-		return nil, err
-	}
-
-	release := make([]byte, len(uts.Release))
-
-	i := 0
-	for _, c := range uts.Release {
-		release[i] = byte(c)
-		i++
-	}
-
-	// Remove the \x00 from the release for Atoi to parse correctly
-	release = release[:bytes.IndexByte(release, 0)]
-
-	return ParseRelease(string(release))
-}
-
-func ParseRelease(release string) (*KernelVersionInfo, error) {
-	var (
-		kernel, major, minor, parsed int
-		flavor, partial              string
-	)
-
-	// Ignore error from Sscanf to allow an empty flavor.  Instead, just
-	// make sure we got all the version numbers.
-	parsed, _ = fmt.Sscanf(release, "%d.%d%s", &kernel, &major, &partial)
-	if parsed < 2 {
-		return nil, errors.New("Can't parse kernel version " + release)
-	}
-
-	// sometimes we have 3.12.25-gentoo, but sometimes we just have 3.12-1-amd64
-	parsed, _ = fmt.Sscanf(partial, ".%d%s", &minor, &flavor)
-	if parsed < 1 {
-		flavor = partial
-	}
-
-	return &KernelVersionInfo{
-		Kernel: kernel,
-		Major:  major,
-		Minor:  minor,
-		Flavor: flavor,
-	}, nil
-}

+ 0 - 61
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel_test.go

@@ -1,61 +0,0 @@
-package kernel
-
-import (
-	"testing"
-)
-
-func assertParseRelease(t *testing.T, release string, b *KernelVersionInfo, result int) {
-	var (
-		a *KernelVersionInfo
-	)
-	a, _ = ParseRelease(release)
-
-	if r := CompareKernelVersion(a, b); r != result {
-		t.Fatalf("Unexpected kernel version comparison result. Found %d, expected %d", r, result)
-	}
-	if a.Flavor != b.Flavor {
-		t.Fatalf("Unexpected parsed kernel flavor.  Found %s, expected %s", a.Flavor, b.Flavor)
-	}
-}
-
-func TestParseRelease(t *testing.T) {
-	assertParseRelease(t, "3.8.0", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, 0)
-	assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54, Flavor: ".longterm-1"}, 0)
-	assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54, Flavor: ".longterm-1"}, 0)
-	assertParseRelease(t, "3.8.0-19-generic", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "-19-generic"}, 0)
-	assertParseRelease(t, "3.12.8tag", &KernelVersionInfo{Kernel: 3, Major: 12, Minor: 8, Flavor: "tag"}, 0)
-	assertParseRelease(t, "3.12-1-amd64", &KernelVersionInfo{Kernel: 3, Major: 12, Minor: 0, Flavor: "-1-amd64"}, 0)
-}
-
-func assertKernelVersion(t *testing.T, a, b *KernelVersionInfo, result int) {
-	if r := CompareKernelVersion(a, b); r != result {
-		t.Fatalf("Unexpected kernel version comparison result. Found %d, expected %d", r, result)
-	}
-}
-
-func TestCompareKernelVersion(t *testing.T) {
-	assertKernelVersion(t,
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
-		0)
-	assertKernelVersion(t,
-		&KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0},
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
-		-1)
-	assertKernelVersion(t,
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
-		&KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0},
-		1)
-	assertKernelVersion(t,
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
-		0)
-	assertKernelVersion(t,
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 5},
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
-		1)
-	assertKernelVersion(t,
-		&KernelVersionInfo{Kernel: 3, Major: 0, Minor: 20},
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
-		-1)
-}

+ 0 - 16
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/uname_linux.go

@@ -1,16 +0,0 @@
-package kernel
-
-import (
-	"syscall"
-)
-
-type Utsname syscall.Utsname
-
-func uname() (*syscall.Utsname, error) {
-	uts := &syscall.Utsname{}
-
-	if err := syscall.Uname(uts); err != nil {
-		return nil, err
-	}
-	return uts, nil
-}

+ 0 - 15
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/uname_unsupported.go

@@ -1,15 +0,0 @@
-// +build !linux
-
-package kernel
-
-import (
-	"errors"
-)
-
-type Utsname struct {
-	Release [65]byte
-}
-
-func uname() (*Utsname, error) {
-	return nil, errors.New("Kernel version detection is available only on linux")
-}

+ 1 - 0
libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS

@@ -1 +1,2 @@
 Tianon Gravi <admwiggin@gmail.com> (@tianon)
+Aleksa Sarai <cyphar@cyphar.com> (@cyphar)

+ 8 - 8
libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go

@@ -9,22 +9,22 @@ import (
 
 // Unix-specific path to the passwd and group formatted files.
 const (
-	unixPasswdFile = "/etc/passwd"
-	unixGroupFile  = "/etc/group"
+	unixPasswdPath = "/etc/passwd"
+	unixGroupPath  = "/etc/group"
 )
 
-func GetPasswdFile() (string, error) {
-	return unixPasswdFile, nil
+func GetPasswdPath() (string, error) {
+	return unixPasswdPath, nil
 }
 
 func GetPasswd() (io.ReadCloser, error) {
-	return os.Open(unixPasswdFile)
+	return os.Open(unixPasswdPath)
 }
 
-func GetGroupFile() (string, error) {
-	return unixGroupFile, nil
+func GetGroupPath() (string, error) {
+	return unixGroupPath, nil
 }
 
 func GetGroup() (io.ReadCloser, error) {
-	return os.Open(unixGroupFile)
+	return os.Open(unixGroupPath)
 }

+ 2 - 2
libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go

@@ -4,7 +4,7 @@ package user
 
 import "io"
 
-func GetPasswdFile() (string, error) {
+func GetPasswdPath() (string, error) {
 	return "", ErrUnsupported
 }
 
@@ -12,7 +12,7 @@ func GetPasswd() (io.ReadCloser, error) {
 	return nil, ErrUnsupported
 }
 
-func GetGroupFile() (string, error) {
+func GetGroupPath() (string, error) {
 	return "", ErrUnsupported
 }
 

+ 2 - 2
libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go

@@ -197,11 +197,11 @@ type ExecUser struct {
 	Home     string
 }
 
-// GetExecUserFile is a wrapper for GetExecUser. It reads data from each of the
+// GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the
 // given file paths and uses that data as the arguments to GetExecUser. If the
 // files cannot be opened for any reason, the error is ignored and a nil
 // io.Reader is passed instead.
-func GetExecUserFile(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
+func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
 	passwd, err := os.Open(passwdPath)
 	if err != nil {
 		passwd = nil

+ 49 - 31
libnetwork/drivers/bridge/bridge.go

@@ -16,10 +16,11 @@ import (
 )
 
 const (
-	networkType   = "bridge"
-	vethPrefix    = "veth"
-	vethLen       = 7
-	containerVeth = "eth0"
+	networkType             = "bridge"
+	vethPrefix              = "veth"
+	vethLen                 = 7
+	containerVeth           = "eth0"
+	maxAllocatePortAttempts = 10
 )
 
 var (
@@ -42,11 +43,13 @@ type Configuration struct {
 	Mtu                   int
 	DefaultGatewayIPv4    net.IP
 	DefaultGatewayIPv6    net.IP
+	DefaultBindingIP      net.IP
 }
 
 // EndpointConfiguration represents the user specified configuration for the sandbox endpoint
 type EndpointConfiguration struct {
-	MacAddress net.HardwareAddr
+	MacAddress   net.HardwareAddr
+	PortBindings []netutils.PortBinding
 }
 
 // ContainerConfiguration represents the user specified configuration for a container
@@ -55,9 +58,10 @@ type ContainerConfiguration struct {
 }
 
 type bridgeEndpoint struct {
-	id     types.UUID
-	port   *sandbox.Interface
-	config *EndpointConfiguration // User specified parameters
+	id          types.UUID
+	port        *sandbox.Interface
+	config      *EndpointConfiguration // User specified parameters
+	portMapping []netutils.PortBinding // Operation port bindings
 }
 
 type bridgeNetwork struct {
@@ -343,7 +347,6 @@ func (d *driver) CreateEndpoint(nid, eid types.UUID, epOptions map[string]interf
 	// Try to convert the options to endpoint configuration
 	epConfig, err := parseEndpointOptions(epOptions)
 	if err != nil {
-		n.Unlock()
 		return nil, err
 	}
 
@@ -404,12 +407,8 @@ func (d *driver) CreateEndpoint(nid, eid types.UUID, epOptions map[string]interf
 		}
 	}()
 
-	mac := netutils.GenerateRandomMAC()
-	// Add user specified attributes
-	if epConfig != nil && epConfig.MacAddress != nil {
-		mac = epConfig.MacAddress
-	}
-
+	// Set the sbox's MAC. If specified, use the one configured by user, otherwise use a random one
+	mac := electMacAddress(epConfig)
 	err = netlink.LinkSetHardwareAddr(sbox, mac)
 	if err != nil {
 		return nil, err
@@ -466,14 +465,13 @@ func (d *driver) CreateEndpoint(nid, eid types.UUID, epOptions map[string]interf
 		ipv6Addr = &net.IPNet{IP: ip6, Mask: network.Mask}
 	}
 
-	// Store the sandbox side pipe interface
-	// This is needed for cleanup on DeleteEndpoint()
+	// Create the sandbox side pipe interface
 	intf := &sandbox.Interface{}
 	intf.SrcName = name2
 	intf.DstName = containerVeth
 	intf.Address = ipv4Addr
 
-	// Update endpoint with the sandbox interface info
+	// Store the interface in endpoint, this is needed for cleanup on DeleteEndpoint()
 	endpoint.port = intf
 
 	// Generate the sandbox info to return
@@ -486,6 +484,12 @@ func (d *driver) CreateEndpoint(nid, eid types.UUID, epOptions map[string]interf
 		sinfo.GatewayIPv6 = n.bridge.gatewayIPv6
 	}
 
+	// Program any required port mapping and store them in the endpoint
+	endpoint.portMapping, err = allocatePorts(epConfig, sinfo, config.DefaultBindingIP)
+	if err != nil {
+		return nil, err
+	}
+
 	return sinfo, nil
 }
 
@@ -535,6 +539,9 @@ func (d *driver) DeleteEndpoint(nid, eid types.UUID) error {
 		}
 	}()
 
+	// Remove port mappings. Do not stop endpoint delete on unmap failure
+	releasePorts(ep)
+
 	// Release the v4 address allocated to this endpoint's sandbox interface
 	err = ipAllocator.ReleaseIP(n.bridge.bridgeIPv4, ep.port.Address.IP)
 	if err != nil {
@@ -577,22 +584,26 @@ func parseEndpointOptions(epOptions map[string]interface{}) (*EndpointConfigurat
 	if epOptions == nil {
 		return nil, nil
 	}
-	genericData := epOptions[options.GenericData]
-	if genericData == nil {
-		return nil, nil
+
+	ec := &EndpointConfiguration{}
+
+	if opt, ok := epOptions[options.MacAddress]; ok {
+		if mac, ok := opt.(net.HardwareAddr); ok {
+			ec.MacAddress = mac
+		} else {
+			return nil, ErrInvalidEndpointConfig
+		}
 	}
-	switch opt := genericData.(type) {
-	case options.Generic:
-		opaqueConfig, err := options.GenerateFromModel(opt, &EndpointConfiguration{})
-		if err != nil {
-			return nil, err
+
+	if opt, ok := epOptions[options.PortMap]; ok {
+		if bs, ok := opt.([]netutils.PortBinding); ok {
+			ec.PortBindings = bs
+		} else {
+			return nil, ErrInvalidEndpointConfig
 		}
-		return opaqueConfig.(*EndpointConfiguration), nil
-	case *EndpointConfiguration:
-		return opt, nil
-	default:
-		return nil, ErrInvalidEndpointConfig
 	}
+
+	return ec, nil
 }
 
 func parseContainerOptions(cOptions interface{}) (*ContainerConfiguration, error) {
@@ -613,6 +624,13 @@ func parseContainerOptions(cOptions interface{}) (*ContainerConfiguration, error
 	}
 }
 
+func electMacAddress(epConfig *EndpointConfiguration) net.HardwareAddr {
+	if epConfig != nil && epConfig.MacAddress != nil {
+		return epConfig.MacAddress
+	}
+	return netutils.GenerateRandomMAC()
+}
+
 // Generates a name to be used for a virtual ethernet
 // interface. The name is constructed by 'veth' appended
 // by a randomly generated hex value. (example: veth0f60e2c)

+ 6 - 6
libnetwork/drivers/bridge/bridge_test.go

@@ -74,10 +74,10 @@ func TestCreateLinkWithOptions(t *testing.T) {
 	_, d := New()
 
 	config := &Configuration{BridgeName: DefaultBridgeName}
-	genericOption := make(map[string]interface{})
-	genericOption[options.GenericData] = config
+	driverOptions := make(map[string]interface{})
+	driverOptions[options.GenericData] = config
 
-	if err := d.Config(genericOption); err != nil {
+	if err := d.Config(driverOptions); err != nil {
 		t.Fatalf("Failed to setup driver config: %v", err)
 	}
 
@@ -87,10 +87,10 @@ func TestCreateLinkWithOptions(t *testing.T) {
 	}
 
 	mac := net.HardwareAddr([]byte{0x1e, 0x67, 0x66, 0x44, 0x55, 0x66})
-	epConf := &EndpointConfiguration{MacAddress: mac}
-	genericOption[options.GenericData] = epConf
+	epOptions := make(map[string]interface{})
+	epOptions[options.MacAddress] = mac
 
-	sinfo, err := d.CreateEndpoint("net1", "ep", genericOption)
+	sinfo, err := d.CreateEndpoint("net1", "ep", epOptions)
 	if err != nil {
 		t.Fatalf("Failed to create a link: %s", err.Error())
 	}

+ 22 - 1
libnetwork/drivers/bridge/error.go

@@ -37,10 +37,31 @@ var (
 	// ErrInvalidContainerSubnet is returned when the container subnet (FixedCIDR) is not valid.
 	ErrInvalidContainerSubnet = errors.New("container subnet must be a subset of bridge network")
 
-	// ErrInvalidMtu is returned when the user provided MTU is not valid
+	// ErrInvalidMtu is returned when the user provided MTU is not valid.
 	ErrInvalidMtu = errors.New("invalid MTU number")
 )
 
+// ErrInvalidPort is returned when the container or host port specified in the port binding is not valid.
+type ErrInvalidPort string
+
+func (ip ErrInvalidPort) Error() string {
+	return fmt.Sprintf("invalid transport port: %s", string(ip))
+}
+
+// ErrUnsupportedAddressType is returned when the specified address type is not supported.
+type ErrUnsupportedAddressType string
+
+func (uat ErrUnsupportedAddressType) Error() string {
+	return fmt.Sprintf("unsupported address type: %s", string(uat))
+}
+
+// ErrInvalidAddressBinding is returned when the host address specfied in the port binding is not valid.
+type ErrInvalidAddressBinding string
+
+func (iab ErrInvalidAddressBinding) Error() string {
+	return fmt.Sprintf("invalid host address in port binding: %s", string(iab))
+}
+
 // ActiveEndpointsError is returned when there are
 // still active endpoints in the network being deleted.
 type ActiveEndpointsError string

+ 124 - 0
libnetwork/drivers/bridge/port_mapping.go

@@ -0,0 +1,124 @@
+package bridge
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"net"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/libnetwork/netutils"
+	"github.com/docker/libnetwork/sandbox"
+)
+
+var (
+	defaultBindingIP = net.IPv4(0, 0, 0, 0)
+)
+
+func allocatePorts(epConfig *EndpointConfiguration, sinfo *sandbox.Info, reqDefBindIP net.IP) ([]netutils.PortBinding, error) {
+	if epConfig == nil || epConfig.PortBindings == nil {
+		return nil, nil
+	}
+
+	defHostIP := defaultBindingIP
+	if reqDefBindIP != nil {
+		defHostIP = reqDefBindIP
+	}
+
+	return allocatePortsInternal(epConfig.PortBindings, sinfo.Interfaces[0].Address.IP, defHostIP)
+}
+
+func allocatePortsInternal(bindings []netutils.PortBinding, containerIP, defHostIP net.IP) ([]netutils.PortBinding, error) {
+	bs := make([]netutils.PortBinding, 0, len(bindings))
+	for _, c := range bindings {
+		b := c.GetCopy()
+		if err := allocatePort(&b, containerIP, defHostIP); err != nil {
+			// On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
+			if cuErr := releasePortsInternal(bs); cuErr != nil {
+				logrus.Warnf("Upon allocation failure for %v, failed to clear previously allocated port bindings: %v", b, cuErr)
+			}
+			return nil, err
+		}
+		bs = append(bs, b)
+	}
+	return bs, nil
+}
+
+func allocatePort(bnd *netutils.PortBinding, containerIP, defHostIP net.IP) error {
+	var (
+		host net.Addr
+		err  error
+	)
+
+	// Store the container interface address in the operational binding
+	bnd.IP = containerIP
+
+	// Adjust the host address in the operational binding
+	if len(bnd.HostIP) == 0 {
+		bnd.HostIP = defHostIP
+	}
+
+	// Construct the container side transport address
+	container, err := bnd.ContainerAddr()
+	if err != nil {
+		return err
+	}
+
+	// Try up to maxAllocatePortAttempts times to get a port that's not already allocated.
+	for i := 0; i < maxAllocatePortAttempts; i++ {
+		if host, err = portMapper.Map(container, bnd.HostIP, int(bnd.HostPort)); err == nil {
+			break
+		}
+		// There is no point in immediately retrying to map an explicitly chosen port.
+		if bnd.HostPort != 0 {
+			logrus.Warnf("Failed to allocate and map port %d: %s", bnd.HostPort, err)
+			break
+		}
+		logrus.Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1)
+	}
+	if err != nil {
+		return err
+	}
+
+	// Save the host port (regardless it was or not specified in the binding)
+	switch netAddr := host.(type) {
+	case *net.TCPAddr:
+		bnd.HostPort = uint16(host.(*net.TCPAddr).Port)
+		return nil
+	case *net.UDPAddr:
+		bnd.HostPort = uint16(host.(*net.UDPAddr).Port)
+		return nil
+	default:
+		// For completeness
+		return ErrUnsupportedAddressType(fmt.Sprintf("%T", netAddr))
+	}
+}
+
+func releasePorts(ep *bridgeEndpoint) error {
+	return releasePortsInternal(ep.portMapping)
+}
+
+func releasePortsInternal(bindings []netutils.PortBinding) error {
+	var errorBuf bytes.Buffer
+
+	// Attempt to release all port bindings, do not stop on failure
+	for _, m := range bindings {
+		if err := releasePort(m); err != nil {
+			errorBuf.WriteString(fmt.Sprintf("\ncould not release %v because of %v", m, err))
+		}
+	}
+
+	if errorBuf.Len() != 0 {
+		return errors.New(errorBuf.String())
+	}
+	return nil
+}
+
+func releasePort(bnd netutils.PortBinding) error {
+	// Construct the host side transport address
+	host, err := bnd.HostAddr()
+	if err != nil {
+		return err
+	}
+	return portMapper.Unmap(host)
+}

+ 72 - 0
libnetwork/drivers/bridge/port_mapping_test.go

@@ -0,0 +1,72 @@
+package bridge
+
+import (
+	"fmt"
+	"os"
+	"testing"
+
+	"github.com/docker/docker/pkg/reexec"
+	"github.com/docker/libnetwork/netutils"
+	"github.com/docker/libnetwork/pkg/options"
+)
+
+func TestMain(m *testing.M) {
+	if reexec.Init() {
+		return
+	}
+	os.Exit(m.Run())
+}
+
+func TestPortMappingConfig(t *testing.T) {
+	defer netutils.SetupTestNetNS(t)()
+	_, d := New()
+
+	binding1 := netutils.PortBinding{Proto: netutils.UDP, Port: uint16(400), HostPort: uint16(54000)}
+	binding2 := netutils.PortBinding{Proto: netutils.TCP, Port: uint16(500), HostPort: uint16(65000)}
+	portBindings := []netutils.PortBinding{binding1, binding2}
+
+	epOptions := make(map[string]interface{})
+	epOptions[options.PortMap] = portBindings
+
+	driverConfig := &Configuration{
+		BridgeName:     DefaultBridgeName,
+		EnableIPTables: true,
+	}
+	driverOptions := make(map[string]interface{})
+	driverOptions[options.GenericData] = driverConfig
+
+	if err := d.Config(driverOptions); err != nil {
+		t.Fatalf("Failed to setup driver config: %v", err)
+	}
+
+	err := d.CreateNetwork("dummy", nil)
+	if err != nil {
+		t.Fatalf("Failed to create bridge: %v", err)
+	}
+
+	_, err = d.CreateEndpoint("dummy", "ep1", epOptions)
+	if err != nil {
+		t.Fatalf("Failed to create the endpoint: %s", err.Error())
+	}
+
+	dd := d.(*driver)
+	ep, _ := dd.network.endpoints["ep1"]
+	if len(ep.portMapping) != 2 {
+		t.Fatalf("Failed to store the port bindings into the sandbox info. Found: %v", ep.portMapping)
+	}
+	if ep.portMapping[0].Proto != binding1.Proto || ep.portMapping[0].Port != binding1.Port ||
+		ep.portMapping[1].Proto != binding2.Proto || ep.portMapping[1].Port != binding2.Port {
+		t.Fatalf("bridgeEndpoint has incorrect port mapping values")
+	}
+	if ep.portMapping[0].HostIP == nil || ep.portMapping[0].HostPort == 0 ||
+		ep.portMapping[1].HostIP == nil || ep.portMapping[1].HostPort == 0 {
+		t.Fatalf("operational port mapping data not found on bridgeEndpoint")
+	}
+
+	fmt.Printf("\nendpoint: %v\n", ep.portMapping)
+
+	err = releasePorts(ep)
+	if err != nil {
+		t.Fatalf("Failed to release mapped ports: %v", err)
+	}
+}

+ 73 - 80
libnetwork/endpoint.go

@@ -5,6 +5,8 @@ import (
 	"path/filepath"
 
 	"github.com/docker/docker/pkg/etchosts"
+	"github.com/docker/libnetwork/netutils"
+	"github.com/docker/libnetwork/pkg/options"
 	"github.com/docker/libnetwork/sandbox"
 	"github.com/docker/libnetwork/types"
 )
@@ -23,11 +25,11 @@ type Endpoint interface {
 	// Join creates a new sandbox for the given container ID and populates the
 	// network resources allocated for the endpoint and joins the sandbox to
 	// the endpoint. It returns the sandbox key to the caller
-	Join(containerID string, options ...JoinOption) (*ContainerData, error)
+	Join(containerID string, options ...EndpointOption) (*ContainerData, error)
 
 	// Leave removes the sandbox associated with  container ID and detaches
 	// the network resources populated in the sandbox
-	Leave(containerID string, options ...LeaveOption) error
+	Leave(containerID string, options ...EndpointOption) error
 
 	// SandboxInfo returns the sandbox information for this endpoint.
 	SandboxInfo() *sandbox.Info
@@ -36,43 +38,39 @@ type Endpoint interface {
 	Delete() error
 }
 
+// EndpointOption is a option setter function type used to pass varios options to Network
+// and Endpoint interfaces methods. The various setter functions of type EndpointOption are
+// provided by libnetwork, they look like <Create|Join|Leave>Option[...](...)
+type EndpointOption func(ep *endpoint)
+
 // ContainerData is a set of data returned when a container joins an endpoint.
 type ContainerData struct {
 	SandboxKey string
 	HostsPath  string
 }
 
-// JoinOption is a option setter function type used to pass varios options to
-// endpoint Join method. The various setter functions of type JoinOption are
-// provided by libnetwork, they look like JoinOption[...](...)
-type JoinOption func(ep *endpoint)
-
-// LeaveOption is a option setter function type used to pass varios options to
-// endpoint Leave method. The various setter functions of type LeaveOption are
-// provided by libnetwork, they look like LeaveOptionXXXX(...)
-type LeaveOption func(ep *endpoint)
-
 type containerConfig struct {
-	Hostname   string
-	Domainname string
+	hostName   string
+	domainName string
 	generic    map[string]interface{}
 }
 
 type containerInfo struct {
-	ID     string
-	Config containerConfig
-	Data   ContainerData
+	id     string
+	config containerConfig
+	data   ContainerData
 }
 
 type endpoint struct {
-	name        string
-	id          types.UUID
-	network     *network
-	sandboxInfo *sandbox.Info
-	sandBox     sandbox.Sandbox
-	container   *containerInfo
-	generic     map[string]interface{}
-	context     map[string]interface{}
+	name         string
+	id           types.UUID
+	network      *network
+	sandboxInfo  *sandbox.Info
+	sandBox      sandbox.Sandbox
+	container    *containerInfo
+	exposedPorts []netutils.TransportPort
+	generic      map[string]interface{}
+	context      map[string]interface{}
 }
 
 const prefix = "/var/lib/docker/network/files"
@@ -96,19 +94,6 @@ func (ep *endpoint) SandboxInfo() *sandbox.Info {
 	return ep.sandboxInfo.GetCopy()
 }
 
-// EndpointOption is a option setter function type used to pass various options to
-// CreateEndpoint method. The various setter functions of type EndpointOption are
-// provided by libnetwork, they look like EndpointOptionXXXX(...)
-type EndpointOption func(ep *endpoint)
-
-// EndpointOptionGeneric function returns an option setter for a Generic option defined
-// in a Dictionary of Key-Value pair
-func EndpointOptionGeneric(generic map[string]interface{}) EndpointOption {
-	return func(ep *endpoint) {
-		ep.generic = generic
-	}
-}
-
 func (ep *endpoint) processOptions(options ...EndpointOption) {
 	for _, opt := range options {
 		if opt != nil {
@@ -143,7 +128,7 @@ func createHostsFile(path string) error {
 	return err
 }
 
-func (ep *endpoint) Join(containerID string, options ...JoinOption) (*ContainerData, error) {
+func (ep *endpoint) Join(containerID string, options ...EndpointOption) (*ContainerData, error) {
 	var err error
 
 	if containerID == "" {
@@ -161,10 +146,10 @@ func (ep *endpoint) Join(containerID string, options ...JoinOption) (*ContainerD
 		}
 	}()
 
-	ep.processJoinOptions(options...)
+	ep.processOptions(options...)
 
-	ep.container.Data.HostsPath = prefix + "/" + containerID + "/hosts"
-	err = createHostsFile(ep.container.Data.HostsPath)
+	ep.container.data.HostsPath = prefix + "/" + containerID + "/hosts"
+	err = createHostsFile(ep.container.data.HostsPath)
 	if err != nil {
 		return nil, err
 	}
@@ -186,7 +171,7 @@ func (ep *endpoint) Join(containerID string, options ...JoinOption) (*ContainerD
 	}()
 
 	n := ep.network
-	err = n.driver.Join(n.id, ep.id, sboxKey, ep.container.Config.generic)
+	err = n.driver.Join(n.id, ep.id, sboxKey, ep.container.config.generic)
 	if err != nil {
 		return nil, err
 	}
@@ -211,28 +196,28 @@ func (ep *endpoint) Join(containerID string, options ...JoinOption) (*ContainerD
 		}
 	}
 
-	ep.container.ID = containerID
-	ep.container.Data.SandboxKey = sb.Key()
+	ep.container.id = containerID
+	ep.container.data.SandboxKey = sb.Key()
 
-	cData := ep.container.Data
+	cData := ep.container.data
 	return &cData, nil
 }
 
-func (ep *endpoint) Leave(containerID string, options ...LeaveOption) error {
-	if ep.container == nil || ep.container.ID == "" ||
-		containerID == "" || ep.container.ID != containerID {
+func (ep *endpoint) Leave(containerID string, options ...EndpointOption) error {
+	if ep.container == nil || ep.container.id == "" ||
+		containerID == "" || ep.container.id != containerID {
 		return InvalidContainerIDError(containerID)
 	}
 
-	n := ep.network
-	ep.processLeaveOptions(options...)
+	ep.processOptions(options...)
 
+	n := ep.network
 	err := n.driver.Leave(n.id, ep.id, ep.context)
 	if err != nil {
 		return err
 	}
 
-	ep.network.ctrlr.sandboxRm(ep.container.Data.SandboxKey)
+	ep.network.ctrlr.sandboxRm(ep.container.data.SandboxKey)
 	ep.container = nil
 	ep.context = nil
 	return nil
@@ -266,9 +251,9 @@ func (ep *endpoint) Delete() error {
 func (ep *endpoint) buildHostsFiles() error {
 	var extraContent []etchosts.Record
 
-	name := ep.container.Config.Hostname
-	if ep.container.Config.Domainname != "" {
-		name = name + "." + ep.container.Config.Domainname
+	name := ep.container.config.hostName
+	if ep.container.config.domainName != "" {
+		name = name + "." + ep.container.config.domainName
 	}
 
 	IP := ""
@@ -277,56 +262,64 @@ func (ep *endpoint) buildHostsFiles() error {
 		IP = ep.sandboxInfo.Interfaces[0].Address.IP.String()
 	}
 
-	return etchosts.Build(ep.container.Data.HostsPath, IP, ep.container.Config.Hostname,
-		ep.container.Config.Domainname, extraContent)
+	return etchosts.Build(ep.container.data.HostsPath, IP, ep.container.config.hostName,
+		ep.container.config.domainName, extraContent)
+}
+
+// EndpointOptionGeneric function returns an option setter for a Generic option defined
+// in a Dictionary of Key-Value pair
+func EndpointOptionGeneric(generic map[string]interface{}) EndpointOption {
+	return func(ep *endpoint) {
+		for k, v := range generic {
+			ep.generic[k] = v
+		}
+	}
 }
 
 // JoinOptionHostname function returns an option setter for hostname option to
 // be passed to endpoint Join method.
-func JoinOptionHostname(name string) JoinOption {
+func JoinOptionHostname(name string) EndpointOption {
 	return func(ep *endpoint) {
-		ep.container.Config.Hostname = name
+		ep.container.config.hostName = name
 	}
 }
 
 // JoinOptionDomainname function returns an option setter for domainname option to
 // be passed to endpoint Join method.
-func JoinOptionDomainname(name string) JoinOption {
+func JoinOptionDomainname(name string) EndpointOption {
 	return func(ep *endpoint) {
-		ep.container.Config.Domainname = name
+		ep.container.config.domainName = name
 	}
 }
 
-// JoinOptionGeneric function returns an option setter for Generic configuration
-// that is not managed by libNetwork but can be used by the Drivers during the call to
-// endpoint join method. Container Labels are a good example.
-func JoinOptionGeneric(generic map[string]interface{}) JoinOption {
+// CreateOptionPortMapping function returns an option setter for the container exposed
+// ports option to be passed to network.CreateEndpoint() method.
+func CreateOptionPortMapping(portBindings []netutils.PortBinding) EndpointOption {
 	return func(ep *endpoint) {
-		ep.container.Config.generic = generic
+		// Store endpoint label
+		ep.generic[options.PortMap] = portBindings
+		// Extract exposed ports as this is the only concern of libnetwork endpoint
+		ep.exposedPorts = make([]netutils.TransportPort, 0, len(portBindings))
+		for _, b := range portBindings {
+			ep.exposedPorts = append(ep.exposedPorts, netutils.TransportPort{Proto: b.Proto, Port: b.Port})
+		}
 	}
 }
 
-func (ep *endpoint) processJoinOptions(options ...JoinOption) {
-	for _, opt := range options {
-		if opt != nil {
-			opt(ep)
-		}
+// JoinOptionGeneric function returns an option setter for Generic configuration
+// that is not managed by libNetwork but can be used by the Drivers during the call to
+// endpoint join method. Container Labels are a good example.
+func JoinOptionGeneric(generic map[string]interface{}) EndpointOption {
+	return func(ep *endpoint) {
+		ep.container.config.generic = generic
 	}
 }
 
 // LeaveOptionGeneric function returns an option setter for Generic configuration
 // that is not managed by libNetwork but can be used by the Drivers during the call to
 // endpoint leave method. Container Labels are a good example.
-func LeaveOptionGeneric(context map[string]interface{}) JoinOption {
+func LeaveOptionGeneric(context map[string]interface{}) EndpointOption {
 	return func(ep *endpoint) {
 		ep.context = context
 	}
 }
-
-func (ep *endpoint) processLeaveOptions(options ...LeaveOption) {
-	for _, opt := range options {
-		if opt != nil {
-			opt(ep)
-		}
-	}
-}

+ 45 - 31
libnetwork/libnetwork_test.go

@@ -2,19 +2,28 @@ package libnetwork_test
 
 import (
 	"net"
+	"os"
 	"testing"
 
 	log "github.com/Sirupsen/logrus"
+	"github.com/docker/docker/pkg/reexec"
 	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork/netutils"
 	"github.com/docker/libnetwork/pkg/options"
 )
 
 const (
-	netType    = "bridge"
-	bridgeName = "dockertest0"
+	bridgeNetType = "bridge"
+	bridgeName    = "docker0"
 )
 
+func TestMain(m *testing.M) {
+	if reexec.Init() {
+		return
+	}
+	os.Exit(m.Run())
+}
+
 func createTestNetwork(networkType, networkName string, option options.Generic) (libnetwork.Network, error) {
 	controller := libnetwork.New()
 	genericOption := make(map[string]interface{})
@@ -39,6 +48,14 @@ func getEmptyGenericOption() map[string]interface{} {
 	return genericOption
 }
 
+func getPortMapping() []netutils.PortBinding {
+	return []netutils.PortBinding{
+		netutils.PortBinding{Proto: netutils.TCP, Port: uint16(230), HostPort: uint16(23000)},
+		netutils.PortBinding{Proto: netutils.UDP, Port: uint16(200), HostPort: uint16(22000)},
+		netutils.PortBinding{Proto: netutils.TCP, Port: uint16(120), HostPort: uint16(12000)},
+	}
+}
+
 func TestNull(t *testing.T) {
 	network, err := createTestNetwork("null", "testnetwork", options.Generic{})
 	if err != nil {
@@ -104,12 +121,12 @@ func TestBridge(t *testing.T) {
 		"EnableIPForwarding":    true,
 		"AllowNonDefaultBridge": true}
 
-	network, err := createTestNetwork(netType, "testnetwork", option)
+	network, err := createTestNetwork(bridgeNetType, "testnetwork", option)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	ep, err := network.CreateEndpoint("testep")
+	ep, err := network.CreateEndpoint("testep", libnetwork.CreateOptionPortMapping(getPortMapping()))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -171,17 +188,17 @@ func TestDuplicateNetwork(t *testing.T) {
 	genericOption := make(map[string]interface{})
 	genericOption[options.GenericData] = options.Generic{}
 
-	err := controller.ConfigureNetworkDriver(netType, genericOption)
+	err := controller.ConfigureNetworkDriver(bridgeNetType, genericOption)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	_, err = controller.NewNetwork(netType, "testnetwork", nil)
+	_, err = controller.NewNetwork(bridgeNetType, "testnetwork", nil)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	_, err = controller.NewNetwork(netType, "testnetwork")
+	_, err = controller.NewNetwork(bridgeNetType, "testnetwork")
 	if err == nil {
 		t.Fatal("Expected to fail. But instead succeeded")
 	}
@@ -192,9 +209,10 @@ func TestDuplicateNetwork(t *testing.T) {
 }
 
 func TestNetworkName(t *testing.T) {
+	defer netutils.SetupTestNetNS(t)()
 	networkName := "testnetwork"
 
-	n, err := createTestNetwork(netType, networkName, options.Generic{})
+	n, err := createTestNetwork(bridgeNetType, networkName, options.Generic{})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -205,22 +223,21 @@ func TestNetworkName(t *testing.T) {
 }
 
 func TestNetworkType(t *testing.T) {
-	networkType := netType
-
-	n, err := createTestNetwork(networkType, "testnetwork", options.Generic{})
+	defer netutils.SetupTestNetNS(t)()
+	n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{})
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	if n.Type() != networkType {
-		t.Fatalf("Expected network type %s, got %s", networkType, n.Type())
+	if n.Type() != bridgeNetType {
+		t.Fatalf("Expected network type %s, got %s", bridgeNetType, n.Type())
 	}
 }
 
 func TestNetworkID(t *testing.T) {
-	networkType := netType
+	defer netutils.SetupTestNetNS(t)()
 
-	n, err := createTestNetwork(networkType, "testnetwork", options.Generic{})
+	n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -236,7 +253,7 @@ func TestDeleteNetworkWithActiveEndpoints(t *testing.T) {
 		"BridgeName":            bridgeName,
 		"AllowNonDefaultBridge": true}
 
-	network, err := createTestNetwork(netType, "testnetwork", option)
+	network, err := createTestNetwork(bridgeNetType, "testnetwork", option)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -271,7 +288,7 @@ func TestUnknownNetwork(t *testing.T) {
 		"BridgeName":            bridgeName,
 		"AllowNonDefaultBridge": true}
 
-	network, err := createTestNetwork(netType, "testnetwork", option)
+	network, err := createTestNetwork(bridgeNetType, "testnetwork", option)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -304,7 +321,7 @@ func TestUnknownEndpoint(t *testing.T) {
 		"AddressIPv4":           subnet,
 		"AllowNonDefaultBridge": true}
 
-	network, err := createTestNetwork(netType, "testnetwork", option)
+	network, err := createTestNetwork(bridgeNetType, "testnetwork", option)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -337,15 +354,14 @@ func TestUnknownEndpoint(t *testing.T) {
 func TestNetworkEndpointsWalkers(t *testing.T) {
 	defer netutils.SetupTestNetNS(t)()
 	controller := libnetwork.New()
-	netType := "bridge"
 
-	err := controller.ConfigureNetworkDriver(netType, getEmptyGenericOption())
+	err := controller.ConfigureNetworkDriver(bridgeNetType, getEmptyGenericOption())
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	// Create network 1 and add 2 endpoint: ep11, ep12
-	net1, err := controller.NewNetwork(netType, "network1")
+	net1, err := controller.NewNetwork(bridgeNetType, "network1")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -416,15 +432,14 @@ func TestNetworkEndpointsWalkers(t *testing.T) {
 func TestControllerQuery(t *testing.T) {
 	defer netutils.SetupTestNetNS(t)()
 	controller := libnetwork.New()
-	netType := "bridge"
 
-	err := controller.ConfigureNetworkDriver(netType, getEmptyGenericOption())
+	err := controller.ConfigureNetworkDriver(bridgeNetType, getEmptyGenericOption())
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	// Create network 1
-	net1, err := controller.NewNetwork(netType, "network1")
+	net1, err := controller.NewNetwork(bridgeNetType, "network1")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -461,15 +476,14 @@ func TestControllerQuery(t *testing.T) {
 func TestNetworkQuery(t *testing.T) {
 	defer netutils.SetupTestNetNS(t)()
 	controller := libnetwork.New()
-	netType := "bridge"
 
-	err := controller.ConfigureNetworkDriver(netType, getEmptyGenericOption())
+	err := controller.ConfigureNetworkDriver(bridgeNetType, getEmptyGenericOption())
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	// Create network 1 and add 2 endpoint: ep11, ep12
-	net1, err := controller.NewNetwork(netType, "network1")
+	net1, err := controller.NewNetwork(bridgeNetType, "network1")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -514,7 +528,7 @@ const containerID = "valid_container"
 func TestEndpointJoin(t *testing.T) {
 	defer netutils.SetupTestNetNS(t)()
 
-	n, err := createTestNetwork("bridge", "testnetwork", options.Generic{})
+	n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -540,7 +554,7 @@ func TestEndpointJoin(t *testing.T) {
 func TestEndpointJoinInvalidContainerId(t *testing.T) {
 	defer netutils.SetupTestNetNS(t)()
 
-	n, err := createTestNetwork("bridge", "testnetwork", options.Generic{})
+	n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -563,7 +577,7 @@ func TestEndpointJoinInvalidContainerId(t *testing.T) {
 func TestEndpointMultipleJoins(t *testing.T) {
 	defer netutils.SetupTestNetNS(t)()
 
-	n, err := createTestNetwork("bridge", "testnetwork", options.Generic{})
+	n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -599,7 +613,7 @@ func TestEndpointMultipleJoins(t *testing.T) {
 func TestEndpointInvalidLeave(t *testing.T) {
 	defer netutils.SetupTestNetNS(t)()
 
-	n, err := createTestNetwork("bridge", "testnetwork", options.Generic{})
+	n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{})
 	if err != nil {
 		t.Fatal(err)
 	}

+ 97 - 0
libnetwork/netutils/utils.go

@@ -10,6 +10,7 @@ import (
 	"fmt"
 	"io"
 	"net"
+	"strings"
 
 	"github.com/vishvananda/netlink"
 )
@@ -25,6 +26,102 @@ var (
 	networkGetRoutesFct = netlink.RouteList
 )
 
+// ErrInvalidProtocolBinding is returned when the port binding protocol is not valid.
+type ErrInvalidProtocolBinding string
+
+func (ipb ErrInvalidProtocolBinding) Error() string {
+	return fmt.Sprintf("invalid transport protocol: %s", string(ipb))
+}
+
+// TransportPort represent a local Layer 4 endpoint
+type TransportPort struct {
+	Proto Protocol
+	Port  uint16
+}
+
+// PortBinding represent a port binding between the container an the host
+type PortBinding struct {
+	Proto    Protocol
+	IP       net.IP
+	Port     uint16
+	HostIP   net.IP
+	HostPort uint16
+}
+
+// HostAddr returns the host side tranport address
+func (p PortBinding) HostAddr() (net.Addr, error) {
+	switch p.Proto {
+	case UDP:
+		return &net.UDPAddr{IP: p.HostIP, Port: int(p.HostPort)}, nil
+	case TCP:
+		return &net.TCPAddr{IP: p.HostIP, Port: int(p.HostPort)}, nil
+	default:
+		return nil, ErrInvalidProtocolBinding(p.Proto.String())
+	}
+}
+
+// ContainerAddr returns the container side tranport address
+func (p PortBinding) ContainerAddr() (net.Addr, error) {
+	switch p.Proto {
+	case UDP:
+		return &net.UDPAddr{IP: p.IP, Port: int(p.Port)}, nil
+	case TCP:
+		return &net.TCPAddr{IP: p.IP, Port: int(p.Port)}, nil
+	default:
+		return nil, ErrInvalidProtocolBinding(p.Proto.String())
+	}
+}
+
+// GetCopy returns a copy of this PortBinding structure instance
+func (p *PortBinding) GetCopy() PortBinding {
+	return PortBinding{
+		Proto:    p.Proto,
+		IP:       GetIPCopy(p.IP),
+		Port:     p.Port,
+		HostIP:   GetIPCopy(p.HostIP),
+		HostPort: p.HostPort,
+	}
+}
+
+const (
+	// ICMP is for the ICMP ip protocol
+	ICMP = 1
+	// TCP is for the TCP ip protocol
+	TCP = 6
+	// UDP is for the UDP ip protocol
+	UDP = 17
+)
+
+// Protocol represents a IP protocol number
+type Protocol uint8
+
+func (p Protocol) String() string {
+	switch p {
+	case 1:
+		return "icmp"
+	case 6:
+		return "tcp"
+	case 17:
+		return "udp"
+	default:
+		return fmt.Sprintf("%d", p)
+	}
+}
+
+// ParseProtocol returns the respective Protocol type for the passed string
+func ParseProtocol(s string) Protocol {
+	switch strings.ToLower(s) {
+	case "icmp":
+		return 1
+	case "udp":
+		return 6
+	case "tcp":
+		return 17
+	default:
+		return 0
+	}
+}
+
 // CheckNameserverOverlaps checks whether the passed network overlaps with any of the nameservers
 func CheckNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error {
 	if len(nameservers) > 0 {

+ 1 - 1
libnetwork/network.go

@@ -127,7 +127,7 @@ func (n *network) Delete() error {
 }
 
 func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoint, error) {
-	ep := &endpoint{name: name}
+	ep := &endpoint{name: name, generic: make(map[string]interface{})}
 	ep.id = types.UUID(stringid.GenerateRandomID())
 	ep.network = n
 	ep.processOptions(options...)