ipvlan_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  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"
  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. ctx := testutil.StartSpan(baseContext, t)
  24. d := daemon.New(t)
  25. d.StartWithBusybox(ctx, t)
  26. defer d.Stop(t)
  27. // master dummy interface 'di' notation represent 'docker ipvlan'
  28. master := "di-dummy0"
  29. n.CreateMasterDummy(ctx, t, master)
  30. defer n.DeleteInterface(ctx, t, master)
  31. c := d.NewClientT(t)
  32. // create a network specifying the desired sub-interface name
  33. netName := "di-persist"
  34. net.CreateNoError(ctx, t, c, netName,
  35. net.WithIPvlan("di-dummy0.70", ""),
  36. )
  37. assert.Check(t, n.IsNetworkAvailable(ctx, c, netName))
  38. // Restart docker daemon to test the config has persisted to disk
  39. d.Restart(t)
  40. assert.Check(t, n.IsNetworkAvailable(ctx, c, netName))
  41. }
  42. func TestDockerNetworkIpvlan(t *testing.T) {
  43. skip.If(t, testEnv.IsRemoteDaemon)
  44. skip.If(t, !ipvlanKernelSupport(t), "Kernel doesn't support ipvlan")
  45. ctx := testutil.StartSpan(baseContext, t)
  46. for _, tc := range []struct {
  47. name string
  48. test func(context.Context, dclient.APIClient) func(*testing.T)
  49. }{
  50. {
  51. name: "Subinterface",
  52. test: testIpvlanSubinterface,
  53. }, {
  54. name: "OverlapParent",
  55. test: testIpvlanOverlapParent,
  56. }, {
  57. name: "L2NilParent",
  58. test: testIpvlanL2NilParent,
  59. }, {
  60. name: "L2InternalMode",
  61. test: testIpvlanL2InternalMode,
  62. }, {
  63. name: "L3NilParent",
  64. test: testIpvlanL3NilParent,
  65. }, {
  66. name: "L3InternalMode",
  67. test: testIpvlanL3InternalMode,
  68. }, {
  69. name: "L2MultiSubnet",
  70. test: testIpvlanL2MultiSubnet,
  71. }, {
  72. name: "L3MultiSubnet",
  73. test: testIpvlanL3MultiSubnet,
  74. }, {
  75. name: "Addressing",
  76. test: testIpvlanAddressing,
  77. },
  78. } {
  79. t.Run(tc.name, func(t *testing.T) {
  80. testutil.StartSpan(ctx, t)
  81. d := daemon.New(t)
  82. t.Cleanup(func() { d.Stop(t) })
  83. d.StartWithBusybox(ctx, t)
  84. c := d.NewClientT(t)
  85. tc.test(ctx, c)
  86. })
  87. // FIXME(vdemeester) clean network
  88. }
  89. }
  90. func testIpvlanSubinterface(ctx context.Context, client dclient.APIClient) func(*testing.T) {
  91. return func(t *testing.T) {
  92. master := "di-dummy0"
  93. n.CreateMasterDummy(ctx, t, master)
  94. defer n.DeleteInterface(ctx, t, master)
  95. netName := "di-subinterface"
  96. net.CreateNoError(ctx, t, client, netName,
  97. net.WithIPvlan("di-dummy0.60", ""),
  98. )
  99. assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
  100. // delete the network while preserving the parent link
  101. err := client.NetworkRemove(ctx, netName)
  102. assert.NilError(t, err)
  103. assert.Check(t, n.IsNetworkNotAvailable(ctx, client, netName))
  104. // verify the network delete did not delete the predefined link
  105. n.LinkExists(ctx, t, "di-dummy0")
  106. }
  107. }
  108. func testIpvlanOverlapParent(ctx context.Context, client dclient.APIClient) func(*testing.T) {
  109. return func(t *testing.T) {
  110. // verify the same parent interface cannot be used if already in use by an existing network
  111. master := "di-dummy0"
  112. parent := master + ".30"
  113. n.CreateMasterDummy(ctx, t, master)
  114. defer n.DeleteInterface(ctx, t, master)
  115. n.CreateVlanInterface(ctx, t, master, parent, "30")
  116. netName := "di-subinterface"
  117. net.CreateNoError(ctx, t, client, netName,
  118. net.WithIPvlan(parent, ""),
  119. )
  120. assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
  121. _, err := net.Create(ctx, client, netName,
  122. net.WithIPvlan(parent, ""),
  123. )
  124. // verify that the overlap returns an error
  125. assert.Check(t, err != nil)
  126. }
  127. }
  128. func testIpvlanL2NilParent(ctx context.Context, client dclient.APIClient) func(*testing.T) {
  129. return func(t *testing.T) {
  130. // ipvlan l2 mode - dummy parent interface is provisioned dynamically
  131. netName := "di-nil-parent"
  132. net.CreateNoError(ctx, t, client, netName,
  133. net.WithIPvlan("", ""),
  134. )
  135. assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
  136. id1 := container.Run(ctx, t, client, container.WithNetworkMode(netName))
  137. id2 := container.Run(ctx, t, client, container.WithNetworkMode(netName))
  138. _, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
  139. assert.NilError(t, err)
  140. }
  141. }
  142. func testIpvlanL2InternalMode(ctx context.Context, client dclient.APIClient) func(*testing.T) {
  143. return func(t *testing.T) {
  144. netName := "di-internal"
  145. net.CreateNoError(ctx, t, client, netName,
  146. net.WithIPvlan("", ""),
  147. net.WithInternal(),
  148. )
  149. assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
  150. id1 := container.Run(ctx, t, client, container.WithNetworkMode(netName))
  151. id2 := container.Run(ctx, t, client, container.WithNetworkMode(netName))
  152. result, _ := container.Exec(ctx, client, id1, []string{"ping", "-c", "1", "8.8.8.8"})
  153. assert.Check(t, strings.Contains(result.Combined(), "Network is unreachable"))
  154. _, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
  155. assert.NilError(t, err)
  156. }
  157. }
  158. func testIpvlanL3NilParent(ctx context.Context, client dclient.APIClient) func(*testing.T) {
  159. return func(t *testing.T) {
  160. netName := "di-nil-parent-l3"
  161. net.CreateNoError(ctx, t, client, netName,
  162. net.WithIPvlan("", "l3"),
  163. net.WithIPAM("172.28.230.0/24", ""),
  164. net.WithIPAM("172.28.220.0/24", ""),
  165. )
  166. assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
  167. id1 := container.Run(ctx, t, client,
  168. container.WithNetworkMode(netName),
  169. container.WithIPv4(netName, "172.28.220.10"),
  170. )
  171. id2 := container.Run(ctx, t, client,
  172. container.WithNetworkMode(netName),
  173. container.WithIPv4(netName, "172.28.230.10"),
  174. )
  175. _, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
  176. assert.NilError(t, err)
  177. }
  178. }
  179. func testIpvlanL3InternalMode(ctx context.Context, client dclient.APIClient) func(*testing.T) {
  180. return func(t *testing.T) {
  181. netName := "di-internal-l3"
  182. net.CreateNoError(ctx, t, client, netName,
  183. net.WithIPvlan("", "l3"),
  184. net.WithInternal(),
  185. net.WithIPAM("172.28.230.0/24", ""),
  186. net.WithIPAM("172.28.220.0/24", ""),
  187. )
  188. assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
  189. id1 := container.Run(ctx, t, client,
  190. container.WithNetworkMode(netName),
  191. container.WithIPv4(netName, "172.28.220.10"),
  192. )
  193. id2 := container.Run(ctx, t, client,
  194. container.WithNetworkMode(netName),
  195. container.WithIPv4(netName, "172.28.230.10"),
  196. )
  197. result, _ := container.Exec(ctx, client, id1, []string{"ping", "-c", "1", "8.8.8.8"})
  198. assert.Check(t, strings.Contains(result.Combined(), "Network is unreachable"))
  199. _, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
  200. assert.NilError(t, err)
  201. }
  202. }
  203. func testIpvlanL2MultiSubnet(ctx context.Context, client dclient.APIClient) func(*testing.T) {
  204. return func(t *testing.T) {
  205. netName := "dualstackl2"
  206. net.CreateNoError(ctx, t, client, netName,
  207. net.WithIPvlan("", ""),
  208. net.WithIPv6(),
  209. net.WithIPAM("172.28.200.0/24", ""),
  210. net.WithIPAM("172.28.202.0/24", "172.28.202.254"),
  211. net.WithIPAM("2001:db8:abc8::/64", ""),
  212. net.WithIPAM("2001:db8:abc6::/64", "2001:db8:abc6::254"),
  213. )
  214. assert.Check(t, n.IsNetworkAvailable(ctx, client, netName))
  215. // 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
  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(ctx context.Context, client dclient.APIClient) func(*testing.T) {
  264. return func(t *testing.T) {
  265. netName := "dualstackl3"
  266. net.CreateNoError(ctx, 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(ctx, 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. 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(ctx context.Context, 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(ctx, 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(ctx, client, netNameL2))
  335. id1 := container.Run(ctx, t, client,
  336. container.WithNetworkMode(netNameL2),
  337. )
  338. // Validate ipvlan l2 mode defaults gateway sets the default IPAM next-hop inferred from the subnet
  339. result, err := container.Exec(ctx, client, id1, []string{"ip", "route"})
  340. assert.NilError(t, err)
  341. assert.Check(t, strings.Contains(result.Combined(), "default via 172.28.140.254 dev eth0"))
  342. // Validate ipvlan l2 mode sets the v6 gateway to the user specified default gateway/next-hop
  343. result, err = container.Exec(ctx, client, id1, []string{"ip", "-6", "route"})
  344. assert.NilError(t, err)
  345. assert.Check(t, strings.Contains(result.Combined(), "default via 2001:db8:abcb::1 dev eth0"))
  346. // Validate ipvlan l3 mode sets the v4 gateway to dev eth0 and disregards any explicit or inferred next-hops
  347. netNameL3 := "dualstackl3"
  348. net.CreateNoError(ctx, t, client, netNameL3,
  349. net.WithIPvlan("", "l3"),
  350. net.WithIPv6(),
  351. net.WithIPAM("172.28.160.0/24", "172.28.160.254"),
  352. net.WithIPAM("2001:db8:abcd::/64", "2001:db8:abcd::254"),
  353. )
  354. assert.Check(t, n.IsNetworkAvailable(ctx, client, netNameL3))
  355. id2 := container.Run(ctx, t, client,
  356. container.WithNetworkMode(netNameL3),
  357. )
  358. // Validate ipvlan l3 mode sets the v4 gateway to dev eth0 and disregards any explicit or inferred next-hops
  359. result, err = container.Exec(ctx, client, id2, []string{"ip", "route"})
  360. assert.NilError(t, err)
  361. assert.Check(t, strings.Contains(result.Combined(), "default dev eth0"))
  362. // Validate ipvlan l3 mode sets the v6 gateway to dev eth0 and disregards any explicit or inferred next-hops
  363. result, err = container.Exec(ctx, client, id2, []string{"ip", "-6", "route"})
  364. assert.NilError(t, err)
  365. assert.Check(t, strings.Contains(result.Combined(), "default dev eth0"))
  366. }
  367. }
  368. var (
  369. once sync.Once
  370. ipvlanSupported bool
  371. )
  372. // figure out if ipvlan is supported by the kernel
  373. func ipvlanKernelSupport(t *testing.T) bool {
  374. once.Do(func() {
  375. // this may have the side effect of enabling the ipvlan module
  376. exec.Command("modprobe", "ipvlan").Run()
  377. _, err := os.Stat("/sys/module/ipvlan")
  378. if err == nil {
  379. ipvlanSupported = true
  380. } else if !os.IsNotExist(err) {
  381. t.Logf("WARNING: ipvlanKernelSupport: stat failed: %v\n", err)
  382. }
  383. })
  384. return ipvlanSupported
  385. }