add pre-resolve stage that returns addrs if we have used static table previously (#6297)
This commit is contained in:
@@ -181,6 +181,20 @@ async fn resolve(
|
||||
) -> Result<Addrs, ResolveError> {
|
||||
let resolver = resolver.get_or_init(|| HickoryDnsResolver::new_resolver(independent));
|
||||
|
||||
// try checking the static table to see if any of the addresses in the table have been
|
||||
// looked up previously within the timeout to where we are not yet ready to try the
|
||||
// default resolver yet again.
|
||||
if let Some(ref static_resolver) = maybe_static {
|
||||
let resolver =
|
||||
static_resolver.get_or_init(|| HickoryDnsResolver::new_static_fallback(independent));
|
||||
|
||||
if let Some(addrs) = resolver.pre_resolve(name.as_str()) {
|
||||
let addrs: Addrs =
|
||||
Box::new(addrs.into_iter().map(|ip_addr| SocketAddr::new(ip_addr, 0)));
|
||||
return Ok(addrs);
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt a lookup using the primary resolver
|
||||
let resolve_fut = tokio::time::timeout(overall_dns_timeout, resolver.lookup_ip(name.as_str()));
|
||||
let primary_err = match resolve_fut.await {
|
||||
|
||||
@@ -3,27 +3,114 @@ use crate::dns::ResolveError;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{IpAddr, SocketAddr},
|
||||
sync::{Arc, Mutex},
|
||||
sync::{Arc, Mutex, MutexGuard},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
|
||||
use tracing::*;
|
||||
|
||||
const DEFAULT_PRE_RESOLVE_TIMEOUT: Duration = super::DEFAULT_POSITIVE_LOOKUP_CACHE_TTL;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct StaticResolver {
|
||||
static_addr_map: Arc<Mutex<HashMap<String, Vec<IpAddr>>>>,
|
||||
static_addr_map: Arc<Mutex<HashMap<String, Entry>>>,
|
||||
pre_resolve_timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct Entry {
|
||||
valid_for_pre_resolve_until: Option<Instant>,
|
||||
addrs: Vec<IpAddr>,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
fn new(addrs: Vec<IpAddr>) -> Self {
|
||||
Self {
|
||||
valid_for_pre_resolve_until: None,
|
||||
addrs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticResolver {
|
||||
pub fn new(static_entries: HashMap<String, Vec<IpAddr>>) -> StaticResolver {
|
||||
debug!("building static resolver");
|
||||
let static_entries = static_entries
|
||||
.into_iter()
|
||||
.map(|(name, ips)| (name, Entry::new(ips)))
|
||||
.collect();
|
||||
Self {
|
||||
static_addr_map: Arc::new(Mutex::new(static_entries)),
|
||||
pre_resolve_timeout: Some(DEFAULT_PRE_RESOLVE_TIMEOUT),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the full set of domain names and associated addresses stored in this static lookup table
|
||||
pub fn get_addrs(&self) -> HashMap<String, Vec<IpAddr>> {
|
||||
self.static_addr_map.lock().unwrap().clone()
|
||||
let mut out = HashMap::new();
|
||||
self.static_addr_map
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.for_each(|(name, entry)| {
|
||||
out.insert(name.clone(), entry.addrs.clone());
|
||||
});
|
||||
out
|
||||
}
|
||||
|
||||
/// Change the timeout for which domains can be pre-resolved after they are looked up in the
|
||||
/// static lookup table.
|
||||
#[allow(unused)]
|
||||
pub fn with_pre_resolve_timeout(mut self, timeout: Duration) -> Self {
|
||||
self.pre_resolve_timeout = Some(timeout);
|
||||
self
|
||||
}
|
||||
|
||||
/// Try looking up the domain in the static table. If the domain is in the table AND we have
|
||||
/// recently (within the configured timeout) looked it up previously in this static table using
|
||||
/// a regular resolve.
|
||||
pub fn pre_resolve(&self, name: &str) -> Option<Vec<IpAddr>> {
|
||||
debug!("found {name:?} in pre-resolve static table resolver");
|
||||
|
||||
self.pre_resolve_timeout?;
|
||||
|
||||
self.static_addr_map
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(name)
|
||||
.filter(|e| {
|
||||
e.valid_for_pre_resolve_until
|
||||
.is_some_and(|t| t > Instant::now())
|
||||
})
|
||||
.map(|e| e.addrs.clone())
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn resolve_str(&self, name: &str) -> Option<Vec<IpAddr>> {
|
||||
Self::resolve_inner(
|
||||
self.static_addr_map.lock().unwrap(),
|
||||
name,
|
||||
self.pre_resolve_timeout,
|
||||
)
|
||||
.map(|e| e.addrs)
|
||||
}
|
||||
|
||||
fn resolve_inner(
|
||||
mut table: MutexGuard<'_, HashMap<String, Entry>>,
|
||||
name: &str,
|
||||
timeout: Option<Duration>,
|
||||
) -> Option<Entry> {
|
||||
let resolved = table.get_mut(name)?;
|
||||
|
||||
debug!("found {name:?} in static table resolver");
|
||||
|
||||
if let Some(pre_resolve_timeout) = timeout {
|
||||
// We had to look this entry up and a pre-resolve duration is defined, so it will
|
||||
// trigger in pre-resolve lookups for the next _timeout_ window.
|
||||
resolved.valid_for_pre_resolve_until = Some(Instant::now() + pre_resolve_timeout);
|
||||
}
|
||||
Some(resolved.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,15 +118,15 @@ impl Resolve for StaticResolver {
|
||||
fn resolve(&self, name: Name) -> Resolving {
|
||||
debug!("looking up {name:?} in static resolver");
|
||||
let addr_map = self.static_addr_map.clone();
|
||||
let timeout = self.pre_resolve_timeout;
|
||||
Box::pin(async move {
|
||||
let addr_map = addr_map.lock().unwrap();
|
||||
let lookup = match addr_map.get(name.as_str()) {
|
||||
let lookup = match Self::resolve_inner(addr_map, name.as_str(), timeout) {
|
||||
None => return Err(ResolveError::StaticLookupMiss.into()),
|
||||
Some(addrs) => addrs,
|
||||
Some(entry) => entry.addrs,
|
||||
};
|
||||
let addrs: Addrs = Box::new(
|
||||
lookup
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|ip_addr| SocketAddr::new(ip_addr, 0)),
|
||||
);
|
||||
@@ -86,4 +173,45 @@ mod test {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_lookup_pre_resolve() {
|
||||
let example_duration = Duration::from_secs(3);
|
||||
let example_domain = String::from("static.nymvpn.com");
|
||||
let mut addr_map = HashMap::new();
|
||||
let example_ip4: IpAddr = "10.10.10.10".parse().unwrap();
|
||||
let example_ip6: IpAddr = "dead::beef".parse().unwrap();
|
||||
addr_map.insert(example_domain.clone(), vec![example_ip4, example_ip6]);
|
||||
|
||||
let resolver = StaticResolver::new(addr_map).with_pre_resolve_timeout(example_duration);
|
||||
|
||||
// ensure that attempting to pre-resolve without first resolving returns none
|
||||
let result = resolver.pre_resolve(&example_domain);
|
||||
assert!(result.is_none());
|
||||
|
||||
// resolving should now update the pre-resolve validity timeout for the entry
|
||||
let entry = StaticResolver::resolve_inner(
|
||||
resolver.static_addr_map.lock().unwrap(),
|
||||
&example_domain,
|
||||
Some(example_duration),
|
||||
)
|
||||
.expect("missing entry???!!!!");
|
||||
assert!(
|
||||
entry
|
||||
.valid_for_pre_resolve_until
|
||||
.is_some_and(|t| t < Instant::now() + example_duration)
|
||||
);
|
||||
|
||||
// check that pre-resolve now returns the expected record
|
||||
let addrs = resolver
|
||||
.pre_resolve(&example_domain)
|
||||
.expect("entry should be in pre-resolve now");
|
||||
assert!(addrs.contains(&example_ip4));
|
||||
|
||||
std::thread::sleep(example_duration);
|
||||
|
||||
// check that after the timeout duration the pre-resolve no longer returns the address
|
||||
let result = resolver.pre_resolve(&example_domain);
|
||||
assert!(result.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,9 +113,10 @@ impl BuilderConfig {
|
||||
RememberMe::new_mixnet()
|
||||
};
|
||||
|
||||
let identity = self.entry_node.node.identity.to_string();
|
||||
let builder = builder
|
||||
.with_user_agent(self.user_agent)
|
||||
.request_gateway(self.entry_node.node.identity.to_string())
|
||||
.request_gateway(identity.clone())
|
||||
.network_details(self.network_env)
|
||||
.debug_config(debug_config)
|
||||
.credentials_mode(true)
|
||||
@@ -128,9 +129,13 @@ impl BuilderConfig {
|
||||
|
||||
builder
|
||||
.build()
|
||||
.inspect(|_| tracing::debug!("successfully built reg client for {}", identity))
|
||||
.inspect_err(|e| tracing::debug!("failed to build reg client for {}: {e}", identity))
|
||||
.map_err(|err| RegistrationClientError::BuildMixnetClient(Box::new(err)))?
|
||||
.connect_to_mixnet()
|
||||
.await
|
||||
.inspect(|_| tracing::debug!("successfully connected reg client for {}", identity))
|
||||
.inspect_err(|e| tracing::debug!("failed to connect reg client for {}: {e}", identity))
|
||||
.map_err(|err| RegistrationClientError::ConnectToMixnet(Box::new(err)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ impl RegistrationClientBuilder {
|
||||
let (event_tx, event_rx) = mpsc::unbounded();
|
||||
|
||||
let nyxd_client = get_nyxd_client(&self.config.network_env)?;
|
||||
let mixnet_client_startup_timeout = self.config.mixnet_client_startup_timeout;
|
||||
|
||||
let (mixnet_client, bandwidth_controller): (
|
||||
MixnetClient,
|
||||
@@ -46,20 +47,34 @@ impl RegistrationClientBuilder {
|
||||
let builder = MixnetClientBuilder::new_with_storage(mixnet_client_storage)
|
||||
.event_tx(EventSender(event_tx));
|
||||
let mixnet_client = tokio::time::timeout(
|
||||
self.config.mixnet_client_startup_timeout,
|
||||
mixnet_client_startup_timeout,
|
||||
self.config.build_and_connect_mixnet_client(builder),
|
||||
)
|
||||
.await??;
|
||||
.await
|
||||
.inspect_err(|_| {
|
||||
tracing::warn!(
|
||||
"mixnet client connection timed out after {:?}",
|
||||
mixnet_client_startup_timeout
|
||||
)
|
||||
})?
|
||||
.inspect_err(|e| tracing::warn!("mixnet build/connect error: {e}"))?;
|
||||
let bandwidth_controller =
|
||||
Box::new(BandwidthController::new(credential_storage, nyxd_client));
|
||||
(mixnet_client, bandwidth_controller)
|
||||
} else {
|
||||
let builder = MixnetClientBuilder::new_ephemeral().event_tx(EventSender(event_tx));
|
||||
let mixnet_client = tokio::time::timeout(
|
||||
self.config.mixnet_client_startup_timeout,
|
||||
mixnet_client_startup_timeout,
|
||||
self.config.build_and_connect_mixnet_client(builder),
|
||||
)
|
||||
.await??;
|
||||
.await
|
||||
.inspect_err(|_| {
|
||||
tracing::warn!(
|
||||
"mixnet client connection timed out after {:?}",
|
||||
mixnet_client_startup_timeout
|
||||
)
|
||||
})?
|
||||
.inspect_err(|e| tracing::warn!("mixnet build/connect error: {e}"))?;
|
||||
let bandwidth_controller = Box::new(BandwidthController::new(
|
||||
EphemeralCredentialStorage::default(),
|
||||
nyxd_client,
|
||||
|
||||
Reference in New Issue
Block a user