refactor: actually parse upstream response by using AllRecordData
* UnknownRecordData does not know how to handle all record types properly, which resulted in occasionally corrupted responses. * Let's change everything to AllRecordData and actually parse the record at our side. * There is no simple function to convert AllRecordData to owned, so we need a few helper functions in the util module.
This commit is contained in:
parent
e2cc8c4440
commit
74b117bc9f
18
src/cache.rs
18
src/cache.rs
|
@ -1,5 +1,6 @@
|
||||||
use crate::kv;
|
use crate::kv;
|
||||||
use domain::base::{rdata::UnknownRecordData, Dname, Question, Record};
|
use crate::util::OwnedRecordData;
|
||||||
|
use domain::base::{Dname, Question, Record};
|
||||||
use js_sys::Date;
|
use js_sys::Date;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -22,13 +23,14 @@ impl DnsCache {
|
||||||
|
|
||||||
pub async fn put_cache(
|
pub async fn put_cache(
|
||||||
&self,
|
&self,
|
||||||
record: &Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>,
|
record: &Record<Dname<Vec<u8>>, OwnedRecordData>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let ttl = record.ttl();
|
let ttl = record.ttl();
|
||||||
|
let data = crate::util::owned_record_data_to_buffer(record.data())?;
|
||||||
self.store
|
self.store
|
||||||
.put_buf_ttl_metadata(
|
.put_buf_ttl_metadata(
|
||||||
&Self::record_to_key(record),
|
&Self::record_to_key(record, &data),
|
||||||
record.data().data(),
|
&data,
|
||||||
ttl as u64,
|
ttl as u64,
|
||||||
DnsCacheMetadata {
|
DnsCacheMetadata {
|
||||||
created_ts: (Date::now() / 1000f64) as u64,
|
created_ts: (Date::now() / 1000f64) as u64,
|
||||||
|
@ -41,7 +43,7 @@ impl DnsCache {
|
||||||
pub async fn get_cache(
|
pub async fn get_cache(
|
||||||
&self,
|
&self,
|
||||||
question: &Question<Dname<Vec<u8>>>,
|
question: &Question<Dname<Vec<u8>>>,
|
||||||
) -> Option<Vec<Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>>> {
|
) -> Option<Vec<Record<Dname<Vec<u8>>, OwnedRecordData>>> {
|
||||||
// One question can have multiple cached records; so we list by prefix
|
// One question can have multiple cached records; so we list by prefix
|
||||||
// Note that list_prefix returns 1000 records at maximum by default
|
// Note that list_prefix returns 1000 records at maximum by default
|
||||||
// We don't expect one question to have that many answers, so it
|
// We don't expect one question to have that many answers, so it
|
||||||
|
@ -80,14 +82,14 @@ impl DnsCache {
|
||||||
question.qname().to_owned(),
|
question.qname().to_owned(),
|
||||||
question.qclass(),
|
question.qclass(),
|
||||||
remaining_ttl as u32,
|
remaining_ttl as u32,
|
||||||
UnknownRecordData::from_octets(question.qtype(), value),
|
crate::util::octets_to_owned_record_data(question.qtype(), &value).ok()?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(ret)
|
Some(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_to_key(record: &Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>) -> String {
|
fn record_to_key(record: &Record<Dname<Vec<u8>>, OwnedRecordData>, buf: &[u8]) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{};{};{};{}",
|
"{};{};{};{}",
|
||||||
record.owner(),
|
record.owner(),
|
||||||
|
@ -96,7 +98,7 @@ impl DnsCache {
|
||||||
// We need to append the hash of the record data to the key
|
// We need to append the hash of the record data to the key
|
||||||
// because one question might have multiple answers
|
// because one question might have multiple answers
|
||||||
// When reading, we need to list the keys first
|
// When reading, we need to list the keys first
|
||||||
crate::util::hash_buf(record.data().data())
|
crate::util::hash_buf(buf)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::cache::DnsCache;
|
use crate::cache::DnsCache;
|
||||||
use crate::r#override::OverrideResolver;
|
use crate::r#override::OverrideResolver;
|
||||||
|
use crate::util::OwnedRecordData;
|
||||||
use domain::base::{
|
use domain::base::{
|
||||||
iana::{Opcode, Rcode},
|
iana::{Opcode, Rcode},
|
||||||
rdata::UnknownRecordData,
|
|
||||||
Dname, Message, MessageBuilder, ParsedDname, Question, Record, ToDname,
|
Dname, Message, MessageBuilder, ParsedDname, Question, Record, ToDname,
|
||||||
};
|
};
|
||||||
|
use domain::rdata::AllRecordData;
|
||||||
use js_sys::{ArrayBuffer, Uint8Array};
|
use js_sys::{ArrayBuffer, Uint8Array};
|
||||||
use wasm_bindgen_futures::JsFuture;
|
use wasm_bindgen_futures::JsFuture;
|
||||||
use web_sys::{Headers, Request, RequestInit, Response};
|
use web_sys::{Headers, Request, RequestInit, Response};
|
||||||
|
@ -28,7 +29,7 @@ impl Client {
|
||||||
pub async fn query(
|
pub async fn query(
|
||||||
&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>>, OwnedRecordData>>, String> {
|
||||||
// Attempt to answer locally first
|
// Attempt to answer locally first
|
||||||
let (mut local_answers, questions) = self.try_answer_from_local(questions).await;
|
let (mut local_answers, questions) = self.try_answer_from_local(questions).await;
|
||||||
if questions.len() == 0 {
|
if questions.len() == 0 {
|
||||||
|
@ -59,7 +60,7 @@ impl Client {
|
||||||
&self,
|
&self,
|
||||||
questions: Vec<Question<Dname<Vec<u8>>>>,
|
questions: Vec<Question<Dname<Vec<u8>>>>,
|
||||||
retries: usize,
|
retries: usize,
|
||||||
) -> Result<Vec<Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>>, String> {
|
) -> Result<Vec<Record<Dname<Vec<u8>>, OwnedRecordData>>, String> {
|
||||||
let mut last_res = Err("Dummy".to_string());
|
let mut last_res = Err("Dummy".to_string());
|
||||||
for _ in 0..retries {
|
for _ in 0..retries {
|
||||||
last_res = self.query(questions.clone()).await;
|
last_res = self.query(questions.clone()).await;
|
||||||
|
@ -140,7 +141,7 @@ impl Client {
|
||||||
|
|
||||||
fn extract_answers(
|
fn extract_answers(
|
||||||
msg: Message<Vec<u8>>,
|
msg: Message<Vec<u8>>,
|
||||||
) -> Result<Vec<Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>>, String> {
|
) -> Result<Vec<Record<Dname<Vec<u8>>, OwnedRecordData>>, String> {
|
||||||
let answer_section = msg
|
let answer_section = msg
|
||||||
.answer()
|
.answer()
|
||||||
.map_err(|_| "Failed to parse DNS answer from upstream".to_string())?;
|
.map_err(|_| "Failed to parse DNS answer from upstream".to_string())?;
|
||||||
|
@ -149,18 +150,19 @@ impl Client {
|
||||||
// this is different from the server impl
|
// this is different from the server impl
|
||||||
let answers: Vec<_> = answer_section.collect();
|
let answers: Vec<_> = answer_section.collect();
|
||||||
|
|
||||||
let mut ret: Vec<Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>> = Vec::new();
|
let mut ret: Vec<Record<Dname<Vec<u8>>, OwnedRecordData>> = Vec::new();
|
||||||
for a in answers {
|
for a in answers {
|
||||||
let parsed_record = a.map_err(|_| "Failed to parse DNS answer record".to_string())?;
|
let parsed_record = a.map_err(|_| "Failed to parse DNS answer record".to_string())?;
|
||||||
// Use UnknownRecordData here because we don't really care about the actual type of the record
|
// Actually parse the record
|
||||||
// It saves time and saves sanity (because of the type signature of AllRecordData)
|
// Note that we cannot just use UnknownRecordData here and not parse it;
|
||||||
let record: Record<ParsedDname<&Vec<u8>>, UnknownRecordData<&[u8]>> = parsed_record
|
// it does not know how to parse all types of records correctly, which
|
||||||
.to_record()
|
// could corrupt the actual record data
|
||||||
.map_err(|_| "Cannot parse record".to_string())?
|
let record: Record<ParsedDname<&Vec<u8>>, AllRecordData<&[u8], ParsedDname<&Vec<u8>>>> =
|
||||||
.ok_or("Cannot parse record".to_string())?;
|
parsed_record
|
||||||
// Convert everything to owned for sanity in type signature...
|
.to_record()
|
||||||
// We'll need to do a copy before returning outside of the main
|
.map_err(|_| "Cannot parse record".to_string())?
|
||||||
// query function anyway
|
.ok_or("Cannot parse record".to_string())?;
|
||||||
|
// Convert the record to owned for sanity in type signature
|
||||||
let owned_record = Record::new(
|
let owned_record = Record::new(
|
||||||
record
|
record
|
||||||
.owner()
|
.owner()
|
||||||
|
@ -168,10 +170,12 @@ impl Client {
|
||||||
.map_err(|_| "Failed to parse Dname".to_string())?,
|
.map_err(|_| "Failed to parse Dname".to_string())?,
|
||||||
record.class(),
|
record.class(),
|
||||||
record.ttl(),
|
record.ttl(),
|
||||||
UnknownRecordData::from_octets(
|
match crate::util::to_owned_record_data(record.data()) {
|
||||||
record.data().rtype(),
|
Ok(data) => data,
|
||||||
record.data().data().to_vec(),
|
// If this fails, it means that our resolver doesn't support the type yet
|
||||||
),
|
// so just skip this record
|
||||||
|
Err(_) => continue,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
ret.push(owned_record);
|
ret.push(owned_record);
|
||||||
}
|
}
|
||||||
|
@ -185,7 +189,7 @@ impl Client {
|
||||||
&self,
|
&self,
|
||||||
questions: Vec<Question<Dname<Vec<u8>>>>,
|
questions: Vec<Question<Dname<Vec<u8>>>>,
|
||||||
) -> (
|
) -> (
|
||||||
Vec<Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>>,
|
Vec<Record<Dname<Vec<u8>>, OwnedRecordData>>,
|
||||||
Vec<Question<Dname<Vec<u8>>>>,
|
Vec<Question<Dname<Vec<u8>>>>,
|
||||||
) {
|
) {
|
||||||
let mut answers = Vec::new();
|
let mut answers = Vec::new();
|
||||||
|
@ -206,7 +210,7 @@ impl Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
async fn cache_answers(&self, answers: &[Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>]) {
|
async fn cache_answers(&self, answers: &[Record<Dname<Vec<u8>>, OwnedRecordData>]) {
|
||||||
for a in answers {
|
for a in answers {
|
||||||
// Ignore error -- we don't really care
|
// Ignore error -- we don't really care
|
||||||
self.cache.put_cache(a).await;
|
self.cache.put_cache(a).await;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::trie_map::TrieMap;
|
use crate::trie_map::TrieMap;
|
||||||
use domain::base::{rdata::UnknownRecordData, Compose, Dname, Question, Record, Rtype};
|
use crate::util::OwnedRecordData;
|
||||||
|
use domain::base::{Dname, Question, Record, Rtype};
|
||||||
use domain::rdata::{Aaaa, AllRecordData, A};
|
use domain::rdata::{Aaaa, AllRecordData, A};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
@ -74,7 +75,7 @@ impl OverrideResolver {
|
||||||
pub fn try_resolve(
|
pub fn try_resolve(
|
||||||
&self,
|
&self,
|
||||||
question: &Question<Dname<Vec<u8>>>,
|
question: &Question<Dname<Vec<u8>>>,
|
||||||
) -> Option<Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>> {
|
) -> Option<Record<Dname<Vec<u8>>, OwnedRecordData>> {
|
||||||
match question.qtype() {
|
match question.qtype() {
|
||||||
// We only handle resolution of IP addresses
|
// We only handle resolution of IP addresses
|
||||||
Rtype::A | Rtype::A6 | Rtype::Aaaa | Rtype::Cname | Rtype::Any => (),
|
Rtype::A | Rtype::A6 | Rtype::Aaaa | Rtype::Cname | Rtype::Any => (),
|
||||||
|
@ -101,21 +102,17 @@ impl OverrideResolver {
|
||||||
&self,
|
&self,
|
||||||
question: &Question<Dname<Vec<u8>>>,
|
question: &Question<Dname<Vec<u8>>>,
|
||||||
addr: &IpAddr,
|
addr: &IpAddr,
|
||||||
) -> Option<Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>> {
|
) -> Option<Record<Dname<Vec<u8>>, OwnedRecordData>> {
|
||||||
let (rtype, rdata): (_, AllRecordData<Vec<u8>, Dname<Vec<u8>>>) = match addr {
|
let rdata: OwnedRecordData = match addr {
|
||||||
IpAddr::V4(addr) => (Rtype::A, AllRecordData::A(A::new(addr.clone()))),
|
IpAddr::V4(addr) => AllRecordData::A(A::new(addr.clone())),
|
||||||
IpAddr::V6(addr) => (Rtype::Aaaa, AllRecordData::Aaaa(Aaaa::new(addr.clone()))),
|
IpAddr::V6(addr) => AllRecordData::Aaaa(Aaaa::new(addr.clone())),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convert AllRecordData to UnknownRecordData to match the type signature
|
|
||||||
// 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(
|
let record = Record::new(
|
||||||
question.qname().clone(),
|
question.qname().clone(),
|
||||||
question.qclass(),
|
question.qclass(),
|
||||||
self.override_ttl,
|
self.override_ttl,
|
||||||
UnknownRecordData::from_octets(rtype, rdata_buf),
|
rdata,
|
||||||
);
|
);
|
||||||
return Some(record);
|
return Some(record);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ use crate::r#override::OverrideResolver;
|
||||||
use async_static::async_static;
|
use async_static::async_static;
|
||||||
use domain::base::{
|
use domain::base::{
|
||||||
iana::{Opcode, Rcode},
|
iana::{Opcode, Rcode},
|
||||||
rdata::UnknownRecordData,
|
record::AsRecord,
|
||||||
Dname, Message, MessageBuilder, Question, Record, ToDname,
|
Dname, Message, MessageBuilder, Question, ToDname,
|
||||||
};
|
};
|
||||||
use js_sys::{ArrayBuffer, Uint8Array};
|
use js_sys::{ArrayBuffer, Uint8Array};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -204,7 +204,7 @@ impl Server {
|
||||||
fn build_answer_wireformat(
|
fn build_answer_wireformat(
|
||||||
id: u16,
|
id: u16,
|
||||||
questions: Vec<Question<Dname<Vec<u8>>>>,
|
questions: Vec<Question<Dname<Vec<u8>>>>,
|
||||||
records: Vec<Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>>,
|
records: Vec<impl AsRecord>,
|
||||||
) -> Result<Message<Vec<u8>>, String> {
|
) -> Result<Message<Vec<u8>>, String> {
|
||||||
let mut message_builder = MessageBuilder::new_vec();
|
let mut message_builder = MessageBuilder::new_vec();
|
||||||
// Set up the response header
|
// Set up the response header
|
||||||
|
|
42
src/util.rs
42
src/util.rs
|
@ -1,4 +1,7 @@
|
||||||
use domain::base::Message;
|
use domain::base::{
|
||||||
|
octets::Parser, rdata::ParseRecordData, Compose, Dname, Message, ParsedDname, Rtype, ToDname,
|
||||||
|
};
|
||||||
|
use domain::rdata::{AllRecordData, Cname};
|
||||||
use js_sys::{Math, Promise};
|
use js_sys::{Math, Promise};
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
use std::{collections::hash_map::DefaultHasher, hash::Hasher};
|
use std::{collections::hash_map::DefaultHasher, hash::Hasher};
|
||||||
|
@ -58,3 +61,40 @@ pub fn hash_buf(buf: &[u8]) -> u64 {
|
||||||
hasher.write(buf);
|
hasher.write(buf);
|
||||||
hasher.finish()
|
hasher.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shorthand for a fully-owned AllRecordData variant
|
||||||
|
pub type OwnedRecordData = AllRecordData<Vec<u8>, Dname<Vec<u8>>>;
|
||||||
|
|
||||||
|
// Convert a parsed AllRecordData instance to owned
|
||||||
|
pub fn to_owned_record_data<T: AsRef<[u8]>, U: AsRef<[u8]>>(
|
||||||
|
data: &AllRecordData<T, ParsedDname<U>>,
|
||||||
|
) -> Result<OwnedRecordData, String> {
|
||||||
|
match data {
|
||||||
|
AllRecordData::A(data) => Ok(AllRecordData::A(data.clone())),
|
||||||
|
AllRecordData::Aaaa(data) => Ok(AllRecordData::Aaaa(data.clone())),
|
||||||
|
AllRecordData::Cname(data) => Ok(AllRecordData::Cname(Cname::new(
|
||||||
|
data.cname()
|
||||||
|
.to_dname()
|
||||||
|
.map_err(|_| "Failed parsing CNAME".to_string())?,
|
||||||
|
))),
|
||||||
|
// TODO: Fill all of these in
|
||||||
|
_ => Err("Unsupported record type".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert owned record data to Vec buffer
|
||||||
|
pub fn owned_record_data_to_buffer(data: &OwnedRecordData) -> Result<Vec<u8>, String> {
|
||||||
|
let mut ret: Vec<u8> = Vec::new();
|
||||||
|
data.compose(&mut ret)
|
||||||
|
.map_err(|_| "Cannot convert owned record data to buffer".to_string())?;
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse record data buffer and convert to owned record data
|
||||||
|
pub fn octets_to_owned_record_data(rtype: Rtype, octets: &[u8]) -> Result<OwnedRecordData, String> {
|
||||||
|
let parsed: AllRecordData<&[u8], ParsedDname<&[u8]>> =
|
||||||
|
ParseRecordData::parse_data(rtype, &mut Parser::from_ref(octets))
|
||||||
|
.map_err(|_| "Cannot parse given record data".to_string())?
|
||||||
|
.ok_or("Given record data parsed to nothing".to_string())?;
|
||||||
|
to_owned_record_data(&parsed)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue