Skip to content

CDRIVER-5589 add option to prefer TCP for SRV lookup #1625

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/libmongoc/doc/mongoc_uri_t.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ The driver prefixes the service name with "_mongodb._tcp.", then performs a DNS

On Unix, the MongoDB C Driver relies on libresolv to look up SRV and TXT records. If libresolv is unavailable, then using a "mongodb+srv" URI will cause an error. If your libresolv lacks ``res_nsearch`` then the driver will fall back to ``res_search``, which is not thread-safe.

Set the environment variable ``MONGOC_EXPERIMENTAL_SRV_PREFER_TCP`` to prefer TCP for the initial queries. The environment variable is ignored for ``res_search``. Large DNS responses over UDP may be truncated due to UDP size limitations. DNS resolvers are expected to retry over TCP if the UDP response indicates truncation. Some observed DNS environments do not set the truncation flag (TC), preventing the TCP retry. This environment variable is currently experimental and subject to change.

IPv4 and IPv6
-------------

Expand Down
1 change: 1 addition & 0 deletions src/libmongoc/src/mongoc/mongoc-client-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ _mongoc_client_get_rr (const char *hostname,
mongoc_rr_type_t rr_type,
mongoc_rr_data_t *rr_data,
size_t initial_buffer_size,
bool prefer_tcp,
bson_error_t *error);

mongoc_client_t *
Expand Down
18 changes: 14 additions & 4 deletions src/libmongoc/src/mongoc/mongoc-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ txt_callback (const char *hostname, PDNS_RECORD pdns, mongoc_rr_data_t *rr_data,
*/

static bool
_mongoc_get_rr_dnsapi (const char *hostname, mongoc_rr_type_t rr_type, mongoc_rr_data_t *rr_data, bson_error_t *error)
_mongoc_get_rr_dnsapi (
const char *hostname, mongoc_rr_type_t rr_type, mongoc_rr_data_t *rr_data, bool prefer_tcp, bson_error_t *error)
{
const char *rr_type_name;
WORD nst;
Expand Down Expand Up @@ -198,7 +199,11 @@ _mongoc_get_rr_dnsapi (const char *hostname, mongoc_rr_type_t rr_type, mongoc_rr
callback = txt_callback;
}

res = DnsQuery_UTF8 (hostname, nst, DNS_QUERY_BYPASS_CACHE, NULL /* IP Address */, &pdns, 0 /* reserved */);
DWORD options = DNS_QUERY_BYPASS_CACHE;
if (prefer_tcp) {
options |= DNS_QUERY_USE_TCP_ONLY;
}
res = DnsQuery_UTF8 (hostname, nst, options, NULL /* IP Address */, &pdns, 0 /* reserved */);

if (res) {
DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
Expand Down Expand Up @@ -386,6 +391,7 @@ _mongoc_get_rr_search (const char *hostname,
mongoc_rr_type_t rr_type,
mongoc_rr_data_t *rr_data,
size_t initial_buffer_size,
bool prefer_tcp,
bson_error_t *error)
{
#ifdef MONGOC_HAVE_RES_NSEARCH
Expand Down Expand Up @@ -438,6 +444,9 @@ _mongoc_get_rr_search (const char *hostname,
#ifdef MONGOC_HAVE_RES_NSEARCH
/* thread-safe */
res_ninit (&state);
if (prefer_tcp) {
state.options |= RES_USEVC;
}
size = res_nsearch (&state, hostname, ns_c_in, nst, search_buf, buffer_size);
#elif defined(MONGOC_HAVE_RES_SEARCH)
size = res_search (hostname, ns_c_in, nst, search_buf, buffer_size);
Expand Down Expand Up @@ -551,6 +560,7 @@ _mongoc_client_get_rr (const char *hostname,
mongoc_rr_type_t rr_type,
mongoc_rr_data_t *rr_data,
size_t initial_buffer_size,
bool prefer_tcp,
bson_error_t *error)
{
BSON_ASSERT (rr_data);
Expand All @@ -563,9 +573,9 @@ _mongoc_client_get_rr (const char *hostname,
"libresolv unavailable, cannot use mongodb+srv URI");
return false;
#elif defined(MONGOC_HAVE_DNSAPI)
return _mongoc_get_rr_dnsapi (hostname, rr_type, rr_data, error);
return _mongoc_get_rr_dnsapi (hostname, rr_type, rr_data, prefer_tcp, error);
#elif (defined(MONGOC_HAVE_RES_NSEARCH) || defined(MONGOC_HAVE_RES_SEARCH))
return _mongoc_get_rr_search (hostname, rr_type, rr_data, initial_buffer_size, error);
return _mongoc_get_rr_search (hostname, rr_type, rr_data, initial_buffer_size, prefer_tcp, error);
#else
#error No SRV library is available, but ENABLE_SRV is true!
#endif
Expand Down
6 changes: 6 additions & 0 deletions src/libmongoc/src/mongoc/mongoc-topology-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ typedef bool (*_mongoc_rr_resolver_fn) (const char *hostname,
mongoc_rr_type_t rr_type,
mongoc_rr_data_t *rr_data,
size_t initial_buffer_size,
bool prefer_tcp,
bson_error_t *error);

/**
Expand Down Expand Up @@ -217,6 +218,11 @@ typedef struct _mongoc_topology_t {
// `mongoc_client_set_usleep_impl`.
mongoc_usleep_func_t usleep_fn;
void *usleep_data;

// `srv_prefer_tcp` determines if DNS lookup for SRV tries TCP first instead of UDP.
// DNS implementations are expected to try UDP first, then retry with TCP if the UDP response indicates truncation.
// Some DNS servers truncate UDP responses without setting the truncated (TC) flag. This may result in no TCP retry.
bool srv_prefer_tcp;
} mongoc_topology_t;

mongoc_topology_t *
Expand Down
32 changes: 26 additions & 6 deletions src/libmongoc/src/mongoc/mongoc-topology.c
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,14 @@ mongoc_topology_new (const mongoc_uri_t *uri, bool single_threaded)
#endif

topology = (mongoc_topology_t *) bson_malloc0 (sizeof *topology);
// Check if requested to use TCP for SRV lookup.
{
char *srv_prefer_tcp = _mongoc_getenv ("MONGOC_EXPERIMENTAL_SRV_PREFER_TCP");
if (srv_prefer_tcp) {
topology->srv_prefer_tcp = true;
}
bson_free (srv_prefer_tcp);
}
topology->usleep_fn = mongoc_usleep_default_impl;
topology->session_pool = mongoc_server_session_pool_new_with_params (
_server_session_init, _server_session_destroy, _server_session_should_prune, topology);
Expand Down Expand Up @@ -476,16 +484,24 @@ mongoc_topology_new (const mongoc_uri_t *uri, bool single_threaded)

/* a mongodb+srv URI. try SRV lookup, if no error then also try TXT */
prefixed_hostname = bson_strdup_printf ("_%s._tcp.%s", mongoc_uri_get_srv_service_name (uri), srv_hostname);
if (!topology->rr_resolver (
prefixed_hostname, MONGOC_RR_SRV, &rr_data, MONGOC_RR_DEFAULT_BUFFER_SIZE, &topology->scanner->error)) {
if (!topology->rr_resolver (prefixed_hostname,
MONGOC_RR_SRV,
&rr_data,
MONGOC_RR_DEFAULT_BUFFER_SIZE,
topology->srv_prefer_tcp,
&topology->scanner->error)) {
GOTO (srv_fail);
}

/* Failure to find TXT records will not return an error (since it is only
* for options). But _mongoc_client_get_rr may return an error if
* there is more than one TXT record returned. */
if (!topology->rr_resolver (
srv_hostname, MONGOC_RR_TXT, &rr_data, MONGOC_RR_DEFAULT_BUFFER_SIZE, &topology->scanner->error)) {
if (!topology->rr_resolver (srv_hostname,
MONGOC_RR_TXT,
&rr_data,
MONGOC_RR_DEFAULT_BUFFER_SIZE,
topology->srv_prefer_tcp,
&topology->scanner->error)) {
GOTO (srv_fail);
}

Expand Down Expand Up @@ -812,8 +828,12 @@ mongoc_topology_rescan_srv (mongoc_topology_t *topology)
prefixed_hostname =
bson_strdup_printf ("_%s._tcp.%s", mongoc_uri_get_srv_service_name (topology->uri), srv_hostname);

ret = topology->rr_resolver (
prefixed_hostname, MONGOC_RR_SRV, &rr_data, MONGOC_RR_DEFAULT_BUFFER_SIZE, &topology->scanner->error);
ret = topology->rr_resolver (prefixed_hostname,
MONGOC_RR_SRV,
&rr_data,
MONGOC_RR_DEFAULT_BUFFER_SIZE,
topology->srv_prefer_tcp,
&topology->scanner->error);

td = mc_tpld_take_ref (topology);
topology->srv_polling_last_scan_ms = bson_get_monotonic_time () / 1000;
Expand Down
16 changes: 13 additions & 3 deletions src/libmongoc/tests/test-mongoc-dns.c
Original file line number Diff line number Diff line change
Expand Up @@ -599,9 +599,9 @@ test_small_initial_buffer (void *unused)
BSON_UNUSED (unused);

memset (&rr_data, 0, sizeof (rr_data));
ASSERT_OR_PRINT (
_mongoc_client_get_rr ("_mongodb._tcp.test1.test.build.10gen.cc", rr_type, &rr_data, small_buffer_size, &error),
error);
ASSERT_OR_PRINT (_mongoc_client_get_rr (
"_mongodb._tcp.test1.test.build.10gen.cc", rr_type, &rr_data, small_buffer_size, false, &error),
error);
ASSERT_CMPINT (rr_data.count, ==, 2);
bson_free (rr_data.txt_record_opts);
_mongoc_host_list_destroy_all (rr_data.hosts);
Expand All @@ -612,12 +612,14 @@ _mock_rr_resolver_prose_test_9 (const char *service,
mongoc_rr_type_t rr_type,
mongoc_rr_data_t *rr_data,
size_t initial_buffer_size,
bool prefer_tcp,
bson_error_t *error)
{
BSON_UNUSED (service);
BSON_UNUSED (rr_type);
BSON_UNUSED (rr_data);
BSON_UNUSED (initial_buffer_size);
BSON_UNUSED (prefer_tcp);
BSON_UNUSED (error);

test_error ("Expected mock resolver to not be called");
Expand Down Expand Up @@ -853,12 +855,14 @@ _mock_rr_resolver_prose_test_10 (const char *service,
mongoc_rr_type_t rr_type,
mongoc_rr_data_t *rr_data,
size_t initial_buffer_size,
bool prefer_tcp,
bson_error_t *error)
{
BSON_UNUSED (initial_buffer_size);

BSON_ASSERT_PARAM (service);
BSON_ASSERT_PARAM (rr_data);
BSON_UNUSED (prefer_tcp);
BSON_ASSERT_PARAM (error);

if (rr_type == MONGOC_RR_SRV) {
Expand Down Expand Up @@ -953,12 +957,14 @@ _mock_rr_resolver_prose_test_11 (const char *service,
mongoc_rr_type_t rr_type,
mongoc_rr_data_t *rr_data,
size_t initial_buffer_size,
bool prefer_tcp,
bson_error_t *error)
{
BSON_UNUSED (initial_buffer_size);

BSON_ASSERT_PARAM (service);
BSON_ASSERT_PARAM (rr_data);
BSON_UNUSED (prefer_tcp);
BSON_ASSERT_PARAM (error);

if (rr_type == MONGOC_RR_SRV) {
Expand Down Expand Up @@ -1050,12 +1056,14 @@ _mock_rr_resolver_prose_test_12 (const char *service,
mongoc_rr_type_t rr_type,
mongoc_rr_data_t *rr_data,
size_t initial_buffer_size,
bool prefer_tcp,
bson_error_t *error)
{
BSON_UNUSED (initial_buffer_size);

BSON_ASSERT_PARAM (service);
BSON_ASSERT_PARAM (rr_data);
BSON_UNUSED (prefer_tcp);
BSON_ASSERT_PARAM (error);

if (rr_type == MONGOC_RR_SRV) {
Expand Down Expand Up @@ -1198,12 +1206,14 @@ _mock_rr_resolver_with_override (const char *service,
mongoc_rr_type_t rr_type,
mongoc_rr_data_t *rr_data,
size_t initial_buffer_size,
bool prefer_tcp,
bson_error_t *error)
{
BSON_UNUSED (initial_buffer_size);

BSON_ASSERT_PARAM (service);
BSON_ASSERT_PARAM (rr_data);
BSON_UNUSED (prefer_tcp);
BSON_ASSERT_PARAM (error);

if (rr_type == MONGOC_RR_SRV) {
Expand Down