|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* Copyright (C) 2016 SURFnet. |
|
4
|
|
|
* |
|
5
|
|
|
* This program is free software: you can redistribute it and/or modify |
|
6
|
|
|
* it under the terms of the GNU Affero General Public License as |
|
7
|
|
|
* published by the Free Software Foundation, either version 3 of the |
|
8
|
|
|
* License, or (at your option) any later version. |
|
9
|
|
|
* |
|
10
|
|
|
* This program is distributed in the hope that it will be useful, |
|
11
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13
|
|
|
* GNU Affero General Public License for more details. |
|
14
|
|
|
* |
|
15
|
|
|
* You should have received a copy of the GNU Affero General Public License |
|
16
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
17
|
|
|
*/ |
|
18
|
|
|
namespace SURFnet\VPN\Server; |
|
19
|
|
|
|
|
20
|
|
|
use InvalidArgumentException; |
|
21
|
|
|
use SURFnet\VPN\Server\Exception\IPException; |
|
22
|
|
|
|
|
23
|
|
|
class IP |
|
24
|
|
|
{ |
|
25
|
|
|
/** @var string */ |
|
26
|
|
|
private $ipAddress; |
|
27
|
|
|
|
|
28
|
|
|
/** @var int */ |
|
29
|
|
|
private $ipPrefix; |
|
30
|
|
|
|
|
31
|
|
|
/** @var int */ |
|
32
|
|
|
private $ipFamily; |
|
33
|
|
|
|
|
34
|
|
|
public function __construct($ipAddressPrefix) |
|
35
|
|
|
{ |
|
36
|
|
|
// detect if there is a prefix |
|
37
|
|
|
$hasPrefix = false !== strpos($ipAddressPrefix, '/'); |
|
38
|
|
|
if ($hasPrefix) { |
|
39
|
|
|
list($ipAddress, $ipPrefix) = explode('/', $ipAddressPrefix); |
|
40
|
|
|
} else { |
|
41
|
|
|
$ipAddress = $ipAddressPrefix; |
|
42
|
|
|
$ipPrefix = null; |
|
43
|
|
|
} |
|
44
|
|
|
|
|
45
|
|
|
// validate the IP address |
|
46
|
|
|
if (false === filter_var($ipAddress, FILTER_VALIDATE_IP)) { |
|
47
|
|
|
throw new IPException('invalid IP address'); |
|
48
|
|
|
} |
|
49
|
|
|
|
|
50
|
|
|
$is6 = false !== strpos($ipAddress, ':'); |
|
51
|
|
|
if ($is6) { |
|
52
|
|
|
if (is_null($ipPrefix)) { |
|
53
|
|
|
$ipPrefix = 128; |
|
54
|
|
|
} |
|
55
|
|
|
|
|
56
|
|
View Code Duplication |
if (!is_numeric($ipPrefix) || 0 > $ipPrefix || 128 < $ipPrefix) { |
|
|
|
|
|
|
57
|
|
|
throw new IPException('IP prefix must be a number between 0 and 128'); |
|
58
|
|
|
} |
|
59
|
|
|
// normalize the IPv6 address |
|
60
|
|
|
$ipAddress = inet_ntop(inet_pton($ipAddress)); |
|
61
|
|
|
} else { |
|
62
|
|
|
if (is_null($ipPrefix)) { |
|
63
|
|
|
$ipPrefix = 32; |
|
64
|
|
|
} |
|
65
|
|
View Code Duplication |
if (!is_numeric($ipPrefix) || 0 > $ipPrefix || 32 < $ipPrefix) { |
|
|
|
|
|
|
66
|
|
|
throw new IPException('IP prefix must be a number between 0 and 32'); |
|
67
|
|
|
} |
|
68
|
|
|
} |
|
69
|
|
|
|
|
70
|
|
|
$this->ipAddress = $ipAddress; |
|
71
|
|
|
$this->ipPrefix = (int) $ipPrefix; |
|
72
|
|
|
$this->ipFamily = $is6 ? 6 : 4; |
|
73
|
|
|
} |
|
74
|
|
|
|
|
75
|
|
|
public function getAddress() |
|
76
|
|
|
{ |
|
77
|
|
|
return $this->ipAddress; |
|
78
|
|
|
} |
|
79
|
|
|
|
|
80
|
|
|
public function getPrefix() |
|
81
|
|
|
{ |
|
82
|
|
|
return $this->ipPrefix; |
|
83
|
|
|
} |
|
84
|
|
|
|
|
85
|
|
|
public function getAddressPrefix() |
|
86
|
|
|
{ |
|
87
|
|
|
return sprintf('%s/%d', $this->getAddress(), $this->getPrefix()); |
|
88
|
|
|
} |
|
89
|
|
|
|
|
90
|
|
|
public function getFamily() |
|
91
|
|
|
{ |
|
92
|
|
|
return $this->ipFamily; |
|
93
|
|
|
} |
|
94
|
|
|
|
|
95
|
|
|
/** |
|
96
|
|
|
* IPv4 only. |
|
97
|
|
|
*/ |
|
98
|
|
|
public function getNetmask() |
|
99
|
|
|
{ |
|
100
|
|
|
$this->requireIPv4(); |
|
101
|
|
|
|
|
102
|
|
|
return long2ip(-1 << (32 - $this->getPrefix())); |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
/** |
|
106
|
|
|
* IPv4 only. |
|
107
|
|
|
*/ |
|
108
|
|
|
public function getNetwork() |
|
109
|
|
|
{ |
|
110
|
|
|
$this->requireIPv4(); |
|
111
|
|
|
|
|
112
|
|
|
return long2ip(ip2long($this->getAddress()) & ip2long($this->getNetmask())); |
|
113
|
|
|
} |
|
114
|
|
|
|
|
115
|
|
|
/** |
|
116
|
|
|
* IPv4 only. |
|
117
|
|
|
*/ |
|
118
|
|
|
public function getNumberOfHosts() |
|
119
|
|
|
{ |
|
120
|
|
|
$this->requireIPv4(); |
|
121
|
|
|
|
|
122
|
|
|
return pow(2, 32 - $this->getPrefix()) - 2; |
|
123
|
|
|
} |
|
124
|
|
|
|
|
125
|
|
|
public function split($networkCount) |
|
126
|
|
|
{ |
|
127
|
|
|
if (!is_int($networkCount)) { |
|
128
|
|
|
throw new InvalidArgumentException('parameter must be integer'); |
|
129
|
|
|
} |
|
130
|
|
|
|
|
131
|
|
|
if (0 !== ($networkCount & ($networkCount - 1))) { |
|
132
|
|
|
throw new InvalidArgumentException('parameter must be power of 2'); |
|
133
|
|
|
} |
|
134
|
|
|
|
|
135
|
|
|
if (4 === $this->getFamily()) { |
|
136
|
|
|
return $this->split4($networkCount); |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
return $this->split6($networkCount); |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
private function split4($networkCount) |
|
143
|
|
|
{ |
|
144
|
|
|
if (pow(2, 32 - $this->getPrefix() - 2) < $networkCount) { |
|
145
|
|
|
throw new IPException('network too small to split in this many networks'); |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
$prefix = $this->getPrefix() + log($networkCount, 2); |
|
149
|
|
|
$splitRanges = []; |
|
150
|
|
|
for ($i = 0; $i < $networkCount; ++$i) { |
|
151
|
|
|
$noHosts = pow(2, 32 - $prefix); |
|
152
|
|
|
$networkAddress = long2ip($i * $noHosts + ip2long($this->getAddress())); |
|
153
|
|
|
$splitRanges[] = new self($networkAddress.'/'.$prefix); |
|
154
|
|
|
} |
|
155
|
|
|
|
|
156
|
|
|
return $splitRanges; |
|
157
|
|
|
} |
|
158
|
|
|
|
|
159
|
|
|
private function split6($networkCount) |
|
160
|
|
|
{ |
|
161
|
|
|
// NOTE: if networkCount == 1, then there will be one /64 returned, and not |
|
162
|
|
|
// the whole net! |
|
163
|
|
|
if (64 <= $this->getPrefix()) { |
|
164
|
|
|
throw new IPException('network too small to split up, must be bigger than /64'); |
|
165
|
|
|
} |
|
166
|
|
|
|
|
167
|
|
|
if (0 !== $this->getPrefix() % 4) { |
|
168
|
|
|
throw new IPException('network prefix length must be divisible by 4'); |
|
169
|
|
|
} |
|
170
|
|
|
|
|
171
|
|
|
if (pow(2, 64 - $this->getPrefix()) < $networkCount) { |
|
172
|
|
|
throw new IPException('network too small to split in this many networks'); |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
$hexAddress = bin2hex(inet_pton($this->getAddress())); |
|
176
|
|
|
// strip the last digits based on prefix size |
|
177
|
|
|
$hexAddress = substr($hexAddress, 0, 16 - ((64 - $this->getPrefix()) / 4)); |
|
178
|
|
|
|
|
179
|
|
|
$splitRanges = []; |
|
180
|
|
|
for ($i = 0; $i < $networkCount; ++$i) { |
|
181
|
|
|
// pad with zeros until there is enough space for or network number |
|
182
|
|
|
$paddedHexAddress = str_pad($hexAddress, 16 - strlen(dechex($i)), '0'); |
|
183
|
|
|
// append the network number |
|
184
|
|
|
$hexAddressWithNetwork = $paddedHexAddress.dechex($i); |
|
185
|
|
|
// pad it to the end and convert back to IPv6 address |
|
186
|
|
|
$splitRanges[] = new self(sprintf('%s/64', inet_ntop(hex2bin(str_pad($hexAddressWithNetwork, 32, '0'))))); |
|
187
|
|
|
} |
|
188
|
|
|
|
|
189
|
|
|
return $splitRanges; |
|
190
|
|
|
} |
|
191
|
|
|
|
|
192
|
|
|
public function __toString() |
|
193
|
|
|
{ |
|
194
|
|
|
return $this->getAddressPrefix(); |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
private function requireIPv4() |
|
198
|
|
|
{ |
|
199
|
|
|
if (4 !== $this->getFamily()) { |
|
200
|
|
|
throw new IPException('method only for IPv4'); |
|
201
|
|
|
} |
|
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.