diff --git a/libnetwork/Godeps/Godeps.json b/libnetwork/Godeps/Godeps.json index 44d9482cf5..30c8a14de8 100644 --- a/libnetwork/Godeps/Godeps.json +++ b/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", diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel.go deleted file mode 100644 index 70d09003a3..0000000000 --- a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel.go +++ /dev/null @@ -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 -} diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel_test.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel_test.go deleted file mode 100644 index e211a63b7d..0000000000 --- a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel_test.go +++ /dev/null @@ -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) -} diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/uname_linux.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/uname_linux.go deleted file mode 100644 index 8ca814c1fb..0000000000 --- a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/uname_linux.go +++ /dev/null @@ -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 -} diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/uname_unsupported.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/uname_unsupported.go deleted file mode 100644 index 00c5422589..0000000000 --- a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/uname_unsupported.go +++ /dev/null @@ -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") -} diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS b/libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS index 18e05a3070..edbe200669 100644 --- a/libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS +++ b/libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS @@ -1 +1,2 @@ Tianon Gravi (@tianon) +Aleksa Sarai (@cyphar) diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go b/libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go index 409c114e26..758b734c22 100644 --- a/libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go +++ b/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) } diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go b/libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go index 0f15c57d82..7217948870 100644 --- a/libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go +++ b/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 } diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go b/libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go index 69387f2ef6..d7439f12e3 100644 --- a/libnetwork/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go +++ b/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 diff --git a/libnetwork/drivers/bridge/bridge.go b/libnetwork/drivers/bridge/bridge.go index 48dc48b173..aec95d83ea 100644 --- a/libnetwork/drivers/bridge/bridge.go +++ b/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 } @@ -462,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 @@ -482,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 } @@ -531,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 { @@ -573,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 - } - switch opt := genericData.(type) { - case options.Generic: - opaqueConfig, err := options.GenerateFromModel(opt, &EndpointConfiguration{}) - if err != nil { - return nil, err + + ec := &EndpointConfiguration{} + + if opt, ok := epOptions[options.MacAddress]; ok { + if mac, ok := opt.(net.HardwareAddr); ok { + ec.MacAddress = mac + } else { + return nil, ErrInvalidEndpointConfig } - return opaqueConfig.(*EndpointConfiguration), nil - case *EndpointConfiguration: - return opt, nil - default: - 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) { diff --git a/libnetwork/drivers/bridge/bridge_test.go b/libnetwork/drivers/bridge/bridge_test.go index 68f2f29136..68aa684a30 100644 --- a/libnetwork/drivers/bridge/bridge_test.go +++ b/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()) } diff --git a/libnetwork/drivers/bridge/error.go b/libnetwork/drivers/bridge/error.go index fe635959e4..8c2ff3bbd5 100644 --- a/libnetwork/drivers/bridge/error.go +++ b/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 diff --git a/libnetwork/drivers/bridge/port_mapping.go b/libnetwork/drivers/bridge/port_mapping.go new file mode 100644 index 0000000000..08e9dee552 --- /dev/null +++ b/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) +} diff --git a/libnetwork/drivers/bridge/port_mapping_test.go b/libnetwork/drivers/bridge/port_mapping_test.go new file mode 100644 index 0000000000..886c04036b --- /dev/null +++ b/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) + } +} diff --git a/libnetwork/endpoint.go b/libnetwork/endpoint.go index 14d5835e29..4071b569e3 100644 --- a/libnetwork/endpoint.go +++ b/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" ) @@ -65,14 +67,15 @@ type containerInfo struct { } 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" @@ -186,7 +189,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 } @@ -297,12 +300,26 @@ func JoinOptionDomainname(name string) JoinOption { } } +// CreateOptionPortMapping function returns an option setter for the container exposed +// ports option to be passed to endpoint Join 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 // 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 { return func(ep *endpoint) { - ep.container.Config.generic = generic + ep.container.config.generic = generic } } diff --git a/libnetwork/libnetwork_test.go b/libnetwork/libnetwork_test.go index 1a601ebb4f..d6ec1ffe8d 100644 --- a/libnetwork/libnetwork_test.go +++ b/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) } diff --git a/libnetwork/netutils/utils.go b/libnetwork/netutils/utils.go index c3a32a145f..b24776ea09 100644 --- a/libnetwork/netutils/utils.go +++ b/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 { diff --git a/libnetwork/network.go b/libnetwork/network.go index 66c7420ff2..4f65677cf5 100644 --- a/libnetwork/network.go +++ b/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...)