【问题标题】:Match IPv4 address given IP range/mask?匹配给定 IP 范围/掩码的 IPv4 地址?
【发布时间】:2012-05-12 09:23:54
【问题描述】:

使用 PHP 或 RegExp(或两者),如何匹配 IP 地址范围?

传入 IP 示例

10.210.12.12
10.253.12.12
10.210.12.254
10.210.12.95
10.210.12.60

样本范围

10.210.12.0/24
10.210.12.0/16
10.210.*.*
10.*.*.*

我知道我可以做到:

?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)

...但它没有考虑范围。它只是让您匹配传入的号码以查看它是否是每个八位字节为 0-255 的 IP 地址。

编辑:

我在 php.net 上关于 ip2long 函数的评论中也发现了这个函数。

function ip_in_network($ip, $net_addr, $net_mask){ 
    if($net_mask <= 0){ return false; } 
        $ip_binary_string = sprintf("%032b",ip2long($ip)); 
        $net_binary_string = sprintf("%032b",ip2long($net_addr)); 
        return (substr_compare($ip_binary_string,$net_binary_string,0,$net_mask) === 0); 
} 

ip_in_network("192.168.2.1","192.168.2.0",24); //true 
ip_in_network("192.168.6.93","192.168.0.0",16); //true 
ip_in_network("1.6.6.6","128.168.2.0",1); //false

它又短又甜,但与星号的情况不符。我也不知道它是否完全准确,因为当我认为它是错误的时候它会返回一个真实的结果:

echo ip_in_network("192.168.2.1","192.167.0.0",1);

...但也许我误解了 /1 是什么。也许我需要使用 /24。

【问题讨论】:

  • Regex 听起来确实不是处理子网掩码的正确工具(至少不是十进制)。可以做到,但是会很丑。
  • 您可以使用正则表达式来尝试实现这一点,但使用您的语言 string.split / explode 函数并用“。”分解元素可能会更好地为您服务
  • 同时解析两者。应用一些数学。正则表达式将在任何/x 上完全失败,而不是均匀地进入八位字节。所以,考虑到这一点,“你尝试了什么?” ;-)

标签: php regex ip-address


【解决方案1】:

Regex 听起来确实不是处理子网掩码的正确工具(至少不是十进制的)。可以,但是会很丑。

我强烈建议将字符串解析为 4 个整数,组合成 32 位 int,然后使用标准的按位运算(基本上是按位与,然后进行比较)。

【讨论】:

    【解决方案2】:

    转换为 32 位无符号并使用布尔/位运算。

    例如,将 192.168.25.1 转换为 0xC0A81901。

    然后,您可以通过转换掩码的点分十进制部分(即 0xC0A81900)并创建一个 24 位掩码(即 0xFFFFFF00)来查看它是否与掩码 192.168.25/24 匹配。

    在所讨论的地址和掩码之间执行按位与运算,并与掩码规范的点分十进制部分进行比较。例如,

    0xC0A81901 AND 0xFFFFFF00 ==> 0xC0A81900 (result)
    
    compare 0xC0A81900 (result) to 0xC0A81900.
    

    我不知道 PHP,但 google 告诉我 PHP 有 inet_pton(),这是我在 C 中用来执行从点分十进制到 n 位无符号数的转换。见http://php.net/manual/en/function.inet-pton.php

    【讨论】:

      【解决方案3】:

      使用 strpos 将它们匹配为字符串。

      <?php
      $ips = array();
      $ips[0] = "10.210.12.12";
      $ips[1] = "10.253.12.12";
      $ips[2] = "10.210.12.254";
      $ips[3] = "10.210.12.95";
      $ips[4] = "10.210.12.60";
      
      $matches = array();
      
      foreach($ips as $ip){
          if(strpos($ip, "10.253.") === 0){
              $matches[] = $ip;
          }
      }
      
      print_r($matches);
      ?>
      

      【讨论】:

      • @IsisCode - 听说过CIDR?
      【解决方案4】:

      我改编了一个来自 php.net 的答案并使它变得更好。

      function netMatch($network, $ip) {
          $network=trim($network);
          $orig_network = $network;
          $ip = trim($ip);
          if ($ip == $network) {
              echo "used network ($network) for ($ip)\n";
              return TRUE;
          }
          $network = str_replace(' ', '', $network);
          if (strpos($network, '*') !== FALSE) {
              if (strpos($network, '/') !== FALSE) {
                  $asParts = explode('/', $network);
                  $network = @ $asParts[0];
              }
              $nCount = substr_count($network, '*');
              $network = str_replace('*', '0', $network);
              if ($nCount == 1) {
                  $network .= '/24';
              } else if ($nCount == 2) {
                  $network .= '/16';
              } else if ($nCount == 3) {
                  $network .= '/8';
              } else if ($nCount > 3) {
                  return TRUE; // if *.*.*.*, then all, so matched
              }
          }
      
          echo "from original network($orig_network), used network ($network) for ($ip)\n";
      
          $d = strpos($network, '-');
          if ($d === FALSE) {
              $ip_arr = explode('/', $network);
              if (!preg_match("@\d*\.\d*\.\d*\.\d*@", $ip_arr[0], $matches)){
                  $ip_arr[0].=".0";    // Alternate form 194.1.4/24
              }
              $network_long = ip2long($ip_arr[0]);
              $x = ip2long($ip_arr[1]);
              $mask = long2ip($x) == $ip_arr[1] ? $x : (0xffffffff << (32 - $ip_arr[1]));
              $ip_long = ip2long($ip);
              return ($ip_long & $mask) == ($network_long & $mask);
          } else {
              $from = trim(ip2long(substr($network, 0, $d)));
              $to = trim(ip2long(substr($network, $d+1)));
              $ip = ip2long($ip);
              return ($ip>=$from and $ip<=$to);
          }
      }
      
      function ech($b) {
          if ($b) {
              echo "MATCHED\n";
          } else {
              echo "DID NOT MATCH\n";
          }
      }
      
      echo "CLASS A TESTS\n";
      ech(netMatch('10.168.1.0-10.168.1.100', '10.168.1.90'));
      ech(netMatch('10.168.*.*', '10.168.1.90'));
      ech(netMatch('10.168.0.0/16', '10.168.1.90'));
      ech(netMatch('10.169.1.0/24', '10.168.1.90'));
      ech(netMatch('10.168.1.90', '10.168.1.90'));
      echo "\nCLASS B TESTS\n";
      ech(netMatch('130.168.1.0-130.168.1.100', '130.168.1.90'));
      ech(netMatch('130.168.*.*', '130.168.1.90'));
      ech(netMatch('130.168.0.0/16', '130.168.1.90'));
      ech(netMatch('130.169.1.0/24', '130.168.1.90'));
      ech(netMatch('130.168.1.90', '130.168.1.90'));
      echo "\nCLASS C TESTS\n";
      ech(netMatch('192.168.1.0-192.168.1.100', '192.168.1.90'));
      ech(netMatch('192.168.*.*', '192.168.1.90'));
      ech(netMatch('192.168.0.0/16', '192.168.1.90'));
      ech(netMatch('192.169.1.0/24', '192.168.1.90'));
      ech(netMatch('192.168.1.90', '192.168.1.90'));
      echo "\nCLASS D TESTS\n";
      ech(netMatch('230.168.1.0-230.168.1.100', '230.168.1.90'));
      ech(netMatch('230.168.*.*', '230.168.1.90'));
      ech(netMatch('230.168.0.0/16', '230.168.1.90'));
      ech(netMatch('230.169.1.0/24', '230.168.1.90'));
      ech(netMatch('230.168.1.90', '230.168.1.90'));
      echo "\nCLASS E TESTS\n";
      ech(netMatch('250.168.1.0-250.168.1.100', '250.168.1.90'));
      ech(netMatch('250.168.*.*', '250.168.1.90'));
      ech(netMatch('250.168.0.0/16', '250.168.1.90'));
      ech(netMatch('250.169.1.0/24', '250.168.1.90'));
      ech(netMatch('250.168.1.90', '250.168.1.90'));
      

      结果如下:

      CLASS A TESTS
      from orig network (10.168.1.0-10.168.1.100) used network (10.168.1.0-10.168.1.100) for (10.168.1.90)
      MATCHED
      from orig network (10.168.*.*) used network (10.168.0.0/16) for (10.168.1.90)
      MATCHED
      from orig network (10.168.0.0/16) used network (10.168.0.0/16) for (10.168.1.90)
      MATCHED
      from orig network (10.169.1.0/24) used network (10.169.1.0/24) for (10.168.1.90)
      DID NOT MATCH
      used network (10.168.1.90) for (10.168.1.90)
      MATCHED
      
      CLASS B TESTS
      from orig network (130.168.1.0-130.168.1.100) used network (130.168.1.0-130.168.1.100) for (130.168.1.90)
      MATCHED
      from orig network (130.168.*.*) used network (130.168.0.0/16) for (130.168.1.90)
      MATCHED
      from orig network (130.168.0.0/16) used network (130.168.0.0/16) for (130.168.1.90)
      MATCHED
      from orig network (130.169.1.0/24) used network (130.169.1.0/24) for (130.168.1.90)
      DID NOT MATCH
      used network (130.168.1.90) for (130.168.1.90)
      MATCHED
      
      CLASS C TESTS
      from orig network (192.168.1.0-192.168.1.100) used network (192.168.1.0-192.168.1.100) for (192.168.1.90)
      MATCHED
      from orig network (192.168.*.*) used network (192.168.0.0/16) for (192.168.1.90)
      MATCHED
      from orig network (192.168.0.0/16) used network (192.168.0.0/16) for (192.168.1.90)
      MATCHED
      from orig network (192.169.1.0/24) used network (192.169.1.0/24) for (192.168.1.90)
      DID NOT MATCH
      used network (192.168.1.90) for (192.168.1.90)
      MATCHED
      
      CLASS D TESTS
      from orig network (230.168.1.0-230.168.1.100) used network (230.168.1.0-230.168.1.100) for (230.168.1.90)
      MATCHED
      from orig network (230.168.*.*) used network (230.168.0.0/16) for (230.168.1.90)
      MATCHED
      from orig network (230.168.0.0/16) used network (230.168.0.0/16) for (230.168.1.90)
      MATCHED
      from orig network (230.169.1.0/24) used network (230.169.1.0/24) for (230.168.1.90)
      DID NOT MATCH
      used network (230.168.1.90) for (230.168.1.90)
      MATCHED
      
      CLASS E TESTS
      from orig network (250.168.1.0-250.168.1.100) used network (250.168.1.0-250.168.1.100) for (250.168.1.90)
      MATCHED
      from orig network (250.168.*.*) used network (250.168.0.0/16) for (250.168.1.90)
      MATCHED
      from orig network (250.168.0.0/16) used network (250.168.0.0/16) for (250.168.1.90)
      MATCHED
      from orig network (250.169.1.0/24) used network (250.169.1.0/24) for (250.168.1.90)
      DID NOT MATCH
      used network (250.168.1.90) for (250.168.1.90)
      MATCHED
      

      【讨论】:

      • 干得好!。我想添加一项检查以查看网络地址/IP 是否正确。即:if(!$network_long) { return false; }
      • 我认为,确保您的代码足够安全以便处理所有类型(好/坏)的输入
      • 我没有过多研究这个函数,但它因未定义的索引通知而崩溃,并且 ip2long() 期望参数 1 为字符串,给定 null。我最终使用了github.com/S1lentium/IPTools
      【解决方案5】:

      我对上面的例子进行了改进(我有一个带有 /29 的网络掩码,所以它不起作用)。

      function check_netmask($mask, $ip) {
          @list($net, $bits) = explode('/', $mask);
          $bits = isset($bits) ? $bits : 32;
          $bitmask = -pow(2, 32-$bits) & 0x00000000FFFFFFFF;
          $netmask = ip2long($net) & $bitmask;
          $ip_bits = ip2long($ip)  & $bitmask;
          return (($netmask ^ $ip_bits) == 0);
      }
      

      如果您想看到它的实际效果,请添加:

      print("IP Bits: " . str_pad(decbin(ip2long($ip)), 32, '0', STR_PAD_LEFT));
      print "\n";
      print("Bitmask: " . str_pad(decbin($bitmask), 32, '0', STR_PAD_LEFT));
      print "\n";
      print("Netmask: " . str_pad(decbin($netmask), 32, '0', STR_PAD_LEFT));
      print "\n";
      print("Match:   " . str_pad(decbin($netmask ^ $ip_bits), 32, '0', STR_PAD_LEFT));
      print "\n";
      

      用这样的方式运行它:

      print var_dump(check_netmask($argv[1], $argv[2]));
      

      【讨论】:

      • 有趣。不知道 /29 问题。我的时间很紧,但是您能否告诉我您是否运行了我作为示例提供的测试,以及您在这些测试中发现了什么结果?你是如何用更少的代码行来实现这一点的,这也很有趣,所以我想看看它是否符合我的功能,如果有,我可以将答案切换到这个。
      【解决方案6】:

      使用这个库:https://github.com/S1lentium/IPTools

      //Check if IP is within Range:
      
      echo Range::parse('192.168.1.1-192.168.1.254')->contains(new IP('192.168.1.5')); // true
      echo Range::parse('::1-::ffff')->contains(new IP('::1234')); // true
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-06-11
        • 2010-12-01
        • 2011-02-11
        • 1970-01-01
        • 2011-07-28
        • 2016-03-02
        • 2015-09-06
        • 1970-01-01
        相关资源
        最近更新 更多