|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* Copyright 2016 François Kooman <[email protected]>. |
|
4
|
|
|
* |
|
5
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
6
|
|
|
* you may not use this file except in compliance with the License. |
|
7
|
|
|
* You may obtain a copy of the License at |
|
8
|
|
|
* |
|
9
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
10
|
|
|
* |
|
11
|
|
|
* Unless required by applicable law or agreed to in writing, software |
|
12
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
13
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
14
|
|
|
* See the License for the specific language governing permissions and |
|
15
|
|
|
* limitations under the License. |
|
16
|
|
|
*/ |
|
17
|
|
|
|
|
18
|
|
|
namespace fkooman\VPN\Server; |
|
19
|
|
|
|
|
20
|
|
|
use InvalidArgumentException; |
|
21
|
|
|
|
|
22
|
|
|
class IP |
|
23
|
|
|
{ |
|
24
|
|
|
/** @var string */ |
|
25
|
|
|
private $ipAddress; |
|
26
|
|
|
|
|
27
|
|
|
/** @var int */ |
|
28
|
|
|
private $ipPrefix; |
|
29
|
|
|
|
|
30
|
|
|
/** @var int */ |
|
31
|
|
|
private $ipFamily; |
|
32
|
|
|
|
|
33
|
|
|
public function __construct($ipAddressPrefix) |
|
34
|
|
|
{ |
|
35
|
|
|
// detect if there is a prefix |
|
36
|
|
|
$hasPrefix = false !== strpos($ipAddressPrefix, '/'); |
|
37
|
|
|
if ($hasPrefix) { |
|
38
|
|
|
list($ipAddress, $ipPrefix) = explode('/', $ipAddressPrefix); |
|
39
|
|
|
} else { |
|
40
|
|
|
$ipAddress = $ipAddressPrefix; |
|
41
|
|
|
$ipPrefix = null; |
|
42
|
|
|
} |
|
43
|
|
|
|
|
44
|
|
|
// validate the IP address |
|
45
|
|
|
if (false === filter_var($ipAddress, FILTER_VALIDATE_IP)) { |
|
46
|
|
|
throw new IPException('invalid IP address'); |
|
47
|
|
|
} |
|
48
|
|
|
|
|
49
|
|
|
$is6 = false !== strpos($ipAddress, ':'); |
|
50
|
|
|
if ($is6) { |
|
51
|
|
|
if (is_null($ipPrefix)) { |
|
52
|
|
|
$ipPrefix = 128; |
|
53
|
|
|
} |
|
54
|
|
|
|
|
55
|
|
View Code Duplication |
if (!is_numeric($ipPrefix) || 0 > $ipPrefix || 128 < $ipPrefix) { |
|
|
|
|
|
|
56
|
|
|
throw new IPException('IP prefix must be a number between 0 and 128'); |
|
57
|
|
|
} |
|
58
|
|
|
// normalize the IPv6 address |
|
59
|
|
|
$ipAddress = inet_ntop(inet_pton($ipAddress)); |
|
60
|
|
|
} else { |
|
61
|
|
|
if (is_null($ipPrefix)) { |
|
62
|
|
|
$ipPrefix = 32; |
|
63
|
|
|
} |
|
64
|
|
View Code Duplication |
if (!is_numeric($ipPrefix) || 0 > $ipPrefix || 32 < $ipPrefix) { |
|
|
|
|
|
|
65
|
|
|
throw new IPException('IP prefix must be a number between 0 and 32'); |
|
66
|
|
|
} |
|
67
|
|
|
} |
|
68
|
|
|
|
|
69
|
|
|
$this->ipAddress = $ipAddress; |
|
70
|
|
|
$this->ipPrefix = (int) $ipPrefix; |
|
71
|
|
|
$this->ipFamily = $is6 ? 6 : 4; |
|
72
|
|
|
} |
|
73
|
|
|
|
|
74
|
|
|
public function getAddress() |
|
75
|
|
|
{ |
|
76
|
|
|
return $this->ipAddress; |
|
77
|
|
|
} |
|
78
|
|
|
|
|
79
|
|
|
public function getPrefix() |
|
80
|
|
|
{ |
|
81
|
|
|
return $this->ipPrefix; |
|
82
|
|
|
} |
|
83
|
|
|
|
|
84
|
|
|
public function getAddressPrefix() |
|
85
|
|
|
{ |
|
86
|
|
|
return sprintf('%s/%d', $this->getAddress(), $this->getPrefix()); |
|
87
|
|
|
} |
|
88
|
|
|
|
|
89
|
|
|
public function getFamily() |
|
90
|
|
|
{ |
|
91
|
|
|
return $this->ipFamily; |
|
92
|
|
|
} |
|
93
|
|
|
|
|
94
|
|
|
/** |
|
95
|
|
|
* IPv4 only. |
|
96
|
|
|
*/ |
|
97
|
|
View Code Duplication |
public function getNetmask() |
|
|
|
|
|
|
98
|
|
|
{ |
|
99
|
|
|
if (4 !== $this->getFamily()) { |
|
100
|
|
|
throw new IPException('method only for IPv4'); |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
|
|
return long2ip(-1 << (32 - $this->getPrefix())); |
|
104
|
|
|
} |
|
105
|
|
|
|
|
106
|
|
|
/** |
|
107
|
|
|
* IPv4 only. |
|
108
|
|
|
*/ |
|
109
|
|
|
public function getNetwork() |
|
110
|
|
|
{ |
|
111
|
|
|
if (4 !== $this->getFamily()) { |
|
112
|
|
|
throw new IPException('method only for IPv4'); |
|
113
|
|
|
} |
|
114
|
|
|
|
|
115
|
|
|
return long2ip(ip2long($this->getAddress()) & ip2long($this->getNetmask())); |
|
116
|
|
|
} |
|
117
|
|
|
|
|
118
|
|
|
/** |
|
119
|
|
|
* IPv4 only. |
|
120
|
|
|
*/ |
|
121
|
|
View Code Duplication |
public function getNumberOfHosts() |
|
|
|
|
|
|
122
|
|
|
{ |
|
123
|
|
|
if (4 !== $this->getFamily()) { |
|
124
|
|
|
throw new IPException('method only for IPv4'); |
|
125
|
|
|
} |
|
126
|
|
|
|
|
127
|
|
|
return pow(2, 32 - $this->getPrefix()) - 2; |
|
128
|
|
|
} |
|
129
|
|
|
|
|
130
|
|
|
public function split($networkCount) |
|
131
|
|
|
{ |
|
132
|
|
|
if (!is_int($networkCount)) { |
|
133
|
|
|
throw new InvalidArgumentException('parameter must be integer'); |
|
134
|
|
|
} |
|
135
|
|
|
|
|
136
|
|
|
if (0 !== ($networkCount & ($networkCount - 1))) { |
|
137
|
|
|
throw new InvalidArgumentException('parameter must be power of 2'); |
|
138
|
|
|
} |
|
139
|
|
|
|
|
140
|
|
|
if (4 === $this->getFamily()) { |
|
141
|
|
|
return $this->split4($networkCount); |
|
142
|
|
|
} |
|
143
|
|
|
|
|
144
|
|
|
return $this->split6($networkCount); |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
private function split4($networkCount) |
|
148
|
|
|
{ |
|
149
|
|
|
if (30 <= $this->getPrefix()) { |
|
150
|
|
|
throw new IPException('network too small to split up, must be bigger than /30'); |
|
151
|
|
|
} |
|
152
|
|
|
|
|
153
|
|
|
if (pow(2, 32 - $this->getPrefix() - 2) < $networkCount) { |
|
154
|
|
|
throw new IPException('network too small to split in this many networks'); |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
$prefix = $this->getPrefix() + log($networkCount, 2); |
|
158
|
|
|
$splitRanges = []; |
|
159
|
|
|
for ($i = 0; $i < $networkCount; ++$i) { |
|
160
|
|
|
$noHosts = pow(2, 32 - $prefix); |
|
161
|
|
|
$networkAddress = long2ip($i * $noHosts + ip2long($this->getAddress())); |
|
162
|
|
|
$splitRanges[] = new self($networkAddress.'/'.$prefix); |
|
163
|
|
|
} |
|
164
|
|
|
|
|
165
|
|
|
return $splitRanges; |
|
166
|
|
|
} |
|
167
|
|
|
|
|
168
|
|
|
private function split6($networkCount) |
|
169
|
|
|
{ |
|
170
|
|
|
if (64 <= $this->getPrefix()) { |
|
171
|
|
|
throw new IPException('network too small to split up, must be bigger than /64'); |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
|
|
if (0 !== $this->getPrefix() % 4) { |
|
175
|
|
|
throw new IPException('network prefix length must be divisible by 4'); |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
if (pow(2, 64 - $this->getPrefix()) < $networkCount) { |
|
179
|
|
|
throw new IPException('network too small to split in this many networks'); |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
|
|
$hexAddress = bin2hex(inet_pton($this->getAddress())); |
|
183
|
|
|
// strip the last digits based on prefix size |
|
184
|
|
|
$hexAddress = substr($hexAddress, 0, 16 - ((64 - $this->getPrefix()) / 4)); |
|
185
|
|
|
|
|
186
|
|
|
$splitRanges = []; |
|
187
|
|
|
for ($i = 0; $i < $networkCount; ++$i) { |
|
188
|
|
|
// pad with zeros until there is enough space for or network number |
|
189
|
|
|
$paddedHexAddress = str_pad($hexAddress, 16 - strlen(dechex($i)), '0'); |
|
190
|
|
|
// append the network number |
|
191
|
|
|
$hexAddressWithNetwork = $paddedHexAddress.dechex($i); |
|
192
|
|
|
// pad it to the end and convert back to IPv6 address |
|
193
|
|
|
$splitRanges[] = new self(sprintf('%s/64', inet_ntop(hex2bin(str_pad($hexAddressWithNetwork, 32, '0'))))); |
|
194
|
|
|
} |
|
195
|
|
|
|
|
196
|
|
|
return $splitRanges; |
|
197
|
|
|
} |
|
198
|
|
|
|
|
199
|
|
|
public function __toString() |
|
200
|
|
|
{ |
|
201
|
|
|
return $this->getAddressPrefix(); |
|
202
|
|
|
} |
|
203
|
|
|
} |
|
204
|
|
|
|
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.