cdns-rs 1.2.2

A native Sync/Async Rust implementation of client DNS resolver.
Documentation
/*-
 * cdns-rs - a simple sync/async DNS query library
 * 
 * Copyright (C) 2020  Aleksandr Morozov
 * 
 * Copyright (C) 2025 Aleksandr Morozov
 * 
 * The syslog-rs crate can be redistributed and/or modified
 * under the terms of either of the following licenses:
 *
 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
 *                     
 *   2. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
 */

use std::net::IpAddr;
use std::sync::Arc;

use crate::a_sync::interface::MutexedCaches;
use crate::a_sync::network::SocketTaps;
use crate::a_sync::CachesController;
use crate::common::RecordSOA;
use crate::error::*;

use super::query::QDns;
use super::DnsRdata;
use super::QType;
use super::{QuerySetup, ResolveConfig};

/// Resolves the A and AAAA query.
///
/// # Arguments
/// 
/// * `fqdn` - [AsRef] [str] a domain name
/// 
/// * `custom_resolv` - a custom [ResolveConfig] wrapped in [Arc]
/// 
/// # Returns
/// 
/// [CDnsResult] - Ok with inner type [Vec] [RecordSOA]. If vector is empty
///     then no results found.
/// 
/// [CDnsResult] - Err with error description 
pub async 
fn resolve_fqdn<C, LOC, TAP, MC>(
    fqdn: C, 
    custom_resolv: Option<Arc<ResolveConfig>>, 
    cache: Arc<CachesController<MC>>
) -> CDnsResult<Vec<IpAddr>>
where C: AsRef<str>, TAP: SocketTaps<LOC>, MC: MutexedCaches, LOC: Send + Sync
{
    // forming request
    let dns = 
        QDns::<LOC, TAP, MC>::make_a_aaaa_request(custom_resolv, fqdn.as_ref(), 
            QuerySetup::default(), cache).await?;

    // sending request and receiving results
    let res = dns.query().await;
  
    // extracting data
    let mut iplist: Vec<IpAddr> = vec![];

    let recs = res.get_result()?;

    for dnsr in recs.into_iter()
    {
        for resp in dnsr.into_iter()
        {
            match resp.borrow_rdata()
            {
                DnsRdata::A(ip) => 
                    iplist.push(IpAddr::from(ip.ip)),
                DnsRdata::AAAA( ip ) => 
                    iplist.push(IpAddr::from(ip.ip)),
                _ => continue,
            }
        }
    }

    return Ok(iplist);
}

/// Resolves the MX record by domain name. It returns a list of domains.
/// The list is sorted by the `preference`.
/// 
/// # Arguments
/// 
/// * `fqdn` - [AsRef] [str] a domain name
/// 
/// * `custom_resolv` - a custom [ResolveConfig] wrapped in [Arc]
/// 
/// # Returns
/// 
/// [CDnsResult] - Ok with inner type [Vec] [RecordSOA]. If vector is empty
///     then no results found.
/// 
/// [CDnsResult] - Err with error description 
pub async 
fn resolve_mx<C, LOC, TAP, MC>(
    fqdn: C, 
    custom_resolv: Option<Arc<ResolveConfig>>, 
    cache: Arc<CachesController<MC>>
) -> CDnsResult<Vec<String>>
where C: AsRef<str>, TAP: SocketTaps<LOC>, MC: MutexedCaches, LOC: Send + Sync
{
    let mut dns_req = 
        QDns::<LOC, TAP, MC>::make_empty(custom_resolv, QuerySetup::default(), cache).await?;

    dns_req.add_request(QType::MX, fqdn.as_ref());

    // sending request and receiving results
    let res = dns_req.query().await;
  
    // extracting data
    let mut mxlist: Vec<(u16, String)> = Vec::new();

    let recs = res.get_result()?;

    for dnsr in recs.into_iter()
    {
        for resp in dnsr.into_iter()
        {
            match resp.borrow_rdata()
            {
                DnsRdata::MX(mx) =>
                {
                    //iterate and search for the suitable place
                    //let mut index: usize = 0;

                    let idx = mxlist.binary_search_by_key(&mx.preference, |x| x.0).map_or_else(|v| v, |f| f);
                    mxlist.insert(idx, (mx.preference, mx.exchange));

                    /*for (pref, _) in mxlist.iter()
                    {
                        if *pref >= mx.preference
                        {
                            break;
                        }

                        index += 1;
                    }

                    if index == mxlist.len()
                    {
                        // push back
                        mxlist.push((mx.preference, mx.exchange.clone()));
                    }
                    else
                    {
                        mxlist.insert(index, (*preference, exchange.clone()));
                    }*/
                },
                _ => continue,
            }
        }
    }

    return Ok(mxlist.into_iter().map( |(_, ip)| ip).collect());
}

/// Resolves the SOA record
/// 
/// # Arguments
/// 
/// * `fqdn` - [AsRef] [str] a domain name
/// 
/// * `custom_resolv` - a custom [ResolveConfig] wrapped in [Arc]
/// 
/// # Returns
/// 
/// [CDnsResult] - Ok with inner type [Vec] [RecordSOA]. If vector is empty
///     then no results found.
/// 
/// [CDnsResult] - Err with error description 
pub async 
fn resolve_soa<C, LOC, TAP, MC>(
    fqdn: C, 
    custom_resolv: Option<Arc<ResolveConfig>>, 
    cache: Arc<CachesController<MC>>
) -> CDnsResult<Vec<RecordSOA>>
where C: AsRef<str>, TAP: SocketTaps<LOC>, MC: MutexedCaches, LOC: Send + Sync
{
    let mut dns_req = 
        QDns::<LOC, TAP, MC>::make_empty(custom_resolv, QuerySetup::default(), cache).await?;

    dns_req.add_request(QType::SOA, fqdn.as_ref());

    // sending request and receiving results
    let res = dns_req.query().await;

    let mut soa_list: Vec<RecordSOA> = Vec::new();

    let recs = res.get_result()?;

    for dnsr in recs.into_iter()
    {
        for resp in dnsr.into_iter()
        {
            match resp.borrow_rdata()
            {
                DnsRdata::SOA(soa) => 
                    soa_list.push(soa),
                _ => continue,
            }
        }
    }

    return Ok(soa_list);
}

/// Resolves the IP address to FQDN
/// 
/// # Arguments
/// 
/// * `ipaddr` - [AsRef] [str] an IP address
/// 
/// * `custom_resolv` - a custom [ResolveConfig] wrapped in [Arc]
/// 
/// # Returns
/// 
/// [CDnsResult] - Ok with inner type [Vec] [RecordSOA]. If vector is empty
///     then no results found.
/// 
/// [CDnsResult] - Err with error description 
pub async 
fn resolve_reverse<C, LOC, TAP, MC>(
    ipaddr: C, 
    custom_resolv: Option<Arc<ResolveConfig>>,
    cache: Arc<CachesController<MC>>
) -> CDnsResult<Vec<String>>
where C: AsRef<str>, TAP: SocketTaps<LOC>, MC: MutexedCaches, LOC: Send + Sync
{
    let mut dns_req = 
        QDns::<LOC, TAP, MC>::make_empty(custom_resolv, QuerySetup::default(), cache).await?;

    dns_req.add_request(QType::PTR, ipaddr.as_ref());

    // sending request and receiving results
    let res = dns_req.query().await;

    let recs = res.get_result()?;

    let mut ptr_list: Vec<String> = Vec::new();

    for dnsr in recs.into_iter()
    {
        for resp in dnsr.into_iter()
        {
            match resp.borrow_rdata()
            {
                DnsRdata::PTR(ptr) => 
                    ptr_list.push(ptr.fqdn),
                _ => continue,
            }
        }
    }

    return Ok(ptr_list);
}

#[cfg(feature = "use_async_tokio")]
#[cfg(test)]
mod tests
{
    use std::sync::Arc;

    use crate::a_sync::{request::{resolve_fqdn, resolve_mx, resolve_reverse}, SocketBase, CachesController, TokioInterf};

    #[tokio::test]
    async fn test_mx_resolve()
    {
        let cache = Arc::new(CachesController::new().await.unwrap());

        let mx_doms = resolve_mx::<_, SocketBase, SocketBase, TokioInterf>("protonmail.com", None, cache).await;

        assert_eq!(mx_doms.is_err(), false);

        let mx_doms = mx_doms.unwrap();

        let mut index = 0;
        for di in mx_doms
        {
            match index
            {
                0 => assert_eq!(di.as_str(), "mail.protonmail.ch"),
                1 => assert_eq!(di.as_str(), "mailsec.protonmail.ch"),
                _ => panic!("test is required to be modified")
            }

            index += 1;

            println!("{}", di);
        }
        
    }


    #[tokio::test]
    async fn test_a_aaaa_resolve()
    {
        let cache = Arc::new(CachesController::new().await.unwrap());

        let a_aaaa = resolve_fqdn::<_, SocketBase, SocketBase, TokioInterf>("protonmail.com", None, cache).await;

        assert_eq!(a_aaaa.is_ok(), true);

        let a_aaaa = a_aaaa.unwrap();

        for di in a_aaaa
        {
            println!("{}", di);
        }
        
    }

    #[tokio::test]
    async fn test_ptr_resolve_fail()
    {
        let cache = Arc::new(CachesController::new().await.unwrap());

        let ptr = resolve_reverse::<_, SocketBase, SocketBase, TokioInterf>("protonmail.com", None, cache).await;

        assert_eq!(ptr.is_ok(), true);

        let ptr = ptr.unwrap();

        assert_eq!(ptr.len(), 0);
        
    }
}