ソースを参照

Merge pull request #19001 from aboch/pip

Allow user to choose the IP address for the container
Arnaud Porterie 9 年 前
コミット
05de2aadff
34 ファイル変更584 行追加253 行削除
  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/types"
 	"github.com/docker/engine-api/types/container"
+	networktypes "github.com/docker/engine-api/types/network"
 )
 
 func (cli *DockerCli) pullImage(image string) error {
@@ -79,7 +80,7 @@ func newCIDFile(path string) (*cidFile, error) {
 	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
 	if cidfile != "" {
 		var err error
@@ -107,7 +108,8 @@ func (cli *DockerCli) createContainer(config *container.Config, hostConfig *cont
 	}
 
 	//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 err != nil {
 		if client.IsErrImageNotFound(err) {
@@ -124,7 +126,7 @@ func (cli *DockerCli) createContainer(config *container.Config, hostConfig *cont
 			}
 			// Retry
 			var retryErr error
-			response, retryErr = cli.client.ContainerCreate(config, hostConfig, nil, name)
+			response, retryErr = cli.client.ContainerCreate(config, hostConfig, networkingConfig, name)
 			if retryErr != nil {
 				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")
 	)
 
-	config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args)
+	config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
+
 	if err != nil {
 		cmd.ReportError(err.Error(), true)
 		os.Exit(1)
@@ -165,7 +168,7 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
 		cmd.Usage()
 		return nil
 	}
-	response, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
+	response, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
 	if err != nil {
 		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
 //
-// Usage: docker network connect <NETWORK> <CONTAINER>
+// Usage: docker network connect [OPTIONS] <NETWORK> <CONTAINER>
 func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
 	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 {
 		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

+ 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")
 	)
 
-	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
 	if err != nil {
 		cmd.ReportError(err.Error(), true)
@@ -145,7 +146,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 		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 {
 		cmd.ReportError(err.Error(), true)
 		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
 	}
 
-	_, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
+	_, hostConfig, _, err := runconfig.DecodeContainerConfig(r.Body)
 	if err != nil {
 		return err
 	}
@@ -358,7 +358,7 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
 
 	name := r.Form.Get("name")
 
-	config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
+	config, hostConfig, networkingConfig, err := runconfig.DecodeContainerConfig(r.Body)
 	if err != nil {
 		return err
 	}
@@ -366,10 +366,11 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
 	adjustCPUShares := version.LessThan("1.19")
 
 	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 {
 		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
 	}
 
-	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.
 		return err
 	}

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

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

+ 8 - 0
container/container_unix.go

@@ -261,6 +261,14 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network) ([]
 		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
 	// to which container was connected to on docker run.
 	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
 		}
 	}
-	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
 }
@@ -562,7 +565,12 @@ func (daemon *Daemon) updateNetwork(container *container.Container) error {
 }
 
 // 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
 	if container.Config.NetworkDisabled || mode.IsContainer() {
 		return nil
@@ -573,14 +581,35 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
 		networkName = daemon.netController.Config().Daemon.DefaultNetwork
 	}
 	if mode.IsUserDefined() {
-		n, err := daemon.FindNetwork(networkName)
+		n, err = daemon.FindNetwork(networkName)
 		if err != nil {
 			return err
 		}
 		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
 }
 
@@ -598,15 +627,15 @@ func (daemon *Daemon) allocateNetwork(container *container.Container) error {
 			return nil
 		}
 
-		err := daemon.updateContainerNetworkSettings(container)
+		err := daemon.updateContainerNetworkSettings(container, nil)
 		if err != nil {
 			return err
 		}
 		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
 		}
 	}
@@ -626,12 +655,65 @@ func (daemon *Daemon) getNetworkSandbox(container *container.Container) libnetwo
 	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
-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 {
 		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
 	}
 	if err := container.ToDiskLocking(); err != nil {
@@ -640,11 +722,15 @@ func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName
 	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() {
 		return runconfig.ErrConflictSharedNetwork
 	}
 
+	if !containertypes.NetworkMode(idOrName).IsUserDefined() && hasUserDefinedIPAddress(endpointConfig) {
+		return runconfig.ErrUnsupportedNetworkAndIP
+	}
+
 	if containertypes.NetworkMode(idOrName).IsBridge() &&
 		daemon.configStore.DisableBridge {
 		container.Config.NetworkDisabled = true
@@ -658,12 +744,20 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
 		return err
 	}
 
+	if err := validateNetworkingConfig(n, endpointConfig); err != nil {
+		return err
+	}
+
 	if updateSettings {
 		if err := daemon.updateNetworkSettings(container, n); err != nil {
 			return err
 		}
 	}
 
+	if endpointConfig != nil {
+		container.NetworkSettings.Networks[n.Name()] = endpointConfig
+	}
+
 	ep, err := container.GetEndpointInNetwork(n)
 	if err == nil {
 		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
 	settings := container.NetworkSettings.Networks
+	if sid == "" || len(settings) == 0 {
+		return
+	}
+
 	var networks []libnetwork.Network
-	for n := range settings {
+	for n, epSettings := range settings {
 		if nw, err := daemon.FindNetwork(n); err == nil {
 			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)

+ 3 - 2
daemon/container_operations_windows.go

@@ -10,6 +10,7 @@ import (
 	"github.com/docker/docker/daemon/execdriver/windows"
 	derr "github.com/docker/docker/errors"
 	"github.com/docker/docker/layer"
+	networktypes "github.com/docker/engine-api/types/network"
 	"github.com/docker/libnetwork"
 )
 
@@ -18,7 +19,7 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s
 }
 
 // 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
 }
 
@@ -27,7 +28,7 @@ func (daemon *Daemon) initializeNetworking(container *container.Container) error
 }
 
 // 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
 }
 

+ 7 - 1
daemon/create.go

@@ -11,6 +11,7 @@ import (
 	volumestore "github.com/docker/docker/volume/store"
 	"github.com/docker/engine-api/types"
 	containertypes "github.com/docker/engine-api/types/container"
+	networktypes "github.com/docker/engine-api/types/network"
 	"github.com/opencontainers/runc/libcontainer/label"
 )
 
@@ -108,7 +109,12 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig) (retC *containe
 		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
 	}
 

+ 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
 // network. If either cannot be found, an err is returned. If the
 // 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)
 	if err != nil {
 		return err
 	}
-	return daemon.ConnectToNetwork(container, networkName)
+	return daemon.ConnectToNetwork(container, networkName, endpointConfig)
 }
 
 // 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 
   device (in bytes per second or IO per second).
 * `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
 

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

@@ -3031,7 +3031,13 @@ POST /networks/22be93d5babb089c5aab8dbc369042fad48ff791584ca2da2100db837a1c7c30/
 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
 ```
 
+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.
 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
 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.
 

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

@@ -56,6 +56,8 @@ parent = "smn_cli"
       --log-opt=[]                  Log driver specific options
       -m, --memory=""               Memory limit
       --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-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.

+ 2 - 0
docs/reference/run.md

@@ -275,6 +275,8 @@ of the containers.
                         '<network-name>|<network-id>': connect to a user-defined network
     --add-host=""    : Add a line to /etc/hosts (host:IP)
     --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
 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.
 
 ```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:
@@ -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 inspect isolated_nw
-[[
+[
     {
         "Name": "isolated_nw",
-        "Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a",
+        "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
         "Scope": "local",
         "Driver": "bridge",
         "IPAM": {
             "Driver": "default",
             "Config": [
-                {}
+                {
+                    "Subnet": "172.25.0.0/16"
+                }
             ]
         },
         "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": ""
             }
         },
@@ -150,20 +153,28 @@ $ docker network inspect isolated_nw
 ```
 
 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:
 
 ```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`.
 
 ```bash
 $ 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.
 
@@ -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
 {
     "bridge": {
-        "EndpointID": "281b5ead415cf48a6a84fd1a6504342c76e9091fe09b4fdbcc4a01c30b0d3c5b",
+        "EndpointID": "0099f9efb5a3727f6a554f176b1e96fca34cae773da68b3b6a26d046c12cb365",
         "Gateway": "172.17.0.1",
         "GlobalIPv6Address": "",
         "GlobalIPv6PrefixLen": 0,
+        "IPAMConfig": null,
         "IPAddress": "172.17.0.3",
         "IPPrefixLen": 16,
         "IPv6Gateway": "",
         "MacAddress": "02:42:ac:11:00:03"
     },
     "isolated_nw": {
-        "EndpointID": "0e24479cfaafb029104999b4e120858a07b19b1b6d956ae56811033e45d68ad9",
-        "Gateway": "172.21.0.1",
+        "EndpointID": "11cedac1810e864d6b1589d92da12af66203879ab89f4ccd8c8fdaa9b1c48b1d",
+        "Gateway": "172.25.0.1",
         "GlobalIPv6Address": "",
         "GlobalIPv6PrefixLen": 0,
-        "IPAddress": "172.21.0.2",
+        "IPAMConfig": null,
+        "IPAddress": "172.25.0.2",
         "IPPrefixLen": 16,
         "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)
 
 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
           RX packets:8 errors:0 dropped:0 overruns:0 frame:0
           TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
@@ -252,19 +265,19 @@ fe00::0	ip6-localnet
 ff00::0	ip6-mcastprefix
 ff02::1	ip6-allnodes
 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.
 
 ```bash
 / # 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 ---
 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
-[[
+[
     {
         "Name": "isolated_nw",
-        "Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a",
+        "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
         "Scope": "local",
         "Driver": "bridge",
         "IPAM": {
             "Driver": "default",
             "Config": [
-                {}
+                {
+                    "Subnet": "172.25.0.0/16"
+                }
             ]
         },
         "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": ""
             }
         },
@@ -393,7 +409,7 @@ lo        Link encap:Local Loopback
           RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
 
 / # ping container3
-PING container3 (172.20.0.1): 56 data bytes
+PING container3 (172.25.3.3): 56 data bytes
 ^C
 --- container3 ping statistics ---
 2 packets transmitted, 0 packets received, 100% packet loss
@@ -426,13 +442,15 @@ docker network inspect isolated_nw
 [
     {
         "Name": "isolated_nw",
-        "Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a",
+        "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
         "Scope": "local",
         "Driver": "bridge",
         "IPAM": {
             "Driver": "default",
             "Config": [
-                {}
+                {
+                    "Subnet": "172.25.0.0/16"
+                }
             ]
         },
         "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
 
 #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/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b
 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, "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/engine-api/types/container"
+	networktypes "github.com/docker/engine-api/types/network"
 )
 
 // DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
 // struct and returns both a Config and an HostConfig struct
 // Be aware this function is not checking whether the resulted structs are nil,
 // 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
 
 	decoder := json.NewDecoder(src)
 	if err := decoder.Decode(&w); err != nil {
-		return nil, nil, err
+		return nil, nil, nil, err
 	}
 
 	hc := w.getHostConfig()
@@ -33,21 +34,21 @@ func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon
 
 		// Now validate all the volumes and binds
 		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
 	// on the client, as only the daemon knows what is valid for the platform.
 	if err := ValidateNetMode(w.Config, hc); err != nil {
-		return nil, nil, err
+		return nil, nil, nil, err
 	}
 
 	// Validate the isolation level
 	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

+ 9 - 8
runconfig/config_test.go

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

+ 8 - 4
runconfig/config_unix.go

@@ -2,15 +2,19 @@
 
 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)
 // and the corresponding HostConfig (non-portable).
 type ContainerConfigWrapper struct {
 	*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.

+ 6 - 2
runconfig/config_windows.go

@@ -1,12 +1,16 @@
 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)
 // and the corresponding HostConfig (non-portable).
 type ContainerConfigWrapper struct {
 	*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.

+ 4 - 0
runconfig/errors.go

@@ -29,4 +29,8 @@ var (
 	ErrConflictNetworkPublishPorts = fmt.Errorf("Conflicting options: port publishing and the container type 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")
+	// 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/signal"
 	"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/go-connections/nat"
 	"github.com/docker/go-units"
@@ -19,7 +20,7 @@ import (
 // Parse parses the specified args for the specified command and generates a Config,
 // a HostConfig and returns them with the specified command.
 // 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 (
 		// FIXME: use utils.ListOpts for attach and volumes?
 		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)")
 		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)")
+		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")
 		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")
@@ -119,7 +122,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	cmd.Require(flag.Min, 1)
 
 	if err := cmd.ParseFlags(args, true); err != nil {
-		return nil, nil, cmd, err
+		return nil, nil, nil, cmd, err
 	}
 
 	var (
@@ -131,7 +134,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	// Validate the input mac address
 	if *flMacAddress != "" {
 		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 {
@@ -149,7 +152,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	if *flMemoryString != "" {
 		flMemory, err = units.RAMInBytes(*flMemoryString)
 		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 != "" {
 		MemoryReservation, err = units.RAMInBytes(*flMemoryReservation)
 		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 {
 			memorySwap, err = units.RAMInBytes(*flMemorySwap)
 			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 != "" {
 		KernelMemory, err = units.RAMInBytes(*flKernelMemory)
 		if err != nil {
-			return nil, nil, cmd, err
+			return nil, nil, nil, cmd, err
 		}
 	}
 
 	swappiness := *flSwappiness
 	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
 	if *flShmSize != "" {
 		shmSize, err = units.RAMInBytes(*flShmSize)
 		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() {
 		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
 			if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
-				return nil, nil, cmd, err
+				return nil, nil, nil, cmd, err
 			}
 			tmpfs[arr[0]] = arr[1]
 		} else {
@@ -243,13 +246,13 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 
 	ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
 	if err != nil {
-		return nil, nil, cmd, err
+		return nil, nil, nil, cmd, err
 	}
 
 	// Merge in exposed ports to the map of published ports
 	for _, e := range flExpose.GetAll() {
 		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>]
 		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
 		start, end, err := nat.ParsePortRange(port)
 		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++ {
 			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
 			if err != nil {
-				return nil, nil, cmd, err
+				return nil, nil, nil, cmd, err
 			}
 			if _, exists := ports[p]; !exists {
 				ports[p] = struct{}{}
@@ -275,7 +278,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	for _, device := range flDevices.GetAll() {
 		deviceMapping, err := ParseDevice(device)
 		if err != nil {
-			return nil, nil, cmd, err
+			return nil, nil, nil, cmd, err
 		}
 		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
 	envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
 	if err != nil {
-		return nil, nil, cmd, err
+		return nil, nil, nil, cmd, err
 	}
 
 	// collect all the labels for the container
 	labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
 	if err != nil {
-		return nil, nil, cmd, err
+		return nil, nil, nil, cmd, err
 	}
 
 	ipcMode := container.IpcMode(*flIpcMode)
 	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)
 	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)
 	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)
 	if err != nil {
-		return nil, nil, cmd, err
+		return nil, nil, nil, cmd, err
 	}
 
 	loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll())
 	if err != nil {
-		return nil, nil, cmd, err
+		return nil, nil, nil, cmd, err
 	}
 
 	resources := container.Resources{
@@ -405,7 +408,21 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	if config.OpenStdin && config.AttachStdin {
 		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

+ 30 - 29
runconfig/opts/parse_test.go

@@ -13,10 +13,11 @@ import (
 	flag "github.com/docker/docker/pkg/mflag"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/engine-api/types/container"
+	networktypes "github.com/docker/engine-api/types/network"
 	"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.SetOutput(ioutil.Discard)
 	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) {
-	config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
+	config, hostConfig, _, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
 	return config, hostConfig, err
 }
 
@@ -304,7 +305,7 @@ func callDecodeContainerConfig(volumes []string, binds []string) (*container.Con
 	if b, err = json.Marshal(w); err != nil {
 		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 {
 		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) {
 	invalidMacAddress := "--mac-address=invalidMacAddress"
 	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)
 	}
 	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) {
 	invalidMemory := "--memory=invalid"
 	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)
 	}
 	if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
@@ -372,7 +373,7 @@ func TestParseWithMemorySwap(t *testing.T) {
 	invalidMemory := "--memory-swap=invalid"
 	validMemory := "--memory-swap=1G"
 	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)
 	}
 	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"},
 	}
 	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)
 		}
 	}
 	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 {
 			t.Fatal(err)
 		}
@@ -436,7 +437,7 @@ func TestParseWithExpose(t *testing.T) {
 		}
 	}
 	// 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 {
 		t.Fatal(err)
 	}
@@ -475,7 +476,7 @@ func TestParseDevice(t *testing.T) {
 		},
 	}
 	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 {
 			t.Fatal(err)
 		}
@@ -491,11 +492,11 @@ func TestParseDevice(t *testing.T) {
 
 func TestParseModes(t *testing.T) {
 	// 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)
 	}
 	// ipc ok
-	_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
+	_, hostconfig, _, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -503,11 +504,11 @@ func TestParseModes(t *testing.T) {
 		t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
 	}
 	// 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)
 	}
 	// pid ok
-	_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
+	_, hostconfig, _, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -515,11 +516,11 @@ func TestParseModes(t *testing.T) {
 		t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
 	}
 	// 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)
 	}
 	// uts ok
-	_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
+	_, hostconfig, _, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -527,11 +528,11 @@ func TestParseModes(t *testing.T) {
 		t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
 	}
 	// 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)
 	}
 	// shm-size ok
-	_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
+	_, hostconfig, _, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -560,12 +561,12 @@ func TestParseRestartPolicy(t *testing.T) {
 		},
 	}
 	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)
 		}
 	}
 	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 {
 			t.Fatal(err)
 		}
@@ -577,11 +578,11 @@ func TestParseRestartPolicy(t *testing.T) {
 
 func TestParseLoggingOpts(t *testing.T) {
 	// 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)
 	}
 	// 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 {
 		t.Fatal(err)
 	}
@@ -596,18 +597,18 @@ func TestParseEnvfileVariables(t *testing.T) {
 		e = "open nonexistent: The system cannot find the file specified."
 	}
 	// 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)
 	}
 	// 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 {
 		t.Fatal(err)
 	}
 	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
 		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 {
 		t.Fatal(err)
 	}
@@ -622,18 +623,18 @@ func TestParseLabelfileVariables(t *testing.T) {
 		e = "open nonexistent: The system cannot find the file specified."
 	}
 	// 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)
 	}
 	// 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 {
 		t.Fatal(err)
 	}
 	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
 		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 {
 		t.Fatal(err)
 	}
@@ -643,7 +644,7 @@ func TestParseLabelfileVariables(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 {
 		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
+func main() {
+	if reexec.Init() {
+		return
+	}
+
 	// Select and configure the network driver
 	networkType := "bridge"
 
@@ -24,14 +29,14 @@ There are many networking solutions available to suit a broad range of use-cases
 	genericOption[netlabel.GenericData] = driverOptions
 	controller, err := libnetwork.New(config.OptionDriverConfig(networkType, genericOption))
 	if err != nil {
-		return
+		log.Fatalf("libnetwork.New: %s", err)
 	}
 
 	// Create a network for containers to join.
 	// NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can use.
 	network, err := controller.NewNetwork(networkType, "network1")
 	if err != nil {
-		return
+		log.Fatalf("controller.NewNetwork: %s", err)
 	}
 
 	// 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.
 	ep, err := network.CreateEndpoint("Endpoint1")
 	if err != nil {
-		return
+		log.Fatalf("network.CreateEndpoint: %s", err)
 	}
 
 	// 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",
 		libnetwork.OptionHostname("test"),
 		libnetwork.OptionDomainname("docker.io"))
+	if err != nil {
+		log.Fatalf("controller.NewSandbox: %s", err)
+	}
 
 	// A sandbox can join the endpoint via the join api.
 	err = ep.Join(sbx)
 	if err != nil {
-		return
+		log.Fatalf("ep.Join: %s", err)
 	}
 
 	// libnetwork client can check the endpoint's operational data via the Info() API
 	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

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

@@ -106,12 +106,13 @@ type bridgeNetwork 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
 }
 
@@ -244,15 +245,15 @@ func (n *bridgeNetwork) registerIptCleanFunc(clean iptableCleanFunc) {
 	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()
 	defer n.Unlock()
 
 	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 {
@@ -282,26 +283,16 @@ func (n *bridgeNetwork) getEndpoint(eid string) (*bridgeEndpoint, error) {
 // from each of the other networks
 func (n *bridgeNetwork) isolateNetwork(others []*bridgeNetwork, enable bool) error {
 	n.Lock()
-	thisV4 := n.bridge.bridgeIPv4
-	thisV6 := getV6Network(n.config, n.bridge)
+	thisIface := n.config.BridgeName
 	n.Unlock()
 
 	// Install the rules to isolate this networks against each of the other networks
 	for _, o := range others {
 		o.Lock()
-		otherV4 := o.bridge.bridgeIPv4
-		otherV6 := getV6Network(o.config, o.bridge)
+		otherIface := o.config.BridgeName
 		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
 			}
 		}
@@ -347,9 +338,11 @@ func (c *networkConfiguration) conflictsWithNetworks(id string, others []*bridge
 
 func (d *driver) configure(option map[string]interface{}) error {
 	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]
@@ -378,7 +371,7 @@ func (d *driver) configure(option map[string]interface{}) error {
 	}
 
 	if config.EnableIPTables {
-		natChain, filterChain, err = setupIPChains(config)
+		natChain, filterChain, isolationChain, err = setupIPChains(config)
 		if err != nil {
 			return err
 		}
@@ -387,6 +380,7 @@ func (d *driver) configure(option map[string]interface{}) error {
 	d.Lock()
 	d.natChain = natChain
 	d.filterChain = filterChain
+	d.isolationChain = isolationChain
 	d.config = config
 	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
 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.
 	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
 
 	natChain, err := iptables.NewChain(DockerChain, iptables.Nat, hairpinMode)
 	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() {
 		if 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 {
-		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 {
@@ -72,7 +89,7 @@ func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInt
 		return setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false)
 	})
 
-	natChain, filterChain, err := n.getDriverChains()
+	natChain, filterChain, _, err := n.getDriverChains()
 	if err != nil {
 		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 {
 		return fmt.Errorf("Failed to program FILTER chain: %s", err.Error())
 	}
+
+	if err := ensureJumpRule("FORWARD", IsolationChain); err != nil {
+		return err
+	}
+
 	n.registerIptCleanFunc(func() error {
 		return iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, false)
 	})
@@ -166,10 +188,8 @@ func programChainRule(rule iptRule, ruleDescr string, insert bool) error {
 	}
 
 	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())
-		} 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...)...)
 
 			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())
-				} else if len(output) != 0 {
-					return fmt.Errorf("Error disabling intercontainer communication: %s", output)
 				}
 			}
 		} else {
 			iptables.Raw(append([]string{"-D", chain}, dropArgs...)...)
 
 			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())
-				} 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.
-func setINC(network1, network2 string, enable bool) error {
+func setINC(iface1, iface2 string, enable bool) error {
 	var (
 		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 {
@@ -236,10 +252,8 @@ func setINC(network1, network2 string, enable bool) error {
 			if iptables.Exists(table, chain, args[i]...) {
 				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 {
@@ -247,13 +261,51 @@ func setINC(network1, network2 string, enable bool) error {
 			if !iptables.Exists(table, chain, args[i]...) {
 				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
 }
+
+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
 
-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 {
 	if _, err := iptables.Raw("-L", cname); err != nil {
 		return false
@@ -31,12 +21,14 @@ func chainExists(cname string) bool {
 }
 
 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"
 	// In case of remove, make sure to flush the rules in the chain
 	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)
 		}
 		opt = "-X"
 	}
 
 	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)
 		}
 	}
 
 	if !remove {
 		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)
 			}
 		}
@@ -91,12 +83,12 @@ func setFilters(cname, brName string, remove bool) error {
 		for _, chain := range []string{"OUTPUT", "FORWARD"} {
 			exists := iptables.Exists(iptables.Filter, chain, "-j", globalChain)
 			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)
 				}
 			}
 
-			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)
 			}
 		}
@@ -105,7 +97,7 @@ func setFilters(cname, brName string, remove bool) error {
 	// Insert/Delete the rule to jump to per-bridge chain
 	exists := iptables.Exists(iptables.Filter, globalChain, "-o", brName, "-j", cname)
 	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)
 		}
 	}
@@ -115,7 +107,7 @@ func setFilters(cname, brName string, remove bool) error {
 		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)
 	}
 

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

@@ -61,6 +61,7 @@ type endpoint struct {
 	generic       map[string]interface{}
 	joinLeaveDone chan struct{}
 	prefAddress   net.IP
+	prefAddressV6 net.IP
 	ipamOptions   map[string]string
 	dbIndex       uint64
 	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
-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) {
-		ep.prefAddress = prefAddress
+		ep.prefAddress = ipV4
+		ep.prefAddressV6 = ipV6
 		ep.ipamOptions = ipamOptions
 	}
 }
@@ -775,6 +777,8 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
 	var (
 		poolID  *string
 		address **net.IPNet
+		prefAdd net.IP
+		progAdd net.IP
 	)
 
 	n := ep.getNetwork()
@@ -782,9 +786,11 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
 	case 4:
 		poolID = &ep.iface.v4PoolID
 		address = &ep.iface.addr
+		prefAdd = ep.prefAddress
 	case 6:
 		poolID = &ep.iface.v6PoolID
 		address = &ep.iface.addrv6
+		prefAdd = ep.prefAddressV6
 	default:
 		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
 	}
 
+	// 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 {
-		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 {
 			ep.Lock()
 			*address = addr
@@ -809,10 +822,13 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
 			ep.Unlock()
 			return nil
 		}
-		if err != ipamapi.ErrNoAvailableIPs {
+		if err != ipamapi.ErrNoAvailableIPs || progAdd != nil {
 			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())
 }
 

+ 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
 	// in a specific table)
 	ruleString := strings.Join(rule, " ")
+	ruleString = chain + " " + ruleString
 	existingRules, _ := exec.Command(iptablesPath, "-t", string(table), "-S", chain).Output()
 
 	return strings.Contains(string(existingRules), ruleString)
@@ -351,3 +352,12 @@ func Raw(args ...string) ([]byte, error) {
 
 	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.
-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)