We study how to setup a simple firwall using Netfilter, the standard packet filtering tool in Linux. Netfilter allows for:

  1. Packet filtering
  2. Network address translation (NAT)
  3. Packet mangling

Netfilter can be configured through iptables, a very powerful and flexible tool.

Tables and chains

netfilter is based on tables each containing lists of rules called chains. The three most commonly used tables are:

  1. mangle for packet alteration
  2. nat for NATs
  3. filter for packet filtering

Chains are lists of rules that are inspected one after the other. There are five predefined chains that are inspected in specific moments of a packet life cycle:

  1. PREROUTING, as soon as the packet reaches the host;
  2. FORWARD, when the packet is routed through the host;
  3. POSTROUTING, when the packet is about to leave the host;
  4. INPUT, when packets are routed to the host;
  5. OUTPUT, when packets are generated by the host.

The following diagram summarizes chain and table traversal:

Chain traversal

As we stated above, chains are lists of rules that are inspected one after the other. Rules specify criteria to match a packet. If the packet matches the rule then it is processed as specified in the rule target. If instead the rule is not matched, the next rule in the chain is examined.

The most commonly used targets are:

  • ACCEPT, for accepting the packet
  • DROP, for dropping it
  • DNAT, for destination NAT
  • SNAT for source NAT

A default policy is triggered if none of the rules in the chain matches.

A simple firewall for incoming connections

In netfilter the default policy is ACCEPT. We can inspect chains and policies with the following command:

Option -t specifies the table (filter is the default table so we could omit it in this case) and -L stands for “list”.

Keeping ssh port open

Now, if we are connected to the linux host remotely via ssh, before we try to change the default policy we need to add rules that will keep ssh port open, otherwise we will cut ourself out of the host. To this purpose, we add both in INPUT and OUTPUT the following rules with target ACCEPT:

Option -A stands for append while -p tcp specifies tcp protocol. Then we have –dport and –sport to specify destination and source port, respectively. Finally -j ACCEPT specifies the ACCEPT target in both cases. The first rule means that any tcp packet delivered to the host (chain INPUT) with destination port 22 (ssh) will be accepted. The second one states that any tcp packet originating from the host (chain OUTPUT) with source port 22 (ssh) will also be accepted.

Let us check that the two rules have been added in the chains and are matched by current packets. It is enough to issue again a -L with option -v (verbose):

Good, we can see the two rules and on the left the number of packets that have matched the rules so far (if we are connected via ssh this will grow up).

DROP Default Policy

We can now safely change the default policy of INPUT chain to DROP as follows:

Look at the policy for chain INPUT. It is now DROP. This means that the only packets that will go through are the ones directed to port 22! We can check this by pinging the host:

All packets are lost. Let us check that packets have been dropped by the policy:

Good, we have 6 packets dropped by the policy a 6 packets sent by ping. They have all been dropped by our DROP policy.

Policy can be set back to accept with:

You can double-check that ping now works as expected.

You can always flush iptables with iptables -F. This will delete all the chains in the table so don’t execute it now! Notice that this does not change the default policy, so if you have a default DROP policy and you flush you will cut you off from the host!

Established connections

For OUTPUT we still have an ACCEPT policy, thus connection from the host are all enabled. However, notice that only outgoing packets will go through while any answer will be blocked by the INPUT DROP policy. There is a general way to enable all answers to already established connections:

So now we have that ping packets go out because of the ACCEPT policy in OUTPUT and answers go through thanks to the new rule using module (-m) state and accepting all ESTABLISHED packets, i.e., packets that belong to established connections. Notice that new connections to the host won’t be accepted by this rule.

It is possible to see the 5 accepted packets on the second ACCEPT rule in the INPUT chain corresponding to the 5 answers to ping requests above.

Netfilter Connection Tracking

Connection tracking keeps track of the established connections. If we run the command line utility conntrack while pinging another host we can see the following:

In particular we notice that connection tracking keeps track of the source and destination of the outgoing packets (src= dst= and the source and destination of the incoming ones (src= dst= This apparently redundant information is important when address translation is in place, since incoming packets might have difference addresses with respect to outgoing packets, as we will se in the next section.

Notice that conntrack command line utility is not installed by default in all Linux systems.

Network Address Translation (NAT)

Network Address Translation is an important feature of Netfilter which is useful in various situations:

  • Accessing the Internet from an internal, private LAN (source NAT)
  • Redirecting requests to a certain port of a web server located in the internal LAN (destination NAT).
    For example:

    redirects all traffic coming from interface eth0 with destination, port 80, to host port 80. So the web server on host 100 will answer to web requests. Notice that answers from web server on host 100 are translated as coming from host 1, which is what the browser expects. So translation happens transparently for all established packets. NAT rules are inspected only when initiating new connections. Translations are stored and suitably applied to all packets belonging to the same connection.

Notice that for DNAT to work it is necessary that the host is configured so to forward packets that are directed to a different host. This is usually disabled by default. We can inspect this feature as follows:

In order to enable it it is sufficient to put 1 as follows:

Finally, DNAT alone works only if the host acts as a gateway for the final host. In fact, if the final host answers directly to the source host communication will fail (since addresses won’t match). In order to achieve NAT between any host is to use DNAT and SNAT together, as suggested in the following exercise.

Exercise 1

Configure a DNAT and a SNAT so to redirect all traffic directed to the firewall to another host, in a way that answers are correctly delivered to the client host. You can test the firewall configuration through a nc connection. Use conntrack to check NAT.

Exercise 2

Configure a firewall on your vulnerable host so that:

  1. the only open ports are 80 and 22 (be careful to open 22 before you set the policy to DROP)
  2. redirect incoming connections to port 80, to port 8000 on the same host. Run a python SimpleHTTPServer on 8000 to test the redirection (connect through the browser).
  3. direct connections to port 8000 are prevented: the web server should only be reachable through port 80.


Mignis is a tool developed by our security group that simplifies firewall specification and makes it easy maintaining configurations. Discover more about Mignis here.

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.