In this post we will review creation of CIDRs from IP range. This was required in my case as we've received input as IP ranges, but the processing application had built a trie representation of the IPs using CIDRs.
We've started with a simple conversion functions:
func IntToIp(intIp uint32) net.IP {
ip := make(net.IP, 4)
binary.BigEndian.PutUint32(ip, intIp)
return ip
}
func ParseIpv4(ipv4 string) net.IP {
ip := net.ParseIP(ipv4)
return ip[12:]
}
func IpToInt(ip net.IP) uint32 {
return binary.BigEndian.Uint32(ip)
}
And the implemented a naive CIDR creation function:
func MakeCidr(
fromIp net.IP,
toIp net.IP,
) net.IPNet {
fromIpInt := IpToInt(fromIp)
toIpInt := IpToInt(toIp)
ones := 32
for diff := toIpInt - fromIpInt; diff > 0; diff = diff / 2 {
ones--
}
mask := net.CIDRMask(ones, 32)
maskInt := IpToInt(net.IP(mask))
networkAddressInt := fromIpInt & maskInt
networkAddressEndInt := networkAddressInt | (^maskInt)
networkAddress := IntToIp(networkAddressInt)
network := net.IPNet{
IP: networkAddress,
Mask: mask,
}
if networkAddressInt != fromIpInt || networkAddressEndInt != toIpInt {
errsimple.RaiseIfError(fmt.Errorf("invalid range %v-%v, network address is: %v",
fromIp.String(), toIp.String(), network.String()))
}
return network
}
Just to be on the safe side, we've added a protection for non CIDR range. For example the IP range:
1.1.1.0 - 1.1.1.255
Is converted to the CIDR 1.1.1.0/24, but the IP range:
1.1.1.0 - 1.1.1.254
Cannot be converted to a single CIDR.
Then, we run the application, and found that the IP ranges that we have are indeed non-convertable to a single CIDR. To address this issue, we've create another implementation that can split IP range to multiple CIDRs.
func MakeMultipleCidrs(
fromIp uint32,
toIp uint32,
) []net.IPNet {
ones := 0
for checkBitMask := uint32(1) << 31; checkBitMask != 0 && fromIp&checkBitMask == toIp&checkBitMask; checkBitMask = checkBitMask >> 1 {
ones++
}
mask := net.CIDRMask(ones, 32)
maskInt := IpToInt(net.IP(mask))
networkAddressStartInt := fromIp & maskInt
networkAddressEndInt := networkAddressStartInt | (^maskInt)
if networkAddressStartInt == fromIp && networkAddressEndInt == toIp {
networkAddress := IntToIp(networkAddressStartInt)
network := net.IPNet{
IP: networkAddress,
Mask: mask,
}
return []net.IPNet{network}
}
size := networkAddressEndInt - networkAddressStartInt + 1
halfSize := size / 2
secondHalfStartInt := networkAddressStartInt + halfSize
firstHalfEnd := secondHalfStartInt - 1
cidrsFirst := MakeMultipleCidrs(fromIp, firstHalfEnd)
cidrsSecond := MakeMultipleCidrs(secondHalfStartInt, toIp)
cidrs := append(cidrsFirst, cidrsSecond...)
return cidrs
}
This recursive function breaks the range into 2 parts, until it finds a CIDR that match the range.
An example of output for this function is below:
range 1.1.1.1 - 1.1.1.1, cidrs: [1.1.1.1/32]
range 1.1.1.0 - 1.1.1.1, cidrs: [1.1.1.0/31]
range 1.1.1.0 - 1.1.1.2, cidrs: [1.1.1.0/31 1.1.1.2/32]
range 1.1.1.1 - 1.1.1.3, cidrs: [1.1.1.1/32 1.1.1.2/31]
range 1.1.1.0 - 1.1.1.3, cidrs: [1.1.1.0/30]
range 1.1.1.0 - 1.1.1.255, cidrs: [1.1.1.0/24]
range 1.1.1.0 - 1.1.2.255, cidrs: [1.1.1.0/24 1.1.2.0/24]
range 1.1.2.0 - 1.1.3.255, cidrs: [1.1.2.0/23]
Notice that this implementation works only for IPv4, feel free to use the same idea for IPv6.