简体   繁体   中英

How to convert IP address range to CIDR in Java?

I am trying to convert the IP address range into the CIDR notation in Java. Can someone provide an example of how this can be achieved?

I used the SubnetUtils for converting the CIDR to IP address range, but I can't figure out the other way round.

For example: (using http://ip2cidr.com/ )

Input 1: 5.10.64.0
Input 2: 5.10.127.255
Result: 5.10.64.0/18

Thanks, Dhaval

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

In case you haven't figured it out from my comments:

IP math must be done in binary. IP addresses and masks are unsigned integers (32 bits for IPv4, 128 bits for IPv6). All you need to know is an address and mask, and you can figure out everything else.

This is algorithm for what you want to accomplish, and it applies to both IPv4 and IPv6.

Based on your question, you are given the subnet (Input 1) and last address (Input 2).

  1. Subtract the unsigned integer of Input 1 from the unsigned integer of Input 2. The result is the inverse subnet mask. The inverse subnet mask must be 0 , or the inverse subnet mask plus 1 must be a power of 2 , else you have an error in one of the inputs ( STOP, INPUT ERROR ).
  2. The NOT of the inverse mask (result of Step 1) is the subnet mask.
  3. If Input 1 AND the subnet mask does not equal Input 1, you have an error in one of the inputs ( STOP, INPUT ERROR ).
  4. The mask length (CIDR number) is the number of 1 bits in the subnet mask. There are several ways to calculate the number of 1 bits in a binary number, but if the subnet mask is the maximum integer (or the inverse mask is 0 ), then the mask length is 32 (IPv4) or 128 (IPv6). You can loop, counting the number of loops and shifting the subnet mask to the left until it equals 0 , loop counting the number of loops and shifting the inverse mask to the right until it equals 0 then adding 1 to the total and subtracting the total from 32 (IPv4) or 128 (IPv6), or subtract the exponent of the power of 2 of the total inverse mask plus 1 from 32 (IPv4) or 128 (IPv6).
  5. At this point, you have the verified Input 1 (subnet), Input 2 (last address), and calculated the mask length (CIDR number).
  6. The final result will be <Input 1>/<Mask Length> .

Your example:

Step 1 ( 5.10.127.255 - 5.10.64.0 = 0.0.64.127 ):

101000010100111111111111111 - 01000010100100000000000000 = 11111111111111

Step 2 ( NOT 0.0.64.255 = 255.255.192.0 is a power of two):

NOT 00000000000000000011111111111111 = 11111111111111111100000000000000

Step 3 ( 5.10.64.0 AND 255.255.192.0 = 5.10.64.0 ):

01000010100100000000000000 AND 11111111111111111100000000000000 = 01000010100100000000000000

Step 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

Step 5 (Input 1 = 5.10.64.0 , Input 2 = 5.10.127.255 , Mask Length = 18 )

Step 6 (Final Result = 5.10.64.0/18 )

The open-source IPAddress Java library can do this for you. Disclaimer: I am the project manager of the IPAddress library.

Here is sample code to do it:

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

This is how you would use IPAddress to produce the range from the original CIDR string:

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

For your example, we run this code:

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

The output is:

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]

So, I was able to find the Java code here: 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();     
    }
}

Thanks everyone for your insights and help!

Something short and sweet in 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)
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();
}

We researched a lot and here is how we implemented it in the end, just use RouteNotation.fromRange(...) method to get List<RouteNotation> after adding below class:

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

Note that it's a minimal need copy (without our unit-tests and many helper methods), but you may need:

/**
 * @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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM