Skip to content

Commit

Permalink
Improve error messages for failed TCP/IP connections without ext-sockets
Browse files Browse the repository at this point in the history
  • Loading branch information
clue committed Aug 13, 2021
1 parent 5beea91 commit 26e47ff
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 13 deletions.
17 changes: 15 additions & 2 deletions src/TcpConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,27 @@ 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)) {
// actual socket errno and errstr can only be retrieved when ext-sockets is available (see tests)
// If we reach this point, we know the connection is dead, but we don't know the underlying error condition.
// @codeCoverageIgnoreStart
if (\function_exists('socket_import_stream')) {
// actual socket errno and errstr can be retrieved with ext-sockets on PHP 5.4+
$socket = \socket_import_stream($stream);
$errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR);
$errstr = \socket_strerror($errno);
} elseif (\PHP_OS === 'Linux') {
// Linux reports socket errno and errstr again when trying to write to the dead socket.
// Suppress error reporting to get error message below and close dead socket before rejecting.
// This is only known to work on Linux, Mac and Windows are known to not support this.
@\fwrite($stream, \PHP_EOL);
$error = \error_get_last();

// fwrite(): send of 2 bytes failed with errno=111 Connection refused
\preg_match('/errno=(\d+) (.+)/', $error['message'], $m);
$errno = isset($m[1]) ? (int) $m[1] : 0;
$errstr = isset($m[2]) ? $m[2] : $error['message'];
} else {
$errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNRESET : 111;
// Not on Linux and ext-sockets not available? Too bad.
$errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111;
$errstr = 'Connection refused?';
}
// @codeCoverageIgnoreEnd
Expand Down
22 changes: 11 additions & 11 deletions tests/TcpConnectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,21 +103,21 @@ public function connectionToTcpServerShouldFailIfFileDescriptorsAreExceeded()
/** @test */
public function connectionToInvalidNetworkShouldFailWithUnreachableError()
{
if (!defined('SOCKET_ENETUNREACH') || !function_exists('socket_import_stream')) {
$this->markTestSkipped('Test requires ext-socket on PHP 5.4+');
if (PHP_OS !== 'Linux' && !function_exists('socket_import_stream')) {
$this->markTestSkipped('Test requires either Linux or ext-sockets on PHP 5.4+');
}

$enetunreach = defined('SOCKET_ENETUNREACH') ? SOCKET_ENETUNREACH : 101;

// try to find an unreachable network by trying a couple of private network addresses
$errno = 0; $errstr = '';
for ($i = 0; $i < 20; ++$i) {
$errno = 0;
$errstr = '';
for ($i = 0; $i < 20 && $errno !== $enetunreach; ++$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);
if ($client || $errno !== $enetunreach) {
$this->markTestSkipped('Expected error ' . $enetunreach . ' but got ' . $errno . ' (' . $errstr . ') for ' . $address);
}

$loop = Factory::create();
Expand All @@ -127,8 +127,8 @@ public function connectionToInvalidNetworkShouldFailWithUnreachableError()

$this->setExpectedException(
'RuntimeException',
'Connection to ' . $address . ' failed: ' . socket_strerror(SOCKET_ENETUNREACH),
SOCKET_ENETUNREACH
'Connection to ' . $address . ' failed: ' . (function_exists('socket_strerror') ? socket_strerror($enetunreach) : 'Network is unreachable'),
$enetunreach
);
Block\await($promise, $loop, self::TIMEOUT);
}
Expand Down

0 comments on commit 26e47ff

Please sign in to comment.