IP Address Validation

An IP address is one of an IPv4 address, an IPv6 address, or an IPv4-mapped IPv6 address.

An IPv4 address consists of four groups, separated by dots, each containing a decimal value between 0 and 255. A regular expression check to match for an IPv4 address would be as follows:

// IPv4 address

'/^(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?1)){3}$/D'

An IPv6 address consists of eight groups, separated by colons, each containing a hexadecimal value between 0 and FFFF. A regular expression check to match for an IPv6 address would be as follows:

// IPv6 address (full)

'/^([a-f0-9]{1,4})(?>:(?1)){7}$/iD'

In an IPv6 Address, one or more consecutive groups of 0 value can be represented as a double colon; however, this can only occur once. A regular expression check to match for a compressed IPv6 address would be as follows:

// IPv6 address (compressed)

'/^(?!(?:.*[a-f0-9](?>:|$)){8,})(([a-f0-9]{1,4})(?>:(?2)){0,6})?::(?1)?$/iD'

An IPv4-mapped IPv6 address is an IPv6 address with the final two groups represented as an IPv4 address. A regular expression check to match for an IPv4-mapped IPv6 address would be as follows:

// IPv4-mapped IPv6 address (full)

'/^([a-f0-9]{1,4})(?>:(?1)){5}:(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?2)){3}$/iD'

In an IPv4-mapped IPv6 address, one or more consecutive groups of 0 value can be represented as a double colon; however, this can only occur once. A regular expression check to match for a compressed IPv4-mapped IPv6 address would be as follows:

// IPv4-mapped IPv6 address (compressed)

'/^(?!(?:.*[a-f0-9]:){6,})(?>([a-f0-9]{1,4})(?>:(?1)){0,4})?::(?>(?1)(?>:(?1)){0,4}:)?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?2)){3}$/iD'

By bringing these regexes together we are left with the following which matches for every IP address:

// IP address

'/^(?>(?>([a-f0-9]{1,4})(?>:(?1)){7}|(?!(?:.*[a-f0-9](?>:|$)){8,})((?1)(?>:(?1)){0,6})?::(?2)?)|(?>(?>(?1)(?>:(?1)){5}:|(?!(?:.*[a-f0-9]:){6,})(?3)?::(?>((?1)(?>:(?1)){0,4}):)?)?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?4)){3}))$/iD'

We can then create a function which returns the return value of a (case-insensitive) preg_match on the above regular expression:

  function isValidIPAddress($ipAddress)
  {
    return preg_match('/^(?>(?>([a-f0-9]{1,4})(?>:(?1)){7}|(?!(?:.*[a-f0-9](?>:|$)){8,})((?1)(?>:(?1)){0,6})?::(?2)?)|(?>(?>(?1)(?>:(?1)){5}:|(?!(?:.*[a-f0-9]:){6,})(?3)?::(?>((?1)(?>:(?1)){0,4}):)?)?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?4)){3}))$/iD', $ipAddress);
  }

For a class which allows greater control over which type(s) of IP addresses to validate, see below:

<?php

  /**
   * Squiloople Framework
   *
   * LICENSE: Feel free to use and redistribute this code.
   *
   * @author Michael Rushton <michael@squiloople.com>
   * @link http://squiloople.com/
   * @category Squiloople
   * @package Models
   * @subpackage Validators
   * @version 1.0
   * @copyright Copyright © 2010 Michael Rushton
   */

  namespace Models\Validators;

  /**
   * IP Address Validator
   *
   * Validate IPv4, IPv6, and IPv4-mapped IPv6 addresses
   */
  final class IPAddressValidator
  {

    /**
     * The IP address to validate
     *
     * @access private
     * @var string $_ipAddress
     */
    private $_ipAddress;

    /**
     * An IPv4 address is either allowed (true) or not (false)
     *
     * @access private
     * @var bool $_ipv6
     */
    private $_ipv4 = true;

    /**
     * An IPv6 address is either allowed (true) or not (false)
     *
     * @access private
     * @var bool $_ipv6
     */
    private $_ipv6 = false;

    /**
     * Set the IP address and allow IPv6 addresses if required
     *
     * @access public
     * @param string $ipAddress
     * @param bool $all
     */
    public function __construct($ipAddress, $ipv6 = false)
    {

      // Set the IP address
      $this->_ipAddress = $ipAddress;

      // Allow IPv6 addresses if required
      if ($ipv6)
      {
        $this->setIPv6();
      }

    }

    /**
     * Call the constructor fluently
     *
     * @access public
     * @static
     * @param string $ipAddress
     * @param bool $ipv6
     * @return \Models\Validators\IPAddressValidator
     */
    public static function setIPAddress($ipAddress, $ipv6 = false)
    {
      return new self($ipAddress, $ipv6);
    }

    /**
     * Either allow (true) or disallow (false) IPv4 addresses
     *
     * @access public
     * @param bool $allow
     * @return \Models\Validators\IPAddressValidator
     */
    public function setIPv4($allow = true)
    {

      // Either allow (true) or disallow (false) an IPv4 address
      $this->_ipv4 = $allow;

      // Return itself
      return $this;

    }

    /**
     * Either allow (true) or disallow (false) IPv6 addresses
     *
     * @access public
     * @param bool $allow
     * @return \Models\Validators\IPAddressValidator
     */
    public function setIPv6($allow = true)
    {

      // Either allow (true) or disallow (false) an IPv6 address
      $this->_ipv6 = $allow;

      // Return itself
      return $this;

    }

    /**
     * Return the regular expression for a standard IPv6 address
     *
     * @access private
     * @return string
     */
    private function _getIPv6()
    {
      return '([a-f0-9]{1,4})(?>:(?1)){7}|(?!(?:.*[a-f0-9](?>:|$)){8,})((?1)(?>:(?1)){0,6})?::(?2)?';
    }

    /**
     * Return the regular expression for an IPv4-mapped IPv6 address
     *
     * @access private
     * @return string
     */
    private function _getMappedIPv6()
    {
      return '(?1)(?>:(?1)){5}:|(?!(?:.*[a-f0-9]:){6,})(?>(?1)(?>:(?1)){0,4})?::(?>(?1)(?>:(?1)){0,4}:)?';
    }

    /**
     * Return the regular expression for an IPv4 address
     *
     * @access private
     * @return string
     */
    private function _getIPv4()
    {
      return '(?<ip>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?P>ip)){3}';
    }

    /**
     * Establish, and return, the valid format for the IP address
     *
     * @access private
     * @return string
     */
    private function _getIPAddress()
    {

      // The IP address may be either in IPv4 format or in IPv6 format if both are allowed
      if ($this->_ipv6 && $this->_ipv4)
      {
        return '(?>(?>' . $this->_getIPv6() . ')|(?>(?>' . $this->_getMappedIPv6() . ')?' . $this->_getIPv4() . '))';
      }

      // The IP address may be in IPv6 format if allowed
      if ($this->_ipv6)
      {
        return '(?>(?>' . $this->_getIPv6() . ')|(?>(?>' . $this->_getMappedIPv6() . ')' . $this->_getIPv4() . '))';
      }

      // Otherwise the IP address must be in IPv4 format
      return $this->_getIPv4();

    }

    /**
     * Perform the validation check on the IP address's syntax
     *
     * @access public
     * @return int
     */
    public function isValid()
    {
      return preg_match('/^' . $this->_getIPAddress() . '$/iD', $this->_ipAddress);
    }

  }

On creating the object, using either \Models\Validators\IPAddressValidator::setIPAddress($ipAddress) or new \Models\Validators\IPAddressValidator($ipAddress), the default settings allow only for IPv4 addresses. If the (optional) second parameter is set to true then IPv6 and IPv4-mapped IPv6 addresses are also allowed. Allowing IPv6 and IPv4-mapped IPv6 addresses can be toggled on and off by calling the setIPv6() method, passing either no parameter or a true parameter to turn it on or a false parameter to turn it off. If IPv6 and IPv4-mapped IPv6 addresses are allowed then IPv4 addresses may be turned off using the setIPv4() method passing false as the parameter. To return the validation check (either 1 if it’s valid or 0 if it’s not), use the isValid() method.

Tags: ,

Monday, August 16th, 2010 PHP

1 Comment to IP Address Validation

  • rudra says:

    can i have that expression to match ipaddresses (ipv4 and ipv6) in perl?

  • Leave a Reply