Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions pkg/cloudprovider/aws.go

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so previously does it took 'disassociated' subnet entry at [0] which is different from host subnet, what was that subnet value ? just curious.

@jechen0648 jechen0648 Aug 3, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a draft PR trying to print out of the first IPv6 CIDR, I ran egressIP test on dualstack AWS a few times, I have not been able to reproduce the first entry as disassociated one. OCPBUGS-87249 is not always reproducible.

Original file line number Diff line number Diff line change
Expand Up @@ -276,23 +276,32 @@ func (a *AWS) getSubnet(networkInterface *ec2.InstanceNetworkInterface) (*net.IP
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you have to change the variable name here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this subnet variable is used for v4 and v6 below, it's like one variable used for two different ipstack types, it is easy to get confused , that is why I changed the variable name. But I can revert if you prefer minimal change

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 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a possibility of more than one associated subnet ? hope you have checked it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, technically AWS allows multiple IPv6 CIDR blocks in associated state simultaneously (e.g., when additional IPv6 prefixes are added to a subnet). The current code takes the first associated entry, silently ignoring any others. This means EgressIPs can only be assigned from the first CIDR. This is consistent with how we handle the node's subnet today. If multiple associated entries exist, should I add a klog.Warningf when a second associated entry is found to make this situation visible in logs? Is that what you expect?

@pperiyasamy pperiyasamy Aug 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is first associated CIDR entry for node subnet? wanted to confirm that, otherwise we may end up EIP not getting assigned to the node.

@jechen0648 jechen0648 Aug 4, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it is from node’s subnet, the sequence is
getInstance() gets EC instance
then
getNetworkInterfaces() get EC's networkInterface
then we get v4 or v6 CIDR from line 204:
v4Subnet, v6Subnet, err := a.getSubnet(networkInterface)

@jechen0648 jechen0648 Aug 4, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, regarding whether there would be multiple v6 CIDR blocks, I got more accurate answer from @sadasu: within a VPC in which the cluster is installed. there could be 3 Availability zones (AZs). Within each AZ, there needs to be 1 public and 1 private subnet. So, with this calculation, we should have 6 IPv6 CIDRs within an install.
So, when we consider Egress IPs on worker nodes, for worker would be associated with 1 AZ. And within that there would be 1 public subnet. So, for a worker node used for egress OP should have only 1 active IPv6 CIDR.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pperiyasamy Regarding to your question of "Is there a chance private subnet present at 0th index ?", I am not expert of AWS, this is what I got after asking for AI:

each node will be in 1 AZ, there is 1 public and 1 private subnet for each AZ. On a standard (non-private-only) OpenShift AWS cluster:

  1. worker and control plane nodes are usually placed in private subnets
  2. Public subnets are mainly for NAT gateway, load balancers and similar edge infrastructure.

For EgressIP, relevant IPv6 CIDR is normally the private subnet in that worker's AZ.
Each egress-capable worker exposes 1 active associated IPv6 CIDR on its node annotation - the one for the subnet its ENI is actually in.

CNCC does not pick "public Vs private", it reads whichever subnet the node's primary ENI is attached to.

OVNK choose egressIP from that annotated CIDR, so:
if worker is in AZ's private subnet, egressIP is from the private subnet/64
if worker is in AZ's public subnet, egressIP is from the public subnet/64

"private subnet" is an AWS routing concept. egressIP address is additional IP on the instance ENI from that node's subnet CIDR. CNCC assigns it via AssignIPv6Addresses regardless of public/private subnet classification.

@sadasu can you help to see if the above explanation makes sense to you? Thanks!

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
Expand Down