How to convert IP address range to CIDR in Java?
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!
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).
- 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 plus1
must be a power of2
, else you have an error in one of the inputs (STOP, INPUT ERROR). - The
NOT
of the inverse mask (result of Step 1) is the subnet mask. - If Input 1
AND
the subnet mask does not equal Input 1, you have an error in one of the inputs (STOP, INPUT ERROR). - The mask length (CIDR number) is the number of
1
bits in the subnet mask. There are several ways to calculate the number of1
bits in a binary number, but if the subnet mask is the maximum integer (or the inverse mask is0
), then the mask length is32
(IPv4) or128
(IPv6). You can loop, counting the number of loops and shifting the subnet mask to the left until it equals0
, loop counting the number of loops and shifting the inverse mask to the right until it equals0
then adding1
to the total and subtracting the total from32
(IPv4) or128
(IPv6), or subtract the exponent of the power of2
of the total inverse mask plus1
from32
(IPv4) or128
(IPv6). - At this point, you have the verified Input 1 (subnet), Input 2 (last address), and calculated the mask length (CIDR number).
- 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]
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();
}
}