Skip to content

Commit 2e7ce91

Browse files
authored
RUST-483 Allow alternate DNS resolver configs (#217)
1 parent bc2950c commit 2e7ce91

File tree

6 files changed

+86
-15
lines changed

6 files changed

+86
-15
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ This repository contains the officially supported MongoDB Rust driver, a client
1515
- [Inserting documents into a collection](#inserting-documents-into-a-collection)
1616
- [Finding documents in a collection](#finding-documents-in-a-collection)
1717
- [Using the sync API](#using-the-sync-api)
18+
- [Atlas note](#atlas-note)
19+
- [Windows DNS note](#windows-dns-note)
1820
- [Bug Reporting / Feature Requests](#bug-reporting--feature-requests)
1921
- [Contributing](#contributing)
2022
- [Running the tests](#running-the-tests)
@@ -182,6 +184,21 @@ for result in cursor {
182184

183185
Currently, the driver has issues connecting to Atlas tiers above M2 unless the server version is at least 4.2. We're working on fixing this, but in the meantime, a workaround is to upgrade your cluster to 4.2. The driver has no known issues with either M0 or M2 instances.
184186

187+
## Windows DNS note
188+
189+
On Windows, there is a known issue in the `trust-dns-resolver` crate, which the driver uses to perform DNS lookups, that causes severe performance degradation in resolvers that use the system configuration. Since the driver uses the system configuration by default, users are recommended to specify an alternate resolver configuration on Windows until that issue is resolved. This only has an effect when connecting to deployments using a `mongodb+srv` connection string.
190+
191+
e.g.
192+
193+
``` rust
194+
let options = ClientOptions::parse_with_resolver_config(
195+
"mongodb+srv://my.host.com",
196+
ResolverConfig::cloudflare(),
197+
)
198+
.await?;
199+
let client = Client::with_options(options)?;
200+
```
201+
185202
## Bug Reporting / Feature Requests
186203
To file a bug report or submit a feature request, please open a ticket on our [Jira project](https://jira.mongodb.org/browse/RUST):
187204
- Create an account and login at [jira.mongodb.org](https://jira.mongodb.org)

src/client/options/mod.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use rustls::{
2424
};
2525
use serde::Deserialize;
2626
use strsim::jaro_winkler;
27+
use trust_dns_resolver::config::ResolverConfig;
2728
use typed_builder::TypedBuilder;
2829
use webpki_roots::TLS_SERVER_ROOTS;
2930

@@ -360,6 +361,15 @@ pub struct ClientOptions {
360361

361362
#[builder(default)]
362363
pub(crate) original_uri: Option<String>,
364+
365+
/// Configuration of the trust-dns resolver used for SRV and TXT lookups.
366+
/// By default, the host system's resolver configuration will be used.
367+
///
368+
/// On Windows, there is a known performance issue in trust-dns with using the default system
369+
/// configuration, so a custom configuration is recommended.
370+
#[builder(default)]
371+
#[serde(skip)]
372+
pub(crate) resolver_config: Option<ResolverConfig>,
363373
}
364374

365375
fn default_hosts() -> Vec<StreamAddress> {
@@ -574,6 +584,7 @@ impl From<ClientOptionsParser> for ClientOptions {
574584
command_event_handler: None,
575585
original_srv_hostname: None,
576586
original_uri: Some(parser.original_uri),
587+
resolver_config: None,
577588
}
578589
}
579590
}
@@ -639,13 +650,38 @@ impl ClientOptions {
639650
/// * `wTimeoutMS`: maps to the `w_timeout` field of the `write_concern` field
640651
/// * `zlibCompressionLevel`: not yet implemented
641652
pub async fn parse(s: &str) -> Result<Self> {
642-
let parser = ClientOptionsParser::parse(s)?;
653+
Self::parse_uri(s, None).await
654+
}
655+
656+
/// Parses a MongoDB connection string into a `ClientOptions` struct.
657+
/// If the string is malformed or one of the options has an invalid value, an error will be
658+
/// returned.
659+
///
660+
/// In the case that "mongodb+srv" is used, SRV and TXT record lookups will be done using the
661+
/// provided `ResolverConfig` as part of this method.
662+
///
663+
/// The format of a MongoDB connection string is described [here](https://docs.mongodb.com/manual/reference/connection-string/#connection-string-formats).
664+
///
665+
/// See the docstring on `ClientOptions::parse` for information on how the various URI options
666+
/// map to fields on `ClientOptions`.
667+
pub async fn parse_with_resolver_config(
668+
uri: &str,
669+
resolver_config: ResolverConfig,
670+
) -> Result<Self> {
671+
Self::parse_uri(uri, Some(resolver_config)).await
672+
}
673+
674+
/// Populate this `ClientOptions` from the given URI, optionally using the resolver config for
675+
/// DNS lookups.
676+
async fn parse_uri(uri: &str, resolver_config: Option<ResolverConfig>) -> Result<Self> {
677+
let parser = ClientOptionsParser::parse(uri)?;
643678
let srv = parser.srv;
644679
let auth_source_present = parser.auth_source.is_some();
645680
let mut options: Self = parser.into();
681+
options.resolver_config = resolver_config.clone();
646682

647683
if srv {
648-
let mut resolver = SrvResolver::new().await?;
684+
let mut resolver = SrvResolver::new(resolver_config).await?;
649685
let mut config = resolver
650686
.resolve_client_options(&options.hosts[0].hostname)
651687
.await?;

src/runtime/resolver.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::{future::Future, net::SocketAddr, time::Duration};
33
use async_trait::async_trait;
44
use trust_dns_proto::error::ProtoError;
55
use trust_dns_resolver::{
6+
config::ResolverConfig,
67
lookup::{SrvLookup, TxtLookup},
78
name_server::{GenericConnection, GenericConnectionProvider},
89
IntoName,
@@ -26,8 +27,13 @@ pub(crate) struct AsyncResolver {
2627
}
2728

2829
impl AsyncResolver {
29-
pub(crate) async fn new() -> Result<Self> {
30-
let resolver = TrustDnsResolver::from_system_conf(crate::RUNTIME).await?;
30+
pub(crate) async fn new(config: Option<ResolverConfig>) -> Result<Self> {
31+
let resolver = match config {
32+
Some(config) => {
33+
TrustDnsResolver::new(config, Default::default(), crate::RUNTIME).await?
34+
}
35+
None => TrustDnsResolver::from_system_conf(crate::RUNTIME).await?,
36+
};
3137
Ok(Self { resolver })
3238
}
3339
}

src/sdam/srv_polling/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ impl SrvPollingMonitor {
128128
return Ok(resolver);
129129
}
130130

131-
let resolver = SrvResolver::new().await?;
131+
let resolver = SrvResolver::new(self.client_options.resolver_config.clone()).await?;
132132

133133
// Since the connection was not `Some` above, this will always insert the new connection and
134134
// return a reference to it.

src/srv.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use trust_dns_resolver::error::ResolveErrorKind;
1+
use trust_dns_resolver::{config::ResolverConfig, error::ResolveErrorKind};
22

33
use crate::{
44
error::{Error, ErrorKind, Result},
@@ -19,8 +19,8 @@ pub(crate) struct ResolvedConfig {
1919
}
2020

2121
impl SrvResolver {
22-
pub(crate) async fn new() -> Result<Self> {
23-
let resolver = AsyncResolver::new().await?;
22+
pub(crate) async fn new(config: Option<ResolverConfig>) -> Result<Self> {
23+
let resolver = AsyncResolver::new(config).await?;
2424

2525
Ok(Self {
2626
resolver,

src/test/atlas_connectivity.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
use crate::{bson::doc, Client};
1+
use crate::{bson::doc, options::ClientOptions, Client};
2+
use trust_dns_resolver::config::ResolverConfig;
23

3-
async fn run_test(uri_env_var: &str) {
4+
async fn run_test(uri_env_var: &str, resolver_config: Option<ResolverConfig>) {
45
if std::env::var_os("MONGO_ATLAS_TESTS").is_none() {
56
return;
67
}
@@ -11,9 +12,15 @@ async fn run_test(uri_env_var: &str) {
1112
panic!("could not find variable {}", uri_env_var);
1213
};
1314

14-
let client = Client::with_uri_str(uri.to_string_lossy().as_ref())
15-
.await
16-
.unwrap();
15+
let uri_string = uri.to_string_lossy();
16+
let options = match resolver_config {
17+
Some(resolver_config) => {
18+
ClientOptions::parse_with_resolver_config(uri_string.as_ref(), resolver_config).await
19+
}
20+
None => ClientOptions::parse(uri_string.as_ref()).await,
21+
}
22+
.expect("uri parsing should succeed");
23+
let client = Client::with_options(options).expect("option validation should succeed");
1724

1825
let db = client.database("test");
1926
db.run_command(doc! { "isMaster": 1 }, None)
@@ -29,11 +36,16 @@ async fn run_test(uri_env_var: &str) {
2936
#[cfg_attr(feature = "tokio-runtime", tokio::test)]
3037
#[cfg_attr(feature = "async-std-runtime", async_std::test)]
3138
async fn atlas_repl_set() {
32-
run_test("MONGO_ATLAS_FREE_TIER_REPL_URI").await;
39+
run_test("MONGO_ATLAS_FREE_TIER_REPL_URI", None).await;
3340
}
3441

3542
#[cfg_attr(feature = "tokio-runtime", tokio::test)]
3643
#[cfg_attr(feature = "async-std-runtime", async_std::test)]
3744
async fn atlas_repl_set_srv() {
38-
run_test("MONGO_ATLAS_FREE_TIER_REPL_URI_SRV").await;
45+
run_test("MONGO_ATLAS_FREE_TIER_REPL_URI_SRV", None).await;
46+
run_test(
47+
"MONGO_ATLAS_FREE_TIER_REPL_URI_SRV",
48+
Some(ResolverConfig::cloudflare()),
49+
)
50+
.await;
3951
}

0 commit comments

Comments
 (0)