Skip to content

Commit

Permalink
Improve errno detection for failed connections without ext-sockets
Browse files Browse the repository at this point in the history
  • Loading branch information
clue committed Apr 13, 2023
1 parent 936546b commit cdc7a31
Show file tree
Hide file tree
Showing 20 changed files with 65 additions and 46 deletions.
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 comments on commit cdc7a31

Please sign in to comment.