Skip to content

Commit 86ec0cf

Browse files
alissnmlocati
andauthored
Add split and getNetworkPrefix to IP ranges (#93)
* [feat] add method split to range ips, to convert range smaller range * [refactor] change split method to array * [style] * [refactor] delete interface split * [test] add test * [style] * [test] php 5.3, 5.4, 5.5 compatibility * Describe the type of the result of the split method * Be sure that we receive an int as the argument of the split method * Always run all tests * Fix and simplify tests * Use Coveralls only for the mlocati's repo * Simplify "if" statements * Document that split() can throw a \RuntimeException * Optimization + accept a network prefix that's the same as the range's one * Use OutOfBoundsException if $networkPrefix is invalid * Check maximum range sizes for 32-bit systems * Fix running Coveralls * [feat] handle pattern range and update DocBlock * [fix] move split method to single class * [fix] fix class pattern * [chore] update return type * [test] add single test and pattern test * [style] add readme * [style] * Add getNetworkPrefix() to RangeInterface This implies checking $networkPrefix for Single::split() too, without efforts * Add $forceSubnet parameter, test overflow for 64-bit systems too * Fix coding style * Improve documentation about the split() method * Add @SInCE to split() --------- Co-authored-by: Michele Locati <[email protected]>
1 parent 67ce14c commit 86ec0cf

8 files changed

Lines changed: 790 additions & 18 deletions

File tree

.github/workflows/tests.yml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ jobs:
6666
env:
6767
CODE_COVERAGE_TOOL: none
6868
strategy:
69+
fail-fast: false
6970
matrix:
7071
os:
7172
- ubuntu-latest
@@ -95,7 +96,7 @@ jobs:
9596
steps:
9697
-
9798
name: Set code coverage
98-
if: startsWith(matrix.os, 'ubuntu') && matrix.php-version == '7.2'
99+
if: startsWith(matrix.os, 'ubuntu') && matrix.php-version == '7.2' && github.repository_owner == 'mlocati'
99100
run: |
100101
echo "CODE_COVERAGE_TOOL=xdebug" >> $GITHUB_ENV
101102
-
@@ -119,31 +120,31 @@ jobs:
119120
run: composer --ansi --no-interaction --no-progress --optimize-autoloader --ignore-platform-reqs update
120121
-
121122
name: Run PHPUnit (without code coverage)
122-
if: ${{ env.CODE_COVERAGE_TOOL == 'none' }}
123+
if: env.CODE_COVERAGE_TOOL == 'none'
123124
run: composer --ansi --no-interaction run-script test
124125
-
125126
name: Run PHPUnit (with code coverage)
126-
if: ${{ env.CODE_COVERAGE_TOOL != 'none' }}
127+
if: env.CODE_COVERAGE_TOOL != 'none'
127128
run: composer --ansi --no-interaction run-script test -- --coverage-clover coverage-clover.xml
128129
-
129130
name: Download Coveralls
130-
if: ${{ env.CODE_COVERAGE_TOOL != 'none' }}
131+
if: env.CODE_COVERAGE_TOOL != 'none'
131132
run: curl -sSLf -o php-coveralls.phar https://github.com/php-coveralls/php-coveralls/releases/download/v2.5.3/php-coveralls.phar
132133
-
133134
name: Upload Coveralls data
134-
if: ${{ env.CODE_COVERAGE_TOOL != 'none' }}
135+
if: env.CODE_COVERAGE_TOOL != 'none'
135136
env:
136137
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
137138
run: >
138139
php php-coveralls.phar
139140
--coverage_clover=coverage-clover.xml --json_path=coveralls-upload.json --ansi --no-interaction -vvv
140141
-
141142
name: Download Scrutinizer
142-
if: ${{ env.CODE_COVERAGE_TOOL != 'none' }}
143+
if: env.CODE_COVERAGE_TOOL != 'none'
143144
run: curl -sSLf -o ocular.phar https://scrutinizer-ci.com/ocular.phar
144145
-
145146
name: Upload Scrutinizer data
146-
if: ${{ env.CODE_COVERAGE_TOOL != 'none' }}
147+
if: env.CODE_COVERAGE_TOOL != 'none'
147148
run: >
148149
php ocular.phar code-coverage:upload
149150
--format=php-clover --ansi --no-interaction

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,57 @@ echo \IPLib\Factory::parseRangeString('10.0.0.0/8')->asPattern()->toString();
370370

371371
Please remark that all the range types implement the `asPattern()` and `asSubnet()` methods.
372372

373+
### Splitting IP ranges
374+
375+
If you need to divide an IP address range into smaller ranges, you can use the `split` method.
376+
You can specify the length of the network prefix, as well as indicate whether you want to force the Subnet notation (by default, it is not).
377+
378+
For example:
379+
380+
```php
381+
$subnet = \IPLib\Factory::parseRangeString('192.168.112.203/24');
382+
$smallerSubnets = $subnet->split(25);
383+
print_r(array_map('strval', $smallerSubnets));
384+
/*
385+
* You'll have:
386+
* Array
387+
* (
388+
* [0] => 192.168.112.0/25
389+
* [1] => 192.168.112.128/25
390+
* )
391+
*/
392+
393+
$subnet = \IPLib\Factory::parseRangeString('192.168.*.*');
394+
$smallerSubnets = $subnet->split(24);
395+
print_r(array_map('strval', $smallerSubnets));
396+
/*
397+
* You'll have:
398+
* Array
399+
* (
400+
* [0] => 192.168.0.*
401+
* [1] => 192.168.1.*
402+
* [...]
403+
* [254] => 192.168.254.*
404+
* [255] => 192.168.255.*
405+
* )
406+
*/
407+
408+
$subnet = \IPLib\Factory::parseRangeString('192.168.*.*');
409+
$smallerSubnets = $subnet->split(24, true);
410+
print_r(array_map('strval', $smallerSubnets));
411+
/*
412+
* You'll have:
413+
* Array
414+
* (
415+
* [0] => 192.168.0.0/24
416+
* [1] => 192.168.1.0/24
417+
* [...]
418+
* [254] => 192.168.254.0/24
419+
* [255] => 192.168.255.0/24
420+
* )
421+
*/
422+
```
423+
373424
### Getting the subnet mask for IPv4 ranges
374425

375426
You can use the `getSubnetMask()` to get the subnet mask for IPv4 ranges:

src/Range/AbstractRange.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use IPLib\Address\IPv6;
88
use IPLib\Address\Type as AddressType;
99
use IPLib\Factory;
10+
use OutOfBoundsException;
11+
use OverflowException;
1012

1113
/**
1214
* Base class for range classes.
@@ -122,4 +124,48 @@ public function containsRange(RangeInterface $range)
122124

123125
return $result;
124126
}
127+
128+
/**
129+
* {@inheritdoc}
130+
*
131+
* @see \IPLib\Range\RangeInterface::split()
132+
*/
133+
public function split($networkPrefix, $forceSubnet = false)
134+
{
135+
$networkPrefix = (int) $networkPrefix;
136+
$myNetworkPrefix = $this->getNetworkPrefix();
137+
if ($networkPrefix === $myNetworkPrefix) {
138+
return array(
139+
$forceSubnet ? $this->asSubnet() : $this,
140+
);
141+
}
142+
if ($networkPrefix < $myNetworkPrefix) {
143+
throw new OutOfBoundsException("The value of the \$networkPrefix parameter can't be smaller than the network prefix of the range ({$myNetworkPrefix})");
144+
}
145+
$startIp = $this->getStartAddress();
146+
$maxPrefix = $startIp::getNumberOfBits();
147+
if ($networkPrefix > $maxPrefix) {
148+
throw new OutOfBoundsException("The value of the \$networkPrefix parameter can't be larger than the maximum network prefix of the range ({$maxPrefix})");
149+
}
150+
151+
$systemBitness = PHP_INT_SIZE * 8;
152+
$minPrefixByBitness = $maxPrefix - $systemBitness + 2;
153+
if ($networkPrefix < $minPrefixByBitness) {
154+
throw new OverflowException("The value of \$networkPrefix leads to too large ranges for the current machine bitness (you can use a value of at least {$minPrefixByBitness})");
155+
}
156+
157+
$chunkSize = pow(2, $maxPrefix - $networkPrefix);
158+
$maxIndex = $this->getSize() / $chunkSize;
159+
$data = array();
160+
for ($i = 0; $i < $maxIndex; $i++) {
161+
$range = Subnet::parseString(sprintf('%s/%d', $startIp, $networkPrefix));
162+
if (!$forceSubnet && $this instanceof Pattern) {
163+
$range = $range->asPattern() ?: $range;
164+
}
165+
$data[] = $range;
166+
$startIp = $startIp->getAddressAtOffset($chunkSize);
167+
}
168+
169+
return $data;
170+
}
125171
}

src/Range/Pattern.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,11 @@ public function getSize()
308308
}
309309

310310
/**
311-
* @return float|int
311+
* {@inheritdoc}
312+
*
313+
* @see \IPLib\Range\RangeInterface::getNetworkPrefix()
312314
*/
313-
private function getNetworkPrefix()
315+
public function getNetworkPrefix()
314316
{
315317
switch ($this->getAddressType()) {
316318
case AddressType::T_IPv4:

src/Range/RangeInterface.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,31 @@ public function getReverseDNSLookupName();
157157
* @since 1.16.0
158158
*/
159159
public function getSize();
160+
161+
/**
162+
* Get the "network prefix", that is how many bits of the address are dedicated to the network portion.
163+
*
164+
* @return int
165+
*
166+
* @since 1.19.0
167+
*
168+
* @example for 10.0.0.0/24 it's 24
169+
* @example for 10.0.0.* it's 24
170+
*/
171+
public function getNetworkPrefix();
172+
173+
/**
174+
* Split the range into smaller ranges.
175+
*
176+
* @param int $networkPrefix
177+
* @param bool $forceSubnet set to true to always have ranges in "subnet format" (ie 1.2.3.4/5), to false to try to keep the original format if possible (that is, pattern to pattern, single to single)
178+
*
179+
* @throws \OutOfBoundsException if $networkPrefix is not valid
180+
* @throws \OverflowException if you are running a 32-bit system and the ranges are too wide
181+
*
182+
* @return \IPLib\Range\RangeInterface[]
183+
*
184+
* @since 1.19.0
185+
*/
186+
public function split($networkPrefix, $forceSubnet = false);
160187
}

src/Range/Single.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,7 @@ public function getComparableEndString()
190190
*/
191191
public function asSubnet()
192192
{
193-
$networkPrefixes = array(
194-
AddressType::T_IPv4 => 32,
195-
AddressType::T_IPv6 => 128,
196-
);
197-
198-
return new Subnet($this->address, $this->address, $networkPrefixes[$this->address->getAddressType()]);
193+
return new Subnet($this->address, $this->address, $this->getNetworkPrefix());
199194
}
200195

201196
/**
@@ -241,4 +236,19 @@ public function getSize()
241236
{
242237
return 1;
243238
}
239+
240+
/**
241+
* {@inheritdoc}
242+
*
243+
* @see \IPLib\Range\RangeInterface::getNetworkPrefix()
244+
*/
245+
public function getNetworkPrefix()
246+
{
247+
switch ($this->getAddressType()) {
248+
case AddressType::T_IPv4:
249+
return 32;
250+
case AddressType::T_IPv6:
251+
return 128;
252+
}
253+
}
244254
}

src/Range/Subnet.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,9 @@ public static function get6to4()
261261
}
262262

263263
/**
264-
* Get subnet prefix.
265-
*
266-
* @return int
264+
* {@inheritdoc}
267265
*
266+
* @see \IPLib\Range\RangeInterface::getNetworkPrefix()
268267
* @since 1.7.0
269268
*/
270269
public function getNetworkPrefix()

0 commit comments

Comments
 (0)