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 domain::base::{rdata::UnknownRecordData, Dname, Question, Record};
|
||||
use crate::util::OwnedRecordData;
|
||||
use domain::base::{Dname, Question, Record};
|
||||
use js_sys::Date;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -22,13 +23,14 @@ impl DnsCache {
|
|||
|
||||
pub async fn put_cache(
|
||||
&self,
|
||||
record: &Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>,
|
||||
record: &Record<Dname<Vec<u8>>, OwnedRecordData>,
|
||||
) -> Result<(), String> {
|
||||
let ttl = record.ttl();
|
||||
let data = crate::util::owned_record_data_to_buffer(record.data())?;
|
||||
self.store
|
||||
.put_buf_ttl_metadata(
|
||||
&Self::record_to_key(record),
|
||||
record.data().data(),
|
||||
&Self::record_to_key(record, &data),
|
||||
&data,
|
||||
ttl as u64,
|
||||
DnsCacheMetadata {
|
||||
created_ts: (Date::now() / 1000f64) as u64,
|
||||
|
@ -41,7 +43,7 @@ impl DnsCache {
|
|||
pub async fn get_cache(
|
||||
&self,
|
||||
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
|
||||
// Note that list_prefix returns 1000 records at maximum by default
|
||||
// We don't expect one question to have that many answers, so it
|
||||
|
@ -80,14 +82,14 @@ impl DnsCache {
|
|||
question.qname().to_owned(),
|
||||
question.qclass(),
|
||||
remaining_ttl as u32,
|
||||
UnknownRecordData::from_octets(question.qtype(), value),
|
||||
crate::util::octets_to_owned_record_data(question.qtype(), &value).ok()?,
|
||||
));
|
||||
}
|
||||
|
||||
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!(
|
||||
"{};{};{};{}",
|
||||
record.owner(),
|
||||
|
@ -96,7 +98,7 @@ impl DnsCache {
|
|||
// We need to append the hash of the record data to the key
|
||||
// because one question might have multiple answers
|
||||
// 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::r#override::OverrideResolver;
|
||||
use crate::util::OwnedRecordData;
|
||||
use domain::base::{
|
||||
iana::{Opcode, Rcode},
|
||||
rdata::UnknownRecordData,
|
||||
Dname, Message, MessageBuilder, ParsedDname, Question, Record, ToDname,
|
||||
};
|
||||
use domain::rdata::AllRecordData;
|
||||
use js_sys::{ArrayBuffer, Uint8Array};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{Headers, Request, RequestInit, Response};
|
||||
|
@ -28,7 +29,7 @@ impl Client {
|
|||
pub async fn query(
|
||||
&self,
|
||||
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
|
||||
let (mut local_answers, questions) = self.try_answer_from_local(questions).await;
|
||||
if questions.len() == 0 {
|
||||
|
@ -59,7 +60,7 @@ impl Client {
|
|||
&self,
|
||||
questions: Vec<Question<Dname<Vec<u8>>>>,
|
||||
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());
|
||||
for _ in 0..retries {
|
||||
last_res = self.query(questions.clone()).await;
|
||||
|
@ -140,7 +141,7 @@ impl Client {
|
|||
|
||||
fn extract_answers(
|
||||
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
|
||||
.answer()
|
||||
.map_err(|_| "Failed to parse DNS answer from upstream".to_string())?;
|
||||
|
@ -149,18 +150,19 @@ impl Client {
|
|||
// this is different from the server impl
|
||||
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 {
|
||||
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
|
||||
// It saves time and saves sanity (because of the type signature of AllRecordData)
|
||||
let record: Record<ParsedDname<&Vec<u8>>, UnknownRecordData<&[u8]>> = parsed_record
|
||||
.to_record()
|
||||
.map_err(|_| "Cannot parse record".to_string())?
|
||||
.ok_or("Cannot parse record".to_string())?;
|
||||
// Convert everything to owned for sanity in type signature...
|
||||
// We'll need to do a copy before returning outside of the main
|
||||
// query function anyway
|
||||
// Actually parse the record
|
||||
// Note that we cannot just use UnknownRecordData here and not parse it;
|
||||
// it does not know how to parse all types of records correctly, which
|
||||
// could corrupt the actual record data
|
||||
let record: Record<ParsedDname<&Vec<u8>>, AllRecordData<&[u8], ParsedDname<&Vec<u8>>>> =
|
||||
parsed_record
|
||||
.to_record()
|
||||
.map_err(|_| "Cannot parse record".to_string())?
|
||||
.ok_or("Cannot parse record".to_string())?;
|
||||
// Convert the record to owned for sanity in type signature
|
||||
let owned_record = Record::new(
|
||||
record
|
||||
.owner()
|
||||
|
@ -168,10 +170,12 @@ impl Client {
|
|||
.map_err(|_| "Failed to parse Dname".to_string())?,
|
||||
record.class(),
|
||||
record.ttl(),
|
||||
UnknownRecordData::from_octets(
|
||||
record.data().rtype(),
|
||||
record.data().data().to_vec(),
|
||||
),
|
||||
match crate::util::to_owned_record_data(record.data()) {
|
||||
Ok(data) => data,
|
||||
// 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);
|
||||
}
|
||||
|
@ -185,7 +189,7 @@ impl Client {
|
|||
&self,
|
||||
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>>>>,
|
||||
) {
|
||||
let mut answers = Vec::new();
|
||||
|
@ -206,7 +210,7 @@ impl Client {
|
|||
}
|
||||
|
||||
#[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 {
|
||||
// Ignore error -- we don't really care
|
||||
self.cache.put_cache(a).await;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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 lazy_static::lazy_static;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
@ -74,7 +75,7 @@ impl OverrideResolver {
|
|||
pub fn try_resolve(
|
||||
&self,
|
||||
question: &Question<Dname<Vec<u8>>>,
|
||||
) -> Option<Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>> {
|
||||
) -> Option<Record<Dname<Vec<u8>>, OwnedRecordData>> {
|
||||
match question.qtype() {
|
||||
// We only handle resolution of IP addresses
|
||||
Rtype::A | Rtype::A6 | Rtype::Aaaa | Rtype::Cname | Rtype::Any => (),
|
||||
|
@ -101,21 +102,17 @@ impl OverrideResolver {
|
|||
&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()))),
|
||||
) -> Option<Record<Dname<Vec<u8>>, OwnedRecordData>> {
|
||||
let rdata: OwnedRecordData = match addr {
|
||||
IpAddr::V4(addr) => AllRecordData::A(A::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(
|
||||
question.qname().clone(),
|
||||
question.qclass(),
|
||||
self.override_ttl,
|
||||
UnknownRecordData::from_octets(rtype, rdata_buf),
|
||||
rdata,
|
||||
);
|
||||
return Some(record);
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ use crate::r#override::OverrideResolver;
|
|||
use async_static::async_static;
|
||||
use domain::base::{
|
||||
iana::{Opcode, Rcode},
|
||||
rdata::UnknownRecordData,
|
||||
Dname, Message, MessageBuilder, Question, Record, ToDname,
|
||||
record::AsRecord,
|
||||
Dname, Message, MessageBuilder, Question, ToDname,
|
||||
};
|
||||
use js_sys::{ArrayBuffer, Uint8Array};
|
||||
use serde::Deserialize;
|
||||
|
@ -204,7 +204,7 @@ impl Server {
|
|||
fn build_answer_wireformat(
|
||||
id: u16,
|
||||
questions: Vec<Question<Dname<Vec<u8>>>>,
|
||||
records: Vec<Record<Dname<Vec<u8>>, UnknownRecordData<Vec<u8>>>>,
|
||||
records: Vec<impl AsRecord>,
|
||||
) -> Result<Message<Vec<u8>>, String> {
|
||||
let mut message_builder = MessageBuilder::new_vec();
|
||||
// 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 std::ops::Add;
|
||||
use std::{collections::hash_map::DefaultHasher, hash::Hasher};
|
||||
|
@ -58,3 +61,40 @@ pub fn hash_buf(buf: &[u8]) -> u64 {
|
|||
hasher.write(buf);
|
||||
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 New Issue