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 }