Browse code

Merge pull request #19001 from aboch/pip

Allow user to choose the IP address for the container

Arnaud Porterie authored on 2016/01/09 04:49:20
Showing 34 changed files
... ...
@@ -13,6 +13,7 @@ import (
13 13
 	"github.com/docker/engine-api/client"
14 14
 	"github.com/docker/engine-api/types"
15 15
 	"github.com/docker/engine-api/types/container"
16
+	networktypes "github.com/docker/engine-api/types/network"
16 17
 )
17 18
 
18 19
 func (cli *DockerCli) pullImage(image string) error {
... ...
@@ -79,7 +80,7 @@ func newCIDFile(path string) (*cidFile, error) {
79 79
 	return &cidFile{path: path, file: f}, nil
80 80
 }
81 81
 
82
-func (cli *DockerCli) createContainer(config *container.Config, hostConfig *container.HostConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
82
+func (cli *DockerCli) createContainer(config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
83 83
 	var containerIDFile *cidFile
84 84
 	if cidfile != "" {
85 85
 		var err error
... ...
@@ -107,7 +108,8 @@ func (cli *DockerCli) createContainer(config *container.Config, hostConfig *cont
107 107
 	}
108 108
 
109 109
 	//create the container
110
-	response, err := cli.client.ContainerCreate(config, hostConfig, nil, name)
110
+	response, err := cli.client.ContainerCreate(config, hostConfig, networkingConfig, name)
111
+
111 112
 	//if image not found try to pull it
112 113
 	if err != nil {
113 114
 		if client.IsErrImageNotFound(err) {
... ...
@@ -124,7 +126,7 @@ func (cli *DockerCli) createContainer(config *container.Config, hostConfig *cont
124 124
 			}
125 125
 			// Retry
126 126
 			var retryErr error
127
-			response, retryErr = cli.client.ContainerCreate(config, hostConfig, nil, name)
127
+			response, retryErr = cli.client.ContainerCreate(config, hostConfig, networkingConfig, name)
128 128
 			if retryErr != nil {
129 129
 				return nil, retryErr
130 130
 			}
... ...
@@ -156,7 +158,8 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
156 156
 		flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
157 157
 	)
158 158
 
159
-	config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args)
159
+	config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
160
+
160 161
 	if err != nil {
161 162
 		cmd.ReportError(err.Error(), true)
162 163
 		os.Exit(1)
... ...
@@ -165,7 +168,7 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
165 165
 		cmd.Usage()
166 166
 		return nil
167 167
 	}
168
-	response, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
168
+	response, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
169 169
 	if err != nil {
170 170
 		return err
171 171
 	}
... ...
@@ -107,15 +107,22 @@ func (cli *DockerCli) CmdNetworkRm(args ...string) error {
107 107
 
108 108
 // CmdNetworkConnect connects a container to a network
109 109
 //
110
-// Usage: docker network connect <NETWORK> <CONTAINER>
110
+// Usage: docker network connect [OPTIONS] <NETWORK> <CONTAINER>
111 111
 func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
112 112
 	cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
113
-	cmd.Require(flag.Exact, 2)
113
+	flIPAddress := cmd.String([]string{"-ip"}, "", "IP Address")
114
+	flIPv6Address := cmd.String([]string{"-ip6"}, "", "IPv6 Address")
115
+	cmd.Require(flag.Min, 2)
114 116
 	if err := cmd.ParseFlags(args, true); err != nil {
115 117
 		return err
116 118
 	}
117
-
118
-	return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), nil)
119
+	epConfig := &network.EndpointSettings{
120
+		IPAMConfig: &network.EndpointIPAMConfig{
121
+			IPv4Address: *flIPAddress,
122
+			IPv6Address: *flIPv6Address,
123
+		},
124
+	}
125
+	return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), epConfig)
119 126
 }
120 127
 
121 128
 // CmdNetworkDisconnect disconnects a container from a network
... ...
@@ -82,7 +82,8 @@ func (cli *DockerCli) CmdRun(args ...string) error {
82 82
 		ErrConflictDetachAutoRemove           = fmt.Errorf("Conflicting options: --rm and -d")
83 83
 	)
84 84
 
85
-	config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args)
85
+	config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
86
+
86 87
 	// just in case the Parse does not exit
87 88
 	if err != nil {
88 89
 		cmd.ReportError(err.Error(), true)
... ...
@@ -145,7 +146,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
145 145
 		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
146 146
 	}
147 147
 
148
-	createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
148
+	createResponse, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
149 149
 	if err != nil {
150 150
 		cmd.ReportError(err.Error(), true)
151 151
 		return runStartContainerErr(err)
... ...
@@ -332,7 +332,7 @@ func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.Respon
332 332
 		return err
333 333
 	}
334 334
 
335
-	_, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
335
+	_, hostConfig, _, err := runconfig.DecodeContainerConfig(r.Body)
336 336
 	if err != nil {
337 337
 		return err
338 338
 	}
... ...
@@ -358,7 +358,7 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
358 358
 
359 359
 	name := r.Form.Get("name")
360 360
 
361
-	config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
361
+	config, hostConfig, networkingConfig, err := runconfig.DecodeContainerConfig(r.Body)
362 362
 	if err != nil {
363 363
 		return err
364 364
 	}
... ...
@@ -366,10 +366,11 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
366 366
 	adjustCPUShares := version.LessThan("1.19")
367 367
 
368 368
 	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
369
-		Name:            name,
370
-		Config:          config,
371
-		HostConfig:      hostConfig,
372
-		AdjustCPUShares: adjustCPUShares,
369
+		Name:             name,
370
+		Config:           config,
371
+		HostConfig:       hostConfig,
372
+		NetworkingConfig: networkingConfig,
373
+		AdjustCPUShares:  adjustCPUShares,
373 374
 	})
374 375
 	if err != nil {
375 376
 		return err
... ...
@@ -39,7 +39,7 @@ func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.
39 39
 		pause = true
40 40
 	}
41 41
 
42
-	c, _, err := runconfig.DecodeContainerConfig(r.Body)
42
+	c, _, _, err := runconfig.DecodeContainerConfig(r.Body)
43 43
 	if err != nil && err != io.EOF { //Do not fail if body is empty.
44 44
 		return err
45 45
 	}
... ...
@@ -2,7 +2,6 @@ package network
2 2
 
3 3
 import (
4 4
 	"github.com/docker/engine-api/types/network"
5
-
6 5
 	"github.com/docker/libnetwork"
7 6
 )
8 7
 
... ...
@@ -15,7 +14,7 @@ type Backend interface {
15 15
 	GetAllNetworks() []libnetwork.Network
16 16
 	CreateNetwork(name, driver string, ipam network.IPAM,
17 17
 		options map[string]string) (libnetwork.Network, error)
18
-	ConnectContainerToNetwork(containerName, networkName string) error
18
+	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
19 19
 	DisconnectContainerFromNetwork(containerName string,
20 20
 		network libnetwork.Network) error
21 21
 	NetworkControllerEnabled() bool
... ...
@@ -122,7 +122,7 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
122 122
 		return err
123 123
 	}
124 124
 
125
-	return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name())
125
+	return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name(), connect.EndpointConfig)
126 126
 }
127 127
 
128 128
 func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
... ...
@@ -261,6 +261,14 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network) ([]
261 261
 		createOptions = append(createOptions, libnetwork.CreateOptionAnonymous())
262 262
 	}
263 263
 
264
+	if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok {
265
+		ipam := epConfig.IPAMConfig
266
+		if ipam != nil && (ipam.IPv4Address != "" || ipam.IPv6Address != "") {
267
+			createOptions = append(createOptions,
268
+				libnetwork.CreateOptionIpam(net.ParseIP(ipam.IPv4Address), net.ParseIP(ipam.IPv6Address), nil))
269
+		}
270
+	}
271
+
264 272
 	// Other configs are applicable only for the endpoint in the network
265 273
 	// to which container was connected to on docker run.
266 274
 	if n.Name() != container.HostConfig.NetworkMode.NetworkName() &&
... ...
@@ -503,7 +503,10 @@ func (daemon *Daemon) updateNetworkSettings(container *container.Container, n li
503 503
 			return runconfig.ErrConflictNoNetwork
504 504
 		}
505 505
 	}
506
-	container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
506
+
507
+	if _, ok := container.NetworkSettings.Networks[n.Name()]; !ok {
508
+		container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
509
+	}
507 510
 
508 511
 	return nil
509 512
 }
... ...
@@ -562,7 +565,12 @@ func (daemon *Daemon) updateNetwork(container *container.Container) error {
562 562
 }
563 563
 
564 564
 // updateContainerNetworkSettings update the network settings
565
-func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container) error {
565
+func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
566
+	var (
567
+		n   libnetwork.Network
568
+		err error
569
+	)
570
+
566 571
 	mode := container.HostConfig.NetworkMode
567 572
 	if container.Config.NetworkDisabled || mode.IsContainer() {
568 573
 		return nil
... ...
@@ -573,14 +581,35 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
573 573
 		networkName = daemon.netController.Config().Daemon.DefaultNetwork
574 574
 	}
575 575
 	if mode.IsUserDefined() {
576
-		n, err := daemon.FindNetwork(networkName)
576
+		n, err = daemon.FindNetwork(networkName)
577 577
 		if err != nil {
578 578
 			return err
579 579
 		}
580 580
 		networkName = n.Name()
581 581
 	}
582
-	container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings)
583
-	container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings)
582
+	if container.NetworkSettings == nil {
583
+		container.NetworkSettings = &network.Settings{}
584
+	}
585
+	if endpointsConfig != nil {
586
+		container.NetworkSettings.Networks = endpointsConfig
587
+	}
588
+	if container.NetworkSettings.Networks == nil {
589
+		container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings)
590
+		container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings)
591
+	}
592
+	if !mode.IsUserDefined() {
593
+		return nil
594
+	}
595
+	// Make sure to internally store the per network endpoint config by network name
596
+	if _, ok := container.NetworkSettings.Networks[networkName]; ok {
597
+		return nil
598
+	}
599
+	if nwConfig, ok := container.NetworkSettings.Networks[n.ID()]; ok {
600
+		container.NetworkSettings.Networks[networkName] = nwConfig
601
+		delete(container.NetworkSettings.Networks, n.ID())
602
+		return nil
603
+	}
604
+
584 605
 	return nil
585 606
 }
586 607
 
... ...
@@ -598,15 +627,15 @@ func (daemon *Daemon) allocateNetwork(container *container.Container) error {
598 598
 			return nil
599 599
 		}
600 600
 
601
-		err := daemon.updateContainerNetworkSettings(container)
601
+		err := daemon.updateContainerNetworkSettings(container, nil)
602 602
 		if err != nil {
603 603
 			return err
604 604
 		}
605 605
 		updateSettings = true
606 606
 	}
607 607
 
608
-	for n := range container.NetworkSettings.Networks {
609
-		if err := daemon.connectToNetwork(container, n, updateSettings); err != nil {
608
+	for n, nConf := range container.NetworkSettings.Networks {
609
+		if err := daemon.connectToNetwork(container, n, nConf, updateSettings); err != nil {
610 610
 			return err
611 611
 		}
612 612
 	}
... ...
@@ -626,12 +655,65 @@ func (daemon *Daemon) getNetworkSandbox(container *container.Container) libnetwo
626 626
 	return sb
627 627
 }
628 628
 
629
+// hasUserDefinedIPAddress returns whether the passed endpoint configuration contains IP address configuration
630
+func hasUserDefinedIPAddress(epConfig *networktypes.EndpointSettings) bool {
631
+	return epConfig != nil && epConfig.IPAMConfig != nil && (len(epConfig.IPAMConfig.IPv4Address) > 0 || len(epConfig.IPAMConfig.IPv6Address) > 0)
632
+}
633
+
634
+// User specified ip address is acceptable only for networks with user specified subnets.
635
+func validateNetworkingConfig(n libnetwork.Network, epConfig *networktypes.EndpointSettings) error {
636
+	if !hasUserDefinedIPAddress(epConfig) {
637
+		return nil
638
+	}
639
+	_, nwIPv4Configs, nwIPv6Configs := n.Info().IpamConfig()
640
+	for _, s := range []struct {
641
+		ipConfigured  bool
642
+		subnetConfigs []*libnetwork.IpamConf
643
+	}{
644
+		{
645
+			ipConfigured:  len(epConfig.IPAMConfig.IPv4Address) > 0,
646
+			subnetConfigs: nwIPv4Configs,
647
+		},
648
+		{
649
+			ipConfigured:  len(epConfig.IPAMConfig.IPv6Address) > 0,
650
+			subnetConfigs: nwIPv6Configs,
651
+		},
652
+	} {
653
+		if s.ipConfigured {
654
+			foundSubnet := false
655
+			for _, cfg := range s.subnetConfigs {
656
+				if len(cfg.PreferredPool) > 0 {
657
+					foundSubnet = true
658
+					break
659
+				}
660
+			}
661
+			if !foundSubnet {
662
+				return runconfig.ErrUnsupportedNetworkNoSubnetAndIP
663
+			}
664
+		}
665
+	}
666
+
667
+	return nil
668
+}
669
+
670
+// cleanOperationalData resets the operational data from the passed endpoint settings
671
+func cleanOperationalData(es *networktypes.EndpointSettings) {
672
+	es.EndpointID = ""
673
+	es.Gateway = ""
674
+	es.IPAddress = ""
675
+	es.IPPrefixLen = 0
676
+	es.IPv6Gateway = ""
677
+	es.GlobalIPv6Address = ""
678
+	es.GlobalIPv6PrefixLen = 0
679
+	es.MacAddress = ""
680
+}
681
+
629 682
 // ConnectToNetwork connects a container to a network
630
-func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string) error {
683
+func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error {
631 684
 	if !container.Running {
632 685
 		return derr.ErrorCodeNotRunning.WithArgs(container.ID)
633 686
 	}
634
-	if err := daemon.connectToNetwork(container, idOrName, true); err != nil {
687
+	if err := daemon.connectToNetwork(container, idOrName, endpointConfig, true); err != nil {
635 688
 		return err
636 689
 	}
637 690
 	if err := container.ToDiskLocking(); err != nil {
... ...
@@ -640,11 +722,15 @@ func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName
640 640
 	return nil
641 641
 }
642 642
 
643
-func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, updateSettings bool) (err error) {
643
+func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) {
644 644
 	if container.HostConfig.NetworkMode.IsContainer() {
645 645
 		return runconfig.ErrConflictSharedNetwork
646 646
 	}
647 647
 
648
+	if !containertypes.NetworkMode(idOrName).IsUserDefined() && hasUserDefinedIPAddress(endpointConfig) {
649
+		return runconfig.ErrUnsupportedNetworkAndIP
650
+	}
651
+
648 652
 	if containertypes.NetworkMode(idOrName).IsBridge() &&
649 653
 		daemon.configStore.DisableBridge {
650 654
 		container.Config.NetworkDisabled = true
... ...
@@ -658,12 +744,20 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
658 658
 		return err
659 659
 	}
660 660
 
661
+	if err := validateNetworkingConfig(n, endpointConfig); err != nil {
662
+		return err
663
+	}
664
+
661 665
 	if updateSettings {
662 666
 		if err := daemon.updateNetworkSettings(container, n); err != nil {
663 667
 			return err
664 668
 		}
665 669
 	}
666 670
 
671
+	if endpointConfig != nil {
672
+		container.NetworkSettings.Networks[n.Name()] = endpointConfig
673
+	}
674
+
667 675
 	ep, err := container.GetEndpointInNetwork(n)
668 676
 	if err == nil {
669 677
 		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) {
869 869
 
870 870
 	sid := container.NetworkSettings.SandboxID
871 871
 	settings := container.NetworkSettings.Networks
872
+	if sid == "" || len(settings) == 0 {
873
+		return
874
+	}
875
+
872 876
 	var networks []libnetwork.Network
873
-	for n := range settings {
877
+	for n, epSettings := range settings {
874 878
 		if nw, err := daemon.FindNetwork(n); err == nil {
875 879
 			networks = append(networks, nw)
876 880
 		}
877
-		settings[n] = &networktypes.EndpointSettings{}
878
-	}
879
-
880
-	container.NetworkSettings = &network.Settings{Networks: settings}
881
-
882
-	if sid == "" || len(settings) == 0 {
883
-		return
881
+		cleanOperationalData(epSettings)
884 882
 	}
885 883
 
886 884
 	sb, err := daemon.netController.SandboxByID(sid)
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"github.com/docker/docker/daemon/execdriver/windows"
11 11
 	derr "github.com/docker/docker/errors"
12 12
 	"github.com/docker/docker/layer"
13
+	networktypes "github.com/docker/engine-api/types/network"
13 14
 	"github.com/docker/libnetwork"
14 15
 )
15 16
 
... ...
@@ -18,7 +19,7 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s
18 18
 }
19 19
 
20 20
 // updateContainerNetworkSettings update the network settings
21
-func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container) error {
21
+func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
22 22
 	return nil
23 23
 }
24 24
 
... ...
@@ -27,7 +28,7 @@ func (daemon *Daemon) initializeNetworking(container *container.Container) error
27 27
 }
28 28
 
29 29
 // ConnectToNetwork connects a container to the network
30
-func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string) error {
30
+func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error {
31 31
 	return nil
32 32
 }
33 33
 
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	volumestore "github.com/docker/docker/volume/store"
12 12
 	"github.com/docker/engine-api/types"
13 13
 	containertypes "github.com/docker/engine-api/types/container"
14
+	networktypes "github.com/docker/engine-api/types/network"
14 15
 	"github.com/opencontainers/runc/libcontainer/label"
15 16
 )
16 17
 
... ...
@@ -108,7 +109,12 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig) (retC *containe
108 108
 		return nil, err
109 109
 	}
110 110
 
111
-	if err := daemon.updateContainerNetworkSettings(container); err != nil {
111
+	var endpointsConfigs map[string]*networktypes.EndpointSettings
112
+	if params.NetworkingConfig != nil {
113
+		endpointsConfigs = params.NetworkingConfig.EndpointsConfig
114
+	}
115
+
116
+	if err := daemon.updateContainerNetworkSettings(container, endpointsConfigs); err != nil {
112 117
 		return nil, err
113 118
 	}
114 119
 
... ...
@@ -150,12 +150,12 @@ func getIpamConfig(data []network.IPAMConfig) ([]*libnetwork.IpamConf, []*libnet
150 150
 // ConnectContainerToNetwork connects the given container to the given
151 151
 // network. If either cannot be found, an err is returned. If the
152 152
 // network cannot be set up, an err is returned.
153
-func (daemon *Daemon) ConnectContainerToNetwork(containerName, networkName string) error {
153
+func (daemon *Daemon) ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error {
154 154
 	container, err := daemon.GetContainer(containerName)
155 155
 	if err != nil {
156 156
 		return err
157 157
 	}
158
-	return daemon.ConnectToNetwork(container, networkName)
158
+	return daemon.ConnectToNetwork(container, networkName, endpointConfig)
159 159
 }
160 160
 
161 161
 // DisconnectContainerFromNetwork disconnects the given container from
... ...
@@ -110,6 +110,8 @@ This section lists each version from latest to oldest.  Each listing includes a
110 110
 * `POST /containers/create` now allows you to set a read/write rate limit for a 
111 111
   device (in bytes per second or IO per second).
112 112
 * `GET /networks` now supports filtering by `name`, `id` and `type`.
113
+* `POST /containers/create` now allows you to set the static IPv4 and/or IPv6 address for the container.
114
+* `POST /networks/(id)/connect` now allows you to set the static IPv4 and/or IPv6 address for the container.
113 115
 
114 116
 ### v1.21 API changes
115 117
 
... ...
@@ -3031,7 +3031,13 @@ POST /networks/22be93d5babb089c5aab8dbc369042fad48ff791584ca2da2100db837a1c7c30/
3031 3031
 Content-Type: application/json
3032 3032
 
3033 3033
 {
3034
-  "Container":"3613f73ba0e4"
3034
+  "Container":"3613f73ba0e4",
3035
+  "endpoint_config": {
3036
+    "test_nw": {
3037
+        "IPv4Address":"172.24.56.89",
3038
+        "IPv6Address":"2001:db8::5689"
3039
+    }
3040
+  }
3035 3041
 }
3036 3042
 ```
3037 3043
 
... ...
@@ -30,11 +30,18 @@ You can also use the `docker run --net=<network-name>` option to start a contain
30 30
 $ docker run -itd --net=multi-host-network busybox
31 31
 ```
32 32
 
33
+You can specify the IP address you want to be assigned to the container's interface.
34
+
35
+```bash
36
+$ docker network connect multi-host-network --ip 10.10.36.122 container2
37
+```
38
+
33 39
 You can pause, restart, and stop containers that are connected to a network.
34 40
 Paused containers remain connected and a revealed by a `network inspect`. When
35 41
 the container is stopped, it does not appear on the network until you restart
36 42
 it. The container's IP address is not guaranteed to remain the same when a
37
-stopped container rejoins the network.
43
+stopped container rejoins the network, unless you specified one when you run
44
+`docker network connect` command.
38 45
 
39 46
 To verify the container is connected, use the `docker network inspect` command. Use `docker network disconnect` to remove a container from the network.
40 47
 
... ...
@@ -56,6 +56,8 @@ parent = "smn_cli"
56 56
       --log-opt=[]                  Log driver specific options
57 57
       -m, --memory=""               Memory limit
58 58
       --mac-address=""              Container MAC address (e.g. 92:d0:c6:0a:29:33)
59
+      --ip=""                       Container IPv4 address (e.g. 172.30.100.104)
60
+      --ip6=""                      Container IPv6 address (e.g. 2001:db8::33)
59 61
       --memory-reservation=""       Memory soft limit
60 62
       --memory-swap=""              A positive integer equal to memory plus swap. Specify -1 to enable unlimited swap.
61 63
       --memory-swappiness=""        Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.
... ...
@@ -275,6 +275,8 @@ of the containers.
275 275
                         '<network-name>|<network-id>': connect to a user-defined network
276 276
     --add-host=""    : Add a line to /etc/hosts (host:IP)
277 277
     --mac-address="" : Sets the container's Ethernet device's MAC address
278
+    --ip=""          : Sets the container's Ethernet device's IPv4 address
279
+    --ip6=""          : Sets the container's Ethernet device's IPv6 address
278 280
 
279 281
 By default, all containers have networking enabled and they can make any
280 282
 outgoing connections. The operator can completely disable networking
... ...
@@ -115,8 +115,8 @@ $ docker run -itd --name=container2 busybox
115 115
 Then create an isolated, `bridge` network to test with.
116 116
 
117 117
 ```bash
118
-$ docker network create -d bridge isolated_nw
119
-f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a
118
+$ docker network create -d bridge --subnet 172.25.0.0/16 isolated_nw
119
+06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8
120 120
 ```
121 121
 
122 122
 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
124 124
 ```
125 125
 $ docker network connect isolated_nw container2
126 126
 $ docker network inspect isolated_nw
127
-[[
127
+[
128 128
     {
129 129
         "Name": "isolated_nw",
130
-        "Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a",
130
+        "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
131 131
         "Scope": "local",
132 132
         "Driver": "bridge",
133 133
         "IPAM": {
134 134
             "Driver": "default",
135 135
             "Config": [
136
-                {}
136
+                {
137
+                    "Subnet": "172.25.0.0/16"
138
+                }
137 139
             ]
138 140
         },
139 141
         "Containers": {
140
-            "498eaaaf328e1018042c04b2de04036fc04719a6e39a097a4f4866043a2c2152": {
141
-                "EndpointID": "0e24479cfaafb029104999b4e120858a07b19b1b6d956ae56811033e45d68ad9",
142
-                "MacAddress": "02:42:ac:15:00:02",
143
-                "IPv4Address": "172.21.0.2/16",
142
+            "90e1f3ec71caf82ae776a827e0712a68a110a3f175954e5bd4222fd142ac9428": {
143
+                "Name": "container2",
144
+                "EndpointID": "11cedac1810e864d6b1589d92da12af66203879ab89f4ccd8c8fdaa9b1c48b1d",
145
+                "MacAddress": "02:42:ac:19:00:02",
146
+                "IPv4Address": "172.25.0.2/16",
144 147
                 "IPv6Address": ""
145 148
             }
146 149
         },
... ...
@@ -150,20 +153,28 @@ $ docker network inspect isolated_nw
150 150
 ```
151 151
 
152 152
 You can see that the Engine automatically assigns an IP address to `container2`.
153
-If you had specified a `--subnetwork` when creating your network, the network
154
-would have used that addressing. Now, start a third container and connect it to
153
+Given we specified a `--subnet` when creating the network, Engine picked
154
+an address from that same subnet. Now, start a third container and connect it to
155 155
 the network on launch using the `docker run` command's `--net` option:
156 156
 
157 157
 ```bash
158
-$ docker run --net=isolated_nw -itd --name=container3 busybox
159
-c282ca437ee7e926a7303a64fc04109740208d2c20e442366139322211a6481c
158
+$ docker run --net=isolated_nw --ip=172.25.3.3 -itd --name=container3 busybox
159
+467a7863c3f0277ef8e661b38427737f28099b61fa55622d6c30fb288d88c551
160 160
 ```
161 161
 
162
+As you can see you were able to specify the ip address for your container.
163
+As long as the network to which the container is connecting was created with
164
+a user specified subnet, you will be able to select the IPv4 and/or IPv6 address(es)
165
+for your container when executing `docker run` and `docker network connect` commands.
166
+The selected IP address is part of the container networking configuration and will be
167
+preserved across container reload. The feature is only available on user defined networks,
168
+because they guarantee their subnets configuration does not change across daemon reload.
169
+
162 170
 Now, inspect the network resources used by `container3`.
163 171
 
164 172
 ```bash
165 173
 $ docker inspect --format='{{json .NetworkSettings.Networks}}'  container3
166
-{"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"}}
174
+{"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"}}
167 175
 ```
168 176
 Repeat this command for `container2`. If you have Python installed, you can pretty print the output.
169 177
 
... ...
@@ -171,24 +182,26 @@ Repeat this command for `container2`. If you have Python installed, you can pret
171 171
 $ docker inspect --format='{{json .NetworkSettings.Networks}}'  container2 | python -m json.tool
172 172
 {
173 173
     "bridge": {
174
-        "EndpointID": "281b5ead415cf48a6a84fd1a6504342c76e9091fe09b4fdbcc4a01c30b0d3c5b",
174
+        "EndpointID": "0099f9efb5a3727f6a554f176b1e96fca34cae773da68b3b6a26d046c12cb365",
175 175
         "Gateway": "172.17.0.1",
176 176
         "GlobalIPv6Address": "",
177 177
         "GlobalIPv6PrefixLen": 0,
178
+        "IPAMConfig": null,
178 179
         "IPAddress": "172.17.0.3",
179 180
         "IPPrefixLen": 16,
180 181
         "IPv6Gateway": "",
181 182
         "MacAddress": "02:42:ac:11:00:03"
182 183
     },
183 184
     "isolated_nw": {
184
-        "EndpointID": "0e24479cfaafb029104999b4e120858a07b19b1b6d956ae56811033e45d68ad9",
185
-        "Gateway": "172.21.0.1",
185
+        "EndpointID": "11cedac1810e864d6b1589d92da12af66203879ab89f4ccd8c8fdaa9b1c48b1d",
186
+        "Gateway": "172.25.0.1",
186 187
         "GlobalIPv6Address": "",
187 188
         "GlobalIPv6PrefixLen": 0,
188
-        "IPAddress": "172.21.0.2",
189
+        "IPAMConfig": null,
190
+        "IPAddress": "172.25.0.2",
189 191
         "IPPrefixLen": 16,
190 192
         "IPv6Gateway": "",
191
-        "MacAddress": "02:42:ac:15:00:02"
193
+        "MacAddress": "02:42:ac:19:00:02"
192 194
     }
193 195
 }
194 196
 ```
... ...
@@ -223,8 +236,8 @@ eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:03
223 223
           RX bytes:648 (648.0 B)  TX bytes:648 (648.0 B)
224 224
 
225 225
 eth1      Link encap:Ethernet  HWaddr 02:42:AC:15:00:02  
226
-          inet addr:172.21.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
227
-          inet6 addr: fe80::42:acff:fe15:2/64 Scope:Link
226
+          inet addr:172.25.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
227
+          inet6 addr: fe80::42:acff:fe19:2/64 Scope:Link
228 228
           UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
229 229
           RX packets:8 errors:0 dropped:0 overruns:0 frame:0
230 230
           TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
... ...
@@ -252,19 +265,19 @@ fe00::0	ip6-localnet
252 252
 ff00::0	ip6-mcastprefix
253 253
 ff02::1	ip6-allnodes
254 254
 ff02::2	ip6-allrouters
255
-172.21.0.3	container3
256
-172.21.0.3	container3.isolated_nw
255
+172.21.3.3	container3
256
+172.21.3.3	container3.isolated_nw
257 257
 ```
258 258
 
259 259
 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.
260 260
 
261 261
 ```bash
262 262
 / # ping -w 4 container3
263
-PING container3 (172.21.0.3): 56 data bytes
264
-64 bytes from 172.21.0.3: seq=0 ttl=64 time=0.070 ms
265
-64 bytes from 172.21.0.3: seq=1 ttl=64 time=0.080 ms
266
-64 bytes from 172.21.0.3: seq=2 ttl=64 time=0.080 ms
267
-64 bytes from 172.21.0.3: seq=3 ttl=64 time=0.097 ms
263
+PING container3 (172.25.3.3): 56 data bytes
264
+64 bytes from 172.25.3.3: seq=0 ttl=64 time=0.070 ms
265
+64 bytes from 172.25.3.3: seq=1 ttl=64 time=0.080 ms
266
+64 bytes from 172.25.3.3: seq=2 ttl=64 time=0.080 ms
267
+64 bytes from 172.25.3.3: seq=3 ttl=64 time=0.097 ms
268 268
 
269 269
 --- container3 ping statistics ---
270 270
 4 packets transmitted, 4 packets received, 0% packet loss
... ...
@@ -342,23 +355,26 @@ docker inspect --format='{{json .NetworkSettings.Networks}}'  container2 | pytho
342 342
 
343 343
 
344 344
 $ docker network inspect isolated_nw
345
-[[
345
+[
346 346
     {
347 347
         "Name": "isolated_nw",
348
-        "Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a",
348
+        "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
349 349
         "Scope": "local",
350 350
         "Driver": "bridge",
351 351
         "IPAM": {
352 352
             "Driver": "default",
353 353
             "Config": [
354
-                {}
354
+                {
355
+                    "Subnet": "172.25.0.0/16"
356
+                }
355 357
             ]
356 358
         },
357 359
         "Containers": {
358
-            "c282ca437ee7e926a7303a64fc04109740208d2c20e442366139322211a6481c": {
359
-                "EndpointID": "e5d077f9712a69c6929fdd890df5e7c1c649771a50df5b422f7e68f0ae61e847",
360
-                "MacAddress": "02:42:ac:15:00:03",
361
-                "IPv4Address": "172.21.0.3/16",
360
+            "467a7863c3f0277ef8e661b38427737f28099b61fa55622d6c30fb288d88c551": {
361
+                "Name": "container3",
362
+                "EndpointID": "dffc7ec2915af58cc827d995e6ebdc897342be0420123277103c40ae35579103",
363
+                "MacAddress": "02:42:ac:19:03:03",
364
+                "IPv4Address": "172.25.3.3/16",
362 365
                 "IPv6Address": ""
363 366
             }
364 367
         },
... ...
@@ -393,7 +409,7 @@ lo        Link encap:Local Loopback
393 393
           RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
394 394
 
395 395
 / # ping container3
396
-PING container3 (172.20.0.1): 56 data bytes
396
+PING container3 (172.25.3.3): 56 data bytes
397 397
 ^C
398 398
 --- container3 ping statistics ---
399 399
 2 packets transmitted, 0 packets received, 100% packet loss
... ...
@@ -426,13 +442,15 @@ docker network inspect isolated_nw
426 426
 [
427 427
     {
428 428
         "Name": "isolated_nw",
429
-        "Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a",
429
+        "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
430 430
         "Scope": "local",
431 431
         "Driver": "bridge",
432 432
         "IPAM": {
433 433
             "Driver": "default",
434 434
             "Config": [
435
-                {}
435
+                {
436
+                    "Subnet": "172.25.0.0/16"
437
+                }
436 438
             ]
437 439
         },
438 440
         "Containers": {},
... ...
@@ -25,7 +25,7 @@ clone git github.com/docker/go-connections v0.1.2
25 25
 clone git github.com/docker/engine-api v0.1.3
26 26
 
27 27
 #get libnetwork packages
28
-clone git github.com/docker/libnetwork 9f0563ea8f430d8828553aac97161cbff4056436
28
+clone git github.com/docker/libnetwork 49c24217054e269aad3dbfd81ee32780b104dd84
29 29
 clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
30 30
 clone git github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b
31 31
 clone git github.com/hashicorp/memberlist 9a1e242e454d2443df330bdd51a436d5a9058fc4
... ...
@@ -962,3 +962,71 @@ func (s *DockerNetworkSuite) TestDockerNetworkRestartWithMulipleNetworks(c *chec
962 962
 	c.Assert(networks, checker.Contains, "bridge", check.Commentf("Should contain 'bridge' network"))
963 963
 	c.Assert(networks, checker.Contains, "test", check.Commentf("Should contain 'test' netwokr"))
964 964
 }
965
+
966
+func (s *DockerNetworkSuite) TestDockerNetworkConnectPreferredIP(c *check.C) {
967
+	// create two networks
968
+	dockerCmd(c, "network", "create", "--subnet=172.28.0.0/16", "--subnet=2001:db8:1234::/64", "n0")
969
+	assertNwIsAvailable(c, "n0")
970
+
971
+	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")
972
+	assertNwIsAvailable(c, "n1")
973
+
974
+	// run a container on first network specifying the ip addresses
975
+	dockerCmd(c, "run", "-d", "--name", "c0", "--net=n0", "--ip", "172.28.99.88", "--ip6", "2001:db8:1234::9988", "busybox", "top")
976
+	c.Assert(waitRun("c0"), check.IsNil)
977
+	verifyIPAddresses(c, "c0", "n0", "172.28.99.88", "2001:db8:1234::9988")
978
+
979
+	// connect the container to the second network specifying the preferred ip addresses
980
+	dockerCmd(c, "network", "connect", "--ip", "172.30.55.44", "--ip6", "2001:db8:abcd::5544", "n1", "c0")
981
+	verifyIPAddresses(c, "c0", "n1", "172.30.55.44", "2001:db8:abcd::5544")
982
+
983
+	// Stop and restart the container
984
+	dockerCmd(c, "stop", "c0")
985
+	dockerCmd(c, "start", "c0")
986
+
987
+	// verify preferred addresses are applied
988
+	verifyIPAddresses(c, "c0", "n0", "172.28.99.88", "2001:db8:1234::9988")
989
+	verifyIPAddresses(c, "c0", "n1", "172.30.55.44", "2001:db8:abcd::5544")
990
+
991
+	// Still it should fail to connect to the default network with a specified IP (whatever ip)
992
+	out, _, err := dockerCmdWithError("network", "connect", "--ip", "172.21.55.44", "bridge", "c0")
993
+	c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
994
+	c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkAndIP.Error())
995
+
996
+}
997
+
998
+func (s *DockerNetworkSuite) TestDockerNetworkUnsupportedPreferredIP(c *check.C) {
999
+	// preferred IP is not supported on predefined networks
1000
+	for _, mode := range []string{"none", "host", "bridge"} {
1001
+		checkUnsupportedNetworkAndIP(c, mode)
1002
+	}
1003
+
1004
+	// preferred IP is not supported on networks with no user defined subnets
1005
+	dockerCmd(c, "network", "create", "n0")
1006
+	assertNwIsAvailable(c, "n0")
1007
+
1008
+	out, _, err := dockerCmdWithError("run", "-d", "--ip", "172.28.99.88", "--net", "n0", "busybox", "top")
1009
+	c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
1010
+	c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkNoSubnetAndIP.Error())
1011
+
1012
+	out, _, err = dockerCmdWithError("run", "-d", "--ip6", "2001:db8:1234::9988", "--net", "n0", "busybox", "top")
1013
+	c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
1014
+	c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkNoSubnetAndIP.Error())
1015
+
1016
+	dockerCmd(c, "network", "rm", "n0")
1017
+	assertNwNotAvailable(c, "n0")
1018
+}
1019
+
1020
+func checkUnsupportedNetworkAndIP(c *check.C, nwMode string) {
1021
+	out, _, err := dockerCmdWithError("run", "-d", "--net", nwMode, "--ip", "172.28.99.88", "--ip6", "2001:db8:1234::9988", "busybox", "top")
1022
+	c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
1023
+	c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkAndIP.Error())
1024
+}
1025
+
1026
+func verifyIPAddresses(c *check.C, cName, nwname, ipv4, ipv6 string) {
1027
+	out, _ := dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.IPAddress }}'", nwname), cName)
1028
+	c.Assert(strings.TrimSpace(out), check.Equals, ipv4)
1029
+
1030
+	out, _ = dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.GlobalIPv6Address }}'", nwname), cName)
1031
+	c.Assert(strings.TrimSpace(out), check.Equals, ipv6)
1032
+}
... ...
@@ -7,18 +7,19 @@ import (
7 7
 
8 8
 	"github.com/docker/docker/volume"
9 9
 	"github.com/docker/engine-api/types/container"
10
+	networktypes "github.com/docker/engine-api/types/network"
10 11
 )
11 12
 
12 13
 // DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
13 14
 // struct and returns both a Config and an HostConfig struct
14 15
 // Be aware this function is not checking whether the resulted structs are nil,
15 16
 // it's your business to do so
16
-func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostConfig, error) {
17
+func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
17 18
 	var w ContainerConfigWrapper
18 19
 
19 20
 	decoder := json.NewDecoder(src)
20 21
 	if err := decoder.Decode(&w); err != nil {
21
-		return nil, nil, err
22
+		return nil, nil, nil, err
22 23
 	}
23 24
 
24 25
 	hc := w.getHostConfig()
... ...
@@ -33,21 +34,21 @@ func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon
33 33
 
34 34
 		// Now validate all the volumes and binds
35 35
 		if err := validateVolumesAndBindSettings(w.Config, hc); err != nil {
36
-			return nil, nil, err
36
+			return nil, nil, nil, err
37 37
 		}
38 38
 	}
39 39
 
40 40
 	// Certain parameters need daemon-side validation that cannot be done
41 41
 	// on the client, as only the daemon knows what is valid for the platform.
42 42
 	if err := ValidateNetMode(w.Config, hc); err != nil {
43
-		return nil, nil, err
43
+		return nil, nil, nil, err
44 44
 	}
45 45
 
46 46
 	// Validate the isolation level
47 47
 	if err := ValidateIsolationLevel(hc); err != nil {
48
-		return nil, nil, err
48
+		return nil, nil, nil, err
49 49
 	}
50
-	return w.Config, hc, nil
50
+	return w.Config, hc, w.NetworkingConfig, nil
51 51
 }
52 52
 
53 53
 // validateVolumesAndBindSettings validates each of the volumes and bind settings
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"testing"
11 11
 
12 12
 	"github.com/docker/engine-api/types/container"
13
+	networktypes "github.com/docker/engine-api/types/network"
13 14
 	"github.com/docker/engine-api/types/strslice"
14 15
 )
15 16
 
... ...
@@ -45,7 +46,7 @@ func TestDecodeContainerConfig(t *testing.T) {
45 45
 			t.Fatal(err)
46 46
 		}
47 47
 
48
-		c, h, err := DecodeContainerConfig(bytes.NewReader(b))
48
+		c, h, _, err := DecodeContainerConfig(bytes.NewReader(b))
49 49
 		if err != nil {
50 50
 			t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
51 51
 		}
... ...
@@ -70,29 +71,29 @@ func TestDecodeContainerConfig(t *testing.T) {
70 70
 func TestDecodeContainerConfigIsolation(t *testing.T) {
71 71
 
72 72
 	// An invalid isolation level
73
-	if _, _, err := callDecodeContainerConfigIsolation("invalid"); err != nil {
73
+	if _, _, _, err := callDecodeContainerConfigIsolation("invalid"); err != nil {
74 74
 		if !strings.Contains(err.Error(), `invalid --isolation: "invalid"`) {
75 75
 			t.Fatal(err)
76 76
 		}
77 77
 	}
78 78
 
79 79
 	// Blank isolation level (== default)
80
-	if _, _, err := callDecodeContainerConfigIsolation(""); err != nil {
80
+	if _, _, _, err := callDecodeContainerConfigIsolation(""); err != nil {
81 81
 		t.Fatal("Blank isolation should have succeeded")
82 82
 	}
83 83
 
84 84
 	// Default isolation level
85
-	if _, _, err := callDecodeContainerConfigIsolation("default"); err != nil {
85
+	if _, _, _, err := callDecodeContainerConfigIsolation("default"); err != nil {
86 86
 		t.Fatal("default isolation should have succeeded")
87 87
 	}
88 88
 
89 89
 	// Hyper-V Containers isolation level (Valid on Windows only)
90 90
 	if runtime.GOOS == "windows" {
91
-		if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
91
+		if _, _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
92 92
 			t.Fatal("hyperv isolation should have succeeded")
93 93
 		}
94 94
 	} else {
95
-		if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
95
+		if _, _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
96 96
 			if !strings.Contains(err.Error(), `invalid --isolation: "hyperv"`) {
97 97
 				t.Fatal(err)
98 98
 			}
... ...
@@ -102,7 +103,7 @@ func TestDecodeContainerConfigIsolation(t *testing.T) {
102 102
 
103 103
 // callDecodeContainerConfigIsolation is a utility function to call
104 104
 // DecodeContainerConfig for validating isolation levels
105
-func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *container.HostConfig, error) {
105
+func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
106 106
 	var (
107 107
 		b   []byte
108 108
 		err error
... ...
@@ -114,7 +115,7 @@ func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *c
114 114
 			Isolation:   container.IsolationLevel(isolation)},
115 115
 	}
116 116
 	if b, err = json.Marshal(w); err != nil {
117
-		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
117
+		return nil, nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
118 118
 	}
119 119
 	return DecodeContainerConfig(bytes.NewReader(b))
120 120
 }
... ...
@@ -2,15 +2,19 @@
2 2
 
3 3
 package runconfig
4 4
 
5
-import "github.com/docker/engine-api/types/container"
5
+import (
6
+	"github.com/docker/engine-api/types/container"
7
+	networktypes "github.com/docker/engine-api/types/network"
8
+)
6 9
 
7 10
 // ContainerConfigWrapper is a Config wrapper that hold the container Config (portable)
8 11
 // and the corresponding HostConfig (non-portable).
9 12
 type ContainerConfigWrapper struct {
10 13
 	*container.Config
11
-	InnerHostConfig       *container.HostConfig `json:"HostConfig,omitempty"`
12
-	Cpuset                string                `json:",omitempty"` // Deprecated. Exported for backwards compatibility.
13
-	*container.HostConfig                       // Deprecated. Exported to read attributes from json that are not in the inner host config structure.
14
+	InnerHostConfig       *container.HostConfig          `json:"HostConfig,omitempty"`
15
+	Cpuset                string                         `json:",omitempty"` // Deprecated. Exported for backwards compatibility.
16
+	NetworkingConfig      *networktypes.NetworkingConfig `json:"NetworkingConfig,omitempty"`
17
+	*container.HostConfig                                // Deprecated. Exported to read attributes from json that are not in the inner host config structure.
14 18
 }
15 19
 
16 20
 // getHostConfig gets the HostConfig of the Config.
... ...
@@ -1,12 +1,16 @@
1 1
 package runconfig
2 2
 
3
-import "github.com/docker/engine-api/types/container"
3
+import (
4
+	"github.com/docker/engine-api/types/container"
5
+	networktypes "github.com/docker/engine-api/types/network"
6
+)
4 7
 
5 8
 // ContainerConfigWrapper is a Config wrapper that hold the container Config (portable)
6 9
 // and the corresponding HostConfig (non-portable).
7 10
 type ContainerConfigWrapper struct {
8 11
 	*container.Config
9
-	HostConfig *container.HostConfig `json:"HostConfig,omitempty"`
12
+	HostConfig       *container.HostConfig          `json:"HostConfig,omitempty"`
13
+	NetworkingConfig *networktypes.NetworkingConfig `json:"NetworkingConfig,omitempty"`
10 14
 }
11 15
 
12 16
 // getHostConfig gets the HostConfig of the Config.
... ...
@@ -29,4 +29,8 @@ var (
29 29
 	ErrConflictNetworkPublishPorts = fmt.Errorf("Conflicting options: port publishing and the container type network mode")
30 30
 	// ErrConflictNetworkExposePorts conflict between the expose option and the network mode
31 31
 	ErrConflictNetworkExposePorts = fmt.Errorf("Conflicting options: port exposing and the container type network mode")
32
+	// ErrUnsupportedNetworkAndIP conflict between network mode and preferred ip address
33
+	ErrUnsupportedNetworkAndIP = fmt.Errorf("User specified IP address is supported on user defined networks only")
34
+	// ErrUnsupportedNetworkNoSubnetAndIP conflict between network with no configured subnet and preferred ip address
35
+	ErrUnsupportedNetworkNoSubnetAndIP = fmt.Errorf("User specified IP address is supported only when connecting to networks with user configured subnets")
32 36
 )
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"github.com/docker/docker/pkg/mount"
12 12
 	"github.com/docker/docker/pkg/signal"
13 13
 	"github.com/docker/engine-api/types/container"
14
+	networktypes "github.com/docker/engine-api/types/network"
14 15
 	"github.com/docker/engine-api/types/strslice"
15 16
 	"github.com/docker/go-connections/nat"
16 17
 	"github.com/docker/go-units"
... ...
@@ -19,7 +20,7 @@ import (
19 19
 // Parse parses the specified args for the specified command and generates a Config,
20 20
 // a HostConfig and returns them with the specified command.
21 21
 // If the specified args are not valid, it will return an error.
22
-func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
22
+func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, *flag.FlagSet, error) {
23 23
 	var (
24 24
 		// FIXME: use utils.ListOpts for attach and volumes?
25 25
 		flAttach            = opts.NewListOpts(ValidateAttach)
... ...
@@ -77,6 +78,8 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
77 77
 		flSwappiness        = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)")
78 78
 		flNetMode           = cmd.String([]string{"-net"}, "default", "Connect a container to a network")
79 79
 		flMacAddress        = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
80
+		flIPv4Address       = cmd.String([]string{"-ip"}, "", "Container IPv4 address (e.g. 172.30.100.104)")
81
+		flIPv6Address       = cmd.String([]string{"-ip6"}, "", "Container IPv6 address (e.g. 2001:db8::33)")
80 82
 		flIpcMode           = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
81 83
 		flRestartPolicy     = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
82 84
 		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
119 119
 	cmd.Require(flag.Min, 1)
120 120
 
121 121
 	if err := cmd.ParseFlags(args, true); err != nil {
122
-		return nil, nil, cmd, err
122
+		return nil, nil, nil, cmd, err
123 123
 	}
124 124
 
125 125
 	var (
... ...
@@ -131,7 +134,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
131 131
 	// Validate the input mac address
132 132
 	if *flMacAddress != "" {
133 133
 		if _, err := ValidateMACAddress(*flMacAddress); err != nil {
134
-			return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
134
+			return nil, nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
135 135
 		}
136 136
 	}
137 137
 	if *flStdin {
... ...
@@ -149,7 +152,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
149 149
 	if *flMemoryString != "" {
150 150
 		flMemory, err = units.RAMInBytes(*flMemoryString)
151 151
 		if err != nil {
152
-			return nil, nil, cmd, err
152
+			return nil, nil, nil, cmd, err
153 153
 		}
154 154
 	}
155 155
 
... ...
@@ -157,7 +160,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
157 157
 	if *flMemoryReservation != "" {
158 158
 		MemoryReservation, err = units.RAMInBytes(*flMemoryReservation)
159 159
 		if err != nil {
160
-			return nil, nil, cmd, err
160
+			return nil, nil, nil, cmd, err
161 161
 		}
162 162
 	}
163 163
 
... ...
@@ -168,7 +171,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
168 168
 		} else {
169 169
 			memorySwap, err = units.RAMInBytes(*flMemorySwap)
170 170
 			if err != nil {
171
-				return nil, nil, cmd, err
171
+				return nil, nil, nil, cmd, err
172 172
 			}
173 173
 		}
174 174
 	}
... ...
@@ -177,20 +180,20 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
177 177
 	if *flKernelMemory != "" {
178 178
 		KernelMemory, err = units.RAMInBytes(*flKernelMemory)
179 179
 		if err != nil {
180
-			return nil, nil, cmd, err
180
+			return nil, nil, nil, cmd, err
181 181
 		}
182 182
 	}
183 183
 
184 184
 	swappiness := *flSwappiness
185 185
 	if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
186
-		return nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
186
+		return nil, nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
187 187
 	}
188 188
 
189 189
 	var shmSize int64
190 190
 	if *flShmSize != "" {
191 191
 		shmSize, err = units.RAMInBytes(*flShmSize)
192 192
 		if err != nil {
193
-			return nil, nil, cmd, err
193
+			return nil, nil, nil, cmd, err
194 194
 		}
195 195
 	}
196 196
 
... ...
@@ -210,7 +213,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
210 210
 	for _, t := range flTmpfs.GetAll() {
211 211
 		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
212 212
 			if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
213
-				return nil, nil, cmd, err
213
+				return nil, nil, nil, cmd, err
214 214
 			}
215 215
 			tmpfs[arr[0]] = arr[1]
216 216
 		} else {
... ...
@@ -243,13 +246,13 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
243 243
 
244 244
 	ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
245 245
 	if err != nil {
246
-		return nil, nil, cmd, err
246
+		return nil, nil, nil, cmd, err
247 247
 	}
248 248
 
249 249
 	// Merge in exposed ports to the map of published ports
250 250
 	for _, e := range flExpose.GetAll() {
251 251
 		if strings.Contains(e, ":") {
252
-			return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
252
+			return nil, nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
253 253
 		}
254 254
 		//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
255 255
 		proto, port := nat.SplitProtoPort(e)
... ...
@@ -257,12 +260,12 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
257 257
 		//if expose a port, the start and end port are the same
258 258
 		start, end, err := nat.ParsePortRange(port)
259 259
 		if err != nil {
260
-			return nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
260
+			return nil, nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
261 261
 		}
262 262
 		for i := start; i <= end; i++ {
263 263
 			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
264 264
 			if err != nil {
265
-				return nil, nil, cmd, err
265
+				return nil, nil, nil, cmd, err
266 266
 			}
267 267
 			if _, exists := ports[p]; !exists {
268 268
 				ports[p] = struct{}{}
... ...
@@ -275,7 +278,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
275 275
 	for _, device := range flDevices.GetAll() {
276 276
 		deviceMapping, err := ParseDevice(device)
277 277
 		if err != nil {
278
-			return nil, nil, cmd, err
278
+			return nil, nil, nil, cmd, err
279 279
 		}
280 280
 		deviceMappings = append(deviceMappings, deviceMapping)
281 281
 	}
... ...
@@ -283,38 +286,38 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
283 283
 	// collect all the environment variables for the container
284 284
 	envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
285 285
 	if err != nil {
286
-		return nil, nil, cmd, err
286
+		return nil, nil, nil, cmd, err
287 287
 	}
288 288
 
289 289
 	// collect all the labels for the container
290 290
 	labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
291 291
 	if err != nil {
292
-		return nil, nil, cmd, err
292
+		return nil, nil, nil, cmd, err
293 293
 	}
294 294
 
295 295
 	ipcMode := container.IpcMode(*flIpcMode)
296 296
 	if !ipcMode.Valid() {
297
-		return nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
297
+		return nil, nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
298 298
 	}
299 299
 
300 300
 	pidMode := container.PidMode(*flPidMode)
301 301
 	if !pidMode.Valid() {
302
-		return nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
302
+		return nil, nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
303 303
 	}
304 304
 
305 305
 	utsMode := container.UTSMode(*flUTSMode)
306 306
 	if !utsMode.Valid() {
307
-		return nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
307
+		return nil, nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
308 308
 	}
309 309
 
310 310
 	restartPolicy, err := ParseRestartPolicy(*flRestartPolicy)
311 311
 	if err != nil {
312
-		return nil, nil, cmd, err
312
+		return nil, nil, nil, cmd, err
313 313
 	}
314 314
 
315 315
 	loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll())
316 316
 	if err != nil {
317
-		return nil, nil, cmd, err
317
+		return nil, nil, nil, cmd, err
318 318
 	}
319 319
 
320 320
 	resources := container.Resources{
... ...
@@ -405,7 +408,21 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
405 405
 	if config.OpenStdin && config.AttachStdin {
406 406
 		config.StdinOnce = true
407 407
 	}
408
-	return config, hostConfig, cmd, nil
408
+
409
+	var networkingConfig *networktypes.NetworkingConfig
410
+	if *flIPv4Address != "" || *flIPv6Address != "" {
411
+		networkingConfig = &networktypes.NetworkingConfig{
412
+			EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
413
+		}
414
+		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = &networktypes.EndpointSettings{
415
+			IPAMConfig: &networktypes.EndpointIPAMConfig{
416
+				IPv4Address: *flIPv4Address,
417
+				IPv6Address: *flIPv6Address,
418
+			},
419
+		}
420
+	}
421
+
422
+	return config, hostConfig, networkingConfig, cmd, nil
409 423
 }
410 424
 
411 425
 // reads a file of line terminated key=value pairs and override that with override parameter
... ...
@@ -13,10 +13,11 @@ import (
13 13
 	flag "github.com/docker/docker/pkg/mflag"
14 14
 	"github.com/docker/docker/runconfig"
15 15
 	"github.com/docker/engine-api/types/container"
16
+	networktypes "github.com/docker/engine-api/types/network"
16 17
 	"github.com/docker/go-connections/nat"
17 18
 )
18 19
 
19
-func parseRun(args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
20
+func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, *flag.FlagSet, error) {
20 21
 	cmd := flag.NewFlagSet("run", flag.ContinueOnError)
21 22
 	cmd.SetOutput(ioutil.Discard)
22 23
 	cmd.Usage = nil
... ...
@@ -24,7 +25,7 @@ func parseRun(args []string) (*container.Config, *container.HostConfig, *flag.Fl
24 24
 }
25 25
 
26 26
 func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
27
-	config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
27
+	config, hostConfig, _, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
28 28
 	return config, hostConfig, err
29 29
 }
30 30
 
... ...
@@ -304,7 +305,7 @@ func callDecodeContainerConfig(volumes []string, binds []string) (*container.Con
304 304
 	if b, err = json.Marshal(w); err != nil {
305 305
 		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
306 306
 	}
307
-	c, h, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
307
+	c, h, _, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
308 308
 	if err != nil {
309 309
 		return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
310 310
 	}
... ...
@@ -349,7 +350,7 @@ func setupPlatformVolume(u []string, w []string) ([]string, string) {
349 349
 func TestParseWithMacAddress(t *testing.T) {
350 350
 	invalidMacAddress := "--mac-address=invalidMacAddress"
351 351
 	validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
352
-	if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
352
+	if _, _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
353 353
 		t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
354 354
 	}
355 355
 	if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
... ...
@@ -360,7 +361,7 @@ func TestParseWithMacAddress(t *testing.T) {
360 360
 func TestParseWithMemory(t *testing.T) {
361 361
 	invalidMemory := "--memory=invalid"
362 362
 	validMemory := "--memory=1G"
363
-	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
363
+	if _, _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
364 364
 		t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
365 365
 	}
366 366
 	if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
... ...
@@ -372,7 +373,7 @@ func TestParseWithMemorySwap(t *testing.T) {
372 372
 	invalidMemory := "--memory-swap=invalid"
373 373
 	validMemory := "--memory-swap=1G"
374 374
 	anotherValidMemory := "--memory-swap=-1"
375
-	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
375
+	if _, _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
376 376
 		t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
377 377
 	}
378 378
 	if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
... ...
@@ -417,12 +418,12 @@ func TestParseWithExpose(t *testing.T) {
417 417
 		"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
418 418
 	}
419 419
 	for expose, expectedError := range invalids {
420
-		if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
420
+		if _, _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
421 421
 			t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
422 422
 		}
423 423
 	}
424 424
 	for expose, exposedPorts := range valids {
425
-		config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
425
+		config, _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
426 426
 		if err != nil {
427 427
 			t.Fatal(err)
428 428
 		}
... ...
@@ -436,7 +437,7 @@ func TestParseWithExpose(t *testing.T) {
436 436
 		}
437 437
 	}
438 438
 	// Merge with actual published port
439
-	config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
439
+	config, _, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
440 440
 	if err != nil {
441 441
 		t.Fatal(err)
442 442
 	}
... ...
@@ -475,7 +476,7 @@ func TestParseDevice(t *testing.T) {
475 475
 		},
476 476
 	}
477 477
 	for device, deviceMapping := range valids {
478
-		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
478
+		_, hostconfig, _, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
479 479
 		if err != nil {
480 480
 			t.Fatal(err)
481 481
 		}
... ...
@@ -491,11 +492,11 @@ func TestParseDevice(t *testing.T) {
491 491
 
492 492
 func TestParseModes(t *testing.T) {
493 493
 	// ipc ko
494
-	if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
494
+	if _, _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
495 495
 		t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
496 496
 	}
497 497
 	// ipc ok
498
-	_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
498
+	_, hostconfig, _, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
499 499
 	if err != nil {
500 500
 		t.Fatal(err)
501 501
 	}
... ...
@@ -503,11 +504,11 @@ func TestParseModes(t *testing.T) {
503 503
 		t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
504 504
 	}
505 505
 	// pid ko
506
-	if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
506
+	if _, _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
507 507
 		t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
508 508
 	}
509 509
 	// pid ok
510
-	_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
510
+	_, hostconfig, _, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
511 511
 	if err != nil {
512 512
 		t.Fatal(err)
513 513
 	}
... ...
@@ -515,11 +516,11 @@ func TestParseModes(t *testing.T) {
515 515
 		t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
516 516
 	}
517 517
 	// uts ko
518
-	if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
518
+	if _, _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
519 519
 		t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
520 520
 	}
521 521
 	// uts ok
522
-	_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
522
+	_, hostconfig, _, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
523 523
 	if err != nil {
524 524
 		t.Fatal(err)
525 525
 	}
... ...
@@ -527,11 +528,11 @@ func TestParseModes(t *testing.T) {
527 527
 		t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
528 528
 	}
529 529
 	// shm-size ko
530
-	if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
530
+	if _, _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
531 531
 		t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
532 532
 	}
533 533
 	// shm-size ok
534
-	_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
534
+	_, hostconfig, _, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
535 535
 	if err != nil {
536 536
 		t.Fatal(err)
537 537
 	}
... ...
@@ -560,12 +561,12 @@ func TestParseRestartPolicy(t *testing.T) {
560 560
 		},
561 561
 	}
562 562
 	for restart, expectedError := range invalids {
563
-		if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
563
+		if _, _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
564 564
 			t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
565 565
 		}
566 566
 	}
567 567
 	for restart, expected := range valids {
568
-		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
568
+		_, hostconfig, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
569 569
 		if err != nil {
570 570
 			t.Fatal(err)
571 571
 		}
... ...
@@ -577,11 +578,11 @@ func TestParseRestartPolicy(t *testing.T) {
577 577
 
578 578
 func TestParseLoggingOpts(t *testing.T) {
579 579
 	// logging opts ko
580
-	if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" {
580
+	if _, _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" {
581 581
 		t.Fatalf("Expected an error with message 'Invalid logging opts for driver none', got %v", err)
582 582
 	}
583 583
 	// logging opts ok
584
-	_, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
584
+	_, hostconfig, _, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
585 585
 	if err != nil {
586 586
 		t.Fatal(err)
587 587
 	}
... ...
@@ -596,18 +597,18 @@ func TestParseEnvfileVariables(t *testing.T) {
596 596
 		e = "open nonexistent: The system cannot find the file specified."
597 597
 	}
598 598
 	// env ko
599
-	if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
599
+	if _, _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
600 600
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
601 601
 	}
602 602
 	// env ok
603
-	config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
603
+	config, _, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
604 604
 	if err != nil {
605 605
 		t.Fatal(err)
606 606
 	}
607 607
 	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
608 608
 		t.Fatalf("Expected a a config with [ENV1=value1], got %v", config.Env)
609 609
 	}
610
-	config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
610
+	config, _, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
611 611
 	if err != nil {
612 612
 		t.Fatal(err)
613 613
 	}
... ...
@@ -622,18 +623,18 @@ func TestParseLabelfileVariables(t *testing.T) {
622 622
 		e = "open nonexistent: The system cannot find the file specified."
623 623
 	}
624 624
 	// label ko
625
-	if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
625
+	if _, _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
626 626
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
627 627
 	}
628 628
 	// label ok
629
-	config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
629
+	config, _, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
630 630
 	if err != nil {
631 631
 		t.Fatal(err)
632 632
 	}
633 633
 	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
634 634
 		t.Fatalf("Expected a a config with [LABEL1:value1], got %v", config.Labels)
635 635
 	}
636
-	config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
636
+	config, _, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
637 637
 	if err != nil {
638 638
 		t.Fatal(err)
639 639
 	}
... ...
@@ -643,7 +644,7 @@ func TestParseLabelfileVariables(t *testing.T) {
643 643
 }
644 644
 
645 645
 func TestParseEntryPoint(t *testing.T) {
646
-	config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
646
+	config, _, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
647 647
 	if err != nil {
648 648
 		t.Fatal(err)
649 649
 	}
... ...
@@ -15,6 +15,11 @@ There are many networking solutions available to suit a broad range of use-cases
15 15
 
16 16
 
17 17
 ```go
18
+func main() {
19
+	if reexec.Init() {
20
+		return
21
+	}
22
+
18 23
 	// Select and configure the network driver
19 24
 	networkType := "bridge"
20 25
 
... ...
@@ -24,14 +29,14 @@ There are many networking solutions available to suit a broad range of use-cases
24 24
 	genericOption[netlabel.GenericData] = driverOptions
25 25
 	controller, err := libnetwork.New(config.OptionDriverConfig(networkType, genericOption))
26 26
 	if err != nil {
27
-		return
27
+		log.Fatalf("libnetwork.New: %s", err)
28 28
 	}
29 29
 
30 30
 	// Create a network for containers to join.
31 31
 	// NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can use.
32 32
 	network, err := controller.NewNetwork(networkType, "network1")
33 33
 	if err != nil {
34
-		return
34
+		log.Fatalf("controller.NewNetwork: %s", err)
35 35
 	}
36 36
 
37 37
 	// 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
40 40
 	// from the returned endpoint.
41 41
 	ep, err := network.CreateEndpoint("Endpoint1")
42 42
 	if err != nil {
43
-		return
43
+		log.Fatalf("network.CreateEndpoint: %s", err)
44 44
 	}
45 45
 
46 46
 	// Create the sandbox for the container.
... ...
@@ -48,22 +53,29 @@ There are many networking solutions available to suit a broad range of use-cases
48 48
 	sbx, err := controller.NewSandbox("container1",
49 49
 		libnetwork.OptionHostname("test"),
50 50
 		libnetwork.OptionDomainname("docker.io"))
51
+	if err != nil {
52
+		log.Fatalf("controller.NewSandbox: %s", err)
53
+	}
51 54
 
52 55
 	// A sandbox can join the endpoint via the join api.
53 56
 	err = ep.Join(sbx)
54 57
 	if err != nil {
55
-		return
58
+		log.Fatalf("ep.Join: %s", err)
56 59
 	}
57 60
 
58 61
 	// libnetwork client can check the endpoint's operational data via the Info() API
59 62
 	epInfo, err := ep.DriverInfo()
60
-	mapData, ok := epInfo[netlabel.PortMap]
61
-	if ok {
62
-		portMapping, ok := mapData.([]types.PortBinding)
63
-		if ok {
64
-			fmt.Printf("Current port mapping for endpoint %s: %v", ep.Name(), portMapping)
65
-		}
63
+	if err != nil {
64
+		log.Fatalf("ep.DriverInfo: %s", err)
66 65
 	}
66
+
67
+	macAddress, ok := epInfo[netlabel.MacAddress]
68
+	if !ok {
69
+		log.Fatalf("failed to get mac address from endpoint info")
70
+	}
71
+
72
+	fmt.Printf("Joined endpoint %s (%s) to sandbox %s (%s)\n", ep.Name(), macAddress, sbx.ContainerID(), sbx.Key())
73
+}
67 74
 ```
68 75
 
69 76
 ## Future
... ...
@@ -106,12 +106,13 @@ type bridgeNetwork struct {
106 106
 }
107 107
 
108 108
 type driver struct {
109
-	config      *configuration
110
-	network     *bridgeNetwork
111
-	natChain    *iptables.ChainInfo
112
-	filterChain *iptables.ChainInfo
113
-	networks    map[string]*bridgeNetwork
114
-	store       datastore.DataStore
109
+	config         *configuration
110
+	network        *bridgeNetwork
111
+	natChain       *iptables.ChainInfo
112
+	filterChain    *iptables.ChainInfo
113
+	isolationChain *iptables.ChainInfo
114
+	networks       map[string]*bridgeNetwork
115
+	store          datastore.DataStore
115 116
 	sync.Mutex
116 117
 }
117 118
 
... ...
@@ -244,15 +245,15 @@ func (n *bridgeNetwork) registerIptCleanFunc(clean iptableCleanFunc) {
244 244
 	n.iptCleanFuncs = append(n.iptCleanFuncs, clean)
245 245
 }
246 246
 
247
-func (n *bridgeNetwork) getDriverChains() (*iptables.ChainInfo, *iptables.ChainInfo, error) {
247
+func (n *bridgeNetwork) getDriverChains() (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) {
248 248
 	n.Lock()
249 249
 	defer n.Unlock()
250 250
 
251 251
 	if n.driver == nil {
252
-		return nil, nil, types.BadRequestErrorf("no driver found")
252
+		return nil, nil, nil, types.BadRequestErrorf("no driver found")
253 253
 	}
254 254
 
255
-	return n.driver.natChain, n.driver.filterChain, nil
255
+	return n.driver.natChain, n.driver.filterChain, n.driver.isolationChain, nil
256 256
 }
257 257
 
258 258
 func (n *bridgeNetwork) getNetworkBridgeName() string {
... ...
@@ -282,26 +283,16 @@ func (n *bridgeNetwork) getEndpoint(eid string) (*bridgeEndpoint, error) {
282 282
 // from each of the other networks
283 283
 func (n *bridgeNetwork) isolateNetwork(others []*bridgeNetwork, enable bool) error {
284 284
 	n.Lock()
285
-	thisV4 := n.bridge.bridgeIPv4
286
-	thisV6 := getV6Network(n.config, n.bridge)
285
+	thisIface := n.config.BridgeName
287 286
 	n.Unlock()
288 287
 
289 288
 	// Install the rules to isolate this networks against each of the other networks
290 289
 	for _, o := range others {
291 290
 		o.Lock()
292
-		otherV4 := o.bridge.bridgeIPv4
293
-		otherV6 := getV6Network(o.config, o.bridge)
291
+		otherIface := o.config.BridgeName
294 292
 		o.Unlock()
295
-
296
-		if !types.CompareIPNet(thisV4, otherV4) {
297
-			// It's ok to pass a.b.c.d/x, iptables will ignore the host subnet bits
298
-			if err := setINC(thisV4.String(), otherV4.String(), enable); err != nil {
299
-				return err
300
-			}
301
-		}
302
-
303
-		if thisV6 != nil && otherV6 != nil && !types.CompareIPNet(thisV6, otherV6) {
304
-			if err := setINC(thisV6.String(), otherV6.String(), enable); err != nil {
293
+		if thisIface != otherIface {
294
+			if err := setINC(thisIface, otherIface, enable); err != nil {
305 295
 				return err
306 296
 			}
307 297
 		}
... ...
@@ -347,9 +338,11 @@ func (c *networkConfiguration) conflictsWithNetworks(id string, others []*bridge
347 347
 
348 348
 func (d *driver) configure(option map[string]interface{}) error {
349 349
 	var (
350
-		config                *configuration
351
-		err                   error
352
-		natChain, filterChain *iptables.ChainInfo
350
+		config         *configuration
351
+		err            error
352
+		natChain       *iptables.ChainInfo
353
+		filterChain    *iptables.ChainInfo
354
+		isolationChain *iptables.ChainInfo
353 355
 	)
354 356
 
355 357
 	genericData, ok := option[netlabel.GenericData]
... ...
@@ -378,7 +371,7 @@ func (d *driver) configure(option map[string]interface{}) error {
378 378
 	}
379 379
 
380 380
 	if config.EnableIPTables {
381
-		natChain, filterChain, err = setupIPChains(config)
381
+		natChain, filterChain, isolationChain, err = setupIPChains(config)
382 382
 		if err != nil {
383 383
 			return err
384 384
 		}
... ...
@@ -387,6 +380,7 @@ func (d *driver) configure(option map[string]interface{}) error {
387 387
 	d.Lock()
388 388
 	d.natChain = natChain
389 389
 	d.filterChain = filterChain
390
+	d.isolationChain = isolationChain
390 391
 	d.config = config
391 392
 	d.Unlock()
392 393
 
... ...
@@ -11,35 +11,52 @@ import (
11 11
 
12 12
 // DockerChain: DOCKER iptable chain name
13 13
 const (
14
-	DockerChain = "DOCKER"
14
+	DockerChain    = "DOCKER"
15
+	IsolationChain = "DOCKER-ISOLATION"
15 16
 )
16 17
 
17
-func setupIPChains(config *configuration) (*iptables.ChainInfo, *iptables.ChainInfo, error) {
18
+func setupIPChains(config *configuration) (*iptables.ChainInfo, *iptables.ChainInfo, *iptables.ChainInfo, error) {
18 19
 	// Sanity check.
19 20
 	if config.EnableIPTables == false {
20
-		return nil, nil, fmt.Errorf("Cannot create new chains, EnableIPTable is disabled")
21
+		return nil, nil, nil, fmt.Errorf("cannot create new chains, EnableIPTable is disabled")
21 22
 	}
22 23
 
23 24
 	hairpinMode := !config.EnableUserlandProxy
24 25
 
25 26
 	natChain, err := iptables.NewChain(DockerChain, iptables.Nat, hairpinMode)
26 27
 	if err != nil {
27
-		return nil, nil, fmt.Errorf("Failed to create NAT chain: %s", err.Error())
28
+		return nil, nil, nil, fmt.Errorf("failed to create NAT chain: %v", err)
28 29
 	}
29 30
 	defer func() {
30 31
 		if err != nil {
31 32
 			if err := iptables.RemoveExistingChain(DockerChain, iptables.Nat); err != nil {
32
-				logrus.Warnf("Failed on removing iptables NAT chain on cleanup: %v", err)
33
+				logrus.Warnf("failed on removing iptables NAT chain on cleanup: %v", err)
33 34
 			}
34 35
 		}
35 36
 	}()
36 37
 
37
-	filterChain, err := iptables.NewChain(DockerChain, iptables.Filter, hairpinMode)
38
+	filterChain, err := iptables.NewChain(DockerChain, iptables.Filter, false)
38 39
 	if err != nil {
39
-		return nil, nil, fmt.Errorf("Failed to create FILTER chain: %s", err.Error())
40
+		return nil, nil, nil, fmt.Errorf("failed to create FILTER chain: %v", err)
41
+	}
42
+	defer func() {
43
+		if err != nil {
44
+			if err := iptables.RemoveExistingChain(DockerChain, iptables.Filter); err != nil {
45
+				logrus.Warnf("failed on removing iptables FILTER chain on cleanup: %v", err)
46
+			}
47
+		}
48
+	}()
49
+
50
+	isolationChain, err := iptables.NewChain(IsolationChain, iptables.Filter, false)
51
+	if err != nil {
52
+		return nil, nil, nil, fmt.Errorf("failed to create FILTER isolation chain: %v", err)
53
+	}
54
+
55
+	if err := addReturnRule(IsolationChain); err != nil {
56
+		return nil, nil, nil, err
40 57
 	}
41 58
 
42
-	return natChain, filterChain, nil
59
+	return natChain, filterChain, isolationChain, nil
43 60
 }
44 61
 
45 62
 func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInterface) error {
... ...
@@ -72,7 +89,7 @@ func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInt
72 72
 		return setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false)
73 73
 	})
74 74
 
75
-	natChain, filterChain, err := n.getDriverChains()
75
+	natChain, filterChain, _, err := n.getDriverChains()
76 76
 	if err != nil {
77 77
 		return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %s", err.Error())
78 78
 	}
... ...
@@ -86,6 +103,11 @@ func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInt
86 86
 	if err != nil {
87 87
 		return fmt.Errorf("Failed to program FILTER chain: %s", err.Error())
88 88
 	}
89
+
90
+	if err := ensureJumpRule("FORWARD", IsolationChain); err != nil {
91
+		return err
92
+	}
93
+
89 94
 	n.registerIptCleanFunc(func() error {
90 95
 		return iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, false)
91 96
 	})
... ...
@@ -166,10 +188,8 @@ func programChainRule(rule iptRule, ruleDescr string, insert bool) error {
166 166
 	}
167 167
 
168 168
 	if condition {
169
-		if output, err := iptables.Raw(append(prefix, rule.args...)...); err != nil {
169
+		if err := iptables.RawCombinedOutput(append(prefix, rule.args...)...); err != nil {
170 170
 			return fmt.Errorf("Unable to %s %s rule: %s", operation, ruleDescr, err.Error())
171
-		} else if len(output) != 0 {
172
-			return &iptables.ChainError{Chain: rule.chain, Output: output}
173 171
 		}
174 172
 	}
175 173
 
... ...
@@ -190,20 +210,16 @@ func setIcc(bridgeIface string, iccEnable, insert bool) error {
190 190
 			iptables.Raw(append([]string{"-D", chain}, acceptArgs...)...)
191 191
 
192 192
 			if !iptables.Exists(table, chain, dropArgs...) {
193
-				if output, err := iptables.Raw(append([]string{"-A", chain}, dropArgs...)...); err != nil {
193
+				if err := iptables.RawCombinedOutput(append([]string{"-A", chain}, dropArgs...)...); err != nil {
194 194
 					return fmt.Errorf("Unable to prevent intercontainer communication: %s", err.Error())
195
-				} else if len(output) != 0 {
196
-					return fmt.Errorf("Error disabling intercontainer communication: %s", output)
197 195
 				}
198 196
 			}
199 197
 		} else {
200 198
 			iptables.Raw(append([]string{"-D", chain}, dropArgs...)...)
201 199
 
202 200
 			if !iptables.Exists(table, chain, acceptArgs...) {
203
-				if output, err := iptables.Raw(append([]string{"-I", chain}, acceptArgs...)...); err != nil {
201
+				if err := iptables.RawCombinedOutput(append([]string{"-I", chain}, acceptArgs...)...); err != nil {
204 202
 					return fmt.Errorf("Unable to allow intercontainer communication: %s", err.Error())
205
-				} else if len(output) != 0 {
206
-					return fmt.Errorf("Error enabling intercontainer communication: %s", output)
207 203
 				}
208 204
 			}
209 205
 		}
... ...
@@ -224,11 +240,11 @@ func setIcc(bridgeIface string, iccEnable, insert bool) error {
224 224
 }
225 225
 
226 226
 // Control Inter Network Communication. Install/remove only if it is not/is present.
227
-func setINC(network1, network2 string, enable bool) error {
227
+func setINC(iface1, iface2 string, enable bool) error {
228 228
 	var (
229 229
 		table = iptables.Filter
230
-		chain = "FORWARD"
231
-		args  = [2][]string{{"-s", network1, "-d", network2, "-j", "DROP"}, {"-s", network2, "-d", network1, "-j", "DROP"}}
230
+		chain = IsolationChain
231
+		args  = [2][]string{{"-i", iface1, "-o", iface2, "-j", "DROP"}, {"-i", iface2, "-o", iface1, "-j", "DROP"}}
232 232
 	)
233 233
 
234 234
 	if enable {
... ...
@@ -236,10 +252,8 @@ func setINC(network1, network2 string, enable bool) error {
236 236
 			if iptables.Exists(table, chain, args[i]...) {
237 237
 				continue
238 238
 			}
239
-			if output, err := iptables.Raw(append([]string{"-I", chain}, args[i]...)...); err != nil {
240
-				return fmt.Errorf("unable to add inter-network communication rule: %s", err.Error())
241
-			} else if len(output) != 0 {
242
-				return fmt.Errorf("error adding inter-network communication rule: %s", string(output))
239
+			if err := iptables.RawCombinedOutput(append([]string{"-I", chain}, args[i]...)...); err != nil {
240
+				return fmt.Errorf("unable to add inter-network communication rule: %v", err)
243 241
 			}
244 242
 		}
245 243
 	} else {
... ...
@@ -247,13 +261,51 @@ func setINC(network1, network2 string, enable bool) error {
247 247
 			if !iptables.Exists(table, chain, args[i]...) {
248 248
 				continue
249 249
 			}
250
-			if output, err := iptables.Raw(append([]string{"-D", chain}, args[i]...)...); err != nil {
251
-				return fmt.Errorf("unable to remove inter-network communication rule: %s", err.Error())
252
-			} else if len(output) != 0 {
253
-				return fmt.Errorf("error removing inter-network communication rule: %s", string(output))
250
+			if err := iptables.RawCombinedOutput(append([]string{"-D", chain}, args[i]...)...); err != nil {
251
+				return fmt.Errorf("unable to remove inter-network communication rule: %v", err)
254 252
 			}
255 253
 		}
256 254
 	}
257 255
 
258 256
 	return nil
259 257
 }
258
+
259
+func addReturnRule(chain string) error {
260
+	var (
261
+		table = iptables.Filter
262
+		args  = []string{"-j", "RETURN"}
263
+	)
264
+
265
+	if iptables.Exists(table, chain, args...) {
266
+		return nil
267
+	}
268
+
269
+	err := iptables.RawCombinedOutput(append([]string{"-I", chain}, args...)...)
270
+	if err != nil {
271
+		return fmt.Errorf("unable to add return rule in %s chain: %s", chain, err.Error())
272
+	}
273
+
274
+	return nil
275
+}
276
+
277
+// Ensure the jump rule is on top
278
+func ensureJumpRule(fromChain, toChain string) error {
279
+	var (
280
+		table = iptables.Filter
281
+		args  = []string{"-j", toChain}
282
+	)
283
+
284
+	if iptables.Exists(table, fromChain, args...) {
285
+		err := iptables.RawCombinedOutput(append([]string{"-D", fromChain}, args...)...)
286
+		if err != nil {
287
+			return fmt.Errorf("unable to remove jump to %s rule in %s chain: %s", toChain, fromChain, err.Error())
288
+		}
289
+	}
290
+
291
+	err := iptables.RawCombinedOutput(append([]string{"-I", fromChain}, args...)...)
292
+	if err != nil {
293
+		return fmt.Errorf("unable to insert jump to %s rule in %s chain: %s", toChain, fromChain, err.Error())
294
+	}
295
+
296
+	return nil
297
+}
... ...
@@ -12,16 +12,6 @@ const globalChain = "DOCKER-OVERLAY"
12 12
 
13 13
 var filterOnce sync.Once
14 14
 
15
-func rawIPTables(args ...string) error {
16
-	if output, err := iptables.Raw(args...); err != nil {
17
-		return fmt.Errorf("unable to add overlay filter: %v", err)
18
-	} else if len(output) != 0 {
19
-		return fmt.Errorf("unable to add overlay filter: %s", string(output))
20
-	}
21
-
22
-	return nil
23
-}
24
-
25 15
 func chainExists(cname string) bool {
26 16
 	if _, err := iptables.Raw("-L", cname); err != nil {
27 17
 		return false
... ...
@@ -31,12 +21,14 @@ func chainExists(cname string) bool {
31 31
 }
32 32
 
33 33
 func setupGlobalChain() {
34
-	if err := rawIPTables("-N", globalChain); err != nil {
35
-		logrus.Debugf("could not create global overlay chain: %v", err)
34
+	if err := iptables.RawCombinedOutput("-N", globalChain); err != nil {
35
+		logrus.Errorf("could not create global overlay chain: %v", err)
36
+		return
36 37
 	}
37 38
 
38
-	if err := rawIPTables("-A", globalChain, "-j", "RETURN"); err != nil {
39
-		logrus.Debugf("could not install default return chain in the overlay global chain: %v", err)
39
+	if err := iptables.RawCombinedOutput("-A", globalChain, "-j", "RETURN"); err != nil {
40
+		logrus.Errorf("could not install default return chain in the overlay global chain: %v", err)
41
+		return
40 42
 	}
41 43
 }
42 44
 
... ...
@@ -49,21 +41,21 @@ func setNetworkChain(cname string, remove bool) error {
49 49
 	opt := "-N"
50 50
 	// In case of remove, make sure to flush the rules in the chain
51 51
 	if remove && exists {
52
-		if err := rawIPTables("-F", cname); err != nil {
52
+		if err := iptables.RawCombinedOutput("-F", cname); err != nil {
53 53
 			return fmt.Errorf("failed to flush overlay network chain %s rules: %v", cname, err)
54 54
 		}
55 55
 		opt = "-X"
56 56
 	}
57 57
 
58 58
 	if (!remove && !exists) || (remove && exists) {
59
-		if err := rawIPTables(opt, cname); err != nil {
59
+		if err := iptables.RawCombinedOutput(opt, cname); err != nil {
60 60
 			return fmt.Errorf("failed network chain operation %q for chain %s: %v", opt, cname, err)
61 61
 		}
62 62
 	}
63 63
 
64 64
 	if !remove {
65 65
 		if !iptables.Exists(iptables.Filter, cname, "-j", "DROP") {
66
-			if err := rawIPTables("-A", cname, "-j", "DROP"); err != nil {
66
+			if err := iptables.RawCombinedOutput("-A", cname, "-j", "DROP"); err != nil {
67 67
 				return fmt.Errorf("failed adding default drop rule to overlay network chain %s: %v", cname, err)
68 68
 			}
69 69
 		}
... ...
@@ -91,12 +83,12 @@ func setFilters(cname, brName string, remove bool) error {
91 91
 		for _, chain := range []string{"OUTPUT", "FORWARD"} {
92 92
 			exists := iptables.Exists(iptables.Filter, chain, "-j", globalChain)
93 93
 			if exists {
94
-				if err := rawIPTables("-D", chain, "-j", globalChain); err != nil {
94
+				if err := iptables.RawCombinedOutput("-D", chain, "-j", globalChain); err != nil {
95 95
 					return fmt.Errorf("failed to delete overlay hook in chain %s while moving the hook: %v", chain, err)
96 96
 				}
97 97
 			}
98 98
 
99
-			if err := rawIPTables("-I", chain, "-j", globalChain); err != nil {
99
+			if err := iptables.RawCombinedOutput("-I", chain, "-j", globalChain); err != nil {
100 100
 				return fmt.Errorf("failed to insert overlay hook in chain %s: %v", chain, err)
101 101
 			}
102 102
 		}
... ...
@@ -105,7 +97,7 @@ func setFilters(cname, brName string, remove bool) error {
105 105
 	// Insert/Delete the rule to jump to per-bridge chain
106 106
 	exists := iptables.Exists(iptables.Filter, globalChain, "-o", brName, "-j", cname)
107 107
 	if (!remove && !exists) || (remove && exists) {
108
-		if err := rawIPTables(opt, globalChain, "-o", brName, "-j", cname); err != nil {
108
+		if err := iptables.RawCombinedOutput(opt, globalChain, "-o", brName, "-j", cname); err != nil {
109 109
 			return fmt.Errorf("failed to add per-bridge filter rule for bridge %s, network chain %s: %v", brName, cname, err)
110 110
 		}
111 111
 	}
... ...
@@ -115,7 +107,7 @@ func setFilters(cname, brName string, remove bool) error {
115 115
 		return nil
116 116
 	}
117 117
 
118
-	if err := rawIPTables(opt, cname, "-i", brName, "-j", "ACCEPT"); err != nil {
118
+	if err := iptables.RawCombinedOutput(opt, cname, "-i", brName, "-j", "ACCEPT"); err != nil {
119 119
 		return fmt.Errorf("failed to add overlay filter rile for network chain %s, bridge %s: %v", cname, brName, err)
120 120
 	}
121 121
 
... ...
@@ -61,6 +61,7 @@ type endpoint struct {
61 61
 	generic       map[string]interface{}
62 62
 	joinLeaveDone chan struct{}
63 63
 	prefAddress   net.IP
64
+	prefAddressV6 net.IP
64 65
 	ipamOptions   map[string]string
65 66
 	dbIndex       uint64
66 67
 	dbExists      bool
... ...
@@ -688,9 +689,10 @@ func EndpointOptionGeneric(generic map[string]interface{}) EndpointOption {
688 688
 }
689 689
 
690 690
 // CreateOptionIpam function returns an option setter for the ipam configuration for this endpoint
691
-func CreateOptionIpam(prefAddress net.IP, ipamOptions map[string]string) EndpointOption {
691
+func CreateOptionIpam(ipV4, ipV6 net.IP, ipamOptions map[string]string) EndpointOption {
692 692
 	return func(ep *endpoint) {
693
-		ep.prefAddress = prefAddress
693
+		ep.prefAddress = ipV4
694
+		ep.prefAddressV6 = ipV6
694 695
 		ep.ipamOptions = ipamOptions
695 696
 	}
696 697
 }
... ...
@@ -775,6 +777,8 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
775 775
 	var (
776 776
 		poolID  *string
777 777
 		address **net.IPNet
778
+		prefAdd net.IP
779
+		progAdd net.IP
778 780
 	)
779 781
 
780 782
 	n := ep.getNetwork()
... ...
@@ -782,9 +786,11 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
782 782
 	case 4:
783 783
 		poolID = &ep.iface.v4PoolID
784 784
 		address = &ep.iface.addr
785
+		prefAdd = ep.prefAddress
785 786
 	case 6:
786 787
 		poolID = &ep.iface.v6PoolID
787 788
 		address = &ep.iface.addrv6
789
+		prefAdd = ep.prefAddressV6
788 790
 	default:
789 791
 		return types.InternalErrorf("incorrect ip version number passed: %d", ipVer)
790 792
 	}
... ...
@@ -796,12 +802,19 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
796 796
 		return nil
797 797
 	}
798 798
 
799
+	// The address to program may be chosen by the user or by the network driver in one specific
800
+	// case to support backward compatibility with `docker daemon --fixed-cidrv6` use case
801
+	if prefAdd != nil {
802
+		progAdd = prefAdd
803
+	} else if *address != nil {
804
+		progAdd = (*address).IP
805
+	}
806
+
799 807
 	for _, d := range ipInfo {
800
-		var prefIP net.IP
801
-		if *address != nil {
802
-			prefIP = (*address).IP
808
+		if progAdd != nil && !d.Pool.Contains(progAdd) {
809
+			continue
803 810
 		}
804
-		addr, _, err := ipam.RequestAddress(d.PoolID, prefIP, ep.ipamOptions)
811
+		addr, _, err := ipam.RequestAddress(d.PoolID, progAdd, ep.ipamOptions)
805 812
 		if err == nil {
806 813
 			ep.Lock()
807 814
 			*address = addr
... ...
@@ -809,10 +822,13 @@ func (ep *endpoint) assignAddressVersion(ipVer int, ipam ipamapi.Ipam) error {
809 809
 			ep.Unlock()
810 810
 			return nil
811 811
 		}
812
-		if err != ipamapi.ErrNoAvailableIPs {
812
+		if err != ipamapi.ErrNoAvailableIPs || progAdd != nil {
813 813
 			return err
814 814
 		}
815 815
 	}
816
+	if progAdd != nil {
817
+		return types.BadRequestErrorf("Invalid preferred address %s: It does not belong to any of this network's subnets")
818
+	}
816 819
 	return fmt.Errorf("no available IPv%d addresses on this network's address pools: %s (%s)", ipVer, n.Name(), n.ID())
817 820
 }
818 821
 
... ...
@@ -312,6 +312,7 @@ func Exists(table Table, chain string, rule ...string) bool {
312 312
 	// parse "iptables -S" for the rule (this checks rules in a specific chain
313 313
 	// in a specific table)
314 314
 	ruleString := strings.Join(rule, " ")
315
+	ruleString = chain + " " + ruleString
315 316
 	existingRules, _ := exec.Command(iptablesPath, "-t", string(table), "-S", chain).Output()
316 317
 
317 318
 	return strings.Contains(string(existingRules), ruleString)
... ...
@@ -351,3 +352,12 @@ func Raw(args ...string) ([]byte, error) {
351 351
 
352 352
 	return output, err
353 353
 }
354
+
355
+// RawCombinedOutput inernally calls the Raw function and returns a non nil
356
+// error if Raw returned a non nil error or a non empty output
357
+func RawCombinedOutput(args ...string) error {
358
+	if output, err := Raw(args...); err != nil || len(output) != 0 {
359
+		return fmt.Errorf("%s (%v)", string(output), err)
360
+	}
361
+	return nil
362
+}
... ...
@@ -5,7 +5,7 @@ import (
5 5
 )
6 6
 
7 7
 // IPLocalhost is a regex patter for localhost IP address range.
8
-const IPLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1))`
8
+const IPLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)`
9 9
 
10 10
 var localhostIPRegexp = regexp.MustCompile(IPLocalhost)
11 11