Poor Man’s device discovery (DNS)

Mike Green
Moqume Blog
Published in
7 min readAug 4, 2016

I have a home network that contains a mixture of devices, some of which that receive a static IP address such as the printer, and some of which receive a dynamic IP address such as mobile phones and tablets.

The home router is setup to give every device with a static IP address a host name, such as “printer.home” or “nas.home”, making it easy to access the device’s UI (if it has one). However, the router isn’t capable of assigning host names to devices with a dynamic IP address.

For the most part this isn’t an issue, but every once in a while I do need to access the mobile phone or tablet via the browser or similar. This means having to lookup the IP address of the device in the router, which in turns means I have to login to it and navigate through various screens.

So I thought: “why not give every device a host name / DNS entry?”. But with the router not capable of assigning them to dynamic IP addresses (based on MAC for example), or able to set the host name on some devices, how? Luckily I have a Raspberry Pi that’s sitting in a closet 24/7 doing very little, so I’ve put that to good use.

Dnsmasq

First things first, I wanted to keep DHCP services with the home router. DNS could be off-loaded with ease to Dnsmasq, a very lightweight DNS / DHCP / RA server, due to the option in the router that allowed me to specify which DNS server to advertise with DHCP requests. That is, whenever a device on my home network asks for an IP address via DHCP, it also receives details about which DNS server(s) to use.

Raspbian installation: sudo apt-get install dnsmasq

So after giving the Raspberry Pi (one of the early model B’s with 256 Mbytes of RAM) a static IP address, I’ve set the router to advertise that IP address for DNS lookups. So all devices in my home network will now use the Raspberry Pi for DNS lookups.

The DNS lookups are being handled by Dnsmasq, which I first configured to not use the /etc/resolv.conf file, because that file was auto-populated by the Raspberry Pi and self-referencing (so Dnsmasq wouldn't know what remote DNS servers to use for resolving external domains). I did this by removing the comment on the following line in the /etc/dnsmasq.conf file:

# If you don’t want dnsmasq to read /etc/resolv.conf or any other
# file, getting its servers from this file instead (see below), then
# uncomment this.
no-resolv

Obviously I could have simply configured a static IP address in the Raspberry’s /etc/network/interfaces file, but I wanted to keep it at DHCP for sake of being able to move it around places (i.e., at a hotel or a friend's place).

The next step was to set what remote DNS servers Dnsmasq had to use for external domain lookups. One could use Google’s public DNS for example:

# Add other name servers here, with domain specs if they are for
# non-public domains.
#server=/localnet/192.168.0.1
server=8.8.8.8
server=8.8.4.4

The next step was to get Dnsmasq to somehow associate all my devices in the home network with a host name. Looking at the above bit of configuration, I could add something like:

server=/home/192.168.1.1

Where 192.168.1.1 is the IP address of the router. This means host names with the .home extension would be resolved by the router but, as mentioned, the router could only do so for devices with a static IP address, thus defeating the purpose.

ARP-scan

So I needed a way to 1) find all the devices on my home network and 2) assign a host name to known devices regardless of IP address and then 3) pass this information on to Dnsmasq. Lucky me, there’s a rather simple solution to this than might seem. Enter arp-scan, the tool that sends out ARP packets and outputs the responses received.

Raspbian installation: sudo apt-get install arp-scan

The beauty of an ARP scan, other than very quickly finding all the devices in your local network, is that it also returns the MAC address of the device, which remains pretty much constant regardless of the IP address assigned to it. This allows me to use the MAC address to associate it with a host name.

What’s even better, arp-scan uses a MAC vendor mapping file to figure out who made the device based on the MAC address’ prefix. By default it uses the files in the /usr/share/arp-scan/ directory, but you can also specify your own using the --macfile=<m> or -m <m> option. This vendor mapping file uses a very simple format, as explained in one of the files:

# mac-vendor.txt — Ethernet vendor file for arp-scan
#
# This file contains Ethernet vendor mappings for arp-scan. These are used
# to determine the vendor for a give Ethernet interface given the MAC address.
#
# Each line of this file contains a MAC-vendor mapping in the form:
#
# <MAC-Prefix><TAB><Vendor>
#
# Where <MAC-Prefix> is the prefix of the MAC address in hex, and <Vendor>
# is the name of the vendor. The prefix can be of any length from two hex
# digits (one octet) to twelve hex digits (six octets, the entire Ethernet
# hardware address).
#
# For example:
#
# 012345 would match 01:23:45:xx:xx:xx, where xx represents any value;
# 0123456 would match 01:23:45:6x:xx:xx; and
# 01234567 would match 01:23:45:67:xx:xx.
#
# …truncated…
#
# The alphabetic hex characters [A-F] must be entered in upper case.
#
# The order of entries in this file are not important.
#
# arp-scan will attempt to match larger prefixes before trying to match
# smaller ones, and will stop at the first match.
#
# Blank lines and lines beginning with “#” are ignored.
#
# Additional information is available on the arp-scan wiki at
# http://www.nta-monitor.com/wiki

This allowed me to create a file that contains the full MAC address and the host name it should have. I created a file /etc/mac-dns.txt that contains entries such as:

B82734FAB128 raspberrypi.home

where the first portion is the MAC address as shown with ifconfig:

~# ifconfig eth0 | grep HWaddr
eth0 Link encap:Ethernet HWaddr b8:27:34:fa:b1:28

I would then specify this file as the MAC vendor mapping file with arp-scan, which gives me output similar as following:

~# arp-scan -l -m /etc/mac-dns.txt
Interface: eth0, datalink type: EN10MB (Ethernet)
Starting arp-scan 1.8.1 with 256 hosts (http://www.nta-monitor.com/tools/arp-scan/)
192.168.1.197 b8:27:34:fa:b1:28 raspberrypi.home
…(truncated output)…14 packets received by filter, 0 packets dropped by kernel
Ending arp-scan 1.8.1: 256 hosts scanned in 2.584 seconds (99.07 hosts/sec). 11 responded

Note I also use the -l option in arp-scan (equivalent to --localnet), so that arp-scan only performs a scan of the local network. That's #1 and #2 of the 3-point requirement done! Now to feed this into Dnsmasq...

Putting it together

Now that I could quickly gather information about the devices in my home network and the host name they should be using, I needed a way to feed this into Dnsmasq, so that DNS lookups for these devices would work. Once again luck was on my side, as Dnsmasq offers the option for providing additional host files. I edited the /etc/dnsmasq.conf file accordingly to read a file called /etc/hosts.home:

# or if you want it to read another file, as well as /etc/hosts, use
# this.
#addn-hosts=/etc/banner_add_hosts
addn-hosts=/etc/hosts.home

As you may know, the /etc/hosts file simply consists of an IP address followed by the host name. Coincidentally, arp-scan prints its output nearly as it should — I just needed to remove the 2nd column with the MAC addresses and the footer / headers. That’s easy:

arp-scan -l -m /etc/mac-dns.txt | head -n-3 | tail -n+3 | cut -f1,3-

Now I could use the output of arp-scan to send it to the /etc/hosts.home file, and signal Dnsmasq to reload the file once I've done so. A cron entry for this looks like:

*/15 * * * * arp-scan -l -m /etc/mac-dns.txt | head -n-3 | tail -n+3 | cut -f1,3- > /etc/hosts.home && pkill -SIGHUP dnsmasq

So every 15 minutes the file is updated and, when successful, a SIGHUP signal is sent to the running Dnsmasq instance that forces it to reload (and also purge any cache, so to conserve resources try not to run the cron too often).

And then check if things actually work:

~# dig raspberrypi.home; <<>> DiG 9.9.5–9+deb8u6-Raspbian <<>> raspberrypi.home
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 13944
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;raspberrypi.home. IN A
;; ANSWER SECTION:
raspberrypi.home. 0 IN A 192.168.1.197
;; Query time: 8 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Thu Aug 04 22:25:23 CEST 2016
;; MSG SIZE rcvd: 61

So it does! No more digging around for IP addresses on my home network!

Thoughts

There are more elegant options out there, of course. You have mdns-scan / DNS Service Discovery, but it only supports devices that actually advertise themselves. Similarly, there are also agent-based solutions, particularly enterprise solutions meant for etcd (think Docker) or similar. But for a simple 123 = abc.home solution, this would suffice, particularly as it is very low on resources and essentially requires 10 minutes of work to set it all up.

--

--