完整的 PHP+MySQL IPv4 和IPv6解决方案?

发布于 2024-12-09 10:17:10 字数 365 浏览 0 评论 0原文

我对网络主题不是很了解,但我必须为我的项目存储 IP 地址,并且我想准备好处理 IPv4 和 IPv6。我读过的最佳解决方案似乎是两个 BIGINT 无符号字段,其中一个在 IPv4 的情况下为空:
如何在关系数据库中存储 IPv6 兼容地址< /a> 有人有完整的解决方案吗?

我需要代码从字符串地址(如 $_SERVER['HTTP_CLIENT_IP'] 生成)转换为数值,反之亦然。

非常感谢您的帮助。我想确保我这样做是正确的。

I'm not very knowledgeable about networking topics, but I have to store IP addresses for my project, and I want to be prepared to handle both IPv4 and IPv6. The best solution I've read appears to be two BIGINT unsigned fields where one is null in the case of IPv4:
How to store IPv6-compatible address in a relational database
Does anybody have the complete solution?

I need code to go from a string address (like $_SERVER['HTTP_CLIENT_IP'] produces) to numeric values and vice versa.

Thank you very much for any help. I want to make sure I'm doing this correctly.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

寄人书 2024-12-16 10:17:10

或者,您也可以使用 PostgreSQL 等数据库(如果可以的话)。它具有用于存储和搜索 IPv4 和 IPv6 地址及前缀的本机数据类型。输入和输出(通常)以字符串表示形式完成。

如果您必须使用 MySQL,这实际上取决于您想要如何使用地址。如果您想搜索子网、按前缀分组等,那么整数是最有用的。如果您只需要存储它们,那么 varchar 就是最佳选择。

Or you can use a database like PostgreSQL if that is an option. It has native data types for storing and searching IPv4 and IPv6 addresses and prefixes. Input and output are done in string representation (usually).

If you have to use MySQL it really depends on how you want to use the addresses. If you want to search for subnets, group by prefix etc then integers are the most useful. If you just need to store them then varchar is the way to go.

酒与心事 2024-12-16 10:17:10

实际上,我按照人类书写的方式存储序数。因此,IPv6 有 8 个 16 位无符号整数字段,IPv4 有 4 个 8 位无符号整数字段。对我来说,这使得搜索某些网络变得简单,尽管我可以看到 2 个无符号 bigint 也可以很简单。

或者,您可以按照代码中最常用的格式存储它,并按原样检索它。这似乎是一个字符串,大约有 40 个字符长。如果您这样做,您将需要使用 rfc5952 中建议的规范表示形式。

我不能只为你写转换代码;您需要提供一些示例,说明您尝试过但不起作用的方法以及原因。

I actually store the ordinals the way humans write them. So 8 16 bit unsigned integer fields for IPv6 and 4 8 bit unsigned integer fields for IPv4. For me, this makes searching for certain networks simple, though I can see that 2 unsigned bigints can also be simple.

Or you could store it in the format that you have it in most often in your code and will retrieve it as such. Which seems to be a string, one that's about 40 characters long. If you do that you'll want to use a canonical representation like that suggested in rfc5952.

I can't just write the conversion code for you; you'll need to provide some example of what you tried that didn't work, and why.

我不吻晚风 2024-12-16 10:17:10

对于 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})/

编辑:刚刚注意到这依赖于我的变量解析类 VParse - 你几乎可以替换任何实例VParse::toInt() 具有 PHP 的标准 (int) 类型转换功能。

; //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 - 你几乎可以替换任何实例VParse::toInt() 具有 PHP 的标准 (int) 类型转换功能。

For IP address (format) verification, I'm currently using this as part of something I'm working on - not 100% certain that it's entirely correct as yet - need to throw more data at it (and I don't like the naming convention I've used on the private members either - but that's an easy fix with refactoring):

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})/

EDIT: just noticed this relies on my variable parsing class VParse - you can pretty much replace any instance of VParse::toInt() with PHP's standard (int) type casting functionality.

; //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 }

EDIT: just noticed this relies on my variable parsing class VParse - you can pretty much replace any instance of VParse::toInt() with PHP's standard (int) type casting functionality.

再见回来 2024-12-16 10:17:10

有关选择 IP 地址列类型的一些提示。

当 IP 存储为人类可读字符串时,IPv4 和 IPv6 的最大长度分别为 VARCHAR(15) 和 VARCHAR(39)。

我们可以通过将数据类型设置为 VARBINARY(16) 以二进制形式存储 IP(IPv4 和 IPv6)。我们可以使用 PHP 函数 inet_pton()inet_ntop() 在数据库中存储和检索 IP。

使用 VARBINARY 类型的优点:
通过以二进制形式存储 IP,它将消耗更少的磁盘空间,更小的索引大小意味着更好的性能(获取/插入等),更少的内存(RAM)将用于缓存数据/索引。

使用 VARBINARY 类型的缺点:
存储的值不是人类可读的。

Few tips on choosing the column type for IP Addresses.

When IPs are stored as human readable strings, the maximum length of IPv4 and IPv6 is VARCHAR(15) and VARCHAR(39) respectively.

We can store IPs(both IPv4 & IPv6) in binary form by setting data type to VARBINARY(16). We can use PHP functions inet_pton() and inet_ntop() to store and retrieve IP from DB.

Pros of using VARBINARY type:
By storing IPs in binary form it will consume less disk space, smaller index size means better performance(fetching/inserting etc.), lesser memory(RAM) will be used to cache the data/indexes.

Cons of using VARBINARY type:
The value stored is not human-readable.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文