-
Notifications
You must be signed in to change notification settings - Fork 260
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PHPLIB-572: Add debugging tools (#996)
* Add connection debugging script * Fix grammar * Add extension detection script * Fix wording feedback * Document using pre tags for formatted output in web SAPI
- Loading branch information
Showing
4 changed files
with
281 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
<?php | ||
|
||
function getHosts(string $uri): array | ||
{ | ||
if (strpos($uri, '://') === false) { | ||
return [$uri]; | ||
} | ||
|
||
$parsed = parse_url($uri); | ||
|
||
if (isset($parsed['scheme']) && $parsed['scheme'] !== 'mongodb') { | ||
// TODO: Resolve SRV records (https://github.com/mongodb/specifications/blob/master/source/initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.rst) | ||
throw new RuntimeException('Unsupported scheme: ' . $parsed['scheme']); | ||
} | ||
|
||
$hosts = sprintf('%s:%d', $parsed['host'], $parsed['port'] ?? 27017); | ||
|
||
return explode(',', $hosts); | ||
} | ||
|
||
/** @param resource $stream */ | ||
function streamWrite($stream, string $data): int | ||
{ | ||
for ($written = 0; $written < strlen($data); $written += $fwrite) { | ||
$fwrite = fwrite($stream, substr($data, $written)); | ||
|
||
if ($fwrite === false) { | ||
return $written; | ||
} | ||
} | ||
|
||
return $written; | ||
} | ||
|
||
/** @param resource $stream */ | ||
function streamRead($stream, int $length): string | ||
{ | ||
$contents = ''; | ||
|
||
while (! feof($stream) && strlen($contents) < $length) { | ||
$fread = fread($stream, min($length - strlen($contents), 8192)); | ||
|
||
if ($fread === false) { | ||
return $contents; | ||
} | ||
|
||
$contents .= $fread; | ||
} | ||
|
||
return $contents; | ||
} | ||
|
||
function connect(string $host, bool $ssl): void | ||
{ | ||
$uri = sprintf('%s://%s', $ssl ? 'ssl' : 'tcp', $host); | ||
$context = stream_context_create($ssl ? ['ssl' => ['capture_peer_cert' => true]] : []); | ||
$client = @stream_socket_client($uri, $errno, $errorMessage, 5, STREAM_CLIENT_CONNECT, $context); | ||
|
||
if ($client === false) { | ||
printf("Could not connect to %s: %s\n", $host, $errorMessage); | ||
|
||
return; | ||
} | ||
|
||
if ($ssl) { | ||
$peerCertificate = stream_context_get_params($client)['options']['ssl']['peer_certificate'] ?? null; | ||
|
||
if (! isset($peerCertificate)) { | ||
printf("Could not capture peer certificate for %s\n", $host); | ||
|
||
return; | ||
} | ||
|
||
$certificateProperties = openssl_x509_parse($peerCertificate); | ||
|
||
// TODO: Check that the certificate common name (CN) matches the hostname | ||
$now = new DateTime(); | ||
$validFrom = DateTime::createFromFormat('U', $certificateProperties['validFrom_time_t']); | ||
$validTo = DateTime::createFromFormat('U', $certificateProperties['validTo_time_t']); | ||
$isValid = $now >= $validFrom && $now <= $validTo; | ||
|
||
printf("Peer certificate for %s is %s\n", $host, $isValid ? 'valid' : 'expired'); | ||
|
||
if (! $isValid) { | ||
printf(" Valid from %s to %s\n", $validFrom->format('c'), $validTo->format('c')); | ||
} | ||
} | ||
|
||
$request = pack( | ||
'Va*xVVa*', | ||
1 << 2 /* slaveOk */, | ||
'admin.$cmd', /* namespace */ | ||
0, /* numberToSkip */ | ||
1, /* numberToReturn */ | ||
hex2bin('130000001069734d6173746572000100000000') /* { "isMaster": 1 } */ | ||
); | ||
$requestLength = 16 /* MsgHeader length */ + strlen($request); | ||
$header = pack('V4', $requestLength, 0 /* requestID */, 0 /* responseTo */, 2004 /* OP_QUERY */); | ||
|
||
if ($requestLength !== streamWrite($client, $header . $request)) { | ||
printf("Could not write request to %s\n", $host); | ||
|
||
return; | ||
} | ||
|
||
$data = streamRead($client, 4); | ||
|
||
if ($data === false || strlen($data) !== 4) { | ||
printf("Could not read response header from %s\n", $host); | ||
|
||
return; | ||
} | ||
|
||
[, $responseLength] = unpack('V', $data); | ||
|
||
$data = streamRead($client, $responseLength - 4); | ||
|
||
if ($data === false || strlen($data) !== $responseLength - 4) { | ||
printf("Could not read response from %s\n", $host); | ||
|
||
return; | ||
} | ||
|
||
printf("Successfully received response from %s\n", $host); | ||
} | ||
|
||
$uri = $argv[1] ?? 'mongodb://127.0.0.1'; | ||
printf("Looking up MongoDB at %s\n", $uri); | ||
$hosts = getHosts($uri); | ||
$ssl = stripos(parse_url($uri, PHP_URL_QUERY) ?? '', 'ssl=true') !== false; | ||
|
||
printf("Found %d host(s) in the URI. Will attempt to connect to each.\n", count($hosts)); | ||
|
||
foreach ($hosts as $host) { | ||
echo "\n"; | ||
connect($host, $ssl); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<?php | ||
|
||
function grepIniFile(string $filename, string $extension): int | ||
{ | ||
$lines = []; | ||
|
||
foreach (new SplFileObject($filename) as $i => $line) { | ||
if (strpos($line, 'extension') === false) { | ||
continue; | ||
} | ||
|
||
if (strpos($line, $extension) === false) { | ||
continue; | ||
} | ||
|
||
$lines[$i] = $line; | ||
} | ||
|
||
if (empty($lines)) { | ||
printf("No interesting lines in %s.\n", $filename); | ||
|
||
return 0; | ||
} | ||
|
||
printf("Interesting lines in %s...\n", $filename); | ||
foreach ($lines as $i => $line) { | ||
printf(" %d: %s\n", $i + 1, trim($line)); | ||
} | ||
|
||
return count($lines); | ||
} | ||
|
||
$extension = $argv[1] ?? 'mongodb'; | ||
$extensionDir = ini_get('extension_dir'); | ||
|
||
$version = phpversion($extension); | ||
|
||
if ($version !== false) { | ||
printf("Extension \"%s\" is loaded. Version: %s\n", $extension, $version); | ||
exit; | ||
} | ||
|
||
printf("Extension \"%s\" is not loaded. Will attempt to scan INI files.\n", $extension); | ||
|
||
// Check main INI file | ||
$ini = php_ini_loaded_file(); | ||
$lines = 0; | ||
|
||
if ($ini === false) { | ||
printf("No php.ini file is loaded. Will attempt to scan additional INI files.\n"); | ||
} else { | ||
$lines += grepIniFile($ini, $extension); | ||
} | ||
|
||
// Check additional INI files in scan directory | ||
// See: https://www.php.net/manual/en/configuration.file.php#configuration.file.scan | ||
$files = php_ini_scanned_files(); | ||
|
||
if (empty($files)) { | ||
printf("No additional INI files are loaded. Nothing left to scan.\n"); | ||
} else { | ||
foreach (explode(',', $files) as $ini) { | ||
$lines += grepIniFile(trim($ini), $extension); | ||
} | ||
} | ||
|
||
$mask = defined('PHP_WINDOWS_VERSION_BUILD') ? 'php_%s.dll' : '%s.so'; | ||
$filename = sprintf($mask, $extension); | ||
$extensionFileExists = file_exists($extensionDir . '/' . $filename); | ||
|
||
echo "\n"; | ||
printf("PHP will look for extensions in: %s\n", $extensionDir); | ||
printf("Checking if that directory is readable: %s\n", is_dir($extensionDir) || ! is_readable($extensionDir) ? 'yes' : 'no'); | ||
printf("Checking if extension file exists in that directory: %s\n", $extensionFileExists ? 'yes' : 'no'); | ||
echo "\n"; | ||
|
||
if ($extensionFileExists) { | ||
printf("A file named %s exists in the extension directory. Make sure you have enabled the extension in php.ini.\n", $filename); | ||
} elseif (! defined('PHP_WINDOWS_VERSION_BUILD')) { | ||
// Installation instructions for non-Windows systems are only necessary if the extension file does not exist. | ||
printf("You should install the extension using the pecl command in %s\n", PHP_BINDIR); | ||
printf("After installing the extension, you should add \"extension=%s\" to php.ini\n", $filename); | ||
} | ||
|
||
if (defined('PHP_WINDOWS_VERSION_BUILD')) { | ||
$zts = PHP_ZTS ? 'Thread Safe (TS)' : 'Non Thread Safe (NTS)'; | ||
$arch = PHP_INT_SIZE === 8 ? 'x64' : 'x86'; | ||
$dll = sprintf("%d.%d %s %s", PHP_MAJOR_VERSION, PHP_MINOR_VERSION, $zts, $arch); | ||
|
||
printf("You likely need to download a Windows DLL for: %s\n", $dll); | ||
printf("Windows DLLs should be available from: https://pecl.php.net/package/%s\n", $extension); | ||
|
||
if ($extensionFileExists) { | ||
echo "If you have enabled the extension in php.ini and it is not loading, make sure you have downloaded the correct DLL file as indicated above.\n"; | ||
} else { | ||
printf("After installing the extension, you should add \"extension=%s\" to php.ini\n", $filename); | ||
} | ||
} |