我有一个这样的IP块列表,我是通过将不同的来源拼接在一起而获得的。
160.11.0.0/16
160.11.14.0/20
160.12.0.0/14
160.16.5.0/15
160.16.1.0/14
160.18.0.0/16
160.20.0.0/14
160.24.0.0/16
160.26.0.0/15
160.28.0.0/15
160.74.0.0/16
...
我想要一个 php 函数,它可以接受此列表的输入并通过删除不必要的冗余来优化其大小,因为所有这些的最终输出将提供给能够比较 IP 的软件,它的性能将取决于它将获得多少行输入(列表越短, 更好的将是软件性能)。
我的代码是这样的:
$input_array = file("list.txt");
$output_array = optimize_ipblocks($input_array);
file_put_contents(implode("'n", $output));
此optimize_ipblocks函数应该能够:
- 隔离所有块,看看是否有较小的块已经包含在较大的块中,删除较小的块。
- 如果存在相同类型的重复项,请将其删除。
- 如果有块可以连接在一起,因为它们共享部分内容,或者如果他们接触自己,将它们合并到一个更大的块中。
- 如果可以实现块的某些聚合,则将它们聚合到等等。
我对 ip 块解包和比较的知识有点有限,所以现在我唯一可以摆脱的部分是重复检查建模功能,例如:
function optimize_ipblocks($input_array) {
$blocks = array();
foreach($input_array as $key => $val) {
if(!in_array($val, $blocks)) $blocks[] = $val;
}
return $blocks;
}
我不知道如何对块和聚合进行比较。
有用的解决方案链接:
这篇维基百科文章可以帮助 https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing 到它讨论IPv4 CIDR块的部分。
这个用于聚合 cidr 地址的 perl 脚本 http://www.zwitterion.org/software/aggregate-cidr-addresses/
http://www.perlmonks.org/?node_id=118346
我编写了一个类,用于将 IP 范围合并到优化的 IP 范围超集中。对于您的使用情况,应首先将 CIDR 地址转换为"从-到"IP 范围,如本用法示例中的 $ranges
数组所示:
$ranges=[
["from" => '192.168.0.1',"to" => '192.168.0.9'],
["from" => '192.168.0.3',"to" => '192.168.0.6'],
["from" => '192.168.0.1',"to" => '192.168.0.5'],
["from" => '192.168.0.13',"to" => '192.168.0.17'],
["from" => '192.168.0.2',"to" => '192.168.0.4'],
["from" => '192.168.0.2',"to" => '192.168.0.7'],
["from" => '192.168.0.12',"to" => '192.168.0.14'],
];
$rm=new C_RangeMerger($ranges,'ip');
$mergedRanges=$rm->getMergedRanges();
echo("<pre>".print_r($mergedRanges,1)."</pre>");
类C_RangeMerger
输出:
Array
(
[0] => Array
(
[from] => 192.168.0.1
[to] => 192.168.0.9
)
[1] => Array
(
[from] => 192.168.0.12
[to] => 192.168.0.17
)
)
希望对您有所帮助!
这个函数应该做你想做的事。
<?php
function merge_cidr(array $cidr_or_ipv4_list)
{ // Main function
$valid_ip='0*?((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))'; // Build the valid ipv4 regex
$valid_ip.=str_repeat(".$valid_ip",3); // Finalize the ipv4 regex accepting leading zeros for each part
$valid_routing_prefix='(?:0*?((?:(?:0)|(?:3[0-2])|(?:[1-2]?[0-9]))))'; // Build a regex for the routing prefix (accepting leading zeros)
foreach($cidr_or_ipv4_list as $a) // For each entry you pass to the function
if (is_string($a) && preg_match("#^[^0-9]*$valid_ip(?:/$valid_routing_prefix)?[^0-9]*$#", $a, $m))
{ // Extracting the valid ipv4 and optionnaly the routing prefix
$m[5] = ctype_digit($m[5]) ? ((int)$m[5]) : 32; // Initialize the valid routing prefix to the extracted value or 32 if mismatch
$c[$m[5]][] = ip2long("$m[1].$m[2].$m[3].$m[4]") & (-1 << (32 - $m[5])); // Initialize the working array with key (prefix) and value as subnet by bitwise the decimal ip
}
if ($c) // If any valid ipv4 with optional routing prefix matched
{
foreach($c as &$unique) $unique=array_unique($unique); //Make unique as possible before processing
$c = merge_cidr_summarize($c); // Pass the valid array to the slave function
foreach($c as $d => & $e) // For each result as routing prefix => Decimal value
$e = array_map(
function ($a) use($d)
{
return [$a, $a + (1 << (32 - $d)) - 1];
}
, $e); // Change it to an array containing the range of ip
foreach($c as $f => $g) // For each result as routing prefix => array of decimal value
foreach($c as $h => $i) // For each result as routing prefix => array of decimal value
if ($f > $h) // If we are not in the same line and the second line have a lower routing prefix
foreach($g as $j => $k) // For each line as id => array of decimal values
foreach($i as $l) // For each line as decimal value in the second foreach
if ($k[0] >= $l[0] && $k[1] <= $l[1]) // If the block with lower routing prefix is totally including the first
unset($c[$f][$j]); // Unset the smaller block
foreach($c as $f => $g) // For each result as routing prefix => array of decimal value
{
usort($g,
function (array $a, array $b)
{
return $b[0]>$a[0]?1:($b[0]<$a[0]?-1:0);
}); // Order the result "naturally" inversed
foreach($g as $h) // For each ordered result
$z[] = long2ip($h[0]) . '/' . $f; // Convert it to human readable
}
return array_reverse($z); // And return the reversed result (order by routing prefix DESC and ip DESC)
}
}
function merge_cidr_summarize(array $a)
{ // Slave function
$b = false; // Did we make a change ?
$c = []; // Initialize the result to an empty array
krsort($a); // Order the input by routing prefix DESC
foreach($a as $d => $e) { // For each entry as cidr => Array of decimal values
sort($a[$d]); // Order the values "naturally"
$k = count($a[$d]); // Count the values for the loop
for ($i = 0; $i < $k; $i++) // Loop to check all values with this routing prefix
if ($a[$d][$i] == $a[$d][$i + 1]) continue; // If the subnet is the same as the next, then directly goto the next
elseif (($a[$d][$i] & (-1 << 33 - $d)) == ($a[$d][$i + 1] & (-1 << 33 - $d))) { // Check if subnet of this line and the next line are equals
$c[$d - 1][] = $a[$d][$i++] & (-1 << 33 - $d); // If yes add the new subnet in result array and skip the next line
$b = true; // And tell the script to run again
}
else $c[$d][] = $a[$d][$i]; // Else don't make anything
}
return $b ? merge_cidr_summarize($c) : $a; // If any change run again else return the result
}
要尝试一下
<?php
$your_array=['160.11.0.0/16','160.11.14.0/20','160.12.0.0/14','160.16.5.0/15','160.16.1.0/14','160.18.0.0/16','160.20.0.0/14','160.24.0.0/16','160.26.0.0/15','160.28.0.0/15','160.74.0.0/16'];
print_r(merge_cidr($your_array));
输出。。在 https://eval.in/745895 中检查
/*
Array
(
[0] => 160.16.0.0/13
[1] => 160.12.0.0/14
[2] => 160.28.0.0/15
[3] => 160.26.0.0/15
[4] => 160.74.0.0/16
[5] => 160.24.0.0/16
[6] => 160.11.0.0/16
)
*/