@@ -176,6 +176,60 @@ func (s *Service) reconcileSubnets() error {
176176 return errors .Wrapf (err , "expected the zone attributes to be populated to subnet" )
177177 }
178178
179+ // Auto-assign IPv6 CIDRs to subnets (new subnets not yet created) with isIPv6=true but no IPv6CidrBlock.
180+ // This only applies to managed VPCs with IPv6 enabled.
181+ if ! unmanagedVPC && s .scope .VPC ().IsIPv6Enabled () {
182+ // Collect subnets needing IPv6 assignment and track already-used IPv6 CIDRs.
183+ var subnetsRequiringIPv6Assignment []* infrav1.SubnetSpec
184+ usedIPv6CIDRs := make (map [string ]bool )
185+
186+ for i := range subnets {
187+ subnet := & subnets [i ]
188+ if subnet .IPv6CidrBlock != "" {
189+ usedIPv6CIDRs [subnet .IPv6CidrBlock ] = true
190+ }
191+ // Only assign to subnets that don't exist yet (no ResourceID) and have isIPv6 but no IPv6CidrBlock.
192+ // This includes both dual-stack subnets and IPv6-only subnets.
193+ if subnet .ResourceID == "" && subnet .IsIPv6 && subnet .IPv6CidrBlock == "" {
194+ subnetsRequiringIPv6Assignment = append (subnetsRequiringIPv6Assignment , subnet )
195+ }
196+ }
197+
198+ if len (subnetsRequiringIPv6Assignment ) > 0 {
199+ // Calculate total number of subnets needed including already assigned ones.
200+ totalSubnetsNeeded := len (usedIPv6CIDRs ) + len (subnetsRequiringIPv6Assignment )
201+
202+ // Generate IPv6 subnet CIDRs from the VPC's IPv6 block.
203+ ipv6SubnetCIDRs , err := cidr .SplitIntoSubnetsIPv6 (s .scope .VPC ().IPv6 .CidrBlock , totalSubnetsNeeded )
204+ if err != nil {
205+ return fmt .Errorf ("failed splitting IPv6 VPC CIDR %q into subnets: %w" , s .scope .VPC ().IPv6 .CidrBlock , err )
206+ }
207+
208+ // Assign available IPv6 CIDRs to subnets that need them.
209+ assignedCount := 0
210+ for _ , subnetCIDR := range ipv6SubnetCIDRs {
211+ if assignedCount >= len (subnetsRequiringIPv6Assignment ) {
212+ break
213+ }
214+
215+ cidrBlock := subnetCIDR .String ()
216+ if ! usedIPv6CIDRs [cidrBlock ] {
217+ subnet := subnetsRequiringIPv6Assignment [assignedCount ]
218+ subnet .IPv6CidrBlock = cidrBlock
219+ usedIPv6CIDRs [cidrBlock ] = true
220+
221+ s .scope .Info ("Auto-assigned IPv6 CIDR to subnet" , "subnet-id" , subnet .ID , "ipv6-cidr-block" , cidrBlock )
222+ assignedCount ++
223+ }
224+ }
225+
226+ // Verify all subnets were assigned.
227+ if assignedCount < len (subnetsRequiringIPv6Assignment ) {
228+ return fmt .Errorf ("failed to assign IPv6 CIDRs to all subnets: assigned %d out of %d" , assignedCount , len (subnetsRequiringIPv6Assignment ))
229+ }
230+ }
231+ }
232+
179233 // When the VPC is managed by CAPA, we need to create the subnets.
180234 if ! unmanagedVPC {
181235 // Check that we need at least 1 public subnet after we have updated the metadata
@@ -498,7 +552,6 @@ func (s *Service) createSubnet(sn *infrav1.SubnetSpec) (*infrav1.SubnetSpec, err
498552 // Build the subnet creation request.
499553 input := & ec2.CreateSubnetInput {
500554 VpcId : aws .String (s .scope .VPC ().ID ),
501- CidrBlock : aws .String (sn .CidrBlock ),
502555 AvailabilityZone : aws .String (sn .AvailabilityZone ),
503556 TagSpecifications : []types.TagSpecification {
504557 tags .BuildParamsToTagSpecification (
@@ -507,8 +560,23 @@ func (s *Service) createSubnet(sn *infrav1.SubnetSpec) (*infrav1.SubnetSpec, err
507560 ),
508561 },
509562 }
563+ // Set IPv4 CIDR if provided (dual-stack or IPv4-only subnet).
564+ if sn .CidrBlock != "" {
565+ input .CidrBlock = aws .String (sn .CidrBlock )
566+ }
567+ // Set IPv6 CIDR if this is an IPv6 subnet (dual-stack or IPv6-only).
510568 if sn .IsIPv6 {
569+ if sn .IPv6CidrBlock == "" {
570+ err := fmt .Errorf ("IPv6 CIDR block is required when isIpv6 is set to true" )
571+ record .Warnf (s .scope .InfraCluster (), "FailedCreateSubnet" , "Failed to create managed subnet: %v" , err )
572+ return nil , err
573+ }
574+
511575 input .Ipv6CidrBlock = aws .String (sn .IPv6CidrBlock )
576+ // For IPv6-only subnets, we need to specify Ipv6Native.
577+ if sn .CidrBlock == "" {
578+ input .Ipv6Native = aws .Bool (true )
579+ }
512580 }
513581 out , err := s .EC2Client .CreateSubnet (context .TODO (), input )
514582 if err != nil {
0 commit comments