Selaa lähdekoodia

Merge pull request #19001 from aboch/pip

Allow user to choose the IP address for the container
Arnaud Porterie 9 vuotta sitten
vanhempi
commit
05de2aadff
34 muutettua tiedostoa jossa 584 lisäystä ja 253 poistoa
  1. 8 5
      api/client/create.go
  2. 11 4
      api/client/network.go
  3. 3 2
      api/client/run.go
  4. 7 6
      api/server/router/container/container_routes.go
  5. 1 1
      api/server/router/local/image.go
  6. 1 2
      api/server/router/network/backend.go
  7. 1 1
      api/server/router/network/network_routes.go
  8. 8 0
      container/container_unix.go
  9. 111 19
      daemon/container_operations_unix.go
  10. 3 2
      daemon/container_operations_windows.go
  11. 7 1
      daemon/create.go
  12. 2 2
      daemon/network.go
  13. 2 0
      docs/reference/api/docker_remote_api.md
  14. 7 1
      docs/reference/api/docker_remote_api_v1.22.md
  15. 8 1
      docs/reference/commandline/network_connect.md
  16. 2 0
      docs/reference/commandline/run.md
  17. 2 0
      docs/reference/run.md
  18. 56 38
      docs/userguide/networking/work-with-networks.md
  19. 1 1
      hack/vendor.sh
  20. 68 0
      integration-cli/docker_cli_network_unix_test.go
  21. 7 6
      runconfig/config.go
  22. 9 8
      runconfig/config_test.go
  23. 8 4
      runconfig/config_unix.go
  24. 6 2
      runconfig/config_windows.go
  25. 4 0
      runconfig/errors.go
  26. 40 23
      runconfig/opts/parse.go
  27. 30 29
      runconfig/opts/parse_test.go
  28. 22 10
      vendor/src/github.com/docker/libnetwork/README.md
  29. 21 27
      vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go
  30. 81 29
      vendor/src/github.com/docker/libnetwork/drivers/bridge/setup_ip_tables.go
  31. 13 21
      vendor/src/github.com/docker/libnetwork/drivers/overlay/filter.go
  32. 23 7
      vendor/src/github.com/docker/libnetwork/endpoint.go
  33. 10 0
      vendor/src/github.com/docker/libnetwork/iptables/iptables.go
  34. 1 1
      vendor/src/github.com/docker/libnetwork/resolvconf/dns/resolvconf.go

+ 8 - 5
api/client/create.go

@@ -13,6 +13,7 @@ import (
 	"github.com/docker/engine-api/client"
 	"github.com/docker/engine-api/client"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/container"
+	networktypes "github.com/docker/engine-api/types/network"
 )
 )
 
 
 func (cli *DockerCli) pullImage(image string) error {
 func (cli *DockerCli) pullImage(image string) error {
@@ -79,7 +80,7 @@ func newCIDFile(path string) (*cidFile, error) {
 	return &cidFile{path: path, file: f}, nil
 	return &cidFile{path: path, file: f}, nil
 }
 }
 
 
-func (cli *DockerCli) createContainer(config *container.Config, hostConfig *container.HostConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
+func (cli *DockerCli) createContainer(config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
 	var containerIDFile *cidFile
 	var containerIDFile *cidFile
 	if cidfile != "" {
 	if cidfile != "" {
 		var err error
 		var err error
@@ -107,7 +108,8 @@ func (cli *DockerCli) createContainer(config *container.Config, hostConfig *cont
 	}
 	}
 
 
 	//create the container
 	//create the container
-	response, err := cli.client.ContainerCreate(config, hostConfig, nil, name)
+	response, err := cli.client.ContainerCreate(config, hostConfig, networkingConfig, name)
+
 	//if image not found try to pull it
 	//if image not found try to pull it
 	if err != nil {
 	if err != nil {
 		if client.IsErrImageNotFound(err) {
 		if client.IsErrImageNotFound(err) {
@@ -124,7 +126,7 @@ func (cli *DockerCli) createContainer(config *container.Config, hostConfig *cont
 			}
 			}
 			// Retry
 			// Retry
 			var retryErr error
 			var retryErr error
-			response, retryErr = cli.client.ContainerCreate(config, hostConfig, nil, name)
+			response, retryErr = cli.client.ContainerCreate(config, hostConfig, networkingConfig, name)
 			if retryErr != nil {
 			if retryErr != nil {
 				return nil, retryErr
 				return nil, retryErr
 			}
 			}
@@ -156,7 +158,8 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
 		flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
 		flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
 	)
 	)
 
 
-	config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args)
+	config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
+
 	if err != nil {
 	if err != nil {
 		cmd.ReportError(err.Error(), true)
 		cmd.ReportError(err.Error(), true)
 		os.Exit(1)
 		os.Exit(1)
@@ -165,7 +168,7 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
 		cmd.Usage()
 		cmd.Usage()
 		return nil
 		return nil
 	}
 	}
-	response, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
+	response, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 11 - 4
api/client/network.go

@@ -107,15 +107,22 @@ func (cli *DockerCli) CmdNetworkRm(args ...string) error {
 
 
 // CmdNetworkConnect connects a container to a network
 // CmdNetworkConnect connects a container to a network
 //
 //
-// Usage: docker network connect <NETWORK> <CONTAINER>
+// Usage: docker network connect [OPTIONS] <NETWORK> <CONTAINER>
 func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
 func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
 	cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
 	cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
-	cmd.Require(flag.Exact, 2)
+	flIPAddress := cmd.String([]string{"-ip"}, "", "IP Address")
+	flIPv6Address := cmd.String([]string{"-ip6"}, "", "IPv6 Address")
+	cmd.Require(flag.Min, 2)
 	if err := cmd.ParseFlags(args, true); err != nil {
 	if err := cmd.ParseFlags(args, true); err != nil {
 		return err
 		return err
 	}
 	}
-
-	return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), nil)
+	epConfig := &network.EndpointSettings{
+		IPAMConfig: &network.EndpointIPAMConfig{
+			IPv4Address: *flIPAddress,
+			IPv6Address: *flIPv6Address,
+		},
+	}
+	return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), epConfig)
 }
 }
 
 
 // CmdNetworkDisconnect disconnects a container from a network
 // CmdNetworkDisconnect disconnects a container from a network

+ 3 - 2
api/client/run.go

@@ -82,7 +82,8 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 		ErrConflictDetachAutoRemove           = fmt.Errorf("Conflicting options: --rm and -d")
 		ErrConflictDetachAutoRemove           = fmt.Errorf("Conflicting options: --rm and -d")
 	)
 	)
 
 
-	config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args)
+	config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
+
 	// just in case the Parse does not exit
 	// just in case the Parse does not exit
 	if err != nil {
 	if err != nil {
 		cmd.ReportError(err.Error(), true)
 		cmd.ReportError(err.Error(), true)
@@ -145,7 +146,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
 		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
 	}
 	}
 
 
-	createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
+	createResponse, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
 	if err != nil {
 	if err != nil {
 		cmd.ReportError(err.Error(), true)
 		cmd.ReportError(err.Error(), true)
 		return runStartContainerErr(err)
 		return runStartContainerErr(err)

+ 7 - 6
api/server/router/container/container_routes.go

@@ -332,7 +332,7 @@ func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.Respon
 		return err
 		return err
 	}
 	}
 
 
-	_, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
+	_, hostConfig, _, err := runconfig.DecodeContainerConfig(r.Body)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -358,7 +358,7 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
 
 
 	name := r.Form.Get("name")
 	name := r.Form.Get("name")
 
 
-	config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
+	config, hostConfig, networkingConfig, err := runconfig.DecodeContainerConfig(r.Body)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -366,10 +366,11 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
 	adjustCPUShares := version.LessThan("1.19")
 	adjustCPUShares := version.LessThan("1.19")
 
 
 	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
 	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
-		Name:            name,
-		Config:          config,
-		HostConfig:      hostConfig,
-		AdjustCPUShares: adjustCPUShares,
+		Name:             name,
+		Config:           config,
+		HostConfig:       hostConfig,
+		NetworkingConfig: networkingConfig,
+		AdjustCPUShares:  adjustCPUShares,
 	})
 	})
 	if err != nil {
 	if err != nil {
 		return err
 		return err

+ 1 - 1
api/server/router/local/image.go

@@ -39,7 +39,7 @@ func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.
 		pause = true
 		pause = true
 	}
 	}
 
 
-	c, _, err := runconfig.DecodeContainerConfig(r.Body)
+	c, _, _, err := runconfig.DecodeContainerConfig(r.Body)
 	if err != nil && err != io.EOF { //Do not fail if body is empty.
 	if err != nil && err != io.EOF { //Do not fail if body is empty.
 		return err
 		return err
 	}
 	}

+ 1 - 2
api/server/router/network/backend.go

@@ -2,7 +2,6 @@ package network
 
 
 import (
 import (
 	"github.com/docker/engine-api/types/network"
 	"github.com/docker/engine-api/types/network"
-
 	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork"
 )
 )
 
 
@@ -15,7 +14,7 @@ type Backend interface {
 	GetAllNetworks() []libnetwork.Network
 	GetAllNetworks() []libnetwork.Network
 	CreateNetwork(name, driver string, ipam network.IPAM,
 	CreateNetwork(name, driver string, ipam network.IPAM,
 		options map[string]string) (libnetwork.Network, error)
 		options map[string]string) (libnetwork.Network, error)
-	ConnectContainerToNetwork(containerName, networkName string) error
+	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
 	DisconnectContainerFromNetwork(containerName string,
 	DisconnectContainerFromNetwork(containerName string,
 		network libnetwork.Network) error
 		network libnetwork.Network) error
 	NetworkControllerEnabled() bool
 	NetworkControllerEnabled() bool

+ 1 - 1
api/server/router/network/network_routes.go

@@ -122,7 +122,7 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
 		return err
 		return err
 	}
 	}
 
 
-	return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name())
+	return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name(), connect.EndpointConfig)
 }
 }
 
 
 func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {

+ 8 - 0
container/container_unix.go

@@ -261,6 +261,14 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network) ([]
 		createOptions = append(createOptions, libnetwork.CreateOptionAnonymous())
 		createOptions = append(createOptions, libnetwork.CreateOptionAnonymous())
 	}
 	}
 
 
+	if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok {
+		ipam := epConfig.IPAMConfig
+		if ipam != nil && (ipam.IPv4Address != "" || ipam.IPv6Address != "") {
+			createOptions = append(createOptions,
+				libnetwork.CreateOptionIpam(net.ParseIP(ipam.IPv4Address), net.ParseIP(ipam.IPv6Address), nil))
+		}
+	}
+
 	// Other configs are applicable only for the endpoint in the network
 	// Other configs are applicable only for the endpoint in the network
 	// to which container was connected to on docker run.
 	// to which container was connected to on docker run.
 	if n.Name() != container.HostConfig.NetworkMode.NetworkName() &&
 	if n.Name() != container.HostConfig.NetworkMode.NetworkName() &&

+ 111 - 19
daemon/container_operations_unix.go

@@ -503,7 +503,10 @@ func (daemon *Daemon) updateNetworkSettings(container *container.Container, n li
 			return runconfig.ErrConflictNoNetwork
 			return runconfig.ErrConflictNoNetwork
 		}
 		}
 	}
 	}
-	container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
+
+	if _, ok := container.NetworkSettings.Networks[n.Name()]; !ok {
+		container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
+	}
 
 
 	return nil
 	return nil
 }
 }
@@ -562,7 +565,12 @@ func (daemon *Daemon) updateNetwork(container *container.Container) error {
 }
 }
 
 
 // updateContainerNetworkSettings update the network settings
 // updateContainerNetworkSettings update the network settings
-func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container) error {
+func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
+	var (
+		n   libnetwork.Network
+		err error
+	)
+
 	mode := container.HostConfig.NetworkMode
 	mode := container.HostConfig.NetworkMode
 	if container.Config.NetworkDisabled || mode.IsContainer() {
 	if container.Config.NetworkDisabled || mode.IsContainer() {
 		return nil
 		return nil
@@ -573,14 +581,35 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
 		networkName = daemon.netController.Config().Daemon.DefaultNetwork
 		networkName = daemon.netController.Config().Daemon.DefaultNetwork
 	}
 	}
 	if mode.IsUserDefined() {
 	if mode.IsUserDefined() {
-		n, err := daemon.FindNetwork(networkName)
+		n, err = daemon.FindNetwork(networkName)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
 		networkName = n.Name()
 		networkName = n.Name()
 	}
 	}
-	container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings)
-	container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings)
+	if container.NetworkSettings == nil {
+		container.NetworkSettings = &network.Settings{}
+	}
+	if endpointsConfig != nil {
+		container.NetworkSettings.Networks = endpointsConfig
+	}
+	if container.NetworkSettings.Networks == nil {
+		container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings)
+		container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings)
+	}
+	if !mode.IsUserDefined() {
+		return nil
+	}
+	// Make sure to internally store the per network endpoint config by network name
+	if _, ok := container.NetworkSettings.Networks[networkName]; ok {
+		return nil
+	}
+	if nwConfig, ok := container.NetworkSettings.Networks[n.ID()]; ok {
+		container.NetworkSettings.Networks[networkName] = nwConfig
+		delete(container.NetworkSettings.Networks, n.ID())
+		return nil
+	}
+
 	return nil
 	return nil
 }
 }
 
 
@@ -598,15 +627,15 @@ func (daemon *Daemon) allocateNetwork(container *container.Container) error {
 			return nil
 			return nil
 		}
 		}
 
 
-		err := daemon.updateContainerNetworkSettings(container)
+		err := daemon.updateContainerNetworkSettings(container, nil)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
 		updateSettings = true
 		updateSettings = true
 	}
 	}
 
 
-	for n := range container.NetworkSettings.Networks {
-		if err := daemon.connectToNetwork(container, n, updateSettings); err != nil {
+	for n, nConf := range container.NetworkSettings.Networks {
+		if err := daemon.connectToNetwork(container, n, nConf, updateSettings); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
@@ -626,12 +655,65 @@ func (daemon *Daemon) getNetworkSandbox(container *container.Container) libnetwo
 	return sb
 	return sb
 }
 }
 
 
+// hasUserDefinedIPAddress returns whether the passed endpoint configuration contains IP address configuration
+func hasUserDefinedIPAddress(epConfig *networktypes.EndpointSettings) bool {
+	return epConfig != nil && epConfig.IPAMConfig != nil && (len(epConfig.IPAMConfig.IPv4Address) > 0 || len(epConfig.IPAMConfig.IPv6Address) > 0)
+}
+
+// User specified ip address is acceptable only for networks with user specified subnets.
+func validateNetworkingConfig(n libnetwork.Network, epConfig *networktypes.EndpointSettings) error {
+	if !hasUserDefinedIPAddress(epConfig) {
+		return nil
+	}
+	_, nwIPv4Configs, nwIPv6Configs := n.Info().IpamConfig()
+	for _, s := range []struct {
+		ipConfigured  bool
+		subnetConfigs []*libnetwork.IpamConf
+	}{
+		{
+			ipConfigured:  len(epConfig.IPAMConfig.IPv4Address) > 0,
+			subnetConfigs: nwIPv4Configs,
+		},
+		{
+			ipConfigured:  len(epConfig.IPAMConfig.IPv6Address) > 0,
+			subnetConfigs: nwIPv6Configs,
+		},
+	} {
+		if s.ipConfigured {
+			foundSubnet := false
+			for _, cfg := range s.subnetConfigs {
+				if len(cfg.PreferredPool) > 0 {
+					foundSubnet = true
+					break
+				}
+			}
+			if !foundSubnet {
+				return runconfig.ErrUnsupportedNetworkNoSubnetAndIP
+			}
+		}
+	}
+
+	return nil
+}
+
+// cleanOperationalData resets the operational data from the passed endpoint settings
+func cleanOperationalData(es *networktypes.EndpointSettings) {
+	es.EndpointID = ""
+	es.Gateway = ""
+	es.IPAddress = ""
+	es.IPPrefixLen = 0
+	es.IPv6Gateway = ""
+	es.GlobalIPv6Address = ""
+	es.GlobalIPv6PrefixLen = 0
+	es.MacAddress = ""
+}
+
 // ConnectToNetwork connects a container to a network
 // ConnectToNetwork connects a container to a network
-func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string) error {
+func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error {
 	if !container.Running {
 	if !container.Running {
 		return derr.ErrorCodeNotRunning.WithArgs(container.ID)
 		return derr.ErrorCodeNotRunning.WithArgs(container.ID)
 	}
 	}
-	if err := daemon.connectToNetwork(container, idOrName, true); err != nil {
+	if err := daemon.connectToNetwork(container, idOrName, endpointConfig, true); err != nil {
 		return err
 		return err
 	}
 	}
 	if err := container.ToDiskLocking(); err != nil {
 	if err := container.ToDiskLocking(); err != nil {
@@ -640,11 +722,15 @@ func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName
 	return nil
 	return nil
 }
 }
 
 
-func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, updateSettings bool) (err error) {
+func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) {
 	if container.HostConfig.NetworkMode.IsContainer() {
 	if container.HostConfig.NetworkMode.IsContainer() {
 		return runconfig.ErrConflictSharedNetwork
 		return runconfig.ErrConflictSharedNetwork
 	}
 	}
 
 
+	if !containertypes.NetworkMode(idOrName).IsUserDefined() && hasUserDefinedIPAddress(endpointConfig) {
+		return runconfig.ErrUnsupportedNetworkAndIP
+	}
+
 	if containertypes.NetworkMode(idOrName).IsBridge() &&
 	if containertypes.NetworkMode(idOrName).IsBridge() &&
 		daemon.configStore.DisableBridge {
 		daemon.configStore.DisableBridge {
 		container.Config.NetworkDisabled = true
 		container.Config.NetworkDisabled = true
@@ -658,12 +744,20 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
 		return err
 		return err
 	}
 	}
 
 
+	if err := validateNetworkingConfig(n, endpointConfig); err != nil {
+		return err
+	}
+
 	if updateSettings {
 	if updateSettings {
 		if err := daemon.updateNetworkSettings(container, n); err != nil {
 		if err := daemon.updateNetworkSettings(container, n); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
 
 
+	if endpointConfig != nil {
+		container.NetworkSettings.Networks[n.Name()] = endpointConfig
+	}
+
 	ep, err := container.GetEndpointInNetwork(n)
 	ep, err := container.GetEndpointInNetwork(n)
 	if err == nil {
 	if err == nil {
 		return fmt.Errorf("Conflict. A container with name %q is already connected to network %s.", strings.TrimPrefix(container.Name, "/"), idOrName)
 		return fmt.Errorf("Conflict. A container with name %q is already connected to network %s.", strings.TrimPrefix(container.Name, "/"), idOrName)
@@ -869,18 +963,16 @@ func (daemon *Daemon) releaseNetwork(container *container.Container) {
 
 
 	sid := container.NetworkSettings.SandboxID
 	sid := container.NetworkSettings.SandboxID
 	settings := container.NetworkSettings.Networks
 	settings := container.NetworkSettings.Networks
+	if sid == "" || len(settings) == 0 {
+		return
+	}
+
 	var networks []libnetwork.Network
 	var networks []libnetwork.Network
-	for n := range settings {
+	for n, epSettings := range settings {
 		if nw, err := daemon.FindNetwork(n); err == nil {
 		if nw, err := daemon.FindNetwork(n); err == nil {
 			networks = append(networks, nw)
 			networks = append(networks, nw)
 		}
 		}
-		settings[n] = &networktypes.EndpointSettings{}
-	}
-
-	container.NetworkSettings = &network.Settings{Networks: settings}
-
-	if sid == "" || len(settings) == 0 {
-		return
+		cleanOperationalData(epSettings)
 	}
 	}
 
 
 	sb, err := daemon.netController.SandboxByID(sid)
 	sb, err := daemon.netController.SandboxByID(sid)

+ 3 - 2
daemon/container_operations_windows.go

@@ -10,6 +10,7 @@ import (
 	"github.com/docker/docker/daemon/execdriver/windows"
 	"github.com/docker/docker/daemon/execdriver/windows"
 	derr "github.com/docker/docker/errors"
 	derr "github.com/docker/docker/errors"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
+	networktypes "github.com/docker/engine-api/types/network"
 	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork"
 )
 )
 
 
@@ -18,7 +19,7 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s
 }
 }
 
 
 // updateContainerNetworkSettings update the network settings
 // updateContainerNetworkSettings update the network settings
-func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container) error {
+func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
 	return nil
 	return nil
 }
 }
 
 
@@ -27,7 +28,7 @@ func (daemon *Daemon) initializeNetworking(container *container.Container) error
 }
 }
 
 
 // ConnectToNetwork connects a container to the network
 // ConnectToNetwork connects a container to the network
-func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string) error {
+func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error {
 	return nil
 	return nil
 }
 }
 
 

+ 7 - 1
daemon/create.go

@@ -11,6 +11,7 @@ import (
 	volumestore "github.com/docker/docker/volume/store"
 	volumestore "github.com/docker/docker/volume/store"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 	containertypes "github.com/docker/engine-api/types/container"
 	containertypes "github.com/docker/engine-api/types/container"
+	networktypes "github.com/docker/engine-api/types/network"
 	"github.com/opencontainers/runc/libcontainer/label"
 	"github.com/opencontainers/runc/libcontainer/label"
 )
 )
 
 
@@ -108,7 +109,12 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig) (retC *containe
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	if err := daemon.updateContainerNetworkSettings(container); err != nil {
+	var endpointsConfigs map[string]*networktypes.EndpointSettings
+	if params.NetworkingConfig != nil {
+		endpointsConfigs = params.NetworkingConfig.EndpointsConfig
+	}
+
+	if err := daemon.updateContainerNetworkSettings(container, endpointsConfigs); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 

+ 2 - 2
daemon/network.go

@@ -150,12 +150,12 @@ func getIpamConfig(data []network.IPAMConfig) ([]*libnetwork.IpamConf, []*libnet
 // ConnectContainerToNetwork connects the given container to the given
 // ConnectContainerToNetwork connects the given container to the given
 // network. If either cannot be found, an err is returned. If the
 // network. If either cannot be found, an err is returned. If the
 // network cannot be set up, an err is returned.
 // network cannot be set up, an err is returned.
-func (daemon *Daemon) ConnectContainerToNetwork(containerName, networkName string) error {
+func (daemon *Daemon) ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error {
 	container, err := daemon.GetContainer(containerName)
 	container, err := daemon.GetContainer(containerName)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	return daemon.ConnectToNetwork(container, networkName)
+	return daemon.ConnectToNetwork(container, networkName, endpointConfig)
 }
 }
 
 
 // DisconnectContainerFromNetwork disconnects the given container from
 // DisconnectContainerFromNetwork disconnects the given container from

+ 2 - 0
docs/reference/api/docker_remote_api.md

@@ -110,6 +110,8 @@ This section lists each version from latest to oldest.  Each listing includes a
 * `POST /containers/create` now allows you to set a read/write rate limit for a 
 * `POST /containers/create` now allows you to set a read/write rate limit for a 
   device (in bytes per second or IO per second).
   device (in bytes per second or IO per second).
 * `GET /networks` now supports filtering by `name`, `id` and `type`.
 * `GET /networks` now supports filtering by `name`, `id` and `type`.
+* `POST /containers/create` now allows you to set the static IPv4 and/or IPv6 address for the container.
+* `POST /networks/(id)/connect` now allows you to set the static IPv4 and/or IPv6 address for the container.
 
 
 ### v1.21 API changes
 ### v1.21 API changes
 
 

+ 7 - 1
docs/reference/api/docker_remote_api_v1.22.md

@@ -3031,7 +3031,13 @@ POST /networks/22be93d5babb089c5aab8dbc369042fad48ff791584ca2da2100db837a1c7c30/
 Content-Type: application/json
 Content-Type: application/json
 
 
 {
 {
-  "Container":"3613f73ba0e4"
+  "Container":"3613f73ba0e4",
+  "endpoint_config": {
+    "test_nw": {
+        "IPv4Address":"172.24.56.89",
+        "IPv6Address":"2001:db8::5689"
+    }
+  }
 }
 }
 ```
 ```
 
 

+ 8 - 1
docs/reference/commandline/network_connect.md

@@ -30,11 +30,18 @@ You can also use the `docker run --net=<network-name>` option to start a contain
 $ docker run -itd --net=multi-host-network busybox
 $ docker run -itd --net=multi-host-network busybox
 ```
 ```
 
 
+You can specify the IP address you want to be assigned to the container's interface.
+
+```bash
+$ docker network connect multi-host-network --ip 10.10.36.122 container2
+```
+
 You can pause, restart, and stop containers that are connected to a network.
 You can pause, restart, and stop containers that are connected to a network.
 Paused containers remain connected and a revealed by a `network inspect`. When
 Paused containers remain connected and a revealed by a `network inspect`. When
 the container is stopped, it does not appear on the network until you restart
 the container is stopped, it does not appear on the network until you restart
 it. The container's IP address is not guaranteed to remain the same when a
 it. The container's IP address is not guaranteed to remain the same when a
-stopped container rejoins the network.
+stopped container rejoins the network, unless you specified one when you run
+`docker network connect` command.
 
 
 To verify the container is connected, use the `docker network inspect` command. Use `docker network disconnect` to remove a container from the network.
 To verify the container is connected, use the `docker network inspect` command. Use `docker network disconnect` to remove a container from the network.
 
 

+ 2 - 0
docs/reference/commandline/run.md

@@ -56,6 +56,8 @@ parent = "smn_cli"
       --log-opt=[]                  Log driver specific options
       --log-opt=[]                  Log driver specific options
       -m, --memory=""               Memory limit
       -m, --memory=""               Memory limit
       --mac-address=""              Container MAC address (e.g. 92:d0:c6:0a:29:33)
       --mac-address=""              Container MAC address (e.g. 92:d0:c6:0a:29:33)
+      --ip=""                       Container IPv4 address (e.g. 172.30.100.104)
+      --ip6=""                      Container IPv6 address (e.g. 2001:db8::33)
       --memory-reservation=""       Memory soft limit
       --memory-reservation=""       Memory soft limit
       --memory-swap=""              A positive integer equal to memory plus swap. Specify -1 to enable unlimited swap.
       --memory-swap=""              A positive integer equal to memory plus swap. Specify -1 to enable unlimited swap.
       --memory-swappiness=""        Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.
       --memory-swappiness=""        Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.

+ 2 - 0
docs/reference/run.md

@@ -275,6 +275,8 @@ of the containers.
                         '<network-name>|<network-id>': connect to a user-defined network
                         '<network-name>|<network-id>': connect to a user-defined network
     --add-host=""    : Add a line to /etc/hosts (host:IP)
     --add-host=""    : Add a line to /etc/hosts (host:IP)
     --mac-address="" : Sets the container's Ethernet device's MAC address
     --mac-address="" : Sets the container's Ethernet device's MAC address
+    --ip=""          : Sets the container's Ethernet device's IPv4 address
+    --ip6=""          : Sets the container's Ethernet device's IPv6 address
 
 
 By default, all containers have networking enabled and they can make any
 By default, all containers have networking enabled and they can make any
 outgoing connections. The operator can completely disable networking
 outgoing connections. The operator can completely disable networking

+ 56 - 38
docs/userguide/networking/work-with-networks.md

@@ -115,8 +115,8 @@ $ docker run -itd --name=container2 busybox
 Then create an isolated, `bridge` network to test with.
 Then create an isolated, `bridge` network to test with.
 
 
 ```bash
 ```bash
-$ docker network create -d bridge isolated_nw
-f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a
+$ docker network create -d bridge --subnet 172.25.0.0/16 isolated_nw
+06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8
 ```
 ```
 
 
 Connect `container2` to the network and then `inspect` the network to verify the connection:
 Connect `container2` to the network and then `inspect` the network to verify the connection:
@@ -124,23 +124,26 @@ Connect `container2` to the network and then `inspect` the network to verify the
 ```
 ```
 $ docker network connect isolated_nw container2
 $ docker network connect isolated_nw container2
 $ docker network inspect isolated_nw
 $ docker network inspect isolated_nw
-[[
+[
     {
     {
         "Name": "isolated_nw",
         "Name": "isolated_nw",
-        "Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a",
+        "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
         "Scope": "local",
         "Scope": "local",
         "Driver": "bridge",
         "Driver": "bridge",
         "IPAM": {
         "IPAM": {
             "Driver": "default",
             "Driver": "default",
             "Config": [
             "Config": [
-                {}
+                {
+                    "Subnet": "172.25.0.0/16"
+                }
             ]
             ]
         },
         },
         "Containers": {
         "Containers": {
-            "498eaaaf328e1018042c04b2de04036fc04719a6e39a097a4f4866043a2c2152": {
-                "EndpointID": "0e24479cfaafb029104999b4e120858a07b19b1b6d956ae56811033e45d68ad9",
-                "MacAddress": "02:42:ac:15:00:02",
-                "IPv4Address": "172.21.0.2/16",
+            "90e1f3ec71caf82ae776a827e0712a68a110a3f175954e5bd4222fd142ac9428": {
+                "Name": "container2",
+                "EndpointID": "11cedac1810e864d6b1589d92da12af66203879ab89f4ccd8c8fdaa9b1c48b1d",
+                "MacAddress": "02:42:ac:19:00:02",
+                "IPv4Address": "172.25.0.2/16",
                 "IPv6Address": ""
                 "IPv6Address": ""
             }
             }
         },
         },
@@ -150,20 +153,28 @@ $ docker network inspect isolated_nw
 ```
 ```
 
 
 You can see that the Engine automatically assigns an IP address to `container2`.
 You can see that the Engine automatically assigns an IP address to `container2`.
-If you had specified a `--subnetwork` when creating your network, the network
-would have used that addressing. Now, start a third container and connect it to
+Given we specified a `--subnet` when creating the network, Engine picked
+an address from that same subnet. Now, start a third container and connect it to
 the network on launch using the `docker run` command's `--net` option:
 the network on launch using the `docker run` command's `--net` option:
 
 
 ```bash
 ```bash
-$ docker run --net=isolated_nw -itd --name=container3 busybox
-c282ca437ee7e926a7303a64fc04109740208d2c20e442366139322211a6481c
+$ docker run --net=isolated_nw --ip=172.25.3.3 -itd --name=container3 busybox
+467a7863c3f0277ef8e661b38427737f28099b61fa55622d6c30fb288d88c551
 ```
 ```
 
 
+As you can see you were able to specify the ip address for your container.
+As long as the network to which the container is connecting was created with
+a user specified subnet, you will be able to select the IPv4 and/or IPv6 address(es)
+for your container when executing `docker run` and `docker network connect` commands.
+The selected IP address is part of the container networking configuration and will be
+preserved across container reload. The feature is only available on user defined networks,
+because they guarantee their subnets configuration does not change across daemon reload.
+
 Now, inspect the network resources used by `container3`.
 Now, inspect the network resources used by `container3`.
 
 
 ```bash
 ```bash
 $ docker inspect --format='{{json .NetworkSettings.Networks}}'  container3
 $ docker inspect --format='{{json .NetworkSettings.Networks}}'  container3
-{"isolated_nw":{"EndpointID":"e5d077f9712a69c6929fdd890df5e7c1c649771a50df5b422f7e68f0ae61e847","Gateway":"172.21.0.1","IPAddress":"172.21.0.3","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:15:00:03"}}
+{"isolated_nw":{"IPAMConfig":{"IPv4Address":"172.25.3.3"},"EndpointID":"dffc7ec2915af58cc827d995e6ebdc897342be0420123277103c40ae35579103","Gateway":"172.25.0.1","IPAddress":"172.25.3.3","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:19:03:03"}}
 ```
 ```
 Repeat this command for `container2`. If you have Python installed, you can pretty print the output.
 Repeat this command for `container2`. If you have Python installed, you can pretty print the output.
 
 
@@ -171,24 +182,26 @@ Repeat this command for `container2`. If you have Python installed, you can pret
 $ docker inspect --format='{{json .NetworkSettings.Networks}}'  container2 | python -m json.tool
 $ docker inspect --format='{{json .NetworkSettings.Networks}}'  container2 | python -m json.tool
 {
 {
     "bridge": {
     "bridge": {
-        "EndpointID": "281b5ead415cf48a6a84fd1a6504342c76e9091fe09b4fdbcc4a01c30b0d3c5b",
+        "EndpointID": "0099f9efb5a3727f6a554f176b1e96fca34cae773da68b3b6a26d046c12cb365",
         "Gateway": "172.17.0.1",
         "Gateway": "172.17.0.1",
         "GlobalIPv6Address": "",
         "GlobalIPv6Address": "",
         "GlobalIPv6PrefixLen": 0,
         "GlobalIPv6PrefixLen": 0,
+        "IPAMConfig": null,
         "IPAddress": "172.17.0.3",
         "IPAddress": "172.17.0.3",
         "IPPrefixLen": 16,
         "IPPrefixLen": 16,
         "IPv6Gateway": "",
         "IPv6Gateway": "",
         "MacAddress": "02:42:ac:11:00:03"
         "MacAddress": "02:42:ac:11:00:03"
     },
     },
     "isolated_nw": {
     "isolated_nw": {
-        "EndpointID": "0e24479cfaafb029104999b4e120858a07b19b1b6d956ae56811033e45d68ad9",
-        "Gateway": "172.21.0.1",
+        "EndpointID": "11cedac1810e864d6b1589d92da12af66203879ab89f4ccd8c8fdaa9b1c48b1d",
+        "Gateway": "172.25.0.1",
         "GlobalIPv6Address": "",
         "GlobalIPv6Address": "",
         "GlobalIPv6PrefixLen": 0,
         "GlobalIPv6PrefixLen": 0,
-        "IPAddress": "172.21.0.2",
+        "IPAMConfig": null,
+        "IPAddress": "172.25.0.2",
         "IPPrefixLen": 16,
         "IPPrefixLen": 16,
         "IPv6Gateway": "",
         "IPv6Gateway": "",
-        "MacAddress": "02:42:ac:15:00:02"
+        "MacAddress": "02:42:ac:19:00:02"
     }
     }
 }
 }
 ```
 ```
@@ -223,8 +236,8 @@ eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:03
           RX bytes:648 (648.0 B)  TX bytes:648 (648.0 B)
           RX bytes:648 (648.0 B)  TX bytes:648 (648.0 B)
 
 
 eth1      Link encap:Ethernet  HWaddr 02:42:AC:15:00:02  
 eth1      Link encap:Ethernet  HWaddr 02:42:AC:15:00:02  
-          inet addr:172.21.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
-          inet6 addr: fe80::42:acff:fe15:2/64 Scope:Link
+          inet addr:172.25.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
+          inet6 addr: fe80::42:acff:fe19:2/64 Scope:Link
           UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
           UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
           RX packets:8 errors:0 dropped:0 overruns:0 frame:0
           RX packets:8 errors:0 dropped:0 overruns:0 frame:0
           TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
           TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
@@ -252,19 +265,19 @@ fe00::0	ip6-localnet
 ff00::0	ip6-mcastprefix
 ff00::0	ip6-mcastprefix
 ff02::1	ip6-allnodes
 ff02::1	ip6-allnodes
 ff02::2	ip6-allrouters
 ff02::2	ip6-allrouters
-172.21.0.3	container3
-172.21.0.3	container3.isolated_nw
+172.21.3.3	container3
+172.21.3.3	container3.isolated_nw
 ```
 ```
 
 
 On the `isolated_nw` which was user defined, the Docker network feature updated the `/etc/hosts` with the proper name resolution.  Inside of `container2` it is possible to ping `container3` by name.
 On the `isolated_nw` which was user defined, the Docker network feature updated the `/etc/hosts` with the proper name resolution.  Inside of `container2` it is possible to ping `container3` by name.
 
 
 ```bash
 ```bash
 / # ping -w 4 container3
 / # ping -w 4 container3
-PING container3 (172.21.0.3): 56 data bytes
-64 bytes from 172.21.0.3: seq=0 ttl=64 time=0.070 ms
-64 bytes from 172.21.0.3: seq=1 ttl=64 time=0.080 ms
-64 bytes from 172.21.0.3: seq=2 ttl=64 time=0.080 ms
-64 bytes from 172.21.0.3: seq=3 ttl=64 time=0.097 ms
+PING container3 (172.25.3.3): 56 data bytes
+64 bytes from 172.25.3.3: seq=0 ttl=64 time=0.070 ms
+64 bytes from 172.25.3.3: seq=1 ttl=64 time=0.080 ms
+64 bytes from 172.25.3.3: seq=2 ttl=64 time=0.080 ms
+64 bytes from 172.25.3.3: seq=3 ttl=64 time=0.097 ms
 
 
 --- container3 ping statistics ---
 --- container3 ping statistics ---
 4 packets transmitted, 4 packets received, 0% packet loss
 4 packets transmitted, 4 packets received, 0% packet loss
@@ -342,23 +355,26 @@ docker inspect --format='{{json .NetworkSettings.Networks}}'  container2 | pytho
 
 
 
 
 $ docker network inspect isolated_nw
 $ docker network inspect isolated_nw
-[[
+[
     {
     {
         "Name": "isolated_nw",
         "Name": "isolated_nw",
-        "Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a",
+        "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
         "Scope": "local",
         "Scope": "local",
         "Driver": "bridge",
         "Driver": "bridge",
         "IPAM": {
         "IPAM": {
             "Driver": "default",
             "Driver": "default",
             "Config": [
             "Config": [
-                {}
+                {
+                    "Subnet": "172.25.0.0/16"
+                }
             ]
             ]
         },
         },
         "Containers": {
         "Containers": {
-            "c282ca437ee7e926a7303a64fc04109740208d2c20e442366139322211a6481c": {
-                "EndpointID": "e5d077f9712a69c6929fdd890df5e7c1c649771a50df5b422f7e68f0ae61e847",
-                "MacAddress": "02:42:ac:15:00:03",
-                "IPv4Address": "172.21.0.3/16",
+            "467a7863c3f0277ef8e661b38427737f28099b61fa55622d6c30fb288d88c551": {
+                "Name": "container3",
+                "EndpointID": "dffc7ec2915af58cc827d995e6ebdc897342be0420123277103c40ae35579103",
+                "MacAddress": "02:42:ac:19:03:03",
+                "IPv4Address": "172.25.3.3/16",
                 "IPv6Address": ""
                 "IPv6Address": ""
             }
             }
         },
         },
@@ -393,7 +409,7 @@ lo        Link encap:Local Loopback
           RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
           RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
 
 
 / # ping container3
 / # ping container3
-PING container3 (172.20.0.1): 56 data bytes
+PING container3 (172.25.3.3): 56 data bytes
 ^C
 ^C
 --- container3 ping statistics ---
 --- container3 ping statistics ---
 2 packets transmitted, 0 packets received, 100% packet loss
 2 packets transmitted, 0 packets received, 100% packet loss
@@ -426,13 +442,15 @@ docker network inspect isolated_nw
 [
 [
     {
     {
         "Name": "isolated_nw",
         "Name": "isolated_nw",
-        "Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a",
+        "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
         "Scope": "local",
         "Scope": "local",
         "Driver": "bridge",
         "Driver": "bridge",
         "IPAM": {
         "IPAM": {
             "Driver": "default",
             "Driver": "default",
             "Config": [
             "Config": [
-                {}
+                {
+                    "Subnet": "172.25.0.0/16"
+                }
             ]
             ]
         },
         },
         "Containers": {},
         "Containers": {},

+ 1 - 1
hack/vendor.sh

@@ -25,7 +25,7 @@ clone git github.com/docker/go-connections v0.1.2
 clone git github.com/docker/engine-api v0.1.3
 clone git github.com/docker/engine-api v0.1.3
 
 
 #get libnetwork packages
 #get libnetwork packages
-clone git github.com/docker/libnetwork 9f0563ea8f430d8828553aac97161cbff4056436
+clone git github.com/docker/libnetwork 49c24217054e269aad3dbfd81ee32780b104dd84
 clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
 clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
 clone git github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b
 clone git github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b
 clone git github.com/hashicorp/memberlist 9a1e242e454d2443df330bdd51a436d5a9058fc4
 clone git github.com/hashicorp/memberlist 9a1e242e454d2443df330bdd51a436d5a9058fc4

+ 68 - 0
integration-cli/docker_cli_network_unix_test.go

@@ -962,3 +962,71 @@ func (s *DockerNetworkSuite) TestDockerNetworkRestartWithMulipleNetworks(c *chec
 	c.Assert(networks, checker.Contains, "bridge", check.Commentf("Should contain 'bridge' network"))
 	c.Assert(networks, checker.Contains, "bridge", check.Commentf("Should contain 'bridge' network"))
 	c.Assert(networks, checker.Contains, "test", check.Commentf("Should contain 'test' netwokr"))
 	c.Assert(networks, checker.Contains, "test", check.Commentf("Should contain 'test' netwokr"))
 }
 }
+
+func (s *DockerNetworkSuite) TestDockerNetworkConnectPreferredIP(c *check.C) {
+	// create two networks
+	dockerCmd(c, "network", "create", "--subnet=172.28.0.0/16", "--subnet=2001:db8:1234::/64", "n0")
+	assertNwIsAvailable(c, "n0")
+
+	dockerCmd(c, "network", "create", "--subnet=172.30.0.0/16", "--ip-range=172.30.5.0/24", "--subnet=2001:db8:abcd::/64", "--ip-range=2001:db8:abcd::/80", "n1")
+	assertNwIsAvailable(c, "n1")
+
+	// run a container on first network specifying the ip addresses
+	dockerCmd(c, "run", "-d", "--name", "c0", "--net=n0", "--ip", "172.28.99.88", "--ip6", "2001:db8:1234::9988", "busybox", "top")
+	c.Assert(waitRun("c0"), check.IsNil)
+	verifyIPAddresses(c, "c0", "n0", "172.28.99.88", "2001:db8:1234::9988")
+
+	// connect the container to the second network specifying the preferred ip addresses
+	dockerCmd(c, "network", "connect", "--ip", "172.30.55.44", "--ip6", "2001:db8:abcd::5544", "n1", "c0")
+	verifyIPAddresses(c, "c0", "n1", "172.30.55.44", "2001:db8:abcd::5544")
+
+	// Stop and restart the container
+	dockerCmd(c, "stop", "c0")
+	dockerCmd(c, "start", "c0")
+
+	// verify preferred addresses are applied
+	verifyIPAddresses(c, "c0", "n0", "172.28.99.88", "2001:db8:1234::9988")
+	verifyIPAddresses(c, "c0", "n1", "172.30.55.44", "2001:db8:abcd::5544")
+
+	// Still it should fail to connect to the default network with a specified IP (whatever ip)
+	out, _, err := dockerCmdWithError("network", "connect", "--ip", "172.21.55.44", "bridge", "c0")
+	c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
+	c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkAndIP.Error())
+
+}
+
+func (s *DockerNetworkSuite) TestDockerNetworkUnsupportedPreferredIP(c *check.C) {
+	// preferred IP is not supported on predefined networks
+	for _, mode := range []string{"none", "host", "bridge"} {
+		checkUnsupportedNetworkAndIP(c, mode)
+	}
+
+	// preferred IP is not supported on networks with no user defined subnets
+	dockerCmd(c, "network", "create", "n0")
+	assertNwIsAvailable(c, "n0")
+
+	out, _, err := dockerCmdWithError("run", "-d", "--ip", "172.28.99.88", "--net", "n0", "busybox", "top")
+	c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
+	c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkNoSubnetAndIP.Error())
+
+	out, _, err = dockerCmdWithError("run", "-d", "--ip6", "2001:db8:1234::9988", "--net", "n0", "busybox", "top")
+	c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
+	c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkNoSubnetAndIP.Error())
+
+	dockerCmd(c, "network", "rm", "n0")
+	assertNwNotAvailable(c, "n0")
+}
+
+func checkUnsupportedNetworkAndIP(c *check.C, nwMode string) {
+	out, _, err := dockerCmdWithError("run", "-d", "--net", nwMode, "--ip", "172.28.99.88", "--ip6", "2001:db8:1234::9988", "busybox", "top")
+	c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
+	c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkAndIP.Error())
+}
+
+func verifyIPAddresses(c *check.C, cName, nwname, ipv4, ipv6 string) {
+	out, _ := dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.IPAddress }}'", nwname), cName)
+	c.Assert(strings.TrimSpace(out), check.Equals, ipv4)
+
+	out, _ = dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.GlobalIPv6Address }}'", nwname), cName)
+	c.Assert(strings.TrimSpace(out), check.Equals, ipv6)
+}

+ 7 - 6
runconfig/config.go

@@ -7,18 +7,19 @@ import (
 
 
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/container"
+	networktypes "github.com/docker/engine-api/types/network"
 )
 )
 
 
 // DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
 // DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
 // struct and returns both a Config and an HostConfig struct
 // struct and returns both a Config and an HostConfig struct
 // Be aware this function is not checking whether the resulted structs are nil,
 // Be aware this function is not checking whether the resulted structs are nil,
 // it's your business to do so
 // it's your business to do so
-func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostConfig, error) {
+func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
 	var w ContainerConfigWrapper
 	var w ContainerConfigWrapper
 
 
 	decoder := json.NewDecoder(src)
 	decoder := json.NewDecoder(src)
 	if err := decoder.Decode(&w); err != nil {
 	if err := decoder.Decode(&w); err != nil {
-		return nil, nil, err
+		return nil, nil, nil, err
 	}
 	}
 
 
 	hc := w.getHostConfig()
 	hc := w.getHostConfig()
@@ -33,21 +34,21 @@ func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon
 
 
 		// Now validate all the volumes and binds
 		// Now validate all the volumes and binds
 		if err := validateVolumesAndBindSettings(w.Config, hc); err != nil {
 		if err := validateVolumesAndBindSettings(w.Config, hc); err != nil {
-			return nil, nil, err
+			return nil, nil, nil, err
 		}
 		}
 	}
 	}
 
 
 	// Certain parameters need daemon-side validation that cannot be done
 	// Certain parameters need daemon-side validation that cannot be done
 	// on the client, as only the daemon knows what is valid for the platform.
 	// on the client, as only the daemon knows what is valid for the platform.
 	if err := ValidateNetMode(w.Config, hc); err != nil {
 	if err := ValidateNetMode(w.Config, hc); err != nil {
-		return nil, nil, err
+		return nil, nil, nil, err
 	}
 	}
 
 
 	// Validate the isolation level
 	// Validate the isolation level
 	if err := ValidateIsolationLevel(hc); err != nil {
 	if err := ValidateIsolationLevel(hc); err != nil {
-		return nil, nil, err
+		return nil, nil, nil, err
 	}
 	}
-	return w.Config, hc, nil
+	return w.Config, hc, w.NetworkingConfig, nil
 }
 }
 
 
 // validateVolumesAndBindSettings validates each of the volumes and bind settings
 // validateVolumesAndBindSettings validates each of the volumes and bind settings

+ 9 - 8
runconfig/config_test.go

@@ -10,6 +10,7 @@ import (
 	"testing"
 	"testing"
 
 
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/container"
+	networktypes "github.com/docker/engine-api/types/network"
 	"github.com/docker/engine-api/types/strslice"
 	"github.com/docker/engine-api/types/strslice"
 )
 )
 
 
@@ -45,7 +46,7 @@ func TestDecodeContainerConfig(t *testing.T) {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
 
 
-		c, h, err := DecodeContainerConfig(bytes.NewReader(b))
+		c, h, _, err := DecodeContainerConfig(bytes.NewReader(b))
 		if err != nil {
 		if err != nil {
 			t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
 			t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
 		}
 		}
@@ -70,29 +71,29 @@ func TestDecodeContainerConfig(t *testing.T) {
 func TestDecodeContainerConfigIsolation(t *testing.T) {
 func TestDecodeContainerConfigIsolation(t *testing.T) {
 
 
 	// An invalid isolation level
 	// An invalid isolation level
-	if _, _, err := callDecodeContainerConfigIsolation("invalid"); err != nil {
+	if _, _, _, err := callDecodeContainerConfigIsolation("invalid"); err != nil {
 		if !strings.Contains(err.Error(), `invalid --isolation: "invalid"`) {
 		if !strings.Contains(err.Error(), `invalid --isolation: "invalid"`) {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
 	}
 	}
 
 
 	// Blank isolation level (== default)
 	// Blank isolation level (== default)
-	if _, _, err := callDecodeContainerConfigIsolation(""); err != nil {
+	if _, _, _, err := callDecodeContainerConfigIsolation(""); err != nil {
 		t.Fatal("Blank isolation should have succeeded")
 		t.Fatal("Blank isolation should have succeeded")
 	}
 	}
 
 
 	// Default isolation level
 	// Default isolation level
-	if _, _, err := callDecodeContainerConfigIsolation("default"); err != nil {
+	if _, _, _, err := callDecodeContainerConfigIsolation("default"); err != nil {
 		t.Fatal("default isolation should have succeeded")
 		t.Fatal("default isolation should have succeeded")
 	}
 	}
 
 
 	// Hyper-V Containers isolation level (Valid on Windows only)
 	// Hyper-V Containers isolation level (Valid on Windows only)
 	if runtime.GOOS == "windows" {
 	if runtime.GOOS == "windows" {
-		if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
+		if _, _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
 			t.Fatal("hyperv isolation should have succeeded")
 			t.Fatal("hyperv isolation should have succeeded")
 		}
 		}
 	} else {
 	} else {
-		if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
+		if _, _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
 			if !strings.Contains(err.Error(), `invalid --isolation: "hyperv"`) {
 			if !strings.Contains(err.Error(), `invalid --isolation: "hyperv"`) {
 				t.Fatal(err)
 				t.Fatal(err)
 			}
 			}
@@ -102,7 +103,7 @@ func TestDecodeContainerConfigIsolation(t *testing.T) {
 
 
 // callDecodeContainerConfigIsolation is a utility function to call
 // callDecodeContainerConfigIsolation is a utility function to call
 // DecodeContainerConfig for validating isolation levels
 // DecodeContainerConfig for validating isolation levels
-func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *container.HostConfig, error) {
+func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
 	var (
 	var (
 		b   []byte
 		b   []byte
 		err error
 		err error
@@ -114,7 +115,7 @@ func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *c
 			Isolation:   container.IsolationLevel(isolation)},
 			Isolation:   container.IsolationLevel(isolation)},
 	}
 	}
 	if b, err = json.Marshal(w); err != nil {
 	if b, err = json.Marshal(w); err != nil {
-		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
+		return nil, nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
 	}
 	}
 	return DecodeContainerConfig(bytes.NewReader(b))
 	return DecodeContainerConfig(bytes.NewReader(b))
 }
 }

+ 8 - 4
runconfig/config_unix.go

@@ -2,15 +2,19 @@
 
 
 package runconfig
 package runconfig
 
 
-import "github.com/docker/engine-api/types/container"
+import (
+	"github.com/docker/engine-api/types/container"
+	networktypes "github.com/docker/engine-api/types/network"
+)
 
 
 // ContainerConfigWrapper is a Config wrapper that hold the container Config (portable)
 // ContainerConfigWrapper is a Config wrapper that hold the container Config (portable)
 // and the corresponding HostConfig (non-portable).
 // and the corresponding HostConfig (non-portable).
 type ContainerConfigWrapper struct {
 type ContainerConfigWrapper struct {
 	*container.Config
 	*container.Config
-	InnerHostConfig       *container.HostConfig `json:"HostConfig,omitempty"`
-	Cpuset                string                `json:",omitempty"` // Deprecated. Exported for backwards compatibility.
-	*container.HostConfig                       // Deprecated. Exported to read attributes from json that are not in the inner host config structure.
+	InnerHostConfig       *container.HostConfig          `json:"HostConfig,omitempty"`
+	Cpuset                string                         `json:",omitempty"` // Deprecated. Exported for backwards compatibility.
+	NetworkingConfig      *networktypes.NetworkingConfig `json:"NetworkingConfig,omitempty"`
+	*container.HostConfig                                // Deprecated. Exported to read attributes from json that are not in the inner host config structure.
 }
 }
 
 
 // getHostConfig gets the HostConfig of the Config.
 // getHostConfig gets the HostConfig of the Config.

+ 6 - 2
runconfig/config_windows.go

@@ -1,12 +1,16 @@
 package runconfig
 package runconfig
 
 
-import "github.com/docker/engine-api/types/container"
+import (
+	"github.com/docker/engine-api/types/container"
+	networktypes "github.com/docker/engine-api/types/network"
+)
 
 
 // ContainerConfigWrapper is a Config wrapper that hold the container Config (portable)
 // ContainerConfigWrapper is a Config wrapper that hold the container Config (portable)
 // and the corresponding HostConfig (non-portable).
 // and the corresponding HostConfig (non-portable).
 type ContainerConfigWrapper struct {
 type ContainerConfigWrapper struct {
 	*container.Config
 	*container.Config
-	HostConfig *container.HostConfig `json:"HostConfig,omitempty"`
+	HostConfig       *container.HostConfig          `json:"HostConfig,omitempty"`
+	NetworkingConfig *networktypes.NetworkingConfig `json:"NetworkingConfig,omitempty"`
 }
 }
 
 
 // getHostConfig gets the HostConfig of the Config.
 // getHostConfig gets the HostConfig of the Config.

+ 4 - 0
runconfig/errors.go

@@ -29,4 +29,8 @@ var (
 	ErrConflictNetworkPublishPorts = fmt.Errorf("Conflicting options: port publishing and the container type network mode")
 	ErrConflictNetworkPublishPorts = fmt.Errorf("Conflicting options: port publishing and the container type network mode")
 	// ErrConflictNetworkExposePorts conflict between the expose option and the network mode
 	// ErrConflictNetworkExposePorts conflict between the expose option and the network mode
 	ErrConflictNetworkExposePorts = fmt.Errorf("Conflicting options: port exposing and the container type network mode")
 	ErrConflictNetworkExposePorts = fmt.Errorf("Conflicting options: port exposing and the container type network mode")
+	// ErrUnsupportedNetworkAndIP conflict between network mode and preferred ip address
+	ErrUnsupportedNetworkAndIP = fmt.Errorf("User specified IP address is supported on user defined networks only")
+	// ErrUnsupportedNetworkNoSubnetAndIP conflict between network with no configured subnet and preferred ip address
+	ErrUnsupportedNetworkNoSubnetAndIP = fmt.Errorf("User specified IP address is supported only when connecting to networks with user configured subnets")
 )
 )

+ 40 - 23
runconfig/opts/parse.go

@@ -11,6 +11,7 @@ import (
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/container"
+	networktypes "github.com/docker/engine-api/types/network"
 	"github.com/docker/engine-api/types/strslice"
 	"github.com/docker/engine-api/types/strslice"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-units"
 	"github.com/docker/go-units"
@@ -19,7 +20,7 @@ import (
 // Parse parses the specified args for the specified command and generates a Config,
 // Parse parses the specified args for the specified command and generates a Config,
 // a HostConfig and returns them with the specified command.
 // a HostConfig and returns them with the specified command.
 // If the specified args are not valid, it will return an error.
 // If the specified args are not valid, it will return an error.
-func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
+func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, *flag.FlagSet, error) {
 	var (
 	var (
 		// FIXME: use utils.ListOpts for attach and volumes?
 		// FIXME: use utils.ListOpts for attach and volumes?
 		flAttach            = opts.NewListOpts(ValidateAttach)
 		flAttach            = opts.NewListOpts(ValidateAttach)
@@ -77,6 +78,8 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 		flSwappiness        = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)")
 		flSwappiness        = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)")
 		flNetMode           = cmd.String([]string{"-net"}, "default", "Connect a container to a network")
 		flNetMode           = cmd.String([]string{"-net"}, "default", "Connect a container to a network")
 		flMacAddress        = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
 		flMacAddress        = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
+		flIPv4Address       = cmd.String([]string{"-ip"}, "", "Container IPv4 address (e.g. 172.30.100.104)")
+		flIPv6Address       = cmd.String([]string{"-ip6"}, "", "Container IPv6 address (e.g. 2001:db8::33)")
 		flIpcMode           = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
 		flIpcMode           = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
 		flRestartPolicy     = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
 		flRestartPolicy     = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
 		flReadonlyRootfs    = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
 		flReadonlyRootfs    = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
@@ -119,7 +122,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	cmd.Require(flag.Min, 1)
 	cmd.Require(flag.Min, 1)
 
 
 	if err := cmd.ParseFlags(args, true); err != nil {
 	if err := cmd.ParseFlags(args, true); err != nil {
-		return nil, nil, cmd, err
+		return nil, nil, nil, cmd, err
 	}
 	}
 
 
 	var (
 	var (
@@ -131,7 +134,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	// Validate the input mac address
 	// Validate the input mac address
 	if *flMacAddress != "" {
 	if *flMacAddress != "" {
 		if _, err := ValidateMACAddress(*flMacAddress); err != nil {
 		if _, err := ValidateMACAddress(*flMacAddress); err != nil {
-			return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
+			return nil, nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
 		}
 		}
 	}
 	}
 	if *flStdin {
 	if *flStdin {
@@ -149,7 +152,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	if *flMemoryString != "" {
 	if *flMemoryString != "" {
 		flMemory, err = units.RAMInBytes(*flMemoryString)
 		flMemory, err = units.RAMInBytes(*flMemoryString)
 		if err != nil {
 		if err != nil {
-			return nil, nil, cmd, err
+			return nil, nil, nil, cmd, err
 		}
 		}
 	}
 	}
 
 
@@ -157,7 +160,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	if *flMemoryReservation != "" {
 	if *flMemoryReservation != "" {
 		MemoryReservation, err = units.RAMInBytes(*flMemoryReservation)
 		MemoryReservation, err = units.RAMInBytes(*flMemoryReservation)
 		if err != nil {
 		if err != nil {
-			return nil, nil, cmd, err
+			return nil, nil, nil, cmd, err
 		}
 		}
 	}
 	}
 
 
@@ -168,7 +171,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 		} else {
 		} else {
 			memorySwap, err = units.RAMInBytes(*flMemorySwap)
 			memorySwap, err = units.RAMInBytes(*flMemorySwap)
 			if err != nil {
 			if err != nil {
-				return nil, nil, cmd, err
+				return nil, nil, nil, cmd, err
 			}
 			}
 		}
 		}
 	}
 	}
@@ -177,20 +180,20 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	if *flKernelMemory != "" {
 	if *flKernelMemory != "" {
 		KernelMemory, err = units.RAMInBytes(*flKernelMemory)
 		KernelMemory, err = units.RAMInBytes(*flKernelMemory)
 		if err != nil {
 		if err != nil {
-			return nil, nil, cmd, err
+			return nil, nil, nil, cmd, err
 		}
 		}
 	}
 	}
 
 
 	swappiness := *flSwappiness
 	swappiness := *flSwappiness
 	if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
 	if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
-		return nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
+		return nil, nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
 	}
 	}
 
 
 	var shmSize int64
 	var shmSize int64
 	if *flShmSize != "" {
 	if *flShmSize != "" {
 		shmSize, err = units.RAMInBytes(*flShmSize)
 		shmSize, err = units.RAMInBytes(*flShmSize)
 		if err != nil {
 		if err != nil {
-			return nil, nil, cmd, err
+			return nil, nil, nil, cmd, err
 		}
 		}
 	}
 	}
 
 
@@ -210,7 +213,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	for _, t := range flTmpfs.GetAll() {
 	for _, t := range flTmpfs.GetAll() {
 		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
 		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
 			if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
 			if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
-				return nil, nil, cmd, err
+				return nil, nil, nil, cmd, err
 			}
 			}
 			tmpfs[arr[0]] = arr[1]
 			tmpfs[arr[0]] = arr[1]
 		} else {
 		} else {
@@ -243,13 +246,13 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 
 
 	ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
 	ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
 	if err != nil {
 	if err != nil {
-		return nil, nil, cmd, err
+		return nil, nil, nil, cmd, err
 	}
 	}
 
 
 	// Merge in exposed ports to the map of published ports
 	// Merge in exposed ports to the map of published ports
 	for _, e := range flExpose.GetAll() {
 	for _, e := range flExpose.GetAll() {
 		if strings.Contains(e, ":") {
 		if strings.Contains(e, ":") {
-			return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
+			return nil, nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
 		}
 		}
 		//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
 		//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
 		proto, port := nat.SplitProtoPort(e)
 		proto, port := nat.SplitProtoPort(e)
@@ -257,12 +260,12 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 		//if expose a port, the start and end port are the same
 		//if expose a port, the start and end port are the same
 		start, end, err := nat.ParsePortRange(port)
 		start, end, err := nat.ParsePortRange(port)
 		if err != nil {
 		if err != nil {
-			return nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
+			return nil, nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
 		}
 		}
 		for i := start; i <= end; i++ {
 		for i := start; i <= end; i++ {
 			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
 			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
 			if err != nil {
 			if err != nil {
-				return nil, nil, cmd, err
+				return nil, nil, nil, cmd, err
 			}
 			}
 			if _, exists := ports[p]; !exists {
 			if _, exists := ports[p]; !exists {
 				ports[p] = struct{}{}
 				ports[p] = struct{}{}
@@ -275,7 +278,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	for _, device := range flDevices.GetAll() {
 	for _, device := range flDevices.GetAll() {
 		deviceMapping, err := ParseDevice(device)
 		deviceMapping, err := ParseDevice(device)
 		if err != nil {
 		if err != nil {
-			return nil, nil, cmd, err
+			return nil, nil, nil, cmd, err
 		}
 		}
 		deviceMappings = append(deviceMappings, deviceMapping)
 		deviceMappings = append(deviceMappings, deviceMapping)
 	}
 	}
@@ -283,38 +286,38 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	// collect all the environment variables for the container
 	// collect all the environment variables for the container
 	envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
 	envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
 	if err != nil {
 	if err != nil {
-		return nil, nil, cmd, err
+		return nil, nil, nil, cmd, err
 	}
 	}
 
 
 	// collect all the labels for the container
 	// collect all the labels for the container
 	labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
 	labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
 	if err != nil {
 	if err != nil {
-		return nil, nil, cmd, err
+		return nil, nil, nil, cmd, err
 	}
 	}
 
 
 	ipcMode := container.IpcMode(*flIpcMode)
 	ipcMode := container.IpcMode(*flIpcMode)
 	if !ipcMode.Valid() {
 	if !ipcMode.Valid() {
-		return nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
+		return nil, nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
 	}
 	}
 
 
 	pidMode := container.PidMode(*flPidMode)
 	pidMode := container.PidMode(*flPidMode)
 	if !pidMode.Valid() {
 	if !pidMode.Valid() {
-		return nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
+		return nil, nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
 	}
 	}
 
 
 	utsMode := container.UTSMode(*flUTSMode)
 	utsMode := container.UTSMode(*flUTSMode)
 	if !utsMode.Valid() {
 	if !utsMode.Valid() {
-		return nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
+		return nil, nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
 	}
 	}
 
 
 	restartPolicy, err := ParseRestartPolicy(*flRestartPolicy)
 	restartPolicy, err := ParseRestartPolicy(*flRestartPolicy)
 	if err != nil {
 	if err != nil {
-		return nil, nil, cmd, err
+		return nil, nil, nil, cmd, err
 	}
 	}
 
 
 	loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll())
 	loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll())
 	if err != nil {
 	if err != nil {
-		return nil, nil, cmd, err
+		return nil, nil, nil, cmd, err
 	}
 	}
 
 
 	resources := container.Resources{
 	resources := container.Resources{
@@ -405,7 +408,21 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	if config.OpenStdin && config.AttachStdin {
 	if config.OpenStdin && config.AttachStdin {
 		config.StdinOnce = true
 		config.StdinOnce = true
 	}
 	}
-	return config, hostConfig, cmd, nil
+
+	var networkingConfig *networktypes.NetworkingConfig
+	if *flIPv4Address != "" || *flIPv6Address != "" {
+		networkingConfig = &networktypes.NetworkingConfig{
+			EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
+		}
+		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = &networktypes.EndpointSettings{
+			IPAMConfig: &networktypes.EndpointIPAMConfig{
+				IPv4Address: *flIPv4Address,
+				IPv6Address: *flIPv6Address,
+			},
+		}
+	}
+
+	return config, hostConfig, networkingConfig, cmd, nil
 }
 }
 
 
 // reads a file of line terminated key=value pairs and override that with override parameter
 // reads a file of line terminated key=value pairs and override that with override parameter

+ 30 - 29
runconfig/opts/parse_test.go

@@ -13,10 +13,11 @@ import (
 	flag "github.com/docker/docker/pkg/mflag"
 	flag "github.com/docker/docker/pkg/mflag"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/container"
+	networktypes "github.com/docker/engine-api/types/network"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
 )
 )
 
 
-func parseRun(args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
+func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, *flag.FlagSet, error) {
 	cmd := flag.NewFlagSet("run", flag.ContinueOnError)
 	cmd := flag.NewFlagSet("run", flag.ContinueOnError)
 	cmd.SetOutput(ioutil.Discard)
 	cmd.SetOutput(ioutil.Discard)
 	cmd.Usage = nil
 	cmd.Usage = nil
@@ -24,7 +25,7 @@ func parseRun(args []string) (*container.Config, *container.HostConfig, *flag.Fl
 }
 }
 
 
 func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
 func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
-	config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
+	config, hostConfig, _, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
 	return config, hostConfig, err
 	return config, hostConfig, err
 }
 }
 
 
@@ -304,7 +305,7 @@ func callDecodeContainerConfig(volumes []string, binds []string) (*container.Con
 	if b, err = json.Marshal(w); err != nil {
 	if b, err = json.Marshal(w); err != nil {
 		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
 		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
 	}
 	}
-	c, h, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
+	c, h, _, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
 	if err != nil {
 	if err != nil {
 		return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
 		return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
 	}
 	}
@@ -349,7 +350,7 @@ func setupPlatformVolume(u []string, w []string) ([]string, string) {
 func TestParseWithMacAddress(t *testing.T) {
 func TestParseWithMacAddress(t *testing.T) {
 	invalidMacAddress := "--mac-address=invalidMacAddress"
 	invalidMacAddress := "--mac-address=invalidMacAddress"
 	validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
 	validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
-	if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
+	if _, _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
 		t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
 		t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
 	}
 	}
 	if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
 	if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
@@ -360,7 +361,7 @@ func TestParseWithMacAddress(t *testing.T) {
 func TestParseWithMemory(t *testing.T) {
 func TestParseWithMemory(t *testing.T) {
 	invalidMemory := "--memory=invalid"
 	invalidMemory := "--memory=invalid"
 	validMemory := "--memory=1G"
 	validMemory := "--memory=1G"
-	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
+	if _, _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
 		t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
 		t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
 	}
 	}
 	if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
 	if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
@@ -372,7 +373,7 @@ func TestParseWithMemorySwap(t *testing.T) {
 	invalidMemory := "--memory-swap=invalid"
 	invalidMemory := "--memory-swap=invalid"
 	validMemory := "--memory-swap=1G"
 	validMemory := "--memory-swap=1G"
 	anotherValidMemory := "--memory-swap=-1"
 	anotherValidMemory := "--memory-swap=-1"
-	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
+	if _, _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
 		t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
 		t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
 	}
 	}
 	if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
 	if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
@@ -417,12 +418,12 @@ func TestParseWithExpose(t *testing.T) {
 		"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
 		"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
 	}
 	}
 	for expose, expectedError := range invalids {
 	for expose, expectedError := range invalids {
-		if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
+		if _, _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
 			t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
 			t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
 		}
 		}
 	}
 	}
 	for expose, exposedPorts := range valids {
 	for expose, exposedPorts := range valids {
-		config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
+		config, _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
 		if err != nil {
 		if err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
@@ -436,7 +437,7 @@ func TestParseWithExpose(t *testing.T) {
 		}
 		}
 	}
 	}
 	// Merge with actual published port
 	// Merge with actual published port
-	config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
+	config, _, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -475,7 +476,7 @@ func TestParseDevice(t *testing.T) {
 		},
 		},
 	}
 	}
 	for device, deviceMapping := range valids {
 	for device, deviceMapping := range valids {
-		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
+		_, hostconfig, _, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
 		if err != nil {
 		if err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
@@ -491,11 +492,11 @@ func TestParseDevice(t *testing.T) {
 
 
 func TestParseModes(t *testing.T) {
 func TestParseModes(t *testing.T) {
 	// ipc ko
 	// ipc ko
-	if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
+	if _, _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
 		t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
 		t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
 	}
 	}
 	// ipc ok
 	// ipc ok
-	_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
+	_, hostconfig, _, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -503,11 +504,11 @@ func TestParseModes(t *testing.T) {
 		t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
 		t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
 	}
 	}
 	// pid ko
 	// pid ko
-	if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
+	if _, _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
 		t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
 		t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
 	}
 	}
 	// pid ok
 	// pid ok
-	_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
+	_, hostconfig, _, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -515,11 +516,11 @@ func TestParseModes(t *testing.T) {
 		t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
 		t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
 	}
 	}
 	// uts ko
 	// uts ko
-	if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
+	if _, _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
 		t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
 		t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
 	}
 	}
 	// uts ok
 	// uts ok
-	_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
+	_, hostconfig, _, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -527,11 +528,11 @@ func TestParseModes(t *testing.T) {
 		t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
 		t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
 	}
 	}
 	// shm-size ko
 	// shm-size ko
-	if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
+	if _, _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
 		t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
 		t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
 	}
 	}
 	// shm-size ok
 	// shm-size ok
-	_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
+	_, hostconfig, _, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -560,12 +561,12 @@ func TestParseRestartPolicy(t *testing.T) {
 		},
 		},
 	}
 	}
 	for restart, expectedError := range invalids {
 	for restart, expectedError := range invalids {
-		if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
+		if _, _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
 			t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
 			t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
 		}
 		}
 	}
 	}
 	for restart, expected := range valids {
 	for restart, expected := range valids {
-		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
+		_, hostconfig, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
 		if err != nil {
 		if err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
@@ -577,11 +578,11 @@ func TestParseRestartPolicy(t *testing.T) {
 
 
 func TestParseLoggingOpts(t *testing.T) {
 func TestParseLoggingOpts(t *testing.T) {
 	// logging opts ko
 	// logging opts ko
-	if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" {
+	if _, _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" {
 		t.Fatalf("Expected an error with message 'Invalid logging opts for driver none', got %v", err)
 		t.Fatalf("Expected an error with message 'Invalid logging opts for driver none', got %v", err)
 	}
 	}
 	// logging opts ok
 	// logging opts ok
-	_, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
+	_, hostconfig, _, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -596,18 +597,18 @@ func TestParseEnvfileVariables(t *testing.T) {
 		e = "open nonexistent: The system cannot find the file specified."
 		e = "open nonexistent: The system cannot find the file specified."
 	}
 	}
 	// env ko
 	// env ko
-	if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
+	if _, _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 	}
 	}
 	// env ok
 	// env ok
-	config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
+	config, _, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
 	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
 		t.Fatalf("Expected a a config with [ENV1=value1], got %v", config.Env)
 		t.Fatalf("Expected a a config with [ENV1=value1], got %v", config.Env)
 	}
 	}
-	config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
+	config, _, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -622,18 +623,18 @@ func TestParseLabelfileVariables(t *testing.T) {
 		e = "open nonexistent: The system cannot find the file specified."
 		e = "open nonexistent: The system cannot find the file specified."
 	}
 	}
 	// label ko
 	// label ko
-	if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
+	if _, _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 	}
 	}
 	// label ok
 	// label ok
-	config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
+	config, _, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
 	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
 		t.Fatalf("Expected a a config with [LABEL1:value1], got %v", config.Labels)
 		t.Fatalf("Expected a a config with [LABEL1:value1], got %v", config.Labels)
 	}
 	}
-	config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
+	config, _, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -643,7 +644,7 @@ func TestParseLabelfileVariables(t *testing.T) {
 }
 }
 
 
 func TestParseEntryPoint(t *testing.T) {
 func TestParseEntryPoint(t *testing.T) {
-	config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
+	config, _, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}

+ 22 - 10
vendor/src/github.com/docker/libnetwork/README.md

@@ -15,6 +15,11 @@ There are many networking solutions available to suit a broad range of use-cases
 
 
 
 
 ```go
 ```go
+func main() {
+	if reexec.Init() {
+		return
+	}
+
 	// Select and configure the network driver
 	// Select and configure the network driver
 	networkType := "bridge"
 	networkType := "bridge"
 
 
@@ -24,14 +29,14 @@ There are many networking solutions available to suit a broad range of use-cases
 	genericOption[netlabel.GenericData] = driverOptions
 	genericOption[netlabel.GenericData] = driverOptions
 	controller, err := libnetwork.New(config.OptionDriverConfig(networkType, genericOption))
 	controller, err := libnetwork.New(config.OptionDriverConfig(networkType, genericOption))
 	if err != nil {
 	if err != nil {
-		return
+		log.Fatalf("libnetwork.New: %s", err)
 	}
 	}
 
 
 	// Create a network for containers to join.
 	// Create a network for containers to join.
 	// NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can use.
 	// NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can use.
 	network, err := controller.NewNetwork(networkType, "network1")
 	network, err := controller.NewNetwork(networkType, "network1")
 	if err != nil {
 	if err != nil {
-		return
+		log.Fatalf("controller.NewNetwork: %s", err)
 	}
 	}
 
 
 	// For each new container: allocate IP and interfaces. The returned network
 	// For each new container: allocate IP and interfaces. The returned network
@@ -40,7 +45,7 @@ There are many networking solutions available to suit a broad range of use-cases
 	// from the returned endpoint.
 	// from the returned endpoint.
 	ep, err := network.CreateEndpoint("Endpoint1")
 	ep, err := network.CreateEndpoint("Endpoint1")
 	if err != nil {
 	if err != nil {
-		return
+		log.Fatalf("network.CreateEndpoint: %s", err)
 	}
 	}
 
 
 	// Create the sandbox for the container.
 	// Create the sandbox for the container.
@@ -48,22 +53,29 @@ There are many networking solutions available to suit a broad range of use-cases
 	sbx, err := controller.NewSandbox("container1",
 	sbx, err := controller.NewSandbox("container1",
 		libnetwork.OptionHostname("test"),
 		libnetwork.OptionHostname("test"),
 		libnetwork.OptionDomainname("docker.io"))
 		libnetwork.OptionDomainname("docker.io"))
+	if err != nil {
+		log.Fatalf("controller.NewSandbox: %s", err)
+	}
 
 
 	// A sandbox can join the endpoint via the join api.
 	// A sandbox can join the endpoint via the join api.
 	err = ep.Join(sbx)
 	err = ep.Join(sbx)
 	if err != nil {
 	if err != nil {
-		return
+		log.Fatalf("ep.Join: %s", err)
 	}
 	}
 
 
 	// libnetwork client can check the endpoint's operational data via the Info() API
 	// libnetwork client can check the endpoint's operational data via the Info() API
 	epInfo, err := ep.DriverInfo()
 	epInfo, err := ep.DriverInfo()
-	mapData, ok := epInfo[netlabel.PortMap]
-	if ok {
-		portMapping, ok := mapData.([]types.PortBinding)
-		if ok {
-			fmt.Printf("Current port mapping for endpoint %s: %v", ep.Name(), portMapping)
-		}
+	if err != nil {
+		log.Fatalf("ep.DriverInfo: %s", err)
 	}
 	}
+
+	macAddress, ok := epInfo[netlabel.MacAddress]
+	if !ok {
+		log.Fatalf("failed to get mac address from endpoint info")
+	}
+
+	fmt.Printf("Joined endpoint %s (%s) to sandbox %s (%s)\n", ep.Name(), macAddress, sbx.ContainerID(), sbx.Key())
+}
 ```
 ```
 
 
 ## Future
 ## Future

+ 21 - 27
vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go

@@ -106,12 +106,13 @@ type bridgeNetwork struct {
 }
 }
 
 
 type driver struct {
 type driver struct {
-	config      *configuration
-	network     *bridgeNetwork
-	natChain    *iptables.ChainInfo
-	filterChain *iptables.ChainInfo
-	networks    map[string]*bridgeNetwork
-	store       datastore.DataStore
+	config         *configuration
+	network        *bridgeNetwork
+	natChain       *iptables.ChainInfo
+	filterChain    *iptables.ChainInfo
+	isolationChain *iptables.ChainInfo
+	networks       map[string]*bridgeNetwork
+	store          datastore.DataStore
 	sync.Mutex
 	sync.Mutex
 }
 }
 
 
@@ -244,15 +245,15 @@ func (n *bridgeNetwork) registerIptCleanFunc(clean iptableCleanFunc) {
 	n.iptCleanFuncs = append(n.iptCleanFuncs, clean)
 	n.iptCleanFuncs = append(n.iptCleanFuncs, clean)
 }
 }
 
 
-func (n *bridgeNetwork) getDriverChains() (*iptables.ChainInfo, *iptables.ChainInfo, error) {
+func (n *bridgeNetwork) getDriverChains() (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) {
 	n.Lock()
 	n.Lock()
 	defer n.Unlock()
 	defer n.Unlock()
 
 
 	if n.driver == nil {
 	if n.driver == nil {
-		return nil, nil, types.BadRequestErrorf("no driver found")
+		return nil, nil, nil, types.BadRequestErrorf("no driver found")
 	}
 	}
 
 
-	return n.driver.natChain, n.driver.filterChain, nil
+	return n.driver.natChain, n.driver.filterChain, n.driver.isolationChain, nil
 }
 }
 
 
 func (n *bridgeNetwork) getNetworkBridgeName() string {
 func (n *bridgeNetwork) getNetworkBridgeName() string {
@@ -282,26 +283,16 @@ func (n *bridgeNetwork) getEndpoint(eid string) (*bridgeEndpoint, error) {
 // from each of the other networks
 // from each of the other networks
 func (n *bridgeNetwork) isolateNetwork(others []*bridgeNetwork, enable bool) error {
 func (n *bridgeNetwork) isolateNetwork(others []*bridgeNetwork, enable bool) error {
 	n.Lock()
 	n.Lock()
-	thisV4 := n.bridge.bridgeIPv4
-	thisV6 := getV6Network(n.config, n.bridge)
+	thisIface := n.config.BridgeName
 	n.Unlock()
 	n.Unlock()
 
 
 	// Install the rules to isolate this networks against each of the other networks
 	// Install the rules to isolate this networks against each of the other networks
 	for _, o := range others {
 	for _, o := range others {
 		o.Lock()
 		o.Lock()
-		otherV4 := o.bridge.bridgeIPv4
-		otherV6 := getV6Network(o.config, o.bridge)
+		otherIface := o.config.BridgeName
 		o.Unlock()
 		o.Unlock()
-
-		if !types.CompareIPNet(thisV4, otherV4) {
-			// It's ok to pass a.b.c.d/x, iptables will ignore the host subnet bits
-			if err := setINC(thisV4.String(), otherV4.String(), enable); err != nil {
-				return err
-			}
-		}
-
-		if thisV6 != nil && otherV6 != nil && !types.CompareIPNet(thisV6, otherV6) {
-			if err := setINC(thisV6.String(), otherV6.String(), enable); err != nil {
+		if thisIface != otherIface {
+			if err := setINC(thisIface, otherIface, enable); err != nil {
 				return err
 				return err
 			}
 			}
 		}
 		}
@@ -347,9 +338,11 @@ func (c *networkConfiguration) conflictsWithNetworks(id string, others []*bridge
 
 
 func (d *driver) configure(option map[string]interface{}) error {
 func (d *driver) configure(option map[string]interface{}) error {
 	var (
 	var (
-		config                *configuration
-		err                   error
-		natChain, filterChain *iptables.ChainInfo
+		config         *configuration
+		err            error
+		natChain       *iptables.ChainInfo
+		filterChain    *iptables.ChainInfo
+		isolationChain *iptables.ChainInfo
 	)
 	)
 
 
 	genericData, ok := option[netlabel.GenericData]
 	genericData, ok := option[netlabel.GenericData]
@@ -378,7 +371,7 @@ func (d *driver) configure(option map[string]interface{}) error {
 	}
 	}
 
 
 	if config.EnableIPTables {
 	if config.EnableIPTables {
-		natChain, filterChain, err = setupIPChains(config)
+		natChain, filterChain, isolationChain, err = setupIPChains(config)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -387,6 +380,7 @@ func (d *driver) configure(option map[string]interface{}) error {
 	d.Lock()
 	d.Lock()
 	d.natChain = natChain
 	d.natChain = natChain
 	d.filterChain = filterChain
 	d.filterChain = filterChain
+	d.isolationChain = isolationChain
 	d.config = config
 	d.config = config
 	d.Unlock()
 	d.Unlock()
 
 

+ 81 - 29
vendor/src/github.com/docker/libnetwork/drivers/bridge/setup_ip_tables.go

@@ -11,35 +11,52 @@ import (
 
 
 // DockerChain: DOCKER iptable chain name
 // DockerChain: DOCKER iptable chain name
 const (
 const (
-	DockerChain = "DOCKER"
+	DockerChain    = "DOCKER"
+	IsolationChain = "DOCKER-ISOLATION"
 )
 )
 
 
-func setupIPChains(config *configuration) (*iptables.ChainInfo, *iptables.ChainInfo, error) {
+func setupIPChains(config *configuration) (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) {
 	// Sanity check.
 	// Sanity check.
 	if config.EnableIPTables == false {
 	if config.EnableIPTables == false {
-		return nil, nil, fmt.Errorf("Cannot create new chains, EnableIPTable is disabled")
+		return nil, nil, nil, fmt.Errorf("cannot create new chains, EnableIPTable is disabled")
 	}
 	}
 
 
 	hairpinMode := !config.EnableUserlandProxy
 	hairpinMode := !config.EnableUserlandProxy
 
 
 	natChain, err := iptables.NewChain(DockerChain, iptables.Nat, hairpinMode)
 	natChain, err := iptables.NewChain(DockerChain, iptables.Nat, hairpinMode)
 	if err != nil {
 	if err != nil {
-		return nil, nil, fmt.Errorf("Failed to create NAT chain: %s", err.Error())
+		return nil, nil, nil, fmt.Errorf("failed to create NAT chain: %v", err)
 	}
 	}
 	defer func() {
 	defer func() {
 		if err != nil {
 		if err != nil {
 			if err := iptables.RemoveExistingChain(DockerChain, iptables.Nat); err != nil {
 			if err := iptables.RemoveExistingChain(DockerChain, iptables.Nat); err != nil {
-				logrus.Warnf("Failed on removing iptables NAT chain on cleanup: %v", err)
+				logrus.Warnf("failed on removing iptables NAT chain on cleanup: %v", err)
 			}
 			}
 		}
 		}
 	}()
 	}()
 
 
-	filterChain, err := iptables.NewChain(DockerChain, iptables.Filter, hairpinMode)
+	filterChain, err := iptables.NewChain(DockerChain, iptables.Filter, false)
 	if err != nil {
 	if err != nil {
-		return nil, nil, fmt.Errorf("Failed to create FILTER chain: %s", err.Error())
+		return nil, nil, nil, fmt.Errorf("failed to create FILTER chain: %v", err)
+	}
+	defer func() {
+		if err != nil {
+			if err := iptables.RemoveExistingChain(DockerChain, iptables.Filter); err != nil {
+				logrus.Warnf("failed on removing iptables FILTER chain on cleanup: %v", err)
+			}
+		}
+	}()
+
+	isolationChain, err := iptables.NewChain(IsolationChain, iptables.Filter, false)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err)
+	}
+
+	if err := addReturnRule(IsolationChain); err != nil {
+		return nil, nil, nil, err
 	}
 	}
 
 
-	return natChain, filterChain, nil
+	return natChain, filterChain, isolationChain, nil
 }
 }
 
 
 func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInterface) error {
 func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInterface) error {
@@ -72,7 +89,7 @@ func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInt
 		return setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false)
 		return setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false)
 	})
 	})
 
 
-	natChain, filterChain, err := n.getDriverChains()
+	natChain, filterChain, _, err := n.getDriverChains()
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %s", err.Error())
 		return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %s", err.Error())
 	}
 	}
@@ -86,6 +103,11 @@ func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInt
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("Failed to program FILTER chain: %s", err.Error())
 		return fmt.Errorf("Failed to program FILTER chain: %s", err.Error())
 	}
 	}
+
+	if err := ensureJumpRule("FORWARD", IsolationChain); err != nil {
+		return err
+	}
+
 	n.registerIptCleanFunc(func() error {
 	n.registerIptCleanFunc(func() error {
 		return iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, false)
 		return iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, false)
 	})
 	})
@@ -166,10 +188,8 @@ func programChainRule(rule iptRule, ruleDescr string, insert bool) error {
 	}
 	}
 
 
 	if condition {
 	if condition {
-		if output, err := iptables.Raw(append(prefix, rule.args...)...); err != nil {
+		if err := iptables.RawCombinedOutput(append(prefix, rule.args...)...); err != nil {
 			return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error())
 			return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error())
-		} else if len(output) != 0 {
-			return &iptables.ChainError{Chain: rule.chain, Output: output}
 		}
 		}
 	}
 	}
 
 
@@ -190,20 +210,16 @@ func setIcc(bridgeIface string, iccEnable, insert bool) error {
 			iptables.Raw(append([]string{"-D", chain}, acceptArgs...)...)
 			iptables.Raw(append([]string{"-D", chain}, acceptArgs...)...)
 
 
 			if !iptables.Exists(table, chain, dropArgs...) {
 			if !iptables.Exists(table, chain, dropArgs...) {
-				if output, err := iptables.Raw(append([]string{"-A", chain}, dropArgs...)...); err != nil {
+				if err := iptables.RawCombinedOutput(append([]string{"-A", chain}, dropArgs...)...); err != nil {
 					return fmt.Errorf("Unable to prevent intercontainer communication: %s", err.Error())
 					return fmt.Errorf("Unable to prevent intercontainer communication: %s", err.Error())
-				} else if len(output) != 0 {
-					return fmt.Errorf("Error disabling intercontainer communication: %s", output)
 				}
 				}
 			}
 			}
 		} else {
 		} else {
 			iptables.Raw(append([]string{"-D", chain}, dropArgs...)...)
 			iptables.Raw(append([]string{"-D", chain}, dropArgs...)...)
 
 
 			if !iptables.Exists(table, chain, acceptArgs...) {
 			if !iptables.Exists(table, chain, acceptArgs...) {
-				if output, err := iptables.Raw(append([]string{"-I", chain}, acceptArgs...)...); err != nil {
+				if err := iptables.RawCombinedOutput(append([]string{"-I", chain}, acceptArgs...)...); err != nil {
 					return fmt.Errorf("Unable to allow intercontainer communication: %s", err.Error())
 					return fmt.Errorf("Unable to allow intercontainer communication: %s", err.Error())
-				} else if len(output) != 0 {
-					return fmt.Errorf("Error enabling intercontainer communication: %s", output)
 				}
 				}
 			}
 			}
 		}
 		}
@@ -224,11 +240,11 @@ func setIcc(bridgeIface string, iccEnable, insert bool) error {
 }
 }
 
 
 // Control Inter Network Communication. Install/remove only if it is not/is present.
 // Control Inter Network Communication. Install/remove only if it is not/is present.
-func setINC(network1, network2 string, enable bool) error {
+func setINC(iface1, iface2 string, enable bool) error {
 	var (
 	var (
 		table = iptables.Filter
 		table = iptables.Filter
-		chain = "FORWARD"
-		args  = [2][]string{{"-s", network1, "-d", network2, "-j", "DROP"}, {"-s", network2, "-d", network1, "-j", "DROP"}}
+		chain = IsolationChain
+		args  = [2][]string{{"-i", iface1, "-o", iface2, "-j", "DROP"}, {"-i", iface2, "-o", iface1, "-j", "DROP"}}
 	)
 	)
 
 
 	if enable {
 	if enable {
@@ -236,10 +252,8 @@ func setINC(network1, network2 string, enable bool) error {
 			if iptables.Exists(table, chain, args[i]...) {
 			if iptables.Exists(table, chain, args[i]...) {
 				continue
 				continue
 			}
 			}
-			if output, err := iptables.Raw(append([]string{"-I", chain}, args[i]...)...); err != nil {
-				return fmt.Errorf("unable to add inter-network communication rule: %s", err.Error())
-			} else if len(output) != 0 {
-				return fmt.Errorf("error adding inter-network communication rule: %s", string(output))
+			if err := iptables.RawCombinedOutput(append([]string{"-I", chain}, args[i]...)...); err != nil {
+				return fmt.Errorf("unable to add inter-network communication rule: %v", err)
 			}
 			}
 		}
 		}
 	} else {
 	} else {
@@ -247,13 +261,51 @@ func setINC(network1, network2 string, enable bool) error {
 			if !iptables.Exists(table, chain, args[i]...) {
 			if !iptables.Exists(table, chain, args[i]...) {
 				continue
 				continue
 			}
 			}
-			if output, err := iptables.Raw(append([]string{"-D", chain}, args[i]...)...); err != nil {
-				return fmt.Errorf("unable to remove inter-network communication rule: %s", err.Error())
-			} else if len(output) != 0 {
-				return fmt.Errorf("error removing inter-network communication rule: %s", string(output))
+			if err := iptables.RawCombinedOutput(append([]string{"-D", chain}, args[i]...)...); err != nil {
+				return fmt.Errorf("unable to remove inter-network communication rule: %v", err)
 			}
 			}
 		}
 		}
 	}
 	}
 
 
 	return nil
 	return nil
 }
 }
+
+func addReturnRule(chain string) error {
+	var (
+		table = iptables.Filter
+		args  = []string{"-j", "RETURN"}
+	)
+
+	if iptables.Exists(table, chain, args...) {
+		return nil
+	}
+
+	err := iptables.RawCombinedOutput(append([]string{"-I", chain}, args...)...)
+	if err != nil {
+		return fmt.Errorf("unable to add return rule in %s chain: %s", chain, err.Error())
+	}
+
+	return nil
+}
+
+// Ensure the jump rule is on top
+func ensureJumpRule(fromChain, toChain string) error {
+	var (
+		table = iptables.Filter
+		args  = []string{"-j", toChain}
+	)
+
+	if iptables.Exists(table, fromChain, args...) {
+		err := iptables.RawCombinedOutput(append([]string{"-D", fromChain}, args...)...)
+		if err != nil {
+			return fmt.Errorf("unable to remove jump to %s rule in %s chain: %s", toChain, fromChain, err.Error())
+		}
+	}
+
+	err := iptables.RawCombinedOutput(append([]string{"-I", fromChain}, args...)...)
+	if err != nil {
+		return fmt.Errorf("unable to insert jump to %s rule in %s chain: %s", toChain, fromChain, err.Error())
+	}
+
+	return nil
+}

+ 13 - 21
vendor/src/github.com/docker/libnetwork/drivers/overlay/filter.go

@@ -12,16 +12,6 @@ const globalChain = "DOCKER-OVERLAY"
 
 
 var filterOnce sync.Once
 var filterOnce sync.Once
 
 
-func rawIPTables(args ...string) error {
-	if output, err := iptables.Raw(args...); err != nil {
-		return fmt.Errorf("unable to add overlay filter: %v", err)
-	} else if len(output) != 0 {
-		return fmt.Errorf("unable to add overlay filter: %s", string(output))
-	}
-
-	return nil
-}
-
 func chainExists(cname string) bool {
 func chainExists(cname string) bool {
 	if _, err := iptables.Raw("-L", cname); err != nil {
 	if _, err := iptables.Raw("-L", cname); err != nil {
 		return false
 		return false
@@ -31,12 +21,14 @@ func chainExists(cname string) bool {
 }
 }
 
 
 func setupGlobalChain() {
 func setupGlobalChain() {
-	if err := rawIPTables("-N", globalChain); err != nil {
-		logrus.Debugf("could not create global overlay chain: %v", err)
+	if err := iptables.RawCombinedOutput("-N", globalChain); err != nil {
+		logrus.Errorf("could not create global overlay chain: %v", err)
+		return
 	}
 	}
 
 
-	if err := rawIPTables("-A", globalChain, "-j", "RETURN"); err != nil {
-		logrus.Debugf("could not install default return chain in the overlay global chain: %v", err)
+	if err := iptables.RawCombinedOutput("-A", globalChain, "-j", "RETURN"); err != nil {
+		logrus.Errorf("could not install default return chain in the overlay global chain: %v", err)
+		return
 	}
 	}
 }
 }
 
 
@@ -49,21 +41,21 @@ func setNetworkChain(cname string, remove bool) error {
 	opt := "-N"
 	opt := "-N"
 	// In case of remove, make sure to flush the rules in the chain
 	// In case of remove, make sure to flush the rules in the chain
 	if remove && exists {
 	if remove && exists {
-		if err := rawIPTables("-F", cname); err != nil {
+		if err := iptables.RawCombinedOutput("-F", cname); err != nil {
 			return fmt.Errorf("failed to flush overlay network chain %s rules: %v", cname, err)
 			return fmt.Errorf("failed to flush overlay network chain %s rules: %v", cname, err)
 		}
 		}
 		opt = "-X"
 		opt = "-X"
 	}
 	}
 
 
 	if (!remove && !exists) || (remove && exists) {
 	if (!remove && !exists) || (remove && exists) {
-		if err := rawIPTables(opt, cname); err != nil {
+		if err := iptables.RawCombinedOutput(opt, cname); err != nil {
 			return fmt.Errorf("failed network chain operation %q for chain %s: %v", opt, cname, err)
 			return fmt.Errorf("failed network chain operation %q for chain %s: %v", opt, cname, err)
 		}
 		}
 	}
 	}
 
 
 	if !remove {
 	if !remove {
 		if !iptables.Exists(iptables.Filter, cname, "-j", "DROP") {
 		if !iptables.Exists(iptables.Filter, cname, "-j", "DROP") {
-			if err := rawIPTables("-A", cname, "-j", "DROP"); err != nil {
+			if err := iptables.RawCombinedOutput("-A", cname, "-j", "DROP"); err != nil {
 				return fmt.Errorf("failed adding default drop rule to overlay network chain %s: %v", cname, err)
 				return fmt.Errorf("failed adding default drop rule to overlay network chain %s: %v", cname, err)
 			}
 			}
 		}
 		}
@@ -91,12 +83,12 @@ func setFilters(cname, brName string, remove bool) error {
 		for _, chain := range []string{"OUTPUT", "FORWARD"} {
 		for _, chain := range []string{"OUTPUT", "FORWARD"} {
 			exists := iptables.Exists(iptables.Filter, chain, "-j", globalChain)
 			exists := iptables.Exists(iptables.Filter, chain, "-j", globalChain)
 			if exists {
 			if exists {
-				if err := rawIPTables("-D", chain, "-j", globalChain); err != nil {
+				if err := iptables.RawCombinedOutput("-D", chain, "-j", globalChain); err != nil {
 					return fmt.Errorf("failed to delete overlay hook in chain %s while moving the hook: %v", chain, err)
 					return fmt.Errorf("failed to delete overlay hook in chain %s while moving the hook: %v", chain, err)
 				}
 				}
 			}
 			}
 
 
-			if err := rawIPTables("-I", chain, "-j", globalChain); err != nil {
+			if err := iptables.RawCombinedOutput("-I", chain, "-j", globalChain); err != nil {
 				return fmt.Errorf("failed to insert overlay hook in chain %s: %v", chain, err)
 				return fmt.Errorf("failed to insert overlay hook in chain %s: %v", chain, err)
 			}
 			}
 		}
 		}
@@ -105,7 +97,7 @@ func setFilters(cname, brName string, remove bool) error {
 	// Insert/Delete the rule to jump to per-bridge chain
 	// Insert/Delete the rule to jump to per-bridge chain
 	exists := iptables.Exists(iptables.Filter, globalChain, "-o", brName, "-j", cname)
 	exists := iptables.Exists(iptables.Filter, globalChain, "-o", brName, "-j", cname)
 	if (!remove && !exists) || (remove && exists) {
 	if (!remove && !exists) || (remove && exists) {
-		if err := rawIPTables(opt, globalChain, "-o", brName, "-j", cname); err != nil {
+		if err := iptables.RawCombinedOutput(opt, globalChain, "-o", brName, "-j", cname); err != nil {
 			return fmt.Errorf("failed to add per-bridge filter rule for bridge %s, network chain %s: %v", brName, cname, err)
 			return fmt.Errorf("failed to add per-bridge filter rule for bridge %s, network chain %s: %v", brName, cname, err)
 		}
 		}
 	}
 	}
@@ -115,7 +107,7 @@ func setFilters(cname, brName string, remove bool) error {
 		return nil
 		return nil
 	}
 	}
 
 
-	if err := rawIPTables(opt, cname, "-i", brName, "-j", "ACCEPT"); err != nil {
+	if err := iptables.RawCombinedOutput(opt, cname, "-i", brName, "-j", "ACCEPT"); err != nil {
 		return fmt.Errorf("failed to add overlay filter rile for network chain %s, bridge %s: %v", cname, brName, err)
 		return fmt.Errorf("failed to add overlay filter rile for network chain %s, bridge %s: %v", cname, brName, err)
 	}
 	}
 
 

+ 23 - 7
vendor/src/github.com/docker/libnetwork/endpoint.go

@@ -61,6 +61,7 @@ type endpoint struct {
 	generic       map[string]interface{}
 	generic       map[string]interface{}
 	joinLeaveDone chan struct{}
 	joinLeaveDone chan struct{}
 	prefAddress   net.IP
 	prefAddress   net.IP
+	prefAddressV6 net.IP
 	ipamOptions   map[string]string
 	ipamOptions   map[string]string
 	dbIndex       uint64
 	dbIndex       uint64
 	dbExists      bool
 	dbExists      bool
@@ -688,9 +689,10 @@ func EndpointOptionGeneric(generic map[string]interface{}) EndpointOption {
 }
 }
 
 
 // CreateOptionIpam function returns an option setter for the ipam configuration for this endpoint
 // CreateOptionIpam function returns an option setter for the ipam configuration for this endpoint
-func CreateOptionIpam(prefAddress net.IP, ipamOptions map[string]string) EndpointOption {
+func CreateOptionIpam(ipV4, ipV6 net.IP, ipamOptions map[string]string) EndpointOption {
 	return func(ep *endpoint) {
 	return func(ep *endpoint) {
-		ep.prefAddress = prefAddress
+		ep.prefAddress = ipV4
+		ep.prefAddressV6 = ipV6
 		ep.ipamOptions = ipamOptions
 		ep.ipamOptions = ipamOptions
 	}
 	}
 }
 }
@@ -775,6 +777,8 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
 	var (
 	var (
 		poolID  *string
 		poolID  *string
 		address **net.IPNet
 		address **net.IPNet
+		prefAdd net.IP
+		progAdd net.IP
 	)
 	)
 
 
 	n := ep.getNetwork()
 	n := ep.getNetwork()
@@ -782,9 +786,11 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
 	case 4:
 	case 4:
 		poolID = &ep.iface.v4PoolID
 		poolID = &ep.iface.v4PoolID
 		address = &ep.iface.addr
 		address = &ep.iface.addr
+		prefAdd = ep.prefAddress
 	case 6:
 	case 6:
 		poolID = &ep.iface.v6PoolID
 		poolID = &ep.iface.v6PoolID
 		address = &ep.iface.addrv6
 		address = &ep.iface.addrv6
+		prefAdd = ep.prefAddressV6
 	default:
 	default:
 		return types.InternalErrorf("incorrect ip version number passed: %d", ipVer)
 		return types.InternalErrorf("incorrect ip version number passed: %d", ipVer)
 	}
 	}
@@ -796,12 +802,19 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
 		return nil
 		return nil
 	}
 	}
 
 
+	// The address to program may be chosen by the user or by the network driver in one specific
+	// case to support backward compatibility with `docker daemon --fixed-cidrv6` use case
+	if prefAdd != nil {
+		progAdd = prefAdd
+	} else if *address != nil {
+		progAdd = (*address).IP
+	}
+
 	for _, d := range ipInfo {
 	for _, d := range ipInfo {
-		var prefIP net.IP
-		if *address != nil {
-			prefIP = (*address).IP
+		if progAdd != nil && !d.Pool.Contains(progAdd) {
+			continue
 		}
 		}
-		addr, _, err := ipam.RequestAddress(d.PoolID, prefIP, ep.ipamOptions)
+		addr, _, err := ipam.RequestAddress(d.PoolID, progAdd, ep.ipamOptions)
 		if err == nil {
 		if err == nil {
 			ep.Lock()
 			ep.Lock()
 			*address = addr
 			*address = addr
@@ -809,10 +822,13 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
 			ep.Unlock()
 			ep.Unlock()
 			return nil
 			return nil
 		}
 		}
-		if err != ipamapi.ErrNoAvailableIPs {
+		if err != ipamapi.ErrNoAvailableIPs || progAdd != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
+	if progAdd != nil {
+		return types.BadRequestErrorf("Invalid preferred address %s: It does not belong to any of this network's subnets")
+	}
 	return fmt.Errorf("no available IPv%d addresses on this network's address pools: %s (%s)", ipVer, n.Name(), n.ID())
 	return fmt.Errorf("no available IPv%d addresses on this network's address pools: %s (%s)", ipVer, n.Name(), n.ID())
 }
 }
 
 

+ 10 - 0
vendor/src/github.com/docker/libnetwork/iptables/iptables.go

@@ -312,6 +312,7 @@ func Exists(table Table, chain string, rule ...string) bool {
 	// parse "iptables -S" for the rule (this checks rules in a specific chain
 	// parse "iptables -S" for the rule (this checks rules in a specific chain
 	// in a specific table)
 	// in a specific table)
 	ruleString := strings.Join(rule, " ")
 	ruleString := strings.Join(rule, " ")
+	ruleString = chain + " " + ruleString
 	existingRules, _ := exec.Command(iptablesPath, "-t", string(table), "-S", chain).Output()
 	existingRules, _ := exec.Command(iptablesPath, "-t", string(table), "-S", chain).Output()
 
 
 	return strings.Contains(string(existingRules), ruleString)
 	return strings.Contains(string(existingRules), ruleString)
@@ -351,3 +352,12 @@ func Raw(args ...string) ([]byte, error) {
 
 
 	return output, err
 	return output, err
 }
 }
+
+// RawCombinedOutput inernally calls the Raw function and returns a non nil
+// error if Raw returned a non nil error or a non empty output
+func RawCombinedOutput(args ...string) error {
+	if output, err := Raw(args...); err != nil || len(output) != 0 {
+		return fmt.Errorf("%s (%v)", string(output), err)
+	}
+	return nil
+}

+ 1 - 1
vendor/src/github.com/docker/libnetwork/resolvconf/dns/resolvconf.go

@@ -5,7 +5,7 @@ import (
 )
 )
 
 
 // IPLocalhost is a regex patter for localhost IP address range.
 // IPLocalhost is a regex patter for localhost IP address range.
-const IPLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1))`
+const IPLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)`
 
 
 var localhostIPRegexp = regexp.MustCompile(IPLocalhost)
 var localhostIPRegexp = regexp.MustCompile(IPLocalhost)