// Copyright 2017 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 engine import ( "fmt" "net" "syscall" "github.com/aws/amazon-ecs-cni-plugins/pkg/netlinkwrapper" "github.com/containernetworking/cni/pkg/ns" "github.com/pkg/errors" "github.com/vishvananda/netlink" ) var ( // InstanceMetadataEndpoints is the list of EC2 instance metadata endpoints. InstanceMetadataEndpoints = []string{"169.254.169.254/32", "fd00:ec2::254/128"} linkWithMACNotFoundError = errors.New("engine: device with mac address not found") ) // setupNamespaceClosureContext wraps the parameters and the method to configure the container's namespace type setupNamespaceClosureContext struct { netLink netlinkwrapper.NetLink ifName string deviceName string macAddress string ipAddrs []*netlink.Addr gatewayAddrs []net.IP blockIMDS bool mtu int } // teardownNamespaceClosureContext wraps the parameters and the method to teardown the // container's namespace type teardownNamespaceClosureContext struct { netLink netlinkwrapper.NetLink hardwareAddr net.HardwareAddr } // newSetupNamespaceClosureContext creates a new setupNamespaceClosure object func newSetupNamespaceClosureContext( netLink netlinkwrapper.NetLink, ifName string, deviceName string, macAddress string, ipAddresses []string, gatewayAddresses []string, blockIMDS bool, mtu int) (*setupNamespaceClosureContext, error) { nsClosure := &setupNamespaceClosureContext{ netLink: netLink, ifName: ifName, deviceName: deviceName, macAddress: macAddress, blockIMDS: blockIMDS, mtu: mtu, } for _, addr := range ipAddresses { nlIPAddr, err := netLink.ParseAddr(addr) if err != nil { return nil, errors.Wrapf(err, "setupNamespaceClosure engine: unable to parse ip address '%s' for the interface", addr) } nsClosure.ipAddrs = append(nsClosure.ipAddrs, nlIPAddr) } for _, addr := range gatewayAddresses { gatewayAddr := net.ParseIP(addr) if gatewayAddr == nil { return nil, fmt.Errorf( "setupNamespaceClosure engine: unable to parse gateway ip address '%s'", addr) } nsClosure.gatewayAddrs = append(nsClosure.gatewayAddrs, gatewayAddr) } return nsClosure, nil } // newTeardownNamespaceClosureContext creates a new teardownNamespaceClosure object func newTeardownNamespaceClosureContext(netLink netlinkwrapper.NetLink, mac string) (*teardownNamespaceClosureContext, error) { hardwareAddr, err := net.ParseMAC(mac) if err != nil { return nil, errors.Wrapf(err, "newTeardownNamespaceClosure engine: malformatted mac address specified") } return &teardownNamespaceClosureContext{ netLink: netLink, hardwareAddr: hardwareAddr, }, nil } // run defines the closure to execute within the container's namespace to configure it // appropriately func (closureContext *setupNamespaceClosureContext) run(_ ns.NetNS) error { // Get the link for the ENI device eniLink, err := closureContext.netLink.LinkByName(closureContext.deviceName) if err != nil { return errors.Wrapf(err, "setupNamespaceClosure engine: unable to get link for device '%s'", closureContext.deviceName) } err = closureContext.netLink.LinkSetName(eniLink, closureContext.ifName) if err != nil { return errors.Wrap(err, "setupNamespaceClosure engine: unable to change interface name") } // Add IP addresses to the link for _, addr := range closureContext.ipAddrs { err = closureContext.netLink.AddrAdd(eniLink, addr) if err != nil { return errors.Wrap(err, "setupNamespaceClosure engine: unable to add ip address to the interface") } } // Bring it up err = closureContext.netLink.LinkSetUp(eniLink) if err != nil { return errors.Wrap(err, "setupNamespaceClosure engine: unable to bring up the device") } // Change the MTU if it is customized if closureContext.mtu != 0 { err = closureContext.netLink.LinkSetMTU(eniLink, closureContext.mtu) if err != nil { return errors.Wrap(err, "setupNamespaceClosure engine: unable to set mtu of interface") } } // Add a blackhole route for IMDS endpoint if required if closureContext.blockIMDS { for _, ep := range InstanceMetadataEndpoints { _, imdsNetwork, err := net.ParseCIDR(ep) if err != nil { // This should never happen as these IP addresses are hardcoded. return errors.Wrapf(err, "setupNamespaceClosure engine: unable to parse instance metadata endpoint") } if err = closureContext.netLink.RouteAdd(&netlink.Route{ Dst: imdsNetwork, Type: syscall.RTN_BLACKHOLE, }); err != nil { return errors.Wrapf(err, "setupNamespaceClosure engine: unable to add route to block instance metadata") } } } // Setup IP routes for the gateways for _, gwAddr := range closureContext.gatewayAddrs { err = closureContext.netLink.RouteAdd(&netlink.Route{ LinkIndex: eniLink.Attrs().Index, Gw: gwAddr, }) if err != nil && !isRouteExistsError(err) { return errors.Wrap(err, "setupNamespaceClosure engine: unable to add the route for the gateway") } } return nil } // isRouteExistsError returns true if the error type is syscall.EEXIST // This helps us determine if we should ignore this error as the route // that we want to add already exists in the routing table func isRouteExistsError(err error) bool { if errno, ok := err.(syscall.Errno); ok { return errno == syscall.EEXIST } return false } // run defines the closure to execute within the container's namespace to tear it down func (closureContext *teardownNamespaceClosureContext) run(_ ns.NetNS) error { return nil } // getLinkByHardwareAddress gets the link device based on the mac address func getLinkByHardwareAddress(netLink netlinkwrapper.NetLink, hardwareAddr net.HardwareAddr) (netlink.Link, error) { links, err := netLink.LinkList() if err != nil { return nil, err } for _, link := range links { // TODO: Evaluate if reflect.DeepEqual is a better alternative here if link.Attrs().HardwareAddr.String() == hardwareAddr.String() { return link, nil } } return nil, linkWithMACNotFoundError }