Как преобразовать диапазон IP-адресов в CIDR в Java?
Я пытаюсь преобразовать диапазон IP-адресов в нотацию CIDR в Java. Может кто-нибудь привести пример того, как этого можно достичь?
Я использовал SubnetUtils для преобразования CIDR в диапазон IP-адресов, но не могу разобраться с другой стороны.
Например: (используя http://ip2cidr.com/)
Вход 1: 5.10.64.0
Вход 2: 5.10.127.255
Результат: 5.10.64.0/18
Спасибо, Давал
5 ответов
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();
}
}
Если вы не поняли это из моих комментариев:
IP математика должна быть сделана в двоичном виде. IP-адреса и маски представляют собой целые числа без знака (32 бита для IPv4, 128 бит для IPv6). Все, что вам нужно знать, это адрес и маску, и вы можете выяснить все остальное.
Это алгоритм того, чего вы хотите достичь, и он применяется как к IPv4, так и к IPv6.
На основании вашего вопроса вы получаете подсеть (вход 1) и последний адрес (вход 2).
- Вычтите целое число без знака на входе 1 из целого числа без знака на входе 2. Результат - обратная маска подсети. Обратная маска подсети должна быть
0
или обратная маска подсети плюс1
должна быть сила2
иначе у вас есть ошибка в одном из входов (STOP, INPUT ERROR). NOT
обратной маски (результат шага 1) - маска подсети.- Если вход 1
AND
маска подсети не равна входу 1, у вас есть ошибка в одном из входов (STOP, INPUT ERROR). - Длина маски (номер CIDR) - это число
1
биты в маске подсети. Есть несколько способов рассчитать количество1
биты в двоичном числе, но если маска подсети является максимальным целым числом (или обратная маска0
), то длина маски32
(IPv4) или128
(IPv6). Вы можете выполнять цикл, подсчитывая количество циклов и смещая маску подсети влево, пока она не станет равной0
, подсчет циклов, число циклов и смещение обратной маски вправо, пока она не станет равной0
затем добавление1
к сумме и вычитая сумму из32
(IPv4) или128
(IPv6) или вычтите показатель степени2
от общей обратной маски плюс1
от32
(IPv4) или128
(IPv6). - На данный момент у вас есть проверенный Вход 1 (подсеть), Вход 2 (последний адрес) и рассчитанная длина маски (номер CIDR).
- Окончательный результат будет
<Input 1>/<Mask Length>
,
Ваш пример:
Шаг 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
это степень двух):
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
)
Java-библиотека с открытым исходным кодом IPAddress может сделать это для вас. Отказ от ответственности: я руководитель проекта библиотеки 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]
Итак, я смог найти код 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();
}
}
Спасибо всем за ваши идеи и помощь!
Что-то короткое и приятное в 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();
}
Мы много исследовали, и вот как мы это реализовали в итоге, просто используйте
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;
}