cdns_rs/lib.rs
1/*-
2 * cdns-rs - a simple sync/async DNS query library
3 *
4 * Copyright (C) 2020 Aleksandr Morozov
5 *
6 * Copyright (C) 2025 Aleksandr Morozov
7 *
8 * The syslog-rs crate can be redistributed and/or modified
9 * under the terms of either of the following licenses:
10 *
11 * 1. the Mozilla Public License Version 2.0 (the “MPL”) OR
12 *
13 * 2. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
14 */
15
16
17
18/*!
19# cdns-rs
20
21v 1.2.0
22
23<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"/>
24
25An implementation of client side DNS query library which also is able to look for host name in `/etc/hosts`.
26Also 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.
27
28This library supports both async and sync code. At the moment async part is not available because:
29- it is based on the sync realization because async code is based on sync code and sync code is unstable at the moment
30- it requires proper porting from sync, because sync uses `poll(2)` to achieve the parallel name resolution
31
32## Supports
33
34- Sending and receiving responses via TCP/UDP
35- Reacting on the message truncated event by trying TCP
36- Parsing /etc/hosts (all options)
37- Partial parsing /etc/resolve.conf (all options)
38- Async and Sync code (separate implementations)
39- Sequential and pipelined requests.
40- DNS-over-TLS
41- IDN international domain names (experimental)
42
43## ToDo
44- DNS-over-HTTPS
45- Parse /etc/nsswitch.conf
46- DNSSEC
47- OPT_NO_CHECK_NAMES
48- resolv.conf (search, domain, sortlist)
49
50# Extension
51
52To use the DNS-over-TLS, the record to system's resolv.conf can be added:
53```text
54nameserver 1.1.1.1#@853#cloudflare-dns.com
55```
56All after the # is considered as extension if there is no space between IP address and '#'.
57
58# Features
59
60`enable_IDN_support` - (enabled by default) allows to resolve IDN
61`use_sync` - enabled a sync code base
62`use_sync_tls` - enables a TLS support (HTTPS is not yet functional)
63`no_error_output` - does not output any errors to stderr
64
65One of the following:
66+ `use_async` - enables an async code base
67+ `use_async_tokio` - enablesan async tokio code base
68
69One of the following:
70
71+ `use_async_tls` - enables a general TLS support (own implementation out of crate)
72+ `use_async_tokio_tls` - enables a tokio based TLS
73
74Usage:
75
76- see ./examples/
77
78Simple Example:
79
80```ignore
81use std::{net::{IpAddr, SocketAddr}, sync::Arc, time::Instant};
82
83use cdns_rs::{cfg_resolv_parser::{OptionFlags, ResolveConfEntry}, common::bind_all, sync::{request, ResolveConfig}};
84
85fn main()
86{
87 let now = Instant::now();
88
89 let cfg =
90 "nameserver 8.8.8.8 \n\
91 options use-vc";
92
93 // -- or --
94 let resolver_ip: IpAddr = "8.8.8.8".parse().unwrap();
95
96 let mut resolve = ResolveConfig::default();
97
98 resolve.nameservers.push(Arc::new(ResolveConfEntry::new(SocketAddr::new(resolver_ip, 53), None, bind_all(resolver_ip)).unwrap()));
99 resolve.option_flags = OptionFlags::OPT_USE_VC;
100
101 let cust_parsed = Arc::new(ResolveConfig::custom_config(cfg).unwrap());
102 let cust_manual = Arc::new(resolve);
103
104 assert_eq!(cust_parsed, cust_manual);
105
106 // a, aaaa
107 let res_a = request::resolve_fqdn("protonmail.com", Some(cust_parsed)).unwrap();
108
109 let elapsed = now.elapsed();
110 println!("Elapsed: {:.2?}", elapsed);
111
112 println!("A/AAAA:");
113 for a in res_a
114 {
115 println!("\t{}", a);
116 }
117
118 return;
119}
120```
121
122ToSockAddr
123
124```ignore
125use std::net::UdpSocket;
126
127use cdns_rs::sync::query::QDnsSockerAddr;
128
129
130
131// run in shell `nc -u -l 44444` a "test" should be received
132fn main()
133{
134 let udp = UdpSocket::bind("127.0.0.1:33333").unwrap();
135 udp.connect(QDnsSockerAddr::resolve("localhost:44444").unwrap()).unwrap();
136
137 udp.send("test".as_bytes()).unwrap();
138
139 return;
140}
141```
142
143### Custom resolv.conf use TCP:
144```ignore
145use std::{net::{IpAddr, SocketAddr}, sync::Arc, time::Instant};
146
147use cdns_rs::{cfg_resolv_parser::{OptionFlags, ResolveConfEntry}, common::bind_all, sync::{request, ResolveConfig}};
148
149fn main()
150{
151 let now = Instant::now();
152
153 let cfg =
154 "nameserver 8.8.8.8 \n\
155 options use-vc";
156
157 // -- or --
158 let resolver_ip: IpAddr = "8.8.8.8".parse().unwrap();
159
160 let mut resolve = ResolveConfig::default();
161
162 resolve.nameservers.push(Arc::new(ResolveConfEntry::new(SocketAddr::new(resolver_ip, 53), None, bind_all(resolver_ip)).unwrap()));
163 resolve.option_flags = OptionFlags::OPT_USE_VC;
164
165 let cust_parsed = Arc::new(ResolveConfig::custom_config(cfg).unwrap());
166 let cust_manual = Arc::new(resolve);
167
168 assert_eq!(cust_parsed, cust_manual);
169
170 // a, aaaa
171 let res_a = request::resolve_fqdn("protonmail.com", Some(cust_parsed)).unwrap();
172
173 let elapsed = now.elapsed();
174 println!("Elapsed: {:.2?}", elapsed);
175
176 println!("A/AAAA:");
177 for a in res_a
178 {
179 println!("\t{}", a);
180 }
181
182 return;
183}
184```
185
186### Custom resolv.conf use TLS:
187```ignore
188use std::{sync::Arc, time::Instant};
189
190use cdns_rs::sync::{request, ResolveConfig};
191
192fn main()
193{
194 let now = Instant::now();
195
196 let cfg = "nameserver 1.1.1.1#@853#cloudflare-dns.com \n\
197 options use-vc single-request";
198
199 let cust = Arc::new(ResolveConfig::custom_config(cfg).unwrap());
200
201 // a, aaaa
202 let res_a = request::resolve_fqdn("google.com", Some(cust.clone())).unwrap();
203
204
205 let elapsed = now.elapsed();
206 println!("Elapsed: {:.2?}", elapsed);
207
208 println!("A/AAAA:");
209 for a in res_a
210 {
211 println!("\t{}", a);
212 }
213
214 return;
215}
216
217```
218
219# Async (tokio) feature = "use_async_tokio"
220
221```rust
222
223use std::sync::Arc;
224
225use cdns_rs::a_sync::{request, CachesController, IoInterf, QDns, QType, QuerySetup, ResolveConfig, SocketBase};
226use tokio::time::Instant;
227
228#[tokio::main]
229async fn main()
230{
231 let cache = Arc::new(CachesController::new().await.unwrap());
232 let cfg = "nameserver 1.1.1.1";
233
234 let cust = Arc::new(ResolveConfig::async_custom_config(cfg).await.unwrap());
235 let now = Instant::now();
236
237 // a, aaaa
238 let res_a = request::resolve_fqdn::<_, SocketBase, SocketBase, IoInterf>("protonmail.com", Some(cust.clone()), cache.clone()).await.unwrap();
239
240 // mx
241 let res_mx = request::resolve_mx::<_, SocketBase, SocketBase, IoInterf>("protonmail.com", Some(cust.clone()), cache.clone()).await.unwrap();
242
243 let mut dns_req =
244 QDns::<SocketBase, SocketBase, IoInterf>::make_empty(Some(cust.clone()), QuerySetup::default(), cache.clone())
245 .await
246 .unwrap();
247
248 dns_req.add_request(QType::SOA, "protonmail.com");
249
250 // sending request and receiving results
251 let res = dns_req.query().await;
252
253 let elapsed = now.elapsed();
254 println!("Elapsed: {:.2?}", elapsed);
255
256 println!("A/AAAA:");
257 for a in res_a
258 {
259 println!("\t{}", a);
260 }
261
262 println!("MX:");
263 for mx in res_mx
264 {
265 println!("\t{}", mx);
266 }
267
268 println!("SOA:");
269 let soa_res = res.get_result();
270
271 if soa_res.is_err() == true
272 {
273 println!("error: {}", soa_res.err().unwrap());
274 }
275 else
276 {
277 let soa = soa_res.unwrap();
278
279 if soa.is_empty() == true
280 {
281 println!("\tNo SOA found!")
282 }
283 else
284 {
285 for s in soa
286 {
287 for i in s.resp
288 {
289 println!("\t{}", i)
290 }
291 }
292 }
293
294 }
295}
296```
297 */
298
299
300
301extern crate rand;
302#[macro_use] extern crate bitflags;
303extern crate nix;
304
305#[cfg(any(feature = "use_sync_tls", feature = "use_async_tokio_tls"))]
306extern crate rustls;
307
308#[cfg(feature = "use_async_tokio_tls")]
309extern crate tokio_rustls;
310
311#[cfg(any(feature = "use_sync_tls", feature = "use_async_tokio_tls"))]
312extern crate webpki;
313#[cfg(any(feature = "use_sync_tls", feature = "use_async_tokio_tls"))]
314extern crate webpki_roots;
315//extern crate webrtc_dtls;
316
317extern crate httparse;
318
319#[cfg(feature = "use_sync")]
320extern crate byteorder;
321#[cfg(feature = "use_sync")]
322extern crate crossbeam_utils;
323
324
325#[cfg(feature = "use_async")]
326extern crate async_recursion;
327
328#[cfg(feature = "use_async")]
329extern crate async_trait;
330
331/// An `async` code.
332#[cfg(feature = "use_async")]
333pub mod a_sync;
334
335#[cfg(feature = "use_async_tokio")]
336extern crate tokio;
337
338/// A `sync` code.
339#[cfg(feature = "use_sync")]
340pub mod sync;
341
342/// An external code under the separate license.
343pub mod external;
344
345/// A common `hosts` file parser.
346pub mod cfg_host_parser;
347
348/// A common `resolv.conf` file parser.
349pub mod cfg_resolv_parser;
350
351/// A common things for networking.
352mod network_common;
353
354/// A private things for quries.
355mod query_private;
356
357/// A public items for query.
358pub mod query;
359
360/// A common functions (shared).
361pub mod common;
362
363/// A portable code which is different for the OSes.
364mod portable;
365
366/// Error handling.
367#[macro_use] pub mod error;
368
369/// A config tokenizer.
370mod tokenizer;
371
372pub use error::*;
373pub use common::{QType, DnsResponsePayload, DnsRdata};
374pub use query::{QDnsQueryResult, QDnsQuery, QuerySetup, QDnsQueryRec};
375
376pub use cfg_host_parser::HostConfig;
377pub use cfg_resolv_parser::ResolveConfig;
378pub use network_common::SocketTapCommon;
379
380