wip: layered api
This commit is contained in:
parent
376a6bd319
commit
419b78b55e
20 changed files with 995 additions and 564 deletions
|
@ -1,6 +1,6 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::ressouces::zone::ZoneModel;
|
use crate::resources::zone::ZoneModel;
|
||||||
|
|
||||||
pub trait Db: ZoneModel + Send + Sync {}
|
pub trait Db: ZoneModel + Send + Sync {}
|
||||||
pub type BoxedDb = Arc<dyn Db>;
|
pub type BoxedDb = Arc<dyn Db>;
|
||||||
|
|
|
@ -11,7 +11,8 @@ use domain::tsig::{Algorithm, Key, KeyName};
|
||||||
use domain::net::client::request::{self, RequestMessage, RequestMessageMulti, SendRequest, SendRequestMulti};
|
use domain::net::client::request::{self, RequestMessage, RequestMessageMulti, SendRequest, SendRequestMulti};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
use crate::ressouces::{rdata, record};
|
use crate::resources::record;
|
||||||
|
use crate::proto;
|
||||||
use super::{RecordDriver, ZoneDriver, DnsDriverError};
|
use super::{RecordDriver, ZoneDriver, DnsDriverError};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
@ -162,7 +163,7 @@ impl RecordDriver for DnsDriver {
|
||||||
|
|
||||||
let answer = reply.answer()?;
|
let answer = reply.answer()?;
|
||||||
|
|
||||||
for record in answer.limit_to::<rdata::ParsedRData<_, _>>() {
|
for record in answer.limit_to::<proto::dns::ParsedRData<_, _>>() {
|
||||||
let record = record?;
|
let record = record?;
|
||||||
records.push(record.into())
|
records.push(record.into())
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
use crate::ressouces::record;
|
use crate::resources::record;
|
||||||
|
|
||||||
pub type BoxedZoneDriver = Arc<dyn ZoneDriver>;
|
pub type BoxedZoneDriver = Arc<dyn ZoneDriver>;
|
||||||
pub type BoxedRecordDriver = Arc<dyn RecordDriver>;
|
pub type BoxedRecordDriver = Arc<dyn RecordDriver>;
|
||||||
|
|
|
@ -8,10 +8,11 @@ use serde::{Serialize, Serializer};
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
use crate::dns::DnsDriverError;
|
use crate::dns::DnsDriverError;
|
||||||
use crate::ressouces::record::{RecordError, RecordParseError};
|
use crate::resources::record::{RecordError, RecordParseError};
|
||||||
use crate::ressouces::zone::ZoneError;
|
use crate::resources::zone::ZoneError;
|
||||||
use crate::validation::{DomainValidationError, TxtParseError};
|
use crate::validation::{DomainValidationError, TxtParseError};
|
||||||
use crate::template::TemplateError;
|
use crate::template::TemplateError;
|
||||||
|
use crate::proto::dns::ProtoDnsError;
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
|
@ -387,3 +388,18 @@ impl From<RecordError > for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ProtoDnsError> for Error {
|
||||||
|
fn from(value: ProtoDnsError) -> Self {
|
||||||
|
match value {
|
||||||
|
ProtoDnsError::RDataUnknown { input, field, rtype } => {
|
||||||
|
Error::new("proto:dns:rdata_unknown", "Unknown error while converting internal rdata to proto rdata")
|
||||||
|
.with_details(json!({
|
||||||
|
"input": input,
|
||||||
|
"field": field,
|
||||||
|
"rtype": rtype,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
mod errors;
|
mod errors;
|
||||||
mod dns;
|
mod dns;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod ressouces;
|
mod resources;
|
||||||
mod database;
|
mod database;
|
||||||
mod validation;
|
mod validation;
|
||||||
mod macros;
|
mod macros;
|
||||||
mod template;
|
mod template;
|
||||||
|
mod proto;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
425
src/proto/dns.rs
Normal file
425
src/proto/dns.rs
Normal file
|
@ -0,0 +1,425 @@
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use domain::base::rdata::ComposeRecordData;
|
||||||
|
use domain::base::wire::{Composer, ParseError};
|
||||||
|
use domain::base::{Name, ParseRecordData, ParsedName, RecordData, Rtype, ToName, Ttl};
|
||||||
|
use domain::rdata;
|
||||||
|
use domain::dep::octseq::{Parser, Octets};
|
||||||
|
|
||||||
|
use crate::resources::dns::internal;
|
||||||
|
use crate::errors::Error;
|
||||||
|
|
||||||
|
pub enum ProtoDnsError {
|
||||||
|
RDataUnknown { input: String, field: String, rtype: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- A --------- */
|
||||||
|
|
||||||
|
impl From<domain::rdata::A> for internal::A {
|
||||||
|
fn from(record_data: domain::rdata::A) -> Self {
|
||||||
|
internal::A { address: record_data.addr() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<internal::A> for domain::rdata::A {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(record_data: internal::A) -> Result<Self, Self::Error> {
|
||||||
|
Ok(domain::rdata::A::new(record_data.address))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- AAAA --------- */
|
||||||
|
|
||||||
|
impl From<domain::rdata::Aaaa> for internal::Aaaa {
|
||||||
|
fn from(record_data: domain::rdata::Aaaa) -> Self {
|
||||||
|
internal::Aaaa { address: record_data.addr() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<internal::Aaaa> for domain::rdata::Aaaa {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(record_data: internal::Aaaa) -> Result<Self, Self::Error> {
|
||||||
|
Ok(domain::rdata::Aaaa::new(record_data.address))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- CNAME --------- */
|
||||||
|
|
||||||
|
impl<N: ToString> From<domain::rdata::Cname<N>> for internal::Cname {
|
||||||
|
fn from(record_data: domain::rdata::Cname<N>) -> Self {
|
||||||
|
internal::Cname { target: internal::Name::new(record_data.cname().to_string()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<internal::Cname> for domain::rdata::Cname<Name<Vec<u8>>> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(record_data: internal::Cname) -> Result<domain::rdata::Cname<Name<Vec<u8>>>, Self::Error> {
|
||||||
|
let cname = record_data.target.to_string();
|
||||||
|
|
||||||
|
let cname: Name<_> = cname.parse::<Name<_>>().map_err(|e| {
|
||||||
|
Error::from(ProtoDnsError::RDataUnknown {
|
||||||
|
input: cname,
|
||||||
|
field: "target".into(),
|
||||||
|
rtype: "CNAME".into(),
|
||||||
|
}).with_cause(&e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(domain::rdata::Cname::new(cname))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- MX --------- */
|
||||||
|
|
||||||
|
impl<N: ToString> From<domain::rdata::Mx<N>> for internal::Mx {
|
||||||
|
fn from(record_data: domain::rdata::Mx<N>) -> Self {
|
||||||
|
internal::Mx {
|
||||||
|
preference: record_data.preference(),
|
||||||
|
mail_exchanger: internal::Name::new(record_data.exchange().to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<internal::Mx> for domain::rdata::Mx<Name<Vec<u8>>> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(record_data: internal::Mx) -> Result<domain::rdata::Mx<Name<Vec<u8>>>, Self::Error> {
|
||||||
|
let mail_exchanger = record_data.mail_exchanger.to_string();
|
||||||
|
|
||||||
|
let mail_exchanger: Name<_> = mail_exchanger.parse::<Name<_>>().map_err(|e| {
|
||||||
|
Error::from(ProtoDnsError::RDataUnknown {
|
||||||
|
input: mail_exchanger,
|
||||||
|
field: "mail_exchanger".into(),
|
||||||
|
rtype: "MX".into(),
|
||||||
|
}).with_cause(&e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(domain::rdata::Mx::new(record_data.preference, mail_exchanger))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- NS --------- */
|
||||||
|
|
||||||
|
impl<N: ToString> From<domain::rdata::Ns<N>> for internal::Ns {
|
||||||
|
fn from(record_data: domain::rdata::Ns<N>) -> Self {
|
||||||
|
internal::Ns {
|
||||||
|
target: internal::Name::new(record_data.nsdname().to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<internal::Ns> for domain::rdata::Ns<Name<Vec<u8>>> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(record_data: internal::Ns) -> Result<domain::rdata::Ns<Name<Vec<u8>>>, Self::Error> {
|
||||||
|
let target = record_data.target.to_string();
|
||||||
|
|
||||||
|
let target: Name<_> = target.parse::<Name<_>>().map_err(|e| {
|
||||||
|
Error::from(ProtoDnsError::RDataUnknown {
|
||||||
|
input: target,
|
||||||
|
field: "target".into(),
|
||||||
|
rtype: "NS".into(),
|
||||||
|
}).with_cause(&e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(domain::rdata::Ns::new(target))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- PTR --------- */
|
||||||
|
|
||||||
|
impl<N: ToString> From<domain::rdata::Ptr<N>> for internal::Ptr {
|
||||||
|
fn from(record_data: domain::rdata::Ptr<N>) -> Self {
|
||||||
|
internal::Ptr {
|
||||||
|
target: internal::Name::new(record_data.ptrdname().to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<internal::Ptr> for domain::rdata::Ptr<Name<Vec<u8>>> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(record_data: internal::Ptr) -> Result<domain::rdata::Ptr<Name<Vec<u8>>>, Self::Error> {
|
||||||
|
let target = record_data.target.to_string();
|
||||||
|
|
||||||
|
let target: Name<_> = target.parse::<Name<_>>().map_err(|e| {
|
||||||
|
Error::from(ProtoDnsError::RDataUnknown {
|
||||||
|
input: target,
|
||||||
|
field: "target".into(),
|
||||||
|
rtype: "PTR".into(),
|
||||||
|
}).with_cause(&e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(domain::rdata::Ptr::new(target))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- SOA --------- */
|
||||||
|
|
||||||
|
impl<N: ToString> From<domain::rdata::Soa<N>> for internal::Soa {
|
||||||
|
fn from(record_rdata: domain::rdata::Soa<N>) -> Self {
|
||||||
|
internal::Soa {
|
||||||
|
primary_server: internal::Name::new(record_rdata.mname().to_string()),
|
||||||
|
maintainer: internal::Name::new(record_rdata.rname().to_string()),
|
||||||
|
refresh: record_rdata.refresh().as_secs(),
|
||||||
|
retry: record_rdata.retry().as_secs(),
|
||||||
|
expire: record_rdata.expire().as_secs(),
|
||||||
|
minimum: record_rdata.minimum().as_secs(),
|
||||||
|
serial: record_rdata.serial().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<internal::Soa> for domain::rdata::Soa<Name<Vec<u8>>> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(record_data: internal::Soa) -> Result<domain::rdata::Soa<Name<Vec<u8>>>, Self::Error> {
|
||||||
|
let primary_server = record_data.primary_server.to_string();
|
||||||
|
let primary_server: Name<_> = primary_server.parse::<Name<_>>().map_err(|e| {
|
||||||
|
Error::from(ProtoDnsError::RDataUnknown {
|
||||||
|
input: primary_server,
|
||||||
|
field: "primary_server".into(),
|
||||||
|
rtype: "SOA".into(),
|
||||||
|
}).with_cause(&e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let maintainer = record_data.maintainer.to_string();
|
||||||
|
let maintainer: Name<_> = maintainer.parse::<Name<_>>().map_err(|e| {
|
||||||
|
Error::from(ProtoDnsError::RDataUnknown {
|
||||||
|
input: maintainer,
|
||||||
|
field: "maintainer".into(),
|
||||||
|
rtype: "SOA".into(),
|
||||||
|
}).with_cause(&e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(domain::rdata::Soa::new(
|
||||||
|
primary_server,
|
||||||
|
maintainer,
|
||||||
|
record_data.serial.into(),
|
||||||
|
Ttl::from_secs(record_data.refresh),
|
||||||
|
Ttl::from_secs(record_data.retry),
|
||||||
|
Ttl::from_secs(record_data.expire),
|
||||||
|
Ttl::from_secs(record_data.minimum),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- SRV --------- */
|
||||||
|
|
||||||
|
impl<N: ToString> From<domain::rdata::Srv<N>> for internal::Srv {
|
||||||
|
fn from(record_data: domain::rdata::Srv<N>) -> Self {
|
||||||
|
internal::Srv {
|
||||||
|
server: internal::Name::new(record_data.target().to_string()),
|
||||||
|
priority: record_data.priority(),
|
||||||
|
weight: record_data.weight(),
|
||||||
|
port: record_data.port(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<internal::Srv> for domain::rdata::Srv<Name<Vec<u8>>> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(record_data: internal::Srv) -> Result<domain::rdata::Srv<Name<Vec<u8>>>, Self::Error> {
|
||||||
|
let server = record_data.server.to_string();
|
||||||
|
let server: Name<_> = server.parse::<Name<_>>().map_err(|e| {
|
||||||
|
Error::from(ProtoDnsError::RDataUnknown {
|
||||||
|
input: server,
|
||||||
|
field: "server".into(),
|
||||||
|
rtype: "SRV".into(),
|
||||||
|
}).with_cause(&e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
|
||||||
|
Ok(domain::rdata::Srv::new(
|
||||||
|
record_data.priority,
|
||||||
|
record_data.weight,
|
||||||
|
record_data.port,
|
||||||
|
server
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- TXT --------- */
|
||||||
|
|
||||||
|
impl<O: AsRef<[u8]>> From<domain::rdata::Txt<O>> for internal::Txt {
|
||||||
|
fn from(record_data: rdata::Txt<O>) -> Self {
|
||||||
|
let concatenated_text: Vec<_> = record_data.iter()
|
||||||
|
.flatten()
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
internal::Txt {
|
||||||
|
text: concatenated_text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<internal::Txt> for domain::rdata::Txt<Vec<u8>> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(record_data: internal::Txt) -> Result<domain::rdata::Txt<Vec<u8>>, Self::Error> {
|
||||||
|
let txt: domain::rdata::Txt<_> = rdata::Txt::build_from_slice(&record_data.text).map_err(|e| {
|
||||||
|
let text = record_data.text.iter()
|
||||||
|
.fold(String::new(), |mut output, b| {
|
||||||
|
write!(output, "{:02X}", b).unwrap();
|
||||||
|
output
|
||||||
|
});
|
||||||
|
|
||||||
|
Error::from(ProtoDnsError::RDataUnknown {
|
||||||
|
input: format!("0x{}", text),
|
||||||
|
field: "text".into(),
|
||||||
|
rtype: "TXT".into(),
|
||||||
|
}).with_cause(&e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
|
||||||
|
Ok(txt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- ParsedRData --------- */
|
||||||
|
|
||||||
|
pub enum ParsedRData<Name, Octs> {
|
||||||
|
A(domain::rdata::A),
|
||||||
|
Aaaa(domain::rdata::Aaaa),
|
||||||
|
Cname(domain::rdata::Cname<Name>),
|
||||||
|
Mx(domain::rdata::Mx<Name>),
|
||||||
|
Ns(domain::rdata::Ns<Name>),
|
||||||
|
Ptr(domain::rdata::Ptr<Name>),
|
||||||
|
Soa(domain::rdata::Soa<Name>),
|
||||||
|
Srv(domain::rdata::Srv<Name>),
|
||||||
|
Txt(domain::rdata::Txt<Octs>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Name: ToString, Octs: AsRef<[u8]>> From<ParsedRData<Name, Octs>> for internal::RData {
|
||||||
|
fn from(value: ParsedRData<Name, Octs>) -> Self {
|
||||||
|
match value {
|
||||||
|
ParsedRData::A(record_rdata) => internal::RData::A(record_rdata.into()),
|
||||||
|
ParsedRData::Aaaa(record_rdata) => internal::RData::Aaaa(record_rdata.into()),
|
||||||
|
ParsedRData::Cname(record_rdata) => internal::RData::Cname(record_rdata.into()),
|
||||||
|
ParsedRData::Mx(record_rdata) => internal::RData::Mx(record_rdata.into()),
|
||||||
|
ParsedRData::Ns(record_rdata) => internal::RData::Ns(record_rdata.into()),
|
||||||
|
ParsedRData::Ptr(record_rdata) => internal::RData::Ptr(record_rdata.into()),
|
||||||
|
ParsedRData::Soa(record_rdata) => internal::RData::Soa(record_rdata.into()),
|
||||||
|
ParsedRData::Srv(record_rdata) => internal::RData::Srv(record_rdata.into()),
|
||||||
|
ParsedRData::Txt(record_rdata) => internal::RData::Txt(record_rdata.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<internal::RData> for ParsedRData<Name<Vec<u8>>, Vec<u8>> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: internal::RData) -> Result<Self, Self::Error> {
|
||||||
|
let rdata = match value {
|
||||||
|
internal::RData::A(record_rdata) => ParsedRData::A(record_rdata.try_into()?),
|
||||||
|
internal::RData::Aaaa(record_rdata) => ParsedRData::Aaaa(record_rdata.try_into()?),
|
||||||
|
internal::RData::Cname(record_rdata) => ParsedRData::Cname(record_rdata.try_into()?),
|
||||||
|
internal::RData::Mx(record_rdata) => ParsedRData::Mx(record_rdata.try_into()?),
|
||||||
|
internal::RData::Ns(record_rdata) => ParsedRData::Ns(record_rdata.try_into()?),
|
||||||
|
internal::RData::Ptr(record_rdata) => ParsedRData::Ptr(record_rdata.try_into()?),
|
||||||
|
internal::RData::Soa(record_rdata) => ParsedRData::Soa(record_rdata.try_into()?),
|
||||||
|
internal::RData::Srv(record_rdata) => ParsedRData::Srv(record_rdata.try_into()?),
|
||||||
|
internal::RData::Txt(record_rdata) => ParsedRData::Txt(record_rdata.try_into()?),
|
||||||
|
};
|
||||||
|
Ok(rdata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<Name, Octs> ParsedRData<Name, Octs> {
|
||||||
|
pub fn rtype(&self) -> Rtype {
|
||||||
|
match self {
|
||||||
|
ParsedRData::A(_) => Rtype::A,
|
||||||
|
ParsedRData::Aaaa(_) => Rtype::AAAA,
|
||||||
|
ParsedRData::Cname(_) => Rtype::CNAME,
|
||||||
|
ParsedRData::Mx(_) => Rtype::MX,
|
||||||
|
ParsedRData::Ns(_) => Rtype::NS,
|
||||||
|
ParsedRData::Ptr(_) => Rtype::PTR,
|
||||||
|
ParsedRData::Soa(_) => Rtype::SOA,
|
||||||
|
ParsedRData::Srv(_) => Rtype::SRV,
|
||||||
|
ParsedRData::Txt(_) => Rtype::TXT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Name, Octs> RecordData for ParsedRData<Name, Octs> {
|
||||||
|
fn rtype(&self) -> Rtype {
|
||||||
|
ParsedRData::rtype(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs> for ParsedRData<ParsedName<Octs::Range<'a>>, Octs::Range<'a>> {
|
||||||
|
fn parse_rdata(
|
||||||
|
rtype: Rtype,
|
||||||
|
parser: &mut Parser<'a, Octs>,
|
||||||
|
) -> Result<Option<Self>, ParseError> {
|
||||||
|
let record = match rtype {
|
||||||
|
Rtype::A => ParsedRData::A(rdata::A::parse(parser)?),
|
||||||
|
Rtype::AAAA => ParsedRData::Aaaa(rdata::Aaaa::parse(parser)?),
|
||||||
|
Rtype::CNAME => ParsedRData::Cname(rdata::Cname::parse(parser)?),
|
||||||
|
Rtype::MX => ParsedRData::Mx(rdata::Mx::parse(parser)?),
|
||||||
|
Rtype::NS => ParsedRData::Ns(rdata::Ns::parse(parser)?),
|
||||||
|
Rtype::PTR => ParsedRData::Ptr(rdata::Ptr::parse(parser)?),
|
||||||
|
Rtype::SOA => ParsedRData::Soa(rdata::Soa::parse(parser)?),
|
||||||
|
Rtype::SRV => ParsedRData::Srv(rdata::Srv::parse(parser)?),
|
||||||
|
Rtype::TXT => ParsedRData::Txt(rdata::Txt::parse(parser)?),
|
||||||
|
_ => return Ok(None)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(record))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Name: ToName, Octs: AsRef<[u8]>> ComposeRecordData for ParsedRData<Name, Octs> {
|
||||||
|
fn rdlen(&self, compress: bool) -> Option<u16> {
|
||||||
|
match self {
|
||||||
|
ParsedRData::A(record_rdata) => record_rdata.rdlen(compress),
|
||||||
|
ParsedRData::Aaaa(record_rdata) => record_rdata.rdlen(compress),
|
||||||
|
ParsedRData::Cname(record_rdata) => record_rdata.rdlen(compress),
|
||||||
|
ParsedRData::Mx(record_rdata) => record_rdata.rdlen(compress),
|
||||||
|
ParsedRData::Ns(record_rdata) => record_rdata.rdlen(compress),
|
||||||
|
ParsedRData::Ptr(record_rdata) => record_rdata.rdlen(compress),
|
||||||
|
ParsedRData::Soa(record_rdata) => record_rdata.rdlen(compress),
|
||||||
|
ParsedRData::Srv(record_rdata) => record_rdata.rdlen(compress),
|
||||||
|
ParsedRData::Txt(record_rdata) => record_rdata.rdlen(compress),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compose_rdata<Target: Composer + ?Sized>(
|
||||||
|
&self,
|
||||||
|
target: &mut Target,
|
||||||
|
) -> Result<(), Target::AppendError> {
|
||||||
|
match self {
|
||||||
|
ParsedRData::A(record_rdata) => record_rdata.compose_rdata(target),
|
||||||
|
ParsedRData::Aaaa(record_rdata) => record_rdata.compose_rdata(target),
|
||||||
|
ParsedRData::Cname(record_rdata) => record_rdata.compose_rdata(target),
|
||||||
|
ParsedRData::Mx(record_rdata) => record_rdata.compose_rdata(target),
|
||||||
|
ParsedRData::Ns(record_rdata) => record_rdata.compose_rdata(target),
|
||||||
|
ParsedRData::Ptr(record_rdata) => record_rdata.compose_rdata(target),
|
||||||
|
ParsedRData::Soa(record_rdata) => record_rdata.compose_rdata(target),
|
||||||
|
ParsedRData::Srv(record_rdata) => record_rdata.compose_rdata(target),
|
||||||
|
ParsedRData::Txt(record_rdata) => record_rdata.compose_rdata(target),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compose_canonical_rdata<Target: Composer + ?Sized>(
|
||||||
|
&self,
|
||||||
|
target: &mut Target,
|
||||||
|
) -> Result<(), Target::AppendError> {
|
||||||
|
match self {
|
||||||
|
ParsedRData::A(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
||||||
|
ParsedRData::Aaaa(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
||||||
|
ParsedRData::Cname(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
||||||
|
ParsedRData::Mx(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
||||||
|
ParsedRData::Ns(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
||||||
|
ParsedRData::Ptr(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
||||||
|
ParsedRData::Soa(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
||||||
|
ParsedRData::Srv(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
||||||
|
ParsedRData::Txt(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
src/proto/mod.rs
Normal file
1
src/proto/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod dns;
|
2
src/resources/dns/external/mod.rs
vendored
Normal file
2
src/resources/dns/external/mod.rs
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod rdata;
|
||||||
|
pub use rdata::*;
|
437
src/resources/dns/external/rdata.rs
vendored
Normal file
437
src/resources/dns/external/rdata.rs
vendored
Normal file
|
@ -0,0 +1,437 @@
|
||||||
|
use std::fmt::Write;
|
||||||
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
|
|
||||||
|
use domain::base::{Rtype, scan::Symbol};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::errors::Error;
|
||||||
|
use crate::validation;
|
||||||
|
|
||||||
|
use crate::macros::{append_errors, push_error};
|
||||||
|
use crate::resources::record::RecordParseError;
|
||||||
|
use crate::resources::dns::internal;
|
||||||
|
|
||||||
|
/// Type used to serialize / deserialize resource records data to response / request
|
||||||
|
///
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(tag = "type", content = "rdata")]
|
||||||
|
#[serde(rename_all = "UPPERCASE")]
|
||||||
|
pub enum RData {
|
||||||
|
A(A),
|
||||||
|
Aaaa(Aaaa),
|
||||||
|
// TODO: CAA
|
||||||
|
Cname(Cname),
|
||||||
|
// TODO: DS
|
||||||
|
Mx(Mx),
|
||||||
|
Ns(Ns),
|
||||||
|
Ptr(Ptr),
|
||||||
|
Soa(Soa),
|
||||||
|
Srv(Srv),
|
||||||
|
// TODO: SSHFP
|
||||||
|
// TODO: SVCB / HTTPS
|
||||||
|
// TODO: TLSA
|
||||||
|
Txt(Txt),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RData {
|
||||||
|
pub fn rtype(&self) -> Rtype {
|
||||||
|
match self {
|
||||||
|
RData::A(_) => Rtype::A,
|
||||||
|
RData::Aaaa(_) => Rtype::AAAA,
|
||||||
|
RData::Cname(_) => Rtype::CNAME,
|
||||||
|
RData::Mx(_) => Rtype::MX,
|
||||||
|
RData::Ns(_) => Rtype::NS,
|
||||||
|
RData::Ptr(_) => Rtype::PTR,
|
||||||
|
RData::Soa(_) => Rtype::SOA,
|
||||||
|
RData::Srv(_) => Rtype::SRV,
|
||||||
|
RData::Txt(_) => Rtype::TXT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate(self) -> Result<internal::RData, Vec<Error>> {
|
||||||
|
let rdata = match self {
|
||||||
|
RData::A(data) => internal::RData::A(data.validate()?),
|
||||||
|
RData::Aaaa(data) => internal::RData::Aaaa(data.validate()?),
|
||||||
|
RData::Cname(data) => internal::RData::Cname(data.validate()?),
|
||||||
|
RData::Mx(data) => internal::RData::Mx(data.validate()?),
|
||||||
|
RData::Ns(data) => internal::RData::Ns(data.validate()?),
|
||||||
|
RData::Ptr(data) => internal::RData::Ptr(data.validate()?),
|
||||||
|
RData::Soa(data) => internal::RData::Soa(data.validate()?),
|
||||||
|
RData::Srv(data) => internal::RData::Srv(data.validate()?),
|
||||||
|
RData::Txt(data) => internal::RData::Txt(data.validate()?),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(rdata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<internal::RData> for RData {
|
||||||
|
fn from(value: internal::RData) -> Self {
|
||||||
|
match value {
|
||||||
|
internal::RData::A(data) => RData::A(data.into()),
|
||||||
|
internal::RData::Aaaa(data) => RData::Aaaa(data.into()),
|
||||||
|
internal::RData::Cname(data) => RData::Cname(data.into()),
|
||||||
|
internal::RData::Mx(data) => RData::Mx(data.into()),
|
||||||
|
internal::RData::Ns(data) => RData::Ns(data.into()),
|
||||||
|
internal::RData::Ptr(data) => RData::Ptr(data.into()),
|
||||||
|
internal::RData::Soa(data) => RData::Soa(data.into()),
|
||||||
|
internal::RData::Srv(data) => RData::Srv(data.into()),
|
||||||
|
internal::RData::Txt(data) => RData::Txt(data.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- A --------- */
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct A {
|
||||||
|
pub address: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<internal::A> for A {
|
||||||
|
fn from(value: internal::A) -> Self {
|
||||||
|
A {
|
||||||
|
address: value.address.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl A {
|
||||||
|
pub fn validate(self) -> Result<internal::A, Vec<Error>> {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
let address = push_error!(self.address.parse::<Ipv4Addr>().map_err(|e| {
|
||||||
|
Error::from(RecordParseError::Ip4Address { input: self.address })
|
||||||
|
.with_cause(&e.to_string())
|
||||||
|
.with_path("/address")
|
||||||
|
}), errors);
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
|
Ok(internal::A {
|
||||||
|
address: address.unwrap()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- AAAA --------- */
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Aaaa {
|
||||||
|
pub address: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<internal::Aaaa> for Aaaa {
|
||||||
|
fn from(value: internal::Aaaa) -> Self {
|
||||||
|
Aaaa {
|
||||||
|
address: value.address.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Aaaa {
|
||||||
|
pub fn validate(self) -> Result<internal::Aaaa, Vec<Error>> {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
// TODO: replace with custom validation
|
||||||
|
let address = push_error!(self.address.parse::<Ipv6Addr>().map_err(|e| {
|
||||||
|
Error::from(RecordParseError::Ip6Address { input: self.address })
|
||||||
|
.with_cause(&e.to_string())
|
||||||
|
.with_path("/address")
|
||||||
|
}), errors);
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
|
Ok(internal::Aaaa {
|
||||||
|
address: address.unwrap()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- CNAME --------- */
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Cname {
|
||||||
|
pub target: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<internal::Cname> for Cname {
|
||||||
|
fn from(value: internal::Cname) -> Self {
|
||||||
|
Cname {
|
||||||
|
target: value.target.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cname {
|
||||||
|
pub fn validate(self) -> Result<internal::Cname, Vec<Error>> {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
let cname = push_error!(
|
||||||
|
validation::normalize_domain(&self.target),
|
||||||
|
errors, "/target"
|
||||||
|
);
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
|
Ok(internal::Cname {
|
||||||
|
target: internal::Name::new(cname.unwrap())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- MX --------- */
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Mx {
|
||||||
|
// TODO: Validate number
|
||||||
|
pub preference: u16,
|
||||||
|
pub mail_exchanger: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<internal::Mx> for Mx {
|
||||||
|
fn from(value: internal::Mx) -> Self {
|
||||||
|
Mx {
|
||||||
|
preference: value.preference,
|
||||||
|
mail_exchanger: value.mail_exchanger.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mx {
|
||||||
|
pub fn validate(self) -> Result<internal::Mx, Vec<Error>> {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
let mail_exchanger = push_error!(
|
||||||
|
validation::normalize_domain(&self.mail_exchanger),
|
||||||
|
errors, "/mail_exchanger"
|
||||||
|
);
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
|
Ok(internal::Mx {
|
||||||
|
preference: self.preference,
|
||||||
|
mail_exchanger: internal::Name::new(mail_exchanger.unwrap()),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- NS --------- */
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Ns {
|
||||||
|
pub target: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<internal::Ns> for Ns {
|
||||||
|
fn from(value: internal::Ns) -> Self {
|
||||||
|
Ns {
|
||||||
|
target: value.target.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ns {
|
||||||
|
pub fn validate(self) -> Result<internal::Ns, Vec<Error>> {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
let target = push_error!(
|
||||||
|
validation::normalize_domain(&self.target),
|
||||||
|
errors, "/target"
|
||||||
|
);
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
|
Ok(internal::Ns {
|
||||||
|
target: internal::Name::new(target.unwrap()),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- PTR --------- */
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Ptr {
|
||||||
|
pub target: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<internal::Ptr> for Ptr {
|
||||||
|
fn from(value: internal::Ptr) -> Self {
|
||||||
|
Ptr {
|
||||||
|
target: value.target.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ptr {
|
||||||
|
pub fn validate(self) -> Result<internal::Ptr, Vec<Error>> {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
let target = push_error!(
|
||||||
|
validation::normalize_domain(&self.target),
|
||||||
|
errors, "/target"
|
||||||
|
);
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
|
Ok(internal::Ptr {
|
||||||
|
target: internal::Name::new(target.unwrap()),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- SOA --------- */
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Soa {
|
||||||
|
pub primary_server: String,
|
||||||
|
pub maintainer: String,
|
||||||
|
pub refresh: u32,
|
||||||
|
pub retry: u32,
|
||||||
|
pub expire: u32,
|
||||||
|
pub minimum: u32,
|
||||||
|
pub serial: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<internal::Soa> for Soa {
|
||||||
|
fn from(value: internal::Soa) -> Self {
|
||||||
|
Soa {
|
||||||
|
primary_server: value.primary_server.to_string(),
|
||||||
|
maintainer: value.maintainer.to_string(),
|
||||||
|
refresh: value.refresh,
|
||||||
|
retry: value.retry,
|
||||||
|
expire: value.expire,
|
||||||
|
minimum: value.minimum,
|
||||||
|
serial: value.serial,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Soa {
|
||||||
|
pub fn validate(self) -> Result<internal::Soa, Vec<Error>> {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
let primary_server = push_error!(
|
||||||
|
validation::normalize_domain(&self.primary_server),
|
||||||
|
errors, "/primary_server"
|
||||||
|
);
|
||||||
|
|
||||||
|
let maintainer = push_error!(
|
||||||
|
validation::normalize_domain(&self.maintainer),
|
||||||
|
errors, "/maintainer"
|
||||||
|
);
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
|
Ok(internal::Soa {
|
||||||
|
primary_server: internal::Name::new(primary_server.unwrap()),
|
||||||
|
maintainer: internal::Name::new(maintainer.unwrap()),
|
||||||
|
refresh: self.refresh,
|
||||||
|
retry: self.retry,
|
||||||
|
expire: self.expire,
|
||||||
|
minimum: self.minimum,
|
||||||
|
serial: self.serial,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------- SRV --------- */
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Srv {
|
||||||
|
pub server: String,
|
||||||
|
pub port: u16,
|
||||||
|
pub priority: u16,
|
||||||
|
pub weight: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<internal::Srv> for Srv {
|
||||||
|
fn from(value: internal::Srv) -> Self {
|
||||||
|
Srv {
|
||||||
|
server: value.server.to_string(),
|
||||||
|
port: value.port,
|
||||||
|
priority: value.priority,
|
||||||
|
weight: value.weight,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Srv {
|
||||||
|
pub fn validate(self) -> Result<internal::Srv, Vec<Error>> {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
let server = push_error!(
|
||||||
|
validation::normalize_domain(&self.server),
|
||||||
|
errors, "/server"
|
||||||
|
);
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
|
Ok(internal::Srv {
|
||||||
|
server: internal::Name::new(server.unwrap()),
|
||||||
|
priority: self.priority,
|
||||||
|
weight: self.weight,
|
||||||
|
port: self.port,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* --------- TXT --------- */
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Txt {
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<internal::Txt> for Txt {
|
||||||
|
fn from(value: internal::Txt) -> Self {
|
||||||
|
|
||||||
|
let mut concatenated_text = String::new();
|
||||||
|
for c in value.text.iter() {
|
||||||
|
// Escapes '\' and non printable chars
|
||||||
|
let c = Symbol::display_from_octet(*c);
|
||||||
|
write!(concatenated_text, "{}", c).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Txt {
|
||||||
|
text: concatenated_text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Txt {
|
||||||
|
pub fn validate(self) -> Result<internal::Txt, Vec<Error>> {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
let text = append_errors!(
|
||||||
|
validation::parse_txt_data(&self.text),
|
||||||
|
errors, "/text"
|
||||||
|
);
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
|
Ok(internal::Txt {
|
||||||
|
text: text.unwrap()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
src/resources/dns/internal/mod.rs
Normal file
3
src/resources/dns/internal/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod rdata;
|
||||||
|
|
||||||
|
pub use rdata::*;
|
78
src/resources/dns/internal/rdata.rs
Normal file
78
src/resources/dns/internal/rdata.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
pub enum RData {
|
||||||
|
A(A),
|
||||||
|
Aaaa(Aaaa),
|
||||||
|
Cname(Cname),
|
||||||
|
Mx(Mx),
|
||||||
|
Ns(Ns),
|
||||||
|
Ptr(Ptr),
|
||||||
|
Soa(Soa),
|
||||||
|
Srv(Srv),
|
||||||
|
Txt(Txt),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Name {
|
||||||
|
name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Name {
|
||||||
|
pub fn new(name: String) -> Self {
|
||||||
|
Name {
|
||||||
|
name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Name {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct A {
|
||||||
|
pub address: Ipv4Addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Aaaa {
|
||||||
|
pub address: Ipv6Addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Cname {
|
||||||
|
pub target: Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Mx {
|
||||||
|
pub preference: u16,
|
||||||
|
pub mail_exchanger: Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Ns {
|
||||||
|
pub target: Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Ptr {
|
||||||
|
pub target: Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Soa {
|
||||||
|
pub primary_server: Name,
|
||||||
|
pub maintainer: Name,
|
||||||
|
pub refresh: u32,
|
||||||
|
pub retry: u32,
|
||||||
|
pub expire: u32,
|
||||||
|
pub minimum: u32,
|
||||||
|
pub serial: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Srv {
|
||||||
|
pub server: Name,
|
||||||
|
pub port: u16,
|
||||||
|
pub priority: u16,
|
||||||
|
pub weight: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Txt {
|
||||||
|
pub text: Vec<u8>,
|
||||||
|
}
|
3
src/resources/dns/mod.rs
Normal file
3
src/resources/dns/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod external;
|
||||||
|
pub mod internal;
|
||||||
|
//pub mod friendly;
|
|
@ -20,5 +20,6 @@ pub use zone::{Zone, AddZoneMemberRequest, CreateZoneRequest};
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pub mod zone;
|
pub mod zone;
|
||||||
pub mod rdata;
|
//pub mod rdata;
|
||||||
pub mod record;
|
pub mod record;
|
||||||
|
pub mod dns;
|
|
@ -4,7 +4,10 @@ use domain::base::{iana::Class, Name, Record as DnsRecord, Ttl};
|
||||||
|
|
||||||
use crate::{errors::Error, validation};
|
use crate::{errors::Error, validation};
|
||||||
use crate::macros::{append_errors, push_error};
|
use crate::macros::{append_errors, push_error};
|
||||||
use super::rdata::{ParsedRData, RData};
|
|
||||||
|
use crate::resources::dns::external;
|
||||||
|
use crate::resources::dns::internal;
|
||||||
|
use crate::proto;
|
||||||
|
|
||||||
pub enum RecordParseError {
|
pub enum RecordParseError {
|
||||||
Ip4Address { input: String },
|
Ip4Address { input: String },
|
||||||
|
@ -20,7 +23,7 @@ pub enum RecordError {
|
||||||
|
|
||||||
pub(crate) type DnsRecordImpl = DnsRecord<
|
pub(crate) type DnsRecordImpl = DnsRecord<
|
||||||
Name<Vec<u8>>,
|
Name<Vec<u8>>,
|
||||||
ParsedRData<Name<Vec<u8>>,Vec<u8>>
|
proto::dns::ParsedRData<Name<Vec<u8>>,Vec<u8>>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
@ -28,15 +31,16 @@ pub struct Record {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub ttl: u32,
|
pub ttl: u32,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub rdata: RData
|
pub rdata: external::RData
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Name: ToString, Oct: AsRef<[u8]>> From<DnsRecord<Name, ParsedRData<Name, Oct>>> for Record {
|
// TODO: Proto
|
||||||
fn from(value: DnsRecord<Name, ParsedRData<Name, Oct>>) -> Self {
|
impl<Name: ToString, Oct: AsRef<[u8]>> From<DnsRecord<Name, proto::dns::ParsedRData<Name, Oct>>> for Record {
|
||||||
|
fn from(value: DnsRecord<Name, proto::dns::ParsedRData<Name, Oct>>) -> Self {
|
||||||
Record {
|
Record {
|
||||||
name: value.owner().to_string(),
|
name: value.owner().to_string(),
|
||||||
ttl: value.ttl().as_secs(),
|
ttl: value.ttl().as_secs(),
|
||||||
rdata: value.into_data().into(),
|
rdata: internal::RData::from(value.into_data()).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,10 +70,13 @@ impl Record {
|
||||||
});
|
});
|
||||||
|
|
||||||
let ttl = Ttl::from_secs(self.ttl);
|
let ttl = Ttl::from_secs(self.ttl);
|
||||||
let rdata = append_errors!(ParsedRData::try_from(self.rdata), errors, "/rdata");
|
let rdata = append_errors!(self.rdata.validate(), errors, "/rdata");
|
||||||
|
|
||||||
|
|
||||||
if errors.is_empty() {
|
if errors.is_empty() {
|
||||||
Ok(DnsRecord::new(name.unwrap(), Class::IN, ttl, rdata.unwrap()))
|
// TODO: Split this in proto / external
|
||||||
|
let rdata = proto::dns::ParsedRData::try_from(rdata.unwrap()).unwrap();
|
||||||
|
Ok(DnsRecord::new(name.unwrap(), Class::IN, ttl, rdata))
|
||||||
} else {
|
} else {
|
||||||
Err(errors)
|
Err(errors)
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@ use crate::database::{BoxedDb, sqlite::SqliteDB};
|
||||||
use crate::dns::{BoxedZoneDriver, BoxedRecordDriver, DnsDriverError};
|
use crate::dns::{BoxedZoneDriver, BoxedRecordDriver, DnsDriverError};
|
||||||
use crate::errors::Error;
|
use crate::errors::Error;
|
||||||
use crate::macros::push_error;
|
use crate::macros::push_error;
|
||||||
use crate::ressouces::record::RecordList;
|
use crate::resources::record::RecordList;
|
||||||
use crate::validation;
|
use crate::validation;
|
||||||
|
|
||||||
pub enum ZoneError {
|
pub enum ZoneError {
|
|
@ -1,544 +0,0 @@
|
||||||
use std::fmt::Write;
|
|
||||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
|
||||||
|
|
||||||
use domain::base::rdata::ComposeRecordData;
|
|
||||||
use domain::base::scan::Symbol;
|
|
||||||
use domain::base::wire::{Composer, ParseError};
|
|
||||||
use domain::base::{Name, ParseRecordData, ParsedName, RecordData, Rtype, ToName, Ttl};
|
|
||||||
use domain::rdata;
|
|
||||||
use domain::dep::octseq::{Parser, Octets};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::errors::Error;
|
|
||||||
use crate::validation;
|
|
||||||
|
|
||||||
use crate::macros::{append_errors, push_error};
|
|
||||||
use super::record::RecordParseError;
|
|
||||||
|
|
||||||
/// Type used to serialize / deserialize resource records data to response / request
|
|
||||||
///
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(tag = "type", content = "rdata")]
|
|
||||||
#[serde(rename_all = "UPPERCASE")]
|
|
||||||
pub enum RData {
|
|
||||||
A(A),
|
|
||||||
Aaaa(Aaaa),
|
|
||||||
// TODO: CAA
|
|
||||||
Cname(Cname),
|
|
||||||
// TODO: DS
|
|
||||||
Mx(Mx),
|
|
||||||
Ns(Ns),
|
|
||||||
Ptr(Ptr),
|
|
||||||
Soa(Soa),
|
|
||||||
Srv(Srv),
|
|
||||||
// TODO: SSHFP
|
|
||||||
// TODO: SVCB / HTTPS
|
|
||||||
// TODO: TLSA
|
|
||||||
Txt(Txt),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RData {
|
|
||||||
pub fn rtype(&self) -> Rtype {
|
|
||||||
match self {
|
|
||||||
RData::A(_) => Rtype::A,
|
|
||||||
RData::Aaaa(_) => Rtype::AAAA,
|
|
||||||
RData::Cname(_) => Rtype::CNAME,
|
|
||||||
RData::Mx(_) => Rtype::MX,
|
|
||||||
RData::Ns(_) => Rtype::NS,
|
|
||||||
RData::Ptr(_) => Rtype::PTR,
|
|
||||||
RData::Soa(_) => Rtype::SOA,
|
|
||||||
RData::Srv(_) => Rtype::SRV,
|
|
||||||
RData::Txt(_) => Rtype::TXT,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ParsedRData<Name, Octs> {
|
|
||||||
A(rdata::A),
|
|
||||||
Aaaa(rdata::Aaaa),
|
|
||||||
Cname(rdata::Cname<Name>),
|
|
||||||
Mx(rdata::Mx<Name>),
|
|
||||||
Ns(rdata::Ns<Name>),
|
|
||||||
Ptr(rdata::Ptr<Name>),
|
|
||||||
Soa(rdata::Soa<Name>),
|
|
||||||
Srv(rdata::Srv<Name>),
|
|
||||||
Txt(rdata::Txt<Octs>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Name: ToString, Octs: AsRef<[u8]>> From<ParsedRData<Name, Octs>> for RData {
|
|
||||||
fn from(value: ParsedRData<Name, Octs>) -> Self {
|
|
||||||
match value {
|
|
||||||
ParsedRData::A(record_rdata) => RData::A(record_rdata.into()),
|
|
||||||
ParsedRData::Aaaa(record_rdata) => RData::Aaaa(record_rdata.into()),
|
|
||||||
ParsedRData::Cname(record_rdata) => RData::Cname(record_rdata.into()),
|
|
||||||
ParsedRData::Mx(record_rdata) => RData::Mx(record_rdata.into()),
|
|
||||||
ParsedRData::Ns(record_rdata) => RData::Ns(record_rdata.into()),
|
|
||||||
ParsedRData::Ptr(record_rdata) => RData::Ptr(record_rdata.into()),
|
|
||||||
ParsedRData::Soa(record_rdata) => RData::Soa(record_rdata.into()),
|
|
||||||
ParsedRData::Srv(record_rdata) => RData::Srv(record_rdata.into()),
|
|
||||||
ParsedRData::Txt(record_rdata) => RData::Txt(record_rdata.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<RData> for ParsedRData<Name<Vec<u8>>, Vec<u8>> {
|
|
||||||
type Error = Vec<Error>;
|
|
||||||
|
|
||||||
fn try_from(value: RData) -> Result<Self, Self::Error> {
|
|
||||||
let rdata = match value {
|
|
||||||
RData::A(record_rdata) => ParsedRData::A(record_rdata.parse_record()?),
|
|
||||||
RData::Aaaa(record_rdata) => ParsedRData::Aaaa(record_rdata.parse_record()?),
|
|
||||||
RData::Cname(record_rdata) => ParsedRData::Cname(record_rdata.parse_record()?),
|
|
||||||
RData::Mx(record_rdata) => ParsedRData::Mx(record_rdata.parse_record()?),
|
|
||||||
RData::Ns(record_rdata) => ParsedRData::Ns(record_rdata.parse_record()?),
|
|
||||||
RData::Ptr(record_rdata) => ParsedRData::Ptr(record_rdata.parse_record()?),
|
|
||||||
RData::Soa(record_rdata) => ParsedRData::Soa(record_rdata.parse_record()?),
|
|
||||||
RData::Srv(record_rdata) => ParsedRData::Srv(record_rdata.parse_record()?),
|
|
||||||
RData::Txt(record_rdata) => ParsedRData::Txt(record_rdata.parse_record()?),
|
|
||||||
};
|
|
||||||
Ok(rdata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
macro_rules! parse_name {
|
|
||||||
($value:expr, $field:ident, $rtype:literal, $errors:expr) => {
|
|
||||||
{
|
|
||||||
let name = push_error!(
|
|
||||||
validation::normalize_domain(&$value.$field),
|
|
||||||
$errors, concat!("/", stringify!($field))
|
|
||||||
);
|
|
||||||
|
|
||||||
let name = name.and_then(|name| {
|
|
||||||
push_error!(
|
|
||||||
name.parse::<Name<_>>().map_err(|e| {
|
|
||||||
Error::from(RecordParseError::RDataUnknown {
|
|
||||||
input: $value.$field,
|
|
||||||
field: stringify!(field).to_string(),
|
|
||||||
rtype: $rtype.to_string(),
|
|
||||||
}).with_cause(&e.to_string())
|
|
||||||
}),
|
|
||||||
$errors, concat!("/", stringify!($field))
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
name
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------- A --------- */
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct A {
|
|
||||||
pub address: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<rdata::A> for A {
|
|
||||||
fn from(record_data: rdata::A) -> Self {
|
|
||||||
A { address: record_data.addr().to_string() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl A {
|
|
||||||
pub fn parse_record(self) -> Result<rdata::A, Vec<Error>> {
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
|
|
||||||
let address = push_error!(self.address.parse::<Ipv4Addr>().map_err(|e| {
|
|
||||||
Error::from(RecordParseError::Ip4Address { input: self.address })
|
|
||||||
.with_cause(&e.to_string())
|
|
||||||
.with_path("/address")
|
|
||||||
}), errors);
|
|
||||||
|
|
||||||
if errors.is_empty() {
|
|
||||||
Ok(rdata::A::new(address.unwrap()))
|
|
||||||
} else {
|
|
||||||
Err(errors)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------- AAAA --------- */
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Aaaa {
|
|
||||||
pub address: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<rdata::Aaaa> for Aaaa {
|
|
||||||
fn from(record_data: rdata::Aaaa) -> Self {
|
|
||||||
Aaaa { address: record_data.addr().to_string() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Aaaa {
|
|
||||||
pub fn parse_record(self) -> Result<rdata::Aaaa, Vec<Error>> {
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
|
|
||||||
let address = push_error!(self.address.parse::<Ipv6Addr>().map_err(|e| {
|
|
||||||
Error::from(RecordParseError::Ip6Address { input: self.address })
|
|
||||||
.with_cause(&e.to_string())
|
|
||||||
.with_path("/address")
|
|
||||||
}), errors);
|
|
||||||
|
|
||||||
if errors.is_empty() {
|
|
||||||
Ok(rdata::Aaaa::new(address.unwrap()))
|
|
||||||
} else {
|
|
||||||
Err(errors)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------- CNAME --------- */
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Cname {
|
|
||||||
pub target: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: ToString> From<rdata::Cname<N>> for Cname {
|
|
||||||
fn from(record_data: rdata::Cname<N>) -> Self {
|
|
||||||
Cname { target: record_data.cname().to_string() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cname {
|
|
||||||
pub fn parse_record(self) -> Result<rdata::Cname<Name<Vec<u8>>>, Vec<Error>> {
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
|
|
||||||
let cname = parse_name!(self, target, "CNAME", errors);
|
|
||||||
|
|
||||||
if errors.is_empty() {
|
|
||||||
Ok(rdata::Cname::new(cname.unwrap()))
|
|
||||||
} else {
|
|
||||||
Err(errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------- MX --------- */
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Mx {
|
|
||||||
pub preference: u16,
|
|
||||||
pub mail_exchanger: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: ToString> From<rdata::Mx<N>> for Mx {
|
|
||||||
fn from(record_data: rdata::Mx<N>) -> Self {
|
|
||||||
Mx {
|
|
||||||
preference: record_data.preference(),
|
|
||||||
mail_exchanger: record_data.exchange().to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mx {
|
|
||||||
fn parse_record(self) -> Result<rdata::Mx<Name<Vec<u8>>>, Vec<Error>> {
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
|
|
||||||
let mail_exchanger = parse_name!(self, mail_exchanger, "MX", errors);
|
|
||||||
|
|
||||||
if errors.is_empty() {
|
|
||||||
Ok(rdata::Mx::new(self.preference, mail_exchanger.unwrap()))
|
|
||||||
} else {
|
|
||||||
Err(errors)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------- NS --------- */
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Ns {
|
|
||||||
pub target: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: ToString> From<rdata::Ns<N>> for Ns {
|
|
||||||
fn from(record_rdata: rdata::Ns<N>) -> Self {
|
|
||||||
Ns {
|
|
||||||
target: record_rdata.nsdname().to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ns {
|
|
||||||
fn parse_record(self) -> Result<rdata::Ns<Name<Vec<u8>>>, Vec<Error>> {
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
|
|
||||||
let ns_name = parse_name!(self, target, "NS", errors);
|
|
||||||
|
|
||||||
if errors.is_empty() {
|
|
||||||
Ok(rdata::Ns::new(ns_name.unwrap()))
|
|
||||||
} else {
|
|
||||||
Err(errors)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------- PTR --------- */
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Ptr {
|
|
||||||
pub target: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: ToString> From<rdata::Ptr<N>> for Ptr {
|
|
||||||
fn from(record_rdata: rdata::Ptr<N>) -> Self {
|
|
||||||
Ptr {
|
|
||||||
target: record_rdata.ptrdname().to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ptr {
|
|
||||||
fn parse_record(self) -> Result<rdata::Ptr<Name<Vec<u8>>>, Vec<Error>> {
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
|
|
||||||
let ptr_name = parse_name!(self, target, "PTR", errors);
|
|
||||||
|
|
||||||
if errors.is_empty() {
|
|
||||||
Ok(rdata::Ptr::new(ptr_name.unwrap()))
|
|
||||||
} else {
|
|
||||||
Err(errors)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------- SOA --------- */
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Soa {
|
|
||||||
pub primary_server: String,
|
|
||||||
pub maintainer: String,
|
|
||||||
pub refresh: u32,
|
|
||||||
pub retry: u32,
|
|
||||||
pub expire: u32,
|
|
||||||
pub minimum: u32,
|
|
||||||
pub serial: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: ToString> From<rdata::Soa<N>> for Soa {
|
|
||||||
fn from(record_rdata: rdata::Soa<N>) -> Self {
|
|
||||||
Soa {
|
|
||||||
primary_server: record_rdata.mname().to_string(),
|
|
||||||
maintainer: record_rdata.rname().to_string(),
|
|
||||||
refresh: record_rdata.refresh().as_secs(),
|
|
||||||
retry: record_rdata.retry().as_secs(),
|
|
||||||
expire: record_rdata.expire().as_secs(),
|
|
||||||
minimum: record_rdata.minimum().as_secs(),
|
|
||||||
serial: record_rdata.serial().into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Soa {
|
|
||||||
fn parse_record(self) -> Result<rdata::Soa<Name<Vec<u8>>>, Vec<Error>> {
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
|
|
||||||
let primary_ns = parse_name!(self, primary_server, "SOA", errors);
|
|
||||||
let maintainer = parse_name!(self, maintainer, "SOA", errors);
|
|
||||||
|
|
||||||
if errors.is_empty() {
|
|
||||||
Ok(rdata::Soa::new(
|
|
||||||
primary_ns.unwrap(),
|
|
||||||
maintainer.unwrap(),
|
|
||||||
self.refresh.into(),
|
|
||||||
Ttl::from_secs(self.retry),
|
|
||||||
Ttl::from_secs(self.expire),
|
|
||||||
Ttl::from_secs(self.minimum),
|
|
||||||
Ttl::from_secs(self.serial),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Err(errors)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------- SRV --------- */
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Srv {
|
|
||||||
pub server: String,
|
|
||||||
pub port: u16,
|
|
||||||
pub priority: u16,
|
|
||||||
pub weight: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: ToString> From<rdata::Srv<N>> for Srv {
|
|
||||||
fn from(record_data: rdata::Srv<N>) -> Self {
|
|
||||||
Srv {
|
|
||||||
server: record_data.target().to_string(),
|
|
||||||
priority: record_data.priority(),
|
|
||||||
weight: record_data.weight(),
|
|
||||||
port: record_data.port(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Srv {
|
|
||||||
fn parse_record(self) -> Result<rdata::Srv<Name<Vec<u8>>>, Vec<Error>> {
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
|
|
||||||
let server = parse_name!(self, server, "SRV", errors);
|
|
||||||
|
|
||||||
if errors.is_empty() {
|
|
||||||
Ok(rdata::Srv::new(
|
|
||||||
self.priority,
|
|
||||||
self.weight,
|
|
||||||
self.port,
|
|
||||||
server.unwrap(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Err(errors)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* --------- TXT --------- */
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Txt {
|
|
||||||
pub text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<O: AsRef<[u8]>> From<rdata::Txt<O>> for Txt {
|
|
||||||
fn from(record_data: rdata::Txt<O>) -> Self {
|
|
||||||
let mut concatenated_text = String::new();
|
|
||||||
for text in record_data.iter() {
|
|
||||||
for c in text {
|
|
||||||
// Escapes '\' and non printable chars
|
|
||||||
let c = Symbol::display_from_octet(*c);
|
|
||||||
write!(concatenated_text, "{}", c).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Txt {
|
|
||||||
text: concatenated_text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Txt {
|
|
||||||
fn parse_record(self) -> Result<rdata::Txt<Vec<u8>>, Vec<Error>> {
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
let data = append_errors!(validation::parse_txt_data(&self.text), errors, "/text");
|
|
||||||
let data = data.and_then(|data| {
|
|
||||||
push_error!(rdata::Txt::build_from_slice(&data).map_err(|e| {
|
|
||||||
Error::from(RecordParseError::RDataUnknown {
|
|
||||||
input: self.text,
|
|
||||||
field: "text".into(),
|
|
||||||
rtype: "TXT".into(),
|
|
||||||
}).with_cause(&e.to_string())
|
|
||||||
.with_path("/text")
|
|
||||||
}), errors)
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if errors.is_empty() {
|
|
||||||
Ok(data.unwrap())
|
|
||||||
} else {
|
|
||||||
Err(errors)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------- ParsedRData: domain traits impl --------- */
|
|
||||||
|
|
||||||
impl<Name, Octs> ParsedRData<Name, Octs> {
|
|
||||||
pub fn rtype(&self) -> Rtype {
|
|
||||||
match self {
|
|
||||||
ParsedRData::A(_) => Rtype::A,
|
|
||||||
ParsedRData::Aaaa(_) => Rtype::AAAA,
|
|
||||||
ParsedRData::Cname(_) => Rtype::CNAME,
|
|
||||||
ParsedRData::Mx(_) => Rtype::MX,
|
|
||||||
ParsedRData::Ns(_) => Rtype::NS,
|
|
||||||
ParsedRData::Ptr(_) => Rtype::PTR,
|
|
||||||
ParsedRData::Soa(_) => Rtype::SOA,
|
|
||||||
ParsedRData::Srv(_) => Rtype::SRV,
|
|
||||||
ParsedRData::Txt(_) => Rtype::TXT,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Name, Octs> RecordData for ParsedRData<Name, Octs> {
|
|
||||||
fn rtype(&self) -> Rtype {
|
|
||||||
ParsedRData::rtype(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs> for ParsedRData<ParsedName<Octs::Range<'a>>, Octs::Range<'a>> {
|
|
||||||
fn parse_rdata(
|
|
||||||
rtype: Rtype,
|
|
||||||
parser: &mut Parser<'a, Octs>,
|
|
||||||
) -> Result<Option<Self>, ParseError> {
|
|
||||||
let record = match rtype {
|
|
||||||
Rtype::A => ParsedRData::A(rdata::A::parse(parser)?),
|
|
||||||
Rtype::AAAA => ParsedRData::Aaaa(rdata::Aaaa::parse(parser)?),
|
|
||||||
Rtype::CNAME => ParsedRData::Cname(rdata::Cname::parse(parser)?),
|
|
||||||
Rtype::MX => ParsedRData::Mx(rdata::Mx::parse(parser)?),
|
|
||||||
Rtype::NS => ParsedRData::Ns(rdata::Ns::parse(parser)?),
|
|
||||||
Rtype::PTR => ParsedRData::Ptr(rdata::Ptr::parse(parser)?),
|
|
||||||
Rtype::SOA => ParsedRData::Soa(rdata::Soa::parse(parser)?),
|
|
||||||
Rtype::SRV => ParsedRData::Srv(rdata::Srv::parse(parser)?),
|
|
||||||
Rtype::TXT => ParsedRData::Txt(rdata::Txt::parse(parser)?),
|
|
||||||
_ => return Ok(None)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(record))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Name: ToName, Octs: AsRef<[u8]>> ComposeRecordData for ParsedRData<Name, Octs> {
|
|
||||||
fn rdlen(&self, compress: bool) -> Option<u16> {
|
|
||||||
match self {
|
|
||||||
ParsedRData::A(record_rdata) => record_rdata.rdlen(compress),
|
|
||||||
ParsedRData::Aaaa(record_rdata) => record_rdata.rdlen(compress),
|
|
||||||
ParsedRData::Cname(record_rdata) => record_rdata.rdlen(compress),
|
|
||||||
ParsedRData::Mx(record_rdata) => record_rdata.rdlen(compress),
|
|
||||||
ParsedRData::Ns(record_rdata) => record_rdata.rdlen(compress),
|
|
||||||
ParsedRData::Ptr(record_rdata) => record_rdata.rdlen(compress),
|
|
||||||
ParsedRData::Soa(record_rdata) => record_rdata.rdlen(compress),
|
|
||||||
ParsedRData::Srv(record_rdata) => record_rdata.rdlen(compress),
|
|
||||||
ParsedRData::Txt(record_rdata) => record_rdata.rdlen(compress),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compose_rdata<Target: Composer + ?Sized>(
|
|
||||||
&self,
|
|
||||||
target: &mut Target,
|
|
||||||
) -> Result<(), Target::AppendError> {
|
|
||||||
match self {
|
|
||||||
ParsedRData::A(record_rdata) => record_rdata.compose_rdata(target),
|
|
||||||
ParsedRData::Aaaa(record_rdata) => record_rdata.compose_rdata(target),
|
|
||||||
ParsedRData::Cname(record_rdata) => record_rdata.compose_rdata(target),
|
|
||||||
ParsedRData::Mx(record_rdata) => record_rdata.compose_rdata(target),
|
|
||||||
ParsedRData::Ns(record_rdata) => record_rdata.compose_rdata(target),
|
|
||||||
ParsedRData::Ptr(record_rdata) => record_rdata.compose_rdata(target),
|
|
||||||
ParsedRData::Soa(record_rdata) => record_rdata.compose_rdata(target),
|
|
||||||
ParsedRData::Srv(record_rdata) => record_rdata.compose_rdata(target),
|
|
||||||
ParsedRData::Txt(record_rdata) => record_rdata.compose_rdata(target),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compose_canonical_rdata<Target: Composer + ?Sized>(
|
|
||||||
&self,
|
|
||||||
target: &mut Target,
|
|
||||||
) -> Result<(), Target::AppendError> {
|
|
||||||
match self {
|
|
||||||
ParsedRData::A(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
|
||||||
ParsedRData::Aaaa(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
|
||||||
ParsedRData::Cname(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
|
||||||
ParsedRData::Mx(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
|
||||||
ParsedRData::Ns(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
|
||||||
ParsedRData::Ptr(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
|
||||||
ParsedRData::Soa(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
|
||||||
ParsedRData::Srv(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
|
||||||
ParsedRData::Txt(record_rdata) => record_rdata.compose_canonical_rdata(target),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,8 +3,8 @@ use axum::Json;
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use crate::errors::Error;
|
use crate::errors::Error;
|
||||||
use crate::ressouces::zone::{CreateZoneRequest, Zone};
|
use crate::resources::zone::{CreateZoneRequest, Zone};
|
||||||
use crate::ressouces::record::{AddRecordsRequest, Record, RecordList};
|
use crate::resources::record::{AddRecordsRequest, Record, RecordList};
|
||||||
|
|
||||||
|
|
||||||
pub async fn create_zone(
|
pub async fn create_zone(
|
||||||
|
|
|
@ -3,7 +3,7 @@ use serde_json::{Value, json};
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use crate::errors::Error;
|
use crate::errors::Error;
|
||||||
use crate::ressouces::zone::Zone;
|
use crate::resources::zone::Zone;
|
||||||
use crate::template::Template;
|
use crate::template::Template;
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue