如何在 PHP 中处理 IPv6 地址?
Posted
技术标签:
【中文标题】如何在 PHP 中处理 IPv6 地址?【英文标题】:How to handle IPv6 Addresses in PHP? 【发布时间】:2010-10-01 11:48:54 【问题描述】:在彻底搜索之后,我注意到 php 中稍微缺乏处理 IPv6 的函数。为了我个人的满意,我创建了一些函数来帮助过渡。
IPv6ToLong()
函数是对这里提出的问题的临时解决方案:How to store IPv6-compatible address in a relational database。它会将 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://***.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);
对于这些函数,我通常先调用这个函数来实现它们:
/**
* 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';
// [snip: deleted some dangerous code not relevant to question. @webb]
if (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);
如果我在这里重新发明***或者我做错了什么,请告诉我。
此实现将 IPv4 转换为 IPv6。任何不涉及的 IPv6 地址。
【问题讨论】:
您的getRealRemoteIp
存在危险的缺陷 - 攻击者可以欺骗方法测试的这些标头之一,并使用任意值覆盖 REMOTE_ADDRESS
。如果您使用该 IP 地址进行日志记录,则不能依赖记录的地址。
为了互联网的健康,请编辑您的问题并删除 GetRealRemoteIp 功能(这是用词不当,GetSpoofedRemoteIp 更合适)。
【参考方案1】:
inet_ntop()
怎么样?然后,您只需使用varbinary(16)
来存储它,而不是将事物切成整数。
【讨论】:
为什么我应该使用varbinary而不是varchar,这个inet_ntop是什么,为什么不应该使用$_SERVER['REMOTE_ADDR']; 不应该是binary(16)
吗?
不,因为如果地址只是 IPv4,那么它将只消耗 4 个字节而不是 16 个。
我想你的意思是 inet_pton();
@Marcin:用于存储值是的,但最终两者都需要。【参考方案2】:
PHP.net 的 Filter extension 包含用于匹配 IPv4 和 IPv6 地址的 some constants,这对于检查地址可能很有用。不过,我还没有看到任何转换实用程序。
【讨论】:
【参考方案3】:您还可以将地址存储在 mysql 中的二进制 (16) 中,因此您应该可以选择从 IPv6ToLong() 以二进制形式输出它。
这确实是需要在 PHP 中本地添加的东西,尤其是当许多启用 IPv6 的 Web 服务器报告 ::FFFF:1.2.3.4 作为客户端 IP 并且它与 ip2long 不兼容时,会破坏很多东西。
【讨论】:
只是不要使用 ip2long。【参考方案4】:这是一个使用 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;
【讨论】:
filter_var()
将在成功时返回过滤后的数据,因此您的第一个 if
语句实际上会因有效的 IPv6 地址而失败。修复将 === true
更改为 !== false
【参考方案5】:
回过头来,我写了两个函数,dtr_pton
和 dtr_ntop
,它们都适用于 IPv4 和 IPv6。它将在可打印和二进制之间来回转换它们。
第一个函数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 - mike@bakeryphp.com
* @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;
第二个函数,dtr_ntop
会将 IP 的二进制表示转换回可打印的 IP 地址。
/**
* dtr_ntop
*
* Converts an unpacked binary string into a printable IP
*
* @author Mike Mackintosh - mike@bakeryphp.com
* @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;
另外,这里有一个扩展 IPv6 地址的快速方法found on ***
function expand($ip)
$hex = unpack("H*hex", inet_pton($ip));
$ip = substr(preg_replace("/([A-f0-9]4)/", "$1:", $hex['hex']), 0, -1);
return $ip;
此外,可以在我的博客HighOnPHP: 5 Tips for Working With IPv6 in PHP 上找到有关该主题的好读物。本文在一个面向对象的类中使用了上面描述的一些方法,可以在GitHub: mikemackintosh\dTR-IP找到。
【讨论】:
在这两个函数中,'A' 应该是 'a',和前者一样,任何 '0' 八位位组都会产生 '32' (ipv4) 或 '20' (ipv6)用空格(ASCII 32)填充,而不是 NUL。以上是关于如何在 PHP 中处理 IPv6 地址?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Vista/Windows7 中添加持久 IPv6 地址?
如何在 mobilefirst 混合应用程序中使用 IPV6 IP 地址构建应用程序?