add pre-resolve stage that returns addrs if we have used static table previously (#6297)

This commit is contained in:
Jack Wampler
2025-12-17 09:06:43 -07:00
committed by GitHub
parent 177fbaec99
commit ae54e86bf4
4 changed files with 173 additions and 11 deletions
+14
View File
@@ -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)))
}
}
+19 -4
View File
@@ -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,