Skip to content

Commit 1a4864c

Browse files
committed
SubnetGatewayIPV6Address and IPv6SubnetCIDRBlock metadata for IPv6-only task ENIs
1 parent 0e29ce7 commit 1a4864c

File tree

4 files changed

+121
-2
lines changed

4 files changed

+121
-2
lines changed

agent/handlers/task_server_setup_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,49 @@ func expectedTaskResponse() v2.TaskResponse {
648648
}
649649
}
650650

651+
// standardV4ContainerResponseAWSVPC returns a standard TMDS v4 container response for testing.
652+
// The response corresponds to the standard awsvpc container defined in this package.
653+
func standardV4ContainerResponseAWSVPC() *v4.ContainerResponse {
654+
return &v4.ContainerResponse{
655+
ContainerResponse: &v2.ContainerResponse{
656+
ID: containerID,
657+
Name: containerName,
658+
DockerName: containerName,
659+
Image: imageName,
660+
ImageID: imageID,
661+
DesiredStatus: statusRunning,
662+
KnownStatus: statusRunning,
663+
ContainerARN: "arn:aws:ecs:ap-northnorth-1:NNN:container/NNNNNNNN-aaaa-4444-bbbb-00000000000",
664+
Limits: v2.LimitsResponse{
665+
CPU: aws.Float64(cpu),
666+
Memory: aws.Int64(memory),
667+
},
668+
Type: containerType,
669+
Labels: labels,
670+
Ports: []tmdsresponse.PortResponse{
671+
{
672+
ContainerPort: containerPort,
673+
Protocol: containerPortProtocol,
674+
HostPort: containerPort,
675+
},
676+
},
677+
},
678+
Networks: []v4.Network{{
679+
Network: tmdsresponse.Network{
680+
NetworkMode: utils.NetworkModeAWSVPC,
681+
IPv4Addresses: []string{eniIPv4Address},
682+
},
683+
NetworkInterfaceProperties: v4.NetworkInterfaceProperties{
684+
AttachmentIndex: &attachmentIndexVar,
685+
IPV4SubnetCIDRBlock: iPv4SubnetCIDRBlock,
686+
MACAddress: macAddress,
687+
PrivateDNSName: privateDNSName,
688+
SubnetGatewayIPV4Address: subnetGatewayIpv4Address,
689+
}},
690+
},
691+
}
692+
}
693+
651694
// Creates a v4 ContainerResponse given a v2 ContainerResponse and v4 networks
652695
func v4ContainerResponseFromV2(
653696
v2ContainerResponse v2.ContainerResponse, networks []v4.Network) v4.ContainerResponse {
@@ -2040,6 +2083,65 @@ func TestV4ContainerMetadata(t *testing.T) {
20402083
expectedResponseBody: expectedV4ContainerResponse,
20412084
})
20422085
})
2086+
t.Run("happy case awsvpc task - dual stack case", func(t *testing.T) {
2087+
testTMDSRequest(t, TMDSTestCase[v4.ContainerResponse]{
2088+
path: v4BasePath + v3EndpointID,
2089+
setStateExpectations: func(state *mock_dockerstate.MockTaskEngineState) {
2090+
task := standardTask()
2091+
task.ENIs[0].SubnetGatewayIPV4Address = "1.2.3.1/24"
2092+
task.ENIs[0].SubnetGatewayIPV6Address = "8:3:1:1:1::/64"
2093+
task.ENIs[0].IPV4Addresses = []*ni.IPV4Address{{Address: "1.2.3.5"}}
2094+
task.ENIs[0].IPV6Addresses = []*ni.IPV6Address{{Address: "8:3:1:1:a::"}}
2095+
gomock.InOrder(
2096+
state.EXPECT().DockerIDByV3EndpointID(v3EndpointID).Return(containerID, true),
2097+
state.EXPECT().ContainerByID(containerID).Return(dockerContainer, true).AnyTimes(),
2098+
state.EXPECT().TaskByID(containerID).Return(task, true).Times(2),
2099+
state.EXPECT().ContainerByID(containerID).Return(dockerContainer, true).AnyTimes(),
2100+
)
2101+
},
2102+
expectedStatusCode: http.StatusOK,
2103+
expectedResponseBody: func() v4.ContainerResponse {
2104+
response := standardV4ContainerResponseAWSVPC()
2105+
response.Networks[0].SubnetGatewayIPV4Address = "1.2.3.1/24"
2106+
// subnet gateway IPv6 address not populated for dual-stack ENIs for historical reasons
2107+
response.Networks[0].SubnetGatewayIPV6Address = ""
2108+
response.Networks[0].IPV4SubnetCIDRBlock = "1.2.3.0/24"
2109+
response.Networks[0].IPv6SubnetCIDRBlock = "8:3:1:1::/64"
2110+
response.Networks[0].IPv4Addresses = []string{"1.2.3.5"}
2111+
response.Networks[0].IPv6Addresses = []string{"8:3:1:1:a::"}
2112+
return *response
2113+
}(),
2114+
})
2115+
})
2116+
t.Run("happy case awsvpc task - IPv6-only case", func(t *testing.T) {
2117+
testTMDSRequest(t, TMDSTestCase[v4.ContainerResponse]{
2118+
path: v4BasePath + v3EndpointID,
2119+
setStateExpectations: func(state *mock_dockerstate.MockTaskEngineState) {
2120+
task := standardTask()
2121+
task.ENIs[0].SubnetGatewayIPV4Address = ""
2122+
task.ENIs[0].SubnetGatewayIPV6Address = "8:3:1:1::/48"
2123+
task.ENIs[0].IPV4Addresses = nil
2124+
task.ENIs[0].IPV6Addresses = []*ni.IPV6Address{{Address: "8:3:1:9::"}}
2125+
gomock.InOrder(
2126+
state.EXPECT().DockerIDByV3EndpointID(v3EndpointID).Return(containerID, true),
2127+
state.EXPECT().ContainerByID(containerID).Return(dockerContainer, true).AnyTimes(),
2128+
state.EXPECT().TaskByID(containerID).Return(task, true).Times(2),
2129+
state.EXPECT().ContainerByID(containerID).Return(dockerContainer, true).AnyTimes(),
2130+
)
2131+
},
2132+
expectedStatusCode: http.StatusOK,
2133+
expectedResponseBody: func() v4.ContainerResponse {
2134+
response := standardV4ContainerResponseAWSVPC()
2135+
response.Networks[0].SubnetGatewayIPV4Address = ""
2136+
response.Networks[0].SubnetGatewayIPV6Address = "8:3:1:1::/48"
2137+
response.Networks[0].IPV4SubnetCIDRBlock = ""
2138+
response.Networks[0].IPv6SubnetCIDRBlock = "8:3:1::/48"
2139+
response.Networks[0].IPv4Addresses = nil
2140+
response.Networks[0].IPv6Addresses = []string{"8:3:1:9::"}
2141+
return *response
2142+
}(),
2143+
})
2144+
})
20432145
t.Run("bridge mode container not found during network population", func(t *testing.T) {
20442146
testTMDSRequest(t, TMDSTestCase[string]{
20452147
path: v4BasePath + v3EndpointID,

agent/handlers/v4/response.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ func newNetworkInterfaceProperties(task *apitask.Task) (tmdsv4.NetworkInterfaceP
161161
attachmentIndexPtr = &vpcIndex
162162
}
163163

164+
var subnetGatewayIPv6Address string
165+
if eni.IPv6Only() {
166+
// Only populate SubnetGatewayIPv6Address for IPv6-only task ENIs as we do not
167+
// populate it for dual-stack ENIs for historical reasons
168+
subnetGatewayIPv6Address = eni.SubnetGatewayIPV6Address
169+
}
164170
return tmdsv4.NetworkInterfaceProperties{
165171
// TODO this is hard-coded to `0` for now. Once backend starts populating
166172
// `Index` field for an ENI, we should set it as per that. Since we
@@ -173,6 +179,7 @@ func newNetworkInterfaceProperties(task *apitask.Task) (tmdsv4.NetworkInterfaceP
173179
DomainNameSearchList: eni.DomainNameSearchList,
174180
PrivateDNSName: eni.PrivateDNSName,
175181
SubnetGatewayIPV4Address: eni.SubnetGatewayIPV4Address,
182+
SubnetGatewayIPV6Address: subnetGatewayIPv6Address,
176183
}, nil
177184
}
178185

agent/vendor/github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/networkinterface/networkinterface.go

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ecs-agent/netlib/model/networkinterface/networkinterface.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,12 @@ func (ni *NetworkInterface) GetIPv4SubnetCIDRBlock() string {
296296
// GetIPv6SubnetCIDRBlock returns the IPv6 CIDR block, if any, of the NetworkInterface's subnet.
297297
func (ni *NetworkInterface) GetIPv6SubnetCIDRBlock() string {
298298
if ni.ipv6SubnetCIDRBlock == "" && len(ni.IPV6Addresses) > 0 {
299-
ipv6Addr := ni.IPV6Addresses[0].Address + "/" + IPv6SubnetPrefixLength
299+
// Hardcoded prefix length is used for dual-stack ENIs for historical reasons
300+
prefixLength := IPv6SubnetPrefixLength
301+
if ni.IPv6Only() {
302+
prefixLength = ni.GetIPv6SubnetPrefixLength()
303+
}
304+
ipv6Addr := ni.IPV6Addresses[0].Address + "/" + prefixLength
300305
_, ipv6Net, err := net.ParseCIDR(ipv6Addr)
301306
if err == nil {
302307
ni.ipv6SubnetCIDRBlock = ipv6Net.String()

0 commit comments

Comments
 (0)