diff --git a/Makefile b/Makefile index b455c12f43766..3e6935666a59d 100644 --- a/Makefile +++ b/Makefile @@ -101,6 +101,11 @@ bundles: cross: build $(DOCKER_RUN_DOCKER) hack/make.sh dynbinary binary cross + + +win: build + $(DOCKER_RUN_DOCKER) hack/make.sh win + deb: build $(DOCKER_RUN_DOCKER) hack/make.sh dynbinary build-deb diff --git a/container/container.go b/container/container.go index 42ce6db8f41ed..b62b2b316a9b5 100644 --- a/container/container.go +++ b/container/container.go @@ -4,8 +4,11 @@ import ( "encoding/json" "fmt" "io" + "net" "os" "path/filepath" + "strconv" + "strings" "sync" "syscall" "time" @@ -23,14 +26,25 @@ import ( "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/runconfig" + runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/docker/volume" containertypes "github.com/docker/engine-api/types/container" + networktypes "github.com/docker/engine-api/types/network" "github.com/docker/go-connections/nat" + "github.com/docker/libnetwork" + "github.com/docker/libnetwork/netlabel" + "github.com/docker/libnetwork/options" + "github.com/docker/libnetwork/types" "github.com/opencontainers/runc/libcontainer/label" ) const configFileName = "config.v2.json" +var ( + errInvalidEndpoint = fmt.Errorf("invalid endpoint while building port map info") + errInvalidNetwork = fmt.Errorf("invalid network settings while building port map info") +) + // CommonContainer holds the fields for a container which are // applicable across all platforms supported by the daemon. type CommonContainer struct { @@ -581,6 +595,293 @@ func (container *Container) InitDNSHostConfig() { } } +// GetEndpointInNetwork returns the container's endpoint to the provided network. +func (container *Container) GetEndpointInNetwork(n libnetwork.Network) (libnetwork.Endpoint, error) { + endpointName := strings.TrimPrefix(container.Name, "/") + return n.EndpointByName(endpointName) +} + +func (container *Container) buildPortMapInfo(ep libnetwork.Endpoint) error { + if ep == nil { + return errInvalidEndpoint + } + + networkSettings := container.NetworkSettings + if networkSettings == nil { + return errInvalidNetwork + } + + if len(networkSettings.Ports) == 0 { + pm, err := getEndpointPortMapInfo(ep) + if err != nil { + return err + } + networkSettings.Ports = pm + } + return nil +} + +func getEndpointPortMapInfo(ep libnetwork.Endpoint) (nat.PortMap, error) { + pm := nat.PortMap{} + driverInfo, err := ep.DriverInfo() + if err != nil { + return pm, err + } + + if driverInfo == nil { + // It is not an error for epInfo to be nil + return pm, nil + } + + if expData, ok := driverInfo[netlabel.ExposedPorts]; ok { + if exposedPorts, ok := expData.([]types.TransportPort); ok { + for _, tp := range exposedPorts { + natPort, err := nat.NewPort(tp.Proto.String(), strconv.Itoa(int(tp.Port))) + if err != nil { + return pm, fmt.Errorf("Error parsing Port value(%v):%v", tp.Port, err) + } + pm[natPort] = nil + } + } + } + + mapData, ok := driverInfo[netlabel.PortMap] + if !ok { + return pm, nil + } + + if portMapping, ok := mapData.([]types.PortBinding); ok { + for _, pp := range portMapping { + natPort, err := nat.NewPort(pp.Proto.String(), strconv.Itoa(int(pp.Port))) + if err != nil { + return pm, err + } + natBndg := nat.PortBinding{HostIP: pp.HostIP.String(), HostPort: strconv.Itoa(int(pp.HostPort))} + pm[natPort] = append(pm[natPort], natBndg) + } + } + + return pm, nil +} + +func getSandboxPortMapInfo(sb libnetwork.Sandbox) nat.PortMap { + pm := nat.PortMap{} + if sb == nil { + return pm + } + + for _, ep := range sb.Endpoints() { + pm, _ = getEndpointPortMapInfo(ep) + if len(pm) > 0 { + break + } + } + return pm +} + +// BuildEndpointInfo sets endpoint-related fields on container.NetworkSettings based on the provided network and endpoint. +func (container *Container) BuildEndpointInfo(n libnetwork.Network, ep libnetwork.Endpoint) error { + if ep == nil { + return errInvalidEndpoint + } + + networkSettings := container.NetworkSettings + if networkSettings == nil { + return errInvalidNetwork + } + + epInfo := ep.Info() + if epInfo == nil { + // It is not an error to get an empty endpoint info + return nil + } + + if _, ok := networkSettings.Networks[n.Name()]; !ok { + networkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings) + } + networkSettings.Networks[n.Name()].NetworkID = n.ID() + networkSettings.Networks[n.Name()].EndpointID = ep.ID() + + iface := epInfo.Iface() + if iface == nil { + return nil + } + + if iface.MacAddress() != nil { + networkSettings.Networks[n.Name()].MacAddress = iface.MacAddress().String() + } + + if iface.Address() != nil { + ones, _ := iface.Address().Mask.Size() + networkSettings.Networks[n.Name()].IPAddress = iface.Address().IP.String() + networkSettings.Networks[n.Name()].IPPrefixLen = ones + } + + if iface.AddressIPv6() != nil && iface.AddressIPv6().IP.To16() != nil { + onesv6, _ := iface.AddressIPv6().Mask.Size() + networkSettings.Networks[n.Name()].GlobalIPv6Address = iface.AddressIPv6().IP.String() + networkSettings.Networks[n.Name()].GlobalIPv6PrefixLen = onesv6 + } + + return nil +} + +// UpdateJoinInfo updates network settings when container joins network n with endpoint ep. +func (container *Container) UpdateJoinInfo(n libnetwork.Network, ep libnetwork.Endpoint) error { + if err := container.buildPortMapInfo(ep); err != nil { + return err + } + + epInfo := ep.Info() + if epInfo == nil { + // It is not an error to get an empty endpoint info + return nil + } + if epInfo.Gateway() != nil { + container.NetworkSettings.Networks[n.Name()].Gateway = epInfo.Gateway().String() + } + if epInfo.GatewayIPv6().To16() != nil { + container.NetworkSettings.Networks[n.Name()].IPv6Gateway = epInfo.GatewayIPv6().String() + } + + return nil +} + +// UpdateSandboxNetworkSettings updates the sandbox ID and Key. +func (container *Container) UpdateSandboxNetworkSettings(sb libnetwork.Sandbox) error { + container.NetworkSettings.SandboxID = sb.ID() + container.NetworkSettings.SandboxKey = sb.Key() + return nil +} + +// BuildJoinOptions builds endpoint Join options from a given network. +func (container *Container) BuildJoinOptions(n libnetwork.Network) ([]libnetwork.EndpointOption, error) { + var joinOptions []libnetwork.EndpointOption + if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok { + for _, str := range epConfig.Links { + name, alias, err := runconfigopts.ParseLink(str) + if err != nil { + return nil, err + } + joinOptions = append(joinOptions, libnetwork.CreateOptionAlias(name, alias)) + } + } + return joinOptions, nil +} + +// BuildCreateEndpointOptions builds endpoint options from a given network. +func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epConfig *networktypes.EndpointSettings, sb libnetwork.Sandbox) ([]libnetwork.EndpointOption, error) { + var ( + bindings = make(nat.PortMap) + pbList []types.PortBinding + exposeList []types.TransportPort + createOptions []libnetwork.EndpointOption + ) + + defaultNetName := runconfig.DefaultDaemonNetworkMode().NetworkName() + + if n.Name() == defaultNetName || container.NetworkSettings.IsAnonymousEndpoint { + createOptions = append(createOptions, libnetwork.CreateOptionAnonymous()) + } + + if epConfig != nil { + ipam := epConfig.IPAMConfig + if ipam != nil && (ipam.IPv4Address != "" || ipam.IPv6Address != "") { + createOptions = append(createOptions, + libnetwork.CreateOptionIpam(net.ParseIP(ipam.IPv4Address), net.ParseIP(ipam.IPv6Address), nil)) + } + + for _, alias := range epConfig.Aliases { + createOptions = append(createOptions, libnetwork.CreateOptionMyAlias(alias)) + } + } + + if !containertypes.NetworkMode(n.Name()).IsUserDefined() { + createOptions = append(createOptions, libnetwork.CreateOptionDisableResolution()) + } + + // configs that are applicable only for the endpoint in the network + // to which container was connected to on docker run. + // Ideally all these network-specific endpoint configurations must be moved under + // container.NetworkSettings.Networks[n.Name()] + if n.Name() == container.HostConfig.NetworkMode.NetworkName() || + (n.Name() == defaultNetName && container.HostConfig.NetworkMode.IsDefault()) { + if container.Config.MacAddress != "" { + mac, err := net.ParseMAC(container.Config.MacAddress) + if err != nil { + return nil, err + } + + genericOption := options.Generic{ + netlabel.MacAddress: mac, + } + + createOptions = append(createOptions, libnetwork.EndpointOptionGeneric(genericOption)) + } + } + + // Port-mapping rules belong to the container & applicable only to non-internal networks + portmaps := getSandboxPortMapInfo(sb) + if n.Info().Internal() || len(portmaps) > 0 { + return createOptions, nil + } + + if container.HostConfig.PortBindings != nil { + for p, b := range container.HostConfig.PortBindings { + bindings[p] = []nat.PortBinding{} + for _, bb := range b { + bindings[p] = append(bindings[p], nat.PortBinding{ + HostIP: bb.HostIP, + HostPort: bb.HostPort, + }) + } + } + } + + portSpecs := container.Config.ExposedPorts + ports := make([]nat.Port, len(portSpecs)) + var i int + for p := range portSpecs { + ports[i] = p + i++ + } + nat.SortPortMap(ports, bindings) + for _, port := range ports { + expose := types.TransportPort{} + expose.Proto = types.ParseProtocol(port.Proto()) + expose.Port = uint16(port.Int()) + exposeList = append(exposeList, expose) + + pb := types.PortBinding{Port: expose.Port, Proto: expose.Proto} + binding := bindings[port] + for i := 0; i < len(binding); i++ { + pbCopy := pb.GetCopy() + newP, err := nat.NewPort(nat.SplitProtoPort(binding[i].HostPort)) + var portStart, portEnd int + if err == nil { + portStart, portEnd, err = newP.Range() + } + if err != nil { + return nil, fmt.Errorf("Error parsing HostPort value(%s):%v", binding[i].HostPort, err) + } + pbCopy.HostPort = uint16(portStart) + pbCopy.HostPortEnd = uint16(portEnd) + pbCopy.HostIP = net.ParseIP(binding[i].HostIP) + pbList = append(pbList, pbCopy) + } + + if container.HostConfig.PublishAllPorts && len(binding) == 0 { + pbList = append(pbList, pb) + } + } + + createOptions = append(createOptions, + libnetwork.CreateOptionPortMapping(pbList), + libnetwork.CreateOptionExposedPorts(exposeList)) + + return createOptions, nil +} + // UpdateMonitor updates monitor configure for running container func (container *Container) UpdateMonitor(restartPolicy containertypes.RestartPolicy) { monitor := container.monitor diff --git a/container/container_unix.go b/container/container_unix.go index 3cffb8a1d093c..0c41121975427 100644 --- a/container/container_unix.go +++ b/container/container_unix.go @@ -5,10 +5,8 @@ package container import ( "fmt" "io/ioutil" - "net" "os" "path/filepath" - "strconv" "strings" "syscall" @@ -17,27 +15,15 @@ import ( "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/system" - runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/docker/utils" "github.com/docker/docker/volume" containertypes "github.com/docker/engine-api/types/container" - "github.com/docker/engine-api/types/network" - "github.com/docker/go-connections/nat" - "github.com/docker/libnetwork" - "github.com/docker/libnetwork/netlabel" - "github.com/docker/libnetwork/options" - "github.com/docker/libnetwork/types" "github.com/opencontainers/runc/libcontainer/label" ) // DefaultSHMSize is the default size (64MB) of the SHM which will be mounted in the container const DefaultSHMSize int64 = 67108864 -var ( - errInvalidEndpoint = fmt.Errorf("invalid endpoint while building port map info") - errInvalidNetwork = fmt.Errorf("invalid network settings while building port map info") -) - // Container holds the fields specific to unixen implementations. // See CommonContainer for standard fields common to all containers. type Container struct { @@ -113,291 +99,6 @@ func (container *Container) BuildHostnameFile() error { return ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644) } -// GetEndpointInNetwork returns the container's endpoint to the provided network. -func (container *Container) GetEndpointInNetwork(n libnetwork.Network) (libnetwork.Endpoint, error) { - endpointName := strings.TrimPrefix(container.Name, "/") - return n.EndpointByName(endpointName) -} - -func (container *Container) buildPortMapInfo(ep libnetwork.Endpoint) error { - if ep == nil { - return errInvalidEndpoint - } - - networkSettings := container.NetworkSettings - if networkSettings == nil { - return errInvalidNetwork - } - - if len(networkSettings.Ports) == 0 { - pm, err := getEndpointPortMapInfo(ep) - if err != nil { - return err - } - networkSettings.Ports = pm - } - return nil -} - -func getEndpointPortMapInfo(ep libnetwork.Endpoint) (nat.PortMap, error) { - pm := nat.PortMap{} - driverInfo, err := ep.DriverInfo() - if err != nil { - return pm, err - } - - if driverInfo == nil { - // It is not an error for epInfo to be nil - return pm, nil - } - - if expData, ok := driverInfo[netlabel.ExposedPorts]; ok { - if exposedPorts, ok := expData.([]types.TransportPort); ok { - for _, tp := range exposedPorts { - natPort, err := nat.NewPort(tp.Proto.String(), strconv.Itoa(int(tp.Port))) - if err != nil { - return pm, fmt.Errorf("Error parsing Port value(%v):%v", tp.Port, err) - } - pm[natPort] = nil - } - } - } - - mapData, ok := driverInfo[netlabel.PortMap] - if !ok { - return pm, nil - } - - if portMapping, ok := mapData.([]types.PortBinding); ok { - for _, pp := range portMapping { - natPort, err := nat.NewPort(pp.Proto.String(), strconv.Itoa(int(pp.Port))) - if err != nil { - return pm, err - } - natBndg := nat.PortBinding{HostIP: pp.HostIP.String(), HostPort: strconv.Itoa(int(pp.HostPort))} - pm[natPort] = append(pm[natPort], natBndg) - } - } - - return pm, nil -} - -func getSandboxPortMapInfo(sb libnetwork.Sandbox) nat.PortMap { - pm := nat.PortMap{} - if sb == nil { - return pm - } - - for _, ep := range sb.Endpoints() { - pm, _ = getEndpointPortMapInfo(ep) - if len(pm) > 0 { - break - } - } - return pm -} - -// BuildEndpointInfo sets endpoint-related fields on container.NetworkSettings based on the provided network and endpoint. -func (container *Container) BuildEndpointInfo(n libnetwork.Network, ep libnetwork.Endpoint) error { - if ep == nil { - return errInvalidEndpoint - } - - networkSettings := container.NetworkSettings - if networkSettings == nil { - return errInvalidNetwork - } - - epInfo := ep.Info() - if epInfo == nil { - // It is not an error to get an empty endpoint info - return nil - } - - if _, ok := networkSettings.Networks[n.Name()]; !ok { - networkSettings.Networks[n.Name()] = new(network.EndpointSettings) - } - networkSettings.Networks[n.Name()].NetworkID = n.ID() - networkSettings.Networks[n.Name()].EndpointID = ep.ID() - - iface := epInfo.Iface() - if iface == nil { - return nil - } - - if iface.MacAddress() != nil { - networkSettings.Networks[n.Name()].MacAddress = iface.MacAddress().String() - } - - if iface.Address() != nil { - ones, _ := iface.Address().Mask.Size() - networkSettings.Networks[n.Name()].IPAddress = iface.Address().IP.String() - networkSettings.Networks[n.Name()].IPPrefixLen = ones - } - - if iface.AddressIPv6() != nil && iface.AddressIPv6().IP.To16() != nil { - onesv6, _ := iface.AddressIPv6().Mask.Size() - networkSettings.Networks[n.Name()].GlobalIPv6Address = iface.AddressIPv6().IP.String() - networkSettings.Networks[n.Name()].GlobalIPv6PrefixLen = onesv6 - } - - return nil -} - -// UpdateJoinInfo updates network settings when container joins network n with endpoint ep. -func (container *Container) UpdateJoinInfo(n libnetwork.Network, ep libnetwork.Endpoint) error { - if err := container.buildPortMapInfo(ep); err != nil { - return err - } - - epInfo := ep.Info() - if epInfo == nil { - // It is not an error to get an empty endpoint info - return nil - } - if epInfo.Gateway() != nil { - container.NetworkSettings.Networks[n.Name()].Gateway = epInfo.Gateway().String() - } - if epInfo.GatewayIPv6().To16() != nil { - container.NetworkSettings.Networks[n.Name()].IPv6Gateway = epInfo.GatewayIPv6().String() - } - - return nil -} - -// UpdateSandboxNetworkSettings updates the sandbox ID and Key. -func (container *Container) UpdateSandboxNetworkSettings(sb libnetwork.Sandbox) error { - container.NetworkSettings.SandboxID = sb.ID() - container.NetworkSettings.SandboxKey = sb.Key() - return nil -} - -// BuildJoinOptions builds endpoint Join options from a given network. -func (container *Container) BuildJoinOptions(n libnetwork.Network) ([]libnetwork.EndpointOption, error) { - var joinOptions []libnetwork.EndpointOption - if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok { - for _, str := range epConfig.Links { - name, alias, err := runconfigopts.ParseLink(str) - if err != nil { - return nil, err - } - joinOptions = append(joinOptions, libnetwork.CreateOptionAlias(name, alias)) - } - } - return joinOptions, nil -} - -// BuildCreateEndpointOptions builds endpoint options from a given network. -func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epConfig *network.EndpointSettings, sb libnetwork.Sandbox) ([]libnetwork.EndpointOption, error) { - var ( - bindings = make(nat.PortMap) - pbList []types.PortBinding - exposeList []types.TransportPort - createOptions []libnetwork.EndpointOption - ) - - if n.Name() == "bridge" || container.NetworkSettings.IsAnonymousEndpoint { - createOptions = append(createOptions, libnetwork.CreateOptionAnonymous()) - } - - if epConfig != nil { - ipam := epConfig.IPAMConfig - if ipam != nil && (ipam.IPv4Address != "" || ipam.IPv6Address != "") { - createOptions = append(createOptions, - libnetwork.CreateOptionIpam(net.ParseIP(ipam.IPv4Address), net.ParseIP(ipam.IPv6Address), nil)) - } - - for _, alias := range epConfig.Aliases { - createOptions = append(createOptions, libnetwork.CreateOptionMyAlias(alias)) - } - } - - if !containertypes.NetworkMode(n.Name()).IsUserDefined() { - createOptions = append(createOptions, libnetwork.CreateOptionDisableResolution()) - } - - // configs that are applicable only for the endpoint in the network - // to which container was connected to on docker run. - // Ideally all these network-specific endpoint configurations must be moved under - // container.NetworkSettings.Networks[n.Name()] - if n.Name() == container.HostConfig.NetworkMode.NetworkName() || - (n.Name() == "bridge" && container.HostConfig.NetworkMode.IsDefault()) { - if container.Config.MacAddress != "" { - mac, err := net.ParseMAC(container.Config.MacAddress) - if err != nil { - return nil, err - } - - genericOption := options.Generic{ - netlabel.MacAddress: mac, - } - - createOptions = append(createOptions, libnetwork.EndpointOptionGeneric(genericOption)) - } - } - - // Port-mapping rules belong to the container & applicable only to non-internal networks - portmaps := getSandboxPortMapInfo(sb) - if n.Info().Internal() || len(portmaps) > 0 { - return createOptions, nil - } - - if container.HostConfig.PortBindings != nil { - for p, b := range container.HostConfig.PortBindings { - bindings[p] = []nat.PortBinding{} - for _, bb := range b { - bindings[p] = append(bindings[p], nat.PortBinding{ - HostIP: bb.HostIP, - HostPort: bb.HostPort, - }) - } - } - } - - portSpecs := container.Config.ExposedPorts - ports := make([]nat.Port, len(portSpecs)) - var i int - for p := range portSpecs { - ports[i] = p - i++ - } - nat.SortPortMap(ports, bindings) - for _, port := range ports { - expose := types.TransportPort{} - expose.Proto = types.ParseProtocol(port.Proto()) - expose.Port = uint16(port.Int()) - exposeList = append(exposeList, expose) - - pb := types.PortBinding{Port: expose.Port, Proto: expose.Proto} - binding := bindings[port] - for i := 0; i < len(binding); i++ { - pbCopy := pb.GetCopy() - newP, err := nat.NewPort(nat.SplitProtoPort(binding[i].HostPort)) - var portStart, portEnd int - if err == nil { - portStart, portEnd, err = newP.Range() - } - if err != nil { - return nil, fmt.Errorf("Error parsing HostPort value(%s):%v", binding[i].HostPort, err) - } - pbCopy.HostPort = uint16(portStart) - pbCopy.HostPortEnd = uint16(portEnd) - pbCopy.HostIP = net.ParseIP(binding[i].HostIP) - pbList = append(pbList, pbCopy) - } - - if container.HostConfig.PublishAllPorts && len(binding) == 0 { - pbList = append(pbList, pb) - } - } - - createOptions = append(createOptions, - libnetwork.CreateOptionPortMapping(pbList), - libnetwork.CreateOptionExposedPorts(exposeList)) - - return createOptions, nil -} - // appendNetworkMounts appends any network mounts to the array of mount points passed in func appendNetworkMounts(container *Container, volumeMounts []volume.MountPoint) ([]volume.MountPoint, error) { for _, mnt := range container.NetworkMounts() { diff --git a/container/container_windows.go b/container/container_windows.go index 18c7e0b0ffb25..fb24ebb968913 100644 --- a/container/container_windows.go +++ b/container/container_windows.go @@ -17,6 +17,9 @@ import ( type Container struct { CommonContainer + HostnamePath string + HostsPath string + ResolvConfPath string // Fields below here are platform specific. } @@ -84,6 +87,11 @@ func cleanResourcePath(path string) string { return filepath.Join(string(os.PathSeparator), path) } +// BuildHostnameFile writes the container's hostname file. +func (container *Container) BuildHostnameFile() error { + return nil +} + // canMountFS determines if the file system for the container // can be mounted locally. In the case of Windows, this is not possible // for Hyper-V containers during WORKDIR execution for example. diff --git a/daemon/config_windows.go b/daemon/config_windows.go index cad0c8719c888..9d5d61bffbf67 100644 --- a/daemon/config_windows.go +++ b/daemon/config_windows.go @@ -15,7 +15,9 @@ var ( // bridgeConfig stores all the bridge driver specific // configuration. type bridgeConfig struct { - VirtualSwitchName string `json:"bridge,omitempty"` + FixedCIDR string + NetworkMode string + Iface string `json:"bridge,omitempty"` } // Config defines the configuration of a docker daemon. @@ -37,6 +39,7 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin config.InstallCommonFlags(cmd, usageFn) // Then platform-specific install flags. - cmd.StringVar(&config.bridgeConfig.VirtualSwitchName, []string{"b", "-bridge"}, "", "Attach containers to a virtual switch") + cmd.StringVar(&config.bridgeConfig.FixedCIDR, []string{"-fixed-cidr"}, "", usageFn("IPv4 subnet for fixed IPs")) + cmd.StringVar(&config.bridgeConfig.Iface, []string{"b", "-bridge"}, "", "Attach containers to a virtual switch") cmd.StringVar(&config.SocketGroup, []string{"G", "-group"}, "", usageFn("Users or groups that can access the named pipe")) } diff --git a/daemon/container_operations.go b/daemon/container_operations.go index f36e58032504a..3aaca03351da7 100644 --- a/daemon/container_operations.go +++ b/daemon/container_operations.go @@ -1,9 +1,745 @@ package daemon -import "errors" +import ( + "errors" + "fmt" + "net" + "os" + "path" + "strings" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/container" + "github.com/docker/docker/daemon/network" + derr "github.com/docker/docker/errors" + "github.com/docker/docker/runconfig" + containertypes "github.com/docker/engine-api/types/container" + networktypes "github.com/docker/engine-api/types/network" + "github.com/docker/go-connections/nat" + "github.com/docker/libnetwork" + "github.com/docker/libnetwork/netlabel" + "github.com/docker/libnetwork/options" + "github.com/docker/libnetwork/types" +) var ( // ErrRootFSReadOnly is returned when a container // rootfs is marked readonly. ErrRootFSReadOnly = errors.New("container rootfs is marked read-only") ) + +func (daemon *Daemon) buildSandboxOptions(container *container.Container, n libnetwork.Network) ([]libnetwork.SandboxOption, error) { + var ( + sboxOptions []libnetwork.SandboxOption + err error + dns []string + dnsSearch []string + dnsOptions []string + bindings = make(nat.PortMap) + pbList []types.PortBinding + exposeList []types.TransportPort + ) + + defaultNetName := runconfig.DefaultDaemonNetworkMode().NetworkName() + sboxOptions = append(sboxOptions, libnetwork.OptionHostname(container.Config.Hostname), + libnetwork.OptionDomainname(container.Config.Domainname)) + + if container.HostConfig.NetworkMode.IsHost() { + sboxOptions = append(sboxOptions, libnetwork.OptionUseDefaultSandbox()) + sboxOptions = append(sboxOptions, libnetwork.OptionOriginHostsPath("/etc/hosts")) + sboxOptions = append(sboxOptions, libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf")) + } else if daemon.execDriver.SupportsHooks() { + // OptionUseExternalKey is mandatory for userns support. + // But optional for non-userns support + sboxOptions = append(sboxOptions, libnetwork.OptionUseExternalKey()) + } + + container.HostsPath, err = container.GetRootResourcePath("hosts") + if err != nil { + return nil, err + } + sboxOptions = append(sboxOptions, libnetwork.OptionHostsPath(container.HostsPath)) + + container.ResolvConfPath, err = container.GetRootResourcePath("resolv.conf") + if err != nil { + return nil, err + } + sboxOptions = append(sboxOptions, libnetwork.OptionResolvConfPath(container.ResolvConfPath)) + + if len(container.HostConfig.DNS) > 0 { + dns = container.HostConfig.DNS + } else if len(daemon.configStore.DNS) > 0 { + dns = daemon.configStore.DNS + } + + for _, d := range dns { + sboxOptions = append(sboxOptions, libnetwork.OptionDNS(d)) + } + + if len(container.HostConfig.DNSSearch) > 0 { + dnsSearch = container.HostConfig.DNSSearch + } else if len(daemon.configStore.DNSSearch) > 0 { + dnsSearch = daemon.configStore.DNSSearch + } + + for _, ds := range dnsSearch { + sboxOptions = append(sboxOptions, libnetwork.OptionDNSSearch(ds)) + } + + if len(container.HostConfig.DNSOptions) > 0 { + dnsOptions = container.HostConfig.DNSOptions + } else if len(daemon.configStore.DNSOptions) > 0 { + dnsOptions = daemon.configStore.DNSOptions + } + + for _, ds := range dnsOptions { + sboxOptions = append(sboxOptions, libnetwork.OptionDNSOptions(ds)) + } + + if container.NetworkSettings.SecondaryIPAddresses != nil { + name := container.Config.Hostname + if container.Config.Domainname != "" { + name = name + "." + container.Config.Domainname + } + + for _, a := range container.NetworkSettings.SecondaryIPAddresses { + sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(name, a.Addr)) + } + } + + for _, extraHost := range container.HostConfig.ExtraHosts { + // allow IPv6 addresses in extra hosts; only split on first ":" + parts := strings.SplitN(extraHost, ":", 2) + sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(parts[0], parts[1])) + } + + if container.HostConfig.PortBindings != nil { + for p, b := range container.HostConfig.PortBindings { + bindings[p] = []nat.PortBinding{} + for _, bb := range b { + bindings[p] = append(bindings[p], nat.PortBinding{ + HostIP: bb.HostIP, + HostPort: bb.HostPort, + }) + } + } + } + + portSpecs := container.Config.ExposedPorts + ports := make([]nat.Port, len(portSpecs)) + var i int + for p := range portSpecs { + ports[i] = p + i++ + } + nat.SortPortMap(ports, bindings) + for _, port := range ports { + expose := types.TransportPort{} + expose.Proto = types.ParseProtocol(port.Proto()) + expose.Port = uint16(port.Int()) + exposeList = append(exposeList, expose) + + pb := types.PortBinding{Port: expose.Port, Proto: expose.Proto} + binding := bindings[port] + for i := 0; i < len(binding); i++ { + pbCopy := pb.GetCopy() + newP, err := nat.NewPort(nat.SplitProtoPort(binding[i].HostPort)) + var portStart, portEnd int + if err == nil { + portStart, portEnd, err = newP.Range() + } + if err != nil { + return nil, fmt.Errorf("Error parsing HostPort value(%s):%v", binding[i].HostPort, err) + } + pbCopy.HostPort = uint16(portStart) + pbCopy.HostPortEnd = uint16(portEnd) + pbCopy.HostIP = net.ParseIP(binding[i].HostIP) + pbList = append(pbList, pbCopy) + } + + if container.HostConfig.PublishAllPorts && len(binding) == 0 { + pbList = append(pbList, pb) + } + } + + sboxOptions = append(sboxOptions, + libnetwork.OptionPortMapping(pbList), + libnetwork.OptionExposedPorts(exposeList)) + + // Link feature is supported only for the default bridge network. + // return if this call to build join options is not for default bridge network + if n.Name() != defaultNetName { + return sboxOptions, nil + } + + ep, _ := container.GetEndpointInNetwork(n) + if ep == nil { + return sboxOptions, nil + } + + var childEndpoints, parentEndpoints []string + + children := daemon.children(container) + for linkAlias, child := range children { + if !isLinkable(child) { + return nil, fmt.Errorf("Cannot link to %s, as it does not belong to the default network", child.Name) + } + _, alias := path.Split(linkAlias) + // allow access to the linked container via the alias, real name, and container hostname + aliasList := alias + " " + child.Config.Hostname + // only add the name if alias isn't equal to the name + if alias != child.Name[1:] { + aliasList = aliasList + " " + child.Name[1:] + } + sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(aliasList, child.NetworkSettings.Networks[defaultNetName].IPAddress)) + cEndpoint, _ := child.GetEndpointInNetwork(n) + if cEndpoint != nil && cEndpoint.ID() != "" { + childEndpoints = append(childEndpoints, cEndpoint.ID()) + } + } + + bridgeSettings := container.NetworkSettings.Networks[defaultNetName] + for alias, parent := range daemon.parents(container) { + if daemon.configStore.DisableBridge || !container.HostConfig.NetworkMode.IsPrivate() { + continue + } + + _, alias = path.Split(alias) + logrus.Debugf("Update /etc/hosts of %s for alias %s with ip %s", parent.ID, alias, bridgeSettings.IPAddress) + sboxOptions = append(sboxOptions, libnetwork.OptionParentUpdate( + parent.ID, + alias, + bridgeSettings.IPAddress, + )) + if ep.ID() != "" { + parentEndpoints = append(parentEndpoints, ep.ID()) + } + } + + linkOptions := options.Generic{ + netlabel.GenericData: options.Generic{ + "ParentEndpoints": parentEndpoints, + "ChildEndpoints": childEndpoints, + }, + } + + sboxOptions = append(sboxOptions, libnetwork.OptionGeneric(linkOptions)) + return sboxOptions, nil +} + +func (daemon *Daemon) updateNetworkSettings(container *container.Container, n libnetwork.Network) error { + if container.NetworkSettings == nil { + container.NetworkSettings = &network.Settings{Networks: make(map[string]*networktypes.EndpointSettings)} + } + + if !container.HostConfig.NetworkMode.IsHost() && containertypes.NetworkMode(n.Type()).IsHost() { + return runconfig.ErrConflictHostNetwork + } + + for s := range container.NetworkSettings.Networks { + sn, err := daemon.FindNetwork(s) + if err != nil { + continue + } + + if sn.Name() == n.Name() { + // Avoid duplicate config + return nil + } + if !containertypes.NetworkMode(sn.Type()).IsPrivate() || + !containertypes.NetworkMode(n.Type()).IsPrivate() { + return runconfig.ErrConflictSharedNetwork + } + if containertypes.NetworkMode(sn.Name()).IsNone() || + containertypes.NetworkMode(n.Name()).IsNone() { + return runconfig.ErrConflictNoNetwork + } + } + + if _, ok := container.NetworkSettings.Networks[n.Name()]; !ok { + container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings) + } + + return nil +} + +func (daemon *Daemon) updateEndpointNetworkSettings(container *container.Container, n libnetwork.Network, ep libnetwork.Endpoint) error { + if err := container.BuildEndpointInfo(n, ep); err != nil { + return err + } + + if container.HostConfig.NetworkMode == runconfig.DefaultDaemonNetworkMode() { + container.NetworkSettings.Bridge = daemon.configStore.bridgeConfig.Iface + } + + return nil +} + +// UpdateNetwork is used to update the container's network (e.g. when linked containers +// get removed/unlinked). +func (daemon *Daemon) updateNetwork(container *container.Container) error { + ctrl := daemon.netController + sid := container.NetworkSettings.SandboxID + + sb, err := ctrl.SandboxByID(sid) + if err != nil { + return fmt.Errorf("error locating sandbox id %s: %v", sid, err) + } + + // Find if container is connected to the default bridge network + var n libnetwork.Network + for name := range container.NetworkSettings.Networks { + sn, err := daemon.FindNetwork(name) + if err != nil { + continue + } + if sn.Name() == runconfig.DefaultDaemonNetworkMode().NetworkName() { + n = sn + break + } + } + + if n == nil { + // Not connected to the default bridge network; Nothing to do + return nil + } + + options, err := daemon.buildSandboxOptions(container, n) + if err != nil { + return fmt.Errorf("Update network failed: %v", err) + } + + if err := sb.Refresh(options...); err != nil { + return fmt.Errorf("Update network failed: Failure in refresh sandbox %s: %v", sid, err) + } + + return nil +} + +// updateContainerNetworkSettings update the network settings +func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error { + var ( + n libnetwork.Network + err error + ) + + // TODO Windows: Remove this once TP4 builds are not supported + // Windows TP4 build don't support libnetwork and in that case + // daemon.netController will be nil + if daemon.netController == nil { + return nil + } + + mode := container.HostConfig.NetworkMode + if container.Config.NetworkDisabled || mode.IsContainer() { + return nil + } + + networkName := mode.NetworkName() + if mode.IsDefault() { + networkName = daemon.netController.Config().Daemon.DefaultNetwork + } + if mode.IsUserDefined() { + n, err = daemon.FindNetwork(networkName) + if err != nil { + return err + } + networkName = n.Name() + } + if container.NetworkSettings == nil { + container.NetworkSettings = &network.Settings{} + } + if len(endpointsConfig) > 0 { + container.NetworkSettings.Networks = endpointsConfig + } + if container.NetworkSettings.Networks == nil { + container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings) + container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings) + } + if !mode.IsUserDefined() { + return nil + } + // Make sure to internally store the per network endpoint config by network name + if _, ok := container.NetworkSettings.Networks[networkName]; ok { + return nil + } + if nwConfig, ok := container.NetworkSettings.Networks[n.ID()]; ok { + container.NetworkSettings.Networks[networkName] = nwConfig + delete(container.NetworkSettings.Networks, n.ID()) + return nil + } + + return nil +} + +func (daemon *Daemon) allocateNetwork(container *container.Container) error { + controller := daemon.netController + + if daemon.netController == nil { + return nil + } + + // Cleanup any stale sandbox left over due to ungraceful daemon shutdown + if err := controller.SandboxDestroy(container.ID); err != nil { + logrus.Errorf("failed to cleanup up stale network sandbox for container %s", container.ID) + } + + updateSettings := false + if len(container.NetworkSettings.Networks) == 0 { + if container.Config.NetworkDisabled || container.HostConfig.NetworkMode.IsContainer() { + return nil + } + + err := daemon.updateContainerNetworkSettings(container, nil) + if err != nil { + return err + } + updateSettings = true + } + + for n, nConf := range container.NetworkSettings.Networks { + if err := daemon.connectToNetwork(container, n, nConf, updateSettings); err != nil { + return err + } + } + + return container.WriteHostConfig() +} + +func (daemon *Daemon) getNetworkSandbox(container *container.Container) libnetwork.Sandbox { + var sb libnetwork.Sandbox + daemon.netController.WalkSandboxes(func(s libnetwork.Sandbox) bool { + if s.ContainerID() == container.ID { + sb = s + return true + } + return false + }) + return sb +} + +// hasUserDefinedIPAddress returns whether the passed endpoint configuration contains IP address configuration +func hasUserDefinedIPAddress(epConfig *networktypes.EndpointSettings) bool { + return epConfig != nil && epConfig.IPAMConfig != nil && (len(epConfig.IPAMConfig.IPv4Address) > 0 || len(epConfig.IPAMConfig.IPv6Address) > 0) +} + +// User specified ip address is acceptable only for networks with user specified subnets. +func validateNetworkingConfig(n libnetwork.Network, epConfig *networktypes.EndpointSettings) error { + if n == nil || epConfig == nil { + return nil + } + if !hasUserDefinedIPAddress(epConfig) { + return nil + } + _, _, nwIPv4Configs, nwIPv6Configs := n.Info().IpamConfig() + for _, s := range []struct { + ipConfigured bool + subnetConfigs []*libnetwork.IpamConf + }{ + { + ipConfigured: len(epConfig.IPAMConfig.IPv4Address) > 0, + subnetConfigs: nwIPv4Configs, + }, + { + ipConfigured: len(epConfig.IPAMConfig.IPv6Address) > 0, + subnetConfigs: nwIPv6Configs, + }, + } { + if s.ipConfigured { + foundSubnet := false + for _, cfg := range s.subnetConfigs { + if len(cfg.PreferredPool) > 0 { + foundSubnet = true + break + } + } + if !foundSubnet { + return runconfig.ErrUnsupportedNetworkNoSubnetAndIP + } + } + } + + return nil +} + +// cleanOperationalData resets the operational data from the passed endpoint settings +func cleanOperationalData(es *networktypes.EndpointSettings) { + es.EndpointID = "" + es.Gateway = "" + es.IPAddress = "" + es.IPPrefixLen = 0 + es.IPv6Gateway = "" + es.GlobalIPv6Address = "" + es.GlobalIPv6PrefixLen = 0 + es.MacAddress = "" +} + +func (daemon *Daemon) updateNetworkConfig(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (libnetwork.Network, error) { + if container.HostConfig.NetworkMode.IsContainer() { + return nil, runconfig.ErrConflictSharedNetwork + } + + if containertypes.NetworkMode(idOrName).IsBridge() && + daemon.configStore.DisableBridge { + container.Config.NetworkDisabled = true + return nil, nil + } + + if !containertypes.NetworkMode(idOrName).IsUserDefined() { + if hasUserDefinedIPAddress(endpointConfig) { + return nil, runconfig.ErrUnsupportedNetworkAndIP + } + if endpointConfig != nil && len(endpointConfig.Aliases) > 0 { + return nil, runconfig.ErrUnsupportedNetworkAndAlias + } + } + + n, err := daemon.FindNetwork(idOrName) + if err != nil { + return nil, err + } + + if err := validateNetworkingConfig(n, endpointConfig); err != nil { + return nil, err + } + + if updateSettings { + if err := daemon.updateNetworkSettings(container, n); err != nil { + return nil, err + } + } + return n, nil +} + +func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) { + // TODO Windows: Remove this once TP4 builds are not supported + // Windows TP4 build don't support libnetwork and in that case + // daemon.netController will be nil + if daemon.netController == nil { + return nil + } + + n, err := daemon.updateNetworkConfig(container, idOrName, endpointConfig, updateSettings) + if err != nil { + return err + } + if n == nil { + return nil + } + + controller := daemon.netController + + sb := daemon.getNetworkSandbox(container) + createOptions, err := container.BuildCreateEndpointOptions(n, endpointConfig, sb) + if err != nil { + return err + } + + endpointName := strings.TrimPrefix(container.Name, "/") + ep, err := n.CreateEndpoint(endpointName, createOptions...) + if err != nil { + return err + } + defer func() { + if err != nil { + if e := ep.Delete(false); e != nil { + logrus.Warnf("Could not rollback container connection to network %s", idOrName) + } + } + }() + + if endpointConfig != nil { + container.NetworkSettings.Networks[n.Name()] = endpointConfig + } + + if err := daemon.updateEndpointNetworkSettings(container, n, ep); err != nil { + return err + } + + if sb == nil { + options, err := daemon.buildSandboxOptions(container, n) + if err != nil { + return err + } + sb, err = controller.NewSandbox(container.ID, options...) + if err != nil { + return err + } + + container.UpdateSandboxNetworkSettings(sb) + } + + joinOptions, err := container.BuildJoinOptions(n) + if err != nil { + return err + } + + if err := ep.Join(sb, joinOptions...); err != nil { + return err + } + + if err := container.UpdateJoinInfo(n, ep); err != nil { + return fmt.Errorf("Updating join info failed: %v", err) + } + + daemon.LogNetworkEventWithAttributes(n, "connect", map[string]string{"container": container.ID}) + return nil +} + +// ForceEndpointDelete deletes an endpoing from a network forcefully +func (daemon *Daemon) ForceEndpointDelete(name string, n libnetwork.Network) error { + ep, err := n.EndpointByName(name) + if err != nil { + return err + } + return ep.Delete(true) +} + +func disconnectFromNetwork(container *container.Container, n libnetwork.Network, force bool) error { + var ( + ep libnetwork.Endpoint + sbox libnetwork.Sandbox + ) + + s := func(current libnetwork.Endpoint) bool { + epInfo := current.Info() + if epInfo == nil { + return false + } + if sb := epInfo.Sandbox(); sb != nil { + if sb.ContainerID() == container.ID { + ep = current + sbox = sb + return true + } + } + return false + } + n.WalkEndpoints(s) + + if ep == nil && force { + epName := strings.TrimPrefix(container.Name, "/") + ep, err := n.EndpointByName(epName) + if err != nil { + return err + } + return ep.Delete(force) + } + + if ep == nil { + return fmt.Errorf("container %s is not connected to the network", container.ID) + } + + if err := ep.Leave(sbox); err != nil { + return fmt.Errorf("container %s failed to leave network %s: %v", container.ID, n.Name(), err) + } + + if err := ep.Delete(false); err != nil { + return fmt.Errorf("endpoint delete failed for container %s on network %s: %v", container.ID, n.Name(), err) + } + + delete(container.NetworkSettings.Networks, n.Name()) + return nil +} + +func (daemon *Daemon) initializeNetworking(container *container.Container) error { + var err error + + // TODO Windows: Remove this once TP4 builds are not supported + // Windows TP4 build don't support libnetwork and in that case + // daemon.netController will be nil + if daemon.netController == nil { + return nil + } + + if container.HostConfig.NetworkMode.IsContainer() { + // we need to get the hosts files from the container to join + nc, err := daemon.getNetworkedContainer(container.ID, container.HostConfig.NetworkMode.ConnectedContainer()) + if err != nil { + return err + } + container.HostnamePath = nc.HostnamePath + container.HostsPath = nc.HostsPath + container.ResolvConfPath = nc.ResolvConfPath + container.Config.Hostname = nc.Config.Hostname + container.Config.Domainname = nc.Config.Domainname + return nil + } + + if container.HostConfig.NetworkMode.IsHost() { + container.Config.Hostname, err = os.Hostname() + if err != nil { + return err + } + + parts := strings.SplitN(container.Config.Hostname, ".", 2) + if len(parts) > 1 { + container.Config.Hostname = parts[0] + container.Config.Domainname = parts[1] + } + + } + + if err := daemon.allocateNetwork(container); err != nil { + return err + } + + return container.BuildHostnameFile() +} + +func (daemon *Daemon) getNetworkedContainer(containerID, connectedContainerID string) (*container.Container, error) { + nc, err := daemon.GetContainer(connectedContainerID) + if err != nil { + return nil, err + } + if containerID == nc.ID { + return nil, fmt.Errorf("cannot join own network") + } + if !nc.IsRunning() { + err := fmt.Errorf("cannot join network of a non running container: %s", connectedContainerID) + return nil, derr.NewRequestConflictError(err) + } + if nc.IsRestarting() { + return nil, errContainerIsRestarting(connectedContainerID) + } + return nc, nil +} + +func (daemon *Daemon) releaseNetwork(container *container.Container) { + if container.HostConfig.NetworkMode.IsContainer() || container.Config.NetworkDisabled { + return + } + + sid := container.NetworkSettings.SandboxID + settings := container.NetworkSettings.Networks + container.NetworkSettings.Ports = nil + + if sid == "" || len(settings) == 0 { + return + } + + var networks []libnetwork.Network + for n, epSettings := range settings { + if nw, err := daemon.FindNetwork(n); err == nil { + networks = append(networks, nw) + } + cleanOperationalData(epSettings) + } + + sb, err := daemon.netController.SandboxByID(sid) + if err != nil { + logrus.Errorf("error locating sandbox id %s: %v", sid, err) + return + } + + if err := sb.Delete(); err != nil { + logrus.Errorf("Error deleting sandbox id %s for container %s: %v", sid, container.ID, err) + } + + attributes := map[string]string{ + "container": container.ID, + } + for _, nw := range networks { + daemon.LogNetworkEventWithAttributes(nw, "disconnect", attributes) + } +} diff --git a/daemon/container_operations_unix.go b/daemon/container_operations_unix.go index e93f2dad4c11b..16679180b9fee 100644 --- a/daemon/container_operations_unix.go +++ b/daemon/container_operations_unix.go @@ -4,9 +4,7 @@ package daemon import ( "fmt" - "net" "os" - "path" "path/filepath" "strconv" "strings" @@ -17,8 +15,6 @@ import ( "github.com/docker/docker/container" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/links" - "github.com/docker/docker/daemon/network" - "github.com/docker/docker/errors" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/mount" @@ -26,12 +22,8 @@ import ( "github.com/docker/docker/runconfig" containertypes "github.com/docker/engine-api/types/container" networktypes "github.com/docker/engine-api/types/network" - "github.com/docker/go-connections/nat" "github.com/docker/go-units" "github.com/docker/libnetwork" - "github.com/docker/libnetwork/netlabel" - "github.com/docker/libnetwork/options" - "github.com/docker/libnetwork/types" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/devices" "github.com/opencontainers/runc/libcontainer/label" @@ -41,7 +33,7 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s var env []string children := daemon.children(container) - bridgeSettings := container.NetworkSettings.Networks["bridge"] + bridgeSettings := container.NetworkSettings.Networks[runconfig.DefaultDaemonNetworkMode().NetworkName()] if bridgeSettings == nil { return nil, nil } @@ -51,7 +43,7 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, linkAlias) } - childBridgeSettings := child.NetworkSettings.Networks["bridge"] + childBridgeSettings := child.NetworkSettings.Networks[runconfig.DefaultDaemonNetworkMode().NetworkName()] if childBridgeSettings == nil { return nil, fmt.Errorf("container %s not attached to default bridge network", child.ID) } @@ -316,477 +308,6 @@ func (daemon *Daemon) getSize(container *container.Container) (int64, int64) { return sizeRw, sizeRootfs } -func (daemon *Daemon) buildSandboxOptions(container *container.Container, n libnetwork.Network) ([]libnetwork.SandboxOption, error) { - var ( - sboxOptions []libnetwork.SandboxOption - err error - dns []string - dnsSearch []string - dnsOptions []string - bindings = make(nat.PortMap) - pbList []types.PortBinding - exposeList []types.TransportPort - ) - - sboxOptions = append(sboxOptions, libnetwork.OptionHostname(container.Config.Hostname), - libnetwork.OptionDomainname(container.Config.Domainname)) - - if container.HostConfig.NetworkMode.IsHost() { - sboxOptions = append(sboxOptions, libnetwork.OptionUseDefaultSandbox()) - sboxOptions = append(sboxOptions, libnetwork.OptionOriginHostsPath("/etc/hosts")) - sboxOptions = append(sboxOptions, libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf")) - } else if daemon.execDriver.SupportsHooks() { - // OptionUseExternalKey is mandatory for userns support. - // But optional for non-userns support - sboxOptions = append(sboxOptions, libnetwork.OptionUseExternalKey()) - } - - container.HostsPath, err = container.GetRootResourcePath("hosts") - if err != nil { - return nil, err - } - sboxOptions = append(sboxOptions, libnetwork.OptionHostsPath(container.HostsPath)) - - container.ResolvConfPath, err = container.GetRootResourcePath("resolv.conf") - if err != nil { - return nil, err - } - sboxOptions = append(sboxOptions, libnetwork.OptionResolvConfPath(container.ResolvConfPath)) - - if len(container.HostConfig.DNS) > 0 { - dns = container.HostConfig.DNS - } else if len(daemon.configStore.DNS) > 0 { - dns = daemon.configStore.DNS - } - - for _, d := range dns { - sboxOptions = append(sboxOptions, libnetwork.OptionDNS(d)) - } - - if len(container.HostConfig.DNSSearch) > 0 { - dnsSearch = container.HostConfig.DNSSearch - } else if len(daemon.configStore.DNSSearch) > 0 { - dnsSearch = daemon.configStore.DNSSearch - } - - for _, ds := range dnsSearch { - sboxOptions = append(sboxOptions, libnetwork.OptionDNSSearch(ds)) - } - - if len(container.HostConfig.DNSOptions) > 0 { - dnsOptions = container.HostConfig.DNSOptions - } else if len(daemon.configStore.DNSOptions) > 0 { - dnsOptions = daemon.configStore.DNSOptions - } - - for _, ds := range dnsOptions { - sboxOptions = append(sboxOptions, libnetwork.OptionDNSOptions(ds)) - } - - if container.NetworkSettings.SecondaryIPAddresses != nil { - name := container.Config.Hostname - if container.Config.Domainname != "" { - name = name + "." + container.Config.Domainname - } - - for _, a := range container.NetworkSettings.SecondaryIPAddresses { - sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(name, a.Addr)) - } - } - - for _, extraHost := range container.HostConfig.ExtraHosts { - // allow IPv6 addresses in extra hosts; only split on first ":" - parts := strings.SplitN(extraHost, ":", 2) - sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(parts[0], parts[1])) - } - - if container.HostConfig.PortBindings != nil { - for p, b := range container.HostConfig.PortBindings { - bindings[p] = []nat.PortBinding{} - for _, bb := range b { - bindings[p] = append(bindings[p], nat.PortBinding{ - HostIP: bb.HostIP, - HostPort: bb.HostPort, - }) - } - } - } - - portSpecs := container.Config.ExposedPorts - ports := make([]nat.Port, len(portSpecs)) - var i int - for p := range portSpecs { - ports[i] = p - i++ - } - nat.SortPortMap(ports, bindings) - for _, port := range ports { - expose := types.TransportPort{} - expose.Proto = types.ParseProtocol(port.Proto()) - expose.Port = uint16(port.Int()) - exposeList = append(exposeList, expose) - - pb := types.PortBinding{Port: expose.Port, Proto: expose.Proto} - binding := bindings[port] - for i := 0; i < len(binding); i++ { - pbCopy := pb.GetCopy() - newP, err := nat.NewPort(nat.SplitProtoPort(binding[i].HostPort)) - var portStart, portEnd int - if err == nil { - portStart, portEnd, err = newP.Range() - } - if err != nil { - return nil, fmt.Errorf("Error parsing HostPort value(%s):%v", binding[i].HostPort, err) - } - pbCopy.HostPort = uint16(portStart) - pbCopy.HostPortEnd = uint16(portEnd) - pbCopy.HostIP = net.ParseIP(binding[i].HostIP) - pbList = append(pbList, pbCopy) - } - - if container.HostConfig.PublishAllPorts && len(binding) == 0 { - pbList = append(pbList, pb) - } - } - - sboxOptions = append(sboxOptions, - libnetwork.OptionPortMapping(pbList), - libnetwork.OptionExposedPorts(exposeList)) - - // Link feature is supported only for the default bridge network. - // return if this call to build join options is not for default bridge network - if n.Name() != "bridge" { - return sboxOptions, nil - } - - ep, _ := container.GetEndpointInNetwork(n) - if ep == nil { - return sboxOptions, nil - } - - var childEndpoints, parentEndpoints []string - - children := daemon.children(container) - for linkAlias, child := range children { - if !isLinkable(child) { - return nil, fmt.Errorf("Cannot link to %s, as it does not belong to the default network", child.Name) - } - _, alias := path.Split(linkAlias) - // allow access to the linked container via the alias, real name, and container hostname - aliasList := alias + " " + child.Config.Hostname - // only add the name if alias isn't equal to the name - if alias != child.Name[1:] { - aliasList = aliasList + " " + child.Name[1:] - } - sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(aliasList, child.NetworkSettings.Networks["bridge"].IPAddress)) - cEndpoint, _ := child.GetEndpointInNetwork(n) - if cEndpoint != nil && cEndpoint.ID() != "" { - childEndpoints = append(childEndpoints, cEndpoint.ID()) - } - } - - bridgeSettings := container.NetworkSettings.Networks["bridge"] - for alias, parent := range daemon.parents(container) { - if daemon.configStore.DisableBridge || !container.HostConfig.NetworkMode.IsPrivate() { - continue - } - - _, alias = path.Split(alias) - logrus.Debugf("Update /etc/hosts of %s for alias %s with ip %s", parent.ID, alias, bridgeSettings.IPAddress) - sboxOptions = append(sboxOptions, libnetwork.OptionParentUpdate( - parent.ID, - alias, - bridgeSettings.IPAddress, - )) - if ep.ID() != "" { - parentEndpoints = append(parentEndpoints, ep.ID()) - } - } - - linkOptions := options.Generic{ - netlabel.GenericData: options.Generic{ - "ParentEndpoints": parentEndpoints, - "ChildEndpoints": childEndpoints, - }, - } - - sboxOptions = append(sboxOptions, libnetwork.OptionGeneric(linkOptions)) - return sboxOptions, nil -} - -func (daemon *Daemon) updateNetworkSettings(container *container.Container, n libnetwork.Network) error { - if container.NetworkSettings == nil { - container.NetworkSettings = &network.Settings{Networks: make(map[string]*networktypes.EndpointSettings)} - } - - if !container.HostConfig.NetworkMode.IsHost() && containertypes.NetworkMode(n.Type()).IsHost() { - return runconfig.ErrConflictHostNetwork - } - - for s := range container.NetworkSettings.Networks { - sn, err := daemon.FindNetwork(s) - if err != nil { - continue - } - - if sn.Name() == n.Name() { - // Avoid duplicate config - return nil - } - if !containertypes.NetworkMode(sn.Type()).IsPrivate() || - !containertypes.NetworkMode(n.Type()).IsPrivate() { - return runconfig.ErrConflictSharedNetwork - } - if containertypes.NetworkMode(sn.Name()).IsNone() || - containertypes.NetworkMode(n.Name()).IsNone() { - return runconfig.ErrConflictNoNetwork - } - } - - if _, ok := container.NetworkSettings.Networks[n.Name()]; !ok { - container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings) - } - - return nil -} - -func (daemon *Daemon) updateEndpointNetworkSettings(container *container.Container, n libnetwork.Network, ep libnetwork.Endpoint) error { - if err := container.BuildEndpointInfo(n, ep); err != nil { - return err - } - - if container.HostConfig.NetworkMode == containertypes.NetworkMode("bridge") { - container.NetworkSettings.Bridge = daemon.configStore.bridgeConfig.Iface - } - - return nil -} - -// UpdateNetwork is used to update the container's network (e.g. when linked containers -// get removed/unlinked). -func (daemon *Daemon) updateNetwork(container *container.Container) error { - ctrl := daemon.netController - sid := container.NetworkSettings.SandboxID - - sb, err := ctrl.SandboxByID(sid) - if err != nil { - return fmt.Errorf("error locating sandbox id %s: %v", sid, err) - } - - // Find if container is connected to the default bridge network - var n libnetwork.Network - for name := range container.NetworkSettings.Networks { - sn, err := daemon.FindNetwork(name) - if err != nil { - continue - } - if sn.Name() == "bridge" { - n = sn - break - } - } - - if n == nil { - // Not connected to the default bridge network; Nothing to do - return nil - } - - options, err := daemon.buildSandboxOptions(container, n) - if err != nil { - return fmt.Errorf("Update network failed: %v", err) - } - - if err := sb.Refresh(options...); err != nil { - return fmt.Errorf("Update network failed: Failure in refresh sandbox %s: %v", sid, err) - } - - return nil -} - -// updateContainerNetworkSettings update the network settings -func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error { - var ( - n libnetwork.Network - err error - ) - - mode := container.HostConfig.NetworkMode - if container.Config.NetworkDisabled || mode.IsContainer() { - return nil - } - - networkName := mode.NetworkName() - if mode.IsDefault() { - networkName = daemon.netController.Config().Daemon.DefaultNetwork - } - if mode.IsUserDefined() { - n, err = daemon.FindNetwork(networkName) - if err != nil { - return err - } - networkName = n.Name() - } - if container.NetworkSettings == nil { - container.NetworkSettings = &network.Settings{} - } - if len(endpointsConfig) > 0 { - container.NetworkSettings.Networks = endpointsConfig - } - if container.NetworkSettings.Networks == nil { - container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings) - container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings) - } - if !mode.IsUserDefined() { - return nil - } - // Make sure to internally store the per network endpoint config by network name - if _, ok := container.NetworkSettings.Networks[networkName]; ok { - return nil - } - if nwConfig, ok := container.NetworkSettings.Networks[n.ID()]; ok { - container.NetworkSettings.Networks[networkName] = nwConfig - delete(container.NetworkSettings.Networks, n.ID()) - return nil - } - - return nil -} - -func (daemon *Daemon) allocateNetwork(container *container.Container) error { - controller := daemon.netController - - // Cleanup any stale sandbox left over due to ungraceful daemon shutdown - if err := controller.SandboxDestroy(container.ID); err != nil { - logrus.Errorf("failed to cleanup up stale network sandbox for container %s", container.ID) - } - - updateSettings := false - if len(container.NetworkSettings.Networks) == 0 { - if container.Config.NetworkDisabled || container.HostConfig.NetworkMode.IsContainer() { - return nil - } - - err := daemon.updateContainerNetworkSettings(container, nil) - if err != nil { - return err - } - updateSettings = true - } - - for n, nConf := range container.NetworkSettings.Networks { - if err := daemon.connectToNetwork(container, n, nConf, updateSettings); err != nil { - return err - } - } - - return container.WriteHostConfig() -} - -func (daemon *Daemon) getNetworkSandbox(container *container.Container) libnetwork.Sandbox { - var sb libnetwork.Sandbox - daemon.netController.WalkSandboxes(func(s libnetwork.Sandbox) bool { - if s.ContainerID() == container.ID { - sb = s - return true - } - return false - }) - return sb -} - -// hasUserDefinedIPAddress returns whether the passed endpoint configuration contains IP address configuration -func hasUserDefinedIPAddress(epConfig *networktypes.EndpointSettings) bool { - return epConfig != nil && epConfig.IPAMConfig != nil && (len(epConfig.IPAMConfig.IPv4Address) > 0 || len(epConfig.IPAMConfig.IPv6Address) > 0) -} - -// User specified ip address is acceptable only for networks with user specified subnets. -func validateNetworkingConfig(n libnetwork.Network, epConfig *networktypes.EndpointSettings) error { - if n == nil || epConfig == nil { - return nil - } - if !hasUserDefinedIPAddress(epConfig) { - return nil - } - _, _, nwIPv4Configs, nwIPv6Configs := n.Info().IpamConfig() - for _, s := range []struct { - ipConfigured bool - subnetConfigs []*libnetwork.IpamConf - }{ - { - ipConfigured: len(epConfig.IPAMConfig.IPv4Address) > 0, - subnetConfigs: nwIPv4Configs, - }, - { - ipConfigured: len(epConfig.IPAMConfig.IPv6Address) > 0, - subnetConfigs: nwIPv6Configs, - }, - } { - if s.ipConfigured { - foundSubnet := false - for _, cfg := range s.subnetConfigs { - if len(cfg.PreferredPool) > 0 { - foundSubnet = true - break - } - } - if !foundSubnet { - return runconfig.ErrUnsupportedNetworkNoSubnetAndIP - } - } - } - - return nil -} - -// cleanOperationalData resets the operational data from the passed endpoint settings -func cleanOperationalData(es *networktypes.EndpointSettings) { - es.EndpointID = "" - es.Gateway = "" - es.IPAddress = "" - es.IPPrefixLen = 0 - es.IPv6Gateway = "" - es.GlobalIPv6Address = "" - es.GlobalIPv6PrefixLen = 0 - es.MacAddress = "" -} - -func (daemon *Daemon) updateNetworkConfig(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (libnetwork.Network, error) { - if container.HostConfig.NetworkMode.IsContainer() { - return nil, runconfig.ErrConflictSharedNetwork - } - - if containertypes.NetworkMode(idOrName).IsBridge() && - daemon.configStore.DisableBridge { - container.Config.NetworkDisabled = true - return nil, nil - } - - if !containertypes.NetworkMode(idOrName).IsUserDefined() { - if hasUserDefinedIPAddress(endpointConfig) { - return nil, runconfig.ErrUnsupportedNetworkAndIP - } - if endpointConfig != nil && len(endpointConfig.Aliases) > 0 { - return nil, runconfig.ErrUnsupportedNetworkAndAlias - } - } - - n, err := daemon.FindNetwork(idOrName) - if err != nil { - return nil, err - } - - if err := validateNetworkingConfig(n, endpointConfig); err != nil { - return nil, err - } - - if updateSettings { - if err := daemon.updateNetworkSettings(container, n); err != nil { - return nil, err - } - } - return n, nil -} - // ConnectToNetwork connects a container to a network func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error { if !container.Running { @@ -810,83 +331,6 @@ func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName return nil } -func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) { - n, err := daemon.updateNetworkConfig(container, idOrName, endpointConfig, updateSettings) - if err != nil { - return err - } - if n == nil { - return nil - } - - controller := daemon.netController - - sb := daemon.getNetworkSandbox(container) - createOptions, err := container.BuildCreateEndpointOptions(n, endpointConfig, sb) - if err != nil { - return err - } - - endpointName := strings.TrimPrefix(container.Name, "/") - ep, err := n.CreateEndpoint(endpointName, createOptions...) - if err != nil { - return err - } - defer func() { - if err != nil { - if e := ep.Delete(false); e != nil { - logrus.Warnf("Could not rollback container connection to network %s", idOrName) - } - } - }() - - if endpointConfig != nil { - container.NetworkSettings.Networks[n.Name()] = endpointConfig - } - - if err := daemon.updateEndpointNetworkSettings(container, n, ep); err != nil { - return err - } - - if sb == nil { - options, err := daemon.buildSandboxOptions(container, n) - if err != nil { - return err - } - sb, err = controller.NewSandbox(container.ID, options...) - if err != nil { - return err - } - - container.UpdateSandboxNetworkSettings(sb) - } - - joinOptions, err := container.BuildJoinOptions(n) - if err != nil { - return err - } - - if err := ep.Join(sb, joinOptions...); err != nil { - return err - } - - if err := container.UpdateJoinInfo(n, ep); err != nil { - return fmt.Errorf("Updating join info failed: %v", err) - } - - daemon.LogNetworkEventWithAttributes(n, "connect", map[string]string{"container": container.ID}) - return nil -} - -// ForceEndpointDelete deletes an endpoing from a network forcefully -func (daemon *Daemon) ForceEndpointDelete(name string, n libnetwork.Network) error { - ep, err := n.EndpointByName(name) - if err != nil { - return err - } - return ep.Delete(true) -} - // DisconnectFromNetwork disconnects container from network n. func (daemon *Daemon) DisconnectFromNetwork(container *container.Container, n libnetwork.Network, force bool) error { if container.HostConfig.NetworkMode.IsHost() && containertypes.NetworkMode(n.Type()).IsHost() { @@ -918,91 +362,6 @@ func (daemon *Daemon) DisconnectFromNetwork(container *container.Container, n li return nil } -func disconnectFromNetwork(container *container.Container, n libnetwork.Network, force bool) error { - var ( - ep libnetwork.Endpoint - sbox libnetwork.Sandbox - ) - - s := func(current libnetwork.Endpoint) bool { - epInfo := current.Info() - if epInfo == nil { - return false - } - if sb := epInfo.Sandbox(); sb != nil { - if sb.ContainerID() == container.ID { - ep = current - sbox = sb - return true - } - } - return false - } - n.WalkEndpoints(s) - - if ep == nil && force { - epName := strings.TrimPrefix(container.Name, "/") - ep, err := n.EndpointByName(epName) - if err != nil { - return err - } - return ep.Delete(force) - } - - if ep == nil { - return fmt.Errorf("container %s is not connected to the network", container.ID) - } - - if err := ep.Leave(sbox); err != nil { - return fmt.Errorf("container %s failed to leave network %s: %v", container.ID, n.Name(), err) - } - - if err := ep.Delete(false); err != nil { - return fmt.Errorf("endpoint delete failed for container %s on network %s: %v", container.ID, n.Name(), err) - } - - delete(container.NetworkSettings.Networks, n.Name()) - return nil -} - -func (daemon *Daemon) initializeNetworking(container *container.Container) error { - var err error - - if container.HostConfig.NetworkMode.IsContainer() { - // we need to get the hosts files from the container to join - nc, err := daemon.getNetworkedContainer(container.ID, container.HostConfig.NetworkMode.ConnectedContainer()) - if err != nil { - return err - } - container.HostnamePath = nc.HostnamePath - container.HostsPath = nc.HostsPath - container.ResolvConfPath = nc.ResolvConfPath - container.Config.Hostname = nc.Config.Hostname - container.Config.Domainname = nc.Config.Domainname - return nil - } - - if container.HostConfig.NetworkMode.IsHost() { - container.Config.Hostname, err = os.Hostname() - if err != nil { - return err - } - - parts := strings.SplitN(container.Config.Hostname, ".", 2) - if len(parts) > 1 { - container.Config.Hostname = parts[0] - container.Config.Domainname = parts[1] - } - - } - - if err := daemon.allocateNetwork(container); err != nil { - return err - } - - return container.BuildHostnameFile() -} - // called from the libcontainer pre-start hook to set the network // namespace configuration linkage to the libnetwork "sandbox" entity func (daemon *Daemon) setNetworkNamespaceKey(containerID string, pid int) error { @@ -1032,63 +391,6 @@ func (daemon *Daemon) getIpcContainer(container *container.Container) (*containe return c, nil } -func (daemon *Daemon) getNetworkedContainer(containerID, connectedContainerID string) (*container.Container, error) { - nc, err := daemon.GetContainer(connectedContainerID) - if err != nil { - return nil, err - } - if containerID == nc.ID { - return nil, fmt.Errorf("cannot join own network") - } - if !nc.IsRunning() { - err := fmt.Errorf("cannot join network of a non running container: %s", connectedContainerID) - return nil, errors.NewRequestConflictError(err) - } - if nc.IsRestarting() { - return nil, errContainerIsRestarting(connectedContainerID) - } - return nc, nil -} - -func (daemon *Daemon) releaseNetwork(container *container.Container) { - if container.HostConfig.NetworkMode.IsContainer() || container.Config.NetworkDisabled { - return - } - - sid := container.NetworkSettings.SandboxID - settings := container.NetworkSettings.Networks - container.NetworkSettings.Ports = nil - - if sid == "" || len(settings) == 0 { - return - } - - var networks []libnetwork.Network - for n, epSettings := range settings { - if nw, err := daemon.FindNetwork(n); err == nil { - networks = append(networks, nw) - } - cleanOperationalData(epSettings) - } - - sb, err := daemon.netController.SandboxByID(sid) - if err != nil { - logrus.Errorf("error locating sandbox id %s: %v", sid, err) - return - } - - if err := sb.Delete(); err != nil { - logrus.Errorf("Error deleting sandbox id %s for container %s: %v", sid, container.ID, err) - } - - attributes := map[string]string{ - "container": container.ID, - } - for _, nw := range networks { - daemon.LogNetworkEventWithAttributes(nw, "disconnect", attributes) - } -} - func (daemon *Daemon) setupIpcDirs(c *container.Container) error { rootUID, rootGID := daemon.GetRemappedUIDGID() if !c.HasMountFor("/dev/shm") { @@ -1242,7 +544,7 @@ func detachMounted(path string) error { func isLinkable(child *container.Container) bool { // A container is linkable only if it belongs to the default network - _, ok := child.NetworkSettings.Networks["bridge"] + _, ok := child.NetworkSettings.Networks[runconfig.DefaultDaemonNetworkMode().NetworkName()] return ok } diff --git a/daemon/container_operations_windows.go b/daemon/container_operations_windows.go index 02173d09f1ef9..56e95abf253b4 100644 --- a/daemon/container_operations_windows.go +++ b/daemon/container_operations_windows.go @@ -6,11 +6,12 @@ import ( "fmt" "strings" + networktypes "github.com/docker/engine-api/types/network" + "github.com/docker/docker/container" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/execdriver/windows" "github.com/docker/docker/layer" - networktypes "github.com/docker/engine-api/types/network" "github.com/docker/libnetwork" ) @@ -18,28 +19,14 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s return nil, nil } -// updateContainerNetworkSettings update the network settings -func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error { - return nil -} - -func (daemon *Daemon) initializeNetworking(container *container.Container) error { - return nil -} - -// ConnectToNetwork connects a container to the network +// ConnectToNetwork connects a container to a network func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error { - return nil + return fmt.Errorf("Windows does not support connecting a running container to a network") } -// ForceEndpointDelete deletes an endpoing from a network forcefully -func (daemon *Daemon) ForceEndpointDelete(name string, n libnetwork.Network) error { - return nil -} - -// DisconnectFromNetwork disconnects a container from the network. +// DisconnectFromNetwork disconnects container from a network. func (daemon *Daemon) DisconnectFromNetwork(container *container.Container, n libnetwork.Network, force bool) error { - return nil + return fmt.Errorf("Windows does not support disconnecting a running container from a network") } func (daemon *Daemon) populateCommand(c *container.Container, env []string) error { @@ -47,24 +34,51 @@ func (daemon *Daemon) populateCommand(c *container.Container, env []string) erro Interface: nil, } - parts := strings.SplitN(string(c.HostConfig.NetworkMode), ":", 2) - switch parts[0] { - case "none": - case "default", "": // empty string to support existing containers - if !c.Config.NetworkDisabled { - en.Interface = &execdriver.NetworkInterface{ - MacAddress: c.Config.MacAddress, - Bridge: daemon.configStore.bridgeConfig.VirtualSwitchName, - PortBindings: c.HostConfig.PortBindings, - - // TODO Windows. Include IPAddress. There already is a - // property IPAddress on execDrive.CommonNetworkInterface, - // but there is no CLI option in docker to pass through - // an IPAddress on docker run. + var epList []string + + // Connect all the libnetwork allocated networks to the container + if c.NetworkSettings != nil { + for n := range c.NetworkSettings.Networks { + sn, err := daemon.FindNetwork(n) + if err != nil { + continue + } + + ep, err := c.GetEndpointInNetwork(sn) + if err != nil { + continue + } + + data, err := ep.DriverInfo() + if err != nil { + continue + } + if data["hnsid"] != nil { + epList = append(epList, data["hnsid"].(string)) } } - default: - return fmt.Errorf("invalid network mode: %s", c.HostConfig.NetworkMode) + } + + if daemon.netController == nil { + parts := strings.SplitN(string(c.HostConfig.NetworkMode), ":", 2) + switch parts[0] { + case "none": + case "default", "": // empty string to support existing containers + if !c.Config.NetworkDisabled { + en.Interface = &execdriver.NetworkInterface{ + MacAddress: c.Config.MacAddress, + Bridge: daemon.configStore.bridgeConfig.Iface, + PortBindings: c.HostConfig.PortBindings, + + // TODO Windows. Include IPAddress. There already is a + // property IPAddress on execDrive.CommonNetworkInterface, + // but there is no CLI option in docker to pass through + // an IPAddress on docker run. + } + } + default: + return fmt.Errorf("invalid network mode: %s", c.HostConfig.NetworkMode) + } } // TODO Windows. More resource controls to be implemented later. @@ -138,6 +152,7 @@ func (daemon *Daemon) populateCommand(c *container.Container, env []string) erro Isolation: string(c.HostConfig.Isolation), ArgsEscaped: c.Config.ArgsEscaped, HvPartition: hvPartition, + EpList: epList, } return nil @@ -154,18 +169,6 @@ func (daemon *Daemon) setNetworkNamespaceKey(containerID string, pid int) error return nil } -// allocateNetwork is a no-op on Windows. -func (daemon *Daemon) allocateNetwork(container *container.Container) error { - return nil -} - -func (daemon *Daemon) updateNetwork(container *container.Container) error { - return nil -} - -func (daemon *Daemon) releaseNetwork(container *container.Container) { -} - func (daemon *Daemon) setupIpcDirs(container *container.Container) error { return nil } @@ -187,3 +190,7 @@ func detachMounted(path string) error { func killProcessDirectly(container *container.Container) error { return nil } + +func isLinkable(child *container.Container) bool { + return false +} diff --git a/daemon/daemon.go b/daemon/daemon.go index 892e83dac04ba..34ac30d0143a3 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -71,6 +71,7 @@ import ( "github.com/docker/docker/volume/store" "github.com/docker/go-connections/nat" "github.com/docker/libnetwork" + nwconfig "github.com/docker/libnetwork/config" lntypes "github.com/docker/libnetwork/types" "github.com/docker/libtrust" "github.com/opencontainers/runc/libcontainer" @@ -1693,3 +1694,45 @@ func validateID(id string) error { } return nil } + +func isBridgeNetworkDisabled(config *Config) bool { + return config.bridgeConfig.Iface == disableNetworkBridge +} + +func (daemon *Daemon) networkOptions(dconfig *Config) ([]nwconfig.Option, error) { + options := []nwconfig.Option{} + if dconfig == nil { + return options, nil + } + + options = append(options, nwconfig.OptionDataDir(dconfig.Root)) + + dd := runconfig.DefaultDaemonNetworkMode() + dn := runconfig.DefaultDaemonNetworkMode().NetworkName() + options = append(options, nwconfig.OptionDefaultDriver(string(dd))) + options = append(options, nwconfig.OptionDefaultNetwork(dn)) + + if strings.TrimSpace(dconfig.ClusterStore) != "" { + kv := strings.Split(dconfig.ClusterStore, "://") + if len(kv) != 2 { + return nil, fmt.Errorf("kv store daemon config must be of the form KV-PROVIDER://KV-URL") + } + options = append(options, nwconfig.OptionKVProvider(kv[0])) + options = append(options, nwconfig.OptionKVProviderURL(kv[1])) + } + if len(dconfig.ClusterOpts) > 0 { + options = append(options, nwconfig.OptionKVOpts(dconfig.ClusterOpts)) + } + + if daemon.discoveryWatcher != nil { + options = append(options, nwconfig.OptionDiscoveryWatcher(daemon.discoveryWatcher)) + } + + if dconfig.ClusterAdvertise != "" { + options = append(options, nwconfig.OptionDiscoveryAddress(dconfig.ClusterAdvertise)) + } + + options = append(options, nwconfig.OptionLabels(dconfig.Labels)) + options = append(options, driverOptions(dconfig)...) + return options, nil +} diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 21923fb0b2d1c..6c1ae734ad3e3 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -518,48 +518,6 @@ func configureKernelSecuritySupport(config *Config, driverName string) error { return nil } -func isBridgeNetworkDisabled(config *Config) bool { - return config.bridgeConfig.Iface == disableNetworkBridge -} - -func (daemon *Daemon) networkOptions(dconfig *Config) ([]nwconfig.Option, error) { - options := []nwconfig.Option{} - if dconfig == nil { - return options, nil - } - - options = append(options, nwconfig.OptionDataDir(dconfig.Root)) - - dd := runconfig.DefaultDaemonNetworkMode() - dn := runconfig.DefaultDaemonNetworkMode().NetworkName() - options = append(options, nwconfig.OptionDefaultDriver(string(dd))) - options = append(options, nwconfig.OptionDefaultNetwork(dn)) - - if strings.TrimSpace(dconfig.ClusterStore) != "" { - kv := strings.Split(dconfig.ClusterStore, "://") - if len(kv) != 2 { - return nil, fmt.Errorf("kv store daemon config must be of the form KV-PROVIDER://KV-URL") - } - options = append(options, nwconfig.OptionKVProvider(kv[0])) - options = append(options, nwconfig.OptionKVProviderURL(kv[1])) - } - if len(dconfig.ClusterOpts) > 0 { - options = append(options, nwconfig.OptionKVOpts(dconfig.ClusterOpts)) - } - - if daemon.discoveryWatcher != nil { - options = append(options, nwconfig.OptionDiscoveryWatcher(daemon.discoveryWatcher)) - } - - if dconfig.ClusterAdvertise != "" { - options = append(options, nwconfig.OptionDiscoveryAddress(dconfig.ClusterAdvertise)) - } - - options = append(options, nwconfig.OptionLabels(dconfig.Labels)) - options = append(options, driverOptions(dconfig)...) - return options, nil -} - func (daemon *Daemon) initNetworkController(config *Config) (libnetwork.NetworkController, error) { netOptions, err := daemon.networkOptions(config) if err != nil { diff --git a/daemon/daemon_windows.go b/daemon/daemon_windows.go index b9aadc6b00d63..27f19be50d671 100644 --- a/daemon/daemon_windows.go +++ b/daemon/daemon_windows.go @@ -9,6 +9,7 @@ import ( "runtime" "strings" + "github.com/Microsoft/hcsshim" "github.com/Sirupsen/logrus" "github.com/docker/docker/container" "github.com/docker/docker/daemon/graphdriver" @@ -16,6 +17,7 @@ import ( "github.com/docker/docker/image" "github.com/docker/docker/layer" "github.com/docker/docker/reference" + "github.com/docker/docker/runconfig" containertypes "github.com/docker/engine-api/types/container" // register the windows graph driver "github.com/docker/docker/daemon/graphdriver/windows" @@ -23,11 +25,15 @@ import ( "github.com/docker/docker/pkg/system" "github.com/docker/libnetwork" nwconfig "github.com/docker/libnetwork/config" + winlibnetwork "github.com/docker/libnetwork/drivers/windows" + "github.com/docker/libnetwork/netlabel" + "github.com/docker/libnetwork/options" blkiodev "github.com/opencontainers/runc/libcontainer/configs" ) const ( defaultVirtualSwitch = "Virtual Switch" + defaultNetworkSpace = "172.16.0.0/12" platformSupported = true windowsMinCPUShares = 1 windowsMaxCPUShares = 10000 @@ -125,16 +131,154 @@ func configureMaxThreads(config *Config) error { return nil } -func isBridgeNetworkDisabled(config *Config) bool { - return false +func (daemon *Daemon) initNetworkController(config *Config) (libnetwork.NetworkController, error) { + // TODO Windows: Remove this check once TP4 is no longer supported + osv, err := system.GetOSVersion() + if err != nil { + return nil, err + } + + if osv.Build < 14260 { + // Set the name of the virtual switch if not specified by -b on daemon start + if config.bridgeConfig.Iface == "" { + config.bridgeConfig.Iface = defaultVirtualSwitch + } + logrus.Warnf("Network controller is not supported by the current platform build version") + return nil, nil + } + + netOptions, err := daemon.networkOptions(config) + if err != nil { + return nil, err + } + controller, err := libnetwork.New(netOptions...) + if err != nil { + return nil, fmt.Errorf("error obtaining controller instance: %v", err) + } + + hnsresponse, err := hcsshim.HNSListNetworkRequest("GET", "", "") + if err != nil { + return nil, err + } + + // Remove networks not present in HNS + for _, v := range controller.Networks() { + options := v.Info().DriverOptions() + hnsid := options[winlibnetwork.HNSID] + found := false + + for _, v := range hnsresponse { + if v.Id == hnsid { + found = true + break + } + } + + if !found { + err = v.Delete() + if err != nil { + return nil, err + } + } + } + + _, err = controller.NewNetwork("null", "none", libnetwork.NetworkOptionPersist(false)) + if err != nil { + return nil, err + } + + // discover and add HNS networks to windows + // network that exist are removed and added again + for _, v := range hnsresponse { + var n libnetwork.Network + s := func(current libnetwork.Network) bool { + options := current.Info().DriverOptions() + if options[winlibnetwork.HNSID] == v.Id { + n = current + return true + } + return false + } + + controller.WalkNetworks(s) + if n != nil { + v.Name = n.Name() + n.Delete() + } + + netOption := map[string]string{ + winlibnetwork.NetworkName: v.Name, + winlibnetwork.HNSID: v.Id, + } + + v4Conf := []*libnetwork.IpamConf{} + for _, subnet := range v.Subnets { + ipamV4Conf := libnetwork.IpamConf{} + ipamV4Conf.PreferredPool = subnet.AddressPrefix + ipamV4Conf.Gateway = subnet.GatewayAddress + v4Conf = append(v4Conf, &ipamV4Conf) + } + + name := v.Name + // There is only one nat network supported in windows. + // If it exists with a different name add it as the default name + if runconfig.DefaultDaemonNetworkMode() == containertypes.NetworkMode(strings.ToLower(v.Type)) { + name = runconfig.DefaultDaemonNetworkMode().NetworkName() + } + + v6Conf := []*libnetwork.IpamConf{} + _, err := controller.NewNetwork(strings.ToLower(v.Type), name, + libnetwork.NetworkOptionGeneric(options.Generic{ + netlabel.GenericData: netOption, + }), + libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil), + ) + + if err != nil { + logrus.Errorf("Error occurred when creating network %v", err) + } + } + + if !config.DisableBridge { + // Initialize default driver "bridge" + if err := initBridgeDriver(controller, config); err != nil { + return nil, err + } + } + + return controller, nil } -func (daemon *Daemon) initNetworkController(config *Config) (libnetwork.NetworkController, error) { - // Set the name of the virtual switch if not specified by -b on daemon start - if config.bridgeConfig.VirtualSwitchName == "" { - config.bridgeConfig.VirtualSwitchName = defaultVirtualSwitch +func initBridgeDriver(controller libnetwork.NetworkController, config *Config) error { + if _, err := controller.NetworkByName(runconfig.DefaultDaemonNetworkMode().NetworkName()); err == nil { + return nil } - return nil, nil + + netOption := map[string]string{ + winlibnetwork.NetworkName: runconfig.DefaultDaemonNetworkMode().NetworkName(), + } + + ipamV4Conf := libnetwork.IpamConf{} + if config.bridgeConfig.FixedCIDR == "" { + ipamV4Conf.PreferredPool = defaultNetworkSpace + } else { + ipamV4Conf.PreferredPool = config.bridgeConfig.FixedCIDR + } + + v4Conf := []*libnetwork.IpamConf{&ipamV4Conf} + v6Conf := []*libnetwork.IpamConf{} + + _, err := controller.NewNetwork(string(runconfig.DefaultDaemonNetworkMode()), runconfig.DefaultDaemonNetworkMode().NetworkName(), + libnetwork.NetworkOptionGeneric(options.Generic{ + netlabel.GenericData: netOption, + }), + libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil), + ) + + if err != nil { + return fmt.Errorf("Error creating default network: %v", err) + } + return nil } // registerLinks sets up links between containers and writes the @@ -257,6 +401,6 @@ func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) erro return nil } -func (daemon *Daemon) networkOptions(dconfig *Config) ([]nwconfig.Option, error) { - return nil, fmt.Errorf("Network controller config reload not aavailable on Windows yet") +func driverOptions(config *Config) []nwconfig.Option { + return []nwconfig.Option{} } diff --git a/daemon/execdriver/driver_windows.go b/daemon/execdriver/driver_windows.go index 2fdb533729b69..4b2505623fdf9 100644 --- a/daemon/execdriver/driver_windows.go +++ b/daemon/execdriver/driver_windows.go @@ -56,6 +56,7 @@ type Command struct { Isolation string `json:"isolation"` // Isolation technology for the container ArgsEscaped bool `json:"args_escaped"` // True if args are already escaped HvPartition bool `json:"hv_partition"` // True if it's an hypervisor partition + EpList []string `json:"endpoints"` // List of network endpoints for HNS } // ExitStatus provides exit reasons for a container. diff --git a/daemon/execdriver/windows/run.go b/daemon/execdriver/windows/run.go index b02fa747d9eeb..6ecfee4e36fa9 100644 --- a/daemon/execdriver/windows/run.go +++ b/daemon/execdriver/windows/run.go @@ -89,6 +89,7 @@ type containerInit struct { MappedDirectories []mappedDir // List of mapped directories (volumes/mounts) SandboxPath string // Location of unmounted sandbox (used for Hyper-V containers, not Windows Server containers) HvPartition bool // True if it a Hyper-V Container + EndpointList []string // List of endpoints to be attached to container } // defaultOwner is a tag passed to HCS to allow it to differentiate between @@ -104,6 +105,7 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execd err error ) + // Allocate Network only if there is no network interface cu := &containerInit{ SystemType: "Container", Name: c.ID, @@ -114,6 +116,7 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execd LayerFolderPath: c.LayerFolder, ProcessorWeight: c.Resources.CPUShares, HostName: c.Hostname, + EndpointList: c.EpList, } cu.HvPartition = c.HvPartition diff --git a/hack/make/win b/hack/make/win new file mode 100644 index 0000000000000..f9f4111276a25 --- /dev/null +++ b/hack/make/win @@ -0,0 +1,20 @@ +#!/bin/bash +set -e + +# explicit list of os/arch combos that support being a daemon +declare -A daemonSupporting +daemonSupporting=( + [linux/amd64]=1 + [windows/amd64]=1 +) +platform="windows/amd64" +export DEST="$DEST/$platform" # bundles/VERSION/cross/GOOS/GOARCH/docker-VERSION +mkdir -p "$DEST" +ABS_DEST="$(cd "$DEST" && pwd -P)" +export GOOS=${platform%/*} +export GOARCH=${platform##*/} +if [ -z "${daemonSupporting[$platform]}" ]; then + export LDFLAGS_STATIC_DOCKER="" # we just need a simple client for these platforms + export BUILDFLAGS=( "${ORIG_BUILDFLAGS[@]/ daemon/}" ) # remove the "daemon" build tag from platforms that aren't supported +fi +source "${MAKEDIR}/binary" diff --git a/runconfig/hostconfig_windows.go b/runconfig/hostconfig_windows.go index ea36666b898b5..bab24e4465ddf 100644 --- a/runconfig/hostconfig_windows.go +++ b/runconfig/hostconfig_windows.go @@ -10,25 +10,22 @@ import ( // DefaultDaemonNetworkMode returns the default network stack the daemon should // use. func DefaultDaemonNetworkMode() container.NetworkMode { - return container.NetworkMode("default") + return container.NetworkMode("nat") } // IsPreDefinedNetwork indicates if a network is predefined by the daemon func IsPreDefinedNetwork(network string) bool { - return false + return !container.NetworkMode(network).IsUserDefined() } // ValidateNetMode ensures that the various combinations of requested // network settings are valid. func ValidateNetMode(c *container.Config, hc *container.HostConfig) error { - // We may not be passed a host config, such as in the case of docker commit if hc == nil { return nil } parts := strings.Split(string(hc.NetworkMode), ":") - switch mode := parts[0]; mode { - case "default", "none": - default: + if len(parts) > 1 { return fmt.Errorf("invalid --net: %s", hc.NetworkMode) } return nil