Skip to content

Commit 469b8ee

Browse files
authored
Merge pull request #81 from clue-labs/socks4a
Remove explicit SOCKS4a handling and merge into SOCKS4 protocol handling and remove explicit SOCKS4a handling and merge into SOCKS4 protocol handling
2 parents c7c7ba0 + 58ff8c4 commit 469b8ee

6 files changed

Lines changed: 66 additions & 194 deletions

File tree

README.md

Lines changed: 45 additions & 66 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.
@@ -22,7 +24,6 @@ of the actual application level protocol, such as HTTP, SMTP, IMAP, Telnet etc.
2224
* [Unix domain sockets](#unix-domain-sockets)
2325
* [Server](#server)
2426
* [Server connector](#server-connector)
25-
* [Protocol version](#server-protocol-version)
2627
* [Authentication](#server-authentication)
2728
* [Proxy chaining](#server-proxy-chaining)
2829
* [SOCKS over TLS](#server-socks-over-tls)
@@ -241,91 +242,91 @@ This works for both plain HTTP and SSL encrypted HTTPS requests.
241242

242243
#### Protocol version
243244

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.
245+
This library supports the SOCKS5 and SOCKS4(a) protocol versions.
246+
It focuses on the most commonly used core feature of connecting to a destination
247+
host through the SOCKS proxy server. In this mode, a SOCKS proxy server acts as
248+
a generic proxy allowing higher level application protocols to work through it.
251249

252250
<table>
253251
<tr>
254252
<th></th>
255-
<th>SOCKS4</th>
256-
<th>SOCKS4a</th>
257253
<th>SOCKS5</th>
254+
<th>SOCKS4(a)</th>
258255
</tr>
259256
<tr>
260257
<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>
263258
<td><a href="http://tools.ietf.org/html/rfc1928">RFC 1928</a></td>
259+
<td>
260+
<a href="http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol">SOCKS4.protocol</a> /
261+
<a href="http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4A.protocol">SOCKS4A.protocol</a>
262+
</td>
264263
</tr>
265264
<tr>
266-
<th>Tunnel outgoing TCP connections</th>
267-
<td>✓</td>
265+
<th>Tunnel outgoing TCP/IP connections</th>
268266
<td>✓</td>
269267
<td>✓</td>
270268
</tr>
271269
<tr>
272-
<th><a href="#dns-resolution">Remote DNS resolving</a></th>
273-
<td>✗</td>
274-
<td>✓</td>
270+
<th><a href="#dns-resolution">Remote DNS resolution</a></th>
275271
<td>✓</td>
272+
<td>✗ / ✓</td>
276273
</tr>
277274
<tr>
278275
<th>IPv6 addresses</th>
279-
<td>✗</td>
280-
<td>✗</td>
281276
<td>✓</td>
277+
<td>✗</td>
282278
</tr>
283279
<tr>
284280
<th><a href="#authentication">Username/Password authentication</a></th>
285-
<td>✗</td>
286-
<td>✗</td>
287281
<td>✓ (as per <a href="http://tools.ietf.org/html/rfc1929">RFC 1929</a>)</td>
282+
<td>✗</td>
288283
</tr>
289284
<tr>
290285
<th>Handshake # roundtrips</th>
291-
<td>1</td>
292-
<td>1</td>
293286
<td>2 (3 with authentication)</td>
287+
<td>1</td>
294288
</tr>
295289
<tr>
296290
<th>Handshake traffic<br />+ remote DNS</th>
297-
<td>17 bytes<br />✗</td>
298-
<td>17 bytes<br />+ hostname + 1</td>
299291
<td><em>variable</em> (+ auth + IPv6)<br />+ hostname - 3</td>
292+
<td>17 bytes<br />+ hostname + 1</td>
293+
</tr>
294+
<tr>
295+
<th>Incoming BIND requests</th>
296+
<td><em>not implemented</em></td>
297+
<td><em>not implemented</em></td>
298+
</tr>
299+
<tr>
300+
<th>UDP datagrams</th>
301+
<td><em>not implemented</em></td>
302+
<td>✗</td>
303+
</tr>
304+
<tr>
305+
<th>GSSAPI authentication</th>
306+
<td><em>not implemented</em></td>
307+
<td>✗</td>
300308
</tr>
301309
</table>
302310

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-
306311
By default, the `Client` communicates via SOCKS5 with the SOCKS server.
307312
This is done because SOCKS5 is the latest version from the SOCKS protocol family
308313
and generally has best support across other vendors.
309314

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:
315+
If want to explicitly set the protocol version to SOCKS4(a), you can use the URI
316+
scheme `socks4://` as part of the SOCKS URI:
312317

313318
```php
314-
$client = new Client('socks4a://127.0.0.1', $connector);
319+
$client = new Client('socks4://127.0.0.1', $connector);
315320
```
316321

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-
321322
#### DNS resolution
322323

323324
By default, the `Client` does not perform any DNS resolution at all and simply
324325
forwards any hostname you're trying to connect to to the SOCKS server.
325326
The remote SOCKS server is thus responsible for looking up any hostnames via DNS
326327
(this default mode is thus called *remote DNS resolution*).
327328
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.
329+
not the original SOCKS4 protocol, as the protocol lacks a way to communicate hostnames.
329330

330331
On the other hand, all SOCKS protocol versions support sending destination IP
331332
addresses to the SOCKS server.
@@ -372,12 +373,6 @@ as usual.
372373
> Note how local DNS resolution is in fact entirely handled outside of this
373374
SOCKS client implementation.
374375

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-
381376
#### Authentication
382377

383378
This library supports username/password authentication for SOCKS5 servers as
@@ -617,6 +612,7 @@ $client = new Client('socks+unix://user:pass@/tmp/proxy.sock', new Connector($lo
617612

618613
The `Server` is responsible for accepting incoming communication from SOCKS clients
619614
and forwarding the requested connection to the target host.
615+
It supports the SOCKS5 and SOCKS4(a) protocol versions by default.
620616
It also registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage)
621617
and an underlying TCP/IP socket server like this:
622618

@@ -666,23 +662,6 @@ You can use this parameter for logging purposes or to restrict connection
666662
requests for certain clients by providing a custom implementation of the
667663
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface).
668664

669-
#### Server protocol version
670-
671-
The `Server` supports all protocol versions (SOCKS4, SOCKS4a and SOCKS5) by default.
672-
673-
If want to explicitly set the protocol version, use the supported values `4`, `4a` or `5`:
674-
675-
```PHP
676-
$server->setProtocolVersion(5);
677-
```
678-
679-
In order to reset the protocol version to its default (i.e. automatic detection),
680-
use `null` as protocol version.
681-
682-
```PHP
683-
$server->setProtocolVersion(null);
684-
```
685-
686665
#### Server authentication
687666

688667
By default, the `Server` does not require any authentication from the clients.
@@ -789,8 +768,8 @@ Proxy chaining can happen on the server side and/or the client side:
789768

790769
#### Server SOCKS over TLS
791770

792-
All [SOCKS protocol versions](#server-protocol-version) support forwarding TCP/IP
793-
based connections and higher level protocols.
771+
Both SOCKS5 and SOCKS4(a) protocol versions support forwarding TCP/IP based
772+
connections and higher level protocols.
794773
This implies that you can also use [secure TLS connections](#secure-tls-connections)
795774
to transfer sensitive data across SOCKS proxy servers.
796775
This means that no eavesdropper nor the proxy server will be able to decrypt
@@ -841,8 +820,8 @@ See also [example 31](examples).
841820

842821
#### Server Unix domain sockets
843822

844-
All [SOCKS protocol versions](#server-protocol-version) support forwarding TCP/IP
845-
based connections and higher level protocols.
823+
Both SOCKS5 and SOCKS4(a) protocol versions support forwarding TCP/IP based
824+
connections and higher level protocols.
846825
In some advanced cases, it may be useful to let your SOCKS server listen on a
847826
Unix domain socket (UDS) path instead of a IP:port combination.
848827
For example, this allows you to rely on file system permissions instead of
@@ -925,7 +904,7 @@ $client = new Client('socks+unix:///tmp/proxy.sock', $connector);
925904

926905
The [Tor anonymity network](http://www.torproject.org) client software is designed
927906
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
907+
It presents a SOCKS5 and SOCKS4(a) interface on TCP port 9050 by default
929908
which allows you to tunnel any traffic through the anonymity network.
930909
In most scenarios you probably don't want your client to resolve the target hostnames,
931910
because you would leak DNS information to anybody observing your local traffic.
@@ -984,7 +963,7 @@ MIT, see LICENSE
984963
* If you want to learn more about processing streams of data, refer to the
985964
documentation of the underlying
986965
[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
966+
* As an alternative to a SOCKS5 / SOCKS4(a) proxy, you may also want to look into
988967
using an HTTP CONNECT proxy instead.
989968
You may want to use [clue/reactphp-http-proxy](https://github.com/clue/reactphp-http-proxy)
990969
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: 8 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ final class Server
4141

4242
private $auth = null;
4343

44-
private $protocolVersion = null;
45-
4644
public function __construct(LoopInterface $loop, ConnectorInterface $connector = null)
4745
{
4846
if ($connector === null) {
@@ -65,28 +63,12 @@ public function listen(ServerInterface $socket)
6563
});
6664
}
6765

68-
public function setProtocolVersion($version)
69-
{
70-
if ($version !== null) {
71-
$version = (string)$version;
72-
if (!in_array($version, array('4', '4a', '5'), true)) {
73-
throw new InvalidArgumentException('Invalid protocol version given');
74-
}
75-
if ($version !== '5' && $this->auth !== null){
76-
throw new UnexpectedValueException('Unable to change protocol version to anything but SOCKS5 while authentication is used. Consider removing authentication info or sticking to SOCKS5');
77-
}
78-
}
79-
$this->protocolVersion = $version;
80-
}
81-
8266
public function setAuth($auth)
8367
{
8468
if (!is_callable($auth)) {
8569
throw new InvalidArgumentException('Given authenticator is not a valid callable');
8670
}
87-
if ($this->protocolVersion !== null && $this->protocolVersion !== '5') {
88-
throw new UnexpectedValueException('Authentication requires SOCKS5. Consider using protocol version 5 or waive authentication');
89-
}
71+
9072
// wrap authentication callback in order to cast its return value to a promise
9173
$this->auth = function($username, $password, $remote) use ($auth) {
9274
$ret = call_user_func($auth, $username, $password, $remote);
@@ -163,38 +145,24 @@ private function handleSocks(ConnectionInterface $stream)
163145
$stream->on('data', array($reader, 'write'));
164146

165147
$that = $this;
166-
$that = $this;
167-
168148
$auth = $this->auth;
169-
$protocolVersion = $this->protocolVersion;
170149

171-
// authentication requires SOCKS5
172-
if ($auth !== null) {
173-
$protocolVersion = '5';
174-
}
175-
176-
return $reader->readByte()->then(function ($version) use ($stream, $that, $protocolVersion, $auth, $reader){
150+
return $reader->readByte()->then(function ($version) use ($stream, $that, $auth, $reader){
177151
if ($version === 0x04) {
178-
if ($protocolVersion === '5') {
179-
throw new UnexpectedValueException('SOCKS4 not allowed due to configuration');
152+
if ($auth !== null) {
153+
throw new UnexpectedValueException('SOCKS4 not allowed because authentication is required');
180154
}
181-
return $that->handleSocks4($stream, $protocolVersion, $reader);
155+
return $that->handleSocks4($stream, $reader);
182156
} else if ($version === 0x05) {
183-
if ($protocolVersion !== null && $protocolVersion !== '5') {
184-
throw new UnexpectedValueException('SOCKS5 not allowed due to configuration');
185-
}
186157
return $that->handleSocks5($stream, $auth, $reader);
187158
}
188159
throw new UnexpectedValueException('Unexpected/unknown version number');
189160
});
190161
}
191162

192163
/** @internal */
193-
public function handleSocks4(ConnectionInterface $stream, $protocolVersion, StreamReader $reader)
164+
public function handleSocks4(ConnectionInterface $stream, StreamReader $reader)
194165
{
195-
// suppliying hostnames is only allowed for SOCKS4a (or automatically detected version)
196-
$supportsHostname = ($protocolVersion === null || $protocolVersion === '4a');
197-
198166
$remote = $stream->getRemoteAddress();
199167
if ($remote !== null) {
200168
// remove transport scheme and prefix socks4:// instead
@@ -212,7 +180,7 @@ public function handleSocks4(ConnectionInterface $stream, $protocolVersion, Stre
212180
'ipLong' => 'N',
213181
'null' => 'C'
214182
));
215-
})->then(function ($data) use ($reader, $supportsHostname, $remote) {
183+
})->then(function ($data) use ($reader, $remote) {
216184
if ($data['null'] !== 0x00) {
217185
throw new Exception('Not a null byte');
218186
}
@@ -222,7 +190,7 @@ public function handleSocks4(ConnectionInterface $stream, $protocolVersion, Stre
222190
if ($data['port'] === 0) {
223191
throw new Exception('Invalid port');
224192
}
225-
if ($data['ipLong'] < 256 && $supportsHostname) {
193+
if ($data['ipLong'] < 256) {
226194
// invalid IP => probably a SOCKS4a request which appends the hostname
227195
return $reader->readStringNull()->then(function ($string) use ($data, $remote){
228196
return array($string, $data['port'], $remote);

0 commit comments

Comments
 (0)