IP.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. <?php
  2. /*
  3. * @Author "Ashar Voultoiz" <hashar@altern.org>
  4. * @License GPL v2 or later
  5. */
  6. // Some regex definition to "play" with IP address and IP address blocks
  7. // An IP is made of 4 bytes from x00 to xFF which is d0 to d255
  8. define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])');
  9. define( 'RE_IP_ADD' , RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE );
  10. // An IPv4 block is an IP address and a prefix (d1 to d32)
  11. define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)');
  12. define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX);
  13. // For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
  14. define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' );
  15. define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
  16. define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
  17. // An IPv6 block is an IP address and a prefix (d1 to d128)
  18. define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)');
  19. // An IPv6 IP is made up of 8 octets. However abbreviations like "::" can be used. This is lax!
  20. define( 'RE_IPV6_ADD', '(:(:' . RE_IPV6_WORD . '){1,7}|' . RE_IPV6_WORD . '(:{1,2}' . RE_IPV6_WORD . '|::$){1,7})' );
  21. define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
  22. // This might be useful for regexps used elsewhere, matches any IPv6 or IPv6 address or network
  23. define( 'IP_ADDRESS_STRING',
  24. '(?:' .
  25. RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)' .
  26. '|' .
  27. RE_IPV6_ADD . '(\/' . RE_IPV6_PREFIX . '|)' .
  28. ')' );
  29. /**
  30. * A collection of public static functions to play with IP address
  31. * and IP blocks.
  32. */
  33. class IP {
  34. /**
  35. * Given a string, determine if it as valid IP
  36. * Unlike isValid(), this looks for networks too
  37. * @param $ip IP address.
  38. * @return string
  39. */
  40. public static function isIPAddress( $ip ) {
  41. if ( !$ip ) return false;
  42. if ( is_array( $ip ) ) {
  43. throw new MWException( "invalid value passed to " . __METHOD__ );
  44. }
  45. // IPv6 IPs with two "::" strings are ambiguous and thus invalid
  46. return preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip) && ( substr_count($ip, '::') < 2 );
  47. }
  48. public static function isIPv6( $ip ) {
  49. if ( !$ip ) return false;
  50. if( is_array( $ip ) ) {
  51. throw new MWException( "invalid value passed to " . __METHOD__ );
  52. }
  53. // IPv6 IPs with two "::" strings are ambiguous and thus invalid
  54. return preg_match( '/^' . RE_IPV6_ADD . '(\/' . RE_IPV6_PREFIX . '|)$/', $ip) && ( substr_count($ip, '::') < 2);
  55. }
  56. public static function isIPv4( $ip ) {
  57. if ( !$ip ) return false;
  58. return preg_match( '/^' . RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)$/', $ip);
  59. }
  60. /**
  61. * Given an IP address in dotted-quad notation, returns an IPv6 octet.
  62. * See http://www.answers.com/topic/ipv4-compatible-address
  63. * IPs with the first 92 bits as zeros are reserved from IPv6
  64. * @param $ip quad-dotted IP address.
  65. * @return string
  66. */
  67. public static function IPv4toIPv6( $ip ) {
  68. if ( !$ip ) return null;
  69. // Convert only if needed
  70. if ( self::isIPv6( $ip ) ) return $ip;
  71. // IPv4 CIDRs
  72. if ( strpos( $ip, '/' ) !== false ) {
  73. $parts = explode( '/', $ip, 2 );
  74. if ( count( $parts ) != 2 ) {
  75. return false;
  76. }
  77. $network = self::toUnsigned( $parts[0] );
  78. if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) {
  79. $bits = $parts[1] + 96;
  80. return self::toOctet( $network ) . "/$bits";
  81. } else {
  82. return false;
  83. }
  84. }
  85. return self::toOctet( self::toUnsigned( $ip ) );
  86. }
  87. /**
  88. * Given an IPv6 address in octet notation, returns an unsigned integer.
  89. * @param $ip octet ipv6 IP address.
  90. * @return string
  91. */
  92. public static function toUnsigned6( $ip ) {
  93. if ( !$ip ) return null;
  94. $ip = explode(':', self::sanitizeIP( $ip ) );
  95. $r_ip = '';
  96. foreach ($ip as $v) {
  97. $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
  98. }
  99. $r_ip = wfBaseConvert( $r_ip, 16, 10 );
  100. return $r_ip;
  101. }
  102. /**
  103. * Given an IPv6 address in octet notation, returns the expanded octet.
  104. * IPv4 IPs will be trimmed, thats it...
  105. * @param $ip octet ipv6 IP address.
  106. * @return string
  107. */
  108. public static function sanitizeIP( $ip ) {
  109. $ip = trim( $ip );
  110. if ( $ip === '' ) return null;
  111. // Trim and return IPv4 addresses
  112. if ( self::isIPv4($ip) ) return $ip;
  113. // Only IPv6 addresses can be expanded
  114. if ( !self::isIPv6($ip) ) return $ip;
  115. // Remove any whitespaces, convert to upper case
  116. $ip = strtoupper( $ip );
  117. // Expand zero abbreviations
  118. if ( strpos( $ip, '::' ) !== false ) {
  119. $ip = str_replace('::', str_repeat(':0', 8 - substr_count($ip, ':')) . ':', $ip);
  120. }
  121. // For IPs that start with "::", correct the final IP so that it starts with '0' and not ':'
  122. if ( $ip[0] == ':' ) $ip = "0$ip";
  123. // Remove leading zereos from each bloc as needed
  124. $ip = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip );
  125. return $ip;
  126. }
  127. /**
  128. * Given an unsigned integer, returns an IPv6 address in octet notation
  129. * @param $ip integer IP address.
  130. * @return string
  131. */
  132. public static function toOctet( $ip_int ) {
  133. // Convert to padded uppercase hex
  134. $ip_hex = wfBaseConvert($ip_int, 10, 16, 32, false);
  135. // Separate into 8 octets
  136. $ip_oct = substr( $ip_hex, 0, 4 );
  137. for ($n=1; $n < 8; $n++) {
  138. $ip_oct .= ':' . substr($ip_hex, 4*$n, 4);
  139. }
  140. // NO leading zeroes
  141. $ip_oct = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip_oct );
  142. return $ip_oct;
  143. }
  144. /**
  145. * Given a hexadecimal number, returns to an IPv6 address in octet notation
  146. * @param $ip string hex IP
  147. * @return string
  148. */
  149. public static function HextoOctet( $ip_hex ) {
  150. // Convert to padded uppercase hex
  151. $ip_hex = str_pad( strtoupper($ip_hex), 32, '0');
  152. // Separate into 8 octets
  153. $ip_oct = substr( $ip_hex, 0, 4 );
  154. for ($n=1; $n < 8; $n++) {
  155. $ip_oct .= ':' . substr($ip_hex, 4*$n, 4);
  156. }
  157. // NO leading zeroes
  158. $ip_oct = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip_oct );
  159. return $ip_oct;
  160. }
  161. /**
  162. * Converts a hexadecimal number to an IPv4 address in octet notation
  163. * @param $ip string Hex IP
  164. * @return string
  165. */
  166. public static function hexToQuad( $ip ) {
  167. // Converts a hexadecimal IP to nnn.nnn.nnn.nnn format
  168. $dec = wfBaseConvert( $ip, 16, 10 );
  169. $parts[3] = $dec % 256;
  170. $dec /= 256;
  171. $parts[2] = $dec % 256;
  172. $dec /= 256;
  173. $parts[1] = $dec % 256;
  174. $parts[0] = $dec / 256;
  175. return implode( '.', array_reverse( $parts ) );
  176. }
  177. /**
  178. * Convert a network specification in IPv6 CIDR notation to an integer network and a number of bits
  179. * @return array(string, int)
  180. */
  181. public static function parseCIDR6( $range ) {
  182. # Expand any IPv6 IP
  183. $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
  184. if ( count( $parts ) != 2 ) {
  185. return array( false, false );
  186. }
  187. $network = self::toUnsigned6( $parts[0] );
  188. if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 128 ) {
  189. $bits = $parts[1];
  190. if ( $bits == 0 ) {
  191. $network = 0;
  192. } else {
  193. # Native 32 bit functions WONT work here!!!
  194. # Convert to a padded binary number
  195. $network = wfBaseConvert( $network, 10, 2, 128 );
  196. # Truncate the last (128-$bits) bits and replace them with zeros
  197. $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
  198. # Convert back to an integer
  199. $network = wfBaseConvert( $network, 2, 10 );
  200. }
  201. } else {
  202. $network = false;
  203. $bits = false;
  204. }
  205. return array( $network, $bits );
  206. }
  207. /**
  208. * Given a string range in a number of formats, return the start and end of
  209. * the range in hexadecimal. For IPv6.
  210. *
  211. * Formats are:
  212. * 2001:0db8:85a3::7344/96 CIDR
  213. * 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range
  214. * 2001:0db8:85a3::7344/96 Single IP
  215. * @return array(string, int)
  216. */
  217. public static function parseRange6( $range ) {
  218. # Expand any IPv6 IP
  219. $range = IP::sanitizeIP( $range );
  220. if ( strpos( $range, '/' ) !== false ) {
  221. # CIDR
  222. list( $network, $bits ) = self::parseCIDR6( $range );
  223. if ( $network === false ) {
  224. $start = $end = false;
  225. } else {
  226. $start = wfBaseConvert( $network, 10, 16, 32, false );
  227. # Turn network to binary (again)
  228. $end = wfBaseConvert( $network, 10, 2, 128 );
  229. # Truncate the last (128-$bits) bits and replace them with ones
  230. $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
  231. # Convert to hex
  232. $end = wfBaseConvert( $end, 2, 16, 32, false );
  233. # see toHex() comment
  234. $start = "v6-$start"; $end = "v6-$end";
  235. }
  236. } elseif ( strpos( $range, '-' ) !== false ) {
  237. # Explicit range
  238. list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
  239. $start = self::toUnsigned6( $start ); $end = self::toUnsigned6( $end );
  240. if ( $start > $end ) {
  241. $start = $end = false;
  242. } else {
  243. $start = wfBaseConvert( $start, 10, 16, 32, false );
  244. $end = wfBaseConvert( $end, 10, 16, 32, false );
  245. }
  246. # see toHex() comment
  247. $start = "v6-$start"; $end = "v6-$end";
  248. } else {
  249. # Single IP
  250. $start = $end = self::toHex( $range );
  251. }
  252. if ( $start === false || $end === false ) {
  253. return array( false, false );
  254. } else {
  255. return array( $start, $end );
  256. }
  257. }
  258. /**
  259. * Validate an IP address.
  260. * @return boolean True if it is valid.
  261. */
  262. public static function isValid( $ip ) {
  263. return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip) || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip) );
  264. }
  265. /**
  266. * Validate an IP Block.
  267. * @return boolean True if it is valid.
  268. */
  269. public static function isValidBlock( $ipblock ) {
  270. return ( count(self::toArray($ipblock)) == 1 + 5 );
  271. }
  272. /**
  273. * Determine if an IP address really is an IP address, and if it is public,
  274. * i.e. not RFC 1918 or similar
  275. * Comes from ProxyTools.php
  276. */
  277. public static function isPublic( $ip ) {
  278. $n = self::toUnsigned( $ip );
  279. if ( !$n ) {
  280. return false;
  281. }
  282. // ip2long accepts incomplete addresses, as well as some addresses
  283. // followed by garbage characters. Check that it's really valid.
  284. if( $ip != long2ip( $n ) ) {
  285. return false;
  286. }
  287. static $privateRanges = false;
  288. if ( !$privateRanges ) {
  289. $privateRanges = array(
  290. array( '10.0.0.0', '10.255.255.255' ), # RFC 1918 (private)
  291. array( '172.16.0.0', '172.31.255.255' ), # "
  292. array( '192.168.0.0', '192.168.255.255' ), # "
  293. array( '0.0.0.0', '0.255.255.255' ), # this network
  294. array( '127.0.0.0', '127.255.255.255' ), # loopback
  295. );
  296. }
  297. foreach ( $privateRanges as $r ) {
  298. $start = self::toUnsigned( $r[0] );
  299. $end = self::toUnsigned( $r[1] );
  300. if ( $n >= $start && $n <= $end ) {
  301. return false;
  302. }
  303. }
  304. return true;
  305. }
  306. /**
  307. * Split out an IP block as an array of 4 bytes and a mask,
  308. * return false if it can't be determined
  309. *
  310. * @param $ip string A quad dotted/octet IP address
  311. * @return array
  312. */
  313. public static function toArray( $ipblock ) {
  314. $matches = array();
  315. if( preg_match( '/^' . RE_IP_ADD . '(?:\/(?:'.RE_IP_PREFIX.'))?' . '$/', $ipblock, $matches ) ) {
  316. return $matches;
  317. } else if ( preg_match( '/^' . RE_IPV6_ADD . '(?:\/(?:'.RE_IPV6_PREFIX.'))?' . '$/', $ipblock, $matches ) ) {
  318. return $matches;
  319. } else {
  320. return false;
  321. }
  322. }
  323. /**
  324. * Return a zero-padded hexadecimal representation of an IP address.
  325. *
  326. * Hexadecimal addresses are used because they can easily be extended to
  327. * IPv6 support. To separate the ranges, the return value from this
  328. * function for an IPv6 address will be prefixed with "v6-", a non-
  329. * hexadecimal string which sorts after the IPv4 addresses.
  330. *
  331. * @param $ip Quad dotted/octet IP address.
  332. * @return hexidecimal
  333. */
  334. public static function toHex( $ip ) {
  335. $n = self::toUnsigned( $ip );
  336. if ( $n !== false ) {
  337. $n = self::isIPv6($ip) ? "v6-" . wfBaseConvert( $n, 10, 16, 32, false ) : wfBaseConvert( $n, 10, 16, 8, false );
  338. }
  339. return $n;
  340. }
  341. /**
  342. * Given an IP address in dotted-quad/octet notation, returns an unsigned integer.
  343. * Like ip2long() except that it actually works and has a consistent error return value.
  344. * Comes from ProxyTools.php
  345. * @param $ip Quad dotted IP address.
  346. * @return integer
  347. */
  348. public static function toUnsigned( $ip ) {
  349. // Use IPv6 functions if needed
  350. if ( self::isIPv6( $ip ) ) {
  351. return self::toUnsigned6( $ip );
  352. }
  353. if ( $ip == '255.255.255.255' ) {
  354. $n = -1;
  355. } else {
  356. $n = ip2long( $ip );
  357. if ( $n == -1 || $n === false ) { # Return value on error depends on PHP version
  358. $n = false;
  359. }
  360. }
  361. if ( $n < 0 ) {
  362. $n += pow( 2, 32 );
  363. }
  364. return $n;
  365. }
  366. /**
  367. * Convert a dotted-quad IP to a signed integer
  368. * Returns false on failure
  369. */
  370. public static function toSigned( $ip ) {
  371. if ( $ip == '255.255.255.255' ) {
  372. $n = -1;
  373. } else {
  374. $n = ip2long( $ip );
  375. if ( $n == -1 ) {
  376. $n = false;
  377. }
  378. }
  379. return $n;
  380. }
  381. /**
  382. * Convert a network specification in CIDR notation to an integer network and a number of bits
  383. * @return array(string, int)
  384. */
  385. public static function parseCIDR( $range ) {
  386. $parts = explode( '/', $range, 2 );
  387. if ( count( $parts ) != 2 ) {
  388. return array( false, false );
  389. }
  390. $network = self::toSigned( $parts[0] );
  391. if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) {
  392. $bits = $parts[1];
  393. if ( $bits == 0 ) {
  394. $network = 0;
  395. } else {
  396. $network &= ~((1 << (32 - $bits)) - 1);
  397. }
  398. # Convert to unsigned
  399. if ( $network < 0 ) {
  400. $network += pow( 2, 32 );
  401. }
  402. } else {
  403. $network = false;
  404. $bits = false;
  405. }
  406. return array( $network, $bits );
  407. }
  408. /**
  409. * Given a string range in a number of formats, return the start and end of
  410. * the range in hexadecimal.
  411. *
  412. * Formats are:
  413. * 1.2.3.4/24 CIDR
  414. * 1.2.3.4 - 1.2.3.5 Explicit range
  415. * 1.2.3.4 Single IP
  416. *
  417. * 2001:0db8:85a3::7344/96 CIDR
  418. * 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range
  419. * 2001:0db8:85a3::7344 Single IP
  420. * @return array(string, int)
  421. */
  422. public static function parseRange( $range ) {
  423. // Use IPv6 functions if needed
  424. if ( self::isIPv6( $range ) ) {
  425. return self::parseRange6( $range );
  426. }
  427. if ( strpos( $range, '/' ) !== false ) {
  428. # CIDR
  429. list( $network, $bits ) = self::parseCIDR( $range );
  430. if ( $network === false ) {
  431. $start = $end = false;
  432. } else {
  433. $start = sprintf( '%08X', $network );
  434. $end = sprintf( '%08X', $network + pow( 2, (32 - $bits) ) - 1 );
  435. }
  436. } elseif ( strpos( $range, '-' ) !== false ) {
  437. # Explicit range
  438. list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
  439. if( self::isIPAddress( $start ) && self::isIPAddress( $end ) ) {
  440. $start = self::toUnsigned( $start ); $end = self::toUnsigned( $end );
  441. if ( $start > $end ) {
  442. $start = $end = false;
  443. } else {
  444. $start = sprintf( '%08X', $start );
  445. $end = sprintf( '%08X', $end );
  446. }
  447. } else {
  448. $start = $end = false;
  449. }
  450. } else {
  451. # Single IP
  452. $start = $end = self::toHex( $range );
  453. }
  454. if ( $start === false || $end === false ) {
  455. return array( false, false );
  456. } else {
  457. return array( $start, $end );
  458. }
  459. }
  460. /**
  461. * Determine if a given IPv4/IPv6 address is in a given CIDR network
  462. * @param $addr The address to check against the given range.
  463. * @param $range The range to check the given address against.
  464. * @return bool Whether or not the given address is in the given range.
  465. */
  466. public static function isInRange( $addr, $range ) {
  467. // Convert to IPv6 if needed
  468. $unsignedIP = self::toHex( $addr );
  469. list( $start, $end ) = self::parseRange( $range );
  470. return (($unsignedIP >= $start) && ($unsignedIP <= $end));
  471. }
  472. /**
  473. * Convert some unusual representations of IPv4 addresses to their
  474. * canonical dotted quad representation.
  475. *
  476. * This currently only checks a few IPV4-to-IPv6 related cases. More
  477. * unusual representations may be added later.
  478. *
  479. * @param $addr something that might be an IP address
  480. * @return valid dotted quad IPv4 address or null
  481. */
  482. public static function canonicalize( $addr ) {
  483. if ( self::isValid( $addr ) )
  484. return $addr;
  485. // Annoying IPv6 representations like ::ffff:1.2.3.4
  486. if ( strpos($addr,':') !==false && strpos($addr,'.') !==false ) {
  487. $addr = str_replace( '.', ':', $addr );
  488. if( IP::isIPv6( $addr ) )
  489. return $addr;
  490. }
  491. // IPv6 loopback address
  492. $m = array();
  493. if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) )
  494. return '127.0.0.1';
  495. // IPv4-mapped and IPv4-compatible IPv6 addresses
  496. if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . '(' . RE_IP_ADD . ')$/i', $addr, $m ) )
  497. return $m[1];
  498. if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD . ':' . RE_IPV6_WORD . '$/i', $addr, $m ) )
  499. return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) );
  500. return null; // give up
  501. }
  502. }