macvlan_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. package network
  2. import (
  3. "context"
  4. "fmt"
  5. "strings"
  6. "testing"
  7. "time"
  8. "github.com/docker/docker/api/types"
  9. "github.com/docker/docker/api/types/network"
  10. "github.com/docker/docker/client"
  11. "github.com/docker/docker/integration/internal/container"
  12. "github.com/docker/docker/internal/test/daemon"
  13. "github.com/docker/docker/pkg/parsers/kernel"
  14. "github.com/gotestyourself/gotestyourself/assert"
  15. "github.com/gotestyourself/gotestyourself/assert/cmp"
  16. "github.com/gotestyourself/gotestyourself/icmd"
  17. "github.com/gotestyourself/gotestyourself/skip"
  18. )
  19. func TestDockerNetworkMacvlanPersistance(t *testing.T) {
  20. // verify the driver automatically provisions the 802.1q link (dm-dummy0.60)
  21. skip.If(t, testEnv.DaemonInfo.OSType != "linux")
  22. skip.If(t, testEnv.IsRemoteDaemon())
  23. skip.If(t, !macvlanKernelSupport(), "Kernel doesn't support macvlan")
  24. d := daemon.New(t)
  25. d.StartWithBusybox(t)
  26. defer d.Stop(t)
  27. master := "dm-dummy0"
  28. createMasterDummy(t, master)
  29. defer deleteInterface(t, master)
  30. client, err := d.NewClient()
  31. assert.NilError(t, err)
  32. _, err = client.NetworkCreate(context.Background(), "dm-persist", types.NetworkCreate{
  33. Driver: "macvlan",
  34. Options: map[string]string{
  35. "parent": "dm-dummy0.60",
  36. },
  37. })
  38. assert.NilError(t, err)
  39. assert.Check(t, isNetworkAvailable(client, "dm-persist"))
  40. d.Restart(t)
  41. assert.Check(t, isNetworkAvailable(client, "dm-persist"))
  42. }
  43. func TestDockerNetworkMacvlan(t *testing.T) {
  44. skip.If(t, testEnv.DaemonInfo.OSType != "linux")
  45. skip.If(t, testEnv.IsRemoteDaemon())
  46. skip.If(t, !macvlanKernelSupport(), "Kernel doesn't support macvlan")
  47. for _, tc := range []struct {
  48. name string
  49. test func(client.APIClient) func(*testing.T)
  50. }{
  51. {
  52. name: "Subinterface",
  53. test: testMacvlanSubinterface,
  54. }, {
  55. name: "OverlapParent",
  56. test: testMacvlanOverlapParent,
  57. }, {
  58. name: "NilParent",
  59. test: testMacvlanNilParent,
  60. }, {
  61. name: "InternalMode",
  62. test: testMacvlanInternalMode,
  63. }, {
  64. name: "Addressing",
  65. test: testMacvlanAddressing,
  66. },
  67. } {
  68. d := daemon.New(t)
  69. d.StartWithBusybox(t)
  70. client, err := d.NewClient()
  71. assert.NilError(t, err)
  72. t.Run(tc.name, tc.test(client))
  73. d.Stop(t)
  74. // FIXME(vdemeester) clean network
  75. }
  76. }
  77. func testMacvlanOverlapParent(client client.APIClient) func(*testing.T) {
  78. return func(t *testing.T) {
  79. // verify the same parent interface cannot be used if already in use by an existing network
  80. master := "dm-dummy0"
  81. createMasterDummy(t, master)
  82. defer deleteInterface(t, master)
  83. _, err := client.NetworkCreate(context.Background(), "dm-subinterface", types.NetworkCreate{
  84. Driver: "macvlan",
  85. Options: map[string]string{
  86. "parent": "dm-dummy0.40",
  87. },
  88. })
  89. assert.NilError(t, err)
  90. assert.Check(t, isNetworkAvailable(client, "dm-subinterface"))
  91. _, err = client.NetworkCreate(context.Background(), "dm-parent-net-overlap", types.NetworkCreate{
  92. Driver: "macvlan",
  93. Options: map[string]string{
  94. "parent": "dm-dummy0.40",
  95. },
  96. })
  97. assert.Check(t, err != nil)
  98. // delete the network while preserving the parent link
  99. err = client.NetworkRemove(context.Background(), "dm-subinterface")
  100. assert.NilError(t, err)
  101. assert.Check(t, isNetworkNotAvailable(client, "dm-subinterface"))
  102. // verify the network delete did not delete the predefined link
  103. linkExists(t, "dm-dummy0")
  104. }
  105. }
  106. func testMacvlanSubinterface(client client.APIClient) func(*testing.T) {
  107. return func(t *testing.T) {
  108. // verify the same parent interface cannot be used if already in use by an existing network
  109. master := "dm-dummy0"
  110. createMasterDummy(t, master)
  111. defer deleteInterface(t, master)
  112. createVlanInterface(t, master, "dm-dummy0.20", "20")
  113. _, err := client.NetworkCreate(context.Background(), "dm-subinterface", types.NetworkCreate{
  114. Driver: "macvlan",
  115. Options: map[string]string{
  116. "parent": "dm-dummy0.20",
  117. },
  118. })
  119. assert.NilError(t, err)
  120. assert.Check(t, isNetworkAvailable(client, "dm-subinterface"))
  121. // delete the network while preserving the parent link
  122. err = client.NetworkRemove(context.Background(), "dm-subinterface")
  123. assert.NilError(t, err)
  124. assert.Check(t, isNetworkNotAvailable(client, "dm-subinterface"))
  125. // verify the network delete did not delete the predefined link
  126. linkExists(t, "dm-dummy0.20")
  127. }
  128. }
  129. func testMacvlanNilParent(client client.APIClient) func(*testing.T) {
  130. return func(t *testing.T) {
  131. // macvlan bridge mode - dummy parent interface is provisioned dynamically
  132. _, err := client.NetworkCreate(context.Background(), "dm-nil-parent", types.NetworkCreate{
  133. Driver: "macvlan",
  134. })
  135. assert.NilError(t, err)
  136. assert.Check(t, isNetworkAvailable(client, "dm-nil-parent"))
  137. ctx := context.Background()
  138. id1 := container.Run(t, ctx, client, container.WithNetworkMode("dm-nil-parent"))
  139. id2 := container.Run(t, ctx, client, container.WithNetworkMode("dm-nil-parent"))
  140. _, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
  141. assert.Check(t, err == nil)
  142. }
  143. }
  144. func testMacvlanInternalMode(client client.APIClient) func(*testing.T) {
  145. return func(t *testing.T) {
  146. // macvlan bridge mode - dummy parent interface is provisioned dynamically
  147. _, err := client.NetworkCreate(context.Background(), "dm-internal", types.NetworkCreate{
  148. Driver: "macvlan",
  149. Internal: true,
  150. })
  151. assert.NilError(t, err)
  152. assert.Check(t, isNetworkAvailable(client, "dm-internal"))
  153. ctx := context.Background()
  154. id1 := container.Run(t, ctx, client, container.WithNetworkMode("dm-internal"))
  155. id2 := container.Run(t, ctx, client, container.WithNetworkMode("dm-internal"))
  156. timeoutCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
  157. defer cancel()
  158. _, err = container.Exec(timeoutCtx, client, id1, []string{"ping", "-c", "1", "-w", "1", "8.8.8.8"})
  159. // FIXME(vdemeester) check the time of error ?
  160. assert.Check(t, err != nil)
  161. assert.Check(t, timeoutCtx.Err() == context.DeadlineExceeded)
  162. _, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
  163. assert.Check(t, err == nil)
  164. }
  165. }
  166. func testMacvlanMultiSubnet(client client.APIClient) func(*testing.T) {
  167. return func(t *testing.T) {
  168. // t.Skip("Temporarily skipping while investigating sporadic v6 CI issues")
  169. _, err := client.NetworkCreate(context.Background(), "dualstackbridge", types.NetworkCreate{
  170. Driver: "macvlan",
  171. EnableIPv6: true,
  172. IPAM: &network.IPAM{
  173. Config: []network.IPAMConfig{
  174. {
  175. Subnet: "172.28.100.0/24",
  176. AuxAddress: map[string]string{},
  177. },
  178. {
  179. Subnet: "172.28.102.0/24",
  180. Gateway: "172.28.102.254",
  181. AuxAddress: map[string]string{},
  182. },
  183. {
  184. Subnet: "2001:db8:abc2::/64",
  185. AuxAddress: map[string]string{},
  186. },
  187. {
  188. Subnet: "2001:db8:abc4::/64",
  189. Gateway: "2001:db8:abc4::254",
  190. AuxAddress: map[string]string{},
  191. },
  192. },
  193. },
  194. })
  195. assert.NilError(t, err)
  196. assert.Check(t, isNetworkAvailable(client, "dualstackbridge"))
  197. // 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
  198. ctx := context.Background()
  199. id1 := container.Run(t, ctx, client,
  200. container.WithNetworkMode("dualstackbridge"),
  201. container.WithIPv4("dualstackbridge", "172.28.100.20"),
  202. container.WithIPv6("dualstackbridge", "2001:db8:abc2::20"),
  203. )
  204. id2 := container.Run(t, ctx, client,
  205. container.WithNetworkMode("dualstackbridge"),
  206. container.WithIPv4("dualstackbridge", "172.28.100.21"),
  207. container.WithIPv6("dualstackbridge", "2001:db8:abc2::21"),
  208. )
  209. c1, err := client.ContainerInspect(ctx, id1)
  210. assert.NilError(t, err)
  211. // verify ipv4 connectivity to the explicit --ipv address second to first
  212. _, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks["dualstackbridge"].IPAddress})
  213. assert.NilError(t, err)
  214. // verify ipv6 connectivity to the explicit --ipv6 address second to first
  215. _, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks["dualstackbridge"].GlobalIPv6Address})
  216. assert.NilError(t, err)
  217. // 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
  218. id3 := container.Run(t, ctx, client,
  219. container.WithNetworkMode("dualstackbridge"),
  220. container.WithIPv4("dualstackbridge", "172.28.102.20"),
  221. container.WithIPv6("dualstackbridge", "2001:db8:abc4::20"),
  222. )
  223. id4 := container.Run(t, ctx, client,
  224. container.WithNetworkMode("dualstackbridge"),
  225. container.WithIPv4("dualstackbridge", "172.28.102.21"),
  226. container.WithIPv6("dualstackbridge", "2001:db8:abc4::21"),
  227. )
  228. c3, err := client.ContainerInspect(ctx, id3)
  229. assert.NilError(t, err)
  230. // verify ipv4 connectivity to the explicit --ipv address from third to fourth
  231. _, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks["dualstackbridge"].IPAddress})
  232. assert.NilError(t, err)
  233. // verify ipv6 connectivity to the explicit --ipv6 address from third to fourth
  234. _, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks["dualstackbridge"].GlobalIPv6Address})
  235. assert.NilError(t, err)
  236. // Inspect the v4 gateway to ensure the proper default GW was assigned
  237. assert.Equal(t, c1.NetworkSettings.Networks["dualstackbridge"].Gateway, "172.28.100.1")
  238. // Inspect the v6 gateway to ensure the proper default GW was assigned
  239. assert.Equal(t, c1.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway, "2001:db8:abc2::1")
  240. // Inspect the v4 gateway to ensure the proper explicitly assigned default GW was assigned
  241. assert.Equal(t, c3.NetworkSettings.Networks["dualstackbridge"].Gateway, "172.28.102.254")
  242. // Inspect the v6 gateway to ensure the proper explicitly assigned default GW was assigned
  243. assert.Equal(t, c3.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway, "2001:db8.abc4::254")
  244. }
  245. }
  246. func testMacvlanAddressing(client client.APIClient) func(*testing.T) {
  247. return func(t *testing.T) {
  248. // Ensure the default gateways, next-hops and default dev devices are properly set
  249. _, err := client.NetworkCreate(context.Background(), "dualstackbridge", types.NetworkCreate{
  250. Driver: "macvlan",
  251. EnableIPv6: true,
  252. Options: map[string]string{
  253. "macvlan_mode": "bridge",
  254. },
  255. IPAM: &network.IPAM{
  256. Config: []network.IPAMConfig{
  257. {
  258. Subnet: "172.28.130.0/24",
  259. AuxAddress: map[string]string{},
  260. },
  261. {
  262. Subnet: "2001:db8:abca::/64",
  263. Gateway: "2001:db8:abca::254",
  264. AuxAddress: map[string]string{},
  265. },
  266. },
  267. },
  268. })
  269. assert.NilError(t, err)
  270. assert.Check(t, isNetworkAvailable(client, "dualstackbridge"))
  271. ctx := context.Background()
  272. id1 := container.Run(t, ctx, client,
  273. container.WithNetworkMode("dualstackbridge"),
  274. )
  275. // Validate macvlan bridge mode defaults gateway sets the default IPAM next-hop inferred from the subnet
  276. result, err := container.Exec(ctx, client, id1, []string{"ip", "route"})
  277. assert.NilError(t, err)
  278. assert.Check(t, strings.Contains(result.Combined(), "default via 172.28.130.1 dev eth0"))
  279. // Validate macvlan bridge mode sets the v6 gateway to the user specified default gateway/next-hop
  280. result, err = container.Exec(ctx, client, id1, []string{"ip", "-6", "route"})
  281. assert.NilError(t, err)
  282. assert.Check(t, strings.Contains(result.Combined(), "default via 2001:db8:abca::254 dev eth0"))
  283. }
  284. }
  285. func isNetworkAvailable(c client.NetworkAPIClient, name string) cmp.Comparison {
  286. return func() cmp.Result {
  287. networks, err := c.NetworkList(context.Background(), types.NetworkListOptions{})
  288. if err != nil {
  289. return cmp.ResultFromError(err)
  290. }
  291. for _, network := range networks {
  292. if network.Name == name {
  293. return cmp.ResultSuccess
  294. }
  295. }
  296. return cmp.ResultFailure(fmt.Sprintf("could not find network %s", name))
  297. }
  298. }
  299. func isNetworkNotAvailable(c client.NetworkAPIClient, name string) cmp.Comparison {
  300. return func() cmp.Result {
  301. networks, err := c.NetworkList(context.Background(), types.NetworkListOptions{})
  302. if err != nil {
  303. return cmp.ResultFromError(err)
  304. }
  305. for _, network := range networks {
  306. if network.Name == name {
  307. return cmp.ResultFailure(fmt.Sprintf("network %s is still present", name))
  308. }
  309. }
  310. return cmp.ResultSuccess
  311. }
  312. }
  313. func createMasterDummy(t *testing.T, master string) {
  314. // ip link add <dummy_name> type dummy
  315. icmd.RunCommand("ip", "link", "add", master, "type", "dummy").Assert(t, icmd.Success)
  316. icmd.RunCommand("ip", "link", "set", master, "up").Assert(t, icmd.Success)
  317. }
  318. func createVlanInterface(t *testing.T, master, slave, id string) {
  319. // ip link add link <master> name <master>.<VID> type vlan id <VID>
  320. icmd.RunCommand("ip", "link", "add", "link", master, "name", slave, "type", "vlan", "id", id).Assert(t, icmd.Success)
  321. // ip link set <sub_interface_name> up
  322. icmd.RunCommand("ip", "link", "set", slave, "up").Assert(t, icmd.Success)
  323. }
  324. func deleteInterface(t *testing.T, ifName string) {
  325. icmd.RunCommand("ip", "link", "delete", ifName).Assert(t, icmd.Success)
  326. icmd.RunCommand("iptables", "-t", "nat", "--flush").Assert(t, icmd.Success)
  327. icmd.RunCommand("iptables", "--flush").Assert(t, icmd.Success)
  328. }
  329. func linkExists(t *testing.T, master string) {
  330. // verify the specified link exists, ip link show <link_name>
  331. icmd.RunCommand("ip", "link", "show", master).Assert(t, icmd.Success)
  332. }
  333. // ensure Kernel version is >= v3.9 for macvlan support
  334. func macvlanKernelSupport() bool {
  335. return checkKernelMajorVersionGreaterOrEqualThen(3, 9)
  336. }
  337. func checkKernelMajorVersionGreaterOrEqualThen(kernelVersion int, majorVersion int) bool {
  338. kv, err := kernel.GetKernelVersion()
  339. if err != nil {
  340. return false
  341. }
  342. if kv.Kernel < kernelVersion || (kv.Kernel == kernelVersion && kv.Major < majorVersion) {
  343. return false
  344. }
  345. return true
  346. }