* @license BSD */ namespace Domframework; /** Manage the IP addresses conversions */ class Ipaddresses { /** Return true if the provided IP address is valid (IPv4 or IPv6) * @param string $ip The IP Address to validate */ public function validIPAddress($ip) { if (!is_string($ip) || $ip === "") { throw new \Exception( dgettext("domframework", "Invalid IP address"), 500 ); } $rc = $this->validIPv4Address($ip); if ($rc === true) { return true; } $rc = $this->validIPv6Address($ip); return $rc; } /** Return true if the provided IP address is valid and is IPv4 * @param string $ip The IP Address to validate */ public function validIPv4Address($ip) { if (!is_string($ip) || $ip === "") { throw new \Exception( dgettext("domframework", "Invalid IPv4 address"), 500 ); } $rc = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); if ($rc === false) { return false; } return true; } /** Return true if the provided IP address is valid and is IPv6 * @param string $ip The IP Address to validate */ public function validIPv6Address($ip) { if (!is_string($ip) || $ip === "") { throw new \Exception( dgettext("domframework", "Invalid IPv6 address"), 500 ); } $rc = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); if ($rc === false) { return false; } return true; } /** Return true if the provided IP address is valid (IPv4 or IPv6) and the * provided CIDR is valid too * @param string $ip The IP Address to validate with a CIDR */ public function validIPAddressWithCIDR($ip) { if (!is_string($ip) || $ip === "") { throw new \Exception( dgettext("domframework", "Invalid IP address"), 500 ); } $rc = $this->validIPv4AddressWithCIDR($ip); if ($rc === true) { return true; } $rc = $this->validIPv6AddressWithCIDR($ip); return $rc; } /** Return true if the provided IP address is valid and is IPv4 and the * provided CIDR is valid too * @param string $ip The IP Address to validate with a CIDR */ public function validIPv4AddressWithCIDR($ip) { if (!is_string($ip) || $ip === "") { throw new \Exception( dgettext("domframework", "Invalid IPv4 address"), 500 ); } @list($ip, $cidr) = @explode("/", $ip); if ($cidr === null) { return false; } $rc = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); if ($rc === false) { return false; } if ($this->validIPv4CIDR($cidr) === false) { return false; } return true; } /** Return true if the provided IP address is valid and is IPv6 and the * provided CIDR is valid too * @param string $ip The IP Address to validate with a CIDR */ public function validIPv6AddressWithCIDR($ip) { if (!is_string($ip) || $ip === "") { throw new \Exception( dgettext("domframework", "Invalid IPv6 address"), 500 ); } @list($ip, $cidr) = @explode("/", $ip); if ($cidr === null) { return false; } $rc = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); if ($rc === false) { return false; } if ($this->validIPv6CIDR($cidr) === false) { return false; } return true; } /** Return true if the provided CIDR is valid. The CIDR can be valid in IPv4 * or IPv6 * @param integer $cidr The CIDR to test * @return boolean The CIDR is valid */ public function validCIDR($cidr) { if (! is_integer($cidr) && ! is_integer($cidr)) { throw new \Exception( dgettext("domframework", "Invalid CIDR provided"), 500 ); } if (strspn($cidr, "0123456879") !== strlen($cidr)) { return false; } if ($cidr < 0 || $cidr > 128) { return false; } return true; } /** Return true if the provided CIDR is valid. The CIDR can be valid in IPv4. * @param integer $cidr The CIDR to test * @return boolean The CIDR is valid */ public function validIPv4CIDR($cidr) { if (! is_integer($cidr) && ! is_string($cidr)) { throw new \Exception( dgettext("domframework", "Invalid CIDR provided"), 500 ); } if (strspn($cidr, "0123456879") !== strlen($cidr)) { return false; } if ($cidr < 0 || $cidr > 32) { return false; } return true; } /** Return true if the provided CIDR is valid. The CIDR can be valid in IPv6. * @param integer $cidr The CIDR to test * @return boolean The CIDR is valid */ public function validIPv6CIDR($cidr) { if (! is_integer($cidr) && ! is_string($cidr)) { throw new \Exception( dgettext("domframework", "Invalid CIDR provided"), 500 ); } if (strspn($cidr, "0123456879") !== strlen($cidr)) { return false; } if ($cidr < 0 || $cidr > 128) { return false; } return true; } /** Return the IPv6 to compressed (or compact) form. * Remove the 0 when they are placed on the begin of the nibble. * Remove all the blocks only zero to convert them to :: (but only one time) * Example: 2001:0660:530d:0201:0000:0000:0000:0124 => 2001:660:530d:201::124 * If an IPv4 is provided, return it without modification * @param string $ip The IP to compress */ public function compressIP($ip) { if (strpos($ip, ":") === false) { return $ip; } $ip = $this->uncompressIPv6($ip); $ipArr = explode(":", $ip); if (count($ipArr) !== 8) { return $ip; } // Remove the 0 if they are at the beginning of the nibble foreach ($ipArr as &$ip) { $ip = sprintf("%x", hexdec($ip)); } // Remove the 0 nibble if there is an other 0 nibble after $cleanLoop = false; $ipCompArr = array(); foreach ($ipArr as $key => $val) { // echo "VAL=".var_export ($val, true). // ", cleanLoop=".var_export ($cleanLoop, true); if ($val !== "0") { // echo " => In NOT zero\n"; if ($cleanLoop) { $cleanLoop = false; } } elseif ($cleanLoop) { // echo " => In CleanLoop\n"; unset($ipArr[$key]); } else { // echo " => In ZERO\n"; if (key_exists($key + 1, $ipArr) && $ipArr[$key + 1] === "0") { // echo " ===> NEXT ZERO : CleanLoop Start\n"; $cleanLoop = true; $ipArr[$key] = ""; } } } $ipArr = array_values($ipArr); if (end($ipArr) === "") { array_pop($ipArr); $ipArr[] = ":"; } if (reset($ipArr) === "") { $ipArr[0] = ":"; } if (count($ipArr) === 1) { $ipArr[] = ""; } $ipHex = implode(":", $ipArr); return $ipHex; } /** Return the IPv6 uncompressed (all the fields exists). They are not filled * by zeros * If the provided IP is IPv4, there is no change applied * Return False if the parameter is invalid * Based on http://www.weberdev.com/get_example.php3?ExampleID=3921 * @param string $ip The IP address to uncompress * Example : $ip="::" => return "0:0:0:0:0:0:0:0" * Example : $ip = "::ffff:127.0.0.1", * return "0:0:0:0:0:0:ffff:7f00:1" */ public function uncompressIPv6($ip) { if ( ! is_string($ip) || $ip === "" || $this->validIPAddress($ip) === false ) { throw new \Exception( dgettext("domframework", "Invalid IP address"), 500 ); } if (strstr($ip, "::")) { $e = explode(":", $ip); // Case where :: is in start if ($e[0] === "") { $e[0] = "0"; } // Case where :: is in end if ($e[count($e) - 1] === "") { $e[count($e) - 1] = "0"; } $s = 8 - count($e); foreach ($e as $key => $val) { if ($val === "") { for ($i = 0; $i <= $s; $i++) { $newipv6[] = 0; } } else { $newipv6[] = $val; } } $ip = implode(":", $newipv6); if (substr_count($ip, ":") !== 7) { throw new \Exception("uncompressIPv6: Invalid IP provided", 500); } } if (substr($ip, 0, 17) === "0:0:0:0:0:0:ffff:") { // Manage the IPv4 blocks in IPv6 : ::ffff:192.168.1.2 // If the IP is already in valid IPv6 (without dots), skip this part $ipv4Block = substr($ip, 17); if (strpos($ipv4Block, ".") !== false) { @list($ip1, $ip2, $ip3, $ip4) = @explode(".", $ipv4Block); // remove the first 0: as there is 2 nibbles for the IPv4 address $ip = substr($ip, 2, 15); $ip .= sprintf("%x:%x", $ip1 * 256 + $ip2, $ip3 * 256 + $ip4); } } return $ip; } /** Get an IPv6 address with the format * x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x * and return it with format * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx * Return false if the IP provided is not complete * @param string $ipv6 The IPv6 to group */ public function groupIPv6($ipv6) { if (! is_string($ipv6) || $ipv6 === "") { throw new \Exception( dgettext("domframework", "Invalid IPv6 address"), 500 ); } if (substr_count($ipv6, ".") !== 31) { throw new \Exception( dgettext("domframework", "Invalid IPv6 address"), 500 ); } $ipv6 = str_replace(".", "", $ipv6); $new = ""; for ($i = 0; $i < 32; $i++) { if ($i % 4 === 0 && $i !== 0) { $new .= ":"; } $new .= $ipv6[$i]; } return $new; } /** Return the IP adddress with filling the fields with the missing zeros. * Valid only on IPv6 (but don't change anything if the provided address is * IPv4) * @param string $ip The IP to complete * @return string The address in nibbles * Example : $ip = "::", return "0000:0000:0000:0000:0000:0000:0000:0000" * Example : $ip = "::ffff:127.0.0.1", * return "0000:0000:0000:0000:0000:0000:ffff:7f00:0001" */ public function completeAddressWithZero($ip) { if (! is_string($ip) || $ip === "") { throw new \Exception( dgettext("domframework", "Invalid IP address"), 500 ); } $ip = $this->uncompressIPv6($ip); if (substr_count($ip, ":") === 7) { // IPv6 $ips = explode(":", $ip); $ipnew = array(); foreach ($ips as $iptmp) { $ipnew[] = sprintf("%04x", hexdec($iptmp)); } return implode(":", $ipnew); } elseif (substr_count($ip, ".") === 31) { // Full IPv6 with dots return $ip; } elseif (substr_count($ip, ".") === 3) { // IPv4 return $ip; } throw new \Exception(dgettext("domframework", "Invalid IP address"), 500); } /** Return the provided CIDR in binary. Length must be in bytes. * Return FALSE if the parameters are invalid * @param integer $cidr The CIDR to convert * @param integer $length The length to use */ public function cidrToBin($cidr, $length) { if (! is_numeric($cidr) || $cidr < 0 || $cidr > 128) { throw new \Exception(dgettext("domframework", "Invalid CIDR"), 500); } if (! is_numeric($length) || $length < 1 || $length > 16) { throw new \Exception(dgettext("domframework", "Invalid length"), 500); } $val = ""; for ($i = 0; $i < $length * 8; $i++) { if ($i < $cidr) { $val .= "1"; } else { $val .= "0"; } } return pack('H*', $this->str_base_convert($val, 2, 16)); } /** Base conversion with 128 bits support for IPv6 * Based on http://fr2.php.net/manual/en/function.base-convert.php#109660 * @param string $str The string to convert * @param integer|null $frombase The base of the provided string (10 by * default) * @param integer|null $tobase The base of the returned string (36 by * default) */ public function str_base_convert($str, $frombase = 10, $tobase = 36) { $str = trim($str); if (intval($frombase) != 10) { $len = strlen($str); $q = 0; for ($i = 0; $i < $len; $i++) { $r = base_convert($str[$i], $frombase, 10); $q = bcadd(bcmul($q, $frombase), $r); } } else { $q = $str; } if (intval($tobase) != 10) { $s = ''; while (bccomp($q, '0', 0) > 0) { $r = intval(bcmod($q, $tobase)); $s = base_convert($r, 10, $tobase) . $s; $q = bcdiv($q, $tobase, 0); } } else { $s = $q; } return $s; } /** Reverse the provided IP address * The IPv6 are returned in format : * x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x * @param string $ipReverse The IPv6 to reverse */ public function reverseIPAddress($ipReverse) { if (!is_string($ipReverse) || $ipReverse === "") { throw new \Exception( dgettext("domframework", "Invalid IP address"), 500 ); } $ipReverse = $this->completeAddressWithZero($ipReverse); if (substr_count($ipReverse, ":") === 7 && strlen($ipReverse) == 39) { // Complete IPv6 with quadruplets and colon $ip = str_replace(":", "", $ipReverse); $tmp2 = array_reverse(str_split($ip)); return implode(".", $tmp2); } elseif (substr_count($ipReverse, ".") === 31) { // IPv6 with dots $tmp2 = explode(".", $ipReverse); $tmp2 = array_reverse($tmp2); $ipnew = implode(".", $tmp2); return $ipnew; } elseif (substr_count($ipReverse, ".") === 3) { // IPv4 $tmp2 = explode(".", $ipReverse); $tmp2 = array_reverse($tmp2); $ipnew = implode(".", $tmp2); return $ipnew; } throw new \Exception(dgettext("domframework", "Invalid IP address"), 500); } /** This function return the CIDR associated to the provided netmask * Ex. Return 24 for a mask 255.255.255.0 in direct * Ex. Return 24 for a mask 0.0.0.255 in wildcard * Work only in IPv4 * Return FALSE if the provided mask is invalid (155.0.0.0 by example) * Throw an exception if the provided IP is invalid * @param string $netmask The mask to convert in CIDR * @param boolean|null $maskdirect If true check a direct mask, if false * check a wildcard mask */ public function netmask2cidr($netmask, $maskdirect = true) { $maskdirect = "" . ($maskdirect + 0); $maskrevert = ($maskdirect === "0") ? "1" : "0"; $netmask = ip2long($netmask); if ($netmask === false) { throw new \Exception(dgettext("domframework", "Invalid netmask"), 500); } $netmask = decbin($netmask); $netmask = sprintf("%032s", $netmask); $res = -1; for ($i = 0; $i < 32; $i++) { if ($res === -1 && $netmask[$i] === $maskdirect) { } elseif ($res === -1 && $netmask[$i] === $maskrevert) { $res = $i; } elseif ($res !== -1 && $netmask[$i] === $maskdirect) { return false; } else { } } if ($res === -1 && $i === 32) { return 32; } if ($res === -1 && $i === 1) { return 32; } return $res; } /** This function return the netmask associated to a CIDR. * Work only on IPv4 addresses (CIDR between 0 and 32) * @param string $cidr The CIDR to convert in netmask * @param boolean|null $maskdirect If true return a direct mask, if false * return a wildcard mask * @return the mask * @return false if the CIDR is not between 0 and 32 */ public function cidr2netmask($cidr, $maskdirect = true) { if ($cidr < 0 || $cidr > 32) { return false; } $maskdirect = "" . ($maskdirect + 0); $maskrevert = ($maskdirect === "0") ? "1" : "0"; $bin = ""; for ($i = 0; $i < 32; $i++) { if ($i < $cidr) { $bin .= $maskdirect; } else { $bin .= $maskrevert; } } $res = ""; for ($i = 0; $i < 32; $i = $i + 8) { $block = substr($bin, $i, 8); if ($i > 0) { $res .= "."; } $res .= bindec($block); } return $res; } /** This function return true if the provided address is in the provided * network with the associated cidr * @param string $ip The IPv4 or IPv6 to test * @param string $network The IPv4 or IPv6 network base * @param string $cidr The CIDR to apply * @return boolean True if $ip is in $network/$cidr */ public function ipInNetwork($ip, $network, $cidr) { if ($this->validIPAddress($ip) === false) { throw new \Exception( dgettext("domframework", "Invalid IP address"), 500 ); } if ($this->validIPAddress($network) === false) { throw new \Exception(dgettext( "domframework", "Invalid Network address" ), 500); } $ipv4 = $this->validIPv4Address($ip); $networkv4 = $this->validIPv4Address($network); if ($ipv4 !== $networkv4) { throw new \Exception(dgettext( "domframework", "Network and IP address are not compatible" ), 500); } if ($ipv4 === true) { if ($this->validIPv4CIDR($cidr) === false) { throw new \Exception(dgettext( "domframework", "CIDR is not IPv4 compatible" ), 500); } } else { if ($this->validIPv6CIDR($cidr) === false) { throw new \Exception(dgettext( "domframework", "CIDR is not IPv6 compatible" ), 500); } } return ($this->networkFirstIP($ip, $cidr) === $this->networkFirstIP($network, $cidr)); } /** Get the first IP of a network. * IPv4 and IPv6 compatible * @param string $ip The IPv4 or IPv6 in the network * @param string $cidr The CIDR to apply * @return string the network base * Example : $ip="192.168.1.2", $cidr=24 => return "192.168.1.0" */ public function networkFirstIP($ip, $cidr) { return $this->networkFirstLastIP($ip, $cidr, "0"); } /** Get the last IP of a network. * IPv4 and IPv6 compatible * @param string $ip The IPv4 or IPv6 in the network * @param string $cidr The CIDR to apply * @return string the network base * Example : $ip="192.168.1.2", $cidr=24 => return "192.168.1.255" */ public function networkLastIP($ip, $cidr) { return $this->networkFirstLastIP($ip, $cidr, "1"); } /** Get the network first IP. * IPv4 and IPv6 compatible * @param string $ip The IPv4 or IPv6 in the network * @param string $cidr The CIDR to apply * @param string $map if "0", get the first address of the network, * if "1" get the last address of the network * @return string the network base * Example : $ip="192.168.1.2", $cidr=24 => return "192.168.1.0" */ private function networkFirstLastIP($ip, $cidr, $map) { if ($this->validIPAddress($ip) === false) { throw new \Exception( dgettext("domframework", "Invalid IP address"), 500 ); } $ipv4 = $this->validIPv4Address($ip); if ($ipv4 === true) { if ($this->validIPv4CIDR($cidr) === false) { throw new \Exception(dgettext( "domframework", "CIDR is not IPv4 compatible" ), 500); } } else { if ($this->validIPv6CIDR($cidr) === false) { throw new \Exception(dgettext( "domframework", "CIDR is not IPv6 compatible" ), 500); } } // Convert the IP and CIDR to binary string if ($ipv4) { list($ip1, $ip2, $ip3, $ip4) = explode(".", $ip); $ipBin = sprintf("%08b%08b%08b%08b", $ip1, $ip2, $ip3, $ip4); $cidrBin = ""; for ($i = 0; $i < $cidr; $i++) { $cidrBin .= "1"; } for ($i = $cidr; $i < 32; $i++) { $cidrBin .= "0"; } } else { $ip = $this->completeAddressWithZero($ip); $ip = str_replace(":", "", $ip); $ipArr = str_split($ip, 2); $ipBin = ""; foreach ($ipArr as $ip) { $ipBin .= sprintf("%08b", hexdec($ip)); } $cidrBin = ""; for ($i = 0; $i < $cidr; $i++) { $cidrBin .= "1"; } for ($i = $cidr; $i < 128; $i++) { $cidrBin .= "0"; } } // Get the binary value of IP if the mask is 1 or put $map if the mask is 0 $ipBaseBin = ""; for ($i = 0; $i < strlen($ipBin); $i++) { if ($cidrBin[$i] === "1") { $ipBaseBin .= $ipBin[$i]; } else { $ipBaseBin .= "$map"; } } // Convert ipBaseBin from binary to decimal if IPv4 and to hexa for IPv6 if ($ipv4) { $ipBase = ""; for ($i = 0; $i < 32; $i = $i + 8) { $block = substr($ipBaseBin, $i, 8); if ($i > 0) { $ipBase .= "."; } $ipBase .= bindec($block); } } else { $ipBase = ""; for ($i = 0; $i < 128; $i = $i + 8) { $block = substr($ipBaseBin, $i, 8); if ($i > 0 && $i % 16 == 0) { $ipBase .= ":"; } $ipBase .= sprintf("%02x", bindec($block)); } $ipBase = $this->compressIP($ipBase); } return $ipBase; } }