├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── install.sh └── src ├── cli.rs ├── dns_types.rs ├── io.rs ├── main.rs ├── message.rs ├── message ├── header.rs ├── parser_utils.rs ├── question.rs └── record.rs └── parse.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.53" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" 10 | 11 | [[package]] 12 | name = "ascii" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" 16 | 17 | [[package]] 18 | name = "bitvec" 19 | version = "1.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" 22 | dependencies = [ 23 | "funty", 24 | "radium", 25 | "tap", 26 | "wyz", 27 | ] 28 | 29 | [[package]] 30 | name = "cfg-if" 31 | version = "1.0.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 34 | 35 | [[package]] 36 | name = "dingo" 37 | version = "0.1.0" 38 | dependencies = [ 39 | "anyhow", 40 | "ascii", 41 | "bitvec", 42 | "nom", 43 | "pico-args", 44 | "rand", 45 | ] 46 | 47 | [[package]] 48 | name = "funty" 49 | version = "2.0.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" 52 | 53 | [[package]] 54 | name = "getrandom" 55 | version = "0.2.4" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" 58 | dependencies = [ 59 | "cfg-if", 60 | "libc", 61 | "wasi", 62 | ] 63 | 64 | [[package]] 65 | name = "libc" 66 | version = "0.2.117" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" 69 | 70 | [[package]] 71 | name = "memchr" 72 | version = "2.4.1" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 75 | 76 | [[package]] 77 | name = "minimal-lexical" 78 | version = "0.2.1" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 81 | 82 | [[package]] 83 | name = "nom" 84 | version = "7.1.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" 87 | dependencies = [ 88 | "memchr", 89 | "minimal-lexical", 90 | "version_check", 91 | ] 92 | 93 | [[package]] 94 | name = "pico-args" 95 | version = "0.4.2" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" 98 | 99 | [[package]] 100 | name = "ppv-lite86" 101 | version = "0.2.16" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 104 | 105 | [[package]] 106 | name = "radium" 107 | version = "0.7.0" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" 110 | 111 | [[package]] 112 | name = "rand" 113 | version = "0.8.4" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 116 | dependencies = [ 117 | "libc", 118 | "rand_chacha", 119 | "rand_core", 120 | "rand_hc", 121 | ] 122 | 123 | [[package]] 124 | name = "rand_chacha" 125 | version = "0.3.1" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 128 | dependencies = [ 129 | "ppv-lite86", 130 | "rand_core", 131 | ] 132 | 133 | [[package]] 134 | name = "rand_core" 135 | version = "0.6.3" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 138 | dependencies = [ 139 | "getrandom", 140 | ] 141 | 142 | [[package]] 143 | name = "rand_hc" 144 | version = "0.3.1" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 147 | dependencies = [ 148 | "rand_core", 149 | ] 150 | 151 | [[package]] 152 | name = "tap" 153 | version = "1.0.1" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 156 | 157 | [[package]] 158 | name = "version_check" 159 | version = "0.9.4" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 162 | 163 | [[package]] 164 | name = "wasi" 165 | version = "0.10.2+wasi-snapshot-preview1" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 168 | 169 | [[package]] 170 | name = "wyz" 171 | version = "0.5.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" 174 | dependencies = [ 175 | "tap", 176 | ] 177 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dingo" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Adam Chalmers"] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | anyhow = "1.0.53" 11 | ascii = "1.0.0" 12 | bitvec = "1.0.0" 13 | nom = "7.1.0" 14 | pico-args = "0.4.2" 15 | rand = "0.8.4" 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dingo 2 | 3 | Domain INformation Gatherer, Obviously. 4 | 5 | ## Installation 6 | 7 | 1. Install cargo, see [instructions on the Rust website](https://doc.rust-lang.org/cargo/getting-started/installation.html) 8 | 2. Run ./install.sh (it just does cargo build and copies the program to `/usr/local/bin/dingo`) 9 | 10 | ## Examples 11 | ```sh 12 | $ dingo --record-type A seriouseats.com 13 | 14 | # Output 15 | Questions: 16 | A: seriouseats.com. 17 | Answers: 18 | 151.101.2.137 (TTL 142) 19 | 151.101.194.137 (TTL 142) 20 | 151.101.130.137 (TTL 142) 21 | 151.101.66.137 (TTL 142) 22 | ``` 23 | 24 | ## Usage 25 | ``` 26 | dingo [OPTIONS] --record-type TYPE NAME 27 | 28 | FLAGS: 29 | -h, --help Prints help information 30 | OPTIONS: 31 | -t, --record-type TYPE Choose the DNS record type (does not support anywhere near all the record types) 32 | -r, --resolver IP Which DNS resolver to query (default is 1.1.1.1:53) 33 | ARGS: 34 | NAME A domain name to look up. Remember, these must be ASCII. 35 | ``` 36 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | cargo build -q --release && cp target/release/dingo /usr/local/bin/dingo -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | net::{Ipv4Addr, SocketAddr, SocketAddrV4}, 3 | process::exit, 4 | }; 5 | 6 | use ascii::AsciiString; 7 | 8 | use crate::dns_types::RecordType; 9 | 10 | const HELP: &str = "\ 11 | dingo -- domain information gatherer, obviously 12 | USAGE: 13 | dingo [OPTIONS] --record-type TYPE NAME 14 | FLAGS: 15 | -h, --help Prints help information 16 | OPTIONS: 17 | -t, --record-type TYPE Choose the DNS record type (currently only supports A, CNAME, SOA and AAAA) 18 | -r, --resolver IP Which DNS resolver to query (default is 1.1.1.1:53) 19 | ARGS: 20 | NAME A domain name to look up. Remember, these must be ASCII. 21 | "; 22 | 23 | /// Values derived from the CLI arguments. 24 | #[derive(Debug)] 25 | pub struct AppArgs { 26 | pub record_type: RecordType, 27 | pub name: String, 28 | pub resolver: SocketAddr, 29 | } 30 | 31 | impl AppArgs { 32 | pub fn parse() -> Result { 33 | let mut pargs = pico_args::Arguments::from_env(); 34 | 35 | // Help has a higher priority and should be handled separately. 36 | if pargs.contains(["-h", "--help"]) { 37 | print!("{}", HELP); 38 | std::process::exit(0); 39 | } 40 | 41 | let record_type = match pargs 42 | .opt_value_from_str("--record-type")? 43 | .xor(pargs.opt_value_from_str("-t")?) 44 | { 45 | Some(rt) => rt, 46 | None => { 47 | eprintln!("You must supply exactly one of either -t or --record-type"); 48 | print!("{}", HELP); 49 | std::process::exit(1); 50 | } 51 | }; 52 | 53 | // I asked some coworkers and they suggested this DNS resolver 54 | let default_resolver = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(1, 1, 1, 1), 53)); 55 | let resolver = pargs 56 | .opt_value_from_str("--resolver")? 57 | .or(pargs.opt_value_from_str("-r")?) 58 | .unwrap_or(default_resolver); 59 | 60 | let mut name: String = pargs.free_from_str()?; 61 | use std::str::FromStr; 62 | if AsciiString::from_str(&name).is_err() { 63 | eprintln!("DNS names must be ASCII, and {name} is not."); 64 | exit(1); 65 | } 66 | if !name.ends_with('.') { 67 | name.push('.'); 68 | } 69 | 70 | let args = AppArgs { 71 | record_type, 72 | name, 73 | resolver, 74 | }; 75 | 76 | let remaining = pargs.finish(); 77 | if !remaining.is_empty() { 78 | eprintln!("Warning: unused arguments left: {:?}.", remaining); 79 | } 80 | 81 | Ok(args) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/dns_types.rs: -------------------------------------------------------------------------------- 1 | //! Common DNS types that get used in several different parts of the codebase. 2 | use bitvec::prelude::*; 3 | use std::{fmt, str::FromStr}; 4 | 5 | #[derive(Debug)] 6 | pub enum RecordType { 7 | A, 8 | Aaaa, 9 | Cname, 10 | Soa, 11 | Ns, 12 | // TODO: Add more record types 13 | } 14 | 15 | impl FromStr for RecordType { 16 | type Err = String; 17 | 18 | fn from_str(s: &str) -> Result { 19 | let rt = match s.to_uppercase().as_str() { 20 | "A" => Self::A, 21 | "AAAA" => Self::Aaaa, 22 | "CNAME" => Self::Cname, 23 | "SOA" => Self::Soa, 24 | "NS" => Self::Ns, 25 | other => return Err(format!("{other} is not a valid DNS record type")), 26 | }; 27 | Ok(rt) 28 | } 29 | } 30 | 31 | impl fmt::Display for RecordType { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | let s = match self { 34 | Self::A => "A", 35 | Self::Aaaa => "AAAA", 36 | Self::Cname => "CNAME", 37 | Self::Soa => "SOA", 38 | Self::Ns => "NS", 39 | }; 40 | s.fmt(f) 41 | } 42 | } 43 | 44 | impl RecordType { 45 | pub fn serialize(&self, bv: &mut BitVec) { 46 | let type_num: u16 = match self { 47 | Self::A => 1, 48 | Self::Aaaa => 28, 49 | Self::Cname => 5, 50 | Self::Soa => 6, 51 | Self::Ns => 2, 52 | }; 53 | bv.extend_from_bitslice(type_num.view_bits::()) 54 | } 55 | } 56 | 57 | impl TryFrom for RecordType { 58 | type Error = anyhow::Error; 59 | 60 | fn try_from(value: u16) -> Result { 61 | let record_type = match value { 62 | 1 => Self::A, 63 | 28 => Self::Aaaa, 64 | 5 => Self::Cname, 65 | 6 => Self::Soa, 66 | 2 => Self::Ns, 67 | other => anyhow::bail!("Invalid record type number {other:b}"), 68 | }; 69 | Ok(record_type) 70 | } 71 | } 72 | 73 | #[derive(Debug)] 74 | #[cfg_attr(test, derive(Eq, PartialEq))] 75 | pub enum Class { 76 | IN, 77 | } 78 | 79 | impl fmt::Display for Class { 80 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 81 | let s = match self { 82 | Self::IN => "IN", 83 | }; 84 | s.fmt(f) 85 | } 86 | } 87 | 88 | impl Class { 89 | pub fn serialize(&self, bv: &mut BitVec) { 90 | let type_num: u16 = match self { 91 | Self::IN => 1, 92 | }; 93 | bv.extend_from_bitslice(type_num.view_bits::()) 94 | } 95 | } 96 | 97 | impl TryFrom for Class { 98 | type Error = anyhow::Error; 99 | 100 | fn try_from(value: u16) -> Result { 101 | let record_type = match value { 102 | 1 => Self::IN, 103 | other => anyhow::bail!("Invalid class number {other}"), 104 | }; 105 | Ok(record_type) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/io.rs: -------------------------------------------------------------------------------- 1 | //! Doing network IO and printing to the terminal. 2 | use crate::message::{header::ResponseCode, Message, MAX_UDP_BYTES}; 3 | use anyhow::{anyhow, Result as AResult}; 4 | use std::{ 5 | net::{SocketAddr, UdpSocket}, 6 | time::Duration, 7 | }; 8 | 9 | /// Sends the given DNS message to the given resolver. 10 | /// Returns the binary response. 11 | pub fn send_req(msg: Message, resolver: SocketAddr, verbose: bool) -> AResult<(Vec, usize)> { 12 | // Connect to the DNS resolver 13 | let local_addr = "0.0.0.0:0"; 14 | let socket = UdpSocket::bind(local_addr).expect("couldn't bind to a local address"); 15 | socket.set_read_timeout(Some(Duration::from_secs(5)))?; 16 | if verbose { 17 | println!("Bound to local {}", socket.local_addr()?); 18 | } 19 | socket 20 | .connect(resolver) 21 | .expect("couldn't connect to the DNS resolver"); 22 | if verbose { 23 | println!("Connected to remote {resolver}"); 24 | } 25 | 26 | // Send the DNS resolver the message 27 | let body = msg.serialize_bytes()?; 28 | if verbose { 29 | println!("Request size: {} bytes", body.len()); 30 | } 31 | let bytes_sent = socket.send(&body).expect("couldn't send data"); 32 | if bytes_sent != body.len() { 33 | panic!("Only {bytes_sent} bytes, message was probably truncated"); 34 | } 35 | 36 | // Get the resolver's response. 37 | // Note, you have to actually allocate space to write into. 38 | // I was originally using an empty vector, but reading into an empty vector always 39 | // instantly succeeds (by writing nothing), so I was discarding the response. 40 | // See 41 | let mut response_buf = vec![0; MAX_UDP_BYTES]; 42 | match socket.recv(&mut response_buf) { 43 | Ok(received) => Ok((response_buf, received)), 44 | Err(e) => Err(anyhow!("recv function failed: {:?}", e)), 45 | } 46 | } 47 | 48 | /// Parse the binary response into a DNS message, and print it nicely. 49 | pub fn print_resp(resp: Vec, len: usize, sent_query_id: u16, verbose: bool) -> AResult<()> { 50 | if verbose { 51 | println!("Response size: {len} bytes"); 52 | println!("{resp:?}"); 53 | } 54 | 55 | // Parse and validate the response. 56 | let input = resp[..len].to_vec(); 57 | let response_msg = match Message::deserialize(input) { 58 | Ok(msg) => msg, 59 | Err(e) => anyhow::bail!("Error parsing response: {e}"), 60 | }; 61 | let received_query_id = response_msg.header.id; 62 | if sent_query_id != received_query_id { 63 | eprintln!("Mismatch between query IDs. Client sent {sent_query_id} and received {received_query_id}") 64 | } 65 | match response_msg.header.resp_code { 66 | ResponseCode::NoError => {} 67 | err => anyhow::bail!("Error from resolver: {err}"), 68 | }; 69 | 70 | // Reprint the question, why not? 71 | println!("Questions:"); 72 | for question in response_msg.question.iter() { 73 | println!("{question}"); 74 | } 75 | 76 | // Print records sent by the resolver. 77 | if !response_msg.answer.is_empty() { 78 | println!("Answers:"); 79 | for record in response_msg.answer { 80 | println!("{}", record.as_dns_response()); 81 | } 82 | } 83 | if !response_msg.authority.is_empty() { 84 | println!("Authority records:"); 85 | for record in response_msg.authority { 86 | println!("{}", record.as_dns_response()); 87 | } 88 | } 89 | if !response_msg.additional.is_empty() { 90 | println!("Additional records:"); 91 | for record in response_msg.additional { 92 | println!("{}", record.as_dns_response()); 93 | } 94 | } 95 | Ok(()) 96 | } 97 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | cli::AppArgs, 3 | dns_types::{Class, RecordType}, 4 | message::Message, 5 | }; 6 | use rand::Rng; 7 | 8 | mod cli; 9 | mod dns_types; 10 | mod io; 11 | mod message; 12 | mod parse; 13 | 14 | const VERBOSE: bool = false; 15 | 16 | fn main() { 17 | let AppArgs { 18 | name, 19 | record_type, 20 | resolver, 21 | } = AppArgs::parse().unwrap(); 22 | let query_id = rand::thread_rng().gen(); 23 | let msg = Message::new_query(query_id, name, record_type).unwrap(); 24 | let (resp, len) = io::send_req(msg, resolver, VERBOSE).unwrap(); 25 | if let Err(e) = io::print_resp(resp, len, query_id, VERBOSE) { 26 | println!("Error: {e}"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/message.rs: -------------------------------------------------------------------------------- 1 | pub mod header; 2 | mod parser_utils; 3 | mod question; 4 | pub mod record; 5 | 6 | use crate::{ 7 | dns_types::Class, 8 | message::{question::Entry, record::Record}, 9 | parse::parse_label, 10 | RecordType, 11 | }; 12 | use anyhow::Result as AResult; 13 | use bitvec::prelude::*; 14 | use header::Header; 15 | use nom::{ 16 | combinator::{map, map_res, peek}, 17 | error::Error, 18 | multi::{count, length_value}, 19 | number::complete::{be_u16, be_u32, be_u8}, 20 | sequence::tuple, 21 | IResult, 22 | }; 23 | use std::{ 24 | io::Read, 25 | net::{Ipv4Addr, Ipv6Addr}, 26 | }; 27 | 28 | use self::record::{RecordData, SoaData}; 29 | 30 | /// Defined by the spec 31 | /// UDP messages 512 octets or less 32 | pub(crate) const MAX_UDP_BYTES: usize = 512; 33 | 34 | /// Defined by the spec 35 | /// labels 63 octets or less 36 | const MAX_LABEL_BYTES: usize = 63; 37 | 38 | /// Defined by the spec 39 | /// names 255 octets or less 40 | const MAX_NAME_BYTES: usize = 255; 41 | 42 | const MAX_RECURSION_DEPTH: u8 = 20; 43 | 44 | #[derive(Debug)] 45 | pub struct Message { 46 | /// The header section is always present. The header includes fields that 47 | /// specify which of the remaining sections are present, and also specify 48 | /// whether the message is a query or a response, a standard query or some 49 | /// other opcode, etc. 50 | pub header: Header, 51 | // The question section contains fields that describe a 52 | // question to a name server. These fields are a query type (QTYPE), a 53 | // query class (QCLASS), and a query domain name (QNAME). 54 | pub question: Vec, 55 | // The last three 56 | // sections have the same format: a possibly empty list of concatenated 57 | // resource records (RRs). 58 | /// The answer section contains RRs that answer the question 59 | pub answer: Vec, 60 | /// the authority section contains RRs that point toward an 61 | /// authoritative name server; 62 | pub authority: Vec, 63 | /// the additional records section contains RRs 64 | /// which relate to the query, but are not strictly answers for the 65 | /// question. 66 | pub additional: Vec, 67 | } 68 | 69 | impl Message { 70 | pub(crate) fn new_query( 71 | id: u16, 72 | domain_name: String, 73 | record_type: RecordType, 74 | ) -> AResult { 75 | let name_len = domain_name.len(); 76 | if name_len > MAX_NAME_BYTES { 77 | anyhow::bail!( 78 | "Domain name is {name_len} bytes, which is over the max of {MAX_NAME_BYTES}" 79 | ); 80 | } 81 | let labels: Vec<_> = domain_name.split('.').map(|a| a.to_owned()).collect(); 82 | if labels.iter().any(|label| label.len() > MAX_LABEL_BYTES) { 83 | anyhow::bail!( 84 | "One of the labels in your domain is over the max of {MAX_LABEL_BYTES} bytes" 85 | ); 86 | } 87 | let msg = Message { 88 | header: Header::new_query(id), 89 | question: vec![Entry::new(labels, record_type)], 90 | answer: Vec::new(), 91 | authority: Vec::new(), 92 | additional: Vec::new(), 93 | }; 94 | Ok(msg) 95 | } 96 | 97 | fn serialize_bits(&self, bv: &mut BitVec) -> AResult<()> { 98 | self.header.serialize(bv); 99 | for q in &self.question { 100 | q.serialize(bv)?; 101 | } 102 | Ok(()) 103 | } 104 | 105 | pub fn serialize_bytes(&self) -> AResult> { 106 | let mut bv = BitVec::::new(); 107 | self.serialize_bits(&mut bv)?; 108 | let mut msg_bytes = Vec::with_capacity(MAX_UDP_BYTES); 109 | bv.as_bitslice().read_to_end(&mut msg_bytes).unwrap(); 110 | Ok(msg_bytes) 111 | } 112 | 113 | pub fn deserialize(input: Vec) -> anyhow::Result { 114 | let mp = MsgParser { input }; 115 | let slice = &mp.input[..]; 116 | let msg: Message = mp.parse_message(slice).unwrap().1; 117 | Ok(msg) 118 | } 119 | } 120 | 121 | struct MsgParser { 122 | input: Vec, 123 | } 124 | 125 | impl MsgParser { 126 | /// Returns a parser that can parse DNS record data of the given record type. 127 | fn parse_rdata<'i>( 128 | &self, 129 | record_type: RecordType, 130 | ) -> impl FnMut(&'i [u8]) -> IResult<&'i [u8], RecordData> + '_ { 131 | move |i| { 132 | let recursion_depth = 0; 133 | let record = match record_type { 134 | RecordType::A => map(tuple((be_u8, be_u8, be_u8, be_u8)), |(a, b, c, d)| { 135 | RecordData::A(Ipv4Addr::new(a, b, c, d)) 136 | })(i)?, 137 | RecordType::Aaaa => map( 138 | tuple(( 139 | be_u16, be_u16, be_u16, be_u16, be_u16, be_u16, be_u16, be_u16, 140 | )), 141 | |(a, b, c, d, e, f, g, h)| { 142 | RecordData::Aaaa(Ipv6Addr::new(a, b, c, d, e, f, g, h)) 143 | }, 144 | )(i)?, 145 | RecordType::Cname => { 146 | map(|i| self.parse_name(i, recursion_depth), RecordData::Cname)(i)? 147 | } 148 | RecordType::Ns => map(|i| self.parse_name(i, recursion_depth), RecordData::Ns)(i)?, 149 | RecordType::Soa => { 150 | let (i, mname) = self.parse_name(i, recursion_depth)?; 151 | let (i, rname) = self.parse_name(i, recursion_depth)?; 152 | let (i, serial) = be_u32(i)?; 153 | let (i, refresh) = be_u32(i)?; 154 | let (i, retry) = be_u32(i)?; 155 | let (i, expire) = be_u32(i)?; 156 | let rd = SoaData { 157 | mname, 158 | rname, 159 | serial, 160 | refresh, 161 | retry, 162 | expire, 163 | }; 164 | (i, RecordData::Soa(rd)) 165 | } 166 | }; 167 | Ok(record) 168 | } 169 | } 170 | 171 | /// Parse a domain name. 172 | fn parse_name<'i>( 173 | &self, 174 | mut input: &'i [u8], 175 | recursion_depth: u8, 176 | ) -> IResult<&'i [u8], String> { 177 | let mut name = String::new(); 178 | loop { 179 | let (i, first_byte) = peek(be_u8)(input)?; 180 | input = i; 181 | const POINTER_HEADER: u8 = 0b11000000; 182 | if first_byte >= POINTER_HEADER { 183 | // The message is using Message Compression: 184 | // This label is a pointer, and it ends the sequence of labels. 185 | // The remaining 14 bits are the offset that the pointer points at. 186 | // So, first, examine the 14 bits to find the offset of the next label. 187 | let dereference_pointer = |ptr| (ptr - ((POINTER_HEADER as u16) << 8)) as usize; 188 | let (i, next_label_offset) = map(be_u16, dereference_pointer)(input)?; 189 | 190 | if recursion_depth >= MAX_RECURSION_DEPTH { 191 | panic!("too many DNS message compression indirections!") 192 | } 193 | 194 | // Now, just parse a name from that offset. 195 | let (_, pointed_label) = self 196 | .parse_name(&self.input[next_label_offset..], recursion_depth + 1) 197 | .unwrap(); 198 | name += &pointed_label; 199 | input = i; 200 | break; 201 | } else { 202 | // This label is a literal. 203 | let (i, label) = parse_label(input)?; 204 | input = i; 205 | name += &label; 206 | // Domain names end with a zero-length terminal label. 207 | // (that's why in `dig` the names always end in an unnecessary dot, 208 | // e.g. adamchalmers.com.) 209 | if label.is_empty() { 210 | break; 211 | } 212 | name.push('.'); 213 | } 214 | } 215 | Ok((input, name)) 216 | } 217 | 218 | fn parse_record<'i>(&self, input: &'i [u8]) -> IResult<&'i [u8], Record, Error<&'i [u8]>> { 219 | let (input, name) = self.parse_name(input, 0)?; 220 | let (input, record_type) = map_res(be_u16, RecordType::try_from)(input)?; 221 | let (input, class) = map_res(be_u16, Class::try_from)(input)?; 222 | // RFC defines the max TTL as "positive values of a signed 32 bit number." 223 | let max_ttl: isize = i32::MAX.try_into().unwrap(); 224 | let (input, ttl) = map_res(be_u32, |ttl| { 225 | if (ttl as isize) > max_ttl { 226 | Err(format!("TTL {ttl} is too large")) 227 | } else { 228 | Ok(ttl) 229 | } 230 | })(input)?; 231 | let (i, data) = length_value(be_u16, self.parse_rdata(record_type))(input)?; 232 | Ok(( 233 | i, 234 | Record { 235 | name, 236 | class, 237 | ttl, 238 | data, 239 | }, 240 | )) 241 | } 242 | 243 | fn parse_message<'i>(&self, i: &'i [u8]) -> IResult<&'i [u8], Message, Error<&'i [u8]>> { 244 | // The Header parser requires parsing individual bits, because the RFC stores some boolean 245 | // flags as single bits, and some numbers as 4-bit numbers. 246 | // So, first convert the input from bytestream to bitstream, then run the Header parser, 247 | // then convert the bitstream back to a bystream for the following steps. 248 | let (i, header) = nom::bits::bits(Header::deserialize)(i)?; 249 | 250 | // Parse the right number of question sections. 251 | let (i, question) = count(question::Entry::deserialize, header.question_count.into())(i)?; 252 | 253 | // After the question comes the DNS records themselves. Parse the right number of each kind! 254 | let (i, answer) = count(|i| self.parse_record(i), header.answer_count.into())(i)?; 255 | let (i, authority) = count(|i| self.parse_record(i), header.name_server_count.into())(i)?; 256 | let (i, additional) = count( 257 | |i| self.parse_record(i), 258 | header.additional_records_count.into(), 259 | )(i)?; 260 | Ok(( 261 | i, 262 | Message { 263 | header, 264 | question, 265 | answer, 266 | authority, 267 | additional, 268 | }, 269 | )) 270 | } 271 | } 272 | 273 | #[cfg(test)] 274 | mod tests { 275 | use std::net::Ipv4Addr; 276 | 277 | use crate::{ 278 | dns_types::Class, 279 | message::record::{Record, RecordData}, 280 | }; 281 | 282 | use super::*; 283 | 284 | #[test] 285 | fn test_msg_with_soa_records() { 286 | let response_msg = vec![ 287 | 190, 125, 129, 128, 0, 1, 0, 0, 0, 1, 0, 0, 11, 115, 101, 114, 105, 111, 117, 115, 101, 288 | 97, 116, 115, 3, 99, 111, 109, 0, 0, 5, 0, 1, 192, 12, 0, 6, 0, 1, 0, 0, 1, 44, 0, 53, 289 | 4, 100, 110, 115, 49, 3, 112, 48, 51, 5, 110, 115, 111, 110, 101, 3, 110, 101, 116, 0, 290 | 10, 104, 111, 115, 116, 109, 97, 115, 116, 101, 114, 192, 54, 97, 247, 178, 208, 0, 0, 291 | 168, 192, 0, 0, 2, 88, 0, 9, 58, 128, 0, 0, 1, 44, 292 | ]; 293 | 294 | let msg = Message::deserialize(response_msg).unwrap(); 295 | assert_eq!(msg.header.name_server_count, 1); 296 | assert_eq!(msg.authority.len(), 1); 297 | } 298 | 299 | #[test] 300 | fn test_parse_msg() { 301 | let response_msg = vec![ 302 | 0, 33, 129, 128, 0, 1, 0, 2, 0, 0, 0, 0, // Header (12 bytes) 303 | 4, 98, 108, 111, 103, // blog 304 | 12, 97, 100, 97, 109, 99, 104, 97, 108, 109, 101, 114, 115, // adamchalmers 305 | 3, 99, 111, 109, // com 306 | 0, // . 307 | 0, 1, 0, 1, // class, type 308 | 192, 12, // Answer #1: name, which is a pointer to byte 12. 309 | 0, 1, 0, 1, // class, type 310 | 0, 0, 0, 179, // TTL (u32) 311 | 0, 4, // rdata length 312 | 104, 19, 237, 120, // rdata, an IPv4 313 | 192, 12, // Answer #1: name, which is a pointer to byte 12. 314 | 0, 1, 0, 1, // class, type 315 | 0, 0, 0, 179, // TTL (u32) 316 | 0, 4, // rdata length 317 | 104, 19, 238, 120, // IPv4 318 | ]; 319 | 320 | // Try to parse it 321 | let actual_msg = Message::deserialize(response_msg).unwrap(); 322 | 323 | // Was it correct? 324 | let name = String::from("blog.adamchalmers.com."); 325 | let expected_answers = vec![ 326 | Record { 327 | name: name.clone(), 328 | class: Class::IN, 329 | ttl: 179, 330 | data: RecordData::A(Ipv4Addr::new(104, 19, 237, 120)), 331 | }, 332 | Record { 333 | name, 334 | class: Class::IN, 335 | ttl: 179, 336 | data: RecordData::A(Ipv4Addr::new(104, 19, 238, 120)), 337 | }, 338 | ]; 339 | let actual_answers = actual_msg.answer; 340 | assert_eq!(actual_answers, expected_answers) 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /src/message/header.rs: -------------------------------------------------------------------------------- 1 | use crate::message::parser_utils::*; 2 | use bitvec::prelude::*; 3 | use nom::IResult; 4 | 5 | /// RFC 1035 defines DNS headers as 12 bytes long. 6 | const EXPECTED_SIZE_BYTES: usize = 12; 7 | 8 | /// All DNS messages start with a Header (both queries and responses!) 9 | /// Structure is defined at 10 | #[derive(Debug)] 11 | pub struct Header { 12 | /// A 16 bit identifier assigned by the program that generates any kind of query. This identifier is copied the corresponding reply and can be used by the requester to match up replies to outstanding queries. 13 | pub id: u16, 14 | /// A one bit field that specifies whether this message is a query (0), or a response (1). 15 | is_query: bool, 16 | /// A four bit field that specifies kind of query in this message. This value is set by the originator of a query and copied into the response. 17 | opcode: Opcode, 18 | /// This bit is valid in responses, and specifies that the responding name server is an authority for the domain name in question section. Note that the contents of the answer section may have multiple owner names because of aliases. The AA bit corresponds to the name which matches the query name, or the first owner name in the answer section. 19 | authoritative_answer: bool, 20 | /// Specifies that this message was truncated due to length greater than that permitted on the transmission channel. 21 | truncation: bool, 22 | /// This bit may be set in a query and is copied into the response. If RD is set, it directs the name server to pursue the query recursively. Recursive query support is optional. 23 | recursion_desired: bool, 24 | /// This be (sic) is set or cleared in a response, and denotes whether recursive query support is available in the name server. 25 | recursion_available: bool, 26 | pub resp_code: ResponseCode, 27 | /// Number of entries in the question section. 28 | pub question_count: u16, 29 | /// Number of resource records in the answer section. 30 | pub answer_count: u16, 31 | /// Number of name server resource records in the authority records section. 32 | pub name_server_count: u16, 33 | /// Number of resource records in the additional records section. 34 | pub additional_records_count: u16, 35 | } 36 | 37 | impl Header { 38 | /// Generate the header for a query with one question. 39 | pub fn new_query(id: u16) -> Self { 40 | Self { 41 | id, 42 | is_query: false, 43 | opcode: Opcode::Query, 44 | authoritative_answer: Default::default(), 45 | truncation: false, 46 | recursion_desired: true, 47 | recursion_available: Default::default(), 48 | resp_code: ResponseCode::NoError, // This doesn't matter for a query 49 | // In a query, there will be 1 question and no records. 50 | question_count: 1, 51 | answer_count: 0, 52 | name_server_count: 0, 53 | additional_records_count: 0, 54 | } 55 | } 56 | 57 | /// Serialize the Header and write it into the stream of bits. 58 | pub fn serialize(&self, bv: &mut BitVec) { 59 | let initial_length_bits = bv.len(); 60 | bv.extend_from_bitslice(self.id.view_bits::()); 61 | bv.push(self.is_query); 62 | self.opcode.serialize(bv); 63 | bv.push(self.authoritative_answer); 64 | bv.push(self.truncation); 65 | bv.push(self.recursion_desired); 66 | bv.push(self.recursion_available); 67 | // the Z field, reserved for future use. 68 | // Must be zero in all queries and responses. 69 | bv.extend_from_bitslice(bits![0; 3]); 70 | self.resp_code.serialize(bv); 71 | bv.extend_from_bitslice(self.question_count.view_bits::()); 72 | bv.extend_from_bitslice(self.answer_count.view_bits::()); 73 | bv.extend_from_bitslice(self.name_server_count.view_bits::()); 74 | bv.extend_from_bitslice(self.additional_records_count.view_bits::()); 75 | let bits_written = bv.len() - initial_length_bits; 76 | assert_eq!(bits_written, 8 * EXPECTED_SIZE_BYTES); 77 | } 78 | 79 | pub fn deserialize(i: BitInput) -> IResult { 80 | use nom::combinator::map_res; 81 | 82 | // From RFC 1035, section 4.1.1 83 | // The header contains the following fields: 84 | // 85 | // 1 1 1 1 1 1 86 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 87 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 88 | // | ID | 89 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 90 | // |QR| Opcode |AA|TC|RD|RA| Z | RCODE | 91 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 92 | // | QDCOUNT | 93 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 94 | // | ANCOUNT | 95 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 96 | // | NSCOUNT | 97 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 98 | // | ARCOUNT | 99 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 100 | let (i, id) = take_u16(i)?; 101 | let (i, qr) = take_bit(i)?; 102 | let (i, opcode) = map_res(take_nibble, Opcode::try_from)(i)?; 103 | let (i, aa) = take_bit(i)?; 104 | let (i, tc) = take_bit(i)?; 105 | let (i, rd) = take_bit(i)?; 106 | let (mut i, ra) = take_bit(i)?; 107 | for _ in 0..3 { 108 | let z; 109 | (i, z) = take_bit(i)?; 110 | assert!(!z); 111 | } 112 | let (i, rcode) = map_res(take_nibble, ResponseCode::try_from)(i)?; 113 | let (i, qdcount) = take_u16(i)?; 114 | let (i, ancount) = take_u16(i)?; 115 | let (i, nscount) = take_u16(i)?; 116 | let (i, arcount) = take_u16(i)?; 117 | let header = Header { 118 | id, 119 | is_query: qr, 120 | opcode, 121 | authoritative_answer: aa, 122 | truncation: tc, 123 | recursion_desired: rd, 124 | recursion_available: ra, 125 | resp_code: rcode, 126 | question_count: qdcount, 127 | answer_count: ancount, 128 | name_server_count: nscount, 129 | additional_records_count: arcount, 130 | }; 131 | Ok((i, header)) 132 | } 133 | } 134 | 135 | /// A four bit field that specifies kind of query in this message. 136 | /// This value is set by the originator of a query and copied into the response. 137 | #[derive(Debug)] 138 | enum Opcode { 139 | /// 0: a standard query (QUERY) 140 | Query, 141 | /// 1: an inverse query (IQUERY) 142 | InverseQuery, 143 | /// 2: a server status request (STATUS) 144 | Status, 145 | } 146 | 147 | impl TryFrom for Opcode { 148 | type Error = anyhow::Error; 149 | 150 | fn try_from(value: u8) -> Result { 151 | let op = match value { 152 | 0 => Self::Query, 153 | 1 => Self::InverseQuery, 154 | 2 => Self::Status, 155 | other => anyhow::bail!("Unknown opcode {other}"), 156 | }; 157 | Ok(op) 158 | } 159 | } 160 | 161 | impl Opcode { 162 | fn serialize(&self, bv: &mut BitVec) { 163 | match self { 164 | Self::Query => bv.extend_from_bitslice(bits![u8, Msb0; 0; 4]), 165 | Self::InverseQuery => bv.extend_from_bitslice(bits![u8, Msb0; 0, 0, 0, 1]), 166 | Self::Status => bv.extend_from_bitslice(bits![u8, Msb0; 0, 0, 1, 0]), 167 | } 168 | } 169 | } 170 | 171 | /// This field is set by the DNS resolver and indicates if the DNS query was successful or erroneous. 172 | #[derive(Debug)] 173 | #[cfg_attr(test, derive(Eq, PartialEq))] 174 | pub enum ResponseCode { 175 | NoError, 176 | /// The name server was unable to interpret the query 177 | FormatError, 178 | /// The name server was unable to process this query due to a problem with the name server. 179 | ServerFailure, 180 | /// Meaningful only for 181 | /// responses from an authoritative name 182 | /// server, this code signifies that the 183 | /// domain name referenced in the query does 184 | /// not exist. 185 | NameError, 186 | /// The name server does not support the requested kind of query. 187 | NotImplemented, 188 | /// The name server refuses to 189 | /// perform the specified operation for 190 | /// policy reasons. For example, a name 191 | /// server may not wish to provide the 192 | /// information to the particular requester, 193 | /// or a name server may not wish to perform 194 | /// a particular operation (e.g., zone 195 | Refused, 196 | } 197 | 198 | impl ResponseCode { 199 | fn serialize(&self, bv: &mut BitVec) { 200 | match self { 201 | Self::NoError => bv.extend_from_bitslice(bits![u8, Msb0; 0; 4]), 202 | Self::FormatError => bv.extend_from_bitslice(bits![u8, Msb0; 0, 0, 0, 1]), 203 | Self::ServerFailure => bv.extend_from_bitslice(bits![u8, Msb0; 0, 0, 1, 0]), 204 | Self::NameError => bv.extend_from_bitslice(bits![u8, Msb0; 0, 0, 1, 1]), 205 | Self::NotImplemented => bv.extend_from_bitslice(bits![u8, Msb0; 0, 1, 0, 0]), 206 | Self::Refused => bv.extend_from_bitslice(bits![u8, Msb0; 0, 1, 0, 1]), 207 | }; 208 | } 209 | } 210 | 211 | impl std::fmt::Display for ResponseCode { 212 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 213 | let s = match self { 214 | Self::NoError => "No error condition", 215 | Self::FormatError => "The name server was unable to interpret the query", 216 | Self::ServerFailure => "The name server was unable to process this query due to a problem with the name server.", 217 | Self::NameError => "Domain name referenced in the query does not exist", 218 | Self::NotImplemented => "The name server does not support the requested kind of query", 219 | Self::Refused => "The name server refuses to perform the specified operation for policy reasons. For example, a name server may not wish to provide the information to the particular requester, or a name server may not wish to perform a particular operation" 220 | }; 221 | s.fmt(f) 222 | } 223 | } 224 | 225 | impl TryFrom for ResponseCode { 226 | type Error = anyhow::Error; 227 | 228 | fn try_from(value: u8) -> Result { 229 | let op = match value { 230 | 0 => Self::NoError, 231 | 1 => Self::FormatError, 232 | 2 => Self::ServerFailure, 233 | 3 => Self::NameError, 234 | 4 => Self::NotImplemented, 235 | 5 => Self::Refused, 236 | other => anyhow::bail!("Unknown response code {other}"), 237 | }; 238 | Ok(op) 239 | } 240 | } 241 | 242 | #[cfg(test)] 243 | mod tests { 244 | 245 | use super::*; 246 | use std::io::Read; 247 | 248 | #[test] 249 | fn test_serialize_header_for_query() { 250 | let test_id = 33; 251 | let h = Header::new_query(test_id); 252 | let mut bv = BitVec::::new(); 253 | h.serialize(&mut bv); 254 | let mut buf = [0; EXPECTED_SIZE_BYTES]; 255 | bv.as_bitslice().read_exact(&mut buf).unwrap(); 256 | } 257 | 258 | #[test] 259 | fn test_deserialize() { 260 | // This is a real response from a DNS resolver (1.1.1.1) 261 | let i = vec![ 262 | 0, 33, 128, 130, 0, 1, 0, 0, 0, 0, 0, 0, 4, 98, 108, 111, 103, 12, 97, 100, 97, 109, 263 | 99, 104, 97, 108, 109, 101, 114, 115, 3, 99, 111, 109, 0, 0, 1, 0, 1, 264 | ]; 265 | 266 | pub fn deser(i: &[u8]) -> IResult<&[u8], Header> { 267 | nom::bits::bits(Header::deserialize)(i) 268 | } 269 | let (_i, h): (&[u8], Header) = deser(&i).unwrap(); 270 | assert_eq!(h.id, 33); 271 | assert_eq!(h.resp_code, ResponseCode::ServerFailure); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/message/parser_utils.rs: -------------------------------------------------------------------------------- 1 | use nom::{bits::complete::take, IResult}; 2 | 3 | /// Newtype around a very common type in Nom. 4 | /// Represents a binary sequence which can be parsed one bit at a time. 5 | /// Nom represents this as a sequence of bytes, and an offset tracking which number bit 6 | /// is currently being read. 7 | /// 8 | /// For example, you might start with 16 bits, pointing at the 0th bit: 9 | ///``` 10 | /// 1111000011001100 11 | /// ^ 12 | /// ``` 13 | /// Nom represents this using the BitInput type as: 14 | /// ``` 15 | /// ([0b11110000, 0b11001100], 0) 16 | /// ^ 17 | /// ``` 18 | /// Lets say you parsed 3 bits from there. After that, the BitInput would be 19 | /// 20 | /// ``` 21 | /// ([0b11110000, 0b11001100], 3) 22 | /// ^ 23 | /// ``` 24 | /// After reading another six bits, the input would have advanced past the first byte: 25 | /// 26 | /// ``` 27 | /// ([0b11110000, 0b11001100], 9) 28 | /// ^ 29 | /// ``` 30 | /// Because the first byte will never be used again, Nom optimizes by dropping the first byte 31 | /// 32 | /// ``` 33 | /// ([0b11001100], 1) 34 | /// ^ 35 | /// ``` 36 | pub type BitInput<'a> = (&'a [u8], usize); 37 | 38 | /// Take 4 bits from the BitInput. 39 | /// Parse into a uint with most significant bit first. 40 | /// Add 0000 as padding to the most significant bits to the output number to make it 41 | /// fit into a u8. 42 | pub fn take_nibble(i: BitInput) -> IResult { 43 | take(4u8)(i) 44 | } 45 | 46 | /// Take 16 bits from the BitInput, parse into a uint with most significant bit first.. 47 | pub fn take_u16(i: BitInput) -> IResult { 48 | take(16u8)(i) 49 | } 50 | 51 | /// Takes one bit from the BitInput. 52 | pub fn take_bit(i: BitInput) -> IResult { 53 | let (i, bit): (BitInput, u8) = take(1u8)(i)?; 54 | Ok((i, bit != 0)) 55 | } 56 | -------------------------------------------------------------------------------- /src/message/question.rs: -------------------------------------------------------------------------------- 1 | use crate::{parse::parse_labels_then_zero, Class, RecordType}; 2 | use anyhow::{anyhow, Result as AResult}; 3 | use bitvec::prelude::*; 4 | use nom::{combinator::map_res, number::complete::be_u16, IResult}; 5 | use std::fmt; 6 | 7 | const LABEL_TOO_LONG: &str = "is too long (must be <64 chars)"; 8 | 9 | #[derive(Debug)] 10 | pub struct Entry { 11 | labels: Vec, 12 | record_type: RecordType, 13 | record_qclass: Class, 14 | } 15 | 16 | impl fmt::Display for Entry { 17 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 | let s = format!("{}: {}", self.record_type, &self.labels.join(".")); 19 | s.fmt(f) 20 | } 21 | } 22 | 23 | impl Entry { 24 | pub(crate) fn new(labels: Vec, record_type: RecordType) -> Self { 25 | Self { 26 | labels, 27 | record_type, 28 | record_qclass: Class::IN, 29 | } 30 | } 31 | 32 | pub fn serialize(&self, bv: &mut BitVec) -> AResult<()> { 33 | self.serialize_qname(bv)?; 34 | self.record_type.serialize(bv); 35 | self.record_qclass.serialize(bv); 36 | Ok(()) 37 | } 38 | 39 | fn serialize_qname(&self, bv: &mut BitVec) -> AResult<()> { 40 | // QNAME a domain name represented as a sequence of labels, where 41 | // each label consists of a length octet followed by that 42 | // number of octets. 43 | for label in &self.labels { 44 | // The mapping of domain names to labels is defined in RFC 1035: 45 | // 2.3.1. Preferred name syntax 46 | let len = label.len(); 47 | let fmt = format!("Label {label} {LABEL_TOO_LONG}"); 48 | let len = u8::try_from(len).map_err(|_| anyhow!("{fmt}"))?; 49 | if len >= 64 { 50 | anyhow::bail!("{fmt}") 51 | } 52 | bv.extend_from_bitslice(len.view_bits::()); 53 | label 54 | .chars() 55 | .map(|ch| ch.try_into().unwrap()) 56 | .for_each(|byte: u8| bv.extend_from_bitslice(byte.view_bits::())); 57 | } 58 | Ok(()) 59 | } 60 | 61 | pub fn deserialize(i: &[u8]) -> IResult<&[u8], Self> { 62 | let (i, labels) = parse_labels_then_zero(i)?; 63 | let (i, record_type) = map_res(be_u16, RecordType::try_from)(i)?; 64 | let (i, record_qclass) = map_res(be_u16, Class::try_from)(i)?; 65 | Ok(( 66 | i, 67 | Self { 68 | labels, 69 | record_type, 70 | record_qclass, 71 | }, 72 | )) 73 | } 74 | } 75 | 76 | #[cfg(test)] 77 | mod tests { 78 | 79 | use super::*; 80 | use std::io::Read; 81 | 82 | #[test] 83 | fn test_serialize_entry() { 84 | let entry = Entry { 85 | labels: vec![ 86 | String::from("adamchalmers"), 87 | String::from("com"), 88 | String::from(""), 89 | ], 90 | record_type: RecordType::A, 91 | record_qclass: Class::IN, 92 | }; 93 | let mut bv = BitVec::::new(); 94 | entry.serialize(&mut bv).unwrap(); 95 | let mut buf = Vec::new(); 96 | let expected_bytes_read = "adamchalmers".len() + 1 + // First label 97 | "com".len() + 1 // Second label 98 | + 1 // Last empty label 99 | + 2 // QCLASS is 16 bits 100 | + 2; // QTYPE is 16 bits 101 | let actual_bytes_read = bv.as_bitslice().read_to_end(&mut buf).unwrap(); 102 | assert_eq!(expected_bytes_read, actual_bytes_read); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/message/record.rs: -------------------------------------------------------------------------------- 1 | use std::net::{Ipv4Addr, Ipv6Addr}; 2 | 3 | use crate::{Class, RecordType}; 4 | 5 | #[derive(Debug)] 6 | #[cfg_attr(test, derive(Eq, PartialEq))] 7 | pub struct Record { 8 | pub name: String, 9 | pub class: Class, 10 | pub ttl: u32, 11 | pub data: RecordData, 12 | } 13 | 14 | impl Record { 15 | pub fn as_dns_response(&self) -> String { 16 | let rdata = match &self.data { 17 | RecordData::A(ipv4) => ipv4.to_string(), 18 | RecordData::Aaaa(ipv6) => ipv6.to_string(), 19 | RecordData::Cname(name) => name.to_string(), 20 | RecordData::Soa(soa) => format!("{soa:?}"), 21 | RecordData::Ns(name) => name.to_string(), 22 | }; 23 | format!("{}: {rdata} (TTL {})", self.data.as_type(), self.ttl) 24 | } 25 | } 26 | 27 | #[derive(Debug)] 28 | #[cfg_attr(test, derive(Eq, PartialEq))] 29 | pub enum RecordData { 30 | A(Ipv4Addr), 31 | Aaaa(Ipv6Addr), 32 | Cname(String), 33 | Soa(SoaData), 34 | Ns(String), 35 | } 36 | 37 | impl RecordData { 38 | fn as_type(&self) -> RecordType { 39 | match self { 40 | Self::A(_) => RecordType::A, 41 | Self::Aaaa(_) => RecordType::Aaaa, 42 | Self::Cname(_) => RecordType::Cname, 43 | Self::Soa(_) => RecordType::Soa, 44 | Self::Ns(_) => RecordType::Ns, 45 | } 46 | } 47 | } 48 | 49 | #[derive(Debug)] 50 | #[cfg_attr(test, derive(Eq, PartialEq))] 51 | pub struct SoaData { 52 | /// name server that was the original or primary source of data for this zone. 53 | pub mname: String, 54 | /// mailbox of the person responsible for this zone. 55 | pub rname: String, 56 | /// The unsigned 32 bit version number of the original copy 57 | /// of the zone. Zone transfers preserve this value. This 58 | /// value wraps and should be compared using sequence space 59 | /// arithmetic. 60 | pub serial: u32, 61 | /// time interval before the zone should be refreshed. 62 | pub refresh: u32, 63 | /// time interval that should elapse before a failed refresh should be retried. 64 | pub retry: u32, 65 | /// upper limit on the time interval that can elapse before the zone is no longer authoritative. 66 | pub expire: u32, 67 | } 68 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use nom::{combinator::map_res, IResult}; 2 | 3 | /// Matches a sequence of labels, terminated by a zero-length label. 4 | pub fn parse_labels_then_zero(mut i: &[u8]) -> IResult<&[u8], Vec> { 5 | let mut labels = Vec::new(); 6 | loop { 7 | let (new_i, label) = parse_label(i)?; 8 | i = new_i; 9 | let len = label.len(); 10 | labels.push(label); 11 | if len == 0 { 12 | return Ok((i, labels)); 13 | } 14 | } 15 | } 16 | 17 | /// Read one byte as a u8. Then read that many following bytes and output them, as ASCII. 18 | pub fn parse_label(i: &[u8]) -> IResult<&[u8], String> { 19 | let parse_len = map_res(nom::number::complete::be_u8, |num| { 20 | if num >= 64 { 21 | Err(format!( 22 | "DNS name labels must be <=63 bytes but this one is {num}" 23 | )) 24 | } else { 25 | Ok(num) 26 | } 27 | }); 28 | let parse_label = nom::multi::length_data(parse_len); 29 | map_res(parse_label, |bytes: &[u8]| { 30 | String::from_utf8(bytes.to_vec()) 31 | })(i) 32 | } 33 | --------------------------------------------------------------------------------