diff --git a/.gitignore b/.gitignore index cbdb888df4..d15e3a0a30 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,4 @@ coverage.txt core-plugins/ build/ vendor -egress-v4-cni +egress-cni diff --git a/Makefile b/Makefile index 7fec374541..a22a96b9e6 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,7 @@ LDFLAGS = -X pkg/version/info.Version=$(VERSION) -X pkg/awsutils/awssession.vers # ALLPKGS is the set of packages provided in source. ALLPKGS = $(shell go list $(VENDOR_OVERRIDE_FLAG) ./... | grep -v cmd/packet-verifier) # BINS is the set of built command executables. -BINS = aws-k8s-agent aws-cni grpc-health-probe cni-metrics-helper aws-vpc-cni aws-vpc-cni-init egress-v4-cni +BINS = aws-k8s-agent aws-cni grpc-health-probe cni-metrics-helper aws-vpc-cni aws-vpc-cni-init egress-cni # CORE_PLUGIN_DIR is the directory containing upstream containernetworking plugins CORE_PLUGIN_DIR = $(MAKEFILE_PATH)/core-plugins/ @@ -134,7 +134,7 @@ build-linux: ## Build the VPC CNI plugin agent using the host's Go toolchain. go build $(VENDOR_OVERRIDE_FLAG) $(BUILD_FLAGS) -o aws-k8s-agent ./cmd/aws-k8s-agent go build $(VENDOR_OVERRIDE_FLAG) $(BUILD_FLAGS) -o aws-cni ./cmd/routed-eni-cni-plugin go build $(VENDOR_OVERRIDE_FLAG) $(BUILD_FLAGS) -o grpc-health-probe ./cmd/grpc-health-probe - go build $(VENDOR_OVERRIDE_FLAG) $(BUILD_FLAGS) -o egress-v4-cni ./cmd/egress-v4-cni-plugin + go build $(VENDOR_OVERRIDE_FLAG) $(BUILD_FLAGS) -o egress-cni ./cmd/egress-cni-plugin # Build VPC CNI init container entrypoint build-aws-vpc-cni-init: BUILD_FLAGS = $(BUILD_MODE) -ldflags '-s -w $(LDFLAGS)' diff --git a/cmd/aws-vpc-cni/main.go b/cmd/aws-vpc-cni/main.go index 6ee4980819..28c659edf0 100644 --- a/cmd/aws-vpc-cni/main.go +++ b/cmd/aws-vpc-cni/main.go @@ -38,7 +38,6 @@ package main import ( "bytes" "encoding/json" - "io/ioutil" "net" "os" "os/exec" @@ -48,13 +47,17 @@ import ( "github.com/pkg/errors" log "github.com/sirupsen/logrus" + "github.com/containernetworking/cni/pkg/types" + "github.com/aws/amazon-vpc-cni-k8s/utils" "github.com/aws/amazon-vpc-cni-k8s/utils/cp" "github.com/aws/amazon-vpc-cni-k8s/utils/imds" - "github.com/containernetworking/cni/pkg/types" ) const ( + egressPluginIpamSubnetV4 = "169.254.172.0/22" + egressPluginIpamDstV4 = "0.0.0.0/0" + egressPluginIpamDataDirV4 = "/run/cni/v6pd/egress-v4-ipam" defaultHostCNIBinPath = "/host/opt/cni/bin" defaultHostCNIConfDirPath = "/host/etc/cni/net.d" defaultAWSconflistFile = "/app/10-aws.conflist" @@ -214,7 +217,7 @@ func isValidJSON(inFile string) error { } func generateJSON(jsonFile string, outFile string, nodeIP string) error { - byteValue, err := ioutil.ReadFile(jsonFile) + byteValue, err := os.ReadFile(jsonFile) if err != nil { return err } @@ -234,8 +237,11 @@ func generateJSON(jsonFile string, outFile string, nodeIP string) error { netconf = strings.Replace(netconf, "__PODSGENFORCINGMODE__", podSGEnforcingMode, -1) netconf = strings.Replace(netconf, "__PLUGINLOGFILE__", pluginLogFile, -1) netconf = strings.Replace(netconf, "__PLUGINLOGLEVEL__", pluginLogLevel, -1) - netconf = strings.Replace(netconf, "__EGRESSV4PLUGINLOGFILE__", egressV4pluginLogFile, -1) - netconf = strings.Replace(netconf, "__EGRESSV4PLUGINENABLED__", enabledIPv6, -1) + netconf = strings.Replace(netconf, "__EGRESSPLUGINLOGFILE__", egressV4pluginLogFile, -1) + netconf = strings.Replace(netconf, "__EGRESSPLUGINENABLED__", enabledIPv6, -1) + netconf = strings.Replace(netconf, "__EGRESSPLUGINIPAMSUBNET__", egressPluginIpamSubnetV4, -1) + netconf = strings.Replace(netconf, "__EGRESSPLUGINIPAMDST__", egressPluginIpamDstV4, -1) + netconf = strings.Replace(netconf, "__EGRESSPLUGINIPAMDATADIR__", egressPluginIpamDataDirV4, -1) netconf = strings.Replace(netconf, "__RANDOMIZESNAT__", randomizeSNAT, -1) netconf = strings.Replace(netconf, "__NODEIP__", nodeIP, -1) @@ -265,7 +271,7 @@ func generateJSON(jsonFile string, outFile string, nodeIP string) error { log.Fatalf("%s is not a valid json object, error: %s", netconf, err) } - err = ioutil.WriteFile(outFile, byteValue, 0644) + err = os.WriteFile(outFile, byteValue, 0644) return err } @@ -345,7 +351,7 @@ func _main() int { log.WithError(err).Error("Failed to enable nftables") } - pluginBins := []string{"aws-cni", "egress-v4-cni"} + pluginBins := []string{"aws-cni", "egress-cni"} hostCNIBinPath := utils.GetEnv(envHostCniBinPath, defaultHostCNIBinPath) err := cp.InstallBinaries(pluginBins, hostCNIBinPath) if err != nil { diff --git a/cmd/egress-cni-plugin/egressContext.go b/cmd/egress-cni-plugin/egressContext.go new file mode 100644 index 0000000000..97d6af49ea --- /dev/null +++ b/cmd/egress-cni-plugin/egressContext.go @@ -0,0 +1,333 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package main + +import ( + "fmt" + "net" + "os" + + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/plugins/pkg/ip" + "github.com/containernetworking/plugins/pkg/ns" + "github.com/coreos/go-iptables/iptables" + "github.com/vishvananda/netlink" + + "github.com/aws/amazon-vpc-cni-k8s/cmd/egress-cni-plugin/snat" + "github.com/aws/amazon-vpc-cni-k8s/pkg/hostipamwrapper" + "github.com/aws/amazon-vpc-cni-k8s/pkg/iptableswrapper" + "github.com/aws/amazon-vpc-cni-k8s/pkg/netlinkwrapper" + "github.com/aws/amazon-vpc-cni-k8s/pkg/nswrapper" + "github.com/aws/amazon-vpc-cni-k8s/pkg/procsyswrapper" + "github.com/aws/amazon-vpc-cni-k8s/pkg/utils/cniutils" + "github.com/aws/amazon-vpc-cni-k8s/pkg/utils/logger" + "github.com/aws/amazon-vpc-cni-k8s/pkg/vethwrapper" +) + +const ( + ipv4MulticastRange = "224.0.0.0/4" +) + +// EgressContext includes all info to run container ADD/DEL action +type EgressContext struct { + Procsys procsyswrapper.ProcSys + Ipam hostipamwrapper.HostIpam + Link netlinkwrapper.NetLink + Ns nswrapper.NS + NsPath string + ArgsIfName string + Veth vethwrapper.Veth + IpTablesIface iptableswrapper.IPTablesIface + IptCreator func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) + + NetConf *NetConf + Result *current.Result + TmpResult *current.Result + Log logger.Logger + + Mtu int + // SnatChain is the chain name for iptables rules + SnatChain string + // SnatComment is the comment for iptables rules + SnatComment string +} + +// NewEgressAddContext create a context for container egress traffic +func NewEgressAddContext(nsPath, ifName string) EgressContext { + return EgressContext{ + Procsys: procsyswrapper.NewProcSys(), + Ipam: hostipamwrapper.NewIpam(), + Link: netlinkwrapper.NewNetLink(), + Ns: nswrapper.NewNS(), + NsPath: nsPath, + ArgsIfName: ifName, + Veth: vethwrapper.NewSetupVeth(), + IptCreator: func(protocol iptables.Protocol) (iptableswrapper.IPTablesIface, error) { + return iptableswrapper.NewIPTables(protocol) + }, + } +} + +// NewEgressDelContext create a context for container egress traffic +func NewEgressDelContext(nsPath string) EgressContext { + return EgressContext{ + Ipam: hostipamwrapper.NewIpam(), + Link: netlinkwrapper.NewNetLink(), + Ns: nswrapper.NewNS(), + NsPath: nsPath, + IptCreator: func(protocol iptables.Protocol) (iptableswrapper.IPTablesIface, error) { + return iptableswrapper.NewIPTables(protocol) + }, + } +} + +func (c *EgressContext) SetupContainerVeth() (*current.Interface, *current.Interface, error) { + // The IPAM result will be something like IP=192.168.3.5/24, GW=192.168.3.1. + // What we want is really a point-to-point link but veth does not support IFF_POINTTOPOINT. + // Next best thing would be to let it ARP but set interface to 192.168.3.5/32 and + // add a route like "192.168.3.0/24 via 192.168.3.1 dev $ifName". + // Unfortunately that won't work as the GW will be outside the interface's subnet. + + // Our solution is to configure the interface with 192.168.3.5/24, then delete the + // "192.168.3.0/24 dev $ifName" route that was automatically added. Then we add + // "192.168.3.1/32 dev $ifName" and "192.168.3.0/24 via 192.168.3.1 dev $ifName". + // In other words we force all traffic to ARP via the gateway except for GW itself. + + hostInterface := ¤t.Interface{} + containerInterface := ¤t.Interface{} + + err := c.Ns.WithNetNSPath(c.NsPath, func(hostNS ns.NetNS) error { + hostVeth, contVeth0, err := c.Veth.Setup(c.NetConf.IfName, c.Mtu, hostNS) + if err != nil { + return err + } + hostInterface.Name = hostVeth.Name + hostInterface.Mac = hostVeth.HardwareAddr.String() + containerInterface.Name = contVeth0.Name + containerInterface.Mac = contVeth0.HardwareAddr.String() + containerInterface.Sandbox = c.NsPath + + for _, ipc := range c.TmpResult.IPs { + // All addresses apply to the container veth interface + ipc.Interface = current.Int(1) + } + + c.TmpResult.Interfaces = []*current.Interface{hostInterface, containerInterface} + + if err = c.Ipam.ConfigureIface(c.NetConf.IfName, c.TmpResult); err != nil { + return err + } + + contVeth, err := c.Link.LinkByName(c.NetConf.IfName) + if err != nil { + return fmt.Errorf("failed to look up %q: %v", c.NetConf.IfName, err) + } + + for _, ipc := range c.TmpResult.IPs { + // Delete the route that was automatically added + route := netlink.Route{ + LinkIndex: contVeth.Attrs().Index, + Dst: &net.IPNet{ + IP: ipc.Address.IP.Mask(ipc.Address.Mask), + Mask: ipc.Address.Mask, + }, + Scope: netlink.SCOPE_NOWHERE, + } + + if err := c.Link.RouteDel(&route); err != nil { + return fmt.Errorf("failed to delete route %v: %v", route, err) + } + + addrBits := 128 + if ipc.Address.IP.To4() != nil { + addrBits = 32 + } + + for _, r := range []netlink.Route{ + { + LinkIndex: contVeth.Attrs().Index, + Dst: &net.IPNet{ + IP: ipc.Gateway, + Mask: net.CIDRMask(addrBits, addrBits), + }, + Scope: netlink.SCOPE_LINK, + Src: ipc.Address.IP, + }, + { + LinkIndex: contVeth.Attrs().Index, + Dst: &net.IPNet{ + IP: ipc.Address.IP.Mask(ipc.Address.Mask), + Mask: ipc.Address.Mask, + }, + Scope: netlink.SCOPE_UNIVERSE, + Gw: ipc.Gateway, + Src: ipc.Address.IP, + }, + } { + if err := c.Link.RouteAdd(&r); err != nil { + return fmt.Errorf("failed to add route %v: %v", r, err) + } + } + } + return nil + }) + if err != nil { + return nil, nil, err + } + return hostInterface, containerInterface, nil +} + +func (c *EgressContext) SetupHostVeth(vethName string) error { + // hostVeth moved namespaces and may have a new ifindex + veth, err := c.Link.LinkByName(vethName) + if err != nil { + return fmt.Errorf("failed to lookup %q: %v", vethName, err) + } + + for _, ipc := range c.TmpResult.IPs { + maskLen := 128 + if ipc.Address.IP.To4() != nil { + maskLen = 32 + } + + // NB: this is modified from standard ptp plugin. + + ipn := &net.IPNet{ + IP: ipc.Gateway, + Mask: net.CIDRMask(maskLen, maskLen), + } + addr := &netlink.Addr{ + IPNet: ipn, + Scope: int(netlink.SCOPE_LINK), // <- ptp uses SCOPE_UNIVERSE here + } + if err = c.Link.AddrAdd(veth, addr); err != nil { + return fmt.Errorf("failed to add IP addr (%#v) to veth: %v", ipn, err) + } + + ipn = &net.IPNet{ + IP: ipc.Address.IP, + Mask: net.CIDRMask(maskLen, maskLen), + } + err := c.Link.RouteAdd(&netlink.Route{ + LinkIndex: veth.Attrs().Index, + Scope: netlink.SCOPE_LINK, // <- ptp uses SCOPE_HOST here + Dst: ipn, + }) + if err != nil && !os.IsExist(err) { + return fmt.Errorf("failed to add route on host: %v", err) + } + } + + return nil +} + +// CmdAddEgressV4 exec necessary settings to support IPv4 egress traffic in EKS IPv6 cluster +func (c *EgressContext) CmdAddEgressV4() error { + + if err := cniutils.EnableIpForwarding(c.Procsys, c.TmpResult.IPs); err != nil { + return fmt.Errorf("could not enable IP forwarding: %v", err) + } + + // NB: This uses netConf.IfName NOT args.IfName. + hostInterface, _, err := c.SetupContainerVeth() + if err != nil { + c.Log.Debugf("failed to setup container Veth: %v", err) + return err + } + + if err = c.SetupHostVeth(hostInterface.Name); err != nil { + return err + } + + c.Log.Debugf("Node IP: %s", c.NetConf.NodeIP) + if c.NetConf.NodeIP != nil { + for _, ipc := range c.TmpResult.IPs { + if ipc.Address.IP.To4() != nil { + // add SNAT chain/rules necessary for the container IPv6 egress traffic + if err = snat.Add(c.IpTablesIface, c.NetConf.NodeIP, ipc.Address.IP, ipv4MulticastRange, c.SnatChain, c.SnatComment, c.NetConf.RandomizeSNAT); err != nil { + return err + } + } + } + } + + // Copy interfaces over to result, but not IPs. + c.Result.Interfaces = append(c.Result.Interfaces, c.TmpResult.Interfaces...) + + // Pass through the previous result + return types.PrintResult(c.Result, c.NetConf.CNIVersion) +} + +// CmdDelEgressV4 exec clear the setting to support IPv4 egress traffic in EKS IPv6 cluster +func (c *EgressContext) CmdDelEgressV4() error { + var ipnets []*net.IPNet + + if c.NsPath != "" { + err := c.Ns.WithNetNSPath(c.NsPath, func(hostNS ns.NetNS) error { + var err error + + // DelLinkByNameAddr function deletes an interface and returns IPs assigned to it but it + // excludes IPs that are not global unicast addresses (or) private IPs. Will not work for + // our scenario as we use 169.254.0.0/16 range for v4 IPs. + + //Get the interface we want to delete + iface, err := c.Link.LinkByName(c.NetConf.IfName) + + if err != nil { + if _, ok := err.(netlink.LinkNotFoundError); ok { + return nil + } + return nil + } + + //Retrieve IP addresses assigned to the interface + addrs, err := c.Link.AddrList(iface, netlink.FAMILY_V4) + if err != nil { + return fmt.Errorf("failed to get IP addresses for %q: %v", c.NetConf.IfName, err) + } + + //Delete the interface/link. + if err = c.Link.LinkDel(iface); err != nil { + return fmt.Errorf("failed to delete %q: %v", c.NetConf.IfName, err) + } + + for _, addr := range addrs { + ipnets = append(ipnets, addr.IPNet) + } + + if err != nil && err == ip.ErrLinkNotFound { + c.Log.Debugf("DEL: Link Not Found, returning", err) + return nil + } + return err + }) + + //DEL should be best-effort. We should clean up as much as we can and avoid returning error + if err != nil { + c.Log.Debugf("DEL: Executing in container ns errored out, returning", err) + } + } + + if c.NetConf.NodeIP != nil { + c.Log.Debugf("DEL: SNAT setup, let's clean them up. Size of ipnets: %d", len(ipnets)) + for _, ipn := range ipnets { + if err := snat.Del(c.IpTablesIface, ipn.IP, c.SnatChain, c.SnatComment); err != nil { + return err + } + } + } + + return nil +} diff --git a/cmd/egress-cni-plugin/main.go b/cmd/egress-cni-plugin/main.go new file mode 100644 index 0000000000..69e1664e23 --- /dev/null +++ b/cmd/egress-cni-plugin/main.go @@ -0,0 +1,152 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package main + +import ( + "fmt" + "runtime" + "strconv" + + "github.com/coreos/go-iptables/iptables" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + cniversion "github.com/containernetworking/cni/pkg/version" + "github.com/containernetworking/plugins/pkg/utils" +) + +var version string + +func init() { + // this ensures that main runs only on main thread (thread group leader). + // since namespace ops (unshare, setns) are done for a single thread, we + // must ensure that the goroutine does not jump from OS thread to thread + runtime.LockOSThread() +} + +func main() { + skel.PluginMain(cmdAdd, nil, cmdDel, cniversion.All, fmt.Sprintf("egress CNI plugin %s", version)) +} + +func cmdAdd(args *skel.CmdArgs) error { + ec := NewEgressAddContext(args.Netns, args.IfName) + return add(args, &ec) +} + +func add(args *skel.CmdArgs, ec *EgressContext) (err error) { + ec.NetConf, ec.Log, err = LoadConf(args.StdinData) + if err != nil { + return fmt.Errorf("failed to parse config: %v", err) + } + if ec.NetConf.PrevResult == nil { + ec.Log.Debugf("must be called as a chained plugin") + return fmt.Errorf("must be called as a chained plugin") + } + + ec.Result, err = current.GetResult(ec.NetConf.PrevResult) + if err != nil { + ec.Log.Errorf("failed to get PrevResult: %v", err) + return err + } + // Convert MTU from string to int + ec.Mtu, err = strconv.Atoi(ec.NetConf.MTU) + if err != nil { + ec.Log.Errorf("failed to parse MTU: %s, err: %v", ec.NetConf.MTU, err) + return err + } + + ec.Log.Debugf("Received an ADD request for: conf=%v; Plugin enabled=%s", ec.NetConf, ec.NetConf.Enabled) + // We will not be vending out this as a separate plugin by itself, and it is only intended to be used as a + // chained plugin to VPC CNI. We only need this plugin to kick in if egress is enabled in VPC CNI. So, the + // value of an env variable in VPC CNI determines whether this plugin should be enabled and this is an attempt to + // pass through the variable configured in VPC CNI. + if ec.NetConf.Enabled != "true" { + return types.PrintResult(ec.Result, ec.NetConf.CNIVersion) + } + + if ec.IpTablesIface == nil { + if ec.IpTablesIface, err = ec.IptCreator(iptables.ProtocolIPv4); err != nil { + ec.Log.Error("command iptables not found") + return err + } + } + + ec.SnatChain = utils.MustFormatChainNameWithPrefix(ec.NetConf.Name, args.ContainerID, "E4-") + ec.SnatComment = utils.FormatComment(ec.NetConf.Name, args.ContainerID) + + // Invoke ipam del if err to avoid ip leak + defer func() { + if err != nil { + ec.Ipam.ExecDel(ec.NetConf.IPAM.Type, args.StdinData) + } + }() + + var ipamResultI types.Result + if ipamResultI, err = ec.Ipam.ExecAdd(ec.NetConf.IPAM.Type, args.StdinData); err != nil { + return fmt.Errorf("running IPAM plugin failed: %v", err) + } + + if ec.TmpResult, err = current.NewResultFromResult(ipamResultI); err != nil { + return err + } + + if len(ec.TmpResult.IPs) == 0 { + err = fmt.Errorf("IPAM plugin returned zero IPs") + return err + } + + // IPv4 egress + ec.NetConf.IfName = EgressIPv4InterfaceName + // explicitly set err var so that above defer function can call ipam.ExecDel + err = ec.CmdAddEgressV4() + return err +} + +func cmdDel(args *skel.CmdArgs) error { + ec := NewEgressDelContext(args.Netns) + return del(args, &ec) +} + +func del(args *skel.CmdArgs, ec *EgressContext) (err error) { + ec.NetConf, ec.Log, err = LoadConf(args.StdinData) + if err != nil { + return fmt.Errorf("failed to parse config: %v", err) + } + + // We only need this plugin to kick in if egress is enabled + if ec.NetConf.Enabled != "true" { + ec.Log.Debugf("egress-cni plugin is disabled") + return nil + } + ec.Log.Debugf("Received Del Request: nsPath: %s conf=%v", ec.NsPath, ec.NetConf) + + if ec.IpTablesIface == nil { + if ec.IpTablesIface, err = ec.IptCreator(iptables.ProtocolIPv4); err != nil { + ec.Log.Error("command iptables not found") + } + } + + if err = ec.Ipam.ExecDel(ec.NetConf.IPAM.Type, args.StdinData); err != nil { + ec.Log.Debugf("running IPAM plugin failed: %v", err) + return fmt.Errorf("running IPAM plugin failed: %v", err) + } + + ec.SnatChain = utils.MustFormatChainNameWithPrefix(ec.NetConf.Name, args.ContainerID, "E4-") + ec.SnatComment = utils.FormatComment(ec.NetConf.Name, args.ContainerID) + + // IPv4 egress + ec.NetConf.IfName = EgressIPv4InterfaceName + return ec.CmdDelEgressV4() +} diff --git a/cmd/egress-cni-plugin/main_test.go b/cmd/egress-cni-plugin/main_test.go new file mode 100644 index 0000000000..57ec8290d7 --- /dev/null +++ b/cmd/egress-cni-plugin/main_test.go @@ -0,0 +1,163 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package main + +import ( + "fmt" + "testing" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + + mock_ipamwrapper "github.com/aws/amazon-vpc-cni-k8s/pkg/hostipamwrapper/mocks" + mock_iptables "github.com/aws/amazon-vpc-cni-k8s/pkg/iptableswrapper/mocks" + mock_netlinkwrapper "github.com/aws/amazon-vpc-cni-k8s/pkg/netlinkwrapper/mocks" + mock_nswrapper "github.com/aws/amazon-vpc-cni-k8s/pkg/nswrapper/mocks" + mock_procsyswrapper "github.com/aws/amazon-vpc-cni-k8s/pkg/procsyswrapper/mocks" + mock_veth "github.com/aws/amazon-vpc-cni-k8s/pkg/vethwrapper/mocks" +) + +const ( + snatChainV4 = "CNI-E4-5740307710ebfd5fb24f5" +) + +func TestCmdAddV4(t *testing.T) { + ctrl := gomock.NewController(t) + + args := &skel.CmdArgs{ + ContainerID: "containerId-123", + IfName: "eth0", + StdinData: []byte(`{ + "cniVersion":"0.4.0", + "mtu":"9001", + "name":"aws-cni", + "enabled":"true", + "nodeIP": "192.168.1.123", + "ipam": {"type":"host-local","ranges":[[{"subnet": "169.254.172.0/22"}]],"routes":[{"dst":"0.0.0.0"}],"dataDir":"/run/cni/v6pd/egress-v4-ipam"}, + "pluginLogFile":"egress-plugin.log", + "pluginLogLevel":"DEBUG", + "podSGEnforcingMode":"strict", + "prevResult": + { + "cniVersion":"0.4.0", + "interfaces": + [ + {"name":"eni36e5b0ee702"}, + {"name":"eth0","sandbox":"/var/run/netns/cni-266298c1-b141-9c7f-f26b-97ff084f3fcc"}, + {"name":"dummy36e5b0ee702","mac":"0","sandbox":"0"}], + "ips": + [{"version":"4","interface":1,"address":"192.168.13.226/32"}], + "dns":{} + }, + "type":"aws-cni", + "vethPrefix":"eni" + }`), + } + + ec := EgressContext{ + Procsys: mock_procsyswrapper.NewMockProcSys(ctrl), + Ns: mock_nswrapper.NewMockNS(ctrl), + NsPath: "/var/run/netns/cni-xxxx", + ArgsIfName: args.IfName, + IpTablesIface: mock_iptables.NewMockIPTablesIface(ctrl), + Ipam: mock_ipamwrapper.NewMockHostIpam(ctrl), + Link: mock_netlinkwrapper.NewMockNetLink(ctrl), + Veth: mock_veth.NewMockVeth(ctrl), + } + + var actualIptablesRules, actualRouteAdd, actualRouteDel []string + err := SetupAddExpectV4(ec, snatChainV4, &actualIptablesRules, &actualRouteAdd, &actualRouteDel) + assert.Nil(t, err) + + err = add(args, &ec) + assert.Nil(t, err) + + expectIptablesRules := []string{ + fmt.Sprintf("nat %s -d 224.0.0.0/4 -j ACCEPT -m comment --comment name: \"aws-cni\" id: \"containerId-123\"", snatChainV4), + fmt.Sprintf("nat %s -j SNAT --to-source 192.168.1.123 -m comment --comment name: \"aws-cni\" id: \"containerId-123\" --random-fully", snatChainV4), + fmt.Sprintf("nat POSTROUTING -s 169.254.172.10 -j %s -m comment --comment name: \"aws-cni\" id: \"containerId-123\"", snatChainV4)} + assert.EqualValues(t, expectIptablesRules, actualIptablesRules) + + expectRouteDel := []string{"route del: {Ifindex: 2 Dst: 169.254.172.0/22 Src: Gw: Flags: [] Table: 0}"} + assert.EqualValues(t, expectRouteDel, actualRouteDel) + + expectRouteAdd := []string{ + "route add: {Ifindex: 2 Dst: 169.254.172.1/32 Src: 169.254.172.10 Gw: Flags: [] Table: 0}", + "route add: {Ifindex: 2 Dst: 169.254.172.0/22 Src: 169.254.172.10 Gw: 169.254.172.1 Flags: [] Table: 0}", + "route add: {Ifindex: 100 Dst: 169.254.172.10/32 Src: Gw: Flags: [] Table: 0}"} + assert.EqualValues(t, expectRouteAdd, actualRouteAdd) + + // the unit test write some output string not ends with '\n' and this cause go runner unable to interpret that a test was run. + // Adding a newline, keeps a clean output + fmt.Println() +} + +func TestCmdDelV4(t *testing.T) { + ctrl := gomock.NewController(t) + + args := &skel.CmdArgs{ + ContainerID: "containerId-123", + IfName: "eth0", + StdinData: []byte(`{ + "cniVersion":"0.4.0", + "mtu":"9001", + "name":"aws-cni", + "enabled":"true", + "nodeIP": "192.168.1.123", + "ipam": {"type":"host-local","ranges":[[{"subnet": "169.254.172.0/22"}]],"routes":[{"dst":"0.0.0.0"}],"dataDir":"/run/cni/v6pd/egress-v4-ipam"}, + "pluginLogFile":"egress-plugin.log", + "pluginLogLevel":"DEBUG", + "podSGEnforcingMode":"strict", + "prevResult": + { + "cniVersion":"0.4.0", + "interfaces": + [ + {"name":"eni36e5b0ee702"}, + {"name":"eth0","sandbox":"/var/run/netns/cni-266298c1-b141-9c7f-f26b-97ff084f3fcc"}, + {"name":"dummy36e5b0ee702","mac":"0","sandbox":"0"}], + "ips": + [{"version":"4","interface":1,"address":"192.168.13.226/32"}], + "dns":{} + }, + "type":"aws-cni", + "vethPrefix":"eni" + }`), + } + + ec := EgressContext{ + Ns: mock_nswrapper.NewMockNS(ctrl), + NsPath: "/var/run/netns/cni-xxxx", + IpTablesIface: mock_iptables.NewMockIPTablesIface(ctrl), + Ipam: mock_ipamwrapper.NewMockHostIpam(ctrl), + Link: mock_netlinkwrapper.NewMockNetLink(ctrl), + } + + var actualLinkDel, actualIptablesDel []string + err := SetupDelExpectV4(ec, &actualLinkDel, &actualIptablesDel) + assert.Nil(t, err) + + err = del(args, &ec) + assert.Nil(t, err) + + expectLinkDel := []string{"link del - name: v4if0"} + assert.EqualValues(t, expectLinkDel, actualLinkDel) + + expectIptablesDel := []string{ + fmt.Sprintf("nat POSTROUTING -s 169.254.172.10 -j %s -m comment --comment name: \"aws-cni\" id: \"containerId-123\"", snatChainV4), + fmt.Sprintf("clear chain nat %s", snatChainV4), + fmt.Sprintf("del chain nat %s", snatChainV4)} + assert.EqualValues(t, expectIptablesDel, actualIptablesDel) +} diff --git a/cmd/egress-cni-plugin/netconf.go b/cmd/egress-cni-plugin/netconf.go new file mode 100644 index 0000000000..2f2bdfb1e3 --- /dev/null +++ b/cmd/egress-cni-plugin/netconf.go @@ -0,0 +1,76 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package main + +import ( + "encoding/json" + "fmt" + "net" + + "github.com/containernetworking/cni/pkg/types" + cniversion "github.com/containernetworking/cni/pkg/version" + + "github.com/aws/amazon-vpc-cni-k8s/pkg/utils/logger" +) + +const ( + // EgressIPv4InterfaceName interface name used in container ns for IPv4 egress traffic + EgressIPv4InterfaceName = "v4if0" + + // EgressIPv6InterfaceName interface name used in container ns for IPv6 egress traffic + EgressIPv6InterfaceName = "v6if0" +) + +// NetConf is our CNI config structure +type NetConf struct { + types.NetConf + + // Interface inside container to create + IfName string `json:"ifName"` + + // MTU for Egress v4 interface + MTU string `json:"mtu"` + + Enabled string `json:"enabled"` + + RandomizeSNAT string `json:"randomizeSNAT"` + + // IP to use as SNAT target + NodeIP net.IP `json:"nodeIP"` + + PluginLogFile string `json:"pluginLogFile"` + PluginLogLevel string `json:"pluginLogLevel"` +} + +// LoadConf load stdin and parse to NetConf type, a new log instance is created based on conf settings +func LoadConf(bytes []byte) (*NetConf, logger.Logger, error) { + conf := &NetConf{} + + if err := json.Unmarshal(bytes, conf); err != nil { + return nil, nil, err + } + + if conf.RawPrevResult != nil { + if err := cniversion.ParsePrevResult(&conf.NetConf); err != nil { + return nil, nil, fmt.Errorf("could not parse prevResult: %v", err) + } + } + + logConfig := logger.Configuration{ + LogLevel: conf.PluginLogLevel, + LogLocation: conf.PluginLogFile, + } + log := logger.New(&logConfig) + return conf, log, nil +} diff --git a/cmd/egress-v4-cni-plugin/snat/snat.go b/cmd/egress-cni-plugin/snat/snat.go similarity index 64% rename from cmd/egress-v4-cni-plugin/snat/snat.go rename to cmd/egress-cni-plugin/snat/snat.go index 8e741039ff..63558c4420 100644 --- a/cmd/egress-v4-cni-plugin/snat/snat.go +++ b/cmd/egress-cni-plugin/snat/snat.go @@ -14,17 +14,18 @@ package snat import ( - "fmt" "net" "github.com/coreos/go-iptables/iptables" + + "github.com/aws/amazon-vpc-cni-k8s/pkg/iptableswrapper" ) -func iptRules4(target, src net.IP, chain, comment string, useRandomFully, useHashRandom bool) [][]string { +func iptRules(target, src net.IP, multicastRange, chain, comment string, useRandomFully, useHashRandom bool) [][]string { var rules [][]string // Accept/ignore multicast (just because we can) - rules = append(rules, []string{chain, "-d", "224.0.0.0/4", "-j", "ACCEPT", "-m", "comment", "--comment", comment}) + rules = append(rules, []string{chain, "-d", multicastRange, "-j", "ACCEPT", "-m", "comment", "--comment", comment}) // SNAT args := []string{ @@ -46,24 +47,19 @@ func iptRules4(target, src net.IP, chain, comment string, useRandomFully, useHas return rules } -// Snat4 SNATs IPv4 connections from `src` to `target` -func Snat4(target, src net.IP, chain, comment, randomizeSNAT string) error { - ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4) - if err != nil { - return fmt.Errorf("failed to locate iptables: %v", err) - } - +// Add NAT entries to iptables for POD egress IPv6/IPv4 traffic +func Add(ipt iptableswrapper.IPTablesIface, nodeIP, src net.IP, multicastRange, chain, comment, rndSNAT string) error { //Defaults to `random-fully` unless a different option is explicitly set via //`AWS_VPC_K8S_CNI_RANDOMIZESNAT`. If the underlying iptables version doesn't support //'random-fully`, we will fall back to `random`. useRandomFully, useHashRandom := true, false - if randomizeSNAT == "none" { + if rndSNAT == "none" { useRandomFully = false - } else if randomizeSNAT == "hashrandom" || !ipt.HasRandomFully() { + } else if rndSNAT == "hashrandom" || !ipt.HasRandomFully() { useHashRandom, useRandomFully = true, false } - rules := iptRules4(target, src, chain, comment, useRandomFully, useHashRandom) + rules := iptRules(nodeIP, src, multicastRange, chain, comment, useRandomFully, useHashRandom) chains, err := ipt.ListChains("nat") if err != nil { @@ -75,18 +71,18 @@ func Snat4(target, src net.IP, chain, comment, randomizeSNAT string) error { } for _, rule := range rules { - chain := rule[0] - if !existingChains[chain] { - if err = ipt.NewChain("nat", chain); err != nil { + _chain := rule[0] + if !existingChains[_chain] { + if err = ipt.NewChain("nat", _chain); err != nil { return err } - existingChains[chain] = true + existingChains[_chain] = true } } for _, rule := range rules { - chain := rule[0] - if err := ipt.AppendUnique("nat", chain, rule[1:]...); err != nil { + _chain := rule[0] + if err = ipt.AppendUnique("nat", _chain, rule[1:]...); err != nil { return err } } @@ -94,14 +90,9 @@ func Snat4(target, src net.IP, chain, comment, randomizeSNAT string) error { return nil } -// Snat4Del removes rules added by snat4 -func Snat4Del(src net.IP, chain, comment string) error { - ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4) - if err != nil { - return fmt.Errorf("failed to locate iptables: %v", err) - } - - err = ipt.Delete("nat", "POSTROUTING", "-s", src.String(), "-j", chain, "-m", "comment", "--comment", comment) +// Del removes rules added by snat +func Del(ipt iptableswrapper.IPTablesIface, src net.IP, chain, comment string) error { + err := ipt.Delete("nat", "POSTROUTING", "-s", src.String(), "-j", chain, "-m", "comment", "--comment", comment) if err != nil && !isNotExist(err) { return err } diff --git a/cmd/egress-cni-plugin/snat/snat_test.go b/cmd/egress-cni-plugin/snat/snat_test.go new file mode 100644 index 0000000000..9631ccb990 --- /dev/null +++ b/cmd/egress-cni-plugin/snat/snat_test.go @@ -0,0 +1,126 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package snat + +import ( + "fmt" + "net" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + + "github.com/aws/amazon-vpc-cni-k8s/pkg/iptableswrapper" + mock_iptables "github.com/aws/amazon-vpc-cni-k8s/pkg/iptableswrapper/mocks" +) + +const ( + //ipv6MulticastRange = "ff00::/8" + ipv4MulticastRange = "224.0.0.0/4" + + chainV4 = "CNI-E4" + //chainV6 = "CNI-E6" + comment = "unit-test-comment" + rndSNAT = "hashrandom" +) + +var ( + //containerIpv6 = net.ParseIP("fd00::10") + //nodeIPv6 = net.ParseIP("2600::") + containerIPv4 = net.ParseIP("169.254.172.10") + nodeIPv4 = net.ParseIP("192.168.1.123") +) + +func TestAddV4(t *testing.T) { + ipt := mock_iptables.NewMockIPTablesIface(gomock.NewController(t)) + + expectChain := []string{chainV4} + actualChain := []string{} + + expectRule := []string{ + fmt.Sprintf("nat %s -d %s -j ACCEPT -m comment --comment %s", chainV4, ipv4MulticastRange, comment), + fmt.Sprintf("nat %s -j SNAT --to-source %s -m comment --comment %s --random", chainV4, nodeIPv4.String(), comment), + fmt.Sprintf("nat POSTROUTING -s %s -j %s -m comment --comment %s", containerIPv4.String(), chainV4, comment), + } + actualRule := []string{} + + setupAddExpect(ipt, &actualChain, &actualRule) + + err := Add(ipt, nodeIPv4, containerIPv4, ipv4MulticastRange, chainV4, comment, rndSNAT) + assert.Nil(t, err) + + assert.EqualValuesf(t, expectChain, actualChain, "iptables chain is expected to be created") + + assert.EqualValuesf(t, expectRule, actualRule, "iptables rules are expected to be created") +} + +func TestDelV4(t *testing.T) { + ipt := mock_iptables.NewMockIPTablesIface(gomock.NewController(t)) + + expectClearChain := []string{chainV4} + actualClearChain := []string{} + + expectDeleteChain := []string{chainV4} + actualDeleteChain := []string{} + + expectRule := []string{fmt.Sprintf("nat POSTROUTING -s %s -j %s -m comment --comment %s", containerIPv4.String(), chainV4, comment)} + actualRule := []string{} + + setupDelExpect(ipt, &actualClearChain, &actualDeleteChain, &actualRule) + + err := Del(ipt, containerIPv4, chainV4, comment) + assert.Nil(t, err) + + assert.EqualValuesf(t, expectClearChain, actualClearChain, "iptables chain is expected to be cleared") + + assert.EqualValuesf(t, expectDeleteChain, actualDeleteChain, "iptables chain is expected to be removed") + + assert.EqualValuesf(t, expectRule, actualRule, "iptables rule is expected to be removed") +} + +func setupAddExpect(ipt iptableswrapper.IPTablesIface, actualNewChain, actualNewRule *[]string) { + ipt.(*mock_iptables.MockIPTablesIface).EXPECT().ListChains("nat").Return( + []string{"POSTROUTING"}, nil) + + ipt.(*mock_iptables.MockIPTablesIface).EXPECT().NewChain("nat", gomock.Any()).Do(func(_, arg1 interface{}) { + chain := arg1.(string) + *actualNewChain = append(*actualNewChain, chain) + }).Return(nil) + + ipt.(*mock_iptables.MockIPTablesIface).EXPECT().AppendUnique("nat", gomock.Any(), gomock.Any()).Do(func(arg1, arg2 interface{}, arg3 ...interface{}) { + rule := arg1.(string) + " " + arg2.(string) + for _, arg := range arg3 { + rule += " " + arg.(string) + } + *actualNewRule = append(*actualNewRule, rule) + }).Return(nil).AnyTimes() +} + +func setupDelExpect(ipt iptableswrapper.IPTablesIface, actualClearChain, actualDeleteChain, actualRule *[]string) { + ipt.(*mock_iptables.MockIPTablesIface).EXPECT().ClearChain("nat", gomock.Any()).Do(func(_, arg2 interface{}) { + *actualClearChain = append(*actualClearChain, arg2.(string)) + }).Return(nil) + + ipt.(*mock_iptables.MockIPTablesIface).EXPECT().DeleteChain("nat", gomock.Any()).Do(func(_, arg2 interface{}) { + *actualDeleteChain = append(*actualDeleteChain, arg2.(string)) + }).Return(nil) + + ipt.(*mock_iptables.MockIPTablesIface).EXPECT().Delete("nat", gomock.Any(), gomock.Any()).Do(func(arg1, arg2 interface{}, arg3 ...interface{}) { + rule := arg1.(string) + " " + arg2.(string) + for _, arg := range arg3 { + rule += " " + arg.(string) + } + *actualRule = append(*actualRule, rule) + }).Return(nil).AnyTimes() +} diff --git a/cmd/egress-cni-plugin/test_utils.go b/cmd/egress-cni-plugin/test_utils.go new file mode 100644 index 0000000000..900f1d9a2a --- /dev/null +++ b/cmd/egress-cni-plugin/test_utils.go @@ -0,0 +1,186 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package main + +import ( + "net" + + "github.com/containernetworking/cni/pkg/types/current" + _ns "github.com/containernetworking/plugins/pkg/ns" + "github.com/golang/mock/gomock" + "github.com/vishvananda/netlink" + + mock_ipam "github.com/aws/amazon-vpc-cni-k8s/pkg/hostipamwrapper/mocks" + mock_iptables "github.com/aws/amazon-vpc-cni-k8s/pkg/iptableswrapper/mocks" + mock_netlink "github.com/aws/amazon-vpc-cni-k8s/pkg/netlinkwrapper/mocks" + mock_ns "github.com/aws/amazon-vpc-cni-k8s/pkg/nswrapper/mocks" + mock_procsys "github.com/aws/amazon-vpc-cni-k8s/pkg/procsyswrapper/mocks" + mock_veth "github.com/aws/amazon-vpc-cni-k8s/pkg/vethwrapper/mocks" +) + +const ( + // HostIfName is the interface name in host network namespace, it starts with veth + HostIfName = "vethxxxx" +) + +// SetupAddExpectV4 has all the mock EXPECT required when a container is added +func SetupAddExpectV4(c EgressContext, chain string, actualIptablesRules, actualRouteAdd, actualRouteDel *[]string) error { + nsParent, err := _ns.GetCurrentNS() + if err != nil { + return err + } + + c.Ipam.(*mock_ipam.MockHostIpam).EXPECT().ExecAdd("host-local", gomock.Any()).Return( + ¤t.Result{ + CNIVersion: "0.4.0", + IPs: []*current.IPConfig{ + ¤t.IPConfig{ + Version: "4", + Address: net.IPNet{ + IP: net.ParseIP("169.254.172.10"), + Mask: net.CIDRMask(22, 32), + }, + Gateway: net.ParseIP("169.254.172.1"), + }, + }, + }, nil) + + c.IpTablesIface.(*mock_iptables.MockIPTablesIface).EXPECT().NewChain("nat", chain).Return(nil) + + macHost := [6]byte{0xCB, 0xB8, 0x33, 0x4C, 0x88, 0x4F} + macCont := [6]byte{0xCC, 0xB8, 0x33, 0x4C, 0x88, 0x4F} + + c.Ns.(*mock_ns.MockNS).EXPECT().WithNetNSPath(c.NsPath, gomock.Any()).Do(func(_nsPath string, f func(_ns.NetNS) error) { + f(nsParent) + }).Return(nil) + + c.Veth.(*mock_veth.MockVeth).EXPECT().Setup(EgressIPv4InterfaceName, 9001, gomock.Any()).Return( + net.Interface{ + Name: HostIfName, + HardwareAddr: macHost[:], + }, + net.Interface{ + Name: EgressIPv4InterfaceName, + HardwareAddr: macCont[:], + }, + nil) + + c.Link.(*mock_netlink.MockNetLink).EXPECT().AddrAdd(gomock.Any(), gomock.Any()).Return(nil) + + c.Link.(*mock_netlink.MockNetLink).EXPECT().LinkByName(HostIfName).Return( + &netlink.Veth{ + LinkAttrs: netlink.LinkAttrs{ + Name: HostIfName, + Index: 100, + }, + }, nil) + c.Link.(*mock_netlink.MockNetLink).EXPECT().LinkByName(EgressIPv4InterfaceName).Return( + &netlink.Veth{ + LinkAttrs: netlink.LinkAttrs{ + Name: EgressIPv4InterfaceName, + Index: 2, + }, + }, nil) + c.Link.(*mock_netlink.MockNetLink).EXPECT().RouteDel(gomock.Any()).Do(func(arg1 interface{}) error { + r := arg1.(*netlink.Route) + *actualRouteDel = append(*actualRouteDel, "route del: "+r.String()) + return nil + }).Return(nil) + + c.Link.(*mock_netlink.MockNetLink).EXPECT().RouteAdd(gomock.Any()).Do(func(arg1 interface{}) error { + r := arg1.(*netlink.Route) + // container route adding + *actualRouteAdd = append(*actualRouteAdd, "route add: "+r.String()) + return nil + }).Return(nil).Times(3) + + c.Ipam.(*mock_ipam.MockHostIpam).EXPECT().ConfigureIface(EgressIPv4InterfaceName, gomock.Any()).Return(nil) + c.Procsys.(*mock_procsys.MockProcSys).EXPECT().Get("net/ipv4/ip_forward").Return("0", nil) + c.Procsys.(*mock_procsys.MockProcSys).EXPECT().Set("net/ipv4/ip_forward", "1").Return(nil) + c.IpTablesIface.(*mock_iptables.MockIPTablesIface).EXPECT().HasRandomFully().Return(true) + c.IpTablesIface.(*mock_iptables.MockIPTablesIface).EXPECT().ListChains("nat").Return([]string{"POSTROUTING"}, nil) + + c.IpTablesIface.(*mock_iptables.MockIPTablesIface).EXPECT().AppendUnique("nat", gomock.Any(), gomock.Any()).Do(func(arg1, arg2 interface{}, arg3 ...interface{}) { + actualResult := arg1.(string) + " " + arg2.(string) + for _, arg := range arg3 { + actualResult += " " + arg.(string) + } + *actualIptablesRules = append(*actualIptablesRules, actualResult) + }).Return(nil).Times(3) + + return nil +} + +// SetupDelExpectV4 has all the mock EXPECT required when a container is deleted +func SetupDelExpectV4(c EgressContext, actualLinkDel, actualIptablesDel *[]string) error { + nsParent, err := _ns.GetCurrentNS() + if err != nil { + return err + } + + c.Ipam.(*mock_ipam.MockHostIpam).EXPECT().ExecDel("host-local", gomock.Any()).Return(nil) + + c.Ns.(*mock_ns.MockNS).EXPECT().WithNetNSPath(c.NsPath, gomock.Any()).Do(func(_nsPath string, f func(_ns.NetNS) error) { + f(nsParent) + }).Return(nil) + + c.Link.(*mock_netlink.MockNetLink).EXPECT().LinkByName(EgressIPv4InterfaceName).Return( + &netlink.Veth{ + LinkAttrs: netlink.LinkAttrs{ + Name: EgressIPv4InterfaceName, + Index: 2, + }, + }, nil) + + c.Link.(*mock_netlink.MockNetLink).EXPECT().AddrList(gomock.Any(), netlink.FAMILY_V4).Return( + []netlink.Addr{ + { + IPNet: &net.IPNet{ + IP: net.ParseIP("169.254.172.10"), + Mask: net.CIDRMask(22, 32), + }, + LinkIndex: 2, + }, + }, nil) + + c.Link.(*mock_netlink.MockNetLink).EXPECT().LinkDel(gomock.Any()).Do( + func(arg1 interface{}) error { + link := arg1.(netlink.Link) + *actualLinkDel = append(*actualLinkDel, "link del - name: "+link.Attrs().Name) + return nil + }).Return(nil) + + c.IpTablesIface.(*mock_iptables.MockIPTablesIface).EXPECT().Delete("nat", "POSTROUTING", gomock.Any()).Do( + func(arg1 interface{}, arg2 interface{}, arg3 ...interface{}) { + actualResult := arg1.(string) + " " + arg2.(string) + for _, arg := range arg3 { + actualResult += " " + arg.(string) + } + *actualIptablesDel = append(*actualIptablesDel, actualResult) + }).Return(nil).AnyTimes() + + c.IpTablesIface.(*mock_iptables.MockIPTablesIface).EXPECT().ClearChain("nat", gomock.Any()).Do( + func(arg1 interface{}, arg2 interface{}) { + actualResult := arg1.(string) + " " + arg2.(string) + *actualIptablesDel = append(*actualIptablesDel, "clear chain "+actualResult) + }).Return(nil).AnyTimes() + + c.IpTablesIface.(*mock_iptables.MockIPTablesIface).EXPECT().DeleteChain("nat", gomock.Any()).Do( + func(arg1 interface{}, arg2 interface{}) { + actualResult := arg1.(string) + " " + arg2.(string) + *actualIptablesDel = append(*actualIptablesDel, "del chain "+actualResult) + }).Return(nil).AnyTimes() + + return nil +} diff --git a/cmd/egress-v4-cni-plugin/cni.go b/cmd/egress-v4-cni-plugin/cni.go deleted file mode 100644 index 68990937a4..0000000000 --- a/cmd/egress-v4-cni-plugin/cni.go +++ /dev/null @@ -1,430 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at -// -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. - -package main - -import ( - "encoding/json" - "fmt" - "net" - "os" - "runtime" - "strconv" - - "github.com/aws/amazon-vpc-cni-k8s/pkg/utils/logger" - - "github.com/containernetworking/cni/pkg/skel" - "github.com/containernetworking/cni/pkg/types" - "github.com/containernetworking/cni/pkg/types/current" - cniversion "github.com/containernetworking/cni/pkg/version" - "github.com/containernetworking/plugins/pkg/ip" - "github.com/containernetworking/plugins/pkg/ipam" - "github.com/containernetworking/plugins/pkg/ns" - "github.com/containernetworking/plugins/pkg/utils" - "github.com/vishvananda/netlink" - - "github.com/aws/amazon-vpc-cni-k8s/cmd/egress-v4-cni-plugin/snat" -) - -var version string - -func init() { - // this ensures that main runs only on main thread (thread group leader). - // since namespace ops (unshare, setns) are done for a single thread, we - // must ensure that the goroutine does not jump from OS thread to thread - runtime.LockOSThread() -} - -// NetConf is our CNI config structure -type NetConf struct { - types.NetConf - - // Interface inside container to create - IfName string `json:"ifName"` - - // MTU for Egress v4 interface - MTU string `json:"mtu"` - - Enabled string `json:"enabled"` - - RandomizeSNAT string `json:"randomizeSNAT"` - - // IP to use as SNAT target - NodeIP net.IP `json:"nodeIP"` - - PluginLogFile string `json:"pluginLogFile"` - PluginLogLevel string `json:"pluginLogLevel"` -} - -func loadConf(bytes []byte) (*NetConf, logger.Logger, error) { - conf := &NetConf{IfName: "v4if0"} - - if err := json.Unmarshal(bytes, conf); err != nil { - return nil, nil, err - } - - if conf.RawPrevResult != nil { - if err := cniversion.ParsePrevResult(&conf.NetConf); err != nil { - return nil, nil, fmt.Errorf("could not parse prevResult: %v", err) - } - } - - logConfig := logger.Configuration{ - LogLevel: conf.PluginLogLevel, - LogLocation: conf.PluginLogFile, - } - log := logger.New(&logConfig) - return conf, log, nil -} - -// The bulk of this file is mostly based on standard ptp CNI plugin. -// -// Note: There are other options, for example we could add a new -// address onto an existing container/host interface. -// -// Unfortunately kubelet's dockershim (at least) ignores the CNI -// result structure, and directly queries the addresses on the -// container's IfName - and then prefers any global v4 address found. -// We do _not_ want our v4 NAT address to become "the" pod IP! -// -// Also, standard `loopback` CNI plugin checks and aborts if it finds -// any global-scope addresses on `lo`, so we can't just do that -// either. -// -// So we have to create a new interface (not args.IfName) to hide our -// NAT address from all this logic (or patch dockershim, or (better) -// just stop using dockerd...). Hence ptp. -// - -func setupContainerVeth(netns ns.NetNS, ifName string, mtu int, pr *current.Result) (*current.Interface, *current.Interface, error) { - // The IPAM result will be something like IP=192.168.3.5/24, GW=192.168.3.1. - // What we want is really a point-to-point link but veth does not support IFF_POINTTOPOINT. - // Next best thing would be to let it ARP but set interface to 192.168.3.5/32 and - // add a route like "192.168.3.0/24 via 192.168.3.1 dev $ifName". - // Unfortunately that won't work as the GW will be outside the interface's subnet. - - // Our solution is to configure the interface with 192.168.3.5/24, then delete the - // "192.168.3.0/24 dev $ifName" route that was automatically added. Then we add - // "192.168.3.1/32 dev $ifName" and "192.168.3.0/24 via 192.168.3.1 dev $ifName". - // In other words we force all traffic to ARP via the gateway except for GW itself. - - hostInterface := ¤t.Interface{} - containerInterface := ¤t.Interface{} - - err := netns.Do(func(hostNS ns.NetNS) error { - hostVeth, contVeth0, err := ip.SetupVeth(ifName, mtu, hostNS) - if err != nil { - return err - } - hostInterface.Name = hostVeth.Name - hostInterface.Mac = hostVeth.HardwareAddr.String() - containerInterface.Name = contVeth0.Name - containerInterface.Mac = contVeth0.HardwareAddr.String() - containerInterface.Sandbox = netns.Path() - - for _, ipc := range pr.IPs { - // All addresses apply to the container veth interface - ipc.Interface = current.Int(1) - } - - pr.Interfaces = []*current.Interface{hostInterface, containerInterface} - - if err = ipam.ConfigureIface(ifName, pr); err != nil { - return err - } - - contVeth, err := net.InterfaceByName(ifName) - if err != nil { - return fmt.Errorf("failed to look up %q: %v", ifName, err) - } - - for _, ipc := range pr.IPs { - // Delete the route that was automatically added - route := netlink.Route{ - LinkIndex: contVeth.Index, - Dst: &net.IPNet{ - IP: ipc.Address.IP.Mask(ipc.Address.Mask), - Mask: ipc.Address.Mask, - }, - Scope: netlink.SCOPE_NOWHERE, - } - - if err := netlink.RouteDel(&route); err != nil { - return fmt.Errorf("failed to delete route %v: %v", route, err) - } - - addrBits := 128 - if ipc.Address.IP.To4() != nil { - addrBits = 32 - } - - for _, r := range []netlink.Route{ - { - LinkIndex: contVeth.Index, - Dst: &net.IPNet{ - IP: ipc.Gateway, - Mask: net.CIDRMask(addrBits, addrBits), - }, - Scope: netlink.SCOPE_LINK, - Src: ipc.Address.IP, - }, - { - LinkIndex: contVeth.Index, - Dst: &net.IPNet{ - IP: ipc.Address.IP.Mask(ipc.Address.Mask), - Mask: ipc.Address.Mask, - }, - Scope: netlink.SCOPE_UNIVERSE, - Gw: ipc.Gateway, - Src: ipc.Address.IP, - }, - } { - if err := netlink.RouteAdd(&r); err != nil { - return fmt.Errorf("failed to add route %v: %v", r, err) - } - } - } - return nil - }) - if err != nil { - return nil, nil, err - } - return hostInterface, containerInterface, nil -} - -func setupHostVeth(vethName string, result *current.Result) error { - // hostVeth moved namespaces and may have a new ifindex - veth, err := netlink.LinkByName(vethName) - if err != nil { - return fmt.Errorf("failed to lookup %q: %v", vethName, err) - } - - for _, ipc := range result.IPs { - maskLen := 128 - if ipc.Address.IP.To4() != nil { - maskLen = 32 - } - - // NB: this is modified from standard ptp plugin. - - ipn := &net.IPNet{ - IP: ipc.Gateway, - Mask: net.CIDRMask(maskLen, maskLen), - } - addr := &netlink.Addr{ - IPNet: ipn, - Scope: int(netlink.SCOPE_LINK), // <- ptp uses SCOPE_UNIVERSE here - } - if err = netlink.AddrAdd(veth, addr); err != nil { - return fmt.Errorf("failed to add IP addr (%#v) to veth: %v", ipn, err) - } - - ipn = &net.IPNet{ - IP: ipc.Address.IP, - Mask: net.CIDRMask(maskLen, maskLen), - } - err := netlink.RouteAdd(&netlink.Route{ - LinkIndex: veth.Attrs().Index, - Scope: netlink.SCOPE_LINK, // <- ptp uses SCOPE_HOST here - Dst: ipn, - }) - if err != nil && !os.IsExist(err) { - return fmt.Errorf("failed to add route on host: %v", err) - } - } - - return nil -} - -func main() { - skel.PluginMain(cmdAdd, nil, cmdDel, cniversion.All, fmt.Sprintf("egress-v4 CNI plugin %s", version)) -} - -func cmdAdd(args *skel.CmdArgs) error { - netConf, log, err := loadConf(args.StdinData) - if err != nil { - log.Debugf("Received Add request: Failed to parse config") - return fmt.Errorf("failed to parse config: %v", err) - } - - if netConf.PrevResult == nil { - return fmt.Errorf("must be called as a chained plugin") - } - - result, err := current.GetResult(netConf.PrevResult) - if err != nil { - return err - } - - log.Debugf("Received an ADD request for: conf=%v; Plugin enabled=%s", netConf, netConf.Enabled) - // We will not be vending out this as a separate plugin by itself and it is only intended to be used as a - // chained plugin to VPC CNI in IPv6 mode. We only need this plugin to kick in if v6 is enabled in VPC CNI. So, the - // value of an env variable in VPC CNI determines whether this plugin should be enabled and this is an attempt to - // pass through the variable configured in VPC CNI. - if netConf.Enabled == "false" { - return types.PrintResult(result, netConf.CNIVersion) - } - - chain := utils.MustFormatChainNameWithPrefix(netConf.Name, args.ContainerID, "E4-") - comment := utils.FormatComment(netConf.Name, args.ContainerID) - - ipamResultI, err := ipam.ExecAdd(netConf.IPAM.Type, args.StdinData) - if err != nil { - return fmt.Errorf("running IPAM plugin failed: %v", err) - } - - // Invoke ipam del if err to avoid ip leak - defer func() { - if err != nil { - ipam.ExecDel(netConf.IPAM.Type, args.StdinData) - } - }() - - tmpResult, err := current.NewResultFromResult(ipamResultI) - if err != nil { - return err - } - - if len(tmpResult.IPs) == 0 { - return fmt.Errorf("IPAM plugin returned zero IPs") - } - - if err := ip.EnableForward(tmpResult.IPs); err != nil { - return fmt.Errorf("could not enable IP forwarding: %v", err) - } - - netns, err := ns.GetNS(args.Netns) - if err != nil { - return fmt.Errorf("failed to open netns %q: %v", args.Netns, err) - } - defer netns.Close() - - // Convert MTU from string to int - mtu, err := strconv.Atoi(netConf.MTU) - if err != nil { - log.Debugf("failed to parse MTU: %s, err: %v", netConf.MTU, err) - return err - } - - // NB: This uses netConf.IfName NOT args.IfName. - hostInterface, _, err := setupContainerVeth(netns, netConf.IfName, mtu, tmpResult) - if err != nil { - log.Debugf("failed to setup container Veth: %v", err) - return err - } - - if err = setupHostVeth(hostInterface.Name, tmpResult); err != nil { - return err - } - - log.Debugf("Node IP: %s", netConf.NodeIP) - if netConf.NodeIP != nil { - for _, ipc := range tmpResult.IPs { - if ipc.Address.IP.To4() != nil { - //log.Printf("Configuring SNAT %s -> %s", ipc.Address.IP, netConf.SnatIP) - if err := snat.Snat4(netConf.NodeIP, ipc.Address.IP, chain, comment, netConf.RandomizeSNAT); err != nil { - return err - } - } - } - } - - // Copy interfaces over to result, but not IPs. - result.Interfaces = append(result.Interfaces, tmpResult.Interfaces...) - // Note: Useful for debug, will do away with the below log prior to release - for _, v := range result.IPs { - log.Debugf("Interface Name: %v; IP: %s", v.Interface, v.Address) - } - - // Pass through the previous result - return types.PrintResult(result, netConf.CNIVersion) -} - -func cmdDel(args *skel.CmdArgs) error { - netConf, log, err := loadConf(args.StdinData) - if err != nil { - return fmt.Errorf("failed to parse config: %v", err) - } - - // We only need this plugin to kick in if v6 is enabled - if netConf.Enabled == "false" { - return nil - } - - log.Debugf("Received Del Request: conf=%v", netConf) - if err := ipam.ExecDel(netConf.IPAM.Type, args.StdinData); err != nil { - log.Debugf("running IPAM plugin failed: %v", err) - return fmt.Errorf("running IPAM plugin failed: %v", err) - } - - ipnets := []*net.IPNet{} - if args.Netns != "" { - err := ns.WithNetNSPath(args.Netns, func(hostNS ns.NetNS) error { - var err error - - // DelLinkByNameAddr function deletes an interface and returns IPs assigned to it but it - // excludes IPs that are not global unicast addresses (or) private IPs. Will not work for - // our scenario as we use 169.254.0.0/16 range for v4 IPs. - - //Get the interface we want to delete - iface, err := netlink.LinkByName(netConf.IfName) - - if err != nil { - if _, ok := err.(netlink.LinkNotFoundError); ok { - return nil - } - return nil - } - - //Retrieve IP addresses assigned to the interface - addrs, err := netlink.AddrList(iface, netlink.FAMILY_V4) - if err != nil { - return fmt.Errorf("failed to get IP addresses for %q: %v", netConf.IfName, err) - } - - //Delete the interface/link. - if err = netlink.LinkDel(iface); err != nil { - return fmt.Errorf("failed to delete %q: %v", netConf.IfName, err) - } - - for _, addr := range addrs { - ipnets = append(ipnets, addr.IPNet) - } - - if err != nil && err == ip.ErrLinkNotFound { - log.Debugf("DEL: Link Not Found, returning", err) - return nil - } - return err - }) - - //DEL should be best effort. We should clean up as much as we can and avoid returning error - if err != nil { - log.Debugf("DEL: Executing in container ns errored out, returning", err) - } - } - - chain := utils.MustFormatChainNameWithPrefix(netConf.Name, args.ContainerID, "E4-") - comment := utils.FormatComment(netConf.Name, args.ContainerID) - - if netConf.NodeIP != nil { - log.Debugf("DEL: SNAT setup, let's clean them up. Size of ipnets: %d", len(ipnets)) - for _, ipn := range ipnets { - if err := snat.Snat4Del(ipn.IP, chain, comment); err != nil { - return err - } - } - } - - return nil -} diff --git a/cmd/routed-eni-cni-plugin/driver/driver.go b/cmd/routed-eni-cni-plugin/driver/driver.go index 988af80ea8..720f198bf3 100644 --- a/cmd/routed-eni-cni-plugin/driver/driver.go +++ b/cmd/routed-eni-cni-plugin/driver/driver.go @@ -18,7 +18,6 @@ import ( "fmt" "net" "os" - "syscall" "time" "github.com/aws/amazon-vpc-cni-k8s/pkg/sgpp" @@ -33,6 +32,7 @@ import ( "github.com/aws/amazon-vpc-cni-k8s/pkg/netlinkwrapper" "github.com/aws/amazon-vpc-cni-k8s/pkg/nswrapper" "github.com/aws/amazon-vpc-cni-k8s/pkg/procsyswrapper" + "github.com/aws/amazon-vpc-cni-k8s/pkg/utils/cniutils" "github.com/aws/amazon-vpc-cni-k8s/pkg/utils/logger" ) @@ -220,7 +220,7 @@ func (createVethContext *createVethPairContext) run(hostNS ns.NetNS) error { } if createVethContext.v6Addr != nil && createVethContext.v6Addr.IP.To16() != nil { - if err := waitForAddressesToBeStable(createVethContext.netLink, createVethContext.contVethName, v6DADTimeout); err != nil { + if err := cniutils.WaitForAddressesToBeStable(createVethContext.netLink, createVethContext.contVethName, v6DADTimeout, WAIT_INTERVAL); err != nil { return errors.Wrap(err, "setup NS network: failed while waiting for v6 addresses to be stable") } } @@ -233,44 +233,6 @@ func (createVethContext *createVethPairContext) run(hostNS ns.NetNS) error { return nil } -// Implements `SettleAddresses` functionality of the `ip` package. -// waitForAddressesToBeStable waits for all addresses on a link to leave tentative state. -// Will be particularly useful for ipv6, where all addresses need to do DAD. -// If any addresses are still tentative after timeout seconds, then error. -func waitForAddressesToBeStable(netLink netlinkwrapper.NetLink, ifName string, timeout time.Duration) error { - link, err := netLink.LinkByName(ifName) - if err != nil { - return fmt.Errorf("failed to retrieve link: %v", err) - } - - deadline := time.Now().Add(timeout) - for { - addrs, err := netLink.AddrList(link, netlink.FAMILY_V6) - if err != nil { - return fmt.Errorf("could not list addresses: %v", err) - } - - ok := true - for _, addr := range addrs { - if addr.Flags&(syscall.IFA_F_TENTATIVE|syscall.IFA_F_DADFAILED) > 0 { - ok = false - break - } - } - - if ok { - return nil - } - if time.Now().After(deadline) { - return fmt.Errorf("link %s still has tentative addresses after %d seconds", - ifName, - timeout) - } - - time.Sleep(WAIT_INTERVAL) - } -} - // SetupPodNetwork wires up linux networking for a pod's network // we expect v4Addr and v6Addr to have correct IPAddress Family. func (n *linuxNetwork) SetupPodNetwork(hostVethName string, contVethName string, netnsPath string, v4Addr *net.IPNet, v6Addr *net.IPNet, diff --git a/misc/10-aws.conflist b/misc/10-aws.conflist index 0affce9270..4a54989918 100644 --- a/misc/10-aws.conflist +++ b/misc/10-aws.conflist @@ -13,19 +13,19 @@ "pluginLogLevel": "__PLUGINLOGLEVEL__" }, { - "name": "egress-v4-cni", - "type": "egress-v4-cni", + "name": "egress-cni", + "type": "egress-cni", "mtu": "9001", - "enabled": "__EGRESSV4PLUGINENABLED__", + "enabled": "__EGRESSPLUGINENABLED__", "randomizeSNAT": "__RANDOMIZESNAT__", "nodeIP": "__NODEIP__", "ipam": { "type": "host-local", - "ranges": [[{"subnet": "169.254.172.0/22"}]], - "routes": [{"dst": "0.0.0.0/0"}], - "dataDir": "/run/cni/v6pd/egress-v4-ipam" + "ranges": [[{"subnet": "__EGRESSPLUGINIPAMSUBNET__"}]], + "routes": [{"dst": "__EGRESSPLUGINIPAMDST__"}], + "dataDir": "__EGRESSPLUGINIPAMDATADIR__" }, - "pluginLogFile": "__EGRESSV4PLUGINLOGFILE__", + "pluginLogFile": "__EGRESSPLUGINLOGFILE__", "pluginLogLevel": "__PLUGINLOGLEVEL__" }, { diff --git a/pkg/hostipamwrapper/generate_mocks.go b/pkg/hostipamwrapper/generate_mocks.go new file mode 100644 index 0000000000..a7348b49d6 --- /dev/null +++ b/pkg/hostipamwrapper/generate_mocks.go @@ -0,0 +1,16 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package hostipamwrapper + +//go:generate go run github.com/golang/mock/mockgen -destination mocks/hostipam_mocks.go -copyright_file ../../scripts/copyright.txt . HostIpam diff --git a/pkg/hostipamwrapper/hostipam.go b/pkg/hostipamwrapper/hostipam.go new file mode 100644 index 0000000000..6969f9b20a --- /dev/null +++ b/pkg/hostipamwrapper/hostipam.go @@ -0,0 +1,55 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Package hostipamwrapper is a wrapper method for the hostipam package +package hostipamwrapper + +import ( + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + _ipam "github.com/containernetworking/plugins/pkg/ipam" +) + +// HostIpam is an interface created to make code unit testable. +// Both the hostipam package version and mocked version implement the same interface +type HostIpam interface { + ExecAdd(plugin string, netconf []byte) (types.Result, error) + + ExecCheck(plugin string, netconf []byte) error + + ExecDel(plugin string, netconf []byte) error + + ConfigureIface(ifName string, res *current.Result) error +} + +type hostipam struct{} + +// NewIpam return a new HostIpam object +func NewIpam() HostIpam { + return &hostipam{} +} +func (h *hostipam) ExecAdd(plugin string, netconf []byte) (types.Result, error) { + return _ipam.ExecAdd(plugin, netconf) +} + +func (h *hostipam) ExecCheck(plugin string, netconf []byte) error { + return _ipam.ExecCheck(plugin, netconf) +} + +func (h *hostipam) ExecDel(plugin string, netconf []byte) error { + return _ipam.ExecDel(plugin, netconf) +} + +func (h *hostipam) ConfigureIface(ifName string, res *current.Result) error { + return _ipam.ConfigureIface(ifName, res) +} diff --git a/pkg/hostipamwrapper/mocks/hostipam_mocks.go b/pkg/hostipamwrapper/mocks/hostipam_mocks.go new file mode 100644 index 0000000000..d09cc3a403 --- /dev/null +++ b/pkg/hostipamwrapper/mocks/hostipam_mocks.go @@ -0,0 +1,107 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/aws/amazon-vpc-cni-k8s/pkg/hostipamwrapper (interfaces: HostIpam) + +// Package mock_hostipamwrapper is a generated GoMock package. +package mock_hostipamwrapper + +import ( + reflect "reflect" + + types "github.com/containernetworking/cni/pkg/types" + current "github.com/containernetworking/cni/pkg/types/current" + gomock "github.com/golang/mock/gomock" +) + +// MockHostIpam is a mock of HostIpam interface. +type MockHostIpam struct { + ctrl *gomock.Controller + recorder *MockHostIpamMockRecorder +} + +// MockHostIpamMockRecorder is the mock recorder for MockHostIpam. +type MockHostIpamMockRecorder struct { + mock *MockHostIpam +} + +// NewMockHostIpam creates a new mock instance. +func NewMockHostIpam(ctrl *gomock.Controller) *MockHostIpam { + mock := &MockHostIpam{ctrl: ctrl} + mock.recorder = &MockHostIpamMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHostIpam) EXPECT() *MockHostIpamMockRecorder { + return m.recorder +} + +// ConfigureIface mocks base method. +func (m *MockHostIpam) ConfigureIface(arg0 string, arg1 *current.Result) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ConfigureIface", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ConfigureIface indicates an expected call of ConfigureIface. +func (mr *MockHostIpamMockRecorder) ConfigureIface(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigureIface", reflect.TypeOf((*MockHostIpam)(nil).ConfigureIface), arg0, arg1) +} + +// ExecAdd mocks base method. +func (m *MockHostIpam) ExecAdd(arg0 string, arg1 []byte) (types.Result, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecAdd", arg0, arg1) + ret0, _ := ret[0].(types.Result) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecAdd indicates an expected call of ExecAdd. +func (mr *MockHostIpamMockRecorder) ExecAdd(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecAdd", reflect.TypeOf((*MockHostIpam)(nil).ExecAdd), arg0, arg1) +} + +// ExecCheck mocks base method. +func (m *MockHostIpam) ExecCheck(arg0 string, arg1 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecCheck", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ExecCheck indicates an expected call of ExecCheck. +func (mr *MockHostIpamMockRecorder) ExecCheck(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecCheck", reflect.TypeOf((*MockHostIpam)(nil).ExecCheck), arg0, arg1) +} + +// ExecDel mocks base method. +func (m *MockHostIpam) ExecDel(arg0 string, arg1 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecDel", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ExecDel indicates an expected call of ExecDel. +func (mr *MockHostIpamMockRecorder) ExecDel(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecDel", reflect.TypeOf((*MockHostIpam)(nil).ExecDel), arg0, arg1) +} diff --git a/pkg/iptableswrapper/generate_mocks.go b/pkg/iptableswrapper/generate_mocks.go new file mode 100644 index 0000000000..93414332d0 --- /dev/null +++ b/pkg/iptableswrapper/generate_mocks.go @@ -0,0 +1,16 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package iptableswrapper + +//go:generate go run github.com/golang/mock/mockgen -destination mocks/iptables_mocks.go -copyright_file ../../scripts/copyright.txt . IPTablesIface diff --git a/pkg/iptableswrapper/iptables.go b/pkg/iptableswrapper/iptables.go new file mode 100644 index 0000000000..7f78e1e6fe --- /dev/null +++ b/pkg/iptableswrapper/iptables.go @@ -0,0 +1,104 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Package iptableswrapper is a wrapper interface for the iptables package +package iptableswrapper + +import "github.com/coreos/go-iptables/iptables" + +// IPTablesIface is an interface created to make code unit testable. +// Both the iptables package version and mocked version implement the same interface +type IPTablesIface interface { + Exists(table, chain string, rulespec ...string) (bool, error) + Insert(table, chain string, pos int, rulespec ...string) error + Append(table, chain string, rulespec ...string) error + AppendUnique(table, chain string, rulespec ...string) error + Delete(table, chain string, rulespec ...string) error + List(table, chain string) ([]string, error) + NewChain(table, chain string) error + ClearChain(table, chain string) error + DeleteChain(table, chain string) error + ListChains(table string) ([]string, error) + HasRandomFully() bool +} + +// ipTables is a struct that implements IPTablesIface using iptables package. +type ipTables struct { + ipt *iptables.IPTables +} + +// NewIPTables return a ipTables struct that implements IPTablesIface +func NewIPTables(protocol iptables.Protocol) (IPTablesIface, error) { + ipt, err := iptables.NewWithProtocol(protocol) + if err != nil { + return nil, err + } + return &ipTables{ + ipt: ipt, + }, nil +} + +// Exists implements IPTablesIface interface by calling iptables package +func (i ipTables) Exists(table, chain string, rulespec ...string) (bool, error) { + return i.ipt.Exists(table, chain, rulespec...) +} + +// Insert implements IPTablesIface interface by calling iptables package +func (i ipTables) Insert(table, chain string, pos int, rulespec ...string) error { + return i.ipt.Insert(table, chain, pos, rulespec...) +} + +// Append implements IPTablesIface interface by calling iptables package +func (i ipTables) Append(table, chain string, rulespec ...string) error { + return i.ipt.Append(table, chain, rulespec...) +} + +// AppendUnique implements IPTablesIface interface by calling iptables package +func (i ipTables) AppendUnique(table, chain string, rulespec ...string) error { + return i.ipt.AppendUnique(table, chain, rulespec...) +} + +// Delete implements IPTablesIface interface by calling iptables package +func (i ipTables) Delete(table, chain string, rulespec ...string) error { + return i.ipt.Delete(table, chain, rulespec...) +} + +// List implements IPTablesIface interface by calling iptables package +func (i ipTables) List(table, chain string) ([]string, error) { + return i.ipt.List(table, chain) +} + +// NewChain implements IPTablesIface interface by calling iptables package +func (i ipTables) NewChain(table, chain string) error { + return i.ipt.NewChain(table, chain) +} + +// ClearChain implements IPTablesIface interface by calling iptables package +func (i ipTables) ClearChain(table, chain string) error { + return i.ipt.ClearChain(table, chain) +} + +// DeleteChain implements IPTablesIface interface by calling iptables package +func (i ipTables) DeleteChain(table, chain string) error { + return i.ipt.DeleteChain(table, chain) +} + +// ListChains implements IPTablesIface interface by calling iptables package +func (i ipTables) ListChains(table string) ([]string, error) { + return i.ipt.ListChains(table) +} + +// HasRandomFully implements IPTablesIface interface by calling iptables package +func (i ipTables) HasRandomFully() bool { + return i.ipt.HasRandomFully() +} diff --git a/pkg/iptableswrapper/mocks/iptables_maps.go b/pkg/iptableswrapper/mocks/iptables_maps.go new file mode 100644 index 0000000000..e72a99c8d4 --- /dev/null +++ b/pkg/iptableswrapper/mocks/iptables_maps.go @@ -0,0 +1,130 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package mock_iptableswrapper + +import ( + "fmt" + "reflect" + "strings" + + "github.com/pkg/errors" +) + +type MockIptables struct { + // DataplaneState is a map from table name to chain name to slice of rulespecs + DataplaneState map[string]map[string][][]string +} + +func NewMockIptables() *MockIptables { + return &MockIptables{DataplaneState: map[string]map[string][][]string{}} +} + +func (ipt *MockIptables) Exists(table, chainName string, rulespec ...string) (bool, error) { + chain := ipt.DataplaneState[table][chainName] + for _, r := range chain { + if reflect.DeepEqual(rulespec, r) { + return true, nil + } + } + return false, nil +} + +func (ipt *MockIptables) Insert(table, chain string, pos int, rulespec ...string) error { + if ipt.DataplaneState[table] == nil { + ipt.DataplaneState[table] = map[string][][]string{} + } + ipt.DataplaneState[table][chain] = append(ipt.DataplaneState[table][chain], rulespec) + return nil +} + +func (ipt *MockIptables) Append(table, chain string, rulespec ...string) error { + if ipt.DataplaneState[table] == nil { + ipt.DataplaneState[table] = map[string][][]string{} + } + ipt.DataplaneState[table][chain] = append(ipt.DataplaneState[table][chain], rulespec) + return nil +} + +func (ipt *MockIptables) AppendUnique(table, chain string, rulespec ...string) error { + exists, err := ipt.Exists(table, chain, rulespec...) + if err != nil { + return err + } + + if !exists { + return ipt.Append(table, chain, rulespec...) + } + + return nil +} + +func (ipt *MockIptables) Delete(table, chainName string, rulespec ...string) error { + chain := ipt.DataplaneState[table][chainName] + updatedChain := chain[:0] + found := false + for _, r := range chain { + if !found && reflect.DeepEqual(rulespec, r) { + found = true + continue + } + updatedChain = append(updatedChain, r) + } + if !found { + return errors.New("not found") + } + ipt.DataplaneState[table][chainName] = updatedChain + return nil +} + +func (ipt *MockIptables) List(table, chain string) ([]string, error) { + var chains []string + chainContents := ipt.DataplaneState[table][chain] + for _, ruleSpec := range chainContents { + sanitizedRuleSpec := []string{"-A", chain} + for _, item := range ruleSpec { + if strings.Contains(item, " ") { + item = fmt.Sprintf("%q", item) + } + sanitizedRuleSpec = append(sanitizedRuleSpec, item) + } + chains = append(chains, strings.Join(sanitizedRuleSpec, " ")) + } + return chains, nil + +} + +func (ipt *MockIptables) NewChain(table, chain string) error { + return nil +} + +func (ipt *MockIptables) ClearChain(table, chain string) error { + return nil +} + +func (ipt *MockIptables) DeleteChain(table, chain string) error { + return nil +} + +func (ipt *MockIptables) ListChains(table string) ([]string, error) { + var chains []string + for chain := range ipt.DataplaneState[table] { + chains = append(chains, chain) + } + return chains, nil +} + +func (ipt *MockIptables) HasRandomFully() bool { + // TODO: Work out how to write a test case for this + return true +} diff --git a/pkg/iptableswrapper/mocks/iptables_mocks.go b/pkg/iptableswrapper/mocks/iptables_mocks.go new file mode 100644 index 0000000000..480fc6783d --- /dev/null +++ b/pkg/iptableswrapper/mocks/iptables_mocks.go @@ -0,0 +1,230 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/aws/amazon-vpc-cni-k8s/pkg/iptableswrapper (interfaces: IPTablesIface) + +// Package mock_iptableswrapper is a generated GoMock package. +package mock_iptableswrapper + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockIPTablesIface is a mock of IPTablesIface interface. +type MockIPTablesIface struct { + ctrl *gomock.Controller + recorder *MockIPTablesIfaceMockRecorder +} + +// MockIPTablesIfaceMockRecorder is the mock recorder for MockIPTablesIface. +type MockIPTablesIfaceMockRecorder struct { + mock *MockIPTablesIface +} + +// NewMockIPTablesIface creates a new mock instance. +func NewMockIPTablesIface(ctrl *gomock.Controller) *MockIPTablesIface { + mock := &MockIPTablesIface{ctrl: ctrl} + mock.recorder = &MockIPTablesIfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIPTablesIface) EXPECT() *MockIPTablesIfaceMockRecorder { + return m.recorder +} + +// Append mocks base method. +func (m *MockIPTablesIface) Append(arg0, arg1 string, arg2 ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Append", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Append indicates an expected call of Append. +func (mr *MockIPTablesIfaceMockRecorder) Append(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Append", reflect.TypeOf((*MockIPTablesIface)(nil).Append), varargs...) +} + +// AppendUnique mocks base method. +func (m *MockIPTablesIface) AppendUnique(arg0, arg1 string, arg2 ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AppendUnique", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// AppendUnique indicates an expected call of AppendUnique. +func (mr *MockIPTablesIfaceMockRecorder) AppendUnique(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppendUnique", reflect.TypeOf((*MockIPTablesIface)(nil).AppendUnique), varargs...) +} + +// ClearChain mocks base method. +func (m *MockIPTablesIface) ClearChain(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClearChain", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClearChain indicates an expected call of ClearChain. +func (mr *MockIPTablesIfaceMockRecorder) ClearChain(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearChain", reflect.TypeOf((*MockIPTablesIface)(nil).ClearChain), arg0, arg1) +} + +// Delete mocks base method. +func (m *MockIPTablesIface) Delete(arg0, arg1 string, arg2 ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Delete", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockIPTablesIfaceMockRecorder) Delete(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockIPTablesIface)(nil).Delete), varargs...) +} + +// DeleteChain mocks base method. +func (m *MockIPTablesIface) DeleteChain(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteChain", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteChain indicates an expected call of DeleteChain. +func (mr *MockIPTablesIfaceMockRecorder) DeleteChain(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteChain", reflect.TypeOf((*MockIPTablesIface)(nil).DeleteChain), arg0, arg1) +} + +// Exists mocks base method. +func (m *MockIPTablesIface) Exists(arg0, arg1 string, arg2 ...string) (bool, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Exists", varargs...) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Exists indicates an expected call of Exists. +func (mr *MockIPTablesIfaceMockRecorder) Exists(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockIPTablesIface)(nil).Exists), varargs...) +} + +// HasRandomFully mocks base method. +func (m *MockIPTablesIface) HasRandomFully() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasRandomFully") + ret0, _ := ret[0].(bool) + return ret0 +} + +// HasRandomFully indicates an expected call of HasRandomFully. +func (mr *MockIPTablesIfaceMockRecorder) HasRandomFully() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasRandomFully", reflect.TypeOf((*MockIPTablesIface)(nil).HasRandomFully)) +} + +// Insert mocks base method. +func (m *MockIPTablesIface) Insert(arg0, arg1 string, arg2 int, arg3 ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1, arg2} + for _, a := range arg3 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Insert", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Insert indicates an expected call of Insert. +func (mr *MockIPTablesIfaceMockRecorder) Insert(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockIPTablesIface)(nil).Insert), varargs...) +} + +// List mocks base method. +func (m *MockIPTablesIface) List(arg0, arg1 string) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", arg0, arg1) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockIPTablesIfaceMockRecorder) List(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockIPTablesIface)(nil).List), arg0, arg1) +} + +// ListChains mocks base method. +func (m *MockIPTablesIface) ListChains(arg0 string) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListChains", arg0) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListChains indicates an expected call of ListChains. +func (mr *MockIPTablesIfaceMockRecorder) ListChains(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListChains", reflect.TypeOf((*MockIPTablesIface)(nil).ListChains), arg0) +} + +// NewChain mocks base method. +func (m *MockIPTablesIface) NewChain(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewChain", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// NewChain indicates an expected call of NewChain. +func (mr *MockIPTablesIfaceMockRecorder) NewChain(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewChain", reflect.TypeOf((*MockIPTablesIface)(nil).NewChain), arg0, arg1) +} diff --git a/pkg/networkutils/network.go b/pkg/networkutils/network.go index 5bf5c21faa..a5109061ff 100644 --- a/pkg/networkutils/network.go +++ b/pkg/networkutils/network.go @@ -27,19 +27,23 @@ import ( "syscall" "time" + "github.com/coreos/go-iptables/iptables" + "github.com/aws/amazon-vpc-cni-k8s/pkg/sgpp" - "github.com/aws/amazon-vpc-cni-k8s/pkg/utils/retry" "k8s.io/apimachinery/pkg/util/sets" + "github.com/aws/amazon-vpc-cni-k8s/pkg/utils/retry" + "github.com/pkg/errors" "golang.org/x/sys/unix" "github.com/aws/amazon-vpc-cni-k8s/pkg/utils/logger" - "github.com/coreos/go-iptables/iptables" "github.com/vishvananda/netlink" + "github.com/aws/amazon-vpc-cni-k8s/pkg/iptableswrapper" + "github.com/aws/amazon-vpc-cni-k8s/pkg/netlinkwrapper" "github.com/aws/amazon-vpc-cni-k8s/pkg/nswrapper" ) @@ -162,23 +166,10 @@ type linuxNetwork struct { netLink netlinkwrapper.NetLink ns nswrapper.NS - newIptables func(IPProtocol iptables.Protocol) (iptablesIface, error) + newIptables func(IPProtocol iptables.Protocol) (iptableswrapper.IPTablesIface, error) mainENIMark uint32 } -type iptablesIface interface { - Exists(table, chain string, rulespec ...string) (bool, error) - Insert(table, chain string, pos int, rulespec ...string) error - Append(table, chain string, rulespec ...string) error - Delete(table, chain string, rulespec ...string) error - List(table, chain string) ([]string, error) - NewChain(table, chain string) error - ClearChain(table, chain string) error - DeleteChain(table, chain string) error - ListChains(table string) ([]string, error) - HasRandomFully() bool -} - type snatType uint32 const ( @@ -202,7 +193,7 @@ func New() NetworkAPIs { netLink: netlinkwrapper.NewNetLink(), ns: nswrapper.NewNS(), - newIptables: func(IPProtocol iptables.Protocol) (iptablesIface, error) { + newIptables: func(IPProtocol iptables.Protocol) (iptableswrapper.IPTablesIface, error) { ipt, err := iptables.NewWithProtocol(IPProtocol) return ipt, err }, @@ -393,7 +384,7 @@ func (n *linuxNetwork) updateHostIptablesRules(vpcCIDRs []string, primaryMAC str return nil } -func (n *linuxNetwork) buildIptablesSNATRules(vpcCIDRs []string, primaryAddr *net.IP, primaryIntf string, ipt iptablesIface) ([]iptablesRule, error) { +func (n *linuxNetwork) buildIptablesSNATRules(vpcCIDRs []string, primaryAddr *net.IP, primaryIntf string, ipt iptableswrapper.IPTablesIface) ([]iptablesRule, error) { type snatCIDR struct { cidr string isExclusion bool @@ -526,7 +517,7 @@ func (n *linuxNetwork) buildIptablesSNATRules(vpcCIDRs []string, primaryAddr *ne return iptableRules, nil } -func (n *linuxNetwork) buildIptablesConnmarkRules(vpcCIDRs []string, ipt iptablesIface) ([]iptablesRule, error) { +func (n *linuxNetwork) buildIptablesConnmarkRules(vpcCIDRs []string, ipt iptableswrapper.IPTablesIface) ([]iptablesRule, error) { var allCIDRs []string allCIDRs = append(allCIDRs, vpcCIDRs...) allCIDRs = append(allCIDRs, n.excludeSNATCIDRs...) @@ -633,7 +624,7 @@ func (n *linuxNetwork) buildIptablesConnmarkRules(vpcCIDRs []string, ipt iptable return iptableRules, nil } -func (n *linuxNetwork) updateIptablesRules(iptableRules []iptablesRule, ipt iptablesIface) error { +func (n *linuxNetwork) updateIptablesRules(iptableRules []iptablesRule, ipt iptableswrapper.IPTablesIface) error { for _, rule := range iptableRules { log.Debugf("execute iptable rule : %s", rule.name) @@ -661,7 +652,7 @@ func (n *linuxNetwork) updateIptablesRules(iptableRules []iptablesRule, ipt ipta return nil } -func listCurrentIptablesRules(ipt iptablesIface, table, chainPrefix string) ([]iptablesRule, error) { +func listCurrentIptablesRules(ipt iptableswrapper.IPTablesIface, table, chainPrefix string) ([]iptablesRule, error) { var toClear []iptablesRule log.Debugf("Setup Host Network: loading existing iptables %s rules with chain prefix %s", table, chainPrefix) existingChains, err := ipt.ListChains(table) @@ -696,7 +687,7 @@ func listCurrentIptablesRules(ipt iptablesIface, table, chainPrefix string) ([]i return toClear, nil } -func computeStaleIptablesRules(ipt iptablesIface, table, chainPrefix string, newRules []iptablesRule, chains []string) ([]iptablesRule, error) { +func computeStaleIptablesRules(ipt iptableswrapper.IPTablesIface, table, chainPrefix string, newRules []iptablesRule, chains []string) ([]iptablesRule, error) { var staleRules []iptablesRule existingRules, err := listCurrentIptablesRules(ipt, table, chainPrefix) if err != nil { diff --git a/pkg/networkutils/network_test.go b/pkg/networkutils/network_test.go index b2b2d0b8f3..09db81ac43 100644 --- a/pkg/networkutils/network_test.go +++ b/pkg/networkutils/network_test.go @@ -14,12 +14,9 @@ package networkutils import ( - "errors" "fmt" "net" "os" - "reflect" - "strings" "testing" "time" @@ -32,6 +29,8 @@ import ( "github.com/vishvananda/netlink" "golang.org/x/sys/unix" + "github.com/aws/amazon-vpc-cni-k8s/pkg/iptableswrapper" + mock_iptables "github.com/aws/amazon-vpc-cni-k8s/pkg/iptableswrapper/mocks" mocks_ip "github.com/aws/amazon-vpc-cni-k8s/pkg/ipwrapper/mocks" "github.com/aws/amazon-vpc-cni-k8s/pkg/netlinkwrapper/mock_netlink" mock_netlinkwrapper "github.com/aws/amazon-vpc-cni-k8s/pkg/netlinkwrapper/mocks" @@ -60,13 +59,13 @@ func setup(t *testing.T) (*gomock.Controller, *mock_netlinkwrapper.MockNetLink, *mocks_ip.MockIP, *mock_nswrapper.MockNS, - *mockIptables) { + iptableswrapper.IPTablesIface) { ctrl := gomock.NewController(t) return ctrl, mock_netlinkwrapper.NewMockNetLink(ctrl), mocks_ip.NewMockIP(ctrl), mock_nswrapper.NewMockNS(ctrl), - newMockIptables() + mock_iptables.NewMockIptables() } func TestSetupENINetwork(t *testing.T) { @@ -152,7 +151,7 @@ func TestSetupHostNetworkNodePortDisabledAndSNATDisabled(t *testing.T) { mtu: testMTU, netLink: mockNetLink, ns: mockNS, - newIptables: func(iptables.Protocol) (iptablesIface, error) { + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { return mockIptables, nil }, } @@ -233,14 +232,14 @@ func TestSetupHostNetworkNodePortEnabledAndSNATDisabled(t *testing.T) { netLink: mockNetLink, ns: mockNS, - newIptables: func(iptables.Protocol) (iptablesIface, error) { + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { return mockIptables, nil }, } - log.Debugf("mockIPtables.Dp state: ", mockIptables.dataplaneState) + log.Debugf("mockIPtables.Dp state: ", mockIptables.(*mock_iptables.MockIptables).DataplaneState) setupNetLinkMocks(ctrl, mockNetLink) - log.Debugf("After: mockIPtables.Dp state: ", mockIptables.dataplaneState) + log.Debugf("After: mockIPtables.Dp state: ", mockIptables.(*mock_iptables.MockIptables).DataplaneState) var vpcCIDRs []string err := ln.SetupHostNetwork(vpcCIDRs, loopback, &testENINetIP, false, true, false) @@ -265,7 +264,7 @@ func TestSetupHostNetworkNodePortEnabledAndSNATDisabled(t *testing.T) { }, }, }, - }, mockIptables.dataplaneState) + }, mockIptables.(*mock_iptables.MockIptables).DataplaneState) } func TestSetupHostNetworkNodePortDisabledAndSNATEnabled(t *testing.T) { @@ -281,14 +280,14 @@ func TestSetupHostNetworkNodePortDisabledAndSNATEnabled(t *testing.T) { netLink: mockNetLink, ns: mockNS, - newIptables: func(iptables.Protocol) (iptablesIface, error) { + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { return mockIptables, nil }, } - log.Debugf("mockIPtables.Dp state: ", mockIptables.dataplaneState) + log.Debugf("mockIPtables.Dp state: ", mockIptables.(*mock_iptables.MockIptables).DataplaneState) setupNetLinkMocks(ctrl, mockNetLink) - log.Debugf("After: mockIPtables.Dp state: ", mockIptables.dataplaneState) + log.Debugf("After: mockIPtables.Dp state: ", mockIptables.(*mock_iptables.MockIptables).DataplaneState) var vpcCIDRs []string @@ -313,7 +312,7 @@ func TestSetupHostNetworkNodePortDisabledAndSNATEnabled(t *testing.T) { }, }, }, - }, mockIptables.dataplaneState) + }, mockIptables.(*mock_iptables.MockIptables).DataplaneState) } func TestLoadMTUFromEnvTooLow(t *testing.T) { @@ -353,7 +352,7 @@ func TestSetupHostNetworkWithExcludeSNATCIDRs(t *testing.T) { netLink: mockNetLink, ns: mockNS, - newIptables: func(iptables.Protocol) (iptablesIface, error) { + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { return mockIptables, nil }, } @@ -388,7 +387,7 @@ func TestSetupHostNetworkWithExcludeSNATCIDRs(t *testing.T) { {"-m", "comment", "--comment", "AWS, primary ENI", "-i", "vlan+", "-j", "CONNMARK", "--restore-mark", "--mask", "0x80"}, }, }, - }, mockIptables.dataplaneState) + }, mockIptables.(*mock_iptables.MockIptables).DataplaneState) } func TestSetupHostNetworkCleansUpStaleSNATRules(t *testing.T) { @@ -405,7 +404,7 @@ func TestSetupHostNetworkCleansUpStaleSNATRules(t *testing.T) { netLink: mockNetLink, ns: mockNS, - newIptables: func(iptables.Protocol) (iptablesIface, error) { + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { return mockIptables, nil }, } @@ -456,7 +455,7 @@ func TestSetupHostNetworkCleansUpStaleSNATRules(t *testing.T) { {"-m", "comment", "--comment", "AWS, primary ENI", "-i", "vlan+", "-j", "CONNMARK", "--restore-mark", "--mask", "0x80"}, }, }, - }, mockIptables.dataplaneState) + }, mockIptables.(*mock_iptables.MockIptables).DataplaneState) } func TestSetupHostNetworkWithDifferentVethPrefix(t *testing.T) { @@ -473,7 +472,7 @@ func TestSetupHostNetworkWithDifferentVethPrefix(t *testing.T) { netLink: mockNetLink, ns: mockNS, - newIptables: func(iptables.Protocol) (iptablesIface, error) { + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { return mockIptables, nil }, } @@ -524,7 +523,7 @@ func TestSetupHostNetworkWithDifferentVethPrefix(t *testing.T) { {"-m", "comment", "--comment", "AWS, primary ENI", "-i", "vlan+", "-j", "CONNMARK", "--restore-mark", "--mask", "0x80"}, }, }, - }, mockIptables.dataplaneState) + }, mockIptables.(*mock_iptables.MockIptables).DataplaneState) } func TestSetupHostNetworkExternalNATCleanupConnmark(t *testing.T) { ctrl, mockNetLink, _, mockNS, mockIptables := setup(t) @@ -540,7 +539,7 @@ func TestSetupHostNetworkExternalNATCleanupConnmark(t *testing.T) { netLink: mockNetLink, ns: mockNS, - newIptables: func(iptables.Protocol) (iptablesIface, error) { + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { return mockIptables, nil }, } @@ -588,7 +587,7 @@ func TestSetupHostNetworkExternalNATCleanupConnmark(t *testing.T) { {"-m", "comment", "--comment", "AWS, primary ENI", "-i", "vlan+", "-j", "CONNMARK", "--restore-mark", "--mask", "0x80"}, }, }, - }, mockIptables.dataplaneState) + }, mockIptables.(*mock_iptables.MockIptables).DataplaneState) } func TestSetupHostNetworkExcludedSNATCIDRsIdempotent(t *testing.T) { ctrl, mockNetLink, _, mockNS, mockIptables := setup(t) @@ -604,7 +603,7 @@ func TestSetupHostNetworkExcludedSNATCIDRsIdempotent(t *testing.T) { netLink: mockNetLink, ns: mockNS, - newIptables: func(iptables.Protocol) (iptablesIface, error) { + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { return mockIptables, nil }, } @@ -655,7 +654,7 @@ func TestSetupHostNetworkExcludedSNATCIDRsIdempotent(t *testing.T) { {"-m", "comment", "--comment", "AWS, primary ENI", "-i", "vlan+", "-j", "CONNMARK", "--restore-mark", "--mask", "0x80"}, }, }, - }, mockIptables.dataplaneState) + }, mockIptables.(*mock_iptables.MockIptables).DataplaneState) } func TestUpdateHostIptablesRules(t *testing.T) { @@ -671,7 +670,7 @@ func TestUpdateHostIptablesRules(t *testing.T) { netLink: mockNetLink, ns: mockNS, - newIptables: func(iptables.Protocol) (iptablesIface, error) { + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { return mockIptables, nil }, } @@ -715,7 +714,7 @@ func TestUpdateHostIptablesRules(t *testing.T) { {"-m", "comment", "--comment", "AWS, primary ENI", "-i", "veth+", "-j", "CONNMARK", "--restore-mark", "--mask", "0x80"}, }, }, - }, mockIptables.dataplaneState) + }, mockIptables.(*mock_iptables.MockIptables).DataplaneState) } func TestSetupHostNetworkMultipleCIDRs(t *testing.T) { ctrl, mockNetLink, _, mockNS, mockIptables := setup(t) @@ -730,7 +729,7 @@ func TestSetupHostNetworkMultipleCIDRs(t *testing.T) { netLink: mockNetLink, ns: mockNS, - newIptables: func(iptables.Protocol) (iptablesIface, error) { + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { return mockIptables, nil }, } @@ -755,7 +754,7 @@ func TestSetupHostNetworkWithIPv6Enabled(t *testing.T) { netLink: mockNetLink, ns: mockNS, - newIptables: func(iptables.Protocol) (iptablesIface, error) { + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { return mockIptables, nil }, } @@ -776,7 +775,7 @@ func TestSetupHostNetworkWithIPv6Enabled(t *testing.T) { }, }, }, - }, mockIptables.dataplaneState) + }, mockIptables.(*mock_iptables.MockIptables).DataplaneState) } func TestIncrementIPv4Addr(t *testing.T) { @@ -818,7 +817,7 @@ func TestSetupHostNetworkIgnoringRpFilterUpdate(t *testing.T) { netLink: mockNetLink, ns: mockNS, - newIptables: func(iptables.Protocol) (iptablesIface, error) { + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { return mockIptables, nil }, } @@ -843,7 +842,7 @@ func TestSetupHostNetworkUpdateLocalRule(t *testing.T) { netLink: mockNetLink, ns: mockNS, - newIptables: func(iptables.Protocol) (iptablesIface, error) { + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { return mockIptables, nil }, } @@ -871,7 +870,7 @@ func TestSetupHostNetworkDeleteOldConnmarkRuleForNonVpcOutboundTraffic(t *testin netLink: mockNetLink, ns: mockNS, - newIptables: func(iptables.Protocol) (iptablesIface, error) { + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { return mockIptables, nil }, } @@ -1044,98 +1043,3 @@ func setupVethNetLinkMocks(mockNetLink *mock_netlinkwrapper.MockNetLink) { mockNetLink.EXPECT().RuleAdd(&localRule) mockNetLink.EXPECT().RuleDel(&localRule) } - -type mockIptables struct { - // dataplaneState is a map from table name to chain name to slice of rulespecs - dataplaneState map[string]map[string][][]string -} - -func newMockIptables() *mockIptables { - return &mockIptables{dataplaneState: map[string]map[string][][]string{}} -} - -func (ipt *mockIptables) Exists(table, chainName string, rulespec ...string) (bool, error) { - chain := ipt.dataplaneState[table][chainName] - for _, r := range chain { - if reflect.DeepEqual(rulespec, r) { - return true, nil - } - } - return false, nil -} - -func (ipt *mockIptables) Insert(table, chain string, pos int, rulespec ...string) error { - if ipt.dataplaneState[table] == nil { - ipt.dataplaneState[table] = map[string][][]string{} - } - ipt.dataplaneState[table][chain] = append(ipt.dataplaneState[table][chain], rulespec) - return nil -} - -func (ipt *mockIptables) Append(table, chain string, rulespec ...string) error { - if ipt.dataplaneState[table] == nil { - ipt.dataplaneState[table] = map[string][][]string{} - } - ipt.dataplaneState[table][chain] = append(ipt.dataplaneState[table][chain], rulespec) - return nil -} - -func (ipt *mockIptables) Delete(table, chainName string, rulespec ...string) error { - chain := ipt.dataplaneState[table][chainName] - updatedChain := chain[:0] - found := false - for _, r := range chain { - if !found && reflect.DeepEqual(rulespec, r) { - found = true - continue - } - updatedChain = append(updatedChain, r) - } - if !found { - return errors.New("not found") - } - ipt.dataplaneState[table][chainName] = updatedChain - return nil -} - -func (ipt *mockIptables) List(table, chain string) ([]string, error) { - var chains []string - chainContents := ipt.dataplaneState[table][chain] - for _, ruleSpec := range chainContents { - sanitizedRuleSpec := []string{"-A", chain} - for _, item := range ruleSpec { - if strings.Contains(item, " ") { - item = fmt.Sprintf("%q", item) - } - sanitizedRuleSpec = append(sanitizedRuleSpec, item) - } - chains = append(chains, strings.Join(sanitizedRuleSpec, " ")) - } - return chains, nil - -} - -func (ipt *mockIptables) NewChain(table, chain string) error { - return nil -} - -func (ipt *mockIptables) ClearChain(table, chain string) error { - return nil -} - -func (ipt *mockIptables) DeleteChain(table, chain string) error { - return nil -} - -func (ipt *mockIptables) ListChains(table string) ([]string, error) { - var chains []string - for chain := range ipt.dataplaneState[table] { - chains = append(chains, chain) - } - return chains, nil -} - -func (ipt *mockIptables) HasRandomFully() bool { - // TODO: Work out how to write a test case for this - return true -} diff --git a/pkg/procsyswrapper/procsys.go b/pkg/procsyswrapper/procsys.go index 3e3fec89a6..c3b930a289 100644 --- a/pkg/procsyswrapper/procsys.go +++ b/pkg/procsyswrapper/procsys.go @@ -15,7 +15,7 @@ package procsyswrapper import ( - "io/ioutil" + "os" ) // ProcSys is the /proc/sys interface wrapper @@ -38,10 +38,10 @@ func (p *procSys) path(key string) string { } func (p *procSys) Get(key string) (string, error) { - data, err := ioutil.ReadFile(p.path(key)) + data, err := os.ReadFile(p.path(key)) return string(data), err } func (p *procSys) Set(key, value string) error { - return ioutil.WriteFile(p.path(key), []byte(value), 0644) + return os.WriteFile(p.path(key), []byte(value), 0644) } diff --git a/pkg/utils/cniutils/cni_utils.go b/pkg/utils/cniutils/cni_utils.go index 15969169a8..64616d3d62 100644 --- a/pkg/utils/cniutils/cni_utils.go +++ b/pkg/utils/cniutils/cni_utils.go @@ -1,6 +1,21 @@ package cniutils -import "github.com/containernetworking/cni/pkg/types/current" +import ( + "fmt" + "syscall" + "time" + + "github.com/containernetworking/cni/pkg/types/current" + "github.com/vishvananda/netlink" + + "github.com/aws/amazon-vpc-cni-k8s/pkg/netlinkwrapper" + "github.com/aws/amazon-vpc-cni-k8s/pkg/procsyswrapper" +) + +const ( + ipv4ForwardKey = "net/ipv4/ip_forward" + ipv6ForwardKey = "net/ipv6/conf/all/forwarding" +) func FindInterfaceByName(ifaceList []*current.Interface, ifaceName string) (ifaceIndex int, iface *current.Interface, found bool) { for ifaceIndex, iface := range ifaceList { @@ -20,3 +35,78 @@ func FindIPConfigsByIfaceIndex(ipConfigs []*current.IPConfig, ifaceIndex int) [] } return matchedIPConfigs } + +// WaitForAddressesToBeStable Implements `SettleAddresses` functionality of the `ip` package. +// waitForAddressesToBeStable waits for all addresses on a link to leave tentative state. +// Will be particularly useful for ipv6, where all addresses need to do DAD. +// If any addresses are still tentative after timeout seconds, then error. +func WaitForAddressesToBeStable(netLink netlinkwrapper.NetLink, ifName string, timeout, waitInterval time.Duration) error { + link, err := netLink.LinkByName(ifName) + if err != nil { + return fmt.Errorf("failed to retrieve link: %v", err) + } + + deadline := time.Now().Add(timeout) + for { + addrs, err := netLink.AddrList(link, netlink.FAMILY_V6) + if err != nil { + return fmt.Errorf("could not list addresses: %v", err) + } + + ok := true + for _, addr := range addrs { + if addr.Flags&(syscall.IFA_F_TENTATIVE|syscall.IFA_F_DADFAILED) > 0 { + ok = false + break + } + } + + if ok { + return nil + } + if time.Now().After(deadline) { + return fmt.Errorf("link %s still has tentative addresses after %d seconds", + ifName, + timeout) + } + + time.Sleep(waitInterval) + } +} + +// EnableIpForwarding sets forwarding to 1 for both IPv4 and IPv6 if applicable. +// This func is to have a unit testable version of ip.EnableForward in ipforward_linux.go file +// link: https://github.com/containernetworking/plugins/blob/main/pkg/ip/ipforward_linux.go#L34 +func EnableIpForwarding(procSys procsyswrapper.ProcSys, ips []*current.IPConfig) error { + v4 := false + v6 := false + + for _, ip := range ips { + if ip.Version == "4" && !v4 { + valueV4, err := procSys.Get(ipv4ForwardKey) + if err != nil { + return err + } + if valueV4 != "1" { + err = procSys.Set(ipv4ForwardKey, "1") + if err != nil { + return err + } + } + v4 = true + } else if ip.Version == "6" && !v6 { + valueV6, err := procSys.Get(ipv6ForwardKey) + if err != nil { + return err + } + if valueV6 != "1" { + err = procSys.Set(ipv6ForwardKey, "1") + if err != nil { + return err + } + } + v6 = true + } + } + return nil +} diff --git a/pkg/vethwrapper/generate_mocks.go b/pkg/vethwrapper/generate_mocks.go new file mode 100644 index 0000000000..c96888cdf0 --- /dev/null +++ b/pkg/vethwrapper/generate_mocks.go @@ -0,0 +1,16 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package vethwrapper + +//go:generate go run github.com/golang/mock/mockgen -destination mocks/veth_mocks.go -copyright_file ../../scripts/copyright.txt . Veth diff --git a/pkg/vethwrapper/mocks/veth_mocks.go b/pkg/vethwrapper/mocks/veth_mocks.go new file mode 100644 index 0000000000..b93712f9bc --- /dev/null +++ b/pkg/vethwrapper/mocks/veth_mocks.go @@ -0,0 +1,66 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/aws/amazon-vpc-cni-k8s/pkg/vethwrapper (interfaces: Veth) + +// Package mock_vethwrapper is a generated GoMock package. +package mock_vethwrapper + +import ( + net "net" + reflect "reflect" + + ns "github.com/containernetworking/plugins/pkg/ns" + gomock "github.com/golang/mock/gomock" +) + +// MockVeth is a mock of Veth interface. +type MockVeth struct { + ctrl *gomock.Controller + recorder *MockVethMockRecorder +} + +// MockVethMockRecorder is the mock recorder for MockVeth. +type MockVethMockRecorder struct { + mock *MockVeth +} + +// NewMockVeth creates a new mock instance. +func NewMockVeth(ctrl *gomock.Controller) *MockVeth { + mock := &MockVeth{ctrl: ctrl} + mock.recorder = &MockVethMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockVeth) EXPECT() *MockVethMockRecorder { + return m.recorder +} + +// Setup mocks base method. +func (m *MockVeth) Setup(arg0 string, arg1 int, arg2 ns.NetNS) (net.Interface, net.Interface, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Setup", arg0, arg1, arg2) + ret0, _ := ret[0].(net.Interface) + ret1, _ := ret[1].(net.Interface) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Setup indicates an expected call of Setup. +func (mr *MockVethMockRecorder) Setup(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Setup", reflect.TypeOf((*MockVeth)(nil).Setup), arg0, arg1, arg2) +} diff --git a/pkg/vethwrapper/veth.go b/pkg/vethwrapper/veth.go new file mode 100644 index 0000000000..de1a08f25b --- /dev/null +++ b/pkg/vethwrapper/veth.go @@ -0,0 +1,38 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +// Package vethwrapper is a wrapper method for the ip.SetupVeth method +package vethwrapper + +import ( + "net" + + "github.com/containernetworking/plugins/pkg/ip" + "github.com/containernetworking/plugins/pkg/ns" +) + +// Veth is an interface created to make code unit testable. +// Both the veth version and mocked version implement the same interface +type Veth interface { + Setup(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) +} + +type veth struct{} + +// NewSetupVeth return a new veth object +func NewSetupVeth() Veth { + return &veth{} +} +func (v *veth) Setup(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) { + return ip.SetupVeth(contVethName, mtu, hostNS) +} diff --git a/scripts/dockerfiles/Dockerfile.release b/scripts/dockerfiles/Dockerfile.release index 234a2bca92..e2205402f4 100644 --- a/scripts/dockerfiles/Dockerfile.release +++ b/scripts/dockerfiles/Dockerfile.release @@ -25,7 +25,7 @@ COPY --from=builder /go/src/github.com/aws/amazon-vpc-cni-k8s/aws-cni \ /go/src/github.com/aws/amazon-vpc-cni-k8s/misc/10-aws.conflist \ /go/src/github.com/aws/amazon-vpc-cni-k8s/aws-k8s-agent \ /go/src/github.com/aws/amazon-vpc-cni-k8s/grpc-health-probe \ - /go/src/github.com/aws/amazon-vpc-cni-k8s/egress-v4-cni \ + /go/src/github.com/aws/amazon-vpc-cni-k8s/egress-cni \ /go/src/github.com/aws/amazon-vpc-cni-k8s/aws-vpc-cni /app/ ENTRYPOINT ["/app/aws-vpc-cni"]