The version of vichan running on lainchan.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

208 lines
6.7KB

  1. <?php
  2. /**
  3. * This file is part of the Lifo\IP PHP Library.
  4. *
  5. * (c) Jason Morriss <lifo2013@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Lifo\IP;
  11. /**
  12. * IP Address helper class.
  13. *
  14. * Provides routines to translate IPv4 and IPv6 addresses between human readable
  15. * strings, decimal, hexidecimal and binary.
  16. *
  17. * Requires BCmath extension and IPv6 PHP support
  18. */
  19. abstract class IP
  20. {
  21. /**
  22. * Convert a human readable (presentational) IP address string into a decimal string.
  23. */
  24. public static function inet_ptod($ip)
  25. {
  26. // shortcut for IPv4 addresses
  27. if (strpos($ip, ':') === false && strpos($ip, '.') !== false) {
  28. return sprintf('%u', ip2long($ip));
  29. }
  30. // remove any cidr block notation
  31. if (($o = strpos($ip, '/')) !== false) {
  32. $ip = substr($ip, 0, $o);
  33. }
  34. // unpack into 4 32bit integers
  35. $parts = unpack('N*', inet_pton($ip));
  36. foreach ($parts as &$part) {
  37. if ($part < 0) {
  38. // convert signed int into unsigned
  39. $part = sprintf('%u', $part);
  40. //$part = bcadd($part, '4294967296');
  41. }
  42. }
  43. // add each 32bit integer to the proper bit location in our big decimal
  44. $decimal = $parts[4]; // << 0
  45. $decimal = bcadd($decimal, bcmul($parts[3], '4294967296')); // << 32
  46. $decimal = bcadd($decimal, bcmul($parts[2], '18446744073709551616')); // << 64
  47. $decimal = bcadd($decimal, bcmul($parts[1], '79228162514264337593543950336')); // << 96
  48. return $decimal;
  49. }
  50. /**
  51. * Convert a decimal string into a human readable IP address.
  52. */
  53. public static function inet_dtop($decimal, $expand = false)
  54. {
  55. $parts = array();
  56. $parts[1] = bcdiv($decimal, '79228162514264337593543950336', 0); // >> 96
  57. $decimal = bcsub($decimal, bcmul($parts[1], '79228162514264337593543950336'));
  58. $parts[2] = bcdiv($decimal, '18446744073709551616', 0); // >> 64
  59. $decimal = bcsub($decimal, bcmul($parts[2], '18446744073709551616'));
  60. $parts[3] = bcdiv($decimal, '4294967296', 0); // >> 32
  61. $decimal = bcsub($decimal, bcmul($parts[3], '4294967296'));
  62. $parts[4] = $decimal; // >> 0
  63. foreach ($parts as &$part) {
  64. if (bccomp($part, '2147483647') == 1) {
  65. $part = bcsub($part, '4294967296');
  66. }
  67. $part = (int) $part;
  68. }
  69. // if the first 96bits is all zeros then we can safely assume we
  70. // actually have an IPv4 address. Even though it's technically possible
  71. // you're not really ever going to see an IPv6 address in the range:
  72. // ::0 - ::ffff
  73. // It's feasible to see an IPv6 address of "::", in which case the
  74. // caller is going to have to account for that on their own.
  75. if (($parts[1] | $parts[2] | $parts[3]) == 0) {
  76. $ip = long2ip($parts[4]);
  77. } else {
  78. $packed = pack('N4', $parts[1], $parts[2], $parts[3], $parts[4]);
  79. $ip = inet_ntop($packed);
  80. }
  81. // Turn IPv6 to IPv4 if it's IPv4
  82. if (preg_match('/^::\d+\./', $ip)) {
  83. return substr($ip, 2);
  84. }
  85. return $expand ? self::inet_expand($ip) : $ip;
  86. }
  87. /**
  88. * Convert a human readable (presentational) IP address into a HEX string.
  89. */
  90. public static function inet_ptoh($ip)
  91. {
  92. return bin2hex(inet_pton($ip));
  93. //return BC::bcdechex(self::inet_ptod($ip));
  94. }
  95. /**
  96. * Convert a human readable (presentational) IP address into a BINARY string.
  97. */
  98. public static function inet_ptob($ip, $bits = 128)
  99. {
  100. return BC::bcdecbin(self::inet_ptod($ip), $bits);
  101. }
  102. /**
  103. * Convert a binary string into an IP address (presentational) string.
  104. */
  105. public static function inet_btop($bin)
  106. {
  107. return self::inet_dtop(BC::bcbindec($bin));
  108. }
  109. /**
  110. * Convert a HEX string into a human readable (presentational) IP address
  111. */
  112. public static function inet_htop($hex)
  113. {
  114. return self::inet_dtop(BC::bchexdec($hex));
  115. }
  116. /**
  117. * Expand an IP address. IPv4 addresses are returned as-is.
  118. *
  119. * Example:
  120. * 2001::1 expands to 2001:0000:0000:0000:0000:0000:0000:0001
  121. * ::127.0.0.1 expands to 0000:0000:0000:0000:0000:0000:7f00:0001
  122. * 127.0.0.1 expands to 127.0.0.1
  123. */
  124. public static function inet_expand($ip)
  125. {
  126. // strip possible cidr notation off
  127. if (($pos = strpos($ip, '/')) !== false) {
  128. $ip = substr($ip, 0, $pos);
  129. }
  130. $bytes = unpack('n*', inet_pton($ip));
  131. if (count($bytes) > 2) {
  132. return implode(':', array_map(function ($b) {
  133. return sprintf("%04x", $b);
  134. }, $bytes));
  135. }
  136. return $ip;
  137. }
  138. /**
  139. * Convert an IPv4 address into an IPv6 address.
  140. *
  141. * One use-case for this is IP 6to4 tunnels used in networking.
  142. *
  143. * @example
  144. * to_ipv4("10.10.10.10") == a0a:a0a
  145. *
  146. * @param string $ip IPv4 address.
  147. * @param boolean $mapped If true a Full IPv6 address is returned within the
  148. * official ipv4to6 mapped space "0:0:0:0:0:ffff:x:x"
  149. */
  150. public static function to_ipv6($ip, $mapped = false)
  151. {
  152. if (!self::isIPv4($ip)) {
  153. throw new \InvalidArgumentException("Invalid IPv4 address \"$ip\"");
  154. }
  155. $num = IP::inet_ptod($ip);
  156. $o1 = dechex($num >> 16);
  157. $o2 = dechex($num & 0x0000FFFF);
  158. return $mapped ? "0:0:0:0:0:ffff:$o1:$o2" : "$o1:$o2";
  159. }
  160. /**
  161. * Returns true if the IP address is a valid IPv4 address
  162. */
  163. public static function isIPv4($ip)
  164. {
  165. return $ip === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
  166. }
  167. /**
  168. * Returns true if the IP address is a valid IPv6 address
  169. */
  170. public static function isIPv6($ip)
  171. {
  172. return $ip === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
  173. }
  174. /**
  175. * Compare two IP's (v4 or v6) and return -1, 0, 1 if the first is < = >
  176. * the second.
  177. *
  178. * @param string $ip1 IP address
  179. * @param string $ip2 IP address to compare against
  180. * @return integer Return -1,0,1 depending if $ip1 is <=> $ip2
  181. */
  182. public static function cmp($ip1, $ip2)
  183. {
  184. return bccomp(self::inet_ptod($ip1), self::inet_ptod($ip2), 0);
  185. }
  186. }