| package util |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| */ |
| |
| import ( |
| "bytes" |
| "errors" |
| "net" |
| "strconv" |
| "strings" |
| |
| "github.com/apache/trafficcontrol/lib/go-log" |
| ) |
| |
| const BitsPerByte = 8 |
| |
| // CoalesceIPs combines ips into CIDRs, by combining overlapping networks into networks of size coalesceMaskLen, if there are at least coalesceNumber IPs in the larger mask. |
| func CoalesceIPs(ips []net.IP, coalesceNumber int, coalesceMaskLen int) []*net.IPNet { |
| if len(ips) == 0 { |
| return nil |
| } |
| |
| maskIP := ips[0].To4() |
| isV4 := maskIP != nil |
| if maskIP == nil { |
| maskIP = ips[0] |
| } |
| |
| mask := net.CIDRMask(coalesceMaskLen, len(maskIP)*BitsPerByte) |
| |
| type IPNetSources struct { |
| Net *net.IPNet |
| Sources []net.IP |
| } |
| |
| nets := []IPNetSources{} |
| |
| iploop: |
| for _, ip := range ips { |
| ipIsV4 := ip.To4() != nil |
| if isV4 != ipIsV4 { |
| log.Errorln("CoalesceIPs got both V4 and V6 IPs, ignoring IP '" + ip.String() + "'") |
| continue |
| } |
| for i, net := range nets { |
| if net.Net.Contains(ip) { |
| nets[i].Sources = append(nets[i].Sources, ip) |
| continue iploop |
| } |
| } |
| |
| ipnet := &net.IPNet{IP: ip.Mask(mask), Mask: mask} |
| nets = append(nets, IPNetSources{ipnet, []net.IP{ip}}) |
| } |
| |
| finalNets := []*net.IPNet{} |
| for _, ipnet := range nets { |
| if len(ipnet.Sources) >= coalesceNumber { |
| finalNets = append(finalNets, ipnet.Net) |
| continue |
| } |
| |
| for _, ip := range ipnet.Sources { |
| finalNets = append(finalNets, IPToCIDR(ip)) |
| } |
| } |
| |
| return finalNets |
| } |
| |
| // CoalesceCIDRs coalesces cidrs into a smaller set of CIDRs, by combining overlapping networks into networks of size coalesceMaskLen, if there are at least coalesceNumber cidrs in the larger mask. |
| func CoalesceCIDRs(cidrs []*net.IPNet, coalesceNumber int, coalesceMaskLen int) []*net.IPNet { |
| if len(cidrs) == 0 { |
| return nil |
| } |
| |
| maskIP := cidrs[0].IP.To4() |
| isV4 := maskIP != nil |
| if maskIP == nil { |
| maskIP = cidrs[0].IP |
| } |
| |
| mask := net.CIDRMask(coalesceMaskLen, len(maskIP)*BitsPerByte) |
| |
| type IPNetSources struct { |
| Net *net.IPNet |
| Sources []*net.IPNet |
| } |
| |
| nets := []IPNetSources{} |
| |
| iploop: |
| for _, cidr := range cidrs { |
| ipIsV4 := cidr.IP.To4() != nil |
| if isV4 != ipIsV4 { |
| log.Errorln("CoalesceIPs got both V4 and V6 IPs, ignoring CIDR '" + cidr.String() + "'") |
| continue |
| } |
| |
| for i, net := range nets { |
| if CIDRIsSubset(cidr, net.Net) { |
| nets[i].Sources = append(nets[i].Sources, cidr) |
| continue iploop |
| } |
| if CIDRIsSubset(net.Net, cidr) { |
| // if the existing net is a subset of the new cidr, replace the existing net with our larger cidr |
| nets[i].Net = cidr |
| nets[i].Sources = append(nets[i].Sources, cidr) |
| continue iploop |
| } |
| } |
| |
| // use the larger of the coalesceMaskLen and this cidr's mask |
| largerMask := mask |
| if bytes.Compare(cidr.Mask, mask) < 1 { |
| |
| // Note this means cidr.Mask is numerically smaller, but that actually means it's masking more things. |
| // Note bytes.Compare is defined to be lexographical, and we need a bit-wise comparison, but that's actually the same. |
| largerMask = cidr.Mask |
| } |
| |
| ipnet := &net.IPNet{IP: cidr.IP.Mask(largerMask), Mask: largerMask} |
| nets = append(nets, IPNetSources{ipnet, []*net.IPNet{cidr}}) |
| } |
| |
| finalNets := []*net.IPNet{} |
| for _, ipnet := range nets { |
| if len(ipnet.Sources) >= coalesceNumber { |
| finalNets = append(finalNets, ipnet.Net) |
| continue |
| } |
| |
| for _, cidr := range ipnet.Sources { |
| finalNets = append(finalNets, cidr) |
| } |
| } |
| |
| return finalNets |
| } |
| |
| // CIDRIsSubset returns whether na is a subset (possibly improper) of nb. |
| func CIDRIsSubset(na *net.IPNet, nb *net.IPNet) bool { |
| return nb.Contains(FirstIP(na)) && nb.Contains(LastIP(na)) |
| } |
| |
| // FirstIP returns the first IP in the CIDR. |
| // For example, The CIDR 192.0.2.0/24 returns 192.0.2.0. |
| func FirstIP(ipn *net.IPNet) net.IP { |
| return ipn.IP.Mask(ipn.Mask) |
| } |
| |
| // LastIP returns the last IP in the CIDR. |
| // For example, The CIDR 192.0.2.0/24 returns 192.0.2.255. |
| func LastIP(ipn *net.IPNet) net.IP { |
| inverseMask := make([]byte, len(ipn.Mask), len(ipn.Mask)) |
| for i, b := range ipn.Mask { |
| inverseMask[i] = b ^ 0xFF |
| } |
| |
| maxIPBts := make([]byte, len(ipn.IP), len(ipn.IP)) |
| |
| for i, b := range ipn.IP { |
| maxIPBts[i] = b | inverseMask[i] |
| } |
| |
| maxIP := net.IP(maxIPBts) |
| return maxIP |
| } |
| |
| // RangeStr returns the hyphenated range of IPs. |
| // For example, The CIDR 192.0.2.0/24 returns "192.0.2.0-192.0.2.255". |
| func RangeStr(ipn *net.IPNet) string { |
| firstIP := FirstIP(ipn) |
| lastIP := LastIP(ipn) |
| if firstIP.Equal(lastIP) { |
| return firstIP.String() |
| } |
| return firstIP.String() + "-" + lastIP.String() |
| } |
| |
| // IPToCIDR returns the CIDR containing just the given IP. For IPv6, this means /128, for IPv4, /32. |
| func IPToCIDR(ip net.IP) *net.IPNet { |
| fullMask := net.IPMask([]byte{255, 255, 255, 255}) |
| if isV4 := ip.To4() != nil; !isV4 { |
| fullMask = net.IPMask([]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}) |
| } |
| return &net.IPNet{IP: ip, Mask: fullMask} |
| } |
| |
| func IP4ToNum(ip string) (uint32, error) { |
| parts := strings.Split(ip, `.`) |
| if len(parts) != 4 { |
| return 0, errors.New("malformed IPv4") |
| } |
| intParts := []uint32{} |
| for _, part := range parts { |
| i, err := strconv.ParseUint(part, 10, 32) |
| if err != nil { |
| return 0, errors.New("malformed IPv4") |
| } |
| intParts = append(intParts, uint32(i)) |
| } |
| |
| num := intParts[3] |
| num += intParts[2] << 8 |
| num += intParts[1] << 16 |
| num += intParts[0] << 24 |
| |
| return num, nil |
| } |
| |
| func IP4InRange(ip, ipRange string) (bool, error) { |
| ab := strings.Split(ipRange, `-`) |
| if len(ab) != 2 { |
| if len(ab) == 1 { // no range check for equality |
| return ip == ipRange, nil |
| } |
| return false, errors.New("malformed range") |
| } |
| ipNum, err := IP4ToNum(ip) |
| if err != nil { |
| return false, errors.New("malformed ip") |
| } |
| aNum, err := IP4ToNum(ab[0]) |
| if err != nil { |
| return false, errors.New("malformed range (first part)") |
| } |
| bNum, err := IP4ToNum(ab[1]) |
| if err != nil { |
| return false, errors.New("malformed range (second part)") |
| } |
| return ipNum >= aNum && ipNum <= bNum, nil |
| } |