Merge pull request #102 from aboch/pm

Bridge to handle port mapping
This commit is contained in:
Jana Radhakrishnan 2015-05-03 00:15:01 -07:00
commit 284c1713e8
18 changed files with 506 additions and 351 deletions

View file

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

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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")
}

View file

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

View file

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

View file

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

View file

@ -197,11 +197,11 @@ type ExecUser struct {
Home string 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 // 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 // files cannot be opened for any reason, the error is ignored and a nil
// io.Reader is passed instead. // 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) passwd, err := os.Open(passwdPath)
if err != nil { if err != nil {
passwd = nil passwd = nil

View file

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

View file

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

View file

@ -37,10 +37,31 @@ var (
// ErrInvalidContainerSubnet is returned when the container subnet (FixedCIDR) is not valid. // ErrInvalidContainerSubnet is returned when the container subnet (FixedCIDR) is not valid.
ErrInvalidContainerSubnet = errors.New("container subnet must be a subset of bridge network") 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") 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 // ActiveEndpointsError is returned when there are
// still active endpoints in the network being deleted. // still active endpoints in the network being deleted.
type ActiveEndpointsError string type ActiveEndpointsError string

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -5,6 +5,8 @@ import (
"path/filepath" "path/filepath"
"github.com/docker/docker/pkg/etchosts" "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/sandbox"
"github.com/docker/libnetwork/types" "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 // Join creates a new sandbox for the given container ID and populates the
// network resources allocated for the endpoint and joins the sandbox to // network resources allocated for the endpoint and joins the sandbox to
// the endpoint. It returns the sandbox key to the caller // 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 // Leave removes the sandbox associated with container ID and detaches
// the network resources populated in the sandbox // 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 returns the sandbox information for this endpoint.
SandboxInfo() *sandbox.Info SandboxInfo() *sandbox.Info
@ -36,32 +38,27 @@ type Endpoint interface {
Delete() error 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. // ContainerData is a set of data returned when a container joins an endpoint.
type ContainerData struct { type ContainerData struct {
SandboxKey string SandboxKey string
HostsPath 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 { type containerConfig struct {
Hostname string hostName string
Domainname string domainName string
generic map[string]interface{} generic map[string]interface{}
} }
type containerInfo struct { type containerInfo struct {
ID string id string
Config containerConfig config containerConfig
Data ContainerData data ContainerData
} }
type endpoint struct { type endpoint struct {
@ -71,6 +68,7 @@ type endpoint struct {
sandboxInfo *sandbox.Info sandboxInfo *sandbox.Info
sandBox sandbox.Sandbox sandBox sandbox.Sandbox
container *containerInfo container *containerInfo
exposedPorts []netutils.TransportPort
generic map[string]interface{} generic map[string]interface{}
context map[string]interface{} context map[string]interface{}
} }
@ -96,19 +94,6 @@ func (ep *endpoint) SandboxInfo() *sandbox.Info {
return ep.sandboxInfo.GetCopy() 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) { func (ep *endpoint) processOptions(options ...EndpointOption) {
for _, opt := range options { for _, opt := range options {
if opt != nil { if opt != nil {
@ -143,7 +128,7 @@ func createHostsFile(path string) error {
return err 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 var err error
if containerID == "" { 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" ep.container.data.HostsPath = prefix + "/" + containerID + "/hosts"
err = createHostsFile(ep.container.Data.HostsPath) err = createHostsFile(ep.container.data.HostsPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -186,7 +171,7 @@ func (ep *endpoint) Join(containerID string, options ...JoinOption) (*ContainerD
}() }()
n := ep.network 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 { if err != nil {
return nil, err return nil, err
} }
@ -211,28 +196,28 @@ func (ep *endpoint) Join(containerID string, options ...JoinOption) (*ContainerD
} }
} }
ep.container.ID = containerID ep.container.id = containerID
ep.container.Data.SandboxKey = sb.Key() ep.container.data.SandboxKey = sb.Key()
cData := ep.container.Data cData := ep.container.data
return &cData, nil return &cData, nil
} }
func (ep *endpoint) Leave(containerID string, options ...LeaveOption) error { func (ep *endpoint) Leave(containerID string, options ...EndpointOption) error {
if ep.container == nil || ep.container.ID == "" || if ep.container == nil || ep.container.id == "" ||
containerID == "" || ep.container.ID != containerID { containerID == "" || ep.container.id != containerID {
return InvalidContainerIDError(containerID) return InvalidContainerIDError(containerID)
} }
n := ep.network ep.processOptions(options...)
ep.processLeaveOptions(options...)
n := ep.network
err := n.driver.Leave(n.id, ep.id, ep.context) err := n.driver.Leave(n.id, ep.id, ep.context)
if err != nil { if err != nil {
return err return err
} }
ep.network.ctrlr.sandboxRm(ep.container.Data.SandboxKey) ep.network.ctrlr.sandboxRm(ep.container.data.SandboxKey)
ep.container = nil ep.container = nil
ep.context = nil ep.context = nil
return nil return nil
@ -266,9 +251,9 @@ func (ep *endpoint) Delete() error {
func (ep *endpoint) buildHostsFiles() error { func (ep *endpoint) buildHostsFiles() error {
var extraContent []etchosts.Record var extraContent []etchosts.Record
name := ep.container.Config.Hostname name := ep.container.config.hostName
if ep.container.Config.Domainname != "" { if ep.container.config.domainName != "" {
name = name + "." + ep.container.Config.Domainname name = name + "." + ep.container.config.domainName
} }
IP := "" IP := ""
@ -277,56 +262,64 @@ func (ep *endpoint) buildHostsFiles() error {
IP = ep.sandboxInfo.Interfaces[0].Address.IP.String() IP = ep.sandboxInfo.Interfaces[0].Address.IP.String()
} }
return etchosts.Build(ep.container.Data.HostsPath, IP, ep.container.Config.Hostname, return etchosts.Build(ep.container.data.HostsPath, IP, ep.container.config.hostName,
ep.container.Config.Domainname, extraContent) 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 // JoinOptionHostname function returns an option setter for hostname option to
// be passed to endpoint Join method. // be passed to endpoint Join method.
func JoinOptionHostname(name string) JoinOption { func JoinOptionHostname(name string) EndpointOption {
return func(ep *endpoint) { return func(ep *endpoint) {
ep.container.Config.Hostname = name ep.container.config.hostName = name
} }
} }
// JoinOptionDomainname function returns an option setter for domainname option to // JoinOptionDomainname function returns an option setter for domainname option to
// be passed to endpoint Join method. // be passed to endpoint Join method.
func JoinOptionDomainname(name string) JoinOption { func JoinOptionDomainname(name string) EndpointOption {
return func(ep *endpoint) { return func(ep *endpoint) {
ep.container.Config.Domainname = name ep.container.config.domainName = name
}
}
// 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) {
// 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})
}
} }
} }
// JoinOptionGeneric function returns an option setter for Generic configuration // 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 // 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. // endpoint join method. Container Labels are a good example.
func JoinOptionGeneric(generic map[string]interface{}) JoinOption { func JoinOptionGeneric(generic map[string]interface{}) EndpointOption {
return func(ep *endpoint) { return func(ep *endpoint) {
ep.container.Config.generic = generic ep.container.config.generic = generic
}
}
func (ep *endpoint) processJoinOptions(options ...JoinOption) {
for _, opt := range options {
if opt != nil {
opt(ep)
}
} }
} }
// LeaveOptionGeneric function returns an option setter for Generic configuration // 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 // 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. // 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) { return func(ep *endpoint) {
ep.context = context ep.context = context
} }
} }
func (ep *endpoint) processLeaveOptions(options ...LeaveOption) {
for _, opt := range options {
if opt != nil {
opt(ep)
}
}
}

View file

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

View file

@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"strings"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
) )
@ -25,6 +26,102 @@ var (
networkGetRoutesFct = netlink.RouteList 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 // CheckNameserverOverlaps checks whether the passed network overlaps with any of the nameservers
func CheckNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error { func CheckNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error {
if len(nameservers) > 0 { if len(nameservers) > 0 {

View file

@ -127,7 +127,7 @@ func (n *network) Delete() error {
} }
func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoint, 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.id = types.UUID(stringid.GenerateRandomID())
ep.network = n ep.network = n
ep.processOptions(options...) ep.processOptions(options...)