PHP:匹配子网列表 (CIDR) 中的 IP

标签 php ip cidr

<分区>

我有一个这样的 CIDR 列表:

192.168.0.1/24
10.0.0.1/32
etc...

名单还在增加。
为了检查 IP 是否符合这些 CIDR 之一,我执行了一个具有以下功能的循环:

function cidr_match($ip, $range){
    list ($subnet, $bits) = explode('/', $range);
    $ip = ip2long($ip);
    $subnet = ip2long($subnet);
    $mask = -1 << (32 - $bits);
    $subnet &= $mask; // in case the supplied subnet was not correctly aligned
    return ($ip & $mask) == $subnet;
}

由于我的 CIDR 列表在增长,我想改进该功能以避免在返回 true 之前逐行测试 CIDR。我想去掉上面函数周围的 for 循环。
有没有办法对我要检查的 IP 执行某种“预检查”,这样它就不会按顺序(从上到下)运行完整列表?
我想优化以便我的代码以这种方式运行:将 IP 提供给函数 --> 函数种类“排序”列表或“查找”最可能的 CIDR --> 对 IP 运行检查最可能的 CIDR(s) --> 返回“真”尽快
将不胜感激。

最佳答案

老实说,除非您的 CIDR 范围很大并且您在同一个进程中检查大量 IP,否则您可能不会看到太多性能提升。但是,如果那是您正在查看的场景,那么您可以考虑尝试通过预处理您的范围和 IP 来压缩一些性能(执行一次 ip2long() 调用并存储分离的掩码/子网以进行比较).

例如,这是你今天做这件事的方式,我假设:

<?php
// Original style
$ranges = array(
  "192.168.0.1/32",
  "192.168.0.1/26",
  "192.168.0.1/24",
  "192.168.0.1/16",
  "127.0.0.1/24",
  "10.0.0.1/32",
  "10.0.0.1/24"
);


// Run the check
$start = microtime(true);
find_cidr("10.0.0.42", $ranges);
find_cidr("192.168.0.12", $ranges);
find_cidr("10.0.0.1", $ranges);
$end = microtime(true);
echo "Ran 3 find routines in " . ($end - $start) . " seconds!\n";

function find_cidr($ip, $ranges)
{
  foreach($ranges as $range)
  {
    if(cidr_match($ip, $range))
    {
      echo "IP {$ip} found in range {$range}!\n";
      break;
    }
  }  
}

function cidr_match($ip, $range){
    list ($subnet, $bits) = explode('/', $range);
    $ip = ip2long($ip);
    $subnet = ip2long($subnet);
    $mask = -1 << (32 - $bits);
    $subnet &= $mask; // in case the supplied subnet was not correctly aligned
    return ($ip & $mask) == $subnet;
}

在我的机器上,运行时间约为 0.0005 - 0.001 秒(针对一小部分范围检查 3 个 IP)。

如果我写一些东西来预处理范围:

<?php
// Slightly-optimized style

$ranges = array(
  "192.168.0.1/32",
  "192.168.0.1/26",
  "192.168.0.1/24",
  "192.168.0.1/16",
  "127.0.0.1/24",
  "10.0.0.1/32",
  "10.0.0.1/24"
);

$matcher = new BulkCIDRMatch($ranges);
$start = microtime(true);
$matcher->FindCIDR("10.0.0.42");
$matcher->FindCIDR("192.168.0.12");
$matcher->FindCIDR("10.0.0.1");
$end = microtime(true);
echo "Ran 3 find routines in " . ($end - $start) . " seconds!\n";


class BulkCIDRMatch
{
  private $_preparedRanges = array();

  public function __construct($ranges)
  {
    foreach($ranges as $range)
    {
      list ($subnet, $bits) = explode('/', $range);
      $subnet = ip2long($subnet);
      $mask = -1 << (32 - $bits);
      $subnet &= $mask; // in case the supplied subnet was not correctly aligned

      $this->_preparedRanges[$range] = array($mask,$subnet);
    }
  }

  public function FindCIDR($ip)
  {
    $result = $this->_FindCIDR(ip2long($ip));
    if($result !== null)
    {
      echo "IP {$ip} found in range {$result}!\n";
    }
    return $result;
  }

  private function _FindCIDR($iplong)
  {
    foreach($this->_preparedRanges as $range => $details)
    {
      if(($iplong & $details[0]) == $details[1])
      {
        return $range;
      }
    }

    // No match
    return null;
  }
}

...然后我看到更快的 CHECKING 但在初始化类并处理和存储所有范围时,开始时的开销略多。因此,如果我仅使用 3 个 IP 对少数几个范围进行 OVERALL 运行,则“优化”方式实际上会慢一些。但是,如果我针对 10,000 个 CIDR 运行 1,000 个 IP,“优化”方式将比原始方式有更明显的改进(以额外内存使用为代价来存储预处理范围数据)。

所以这完全取决于音量和您想要做什么。

也就是说,如果您担心 0.001 秒的性能太慢,那么 PHP 可能不是用于检查的正确语言。或者至少您可能想要考虑编写一个自定义扩展,以便更多的处理在 C 中完成。

编辑:要回答有关查找“可能”范围以进行检查的原始问题(在从其字符串形式进行任何类型的转换之前),尝试这可能不是一件非常可靠的事情。范围可以跨越它们的初始八位字节,因此如果您开始比较这些值(例如“我正在查看 192.168.1.0,所以我只打算查看从 192 开始的范围”),您不仅会招致每个条目的字符串比较的性能开销(这会减慢整体查找速度),但您可能会错过有效范围。

关于PHP:匹配子网列表 (CIDR) 中的 IP,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48311686/

相关文章:

php - Docker Compose LAMP 堆栈不提供 index.php

python - 将ip转换为二进制

c# - 选择具有多个 IP 时 HTTP 请求使用的 IP (.NET)

java - 验证 Java 中的 CIDR 重叠

IP地址/子网掩码(点十进制)的golang CIDR前缀表示法

amazon-web-services - 如何确定公有子网和私有(private)子网以及 VPC 的 CIDR block ?

php - 在将用户输入插入数据库之前,除了准备好的语句之外还有什么?

php - PHP 中的多线程编程以避免运行时限制

php cron 作业未运行

linux - 如何在 AWS ec2 实例中为不同的浏览器选项卡/程序使用多个 IP?