summaryrefslogtreecommitdiff
path: root/packet/src/lib.rs
diff options
context:
space:
mode:
authorTyler Murphy <tylermurphy534@gmail.com>2023-03-01 01:13:25 -0500
committerTyler Murphy <tylermurphy534@gmail.com>2023-03-01 01:13:25 -0500
commitb7676d06363f71b4a856c5f5815f75e2bf7ca8ec (patch)
treeb7e188aa4e1abaa69b1b9ee4fb6cd5a0ad60637d /packet/src/lib.rs
parentgpl (diff)
downloadwrapper-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.rs157
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