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
 */



/*! 
# cdns-rs

v 1.2.0 

<img src="https://cdn.4neko.org/cdns_rs_logo.webp" width="280"/> <img src="https://cdn.4neko.org/source_avail.webp" width="280"/> <img src="https://cdn.4neko.org/mpl_or_eupl_500.webp" width="280"/>

An implementation of client side DNS query library which also is able to look for host name in `/etc/hosts`.  
Also it is able to `/etc/resolv.conf` and uses options from this file to configure itself. So it acts like libc's `gethostbyname(3)` or `gethostbyaddr(3)`. The configuration can be overriden.

This library supports both async and sync code. At the moment async part is not available because:
- it is based on the sync realization because async code is based on sync code and sync code is unstable at the moment
- it requires proper porting from sync, because sync uses `poll(2)` to achieve the parallel name resolution

## Supports

- Sending and receiving responses via TCP/UDP
- Reacting on the message truncated event by trying TCP
- Parsing /etc/hosts (all options)
- Partial parsing /etc/resolve.conf (all options)
- Async and Sync code (separate implementations) 
- Sequential and pipelined requests.
- DNS-over-TLS
- IDN international domain names (experimental)

## ToDo
- DNS-over-HTTPS
- Parse /etc/nsswitch.conf
- DNSSEC
- OPT_NO_CHECK_NAMES
- resolv.conf (search, domain, sortlist)

# Extension

To use the DNS-over-TLS, the record to system's resolv.conf can be added:
```text
nameserver 1.1.1.1#@853#cloudflare-dns.com
```
All after the # is considered as extension if there is no space between IP address and '#'.

# Features

`enable_IDN_support` - (enabled by default) allows to resolve IDN
`use_sync` - enabled a sync code base
`use_sync_tls` - enables a TLS support (HTTPS is not yet functional)
`no_error_output` - does not output any errors to stderr

One of the following:
+ `use_async` - enables an async code base
+ `use_async_tokio` - enablesan async tokio code base

One of the following:

+ `use_async_tls` - enables a general TLS support (own implementation out of crate)
+ `use_async_tokio_tls` - enables a tokio based TLS

Usage:  

- see ./examples/

Simple Example:

```ignore
use std::{net::{IpAddr, SocketAddr}, sync::Arc, time::Instant};

use cdns_rs::{cfg_resolv_parser::{OptionFlags, ResolveConfEntry}, common::bind_all, sync::{request, ResolveConfig}};

fn main()
{
    let now = Instant::now();

    let cfg = 
    "nameserver 8.8.8.8 \n\
    options use-vc";

    //  -- or --
    let resolver_ip: IpAddr = "8.8.8.8".parse().unwrap();

    let mut resolve = ResolveConfig::default();

    resolve.nameservers.push(Arc::new(ResolveConfEntry::new(SocketAddr::new(resolver_ip, 53), None, bind_all(resolver_ip)).unwrap()));
    resolve.option_flags = OptionFlags::OPT_USE_VC;

    let cust_parsed = Arc::new(ResolveConfig::custom_config(cfg).unwrap());
    let cust_manual = Arc::new(resolve);

    assert_eq!(cust_parsed, cust_manual);

    // a, aaaa
    let res_a = request::resolve_fqdn("protonmail.com", Some(cust_parsed)).unwrap();

    let elapsed = now.elapsed();
    println!("Elapsed: {:.2?}", elapsed);

    println!("A/AAAA:");
    for a in res_a
    {
        println!("\t{}", a);
    }

    return;
}
```

ToSockAddr

```ignore
use std::net::UdpSocket;

use cdns_rs::sync::query::QDnsSockerAddr;



// run in shell `nc -u -l 44444` a "test" should be received
fn main()
{
    let udp = UdpSocket::bind("127.0.0.1:33333").unwrap();
    udp.connect(QDnsSockerAddr::resolve("localhost:44444").unwrap()).unwrap();

    udp.send("test".as_bytes()).unwrap();

    return;
}
```

### Custom resolv.conf use TCP:
```ignore
use std::{net::{IpAddr, SocketAddr}, sync::Arc, time::Instant};

use cdns_rs::{cfg_resolv_parser::{OptionFlags, ResolveConfEntry}, common::bind_all, sync::{request, ResolveConfig}};

fn main()
{
    let now = Instant::now();

    let cfg = 
    "nameserver 8.8.8.8 \n\
    options use-vc";

    //  -- or --
    let resolver_ip: IpAddr = "8.8.8.8".parse().unwrap();

    let mut resolve = ResolveConfig::default();

    resolve.nameservers.push(Arc::new(ResolveConfEntry::new(SocketAddr::new(resolver_ip, 53), None, bind_all(resolver_ip)).unwrap()));
    resolve.option_flags = OptionFlags::OPT_USE_VC;

    let cust_parsed = Arc::new(ResolveConfig::custom_config(cfg).unwrap());
    let cust_manual = Arc::new(resolve);

    assert_eq!(cust_parsed, cust_manual);

    // a, aaaa
    let res_a = request::resolve_fqdn("protonmail.com", Some(cust_parsed)).unwrap();

    let elapsed = now.elapsed();
    println!("Elapsed: {:.2?}", elapsed);

    println!("A/AAAA:");
    for a in res_a
    {
        println!("\t{}", a);
    }

    return;
}
```

### Custom resolv.conf use TLS:
```ignore
use std::{sync::Arc, time::Instant};

use cdns_rs::sync::{request, ResolveConfig};

fn main()
{
    let now = Instant::now();

    let cfg = "nameserver 1.1.1.1#@853#cloudflare-dns.com \n\
    options use-vc single-request";

    let cust = Arc::new(ResolveConfig::custom_config(cfg).unwrap());

    // a, aaaa
    let res_a = request::resolve_fqdn("google.com", Some(cust.clone())).unwrap();


    let elapsed = now.elapsed();
    println!("Elapsed: {:.2?}", elapsed);

    println!("A/AAAA:");
    for a in res_a
    {
        println!("\t{}", a);
    }

    return;
}

```

# Async (tokio) feature = "use_async_tokio"

```rust

use std::sync::Arc;

use cdns_rs::a_sync::{request, CachesController, IoInterf, QDns, QType, QuerySetup, ResolveConfig, SocketBase};
use tokio::time::Instant;

#[tokio::main]
async fn main()
{
    let cache = Arc::new(CachesController::new().await.unwrap());
    let cfg = "nameserver 1.1.1.1";

    let cust = Arc::new(ResolveConfig::async_custom_config(cfg).await.unwrap());
    let now = Instant::now();

    // a, aaaa
    let res_a = request::resolve_fqdn::<_, SocketBase, SocketBase, IoInterf>("protonmail.com", Some(cust.clone()), cache.clone()).await.unwrap();
    
    // mx
    let res_mx = request::resolve_mx::<_, SocketBase, SocketBase, IoInterf>("protonmail.com", Some(cust.clone()), cache.clone()).await.unwrap();

    let mut dns_req = 
    QDns::<SocketBase, SocketBase, IoInterf>::make_empty(Some(cust.clone()), QuerySetup::default(), cache.clone())
        .await
        .unwrap();

    dns_req.add_request(QType::SOA, "protonmail.com");

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

    let elapsed = now.elapsed();
    println!("Elapsed: {:.2?}", elapsed);

    println!("A/AAAA:");
    for a in res_a
    {
        println!("\t{}", a);
    }

    println!("MX:");
    for mx in res_mx
    {
        println!("\t{}", mx);
    }

    println!("SOA:");
    let soa_res = res.get_result();

    if soa_res.is_err() == true
    {
        println!("error: {}", soa_res.err().unwrap());
    }
    else
    {
        let soa = soa_res.unwrap();

        if soa.is_empty() == true
        {
            println!("\tNo SOA found!")
        }
        else
        {
            for s in soa
            {
                for i in s.resp
                {
                    println!("\t{}", i)
                }
            }
        }
    
    }
}
```
 */



extern crate rand;
#[macro_use] extern crate bitflags;
extern crate nix;

#[cfg(any(feature = "use_sync_tls", feature = "use_async_tokio_tls"))]
extern crate rustls;

#[cfg(feature = "use_async_tokio_tls")]
extern crate tokio_rustls;

#[cfg(any(feature = "use_sync_tls", feature = "use_async_tokio_tls"))]
extern crate webpki;
#[cfg(any(feature = "use_sync_tls", feature = "use_async_tokio_tls"))]
extern crate webpki_roots;
//extern crate webrtc_dtls;

extern crate httparse;

#[cfg(feature = "use_sync")]
extern crate byteorder;
#[cfg(feature = "use_sync")]
extern crate crossbeam_utils;


#[cfg(feature = "use_async")]
extern crate async_recursion;

#[cfg(feature = "use_async")]
extern crate async_trait;

/// An `async` code.
#[cfg(feature = "use_async")]
pub mod a_sync;

#[cfg(feature = "use_async_tokio")]
extern crate tokio;

/// A `sync` code.
#[cfg(feature = "use_sync")]
pub mod sync;

/// An external code under the separate license.
pub mod external;

/// A common `hosts` file parser.
pub mod cfg_host_parser;

/// A common `resolv.conf` file parser.
pub mod cfg_resolv_parser;

/// A common things for networking.
mod network_common;

/// A private things for quries.
mod query_private;

/// A public items for query.
pub mod query;

/// A common functions (shared).
pub mod common;

/// A portable code which is different for the OSes.
mod portable;

/// Error handling.
#[macro_use] pub mod error;

/// A config tokenizer.
mod tokenizer;

pub use error::*;
pub use common::{QType, DnsResponsePayload, DnsRdata};
pub use query::{QDnsQueryResult, QDnsQuery, QuerySetup, QDnsQueryRec};

pub use cfg_host_parser::HostConfig;
pub use cfg_resolv_parser::ResolveConfig;
pub use network_common::SocketTapCommon;