refactor: simplify failover candidate list and deduplicate recursive pool

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Razvan Dimescu
2026-04-10 23:45:34 +03:00
parent db80d9f8f9
commit c3c11c40db
2 changed files with 29 additions and 42 deletions

View File

@@ -106,36 +106,30 @@ pub async fn forward_with_failover(
srtt: &RwLock<SrttCache>, srtt: &RwLock<SrttCache>,
timeout_duration: Duration, timeout_duration: Duration,
) -> Result<DnsPacket> { ) -> Result<DnsPacket> {
// Build candidate list: primary (sorted by SRTT) then fallback (config order) // Build candidate list: primary (sorted by SRTT for UDP) then fallback
let mut candidates: Vec<&Upstream> = let mut candidates: Vec<(usize, u64)> = pool
Vec::with_capacity(pool.primary.len() + pool.fallback.len()); .primary
.iter()
.enumerate()
.map(|(i, u)| {
let rtt = match u {
Upstream::Udp(addr) => srtt.read().unwrap().get(addr.ip()),
_ => 0, // DoH: keep config order (stable sort preserves it)
};
(i, rtt)
})
.collect();
candidates.sort_by_key(|&(_, rtt)| rtt);
// Sort primary UDP upstreams by SRTT; DoH keeps config order let all_upstreams: Vec<&Upstream> = candidates
let mut primary_udp_indices: Vec<(usize, u64)> = Vec::new(); .iter()
for (i, u) in pool.primary.iter().enumerate() { .map(|&(i, _)| &pool.primary[i])
if let Upstream::Udp(addr) = u { .chain(pool.fallback.iter())
let rtt = srtt.read().unwrap().get(addr.ip()); .collect();
primary_udp_indices.push((i, rtt));
}
}
primary_udp_indices.sort_by_key(|&(_, rtt)| rtt);
// Interleave: sorted UDP first, then DoH in config order
for &(i, _) in &primary_udp_indices {
candidates.push(&pool.primary[i]);
}
for u in &pool.primary {
if matches!(u, Upstream::Doh { .. }) {
candidates.push(u);
}
}
for u in &pool.fallback {
candidates.push(u);
}
let mut last_err: Option<Box<dyn std::error::Error + Send + Sync>> = None; let mut last_err: Option<Box<dyn std::error::Error + Send + Sync>> = None;
for upstream in &candidates { for upstream in &all_upstreams {
let start = Instant::now(); let start = Instant::now();
match forward_query(query, upstream, timeout_duration).await { match forward_query(query, upstream, timeout_duration).await {
Ok(resp) => { Ok(resp) => {

View File

@@ -129,19 +129,18 @@ async fn main() -> numa::Result<()> {
let root_hints = numa::recursive::parse_root_hints(&config.upstream.root_hints); let root_hints = numa::recursive::parse_root_hints(&config.upstream.root_hints);
let recursive_pool = || {
let dummy = UpstreamPool::new(vec![Upstream::Udp("0.0.0.0:0".parse().unwrap())], vec![]);
(dummy, "recursive (root hints)".to_string())
};
let (resolved_mode, upstream_auto, pool, upstream_label) = match config.upstream.mode { let (resolved_mode, upstream_auto, pool, upstream_label) = match config.upstream.mode {
numa::config::UpstreamMode::Auto => { numa::config::UpstreamMode::Auto => {
info!("auto mode: probing recursive resolution..."); info!("auto mode: probing recursive resolution...");
if numa::recursive::probe_recursive(&root_hints).await { if numa::recursive::probe_recursive(&root_hints).await {
info!("recursive probe succeeded — self-sovereign mode"); info!("recursive probe succeeded — self-sovereign mode");
let dummy = let (pool, label) = recursive_pool();
UpstreamPool::new(vec![Upstream::Udp("0.0.0.0:0".parse().unwrap())], vec![]); (numa::config::UpstreamMode::Recursive, false, pool, label)
(
numa::config::UpstreamMode::Recursive,
false,
dummy,
"recursive (root hints)".to_string(),
)
} else { } else {
log::warn!("recursive probe failed — falling back to Quad9 DoH"); log::warn!("recursive probe failed — falling back to Quad9 DoH");
let client = reqwest::Client::builder() let client = reqwest::Client::builder()
@@ -155,14 +154,8 @@ async fn main() -> numa::Result<()> {
} }
} }
numa::config::UpstreamMode::Recursive => { numa::config::UpstreamMode::Recursive => {
let dummy = let (pool, label) = recursive_pool();
UpstreamPool::new(vec![Upstream::Udp("0.0.0.0:0".parse().unwrap())], vec![]); (numa::config::UpstreamMode::Recursive, false, pool, label)
(
numa::config::UpstreamMode::Recursive,
false,
dummy,
"recursive (root hints)".to_string(),
)
} }
numa::config::UpstreamMode::Forward => { numa::config::UpstreamMode::Forward => {
let addrs = if config.upstream.address.is_empty() { let addrs = if config.upstream.address.is_empty() {