From 7155c1cc5492d9a41af7e30df968091f8032e523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Berthaud-M=C3=BCller?= Date: Sat, 20 Mar 2021 02:08:55 -0400 Subject: [PATCH] improve rdata deserialization support and better implemetation of get zones records --- Cargo.lock | 1 + Cargo.toml | 1 + src/main.rs | 14 ++-- src/types/dns.rs | 164 +++++++++++++++++++++++++++++++++++------------ src/types/mod.rs | 10 +++ 5 files changed, 144 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab3b645..8348136 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -710,6 +710,7 @@ dependencies = [ name = "nomilo" version = "0.1.0-dev" dependencies = [ + "base64 0.13.0", "rocket", "rocket_contrib", "serde", diff --git a/Cargo.toml b/Cargo.toml index 3deeb71..b4276fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ serde_json = "1.0" rocket = "0.4.7" rocket_contrib = { version = "0.4", default-features = false, features = ["json"]} toml = "0.5" +base64 = "0.13.0" diff --git a/src/main.rs b/src/main.rs index 9e8cccf..ea20c80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,11 +19,17 @@ fn zone_records(client: State>, zone: String) -> // TODO: Implement FromParam for Name let name = Name::from_str(&zone).unwrap(); - // TODO: add support for all trust-dns record types - // then use AXFR here and filter out dnssec related fields - let response: DnsResponse = client.query(&name, DNSClass::IN, RecordType::AAAA).unwrap(); + let response: DnsResponse = client.query(&name, DNSClass::IN, RecordType::AXFR).unwrap(); let answers: &[Record] = response.answers(); - let records: Vec = answers.to_vec().into_iter().map(|record| record.into()).collect(); + let mut records: Vec<_> = answers.to_vec().into_iter() + .map(|record| types::dns::Record::from(record)) + .filter(|record| match record.rdata { + types::dns::RData::NULL { .. } | types::dns::RData::DNSSEC(_) => false, + _ => true, + }).collect(); + + // AXFR response ends with SOA, we remove it so it is not doubled in the response. + records.pop(); Json(records) } diff --git a/src/types/dns.rs b/src/types/dns.rs index edebb94..b6dad10 100644 --- a/src/types/dns.rs +++ b/src/types/dns.rs @@ -1,16 +1,11 @@ use std::net::{Ipv6Addr, Ipv4Addr}; +use std::fmt; use serde::{Serialize, Deserialize}; +use trust_dns_client::serialize::binary::BinEncoder; +use base64; -pub mod trust_dns_types { - pub use trust_dns_client::rr::rdata::{ - DNSSECRecordType, NULL, - }; - pub use trust_dns_client::rr::{ - RecordType, RData, DNSClass, Record - }; - pub use trust_dns_proto::rr::Name; -} +use super::trust_dns_types; #[derive(Deserialize, Serialize)] pub enum RecordType { @@ -140,6 +135,7 @@ impl Into for RecordType { #[derive(Deserialize, Serialize)] #[serde(tag = "Type")] +#[serde(rename_all = "UPPERCASE")] pub enum RData { #[serde(rename_all = "PascalCase")] A { @@ -149,28 +145,61 @@ pub enum RData { AAAA { address: Ipv6Addr }, - ANAME(StringName), - // CAA(CAA), - CNAME(StringName), + #[serde(rename_all = "PascalCase")] + CAA { + issuer_critical: bool, + value: String, + property_tag: String, + }, + #[serde(rename_all = "PascalCase")] + CNAME { + target: String + }, // HINFO(HINFO), // HTTPS(SVCB), - // MX(MX), + #[serde(rename_all = "PascalCase")] + MX { + preference: u16, + mail_exchanger: String + }, // NAPTR(NAPTR), - NULL(NULL), - NS(StringName), + #[serde(rename_all = "PascalCase")] + NULL { + data: String + }, + #[serde(rename_all = "PascalCase")] + NS { + target: String + }, // OPENPGPKEY(OPENPGPKEY), // OPT(OPT), - PTR(StringName), - // SOA(SOA), + #[serde(rename_all = "PascalCase")] + PTR { + target: String + }, + #[serde(rename_all = "PascalCase")] + SOA { + master_server_name: String, + maintainer_name: String, + refresh: i32, + retry: i32, + expire: i32, + minimum: u32, + serial: u32 + }, // SRV(SRV), // SSHFP(SSHFP), // SVCB(SVCB), // TLSA(TLSA), // TXT(TXT), - // DNSSEC(DNSSECRData), + + // TODO: Eventually allow deserialization of DNSSEC records + #[serde(skip)] + DNSSEC(trust_dns_types::DNSSECRData), + #[serde(rename_all = "PascalCase")] Unknown { code: u16, - rdata: NULL, + data: String, }, // ZERO, } @@ -180,33 +209,84 @@ impl From for RData { match rdata { trust_dns_types::RData::A(address) => RData::A { address }, trust_dns_types::RData::AAAA(address) => RData::AAAA { address }, - _ => unimplemented!() + // Still a draft, no iana number yet, I don't to put something that is not currently supported so that's why NULL and not unknown. + // TODO: probably need better error here, I don't know what to do about that as this would require to change the From for something else. + // (empty data because I'm lazy) + trust_dns_types::RData::ANAME(_) => RData::NULL { + data: String::new() + }, + trust_dns_types::RData::CNAME(target) => RData::CNAME { + target: target.to_utf8() + }, + trust_dns_types::RData::CAA(caa) => RData::CAA { + issuer_critical: caa.issuer_critical(), + value: format!("{}", CAAValue(caa.value())), + property_tag: caa.tag().as_str().to_string(), + }, + trust_dns_types::RData::MX(mx) => RData::MX { + preference: mx.preference(), + mail_exchanger: mx.exchange().to_utf8() + }, + trust_dns_types::RData::NULL(null) => RData::NULL { + data: base64::encode(null.anything().map(|data| data.to_vec()).unwrap_or_default()) + }, + trust_dns_types::RData::NS(target) => RData::NS { + target: target.to_utf8() + }, + trust_dns_types::RData::PTR(target) => RData::PTR { + target: target.to_utf8() + }, + trust_dns_types::RData::SOA(soa) => RData::SOA { + master_server_name: soa.mname().to_utf8(), + maintainer_name: soa.rname().to_utf8(), + refresh: soa.refresh(), + retry: soa.retry(), + expire: soa.expire(), + minimum: soa.minimum(), + serial: soa.serial() + }, + trust_dns_types::RData::DNSSEC(data) => RData::DNSSEC(data), + rdata => { + let code = rdata.to_record_type().into(); + let mut data = Vec::new(); + let mut encoder = BinEncoder::new(&mut data); + rdata.emit(&mut encoder).expect("could not encode data"); + + RData::Unknown { + code, + data: base64::encode(data), + } + } } } } +struct CAAValue<'a>(&'a trust_dns_types::caa::Value); -#[derive(Deserialize, Serialize)] -pub struct StringName(String); +// trust_dns Display implementation panics if no parameters +// Implementation based on caa::emit_value +// Also the quotes are strips to render in JSON +impl<'a> fmt::Display for CAAValue<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self.0 { + trust_dns_types::caa::Value::Issuer(name, parameters) => { + if let Some(name) = name { + write!(f, "{}", name)?; + } -impl From for StringName { - fn from(name: trust_dns_types::Name) -> StringName { - StringName(name.to_utf8()) - } -} + if name.is_none() && parameters.is_empty() { + write!(f, ";")?; + } - -#[derive(Deserialize, Serialize)] -pub struct NULL { - pub anything: Option>, -} - -impl From for NULL { - fn from(null: trust_dns_types::NULL) -> NULL { - NULL { - anything: null.anything().map(|e| e.to_vec()) + for value in parameters { + write!(f, "; {}", value)?; + } + } + trust_dns_types::caa::Value::Url(url) => write!(f, "{}", url)?, + trust_dns_types::caa::Value::Unknown(v) => write!(f, "{:?}", v)?, } + Ok(()) } } @@ -237,21 +317,21 @@ impl From for DNSClass { #[derive(Deserialize, Serialize)] pub struct Record { #[serde(rename = "Name")] - name: StringName, + pub name: String, //#[serde(rename = "Type")] //rr_type: RecordType, #[serde(rename = "Class")] - dns_class: DNSClass, + pub dns_class: DNSClass, #[serde(rename = "TTL")] - ttl: u32, + pub ttl: u32, #[serde(flatten)] - rdata: RData, + pub rdata: RData, } impl From for Record { fn from(record: trust_dns_types::Record) -> Record { Record { - name: StringName(record.name().to_utf8()), + name: record.name().to_utf8(), //rr_type: record.rr_type().into(), dns_class: record.dns_class().into(), ttl: record.ttl(), diff --git a/src/types/mod.rs b/src/types/mod.rs index 4670a6a..0dd7f57 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1 +1,11 @@ pub mod dns; + +pub mod trust_dns_types { + pub use trust_dns_client::rr::rdata::{ + DNSSECRecordType, NULL, caa, DNSSECRData + }; + pub use trust_dns_client::rr::{ + RecordType, RData, DNSClass, Record + }; + pub use trust_dns_proto::rr::Name; +}