// 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"`
	// SubnetGatewayIPV6Address is the IPv6 address of the subnet gateway of the NetworkInterface
	SubnetGatewayIPV6Address 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"
	NetworkInterfaceDeviceName      = "eth1" // default network interface name in the task network namespace.
)

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())
	}

	if ni.IPv6Only() {
		for _, addr := range ni.IPV6Addresses {
			addresses = append(addresses, addr.Address+"/"+ni.GetIPv6SubnetPrefixLength())
		}
	} else {
		// To be backwards compatible, we should use this for dual stack tasks.
		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
}

// GetIPv6SubnetPrefixLength returns the IPv6 prefix length of the NetworkInterface's subnet.
func (ni *NetworkInterface) GetIPv6SubnetPrefixLength() string {
	prefixLen := IPv6SubnetPrefixLength
	if strings.Contains(ni.SubnetGatewayIPV6Address, "/") {
		prefixLen = strings.Split(ni.SubnetGatewayIPV6Address, "/")[1]
	}
	return prefixLen
}

// 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 {
		// Hardcoded prefix length is used for dual-stack ENIs for historical reasons
		prefixLength := IPv6SubnetPrefixLength
		if ni.IPv6Only() {
			prefixLength = ni.GetIPv6SubnetPrefixLength()
		}
		ipv6Addr := ni.IPV6Addresses[0].Address + "/" + prefixLength
		_, 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
}

// GetSubnetGatewayIPv6Address returns the subnet gateway IPv6 address for the NetworkInterface.
func (ni *NetworkInterface) GetSubnetGatewayIPv6Address() string {
	var gwAddr string
	if ni.SubnetGatewayIPV6Address != "" {
		gwAddr = strings.Split(ni.SubnetGatewayIPV6Address, "/")[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], gateway ipv6: [%s][%s]", ni.ID, ni.MacAddress, ni.GetHostname(), strings.Join(ipv4Addresses, ","),
		strings.Join(ipv6Addresses, ","), strings.Join(ni.DomainNameServers, ","),
		strings.Join(ni.DomainNameSearchList, ","), ni.SubnetGatewayIPV4Address,
		ni.SubnetGatewayIPV6Address, 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.
	for _, ec2Ipv6 := range acsENI.Ipv6Addresses {
		ipv6Addrs = append(ipv6Addrs, &IPV6Address{
			Primary: aws.ToBool(ec2Ipv6.Primary),
			Address: aws.ToString(ec2Ipv6.Address),
		})
	}

	ni := &NetworkInterface{
		ID:                           aws.ToString(acsENI.Ec2Id),
		MacAddress:                   aws.ToString(acsENI.MacAddress),
		IPV4Addresses:                ipv4Addrs,
		IPV6Addresses:                ipv6Addrs,
		SubnetGatewayIPV4Address:     aws.ToString(acsENI.SubnetGatewayIpv4Address),
		SubnetGatewayIPV6Address:     aws.ToString(acsENI.SubnetGatewayIpv6Address),
		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 {
	var validateSubnetGatewayAddr = func(version, addr string) error {
		s := strings.Split(addr, "/")
		if len(s) != 2 {
			return errors.Errorf(
				"eni message validation: invalid subnet gateway %s address %s", version, addr)
		}
		return nil
	}

	ipv6OnlyTask := len(acsENI.Ipv6Addresses) > 0 && len(acsENI.Ipv4Addresses) == 0
	if ipv6OnlyTask {
		if acsENI.SubnetGatewayIpv6Address == nil {
			return errors.Errorf("eni message validation: no subnet gateway ipv6 address in the message")
		}
		if err := validateSubnetGatewayAddr("ipv6", aws.ToString(acsENI.SubnetGatewayIpv6Address)); err != nil {
			return err
		}
	} 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")
		}
		if err := validateSubnetGatewayAddr("ipv4", aws.ToString(acsENI.SubnetGatewayIpv4Address)); err != nil {
			return err
		}
	}

	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.
		_, 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)
		}
		// Normalize VLAN interface names to eth1 for consistent experience and metrics collection
		ni.DeviceName = NetworkInterfaceDeviceName
	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)
}
