use crate::{DnsError, Error, Instant};
#[cfg(not(feature = "dns-doh"))]
pub(crate) use hickory_resolver::proto::op::ResponseCode;
use std::{
borrow::Cow,
net::{IpAddr, Ipv4Addr},
sync::Arc,
};
pub mod cache;
#[cfg(feature = "dns-doh")]
mod doh;
#[cfg(not(feature = "dns-doh"))]
mod hickory;
mod lookup;
mod query;
mod txt;
pub use cache::{DnsCache, NoCache, Parameters, ResolverCache};
#[cfg(feature = "dns-doh")]
pub use doh::DohResolver;
#[cfg(any(test, feature = "test"))]
pub use query::mock_resolve;
pub(crate) use query::{QueryError, QueryResult};
pub use txt::{TxtRecord, TxtRecordParser, UnwrapTxtRecord};
#[cfg(not(feature = "dns-doh"))]
pub(crate) const DNS_RCODE_NXDOMAIN: ResponseCode = ResponseCode::NXDomain;
#[cfg(feature = "dns-doh")]
pub(crate) const DNS_RCODE_NXDOMAIN: u16 = 3;
#[cfg(feature = "dns-doh")]
pub(crate) type ResponseCode = u16;
#[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq)]
pub enum IpLookupStrategy {
Ipv4Only,
Ipv6Only,
Ipv6thenIpv4,
#[default]
Ipv4thenIpv6,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RecordSet<T> {
pub records: Arc<[T]>,
pub dnssec_status: DnssecStatus,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Negative {
#[cfg(not(feature = "dns-doh"))]
pub code: ResponseCode,
#[cfg(feature = "dns-doh")]
pub code: u16,
}
const _: () =
assert!(size_of::<Result<RecordSet<Ipv4Addr>, Negative>>() == size_of::<RecordSet<Ipv4Addr>>());
impl From<Negative> for Error {
fn from(negative: Negative) -> Self {
Error::Dns(DnsError::RecordNotFound(negative.code))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Mx {
pub exchanges: Box<[Box<str>]>,
pub preference: u16,
}
#[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq)]
#[repr(u16)]
pub enum DnssecStatus {
Secure,
Insecure,
Bogus,
#[default]
Indeterminate,
}
pub struct DnsEntry<T> {
pub entry: T,
pub expires: Instant,
}
#[inline]
pub fn has_valid_labels(domain: &str) -> bool {
let mut has_dots = false;
let mut has_chars = false;
let mut label_len = 0;
for ch in domain.chars() {
label_len += 1;
if ch.is_alphanumeric() {
has_chars = true;
} else if ch == '.' {
has_dots = true;
label_len = 0;
}
if label_len > 63 {
return false;
}
}
has_chars && has_dots
}
pub(crate) fn to_a_label(domain: &str) -> Cow<'_, str> {
if !domain.is_ascii() {
idna::domain_to_ascii(domain)
.map(Cow::Owned)
.unwrap_or(Cow::Borrowed(domain))
} else if domain.bytes().any(|byte| byte.is_ascii_uppercase()) {
Cow::Owned(domain.to_ascii_lowercase())
} else {
Cow::Borrowed(domain)
}
}
pub trait ToFqdn {
fn to_fqdn(&self) -> Cow<'_, str>;
}
impl<T: AsRef<str>> ToFqdn for T {
fn to_fqdn(&self) -> Cow<'_, str> {
let value = self.as_ref();
let bytes = value.as_bytes();
if matches!(bytes.last(), Some(b'.'))
&& !bytes
.iter()
.any(|byte| byte.is_ascii_uppercase() || !byte.is_ascii())
{
Cow::Borrowed(value)
} else if value.is_ascii() {
let mut fqdn = String::with_capacity(value.len() + 1);
fqdn.push_str(value);
fqdn.make_ascii_lowercase();
if !matches!(bytes.last(), Some(b'.')) {
fqdn.push('.');
}
Cow::Owned(fqdn)
} else {
let mut fqdn = value.to_lowercase();
if !value.ends_with('.') {
fqdn.push('.');
}
Cow::Owned(fqdn)
}
}
}
pub trait ToReverseName {
fn to_reverse_name(&self) -> String;
}
impl ToReverseName for IpAddr {
fn to_reverse_name(&self) -> String {
match self {
IpAddr::V4(ip) => {
let mut segments = String::with_capacity(15);
let mut buf = [0u8; 3];
for octet in ip.octets().iter().rev() {
if !segments.is_empty() {
segments.push('.');
}
for &digit in decimal_u8(*octet, &mut buf) {
segments.push(char::from(digit));
}
}
segments
}
IpAddr::V6(ip) => {
let mut segments = String::with_capacity(63);
for segment in ip.segments().iter().rev() {
for shift in [0u32, 4, 8, 12] {
if !segments.is_empty() {
segments.push('.');
}
segments.push(char::from(hex_nibble((segment >> shift) as u8)));
}
}
segments
}
}
}
}
#[inline(always)]
pub(crate) fn hex_nibble(value: u8) -> u8 {
b"0123456789abcdef"[(value & 0x0f) as usize]
}
#[inline(always)]
pub(crate) fn decimal_u8(value: u8, buf: &mut [u8; 3]) -> &[u8] {
buf[0] = b'0' + value / 100;
buf[1] = b'0' + (value / 10) % 10;
buf[2] = b'0' + value % 10;
let start = if value >= 100 {
0
} else if value >= 10 {
1
} else {
2
};
&buf[start..]
}
#[cfg(test)]
mod test {
use super::ToReverseName;
use std::net::IpAddr;
#[test]
fn reverse_lookup_addr() {
for (addr, expected) in [
("1.2.3.4", "4.3.2.1"),
(
"2001:db8::cb01",
"1.0.b.c.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2",
),
(
"2a01:4f9:c011:b43c::1",
"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.c.3.4.b.1.1.0.c.9.f.4.0.1.0.a.2",
),
] {
assert_eq!(addr.parse::<IpAddr>().unwrap().to_reverse_name(), expected);
}
}
}