bridge_test.go 22 KB

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