From b9560fd5c162df31b579366807b2186e79ceef99 Mon Sep 17 00:00:00 2001 From: Clint Armstrong Date: Wed, 29 Apr 2020 23:05:37 -0400 Subject: [PATCH] macvlan: set mac address from CNI_ARGS This change sets the mac address if specified during the creation of the macvlan interface. This is superior to setting it via the tuning plugin because this ensures the mac address is set before an IP is set, allowing a container to get a reserved IP address from DHCP. Related #450 Signed-off-by: Clint Armstrong --- plugins/main/macvlan/macvlan.go | 59 +++++++-- plugins/main/macvlan/macvlan_test.go | 172 +++++++++++++++++++++++++++ 2 files changed, 220 insertions(+), 11 deletions(-) diff --git a/plugins/main/macvlan/macvlan.go b/plugins/main/macvlan/macvlan.go index d290e709c..a7bc4e7ac 100644 --- a/plugins/main/macvlan/macvlan.go +++ b/plugins/main/macvlan/macvlan.go @@ -45,6 +45,17 @@ type NetConf struct { Master string `json:"master"` Mode string `json:"mode"` MTU int `json:"mtu"` + Mac string `json:"mac,omitempty"` + + RuntimeConfig struct { + Mac string `json:"mac,omitempty"` + } `json:"runtimeConfig,omitempty"` +} + +// MacEnvArgs represents CNI_ARG +type MacEnvArgs struct { + types.CommonArgs + MAC types.UnmarshallableString `json:"mac,omitempty"` } func init() { @@ -73,7 +84,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 +106,22 @@ 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) + } + } + + if n.RuntimeConfig.Mac != "" { + n.Mac = n.RuntimeConfig.Mac + } + return n, n.CNIVersion, nil } @@ -156,14 +183,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 +241,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 +348,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 +386,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..e0890ff97 100644 --- a/plugins/main/macvlan/macvlan_test.go +++ b/plugins/main/macvlan/macvlan_test.go @@ -594,4 +594,176 @@ var _ = Describe("macvlan Operations", func() { Expect(err).NotTo(HaveOccurred()) }) + It("configures and deconfigures l2 macvlan link with mac address (from CNI_ARGS) with CNI v4 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()) + }) + + It("configures and deconfigures l2 macvlan link with mac address (from RuntimeConfig) with ADD/DEL", func() { + const IFNAME = "macvl0" + + conf := fmt.Sprintf(`{ + "cniVersion": "0.3.1", + "capabilities": {"mac": true}, + "RuntimeConfig": { + "mac": "c2:11:22:33:44:55" + }, + "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("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()) + }) })