Files
DomFramework/ipaddresses.php
Dominique Fournier 068b1e3ec3 ipaddresses::netmask2cidr : manage the calculation of the wildcard IP
ipaddresses::netmask2cidr : catch invalid IP mask and return false


git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@5987 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
2020-05-11 19:39:20 +00:00

677 lines
20 KiB
PHP

<?php
/** DomFramework
@package domframework
@author Dominique Fournier <dominique@fournier38.fr> */
/** 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 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);
$res = -1;
for ($i = 0 ; $i < 32 ; $i++)
{
if (! isset ($netmask[$i]))
{
break;
}
elseif ($res == -1 && $netmask[$i] === $maskrevert)
{
$res = $i;
}
elseif ($res !== -1 && $netmask[$i] === $maskdirect)
{
return false;
}
}
if ($res === -1 && $i === 32)
return 32;
if ($res === -1 && $i === 1)
return 32;
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;
}
// }}}
}