我对网络主题知之甚少,但我必须为我的项目存储IP地址,并且我想准备好处理IPv4和IPv6。我读过的最佳解决方案似乎是两个BIGINT无符号字段,其中一个在IPv4的情况下为空:
How to store IPv6-compatible address in a relational database
有人有完整的解决方案吗?
我需要代码从字符串地址(如$ _SERVER ['HTTP_CLIENT_IP']生成)到数值,反之亦然。
非常感谢您的帮助。我想确保我正确地做到这一点。
答案 0 :(得分:1)
或者你可以使用像PostgreSQL这样的数据库,如果这是一个选项。它具有用于存储和搜索IPv4和IPv6地址和前缀的本机数据类型。输入和输出以字符串表示形式完成(通常)。
如果你必须使用MySQL,它实际上取决于你想如何使用地址。如果你想搜索子网,按前缀分组等,那么整数是最有用的。如果您只需要存储它们,那么varchar就是您的选择。
答案 1 :(得分:0)
我实际上以人类写它们的方式存储序数。因此,IPv6的8个16位无符号整数字段和IPv4的4个8位无符号整数字段。对我来说,这使得搜索某些网络变得简单,但我可以看到2个无符号的bigint也可以很简单。
或者您可以在代码中以最常用的格式存储它,并将其检索。这似乎是一个字符串,长度约为40个字符。如果您这样做,您将需要使用rfc5952中建议的规范表示。
我不能只为您编写转换代码;你需要提供一些你尝试过的不起作用的例子,以及为什么。
答案 2 :(得分:-1)
对于IP地址(格式)验证,我目前正在使用它作为我正在处理的事情的一部分 - 并非100%确定它现在完全正确 - 需要向其投入更多数据(而且我不是就像我在私人成员上使用的命名约定一样 - 但这是一个很容易解决的重构方法):
class IPAddress {
//IP Address string
private $ip_address;
//IPv4 verification (RegExp insert)
private $match_ipv4;
//IPv6 verification (RegExp insert)
private $match_ipv6;
/**
* Constructor function
*
* The $sIPAddress parameter is optional -
* it allows you to set the IP address in
* the object at creation.
*
* @param string $sIPAddress
* @return void
*/
public function __construct($sIPAddress=null) {
//setup regexp inserts
//IPv4 decimal octets match
$sDecOctet = "([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])";
//IPv4 match
$this->match_ipv4 = "({$sDecOctet}\.){3}{$sDecOctet}";
//Hex 16 match
$sH16 = "[0-9a-fA-F]{1,4}";
//Char32 match
$sLS32 = "({$sH16}:{$sH16}|{$this->match_ipv4})";
//IPv6 match
$this->match_ipv6 = "((({$sH16}:){6}"
. "|::({$sH16}:){5}"
. "|({$sH16})?::({$sH16}:){4}"
. "|(({$sH16}:){0,1}{$sH16})?::({$sH16}:){3}"
. "|(({$sH16}:){0,2}{$sH16})?::({$sH16}:){2}"
. "|(({$sH16}:){0,3}{$sH16})?::{$sH16}:"
. "|(({$sH16}:){0,4}{$sH16})?::"
. "){$sLS32}"
. "|((({$sH16}:){0,5}{$sH16})?::{$sH16}"
. "|(({$sH16}:){0,6}{$sH16})?::"
. "))";
//set the IP address if required
if(!is_null($sIPAddress)) {
$this->setIPAddress($sIPAddress);
}
}
/**
* IP Address setter
*
* Sets the IP address string - this can
* be either IPv4 or IPv6 format.
*
* @param string $sIPAddress
* @return void
*/
public function setIPAddress($sIPAddress) {
$this->ip_address = $sIPAddress;
}
/**
* IP Address getter
*
* Returns the IP address string - this
* can be either IPv4 or IPv6 format.
*
* @return string
*/
public function getIPAddress() {
return $this->ip_address;
}
/**
* IPv4 RegExp getter
*
* Returns Regular Expression used to
* validate IPv4 addresses.
*
* @return string
*/
public function getIPv4RegExp() {
return '/^' . $this->match_ipv4 . '$/';
}
/**
* IPv6 RegExp getter
*
* Returns the Regular Expression used to
* validate IPv6 addresses.
*
* @return string
*/
public function getIPv6RegExp() {
return '/^' . $this->match_ipv6 . '$/i';
}
/**
* IPv4 validation
*
* Validates the stored IP address
* against the IPv4 pattern and returns
* a boolean denoting whether the address
* if of IPv4 format or not.
*
* @return bool
*/
public function validateIPv4() {
return ip2long($this->ip_address) && ip2long($this->ip_address) !== -1 ? true : false;
}
/**
* IPv6 validation
*
* Validates the stored IP address
* against the IPv6 pattern and returns
* a boolean denoting whether the address
* if of IPv6 format or not.
*
* @return bool
*/
public function validateIPv6() {
return preg_match($this->getIPv6RegExp(), $this->ip_address) ? true : false;
}
/**
* General validity check
*
* Validates the stored IP address against
* both the IPv4 and IPv6 patterns - if
* EITHER matches then true is returned
* (it's a correctly formatted IP address).
*
* Otherwise it's not a valid IP address
* and false is returned.
*
* @return bool
*/
public function isValid() {
return $this->validateIPv4() || $this->validateIPv6() ? true : false;
}
/**
* Reserved state checker
*
* This method checks wheter the stored IP address
* is part of the local network range (i.e. it's in
* the private reserved IP address range)
*
* A boolean is returned denoting this reserved state
* unless the IP address itself is invalid - in which
* case null is returned.
*
* @return bool
*/
public function isReserved() {
//IPv4 format
if($this->validateIPv4()) {
return $this->_getIPv4IsReserved($this->ip_address);
}
//IPv6 format
elseif($this->validateIPv6()) {
//IPv4 masking
// this falls over if the IPv4 part is short-handed
// for instance ::ffff:192.0.2.128 can be written as ::ffff:c000:280
$reIPv4Masking = '/^((0{1,4}:){6}|(0{1,4}:){1,5}ffff:|::ffff:)(([0-9]{1,3}\.){3}[0-9]{1,3})/';
//standard reserved IPv6 addresses
//local loopback = 0:0:0:0:0:0:0:1 || ::1
if(preg_match('/^(0{1,4}:){1,7}1|::1|fc00:.*$/i', $this->ip_address)) {
return true;
}
//if this is really an IPv4 address stacked in IPv6...
elseif(preg_match($reIPv4Masking, $this->ip_address)) {
$sIPv4Address = preg_replace($reIPv4Masking, "$2", $this->ip_address);
return $this->_getIPv4IsReserved($sIPv4Address);
}
//not reserved
else {
return false;
}
}
//invalid format
else {
return null;
}
}
/**
* IPv4 reserved state checker
*
* Private method to determine whether an IPv4 address is in
* one of the reserved private brackets (e.g. it's probably local)
*
* Returns a boolean denoting whether it's a reserved IPv4 address
* or null should the IP address fail validation
*
* @param string $sIPv4Address
* @return bool
*/
private function _getIPv4IsReserved($sIPv4Address) {
$sIP = long2ip(ip2long($sIPv4Address));
$reIPv4 = '/([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/$'; //just a quick and dirty RegExp without sanity checking since we've already done that
if(preg_match($reIPv4, $sIP)) {
//break the IP address into parts and cast to integers
$iIPp1 = VParse::toInt(preg_replace($reIPv4, "$1", $sIP));
$iIPp2 = VParse::toInt(preg_replace($reIPv4, "$2", $sIP));
$iIPp3 = VParse::toInt(preg_replace($reIPv4, "$3", $sIP));
$iIPp4 = VParse::toInt(preg_replace($reIPv4, "$4", $sIP));
//check for reserved IP addresses
// 127.0.0.1 (local loopback)
// 10.0.0.0 - 10.255.255.255
// 172.16.0.0 - 172.31.255.255
// 192.168.0.0 - 192.168.255.255
if( ($iIPp1 == 127 && $iIPp2 == 0 && $iIPp3 == 0 && $iIPp4 == 1) || $iIPp1 == 10 || ($iIPp1 == 172 && $iIP2 >= 16 && $iIP2 <= 31) || ($iIPp1 == 192 && $iIPp2 == 168) ) {
return true;
}
//not part of the standard private IP address ranges
else {
return false;
}
}
//invalid format
else {
return null;
}
}
//end class
}
编辑:刚才注意到这依赖于我的变量解析类VParse - 您几乎可以用PHP的标准VParse::toInt()
类型转换功能替换(int)
的任何实例。