8000 Improve errno detection for failed connections without ext-sockets by clue · Pull Request #304 · reactphp/socket · GitHub
[go: up one dir, main page]

Skip to content

Improve errno detection for failed connections without ext-sockets #304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Connector.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public function connect($uri)
if (!isset($this->connectors[$scheme])) {
return \React\Promise\reject(new \RuntimeException(
'No connector available for URI scheme "' . $scheme . '" (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
));
}

Expand Down
2 changes: 1 addition & 1 deletion src/DnsConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function connect($uri)
if (!$parts || !isset($parts['host'])) {
return Promise\reject(new \InvalidArgumentException(
'Given URI "' . $original . '" is invalid (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
));
}

Expand Down
2 changes: 1 addition & 1 deletion src/FdServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public function __construct($fd, LoopInterface $loop = null)
if (!\is_int($fd) || $fd < 0 || $fd >= \PHP_INT_MAX) {
throw new \InvalidArgumentException(
'Invalid FD number given (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/HappyEyeBallsConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function connect($uri)
if (!$parts || !isset($parts['host'])) {
return Promise\reject(new \InvalidArgumentException(
'Given URI "' . $original . '" is invalid (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
));
}

Expand Down
2 changes: 1 addition & 1 deletion src/SecureConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function connect($uri)
if (!$parts || !isset($parts['scheme']) || $parts['scheme'] !== 'tls') {
return Promise\reject(new \InvalidArgumentException(
'Given URI "' . $uri . '" is invalid (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
));
}

Expand Down
47 changes: 33 additions & 14 deletions src/SocketServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public function __construct($uri, array $context = array(), LoopInterface $loop
if (preg_match('#^(?:\w+://)?\d+$#', $uri)) {
throw new \InvalidArgumentException(
'Invalid URI given (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
);
}

Expand Down Expand Up @@ -135,25 +135,42 @@ public static function accept($socket)
* The errno and errstr values describes the type of error that has been
* encountered. This method tries to look up the given errstr and find a
* matching errno value which can be useful to provide more context to error
* messages. It goes through the list of known errno constants when
* ext-sockets is available to find an errno matching the given errstr.
* messages. It goes through the list of known errno constants when either
* `ext-sockets`, `ext-posix` or `ext-pcntl` is available to find an errno
* matching the given errstr.
*
* @param string $errstr
* @return int errno value (e.g. value of `SOCKET_ECONNREFUSED`) or 0 if not found
* @internal
* @copyright Copyright (c) 2018 Christian Lück, taken from https://github.com/clue/errno with permission
* @copyright Copyright (c) 2023 Christian Lück, taken from https://github.com/clue/errno with permission
* @codeCoverageIgnore
*/
public static function errno($errstr)
{
if (\function_exists('socket_strerror')) {
// PHP defines the required `strerror()` function through either `ext-sockets`, `ext-posix` or `ext-pcntl`
$strerror = \function_exists('socket_strerror') ? 'socket_strerror' : (\function_exists('posix_strerror') ? 'posix_strerror' : (\function_exists('pcntl_strerror') ? 'pcntl_strerror' : null));
if ($strerror !== null) {
assert(\is_string($strerror) && \is_callable($strerror));

// PHP defines most useful errno constants like `ECONNREFUSED` through constants in `ext-sockets` like `SOCKET_ECONNREFUSED`
// PHP also defines a hand full of errno constants like `EMFILE` through constants in `ext-pcntl` like `PCNTL_EMFILE`
// go through list of all defined constants like `SOCKET_E*` and `PCNTL_E*` and see if they match the given `$errstr`
foreach (\get_defined_constants(false) as $name => $value) {
if (\strpos($name, 'SOCKET_E') === 0 && \socket_strerror($value) === $errstr) {
if (\is_int($value) && (\strpos($name, 'SOCKET_E') === 0 || \strpos($name, 'PCNTL_E') === 0) && $strerror($value) === $errstr) {
return $value;
}
}

// if we reach this, no matching errno constant could be found (unlikely when `ext-sockets` is available)
// go through list of all possible errno values from 1 to `MAX_ERRNO` and see if they match the given `$errstr`
for ($errno = 1, $max = \defined('MAX_ERRNO') ? \MAX_ERRNO : 4095; $errno <= $max; ++$errno) {
if ($strerror($errno) === $errstr) {
return $errno;
}
}
}

// if we reach this, no matching errno value could be found (unlikely when either `ext-sockets`, `ext-posix` or `ext-pcntl` is available)
return 0;
}

Expand All @@ -164,8 +181,8 @@ public static function errno($errstr)
* This method tries to look up the given errno value and find a matching
* errno constant name which can be useful to provide more context and more
* descriptive error messages. It goes through the list of known errno
* constants when ext-sockets is available to find the matching errno
* constant name.
* constants when either `ext-sockets` or `ext-pcntl` is available to find
* the matching errno constant name.
*
* Because this method is used to append more context to error messages, the
* constant name will be prefixed with a space and put between parenthesis
Expand All @@ -174,19 +191,21 @@ public static function errno($errstr)
* @param int $errno
* @return string e.g. ` (ECONNREFUSED)` or empty string if no matching const for the given errno could be found
* @internal
* @copyright Copyright (c) 2018 Christian Lück, taken from https://github.com/clue/errno with permission
* @copyright Copyright (c) 2023 Christian Lück, taken from https://github.com/clue/errno with permission
* @codeCoverageIgnore
*/
public static function errconst($errno)
{
if (\function_exists('socket_strerror')) {
foreach (\get_defined_constants(false) as $name => $value) {
if ($value === $errno && \strpos($name, 'SOCKET_E') === 0) {
return ' (' . \substr($name, 7) . ')';
}
// PHP defines most useful errno constants like `ECONNREFUSED` through constants in `ext-sockets` like `SOCKET_ECONNREFUSED`
// PHP also defines a hand full of errno constants like `EMFILE` through constants in `ext-pcntl` like `PCNTL_EMFILE`
// go through list of all defined constants like `SOCKET_E*` and `PCNTL_E*` and see if they match the given `$errno`
foreach (\get_defined_constants(false) as $name => $value) {
if ($value === $errno && (\strpos($name, 'SOCKET_E') === 0 || \strpos($name, 'PCNTL_E') === 0)) {
return ' (' . \substr($name, \strpos($name, '_') + 1) . ')';
}
}

// if we reach this, no matching errno constant could be found (unlikely when `ext-sockets` is available)
return '';
}
}
4 changes: 2 additions & 2 deletions src/TcpConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ public function connect($uri)
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
return Promise\reject(new \InvalidArgumentException(
'Given URI "' . $uri . '" is invalid (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
));
}

$ip = \trim($parts['host'], '[]');
if (@\inet_pton($ip) === false) {
return Promise\reject(new \InvalidArgumentException(
'Given URI "' . $uri . '" does not contain a valid host IP (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
));
}

Expand Down
4 changes: 2 additions & 2 deletions src/TcpServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,14 @@ public function __construct($uri, LoopInterface $loop = null, array $context = a
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
throw new \InvalidArgumentException(
'Invalid URI "' . $uri . '" given (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
);
}

if (@\inet_pton(\trim($parts['host'], '[]')) === false) {
throw new \InvalidArgumentException(
'Given URI "' . $uri . '" does not contain a valid host IP (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/UnixConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function connect($path)
} elseif (\substr($path, 0, 7) !== 'unix://') {
return Promise\reject(new \InvalidArgumentException(
'Given URI "' . $path . '" is invalid (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
));
}

Expand Down
2 changes: 1 addition & 1 deletion src/UnixServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function __construct($path, LoopInterface $loop = null, array $context =
} elseif (\substr($path, 0, 7) !== 'unix://') {
throw new \InvalidArgumentException(
'Given URI "' . $path . '" is invalid (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : 22
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
);
}

Expand Down
10 changes: 5 additions & 5 deletions tests/ConnectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public function testConnectorWithUnknownSchemeAlwaysFails()
$promise->then(null, $this->expectCallableOnceWithException(
'RuntimeException',
'No connector available for URI scheme "unknown" (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
));
}

Expand All @@ -189,7 +189,7 @@ public function testConnectorWithDisabledTcpDefaultSchemeAlwaysFails()
$promise->then(null, $this->expectCallableOnceWithException(
'RuntimeException',
'No connector available for URI scheme "tcp" (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
));
}

Expand All @@ -205,7 +205,7 @@ public function testConnectorWithDisabledTcpSchemeAlwaysFails()
$promise->then(null, $this->expectCallableOnceWithException(
'RuntimeException',
'No connector available for URI scheme "tcp" (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
));
}

Expand All @@ -221,7 +221,7 @@ public function testConnectorWithDisabledTlsSchemeAlwaysFails()
$promise->then(null, $this->expectCallableOnceWithException(
'RuntimeException',
'No connector available for URI scheme "tls" (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
));
}

Expand All @@ -237,7 +237,7 @@ public function testConnectorWithDisabledUnixSchemeAlwaysFails()
$promise->then(null, $this->expectCallableOnceWithException(
'RuntimeException',
'No connector available for URI scheme "unix" (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
));
}

Expand Down
2 changes: 1 addition & 1 deletion tests/DnsConnectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public function testRejectsImmediatelyIfUriIsInvalid()
$promise->then(null, $this->expectCallableOnceWithException(
'InvalidArgumentException',
'Given URI "////" is invalid (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
));
}

Expand Down
4 changes: 2 additions & 2 deletions tests/FdServerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function testCtorThrowsForInvalidFd()
$this->setExpectedException(
'InvalidArgumentException',
'Invalid FD number given (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
);
new FdServer(-1, $loop);
}
Expand All @@ -45,7 +45,7 @@ public function testCtorThrowsForInvalidUrl()
$this->setExpectedException(
'InvalidArgumentException',
'Invalid FD number given (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
);
new FdServer('tcp://127.0.0.1:8080', $loop);
}
Expand Down
8 changes: 4 additions & 4 deletions tests/FunctionalTcpServerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ public function testFailsToListenOnInvalidUri()
$this->setExpectedException(
'InvalidArgumentException',
'Invalid URI "tcp://///" given (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
);
new TcpServer('///');
}
Expand All @@ -404,7 +404,7 @@ public function testFailsToListenOnUriWithoutPort()
$this->setExpectedException(
'InvalidArgumentException',
'Invalid URI "tcp://127.0.0.1" given (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
);
new TcpServer('127.0.0.1');
}
Expand All @@ -414,7 +414,7 @@ public function testFailsToListenOnUriWithWrongScheme()
$this->setExpectedException(
'InvalidArgumentException',
'Invalid URI "udp://127.0.0.1:0" given (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
);
new TcpServer('udp://127.0.0.1:0');
}
Expand All @@ -424,7 +424,7 @@ public function testFailsToListenOnUriWIthHostname()
$this->setExpectedException(
'InvalidArgumentException',
'Given URI "tcp://localhost:8080" does not contain a valid host IP (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
);
new TcpServer('localhost:8080');
}
Expand Down
2 changes: 1 addition & 1 deletion tests/HappyEyeBallsConnectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ public function testRejectsImmediatelyIfUriIsInvalid()
$promise->then(null, $this->expectCallableOnceWithException(
'InvalidArgumentException',
'Given URI "////" is invalid (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
));
}

Expand Down
2 changes: 1 addition & 1 deletion tests/SecureConnectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public function testConnectionToInvalidSchemeWillReject()
$promise->then(null, $this->expectCallableOnceWithException(
'InvalidArgumentException',
'Given URI "tcp://example.com:80" is invalid (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
));
}

Expand Down
6 changes: 3 additions & 3 deletions tests/SocketServerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function testConstructorWithInvalidUriThrows()
$this->setExpectedException(
'InvalidArgumentException',
'Invalid URI "tcp://invalid URI" given (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
);
new SocketServer('invalid URI');
}
Expand All @@ -50,7 +50,7 @@ public function testConstructorWithInvalidUriWithPortOnlyThrows()
$this->setExpectedException(
'InvalidArgumentException',
'Invalid URI given (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
);
new SocketServer('0');
}
Expand All @@ -60,7 +60,7 @@ public function testConstructorWithInvalidUriWithSchemaAndPortOnlyThrows()
$this->setExpectedException(
'InvalidArgumentException',
'Invalid URI given (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
);
new SocketServer('tcp://0');
}
Expand Down
4 changes: 2 additions & 2 deletions tests/TcpConnectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ public function connectionToHostnameShouldFailImmediately()
$promise->then(null, $this->expectCallableOnceWithException(
'InvalidArgumentException',
'Given URI "tcp://www.google.com:80" does not contain a valid host IP (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
));
}

Expand All @@ -296,7 +296,7 @@ public function connectionToInvalidPortShouldFailImmediately()
$promise->then(null, $this->expectCallableOnceWithException(
'InvalidArgumentException',
'Given URI "tcp://255.255.255.255:12345678" is invalid (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
));
}

Expand Down
2 changes: 1 addition & 1 deletion tests/UnixConnectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public function testInvalidScheme()
$promise->then(null, $this->expectCallableOnceWithException(
'InvalidArgumentException',
'Given URI "tcp://google.com:80" is invalid (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
));
}

Expand Down
2 changes: 1 addition & 1 deletion tests/UnixServerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ public function testCtorThrowsForInvalidAddressScheme()
$this->setExpectedException(
'InvalidArgumentException',
'Given URI "tcp://localhost:0" is invalid (EINVAL)',
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
defined('SOCKET_EINVAL') ? SOCKET_EINVAL : (defined('PCNTL_EINVAL') ? PCNTL_EINVAL : 22)
);
new UnixServer('tcp://localhost:0', $loop);
}
Expand Down
0