Skip to content

Commit a46827f

Browse files
committed
manifests/capiutils: introduce package to work with Cluster API
This commit introduces a few helper functions, packages (like cidr) and types that are needed to work with Cluster API. The purpose of this package is to provide consistency across all the uses of Cluster API. Signed-off-by: Vince Prignano <[email protected]>
1 parent 2abace9 commit a46827f

File tree

5 files changed

+466
-0
lines changed

5 files changed

+466
-0
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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+
}

0 commit comments

Comments
 (0)