28

我喜欢检查 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; 
}
?>

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

4

6 回答 6

95

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

如果 IP 地址是私有地址,则与以下验证规则一起使用的filter_var将返回 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 文档

于 2012-12-11T10:48:25.953 回答
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

您可能还想在此处查看私有地址空间

于 2012-12-11T10:19:42.003 回答
6

好的,这是 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。或者更具体一点。您可以验证以下类型:

  • 知识产权
  • 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;
}
于 2020-03-31T16:56:39.340 回答
3

...我的 5 美分:

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

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

例如,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;
}

我现在想知道的是:“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 地址设置网络...我们使用的是专用网络地址吗?

于 2016-04-16T16:48:21.787 回答
1

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

}
于 2015-05-09T17:43:32.237 回答
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;
}
于 2018-01-08T07:44:51.437 回答