* Moved Werdna's new IP functions to the IP class
[lhc/web/wiklou.git] / includes / IP.php
1 <?php
2 /*
3 * Collection of public static functions to play with IP address
4 * and IP blocks.
5 *
6 * @Author "Ashar Voultoiz" <hashar@altern.org>
7 * @License GPL v2 or later
8 */
9
10 // Some regex definition to "play" with IP address and IP address blocks
11
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);
18
19 class IP {
20
21 /**
22 * Validate an IP address.
23 * @param string $ipblock Dotted quad
24 * @return boolean True if it is valid.
25 */
26 public static function isValid( $ip ) {
27 return preg_match( '/^' . RE_IP_ADD . '$/', $ip, $matches) ;
28 }
29
30 /**
31 * Validate an IP Block.
32 * @param string $ipblock Dotted quad
33 * @return boolean True if it is valid.
34 */
35 public static function isValidBlock( $ipblock ) {
36 return ( count(self::toArray($ipblock)) == 1 + 5 );
37 }
38
39 /**
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
43 *
44 * @param string $ip Dotted quad
45 * @return bool
46 */
47 public static function isPublic( $ip ) {
48 $n = IP::toUnsigned( $ip );
49 if ( !$n ) {
50 return false;
51 }
52
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 ) ) {
56 return false;
57 }
58
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
67 );
68 }
69
70 foreach ( $privateRanges as $r ) {
71 $start = IP::toUnsigned( $r[0] );
72 $end = IP::toUnsigned( $r[1] );
73 if ( $n >= $start && $n <= $end ) {
74 return false;
75 }
76 }
77 return true;
78 }
79
80 /**
81 * Split out an IP block as an array of 4 bytes and a mask,
82 * return false if it can't be determined
83 *
84 * @param string $ipblock A quad dotted IP address
85 * @return mixed Array or false
86 */
87 public static function toArray( $ipblock ) {
88 if(! preg_match( '/^' . RE_IP_ADD . '(?:\/(?:'.RE_IP_PREFIX.'))?' . '$/', $ipblock, $matches ) ) {
89 return false;
90 } else {
91 return $matches;
92 }
93 }
94
95 /**
96 * Return a zero-padded hexadecimal representation of an IP address.
97 *
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.
102 *
103 * @param string $ip Quad dotted IP address.
104 * @return mixed String or false
105 */
106 public static function toHex( $ip ) {
107 $n = self::toUnsigned( $ip );
108 if ( $n !== false ) {
109 $n = sprintf( '%08X', $n );
110 }
111 return $n;
112 }
113
114 /**
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
120 */
121 public static function toUnsigned( $ip ) {
122 if ( $ip == '255.255.255.255' ) {
123 $n = -1;
124 } else {
125 $n = ip2long( $ip );
126 if ( $n == -1 || $n === false ) { # Return value on error depends on PHP version
127 $n = false;
128 }
129 }
130 if ( $n < 0 ) {
131 $n += pow( 2, 32 );
132 }
133 return $n;
134 }
135
136 /**
137 * Convert a dotted-quad IP to a signed integer
138 * Returns false on failure
139 */
140 public static function toSigned( $ip ) {
141 if ( $ip == '255.255.255.255' ) {
142 $n = -1;
143 } else {
144 $n = ip2long( $ip );
145 if ( $n == -1 ) {
146 $n = false;
147 }
148 }
149 return $n;
150 }
151
152 /**
153 * Convert a network specification in CIDR notation to an integer network and a number of bits
154 */
155 public static function parseCIDR( $range ) {
156 $parts = explode( '/', $range, 2 );
157 if ( count( $parts ) != 2 ) {
158 return array( false, false );
159 }
160 $network = IP::toSigned( $parts[0] );
161 if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) {
162 $bits = $parts[1];
163 if ( $bits == 0 ) {
164 $network = 0;
165 } else {
166 $network &= ~((1 << (32 - $bits)) - 1);
167 }
168 # Convert to unsigned
169 if ( $network < 0 ) {
170 $network += pow( 2, 32 );
171 }
172 } else {
173 $network = false;
174 $bits = false;
175 }
176 return array( $network, $bits );
177 }
178
179 /**
180 * Given a string range in a number of formats, return the start and end of
181 * the range in hexadecimal.
182 *
183 * Formats are:
184 * 1.2.3.4/24 CIDR
185 * 1.2.3.4 - 1.2.3.5 Explicit range
186 * 1.2.3.4 Single IP
187 * @param string $range
188 * @return array (hex string start, hex string end), or (false, false) if an error occurred
189 */
190 public static function parseRange( $range ) {
191 if ( strpos( $range, '/' ) !== false ) {
192 # CIDR
193 list( $network, $bits ) = IP::parseCIDR( $range );
194 if ( $network === false ) {
195 $start = $end = false;
196 } else {
197 $start = sprintf( '%08X', $network );
198 $end = sprintf( '%08X', $network + pow( 2, (32 - $bits) ) - 1 );
199 }
200 } elseif ( strpos( $range, '-' ) !== false ) {
201 # Explicit range
202 list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
203 if ( $start > $end ) {
204 $start = $end = false;
205 } else {
206 $start = IP::toHex( $start );
207 $end = IP::toHex( $end );
208 }
209 } else {
210 # Single IP
211 $start = $end = IP::toHex( $range );
212 }
213 if ( $start === false || $end === false ) {
214 return array( false, false );
215 } else {
216 return array( $start, $end );
217 }
218 }
219
220 /**
221 * Determine if a given integer IPv4 address is in a given range
222 * @param int $addr
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.
225 */
226 public static function isAddressInRange( $addr, $range ) {
227 $unsignedIP = IP::toUnsigned($addr);
228 list($start, $end) = IP::parseRange($range);
229
230 if ($start == false || $end == false)
231 return false;
232 else
233 return (($unsignedIP >= $start) && ($unsignedip <= $end)); // string comparison
234 }
235 }
236 ?>