Browse code

Merge pull request #19229 from mavenugo/udlinks

Local alias support

Sebastiaan van Stijn authored on 2016/01/13 09:47:32
Showing 12 changed files
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"github.com/docker/docker/opts"
11 11
 	flag "github.com/docker/docker/pkg/mflag"
12 12
 	"github.com/docker/docker/pkg/stringid"
13
+	runconfigopts "github.com/docker/docker/runconfig/opts"
13 14
 	"github.com/docker/engine-api/types"
14 15
 	"github.com/docker/engine-api/types/filters"
15 16
 	"github.com/docker/engine-api/types/network"
... ...
@@ -112,6 +113,8 @@ 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 113
 	flIPAddress := cmd.String([]string{"-ip"}, "", "IP Address")
114 114
 	flIPv6Address := cmd.String([]string{"-ip6"}, "", "IPv6 Address")
115
+	flLinks := opts.NewListOpts(runconfigopts.ValidateLink)
116
+	cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
115 117
 	cmd.Require(flag.Min, 2)
116 118
 	if err := cmd.ParseFlags(args, true); err != nil {
117 119
 		return err
... ...
@@ -121,6 +124,7 @@ func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
121 121
 			IPv4Address: *flIPAddress,
122 122
 			IPv6Address: *flIPv6Address,
123 123
 		},
124
+		Links: flLinks.GetAll(),
124 125
 	}
125 126
 	return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), epConfig)
126 127
 }
... ...
@@ -18,6 +18,7 @@ import (
18 18
 	"github.com/docker/docker/pkg/chrootarchive"
19 19
 	"github.com/docker/docker/pkg/symlink"
20 20
 	"github.com/docker/docker/pkg/system"
21
+	runconfigopts "github.com/docker/docker/runconfig/opts"
21 22
 	"github.com/docker/docker/utils"
22 23
 	"github.com/docker/docker/volume"
23 24
 	"github.com/docker/engine-api/types/container"
... ...
@@ -247,6 +248,21 @@ func (container *Container) UpdateSandboxNetworkSettings(sb libnetwork.Sandbox)
247 247
 	return nil
248 248
 }
249 249
 
250
+// BuildJoinOptions builds endpoint Join options from a given network.
251
+func (container *Container) BuildJoinOptions(n libnetwork.Network) ([]libnetwork.EndpointOption, error) {
252
+	var joinOptions []libnetwork.EndpointOption
253
+	if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok {
254
+		for _, str := range epConfig.Links {
255
+			name, alias, err := runconfigopts.ParseLink(str)
256
+			if err != nil {
257
+				return nil, err
258
+			}
259
+			joinOptions = append(joinOptions, libnetwork.CreateOptionAlias(name, alias))
260
+		}
261
+	}
262
+	return joinOptions, nil
263
+}
264
+
250 265
 // BuildCreateEndpointOptions builds endpoint options from a given network.
251 266
 func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network) ([]libnetwork.EndpointOption, error) {
252 267
 	var (
... ...
@@ -586,7 +586,7 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
586 586
 	if container.NetworkSettings == nil {
587 587
 		container.NetworkSettings = &network.Settings{}
588 588
 	}
589
-	if endpointsConfig != nil {
589
+	if len(endpointsConfig) > 0 {
590 590
 		container.NetworkSettings.Networks = endpointsConfig
591 591
 	}
592 592
 	if container.NetworkSettings.Networks == nil {
... ...
@@ -816,7 +816,12 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
816 816
 		container.UpdateSandboxNetworkSettings(sb)
817 817
 	}
818 818
 
819
-	if err := ep.Join(sb); err != nil {
819
+	joinOptions, err := container.BuildJoinOptions(n)
820
+	if err != nil {
821
+		return err
822
+	}
823
+
824
+	if err := ep.Join(sb, joinOptions...); err != nil {
820 825
 		return err
821 826
 	}
822 827
 
... ...
@@ -868,7 +868,7 @@ func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error
868 868
 
869 869
 // registerLinks writes the links to a file.
870 870
 func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *containertypes.HostConfig) error {
871
-	if hostConfig == nil {
871
+	if hostConfig == nil || hostConfig.NetworkMode.IsUserDefined() {
872 872
 		return nil
873 873
 	}
874 874
 
... ...
@@ -15,6 +15,9 @@ parent = "smn_cli"
15 15
     Connects a container to a network
16 16
 
17 17
       --help             Print usage
18
+      --ip               IP Address
19
+      --ip6              IPv6 Address
20
+      --link=[]          Add a link to another container
18 21
 
19 22
 Connects a container to a network. You can connect a container by name
20 23
 or by ID. Once connected, the container can communicate with other containers in
... ...
@@ -33,7 +36,13 @@ $ docker run -itd --net=multi-host-network busybox
33 33
 You can specify the IP address you want to be assigned to the container's interface.
34 34
 
35 35
 ```bash
36
-$ docker network connect multi-host-network --ip 10.10.36.122 container2
36
+$ docker network connect --ip 10.10.36.122 multi-host-network container2
37
+```
38
+
39
+You can use `--link` option to link another container with a prefered alias
40
+
41
+```bash
42
+$ docker network connect --link container1:c1 multi-host-network container2
37 43
 ```
38 44
 
39 45
 You can pause, restart, and stop containers that are connected to a network.
... ...
@@ -60,3 +69,4 @@ You can connect a container to one or more networks. The networks need not be th
60 60
 * [network ls](network_ls.md)
61 61
 * [network rm](network_rm.md)
62 62
 * [Understand Docker container networks](../../userguide/networking/dockernetworks.md)
63
+* [Work with networks](../../userguide/networking/work-with-networks.md)
... ...
@@ -1301,12 +1301,12 @@ specifies `EXPOSE 80` in the Dockerfile). At runtime, the port might be
1301 1301
 bound to 42800 on the host. To find the mapping between the host ports
1302 1302
 and the exposed ports, use `docker port`.
1303 1303
 
1304
-If the operator uses `--link` when starting a new client container, then the
1305
-client container can access the exposed port via a private networking interface.
1306
-Linking is a legacy feature that is only supported on the default bridge
1307
-network. You should prefer the Docker networks feature instead. For more
1308
-information on this feature, see the [*Docker network
1309
-overview*""](../userguide/networking/index.md)).
1304
+If the operator uses `--link` when starting a new client container in the
1305
+default bridge network, then the client container can access the exposed
1306
+port via a private networking interface.
1307
+If `--link` is used when starting a container in a user-defined network as
1308
+described in [*Docker network overview*""](../userguide/networking/index.md)),
1309
+it will provide a named alias for the container being linked to.
1310 1310
 
1311 1311
 ### ENV (environment variables)
1312 1312
 
... ...
@@ -17,14 +17,11 @@ Before the [Docker networks feature](../dockernetworks.md), you could use the
17 17
 Docker link feature to allow containers to discover each other and securely
18 18
 transfer information about one container to another container. With the
19 19
 introduction of the Docker networks feature, you can still create links but they
20
-are only supported on the default `bridge` network named `bridge` and appearing
21
-in your network stack as `docker0`.
20
+behave differently between default `bridge` network and
21
+[user defined networks](../work-with-networks.md#linking-containers-in-user-defined-networks)
22 22
 
23 23
 This section briefly discusses connecting via a network port and then goes into
24
-detail on container linking. While links are still supported on Docker's default
25
-network (`bridge`), you should avoid them in preference of the Docker
26
-networks feature. Linking is expected to be deprecated and removed in a future
27
-release.
24
+detail on container linking in default `bridge` network.
28 25
 
29 26
 ## Connect using network port mapping
30 27
 
... ...
@@ -268,7 +268,7 @@ PING container3 (172.25.3.3): 56 data bytes
268 268
 round-trip min/avg/max = 0.070/0.081/0.097 ms
269 269
 ```
270 270
 
271
-This isn't the case for the default bridge network. Both `container2` and  `container1` are connected to the default bridge network. Docker does not support automatic service discovery on this network. For this reason, pinging  `container1` by name fails as you would expect based on the `/etc/hosts` file:
271
+This isn't the case for the default `bridge` network. Both `container2` and  `container1` are connected to the default bridge network. Docker does not support automatic service discovery on this network. For this reason, pinging  `container1` by name fails as you would expect based on the `/etc/hosts` file:
272 272
 
273 273
 ```bash
274 274
 / # ping -w 4 container1
... ...
@@ -314,6 +314,192 @@ PING 172.17.0.2 (172.17.0.2): 56 data bytes
314 314
 You can connect both running and non-running containers to a network. However,
315 315
 `docker network inspect` only displays information on running containers.
316 316
 
317
+### Linking containers in user-defined networks
318
+
319
+In the above example, container_2 was able to resolve container_3's name automatically
320
+in the user defined network `isolated_nw`, but the name resolution did not succeed
321
+automatically in the default `bridge` network. This is expected in order to maintain
322
+backward compatibility with [legacy link](default_network/dockerlinks.md).
323
+
324
+The `legacy link` provided 4 major functionalities to the default `bridge` network.
325
+
326
+* name resolution
327
+* name alias for the linked container using `--link=CONTAINER-NAME:ALIAS`
328
+* secured container connectivity (in isolation via `--icc=false`)
329
+* environment variable injection
330
+
331
+Comparing the above 4 functionalities with the non-default user-defined networks such as
332
+`isolated_nw` in this example, without any additional config, `docker network` provides
333
+
334
+* automatic name resolution using DNS
335
+* automatic secured isolated environment for the containers in a network
336
+* ability to dynamically attach and detach to multiple networks
337
+* supports the `--link` option to provide name alias for the linked container
338
+
339
+Continuing with the above example, create another container `container_4` in `isolated_nw`
340
+with `--link` to provide additional name resolution using alias for other containers in
341
+the same network.
342
+
343
+```bash
344
+$ docker run --net=isolated_nw -itd --name=container4 --link container5:c5 busybox
345
+01b5df970834b77a9eadbaff39051f237957bd35c4c56f11193e0594cfd5117c
346
+```
347
+
348
+With the help of `--link` container4 will be able to reach container5 using the
349
+aliased name `c5` as well.
350
+
351
+Please note that while creating container4, we linked to a container named `container5`
352
+which is not created yet. That is one of the differences in behavior between the
353
+`legacy link` in default `bridge` network and the new `link` functionality in user defined
354
+networks. The `legacy link` is static in nature and it hard-binds the container with the
355
+alias and it doesnt tolerate linked container restarts. While the new `link` functionality
356
+in user defined networks are dynamic in nature and supports linked container restarts
357
+including tolerating ip-address changes on the linked container.
358
+
359
+Now let us launch another container named `container5` linking container4 to c4.
360
+
361
+```bash
362
+$ docker run --net=isolated_nw -itd --name=container5 --link container4:c4 busybox
363
+72eccf2208336f31e9e33ba327734125af00d1e1d2657878e2ee8154fbb23c7a
364
+```
365
+
366
+As expected, container4 will be able to reach container5 by both its container name and
367
+its alias c5 and container5 will be able to reach container4 by its container name and
368
+its alias c4.
369
+
370
+```bash
371
+$ docker attach container4
372
+/ # ping -w 4 c5
373
+PING c5 (172.25.0.5): 56 data bytes
374
+64 bytes from 172.25.0.5: seq=0 ttl=64 time=0.070 ms
375
+64 bytes from 172.25.0.5: seq=1 ttl=64 time=0.080 ms
376
+64 bytes from 172.25.0.5: seq=2 ttl=64 time=0.080 ms
377
+64 bytes from 172.25.0.5: seq=3 ttl=64 time=0.097 ms
378
+
379
+--- c5 ping statistics ---
380
+4 packets transmitted, 4 packets received, 0% packet loss
381
+round-trip min/avg/max = 0.070/0.081/0.097 ms
382
+
383
+/ # ping -w 4 container5
384
+PING container5 (172.25.0.5): 56 data bytes
385
+64 bytes from 172.25.0.5: seq=0 ttl=64 time=0.070 ms
386
+64 bytes from 172.25.0.5: seq=1 ttl=64 time=0.080 ms
387
+64 bytes from 172.25.0.5: seq=2 ttl=64 time=0.080 ms
388
+64 bytes from 172.25.0.5: seq=3 ttl=64 time=0.097 ms
389
+
390
+--- container5 ping statistics ---
391
+4 packets transmitted, 4 packets received, 0% packet loss
392
+round-trip min/avg/max = 0.070/0.081/0.097 ms
393
+```
394
+
395
+```bash
396
+$ docker attach container5
397
+/ # ping -w 4 c4
398
+PING c4 (172.25.0.4): 56 data bytes
399
+64 bytes from 172.25.0.4: seq=0 ttl=64 time=0.065 ms
400
+64 bytes from 172.25.0.4: seq=1 ttl=64 time=0.070 ms
401
+64 bytes from 172.25.0.4: seq=2 ttl=64 time=0.067 ms
402
+64 bytes from 172.25.0.4: seq=3 ttl=64 time=0.082 ms
403
+
404
+--- c4 ping statistics ---
405
+4 packets transmitted, 4 packets received, 0% packet loss
406
+round-trip min/avg/max = 0.065/0.070/0.082 ms
407
+
408
+/ # ping -w 4 container4
409
+PING container4 (172.25.0.4): 56 data bytes
410
+64 bytes from 172.25.0.4: seq=0 ttl=64 time=0.065 ms
411
+64 bytes from 172.25.0.4: seq=1 ttl=64 time=0.070 ms
412
+64 bytes from 172.25.0.4: seq=2 ttl=64 time=0.067 ms
413
+64 bytes from 172.25.0.4: seq=3 ttl=64 time=0.082 ms
414
+
415
+--- container4 ping statistics ---
416
+4 packets transmitted, 4 packets received, 0% packet loss
417
+round-trip min/avg/max = 0.065/0.070/0.082 ms
418
+```
419
+
420
+Similar to the legacy link functionality the new link alias is localized to a container
421
+and the aliased name has no meaning outside of the container using the `--link`.
422
+
423
+Also, it is important to note that if a container belongs to multiple networks, the
424
+linked alias is scoped within a given network. Hence the containers can be linked to
425
+different aliases in different networks.
426
+
427
+Extending the example, let us create another network named `local_alias`
428
+
429
+```bash
430
+$ docker network create -d bridge --subnet 172.26.0.0/24 local_alias
431
+76b7dc932e037589e6553f59f76008e5b76fa069638cd39776b890607f567aaa
432
+```
433
+
434
+let us connect container4 and container5 to the new network `local_alias`
435
+
436
+```
437
+$ docker network connect --link container5:foo local_alias container4
438
+$ docker network connect --link container4:bar local_alias container5
439
+```
440
+
441
+```bash
442
+$ docker attach container4
443
+
444
+/ # ping -w 4 foo
445
+PING foo (172.26.0.3): 56 data bytes
446
+64 bytes from 172.26.0.3: seq=0 ttl=64 time=0.070 ms
447
+64 bytes from 172.26.0.3: seq=1 ttl=64 time=0.080 ms
448
+64 bytes from 172.26.0.3: seq=2 ttl=64 time=0.080 ms
449
+64 bytes from 172.26.0.3: seq=3 ttl=64 time=0.097 ms
450
+
451
+--- foo ping statistics ---
452
+4 packets transmitted, 4 packets received, 0% packet loss
453
+round-trip min/avg/max = 0.070/0.081/0.097 ms
454
+
455
+/ # ping -w 4 c5
456
+PING c5 (172.25.0.5): 56 data bytes
457
+64 bytes from 172.25.0.5: seq=0 ttl=64 time=0.070 ms
458
+64 bytes from 172.25.0.5: seq=1 ttl=64 time=0.080 ms
459
+64 bytes from 172.25.0.5: seq=2 ttl=64 time=0.080 ms
460
+64 bytes from 172.25.0.5: seq=3 ttl=64 time=0.097 ms
461
+
462
+--- c5 ping statistics ---
463
+4 packets transmitted, 4 packets received, 0% packet loss
464
+round-trip min/avg/max = 0.070/0.081/0.097 ms
465
+```
466
+
467
+Note that the ping succeeds for both the aliases but on different networks.
468
+Let us conclude this section by disconnecting container5 from the `isolated_nw`
469
+and observe the results
470
+
471
+```
472
+$ docker network disconnect isolated_nw container5
473
+
474
+$ docker attach container4
475
+
476
+/ # ping -w 4 c5
477
+ping: bad address 'c5'
478
+
479
+/ # ping -w 4 foo
480
+PING foo (172.26.0.3): 56 data bytes
481
+64 bytes from 172.26.0.3: seq=0 ttl=64 time=0.070 ms
482
+64 bytes from 172.26.0.3: seq=1 ttl=64 time=0.080 ms
483
+64 bytes from 172.26.0.3: seq=2 ttl=64 time=0.080 ms
484
+64 bytes from 172.26.0.3: seq=3 ttl=64 time=0.097 ms
485
+
486
+--- foo ping statistics ---
487
+4 packets transmitted, 4 packets received, 0% packet loss
488
+round-trip min/avg/max = 0.070/0.081/0.097 ms
489
+
490
+```
491
+
492
+In conclusion, the new link functionality in user defined networks provides all the
493
+benefits of legacy links while avoiding most of the well-known issues with `legacy links`.
494
+
495
+One notable missing functionality compared to `legacy links` is the injection of
496
+environment variables. Though very useful, environment variable injection is static
497
+in nature and must be injected when the container is started. One cannot inject
498
+environment variables into a running container without significant effort and hence
499
+it is not compatible with `docker network` which provides a dynamic way to connect/
500
+disconnect containers to/from a network.
501
+
502
+
317 503
 ## Disconnecting containers
318 504
 
319 505
 You can disconnect a container from a network using the `docker network
... ...
@@ -1040,3 +1040,44 @@ func verifyIPAddresses(c *check.C, cName, nwname, ipv4, ipv6 string) {
1040 1040
 	out, _ = dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.GlobalIPv6Address }}'", nwname), cName)
1041 1041
 	c.Assert(strings.TrimSpace(out), check.Equals, ipv6)
1042 1042
 }
1043
+
1044
+func (s *DockerSuite) TestUserDefinedNetworkConnectDisconnectLink(c *check.C) {
1045
+	testRequires(c, DaemonIsLinux, NotUserNamespace)
1046
+	dockerCmd(c, "network", "create", "-d", "bridge", "foo1")
1047
+	dockerCmd(c, "network", "create", "-d", "bridge", "foo2")
1048
+
1049
+	dockerCmd(c, "run", "-d", "--net=foo1", "--name=first", "busybox", "top")
1050
+	c.Assert(waitRun("first"), check.IsNil)
1051
+
1052
+	// run a container in user-defined network udlinkNet with a link for an existing container
1053
+	// and a link for a container that doesnt exist
1054
+	dockerCmd(c, "run", "-d", "--net=foo1", "--name=second", "--link=first:FirstInFoo1",
1055
+		"--link=third:bar", "busybox", "top")
1056
+	c.Assert(waitRun("second"), check.IsNil)
1057
+
1058
+	// ping to first and its alias FirstInFoo1 must succeed
1059
+	_, _, err := dockerCmdWithError("exec", "second", "ping", "-c", "1", "first")
1060
+	c.Assert(err, check.IsNil)
1061
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "FirstInFoo1")
1062
+	c.Assert(err, check.IsNil)
1063
+
1064
+	// connect first container to foo2 network
1065
+	dockerCmd(c, "network", "connect", "foo2", "first")
1066
+	// connect second container to foo2 network with a different alias for first container
1067
+	dockerCmd(c, "network", "connect", "--link=first:FirstInFoo2", "foo2", "second")
1068
+
1069
+	// ping the new alias in network foo2
1070
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "FirstInFoo2")
1071
+	c.Assert(err, check.IsNil)
1072
+
1073
+	// disconnect first container from foo1 network
1074
+	dockerCmd(c, "network", "disconnect", "foo1", "first")
1075
+
1076
+	// link in foo1 network must fail
1077
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "FirstInFoo1")
1078
+	c.Assert(err, check.NotNil)
1079
+
1080
+	// link in foo2 network must succeed
1081
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "FirstInFoo2")
1082
+	c.Assert(err, check.IsNil)
1083
+}
... ...
@@ -199,6 +199,80 @@ func (s *DockerSuite) TestRunLinksContainerWithContainerId(c *check.C) {
199 199
 	}
200 200
 }
201 201
 
202
+func (s *DockerSuite) TestUserDefinedNetworkLinks(c *check.C) {
203
+	testRequires(c, DaemonIsLinux, NotUserNamespace)
204
+	dockerCmd(c, "network", "create", "-d", "bridge", "udlinkNet")
205
+
206
+	dockerCmd(c, "run", "-d", "--net=udlinkNet", "--name=first", "busybox", "top")
207
+	c.Assert(waitRun("first"), check.IsNil)
208
+
209
+	// run a container in user-defined network udlinkNet with a link for an existing container
210
+	// and a link for a container that doesnt exist
211
+	dockerCmd(c, "run", "-d", "--net=udlinkNet", "--name=second", "--link=first:foo",
212
+		"--link=third:bar", "busybox", "top")
213
+	c.Assert(waitRun("second"), check.IsNil)
214
+
215
+	// ping to first and its alias foo must succeed
216
+	_, _, err := dockerCmdWithError("exec", "second", "ping", "-c", "1", "first")
217
+	c.Assert(err, check.IsNil)
218
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "foo")
219
+	c.Assert(err, check.IsNil)
220
+
221
+	// ping to third and its alias must fail
222
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "third")
223
+	c.Assert(err, check.NotNil)
224
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "bar")
225
+	c.Assert(err, check.NotNil)
226
+
227
+	// start third container now
228
+	dockerCmd(c, "run", "-d", "--net=udlinkNet", "--name=third", "busybox", "top")
229
+	c.Assert(waitRun("third"), check.IsNil)
230
+
231
+	// ping to third and its alias must succeed now
232
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "third")
233
+	c.Assert(err, check.IsNil)
234
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "bar")
235
+	c.Assert(err, check.IsNil)
236
+}
237
+
238
+func (s *DockerSuite) TestUserDefinedNetworkLinksWithRestart(c *check.C) {
239
+	testRequires(c, DaemonIsLinux, NotUserNamespace)
240
+	dockerCmd(c, "network", "create", "-d", "bridge", "udlinkNet")
241
+
242
+	dockerCmd(c, "run", "-d", "--net=udlinkNet", "--name=first", "busybox", "top")
243
+	c.Assert(waitRun("first"), check.IsNil)
244
+
245
+	dockerCmd(c, "run", "-d", "--net=udlinkNet", "--name=second", "--link=first:foo",
246
+		"busybox", "top")
247
+	c.Assert(waitRun("second"), check.IsNil)
248
+
249
+	// ping to first and its alias foo must succeed
250
+	_, _, err := dockerCmdWithError("exec", "second", "ping", "-c", "1", "first")
251
+	c.Assert(err, check.IsNil)
252
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "foo")
253
+	c.Assert(err, check.IsNil)
254
+
255
+	// Restart first container
256
+	dockerCmd(c, "restart", "first")
257
+	c.Assert(waitRun("first"), check.IsNil)
258
+
259
+	// ping to first and its alias foo must still succeed
260
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "first")
261
+	c.Assert(err, check.IsNil)
262
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "foo")
263
+	c.Assert(err, check.IsNil)
264
+
265
+	// Restart second container
266
+	dockerCmd(c, "restart", "second")
267
+	c.Assert(waitRun("second"), check.IsNil)
268
+
269
+	// ping to first and its alias foo must still succeed
270
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "first")
271
+	c.Assert(err, check.IsNil)
272
+	_, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "foo")
273
+	c.Assert(err, check.IsNil)
274
+}
275
+
202 276
 // Issue 9677.
203 277
 func (s *DockerSuite) TestRunWithDaemonFlags(c *check.C) {
204 278
 	out, _, err := dockerCmdWithError("--exec-opt", "foo=bar", "run", "-i", "busybox", "true")
... ...
@@ -48,10 +48,6 @@ func ValidateNetMode(c *container.Config, hc *container.HostConfig) error {
48 48
 		return ErrConflictContainerNetworkAndLinks
49 49
 	}
50 50
 
51
-	if hc.NetworkMode.IsUserDefined() && len(hc.Links) > 0 {
52
-		return ErrConflictUserDefinedNetworkAndLinks
53
-	}
54
-
55 51
 	if (hc.NetworkMode.IsHost() || hc.NetworkMode.IsContainer()) && len(hc.DNS) > 0 {
56 52
 		return ErrConflictNetworkAndDNS
57 53
 	}
... ...
@@ -417,11 +417,11 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
417 417
 		config.StdinOnce = true
418 418
 	}
419 419
 
420
-	var networkingConfig *networktypes.NetworkingConfig
420
+	networkingConfig := &networktypes.NetworkingConfig{
421
+		EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
422
+	}
423
+
421 424
 	if *flIPv4Address != "" || *flIPv6Address != "" {
422
-		networkingConfig = &networktypes.NetworkingConfig{
423
-			EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
424
-		}
425 425
 		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = &networktypes.EndpointSettings{
426 426
 			IPAMConfig: &networktypes.EndpointIPAMConfig{
427 427
 				IPv4Address: *flIPv4Address,
... ...
@@ -430,6 +430,16 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
430 430
 		}
431 431
 	}
432 432
 
433
+	if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 {
434
+		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
435
+		if epConfig == nil {
436
+			epConfig = &networktypes.EndpointSettings{}
437
+		}
438
+		epConfig.Links = make([]string, len(hostConfig.Links))
439
+		copy(epConfig.Links, hostConfig.Links)
440
+		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
441
+	}
442
+
433 443
 	return config, hostConfig, networkingConfig, cmd, nil
434 444
 }
435 445