diff --git a/docs/faq.txt b/docs/faq.txt index ec541a820..ff9acc025 100644 --- a/docs/faq.txt +++ b/docs/faq.txt @@ -53,6 +53,25 @@ SAPI. Additionally, :php:`php_ini_loaded_file() ` and :php:`php_ini_scanned_files() ` may be used to determine exactly which INI files have been loaded by PHP. +To debug issues with the extension not being loaded, you can use the +``detect-extension`` script provided in the tools directory. You can run this +script from the CLI or include it in a script accessible via your web server. +The tool will point out potential issues and installation instructions for your +system. Assuming you've installed the library through Composer, you can call the +script from the vendor directory: + +.. code-block:: none + + php vendor/mongodb/mongodb/tools/detect-extension.php + +If you want to check configuration for a web server SAPI, include the file in +a script accessible from the web server and open it in your browser. Remember to +wrap the script in ``
`` tags to properly format its output:
+
+.. code-block:: php
+
+   
+ Loading an Incompatible DLL on Windows ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -80,7 +99,7 @@ for the correct environment. Server Selection Failures ------------------------- -The follow are all examples of +The following are all examples of :doc:`Server Selection ` failures: .. code-block:: none @@ -133,3 +152,28 @@ failure: but was dropped or otherwise timeout out due to latency. - "TLS handshake failed" suggests something related to TLS or OCSP verification and is sometimes indicative of misconfigured TLS certificates. + +In the case of a connection failure, you can use the ``connect`` tool to try and +receive more information. This tool attempts to connect to each host in a +connection string using socket functions to see if it is able to establish a +connection, send, and receive data. The tool takes the connection string to a +MongoDB deployment as its only argument. Assuming you've installed the library +through Composer, you would call the script from the vendor directory: + +.. code-block:: none + + php vendor/mongodb/mongodb/tools/connect.php mongodb://127.0.0.1:27017 + +In case the server does not accept connections, the output will look like this: + +.. code-block:: none + + Looking up MongoDB at mongodb://127.0.0.1:27017 + Found 1 host(s) in the URI. Will attempt to connect to each. + + Could not connect to 127.0.0.1:27017: Connection refused + +.. note:: + + The tool only supports the ``mongodb://`` URI schema. Using the + ``mongodb+srv`` scheme is not supported. diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 7db7074b7..a2d56ccce 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -12,6 +12,7 @@ src examples tests + tools diff --git a/tools/connect.php b/tools/connect.php new file mode 100644 index 000000000..121f727c7 --- /dev/null +++ b/tools/connect.php @@ -0,0 +1,137 @@ + ['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); +} diff --git a/tools/detect-extension.php b/tools/detect-extension.php new file mode 100644 index 000000000..4b599fd4b --- /dev/null +++ b/tools/detect-extension.php @@ -0,0 +1,98 @@ + $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); + } +}