// Copyright 2017-2020 Authors of Cilium // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package merge provides helper functions for merging a list of // IP addresses and subnets into the smallest possible list of CIDRs. // Original Implementation: https://github.com/cilium/cilium package merge import ( "bytes" "encoding/binary" "math/big" "net" ) const ( ipv4BitLen = 8 * net.IPv4len ipv6BitLen = 8 * net.IPv6len ) var ( v4Mappedv6Prefix = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff} defaultIPv4 = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0} defaultIPv6 = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} upperIPv4 = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 255, 255, 255, 255} upperIPv6 = []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} ipv4LeadingZeroes = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} ) // RangeToCIDRs converts the range of IPs covered by firstIP and lastIP to // a list of CIDRs that contains all of the IPs covered by the range. func RangeToCIDRs(firstIP, lastIP net.IP) []*net.IPNet { // First, create a CIDR that spans both IPs. spanningCIDR := createSpanningCIDR(&firstIP, &lastIP) firstIPSpanning, lastIPSpanning := GetAddressRange(spanningCIDR) cidrList := []*net.IPNet{} // If the first IP of the spanning CIDR passes the lower bound (firstIP), // we need to split the spanning CIDR and only take the IPs that are // greater than the value which we split on, as we do not want the lesser // values since they are less than the lower-bound (firstIP). if bytes.Compare(firstIPSpanning, firstIP) < 0 { // Split on the previous IP of the first IP so that the right list of IPs // of the partition includes the firstIP. prevFirstRangeIP := GetPreviousIP(firstIP) var bitLen int if prevFirstRangeIP.To4() != nil { bitLen = ipv4BitLen } else { bitLen = ipv6BitLen } _, _, right := partitionCIDR(spanningCIDR, net.IPNet{IP: prevFirstRangeIP, Mask: net.CIDRMask(bitLen, bitLen)}) // Append all CIDRs but the first, as this CIDR includes the upper // bound of the spanning CIDR, which we still need to partition on. cidrList = append(cidrList, right...) spanningCIDR = *right[0] cidrList = cidrList[1:] } // Conversely, if the last IP of the spanning CIDR passes the upper bound // (lastIP), we need to split the spanning CIDR and only take the IPs that // are greater than the value which we split on, as we do not want the greater // values since they are greater than the upper-bound (lastIP). if bytes.Compare(lastIPSpanning, lastIP) > 0 { // Split on the next IP of the last IP so that the left list of IPs // of the partition include the lastIP. nextFirstRangeIP := getNextIP(lastIP) var bitLen int if nextFirstRangeIP.To4() != nil { bitLen = ipv4BitLen } else { bitLen = ipv6BitLen } left, _, _ := partitionCIDR(spanningCIDR, net.IPNet{IP: nextFirstRangeIP, Mask: net.CIDRMask(bitLen, bitLen)}) cidrList = append(cidrList, left...) } else { // Otherwise, there is no need to partition; just use add the spanning // CIDR to the list of networks. cidrList = append(cidrList, &spanningCIDR) } return cidrList } // GetAddressRange returns the first and last addresses in the given CIDR range. func GetAddressRange(ipNet net.IPNet) (net.IP, net.IP) { firstIP := make(net.IP, len(ipNet.IP)) lastIP := make(net.IP, len(ipNet.IP)) copy(firstIP, ipNet.IP) copy(lastIP, ipNet.IP) firstIP = firstIP.Mask(ipNet.Mask) lastIP = lastIP.Mask(ipNet.Mask) if firstIP.To4() != nil { firstIP = append(v4Mappedv6Prefix, firstIP...) lastIP = append(v4Mappedv6Prefix, lastIP...) } lastIPMask := make(net.IPMask, len(ipNet.Mask)) copy(lastIPMask, ipNet.Mask) for i := range lastIPMask { lastIPMask[len(lastIPMask)-i-1] = ^lastIPMask[len(lastIPMask)-i-1] lastIP[net.IPv6len-i-1] = lastIP[net.IPv6len-i-1] | lastIPMask[len(lastIPMask)-i-1] } return firstIP, lastIP } // GetPreviousIP returns the previous IP from the given IP address. func GetPreviousIP(ip net.IP) net.IP { // Cannot go lower than zero! if ip.Equal(net.IP(defaultIPv4)) || ip.Equal(net.IP(defaultIPv6)) { return ip } previousIP := make(net.IP, len(ip)) copy(previousIP, ip) var overflow bool var lowerByteBound int if ip.To4() != nil { lowerByteBound = net.IPv6len - net.IPv4len } else { lowerByteBound = 0 } for i := len(ip) - 1; i >= lowerByteBound; i-- { if overflow || i == len(ip)-1 { previousIP[i]-- } // Track if we have overflowed and thus need to continue subtracting. if ip[i] == 0 && previousIP[i] == 255 { overflow = true } else { overflow = false } } return previousIP } // createSpanningCIDR returns a single IP network spanning the // the lower and upper bound IP addresses. func createSpanningCIDR(firstIP, lastIP *net.IP) net.IPNet { // Don't want to modify the values of the provided range, so make copies. lowest := *firstIP highest := *lastIP var isIPv4 bool var spanningMaskSize, bitLen, byteLen int if lowest.To4() != nil { isIPv4 = true bitLen = ipv4BitLen byteLen = net.IPv4len } else { bitLen = ipv6BitLen byteLen = net.IPv6len } if isIPv4 { spanningMaskSize = ipv4BitLen } else { spanningMaskSize = ipv6BitLen } // Convert to big Int so we can easily do bitshifting on the IP addresses, // since golang only provides up to 64-bit unsigned integers. lowestBig := big.NewInt(0).SetBytes(lowest) highestBig := big.NewInt(0).SetBytes(highest) // Starting from largest mask / smallest range possible, apply a mask one bit // larger in each iteration to the upper bound in the range until we have // masked enough to pass the lower bound in the range. This // gives us the size of the prefix for the spanning CIDR to return as // well as the IP for the CIDR prefix of the spanning CIDR. for spanningMaskSize > 0 && lowestBig.Cmp(highestBig) < 0 { spanningMaskSize-- mask := big.NewInt(1) mask = mask.Lsh(mask, uint(bitLen-spanningMaskSize)) mask = mask.Mul(mask, big.NewInt(-1)) highestBig = highestBig.And(highestBig, mask) } // If ipv4, need to append 0s because math.Big gets rid of preceding zeroes. if isIPv4 { highest = append(ipv4LeadingZeroes, highestBig.Bytes()...) } else { highest = highestBig.Bytes() } // Int does not store leading zeroes. if len(highest) == 0 { highest = make([]byte, byteLen) } newNet := net.IPNet{IP: highest, Mask: net.CIDRMask(spanningMaskSize, bitLen)} return newNet } // partitionCIDR returns a list of IP Networks partitioned upon excludeCIDR. // The first list contains the networks to the left of the excludeCIDR in the // partition, the second is a list containing the excludeCIDR itself if it is // contained within the targetCIDR (nil otherwise), and the // third is a list containing the networks to the right of the excludeCIDR in // the partition. func partitionCIDR(targetCIDR net.IPNet, excludeCIDR net.IPNet) ([]*net.IPNet, []*net.IPNet, []*net.IPNet) { var targetIsIPv4 bool if targetCIDR.IP.To4() != nil { targetIsIPv4 = true } targetFirstIP, targetLastIP := GetAddressRange(targetCIDR) excludeFirstIP, excludeLastIP := GetAddressRange(excludeCIDR) targetMaskSize, _ := targetCIDR.Mask.Size() excludeMaskSize, _ := excludeCIDR.Mask.Size() if bytes.Compare(excludeLastIP, targetFirstIP) < 0 { return nil, nil, []*net.IPNet{&targetCIDR} } else if bytes.Compare(targetLastIP, excludeFirstIP) < 0 { return []*net.IPNet{&targetCIDR}, nil, nil } if targetMaskSize >= excludeMaskSize { return nil, []*net.IPNet{&targetCIDR}, nil } left := []*net.IPNet{} right := []*net.IPNet{} newPrefixLen := targetMaskSize + 1 targetFirstCopy := make(net.IP, len(targetFirstIP)) copy(targetFirstCopy, targetFirstIP) iLowerOld := make(net.IP, len(targetFirstCopy)) copy(iLowerOld, targetFirstCopy) // Since golang only supports up to unsigned 64-bit integers, and we need // to perform addition on addresses, use math/big library, which allows // for manipulation of large integers. // Used to track the current lower and upper bounds of the ranges to compare // to excludeCIDR. iLower := big.NewInt(0) iUpper := big.NewInt(0) iLower = iLower.SetBytes(targetFirstCopy) var bitLen int if targetIsIPv4 { bitLen = ipv4BitLen } else { bitLen = ipv6BitLen } shiftAmount := (uint)(bitLen - newPrefixLen) targetIPInt := big.NewInt(0) targetIPInt.SetBytes(targetFirstIP.To16()) exp := big.NewInt(0) // Use left shift for exponentiation exp = exp.Lsh(big.NewInt(1), shiftAmount) iUpper = iUpper.Add(targetIPInt, exp) matched := big.NewInt(0) for excludeMaskSize >= newPrefixLen { // Append leading zeros to IPv4 addresses, as math.Big.Int does not // append them when the IP address is copied from a byte array to // math.Big.Int. Leading zeroes are required for parsing IPv4 addresses // for use with net.IP / net.IPNet. var iUpperBytes, iLowerBytes []byte if targetIsIPv4 { iUpperBytes = append(ipv4LeadingZeroes, iUpper.Bytes()...) iLowerBytes = append(ipv4LeadingZeroes, iLower.Bytes()...) } else { iUpperBytesLen := len(iUpper.Bytes()) // Make sure that the number of bytes in the array matches what net // package expects, as big package doesn't append leading zeroes. if iUpperBytesLen != net.IPv6len { numZeroesToAppend := net.IPv6len - iUpperBytesLen zeroBytes := make([]byte, numZeroesToAppend) iUpperBytes = append(zeroBytes, iUpper.Bytes()...) } else { iUpperBytes = iUpper.Bytes() } iLowerBytesLen := len(iLower.Bytes()) if iLowerBytesLen != net.IPv6len { numZeroesToAppend := net.IPv6len - iLowerBytesLen zeroBytes := make([]byte, numZeroesToAppend) iLowerBytes = append(zeroBytes, iLower.Bytes()...) } else { iLowerBytes = iLower.Bytes() } } // If the IP we are excluding over is of a higher value than the current // CIDR prefix we are generating, add the CIDR prefix to the set of IPs // to the left of the exclude CIDR if bytes.Compare(excludeFirstIP, iUpperBytes) >= 0 { left = append(left, &net.IPNet{IP: iLowerBytes, Mask: net.CIDRMask(newPrefixLen, bitLen)}) matched = matched.Set(iUpper) } else { // Same as above, but opposite. right = append(right, &net.IPNet{IP: iUpperBytes, Mask: net.CIDRMask(newPrefixLen, bitLen)}) matched = matched.Set(iLower) } newPrefixLen++ if newPrefixLen > bitLen { break } iLower = iLower.Set(matched) iUpper = iUpper.Add(matched, big.NewInt(0).Lsh(big.NewInt(1), uint(bitLen-newPrefixLen))) } excludeList := []*net.IPNet{&excludeCIDR} return left, excludeList, right } func getNextIP(ip net.IP) net.IP { if ip.Equal(upperIPv4) || ip.Equal(upperIPv6) { return ip } nextIP := make(net.IP, len(ip)) switch len(ip) { case net.IPv4len: ipU32 := binary.BigEndian.Uint32(ip) ipU32++ binary.BigEndian.PutUint32(nextIP, ipU32) return nextIP case net.IPv6len: ipU64 := binary.BigEndian.Uint64(ip[net.IPv6len/2:]) ipU64++ binary.BigEndian.PutUint64(nextIP[net.IPv6len/2:], ipU64) if ipU64 == 0 { ipU64 = binary.BigEndian.Uint64(ip[:net.IPv6len/2]) ipU64++ binary.BigEndian.PutUint64(nextIP[:net.IPv6len/2], ipU64) } else { copy(nextIP[:net.IPv6len/2], ip[:net.IPv6len/2]) } return nextIP default: return ip } }