【问题标题】:How to convert IP address range to CIDR in Java?如何在 Java 中将 IP 地址范围转换为 CIDR?
【发布时间】:2020-10-24 20:53:16
【问题描述】:

我正在尝试将 IP 地址范围转换为 Java 中的 CIDR 表示法。有人可以举例说明如何实现这一点吗?

我使用 SubnetUtils 将 CIDR 转换为 IP 地址范围,但反过来我无法弄清楚。

例如:(使用http://ip2cidr.com/

输入 1: 5.10.64.0
输入 2: 5.10.127.255
结果: 5.10.64.0/18

谢谢, 达瓦尔

【问题讨论】:

  • 我想你会在这篇文章中找到你正在寻找的信息:networkengineering.stackexchange.com/questions/3697/…
  • 真正的方法是address AND mask获取子网地址,subnet + NOT mask获取广播地址。 IP 地址和掩码都是 32 位无符号整数,您需要使用它们来进行 IP 地址操作..
  • 如果您始终确定您将拥有第一个(0)地址和最后一个(广播地址),那么您可以获得明确的答案。但如果你只是给它一个任意范围,你会得到不止一个答案。
  • 澄清一下 - 我的列表 (github.com/client9/ipcat/blob/master/datacenters.csv) 是数据中心的开始和结束范围。所以,我不会总是有第一个(0)地址和最后一个(广播地址)
  • 正确地通过AND输入地址和掩码将始终为您提供正确的子网,并将子网添加到掩码的反面将 always 为您提供 IPv4 的广播地址(IPv6 没有广播,可以使用子网中的每个地址,包括子网和最后一个地址)。无论子网从哪个幂次方开始,此方法都有效。这就是 IP 地址的工作原理,它也适用于 IPv6,只是 IPv6 地址和掩码需要 128 位无符号整数,而不是 IPv4 的 32 位无符号整数。

标签: java ip cidr


【解决方案1】:
import java.util.ArrayList;
import java.util.List;

public class RangeToCidr {
    public static List<String> range2cidrlist( String startIp, String endIp ) {
        // check parameters
        if (startIp == null || startIp.length() < 8 ||
            endIp == null || endIp.length() < 8) return null;
        long start = ipToLong(startIp);
        long end = ipToLong(endIp);
        // check parameters
        if (start > end) return null;

        List<String> result = new ArrayList<String>();
        while (start <= end) {
            // identify the location of first 1's from lower bit to higher bit of start IP
            // e.g. 00000001.00000001.00000001.01101100, return 4 (100)
            long locOfFirstOne = start & (-start);
            int maxMask = 32 - (int) (Math.log(locOfFirstOne) / Math.log(2));

            // calculate how many IP addresses between the start and end
            // e.g. between 1.1.1.111 and 1.1.1.120, there are 10 IP address
            // 3 bits to represent 8 IPs, from 1.1.1.112 to 1.1.1.119 (119 - 112 + 1 = 8)
            double curRange = Math.log(end - start + 1) / Math.log(2);
            int maxDiff = 32 - (int) Math.floor(curRange);

            // why max?
            // if the maxDiff is larger than maxMask
            // which means the numbers of IPs from start to end is smaller than mask range
            // so we can't use as many as bits we want to mask the start IP to avoid exceed the end IP
            // Otherwise, if maxDiff is smaller than maxMask, which means number of IPs is larger than mask range
            // in this case we can use maxMask to mask as many as IPs from start we want.
            maxMask = Math.max(maxDiff, maxMask);

            // Add to results
            String ip = longToIP(start);
            result.add(ip + "/" + maxMask);
            // We have already included 2^(32 - maxMask) numbers of IP into result
            // So the next round start must add that number
            start += Math.pow(2, (32 - maxMask));
        }
        return result;
    }

    private static long ipToLong(String strIP) {
        String[] ipSegs = strIP.split("\\.");
        long res = 0;
        for (int i = 0; i < 4; i++) {
            res += Long.valueOf(ipSegs[i]) << (8 * (3 - i));
        }
        return res;
    }

    private static String longToIP(long longIP) {
        StringBuffer sb = new StringBuffer();
        sb.append(longIP >>> 24).append(".")
          .append((longIP & 0x00FFFFFF) >>> 16).append(".")
          .append(String.valueOf((longIP & 0x0000FFFF) >>> 8)).append(".")
          .append(String.valueOf(longIP & 0x000000FF));

        return sb.toString();
    }
}

【讨论】:

  • 我只是将long static int数组替换为start & - start,这是查找第一个的快捷方式。
  • 这段代码在 0.0.0.0 到 0.255.255.255 => 0.0.0.0/8 的范围内有一些问题,开始为 0,零的自然对数不是那么好。可能是极端情况,但范围很广。如果其他人有同样的问题,只是提到它。不过太糟糕了,因为我喜欢比特和快捷方式!
【解决方案2】:

open-source IPAddress Java library 可以为您做到这一点。免责声明:我是 IPAddress 库的项目经理。

这是执行此操作的示例代码:

static void toPrefixBlocks(String str1, String str2) {
    IPAddressString string1 = new IPAddressString(str1);
    IPAddressString string2 = new IPAddressString(str2);
    IPAddress one = string1.getAddress(), two = string2.getAddress();
    IPAddressSeqRange range = one.toSequentialRange(two);
    System.out.println("starting with range " + range);
    IPAddress blocks[] = range.spanWithPrefixBlocks();
    System.out.println("prefix blocks are " + Arrays.asList(blocks));
}

这是您将如何使用 IPAddress 从原始 CIDR 字符串生成范围:

static String[] toRange(String str) {
    IPAddressString string = new IPAddressString(str);
    IPAddress addr = string.getAddress();
    System.out.println("starting with CIDR " + addr);
    IPAddress lower = addr.getLower(), upper = addr.getUpper();
    System.out.println("range is " + lower + " to " + upper);
    return new String[] {lower.toString(), upper.toString()};
}

对于您的示例,我们运行以下代码:

String strs[] = toRange("5.10.64.0/18");
System.out.println();
toPrefixBlocks(strs[0], strs[1]);

输出是:

starting with CIDR 5.10.64.0/18
range is 5.10.64.0/18 to 5.10.127.255/18

starting with range 5.10.64.0 -> 5.10.127.255
prefix blocks are [5.10.64.0/18]

【讨论】:

    【解决方案3】:

    如果你还没有从我的 cmets 中弄清楚:

    IP 数学必须以二进制形式完成。 IP 地址和掩码是无符号整数(IPv4 为 32 位,IPv6 为 128 位)。您只需要知道地址和掩码,就可以弄清楚其他所有内容。

    这是您想要完成的算法,它适用于 IPv4 和 IPv6。

    根据您的问题,您将获得子网(输入 1)和最后一个地址(输入 2)。

    1. 从无符号整数中减去输入 1 的无符号整数 输入 2。结果是反子网掩码。逆 子网掩码必须是 0,或者反向子网掩码加上 1 必须是 2 的幂,否则其中一个输入有错误(STOP, 输入错误)。
    2. 反向掩码的NOT(步骤 1 的结果)是子网掩码。
    3. 如果输入 1 AND 子网掩码不等于输入 1,则您有一个 输入之一错误(STOP,INPUT ERROR)。
    4. 掩码长度(CIDR编号)是1位在 子网掩码。有几种方法可以计算1的数量 二进制数中的位,但如果子网掩码是最大值 整数(或反掩码为0),则掩码长度为32 (IPv4) 或128 (IPv6)。你可以循环,计算循环次数 并将子网掩码向左移动,直到等于0,循环 计算循环次数并将反向掩码移动到 直到等于0 然后将1 添加到总数中 从32 (IPv4) 或128 (IPv6) 中减去总数,或减去 总逆掩码的2 的幂的指数 加上 1 来自 32 (IPv4) 或 128 (IPv6)。
    5. 此时,您已验证输入 1(子网)、输入 2(最后 地址),并计算掩码长度(CIDR 编号)。
    6. 最终结果将是&lt;Input 1&gt;/&lt;Mask Length&gt;

    你的例子:

    第 1 步 (5.10.127.255 - 5.10.64.0 = 0.0.64.127):

    101000010100111111111111111 - 01000010100100000000000000 = 11111111111111
    

    第 2 步(NOT 0.0.64.255 = 255.255.192.0 是 2 的幂):

    NOT 00000000000000000011111111111111 = 11111111111111111100000000000000
    

    第 3 步 (5.10.64.0 AND 255.255.192.0 = 5.10.64.0):

    01000010100100000000000000 AND 11111111111111111100000000000000 = 01000010100100000000000000
    

    第 4 步 (0.0.64.255 + 1 = 0.0.65.0 = 2^14, exponent of 2^14 = 14, 32 - 14 = 18):

    00000000000000000011111111111111 + 1 = 00000000000000000100000000000000 = 2^14, exponent of 2^14 = 14, 32 - 14 = 18
    

    第 5 步(输入 1 = 5.10.64.0,输入 2 = 5.10.127.255,掩码长度 = 18

    第 6 步(最终结果 = 5.10.64.0/18

    【讨论】:

    • 这很有见地。感谢您的详细撰写!
    • 这处理规范的情况。但没有解释如何处理诸如 5.10.64.1 到 5.10.127.255 的范围,这会导致 14 个 CIDR:5.10.64.1/32 5.10.64.2/31 5.10.64.4/30 5.10.64.8/29 5.10.64.16/28 5.10.64.32/27 5.10.64.64/26 5.10.64.128/25 5.10.65.0/24 5.10.66.0/23 5.10.68.0/22 5.10.72.0/21 5.10.80.0/20 5.10.96.0/19
    • 您的示例与我正在回答的原始问题无关。
    • @RonMaupin - OP 说:“我正在尝试将 IP 地址范围转换为 Java 中的 CIDR 表示法。有人可以提供一个如何实现的示例吗?” CIDR 不是单个条目 - 它们是块。因此 GreatAndPowerfulOz 的评论在这种情况下非常相关。而且它经常被忽视。
    【解决方案4】:

    所以,我可以在这里找到 Java 代码:In Java, given an IP Address range, return the minimum list of CIDR blocks that covers the range

    public class IP2CIDR {
    
        public static void main(String[] args) {
            System.out.println(range2cidrlist("5.104.109.160", "5.104.109.191"));
        }
    
        public static List<String> range2cidrlist( String startIp, String endIp ) {         
            long start = ipToLong(startIp);         
            long end = ipToLong(endIp);           
    
            ArrayList<String> pairs = new ArrayList<String>();         
            while ( end >= start ) {             
                byte maxsize = 32;             
                while ( maxsize > 0) {                 
                    long mask = CIDR2MASK[ maxsize -1 ];                 
                    long maskedBase = start & mask;                 
    
                    if ( maskedBase != start ) {                     
                        break;                 
                    }                 
    
                    maxsize--;             
                }               
                double x = Math.log( end - start + 1) / Math.log( 2 );             
                byte maxdiff = (byte)( 32 - Math.floor( x ) );             
                if ( maxsize < maxdiff) {                 
                    maxsize = maxdiff;             
                }             
                String ip = longToIP(start);             
                pairs.add( ip + "/" + maxsize);             
                start += Math.pow( 2, (32 - maxsize) );         
            }         
            return pairs;     
        }       
    
        public static final int[] CIDR2MASK = new int[] { 0x00000000, 0x80000000,             
            0xC0000000, 0xE0000000, 0xF0000000, 0xF8000000, 0xFC000000,             
            0xFE000000, 0xFF000000, 0xFF800000, 0xFFC00000, 0xFFE00000,             
            0xFFF00000, 0xFFF80000, 0xFFFC0000, 0xFFFE0000, 0xFFFF0000,             
            0xFFFF8000, 0xFFFFC000, 0xFFFFE000, 0xFFFFF000, 0xFFFFF800,             
            0xFFFFFC00, 0xFFFFFE00, 0xFFFFFF00, 0xFFFFFF80, 0xFFFFFFC0,             
            0xFFFFFFE0, 0xFFFFFFF0, 0xFFFFFFF8, 0xFFFFFFFC, 0xFFFFFFFE,             
            0xFFFFFFFF };       
    
        private static long ipToLong(String strIP) {         
            long[] ip = new long[4];         
            String[] ipSec = strIP.split("\\.");         
            for (int k = 0; k < 4; k++) {             
                ip[k] = Long.valueOf(ipSec[k]);         
            }         
    
            return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3];     
        }       
    
        private static String longToIP(long longIP) {         
            StringBuffer sb = new StringBuffer("");         
            sb.append(String.valueOf(longIP >>> 24));         
            sb.append(".");         
            sb.append(String.valueOf((longIP & 0x00FFFFFF) >>> 16));         
            sb.append(".");         
            sb.append(String.valueOf((longIP & 0x0000FFFF) >>> 8));         
            sb.append(".");         
            sb.append(String.valueOf(longIP & 0x000000FF));   
    
            return sb.toString();     
        }
    }
    

    感谢大家的见解和帮助!

    【讨论】:

      【解决方案5】:

      Python 中的短小精悍:

      #!/usr/bin/env python
      import ipaddress
      import math
      
      ip_from = '5.10.64.0'
      ip_to = '5.10.127.255'
      ip_from_long = int(ipaddress.ip_address(unicode(ip_from)))
      ip_to_long = int(ipaddress.ip_address(unicode(ip_to)))
      ip_range = ip_to_long - ip_from_long
      ip_range +=1
      # the clever line of code
      cidr_range = math.log(4294967296/ip_range)/math.log(2)
      # test for a zero/non-zero fractional part
      if cidr_range % 1 == 0:
        # the output will be: 5.10.64.0/18
        print ip_from + '/' + str(int(cidr_range))
      else:
        print "Error: Not an exact CIDR range - " + str(cidr_range)
      

      【讨论】:

        【解决方案6】:
        public static int log2(int i) {
            int count = 0;
            i >>= 1;
            while(i > 0) {
                count++;
                i >>= 1;
            }
            return count;
        }
        
        public static List<String> range2CIDR(String startIp, String endIp) {
            List<String> res = new ArrayList<>();
            try {
                int start = ipS2I(startIp);
                int end = ipS2I(endIp);
                while(start <= end) {
                    int firstNonZero = start & -start;
                    int maxMask = 32 - log2(firstNonZero);
                    maxMask = Math.max(maxMask, 32 - log2(end - start + 1));
                    res.add(ipI2S(start) + "/" + maxMask);
                    start += 1 << (32 - maxMask);
                }
            }catch(Exception e) {
                return res;
            }
        
            return res;
        }
        
        public static int ipS2I(String ip) throws Exception {
            String[] sa = ip.split("\\.");
            if (sa.length != 4) {
                throw new Exception("Bad ip address");
            }
            int res = 0;
            for (int i = 0; i < 4; i++) {
                int t = Integer.valueOf(sa[i]);
                if (t < 0 || t > 255) {
                    throw new Exception("Bad ip address");
                }
                res += t << ((3-i) << 3);
            }
            return res;
        }
        
        public static String ipI2S(int ip) {
            StringBuilder sb = new StringBuilder();
            sb.append((ip>>24) & 0xFF).append(".").append((ip>>16)&0xFF).append(".").append((ip>>8) & 0xFF).append(".").append(ip & 0xFF);
            return sb.toString();
        }
        

        【讨论】:

          【解决方案7】:

          我们研究了很多,最后是这样实现的,添加下面的类后使用RouteNotation.fromRange(...)方法得到List&lt;RouteNotation&gt;

          package my.vpn.utils;
          
          import java.util.ArrayList;
          import java.util.List;
          
          public class RouteNotation {
              private int ipAddress;
              private int prefix;
          
              private RouteNotation(long ipAddress, int prefix) {
                  this.ipAddress = (int) ipAddress;
                  this.prefix = prefix;
              }
          
              public static RouteNotation fromIPv4(long ipAddress, int prefix) {
                  return new RouteNotation(ipAddress, prefix);
              }
          
              public long getIP() {
                  return this.ipAddress;
              }
          
              public void setIP(long v) {
                  this.ipAddress = (int) v;
              }
          
              public int getPrefix() {
                  return this.prefix;
              }
          
              public void setPrefix(int bitCount) {
                  this.prefix = bitCount;
              }
          
              public long getFirst() {
                  return this.ipAddress & getMaskFromPrefix(this.prefix);
              }
          
              public long getLast() {
                  return Notation.getNext(this.ipAddress, this.prefix) - 1;
              }
          
              /**
               * Generates Notation list from given range.
               * <br>
               * Each IP range can result to multiple notations.
               *
               * @param first First IP included in returned notations.
               * @param last  Last IP included in notations.
               * @return Generated routing range notations.
               */
              public static List<RouteNotation> fromRange(long first, long last) {
                  List<RouteNotation> result = new ArrayList<>();
                  while (last >= first) {
                      // Find prefix required to mask first IP in range.
                      byte prefix = getPrefixFromRange(first, last);
                      // At last, push current range to result list.
                      result.add(RouteNotation.fromIPv4(first, prefix));
                      // Skip current Notation range.
                      first = getNext(first, prefix);
                  }
                  return result;
              }
          
              public static int getMaskFromPrefix(int bitCount) {
                  return (int) (0xFFFFFFFF00000000L >> bitCount);
              }
          
              /**
               * Skips given Notation range and get first of next notation.
               * <p>
               * Used to get next IP right out of prefix range.
               */
              public static long getNext(long ip, int prefix) {
                  // Forced to use left-shift or "getUnsigned(ip)", cause else Java would
                  // cast result of "Math.pow(2, 32 - prefix)" method to integer
                  // (and prevents any possible integer-overflow which we require).
                  return (ip & getMaskFromPrefix(prefix)) + (1L << (32 - prefix));
              }
          
              public static byte getPrefixFromRange(long first, long possibleLast) {
                  // Find max prefix required for distance (anything less is invalid).
                  long distance = (possibleLast - first) + 1;
                  double bitsRequiredForDistance = Math.log(distance) / Math.log(2);
                  // Get max constant bit count.
                  byte prefix = (byte) (32 - Math.ceil(bitsRequiredForDistance));
                  // Validate and increase limit (more prefix means less range).
                  while (prefix < 32) {
                      // Limit difference to last IP in range (maximum).
                      long max = RouteNotation.getNext(first, prefix) - 1;
                      if (max > possibleLast) {
                          ++prefix;
                      }
                      // Never allow IP less than first in range (minimum).
                      else if ((first & getMaskFromPrefix(prefix)) < first) {
                          ++prefix;
                      } else {
                          break;
                      }
                  }
                  return prefix;
              }
          }
          

          请注意,这是一个最低需求副本(没有我们的单元测试和许多辅助方法),但您可能需要:

          /**
           * @return Zero on any error, else IP v4 integer.
           */
          public static long ipv4_cast(InetAddress address) {
              long result = 0;
              if (address != null) {
                  byte data[] = address.getAddress();
                  if (data.length <= 4) {
                      for (byte b : data) {
                          result <<= 8;
                          result |= b & 0xFF;
                      }
                  }
              }
              return result;
          }
          

          【讨论】: