github.com/openshift/installer@v1.4.17/pkg/asset/manifests/capiutils/cidr/cidr.go (about)

     1  package cidr
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"math"
     7  	"net"
     8  
     9  	"github.com/pkg/errors"
    10  )
    11  
    12  // SplitIntoSubnetsIPv4 splits a IPv4 CIDR into a specified number of subnets.
    13  // If the number of required subnets isn't a power of 2 then CIDR will be split
    14  // into the next highest power of 2, and you will end up with unused ranges.
    15  // NOTE: this code is adapted from kops https://github.com/kubernetes/kops/blob/c323819e6480d71bad8d21184516e3162eaeca8f/pkg/util/subnet/subnet.go#L46
    16  func SplitIntoSubnetsIPv4(cidrBlock string, numSubnets int) ([]*net.IPNet, error) {
    17  	_, parent, err := net.ParseCIDR(cidrBlock)
    18  	if err != nil {
    19  		return nil, errors.Wrap(err, "failed to parse CIDR")
    20  	}
    21  
    22  	subnetBits := math.Ceil(math.Log2(float64(numSubnets)))
    23  
    24  	networkLen, addrLen := parent.Mask.Size()
    25  	modifiedNetworkLen := networkLen + int(subnetBits)
    26  
    27  	if modifiedNetworkLen > addrLen {
    28  		return nil, errors.Errorf("cidr %s cannot accommodate %d subnets", cidrBlock, numSubnets)
    29  	}
    30  
    31  	var subnets []*net.IPNet
    32  	for i := 0; i < numSubnets; i++ {
    33  		ip4 := parent.IP.To4()
    34  		if ip4 == nil {
    35  			return nil, errors.Errorf("unexpected IP address type: %s", parent)
    36  		}
    37  
    38  		n := binary.BigEndian.Uint32(ip4)
    39  		n += uint32(i) << uint(32-modifiedNetworkLen)
    40  		subnetIP := make(net.IP, len(ip4))
    41  		binary.BigEndian.PutUint32(subnetIP, n)
    42  
    43  		subnets = append(subnets, &net.IPNet{
    44  			IP:   subnetIP,
    45  			Mask: net.CIDRMask(modifiedNetworkLen, 32),
    46  		})
    47  	}
    48  
    49  	return subnets, nil
    50  }
    51  
    52  const subnetIDLocation = 7
    53  
    54  // SplitIntoSubnetsIPv6 splits a IPv6 address into a specified number of subnets.
    55  // AWS IPv6 based subnets **must always have a /64 prefix**. AWS provides an IPv6
    56  // CIDR with /56 prefix. That's the initial CIDR. We must convert that to /64 and
    57  // slice the subnets by increasing the subnet ID by 1.
    58  // so given: 2600:1f14:e08:7400::/56
    59  // sub1: 2600:1f14:e08:7400::/64
    60  // sub2: 2600:1f14:e08:7401::/64
    61  // sub3: 2600:1f14:e08:7402::/64
    62  // sub4: 2600:1f14:e08:7403::/64
    63  // This function can also be called with /64 prefix to further slice existing subnet
    64  // addresses.
    65  // When splitting further, we always have to take the LAST one to avoid collisions
    66  // since the prefix stays the same, but the subnet ID increases.
    67  // To see this restriction read https://docs.aws.amazon.com/vpc/latest/userguide/how-it-works.html#ipv4-ipv6-comparison
    68  func SplitIntoSubnetsIPv6(cidrBlock string, numSubnets int) ([]*net.IPNet, error) {
    69  	_, ipv6CidrBlock, err := net.ParseCIDR(cidrBlock)
    70  	if err != nil {
    71  		return nil, fmt.Errorf("failed to parse cidr block %s with error: %w", cidrBlock, err)
    72  	}
    73  	// update the prefix to 64.
    74  	ipv6CidrBlock.Mask = net.CIDRMask(64, 128)
    75  	var (
    76  		subnets []*net.IPNet
    77  	)
    78  	for i := 0; i < numSubnets; i++ {
    79  		ipv6CidrBlock.IP[subnetIDLocation]++
    80  		newIP := net.ParseIP(ipv6CidrBlock.IP.String())
    81  		v := &net.IPNet{
    82  			IP:   newIP,
    83  			Mask: net.CIDRMask(64, 128),
    84  		}
    85  		subnets = append(subnets, v)
    86  	}
    87  	return subnets, nil
    88  }
    89  
    90  // GetIPv4Cidrs gets the IPv4 CIDRs from a string slice.
    91  func GetIPv4Cidrs(cidrs []string) ([]string, error) {
    92  	found := []string{}
    93  
    94  	for i := range cidrs {
    95  		cidr := cidrs[i]
    96  
    97  		ip, _, err := net.ParseCIDR(cidr)
    98  		if err != nil {
    99  			return found, fmt.Errorf("parsing %s as cidr: %w", cidr, err)
   100  		}
   101  
   102  		ipv4 := ip.To4()
   103  		if ipv4 != nil {
   104  			found = append(found, cidr)
   105  		}
   106  	}
   107  
   108  	return found, nil
   109  }
   110  
   111  // GetIPv6Cidrs gets the IPv6 CIDRs from a string slice.
   112  func GetIPv6Cidrs(cidrs []string) ([]string, error) {
   113  	found := []string{}
   114  
   115  	for i := range cidrs {
   116  		cidr := cidrs[i]
   117  
   118  		ip, _, err := net.ParseCIDR(cidr)
   119  		if err != nil {
   120  			return found, fmt.Errorf("parsing %s as cidr: %w", cidr, err)
   121  		}
   122  
   123  		ipv4 := ip.To4()
   124  		if ipv4 == nil {
   125  			found = append(found, cidr)
   126  		}
   127  	}
   128  
   129  	return found, nil
   130  }