diff --git a/README.md b/README.md
index 4c7ef0d..4607a8c 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
# clue/reactphp-socks [](https://travis-ci.org/clue/reactphp-socks)
-Async SOCKS4, SOCKS4a and SOCKS5 proxy client and server implementation, built on top of [ReactPHP](http://reactphp.org).
+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](https://reactphp.org).
The SOCKS protocol family can be used to easily tunnel TCP connections independent
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.
* [Unix domain sockets](#unix-domain-sockets)
* [Server](#server)
* [Server connector](#server-connector)
- * [Protocol version](#server-protocol-version)
* [Authentication](#server-authentication)
* [Proxy chaining](#server-proxy-chaining)
* [SOCKS over TLS](#server-socks-over-tls)
@@ -241,83 +242,83 @@ This works for both plain HTTP and SSL encrypted HTTPS requests.
#### Protocol version
-This library supports the SOCKS4, SOCKS4a and SOCKS5 protocol versions.
-
-While SOCKS4 already had (a somewhat limited) support for `SOCKS BIND` requests
-and SOCKS5 added generic UDP support (`SOCKS UDPASSOCIATE`), this library
-focuses on the most commonly used core feature of `SOCKS CONNECT`.
-In this mode, a SOCKS server acts as a generic proxy allowing higher level
-application protocols to work through it.
+This library supports the SOCKS5 and SOCKS4(a) protocol versions.
+It focuses on the most commonly used core feature of connecting to a destination
+host through the SOCKS proxy server. In this mode, a SOCKS proxy server acts as
+a generic proxy allowing higher level application protocols to work through it.
-Note, this is __not__ a full SOCKS5 implementation due to missing GSSAPI
-authentication (but it's unlikely you're going to miss it anyway).
-
By default, the `Client` communicates via SOCKS5 with the SOCKS server.
This is done because SOCKS5 is the latest version from the SOCKS protocol family
and generally has best support across other vendors.
-If want to explicitly set the protocol version, use the supported values URI
-schemes `socks4://` or `socks4a://`as part of the SOCKS URI:
+If want to explicitly set the protocol version to SOCKS4(a), you can use the URI
+scheme `socks4://` as part of the SOCKS URI:
```php
-$client = new Client('socks4a://127.0.0.1', $connector);
+$client = new Client('socks4://127.0.0.1', $connector);
```
-As seen above, both SOCKS5 and SOCKS4a support remote and local DNS resolution.
-If you've explicitly set this to SOCKS4, then you may want to check the following
-chapter about local DNS resolution or you may only connect to IPv4 addresses.
-
#### DNS resolution
By default, the `Client` does not perform any DNS resolution at all and simply
@@ -325,7 +326,7 @@ forwards any hostname you're trying to connect to to the SOCKS server.
The remote SOCKS server is thus responsible for looking up any hostnames via DNS
(this default mode is thus called *remote DNS resolution*).
As seen above, this mode is supported by the SOCKS5 and SOCKS4a protocols, but
-not the SOCKS4 protocol, as the protocol lacks a way to communicate hostnames.
+not the original SOCKS4 protocol, as the protocol lacks a way to communicate hostnames.
On the other hand, all SOCKS protocol versions support sending destination IP
addresses to the SOCKS server.
@@ -372,12 +373,6 @@ as usual.
> Note how local DNS resolution is in fact entirely handled outside of this
SOCKS client implementation.
-If you've explicitly set the client to SOCKS4 and stick to the default
-*remote DNS resolution*, then you may only connect to IPv4 addresses because
-the protocol lacks a way to communicate hostnames.
-If you try to connect to a hostname despite, the resulting promise will be
-rejected right away.
-
#### Authentication
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
The `Server` is responsible for accepting incoming communication from SOCKS clients
and forwarding the requested connection to the target host.
+It supports the SOCKS5 and SOCKS4(a) protocol versions by default.
It also registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage)
and an underlying TCP/IP socket server like this:
@@ -666,23 +662,6 @@ You can use this parameter for logging purposes or to restrict connection
requests for certain clients by providing a custom implementation of the
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface).
-#### Server protocol version
-
-The `Server` supports all protocol versions (SOCKS4, SOCKS4a and SOCKS5) by default.
-
-If want to explicitly set the protocol version, use the supported values `4`, `4a` or `5`:
-
-```PHP
-$server->setProtocolVersion(5);
-```
-
-In order to reset the protocol version to its default (i.e. automatic detection),
-use `null` as protocol version.
-
-```PHP
-$server->setProtocolVersion(null);
-```
-
#### Server authentication
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:
#### Server SOCKS over TLS
-All [SOCKS protocol versions](#server-protocol-version) support forwarding TCP/IP
-based connections and higher level protocols.
+Both SOCKS5 and SOCKS4(a) protocol versions support forwarding TCP/IP based
+connections and higher level protocols.
This implies that you can also use [secure TLS connections](#secure-tls-connections)
to transfer sensitive data across SOCKS proxy servers.
This means that no eavesdropper nor the proxy server will be able to decrypt
@@ -841,8 +820,8 @@ See also [example 31](examples).
#### Server Unix domain sockets
-All [SOCKS protocol versions](#server-protocol-version) support forwarding TCP/IP
-based connections and higher level protocols.
+Both SOCKS5 and SOCKS4(a) protocol versions support forwarding TCP/IP based
+connections and higher level protocols.
In some advanced cases, it may be useful to let your SOCKS server listen on a
Unix domain socket (UDS) path instead of a IP:port combination.
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);
The [Tor anonymity network](http://www.torproject.org) client software is designed
to encrypt your traffic and route it over a network of several nodes to conceal its origin.
-It presents a SOCKS4 and SOCKS5 interface on TCP port 9050 by default
+It presents a SOCKS5 and SOCKS4(a) interface on TCP port 9050 by default
which allows you to tunnel any traffic through the anonymity network.
In most scenarios you probably don't want your client to resolve the target hostnames,
because you would leak DNS information to anybody observing your local traffic.
@@ -984,7 +963,7 @@ MIT, see LICENSE
* If you want to learn more about processing streams of data, refer to the
documentation of the underlying
[react/stream](https://github.com/reactphp/stream) component.
-* As an alternative to a SOCKS (SOCKS4/SOCKS5) proxy, you may also want to look into
+* As an alternative to a SOCKS5 / SOCKS4(a) proxy, you may also want to look into
using an HTTP CONNECT proxy instead.
You may want to use [clue/reactphp-http-proxy](https://github.com/clue/reactphp-http-proxy)
which also provides an implementation of the same
diff --git a/composer.json b/composer.json
index daa9c32..5272ac4 100644
--- a/composer.json
+++ b/composer.json
@@ -1,7 +1,7 @@
{
"name": "clue/socks-react",
- "description": "Async SOCKS4, SOCKS4a and SOCKS5 proxy client and server implementation, built on top of ReactPHP",
- "keywords": ["socks client", "socks server", "proxy", "tcp tunnel", "socks protocol", "async", "ReactPHP"],
+ "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.",
+ "keywords": ["socks client", "socks server", "socks5", "socks4a", "proxy server", "tcp tunnel", "async", "ReactPHP"],
"homepage": "https://github.com/clue/reactphp-socks",
"license": "MIT",
"authors": [
diff --git a/src/Client.php b/src/Client.php
index 70c33fd..e37a7e0 100644
--- a/src/Client.php
+++ b/src/Client.php
@@ -22,7 +22,7 @@ final class Client implements ConnectorInterface
private $socksUri;
- private $protocolVersion = '5';
+ private $protocolVersion = 5;
private $auth = null;
@@ -30,7 +30,7 @@ public function __construct($socksUri, ConnectorInterface $connector)
{
// support `sockss://` scheme for SOCKS over TLS
// support `socks+unix://` scheme for Unix domain socket (UDS) paths
- if (preg_match('/^(socks(?:5|4|4a)?)(s|\+unix):\/\/(.*?@)?(.+?)$/', $socksUri, $match)) {
+ if (preg_match('/^(socks(?:5|4)?)(s|\+unix):\/\/(.*?@)?(.+?)$/', $socksUri, $match)) {
// rewrite URI to parse SOCKS scheme, authentication and dummy host
$socksUri = $match[1] . '://' . $match[3] . 'localhost';
@@ -77,11 +77,9 @@ public function __construct($socksUri, ConnectorInterface $connector)
private function setProtocolVersionFromScheme($scheme)
{
if ($scheme === 'socks' || $scheme === 'socks5') {
- $this->protocolVersion = '5';
- } elseif ($scheme === 'socks4a') {
- $this->protocolVersion = '4a';
+ $this->protocolVersion = 5;
} elseif ($scheme === 'socks4') {
- $this->protocolVersion = '4';
+ $this->protocolVersion = 4;
} else {
throw new InvalidArgumentException('Invalid protocol version given "' . $scheme . '://"');
}
@@ -128,10 +126,6 @@ public function connect($uri)
$host = trim($parts['host'], '[]');
$port = $parts['port'];
- if ($this->protocolVersion === '4' && false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
- return Promise\reject(new InvalidArgumentException('Requires an IPv4 address for SOCKS4'));
- }
-
if (strlen($host) > 255 || $port > 65535 || $port < 0 || (string)$port !== (string)(int)$port) {
return Promise\reject(new InvalidArgumentException('Invalid target specified'));
}
@@ -204,7 +198,7 @@ public function handleConnectedSocks(ConnectionInterface $stream, $host, $port)
$deferred->reject(new RuntimeException('Connection to proxy lost while waiting for response (ECONNRESET)', defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104));
});
- if ($this->protocolVersion === '5') {
+ if ($this->protocolVersion === 5) {
$promise = $this->handleSocks5($stream, $host, $port, $reader);
} else {
$promise = $this->handleSocks4($stream, $host, $port, $reader);
diff --git a/src/Server.php b/src/Server.php
index 080994b..ef6d143 100644
--- a/src/Server.php
+++ b/src/Server.php
@@ -41,8 +41,6 @@ final class Server
private $auth = null;
- private $protocolVersion = null;
-
public function __construct(LoopInterface $loop, ConnectorInterface $connector = null)
{
if ($connector === null) {
@@ -65,28 +63,12 @@ public function listen(ServerInterface $socket)
});
}
- public function setProtocolVersion($version)
- {
- if ($version !== null) {
- $version = (string)$version;
- if (!in_array($version, array('4', '4a', '5'), true)) {
- throw new InvalidArgumentException('Invalid protocol version given');
- }
- if ($version !== '5' && $this->auth !== null){
- throw new UnexpectedValueException('Unable to change protocol version to anything but SOCKS5 while authentication is used. Consider removing authentication info or sticking to SOCKS5');
- }
- }
- $this->protocolVersion = $version;
- }
-
public function setAuth($auth)
{
if (!is_callable($auth)) {
throw new InvalidArgumentException('Given authenticator is not a valid callable');
}
- if ($this->protocolVersion !== null && $this->protocolVersion !== '5') {
- throw new UnexpectedValueException('Authentication requires SOCKS5. Consider using protocol version 5 or waive authentication');
- }
+
// wrap authentication callback in order to cast its return value to a promise
$this->auth = function($username, $password, $remote) use ($auth) {
$ret = call_user_func($auth, $username, $password, $remote);
@@ -163,26 +145,15 @@ private function handleSocks(ConnectionInterface $stream)
$stream->on('data', array($reader, 'write'));
$that = $this;
- $that = $this;
-
$auth = $this->auth;
- $protocolVersion = $this->protocolVersion;
- // authentication requires SOCKS5
- if ($auth !== null) {
- $protocolVersion = '5';
- }
-
- return $reader->readByte()->then(function ($version) use ($stream, $that, $protocolVersion, $auth, $reader){
+ return $reader->readByte()->then(function ($version) use ($stream, $that, $auth, $reader){
if ($version === 0x04) {
- if ($protocolVersion === '5') {
- throw new UnexpectedValueException('SOCKS4 not allowed due to configuration');
+ if ($auth !== null) {
+ throw new UnexpectedValueException('SOCKS4 not allowed because authentication is required');
}
- return $that->handleSocks4($stream, $protocolVersion, $reader);
+ return $that->handleSocks4($stream, $reader);
} else if ($version === 0x05) {
- if ($protocolVersion !== null && $protocolVersion !== '5') {
- throw new UnexpectedValueException('SOCKS5 not allowed due to configuration');
- }
return $that->handleSocks5($stream, $auth, $reader);
}
throw new UnexpectedValueException('Unexpected/unknown version number');
@@ -190,11 +161,8 @@ private function handleSocks(ConnectionInterface $stream)
}
/** @internal */
- public function handleSocks4(ConnectionInterface $stream, $protocolVersion, StreamReader $reader)
+ public function handleSocks4(ConnectionInterface $stream, StreamReader $reader)
{
- // suppliying hostnames is only allowed for SOCKS4a (or automatically detected version)
- $supportsHostname = ($protocolVersion === null || $protocolVersion === '4a');
-
$remote = $stream->getRemoteAddress();
if ($remote !== null) {
// remove transport scheme and prefix socks4:// instead
@@ -212,7 +180,7 @@ public function handleSocks4(ConnectionInterface $stream, $protocolVersion, Stre
'ipLong' => 'N',
'null' => 'C'
));
- })->then(function ($data) use ($reader, $supportsHostname, $remote) {
+ })->then(function ($data) use ($reader, $remote) {
if ($data['null'] !== 0x00) {
throw new Exception('Not a null byte');
}
@@ -222,7 +190,7 @@ public function handleSocks4(ConnectionInterface $stream, $protocolVersion, Stre
if ($data['port'] === 0) {
throw new Exception('Invalid port');
}
- if ($data['ipLong'] < 256 && $supportsHostname) {
+ if ($data['ipLong'] < 256) {
// invalid IP => probably a SOCKS4a request which appends the hostname
return $reader->readStringNull()->then(function ($string) use ($data, $remote){
return array($string, $data['port'], $remote);
diff --git a/tests/FunctionalTest.php b/tests/FunctionalTest.php
index ec74e6b..615069b 100644
--- a/tests/FunctionalTest.php
+++ b/tests/FunctionalTest.php
@@ -50,19 +50,17 @@ public function testConnectionInvalid()
public function testConnectionWithIpViaSocks4()
{
- $this->server->setProtocolVersion('4');
-
$this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
$this->assertResolveStream($this->client->connect('127.0.0.1:' . $this->port));
}
/** @group internet */
- public function testConnectionWithHostnameViaSocks4Fails()
+ public function testConnectionWithHostnameViaSocks4a()
{
$this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
- $this->assertRejectPromise($this->client->connect('www.google.com:80'));
+ $this->assertResolveStream($this->client->connect('www.google.com:80'));
}
/** @group internet */
@@ -78,32 +76,14 @@ public function testConnectionWithIpv6ViaSocks4Fails()
$this->assertRejectPromise($this->client->connect('[::1]:80'));
}
- /** @group internet */
- public function testConnectionSocks4a()
- {
- $this->server->setProtocolVersion('4a');
- $this->client = new Client('socks4a://127.0.0.1:' . $this->port, $this->connector);
-
- $this->assertResolveStream($this->client->connect('www.google.com:80'));
- }
-
/** @group internet */
public function testConnectionSocks5()
{
- $this->server->setProtocolVersion(5);
$this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
$this->assertResolveStream($this->client->connect('www.google.com:80'));
}
- /** @group internet */
- public function testConnectionDefaultsToSocks5()
- {
- $this->server->setProtocolVersion(5);
-
- $this->assertResolveStream($this->client->connect('www.google.com:80'));
- }
-
/** @group internet */
public function testConnectionSocksOverTls()
{
@@ -182,7 +162,6 @@ public function testConnectionSocks5OverUnix()
$socket = new UnixServer($path, $this->loop);
$this->server = new Server($this->loop);
$this->server->listen($socket);
- $this->server->setProtocolVersion(5);
$this->connector = new Connector($this->loop);
$this->client = new Client('socks5+unix://' . $path, $this->connector);
@@ -296,20 +275,13 @@ public function testConnectionAuthenticationUnused()
$this->assertResolveStream($this->client->connect('www.google.com:80'));
}
- public function testConnectionInvalidProtocolDoesNotMatchSocks5()
+ public function testConnectionInvalidNoAuthenticationOverLegacySocks4()
{
- $this->server->setProtocolVersion(5);
- $this->client = new Client('socks4a://127.0.0.1:' . $this->port, $this->connector);
-
- $this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_ECONNRESET);
- }
+ $this->server->setAuthArray(array('name' => 'pass'));
- public function testConnectionInvalidProtocolDoesNotMatchSocks4()
- {
- $this->server->setProtocolVersion(4);
- $this->client = new Client('socks5://127.0.0.1:' . $this->port, $this->connector);
+ $this->client = new Client('socks4://127.0.0.1:' . $this->port, $this->connector);
- $this->assertRejectPromise($this->client->connect('www.google.com:80'), null, SOCKET_ECONNRESET);
+ $this->assertRejectPromise($this->client->connect('www.google.com:80'));
}
public function testConnectionInvalidNoAuthentication()
diff --git a/tests/ServerTest.php b/tests/ServerTest.php
index 965bccc..375e415 100644
--- a/tests/ServerTest.php
+++ b/tests/ServerTest.php
@@ -31,24 +31,6 @@ public function testListen()
$this->server->listen($socket);
}
- public function testSetProtocolVersion()
- {
- $this->server->setProtocolVersion(4);
- $this->server->setProtocolVersion('4a');
- $this->server->setProtocolVersion(5);
- $this->server->setProtocolVersion(null);
-
- $this->assertTrue(true);
- }
-
- /**
- * @expectedException InvalidArgumentException
- */
- public function testSetInvalidProtocolVersion()
- {
- $this->server->setProtocolVersion(6);
- }
-
public function testSetAuthArray()
{
$this->server->setAuthArray(array());
@@ -69,29 +51,6 @@ public function testSetAuthInvalid()
$this->server->setAuth(true);
}
- /**
- * @expectedException UnexpectedValueException
- */
- public function testUnableToSetAuthIfProtocolDoesNotSupportAuth()
- {
- $this->server->setProtocolVersion(4);
-
- $this->server->setAuthArray(array());
- }
-
- /**
- * @expectedException UnexpectedValueException
- */
- public function testUnableToSetProtocolWhichDoesNotSupportAuth()
- {
- $this->server->setAuthArray(array());
-
- // this is okay
- $this->server->setProtocolVersion(5);
-
- $this->server->setProtocolVersion(4);
- }
-
public function testConnectWillCreateConnection()
{
$stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();