Skip to content

Commit 2d51865

Browse files
authored
Merge pull request #188 from clue-labs/handshake-address
Avoid possibility of missing remote address when TLS handshake fails
2 parents de2c87f + 92411ce commit 2d51865

File tree

2 files changed

+77
-6
lines changed

2 files changed

+77
-6
lines changed

src/SecureServer.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,28 +169,30 @@ public function handleConnection(ConnectionInterface $connection)
169169
{
170170
if (!$connection instanceof Connection) {
171171
$this->emit('error', array(new \UnexpectedValueException('Base server does not use internal Connection class exposing stream resource')));
172-
$connection->end();
172+
$connection->close();
173173
return;
174174
}
175175

176176
foreach ($this->context as $name => $value) {
177177
\stream_context_set_option($connection->stream, 'ssl', $name, $value);
178178
}
179179

180+
// get remote address before starting TLS handshake in case connection closes during handshake
181+
$remote = $connection->getRemoteAddress();
180182
$that = $this;
181183

182184
$this->encryption->enable($connection)->then(
183185
function ($conn) use ($that) {
184186
$that->emit('connection', array($conn));
185187
},
186-
function ($error) use ($that, $connection) {
188+
function ($error) use ($that, $connection, $remote) {
187189
$error = new \RuntimeException(
188-
'Connection from ' . $connection->getRemoteAddress() . ' failed during TLS handshake: ' . $error->getMessage(),
190+
'Connection from ' . $remote . ' failed during TLS handshake: ' . $error->getMessage(),
189191
$error->getCode()
190192
);
191193

192194
$that->emit('error', array($error));
193-
$connection->end();
195+
$connection->close();
194196
}
195197
);
196198
}

tests/SecureServerTest.php

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use React\Socket\SecureServer;
66
use React\Socket\TcpServer;
7+
use React\Promise\Promise;
78

89
class SecureServerTest extends TestCase
910
{
@@ -74,14 +75,14 @@ public function testCloseWillBePassedThroughToTcpServer()
7475
$server->close();
7576
}
7677

77-
public function testConnectionWillBeEndedWithErrorIfItIsNotAStream()
78+
public function testConnectionWillBeClosedWithErrorIfItIsNotAStream()
7879
{
7980
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
8081

8182
$tcp = new TcpServer(0, $loop);
8283

8384
$connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
84-
$connection->expects($this->once())->method('end');
85+
$connection->expects($this->once())->method('close');
8586

8687
$server = new SecureServer($tcp, $loop, array());
8788

@@ -90,6 +91,74 @@ public function testConnectionWillBeEndedWithErrorIfItIsNotAStream()
9091
$tcp->emit('connection', array($connection));
9192
}
9293

94+
public function testConnectionWillTryToEnableEncryptionAndWaitForHandshake()
95+
{
96+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
97+
98+
$tcp = new TcpServer(0, $loop);
99+
100+
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->getMock();
101+
$connection->expects($this->once())->method('getRemoteAddress')->willReturn('tcp://127.0.0.1:1234');
102+
$connection->expects($this->never())->method('close');
103+
104+
$server = new SecureServer($tcp, $loop, array());
105+
106+
$pending = new Promise(function () { });
107+
108+
$encryption = $this->getMockBuilder('React\Socket\StreamEncryption')->disableOriginalConstructor()->getMock();
109+
$encryption->expects($this->once())->method('enable')->willReturn($pending);
110+
111+
$ref = new \ReflectionProperty($server, 'encryption');
112+
$ref->setAccessible(true);
113+
$ref->setValue($server, $encryption);
114+
115+
$ref = new \ReflectionProperty($server, 'context');
116+
$ref->setAccessible(true);
117+
$ref->setValue($server, array());
118+
119+
$server->on('error', $this->expectCallableNever());
120+
$server->on('connection', $this->expectCallableNever());
121+
122+
$tcp->emit('connection', array($connection));
123+
}
124+
125+
public function testConnectionWillBeClosedWithErrorIfEnablingEncryptionFails()
126+
{
127+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
128+
129+
$tcp = new TcpServer(0, $loop);
130+
131+
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->getMock();
132+
$connection->expects($this->once())->method('getRemoteAddress')->willReturn('tcp://127.0.0.1:1234');
133+
$connection->expects($this->once())->method('close');
134+
135+
$server = new SecureServer($tcp, $loop, array());
136+
137+
$error = new \RuntimeException('Original');
138+
139+
$encryption = $this->getMockBuilder('React\Socket\StreamEncryption')->disableOriginalConstructor()->getMock();
140+
$encryption->expects($this->once())->method('enable')->willReturn(\React\Promise\reject($error));
141+
142+
$ref = new \ReflectionProperty($server, 'encryption');
143+
$ref->setAccessible(true);
144+
$ref->setValue($server, $encryption);
145+
146+
$ref = new \ReflectionProperty($server, 'context');
147+
$ref->setAccessible(true);
148+
$ref->setValue($server, array());
149+
150+
$error = null;
151+
$server->on('error', $this->expectCallableOnce());
152+
$server->on('error', function ($e) use (&$error) {
153+
$error = $e;
154+
});
155+
156+
$tcp->emit('connection', array($connection));
157+
158+
$this->assertInstanceOf('RuntimeException', $error);
159+
$this->assertEquals('Connection from tcp://127.0.0.1:1234 failed during TLS handshake: Original', $error->getMessage());
160+
}
161+
93162
public function testSocketErrorWillBeForwarded()
94163
{
95164
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();

0 commit comments

Comments
 (0)