#![allow(dead_code)]
#![allow(missing_debug_implementations)]
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
use bytes::{BufMut, Bytes, BytesMut};
use parking_lot::{Mutex, RwLock};
use rand::Rng;
use thiserror::Error;
use tokio::sync::{broadcast, Semaphore};
use tokio::time::{sleep, timeout};
pub const DNS_HEADER_LEN: usize = 12;
pub const DNS_MAX_UDP: usize = 1232;
pub const DNS_MAX_NAME: usize = 255;
pub const DNS_MAX_LABEL: usize = 63;
pub const DNS_MAX_CNAME_DEPTH: usize = 16;
pub const DNS_MAX_COMPRESSION_HOPS: usize = 128;
pub const CLASS_IN: u16 = 1;
pub const TYPE_A: u16 = 1;
pub const TYPE_NS: u16 = 2;
pub const TYPE_CNAME: u16 = 5;
pub const TYPE_SOA: u16 = 6;
pub const TYPE_PTR: u16 = 12;
pub const TYPE_MX: u16 = 15;
pub const TYPE_TXT: u16 = 16;
pub const TYPE_AAAA: u16 = 28;
pub const TYPE_SRV: u16 = 33;
pub const TYPE_DNAME: u16 = 39;
pub const TYPE_OPT: u16 = 41;
pub const TYPE_DS: u16 = 43;
pub const TYPE_RRSIG: u16 = 46;
pub const TYPE_NSEC: u16 = 47;
pub const TYPE_DNSKEY: u16 = 48;
pub const TYPE_NSEC3: u16 = 50;
pub const TYPE_NSEC3PARAM: u16 = 51;
pub const TYPE_TLSA: u16 = 52;
pub const TYPE_SVCB: u16 = 64;
pub const TYPE_HTTPS: u16 = 65;
pub const TYPE_ANY: u16 = 255;
pub const RCODE_NOERROR: u8 = 0;
pub const RCODE_FORMERR: u8 = 1;
pub const RCODE_SERVFAIL: u8 = 2;
pub const RCODE_NXDOMAIN: u8 = 3;
pub const RCODE_NOTIMP: u8 = 4;
pub const RCODE_REFUSED: u8 = 5;
pub const RCODE_YXDOMAIN: u8 = 6;
pub const RCODE_BADVERS: u8 = 16;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum DnssecMode {
No = 0,
AllowDowngrade = 1,
Yes = 2,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum DnssecState {
Secure,
Insecure,
Bogus,
Indeterminate,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TransportKind {
Udp,
Tcp,
Tls, Https, }
#[derive(Debug, Error)]
pub enum HyperError {
#[error("wire parse: {0}")]
Wire(String),
#[error("name error: {0}")]
Name(String),
#[error("timeout after {0:?}")]
Timeout(Duration),
#[error("all upstreams failed")]
AllUpstreamsFailed,
#[error("policy denied")]
PolicyDenied,
#[error("DNSSEC bogus")]
DnssecBogus,
#[error("CNAME loop")]
CnameLoop,
#[error("CNAME depth exceeded")]
CnameDepth,
#[error("arena exhausted")]
ArenaExhausted,
#[error("transaction mismatch")]
TxidMismatch,
#[error("response from unexpected peer")]
PeerMismatch,
#[error("cancelled")]
Cancelled,
#[error("internal: {0}")]
Internal(String),
}
pub type HResult<T> = Result<T, HyperError>;
pub struct WireArena {
slabs: Vec<Mutex<BytesMut>>,
slab_size: usize,
current: AtomicUsize,
epoch: AtomicU64,
retired: Mutex<HashMap<u64, Arc<ArenaEpoch>>>,
}
pub struct ArenaEpoch {
pub id: u64,
slabs: Vec<Bytes>,
live: AtomicUsize,
}
pub struct ArenaBytes {
epoch: Arc<ArenaEpoch>,
bytes: Bytes,
}
impl Clone for ArenaBytes {
fn clone(&self) -> Self {
self.epoch.live.fetch_add(1, Ordering::Relaxed);
Self {
epoch: Arc::clone(&self.epoch),
bytes: self.bytes.clone(),
}
}
}
impl Drop for ArenaBytes {
fn drop(&mut self) {
self.epoch.live.fetch_sub(1, Ordering::Release);
}
}
impl std::ops::Deref for ArenaBytes {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.bytes
}
}
impl WireArena {
pub fn new(num_slabs: usize, slab_size: usize) -> Self {
let mut slabs = Vec::with_capacity(num_slabs);
for _ in 0..num_slabs {
slabs.push(Mutex::new(BytesMut::with_capacity(slab_size)));
}
Self {
slabs,
slab_size,
current: AtomicUsize::new(0),
epoch: AtomicU64::new(1),
retired: Mutex::new(HashMap::new()),
}
}
pub fn alloc(&self, len: usize) -> HResult<ArenaBytes> {
if len > self.slab_size {
return Err(HyperError::ArenaExhausted);
}
let n = self.slabs.len();
for attempt in 0..n {
let idx = (self.current.load(Ordering::Relaxed) + attempt) % n;
let mut slab = self.slabs[idx].lock();
if slab.capacity() - slab.len() < len {
if slab.len() + len > self.slab_size {
continue;
}
}
if slab.capacity() < len {
continue;
}
if slab.capacity() - slab.len() < len {
continue;
}
let start = slab.len();
slab.resize(start + len, 0);
let frozen = slab.split_to(start + len).freeze();
let _ = frozen;
drop(slab);
return self.alloc_fresh(idx, len);
}
self.advance_epoch();
self.alloc_fresh(0, len)
}
fn alloc_fresh(&self, idx: usize, len: usize) -> HResult<ArenaBytes> {
let mut slab = self.slabs[idx].lock();
if slab.capacity() - slab.len() < len {
slab.clear();
if slab.capacity() < len {
*slab = BytesMut::with_capacity(self.slab_size.max(len));
}
}
let slab_len = slab.len();
let _chunk = slab.split_off(slab_len);
drop(slab);
let mut buf = BytesMut::with_capacity(len);
buf.resize(len, 0);
let bytes = buf.freeze();
let epoch_id = self.epoch.load(Ordering::Acquire);
let epoch = {
let mut ret = self.retired.lock();
ret.entry(epoch_id)
.or_insert_with(|| {
Arc::new(ArenaEpoch {
id: epoch_id,
slabs: Vec::new(),
live: AtomicUsize::new(0),
})
})
.clone()
};
epoch.live.fetch_add(1, Ordering::Relaxed);
Ok(ArenaBytes { epoch, bytes })
}
pub fn advance_epoch(&self) {
let old = self.epoch.fetch_add(1, Ordering::AcqRel);
let mut ret = self.retired.lock();
ret.retain(|id, ep| *id == old + 1 || ep.live.load(Ordering::Acquire) > 0);
self.current.fetch_add(1, Ordering::Relaxed);
}
pub fn copy_from(&self, src: &[u8]) -> HResult<ArenaBytes> {
let ab = self.alloc(src.len())?;
let mut bm = BytesMut::with_capacity(src.len());
bm.extend_from_slice(src);
let bytes = bm.freeze();
ab.epoch.live.fetch_add(1, Ordering::Relaxed);
Ok(ArenaBytes {
epoch: ab.epoch.clone(),
bytes,
})
}
}
#[derive(Clone, Eq)]
pub struct NameKey {
wire: Bytes, }
impl PartialEq for NameKey {
fn eq(&self, other: &Self) -> bool {
self.wire == other.wire
}
}
impl std::hash::Hash for NameKey {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u64(name_hash64(&self.wire));
}
}
impl std::fmt::Debug for NameKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "NameKey({})", name_to_presentation(&self.wire))
}
}
impl NameKey {
pub fn from_wire_uncompressed(wire: &[u8]) -> HResult<Self> {
validate_uncompressed(wire)?;
let mut out = BytesMut::with_capacity(wire.len());
let mut i = 0;
while i < wire.len() {
let l = wire[i] as usize;
out.put_u8(wire[i]);
if l == 0 {
break;
}
if l > DNS_MAX_LABEL || i + 1 + l > wire.len() {
return Err(HyperError::Name("bad label".into()));
}
for j in 0..l {
let b = wire[i + 1 + j];
out.put_u8(if b.is_ascii_uppercase() { b + 32 } else { b });
}
i += 1 + l;
}
Ok(Self { wire: out.freeze() })
}
pub fn from_labels(labels: &[&[u8]]) -> HResult<Self> {
let mut out = BytesMut::with_capacity(64);
let mut total = 1usize;
for lab in labels {
if lab.is_empty() || lab.len() > DNS_MAX_LABEL {
return Err(HyperError::Name("bad label".into()));
}
total += 1 + lab.len();
if total > DNS_MAX_NAME {
return Err(HyperError::Name("too long".into()));
}
out.put_u8(lab.len() as u8);
for &b in *lab {
out.put_u8(if b.is_ascii_uppercase() { b + 32 } else { b });
}
}
out.put_u8(0);
Ok(Self { wire: out.freeze() })
}
pub fn wire(&self) -> &[u8] {
&self.wire
}
pub fn is_root(&self) -> bool {
self.wire.len() == 1 && self.wire[0] == 0
}
pub fn parent(&self) -> Option<NameKey> {
if self.is_root() {
return None;
}
let l = self.wire[0] as usize;
let rest = &self.wire[1 + l..];
Some(NameKey {
wire: Bytes::copy_from_slice(rest),
})
}
}
#[inline]
pub fn name_hash64(wire: &[u8]) -> u64 {
const OFF: u64 = 0xcbf29ce484222325;
const P: u64 = 0x100000001b3;
let mut h = OFF;
for &b in wire {
h ^= u64::from(b);
h = h.wrapping_mul(P);
}
h ^= h >> 33;
h = h.wrapping_mul(0xff51afd7ed558ccd);
h ^= h >> 33;
h = h.wrapping_mul(0xc4ceb9fe1a85ec53);
h ^= h >> 33;
h
}
fn validate_uncompressed(wire: &[u8]) -> HResult<()> {
if wire.is_empty() || wire.len() > DNS_MAX_NAME {
return Err(HyperError::Name("length".into()));
}
let mut i = 0usize;
let mut labels = 0usize;
loop {
if i >= wire.len() {
return Err(HyperError::Name("truncated".into()));
}
let l = wire[i] as usize;
if l == 0 {
if i + 1 != wire.len() {
return Err(HyperError::Name("trailing".into()));
}
return Ok(());
}
if l > DNS_MAX_LABEL || (l & 0xC0) != 0 {
return Err(HyperError::Name("label".into()));
}
i += 1 + l;
labels += 1;
if labels > 128 {
return Err(HyperError::Name("too many labels".into()));
}
}
}
pub fn name_to_presentation(wire: &[u8]) -> String {
let mut s = String::new();
let mut i = 0usize;
while i < wire.len() {
let l = wire[i] as usize;
if l == 0 {
if s.is_empty() {
return ".".into();
}
break;
}
if !s.is_empty() {
s.push('.');
}
if i + 1 + l > wire.len() {
s.push_str("???");
break;
}
for &b in &wire[i + 1..i + 1 + l] {
s.push(b as char);
}
i += 1 + l;
}
s
}
#[derive(Clone)]
pub struct PacketView {
raw: ArenaBytes,
}
impl PacketView {
pub fn new(raw: ArenaBytes) -> HResult<Self> {
if raw.len() < DNS_HEADER_LEN {
return Err(HyperError::Wire("short header".into()));
}
Ok(Self { raw })
}
pub fn bytes(&self) -> &[u8] {
&self.raw
}
#[inline]
pub fn id(&self) -> u16 {
u16::from_be_bytes([self.raw[0], self.raw[1]])
}
#[inline]
pub fn flags(&self) -> u16 {
u16::from_be_bytes([self.raw[2], self.raw[3]])
}
#[inline]
pub fn qr(&self) -> bool {
self.flags() & 0x8000 != 0
}
#[inline]
pub fn opcode(&self) -> u8 {
((self.flags() >> 11) & 0xF) as u8
}
#[inline]
pub fn aa(&self) -> bool {
self.flags() & 0x0400 != 0
}
#[inline]
pub fn tc(&self) -> bool {
self.flags() & 0x0200 != 0
}
#[inline]
pub fn rd(&self) -> bool {
self.flags() & 0x0100 != 0
}
#[inline]
pub fn ra(&self) -> bool {
self.flags() & 0x0080 != 0
}
#[inline]
pub fn ad(&self) -> bool {
self.flags() & 0x0020 != 0
}
#[inline]
pub fn cd(&self) -> bool {
self.flags() & 0x0010 != 0
}
#[inline]
pub fn rcode(&self) -> u8 {
(self.flags() & 0x000F) as u8
}
#[inline]
pub fn qdcount(&self) -> u16 {
u16::from_be_bytes([self.raw[4], self.raw[5]])
}
#[inline]
pub fn ancount(&self) -> u16 {
u16::from_be_bytes([self.raw[6], self.raw[7]])
}
#[inline]
pub fn nscount(&self) -> u16 {
u16::from_be_bytes([self.raw[8], self.raw[9]])
}
#[inline]
pub fn arcount(&self) -> u16 {
u16::from_be_bytes([self.raw[10], self.raw[11]])
}
pub fn question(&self) -> HResult<(NameKey, u16, u16)> {
if self.qdcount() == 0 {
return Err(HyperError::Wire("no question".into()));
}
let mut off = DNS_HEADER_LEN;
let (name, next) = decompress_name(&self.raw, off)?;
off = next;
if off + 4 > self.raw.len() {
return Err(HyperError::Wire("short question".into()));
}
let qtype = u16::from_be_bytes([self.raw[off], self.raw[off + 1]]);
let qclass = u16::from_be_bytes([self.raw[off + 2], self.raw[off + 3]]);
Ok((name, qtype, qclass))
}
}
pub fn decompress_name(msg: &[u8], off: usize) -> HResult<(NameKey, usize)> {
let mut out = BytesMut::with_capacity(64);
let mut o = off;
let mut hops = 0usize;
let mut jumped = false;
let mut return_off = 0usize;
let mut seen = [0u64; 1024];
loop {
if o >= msg.len() {
return Err(HyperError::Wire("name oob".into()));
}
if hops > DNS_MAX_COMPRESSION_HOPS {
return Err(HyperError::Wire("name hops".into()));
}
hops += 1;
if o < 65536 {
let idx = o >> 6;
let bit = 1u64 << (o & 63);
if seen[idx] & bit != 0 {
return Err(HyperError::Wire("name cycle".into()));
}
seen[idx] |= bit;
}
let lab = msg[o];
if lab == 0 {
out.put_u8(0);
if out.len() > DNS_MAX_NAME {
return Err(HyperError::Wire("name too long".into()));
}
let next = if jumped { return_off } else { o + 1 };
let key = NameKey { wire: out.freeze() };
return Ok((key, next));
}
if lab & 0xC0 == 0xC0 {
if o + 1 >= msg.len() {
return Err(HyperError::Wire("ptr oob".into()));
}
let ptr = (((lab as usize) & 0x3F) << 8) | (msg[o + 1] as usize);
if ptr >= msg.len() {
return Err(HyperError::Wire("ptr target".into()));
}
if !jumped {
return_off = o + 2;
jumped = true;
}
o = ptr;
continue;
}
if lab & 0xC0 != 0 {
return Err(HyperError::Wire("bad label bits".into()));
}
let l = lab as usize;
if l > DNS_MAX_LABEL || o + 1 + l > msg.len() {
return Err(HyperError::Wire("label".into()));
}
if out.len() + 1 + l + 1 > DNS_MAX_NAME {
return Err(HyperError::Wire("name too long".into()));
}
out.put_u8(lab);
for j in 0..l {
let b = msg[o + 1 + j];
out.put_u8(if b.is_ascii_uppercase() { b + 32 } else { b });
}
o += 1 + l;
}
}
pub struct QueryBuilder {
buf: BytesMut,
}
impl QueryBuilder {
pub fn new(id: u16, name: &NameKey, qtype: u16, qclass: u16) -> Self {
let mut buf = BytesMut::with_capacity(512);
buf.put_u16(id);
buf.put_u16(0x0100);
buf.put_u16(1); buf.put_u16(0);
buf.put_u16(0);
buf.put_u16(1); buf.extend_from_slice(name.wire());
buf.put_u16(qtype);
buf.put_u16(qclass);
buf.put_u8(0);
buf.put_u16(TYPE_OPT);
buf.put_u16(DNS_MAX_UDP as u16);
buf.put_u32(0); let opt_ttl_pos = buf.len() - 4;
let do_flags: u32 = 0x0000_8000; buf[opt_ttl_pos] = (do_flags >> 24) as u8;
buf[opt_ttl_pos + 1] = (do_flags >> 16) as u8;
buf[opt_ttl_pos + 2] = (do_flags >> 8) as u8;
buf[opt_ttl_pos + 3] = do_flags as u8;
buf.put_u16(0); Self { buf }
}
pub fn set_cd(&mut self, cd: bool) {
if self.buf.len() >= 4 {
if cd {
self.buf[3] |= 0x10;
} else {
self.buf[3] &= !0x10;
}
}
}
pub fn set_dnssec_ok(&mut self, on: bool) {
if self.buf.len() < 12 {
return;
}
let _ = on;
}
pub fn finish(self) -> Bytes {
self.buf.freeze()
}
}
#[derive(Clone, Debug)]
pub struct RrMeta {
pub rcode: u8,
pub dnssec: DnssecState,
pub answer: Bytes, pub expires: Instant,
pub stale_until: Instant,
pub min_ttl: u32,
pub from_link: i32,
}
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
pub struct CacheKey {
pub name: NameKey,
pub qtype: u16,
pub qclass: u16,
pub cd: bool, }
struct CacheShard {
map: RwLock<HashMap<CacheKey, RrMeta>>,
}
pub struct HierarchicalCache {
shards: Vec<CacheShard>,
mask: u64,
max_per_shard: usize,
stale: Duration,
hits: AtomicU64,
misses: AtomicU64,
stale_hits: AtomicU64,
}
impl HierarchicalCache {
pub fn new(bits: u32, max_per_shard: usize, stale: Duration) -> Self {
let n = 1usize << bits;
Self {
shards: (0..n)
.map(|_| CacheShard {
map: RwLock::new(HashMap::with_capacity(256)),
})
.collect(),
mask: (n as u64) - 1,
max_per_shard,
stale,
hits: AtomicU64::new(0),
misses: AtomicU64::new(0),
stale_hits: AtomicU64::new(0),
}
}
fn idx(&self, k: &CacheKey) -> usize {
let h = name_hash64(k.name.wire())
^ (u64::from(k.qtype) << 17)
^ (u64::from(k.qclass) << 3)
^ u64::from(k.cd).wrapping_mul(0x9E3779B97F4A7C15);
(h & self.mask) as usize
}
pub fn get(&self, k: &CacheKey, now: Instant) -> Option<(RrMeta, bool /*stale*/)> {
let s = &self.shards[self.idx(k)];
let g = s.map.read();
let e = g.get(k)?;
if now < e.expires {
self.hits.fetch_add(1, Ordering::Relaxed);
Some((e.clone(), false))
} else if now < e.stale_until {
self.stale_hits.fetch_add(1, Ordering::Relaxed);
Some((e.clone(), true))
} else {
self.misses.fetch_add(1, Ordering::Relaxed);
None
}
}
pub fn put(&self, k: CacheKey, mut meta: RrMeta) {
let s = &self.shards[self.idx(&k)];
let mut g = s.map.write();
if let Some(old) = g.get(&k) {
if old.dnssec == DnssecState::Secure
&& meta.dnssec != DnssecState::Secure
&& Instant::now() < old.expires
{
return;
}
}
if g.len() >= self.max_per_shard {
let now = Instant::now();
g.retain(|_, v| now < v.stale_until);
if g.len() >= self.max_per_shard {
let keys: Vec<_> = g.keys().take(g.len() / 8 + 1).cloned().collect();
for key in keys {
g.remove(&key);
}
}
}
if meta.stale_until <= meta.expires {
meta.stale_until = meta.expires + self.stale;
}
g.insert(k, meta);
}
}
struct Flight {
tx: broadcast::Sender<Result<RrMeta, ()>>,
}
pub struct Singleflight {
inner: Mutex<HashMap<CacheKey, Arc<Flight>>>,
coalesced: AtomicU64,
}
impl Default for Singleflight {
fn default() -> Self {
Self::new()
}
}
impl Singleflight {
pub fn new() -> Self {
Self {
inner: Mutex::new(HashMap::new()),
coalesced: AtomicU64::new(0),
}
}
pub async fn join_or_lead(&self, key: &CacheKey) -> LeadOrFollow {
let mut g = self.inner.lock();
if let Some(f) = g.get(key) {
self.coalesced.fetch_add(1, Ordering::Relaxed);
LeadOrFollow::Follow(f.tx.subscribe())
} else {
let (tx, _rx) = broadcast::channel(32);
g.insert(key.clone(), Arc::new(Flight { tx: tx.clone() }));
LeadOrFollow::Lead(tx)
}
}
pub fn finish(&self, key: &CacheKey) {
self.inner.lock().remove(key);
}
}
pub enum LeadOrFollow {
Lead(broadcast::Sender<Result<RrMeta, ()>>),
Follow(broadcast::Receiver<Result<RrMeta, ()>>),
}
#[derive(Clone, Debug)]
pub struct Upstream {
pub id: u32,
pub addr: SocketAddr,
pub transport: TransportKind,
pub link_ifindex: i32,
pub dnssec_capable: bool,
pub sni: Option<String>, pub doh_url: Option<String>,
}
#[derive(Clone, Debug)]
pub struct UpstreamScore {
pub upstream_id: u32,
pub score_ms: f64,
pub reachable: bool,
}
#[async_trait::async_trait]
pub trait Transport: Send + Sync {
async fn exchange(
&self,
up: &Upstream,
query: Bytes,
timeout: Duration,
) -> HResult<(Bytes, Duration)>;
}
pub struct SpeculativePool {
pub k: usize,
pub per_try: Duration,
pub overall: Duration,
pub stagger: Duration, }
impl Default for SpeculativePool {
fn default() -> Self {
Self {
k: 3,
per_try: Duration::from_millis(800),
overall: Duration::from_secs(5),
stagger: Duration::from_millis(50),
}
}
}
struct SendPtr<T: ?Sized>(*const T);
unsafe impl<T: ?Sized> Send for SendPtr<T> {}
unsafe impl<T: ?Sized> Sync for SendPtr<T> {}
impl SpeculativePool {
pub async fn race(
&self,
transport: &dyn Transport,
upstreams: &[Upstream],
scores: &[UpstreamScore],
query_template: &Bytes, validate: impl Fn(&[u8], &Upstream) -> HResult<()> + Send + Sync,
) -> HResult<(PacketView, Upstream, Duration)> {
let mut ranked: Vec<&Upstream> = scores
.iter()
.filter(|s| s.reachable)
.filter_map(|s| upstreams.iter().find(|u| u.id == s.upstream_id))
.take(self.k)
.collect();
if ranked.is_empty() {
ranked = upstreams.iter().take(self.k).collect();
}
if ranked.is_empty() {
return Err(HyperError::AllUpstreamsFailed);
}
let (tx, mut rx) =
tokio::sync::mpsc::channel::<HResult<(PacketView, Upstream, Duration)>>(1);
let cancel = tokio_util::sync::CancellationToken::new();
let overall = self.overall;
let per_try = self.per_try;
let stagger = self.stagger;
for (i, up) in ranked.into_iter().enumerate() {
let up = up.clone();
let tx = tx.clone();
let child = cancel.child_token();
let qbase = query_template.clone();
let transport_ptr_raw: (usize, usize) =
unsafe { std::mem::transmute(transport as *const dyn Transport) };
let validate_dyn: &(dyn Fn(&[u8], &Upstream) -> HResult<()> + Send + Sync) = &validate;
let validate_ptr_raw: (usize, usize) =
unsafe { std::mem::transmute(validate_dyn as *const _) };
tokio::spawn(async move {
if i > 0 {
tokio::select! {
() = sleep(stagger * i as u32) => {}
() = child.cancelled() => return,
}
}
if child.is_cancelled() {
return;
}
let id: u16 = rand::thread_rng().gen();
let mut q = BytesMut::from(qbase.as_ref());
if q.len() >= 2 {
q[0] = (id >> 8) as u8;
q[1] = id as u8;
}
let q = q.freeze();
let transport: &dyn Transport = unsafe {
&*std::mem::transmute::<(usize, usize), *const dyn Transport>(transport_ptr_raw)
};
let validate: &(dyn Fn(&[u8], &Upstream) -> HResult<()> + Send + Sync) = unsafe {
&*std::mem::transmute::<
(usize, usize),
*const (dyn Fn(&[u8], &Upstream) -> HResult<()> + Send + Sync),
>(validate_ptr_raw)
};
let started = Instant::now();
let res = tokio::select! {
r = transport.exchange(&up, q, per_try) => r,
() = child.cancelled() => Err(HyperError::Cancelled),
};
match res {
Ok((raw, _rtt)) => {
if raw.len() >= 2 {
let rid = u16::from_be_bytes([raw[0], raw[1]]);
if rid != id {
let _ = tx.send(Err(HyperError::TxidMismatch)).await;
return;
}
}
let ab = ArenaBytes {
epoch: Arc::new(ArenaEpoch {
id: 0,
slabs: vec![],
live: AtomicUsize::new(1),
}),
bytes: raw,
};
match PacketView::new(ab).and_then(|pv| {
validate(pv.bytes(), &up)?;
Ok(pv)
}) {
Ok(pv) => {
let _ = tx.send(Ok((pv, up, started.elapsed()))).await;
}
Err(e) => {
let _ = tx.send(Err(e)).await;
}
}
}
Err(e) => {
let _ = tx.send(Err(e)).await;
}
}
});
}
drop(tx);
let result = timeout(overall, async {
let mut last_err = HyperError::AllUpstreamsFailed;
while let Some(item) = rx.recv().await {
match item {
Ok(v) => {
cancel.cancel();
return Ok(v);
}
Err(HyperError::Cancelled) => {}
Err(e) => last_err = e,
}
}
Err(last_err)
})
.await;
cancel.cancel();
match result {
Ok(inner) => inner,
Err(_) => Err(HyperError::Timeout(overall)),
}
}
}
#[async_trait::async_trait]
pub trait DnssecValidator: Send + Sync {
async fn validate(
&self,
qname: &NameKey,
qtype: u16,
packet: &PacketView,
mode: DnssecMode,
) -> HResult<DnssecState>;
async fn validate_with_context(
&self,
qname: &NameKey,
qtype: u16,
packet: &PacketView,
mode: DnssecMode,
context: Option<DnssecQueryContext>,
) -> HResult<DnssecState> {
let _ = context;
self.validate(qname, qtype, packet, mode).await
}
}
#[derive(Clone)]
pub struct DnssecQueryContext {
pub transport: Arc<dyn Transport>,
pub upstream: Upstream,
pub timeout: Duration,
}
pub struct TrustAdValidator;
impl std::fmt::Debug for TrustAdValidator {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.debug_struct("TrustAdValidator").finish()
}
}
impl Default for TrustAdValidator {
fn default() -> Self {
Self::new()
}
}
impl TrustAdValidator {
pub fn new() -> Self {
Self
}
async fn validate_anchored(
&self,
qname: &NameKey,
qtype: u16,
packet: &[u8],
mode: DnssecMode,
anchors: &[crate::resolver::PositiveTrustAnchor],
context: &DnssecQueryContext,
) -> HResult<DnssecState> {
if anchors.is_empty() {
return if mode == DnssecMode::Yes {
Err(HyperError::DnssecBogus)
} else {
Ok(DnssecState::Insecure)
};
}
let (header, questions, records, end) = crate::wire::parse_sections(packet)
.map_err(|error| HyperError::Wire(error.to_string()))?;
if end != packet.len() {
return Err(HyperError::Wire("DNSSEC packet has trailing data".into()));
}
let Some(question) = questions.first() else {
return Err(HyperError::Wire("DNSSEC packet has no question".into()));
};
if questions.len() != 1
|| question.name.canonical_wire() != qname.wire()
|| question.rr_type != qtype
|| question.class != CLASS_IN
{
return Err(HyperError::Wire(
"DNSSEC question does not match query".into(),
));
}
let answer_count = usize::from(header.answer_count);
let answers = records
.get(..answer_count)
.ok_or_else(|| HyperError::Wire("DNSSEC answer section is truncated".into()))?;
let authenticated_count = answer_count
.checked_add(usize::from(header.authority_count))
.ok_or_else(|| HyperError::Wire("DNSSEC record count overflow".into()))?;
let authenticated_records = records
.get(..authenticated_count)
.ok_or_else(|| HyperError::Wire("DNSSEC authority section is truncated".into()))?;
let rrsets = relevant_answer_rrsets(answers, qname, qtype);
if rrsets.is_empty() {
return self
.validate_negative(
qname,
qtype,
packet,
&records,
authenticated_records,
mode,
anchors,
context,
)
.await;
}
let now = SystemTime::now();
for (owner, rr_type, class) in rrsets {
let rrset = answers
.iter()
.filter(|record| {
record.name.canonical_wire() == owner
&& record.rr_type == rr_type
&& record.class == class
})
.cloned()
.collect::<Vec<_>>();
let signatures = authenticated_records
.iter()
.filter(|record| {
record.name.canonical_wire() == owner
&& record.rr_type == crate::wire::TYPE_RRSIG
&& record.class == class
})
.collect::<Vec<_>>();
if signatures.is_empty() {
let has_dnssec_material = authenticated_records.iter().any(|record| {
matches!(
record.rr_type,
crate::wire::TYPE_RRSIG
| crate::wire::TYPE_NSEC
| crate::wire::TYPE_NSEC3
| crate::wire::TYPE_DNSKEY
| crate::wire::TYPE_DS
)
});
return if has_dnssec_material || mode == DnssecMode::Yes {
Err(HyperError::DnssecBogus)
} else {
Ok(DnssecState::Insecure)
};
}
let mut verified = false;
let mut saw_chain = false;
let mut saw_insecure_chain = false;
for signature in signatures {
let parsed = crate::wire::parse_rrsig(packet, signature)
.map_err(|error| HyperError::DnssecBogus)?;
if parsed.type_covered != rr_type {
continue;
}
let signer = parsed.signer.text().to_owned();
match self
.trusted_keys_for_zone(&signer, packet, &records, anchors, context)
.await
{
Ok(Some(keys)) => {
saw_chain = true;
if self.verify_rrset_at(packet, signature, &rrset, &keys, now)? {
verified = true;
break;
}
}
Ok(None) => saw_insecure_chain = true,
Err(error) if is_dnssec_lookup_transport_error(&error) => {
return Ok(DnssecState::Indeterminate);
}
Err(error) => return Err(error),
}
}
if verified {
continue;
}
if saw_insecure_chain && !saw_chain {
return Ok(DnssecState::Insecure);
}
return Err(HyperError::DnssecBogus);
}
let query = crate::wire::make_query_with_class(
&name_to_presentation(qname.wire()),
qtype,
CLASS_IN,
0x4a31,
)
.map_err(|error| HyperError::Wire(error.to_string()))?;
match crate::resolver::authenticated_response_semantics(&query, packet, &records) {
Ok(crate::resolver::DnssecVerdict::Secure) => Ok(DnssecState::Secure),
Ok(crate::resolver::DnssecVerdict::Insecure) => Ok(DnssecState::Insecure),
Ok(crate::resolver::DnssecVerdict::NotValidated) => Ok(DnssecState::Indeterminate),
Err(_) => Err(HyperError::DnssecBogus),
}
}
async fn validate_negative(
&self,
qname: &NameKey,
qtype: u16,
packet: &[u8],
records: &[crate::wire::ResourceRecord],
authenticated_records: &[crate::wire::ResourceRecord],
_mode: DnssecMode,
anchors: &[crate::resolver::PositiveTrustAnchor],
context: &DnssecQueryContext,
) -> HResult<DnssecState> {
let denial_records = authenticated_records
.iter()
.filter(|record| {
matches!(
record.rr_type,
crate::wire::TYPE_NSEC | crate::wire::TYPE_NSEC3
)
})
.collect::<Vec<_>>();
if denial_records.is_empty() {
return Ok(DnssecState::Indeterminate);
}
let now = SystemTime::now();
let mut saw_insecure = false;
let mut rrsets = Vec::<(Vec<u8>, u16, u16)>::new();
for record in denial_records {
let key = (
record.name.canonical_wire().to_vec(),
record.rr_type,
record.class,
);
if !rrsets.contains(&key) {
rrsets.push(key);
}
}
for (owner, rr_type, class) in rrsets {
let rrset = authenticated_records
.iter()
.filter(|record| {
record.name.canonical_wire() == owner
&& record.rr_type == rr_type
&& record.class == class
})
.cloned()
.collect::<Vec<_>>();
let signatures = authenticated_records
.iter()
.filter(|record| {
record.name.canonical_wire() == owner
&& record.rr_type == crate::wire::TYPE_RRSIG
&& record.class == class
})
.collect::<Vec<_>>();
if signatures.is_empty() {
return Err(HyperError::DnssecBogus);
}
let mut verified = false;
let mut rrset_saw_insecure = false;
for signature in signatures {
let parsed = crate::wire::parse_rrsig(packet, signature)
.map_err(|_| HyperError::DnssecBogus)?;
if parsed.type_covered != rr_type {
continue;
}
match self
.trusted_keys_for_zone(parsed.signer.text(), packet, records, anchors, context)
.await
{
Ok(Some(keys)) => {
if self.verify_rrset_at(packet, signature, &rrset, &keys, now)? {
verified = true;
break;
}
}
Ok(None) => {
saw_insecure = true;
rrset_saw_insecure = true;
}
Err(error) if is_dnssec_lookup_transport_error(&error) => {
return Ok(DnssecState::Indeterminate);
}
Err(error) => return Err(error),
}
}
if !verified && !rrset_saw_insecure {
return Err(HyperError::DnssecBogus);
}
}
if saw_insecure {
return Ok(DnssecState::Insecure);
}
let query = crate::wire::make_query_with_class(
&name_to_presentation(qname.wire()),
qtype,
CLASS_IN,
0x4a31,
)
.map_err(|error| HyperError::Wire(error.to_string()))?;
match crate::resolver::authenticated_response_semantics(&query, packet, records) {
Ok(crate::resolver::DnssecVerdict::Secure) => Ok(DnssecState::Secure),
Ok(crate::resolver::DnssecVerdict::Insecure) => Ok(DnssecState::Insecure),
Ok(crate::resolver::DnssecVerdict::NotValidated) => Ok(DnssecState::Indeterminate),
Err(error) => Err(HyperError::DnssecBogus),
}
}
async fn trusted_keys_for_zone(
&self,
zone: &str,
source_packet: &[u8],
source_records: &[crate::wire::ResourceRecord],
anchors: &[crate::resolver::PositiveTrustAnchor],
context: &DnssecQueryContext,
) -> HResult<Option<Vec<crate::wire::ResourceRecord>>> {
let zone = crate::resolver::normalize_dns_name(zone);
let zones = crate::resolver::dns_name_ancestors(&zone);
let Some(anchor_index) = zones.iter().enumerate().rev().find_map(|(index, owner)| {
anchors
.iter()
.any(|anchor| crate::resolver::dns_names_equal(&anchor.owner, owner))
.then_some(index)
}) else {
return Err(HyperError::DnssecBogus);
};
let anchor_owner = &zones[anchor_index];
let anchor_source = source_records
.iter()
.filter(|record| {
record.rr_type == crate::wire::TYPE_DNSKEY
&& crate::resolver::dns_names_equal(record.name.text(), anchor_owner)
})
.cloned()
.collect::<Vec<_>>();
let anchor_packet = if anchor_source.is_empty() {
self.fetch_dnssec_packet(anchor_owner, crate::wire::TYPE_DNSKEY, context)
.await?
} else {
source_packet.to_vec()
};
let anchor_keys = if anchor_source.is_empty() {
crate::resolver::records_of_type(&anchor_packet, anchor_owner, crate::wire::TYPE_DNSKEY)
.map_err(map_resolver_dnssec_error)?
} else {
anchor_source
};
let anchor_set = anchors
.iter()
.filter(|anchor| crate::resolver::dns_names_equal(&anchor.owner, anchor_owner))
.collect::<Vec<_>>();
let anchor_signing_keys = anchor_keys
.iter()
.filter(|key| {
anchor_set.iter().any(|anchor| {
crate::resolver::trust_anchor_matches_dnskey(anchor, key).unwrap_or(false)
})
})
.cloned()
.collect::<Vec<_>>();
if anchor_signing_keys.is_empty() {
return Err(HyperError::DnssecBogus);
}
if !crate::resolver::verify_packet_rrset(
&anchor_packet,
anchor_owner,
crate::wire::TYPE_DNSKEY,
&anchor_signing_keys,
)
.map_err(map_resolver_dnssec_error)?
{
return Err(HyperError::DnssecBogus);
}
let mut trusted = crate::resolver::authenticated_zone_signing_keys(&anchor_keys)
.map_err(map_resolver_dnssec_error)?;
if trusted.is_empty() {
return Err(HyperError::DnssecBogus);
}
for child in zones.iter().skip(anchor_index + 1) {
let ds_packet = self
.source_or_fetch_signed(source_packet, child, crate::wire::TYPE_DS, context)
.await?;
let ds_records =
crate::resolver::records_of_type(&ds_packet, child, crate::wire::TYPE_DS)
.map_err(map_resolver_dnssec_error)?;
if ds_records.is_empty() {
let denied = crate::resolver::authenticated_ds_denial(&ds_packet, child, &trusted)
.map_err(map_resolver_dnssec_error)?;
if denied {
return Ok(None);
}
return Err(HyperError::DnssecBogus);
}
if !crate::resolver::verify_packet_rrset(
&ds_packet,
child,
crate::wire::TYPE_DS,
&trusted,
)
.map_err(map_resolver_dnssec_error)?
{
return Err(HyperError::DnssecBogus);
}
let key_packet = self
.source_or_fetch_signed(source_packet, child, crate::wire::TYPE_DNSKEY, context)
.await?;
let keys =
crate::resolver::records_of_type(&key_packet, child, crate::wire::TYPE_DNSKEY)
.map_err(map_resolver_dnssec_error)?;
let valid_signing_keys = keys
.iter()
.filter(|key| {
ds_records
.iter()
.any(|ds| crate::dnssec::ds_matches_dnskey(ds, key).unwrap_or(false))
})
.cloned()
.collect::<Vec<_>>();
if valid_signing_keys.is_empty()
|| !crate::resolver::verify_packet_rrset(
&key_packet,
child,
crate::wire::TYPE_DNSKEY,
&valid_signing_keys,
)
.map_err(map_resolver_dnssec_error)?
{
return Err(HyperError::DnssecBogus);
}
trusted = crate::resolver::authenticated_zone_signing_keys(&keys)
.map_err(map_resolver_dnssec_error)?;
if trusted.is_empty() {
return Err(HyperError::DnssecBogus);
}
}
Ok(Some(trusted))
}
async fn source_or_fetch_signed(
&self,
source_packet: &[u8],
owner: &str,
rr_type: u16,
context: &DnssecQueryContext,
) -> HResult<Vec<u8>> {
let (_, _, records, end) = crate::wire::parse_sections(source_packet)
.map_err(|error| HyperError::Wire(error.to_string()))?;
if end != source_packet.len() {
return Err(HyperError::Wire("DNSSEC packet has trailing data".into()));
}
let has_rrset = records.iter().any(|record| {
record.rr_type == rr_type && crate::resolver::dns_names_equal(record.name.text(), owner)
});
let has_signature = records.iter().any(|record| {
if record.rr_type != crate::wire::TYPE_RRSIG
|| !crate::resolver::dns_names_equal(record.name.text(), owner)
{
return false;
}
crate::wire::parse_rrsig(source_packet, record)
.is_ok_and(|signature| signature.type_covered == rr_type)
});
if has_rrset && has_signature {
return Ok(source_packet.to_vec());
}
self.fetch_dnssec_packet(owner, rr_type, context).await
}
async fn fetch_dnssec_packet(
&self,
owner: &str,
rr_type: u16,
context: &DnssecQueryContext,
) -> HResult<Vec<u8>> {
let id = rand::thread_rng().gen::<u16>();
let owner_wire =
crate::wire::encode_name(owner).map_err(|error| HyperError::Wire(error.to_string()))?;
let owner = NameKey::from_wire_uncompressed(&owner_wire)?;
let query = QueryBuilder::new(id, &owner, rr_type, CLASS_IN).finish();
let (response, _) = context
.transport
.exchange(&context.upstream, query.clone(), context.timeout)
.await?;
crate::wire::response_matches(&query, &response)
.map_err(|error| HyperError::Wire(error.to_string()))?;
Ok(response.to_vec())
}
fn verify_rrset_at(
&self,
packet: &[u8],
signature: &crate::wire::ResourceRecord,
rrset: &[crate::wire::ResourceRecord],
keys: &[crate::wire::ResourceRecord],
now: SystemTime,
) -> HResult<bool> {
for key in keys {
match crate::dnssec::verify_rrsig(packet, signature, rrset, key, now) {
Ok(true) => return Ok(true),
Ok(false) => {}
Err(crate::dnssec::DnssecError::UnsupportedAlgorithm(_))
| Err(crate::dnssec::DnssecError::UnsupportedDigest(_)) => {}
Err(error) => return Err(HyperError::DnssecBogus),
}
}
Ok(false)
}
}
#[async_trait::async_trait]
impl DnssecValidator for TrustAdValidator {
async fn validate(
&self,
qname: &NameKey,
qtype: u16,
packet: &PacketView,
mode: DnssecMode,
) -> HResult<DnssecState> {
self.validate_with_context(qname, qtype, packet, mode, None)
.await
}
async fn validate_with_context(
&self,
qname: &NameKey,
qtype: u16,
packet: &PacketView,
mode: DnssecMode,
context: Option<DnssecQueryContext>,
) -> HResult<DnssecState> {
if mode == DnssecMode::No {
return Ok(DnssecState::Insecure);
}
if !packet.qr() {
return Err(HyperError::Wire("DNSSEC input is not a response".into()));
}
if packet.cd() {
return Ok(DnssecState::Insecure);
}
if let Some(context) = context {
let anchors = crate::resolver::load_positive_trust_anchors();
return self
.validate_anchored(qname, qtype, packet.bytes(), mode, &anchors, &context)
.await;
}
match packet_dnssec_evidence(qname, qtype, packet.bytes())? {
PacketDnssecEvidence::Unsigned => {
if mode == DnssecMode::Yes {
Err(HyperError::DnssecBogus)
} else {
Ok(DnssecState::Insecure)
}
}
PacketDnssecEvidence::Invalid => Err(HyperError::DnssecBogus),
PacketDnssecEvidence::Incomplete => {
if mode == DnssecMode::Yes && packet.rcode() == RCODE_SERVFAIL {
Err(HyperError::DnssecBogus)
} else {
Ok(DnssecState::Indeterminate)
}
}
PacketDnssecEvidence::VerifiedUnanchored => Ok(DnssecState::Indeterminate),
}
}
}
fn map_resolver_dnssec_error(error: crate::resolver::ResolveError) -> HyperError {
match error {
crate::resolver::ResolveError::Wire(error) => HyperError::Wire(error.to_string()),
crate::resolver::ResolveError::DnssecValidationFailed { .. }
| crate::resolver::ResolveError::NoTrustAnchor => HyperError::DnssecBogus,
other => HyperError::Internal(other.to_string()),
}
}
fn is_dnssec_lookup_transport_error(error: &HyperError) -> bool {
matches!(
error,
HyperError::Timeout(_)
| HyperError::AllUpstreamsFailed
| HyperError::Cancelled
| HyperError::PeerMismatch
| HyperError::Internal(_)
)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum PacketDnssecEvidence {
Unsigned,
Incomplete,
VerifiedUnanchored,
Invalid,
}
fn packet_dnssec_evidence(
qname: &NameKey,
qtype: u16,
packet: &[u8],
) -> HResult<PacketDnssecEvidence> {
packet_dnssec_evidence_at(qname, qtype, packet, SystemTime::now())
}
fn packet_dnssec_evidence_at(
qname: &NameKey,
qtype: u16,
packet: &[u8],
now: SystemTime,
) -> HResult<PacketDnssecEvidence> {
let (header, questions, records, end) =
crate::wire::parse_sections(packet).map_err(|error| HyperError::Wire(error.to_string()))?;
if end != packet.len() {
return Err(HyperError::Wire("DNSSEC packet has trailing data".into()));
}
if !header.is_response() {
return Err(HyperError::Wire("DNSSEC packet is not a response".into()));
}
let Some(question) = questions.first() else {
return Err(HyperError::Wire("DNSSEC packet has no question".into()));
};
if questions.len() != 1
|| question.name.canonical_wire() != qname.wire()
|| question.rr_type != qtype
|| question.class != CLASS_IN
{
return Err(HyperError::Wire(
"DNSSEC question does not match query".into(),
));
}
let answer_count = usize::from(header.answer_count);
let answers = records
.get(..answer_count)
.ok_or_else(|| HyperError::Wire("DNSSEC answer section is truncated".into()))?;
let authenticated_record_count = answer_count
.checked_add(usize::from(header.authority_count))
.ok_or_else(|| HyperError::Wire("DNSSEC record count overflow".into()))?;
let authenticated_records = records
.get(..authenticated_record_count)
.ok_or_else(|| HyperError::Wire("DNSSEC authority section is truncated".into()))?;
let rrsets = relevant_answer_rrsets(answers, qname, qtype);
if rrsets.is_empty() {
return Ok(
if records.iter().any(|record| {
matches!(
record.rr_type,
crate::wire::TYPE_RRSIG
| crate::wire::TYPE_NSEC
| crate::wire::TYPE_NSEC3
| crate::wire::TYPE_DNSKEY
| 43
)
}) {
PacketDnssecEvidence::Incomplete
} else {
PacketDnssecEvidence::Unsigned
},
);
}
let mut saw_incomplete = false;
let mut all_rrsets_verified = true;
let mut saw_any_matching_signature = false;
for (owner, rr_type, class) in rrsets {
let rrset = answers
.iter()
.filter(|record| {
record.name.canonical_wire() == owner
&& record.rr_type == rr_type
&& record.class == class
})
.cloned()
.collect::<Vec<_>>();
let signatures = authenticated_records
.iter()
.filter(|record| {
record.name.canonical_wire() == owner
&& record.rr_type == crate::wire::TYPE_RRSIG
&& record.class == class
})
.collect::<Vec<_>>();
let mut saw_matching_signature = false;
let mut rrset_verified = false;
let mut rrset_saw_missing_key = false;
let mut rrset_saw_unsupported = false;
let mut rrset_saw_invalid = false;
for signature in signatures {
let parsed = match crate::wire::parse_rrsig(packet, signature) {
Ok(parsed) if parsed.type_covered == rr_type => parsed,
Ok(_) => continue,
Err(_) => return Ok(PacketDnssecEvidence::Invalid),
};
saw_matching_signature = true;
saw_any_matching_signature = true;
let keys = records.iter().filter(|key| {
key.rr_type == crate::wire::TYPE_DNSKEY
&& key.class == class
&& key.name.canonical_wire() == parsed.signer.canonical_wire()
});
let mut signature_key_count = 0usize;
let mut signature_verified = false;
let mut signature_saw_unsupported = false;
let mut signature_saw_invalid = false;
for key in keys {
signature_key_count += 1;
match crate::dnssec::verify_rrsig(packet, signature, &rrset, key, now) {
Ok(true) => {
rrset_verified = true;
signature_verified = true;
break;
}
Ok(false) => {
signature_saw_invalid = true;
}
Err(crate::dnssec::DnssecError::UnsupportedAlgorithm(_))
| Err(crate::dnssec::DnssecError::UnsupportedDigest(_)) => {
signature_saw_unsupported = true;
}
Err(_) => return Ok(PacketDnssecEvidence::Invalid),
}
}
if signature_key_count == 0 {
rrset_saw_missing_key = true;
} else if !signature_verified {
rrset_saw_unsupported |= signature_saw_unsupported;
rrset_saw_invalid |= signature_saw_invalid;
}
}
if !saw_matching_signature {
saw_incomplete = true;
all_rrsets_verified = false;
} else if !rrset_verified {
all_rrsets_verified = false;
if rrset_saw_invalid {
return Ok(PacketDnssecEvidence::Invalid);
}
if rrset_saw_missing_key || rrset_saw_unsupported {
saw_incomplete = true;
}
}
}
if !saw_any_matching_signature
&& !records.iter().any(|record| {
matches!(
record.rr_type,
crate::wire::TYPE_RRSIG
| crate::wire::TYPE_NSEC
| crate::wire::TYPE_NSEC3
| crate::wire::TYPE_DNSKEY
| crate::wire::TYPE_DS
)
})
{
return Ok(PacketDnssecEvidence::Unsigned);
}
if saw_incomplete || !all_rrsets_verified {
return Ok(PacketDnssecEvidence::Incomplete);
}
Ok(PacketDnssecEvidence::VerifiedUnanchored)
}
fn relevant_answer_rrsets(
answers: &[crate::wire::ResourceRecord],
qname: &NameKey,
qtype: u16,
) -> Vec<(Vec<u8>, u16, u16)> {
let owner = qname.wire();
let has_cname = qtype != TYPE_CNAME
&& answers.iter().any(|record| {
record.rr_type == crate::wire::TYPE_CNAME
&& record.name.canonical_wire() == owner
&& record.class == CLASS_IN
});
let mut output = Vec::new();
for record in answers {
if record.name.canonical_wire() != owner || record.class != CLASS_IN {
continue;
}
if matches!(
record.rr_type,
crate::wire::TYPE_RRSIG | crate::wire::TYPE_OPT | crate::wire::TYPE_TSIG
) {
continue;
}
if (record.rr_type == crate::wire::TYPE_DNSKEY && qtype != TYPE_DNSKEY)
|| (record.rr_type == crate::wire::TYPE_DS && qtype != TYPE_DS)
|| (record.rr_type == crate::wire::TYPE_NSEC && qtype != TYPE_NSEC)
|| (record.rr_type == crate::wire::TYPE_NSEC3 && qtype != TYPE_NSEC3)
|| (record.rr_type == crate::wire::TYPE_NSEC3PARAM && qtype != TYPE_NSEC3PARAM)
{
continue;
}
if has_cname && record.rr_type != crate::wire::TYPE_CNAME {
continue;
}
if !has_cname && qtype != TYPE_ANY && record.rr_type != qtype {
continue;
}
let key = (
record.name.canonical_wire().to_vec(),
record.rr_type,
record.class,
);
if !output.contains(&key) {
output.push(key);
}
}
output
}
pub struct ChaseResult {
pub final_name: NameKey,
pub chain: Vec<NameKey>,
pub packet: PacketView,
pub dnssec: DnssecState,
}
pub struct HyperConfig {
pub dnssec: DnssecMode,
pub speculative: SpeculativePool,
pub max_inflight: usize,
pub negative_max: Duration,
pub positive_max: Duration,
pub stale_window: Duration,
pub cache_shard_bits: u32,
pub cache_per_shard: usize,
}
impl Default for HyperConfig {
fn default() -> Self {
Self {
dnssec: DnssecMode::AllowDowngrade,
speculative: SpeculativePool::default(),
max_inflight: 8192,
negative_max: Duration::from_secs(1800),
positive_max: Duration::from_secs(86400),
stale_window: Duration::from_secs(30),
cache_shard_bits: 6,
cache_per_shard: 4096,
}
}
}
pub struct HyperResolver {
pub cfg: HyperConfig,
pub cache: Arc<HierarchicalCache>,
pub flights: Arc<Singleflight>,
pub arena: Arc<WireArena>,
pub transport: Arc<dyn Transport>,
pub dnssec: Arc<dyn DnssecValidator>,
pub upstreams: RwLock<Vec<Upstream>>,
pub scores: RwLock<Vec<UpstreamScore>>,
inflight_sem: Arc<Semaphore>,
metrics: Arc<Metrics>,
}
pub struct Metrics {
pub queries: AtomicU64,
pub cache_hits: AtomicU64,
pub upstream_ok: AtomicU64,
pub upstream_fail: AtomicU64,
pub dnssec_bogus: AtomicU64,
pub cname_chases: AtomicU64,
}
impl HyperResolver {
pub fn new(
cfg: HyperConfig,
transport: Arc<dyn Transport>,
dnssec: Arc<dyn DnssecValidator>,
) -> Self {
let max = cfg.max_inflight;
let stale = cfg.stale_window;
let bits = cfg.cache_shard_bits;
let per = cfg.cache_per_shard;
Self {
cache: Arc::new(HierarchicalCache::new(bits, per, stale)),
flights: Arc::new(Singleflight::new()),
arena: Arc::new(WireArena::new(32, 2 * 1024 * 1024)),
transport,
dnssec,
upstreams: RwLock::new(Vec::new()),
scores: RwLock::new(Vec::new()),
inflight_sem: Arc::new(Semaphore::new(max)),
metrics: Arc::new(Metrics {
queries: AtomicU64::new(0),
cache_hits: AtomicU64::new(0),
upstream_ok: AtomicU64::new(0),
upstream_fail: AtomicU64::new(0),
dnssec_bogus: AtomicU64::new(0),
cname_chases: AtomicU64::new(0),
}),
cfg,
}
}
pub fn set_upstreams(&self, ups: Vec<Upstream>, scores: Vec<UpstreamScore>) {
*self.upstreams.write() = ups;
*self.scores.write() = scores;
}
pub async fn resolve(&self, name: NameKey, qtype: u16, qclass: u16) -> HResult<RrMeta> {
self.metrics.queries.fetch_add(1, Ordering::Relaxed);
let _permit = self
.inflight_sem
.acquire()
.await
.map_err(|_| HyperError::Internal("sem closed".into()))?;
let key = CacheKey {
name: name.clone(),
qtype,
qclass,
cd: self.cfg.dnssec == DnssecMode::No,
};
let now = Instant::now();
if let Some((meta, stale)) = self.cache.get(&key, now) {
self.metrics.cache_hits.fetch_add(1, Ordering::Relaxed);
if stale {
let this = self as *const HyperResolver;
let key_bg = key.clone();
tokio::spawn(async move {
let _ = this;
let _ = key_bg;
});
}
if meta.dnssec == DnssecState::Bogus && self.cfg.dnssec == DnssecMode::Yes {
return Err(HyperError::DnssecBogus);
}
return Ok(meta);
}
match self.flights.join_or_lead(&key).await {
LeadOrFollow::Follow(mut rx) => match rx.recv().await {
Ok(Ok(m)) => Ok(m),
Ok(Err(())) => Err(HyperError::AllUpstreamsFailed),
Err(_) => Err(HyperError::Cancelled),
},
LeadOrFollow::Lead(tx) => {
let result = self.resolve_lead(&key).await;
self.flights.finish(&key);
match &result {
Ok(m) => {
let _ = tx.send(Ok(m.clone()));
}
Err(_) => {
let _ = tx.send(Err(()));
}
}
result
}
}
}
async fn resolve_lead(&self, key: &CacheKey) -> HResult<RrMeta> {
let mut current = key.name.clone();
let mut chain = Vec::new();
let mut depth = 0usize;
loop {
if depth > DNS_MAX_CNAME_DEPTH {
return Err(HyperError::CnameDepth);
}
if chain.iter().any(|n: &NameKey| n == ¤t) {
return Err(HyperError::CnameLoop);
}
chain.push(current.clone());
let id: u16 = rand::thread_rng().gen();
let q = QueryBuilder::new(id, ¤t, key.qtype, key.qclass).finish();
let ups = self.upstreams.read().clone();
let scores = self.scores.read().clone();
let (pv, up, rtt) = self
.cfg
.speculative
.race(self.transport.as_ref(), &ups, &scores, &q, |raw, u| {
if raw.len() < DNS_HEADER_LEN {
return Err(HyperError::Wire("short".into()));
}
if !raw[2] & 0x80 != 0 && raw[2] & 0x80 == 0 {
}
let qr = raw[2] & 0x80 != 0;
if !qr {
return Err(HyperError::Wire("not response".into()));
}
let _ = u;
Ok(())
})
.await
.map_err(|e| {
self.metrics.upstream_fail.fetch_add(1, Ordering::Relaxed);
e
})?;
self.metrics.upstream_ok.fetch_add(1, Ordering::Relaxed);
let _ = rtt;
let ab = self.arena.copy_from(pv.bytes())?;
let pv = PacketView::new(ab)?;
let state = self
.dnssec
.validate_with_context(
¤t,
key.qtype,
&pv,
self.cfg.dnssec,
Some(DnssecQueryContext {
transport: Arc::clone(&self.transport),
upstream: up.clone(),
timeout: self.cfg.speculative.per_try,
}),
)
.await
.map_err(|e| {
if matches!(e, HyperError::DnssecBogus) {
self.metrics.dnssec_bogus.fetch_add(1, Ordering::Relaxed);
}
e
})?;
if key.qtype != TYPE_CNAME && pv.rcode() == RCODE_NOERROR {
if let Some(target) = extract_cname_target(pv.bytes(), ¤t)? {
self.metrics.cname_chases.fetch_add(1, Ordering::Relaxed);
current = target;
depth += 1;
continue;
}
}
let ttl = extract_min_ttl(pv.bytes()).unwrap_or(60);
let ttl = Duration::from_secs(u64::from(ttl))
.min(if pv.rcode() == RCODE_NXDOMAIN {
self.cfg.negative_max
} else {
self.cfg.positive_max
})
.max(Duration::from_secs(1));
let now = Instant::now();
let meta = RrMeta {
rcode: pv.rcode(),
dnssec: state,
answer: Bytes::copy_from_slice(pv.bytes()),
expires: now + ttl,
stale_until: now + ttl + self.cfg.stale_window,
min_ttl: ttl.as_secs() as u32,
from_link: 0,
};
self.cache.put(key.clone(), meta.clone());
if current != key.name {
let ck = CacheKey {
name: current,
qtype: key.qtype,
qclass: key.qclass,
cd: key.cd,
};
self.cache.put(ck, meta.clone());
}
return Ok(meta);
}
}
pub async fn resolve_addresses(&self, name: NameKey) -> HResult<Vec<IpAddr>> {
let a = self.resolve(name.clone(), TYPE_A, CLASS_IN);
let aaaa = self.resolve(name, TYPE_AAAA, CLASS_IN);
let (ra, raaaa) = tokio::join!(a, aaaa);
let mut out = Vec::new();
if let Ok(m) = raaaa {
out.extend(extract_addrs(m.answer.as_ref(), true));
}
if let Ok(m) = ra {
out.extend(extract_addrs(m.answer.as_ref(), false));
}
if out.is_empty() {
return Err(HyperError::AllUpstreamsFailed);
}
Ok(out)
}
}
fn extract_cname_target(msg: &[u8], owner: &NameKey) -> HResult<Option<NameKey>> {
if msg.len() < DNS_HEADER_LEN {
return Ok(None);
}
let an = u16::from_be_bytes([msg[6], msg[7]]) as usize;
let mut off = DNS_HEADER_LEN;
let qd = u16::from_be_bytes([msg[4], msg[5]]) as usize;
for _ in 0..qd {
let (_, n) = decompress_name(msg, off)?;
off = n + 4;
}
for _ in 0..an {
let (nm, n) = decompress_name(msg, off)?;
off = n;
if off + 10 > msg.len() {
return Err(HyperError::Wire("rr".into()));
}
let typ = u16::from_be_bytes([msg[off], msg[off + 1]]);
let rdlen = u16::from_be_bytes([msg[off + 8], msg[off + 9]]) as usize;
off += 10;
if off + rdlen > msg.len() {
return Err(HyperError::Wire("rdata".into()));
}
if typ == TYPE_CNAME && &nm == owner {
let (target, _) = decompress_name(msg, off)?;
return Ok(Some(target));
}
off += rdlen;
}
Ok(None)
}
fn extract_min_ttl(msg: &[u8]) -> Option<u32> {
if msg.len() < DNS_HEADER_LEN {
return None;
}
let an = u16::from_be_bytes([msg[6], msg[7]]) as usize;
let ns = u16::from_be_bytes([msg[8], msg[9]]) as usize;
let mut off = DNS_HEADER_LEN;
let qd = u16::from_be_bytes([msg[4], msg[5]]) as usize;
let mut min_ttl = u32::MAX;
let skip_name = |msg: &[u8], mut off: usize| -> Option<usize> {
let mut hops = 0;
loop {
if off >= msg.len() || hops > 128 {
return None;
}
let l = msg[off];
if l == 0 {
return Some(off + 1);
}
if l & 0xC0 == 0xC0 {
return Some(off + 2);
}
if l & 0xC0 != 0 {
return None;
}
off += 1 + l as usize;
hops += 1;
}
};
for _ in 0..qd {
off = skip_name(msg, off)? + 4;
}
for _ in 0..(an + ns) {
off = skip_name(msg, off)?;
if off + 10 > msg.len() {
break;
}
let ttl = u32::from_be_bytes([msg[off + 4], msg[off + 5], msg[off + 6], msg[off + 7]]);
let rdlen = u16::from_be_bytes([msg[off + 8], msg[off + 9]]) as usize;
min_ttl = min_ttl.min(ttl);
off += 10 + rdlen;
}
if min_ttl == u32::MAX {
None
} else {
Some(min_ttl)
}
}
fn extract_addrs(msg: &[u8], v6: bool) -> Vec<IpAddr> {
let mut out = Vec::new();
let want = if v6 { TYPE_AAAA } else { TYPE_A };
if msg.len() < DNS_HEADER_LEN {
return out;
}
let an = u16::from_be_bytes([msg[6], msg[7]]) as usize;
let mut off = DNS_HEADER_LEN;
let qd = u16::from_be_bytes([msg[4], msg[5]]) as usize;
let skip_name = |msg: &[u8], mut off: usize| -> Option<usize> {
loop {
if off >= msg.len() {
return None;
}
let l = msg[off];
if l == 0 {
return Some(off + 1);
}
if l & 0xC0 == 0xC0 {
return Some(off + 2);
}
off += 1 + (l as usize & 0x3F);
}
};
for _ in 0..qd {
match skip_name(msg, off) {
Some(n) => off = n + 4,
None => return out,
}
}
for _ in 0..an {
match skip_name(msg, off) {
Some(n) => off = n,
None => break,
}
if off + 10 > msg.len() {
break;
}
let typ = u16::from_be_bytes([msg[off], msg[off + 1]]);
let rdlen = u16::from_be_bytes([msg[off + 8], msg[off + 9]]) as usize;
off += 10;
if off + rdlen > msg.len() {
break;
}
if typ == want {
if !v6 && rdlen == 4 {
out.push(IpAddr::V4(Ipv4Addr::new(
msg[off],
msg[off + 1],
msg[off + 2],
msg[off + 3],
)));
} else if v6 && rdlen == 16 {
let mut a = [0u8; 16];
a.copy_from_slice(&msg[off..off + 16]);
out.push(IpAddr::V6(Ipv6Addr::from(a)));
}
}
off += rdlen;
}
out
}
#[cfg(test)]
mod hyper_dnssec_tests {
use super::*;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
const ED25519_PUBLIC_KEY: [u8; 32] = [
0xd7, 0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, 0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07,
0x3a, 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07,
0x51, 0x1a,
];
const EXAMPLE_A_SIGNATURE: [u8; 64] = [
0xe1, 0x81, 0x5f, 0x4f, 0x6d, 0x63, 0x78, 0x64, 0x7f, 0xd4, 0x47, 0x02, 0xbe, 0x7f, 0x01,
0xd7, 0x80, 0xb4, 0x0e, 0x63, 0x3d, 0x3b, 0xca, 0x9c, 0x18, 0x9b, 0xc7, 0xc1, 0x96, 0x53,
0xa6, 0xdd, 0xa4, 0xa9, 0x04, 0x9e, 0xd2, 0xf4, 0x16, 0xdd, 0x74, 0xc9, 0xff, 0x5c, 0xb6,
0x88, 0x94, 0x7e, 0x21, 0x98, 0x55, 0x59, 0x96, 0xc6, 0x47, 0xb6, 0x9f, 0xda, 0x7e, 0xe2,
0xc7, 0x36, 0xdc, 0x05,
];
const ANCHORED_DNSKEY_SIGNATURE: [u8; 64] = [
0xb7, 0x49, 0xc8, 0xc4, 0xac, 0x70, 0xab, 0xac, 0x22, 0x37, 0x15, 0xad, 0xb9, 0xec, 0x97,
0xfb, 0xec, 0x01, 0xed, 0x78, 0x64, 0x2f, 0x5b, 0xf8, 0x3f, 0x76, 0xb3, 0x96, 0xb8, 0x2c,
0xf4, 0x9b, 0x77, 0x20, 0x22, 0x52, 0x1e, 0x18, 0x4d, 0x49, 0x43, 0x72, 0x0b, 0x79, 0x2f,
0xa5, 0x23, 0x72, 0x28, 0xc3, 0x66, 0x68, 0x61, 0x12, 0x49, 0x98, 0xe3, 0x58, 0xbd, 0xff,
0x7c, 0x0c, 0xdc, 0x04,
];
const ANCHORED_ROOT_DNSKEY_SIGNATURE: [u8; 64] = [
0x3d, 0xc7, 0xc9, 0x3d, 0x87, 0xe3, 0xcf, 0x12, 0xb2, 0x65, 0xbd, 0x3e, 0x08, 0x3f, 0x80,
0x84, 0x12, 0x9c, 0x5f, 0x37, 0x82, 0xc0, 0x0f, 0xf3, 0x9a, 0x22, 0xcf, 0x0b, 0xec, 0x43,
0x93, 0x3a, 0x5a, 0xbb, 0x3a, 0x6f, 0x4c, 0xa4, 0xa8, 0x6a, 0xad, 0x61, 0x53, 0x52, 0xf4,
0x45, 0x39, 0x51, 0x1d, 0x23, 0x08, 0xdd, 0xed, 0xef, 0xed, 0x70, 0x12, 0x03, 0x0f, 0x12,
0xf1, 0xff, 0x58, 0x06,
];
const ANCHORED_DS_SIGNATURE: [u8; 64] = [
0x3f, 0xb7, 0xe6, 0x1c, 0xc0, 0x4e, 0xfd, 0xab, 0x16, 0x20, 0xbc, 0x52, 0x6d, 0xd2, 0x73,
0x15, 0xcf, 0x22, 0xf9, 0x4e, 0xcd, 0x00, 0xa8, 0x13, 0x7a, 0x5f, 0x67, 0x30, 0x0f, 0xbf,
0xfb, 0x29, 0x50, 0x73, 0x26, 0x61, 0xc2, 0x10, 0x3c, 0x07, 0xfa, 0xa9, 0x1e, 0xa0, 0x8b,
0xa3, 0xbf, 0x37, 0x3d, 0x21, 0x73, 0x1b, 0x9a, 0xf3, 0x44, 0xb5, 0x81, 0xc7, 0xcc, 0x04,
0xfc, 0x6b, 0x85, 0x06,
];
const ANCHORED_A_SIGNATURE: [u8; 64] = [
0x4f, 0x13, 0x48, 0xe3, 0x60, 0x31, 0xf4, 0x56, 0xcf, 0xb4, 0xa0, 0xfb, 0xa7, 0xa5, 0x34,
0xdf, 0xd5, 0xb1, 0x53, 0xe1, 0x4c, 0xc2, 0x71, 0x42, 0x4c, 0x75, 0x53, 0xaa, 0xc6, 0x40,
0x34, 0x42, 0x59, 0x9d, 0x30, 0x3c, 0x1c, 0xb5, 0x64, 0xdf, 0x80, 0x92, 0xf7, 0xd7, 0xcf,
0x83, 0xbf, 0xc9, 0x96, 0x71, 0x8c, 0x14, 0x04, 0xeb, 0x18, 0xb0, 0x93, 0xbd, 0x23, 0xc0,
0x94, 0x19, 0xf2, 0x08,
];
const ANCHORED_EXAMPLE_NSEC_SIGNATURE: [u8; 64] = [
0x72, 0x30, 0xb6, 0x0d, 0xea, 0x80, 0x9f, 0x98, 0xe0, 0x25, 0xdb, 0x2c, 0x7f, 0xcc, 0xcb,
0x12, 0x0d, 0x82, 0xc4, 0x44, 0x89, 0xea, 0xe6, 0x5a, 0x24, 0xc1, 0x6f, 0x67, 0x30, 0x95,
0xd1, 0x2c, 0x47, 0xc7, 0x9f, 0xbb, 0xbe, 0x31, 0xe5, 0x7f, 0x5d, 0xa6, 0x96, 0x22, 0xe4,
0xf4, 0x18, 0x4c, 0x9b, 0x17, 0x4a, 0x05, 0x67, 0x48, 0x41, 0x57, 0xc4, 0xa9, 0xba, 0x7d,
0x03, 0xa1, 0x76, 0x04,
];
const ANCHORED_A_NSEC_SIGNATURE: [u8; 64] = [
0x16, 0x0b, 0x7c, 0xff, 0x1d, 0x46, 0xf0, 0x6a, 0x67, 0x5e, 0xfa, 0x76, 0x97, 0xc9, 0x1d,
0xe4, 0xfa, 0x5d, 0x36, 0x7d, 0x69, 0x1f, 0xe2, 0x3a, 0x66, 0xa1, 0xd7, 0x85, 0xe8, 0x70,
0xfd, 0xae, 0x4f, 0xbc, 0xcd, 0x61, 0x4d, 0x90, 0xd0, 0xc6, 0x7e, 0x33, 0x57, 0x82, 0x66,
0xf9, 0x25, 0x5f, 0x76, 0x91, 0xcb, 0x88, 0xf3, 0x59, 0x10, 0xcf, 0xf5, 0x71, 0x43, 0x23,
0x69, 0xd7, 0x0e, 0x00,
];
fn name(text: &str) -> NameKey {
let wire = crate::wire::encode_name(text).expect("name wire");
NameKey::from_wire_uncompressed(&wire).expect("name key")
}
fn record_wire(owner: &str, rr_type: u16, ttl: u32, rdata: &[u8]) -> Vec<u8> {
let mut wire = crate::wire::encode_name(owner).expect("owner wire");
wire.extend_from_slice(&rr_type.to_be_bytes());
wire.extend_from_slice(&CLASS_IN.to_be_bytes());
wire.extend_from_slice(&ttl.to_be_bytes());
wire.extend_from_slice(
&u16::try_from(rdata.len())
.expect("RDATA length")
.to_be_bytes(),
);
wire.extend_from_slice(rdata);
wire
}
fn response(
flags: u16,
qname: &NameKey,
qtype: u16,
answers: &[Vec<u8>],
authority: &[Vec<u8>],
additional: &[Vec<u8>],
) -> Vec<u8> {
let mut packet = Vec::new();
packet.extend_from_slice(&0x1234_u16.to_be_bytes());
packet.extend_from_slice(&flags.to_be_bytes());
packet.extend_from_slice(&1_u16.to_be_bytes());
packet.extend_from_slice(
&u16::try_from(answers.len())
.expect("answer count")
.to_be_bytes(),
);
packet.extend_from_slice(
&u16::try_from(authority.len())
.expect("authority count")
.to_be_bytes(),
);
packet.extend_from_slice(
&u16::try_from(additional.len())
.expect("additional count")
.to_be_bytes(),
);
packet.extend_from_slice(qname.wire());
packet.extend_from_slice(&qtype.to_be_bytes());
packet.extend_from_slice(&CLASS_IN.to_be_bytes());
for record in answers
.iter()
.chain(authority.iter())
.chain(additional.iter())
{
packet.extend_from_slice(record);
}
packet
}
fn packet_view(packet: &[u8]) -> PacketView {
let arena = WireArena::new(1, packet.len().max(64));
let bytes = arena.copy_from(packet).expect("arena packet");
PacketView::new(bytes).expect("packet view")
}
fn a_record(ttl: u32, address: [u8; 4]) -> Vec<u8> {
record_wire("example.", TYPE_A, ttl, &address)
}
fn dnskey_record_for(owner: &str) -> Vec<u8> {
let mut rdata = vec![0x01, 0x01, 0x03, 0x0f];
rdata.extend_from_slice(&ED25519_PUBLIC_KEY);
record_wire(owner, TYPE_DNSKEY, 60, &rdata)
}
fn dnskey_record() -> Vec<u8> {
dnskey_record_for("example.")
}
fn rrsig_rdata(signature: &[u8], expiration: u32, inception: u32) -> Vec<u8> {
let mut rdata = Vec::new();
rdata.extend_from_slice(&TYPE_A.to_be_bytes());
rdata.extend_from_slice(&[15, 1]);
rdata.extend_from_slice(&60_u32.to_be_bytes());
rdata.extend_from_slice(&expiration.to_be_bytes());
rdata.extend_from_slice(&inception.to_be_bytes());
rdata.extend_from_slice(&14_017_u16.to_be_bytes());
rdata.extend_from_slice(&crate::wire::encode_name("example.").expect("signer wire"));
rdata.extend_from_slice(signature);
rdata
}
fn rrsig_record(signature: &[u8], expiration: u32, inception: u32) -> Vec<u8> {
record_wire(
"example.",
TYPE_RRSIG,
60,
&rrsig_rdata(signature, expiration, inception),
)
}
fn anchored_rrsig_record_with_signer(
owner: &str,
covered: u16,
labels: u8,
signer: &str,
signature: &[u8],
) -> Vec<u8> {
let mut rdata = Vec::new();
rdata.extend_from_slice(&covered.to_be_bytes());
rdata.extend_from_slice(&[15, labels]);
rdata.extend_from_slice(&60_u32.to_be_bytes());
rdata.extend_from_slice(&u32::MAX.to_be_bytes());
rdata.extend_from_slice(&0_u32.to_be_bytes());
rdata.extend_from_slice(&14_017_u16.to_be_bytes());
rdata.extend_from_slice(&crate::wire::encode_name(signer).expect("signer wire"));
rdata.extend_from_slice(signature);
record_wire(owner, TYPE_RRSIG, 60, &rdata)
}
fn anchored_rrsig_record(owner: &str, covered: u16, labels: u8, signature: &[u8]) -> Vec<u8> {
anchored_rrsig_record_with_signer(owner, covered, labels, "example.", signature)
}
fn nsec_record(owner: &str, next: &str, types: &[u16]) -> Vec<u8> {
let mut rdata = crate::wire::encode_name(next).expect("NSEC next name");
let length = types
.iter()
.map(|rr_type| usize::from(*rr_type) / 8 + 1)
.max()
.unwrap_or(1);
let mut bitmap = vec![0, u8::try_from(length).expect("NSEC bitmap length")];
bitmap.resize(2 + length, 0);
for rr_type in types {
let bit = usize::from(*rr_type);
bitmap[2 + bit / 8] |= 0x80 >> (bit % 8);
}
rdata.extend_from_slice(&bitmap);
record_wire(owner, crate::wire::TYPE_NSEC, 60, &rdata)
}
fn current_signature_window() -> (u32, u32) {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time")
.as_secs();
(
u32::try_from(now.saturating_add(3_600)).expect("current time fits DNSSEC time"),
u32::try_from(now.saturating_sub(3_600)).expect("current time fits DNSSEC time"),
)
}
#[tokio::test]
async fn forged_ad_bit_never_authenticates_unsigned_answer() {
let qname = name("example.");
let packet = response(
0x8000 | 0x0020,
&qname,
TYPE_A,
&[a_record(60, [192, 0, 2, 1])],
&[],
&[],
);
let validator = TrustAdValidator::default();
assert_eq!(
validator
.validate(
&qname,
TYPE_A,
&packet_view(&packet),
DnssecMode::AllowDowngrade,
)
.await
.expect("allow-downgrade result"),
DnssecState::Insecure
);
assert!(matches!(
validator
.validate(&qname, TYPE_A, &packet_view(&packet), DnssecMode::Yes)
.await,
Err(HyperError::DnssecBogus)
));
}
#[tokio::test]
async fn checking_disabled_suppresses_ad_even_when_response_sets_it() {
let qname = name("example.");
let packet = response(
0x8000 | 0x0020 | 0x0010,
&qname,
TYPE_A,
&[a_record(60, [192, 0, 2, 2])],
&[],
&[],
);
assert_eq!(
TrustAdValidator::default()
.validate(&qname, TYPE_A, &packet_view(&packet), DnssecMode::Yes)
.await
.expect("checking-disabled result"),
DnssecState::Insecure
);
}
#[tokio::test]
async fn missing_rrsig_is_insecure_only_when_downgrade_is_allowed() {
let qname = name("example.");
let packet = response(
0x8000,
&qname,
TYPE_A,
&[a_record(60, [192, 0, 2, 3])],
&[],
&[],
);
let validator = TrustAdValidator::default();
assert_eq!(
validator
.validate(
&qname,
TYPE_A,
&packet_view(&packet),
DnssecMode::AllowDowngrade,
)
.await
.expect("unsigned downgrade result"),
DnssecState::Insecure
);
assert!(matches!(
validator
.validate(&qname, TYPE_A, &packet_view(&packet), DnssecMode::Yes)
.await,
Err(HyperError::DnssecBogus)
));
}
#[tokio::test]
async fn bogus_rrsig_is_never_downgraded_or_authorized() {
let qname = name("example.");
let (expiration, inception) = current_signature_window();
let packet = response(
0x8000 | 0x0020,
&qname,
TYPE_A,
&[
a_record(30, [192, 0, 2, 4]),
rrsig_record(&[0; 64], expiration, inception),
],
&[],
&[dnskey_record()],
);
let validator = TrustAdValidator::default();
for mode in [DnssecMode::AllowDowngrade, DnssecMode::Yes] {
assert!(matches!(
validator
.validate(&qname, TYPE_A, &packet_view(&packet), mode)
.await,
Err(HyperError::DnssecBogus)
));
}
assert_eq!(
validator
.validate(&qname, TYPE_A, &packet_view(&packet), DnssecMode::No)
.await
.expect("DNSSEC=no result"),
DnssecState::Insecure
);
}
#[test]
fn valid_in_packet_rrsig_is_verified_but_not_trusted_without_a_chain() {
let qname = name("example.");
let packet = response(
0x8000 | 0x0020,
&qname,
TYPE_A,
&[
a_record(30, [192, 0, 2, 1]),
rrsig_record(&EXAMPLE_A_SIGNATURE, 10_600, 9_400),
],
&[],
&[dnskey_record()],
);
assert_eq!(
packet_dnssec_evidence_at(
&qname,
TYPE_A,
&packet,
UNIX_EPOCH + Duration::from_secs(10_000),
)
.expect("packet DNSSEC evidence"),
PacketDnssecEvidence::VerifiedUnanchored
);
}
#[tokio::test]
async fn signed_answer_without_dnskey_is_indeterminate_not_secure() {
let qname = name("example.");
let packet = response(
0x8000,
&qname,
TYPE_A,
&[
a_record(30, [192, 0, 2, 5]),
rrsig_record(&[0; 64], 10_600, 9_400),
],
&[],
&[],
);
assert_eq!(
TrustAdValidator::default()
.validate(
&qname,
TYPE_A,
&packet_view(&packet),
DnssecMode::AllowDowngrade,
)
.await
.expect("missing DNSKEY result"),
DnssecState::Indeterminate
);
}
struct NoopTransport;
#[async_trait::async_trait]
impl Transport for NoopTransport {
async fn exchange(
&self,
_upstream: &Upstream,
_query: Bytes,
_timeout: Duration,
) -> HResult<(Bytes, Duration)> {
Err(HyperError::AllUpstreamsFailed)
}
}
struct ChainTransport {
root_dnskey: Vec<u8>,
child_ds: Vec<u8>,
child_dnskey: Vec<u8>,
}
#[async_trait::async_trait]
impl Transport for ChainTransport {
async fn exchange(
&self,
_upstream: &Upstream,
query: Bytes,
_timeout: Duration,
) -> HResult<(Bytes, Duration)> {
let question = crate::wire::first_question(&query)
.map_err(|error| HyperError::Wire(error.to_string()))?;
let packet = match (question.name.text(), question.rr_type) {
(".", TYPE_DNSKEY) => self.root_dnskey.clone(),
("example", TYPE_DS) => self.child_ds.clone(),
("example", TYPE_DNSKEY) => self.child_dnskey.clone(),
_ => return Err(HyperError::AllUpstreamsFailed),
};
let mut packet = packet;
crate::wire::rewrite_id(
&mut packet,
crate::wire::Header::parse(&query)
.map_err(|error| HyperError::Wire(error.to_string()))?
.id,
)
.map_err(|error| HyperError::Wire(error.to_string()))?;
Ok((Bytes::from(packet), Duration::from_millis(1)))
}
}
#[tokio::test]
async fn anchored_chain_reaches_secure_without_trusting_ad() {
let qname = name("example.");
let dnskey = dnskey_record();
let packet = response(
0x8000 | 0x0020,
&qname,
TYPE_A,
&[
a_record(60, [192, 0, 2, 1]),
anchored_rrsig_record("example.", TYPE_A, 1, &ANCHORED_A_SIGNATURE),
],
&[
dnskey.clone(),
anchored_rrsig_record("example.", TYPE_DNSKEY, 1, &ANCHORED_DNSKEY_SIGNATURE),
],
&[],
);
let dnskey_rdata = dnskey
.get(dnskey.len().saturating_sub(36)..)
.expect("DNSKEY RDATA")
.to_vec();
let anchors = vec![crate::resolver::PositiveTrustAnchor {
owner: "example".to_owned(),
data: crate::resolver::PositiveTrustAnchorData::Dnskey(dnskey_rdata),
}];
let context = DnssecQueryContext {
transport: Arc::new(NoopTransport),
upstream: Upstream {
id: 1,
addr: "192.0.2.53:53".parse().expect("upstream address"),
transport: TransportKind::Udp,
link_ifindex: 0,
dnssec_capable: true,
sni: None,
doh_url: None,
},
timeout: Duration::from_secs(1),
};
let (_, _, records, _) = crate::wire::parse_sections(&packet).expect("fixture packet");
let key_record = records
.iter()
.find(|record| record.rr_type == TYPE_DNSKEY)
.expect("fixture DNSKEY")
.clone();
let answer_signature = records
.iter()
.find(|record| {
record.rr_type == crate::wire::TYPE_RRSIG
&& crate::wire::parse_rrsig(&packet, record)
.is_ok_and(|signature| signature.type_covered == TYPE_A)
})
.expect("fixture answer signature")
.clone();
let key_signature = records
.iter()
.find(|record| {
record.rr_type == crate::wire::TYPE_RRSIG
&& crate::wire::parse_rrsig(&packet, record)
.is_ok_and(|signature| signature.type_covered == TYPE_DNSKEY)
})
.expect("fixture DNSKEY signature")
.clone();
let answer_rrset = records
.iter()
.filter(|record| record.rr_type == TYPE_A)
.cloned()
.collect::<Vec<_>>();
let key_rrset = records
.iter()
.filter(|record| record.rr_type == TYPE_DNSKEY)
.cloned()
.collect::<Vec<_>>();
assert!(crate::dnssec::verify_rrsig(
&packet,
&answer_signature,
&answer_rrset,
&key_record,
UNIX_EPOCH,
)
.expect("answer signature check"));
assert!(crate::dnssec::verify_rrsig(
&packet,
&key_signature,
&key_rrset,
&key_record,
UNIX_EPOCH,
)
.expect("DNSKEY signature check"));
assert_eq!(
TrustAdValidator::default()
.validate_anchored(&qname, TYPE_A, &packet, DnssecMode::Yes, &anchors, &context,)
.await
.expect("anchored validation"),
DnssecState::Secure
);
let (_, _, records, _) = crate::wire::parse_sections(&packet).expect("fixture records");
let answer = records
.iter()
.find(|record| record.rr_type == TYPE_A)
.expect("fixture answer");
let mut forged_answer = packet.clone();
forged_answer[answer.rdata_offset] ^= 1;
assert!(matches!(
TrustAdValidator::default()
.validate_anchored(
&qname,
TYPE_A,
&forged_answer,
DnssecMode::Yes,
&anchors,
&context,
)
.await,
Err(HyperError::DnssecBogus)
));
let dnskey_record = records
.iter()
.find(|record| record.rr_type == TYPE_DNSKEY)
.expect("fixture DNSKEY");
let mut forged_chain = packet;
forged_chain[dnskey_record.rdata_offset + 4] ^= 1;
assert!(matches!(
TrustAdValidator::default()
.validate_anchored(
&qname,
TYPE_A,
&forged_chain,
DnssecMode::Yes,
&anchors,
&context,
)
.await,
Err(HyperError::DnssecBogus)
));
}
#[tokio::test]
async fn anchored_ds_dnskey_chain_fetches_and_rejects_forged_delegation() {
let qname = name("example.");
let root_dnskey = dnskey_record_for(".");
let child_dnskey = dnskey_record_for("example.");
let mut ds_rdata = vec![0x36, 0xc1, 15, 2];
ds_rdata.extend_from_slice(&[
0x92, 0xca, 0x55, 0x55, 0xa1, 0x55, 0xdf, 0x1f, 0x77, 0x34, 0xf7, 0x93, 0x67, 0x29,
0x0d, 0xee, 0xd1, 0x97, 0x52, 0xd3, 0x25, 0xaa, 0xdf, 0x36, 0x67, 0xc3, 0xcc, 0x24,
0x8f, 0xc5, 0xad, 0xed,
]);
let initial = response(
0x8000 | 0x0020,
&qname,
TYPE_A,
&[
a_record(60, [192, 0, 2, 1]),
anchored_rrsig_record("example.", TYPE_A, 1, &ANCHORED_A_SIGNATURE),
],
&[],
&[],
);
let root_packet = response(
0x8000,
&name("."),
TYPE_DNSKEY,
&[
root_dnskey.clone(),
anchored_rrsig_record_with_signer(
".",
TYPE_DNSKEY,
0,
".",
&ANCHORED_ROOT_DNSKEY_SIGNATURE,
),
],
&[],
&[],
);
let ds_packet = response(
0x8000,
&name("example."),
TYPE_DS,
&[
record_wire("example.", TYPE_DS, 60, &ds_rdata),
anchored_rrsig_record_with_signer(
"example.",
TYPE_DS,
1,
".",
&ANCHORED_DS_SIGNATURE,
),
],
&[],
&[],
);
let child_key_packet = response(
0x8000,
&name("example."),
TYPE_DNSKEY,
&[
child_dnskey,
anchored_rrsig_record("example.", TYPE_DNSKEY, 1, &ANCHORED_DNSKEY_SIGNATURE),
],
&[],
&[],
);
let transport = Arc::new(ChainTransport {
root_dnskey: root_packet,
child_ds: ds_packet,
child_dnskey: child_key_packet,
});
let context = DnssecQueryContext {
transport,
upstream: Upstream {
id: 1,
addr: "192.0.2.53:53".parse().expect("upstream address"),
transport: TransportKind::Udp,
link_ifindex: 0,
dnssec_capable: true,
sni: None,
doh_url: None,
},
timeout: Duration::from_secs(1),
};
let anchors = vec![crate::resolver::PositiveTrustAnchor {
owner: ".".to_owned(),
data: crate::resolver::PositiveTrustAnchorData::Ds {
key_tag: 14_017,
algorithm: 15,
digest_type: 2,
digest: vec![
0x72, 0x60, 0x33, 0xbd, 0x66, 0x46, 0xd6, 0x29, 0x93, 0xb4, 0x10, 0x77, 0x18,
0x86, 0xc0, 0x61, 0x68, 0xc2, 0x02, 0xc2, 0x36, 0xc9, 0x30, 0xe1, 0x28, 0x94,
0xae, 0x28, 0x4a, 0xfd, 0x7f, 0xaa,
],
},
}];
assert_eq!(
TrustAdValidator::default()
.validate_anchored(
&qname,
TYPE_A,
&initial,
DnssecMode::Yes,
&anchors,
&context,
)
.await
.expect("fetched DS/DNSKEY chain"),
DnssecState::Secure
);
let mut forged_ds = ds_rdata;
forged_ds[4] ^= 1;
let forged_packet = response(
0x8000,
&name("example."),
TYPE_DS,
&[
record_wire("example.", TYPE_DS, 60, &forged_ds),
anchored_rrsig_record_with_signer(
"example.",
TYPE_DS,
1,
".",
&ANCHORED_DS_SIGNATURE,
),
],
&[],
&[],
);
let forged_transport = Arc::new(ChainTransport {
root_dnskey: response(
0x8000,
&name("."),
TYPE_DNSKEY,
&[
root_dnskey.clone(),
anchored_rrsig_record_with_signer(
".",
TYPE_DNSKEY,
0,
".",
&ANCHORED_ROOT_DNSKEY_SIGNATURE,
),
],
&[],
&[],
),
child_ds: forged_packet,
child_dnskey: response(
0x8000,
&name("example."),
TYPE_DNSKEY,
&[
dnskey_record_for("example."),
anchored_rrsig_record("example.", TYPE_DNSKEY, 1, &ANCHORED_DNSKEY_SIGNATURE),
],
&[],
&[],
),
});
let forged_context = DnssecQueryContext {
transport: forged_transport,
..context
};
assert!(matches!(
TrustAdValidator::default()
.validate_anchored(
&qname,
TYPE_A,
&initial,
DnssecMode::Yes,
&anchors,
&forged_context,
)
.await,
Err(HyperError::DnssecBogus)
));
}
#[tokio::test]
async fn anchored_auxiliary_lookup_failure_stays_indeterminate() {
let qname = name("example.");
let packet = response(
0x8000 | 0x0020,
&qname,
TYPE_A,
&[
a_record(60, [192, 0, 2, 1]),
anchored_rrsig_record("example.", TYPE_A, 1, &ANCHORED_A_SIGNATURE),
],
&[],
&[],
);
let key = dnskey_record();
let anchors = vec![crate::resolver::PositiveTrustAnchor {
owner: "example".to_owned(),
data: crate::resolver::PositiveTrustAnchorData::Dnskey(
key[key.len().saturating_sub(36)..].to_vec(),
),
}];
let context = DnssecQueryContext {
transport: Arc::new(NoopTransport),
upstream: Upstream {
id: 1,
addr: "192.0.2.53:53".parse().expect("upstream address"),
transport: TransportKind::Udp,
link_ifindex: 0,
dnssec_capable: true,
sni: None,
doh_url: None,
},
timeout: Duration::from_secs(1),
};
assert_eq!(
TrustAdValidator::default()
.validate_anchored(&qname, TYPE_A, &packet, DnssecMode::Yes, &anchors, &context,)
.await
.expect("transport failure is indeterminate"),
DnssecState::Indeterminate
);
}
#[tokio::test]
async fn anchored_nsec_proves_nxdomain_and_missing_proof_is_bogus() {
let qname = name("missing.example.");
let dnskey = dnskey_record();
let example_nsec = nsec_record("example.", "a.example.", &[6, 46, 47]);
let a_nsec = nsec_record("a.example.", "z.example.", &[1, 46, 47]);
let packet = response(
0x8000 | 0x0003,
&qname,
TYPE_A,
&[],
&[
example_nsec,
anchored_rrsig_record(
"example.",
crate::wire::TYPE_NSEC,
1,
&ANCHORED_EXAMPLE_NSEC_SIGNATURE,
),
a_nsec,
anchored_rrsig_record(
"a.example.",
crate::wire::TYPE_NSEC,
2,
&ANCHORED_A_NSEC_SIGNATURE,
),
dnskey.clone(),
anchored_rrsig_record("example.", TYPE_DNSKEY, 1, &ANCHORED_DNSKEY_SIGNATURE),
],
&[],
);
let dnskey_rdata = dnskey
.get(dnskey.len().saturating_sub(36)..)
.expect("DNSKEY RDATA")
.to_vec();
let anchors = vec![crate::resolver::PositiveTrustAnchor {
owner: "example".to_owned(),
data: crate::resolver::PositiveTrustAnchorData::Dnskey(dnskey_rdata),
}];
let context = DnssecQueryContext {
transport: Arc::new(NoopTransport),
upstream: Upstream {
id: 1,
addr: "192.0.2.53:53".parse().expect("upstream address"),
transport: TransportKind::Udp,
link_ifindex: 0,
dnssec_capable: true,
sni: None,
doh_url: None,
},
timeout: Duration::from_secs(1),
};
assert_eq!(
TrustAdValidator::default()
.validate_anchored(&qname, TYPE_A, &packet, DnssecMode::Yes, &anchors, &context,)
.await
.expect("authenticated NXDOMAIN"),
DnssecState::Secure
);
let (_, _, records, _) = crate::wire::parse_sections(&packet).expect("NSEC records");
let missing_proof = records
.iter()
.filter(|record| {
record.rr_type != crate::wire::TYPE_NSEC
&& record.rr_type != crate::wire::TYPE_RRSIG
|| record.name.text() != "a.example"
})
.map(|record| {
let mut raw = crate::wire::encode_name(record.name.text()).expect("record name");
raw.extend_from_slice(&record.rr_type.to_be_bytes());
raw.extend_from_slice(&record.class.to_be_bytes());
raw.extend_from_slice(&record.ttl.to_be_bytes());
raw.extend_from_slice(
&u16::try_from(record.rdata.len())
.expect("RDATA length")
.to_be_bytes(),
);
raw.extend_from_slice(&record.rdata);
raw
})
.collect::<Vec<_>>();
let missing_packet = response(0x8000 | 0x0003, &qname, TYPE_A, &[], &missing_proof, &[]);
assert!(matches!(
TrustAdValidator::default()
.validate_anchored(
&qname,
TYPE_A,
&missing_packet,
DnssecMode::Yes,
&anchors,
&context,
)
.await,
Err(HyperError::DnssecBogus)
));
}
}