ipvlan_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. //go:build !windows
  2. // +build !windows
  3. package ipvlan // import "github.com/docker/docker/integration/network/ipvlan"
  4. import (
  5. "context"
  6. "os"
  7. "os/exec"
  8. "strings"
  9. "sync"
  10. "testing"
  11. dclient "github.com/docker/docker/client"
  12. "github.com/docker/docker/integration/internal/container"
  13. net "github.com/docker/docker/integration/internal/network"
  14. n "github.com/docker/docker/integration/network"
  15. "github.com/docker/docker/testutil/daemon"
  16. "gotest.tools/v3/assert"
  17. "gotest.tools/v3/skip"
  18. )
  19. func TestDockerNetworkIpvlanPersistance(t *testing.T) {
  20. // verify the driver automatically provisions the 802.1q link (di-dummy0.70)
  21. skip.If(t, testEnv.IsRemoteDaemon)
  22. skip.If(t, !ipvlanKernelSupport(t), "Kernel doesn't support ipvlan")
  23. d := daemon.New(t)
  24. d.StartWithBusybox(t)
  25. defer d.Stop(t)
  26. // master dummy interface 'di' notation represent 'docker ipvlan'
  27. master := "di-dummy0"
  28. n.CreateMasterDummy(t, master)
  29. defer n.DeleteInterface(t, master)
  30. c := d.NewClientT(t)
  31. // create a network specifying the desired sub-interface name
  32. netName := "di-persist"
  33. net.CreateNoError(context.Background(), t, c, netName,
  34. net.WithIPvlan("di-dummy0.70", ""),
  35. )
  36. assert.Check(t, n.IsNetworkAvailable(c, netName))
  37. // Restart docker daemon to test the config has persisted to disk
  38. d.Restart(t)
  39. assert.Check(t, n.IsNetworkAvailable(c, netName))
  40. }
  41. func TestDockerNetworkIpvlan(t *testing.T) {
  42. skip.If(t, testEnv.IsRemoteDaemon)
  43. skip.If(t, !ipvlanKernelSupport(t), "Kernel doesn't support ipvlan")
  44. for _, tc := range []struct {
  45. name string
  46. test func(dclient.APIClient) func(*testing.T)
  47. }{
  48. {
  49. name: "Subinterface",
  50. test: testIpvlanSubinterface,
  51. }, {
  52. name: "OverlapParent",
  53. test: testIpvlanOverlapParent,
  54. }, {
  55. name: "L2NilParent",
  56. test: testIpvlanL2NilParent,
  57. }, {
  58. name: "L2InternalMode",
  59. test: testIpvlanL2InternalMode,
  60. }, {
  61. name: "L3NilParent",
  62. test: testIpvlanL3NilParent,
  63. }, {
  64. name: "L3InternalMode",
  65. test: testIpvlanL3InternalMode,
  66. }, {
  67. name: "L2MultiSubnet",
  68. test: testIpvlanL2MultiSubnet,
  69. }, {
  70. name: "L3MultiSubnet",
  71. test: testIpvlanL3MultiSubnet,
  72. }, {
  73. name: "Addressing",
  74. test: testIpvlanAddressing,
  75. },
  76. } {
  77. d := daemon.New(t)
  78. d.StartWithBusybox(t)
  79. c := d.NewClientT(t)
  80. t.Run(tc.name, tc.test(c))
  81. d.Stop(t)
  82. // FIXME(vdemeester) clean network
  83. }
  84. }
  85. func testIpvlanSubinterface(client dclient.APIClient) func(*testing.T) {
  86. return func(t *testing.T) {
  87. master := "di-dummy0"
  88. n.CreateMasterDummy(t, master)
  89. defer n.DeleteInterface(t, master)
  90. netName := "di-subinterface"
  91. net.CreateNoError(context.Background(), t, client, netName,
  92. net.WithIPvlan("di-dummy0.60", ""),
  93. )
  94. assert.Check(t, n.IsNetworkAvailable(client, netName))
  95. // delete the network while preserving the parent link
  96. err := client.NetworkRemove(context.Background(), netName)
  97. assert.NilError(t, err)
  98. assert.Check(t, n.IsNetworkNotAvailable(client, netName))
  99. // verify the network delete did not delete the predefined link
  100. n.LinkExists(t, "di-dummy0")
  101. }
  102. }
  103. func testIpvlanOverlapParent(client dclient.APIClient) func(*testing.T) {
  104. return func(t *testing.T) {
  105. // verify the same parent interface cannot be used if already in use by an existing network
  106. master := "di-dummy0"
  107. parent := master + ".30"
  108. n.CreateMasterDummy(t, master)
  109. defer n.DeleteInterface(t, master)
  110. n.CreateVlanInterface(t, master, parent, "30")
  111. netName := "di-subinterface"
  112. net.CreateNoError(context.Background(), t, client, netName,
  113. net.WithIPvlan(parent, ""),
  114. )
  115. assert.Check(t, n.IsNetworkAvailable(client, netName))
  116. _, err := net.Create(context.Background(), client, netName,
  117. net.WithIPvlan(parent, ""),
  118. )
  119. // verify that the overlap returns an error
  120. assert.Check(t, err != nil)
  121. }
  122. }
  123. func testIpvlanL2NilParent(client dclient.APIClient) func(*testing.T) {
  124. return func(t *testing.T) {
  125. // ipvlan l2 mode - dummy parent interface is provisioned dynamically
  126. netName := "di-nil-parent"
  127. net.CreateNoError(context.Background(), t, client, netName,
  128. net.WithIPvlan("", ""),
  129. )
  130. assert.Check(t, n.IsNetworkAvailable(client, netName))
  131. ctx := context.Background()
  132. id1 := container.Run(ctx, t, client, container.WithNetworkMode(netName))
  133. id2 := container.Run(ctx, t, client, container.WithNetworkMode(netName))
  134. _, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
  135. assert.NilError(t, err)
  136. }
  137. }
  138. func testIpvlanL2InternalMode(client dclient.APIClient) func(*testing.T) {
  139. return func(t *testing.T) {
  140. netName := "di-internal"
  141. net.CreateNoError(context.Background(), t, client, netName,
  142. net.WithIPvlan("", ""),
  143. net.WithInternal(),
  144. )
  145. assert.Check(t, n.IsNetworkAvailable(client, netName))
  146. ctx := context.Background()
  147. id1 := container.Run(ctx, t, client, container.WithNetworkMode(netName))
  148. id2 := container.Run(ctx, t, client, container.WithNetworkMode(netName))
  149. result, _ := container.Exec(ctx, client, id1, []string{"ping", "-c", "1", "8.8.8.8"})
  150. assert.Check(t, strings.Contains(result.Combined(), "Network is unreachable"))
  151. _, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
  152. assert.NilError(t, err)
  153. }
  154. }
  155. func testIpvlanL3NilParent(client dclient.APIClient) func(*testing.T) {
  156. return func(t *testing.T) {
  157. netName := "di-nil-parent-l3"
  158. net.CreateNoError(context.Background(), t, client, netName,
  159. net.WithIPvlan("", "l3"),
  160. net.WithIPAM("172.28.230.0/24", ""),
  161. net.WithIPAM("172.28.220.0/24", ""),
  162. )
  163. assert.Check(t, n.IsNetworkAvailable(client, netName))
  164. ctx := context.Background()
  165. id1 := container.Run(ctx, t, client,
  166. container.WithNetworkMode(netName),
  167. container.WithIPv4(netName, "172.28.220.10"),
  168. )
  169. id2 := container.Run(ctx, t, client,
  170. container.WithNetworkMode(netName),
  171. container.WithIPv4(netName, "172.28.230.10"),
  172. )
  173. _, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
  174. assert.NilError(t, err)
  175. }
  176. }
  177. func testIpvlanL3InternalMode(client dclient.APIClient) func(*testing.T) {
  178. return func(t *testing.T) {
  179. netName := "di-internal-l3"
  180. net.CreateNoError(context.Background(), t, client, netName,
  181. net.WithIPvlan("", "l3"),
  182. net.WithInternal(),
  183. net.WithIPAM("172.28.230.0/24", ""),
  184. net.WithIPAM("172.28.220.0/24", ""),
  185. )
  186. assert.Check(t, n.IsNetworkAvailable(client, netName))
  187. ctx := context.Background()
  188. id1 := container.Run(ctx, t, client,
  189. container.WithNetworkMode(netName),
  190. container.WithIPv4(netName, "172.28.220.10"),
  191. )
  192. id2 := container.Run(ctx, t, client,
  193. container.WithNetworkMode(netName),
  194. container.WithIPv4(netName, "172.28.230.10"),
  195. )
  196. result, _ := container.Exec(ctx, client, id1, []string{"ping", "-c", "1", "8.8.8.8"})
  197. assert.Check(t, strings.Contains(result.Combined(), "Network is unreachable"))
  198. _, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
  199. assert.NilError(t, err)
  200. }
  201. }
  202. func testIpvlanL2MultiSubnet(client dclient.APIClient) func(*testing.T) {
  203. return func(t *testing.T) {
  204. netName := "dualstackl2"
  205. net.CreateNoError(context.Background(), t, client, netName,
  206. net.WithIPvlan("", ""),
  207. net.WithIPv6(),
  208. net.WithIPAM("172.28.200.0/24", ""),
  209. net.WithIPAM("172.28.202.0/24", "172.28.202.254"),
  210. net.WithIPAM("2001:db8:abc8::/64", ""),
  211. net.WithIPAM("2001:db8:abc6::/64", "2001:db8:abc6::254"),
  212. )
  213. assert.Check(t, n.IsNetworkAvailable(client, netName))
  214. // start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.100.0/24 and 2001:db8:abc2::/64
  215. ctx := context.Background()
  216. id1 := container.Run(ctx, t, client,
  217. container.WithNetworkMode(netName),
  218. container.WithIPv4(netName, "172.28.200.20"),
  219. container.WithIPv6(netName, "2001:db8:abc8::20"),
  220. )
  221. id2 := container.Run(ctx, t, client,
  222. container.WithNetworkMode(netName),
  223. container.WithIPv4(netName, "172.28.200.21"),
  224. container.WithIPv6(netName, "2001:db8:abc8::21"),
  225. )
  226. c1, err := client.ContainerInspect(ctx, id1)
  227. assert.NilError(t, err)
  228. // verify ipv4 connectivity to the explicit --ipv address second to first
  229. _, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks[netName].IPAddress})
  230. assert.NilError(t, err)
  231. // verify ipv6 connectivity to the explicit --ipv6 address second to first
  232. _, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks[netName].GlobalIPv6Address})
  233. assert.NilError(t, err)
  234. // start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.102.0/24 and 2001:db8:abc4::/64
  235. id3 := container.Run(ctx, t, client,
  236. container.WithNetworkMode(netName),
  237. container.WithIPv4(netName, "172.28.202.20"),
  238. container.WithIPv6(netName, "2001:db8:abc6::20"),
  239. )
  240. id4 := container.Run(ctx, t, client,
  241. container.WithNetworkMode(netName),
  242. container.WithIPv4(netName, "172.28.202.21"),
  243. container.WithIPv6(netName, "2001:db8:abc6::21"),
  244. )
  245. c3, err := client.ContainerInspect(ctx, id3)
  246. assert.NilError(t, err)
  247. // verify ipv4 connectivity to the explicit --ipv address from third to fourth
  248. _, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks[netName].IPAddress})
  249. assert.NilError(t, err)
  250. // verify ipv6 connectivity to the explicit --ipv6 address from third to fourth
  251. _, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks[netName].GlobalIPv6Address})
  252. assert.NilError(t, err)
  253. // Inspect the v4 gateway to ensure the proper default GW was assigned
  254. assert.Equal(t, c1.NetworkSettings.Networks[netName].Gateway, "172.28.200.1")
  255. // Inspect the v6 gateway to ensure the proper default GW was assigned
  256. assert.Equal(t, c1.NetworkSettings.Networks[netName].IPv6Gateway, "2001:db8:abc8::1")
  257. // Inspect the v4 gateway to ensure the proper explicitly assigned default GW was assigned
  258. assert.Equal(t, c3.NetworkSettings.Networks[netName].Gateway, "172.28.202.254")
  259. // Inspect the v6 gateway to ensure the proper explicitly assigned default GW was assigned
  260. assert.Equal(t, c3.NetworkSettings.Networks[netName].IPv6Gateway, "2001:db8:abc6::254")
  261. }
  262. }
  263. func testIpvlanL3MultiSubnet(client dclient.APIClient) func(*testing.T) {
  264. return func(t *testing.T) {
  265. netName := "dualstackl3"
  266. net.CreateNoError(context.Background(), t, client, netName,
  267. net.WithIPvlan("", "l3"),
  268. net.WithIPv6(),
  269. net.WithIPAM("172.28.10.0/24", ""),
  270. net.WithIPAM("172.28.12.0/24", "172.28.12.254"),
  271. net.WithIPAM("2001:db8:abc9::/64", ""),
  272. net.WithIPAM("2001:db8:abc7::/64", "2001:db8:abc7::254"),
  273. )
  274. assert.Check(t, n.IsNetworkAvailable(client, netName))
  275. // start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.100.0/24 and 2001:db8:abc2::/64
  276. ctx := context.Background()
  277. id1 := container.Run(ctx, t, client,
  278. container.WithNetworkMode(netName),
  279. container.WithIPv4(netName, "172.28.10.20"),
  280. container.WithIPv6(netName, "2001:db8:abc9::20"),
  281. )
  282. id2 := container.Run(ctx, t, client,
  283. container.WithNetworkMode(netName),
  284. container.WithIPv4(netName, "172.28.10.21"),
  285. container.WithIPv6(netName, "2001:db8:abc9::21"),
  286. )
  287. c1, err := client.ContainerInspect(ctx, id1)
  288. assert.NilError(t, err)
  289. // verify ipv4 connectivity to the explicit --ipv address second to first
  290. _, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks[netName].IPAddress})
  291. assert.NilError(t, err)
  292. // verify ipv6 connectivity to the explicit --ipv6 address second to first
  293. _, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks[netName].GlobalIPv6Address})
  294. assert.NilError(t, err)
  295. // start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.102.0/24 and 2001:db8:abc4::/64
  296. id3 := container.Run(ctx, t, client,
  297. container.WithNetworkMode(netName),
  298. container.WithIPv4(netName, "172.28.12.20"),
  299. container.WithIPv6(netName, "2001:db8:abc7::20"),
  300. )
  301. id4 := container.Run(ctx, t, client,
  302. container.WithNetworkMode(netName),
  303. container.WithIPv4(netName, "172.28.12.21"),
  304. container.WithIPv6(netName, "2001:db8:abc7::21"),
  305. )
  306. c3, err := client.ContainerInspect(ctx, id3)
  307. assert.NilError(t, err)
  308. // verify ipv4 connectivity to the explicit --ipv address from third to fourth
  309. _, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks[netName].IPAddress})
  310. assert.NilError(t, err)
  311. // verify ipv6 connectivity to the explicit --ipv6 address from third to fourth
  312. _, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks[netName].GlobalIPv6Address})
  313. assert.NilError(t, err)
  314. // Inspect the v4 gateway to ensure no next hop is assigned in L3 mode
  315. assert.Equal(t, c1.NetworkSettings.Networks[netName].Gateway, "")
  316. // Inspect the v6 gateway to ensure the explicitly specified default GW is ignored per L3 mode enabled
  317. assert.Equal(t, c1.NetworkSettings.Networks[netName].IPv6Gateway, "")
  318. // Inspect the v4 gateway to ensure no next hop is assigned in L3 mode
  319. assert.Equal(t, c3.NetworkSettings.Networks[netName].Gateway, "")
  320. // Inspect the v6 gateway to ensure the explicitly specified default GW is ignored per L3 mode enabled
  321. assert.Equal(t, c3.NetworkSettings.Networks[netName].IPv6Gateway, "")
  322. }
  323. }
  324. func testIpvlanAddressing(client dclient.APIClient) func(*testing.T) {
  325. return func(t *testing.T) {
  326. // Verify ipvlan l2 mode sets the proper default gateway routes via netlink
  327. // for either an explicitly set route by the user or inferred via default IPAM
  328. netNameL2 := "dualstackl2"
  329. net.CreateNoError(context.Background(), t, client, netNameL2,
  330. net.WithIPvlan("", "l2"),
  331. net.WithIPv6(),
  332. net.WithIPAM("172.28.140.0/24", "172.28.140.254"),
  333. net.WithIPAM("2001:db8:abcb::/64", ""),
  334. )
  335. assert.Check(t, n.IsNetworkAvailable(client, netNameL2))
  336. ctx := context.Background()
  337. id1 := container.Run(ctx, t, client,
  338. container.WithNetworkMode(netNameL2),
  339. )
  340. // Validate ipvlan l2 mode defaults gateway sets the default IPAM next-hop inferred from the subnet
  341. result, err := container.Exec(ctx, client, id1, []string{"ip", "route"})
  342. assert.NilError(t, err)
  343. assert.Check(t, strings.Contains(result.Combined(), "default via 172.28.140.254 dev eth0"))
  344. // Validate ipvlan l2 mode sets the v6 gateway to the user specified default gateway/next-hop
  345. result, err = container.Exec(ctx, client, id1, []string{"ip", "-6", "route"})
  346. assert.NilError(t, err)
  347. assert.Check(t, strings.Contains(result.Combined(), "default via 2001:db8:abcb::1 dev eth0"))
  348. // Validate ipvlan l3 mode sets the v4 gateway to dev eth0 and disregards any explicit or inferred next-hops
  349. netNameL3 := "dualstackl3"
  350. net.CreateNoError(context.Background(), t, client, netNameL3,
  351. net.WithIPvlan("", "l3"),
  352. net.WithIPv6(),
  353. net.WithIPAM("172.28.160.0/24", "172.28.160.254"),
  354. net.WithIPAM("2001:db8:abcd::/64", "2001:db8:abcd::254"),
  355. )
  356. assert.Check(t, n.IsNetworkAvailable(client, netNameL3))
  357. id2 := container.Run(ctx, t, client,
  358. container.WithNetworkMode(netNameL3),
  359. )
  360. // Validate ipvlan l3 mode sets the v4 gateway to dev eth0 and disregards any explicit or inferred next-hops
  361. result, err = container.Exec(ctx, client, id2, []string{"ip", "route"})
  362. assert.NilError(t, err)
  363. assert.Check(t, strings.Contains(result.Combined(), "default dev eth0"))
  364. // Validate ipvlan l3 mode sets the v6 gateway to dev eth0 and disregards any explicit or inferred next-hops
  365. result, err = container.Exec(ctx, client, id2, []string{"ip", "-6", "route"})
  366. assert.NilError(t, err)
  367. assert.Check(t, strings.Contains(result.Combined(), "default dev eth0"))
  368. }
  369. }
  370. var (
  371. once sync.Once
  372. ipvlanSupported bool
  373. )
  374. // figure out if ipvlan is supported by the kernel
  375. func ipvlanKernelSupport(t *testing.T) bool {
  376. once.Do(func() {
  377. // this may have the side effect of enabling the ipvlan module
  378. exec.Command("modprobe", "ipvlan").Run()
  379. _, err := os.Stat("/sys/module/ipvlan")
  380. if err == nil {
  381. ipvlanSupported = true
  382. } else if !os.IsNotExist(err) {
  383. t.Logf("WARNING: ipvlanKernelSupport: stat failed: %v\n", err)
  384. }
  385. })
  386. return ipvlanSupported
  387. }