2 - Building a stub resolver ============================ While it's slightly satisfying to know that we're able to successfully parse DNS packets, it's not much use to just read them off disk. As our next step, we'll use it to build a `stub resolver`, which is a DNS client that doesn't feature any built-in support for recursive lookup and that will only work with a DNS server that does. Later we'll implement an actual recursive resolver to lose the need for a server. ### Extending BytePacketBuffer for writing In order to be able to service a query, we need to be able to not just read packets, but also write them. To do so, we'll need to extend `BytePacketBuffer` with some additional methods: ```rust impl BytePacketBuffer { - snip - fn write(&mut self, val: u8) -> Result<()> { if self.pos >= 512 { return Err("End of buffer".into()); } self.buf[self.pos] = val; self.pos += 1; Ok(()) } fn write_u8(&mut self, val: u8) -> Result<()> { self.write(val)?; Ok(()) } fn write_u16(&mut self, val: u16) -> Result<()> { self.write((val >> 8) as u8)?; self.write((val & 0xFF) as u8)?; Ok(()) } fn write_u32(&mut self, val: u32) -> Result<()> { self.write(((val >> 24) & 0xFF) as u8)?; self.write(((val >> 16) & 0xFF) as u8)?; self.write(((val >> 8) & 0xFF) as u8)?; self.write(((val >> 0) & 0xFF) as u8)?; Ok(()) } ``` We'll also need a function for writing query names in labeled form: ```rust fn write_qname(&mut self, qname: &str) -> Result<()> { for label in qname.split('.') { let len = label.len(); if len > 0x3f { return Err("Single label exceeds 63 characters of length".into()); } self.write_u8(len as u8)?; for b in label.as_bytes() { self.write_u8(*b)?; } } self.write_u8(0)?; Ok(()) } } // End of BytePacketBuffer ``` ### Extending DnsHeader for writing Building on our new functions we can extend our protocol representation structs. Starting with `DnsHeader`: ```rust impl DnsHeader { - snip - pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> { buffer.write_u16(self.id)?; 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, )?; buffer.write_u8( (self.rescode 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), )?; buffer.write_u16(self.questions)?; buffer.write_u16(self.answers)?; buffer.write_u16(self.authoritative_entries)?; buffer.write_u16(self.resource_entries)?; Ok(()) } } ``` ### Extending DnsQuestion for writing Moving on to `DnsQuestion`: ```rust impl DnsQuestion { - snip - pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<()> { buffer.write_qname(&self.name)?; let typenum = self.qtype.to_num(); buffer.write_u16(typenum)?; buffer.write_u16(1)?; Ok(()) } } ``` ### Extending DnsRecord for writing `DnsRecord` is for now quite compact as well, although we'll eventually add quite a bit of code here to handle different record types: ```rust impl DnsRecord { - snip - pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result { let start_pos = buffer.pos(); match *self { DnsRecord::A { ref domain, ref addr, ttl, } => { buffer.write_qname(domain)?; buffer.write_u16(QueryType::A.to_num())?; buffer.write_u16(1)?; buffer.write_u32(ttl)?; buffer.write_u16(4)?; let octets = addr.octets(); buffer.write_u8(octets[0])?; buffer.write_u8(octets[1])?; buffer.write_u8(octets[2])?; buffer.write_u8(octets[3])?; } DnsRecord::UNKNOWN { .. } => { println!("Skipping record: {:?}", self); } } Ok(buffer.pos() - start_pos) } } ``` ### Extending DnsPacket for writing Putting it all together in `DnsPacket`: ```rust impl DnsPacket { - snip - 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; self.header.write(buffer)?; for question in &self.questions { question.write(buffer)?; } for rec in &self.answers { rec.write(buffer)?; } for rec in &self.authorities { rec.write(buffer)?; } for rec in &self.resources { rec.write(buffer)?; } Ok(()) } } ``` ### Implementing a stub resolver We're ready to implement our stub resolver. Rust includes a convenient `UDPSocket` which does most of the work. ```rust fn main() -> Result<()> { // Perform an A query for google.com let qname = "google.com"; let qtype = QueryType::A; // Using googles public DNS server let server = ("8.8.8.8", 53); // Bind a UDP socket to an arbitrary port let socket = UdpSocket::bind(("0.0.0.0", 43210))?; // Build our query packet. It's important that we remember to set the // `recursion_desired` flag. As noted earlier, the packet id is arbitrary. 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)); // Use our new write method to write the packet to a buffer... let mut req_buffer = BytePacketBuffer::new(); packet.write(&mut req_buffer)?; // ...and send it off to the server using our socket: socket.send_to(&req_buffer.buf[0..req_buffer.pos], server)?; // To prepare for receiving the response, we'll create a new `BytePacketBuffer`, // and ask the socket to write the response directly into our buffer. let mut res_buffer = BytePacketBuffer::new(); socket.recv_from(&mut res_buffer.buf)?; // As per the previous section, `DnsPacket::from_buffer()` is then used to // actually parse the packet after which we can print the response. let res_packet = DnsPacket::from_buffer(&mut res_buffer)?; 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); } Ok(()) } ``` Running it will print: ```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: 1, authoritative_entries: 0, resource_entries: 0 } DnsQuestion { name: "google.com", qtype: A } A { domain: "google.com", addr: 216.58.209.110, ttl: 79 } ``` The next chapter covers implementing a richer set of record types: [Chapter 3 - Adding more Record Types](/chapter3.md)