// An IPv4 block is an IP address and a prefix (d1 to d32)
define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)' );
define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX );
-// For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
-define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' );
-define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
-define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
+
// An IPv6 block is an IP address and a prefix (d1 to d128)
+define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' );
define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)');
-// An IPv6 address is made up of 8 octets. However, the "::" abbreviations can be used.
+// An IPv6 address is made up of 8 words. However, the "::" abbreviations can be used.
define( 'RE_IPV6_ADD',
'(' . // starts with "::" (includes the address "::")
'(::|:(:' . RE_IPV6_WORD . '){1,7})' .
')'
);
define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
+// For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
+define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
+define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
+
// This might be useful for regexps used elsewhere, matches any IPv6 or IPv6 address or network
define( 'IP_ADDRESS_STRING',
'(?:' .
*/
class IP {
/**
- * Given a string, determine if it as valid IP.
- * Note: Unlike isValid(), this looks for networks too.
+ * Determine if a string is as valid IP address or network (CIDR prefix).
+ * SIIT IPv4-translated addresses are rejected.
+ * Note: canonicalize() tries to convert translated addresses to IPv4.
* @param string $ip possible IP address
* @return bool
*/
return (bool)preg_match( '/^' . RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)$/', $ip );
}
+ /**
+ * Validate an IP address. Ranges are NOT considered valid.
+ * SIIT IPv4-translated addresses are rejected.
+ * Note: canonicalize() tries to convert translated addresses to IPv4.
+ * @param string $ip
+ * @return boolean True if it is valid.
+ */
+ public static function isValid( $ip ) {
+ return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip )
+ || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) );
+ }
+
+ /**
+ * Validate an IP Block (valid address WITH a valid prefix).
+ * SIIT IPv4-translated addresses are rejected.
+ * Note: canonicalize() tries to convert translated addresses to IPv4.
+ * @param string $ipblock
+ * @return boolean True if it is valid.
+ */
+ public static function isValidBlock( $ipblock ) {
+ return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock )
+ || preg_match( '/^' . RE_IPV4_BLOCK . '$/', $ipblock ) );
+ }
+
/**
* Given an IP address in dotted-quad notation, returns an IPv6 octet.
* See http://www.answers.com/topic/ipv4-compatible-address
return self::toOctet( self::toUnsigned( $ip ) );
}
- private static function toUnsigned6( $ip ) {
- if ( self::isIPv6( $ip ) ) {
- return wfBaseConvert( self::IPv6ToRawHex( $ip ), 16, 10 );
- }
- return false;
- }
-
/**
* Convert an IP into a nice standard form.
- * IPv6 addresses in octet notation are expanded to 8 octets.
+ * IPv6 addresses in octet notation are expanded to 8 words.
* IPv4 addresses are just trimmed.
* @param string $ip IP address in quad or octet form (CIDR or not).
* @return string
* @return string
*/
public static function toOctet( $ip_int ) {
- $ip_hex = wfBaseConvert( $ip_int, 10, 16, 32, false ); // uppercase hex
- return self::hexToOctet( $ip_hex );
+ return self::hexToOctet( wfBaseConvert( $ip_int, 10, 16, 32, false ) );
}
/**
* @return string (of format a:b:c:d:e:f:g:h)
*/
public static function hexToOctet( $ip_hex ) {
- // Convert to padded uppercase hex
+ // Pad hex to 32 chars (128 bits)
$ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0', STR_PAD_LEFT );
- // Separate into 8 octets
+ // Separate into 8 words
$ip_oct = substr( $ip_hex, 0, 4 );
for ( $n = 1; $n < 8; $n++ ) {
$ip_oct .= ':' . substr( $ip_hex, 4 * $n, 4 );
* @return string (of format a.b.c.d)
*/
public static function hexToQuad( $ip_hex ) {
- // Convert to padded uppercase hex
+ // Pad hex to 8 chars (32 bits)
$ip_hex = str_pad( strtoupper( $ip_hex ), 8, '0', STR_PAD_LEFT );
// Separate into four quads
$s = '';
return $s;
}
- /**
- * Convert a network specification in IPv6 CIDR notation to an
- * integer network and a number of bits
- * @return array(string, int)
- */
- private static function parseCIDR6( $range ) {
- # Explode into <expanded IP,range>
- $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
- if ( count( $parts ) != 2 ) {
- return array( false, false );
- }
- list( $network, $bits ) = $parts;
- $network = self::IPv6ToRawHex( $network );
- if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) {
- if ( $bits == 0 ) {
- $network = "0";
- } else {
- # Native 32 bit functions WONT work here!!!
- # Convert to a padded binary number
- $network = wfBaseConvert( $network, 16, 2, 128 );
- # Truncate the last (128-$bits) bits and replace them with zeros
- $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
- # Convert back to an integer
- $network = wfBaseConvert( $network, 2, 10 );
- }
- } else {
- $network = false;
- $bits = false;
- }
- return array( $network, (int)$bits );
- }
-
- /**
- * Given a string range in a number of formats, return the
- * start and end of the range in hexadecimal. For IPv6.
- *
- * Formats are:
- * 2001:0db8:85a3::7344/96 CIDR
- * 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range
- * 2001:0db8:85a3::7344/96 Single IP
- * @return array(string, int)
- */
- private static function parseRange6( $range ) {
- # Expand any IPv6 IP
- $range = IP::sanitizeIP( $range );
- // CIDR notation...
- if ( strpos( $range, '/' ) !== false ) {
- list( $network, $bits ) = self::parseCIDR6( $range );
- if ( $network === false ) {
- $start = $end = false;
- } else {
- $start = wfBaseConvert( $network, 10, 16, 32, false );
- # Turn network to binary (again)
- $end = wfBaseConvert( $network, 10, 2, 128 );
- # Truncate the last (128-$bits) bits and replace them with ones
- $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
- # Convert to hex
- $end = wfBaseConvert( $end, 2, 16, 32, false );
- # see toHex() comment
- $start = "v6-$start";
- $end = "v6-$end";
- }
- // Explicit range notation...
- } elseif ( strpos( $range, '-' ) !== false ) {
- list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
- $start = self::toUnsigned6( $start );
- $end = self::toUnsigned6( $end );
- if ( $start > $end ) {
- $start = $end = false;
- } else {
- $start = wfBaseConvert( $start, 10, 16, 32, false );
- $end = wfBaseConvert( $end, 10, 16, 32, false );
- }
- # see toHex() comment
- $start = "v6-$start";
- $end = "v6-$end";
- } else {
- # Single IP
- $start = $end = self::toHex( $range );
- }
- if ( $start === false || $end === false ) {
- return array( false, false );
- } else {
- return array( $start, $end );
- }
- }
-
- /**
- * Validate an IP address. Ranges are NOT considered valid.
- * @param string $ip
- * @return boolean True if it is valid.
- */
- public static function isValid( $ip ) {
- return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip )
- || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) );
- }
-
- /**
- * Validate an IP Block (valid address WITH a valid prefix).
- * @param string $ipblock
- * @return boolean True if it is valid.
- */
- public static function isValidBlock( $ipblock ) {
- return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock )
- || preg_match( '/^' . RE_IPV4_BLOCK . '$/', $ipblock ) );
- }
-
/**
* Determine if an IP address really is an IP address, and if it is public,
* i.e. not RFC 1918 or similar
if ( !$privateRanges ) {
$privateRanges = array(
array( 'fc::', 'fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' ), # RFC 4193 (local)
- array( '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1' ), # loopback
+ array( '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1' ), # loopback
);
}
$n = self::toHex( $ip );
return $n;
}
- /**
- * Given an IP address in dotted-quad/octet notation, returns an unsigned integer.
- * Like ip2long() except that it actually works and has a consistent error return value.
- * Comes from ProxyTools.php
- * @param string $ip Quad dotted IP address.
- * @return mixed (string/int/false)
- */
- public static function toUnsigned( $ip ) {
- if ( self::isIPv6( $ip ) ) {
- $n = wfBaseConvert( self::IPv6ToRawHex( $ip ), 16, 10 );
- } else {
- if ( $ip == '255.255.255.255' ) {
- $n = -1;
- } else {
- $n = ip2long( $ip );
- if ( $n == -1 || $n === false ) { # Return value on error depends on PHP version
- $n = false;
- }
- }
- if ( $n < 0 ) {
- $n += pow( 2, 32 );
- }
- }
- return $n;
- }
-
/**
* Given an IPv6 address in octet notation, returns a pure hex string.
* @param string $ip octet ipv6 IP address.
- * @return string hex (uppercase)
+ * @return string pure hex (uppercase)
*/
private static function IPv6ToRawHex( $ip ) {
$ip = self::sanitizeIP( $ip );
}
/**
- * Convert a dotted-quad IP to a signed integer
- * @param string $ip
- * @return mixed (string/false)
+ * Given an IP address in dotted-quad/octet notation, returns an unsigned integer.
+ * Like ip2long() except that it actually works and has a consistent error return value.
+ * Comes from ProxyTools.php
+ * @param string $ip Quad dotted IP address.
+ * @return mixed (string/int/false)
*/
- public static function toSigned( $ip ) {
- if ( $ip == '255.255.255.255' ) {
- $n = -1;
+ public static function toUnsigned( $ip ) {
+ if ( self::isIPv6( $ip ) ) {
+ $n = wfBaseConvert( self::IPv6ToRawHex( $ip ), 16, 10 );
} else {
$n = ip2long( $ip );
- if ( $n == -1 ) {
- $n = false;
+ if ( $n < 0 ) {
+ $n += pow( 2, 32 );
}
}
return $n;
}
+ private static function toUnsigned6( $ip ) {
+ return wfBaseConvert( self::IPv6ToRawHex( $ip ), 16, 10 );
+ }
+
/**
- * Convert a network specification in CIDR notation to an integer network and a number of bits
+ * Convert a network specification in CIDR notation
+ * to an integer network and a number of bits
* @param string $range (CIDR IP)
* @return array(int, int)
*/
return array( false, false );
}
list( $network, $bits ) = $parts;
- $network = self::toSigned( $network );
+ $network = ip2long( $network );
if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 32 ) {
if ( $bits == 0 ) {
$network = 0;
}
}
+ /**
+ * Convert a network specification in IPv6 CIDR notation to an
+ * integer network and a number of bits
+ * @return array(string, int)
+ */
+ private static function parseCIDR6( $range ) {
+ # Explode into <expanded IP,range>
+ $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
+ if ( count( $parts ) != 2 ) {
+ return array( false, false );
+ }
+ list( $network, $bits ) = $parts;
+ $network = self::IPv6ToRawHex( $network );
+ if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) {
+ if ( $bits == 0 ) {
+ $network = "0";
+ } else {
+ # Native 32 bit functions WONT work here!!!
+ # Convert to a padded binary number
+ $network = wfBaseConvert( $network, 16, 2, 128 );
+ # Truncate the last (128-$bits) bits and replace them with zeros
+ $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
+ # Convert back to an integer
+ $network = wfBaseConvert( $network, 2, 10 );
+ }
+ } else {
+ $network = false;
+ $bits = false;
+ }
+ return array( $network, (int)$bits );
+ }
+
+ /**
+ * Given a string range in a number of formats, return the
+ * start and end of the range in hexadecimal. For IPv6.
+ *
+ * Formats are:
+ * 2001:0db8:85a3::7344/96 CIDR
+ * 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range
+ * 2001:0db8:85a3::7344/96 Single IP
+ * @return array(string, int)
+ */
+ private static function parseRange6( $range ) {
+ # Expand any IPv6 IP
+ $range = IP::sanitizeIP( $range );
+ // CIDR notation...
+ if ( strpos( $range, '/' ) !== false ) {
+ list( $network, $bits ) = self::parseCIDR6( $range );
+ if ( $network === false ) {
+ $start = $end = false;
+ } else {
+ $start = wfBaseConvert( $network, 10, 16, 32, false );
+ # Turn network to binary (again)
+ $end = wfBaseConvert( $network, 10, 2, 128 );
+ # Truncate the last (128-$bits) bits and replace them with ones
+ $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
+ # Convert to hex
+ $end = wfBaseConvert( $end, 2, 16, 32, false );
+ # see toHex() comment
+ $start = "v6-$start";
+ $end = "v6-$end";
+ }
+ // Explicit range notation...
+ } elseif ( strpos( $range, '-' ) !== false ) {
+ list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
+ $start = self::toUnsigned6( $start );
+ $end = self::toUnsigned6( $end );
+ if ( $start > $end ) {
+ $start = $end = false;
+ } else {
+ $start = wfBaseConvert( $start, 10, 16, 32, false );
+ $end = wfBaseConvert( $end, 10, 16, 32, false );
+ }
+ # see toHex() comment
+ $start = "v6-$start";
+ $end = "v6-$end";
+ } else {
+ # Single IP
+ $start = $end = self::toHex( $range );
+ }
+ if ( $start === false || $end === false ) {
+ return array( false, false );
+ } else {
+ return array( $start, $end );
+ }
+ }
+
/**
* Determine if a given IPv4/IPv6 address is in a given CIDR network
* @param $addr The address to check against the given range.