ecs-agent/netlib/model/networkinterface/networkinterface.go (434 lines of code) (raw):
// 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.
//lint:file-ignore U1000 Ignore unused fields as some of them are only used by Fargate
package networkinterface
import (
"fmt"
"net"
"strings"
"sync"
"github.com/aws/amazon-ecs-agent/ecs-agent/acs/model/ecsacs"
"github.com/aws/amazon-ecs-agent/ecs-agent/logger"
loggerfield "github.com/aws/amazon-ecs-agent/ecs-agent/logger/field"
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/status"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/pkg/errors"
)
// NetworkInterface contains information of the network interface
type NetworkInterface struct {
// ID is the id of eni
ID string `json:"ec2Id"`
// LinkName is the name of the NetworkInterface on the instance.
// Currently, this field is being used only for Windows and is used during task networking setup.
LinkName string
// MacAddress is the mac address of the eni
MacAddress string
// IPV4Addresses is the ipv4 address associated with the eni
IPV4Addresses []*IPV4Address
// IPV6Addresses is the ipv6 address associated with the eni
IPV6Addresses []*IPV6Address
// SubnetGatewayIPV4Address is the IPv4 address of the subnet gateway of the NetworkInterface
SubnetGatewayIPV4Address string `json:",omitempty"`
// DomainNameServers specifies the nameserver IP addresses for the eni
DomainNameServers []string `json:",omitempty"`
// DomainNameSearchList specifies the search list for the domain
// name lookup, for the eni
DomainNameSearchList []string `json:",omitempty"`
// PrivateDNSName is the dns name assigned by the vpc to this eni
PrivateDNSName string `json:",omitempty"`
// InterfaceAssociationProtocol is the type of NetworkInterface, valid value: "default", "vlan"
InterfaceAssociationProtocol string `json:",omitempty"`
Index int64 `json:"Index"`
UserID uint32 `json:"UserID"`
Name string `json:"Name"`
DeviceName string `json:"DeviceName"`
KnownStatus status.NetworkStatus `json:"KnownStatus"`
DesiredStatus status.NetworkStatus `json:"DesiredStatus"`
// GuestNetNSName represents the interface's network namespace inside a guest OS if applicable.
// A sample use case is while running tasks inside Firecracker microVMs.
GuestNetNSName string `json:"GuestNetNSName,omitempty"`
// InterfaceVlanProperties contains information for an interface
// that is supposed to be used as a VLAN device
InterfaceVlanProperties *InterfaceVlanProperties `json:",omitempty"`
// TunnelProperties contains information for tunnel interface
TunnelProperties *TunnelProperties `json:",omitempty"`
// VETHProperties contains information for a virtual ethernet interface
VETHProperties *VETHProperties `json:",omitempty"`
// Certain tasks such as service connect tasks may require additional
// domain name to IP address mapping defined in their /etc/hosts files.
// DNSMappingList will contain this for each NetworkInterface since /etc/hosts file
// is created per NetworkInterface.
DNSMappingList []DNSMapping
// Due to historical reasons, the IPv4 subnet prefix length is sent with IPv4 subnet gateway
// address instead of the NetworkInterface's IP addresses. However, CNI plugins and many OS APIs expect it
// the other way around. Instead of doing this conversion all the time for each CNI and TMDS
// request, compute it once on demand and cache it here.
ipv4SubnetPrefixLength string
ipv4SubnetCIDRBlock string
ipv6SubnetCIDRBlock string
// Default denotes whether the interface is responsible
// for handling default route within the netns it resides in.
Default bool
// guard protects access to fields of this struct.
guard sync.RWMutex
}
// InterfaceVlanProperties contains information for an interface that
// is supposed to be used as a VLAN device
type InterfaceVlanProperties struct {
VlanID string
TrunkInterfaceMacAddress string
}
// TunnelProperties holds ID (e.g. VNI), destination IP address and port for tunnel interfaces.
type TunnelProperties struct {
ID string `json:"ID"`
DestinationIPAddress string `json:"DestinationIPAddress"`
DestinationPort uint16 `json:"DestinationPort"`
}
// VETHProperties holds the properties for virtual ethernet interfaces.
type VETHProperties struct {
PeerInterfaceName string `json:"PeerInterfaceName"`
}
// DNSMapping holds additional pre-defined DNS entries for containers.
// These additional entries will be written into /etc/hosts file eventually.
type DNSMapping struct {
Hostname string
Address string
}
const (
// DefaultInterfaceAssociationProtocol represents the standard NetworkInterface type.
DefaultInterfaceAssociationProtocol = "default"
// VLANInterfaceAssociationProtocol represents the NetworkInterface with trunking enabled.
VLANInterfaceAssociationProtocol = "vlan"
// IPv6SubnetPrefixLength is the IPv6 global unicast address prefix length, consisting of
// global routing prefix and subnet ID lengths as specified in IPv6 addressing architecture
// (RFC 4291 section 2.5.4) and IPv6 Global Unicast Address Format (RFC 3587).
// The ACS NetworkInterface payload structure does not contain an IPv6 subnet prefix length because "/64" is
// the only allowed length per RFCs above, and the only one that VPC supports.
IPv6SubnetPrefixLength = "64"
// TapDeviceNamePrefix holds the name prefix for interfaces attached to a MicroVM.
// In a multi NetworkInterface task, there will be multiple tap ENIs attached to it.
// They follow a naming pattern 'eth<eni index>'.
TapDeviceNamePrefix = "eth"
// DefaultTapDeviceName is the name of the tap device created by CNI plugin
// which connects the MicroVM with the branch NetworkInterface.
DefaultTapDeviceName = TapDeviceNamePrefix + "0"
// Standard ENIs and branch ENIs are supported both in ECS EC2 instances and Fargate. Therefore
// the processing of those common interface types are implemented in the ECS Agent's NetworkInterface package
// (ecseni) and simply imported here. VETH and V2N interface types are supported only in Fargate
// The constants below and functionality specific to those types are implemented in this file.
// VETHInterfaceAssociationProtocol is the interface association protocol for veth interfaces.
VETHInterfaceAssociationProtocol = "veth"
// V2NInterfaceAssociationProtocol is the interface association protocol for V2N tunnel interfaces.
V2NInterfaceAssociationProtocol = "tunnel"
// GeneveInterfaceNamePattern holds pattern of GENEVE interface name:
// 'gnv<v2nVNI><destination port>'.
// We have both the VNI and destination port in the name because that is the only
// guaranteed combination that can make the name of the interface unique.
// It is important that the name is unique for all GENEVE interfaces because
// the interface is always first created in the default network namespace
// before moving it to a custom namespace.
GeneveInterfaceNamePattern = "gnv%s%d"
// DefaultGeneveInterfaceIPAddress is the IP address that will be assigned to the
// GENEVE interface created for the V2N NetworkInterface. These IP addresses are chosen because
// they come under the ECS reserved link-local IP range. By having the subnet mask as /31,
// it means there are only 2 available IPs in this chosen subnet - 169.254.175.252 and
// 169.254.175.253. We set 169.254.175.252 as the geneve interface IP and set
// 169.254.175.253 as the default default gateway in the routing rules.
// We also assign a place holder MAC address for the gateway in the ARP table. This
// configuration ensures all traffic generated in the V2N NetworkInterface's netns will pass through
// the GENEVE interface.
DefaultGeneveInterfaceIPAddress = "169.254.175.252"
DefaultGeneveInterfaceGateway = "169.254.175.253/31"
)
var (
// netInterfaces is the Interfaces() method of net package.
netInterfaces = net.Interfaces
)
// GetIPV4Addresses returns the list of IPv4 addresses assigned to the NetworkInterface.
func (ni *NetworkInterface) GetIPV4Addresses() []string {
var addresses []string
for _, addr := range ni.IPV4Addresses {
addresses = append(addresses, addr.Address)
}
return addresses
}
// GetIPV6Addresses returns the list of IPv6 addresses assigned to the NetworkInterface.
func (ni *NetworkInterface) GetIPV6Addresses() []string {
var addresses []string
for _, addr := range ni.IPV6Addresses {
addresses = append(addresses, addr.Address)
}
return addresses
}
// IPv6Only returns the bool value that indicates if the NetworkInterface has IPv6 addresses only.
func (ni *NetworkInterface) IPv6Only() bool {
return len(ni.GetIPV4Addresses()) == 0 && len(ni.GetIPV6Addresses()) > 0
}
// GetPrimaryIPv4Address returns the primary IPv4 address assigned to the NetworkInterface.
func (ni *NetworkInterface) GetPrimaryIPv4Address() string {
var primaryAddr string
for _, addr := range ni.IPV4Addresses {
if addr.Primary {
primaryAddr = addr.Address
break
}
}
return primaryAddr
}
// GetPrimaryIPv6Address returns the primary IPv6 address assigned to the NetworkInterface.
func (ni *NetworkInterface) GetPrimaryIPv6Address() string {
var primaryAddr string
for _, addr := range ni.IPV6Addresses {
if addr.Primary {
primaryAddr = addr.Address
break
}
}
return primaryAddr
}
// GetPrimaryIPv4AddressWithPrefixLength returns the primary IPv4 address assigned to the NetworkInterface with
// its subnet prefix length.
func (ni *NetworkInterface) GetPrimaryIPv4AddressWithPrefixLength() string {
return ni.GetPrimaryIPv4Address() + "/" + ni.GetIPv4SubnetPrefixLength()
}
// GetIPAddressesWithPrefixLength returns the list of all IP addresses assigned to the NetworkInterface with
// their subnet prefix length.
func (ni *NetworkInterface) GetIPAddressesWithPrefixLength() []string {
var addresses []string
for _, addr := range ni.IPV4Addresses {
addresses = append(addresses, addr.Address+"/"+ni.GetIPv4SubnetPrefixLength())
}
for _, addr := range ni.IPV6Addresses {
addresses = append(addresses, addr.Address+"/"+IPv6SubnetPrefixLength)
}
return addresses
}
// GetIPv4SubnetPrefixLength returns the IPv4 prefix length of the NetworkInterface's subnet.
func (ni *NetworkInterface) GetIPv4SubnetPrefixLength() string {
if ni.ipv4SubnetPrefixLength == "" && ni.SubnetGatewayIPV4Address != "" {
ni.ipv4SubnetPrefixLength = strings.Split(ni.SubnetGatewayIPV4Address, "/")[1]
}
return ni.ipv4SubnetPrefixLength
}
// GetIPv4SubnetCIDRBlock returns the IPv4 CIDR block, if any, of the NetworkInterface's subnet.
func (ni *NetworkInterface) GetIPv4SubnetCIDRBlock() string {
if ni.ipv4SubnetCIDRBlock == "" && ni.SubnetGatewayIPV4Address != "" {
_, ipv4Net, err := net.ParseCIDR(ni.SubnetGatewayIPV4Address)
if err == nil {
ni.ipv4SubnetCIDRBlock = ipv4Net.String()
}
}
return ni.ipv4SubnetCIDRBlock
}
// GetIPv6SubnetCIDRBlock returns the IPv6 CIDR block, if any, of the NetworkInterface's subnet.
func (ni *NetworkInterface) GetIPv6SubnetCIDRBlock() string {
if ni.ipv6SubnetCIDRBlock == "" && len(ni.IPV6Addresses) > 0 {
ipv6Addr := ni.IPV6Addresses[0].Address + "/" + IPv6SubnetPrefixLength
_, ipv6Net, err := net.ParseCIDR(ipv6Addr)
if err == nil {
ni.ipv6SubnetCIDRBlock = ipv6Net.String()
}
}
return ni.ipv6SubnetCIDRBlock
}
// GetSubnetGatewayIPv4Address returns the subnet gateway IPv4 address for the NetworkInterface.
func (ni *NetworkInterface) GetSubnetGatewayIPv4Address() string {
var gwAddr string
if ni.SubnetGatewayIPV4Address != "" {
gwAddr = strings.Split(ni.SubnetGatewayIPV4Address, "/")[0]
}
return gwAddr
}
// GetHostname returns the hostname assigned to the NetworkInterface
func (ni *NetworkInterface) GetHostname() string {
return ni.PrivateDNSName
}
// GetLinkName returns the name of the NetworkInterface on the instance.
func (ni *NetworkInterface) GetLinkName() string {
ni.guard.Lock()
defer ni.guard.Unlock()
if ni.LinkName == "" {
// Find all interfaces on the instance.
ifaces, err := netInterfaces()
if err != nil {
logger.Error("Failed to find link name:", logger.Fields{
loggerfield.Error: err,
})
return ""
}
// Iterate over the list and find the interface with the NetworkInterface's MAC address.
for _, iface := range ifaces {
if strings.EqualFold(ni.MacAddress, iface.HardwareAddr.String()) {
ni.LinkName = iface.Name
break
}
}
// If the NetworkInterface is not matched by MAC address above, we will fail to
// assign the LinkName. Log that here since CNI will fail with the empty
// name.
if ni.LinkName == "" {
logger.Error("Failed to find LinkName for given MAC", logger.Fields{
"mac": ni.MacAddress,
})
}
}
return ni.LinkName
}
// IsStandardENI returns true if the NetworkInterface is a standard/regular NetworkInterface. That is, if it
// has its association protocol as standard. To be backwards compatible, if the
// association protocol is not set for an NetworkInterface, it's considered a standard NetworkInterface as well.
func (ni *NetworkInterface) IsStandardENI() bool {
switch ni.InterfaceAssociationProtocol {
case "", DefaultInterfaceAssociationProtocol:
return true
case VLANInterfaceAssociationProtocol:
return false
default:
return false
}
}
// String returns a human-readable version of the NetworkInterface object
func (ni *NetworkInterface) String() string {
var ipv4Addresses []string
for _, addr := range ni.IPV4Addresses {
ipv4Addresses = append(ipv4Addresses, addr.Address)
}
var ipv6Addresses []string
for _, addr := range ni.IPV6Addresses {
ipv6Addresses = append(ipv6Addresses, addr.Address)
}
eniString := ""
if len(ni.InterfaceAssociationProtocol) == 0 {
eniString += fmt.Sprintf(" ,NetworkInterface type: [%s]", ni.InterfaceAssociationProtocol)
}
if ni.InterfaceVlanProperties != nil {
eniString += fmt.Sprintf(" ,VLan ID: [%s], TrunkInterfaceMacAddress: [%s]",
ni.InterfaceVlanProperties.VlanID, ni.InterfaceVlanProperties.TrunkInterfaceMacAddress)
}
return fmt.Sprintf(
"eni id:%s, mac: %s, hostname: %s, ipv4addresses: [%s], ipv6addresses: [%s], dns: [%s], dns search: [%s],"+
" gateway ipv4: [%s][%s]", ni.ID, ni.MacAddress, ni.GetHostname(), strings.Join(ipv4Addresses, ","),
strings.Join(ipv6Addresses, ","), strings.Join(ni.DomainNameServers, ","),
strings.Join(ni.DomainNameSearchList, ","), ni.SubnetGatewayIPV4Address, eniString)
}
// IPV4Address is the ipv4 information of the eni
type IPV4Address struct {
// Primary indicates whether the ip address is primary
Primary bool
// Address is the ipv4 address associated with eni
Address string
}
// IPV6Address is the ipv6 information of the eni
type IPV6Address struct {
// Primary indicates whether the ip address is primary
Primary bool
// Address is the ipv6 address associated with eni
Address string
}
// InterfaceFromACS validates the given ACS NetworkInterface information and creates an NetworkInterface object from it.
func InterfaceFromACS(acsENI *ecsacs.ElasticNetworkInterface) (*NetworkInterface, error) {
err := ValidateENI(acsENI)
if err != nil {
return nil, err
}
var ipv4Addrs []*IPV4Address
var ipv6Addrs []*IPV6Address
// Read IPv4 address information of the NetworkInterface.
for _, ec2Ipv4 := range acsENI.Ipv4Addresses {
ipv4Addrs = append(ipv4Addrs, &IPV4Address{
Primary: aws.ToBool(ec2Ipv4.Primary),
Address: aws.ToString(ec2Ipv4.PrivateAddress),
})
}
// Read IPv6 address information of the NetworkInterface.
firstIpv6 := true
for _, ec2Ipv6 := range acsENI.Ipv6Addresses {
ipv6Addrs = append(ipv6Addrs, &IPV6Address{
// TODO: Primary field is not available yet so set the first one to be primary for now.
Primary: firstIpv6,
Address: aws.ToString(ec2Ipv6.Address),
})
firstIpv6 = false
}
ni := &NetworkInterface{
ID: aws.ToString(acsENI.Ec2Id),
MacAddress: aws.ToString(acsENI.MacAddress),
IPV4Addresses: ipv4Addrs,
IPV6Addresses: ipv6Addrs,
SubnetGatewayIPV4Address: aws.ToString(acsENI.SubnetGatewayIpv4Address),
PrivateDNSName: aws.ToString(acsENI.PrivateDnsName),
InterfaceAssociationProtocol: aws.ToString(acsENI.InterfaceAssociationProtocol),
}
// Read NetworkInterface association properties.
if aws.ToString(acsENI.InterfaceAssociationProtocol) == VLANInterfaceAssociationProtocol {
var interfaceVlanProperties InterfaceVlanProperties
interfaceVlanProperties.TrunkInterfaceMacAddress = aws.ToString(acsENI.InterfaceVlanProperties.TrunkInterfaceMacAddress)
interfaceVlanProperties.VlanID = aws.ToString(acsENI.InterfaceVlanProperties.VlanId)
ni.InterfaceVlanProperties = &interfaceVlanProperties
ni.InterfaceAssociationProtocol = VLANInterfaceAssociationProtocol
}
for _, nameserverIP := range acsENI.DomainNameServers {
ni.DomainNameServers = append(ni.DomainNameServers, aws.ToString(nameserverIP))
}
for _, nameserverDomain := range acsENI.DomainName {
ni.DomainNameSearchList = append(ni.DomainNameSearchList, aws.ToString(nameserverDomain))
}
return ni, nil
}
// ValidateENI validates the NetworkInterface information sent from ACS.
func ValidateENI(acsENI *ecsacs.ElasticNetworkInterface) error {
ipv6OnlyTask := len(acsENI.Ipv6Addresses) > 0 && len(acsENI.Ipv4Addresses) == 0
if ipv6OnlyTask {
// TODO: check subnet gateway for IPv6
} else {
// At least one IPv4 address should be associated with the NetworkInterface.
if len(acsENI.Ipv4Addresses) < 1 {
return errors.Errorf("eni message validation: no ipv4 addresses in the message")
}
if acsENI.SubnetGatewayIpv4Address == nil {
return errors.Errorf("eni message validation: no subnet gateway ipv4 address in the message")
}
gwIPv4Addr := aws.ToString(acsENI.SubnetGatewayIpv4Address)
s := strings.Split(gwIPv4Addr, "/")
if len(s) != 2 {
return errors.Errorf(
"eni message validation: invalid subnet gateway ipv4 address %s", gwIPv4Addr)
}
}
if acsENI.MacAddress == nil {
return errors.Errorf("eni message validation: empty eni mac address in the message")
}
if acsENI.Ec2Id == nil {
return errors.Errorf("eni message validation: empty eni id in the message")
}
// The association protocol, if specified, must be a supported value.
if (acsENI.InterfaceAssociationProtocol != nil) &&
(aws.ToString(acsENI.InterfaceAssociationProtocol) != VLANInterfaceAssociationProtocol) &&
(aws.ToString(acsENI.InterfaceAssociationProtocol) != DefaultInterfaceAssociationProtocol) {
return errors.Errorf("invalid interface association protocol: %s",
aws.ToString(acsENI.InterfaceAssociationProtocol))
}
// If the interface association protocol is vlan, InterfaceVlanProperties must be specified.
if aws.ToString(acsENI.InterfaceAssociationProtocol) == VLANInterfaceAssociationProtocol {
if acsENI.InterfaceVlanProperties == nil ||
len(aws.ToString(acsENI.InterfaceVlanProperties.VlanId)) == 0 ||
len(aws.ToString(acsENI.InterfaceVlanProperties.TrunkInterfaceMacAddress)) == 0 {
return errors.New("vlan interface properties missing")
}
}
return nil
}
// New creates a new NetworkInterface model.
func New(
acsENI *ecsacs.ElasticNetworkInterface,
guestNetNSName string,
ifaceList []*ecsacs.ElasticNetworkInterface,
macToName map[string]string,
) (*NetworkInterface, error) {
var err error
var networkInterface *NetworkInterface
interfaceAssociationProtocol := aws.ToString(acsENI.InterfaceAssociationProtocol)
switch interfaceAssociationProtocol {
case V2NInterfaceAssociationProtocol:
// In case of V2N ENIs, network tunnel properties need to be included in the NetworkInterface object.
// This will be used in creating the GENEVE interface to connect the baremetal host and the BigMac tunnel.
networkInterface, err = v2nTunnelFromACS(acsENI)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal interface tunnel properties")
}
case VETHInterfaceAssociationProtocol:
networkInterface, err = vethPairFromACS(acsENI, ifaceList)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal interface veth properties")
}
// Standard ENIs (Default) and branch ENIs (VLANInterfaceAssociationProtocol) are both processed
// by the common NetworkInterface handler.
default:
// Acquire the NetworkInterface information from the payload.
networkInterface, err = InterfaceFromACS(acsENI)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal interface model")
}
// Historically, if there is no interface association protocol in the NetworkInterface payload, we assume
// it is a standard NetworkInterface. For more context, see the `IsStandardENI()` method in the `ecseni` model.
if networkInterface.InterfaceAssociationProtocol == "" {
networkInterface.InterfaceAssociationProtocol = DefaultInterfaceAssociationProtocol
}
}
networkInterface.Index = aws.ToInt64(acsENI.Index)
networkInterface.Name = GetInterfaceName(acsENI)
networkInterface.KnownStatus = status.NetworkNone
networkInterface.DesiredStatus = status.NetworkReadyPull
networkInterface.GuestNetNSName = guestNetNSName
if err = networkInterface.setDeviceName(macToName); err != nil {
return nil, err
}
return networkInterface, nil
}
// setDeviceName sets the device name for the NetworkInterface based on its type and MAC address.
func (ni *NetworkInterface) setDeviceName(macToName map[string]string) error {
switch ni.InterfaceAssociationProtocol {
case DefaultInterfaceAssociationProtocol:
name, ok := macToName[ni.MacAddress]
if !ok {
// This should never happen in theory. ACS can only send the payload message once
// Agent has acknowledged attachment on the instance. However, we have found that
// in some instances, EC2 detaches the NetworkInterface after attaching it when there are
// asynchornous errors in the NetworkInterface attachment workflow. We return a typed error in
// such scenarios to ensure that we don't get paged when that happens.
return NewUnableToFindENIError(ni.MacAddress, ni.InterfaceAssociationProtocol)
}
ni.DeviceName = name
case VLANInterfaceAssociationProtocol:
// We don't need to find the name for a branch NetworkInterface as it's just a vlan association.
// Get the name of the trunk interface since we need it for constructing the
// name of the branch interface.
trunkName, ok := macToName[ni.InterfaceVlanProperties.TrunkInterfaceMacAddress]
if !ok {
// Same as above. Guard against edge-cases where we're unable to find the trunk
// NetworkInterface because of internal EC2 errors.
return NewUnableToFindENIError(
ni.InterfaceVlanProperties.TrunkInterfaceMacAddress, ni.InterfaceAssociationProtocol)
}
// Name of the branch is based on the vlan id and the name of the trunk.
// Example: eth1.24, where trunk is attached as `eth1` and vlan id is `24`.
ni.DeviceName = fmt.Sprintf("%s.%s", trunkName, ni.InterfaceVlanProperties.VlanID)
default:
// Do nothing.
}
return nil
}
// IsPrimary returns whether the NetworkInterface is the primary NetworkInterface of the task.
func (ni *NetworkInterface) IsPrimary() bool {
return ni.Default
}
// ShouldGenerateNetworkConfigFiles can be used to check if network configuration files (hosts,
// hostname and resolv.conf) need to be generated using this eni's information. In case of warmpool,
// network config files should only be generated for primary ENIs. But as part of multi-NetworkInterface implementation
// it was decided that for firecracker platform the files had to be generated for secondary ENIs as well.
// Hence the NetworkInterface IsPrimary check was moved from here to warmpool specific APIs.
func (ni *NetworkInterface) ShouldGenerateNetworkConfigFiles() bool {
return ni.DesiredStatus == status.NetworkReadyPull
}
// GetInterfaceName creates the NetworkInterface name from the NetworkInterface mac address in case it is empty in the ACS payload.
func GetInterfaceName(acsENI *ecsacs.ElasticNetworkInterface) string {
if acsENI.Name != nil {
return aws.ToString(acsENI.Name)
}
return strings.ReplaceAll(aws.ToString(acsENI.MacAddress), ":", "")
}
// v2nTunnelFromACS creates an NetworkInterface model with V2N tunnel properties from the ACS NetworkInterface payload.
func v2nTunnelFromACS(acsENI *ecsacs.ElasticNetworkInterface) (*NetworkInterface, error) {
// We only require the association protocol and the mac address.
// Mac address is needed primarily because according to current logic, this mac address is assigned to the
// interface that gets attached inside the MicroVM. The mac address is also used to find the interface inside
// the MicroVM to move it to the desired network namespace when the network topology inside the MicroVM is setup.
acsTunnelProperties := acsENI.InterfaceTunnelProperties
if acsTunnelProperties == nil {
return nil, errors.New("interface tunnel properties not found in payload")
}
if acsTunnelProperties.TunnelId == nil {
return nil, errors.New("tunnel ID not found in payload")
}
if acsTunnelProperties.InterfaceIpAddress == nil {
return nil, errors.New("tunnel interface IP not found in payload")
}
return &NetworkInterface{
InterfaceAssociationProtocol: V2NInterfaceAssociationProtocol,
SubnetGatewayIPV4Address: DefaultGeneveInterfaceGateway,
IPV4Addresses: []*IPV4Address{
{
Address: DefaultGeneveInterfaceIPAddress,
},
},
DomainNameServers: aws.ToStringSlice(acsENI.DomainNameServers),
DomainNameSearchList: aws.ToStringSlice(acsENI.DomainName),
TunnelProperties: &TunnelProperties{
ID: aws.ToString(acsTunnelProperties.TunnelId),
DestinationIPAddress: aws.ToString(acsTunnelProperties.InterfaceIpAddress),
},
},
nil
}
// vethPairFromACS creates an NetworkInterface model with veth pair properties from the ACS NetworkInterface payload.
func vethPairFromACS(
acsENI *ecsacs.ElasticNetworkInterface,
ifaceList []*ecsacs.ElasticNetworkInterface) (*NetworkInterface, error) {
if acsENI.InterfaceVethProperties == nil ||
acsENI.InterfaceVethProperties.PeerInterface == nil {
return nil, errors.New("interface veth properties not found in payload")
}
peerName := aws.ToString(acsENI.InterfaceVethProperties.PeerInterface)
var peerInterface *ecsacs.ElasticNetworkInterface
for _, iface := range ifaceList {
if aws.ToString(iface.Name) == peerName {
peerInterface = iface
}
}
if aws.ToString(peerInterface.InterfaceAssociationProtocol) == VETHInterfaceAssociationProtocol {
return nil, errors.New("peer interface cannot be veth")
}
return &NetworkInterface{
InterfaceAssociationProtocol: VETHInterfaceAssociationProtocol,
// DNS related data for VETH interface will be copied from the peer interface's DNS data.
// This is because if default traffic of the container needs to use the VETH interface,
// domain name resolution will be based on the DNS config of the peer interface.
DomainNameServers: aws.ToStringSlice(peerInterface.DomainNameServers),
DomainNameSearchList: aws.ToStringSlice(peerInterface.DomainName),
VETHProperties: &VETHProperties{
PeerInterfaceName: aws.ToString(acsENI.InterfaceVethProperties.PeerInterface),
},
},
nil
}
// NetNSName returns the netns name that the specified network interface will be attached to in a desired task.
func NetNSName(taskID, eniName string) string {
return fmt.Sprintf("%s-%s", taskID, eniName)
}