iptables: Creating an open source firewall

278

Author: Preston St. Pierre

With ever-present threats from online attackers and script kiddies, administrators need a firewall on the border of any network. A Linux box can make a particularly effective and capable firewall at a fraction of the cost of a Cisco or Check Point system.

The most obvious use for a firewall is to block unwanted traffic from entering or leaving a network. Firewalls can also make specific connections from outside hosts to internal systems, such as a mail or Web server, either behind the firewall or on a trusted or “de-militarized zone” (DMZ) segment.

Almost every version of the 2.x series of the Linux kernel has a different firewall implementation, with 2.0 using ipfwadm, 2.2 using ipchains, and 2.4 implementing netfilter. 2.6 continues to use netfilter, as it is essentially a plug-in framework within the network subsystem for whichever firewall implementation we choose to use. 2.4 and 2.6 support both ipfwadm and ipchains through backports of the systems to netfilter. They also support iptables, which was specifically written for use with netfilter. iptables is now the standard firewall on Linux systems. With a wide range of plug-in modules and third-party additions, it fits almost every need.

Getting the kernel ready for iptables

Nearly all distributions come with support for iptables. For those who like their own fresh kernel, compiled from scratch, simply select all of the options under “IPv4: netfilter” to provide support for everything iptables has on offer. Once you have a kernel with netfilter and iptables support, verify that iptables is available with the following command:


# iptables -L| grep Chain
Chain INPUT (policy ACCEPT)
Chain FORWARD (policy ACCEPT)
Chain OUTPUT (policy ACCEPT)

If iptables is compiled as modules, as it is in many distributions, the kernel will automatically load the necessary modules for you when it first executes iptables and as you add rules that require a specific module.

iptables structures the filtering processes into a number of tables, which are built by the kernel at boot time. Each table has a distinct function within the network stack and allows an administrator to construct a variety of rules to perform operations against packets heading to, from, or through a firewall. A standard installation will have three distinct tables: filter, which is used to perform filtering on IP packets; nat, used to modify IP or port information to permit, for example, Internet access by a non-routable block; and mangle, which allows you to modify packets’ Type of Service (ToS) values, or to mark packets for lookup in another rule. 2.6 kernels also have a “raw” table, used to perform packet filtering outside of the connection tracking processes.

Filtering packets

The filter table is split up into three separate chains, a list of rules, filtering packets at specific parts of the routing system on the firewall. The INPUT chain is used to match packets hitting the firewall host, OUTPUT to match packets originating on the firewall, and FORWARD contains rules to match packets routed from one interface to another across the firewall.

iptables offers support for connection tracking, which was lacking in both ipchains and ipfwadm. With connection tracking, the kernel keeps a database of existing connections to allow return packets for connections to pass through the firewall. Previous Linux firewall implementations had to check for specific packet types that were common with new connections, or even open ports up for connections. iptables instead allows the state of a connection to be used in a rule, permitting new or existing connections to be handled differently by the kernel. The connection tracking processes within iptables also track multiple connections that are associated with each other, such as FTP data traffic, or ICMP packets returned from a failed connection.

Generally it’s a good idea to populate a firewall ruleset with rules to allow all loopback traffic on the firewall, and allow existing connections permitted by other rules to pass traffic across the firewall. Firewall rules are constructed using a variety of checks, to match our rule against a specific type of packet, a packet to a host or port, or even a packet to or from a specific interface. Rules are inserted into the specific chain as desired by the location in the routeing process when we want to check packets. Should a packet match a rule, the kernel will process the packet based upon the target of the chain, such as dropping the packet, or allowing it to pass through the firewall unfiltered.

The iptables command manages the kernel iptables system, through which you can add, insert, and delete rules on the firewall. As we’ve not selected a specific table, our rules will manipulate the filter table, and the appropriate chain as defined in the command. Firewall rules are very simple, and have a selection of attributes which must be matched for the packet to activate the specific target. The structure of a typical firewall rule entry would be as follows, although IP arguments can be ommitted if they are no necessary for the rule to be matched. You can find detailed information on the specific format of each argument, and the variety of targets available, within the netfilter documentation.

We build our initial configuration with the following commands:

Drop all packets on the firewall in each of the three chains:

iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP

Allow traffic in and out over the loopback address for local services on the firewall:

iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

Drop packets with unknown connection states, such as TCP acknowledgement packets not related to an existing connection:

iptables -A FORWARD -m state --state INVALID -j DROP

Allow packets which are from an existing connection, or packets which are associated with another active connection:

iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT

At this point the firewall is somewhat limited, as it does not allow connections in or out, nor does it allow any traffic to be forwarded between interfaces. If we have eth0 as our outside interface, facing the Internet, eth1 our DMZ with our untrusted hosts on, and eth2 as our trusted network, we can continue the configuration to permit traffic between and to the specific network interfaces:


iptables -A INPUT -i eth2 -p tcp --dport 22 -j ACCEPT
iptables -A FORWARD -i eth2 -o eth1 -j ACCEPT
iptables -A FORWARD -i eth2 -o eth0 -j ACCEPT

The first rule allows anyone coming from our internal network to connect to our firewall via SSH, which runs on TCP port 22. The iptables -p switch selects the IP protocol used, which could be tcp, udp, icmp, and so forth depending upon the specific requirements of the connection. We also allow the internal network attached to eth2 to connect to our DMZ network on eth1, as well as hosts on the Internet, without restriction.

Network address translation

It is rare nowadays for a network to use only routable IP blocks. The majority of production environments include address blocks commonly known as 1918 addresses, as they are defined in RFC1918. These specific addresses, 10.x.x.x, 172.16.x.x, and 192.168.x.x, are not routed on the Internet, so must be converted to a permitted address before you attempt to make a connection from the internal network to the Internet. This process of rewriting the addresses on a packet is known as network address translation. NAT can also be used to allow hosts on the Internet to access network services that run on devices with non-routable addresses. This means you can run multiple network services on the same public IP address, with each existing on distinct hosts on the DMZ or internal networks.

You add a NAT rule using iptables in almost exactly the same way you add a filter rule, although the target is somewhat different because you must inform the kernel you want to rewrite the packet.


iptables -t nat -A POSTROUTING -o eth0 -s 10.1.0.0/16 -j SNAT -to 207.166.198.1

This rule will rewrite any packet leaving eth0 that currently has a source address of 10.1.0.0/16, which would be a block within our internal network, so it leaves eth0 with a source address of 207.166.198.1. This type of rule is known as a Source NAT rule, as it modifies the source address of the packet, which is always placed into the POSTROUTING chain within the nat table. The kernel checks the POSTROUTING chain following the routing decision by the kernel to learn which interface the packet will head out of. You can also establish a Destination NAT rule, where you rewrite a packet coming in from the outside and translate it onto an internal address. These rules are placed in the PREROUTING chain, which is checked as soon as the packet enters the firewall, allowing the kernel to perform the routing based upon the translated destination, rather than the outside address.

For instance, if you wanted to permit Web traffic from the outside to reach the internal IP address 10.2.1.2 of your Web server on the DMZ, you would specify:


iptables -t nat -A PREROUTING -i eth0 -d 207.166.198.20 -p tcp --dport 80 -j SNAT --to 10.2.1.2

This rule rewrites packets heading to TCP port 80 on 207.166.198.20, an outside IP address, to the internal IP of 10.2.1.2. As we’ve not specified a TCP port for the inside address, the destination port will not be modified, so you can run the Web service on port 80 as you normally would.

Mangle

The mangle chain is rarely used, although it is particularly powerful as the basis for routing table manipulation or traffic prioritization on the network. The most common use for mangle is to mark a packet with an integer value, which can be looked up in another rule. Alternativly, using iproute2, we allow the packet to be handled by a distinct routing table. For example, if you use the POSTROUTING chain in the nat table, you can’t match rules against the interface the packet came in on. However, if you mark the packet using the mangle table when it first enters the firewall, you can create a POSTROUTING rule that checks for the mark and rewrites the packet on the way out as appropriate:


iptables -t mangle -A PREROUTING -i eth2 -j MARK --set-mark 0x2
iptables -t nat -I POSTROUTING -o eth2 -m mark -mark 0x2 -j SNAT --to 192.168.1.4

Going further

I’ve only touched the surface of what you can accomplish using iptables and netfilter. The projects have a substantial user base, with very active mailing lists and an IRC channel on irc.freenode.net, #netfilter, where users can discuss configuration issues.

David Coulson is a Cleveland-based freelance writer, consultant, and open source advocate who specializes in network architecture and integration.