10000 Support custom bind address for local SOCKS proxy server · SimonFrings/reactphp-ssh-proxy@4726de4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4726de4

Browse files
committed
Support custom bind address for local SOCKS proxy server
1 parent c17fa1a commit 4726de4

File tree

4 files changed

+71
-8
lines changed

4 files changed

+71
-8
lines changed

README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,22 @@ approach. If you plan to only use a single or few connections (such as a single
195195
database connection), you're recommended to use the [`SshProcessConnector`](#sshprocessconnector)
196196
instead.
197197

198+
This class defaults to spawning the SSH client process in SOCKS proxy server
199+
mode listening on `127.0.0.1:1080`. If this port is already in use or if you want
200+
to use multiple instances of this class to connect to different SSH proxy
201+
servers, you may optionally pass a unique bind address like this:
202+
203+
```php
204+
$proxy = new Clue\React\SshProxy\SshSocksConnector('user@example.com?bind=127.1.1.1:1081', $loop);
205+
```
206+
198207
> *Security note for multi-user systems*: This class will spawn the SSH client
199-
process in local SOCKS server mode and will accept connections on the
200-
localhost interface only. If you're running on a multi-user system, other
201-
users on the same system may be able to connect to this proxy server and
208+
process in local SOCKS server mode and will accept connections only on the
209+
localhost interface by default. If you're running on a multi-user system,
210+
other users on the same system may be able to connect to this proxy server and
202211
create connections over it. If this applies to your deployment, you're
203-
recommended to use the [`SshProcessConnector](#sshprocessconnector) instead.
212+
recommended to use the [`SshProcessConnector](#sshprocessconnector) instead
213+
or set up custom firewall rules to prevent unauthorized access to this port.
204214

205215
This is one of the two main classes in this package.
206216
Because it implements ReactPHP's standard

src/SshSocksConnector.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class SshSocksConnector implements ConnectorInterface
1818

1919
private $debug = false;
2020

21-
private $port;
21+
private $bind = '127.0.0.1:1080';
2222
private $socks;
2323

2424
private $listen;
@@ -65,9 +65,18 @@ public function __construct($uri, LoopInterface $loop)
6565
$this->cmd .= '-p ' . $parts['port'] . ' ';
6666
}
6767

68-
$this->port = 1080;
69-
$this->socks = new Client('socks4://127.0.0.1:' . $this->port, new TcpConnector($loop));
70-
$this->cmd .= '-D ' . \escapeshellarg('127.0.0.1:' . $this->port) . ' ' . \escapeshellarg($target);
68+
$args = array();
69+
\parse_str(parse_url($uri, \PHP_URL_QUERY), $args);
70+
if (isset($args['bind'])) {
71+
$parts = parse_url('tcp://' . $args['bind']);
72+
if (!isset($parts['scheme'], $parts['host'], $parts['port']) || \filter_var(\trim($parts['host'], '[]'), \FILTER_VALIDATE_IP) === false) {
73+
throw new \InvalidArgumentException('Invalid bind address given');
74+
}
75+
$this->bind = $args['bind'];
76+
}
77+
78+
$this->socks = new Client('socks4://' . $this->bind, new TcpConnector($loop));
79+
$this->cmd .= '-D ' . \escapeshellarg($this->bind) . ' ' . \escapeshellarg($target);
7180
$this->loop = $loop;
7281
}
7382

tests/FunctionalSshSocksConnectorTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ public function testConnectValidTargetWillReturnPromiseWhichResolvesToConnection
7676
$connection->close();
7777
}
7878

79+
public function testConnectValidTargetWillReturnPromiseWhichResolvesToConnectionForCustomBindAddress()
80+
{
81+
$this->connector = new SshSocksConnector(getenv('SSH_PROXY') . '?bind=127.0.0.1:1081', $this->loop);
82+
$promise = $this->connector->connect('example.com:80');
83+
84+
$connection = \Clue\React\Block\await($promise, $this->loop, self::TIMEOUT);
85+
86+
$this->assertInstanceOf('React\Socket\ConnectionInterface', $connection);
87+
$this->assertTrue($connection->isReadable());
88+
$this->assertTrue($connection->isWritable());
89+
$connection->close();
90+
}
91+
7992
public function testConnectValidTargetWillNotInheritActiveFileDescriptors()
8093
{
8194
$server = stream_socket_server('tcp://127.0.0.1:0');

tests/SshSocksConnectorTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,28 @@ public function testConstructorAcceptsUriWithPasswordWillPrefixSshCommandWithSsh
5050
$this->assertEquals('exec sshpass -p \'pass\' ssh -v -o ExitOnForwardFailure=yes -N -D \'127.0.0.1:1080\' \'user@host\'', $ref->getValue($connector));
5151
}
5252

53+
public function testConstructorAcceptsUriWithCustomBindUrl()
54+
{
55+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
56+
$connector = new SshSocksConnector('host?bind=127.1.0.1:2711', $loop);
57+
58+
$ref = new ReflectionProperty($connector, 'cmd');
59+
$ref->setAccessible(true);
60+
61+
$this->assertEquals('exec ssh -v -o ExitOnForwardFailure=yes -N -o BatchMode=yes -D \'127.1.0.1:2711\' \'host\'', $ref->getValue($connector));
62+
}
63+
64+
public function testConstructorAcceptsUriWithCustomBindUrlIpv6()
65+
{
66+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
67+
$connector = new SshSocksConnector('host?bind=[::1]:2711', $loop);
68+
69+
$ref = new ReflectionProperty($connector, 'cmd');
70+
$ref->setAccessible(true);
71+
72+
$this->assertEquals('exec ssh -v -o ExitOnForwardFailure=yes -N -o BatchMode=yes -D \'[::1]:2711\' \'host\'', $ref->getValue($connector));
73+
}
74+
5375
/**
5476
* @expectedException InvalidArgumentException
5577
*/
@@ -86,6 +108,15 @@ public function testConstructorThrowsForInvalidHost()
86108
new SshSocksConnector('-host', $loop);
87109
}
88110

111+
/**
112+
* @expectedException InvalidArgumentException
113+
*/
114+
public function testConstructorThrowsForInvalidBindHost()
115+
{
116+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
117+
new SshSocksConnector('host?bind=example:1080', $loop);
118+
}
119+
89120
/**
90121
* @doesNotPerformAssertions
91122
*/

0 commit comments

Comments
 (0)
0