diff --git a/plugins/main/macvlan/README.md b/plugins/main/macvlan/README.md index df7c1326..7e8239b1 100644 --- a/plugins/main/macvlan/README.md +++ b/plugins/main/macvlan/README.md @@ -26,7 +26,7 @@ Since each macvlan interface has its own MAC address, it makes it easy to use wi * `master` (string, optional): name of the host interface to enslave. Defaults to default route interace. * `mode` (string, optional): one of "bridge", "private", "vepa", "passthru". Defaults to "bridge". * `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel. -* `ipam` (dictionary, required): IPAM configuration to be used for this network. +* `ipam` (dictionary, required): IPAM configuration to be used for this network. For interface only without ip address, create empty dictionary. ## Notes diff --git a/plugins/main/macvlan/macvlan.go b/plugins/main/macvlan/macvlan.go index 3027e055..5aea4baa 100644 --- a/plugins/main/macvlan/macvlan.go +++ b/plugins/main/macvlan/macvlan.go @@ -192,6 +192,8 @@ func cmdAdd(args *skel.CmdArgs) error { return err } + isLayer3 := n.IPAM.Type != "" + netns, err := ns.GetNS(args.Netns) if err != nil { return fmt.Errorf("failed to open netns %q: %v", netns, err) @@ -212,56 +214,80 @@ func cmdAdd(args *skel.CmdArgs) error { } }() - // run the IPAM plugin and get back the config to apply - r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) - if err != nil { - return err - } + // Assume L2 interface only + result := ¤t.Result{CNIVersion: cniVersion, Interfaces: []*current.Interface{macvlanInterface}} - // Invoke ipam del if err to avoid ip leak - defer func() { + if isLayer3 { + // run the IPAM plugin and get back the config to apply + r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) if err != nil { - os.Setenv("CNI_COMMAND", "DEL") - ipam.ExecDel(n.IPAM.Type, args.StdinData) - os.Setenv("CNI_COMMAND", "ADD") - } - }() - - // Convert whatever the IPAM result was into the current Result type - result, err := current.NewResultFromResult(r) - if err != nil { - return err - } - - if len(result.IPs) == 0 { - return errors.New("IPAM plugin returned missing IP config") - } - result.Interfaces = []*current.Interface{macvlanInterface} - - for _, ipc := range result.IPs { - // All addresses apply to the container macvlan interface - ipc.Interface = current.Int(0) - } - - err = netns.Do(func(_ ns.NetNS) error { - if err := ipam.ConfigureIface(args.IfName, result); err != nil { return err } - contVeth, err := net.InterfaceByName(args.IfName) + // Invoke ipam del if err to avoid ip leak + defer func() { + if err != nil { + os.Setenv("CNI_COMMAND", "DEL") + ipam.ExecDel(n.IPAM.Type, args.StdinData) + os.Setenv("CNI_COMMAND", "ADD") + } + }() + + // Convert whatever the IPAM result was into the current Result type + ipamResult, err := current.NewResultFromResult(r) if err != nil { - return fmt.Errorf("failed to look up %q: %v", args.IfName, err) + return err } - for _, ipc := range result.IPs { - if ipc.Version == "4" { - _ = arping.GratuitousArpOverIface(ipc.Address.IP, *contVeth) - } + if len(ipamResult.IPs) == 0 { + return errors.New("IPAM plugin returned missing IP config") + } + + result.IPs = ipamResult.IPs + result.Routes = ipamResult.Routes + + for _, ipc := range result.IPs { + // All addresses apply to the container macvlan interface + ipc.Interface = current.Int(0) + } + + err = netns.Do(func(_ ns.NetNS) error { + if err := ipam.ConfigureIface(args.IfName, result); err != nil { + return err + } + + contVeth, err := net.InterfaceByName(args.IfName) + if err != nil { + return fmt.Errorf("failed to look up %q: %v", args.IfName, err) + } + + for _, ipc := range result.IPs { + if ipc.Version == "4" { + _ = arping.GratuitousArpOverIface(ipc.Address.IP, *contVeth) + } + } + return nil + }) + if err != nil { + return err + } + } else { + // For L2 just change interface status to up + err = netns.Do(func(_ ns.NetNS) error { + macvlanInterfaceLink, err := netlink.LinkByName(args.IfName) + if err != nil { + return fmt.Errorf("failed to find interface name %q: %v", macvlanInterface.Name, err) + } + + if err := netlink.LinkSetUp(macvlanInterfaceLink); err != nil { + return fmt.Errorf("failed to set %q UP: %v", args.IfName, err) + } + + return nil + }) + if err != nil { + return err } - return nil - }) - if err != nil { - return err } result.DNS = n.DNS @@ -275,9 +301,13 @@ func cmdDel(args *skel.CmdArgs) error { return err } - err = ipam.ExecDel(n.IPAM.Type, args.StdinData) - if err != nil { - return err + isLayer3 := n.IPAM.Type != "" + + if isLayer3 { + err = ipam.ExecDel(n.IPAM.Type, args.StdinData) + if err != nil { + return err + } } if args.Netns == "" { @@ -308,16 +338,20 @@ func cmdCheck(args *skel.CmdArgs) error { if err != nil { return err } + isLayer3 := n.IPAM.Type != "" + netns, err := ns.GetNS(args.Netns) if err != nil { return fmt.Errorf("failed to open netns %q: %v", args.Netns, err) } defer netns.Close() - // run the IPAM plugin and get back the config to apply - err = ipam.ExecCheck(n.IPAM.Type, args.StdinData) - if err != nil { - return err + if isLayer3 { + // run the IPAM plugin and get back the config to apply + err = ipam.ExecCheck(n.IPAM.Type, args.StdinData) + if err != nil { + return err + } } // Parse previous result. diff --git a/plugins/main/macvlan/macvlan_test.go b/plugins/main/macvlan/macvlan_test.go index 36a7eb56..b7740517 100644 --- a/plugins/main/macvlan/macvlan_test.go +++ b/plugins/main/macvlan/macvlan_test.go @@ -286,6 +286,89 @@ var _ = Describe("macvlan Operations", func() { }) + It("configures and deconfigures a l2 macvlan link with ADD/DEL", func() { + const IFNAME = "macvl0" + + conf := fmt.Sprintf(`{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "macvlan", + "master": "%s", + "ipam": {} +}`, MASTER_NAME) + + targetNs, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + defer targetNs.Close() + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: IFNAME, + StdinData: []byte(conf), + } + + var result *current.Result + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + result, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + + Expect(len(result.Interfaces)).To(Equal(1)) + Expect(result.Interfaces[0].Name).To(Equal(IFNAME)) + Expect(len(result.IPs)).To(Equal(0)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure macvlan link exists in the target namespace + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Name).To(Equal(IFNAME)) + + hwaddr, err := net.ParseMAC(result.Interfaces[0].Mac) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr)) + + addrs, err := netlink.AddrList(link, syscall.AF_INET) + Expect(err).NotTo(HaveOccurred()) + Expect(len(addrs)).To(Equal(0)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + err := testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Make sure macvlan link has been deleted + err = targetNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(IFNAME) + Expect(err).To(HaveOccurred()) + Expect(link).To(BeNil()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + It("configures and deconfigures a cniVersion 0.4.0 macvlan link with ADD/DEL", func() { const IFNAME = "macvl0"