My home firewall router with FreeBSD—A year retrospective
2011-06-30 15:06
A little over one year ago, I purchased a Seagate Dockstar on woot.com. Everything about it was irresistible. The price, $25. The efficiency, a 7 watt draw. The power, a 1.2ghz ARM. I hoped to turn it into a firewall router to replace my aging, under-powered WRT54G.
One year later, I’ve rolled an operating system and written several times about my little ARM box, and it lived up to every hope I had.
Operating System
The firmware in NAND was a non-starter, incapable of much beyond serving files and connecting to the PogoPlug service.
Plugbox Linux, now known as Arch Linux ARM, already existed, but Arch has never been a favourite environment of mine.
Dissatisfied, I turned toward my go-to OS, FreeBSD. The wiki proclaimed support for Marvell Kirkwood, so I cracked open the Dockstar, added a serial console connection, and set to work. A week later, I released patches for FreeBSD 8.1 to make it play nice on the little white device. I used it lightly for a few months determining what worked what didn’t, fixing things along the way. Those changes paved the way for an extremely solid 8.2 release earlier this year.
As a Router
I don’t demand much of a home router, just 100%1 uptime and reliability along with firewalling, IPv4 NAT, IPv6, NTP, DNS, DHCP, and uPNP.
The first glaring limitation of the Dockstar is its single LAN port, correctable with a USB ethernet controller. I have two 10/100 controllers, one USB 1.1 and one USB 2.0, supported by aue and axe, respectively. USB 1.1 negates 100Mbit operation of the aue controller, because in practice it has trouble reaching 10Mbit.
In this example, ue0 is my USB WAN interface, and mge0 is the gigabit LAN interface. I’ve chosen 172.16.0.0/12 as my private IPv4 address range.
We have cable Internet through Comcast, so the WAN link is configured as DHCP. I use SYNCDHCP, which halts the boot process until ue0 gets an IP. PF will fail to initialize at boot otherwise.
IPv6 connectivity
My ISP doesn’t offer IPv6 service, but it’s possible to get connected through a tunnel broker such as Hurricane Electric or SixXS. Either service provides a tunnel to get IPv6 traffic over an IPv4-only WAN link as well as a subnet of IPv6 addresses for machines on the LAN. I went with SixXS, because their AICCU daemon updates my tunnel endpoint address automatically if my IPv4 WAN IP changes. Just enable IPv6, assign the LAN interface a v6 address from the assigned subnet, create a gif device, set it as UP at boot, and let AICCU do the rest:
PF
PF is a stateful packet filter imported from OpenBSD. In my setup, it acts as NAT for IPv4 clients and as firewall for both IPv4 and IPv6 clients.
ftp-proxy
FTP is troublesome. To make it work through PF, enable ftp-proxy (part on the base system, no Port installation needed) and add anchors for it to the rdr and filter sections of pf.conf, as shown below.
UPnP
UPnP allows services inside the LAN to automatically configure port forwards for themselves. It’s a handy way to set up things like P2P applications and games consoles. My home network is a pretty relaxed environment, but don’t enable UPnP if you’re not keen on letting things punch arbitrary holes in your firewall.
Otherwise, grab miniupnpd from Ports or from my pre-built package:
And add anchors for it to the rdr and filter sections of pf.conf, as shown, then edit miniupnpd.conf to suit your environment.
pf.conf
pf.conf is PF’s configuration file. It holds rules that define how the firewall behaves, such as what ports or services are blocked or allowed and what interfaces get NAT.
Instead of explaining it line-by-line, here’s a well-commented working example from my firewall:
IPv4 DHCP
Grab isc-dhcp41-server. It will be used to hand out IPv4 addresses on the network.
Generate a DNSSEC key
Using the dnssec-keygen utility, generate a key that DHCPD will use to authenticate with named for dynamic DNS. That means when a machine gets an address from DHCPD, the daemon will update named’s zone files with forward and reverse DNS for the new host. You can name the key anything you please. I called mine dhcp-ddns.
It will spit out two key files. Grab the Base64-encoded key from either file. It will look like a random string of characters.
In this example there are two defined DNS zones. One forward, aloe.cooltrainer.org., and one reverse, 0.16.172-in-addr.arpa.
My dhcpd.conf has some defined hosts. These systems can use DHCP for ease of configuration but will always get the same address.
DNS
BIND named is part of the base system, version 9.6 as of FreeBSD 8.2. Most of my configuration file is based on the examples from the FreeBSD handbook with the addition of my DDNS key, my two zones, and some upstream DNS servers (Comcast, Google) to forward and cache.
Named is run jailed by default, so its configuration file can be found in /var/named/etc/namedb/.
Once dhcpd and named are both configured, DHCP clients should get forward and
reverse DNS records.
Using DNS this way simplifies home networking. One can set a mount point for a hostname, for example, so the actual IP address doesn’t matter and can change.
Stateless IPv6 autoconfiguration (SLAAC)
Now, we need a way to hand out IPv6 addresses from our subnet to clients on the LAN. The easiest method is rtadvd, the router advertisement daemon, inherited in BSD from the KAME project.
rtadvd is very simple to configure. The only piece of required information is your IPv6 subnet, 2001:4830:1632::/64 in my case.
Stateful IPv6 autoconfiguration (DHCPv6)
SLAAC is very easy to set up and use, but it can’t interact with named like DHCP can. Luckily, ISC-DHCP supports DHCPv6 as of version 4.0. I’ve yet to set this up, but I will update this section of this post when I do.
Network Time
I prefer OpenBSD’s OpenNTPD to the ntpd in FreeBSD’s base system. Install it.
Its configuration, then, is incredibly straightforward. It listens on localhost and on mge0. I use servers from the pool.ntp.org project.
The -s argument allows for a large time correction with openntpd instead of using ntpdate or rdate. This is important on the Dockstar, because it has no real time clock hardware and needs a large time jump at boot.
Inadyn is a small utility to inform afraid.org when our IP changes.
Wireless
My old WRT54G was re-purposed as a wireless access point for this project, using a BrainSlayer build of DD-WRT r14929 (dd-wrt.v24_nokaid_generic.bin) that supports IPv6. It’s configured with a static IP for itself and forwards DHCP and SLAAC advertisements to wireless clients with the configuration described here.
Wrap-up
Everything on the LAN should now receive an IPv4 and an IPv6 address, get network time, get cached DNS, and have no trouble opening ports through the IPv4 NAT.
Here’s a sample of the new machine’s throughout and packet-pushing performance while running several dozen well-seeded torrents at once, generated with pfstat. Only IPv4 is shown, since there weren’t enough fellow IPv6 hosts to make the test worthwhile. Our connection is rated for 12Mbit down, 3 up, hence the hard cutoff near 12Mbit/s.
It didn’t let me down!
My config files for Aloe are stored in Aloe’s GitHub repo. Check them out if you need an example!