diff --git a/plugins/main/macvlan/macvlan.go b/plugins/main/macvlan/macvlan.go index d290e709c..8388271ba 100644 --- a/plugins/main/macvlan/macvlan.go +++ b/plugins/main/macvlan/macvlan.go @@ -45,6 +45,13 @@ type NetConf struct { Master string `json:"master"` Mode string `json:"mode"` MTU int `json:"mtu"` + Mac string `json:"mac,omitempty"` +} + +// MacEnvArgs represents CNI_ARG +type MacEnvArgs struct { + types.CommonArgs + MAC types.UnmarshallableString `json:"mac,omitempty"` } func init() { @@ -73,7 +80,7 @@ func getDefaultRouteInterfaceName() (string, error) { return "", fmt.Errorf("no default route interface found") } -func loadConf(bytes []byte) (*NetConf, string, error) { +func loadConf(bytes []byte, envArgs string) (*NetConf, string, error) { n := &NetConf{} if err := json.Unmarshal(bytes, n); err != nil { return nil, "", fmt.Errorf("failed to load netconf: %v", err) @@ -95,6 +102,18 @@ func loadConf(bytes []byte) (*NetConf, string, error) { return nil, "", fmt.Errorf("invalid MTU %d, must be [0, master MTU(%d)]", n.MTU, masterMTU) } + if envArgs != "" { + e := MacEnvArgs{} + err := types.LoadArgs(envArgs, &e) + if err != nil { + return nil, "", err + } + + if e.MAC != "" { + n.Mac = string(e.MAC) + } + } + return n, n.CNIVersion, nil } @@ -156,14 +175,24 @@ func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Inter return nil, err } + linkAttrs := netlink.LinkAttrs{ + MTU: conf.MTU, + Name: tmpName, + ParentIndex: m.Attrs().Index, + Namespace: netlink.NsFd(int(netns.Fd())), + } + + if conf.Mac != "" { + addr, err := net.ParseMAC(conf.Mac) + if err != nil { + return nil, fmt.Errorf("invalid args %v for MAC addr: %v", conf.Mac, err) + } + linkAttrs.HardwareAddr = addr + } + mv := &netlink.Macvlan{ - LinkAttrs: netlink.LinkAttrs{ - MTU: conf.MTU, - Name: tmpName, - ParentIndex: m.Attrs().Index, - Namespace: netlink.NsFd(int(netns.Fd())), - }, - Mode: mode, + LinkAttrs: linkAttrs, + Mode: mode, } if err := netlink.LinkAdd(mv); err != nil { @@ -204,7 +233,7 @@ func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Inter } func cmdAdd(args *skel.CmdArgs) error { - n, cniVersion, err := loadConf(args.StdinData) + n, cniVersion, err := loadConf(args.StdinData, args.Args) if err != nil { return err } @@ -311,7 +340,7 @@ func cmdAdd(args *skel.CmdArgs) error { } func cmdDel(args *skel.CmdArgs) error { - n, _, err := loadConf(args.StdinData) + n, _, err := loadConf(args.StdinData, args.Args) if err != nil { return err } @@ -349,7 +378,7 @@ func main() { func cmdCheck(args *skel.CmdArgs) error { - n, _, err := loadConf(args.StdinData) + n, _, err := loadConf(args.StdinData, args.Args) if err != nil { return err } diff --git a/plugins/main/macvlan/macvlan_test.go b/plugins/main/macvlan/macvlan_test.go index 9ee2d5825..de09e6536 100644 --- a/plugins/main/macvlan/macvlan_test.go +++ b/plugins/main/macvlan/macvlan_test.go @@ -594,4 +594,88 @@ var _ = Describe("macvlan Operations", func() { Expect(err).NotTo(HaveOccurred()) }) + It("configures and deconfigures a l2 macvlan link with mac address 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), + Args: "IgnoreUnknown=true;MAC=c2:11:22:33:44:55", + } + + 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("c2:11:22:33:44:55") + 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()) + }) })