chg: [internal] Optimise CidrTool

pull/6926/head
Jakub Onderka 2021-01-29 14:31:13 +01:00
parent 73a8a973c7
commit 6bd3bdfc3e
2 changed files with 31 additions and 26 deletions

View File

@ -4,6 +4,12 @@ class CidrTool
/** @var array */
private $ipv4 = [];
/**
* Minimum netmask for IPv4 in list. 33 because maximum netmask is 32..
* @var int
*/
private $minimumIpv4Mask = 33;
/** @var array */
private $ipv6 = [];
@ -29,7 +35,7 @@ class CidrTool
// and then check if given hash table contains that CIDR.
$ip = ip2long($value);
// Start from 1, because doesn't make sense to check 0.0.0.0/0 match
for ($bits = 1; $bits <= 32; $bits++) {
for ($bits = $this->minimumIpv4Mask; $bits <= 32; $bits++) {
$mask = -1 << (32 - $bits);
$needle = long2ip($ip & $mask) . "/$bits";
if (isset($this->ipv4[$needle])) {
@ -40,10 +46,12 @@ class CidrTool
} elseif (filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$value = unpack('n*', inet_pton($value));
foreach ($this->ipv6 as $lv) {
if ($this->ipv6InCidr($value, $lv)) {
$match = $lv;
break;
foreach ($this->ipv6 as $netmask => $lv) {
foreach ($lv as $l) {
if ($this->ipv6InCidr($value, $l, $netmask)) {
$match = inet_ntop($l) . "/$netmask";
break;
}
}
}
}
@ -63,13 +71,12 @@ class CidrTool
*
* @param array $ip
* @param string $cidr
* @param int $netmask
* @return bool
*/
private function ipv6InCidr($ip, $cidr)
private function ipv6InCidr($ip, $cidr, $netmask)
{
list($address, $netmask) = explode('/', $cidr);
$bytesAddr = unpack('n*', inet_pton($address));
$bytesAddr = unpack('n*', $cidr);
for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) {
$left = $netmask - 16 * ($i - 1);
$left = ($left <= 16) ? $left : 16;
@ -90,31 +97,29 @@ class CidrTool
{
foreach ($list as $v) {
$parts = explode('/', $v, 2);
if (filter_var($parts[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$maximumNetmask = 32;
} else if (filter_var($parts[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$parts[0] = strtolower($parts[0]);
$maximumNetmask = 128;
} else {
// IP address part of CIDR is invalid
continue;
$ipBytes = inet_pton($parts[0]);
if ($ipBytes === false) {
continue; // IP address part of CIDR is invalid
}
$maximumNetmask = strlen($ipBytes) === 4 ? 32 : 128;
if (!isset($parts[1])) {
// If CIDR doesnt contains '/', we will consider CIDR as /32 for IPv4 or /128 for IPv6
$v = "$v/$maximumNetmask";
} else if ($parts[1] > $maximumNetmask || $parts[1] < 0) {
if (isset($parts[1]) && ($parts[1] > $maximumNetmask || $parts[1] < 0)) {
// Netmask part of CIDR is invalid
continue;
}
$mask = isset($parts[1]) ? $parts[1] : $maximumNetmask;
if ($maximumNetmask === 32) {
if ($mask < $this->minimumIpv4Mask) {
$this->minimumIpv4Mask = (int)$mask;
}
if (!isset($parts[1])) {
$v = "$v/$maximumNetmask"; // If CIDR doesnt contains '/', we will consider CIDR as /32
}
$this->ipv4[$v] = true;
} else {
$this->ipv6[] = $v;
$this->ipv6[$mask][] = $ipBytes;
}
}
}
}

View File

@ -34,8 +34,8 @@ class CidrToolTest extends TestCase
public function testIpv6(): void
{
$cidrTool = new CidrTool(['2001:0db8:1234::/48']);
$this->assertEquals('2001:0db8:1234::/48', $cidrTool->contains('2001:0db8:1234:0000:0000:0000:0000:0000'));
$this->assertEquals('2001:0db8:1234::/48', $cidrTool->contains('2001:0db8:1234:ffff:ffff:ffff:ffff:ffff'));
$this->assertEquals('2001:db8:1234::/48', $cidrTool->contains('2001:0db8:1234:0000:0000:0000:0000:0000'));
$this->assertEquals('2001:db8:1234::/48', $cidrTool->contains('2001:0db8:1234:ffff:ffff:ffff:ffff:ffff'));
$this->assertFalse($cidrTool->contains('2002:0db8:1234:ffff:ffff:ffff:ffff:ffff'));
}
}