tokio_xmpp/connect/
dns.rs#[cfg(feature = "dns")]
use futures::{future::select_ok, FutureExt};
#[cfg(feature = "dns")]
use hickory_resolver::{
config::LookupIpStrategy, name_server::TokioConnectionProvider, IntoName, TokioAsyncResolver,
};
#[cfg(feature = "dns")]
use log::debug;
use std::net::SocketAddr;
use tokio::net::TcpStream;
use crate::Error;
#[derive(Clone, Debug)]
pub enum DnsConfig {
#[cfg(feature = "dns")]
UseSrv {
host: String,
srv: String,
fallback_port: u16,
},
#[allow(unused)]
#[cfg(feature = "dns")]
NoSrv {
host: String,
port: u16,
},
#[allow(unused)]
Addr {
addr: String,
},
}
impl std::fmt::Display for DnsConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#[cfg(feature = "dns")]
Self::UseSrv { host, .. } => write!(f, "{}", host),
#[cfg(feature = "dns")]
Self::NoSrv { host, port } => write!(f, "{}:{}", host, port),
Self::Addr { addr } => write!(f, "{}", addr),
}
}
}
impl DnsConfig {
#[cfg(feature = "dns")]
pub fn srv(host: &str, srv: &str, fallback_port: u16) -> Self {
Self::UseSrv {
host: host.to_string(),
srv: srv.to_string(),
fallback_port,
}
}
#[cfg(feature = "dns")]
pub fn srv_default_client(host: &str) -> Self {
Self::UseSrv {
host: host.to_string(),
srv: "_xmpp-client._tcp".to_string(),
fallback_port: 5222,
}
}
#[cfg(feature = "dns")]
pub fn no_srv(host: &str, port: u16) -> Self {
Self::NoSrv {
host: host.to_string(),
port,
}
}
pub fn addr(addr: &str) -> Self {
Self::Addr {
addr: addr.to_string(),
}
}
pub async fn resolve(&self) -> Result<TcpStream, Error> {
match self {
#[cfg(feature = "dns")]
Self::UseSrv {
host,
srv,
fallback_port,
} => Self::resolve_srv(host, srv, *fallback_port).await,
#[cfg(feature = "dns")]
Self::NoSrv { host, port } => Self::resolve_no_srv(host, *port).await,
Self::Addr { addr } => {
let addr: SocketAddr = addr.parse()?;
return Ok(TcpStream::connect(&SocketAddr::new(addr.ip(), addr.port())).await?);
}
}
}
#[cfg(feature = "dns")]
async fn resolve_srv(host: &str, srv: &str, fallback_port: u16) -> Result<TcpStream, Error> {
let ascii_domain = idna::domain_to_ascii(&host)?;
if let Ok(ip) = ascii_domain.parse() {
debug!("Attempting connection to {ip}:{fallback_port}");
return Ok(TcpStream::connect(&SocketAddr::new(ip, fallback_port)).await?);
}
let resolver = TokioAsyncResolver::tokio_from_system_conf()?;
let srv_domain = format!("{}.{}.", srv, ascii_domain).into_name()?;
let srv_records = resolver.srv_lookup(srv_domain.clone()).await.ok();
match srv_records {
Some(lookup) => {
for srv in lookup.iter() {
debug!("Attempting connection to {srv_domain} {srv}");
if let Ok(stream) =
Self::resolve_no_srv(&srv.target().to_ascii(), srv.port()).await
{
return Ok(stream);
}
}
Err(Error::Disconnected)
}
None => {
debug!("Attempting connection to {host}:{fallback_port}");
Self::resolve_no_srv(host, fallback_port).await
}
}
}
#[cfg(feature = "dns")]
async fn resolve_no_srv(host: &str, port: u16) -> Result<TcpStream, Error> {
let ascii_domain = idna::domain_to_ascii(&host)?;
if let Ok(ip) = ascii_domain.parse() {
return Ok(TcpStream::connect(&SocketAddr::new(ip, port)).await?);
}
let (config, mut options) = hickory_resolver::system_conf::read_system_conf()?;
options.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
let resolver = TokioAsyncResolver::new(config, options, TokioConnectionProvider::default());
let ips = resolver.lookup_ip(ascii_domain).await?;
select_ok(
ips.into_iter()
.map(|ip| TcpStream::connect(SocketAddr::new(ip, port)).boxed()),
)
.await
.map(|(result, _)| result)
.map_err(|_| Error::Disconnected)
}
}