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

时间:2011-10-11 17:01:46

标签: php mysql ipv6 ipv4

我对网络主题知之甚少,但我必须为我的项目存储IP地址,并且我想准备好处理IPv4和IPv6。我读过的最佳解决方案似乎是两个BIGINT无符号字段,其中一个在IPv4的情况下为空:
How to store IPv6-compatible address in a relational database 有人有完整的解决方案吗?

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

非常感谢您的帮助。我想确保我正确地做到这一点。

3 个答案:

答案 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)的任何实例。