Skip to content

Commit

Permalink
Geeklog\IP::matchCIDR() and Geeklog\IP::matchRange() now supports IPv…
Browse files Browse the repository at this point in the history
…6 addresses
  • Loading branch information
mystralkk committed Aug 27, 2019
1 parent 9d89945 commit 1b8273f
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 67 deletions.
131 changes: 66 additions & 65 deletions system/classes/IP.php
Expand Up @@ -2,105 +2,106 @@

namespace Geeklog;

use InvalidArgumentException;
use IPLib\Address\AddressInterface;
use IPLib\Address\IPv4;
use IPLib\Factory;
use IPLib\Range\RangeInterface;
use IPLib\Range\Subnet;

/**
* Class IP
*
* @package Geeklog
* @package Geeklog
* @copyright (C) 2004-2017 Tom Willett - tomw AT pigstye DOT net
* @copyright (C) 2017-2017 Kenji ITO - mystralkk AT gmail DOT com
* @license GPL
* @note most of the code below were taken from IP.Examine.class.php created by Tom Willett.
* @copyright (C) 2017-2019 Kenji ITO - mystralkk AT gmail DOT com
* @license GPL
* @note most of the code below were taken from IP.Examine.class.php created by Tom Willett.
*/
class IP
abstract class IP
{
/**
* Private internal method to match an IP address against a CIDR
* Return if an IP address matches a CIDR
*
* @param string $ipToCheck IP address to check
* @param string $ipInCIDR IP address range to check against
* @return bool true if IP falls into the CIDR, else false
* @todo CIDR support for IPv6 addresses
* @link http://www.php.net/manual/en/function.ip2long.php#71939
* @param string $ipToCheck IP address (IPv4 and IPv6) to check
* @param string $CIDR CIDR
* @return bool true if IP falls into the CIDR, else false
*/
public static function matchCIDR($ipToCheck, $ipInCIDR)
public static function matchCIDR($ipToCheck, $CIDR)
{
// not for IPv6 addresses
if (strpos($ipToCheck, ':') !== false) {
return false;
}

list ($base, $bits) = explode('/', $ipInCIDR, 2);

// now split it up into its classes
$classes = explode('.', $base);
$elements = count($classes);
if ($elements < 4) {
for ($i = $elements; $i < 4; $i++) {
$classes[$i] = 0;
}
}
list ($a, $b, $c, $d) = $classes;
$address = Factory::addressFromString($ipToCheck);
$range = Factory::rangeFromString($CIDR);

// now do some bit shifting/switching to convert to ints
$i = ($a << 24) + ($b << 16) + ($c << 8) + $d;
$mask = $bits == 0 ? 0 : (~0 << (32 - $bits));

// here's our lowest int
$low = $i & $mask;

// here's our highest int
$high = $i | (~$mask & 0xFFFFFFFF);

// now split the ip we're checking against up into classes
$ex = explode('.', $ipToCheck);

if (count($ex) === 4) {
// now convert the ip we're checking against to an int
$check = ($ex[0] << 24) + ($ex[1] << 16) + ($ex[2] << 8) + $ex[3];

// if the ip is within the range, including
// highest/lowest values, then it's within the CIDR range
if (($check >= $low) && ($check <= $high)) {
return true;
}
if ((!$address instanceof AddressInterface) || (!$range instanceof RangeInterface)) {
return false;
} else {
return $range->contains($address);
}

return false;
}

/**
* Private internal method to match an IP address against an address range
* Return if an IP address is in the range given
*
* Original authors: dh06 and Stephane, taken from
*
* @link http://www.php.net/manual/en/function.ip2long.php#70707
* @param string $ip IP address to check
* @param string $range IP address range to check against
* @param string $ip IP address (IPv4 or IPv6) to check
* @param string $range IP address range to check against
* @return bool true if IP falls into the IP range, else false
* @throws InvalidArgumentException
*/
public static function matchRange($ip, $range)
{
if (strpos($range, '-') === false) {
throw new InvalidArgumentException(__METHOD__ . ': range must be in "IP1-IP2" format');
}

// not for IPv6 addresses
if (strpos($ip, ':') !== false) {
return false;
return self::matchRangeIPv6($ip, $range);
}

$d = strpos($range, '-');
if ($d !== false) {
$from = ip2long(trim(substr($range, 0, $d)));
$to = ip2long(trim(substr($range, $d + 1)));
$from = ip2long(trim(substr($range, 0, $d)));
$to = ip2long(trim(substr($range, $d + 1)));
$ip = ip2long($ip);

$ip = ip2long($ip);
return (($ip >= $from) && ($ip <= $to));
}

return (($ip >= $from) && ($ip <= $to));
/**
* Return if an IP address is in the range given
*
* @param string $ip IP address to check
* @param string $range IP address range to check against
* @return bool true if IP falls into the IP range, else false
* @throws InvalidArgumentException
*/
private static function matchRangeIPv6($ip, $range)
{
$ips = explode('-', $range, 2);
$from = Factory::addressFromString($ips[0]);
$to = Factory::addressFromString($ips[1]);
if ((!$from instanceof AddressInterface) || (!$to instanceof AddressInterface)) {
throw new InvalidArgumentException(__METHOD__ . ': an IP in the range was invalid as IPv6 address');
}

return false;
$address = Factory::addressFromString($ip);
if (!$address instanceof AddressInterface) {
throw new InvalidArgumentException(__METHOD__ . ': IP was invalid as IPv6 address');
}

$from = $from->toString(true);
$to = $to->toString(true);
$address = $address->toString(true);

return (strcmp($from, $address) <= 0) && (strcmp($address, $to) <= 0);
}

/**
* Return if the IP address given is valid
*
* @param string $ip
* @param string $ip
* @return bool
*/
public static function isValidIP($ip)
Expand All @@ -111,7 +112,7 @@ public static function isValidIP($ip)
/**
* Return if the IP address given is valid as an IPv4 address
*
* @param string $ip
* @param string $ip
* @return bool
*/
public static function isValidIPv4($ip)
Expand All @@ -122,7 +123,7 @@ public static function isValidIPv4($ip)
/**
* Return if the IP address given is valid as an IPv6 address
*
* @param string $ip
* @param string $ip
* @return bool
*/
public static function isValidIPv6($ip)
Expand Down
43 changes: 41 additions & 2 deletions tests/system/classes/IpClassTest.php
Expand Up @@ -17,10 +17,33 @@ public function testMatchCIDR()
'204.0.127.255' => true,
'204.0.128.0.0' => false,
];
$ipInCIDR = '204.0.113.1/18';
$CIDR = '204.0.113.1/18';

foreach ($data as $ip => $expected) {
$got = \Geeklog\IP::matchCIDR($ip, $ipInCIDR);
$got = \Geeklog\IP::matchCIDR($ip, $CIDR);
$this->assertEquals(
$expected,
$got,
'Expected: ' . self::$boolString[$expected] . ', Got: ' . self::$boolString[$got]
);
}
}

/**
* Test for IPv6 CIDR matching
*/
public function testMatchCIDRIPv6()
{
$data = [
'2001:4760:4800:0000:0000:0000:0000:0000' => false,
'2001:4860:4860:0000:0000:0000:0000:8888' => true,
'2001:4860:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF' => true,
'2001:4900:0000:0000:0000:0000:0000:0000' => false,
];
$CIDR = '2001:4860:4860::8888/32';

foreach ($data as $ip => $expected) {
$got = \Geeklog\IP::matchCIDR($ip, $CIDR);
$this->assertEquals(
$expected,
$got,
Expand All @@ -34,6 +57,7 @@ public function testMatchCIDR()
*/
public function testMatchRange()
{
// IPv4
$data = [
'::1-::2' => false,
'100.0.112.0-100.0.127.255' => true,
Expand All @@ -50,6 +74,21 @@ public function testMatchRange()
'Expected: ' . self::$boolString[$expected] . ', Got: ' . self::$boolString[$got]
);
}

// IPv6
$data = [
'2001:4860:4860:0000:0000:0000:0000:8888-2001:4860:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF' => true,
];
$ipToCheck = '2001:4860:4860:0000:0000:0000:1000:8888';

foreach ($data as $range => $expected) {
$got = \Geeklog\IP::matchRange($ipToCheck, $range);
$this->assertEquals(
$expected,
$got,
'Expected: ' . self::$boolString[$expected] . ', Got: ' . self::$boolString[$got]
);
}
}

/**
Expand Down

0 comments on commit 1b8273f

Please sign in to comment.