Adam.S
Posted on March 25, 2021
I used to often work with DNS in one of my previous jobs. So I have always had a long running interesting in DNS. Most recently I have been looking into Rust. I wanted to see how easy it might be to use rust to access DNS records, one; because rust is said to be fast, and also because it's a safe programming language.
This will be a brief write up at my attempt to use trust-dns-resolver to do MX record lookups and subsequently host address lookups.
Warning: This code is not intended to be used in production. You should review and adjust to your own needs.
Getting Started
First we will need to create our development environment.
cargo new trust-dns-resolver && cd $_
This will give us our standard rust directly structure. We need to add our crate to the Cargo.toml
-snip-
[dependencies]
trust-dns-resolver = "0.20.1"
Next edit the src/main.rs
as follows. This code was taken from the crate documentation with a couple of minor edits to get to compile.
use std::net::*;
use trust_dns_resolver::config::*;
use trust_dns_resolver::Resolver;
fn main() {
// Construct a new Resolver with default configuration options
let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()).unwrap();
// Lookup the IP addresses associated with a name.
let response = resolver.lookup_ip("www.example.com.").unwrap();
// There can be many addresses associated with the name,
// this can return IPv4 and/or IPv6 addresses
let address = response.iter().next().expect("no addresses returned!");
if address.is_ipv4() {
assert_eq!(address, IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)));
} else {
assert_eq!(address, IpAddr::V6(Ipv6Addr::new(0x2606, 0x2800, 0x220, 0x1, 0x248, 0x1893, 0x25c8, 0x1946)));
}
}
Compile and run the code to make sure everything is ok. There should be no output.
cargo run
Assuming this has worked and we got a clean build and run we can move onto digging up MX records.
Changing the code to get MX records
Lets replace the entire code with the following:
use trust_dns_resolver::config::*;
use trust_dns_resolver::Resolver;
fn main() {
// Construct a new Resolver with default configuration options
let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()).unwrap();
// Lookup the IP addresses associated with a name.
// The final dot forces this to be an FQDN, otherwise the search rules as specified
// in `ResolverOpts` will take effect. FQDN's are generally cheaper queries.
let mx_response = resolver.mx_lookup("hotmail.com.");
// There can be many addresses associated with the name,
// this can return IPv4 and/or IPv6 addresses
//let address = response.iter().next().expect("no addresses returned!");
//println!("{}", address);
match mx_response {
Err(_) => println!("No Records"),
Ok(mx_response) => {
let addresses = mx_response.iter();
for record in addresses {
println!("{} {}", record.preference(), record.exchange());
let host_name = record.exchange();
let lookup_response = resolver.lookup_ip(host_name.to_string().as_str()).unwrap();
let addr_list = lookup_response.iter();
for addr in addr_list {
if addr.is_ipv4() {
println!("\tip4: {}", addr)
} else {
println!("\tip6: {}", addr)
}
}
}
}
}
}
Explaining the code
Let's take a look at what we are doing here.
First we create a resolver which will do the work of doing the DNS lookups.
Next we use the resolver to call mx_lookup()
and store the result into mx_response
mx_response
will contain either and MXLookup
or an Err
. For this reason we need to handle these two cases. Here I will use match
.
In the case of Err
. I do nothing and just report there were no records.
In the case of MXLookup
, I will need to do some more processing.
- First lets make an
iter()
out ofmx_response
and loop over it. - In the loop we get the
preference()
or MX weight and theexchange()
or DNS record for the host. - I am taking a short cut in the next step where I look up the ip address. I am assuming that because there is an MX host record, there will be an ip address. This could be a false assumption due to misconfiguration, so I will address that later. For now I will simply do a
lookup_ip()
and trust thatunwrap()
will no panic. - Again I convert the
lookup_response
to aniter()
and loop over the result.
The output if this code is:
Finished dev [unoptimized + debuginfo] target(s) in 0.28s
Running `target/debug/trust-dns`
2 hotmail-com.olc.protection.outlook.com.
ip4: 104.47.55.33
ip4: 104.47.58.33
Preference: 2
Exchange: hotmail-com.olc.protection.outlook.com.
This exchange host resolves to two ip addresses. Both of which are IPv4
If we change the code a little and replace hotmail.com
with gmail.com
we get:
10 alt1.gmail-smtp-in.l.google.com.
ip4: 74.125.137.26
5 gmail-smtp-in.l.google.com.
ip4: 108.177.125.27
20 alt2.gmail-smtp-in.l.google.com.
ip4: 142.250.138.27
30 alt3.gmail-smtp-in.l.google.com.
ip4: 173.194.199.26
40 alt4.gmail-smtp-in.l.google.com.
ip4: 209.85.145.26
Though I know gmail has IPv6 addresses, I am currently not getting them. I will have to see if I am missing something. I do actually expect to get them.
Improving on the Code
Let's make things a little safer by remove the unwrap()
To keep things simpler, I will just provide the updated match
code here.
match mx_response {
Err(_) => println!("No Records"),
Ok(mx_response) => {
let records = mx_response.iter();
for record in records {
println!("{} {}", record.preference(), record.exchange());
let lookup_response = resolver.lookup_ip(record.exchange().to_string().as_str());
match lookup_response {
Err(_) => println!("This exchange host has no address."),
Ok(lookup_response) => {
let ip_addrs = lookup_response.iter();
for ip_addr in ip_addrs {
if ip_addr.is_ipv4() {
println!(" ip4: {}", ip_addr)
} else {
println!(" ip6: {}", ip_addr)
}
}
}
}
}
}
}
Please note I have update some of the variable names and we now have nested match
statements. But the code code generates the same output as in the previous example.
I hope you find this interesting and useful.
Posted on March 25, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.