diff --git a/plugins/main/macvlan/README.md b/plugins/main/macvlan/README.md index 5d17c556..df7c1326 100644 --- a/plugins/main/macvlan/README.md +++ b/plugins/main/macvlan/README.md @@ -23,7 +23,7 @@ Since each macvlan interface has its own MAC address, it makes it easy to use wi * `name` (string, required): the name of the network * `type` (string, required): "macvlan" -* `master` (string, required): name of the host interface to enslave +* `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. diff --git a/plugins/main/macvlan/macvlan.go b/plugins/main/macvlan/macvlan.go index 863f878f..77127eac 100644 --- a/plugins/main/macvlan/macvlan.go +++ b/plugins/main/macvlan/macvlan.go @@ -51,13 +51,33 @@ func init() { runtime.LockOSThread() } +func getDefaultRouteInterfaceName() string { + routeToDstIP, err := netlink.RouteList(nil, netlink.FAMILY_ALL) + if err != nil { + return "" + } + + for _, v := range routeToDstIP { + l, _ := netlink.LinkByIndex(v.LinkIndex) + if v.Dst == nil { + return l.Attrs().Name + } + } + + return "" +} + func loadConf(bytes []byte) (*NetConf, string, error) { n := &NetConf{} if err := json.Unmarshal(bytes, n); err != nil { return nil, "", fmt.Errorf("failed to load netconf: %v", err) } if n.Master == "" { - return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`) + defaultRouteInterface := getDefaultRouteInterfaceName() + if defaultRouteInterface == "" { + return nil, "", fmt.Errorf(`cannot get default route interface for master`) + } + n.Master = defaultRouteInterface } return n, n.CNIVersion, nil } diff --git a/plugins/main/macvlan/macvlan_test.go b/plugins/main/macvlan/macvlan_test.go index d76540ee..36a7eb56 100644 --- a/plugins/main/macvlan/macvlan_test.go +++ b/plugins/main/macvlan/macvlan_test.go @@ -399,4 +399,115 @@ var _ = Describe("macvlan Operations", func() { }) Expect(err).NotTo(HaveOccurred()) }) + + It("configures and deconfigures a macvlan link with ADD/DEL, without master config", func() { + const IFNAME = "macvl0" + + conf := `{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "macvlan", + "ipam": { + "type": "host-local", + "subnet": "10.1.2.0/24" + } +}` + + targetNs, err := testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + defer targetNs.Close() + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: IFNAME, + StdinData: []byte(conf), + } + + // Make MASTER_NAME as default route interface + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(MASTER_NAME) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetUp(link) + Expect(err).NotTo(HaveOccurred()) + + var address = &net.IPNet{IP: net.IPv4(192, 0, 0, 1), Mask: net.CIDRMask(24, 32)} + var addr = &netlink.Addr{IPNet: address} + err = netlink.AddrAdd(link, addr) + Expect(err).NotTo(HaveOccurred()) + + // add default gateway into MASTER + dst := &net.IPNet{ + IP: net.IPv4(0, 0, 0, 0), + Mask: net.CIDRMask(0, 0), + } + ip := net.IPv4(192, 0, 0, 254) + route := netlink.Route{LinkIndex: link.Attrs().Index, Dst: dst, Gw: ip} + err = netlink.RouteAdd(&route) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + var result *current.Result + err = originalNS.Do(func(ns.NetNS) error { + 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(1)) + 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(1)) + 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()) + }) + })