1
0
Fork 0

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 set
pull/11794/head
Jordi Boggiano 2024-01-11 17:13:54 +01:00 committed by GitHub
parent c069174ac7
commit 3491986ad3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 39 additions and 17 deletions

View File

@ -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
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
If you use a proxy, but it does not support the request_fulluri flag, then you

View File

@ -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
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:**
On linux, it seems that running this command helps to make ipv4 traffic have a

View File

@ -765,16 +765,6 @@ parameters:
count: 1
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\\.$#"
count: 2
@ -4314,7 +4304,7 @@ parameters:
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
path: ../src/Composer/Util/Http/CurlDownloader.php

View File

@ -553,7 +553,7 @@ EOT
if ($result) {
foreach ($result as $message) {
$io->write($message);
$io->write(trim($message));
}
}
}
@ -776,6 +776,11 @@ EOT
$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;
}

View File

@ -28,7 +28,7 @@ use React\Promise\Promise;
* @internal
* @author Jordi Boggiano <j.boggiano@seld.be>
* @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}
*/
class CurlDownloader
@ -143,7 +143,7 @@ class CurlDownloader
/**
* @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
*
* @return int internal job id
@ -155,8 +155,15 @@ class CurlDownloader
'redirects' => 0,
'retries' => 0,
'storeAuth' => false,
'ipResolve' => null,
], $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;
// 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_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')) {
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'))
) && $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->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]);
$this->restartJobWithDelay($job, $job['url'], $attributes);
continue;
}
@ -582,7 +599,7 @@ class CurlDownloader
* @param Job $job
* @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
{
@ -600,7 +617,7 @@ class CurlDownloader
* @param Job $job
* @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
{