use std::str::FromStr;
use std::fmt::Formatter;
use std::sync::{Arc, OnceLock};
use std::io::{Error, Result, ErrorKind};
use std::net::{SocketAddr, ToSocketAddrs};
use std::ops::Deref;
use pi_async_rt::rt::{serial::AsyncRuntimeBuilder,
serial_local_thread::LocalTaskRuntime};
use dashmap::DashMap;
use hickory_proto::{rr::RecordType,
op::Query};
use hickory_resolver::{Name, Hosts, Resolver,
config::{ResolverConfig, ResolverOpts},
lookup::Lookup};
#[cfg(all(test, feature = "runtime_metrics", not(target_arch = "wasm32")))]
std::thread_local! {
static DNS_METRICS_TEST_RESULT: std::cell::RefCell<Option<(usize, String,
std::result::Result<pi_async_rt::runtime_metrics::RuntimeMetricId,
pi_async_rt::runtime_metrics::RuntimeMetricRegistrationError>)>> = const { std::cell::RefCell::new(None) };
}
#[cfg(all(test, feature = "runtime_metrics", not(target_arch = "wasm32")))]
mod batch_runtime_metrics_tests {
use super::*;
#[test]
fn test_dns_registers_runtime_metrics() {
DNS_METRICS_TEST_RESULT.with(|value| { value.borrow_mut().take(); });
let map = GLOBAL_DNS_RESOLVE_SERVICES.get_or_init(DashMap::new);
join_dns_resolver("runtime-metrics-unit", DNSResolver::with_system().expect("真实系统解析器构造"));
let observed = DNS_METRICS_TEST_RESULT.with(|value| value.borrow_mut().take());
if let Some((_, _, Ok(id))) = &observed {
let _ = pi_async_rt::runtime_metrics::unregister_runtime_metrics(*id);
}
let (owned, _) = map.remove("runtime-metrics-unit").expect("原join必须发布解析器").1;
let expected_id = owned.get_id();
let (ready_tx, ready_rx) = std::sync::mpsc::sync_channel(1);
owned.send(async move { let _ = ready_tx.send(()); });
let ready = ready_rx.recv_timeout(std::time::Duration::from_secs(2));
owned.close();
ready.expect("原DNS所属线程必须启动才能完成夹具清理");
let (actual_id, actual_name, registration) = observed.expect("原join必须实际登记");
assert_eq!(actual_id, expected_id);
assert_eq!(actual_name, format!("pi_dns_resolver-local-{}", expected_id));
assert!(registration.is_ok(), "真实登记必须成功: {:?}", registration);
}
}
struct DNSResolverRuntime(LocalTaskRuntime<()>);
unsafe impl Sync for DNSResolverRuntime {}
impl Deref for DNSResolverRuntime {
type Target = LocalTaskRuntime<()>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DNSResolverRuntime {
pub fn close(self) {
self.0.close();
}
}
pub const DEFAULT_DNS_RESOLVER_NAME: String = String::new();
static GLOBAL_DNS_RESOLVE_SERVICES: OnceLock<DashMap<String, (DNSResolverRuntime, DNSResolver)>> = OnceLock::new();
pub fn init_global_dns_resolve_service() {
let _ = GLOBAL_DNS_RESOLVE_SERVICES.get_or_init(|| {
DashMap::new()
});
join_dns_resolver(DEFAULT_DNS_RESOLVER_NAME,
DNSResolver::with_system_conf().unwrap());
}
pub fn join_dns_resolver<N: AsRef<str>>(name: N,
resolver: DNSResolver) {
if let Some(map) = GLOBAL_DNS_RESOLVE_SERVICES.get() {
let rt = AsyncRuntimeBuilder
::default_local_thread(Some(("DNS-Resolver-".to_string() + name.as_ref()).as_str()),
None);
#[cfg(all(feature = "runtime_metrics", not(target_arch = "wasm32")))]
{
let metric_name = format!("pi_dns_resolver-local-{}", rt.get_id());
let registration = pi_async_rt::runtime_metrics::register_runtime_metrics(&rt, &metric_name);
#[cfg(test)]
DNS_METRICS_TEST_RESULT.with(|value| {
*value.borrow_mut() = Some((rt.get_id(), metric_name.clone(), registration));
});
if let Err(error) = registration {
log::warn!("DNS 运行时指标注册失败, 名称: {}, 原因: {}", metric_name, error);
}
}
if let Some((last_rt, _last_resolver)) = map
.insert(name.as_ref().to_string(),
(DNSResolverRuntime(rt), resolver))
{
last_rt.close();
}
}
}
pub fn async_resolve_ip<N, F>(name: Option<N>,
host_name: N,
callback: F) -> Result<()>
where N: AsRef<str> + 'static,
F: FnOnce(Result<Vec<SocketAddr>>) + 'static
{
let resolver_name = if let Some(name) = name {
name.as_ref().to_string()
} else {
DEFAULT_DNS_RESOLVER_NAME
};
if let Some(map) = GLOBAL_DNS_RESOLVE_SERVICES.get() {
if let Some(item) = map.get(&resolver_name) {
let (rt, resolver) = item.value();
let resolver_copy = resolver.clone();
rt.send(async move {
let result = resolver_copy.lookup_ip(host_name);
callback(result);
});
}
Ok(())
} else {
Err(Error::new(ErrorKind::BrokenPipe,
format!("Async resolve ip failed, name: {:?}, reason: resolver not exist",
resolver_name)))
}
}
pub type DNSResolverConfig = ResolverConfig;
pub type DNSResolverOpts = ResolverOpts;
pub type DNSResolverRecordType = RecordType;
pub fn local_domain() -> Option<Name> {
if let Some(domain) = ResolverConfig::default().domain() {
Some(domain.clone())
} else {
None
}
}
#[derive(Debug)]
pub struct DNSResolver(Arc<InnerDNSResolver>);
impl Clone for DNSResolver {
fn clone(&self) -> Self {
DNSResolver(self.0.clone())
}
}
impl DNSResolver {
pub fn new(config: DNSResolverConfig,
opts: DNSResolverOpts) -> Result<Self> {
let resolver = Resolver::new(config, opts)?;
let inner = InnerDNSResolver::Custom(resolver);
Ok(DNSResolver(Arc::new(inner)))
}
pub fn with_local_hosts_conf() -> Result<Self> {
let inner = InnerDNSResolver::Local(Hosts::new());
Ok(DNSResolver(Arc::new(inner)))
}
pub fn with_system() -> Result<Self> {
let inner = InnerDNSResolver::System;
Ok(DNSResolver(Arc::new(inner)))
}
pub fn with_system_conf() -> Result<Self> {
let resolver = Resolver::from_system_conf()?;
let inner = InnerDNSResolver::Custom(resolver);
Ok(DNSResolver(Arc::new(inner)))
}
pub fn with_google() -> Result<Self> {
let resolver = Resolver::new(ResolverConfig::google(), ResolverOpts::default())?;
let inner = InnerDNSResolver::Custom(resolver);
Ok(DNSResolver(Arc::new(inner)))
}
pub fn with_cloudflare() -> Result<Self> {
let resolver = Resolver::new(ResolverConfig::cloudflare(), ResolverOpts::default())?;
let inner = InnerDNSResolver::Custom(resolver);
Ok(DNSResolver(Arc::new(inner)))
}
pub fn with_quad9() -> Result<Self> {
let resolver = Resolver::new(ResolverConfig::quad9(), ResolverOpts::default())?;
let inner = InnerDNSResolver::Custom(resolver);
Ok(DNSResolver(Arc::new(inner)))
}
pub fn is_local_hosts(&self) -> bool {
if let InnerDNSResolver::Local(_) = self.0.as_ref() {
true
} else {
false
}
}
pub fn is_system(&self) -> bool {
if let InnerDNSResolver::System = self.0.as_ref() {
true
} else {
false
}
}
pub fn is_custom(&self) -> bool {
if let InnerDNSResolver::Custom(_) = self.0.as_ref() {
true
} else {
false
}
}
pub fn lookup_ip<N: AsRef<str>>(&self, name: N)
-> Result<Vec<SocketAddr>>
{
match self.0.as_ref() {
InnerDNSResolver::Local(hosts) => {
match Name::from_str(name.as_ref()) {
Err(e) => {
Err(Error::new(ErrorKind::AddrNotAvailable,
format!("{}", e)))
},
Ok(query_name) => {
if let Some(lookup) = hosts
.lookup_static_host(&Query::query(query_name,
RecordType::A.into())) {
Ok(lookup
.iter()
.filter_map(|data| {
if let Some(ip) = data.ip_addr() {
Some(SocketAddr::new(ip, 0))
} else {
None
}
}).collect())
} else {
Ok(vec![])
}
},
}
},
InnerDNSResolver::System => {
let addrs = if name.as_ref().find(":").is_some() {
name.as_ref().to_socket_addrs()?
} else {
(name.as_ref().to_string() + ":0").as_str().to_socket_addrs()?
};
Ok(addrs.map(|addr| {
addr
}).collect())
},
InnerDNSResolver::Custom(resolver) => {
match resolver.lookup_ip(name.as_ref()) {
Err(e) => {
Err(Error::new(ErrorKind::AddrNotAvailable,
format!("{}", e)))
},
Ok(lookup) => {
Ok(lookup.iter().map(|ip| {
SocketAddr::new(ip, 0)
}).collect())
},
}
},
}
}
pub fn lookup<N: AsRef<str>>(&self,
name: N,
record_type: DNSResolverRecordType)
-> Result<Lookup>
{
match self.0.as_ref() {
InnerDNSResolver::Custom(resolver) => {
match resolver.lookup(name.as_ref(), record_type) {
Err(e) => {
Err(Error::new(ErrorKind::AddrNotAvailable,
format!("{}", e)))
},
Ok(lookup) => {
Ok(lookup)
},
}
},
_ => {
Err(Error::new(ErrorKind::AddrNotAvailable,
format!("Lookup name failed, name: {:?}, reason: disable lookup with hosts config or system resolver",
name.as_ref())))
}
}
}
}
enum InnerDNSResolver {
Local(Hosts), System, Custom(Resolver), }
impl std::fmt::Debug for InnerDNSResolver {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if let Self::Custom(_) = self {
write!(f, "Custom")
} else {
write!(f, "{:?}", self)
}
}
}