1
|
|
|
<?php |
2
|
|
|
namespace SamIT\Proxy; |
3
|
|
|
|
4
|
|
|
class Header |
5
|
|
|
{ |
6
|
|
|
const CMD_PROXY = 1; |
7
|
|
|
const CMD_LOCAL = 0; |
8
|
|
|
const UNSPECIFIED_PROTOCOL = "\x00"; |
9
|
|
|
const TCP4 = "\x11"; |
10
|
|
|
const UDP4 = "\x12"; |
11
|
|
|
const TCP6 = "\x21"; |
12
|
|
|
const UDP6 = "\x22"; |
13
|
|
|
const USTREAM = "\x31"; |
14
|
|
|
const USOCK = "\x32"; |
15
|
|
|
|
16
|
|
|
// 12 bytes. |
17
|
|
|
protected static $signatures = [ |
18
|
|
|
2 => "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", |
19
|
|
|
1 => "PROXY" |
20
|
|
|
]; |
21
|
|
|
// 4 bits |
22
|
|
|
public $version = 2; |
23
|
|
|
// 4 bits |
24
|
|
|
public $command = self::CMD_PROXY; |
25
|
|
|
|
26
|
|
|
// 1 byte |
27
|
|
|
public $protocol = self::TCP4; |
28
|
|
|
|
29
|
|
|
protected static $lengths = [ |
30
|
|
|
self::TCP4 => 12, |
31
|
|
|
self::UDP4 => 12, |
32
|
|
|
self::TCP6 => 36, |
33
|
|
|
self::UDP6 => 36, |
34
|
|
|
self::USTREAM => 216, |
35
|
|
|
self::USOCK => 216, |
36
|
|
|
]; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var string The address of the client. |
40
|
|
|
*/ |
41
|
|
|
public $sourceAddress; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var string The address to which the client connected. |
45
|
|
|
*/ |
46
|
|
|
public $targetAddress; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var int The port of the client |
50
|
|
|
*/ |
51
|
|
|
public $sourcePort; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @var int The port to which the client connected. |
55
|
|
|
*/ |
56
|
|
|
public $targetPort; |
57
|
|
|
|
58
|
|
|
|
59
|
1 |
|
protected function getProtocol() |
60
|
|
|
{ |
61
|
1 |
|
if ($this->version == 2) { |
62
|
1 |
|
return $this->protocol; |
63
|
|
|
} else { |
64
|
|
|
return array_flip((new \ReflectionClass($this))->getConstants())[$this->protocol]; |
65
|
|
|
} |
66
|
|
|
} |
67
|
1 |
|
protected function getVersionCommand() { |
68
|
1 |
|
if ($this->version == 2) { |
69
|
1 |
|
return chr(($this->version << 4) + $this->command); |
70
|
|
|
} |
71
|
|
|
} |
72
|
|
|
/** |
73
|
|
|
* @return uint16_t |
74
|
|
|
*/ |
75
|
1 |
|
protected function getAddressLength() |
76
|
|
|
{ |
77
|
1 |
|
if ($this->version == 2) { |
78
|
1 |
|
return pack('n', self::$lengths[$this->protocol]); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
} |
82
|
|
|
|
83
|
1 |
View Code Duplication |
protected function encodeAddress($address, $protocol) { |
|
|
|
|
84
|
1 |
|
if ($this->version == 1) { |
85
|
|
|
return $address; |
86
|
|
|
} |
87
|
|
|
switch ($protocol) { |
88
|
1 |
|
case self::TCP4: |
89
|
1 |
|
case self::UDP4: |
90
|
1 |
|
case self::TCP6: |
91
|
1 |
|
case self::UDP6: |
92
|
1 |
|
$result = inet_pton($address); |
93
|
1 |
|
break; |
94
|
|
|
case self::USTREAM: |
95
|
|
|
case self::USOCK: |
96
|
|
|
throw new \Exception("Unix socket not (yet) supported."); |
97
|
|
|
default: |
98
|
|
|
throw new \UnexpectedValueException("Invalid protocol."); |
99
|
|
|
} |
100
|
1 |
|
return $result; |
101
|
|
|
} |
102
|
|
|
|
103
|
1 |
View Code Duplication |
protected static function decodeAddress($version, $address, $protocol) |
|
|
|
|
104
|
|
|
{ |
105
|
1 |
|
if ($version == 1) { |
106
|
|
|
return $address; |
107
|
|
|
} |
108
|
|
|
switch ($protocol) { |
109
|
1 |
|
case self::TCP4: |
110
|
1 |
|
case self::UDP4: |
111
|
1 |
|
case self::TCP6: |
112
|
1 |
|
case self::UDP6: |
113
|
1 |
|
$result = inet_ntop($address); |
114
|
1 |
|
break; |
115
|
|
|
case self::USTREAM: |
116
|
|
|
case self::USOCK: |
117
|
|
|
throw new \Exception("Unix socket not (yet) supported."); |
118
|
|
|
default: |
119
|
|
|
throw new \UnexpectedValueException("Invalid protocol."); |
120
|
|
|
|
121
|
|
|
} |
122
|
1 |
|
return $result; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* @return string |
127
|
|
|
* @throws \Exception |
128
|
|
|
*/ |
129
|
1 |
|
protected function getAddresses() |
130
|
|
|
{ |
131
|
1 |
|
return $this->encodeAddress($this->sourceAddress, $this->protocol) . ($this->version == 1 ? " " : "") .$this->encodeAddress($this->targetAddress, $this->protocol); |
132
|
|
|
} |
133
|
|
|
|
134
|
1 |
View Code Duplication |
protected function encodePort($port, $protocol) { |
|
|
|
|
135
|
1 |
|
if ($this->version == 1) { |
136
|
|
|
return $port; |
137
|
|
|
} |
138
|
|
|
switch ($protocol) { |
139
|
1 |
|
case self::TCP4: |
140
|
1 |
|
case self::UDP4: |
141
|
1 |
|
case self::TCP6: |
142
|
1 |
|
case self::UDP6: |
143
|
1 |
|
$result = pack('n', $port); |
144
|
1 |
|
break; |
145
|
|
|
case self::USTREAM: |
146
|
|
|
case self::USOCK: |
147
|
|
|
throw new \Exception("Unix socket not (yet) supported."); |
148
|
|
|
default: |
149
|
|
|
throw new \UnexpectedValueException("Invalid protocol."); |
150
|
|
|
|
151
|
|
|
} |
152
|
1 |
|
return $result; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* @return string |
157
|
|
|
* @throws \Exception |
158
|
|
|
*/ |
159
|
1 |
|
protected function getPorts() |
160
|
|
|
{ |
161
|
1 |
|
return $this->encodePort($this->sourcePort, $this->protocol) . ($this->version == 1 ? " " : "") . $this->encodePort($this->targetPort, $this->protocol); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* @return string |
166
|
|
|
*/ |
167
|
1 |
|
protected function getSignature() |
168
|
|
|
{ |
169
|
1 |
|
return self::$signatures[$this->version]; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* Constructs the header by concatenating all relevant fields. |
174
|
|
|
* @return string |
175
|
|
|
*/ |
176
|
1 |
|
public function constructProxyHeader() { |
177
|
1 |
|
return implode($this->version == 1 ? "\x20" : "", array_filter([ |
178
|
1 |
|
$this->getSignature(), |
179
|
1 |
|
$this->getVersionCommand(), |
180
|
1 |
|
$this->getProtocol(), |
181
|
1 |
|
$this->getAddressLength(), |
182
|
1 |
|
$this->getAddresses(), |
183
|
1 |
|
$this->getPorts(), |
184
|
1 |
|
$this->version == 1 ? "\r\n" : null |
185
|
1 |
|
])); |
186
|
|
|
} |
187
|
|
|
|
188
|
1 |
|
public function __toString() |
189
|
|
|
{ |
190
|
1 |
|
return $this->constructProxyHeader(); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* This function creates the forwarding header. This header should be sent over the upstream connection as soon as |
195
|
|
|
* it is established. |
196
|
|
|
* @param string $sourceAddress |
197
|
|
|
* @param int $sourcePort |
198
|
|
|
* @param string $targetAddress |
199
|
|
|
* @param int $targetPort |
200
|
|
|
* @return self |
201
|
|
|
* @throws \Exception |
202
|
|
|
*/ |
203
|
2 |
|
public static function createForward4($sourceAddress, $sourcePort, $targetAddress, $targetPort) { |
204
|
2 |
|
$result = new static(); |
205
|
2 |
|
$result->version = 2; |
206
|
2 |
|
$result->sourceAddress = $sourceAddress; |
207
|
2 |
|
$result->targetPort = $targetPort; |
208
|
2 |
|
$result->targetAddress = $targetAddress; |
209
|
2 |
|
$result->sourcePort = $sourcePort; |
210
|
2 |
|
return $result; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* @param string $data |
215
|
|
|
* @return Header|null |
216
|
|
|
*/ |
217
|
1 |
|
public static function parseHeader(&$data) |
218
|
|
|
{ |
219
|
1 |
|
foreach(self::$signatures as $version => $signature) { |
220
|
|
|
// Match. |
221
|
1 |
|
if (strncmp($data, $signature, strlen($signature)) === 0) { |
222
|
1 |
|
if ($version === 1) { |
223
|
|
|
$result = self::parseVersion1($data); |
224
|
|
|
break; |
225
|
1 |
|
} elseif ($version === 2) { |
226
|
1 |
|
$result = self::parseVersion2($data); |
227
|
1 |
|
break; |
228
|
|
|
} |
229
|
|
|
} |
230
|
1 |
|
} |
231
|
1 |
|
if (isset($result)) { |
232
|
1 |
|
$constructed = $result->constructProxyHeader(); |
233
|
1 |
|
if (strncmp($constructed, $data, strlen($constructed)) === 0) { |
234
|
1 |
|
$data = substr($data, strlen($constructed)); |
235
|
1 |
|
return $result; |
236
|
|
|
} |
237
|
|
|
} |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
protected static function parseVersion1($data) |
241
|
|
|
{ |
242
|
|
|
$parts = explode("\x20", $data); |
243
|
|
|
if (count($parts) === 7 && $parts[6] === "\r\n") { |
244
|
|
|
$result = new Header(); |
245
|
|
|
$result->version = 1; |
246
|
|
|
$result->protocol = $parts[1]; |
247
|
|
|
$result->sourceAddress = $parts[2]; |
248
|
|
|
$result->targetAddress = $parts[3]; |
249
|
|
|
$result->sourcePort = $parts[4]; |
250
|
|
|
$result->targetPort = $parts[5]; |
251
|
|
|
return $result; |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
|
255
|
1 |
|
protected static function parseVersion2($data) |
256
|
|
|
{ |
257
|
1 |
|
$version = ord(substr($data, 12, 1)) >> 4; |
258
|
1 |
|
$command = ord(substr($data, 12, 1)) % 16; |
259
|
1 |
|
$protocol = substr($data, 13, 1); |
260
|
|
|
|
261
|
1 |
|
$pos = 16; |
262
|
1 |
|
$sourceAddress = self::decodeAddress($version, substr($data, $pos, self::$lengths[$protocol] / 2 - 2), $protocol); |
263
|
1 |
|
$pos += self::$lengths[$protocol] / 2 - 2; |
264
|
1 |
|
$targetAddress = self::decodeAddress($version, substr($data, $pos, self::$lengths[$protocol] / 2 - 2), $protocol); |
265
|
1 |
|
$pos += self::$lengths[$protocol] / 2 - 2; |
266
|
1 |
|
$sourcePort = unpack('n', substr($data, $pos, 2))[1]; |
267
|
1 |
|
$targetPort = unpack('n', substr($data, $pos + 2, 2))[1]; |
268
|
|
|
|
269
|
1 |
|
$result = new Header(); |
270
|
1 |
|
$result->version = 2; |
271
|
1 |
|
$result->command = $command; |
272
|
1 |
|
$result->protocol = $protocol; |
273
|
1 |
|
$result->sourceAddress = $sourceAddress; |
274
|
1 |
|
$result->targetAddress = $targetAddress; |
275
|
1 |
|
$result->sourcePort = $sourcePort; |
276
|
1 |
|
$result->targetPort = $targetPort; |
277
|
1 |
|
return $result; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
} |
281
|
|
|
|
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.