use std::net::IpAddr; use self::{header::DnsHeader, question::DnsQuestion, record::DnsRecord, query::QueryType}; type Error = Box; pub type Result = std::result::Result; mod buffer; mod header; mod query; mod question; mod record; mod result; #[derive(Clone, Debug)] pub struct Packet { pub header: DnsHeader, pub questions: Vec, pub answers: Vec, pub authorities: Vec, pub resources: Vec, } 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() -> Self { Self { header: DnsHeader::new(), questions: Vec::new(), answers: Vec::new(), authorities: Vec::new(), resources: Vec::new(), } } pub fn from_buffer(buffer: &mut PacketBuffer) -> Result { let mut result = Self::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 { 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 { 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 { // 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, }) }) // 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() } }