Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge branch 'ipv6' into dev #2646

Merged
merged 11 commits into from
Sep 22, 2020
1 change: 1 addition & 0 deletions agent/acs/handler/acs_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const (
"ipv6Addresses": [{
"address": "ipv6"
}],
"subnetGatewayIpv4Address": "ipv4/20",
"macAddress": "mac"
}],
"roleCredentials": {
Expand Down
10 changes: 6 additions & 4 deletions agent/acs/handler/payload_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -724,7 +724,8 @@ func TestPayloadHandlerAddedENIToTask(t *testing.T) {
Address: aws.String("ipv6"),
},
},
MacAddress: aws.String("mac"),
SubnetGatewayIpv4Address: aws.String("ipv4/20"),
MacAddress: aws.String("mac"),
},
},
},
Expand All @@ -733,7 +734,7 @@ func TestPayloadHandlerAddedENIToTask(t *testing.T) {
}

err := tester.payloadHandler.handleSingleMessage(payloadMessage)
assert.NoError(t, err)
require.NoError(t, err)

// Validate the added task has the eni information as expected
expectedENI := payloadMessage.Tasks[0].ElasticNetworkInterfaces[0]
Expand Down Expand Up @@ -849,7 +850,8 @@ func TestPayloadHandlerAddedENITrunkToTask(t *testing.T) {
Address: aws.String("ipv6"),
},
},
MacAddress: aws.String("mac"),
SubnetGatewayIpv4Address: aws.String("ipv4/20"),
MacAddress: aws.String("mac"),
InterfaceVlanProperties: &ecsacs.NetworkInterfaceVlanProperties{
VlanId: aws.String("12345"),
TrunkInterfaceMacAddress: aws.String("mac"),
Expand All @@ -862,7 +864,7 @@ func TestPayloadHandlerAddedENITrunkToTask(t *testing.T) {
}

err := tester.payloadHandler.handleSingleMessage(payloadMessage)
assert.NoError(t, err)
require.NoError(t, err)

taskeni := addedTask.GetPrimaryENI()

Expand Down
127 changes: 106 additions & 21 deletions agent/api/eni/eni.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package eni

import (
"fmt"
"net"
"strings"

"github.com/aws/amazon-ecs-agent/agent/acs/model/ecsacs"
Expand All @@ -26,26 +27,34 @@ import (
type ENI struct {
// ID is the id of eni
ID string `json:"ec2Id"`
// InterfaceAssociationProtocol is the type of ENI, valid value: "default", "vlan"
InterfaceAssociationProtocol string `json:",omitempty"`
// MacAddress is the mac address of the eni
MacAddress string
// IPV4Addresses is the ipv4 address associated with the eni
IPV4Addresses []*ENIIPV4Address
// IPV6Addresses is the ipv6 address associated with the eni
IPV6Addresses []*ENIIPV6Address
// MacAddress is the mac address of the eni
MacAddress string
// SubnetGatewayIPV4Address is the IPv4 address of the subnet gateway of the ENI
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 ENI, valid value: "default", "vlan"
InterfaceAssociationProtocol string `json:",omitempty"`
// InterfaceVlanProperties contains information for an interface
// that is supposed to be used as a VLAN device
InterfaceVlanProperties *InterfaceVlanProperties `json:",omitempty"`
// PrivateDNSName is the dns name assigned by the vpc to this eni
PrivateDNSName string `json:",omitempty"`
// SubnetGatewayIPV4Address is the address to the subnet gateway for the eni
SubnetGatewayIPV4Address string `json:",omitempty"`

// Due to historical reasons, the IPv4 subnet prefix length is sent with IPv4 subnet gateway
// address instead of the ENI'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
}

// InterfaceVlanProperties contains information for an interface that
Expand All @@ -61,9 +70,16 @@ const (

// VLANInterfaceAssociationProtocol represents the ENI 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 ENI 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"
)

// GetIPV4Addresses returns a list of ipv4 addresses allocated to the ENI
// GetIPV4Addresses returns the list of IPv4 addresses assigned to the ENI.
func (eni *ENI) GetIPV4Addresses() []string {
var addresses []string
for _, addr := range eni.IPV4Addresses {
Expand All @@ -73,7 +89,17 @@ func (eni *ENI) GetIPV4Addresses() []string {
return addresses
}

// GetPrimaryIPv4Address returns the primary IPv4 address associated with the ENI.
// GetIPV6Addresses returns the list of IPv6 addresses assigned to the ENI.
func (eni *ENI) GetIPV6Addresses() []string {
var addresses []string
for _, addr := range eni.IPV6Addresses {
addresses = append(addresses, addr.Address)
}

return addresses
}

// GetPrimaryIPv4Address returns the primary IPv4 address assigned to the ENI.
func (eni *ENI) GetPrimaryIPv4Address() string {
var primaryAddr string
for _, addr := range eni.IPV4Addresses {
Expand All @@ -86,27 +112,75 @@ func (eni *ENI) GetPrimaryIPv4Address() string {
return primaryAddr
}

// GetIPV6Addresses returns a list of ipv6 addresses allocated to the ENI
func (eni *ENI) GetIPV6Addresses() []string {
// GetPrimaryIPv4AddressWithPrefixLength returns the primary IPv4 address assigned to the ENI with
// its subnet prefix length.
func (eni *ENI) GetPrimaryIPv4AddressWithPrefixLength() string {
return eni.GetPrimaryIPv4Address() + "/" + eni.GetIPv4SubnetPrefixLength()
}

// GetIPAddressesWithPrefixLength returns the list of all IP addresses assigned to the ENI with
// their subnet prefix length.
func (eni *ENI) GetIPAddressesWithPrefixLength() []string {
var addresses []string
for _, addr := range eni.IPV4Addresses {
addresses = append(addresses, addr.Address+"/"+eni.GetIPv4SubnetPrefixLength())
}
for _, addr := range eni.IPV6Addresses {
addresses = append(addresses, addr.Address)
addresses = append(addresses, addr.Address+"/"+IPv6SubnetPrefixLength)
}

return addresses
}

// GetIPv4SubnetPrefixLength returns the IPv4 prefix length of the ENI's subnet.
func (eni *ENI) GetIPv4SubnetPrefixLength() string {
if eni.ipv4SubnetPrefixLength == "" && eni.SubnetGatewayIPV4Address != "" {
eni.ipv4SubnetPrefixLength = strings.Split(eni.SubnetGatewayIPV4Address, "/")[1]
}

return eni.ipv4SubnetPrefixLength
}

// GetIPv4SubnetCIDRBlock returns the IPv4 CIDR block, if any, of the ENI's subnet.
func (eni *ENI) GetIPv4SubnetCIDRBlock() string {
if eni.ipv4SubnetCIDRBlock == "" && eni.SubnetGatewayIPV4Address != "" {
_, ipv4Net, err := net.ParseCIDR(eni.SubnetGatewayIPV4Address)
if err == nil {
eni.ipv4SubnetCIDRBlock = ipv4Net.String()
}
}

return eni.ipv4SubnetCIDRBlock
}

// GetIPv6SubnetCIDRBlock returns the IPv6 CIDR block, if any, of the ENI's subnet.
func (eni *ENI) GetIPv6SubnetCIDRBlock() string {
if eni.ipv6SubnetCIDRBlock == "" && len(eni.IPV6Addresses) > 0 {
ipv6Addr := eni.IPV6Addresses[0].Address + "/" + IPv6SubnetPrefixLength
_, ipv6Net, err := net.ParseCIDR(ipv6Addr)
if err == nil {
eni.ipv6SubnetCIDRBlock = ipv6Net.String()
}
}

return eni.ipv6SubnetCIDRBlock
}

// GetSubnetGatewayIPv4Address returns the subnet gateway IPv4 address for the ENI.
func (eni *ENI) GetSubnetGatewayIPv4Address() string {
var gwAddr string
if eni.SubnetGatewayIPV4Address != "" {
gwAddr = strings.Split(eni.SubnetGatewayIPV4Address, "/")[0]
}

return gwAddr
}

// GetHostname returns the hostname assigned to the ENI
func (eni *ENI) GetHostname() string {
return eni.PrivateDNSName
}

// GetSubnetGatewayIPV4Address returns the subnet IPv4 gateway address assigned
// to the ENI
func (eni *ENI) GetSubnetGatewayIPV4Address() string {
return eni.SubnetGatewayIPV4Address
}

// IsStandardENI returns true if the ENI is a standard/regular ENI. That is, if it
// has its association protocol as standard. To be backwards compatible, if the
// association protocol is not set for an ENI, it's considered a standard ENI as well.
Expand Down Expand Up @@ -189,6 +263,7 @@ func ENIFromACS(acsENI *ecsacs.ElasticNetworkInterface) (*ENI, error) {
})
}

// Read ENI association properties.
var interfaceVlanProperties InterfaceVlanProperties

if aws.StringValue(acsENI.InterfaceAssociationProtocol) == VLANInterfaceAssociationProtocol {
Expand All @@ -198,11 +273,11 @@ func ENIFromACS(acsENI *ecsacs.ElasticNetworkInterface) (*ENI, error) {

eni := &ENI{
ID: aws.StringValue(acsENI.Ec2Id),
MacAddress: aws.StringValue(acsENI.MacAddress),
IPV4Addresses: ipv4Addrs,
IPV6Addresses: ipv6Addrs,
MacAddress: aws.StringValue(acsENI.MacAddress),
PrivateDNSName: aws.StringValue(acsENI.PrivateDnsName),
SubnetGatewayIPV4Address: aws.StringValue(acsENI.SubnetGatewayIpv4Address),
PrivateDNSName: aws.StringValue(acsENI.PrivateDnsName),
InterfaceAssociationProtocol: aws.StringValue(acsENI.InterfaceAssociationProtocol),
InterfaceVlanProperties: &interfaceVlanProperties,
}
Expand All @@ -224,6 +299,16 @@ func ValidateTaskENI(acsENI *ecsacs.ElasticNetworkInterface) error {
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.StringValue(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")
}
Expand Down
Loading