Skip to content

Commit e634a45

Browse files
authored
IPv6 default route for IPv6-only awsvpc tasks (#4603)
1 parent c87fccb commit e634a45

File tree

5 files changed

+301
-71
lines changed

5 files changed

+301
-71
lines changed

agent/ecscni/netconfig_linux.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,21 @@ func newIPAMConfig(cfg *Config) (IPAMConfig, error) {
114114

115115
// NewVPCENINetworkConfig creates a new vpc-eni CNI plugin configuration.
116116
func NewVPCENINetworkConfig(eni *ni.NetworkInterface, cfg *Config) (string, *libcni.NetworkConfig, error) {
117+
gatewayIPAddresses := []string{eni.GetSubnetGatewayIPv4Address()}
118+
if eni.IPv6Only() {
119+
// Populate IPv6 Subnet Gateway address only for IPv6-only case as historically it hasn't
120+
// been populated for dual-stack case.
121+
if eni.GetSubnetGatewayIPv6Address() == "" {
122+
return "", nil, fmt.Errorf("task ENI is IPv6-only but has no IPv6 subnet gateway address")
123+
}
124+
gatewayIPAddresses = []string{eni.GetSubnetGatewayIPv6Address()}
125+
}
126+
117127
eniConf := VPCENIPluginConfig{
118128
Type: VPCENIPluginName,
119129
ENIMACAddress: eni.MacAddress,
120130
ENIIPAddresses: eni.GetIPAddressesWithPrefixLength(),
121-
GatewayIPAddresses: []string{eni.GetSubnetGatewayIPv4Address()},
131+
GatewayIPAddresses: gatewayIPAddresses,
122132
BlockIMDS: cfg.BlockInstanceMetadata,
123133
}
124134

@@ -132,13 +142,23 @@ func NewVPCENINetworkConfig(eni *ni.NetworkInterface, cfg *Config) (string, *lib
132142

133143
// NewBranchENINetworkConfig creates a new branch ENI CNI network configuration.
134144
func NewBranchENINetworkConfig(eni *ni.NetworkInterface, cfg *Config) (string, *libcni.NetworkConfig, error) {
145+
gatewayIPAddresses := []string{eni.GetSubnetGatewayIPv4Address()}
146+
if eni.IPv6Only() {
147+
// Populate IPv6 Subnet Gateway address only for IPv6-only case as historically it hasn't
148+
// been populated for dual-stack case.
149+
if eni.GetSubnetGatewayIPv6Address() == "" {
150+
return "", nil, fmt.Errorf("task ENI is IPv6-only but has no IPv6 subnet gateway address")
151+
}
152+
gatewayIPAddresses = []string{eni.GetSubnetGatewayIPv6Address()}
153+
}
154+
135155
eniConf := BranchENIConfig{
136156
Type: ECSBranchENIPluginName,
137157
TrunkMACAddress: eni.InterfaceVlanProperties.TrunkInterfaceMacAddress,
138158
BranchVlanID: eni.InterfaceVlanProperties.VlanID,
139159
BranchMACAddress: eni.MacAddress,
140160
IPAddresses: eni.GetIPAddressesWithPrefixLength(),
141-
GatewayIPAddresses: []string{eni.GetSubnetGatewayIPv4Address()},
161+
GatewayIPAddresses: gatewayIPAddresses,
142162
BlockInstanceMetadata: cfg.BlockInstanceMetadata,
143163
InterfaceType: vpcCNIPluginInterfaceType,
144164
}

agent/ecscni/plugin_linux_test.go

Lines changed: 196 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -492,84 +492,215 @@ func TestReleaseIPInIPAM(t *testing.T) {
492492
// TestConstructVPCENINetworkConfig tests that NewVPCENINetworkConfig creates the correct
493493
// configuration for vpc-eni plugin
494494
func TestConstructVPCENINetworkConfig(t *testing.T) {
495-
config := &Config{
496-
ContainerID: "containerid12",
497-
ContainerPID: "pid",
498-
BlockInstanceMetadata: true,
499-
}
500-
501-
eniName, eniNetworkConfig, err := NewVPCENINetworkConfig(
502-
&ni.NetworkInterface{
503-
ID: eniID,
504-
IPV4Addresses: []*ni.IPV4Address{
505-
{Address: ipv4Address, Primary: true},
506-
},
507-
IPV6Addresses: []*ni.IPV6Address{
508-
{
509-
Address: ipv6Address,
495+
tests := []struct {
496+
name string
497+
iface *ni.NetworkInterface
498+
expectedError string
499+
expected VPCENIPluginConfig
500+
}{
501+
{
502+
name: "Dual stack - IPv6 subnet gateway is ignored due to historical reasons",
503+
iface: &ni.NetworkInterface{
504+
ID: eniID,
505+
IPV4Addresses: []*ni.IPV4Address{{Address: ipv4Address}},
506+
IPV6Addresses: []*ni.IPV6Address{
507+
{
508+
Address: ipv6Address,
509+
},
510510
},
511+
MacAddress: eniMACAddress,
512+
SubnetGatewayIPV4Address: eniSubnetGatewayIPV4Address,
513+
SubnetGatewayIPV6Address: "1:2:3:4::1",
514+
},
515+
expected: VPCENIPluginConfig{
516+
Type: "vpc-eni",
517+
ENIIPAddresses: []string{eniIPV4AddressWithBlockSize, eniIPV6AddressWithBlockSize},
518+
ENIMACAddress: eniMACAddress,
519+
BlockIMDS: true,
520+
GatewayIPAddresses: []string{eniSubnetGatewayIPV4AddressWithoutBlockSize},
511521
},
512-
MacAddress: eniMACAddress,
513-
SubnetGatewayIPV4Address: eniSubnetGatewayIPV4Address,
514522
},
515-
config)
516-
require.NoError(t, err, "Failed to construct eni network config")
517-
assert.Equal(t, "eth0", eniName)
518-
eniConfig := &VPCENIPluginConfig{}
519-
err = json.Unmarshal(eniNetworkConfig.Bytes, eniConfig)
520-
require.NoError(t, err, "unmarshal config from bytes failed")
521-
assert.Equal(t, &VPCENIPluginConfig{
522-
Type: "vpc-eni",
523-
ENIIPAddresses: []string{eniIPV4AddressWithBlockSize, eniIPV6AddressWithBlockSize},
524-
ENIMACAddress: eniMACAddress,
525-
BlockIMDS: true,
526-
GatewayIPAddresses: []string{eniSubnetGatewayIPV4AddressWithoutBlockSize},
527-
}, eniConfig)
523+
{
524+
name: "IPv4 only",
525+
iface: &ni.NetworkInterface{
526+
ID: eniID,
527+
IPV4Addresses: []*ni.IPV4Address{{Address: ipv4Address}},
528+
MacAddress: eniMACAddress,
529+
SubnetGatewayIPV4Address: eniSubnetGatewayIPV4Address,
530+
},
531+
expected: VPCENIPluginConfig{
532+
Type: "vpc-eni",
533+
ENIIPAddresses: []string{eniIPV4AddressWithBlockSize},
534+
ENIMACAddress: eniMACAddress,
535+
BlockIMDS: true,
536+
GatewayIPAddresses: []string{eniSubnetGatewayIPV4AddressWithoutBlockSize},
537+
},
538+
},
539+
{
540+
name: "IPv6 only",
541+
iface: &ni.NetworkInterface{
542+
ID: eniID,
543+
IPV6Addresses: []*ni.IPV6Address{{Address: ipv6Address}},
544+
MacAddress: eniMACAddress,
545+
SubnetGatewayIPV6Address: "1:2:3:4::1",
546+
},
547+
expected: VPCENIPluginConfig{
548+
Type: "vpc-eni",
549+
ENIIPAddresses: []string{eniIPV6AddressWithBlockSize},
550+
ENIMACAddress: eniMACAddress,
551+
BlockIMDS: true,
552+
GatewayIPAddresses: []string{"1:2:3:4::1"},
553+
},
554+
},
555+
{
556+
name: "IPv6 only - no subnet gateway address",
557+
iface: &ni.NetworkInterface{
558+
ID: eniID,
559+
IPV6Addresses: []*ni.IPV6Address{{Address: ipv6Address}},
560+
MacAddress: eniMACAddress,
561+
},
562+
expectedError: "task ENI is IPv6-only but has no IPv6 subnet gateway address",
563+
},
564+
}
565+
566+
for _, tt := range tests {
567+
t.Run(tt.name, func(t *testing.T) {
568+
config := &Config{
569+
ContainerID: "containerid12",
570+
ContainerPID: "pid",
571+
BlockInstanceMetadata: true,
572+
}
573+
eniName, eniNetworkConfig, err := NewVPCENINetworkConfig(tt.iface, config)
574+
if tt.expectedError != "" {
575+
assert.EqualError(t, err, tt.expectedError)
576+
} else {
577+
require.NoError(t, err, "Failed to construct eni network config")
578+
assert.Equal(t, "eth0", eniName)
579+
eniConfig := &VPCENIPluginConfig{}
580+
err = json.Unmarshal(eniNetworkConfig.Bytes, eniConfig)
581+
require.NoError(t, err, "unmarshal config from bytes failed")
582+
assert.Equal(t, tt.expected, *eniConfig)
583+
}
584+
})
585+
}
528586
}
529587

530588
// TestConstructBranchENINetworkConfig tests createBranchENINetworkConfig creates the correct
531589
// configuration for eni plugin
532590
func TestConstructBranchENINetworkConfig(t *testing.T) {
533-
config := &Config{
534-
ContainerID: "containerid12",
535-
ContainerPID: "pid",
536-
BlockInstanceMetadata: true,
537-
}
538-
539-
eniName, eniNetworkConfig, err := NewBranchENINetworkConfig(
540-
&ni.NetworkInterface{
541-
ID: eniID,
542-
IPV4Addresses: []*ni.IPV4Address{
543-
{Address: ipv4Address, Primary: true},
591+
tests := []struct {
592+
name string
593+
iface *ni.NetworkInterface
594+
expected BranchENIConfig
595+
expectedError string
596+
}{
597+
{
598+
name: "dual stack - IPv6 subnet gateway is ignored due to historical reasons",
599+
iface: &ni.NetworkInterface{
600+
ID: eniID,
601+
IPV4Addresses: []*ni.IPV4Address{{Address: ipv4Address}},
602+
IPV6Addresses: []*ni.IPV6Address{{Address: ipv6Address}},
603+
MacAddress: eniMACAddress,
604+
SubnetGatewayIPV4Address: eniSubnetGatewayIPV4Address,
605+
SubnetGatewayIPV6Address: "1:2:3:4::1",
606+
InterfaceVlanProperties: &ni.InterfaceVlanProperties{
607+
TrunkInterfaceMacAddress: trunkENIMACAddress,
608+
VlanID: branchENIVLANID,
609+
},
544610
},
545-
IPV6Addresses: []*ni.IPV6Address{
546-
{
547-
Address: ipv6Address,
611+
expected: BranchENIConfig{
612+
Type: "vpc-branch-eni",
613+
IPAddresses: []string{eniIPV4AddressWithBlockSize, eniIPV6AddressWithBlockSize},
614+
BranchMACAddress: eniMACAddress,
615+
BlockInstanceMetadata: true,
616+
GatewayIPAddresses: []string{eniSubnetGatewayIPV4AddressWithoutBlockSize},
617+
TrunkMACAddress: trunkENIMACAddress,
618+
BranchVlanID: branchENIVLANID,
619+
InterfaceType: "vlan",
620+
},
621+
},
622+
{
623+
name: "IPv4 only",
624+
iface: &ni.NetworkInterface{
625+
ID: eniID,
626+
IPV4Addresses: []*ni.IPV4Address{{Address: ipv4Address}},
627+
MacAddress: eniMACAddress,
628+
SubnetGatewayIPV4Address: eniSubnetGatewayIPV4Address,
629+
InterfaceVlanProperties: &ni.InterfaceVlanProperties{
630+
TrunkInterfaceMacAddress: trunkENIMACAddress,
631+
VlanID: branchENIVLANID,
548632
},
549633
},
550-
MacAddress: eniMACAddress,
551-
SubnetGatewayIPV4Address: eniSubnetGatewayIPV4Address,
552-
InterfaceVlanProperties: &ni.InterfaceVlanProperties{
553-
TrunkInterfaceMacAddress: trunkENIMACAddress,
554-
VlanID: branchENIVLANID,
634+
expected: BranchENIConfig{
635+
Type: "vpc-branch-eni",
636+
IPAddresses: []string{eniIPV4AddressWithBlockSize},
637+
BranchMACAddress: eniMACAddress,
638+
BlockInstanceMetadata: true,
639+
GatewayIPAddresses: []string{eniSubnetGatewayIPV4AddressWithoutBlockSize},
640+
TrunkMACAddress: trunkENIMACAddress,
641+
BranchVlanID: branchENIVLANID,
642+
InterfaceType: "vlan",
555643
},
556644
},
557-
config)
558-
require.NoError(t, err, "Failed to construct eni network config")
559-
assert.Equal(t, "eth0", eniName)
560-
branchENIConfig := &BranchENIConfig{}
561-
err = json.Unmarshal(eniNetworkConfig.Bytes, branchENIConfig)
562-
require.NoError(t, err, "unmarshal config from bytes failed")
563-
assert.Equal(t, &BranchENIConfig{
564-
Type: "vpc-branch-eni",
565-
IPAddresses: []string{eniIPV4AddressWithBlockSize, eniIPV6AddressWithBlockSize},
566-
BranchMACAddress: eniMACAddress,
567-
BlockInstanceMetadata: true,
568-
GatewayIPAddresses: []string{eniSubnetGatewayIPV4AddressWithoutBlockSize},
569-
TrunkMACAddress: trunkENIMACAddress,
570-
BranchVlanID: branchENIVLANID,
571-
InterfaceType: "vlan",
572-
}, branchENIConfig)
645+
{
646+
name: "IPv6 only",
647+
iface: &ni.NetworkInterface{
648+
ID: eniID,
649+
IPV6Addresses: []*ni.IPV6Address{{Address: ipv6Address}},
650+
MacAddress: eniMACAddress,
651+
SubnetGatewayIPV6Address: "1:2:3:4::1",
652+
InterfaceVlanProperties: &ni.InterfaceVlanProperties{
653+
TrunkInterfaceMacAddress: trunkENIMACAddress,
654+
VlanID: branchENIVLANID,
655+
},
656+
},
657+
expected: BranchENIConfig{
658+
Type: "vpc-branch-eni",
659+
IPAddresses: []string{eniIPV6AddressWithBlockSize},
660+
BranchMACAddress: eniMACAddress,
661+
BlockInstanceMetadata: true,
662+
GatewayIPAddresses: []string{"1:2:3:4::1"},
663+
TrunkMACAddress: trunkENIMACAddress,
664+
BranchVlanID: branchENIVLANID,
665+
InterfaceType: "vlan",
666+
},
667+
},
668+
{
669+
name: "IPv6 only - no subnet gateway address found",
670+
iface: &ni.NetworkInterface{
671+
ID: eniID,
672+
IPV6Addresses: []*ni.IPV6Address{{Address: ipv6Address}},
673+
MacAddress: eniMACAddress,
674+
InterfaceVlanProperties: &ni.InterfaceVlanProperties{
675+
TrunkInterfaceMacAddress: trunkENIMACAddress,
676+
VlanID: branchENIVLANID,
677+
},
678+
},
679+
expectedError: "task ENI is IPv6-only but has no IPv6 subnet gateway address",
680+
},
681+
}
682+
683+
for _, tt := range tests {
684+
t.Run(tt.name, func(t *testing.T) {
685+
config := &Config{
686+
ContainerID: "containerid12",
687+
ContainerPID: "pid",
688+
BlockInstanceMetadata: true,
689+
}
690+
691+
eniName, eniNetworkConfig, err := NewBranchENINetworkConfig(tt.iface, config)
692+
if tt.expectedError != "" {
693+
assert.EqualError(t, err, tt.expectedError)
694+
} else {
695+
require.NoError(t, err, "Failed to construct eni network config")
696+
assert.Equal(t, "eth0", eniName)
697+
branchENIConfig := &BranchENIConfig{}
698+
err = json.Unmarshal(eniNetworkConfig.Bytes, branchENIConfig)
699+
require.NoError(t, err, "unmarshal config from bytes failed")
700+
assert.Equal(t, tt.expected, *branchENIConfig)
701+
}
702+
})
703+
}
573704
}
574705

575706
// TestConstructBridgeNetworkConfigWithoutIPAM tests createBridgeNetworkConfigWithoutIPAM creates the right configuration for bridge plugin

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

Lines changed: 15 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)