bridge_test.go 17 KB

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