diff options
author | Tyler Murphy <tylermurphy534@gmail.com> | 2023-03-01 01:13:25 -0500 |
---|---|---|
committer | Tyler Murphy <tylermurphy534@gmail.com> | 2023-03-01 01:13:25 -0500 |
commit | b7676d06363f71b4a856c5f5815f75e2bf7ca8ec (patch) | |
tree | b7e188aa4e1abaa69b1b9ee4fb6cd5a0ad60637d /packet/src/lib.rs | |
parent | gpl (diff) | |
download | wrapper-b7676d06363f71b4a856c5f5815f75e2bf7ca8ec.tar.gz wrapper-b7676d06363f71b4a856c5f5815f75e2bf7ca8ec.tar.bz2 wrapper-b7676d06363f71b4a856c5f5815f75e2bf7ca8ec.zip |
inital working dns
Diffstat (limited to 'packet/src/lib.rs')
-rw-r--r-- | packet/src/lib.rs | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/packet/src/lib.rs b/packet/src/lib.rs new file mode 100644 index 0000000..6c9a097 --- /dev/null +++ b/packet/src/lib.rs @@ -0,0 +1,157 @@ +use std::net::IpAddr; + +use self::{header::DnsHeader, question::DnsQuestion, record::DnsRecord, query::QueryType}; + +type Error = Box<dyn std::error::Error>; +pub type Result<T> = std::result::Result<T, Error>; + +mod buffer; +mod header; +mod query; +mod question; +mod record; +mod result; + +#[derive(Clone, Debug)] +pub struct Packet { + pub header: DnsHeader, + pub questions: Vec<DnsQuestion>, + pub answers: Vec<DnsRecord>, + pub authorities: Vec<DnsRecord>, + pub resources: Vec<DnsRecord>, +} + +pub use buffer::PacketBuffer; +pub use result::ResultCode; + +pub use query::QueryType as PacketType; +pub use question::DnsQuestion as PacketQuestion; + +impl Packet { + pub fn new() -> Packet { + Packet { + header: DnsHeader::new(), + questions: Vec::new(), + answers: Vec::new(), + authorities: Vec::new(), + resources: Vec::new(), + } + } + + pub fn from_buffer(buffer: &mut PacketBuffer) -> Result<Packet> { + let mut result = Packet::new(); + result.header.read(buffer)?; + + for _ in 0..result.header.questions { + let mut question = DnsQuestion::new("".to_string(), QueryType::UNKNOWN(0)); + question.read(buffer)?; + result.questions.push(question); + } + + for _ in 0..result.header.answers { + let rec = DnsRecord::read(buffer)?; + result.answers.push(rec); + } + for _ in 0..result.header.authoritative_entries { + let rec = DnsRecord::read(buffer)?; + result.authorities.push(rec); + } + for _ in 0..result.header.resource_entries { + let rec = DnsRecord::read(buffer)?; + result.resources.push(rec); + } + + Ok(result) + } + + pub fn write(&mut self, buffer: &mut PacketBuffer) -> Result<()> { + self.header.questions = self.questions.len() as u16; + self.header.answers = self.answers.len() as u16; + self.header.authoritative_entries = self.authorities.len() as u16; + self.header.resource_entries = self.resources.len() as u16; + + self.header.write(buffer)?; + + for question in &self.questions { + question.write(buffer)?; + } + for rec in &self.answers { + rec.write(buffer)?; + } + for rec in &self.authorities { + rec.write(buffer)?; + } + for rec in &self.resources { + rec.write(buffer)?; + } + + Ok(()) + } + + /// It's useful to be able to pick a random A record from a packet. When we + /// get multiple IP's for a single name, it doesn't matter which one we + /// choose, so in those cases we can now pick one at random. + pub fn get_random_a(&self) -> Option<IpAddr> { + self.answers + .iter() + .filter_map(|record| match record { + DnsRecord::A { addr, .. } => Some(IpAddr::V4(*addr)), + DnsRecord::AAAA { addr, .. } => Some(IpAddr::V6(*addr)), + _ => None, + }) + .next() + } + + /// A helper function which returns an iterator over all name servers in + /// the authorities section, represented as (domain, host) tuples + fn get_ns<'a>(&'a self, qname: &'a str) -> impl Iterator<Item = (&'a str, &'a str)> { + self.authorities + .iter() + // In practice, these are always NS records in well formed packages. + // Convert the NS records to a tuple which has only the data we need + // to make it easy to work with. + .filter_map(|record| match record { + DnsRecord::NS { domain, host, .. } => Some((domain.as_str(), host.as_str())), + _ => None, + }) + // Discard servers which aren't authoritative to our query + .filter(move |(domain, _)| qname.ends_with(*domain)) + } + + /// We'll use the fact that name servers often bundle the corresponding + /// A records when replying to an NS query to implement a function that + /// returns the actual IP for an NS record if possible. + pub fn get_resolved_ns(&self, qname: &str) -> Option<IpAddr> { + // Get an iterator over the nameservers in the authorities section + self.get_ns(qname) + // Now we need to look for a matching A record in the additional + // section. Since we just want the first valid record, we can just + // build a stream of matching records. + .flat_map(|(_, host)| { + self.resources + .iter() + // Filter for A records where the domain match the host + // of the NS record that we are currently processing + .filter_map(move |record| match record { + DnsRecord::A { domain, addr, .. } if domain == host => Some(IpAddr::V4(*addr)), + DnsRecord::AAAA { domain, addr, .. } if domain == host => Some(IpAddr::V6(*addr)), + _ => None, + }) + }) + .map(|addr| addr) + // Finally, pick the first valid entry + .next() + } + + /// However, not all name servers are as that nice. In certain cases there won't + /// be any A records in the additional section, and we'll have to perform *another* + /// lookup in the midst. For this, we introduce a method for returning the host + /// name of an appropriate name server. + pub fn get_unresolved_ns<'a>(&'a self, qname: &'a str) -> Option<&'a str> { + // Get an iterator over the nameservers in the authorities section + self.get_ns(qname) + .map(|(_, host)| host) + // Finally, pick the first valid entry + .next() + } +}
\ No newline at end of file |