Skip to content

Commit f080cb2

Browse files
committed
fix IP range to CIDR parser
1 parent 122d1b5 commit f080cb2

File tree

2 files changed

+74
-8
lines changed

2 files changed

+74
-8
lines changed

src/global.php

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
use ProxyNova\RangeOptimizer\CIDR;
44

5+
if (!function_exists('cidr_mask_length')) {
6+
7+
// Formula: # of IP addresses = 2^(32 - CIDR suffix)
8+
function cidr_mask_length(int $start, int $end): int
9+
{
10+
$prefix = 32 - log($end - $start + 1, 2);
11+
$prefix = (int)ceil($prefix);
12+
13+
return min(32, $prefix);
14+
}
15+
}
16+
517
/**
618
* can return multiple ranges
719
*
@@ -12,22 +24,26 @@
1224
function range_to_cidr(int $start, int $end): array
1325
{
1426
$ranges = [];
15-
$maxSize = 32;
1627

1728
// until no ranges left
1829
while ($start <= $end) {
1930

20-
$prefix = long2ip($start);
21-
$diff = $end - $start;
31+
$address = long2ip($start);
32+
33+
// from: https://github.com/allaboutjst/airbnb/blob/master/src/main/java/ip_range_to_cidr/IPRangetoCIDR.java#L41
34+
// Find the location of the first 1 bit
35+
$locOfFirstOne = $start & (-$start);
36+
37+
// Calculate the corresponding mask
38+
$curMask = 32 - (int)(log($locOfFirstOne) / log(2));
2239

23-
$bits = 32 - log($diff + 1, 2);
40+
$maxLen = cidr_mask_length($start, $end);
41+
$prefixLength = max($curMask, $maxLen);
2442

25-
// larger length => smaller range
26-
$bits = min($maxSize, ceil($bits));
27-
$ranges[] = new CIDR($prefix . '/' . $bits);
43+
$ranges[] = new CIDR($address . '/' . $prefixLength);
2844

2945
// how many IPs was that?
30-
$ipCount = pow(2, 32 - $bits);
46+
$ipCount = pow(2, 32 - $prefixLength);
3147

3248
$start += $ipCount;
3349
}

tests/CIDRTest.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace ProxyNova\RangeOptimizer\Tests;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use ProxyNova\RangeOptimizer\CIDR;
7+
8+
class CIDRTest extends TestCase
9+
{
10+
public function test_parse()
11+
{
12+
// CIDR => [expected network, expected broadcast]
13+
$data = [
14+
"172.233.173.111/8" => ["172.0.0.0", "172.255.255.255"],
15+
"74.207.237.201/14" => ["74.204.0.0", "74.207.255.255"],
16+
"45.33.60.45/7" => ["44.0.0.0", "45.255.255.255"]
17+
];
18+
19+
foreach ($data as $input => $outputs) {
20+
$temp = new CIDR($input);
21+
22+
$this->assertEquals($outputs[0], $temp->getFirstAddress());
23+
$this->assertEquals($outputs[1], $temp->getLastAddress());
24+
}
25+
}
26+
27+
public function test_range_to_cidr()
28+
{
29+
// start IP, end IP, CIDRs
30+
$ranges = [
31+
["13.49.126.128", "13.49.126.191", ["13.49.126.128/26"]],
32+
["18.139.204.176", "18.139.204.223", ["18.139.204.176/28", "18.139.204.192/27"]],
33+
["3.25.37.128", "3.25.40.255", ["3.25.37.128/25", "3.25.38.0/23", "3.25.40.0/24"]],
34+
["35.80.36.208", "35.80.36.239", ["35.80.36.208/28", "35.80.36.224/28"]]
35+
];
36+
37+
foreach ($ranges as $range) {
38+
39+
$cidrArray = range_to_cidr(ip2long($range[0]), ip2long($range[1]));
40+
$expected = $range[2];
41+
42+
$this->assertEquals(count($expected), count($cidrArray));
43+
44+
foreach ($expected as $index => $value) {
45+
$str = strval($cidrArray[$index]);
46+
$this->assertEquals($value, $str);
47+
}
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)