initial implementation of local override
This commit is contained in:
parent
fef165aca8
commit
78a971908b
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue