I set out to run FreeBSD on my Beagle Bone Black (now dubbed "smurf" by the kids on account of it's small and blue), for network services. My DSL modem is a crappy under-configurable thing, but I don't dare to start hacking on it directly because it runs the telephony side of things, too. So I decided to use the Beagle Bone Black to take control of my home network.

The modem is, of course, responsible for the actual internet connection. Its internal IPv4 address is 192.168.0.1, and it functions as the router for the whole house. Smurf is wired to one of the LAN ports on the modem, and it lives at 192.168.0.2. There's a switch wired to another LAN port on the modem, and the rest of the wired network lives behind that switch. Wifi (still) goes through the modem. I might change that too, since the range is lousy compared to my old WRT 54GL. It looks something like this:

Network diagram described in text Network diagram showing services and hostnames

Inside the network, there are a couple of machines with specific roles. There's the printer, and the media server, and the NAS, and the poudriere builder, and a couple more. Most of these machines run services with some form of autodiscovery -- like the printer and the media server -- but not all of them. And there's the configuration aspect of each: not only do I need to be able to find the printer's IPP service, I may also need to reach the printer's configuration webpage. It's most convenient when I can put http://printer/ into a browser anywhere in the house and get the webpage for exactly that.

There's probably plenty of solutions for this, but the one I settled on is somewhat old-school: control over DHCP and over DNS, with DHCP handing out fixed addresses in-house to the machines that need it (e.g. the printer is always 192.168.0.10) and DNS set to something I control that can respond with printer.example.com. 3600 IN A 192.168.0.10. For the non-special machines -- random desktops, laptops, phones -- I don't need any kind of in-house naming, those machines just need an address.

Starting DHCP. On the modem side, I had to change two settings: DHCP server off, and DHCP relay on (to the fixed IP address for smurf). Until I got DHCP up and running on smurf, this effectively killed new wireless connections and wired connections that weren't set to a fixed address, (this was mostly annoying for the kids on their phones).

Smurf is configured with a fixed address, and fixed routing to the outside world, and nameservers as provided by my ISP (I suppose I could add some public DNS there too -- in the example configuration below, that's Google). I used the isc-dhcp41-server package that I'd previously built with poudriere. Basic configuration was really simple (in /usr/local/etc/dhcpd.conf):

option domain-name "example.com";
option domain-name-servers 8.8.8.8;
authoritative;
subnet 192.168.0.0 netmask 255.255.255.0 {
  range 192.168.0.128 192.168.0.250;
  option routers 192.168.0.1;
}

A little testing shows that yes, the DHCP server is handing out addresses. I left a non-daemonized, verbose logging version of the server running while testing the rest.

Fixed DHCP. Next up is assigning fixed addresses to my special machines. Before starting on this, I had them all configured with fixed IPv4 addresses, so they all need to be changed to use DHCP, and on the DHCP side of things, their MAC addresses need to be associated with a fixed address. The easiest way to gather the necessary information seemed to me to be a combination of nmap(1) and arp(8) -- use nmap to ping the whole network, then get the MAC addresses out of the ARP cache:

$ nmap -sn 192.168.0.0/24
$ arp -a

For each special host, I added a host block to the DHCP server configuration, like so:

host printer {
  hardware ethernet e8:9a:db:db:db:db;
  fixed-address printer.example.com;
}

Note that I'm using a hostname in this configuration, so I added a line to /etc/hosts as well to fix the IPv4 address for that name. That's needed as long as I don't have DNS set up to resolve hostnames "printer" and "printer.example.com" to the desired address.

Starting DNS. FreeBSD has recently switched to shipping unbound(8) with the base system. Previously you could use BIND(8) or unbound(8) from ports. Unbound is much smaller and lighter, especially for the home-network situation. I can use it as DNS for the whole home network, and it will cache requests -- but it also gives me control over the naming inside the house.

I followed an Unbound DNS tutorial, which was pretty comprehensive. On FreeBSD it's even easier: set up the right nameservers in /etc/resolv.conf, then add local_unbound_enable="YES" to /etc/rc.conf, and start the local_unbound service. A configuration file is generated and unbound(8) is started. The unbound.conf example under Authoritative, validating, recursive caching DNS setup and install in that tutorial is pretty much what I ended up using.

Unfortunately, the auto-setup of unbound(8) seems to leave two things out: it runs unbound(8) in a chroot where there's no /dev/random, and then everything comes to a DNS-crashing halt. It took me a while to figure that out even though it's mentioned in the documentation. I ended up just adding it to /etc/fstab:

devfs   /etc/unbound/dev        devfs   rw      0       0

After that, unbound started complaining about its DNSSEC trust root file. I couldn't quickly figure out what that was about, so I ended up just disabling the auto-trust-anchor-file in the configuration. The config files are spread out a bit, but here's the most important bits:

# Server listens on the local network and allows all queries
server:
        interface: 127.0.0.1
        interface: 192.168.0.2
        access-control: 127.0.0.0/8 allow
        access-control: 192.168.0.0/24 allow
        unblock-lan-zones: yes
        domain-insecure: 168.192.in-addr.arpa.
        domain-insecure: 127.in-addr.arpa.
# Forward to Google's public DNS
forward-zone:
        name: "."
        forward-addr: 8.8.8.8

After a few tests with host(1) -- which initially all returned SERVFAIL until I had the random-number thing sorted out -- I was confident I could use this from smurf and from hosts in the local network as DNS. So the next step was to switch the option "domain-name-servers" in the DHCP configuration over to unbound(8) running on smurf.

Fixed DNS. The last step in this setup is to associate the names for machines on the local network with the local IPv4 addresses I've chosen for them. This is done through local-data and local-data-ptr records in the unbound(8) configuration. FreeBSD has an /etc/unbound/conf.d/ directory to drop configuration files into, so I added a local.conf with the local network definitions:

server:
        local-zone: "example.com" static
        local-data: "smurf.example.com.     IN A 192.168.0.2"
        local-data: "printer.example.com.   IN A 192.168.0.10"
        local-data-ptr: "192.168.0.2  smurf.example.com"
        local-data-ptr: "192.168.0.10  printer.example.com"

Once that is done, then all the hosts in my local network that use DNS from smurf -- and that means all of them that use DHCP -- can use "smurf" as a hostname, and they'll resolve 192.168.0.2, and reach the Beagle Bone Black.

With these two services in place, I can take each machine in the house and switch it to use DHCP, knowing that the ones that need a fixed address will still get one -- and better yet, that they now have a useful name from all over the local network -- and that the rest will have the names of the fixed-address machines available.

Musings. Conceptually, I'm working with configuration triplets: a particular MAC address has a particular name and designated IPv4 address. In the configuration, though, the triplet gets scattered across a number of configuration files, each in a different format. The issue expands with DNS, since then I have to repeat some of the information from /etc/hosts. Let's call the elements of these triplets (M, N, A). I could make a small table of them. But getting that information into the right places is annoying, since the data gets spread out like this (with a bunch of hand-wavey notation):

  • N, A -> /etc/hosts format "${A} ${N} ${N}.example.com",
  • N, A -> /etc/unbound/conf/local.conf format "local-data: '${N}.example.com. IN A ${A}" (but it has to be at the right spot in the file),
  • N, A -> /etc/unbound/conf/local.conf for the local-data-ptr line,
  • M, N -> /usr/local/etc/dhcpd.conf for the MAC-to-IPv4 mapping.

For my small home network, as long as I don't go crazy buying devices that need a fixed address and name, the management burden is small. But it cries out for some kind of automation, so that I only need to write down the essentials somewhere once.