Skip to content

Commit 3e2e295

Browse files
committed
Remove explicit SOCKS4a handling and merge into SOCKS4 protocol handling
1 parent 60adee7 commit 3e2e295

6 files changed

Lines changed: 57 additions & 80 deletions

File tree

README.md

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# clue/reactphp-socks [![Build Status](https://travis-ci.org/clue/reactphp-socks.svg?branch=master)](https://travis-ci.org/clue/reactphp-socks)
22

3-
Async SOCKS4, SOCKS4a and SOCKS5 proxy client and server implementation, built on top of [ReactPHP](http://reactphp.org).
3+
Async SOCKS proxy connector client and server implementation, use any TCP/IP-based
4+
protocol through a SOCKS5 or SOCKS4(a) proxy server, built on top of
5+
[ReactPHP](https://reactphp.org).
46

57
The SOCKS protocol family can be used to easily tunnel TCP connections independent
68
of the actual application level protocol, such as HTTP, SMTP, IMAP, Telnet etc.
@@ -241,91 +243,91 @@ This works for both plain HTTP and SSL encrypted HTTPS requests.
241243

242244
#### Protocol version
243245

244-
This library supports the SOCKS4, SOCKS4a and SOCKS5 protocol versions.
245-
246-
While SOCKS4 already had (a somewhat limited) support for `SOCKS BIND` requests
247-
and SOCKS5 added generic UDP support (`SOCKS UDPASSOCIATE`), this library
248-
focuses on the most commonly used core feature of `SOCKS CONNECT`.
249-
In this mode, a SOCKS server acts as a generic proxy allowing higher level
250-
application protocols to work through it.
246+
This library supports the SOCKS5 and SOCKS4(a) protocol versions.
247+
It focuses on the most commonly used core feature of connecting to a destination
248+
host through the SOCKS proxy server. In this mode, a SOCKS proxy server acts as
249+
a generic proxy allowing higher level application protocols to work through it.
251250

252251
<table>
253252
<tr>
254253
<th></th>
255-
<th>SOCKS4</th>
256-
<th>SOCKS4a</th>
257254
<th>SOCKS5</th>
255+
<th>SOCKS4(a)</th>
258256
</tr>
259257
<tr>
260258
<th>Protocol specification</th>
261-
<td><a href="http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol">SOCKS4.protocol</a></td>
262-
<td><a href="http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4A.protocol">SOCKS4A.protocol</a></td>
263259
<td><a href="http://tools.ietf.org/html/rfc1928">RFC 1928</a></td>
260+
<td>
261+
<a href="http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol">SOCKS4.protocol</a> /
262+
<a href="http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4A.protocol">SOCKS4A.protocol</a>
263+
</td>
264264
</tr>
265265
<tr>
266-
<th>Tunnel outgoing TCP connections</th>
267-
<td>✓</td>
266+
<th>Tunnel outgoing TCP/IP connections</th>
268267
<td>✓</td>
269268
<td>✓</td>
270269
</tr>
271270
<tr>
272-
<th><a href="#dns-resolution">Remote DNS resolving</a></th>
273-
<td>✗</td>
274-
<td>✓</td>
271+
<th><a href="#dns-resolution">Remote DNS resolution</a></th>
275272
<td>✓</td>
273+
<td>✗ / ✓</td>
276274
</tr>
277275
<tr>
278276
<th>IPv6 addresses</th>
279-
<td>✗</td>
280-
<td>✗</td>
281277
<td>✓</td>
278+
<td>✗</td>
282279
</tr>
283280
<tr>
284281
<th><a href="#authentication">Username/Password authentication</a></th>
285-
<td>✗</td>
286-
<td>✗</td>
287282
<td>✓ (as per <a href="http://tools.ietf.org/html/rfc1929">RFC 1929</a>)</td>
283+
<td>✗</td>
288284
</tr>
289285
<tr>
290286
<th>Handshake # roundtrips</th>
291-
<td>1</td>
292-
<td>1</td>
293287
<td>2 (3 with authentication)</td>
288+
<td>1</td>
294289
</tr>
295290
<tr>
296291
<th>Handshake traffic<br />+ remote DNS</th>
297-
<td>17 bytes<br />✗</td>
298-
<td>17 bytes<br />+ hostname + 1</td>
299292
<td><em>variable</em> (+ auth + IPv6)<br />+ hostname - 3</td>
293+
<td>17 bytes<br />+ hostname + 1</td>
294+
</tr>
295+
<tr>
296+
<th>Incoming BIND requests</th>
297+
<td><em>not implemented</em></td>
298+
<td><em>not implemented</em></td>
299+
</tr>
300+
<tr>
301+
<th>UDP datagrams</th>
302+
<td><em>not implemented</em></td>
303+
<td>✗</td>
304+
</tr>
305+
<tr>
306+
<th>GSSAPI authentication</th>
307+
<td><em>not implemented</em></td>
308+
<td>✗</td>
300309
</tr>
301310
</table>
302311

303-
Note, this is __not__ a full SOCKS5 implementation due to missing GSSAPI
304-
authentication (but it's unlikely you're going to miss it anyway).
305-
306312
By default, the `Client` communicates via SOCKS5 with the SOCKS server.
307313
This is done because SOCKS5 is the latest version from the SOCKS protocol family
308314
and generally has best support across other vendors.
309315

310-
If want to explicitly set the protocol version, use the supported values URI
311-
schemes `socks4://` or `socks4a://`as part of the SOCKS URI:
316+
If want to explicitly set the protocol version to SOCKS4(a), you can use the URI
317+
scheme `socks4://` as part of the SOCKS URI:
312318

313319
```php
314-
$client = new Client('socks4a://127.0.0.1', $connector);
320+
$client = new Client('socks4://127.0.0.1', $connector);
315321
```
316322

317-
As seen above, both SOCKS5 and SOCKS4a support remote and local DNS resolution.
318-
If you've explicitly set this to SOCKS4, then you may want to check the following
319-
chapter about local DNS resolution or you may only connect to IPv4 addresses.
320-
321323
#### DNS resolution
322324

323325
By default, the `Client` does not perform any DNS resolution at all and simply
324326
forwards any hostname you're trying to connect to to the SOCKS server.
325327
The remote SOCKS server is thus responsible for looking up any hostnames via DNS
326328
(this default mode is thus called *remote DNS resolution*).
327329
As seen above, this mode is supported by the SOCKS5 and SOCKS4a protocols, but
328-
not the SOCKS4 protocol, as the protocol lacks a way to communicate hostnames.
330+
not the original SOCKS4 protocol, as the protocol lacks a way to communicate hostnames.
329331

330332
On the other hand, all SOCKS protocol versions support sending destination IP
331333
addresses to the SOCKS server.
@@ -372,12 +374,6 @@ as usual.
372374
> Note how local DNS resolution is in fact entirely handled outside of this
373375
SOCKS client implementation.
374376

375-
If you've explicitly set the client to SOCKS4 and stick to the default
376-
*remote DNS resolution*, then you may only connect to IPv4 addresses because
377-
the protocol lacks a way to communicate hostnames.
378-
If you try to connect to a hostname despite, the resulting promise will be
379-
rejected right away.
380-
381377
#### Authentication
382378

383379
This library supports username/password authentication for SOCKS5 servers as
@@ -668,9 +664,9 @@ requests for certain clients by providing a custom implementation of the
668664

669665
#### Server protocol version
670666

671-
The `Server` supports all protocol versions (SOCKS4, SOCKS4a and SOCKS5) by default.
667+
The `Server` supports the SOCKS5 and SOCKS4(a) protocol versions by default.
672668

673-
If want to explicitly set the protocol version, use the supported values `4`, `4a` or `5`:
669+
If want to explicitly set the protocol version, use the supported values `4`, or `5`:
674670

675671
```PHP
676672
$server->setProtocolVersion(5);
@@ -925,7 +921,7 @@ $client = new Client('socks+unix:///tmp/proxy.sock', $connector);
925921

926922
The [Tor anonymity network](http://www.torproject.org) client software is designed
927923
to encrypt your traffic and route it over a network of several nodes to conceal its origin.
928-
It presents a SOCKS4 and SOCKS5 interface on TCP port 9050 by default
924+
It presents a SOCKS5 and SOCKS4(a) interface on TCP port 9050 by default
929925
which allows you to tunnel any traffic through the anonymity network.
930926
In most scenarios you probably don't want your client to resolve the target hostnames,
931927
because you would leak DNS information to anybody observing your local traffic.
@@ -984,7 +980,7 @@ MIT, see LICENSE
984980
* If you want to learn more about processing streams of data, refer to the
985981
documentation of the underlying
986982
[react/stream](https://github.com/reactphp/stream) component.
987-
* As an alternative to a SOCKS (SOCKS4/SOCKS5) proxy, you may also want to look into
983+
* As an alternative to a SOCKS5 / SOCKS4(a) proxy, you may also want to look into
988984
using an HTTP CONNECT proxy instead.
989985
You may want to use [clue/reactphp-http-proxy](https://github.com/clue/reactphp-http-proxy)
990986
which also provides an implementation of the same

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "clue/socks-react",
3-
"description": "Async SOCKS4, SOCKS4a and SOCKS5 proxy client and server implementation, built on top of ReactPHP",
4-
"keywords": ["socks client", "socks server", "proxy", "tcp tunnel", "socks protocol", "async", "ReactPHP"],
3+
"description": "Async SOCKS proxy connector client and server implementation, use any TCP/IP-based protocol through a SOCKS5 or SOCKS4(a) proxy server, built on top of ReactPHP.",
4+
"keywords": ["socks client", "socks server", "socks5", "socks4a", "proxy server", "tcp tunnel", "async", "ReactPHP"],
55
"homepage": "https://github.com/clue/reactphp-socks",
66
"license": "MIT",
77
"authors": [

src/Client.php

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ final class Client implements ConnectorInterface
2222

2323
private $socksUri;
2424

25-
private $protocolVersion = '5';
25+
private $protocolVersion = 5;
2626

2727
private $auth = null;
2828

2929
public function __construct($socksUri, ConnectorInterface $connector)
3030
{
3131
// support `sockss://` scheme for SOCKS over TLS
3232
// support `socks+unix://` scheme for Unix domain socket (UDS) paths
33-
if (preg_match('/^(socks(?:5|4|4a)?)(s|\+unix):\/\/(.*?@)?(.+?)$/', $socksUri, $match)) {
33+
if (preg_match('/^(socks(?:5|4)?)(s|\+unix):\/\/(.*?@)?(.+?)$/', $socksUri, $match)) {
3434
// rewrite URI to parse SOCKS scheme, authentication and dummy host
3535
$socksUri = $match[1] . '://' . $match[3] . 'localhost';
3636

@@ -77,11 +77,9 @@ public function __construct($socksUri, ConnectorInterface $connector)
7777
private function setProtocolVersionFromScheme($scheme)
7878
{
7979
if ($scheme === 'socks' || $scheme === 'socks5') {
80-
$this->protocolVersion = '5';
81-
} elseif ($scheme === 'socks4a') {
82-
$this->protocolVersion = '4a';
80+
$this->protocolVersion = 5;
8381
} elseif ($scheme === 'socks4') {
84-
$this->protocolVersion = '4';
82+
$this->protocolVersion = 4;
8583
} else {
8684
throw new InvalidArgumentException('Invalid protocol version given "' . $scheme . '://"');
8785
}
@@ -128,10 +126,6 @@ public function connect($uri)
128126
$host = trim($parts['host'], '[]');
129127
$port = $parts['port'];
130128

131-
if ($this->protocolVersion === '4' && false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
132-
return Promise\reject(new InvalidArgumentException('Requires an IPv4 address for SOCKS4'));
133-
}
134-
135129
if (strlen($host) > 255 || $port > 65535 || $port < 0 || (string)$port !== (string)(int)$port) {
136130
return Promise\reject(new InvalidArgumentException('Invalid target specified'));
137131
}
@@ -204,7 +198,7 @@ public function handleConnectedSocks(ConnectionInterface $stream, $host, $port)
204198
$deferred->reject(new RuntimeException('Connection to proxy lost while waiting for response (ECONNRESET)', defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104));
205199
});
206200

207-
if ($this->protocolVersion === '5') {
201+
if ($this->protocolVersion === 5) {
208202
$promise = $this->handleSocks5($stream, $host, $port, $reader);
209203
} else {
210204
$promise = $this->handleSocks4($stream, $host, $port, $reader);

src/Server.php

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public function setProtocolVersion($version)
6969
{
7070
if ($version !== null) {
7171
$version = (string)$version;
72-
if (!in_array($version, array('4', '4a', '5'), true)) {
72+
if (!in_array($version, array('4', '5'), true)) {
7373
throw new InvalidArgumentException('Invalid protocol version given');
7474
}
7575
if ($version !== '5' && $this->auth !== null){
@@ -178,7 +178,7 @@ private function handleSocks(ConnectionInterface $stream)
178178
if ($protocolVersion === '5') {
179179
throw new UnexpectedValueException('SOCKS4 not allowed due to configuration');
180180
}
181-
return $that->handleSocks4($stream, $protocolVersion, $reader);
181+
return $that->handleSocks4($stream, $reader);
182182
} else if ($version === 0x05) {
183183
if ($protocolVersion !== null && $protocolVersion !== '5') {
184184
throw new UnexpectedValueException('SOCKS5 not allowed due to configuration');
@@ -190,11 +190,8 @@ private function handleSocks(ConnectionInterface $stream)
190190
}
191191

192192
/** @internal */
193-
public function handleSocks4(ConnectionInterface $stream, $protocolVersion, StreamReader $reader)
193+
public function handleSocks4(ConnectionInterface $stream, StreamReader $reader)
194194
{
195-
// suppliying hostnames is only allowed for SOCKS4a (or automatically detected version)
196-
$supportsHostname = ($protocolVersion === null || $protocolVersion === '4a');
197-
198195
$remote = $stream->getRemoteAddress();
199196
if ($remote !== null) {
200197
// remove transport scheme and prefix socks4:// instead
@@ -212,7 +209,7 @@ public function handleSocks4(ConnectionInterface $stream, $protocolVersion, Stre
212209
'ipLong' => 'N',
213210
'null' => 'C'
214211
));
215-
})->then(function ($data) use ($reader, $supportsHostname, $remote) {
212+
})->then(function ($data) use ($reader, $remote) {
216213
if ($data['null'] !== 0x00) {
217214
throw new Exception('Not a null byte');
218215
}
@@ -222,7 +219,7 @@ public function handleSocks4(ConnectionInterface $stream, $protocolVersion, Stre
222219
if ($data['port'] === 0) {
223220
throw new Exception('Invalid port');
224221
}
225-
if ($data['ipLong'] < 256 && $supportsHostname) {
222+
if ($data['ipLong'] < 256) {
226223
// invalid IP => probably a SOCKS4a request which appends the hostname
227224
return $reader->readStringNull()->then(function ($string) use ($data, $remote){
228225
return array($string, $data['port'], $remote);

tests/FunctionalTest.php

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ public function testConnectionWithIpViaSocks4()
5858
}
5959

6060
/** @group internet */
61-
public function testConnectionWithHostnameViaSocks4Fails()
61+
public function testConnectionWithHostnameViaSocks4a()
6262
{
6363
$this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
6464

65-
$this->assertRejectPromise($this->client->connect('www.google.com:80'));
65+
$this->assertResolveStream($this->client->connect('www.google.com:80'));
6666
}
6767

6868
/** @group internet */
@@ -78,15 +78,6 @@ public function testConnectionWithIpv6ViaSocks4Fails()
7878
$this->assertRejectPromise($this->client->connect('[::1]:80'));
7979
}
8080

81-
/** @group internet */
82-
public function testConnectionSocks4a()
83-
{
84-
$this->server->setProtocolVersion('4a');
85-
$this->client = new Client('socks4a://127.0.0.1:' . $this->port, $this->connector);
86-
87-
$this->assertResolveStream($this->client->connect('www.google.com:80'));
88-
}
89-
9081
/** @group internet */
9182
public function testConnectionSocks5()
9283
{
@@ -299,7 +290,7 @@ public function testConnectionAuthenticationUnused()
299290
public function testConnectionInvalidProtocolDoesNotMatchSocks5()
300291
{
301292
$this->server->setProtocolVersion(5);
302-
$this->client = new Client('socks4a://127.0.0.1:' . $this->port, $this->connector);
293+
$this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
303294

304295
$this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_ECONNRESET);
305296
}

tests/ServerTest.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ public function testListen()
3434
public function testSetProtocolVersion()
3535
{
3636
$this->server->setProtocolVersion(4);
37-
$this->server->setProtocolVersion('4a');
3837
$this->server->setProtocolVersion(5);
3938
$this->server->setProtocolVersion(null);
4039

0 commit comments

Comments
 (0)