diff --git a/pkg/cloudprovider/aws.go b/pkg/cloudprovider/aws.go index 44deb2c65..ab3403edf 100644 --- a/pkg/cloudprovider/aws.go +++ b/pkg/cloudprovider/aws.go @@ -265,34 +265,43 @@ func (a *AWS) getSubnet(networkInterface *ec2.InstanceNetworkInterface) (*net.IP if err != nil { return nil, nil, fmt.Errorf("error: cannot list ec2 subnets, err: %v", err) } - if len(describeOutput.Subnets) > 1 { + if len(describeOutput.Subnets) != 1 { subnetId := "" if networkInterface.SubnetId != nil { subnetId = *networkInterface.SubnetId } - return nil, nil, fmt.Errorf("error: multiple subnets found for the subnet ID: %s", subnetId) + return nil, nil, fmt.Errorf("error: expected exactly one subnet for subnet ID %s, got %d", subnetId, len(describeOutput.Subnets)) } var v4Subnet, v6Subnet *net.IPNet subnet := describeOutput.Subnets[0] if subnet.CidrBlock != nil && *subnet.CidrBlock != "" { - _, subnet, err := net.ParseCIDR(*subnet.CidrBlock) + _, v4Net, err := net.ParseCIDR(*subnet.CidrBlock) if err != nil { return nil, nil, fmt.Errorf("error: unable to parse IPv4 subnet, err: %v", err) } - v4Subnet = subnet + v4Subnet = v4Net } - // I don't know what it means to have several IPv6 CIDR blocks defined for - // one subnet, specially given that you can only have one IPv4 CIDR block - // defined...¯\_(ツ)_/¯ - // Let's just pick the first. - if len(subnet.Ipv6CidrBlockAssociationSet) > 0 && subnet.Ipv6CidrBlockAssociationSet[0].Ipv6CidrBlock != nil && *subnet.Ipv6CidrBlockAssociationSet[0].Ipv6CidrBlock != "" { - _, subnet, err := net.ParseCIDR(*subnet.Ipv6CidrBlockAssociationSet[0].Ipv6CidrBlock) + // A subnet can have multiple IPv6 CIDR block associations (e.g., a previous + // "disassociated" one and a current "associated" one). On dualstack AWS clusters + // after subnet CIDR changes, picking the wrong (stale) CIDR causes egress IP + // assignment to fail because OVN-Kubernetes selects IPs from the wrong range. + // Always select the first association in "associated" state. + for _, assoc := range subnet.Ipv6CidrBlockAssociationSet { + if assoc.Ipv6CidrBlock == nil || *assoc.Ipv6CidrBlock == "" { + continue + } + if assoc.Ipv6CidrBlockState == nil || assoc.Ipv6CidrBlockState.State == nil || + *assoc.Ipv6CidrBlockState.State != ec2.SubnetCidrBlockStateCodeAssociated { + continue + } + _, v6Net, err := net.ParseCIDR(*assoc.Ipv6CidrBlock) if err != nil { return nil, nil, fmt.Errorf("error: unable to parse IPv6 subnet, err: %v", err) } - v6Subnet = subnet + v6Subnet = v6Net + break } return v4Subnet, v6Subnet, nil