【问题标题】:In Java, given an IP Address range, return the minimum list of CIDR blocks that covers the range在 Java 中,给定一个 IP 地址范围,返回覆盖该范围的 CIDR 块的最小列表
【发布时间】:2011-06-28 13:33:45
【问题描述】:

我在将 IP 地址范围转换为 CIDR 块列表时遇到了一些逻辑问题。我相信这个网站做得对:http://ip2cidr.com/

我想传入一个起始 IP 地址和一个结束 IP 地址,并让 java 吐出所需的 CIDR 块的最小列表,仅涵盖传入的范围,仅此而已。

例如,如果我传入 1.1.1.111 的起始地址和 1.1.1.120 的结束地址,我希望得到回报: 1.1.1.111/32 1.1.1.112/29 1.1.1.120/32

(/32 表示单个地址。)

【问题讨论】:

    标签: java ip cidr


    【解决方案1】:

    当 IP 地址的第一个八位字节太大时,我的上一个答案中有一些错误。这个效果更好。几乎完全从这里提升:http://facedroid.blogspot.com/2010/06/ip-range-to-cidr.html

    import java.util.ArrayList;
    import java.util.List;
    
    public class RangeToCidr {
        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();     
        } 
    }
    

    【讨论】:

      【解决方案2】:

      你需要了解二进制数,仅此而已。

      一个 CIDR 块只不过是一系列二进制数具有公共前缀所有可能的后缀。 假设对于下面的示例,我们有 8 位 IP 地址,类别为 /1,... 到 /8

      在您的情况下(暂时忽略 1.1.1),我们将您的数字写为二进制数:

       1101111   - 111
       1110000   - 112
       1110001   - 113
         ...
       1110110   - 118
       1110111   - 119
       1111000   - 120
      

      您会看到所有号码都有一个共同的11 前缀,但我们的列表并不包含所有这些号码。所以我们必须把它分成两个列表——一个是110,一个是111。第一个只包含一个数字,所以我们用它制作了一个/8 块(111/8)。

      另一个列表(从 112 到 120)并非包含所有带有 111 的数字(从那时起它会上升到 127),所以我们再次拆分 - 一个带有 1110 的列表,另一个带有 1111 的列表。第一个现在是完整的块1110????(或112/4),第二个只有一个地址,即11111000(或120/8)。

      所以,现在只扩展到 32 位而不是 8 位,并用 Java 实现,你就准备好了。

      用数学术语来说,一个块总是从 x * 2^n(x+1) * 2^n - 1,然后我们使用 32 - n 作为块大小的后缀。所以你只需要找到一些二的幂的下一个倍数。

      【讨论】:

        【解决方案3】:

        我最终重新利用了我找到的一些 PHP 代码,并根据我的需要进行了调整。下面是我最后学习的课程。

        import java.util.ArrayList;
        import java.util.List;
        import java.util.regex.Matcher;
        import java.util.regex.Pattern;
        
        public class RangeToCidr {
            private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})";
            private static final Pattern addressPattern = Pattern.compile(IP_ADDRESS);
        
            public static List<String> rangeToCidrList(String istart, String iend)  {       
                int start = toInteger(istart);
                int end = toInteger(iend);
        
                List<String> result = new ArrayList<String>();
        
                while (end >= start) {
                    int maxsize = imaxblock( start, 32);
                    double x = (Math.log(end - start + 1) / Math.log(2) ) ;
                    int maxdiff = (int) (Math.floor(32 - Math.floor(x)));
        
                    String ip = intToIP(start);
                    if (maxsize < maxdiff) {
                        maxsize = maxdiff;
                    }
                    result.add( ip + "/" + (int)maxsize );
                    start += Math.pow(2, (32-maxsize));
                }
                return result;
            }
        
            private static int toInteger(String address) {
                Matcher matcher = addressPattern.matcher(address);
                if (matcher.matches()) {
                    return matchAddress(matcher);
                }
                else
                    throw new IllegalArgumentException("Could not parse [" + address + "]");
            }
        
            private static int matchAddress(Matcher matcher) {
                int addr = 0;
                for (int i = 1; i <= 4; ++i) { 
                    int n = (rangeCheck(Integer.parseInt(matcher.group(i)), -1, 255));
                    addr |= ((n & 0xff) << 8*(4-i));
                }
                return addr;
            }
        
            private static int rangeCheck(int value, int begin, int end) {
                if (value > begin && value <= end) // (begin,end]
                    return value;
        
                throw new IllegalArgumentException("Value [" + value + "] not in range ("+begin+","+end+"]");
            }
        
            private static String intToIP(int val) {
                int octets[] = new int[4];
                for (int j = 3; j >= 0; --j)
                    octets[j] |= ((val >>> 8*(3-j)) & (0xff));
        
                StringBuilder str = new StringBuilder();
                for (int i =0; i < octets.length; ++i){
                    str.append(octets[i]);
                    if (i != octets.length - 1) {
                        str.append("."); 
                    }
                }
                return str.toString();
            }
        
            private static long imask(int t)    {
                return (long)(Math.pow(2, 32) - Math.pow(2, 32-t) ) ;
            }
        
            private static int imaxblock(long ibase, int tbit)  {
                while (tbit > 0)    {
                    long im = imask(tbit-1);
                    long imand = ibase & im ;
                    if (imand != ibase) {
                        break;
                    }
                    tbit--;
                }
                return tbit;
            }
        }
        

        我有一些辅助方法,出于这个问题的目的,这些方法会使事情变得混乱。但是你可以大致了解一下。

        【讨论】:

        • 您可能需要考虑使用java.net.InetAddress,而不是手动进行字符串转换。那么,不要将 Math.pow & Co 用于如此简单的 int 操作。
        【解决方案4】:

        以下 CIDR 块包含(不限于)地址范围 1.1.1.111 - 1.1.1.120

        /1 - /27

        address   prefix   network    DirectedBroadcast 
        1.1.1.111   /27    1.1.1.96   1.1.1.127
        1.1.1.111   /26    1.1.1.64   1.1.1.127
        1.1.1.111   /25    1.1.1.0    1.1.1.127
        1.1.1.111   /24    1.1.1.0    1.1.1.255
        

        等等

        【讨论】:

          【解决方案5】:

          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));
          }
          

          使用您的示例:

          toPrefixBlocks("1.1.1.111","1.1.1.120");
          

          输出是:

          starting with range 1.1.1.111 -> 1.1.1.120
          prefix blocks are [1.1.1.111/32, 1.1.1.112/29, 1.1.1.120/32]
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-11-07
            • 2020-12-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-04-05
            相关资源
            最近更新 更多