diff --git a/src/TcpConnector.php b/src/TcpConnector.php index 532533bd..23aba725 100644 --- a/src/TcpConnector.php +++ b/src/TcpConnector.php @@ -99,9 +99,20 @@ public function connect($uri) // The following hack looks like the only way to // detect connection refused errors with PHP's stream sockets. if (false === \stream_socket_get_name($stream, true)) { - \fclose($stream); + // actual socket errno and errstr can only be retrieved when ext-sockets is available (see tests) + // @codeCoverageIgnoreStart + if (\function_exists('socket_import_stream')) { + $socket = \socket_import_stream($stream); + $errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR); + $errstr = \socket_strerror($errno); + } else { + $errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNRESET : 111; + $errstr = 'Connection refused?'; + } + // @codeCoverageIgnoreEnd - $reject(new \RuntimeException('Connection to ' . $uri . ' failed: Connection refused')); + \fclose($stream); + $reject(new \RuntimeException('Connection to ' . $uri . ' failed: ' . $errstr, $errno)); } else { $resolve(new Connection($stream, $loop)); } diff --git a/tests/TcpConnectorTest.php b/tests/TcpConnectorTest.php index e6a03e6e..4b25ee6a 100644 --- a/tests/TcpConnectorTest.php +++ b/tests/TcpConnectorTest.php @@ -100,6 +100,39 @@ public function connectionToTcpServerShouldFailIfFileDescriptorsAreExceeded() Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT); } + /** @test */ + public function connectionToInvalidNetworkShouldFailWithUnreachableError() + { + if (!defined('SOCKET_ENETUNREACH') || !function_exists('socket_import_stream')) { + $this->markTestSkipped('Test requires ext-socket on PHP 5.4+'); + } + + // try to find an unreachable network by trying a couple of private network addresses + $errno = 0; $errstr = ''; + for ($i = 0; $i < 20; ++$i) { + $address = 'tcp://192.168.' . mt_rand(0, 255) . '.' . mt_rand(1, 254) . ':8123'; + $client = @stream_socket_client($address, $errno, $errstr, 0.1 * $i); + if ($errno === SOCKET_ENETUNREACH) { + break; + } + } + if ($client || $errno !== SOCKET_ENETUNREACH) { + $this->markTestSkipped('Expected error ' . SOCKET_ENETUNREACH . ' but got ' . $errno . ' (' . $errstr . ') for ' . $address); + } + + $loop = Factory::create(); + $connector = new TcpConnector($loop); + + $promise = $connector->connect($address); + + $this->setExpectedException( + 'RuntimeException', + 'Connection to ' . $address . ' failed: ' . socket_strerror(SOCKET_ENETUNREACH), + SOCKET_ENETUNREACH + ); + Block\await($promise, $loop, self::TIMEOUT); + } + /** @test */ public function connectionToTcpServerShouldSucceedWithRemoteAdressSameAsTarget() {