Tuesday, March 1, 2016

How to create a SOHO router using Ubuntu Linux

On Security Weekly Episode 452, I presented a technical segment on how to build your own small office / home office wired router.   This blog post will list of the essential components, and expand upon the technical segment.   Our goal is to build a multi-segment wired router that performs Network Address Translation (NAT) with IPv4, runs Internet Software Consortium (ISC) Bind9 for domain name service, and ISC DHCP services to deliver IP addresses on the inside of your network.   

NOTE: Supporting configuration files associated with this blog post can be found at https://bitbucket.org/jsthyer/soho_router.

From a hardware standpoint, you can choose any dual NIC or higher computer that will support an Ubuntu 14.04.4 LTS server installation.   I would recommend a minimum of 1024MB (1GB) of RAM, and 16GB of hard disk space.   Some hardware that I have found useful includes the Soekris Net6501 ( http://soekris.com/products/net6501-1.html), or the Netgate RCC-VE 2440 (http://store.netgate.com/ADI/RCC-VE-2440.aspx).

The starting point for building the router is to install Ubuntu-14.04.4 LTS server (64-bit), and then install the following additional packages:
  • apt-get install bind9
  • apt-get install isc-dhcp-server
  • apt-get install ntp
The next and very important step is to ensure that IP forwarding is turned on in your kernel.   If you don’t do this, you don’t route any packets and the game is over.  In order to enable IP forwarding, please add the following lines to the bottom of the /etc/sysctl.conf file, and reboot your system.  Note that while we at changing the system configuration, we will disable IPv6 since you are probably not using it.


The core of the configuration for a router is to make sure that your network interfaces are configured properly, and that your IPTABLES configuration is setup to properly translate, and forward traffic to the Internet.

Network Interface Configuration

Starting with network interfaces, we will assume that your public Internet address can either be static or obtained via DHCP.   We will assign the Linux network interface “eth0” to be the Wide Area Network (WAN) connection to your Internet Service Provider.   Just for demonstration purposes, we will assume a static Internet address of and a network mask of /30.   Your ISP’s device will be assigned   Your public network subnet mask is calculated using the following math:  subnet mask = 2^32 - 2^(32-30) ⇒ in dot quad notation.  We will also assume that you have a total of four network interfaces on your router device which will yield up to three internal network segments.

Listed below is the top section of what will be the /etc/network/interfaces file.  This not only contains the “eth0” definition, but also contains some addition security features in the form of “null routes” for any RFC1918 network traffic that appears with a shorter prefix than the connected interfaces, and also routes multicast ( to the bit bucket.  If you need to use DHCP for your Internet public address, you can un-comment the marked entries for the “eth0” interface that starts with “using dhcp”, and comment out the static address part.   One more aspect is that the iptables rules are expected to be listed in /etc/iptables.rules.  More about this later in the article.


Now we need to establish what the internal / inside interfaces of our network look like.  For simplicity, we will use class C (/24) networks and assign them the addresses,, and respectively.   This is how you configure the remainder of the /etc/network/interfaces file to reflect this.


IPTABLES Rules Configuration

As listed in the network interfaces configuration file, we are going to create the file /etc/iptables.rules, and depend upon the networking code to load the configuration when the system boots.   We can also test our iptables configuration at any time using the “iptables-restore” command.    The IPTABLES configuration is broken into two sections, these being the Network Address Translation, and the Filtering section.   In short, performing Network Address Translation with IPTABLES is a one liner.   In this example, we assume that the internal network is addressed in the range, and that the public Internet Protocol address (WAN interface) is configured on “eth0”.   As a bonus, and if you want to run the Squid web proxy, there is a line to rewrite traffic on internal network segments destined to TCP port 80 to the standard Squid TCP port of 3128.

NAT section of /etc/iptables.rules

Having created the NAT section of the iptables ruleset, you are still required to create the filtering rules to determine what is going to ingress and egress your actual gateway router system, as well as determine what traffic will forward across your router.   

I am going to break the filter section of the IPTABLES rules down into multiple different parts of this article, these being:
  1. Traffic being received by the router (INPUT)
  2. Traffic being sent by the router (OUTPUT)
  3. Traffic being forwarded across the router (FORWARD)
  4. Traffic being logged by the router (LOG_DROPS)

We will start the filtering section of the IPTABLES configuration by adding a “LOG_DROPS” chain to the rule set.  This will allow us to write logs on any traffic that is dropped.   After that, we will implement some common sense network protections for the router itself which include:
  • dropping any traffic to “eth0” that sources from
  • dropping any traffic to “eth0” that sources from RFC1918 addresses
  • dropping any traffic to “eth0” that sources from a multicast address (
  • dropping fragmented IP traffic
  • dropping ingress packets that have an IP TTL less than 4.
  • dropping any packets destined to TCP/UDP port 0.
  • dropping any packets with all or no TCP flags set

Starting portion of “filter” section.  Common sense protections.

In the next part of the INPUT section, we are defining the following rules for the router to receive traffic as follows:
  • Accept all traffic to the Loopback interface.  A lot of software will use Loopback for internal communications and it is better to not break things.
  • Accept traffic for the Domain Name Service (DNS) bind9 server on any interface.  This is needed because we are running bind9 on the router itself, and we might likely decide to host some of our own DNS zones.
  • Accept specific traffic from our internal network.  This includes DNS, DHCP server requests, network time protocol, and Squid traffic (if you choose to run Squid).
  • Accept internet control message protocol (ICMP).

Packet input/ingress (to router) section of “filter” section

In the OUTPUT section, we need to the router to forward all traffic to the Loopback interface, and then we need to define rules for the router itself to transmit to the internal network, and the Internet as follows:
  • Transmit DNS traffic to any host on any network.
  • Allow the router to perform “WHOIS” queries on TCP port 43, and allow for Ubuntu software updates across HTTP/HTTPS.
  • Allow the router to perform Network Time Protocol queries.
  • Allow the router to transmit DHCP INFORM packets on the internal network.
  • Allow the router to transmit ICMP packets on the internal network.

Packet output/egress section (from router) of “filter” section

Now we accept state related packet flows, and then drop and log anything else

The FORWARD section of the IPTABLES rules determines exactly what traffic is able to flow (be forwarded) across your router.  It is important to not confuse this section with the INPUT/OUTPUT portions of the rules.   The FORWARD section is where the magic happens to get packets from your internal network to the Internet.   In this example, we have a fairly liberal policy which allows all IPv4 TCP, UDP, and ICMP traffic to the Internet and accepts any state related traffic.

Packets that will be forwarded across the router interfaces

As a final step in our configuration, we log all dropped packets to the syslog LOCAL7 facility.  The idea being that we can configure “rsyslog” with a rule that matches this prefix and writes the logging data to a file.

Finally, we log things by prefixing “iptables:” to the syslog data flow

For extra information, here is the “rsyslog” configuration file I use to log the data.


DHCP, and DNS Services

Now we have covered the essential core components of forwarding packets, we can talk about DHCP and DNS.   Starting with DHCP, what we need to do is provide basic IP address service on our three internal network segments.  On each segment, we will start with a lower address at x.x.x.50 so we can reserve a little static address space for other miscellaneous uses.  We will also set up lease times for 30 days (30 * 86400 seconds).   Addresses will be provided on all three internal network interfaces (eth1, eth2, and eth3).  This file is to be saved as “/etc/dhcpd/dhcp.conf”.


With regard to bind9 (DNS services), the default Ubuntu installation will yield a caching name server which utilizes the Internet root caching servers, and is sufficient for most purposes.  The extension some people may want to consider is to forward queries to a DNS filtering service (such as OpenDNS), and/or run some specific filtering on your own.   In my case, I leverage the “dshield” bad domain lists which as maintained by Johannes Ulrich of the SANS institute.   An example of how to configure bind9 to forward all queries to an upstream DNS server is listed below.

The configuration screenshot below is a modification to the “/etc/bind/named.conf.options” file to forward all queries to the upstream Google DNS server of, and to filter the networks able to perform recursive DNS queries.   Forwarding to an upstream server is completely optional, and if you choose this, a trusted DNS filtering service is advisable.   Filtering on what clients can make recursive queries should be considered as an essential part of the configuration.


As regards the “dshield” bad domains list, I have created a shell script called “get_malware_domains.sh” whose job it is to fetch the URL “https://isc.sans.edu/feeds/suspiciousdomains_Low.txt” and then convert that list into bind9 configuration file format.   An example of the configuration file format is as follows.

named.conf.dshield file

The concept is that any domain listed in this file will be resolved to the address “”.

The “db.blackhole” file contents.

All of the above descriptive text will also be supported by a small tar file containing some of the key file contents described here.   Happy hunting!