Add IPv4 fallback on connection timeout, and adds COMPOSER_IPRESOLVE env var (#11791)
* Add IPv4 fallback on connection timeout, and adds COMPOSER_IPRESOLVE env var, fixes #530 * Address feedback * Add warning in diagnose command when COMPOSER_IPRESOLVE is setpull/11794/head
parent
c069174ac7
commit
3491986ad3
|
@ -1244,6 +1244,11 @@ defaults to 12 and must be between 1 and 50. If your proxy has issues with
|
||||||
concurrency maybe you want to lower this. Increasing it should generally not result
|
concurrency maybe you want to lower this. Increasing it should generally not result
|
||||||
in performance gains.
|
in performance gains.
|
||||||
|
|
||||||
|
### COMPOSER_IPRESOLVE
|
||||||
|
|
||||||
|
Set to `4` or `6` to force IPv4 or IPv6 DNS resolution. This only works when the
|
||||||
|
curl extension is used for downloads.
|
||||||
|
|
||||||
### HTTP_PROXY_REQUEST_FULLURI
|
### HTTP_PROXY_REQUEST_FULLURI
|
||||||
|
|
||||||
If you use a proxy, but it does not support the request_fulluri flag, then you
|
If you use a proxy, but it does not support the request_fulluri flag, then you
|
||||||
|
|
|
@ -304,6 +304,11 @@ open stream: Operation timed out
|
||||||
We recommend you fix your IPv6 setup. If that is not possible, you can try the
|
We recommend you fix your IPv6 setup. If that is not possible, you can try the
|
||||||
following workarounds:
|
following workarounds:
|
||||||
|
|
||||||
|
**Generic Workaround:**
|
||||||
|
|
||||||
|
Set the [`COMPOSER_IPRESOLVE=4`](../03-cli.md#composer-ipresolve) environment variable which will force curl to resolve
|
||||||
|
domains using IPv4. This only works when the curl extension is used for downloads.
|
||||||
|
|
||||||
**Workaround Linux:**
|
**Workaround Linux:**
|
||||||
|
|
||||||
On linux, it seems that running this command helps to make ipv4 traffic have a
|
On linux, it seems that running this command helps to make ipv4 traffic have a
|
||||||
|
|
|
@ -765,16 +765,6 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: ../src/Composer/Command/ShowCommand.php
|
path: ../src/Composer/Command/ShowCommand.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Only booleans are allowed in \\|\\|, array\\<string, Composer\\\\Package\\\\Link\\> given on the left side\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: ../src/Composer/Command/ShowCommand.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Only booleans are allowed in \\|\\|, array\\<string, Composer\\\\Package\\\\Link\\> given on the right side\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: ../src/Composer/Command/ShowCommand.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#1 \\$arrayTree of method Composer\\\\Command\\\\ShowCommand\\:\\:displayPackageTree\\(\\) expects array\\<int, array\\<string, array\\|string\\>\\>, array\\<int, array\\<string, array\\<int, array\\<string, array\\|string\\>\\>\\|string\\|null\\>\\> given\\.$#"
|
message: "#^Parameter \\#1 \\$arrayTree of method Composer\\\\Command\\\\ShowCommand\\:\\:displayPackageTree\\(\\) expects array\\<int, array\\<string, array\\|string\\>\\>, array\\<int, array\\<string, array\\<int, array\\<string, array\\|string\\>\\>\\|string\\|null\\>\\> given\\.$#"
|
||||||
count: 2
|
count: 2
|
||||||
|
@ -4314,7 +4304,7 @@ parameters:
|
||||||
path: ../src/Composer/Util/Http/CurlDownloader.php
|
path: ../src/Composer/Util/Http/CurlDownloader.php
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$jobs \\(array\\<array\\{url\\: non\\-empty\\-string, origin\\: string, attributes\\: array\\{retryAuthFailure\\: bool, redirects\\: int\\<0, max\\>, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\) does not accept non\\-empty\\-array\\<array\\{url\\: non\\-empty\\-string, origin\\: string, attributes\\: array\\{retryAuthFailure\\: bool, redirects\\: int\\<0, max\\>, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle\\|resource, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\.$#"
|
message: "#^Property Composer\\\\Util\\\\Http\\\\CurlDownloader\\:\\:\\$jobs \\(array\\<array\\{url\\: non\\-empty\\-string, origin\\: string, attributes\\: array\\{retryAuthFailure\\: bool, redirects\\: int\\<0, max\\>, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool, ipResolve\\: 4\\|6\\|null\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\) does not accept non\\-empty\\-array\\<array\\{url\\: non\\-empty\\-string, origin\\: string, attributes\\: array\\{retryAuthFailure\\: bool, redirects\\: int\\<0, max\\>, retries\\: int\\<0, max\\>, storeAuth\\: 'prompt'\\|bool, ipResolve\\: 4\\|6\\|null\\}, options\\: array, progress\\: array, curlHandle\\: CurlHandle\\|resource, filename\\: string\\|null, headerHandle\\: resource, \\.\\.\\.\\}\\>\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: ../src/Composer/Util/Http/CurlDownloader.php
|
path: ../src/Composer/Util/Http/CurlDownloader.php
|
||||||
|
|
||||||
|
|
|
@ -553,7 +553,7 @@ EOT
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
foreach ($result as $message) {
|
foreach ($result as $message) {
|
||||||
$io->write($message);
|
$io->write(trim($message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -776,6 +776,11 @@ EOT
|
||||||
$out($iniMessage, 'comment');
|
$out($iniMessage, 'comment');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (in_array(Platform::getEnv('COMPOSER_IPRESOLVE'), ['4', '6'], true)) {
|
||||||
|
$warnings['ipresolve'] = true;
|
||||||
|
$out('The COMPOSER_IPRESOLVE env var is set to ' . Platform::getEnv('COMPOSER_IPRESOLVE') .' which may result in network failures below.', 'comment');
|
||||||
|
}
|
||||||
|
|
||||||
return count($warnings) === 0 && count($errors) === 0 ? true : $output;
|
return count($warnings) === 0 && count($errors) === 0 ? true : $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ use React\Promise\Promise;
|
||||||
* @internal
|
* @internal
|
||||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
* @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int<0, max>, retries: int<0, max>, storeAuth: 'prompt'|bool}
|
* @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int<0, max>, retries: int<0, max>, storeAuth: 'prompt'|bool, ipResolve: 4|6|null}
|
||||||
* @phpstan-type Job array{url: non-empty-string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: \CurlHandle, filename: string|null, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable}
|
* @phpstan-type Job array{url: non-empty-string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: \CurlHandle, filename: string|null, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable}
|
||||||
*/
|
*/
|
||||||
class CurlDownloader
|
class CurlDownloader
|
||||||
|
@ -143,7 +143,7 @@ class CurlDownloader
|
||||||
/**
|
/**
|
||||||
* @param mixed[] $options
|
* @param mixed[] $options
|
||||||
*
|
*
|
||||||
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, retries?: int<0, max>, storeAuth?: 'prompt'|bool} $attributes
|
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, retries?: int<0, max>, storeAuth?: 'prompt'|bool, ipResolve?: 4|6|null} $attributes
|
||||||
* @param non-empty-string $url
|
* @param non-empty-string $url
|
||||||
*
|
*
|
||||||
* @return int internal job id
|
* @return int internal job id
|
||||||
|
@ -155,8 +155,15 @@ class CurlDownloader
|
||||||
'redirects' => 0,
|
'redirects' => 0,
|
||||||
'retries' => 0,
|
'retries' => 0,
|
||||||
'storeAuth' => false,
|
'storeAuth' => false,
|
||||||
|
'ipResolve' => null,
|
||||||
], $attributes);
|
], $attributes);
|
||||||
|
|
||||||
|
if ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '4') {
|
||||||
|
$attributes['ipResolve'] = 4;
|
||||||
|
} elseif ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '6') {
|
||||||
|
$attributes['ipResolve'] = 6;
|
||||||
|
}
|
||||||
|
|
||||||
$originalOptions = $options;
|
$originalOptions = $options;
|
||||||
|
|
||||||
// check URL can be accessed (i.e. is not insecure), but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
|
// check URL can be accessed (i.e. is not insecure), but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
|
||||||
|
@ -199,6 +206,12 @@ class CurlDownloader
|
||||||
curl_setopt($curlHandle, CURLOPT_ENCODING, ""); // let cURL set the Accept-Encoding header to what it supports
|
curl_setopt($curlHandle, CURLOPT_ENCODING, ""); // let cURL set the Accept-Encoding header to what it supports
|
||||||
curl_setopt($curlHandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
curl_setopt($curlHandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
||||||
|
|
||||||
|
if ($attributes['ipResolve'] === 4) {
|
||||||
|
curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
||||||
|
} elseif ($attributes['ipResolve'] === 6) {
|
||||||
|
curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
|
||||||
|
}
|
||||||
|
|
||||||
if (function_exists('curl_share_init')) {
|
if (function_exists('curl_share_init')) {
|
||||||
curl_setopt($curlHandle, CURLOPT_SHARE, $this->shareHandle);
|
curl_setopt($curlHandle, CURLOPT_SHARE, $this->shareHandle);
|
||||||
}
|
}
|
||||||
|
@ -352,8 +365,12 @@ class CurlDownloader
|
||||||
|| (in_array($errno, [56 /* CURLE_RECV_ERROR */, 35 /* CURLE_SSL_CONNECT_ERROR */], true) && str_contains((string) $error, 'Connection reset by peer'))
|
|| (in_array($errno, [56 /* CURLE_RECV_ERROR */, 35 /* CURLE_SSL_CONNECT_ERROR */], true) && str_contains((string) $error, 'Connection reset by peer'))
|
||||||
) && $job['attributes']['retries'] < $this->maxRetries
|
) && $job['attributes']['retries'] < $this->maxRetries
|
||||||
) {
|
) {
|
||||||
|
$attributes = ['retries' => $job['attributes']['retries'] + 1];
|
||||||
|
if ($errno === 7 && !isset($job['attributes']['ipResolve'])) { // CURLE_COULDNT_CONNECT, retry forcing IPv4 if no IP stack was selected
|
||||||
|
$attributes['ipResolve'] = 4;
|
||||||
|
}
|
||||||
$this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to curl error '. $errno, true, IOInterface::DEBUG);
|
$this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to curl error '. $errno, true, IOInterface::DEBUG);
|
||||||
$this->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]);
|
$this->restartJobWithDelay($job, $job['url'], $attributes);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -582,7 +599,7 @@ class CurlDownloader
|
||||||
* @param Job $job
|
* @param Job $job
|
||||||
* @param non-empty-string $url
|
* @param non-empty-string $url
|
||||||
*
|
*
|
||||||
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries?: int<1, max>} $attributes
|
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries?: int<1, max>, ipResolve?: 4|6} $attributes
|
||||||
*/
|
*/
|
||||||
private function restartJob(array $job, string $url, array $attributes = []): void
|
private function restartJob(array $job, string $url, array $attributes = []): void
|
||||||
{
|
{
|
||||||
|
@ -600,7 +617,7 @@ class CurlDownloader
|
||||||
* @param Job $job
|
* @param Job $job
|
||||||
* @param non-empty-string $url
|
* @param non-empty-string $url
|
||||||
*
|
*
|
||||||
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries: int<1, max>} $attributes
|
* @param array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries: int<1, max>, ipResolve?: 4|6} $attributes
|
||||||
*/
|
*/
|
||||||
private function restartJobWithDelay(array $job, string $url, array $attributes): void
|
private function restartJobWithDelay(array $job, string $url, array $attributes): void
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue