cdns-rs 1.2.1

A native Sync/Async Rust implementation of client DNS resolver.
docs.rs failed to build cdns-rs-1.2.1
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: cdns-rs-1.1.1

CDNS-rs

An implementation of client side DNS query library which also is capable 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 sync and async code. The async is provided for tokio crate or for custom realization using common interface traits.

Also the experimental IDN (international domain names) were added.

Dual licensed crate.

  • This crate i.e code is NOT an Open Source software. This is a FREE (gratis) software and follows the principle of Sources Available/Disclosed software which must be fairly used.
  • It is published under FSF/OSI approved licenses however author does not follow/share/respect OSI and FSF principles and phylosophy.
  • License is subject to be changed in further versions without warning.
  • If you are using code in non-free (in terms of gratis) software you MUST NEVER demand a development of any features which are missing and needed for your business if you are not sponsoring/contributing those changes.
  • Access to the code can be limited by author/platform to specific entities due to the local laws (not my bad or fault)(despite what is said in the license).
  • AI generated sloppy code is prohibited. AI generates slop "a priori" (anyway).
  • Licenses (thank you OSS sectarians ) do not anyhow limit AI training, but f^ck you all - ChatGPT, CockPilot, especially Claude and rest unidentified cr@p.
  • It is strongly discouraged from using the AI based tools to write or enhance the code. AI slope would 100% violate the license by introducing the 3rd party licensed code.

The pull requests are now supported because the repository was moved to Codeberg. The alternative way is to send patches over the email to patch[at]4neko.org.

In case if you would like to contribute the code, please use pull request. Your pull request should include:

  • Description of changes and why it is needed.

  • Test the pull request.

    In case of you prefer email and patch files please consider the following:

  • For each feature or fix, please send patches separatly.

  • Please write what your patch is implementing or fixing.

  • I can read the code and I am able to understand it, so don't write a poem or essay in the description to the patches.

  • Please test your patch.

  • Can I use the MPL-2.0 licensed code (crate) in larger project licensed with more permissive license like BSD or MIT.

Yes, MPL- and Apache-licensed code can be used with an MIT codebase (so in that sense, they are "compatible"). However, the MPL- / Apache-licensed code remains under its original license. (So although compatible, you cannot relicense someone else's MPL or Apache code into the MIT license.) This means that your final codebase will contain a mix of MPL, Apache, and MIT licensed code. As an example, MPL has weak copyleft, so if you modified an MPL file, that file (including your changes) must remain under the MPL license.

Answer1

  • I want to distribute (outside my organization) executable programs or libraries that I have compiled from someone else's unchanged MPL-licensed source code, either standalone or part of a larger work. What do I have to do?

You must inform the recipients where they can get the source for the MPLed code in the executable program or library you are distributing (i.e., you must comply with Section 3.2). You may distribute any executables you create under a license of your choosing, as long as that license does not interfere with the recipients' rights to the source under the terms of the MPL.

MPL2.0 FAQ

You should use this license if you are located in the EU which gives you more advantages over GPL because in case of any disputes, the license allows you to defend your rights in a European Union country, in this case it will be Spain. It has also been translated into all languages of the EU member states.

Matrix of EUPL compatible open source licences

EUPL-1.2 is incompatiable with GPL according to GNU ORG

This is a free software license. By itself, it has a copyleft comparable to the GPL's, and incompatible with it.

Version

v 1.2.0

  • Added International Domain Name support
  • Changes in API (is not yet stabilized)

License:

Sources are available under: MPL-2.0 OR EUPL-1.2

Issues tracker:

The project has moved to Codeberg.

Supported

  • 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)

Supported OSes

  • GNU/Linux (in general)
  • FreeBSD
  • OpenBSD
  • NetBSD
  • DragonflyBSD
  • other UNIX alike OSes

Windows is not supported.

Extension

To use the DNS-over-TLS, the record to system's resolv.conf can be added:

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

ToDo

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

Usage:

  • see ./examples/

Examples

Simple Example:

use cdns_rs::sync::{QDns, QuerySetup, QType, request};

fn main()
{
    // a, aaaa
    let res_a = request::resolve_fqdn("protonmail.com", None).unwrap();

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

Custom query:

use cdns_rs::sync::{QDns, QuerySetup, QType, request, caches::CACHE};

fn main()
{
    // soa
    let mut dns_req = 
        QDns::make_empty(None, QuerySetup::default()).unwrap();

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

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


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

    if soa_res.is_err() == true
    {
        println!("{}", 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)
                }
            }
        }
       
    }
}

Custom resolv.conf use TCP:

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:

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;
}

ToSockAddress

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;
}

Async (tokio) feature = "use_async_tokio"


  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)
                  }
              }
          }
      
      }
  }

Async (custrom exec) feature = "use_async"


// This example demonstrates how to use the async code with custom executor.

use std::{io::ErrorKind, os::fd::AsFd, sync::Arc, time::Duration};

use async_trait::async_trait;
use cdns_rs::
{
    a_sync::
    {
        interface::{AsyncMutex, AsyncMutexGuard, MutexedCaches, UnifiedFs}, 
        network::NetworkTapType, 
        request, 
        CacheInstance, 
        CachesController, 
        NetworkTap, 
        QDns, 
        SocketTap, 
        SocketTaps
    }, 
    cfg_resolv_parser::ResolveConfEntry, 
    internal_error, 
    internal_error_map, 
    CDnsErrorType, 
    CDnsResult, 
    HostConfig, 
    QType, 
    QuerySetup, 
    ResolveConfig, 
    SocketTapCommon
};

use tokio::
{
    fs::File, 
    io::{AsyncReadExt, AsyncWriteExt}, 
    net::{TcpSocket, TcpStream, UdpSocket}, 
    sync::{Mutex, MutexGuard}, 
    time::{timeout, Instant}
};

 
/// Implementaton of the custom sockets interface where the appropriate socket types are
/// defined. This is needed for the crate to use async sockets.
#[derive(Clone, Debug)]
pub struct SocketCustomBase;


/// Implementing a [SocketTaps] for out [SocketCustomBase]. The generic in the trait is needed
/// to overcome the Rust restrictions on implementing foreign traits on foreign structs.
impl SocketTaps<SocketCustomBase> for SocketCustomBase
{
    type TcpSock = LocaltcpStrem;

    type UdpSock = LocalUdpSocket;

    
    #[inline]
    fn new_tcp_socket(resolver: Arc<ResolveConfEntry>, timeout: Duration) -> CDnsResult<Box<NetworkTapType<SocketCustomBase>>>
    {
        return NetworkTap::<Self::TcpSock, SocketCustomBase>::new(resolver, timeout)
    }
    
    #[inline]
    fn new_udp_socket(resolver: Arc<ResolveConfEntry>, timeout: Duration) -> CDnsResult<Box<NetworkTapType<SocketCustomBase>>>
    {
        return NetworkTap::<Self::UdpSock, SocketCustomBase>::new(resolver, timeout)
    }
    
}

/// Defining the struct to avoiding the same problem with foreign struct and traits.
/// Implementation for the UdpSocket.
#[repr(transparent)]
#[derive(Debug)]
pub struct LocalUdpSocket(UdpSocket);

impl AsFd for LocalUdpSocket
{
    fn as_fd(&self) -> std::os::unix::prelude::BorrowedFd<'_> 
    {
        return self.0.as_fd();
    }
}

#[derive(Debug)]
pub struct LocaltcpStrem(TcpStream);

impl AsFd for LocaltcpStrem
{
    fn as_fd(&self) -> std::os::unix::prelude::BorrowedFd<'_> 
    {
        return self.0.as_fd();
    }
}

async 
fn new_tcp_stream(cfg: &ResolveConfEntry, conn_timeout: Option<Duration>) -> CDnsResult<TcpStream> 
{
    // create socket
    let socket = 
        if cfg.get_resolver_ip().is_ipv4() == true
        {
            TcpSocket::new_v4()
        }
        else
        {
            TcpSocket::new_v6()
        }
        .map_err(|e| internal_error_map!(CDnsErrorType::IoError, "{}", e))?;

    // bind address
    socket.bind(*cfg.get_adapter_ip()).map_err(|e| internal_error_map!(CDnsErrorType::IoError, "{}", e))?;

    socket.set_keepalive(false).map_err(|e| internal_error_map!(CDnsErrorType::IoError, "{}", e))?;

    socket.set_nodelay(true).map_err(|e| internal_error_map!(CDnsErrorType::IoError, "{}", e))?;

    // connect
    let tcpstream = 
        if let Some(c_timeout) = conn_timeout
        {
            timeout(c_timeout, socket.connect(*cfg.get_resolver_sa()))
                .await
                .map_err(|e| internal_error_map!(CDnsErrorType::IoError, "{}", e))?
                .map_err(|e| internal_error_map!(CDnsErrorType::IoError, "{}", e))?
        }
        else
        {
            socket
                .connect(*cfg.get_resolver_sa())
                .await
                .map_err(|e| internal_error_map!(CDnsErrorType::IoError, "{}", e))?
        };

    return Ok(tcpstream);
}

#[async_trait]
impl SocketTap<SocketCustomBase> for NetworkTap<LocaltcpStrem, SocketCustomBase>
{
    async 
    fn connect(&mut self, conn_timeout: Option<Duration>) -> CDnsResult<()> 
    {
        if self.sock.is_some() == true
        {
            // ignore
            return Ok(());
        }

        // create socket
        let tcpstream = new_tcp_stream(&self.cfg, conn_timeout).await?;

        self.sock = Some(LocaltcpStrem(tcpstream));

        return Ok(());
    }

    fn is_encrypted(&self) -> bool 
    {
        return false;
    }

    fn is_tcp(&self) -> bool 
    {
        return true;
    }

    fn should_append_len(&self) -> bool
    {
        return true;
    }

    async 
    fn poll_read(&self) -> CDnsResult<()>
    {
        timeout(self.timeout, self.sock.as_ref().unwrap().0.readable())
            .await
            .map_err(|e|
                internal_error_map!(CDnsErrorType::IoError, "Timeout {}", e)
            )?
            .map_err(|e|
                internal_error_map!(CDnsErrorType::IoError, "socket poll error {}", e)
            )
    }

    async 
    fn send(&mut self, sndbuf: &[u8]) -> CDnsResult<usize>  
    {
        return 
            self
                .sock
                .as_mut()
                .unwrap()
                .0
                .write(sndbuf)
                .await
                .map_err(|e| internal_error_map!(CDnsErrorType::IoError, "{}", e));
    }

    async 
    fn recv(&mut self, rcvbuf: &mut [u8]) -> CDnsResult<usize> 
    {
        async 
        fn sub_recv(this: &mut NetworkTap<LocaltcpStrem, SocketCustomBase>, rcvbuf: &mut [u8]) -> CDnsResult<usize> 
        {
            loop
            {
                match this.sock.as_mut().unwrap().0.read(rcvbuf).await
                {
                    Ok(n) => 
                    {
                        return Ok(n);
                    },
                    Err(ref e) if e.kind() == ErrorKind::WouldBlock =>
                    {
                        continue;
                    },
                    Err(ref e) if e.kind() == ErrorKind::Interrupted =>
                    {
                        continue;
                    },
                    Err(e) =>
                    {
                        internal_error!(CDnsErrorType::IoError, "{}", e); 
                    }
                } // match
            } // loop
        }

        // wait for timeout
        match timeout(self.timeout, sub_recv(self, rcvbuf)).await
        {
            Ok(r) => return r,
            Err(e) => internal_error!(CDnsErrorType::RequestTimeout, "{}", e)
        }
    }
}

#[async_trait]
impl SocketTap<SocketCustomBase> for NetworkTap<LocalUdpSocket, SocketCustomBase>
{
    async
    fn connect(&mut self, _conn_timeout: Option<Duration>) -> CDnsResult<()>
    {
        if self.sock.is_some() == true
        {
            // ignore
            return Ok(());
        }

        let socket = 
            UdpSocket::bind(self.cfg.get_adapter_ip())
                .await
                .map_err(|e| internal_error_map!(CDnsErrorType::InternalError, "{}", e))?;

        socket.connect(self.cfg.get_resolver_sa())
            .await
            .map_err(|e| internal_error_map!(CDnsErrorType::IoError, "{}", e))?;

        self.sock = Some(LocalUdpSocket(socket));

        return Ok(());
    }

    fn is_encrypted(&self) -> bool 
    {
        return false;
    }

    fn is_tcp(&self) -> bool 
    {
        return false;
    }

    fn should_append_len(&self) -> bool
    {
        return false;
    }

    async 
    fn poll_read(&self) -> CDnsResult<()>
    {
        timeout(self.timeout, self.sock.as_ref().unwrap().0.readable())
            .await
            .map_err(|e|
                internal_error_map!(CDnsErrorType::IoError, "Timeout {}", e)
            )?
            .map_err(|e|
                internal_error_map!(CDnsErrorType::IoError, "socket poll error {}", e)
            )
    }

    async
    fn send(&mut self, sndbuf: &[u8]) -> CDnsResult<usize> 
    {
        return 
            self.sock.as_mut()
                .unwrap()
                .0
                .send(sndbuf)
                .await
                .map_err(|e| internal_error_map!(CDnsErrorType::IoError, "{}", e));
    }

    async 
    fn recv(&mut self, rcvbuf: &mut [u8]) -> CDnsResult<usize> 
    {
        async 
        fn sub_recv(this: &mut NetworkTap<LocalUdpSocket, SocketCustomBase>, rcvbuf: &mut [u8]) -> CDnsResult<usize> 
        {
            loop
            {
                match this.sock.as_mut().unwrap().0.recv_from(rcvbuf).await
                {
                    Ok((rcv_len, rcv_src)) =>
                    {
                        // this should not fail because socket is "connected"
                        if &rcv_src != this.get_remote_addr()
                        {
                            internal_error!(
                                CDnsErrorType::DnsResponse, 
                                "received answer from unknown host: '{}' exp: '{}'", 
                                this.get_remote_addr(), 
                                rcv_src
                            );
                        }

                        return Ok(rcv_len);
                    },
                    Err(ref e) if e.kind() == ErrorKind::WouldBlock =>
                    {
                        continue;
                    },
                    Err(ref e) if e.kind() == ErrorKind::Interrupted =>
                    {
                        continue;
                    },
                    Err(e) =>
                    {
                        internal_error!(CDnsErrorType::IoError, "{}", e); 
                    }
                } // match
            } // loop
            
        }

        // wait for timeout
        match timeout(self.timeout, sub_recv(self, rcvbuf)).await
        {
            Ok(r) => 
                return r,
            Err(e) => 
                internal_error!(CDnsErrorType::RequestTimeout, "{}", e)
        }
    }
}

/// A struct for our IO interface.
#[derive(Debug)]
pub struct LocalTokioInterf;

/// implementation of the IO interface.
impl MutexedCaches for LocalTokioInterf
{
    type MetadataFs = LocalFile;

    type ResolveCache = LocalMutex<CacheInstance<ResolveConfig, Self::MetadataFs>>;

    type HostCahae = LocalMutex<CacheInstance<HostConfig, Self::MetadataFs>>;
}

#[derive(Debug)]
pub struct LocalFile;

impl UnifiedFs for LocalFile
{
    type ErrRes = std::io::Error;

    type FileOp = File;

    async 
    fn metadata(path: &std::path::Path) -> Result<std::fs::Metadata, Self::ErrRes> 
    {
        return tokio::fs::metadata(path).await;
    }

    async 
    fn open<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<Self::FileOp> 
    {
        return tokio::fs::File::open(path).await;
    }

    async 
    fn read_to_string(file: &mut Self::FileOp, buf: &mut String) -> std::io::Result<usize> 
    {
        return file.read_to_string(buf).await;
    }
}

#[repr(transparent)]
#[derive(Debug)]
pub struct LocalMutex<DS: Sized>(Mutex<DS>);

impl<DS: Sized> AsyncMutex<DS> for LocalMutex<DS>
{
    type MutxGuard<'mux> = LocalMutexGuard<'mux, DS> where DS: 'mux;

    fn a_new(v: DS) -> Self 
    {
        return LocalMutex(Mutex::new(v));
    }
    
    async 
    fn a_lock<'mux>(&'mux self) -> Self::MutxGuard<'mux>
    {
        return LocalMutexGuard(self.0.lock().await);
    }
}

#[repr(transparent)]
#[derive(Debug)]
pub struct LocalMutexGuard<'mux, DS: Sized>(MutexGuard<'mux, DS>);

impl<'mux, DS: Sized> AsyncMutexGuard<'mux, DS>for LocalMutexGuard<'mux, DS>
{
    fn guard(&self) -> &DS
    {
        return &self.0;
    }

    fn guard_mut(&mut self) -> &mut DS
    {
        return &mut self.0;
    }
}


#[tokio::main]
async fn main()
{
    let cache = Arc::new(CachesController::<LocalTokioInterf>::new_custom().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::<_, SocketCustomBase, SocketCustomBase, LocalTokioInterf>("protonmail.com", Some(cust.clone()), cache.clone()).await.unwrap();
    
    // mx
    let res_mx = request::resolve_mx::<_, SocketCustomBase, SocketCustomBase, LocalTokioInterf>("protonmail.com", Some(cust.clone()), cache.clone()).await.unwrap();

    // ptr
    let res_ptr_local = request::resolve_reverse::<_, SocketCustomBase, SocketCustomBase, LocalTokioInterf>("::1", Some(cust.clone()), cache.clone()).await.unwrap();

    // ptr
    let res_ptr = request::resolve_reverse::<_, SocketCustomBase, SocketCustomBase, LocalTokioInterf>("8.8.8.8", Some(cust.clone()), cache.clone()).await.unwrap();
    
    // soa
    //let resolvers = CACHE.clone_resolve_list().await.unwrap();

    let mut dns_req = 
        QDns::<SocketCustomBase, SocketCustomBase, LocalTokioInterf>::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!("PTR local:");
    for ptr in res_ptr_local
    {
        println!("\t{}", ptr);
    }

    println!("PTR:");
    for ptr in res_ptr
    {
        println!("\t{}", ptr);
    }

    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)
                }
            }
        }
       
    }


    return;
}