ipvlan_test.go 16 KB

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