如何在Java中将IP地址范围转换为CIDR?

11
我正在尝试在Java中将IP地址范围转换为CIDR表示法。有人能提供一个示例来说明如何实现这一点吗?
我使用了SubnetUtils来将CIDR转换为IP地址范围,但是我无法弄清楚反过来的方法。
例如:(使用http://ip2cidr.com/
输入1:5.10.64.0 输入2:5.10.127.255 结果:5.10.64.0/18

我认为您会在这篇文章中找到您正在寻找的信息:http://networkengineering.stackexchange.com/questions/3697/the-slash-after-an-ip-address-cidr-notation - Jared Dykstra
真正的方法是使用地址 AND 子网掩码来获取子网地址,以及子网地址 + NOT 子网掩码来获取广播地址。IP地址和子网掩码都是32位无符号整数,你需要使用它们来进行IP地址操作。 - Ron Maupin
1
如果你总是确定你将拥有第一个(0)地址和最后一个(广播)地址,那么你可以得到一个明确的答案。但如果你只给出一个任意范围,你将会得到多个答案。 - RealSkeptic
只是为了澄清 - 我拥有的列表(https://github.com/client9/ipcat/blob/master/datacenters.csv)是数据中心起始和结束范围。因此,我不会总是拥有第一个(0)地址和最后一个(广播地址)。 - Dhaval Kotecha
通过使用地址和掩码进行“AND”操作,可以始终正确地得到子网,将子网添加到掩码的反向值中将始终为IPv4广播地址(IPv6没有广播地址,可以使用子网中的每个地址,包括子网和最后一个地址)。无论子网从哪个2的幂开始,此方法都有效。这就是IP地址的工作原理,对于IPv6也是如此,只是需要128位无符号整数来表示IPv6地址和掩码,而不是IPv4的32位无符号整数。 - Ron Maupin
我也在做同样的事情(解析IPCAT CSV文件),并发现这个简单的命令是最好的解决方案:ipcalc -rn 108.179.42.64-108.179.42.255 它可以正确地计算范围,并且在大多数Linux/Mac系统上都可用。 - bartgras
7个回答

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

我只是用start和-start替换了长的静态int数组,这是一种快速找到第一个元素的方法。 - 叶泰航
这段代码在范围0.0.0.0到0.255.255.255=>0.0.0.0/8上有一些问题,起始点为0,而自然对数的零并不好处理。这可能是一种特殊情况,但该范围是无效的。如果其他人遇到同样的问题,就提一下吧。很遗憾,因为我喜欢位与运算符的快捷方式!<3 - elgholm

3
如果你还没有从我的评论中了解到:IP地址的数学运算必须使用二进制。IP地址和掩码是无符号整数(IPv4为32位,IPv6为128位)。你只需要知道一个地址和掩码,就可以推断出其他所有信息。
这是实现你想要完成的算法,适用于IPv4和IPv6。
基于你的问题,你拥有子网(输入1)和最后一个地址(输入2)。
  1. 将输入1的无符号整数从输入2的无符号整数中减去。结果是反子网掩码。反子网掩码必须为0,或反子网掩码加1必须是2的幂,否则其中一个输入错误(停止,输入错误)。
  2. 反掩码(步骤1的结果)的非运算结果是子网掩码。
  3. 如果输入1与子网掩码的AND不等于输入1,则其中一个输入错误(停止,输入错误)。
  4. 掩码长度(CIDR编号)是子网掩码中1位的数量。有几种方法可以计算二进制数字中1的数量,但如果子网掩码是最大整数(或反掩码为0),则掩码长度为32(IPv4)或128(IPv6)。你可以循环计数循环次数并将子网掩码向左移位,直到它等于0,循环计数循环次数并将反掩码向右移位,直到它等于0,然后将总和加1,并从32(IPv4)或128(IPv6)中减去总和,或者从总反掩码的幂指数加1中减去2的幂指数,从而算出掩码长度。
  5. 此时,你已经验证了输入1(子网)、输入2(最后一个地址)并计算了掩码长度(CIDR编号)。
  6. 最终结果将是/
你的例子: 步骤1(5.10.127.255-5.10.64.0 = 0.0.64.127):
101000010100111111111111111 - 01000010100100000000000000 = 11111111111111

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

NOT 00000000000000000011111111111111 = 11111111111111111100000000000000

第三步(5.10.64.0 AND 255.255.192.0 = 5.10.64.0):
01000010100100000000000000 AND 11111111111111111100000000000000 = 01000010100100000000000000

第四步 (0.0.64.255 + 1 = 0.0.65.0 = 2^14,2^14的指数=14,32-14=18):

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

步骤五 (输入 1 = 5.10.64.0,输入 2 = 5.10.127.255,掩码长度 = 18)

步骤六 (最终结果 = 5.10.64.0/18)


这非常有见地。感谢详细的写作! - Dhaval Kotecha
这处理了规范情况。但是没有解释如何处理范围,例如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 - GreatAndPowerfulOz
你的例子与原问题无关,而我回答的正是原问题。 - Ron Maupin
1
@RonMaupin - OP说:“我正在尝试将IP地址范围转换为Java中的CIDR表示法。有人可以提供一个示例,说明如何实现这一点吗?”CIDR不是单个条目-它们是块。因此,GreatAndPowerfulOz的评论在这种情况下非常相关。而且经常被忽视。 - David Lannan

3

您可以使用开源IPAddress Java库来完成此操作。免责声明:我是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.spanWithRange(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]

addr1.toSequentialRange(addr2)已被弃用。是否有其他替代方法? - Prashant Ambardekar
是的,Prashant Ambardekar使用spanWithRange https://seancfoley.github.io/IPAddress/IPAddress/apidocs/inet/ipaddr/IPAddress.html#spanWithRange-inet.ipaddr.IPAddress- - Sean F
我正在更改答案以使用spanWithRange。 - Sean F

2

所以,我找到了这里的Java代码:在Java中,给定一个IP地址范围,返回覆盖该范围的最小CIDR块列表

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

感谢大家的见解和帮助!

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

0
我们进行了大量的研究,最终实现的方法如下:只需使用RouteNotation.fromRange(...)方法,在添加以下类后即可获得List<RouteNotation>
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;
}

0

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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接