bridge_test.go 23 KB

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