1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace IPLib\Address; |
4
|
|
|
|
5
|
|
|
use IPLib\Range\RangeInterface; |
6
|
|
|
use IPLib\Range\Subnet; |
7
|
|
|
use IPLib\Range\Type as RangeType; |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* An IPv4 address. |
11
|
|
|
*/ |
12
|
|
|
class IPv4 implements AddressInterface |
13
|
|
|
{ |
14
|
|
|
/** |
15
|
|
|
* The string representation of the address. |
16
|
|
|
* |
17
|
|
|
* @var string |
18
|
|
|
* |
19
|
|
|
* @example '127.0.0.1' |
20
|
|
|
*/ |
21
|
|
|
protected $address; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* The byte list of the IP address. |
25
|
|
|
* |
26
|
|
|
* @var int[]|null |
27
|
|
|
*/ |
28
|
|
|
protected $bytes; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* The type of the range of this IP address. |
32
|
|
|
* |
33
|
|
|
* @var int|null |
34
|
|
|
*/ |
35
|
|
|
protected $rangeType; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* A string representation of this address than can be used when comparing addresses and ranges. |
39
|
|
|
* |
40
|
|
|
* @var string |
41
|
|
|
*/ |
42
|
|
|
protected $comparableString; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* An array containing RFC designated address ranges. |
46
|
|
|
* |
47
|
|
|
* @var array|null |
48
|
|
|
*/ |
49
|
|
|
private static $reservedRanges = null; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* Initializes the instance. |
53
|
|
|
* |
54
|
|
|
* @param string $address |
55
|
|
|
*/ |
56
|
744 |
|
protected function __construct($address) |
57
|
|
|
{ |
58
|
744 |
|
$this->address = $address; |
59
|
744 |
|
$this->bytes = null; |
60
|
744 |
|
$this->rangeType = null; |
61
|
744 |
|
$this->comparableString = null; |
62
|
744 |
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* {@inheritdoc} |
66
|
|
|
* |
67
|
|
|
* @see \IPLib\Address\AddressInterface::__toString() |
68
|
|
|
*/ |
69
|
58 |
|
public function __toString() |
70
|
|
|
{ |
71
|
58 |
|
return $this->address; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* {@inheritdoc} |
76
|
|
|
* |
77
|
|
|
* @see \IPLib\Address\AddressInterface::getNumberOfBits() |
78
|
|
|
*/ |
79
|
30 |
|
public static function getNumberOfBits() |
80
|
|
|
{ |
81
|
30 |
|
return 32; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Parse a string and returns an IPv4 instance if the string is valid, or null otherwise. |
86
|
|
|
* |
87
|
|
|
* @param string|mixed $address the address to parse |
88
|
|
|
* @param bool $mayIncludePort set to false to avoid parsing addresses with ports |
89
|
|
|
* @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses |
90
|
|
|
* |
91
|
|
|
* @return static|null |
92
|
|
|
*/ |
93
|
1232 |
|
public static function fromString($address, $mayIncludePort = true, $supportNonDecimalIPv4 = false) |
94
|
|
|
{ |
95
|
1232 |
|
if (!is_string($address) || !strpos($address, '.')) { |
96
|
489 |
|
return null; |
97
|
|
|
} |
98
|
756 |
|
$rxChunk = '0?[0-9]{1,3}'; |
99
|
756 |
|
if ($supportNonDecimalIPv4) { |
100
|
32 |
|
$rxChunk = "(?:0[Xx]0*[0-9A-Fa-f]{1,2})|(?:{$rxChunk})"; |
101
|
|
|
} |
102
|
756 |
|
$rx = "0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})"; |
103
|
756 |
|
if ($mayIncludePort) { |
104
|
756 |
|
$rx .= '(?::\d+)?'; |
105
|
|
|
} |
106
|
756 |
|
$matches = null; |
107
|
756 |
|
if (!preg_match('/^' . $rx . '$/', $address, $matches)) { |
108
|
27 |
|
return null; |
109
|
|
|
} |
110
|
738 |
|
$nums = array(); |
111
|
738 |
|
for ($i = 1; $i <= 4; $i++) { |
112
|
738 |
|
$s = $matches[$i]; |
113
|
738 |
|
if ($supportNonDecimalIPv4) { |
114
|
32 |
|
if (stripos($s, '0x') === 0) { |
115
|
21 |
|
$n = hexdec(substr($s, 2)); |
116
|
32 |
|
} elseif ($s[0] === '0') { |
117
|
32 |
|
if (!preg_match('/^[0-7]+$/', $s)) { |
118
|
3 |
|
return null; |
119
|
|
|
} |
120
|
29 |
|
$n = octdec(substr($s, 1)); |
121
|
|
|
} else { |
122
|
29 |
|
$n = (int) $s; |
123
|
|
|
} |
124
|
|
|
} else { |
125
|
718 |
|
$n = (int) $s; |
126
|
|
|
} |
127
|
735 |
|
if ($n < 0 || $n > 255) { |
128
|
6 |
|
return null; |
129
|
|
|
} |
130
|
735 |
|
$nums[] = (string) $n; |
131
|
|
|
} |
132
|
|
|
|
133
|
729 |
|
return new static(implode('.', $nums)); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Parse an array of bytes and returns an IPv4 instance if the array is valid, or null otherwise. |
138
|
|
|
* |
139
|
|
|
* @param int[]|array $bytes |
140
|
|
|
* |
141
|
|
|
* @return static|null |
142
|
|
|
*/ |
143
|
662 |
|
public static function fromBytes(array $bytes) |
144
|
|
|
{ |
145
|
662 |
|
$result = null; |
146
|
662 |
|
if (count($bytes) === 4) { |
147
|
376 |
|
$chunks = array_map( |
148
|
376 |
|
function ($byte) { |
149
|
376 |
|
return (is_int($byte) && $byte >= 0 && $byte <= 255) ? (string) $byte : false; |
150
|
376 |
|
}, |
151
|
376 |
|
$bytes |
152
|
|
|
); |
153
|
376 |
|
if (in_array(false, $chunks, true) === false) { |
154
|
376 |
|
$result = new static(implode('.', $chunks)); |
155
|
|
|
} |
156
|
|
|
} |
157
|
|
|
|
158
|
662 |
|
return $result; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* {@inheritdoc} |
163
|
|
|
* |
164
|
|
|
* @see \IPLib\Address\AddressInterface::toString() |
165
|
|
|
*/ |
166
|
618 |
|
public function toString($long = false) |
167
|
|
|
{ |
168
|
618 |
|
if ($long) { |
169
|
37 |
|
return $this->getComparableString(); |
170
|
|
|
} |
171
|
|
|
|
172
|
618 |
|
return $this->address; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* Get the octal representation of this IP address. |
177
|
|
|
* |
178
|
|
|
* @param bool $long |
179
|
|
|
* |
180
|
|
|
* @return string |
181
|
|
|
* |
182
|
|
|
* @example if $long == false: if the decimal representation is '0.7.8.255': '0.7.010.0377' |
183
|
|
|
* @example if $long == true: if the decimal representation is '0.7.8.255': '0000.0007.0010.0377' |
184
|
|
|
*/ |
185
|
12 |
View Code Duplication |
public function toOctal($long = false) |
|
|
|
|
186
|
|
|
{ |
187
|
12 |
|
$chunks = array(); |
188
|
12 |
|
foreach ($this->getBytes() as $byte) { |
189
|
12 |
|
if ($long) { |
190
|
12 |
|
$chunks[] = sprintf('%04o', $byte); |
191
|
|
|
} else { |
192
|
12 |
|
$chunks[] = '0' . decoct($byte); |
193
|
|
|
} |
194
|
|
|
} |
195
|
|
|
|
196
|
12 |
|
return implode('.', $chunks); |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Get the hexadecimal representation of this IP address. |
201
|
|
|
* |
202
|
|
|
* @param bool $long |
203
|
|
|
* |
204
|
|
|
* @return string |
205
|
|
|
* |
206
|
|
|
* @example if $long == false: if the decimal representation is '0.9.10.255': '0.9.0xa.0xff' |
207
|
|
|
* @example if $long == true: if the decimal representation is '0.9.10.255': '0x00.0x09.0x0a.0xff' |
208
|
|
|
*/ |
209
|
12 |
View Code Duplication |
public function toHexadecimal($long = false) |
|
|
|
|
210
|
|
|
{ |
211
|
12 |
|
$chunks = array(); |
212
|
12 |
|
foreach ($this->getBytes() as $byte) { |
213
|
12 |
|
if ($long) { |
214
|
12 |
|
$chunks[] = sprintf('0x%02x', $byte); |
215
|
|
|
} else { |
216
|
12 |
|
$chunks[] = '0x' . dechex($byte); |
217
|
|
|
} |
218
|
|
|
} |
219
|
|
|
|
220
|
12 |
|
return implode('.', $chunks); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* {@inheritdoc} |
225
|
|
|
* |
226
|
|
|
* @see \IPLib\Address\AddressInterface::getBytes() |
227
|
|
|
*/ |
228
|
562 |
|
public function getBytes() |
229
|
|
|
{ |
230
|
562 |
|
if ($this->bytes === null) { |
231
|
562 |
|
$this->bytes = array_map( |
232
|
562 |
|
function ($chunk) { |
233
|
562 |
|
return (int) $chunk; |
234
|
562 |
|
}, |
235
|
562 |
|
explode('.', $this->address) |
236
|
|
|
); |
237
|
|
|
} |
238
|
|
|
|
239
|
562 |
|
return $this->bytes; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* {@inheritdoc} |
244
|
|
|
* |
245
|
|
|
* @see \IPLib\Address\AddressInterface::getBits() |
246
|
|
|
*/ |
247
|
29 |
View Code Duplication |
public function getBits() |
|
|
|
|
248
|
|
|
{ |
249
|
29 |
|
$parts = array(); |
250
|
29 |
|
foreach ($this->getBytes() as $byte) { |
251
|
29 |
|
$parts[] = sprintf('%08b', $byte); |
252
|
|
|
} |
253
|
|
|
|
254
|
29 |
|
return implode('', $parts); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* {@inheritdoc} |
259
|
|
|
* |
260
|
|
|
* @see \IPLib\Address\AddressInterface::getAddressType() |
261
|
|
|
*/ |
262
|
579 |
|
public function getAddressType() |
263
|
|
|
{ |
264
|
579 |
|
return Type::T_IPv4; |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* {@inheritdoc} |
269
|
|
|
* |
270
|
|
|
* @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType() |
271
|
|
|
*/ |
272
|
173 |
|
public static function getDefaultReservedRangeType() |
273
|
|
|
{ |
274
|
173 |
|
return RangeType::T_PUBLIC; |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* {@inheritdoc} |
279
|
|
|
* |
280
|
|
|
* @see \IPLib\Address\AddressInterface::getReservedRanges() |
281
|
|
|
*/ |
282
|
341 |
|
public static function getReservedRanges() |
283
|
|
|
{ |
284
|
341 |
|
if (self::$reservedRanges === null) { |
285
|
1 |
|
$reservedRanges = array(); |
286
|
|
|
foreach (array( |
287
|
|
|
// RFC 5735 |
288
|
1 |
|
'0.0.0.0/8' => array(RangeType::T_THISNETWORK, array('0.0.0.0/32' => RangeType::T_UNSPECIFIED)), |
289
|
|
|
// RFC 5735 |
290
|
|
|
'10.0.0.0/8' => array(RangeType::T_PRIVATENETWORK), |
291
|
|
|
// RFC 6598 |
292
|
|
|
'100.64.0.0/10' => array(RangeType::T_CGNAT), |
293
|
|
|
// RFC 5735 |
294
|
|
|
'127.0.0.0/8' => array(RangeType::T_LOOPBACK), |
295
|
|
|
// RFC 5735 |
296
|
|
|
'169.254.0.0/16' => array(RangeType::T_LINKLOCAL), |
297
|
|
|
// RFC 5735 |
298
|
|
|
'172.16.0.0/12' => array(RangeType::T_PRIVATENETWORK), |
299
|
|
|
// RFC 5735 |
300
|
|
|
'192.0.0.0/24' => array(RangeType::T_RESERVED), |
301
|
|
|
// RFC 5735 |
302
|
|
|
'192.0.2.0/24' => array(RangeType::T_RESERVED), |
303
|
|
|
// RFC 5735 |
304
|
|
|
'192.88.99.0/24' => array(RangeType::T_ANYCASTRELAY), |
305
|
|
|
// RFC 5735 |
306
|
|
|
'192.168.0.0/16' => array(RangeType::T_PRIVATENETWORK), |
307
|
|
|
// RFC 5735 |
308
|
|
|
'198.18.0.0/15' => array(RangeType::T_RESERVED), |
309
|
|
|
// RFC 5735 |
310
|
|
|
'198.51.100.0/24' => array(RangeType::T_RESERVED), |
311
|
|
|
// RFC 5735 |
312
|
|
|
'203.0.113.0/24' => array(RangeType::T_RESERVED), |
313
|
|
|
// RFC 5735 |
314
|
|
|
'224.0.0.0/4' => array(RangeType::T_MULTICAST), |
315
|
|
|
// RFC 5735 |
316
|
|
|
'240.0.0.0/4' => array(RangeType::T_RESERVED, array('255.255.255.255/32' => RangeType::T_LIMITEDBROADCAST)), |
317
|
|
|
) as $range => $data) { |
318
|
1 |
|
$exceptions = array(); |
319
|
1 |
View Code Duplication |
if (isset($data[1])) { |
|
|
|
|
320
|
1 |
|
foreach ($data[1] as $exceptionRange => $exceptionType) { |
321
|
1 |
|
$exceptions[] = new AssignedRange(Subnet::fromString($exceptionRange), $exceptionType); |
322
|
|
|
} |
323
|
|
|
} |
324
|
1 |
|
$reservedRanges[] = new AssignedRange(Subnet::fromString($range), $data[0], $exceptions); |
325
|
|
|
} |
326
|
1 |
|
self::$reservedRanges = $reservedRanges; |
327
|
|
|
} |
328
|
|
|
|
329
|
341 |
|
return self::$reservedRanges; |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
/** |
333
|
|
|
* {@inheritdoc} |
334
|
|
|
* |
335
|
|
|
* @see \IPLib\Address\AddressInterface::getRangeType() |
336
|
|
|
*/ |
337
|
171 |
|
public function getRangeType() |
338
|
|
|
{ |
339
|
171 |
View Code Duplication |
if ($this->rangeType === null) { |
|
|
|
|
340
|
171 |
|
$rangeType = null; |
341
|
171 |
|
foreach (static::getReservedRanges() as $reservedRange) { |
342
|
171 |
|
$rangeType = $reservedRange->getAddressType($this); |
343
|
171 |
|
if ($rangeType !== null) { |
344
|
171 |
|
break; |
345
|
|
|
} |
346
|
|
|
} |
347
|
171 |
|
$this->rangeType = $rangeType === null ? static::getDefaultReservedRangeType() : $rangeType; |
348
|
|
|
} |
349
|
|
|
|
350
|
171 |
|
return $this->rangeType; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* Create an IPv6 representation of this address (in 6to4 notation). |
355
|
|
|
* |
356
|
|
|
* @return \IPLib\Address\IPv6 |
357
|
|
|
*/ |
358
|
4 |
|
public function toIPv6() |
359
|
|
|
{ |
360
|
4 |
|
$myBytes = $this->getBytes(); |
361
|
|
|
|
362
|
4 |
|
return IPv6::fromString('2002:' . sprintf('%02x', $myBytes[0]) . sprintf('%02x', $myBytes[1]) . ':' . sprintf('%02x', $myBytes[2]) . sprintf('%02x', $myBytes[3]) . '::'); |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
/** |
366
|
|
|
* Create an IPv6 representation of this address (in IPv6 IPv4-mapped notation). |
367
|
|
|
* |
368
|
|
|
* @return \IPLib\Address\IPv6 |
369
|
|
|
*/ |
370
|
4 |
|
public function toIPv6IPv4Mapped() |
371
|
|
|
{ |
372
|
4 |
|
return IPv6::fromBytes(array_merge(array(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff), $this->getBytes())); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* {@inheritdoc} |
377
|
|
|
* |
378
|
|
|
* @see \IPLib\Address\AddressInterface::getComparableString() |
379
|
|
|
*/ |
380
|
416 |
|
public function getComparableString() |
381
|
|
|
{ |
382
|
416 |
|
if ($this->comparableString === null) { |
383
|
416 |
|
$chunks = array(); |
384
|
416 |
|
foreach ($this->getBytes() as $byte) { |
385
|
416 |
|
$chunks[] = sprintf('%03d', $byte); |
386
|
|
|
} |
387
|
416 |
|
$this->comparableString = implode('.', $chunks); |
388
|
|
|
} |
389
|
|
|
|
390
|
416 |
|
return $this->comparableString; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* {@inheritdoc} |
395
|
|
|
* |
396
|
|
|
* @see \IPLib\Address\AddressInterface::matches() |
397
|
|
|
*/ |
398
|
11 |
|
public function matches(RangeInterface $range) |
399
|
|
|
{ |
400
|
11 |
|
return $range->contains($this); |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
/** |
404
|
|
|
* {@inheritdoc} |
405
|
|
|
* |
406
|
|
|
* @see \IPLib\Address\AddressInterface::getAddressAtOffset() |
407
|
|
|
*/ |
408
|
9 |
View Code Duplication |
public function getAddressAtOffset($n) |
|
|
|
|
409
|
|
|
{ |
410
|
9 |
|
if (!is_int($n)) { |
411
|
9 |
|
return null; |
412
|
9 |
|
} |
413
|
9 |
|
|
414
|
6 |
|
$boundary = 256; |
415
|
1 |
|
$mod = $n; |
416
|
1 |
|
$bytes = $this->getBytes(); |
417
|
|
|
for ($i = count($bytes) - 1; $i >= 0; $i--) { |
418
|
6 |
|
$tmp = ($bytes[$i] + $mod) % $boundary; |
419
|
|
|
$mod = (int) floor(($bytes[$i] + $mod) / $boundary); |
420
|
9 |
|
if ($tmp < 0) { |
421
|
9 |
|
$tmp += $boundary; |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
$bytes[$i] = $tmp; |
425
|
9 |
|
} |
426
|
|
|
|
427
|
|
|
if ($mod !== 0) { |
428
|
|
|
return null; |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
return static::fromBytes($bytes); |
432
|
|
|
} |
433
|
9 |
|
|
434
|
|
|
/** |
435
|
9 |
|
* {@inheritdoc} |
436
|
9 |
|
* |
437
|
9 |
|
* @see \IPLib\Address\AddressInterface::getNextAddress() |
438
|
9 |
|
*/ |
439
|
6 |
|
public function getNextAddress() |
440
|
1 |
|
{ |
441
|
1 |
|
return $this->getAddressAtOffset(1); |
442
|
|
|
} |
443
|
6 |
|
|
444
|
|
|
/** |
445
|
9 |
|
* {@inheritdoc} |
446
|
9 |
|
* |
447
|
|
|
* @see \IPLib\Address\AddressInterface::getPreviousAddress() |
448
|
|
|
*/ |
449
|
|
|
public function getPreviousAddress() |
450
|
9 |
|
{ |
451
|
|
|
return $this->getAddressAtOffset(-1); |
452
|
|
|
} |
453
|
|
|
|
454
|
|
|
/** |
455
|
|
|
* {@inheritdoc} |
456
|
|
|
* |
457
|
|
|
* @see \IPLib\Address\AddressInterface::getReverseDNSLookupName() |
458
|
14 |
|
*/ |
459
|
|
|
public function getReverseDNSLookupName() |
460
|
14 |
|
{ |
461
|
14 |
|
return implode( |
462
|
14 |
|
'.', |
463
|
14 |
|
array_reverse($this->getBytes()) |
464
|
|
|
) . '.in-addr.arpa'; |
465
|
|
|
} |
466
|
|
|
} |
467
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.