initial implementation of local override

This commit is contained in:
Peter Cai 2021-04-05 13:48:19 +08:00
parent fef165aca8
commit 78a971908b
4 changed files with 115 additions and 14 deletions

View File

@ -1,4 +1,5 @@
use crate::cache::DnsCache; use crate::cache::DnsCache;
use crate::r#override::OverrideResolver;
use domain::base::iana::{Opcode, Rcode}; use domain::base::iana::{Opcode, Rcode};
use domain::base::message::Message; use domain::base::message::Message;
use domain::base::message_builder::MessageBuilder; use domain::base::message_builder::MessageBuilder;
@ -18,13 +19,15 @@ pub struct ClientOptions {
pub struct Client { pub struct Client {
options: ClientOptions, options: ClientOptions,
cache: DnsCache, cache: DnsCache,
override_resolver: OverrideResolver,
} }
impl Client { impl Client {
pub fn new(options: ClientOptions) -> Client { pub fn new(options: ClientOptions, override_resolver: OverrideResolver) -> Client {
Client { Client {
options, options,
cache: DnsCache::new(), cache: DnsCache::new(),
override_resolver,
} }
} }
@ -32,11 +35,11 @@ impl Client {
&self, &self,
questions: Vec<Question<Dname<Vec<u8>>>>, questions: Vec<Question<Dname<Vec<u8>>>>,
) -> Result<Vec<Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>>, String> { ) -> Result<Vec<Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>>, String> {
// Attempt to read from cache first // Attempt to answer locally first
let (mut cached_answers, questions) = self.try_answer_from_cache(questions).await; let (mut local_answers, questions) = self.try_answer_from_local(questions).await;
if questions.len() == 0 { if questions.len() == 0 {
// No remaining questions to be handled. Return directly. // No remaining questions to be handled. Return directly.
return Ok(cached_answers); return Ok(local_answers);
} }
let msg = Self::build_query(questions)?; let msg = Self::build_query(questions)?;
@ -48,7 +51,7 @@ impl Client {
let mut ret = Self::extract_answers(resp)?; let mut ret = Self::extract_answers(resp)?;
self.cache_answers(&ret).await; self.cache_answers(&ret).await;
// Concatenate the cached answers we retrived previously with the newly-fetched answers // Concatenate the cached answers we retrived previously with the newly-fetched answers
ret.append(&mut cached_answers); ret.append(&mut local_answers);
Ok(ret) Ok(ret)
} }
// NXDOMAIN is not an error we want to retry / panic upon // NXDOMAIN is not an error we want to retry / panic upon
@ -181,10 +184,10 @@ impl Client {
Ok(ret) Ok(ret)
} }
// Try to answer the questions as much as we can from the cache // Try to answer the questions as much as we can from the cache / override map
// returns the available answers, and the remaining questions that cannot be // returns the available answers, and the remaining questions that cannot be
// answered from cache // answered from cache or the override resolver
async fn try_answer_from_cache( async fn try_answer_from_local(
&self, &self,
questions: Vec<Question<Dname<Vec<u8>>>>, questions: Vec<Question<Dname<Vec<u8>>>>,
) -> ( ) -> (
@ -194,9 +197,15 @@ impl Client {
let mut answers = Vec::new(); let mut answers = Vec::new();
let mut remaining = Vec::new(); let mut remaining = Vec::new();
for q in questions { for q in questions {
match self.cache.get_cache(&q).await { if let Some(ans) = self.override_resolver.try_resolve(&q) {
Some(mut ans) => answers.append(&mut ans), // Try to resolve from override map first
None => remaining.push(q), answers.push(ans);
} else if let Some(mut ans) = self.cache.get_cache(&q).await {
// Then try cache
answers.append(&mut ans);
} else {
// If both failed, resolve via upstream
remaining.push(q);
} }
} }
(answers, remaining) (answers, remaining)

View File

@ -1,6 +1,7 @@
mod cache; mod cache;
mod client; mod client;
mod kv; mod kv;
mod r#override;
mod server; mod server;
mod util; mod util;

82
src/override.rs Normal file
View File

@ -0,0 +1,82 @@
use domain::base::rdata::UnknownRecordData;
use domain::base::{question::Question, Compose};
use domain::base::{Dname, Record, Rtype};
use domain::rdata::{Aaaa, AllRecordData, A};
use std::collections::HashMap;
use std::net::IpAddr;
pub struct OverrideResolver {
simple_matches: HashMap<String, IpAddr>,
override_ttl: u32,
}
impl OverrideResolver {
pub fn new(overrides: HashMap<String, String>, override_ttl: u32) -> OverrideResolver {
OverrideResolver {
simple_matches: Self::build_simple_match_table(overrides),
override_ttl,
}
}
fn build_simple_match_table(overrides: HashMap<String, String>) -> HashMap<String, IpAddr> {
let mut ret = HashMap::new();
for (k, v) in overrides.into_iter() {
match v.parse::<IpAddr>() {
Ok(addr) => {
ret.insert(k, addr);
}
// Ignore malformed IP addresses
Err(_) => continue,
}
}
return ret;
}
pub fn try_resolve(
&self,
question: &Question<Dname<Vec<u8>>>,
) -> Option<Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>> {
match question.qtype() {
// We only handle resolution of IP addresses
Rtype::A | Rtype::A6 | Rtype::Aaaa | Rtype::Cname | Rtype::Any => (),
// So if the question is anything else, just skip
_ => return None,
}
let name = question.qname().to_string();
if let Some(addr) = self.simple_matches.get(&name) {
self.respond_with_addr(question, addr)
} else {
None
}
}
fn respond_with_addr(
&self,
question: &Question<Dname<Vec<u8>>>,
addr: &IpAddr,
) -> Option<Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>> {
let (rtype, rdata): (_, AllRecordData<Vec<u8>, Dname<Vec<u8>>>) = match addr {
IpAddr::V4(addr) => (Rtype::A, AllRecordData::A(A::new(addr.clone()))),
IpAddr::V6(addr) => (Rtype::Aaaa, AllRecordData::Aaaa(Aaaa::new(addr.clone()))),
};
let qtype = question.qtype();
if qtype == Rtype::Any || qtype == rtype {
// Convert AllRecordData to UnknownRecordData to match the type
// since our resolver client doesn't really care about the actual type
let mut rdata_buf: Vec<u8> = Vec::new();
rdata.compose(&mut rdata_buf).ok()?;
let record = Record::new(
question.qname().clone(),
question.qclass(),
self.override_ttl,
UnknownRecordData::from_octets(rtype, rdata_buf),
);
return Some(record);
} else {
// If the response and query types don't match, just return none
return None;
}
}
}

View File

@ -1,4 +1,5 @@
use crate::client::*; use crate::client::*;
use crate::r#override::OverrideResolver;
use async_static::async_static; use async_static::async_static;
use domain::base::iana::{Opcode, Rcode}; use domain::base::iana::{Opcode, Rcode};
use domain::base::message::Message; use domain::base::message::Message;
@ -10,6 +11,7 @@ use domain::base::{Dname, ToDname};
use js_sys::{ArrayBuffer, Uint8Array}; use js_sys::{ArrayBuffer, Uint8Array};
use serde::Deserialize; use serde::Deserialize;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::HashMap;
use wasm_bindgen_futures::JsFuture; use wasm_bindgen_futures::JsFuture;
use web_sys::*; use web_sys::*;
@ -43,6 +45,10 @@ enum DnsResponseFormat {
pub struct ServerOptions { pub struct ServerOptions {
upstream_urls: Vec<String>, upstream_urls: Vec<String>,
retries: usize, retries: usize,
#[serde(default)]
overrides: HashMap<String, String>,
#[serde(default)]
override_ttl: u32,
} }
pub struct Server { pub struct Server {
@ -53,9 +59,12 @@ pub struct Server {
impl Server { impl Server {
fn new(options: ServerOptions) -> Server { fn new(options: ServerOptions) -> Server {
Server { Server {
client: Client::new(ClientOptions { client: Client::new(
upstream_urls: options.upstream_urls.clone(), ClientOptions {
}), upstream_urls: options.upstream_urls.clone(),
},
OverrideResolver::new(options.overrides.clone(), options.override_ttl),
),
options, options,
} }
} }