ipvlan_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. // +build !windows
  2. package ipvlan
  3. import (
  4. "context"
  5. "os"
  6. "os/exec"
  7. "strings"
  8. "sync"
  9. "testing"
  10. "time"
  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/internal/test/daemon"
  16. "gotest.tools/assert"
  17. "gotest.tools/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, daemon.WithExperimental)
  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, daemon.WithExperimental)
  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. timeoutCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
  150. defer cancel()
  151. _, err := container.Exec(timeoutCtx, client, id1, []string{"ping", "-c", "1", "-w", "1", "8.8.8.8"})
  152. // FIXME(vdemeester) check the time of error ?
  153. assert.Check(t, err != nil)
  154. assert.Check(t, timeoutCtx.Err() == context.DeadlineExceeded)
  155. _, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
  156. assert.NilError(t, err)
  157. }
  158. }
  159. func testIpvlanL3NilParent(client dclient.APIClient) func(*testing.T) {
  160. return func(t *testing.T) {
  161. netName := "di-nil-parent-l3"
  162. net.CreateNoError(context.Background(), t, client, netName,
  163. net.WithIPvlan("", "l3"),
  164. net.WithIPAM("172.28.230.0/24", ""),
  165. net.WithIPAM("172.28.220.0/24", ""),
  166. )
  167. assert.Check(t, n.IsNetworkAvailable(client, netName))
  168. ctx := context.Background()
  169. id1 := container.Run(ctx, t, client,
  170. container.WithNetworkMode(netName),
  171. container.WithIPv4(netName, "172.28.220.10"),
  172. )
  173. id2 := container.Run(ctx, t, client,
  174. container.WithNetworkMode(netName),
  175. container.WithIPv4(netName, "172.28.230.10"),
  176. )
  177. _, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
  178. assert.NilError(t, err)
  179. }
  180. }
  181. func testIpvlanL3InternalMode(client dclient.APIClient) func(*testing.T) {
  182. return func(t *testing.T) {
  183. netName := "di-internal-l3"
  184. net.CreateNoError(context.Background(), t, client, netName,
  185. net.WithIPvlan("", "l3"),
  186. net.WithInternal(),
  187. net.WithIPAM("172.28.230.0/24", ""),
  188. net.WithIPAM("172.28.220.0/24", ""),
  189. )
  190. assert.Check(t, n.IsNetworkAvailable(client, netName))
  191. ctx := context.Background()
  192. id1 := container.Run(ctx, t, client,
  193. container.WithNetworkMode(netName),
  194. container.WithIPv4(netName, "172.28.220.10"),
  195. )
  196. id2 := container.Run(ctx, t, client,
  197. container.WithNetworkMode(netName),
  198. container.WithIPv4(netName, "172.28.230.10"),
  199. )
  200. timeoutCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
  201. defer cancel()
  202. _, err := container.Exec(timeoutCtx, client, id1, []string{"ping", "-c", "1", "-w", "1", "8.8.8.8"})
  203. // FIXME(vdemeester) check the time of error ?
  204. assert.Check(t, err != nil)
  205. assert.Check(t, timeoutCtx.Err() == context.DeadlineExceeded)
  206. _, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
  207. assert.NilError(t, err)
  208. }
  209. }
  210. func testIpvlanL2MultiSubnet(client dclient.APIClient) func(*testing.T) {
  211. return func(t *testing.T) {
  212. netName := "dualstackl2"
  213. net.CreateNoError(context.Background(), t, client, netName,
  214. net.WithIPvlan("", ""),
  215. net.WithIPv6(),
  216. net.WithIPAM("172.28.200.0/24", ""),
  217. net.WithIPAM("172.28.202.0/24", "172.28.202.254"),
  218. net.WithIPAM("2001:db8:abc8::/64", ""),
  219. net.WithIPAM("2001:db8:abc6::/64", "2001:db8:abc6::254"),
  220. )
  221. assert.Check(t, n.IsNetworkAvailable(client, netName))
  222. // 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
  223. ctx := context.Background()
  224. id1 := container.Run(ctx, t, client,
  225. container.WithNetworkMode(netName),
  226. container.WithIPv4(netName, "172.28.200.20"),
  227. container.WithIPv6(netName, "2001:db8:abc8::20"),
  228. )
  229. id2 := container.Run(ctx, t, client,
  230. container.WithNetworkMode(netName),
  231. container.WithIPv4(netName, "172.28.200.21"),
  232. container.WithIPv6(netName, "2001:db8:abc8::21"),
  233. )
  234. c1, err := client.ContainerInspect(ctx, id1)
  235. assert.NilError(t, err)
  236. // verify ipv4 connectivity to the explicit --ipv address second to first
  237. _, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks[netName].IPAddress})
  238. assert.NilError(t, err)
  239. // verify ipv6 connectivity to the explicit --ipv6 address second to first
  240. _, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks[netName].GlobalIPv6Address})
  241. assert.NilError(t, err)
  242. // 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
  243. id3 := container.Run(ctx, t, client,
  244. container.WithNetworkMode(netName),
  245. container.WithIPv4(netName, "172.28.202.20"),
  246. container.WithIPv6(netName, "2001:db8:abc6::20"),
  247. )
  248. id4 := container.Run(ctx, t, client,
  249. container.WithNetworkMode(netName),
  250. container.WithIPv4(netName, "172.28.202.21"),
  251. container.WithIPv6(netName, "2001:db8:abc6::21"),
  252. )
  253. c3, err := client.ContainerInspect(ctx, id3)
  254. assert.NilError(t, err)
  255. // verify ipv4 connectivity to the explicit --ipv address from third to fourth
  256. _, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks[netName].IPAddress})
  257. assert.NilError(t, err)
  258. // verify ipv6 connectivity to the explicit --ipv6 address from third to fourth
  259. _, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks[netName].GlobalIPv6Address})
  260. assert.NilError(t, err)
  261. // Inspect the v4 gateway to ensure the proper default GW was assigned
  262. assert.Equal(t, c1.NetworkSettings.Networks[netName].Gateway, "172.28.200.1")
  263. // Inspect the v6 gateway to ensure the proper default GW was assigned
  264. assert.Equal(t, c1.NetworkSettings.Networks[netName].IPv6Gateway, "2001:db8:abc8::1")
  265. // Inspect the v4 gateway to ensure the proper explicitly assigned default GW was assigned
  266. assert.Equal(t, c3.NetworkSettings.Networks[netName].Gateway, "172.28.202.254")
  267. // Inspect the v6 gateway to ensure the proper explicitly assigned default GW was assigned
  268. assert.Equal(t, c3.NetworkSettings.Networks[netName].IPv6Gateway, "2001:db8:abc6::254")
  269. }
  270. }
  271. func testIpvlanL3MultiSubnet(client dclient.APIClient) func(*testing.T) {
  272. return func(t *testing.T) {
  273. netName := "dualstackl3"
  274. net.CreateNoError(context.Background(), t, client, netName,
  275. net.WithIPvlan("", "l3"),
  276. net.WithIPv6(),
  277. net.WithIPAM("172.28.10.0/24", ""),
  278. net.WithIPAM("172.28.12.0/24", "172.28.12.254"),
  279. net.WithIPAM("2001:db8:abc9::/64", ""),
  280. net.WithIPAM("2001:db8:abc7::/64", "2001:db8:abc7::254"),
  281. )
  282. assert.Check(t, n.IsNetworkAvailable(client, netName))
  283. // 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
  284. ctx := context.Background()
  285. id1 := container.Run(ctx, t, client,
  286. container.WithNetworkMode(netName),
  287. container.WithIPv4(netName, "172.28.10.20"),
  288. container.WithIPv6(netName, "2001:db8:abc9::20"),
  289. )
  290. id2 := container.Run(ctx, t, client,
  291. container.WithNetworkMode(netName),
  292. container.WithIPv4(netName, "172.28.10.21"),
  293. container.WithIPv6(netName, "2001:db8:abc9::21"),
  294. )
  295. c1, err := client.ContainerInspect(ctx, id1)
  296. assert.NilError(t, err)
  297. // verify ipv4 connectivity to the explicit --ipv address second to first
  298. _, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks[netName].IPAddress})
  299. assert.NilError(t, err)
  300. // verify ipv6 connectivity to the explicit --ipv6 address second to first
  301. _, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks[netName].GlobalIPv6Address})
  302. assert.NilError(t, err)
  303. // 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
  304. id3 := container.Run(ctx, t, client,
  305. container.WithNetworkMode(netName),
  306. container.WithIPv4(netName, "172.28.12.20"),
  307. container.WithIPv6(netName, "2001:db8:abc7::20"),
  308. )
  309. id4 := container.Run(ctx, t, client,
  310. container.WithNetworkMode(netName),
  311. container.WithIPv4(netName, "172.28.12.21"),
  312. container.WithIPv6(netName, "2001:db8:abc7::21"),
  313. )
  314. c3, err := client.ContainerInspect(ctx, id3)
  315. assert.NilError(t, err)
  316. // verify ipv4 connectivity to the explicit --ipv address from third to fourth
  317. _, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks[netName].IPAddress})
  318. assert.NilError(t, err)
  319. // verify ipv6 connectivity to the explicit --ipv6 address from third to fourth
  320. _, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks[netName].GlobalIPv6Address})
  321. assert.NilError(t, err)
  322. // Inspect the v4 gateway to ensure no next hop is assigned in L3 mode
  323. assert.Equal(t, c1.NetworkSettings.Networks[netName].Gateway, "")
  324. // Inspect the v6 gateway to ensure the explicitly specified default GW is ignored per L3 mode enabled
  325. assert.Equal(t, c1.NetworkSettings.Networks[netName].IPv6Gateway, "")
  326. // Inspect the v4 gateway to ensure no next hop is assigned in L3 mode
  327. assert.Equal(t, c3.NetworkSettings.Networks[netName].Gateway, "")
  328. // Inspect the v6 gateway to ensure the explicitly specified default GW is ignored per L3 mode enabled
  329. assert.Equal(t, c3.NetworkSettings.Networks[netName].IPv6Gateway, "")
  330. }
  331. }
  332. func testIpvlanAddressing(client dclient.APIClient) func(*testing.T) {
  333. return func(t *testing.T) {
  334. // Verify ipvlan l2 mode sets the proper default gateway routes via netlink
  335. // for either an explicitly set route by the user or inferred via default IPAM
  336. netNameL2 := "dualstackl2"
  337. net.CreateNoError(context.Background(), t, client, netNameL2,
  338. net.WithIPvlan("", "l2"),
  339. net.WithIPv6(),
  340. net.WithIPAM("172.28.140.0/24", "172.28.140.254"),
  341. net.WithIPAM("2001:db8:abcb::/64", ""),
  342. )
  343. assert.Check(t, n.IsNetworkAvailable(client, netNameL2))
  344. ctx := context.Background()
  345. id1 := container.Run(ctx, t, client,
  346. container.WithNetworkMode(netNameL2),
  347. )
  348. // Validate ipvlan l2 mode defaults gateway sets the default IPAM next-hop inferred from the subnet
  349. result, err := container.Exec(ctx, client, id1, []string{"ip", "route"})
  350. assert.NilError(t, err)
  351. assert.Check(t, strings.Contains(result.Combined(), "default via 172.28.140.254 dev eth0"))
  352. // Validate ipvlan l2 mode sets the v6 gateway to the user specified default gateway/next-hop
  353. result, err = container.Exec(ctx, client, id1, []string{"ip", "-6", "route"})
  354. assert.NilError(t, err)
  355. assert.Check(t, strings.Contains(result.Combined(), "default via 2001:db8:abcb::1 dev eth0"))
  356. // Validate ipvlan l3 mode sets the v4 gateway to dev eth0 and disregards any explicit or inferred next-hops
  357. netNameL3 := "dualstackl3"
  358. net.CreateNoError(context.Background(), t, client, netNameL3,
  359. net.WithIPvlan("", "l3"),
  360. net.WithIPv6(),
  361. net.WithIPAM("172.28.160.0/24", "172.28.160.254"),
  362. net.WithIPAM("2001:db8:abcd::/64", "2001:db8:abcd::254"),
  363. )
  364. assert.Check(t, n.IsNetworkAvailable(client, netNameL3))
  365. id2 := container.Run(ctx, t, client,
  366. container.WithNetworkMode(netNameL3),
  367. )
  368. // Validate ipvlan l3 mode sets the v4 gateway to dev eth0 and disregards any explicit or inferred next-hops
  369. result, err = container.Exec(ctx, client, id2, []string{"ip", "route"})
  370. assert.NilError(t, err)
  371. assert.Check(t, strings.Contains(result.Combined(), "default dev eth0"))
  372. // Validate ipvlan l3 mode sets the v6 gateway to dev eth0 and disregards any explicit or inferred next-hops
  373. result, err = container.Exec(ctx, client, id2, []string{"ip", "-6", "route"})
  374. assert.NilError(t, err)
  375. assert.Check(t, strings.Contains(result.Combined(), "default dev eth0"))
  376. }
  377. }
  378. var (
  379. once sync.Once
  380. ipvlanSupported bool
  381. )
  382. // figure out if ipvlan is supported by the kernel
  383. func ipvlanKernelSupport(t *testing.T) bool {
  384. once.Do(func() {
  385. // this may have the side effect of enabling the ipvlan module
  386. exec.Command("modprobe", "ipvlan").Run()
  387. _, err := os.Stat("/sys/module/ipvlan")
  388. if err == nil {
  389. ipvlanSupported = true
  390. } else if !os.IsNotExist(err) {
  391. t.Logf("WARNING: ipvlanKernelSupport: stat failed: %v\n", err)
  392. }
  393. })
  394. return ipvlanSupported
  395. }