├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── issues.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── async.rs ├── backend ├── async_smol.rs ├── async_std.rs ├── async_tokio.rs ├── mod.rs └── sync.rs ├── lib.rs ├── sync.rs ├── system.rs └── upstream_server.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Rust CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | rust: 18 | - stable 19 | - beta 20 | - nightly 21 | features: 22 | - '--features async-tokio' 23 | - '--features async-smol --no-default-features' 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Install Rust ${{ matrix.rust }} 27 | uses: dtolnay/rust-toolchain@master 28 | with: 29 | toolchain: ${{ matrix.rust }} 30 | - name: Build 31 | run: cargo build ${{ matrix.features }} 32 | - name: Run tests 33 | run: cargo test ${{ matrix.features }} 34 | 35 | compatibility: 36 | runs-on: ubuntu-latest 37 | strategy: 38 | matrix: 39 | rust: [stable] 40 | os: [ubuntu-latest, macos-latest, windows-latest] 41 | steps: 42 | - uses: actions/checkout@v4 43 | - name: Install Rust 44 | uses: dtolnay/rust-toolchain@master 45 | with: 46 | toolchain: ${{ matrix.rust }} 47 | - name: Build default (tokio) 48 | run: cargo build 49 | - name: Build smol backend 50 | run: cargo build --features async-smol --no-default-features 51 | - name: Test default (tokio) 52 | run: cargo test 53 | - name: Test smol backend 54 | run: cargo test --features async-smol --no-default-features 55 | -------------------------------------------------------------------------------- /.github/workflows/issues.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/stale@v9 14 | with: 15 | stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." 16 | close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | *~ 5 | 6 | **/.claude/settings.local.json 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dnsclient" 3 | version = "0.2.2" 4 | description = "A small, simple and secure DNS client library" 5 | authors = ["Frank Denis "] 6 | keywords = ["dns", "client"] 7 | license = "ISC" 8 | homepage = "https://github.com/jedisct1/rust-dnsclient" 9 | repository = "https://github.com/jedisct1/rust-dnsclient" 10 | categories = ["os::unix-apis"] 11 | edition = "2018" 12 | 13 | [dependencies] 14 | smol = { version = "2.0.0", optional = true } 15 | futures-lite = { version = "2.2.0", optional = true } 16 | dnssector = "0.2.14" 17 | rand = "0.9.1" 18 | tokio = { version = "1.44.2", optional = true, features = ["full"] } 19 | 20 | [features] 21 | async-smol = [ "smol", "futures-lite" ] 22 | async-tokio = [ "tokio" ] 23 | default = [ "async-tokio" ] 24 | # If async-smol is specified as a feature, it will be used even if async-tokio is also enabled 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2024, Frank Denis 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A simple and secure DNS client for Rust 2 | ======================================= 3 | 4 | [API documentation](https://docs.rs/dnsclient) 5 | 6 | This is a DNS client crate. Some people may call that a stub resolver. 7 | 8 | It can resolve IPv4 and IPv6 addresses. But unlike `std::net::ToSocketAddrs`, it directly contacts upstream servers, and doesn't depend on the system resolver. Which, in the worst case, could be systemd. 9 | 10 | Instead, your application fully controls what upstream resolvers will be used. 11 | 12 | It can also send raw queries, and return raw responses, retrying over multiple server candidates if necessary. 13 | 14 | DNSClient carefully checks the consistency of every single packet it receives. 15 | 16 | It will not let clients initiate zone transfers. It may prevent funky DNS implementations from crashing or being exploited when a malicious query or response is received. 17 | 18 | It also transparently falls back to TCP when a truncated response is received. 19 | 20 | Finally, its API couldn't be any simpler. 21 | 22 | DNSClient comes with a synchronous interface (`sync::*`) as well as an asynchronous interface (`async::*`). 23 | 24 | Cargo features: 25 | - `async-smol`: use `smol` as an async backend 26 | - `async-tokio`: use `tokio` as an async backend (default) 27 | -------------------------------------------------------------------------------- /src/async.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; 3 | use std::time::Duration; 4 | 5 | use dnssector::constants::{Class, Type}; 6 | use dnssector::*; 7 | use rand::{seq::SliceRandom, Rng}; 8 | 9 | // When both backends are enabled but async-smol is explicitly specified, use smol 10 | // otherwise use tokio (the default) 11 | #[cfg(feature = "async-smol")] 12 | use crate::backend::async_smol::AsyncBackend; 13 | #[cfg(all(feature = "async-tokio", not(feature = "async-smol")))] 14 | use crate::backend::async_tokio::AsyncBackend; 15 | use crate::upstream_server::UpstreamServer; 16 | 17 | #[derive(Clone, Debug)] 18 | pub struct DNSClient { 19 | backend: AsyncBackend, 20 | upstream_servers: Vec, 21 | local_v4_addr: SocketAddr, 22 | local_v6_addr: SocketAddr, 23 | force_tcp: bool, 24 | } 25 | 26 | impl DNSClient { 27 | pub fn new(upstream_servers: Vec) -> Self { 28 | DNSClient { 29 | backend: AsyncBackend::new(Duration::new(6, 0)), 30 | upstream_servers, 31 | local_v4_addr: ([0; 4], 0).into(), 32 | local_v6_addr: ([0; 16], 0).into(), 33 | force_tcp: false, 34 | } 35 | } 36 | 37 | #[cfg(unix)] 38 | pub fn new_with_system_resolvers() -> Result { 39 | Ok(DNSClient::new(crate::system::default_resolvers()?)) 40 | } 41 | 42 | pub fn set_timeout(&mut self, timeout: Duration) { 43 | self.backend.upstream_server_timeout = timeout 44 | } 45 | 46 | pub fn set_local_v4_addr>(&mut self, addr: T) { 47 | self.local_v4_addr = addr.into() 48 | } 49 | 50 | pub fn set_local_v6_addr>(&mut self, addr: T) { 51 | self.local_v6_addr = addr.into() 52 | } 53 | 54 | pub fn force_tcp(&mut self, force_tcp: bool) { 55 | self.force_tcp = force_tcp; 56 | } 57 | 58 | async fn send_query_to_upstream_server( 59 | &self, 60 | upstream_server: &UpstreamServer, 61 | query_tid: u16, 62 | query_question: &Option<(Vec, u16, u16)>, 63 | query: &[u8], 64 | ) -> Result { 65 | let local_addr = match upstream_server.addr { 66 | SocketAddr::V4(_) => &self.local_v4_addr, 67 | SocketAddr::V6(_) => &self.local_v6_addr, 68 | }; 69 | let response = if self.force_tcp { 70 | self.backend 71 | .dns_exchange_tcp(local_addr, upstream_server, query) 72 | .await? 73 | } else { 74 | self.backend 75 | .dns_exchange_udp(local_addr, upstream_server, query) 76 | .await? 77 | }; 78 | let mut parsed_response = DNSSector::new(response) 79 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))? 80 | .parse() 81 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 82 | if !self.force_tcp && parsed_response.flags() & DNS_FLAG_TC == DNS_FLAG_TC { 83 | parsed_response = { 84 | let response = self 85 | .backend 86 | .dns_exchange_tcp(local_addr, upstream_server, query) 87 | .await?; 88 | DNSSector::new(response) 89 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))? 90 | .parse() 91 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))? 92 | }; 93 | } 94 | if parsed_response.tid() != query_tid || &parsed_response.question() != query_question { 95 | return Err(io::Error::new( 96 | io::ErrorKind::PermissionDenied, 97 | "Unexpected response", 98 | )); 99 | } 100 | Ok(parsed_response) 101 | } 102 | 103 | async fn query_from_parsed_query( 104 | &self, 105 | mut parsed_query: ParsedPacket, 106 | ) -> Result { 107 | let query_tid = parsed_query.tid(); 108 | let query_question = parsed_query.question(); 109 | if query_question.is_none() || parsed_query.flags() & DNS_FLAG_QR != 0 { 110 | return Err(io::Error::new( 111 | io::ErrorKind::InvalidInput, 112 | "No DNS question", 113 | )); 114 | } 115 | let valid_query = parsed_query.into_packet(); 116 | for upstream_server in &self.upstream_servers { 117 | if let Ok(parsed_response) = self 118 | .send_query_to_upstream_server( 119 | upstream_server, 120 | query_tid, 121 | &query_question, 122 | &valid_query, 123 | ) 124 | .await 125 | { 126 | return Ok(parsed_response); 127 | } 128 | } 129 | Err(io::Error::new( 130 | io::ErrorKind::InvalidInput, 131 | "No response received from any servers", 132 | )) 133 | } 134 | 135 | /// Send a raw query to the DNS server and return the response. 136 | pub async fn query_raw(&self, query: &[u8], tid_masking: bool) -> Result, io::Error> { 137 | let mut parsed_query = DNSSector::new(query.to_vec()) 138 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))? 139 | .parse() 140 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 141 | let mut tid = 0; 142 | if tid_masking { 143 | tid = parsed_query.tid(); 144 | let mut rnd = rand::rng(); 145 | let masked_tid: u16 = rnd.random(); 146 | parsed_query.set_tid(masked_tid); 147 | } 148 | let mut parsed_response = self.query_from_parsed_query(parsed_query).await?; 149 | if tid_masking { 150 | parsed_response.set_tid(tid); 151 | } 152 | let response = parsed_response.into_packet(); 153 | Ok(response) 154 | } 155 | 156 | /// Return IPv4 addresses. 157 | pub async fn query_a(&self, name: &str) -> Result, io::Error> { 158 | let parsed_query = dnssector::gen::query( 159 | name.as_bytes(), 160 | Type::from_string("A").unwrap(), 161 | Class::from_string("IN").unwrap(), 162 | ) 163 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 164 | let mut parsed_response = self.query_from_parsed_query(parsed_query).await?; 165 | let mut ips = vec![]; 166 | 167 | let mut it = parsed_response.into_iter_answer(); 168 | while let Some(item) = it { 169 | if let Ok(IpAddr::V4(addr)) = item.rr_ip() { 170 | ips.push(addr); 171 | } 172 | it = item.next(); 173 | } 174 | ips.shuffle(&mut rand::rng()); 175 | Ok(ips) 176 | } 177 | 178 | /// Return IPv6 addresses. 179 | pub async fn query_aaaa(&self, name: &str) -> Result, io::Error> { 180 | let parsed_query = dnssector::gen::query( 181 | name.as_bytes(), 182 | Type::from_string("AAAA").unwrap(), 183 | Class::from_string("IN").unwrap(), 184 | ) 185 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 186 | let mut parsed_response = self.query_from_parsed_query(parsed_query).await?; 187 | let mut ips = vec![]; 188 | 189 | let mut it = parsed_response.into_iter_answer(); 190 | while let Some(item) = it { 191 | if let Ok(IpAddr::V6(addr)) = item.rr_ip() { 192 | ips.push(addr); 193 | } 194 | it = item.next(); 195 | } 196 | ips.shuffle(&mut rand::rng()); 197 | Ok(ips) 198 | } 199 | 200 | /// Return both IPv4 and IPv6 addresses, performing both queries 201 | /// simultaneously. 202 | pub async fn query_addrs(&self, name: &str) -> Result, io::Error> { 203 | let futs = self 204 | .backend 205 | .join(self.query_a(name), self.query_aaaa(name)) 206 | .await; 207 | let ipv4_ips = futs.0?; 208 | let ipv6_ips = futs.1?; 209 | let mut ips: Vec<_> = ipv4_ips 210 | .into_iter() 211 | .map(IpAddr::from) 212 | .chain(ipv6_ips.into_iter().map(IpAddr::from)) 213 | .collect(); 214 | ips.shuffle(&mut rand::rng()); 215 | Ok(ips) 216 | } 217 | 218 | /// Return TXT records. 219 | pub async fn query_txt(&self, name: &str) -> Result>, io::Error> { 220 | let rr_class = Class::from_string("IN").unwrap(); 221 | let rr_type = Type::from_string("TXT").unwrap(); 222 | let parsed_query = dnssector::gen::query(name.as_bytes(), rr_type, rr_class) 223 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 224 | let mut parsed_response = self.query_from_parsed_query(parsed_query).await?; 225 | let mut txts: Vec> = vec![]; 226 | 227 | let mut it = parsed_response.into_iter_answer(); 228 | while let Some(item) = it { 229 | if item.rr_class() != rr_class.into() || item.rr_type() != rr_type.into() { 230 | it = item.next(); 231 | continue; 232 | } 233 | if let Ok(RawRRData::Data(data)) = item.rr_rd() { 234 | let mut txt = vec![]; 235 | let mut it = data.iter(); 236 | while let Some(&len) = it.next() { 237 | for _ in 0..len { 238 | txt.push(*it.next().ok_or_else(|| { 239 | io::Error::new(io::ErrorKind::InvalidInput, "Invalid text record") 240 | })?) 241 | } 242 | } 243 | txts.push(txt); 244 | } 245 | it = item.next(); 246 | } 247 | Ok(txts) 248 | } 249 | 250 | /// Reverse IP lookup. 251 | pub async fn query_ptr(&self, ip: &IpAddr) -> Result, io::Error> { 252 | let rr_class = Class::from_string("IN").unwrap(); 253 | let rr_type = Type::from_string("PTR").unwrap(); 254 | let rev_name = match ip { 255 | IpAddr::V4(ip) => { 256 | let mut octets = ip.octets(); 257 | octets.reverse(); 258 | format!( 259 | "{}.{}.{}.{}.in-addr.arpa", 260 | octets[0], octets[1], octets[2], octets[3] 261 | ) 262 | } 263 | IpAddr::V6(ip) => { 264 | let mut octets = ip.octets(); 265 | octets.reverse(); 266 | let rev = octets 267 | .iter() 268 | .map(|x| x.to_string()) 269 | .collect::>() 270 | .join("."); 271 | format!("{}.ip6.arpa", rev) 272 | } 273 | }; 274 | let parsed_query = dnssector::gen::query(rev_name.as_bytes(), rr_type, rr_class) 275 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 276 | let mut parsed_response = self.query_from_parsed_query(parsed_query).await?; 277 | let mut names: Vec = vec![]; 278 | 279 | let mut it = parsed_response.into_iter_answer(); 280 | while let Some(item) = it { 281 | if item.rr_class() != rr_class.into() || item.rr_type() != rr_type.into() { 282 | it = item.next(); 283 | continue; 284 | } 285 | if let Ok(RawRRData::Data(data)) = item.rr_rd() { 286 | let mut name = vec![]; 287 | let mut it = data.iter(); 288 | while let Some(&len) = it.next() { 289 | if len != 0 && !name.is_empty() { 290 | name.push(b'.'); 291 | } 292 | for _ in 0..len { 293 | name.push(*it.next().ok_or_else(|| { 294 | io::Error::new(io::ErrorKind::InvalidInput, "Invalid text record") 295 | })?) 296 | } 297 | } 298 | if name.is_empty() { 299 | name.push(b'.'); 300 | } 301 | if let Ok(name) = String::from_utf8(name) { 302 | match ip { 303 | IpAddr::V4(ip) => { 304 | if self.query_a(&name).await?.contains(ip) { 305 | names.push(name) 306 | } 307 | } 308 | IpAddr::V6(ip) => { 309 | if self.query_aaaa(&name).await?.contains(ip) { 310 | names.push(name) 311 | } 312 | } 313 | }; 314 | } 315 | } 316 | it = item.next(); 317 | } 318 | Ok(names) 319 | } 320 | 321 | /// Return the raw record data for the given query type. 322 | pub async fn query_rrs_data( 323 | &self, 324 | name: &str, 325 | query_class: &str, 326 | query_type: &str, 327 | ) -> Result>, io::Error> { 328 | let rr_class = Class::from_string(query_class) 329 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 330 | let rr_type = Type::from_string(query_type) 331 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 332 | let parsed_query = dnssector::gen::query(name.as_bytes(), rr_type, rr_class) 333 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 334 | let mut parsed_response = self.query_from_parsed_query(parsed_query).await?; 335 | let mut raw_rrs = vec![]; 336 | 337 | let mut it = parsed_response.into_iter_answer(); 338 | while let Some(item) = it { 339 | if item.rr_class() != rr_class.into() || item.rr_type() != rr_type.into() { 340 | it = item.next(); 341 | continue; 342 | } 343 | if let Ok(RawRRData::Data(data)) = item.rr_rd() { 344 | raw_rrs.push(data.to_vec()); 345 | } 346 | it = item.next(); 347 | } 348 | Ok(raw_rrs) 349 | } 350 | } 351 | 352 | #[cfg(test)] 353 | mod tests { 354 | use std::future::Future; 355 | 356 | use super::*; 357 | 358 | #[cfg(feature = "async-smol")] 359 | fn block_on(future: F) -> F::Output { 360 | smol::block_on(future) 361 | } 362 | 363 | #[cfg(all(feature = "async-tokio", not(feature = "async-smol")))] 364 | fn block_on(future: F) -> F::Output { 365 | use tokio::runtime; 366 | let rt = runtime::Builder::new_current_thread() 367 | .enable_time() 368 | .enable_io() 369 | .build() 370 | .unwrap(); 371 | rt.block_on(future) 372 | } 373 | 374 | #[test] 375 | fn test_query_a() { 376 | use std::str::FromStr; 377 | 378 | let dns_client = DNSClient::new(vec![ 379 | UpstreamServer::new(SocketAddr::from_str("1.0.0.1:53").unwrap()), 380 | UpstreamServer::new(SocketAddr::from_str("1.1.1.1:53").unwrap()), 381 | ]); 382 | block_on(async { 383 | let r = dns_client.query_a("one.one.one.one").await.unwrap(); 384 | assert!(r.contains(&Ipv4Addr::new(1, 1, 1, 1))); 385 | }) 386 | } 387 | 388 | #[test] 389 | fn test_query_addrs() { 390 | use std::str::FromStr; 391 | 392 | let dns_client = DNSClient::new(vec![ 393 | UpstreamServer::new(SocketAddr::from_str("1.0.0.1:53").unwrap()), 394 | UpstreamServer::new(SocketAddr::from_str("1.1.1.1:53").unwrap()), 395 | ]); 396 | block_on(async { 397 | let r = dns_client.query_addrs("one.one.one.one").await.unwrap(); 398 | assert!(r.contains(&IpAddr::from(Ipv4Addr::new(1, 1, 1, 1)))); 399 | }) 400 | } 401 | 402 | #[test] 403 | fn test_query_txt() { 404 | use std::str::FromStr; 405 | 406 | let dns_client = DNSClient::new(vec![ 407 | UpstreamServer::new(SocketAddr::from_str("1.0.0.1:53").unwrap()), 408 | UpstreamServer::new(SocketAddr::from_str("1.1.1.1:53").unwrap()), 409 | ]); 410 | block_on(async { 411 | let r = dns_client.query_txt("fastly.com").await.unwrap(); 412 | assert!(r.iter().any(|txt| { 413 | let txt = std::str::from_utf8(txt).unwrap(); 414 | txt.starts_with("google-site") 415 | })) 416 | }) 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /src/backend/async_smol.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::io; 3 | use std::net::SocketAddr; 4 | use std::time::Duration; 5 | 6 | use dnssector::constants::DNS_MAX_COMPRESSED_SIZE; 7 | use futures_lite::future::FutureExt; 8 | use smol::net::{TcpStream, UdpSocket}; 9 | use smol::prelude::*; 10 | use smol::Timer; 11 | 12 | use crate::upstream_server::UpstreamServer; 13 | 14 | #[derive(Clone, Debug)] 15 | pub struct AsyncBackend { 16 | pub upstream_server_timeout: Duration, 17 | } 18 | 19 | impl AsyncBackend { 20 | pub fn new(upstream_server_timeout: Duration) -> Self { 21 | AsyncBackend { 22 | upstream_server_timeout, 23 | } 24 | } 25 | 26 | pub async fn dns_exchange_udp( 27 | &self, 28 | local_addr: &SocketAddr, 29 | upstream_server: &UpstreamServer, 30 | query: &[u8], 31 | ) -> io::Result> { 32 | async { 33 | let socket = UdpSocket::bind(local_addr).await?; 34 | socket.connect(upstream_server.addr).await?; 35 | socket.send(query).await?; 36 | let mut response = vec![0; DNS_MAX_COMPRESSED_SIZE]; 37 | let response_len = socket 38 | .recv(&mut response) 39 | .await 40 | .map_err(|_| io::Error::new(io::ErrorKind::WouldBlock, "Timeout"))?; 41 | response.truncate(response_len); 42 | Ok(response) 43 | } 44 | .or(async { 45 | Timer::after(self.upstream_server_timeout).await; 46 | Err(io::Error::new(io::ErrorKind::TimedOut, "Request timed out")) 47 | }) 48 | .await 49 | } 50 | 51 | pub async fn dns_exchange_tcp( 52 | &self, 53 | _local_addr: &SocketAddr, 54 | upstream_server: &UpstreamServer, 55 | query: &[u8], 56 | ) -> io::Result> { 57 | async { 58 | let mut stream = TcpStream::connect(&upstream_server.addr).await?; 59 | let _ = stream.set_nodelay(true); 60 | let query_len = query.len(); 61 | let mut tcp_query = Vec::with_capacity(2 + query_len); 62 | tcp_query.push((query_len >> 8) as u8); 63 | tcp_query.push(query_len as u8); 64 | tcp_query.extend_from_slice(query); 65 | stream.write_all(&tcp_query).await?; 66 | let mut response_len_bytes = [0u8; 2]; 67 | stream.read_exact(&mut response_len_bytes).await?; 68 | let response_len = 69 | ((response_len_bytes[0] as usize) << 8) | (response_len_bytes[1] as usize); 70 | if response_len > DNS_MAX_COMPRESSED_SIZE { 71 | return Err(io::Error::new( 72 | io::ErrorKind::InvalidData, 73 | "Response too large", 74 | )); 75 | } 76 | let mut response = vec![0; response_len]; 77 | stream.read_exact(&mut response).await?; 78 | Ok(response) 79 | } 80 | .or(async { 81 | Timer::after(self.upstream_server_timeout).await; 82 | Err(io::Error::new(io::ErrorKind::TimedOut, "Request timed out")) 83 | }) 84 | .await 85 | } 86 | 87 | pub async fn join(&self, f1: F1, f2: F2) -> (F1::Output, F2::Output) { 88 | futures_lite::future::zip(f1, f2).await 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/backend/async_std.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::io; 3 | use std::net::SocketAddr; 4 | use std::time::Duration; 5 | 6 | use async_std::net::{TcpStream, UdpSocket}; 7 | use async_std::prelude::*; 8 | use dnssector::constants::DNS_MAX_COMPRESSED_SIZE; 9 | 10 | use crate::upstream_server::UpstreamServer; 11 | 12 | #[derive(Clone, Debug)] 13 | pub struct AsyncBackend { 14 | pub upstream_server_timeout: Duration, 15 | } 16 | 17 | impl AsyncBackend { 18 | pub fn new(upstream_server_timeout: Duration) -> Self { 19 | AsyncBackend { 20 | upstream_server_timeout, 21 | } 22 | } 23 | 24 | pub async fn dns_exchange_udp( 25 | &self, 26 | local_addr: &SocketAddr, 27 | upstream_server: &UpstreamServer, 28 | query: &[u8], 29 | ) -> io::Result> { 30 | async_std::io::timeout(self.upstream_server_timeout, async { 31 | let socket = UdpSocket::bind(local_addr).await?; 32 | socket.connect(upstream_server.addr).await?; 33 | socket.send(query).await?; 34 | let mut response = vec![0; DNS_MAX_COMPRESSED_SIZE]; 35 | let response_len = socket 36 | .recv(&mut response) 37 | .await 38 | .map_err(|_| io::Error::new(io::ErrorKind::WouldBlock, "Timeout"))?; 39 | response.truncate(response_len); 40 | Ok(response) 41 | }) 42 | .await 43 | } 44 | 45 | pub async fn dns_exchange_tcp( 46 | &self, 47 | _local_addr: &SocketAddr, 48 | upstream_server: &UpstreamServer, 49 | query: &[u8], 50 | ) -> io::Result> { 51 | async_std::io::timeout(self.upstream_server_timeout, async { 52 | let mut stream = TcpStream::connect(&upstream_server.addr).await?; 53 | let _ = stream.set_nodelay(true); 54 | let query_len = query.len(); 55 | let mut tcp_query = Vec::with_capacity(2 + query_len); 56 | tcp_query.push((query_len >> 8) as u8); 57 | tcp_query.push(query_len as u8); 58 | tcp_query.extend_from_slice(query); 59 | stream.write_all(&tcp_query).await?; 60 | let mut response_len_bytes = [0u8; 2]; 61 | stream.read_exact(&mut response_len_bytes).await?; 62 | let response_len = 63 | ((response_len_bytes[0] as usize) << 8) | (response_len_bytes[1] as usize); 64 | if response_len > DNS_MAX_COMPRESSED_SIZE { 65 | return Err(io::Error::new( 66 | io::ErrorKind::InvalidData, 67 | "Response too large", 68 | )); 69 | } 70 | let mut response = vec![0; response_len]; 71 | stream.read_exact(&mut response).await?; 72 | Ok(response) 73 | }) 74 | .await 75 | } 76 | 77 | pub async fn join(&self, f1: F1, f2: F2) -> (F1::Output, F2::Output) { 78 | f1.join(f2).await 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/backend/async_tokio.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::io; 3 | use std::net::SocketAddr; 4 | use std::time::Duration; 5 | 6 | use dnssector::constants::DNS_MAX_COMPRESSED_SIZE; 7 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 8 | use tokio::net::{TcpStream, UdpSocket}; 9 | 10 | use crate::upstream_server::UpstreamServer; 11 | 12 | #[derive(Clone, Debug)] 13 | pub struct AsyncBackend { 14 | pub upstream_server_timeout: Duration, 15 | } 16 | 17 | impl AsyncBackend { 18 | pub fn new(upstream_server_timeout: Duration) -> Self { 19 | AsyncBackend { 20 | upstream_server_timeout, 21 | } 22 | } 23 | 24 | pub async fn dns_exchange_udp( 25 | &self, 26 | local_addr: &SocketAddr, 27 | upstream_server: &UpstreamServer, 28 | query: &[u8], 29 | ) -> io::Result> { 30 | tokio::time::timeout(self.upstream_server_timeout, async { 31 | let socket = UdpSocket::bind(local_addr).await?; 32 | socket.connect(upstream_server.addr).await?; 33 | socket.send(query).await?; 34 | let mut response = vec![0; DNS_MAX_COMPRESSED_SIZE]; 35 | let response_len = socket 36 | .recv(&mut response) 37 | .await 38 | .map_err(|_| io::Error::new(io::ErrorKind::WouldBlock, "Timeout"))?; 39 | response.truncate(response_len); 40 | Ok(response) 41 | }) 42 | .await 43 | .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "Timeout"))? 44 | } 45 | 46 | pub async fn dns_exchange_tcp( 47 | &self, 48 | _local_addr: &SocketAddr, 49 | upstream_server: &UpstreamServer, 50 | query: &[u8], 51 | ) -> io::Result> { 52 | tokio::time::timeout(self.upstream_server_timeout, async { 53 | let mut stream = TcpStream::connect(&upstream_server.addr).await?; 54 | let _ = stream.set_nodelay(true); 55 | let query_len = query.len(); 56 | let mut tcp_query = Vec::with_capacity(2 + query_len); 57 | tcp_query.push((query_len >> 8) as u8); 58 | tcp_query.push(query_len as u8); 59 | tcp_query.extend_from_slice(query); 60 | stream.write_all(&tcp_query).await?; 61 | let mut response_len_bytes = [0u8; 2]; 62 | stream.read_exact(&mut response_len_bytes).await?; 63 | let response_len = 64 | ((response_len_bytes[0] as usize) << 8) | (response_len_bytes[1] as usize); 65 | if response_len > DNS_MAX_COMPRESSED_SIZE { 66 | return Err(io::Error::new( 67 | io::ErrorKind::InvalidData, 68 | "Response too large", 69 | )); 70 | } 71 | let mut response = vec![0; response_len]; 72 | stream.read_exact(&mut response).await?; 73 | Ok(response) 74 | }) 75 | .await 76 | .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "Timeout"))? 77 | } 78 | 79 | pub async fn join(&self, f1: F1, f2: F2) -> (F1::Output, F2::Output) { 80 | tokio::join!(f1, f2) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/backend/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "async-smol")] 2 | pub(crate) mod async_smol; 3 | 4 | #[cfg(all(feature = "async-tokio", not(feature = "async-smol")))] 5 | pub(crate) mod async_tokio; 6 | 7 | pub(crate) mod sync; 8 | -------------------------------------------------------------------------------- /src/backend/sync.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Read, Write}; 2 | use std::net::{SocketAddr, TcpStream, UdpSocket}; 3 | use std::time::Duration; 4 | 5 | use dnssector::constants::DNS_MAX_COMPRESSED_SIZE; 6 | 7 | use crate::upstream_server::UpstreamServer; 8 | 9 | #[derive(Clone, Debug)] 10 | pub struct SyncBackend { 11 | pub upstream_server_timeout: Duration, 12 | } 13 | 14 | impl SyncBackend { 15 | pub fn new(upstream_server_timeout: Duration) -> Self { 16 | SyncBackend { 17 | upstream_server_timeout, 18 | } 19 | } 20 | 21 | pub fn dns_exchange_udp( 22 | &self, 23 | local_addr: &SocketAddr, 24 | upstream_server: &UpstreamServer, 25 | query: &[u8], 26 | ) -> io::Result> { 27 | let socket = UdpSocket::bind(local_addr)?; 28 | let _ = socket.set_read_timeout(Some(self.upstream_server_timeout)); 29 | socket.connect(upstream_server.addr)?; 30 | socket.send(query)?; 31 | let mut response = vec![0; DNS_MAX_COMPRESSED_SIZE]; 32 | let response_len = socket 33 | .recv(&mut response) 34 | .map_err(|_| io::Error::new(io::ErrorKind::WouldBlock, "Timeout"))?; 35 | response.truncate(response_len); 36 | Ok(response) 37 | } 38 | 39 | pub fn dns_exchange_tcp( 40 | &self, 41 | _local_addr: &SocketAddr, 42 | upstream_server: &UpstreamServer, 43 | query: &[u8], 44 | ) -> io::Result> { 45 | let mut stream = 46 | TcpStream::connect_timeout(&upstream_server.addr, self.upstream_server_timeout)?; 47 | let _ = stream.set_read_timeout(Some(self.upstream_server_timeout)); 48 | let _ = stream.set_write_timeout(Some(self.upstream_server_timeout)); 49 | let _ = stream.set_nodelay(true); 50 | let query_len = query.len(); 51 | let mut tcp_query = Vec::with_capacity(2 + query_len); 52 | tcp_query.push((query_len >> 8) as u8); 53 | tcp_query.push(query_len as u8); 54 | tcp_query.extend_from_slice(query); 55 | stream.write_all(&tcp_query)?; 56 | let mut response_len_bytes = [0u8; 2]; 57 | stream.read_exact(&mut response_len_bytes)?; 58 | let response_len = 59 | ((response_len_bytes[0] as usize) << 8) | (response_len_bytes[1] as usize); 60 | if response_len > DNS_MAX_COMPRESSED_SIZE { 61 | return Err(io::Error::new( 62 | io::ErrorKind::InvalidData, 63 | "Response too large", 64 | )); 65 | } 66 | let mut response = vec![0; response_len]; 67 | stream.read_exact(&mut response)?; 68 | Ok(response) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | #[cfg(any(feature = "async-smol", feature = "async-tokio"))] 4 | pub mod r#async; 5 | mod backend; 6 | pub mod sync; 7 | 8 | pub mod system; 9 | mod upstream_server; 10 | 11 | pub use crate::upstream_server::*; 12 | 13 | // If async-smol is specified as a feature, it will be used even if tokio is available via default features 14 | // We avoid compiling modules that won't be used 15 | 16 | pub mod reexports { 17 | pub use dnssector; 18 | #[cfg(feature = "async-smol")] 19 | pub use futures_lite; 20 | pub use rand; 21 | #[cfg(feature = "async-smol")] 22 | pub use smol; 23 | #[cfg(all(feature = "async-tokio", not(feature = "async-smol")))] 24 | pub use tokio; 25 | } 26 | -------------------------------------------------------------------------------- /src/sync.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; 3 | use std::time::Duration; 4 | 5 | use dnssector::constants::{Class, Type}; 6 | use dnssector::*; 7 | use rand::{seq::SliceRandom, Rng}; 8 | 9 | use crate::backend::sync::SyncBackend; 10 | use crate::upstream_server::UpstreamServer; 11 | 12 | #[derive(Clone, Debug)] 13 | pub struct DNSClient { 14 | backend: SyncBackend, 15 | upstream_servers: Vec, 16 | local_v4_addr: SocketAddr, 17 | local_v6_addr: SocketAddr, 18 | force_tcp: bool, 19 | } 20 | 21 | impl DNSClient { 22 | pub fn new(upstream_servers: Vec) -> Self { 23 | DNSClient { 24 | backend: SyncBackend::new(Duration::new(6, 0)), 25 | upstream_servers, 26 | local_v4_addr: ([0; 4], 0).into(), 27 | local_v6_addr: ([0; 16], 0).into(), 28 | force_tcp: false, 29 | } 30 | } 31 | 32 | #[cfg(unix)] 33 | pub fn new_with_system_resolvers() -> Result { 34 | Ok(DNSClient::new(crate::system::default_resolvers()?)) 35 | } 36 | 37 | pub fn set_timeout(&mut self, timeout: Duration) { 38 | self.backend.upstream_server_timeout = timeout 39 | } 40 | 41 | pub fn set_local_v4_addr>(&mut self, addr: T) { 42 | self.local_v4_addr = addr.into() 43 | } 44 | 45 | pub fn set_local_v6_addr>(&mut self, addr: T) { 46 | self.local_v6_addr = addr.into() 47 | } 48 | 49 | pub fn force_tcp(&mut self, force_tcp: bool) { 50 | self.force_tcp = force_tcp; 51 | } 52 | 53 | fn send_query_to_upstream_server( 54 | &self, 55 | upstream_server: &UpstreamServer, 56 | query_tid: u16, 57 | query_question: &Option<(Vec, u16, u16)>, 58 | query: &[u8], 59 | ) -> Result { 60 | let local_addr = match upstream_server.addr { 61 | SocketAddr::V4(_) => &self.local_v4_addr, 62 | SocketAddr::V6(_) => &self.local_v6_addr, 63 | }; 64 | let response = if self.force_tcp { 65 | self.backend 66 | .dns_exchange_tcp(local_addr, upstream_server, query)? 67 | } else { 68 | self.backend 69 | .dns_exchange_udp(local_addr, upstream_server, query)? 70 | }; 71 | let mut parsed_response = DNSSector::new(response) 72 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))? 73 | .parse() 74 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 75 | if !self.force_tcp && parsed_response.flags() & DNS_FLAG_TC == DNS_FLAG_TC { 76 | parsed_response = { 77 | let response = self 78 | .backend 79 | .dns_exchange_tcp(local_addr, upstream_server, query)?; 80 | DNSSector::new(response) 81 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))? 82 | .parse() 83 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))? 84 | }; 85 | } 86 | if parsed_response.tid() != query_tid || &parsed_response.question() != query_question { 87 | return Err(io::Error::new( 88 | io::ErrorKind::PermissionDenied, 89 | "Unexpected response", 90 | )); 91 | } 92 | Ok(parsed_response) 93 | } 94 | 95 | fn query_from_parsed_query( 96 | &self, 97 | mut parsed_query: ParsedPacket, 98 | ) -> Result { 99 | let query_tid = parsed_query.tid(); 100 | let query_question = parsed_query.question(); 101 | if query_question.is_none() || parsed_query.flags() & DNS_FLAG_QR != 0 { 102 | return Err(io::Error::new( 103 | io::ErrorKind::InvalidInput, 104 | "No DNS question", 105 | )); 106 | } 107 | let valid_query = parsed_query.into_packet(); 108 | for upstream_server in &self.upstream_servers { 109 | if let Ok(parsed_response) = self.send_query_to_upstream_server( 110 | upstream_server, 111 | query_tid, 112 | &query_question, 113 | &valid_query, 114 | ) { 115 | return Ok(parsed_response); 116 | } 117 | } 118 | Err(io::Error::new( 119 | io::ErrorKind::InvalidInput, 120 | "No response received from any servers", 121 | )) 122 | } 123 | 124 | /// Send a raw query to the DNS server and return the response. 125 | pub fn query_raw(&self, query: &[u8], tid_masking: bool) -> Result, io::Error> { 126 | let mut parsed_query = DNSSector::new(query.to_vec()) 127 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))? 128 | .parse() 129 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 130 | let mut tid = 0; 131 | if tid_masking { 132 | tid = parsed_query.tid(); 133 | let mut rnd = rand::rng(); 134 | let masked_tid: u16 = rnd.random(); 135 | parsed_query.set_tid(masked_tid); 136 | } 137 | let mut parsed_response = self.query_from_parsed_query(parsed_query)?; 138 | if tid_masking { 139 | parsed_response.set_tid(tid); 140 | } 141 | let response = parsed_response.into_packet(); 142 | Ok(response) 143 | } 144 | 145 | /// Return IPv4 addresses. 146 | pub fn query_a(&self, name: &str) -> Result, io::Error> { 147 | let parsed_query = dnssector::gen::query( 148 | name.as_bytes(), 149 | Type::from_string("A").unwrap(), 150 | Class::from_string("IN").unwrap(), 151 | ) 152 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 153 | let mut parsed_response = self.query_from_parsed_query(parsed_query)?; 154 | let mut ips = vec![]; 155 | { 156 | let mut it = parsed_response.into_iter_answer(); 157 | while let Some(item) = it { 158 | if let Ok(IpAddr::V4(addr)) = item.rr_ip() { 159 | ips.push(addr); 160 | } 161 | it = item.next(); 162 | } 163 | } 164 | ips.shuffle(&mut rand::rng()); 165 | Ok(ips) 166 | } 167 | 168 | /// Return IPv6 addresses. 169 | pub fn query_aaaa(&self, name: &str) -> Result, io::Error> { 170 | let parsed_query = dnssector::gen::query( 171 | name.as_bytes(), 172 | Type::from_string("AAAA").unwrap(), 173 | Class::from_string("IN").unwrap(), 174 | ) 175 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 176 | let mut parsed_response = self.query_from_parsed_query(parsed_query)?; 177 | let mut ips = vec![]; 178 | { 179 | let mut it = parsed_response.into_iter_answer(); 180 | while let Some(item) = it { 181 | if let Ok(IpAddr::V6(addr)) = item.rr_ip() { 182 | ips.push(addr); 183 | } 184 | it = item.next(); 185 | } 186 | } 187 | ips.shuffle(&mut rand::rng()); 188 | Ok(ips) 189 | } 190 | 191 | /// Return both IPv4 and IPv6 addresses. 192 | pub fn query_addrs(&self, name: &str) -> Result, io::Error> { 193 | let ipv4_ips = self.query_a(name)?; 194 | let ipv6_ips = self.query_aaaa(name)?; 195 | let mut ips: Vec<_> = ipv4_ips 196 | .into_iter() 197 | .map(IpAddr::from) 198 | .chain(ipv6_ips.into_iter().map(IpAddr::from)) 199 | .collect(); 200 | ips.shuffle(&mut rand::rng()); 201 | Ok(ips) 202 | } 203 | 204 | /// Return TXT records. 205 | pub fn query_txt(&self, name: &str) -> Result>, io::Error> { 206 | let rr_class = Class::from_string("IN").unwrap(); 207 | let rr_type = Type::from_string("TXT").unwrap(); 208 | let parsed_query = dnssector::gen::query(name.as_bytes(), rr_type, rr_class) 209 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 210 | let mut parsed_response = self.query_from_parsed_query(parsed_query)?; 211 | let mut txts: Vec> = vec![]; 212 | 213 | let mut it = parsed_response.into_iter_answer(); 214 | while let Some(item) = it { 215 | if item.rr_class() != rr_class.into() || item.rr_type() != rr_type.into() { 216 | it = item.next(); 217 | continue; 218 | } 219 | if let Ok(RawRRData::Data(data)) = item.rr_rd() { 220 | let mut txt = vec![]; 221 | let mut it = data.iter(); 222 | while let Some(&len) = it.next() { 223 | for _ in 0..len { 224 | txt.push(*it.next().ok_or_else(|| { 225 | io::Error::new(io::ErrorKind::InvalidInput, "Invalid text record") 226 | })?) 227 | } 228 | } 229 | txts.push(txt); 230 | } 231 | it = item.next(); 232 | } 233 | Ok(txts) 234 | } 235 | 236 | /// Reverse IP lookup. 237 | pub fn query_ptr(&self, ip: &IpAddr) -> Result, io::Error> { 238 | let rr_class = Class::from_string("IN").unwrap(); 239 | let rr_type = Type::from_string("PTR").unwrap(); 240 | let rev_name = match ip { 241 | IpAddr::V4(ip) => { 242 | let mut octets = ip.octets(); 243 | octets.reverse(); 244 | format!( 245 | "{}.{}.{}.{}.in-addr.arpa", 246 | octets[0], octets[1], octets[2], octets[3] 247 | ) 248 | } 249 | IpAddr::V6(ip) => { 250 | let mut octets = ip.octets(); 251 | octets.reverse(); 252 | let rev = octets 253 | .iter() 254 | .map(|x| x.to_string()) 255 | .collect::>() 256 | .join("."); 257 | format!("{}.ip6.arpa", rev) 258 | } 259 | }; 260 | let parsed_query = dnssector::gen::query(rev_name.as_bytes(), rr_type, rr_class) 261 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 262 | let mut parsed_response = self.query_from_parsed_query(parsed_query)?; 263 | let mut names: Vec = vec![]; 264 | 265 | let mut it = parsed_response.into_iter_answer(); 266 | while let Some(item) = it { 267 | if item.rr_class() != rr_class.into() || item.rr_type() != rr_type.into() { 268 | it = item.next(); 269 | continue; 270 | } 271 | if let Ok(RawRRData::Data(data)) = item.rr_rd() { 272 | let mut name = vec![]; 273 | let mut it = data.iter(); 274 | while let Some(&len) = it.next() { 275 | if len != 0 && !name.is_empty() { 276 | name.push(b'.'); 277 | } 278 | for _ in 0..len { 279 | name.push(*it.next().ok_or_else(|| { 280 | io::Error::new(io::ErrorKind::InvalidInput, "Invalid text record") 281 | })?) 282 | } 283 | } 284 | if name.is_empty() { 285 | name.push(b'.'); 286 | } 287 | if let Ok(name) = String::from_utf8(name) { 288 | match ip { 289 | IpAddr::V4(ip) => { 290 | if self.query_a(&name)?.contains(ip) { 291 | names.push(name) 292 | } 293 | } 294 | IpAddr::V6(ip) => { 295 | if self.query_aaaa(&name)?.contains(ip) { 296 | names.push(name) 297 | } 298 | } 299 | }; 300 | } 301 | } 302 | it = item.next(); 303 | } 304 | Ok(names) 305 | } 306 | 307 | /// Return the raw record data for the given query type. 308 | pub fn query_rrs_data( 309 | &self, 310 | name: &str, 311 | query_class: &str, 312 | query_type: &str, 313 | ) -> Result>, io::Error> { 314 | let rr_class = Class::from_string(query_class) 315 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 316 | let rr_type = Type::from_string(query_type) 317 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 318 | let parsed_query = dnssector::gen::query(name.as_bytes(), rr_type, rr_class) 319 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?; 320 | let mut parsed_response = self.query_from_parsed_query(parsed_query)?; 321 | let mut raw_rrs = vec![]; 322 | 323 | let mut it = parsed_response.into_iter_answer(); 324 | while let Some(item) = it { 325 | if item.rr_class() != rr_class.into() || item.rr_type() != rr_type.into() { 326 | it = item.next(); 327 | continue; 328 | } 329 | if let Ok(RawRRData::Data(data)) = item.rr_rd() { 330 | raw_rrs.push(data.to_vec()); 331 | } 332 | it = item.next(); 333 | } 334 | Ok(raw_rrs) 335 | } 336 | } 337 | 338 | #[test] 339 | fn test_query_a() { 340 | use std::str::FromStr; 341 | 342 | let upstream_servers = crate::system::default_resolvers().unwrap_or_else(|_| { 343 | vec![ 344 | UpstreamServer::new(SocketAddr::from_str("1.0.0.1:53").unwrap()), 345 | UpstreamServer::new(SocketAddr::from_str("1.1.1.1:53").unwrap()), 346 | ] 347 | }); 348 | let dns_client = DNSClient::new(upstream_servers); 349 | let r = dns_client.query_a("one.one.one.one").unwrap(); 350 | assert!(r.contains(&Ipv4Addr::new(1, 1, 1, 1))); 351 | } 352 | 353 | #[test] 354 | fn test_query_ptr() { 355 | use std::str::FromStr; 356 | 357 | let upstream_servers = crate::system::default_resolvers().unwrap_or_else(|_| { 358 | vec![ 359 | UpstreamServer::new(SocketAddr::from_str("1.0.0.1:53").unwrap()), 360 | UpstreamServer::new(SocketAddr::from_str("1.1.1.1:53").unwrap()), 361 | ] 362 | }); 363 | let dns_client = DNSClient::new(upstream_servers); 364 | let r = dns_client 365 | .query_ptr(&IpAddr::from_str("1.1.1.1").unwrap()) 366 | .unwrap(); 367 | assert_eq!(r[0], "one.one.one.one"); 368 | } 369 | -------------------------------------------------------------------------------- /src/system.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io; 3 | use std::net::{IpAddr, SocketAddr}; 4 | 5 | use crate::UpstreamServer; 6 | 7 | /// Return the set of default (system) resolvers, by parsing /etc/resolv.conf 8 | #[cfg(unix)] 9 | pub fn default_resolvers() -> Result, io::Error> { 10 | let data = fs::read_to_string("/etc/resolv.conf")?; 11 | let mut upstream_servers = vec![]; 12 | for line in data.lines() { 13 | let line = line.trim(); 14 | if !line.starts_with("nameserver") { 15 | continue; 16 | } 17 | let mut it = line.split_whitespace(); 18 | if it.next().is_none() { 19 | continue; 20 | } 21 | if let Some(addr) = it.next() { 22 | let ip = match addr.parse::() { 23 | Ok(ip) => ip, 24 | _ => continue, 25 | }; 26 | let addr = SocketAddr::new(ip, 53); 27 | let upstream_server = UpstreamServer::new(addr); 28 | upstream_servers.push(upstream_server); 29 | } 30 | } 31 | if upstream_servers.is_empty() { 32 | return Err(io::Error::new( 33 | io::ErrorKind::NotFound, 34 | "No upstream servers found", 35 | )); 36 | } 37 | Ok(upstream_servers) 38 | } 39 | 40 | #[cfg(not(unix))] 41 | pub fn default_resolvers() -> Result, io::Error> { 42 | Err(io::Error::new( 43 | io::ErrorKind::NotFound, 44 | "System resolvers are not supported by the software on this platform", 45 | )) 46 | } 47 | -------------------------------------------------------------------------------- /src/upstream_server.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | #[derive(Clone, Debug)] 4 | pub struct UpstreamServer { 5 | pub addr: SocketAddr, 6 | } 7 | 8 | impl UpstreamServer { 9 | pub fn new>(addr: T) -> Self { 10 | UpstreamServer { addr: addr.into() } 11 | } 12 | } 13 | --------------------------------------------------------------------------------