Home network improvements – Setting up a Firewall

This is the fourth blog post about my home network improvements series. I am sorry it is taking me so long to write all those posts, but each takes a lot of hours to write and I am balancing my life more towards family at the moment. I hope you can bear with me until the end.

Great Wall winding over the mountains
Walls need to adapt to their environment

In the previous post, we presented installed the OS and set up networking and routing.

We will now see how to add another very important feature the firewall.

  1. Router features list (published)
  2. Creating a basic router, defining the network and routing (published)
  3. Adding a firewall to our router (this post)
  4. Providing basic network services, DHCP and DNS (to be published)
  5. Testing the firewall (to be published)
  6. Extra services (to be published, could be splitted in more than one post)

So today’s post will present a simple but secure firewall installation.

As I have said in a previous article, I want to try out nftables instead of using iptables. But we will continue iterating on the previous post and use iptables instead one more time. I want to have a working router and then I can think of switching to nftables and solving integration with other tools.

A Basic Firewall

Firewall - Forbidden City Gateway
Firewall

We will use iptables command line to populate the firewall rules. As changing those rules from the command line is not persistent, a simple reboot will restore your OS in the previous configuration so if things do not workout or if we get locked out by a wrong rule, just reboot and restart to setup your firewall. Once we will be happy with the firewall, we will save the rule set and make it permanent.

For rules, we obviously do not want any traffic coming from the WAN to establish new connections inside our LAN or on our router. Only established connections should be allowed through, e.g. an HTTP response is allowed through the firewall so that we can browse the internet. We want some network services to still function, like ICMP or DNS messages to pass through the firewall. We do not want to filter the outgoing traffic for the moment, so everything from the LAN is allowed to reach the WAN.

I like to set default policies for the different iptables chains instead of relying on the last rule to do the policy for me. However, in order to avoid getting locked out, we will set those policies at the very end and always start by defining what is allowed. In order to define our firewall, we will work first with the main chains of the filter table (the default one). Mostly caring of incoming packets and IP forwarding rules.

As a reminder, the WAN interface was defined in the previous article as wan1, and the LAN interface as lan1.

Incoming packets – INPUT

When working on the INPUT chain, one must always keep in mind that it applies to all incoming packets relative to an interface. So for the LAN interface, this is incoming packets from our LAN, but from the WAN interfaces this is packets coming from the WAN, and so on. So in our incoming rules, we will need to distinguish between those coming from outside (lowest trust) and those coming from the inside (higher trust).

Thus let’s authorise all incoming packets on the loopback interfaces – coming from services on the router host – and from the LAN. This is of course when you feel that you can trust all machines on your LAN. Maybe in a future post I will provide better advises here.

$ sudo iptables -A INPUT -i lo -j ACCEPT
$ sudo iptables -A INPUT -i lan1 -j ACCEPT

Our firewall will be a basic stateful firewall, so we will use the conntrack module to analyse connection states. Such firewall requires usually more resources memory and cpu. Indeed, in order to keep track of connection states, the firewall will need to keep them in memory. The amount of memory depends a lot on architecture and kernel version. On my new router with Ubuntu 18.04.1, when tracking the maximum amount of connections – which is net.netfilter.nf_conntrack_max = 262144 – the memory should be less than 100MB. And of course managing this list is time consuming. We will see in a future blog post how it performs and how we can tweak it for better performance.

For the WAN interface, we want to accept certain packets only. We want to allow established connections, those are connections already created or which another rule allowed to create, so we consider them already authorised. We also want to accept new valid connections which are considered related to established ones, like ICMP messages related to a connection. We want all invalid traffic to be dropped.

$ sudo iptables -A INPUT -i wan1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
$ sudo iptables -A INPUT -i wan1 -m conntrack --ctstate INVALID -j DROP

I could not find which ICMP message are identified as related by conntrack, but during my testing it seems many of the ICMP traffic is handled as related. Therefore I assume we have most ICMP cases cover, especially for ICMP destination unreachable (type 3) – which also includes DF code or don’t fragment – and ICMP time exceeded (aka TTL expiration, type 11). So the only rule missing is with ICMP echo request (aka ping, type 8), we are going to allow it:

$ sudo iptables -N ICMP-RULES
$ sudo iptables -A INPUT -i wan1 -p icmp -j ICMP-RULES
$ sudo iptables -A ICMP-RULES -i wan1 -o wan1 -p icmp -m icmp --icmp-type 8 -m conntrack --ctstate NEW -j ACCEPT
$ sudo iptables -A ICMP-RULES -j DROP

Now we will add 2 new chains for managing the allowed UDP and TCP traffic. I do not know why using one chain for each type of traffic, it seems to be the recommend way to have better efficiency. That’s something on my vast todo list to understand this better. I’m using it because it is neater in my humble opinion.

$ sudo iptables -N WAN-SRVC-UDP
$ sudo iptables -N WAN-SRVC-TCP
$ sudo iptables -A INPUT -p udp -m conntrack --ctstate NEW -j WAN-SRVC-UDP
$ sudo iptables -A INPUT -p tcp --syn -m conntrack --ctstate NEW -j WAN-SRVC-TCP
$ sudo iptables -A WAN-SRVC-UDP -j DROP
$ sudo iptables -A WAN-SRVC-TCP -j DROP

If you have services on your router you wish to expose, you would add them now. For example, we will add the SSH connection service on the LAN interface. It is not necessary as we authorise everything from LAN, but this is just to illustrate how to use those chains.

$ sudo iptables -I WAN-SRVC-TCP 1 -i lan1 -p tcp --dport 22 -j ACCEPT

We are now going to add the last rule of our INPUT chain and it is to log all unfiltered packets and see how we can improve our firewall rules. This rule is temporary, and especially before applying the next rule you should check system logs if you see any legitimate message with “INPUT packet unfiltered”, if yes it means one or more of the above rules are incorrect (e.g. typos or wrong interface name, etc.). By legitimate message, I mean a message about for example your current remote SSH connection, you do not want to see it in your logs because when you apply the drop rule it means you will be cut out.

$ sudo iptables -A INPUT -m limit --limit 3/minute --limit-burst 3 -j LOG \
    --log-prefix "INPUT packet unfiltered: "

Before applying our last rule on the INPUT chain, make sure you have checked the logs for any mention of “INPUT packet unfiltered”. Use sudo journalctl -rb to see the logs since last boot in reverse chronological order, so newer first. If no such logs are visible then your are pretty safe. Now it is time to set the default policy for the INPUT chain:

$ sudo iptables -P INPUT DROP

IP forwarding packets – FORWARD

We now need to setup the forwarding chain to filter packets which would be otherwise forwarded between interfaces (see the routing chapter above).

We want to explicitely authorise all traffic from lan1 at destination of lan1 to be forwarded. This will be useful when we will have several network interface cards (NIC) attached to our lan1 bridge. It will allow traffic on this bridge. We also want all traffic from lan1 to wan1 to be forwarded.

$ sudo iptables -A FORWARD -i lan1 -o lan1 -j ACCEPT
$ sudo iptables -A FORWARD -i lan1 -o wan1 -j ACCEPT

Port forwarding is the ability for our router to redirect traffic directed to the wan1 interface on a specific port to a different destination (e.g. inside the LAN). Port forwarding is sometimes known as virtual server. It can be used to redirect incoming traffic from one port to the other on the same host, or to another host. Port forwarding rules will be identified by conntrack as DNAT.
In addition, connection which have been already accepted by another rule should be allowed to be forwarded.

$ sudo iptables -A FORWARD -i wan1 -m conntrack --ctstate DNAT,RELATED,ESTABLISHED -j ACCEPT

Now we can set our default policy for the FORWARD chain.

$ sudo iptables -P FORWARD DROP

Here is an example how to do port forwarding. Let’s consider that the host 192.168.9.2 on the LAN offers a certain web service on port 443 (HTTPS). We want to redirect incoming traffic on the router from port 8443 to the mentioned LAN host. The following rule will create the redirection. Usually it is not enough to work as packets arriving on the wan1 and redirected to the LAN host will need to be forwarded to the lan1 interface. But we do not need an explicit FORWARD rule which would allow that, we are using conntrack to monitor our communications and this communication is now marked as DNAT, and we already have a conntrack rule in our FORWARD chain which will handle this traffic.

$ sudo iptables -t nat -A PREROUTING -i wan1 -p tcp --dport 8443 -j DNAT --to 192.168.9.2:443

Outgoing packets – OUTPUT

We do not specify any special rules here. We allow all packets outgoing on all interfaces.

$ sudo iptables -P OUTPUT ACCEPT

IPv6 firewall – blocked

We do not have any IPv6, so let’s build a simple IPv6 firewall which blocks everything:

$ sudo ip6tables -P INPUT DROP
$ sudo ip6tables -P FORWARD DROP
$ sudo ip6tables -P OUTPUT DROP

Summary

Border Gateway

We do now have a basic firewall, not as sophisticated as the great Chinese firewall, but functional enough for home usage. For convenience, I have summarised all of the commands from the previous chapters in one “script” hereafter. I use this format so I can add some comments within the lines as reminder of what was explained before. If you are happy with this configuration and are using the same interface name, then you could directly run that script. But I would advise you run one command at a time.

Do not forget that those commands are not permanent. So if it messes up with your system, simply reboot it and you will be able to start fresh.

#!/bin/bash

set -eu

# The WAN interface is wan1
# The LAN interface (a bridge) is lan1

# ## INPUT chain
# Accept everything from the host or the LAN
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -i lan1 -j ACCEPT
# Accept all existing or already approved connections
iptables -A INPUT -i wan1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Drop all invalid messages
iptables -A INPUT -i wan1 -m conntrack --ctstate INVALID -j DROP

# Create new chain for ICMP rules
iptables -N ICMP-RULES
iptables -A INPUT -i wan1 -p icmp -j ICMP-RULES
iptables -A ICMP-RULES -i wan1 -o wan1 -p icmp --icmp-type 8 -m conntrack --ctstate NEW -j ACCEPT
iptables -A ICMP-RULES -j RETURN

# Create new chains for UDP and TCP rules
iptables -N WAN-SRVC-UDP
iptables -N WAN-SRVC-TCP
iptables -A INPUT -p udp -m conntrack --ctstate NEW -j WAN-SRVC-UDP
iptables -A INPUT -p tcp --syn -m conntrack --ctstate NEW -j WAN-SRVC-TCP
iptables -A WAN-SRVC-UDP -j DROP
iptables -A WAN-SRVC-TCP -j DROP

# Uncomment the next line to allow SSH access from the LAN
# iptables -I WAN-SRVC-TCP 1 -i lan1 -p tcp --dport 22 -j ACCEPT

iptables -A INPUT -m limit --limit 3/minute --limit-burst 3 -j LOG \
--log-prefix "INPUT packet unfiltered: "
# Default to block
iptables -P INPUT DROP

# ## FORWARD chain
# Allow LAN to LAN forwarding
iptables -A FORWARD -i lan1 -o lan1 -j ACCEPT
# Allow LAN to WAN forwarding
iptables -A FORWARD -i lan1 -o wan1 -j ACCEPT

# Allow forwarding for accepted connections, including DNAT (port forwarding)
iptables -A FORWARD -i wan1 -m conntrack --ctstate DNAT,RELATED,ESTABLISHED -j ACCEPT
# Default to block
iptables -P FORWARD DROP

# Uncomment the next line to do port forwarding from 8443 (outside) to 192.168.9.2:443 (inside)
#iptables -t nat -A PREROUTING -i wan1 -p tcp --dport 8443 -j DNAT --to 192.168.9.2:443

# ## OUTPUT chain
# Default to accept
iptables -P OUTPUT ACCEPT

# ## IPv6 all blocking firewall
ip6tables -P INPUT DROP
ip6tables -P FORWARD DROP
ip6tables -P OUTPUT DROP

For the moment we won’t validate our firewall, I personally did it but describing it requires a lot of time and effort so I am postponing that part for now. We also need to setup other services, mainly DHCP and DNS in order to easily connect clients on the LAN side. Then we will be able to verify fully our firewall and test it with real world application. You will see that as we are not supporting UPnP or equivalent, some applications do not perform at their top. This can easily be fixed by setting proper forwarding rule on your firewall.

It is possible that this firewall can be improved. But it is a good start and basis on which to build securely your router.

Footnotes

All product names, trademarks and registered trademarks are property of their respective owners.
Image credits: All images are credited Vera & Jean-Christophe Magical-World.eu CC BY-SA.

2 Replies to “Home network improvements – Setting up a Firewall”

  1. Hi,

    First, let me thank you for your articles on home router, as I had the same exact questions for my own concern and chose the same solutions as you.

    I would like however to have a precision on how you plan to plug your router regarding your ISP box. At the beginning, you said you will configure your router in the ISP box DMZ. Do you still want to do this or do you plan to fully replace your ISP box ? If you plan to replace it, don’t you have particular configuration to apply in order to get an IP (such as marking some packets with priority according to VLAN ids, using some particular DHCP flags, …) ?

    Thanks !

    1. Hi Kriss,

      Great to hear you are taking a similar path.

      I am still planning to use the DMZ configuration of my ISP’s box. I find this approach generic (I haven’t yet seen an ISP box which did not support DMZ mode), I did not see any drawback and it has the advantage that any not trusted device (e.g. a smart thingy) can be plugged to the ISP box and therefore my LAN is isolated from those things (as long as I control the DNS properly). Or it’s a super robust setup to have a guest WiFi as well, just only set it the guest WiFi on the ISP box with some parental control on it.
      Of course it would be possible to replace the ISP box, but as you said, there are some configurations to perform which depends on the ISP and not all ISP publish or communicate those configurations. On top of that, depending on the type of connection (cable, ADSL, fiber, etc.) one would need extra hardware (so increase the cost). And if with one cable you get TV, landline and internet this might be tricky (e.g. for the phone)
      However, replacing it would be one less device so lower risk in theory and also lower power consumption. So it does have advantages.

      Conclusion: I will stick to my original setup for the moment, it is quicker to set-up.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.