在 PHP 中使用 IPv6 地址

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/444966/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 22:45:56  来源:igfitidea点击:

Working with IPv6 Addresses in PHP

phpipipv6

提问by matpie

After searching around somewhat thoroughly, I noticed a slight lack of functions in PHP for handling IPv6. For my own personal satisfaction I created a few functions to help the transition.

在彻底搜索之后,我注意到 PHP 中处理IPv6的函数略有缺失。为了我个人的满意度,我创建了一些函数来帮助转换。

The IPv6ToLong()function is a temporary solution to that brought up here: How to store IPv6-compatible address in a relational database. It will split the IP in to two integers and return them in an array.

IPv6ToLong()函数是此处提出的临时解决方案:如何在关系数据库中存储与 IPv6 兼容的地址。它会将 IP 拆分为两个整数并在数组中返回它们。

/**
 * Convert an IPv4 address to IPv6
 *
 * @param string IP Address in dot notation (192.168.1.100)
 * @return string IPv6 formatted address or false if invalid input
 */
function IPv4To6($Ip) {
    static $Mask = '::ffff:'; // This tells IPv6 it has an IPv4 address
    $IPv6 = (strpos($Ip, '::') === 0);
    $IPv4 = (strpos($Ip, '.') > 0);

    if (!$IPv4 && !$IPv6) return false;
    if ($IPv6 && $IPv4) $Ip = substr($Ip, strrpos($Ip, ':')+1); // Strip IPv4 Compatibility notation
    elseif (!$IPv4) return $Ip; // Seems to be IPv6 already?
    $Ip = array_pad(explode('.', $Ip), 4, 0);
    if (count($Ip) > 4) return false;
    for ($i = 0; $i < 4; $i++) if ($Ip[$i] > 255) return false;

    $Part7 = base_convert(($Ip[0] * 256) + $Ip[1], 10, 16);
    $Part8 = base_convert(($Ip[2] * 256) + $Ip[3], 10, 16);
    return $Mask.$Part7.':'.$Part8;
}

/**
 * Replace '::' with appropriate number of ':0'
 */
function ExpandIPv6Notation($Ip) {
    if (strpos($Ip, '::') !== false)
        $Ip = str_replace('::', str_repeat(':0', 8 - substr_count($Ip, ':')).':', $Ip);
    if (strpos($Ip, ':') === 0) $Ip = '0'.$Ip;
    return $Ip;
}

/**
 * Convert IPv6 address to an integer
 *
 * Optionally split in to two parts.
 *
 * @see https://stackoverflow.com/questions/420680/
 */
function IPv6ToLong($Ip, $DatabaseParts= 2) {
    $Ip = ExpandIPv6Notation($Ip);
    $Parts = explode(':', $Ip);
    $Ip = array('', '');
    for ($i = 0; $i < 4; $i++) $Ip[0] .= str_pad(base_convert($Parts[$i], 16, 2), 16, 0, STR_PAD_LEFT);
    for ($i = 4; $i < 8; $i++) $Ip[1] .= str_pad(base_convert($Parts[$i], 16, 2), 16, 0, STR_PAD_LEFT);

    if ($DatabaseParts == 2)
            return array(base_convert($Ip[0], 2, 10), base_convert($Ip[1], 2, 10));
    else    return base_convert($Ip[0], 2, 10) + base_convert($Ip[1], 2, 10);
}

For these functions I typically implement them by calling this function first:

对于这些函数,我通常先调用这个函数来实现它们:

/**
 * Attempt to find the client's IP Address
 *
 * @param bool Should the IP be converted using ip2long?
 * @return string|long The IP Address
 */
function GetRealRemoteIp($ForDatabase= false, $DatabaseParts= 2) {
    $Ip = '0.0.0.0';
    if (isset($_SERVER['HTTP_CLIENT_IP']) && $_SERVER['HTTP_CLIENT_IP'] != '')
        $Ip = $_SERVER['HTTP_CLIENT_IP'];
    elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] != '')
        $Ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    elseif (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] != '')
        $Ip = $_SERVER['REMOTE_ADDR'];
    if (($CommaPos = strpos($Ip, ',')) > 0)
        $Ip = substr($Ip, 0, ($CommaPos - 1));

    $Ip = IPv4To6($Ip);
    return ($ForDatabase ? IPv6ToLong($Ip, $DatabaseParts) : $Ip);
}

Someone please tell me if I'm reinventing the wheel here or I've done something wrong.

有人请告诉我是我在这里重新发明轮子还是我做错了什么。

This implementation converts IPv4 to IPv6. Any IPv6 address it doesn't touch.

此实现将 IPv4 转换为 IPv6。它不涉及的任何 IPv6 地址。

采纳答案by matpie

How about inet_ntop()? Then instead of chopping things into integers, you just use a varbinary(16)to store it.

怎么样inet_ntop()?然后,而不是将事物切成整数,您只需使用 avarbinary(16)来存储它。

回答by Fredrik

You could also store the address in a binary(16) in mysql, so you should have an option to output it in binary from IPv6ToLong().

您还可以将地址存储在 mysql 中的二进制 (16) 中,因此您应该可以选择从 IPv6ToLong() 以二进制形式输出它。

This is really something that need to be added natively in PHP, especially when many IPv6 enabled web-servers report ::FFFF:1.2.3.4 as the client IP and it's incompatible with ip2long, and will break alot of stuff.

这确实需要在 PHP 中本地添加,尤其是当许多启用 IPv6 的 Web 服务器报告 ::FFFF:1.2.3.4 作为客户端 IP 并且它与 ip2long 不兼容时,会破坏很多东西。

回答by datahell

Here is an alternative function using filter_var (PHP >= 5.2)

这是一个使用 filter_var 的替代函数(PHP >= 5.2)

function IPv4To6($ip) {
 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === true) {
  if (strpos($ip, '.') > 0) {
   $ip = substr($ip, strrpos($ip, ':')+1);
  } else { //native ipv6
   return $ip;
  }
 }
 $is_v4 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
 if (!$is_v4) { return false; }
 $iparr = array_pad(explode('.', $ip), 4, 0);
    $Part7 = base_convert(($iparr[0] * 256) + $iparr[1], 10, 16);
    $Part8 = base_convert(($iparr[2] * 256) + $iparr[3], 10, 16);
    return '::ffff:'.$Part7.':'.$Part8;
}

回答by Mike Mackintosh

Going back, I wrote two functions, dtr_ptonand dtr_ntopwhich work with both IPv4 and IPv6. It will convert them back and forth between printable and binary.

回去,我写了两个函数,dtr_pton并且dtr_ntop其工作,同时支持IPv4和IPv6。它将在可打印和二进制之间来回转换它们。

The first function, dtr_ptonwill check if the supplied argument is valid IPv4 or valid IPv6. Depending on the outcome, an exception could be thrown, or the binary representation of the IP could be returned. By using this function, you can then perform AND'ing or OR'ing against the result (for subnetting/whathaveyou). I would suggest you store these in your database as a VARBINARY(39)or VARCHAR(39).

第一个函数dtr_pton将检查提供的参数是有效的 IPv4 还是有效的 IPv6。根据结果​​,可以抛出异常,或者可以返回 IP 的二进制表示。通过使用此功能,您可以对结果执行 AND'ing 或 OR'ing(用于子网划分/whathaveyou)。我建议您将这些作为VARBINARY(39)VARCHAR(39).

/**
* dtr_pton
*
* Converts a printable IP into an unpacked binary string
*
* @author Mike Mackintosh - [email protected]
* @param string $ip
* @return string $bin
*/
function dtr_pton( $ip ){

    if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)){
        return current( unpack( "A4", inet_pton( $ip ) ) );
    }
    elseif(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){
        return current( unpack( "A16", inet_pton( $ip ) ) );
    }

    throw new \Exception("Please supply a valid IPv4 or IPv6 address");

    return false;
}

The second function, dtr_ntopwill convert the binary representation of the IP back to a printable IP address.

第二个函数dtr_ntop将把 IP 的二进制表示转换回可打印的 IP 地址。

/**
* dtr_ntop
*
* Converts an unpacked binary string into a printable IP
*
* @author Mike Mackintosh - [email protected]
* @param string $str
* @return string $ip
*/
function dtr_ntop( $str ){
    if( strlen( $str ) == 16 OR strlen( $str ) == 4 ){
        return inet_ntop( pack( "A".strlen( $str ) , $str ) );
    }

    throw new \Exception( "Please provide a 4 or 16 byte string" );

    return false;
}

Also, here is a quick way of expanding IPv6 addresses found on StackOverflow

此外,这是扩展StackOverflow 上的IPv6 地址的一种快速方法

function expand($ip){
    $hex = unpack("H*hex", inet_pton($ip));         
    $ip = substr(preg_replace("/([A-f0-9]{4})/", ":", $hex['hex']), 0, -1);

    return $ip;
}

Also, a good read on the subject could be found on my blog at HighOnPHP: 5 Tips for Working With IPv6 in PHP. This article uses some of the methods described above in an Object Oriented class which can be found at GitHub: mikemackintosh\dTR-IP

另外,可以在我的博客HighOnPHP: 5 Tips for Working With IPv6 in PHP 中找到关于这个主题的很好的读物。本文在面向对象的类中使用了上面描述的一些方法,该类可以在GitHub 上找到:mikemackintosh\dTR-IP

回答by Ross

PHP.net's Filter extensioncontains some constantsfor matching IPv4 and IPv6 addresses, which might be useful for checking the address. I haven't seen any conversion utilities though.

PHP.net 的过滤器扩展包含一些用于匹配 IPv4 和 IPv6 地址的常量,这对于检查地址可能很有用。不过,我还没有看到任何转换实用程序。