bridge_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. package networking
  2. import (
  3. "context"
  4. "fmt"
  5. "testing"
  6. "time"
  7. "github.com/docker/docker/api/types"
  8. containertypes "github.com/docker/docker/api/types/container"
  9. "github.com/docker/docker/integration/internal/container"
  10. "github.com/docker/docker/integration/internal/network"
  11. "github.com/docker/docker/testutil"
  12. "github.com/docker/docker/testutil/daemon"
  13. "gotest.tools/v3/assert"
  14. is "gotest.tools/v3/assert/cmp"
  15. "gotest.tools/v3/skip"
  16. )
  17. // TestBridgeICC tries to ping container ctr1 from container ctr2 using its hostname. Thus, this test checks:
  18. // 1. DNS resolution ; 2. ARP/NDP ; 3. whether containers can communicate with each other ; 4. kernel-assigned SLAAC
  19. // addresses.
  20. func TestBridgeICC(t *testing.T) {
  21. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  22. ctx := setupTest(t)
  23. d := daemon.New(t)
  24. d.StartWithBusybox(ctx, t, "-D", "--experimental", "--ip6tables")
  25. defer d.Stop(t)
  26. c := d.NewClientT(t)
  27. defer c.Close()
  28. testcases := []struct {
  29. name string
  30. bridgeOpts []func(*types.NetworkCreate)
  31. ctr1MacAddress string
  32. isIPv6 bool
  33. isLinkLocal bool
  34. pingHost string
  35. }{
  36. {
  37. name: "IPv4 non-internal network",
  38. bridgeOpts: []func(*types.NetworkCreate){},
  39. },
  40. {
  41. name: "IPv4 internal network",
  42. bridgeOpts: []func(*types.NetworkCreate){
  43. network.WithInternal(),
  44. },
  45. },
  46. {
  47. name: "IPv6 ULA on non-internal network",
  48. bridgeOpts: []func(*types.NetworkCreate){
  49. network.WithIPv6(),
  50. network.WithIPAM("fdf1:a844:380c:b200::/64", "fdf1:a844:380c:b200::1"),
  51. },
  52. isIPv6: true,
  53. },
  54. {
  55. name: "IPv6 ULA on internal network",
  56. bridgeOpts: []func(*types.NetworkCreate){
  57. network.WithIPv6(),
  58. network.WithInternal(),
  59. network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"),
  60. },
  61. isIPv6: true,
  62. },
  63. {
  64. name: "IPv6 link-local address on non-internal network",
  65. bridgeOpts: []func(*types.NetworkCreate){
  66. network.WithIPv6(),
  67. // There's no real way to specify an IPv6 network is only used with SLAAC link-local IPv6 addresses.
  68. // What we can do instead, is to tell the IPAM driver to assign addresses from the link-local prefix.
  69. // Each container will have two link-local addresses: 1. a SLAAC address assigned by the kernel ;
  70. // 2. the one dynamically assigned by the IPAM driver.
  71. network.WithIPAM("fe80::/64", "fe80::1"),
  72. },
  73. isLinkLocal: true,
  74. isIPv6: true,
  75. },
  76. {
  77. name: "IPv6 link-local address on internal network",
  78. bridgeOpts: []func(*types.NetworkCreate){
  79. network.WithIPv6(),
  80. network.WithInternal(),
  81. // See the note above about link-local addresses.
  82. network.WithIPAM("fe80::/64", "fe80::1"),
  83. },
  84. isLinkLocal: true,
  85. isIPv6: true,
  86. },
  87. {
  88. // As for 'LL non-internal', ping the container by name instead of by address
  89. // - the busybox test containers only have one interface with a link local
  90. // address, so the zone index is not required:
  91. // RFC-4007, section 6: "[...] for nodes with only a single non-loopback
  92. // interface (e.g., a single Ethernet interface), the common case, link-local
  93. // addresses need not be qualified with a zone index."
  94. // So, for this common case, LL addresses should be included in DNS config.
  95. name: "IPv6 link-local address on non-internal network ping by name",
  96. bridgeOpts: []func(*types.NetworkCreate){
  97. network.WithIPv6(),
  98. network.WithIPAM("fe80::/64", "fe80::1"),
  99. },
  100. isIPv6: true,
  101. },
  102. {
  103. name: "IPv6 nonstandard link-local subnet on non-internal network ping by name",
  104. // No interfaces apart from the one on the bridge network with this non-default
  105. // subnet will be on this link local subnet (it's not currently possible to
  106. // configure two networks with the same LL subnet, although perhaps it should
  107. // be). So, again, no zone index is required and the LL address should be
  108. // included in DNS config.
  109. bridgeOpts: []func(*types.NetworkCreate){
  110. network.WithIPv6(),
  111. network.WithIPAM("fe80:1234::/64", "fe80:1234::1"),
  112. },
  113. isIPv6: true,
  114. },
  115. {
  116. name: "IPv6 non-internal network with SLAAC LL address",
  117. bridgeOpts: []func(*types.NetworkCreate){
  118. network.WithIPv6(),
  119. network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"),
  120. },
  121. // Link-local address is derived from the MAC address, so we need to
  122. // specify one here to hardcode the SLAAC LL address below.
  123. ctr1MacAddress: "02:42:ac:11:00:02",
  124. pingHost: "fe80::42:acff:fe11:2%eth0",
  125. isIPv6: true,
  126. },
  127. {
  128. name: "IPv6 internal network with SLAAC LL address",
  129. bridgeOpts: []func(*types.NetworkCreate){
  130. network.WithIPv6(),
  131. network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"),
  132. },
  133. // Link-local address is derived from the MAC address, so we need to
  134. // specify one here to hardcode the SLAAC LL address below.
  135. ctr1MacAddress: "02:42:ac:11:00:02",
  136. pingHost: "fe80::42:acff:fe11:2%eth0",
  137. isIPv6: true,
  138. },
  139. }
  140. for tcID, tc := range testcases {
  141. t.Run(tc.name, func(t *testing.T) {
  142. ctx := testutil.StartSpan(ctx, t)
  143. bridgeName := fmt.Sprintf("testnet-icc-%d", tcID)
  144. network.CreateNoError(ctx, t, c, bridgeName, append(tc.bridgeOpts,
  145. network.WithDriver("bridge"),
  146. network.WithOption("com.docker.network.bridge.name", bridgeName))...)
  147. defer network.RemoveNoError(ctx, t, c, bridgeName)
  148. ctr1Name := fmt.Sprintf("ctr-icc-%d-1", tcID)
  149. var ctr1Opts []func(config *container.TestContainerConfig)
  150. if tc.ctr1MacAddress != "" {
  151. ctr1Opts = append(ctr1Opts, container.WithMacAddress(bridgeName, tc.ctr1MacAddress))
  152. }
  153. id1 := container.Run(ctx, t, c, append(ctr1Opts,
  154. container.WithName(ctr1Name),
  155. container.WithImage("busybox:latest"),
  156. container.WithCmd("top"),
  157. container.WithNetworkMode(bridgeName))...)
  158. defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{
  159. Force: true,
  160. })
  161. pingHost := tc.pingHost
  162. if pingHost == "" {
  163. if tc.isLinkLocal {
  164. inspect := container.Inspect(ctx, t, c, id1)
  165. pingHost = inspect.NetworkSettings.Networks[bridgeName].GlobalIPv6Address + "%eth0"
  166. } else {
  167. pingHost = ctr1Name
  168. }
  169. }
  170. ipv := "-4"
  171. if tc.isIPv6 {
  172. ipv = "-6"
  173. }
  174. pingCmd := []string{"ping", "-c1", "-W3", ipv, pingHost}
  175. ctr2Name := fmt.Sprintf("ctr-icc-%d-2", tcID)
  176. attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
  177. defer cancel()
  178. res := container.RunAttach(attachCtx, t, c,
  179. container.WithName(ctr2Name),
  180. container.WithImage("busybox:latest"),
  181. container.WithCmd(pingCmd...),
  182. container.WithNetworkMode(bridgeName))
  183. defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{
  184. Force: true,
  185. })
  186. assert.Check(t, is.Equal(res.ExitCode, 0))
  187. assert.Check(t, is.Equal(res.Stderr.Len(), 0))
  188. assert.Check(t, is.Contains(res.Stdout.String(), "1 packets transmitted, 1 packets received"))
  189. })
  190. }
  191. }
  192. // TestBridgeICCWindows tries to ping container ctr1 from container ctr2 using its hostname.
  193. // Checks DNS resolution, and whether containers can communicate with each other.
  194. // Regression test for https://github.com/moby/moby/issues/47370
  195. func TestBridgeICCWindows(t *testing.T) {
  196. skip.If(t, testEnv.DaemonInfo.OSType != "windows")
  197. ctx := setupTest(t)
  198. c := testEnv.APIClient()
  199. testcases := []struct {
  200. name string
  201. netName string
  202. }{
  203. {
  204. name: "Default nat network",
  205. netName: "nat",
  206. },
  207. {
  208. name: "User defined nat network",
  209. netName: "mynat",
  210. },
  211. }
  212. for _, tc := range testcases {
  213. t.Run(tc.name, func(t *testing.T) {
  214. ctx := testutil.StartSpan(ctx, t)
  215. if tc.netName != "nat" {
  216. network.CreateNoError(ctx, t, c, tc.netName,
  217. network.WithDriver("nat"),
  218. )
  219. defer network.RemoveNoError(ctx, t, c, tc.netName)
  220. }
  221. const ctr1Name = "ctr1"
  222. id1 := container.Run(ctx, t, c,
  223. container.WithName(ctr1Name),
  224. container.WithNetworkMode(tc.netName),
  225. )
  226. defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{Force: true})
  227. pingCmd := []string{"ping", "-n", "1", "-w", "3000", ctr1Name}
  228. const ctr2Name = "ctr2"
  229. attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
  230. defer cancel()
  231. res := container.RunAttach(attachCtx, t, c,
  232. container.WithName(ctr2Name),
  233. container.WithCmd(pingCmd...),
  234. container.WithNetworkMode(tc.netName),
  235. )
  236. defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{Force: true})
  237. assert.Check(t, is.Equal(res.ExitCode, 0))
  238. assert.Check(t, is.Equal(res.Stderr.Len(), 0))
  239. assert.Check(t, is.Contains(res.Stdout.String(), "Sent = 1, Received = 1, Lost = 0"))
  240. })
  241. }
  242. }
  243. // TestBridgeINC makes sure two containers on two different bridge networks can't communicate with each other.
  244. func TestBridgeINC(t *testing.T) {
  245. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  246. ctx := setupTest(t)
  247. d := daemon.New(t)
  248. d.StartWithBusybox(ctx, t, "-D", "--experimental", "--ip6tables")
  249. defer d.Stop(t)
  250. c := d.NewClientT(t)
  251. defer c.Close()
  252. type bridgesOpts struct {
  253. bridge1Opts []func(*types.NetworkCreate)
  254. bridge2Opts []func(*types.NetworkCreate)
  255. }
  256. testcases := []struct {
  257. name string
  258. bridges bridgesOpts
  259. ipv6 bool
  260. stdout string
  261. stderr string
  262. }{
  263. {
  264. name: "IPv4 non-internal network",
  265. bridges: bridgesOpts{
  266. bridge1Opts: []func(*types.NetworkCreate){},
  267. bridge2Opts: []func(*types.NetworkCreate){},
  268. },
  269. stdout: "1 packets transmitted, 0 packets received",
  270. },
  271. {
  272. name: "IPv4 internal network",
  273. bridges: bridgesOpts{
  274. bridge1Opts: []func(*types.NetworkCreate){network.WithInternal()},
  275. bridge2Opts: []func(*types.NetworkCreate){network.WithInternal()},
  276. },
  277. stderr: "sendto: Network is unreachable",
  278. },
  279. {
  280. name: "IPv6 ULA on non-internal network",
  281. bridges: bridgesOpts{
  282. bridge1Opts: []func(*types.NetworkCreate){
  283. network.WithIPv6(),
  284. network.WithIPAM("fdf1:a844:380c:b200::/64", "fdf1:a844:380c:b200::1"),
  285. },
  286. bridge2Opts: []func(*types.NetworkCreate){
  287. network.WithIPv6(),
  288. network.WithIPAM("fdf1:a844:380c:b247::/64", "fdf1:a844:380c:b247::1"),
  289. },
  290. },
  291. ipv6: true,
  292. stdout: "1 packets transmitted, 0 packets received",
  293. },
  294. {
  295. name: "IPv6 ULA on internal network",
  296. bridges: bridgesOpts{
  297. bridge1Opts: []func(*types.NetworkCreate){
  298. network.WithIPv6(),
  299. network.WithInternal(),
  300. network.WithIPAM("fdf1:a844:390c:b200::/64", "fdf1:a844:390c:b200::1"),
  301. },
  302. bridge2Opts: []func(*types.NetworkCreate){
  303. network.WithIPv6(),
  304. network.WithInternal(),
  305. network.WithIPAM("fdf1:a844:390c:b247::/64", "fdf1:a844:390c:b247::1"),
  306. },
  307. },
  308. ipv6: true,
  309. stderr: "sendto: Network is unreachable",
  310. },
  311. }
  312. for tcID, tc := range testcases {
  313. t.Run(tc.name, func(t *testing.T) {
  314. ctx := testutil.StartSpan(ctx, t)
  315. bridge1 := fmt.Sprintf("testnet-inc-%d-1", tcID)
  316. bridge2 := fmt.Sprintf("testnet-inc-%d-2", tcID)
  317. network.CreateNoError(ctx, t, c, bridge1, append(tc.bridges.bridge1Opts,
  318. network.WithDriver("bridge"),
  319. network.WithOption("com.docker.network.bridge.name", bridge1))...)
  320. defer network.RemoveNoError(ctx, t, c, bridge1)
  321. network.CreateNoError(ctx, t, c, bridge2, append(tc.bridges.bridge2Opts,
  322. network.WithDriver("bridge"),
  323. network.WithOption("com.docker.network.bridge.name", bridge2))...)
  324. defer network.RemoveNoError(ctx, t, c, bridge2)
  325. ctr1Name := sanitizeCtrName(t.Name() + "-ctr1")
  326. id1 := container.Run(ctx, t, c,
  327. container.WithName(ctr1Name),
  328. container.WithImage("busybox:latest"),
  329. container.WithCmd("top"),
  330. container.WithNetworkMode(bridge1))
  331. defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{
  332. Force: true,
  333. })
  334. ctr1Info := container.Inspect(ctx, t, c, id1)
  335. targetAddr := ctr1Info.NetworkSettings.Networks[bridge1].IPAddress
  336. if tc.ipv6 {
  337. targetAddr = ctr1Info.NetworkSettings.Networks[bridge1].GlobalIPv6Address
  338. }
  339. pingCmd := []string{"ping", "-c1", "-W3", targetAddr}
  340. ctr2Name := sanitizeCtrName(t.Name() + "-ctr2")
  341. attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
  342. defer cancel()
  343. res := container.RunAttach(attachCtx, t, c,
  344. container.WithName(ctr2Name),
  345. container.WithImage("busybox:latest"),
  346. container.WithCmd(pingCmd...),
  347. container.WithNetworkMode(bridge2))
  348. defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{
  349. Force: true,
  350. })
  351. assert.Check(t, res.ExitCode != 0, "ping unexpectedly succeeded")
  352. assert.Check(t, is.Contains(res.Stdout.String(), tc.stdout))
  353. assert.Check(t, is.Contains(res.Stderr.String(), tc.stderr))
  354. })
  355. }
  356. }
  357. func TestDefaultBridgeIPv6(t *testing.T) {
  358. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  359. ctx := setupTest(t)
  360. testcases := []struct {
  361. name string
  362. fixed_cidr_v6 string
  363. }{
  364. {
  365. name: "IPv6 ULA",
  366. fixed_cidr_v6: "fd00:1234::/64",
  367. },
  368. {
  369. name: "IPv6 LLA only",
  370. fixed_cidr_v6: "fe80::/64",
  371. },
  372. {
  373. name: "IPv6 nonstandard LLA only",
  374. fixed_cidr_v6: "fe80:1234::/64",
  375. },
  376. }
  377. for _, tc := range testcases {
  378. t.Run(tc.name, func(t *testing.T) {
  379. ctx := testutil.StartSpan(ctx, t)
  380. d := daemon.New(t)
  381. d.StartWithBusybox(ctx, t,
  382. "--experimental",
  383. "--ip6tables",
  384. "--ipv6",
  385. "--fixed-cidr-v6", tc.fixed_cidr_v6,
  386. )
  387. defer d.Stop(t)
  388. c := d.NewClientT(t)
  389. defer c.Close()
  390. cID := container.Run(ctx, t, c,
  391. container.WithImage("busybox:latest"),
  392. container.WithCmd("top"),
  393. )
  394. defer c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{
  395. Force: true,
  396. })
  397. networkName := "bridge"
  398. inspect := container.Inspect(ctx, t, c, cID)
  399. pingHost := inspect.NetworkSettings.Networks[networkName].GlobalIPv6Address
  400. attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
  401. defer cancel()
  402. res := container.RunAttach(attachCtx, t, c,
  403. container.WithImage("busybox:latest"),
  404. container.WithCmd("ping", "-c1", "-W3", pingHost),
  405. )
  406. defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{
  407. Force: true,
  408. })
  409. assert.Check(t, is.Equal(res.ExitCode, 0))
  410. assert.Check(t, is.Equal(res.Stderr.String(), ""))
  411. assert.Check(t, is.Contains(res.Stdout.String(), "1 packets transmitted, 1 packets received"))
  412. })
  413. }
  414. }
  415. // Check that it's possible to change 'fixed-cidr-v6' and restart the daemon.
  416. func TestDefaultBridgeAddresses(t *testing.T) {
  417. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  418. ctx := setupTest(t)
  419. d := daemon.New(t)
  420. type testStep struct {
  421. stepName string
  422. fixedCIDRV6 string
  423. expAddrs []string
  424. }
  425. testcases := []struct {
  426. name string
  427. steps []testStep
  428. }{
  429. {
  430. name: "Unique-Local Subnet Changes",
  431. steps: []testStep{
  432. {
  433. stepName: "Set up initial UL prefix",
  434. fixedCIDRV6: "fd1c:f1a0:5d8d:aaaa::/64",
  435. expAddrs: []string{"fd1c:f1a0:5d8d:aaaa::1/64", "fe80::1/64"},
  436. },
  437. {
  438. // Modify that prefix, the default bridge's address must be deleted and re-added.
  439. stepName: "Modify UL prefix - address change",
  440. fixedCIDRV6: "fd1c:f1a0:5d8d:bbbb::/64",
  441. expAddrs: []string{"fd1c:f1a0:5d8d:bbbb::1/64", "fe80::1/64"},
  442. },
  443. {
  444. // Modify the prefix length, the default bridge's address should not change.
  445. stepName: "Modify UL prefix - no address change",
  446. fixedCIDRV6: "fd1c:f1a0:5d8d:bbbb::/80",
  447. // The prefix length displayed by 'ip a' is not updated - it's informational, and
  448. // can't be changed without unnecessarily deleting and re-adding the address.
  449. expAddrs: []string{"fd1c:f1a0:5d8d:bbbb::1/64", "fe80::1/64"},
  450. },
  451. },
  452. },
  453. {
  454. name: "Link-Local Subnet Changes",
  455. steps: []testStep{
  456. {
  457. stepName: "Standard LL subnet prefix",
  458. fixedCIDRV6: "fe80::/64",
  459. expAddrs: []string{"fe80::1/64"},
  460. },
  461. {
  462. // Modify that prefix, the default bridge's address must be deleted and re-added.
  463. // The bridge must still have an address in the required (standard) LL subnet.
  464. stepName: "Nonstandard LL prefix - address change",
  465. fixedCIDRV6: "fe80:1234::/32",
  466. expAddrs: []string{"fe80:1234::1/32", "fe80::1/64"},
  467. },
  468. {
  469. // Modify the prefix length, the addresses should not change.
  470. stepName: "Modify LL prefix - no address change",
  471. fixedCIDRV6: "fe80:1234::/64",
  472. // The prefix length displayed by 'ip a' is not updated - it's informational, and
  473. // can't be changed without unnecessarily deleting and re-adding the address.
  474. expAddrs: []string{"fe80:1234::1/", "fe80::1/64"},
  475. },
  476. },
  477. },
  478. }
  479. for _, tc := range testcases {
  480. t.Run(tc.name, func(t *testing.T) {
  481. for _, step := range tc.steps {
  482. // Check that the daemon starts - regression test for:
  483. // https://github.com/moby/moby/issues/46829
  484. d.Start(t, "--experimental", "--ipv6", "--ip6tables", "--fixed-cidr-v6="+step.fixedCIDRV6)
  485. d.Stop(t)
  486. // Check that the expected addresses have been applied to the bridge. (Skip in
  487. // rootless mode, because the bridge is in a different network namespace.)
  488. if !testEnv.IsRootless() {
  489. res := testutil.RunCommand(ctx, "ip", "-6", "addr", "show", "docker0")
  490. assert.Equal(t, res.ExitCode, 0, step.stepName)
  491. stdout := res.Stdout()
  492. for _, expAddr := range step.expAddrs {
  493. assert.Check(t, is.Contains(stdout, expAddr))
  494. }
  495. }
  496. }
  497. })
  498. }
  499. }
  500. // Test that a container on an 'internal' network has IP connectivity with
  501. // the host (on its own subnet, because the n/w bridge has an address on that
  502. // subnet, and it's in the host's namespace).
  503. // Regression test for https://github.com/moby/moby/issues/47329
  504. func TestInternalNwConnectivity(t *testing.T) {
  505. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  506. ctx := setupTest(t)
  507. d := daemon.New(t)
  508. d.StartWithBusybox(ctx, t, "-D", "--experimental", "--ip6tables")
  509. defer d.Stop(t)
  510. c := d.NewClientT(t)
  511. defer c.Close()
  512. const bridgeName = "intnw"
  513. const gw4 = "172.30.0.1"
  514. const gw6 = "fda9:4130:4715::1234"
  515. network.CreateNoError(ctx, t, c, bridgeName,
  516. network.WithInternal(),
  517. network.WithIPv6(),
  518. network.WithIPAM("172.30.0.0/24", gw4),
  519. network.WithIPAM("fda9:4130:4715::/64", gw6),
  520. network.WithDriver("bridge"),
  521. network.WithOption("com.docker.network.bridge.name", bridgeName),
  522. )
  523. defer network.RemoveNoError(ctx, t, c, bridgeName)
  524. const ctrName = "intctr"
  525. id := container.Run(ctx, t, c,
  526. container.WithName(ctrName),
  527. container.WithImage("busybox:latest"),
  528. container.WithCmd("top"),
  529. container.WithNetworkMode(bridgeName),
  530. )
  531. defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true})
  532. execCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
  533. defer cancel()
  534. res := container.ExecT(execCtx, t, c, id, []string{"ping", "-c1", "-W3", gw4})
  535. assert.Check(t, is.Equal(res.ExitCode, 0))
  536. assert.Check(t, is.Equal(res.Stderr(), ""))
  537. assert.Check(t, is.Contains(res.Stdout(), "1 packets transmitted, 1 packets received"))
  538. res = container.ExecT(execCtx, t, c, id, []string{"ping6", "-c1", "-W3", gw6})
  539. assert.Check(t, is.Equal(res.ExitCode, 0))
  540. assert.Check(t, is.Equal(res.Stderr(), ""))
  541. assert.Check(t, is.Contains(res.Stdout(), "1 packets transmitted, 1 packets received"))
  542. // Addresses outside the internal subnet must not be accessible.
  543. res = container.ExecT(execCtx, t, c, id, []string{"ping", "-c1", "-W3", "8.8.8.8"})
  544. assert.Check(t, is.Equal(res.ExitCode, 1))
  545. assert.Check(t, is.Contains(res.Stderr(), "Network is unreachable"))
  546. }