use std::{net::IpAddr, time::Duration};
use rama_core::telemetry::tracing;
use rama_dns::client::resolver::{BoxDnsAddressResolver, DnsAddressResolver as _};
use rama_net::{address::Domain, mode::DnsResolveIpMode};
use tokio::{sync::mpsc, time::sleep};
const DNS_FAMILY_PREFERENCE_DELAY: Duration = Duration::from_micros(42);
pub(crate) async fn race_resolve_dual(
dns_resolver: &BoxDnsAddressResolver,
domain: Domain,
mode: DnsResolveIpMode,
) -> Option<IpAddr> {
use tracing::{Instrument, trace_span};
let (tx, mut rx) = mpsc::unbounded_channel();
let (delay_ipv4, delay_ipv6) = match mode {
DnsResolveIpMode::Dual => (true, false),
DnsResolveIpMode::DualPreferIpV4 => (false, true),
DnsResolveIpMode::SingleIpV4 | DnsResolveIpMode::SingleIpV6 => (false, false),
};
tokio::spawn(
{
let tx = tx.clone();
let domain = domain.clone();
let dns_resolver = dns_resolver.clone();
async move {
if delay_ipv4 {
sleep(DNS_FAMILY_PREFERENCE_DELAY).await;
}
match dns_resolver.lookup_ipv4_rand(domain.clone()).await {
Some(Ok(addr)) => {
if let Err(err) = tx.send(IpAddr::V4(addr)) {
tracing::debug!(
"failed to send ipv4 lookup result for ip: {addr}; err = {err:?}"
)
}
}
Some(Err(err)) => {
tracing::debug!("failed to lookup ipv4 addresses for domain: {err:?}");
}
None => {
tracing::debug!(
"failed to lookup ipv4 addresses for domain: no addresses found"
);
}
}
}
}
.instrument(trace_span!("dns::ipv4_lookup")),
);
tokio::spawn(
{
let dns_resolver = dns_resolver.clone();
async move {
if delay_ipv6 {
sleep(DNS_FAMILY_PREFERENCE_DELAY).await;
}
match dns_resolver.lookup_ipv6_rand(domain.clone()).await {
Some(Ok(addr)) => {
if let Err(err) = tx.send(IpAddr::V6(addr)) {
tracing::debug!(
"failed to send ipv6 lookup result for ip: {addr}; err = {err:?}"
)
}
}
Some(Err(err)) => {
tracing::debug!("failed to lookup ipv6 addresses for domain: {err:?}");
}
None => {
tracing::debug!(
"failed to lookup ipv6 addresses for domain: no addresses found"
);
}
}
}
}
.instrument(trace_span!("dns::ipv6_lookup")),
);
rx.recv().await
}
#[cfg(test)]
mod tests {
use std::{
convert::Infallible,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
};
use rama_core::futures::{Stream, stream};
use rama_dns::client::resolver::DnsAddressResolver;
use super::*;
struct ImmediateDualResolver;
impl DnsAddressResolver for ImmediateDualResolver {
type Error = Infallible;
fn lookup_ipv4(
&self,
_: Domain,
) -> impl Stream<Item = Result<Ipv4Addr, Self::Error>> + Send + '_ {
stream::once(std::future::ready(Ok(Ipv4Addr::new(192, 0, 2, 4))))
}
fn lookup_ipv6(
&self,
_: Domain,
) -> impl Stream<Item = Result<Ipv6Addr, Self::Error>> + Send + '_ {
stream::once(std::future::ready(Ok(Ipv6Addr::LOCALHOST)))
}
}
#[tokio::test]
async fn race_resolve_dual_prefers_ipv6_for_dual_mode() {
let resolver = BoxDnsAddressResolver::new(ImmediateDualResolver);
let ip = race_resolve_dual(&resolver, Domain::example(), DnsResolveIpMode::Dual)
.await
.expect("dual resolver should return an address");
assert!(matches!(ip, IpAddr::V6(_)));
}
#[tokio::test]
async fn race_resolve_dual_prefers_ipv4_for_dual_prefer_ipv4_mode() {
let resolver = BoxDnsAddressResolver::new(ImmediateDualResolver);
let ip = race_resolve_dual(
&resolver,
Domain::example(),
DnsResolveIpMode::DualPreferIpV4,
)
.await
.expect("dual resolver should return an address");
assert!(matches!(ip, IpAddr::V4(_)));
}
}