Docker integration with libnetwork

- Updated Dockerfile to satisfy libnetwork GOPATH requirements.
    - Reworked daemon to allocate network resources using libnetwork.
    - Reworked remove link code to also update network resources in libnetwork.
    - Adjusted the exec driver command population to reflect libnetwork design.
    - Adjusted the exec driver create command steps.
    - Updated a few test cases to reflect the change in design.
    - Removed the dns setup code from docker as resolv.conf is entirely managed
      in libnetwork.
    - Integrated with lxc exec driver.

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
This commit is contained in:
Jana Radhakrishnan 2015-05-06 22:39:29 +00:00
parent 272f8cd4bc
commit d18919e304
20 changed files with 746 additions and 622 deletions

View File

@ -9,6 +9,7 @@ DOCKER_ENVS := \
-e DOCKER_EXECDRIVER \ -e DOCKER_EXECDRIVER \
-e DOCKER_GRAPHDRIVER \ -e DOCKER_GRAPHDRIVER \
-e DOCKER_STORAGE_OPTS \ -e DOCKER_STORAGE_OPTS \
-e DOCKER_USERLANDPROXY \
-e TESTDIRS \ -e TESTDIRS \
-e TESTFLAGS \ -e TESTFLAGS \
-e TIMEOUT -e TIMEOUT

View File

@ -12,6 +12,7 @@ import (
"github.com/docker/docker/pkg/resolvconf/dns" "github.com/docker/docker/pkg/resolvconf/dns"
"github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/signal"
"github.com/docker/docker/runconfig" "github.com/docker/docker/runconfig"
"github.com/docker/libnetwork/resolvconf/dns"
) )
func (cid *cidFile) Close() error { func (cid *cidFile) Close() error {

View File

@ -23,7 +23,6 @@ import (
"github.com/docker/docker/builder" "github.com/docker/docker/builder"
"github.com/docker/docker/cliconfig" "github.com/docker/docker/cliconfig"
"github.com/docker/docker/daemon" "github.com/docker/docker/daemon"
"github.com/docker/docker/daemon/networkdriver/bridge"
"github.com/docker/docker/graph" "github.com/docker/docker/graph"
"github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/jsonmessage"
@ -36,6 +35,7 @@ import (
"github.com/docker/docker/pkg/version" "github.com/docker/docker/pkg/version"
"github.com/docker/docker/runconfig" "github.com/docker/docker/runconfig"
"github.com/docker/docker/utils" "github.com/docker/docker/utils"
"github.com/docker/libnetwork/portallocator"
) )
type ServerConfig struct { type ServerConfig struct {
@ -1548,8 +1548,9 @@ func allocateDaemonPort(addr string) error {
return fmt.Errorf("failed to lookup %s address in host specification", host) return fmt.Errorf("failed to lookup %s address in host specification", host)
} }
pa := portallocator.Get()
for _, hostIP := range hostIPs { 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) return fmt.Errorf("failed to allocate daemon listening port %d (err: %v)", intPort, err)
} }
} }

View File

@ -1,8 +1,8 @@
package daemon package daemon
import ( import (
"github.com/docker/docker/daemon/networkdriver" "net"
"github.com/docker/docker/daemon/networkdriver/bridge"
"github.com/docker/docker/opts" "github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag" flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/runconfig" "github.com/docker/docker/runconfig"
@ -16,8 +16,9 @@ const (
// CommonConfig defines the configuration of a docker daemon which are // CommonConfig defines the configuration of a docker daemon which are
// common across platforms. // common across platforms.
type CommonConfig struct { type CommonConfig struct {
AutoRestart bool AutoRestart bool
Bridge bridge.Config // Bridge holds bridge network specific configuration.
Bridge bridgeConfig
Context map[string][]string Context map[string][]string
CorsHeaders string CorsHeaders string
DisableNetwork bool DisableNetwork bool
@ -35,6 +36,24 @@ type CommonConfig struct {
TrustKeyPath string 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 // InstallCommonFlags adds command-line options to the top-level flag parser for
// the current process. // the current process.
// Subsequent calls to `flag.Parse` will populate config with values parsed // 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.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.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.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.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.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.EnableIPMasq, []string{"-ip-masq"}, true, "Enable IP masquerading")
flag.BoolVar(&config.Bridge.EnableIPv6, []string{"-ipv6"}, false, "Enable IPv6 networking") 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.IP, []string{"#bip", "-bip"}, "", "Specify network bridge IP")
flag.StringVar(&config.Bridge.Iface, []string{"b", "-bridge"}, "", "Attach containers to a network bridge") 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.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.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") 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"? // FIXME: why the inconsistency between "hosts" and "sockets"?
opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use") opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use")
opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "DNS search domains 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") 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
}

View File

@ -252,18 +252,12 @@ func (container *Container) Start() (err error) {
} }
}() }()
if err := container.setupContainerDns(); err != nil {
return err
}
if err := container.Mount(); err != nil { if err := container.Mount(); err != nil {
return err return err
} }
if err := container.initializeNetworking(); err != nil { if err := container.initializeNetworking(); err != nil {
return err return err
} }
if err := container.updateParentsHosts(); err != nil {
return err
}
container.verifyDaemonSettings() container.verifyDaemonSettings()
if err := container.prepareVolumes(); err != nil { if err := container.prepareVolumes(); err != nil {
return err return err

View File

@ -3,11 +3,13 @@
package daemon package daemon
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"syscall" "syscall"
"time" "time"
@ -15,20 +17,21 @@ import (
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/daemon/network" "github.com/docker/docker/daemon/network"
"github.com/docker/docker/daemon/networkdriver/bridge"
"github.com/docker/docker/links" "github.com/docker/docker/links"
"github.com/docker/docker/nat" "github.com/docker/docker/nat"
"github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/directory" "github.com/docker/docker/pkg/directory"
"github.com/docker/docker/pkg/etchosts"
"github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/resolvconf"
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/ulimit" "github.com/docker/docker/pkg/ulimit"
"github.com/docker/docker/runconfig" "github.com/docker/docker/runconfig"
"github.com/docker/docker/utils" "github.com/docker/docker/utils"
"github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/devices" "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" 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 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) { func (container *Container) setupLinkedContainers() ([]string, error) {
var ( var (
env []string env []string
@ -360,39 +190,16 @@ func getDevicesFromPath(deviceMapping runconfig.DeviceMapping) (devs []*configs.
func populateCommand(c *Container, env []string) error { func populateCommand(c *Container, env []string) error {
en := &execdriver.Network{ en := &execdriver.Network{
Mtu: c.daemon.config.Mtu, NamespacePath: c.NetworkSettings.SandboxKey,
Interface: nil,
} }
parts := strings.SplitN(string(c.hostConfig.NetworkMode), ":", 2) parts := strings.SplitN(string(c.hostConfig.NetworkMode), ":", 2)
switch parts[0] { if parts[0] == "container" {
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":
nc, err := c.getNetworkedContainer() nc, err := c.getNetworkedContainer()
if err != nil { if err != nil {
return err return err
} }
en.ContainerID = nc.ID en.ContainerID = nc.ID
default:
return fmt.Errorf("invalid network mode: %s", c.hostConfig.NetworkMode)
} }
ipc := &execdriver.Ipc{} ipc := &execdriver.Ipc{}
@ -537,40 +344,318 @@ func (container *Container) GetSize() (int64, int64) {
return sizeRw, sizeRootfs return sizeRw, sizeRootfs
} }
func (container *Container) AllocateNetwork() error { func (container *Container) buildHostnameFile() error {
mode := container.hostConfig.NetworkMode hostnamePath, err := container.GetRootResourcePath("hostname")
if container.Config.NetworkDisabled || !mode.IsPrivate() { 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 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 { if err != nil {
return err return err
} }
// Error handling: At this point, the interface is allocated so we have to networkSettings, err = container.buildEndpointInfo(n, ep, networkSettings)
// make sure that it is always released in case of error, otherwise we if err != nil {
// might leak resources. return err
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
}
} }
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 ( var (
portSpecs = make(nat.PortSet) portSpecs = make(nat.PortSet)
bindings = make(nat.PortMap) 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 { if container.Config.ExposedPorts != nil {
portSpecs = container.Config.ExposedPorts portSpecs = container.Config.ExposedPorts
} }
@ -597,52 +682,99 @@ func (container *Container) AllocateNetwork() error {
} }
nat.SortPortMap(ports, bindings) nat.SortPortMap(ports, bindings)
for _, port := range ports { for _, port := range ports {
if err = container.allocatePort(port, bindings); err != nil { expose := netutils.TransportPort{}
bridge.Release(container.ID) expose.Proto = netutils.ParseProtocol(port.Proto())
return err 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 createOptions = append(createOptions,
container.NetworkSettings = networkSettings 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 return nil
} }
func (container *Container) initializeNetworking() error { func (container *Container) initializeNetworking() error {
var err 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) // Make sure NetworkMode has an acceptable value before
if len(parts) > 1 { // initializing networking.
container.Config.Hostname = parts[0] if container.hostConfig.NetworkMode == runconfig.NetworkMode("") {
container.Config.Domainname = parts[1] container.hostConfig.NetworkMode = runconfig.NetworkMode("bridge")
}
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)
} }
if container.hostConfig.NetworkMode.IsContainer() { if container.hostConfig.NetworkMode.IsContainer() {
// we need to get the hosts files from the container to join // we need to get the hosts files from the container to join
nc, err := container.getNetworkedContainer() nc, err := container.getNetworkedContainer()
@ -656,14 +788,30 @@ func (container *Container) initializeNetworking() error {
container.Config.Domainname = nc.Config.Domainname container.Config.Domainname = nc.Config.Domainname
return nil return nil
} }
if container.daemon.config.DisableNetwork { if container.daemon.config.DisableNetwork {
container.Config.NetworkDisabled = true 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 { if err := container.AllocateNetwork(); err != nil {
return err return err
} }
return container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress)
return container.buildHostnameFile()
} }
// Make sure the config is compatible with the current kernel // Make sure the config is compatible with the current kernel
@ -701,62 +849,6 @@ func (container *Container) ExportRw() (archive.Archive, error) {
nil 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) { func (container *Container) getIpcContainer() (*Container, error) {
containerID := container.hostConfig.IpcMode.Container() containerID := container.hostConfig.IpcMode.Container()
c, err := container.daemon.Get(containerID) c, err := container.daemon.Get(containerID)
@ -795,23 +887,6 @@ func (container *Container) setupWorkingDirectory() error {
return nil 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) { func (container *Container) getNetworkedContainer() (*Container, error) {
parts := strings.SplitN(string(container.hostConfig.NetworkMode), ":", 2) parts := strings.SplitN(string(container.hostConfig.NetworkMode), ":", 2)
switch parts[0] { switch parts[0] {
@ -836,36 +911,33 @@ func (container *Container) getNetworkedContainer() (*Container, error) {
} }
func (container *Container) ReleaseNetwork() { func (container *Container) ReleaseNetwork() {
if container.Config.NetworkDisabled || !container.hostConfig.NetworkMode.IsPrivate() { if container.hostConfig.NetworkMode.IsContainer() {
return 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{} 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) { func disableAllActiveLinks(container *Container) {
if container.activeLinks != nil { if container.activeLinks != nil {
for _, link := range container.activeLinks { for _, link := range container.activeLinks {
@ -878,6 +950,10 @@ func (container *Container) DisableLink(name string) {
if container.activeLinks != nil { if container.activeLinks != nil {
if link, exists := container.activeLinks[name]; exists { if link, exists := container.activeLinks[name]; exists {
link.Disable() link.Disable()
delete(container.activeLinks, name)
if err := container.UpdateNetwork(); err != nil {
logrus.Debugf("Could not update network to remove link: %v", err)
}
} else { } else {
logrus.Debugf("Could not find active link for %s", name) logrus.Debugf("Could not find active link for %s", name)
} }

View File

@ -1,10 +1,10 @@
package daemon package daemon
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -15,6 +15,9 @@ import (
"time" "time"
"github.com/docker/libcontainer/label" "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/Sirupsen/logrus"
"github.com/docker/docker/api" "github.com/docker/docker/api"
@ -26,7 +29,6 @@ import (
_ "github.com/docker/docker/daemon/graphdriver/vfs" _ "github.com/docker/docker/daemon/graphdriver/vfs"
"github.com/docker/docker/daemon/logger" "github.com/docker/docker/daemon/logger"
"github.com/docker/docker/daemon/network" "github.com/docker/docker/daemon/network"
"github.com/docker/docker/daemon/networkdriver/bridge"
"github.com/docker/docker/graph" "github.com/docker/docker/graph"
"github.com/docker/docker/image" "github.com/docker/docker/image"
"github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/archive"
@ -37,7 +39,6 @@ import (
"github.com/docker/docker/pkg/namesgenerator" "github.com/docker/docker/pkg/namesgenerator"
"github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/parsers/kernel" "github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/resolvconf"
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/sysinfo" "github.com/docker/docker/pkg/sysinfo"
"github.com/docker/docker/pkg/truncindex" "github.com/docker/docker/pkg/truncindex"
@ -46,8 +47,6 @@ import (
"github.com/docker/docker/trust" "github.com/docker/docker/trust"
"github.com/docker/docker/utils" "github.com/docker/docker/utils"
"github.com/docker/docker/volumes" "github.com/docker/docker/volumes"
"github.com/go-fsnotify/fsnotify"
) )
var ( var (
@ -109,6 +108,7 @@ type Daemon struct {
defaultLogConfig runconfig.LogConfig defaultLogConfig runconfig.LogConfig
RegistryService *registry.Service RegistryService *registry.Service
EventsService *events.Events EventsService *events.Events
netController libnetwork.NetworkController
} }
// Get looks for a container using the provided information, which could be // Get looks for a container using the provided information, which could be
@ -349,61 +349,6 @@ func (daemon *Daemon) restore() error {
return nil 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 { func (daemon *Daemon) checkDeprecatedExpose(config *runconfig.Config) bool {
if config != nil { if config != nil {
if config.PortSpecs != 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) { func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemon, err error) {
if config.Mtu == 0 {
config.Mtu = getDefaultNetworkMtu()
}
// Check for mutually incompatible config options // Check for mutually incompatible config options
if config.Bridge.Iface != "" && config.Bridge.IP != "" { if config.Bridge.Iface != "" && config.Bridge.IP != "" {
return nil, fmt.Errorf("You specified -b & --bip, mutually exclusive options. Please specify only one.") 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.") 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 { if !config.Bridge.EnableIPTables && config.Bridge.EnableIPMasq {
config.Bridge.EnableIpMasq = false config.Bridge.EnableIPMasq = false
} }
config.DisableNetwork = config.Bridge.Iface == disableNetworkBridge config.DisableNetwork = config.Bridge.Iface == disableNetworkBridge
@ -882,8 +824,9 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
} }
if !config.DisableNetwork { if !config.DisableNetwork {
if err := bridge.InitDriver(&config.Bridge); err != nil { d.netController, err = initNetworkController(config)
return nil, fmt.Errorf("Error initializing Bridge: %v", err) 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 return nil, err
} }
// set up filesystem watch on resolv.conf for network changes return d, nil
if err := d.setupResolvconfWatcher(); err != nil { }
return nil, err
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 { func (daemon *Daemon) Shutdown() error {

View File

@ -35,13 +35,14 @@ func (daemon *Daemon) ContainerRm(name string, config *ContainerRmConfig) error
} }
parentContainer, _ := daemon.Get(pe.ID()) parentContainer, _ := daemon.Get(pe.ID())
if err := daemon.ContainerGraph().Delete(name); err != nil {
return err
}
if parentContainer != nil { if parentContainer != nil {
parentContainer.DisableLink(n) parentContainer.DisableLink(n)
} }
if err := daemon.ContainerGraph().Delete(name); err != nil {
return err
}
return nil return nil
} }

View File

@ -72,6 +72,7 @@ type Network struct {
Interface *NetworkInterface `json:"interface"` // if interface is nil then networking is disabled Interface *NetworkInterface `json:"interface"` // if interface is nil then networking is disabled
Mtu int `json:"mtu"` Mtu int `json:"mtu"`
ContainerID string `json:"container_id"` // id of the container to join network. ContainerID string `json:"container_id"` // id of the container to join network.
NamespacePath string `json:"namespace_path"`
HostNetworking bool `json:"host_networking"` HostNetworking bool `json:"host_networking"`
} }

View File

@ -12,6 +12,7 @@ import (
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"runtime"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -30,6 +31,7 @@ import (
"github.com/docker/libcontainer/system" "github.com/docker/libcontainer/system"
"github.com/docker/libcontainer/user" "github.com/docker/libcontainer/user"
"github.com/kr/pty" "github.com/kr/pty"
"github.com/vishvananda/netns"
) )
const DriverName = "lxc" const DriverName = "lxc"
@ -80,6 +82,41 @@ func (d *driver) Name() string {
return fmt.Sprintf("%s-%s", DriverName, version) 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) { func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
var ( var (
term execdriver.Terminal term execdriver.Terminal
@ -87,6 +124,10 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
dataPath = d.containerDir(c.ID) 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) container, err := d.createContainer(c)
if err != nil { if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err 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") params = append(params, "-F")
} }
proc := &os.Process{}
if c.Network.ContainerID != "" { if c.Network.ContainerID != "" {
params = append(params, params = append(params,
"--share-net", c.Network.ContainerID, "--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 != nil {
if c.Ipc.ContainerID != "" { if c.Ipc.ContainerID != "" {
@ -157,15 +208,6 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
"--", "--",
c.InitPath, 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 != "" { if c.ProcessConfig.User != "" {
params = append(params, "-u", 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...) c.ProcessConfig.Args = append([]string{name}, arg...)
if err := createDeviceNodes(c.Rootfs, c.AutoCreatedDevices); err != nil { if err := createDeviceNodes(c.Rootfs, c.AutoCreatedDevices); err != nil {
killNetNsProc(proc)
return execdriver.ExitStatus{ExitCode: -1}, err return execdriver.ExitStatus{ExitCode: -1}, err
} }
if err := c.ProcessConfig.Start(); err != nil { if err := c.ProcessConfig.Start(); err != nil {
killNetNsProc(proc)
return execdriver.ExitStatus{ExitCode: -1}, err 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 // Poll lxc for RUNNING status
pid, err := d.waitForStart(c, waitLock) pid, err := d.waitForStart(c, waitLock)
if err != nil { if err != nil {
killNetNsProc(proc)
return terminate(err) return terminate(err)
} }
killNetNsProc(proc)
cgroupPaths, err := cgroupPaths(c.ID) cgroupPaths, err := cgroupPaths(c.ID)
if err != nil { if err != nil {

View File

@ -15,8 +15,7 @@ import (
"github.com/docker/libcontainer/label" "github.com/docker/libcontainer/label"
) )
const LxcTemplate = ` /* {{if .Network.Interface}}
{{if .Network.Interface}}
# network configuration # network configuration
lxc.network.type = veth lxc.network.type = veth
lxc.network.link = {{.Network.Interface.Bridge}} lxc.network.link = {{.Network.Interface.Bridge}}
@ -30,8 +29,10 @@ lxc.network.type = none
lxc.network.type = empty lxc.network.type = empty
lxc.network.flags = up lxc.network.flags = up
lxc.network.mtu = {{.Network.Mtu}} lxc.network.mtu = {{.Network.Mtu}}
{{end}} {{end}} */
const LxcTemplate = `
lxc.network.type = none
# root filesystem # root filesystem
{{$ROOTFS := .Rootfs}} {{$ROOTFS := .Rootfs}}
lxc.rootfs = {{$ROOTFS}} lxc.rootfs = {{$ROOTFS}}
@ -145,6 +146,7 @@ lxc.network.ipv4.gateway = {{.Network.Interface.Gateway}}
{{if .Network.Interface.MacAddress}} {{if .Network.Interface.MacAddress}}
lxc.network.hwaddr = {{.Network.Interface.MacAddress}} lxc.network.hwaddr = {{.Network.Interface.MacAddress}}
{{end}} {{end}}
{{end}}
{{if .ProcessConfig.Env}} {{if .ProcessConfig.Env}}
lxc.utsname = {{getHostname .ProcessConfig.Env}} lxc.utsname = {{getHostname .ProcessConfig.Env}}
{{end}} {{end}}
@ -164,7 +166,6 @@ lxc.cap.drop = {{.}}
{{end}} {{end}}
{{end}} {{end}}
{{end}} {{end}}
{{end}}
` `
var LxcTemplateCompiled *template.Template var LxcTemplateCompiled *template.Template

View File

@ -264,13 +264,8 @@ func TestCustomLxcConfigMisc(t *testing.T) {
"lxc.cgroup.cpuset.cpus = 0,1", "lxc.cgroup.cpuset.cpus = 0,1",
}, },
Network: &execdriver.Network{ Network: &execdriver.Network{
Mtu: 1500, Mtu: 1500,
Interface: &execdriver.NetworkInterface{ Interface: nil,
Gateway: "10.10.10.1",
IPAddress: "10.10.10.10",
IPPrefixLen: 24,
Bridge: "docker0",
},
}, },
ProcessConfig: processConfig, ProcessConfig: processConfig,
CapAdd: []string{"net_admin", "syslog"}, CapAdd: []string{"net_admin", "syslog"},
@ -282,13 +277,6 @@ func TestCustomLxcConfigMisc(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) 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") grepFile(t, p, "lxc.aa_profile = lxc-container-default-with-nesting")
// hostname // hostname
grepFile(t, p, "lxc.utsname = testhost") grepFile(t, p, "lxc.utsname = testhost")
@ -329,13 +317,8 @@ func TestCustomLxcConfigMiscOverride(t *testing.T) {
"lxc.network.ipv4 = 172.0.0.1", "lxc.network.ipv4 = 172.0.0.1",
}, },
Network: &execdriver.Network{ Network: &execdriver.Network{
Mtu: 1500, Mtu: 1500,
Interface: &execdriver.NetworkInterface{ Interface: nil,
Gateway: "10.10.10.1",
IPAddress: "10.10.10.10",
IPPrefixLen: 24,
Bridge: "docker0",
},
}, },
ProcessConfig: processConfig, ProcessConfig: processConfig,
CapAdd: []string{"NET_ADMIN", "SYSLOG"}, CapAdd: []string{"NET_ADMIN", "SYSLOG"},
@ -346,13 +329,6 @@ func TestCustomLxcConfigMiscOverride(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) 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 // hostname
grepFile(t, p, "lxc.utsname = testhost") grepFile(t, p, "lxc.utsname = testhost")

View File

@ -89,40 +89,6 @@ func generateIfaceName() (string, error) {
} }
func (d *driver) createNetwork(container *configs.Config, c *execdriver.Command) 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 != "" { if c.Network.ContainerID != "" {
d.Lock() d.Lock()
active := d.activeContainers[c.Network.ContainerID] 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]) 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 return nil
} }

View File

@ -2,18 +2,28 @@ package network
import "github.com/docker/docker/nat" import "github.com/docker/docker/nat"
type Address struct {
Addr string
PrefixLen int
}
type Settings struct { type Settings struct {
IPAddress string Bridge string
IPPrefixLen int EndpointID string
MacAddress string Gateway string
LinkLocalIPv6Address string
LinkLocalIPv6PrefixLen int
GlobalIPv6Address string GlobalIPv6Address string
GlobalIPv6PrefixLen int GlobalIPv6PrefixLen int
Gateway string HairpinMode bool
IPAddress string
IPPrefixLen int
IPv6Gateway string IPv6Gateway string
Bridge string LinkLocalIPv6Address string
LinkLocalIPv6PrefixLen int
MacAddress string
NetworkID string
PortMapping map[string]map[string]string // Deprecated PortMapping map[string]map[string]string // Deprecated
Ports nat.PortMap Ports nat.PortMap
HairpinMode bool SandboxKey string
SecondaryIPAddresses []Address
SecondaryIPv6Addresses []Address
} }

View File

@ -17,7 +17,7 @@ import (
"github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/reexec"
) )
const userlandProxyCommandName = "docker-proxy" const userlandProxyCommandName = "docker-proxy-deprecated"
func init() { func init() {
reexec.Register(userlandProxyCommandName, execProxy) reexec.Register(userlandProxyCommandName, execProxy)

View File

@ -15,6 +15,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/docker/libnetwork/iptables"
"github.com/docker/libtrust" "github.com/docker/libtrust"
"github.com/go-check/check" "github.com/go-check/check"
) )
@ -721,13 +722,49 @@ func (s *DockerDaemonSuite) TestDaemonICCLinkExpose(c *check.C) {
c.Assert(matched, check.Equals, true, c.Assert(matched, check.Equals, true,
check.Commentf("iptables output should have contained %q, but was %q", regex, out)) 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") out, err = d.Cmd("run", "-d", "--expose", "4567", "--name", "icc1", "busybox", "nc", "-l", "-p", "4567")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil, check.Commentf(out))
out, err = d.Cmd("run", "--link", "icc1:icc1", "busybox", "nc", "icc1", "4567") out, err = d.Cmd("run", "--link", "icc1:icc1", "busybox", "nc", "icc1", "4567")
c.Assert(err, check.IsNil, check.Commentf(out)) 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) { func (s *DockerDaemonSuite) TestDaemonUlimitDefaults(c *check.C) {
testRequires(c, NativeExecDriver) testRequires(c, NativeExecDriver)

View File

@ -10,7 +10,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/docker/docker/pkg/iptables"
"github.com/go-check/check" "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) { func (s *DockerSuite) TestLinksInspectLinksStarted(c *check.C) {
var ( var (
expected = map[string]struct{}{"/container1:/testinspectlink/alias1": {}, "/container2:/testinspectlink/alias2": {}} expected = map[string]struct{}{"/container1:/testinspectlink/alias1": {}, "/container2:/testinspectlink/alias2": {}}

View File

@ -19,7 +19,7 @@ import (
"time" "time"
"github.com/docker/docker/nat" "github.com/docker/docker/nat"
"github.com/docker/docker/pkg/resolvconf" "github.com/docker/libnetwork/resolvconf"
"github.com/go-check/check" "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 // Test if container resolv.conf gets updated the next time it restarts
// A go-routine is responsible for auto-updating containers which are // if host /etc/resolv.conf has changed. This only applies if the container
// stopped and have an unmodified copy of resolv.conf, as well as // uses the host's /etc/resolv.conf and does not have any dns options provided.
// marking running containers as requiring an update on next restart func (s *DockerSuite) TestRunResolvconfUpdate(c *check.C) {
func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) { testRequires(c, SameHostDaemon)
// 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)
tmpResolvConf := []byte("search pommesfrites.fr\nnameserver 12.34.56.78") tmpResolvConf := []byte("search pommesfrites.fr\nnameserver 12.34.56.78")
tmpLocalhostResolvConf := []byte("nameserver 127.0.0.1") 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") cmd = exec.Command(dockerBinary, "run", "--name='first'", "busybox", "true")
if _, err := runCommand(cmd); err != nil { if _, err := runCommand(cmd); err != nil {
c.Fatal(err) c.Fatal(err)
@ -1508,17 +1505,26 @@ func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) {
c.Fatal(err) 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 // check for update in container
containerResolv, err := readContainerFile(containerID1, "resolv.conf") containerResolv, err := readContainerFile(containerID1, "resolv.conf")
if err != nil { if err != nil {
c.Fatal(err) c.Fatal(err)
} }
if !bytes.Equal(containerResolv, bytesResolvConf) { 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 // 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") cmd = exec.Command(dockerBinary, "run", "--name='second'", "busybox", "sh", "-c", "echo 'search mylittlepony.com' >>/etc/resolv.conf")
if _, err = runCommand(cmd); err != nil { if _, err = runCommand(cmd); err != nil {
@ -1528,24 +1534,26 @@ func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) {
if err != nil { if err != nil {
c.Fatal(err) 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) //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 { if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
c.Fatal(err) c.Fatal(err)
} }
time.Sleep(time.Second / 2) // start the container again
containerResolvHashAfter, err := readContainerFile(containerID2, "resolv.conf.hash") 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 { if err != nil {
c.Fatal(err) c.Fatal(err)
} }
if !bytes.Equal(containerResolvHashBefore, containerResolvHashAfter) { if bytes.Equal(containerResolv, resolvConfSystem) {
c.Fatalf("Stopped container with modified resolv.conf should not have been updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter) 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 //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) runningContainerID := strings.TrimSpace(out)
containerResolvHashBefore, err = readContainerFile(runningContainerID, "resolv.conf.hash")
if err != nil {
c.Fatal(err)
}
// replace resolv.conf // replace resolv.conf
if err := ioutil.WriteFile("/etc/resolv.conf", bytesResolvConf, 0644); err != nil { if err := ioutil.WriteFile("/etc/resolv.conf", bytesResolvConf, 0644); err != nil {
c.Fatal(err) c.Fatal(err)
} }
// make sure the updater has time to run to validate we really aren't // check for update in container
// getting updated containerResolv, err = readContainerFile(runningContainerID, "resolv.conf")
time.Sleep(time.Second / 2)
containerResolvHashAfter, err = readContainerFile(runningContainerID, "resolv.conf.hash")
if err != nil { if err != nil {
c.Fatal(err) c.Fatal(err)
} }
if !bytes.Equal(containerResolvHashBefore, containerResolvHashAfter) { if bytes.Equal(containerResolv, bytesResolvConf) {
c.Fatalf("Running container's resolv.conf should not be updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter) 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 //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) c.Fatal(err)
} }
if !bytes.Equal(containerResolv, bytesResolvConf) { 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 //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) 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 // our first exited container ID should have been updated, but with default DNS
// after the cleanup of resolv.conf found only a localhost nameserver: // after the cleanup of resolv.conf found only a localhost nameserver:
containerResolv, err = readContainerFile(containerID1, "resolv.conf") containerResolv, err = readContainerFile(containerID1, "resolv.conf")
@ -1645,7 +1651,12 @@ func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) {
c.Fatal(err) 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 // check for update in container
containerResolv, err = readContainerFile(containerID3, "resolv.conf") containerResolv, err = readContainerFile(containerID3, "resolv.conf")
if err != nil { if err != nil {

View File

@ -5,9 +5,7 @@ import (
"path" "path"
"strings" "strings"
"github.com/docker/docker/daemon/networkdriver/bridge"
"github.com/docker/docker/nat" "github.com/docker/docker/nat"
"github.com/docker/docker/pkg/iptables"
) )
type Link struct { type Link struct {
@ -140,26 +138,10 @@ func (l *Link) getDefaultPort() *nat.Port {
} }
func (l *Link) Enable() error { 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 l.IsEnabled = true
return nil return nil
} }
func (l *Link) Disable() { 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 l.IsEnabled = false
} }
func (l *Link) toggle(action string, ignoreErrors bool) error {
return bridge.LinkContainers(action, l.ParentIP, l.ChildIP, l.Ports, ignoreErrors)
}

View File

@ -18,7 +18,7 @@ type NetworkMode string
// IsPrivate indicates whether container use it's private network stack // IsPrivate indicates whether container use it's private network stack
func (n NetworkMode) IsPrivate() bool { func (n NetworkMode) IsPrivate() bool {
return !(n.IsHost() || n.IsContainer() || n.IsNone()) return !(n.IsHost() || n.IsContainer())
} }
func (n NetworkMode) IsBridge() bool { func (n NetworkMode) IsBridge() bool {