Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ of the actual application level protocol, such as HTTP, SMTP, IMAP, Telnet etc.
* [Authentication](#authentication)
* [Proxy chaining](#proxy-chaining)
* [Connection timeout](#connection-timeout)
* [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)
* [Unix domain sockets](#server-unix-domain-sockets)
* [Servers](#servers)
* [Using a PHP SOCKS server](#using-a-php-socks-server)
* [Using SSH as a SOCKS server](#using-ssh-as-a-socks-server)
Expand Down Expand Up @@ -553,6 +555,42 @@ as usual.
> Also note how connection timeout is in fact entirely handled outside of this
SOCKS client implementation.

#### Unix domain sockets

All [SOCKS protocol versions](#protocol-version) 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
having to rely on explicit [authentication](#authentication).

You can use the `socks+unix://` URI scheme or use an explicit
[SOCKS protocol version](#protocol-version) like this:

```php
$client = new Client('socks+unix:///tmp/proxy.sock', new Connector($loop));

$client = new Client('socks5+unix:///tmp/proxy.sock', new Connector($loop));
```

Simiarly, you can also combine this with [authentication](#authentication)
like this:

```php
$client = new Client('socks+unix://user:pass@/tmp/proxy.sock', new Connector($loop));
```

> Note that Unix domain sockets (UDS) are considered advanced usage and PHP only
has limited support for this.
In particular, enabling [secure TLS](#secure-tls-connections) may not be
supported.

> Note that SOCKS protocol does not support the notion of UDS paths. The above
works reasonably well because UDS is only used for the connection between
client and proxy server and the path will not actually passed over the protocol.
This implies that this does also not support [proxy chaining](#proxy-chaining)
over multiple UDS paths.

### Server

The `Server` is responsible for accepting incoming communication from SOCKS clients
Expand Down Expand Up @@ -718,6 +756,32 @@ Proxy chaining can happen on the server side and/or the client side:
not really know anything about chaining at all.
This means that this is a server-only property and can be implemented as above.

#### Server Unix domain sockets

All [SOCKS protocol versions](#server-protocol-version) 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
having to rely on explicit [authentication](#server-authentication).

You can simply start your listening socket on the `unix://` URI scheme like this:

```php
$loop = \React\EventLoop\Factory::create();

// listen on /tmp/proxy.sock
$socket = new React\Socket\Server('unix:///tmp/proxy.sock', $loop);
$server = new Server($loop, $socket);
```

> Note that Unix domain sockets (UDS) are considered advanced usage and that
the SOCKS protocol does not support the notion of UDS paths. The above
works reasonably well because UDS is only used for the connection between
client and proxy server and the path will not actually passed over the protocol.
This implies that this does also not support [proxy chaining](#server-proxy-chaining)
over multiple UDS paths.

## Servers

### Using a PHP SOCKS server
Expand Down Expand Up @@ -754,6 +818,22 @@ Now you can simply use this SSH SOCKS server like this:
$client = new Client('127.0.0.1:1080', $connector);
```

Note that the above will allow all users on the local system to connect over
your SOCKS server without authentication which may or may not be what you need.
As an alternative, recent OpenSSH client versions also support
[Unix domain sockets](#unix-domain-sockets) (UDS) paths so that you can rely
on Unix file system permissions instead:

```bash
$ ssh -D/tmp/proxy.sock example.com
```

Now you can simply use this SSH SOCKS server like this:

```PHP
$client = new Client('socks+unix:///tmp/proxy.sock', $connector);
```

### Using the Tor (anonymity network) to tunnel SOCKS connections

The [Tor anonymity network](http://www.torproject.org) client software is designed
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
"require": {
"php": ">=5.3",
"react/socket": "^1.0 || ^0.8 || ^0.7",
"react/socket": "^1.0 || ^0.8.6",
"react/promise": "^2.1 || ^1.2",
"evenement/evenement": "~3.0|~1.0|~2.0"
},
Expand Down
10 changes: 10 additions & 0 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use React\Promise\Deferred;
use React\Socket\ConnectionInterface;
use React\Socket\ConnectorInterface;
use React\Socket\FixedUriConnector;
use \Exception;
use \InvalidArgumentException;
use RuntimeException;
Expand All @@ -27,6 +28,15 @@ class Client implements ConnectorInterface

public function __construct($socksUri, ConnectorInterface $connector)
{
// support `socks+unix://` scheme for Unix domain socket (UDS) paths
if (preg_match('/^(socks(?:5|4|4a)?)\+?unix:\/\/(.*?@)?(.+?$)/', $socksUri, $match)) {
$socksUri = ($match[1] !== '' ? ($match[1] . '://') : '') . $match[2] . 'localhost';
$connector = new FixedUriConnector(
'unix://' . $match[3],
$connector
);
}

// assume default scheme if none is given
if (strpos($socksUri, '://') === false) {
$socksUri = 'socks://' . $socksUri;
Expand Down
14 changes: 14 additions & 0 deletions tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ public function testCtorAcceptsUriWithHostOnlyAssumesDefaultPort()
$this->assertTrue(true);
}

public function testCtorAcceptsUriWithSocksUnixScheme()
{
$client = new Client('socks+unix:///tmp/socks.socket', $this->connector);

$this->assertTrue(true);
}

public function testCtorAcceptsUriWithSocks5UnixScheme()
{
$client = new Client('socks5+unix:///tmp/socks.socket', $this->connector);

$this->assertTrue(true);
}

/**
* @expectedException InvalidArgumentException
*/
Expand Down
61 changes: 61 additions & 0 deletions tests/FunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use React\Socket\TimeoutConnector;
use React\Socket\SecureConnector;
use React\Socket\TcpConnector;
use React\Socket\UnixServer;
use React\Socket\Connector;

class FunctionalTest extends TestCase
{
Expand Down Expand Up @@ -93,6 +95,65 @@ public function testConnectionSocks5()
$this->assertResolveStream($this->client->connect('www.google.com:80'));
}

/** @group internet */
public function testConnectionSocksOverUnix()
{
if (!in_array('unix', stream_get_transports())) {
$this->markTestSkipped('System does not support unix:// scheme');
}

$path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
$socket = new UnixServer($path, $this->loop);
$this->server = new Server($this->loop, $socket);

$this->connector = new Connector($this->loop);
$this->client = new Client('socks+unix://' . $path, $this->connector);

$this->assertResolveStream($this->client->connect('www.google.com:80'));

unlink($path);
}

/** @group internet */
public function testConnectionSocks5OverUnix()
{
if (!in_array('unix', stream_get_transports())) {
$this->markTestSkipped('System does not support unix:// scheme');
}

$path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
$socket = new UnixServer($path, $this->loop);
$this->server = new Server($this->loop, $socket);
$this->server->setProtocolVersion(5);

$this->connector = new Connector($this->loop);
$this->client = new Client('socks5+unix://' . $path, $this->connector);

$this->assertResolveStream($this->client->connect('www.google.com:80'));

unlink($path);
}

/** @group internet */
public function testConnectionSocksWithAuthenticationOverUnix()
{
if (!in_array('unix', stream_get_transports())) {
$this->markTestSkipped('System does not support unix:// scheme');
}

$path = sys_get_temp_dir() . '/test' . mt_rand(1000, 9999) . '.sock';
$socket = new UnixServer($path, $this->loop);
$this->server = new Server($this->loop, $socket);
$this->server->setAuthArray(array('name' => 'pass'));

$this->connector = new Connector($this->loop);
$this->client = new Client('socks+unix://name:pass@' . $path, $this->connector);

$this->assertResolveStream($this->client->connect('www.google.com:80'));

unlink($path);
}

/** @group internet */
public function testConnectionAuthenticationFromUri()
{
Expand Down