*/ /** 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 */ 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 * Work only in IPv4 * Return FALSE if the provided IP is invalid * @param string $netmask The mask to convert in CIDR */ public function netmask2cidr ($netmask) { $netmask = ip2long ($netmask); if ($netmask === FALSE) throw new \Exception (dgettext ("domframework", "Invalid netmask"), 500); $netmask = decbin ($netmask); for ($i=0 ; $i<32 ; $i++) { if ($netmask{$i} == 0) { break; } } return $i; } /** 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; } }