Skip to content

Commit 92411ce

Browse files
committed
Avoid possibility of missing remote address when TLS handshake fails
This fixes a race condition where the remote address could appear to be "emtpy" in the server exception message, that could occur when the connection to the peer was already closed during the TLS handshake. This is simply avoided by getting the remote address prior to starting the TLS handshake, in which case the remote address is known to exist.
1 parent de2c87f commit 92411ce

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)