Adventures in Rolling Your Own Router: Part I

I’ve been rolling my own router / firewall solution for several years now. I started out using OpenBSD, but even though I loved pf I eventually switched over to Arch Linux. Throughout all these years, I’ve been using PiHole installed on a Raspberry Pi 3 for DNS based ad-blocking.

My Arch based router is getting a little crufty, and my documentation is sparse (thanks, past me!), so I have decided now is a good time to start from scratch, documenting the entire process from start to finish.

The goal is to end up with a router / firewall running PiHole for DNS/DHCP and using native systemd services wherever possible. I would also like the total power drawn by my router / switch / access point to be reduced.

In this first post, I’ll describe my current system.

Hardware

I’ll list everything plugged into my APC UPS. The power draw of all these components will determine how long my WiFi stays online during a power outage.

The current power draw of all the components is about 34W. This gives me about two and a half hours of runtime.

Thermal Image of Components

Router

Power Supply

The Seasonic PSU I’m currently using replaced the older fanless Seasonic PSU when the latter developed some coil whine. The former does have a fan, but it hasn’t needed to run since I got it.

Motherboard

The motherboard is an MSI AM1 Mini ITX. I bought it on Newegg for $24.99. Apparently the price has since jumped to $297.00 - this board is probably worth less than I paid for it, so I don’t know why the price jumped so much.

Processor

It has an Athlon 5150 Kabini Quad-Core 1.6 GHz processor which has a TDP of 25W. The heatsink I use is massive overkill for this.

Network Interfaces

To provide dual Ethernet ports, I used an Intel PRO/1000. Aside from running hot, I’ve been extremely happy with this card.

RAM

I reused 8G of RAM from an old machine.

Disk

I reused a 128G SSD from an old machine.

Case

I picked the Cooler Master Elite 130 Mini-Itx case for its small form factor.

Fans

Although this build can run completely fanless without much problem, I use two Noctua fans to keep everything cool. There is one 120mm fan at the front blowing air over the CPU heatsink and through the PSU and one 80mm fan pointed directly at the heatsink on the network card.

I am a loyal Noctua fan-boy (groan) because they make quietest fans I’ve ever used. I’ve never had one fail on me, and the sickly brown color scheme has been transformed in my mind from a downside to a badge of honor.

Switch

Based on the heat output, this Buffalo 16 Port Smart Switch is using a lot of power. I’m only using 8 ports, so I will be able to replace this with a smaller 8 port switch that should hopefully reduce power consumption. Since I’m getting rid of the Raspberry Pi, I’ll still have one port left free in case I need to plug in anything else.

Access Point

The UAP-AC-LITE provides good signal throughout our entire house. It’s plugged in to a 24W PoE injector.

Modem

I own, rather than rent, an Arris Surfboard. I only pay for a 100 Mbps connection, so this device is plenty fast. My only complaint is that it runs extremely hot.

Thermal Image of Components

Raspberry Pi

A slightly older Raspberry Pi runs PiHole. It’s plugged in to a high quality USB power adapter.

Phillips Hue Hub

If our home network goes down, so does our ability to control our Phillips Hue lights. That’s really annoying, so I also have the Phillips Hue Hub plugged in to the UPS.

Software

Arch Linux

I chose Arch Linux for my router because it is the distribution I’m most comfortable with. I also like that it’s a rolling release because I know I’m always getting all the latest updates.

Most of my setup comes from the Arch Wiki pages Router and Simple Stateful Firewall.

The working parts:

The not so great things:

My iptables rules:

(Note: I seem to have port 53 open for UDP and TCP on both the internal and external interfaces. I think this is an error, as my DNS server should only be accessible from inside the LAN. I also shouldn’t have UDP port 67 open to the external interface. This is what I meant by “crufty”…)

# Generated by iptables-save v1.8.6 on Sat Jan 16 16:01:36 2021
*nat
:PREROUTING ACCEPT [69954:7342094]
:INPUT ACCEPT [2535:152263]
:OUTPUT ACCEPT [5496:362059]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o extern0 -j MASQUERADE
-A POSTROUTING -s 192.168.10.0/24 -o extern0 -j MASQUERADE
COMMIT
# Completed on Sat Jan 16 16:01:36 2021
# Generated by iptables-save v1.8.6 on Sat Jan 16 16:01:36 2021
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [60076:6351430]
:TCP - [0:0]
:UDP - [0:0]
:fw-interfaces - [0:0]
:fw-open - [0:0]
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate INVALID -j DROP
-A INPUT -p icmp -m icmp --icmp-type 8 -m conntrack --ctstate NEW -j ACCEPT
-A INPUT -p udp -m conntrack --ctstate NEW -j UDP
-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j TCP
-A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
-A INPUT -p tcp -j REJECT --reject-with tcp-reset
-A INPUT -j REJECT --reject-with icmp-port-unreachable
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -j fw-interfaces
-A FORWARD -j fw-open
-A FORWARD -j REJECT --reject-with icmp-host-unreachable
-A TCP -p tcp -m tcp --dport 22 -j ACCEPT
-A TCP -p tcp -m tcp --dport 53 -j ACCEPT
-A UDP -p udp -m multiport --dports 60000:61000 -j ACCEPT
-A UDP -p udp -m udp --dport 53 -j ACCEPT
-A UDP -p udp -m udp --dport 67 -j ACCEPT
-A fw-interfaces -i intern0 -j ACCEPT
COMMIT
# Completed on Sat Jan 16 16:01:36 2021
# Generated by iptables-save v1.8.6 on Sat Jan 16 16:01:36 2021
*raw
:PREROUTING ACCEPT [6386246:7485406871]
:OUTPUT ACCEPT [60076:6351430]
-A PREROUTING -m rpfilter --invert -j DROP
COMMIT
# Completed on Sat Jan 16 16:01:36 2021

PiHole

PiHole acts as the DNS and DHCP server. PiHole is some extra stuff on top of dnsmasq. Instead of dnsmasq.service, pihole-FTL.service is enabled.

Configuration includes the following dnsmasq entries:

addn-hosts=/etc/pihole/local.list
addn-hosts=/etc/pihole/custom.list
localise-queries
no-resolv
cache-size=10000
log-queries
log-facility=/var/log/pihole.log
local-ttl=2
log-async
server=208.67.222.222
server=208.67.220.220
interface=eth0
server=/use-application-dns.net/

dhcp-authoritative
dhcp-range=192.168.10.2,192.168.10.254,1h
dhcp-option=option:router,192.168.10.1
dhcp-leasefile=/etc/pihole/dhcp.leases
domain=crbj.io

local=/crbj.io/
expand-hosts
read-ethers

This treats any DNS lookups under the domain crbj.io as local queries that should not be forwarded, and ensures that a host like pihole also gets expanded to pihole.crbj.io. Furthermore, the /etc/ethers file is also read to find the MAC address -> hostname mapping.

By ensuring that every host on my network is listed in the /etc/ethers file and that there is a corresponding entry for each host in the /etc/hosts file, every host gets a persistently mapped IP address when it requests a DHCP lease. It’s possible to accomplish the same thing using dhcp-host entries in the dnsmasq config files, but I like using /etc/hosts and /etc/ethers better for aesthetics.

Furthermore, because the PiHole is also the DNS server, I get the added benefit of being able to do DNS lookups of any host from any other host on the network without having to modify any of the clients’ /etc/hosts files.

In my next post, I’ll describe my plan for the new router.