1 | <?php |
||||
2 | |||||
3 | namespace IPLib\Range; |
||||
4 | |||||
5 | use IPLib\Address\AddressInterface; |
||||
6 | use IPLib\Address\IPv4; |
||||
7 | use IPLib\Address\Type as AddressType; |
||||
8 | use IPLib\Factory; |
||||
9 | use IPLib\ParseStringFlag; |
||||
10 | |||||
11 | /** |
||||
12 | * Represents an address range in subnet format (eg CIDR). |
||||
13 | * |
||||
14 | * @example 127.0.0.1/32 |
||||
15 | * @example ::/8 |
||||
16 | */ |
||||
17 | class Subnet extends AbstractRange |
||||
18 | { |
||||
19 | /** |
||||
20 | * Starting address of the range. |
||||
21 | * |
||||
22 | * @var \IPLib\Address\AddressInterface |
||||
23 | */ |
||||
24 | protected $fromAddress; |
||||
25 | |||||
26 | /** |
||||
27 | * Final address of the range. |
||||
28 | * |
||||
29 | * @var \IPLib\Address\AddressInterface |
||||
30 | */ |
||||
31 | protected $toAddress; |
||||
32 | |||||
33 | /** |
||||
34 | * Number of the same bits of the range. |
||||
35 | * |
||||
36 | * @var int |
||||
37 | */ |
||||
38 | protected $networkPrefix; |
||||
39 | |||||
40 | /** |
||||
41 | * The type of the range of this IP range. |
||||
42 | * |
||||
43 | * @var int|null |
||||
44 | * |
||||
45 | * @since 1.5.0 |
||||
46 | */ |
||||
47 | protected $rangeType; |
||||
48 | |||||
49 | /** |
||||
50 | * The 6to4 address IPv6 address range. |
||||
51 | * |
||||
52 | * @var self|null |
||||
53 | */ |
||||
54 | private static $sixToFour; |
||||
55 | |||||
56 | /** |
||||
57 | * Initializes the instance. |
||||
58 | * |
||||
59 | * @param \IPLib\Address\AddressInterface $fromAddress |
||||
60 | * @param \IPLib\Address\AddressInterface $toAddress |
||||
61 | * @param int $networkPrefix |
||||
62 | * |
||||
63 | * @internal |
||||
64 | */ |
||||
65 | 658 | public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $networkPrefix) |
|||
66 | { |
||||
67 | 658 | $this->fromAddress = $fromAddress; |
|||
68 | 658 | $this->toAddress = $toAddress; |
|||
69 | 658 | $this->networkPrefix = $networkPrefix; |
|||
70 | 658 | } |
|||
71 | |||||
72 | /** |
||||
73 | * {@inheritdoc} |
||||
74 | * |
||||
75 | * @see \IPLib\Range\RangeInterface::__toString() |
||||
76 | */ |
||||
77 | 167 | public function __toString() |
|||
78 | { |
||||
79 | 167 | return $this->toString(); |
|||
80 | } |
||||
81 | |||||
82 | /** |
||||
83 | * @deprecated since 1.17.0: use the parseString() method instead. |
||||
84 | * For upgrading: |
||||
85 | * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag |
||||
86 | * |
||||
87 | * @param string|mixed $range |
||||
88 | * @param bool $supportNonDecimalIPv4 |
||||
89 | * |
||||
90 | * @return static|null |
||||
91 | * |
||||
92 | * @see \IPLib\Range\Subnet::parseString() |
||||
93 | * @since 1.10.0 added the $supportNonDecimalIPv4 argument |
||||
94 | */ |
||||
95 | 11 | public static function fromString($range, $supportNonDecimalIPv4 = false) |
|||
96 | { |
||||
97 | 11 | return static::parseString($range, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0)); |
|||
98 | } |
||||
99 | |||||
100 | /** |
||||
101 | * Try get the range instance starting from its string representation. |
||||
102 | * |
||||
103 | * @param string|mixed $range |
||||
104 | * @param int $flags A combination or zero or more flags |
||||
105 | * |
||||
106 | * @return static|null |
||||
107 | * |
||||
108 | * @see \IPLib\ParseStringFlag |
||||
109 | * @since 1.17.0 |
||||
110 | */ |
||||
111 | 784 | public static function parseString($range, $flags = 0) |
|||
112 | { |
||||
113 | 784 | if (!is_string($range)) { |
|||
114 | 3 | return null; |
|||
115 | } |
||||
116 | 781 | $parts = explode('/', $range); |
|||
117 | 781 | if (count($parts) !== 2) { |
|||
118 | 149 | return null; |
|||
119 | } |
||||
120 | 639 | $flags = (int) $flags; |
|||
121 | 639 | if (strpos($parts[0], ':') === false && $flags & ParseStringFlag::IPV4SUBNET_MAYBE_COMPACT) { |
|||
122 | 9 | $missingDots = 3 - substr_count($parts[0], '.'); |
|||
123 | 9 | if ($missingDots > 0) { |
|||
124 | 7 | $parts[0] .= str_repeat('.0', $missingDots); |
|||
125 | } |
||||
126 | } |
||||
127 | 639 | $address = Factory::parseAddressString($parts[0], $flags); |
|||
128 | 639 | if ($address === null) { |
|||
129 | 10 | return null; |
|||
130 | } |
||||
131 | 636 | if (!preg_match('/^[0-9]{1,9}$/', $parts[1])) { |
|||
132 | 2 | return null; |
|||
133 | } |
||||
134 | 634 | $networkPrefix = (int) $parts[1]; |
|||
135 | 634 | $addressBytes = $address->getBytes(); |
|||
136 | 634 | $totalBytes = count($addressBytes); |
|||
137 | 634 | $numDifferentBits = $totalBytes * 8 - $networkPrefix; |
|||
138 | 634 | if ($numDifferentBits < 0) { |
|||
139 | 2 | return null; |
|||
140 | } |
||||
141 | 632 | $numSameBytes = $networkPrefix >> 3; |
|||
142 | 632 | $sameBytes = array_slice($addressBytes, 0, $numSameBytes); |
|||
143 | 632 | $differentBytesStart = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 0); |
|||
144 | 632 | $differentBytesEnd = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 255); |
|||
145 | 632 | $startSameBits = $networkPrefix % 8; |
|||
146 | 632 | if ($startSameBits !== 0) { |
|||
147 | 425 | $varyingByte = $addressBytes[$numSameBytes]; |
|||
148 | 425 | $differentBytesStart[0] = $varyingByte & bindec(str_pad(str_repeat('1', $startSameBits), 8, '0', STR_PAD_RIGHT)); |
|||
149 | 425 | $differentBytesEnd[0] = $differentBytesStart[0] + bindec(str_repeat('1', 8 - $startSameBits)); |
|||
150 | } |
||||
151 | |||||
152 | 632 | return new static( |
|||
153 | 632 | Factory::addressFromBytes(array_merge($sameBytes, $differentBytesStart)), |
|||
154 | 632 | Factory::addressFromBytes(array_merge($sameBytes, $differentBytesEnd)), |
|||
155 | 632 | $networkPrefix |
|||
156 | ); |
||||
157 | } |
||||
158 | |||||
159 | /** |
||||
160 | * {@inheritdoc} |
||||
161 | * |
||||
162 | * @see \IPLib\Range\RangeInterface::toString() |
||||
163 | */ |
||||
164 | 402 | public function toString($long = false) |
|||
165 | { |
||||
166 | 402 | return $this->fromAddress->toString($long) . '/' . $this->networkPrefix; |
|||
167 | } |
||||
168 | |||||
169 | /** |
||||
170 | * {@inheritdoc} |
||||
171 | * |
||||
172 | * @see \IPLib\Range\RangeInterface::getAddressType() |
||||
173 | */ |
||||
174 | 521 | public function getAddressType() |
|||
175 | { |
||||
176 | 521 | return $this->fromAddress->getAddressType(); |
|||
177 | } |
||||
178 | |||||
179 | /** |
||||
180 | * {@inheritdoc} |
||||
181 | * |
||||
182 | * @see \IPLib\Range\RangeInterface::getStartAddress() |
||||
183 | */ |
||||
184 | 151 | public function getStartAddress() |
|||
185 | { |
||||
186 | 151 | return $this->fromAddress; |
|||
187 | } |
||||
188 | |||||
189 | /** |
||||
190 | * {@inheritdoc} |
||||
191 | * |
||||
192 | * @see \IPLib\Range\RangeInterface::getEndAddress() |
||||
193 | */ |
||||
194 | 42 | public function getEndAddress() |
|||
195 | { |
||||
196 | 42 | return $this->toAddress; |
|||
197 | } |
||||
198 | |||||
199 | /** |
||||
200 | * {@inheritdoc} |
||||
201 | * |
||||
202 | * @see \IPLib\Range\RangeInterface::getComparableStartString() |
||||
203 | */ |
||||
204 | 493 | public function getComparableStartString() |
|||
205 | { |
||||
206 | 493 | return $this->fromAddress->getComparableString(); |
|||
207 | } |
||||
208 | |||||
209 | /** |
||||
210 | * {@inheritdoc} |
||||
211 | * |
||||
212 | * @see \IPLib\Range\RangeInterface::getComparableEndString() |
||||
213 | */ |
||||
214 | 492 | public function getComparableEndString() |
|||
215 | { |
||||
216 | 492 | return $this->toAddress->getComparableString(); |
|||
217 | } |
||||
218 | |||||
219 | /** |
||||
220 | * {@inheritdoc} |
||||
221 | * |
||||
222 | * @see \IPLib\Range\RangeInterface::asSubnet() |
||||
223 | */ |
||||
224 | 56 | public function asSubnet() |
|||
225 | { |
||||
226 | 56 | return $this; |
|||
227 | } |
||||
228 | |||||
229 | /** |
||||
230 | * {@inheritdoc} |
||||
231 | * |
||||
232 | * @see \IPLib\Range\RangeInterface::asPattern() |
||||
233 | * @since 1.8.0 |
||||
234 | */ |
||||
235 | 62 | public function asPattern() |
|||
236 | { |
||||
237 | 62 | $address = $this->getStartAddress(); |
|||
238 | 62 | $networkPrefix = $this->getNetworkPrefix(); |
|||
239 | 62 | switch ($address->getAddressType()) { |
|||
240 | case AddressType::T_IPv4: |
||||
241 | 23 | return $networkPrefix % 8 === 0 ? new Pattern($address, $address, 4 - $networkPrefix / 8) : null; |
|||
242 | case AddressType::T_IPv6: |
||||
243 | 39 | return $networkPrefix % 16 === 0 ? new Pattern($address, $address, 8 - $networkPrefix / 16) : null; |
|||
244 | } |
||||
245 | } |
||||
246 | |||||
247 | /** |
||||
248 | * Get the 6to4 address IPv6 address range. |
||||
249 | * |
||||
250 | * @return self |
||||
251 | * |
||||
252 | * @since 1.5.0 |
||||
253 | */ |
||||
254 | 72 | public static function get6to4() |
|||
255 | { |
||||
256 | 72 | if (self::$sixToFour === null) { |
|||
257 | 1 | self::$sixToFour = self::parseString('2002::/16'); |
|||
258 | } |
||||
259 | |||||
260 | 72 | return self::$sixToFour; |
|||
261 | } |
||||
262 | |||||
263 | /** |
||||
264 | * {@inheritdoc} |
||||
265 | * |
||||
266 | * @see \IPLib\Range\RangeInterface::getNetworkPrefix() |
||||
267 | * @since 1.7.0 |
||||
268 | */ |
||||
269 | 289 | public function getNetworkPrefix() |
|||
270 | { |
||||
271 | 289 | return $this->networkPrefix; |
|||
272 | } |
||||
273 | |||||
274 | /** |
||||
275 | * {@inheritdoc} |
||||
276 | * |
||||
277 | * @see \IPLib\Range\RangeInterface::getSubnetMask() |
||||
278 | */ |
||||
279 | 19 | public function getSubnetMask() |
|||
280 | { |
||||
281 | 19 | if ($this->getAddressType() !== AddressType::T_IPv4) { |
|||
282 | 1 | return null; |
|||
283 | } |
||||
284 | 18 | $bytes = array(); |
|||
285 | 18 | $prefix = $this->getNetworkPrefix(); |
|||
286 | 18 | while ($prefix >= 8) { |
|||
287 | 10 | $bytes[] = 255; |
|||
288 | 10 | $prefix -= 8; |
|||
289 | } |
||||
290 | 18 | if ($prefix !== 0) { |
|||
291 | 13 | $bytes[] = bindec(str_pad(str_repeat('1', $prefix), 8, '0')); |
|||
292 | } |
||||
293 | 18 | $bytes = array_pad($bytes, 4, 0); |
|||
294 | |||||
295 | 18 | return IPv4::fromBytes($bytes); |
|||
296 | } |
||||
297 | |||||
298 | /** |
||||
299 | * {@inheritdoc} |
||||
300 | * |
||||
301 | * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName() |
||||
302 | */ |
||||
303 | 24 | public function getReverseDNSLookupName() |
|||
304 | { |
||||
305 | 24 | switch ($this->getAddressType()) { |
|||
306 | case AddressType::T_IPv4: |
||||
307 | 9 | $unitSize = 8; // bytes |
|||
308 | 9 | $maxUnits = 4; |
|||
309 | 9 | $isHex = false; |
|||
310 | 9 | $rxUnit = '\d+'; |
|||
311 | 9 | break; |
|||
312 | case AddressType::T_IPv6: |
||||
313 | 15 | $unitSize = 4; // nibbles |
|||
314 | 15 | $maxUnits = 32; |
|||
315 | 15 | $isHex = true; |
|||
316 | 15 | $rxUnit = '[0-9A-Fa-f]'; |
|||
317 | 15 | break; |
|||
318 | } |
||||
319 | 24 | $totBits = $unitSize * $maxUnits; |
|||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
![]() Comprehensibility
Best Practice
introduced
by
|
|||||
320 | 24 | $prefixUnits = (int) ($this->networkPrefix / $unitSize); |
|||
321 | 24 | $extraBits = ($totBits - $this->networkPrefix) % $unitSize; |
|||
322 | 24 | if ($extraBits !== 0) { |
|||
323 | 5 | $prefixUnits += 1; |
|||
324 | } |
||||
325 | 24 | $numVariants = 1 << $extraBits; |
|||
326 | 24 | $result = array(); |
|||
327 | 24 | $unitsToRemove = $maxUnits - $prefixUnits; |
|||
328 | 24 | $initialPointer = preg_replace("/^(({$rxUnit})\.){{$unitsToRemove}}/", '', $this->getStartAddress()->getReverseDNSLookupName()); |
|||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
329 | 24 | $chunks = explode('.', $initialPointer, 2); |
|||
330 | 24 | for ($index = 0; $index < $numVariants; $index++) { |
|||
331 | 24 | if ($index !== 0) { |
|||
332 | 5 | $chunks[0] = $isHex ? dechex(1 + hexdec($chunks[0])) : (string) (1 + (int) $chunks[0]); |
|||
0 ignored issues
–
show
1 + hexdec($chunks[0]) of type double is incompatible with the type integer expected by parameter $num of dechex() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() Comprehensibility
Best Practice
introduced
by
|
|||||
333 | } |
||||
334 | 24 | $result[] = implode('.', $chunks); |
|||
335 | } |
||||
336 | |||||
337 | 24 | return $result; |
|||
338 | } |
||||
339 | |||||
340 | /** |
||||
341 | * {@inheritdoc} |
||||
342 | * |
||||
343 | * @see \IPLib\Range\RangeInterface::getSize() |
||||
344 | */ |
||||
345 | 4 | public function getSize() |
|||
346 | { |
||||
347 | 4 | $fromAddress = $this->fromAddress; |
|||
348 | 4 | $maxPrefix = $fromAddress::getNumberOfBits(); |
|||
349 | 4 | $prefix = $this->getNetworkPrefix(); |
|||
350 | |||||
351 | 4 | return pow(2, ($maxPrefix - $prefix)); |
|||
352 | } |
||||
353 | } |
||||
354 |