From edab9efdeaa8427f218cdf568905b0f2a49e40aa Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Fri, 17 Feb 2023 16:47:44 +0100 Subject: [PATCH] tap: allow for a tap device to be created as a bridge port This extends the tap plugin API enabling the user to instruct the CNI plugin the created tap device must be set as a port of an *existing* linux bridge on the pod network namespace. This is helpful for KubeVirt, allowing network connectivity to be extended from the pod's interface into the Virtual Machine running inside the pod. Signed-off-by: Miguel Duarte Barroso --- plugins/main/tap/tap.go | 13 ++++ plugins/main/tap/tap_test.go | 115 +++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) diff --git a/plugins/main/tap/tap.go b/plugins/main/tap/tap.go index 760fb4f42..5e2c03fac 100644 --- a/plugins/main/tap/tap.go +++ b/plugins/main/tap/tap.go @@ -47,6 +47,7 @@ type NetConf struct { Owner *uint32 `json:"owner,omitempty"` Group *uint32 `json:"group,omitempty"` SelinuxContext string `json:"selinuxContext,omitempty"` + Bridge string `json:"bridge,omitempty"` Args *struct{} `json:"args,omitempty"` RuntimeConfig struct { Mac string `json:"mac,omitempty"` @@ -216,6 +217,18 @@ func createTap(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface return fmt.Errorf("failed to refetch tap %q: %v", ifName, err) } + if conf.Bridge != "" { + bridge, err := netlink.LinkByName(conf.Bridge) + if err != nil { + return fmt.Errorf("failed to get bridge %s: %v", conf.Bridge, err) + } + + tapDev := link + if err := netlink.LinkSetMaster(tapDev, bridge); err != nil { + return fmt.Errorf("failed to set tap %s as a port of bridge %s: %v", tap.Name, conf.Bridge, err) + } + } + err = netlink.LinkSetUp(link) if err != nil { return fmt.Errorf("failed to set tap interface up: %v", err) diff --git a/plugins/main/tap/tap_test.go b/plugins/main/tap/tap_test.go index 771f6afa8..c39a25ee6 100644 --- a/plugins/main/tap/tap_test.go +++ b/plugins/main/tap/tap_test.go @@ -314,5 +314,120 @@ var _ = Describe("Add, check, remove tap plugin", func() { Expect(err).NotTo(HaveOccurred()) }) + It(fmt.Sprintf("[%s] add, check and remove a tap device as a bridge port", ver), func() { + const bridgeName = "br1" + conf := fmt.Sprintf(`{ + "cniVersion": "%s", + "name": "tapTest", + "type": "tap", + "owner": 0, + "group": 0, + "bridge": %q, + "ipam": { + "type": "host-local", + "subnet": "10.1.2.0/24", + "dataDir": "%s" + } + }`, ver, bridgeName, dataDir) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: IFNAME, + StdinData: []byte(conf), + } + + t := newTesterByVersion(ver) + + var bridge netlink.Link + var result types.Result + var macAddress string + var err error + + Expect( + targetNS.Do(func(ns.NetNS) error { + if err := netlink.LinkAdd(&netlink.Bridge{ + LinkAttrs: netlink.LinkAttrs{ + Name: bridgeName, + }, + }); err != nil { + return err + } + bridge, err = netlink.LinkByName(bridgeName) + if err != nil { + return err + } + return nil + }), + ).To(Succeed()) + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + result, _, err = testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + macAddress = t.verifyResult(result, IFNAME) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + By("Make sure the tap 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)) + Expect(link.Type()).To(Equal(TYPETAP)) + Expect(link.Attrs().MasterIndex).To(Equal(bridge.Attrs().Index)) + + if macAddress != "" { + hwaddr, err := net.ParseMAC(macAddress) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr)) + } + addrs, err := netlink.AddrList(link, syscall.AF_INET) + Expect(err).NotTo(HaveOccurred()) + Expect(addrs).To(HaveLen(1)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + By("Running cmdDel") + args.StdinData = []byte(conf) + 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()) + + 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()) + + By("Running cmdDel more than once without error") + 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()) + }) } })