DNS in the House with DNSMasq

Get rid of that 192.168.0.x

Overview

“I want the devices in my house to communicate with each other, but remembering their IP addresses is almost as bad as going through a gum surgery.” Yeah, man, I hear you. You’d love to refer to that 192.168.0.101 as tv.home, 192.168.0.107 as games.home, and of course 192.168.0.119 as sherlock.home, wouldn’t you? And, while it’d be awesome if you could achieve that directly through your ISP’s router, when you open its web GUI, there’s no option for that… WTF?!?

The time will come when you’ll want to assign names to your network devices, just like you used to do to your High School “friends.” The good news is that your devices won’t get offended. The bad news is that lower end consumer routers won’t let you do it. As usual, we need to take matters into our own hands, literally, and DNSMasq is here to help us.

There are multiple ways of achieving this. For completeness’ sake, we’ll start with the easiest one, the hosts file, but the focus of this article is on using DNSMasq to solve this specific problem. To that end, the only requirement is a Raspberry Pi configured with the Raspbian OS and a fixed IP address, as demonstrated in a previous instalment of this series.

Before getting to the installation and configuration details, let’s take a quick detour around SOHO routers and DNS.

Consumer-grade routers and DNS

As far as I know, all modern consumer routers will come with a DNS relay server out of the box. What a DNS relay server, or DNS forwarding server, does is essentially this: it takes the DNS requests from the devices connected to the LAN and, instead of resolving them itself, forwards them somewhere else (upstream DNS servers, often managed by our ISPs). Most routers will also perform DNS caching behind the scenes, sparing the upstream DNS servers a huge amount of duplicate requests.

This is the process in more detail:

  1. when you plug an ethernet cable into your computer or configure it to use wi-fi, it will use the DHCP protocol to broadcast a message over the network asking for information about it—and an IP address for itself;
  2. the router will catch that request and reply with a new IP address for your computer (192.168.0.36) and a bunch of other information, like the router’s own IP address (192.168.0.1) and subnet mask (255.255.255.0);
  3. from that point on, your computer will use the router for domain name resolution, among other functionalities, without regard to how those things are done;
  4. when you type in computers.wtf in your browser’s address bar, a DNS request is sent to your router. The router will simply forward it to its upstream DNS server, which will then return a response containing this website’s IP address;
  5. the router will cache that response somewhere—a database, a text file, who knows?—and send it back to your computer;
  6. your computer will gladly accept the response, without really caring about all the forwarding/relaying steps performed by the router.

Note that this is a gross simplification and doesn’t cover all possible ways a new device can join a network. More importantly, I’m using the term router very loosely here; technically speaking, consumer routers do way more than just routing (packet forwarding among different networks).

As you certainly know, all this stuff happens without you having to do any extra configuration; just plug the cable in and you’re good to go. Because the computer is using the router as a gateway for all traffic beyond the LAN, including domain name resolution of addresses out in the wild (WAN), all this magic is invisible to the end user. And most end users only need their routers to access things on the Internet. (Aunt Lynda is more than happy with her Gmail account, she couldn’t care less about a local SMTP server.)

But what about the metal heads out there who want to access their self-hosted FTP servers with proper domain names? I’m one of them myself and, if you’ve reached this far in this article, I guess you’re one of us too. The reality is that manufacturers won’t include more advanced features into their lower end products just because we, tech savvy, know how to use them; they’ll instead offer us their more expensive models. Besides, if they did add full-blown DNS servers to their consumer routers, they’d need to append a good chunk of documentation to their manuals and maybe extend help desk support. What’s the chance?

In short, cheaper routers do take on some DNS responsibilities, but those usually don’t include DNS resolution; such routers aren’t equipped with a database that maps domain names to IP addresses of any kind, local or not. The end result is that the router will simply forward all DNS requests that haven’t yet been cached, no matter if you’re trying to access a local machine in the LAN (monkey.local) or a remote server on the Internet (gnu.org). Obviously, the upstream DNS servers—OpenDNS, Google DNS, your ISP’s own DNS etc.—have no idea where to find monkey.local, and will reply with an unresolved domain name if you try to access it.

The solutions to that limitation vary, with the hosts file being the most popular one.

Solution #1: hosts file

The hosts file—/etc/hosts on Linux—is a plain text file that maps hostnames to IP addresses. As the manual page, man hosts, puts it:

Before the advent of DNS, the host table was the only way of resolving hostnames on the fledgling Internet.

Indeed, you can use that file as a local DNS database, mapping hostnames to IP addresses of any kind. If, for some reason, you want a website on the Internet to point to an IP address of another website, you can add the new mapping to your computer’s hosts file:

1.1.1.1         internet.com

The network stack running on your computer will catch that change instantly (or not…) and send your web client to 1.1.1.1 every time you request internet.com.

Naturally, you can do the same thing to your local devices. Say you want to access a smart phone in the LAN without having to remember its IP address; you could assign a hostname to it:

192.168.0.36    android.local

Easy, right? The problem arises when there’s a third device in the mix, or maybe a fourth and a fifth. Imagine yourself having to access that Android phone from your spouse’s laptop; you’d need to modify the laptop’s hosts file as well. Things get even worse when you have to update the hosts file on all those devices after a change in the network configuration. And let’s not forget the fact that some devices will simply not give us any kind of access to their hosts file (assuming they even come with one).

So, /etc/hosts is great when we have a tiny local network to deal with. But, as the manual itself implies, DNS can do more (and better) than that.

Solution #2: local DNS server

A local DNS server is the obvious choice when our LAN is inhabited by numerous machines. And, since our cheap router’s manufacturers won’t give us one, we’ll need to get it ourselves.

When it comes to DNS servers, BIND is a great option, but I find it overkill for most SOHO setups. DNSMasq is simpler to configure and supposedly utilizes less CPU power.1

DNSMasq is actually more than just a DNS server, but it’s important to note that the only features we’re going to use in this article are:

  • DNS caching;
  • DNS forwarding;
  • most importantly, DNS resolution.

I said most importantly because DNS resolution is precisely what our inexpensive routers are missing. All the stuff that’s not strictly related to DNS, such as DHCP, will be left as is by default: inactive.

Before I forget, let me give you a quick overview of how my LAN’s setup:

  • ISP router is kept with factory settings—IP address 192.168.0.1 and network mask 255.255.255.0;
  • RPi is configured with self-assigned IP address 192.168.0.2.

Also, and this will be important later, following is the content of my RPi’s /etc/resolv.conf file:

# Generated by resolvconf
nameserver 192.168.0.1

For now, just note that the only entry in that file points to my router and was generated by the system’s resolver. What that means is that all DNS requests originating from, or sent to, the RPi are being forwarded to the LAN’s router.

Install DNSMasq

Installing DNSMasq on the RPi is as simple as this:

# From RPi
sudo apt install dnsmasq

If you don’t have DNSUtils on your personal computer, I recommend you install it, because it comes with a bunch of tools (nslookup, dig etc.) that will help you test and debug your setup:

# From personal/development machine
sudo apt install dnsutils

That’s it, really. DNSMasq should already be running and serving DNS requests:

# From personal/development machine
$ dig @192.168.0.2 +short duckduckgo.com
107.20.240.232
184.72.104.138
23.21.193.169

Default upstream nameserver

If you look inside your RPi’s /etc/resolv.conf file now, you should see this:

# Generated by resolvconf
nameserver 127.0.0.1

After DNSMasq is installed, it tells the system’s resolver that all DNS queries sent to, or originating from, that machine (localhost) will be taken care of by DNSMasq, and the system’s resolver updates its config file accordingly. The value that was in that file originally—the IP address of the default upstream nameserver, i.e. my router’s—isn’t lost forever, though; the resolver regenerates it in a DNSMasq-specific file, /var/run/dnsmasq/resolv.conf:

# Generated by resolvconf
nameserver 192.168.0.1

That file is responsible for specifying the upstream nameservers that DNSMasq should forward queries to. Consequently, all DNS queries sent to my RPi are still being forwarded to my router, only this time DNSMasq is sitting in between. And that’s the reason why the dig command above worked—because DNSMasq, out of the box, knows where to forward requests it can’t resolve itself.

DNSMasq forwarding DNS requests to router (not what we want).

Of course, this is not what we want—we don’t want to keep our router handling DNS for us, but we’re not going to change that just yet.

Create new config file

For DNSMasq, I prefer centralizing all configuration in a single file, because that’s easier to manage and debug. DNSMasq encourages us to be the least intrusive possible in the system, letting us create custom config files that take precedence over the default ones. So, instead of messing with the main config file itself (/etc/dnsmasq.conf), I’ll create a new one at /etc/dnsmasq.d/home.conf. You can name yours however you want, as long as you put it inside the /etc/dnsmasq.d directory.

One thing you’ll notice about DNSMasq is that it doesn’t get updated every time you add, modify or remove a config file. So, if your changes don’t seem to be honored, make sure you restart the service unit:

# From RPi
sudo systemctl restart dnsmasq.service

Logging

I recommend you enable logging whenever you’re tweaking DNSMasq’s configs, otherwise it can be very trick (if not impossible) to know what’s going on under the hoods. As soon as you’re done, though, it’s a good idea to disable it, because the log file can grow really fast and take up a lot of space in your RPi’s SD card.

We want to see all DNS queries handled by DNSMasq and store those log entries in a specific file (/var/log/dnsmasq.log). To accomplish that, add the following lines to /etc/dnsmasq.d/home.conf:

log-queries
log-facility=/var/log/dnsmasq.log

Let’s restart the service and stream the log file’s contents from a new terminal window so that we can have real-time feedback from DNSMasq:

# From RPi
sudo systemctl restart dnsmasq.service
sudo tail -f /var/log/dnsmasq.log

Now, if you repeat the previous dig command (dig @192.168.0.2 duckduckgo.com) from your personal computer, you should see new output in the DNSMasq log file:

Jul 27 23:41:53 dnsmasq[30927]: query[A] duckduckgo.com from 192.168.0.2
Jul 27 23:41:53 dnsmasq[30927]: forwarded duckduckgo.com to 192.168.0.1
Jul 27 23:41:53 dnsmasq[30927]: forwarded duckduckgo.com to 2607:fea8:520:1e9:1eab:c0ff:fe9e:89a2
Jul 27 23:41:53 dnsmasq[30927]: reply duckduckgo.com is 184.72.104.138
Jul 27 23:41:53 dnsmasq[30927]: reply duckduckgo.com is 107.20.240.232
Jul 27 23:41:53 dnsmasq[30927]: reply duckduckgo.com is 23.21.193.169

As you can see, my DNS requests were correctly sent to DNSMasq, and the logs confirm that they’re being forwarded to my router (192.168.0.1).

DNS mapping

By default, DNSMasq will get its DNS entries from… you guessed right: /etc/hosts. But we don’t want to mess with that system file, so let’s create a new one. I’ll put mine under /etc/hosts.home, with the following content:

192.168.0.10    sherlock sherlock.home

We still need to let DNSMasq know about the new file. Update your custom DNSMasq config file (/etc/dnsmasq.d/home.conf) with the new value for the addn-hosts option:

addn-hosts=/etc/hosts.home

Once you restart the service, DNSMasq will read that custom hosts file and map both sherlock and sherlock.home to 192.168.0.10. Querying for one of those domains now should return a successful result:

# From personal/development machine
$ dig @192.168.0.2 +short sherlock.home
192.168.0.10

Thanks to the new hosts file we created, DNSMasq now knows how to resolve sherlock.home and won’t forward such requests upstream. The logs will confirm that:

Jul 27 23:25:55 dnsmasq[21935]: query[A] sherlock.home from 192.168.0.10
Jul 27 23:25:55 dnsmasq[21935]: /etc/hosts.home sherlock.home is 192.168.0.10

It should be clear at this point that specifying the RPi’s IP address when running dig will force the request to be sent to the RPi’s DNSMasq, but we can’t do much with that. No device in the LAN really knows about our new DNS server and right now DNSMasq is sitting there virtually unutilized. Because the other machines in the local network are using the router’s DNS server by default, we need a way to tell them to switch to DNSMasq. But before we do that, we need to set another configuration option.

Right now DNSMasq is not really being used by any device.

DNS forwarding

The whole idea of this tutorial is to retire our router’s DNS server and let DNSMasq take over. If DNSMasq is to become the LAN’s official DNS server, though, it can’t keep forwarding its unresolved requests to the router; we need to ask it to send those requests somewhere else:

sudo tee -a /etc/dnsmasq.d/home.conf << END
no-resolv
server=1.1.1.1
server=1.0.0.1
server=208.67.222.222
server=208.67.220.220
END

no-resolv tells DNSMasq not to get its upstream nameservers from any resolver file in the filesystem (like /etc/resolv.conf). The four server lines contain the IP addresses of the upstream nameservers I want DNSMasq to use—Cloudflare/APNIC and OpenDNS, respectively.

After restarting the service, let’s try another dig command with a domain name outside of the LAN, similar to the very first one we ran previously:

# From personal/development machine
$ dig @192.168.0.2 +short duckduckgo.com
107.20.240.232
184.72.104.138
23.21.193.169

The end result is the same, but behind the scenes we can see that the new forwarding rules are already taking effect. The logs will give you all the details, including where the request is being forwarded to:

Jul 27 21:57:44 raspberrypi systemd[1]: Started dnsmasq - A lightweight DHCP and caching DNS server.
Jul 27 21:59:01 raspberrypi dnsmasq[32276]: query[A] duckduckgo.com from 192.168.0.10
Jul 27 21:59:01 raspberrypi dnsmasq[32276]: forwarded duckduckgo.com to 208.67.222.222
Jul 27 21:59:01 raspberrypi dnsmasq[32276]: forwarded duckduckgo.com to 208.67.220.220
Jul 27 21:59:01 raspberrypi dnsmasq[32276]: forwarded duckduckgo.com to 1.1.1.1
Jul 27 21:59:01 raspberrypi dnsmasq[32276]: forwarded duckduckgo.com to 1.0.0.1
Jul 27 21:59:01 raspberrypi dnsmasq[32276]: reply duckduckgo.com is 184.72.104.138
Jul 27 21:59:01 raspberrypi dnsmasq[32276]: reply duckduckgo.com is 23.21.193.169
Jul 27 21:59:01 raspberrypi dnsmasq[32276]: reply duckduckgo.com is 107.20.240.232

As you can see, DNSMasq is not sending its non-local requests to my router anymore; those requests are now being sent to the upstream DNS servers I’ve just added to the config file. It’s safe to make the switch at this point.

DNSMasq forwarding to other nameservers (not the router's).

Configure your router to use DNSMasq

Unless you’re dealing with an extremely locked-down router, you should be able to configure it to use DNSMasq as its primary DNS server.

Router's DNS settings.

That covers everything you need to do on the router’s side.

The final test

It’s time for the final and most important test. Before anything else, restart your personal computer’s network stack. If you’re on a Linux distro that comes with NetworkManager, you can run the following command:

# From personal/development machine
sudo systemctl restart network-manager.service

This will refresh your personal computer’s network configurations to match the ones we updated in the router. If you dig again, this time without specifying a DNS server (@ option), you should get responses for domain names both on the Internet and in the local network:

# From personal/development machine
$ dig +short duckduckgo.com
184.72.104.138
23.21.193.169
107.20.240.232

# Logs on RPi
Jul 28 13:56:53 dnsmasq[18726]: query[A] duckduckgo.com from 192.168.0.10
Jul 28 13:56:53 dnsmasq[18726]: forwarded duckduckgo.com to 1.1.1.1
Jul 28 13:56:53 dnsmasq[18726]: reply duckduckgo.com is 184.72.104.138
Jul 28 13:56:53 dnsmasq[18726]: reply duckduckgo.com is 23.21.193.169
Jul 28 13:56:53 dnsmasq[18726]: reply duckduckgo.com is 107.20.240.232

Note how that query is being forwarded to one of the upstream servers we specified earlier (1.1.1.1). Now try with a domain that should be resolved by DNSMasq itself:

# From personal/development machine
$ dig +short sherlock.home
192.168.0.200

# Logs on RPi
Jul 28 13:05:02 dnsmasq[16753]: query[A] sherlock.home from 192.168.0.10
Jul 28 13:05:02 dnsmasq[16753]: /etc/hosts.home sherlock.home is 192.168.0.200

For the local domain query, DNSMasq was able to find the entry (sherlock.home) inside its /etc/hosts.home file.

DNSMasq serving local and non-local requests.

Recapitulation

Once you’re happy with the changes and checked that everything is working consistently, consider disabling query logging. Your final configuration file (/etc/dnsmasq.d/home.conf) should look something like this:

# log-queries ## Uncomment to enable query logging
log-facility=/var/log/dnsmasq.log
addn-hosts=/etc/hosts.home
no-resolv
server=1.1.1.1
server=1.0.0.1
server=208.67.222.222
server=208.67.220.220

Other solutions

If you still find that this is too much work just to get DNS resolution inside the LAN, you can always shop around for more capable (and expensive) routers. Another alternative is to flash tailor-made firmware like dd-wrt or Tomato into the router itself. Not surprisingly, both dd-wrt and Tomato come pre-packaged with DNSMasq.

Conclusion

I find that installing DNSMasq on an RPi is a very modular and non-intrusive way of adding DNS resolution to a local network. Once you get this setup running, you can assign domain names to servers and other devices in your LAN and not worry about memorizing all their IP addresses any more.


  1. That’s what I’ve heard from a few people, but I couldn’t really confirm that Bind eats up more CPU resources than DNSMasq. [return]
diy  dns  dnsmasq  hardware  home  rpi  sbc  soho