forked from clue/reactphp-ssh-proxy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFunctionalSshSocksConnectorTest.php
More file actions
171 lines (134 loc) · 5.86 KB
/
FunctionalSshSocksConnectorTest.php
File metadata and controls
171 lines (134 loc) · 5.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
<?php
namespace Clue\Tests\React\SshProxy;
use Clue\React\SshProxy\SshSocksConnector;
class FunctionalSshSocksConnectorTest extends TestCase
{
const TIMEOUT = 10.0;
private $connector;
/**
* @before
*/
public function setUpConnector()
{
$url = getenv('SSH_PROXY');
if ($url === false) {
$this->markTestSkipped('No SSH_PROXY env set');
}
$this->connector = new SshSocksConnector($url);
}
/**
* @after
*/
public function tearDownSSHClientProcess()
{
// Skip timer-based teardown for PHP 5.3 where React\Promise\Timer is not available
if (!class_exists('React\\Promise\\Timer\\TimeoutException')) {
return;
}
// run loop in order to shut down SSH client process again
\React\Async\await(\React\Promise\Timer\sleep(0.001));
}
// Helper method to check if Timer functions are available
private function hasTimerSupport()
{
return class_exists('React\\Promise\\Timer\\TimeoutException');
}
public function testConnectInvalidProxyUriWillReturnRejectedPromise()
{
if (!$this->hasTimerSupport()) {
$this->markTestSkipped('No Timer support available');
return;
}
$this->connector = new SshSocksConnector(getenv('SSH_PROXY') . '.invalid');
$promise = $this->connector->connect('example.com:80');
$this->setExpectedException('RuntimeException', 'Connection to example.com:80 failed because SSH client process died');
\React\Async\await(\React\Promise\Timer\timeout($promise, self::TIMEOUT));
}
public function testConnectInvalidTargetWillReturnRejectedPromise()
{
if (!$this->hasTimerSupport()) {
$this->markTestSkipped('No Timer support available');
return;
}
$promise = $this->connector->connect('example.invalid:80');
$this->setExpectedException('RuntimeException', 'Connection to tcp://example.invalid:80 failed because connection to proxy was lost');
\React\Async\await(\React\Promise\Timer\timeout($promise, self::TIMEOUT));
}
public function testCancelConnectWillReturnRejectedPromise()
{
if (!$this->hasTimerSupport()) {
$this->markTestSkipped('No Timer support available');
return;
}
$promise = $this->connector->connect('example.com:80');
$promise->cancel();
$this->setExpectedException('RuntimeException', 'Connection to example.com:80 cancelled while waiting for SSH client');
\React\Async\await(\React\Promise\Timer\timeout($promise, 0));
}
public function testConnectValidTargetWillReturnPromiseWhichResolvesToConnection()
{
if (!$this->hasTimerSupport()) {
$this->markTestSkipped('No Timer support available');
return;
}
$promise = $this->connector->connect('example.com:80');
$connection = \React\Async\await(\React\Promise\Timer\timeout($promise, self::TIMEOUT));
$this->assertInstanceOf('React\Socket\ConnectionInterface', $connection);
$this->assertTrue($connection->isReadable());
$this->assertTrue($connection->isWritable());
$connection->close();
}
public function testConnectValidTargetWillReturnPromiseWhichResolvesToConnectionForCustomBindAddress()
{
if (!$this->hasTimerSupport()) {
$this->markTestSkipped('No Timer support available');
return;
}
$this->connector = new SshSocksConnector(getenv('SSH_PROXY') . '?bind=127.0.0.1:1081');
$promise = $this->connector->connect('example.com:80');
$connection = \React\Async\await(\React\Promise\Timer\timeout($promise, self::TIMEOUT));
$this->assertInstanceOf('React\Socket\ConnectionInterface', $connection);
$this->assertTrue($connection->isReadable());
$this->assertTrue($connection->isWritable());
$connection->close();
}
public function testConnectPendingWillNotInheritActiveFileDescriptors()
{
if (!$this->hasTimerSupport()) {
$this->markTestSkipped('No Timer support available');
return;
}
$server = stream_socket_server('tcp://127.0.0.1:0');
$address = stream_socket_get_name($server, false);
// ensure that we can not listen on the same address twice
$copy = @stream_socket_server('tcp://' . $address);
if ($copy !== false) {
fclose($server);
fclose($copy);
$this->markTestSkipped('Platform does not prevent binding to same address (Windows?)');
}
$promise = $this->connector->connect('example.com:80');
// close server and ensure we can start a new server on the previous address
// the pending SSH connection process should not inherit the existing server socket
fclose($server);
$server = @stream_socket_server('tcp://' . $address);
if ($server === false) {
// There's a very short race condition where the forked php process
// first has to `dup()` the file descriptor specs before invoking
// `exec()` to switch to the actual `ssh` child process. We don't
// need to wait for the child process to be ready, but only for the
// forked process to close the file descriptors. This happens ~80%
// of times on single core machines and almost never on multi core
// systems, so simply wait 5ms (plenty of time!) and retry again twice.
usleep(5000);
$server = @stream_socket_server('tcp://' . $address);
if ($server === false) {
usleep(5000);
$server = stream_socket_server('tcp://' . $address);
}
}
$this->assertTrue(is_resource($server));
fclose($server);
$promise->cancel();
}
}