From ba920d81b11950c66abe0a62f0599e8382793837 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Wed, 19 Oct 2022 16:07:13 +0200 Subject: [PATCH 1/5] Add connection debugging script --- docs/faq.txt | 20 +++++++ phpcs.xml.dist | 1 + tools/connect.php | 137 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 tools/connect.php diff --git a/docs/faq.txt b/docs/faq.txt index ec541a820..b401183dc 100644 --- a/docs/faq.txt +++ b/docs/faq.txt @@ -133,3 +133,23 @@ 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 connection 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 mysql://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 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); +} From 45a0873be6c9b77372193a77e412bab9e2817720 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Wed, 19 Oct 2022 16:09:35 +0200 Subject: [PATCH 2/5] Fix grammar --- docs/faq.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.txt b/docs/faq.txt index b401183dc..6e214d928 100644 --- a/docs/faq.txt +++ b/docs/faq.txt @@ -80,7 +80,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 From bc7f2cb0c6a196c098c1735e82f8ba903c93acc5 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 20 Oct 2022 16:20:08 +0200 Subject: [PATCH 3/5] Add extension detection script --- docs/faq.txt | 14 +++++ tools/detect-extension.php | 102 +++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 tools/detect-extension.php diff --git a/docs/faq.txt b/docs/faq.txt index 6e214d928..a341bc30d 100644 --- a/docs/faq.txt +++ b/docs/faq.txt @@ -53,6 +53,20 @@ 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. + Loading an Incompatible DLL on Windows ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tools/detect-extension.php b/tools/detect-extension.php new file mode 100644 index 000000000..6da2336fb --- /dev/null +++ b/tools/detect-extension.php @@ -0,0 +1,102 @@ + $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 (PHP_SAPI !== 'cli') { + echo '
';
+}
+
+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);
+    }
+}

From e718c5c5083599787b37aa77edacb0ca6f5a7b90 Mon Sep 17 00:00:00 2001
From: Andreas Braun 
Date: Mon, 24 Oct 2022 08:49:23 +0200
Subject: [PATCH 4/5] Fix wording feedback

---
 docs/faq.txt | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/docs/faq.txt b/docs/faq.txt
index a341bc30d..e23a828f8 100644
--- a/docs/faq.txt
+++ b/docs/faq.txt
@@ -57,7 +57,7 @@ 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
+system. Assuming you've installed the library through Composer, you can call the
 script from the vendor directory:
 
 .. code-block:: none
@@ -148,16 +148,16 @@ failure:
 - "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 connection tool to try and
+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:
+through Composer, you would call the script from the vendor directory:
 
 .. code-block:: none
 
-   php vendor/mongodb/mongodb/tools/connect.php mysql://127.0.0.1:27017
+   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:
 
@@ -167,3 +167,8 @@ In case the server does not accept connections, the output will look like this:
    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.

From 08e1c2526ed66eb19b118370db17ad796e77b287 Mon Sep 17 00:00:00 2001
From: Andreas Braun 
Date: Mon, 24 Oct 2022 08:51:03 +0200
Subject: [PATCH 5/5] Document using pre tags for formatted output in web SAPI

---
 docs/faq.txt               | 7 ++++++-
 tools/detect-extension.php | 4 ----
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/docs/faq.txt b/docs/faq.txt
index e23a828f8..ff9acc025 100644
--- a/docs/faq.txt
+++ b/docs/faq.txt
@@ -65,7 +65,12 @@ script from the vendor directory:
    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.
+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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tools/detect-extension.php b/tools/detect-extension.php index 6da2336fb..4b599fd4b 100644 --- a/tools/detect-extension.php +++ b/tools/detect-extension.php @@ -35,10 +35,6 @@ function grepIniFile(string $filename, string $extension): int $version = phpversion($extension); -if (PHP_SAPI !== 'cli') { - echo '
';
-}
-
 if ($version !== false) {
     printf("Extension \"%s\" is loaded. Version: %s\n", $extension, $version);
     exit;