#![forbid(unsafe_code, elided_lifetimes_in_paths)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(not(feature = "max-security"))]
mod cache;
pub mod cert;
pub mod crypto;
pub mod error;
pub mod net;
pub mod packet;
#[cfg(feature = "reqwest")]
pub mod reqwest_resolver;
pub mod resolver;
mod rng;
pub use error::Error;
#[cfg(feature = "reqwest")]
pub use reqwest_resolver::DnscryptResolver;
pub use resolver::{
DnscryptClientState, HARDCODED_RESOLVERS, HardcodedResolver, establish_dnscrypt_session,
resolve_domain_via_dnscrypt_session,
};
#[cfg(feature = "tokio")]
pub use resolver::{
establish_dnscrypt_session_async, resolve_async, resolve_domain_via_dnscrypt_session_async,
};
use std::net::IpAddr;
pub fn resolve(
resolvers: &'static [HardcodedResolver],
domain: &str,
) -> Result<Vec<IpAddr>, Error> {
let sessions = establish_dnscrypt_session(resolvers)?;
#[cfg(feature = "max-security")]
{
let mut results = std::collections::HashMap::new();
for session in &sessions {
if let Ok(ips) = resolve_domain_via_dnscrypt_session(session, domain) {
for ip in ips {
*results.entry(ip).or_insert(0) += 1;
}
}
}
if results.is_empty() {
return Err(Error::Protocol(
"max-security: all providers failed to resolve domain".into(),
));
}
Ok(resolver::majority_ips(results))
}
#[cfg(not(feature = "max-security"))]
{
resolver::resolve_with_cache(&sessions, domain)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use crate::crypto::{xchacha20_djb_poly1305_decrypt, xchacha20_djb_poly1305_encrypt};
use crate::packet::{build_a_record_query, pad_query, parse_dns_response, unpad_response};
use std::net::{IpAddr, Ipv4Addr};
#[test]
fn test_build_a_record_query() {
let query = build_a_record_query("github.com", [0xab, 0xcd]);
assert_eq!(
&query[0..12],
&[
0xab, 0xcd, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
);
assert_eq!(query[12], 6);
assert_eq!(&query[13..19], b"github");
assert_eq!(query[19], 3);
assert_eq!(&query[20..23], b"com");
assert_eq!(query[23], 0);
assert_eq!(&query[24..28], &[0x00, 0x01, 0x00, 0x01]);
}
#[test]
fn test_parse_dns_response_valid() {
let mut response = Vec::new();
response.extend_from_slice(&[
0xab, 0xcd, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
]);
response.extend_from_slice(&[6]);
response.extend_from_slice(b"github");
response.extend_from_slice(&[3]);
response.extend_from_slice(b"com");
response.extend_from_slice(&[0]);
response.extend_from_slice(&[0x00, 0x01, 0x00, 0x01]);
response.extend_from_slice(&[0xc0, 0x0c]);
response.extend_from_slice(&[0x00, 0x01, 0x00, 0x01]);
response.extend_from_slice(&[0x00, 0x00, 0x00, 0x3c]);
response.extend_from_slice(&[0x00, 0x04]);
response.extend_from_slice(&[140, 82, 121, 4]);
let parsed = parse_dns_response(&response);
assert_eq!(parsed[0], IpAddr::V4(Ipv4Addr::new(140, 82, 121, 4)));
}
#[test]
fn test_parse_dns_response_truncated() {
assert!(parse_dns_response(&[]).is_empty());
assert!(parse_dns_response(&[0; 10]).is_empty());
let mut response = Vec::new();
response.extend_from_slice(&[
0xab, 0xcd, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
]);
response.extend_from_slice(&[6]);
response.extend_from_slice(b"github");
response.extend_from_slice(&[3]);
response.extend_from_slice(b"com");
response.extend_from_slice(&[0]);
response.extend_from_slice(&[0x00, 0x01, 0x00, 0x01]);
response.extend_from_slice(&[0xc0, 0x0c]);
assert!(parse_dns_response(&response).is_empty());
}
#[test]
fn test_pad_query() {
let query = vec![1, 2, 3];
let padded = pad_query(&query, 64);
assert!(padded.len() >= 64 && padded.len().is_multiple_of(64));
assert_eq!(padded[0..3], [1, 2, 3]);
assert_eq!(padded[3], 0x80);
assert!(padded[4..].iter().all(|&b| b == 0));
let query2 = vec![5; 64];
let padded2 = pad_query(&query2, 64);
assert!(padded2.len() >= 128 && padded2.len().is_multiple_of(64));
assert_eq!(padded2[64], 0x80);
}
#[test]
fn test_unpad_response() {
let valid = vec![1, 2, 3, 0x80, 0, 0, 0];
assert_eq!(unpad_response(&valid).unwrap(), vec![1, 2, 3]);
assert!(unpad_response(&[1, 2, 3, 0, 0, 0]).is_err());
assert!(unpad_response(&[]).is_err());
}
#[test]
fn test_xchacha20_djb_poly1305_roundtrip() {
let key = [9u8; 32];
let nonce = [12u8; 24];
let msg = b"Hello, DNSCrypt!";
let encrypted = xchacha20_djb_poly1305_encrypt(&key, &nonce, msg);
assert_eq!(encrypted.len(), msg.len() + 16);
let decrypted = xchacha20_djb_poly1305_decrypt(&key, &nonce, &encrypted).unwrap();
assert_eq!(decrypted, msg);
}
#[test]
fn test_xchacha20_djb_poly1305_tamper_detection() {
let key = [9u8; 32];
let nonce = [12u8; 24];
let msg = b"Hello, DNSCrypt!";
let mut encrypted = xchacha20_djb_poly1305_encrypt(&key, &nonce, msg);
encrypted[16] ^= 0xff;
assert!(xchacha20_djb_poly1305_decrypt(&key, &nonce, &encrypted).is_err());
}
}