From 3a7f254a632624542e7edb6b1351a9667dde8d15 Mon Sep 17 00:00:00 2001 From: Tomofumi Hayashi Date: Tue, 31 Jul 2018 12:54:15 +0900 Subject: [PATCH] Introduce iplink(MTU MAC and promiscas) feature into tuning This change adds 'ip link' command related feature into tuning meta cni plugin. Currently MTU, MAC and promiscas mode are supported. --- plugins/meta/tuning/tuning.go | 100 ++++++++++++- plugins/meta/tuning/tuning_test.go | 233 +++++++++++++++++++++++++++++ 2 files changed, 330 insertions(+), 3 deletions(-) diff --git a/plugins/meta/tuning/tuning.go b/plugins/meta/tuning/tuning.go index 7b9d2ddb..9397bc31 100644 --- a/plugins/meta/tuning/tuning.go +++ b/plugins/meta/tuning/tuning.go @@ -21,6 +21,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net" "path/filepath" "strings" @@ -29,6 +30,7 @@ import ( "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/plugins/pkg/ns" + "github.com/vishvananda/netlink" ) // TuningConf represents the network tuning configuration. @@ -37,14 +39,35 @@ type TuningConf struct { SysCtl map[string]string `json:"sysctl"` RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` PrevResult *current.Result `json:"-"` + Mac string `json:"mac,omitempty"` + Promisc bool `json:"promisc,omitempty"` + Mtu int `json:"mtu,omitempty"` } -func parseConf(data []byte) (*TuningConf, error) { - conf := TuningConf{} +type MACEnvArgs struct { + types.CommonArgs + MAC types.UnmarshallableString `json:"mac,omitempty"` +} + +func parseConf(data []byte, envArgs string) (*TuningConf, error) { + conf := TuningConf{Promisc: false, Mtu: -1} if err := json.Unmarshal(data, &conf); err != nil { return nil, fmt.Errorf("failed to load netconf: %v", err) } + // Parse custom MAC from both env args + if envArgs != "" { + e := MACEnvArgs{} + err := types.LoadArgs(envArgs, &e) + if err != nil { + return nil, err + } + + if e.MAC != "" { + conf.Mac = string(e.MAC) + } + } + // Parse previous result. if conf.RawPrevResult != nil { resultBytes, err := json.Marshal(conf.RawPrevResult) @@ -65,8 +88,59 @@ func parseConf(data []byte) (*TuningConf, error) { return &conf, nil } +func changeMacAddr(ifName string, newMacAddr string) error { + addr, err := net.ParseMAC(newMacAddr) + if err != nil { + return fmt.Errorf("invalid args %v for MAC addr: %v", newMacAddr, err) + } + + link, err := netlink.LinkByName(ifName) + if err != nil { + return fmt.Errorf("failed to get %q: %v", ifName, err) + } + + err = netlink.LinkSetDown(link) + if err != nil { + return fmt.Errorf("failed to set %q down: %v", ifName, err) + } + err = netlink.LinkSetHardwareAddr(link, addr) + if err != nil { + return fmt.Errorf("failed to set %q address to %q: %v", ifName, newMacAddr, err) + } + return netlink.LinkSetUp(link) +} + +func updateResultsMacAddr(config TuningConf, ifName string, newMacAddr string) error { + for _, i := range config.PrevResult.Interfaces { + if i.Name == ifName { + i.Mac = newMacAddr + } + } + return nil +} + +func changePromisc(ifName string, val bool) error { + link, err := netlink.LinkByName(ifName) + if err != nil { + return fmt.Errorf("failed to get %q: %v", ifName, err) + } + + if val { + return netlink.SetPromiscOn(link) + } + return netlink.SetPromiscOff(link) +} + +func changeMtu(ifName string, mtu int) error { + link, err := netlink.LinkByName(ifName) + if err != nil { + return fmt.Errorf("failed to get %q: %v", ifName, err) + } + return netlink.LinkSetMTU(link, mtu) +} + func cmdAdd(args *skel.CmdArgs) error { - tuningConf, err := parseConf(args.StdinData) + tuningConf, err := parseConf(args.StdinData, args.Args) if err != nil { return err } @@ -90,6 +164,26 @@ func cmdAdd(args *skel.CmdArgs) error { return err } } + + var err error + + if tuningConf.Mac != "" { + if err = changeMacAddr(args.IfName, tuningConf.Mac); err == nil { + err = updateResultsMacAddr(*tuningConf, args.IfName, tuningConf.Mac) + } + } + + if tuningConf.Promisc != false { + if err = changePromisc(args.IfName, true); err != nil { + return err + } + } + + if tuningConf.Mtu != -1 { + if err = changeMtu(args.IfName, tuningConf.Mtu); err != nil { + return err + } + } return nil }) if err != nil { diff --git a/plugins/meta/tuning/tuning_test.go b/plugins/meta/tuning/tuning_test.go index ed74123d..6bba6a3a 100644 --- a/plugins/meta/tuning/tuning_test.go +++ b/plugins/meta/tuning/tuning_test.go @@ -19,6 +19,7 @@ import ( "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/testutils" + "net" "github.com/vishvananda/netlink" @@ -109,4 +110,236 @@ var _ = Describe("tuning plugin", func() { }) Expect(err).NotTo(HaveOccurred()) }) + + It("configures and deconfigures promiscas mode with ADD/DEL", func() { + conf := []byte(`{ + "name": "test", + "type": "iplink", + "cniVersion": "0.3.1", + "promisc": true, + "prevResult": { + "interfaces": [ + {"name": "dummy0", "sandbox":"netns"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } +}`) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: originalNS.Path(), + IfName: IFNAME, + StdinData: conf, + } + + 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(1)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().Promisc).To(Equal(1)) + + err = testutils.CmdDel(originalNS.Path(), + args.ContainerID, "", func() error { return cmdDel(args) }) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("configures and deconfigures mtu with ADD/DEL", func() { + conf := []byte(`{ + "name": "test", + "type": "iplink", + "cniVersion": "0.3.1", + "mtu": 1454, + "prevResult": { + "interfaces": [ + {"name": "dummy0", "sandbox":"netns"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } +}`) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: originalNS.Path(), + IfName: IFNAME, + StdinData: conf, + } + + 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(1)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().MTU).To(Equal(1454)) + + err = testutils.CmdDel(originalNS.Path(), + args.ContainerID, "", func() error { return cmdDel(args) }) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("configures and deconfigures mac address (from conf file) with ADD/DEL", func() { + conf := []byte(`{ + "name": "test", + "type": "iplink", + "cniVersion": "0.3.1", + "mac": "c2:11:22:33:44:55", + "prevResult": { + "interfaces": [ + {"name": "dummy0", "sandbox":"netns"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } +}`) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: originalNS.Path(), + IfName: IFNAME, + StdinData: conf, + } + + 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(1)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + hw, err := net.ParseMAC("c2:11:22:33:44:55") + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(hw)) + + err = testutils.CmdDel(originalNS.Path(), + args.ContainerID, "", func() error { return cmdDel(args) }) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("configures and deconfigures mac address (from CNI_ARGS) with ADD/DEL", func() { + conf := []byte(`{ + "name": "test", + "type": "iplink", + "cniVersion": "0.3.1", + "prevResult": { + "interfaces": [ + {"name": "dummy0", "sandbox":"netns"} + ], + "ips": [ + { + "version": "4", + "address": "10.0.0.2/24", + "gateway": "10.0.0.1", + "interface": 0 + } + ] + } +}`) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: originalNS.Path(), + IfName: IFNAME, + StdinData: conf, + Args: "IgnoreUnknown=true;MAC=c2:11:22:33:44:66", + } + + 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(1)) + Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24")) + + link, err := netlink.LinkByName(IFNAME) + Expect(err).NotTo(HaveOccurred()) + hw, err := net.ParseMAC("c2:11:22:33:44:66") + Expect(err).NotTo(HaveOccurred()) + Expect(link.Attrs().HardwareAddr).To(Equal(hw)) + + err = testutils.CmdDel(originalNS.Path(), + args.ContainerID, "", func() error { return cmdDel(args) }) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) })