From c798f80912901c857d003d1dc164921f2e56b3a1 Mon Sep 17 00:00:00 2001 From: mmirecki Date: Wed, 18 Jan 2023 13:07:09 +0100 Subject: [PATCH] Add support for in-container master for ipvlan Signed-off-by: mmirecki --- plugins/main/ipvlan/ipvlan.go | 75 ++++++++-- plugins/main/ipvlan/ipvlan_test.go | 217 +++++++++++++++++------------ 2 files changed, 187 insertions(+), 105 deletions(-) diff --git a/plugins/main/ipvlan/ipvlan.go b/plugins/main/ipvlan/ipvlan.go index fae42433..20506561 100644 --- a/plugins/main/ipvlan/ipvlan.go +++ b/plugins/main/ipvlan/ipvlan.go @@ -36,9 +36,10 @@ import ( type NetConf struct { types.NetConf - Master string `json:"master"` - Mode string `json:"mode"` - MTU int `json:"mtu"` + Master string `json:"master"` + Mode string `json:"mode"` + MTU int `json:"mtu"` + LinkContNs bool `json:"linkInContainer,omitempty"` } func init() { @@ -48,9 +49,9 @@ func init() { runtime.LockOSThread() } -func loadConf(bytes []byte, cmdCheck bool) (*NetConf, string, error) { +func loadConf(args *skel.CmdArgs, cmdCheck bool) (*NetConf, string, error) { n := &NetConf{} - if err := json.Unmarshal(bytes, n); err != nil { + if err := json.Unmarshal(args.StdinData, n); err != nil { return nil, "", fmt.Errorf("failed to load netconf: %v", err) } @@ -73,8 +74,8 @@ func loadConf(bytes []byte, cmdCheck bool) (*NetConf, string, error) { } if n.Master == "" { if result == nil { - defaultRouteInterface, err := getDefaultRouteInterfaceName() - + var defaultRouteInterface string + defaultRouteInterface, err = getNamespacedDefaultRouteInterfaceName(args.Netns, n.LinkContNs) if err != nil { return nil, "", err } @@ -124,7 +125,15 @@ func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interf return nil, err } - m, err := netlink.LinkByName(conf.Master) + var m netlink.Link + if conf.LinkContNs { + err = netns.Do(func(_ ns.NetNS) error { + m, err = netlink.LinkByName(conf.Master) + return err + }) + } else { + m, err = netlink.LinkByName(conf.Master) + } if err != nil { return nil, fmt.Errorf("failed to lookup master %q: %v", conf.Master, err) } @@ -146,8 +155,14 @@ func createIpvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interf Mode: mode, } - if err := netlink.LinkAdd(mv); err != nil { - return nil, fmt.Errorf("failed to create ipvlan: %v", err) + if conf.LinkContNs { + err = netns.Do(func(_ ns.NetNS) error { + return netlink.LinkAdd(mv) + }) + } else { + if err := netlink.LinkAdd(mv); err != nil { + return nil, fmt.Errorf("failed to create ipvlan: %v", err) + } } err = netns.Do(func(_ ns.NetNS) error { @@ -193,8 +208,31 @@ func getDefaultRouteInterfaceName() (string, error) { return "", fmt.Errorf("no default route interface found") } +func getNamespacedDefaultRouteInterfaceName(namespace string, inContainer bool) (string, error) { + if !inContainer { + return getDefaultRouteInterfaceName() + } + netns, err := ns.GetNS(namespace) + if err != nil { + return "", fmt.Errorf("failed to open netns %q: %v", netns, err) + } + defer netns.Close() + var defaultRouteInterface string + err = netns.Do(func(_ ns.NetNS) error { + defaultRouteInterface, err = getDefaultRouteInterfaceName() + if err != nil { + return err + } + return nil + }) + if err != nil { + return "", err + } + return defaultRouteInterface, nil +} + func cmdAdd(args *skel.CmdArgs) error { - n, cniVersion, err := loadConf(args.StdinData, false) + n, cniVersion, err := loadConf(args, false) if err != nil { return err } @@ -272,7 +310,7 @@ func cmdAdd(args *skel.CmdArgs) error { } func cmdDel(args *skel.CmdArgs) error { - n, _, err := loadConf(args.StdinData, false) + n, _, err := loadConf(args, false) if err != nil { return err } @@ -320,7 +358,7 @@ func main() { func cmdCheck(args *skel.CmdArgs) error { - n, _, err := loadConf(args.StdinData, true) + n, _, err := loadConf(args, true) if err != nil { return err } @@ -369,7 +407,16 @@ func cmdCheck(args *skel.CmdArgs) error { contMap.Sandbox, args.Netns) } - m, err := netlink.LinkByName(n.Master) + var m netlink.Link + if n.LinkContNs { + err = netns.Do(func(_ ns.NetNS) error { + m, err = netlink.LinkByName(n.Master) + return err + }) + } else { + m, err = netlink.LinkByName(n.Master) + } + if err != nil { return fmt.Errorf("failed to lookup master %q: %v", n.Master, err) } diff --git a/plugins/main/ipvlan/ipvlan_test.go b/plugins/main/ipvlan/ipvlan_test.go index 5db3bd36..4ce72349 100644 --- a/plugins/main/ipvlan/ipvlan_test.go +++ b/plugins/main/ipvlan/ipvlan_test.go @@ -38,6 +38,7 @@ import ( ) const MASTER_NAME = "eth0" +const MASTER_NAME_INCONTAINER = "eth1" type Net struct { Name string `json:"name"` @@ -49,6 +50,7 @@ type Net struct { DNS types.DNS `json:"dns"` RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` PrevResult types100.Result `json:"-"` + LinkContNs bool `json:"linkInContainer"` } func buildOneConfig(cniVersion string, master string, orig *Net, prevResult types.Result) (*Net, error) { @@ -289,6 +291,22 @@ var _ = Describe("ipvlan Operations", func() { return nil }) Expect(err).NotTo(HaveOccurred()) + + err = targetNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + // Add master + err = netlink.LinkAdd(&netlink.Dummy{ + LinkAttrs: netlink.LinkAttrs{ + Name: MASTER_NAME_INCONTAINER, + }, + }) + Expect(err).NotTo(HaveOccurred()) + _, err = netlink.LinkByName(MASTER_NAME_INCONTAINER) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + Expect(err).NotTo(HaveOccurred()) }) AfterEach(func() { @@ -301,67 +319,77 @@ var _ = Describe("ipvlan Operations", func() { Expect(testutils.UnmountNS(targetNS)).To(Succeed()) }) - for _, ver := range testutils.AllSpecVersions { - // Redefine ver inside for scope so real value is picked up by each dynamically defined It() - // See Gingkgo's "Patterns for dynamically generating tests" documentation. - ver := ver + for _, inContainer := range []bool{false, true} { + masterInterface := MASTER_NAME + if inContainer { + masterInterface = MASTER_NAME_INCONTAINER + } + //for _, ver := range testutils.AllSpecVersions { + for _, ver := range [...]string{"1.0.0"} { + // Redefine ver inside for scope so real value is picked up by each dynamically defined It() + // See Gingkgo's "Patterns for dynamically generating tests" documentation. + ver := ver + isInContainer := inContainer // Tests need a local var with constant value - It(fmt.Sprintf("[%s] creates an ipvlan link in a non-default namespace", ver), func() { - conf := &NetConf{ - NetConf: types.NetConf{ - CNIVersion: ver, - Name: "testConfig", - Type: "ipvlan", - }, - Master: MASTER_NAME, - Mode: "l2", - MTU: 1500, - } + It(fmt.Sprintf("[%s] creates an ipvlan link in a non-default namespace", ver), func() { + conf := &NetConf{ + NetConf: types.NetConf{ + CNIVersion: ver, + Name: "testConfig", + Type: "ipvlan", + }, + Master: masterInterface, + Mode: "l2", + MTU: 1500, + LinkContNs: isInContainer, + } - err := originalNS.Do(func(ns.NetNS) error { - defer GinkgoRecover() + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, err := createIpvlan(conf, "foobar0", targetNS) + Expect(err).NotTo(HaveOccurred()) + return nil + }) - _, err := createIpvlan(conf, "foobar0", targetNS) Expect(err).NotTo(HaveOccurred()) - return nil + + // Make sure ipvlan link exists in the target namespace + err = targetNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName("foobar0") + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Name).To(Equal("foobar0")) + return nil + }) + Expect(err).NotTo(HaveOccurred()) }) - Expect(err).NotTo(HaveOccurred()) - - // Make sure ipvlan link exists in the target namespace - err = targetNS.Do(func(ns.NetNS) error { - defer GinkgoRecover() - - link, err := netlink.LinkByName("foobar0") - Expect(err).NotTo(HaveOccurred()) - Expect(link.Attrs().Name).To(Equal("foobar0")) - return nil - }) - Expect(err).NotTo(HaveOccurred()) - }) - - It(fmt.Sprintf("[%s] configures and deconfigures an iplvan link with ADD/DEL", ver), func() { - conf := fmt.Sprintf(`{ + It(fmt.Sprintf("[%s] configures and deconfigures an iplvan link with ADD/DEL", ver), func() { + conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "ipvlan", "master": "%s", + "linkInContainer": %t, "ipam": { "type": "host-local", "subnet": "10.1.2.0/24", "dataDir": "%s" } - }`, ver, MASTER_NAME, dataDir) + }`, ver, masterInterface, isInContainer, dataDir) - ipvlanAddCheckDelTest(conf, "", originalNS, targetNS) - }) + ipvlanAddCheckDelTest(conf, "", originalNS, targetNS) + }) - if testutils.SpecVersionHasChaining(ver) { - It(fmt.Sprintf("[%s] configures and deconfigures an iplvan link with ADD/DEL when chained", ver), func() { - conf := fmt.Sprintf(`{ + if testutils.SpecVersionHasChaining(ver) { + It(fmt.Sprintf("[%s] configures and deconfigures an iplvan link with ADD/DEL when chained", ver), func() { + conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "ipvlan", + "linkInContainer": %t, "prevResult": { "interfaces": [ { @@ -378,84 +406,91 @@ var _ = Describe("ipvlan Operations", func() { ], "routes": [] } - }`, ver, MASTER_NAME) + }`, ver, isInContainer, masterInterface) - ipvlanAddCheckDelTest(conf, MASTER_NAME, originalNS, targetNS) - }) - } + ipvlanAddCheckDelTest(conf, masterInterface, originalNS, targetNS) + }) + } - It(fmt.Sprintf("[%s] deconfigures an unconfigured ipvlan link with DEL", ver), func() { - conf := fmt.Sprintf(`{ + It(fmt.Sprintf("[%s] deconfigures an unconfigured ipvlan link with DEL", ver), func() { + conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "ipvlan", "master": "%s", + "linkInContainer": %t, "ipam": { "type": "host-local", "subnet": "10.1.2.0/24", "dataDir": "%s" } - }`, ver, MASTER_NAME, dataDir) + }`, ver, masterInterface, isInContainer, dataDir) - args := &skel.CmdArgs{ - ContainerID: "dummy", - Netns: targetNS.Path(), - IfName: "ipvl0", - StdinData: []byte(conf), - } + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: "ipvl0", + StdinData: []byte(conf), + } - err := originalNS.Do(func(ns.NetNS) error { - defer GinkgoRecover() + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() - err := testutils.CmdDelWithArgs(args, func() error { - return cmdDel(args) + err := testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + Expect(err).NotTo(HaveOccurred()) + return nil }) Expect(err).NotTo(HaveOccurred()) - return nil }) - Expect(err).NotTo(HaveOccurred()) - }) - It(fmt.Sprintf("[%s] configures and deconfigures a ipvlan link with ADD/DEL, without master config", ver), func() { - conf := fmt.Sprintf(`{ + It(fmt.Sprintf("[%s] configures and deconfigures a ipvlan link with ADD/DEL, without master config", ver), func() { + conf := fmt.Sprintf(`{ "cniVersion": "%s", "name": "mynet", "type": "ipvlan", + "linkInContainer": %t, "ipam": { "type": "host-local", "subnet": "10.1.2.0/24", "dataDir": "%s" } - }`, ver, dataDir) + }`, ver, isInContainer, dataDir) - // 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), + // Make MASTER_NAME as default route interface + currentNs := originalNS + if isInContainer { + currentNs = targetNS } - 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()) + err := currentNs.Do(func(ns.NetNS) error { + defer GinkgoRecover() - ipvlanAddCheckDelTest(conf, MASTER_NAME, originalNS, targetNS) - }) + link, err := netlink.LinkByName(masterInterface) + 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()) + + ipvlanAddCheckDelTest(conf, masterInterface, originalNS, targetNS) + }) + } } })