Local DNS server and testing email delivery
Domain Name System (DNS) is one of the most important Internet protocol. It plays especially important role in email delivery being a prerequisite to Mail Transport Agents (MTAs). Running a local DNS test server to test various MTA configurations in an environment that can simulate production DNS is very helpful.
In this blog post I will not cover DNS theory, but rather focus on a practical examples and tools of how to run local DNS server and various tools I use for testing MTA configurations. I’ll use Ubuntu (tested with 14.04), Ruby, RubyDNS, mailcatcher and dig. If you’re not familiar with DNS, here’s an informative tutorial covering the basics.
Disable OS DNS resolution
Before we disable the operating system’s DNS resolution, let’s install RubyDNS that we’ll use as our DNS server:
gem install rubydns
Now, let’s disable DNS on Ubuntu. Comment out dns=dnsmasq
line in NetworkManager.conf
file and restart the network-manager
service.
sudo vim /etc/NetworkManager/NetworkManager.conf
sudo service network-manager restart
After that, network-manager
might update /etc/resolv.conf
file and set your router as nameserver. If so, just edit that file and comment out the nameserver
line to disable it.
sudo vim /etc/resolv.conf
# nameserver 192.168.100.1
Confirm that there is no local DNS server running:
ping google.com
# ping: unknown host google.com
Run RubyDNS DNS server
Create dns.rb
file with the following content:
require 'rubydns'
INTERFACES = [
[:udp, "0.0.0.0", 53],
[:tcp, "0.0.0.0", 53]
]
# Use upstream DNS for name resolution.
UPSTREAM = RubyDNS::Resolver.new([[:udp, "8.8.8.8", 53], [:tcp, "8.8.8.8", 53]])
# Start the RubyDNS server
RubyDNS::run_server(:listen => INTERFACES) do
match('www.example.com', Resolv::DNS::Resource::IN::A) do |transaction|
transaction.respond!("127.0.0.1")
end
match('mail.example.com', Resolv::DNS::Resource::IN::A) do |transaction|
transaction.respond!("127.0.0.1")
end
match("example.com", Resolv::DNS::Resource::IN::MX) do |transaction|
transaction.respond!(10, Resolv::DNS::Name.create("mail.example.com."))
end
# Default DNS handler
otherwise do |transaction|
transaction.passthrough!(UPSTREAM)
end
end
In the example above we have configured A
and MX
records and a passthrough to Google public DNS 8.8.8.8
. If you don’t want to pass through DNS queries, you can disable it by commenting the otherwise
block.
You will need sudo
permissions to bind on port 53 that is the default DNS port. I’m using rbenv
and rbenv-sudo
, so the command to start RubyDNS
server is:
rbenv sudo ruby dns.rb
Query DNS server
To check if our local DNS server is running, let’s query it. I use dig
to query DNS, but there are other alternatives like nslookup
, host
, etc.
dig a www.example.com
You will get a longer response string that should have the following ANSWER
section:
;; ANSWER SECTION:
www.example.com. 86400 IN A 127.0.0.1
To see the most important part, use +short
option:
dig a +short www.example.com
# 127.0.0.1
When debugging DNS, it’s sometimes useful to specify which DNS server you want to query:
dig a +short www.example.com @127.0.0.1
# 127.0.0.1
dig a +short www.example.com @8.8.8.8
# 93.184.216.34
In the second query, we use the Google public DNS at 8.8.8.8
and we get the real IP address for www.example.com
. While, in the first example we query the local DNS at 127.0.0.1
and we get the IP specified by RubyDNS
config.
How DNS resolution for email works
Let’s say we want to send an email from from@example.com
, to to@example.com
.
What MTA will do is look for the domain part of the email, that is example.com
and do a DNS lookup asking for the MX
(Mail eXchanger) record:
dig mx +short example.com
# 10 mail.example.com.
The MX
record specifies the mail server responsible for accepting email messages, in our case that is mail.example.com
. And, for that domain we have an A
record that specifies the mail server IP address.
dig a +short mail.example.com
# 127.0.0.1
Here’s Ruby net/smtp
example on how to send an email to that server. Add this in a file called mail.rb
.
require 'net/smtp'
from = 'from@example.com'
to = 'to@example.com'
message = <<-MESSAGE
Date: Sun, 10 Oct 2015 10:00:00 +0000
From: #{from}
Subject: Test
To: #{to}
MIME-Version: 1.0
Content-Type: text/html; charset=UTF-8
MESSAGE
Net::SMTP.start('mail.example.com', 25) do |smtp|
smtp.send_message message, from, to
end
Before, we run that file, let’s setup mailcatcher
that is a simple SMTP server that catches messages sent to it and displays them in a web interface. Install mailcatcher
gem and start it with sudo
permissions to bind to port 25 that is the default port for SMTP:
gem install mailcatcher
rbenv sudo mailcatcher --ip 0.0.0.0 --smtp-ip 0.0.0.0 --smtp-port 25 --http-port 1080 -f -v
# open http://localhost:1080/ for web interface
Once, that’s running, let’s send an email with:
ruby mail.rb
Then, in mailcatcher
we should see the email:
With this setup, simulating production DNS and testing various MTA configs locally is really easy. It’s often useful to configure the MTA (Postfix, PowerMTA or whatever) route emails to mailcatcher to check their content or to test the routing itself.