diff --git a/Makefile b/Makefile index b98424b6c0..c13836bc32 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ DOCKER_ENVS := \ -e DOCKER_EXECDRIVER \ -e DOCKER_GRAPHDRIVER \ -e DOCKER_STORAGE_OPTS \ + -e DOCKER_USERLANDPROXY \ -e TESTDIRS \ -e TESTFLAGS \ -e TIMEOUT diff --git a/api/client/run.go b/api/client/run.go index 12d1b24283..6f8cdb871f 100644 --- a/api/client/run.go +++ b/api/client/run.go @@ -12,6 +12,7 @@ import ( "github.com/docker/docker/pkg/resolvconf/dns" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/runconfig" + "github.com/docker/libnetwork/resolvconf/dns" ) func (cid *cidFile) Close() error { diff --git a/api/server/server.go b/api/server/server.go index fa591a467c..04038531f5 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -23,7 +23,6 @@ import ( "github.com/docker/docker/builder" "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" - "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/graph" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/jsonmessage" @@ -36,6 +35,7 @@ import ( "github.com/docker/docker/pkg/version" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" + "github.com/docker/libnetwork/portallocator" ) type ServerConfig struct { @@ -1548,8 +1548,9 @@ func allocateDaemonPort(addr string) error { return fmt.Errorf("failed to lookup %s address in host specification", host) } + pa := portallocator.Get() for _, hostIP := range hostIPs { - if _, err := bridge.RequestPort(hostIP, "tcp", intPort); err != nil { + if _, err := pa.RequestPort(hostIP, "tcp", intPort); err != nil { return fmt.Errorf("failed to allocate daemon listening port %d (err: %v)", intPort, err) } } diff --git a/daemon/config.go b/daemon/config.go index b190269498..3599def874 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -1,8 +1,8 @@ package daemon import ( - "github.com/docker/docker/daemon/networkdriver" - "github.com/docker/docker/daemon/networkdriver/bridge" + "net" + "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/runconfig" @@ -16,8 +16,9 @@ const ( // CommonConfig defines the configuration of a docker daemon which are // common across platforms. type CommonConfig struct { - AutoRestart bool - Bridge bridge.Config + AutoRestart bool + // Bridge holds bridge network specific configuration. + Bridge bridgeConfig Context map[string][]string CorsHeaders string DisableNetwork bool @@ -35,6 +36,24 @@ type CommonConfig struct { TrustKeyPath string } +// bridgeConfig stores all the bridge driver specific +// configuration. +type bridgeConfig struct { + EnableIPv6 bool + EnableIPTables bool + EnableIPForward bool + EnableIPMasq bool + EnableUserlandProxy bool + DefaultIP net.IP + Iface string + IP string + FixedCIDR string + FixedCIDRv6 string + DefaultGatewayIPv4 string + DefaultGatewayIPv6 string + InterContainerCommunication bool +} + // InstallCommonFlags adds command-line options to the top-level flag parser for // the current process. // Subsequent calls to `flag.Parse` will populate config with values parsed @@ -45,9 +64,9 @@ func (config *Config) InstallCommonFlags() { flag.StringVar(&config.Root, []string{"g", "-graph"}, defaultGraph, "Root of the Docker runtime") flag.StringVar(&config.ExecRoot, []string{"-exec-root"}, "/var/run/docker", "Root of the Docker execdriver") flag.BoolVar(&config.AutoRestart, []string{"#r", "#-restart"}, true, "--restart on the daemon has been deprecated in favor of --restart policies on docker run") - flag.BoolVar(&config.Bridge.EnableIptables, []string{"#iptables", "-iptables"}, true, "Enable addition of iptables rules") - flag.BoolVar(&config.Bridge.EnableIpForward, []string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward") - flag.BoolVar(&config.Bridge.EnableIpMasq, []string{"-ip-masq"}, true, "Enable IP masquerading") + flag.BoolVar(&config.Bridge.EnableIPTables, []string{"#iptables", "-iptables"}, true, "Enable addition of iptables rules") + flag.BoolVar(&config.Bridge.EnableIPForward, []string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward") + flag.BoolVar(&config.Bridge.EnableIPMasq, []string{"-ip-masq"}, true, "Enable IP masquerading") flag.BoolVar(&config.Bridge.EnableIPv6, []string{"-ipv6"}, false, "Enable IPv6 networking") flag.StringVar(&config.Bridge.IP, []string{"#bip", "-bip"}, "", "Specify network bridge IP") flag.StringVar(&config.Bridge.Iface, []string{"b", "-bridge"}, "", "Attach containers to a network bridge") @@ -61,7 +80,7 @@ func (config *Config) InstallCommonFlags() { flag.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, "Set the containers network MTU") flag.BoolVar(&config.EnableCors, []string{"#api-enable-cors", "#-api-enable-cors"}, false, "Enable CORS headers in the remote API, this is deprecated by --api-cors-header") flag.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", "Set CORS headers in the remote API") - opts.IPVar(&config.Bridge.DefaultIp, []string{"#ip", "-ip"}, "0.0.0.0", "Default IP when binding container ports") + opts.IPVar(&config.Bridge.DefaultIP, []string{"#ip", "-ip"}, "0.0.0.0", "Default IP when binding container ports") // FIXME: why the inconsistency between "hosts" and "sockets"? opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use") opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "DNS search domains to use") @@ -71,10 +90,3 @@ func (config *Config) InstallCommonFlags() { flag.BoolVar(&config.Bridge.EnableUserlandProxy, []string{"-userland-proxy"}, true, "Use userland proxy for loopback traffic") } - -func getDefaultNetworkMtu() int { - if iface, err := networkdriver.GetDefaultRouteIface(); err == nil { - return iface.MTU - } - return defaultNetworkMtu -} diff --git a/daemon/container.go b/daemon/container.go index 55e1f890c5..7a59bec5d7 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -252,18 +252,12 @@ func (container *Container) Start() (err error) { } }() - if err := container.setupContainerDns(); err != nil { - return err - } if err := container.Mount(); err != nil { return err } if err := container.initializeNetworking(); err != nil { return err } - if err := container.updateParentsHosts(); err != nil { - return err - } container.verifyDaemonSettings() if err := container.prepareVolumes(); err != nil { return err diff --git a/daemon/container_linux.go b/daemon/container_linux.go index 58128f49d4..ee9a5092d9 100644 --- a/daemon/container_linux.go +++ b/daemon/container_linux.go @@ -3,11 +3,13 @@ package daemon import ( - "bytes" "fmt" "io/ioutil" + "net" "os" + "path" "path/filepath" + "strconv" "strings" "syscall" "time" @@ -15,20 +17,21 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/network" - "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/links" "github.com/docker/docker/nat" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/directory" - "github.com/docker/docker/pkg/etchosts" "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/resolvconf" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/ulimit" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/devices" + "github.com/docker/libnetwork" + "github.com/docker/libnetwork/netlabel" + "github.com/docker/libnetwork/netutils" + "github.com/docker/libnetwork/options" ) const DefaultPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" @@ -65,179 +68,6 @@ func killProcessDirectly(container *Container) error { return nil } -func (container *Container) setupContainerDns() error { - if container.ResolvConfPath != "" { - // check if this is an existing container that needs DNS update: - if container.UpdateDns { - // read the host's resolv.conf, get the hash and call updateResolvConf - logrus.Debugf("Check container (%s) for update to resolv.conf - UpdateDns flag was set", container.ID) - latestResolvConf, latestHash := resolvconf.GetLastModified() - - // clean container resolv.conf re: localhost nameservers and IPv6 NS (if IPv6 disabled) - updatedResolvConf, modified := resolvconf.FilterResolvDns(latestResolvConf, container.daemon.config.Bridge.EnableIPv6) - if modified { - // changes have occurred during resolv.conf localhost cleanup: generate an updated hash - newHash, err := ioutils.HashData(bytes.NewReader(updatedResolvConf)) - if err != nil { - return err - } - latestHash = newHash - } - - if err := container.updateResolvConf(updatedResolvConf, latestHash); err != nil { - return err - } - // successful update of the restarting container; set the flag off - container.UpdateDns = false - } - return nil - } - - var ( - config = container.hostConfig - daemon = container.daemon - ) - - resolvConf, err := resolvconf.Get() - if err != nil { - return err - } - container.ResolvConfPath, err = container.GetRootResourcePath("resolv.conf") - if err != nil { - return err - } - - if config.NetworkMode.IsBridge() || config.NetworkMode.IsNone() { - // check configurations for any container/daemon dns settings - if len(config.Dns) > 0 || len(daemon.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(daemon.config.DnsSearch) > 0 { - var ( - dns = resolvconf.GetNameservers(resolvConf) - dnsSearch = resolvconf.GetSearchDomains(resolvConf) - ) - if len(config.Dns) > 0 { - dns = config.Dns - } else if len(daemon.config.Dns) > 0 { - dns = daemon.config.Dns - } - if len(config.DnsSearch) > 0 { - dnsSearch = config.DnsSearch - } else if len(daemon.config.DnsSearch) > 0 { - dnsSearch = daemon.config.DnsSearch - } - return resolvconf.Build(container.ResolvConfPath, dns, dnsSearch) - } - - // replace any localhost/127.*, and remove IPv6 nameservers if IPv6 disabled in daemon - resolvConf, _ = resolvconf.FilterResolvDns(resolvConf, daemon.config.Bridge.EnableIPv6) - } - //get a sha256 hash of the resolv conf at this point so we can check - //for changes when the host resolv.conf changes (e.g. network update) - resolvHash, err := ioutils.HashData(bytes.NewReader(resolvConf)) - if err != nil { - return err - } - resolvHashFile := container.ResolvConfPath + ".hash" - if err = ioutil.WriteFile(resolvHashFile, []byte(resolvHash), 0644); err != nil { - return err - } - return ioutil.WriteFile(container.ResolvConfPath, resolvConf, 0644) -} - -// called when the host's resolv.conf changes to check whether container's resolv.conf -// is unchanged by the container "user" since container start: if unchanged, the -// container's resolv.conf will be updated to match the host's new resolv.conf -func (container *Container) updateResolvConf(updatedResolvConf []byte, newResolvHash string) error { - - if container.ResolvConfPath == "" { - return nil - } - if container.Running { - //set a marker in the hostConfig to update on next start/restart - container.UpdateDns = true - return nil - } - - resolvHashFile := container.ResolvConfPath + ".hash" - - //read the container's current resolv.conf and compute the hash - resolvBytes, err := ioutil.ReadFile(container.ResolvConfPath) - if err != nil { - return err - } - curHash, err := ioutils.HashData(bytes.NewReader(resolvBytes)) - if err != nil { - return err - } - - //read the hash from the last time we wrote resolv.conf in the container - hashBytes, err := ioutil.ReadFile(resolvHashFile) - if err != nil { - if !os.IsNotExist(err) { - return err - } - // backwards compat: if no hash file exists, this container pre-existed from - // a Docker daemon that didn't contain this update feature. Given we can't know - // if the user has modified the resolv.conf since container start time, safer - // to just never update the container's resolv.conf during it's lifetime which - // we can control by setting hashBytes to an empty string - hashBytes = []byte("") - } - - //if the user has not modified the resolv.conf of the container since we wrote it last - //we will replace it with the updated resolv.conf from the host - if string(hashBytes) == curHash { - logrus.Debugf("replacing %q with updated host resolv.conf", container.ResolvConfPath) - - // for atomic updates to these files, use temporary files with os.Rename: - dir := filepath.Dir(container.ResolvConfPath) - tmpHashFile, err := ioutil.TempFile(dir, "hash") - if err != nil { - return err - } - tmpResolvFile, err := ioutil.TempFile(dir, "resolv") - if err != nil { - return err - } - - // write the updates to the temp files - if err = ioutil.WriteFile(tmpHashFile.Name(), []byte(newResolvHash), 0644); err != nil { - return err - } - if err = ioutil.WriteFile(tmpResolvFile.Name(), updatedResolvConf, 0644); err != nil { - return err - } - - // rename the temp files for atomic replace - if err = os.Rename(tmpHashFile.Name(), resolvHashFile); err != nil { - return err - } - return os.Rename(tmpResolvFile.Name(), container.ResolvConfPath) - } - return nil -} - -func (container *Container) updateParentsHosts() error { - refs := container.daemon.ContainerGraph().RefPaths(container.ID) - for _, ref := range refs { - if ref.ParentID == "0" { - continue - } - - c, err := container.daemon.Get(ref.ParentID) - if err != nil { - logrus.Error(err) - } - - if c != nil && !container.daemon.config.DisableNetwork && container.hostConfig.NetworkMode.IsPrivate() { - logrus.Debugf("Update /etc/hosts of %s for alias %s with ip %s", c.ID, ref.Name, container.NetworkSettings.IPAddress) - if err := etchosts.Update(c.HostsPath, container.NetworkSettings.IPAddress, ref.Name); err != nil { - logrus.Errorf("Failed to update /etc/hosts in parent container %s for alias %s: %v", c.ID, ref.Name, err) - } - } - } - return nil -} - func (container *Container) setupLinkedContainers() ([]string, error) { var ( env []string @@ -360,39 +190,16 @@ func getDevicesFromPath(deviceMapping runconfig.DeviceMapping) (devs []*configs. func populateCommand(c *Container, env []string) error { en := &execdriver.Network{ - Mtu: c.daemon.config.Mtu, - Interface: nil, + NamespacePath: c.NetworkSettings.SandboxKey, } parts := strings.SplitN(string(c.hostConfig.NetworkMode), ":", 2) - switch parts[0] { - case "none": - case "host": - en.HostNetworking = true - case "bridge", "": // empty string to support existing containers - if !c.Config.NetworkDisabled { - network := c.NetworkSettings - en.Interface = &execdriver.NetworkInterface{ - Gateway: network.Gateway, - Bridge: network.Bridge, - IPAddress: network.IPAddress, - IPPrefixLen: network.IPPrefixLen, - MacAddress: network.MacAddress, - LinkLocalIPv6Address: network.LinkLocalIPv6Address, - GlobalIPv6Address: network.GlobalIPv6Address, - GlobalIPv6PrefixLen: network.GlobalIPv6PrefixLen, - IPv6Gateway: network.IPv6Gateway, - HairpinMode: network.HairpinMode, - } - } - case "container": + if parts[0] == "container" { nc, err := c.getNetworkedContainer() if err != nil { return err } en.ContainerID = nc.ID - default: - return fmt.Errorf("invalid network mode: %s", c.hostConfig.NetworkMode) } ipc := &execdriver.Ipc{} @@ -537,40 +344,318 @@ func (container *Container) GetSize() (int64, int64) { return sizeRw, sizeRootfs } -func (container *Container) AllocateNetwork() error { - mode := container.hostConfig.NetworkMode - if container.Config.NetworkDisabled || !mode.IsPrivate() { +func (container *Container) buildHostnameFile() error { + hostnamePath, err := container.GetRootResourcePath("hostname") + if err != nil { + return err + } + container.HostnamePath = hostnamePath + + if container.Config.Domainname != "" { + return ioutil.WriteFile(container.HostnamePath, []byte(fmt.Sprintf("%s.%s\n", container.Config.Hostname, container.Config.Domainname)), 0644) + } + return ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644) +} + +func (container *Container) buildJoinOptions() ([]libnetwork.EndpointOption, error) { + var ( + joinOptions []libnetwork.EndpointOption + err error + dns []string + dnsSearch []string + ) + + joinOptions = append(joinOptions, libnetwork.JoinOptionHostname(container.Config.Hostname), + libnetwork.JoinOptionDomainname(container.Config.Domainname)) + + if container.hostConfig.NetworkMode.IsHost() { + joinOptions = append(joinOptions, libnetwork.JoinOptionUseDefaultSandbox()) + } + + container.HostsPath, err = container.GetRootResourcePath("hosts") + if err != nil { + return nil, err + } + joinOptions = append(joinOptions, libnetwork.JoinOptionHostsPath(container.HostsPath)) + + container.ResolvConfPath, err = container.GetRootResourcePath("resolv.conf") + if err != nil { + return nil, err + } + joinOptions = append(joinOptions, libnetwork.JoinOptionResolvConfPath(container.ResolvConfPath)) + + if len(container.hostConfig.Dns) > 0 { + dns = container.hostConfig.Dns + } else if len(container.daemon.config.Dns) > 0 { + dns = container.daemon.config.Dns + } + + for _, d := range dns { + joinOptions = append(joinOptions, libnetwork.JoinOptionDNS(d)) + } + + if len(container.hostConfig.DnsSearch) > 0 { + dnsSearch = container.hostConfig.DnsSearch + } else if len(container.daemon.config.DnsSearch) > 0 { + dnsSearch = container.daemon.config.DnsSearch + } + + for _, ds := range dnsSearch { + joinOptions = append(joinOptions, libnetwork.JoinOptionDNSSearch(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 { + joinOptions = append(joinOptions, libnetwork.JoinOptionExtraHost(name, a.Addr)) + } + } + + var childEndpoints, parentEndpoints []string + + children, err := container.daemon.Children(container.Name) + if err != nil { + return nil, err + } + + for linkAlias, child := range children { + _, 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:] + } + joinOptions = append(joinOptions, libnetwork.JoinOptionExtraHost(aliasList, child.NetworkSettings.IPAddress)) + if child.NetworkSettings.EndpointID != "" { + childEndpoints = append(childEndpoints, child.NetworkSettings.EndpointID) + } + } + + for _, extraHost := range container.hostConfig.ExtraHosts { + // allow IPv6 addresses in extra hosts; only split on first ":" + parts := strings.SplitN(extraHost, ":", 2) + joinOptions = append(joinOptions, libnetwork.JoinOptionExtraHost(parts[0], parts[1])) + } + + refs := container.daemon.ContainerGraph().RefPaths(container.ID) + for _, ref := range refs { + if ref.ParentID == "0" { + continue + } + + c, err := container.daemon.Get(ref.ParentID) + if err != nil { + logrus.Error(err) + } + + if c != nil && !container.daemon.config.DisableNetwork && container.hostConfig.NetworkMode.IsPrivate() { + logrus.Debugf("Update /etc/hosts of %s for alias %s with ip %s", c.ID, ref.Name, container.NetworkSettings.IPAddress) + joinOptions = append(joinOptions, libnetwork.JoinOptionParentUpdate(c.NetworkSettings.EndpointID, ref.Name, container.NetworkSettings.IPAddress)) + if c.NetworkSettings.EndpointID != "" { + parentEndpoints = append(parentEndpoints, c.NetworkSettings.EndpointID) + } + } + } + + linkOptions := options.Generic{ + netlabel.GenericData: options.Generic{ + "ParentEndpoints": parentEndpoints, + "ChildEndpoints": childEndpoints, + }, + } + + joinOptions = append(joinOptions, libnetwork.JoinOptionGeneric(linkOptions)) + + return joinOptions, nil +} + +func (container *Container) buildPortMapInfo(n libnetwork.Network, ep libnetwork.Endpoint, networkSettings *network.Settings) (*network.Settings, error) { + if ep == nil { + return nil, fmt.Errorf("invalid endpoint while building port map info") + } + + if networkSettings == nil { + return nil, fmt.Errorf("invalid networksettings while building port map info") + } + + driverInfo, err := ep.DriverInfo() + if err != nil { + return nil, err + } + + if driverInfo == nil { + // It is not an error for epInfo to be nil + return networkSettings, nil + } + + if mac, ok := driverInfo[netlabel.MacAddress]; ok { + networkSettings.MacAddress = mac.(net.HardwareAddr).String() + } + + mapData, ok := driverInfo[netlabel.PortMap] + if !ok { + return networkSettings, nil + } + + if portMapping, ok := mapData.([]netutils.PortBinding); ok { + networkSettings.Ports = nat.PortMap{} + for _, pp := range portMapping { + natPort := nat.NewPort(pp.Proto.String(), strconv.Itoa(int(pp.Port))) + natBndg := nat.PortBinding{HostIp: pp.HostIP.String(), HostPort: strconv.Itoa(int(pp.HostPort))} + networkSettings.Ports[natPort] = append(networkSettings.Ports[natPort], natBndg) + } + } + + return networkSettings, nil +} + +func (container *Container) buildEndpointInfo(n libnetwork.Network, ep libnetwork.Endpoint, networkSettings *network.Settings) (*network.Settings, error) { + if ep == nil { + return nil, fmt.Errorf("invalid endpoint while building port map info") + } + + if networkSettings == nil { + return nil, fmt.Errorf("invalid networksettings while building port map info") + } + + epInfo := ep.Info() + if epInfo == nil { + // It is not an error to get an empty endpoint info + return networkSettings, nil + } + + ifaceList := epInfo.InterfaceList() + if len(ifaceList) == 0 { + return networkSettings, nil + } + + iface := ifaceList[0] + + ones, _ := iface.Address().Mask.Size() + networkSettings.IPAddress = iface.Address().IP.String() + networkSettings.IPPrefixLen = ones + + if iface.AddressIPv6().IP.To16() != nil { + onesv6, _ := iface.AddressIPv6().Mask.Size() + networkSettings.GlobalIPv6Address = iface.AddressIPv6().IP.String() + networkSettings.GlobalIPv6PrefixLen = onesv6 + } + + if len(ifaceList) == 1 { + return networkSettings, nil + } + + networkSettings.SecondaryIPAddresses = make([]network.Address, 0, len(ifaceList)-1) + networkSettings.SecondaryIPv6Addresses = make([]network.Address, 0, len(ifaceList)-1) + for _, iface := range ifaceList[1:] { + ones, _ := iface.Address().Mask.Size() + addr := network.Address{Addr: iface.Address().IP.String(), PrefixLen: ones} + networkSettings.SecondaryIPAddresses = append(networkSettings.SecondaryIPAddresses, addr) + + if iface.AddressIPv6().IP.To16() != nil { + onesv6, _ := iface.AddressIPv6().Mask.Size() + addrv6 := network.Address{Addr: iface.AddressIPv6().IP.String(), PrefixLen: onesv6} + networkSettings.SecondaryIPv6Addresses = append(networkSettings.SecondaryIPv6Addresses, addrv6) + } + } + + return networkSettings, nil +} + +func (container *Container) updateJoinInfo(ep libnetwork.Endpoint) error { + epInfo := ep.Info() + if epInfo == nil { + // It is not an error to get an empty endpoint info return nil } - var err error + container.NetworkSettings.Gateway = epInfo.Gateway().String() + if epInfo.GatewayIPv6().To16() != nil { + container.NetworkSettings.IPv6Gateway = epInfo.GatewayIPv6().String() + } - networkSettings, err := bridge.Allocate(container.ID, container.Config.MacAddress, "", "") + container.NetworkSettings.SandboxKey = epInfo.SandboxKey() + + return nil +} + +func (container *Container) updateNetworkSettings(n libnetwork.Network, ep libnetwork.Endpoint) error { + networkSettings := &network.Settings{NetworkID: n.ID(), EndpointID: ep.ID()} + + networkSettings, err := container.buildPortMapInfo(n, ep, networkSettings) if err != nil { return err } - // Error handling: At this point, the interface is allocated so we have to - // make sure that it is always released in case of error, otherwise we - // might leak resources. - - if container.Config.PortSpecs != nil { - if err = migratePortMappings(container.Config, container.hostConfig); err != nil { - bridge.Release(container.ID) - return err - } - container.Config.PortSpecs = nil - if err = container.WriteHostConfig(); err != nil { - bridge.Release(container.ID) - return err - } + networkSettings, err = container.buildEndpointInfo(n, ep, networkSettings) + if err != nil { + return err } + if container.hostConfig.NetworkMode == runconfig.NetworkMode("bridge") { + networkSettings.Bridge = container.daemon.config.Bridge.Iface + } + + container.NetworkSettings = networkSettings + return nil +} + +func (container *Container) UpdateNetwork() error { + n, err := container.daemon.netController.NetworkByID(container.NetworkSettings.NetworkID) + if err != nil { + return fmt.Errorf("error locating network id %s: %v", container.NetworkSettings.NetworkID, err) + } + + ep, err := n.EndpointByID(container.NetworkSettings.EndpointID) + if err != nil { + return fmt.Errorf("error locating endpoint id %s: %v", container.NetworkSettings.EndpointID, err) + } + + if err := ep.Leave(container.ID); err != nil { + return fmt.Errorf("endpoint leave failed: %v", err) + + } + + joinOptions, err := container.buildJoinOptions() + if err != nil { + return fmt.Errorf("Update network failed: %v", err) + } + + if _, err := ep.Join(container.ID, joinOptions...); err != nil { + return fmt.Errorf("endpoint join failed: %v", err) + } + + if err := container.updateJoinInfo(ep); err != nil { + return fmt.Errorf("Updating join info failed: %v", err) + } + + return nil +} + +func (container *Container) buildCreateEndpointOptions() ([]libnetwork.EndpointOption, error) { var ( - portSpecs = make(nat.PortSet) - bindings = make(nat.PortMap) + portSpecs = make(nat.PortSet) + bindings = make(nat.PortMap) + pbList []netutils.PortBinding + exposeList []netutils.TransportPort + createOptions []libnetwork.EndpointOption ) + if container.Config.PortSpecs != nil { + if err := migratePortMappings(container.Config, container.hostConfig); err != nil { + return nil, err + } + container.Config.PortSpecs = nil + if err := container.WriteHostConfig(); err != nil { + return nil, err + } + } + if container.Config.ExposedPorts != nil { portSpecs = container.Config.ExposedPorts } @@ -597,52 +682,99 @@ func (container *Container) AllocateNetwork() error { } nat.SortPortMap(ports, bindings) for _, port := range ports { - if err = container.allocatePort(port, bindings); err != nil { - bridge.Release(container.ID) - return err + expose := netutils.TransportPort{} + expose.Proto = netutils.ParseProtocol(port.Proto()) + expose.Port = uint16(port.Int()) + exposeList = append(exposeList, expose) + + pb := netutils.PortBinding{Port: expose.Port, Proto: expose.Proto} + binding := bindings[port] + for i := 0; i < len(binding); i++ { + pbCopy := pb.GetCopy() + pbCopy.HostPort = uint16(nat.Port(binding[i].HostPort).Int()) + pbCopy.HostIP = net.ParseIP(binding[i].HostIp) + pbList = append(pbList, pbCopy) + } + + if container.hostConfig.PublishAllPorts && len(binding) == 0 { + pbList = append(pbList, pb) } } - container.WriteHostConfig() - networkSettings.Ports = bindings - container.NetworkSettings = networkSettings + createOptions = append(createOptions, + libnetwork.CreateOptionPortMapping(pbList), + libnetwork.CreateOptionExposedPorts(exposeList)) + + 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)) + } + + return createOptions, nil +} + +func (container *Container) AllocateNetwork() error { + mode := container.hostConfig.NetworkMode + if container.Config.NetworkDisabled || mode.IsContainer() { + return nil + } + + var err error + + n, err := container.daemon.netController.NetworkByName(string(mode)) + if err != nil { + return fmt.Errorf("error locating network with name %s: %v", string(mode), err) + } + + createOptions, err := container.buildCreateEndpointOptions() + if err != nil { + return err + } + + ep, err := n.CreateEndpoint(container.Name, createOptions...) + if err != nil { + return err + } + + if err := container.updateNetworkSettings(n, ep); err != nil { + return err + } + + joinOptions, err := container.buildJoinOptions() + if err != nil { + return err + } + + if _, err := ep.Join(container.ID, joinOptions...); err != nil { + return err + } + + if err := container.updateJoinInfo(ep); err != nil { + return fmt.Errorf("Updating join info failed: %v", err) + } + + container.WriteHostConfig() return nil } func (container *Container) initializeNetworking() error { var err error - 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] - } - - content, err := ioutil.ReadFile("/etc/hosts") - if os.IsNotExist(err) { - return container.buildHostnameAndHostsFiles("") - } else if err != nil { - return err - } - - if err := container.buildHostnameFile(); err != nil { - return err - } - - hostsPath, err := container.GetRootResourcePath("hosts") - if err != nil { - return err - } - container.HostsPath = hostsPath - - return ioutil.WriteFile(container.HostsPath, content, 0644) + // Make sure NetworkMode has an acceptable value before + // initializing networking. + if container.hostConfig.NetworkMode == runconfig.NetworkMode("") { + container.hostConfig.NetworkMode = runconfig.NetworkMode("bridge") } + if container.hostConfig.NetworkMode.IsContainer() { // we need to get the hosts files from the container to join nc, err := container.getNetworkedContainer() @@ -656,14 +788,30 @@ func (container *Container) initializeNetworking() error { container.Config.Domainname = nc.Config.Domainname return nil } + if container.daemon.config.DisableNetwork { container.Config.NetworkDisabled = true - return container.buildHostnameAndHostsFiles("127.0.1.1") } + + 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 := container.AllocateNetwork(); err != nil { return err } - return container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress) + + return container.buildHostnameFile() } // Make sure the config is compatible with the current kernel @@ -701,62 +849,6 @@ func (container *Container) ExportRw() (archive.Archive, error) { nil } -func (container *Container) buildHostnameFile() error { - hostnamePath, err := container.GetRootResourcePath("hostname") - if err != nil { - return err - } - container.HostnamePath = hostnamePath - - if container.Config.Domainname != "" { - return ioutil.WriteFile(container.HostnamePath, []byte(fmt.Sprintf("%s.%s\n", container.Config.Hostname, container.Config.Domainname)), 0644) - } - return ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644) -} - -func (container *Container) buildHostsFiles(IP string) error { - - hostsPath, err := container.GetRootResourcePath("hosts") - if err != nil { - return err - } - container.HostsPath = hostsPath - - var extraContent []etchosts.Record - - children, err := container.daemon.Children(container.Name) - if err != nil { - return err - } - - for linkAlias, child := range children { - _, alias := filepath.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:] - } - extraContent = append(extraContent, etchosts.Record{Hosts: aliasList, IP: child.NetworkSettings.IPAddress}) - } - - for _, extraHost := range container.hostConfig.ExtraHosts { - // allow IPv6 addresses in extra hosts; only split on first ":" - parts := strings.SplitN(extraHost, ":", 2) - extraContent = append(extraContent, etchosts.Record{Hosts: parts[0], IP: parts[1]}) - } - - return etchosts.Build(container.HostsPath, IP, container.Config.Hostname, container.Config.Domainname, extraContent) -} - -func (container *Container) buildHostnameAndHostsFiles(IP string) error { - if err := container.buildHostnameFile(); err != nil { - return err - } - - return container.buildHostsFiles(IP) -} - func (container *Container) getIpcContainer() (*Container, error) { containerID := container.hostConfig.IpcMode.Container() c, err := container.daemon.Get(containerID) @@ -795,23 +887,6 @@ func (container *Container) setupWorkingDirectory() error { return nil } -func (container *Container) allocatePort(port nat.Port, bindings nat.PortMap) error { - binding := bindings[port] - if container.hostConfig.PublishAllPorts && len(binding) == 0 { - binding = append(binding, nat.PortBinding{}) - } - - for i := 0; i < len(binding); i++ { - b, err := bridge.AllocatePort(container.ID, port, binding[i]) - if err != nil { - return err - } - binding[i] = b - } - bindings[port] = binding - return nil -} - func (container *Container) getNetworkedContainer() (*Container, error) { parts := strings.SplitN(string(container.hostConfig.NetworkMode), ":", 2) switch parts[0] { @@ -836,36 +911,33 @@ func (container *Container) getNetworkedContainer() (*Container, error) { } func (container *Container) ReleaseNetwork() { - if container.Config.NetworkDisabled || !container.hostConfig.NetworkMode.IsPrivate() { + if container.hostConfig.NetworkMode.IsContainer() { return } - bridge.Release(container.ID) + + n, err := container.daemon.netController.NetworkByID(container.NetworkSettings.NetworkID) + if err != nil { + logrus.Errorf("error locating network id %s: %v", container.NetworkSettings.NetworkID, err) + return + } + + ep, err := n.EndpointByID(container.NetworkSettings.EndpointID) + if err != nil { + logrus.Errorf("error locating endpoint id %s: %v", container.NetworkSettings.EndpointID, err) + return + } + + if err := ep.Leave(container.ID); err != nil { + logrus.Errorf("leaving endpoint failed: %v", err) + } + + if err := ep.Delete(); err != nil { + logrus.Errorf("deleting endpoint failed: %v", err) + } + container.NetworkSettings = &network.Settings{} } -func (container *Container) RestoreNetwork() error { - mode := container.hostConfig.NetworkMode - // Don't attempt a restore if we previously didn't allocate networking. - // This might be a legacy container with no network allocated, in which case the - // allocation will happen once and for all at start. - if !container.isNetworkAllocated() || container.Config.NetworkDisabled || !mode.IsPrivate() { - return nil - } - - // Re-allocate the interface with the same IP and MAC address. - if _, err := bridge.Allocate(container.ID, container.NetworkSettings.MacAddress, container.NetworkSettings.IPAddress, ""); err != nil { - return err - } - - // Re-allocate any previously allocated ports. - for port := range container.NetworkSettings.Ports { - if err := container.allocatePort(port, container.NetworkSettings.Ports); err != nil { - return err - } - } - return nil -} - func disableAllActiveLinks(container *Container) { if container.activeLinks != nil { for _, link := range container.activeLinks { @@ -878,6 +950,10 @@ func (container *Container) DisableLink(name string) { if container.activeLinks != nil { if link, exists := container.activeLinks[name]; exists { link.Disable() + delete(container.activeLinks, name) + if err := container.UpdateNetwork(); err != nil { + logrus.Debugf("Could not update network to remove link: %v", err) + } } else { logrus.Debugf("Could not find active link for %s", name) } diff --git a/daemon/daemon.go b/daemon/daemon.go index 465213517f..96c4e9c736 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1,10 +1,10 @@ package daemon import ( - "bytes" "fmt" "io" "io/ioutil" + "net" "os" "path" "path/filepath" @@ -15,6 +15,9 @@ import ( "time" "github.com/docker/libcontainer/label" + "github.com/docker/libnetwork" + "github.com/docker/libnetwork/netlabel" + "github.com/docker/libnetwork/options" "github.com/Sirupsen/logrus" "github.com/docker/docker/api" @@ -26,7 +29,6 @@ import ( _ "github.com/docker/docker/daemon/graphdriver/vfs" "github.com/docker/docker/daemon/logger" "github.com/docker/docker/daemon/network" - "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/graph" "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" @@ -37,7 +39,6 @@ import ( "github.com/docker/docker/pkg/namesgenerator" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers/kernel" - "github.com/docker/docker/pkg/resolvconf" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/sysinfo" "github.com/docker/docker/pkg/truncindex" @@ -46,8 +47,6 @@ import ( "github.com/docker/docker/trust" "github.com/docker/docker/utils" "github.com/docker/docker/volumes" - - "github.com/go-fsnotify/fsnotify" ) var ( @@ -109,6 +108,7 @@ type Daemon struct { defaultLogConfig runconfig.LogConfig RegistryService *registry.Service EventsService *events.Events + netController libnetwork.NetworkController } // Get looks for a container using the provided information, which could be @@ -349,61 +349,6 @@ func (daemon *Daemon) restore() error { return nil } -// set up the watch on the host's /etc/resolv.conf so that we can update container's -// live resolv.conf when the network changes on the host -func (daemon *Daemon) setupResolvconfWatcher() error { - - watcher, err := fsnotify.NewWatcher() - if err != nil { - return err - } - - //this goroutine listens for the events on the watch we add - //on the resolv.conf file on the host - go func() { - for { - select { - case event := <-watcher.Events: - if event.Name == "/etc/resolv.conf" && - (event.Op&(fsnotify.Write|fsnotify.Create) != 0) { - // verify a real change happened before we go further--a file write may have happened - // without an actual change to the file - updatedResolvConf, newResolvConfHash, err := resolvconf.GetIfChanged() - if err != nil { - logrus.Debugf("Error retrieving updated host resolv.conf: %v", err) - } else if updatedResolvConf != nil { - // because the new host resolv.conf might have localhost nameservers.. - updatedResolvConf, modified := resolvconf.FilterResolvDns(updatedResolvConf, daemon.config.Bridge.EnableIPv6) - if modified { - // changes have occurred during localhost cleanup: generate an updated hash - newHash, err := ioutils.HashData(bytes.NewReader(updatedResolvConf)) - if err != nil { - logrus.Debugf("Error generating hash of new resolv.conf: %v", err) - } else { - newResolvConfHash = newHash - } - } - logrus.Debug("host network resolv.conf changed--walking container list for updates") - contList := daemon.containers.List() - for _, container := range contList { - if err := container.updateResolvConf(updatedResolvConf, newResolvConfHash); err != nil { - logrus.Debugf("Error on resolv.conf update check for container ID: %s: %v", container.ID, err) - } - } - } - } - case err := <-watcher.Errors: - logrus.Debugf("host resolv.conf notify error: %v", err) - } - } - }() - - if err := watcher.Add("/etc"); err != nil { - return err - } - return nil -} - func (daemon *Daemon) checkDeprecatedExpose(config *runconfig.Config) bool { if config != nil { if config.PortSpecs != nil { @@ -727,18 +672,15 @@ func (daemon *Daemon) RegisterLinks(container *Container, hostConfig *runconfig. } func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemon, err error) { - if config.Mtu == 0 { - config.Mtu = getDefaultNetworkMtu() - } // Check for mutually incompatible config options if config.Bridge.Iface != "" && config.Bridge.IP != "" { return nil, fmt.Errorf("You specified -b & --bip, mutually exclusive options. Please specify only one.") } - if !config.Bridge.EnableIptables && !config.Bridge.InterContainerCommunication { + if !config.Bridge.EnableIPTables && !config.Bridge.InterContainerCommunication { return nil, fmt.Errorf("You specified --iptables=false with --icc=false. ICC uses iptables to function. Please set --icc or --iptables to true.") } - if !config.Bridge.EnableIptables && config.Bridge.EnableIpMasq { - config.Bridge.EnableIpMasq = false + if !config.Bridge.EnableIPTables && config.Bridge.EnableIPMasq { + config.Bridge.EnableIPMasq = false } config.DisableNetwork = config.Bridge.Iface == disableNetworkBridge @@ -882,8 +824,9 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo } if !config.DisableNetwork { - if err := bridge.InitDriver(&config.Bridge); err != nil { - return nil, fmt.Errorf("Error initializing Bridge: %v", err) + d.netController, err = initNetworkController(config) + if err != nil { + return nil, fmt.Errorf("Error initializing network controller: %v", err) } } @@ -942,12 +885,97 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo return nil, err } - // set up filesystem watch on resolv.conf for network changes - if err := d.setupResolvconfWatcher(); err != nil { - return nil, err + return d, nil +} + +func initNetworkController(config *Config) (libnetwork.NetworkController, error) { + controller, err := libnetwork.New() + if err != nil { + return nil, fmt.Errorf("error obtaining controller instance: %v", err) } - return d, nil + // Initialize default driver "null" + + if err := controller.ConfigureNetworkDriver("null", options.Generic{}); err != nil { + return nil, fmt.Errorf("Error initializing null driver: %v", err) + } + + // Initialize default network on "null" + if _, err := controller.NewNetwork("null", "none"); err != nil { + return nil, fmt.Errorf("Error creating default \"null\" network: %v", err) + } + + // Initialize default driver "host" + if err := controller.ConfigureNetworkDriver("host", options.Generic{}); err != nil { + return nil, fmt.Errorf("Error initializing host driver: %v", err) + } + + // Initialize default network on "host" + if _, err := controller.NewNetwork("host", "host"); err != nil { + return nil, fmt.Errorf("Error creating default \"host\" network: %v", err) + } + + // Initialize default driver "bridge" + option := options.Generic{ + "EnableIPForwarding": config.Bridge.EnableIPForward} + + if err := controller.ConfigureNetworkDriver("bridge", options.Generic{netlabel.GenericData: option}); err != nil { + return nil, fmt.Errorf("Error initializing bridge driver: %v", err) + } + + netOption := options.Generic{ + "BridgeName": config.Bridge.Iface, + "Mtu": config.Mtu, + "EnableIPTables": config.Bridge.EnableIPTables, + "EnableIPMasquerade": config.Bridge.EnableIPMasq, + "EnableICC": config.Bridge.InterContainerCommunication, + "EnableUserlandProxy": config.Bridge.EnableUserlandProxy, + } + + if config.Bridge.IP != "" { + ip, bipNet, err := net.ParseCIDR(config.Bridge.IP) + if err != nil { + return nil, err + } + + bipNet.IP = ip + netOption["AddressIPv4"] = bipNet + } + + if config.Bridge.FixedCIDR != "" { + _, fCIDR, err := net.ParseCIDR(config.Bridge.FixedCIDR) + if err != nil { + return nil, err + } + + netOption["FixedCIDR"] = fCIDR + } + + if config.Bridge.FixedCIDRv6 != "" { + _, fCIDRv6, err := net.ParseCIDR(config.Bridge.FixedCIDRv6) + if err != nil { + return nil, err + } + + netOption["FixedCIDRv6"] = fCIDRv6 + } + + // --ip processing + if config.Bridge.DefaultIP != nil { + netOption["DefaultBindingIP"] = config.Bridge.DefaultIP + } + + // Initialize default network on "bridge" with the same name + _, err = controller.NewNetwork("bridge", "bridge", + libnetwork.NetworkOptionGeneric(options.Generic{ + netlabel.GenericData: netOption, + netlabel.EnableIPv6: config.Bridge.EnableIPv6, + })) + if err != nil { + return nil, fmt.Errorf("Error creating default \"bridge\" network: %v", err) + } + + return controller, nil } func (daemon *Daemon) Shutdown() error { diff --git a/daemon/delete.go b/daemon/delete.go index 464193b283..f82f612ae3 100644 --- a/daemon/delete.go +++ b/daemon/delete.go @@ -35,13 +35,14 @@ func (daemon *Daemon) ContainerRm(name string, config *ContainerRmConfig) error } parentContainer, _ := daemon.Get(pe.ID()) + if err := daemon.ContainerGraph().Delete(name); err != nil { + return err + } + if parentContainer != nil { parentContainer.DisableLink(n) } - if err := daemon.ContainerGraph().Delete(name); err != nil { - return err - } return nil } diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index 554ad82c1b..eca77e921e 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -72,6 +72,7 @@ type Network struct { Interface *NetworkInterface `json:"interface"` // if interface is nil then networking is disabled Mtu int `json:"mtu"` ContainerID string `json:"container_id"` // id of the container to join network. + NamespacePath string `json:"namespace_path"` HostNetworking bool `json:"host_networking"` } diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 49db608743..4b5730a3f4 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -12,6 +12,7 @@ import ( "os/exec" "path" "path/filepath" + "runtime" "strconv" "strings" "sync" @@ -30,6 +31,7 @@ import ( "github.com/docker/libcontainer/system" "github.com/docker/libcontainer/user" "github.com/kr/pty" + "github.com/vishvananda/netns" ) const DriverName = "lxc" @@ -80,6 +82,41 @@ func (d *driver) Name() string { return fmt.Sprintf("%s-%s", DriverName, version) } +func setupNetNs(nsPath string) (*os.Process, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + origns, err := netns.Get() + if err != nil { + return nil, err + } + defer origns.Close() + + f, err := os.OpenFile(nsPath, os.O_RDONLY, 0) + if err != nil { + return nil, fmt.Errorf("failed to get network namespace %q: %v", nsPath, err) + } + defer f.Close() + + nsFD := f.Fd() + if err := netns.Set(netns.NsHandle(nsFD)); err != nil { + return nil, fmt.Errorf("failed to set network namespace %q: %v", nsPath, err) + } + defer netns.Set(origns) + + cmd := exec.Command("/bin/sh", "-c", "while true; do sleep 1; done") + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("failed to start netns process: %v", err) + } + + return cmd.Process, nil +} + +func killNetNsProc(proc *os.Process) { + proc.Kill() + proc.Wait() +} + func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) { var ( term execdriver.Terminal @@ -87,6 +124,10 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba dataPath = d.containerDir(c.ID) ) + if c.Network.NamespacePath == "" && c.Network.ContainerID == "" { + return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("empty namespace path for non-container network") + } + container, err := d.createContainer(c) if err != nil { return execdriver.ExitStatus{ExitCode: -1}, err @@ -136,10 +177,20 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba params = append(params, "-F") } + proc := &os.Process{} if c.Network.ContainerID != "" { params = append(params, "--share-net", c.Network.ContainerID, ) + } else { + proc, err = setupNetNs(c.Network.NamespacePath) + if err != nil { + return execdriver.ExitStatus{ExitCode: -1}, err + } + + pidStr := fmt.Sprintf("%d", proc.Pid) + params = append(params, + "--share-net", pidStr) } if c.Ipc != nil { if c.Ipc.ContainerID != "" { @@ -157,15 +208,6 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba "--", c.InitPath, ) - if c.Network.Interface != nil { - params = append(params, - "-g", c.Network.Interface.Gateway, - "-i", fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress, c.Network.Interface.IPPrefixLen), - ) - } - params = append(params, - "-mtu", strconv.Itoa(c.Network.Mtu), - ) if c.ProcessConfig.User != "" { params = append(params, "-u", c.ProcessConfig.User) @@ -214,10 +256,12 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba c.ProcessConfig.Args = append([]string{name}, arg...) if err := createDeviceNodes(c.Rootfs, c.AutoCreatedDevices); err != nil { + killNetNsProc(proc) return execdriver.ExitStatus{ExitCode: -1}, err } if err := c.ProcessConfig.Start(); err != nil { + killNetNsProc(proc) return execdriver.ExitStatus{ExitCode: -1}, err } @@ -245,8 +289,10 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba // Poll lxc for RUNNING status pid, err := d.waitForStart(c, waitLock) if err != nil { + killNetNsProc(proc) return terminate(err) } + killNetNsProc(proc) cgroupPaths, err := cgroupPaths(c.ID) if err != nil { diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index 2f88808a08..03c975c5f7 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -15,8 +15,7 @@ import ( "github.com/docker/libcontainer/label" ) -const LxcTemplate = ` -{{if .Network.Interface}} +/* {{if .Network.Interface}} # network configuration lxc.network.type = veth lxc.network.link = {{.Network.Interface.Bridge}} @@ -30,8 +29,10 @@ lxc.network.type = none lxc.network.type = empty lxc.network.flags = up lxc.network.mtu = {{.Network.Mtu}} -{{end}} +{{end}} */ +const LxcTemplate = ` +lxc.network.type = none # root filesystem {{$ROOTFS := .Rootfs}} lxc.rootfs = {{$ROOTFS}} @@ -145,6 +146,7 @@ lxc.network.ipv4.gateway = {{.Network.Interface.Gateway}} {{if .Network.Interface.MacAddress}} lxc.network.hwaddr = {{.Network.Interface.MacAddress}} {{end}} +{{end}} {{if .ProcessConfig.Env}} lxc.utsname = {{getHostname .ProcessConfig.Env}} {{end}} @@ -164,7 +166,6 @@ lxc.cap.drop = {{.}} {{end}} {{end}} {{end}} -{{end}} ` var LxcTemplateCompiled *template.Template diff --git a/daemon/execdriver/lxc/lxc_template_unit_test.go b/daemon/execdriver/lxc/lxc_template_unit_test.go index fcac6a3e57..904fa120a8 100644 --- a/daemon/execdriver/lxc/lxc_template_unit_test.go +++ b/daemon/execdriver/lxc/lxc_template_unit_test.go @@ -264,13 +264,8 @@ func TestCustomLxcConfigMisc(t *testing.T) { "lxc.cgroup.cpuset.cpus = 0,1", }, Network: &execdriver.Network{ - Mtu: 1500, - Interface: &execdriver.NetworkInterface{ - Gateway: "10.10.10.1", - IPAddress: "10.10.10.10", - IPPrefixLen: 24, - Bridge: "docker0", - }, + Mtu: 1500, + Interface: nil, }, ProcessConfig: processConfig, CapAdd: []string{"net_admin", "syslog"}, @@ -282,13 +277,6 @@ func TestCustomLxcConfigMisc(t *testing.T) { if err != nil { t.Fatal(err) } - // network - grepFile(t, p, "lxc.network.type = veth") - grepFile(t, p, "lxc.network.link = docker0") - grepFile(t, p, "lxc.network.name = eth0") - grepFile(t, p, "lxc.network.ipv4 = 10.10.10.10/24") - grepFile(t, p, "lxc.network.ipv4.gateway = 10.10.10.1") - grepFile(t, p, "lxc.network.flags = up") grepFile(t, p, "lxc.aa_profile = lxc-container-default-with-nesting") // hostname grepFile(t, p, "lxc.utsname = testhost") @@ -329,13 +317,8 @@ func TestCustomLxcConfigMiscOverride(t *testing.T) { "lxc.network.ipv4 = 172.0.0.1", }, Network: &execdriver.Network{ - Mtu: 1500, - Interface: &execdriver.NetworkInterface{ - Gateway: "10.10.10.1", - IPAddress: "10.10.10.10", - IPPrefixLen: 24, - Bridge: "docker0", - }, + Mtu: 1500, + Interface: nil, }, ProcessConfig: processConfig, CapAdd: []string{"NET_ADMIN", "SYSLOG"}, @@ -346,13 +329,6 @@ func TestCustomLxcConfigMiscOverride(t *testing.T) { if err != nil { t.Fatal(err) } - // network - grepFile(t, p, "lxc.network.type = veth") - grepFile(t, p, "lxc.network.link = docker0") - grepFile(t, p, "lxc.network.name = eth0") - grepFile(t, p, "lxc.network.ipv4 = 172.0.0.1") - grepFile(t, p, "lxc.network.ipv4.gateway = 10.10.10.1") - grepFile(t, p, "lxc.network.flags = up") // hostname grepFile(t, p, "lxc.utsname = testhost") diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index ff0da9a276..6d2a1e8c45 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -89,40 +89,6 @@ func generateIfaceName() (string, error) { } func (d *driver) createNetwork(container *configs.Config, c *execdriver.Command) error { - if c.Network.HostNetworking { - container.Namespaces.Remove(configs.NEWNET) - return nil - } - - container.Networks = []*configs.Network{ - { - Type: "loopback", - }, - } - - iName, err := generateIfaceName() - if err != nil { - return err - } - if c.Network.Interface != nil { - vethNetwork := configs.Network{ - Name: "eth0", - HostInterfaceName: iName, - Mtu: c.Network.Mtu, - Address: fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress, c.Network.Interface.IPPrefixLen), - MacAddress: c.Network.Interface.MacAddress, - Gateway: c.Network.Interface.Gateway, - Type: "veth", - Bridge: c.Network.Interface.Bridge, - HairpinMode: c.Network.Interface.HairpinMode, - } - if c.Network.Interface.GlobalIPv6Address != "" { - vethNetwork.IPv6Address = fmt.Sprintf("%s/%d", c.Network.Interface.GlobalIPv6Address, c.Network.Interface.GlobalIPv6PrefixLen) - vethNetwork.IPv6Gateway = c.Network.Interface.IPv6Gateway - } - container.Networks = append(container.Networks, &vethNetwork) - } - if c.Network.ContainerID != "" { d.Lock() active := d.activeContainers[c.Network.ContainerID] @@ -138,8 +104,14 @@ func (d *driver) createNetwork(container *configs.Config, c *execdriver.Command) } container.Namespaces.Add(configs.NEWNET, state.NamespacePaths[configs.NEWNET]) + return nil } + if c.Network.NamespacePath == "" { + return fmt.Errorf("network namespace path is empty") + } + + container.Namespaces.Add(configs.NEWNET, c.Network.NamespacePath) return nil } diff --git a/daemon/network/settings.go b/daemon/network/settings.go index 91d61160a6..ca60ff1980 100644 --- a/daemon/network/settings.go +++ b/daemon/network/settings.go @@ -2,18 +2,28 @@ package network import "github.com/docker/docker/nat" +type Address struct { + Addr string + PrefixLen int +} + type Settings struct { - IPAddress string - IPPrefixLen int - MacAddress string - LinkLocalIPv6Address string - LinkLocalIPv6PrefixLen int + Bridge string + EndpointID string + Gateway string GlobalIPv6Address string GlobalIPv6PrefixLen int - Gateway string + HairpinMode bool + IPAddress string + IPPrefixLen int IPv6Gateway string - Bridge string + LinkLocalIPv6Address string + LinkLocalIPv6PrefixLen int + MacAddress string + NetworkID string PortMapping map[string]map[string]string // Deprecated Ports nat.PortMap - HairpinMode bool + SandboxKey string + SecondaryIPAddresses []Address + SecondaryIPv6Addresses []Address } diff --git a/daemon/networkdriver/portmapper/proxy.go b/daemon/networkdriver/portmapper/proxy.go index 80b0027c70..894cfa7166 100644 --- a/daemon/networkdriver/portmapper/proxy.go +++ b/daemon/networkdriver/portmapper/proxy.go @@ -17,7 +17,7 @@ import ( "github.com/docker/docker/pkg/reexec" ) -const userlandProxyCommandName = "docker-proxy" +const userlandProxyCommandName = "docker-proxy-deprecated" func init() { reexec.Register(userlandProxyCommandName, execProxy) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 2097ad00a0..2a1ce918a5 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -15,6 +15,7 @@ import ( "strings" "time" + "github.com/docker/libnetwork/iptables" "github.com/docker/libtrust" "github.com/go-check/check" ) @@ -721,13 +722,49 @@ func (s *DockerDaemonSuite) TestDaemonICCLinkExpose(c *check.C) { c.Assert(matched, check.Equals, true, check.Commentf("iptables output should have contained %q, but was %q", regex, out)) - _, err = d.Cmd("run", "-d", "--expose", "4567", "--name", "icc1", "busybox", "nc", "-l", "-p", "4567") - c.Assert(err, check.IsNil) + out, err = d.Cmd("run", "-d", "--expose", "4567", "--name", "icc1", "busybox", "nc", "-l", "-p", "4567") + c.Assert(err, check.IsNil, check.Commentf(out)) out, err = d.Cmd("run", "--link", "icc1:icc1", "busybox", "nc", "icc1", "4567") c.Assert(err, check.IsNil, check.Commentf(out)) } +func (s *DockerDaemonSuite) TestDaemonLinksIpTablesRulesWhenLinkAndUnlink(c *check.C) { + bridgeName := "external-bridge" + bridgeIp := "192.169.1.1/24" + + out, err := createInterface(c, "bridge", bridgeName, bridgeIp) + c.Assert(err, check.IsNil, check.Commentf(out)) + defer deleteInterface(c, bridgeName) + + args := []string{"--bridge", bridgeName, "--icc=false"} + err = s.d.StartWithBusybox(args...) + c.Assert(err, check.IsNil) + defer s.d.Restart() + + _, err = s.d.Cmd("run", "-d", "--name", "child", "--publish", "8080:80", "busybox", "top") + c.Assert(err, check.IsNil) + _, err = s.d.Cmd("run", "-d", "--name", "parent", "--link", "child:http", "busybox", "top") + c.Assert(err, check.IsNil) + + childIP := s.d.findContainerIP("child") + parentIP := s.d.findContainerIP("parent") + + sourceRule := []string{"-i", bridgeName, "-o", bridgeName, "-p", "tcp", "-s", childIP, "--sport", "80", "-d", parentIP, "-j", "ACCEPT"} + destinationRule := []string{"-i", bridgeName, "-o", bridgeName, "-p", "tcp", "-s", parentIP, "--dport", "80", "-d", childIP, "-j", "ACCEPT"} + if !iptables.Exists("filter", "DOCKER", sourceRule...) || !iptables.Exists("filter", "DOCKER", destinationRule...) { + c.Fatal("Iptables rules not found") + } + + s.d.Cmd("rm", "--link", "parent/http") + if iptables.Exists("filter", "DOCKER", sourceRule...) || iptables.Exists("filter", "DOCKER", destinationRule...) { + c.Fatal("Iptables rules should be removed when unlink") + } + + s.d.Cmd("kill", "child") + s.d.Cmd("kill", "parent") +} + func (s *DockerDaemonSuite) TestDaemonUlimitDefaults(c *check.C) { testRequires(c, NativeExecDriver) diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index 80c89dcd84..1f7432a8bf 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -10,7 +10,6 @@ import ( "strings" "time" - "github.com/docker/docker/pkg/iptables" "github.com/go-check/check" ) @@ -110,31 +109,6 @@ func (s *DockerSuite) TestLinksPingLinkedContainersAfterRename(c *check.C) { } -func (s *DockerSuite) TestLinksIpTablesRulesWhenLinkAndUnlink(c *check.C) { - testRequires(c, SameHostDaemon) - - dockerCmd(c, "run", "-d", "--name", "child", "--publish", "8080:80", "busybox", "top") - dockerCmd(c, "run", "-d", "--name", "parent", "--link", "child:http", "busybox", "top") - - childIP := findContainerIP(c, "child") - parentIP := findContainerIP(c, "parent") - - sourceRule := []string{"-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", childIP, "--sport", "80", "-d", parentIP, "-j", "ACCEPT"} - destinationRule := []string{"-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", parentIP, "--dport", "80", "-d", childIP, "-j", "ACCEPT"} - if !iptables.Exists("filter", "DOCKER", sourceRule...) || !iptables.Exists("filter", "DOCKER", destinationRule...) { - c.Fatal("Iptables rules not found") - } - - dockerCmd(c, "rm", "--link", "parent/http") - if iptables.Exists("filter", "DOCKER", sourceRule...) || iptables.Exists("filter", "DOCKER", destinationRule...) { - c.Fatal("Iptables rules should be removed when unlink") - } - - dockerCmd(c, "kill", "child") - dockerCmd(c, "kill", "parent") - -} - func (s *DockerSuite) TestLinksInspectLinksStarted(c *check.C) { var ( expected = map[string]struct{}{"/container1:/testinspectlink/alias1": {}, "/container2:/testinspectlink/alias2": {}} diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 86d7a5e1cc..bfb50e418e 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -19,7 +19,7 @@ import ( "time" "github.com/docker/docker/nat" - "github.com/docker/docker/pkg/resolvconf" + "github.com/docker/libnetwork/resolvconf" "github.com/go-check/check" ) @@ -1459,14 +1459,11 @@ func (s *DockerSuite) TestRunDnsOptionsBasedOnHostResolvConf(c *check.C) { } } -// Test the file watch notifier on docker host's /etc/resolv.conf -// A go-routine is responsible for auto-updating containers which are -// stopped and have an unmodified copy of resolv.conf, as well as -// marking running containers as requiring an update on next restart -func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) { - // Because overlay doesn't support inotify properly, we need to skip - // this test if the docker daemon has Storage Driver == overlay - testRequires(c, SameHostDaemon, NotOverlay) +// Test if container resolv.conf gets updated the next time it restarts +// if host /etc/resolv.conf has changed. This only applies if the container +// uses the host's /etc/resolv.conf and does not have any dns options provided. +func (s *DockerSuite) TestRunResolvconfUpdate(c *check.C) { + testRequires(c, SameHostDaemon) tmpResolvConf := []byte("search pommesfrites.fr\nnameserver 12.34.56.78") tmpLocalhostResolvConf := []byte("nameserver 127.0.0.1") @@ -1492,7 +1489,7 @@ func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) { } }() - //1. test that a non-running container gets an updated resolv.conf + //1. test that a restarting container gets an updated resolv.conf cmd = exec.Command(dockerBinary, "run", "--name='first'", "busybox", "true") if _, err := runCommand(cmd); err != nil { c.Fatal(err) @@ -1508,17 +1505,26 @@ func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) { c.Fatal(err) } - time.Sleep(time.Second / 2) + // start the container again to pickup changes + cmd = exec.Command(dockerBinary, "start", "first") + if out, err := runCommand(cmd); err != nil { + c.Fatalf("Errored out %s, \nerror: %v", string(out), err) + } + // check for update in container containerResolv, err := readContainerFile(containerID1, "resolv.conf") if err != nil { c.Fatal(err) } if !bytes.Equal(containerResolv, bytesResolvConf) { - c.Fatalf("Stopped container does not have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv)) + c.Fatalf("Restarted container does not have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv)) } - //2. test that a non-running container does not receive resolv.conf updates + /* //make a change to resolv.conf (in this case replacing our tmp copy with orig copy) + if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { + c.Fatal(err) + } */ + //2. test that a restarting container does not receive resolv.conf updates // if it modified the container copy of the starting point resolv.conf cmd = exec.Command(dockerBinary, "run", "--name='second'", "busybox", "sh", "-c", "echo 'search mylittlepony.com' >>/etc/resolv.conf") if _, err = runCommand(cmd); err != nil { @@ -1528,24 +1534,26 @@ func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) { if err != nil { c.Fatal(err) } - containerResolvHashBefore, err := readContainerFile(containerID2, "resolv.conf.hash") - if err != nil { - c.Fatal(err) - } //make a change to resolv.conf (in this case replacing our tmp copy with orig copy) if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { c.Fatal(err) } - time.Sleep(time.Second / 2) - containerResolvHashAfter, err := readContainerFile(containerID2, "resolv.conf.hash") + // start the container again + cmd = exec.Command(dockerBinary, "start", "second") + if out, err := runCommand(cmd); err != nil { + c.Fatalf("Errored out %s, \nerror: %v", string(out), err) + } + + // check for update in container + containerResolv, err = readContainerFile(containerID2, "resolv.conf") if err != nil { c.Fatal(err) } - if !bytes.Equal(containerResolvHashBefore, containerResolvHashAfter) { - c.Fatalf("Stopped container with modified resolv.conf should not have been updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter) + if bytes.Equal(containerResolv, resolvConfSystem) { + c.Fatalf("Restarting a container after container updated resolv.conf should not pick up host changes; expected %q, got %q", string(containerResolv), string(resolvConfSystem)) } //3. test that a running container's resolv.conf is not modified while running @@ -1556,26 +1564,19 @@ func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) { } runningContainerID := strings.TrimSpace(out) - containerResolvHashBefore, err = readContainerFile(runningContainerID, "resolv.conf.hash") - if err != nil { - c.Fatal(err) - } - // replace resolv.conf if err := ioutil.WriteFile("/etc/resolv.conf", bytesResolvConf, 0644); err != nil { c.Fatal(err) } - // make sure the updater has time to run to validate we really aren't - // getting updated - time.Sleep(time.Second / 2) - containerResolvHashAfter, err = readContainerFile(runningContainerID, "resolv.conf.hash") + // check for update in container + containerResolv, err = readContainerFile(runningContainerID, "resolv.conf") if err != nil { c.Fatal(err) } - if !bytes.Equal(containerResolvHashBefore, containerResolvHashAfter) { - c.Fatalf("Running container's resolv.conf should not be updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter) + if bytes.Equal(containerResolv, bytesResolvConf) { + c.Fatalf("Running container should not have updated resolv.conf; expected %q, got %q", string(resolvConfSystem), string(containerResolv)) } //4. test that a running container's resolv.conf is updated upon restart @@ -1591,7 +1592,7 @@ func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) { c.Fatal(err) } if !bytes.Equal(containerResolv, bytesResolvConf) { - c.Fatalf("Restarted container should have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv)) + c.Fatalf("Restarted container should have updated resolv.conf; expected %q, got %q", string(bytesResolvConf), string(containerResolv)) } //5. test that additions of a localhost resolver are cleaned from @@ -1603,7 +1604,12 @@ func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) { c.Fatal(err) } - time.Sleep(time.Second / 2) + // start the container again to pickup changes + cmd = exec.Command(dockerBinary, "start", "first") + if out, err := runCommand(cmd); err != nil { + c.Fatalf("Errored out %s, \nerror: %v", string(out), err) + } + // our first exited container ID should have been updated, but with default DNS // after the cleanup of resolv.conf found only a localhost nameserver: containerResolv, err = readContainerFile(containerID1, "resolv.conf") @@ -1645,7 +1651,12 @@ func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) { c.Fatal(err) } - time.Sleep(time.Second / 2) + // start the container again to pickup changes + cmd = exec.Command(dockerBinary, "start", "third") + if out, err := runCommand(cmd); err != nil { + c.Fatalf("Errored out %s, \nerror: %v", string(out), err) + } + // check for update in container containerResolv, err = readContainerFile(containerID3, "resolv.conf") if err != nil { diff --git a/links/links.go b/links/links.go index 58fec95f56..a756c8b0e5 100644 --- a/links/links.go +++ b/links/links.go @@ -5,9 +5,7 @@ import ( "path" "strings" - "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/nat" - "github.com/docker/docker/pkg/iptables" ) type Link struct { @@ -140,26 +138,10 @@ func (l *Link) getDefaultPort() *nat.Port { } func (l *Link) Enable() error { - // -A == iptables append flag - if err := l.toggle("-A", false); err != nil { - return err - } - // call this on Firewalld reload - iptables.OnReloaded(func() { l.toggle("-A", false) }) l.IsEnabled = true return nil } func (l *Link) Disable() { - // We do not care about errors here because the link may not - // exist in iptables - // -D == iptables delete flag - l.toggle("-D", true) - // call this on Firewalld reload - iptables.OnReloaded(func() { l.toggle("-D", true) }) l.IsEnabled = false } - -func (l *Link) toggle(action string, ignoreErrors bool) error { - return bridge.LinkContainers(action, l.ParentIP, l.ChildIP, l.Ports, ignoreErrors) -} diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 9fdb6e0fd5..1418dea4a8 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -18,7 +18,7 @@ type NetworkMode string // IsPrivate indicates whether container use it's private network stack func (n NetworkMode) IsPrivate() bool { - return !(n.IsHost() || n.IsContainer() || n.IsNone()) + return !(n.IsHost() || n.IsContainer()) } func (n NetworkMode) IsBridge() bool {