mirror of
https://github.com/EmilHernvall/dnsguide.git
synced 2024-12-22 15:34:17 +07:00
Gave up on literate programming
This commit is contained in:
parent
700334b7ad
commit
945d896208
109
Cargo.lock
generated
109
Cargo.lock
generated
@ -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"
|
||||
|
12
Cargo.toml
12
Cargo.toml
@ -1,12 +0,0 @@
|
||||
[package]
|
||||
name = "dnsguide"
|
||||
version = "0.1.0"
|
||||
authors = ["Emil Hernvall <emil@c0la.se>"]
|
||||
|
||||
build = "tango-build.rs"
|
||||
|
||||
[build-dependencies.tango]
|
||||
git = "https://github.com/pnkfelix/tango"
|
||||
|
||||
[lib]
|
||||
name = "lib"
|
14
README.md
14
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)
|
||||
|
@ -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)]
|
@ -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<usize>;
|
||||
fn write_u8(&mut self, val: u8) -> Result<usize>;
|
||||
fn write_u16(&mut self, val: u16) -> Result<usize>;
|
||||
fn write_u32(&mut self, val: u32) -> Result<usize>;
|
||||
fn write_qname(&mut self, qname: &str) -> Result<usize>;
|
||||
}
|
||||
- snip -
|
||||
|
||||
impl WriteableBuffer for BytePacketBuffer {
|
||||
|
||||
fn write(&mut self, val: u8) -> Result<usize> {
|
||||
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<usize> {
|
||||
fn write_u8(&mut self, val: u8) -> Result<()> {
|
||||
try!(self.write(val));
|
||||
|
||||
Ok(1)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_u16(&mut self, val: u16) -> Result<usize> {
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
fn write_qname(&mut self, qname: &str) -> Result<()> {
|
||||
|
||||
let split_str = qname.split('.').collect::<Vec<&str>>();
|
||||
|
||||
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<usize>;
|
||||
}
|
||||
impl DnsHeader {
|
||||
|
||||
impl BufferWriteable for DnsHeader {
|
||||
- snip -
|
||||
|
||||
fn write(&self, buffer: &mut BytePacketBuffer) -> Result<usize> {
|
||||
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<usize> {
|
||||
- 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<usize> {
|
||||
- snip -
|
||||
|
||||
pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<usize> {
|
||||
|
||||
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<usize>
|
||||
- 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:
|
472
chapter3.md
Normal file
472
chapter3.md
Normal file
@ -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<DnsRecord> {
|
||||
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<usize> {
|
||||
|
||||
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!
|
352
chapter4.md
Normal file
352
chapter4.md
Normal file
@ -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<DnsPacket> {
|
||||
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!
|
427
chapter5.md
Normal file
427
chapter5.md
Normal file
@ -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<String> {
|
||||
if !self.answers.is_empty() {
|
||||
let idx = random::<usize>() % 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<String> {
|
||||
```
|
||||
|
||||
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<String> {
|
||||
|
||||
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::<usize>() % 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<DnsPacket> {
|
||||
```
|
||||
|
||||
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!
|
@ -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<u8> {
|
||||
fn read(&mut self) -> Result<u8> {
|
||||
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<u8> {
|
||||
fn get(&mut self, pos: usize) -> Result<u8> {
|
||||
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<u16>
|
||||
fn read_u16(&mut self) -> Result<u16>
|
||||
{
|
||||
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<u32>
|
||||
fn read_u32(&mut self) -> Result<u32>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
550
samples/sample2.rs
Normal file
550
samples/sample2.rs
Normal file
@ -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<u8> {
|
||||
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<u8> {
|
||||
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<u16>
|
||||
{
|
||||
let res = ((try!(self.read()) as u16) << 8) |
|
||||
(try!(self.read()) as u16);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn read_u32(&mut self) -> Result<u32>
|
||||
{
|
||||
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::<Vec<&str>>();
|
||||
|
||||
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<DnsRecord> {
|
||||
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<usize> {
|
||||
|
||||
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<DnsQuestion>,
|
||||
pub answers: Vec<DnsRecord>,
|
||||
pub authorities: Vec<DnsRecord>,
|
||||
pub resources: Vec<DnsRecord>
|
||||
}
|
||||
|
||||
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<DnsPacket> {
|
||||
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);
|
||||
}
|
||||
}
|
701
samples/sample3.rs
Normal file
701
samples/sample3.rs
Normal file
@ -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<u8> {
|
||||
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<u8> {
|
||||
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<u16>
|
||||
{
|
||||
let res = ((try!(self.read()) as u16) << 8) |
|
||||
(try!(self.read()) as u16);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn read_u32(&mut self) -> Result<u32>
|
||||
{
|
||||
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::<Vec<&str>>();
|
||||
|
||||
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<DnsRecord> {
|
||||
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<usize> {
|
||||
|
||||
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<DnsQuestion>,
|
||||
pub answers: Vec<DnsRecord>,
|
||||
pub authorities: Vec<DnsRecord>,
|
||||
pub resources: Vec<DnsRecord>
|
||||
}
|
||||
|
||||
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<DnsPacket> {
|
||||
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);
|
||||
}
|
||||
}
|
768
samples/sample4.rs
Normal file
768
samples/sample4.rs
Normal file
@ -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<u8> {
|
||||
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<u8> {
|
||||
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<u16>
|
||||
{
|
||||
let res = ((try!(self.read()) as u16) << 8) |
|
||||
(try!(self.read()) as u16);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn read_u32(&mut self) -> Result<u32>
|
||||
{
|
||||
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::<Vec<&str>>();
|
||||
|
||||
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<DnsRecord> {
|
||||
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<usize> {
|
||||
|
||||
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<DnsQuestion>,
|
||||
pub answers: Vec<DnsRecord>,
|
||||
pub authorities: Vec<DnsRecord>,
|
||||
pub resources: Vec<DnsRecord>
|
||||
}
|
||||
|
||||
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<DnsPacket> {
|
||||
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<DnsPacket> {
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
885
samples/sample5.rs
Normal file
885
samples/sample5.rs
Normal file
@ -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<u8> {
|
||||
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<u8> {
|
||||
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<u16>
|
||||
{
|
||||
let res = ((try!(self.read()) as u16) << 8) |
|
||||
(try!(self.read()) as u16);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn read_u32(&mut self) -> Result<u32>
|
||||
{
|
||||
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::<Vec<&str>>();
|
||||
|
||||
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<DnsRecord> {
|
||||
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<usize> {
|
||||
|
||||
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<DnsQuestion>,
|
||||
pub answers: Vec<DnsRecord>,
|
||||
pub authorities: Vec<DnsRecord>,
|
||||
pub resources: Vec<DnsRecord>
|
||||
}
|
||||
|
||||
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<DnsPacket> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
|
||||
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<String> {
|
||||
|
||||
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<DnsPacket> {
|
||||
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<DnsPacket> {
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
extern crate tango;
|
||||
|
||||
fn main() { tango::process_root().unwrap() }
|
Loading…
Reference in New Issue
Block a user