diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 0a0dabc..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,109 +0,0 @@ -[root] -name = "dnsguide" -version = "0.1.0" -dependencies = [ - "tango 0.5.0 (git+https://github.com/pnkfelix/tango)", -] - -[[package]] -name = "filetime" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "libc" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "matches" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rand" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rustc-serialize" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "tango" -version = "0.5.0" -source = "git+https://github.com/pnkfelix/tango#9a673b5e74bf47e43c70fecabb5f10791d374e03" -dependencies = [ - "filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", - "walkdir 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-bidi" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-normalization" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "url" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "uuid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "uuid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "walkdir" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index b5f2954..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "dnsguide" -version = "0.1.0" -authors = ["Emil Hernvall "] - -build = "tango-build.rs" - -[build-dependencies.tango] -git = "https://github.com/pnkfelix/tango" - -[lib] -name = "lib" diff --git a/README.md b/README.md index cf59f69..59d52ae 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ Building a DNS server in Rust ============================= -To me, DNS is one the most exciting aspects of the Internet. Before it was -invented, everyone on the internet - which admittedly wasn't that many at that -stage - relied on a shared file called HOSTS.TXT, maintained by the Stanford +The internet has a rich conceptual foundation, with many exciting ideas that +enables it to function as we know it. One of the really cool ones is DNS. Before +it was invented, everyone on the internet - which admittedly wasn't that many at +that stage - relied on a shared file called HOSTS.TXT, maintained by the Stanford Research Institute. This file was synchronized manually through FTP, and as the number of hosts grew, so did the rate of change and the unfeasibility of the system. In 1983, Paul Mockapetris set out to find a long term solution to the @@ -18,5 +19,8 @@ a truthful chronicle of that journey, but rather an idealized version of it, without all the detours I ended up taking. We'll gradually implement a full DNS server, starting from first principles. - * [Chapter 1 - The DNS protocol](/src/bin/chapter1.md) - * [Chapter 2 - Building a stub resolver](/src/bin/chapter2.md) + * [Chapter 1 - The DNS protocol](/chapter1.md) + * [Chapter 2 - Building a stub resolver](/chapter2.md) + * [Chapter 3 - Adding more Record Types](/chapter3.md) + * [Chapter 4 - Baby's first DNS server](/chapter4.md) + * [Chapter 5 - Recursive Resolve](/chapter5.md) diff --git a/src/bin/chapter1.md b/chapter1.md similarity index 98% rename from src/bin/chapter1.md rename to chapter1.md index 267a420..db5225f 100644 --- a/src/bin/chapter1.md +++ b/chapter1.md @@ -1,5 +1,5 @@ -The DNS protocol ----------------- +1. The DNS protocol +------------------- We'll start out by investigating the DNS protocol and use our knowledge thereof to implement a simple client. @@ -92,12 +92,12 @@ a lookup using the `dig` tool: ;; ANSWER SECTION: google.com. 204 IN A 172.217.18.142 +``` ;; Query time: 0 msec ;; SERVER: 192.168.1.1#53(192.168.1.1) ;; WHEN: Wed Jul 06 13:24:19 CEST 2016 ;; MSG SIZE rcvd: 44 -``` We're using the `+noedns` flag to make sure we stick to the original format. There are a few things of note in the output above: @@ -316,17 +316,9 @@ on to the record type. ### BytePacketBuffer -Now finally we know enough to start implementing! Some tools are needed: - -```rust -use std::io::{Result, Read}; -use std::io::{Error, ErrorKind}; -use std::net::Ipv4Addr; -use std::fs::File; -``` - -The first order of business is that we need some convenient method for manipulating -the packets. For this, we'll use a `struct` called `BytePacketBuffer`. +Now finally we know enough to start implementing! The first order of business is +that we need some convenient method for manipulating the packets. For this, +we'll use a `struct` called `BytePacketBuffer`. ```rust pub struct BytePacketBuffer { @@ -490,7 +482,7 @@ Read another byte, and calculate the jump offset: Indicate that a jump was performed. ```rust - jumped = true; + jumped = true; ``` Restart the loop and retry at the new position. @@ -800,7 +792,7 @@ impl DnsRecord { ### DnsPacket -Finally, we're going to compose them in a struct called `DnsPacket`: +Finally, let's put it all together in a struct called `DnsPacket`: ```rust #[derive(Clone, Debug)] diff --git a/src/bin/chapter2.md b/chapter2.md similarity index 72% rename from src/bin/chapter2.md rename to chapter2.md index 0f9db66..149f3df 100644 --- a/src/bin/chapter2.md +++ b/chapter2.md @@ -1,5 +1,5 @@ -Building a stub resolver ------------------------- +2. Building a stub resolver +--------------------------- While it's slightly satisfying to know that we're able to succesfully parse DNS packets, it's not much use to just read them off disk. As our next step, we'll @@ -15,61 +15,49 @@ packets, but also write them. To do so, we'll need to extend `BytePacketBuffer` with some additional methods: ```rust -extern crate dnsguide; -use dnsguide::chapter2::*; -``` +impl BytePacketBuffer { -```rust -trait WriteableBuffer { - fn write(&mut self, val: u8) -> Result; - fn write_u8(&mut self, val: u8) -> Result; - fn write_u16(&mut self, val: u16) -> Result; - fn write_u32(&mut self, val: u32) -> Result; - fn write_qname(&mut self, qname: &str) -> Result; -} + - snip - -impl WriteableBuffer for BytePacketBuffer { - - fn write(&mut self, val: u8) -> Result { + fn write(&mut self, val: u8) -> Result<()> { if self.pos >= 512 { return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); } self.buf[self.pos] = val; self.pos += 1; - Ok(1) + Ok(()) } - fn write_u8(&mut self, val: u8) -> Result { + fn write_u8(&mut self, val: u8) -> Result<()> { try!(self.write(val)); - Ok(1) + Ok(()) } - fn write_u16(&mut self, val: u16) -> Result { + fn write_u16(&mut self, val: u16) -> Result<()> { try!(self.write((val >> 8) as u8)); try!(self.write((val & 0xFF) as u8)); - Ok(2) + Ok(()) } - fn write_u32(&mut self, val: u32) -> Result { + fn write_u32(&mut self, val: u32) -> Result<()> { try!(self.write(((val >> 24) & 0xFF) as u8)); try!(self.write(((val >> 16) & 0xFF) as u8)); try!(self.write(((val >> 8) & 0xFF) as u8)); try!(self.write(((val >> 0) & 0xFF) as u8)); - Ok(4) + Ok(()) } ``` We'll also need a function for writing query names in labeled form: ```rust - fn write_qname(&mut self, qname: &str) -> Result { + fn write_qname(&mut self, qname: &str) -> Result<()> { let split_str = qname.split('.').collect::>(); - let mut bytes_written = 0; for label in split_str { let len = label.len(); if len > 0x34 { @@ -77,18 +65,14 @@ We'll also need a function for writing query names in labeled form: } try!(self.write_u8(len as u8)); - bytes_written += 1; - for b in label.as_bytes() { try!(self.write_u8(*b)); - bytes_written += 1; } } try!(self.write_u8(0)); - bytes_written += 1; - Ok(bytes_written) + Ok(()) } } // End of BytePacketBuffer @@ -100,13 +84,11 @@ Building on our new functions we can extend our protocol representation structs. Starting with `DnsHeader`: ```rust -trait BufferWriteable { - fn write(&self, buffer: &mut BytePacketBuffer) -> Result; -} +impl DnsHeader { -impl BufferWriteable for DnsHeader { + - snip - - fn write(&self, buffer: &mut BytePacketBuffer) -> Result { + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> { try!(buffer.write_u16(self.id)); try!(buffer.write_u8( ((self.recursion_desired as u8)) | @@ -126,7 +108,7 @@ impl BufferWriteable for DnsHeader { try!(buffer.write_u16(self.authoritative_entries)); try!(buffer.write_u16(self.resource_entries)); - Ok(12) + Ok(()) } } @@ -137,19 +119,19 @@ impl BufferWriteable for DnsHeader { Moving on to `DnsQuestion`: ```rust -impl BufferWriteable for DnsQuestion { +impl DnsQuestion { - fn write(&self, buffer: &mut BytePacketBuffer) -> Result { + - snip - - let mut bytes_written = 0; + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> { - bytes_written += try!(buffer.write_qname(&self.name)); + try!(buffer.write_qname(&self.name)); let typenum = self.qtype.to_num(); - bytes_written += try!(buffer.write_u16(typenum)); - bytes_written += try!(buffer.write_u16(1)); + try!(buffer.write_u16(typenum)); + try!(buffer.write_u16(1)); - Ok(bytes_written) + Ok(()) } } @@ -161,9 +143,11 @@ impl BufferWriteable for DnsQuestion { quite a bit of code here to handle different record types: ```rust -impl BufferWriteable for DnsRecord { +impl DnsRecord { - fn write(&self, buffer: &mut BytePacketBuffer) -> Result { + - snip - + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result { let start_pos = buffer.pos(); @@ -194,38 +178,36 @@ impl BufferWriteable for DnsRecord { ### Extending DnsPacket for writing -Based on what we've accomplished, we can amend `DnsPacket` with its own -`write` function: +Putting it all together in `DnsPacket`: ```rust -impl BufferWriteable for DnsPacket { +impl DnsPacket { - fn write(&self, buffer: &mut BytePacketBuffer) -> Result + - snip - + + pub fn write(&mut self, buffer: &mut BytePacketBuffer) -> Result<()> { - let mut header = self.header.clone(); - header.questions = self.questions.len() as u16; - header.answers = self.answers.len() as u16; - header.authoritative_entries = self.authorities.len() as u16; - header.resource_entries = self.resources.len() as u16; + 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; - let mut bytes_written = 0; - - bytes_written += try!(header.write(buffer)); + try!(self.header.write(buffer)); for question in &self.questions { - bytes_written += try!(question.write(buffer)); + try!(question.write(buffer)); } for rec in &self.answers { - bytes_written += try!(rec.write(buffer)); + try!(rec.write(buffer)); } for rec in &self.authorities { - bytes_written += try!(rec.write(buffer)); + try!(rec.write(buffer)); } for rec in &self.resources { - bytes_written += try!(rec.write(buffer)); + try!(rec.write(buffer)); } - Ok(bytes_written) + Ok(()) } } @@ -249,6 +231,7 @@ fn main() { let socket = UdpSocket::bind(("0.0.0.0", 43210)).unwrap(); ``` + Next we'll build our query packet. It's important that we remember to set the `recursion_desired` flag. As noted earlier, the packet id is arbitrary. @@ -264,8 +247,8 @@ Next we'll build our query packet. It's important that we remember to set the We can use our new write method to write the packet to a buffer... ```rust - let mut req_buffer = BytePacketBuffer::new(); - packet.write(&mut req_buffer).unwrap(); + let mut req_buffer = BytePacketBuffer::new(); + packet.write(&mut req_buffer).unwrap(); ``` ...and send it off to the server using our socket: diff --git a/chapter3.md b/chapter3.md new file mode 100644 index 0000000..295adbc --- /dev/null +++ b/chapter3.md @@ -0,0 +1,472 @@ +3. Adding more Record Types +--------------------------- + +Let's use our program to do a lookup for ''yahoo.com''. + +```rust +let qname = "www.yahoo.com"; +``` + +Running it yields: + +```text +DnsHeader { + id: 6666, + recursion_desired: true, + truncated_message: false, + authoritative_answer: false, + opcode: 0, + response: true, + rescode: NOERROR, + checking_disabled: false, + authed_data: false, + z: false, + recursion_available: true, + questions: 1, + answers: 3, + authoritative_entries: 0, + resource_entries: 0 +} +DnsQuestion { + name: "www.yahoo.com", + qtype: A +} +UNKNOWN { + domain: "www.yahoo.com", + qtype: 5, + data_len: 15, + ttl: 259 +} +A { + domain: "fd-fp3.wg1.b.yahoo.com", + addr: 46.228.47.115, + ttl: 19 +} +A { + domain: "fd-fp3.wg1.b.yahoo.com", + addr: 46.228.47.114, + ttl: 19 +} +``` + +That's odd -- we're getting an UNKNOWN record as well as two A records. The +UNKNOWN record, with query type 5 is a CNAME. There are quite a few DNS record +types, many of which doesn't see any use in practice. That said, let's have +a look at a few essential ones: + +| ID | Name | Description | Encoding | +| --- | ----- | -------------------------------------------------------- | ------------------------------------------------ | +| 1 | A | Alias - Mapping names to IP addresses | Preamble + Four bytes for IPv4 adress | +| 2 | NS | Name Server - The DNS server address for a domain | Preamble + Label Sequence | +| 5 | CNAME | Canonical Name - Maps names to names | Preamble + Label Sequence | +| 15 | MX | Mail eXchange - The host of the mail server for a domain | Preamble + 2-bytes for priority + Label Sequence | +| 28 | AAAA | IPv6 alias | Premable + Sixteen bytes for IPv6 adress | + +### Extending QueryType with more record types + +Let's go ahead and add them to our code! First we'll update our `QueryType` +enum: + +```rust +#[derive(PartialEq,Eq,Debug,Clone,Hash,Copy)] +pub enum QueryType { + UNKNOWN(u16), + A, // 1 + NS, // 2 + CNAME, // 5 + MX, // 15 + AAAA, // 28 +} +``` + +We'll also need to change our utility functions. + +```rust +impl QueryType { + pub fn to_num(&self) -> u16 { + match *self { + QueryType::UNKNOWN(x) => x, + QueryType::A => 1, + QueryType::NS => 2, + QueryType::CNAME => 5, + QueryType::MX => 15, + QueryType::AAAA => 28, + } + } + + pub fn from_num(num: u16) -> QueryType { + match num { + 1 => QueryType::A, + 2 => QueryType::NS, + 5 => QueryType::CNAME, + 15 => QueryType::MX, + 28 => QueryType::AAAA, + _ => QueryType::UNKNOWN(num) + } + } +} +``` + +### Extending DnsRecord for reading new record types + +Now we need a way of holding the data for these records, so we'll make some +modifications to `DnsRecord`. + +```rust +#[derive(Debug,Clone,PartialEq,Eq,Hash,PartialOrd,Ord)] +#[allow(dead_code)] +pub enum DnsRecord { + UNKNOWN { + domain: String, + qtype: u16, + data_len: u16, + ttl: u32 + }, // 0 + A { + domain: String, + addr: Ipv4Addr, + ttl: u32 + }, // 1 + NS { + domain: String, + host: String, + ttl: u32 + }, // 2 + CNAME { + domain: String, + host: String, + ttl: u32 + }, // 5 + MX { + domain: String, + priority: u16, + host: String, + ttl: u32 + }, // 15 + AAAA { + domain: String, + addr: Ipv6Addr, + ttl: u32 + }, // 28 +} +``` + +Here comes the bulk of the work. We'll need to extend the functions for writing +and reading records. Starting with read, we amend it with additional code for +each record type. First off, we've got the common preamble: + +```rust +pub fn read(buffer: &mut BytePacketBuffer) -> Result { + let mut domain = String::new(); + try!(buffer.read_qname(&mut domain)); + + let qtype_num = try!(buffer.read_u16()); + let qtype = QueryType::from_num(qtype_num); + let _ = try!(buffer.read_u16()); + let ttl = try!(buffer.read_u32()); + let data_len = try!(buffer.read_u16()); +``` + +After which we handle each record type separately, starting with the A record +type which remains the same as before. + +```rust + match qtype { + QueryType::A => { + let raw_addr = try!(buffer.read_u32()); + let addr = Ipv4Addr::new(((raw_addr >> 24) & 0xFF) as u8, + ((raw_addr >> 16) & 0xFF) as u8, + ((raw_addr >> 8) & 0xFF) as u8, + ((raw_addr >> 0) & 0xFF) as u8); + + Ok(DnsRecord::A { + domain: domain, + addr: addr, + ttl: ttl + }) + }, +``` + +The AAAA record type follows the same logic, but with more numbers to keep +track off. + +```rust + QueryType::AAAA => { + let raw_addr1 = try!(buffer.read_u32()); + let raw_addr2 = try!(buffer.read_u32()); + let raw_addr3 = try!(buffer.read_u32()); + let raw_addr4 = try!(buffer.read_u32()); + let addr = Ipv6Addr::new(((raw_addr1 >> 16) & 0xFFFF) as u16, + ((raw_addr1 >> 0) & 0xFFFF) as u16, + ((raw_addr2 >> 16) & 0xFFFF) as u16, + ((raw_addr2 >> 0) & 0xFFFF) as u16, + ((raw_addr3 >> 16) & 0xFFFF) as u16, + ((raw_addr3 >> 0) & 0xFFFF) as u16, + ((raw_addr4 >> 16) & 0xFFFF) as u16, + ((raw_addr4 >> 0) & 0xFFFF) as u16); + + Ok(DnsRecord::AAAA { + domain: domain, + addr: addr, + ttl: ttl + }) + }, +``` + +NS and CNAME both have the same structure. + +```rust + QueryType::NS => { + let mut ns = String::new(); + try!(buffer.read_qname(&mut ns)); + + Ok(DnsRecord::NS { + domain: domain, + host: ns, + ttl: ttl + }) + }, + QueryType::CNAME => { + let mut cname = String::new(); + try!(buffer.read_qname(&mut cname)); + + Ok(DnsRecord::CNAME { + domain: domain, + host: cname, + ttl: ttl + }) + }, +``` + +MX is close to the previous two, but with one extra field for priority. + +```rust + QueryType::MX => { + let priority = try!(buffer.read_u16()); + let mut mx = String::new(); + try!(buffer.read_qname(&mut mx)); + + Ok(DnsRecord::MX { + domain: domain, + priority: priority, + host: mx, + ttl: ttl + }) + }, +``` + +And we end with some code for handling unknown record types, as before. + +```rust + QueryType::UNKNOWN(_) => { + try!(buffer.step(data_len as usize)); + + Ok(DnsRecord::UNKNOWN { + domain: domain, + qtype: qtype_num, + data_len: data_len, + ttl: ttl + }) + } + } +} +``` + +It's a bit of a mouthful, but individually not much more complex than what we +had. + +### Extending BytePacketBuffer for setting values in place + +Before we move on to writing records, we'll have to add two more functions to +`BytePacketBuffer`: + +```rust +impl BytePacketBuffer { + + - snip - + + fn set(&mut self, pos: usize, val: u8) -> Result<()> { + self.buf[pos] = val; + + Ok(()) + } + + fn set_u16(&mut self, pos: usize, val: u16) -> Result<()> { + try!(self.set(pos,(val >> 8) as u8)); + try!(self.set(pos+1,(val & 0xFF) as u8)); + + Ok(()) + } + +} +``` + +### Extending DnsRecord for writing new record types + +Now we can amend `DnsRecord::write`. Here's our new function: + +```rust +pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result { + + let start_pos = buffer.pos(); + + match *self { + DnsRecord::A { ref domain, ref addr, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::A.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + try!(buffer.write_u16(4)); + + let octets = addr.octets(); + try!(buffer.write_u8(octets[0])); + try!(buffer.write_u8(octets[1])); + try!(buffer.write_u8(octets[2])); + try!(buffer.write_u8(octets[3])); + }, + DnsRecord::NS { ref domain, ref host, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::NS.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + + let pos = buffer.pos(); + try!(buffer.write_u16(0)); + + try!(buffer.write_qname(host)); + + let size = buffer.pos() - (pos + 2); + try!(buffer.set_u16(pos, size as u16)); + }, + DnsRecord::CNAME { ref domain, ref host, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::CNAME.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + + let pos = buffer.pos(); + try!(buffer.write_u16(0)); + + try!(buffer.write_qname(host)); + + let size = buffer.pos() - (pos + 2); + try!(buffer.set_u16(pos, size as u16)); + }, + DnsRecord::MX { ref domain, priority, ref host, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::MX.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + + let pos = buffer.pos(); + try!(buffer.write_u16(0)); + + try!(buffer.write_u16(priority)); + try!(buffer.write_qname(host)); + + let size = buffer.pos() - (pos + 2); + try!(buffer.set_u16(pos, size as u16)); + }, + DnsRecord::AAAA { ref domain, ref addr, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::AAAA.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + try!(buffer.write_u16(16)); + + for octet in &addr.segments() { + try!(buffer.write_u16(*octet)); + } + }, + DnsRecord::UNKNOWN { .. } => { + println!("Skipping record: {:?}", self); + } + } + + Ok(buffer.pos() - start_pos) +} +``` + +Again, quite a bit of extra code, but thankfully the last thing we've got to +do. We're still not using the write part, but it'll come in handy once we write +our server. + +### Testing the new record types + +Now we're ready to retry our ''yahoo.com'' query: + +```text +DnsHeader { + id: 6666, + recursion_desired: true, + truncated_message: false, + authoritative_answer: false, + opcode: 0, + response: true, + rescode: NOERROR, + checking_disabled: false, + authed_data: false, + z: false, + recursion_available: true, + questions: 1, + answers: 3, + authoritative_entries: 0, + resource_entries: 0 +} +DnsQuestion { + name: "www.yahoo.com", + qtype: A +} +CNAME { + domain: "www.yahoo.com", + host: "fd-fp3.wg1.b.yahoo.com", + ttl: 3 +} +A { + domain: "fd-fp3.wg1.b.yahoo.com", + addr: 46.228.47.115, + ttl: 19 +} +A { + domain: "fd-fp3.wg1.b.yahoo.com", + addr: 46.228.47.114, + ttl: 19 +} +``` + +For good measure, let's try doing an MX lookup as well: + +```rust +let qname = "yahoo.com"; +let qtype = QueryType::MX; +``` + +Which yields: + +```text +- snip - +DnsQuestion { + name: "yahoo.com", + qtype: MX +} +MX { + domain: "yahoo.com", + priority: 1, + host: "mta6.am0.yahoodns.net", + ttl: 1794 +} +MX { + domain: "yahoo.com", + priority: 1, + host: "mta7.am0.yahoodns.net", + ttl: 1794 +} +MX { + domain: "yahoo.com", + priority: 1, + host: "mta5.am0.yahoodns.net", + ttl: 1794 +} +``` + +Encouraging! diff --git a/chapter4.md b/chapter4.md new file mode 100644 index 0000000..3157858 --- /dev/null +++ b/chapter4.md @@ -0,0 +1,352 @@ +4. Baby's first DNS server +-------------------------- + +Haven gotten this far, we're ready to make our first attempt at writing an +actual server. In reality, DNS servers fullfil two different purposes: + + * Authoritative Server - A DNS server hosting one or more "zones". For + instance, the authoritative servers for the zone google.com are + ns1.google.com, ns2.google.com, ns3.google.com and ns4.google.com. + * Caching Server - A DNS server that services DNS lookups by first checking + its cache to see if it already knows of the record being requested, and if + not performing a recursive lookup to figure it out. This includes the DNS + server that is likely running on your home router as well as the DNS server + that your ISP assigns to you through DHCP, and Google's public DNS servers + 8.8.8.8 and 8.8.4.4. + +Strictly speaking, there's nothing to stop a server from doing both things, but +in pracice these two roles are typically mutually exclusive. This also explains +the significance of the flags `RD` (Recursion Desired) and `RA` (Recursion +Available) in the packet header -- a stub resolver querying a caching server +will set the `RD` flag, and since the server allows such queries it will +perform the lookup and send a reply with the `RA` flag set. This won't work for +an Authoritative Server which will only reply to queries relating to the zones +hosted, and as such will send an error response to any queries with the `RD` +flag set. + +Don't take my word for it, though! Let's verify that this is the case. First +off, let's use `8.8.8.8` for looking up *yahoo.com*: + +```text +# dig @8.8.8.8 yahoo.com + +; <<>> DiG 9.10.3-P4-Ubuntu <<>> +recurse @8.8.8.8 yahoo.com +; (1 server found) +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53231 +;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1 + +;; OPT PSEUDOSECTION: +; EDNS: version: 0, flags:; udp: 512 +;; QUESTION SECTION: +;yahoo.com. IN A + +;; ANSWER SECTION: +yahoo.com. 1051 IN A 98.138.253.109 +yahoo.com. 1051 IN A 98.139.183.24 +yahoo.com. 1051 IN A 206.190.36.45 + +;; Query time: 1 msec +;; SERVER: 8.8.8.8#53(8.8.8.8) +;; WHEN: Fri Jul 08 11:43:55 CEST 2016 +;; MSG SIZE rcvd: 86 +``` + +This works as expected. Now let's try sending the same query to one of the +servers hosting the *google.com* zone: + +```text +# dig @ns1.google.com yahoo.com + +; <<>> DiG 9.10.3-P4-Ubuntu <<>> +recurse @ns1.google.com yahoo.com +; (1 server found) +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 12034 +;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0 +;; WARNING: recursion requested but not available + +;; QUESTION SECTION: +;yahoo.com. IN A + +;; Query time: 10 msec +;; SERVER: 216.239.32.10#53(216.239.32.10) +;; WHEN: Fri Jul 08 11:44:07 CEST 2016 +;; MSG SIZE rcvd: 27 +``` + +Notice how the status of the response says `REFUSED`! `dig` also warns us that +while the `RD` flag was set in the query, the server didn't set it in the +response. We can still use the same server for *google.com*, however: + +```text +dig @ns1.google.com google.com <<< + +; <<>> DiG 9.10.3-P4-Ubuntu <<>> +recurse @ns1.google.com google.com +; (1 server found) +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28058 +;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 +;; WARNING: recursion requested but not available + +;; QUESTION SECTION: +;google.com. IN A + +;; ANSWER SECTION: +google.com. 300 IN A 216.58.211.142 + +;; Query time: 10 msec +;; SERVER: 216.239.32.10#53(216.239.32.10) +;; WHEN: Fri Jul 08 11:46:27 CEST 2016 +;; MSG SIZE rcvd: 44 +``` + +No error this time -- however, `dig` still warns us that recursion is +unavailable. We can explicitly unset it using `+norecurse` which gets rid of +the warning: + +```text +# dig +norecurse @ns1.google.com google.com + +; <<>> DiG 9.10.3-P4-Ubuntu <<>> +norecurse @ns1.google.com google.com +; (1 server found) +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 15850 +;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + +;; QUESTION SECTION: +;google.com. IN A + +;; ANSWER SECTION: +google.com. 300 IN A 216.58.211.142 + +;; Query time: 10 msec +;; SERVER: 216.239.32.10#53(216.239.32.10) +;; WHEN: Fri Jul 08 11:47:52 CEST 2016 +;; MSG SIZE rcvd: 44 +``` + +This final query is the type of query that we'd expect to see a caching server +send as part of recursively resolving the name. + +For our first foray into writing our own server, we'll do something even +simpler by implementing a server that simply forwards queries to another +caching server, i.e. a "DNS proxy server". Having already done most of the hard +work, it's a rather quick effort! + +### Separating lookup into a separate function + +We'll start out by doing some quick refactoring, moving our lookup code into +a separate function. This is for the most part the same code as we had in our +`main` function in the previous chapter, with the only change being that we +handle errors gracefully using `try!`. + +```rust +fn lookup(qname: &str, qtype: QueryType, server: (&str, u16)) -> Result { + let socket = try!(UdpSocket::bind(("0.0.0.0", 43210))); + + let mut packet = DnsPacket::new(); + + packet.header.id = 6666; + packet.header.questions = 1; + packet.header.recursion_desired = true; + packet.questions.push(DnsQuestion::new(qname.to_string(), qtype)); + + let mut req_buffer = BytePacketBuffer::new(); + packet.write(&mut req_buffer).unwrap(); + try!(socket.send_to(&req_buffer.buf[0..req_buffer.pos], server)); + + let mut res_buffer = BytePacketBuffer::new(); + socket.recv_from(&mut res_buffer.buf).unwrap(); + + DnsPacket::from_buffer(&mut res_buffer) +} +``` + +### Implementing our first server + +Now we'll write our server code. First, we need get some things in order. + +```rust +fn main() { + // Forward queries to Google's public DNS + let server = ("8.8.8.8", 53); + + // Bind an UDP socket on port 2053 + let socket = UdpSocket::bind(("0.0.0.0", 2053)).unwrap(); +``` + +For now, queries are handled sequentially, so an infinite loop for servicing +requests is initiated. + +```rust + loop { +``` + +With a socket ready, we can go ahead and read a packet. This will block until +one is received. + +```rust + let mut req_buffer = BytePacketBuffer::new(); + let (_, src) = match socket.recv_from(&mut req_buffer.buf) { + Ok(x) => x, + Err(e) => { + println!("Failed to read from UDP socket: {:?}", e); + continue; + } + }; +``` + +Here we use match to safely unwrap the `Result`. If everything's as expected, +the raw bytes are simply returned, and if not it'll abort by restarting the +loop and waiting for the next request. The `recv_from` function will write the +data into the provided buffer, and return the length of the data read as well +as the source adress. We're not interested in the length, but we need to keep +track of the source in order to send our reply later on. + +Next, `DnsPacket::from_buffer` is used to parse the raw bytes into +a `DnsPacket`. It uses the same error handling idiom as the previous statement. + +```rust + let request = match DnsPacket::from_buffer(&mut req_buffer) { + Ok(x) => x, + Err(e) => { + println!("Failed to parse UDP query packet: {:?}", e); + continue; + } + }; +``` + +At this stage, the response packet is created and initiated. + +```rust + let mut packet = DnsPacket::new(); + packet.header.id = request.header.id; + packet.header.recursion_desired = true; + packet.header.recursion_available = true; + packet.header.response = true; +``` + +Being mindful of how unreliable input data from arbitrary senders can be, we +need make sure that a question is actually present. If not, we return `FORMERR` +to indicate that the sender made something wrong. + +```rust + if request.questions.is_empty() { + packet.header.rescode = ResultCode::FORMERR; + } +``` + +Usually a question will be present, though. + +```rust + else { + let question = &request.questions[0]; + println!("Received query: {:?}", question); +``` + +Since all is set up and as expected, the query can be forwarded to the target +server. There's always the possibility that the query will fail, in which case +the `SERVFAIL` response code is set to indicate as much to the client. If +rather everything goes as planned, the question and response records as copied +into our response packet. + +```rust + if let Ok(result) = lookup(&question.name, question.qtype, server) { + packet.questions.push(question.clone()); + packet.header.rescode = result.header.rescode; + + for rec in result.answers { + println!("Answer: {:?}", rec); + packet.answers.push(rec); + } + for rec in result.authorities { + println!("Authority: {:?}", rec); + packet.authorities.push(rec); + } + for rec in result.resources { + println!("Resource: {:?}", rec); + packet.resources.push(rec); + } + } else { + packet.header.rescode = ResultCode::SERVFAIL; + } +``` + +The only thing remaining is to encode our response and send it off! + +```rust + let mut res_buffer = BytePacketBuffer::new(); + match packet.write(&mut res_buffer) { + Ok(_) => {}, + Err(e) => { + println!("Failed to encode UDP response packet: {:?}", e); + continue; + } + }; + + let len = res_buffer.pos(); + let data = match res_buffer.get_range(0, len) { + Ok(x) => x, + Err(e) => { + println!("Failed to retrieve response buffer: {:?}", e); + continue; + } + }; + + match socket.send_to(data, src) { + Ok(_) => {}, + Err(e) => { + println!("Failed to send response buffer: {:?}", e); + continue; + } + }; +``` + +The match idiom for error handling is used again here, since we want to avoid +terminating our request loop at all cost. It's a bit verbose, and normally we'd +like to use `try!` instead. Unfortunately that's unavailable to us here, since +we're in the `main` function which doesn't return a `Result`. + +```rust + } // End of request loop +} // End of main +``` + +All done! Let's try it! We start our server in one terminal, and use `dig` to +perform a lookup in a second terminal. + +```text +# dig @127.0.0.1 -p 2053 google.com + +; <<>> DiG 9.10.3-P4-Ubuntu <<>> @127.0.0.1 -p 2053 google.com +; (1 server found) +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 47200 +;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + +;; QUESTION SECTION: +;google.com. IN A + +;; ANSWER SECTION: +google.com. 68 IN A 216.58.211.142 + +;; Query time: 1 msec +;; SERVER: 127.0.0.1#2053(127.0.0.1) +;; WHEN: Fri Jul 08 12:07:44 CEST 2016 +;; MSG SIZE rcvd: 54 +``` + +Looking at our server terminal we see: + +```text +Received query: DnsQuestion { name: "google.com", qtype: A } +Answer: A { domain: "google.com", addr: 216.58.211.142, ttl: 96 } +``` + +In less than 800 lines of code, we've built a DNS server able to respond to +queries with several different record types! diff --git a/chapter5.md b/chapter5.md new file mode 100644 index 0000000..a8378d0 --- /dev/null +++ b/chapter5.md @@ -0,0 +1,427 @@ +5. Recursive Resolve +-------------------- + +Our server is working, but being reliant on another server to actually perform +the lookup is annoying and less than useful. Now is a good time to dwelve into +the details of how a name is really resolved. + +Assuming that no information is known since before, the question is first +issued to one of the Internet's 13 root servers. Why 13? Because that's how +many that fits into a 512 byte DNS packet (strictly speaking, there's room for +14, but some margin was left). You might think that 13 seems a bit on the low +side for handling all of the internet, and you'd be right -- there are 13 +logical servers, but in reality many more. You can read more about it +[here](http://www.root-servers.org/). Any resolver will need to know of these +13 servers before hand. A file containing all of them, in bind format, is +available and called [named.root](https://www.internic.net/domain/named.root). +These servers all contain the same information, and to get started we can pick +one of them at random. Looking at `named.root` we see that the IP-adress of +*a.root-servers.net* is 198.41.0.4, so we'll go ahead and use that to perform +our initial query for *www.google.com*. + +```text +# dig +norecurse @198.41.0.4 www.google.com + +; <<>> DiG 9.10.3-P4-Ubuntu <<>> +norecurse @198.41.0.4 www.google.com +; (1 server found) +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64866 +;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 13, ADDITIONAL: 16 + +;; OPT PSEUDOSECTION: +; EDNS: version: 0, flags:; udp: 4096 +;; QUESTION SECTION: +;www.google.com. IN A + +;; AUTHORITY SECTION: +com. 172800 IN NS e.gtld-servers.net. +com. 172800 IN NS b.gtld-servers.net. +com. 172800 IN NS j.gtld-servers.net. +com. 172800 IN NS m.gtld-servers.net. +com. 172800 IN NS i.gtld-servers.net. +com. 172800 IN NS f.gtld-servers.net. +com. 172800 IN NS a.gtld-servers.net. +com. 172800 IN NS g.gtld-servers.net. +com. 172800 IN NS h.gtld-servers.net. +com. 172800 IN NS l.gtld-servers.net. +com. 172800 IN NS k.gtld-servers.net. +com. 172800 IN NS c.gtld-servers.net. +com. 172800 IN NS d.gtld-servers.net. + +;; ADDITIONAL SECTION: +e.gtld-servers.net. 172800 IN A 192.12.94.30 +b.gtld-servers.net. 172800 IN A 192.33.14.30 +b.gtld-servers.net. 172800 IN AAAA 2001:503:231d::2:30 +j.gtld-servers.net. 172800 IN A 192.48.79.30 +m.gtld-servers.net. 172800 IN A 192.55.83.30 +i.gtld-servers.net. 172800 IN A 192.43.172.30 +f.gtld-servers.net. 172800 IN A 192.35.51.30 +a.gtld-servers.net. 172800 IN A 192.5.6.30 +a.gtld-servers.net. 172800 IN AAAA 2001:503:a83e::2:30 +g.gtld-servers.net. 172800 IN A 192.42.93.30 +h.gtld-servers.net. 172800 IN A 192.54.112.30 +l.gtld-servers.net. 172800 IN A 192.41.162.30 +k.gtld-servers.net. 172800 IN A 192.52.178.30 +c.gtld-servers.net. 172800 IN A 192.26.92.30 +d.gtld-servers.net. 172800 IN A 192.31.80.30 + +;; Query time: 24 msec +;; SERVER: 198.41.0.4#53(198.41.0.4) +;; WHEN: Fri Jul 08 14:09:20 CEST 2016 +;; MSG SIZE rcvd: 531 +``` + +The root servers don't know about *www.google.com*, but they do know about +*com*, so our reply tells us where to go next. There are a few things to take +note of: + + * We are provided with a set of NS records, which are in the authority + section. NS records tells us *the name* of the name server handling + a domain. + * The server is being helpful by passing along A records corresponding to the + NS records, so we don't have to perform a second lookup. + * We didn't actually perform a query for *com*, but rather *www.google.com*. + However, the NS records all refer to *com*. + +Let's pick a server from the result and move on. *192.5.6.30* for +*a.gtld-servers.net* seems as good as any. + +```text +# dig +norecurse @192.5.6.30 www.google.com + +; <<>> DiG 9.10.3-P4-Ubuntu <<>> +norecurse @192.5.6.30 www.google.com +; (1 server found) +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16229 +;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 4, ADDITIONAL: 5 + +;; OPT PSEUDOSECTION: +; EDNS: version: 0, flags:; udp: 4096 +;; QUESTION SECTION: +;www.google.com. IN A + +;; AUTHORITY SECTION: +google.com. 172800 IN NS ns2.google.com. +google.com. 172800 IN NS ns1.google.com. +google.com. 172800 IN NS ns3.google.com. +google.com. 172800 IN NS ns4.google.com. + +;; ADDITIONAL SECTION: +ns2.google.com. 172800 IN A 216.239.34.10 +ns1.google.com. 172800 IN A 216.239.32.10 +ns3.google.com. 172800 IN A 216.239.36.10 +ns4.google.com. 172800 IN A 216.239.38.10 + +;; Query time: 114 msec +;; SERVER: 192.5.6.30#53(192.5.6.30) +;; WHEN: Fri Jul 08 14:13:26 CEST 2016 +;; MSG SIZE rcvd: 179 +``` + +We're still not at *www.google.com*, but at least we have a set of servers that +handle the *google.com* domain now. Let's give it another shot by sending our +query to *216.239.32.10*. + +```text +# dig +norecurse @216.239.32.10 www.google.com + +; <<>> DiG 9.10.3-P4-Ubuntu <<>> +norecurse @216.239.32.10 www.google.com +; (1 server found) +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20432 +;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + +;; QUESTION SECTION: +;www.google.com. IN A + +;; ANSWER SECTION: +www.google.com. 300 IN A 216.58.211.132 + +;; Query time: 10 msec +;; SERVER: 216.239.32.10#53(216.239.32.10) +;; WHEN: Fri Jul 08 14:15:11 CEST 2016 +;; MSG SIZE rcvd: 48 +``` + +And here we go! The IP of *www.google.com* as we desired. Let's recap: + + * *a.root-servers.net* tells us to check *a.gtld-servers.net* which handles com + * *a.gtld-servers.net* tells us to check *ns1.google.com* which handles google.com + * *ns1.google.com* tells us the IP of *www.google.com* + +This is rather typical, and most lookups will only ever require three steps, +even without caching. It's still possible to have name servers for subdomains, +and further ones for sub-subdomains, though. In practice, a DNS server will +maintain a cache, and most TLD's will be known since before. That means that +most queries will only ever require two lookups by the server, and commonly one +or zero. + +### Extending DnsPacket for recursive lookups + +Before we can get on, we'll need a few utility functions on `DnsPacket`. + +```rust +impl DnsPacket { + + - snip - +``` + +First, it's useful to be able to pick a random A record from a packet. Since we +don't want to introduce an external dependency, and there's no method for +generating random numbers in the rust standard library, we'll just pick the +first entry for now. + +```rust + pub fn get_random_a(&self) -> Option { + if !self.answers.is_empty() { + let idx = random::() % self.answers.len(); + let a_record = &self.answers[idx]; + if let DnsRecord::A{ ref addr, .. } = *a_record { + return Some(addr.to_string()); + } + } + + None + } +``` + +Second, 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. + +```rust + pub fn get_resolved_ns(&self, qname: &str) -> Option { +``` + +First, we scan the list of NS records in the authorities section: + +```rust + let mut new_authorities = Vec::new(); + for auth in &self.authorities { + if let DnsRecord::NS { ref domain, ref host, .. } = *auth { + if !qname.ends_with(domain) { + continue; + } +``` + +Once we've found an NS record, we scan the resources record for a matching +A record... + +```rust + for rsrc in &self.resources { + if let DnsRecord::A{ ref domain, ref addr, ttl } = *rsrc { + if domain != host { + continue; + } + + let rec = DnsRecord::A { + domain: host.clone(), + addr: *addr, + ttl: ttl + }; +``` + +...and push any matches to a list. + +```rust + new_authorities.push(rec); + } + } + } + } +``` + +If there are any matches, we pick the first one. Again, we'll want to introduce +randomization later on. + +```rust + if !new_authorities.is_empty() { + if let DnsRecord::A { addr, .. } = new_authorities[0] { + return Some(addr.to_string()); + } + } + + None + } // End of get_resolved_ns +``` + +However, not all name servers are as well behaved. 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. + +```rust + pub fn get_unresolved_ns(&self, qname: &str) -> Option { + + let mut new_authorities = Vec::new(); + for auth in &self.authorities { + if let DnsRecord::NS { ref domain, ref host, .. } = *auth { + if !qname.ends_with(domain) { + continue; + } + + new_authorities.push(host); + } + } + + if !new_authorities.is_empty() { + let idx = random::() % new_authorities.len(); + return Some(new_authorities[idx].clone()); + } + + None + } // End of get_unresolved_ns + +} // End of DnsPacket +``` + +### Implementing recursive lookup + +We move swiftly on to our new `recursive_lookup` function: + +```rust +fn recursive_lookup(qname: &str, qtype: QueryType) -> Result { +``` + +For now we're always starting with *a.root-servers.net*. + +```rust + let mut ns = "198.41.0.4".to_string(); +``` + +Since it might take an arbitrary number of steps, we enter an unbounded loop. + +```rust + loop { + println!("attempting lookup of {:?} {} with ns {}", qtype, qname, ns); +``` + +The next step is to send the query to the active server. + +```rust + let ns_copy = ns.clone(); + + let server = (ns_copy.as_str(), 53); + let response = try!(lookup(qname, qtype.clone(), server)); +``` + +If there are entries in the answer section, and no errors, we are done! + +```rust + if !response.answers.is_empty() && + response.header.rescode == ResultCode::NOERROR { + + return Ok(response.clone()); + } +``` + +We might also get a `NXDOMAIN` reply, which is the authoritative name servers +way of telling us that the name doesn't exist. + +```rust + if response.header.rescode == ResultCode::NXDOMAIN { + return Ok(response.clone()); + } +``` + +Otherwise, we'll try to find a new nameserver based on NS and a corresponding A +record in the additional section. If this succeeds, we can switch name server +and retry the loop. + +```rust + if let Some(new_ns) = response.get_resolved_ns(qname) { + ns = new_ns.clone(); + + continue; + } +``` + +If not, we'll have to resolve the ip of a NS record. If no NS records exist, +we'll go with what the last server told us. + +```rust + let new_ns_name = match response.get_unresolved_ns(qname) { + Some(x) => x, + None => return Ok(response.clone()) + }; +``` + +Here we go down the rabbit hole by starting _another_ lookup sequence in the +midst of our current one. Hopefully, this will give us the IP of an appropriate +name server. + +```rust + let recursive_response = try!(recursive_lookup(&new_ns_name, QueryType::A)); +``` + +Finally, we pick a random ip from the result, and restart the loop. If no such +record is available, we again return the last result we got. + +```rust + if let Some(new_ns) = recursive_response.get_random_a() { + ns = new_ns.clone(); + } else { + return Ok(response.clone()) + } + } +} // End of recursive_lookup +``` + +### Trying out recursive lookup + +The only thing remaining is to change our main function to use +`recursive_lookup`: + +```rust +fn main() { + + - snip - + + println!("Received query: {:?}", question); + if let Ok(result) = recursive_lookup(&question.name, question.qtype) { + packet.questions.push(question.clone()); + packet.header.rescode = result.header.rescode; + + - snip - + +} +``` + +Let's try it! + +```text +# dig @127.0.0.1 -p 2053 www.google.com + +; <<>> DiG 9.10.3-P4-Ubuntu <<>> @127.0.0.1 -p 2053 www.google.com +; (1 server found) +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41892 +;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + +;; QUESTION SECTION: +;www.google.com. IN A + +;; ANSWER SECTION: +www.google.com. 300 IN A 216.58.211.132 + +;; Query time: 76 msec +;; SERVER: 127.0.0.1#2053(127.0.0.1) +;; WHEN: Fri Jul 08 14:31:39 CEST 2016 +;; MSG SIZE rcvd: 62 +``` + +Looking at our server window, we see: + +```text +Received query: DnsQuestion { name: "www.google.com", qtype: A } +attempting lookup of A www.google.com with ns 198.41.0.4 +attempting lookup of A www.google.com with ns 192.12.94.30 +attempting lookup of A www.google.com with ns 216.239.34.10 +Answer: A { domain: "www.google.com", addr: 216.58.211.132, ttl: 300 } +``` + +This mirrors our manual process earlier. We're really getting somewhere! diff --git a/src/chapter2.rs b/samples/sample1.rs similarity index 89% rename from src/chapter2.rs rename to samples/sample1.rs index 24bc5fe..e2943f1 100644 --- a/src/chapter2.rs +++ b/samples/sample1.rs @@ -1,7 +1,7 @@ -pub use std::io::{Result, Read}; -pub use std::io::{Error, ErrorKind}; -pub use std::net::Ipv4Addr; -pub use std::net::UdpSocket; +use std::io::{Result, Read}; +use std::io::{Error, ErrorKind}; +use std::net::Ipv4Addr; +use std::fs::File; pub struct BytePacketBuffer { pub buf: [u8; 512], @@ -16,23 +16,23 @@ impl BytePacketBuffer { } } - pub fn pos(&self) -> usize { + fn pos(&self) -> usize { self.pos } - pub fn step(&mut self, steps: usize) -> Result<()> { + fn step(&mut self, steps: usize) -> Result<()> { self.pos += steps; Ok(()) } - pub fn seek(&mut self, pos: usize) -> Result<()> { + fn seek(&mut self, pos: usize) -> Result<()> { self.pos = pos; Ok(()) } - pub fn read(&mut self) -> Result { + fn read(&mut self) -> Result { if self.pos >= 512 { return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); } @@ -42,21 +42,21 @@ impl BytePacketBuffer { Ok(res) } - pub fn get(&mut self, pos: usize) -> Result { + fn get(&mut self, pos: usize) -> Result { if pos >= 512 { return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); } Ok(self.buf[pos]) } - pub fn get_range(&mut self, start: usize, len: usize) -> Result<&[u8]> { + fn get_range(&mut self, start: usize, len: usize) -> Result<&[u8]> { if start + len >= 512 { return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); } Ok(&self.buf[start..start+len as usize]) } - pub fn read_u16(&mut self) -> Result + fn read_u16(&mut self) -> Result { let res = ((try!(self.read()) as u16) << 8) | (try!(self.read()) as u16); @@ -64,7 +64,7 @@ impl BytePacketBuffer { Ok(res) } - pub fn read_u32(&mut self) -> Result + fn read_u32(&mut self) -> Result { let res = ((try!(self.read()) as u32) << 24) | ((try!(self.read()) as u32) << 16) | @@ -74,7 +74,7 @@ impl BytePacketBuffer { Ok(res) } - pub fn read_qname(&mut self, outstr: &mut String) -> Result<()> + fn read_qname(&mut self, outstr: &mut String) -> Result<()> { let mut pos = self.pos(); let mut jumped = false; @@ -370,3 +370,25 @@ impl DnsPacket { Ok(result) } } + +fn main() { + let mut f = File::open("response_packet.txt").unwrap(); + let mut buffer = BytePacketBuffer::new(); + f.read(&mut buffer.buf).unwrap(); + + let packet = DnsPacket::from_buffer(&mut buffer).unwrap(); + println!("{:?}", packet.header); + + for q in packet.questions { + println!("{:?}", q); + } + for rec in packet.answers { + println!("{:?}", rec); + } + for rec in packet.authorities { + println!("{:?}", rec); + } + for rec in packet.resources { + println!("{:?}", rec); + } +} diff --git a/samples/sample2.rs b/samples/sample2.rs new file mode 100644 index 0000000..98442eb --- /dev/null +++ b/samples/sample2.rs @@ -0,0 +1,550 @@ +use std::io::{Result, Read}; +use std::io::{Error, ErrorKind}; +use std::net::Ipv4Addr; +use std::net::UdpSocket; + +pub struct BytePacketBuffer { + pub buf: [u8; 512], + pub pos: usize +} + +impl BytePacketBuffer { + pub fn new() -> BytePacketBuffer { + BytePacketBuffer { + buf: [0; 512], + pos: 0 + } + } + + fn pos(&self) -> usize { + self.pos + } + + fn step(&mut self, steps: usize) -> Result<()> { + self.pos += steps; + + Ok(()) + } + + fn seek(&mut self, pos: usize) -> Result<()> { + self.pos = pos; + + Ok(()) + } + + fn read(&mut self) -> Result { + if self.pos >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + let res = self.buf[self.pos]; + self.pos += 1; + + Ok(res) + } + + fn get(&mut self, pos: usize) -> Result { + if pos >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + Ok(self.buf[pos]) + } + + fn get_range(&mut self, start: usize, len: usize) -> Result<&[u8]> { + if start + len >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + Ok(&self.buf[start..start+len as usize]) + } + + fn read_u16(&mut self) -> Result + { + let res = ((try!(self.read()) as u16) << 8) | + (try!(self.read()) as u16); + + Ok(res) + } + + fn read_u32(&mut self) -> Result + { + let res = ((try!(self.read()) as u32) << 24) | + ((try!(self.read()) as u32) << 16) | + ((try!(self.read()) as u32) << 8) | + ((try!(self.read()) as u32) << 0); + + Ok(res) + } + + fn read_qname(&mut self, outstr: &mut String) -> Result<()> + { + let mut pos = self.pos(); + let mut jumped = false; + + let mut delim = ""; + loop { + let len = try!(self.get(pos)); + + // A two byte sequence, where the two highest bits of the first byte is + // set, represents a offset relative to the start of the buffer. We + // handle this by jumping to the offset, setting a flag to indicate + // that we shouldn't update the shared buffer position once done. + if (len & 0xC0) > 0 { + + // When a jump is performed, we only modify the shared buffer + // position once, and avoid making the change later on. + if !jumped { + try!(self.seek(pos+2)); + } + + let b2 = try!(self.get(pos+1)) as u16; + let offset = (((len as u16) ^ 0xC0) << 8) | b2; + pos = offset as usize; + jumped = true; + continue; + } + + pos += 1; + + // Names are terminated by an empty label of length 0 + if len == 0 { + break; + } + + outstr.push_str(delim); + + let str_buffer = try!(self.get_range(pos, len as usize)); + outstr.push_str(&String::from_utf8_lossy(str_buffer).to_lowercase()); + + delim = "."; + + pos += len as usize; + } + + if !jumped { + try!(self.seek(pos)); + } + + Ok(()) + } + + fn write(&mut self, val: u8) -> Result<()> { + if self.pos >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + self.buf[self.pos] = val; + self.pos += 1; + Ok(()) + } + + fn write_u8(&mut self, val: u8) -> Result<()> { + try!(self.write(val)); + + Ok(()) + } + + fn write_u16(&mut self, val: u16) -> Result<()> { + try!(self.write((val >> 8) as u8)); + try!(self.write((val & 0xFF) as u8)); + + Ok(()) + } + + fn write_u32(&mut self, val: u32) -> Result<()> { + try!(self.write(((val >> 24) & 0xFF) as u8)); + try!(self.write(((val >> 16) & 0xFF) as u8)); + try!(self.write(((val >> 8) & 0xFF) as u8)); + try!(self.write(((val >> 0) & 0xFF) as u8)); + + Ok(()) + } + + fn write_qname(&mut self, qname: &str) -> Result<()> { + + let split_str = qname.split('.').collect::>(); + + for label in split_str { + let len = label.len(); + if len > 0x34 { + return Err(Error::new(ErrorKind::InvalidInput, "Single label exceeds 63 characters of length")); + } + + try!(self.write_u8(len as u8)); + for b in label.as_bytes() { + try!(self.write_u8(*b)); + } + } + + try!(self.write_u8(0)); + + Ok(()) + } + +} + +#[derive(Copy,Clone,Debug,PartialEq,Eq)] +pub enum ResultCode { + NOERROR = 0, + FORMERR = 1, + SERVFAIL = 2, + NXDOMAIN = 3, + NOTIMP = 4, + REFUSED = 5 +} + +impl ResultCode { + pub fn from_num(num: u8) -> ResultCode { + match num { + 1 => ResultCode::FORMERR, + 2 => ResultCode::SERVFAIL, + 3 => ResultCode::NXDOMAIN, + 4 => ResultCode::NOTIMP, + 5 => ResultCode::REFUSED, + 0 | _ => ResultCode::NOERROR + } + } +} + +#[derive(Clone,Debug)] +pub struct DnsHeader { + pub id: u16, // 16 bits + + pub recursion_desired: bool, // 1 bit + pub truncated_message: bool, // 1 bit + pub authoritative_answer: bool, // 1 bit + pub opcode: u8, // 4 bits + pub response: bool, // 1 bit + + pub rescode: ResultCode, // 4 bits + pub checking_disabled: bool, // 1 bit + pub authed_data: bool, // 1 bit + pub z: bool, // 1 bit + pub recursion_available: bool, // 1 bit + + pub questions: u16, // 16 bits + pub answers: u16, // 16 bits + pub authoritative_entries: u16, // 16 bits + pub resource_entries: u16 // 16 bits +} + +impl DnsHeader { + pub fn new() -> DnsHeader { + DnsHeader { id: 0, + + recursion_desired: false, + truncated_message: false, + authoritative_answer: false, + opcode: 0, + response: false, + + rescode: ResultCode::NOERROR, + checking_disabled: false, + authed_data: false, + z: false, + recursion_available: false, + + questions: 0, + answers: 0, + authoritative_entries: 0, + resource_entries: 0 } + } + + pub fn read(&mut self, buffer: &mut BytePacketBuffer) -> Result<()> { + self.id = try!(buffer.read_u16()); + + let flags = try!(buffer.read_u16()); + let a = (flags >> 8) as u8; + let b = (flags & 0xFF) as u8; + self.recursion_desired = (a & (1 << 0)) > 0; + self.truncated_message = (a & (1 << 1)) > 0; + self.authoritative_answer = (a & (1 << 2)) > 0; + self.opcode = (a >> 3) & 0x0F; + self.response = (a & (1 << 7)) > 0; + + self.rescode = ResultCode::from_num(b & 0x0F); + self.checking_disabled = (b & (1 << 4)) > 0; + self.authed_data = (b & (1 << 5)) > 0; + self.z = (b & (1 << 6)) > 0; + self.recursion_available = (b & (1 << 7)) > 0; + + self.questions = try!(buffer.read_u16()); + self.answers = try!(buffer.read_u16()); + self.authoritative_entries = try!(buffer.read_u16()); + self.resource_entries = try!(buffer.read_u16()); + + // Return the constant header size + Ok(()) + } + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> { + try!(buffer.write_u16(self.id)); + + try!(buffer.write_u8( ((self.recursion_desired as u8)) | + ((self.truncated_message as u8) << 1) | + ((self.authoritative_answer as u8) << 2) | + (self.opcode << 3) | + ((self.response as u8) << 7) as u8) ); + + try!(buffer.write_u8( (self.rescode.clone() as u8) | + ((self.checking_disabled as u8) << 4) | + ((self.authed_data as u8) << 5) | + ((self.z as u8) << 6) | + ((self.recursion_available as u8) << 7) )); + + try!(buffer.write_u16(self.questions)); + try!(buffer.write_u16(self.answers)); + try!(buffer.write_u16(self.authoritative_entries)); + try!(buffer.write_u16(self.resource_entries)); + + Ok(()) + } +} + +#[derive(PartialEq,Eq,Debug,Clone,Hash,Copy)] +pub enum QueryType { + UNKNOWN(u16), + A, // 1 +} + +impl QueryType { + pub fn to_num(&self) -> u16 { + match *self { + QueryType::UNKNOWN(x) => x, + QueryType::A => 1, + } + } + + pub fn from_num(num: u16) -> QueryType { + match num { + 1 => QueryType::A, + _ => QueryType::UNKNOWN(num) + } + } +} + +#[derive(Debug,Clone,PartialEq,Eq)] +pub struct DnsQuestion { + pub name: String, + pub qtype: QueryType +} + +impl DnsQuestion { + pub fn new(name: String, qtype: QueryType) -> DnsQuestion { + DnsQuestion { + name: name, + qtype: qtype + } + } + + pub fn read(&mut self, buffer: &mut BytePacketBuffer) -> Result<()> { + try!(buffer.read_qname(&mut self.name)); + self.qtype = QueryType::from_num(try!(buffer.read_u16())); // qtype + let _ = try!(buffer.read_u16()); // class + + Ok(()) + } + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> { + + try!(buffer.write_qname(&self.name)); + + let typenum = self.qtype.to_num(); + try!(buffer.write_u16(typenum)); + try!(buffer.write_u16(1)); + + Ok(()) + } + +} + +#[derive(Debug,Clone,PartialEq,Eq,Hash,PartialOrd,Ord)] +#[allow(dead_code)] +pub enum DnsRecord { + UNKNOWN { + domain: String, + qtype: u16, + data_len: u16, + ttl: u32 + }, // 0 + A { + domain: String, + addr: Ipv4Addr, + ttl: u32 + }, // 1 +} + +impl DnsRecord { + + pub fn read(buffer: &mut BytePacketBuffer) -> Result { + let mut domain = String::new(); + try!(buffer.read_qname(&mut domain)); + + let qtype_num = try!(buffer.read_u16()); + let qtype = QueryType::from_num(qtype_num); + let _ = try!(buffer.read_u16()); + let ttl = try!(buffer.read_u32()); + let data_len = try!(buffer.read_u16()); + + match qtype { + QueryType::A => { + let raw_addr = try!(buffer.read_u32()); + let addr = Ipv4Addr::new(((raw_addr >> 24) & 0xFF) as u8, + ((raw_addr >> 16) & 0xFF) as u8, + ((raw_addr >> 8) & 0xFF) as u8, + ((raw_addr >> 0) & 0xFF) as u8); + + Ok(DnsRecord::A { + domain: domain, + addr: addr, + ttl: ttl + }) + }, + QueryType::UNKNOWN(_) => { + try!(buffer.step(data_len as usize)); + + Ok(DnsRecord::UNKNOWN { + domain: domain, + qtype: qtype_num, + data_len: data_len, + ttl: ttl + }) + } + } + } + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result { + + let start_pos = buffer.pos(); + + match *self { + DnsRecord::A { ref domain, ref addr, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::A.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + try!(buffer.write_u16(4)); + + let octets = addr.octets(); + try!(buffer.write_u8(octets[0])); + try!(buffer.write_u8(octets[1])); + try!(buffer.write_u8(octets[2])); + try!(buffer.write_u8(octets[3])); + }, + DnsRecord::UNKNOWN { .. } => { + println!("Skipping record: {:?}", self); + } + } + + Ok(buffer.pos() - start_pos) + } + +} + +#[derive(Clone, Debug)] +pub struct DnsPacket { + pub header: DnsHeader, + pub questions: Vec, + pub answers: Vec, + pub authorities: Vec, + pub resources: Vec +} + +impl DnsPacket { + pub fn new() -> DnsPacket { + DnsPacket { + header: DnsHeader::new(), + questions: Vec::new(), + answers: Vec::new(), + authorities: Vec::new(), + resources: Vec::new() + } + } + + pub fn from_buffer(buffer: &mut BytePacketBuffer) -> Result { + let mut result = DnsPacket::new(); + try!(result.header.read(buffer)); + + for _ in 0..result.header.questions { + let mut question = DnsQuestion::new("".to_string(), + QueryType::UNKNOWN(0)); + try!(question.read(buffer)); + result.questions.push(question); + } + + for _ in 0..result.header.answers { + let rec = try!(DnsRecord::read(buffer)); + result.answers.push(rec); + } + for _ in 0..result.header.authoritative_entries { + let rec = try!(DnsRecord::read(buffer)); + result.authorities.push(rec); + } + for _ in 0..result.header.resource_entries { + let rec = try!(DnsRecord::read(buffer)); + result.resources.push(rec); + } + + Ok(result) + } + + pub fn write(&mut self, buffer: &mut BytePacketBuffer) -> 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; + + try!(self.header.write(buffer)); + + for question in &self.questions { + try!(question.write(buffer)); + } + for rec in &self.answers { + try!(rec.write(buffer)); + } + for rec in &self.authorities { + try!(rec.write(buffer)); + } + for rec in &self.resources { + try!(rec.write(buffer)); + } + + Ok(()) + } +} + +fn main() { + let qname = "www.yahoo.com"; + let qtype = QueryType::A; + let server = ("8.8.8.8", 53); + + let socket = UdpSocket::bind(("0.0.0.0", 43210)).unwrap(); + + let mut packet = DnsPacket::new(); + + packet.header.id = 6666; + packet.header.questions = 1; + packet.header.recursion_desired = true; + packet.questions.push(DnsQuestion::new(qname.to_string(), qtype)); + + let mut req_buffer = BytePacketBuffer::new(); + packet.write(&mut req_buffer).unwrap(); + socket.send_to(&req_buffer.buf[0..req_buffer.pos], server).unwrap(); + + let mut res_buffer = BytePacketBuffer::new(); + socket.recv_from(&mut res_buffer.buf).unwrap(); + + let res_packet = DnsPacket::from_buffer(&mut res_buffer).unwrap(); + println!("{:?}", res_packet.header); + + for q in res_packet.questions { + println!("{:?}", q); + } + for rec in res_packet.answers { + println!("{:?}", rec); + } + for rec in res_packet.authorities { + println!("{:?}", rec); + } + for rec in res_packet.resources { + println!("{:?}", rec); + } +} diff --git a/samples/sample3.rs b/samples/sample3.rs new file mode 100644 index 0000000..9bff2b1 --- /dev/null +++ b/samples/sample3.rs @@ -0,0 +1,701 @@ +use std::io::{Result, Read}; +use std::io::{Error, ErrorKind}; +use std::net::{Ipv4Addr,Ipv6Addr}; +use std::net::UdpSocket; + +pub struct BytePacketBuffer { + pub buf: [u8; 512], + pub pos: usize +} + +impl BytePacketBuffer { + pub fn new() -> BytePacketBuffer { + BytePacketBuffer { + buf: [0; 512], + pos: 0 + } + } + + fn pos(&self) -> usize { + self.pos + } + + fn step(&mut self, steps: usize) -> Result<()> { + self.pos += steps; + + Ok(()) + } + + fn seek(&mut self, pos: usize) -> Result<()> { + self.pos = pos; + + Ok(()) + } + + fn read(&mut self) -> Result { + if self.pos >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + let res = self.buf[self.pos]; + self.pos += 1; + + Ok(res) + } + + fn get(&mut self, pos: usize) -> Result { + if pos >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + Ok(self.buf[pos]) + } + + fn get_range(&mut self, start: usize, len: usize) -> Result<&[u8]> { + if start + len >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + Ok(&self.buf[start..start+len as usize]) + } + + fn read_u16(&mut self) -> Result + { + let res = ((try!(self.read()) as u16) << 8) | + (try!(self.read()) as u16); + + Ok(res) + } + + fn read_u32(&mut self) -> Result + { + let res = ((try!(self.read()) as u32) << 24) | + ((try!(self.read()) as u32) << 16) | + ((try!(self.read()) as u32) << 8) | + ((try!(self.read()) as u32) << 0); + + Ok(res) + } + + fn read_qname(&mut self, outstr: &mut String) -> Result<()> + { + let mut pos = self.pos(); + let mut jumped = false; + + let mut delim = ""; + loop { + let len = try!(self.get(pos)); + + // A two byte sequence, where the two highest bits of the first byte is + // set, represents a offset relative to the start of the buffer. We + // handle this by jumping to the offset, setting a flag to indicate + // that we shouldn't update the shared buffer position once done. + if (len & 0xC0) > 0 { + + // When a jump is performed, we only modify the shared buffer + // position once, and avoid making the change later on. + if !jumped { + try!(self.seek(pos+2)); + } + + let b2 = try!(self.get(pos+1)) as u16; + let offset = (((len as u16) ^ 0xC0) << 8) | b2; + pos = offset as usize; + jumped = true; + continue; + } + + pos += 1; + + // Names are terminated by an empty label of length 0 + if len == 0 { + break; + } + + outstr.push_str(delim); + + let str_buffer = try!(self.get_range(pos, len as usize)); + outstr.push_str(&String::from_utf8_lossy(str_buffer).to_lowercase()); + + delim = "."; + + pos += len as usize; + } + + if !jumped { + try!(self.seek(pos)); + } + + Ok(()) + } + + fn write(&mut self, val: u8) -> Result<()> { + if self.pos >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + self.buf[self.pos] = val; + self.pos += 1; + Ok(()) + } + + fn write_u8(&mut self, val: u8) -> Result<()> { + try!(self.write(val)); + + Ok(()) + } + + fn write_u16(&mut self, val: u16) -> Result<()> { + try!(self.write((val >> 8) as u8)); + try!(self.write((val & 0xFF) as u8)); + + Ok(()) + } + + fn write_u32(&mut self, val: u32) -> Result<()> { + try!(self.write(((val >> 24) & 0xFF) as u8)); + try!(self.write(((val >> 16) & 0xFF) as u8)); + try!(self.write(((val >> 8) & 0xFF) as u8)); + try!(self.write(((val >> 0) & 0xFF) as u8)); + + Ok(()) + } + + fn write_qname(&mut self, qname: &str) -> Result<()> { + + let split_str = qname.split('.').collect::>(); + + for label in split_str { + let len = label.len(); + if len > 0x34 { + return Err(Error::new(ErrorKind::InvalidInput, "Single label exceeds 63 characters of length")); + } + + try!(self.write_u8(len as u8)); + for b in label.as_bytes() { + try!(self.write_u8(*b)); + } + } + + try!(self.write_u8(0)); + + Ok(()) + } + + fn set(&mut self, pos: usize, val: u8) -> Result<()> { + self.buf[pos] = val; + + Ok(()) + } + + fn set_u16(&mut self, pos: usize, val: u16) -> Result<()> { + try!(self.set(pos,(val >> 8) as u8)); + try!(self.set(pos+1,(val & 0xFF) as u8)); + + Ok(()) + } +} + +#[derive(Copy,Clone,Debug,PartialEq,Eq)] +pub enum ResultCode { + NOERROR = 0, + FORMERR = 1, + SERVFAIL = 2, + NXDOMAIN = 3, + NOTIMP = 4, + REFUSED = 5 +} + +impl ResultCode { + pub fn from_num(num: u8) -> ResultCode { + match num { + 1 => ResultCode::FORMERR, + 2 => ResultCode::SERVFAIL, + 3 => ResultCode::NXDOMAIN, + 4 => ResultCode::NOTIMP, + 5 => ResultCode::REFUSED, + 0 | _ => ResultCode::NOERROR + } + } +} + +#[derive(Clone,Debug)] +pub struct DnsHeader { + pub id: u16, // 16 bits + + pub recursion_desired: bool, // 1 bit + pub truncated_message: bool, // 1 bit + pub authoritative_answer: bool, // 1 bit + pub opcode: u8, // 4 bits + pub response: bool, // 1 bit + + pub rescode: ResultCode, // 4 bits + pub checking_disabled: bool, // 1 bit + pub authed_data: bool, // 1 bit + pub z: bool, // 1 bit + pub recursion_available: bool, // 1 bit + + pub questions: u16, // 16 bits + pub answers: u16, // 16 bits + pub authoritative_entries: u16, // 16 bits + pub resource_entries: u16 // 16 bits +} + +impl DnsHeader { + pub fn new() -> DnsHeader { + DnsHeader { id: 0, + + recursion_desired: false, + truncated_message: false, + authoritative_answer: false, + opcode: 0, + response: false, + + rescode: ResultCode::NOERROR, + checking_disabled: false, + authed_data: false, + z: false, + recursion_available: false, + + questions: 0, + answers: 0, + authoritative_entries: 0, + resource_entries: 0 } + } + + pub fn read(&mut self, buffer: &mut BytePacketBuffer) -> Result<()> { + self.id = try!(buffer.read_u16()); + + let flags = try!(buffer.read_u16()); + let a = (flags >> 8) as u8; + let b = (flags & 0xFF) as u8; + self.recursion_desired = (a & (1 << 0)) > 0; + self.truncated_message = (a & (1 << 1)) > 0; + self.authoritative_answer = (a & (1 << 2)) > 0; + self.opcode = (a >> 3) & 0x0F; + self.response = (a & (1 << 7)) > 0; + + self.rescode = ResultCode::from_num(b & 0x0F); + self.checking_disabled = (b & (1 << 4)) > 0; + self.authed_data = (b & (1 << 5)) > 0; + self.z = (b & (1 << 6)) > 0; + self.recursion_available = (b & (1 << 7)) > 0; + + self.questions = try!(buffer.read_u16()); + self.answers = try!(buffer.read_u16()); + self.authoritative_entries = try!(buffer.read_u16()); + self.resource_entries = try!(buffer.read_u16()); + + // Return the constant header size + Ok(()) + } + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> { + try!(buffer.write_u16(self.id)); + + try!(buffer.write_u8( ((self.recursion_desired as u8)) | + ((self.truncated_message as u8) << 1) | + ((self.authoritative_answer as u8) << 2) | + (self.opcode << 3) | + ((self.response as u8) << 7) as u8) ); + + try!(buffer.write_u8( (self.rescode.clone() as u8) | + ((self.checking_disabled as u8) << 4) | + ((self.authed_data as u8) << 5) | + ((self.z as u8) << 6) | + ((self.recursion_available as u8) << 7) )); + + try!(buffer.write_u16(self.questions)); + try!(buffer.write_u16(self.answers)); + try!(buffer.write_u16(self.authoritative_entries)); + try!(buffer.write_u16(self.resource_entries)); + + Ok(()) + } +} + +#[derive(PartialEq,Eq,Debug,Clone,Hash,Copy)] +pub enum QueryType { + UNKNOWN(u16), + A, // 1 + NS, // 2 + CNAME, // 5 + MX, // 15 + AAAA, // 28 +} + +impl QueryType { + pub fn to_num(&self) -> u16 { + match *self { + QueryType::UNKNOWN(x) => x, + QueryType::A => 1, + QueryType::NS => 2, + QueryType::CNAME => 5, + QueryType::MX => 15, + QueryType::AAAA => 28, + } + } + + pub fn from_num(num: u16) -> QueryType { + match num { + 1 => QueryType::A, + 2 => QueryType::NS, + 5 => QueryType::CNAME, + 15 => QueryType::MX, + 28 => QueryType::AAAA, + _ => QueryType::UNKNOWN(num) + } + } +} + +#[derive(Debug,Clone,PartialEq,Eq)] +pub struct DnsQuestion { + pub name: String, + pub qtype: QueryType +} + +impl DnsQuestion { + pub fn new(name: String, qtype: QueryType) -> DnsQuestion { + DnsQuestion { + name: name, + qtype: qtype + } + } + + pub fn read(&mut self, buffer: &mut BytePacketBuffer) -> Result<()> { + try!(buffer.read_qname(&mut self.name)); + self.qtype = QueryType::from_num(try!(buffer.read_u16())); // qtype + let _ = try!(buffer.read_u16()); // class + + Ok(()) + } + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> { + + try!(buffer.write_qname(&self.name)); + + let typenum = self.qtype.to_num(); + try!(buffer.write_u16(typenum)); + try!(buffer.write_u16(1)); + + Ok(()) + } + +} + +#[derive(Debug,Clone,PartialEq,Eq,Hash,PartialOrd,Ord)] +#[allow(dead_code)] +pub enum DnsRecord { + UNKNOWN { + domain: String, + qtype: u16, + data_len: u16, + ttl: u32 + }, // 0 + A { + domain: String, + addr: Ipv4Addr, + ttl: u32 + }, // 1 + NS { + domain: String, + host: String, + ttl: u32 + }, // 2 + CNAME { + domain: String, + host: String, + ttl: u32 + }, // 5 + MX { + domain: String, + priority: u16, + host: String, + ttl: u32 + }, // 15 + AAAA { + domain: String, + addr: Ipv6Addr, + ttl: u32 + }, // 28 +} + +impl DnsRecord { + + pub fn read(buffer: &mut BytePacketBuffer) -> Result { + let mut domain = String::new(); + try!(buffer.read_qname(&mut domain)); + + let qtype_num = try!(buffer.read_u16()); + let qtype = QueryType::from_num(qtype_num); + let _ = try!(buffer.read_u16()); + let ttl = try!(buffer.read_u32()); + let data_len = try!(buffer.read_u16()); + + match qtype { + QueryType::A => { + let raw_addr = try!(buffer.read_u32()); + let addr = Ipv4Addr::new(((raw_addr >> 24) & 0xFF) as u8, + ((raw_addr >> 16) & 0xFF) as u8, + ((raw_addr >> 8) & 0xFF) as u8, + ((raw_addr >> 0) & 0xFF) as u8); + + Ok(DnsRecord::A { + domain: domain, + addr: addr, + ttl: ttl + }) + }, + QueryType::AAAA => { + let raw_addr1 = try!(buffer.read_u32()); + let raw_addr2 = try!(buffer.read_u32()); + let raw_addr3 = try!(buffer.read_u32()); + let raw_addr4 = try!(buffer.read_u32()); + let addr = Ipv6Addr::new(((raw_addr1 >> 16) & 0xFFFF) as u16, + ((raw_addr1 >> 0) & 0xFFFF) as u16, + ((raw_addr2 >> 16) & 0xFFFF) as u16, + ((raw_addr2 >> 0) & 0xFFFF) as u16, + ((raw_addr3 >> 16) & 0xFFFF) as u16, + ((raw_addr3 >> 0) & 0xFFFF) as u16, + ((raw_addr4 >> 16) & 0xFFFF) as u16, + ((raw_addr4 >> 0) & 0xFFFF) as u16); + + Ok(DnsRecord::AAAA { + domain: domain, + addr: addr, + ttl: ttl + }) + }, + QueryType::NS => { + let mut ns = String::new(); + try!(buffer.read_qname(&mut ns)); + + Ok(DnsRecord::NS { + domain: domain, + host: ns, + ttl: ttl + }) + }, + QueryType::CNAME => { + let mut cname = String::new(); + try!(buffer.read_qname(&mut cname)); + + Ok(DnsRecord::CNAME { + domain: domain, + host: cname, + ttl: ttl + }) + }, + QueryType::MX => { + let priority = try!(buffer.read_u16()); + let mut mx = String::new(); + try!(buffer.read_qname(&mut mx)); + + Ok(DnsRecord::MX { + domain: domain, + priority: priority, + host: mx, + ttl: ttl + }) + }, + QueryType::UNKNOWN(_) => { + try!(buffer.step(data_len as usize)); + + Ok(DnsRecord::UNKNOWN { + domain: domain, + qtype: qtype_num, + data_len: data_len, + ttl: ttl + }) + } + } + } + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result { + + let start_pos = buffer.pos(); + + match *self { + DnsRecord::A { ref domain, ref addr, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::A.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + try!(buffer.write_u16(4)); + + let octets = addr.octets(); + try!(buffer.write_u8(octets[0])); + try!(buffer.write_u8(octets[1])); + try!(buffer.write_u8(octets[2])); + try!(buffer.write_u8(octets[3])); + }, + DnsRecord::NS { ref domain, ref host, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::NS.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + + let pos = buffer.pos(); + try!(buffer.write_u16(0)); + + try!(buffer.write_qname(host)); + + let size = buffer.pos() - (pos + 2); + try!(buffer.set_u16(pos, size as u16)); + }, + DnsRecord::CNAME { ref domain, ref host, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::CNAME.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + + let pos = buffer.pos(); + try!(buffer.write_u16(0)); + + try!(buffer.write_qname(host)); + + let size = buffer.pos() - (pos + 2); + try!(buffer.set_u16(pos, size as u16)); + }, + DnsRecord::MX { ref domain, priority, ref host, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::MX.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + + let pos = buffer.pos(); + try!(buffer.write_u16(0)); + + try!(buffer.write_u16(priority)); + try!(buffer.write_qname(host)); + + let size = buffer.pos() - (pos + 2); + try!(buffer.set_u16(pos, size as u16)); + }, + DnsRecord::AAAA { ref domain, ref addr, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::AAAA.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + try!(buffer.write_u16(16)); + + for octet in &addr.segments() { + try!(buffer.write_u16(*octet)); + } + }, + DnsRecord::UNKNOWN { .. } => { + println!("Skipping record: {:?}", self); + } + } + + Ok(buffer.pos() - start_pos) + } + +} + +#[derive(Clone, Debug)] +pub struct DnsPacket { + pub header: DnsHeader, + pub questions: Vec, + pub answers: Vec, + pub authorities: Vec, + pub resources: Vec +} + +impl DnsPacket { + pub fn new() -> DnsPacket { + DnsPacket { + header: DnsHeader::new(), + questions: Vec::new(), + answers: Vec::new(), + authorities: Vec::new(), + resources: Vec::new() + } + } + + pub fn from_buffer(buffer: &mut BytePacketBuffer) -> Result { + let mut result = DnsPacket::new(); + try!(result.header.read(buffer)); + + for _ in 0..result.header.questions { + let mut question = DnsQuestion::new("".to_string(), + QueryType::UNKNOWN(0)); + try!(question.read(buffer)); + result.questions.push(question); + } + + for _ in 0..result.header.answers { + let rec = try!(DnsRecord::read(buffer)); + result.answers.push(rec); + } + for _ in 0..result.header.authoritative_entries { + let rec = try!(DnsRecord::read(buffer)); + result.authorities.push(rec); + } + for _ in 0..result.header.resource_entries { + let rec = try!(DnsRecord::read(buffer)); + result.resources.push(rec); + } + + Ok(result) + } + + pub fn write(&mut self, buffer: &mut BytePacketBuffer) -> 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; + + try!(self.header.write(buffer)); + + for question in &self.questions { + try!(question.write(buffer)); + } + for rec in &self.answers { + try!(rec.write(buffer)); + } + for rec in &self.authorities { + try!(rec.write(buffer)); + } + for rec in &self.resources { + try!(rec.write(buffer)); + } + + Ok(()) + } +} + +fn main() { + let qname = "yahoo.com"; + let qtype = QueryType::MX; + let server = ("8.8.8.8", 53); + + let socket = UdpSocket::bind(("0.0.0.0", 43210)).unwrap(); + + let mut packet = DnsPacket::new(); + + packet.header.id = 6666; + packet.header.questions = 1; + packet.header.recursion_desired = true; + packet.questions.push(DnsQuestion::new(qname.to_string(), qtype)); + + let mut req_buffer = BytePacketBuffer::new(); + packet.write(&mut req_buffer).unwrap(); + socket.send_to(&req_buffer.buf[0..req_buffer.pos], server).unwrap(); + + let mut res_buffer = BytePacketBuffer::new(); + socket.recv_from(&mut res_buffer.buf).unwrap(); + + let res_packet = DnsPacket::from_buffer(&mut res_buffer).unwrap(); + println!("{:?}", res_packet.header); + + for q in res_packet.questions { + println!("{:?}", q); + } + for rec in res_packet.answers { + println!("{:?}", rec); + } + for rec in res_packet.authorities { + println!("{:?}", rec); + } + for rec in res_packet.resources { + println!("{:?}", rec); + } +} diff --git a/samples/sample4.rs b/samples/sample4.rs new file mode 100644 index 0000000..900f736 --- /dev/null +++ b/samples/sample4.rs @@ -0,0 +1,768 @@ +use std::io::{Result, Read}; +use std::io::{Error, ErrorKind}; +use std::net::{Ipv4Addr,Ipv6Addr}; +use std::net::UdpSocket; + +pub struct BytePacketBuffer { + pub buf: [u8; 512], + pub pos: usize +} + +impl BytePacketBuffer { + pub fn new() -> BytePacketBuffer { + BytePacketBuffer { + buf: [0; 512], + pos: 0 + } + } + + fn pos(&self) -> usize { + self.pos + } + + fn step(&mut self, steps: usize) -> Result<()> { + self.pos += steps; + + Ok(()) + } + + fn seek(&mut self, pos: usize) -> Result<()> { + self.pos = pos; + + Ok(()) + } + + fn read(&mut self) -> Result { + if self.pos >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + let res = self.buf[self.pos]; + self.pos += 1; + + Ok(res) + } + + fn get(&mut self, pos: usize) -> Result { + if pos >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + Ok(self.buf[pos]) + } + + fn get_range(&mut self, start: usize, len: usize) -> Result<&[u8]> { + if start + len >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + Ok(&self.buf[start..start+len as usize]) + } + + fn read_u16(&mut self) -> Result + { + let res = ((try!(self.read()) as u16) << 8) | + (try!(self.read()) as u16); + + Ok(res) + } + + fn read_u32(&mut self) -> Result + { + let res = ((try!(self.read()) as u32) << 24) | + ((try!(self.read()) as u32) << 16) | + ((try!(self.read()) as u32) << 8) | + ((try!(self.read()) as u32) << 0); + + Ok(res) + } + + fn read_qname(&mut self, outstr: &mut String) -> Result<()> + { + let mut pos = self.pos(); + let mut jumped = false; + + let mut delim = ""; + loop { + let len = try!(self.get(pos)); + + // A two byte sequence, where the two highest bits of the first byte is + // set, represents a offset relative to the start of the buffer. We + // handle this by jumping to the offset, setting a flag to indicate + // that we shouldn't update the shared buffer position once done. + if (len & 0xC0) > 0 { + + // When a jump is performed, we only modify the shared buffer + // position once, and avoid making the change later on. + if !jumped { + try!(self.seek(pos+2)); + } + + let b2 = try!(self.get(pos+1)) as u16; + let offset = (((len as u16) ^ 0xC0) << 8) | b2; + pos = offset as usize; + jumped = true; + continue; + } + + pos += 1; + + // Names are terminated by an empty label of length 0 + if len == 0 { + break; + } + + outstr.push_str(delim); + + let str_buffer = try!(self.get_range(pos, len as usize)); + outstr.push_str(&String::from_utf8_lossy(str_buffer).to_lowercase()); + + delim = "."; + + pos += len as usize; + } + + if !jumped { + try!(self.seek(pos)); + } + + Ok(()) + } + + fn write(&mut self, val: u8) -> Result<()> { + if self.pos >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + self.buf[self.pos] = val; + self.pos += 1; + Ok(()) + } + + fn write_u8(&mut self, val: u8) -> Result<()> { + try!(self.write(val)); + + Ok(()) + } + + fn write_u16(&mut self, val: u16) -> Result<()> { + try!(self.write((val >> 8) as u8)); + try!(self.write((val & 0xFF) as u8)); + + Ok(()) + } + + fn write_u32(&mut self, val: u32) -> Result<()> { + try!(self.write(((val >> 24) & 0xFF) as u8)); + try!(self.write(((val >> 16) & 0xFF) as u8)); + try!(self.write(((val >> 8) & 0xFF) as u8)); + try!(self.write(((val >> 0) & 0xFF) as u8)); + + Ok(()) + } + + fn write_qname(&mut self, qname: &str) -> Result<()> { + + let split_str = qname.split('.').collect::>(); + + for label in split_str { + let len = label.len(); + if len > 0x34 { + return Err(Error::new(ErrorKind::InvalidInput, "Single label exceeds 63 characters of length")); + } + + try!(self.write_u8(len as u8)); + for b in label.as_bytes() { + try!(self.write_u8(*b)); + } + } + + try!(self.write_u8(0)); + + Ok(()) + } + + fn set(&mut self, pos: usize, val: u8) -> Result<()> { + self.buf[pos] = val; + + Ok(()) + } + + fn set_u16(&mut self, pos: usize, val: u16) -> Result<()> { + try!(self.set(pos,(val >> 8) as u8)); + try!(self.set(pos+1,(val & 0xFF) as u8)); + + Ok(()) + } +} + +#[derive(Copy,Clone,Debug,PartialEq,Eq)] +pub enum ResultCode { + NOERROR = 0, + FORMERR = 1, + SERVFAIL = 2, + NXDOMAIN = 3, + NOTIMP = 4, + REFUSED = 5 +} + +impl ResultCode { + pub fn from_num(num: u8) -> ResultCode { + match num { + 1 => ResultCode::FORMERR, + 2 => ResultCode::SERVFAIL, + 3 => ResultCode::NXDOMAIN, + 4 => ResultCode::NOTIMP, + 5 => ResultCode::REFUSED, + 0 | _ => ResultCode::NOERROR + } + } +} + +#[derive(Clone,Debug)] +pub struct DnsHeader { + pub id: u16, // 16 bits + + pub recursion_desired: bool, // 1 bit + pub truncated_message: bool, // 1 bit + pub authoritative_answer: bool, // 1 bit + pub opcode: u8, // 4 bits + pub response: bool, // 1 bit + + pub rescode: ResultCode, // 4 bits + pub checking_disabled: bool, // 1 bit + pub authed_data: bool, // 1 bit + pub z: bool, // 1 bit + pub recursion_available: bool, // 1 bit + + pub questions: u16, // 16 bits + pub answers: u16, // 16 bits + pub authoritative_entries: u16, // 16 bits + pub resource_entries: u16 // 16 bits +} + +impl DnsHeader { + pub fn new() -> DnsHeader { + DnsHeader { id: 0, + + recursion_desired: false, + truncated_message: false, + authoritative_answer: false, + opcode: 0, + response: false, + + rescode: ResultCode::NOERROR, + checking_disabled: false, + authed_data: false, + z: false, + recursion_available: false, + + questions: 0, + answers: 0, + authoritative_entries: 0, + resource_entries: 0 } + } + + pub fn read(&mut self, buffer: &mut BytePacketBuffer) -> Result<()> { + self.id = try!(buffer.read_u16()); + + let flags = try!(buffer.read_u16()); + let a = (flags >> 8) as u8; + let b = (flags & 0xFF) as u8; + self.recursion_desired = (a & (1 << 0)) > 0; + self.truncated_message = (a & (1 << 1)) > 0; + self.authoritative_answer = (a & (1 << 2)) > 0; + self.opcode = (a >> 3) & 0x0F; + self.response = (a & (1 << 7)) > 0; + + self.rescode = ResultCode::from_num(b & 0x0F); + self.checking_disabled = (b & (1 << 4)) > 0; + self.authed_data = (b & (1 << 5)) > 0; + self.z = (b & (1 << 6)) > 0; + self.recursion_available = (b & (1 << 7)) > 0; + + self.questions = try!(buffer.read_u16()); + self.answers = try!(buffer.read_u16()); + self.authoritative_entries = try!(buffer.read_u16()); + self.resource_entries = try!(buffer.read_u16()); + + // Return the constant header size + Ok(()) + } + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> { + try!(buffer.write_u16(self.id)); + + try!(buffer.write_u8( ((self.recursion_desired as u8)) | + ((self.truncated_message as u8) << 1) | + ((self.authoritative_answer as u8) << 2) | + (self.opcode << 3) | + ((self.response as u8) << 7) as u8) ); + + try!(buffer.write_u8( (self.rescode.clone() as u8) | + ((self.checking_disabled as u8) << 4) | + ((self.authed_data as u8) << 5) | + ((self.z as u8) << 6) | + ((self.recursion_available as u8) << 7) )); + + try!(buffer.write_u16(self.questions)); + try!(buffer.write_u16(self.answers)); + try!(buffer.write_u16(self.authoritative_entries)); + try!(buffer.write_u16(self.resource_entries)); + + Ok(()) + } +} + +#[derive(PartialEq,Eq,Debug,Clone,Hash,Copy)] +pub enum QueryType { + UNKNOWN(u16), + A, // 1 + NS, // 2 + CNAME, // 5 + MX, // 15 + AAAA, // 28 +} + +impl QueryType { + pub fn to_num(&self) -> u16 { + match *self { + QueryType::UNKNOWN(x) => x, + QueryType::A => 1, + QueryType::NS => 2, + QueryType::CNAME => 5, + QueryType::MX => 15, + QueryType::AAAA => 28, + } + } + + pub fn from_num(num: u16) -> QueryType { + match num { + 1 => QueryType::A, + 2 => QueryType::NS, + 5 => QueryType::CNAME, + 15 => QueryType::MX, + 28 => QueryType::AAAA, + _ => QueryType::UNKNOWN(num) + } + } +} + +#[derive(Debug,Clone,PartialEq,Eq)] +pub struct DnsQuestion { + pub name: String, + pub qtype: QueryType +} + +impl DnsQuestion { + pub fn new(name: String, qtype: QueryType) -> DnsQuestion { + DnsQuestion { + name: name, + qtype: qtype + } + } + + pub fn read(&mut self, buffer: &mut BytePacketBuffer) -> Result<()> { + try!(buffer.read_qname(&mut self.name)); + self.qtype = QueryType::from_num(try!(buffer.read_u16())); // qtype + let _ = try!(buffer.read_u16()); // class + + Ok(()) + } + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> { + + try!(buffer.write_qname(&self.name)); + + let typenum = self.qtype.to_num(); + try!(buffer.write_u16(typenum)); + try!(buffer.write_u16(1)); + + Ok(()) + } + +} + +#[derive(Debug,Clone,PartialEq,Eq,Hash,PartialOrd,Ord)] +#[allow(dead_code)] +pub enum DnsRecord { + UNKNOWN { + domain: String, + qtype: u16, + data_len: u16, + ttl: u32 + }, // 0 + A { + domain: String, + addr: Ipv4Addr, + ttl: u32 + }, // 1 + NS { + domain: String, + host: String, + ttl: u32 + }, // 2 + CNAME { + domain: String, + host: String, + ttl: u32 + }, // 5 + MX { + domain: String, + priority: u16, + host: String, + ttl: u32 + }, // 15 + AAAA { + domain: String, + addr: Ipv6Addr, + ttl: u32 + }, // 28 +} + +impl DnsRecord { + + pub fn read(buffer: &mut BytePacketBuffer) -> Result { + let mut domain = String::new(); + try!(buffer.read_qname(&mut domain)); + + let qtype_num = try!(buffer.read_u16()); + let qtype = QueryType::from_num(qtype_num); + let _ = try!(buffer.read_u16()); + let ttl = try!(buffer.read_u32()); + let data_len = try!(buffer.read_u16()); + + match qtype { + QueryType::A => { + let raw_addr = try!(buffer.read_u32()); + let addr = Ipv4Addr::new(((raw_addr >> 24) & 0xFF) as u8, + ((raw_addr >> 16) & 0xFF) as u8, + ((raw_addr >> 8) & 0xFF) as u8, + ((raw_addr >> 0) & 0xFF) as u8); + + Ok(DnsRecord::A { + domain: domain, + addr: addr, + ttl: ttl + }) + }, + QueryType::AAAA => { + let raw_addr1 = try!(buffer.read_u32()); + let raw_addr2 = try!(buffer.read_u32()); + let raw_addr3 = try!(buffer.read_u32()); + let raw_addr4 = try!(buffer.read_u32()); + let addr = Ipv6Addr::new(((raw_addr1 >> 16) & 0xFFFF) as u16, + ((raw_addr1 >> 0) & 0xFFFF) as u16, + ((raw_addr2 >> 16) & 0xFFFF) as u16, + ((raw_addr2 >> 0) & 0xFFFF) as u16, + ((raw_addr3 >> 16) & 0xFFFF) as u16, + ((raw_addr3 >> 0) & 0xFFFF) as u16, + ((raw_addr4 >> 16) & 0xFFFF) as u16, + ((raw_addr4 >> 0) & 0xFFFF) as u16); + + Ok(DnsRecord::AAAA { + domain: domain, + addr: addr, + ttl: ttl + }) + }, + QueryType::NS => { + let mut ns = String::new(); + try!(buffer.read_qname(&mut ns)); + + Ok(DnsRecord::NS { + domain: domain, + host: ns, + ttl: ttl + }) + }, + QueryType::CNAME => { + let mut cname = String::new(); + try!(buffer.read_qname(&mut cname)); + + Ok(DnsRecord::CNAME { + domain: domain, + host: cname, + ttl: ttl + }) + }, + QueryType::MX => { + let priority = try!(buffer.read_u16()); + let mut mx = String::new(); + try!(buffer.read_qname(&mut mx)); + + Ok(DnsRecord::MX { + domain: domain, + priority: priority, + host: mx, + ttl: ttl + }) + }, + QueryType::UNKNOWN(_) => { + try!(buffer.step(data_len as usize)); + + Ok(DnsRecord::UNKNOWN { + domain: domain, + qtype: qtype_num, + data_len: data_len, + ttl: ttl + }) + } + } + } + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result { + + let start_pos = buffer.pos(); + + match *self { + DnsRecord::A { ref domain, ref addr, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::A.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + try!(buffer.write_u16(4)); + + let octets = addr.octets(); + try!(buffer.write_u8(octets[0])); + try!(buffer.write_u8(octets[1])); + try!(buffer.write_u8(octets[2])); + try!(buffer.write_u8(octets[3])); + }, + DnsRecord::NS { ref domain, ref host, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::NS.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + + let pos = buffer.pos(); + try!(buffer.write_u16(0)); + + try!(buffer.write_qname(host)); + + let size = buffer.pos() - (pos + 2); + try!(buffer.set_u16(pos, size as u16)); + }, + DnsRecord::CNAME { ref domain, ref host, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::CNAME.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + + let pos = buffer.pos(); + try!(buffer.write_u16(0)); + + try!(buffer.write_qname(host)); + + let size = buffer.pos() - (pos + 2); + try!(buffer.set_u16(pos, size as u16)); + }, + DnsRecord::MX { ref domain, priority, ref host, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::MX.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + + let pos = buffer.pos(); + try!(buffer.write_u16(0)); + + try!(buffer.write_u16(priority)); + try!(buffer.write_qname(host)); + + let size = buffer.pos() - (pos + 2); + try!(buffer.set_u16(pos, size as u16)); + }, + DnsRecord::AAAA { ref domain, ref addr, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::AAAA.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + try!(buffer.write_u16(16)); + + for octet in &addr.segments() { + try!(buffer.write_u16(*octet)); + } + }, + DnsRecord::UNKNOWN { .. } => { + println!("Skipping record: {:?}", self); + } + } + + Ok(buffer.pos() - start_pos) + } + +} + +#[derive(Clone, Debug)] +pub struct DnsPacket { + pub header: DnsHeader, + pub questions: Vec, + pub answers: Vec, + pub authorities: Vec, + pub resources: Vec +} + +impl DnsPacket { + pub fn new() -> DnsPacket { + DnsPacket { + header: DnsHeader::new(), + questions: Vec::new(), + answers: Vec::new(), + authorities: Vec::new(), + resources: Vec::new() + } + } + + pub fn from_buffer(buffer: &mut BytePacketBuffer) -> Result { + let mut result = DnsPacket::new(); + try!(result.header.read(buffer)); + + for _ in 0..result.header.questions { + let mut question = DnsQuestion::new("".to_string(), + QueryType::UNKNOWN(0)); + try!(question.read(buffer)); + result.questions.push(question); + } + + for _ in 0..result.header.answers { + let rec = try!(DnsRecord::read(buffer)); + result.answers.push(rec); + } + for _ in 0..result.header.authoritative_entries { + let rec = try!(DnsRecord::read(buffer)); + result.authorities.push(rec); + } + for _ in 0..result.header.resource_entries { + let rec = try!(DnsRecord::read(buffer)); + result.resources.push(rec); + } + + Ok(result) + } + + pub fn write(&mut self, buffer: &mut BytePacketBuffer) -> 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; + + try!(self.header.write(buffer)); + + for question in &self.questions { + try!(question.write(buffer)); + } + for rec in &self.answers { + try!(rec.write(buffer)); + } + for rec in &self.authorities { + try!(rec.write(buffer)); + } + for rec in &self.resources { + try!(rec.write(buffer)); + } + + Ok(()) + } +} + +fn lookup(qname: &str, qtype: QueryType, server: (&str, u16)) -> Result { + let socket = try!(UdpSocket::bind(("0.0.0.0", 43210))); + + let mut packet = DnsPacket::new(); + + packet.header.id = 6666; + packet.header.questions = 1; + packet.header.recursion_desired = true; + packet.questions.push(DnsQuestion::new(qname.to_string(), qtype)); + + let mut req_buffer = BytePacketBuffer::new(); + packet.write(&mut req_buffer).unwrap(); + try!(socket.send_to(&req_buffer.buf[0..req_buffer.pos], server)); + + let mut res_buffer = BytePacketBuffer::new(); + socket.recv_from(&mut res_buffer.buf).unwrap(); + + DnsPacket::from_buffer(&mut res_buffer) +} + +fn main() { + let server = ("8.8.8.8", 53); + + let socket = UdpSocket::bind(("0.0.0.0", 2053)).unwrap(); + + loop { + let mut req_buffer = BytePacketBuffer::new(); + let (_, src) = match socket.recv_from(&mut req_buffer.buf) { + Ok(x) => x, + Err(e) => { + println!("Failed to read from UDP socket: {:?}", e); + continue; + } + }; + + let request = match DnsPacket::from_buffer(&mut req_buffer) { + Ok(x) => x, + Err(e) => { + println!("Failed to parse UDP query packet: {:?}", e); + continue; + } + }; + + let mut packet = DnsPacket::new(); + packet.header.id = request.header.id; + packet.header.recursion_desired = true; + packet.header.recursion_available = true; + packet.header.response = true; + + if request.questions.is_empty() { + packet.header.rescode = ResultCode::FORMERR; + } + else { + let question = &request.questions[0]; + println!("Received query: {:?}", question); + + if let Ok(result) = lookup(&question.name, question.qtype, server) { + packet.questions.push(question.clone()); + packet.header.rescode = result.header.rescode; + + for rec in result.answers { + println!("Answer: {:?}", rec); + packet.answers.push(rec); + } + for rec in result.authorities { + println!("Authority: {:?}", rec); + packet.authorities.push(rec); + } + for rec in result.resources { + println!("Resource: {:?}", rec); + packet.resources.push(rec); + } + } else { + packet.header.rescode = ResultCode::SERVFAIL; + } + } + + let mut res_buffer = BytePacketBuffer::new(); + match packet.write(&mut res_buffer) { + Ok(_) => {}, + Err(e) => { + println!("Failed to encode UDP response packet: {:?}", e); + continue; + } + }; + + let len = res_buffer.pos(); + let data = match res_buffer.get_range(0, len) { + Ok(x) => x, + Err(e) => { + println!("Failed to retrieve response buffer: {:?}", e); + continue; + } + }; + + match socket.send_to(data, src) { + Ok(_) => {}, + Err(e) => { + println!("Failed to send response buffer: {:?}", e); + continue; + } + }; + } +} diff --git a/samples/sample5.rs b/samples/sample5.rs new file mode 100644 index 0000000..94a254b --- /dev/null +++ b/samples/sample5.rs @@ -0,0 +1,885 @@ +use std::io::{Result, Read}; +use std::io::{Error, ErrorKind}; +use std::net::{Ipv4Addr,Ipv6Addr}; +use std::net::UdpSocket; + +pub struct BytePacketBuffer { + pub buf: [u8; 512], + pub pos: usize +} + +impl BytePacketBuffer { + pub fn new() -> BytePacketBuffer { + BytePacketBuffer { + buf: [0; 512], + pos: 0 + } + } + + fn pos(&self) -> usize { + self.pos + } + + fn step(&mut self, steps: usize) -> Result<()> { + self.pos += steps; + + Ok(()) + } + + fn seek(&mut self, pos: usize) -> Result<()> { + self.pos = pos; + + Ok(()) + } + + fn read(&mut self) -> Result { + if self.pos >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + let res = self.buf[self.pos]; + self.pos += 1; + + Ok(res) + } + + fn get(&mut self, pos: usize) -> Result { + if pos >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + Ok(self.buf[pos]) + } + + fn get_range(&mut self, start: usize, len: usize) -> Result<&[u8]> { + if start + len >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + Ok(&self.buf[start..start+len as usize]) + } + + fn read_u16(&mut self) -> Result + { + let res = ((try!(self.read()) as u16) << 8) | + (try!(self.read()) as u16); + + Ok(res) + } + + fn read_u32(&mut self) -> Result + { + let res = ((try!(self.read()) as u32) << 24) | + ((try!(self.read()) as u32) << 16) | + ((try!(self.read()) as u32) << 8) | + ((try!(self.read()) as u32) << 0); + + Ok(res) + } + + fn read_qname(&mut self, outstr: &mut String) -> Result<()> + { + let mut pos = self.pos(); + let mut jumped = false; + + let mut delim = ""; + loop { + let len = try!(self.get(pos)); + + // A two byte sequence, where the two highest bits of the first byte is + // set, represents a offset relative to the start of the buffer. We + // handle this by jumping to the offset, setting a flag to indicate + // that we shouldn't update the shared buffer position once done. + if (len & 0xC0) > 0 { + + // When a jump is performed, we only modify the shared buffer + // position once, and avoid making the change later on. + if !jumped { + try!(self.seek(pos+2)); + } + + let b2 = try!(self.get(pos+1)) as u16; + let offset = (((len as u16) ^ 0xC0) << 8) | b2; + pos = offset as usize; + jumped = true; + continue; + } + + pos += 1; + + // Names are terminated by an empty label of length 0 + if len == 0 { + break; + } + + outstr.push_str(delim); + + let str_buffer = try!(self.get_range(pos, len as usize)); + outstr.push_str(&String::from_utf8_lossy(str_buffer).to_lowercase()); + + delim = "."; + + pos += len as usize; + } + + if !jumped { + try!(self.seek(pos)); + } + + Ok(()) + } + + fn write(&mut self, val: u8) -> Result<()> { + if self.pos >= 512 { + return Err(Error::new(ErrorKind::InvalidInput, "End of buffer")); + } + self.buf[self.pos] = val; + self.pos += 1; + Ok(()) + } + + fn write_u8(&mut self, val: u8) -> Result<()> { + try!(self.write(val)); + + Ok(()) + } + + fn write_u16(&mut self, val: u16) -> Result<()> { + try!(self.write((val >> 8) as u8)); + try!(self.write((val & 0xFF) as u8)); + + Ok(()) + } + + fn write_u32(&mut self, val: u32) -> Result<()> { + try!(self.write(((val >> 24) & 0xFF) as u8)); + try!(self.write(((val >> 16) & 0xFF) as u8)); + try!(self.write(((val >> 8) & 0xFF) as u8)); + try!(self.write(((val >> 0) & 0xFF) as u8)); + + Ok(()) + } + + fn write_qname(&mut self, qname: &str) -> Result<()> { + + let split_str = qname.split('.').collect::>(); + + for label in split_str { + let len = label.len(); + if len > 0x34 { + return Err(Error::new(ErrorKind::InvalidInput, "Single label exceeds 63 characters of length")); + } + + try!(self.write_u8(len as u8)); + for b in label.as_bytes() { + try!(self.write_u8(*b)); + } + } + + try!(self.write_u8(0)); + + Ok(()) + } + + fn set(&mut self, pos: usize, val: u8) -> Result<()> { + self.buf[pos] = val; + + Ok(()) + } + + fn set_u16(&mut self, pos: usize, val: u16) -> Result<()> { + try!(self.set(pos,(val >> 8) as u8)); + try!(self.set(pos+1,(val & 0xFF) as u8)); + + Ok(()) + } +} + +#[derive(Copy,Clone,Debug,PartialEq,Eq)] +pub enum ResultCode { + NOERROR = 0, + FORMERR = 1, + SERVFAIL = 2, + NXDOMAIN = 3, + NOTIMP = 4, + REFUSED = 5 +} + +impl ResultCode { + pub fn from_num(num: u8) -> ResultCode { + match num { + 1 => ResultCode::FORMERR, + 2 => ResultCode::SERVFAIL, + 3 => ResultCode::NXDOMAIN, + 4 => ResultCode::NOTIMP, + 5 => ResultCode::REFUSED, + 0 | _ => ResultCode::NOERROR + } + } +} + +#[derive(Clone,Debug)] +pub struct DnsHeader { + pub id: u16, // 16 bits + + pub recursion_desired: bool, // 1 bit + pub truncated_message: bool, // 1 bit + pub authoritative_answer: bool, // 1 bit + pub opcode: u8, // 4 bits + pub response: bool, // 1 bit + + pub rescode: ResultCode, // 4 bits + pub checking_disabled: bool, // 1 bit + pub authed_data: bool, // 1 bit + pub z: bool, // 1 bit + pub recursion_available: bool, // 1 bit + + pub questions: u16, // 16 bits + pub answers: u16, // 16 bits + pub authoritative_entries: u16, // 16 bits + pub resource_entries: u16 // 16 bits +} + +impl DnsHeader { + pub fn new() -> DnsHeader { + DnsHeader { id: 0, + + recursion_desired: false, + truncated_message: false, + authoritative_answer: false, + opcode: 0, + response: false, + + rescode: ResultCode::NOERROR, + checking_disabled: false, + authed_data: false, + z: false, + recursion_available: false, + + questions: 0, + answers: 0, + authoritative_entries: 0, + resource_entries: 0 } + } + + pub fn read(&mut self, buffer: &mut BytePacketBuffer) -> Result<()> { + self.id = try!(buffer.read_u16()); + + let flags = try!(buffer.read_u16()); + let a = (flags >> 8) as u8; + let b = (flags & 0xFF) as u8; + self.recursion_desired = (a & (1 << 0)) > 0; + self.truncated_message = (a & (1 << 1)) > 0; + self.authoritative_answer = (a & (1 << 2)) > 0; + self.opcode = (a >> 3) & 0x0F; + self.response = (a & (1 << 7)) > 0; + + self.rescode = ResultCode::from_num(b & 0x0F); + self.checking_disabled = (b & (1 << 4)) > 0; + self.authed_data = (b & (1 << 5)) > 0; + self.z = (b & (1 << 6)) > 0; + self.recursion_available = (b & (1 << 7)) > 0; + + self.questions = try!(buffer.read_u16()); + self.answers = try!(buffer.read_u16()); + self.authoritative_entries = try!(buffer.read_u16()); + self.resource_entries = try!(buffer.read_u16()); + + // Return the constant header size + Ok(()) + } + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> { + try!(buffer.write_u16(self.id)); + + try!(buffer.write_u8( ((self.recursion_desired as u8)) | + ((self.truncated_message as u8) << 1) | + ((self.authoritative_answer as u8) << 2) | + (self.opcode << 3) | + ((self.response as u8) << 7) as u8) ); + + try!(buffer.write_u8( (self.rescode.clone() as u8) | + ((self.checking_disabled as u8) << 4) | + ((self.authed_data as u8) << 5) | + ((self.z as u8) << 6) | + ((self.recursion_available as u8) << 7) )); + + try!(buffer.write_u16(self.questions)); + try!(buffer.write_u16(self.answers)); + try!(buffer.write_u16(self.authoritative_entries)); + try!(buffer.write_u16(self.resource_entries)); + + Ok(()) + } +} + +#[derive(PartialEq,Eq,Debug,Clone,Hash,Copy)] +pub enum QueryType { + UNKNOWN(u16), + A, // 1 + NS, // 2 + CNAME, // 5 + MX, // 15 + AAAA, // 28 +} + +impl QueryType { + pub fn to_num(&self) -> u16 { + match *self { + QueryType::UNKNOWN(x) => x, + QueryType::A => 1, + QueryType::NS => 2, + QueryType::CNAME => 5, + QueryType::MX => 15, + QueryType::AAAA => 28, + } + } + + pub fn from_num(num: u16) -> QueryType { + match num { + 1 => QueryType::A, + 2 => QueryType::NS, + 5 => QueryType::CNAME, + 15 => QueryType::MX, + 28 => QueryType::AAAA, + _ => QueryType::UNKNOWN(num) + } + } +} + +#[derive(Debug,Clone,PartialEq,Eq)] +pub struct DnsQuestion { + pub name: String, + pub qtype: QueryType +} + +impl DnsQuestion { + pub fn new(name: String, qtype: QueryType) -> DnsQuestion { + DnsQuestion { + name: name, + qtype: qtype + } + } + + pub fn read(&mut self, buffer: &mut BytePacketBuffer) -> Result<()> { + try!(buffer.read_qname(&mut self.name)); + self.qtype = QueryType::from_num(try!(buffer.read_u16())); // qtype + let _ = try!(buffer.read_u16()); // class + + Ok(()) + } + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> { + + try!(buffer.write_qname(&self.name)); + + let typenum = self.qtype.to_num(); + try!(buffer.write_u16(typenum)); + try!(buffer.write_u16(1)); + + Ok(()) + } + +} + +#[derive(Debug,Clone,PartialEq,Eq,Hash,PartialOrd,Ord)] +#[allow(dead_code)] +pub enum DnsRecord { + UNKNOWN { + domain: String, + qtype: u16, + data_len: u16, + ttl: u32 + }, // 0 + A { + domain: String, + addr: Ipv4Addr, + ttl: u32 + }, // 1 + NS { + domain: String, + host: String, + ttl: u32 + }, // 2 + CNAME { + domain: String, + host: String, + ttl: u32 + }, // 5 + MX { + domain: String, + priority: u16, + host: String, + ttl: u32 + }, // 15 + AAAA { + domain: String, + addr: Ipv6Addr, + ttl: u32 + }, // 28 +} + +impl DnsRecord { + + pub fn read(buffer: &mut BytePacketBuffer) -> Result { + let mut domain = String::new(); + try!(buffer.read_qname(&mut domain)); + + let qtype_num = try!(buffer.read_u16()); + let qtype = QueryType::from_num(qtype_num); + let _ = try!(buffer.read_u16()); + let ttl = try!(buffer.read_u32()); + let data_len = try!(buffer.read_u16()); + + match qtype { + QueryType::A => { + let raw_addr = try!(buffer.read_u32()); + let addr = Ipv4Addr::new(((raw_addr >> 24) & 0xFF) as u8, + ((raw_addr >> 16) & 0xFF) as u8, + ((raw_addr >> 8) & 0xFF) as u8, + ((raw_addr >> 0) & 0xFF) as u8); + + Ok(DnsRecord::A { + domain: domain, + addr: addr, + ttl: ttl + }) + }, + QueryType::AAAA => { + let raw_addr1 = try!(buffer.read_u32()); + let raw_addr2 = try!(buffer.read_u32()); + let raw_addr3 = try!(buffer.read_u32()); + let raw_addr4 = try!(buffer.read_u32()); + let addr = Ipv6Addr::new(((raw_addr1 >> 16) & 0xFFFF) as u16, + ((raw_addr1 >> 0) & 0xFFFF) as u16, + ((raw_addr2 >> 16) & 0xFFFF) as u16, + ((raw_addr2 >> 0) & 0xFFFF) as u16, + ((raw_addr3 >> 16) & 0xFFFF) as u16, + ((raw_addr3 >> 0) & 0xFFFF) as u16, + ((raw_addr4 >> 16) & 0xFFFF) as u16, + ((raw_addr4 >> 0) & 0xFFFF) as u16); + + Ok(DnsRecord::AAAA { + domain: domain, + addr: addr, + ttl: ttl + }) + }, + QueryType::NS => { + let mut ns = String::new(); + try!(buffer.read_qname(&mut ns)); + + Ok(DnsRecord::NS { + domain: domain, + host: ns, + ttl: ttl + }) + }, + QueryType::CNAME => { + let mut cname = String::new(); + try!(buffer.read_qname(&mut cname)); + + Ok(DnsRecord::CNAME { + domain: domain, + host: cname, + ttl: ttl + }) + }, + QueryType::MX => { + let priority = try!(buffer.read_u16()); + let mut mx = String::new(); + try!(buffer.read_qname(&mut mx)); + + Ok(DnsRecord::MX { + domain: domain, + priority: priority, + host: mx, + ttl: ttl + }) + }, + QueryType::UNKNOWN(_) => { + try!(buffer.step(data_len as usize)); + + Ok(DnsRecord::UNKNOWN { + domain: domain, + qtype: qtype_num, + data_len: data_len, + ttl: ttl + }) + } + } + } + + pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result { + + let start_pos = buffer.pos(); + + match *self { + DnsRecord::A { ref domain, ref addr, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::A.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + try!(buffer.write_u16(4)); + + let octets = addr.octets(); + try!(buffer.write_u8(octets[0])); + try!(buffer.write_u8(octets[1])); + try!(buffer.write_u8(octets[2])); + try!(buffer.write_u8(octets[3])); + }, + DnsRecord::NS { ref domain, ref host, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::NS.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + + let pos = buffer.pos(); + try!(buffer.write_u16(0)); + + try!(buffer.write_qname(host)); + + let size = buffer.pos() - (pos + 2); + try!(buffer.set_u16(pos, size as u16)); + }, + DnsRecord::CNAME { ref domain, ref host, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::CNAME.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + + let pos = buffer.pos(); + try!(buffer.write_u16(0)); + + try!(buffer.write_qname(host)); + + let size = buffer.pos() - (pos + 2); + try!(buffer.set_u16(pos, size as u16)); + }, + DnsRecord::MX { ref domain, priority, ref host, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::MX.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + + let pos = buffer.pos(); + try!(buffer.write_u16(0)); + + try!(buffer.write_u16(priority)); + try!(buffer.write_qname(host)); + + let size = buffer.pos() - (pos + 2); + try!(buffer.set_u16(pos, size as u16)); + }, + DnsRecord::AAAA { ref domain, ref addr, ttl } => { + try!(buffer.write_qname(domain)); + try!(buffer.write_u16(QueryType::AAAA.to_num())); + try!(buffer.write_u16(1)); + try!(buffer.write_u32(ttl)); + try!(buffer.write_u16(16)); + + for octet in &addr.segments() { + try!(buffer.write_u16(*octet)); + } + }, + DnsRecord::UNKNOWN { .. } => { + println!("Skipping record: {:?}", self); + } + } + + Ok(buffer.pos() - start_pos) + } + +} + +#[derive(Clone, Debug)] +pub struct DnsPacket { + pub header: DnsHeader, + pub questions: Vec, + pub answers: Vec, + pub authorities: Vec, + pub resources: Vec +} + +impl DnsPacket { + pub fn new() -> DnsPacket { + DnsPacket { + header: DnsHeader::new(), + questions: Vec::new(), + answers: Vec::new(), + authorities: Vec::new(), + resources: Vec::new() + } + } + + pub fn from_buffer(buffer: &mut BytePacketBuffer) -> Result { + let mut result = DnsPacket::new(); + try!(result.header.read(buffer)); + + for _ in 0..result.header.questions { + let mut question = DnsQuestion::new("".to_string(), + QueryType::UNKNOWN(0)); + try!(question.read(buffer)); + result.questions.push(question); + } + + for _ in 0..result.header.answers { + let rec = try!(DnsRecord::read(buffer)); + result.answers.push(rec); + } + for _ in 0..result.header.authoritative_entries { + let rec = try!(DnsRecord::read(buffer)); + result.authorities.push(rec); + } + for _ in 0..result.header.resource_entries { + let rec = try!(DnsRecord::read(buffer)); + result.resources.push(rec); + } + + Ok(result) + } + + pub fn write(&mut self, buffer: &mut BytePacketBuffer) -> 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; + + try!(self.header.write(buffer)); + + for question in &self.questions { + try!(question.write(buffer)); + } + for rec in &self.answers { + try!(rec.write(buffer)); + } + for rec in &self.authorities { + try!(rec.write(buffer)); + } + for rec in &self.resources { + try!(rec.write(buffer)); + } + + Ok(()) + } + + pub fn get_random_a(&self) -> Option { + if !self.answers.is_empty() { + let a_record = &self.answers[0]; + if let DnsRecord::A{ ref addr, .. } = *a_record { + return Some(addr.to_string()); + } + } + + None + } + + pub fn get_resolved_ns(&self, qname: &str) -> Option { + + let mut new_authorities = Vec::new(); + for auth in &self.authorities { + if let DnsRecord::NS { ref domain, ref host, .. } = *auth { + if !qname.ends_with(domain) { + continue; + } + + for rsrc in &self.resources { + if let DnsRecord::A{ ref domain, ref addr, ttl } = *rsrc { + if domain != host { + continue; + } + + let rec = DnsRecord::A { + domain: host.clone(), + addr: *addr, + ttl: ttl + }; + + new_authorities.push(rec); + } + } + } + } + + if !new_authorities.is_empty() { + if let DnsRecord::A { addr, .. } = new_authorities[0] { + return Some(addr.to_string()); + } + } + + None + } + + pub fn get_unresolved_ns(&self, qname: &str) -> Option { + + let mut new_authorities = Vec::new(); + for auth in &self.authorities { + if let DnsRecord::NS { ref domain, ref host, .. } = *auth { + if !qname.ends_with(domain) { + continue; + } + + new_authorities.push(host); + } + } + + if !new_authorities.is_empty() { + return Some(new_authorities[0].clone()); + } + + None + } + +} + +fn lookup(qname: &str, qtype: QueryType, server: (&str, u16)) -> Result { + let socket = try!(UdpSocket::bind(("0.0.0.0", 43210))); + + let mut packet = DnsPacket::new(); + + packet.header.id = 6666; + packet.header.questions = 1; + packet.header.recursion_desired = true; + packet.questions.push(DnsQuestion::new(qname.to_string(), qtype)); + + let mut req_buffer = BytePacketBuffer::new(); + packet.write(&mut req_buffer).unwrap(); + try!(socket.send_to(&req_buffer.buf[0..req_buffer.pos], server)); + + let mut res_buffer = BytePacketBuffer::new(); + socket.recv_from(&mut res_buffer.buf).unwrap(); + + DnsPacket::from_buffer(&mut res_buffer) +} + +fn recursive_lookup(qname: &str, qtype: QueryType) -> Result { + + let mut ns = "198.41.0.4".to_string(); + + // Start querying name servers + loop { + println!("attempting lookup of {:?} {} with ns {}", qtype, qname, ns); + + let ns_copy = ns.clone(); + + let server = (ns_copy.as_str(), 53); + let response = try!(lookup(qname, qtype.clone(), server)); + + // If we've got an actual answer, we're done! + if !response.answers.is_empty() && + response.header.rescode == ResultCode::NOERROR { + + return Ok(response.clone()); + } + + if response.header.rescode == ResultCode::NXDOMAIN { + return Ok(response.clone()); + } + + // Otherwise, try to find a new nameserver based on NS and a + // corresponding A record in the additional section + if let Some(new_ns) = response.get_resolved_ns(qname) { + // If there is such a record, we can retry the loop with that NS + ns = new_ns.clone(); + + continue; + } + + // If not, we'll have to resolve the ip of a NS record + let new_ns_name = match response.get_unresolved_ns(qname) { + Some(x) => x, + None => return Ok(response.clone()) + }; + + // Recursively resolve the NS + let recursive_response = try!(recursive_lookup(&new_ns_name, QueryType::A)); + + // Pick a random IP and restart + if let Some(new_ns) = recursive_response.get_random_a() { + ns = new_ns.clone(); + } else { + return Ok(response.clone()) + } + } +} + +fn main() { + let socket = UdpSocket::bind(("0.0.0.0", 2053)).unwrap(); + + loop { + let mut req_buffer = BytePacketBuffer::new(); + let (_, src) = match socket.recv_from(&mut req_buffer.buf) { + Ok(x) => x, + Err(e) => { + println!("Failed to read from UDP socket: {:?}", e); + continue; + } + }; + + let request = match DnsPacket::from_buffer(&mut req_buffer) { + Ok(x) => x, + Err(e) => { + println!("Failed to parse UDP query packet: {:?}", e); + continue; + } + }; + + let mut packet = DnsPacket::new(); + packet.header.id = request.header.id; + packet.header.recursion_desired = true; + packet.header.recursion_available = true; + packet.header.response = true; + + if request.questions.is_empty() { + packet.header.rescode = ResultCode::FORMERR; + } + else { + let question = &request.questions[0]; + println!("Received query: {:?}", question); + + if let Ok(result) = recursive_lookup(&question.name, question.qtype) { + packet.questions.push(question.clone()); + packet.header.rescode = result.header.rescode; + + for rec in result.answers { + println!("Answer: {:?}", rec); + packet.answers.push(rec); + } + for rec in result.authorities { + println!("Authority: {:?}", rec); + packet.authorities.push(rec); + } + for rec in result.resources { + println!("Resource: {:?}", rec); + packet.resources.push(rec); + } + } else { + packet.header.rescode = ResultCode::SERVFAIL; + } + } + + let mut res_buffer = BytePacketBuffer::new(); + match packet.write(&mut res_buffer) { + Ok(_) => {}, + Err(e) => { + println!("Failed to encode UDP response packet: {:?}", e); + continue; + } + }; + + let len = res_buffer.pos(); + let data = match res_buffer.get_range(0, len) { + Ok(x) => x, + Err(e) => { + println!("Failed to retrieve response buffer: {:?}", e); + continue; + } + }; + + match socket.send_to(data, src) { + Ok(_) => {}, + Err(e) => { + println!("Failed to send response buffer: {:?}", e); + continue; + } + }; + } +} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index e69de29..0000000 diff --git a/tango-build.rs b/tango-build.rs deleted file mode 100644 index 69d47a7..0000000 --- a/tango-build.rs +++ /dev/null @@ -1,3 +0,0 @@ -extern crate tango; - -fn main() { tango::process_root().unwrap() }