From 58a0e2ba13522ca2a3792a824a196c10babea3f4 Mon Sep 17 00:00:00 2001 From: Onur Filiz Date: Tue, 25 Jul 2023 16:59:04 -0700 Subject: [PATCH] Add Linux support to vpc-eni plugin (#101) --- network/eni/eni_linux.go | 9 +- plugins/vpc-eni/config/netconfig.go | 3 + plugins/vpc-eni/network/network.go | 2 + plugins/vpc-eni/network/network_linux.go | 140 +++++++++++++++++++++++ plugins/vpc-eni/plugin/commands.go | 2 + plugins/vpc-eni/vpc-eni.conf | 6 +- 6 files changed, 159 insertions(+), 3 deletions(-) diff --git a/network/eni/eni_linux.go b/network/eni/eni_linux.go index ccaecf28..3dc39d8a 100644 --- a/network/eni/eni_linux.go +++ b/network/eni/eni_linux.go @@ -63,11 +63,18 @@ func (eni *ENI) SetOpState(up bool) error { } // SetNetNS sets the network namespace of the ENI. +// If ns argument is nil, the ENI is reset to the host network namespace. func (eni *ENI) SetNetNS(ns netns.NetNS) error { la := netlink.NewLinkAttrs() la.Name = eni.linkName link := &netlink.Dummy{LinkAttrs: la} - return netlink.LinkSetNsFd(link, int(ns.GetFd())) + if ns != nil { + // Move the ENI to the given network namespace. + return netlink.LinkSetNsFd(link, int(ns.GetFd())) + } else { + // PID 1 init is running in the host network namespace. + return netlink.LinkSetNsPid(link, 1) + } } // SetMACAddress sets the MAC address of the ENI. diff --git a/plugins/vpc-eni/config/netconfig.go b/plugins/vpc-eni/config/netconfig.go index 0d0358eb..d74d7f2f 100644 --- a/plugins/vpc-eni/config/netconfig.go +++ b/plugins/vpc-eni/config/netconfig.go @@ -32,6 +32,7 @@ type NetConfig struct { ENIMACAddress net.HardwareAddr ENIIPAddresses []net.IPNet GatewayIPAddresses []net.IP + OpState bool UseExistingNetwork bool BlockIMDS bool } @@ -43,6 +44,7 @@ type netConfigJSON struct { ENIMACAddress string `json:"eniMACAddress"` ENIIPAddresses []string `json:"eniIPAddresses"` GatewayIPAddresses []string `json:"gatewayIPAddresses"` + OpStateDown bool `json:"opStateDown"` UseExistingNetwork bool `json:"useExistingNetwork"` BlockIMDS bool `json:"blockInstanceMetadata"` } @@ -79,6 +81,7 @@ func New(args *cniSkel.CmdArgs) (*NetConfig, error) { netConfig := NetConfig{ NetConf: config.NetConf, ENIName: config.ENIName, + OpState: !config.OpStateDown, UseExistingNetwork: config.UseExistingNetwork, BlockIMDS: config.BlockIMDS, } diff --git a/plugins/vpc-eni/network/network.go b/plugins/vpc-eni/network/network.go index 16bdec10..f4f24d58 100644 --- a/plugins/vpc-eni/network/network.go +++ b/plugins/vpc-eni/network/network.go @@ -42,7 +42,9 @@ type Network struct { type Endpoint struct { ContainerID string NetNSName string + ENIName string MACAddress net.HardwareAddr IPAddresses []net.IPNet + OpState bool BlockIMDS bool } diff --git a/plugins/vpc-eni/network/network_linux.go b/plugins/vpc-eni/network/network_linux.go index 13bbdfd6..789dee33 100644 --- a/plugins/vpc-eni/network/network_linux.go +++ b/plugins/vpc-eni/network/network_linux.go @@ -13,25 +13,165 @@ package network +import ( + "fmt" + + "github.com/aws/amazon-vpc-cni-plugins/network/imds" + "github.com/aws/amazon-vpc-cni-plugins/network/netns" + + log "github.com/cihub/seelog" + "github.com/vishvananda/netlink" +) + // NetBuilder implements the Builder interface for Linux. type NetBuilder struct{} // FindOrCreateNetwork creates a new network. func (nb *NetBuilder) FindOrCreateNetwork(nw *Network) error { + // Vpc-eni does not need any network-level setup on Linux. return nil } // DeleteNetwork deletes an existing network. func (nb *NetBuilder) DeleteNetwork(nw *Network) error { + // Vpc-eni does not need any network-level cleanup on Linux. return nil } // FindOrCreateEndpoint creates a new endpoint in the network. func (nb *NetBuilder) FindOrCreateEndpoint(nw *Network, ep *Endpoint) error { + // Find the network namespace. + log.Infof("Searching for netns %s.", ep.NetNSName) + ns, err := netns.GetNetNS(ep.NetNSName) + if err != nil { + log.Errorf("Failed to find netns %s: %v.", ep.NetNSName, err) + return err + } + + eni := nw.ENI + err = eni.AttachToLink() + if err != nil { + log.Errorf("Failed to find ENI %s: %v", eni, err) + return err + } + + log.Infof("Moving ENI link %s to netns %s.", eni, ep.NetNSName) + err = eni.SetNetNS(ns) + if err != nil { + log.Errorf("Failed to move eni: %v.", err) + return err + } + + // If operational state is down, there is no need to configure anything else. + if !ep.OpState { + return nil + } + + // Complete the remaining setup in target network namespace. + err = ns.Run(func() error { + // Rename the ENI link to the requested interface name. + if eni.GetLinkName() != ep.ENIName { + log.Infof("Renaming ENI link %v to %s.", eni, ep.ENIName) + err := eni.SetLinkName(ep.ENIName) + if err != nil { + log.Errorf("Failed to rename ENI link %v: %v.", eni, err) + return err + } + } + + // Add a blackhole route for IMDS endpoint if required. + if ep.BlockIMDS { + err = imds.BlockInstanceMetadataEndpoint() + if err != nil { + return err + } + } + + // Set ENI IP addresses if specified. + for _, ipAddress := range ep.IPAddresses { + // Assign the IP address. + err = eni.AddIPAddress(&ipAddress) + if err != nil { + log.Errorf("Failed to assign IP address to eni %v: %v.", eni, err) + return err + } + } + + log.Infof("Setting ENI link state up.") + err = eni.SetOpState(true) + if err != nil { + log.Errorf("Failed to set link %v state: %v.", eni, err) + return err + } + + // Set default gateways if specified. + for _, gatewayIPAddress := range nw.GatewayIPAddresses { + // Add default route via ENI link. + route := &netlink.Route{ + Gw: gatewayIPAddress, + LinkIndex: eni.GetLinkIndex(), + } + log.Infof("Adding default IP route %+v.", route) + err = netlink.RouteAdd(route) + if err != nil { + log.Errorf("Failed to add IP route %+v via ENI %v: %v.", route, eni, err) + return err + } + } + + return err + }) + return nil } // DeleteEndpoint deletes an existing endpoint. func (nb *NetBuilder) DeleteEndpoint(nw *Network, ep *Endpoint) error { + // Search for the target network namespace. + netns, err := netns.GetNetNS(ep.NetNSName) + if err != nil { + // Log and ignore the failure. DEL can be called multiple times and thus must be idempotent. + log.Errorf("Failed to find netns %s, ignoring: %v.", ep.NetNSName, err) + return nil + } + + // In target network namespace... + err = netns.Run(func() error { + eni := nw.ENI + err = eni.AttachToLink() + if err != nil { + log.Errorf("Failed to find ENI %s: %v", eni, err) + return err + } + + log.Infof("Setting ENI link state down.") + err = eni.SetOpState(false) + if err != nil { + log.Errorf("Failed to set link %v state: %v.", eni, err) + return err + } + + // Rename the ENI link to its MAC address to avoid naming conflicts in host netns. + eniName := fmt.Sprintf("ecs%x%x%x", ep.MACAddress[0], ep.MACAddress[1], ep.MACAddress[2]) + log.Infof("Renaming ENI link %v to %s.", eni, eniName) + err := eni.SetLinkName(eniName) + if err != nil { + log.Errorf("Failed to rename ENI link %v: %v.", eni, err) + return err + } + + log.Infof("Moving ENI link %s to host netns.", eni) + err = eni.SetNetNS(nil) + if err != nil { + log.Errorf("Failed to move eni: %v.", err) + return err + } + return nil + }) + + if err != nil { + log.Errorf("Failed to set netns to host, ignoring: %v.", err) + } + return nil } diff --git a/plugins/vpc-eni/plugin/commands.go b/plugins/vpc-eni/plugin/commands.go index a58da2fd..d398fa7e 100644 --- a/plugins/vpc-eni/plugin/commands.go +++ b/plugins/vpc-eni/plugin/commands.go @@ -71,8 +71,10 @@ func (plugin *Plugin) Add(args *cniSkel.CmdArgs) error { ep := network.Endpoint{ ContainerID: args.ContainerID, NetNSName: args.Netns, + ENIName: args.IfName, MACAddress: netConfig.ENIMACAddress, IPAddresses: netConfig.ENIIPAddresses, + OpState: netConfig.OpState, BlockIMDS: netConfig.BlockIMDS, } diff --git a/plugins/vpc-eni/vpc-eni.conf b/plugins/vpc-eni/vpc-eni.conf index facd47fb..6729db5d 100644 --- a/plugins/vpc-eni/vpc-eni.conf +++ b/plugins/vpc-eni/vpc-eni.conf @@ -2,9 +2,11 @@ "cniVersion": "0.3.1", "name": "vpc", "type": "vpc-eni", - "eniName": "Ethernet 4", + "eniName": "eth1", "eniMACAddress": "12:34:56:78:9a:bc", "eniIPAddresses": ["192.168.1.42/24"], "gatewayIPAddresses": ["192.168.1.1"], - "useExistingNetwork": "false" + "opStateDown": "false", + "useExistingNetwork": "false", + "blockInstanceMetadata": "false" }