检查IP地址是否为私有地址。

32

我想检查一个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; 
}
?>

非常感谢任何帮助!谢谢!


3
请查看http://mebsd.com/coding-snipits/check-private-ip-function-php.html以获取翻译后的文本。 - user7282
3
私有地址范围为:10.0.0.0/24、172.16.0.0/20和192.168.0.0/16。请参考我在这里的回答,使用适当的重叠测试方法进行检查。 - DaveRandom
你所说的“它不起作用”具体指什么?你能给我们举一个无法得到验证的地址的例子吗?或者至少解释一下你的代码预期要做什么,但实际上没有做到什么? - Pelle
结果:127.0.0.1不是私有地址。这正确吗? - Ruth
PHP现在不需要使用global $ip;来访问该变量了吗? - ott--
6个回答

102

我认为这应该解决问题。

filter_var与以下验证规则一起使用,如果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文档


5
你可能想要添加到 filter_var() 和 PHP 手册中验证过滤器页面的链接,例如:http://php.net/manual/en/function.filter-var.php 和 http://php.net/manual/en/filter.filters.validate.php。 - Craig
1
更短、更快、更具未来性。这是我首选的答案。 - NoChecksum
3
您可以添加 "FILTER_FLAG_IPV6" 以允许私有 IPv6 地址。 - Wireblue
4
如果您想同时处理IPv4和IPv6,移除FILTER_FLAG_IPV4也可以起作用。 - cg909
4
如果您想要一个逻辑结果,请检查 if ($user_ip == filter_var($user_ip ...)) - 我加上这个是因为我浪费了一点时间来记住 filter_var 的工作原理 :) - drchuck
显示剩余3条评论

32
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

您可能还想在这里了解有关私有地址空间的信息。


4
我猜,但这段代码有点烂。如果你要回答使用其他人的代码(尽管标注了出处),那么你至少可以格式化一下,让它更易读吧?此外,这包括环回和自动配置范围,这可能不是所期望的。 - DaveRandom
@DaveRandom 在看到你的评论之前已经整理好了。你关于环回和自动配置范围的观点是正确的,我会添加一些注释来说明这一点。 - Mark Davidson
1
这可能会更简单一些?支持IPv6。filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) - Hans Ott

8

这篇文章已经写了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

...我的看法:

在我看来,底层的问题只是“如何检查IP地址是否属于网络?”。

答案很简单:IP地址 AND 网络掩码 EQUALS 网络地址。

例如,IP地址10.1.2.3是否属于网络10.0.0.0和子网掩码255.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;
}

我现在想知道的是:IPv6中“IPv4映射地址”中的IPV6地址即使在IPv4中是“常规”的IP地址,它也是IPv6中的“特殊”地址。我们是否应该将匹配IPv4专用网络的::ffff:0:0/96子网视为“私有使用”?
编辑以解释最后一条评论:
IPv6网络::ffff:0:0/96将每个IPv4地址映射到一个IPv6地址。这些IPv6地址在IANA注册表中处于单个集合中(“特殊用途”),但映射的IPv4地址在IPv4中的各种集合中(私有网络、环回、广播、公共...)。 “常见IPv4地址”始终是“特殊IPv6地址”。如果我们使用匹配IPv4专用网络的::ffff:0:0/96范围内的IPv6地址设置网络...我们是否正在使用私有网络地址?

能否详细说明一下你上次评论的内容? - CMCDragonkai

2
使用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;

}

2
@CMCDragonkai 也许现在对你来说已经有点晚了,但是现在有IPv6版本了。 - miguel-svq

0
基本上是@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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接