3 * Collection of public static functions to play with IP address
6 * @Author "Ashar Voultoiz" <hashar@altern.org>
7 * @License GPL v2 or later
10 // Some regex definition to "play" with IP address and IP address blocks
12 // An IP is made of 4 bytes from x00 to xFF which is d0 to d255
13 define( 'RE_IP_BYTE', '(25[0-5]|2[0-4]\d|1?\d{1,2})');
14 define( 'RE_IP_ADD' , RE_IP_BYTE
. '\.' . RE_IP_BYTE
. '\.' . RE_IP_BYTE
. '\.' . RE_IP_BYTE
);
15 // An IP block is an IP address and a prefix (d1 to d32)
16 define( 'RE_IP_PREFIX' , '(3[0-2]|[12]?\d)');
17 define( 'RE_IP_BLOCK', RE_IP_ADD
. '\/' . RE_IP_PREFIX
);
22 * Validate an IP address.
23 * @param string $ipblock Dotted quad
24 * @return boolean True if it is valid.
26 public static function isValid( $ip ) {
27 return preg_match( '/^' . RE_IP_ADD
. '$/', $ip, $matches) ;
31 * Validate an IP Block.
32 * @param string $ipblock Dotted quad
33 * @return boolean True if it is valid.
35 public static function isValidBlock( $ipblock ) {
36 return ( count(self
::toArray($ipblock)) == 1 +
5 );
40 * Determine if an IP address really is an IP address, and if it is public,
41 * i.e. not RFC 1918 or similar
42 * Comes from ProxyTools.php
44 * @param string $ip Dotted quad
47 public static function isPublic( $ip ) {
48 $n = IP
::toUnsigned( $ip );
53 // ip2long accepts incomplete addresses, as well as some addresses
54 // followed by garbage characters. Check that it's really valid.
55 if( $ip != long2ip( $n ) ) {
59 static $privateRanges = false;
60 if ( !$privateRanges ) {
61 $privateRanges = array(
62 array( '10.0.0.0', '10.255.255.255' ), # RFC 1918 (private)
63 array( '172.16.0.0', '172.31.255.255' ), # "
64 array( '192.168.0.0', '192.168.255.255' ), # "
65 array( '0.0.0.0', '0.255.255.255' ), # this network
66 array( '127.0.0.0', '127.255.255.255' ), # loopback
70 foreach ( $privateRanges as $r ) {
71 $start = IP
::toUnsigned( $r[0] );
72 $end = IP
::toUnsigned( $r[1] );
73 if ( $n >= $start && $n <= $end ) {
81 * Split out an IP block as an array of 4 bytes and a mask,
82 * return false if it can't be determined
84 * @param string $ipblock A quad dotted IP address
85 * @return mixed Array or false
87 public static function toArray( $ipblock ) {
88 if(! preg_match( '/^' . RE_IP_ADD
. '(?:\/(?:'.RE_IP_PREFIX
.'))?' . '$/', $ipblock, $matches ) ) {
96 * Return a zero-padded hexadecimal representation of an IP address.
98 * Hexadecimal addresses are used because they can easily be extended to
99 * IPv6 support. To separate the ranges, the return value from this
100 * function for an IPv6 address will be prefixed with "v6-", a non-
101 * hexadecimal string which sorts after the IPv4 addresses.
103 * @param string $ip Quad dotted IP address.
104 * @return mixed String or false
106 public static function toHex( $ip ) {
107 $n = self
::toUnsigned( $ip );
108 if ( $n !== false ) {
109 $n = sprintf( '%08X', $n );
115 * Given an IP address in dotted-quad notation, returns an unsigned integer.
116 * Like ip2long() except that it actually works and has a consistent error return value.
117 * Comes from ProxyTools.php
118 * @param $ip Quad dotted IP address.
119 * @return mixed Int or false
121 public static function toUnsigned( $ip ) {
122 if ( $ip == '255.255.255.255' ) {
126 if ( $n == -1 ||
$n === false ) { # Return value on error depends on PHP version
137 * Convert a dotted-quad IP to a signed integer
138 * Returns false on failure
140 public static function toSigned( $ip ) {
141 if ( $ip == '255.255.255.255' ) {
153 * Convert a network specification in CIDR notation to an integer network and a number of bits
155 public static function parseCIDR( $range ) {
156 $parts = explode( '/', $range, 2 );
157 if ( count( $parts ) != 2 ) {
158 return array( false, false );
160 $network = IP
::toSigned( $parts[0] );
161 if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) {
166 $network &= ~
((1 << (32 - $bits)) - 1);
168 # Convert to unsigned
169 if ( $network < 0 ) {
170 $network +
= pow( 2, 32 );
176 return array( $network, $bits );
180 * Given a string range in a number of formats, return the start and end of
181 * the range in hexadecimal.
185 * 1.2.3.4 - 1.2.3.5 Explicit range
187 * @param string $range
188 * @return array (hex string start, hex string end), or (false, false) if an error occurred
190 public static function parseRange( $range ) {
191 if ( strpos( $range, '/' ) !== false ) {
193 list( $network, $bits ) = IP
::parseCIDR( $range );
194 if ( $network === false ) {
195 $start = $end = false;
197 $start = sprintf( '%08X', $network );
198 $end = sprintf( '%08X', $network +
pow( 2, (32 - $bits) ) - 1 );
200 } elseif ( strpos( $range, '-' ) !== false ) {
202 list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
203 if ( $start > $end ) {
204 $start = $end = false;
206 $start = IP
::toHex( $start );
207 $end = IP
::toHex( $end );
211 $start = $end = IP
::toHex( $range );
213 if ( $start === false ||
$end === false ) {
214 return array( false, false );
216 return array( $start, $end );
221 * Determine if a given integer IPv4 address is in a given range
223 * @param string $range (CIDR, hyphenated dotted-quad, or single dotted-quad)
224 * @return bool Whether or not the given address is in the given range. Returns false on error.
226 public static function isAddressInRange( $addr, $range ) {
227 $unsignedIP = IP
::toUnsigned($addr);
228 list($start, $end) = IP
::parseRange($range);
230 if ($start == false ||
$end == false)
233 return (($unsignedIP >= $start) && ($unsignedip <= $end)); // string comparison