* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Lifo\IP; /** * IP Address helper class. * * Provides routines to translate IPv4 and IPv6 addresses between human readable * strings, decimal, hexidecimal and binary. * * Requires BCmath extension and IPv6 PHP support */ abstract class IP { /** * Convert a human readable (presentational) IP address string into a decimal string. */ public static function inet_ptod($ip) { // shortcut for IPv4 addresses if (strpos($ip, ':') === false && strpos($ip, '.') !== false) { return sprintf('%u', ip2long($ip)); } // remove any cidr block notation if (($o = strpos($ip, '/')) !== false) { $ip = substr($ip, 0, $o); } // unpack into 4 32bit integers $parts = unpack('N*', inet_pton($ip)); foreach ($parts as &$part) { if ($part < 0) { // convert signed int into unsigned $part = sprintf('%u', $part); //$part = bcadd($part, '4294967296'); } } // add each 32bit integer to the proper bit location in our big decimal $decimal = $parts[4]; // << 0 $decimal = bcadd($decimal, bcmul($parts[3], '4294967296')); // << 32 $decimal = bcadd($decimal, bcmul($parts[2], '18446744073709551616')); // << 64 $decimal = bcadd($decimal, bcmul($parts[1], '79228162514264337593543950336')); // << 96 return $decimal; } /** * Convert a decimal string into a human readable IP address. */ public static function inet_dtop($decimal, $expand = false) { $parts = array(); $parts[1] = bcdiv($decimal, '79228162514264337593543950336', 0); // >> 96 $decimal = bcsub($decimal, bcmul($parts[1], '79228162514264337593543950336')); $parts[2] = bcdiv($decimal, '18446744073709551616', 0); // >> 64 $decimal = bcsub($decimal, bcmul($parts[2], '18446744073709551616')); $parts[3] = bcdiv($decimal, '4294967296', 0); // >> 32 $decimal = bcsub($decimal, bcmul($parts[3], '4294967296')); $parts[4] = $decimal; // >> 0 foreach ($parts as &$part) { if (bccomp($part, '2147483647') == 1) { $part = bcsub($part, '4294967296'); } $part = (int) $part; } // if the first 96bits is all zeros then we can safely assume we // actually have an IPv4 address. Even though it's technically possible // you're not really ever going to see an IPv6 address in the range: // ::0 - ::ffff // It's feasible to see an IPv6 address of "::", in which case the // caller is going to have to account for that on their own. if (($parts[1] | $parts[2] | $parts[3]) == 0) { $ip = long2ip($parts[4]); } else { $packed = pack('N4', $parts[1], $parts[2], $parts[3], $parts[4]); $ip = inet_ntop($packed); } // Turn IPv6 to IPv4 if it's IPv4 if (preg_match('/^::\d+\./', $ip)) { return substr($ip, 2); } return $expand ? self::inet_expand($ip) : $ip; } /** * Convert a human readable (presentational) IP address into a HEX string. */ public static function inet_ptoh($ip) { return bin2hex(inet_pton($ip)); //return BC::bcdechex(self::inet_ptod($ip)); } /** * Convert a human readable (presentational) IP address into a BINARY string. */ public static function inet_ptob($ip, $bits = 128) { return BC::bcdecbin(self::inet_ptod($ip), $bits); } /** * Convert a binary string into an IP address (presentational) string. */ public static function inet_btop($bin) { return self::inet_dtop(BC::bcbindec($bin)); } /** * Convert a HEX string into a human readable (presentational) IP address */ public static function inet_htop($hex) { return self::inet_dtop(BC::bchexdec($hex)); } /** * Expand an IP address. IPv4 addresses are returned as-is. * * Example: * 2001::1 expands to 2001:0000:0000:0000:0000:0000:0000:0001 * ::127.0.0.1 expands to 0000:0000:0000:0000:0000:0000:7f00:0001 * 127.0.0.1 expands to 127.0.0.1 */ public static function inet_expand($ip) { // strip possible cidr notation off if (($pos = strpos($ip, '/')) !== false) { $ip = substr($ip, 0, $pos); } $bytes = unpack('n*', inet_pton($ip)); if (count($bytes) > 2) { return implode(':', array_map(function ($b) { return sprintf("%04x", $b); }, $bytes)); } return $ip; } /** * Convert an IPv4 address into an IPv6 address. * * One use-case for this is IP 6to4 tunnels used in networking. * * @example * to_ipv4("10.10.10.10") == a0a:a0a * * @param string $ip IPv4 address. * @param boolean $mapped If true a Full IPv6 address is returned within the * official ipv4to6 mapped space "0:0:0:0:0:ffff:x:x" */ public static function to_ipv6($ip, $mapped = false) { if (!self::isIPv4($ip)) { throw new \InvalidArgumentException("Invalid IPv4 address \"$ip\""); } $num = IP::inet_ptod($ip); $o1 = dechex($num >> 16); $o2 = dechex($num & 0x0000FFFF); return $mapped ? "0:0:0:0:0:ffff:$o1:$o2" : "$o1:$o2"; } /** * Returns true if the IP address is a valid IPv4 address */ public static function isIPv4($ip) { return $ip === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); } /** * Returns true if the IP address is a valid IPv6 address */ public static function isIPv6($ip) { return $ip === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); } /** * Compare two IP's (v4 or v6) and return -1, 0, 1 if the first is < = > * the second. * * @param string $ip1 IP address * @param string $ip2 IP address to compare against * @return integer Return -1,0,1 depending if $ip1 is <=> $ip2 */ public static function cmp($ip1, $ip2) { return bccomp(self::inet_ptod($ip1), self::inet_ptod($ip2), 0); } }