1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Copyright (c) 2018 Mattheu Racine |
4
|
|
|
* |
5
|
|
|
* For the full copyright and license information, please view the LICENSE |
6
|
|
|
* file that was distributed with this source code. |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace mracine\IPTools\IPv4; |
10
|
|
|
|
11
|
|
|
use OutOfBoundsException; |
12
|
|
|
use InvalidArgumentException; |
13
|
|
|
use DomainException; |
14
|
|
|
use RuntimeException; |
15
|
|
|
|
16
|
|
|
use mracine\IPTools\IPVersion; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Represents an IPv4 address |
20
|
|
|
* |
21
|
|
|
* Used to represents and manipulate IPv4 addresses |
22
|
|
|
* |
23
|
|
|
* IMMUTABLE : once instancied, cannot be modified. |
24
|
|
|
* New instances are created and returned when results implies differents values. |
25
|
|
|
* |
26
|
|
|
* @package IPTools |
27
|
|
|
*/ |
28
|
|
|
class Address implements IPVersion |
29
|
|
|
{ |
30
|
|
|
/** |
31
|
|
|
* Classes of an address |
32
|
|
|
* |
33
|
|
|
* Obsolete for pure routing but still significant for multicat addresses (class D) |
34
|
|
|
* |
35
|
|
|
* @see https://en.wikipedia.org/wiki/Classful_network |
36
|
|
|
*/ |
37
|
|
|
const CLASS_A = "A", |
38
|
|
|
CLASS_B = "B", |
39
|
|
|
CLASS_C = "C", |
40
|
|
|
CLASS_D = "D", |
41
|
|
|
CLASS_E = "E"; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* IPv4 addresses are stored as a 32 bits integer |
45
|
|
|
* |
46
|
|
|
* @var int |
47
|
|
|
*/ |
48
|
|
|
protected $address = 0; |
49
|
|
|
|
50
|
|
|
// Implemnents version(); |
51
|
|
|
use IPv4; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Creates an instance of Adress from an integer (0x00000000 to 0xffffffff) |
55
|
|
|
* |
56
|
|
|
* @see Address::fromInteger |
57
|
|
|
* @see Address::fromString |
58
|
|
|
* @see Address::fromCidr |
59
|
|
|
* @see Address::fromArray |
60
|
|
|
* @param int $address 32 bits integer reprsenting the adress |
61
|
|
|
* @throws OutOfBoundsException when the value is negative or greater than 32 bits value |
62
|
|
|
* @return self |
63
|
|
|
*/ |
64
|
9 |
|
public function __construct(int $address) |
65
|
|
|
{ |
66
|
9 |
|
if ($address<0 || $address>0xffffffff) |
67
|
|
|
{ |
68
|
4 |
|
throw new OutOfBoundsException(sprintf("Cannot convert 0x%x to an IPv4 address", $address)); |
69
|
|
|
} |
70
|
|
|
|
71
|
5 |
|
$this->address = $address; |
72
|
5 |
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Creates an instance of Adress from an integer (0x00000000 to 0xffffffff) |
76
|
|
|
* |
77
|
|
|
* Maintained to avoid BC-break |
78
|
|
|
* |
79
|
|
|
* @param int $address 32 bits integer reprsenting the adress |
80
|
|
|
* @throws OutOfBoundsException when the value is negative or greater than 32 bits value |
81
|
|
|
* @return self |
82
|
|
|
*/ |
83
|
|
|
|
84
|
3 |
|
public static function fromInteger(int $address) |
85
|
|
|
{ |
86
|
3 |
|
return new static($address); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Creates an instance of Address from an array. |
92
|
|
|
* |
93
|
|
|
* The array must contains excly 4 elements (strings or integer). |
94
|
|
|
* Each elemnt represents a digit (byte) of the dotted quad notation ([192,168,1,10]) |
95
|
|
|
* |
96
|
|
|
* @param (int|string)[] $address |
97
|
|
|
* @throws InvalidArgumentException when the array doesn not contains four elements |
98
|
|
|
* @throws InvalidArgumentException when a string element is empty |
99
|
|
|
* @throws InvalidArgumentException when a string element is not a pure integer representation |
100
|
|
|
* @throws InvalidArgumentException when an element is not a string nor an integer |
101
|
|
|
* @throws OutOfBoundsException when a string element represent a negative value |
102
|
|
|
* @throws OutOfBoundsException when an element is negative or greater than 32 bits integer |
103
|
|
|
* @return self |
104
|
|
|
*/ |
105
|
|
|
|
106
|
22 |
|
public static function fromArray(Array $address) |
107
|
|
|
{ |
108
|
22 |
|
if( count($address) != 4) |
109
|
|
|
{ |
110
|
5 |
|
throw new InvalidArgumentException(sprintf("Array must contain 4 digits, %d found", count($address))); |
111
|
|
|
} |
112
|
|
|
|
113
|
17 |
|
$buffer = 0; |
114
|
17 |
|
foreach($address as $digit) |
115
|
|
|
{ |
116
|
17 |
|
if (is_string($digit) ) |
117
|
|
|
{ |
118
|
6 |
|
$digit = trim($digit); |
119
|
6 |
|
if (strlen($digit)==0) |
120
|
|
|
{ |
121
|
1 |
|
throw new InvalidArgumentException(sprintf("The array must contains only integers or strings with integer content , string found with empty value")); |
122
|
|
|
} |
123
|
5 |
|
if ( $digit[0] == '-' && ctype_digit(ltrim($digit, '-')) ) |
124
|
|
|
{ |
125
|
|
|
// string type : "-123" |
126
|
1 |
|
throw new OutOfBoundsException(sprintf("Cannot convert %d to Ipv4 addresss digit", $digit)); |
127
|
|
|
} |
128
|
4 |
|
if (!ctype_digit($digit)) |
129
|
|
|
{ |
130
|
1 |
|
throw new InvalidArgumentException(sprintf("The array must contains only integers or strings with integer content , string found with bad value %s", $digit)); |
131
|
|
|
} |
132
|
|
|
// Here the string contains only numbers, can be casted to int safely |
133
|
3 |
|
$digit = (int)$digit; |
134
|
|
|
} |
135
|
|
|
|
136
|
17 |
|
if (!is_integer($digit) ) |
137
|
|
|
{ |
138
|
1 |
|
throw new InvalidArgumentException(sprintf("The array must contains only integers or strings with integer content , %s found", gettype($digit))); |
139
|
|
|
} |
140
|
|
|
|
141
|
17 |
|
if($digit<0 || $digit>0xff) |
142
|
|
|
{ |
143
|
5 |
|
throw new OutOfBoundsException(sprintf("Cannot convert %d to Ipv4 addresss digit", $digit)); |
144
|
|
|
|
145
|
|
|
} |
146
|
|
|
// shift adress left from one byte (8 bits), then add digit (the last byte) |
147
|
16 |
|
$buffer = ($buffer << 8) | $digit; |
148
|
|
|
} |
149
|
8 |
|
return static::fromInteger($buffer); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Creates an instance of Address from a dotted quad formated string ("#.#.#.#"") |
154
|
|
|
* |
155
|
|
|
* @param string $address |
156
|
|
|
* @throws InvalidFormatException |
157
|
|
|
* @return self |
158
|
|
|
*/ |
159
|
22 |
|
public static function fromString(string $address) |
160
|
|
|
{ |
161
|
22 |
|
return static::fromArray(explode('.', $address)); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Get the default 'dotted-quad' representation of the IPv4 address ("#.#.#.#") |
166
|
|
|
* |
167
|
|
|
* @return string |
168
|
|
|
*/ |
169
|
2 |
|
public function asDotQuad() |
170
|
|
|
{ |
171
|
2 |
|
return sprintf( |
172
|
2 |
|
"%u.%u.%u.%u", |
173
|
2 |
|
(($this->address & (0xff << 24)) >> 24), |
174
|
2 |
|
(($this->address & (0xff << 16)) >> 16), |
175
|
2 |
|
(($this->address & (0xff << 8)) >> 8), |
176
|
2 |
|
($this->address & 0xff) |
177
|
|
|
); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* Get the integer value of the IPv4 address between 0 to 0xffffffff |
182
|
|
|
* |
183
|
|
|
* @return int |
184
|
|
|
*/ |
185
|
|
|
|
186
|
5 |
|
public function asInteger() |
187
|
|
|
{ |
188
|
5 |
|
return $this->address; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Get the integer value of the IPv4 address between 0 to 0xffffffff |
193
|
|
|
* |
194
|
|
|
* Just a helper/short way to call asInteger() |
195
|
|
|
* |
196
|
|
|
* @see Address::asInteger() |
197
|
|
|
* @return int |
198
|
|
|
*/ |
199
|
|
|
|
200
|
5 |
|
public function int() |
201
|
|
|
{ |
202
|
5 |
|
return $this->asInteger(); |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Get the CIDR integer value (0-32) from the adress if possible (eg : "255.255.255.0" returns 24) |
207
|
|
|
* |
208
|
|
|
* @see Address::fromCidr() |
209
|
|
|
* @throws DomainException when the address cannot be converted to CIDR |
210
|
|
|
* @return integer A value beteween 0 to 32 |
211
|
|
|
*/ |
212
|
|
|
// public function asCidr() |
213
|
|
|
// { |
214
|
|
|
// // Pas très élégant.... |
215
|
|
|
// for ($cidr=32 ; $cidr>=0 ; $cidr--) |
216
|
|
|
// { |
217
|
|
|
// $n = (0xffffffff << (32 - $cidr)) & 0xffffffff; |
218
|
|
|
// if( $n == $this->address ) |
219
|
|
|
// { |
220
|
|
|
// return $cidr; |
221
|
|
|
// } |
222
|
|
|
// } |
223
|
|
|
// throw new DomainException(sprintf("Cannot convert address %s to CIDR, not a netmask", (string)$this)); |
224
|
|
|
// } |
225
|
|
|
|
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Get a string representation of address |
229
|
|
|
* |
230
|
|
|
* Automagically called when PHP needs to convert an address to a string. |
231
|
|
|
* I choose to return the default 'dotted-quad' representation of the IPv4 address ("#.#.#.#") |
232
|
|
|
* |
233
|
|
|
* @return string |
234
|
|
|
*/ |
235
|
2 |
|
public function __toString() |
236
|
|
|
{ |
237
|
2 |
|
return $this->asDotQuad(); |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* Tests if two addresses are equivalent (same value) |
242
|
|
|
* |
243
|
|
|
* @param self $address |
244
|
|
|
* @return bool |
245
|
|
|
*/ |
246
|
7 |
|
public function match(Address $address) |
247
|
|
|
{ |
248
|
7 |
|
return $this->int() == $address->int(); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
/** |
252
|
|
|
* Tells if the address is contained in a range |
253
|
|
|
* |
254
|
|
|
* Return true if the provided address is between the boundaries of the range. |
255
|
|
|
* |
256
|
|
|
* @param Range $container |
257
|
|
|
* @return bool |
258
|
|
|
*/ |
259
|
3 |
|
public function isIn(Range $container) |
260
|
|
|
{ |
261
|
3 |
|
return ($container->getLowerBound()->int()<=$this->int()) && ($this->int()<=$container->getUpperBound()->int()); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Get the class of the IP address (for obsolete classfull routing) |
267
|
|
|
* |
268
|
|
|
* @return string a constant CLASS_[A-E] |
269
|
|
|
*/ |
270
|
13 |
|
public function getClass() |
271
|
|
|
{ |
272
|
13 |
|
$higherOctet = $this->address >> 24; |
273
|
|
|
|
274
|
13 |
|
if (($higherOctet & 0x80) == 0) { |
275
|
5 |
|
return self::CLASS_A; |
276
|
|
|
} |
277
|
|
|
|
278
|
8 |
|
if (($higherOctet & 0xC0) == 0x80) { |
279
|
2 |
|
return self::CLASS_B; |
280
|
|
|
} |
281
|
|
|
|
282
|
6 |
|
if (($higherOctet & 0xE0) == 0xC0) { |
283
|
2 |
|
return self::CLASS_C; |
284
|
|
|
} |
285
|
|
|
|
286
|
4 |
|
if (($higherOctet & 0xF0) == 0xE0) { |
287
|
2 |
|
return self::CLASS_D; |
288
|
|
|
} |
289
|
|
|
|
290
|
2 |
|
if (($higherOctet & 0xF0) == 0xF0) { |
291
|
2 |
|
return self::CLASS_E; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
// Should never be triggered |
295
|
|
|
// @codeCoverageIgnoreStart |
296
|
|
|
throw new RuntimeException(); |
297
|
|
|
// @codeCoverageIgnoreEnd |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* Returns whether the address is part of multicast address range |
302
|
|
|
* |
303
|
|
|
* @return bool |
304
|
|
|
*/ |
305
|
8 |
|
public function isMulticast() |
306
|
|
|
{ |
307
|
8 |
|
return $this->getClass() === self::CLASS_D; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* Returns whether the address is part of the subnets defined in RFC 1918 |
312
|
|
|
* |
313
|
|
|
* RFC1918 defines privates, non Internet routables subnets for end users's privates network |
314
|
|
|
* This subnets are : |
315
|
|
|
* - 10.0.0.0/8 |
316
|
|
|
* - 172.16.0.0/12 |
317
|
|
|
* - 192.168.0.0/16 |
318
|
|
|
* This subnets should be used by end users to build their privates networks. |
319
|
|
|
* |
320
|
|
|
* @see https://tools.ietf.org/html/rfc1918 |
321
|
|
|
* @return bool |
322
|
|
|
*/ |
323
|
14 |
|
public function isRFC1918() |
324
|
|
|
{ |
325
|
|
|
/** @var $subnets Subnet[] */ |
326
|
|
|
$subnets = array( |
327
|
14 |
|
Subnet::fromCidr(Address::fromString("10.0.0.0"), 8), |
328
|
14 |
|
Subnet::fromCidr(Address::fromString("172.16.0.0"), 12), |
329
|
14 |
|
Subnet::fromCidr(Address::fromString("192.168.0.0"), 16), |
330
|
|
|
); |
331
|
14 |
|
foreach($subnets as $subnet) { |
332
|
14 |
|
if($subnet->contains($this)) { |
|
|
|
|
333
|
3 |
|
return true; |
334
|
|
|
} |
335
|
|
|
} |
336
|
11 |
|
return false; |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* Returns whether the address is part of the subnets defined in RFC 6598 |
341
|
|
|
* |
342
|
|
|
* RFC6598 defines a private, non Internet routable subnet for Carrir Grade Networks, mainly used by FAI or non end users network providers |
343
|
|
|
* This subnet is 100.64.0.0/10 |
344
|
|
|
* This subnet should not be used by end users |
345
|
|
|
* |
346
|
|
|
* @see https://tools.ietf.org/html/rfc6598 |
347
|
|
|
* @return bool |
348
|
|
|
*/ |
349
|
14 |
|
public function isRFC6598() |
350
|
|
|
{ |
351
|
14 |
|
$subnet = Subnet::fromCidr(Address::fromString("100.64.0.0"), 10); |
352
|
14 |
|
return $subnet->contains($this) ? true : false; |
|
|
|
|
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* Returns whether the address is part of non Internet routable subnets |
357
|
|
|
* |
358
|
|
|
* @todo improve: multicast, other on routables, ... ? |
359
|
|
|
* @return bool |
360
|
|
|
*/ |
361
|
14 |
|
public function isPrivate() |
362
|
|
|
{ |
363
|
14 |
|
return ($this->isRFC1918() || $this->isRFC6598()); |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
/** |
367
|
|
|
* Returns an address shifted by an amount of units |
368
|
|
|
* |
369
|
|
|
* NOTICE : A new instance of address is instanciated, does not shitf the instance used. |
370
|
|
|
* |
371
|
|
|
* @param int $offset |
372
|
|
|
* @throws OutOfBoundsException when the resulting address is out of the bounds (negative or greater than 32 bits value) |
373
|
|
|
* @return self |
374
|
|
|
*/ |
375
|
14 |
|
public function shift(int $offset) |
376
|
|
|
{ |
377
|
14 |
|
return new static($this->address+$offset); |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
/** |
381
|
|
|
* Returns the address immediately following this address |
382
|
|
|
* |
383
|
|
|
* Examples : |
384
|
|
|
* - "1.2.3.4" => "1.2.3.5" |
385
|
|
|
* - "1.2.3.255" => "1.2.4.0" |
386
|
|
|
* |
387
|
|
|
* NOTICE : A new instance of address is instanciated, does not modify the instance used. |
388
|
|
|
* |
389
|
|
|
* @throws OutOfBoundsException when the resulting address is out of the bounds (more than 32 bits value). Happend only if try to get next of "255.255.255.255" |
390
|
|
|
* @return self |
391
|
|
|
*/ |
392
|
11 |
|
public function next() |
393
|
|
|
{ |
394
|
11 |
|
return $this->shift(1); |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
/** |
398
|
|
|
* Returns the address immediately preceding this address |
399
|
|
|
* |
400
|
|
|
* Examples : |
401
|
|
|
* - "1.2.3.5" => "1.2.3.4" |
402
|
|
|
* - "1.2.4.0" => "1.2.3.255" |
403
|
|
|
* |
404
|
|
|
* NOTICE : A new instance of address is instanciated, does not modify the instance used. |
405
|
|
|
* |
406
|
|
|
* @throws OutOfBoundsException when the resulting address is out of the bounds (less than 0). Happend only if try to get previous of "0.0.0.0" |
407
|
|
|
* @return self |
408
|
|
|
*/ |
409
|
11 |
|
public function previous() |
410
|
|
|
{ |
411
|
11 |
|
return $this->shift(-1); |
412
|
|
|
} |
413
|
|
|
} |
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.