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.
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, Co
ckPilot, 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:
Examples
Simple Example:
use cdns_rs::sync::{QDns, QuerySetup, QType, request};
fn main()
{
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()
{
let mut dns_req =
QDns::make_empty(None, QuerySetup::default()).unwrap();
dns_req.add_request(QType::SOA, "protonmail.com");
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";
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);
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());
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;
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();
let res_a = request::resolve_fqdn::<_, SocketBase, SocketBase, IoInterf>("protonmail.com", Some(cust.clone()), cache.clone()).await.unwrap();
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");
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"
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}
};
#[derive(Clone, Debug)]
pub struct SocketCustomBase;
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)
}
}
#[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>
{
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))?;
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))?;
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
{
return Ok(());
}
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 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
{
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)) =>
{
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 timeout(self.timeout, sub_recv(self, rcvbuf)).await
{
Ok(r) =>
return r,
Err(e) =>
internal_error!(CDnsErrorType::RequestTimeout, "{}", e)
}
}
}
#[derive(Debug)]
pub struct LocalTokioInterf;
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();
let res_a = request::resolve_fqdn::<_, SocketCustomBase, SocketCustomBase, LocalTokioInterf>("protonmail.com", Some(cust.clone()), cache.clone()).await.unwrap();
let res_mx = request::resolve_mx::<_, SocketCustomBase, SocketCustomBase, LocalTokioInterf>("protonmail.com", Some(cust.clone()), cache.clone()).await.unwrap();
let res_ptr_local = request::resolve_reverse::<_, SocketCustomBase, SocketCustomBase, LocalTokioInterf>("::1", Some(cust.clone()), cache.clone()).await.unwrap();
let res_ptr = request::resolve_reverse::<_, SocketCustomBase, SocketCustomBase, LocalTokioInterf>("8.8.8.8", Some(cust.clone()), cache.clone()).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");
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;
}