Firewalls in Linux – A Deep Dive Into IPtables & Netfilter

Understanding how packet filtering with iptables works, and how the packets flow in the Linux kernel

Firewalls are an important piece of hardware/software for network security. They can range from dedicated hardware like Cisco ASA or Firepower series, Fortinet FortiGate, or Sophos devices to software like Windows Defender, to cloud firewalls like web application firewalls (WAFs).

The features of firewalls range from packet filtering, to establishing VPN connections, providing network address translation (NAT), defining security zones, URL filtering and preventing DoS attacks.

In Linux, host-based firewalls are used to filter and control network traffic based on user-defined rules. These tools can perform simple and advanced operations, such as packet filtering, stateful inspection, and Network Address Translation (NAT).These include:

  • iptables

  • nftables

  • firewalld

  • ufw

These are merely interfaces for configuring the real Linux packet inspection mechanism, which is netfilter. Netfilter is a kernel-level subsystem while the other tools exist in the user-space. In this article, we will be using iptables.

In this article, we’re going to look at:

  • Basic concepts of firewalls and iptables

    • Packets

    • Rules

    • Actions

    • Chains

    • Hooks

    • Tables

  • How packets flow in a Linux system

  • Creating firewall rules in iptables (with real-world example)

Basic Concepts

Packets:

A packet is a unit of information that has been broken down to be sent over a network. It is a common networking concept. It is what is been accepted or rejected in firewall rules.

Rules:

Firewalls inspect packets and make the decisions to allow or drop packets coming in or going out of the system. These decisions are made based on rules. It consists of the protocol, the source and destination IP addresses, the source and destination ports, and the action. syntax is:

iptables --table TABLE -A/-C/-D... CHAIN rule --jump Target

And here is an example:

sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT  

In this iptables example is an input rule to accept any traffic from the TCP protocol that comes to port 22 (SSH). It allows SSH connections.

Actions/Targets:

In the example above, the action, specified by the flag -j, tells the system what to do to any packet that matches the rule. The common ones are ACCEPT, DROP, REJECT.

  • ACCEPT means the packet should be allowed to pass through.

  • DROP discards the packet silently without letting the sender know.

  • REJECT discards the packet and lets the sender know.

Chains:

A chain is a sequence of rules which a packet is checked against. If it matches the rules in the chain, the specified action is taken. There are five chains: PRE-ROUTING, INPUT, FORWARD, OUTPUT, POST-ROUTING.

  • PRE-ROUTING alters packets immediately the enter the system

  • INPUT handles packets destined for the host

  • OUPUT handles packets originating for the host

  • FORWARD handles packets that are passing through the host

  • POST-ROUTING alters packets after routing, just before they leave

This is what an example INPUT chain should look like:

Chain INPUT (policy ACCEPT)
num  target     prot opt source      destination
1    ACCEPT     tcp  --  anywhere    anywhere     tcp dpt:ssh
2    ACCEPT     tcp  --  anywhere    anywhere     tcp dpt:http
3    DROP       all  --  anywhere    anywhere

Hooks:

A netfilter hook is a point in the Linux kernel’s network stack where packets can be intercepted and processed. The hooks include:

  • Ingress hook

  • Pre-routing hook

  • Input hook

  • Forward hook

  • Output hook

  • Post-routing hook

  • Egress hook

The ingress and egress hooks are device-level (layer 2) hooks, while the others are IP-layer (layer 3) hooks that deal with IP packets. These hooks are what the chains connect to and register callback functions to be executed on any packet traversing that route. The hooks are basically locations on the kernel where chains (list of rules) are attached to by user-space programs like iptables, or nftables to tell the kernel what to do with the packet when it hits the hooks.

Tables:

Tables are categories that help to group together rules based on their functions. Different tables contain different chains. For example, the INPUT, OUTPUT and FORWARD belong to the filter table. The tables in iptables are:

  • Filter table

  • NAT table

  • Mangle table

  • Raw table

  • Security table

  1. Filter table: This is the main table for packet filtering. It is used to allow and block traffic destined for the host or originating from the host. Traffic from here goes through the following chains:

    • PRE-ROUTING → INPUT for traffic coming into the host

    • PRE-ROUTING → FORWARD → POST-ROUTING for traffic passing through the host

    • OUTPUT → POST-ROUTING for traffic originating from the host

    The chains controlled by this table are the INPUT, FORWARD, and OUTPUT.

    And the actions are ACCEPT, DROP, REJECT.

  2. NAT table: This table is responsible for handling network address translation. This can enable multiple devices in a private network to share a single public IP address. The nat table contains the PRE-ROUTING, OUPUT and POST-ROUTING chains. It also contains the following actions:

    1. DNAT – Modifies the destination address or port of the packet

    2. SNAT – Changes the source IP address or port of the packet to one user specifies

    3. MASQUERADE – Like SNAT but changes to a dynamic IP given by a protocol like DHCP

    4. REDIRECT – It redirects incoming traffic to specified port on the host

    5. RETURN – Stops processing the chain

  3. Mangle table: This table is used for modifying packet headers and changing things like Time To Live (TTL) and Type of Service (ToS) bits. Mostly QoS. All five chains are contained in it —PRE-ROUTING, INPUT, OUTPUT, FORWARD, POST-ROUTING. The actions in this table include, but are not limited to:

    1. MARK – Set a fwmark on the packet

    2. CONNMARK – Marks connections for tracking.

    3. SECMARK – Assign an SELinux security context

    4. TTL – Change Time To Live value

    5. HL – IPv6 equivalent of TTL (Hop Limit)

    6. CLASSIFY – Assign a traffic control class

    7. ACCEPT

    8. DROP

    9. RETURN

  4. Raw table: This table is used to configure exceptions from connection tracking, because the mangle table which comes after it could mark connections and track them. This table is used to set exemptions before the packets reach there. It contains only the PRE-ROUTING and OUTPUT chains. Some of the actions here include:

    1. NOTRACK

    2. ACCEPT

    3. DROP

    4. RETURN

  5. Security table: This is used together with SELinux and other Linux Security Modules (LSMs) for Mandatory Access Control (MAC). It contains the INPUT, OUTPUT and FORWARD chains. Actions include:

    1. SECMARK – Sets a security context mark on a packet, which can later be used by SELinux or other LSMs for access control decisions

    2. CONNSECMARK – Apply security context marks on network connections

    3. ACCEPT

    4. DROP

    5. RETURN.

How packets flow in the Linux system

In the Linux system, here is how it works. When the data enters the system through the network interface card (NIC), it passes through different hooks in the kernel’s netfilter framework.

  1. First, it hits the PRE-ROUTING hook and is passed through the PRE-ROUTING chains of the tables. The raw table first to see if there are any exceptions to connection tracking, then the PRE-ROUTING chain in the mangle table and PRE-ROUTING chain in the NAT table.

  2. The packet is then routed. If it’s for a local process, it’s sent to the INPUT hook. If it’s to be forwarded to another host, it is sent to the FORWARD hook.

  3. When it hits the INPUT hook, it is passed through the INPUT hook and is passed through the INPUT chain in the mangle table, then the INPUT chain in the filter table, then the INPUT chain in the security table, then the INPUT chain in the NAT table that alters packets going to the local sockets. Then it sends it to the process.

  4. For the packets that are to be forwarded, they go through the FORWARD chain of the mangle table where QoS operations can be done, then to the FORWARD chain of the filter table, and the FORWARD chain of the security table.

  5. For packets generated by local processes, the are sent to the OUTPUT chains of the raw table, then mangle table, then NAT table, then filter table and finally security table before moving on to the PRE-ROUTING chains.

  6. Both the OUTPUT and FORWARD chains feed into the POST-ROUTING chains, and the packet goes through the POST-ROUTING chains of the mangle table and then, the NAT table before being sent out of the machine.

Creating firewall rules with iptables

Let’s say you have a database server (PostgreSQL), and want to achieve three (3) things:

  • Prevent people from trying to SSH into your database server. You want to restrict that to only a particular IP address of 203.0.113.45.

  • Make sure that the server is only accessible from your private network (possibly your webserver) of 192.168.1.0/24.

  • Ensure that already established connections are respected, and your host doesn’t block packets from those.

Here’s how you will go about it:

# Allow loopback
iptables -A INPUT -i lo -j ACCEPT

# Allow established/related
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Restrict SSH
iptables -A INPUT -p tcp --dport 22 -s 203.0.113.45 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP

# Allow DB only from private network
iptables -A INPUT -p tcp --dport 5432 -s 192.168.1.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 5432 -j DROP

Here, we can see some flags:

  • The -A flag which is the short for --append. It is used to add rules to chains. Other chain commands include -C (--check) for checking whether a particular rule exists in a chain, -D (--delete) for deleting a rule in a chain, and -L (--list) for listing the rules in a specified chain or all rules.

  • The -p flag is used to specify the protocol. Here, we dealt with the TCP protocol all through.

  • The -i flag is used to specify the interface through which a packet was received. Here, the loopback lo interface is specified in the first rule.

  • The -j flag (--jump) is used to specify the target (action) of the chain. That is what should happen to a packet that matches the rules.

  • The -s flag (--source) is used to specify source IP addresses.

  • The --dport flag is used to specify the destination port of the packet. If the packet has a destination port matching what is specified, the specified action will be taken on it.

  • The -m flag (--match) It is part of iptables-extensions. It is used to load modules to do extra things in iptables. Here, it’s used to work with the conntrack module for connection tracking.

  • The --ctstate flag is used to match connection states for connection tracking. Here, it is used to specify that packets with ESTABLISHED or RELATED connection states should be allowed.

So, the first rule says: append to the INPUT chain, a rule that allows all traffic from the loopback interface.

The second rule says: append to the INPUT chain, a rule that allows all packets from already established connections.

The third rule says: append to the INPUT chain, a rule that allows all traffic on TCP port 22 (SSH) from 203.0.113.45.

The fourth says: append to the INPUT chain, a rule that denies every other SSH traffic.

The fifth rule says: append to the INPUT chain, a rule that allows connections to TCP port 5432 (PostgreSQL) from the 192.168.1.0/24 subnet.

The sixth rule says: append to the INPUT chain, a rule that drops every other connection to that port.

But does it last?

With iptables, the rules are ephemeral. They go away with every reboot. So, to make the configurations permanent, we need to save them. For Red Hat based distributions, you need the iptables-services package. Because iptables is not a service, we need to install the iptables service that enables systemd to manage it and start it on boot. First, install it:

sudo yum install -y iptables-services

Then you save configurations:

sudo iptables-save | sudo tee /etc/sysconfig/iptables

Then you enable iptables service so it always starts on reboot:

sudo systemctl enable iptables

To do that, for Debian-based distros, we need the iptables-persistent package. Install it with this:

sudo apt install iptables-persistent

Ensure that the netfilter-persistent service is enabled.

sudo systemctl is-enabled netfilter-persistent.service

If not, enable it.

sudo systemctl enable netfilter-persistent.service

Now, whenever you modify your configurations, you can save them with

netfilter-persistent save

What if I forget the commands?

You can always use the iptables command as with CLI. But just so you know, there are different man pages for iptables and iptables-extensions. So, you won’t find any match commands for example in the first man page.

Conclusion

Thank you for reading. If you enjoyed this, share the article, and subscribe to the newsletter if you haven’t. You can also follow on X or on LinkedIn. Thank you.

Comments

2 responses to “Firewalls in Linux – A Deep Dive Into IPtables & Netfilter”

  1. Adebola Ayobami Avatar
    Adebola Ayobami

    This is insightful. Well done 👍

    1. Chidiadi Anyanwu Avatar
      Chidiadi Anyanwu

      Thank you

Leave a Reply

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