Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions src/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,15 @@ impl Message {
req.extend_from_slice(&(question.class as u16).to_be_bytes());
}

// TODO Implement answers, etc types.
assert!(self.answers.is_empty());
assert!(self.authoritys.is_empty());
assert!(self.additionals.is_empty());
for record in &self.answers {
record.write(&mut req)?
}
for record in &self.authoritys {
record.write(&mut req)?
}
for record in &self.additionals {
record.write(&mut req)?
}

if let Some(e) = &self.extension {
e.write(&mut req)?
Expand All @@ -279,7 +284,7 @@ impl Message {
/// any compressed pointers) in bytes.
///
// TODO Support compression.
fn write_qname(buf: &mut Vec<u8>, domain: &str) -> io::Result<()> {
pub(crate) fn write_qname(buf: &mut Vec<u8>, domain: &str) -> io::Result<()> {
// Decode this label into the original unicode.
// TODO Switch to using our own idna::Config. (but we can't use disallowed_by_std3_ascii_rules).
let domain = match idna::domain_to_ascii(domain) {
Expand Down
108 changes: 89 additions & 19 deletions src/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,43 @@ pub type PTR = String;
pub struct TXT(pub Vec<Vec<u8>>);

impl Record {
pub(crate) fn write(&self, bfr: &mut Vec<u8>) -> io::Result<()> {
Message::write_qname(bfr, &self.name)?;
bfr.extend_from_slice(&(self.r#type() as u16).to_be_bytes());
bfr.extend_from_slice(&(self.class as u16).to_be_bytes());
bfr.extend_from_slice(&(self.ttl.as_secs() as u32).to_be_bytes());

// At this point we don't know the total length of this record
// Create a temporary buffer that's going to be added to the final
// buffer later

let mut record: Vec<u8> = Vec::new();

match &self.resource {
Resource::A(ip4) => record.extend_from_slice(&ip4.octets()),
Resource::AAAA(ip6) => record.extend_from_slice(&ip6.octets()),

Resource::NS(str) => Message::write_qname(&mut record, &str)?,
Resource::SOA(soa) => soa.write(&mut record)?,
Resource::CNAME(str) => Message::write_qname(&mut record, &str)?,
Resource::PTR(str) => Message::write_qname(&mut record, &str)?,
Resource::MX(mx) => mx.write(&mut record)?,
Resource::TXT(txt) => txt.write(&mut record),
Resource::SPF(spf) => spf.write(&mut record),
Resource::SRV(srv) => srv.write(&mut record)?,

Resource::OPT | Resource::ANY => {
bail!(InvalidData, "invalid record type '{}'", self.r#type());
}
};

// Merge the record buffer into the final one
bfr.extend_from_slice(&(record.len() as u16).to_be_bytes());
bfr.extend(record);

Ok(())
}

pub(crate) fn parse(
cur: &mut Cursor<&[u8]>,
name: String,
Expand Down Expand Up @@ -74,8 +111,8 @@ impl Record {
Type::CNAME => Resource::CNAME(record.read_qname()?),
Type::PTR => Resource::PTR(record.read_qname()?),
Type::MX => Resource::MX(MX::parse(&mut record)?),
Type::TXT => Resource::TXT(parse_txt(&mut record)?),
Type::SPF => Resource::SPF(parse_txt(&mut record)?),
Type::TXT => Resource::TXT(TXT::parse(&mut record)?),
Type::SPF => Resource::SPF(TXT::parse(&mut record)?),
Type::SRV => Resource::SRV(SRV::parse(&mut record)?),

// This should never appear in a answer record unless we have invalid data.
Expand Down Expand Up @@ -177,25 +214,34 @@ fn parse_aaaa(cur: &mut Cursor<&[u8]>, class: Class) -> io::Result<AAAA> {
}
}

fn parse_txt(cur: &mut Cursor<&[u8]>) -> io::Result<TXT> {
let mut txts = Vec::new();

loop {
// Keep reading until EOF is reached.
let len = match cur.read_u8() {
Ok(len) => len,
Err(e) => match e.kind() {
io::ErrorKind::UnexpectedEof => break,
_ => return Err(e),
},
};

let mut txt = vec![0; len.into()];
cur.read_exact(&mut txt)?;
txts.push(txt)
impl TXT {
pub(crate) fn parse(cur: &mut Cursor<&[u8]>) -> io::Result<TXT> {
let mut txts = Vec::new();

loop {
// Keep reading until EOF is reached.
let len = match cur.read_u8() {
Ok(len) => len,
Err(e) => match e.kind() {
io::ErrorKind::UnexpectedEof => break,
_ => return Err(e),
},
};

let mut txt = vec![0; len.into()];
cur.read_exact(&mut txt)?;
txts.push(txt)
}

Ok(TXT(txts))
}

Ok(TXT(txts))
pub(crate) fn write(&self, bfr: &mut Vec<u8>) {
for v in &self.0 {
bfr.extend_from_slice(&(v.len() as u8).to_be_bytes());
bfr.extend(v);
}
}
}

impl SOA {
Expand All @@ -221,6 +267,18 @@ impl SOA {
})
}

pub(crate) fn write(&self, bfr: &mut Vec<u8>) -> io::Result<()> {
Message::write_qname(bfr, &self.mname)?;
Message::write_qname(bfr, &Self::email_to_rname(&self.rname).unwrap())?;

bfr.extend_from_slice(&self.serial.to_be_bytes());
bfr.extend_from_slice(&(self.refresh.as_secs() as u32).to_be_bytes());
bfr.extend_from_slice(&(self.retry.as_secs() as u32).to_be_bytes());
bfr.extend_from_slice(&(self.expire.as_secs() as u32).to_be_bytes());
bfr.extend_from_slice(&(self.minimum.as_secs() as u32).to_be_bytes());
Ok(())
}

/// Converts rnames to email address, for example, "admin.example.com" is
/// converted to "[email protected]", per the rules in
/// https://datatracker.ietf.org/doc/html/rfc1035#section-8
Expand Down Expand Up @@ -275,6 +333,11 @@ impl MX {
exchange,
})
}

pub(crate) fn write(&self, bfr: &mut Vec<u8>) -> io::Result<()> {
bfr.extend_from_slice(&self.preference.to_be_bytes());
Message::write_qname(bfr, &self.exchange)
}
}

impl SRV {
Expand All @@ -292,6 +355,13 @@ impl SRV {
name,
})
}

pub(crate) fn write(&self, bfr: &mut Vec<u8>) -> io::Result<()> {
bfr.extend_from_slice(&self.priority.to_be_bytes());
bfr.extend_from_slice(&self.weight.to_be_bytes());
bfr.extend_from_slice(&self.port.to_be_bytes());
Message::write_qname(bfr, &self.name)
}
}

impl From<&str> for TXT {
Expand Down