@@ -383,6 +383,169 @@ test_mongoc_speculative_auth_request_x509_pool (void)
383383 bson_destroy (response );
384384}
385385
386+ // test_mongoc_speculative_auth_request_x509_network_error is a regression test
387+ // for CDRIVER-4635.
388+ static void
389+ test_mongoc_speculative_auth_request_x509_network_error (void )
390+ {
391+ // Start mock server.
392+ mock_server_t * server ;
393+ {
394+ mongoc_ssl_opt_t server_ssl_opts = {0 };
395+ server_ssl_opts .ca_file = CERT_CA ;
396+ server_ssl_opts .pem_file = CERT_SERVER ;
397+ server = mock_server_new ();
398+ mock_server_set_ssl_opts (server , & server_ssl_opts );
399+ mock_server_run (server );
400+ }
401+
402+ // Create URI configured for X509 authentication.
403+ mongoc_uri_t * uri ;
404+ {
405+ uri = mongoc_uri_copy (mock_server_get_uri (server ));
406+ mongoc_uri_set_option_as_int32 (
407+ uri , MONGOC_URI_HEARTBEATFREQUENCYMS , 15000 );
408+ _setup_speculative_auth_x_509 (uri );
409+ }
410+
411+ // Create single threaded client.
412+ mongoc_client_t * client ;
413+ {
414+ mongoc_ssl_opt_t client_ssl_opts = {0 };
415+ client_ssl_opts .ca_file = CERT_CA ;
416+ client_ssl_opts .pem_file = CERT_CLIENT ;
417+ client = test_framework_client_new_from_uri (uri , NULL );
418+ ASSERT (client );
419+ mongoc_client_set_ssl_opts (client , & client_ssl_opts );
420+ }
421+
422+ // Send a ping, and receive a network error.
423+ {
424+ bson_error_t error ;
425+
426+ // Send ping.
427+ future_t * future = future_client_command_simple (
428+ client , "admin" , tmp_bson ("{'ping': 1}" ), NULL , NULL , & error );
429+
430+ // Expect a hello including speculativeAuthenticate field.
431+ {
432+ request_t * request = mock_server_receives_any_hello (server );
433+ ASSERT (request );
434+ const bson_t * request_doc = request_get_doc (request , 0 );
435+ ASSERT (request_doc );
436+ char * request_str =
437+ bson_as_canonical_extended_json (request_doc , NULL );
438+ ASSERT_WITH_MSG (
439+ bson_has_field (request_doc , "speculativeAuthenticate" ),
440+ "expected hello to contain 'speculativeAuthenticate', got: %s" ,
441+ request_str );
442+ // Respond with a non-empty document "speculativeAuthenticate" field.
443+ // The C driver will interpret this as a successful X509
444+ // authentication.
445+ bson_t * response = BCON_NEW ("ok" ,
446+ BCON_INT32 (1 ),
447+ "isWritablePrimary" ,
448+ BCON_BOOL (true),
449+ "minWireVersion" ,
450+ BCON_INT32 (WIRE_VERSION_MIN ),
451+ "maxWireVersion" ,
452+ BCON_INT32 (WIRE_VERSION_MAX ),
453+ "speculativeAuthenticate" ,
454+ "{" ,
455+ "foo" ,
456+ "bar" ,
457+ "}" );
458+
459+ char * response_str = bson_as_canonical_extended_json (response , NULL );
460+ mock_server_replies_simple (request , response_str );
461+ bson_free (response_str );
462+ bson_destroy (response );
463+ bson_free (request_str );
464+ request_destroy (request );
465+ }
466+
467+ // Expect a ping command. Respond with a network error.
468+ {
469+ request_t * request = mock_server_receives_msg (
470+ server , MONGOC_MSG_NONE , tmp_bson ("{'ping': 1}" ));
471+ ASSERT (request );
472+ // Cause a network error.
473+ mock_server_hangs_up (request );
474+ request_destroy (request );
475+ }
476+
477+ // Expect error.
478+ ASSERT (!future_get_bool (future ));
479+ ASSERT_ERROR_CONTAINS (error ,
480+ MONGOC_ERROR_STREAM ,
481+ MONGOC_ERROR_STREAM_SOCKET ,
482+ "socket error" );
483+ future_destroy (future );
484+ }
485+
486+ // Send another ping, expect another "speculativeAuthenticate" attempt.
487+ {
488+ bson_error_t error ;
489+
490+ // Send ping.
491+ future_t * future = future_client_command_simple (
492+ client , "admin" , tmp_bson ("{'ping': 1}" ), NULL , NULL , & error );
493+
494+ // Expect a hello including speculativeAuthenticate field.
495+ {
496+ request_t * request = mock_server_receives_any_hello (server );
497+ ASSERT (request );
498+ const bson_t * request_doc = request_get_doc (request , 0 );
499+ ASSERT (request_doc );
500+ char * request_str =
501+ bson_as_canonical_extended_json (request_doc , NULL );
502+ ASSERT_WITH_MSG (
503+ bson_has_field (request_doc , "speculativeAuthenticate" ),
504+ "expected hello to contain 'speculativeAuthenticate', got: %s" ,
505+ request_str );
506+ // Respond with a non-empty document "speculativeAuthenticate" field.
507+ // The C driver will interpret this as a successful X509
508+ // authentication.
509+ bson_t * response = BCON_NEW ("ok" ,
510+ BCON_INT32 (1 ),
511+ "isWritablePrimary" ,
512+ BCON_BOOL (true),
513+ "minWireVersion" ,
514+ BCON_INT32 (WIRE_VERSION_MIN ),
515+ "maxWireVersion" ,
516+ BCON_INT32 (WIRE_VERSION_MAX ),
517+ "speculativeAuthenticate" ,
518+ "{" ,
519+ "foo" ,
520+ "bar" ,
521+ "}" );
522+
523+ char * response_str = bson_as_canonical_extended_json (response , NULL );
524+ mock_server_replies_simple (request , response_str );
525+ bson_free (response_str );
526+ bson_destroy (response );
527+ request_destroy (request );
528+ bson_free (request_str );
529+ }
530+
531+ // Expect a ping command. Respond with {"ok": 1}.
532+ {
533+ request_t * request = mock_server_receives_msg (
534+ server , MONGOC_MSG_NONE , tmp_bson ("{'ping': 1}" ));
535+ ASSERT (request );
536+ mock_server_replies_ok_and_destroys (request );
537+ }
538+
539+ // Expect success.
540+ ASSERT_OR_PRINT (future_get_bool (future ), error );
541+ future_destroy (future );
542+ }
543+
544+ mongoc_client_destroy (client );
545+ mongoc_uri_destroy (uri );
546+ mock_server_destroy (server );
547+ }
548+
386549static void
387550test_mongoc_speculative_auth_request_scram (void )
388551{
@@ -450,6 +613,10 @@ test_speculative_auth_install (TestSuite *suite)
450613 TestSuite_AddMockServerTest (suite ,
451614 "/speculative_auth_pool/request_x509" ,
452615 test_mongoc_speculative_auth_request_x509_pool );
616+ TestSuite_AddMockServerTest (
617+ suite ,
618+ "/speculative_auth/request_x509/network_error" ,
619+ test_mongoc_speculative_auth_request_x509_network_error );
453620#endif /* MONGOC_ENABLE_SSL_* */
454621 TestSuite_AddMockServerTest (
455622 suite ,
0 commit comments