From 65a37b7d9c67128c8e139ae37c7e20bcbd09ced3 Mon Sep 17 00:00:00 2001 From: Penny Zheng Date: Wed, 10 Jun 2020 10:27:58 +0000 Subject: [PATCH] rate-limiter: add ifb interface Ingress traffic shaping is very limited, and the htb qdisc discipline couldn't be applied to interface ingress traffic. Here, we import a new pseudo network interface, Intermediate Functional Block (ifb). It is an alternative to tc filters for handling ingress traffic, by redirecting interface ingress traffic to ifb and treat it as egress traffic there. Fixes: #250 Signed-off-by: Penny Zheng --- src/runtime/virtcontainers/network.go | 49 +++++++++++++++++++++++ src/runtime/virtcontainers/utils/utils.go | 22 ++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/runtime/virtcontainers/network.go b/src/runtime/virtcontainers/network.go index 7f6e74bba8..3ff7ad27db 100644 --- a/src/runtime/virtcontainers/network.go +++ b/src/runtime/virtcontainers/network.go @@ -13,6 +13,7 @@ import ( "math/rand" "net" "os" + "os/exec" "runtime" "sort" "time" @@ -1434,3 +1435,51 @@ func addHTBQdisc(linkIndex int, maxRate uint64) error { return nil } + +// The Intermediate Functional Block (ifb) pseudo network interface is an alternative +// to tc filters for handling ingress traffic, +// By redirecting interface ingress traffic to ifb and treat it as egress traffic there, +// we could do network shaping to interface inbound traffic. +func addIFBDevice() (int, error) { + // check whether host supports ifb + if ok, err := utils.SupportsIfb(); !ok { + return -1, err + } + + netHandle, err := netlink.NewHandle() + if err != nil { + return -1, err + } + defer netHandle.Delete() + + // There exists error when using netlink library to create ifb interface + cmd := exec.Command("ip", "link", "add", "dev", "ifb0", "type", "ifb") + if output, err := cmd.CombinedOutput(); err != nil { + return -1, fmt.Errorf("Could not create link ifb0: %v, error %v", output, err) + } + + ifbLink, err := netlink.LinkByName("ifb0") + if err != nil { + return -1, err + } + + if err := netHandle.LinkSetUp(ifbLink); err != nil { + return -1, fmt.Errorf("Could not enable link ifb0 %v", err) + } + + return ifbLink.Attrs().Index, nil +} + +// This is equivalent to calling: +// tc filter add dev source parent ffff: protocol all u32 match u8 0 0 action mirred egress redirect dev ifb +func addIFBRedirecting(sourceIndex int, ifbIndex int) error { + if err := addQdiscIngress(sourceIndex); err != nil { + return err + } + + if err := addRedirectTCFilter(sourceIndex, ifbIndex); err != nil { + return err + } + + return nil +} diff --git a/src/runtime/virtcontainers/utils/utils.go b/src/runtime/virtcontainers/utils/utils.go index 85c554896a..72dff4380f 100644 --- a/src/runtime/virtcontainers/utils/utils.go +++ b/src/runtime/virtcontainers/utils/utils.go @@ -29,6 +29,9 @@ const MaxSocketPathLen = 107 // VHostVSockDevicePath path to vhost-vsock device var VHostVSockDevicePath = "/dev/vhost-vsock" +// sysModuleDir is the directory where system modules locate. +var sysModuleDir = "/sys/module" + // FileCopy copys files from srcPath to dstPath func FileCopy(srcPath, dstPath string) error { if srcPath == "" { @@ -235,6 +238,25 @@ func SupportsVsocks() bool { return true } +// SupportsIfb returns true if ifb are supported, otherwise false +func SupportsIfb() (bool, error) { + ifbModule := "ifb" + // First, check to see if the ifb module is already loaded + path := filepath.Join(sysModuleDir, ifbModule) + if _, err := os.Stat(path); err == nil { + return true, nil + } + + // Try to load the ifb module. + // When inserting the ifb module, tell it the number of virtual interfaces you need, here, it's zero. + // The default is 2. + cmd := exec.Command("modprobe", ifbModule, "numifbs=0") + if output, err := cmd.CombinedOutput(); err != nil { + return false, fmt.Errorf("modprobe insert ifb module failed: %s", string(output)) + } + return true, nil +} + // StartCmd pointer to a function to start a command. // Defined this way to allow mock testing. var StartCmd = func(c *exec.Cmd) error {