检查 IP 地址是不是为私有

Posted

技术标签:

【中文标题】检查 IP 地址是不是为私有【英文标题】:Check if an IP address is private检查 IP 地址是否为私有 【发布时间】:2012-11-28 21:02:14 【问题描述】:

我喜欢检查某个 IP 地址是否在专用网络中。它不起作用。

我的代码:

<?php
$ip = $_SERVER['REMOTE_ADDR'];

function _isPrivate($ip) 

    $i = explode('.', $ip);

    if ($i[0] == 10) 
        return true;
     else if ($i[0] == 172 && $i[1] > 15 && $i[1] < 32) 
        return true;
     else if ($i[0] == 192 && $i[1] == 168) 
        return true;
    
    return false;

?>

另一个:

<?php
$ip = $_SERVER['REMOTE_ADDR'];

function _isPrivate($ip) 

    $ip = ip2long($ip);
    $net_a = ip2long('10.255.255.255') >> 24; 
    $net_b = ip2long('172.31.255.255') >> 20; 
    $net_c = ip2long('192.168.255.255') >> 16; 

    return $ip >> 24 === $net_a || $ip >> 20 === $net_b || $ip >> 16 === $net_c; 

?>

任何帮助将不胜感激,谢谢!

【问题讨论】:

见mebsd.com/coding-snipits/check-private-ip-function-php.html 私有范围是:10.0.0.0/24、172.16.0.0/20 和 192.168.0.0/16。看我的回答here,使用合适的重叠测试方法进行检查。 “这行不通”到底是什么意思?你能给我们一个没有得到验证的地址的例子吗?或者至少解释一下你的代码应该做什么,不做什么? 结果:1​​27.0.0.1 不是私有地址。 没错??? PHP 不再需要global $ip; 来访问该变量了吗? 【参考方案1】:

我认为这应该可以解决问题。

filter_var 与以下validation rules 一起使用,如果 IP 地址是私有地址,则返回 false。

$user_ip = '127.0.0.1';
filter_var(
    $user_ip, 
    FILTER_VALIDATE_IP, 
    FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE |  FILTER_FLAG_NO_RES_RANGE
)

查看上面的链接以获取 php 文档

【讨论】:

您可能想要添加指向filter_var() 的链接以及PHP 手册的验证过滤器页面。即php.net/manual/en/function.filter-var.php 和php.net/manual/en/filter.filters.validate.php 更短、更快速且面向未来。我的首选答案。 您还可以添加“FILTER_FLAG_IPV6”以允许私有 IPv6 地址。 如果您想同时处理 IPv4 和 IPv6,删除 FILTER_FLAG_IPV4 也可以 如果你想要一个合乎逻辑的结果,请检查 if ($user_ip == filter_var($user_ip ...)) - 添加这个因为它浪费了我一些时间来记住 filter_var 的工作原理:)跨度> 【参考方案2】:
function ip_is_private ($ip) 
    $pri_addrs = array (
                      '10.0.0.0|10.255.255.255', // single class A network
                      '172.16.0.0|172.31.255.255', // 16 contiguous class B network
                      '192.168.0.0|192.168.255.255', // 256 contiguous class C network
                      '169.254.0.0|169.254.255.255', // Link-local address also refered to as Automatic Private IP Addressing
                      '127.0.0.0|127.255.255.255' // localhost
                     );

    $long_ip = ip2long ($ip);
    if ($long_ip != -1) 

        foreach ($pri_addrs AS $pri_addr) 
            list ($start, $end) = explode('|', $pri_addr);

             // IF IS PRIVATE
             if ($long_ip >= ip2long ($start) && $long_ip <= ip2long ($end)) 
                 return true;
             
        
    

    return false;

见http://mebsd.com/coding-snipits/check-private-ip-function-php.html

您可能还想查看私人地址空间here

【讨论】:

我猜,但这段代码有点烂。如果您要使用其他人的代码(尽管有信用)来回答,您至少不能格式化它以使其可读吗?此外,这包括可能不需要的环回和自动配置范围。 @DaveRandom 刚刚在看到您的评论之前清理了它。您对环回和自动配置范围是正确的,我将为此添加一些注释。 这可能更简单一点?支持 IPv6。 filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) 【参考方案3】:

好的,这是 7 年的帖子。但我想我可以分享我的解决方案,以便某个地方的某个人可能会发现它有帮助。

我的解决方案基于内置的 PHP filter_var() 函数。这意味着我不必每次需要验证给定值时都预先定义所有私有范围或保留范围。或循环遍历范围。相反,我让 PHP 替我担心。

class IP

    static public function is_ip($ip=NULL) : bool
    
        return filter_var(
            $ip,
            FILTER_VALIDATE_IP,
            FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6
        ) === $ip ? TRUE : FALSE;
    

    static public function is_ipv4($ip=NULL) : bool
    
        return filter_var(
            $ip,
            FILTER_VALIDATE_IP,
            FILTER_FLAG_IPV4
        ) === $ip ? TRUE : FALSE;
    

    static public function is_ipv6($ip=NULL) : bool
    
        return filter_var(
            $ip,
            FILTER_VALIDATE_IP,
            FILTER_FLAG_IPV6
        ) === $ip ? TRUE : FALSE;
    

    static public function is_public_ip($ip=NULL) : bool
    
        return filter_var(
            $ip,
            FILTER_VALIDATE_IP,
            FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
        ) === $ip ? TRUE : FALSE;
    

    static public function is_public_ipv4($ip=NULL) : bool
    
        return filter_var(
            $ip,
            FILTER_VALIDATE_IP,
            FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
        ) === $ip ? TRUE : FALSE;
    

    static public function is_public_ipv6($ip=NULL) : bool
    
        return filter_var(
            $ip,
            FILTER_VALIDATE_IP,
            FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
        ) === $ip ? TRUE : FALSE;
    

    static public function is_private_ip($ip=NULL) : bool
    
        return self::is_ip($ip) && !self::is_public_ip($ip);
    

    static public function is_private_ipv4($ip=NULL) : bool
    
        return self::is_ipv4($ip) && !self::is_public_ipv4($ip);
    

    static public function is_private_ipv6($ip=NULL) : bool
    
        return self::is_ipv6($ip) && !self::is_public_ipv6($ip);
    

它允许您通常验证给定值是否是 IP。或者更具体一点。您可以验证以下类型:

IP IPv4 IPv6 公共 IP 公共 IPv4 公共 IPv6 私有 IP 私有 IPv4 私有 IPv6

让我们测试一下:

$arr = array(
    '127.0.0.0', '127.0.0.1', '127.1.2.3', '127.1.2.255',

    '192.168.0.0', '192.168.0.1', '192.168.2.3', '192.168.2.255',

    '172.16.0.0', '172.16.0.1', '172.16.2.3', '172.16.2.255',
    '172.19.0.0', '172.19.0.1', '172.19.2.3', '172.19.2.255',

    '10.0.0.0', '10.0.0.1', '10.0.2.3', '10.0.2.255',
    '10.5.0.0', '10.5.0.1', '10.5.2.3', '10.5.2.255',

    '8.8.8.8', '8.8.4.4', '255.255.255',

    '182.168.1.300', '256.1.2.3', '0.500.0.0',
    'I am not an IP', NULL, '185.128.72.151'
);

foreach ($arr as $item) 
    echo "$item --> " . (IP::is_private_ip($item) === TRUE ? 'is private' : 'is NOT private or NOT an IP') . PHP_EOL;

【讨论】:

【参考方案4】:

...我的 5 美分:

恕我直言,根本问题只是“如何检查 IP 地址是否属于网络?”。

答案是简单的二进制:IP_address AND network_mask EQUALS network_address。

例如,IP 地址 10.1.2.3 是否属于网络掩码为 255.0.0.0 的网络 10.0.0.0? 10.1.2.3 & 255.0.0.0 是 10.0.0.0,所以答案是:是的。

在二进制中更容易看到它:

  00001010 00000001 00000010 00000011 ( 10.1.2.3) ip address
& 11111111 00000000 00000000 00000000 (255.0.0.0) network mask
= 00001010 00000000 00000000 00000000 ( 10.0.0.0) network address

只需要检查您需要的网络(包括或不包括环回、链接本地等):

function _isPrivate($long_ip) 
    return ( ($long_ip & 0xFF000000) === 0x0A000000 ) || //Private A network: 00001010 ....
           ( ($long_ip & 0xFFF00000) === 0xAC100000 ) || //Private B network: 10101100 0001....
           ( ($long_ip & 0xFFFF0000) === 0xC0A80000 ) || //Private C network: 11000000 10101000 ....
           //Link-local and loopback are NOT private range, so the function in the question yield right results to "is in private range?". Seems it was not the desired behaviour... Those cases can also be checked:
           ( ($long_ip & 0xFFFF0000) === 0xA9FE0000 ) || //Link-local       : 10101001 11111110 ....
           ( ($long_ip & 0xFFFF0000) === 0x7F000000 ) || //Loopback         : 01111111 ....
         //...and add all the fancy networks that you want...
           ( ($long_ip & 0xFFFFFF00) === 0xC0AF3000 ) || //Direct Delegation AS112 Service 192.175.48.0/24...
           ( ($long_ip & 0xF0000000) === 0xF0000000 ); //Reserved 240.0.0.0/4

有趣的是返回值的否定。返回的值并不真正意味着给定的 IP 在专用网络中,但它的否定确实意味着给定的 IP 是“公共 IP 地址”(普通/普通 IP 地址),正如 user4880112 的解决方案所表明的那样。

IPv6

同样适用于 IPv6。 “专用网络”地址(正式名称为“Unique-Local”,RFC 4193)是“fc00::/7”。所以,ip_address & 0xFE00.. === 0xFC00.. 是“私有网络”

采用上述答案并包含来自 IANA 的最新信息...

http://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml

...我们可以像这样做一个更通用的函数:

function isPublicAddress($ip) 
  // returns false on failure.
  // negative if it's a private or special address (-4:IPv4, -16:IPv6)
  // positive if it's a common IP public address (4:IPv4, 16:IPv6)

  $networks = array(
    '4' => array('0.0.0.0/8',
      '10.0.0.0/8',
      '100.64.0.0/10',
      '127.0.0.0/8',
      '169.254.0.0/16',
      '172.16.0.0/12',
      '192.0.0.0/24',
      '192.0.0.0/29',
      '192.0.0.8/32',
      '192.0.0.9/32',
      '192.0.0.170/32',
      '192.0.0.170/32',
      '192.0.2.0/24',
      '192.31.196.0/24',
      '192.52.193.0/24',
      '192.88.99.0/24',
      '192.168.0.0/16',
      '192.175.48.0/24',
      '198.18.0.0/15',
      '198.51.100.0/24',
      '203.0.113.0/24',
      '240.0.0.0/4',
      '255.255.255.255/32')
    ,
    '16' => array('::1/128',
      '::/128',
      '::ffff:0:0/96',
      '64:ff9b::/96',
      '100::/64',
      '2001::/23',
      '2001::/32',
      '2001:1::1/128',
      '2001:2::/48',
      '2001:3::/32',
      '2001:4:112::/48',
      '2001:5::/32',
      '2001:10::/28',
      '2001:20::/28',
      '2001:db8::/32',
      '2002::/16',
      '2620:4f:8000::/48',
      'fc00::/7',
      'fe80::/10') 
    );

    $ip = inet_pton($ip);
    if( $ip === false ) return false;

    $space='16';
    if (strlen($ip) === 4)  
      $space='4';
    

    //Is the IP in a private or special range?
    foreach($networks[$space] as $network) 
      //split $network in address and mask
      $parts=explode('/',$network);
      $network_address = inet_pton($parts[0]);
      $network_mask    = inet_pton( _mask( $ip , $parts[1] ) );
      if (($ip & $network_mask) === $network_address)
        return -1*$space;
      
    
    //Success!
    return $space;


function _mask($ip,$nbits)
  $mask='';
  $nibble=array('0','8','C','E');
  $f_s= $nbits >> 2 ;
  if( $f_s > 0 ) $mask.=str_repeat('F',$f_s);
  if( $nbits % 4 ) $mask.= $nibble[$nbits % 4];
  if( strlen($ip) === 4 )
    if( strlen($mask) < 8 ) $mask.=str_repeat('0', 8 - strlen($mask) );
    long2ip('0x'.$mask);
    $mask=long2ip('0x'.$mask);
  else
    if( strlen($mask) < 32 ) $mask.=str_repeat('0', 32 - strlen($mask) );
    $mask=rtrim(chunk_split($mask,4,':'),':');
  
  return $mask;

我现在想知道的是:“IPv4 映射地址”中的 IPv6 地址是 IPv6 中的“特殊”地址,即使它是 IPv4 中的“普通”IP 地址。我们是否应该考虑“私有使用” ::ffff:0:0/96 中匹配 IPv4 私有网络的子网?

编辑解释最后一条评论:

IPv6 网络 ::ffff:0:0/96 映射到每个 IPv4 地址的 IPv6 地址。这些 IPv6 地址在 IANA 注册中心(“特殊用途”)中的单个集合中,但映射的 IPv4 地址在 IPv4 中的所有类型的集合中(私有网络、环回、广播、公共......)“通用 IPv4地址”总是一个“特殊的 IPv6 地址”。如果我们使用与 IPv4 专用网络匹配的 ::ffff:0:0/96 范围内的 IPv6 地址设置网络...我们使用的是专用网络地址吗?

【讨论】:

如果您在上一条评论的基础上扩展一点会不会很棒?【参考方案5】:

使用 inet_pton 代替 ip2long,并包括一些更晦涩的私有范围:

function isPublicAddress($ip) 

    //Private ranges...
    //http://www.iana.org/assignments/iana-ipv4-special-registry/
    $networks = array('10.0.0.0'        =>  '255.0.0.0',        //LAN.
                      '172.16.0.0'      =>  '255.240.0.0',      //LAN.
                      '192.168.0.0'     =>  '255.255.0.0',      //LAN.
                      '127.0.0.0'       =>  '255.0.0.0',        //Loopback.
                      '169.254.0.0'     =>  '255.255.0.0',      //Link-local.
                      '100.64.0.0'      =>  '255.192.0.0',      //Carrier.
                      '192.0.2.0'       =>  '255.255.255.0',    //Testing.
                      '198.18.0.0'      =>  '255.254.0.0',      //Testing.
                      '198.51.100.0'    =>  '255.255.255.0',    //Testing.
                      '203.0.113.0'     =>  '255.255.255.0',    //Testing.
                      '192.0.0.0'       =>  '255.255.255.0',    //Reserved.
                      '224.0.0.0'       =>  '224.0.0.0',        //Reserved.
                      '0.0.0.0'         =>  '255.0.0.0');       //Reserved.

    //inet_pton.
    $ip = @inet_pton($ip);
    if (strlen($ip) !== 4)  return false; 

    //Is the IP in a private range?
    foreach($networks as $network_address => $network_mask) 
         $network_address   = inet_pton($network_address);
         $network_mask      = inet_pton($network_mask);
         assert(strlen($network_address)    === 4);
         assert(strlen($network_mask)       === 4);
         if (($ip & $network_mask) === $network_address)
            return false;
    

    //Success!
    return true;


【讨论】:

@CMCDragonkai 可能你用的太晚了,不过现在有IPv6版本了。【参考方案6】:

基本上是@Mark Davidson 的答案,但有点数学。

function isPrivate($szAddr) 

   $nIP = ip2long($szAddr);

   $arLocal = [
      [ip2long('127.0.0.0'),   24],
      [ip2long('10.0.0.0'),    24],
      [ip2long('172.16.0.0'),  20],
      [ip2long('192.168.0.0'), 16],
      [ip2long('169.254.0.0'), 16],
   ];

   foreach( $arLocal as $arP ) 

      $maskLo = ~((1 << $arP[1]) - 1);  // CREATE BIT MASK FROM NUMBER

      if( ($nIP & $maskLo) === $arP[0] ) // BITWISE-AND, THEN COMPARE
         return true;
   

   return false;

【讨论】:

以上是关于检查 IP 地址是不是为私有的主要内容,如果未能解决你的问题,请参考以下文章

检查 IP 地址是不是在范围/子网内的标准/安全方法

一个刚进入网络世界的小白对NAT地址转换的理解

使用C语言判断一个IP 地址是否为私有地址

动态NAT的配置

如何将私有 IP 地址名称设置为通用名称以显示给用户?

如何检查一个IP地址是不是在PHP中的两个IP范围内?