Skip to content

Commit 7097f5c

Browse files
committed
Improve error messages for failed TCP/IP connections without ext-sockets
1 parent 5beea91 commit 7097f5c

File tree

3 files changed

+35
-14
lines changed

3 files changed

+35
-14
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ jobs:
3131
php-version: ${{ matrix.php }}
3232
coverage: xdebug
3333
- run: composer install
34-
- run: vendor/bin/phpunit --coverage-text
34+
- run: vendor/bin/phpunit --coverage-text --verbose
3535
if: ${{ matrix.php >= 7.3 }}
36-
- run: vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy
36+
- run: vendor/bin/phpunit --coverage-text --verbose -c phpunit.xml.legacy
3737
if: ${{ matrix.php < 7.3 }}
3838

3939
PHPUnit-macOS:
@@ -58,5 +58,5 @@ jobs:
5858
- uses: azjezz/setup-hhvm@v1
5959
with:
6060
version: lts-3.30
61-
- run: hhvm $(which composer) install
61+
- run: hhvm $(which composer) install --verbose
6262
- run: hhvm vendor/bin/phpunit

src/TcpConnector.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,26 @@ public function connect($uri)
9999
// The following hack looks like the only way to
100100
// detect connection refused errors with PHP's stream sockets.
101101
if (false === \stream_socket_get_name($stream, true)) {
102-
// actual socket errno and errstr can only be retrieved when ext-sockets is available (see tests)
102+
// If we reach this point, we know the connection is dead, but we don't know the underlying error condition.
103103
// @codeCoverageIgnoreStart
104104
if (\function_exists('socket_import_stream')) {
105+
// actual socket errno and errstr can be retrieved with ext-sockets on PHP 5.4+
105106
$socket = \socket_import_stream($stream);
106107
$errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR);
107108
$errstr = \socket_strerror($errno);
109+
} elseif (\PHP_OS === 'Linux') {
110+
// Linux reports socket errno and errstr again when trying to write to the dead socket.
111+
// Suppress error reporting to get error message below and close dead socket before rejecting.
112+
// This is only known to work on Linux, Mac and Windows are known to not support this.
113+
@\fwrite($stream, \PHP_EOL);
114+
$error = \error_get_last();
115+
116+
// fwrite(): send of 2 bytes failed with errno=111 Connection refused
117+
\preg_match('/errno=(\d+) (.+)/', $error['message'], $m);
118+
$errno = isset($m[1]) ? (int) $m[1] : 0;
119+
$errstr = isset($m[2]) ? $m[2] : $error['message'];
108120
} else {
121+
// Not on Linux and ext-sockets not available? Too bad.
109122
$errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNRESET : 111;
110123
$errstr = 'Connection refused?';
111124
}

tests/TcpConnectorTest.php

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,21 +103,29 @@ public function connectionToTcpServerShouldFailIfFileDescriptorsAreExceeded()
103103
/** @test */
104104
public function connectionToInvalidNetworkShouldFailWithUnreachableError()
105105
{
106-
if (!defined('SOCKET_ENETUNREACH') || !function_exists('socket_import_stream')) {
107-
$this->markTestSkipped('Test requires ext-socket on PHP 5.4+');
106+
if (PHP_OS !== 'Linux' && !function_exists('socket_import_stream')) {
107+
$this->markTestSkipped('Test requires either Linux or ext-sockets on PHP 5.4+');
108108
}
109109

110+
$enetunreach = defined('SOCKET_ENETUNREACH') ? SOCKET_ENETUNREACH : 101;
111+
110112
// try to find an unreachable network by trying a couple of private network addresses
111113
$errno = 0; $errstr = '';
112-
for ($i = 0; $i < 20; ++$i) {
114+
for ($i = 0; $i < 10 && $errno !== $enetunreach; ++$i) {
113115
$address = 'tcp://192.168.' . mt_rand(0, 255) . '.' . mt_rand(1, 254) . ':8123';
114116
$client = @stream_socket_client($address, $errno, $errstr, 0.1 * $i);
115-
if ($errno === SOCKET_ENETUNREACH) {
116-
break;
117-
}
118117
}
119-
if ($client || $errno !== SOCKET_ENETUNREACH) {
120-
$this->markTestSkipped('Expected error ' . SOCKET_ENETUNREACH . ' but got ' . $errno . ' (' . $errstr . ') for ' . $address);
118+
for ($i = 0; $i < 10 && $errno !== $enetunreach; ++$i) {
119+
$address = 'tcp://172.' . mt_rand(16, 31) . '.' . mt_rand(0, 255) . '.' . mt_rand(1, 254) . ':8123';
120+
$client = @stream_socket_client($address, $errno, $errstr, 0.1 * $i);
121+
}
122+
for ($i = 0; $i < 10 && $errno !== $enetunreach; ++$i) {
123+
$address = 'tcp://10.' . mt_rand(0, 255) . '.' . mt_rand(0, 255) . '.' . mt_rand(1, 254) . ':8123';
124+
$client = @stream_socket_client($address, $errno, $errstr, 0.1 * $i);
125+
}
126+
if ($client || $errno !== $enetunreach) {
127+
passthru('ip route list');
128+
$this->markTestSkipped('Expected error ' . $enetunreach . ' but got ' . $errno . ' (' . $errstr . ') for ' . $address);
121129
}
122130

123131
$loop = Factory::create();
@@ -127,8 +135,8 @@ public function connectionToInvalidNetworkShouldFailWithUnreachableError()
127135

128136
$this->setExpectedException(
129137
'RuntimeException',
130-
'Connection to ' . $address . ' failed: ' . socket_strerror(SOCKET_ENETUNREACH),
131-
SOCKET_ENETUNREACH
138+
'Connection to ' . $address . ' failed: ' . (function_exists('socket_strerror') ? socket_strerror($enetunreach) : 'Network is unreachable'),
139+
$enetunreach
132140
);
133141
Block\await($promise, $loop, self::TIMEOUT);
134142
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy