mirror of
https://github.com/EmilHernvall/dnsguide.git
synced 2025-07-12 08:48:26 +07:00
Tweaks
This commit is contained in:
@ -216,7 +216,7 @@ impl DnsPacket {
|
|||||||
### Implementing a stub resolver
|
### Implementing a stub resolver
|
||||||
|
|
||||||
We're ready to implement our stub resolver. Rust includes a convenient
|
We're ready to implement our stub resolver. Rust includes a convenient
|
||||||
`UDPSocket` which does most of the work. First there's some house keeping:
|
`UDPSocket` which does most of the work.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -230,7 +230,6 @@ fn main() {
|
|||||||
// Bind a UDP socket to an arbitrary port
|
// Bind a UDP socket to an arbitrary port
|
||||||
let socket = UdpSocket::bind(("0.0.0.0", 43210)).unwrap();
|
let socket = UdpSocket::bind(("0.0.0.0", 43210)).unwrap();
|
||||||
|
|
||||||
|
|
||||||
// Build our query packet. It's important that we remember to set the
|
// Build our query packet. It's important that we remember to set the
|
||||||
// `recursion_desired` flag. As noted earlier, the packet id is arbitrary.
|
// `recursion_desired` flag. As noted earlier, the packet id is arbitrary.
|
||||||
let mut packet = DnsPacket::new();
|
let mut packet = DnsPacket::new();
|
||||||
@ -302,5 +301,3 @@ A {
|
|||||||
ttl: 79
|
ttl: 79
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
We're approaching something useful!
|
|
||||||
|
38
chapter3.md
38
chapter3.md
@ -165,13 +165,11 @@ pub fn read(buffer: &mut BytePacketBuffer) -> Result<DnsRecord> {
|
|||||||
let _ = try!(buffer.read_u16());
|
let _ = try!(buffer.read_u16());
|
||||||
let ttl = try!(buffer.read_u32());
|
let ttl = try!(buffer.read_u32());
|
||||||
let data_len = try!(buffer.read_u16());
|
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 {
|
match qtype {
|
||||||
|
|
||||||
|
// Handle each record type separately, starting with the A record
|
||||||
|
// type which remains the same as before.
|
||||||
QueryType::A => {
|
QueryType::A => {
|
||||||
let raw_addr = try!(buffer.read_u32());
|
let raw_addr = try!(buffer.read_u32());
|
||||||
let addr = Ipv4Addr::new(((raw_addr >> 24) & 0xFF) as u8,
|
let addr = Ipv4Addr::new(((raw_addr >> 24) & 0xFF) as u8,
|
||||||
@ -185,12 +183,9 @@ type which remains the same as before.
|
|||||||
ttl: ttl
|
ttl: ttl
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
```
|
|
||||||
|
|
||||||
The AAAA record type follows the same logic, but with more numbers to keep
|
// The AAAA record type follows the same logic, but with more numbers to keep
|
||||||
track off.
|
// track off.
|
||||||
|
|
||||||
```rust
|
|
||||||
QueryType::AAAA => {
|
QueryType::AAAA => {
|
||||||
let raw_addr1 = try!(buffer.read_u32());
|
let raw_addr1 = try!(buffer.read_u32());
|
||||||
let raw_addr2 = try!(buffer.read_u32());
|
let raw_addr2 = try!(buffer.read_u32());
|
||||||
@ -211,11 +206,8 @@ track off.
|
|||||||
ttl: ttl
|
ttl: ttl
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
```
|
|
||||||
|
|
||||||
NS and CNAME both have the same structure.
|
// NS and CNAME both have the same structure.
|
||||||
|
|
||||||
```rust
|
|
||||||
QueryType::NS => {
|
QueryType::NS => {
|
||||||
let mut ns = String::new();
|
let mut ns = String::new();
|
||||||
try!(buffer.read_qname(&mut ns));
|
try!(buffer.read_qname(&mut ns));
|
||||||
@ -226,6 +218,7 @@ NS and CNAME both have the same structure.
|
|||||||
ttl: ttl
|
ttl: ttl
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
QueryType::CNAME => {
|
QueryType::CNAME => {
|
||||||
let mut cname = String::new();
|
let mut cname = String::new();
|
||||||
try!(buffer.read_qname(&mut cname));
|
try!(buffer.read_qname(&mut cname));
|
||||||
@ -236,11 +229,8 @@ NS and CNAME both have the same structure.
|
|||||||
ttl: ttl
|
ttl: ttl
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
```
|
|
||||||
|
|
||||||
MX is close to the previous two, but with one extra field for priority.
|
// MX is almost like the previous two, but with one extra field for priority.
|
||||||
|
|
||||||
```rust
|
|
||||||
QueryType::MX => {
|
QueryType::MX => {
|
||||||
let priority = try!(buffer.read_u16());
|
let priority = try!(buffer.read_u16());
|
||||||
let mut mx = String::new();
|
let mut mx = String::new();
|
||||||
@ -253,11 +243,8 @@ MX is close to the previous two, but with one extra field for priority.
|
|||||||
ttl: ttl
|
ttl: ttl
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
```
|
|
||||||
|
|
||||||
And we end with some code for handling unknown record types, as before.
|
// And we end with some code for handling unknown record types, as before.
|
||||||
|
|
||||||
```rust
|
|
||||||
QueryType::UNKNOWN(_) => {
|
QueryType::UNKNOWN(_) => {
|
||||||
try!(buffer.step(data_len as usize));
|
try!(buffer.step(data_len as usize));
|
||||||
|
|
||||||
@ -272,8 +259,9 @@ And we end with some code for handling unknown record types, as before.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
It's a bit of a mouthful, but individually not much more complex than what we
|
It's a bit of a mouthful, but there are no especially complicated records in
|
||||||
had.
|
their own right -- it's seeing them all together that makes it look a bit
|
||||||
|
unwieldy.
|
||||||
|
|
||||||
### Extending BytePacketBuffer for setting values in place
|
### Extending BytePacketBuffer for setting values in place
|
||||||
|
|
||||||
@ -468,5 +456,3 @@ MX {
|
|||||||
ttl: 1794
|
ttl: 1794
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Encouraging!
|
|
||||||
|
90
chapter4.md
90
chapter4.md
@ -2,7 +2,7 @@
|
|||||||
===========================
|
===========================
|
||||||
|
|
||||||
Haven gotten this far, we're ready to make our first attempt at writing an
|
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:
|
actual server. Real DNS servers come in two different varieties:
|
||||||
|
|
||||||
* Authoritative Server - A DNS server hosting one or more "zones". For
|
* Authoritative Server - A DNS server hosting one or more "zones". For
|
||||||
instance, the authoritative servers for the zone google.com are
|
instance, the authoritative servers for the zone google.com are
|
||||||
@ -15,7 +15,7 @@ actual server. In reality, DNS servers fullfil two different purposes:
|
|||||||
8.8.8.8 and 8.8.4.4.
|
8.8.8.8 and 8.8.4.4.
|
||||||
|
|
||||||
Strictly speaking, there's nothing to stop a server from doing both things, but
|
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
|
in practice these two roles are typically mutually exclusive. This also explains
|
||||||
the significance of the flags `RD` (Recursion Desired) and `RA` (Recursion
|
the significance of the flags `RD` (Recursion Desired) and `RA` (Recursion
|
||||||
Available) in the packet header -- a stub resolver querying a caching server
|
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
|
will set the `RD` flag, and since the server allows such queries it will
|
||||||
@ -77,7 +77,7 @@ servers hosting the *google.com* zone:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Notice how the status of the response says `REFUSED`! `dig` also warns us that
|
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
|
while the `RD` flag was set in the query, the server didn't set the `RA` flag in the
|
||||||
response. We can still use the same server for *google.com*, however:
|
response. We can still use the same server for *google.com*, however:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@ -177,19 +177,13 @@ fn main() {
|
|||||||
|
|
||||||
// Bind an UDP socket on port 2053
|
// Bind an UDP socket on port 2053
|
||||||
let socket = UdpSocket::bind(("0.0.0.0", 2053)).unwrap();
|
let socket = UdpSocket::bind(("0.0.0.0", 2053)).unwrap();
|
||||||
```
|
|
||||||
|
|
||||||
For now, queries are handled sequentially, so an infinite loop for servicing
|
// For now, queries are handled sequentially, so an infinite loop for servicing
|
||||||
requests is initiated.
|
// requests is initiated.
|
||||||
|
|
||||||
```rust
|
|
||||||
loop {
|
loop {
|
||||||
```
|
|
||||||
|
|
||||||
With a socket ready, we can go ahead and read a packet. This will block until
|
// With a socket ready, we can go ahead and read a packet. This will
|
||||||
one is received.
|
// block until one is received.
|
||||||
|
|
||||||
```rust
|
|
||||||
let mut req_buffer = BytePacketBuffer::new();
|
let mut req_buffer = BytePacketBuffer::new();
|
||||||
let (_, src) = match socket.recv_from(&mut req_buffer.buf) {
|
let (_, src) = match socket.recv_from(&mut req_buffer.buf) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
@ -198,19 +192,17 @@ one is received.
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
```
|
|
||||||
|
|
||||||
Here we use match to safely unwrap the `Result`. If everything's as expected,
|
// 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
|
// 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
|
// 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
|
// 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
|
// 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.
|
// track of the source in order to send our reply later on.
|
||||||
|
|
||||||
Next, `DnsPacket::from_buffer` is used to parse the raw bytes into
|
// 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.
|
// a `DnsPacket`. It uses the same error handling idiom as the previous statement.
|
||||||
|
|
||||||
```rust
|
|
||||||
let request = match DnsPacket::from_buffer(&mut req_buffer) {
|
let request = match DnsPacket::from_buffer(&mut req_buffer) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -218,43 +210,31 @@ a `DnsPacket`. It uses the same error handling idiom as the previous statement.
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
```
|
|
||||||
|
|
||||||
At this stage, the response packet is created and initiated.
|
// Create and initialize the response packet
|
||||||
|
|
||||||
```rust
|
|
||||||
let mut packet = DnsPacket::new();
|
let mut packet = DnsPacket::new();
|
||||||
packet.header.id = request.header.id;
|
packet.header.id = request.header.id;
|
||||||
packet.header.recursion_desired = true;
|
packet.header.recursion_desired = true;
|
||||||
packet.header.recursion_available = true;
|
packet.header.recursion_available = true;
|
||||||
packet.header.response = true;
|
packet.header.response = true;
|
||||||
```
|
|
||||||
|
|
||||||
Being mindful of how unreliable input data from arbitrary senders can be, we
|
// 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`
|
// need make sure that a question is actually present. If not, we return `FORMERR`
|
||||||
to indicate that the sender made something wrong.
|
// to indicate that the sender made something wrong.
|
||||||
|
|
||||||
```rust
|
|
||||||
if request.questions.is_empty() {
|
if request.questions.is_empty() {
|
||||||
packet.header.rescode = ResultCode::FORMERR;
|
packet.header.rescode = ResultCode::FORMERR;
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
Usually a question will be present, though.
|
// Usually a question will be present, though.
|
||||||
|
|
||||||
```rust
|
|
||||||
else {
|
else {
|
||||||
let question = &request.questions[0];
|
let question = &request.questions[0];
|
||||||
println!("Received query: {:?}", question);
|
println!("Received query: {:?}", question);
|
||||||
```
|
|
||||||
|
|
||||||
Since all is set up and as expected, the query can be forwarded to the target
|
// 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
|
// 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
|
// 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
|
// rather everything goes as planned, the question and response records as copied
|
||||||
into our response packet.
|
// into our response packet.
|
||||||
|
|
||||||
```rust
|
|
||||||
if let Ok(result) = lookup(&question.name, question.qtype, server) {
|
if let Ok(result) = lookup(&question.name, question.qtype, server) {
|
||||||
packet.questions.push(question.clone());
|
packet.questions.push(question.clone());
|
||||||
packet.header.rescode = result.header.rescode;
|
packet.header.rescode = result.header.rescode;
|
||||||
@ -274,11 +254,9 @@ into our response packet.
|
|||||||
} else {
|
} else {
|
||||||
packet.header.rescode = ResultCode::SERVFAIL;
|
packet.header.rescode = ResultCode::SERVFAIL;
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
The only thing remaining is to encode our response and send it off!
|
// The only thing remaining is to encode our response and send it off!
|
||||||
|
|
||||||
```rust
|
|
||||||
let mut res_buffer = BytePacketBuffer::new();
|
let mut res_buffer = BytePacketBuffer::new();
|
||||||
match packet.write(&mut res_buffer) {
|
match packet.write(&mut res_buffer) {
|
||||||
Ok(_) => {},
|
Ok(_) => {},
|
||||||
@ -304,18 +282,16 @@ The only thing remaining is to encode our response and send it off!
|
|||||||
continue;
|
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 request loop
|
||||||
} // End of main
|
} // End of main
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The match idiom for error handling is used again and 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`.
|
||||||
|
|
||||||
All done! Let's try it! We start our server in one terminal, and use `dig` to
|
All done! Let's try it! We start our server in one terminal, and use `dig` to
|
||||||
perform a lookup in a second terminal.
|
perform a lookup in a second terminal.
|
||||||
|
|
||||||
@ -348,5 +324,5 @@ Received query: DnsQuestion { name: "google.com", qtype: A }
|
|||||||
Answer: A { domain: "google.com", addr: 216.58.211.142, ttl: 96 }
|
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
|
Success! In less than 800 lines of code, we've built a DNS server able to respond to
|
||||||
queries with several different record types!
|
queries with several different record types!
|
||||||
|
128
chapter5.md
128
chapter5.md
@ -167,14 +167,10 @@ Before we can get on, we'll need a few utility functions on `DnsPacket`.
|
|||||||
impl DnsPacket {
|
impl DnsPacket {
|
||||||
|
|
||||||
- snip -
|
- snip -
|
||||||
```
|
|
||||||
|
|
||||||
First, it's useful to be able to pick a random A record from a packet. Since we
|
// It's useful to be able to pick a random A record from a packet. When we
|
||||||
don't want to introduce an external dependency, and there's no method for
|
// get multiple IP's for a single name, it doesn't matter which one we
|
||||||
generating random numbers in the rust standard library, we'll just pick the
|
// choose, so in those cases we can now pick one at random.
|
||||||
first entry for now.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
pub fn get_random_a(&self) -> Option<String> {
|
pub fn get_random_a(&self) -> Option<String> {
|
||||||
if !self.answers.is_empty() {
|
if !self.answers.is_empty() {
|
||||||
let idx = random::<usize>() % self.answers.len();
|
let idx = random::<usize>() % self.answers.len();
|
||||||
@ -186,31 +182,22 @@ first entry for now.
|
|||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
Second, we'll use the fact that name servers often bundle the corresponding
|
// 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
|
// A records when replying to an NS query to implement a function that returns
|
||||||
the actual IP for an NS record if possible.
|
// the actual IP for an NS record if possible.
|
||||||
|
|
||||||
```rust
|
|
||||||
pub fn get_resolved_ns(&self, qname: &str) -> Option<String> {
|
pub fn get_resolved_ns(&self, qname: &str) -> Option<String> {
|
||||||
```
|
|
||||||
|
|
||||||
First, we scan the list of NS records in the authorities section:
|
// First, we scan the list of NS records in the authorities section:
|
||||||
|
|
||||||
```rust
|
|
||||||
let mut new_authorities = Vec::new();
|
let mut new_authorities = Vec::new();
|
||||||
for auth in &self.authorities {
|
for auth in &self.authorities {
|
||||||
if let DnsRecord::NS { ref domain, ref host, .. } = *auth {
|
if let DnsRecord::NS { ref domain, ref host, .. } = *auth {
|
||||||
if !qname.ends_with(domain) {
|
if !qname.ends_with(domain) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
Once we've found an NS record, we scan the resources record for a matching
|
// Once we've found an NS record, we scan the resources record for a matching
|
||||||
A record...
|
// A record...
|
||||||
|
|
||||||
```rust
|
|
||||||
for rsrc in &self.resources {
|
for rsrc in &self.resources {
|
||||||
if let DnsRecord::A{ ref domain, ref addr, ttl } = *rsrc {
|
if let DnsRecord::A{ ref domain, ref addr, ttl } = *rsrc {
|
||||||
if domain != host {
|
if domain != host {
|
||||||
@ -222,22 +209,15 @@ A record...
|
|||||||
addr: *addr,
|
addr: *addr,
|
||||||
ttl: ttl
|
ttl: ttl
|
||||||
};
|
};
|
||||||
```
|
|
||||||
|
|
||||||
...and push any matches to a list.
|
// ...and push any matches to a list.
|
||||||
|
|
||||||
```rust
|
|
||||||
new_authorities.push(rec);
|
new_authorities.push(rec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
If there are any matches, we pick the first one. Again, we'll want to introduce
|
// If there are any matches, we pick the first one.
|
||||||
randomization later on.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
if !new_authorities.is_empty() {
|
if !new_authorities.is_empty() {
|
||||||
if let DnsRecord::A { addr, .. } = new_authorities[0] {
|
if let DnsRecord::A { addr, .. } = new_authorities[0] {
|
||||||
return Some(addr.to_string());
|
return Some(addr.to_string());
|
||||||
@ -246,14 +226,11 @@ randomization later on.
|
|||||||
|
|
||||||
None
|
None
|
||||||
} // End of get_resolved_ns
|
} // End of get_resolved_ns
|
||||||
```
|
|
||||||
|
|
||||||
However, not all name servers are as well behaved. In certain cases there won't
|
// However, not all name servers are as that nice. In certain cases there won't
|
||||||
be any A records in the additional section, and we'll have to perform *another*
|
// 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
|
// lookup in the midst. For this, we introduce a method for returning the host
|
||||||
name of an appropriate name server.
|
// name of an appropriate name server.
|
||||||
|
|
||||||
```rust
|
|
||||||
pub fn get_unresolved_ns(&self, qname: &str) -> Option<String> {
|
pub fn get_unresolved_ns(&self, qname: &str) -> Option<String> {
|
||||||
|
|
||||||
let mut new_authorities = Vec::new();
|
let mut new_authorities = Vec::new();
|
||||||
@ -284,83 +261,56 @@ We move swiftly on to our new `recursive_lookup` function:
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn recursive_lookup(qname: &str, qtype: QueryType) -> Result<DnsPacket> {
|
fn recursive_lookup(qname: &str, qtype: QueryType) -> Result<DnsPacket> {
|
||||||
```
|
|
||||||
|
|
||||||
For now we're always starting with *a.root-servers.net*.
|
// For now we're always starting with *a.root-servers.net*.
|
||||||
|
|
||||||
```rust
|
|
||||||
let mut ns = "198.41.0.4".to_string();
|
let mut ns = "198.41.0.4".to_string();
|
||||||
```
|
|
||||||
|
|
||||||
Since it might take an arbitrary number of steps, we enter an unbounded loop.
|
// Since it might take an arbitrary number of steps, we enter an unbounded loop.
|
||||||
|
|
||||||
```rust
|
|
||||||
loop {
|
loop {
|
||||||
println!("attempting lookup of {:?} {} with ns {}", qtype, qname, ns);
|
println!("attempting lookup of {:?} {} with ns {}", qtype, qname, ns);
|
||||||
```
|
|
||||||
|
|
||||||
The next step is to send the query to the active server.
|
// The next step is to send the query to the active server.
|
||||||
|
|
||||||
```rust
|
|
||||||
let ns_copy = ns.clone();
|
let ns_copy = ns.clone();
|
||||||
|
|
||||||
let server = (ns_copy.as_str(), 53);
|
let server = (ns_copy.as_str(), 53);
|
||||||
let response = try!(lookup(qname, qtype.clone(), server));
|
let response = try!(lookup(qname, qtype.clone(), server));
|
||||||
```
|
|
||||||
|
|
||||||
If there are entries in the answer section, and no errors, we are done!
|
// If there are entries in the answer section, and no errors, we are done!
|
||||||
|
|
||||||
```rust
|
|
||||||
if !response.answers.is_empty() &&
|
if !response.answers.is_empty() &&
|
||||||
response.header.rescode == ResultCode::NOERROR {
|
response.header.rescode == ResultCode::NOERROR {
|
||||||
|
|
||||||
return Ok(response.clone());
|
return Ok(response.clone());
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
We might also get a `NXDOMAIN` reply, which is the authoritative name servers
|
// We might also get a `NXDOMAIN` reply, which is the authoritative name servers
|
||||||
way of telling us that the name doesn't exist.
|
// way of telling us that the name doesn't exist.
|
||||||
|
|
||||||
```rust
|
|
||||||
if response.header.rescode == ResultCode::NXDOMAIN {
|
if response.header.rescode == ResultCode::NXDOMAIN {
|
||||||
return Ok(response.clone());
|
return Ok(response.clone());
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
Otherwise, we'll try to find a new nameserver based on NS and a corresponding A
|
// 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
|
// record in the additional section. If this succeeds, we can switch name server
|
||||||
and retry the loop.
|
// and retry the loop.
|
||||||
|
|
||||||
```rust
|
|
||||||
if let Some(new_ns) = response.get_resolved_ns(qname) {
|
if let Some(new_ns) = response.get_resolved_ns(qname) {
|
||||||
ns = new_ns.clone();
|
ns = new_ns.clone();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
If not, we'll have to resolve the ip of a NS record. If no NS records exist,
|
// 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.
|
// we'll go with what the last server told us.
|
||||||
|
|
||||||
```rust
|
|
||||||
let new_ns_name = match response.get_unresolved_ns(qname) {
|
let new_ns_name = match response.get_unresolved_ns(qname) {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => return Ok(response.clone())
|
None => return Ok(response.clone())
|
||||||
};
|
};
|
||||||
```
|
|
||||||
|
|
||||||
Here we go down the rabbit hole by starting _another_ lookup sequence in the
|
// 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
|
// midst of our current one. Hopefully, this will give us the IP of an appropriate
|
||||||
name server.
|
// name server.
|
||||||
|
|
||||||
```rust
|
|
||||||
let recursive_response = try!(recursive_lookup(&new_ns_name, QueryType::A));
|
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
|
// 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.
|
// record is available, we again return the last result we got.
|
||||||
|
|
||||||
```rust
|
|
||||||
if let Some(new_ns) = recursive_response.get_random_a() {
|
if let Some(new_ns) = recursive_response.get_random_a() {
|
||||||
ns = new_ns.clone();
|
ns = new_ns.clone();
|
||||||
} else {
|
} else {
|
||||||
@ -424,4 +374,18 @@ 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 }
|
Answer: A { domain: "www.google.com", addr: 216.58.211.132, ttl: 300 }
|
||||||
```
|
```
|
||||||
|
|
||||||
This mirrors our manual process earlier. We're really getting somewhere!
|
This mirrors our manual process earlier. We can now successfully resolve
|
||||||
|
a domain starting from the list of root servers. We've now got a fully
|
||||||
|
functional, albeit suboptimal, DNS server.
|
||||||
|
|
||||||
|
There are many things that we could do better. For instance, there is no true
|
||||||
|
concurrency in this server. We can neither send nor receive queries over TCP.
|
||||||
|
We cannot use it to host our own zones, and allow it to act as an authorative
|
||||||
|
server. The lack of support for DNSSEC leaves us open to DNS poisoning attacks
|
||||||
|
where a malicious server can return records relating to somebody else's domain.
|
||||||
|
|
||||||
|
Many of these problems have been fixed in my own project
|
||||||
|
[hermes](https://github.com/EmilHernvall/hermes), so you can head over there to
|
||||||
|
investigate how I did it, or continue on your own from here. Or maybe you've
|
||||||
|
had enough of DNS for now... :) Regardless, I hope you've gained some new insight
|
||||||
|
into how DNS works.
|
||||||
|
Reference in New Issue
Block a user