#![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};
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>;
}
pub struct TrustAdValidator;
#[async_trait::async_trait]
impl DnssecValidator for TrustAdValidator {
async fn validate(
&self,
_qname: &NameKey,
_qtype: u16,
packet: &PacketView,
mode: DnssecMode,
) -> HResult<DnssecState> {
match mode {
DnssecMode::No => Ok(DnssecState::Insecure),
DnssecMode::AllowDowngrade => {
if packet.ad() {
Ok(DnssecState::Secure)
} else {
Ok(DnssecState::Insecure)
}
}
DnssecMode::Yes => {
if packet.ad() {
Ok(DnssecState::Secure)
} else if packet.rcode() == RCODE_SERVFAIL {
Err(HyperError::DnssecBogus)
} else {
Ok(DnssecState::Indeterminate)
}
}
}
}
}
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 _ = (up, rtt);
let ab = self.arena.copy_from(pv.bytes())?;
let pv = PacketView::new(ab)?;
let state = self
.dnssec
.validate(¤t, key.qtype, &pv, self.cfg.dnssec)
.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
}