如何使用 php 加入或拆分 ip 网络
how to join or split ip networks with php
我有一个任务似乎包含一个子任务。取两个或多个 ip 段(可以是 192.168.1.32/27、192.168.1.64/28 和 192.168.1.128/25)并将它们连接到最近的段(192.168.1.0/24 使用以前的 ip 段)。检查是否可以加入段必须是 .
有人知道是否有 php class 的工具可以做到这一点 - 或者有几个类似的功能?
/拉斯
编辑:
[代码]
$ip_id_array = array();
$ip_level_array = array();
$ip_segment_array = array();
$ip_cidr_array = array();
$toplevel_array = array();
foreach ($_POST["net_ip_id"] AS $ip_id)
{
$sql = "
SELECT
ip_segments.ip_id,
ip_segments.ip_segment,
ip_segments.ip_level,
ip_segments.ip_cidr
FROM
ip_segments
WHERE
ip_id = '".$ip_id."'
";
$relip_res = mysqli_query($db, $sql) or cc("ERROR: SQL Select subsegments related", $sql, mysqli_error($db) , $_SESSION["u_id"], $this_document);
$ip_array = mysqli_fetch_assoc($relip_res);
$ip_id_array[] = $ip_array["ip_id"];
$ip_level_array[] = $ip_array["ip_level"];
$ip_segment_array[] = $ip_array["ip_segment"];
$ip_cidr_array[] = $ip_array["ip_cidr"];
if ($ip_array["ip_level"] != 0 && !empty($ip_array["ip_level"]))
{
$sql = "
SELECT
ip_segments.ip_id,
ip_segments.ip_segment,
ip_segments.ip_level,
ip_segments.ip_cidr
FROM
ip_segments
WHERE
ip_id = '".$ip_array["ip_level"]."'
";
$relip_res = mysqli_query($db, $sql) or cc("ERROR: SQL Select subsegments related", $sql, mysqli_error($db) , $_SESSION["u_id"], $this_document);
$toplevel[] = mysqli_fetch_assoc($relip_res);
}
}
$ip_level_array = array_unique($ip_level_array);
$ip_cidr_array = array_unique($ip_cidr_array);
$toplevel = array_unique($toplevel);
if(sizeof($ip_level_array) > 1)
$field_alerts[] = "IP Segments must be within the same segment.!";
if ($ip_cidr_array[0] <= 1)
$field_alerts[] = "Subnetmasks must be larger than or equal to 1";
if ($toplevel <= 1)
$field_alerts[] = "No Toplevel to merge to!";
if (sizeof($field_alerts) < 1)
{
$new_segment = $ip_id_array[0];
$new_cidr = $toplevel[0]["ip_cidr"];
}
[/code]
编辑:
段是顶级 192.168.1.0/24 (id 1 - level 0)
它可以划分为几种不同类型的子网,范围从 /25 到 /32(主机)。
假设我们分段到 /26。这给出了以下内容:
ID, Level, IP, CIDR
2,1,192.168.1.0,26
3,1,192.168.1.64,26
4,1,192.168.1.128,26
5,1,192.168.1.192,26
参见:http://jodies.de/ipcalc?host=192.168.1.0&mask1=24&mask2=26
我需要的是一段代码,它可以获取一个 id 数组,查看段和 cidr,并查看将段连接到最近的超网(在本例中为 /25 或 /24)的可能性
选项:
Join ID, Result
2,3 => true (/25)
2,4 => false (networks not "next" to each other (a /26 between))
3,4 => false (subsegments will split toplevel (/25) segment
4,5 => true (/25)
如果需要更多信息,请告诉我。
根据您上次的编辑,我认为您希望为提供的子网找到最佳摘要。
假设您的示例中有子网:
192.168.1.0/26
192.168.1.64/26
192.168.1.128/26
192.168.1.192/26
由于所有操作都涉及计算子网属性,因此查看子网的二进制表示可能更容易:
11000000101010000000000100000000
11000000101010000000000101000000
11000000101010000000000110000000
11000000101010000000000111000000
检查两个子网是否可以连接的方法是查看最后一个子网位。屏蔽主机部分(本例中的最后 6 位),两个子网 ID 之间唯一允许的差异必须在最后一个子网位(管道之间)。
1100000010101000000000010|0|hhhhhh
1100000010101000000000010|1|hhhhhh
1100000010101000000000011|0|hhhhhh
1100000010101000000000011|1|hhhhhh
在此示例中,前两个子网的前 25 位相同,因此可以将它们连接在一起。最后两个子网也是如此。但是子网 2 和 3 的第 25 位不同,因此无法合并。因此,对于要连接的两个子网,它们需要具有相同的长度为 N
的子网掩码,并且第一个 N-1
地址位应该相同。
使用一组 IP 实现此功能时,您需要解决两个问题:
- 如何处理数组以及按什么顺序遍历它
- 如何配对最佳候选人并检查他们是否可以总结
这两个问题都可以通过对数组进行结构化和排序来解决。因此,假设您从数据库中获取了数组,并且想要汇总子网。
$ips = array(
array("ip" => "192.168.1.0", "cidr" => "26"),
array("ip" => "192.168.1.64", "cidr" => "26"),
array("ip" => "192.168.1.128", "cidr" => "26"),
array("ip" => "192.168.1.192", "cidr" => "26")
);
我要解决这个问题的方法是构建一个数组,地址用数字表示,CIDR 长度作为键。请注意 & (-1 << (32 - $ips[$i]['cidr'])
部分:如果数据库中的地址 是 子网地址,则不需要此部分,但我将其包括在内只是为了以防万一。这将对 IP 地址和子网掩码执行按位与运算,并计算任何 IP 的子网地址。
for($i = 0; $i < sizeof($ips); $i++){
$net = ip2long($ips[$i]['ip']) & (-1 << (32 - $ips[$i]['cidr']));
$n_ips[$ips[$i]['cidr']][]= $net;
}
这将为您提供 $n_ips
数组(如下所示)并使您能够按 CIDR 对数组进行排序并首先处理最小的子网,然后可能会将该摘要与较大的子网合并。
array
26 =>
array
0 => int -1062731520
1 => int -1062731456
2 => int -1062731392
3 => int -1062731328
你应该对二级数组做同样的事情。对地址进行排序会将最佳候选地址彼此相邻放置。然后您将遍历每个数组并检查是否可以使用前面提到的规则连接子网:
两个子网需要具有相同的长度为 N
的子网掩码(这是因为您按 CIDR 前缀对子网进行了分类)并且第一个 N-1
地址位应该相同。第二件事很容易通过使用短 1 位的 CIDR 前缀计算两个子网地址来完成:$ip & (-1 << 32 - ($cidr+1))
并检查它们是否相同。
因此最终函数(将您之前创建的 $n_ips
数组作为参数)可能如下所示:
function summarize($n_ips){
$changed = false; // Did you change anything in this iteration?
$new = array(); // Array with summarized scopes
krsort($n_ips); // Sort array keys (CIDR)
foreach($n_ips as $cidr => $ips){
sort($n_ips[$cidr]); // Sort the scopes from lowest to highest
for($i = 0; $i < sizeof($n_ips[$cidr]); $i++){
if($n_ips[$cidr][$i] == $n_ips[$cidr][$i+1]){ //Skip if you have two same subnets (not needed if your list of scopes is clean)
continue;
}
if(($n_ips[$cidr][$i] & (-1 << 33 - $cidr)) == ($n_ips[$cidr][$i+1] & (-1 << 33 - $cidr))){ //Are subnet IDs from current and next subnet the same if you have smaller subnet mask?
$new[$cidr-1][] = $n_ips[$cidr][$i] & (-1 << 33 - $cidr); //If yes add the summarized subnet to the new array
$i++; //Skip the next subnet
$changed = true; //And raise the changed flag
}else{
$new[$cidr][] = $n_ips[$cidr][$i]; //If not just copy the current subnet
}
}
}
return $changed ? summarize($new) : $n_ips; //If there were no changes you have the optimized summarization, otherwise summarize the new array
}
输出将是汇总子网的数组,其中包含地址的数字表示形式,您可以将其转换为点十进制表示形式:
$s_ips = summarize($n_ips)
foreach($s_ips as $cidr => $ips){
foreach($ips as $ip){
echo long2ip($ip) . "/" . $cidr . "<br/>";
}
}
你说你只需要 true/false 如果总结是可能的,但我有意提供了更笼统的答案。仅当汇总数组中还剩一个子网时,才可以将函数修改为 return true
我有一个任务似乎包含一个子任务。取两个或多个 ip 段(可以是 192.168.1.32/27、192.168.1.64/28 和 192.168.1.128/25)并将它们连接到最近的段(192.168.1.0/24 使用以前的 ip 段)。检查是否可以加入段必须是 .
有人知道是否有 php class 的工具可以做到这一点 - 或者有几个类似的功能?
/拉斯
编辑: [代码]
$ip_id_array = array();
$ip_level_array = array();
$ip_segment_array = array();
$ip_cidr_array = array();
$toplevel_array = array();
foreach ($_POST["net_ip_id"] AS $ip_id)
{
$sql = "
SELECT
ip_segments.ip_id,
ip_segments.ip_segment,
ip_segments.ip_level,
ip_segments.ip_cidr
FROM
ip_segments
WHERE
ip_id = '".$ip_id."'
";
$relip_res = mysqli_query($db, $sql) or cc("ERROR: SQL Select subsegments related", $sql, mysqli_error($db) , $_SESSION["u_id"], $this_document);
$ip_array = mysqli_fetch_assoc($relip_res);
$ip_id_array[] = $ip_array["ip_id"];
$ip_level_array[] = $ip_array["ip_level"];
$ip_segment_array[] = $ip_array["ip_segment"];
$ip_cidr_array[] = $ip_array["ip_cidr"];
if ($ip_array["ip_level"] != 0 && !empty($ip_array["ip_level"]))
{
$sql = "
SELECT
ip_segments.ip_id,
ip_segments.ip_segment,
ip_segments.ip_level,
ip_segments.ip_cidr
FROM
ip_segments
WHERE
ip_id = '".$ip_array["ip_level"]."'
";
$relip_res = mysqli_query($db, $sql) or cc("ERROR: SQL Select subsegments related", $sql, mysqli_error($db) , $_SESSION["u_id"], $this_document);
$toplevel[] = mysqli_fetch_assoc($relip_res);
}
}
$ip_level_array = array_unique($ip_level_array);
$ip_cidr_array = array_unique($ip_cidr_array);
$toplevel = array_unique($toplevel);
if(sizeof($ip_level_array) > 1)
$field_alerts[] = "IP Segments must be within the same segment.!";
if ($ip_cidr_array[0] <= 1)
$field_alerts[] = "Subnetmasks must be larger than or equal to 1";
if ($toplevel <= 1)
$field_alerts[] = "No Toplevel to merge to!";
if (sizeof($field_alerts) < 1)
{
$new_segment = $ip_id_array[0];
$new_cidr = $toplevel[0]["ip_cidr"];
}
[/code]
编辑: 段是顶级 192.168.1.0/24 (id 1 - level 0) 它可以划分为几种不同类型的子网,范围从 /25 到 /32(主机)。 假设我们分段到 /26。这给出了以下内容:
ID, Level, IP, CIDR
2,1,192.168.1.0,26
3,1,192.168.1.64,26
4,1,192.168.1.128,26
5,1,192.168.1.192,26
参见:http://jodies.de/ipcalc?host=192.168.1.0&mask1=24&mask2=26
我需要的是一段代码,它可以获取一个 id 数组,查看段和 cidr,并查看将段连接到最近的超网(在本例中为 /25 或 /24)的可能性 选项:
Join ID, Result
2,3 => true (/25)
2,4 => false (networks not "next" to each other (a /26 between))
3,4 => false (subsegments will split toplevel (/25) segment
4,5 => true (/25)
如果需要更多信息,请告诉我。
根据您上次的编辑,我认为您希望为提供的子网找到最佳摘要。
假设您的示例中有子网:
192.168.1.0/26
192.168.1.64/26
192.168.1.128/26
192.168.1.192/26
由于所有操作都涉及计算子网属性,因此查看子网的二进制表示可能更容易:
11000000101010000000000100000000
11000000101010000000000101000000
11000000101010000000000110000000
11000000101010000000000111000000
检查两个子网是否可以连接的方法是查看最后一个子网位。屏蔽主机部分(本例中的最后 6 位),两个子网 ID 之间唯一允许的差异必须在最后一个子网位(管道之间)。
1100000010101000000000010|0|hhhhhh
1100000010101000000000010|1|hhhhhh
1100000010101000000000011|0|hhhhhh
1100000010101000000000011|1|hhhhhh
在此示例中,前两个子网的前 25 位相同,因此可以将它们连接在一起。最后两个子网也是如此。但是子网 2 和 3 的第 25 位不同,因此无法合并。因此,对于要连接的两个子网,它们需要具有相同的长度为 N
的子网掩码,并且第一个 N-1
地址位应该相同。
使用一组 IP 实现此功能时,您需要解决两个问题:
- 如何处理数组以及按什么顺序遍历它
- 如何配对最佳候选人并检查他们是否可以总结
这两个问题都可以通过对数组进行结构化和排序来解决。因此,假设您从数据库中获取了数组,并且想要汇总子网。
$ips = array(
array("ip" => "192.168.1.0", "cidr" => "26"),
array("ip" => "192.168.1.64", "cidr" => "26"),
array("ip" => "192.168.1.128", "cidr" => "26"),
array("ip" => "192.168.1.192", "cidr" => "26")
);
我要解决这个问题的方法是构建一个数组,地址用数字表示,CIDR 长度作为键。请注意 & (-1 << (32 - $ips[$i]['cidr'])
部分:如果数据库中的地址 是 子网地址,则不需要此部分,但我将其包括在内只是为了以防万一。这将对 IP 地址和子网掩码执行按位与运算,并计算任何 IP 的子网地址。
for($i = 0; $i < sizeof($ips); $i++){
$net = ip2long($ips[$i]['ip']) & (-1 << (32 - $ips[$i]['cidr']));
$n_ips[$ips[$i]['cidr']][]= $net;
}
这将为您提供 $n_ips
数组(如下所示)并使您能够按 CIDR 对数组进行排序并首先处理最小的子网,然后可能会将该摘要与较大的子网合并。
array
26 =>
array
0 => int -1062731520
1 => int -1062731456
2 => int -1062731392
3 => int -1062731328
你应该对二级数组做同样的事情。对地址进行排序会将最佳候选地址彼此相邻放置。然后您将遍历每个数组并检查是否可以使用前面提到的规则连接子网:
两个子网需要具有相同的长度为 N
的子网掩码(这是因为您按 CIDR 前缀对子网进行了分类)并且第一个 N-1
地址位应该相同。第二件事很容易通过使用短 1 位的 CIDR 前缀计算两个子网地址来完成:$ip & (-1 << 32 - ($cidr+1))
并检查它们是否相同。
因此最终函数(将您之前创建的 $n_ips
数组作为参数)可能如下所示:
function summarize($n_ips){
$changed = false; // Did you change anything in this iteration?
$new = array(); // Array with summarized scopes
krsort($n_ips); // Sort array keys (CIDR)
foreach($n_ips as $cidr => $ips){
sort($n_ips[$cidr]); // Sort the scopes from lowest to highest
for($i = 0; $i < sizeof($n_ips[$cidr]); $i++){
if($n_ips[$cidr][$i] == $n_ips[$cidr][$i+1]){ //Skip if you have two same subnets (not needed if your list of scopes is clean)
continue;
}
if(($n_ips[$cidr][$i] & (-1 << 33 - $cidr)) == ($n_ips[$cidr][$i+1] & (-1 << 33 - $cidr))){ //Are subnet IDs from current and next subnet the same if you have smaller subnet mask?
$new[$cidr-1][] = $n_ips[$cidr][$i] & (-1 << 33 - $cidr); //If yes add the summarized subnet to the new array
$i++; //Skip the next subnet
$changed = true; //And raise the changed flag
}else{
$new[$cidr][] = $n_ips[$cidr][$i]; //If not just copy the current subnet
}
}
}
return $changed ? summarize($new) : $n_ips; //If there were no changes you have the optimized summarization, otherwise summarize the new array
}
输出将是汇总子网的数组,其中包含地址的数字表示形式,您可以将其转换为点十进制表示形式:
$s_ips = summarize($n_ips)
foreach($s_ips as $cidr => $ips){
foreach($ips as $ip){
echo long2ip($ip) . "/" . $cidr . "<br/>";
}
}
你说你只需要 true/false 如果总结是可能的,但我有意提供了更笼统的答案。仅当汇总数组中还剩一个子网时,才可以将函数修改为 return true