Linux Router Part 1: Routing, NAT, and NFTables
Introduction
A few years ago, Jim Salter wrote a number of articles for Ars Technica related to his “homebrew routers“.
Much of what he wrote then still stands, but time marches on, and when I rebuilt my home router, I wanted to translate his lessons to a modern Ubuntu installation and the more approachable nftables syntax.
The hardware
Any old thing with a couple of network interfaces will do fine. In my case I already had a nice machine for the purpose; a solid state 4-NIC mini PC from Qotom.
The goal
What I wanted to achieve was to replicate my current pfSense functionality with tools completely under my control. This includes being able to access the Internet (router), convert human-readable names into IP addresses and vice versa (DNS), and automatically assign IP addresses to devices on my networks (DHCP) – all of these of course are standard functionality you get with any home router usually provided by your ISP. Since I run some web services from home, I also need to allow select incoming traffic to hit the correct server in my house.
Base installation
I chose the latest LTS release of Ubuntu server for my operating system. Other systems are available, but this is an operating system in which I’m comfortable. The installation is mostly a matter of pressing Next a lot, with a couple of exceptions:
First of all, there’s a network configuration screen that fulfills an important purpose: Connect your network cable to a port in the computer and take note of which logical network interface reacts in the user interface. In my case the NIC marked 1 (which I intended to use for my Internet connection or WAN) is called enp1s0, and Interface 4 (which I intended to use for my local network or LAN) is called enp2s0. This will become important further down.
Second we want to make sure to enable the Secure Shell service already here in the installer, to allow remote access after the router goes headless.
After installation has finished, it’s good practice to patch the computer by running sudo apt update && sudo apt upgrade
and then rebooting it.
Basic network configuration
The first thing to do after logging in, is to configure the network. The WAN port usually gets its address information automatically from your ISP, so for that interface we want to enable DHCP. The LAN port on the other hand will need a static configuration. All this is configured using Netplan in Ubuntu. The installer leaves a default configuration file in /etc/netplan
, so let’s just edit that one:
/etc/netplan/00-installer-config.yaml
At this point it’s worth noting that if you already have something on the IP address 10.199.200.1 the two devices will fight it out and there’s no telling who will win – that’s why I chose an uncommon address in this howto.
To perform an initial test of the configuration, run sudo netplan try
. To confirm the configuration, run sudo netplan apply
.
A router will also need to be able to forward network packets from one interface to another. This is enabled by telling the kernel that we allow this functionality. By editing /etc/sysctl.conf
we make the change permanent, and by reloading it using sysctl -p
we make the changes take effect immediately.
(Bonus knowledge: The effect of the sed commandline below is to inline replace (-i) the effects of substituting (s) the commented-out string (starting with #) with the active one. We could edit the file instead – and if we don’t know exactly what we’re looking for that’s probably a faster way to get it right – but since I had just done it I knew the change I wanted to perform.)
Great, so our computer can get an IP address from our ISP, it has an IP address on our local network, and it can technically forward packets but we haven’t told it how yet. Now what?
Router
As mentioned, routing functionality in this case will be provided by nftables:
sudo apt install nftables
This is where things get interesting. Here is my current /etc/nftables.conf
file. This version is thoroughly commented to show how the various instructions fit together.
/etc/nftables.conf
To enable the firewall, we’ll enable the nftables service, and load our configuration file:
To look at our active ruleset, we can run sudo nft list ruleset
.
At this point we have a working router and perimeter firewall for our network. What’s missing is DHCP, so that other devices on the network can get an IP address and access the network, and DNS, so that they can look up human-readable names like duckduckgo.com and convert them to IP addresses like 52.142.124.215. The basic functionality is extremely simple and I’ll detail it in the next few paragraphs, but doing it well is worth its own documentation, which follows.
DNS
The simplest way to achieve DNS functionality is simply to install what the Internet runs on:
DHCP
We’ll run one of the most common DHCP servers here too:
DHCP not only tells clients their IP address, but it also tells them which gateway to use to access other networks and it informs them of services like DNS. To set up a basic configuration let’s edit /etc/dhcp/dhcpd.conf
:
/etc/dhcp/dhcpd.conf
Load the new settings by restarting the DHCP server:
And that’s it, really. Part 2 describes how to make DNS and DHCP cooperate to enhance your local network quality of life.