Protect your network with Linux's powerful firewalling features.
Linux has long had the capability for filtering packets, and it has come a long way since the early days in terms of both power and flexibility. The first generation of packet-filtering code was called ipfw (for "IP firewall") and provided basic filtering capability. Since it was somewhat inflexible and inefficient for complex configurations, ipfw is rarely used now. The second generation of IP filtering was called IP chains. It improved greatly on ipfw and is still in common use. The latest generation of filtering is called Netfilter and is manipulated with the iptables command. It is used exclusively with the 2.4.x and later series of kernels. Although Netfilter is the kernel component and iptables is the user-space configuration tool, these terms are often used interchangeably.
An important concept in Netfilter is the chain , which consists of a list of rules that are applied to packets as they enter, leave, or traverse through the system. The kernel defines three chains by default, but new chains of rules can be specified and linked to the predefined chains. The INPUT chain applies to packets that are received and are destined for the local system, and the OUTPUT chain applies to packets that are transmitted by the local system. Finally, the FORWARD chain applies whenever a packet will be routed from one network interface to another through the system. It is used whenever the system is acting as a packet router or gateway, and applies to packets that are neither originating from nor destined for this system.
The iptables command is used to make changes to the Netfilter chains and rulesets. You can create new chains, delete chains, list the rules in a chain, flush chains (that is, remove all rules from a chain), and set the default action for a chain. iptables also allows you to insert, append, delete, and replace rules in a chain.
Before we get started with some example rules, it's important to set a default behavior for all the chains. To do this we'll use the -P command-line switch, which stands for "policy":
# iptables -P INPUT DROP
# iptables -P FORWARD DROP
This will ensure that only those packets covered by subsequent rules that we specify will make it past our firewall. After all, with the relatively small number of services that will be provided by the network, it is far easier to explicitly specify all the types of traffic that we want to allow, rather than all the traffic that we don't. Note that a default policy was not specified for the OUTPUT chain; this is because we want to allow traffic to proceed out of the firewall itself in a normal manner.
With the default policy set to DROP, we'll specify what is actually allowed. Here's where we'll need to figure out what services will have to be accessible to the outside world. For the rest of these examples, we'll assume that eth0 is the external interface on our firewall and that eth1 is the internal one. Our network will contain a web server (192.168.1.20), a mail server (192.168.1.21), and a DNS server (192.168.1.18)—a fairly minimal setup for a self-managed Internet presence.
However, before we begin specifying rules, we should remove filtering from our loopback interface:
# iptables -P INPUT -i lo -j ACCEPT
# iptables -P OUTPUT -o lo -j ACCEPT
Now let's construct some rules to allow this traffic through. First, we'll make a rule to allow traffic on TCP port 80—the standard port for web servers—to pass to the web server unfettered by our firewall:
# iptables -A FORWARD -m state --state NEW -p tcp \
-d 192.168.1.20 --dport 80 -j ACCEPT
And now for the mail server, which uses TCP port 25 for SMTP:
# iptables -A FORWARD -m state --state NEW -p tcp \
-d 192.168.1.21 --dport 25 -j ACCEPT
Additionally, we might want to allow remote POP3, IMAP, and IMAP+SSL access as well:
POP3
# iptables -A FORWARD -m state --state NEW -p tcp \
-d 192.168.1.21 --dport 110 -j ACCEPT
IMAP
# iptables -A FORWARD -m state --state NEW -p tcp \
-d 192.168.1.21 --dport 143 -j ACCEPT
IMAP+SSL
# iptables -A FORWARD -m state --state NEW -p tcp \
-d 192.168.1.21 --dport 993 -j ACCEPT
Unlike the other services, DNS can use both TCP and UDP port 53:
# iptables -A FORWARD -m state --state NEW -p tcp \
-d 192.168.1.21 --dport 53 -j ACCEPT
Since we're using a default deny policy, it makes it slightly more difficult to use UDP for DNS. This is because our policy relies on the use of state tracking rules, and since UDP is a stateless protocol, there is no way to track it. In this case, we can configure our DNS server either to use only TCP, or to use a UDP source port of 53 for any response that it sends back to clients that were using UDP to query the nameserver.
If the DNS server is configured to respond to clients using UDP port 53, we can allow this traffic through with the following two rules:
# iptables -A FORWARD -p udp -d 192.168.1.18 --dport 53 -j ACCEPT
# iptables -A FORWARD -p udp -s 192.168.1.18 --sport 53 -j ACCEPT
The first rule allows traffic into our network destined for the DNS server, and the second rule allows responses from the DNS server to leave the network.
You may be wondering what the -m state and --state arguments are about. These two options allow us to use Netfilter's stateful packet-inspection engine. Using these options tells Netfilter that we want to allow only new connections to the destination IP and port pairs that we have specified. When these rules are in place, the triggering packet is accepted and its information is entered into a state table.
Now we can specify that we want to allow any outbound traffic that is associated with these connections by adding a rule like this:
# iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
The only thing left now is to allow traffic from machines behind the firewall to reach the outside world. To do this, we'll use a rule like the following:
# iptables -A FORWARD -m state --state NEW -i eth1 -j ACCEPT
This rule enters any outbound connections from the internal network into the state table. It works by matching packets coming into the internal interface of our firewall that are creating new connections. If we were setting up a firewall that had multiple internal interfaces, we could have used a Boolean NOT operator on the external interface (e.g., -i ! eth0). Now any traffic that comes into the firewall through the external interface that corresponds to an outbound connection will be accepted by the preceding rule, because this rule will have put the corresponding connection into the state table.
In these examples, the order in which the rules were entered does not really matter. Since we're operating with a default DENY policy, all our rules have an ACCEPT target. However, if we had specified targets of DROP or REJECT as arguments to the -j option, then we would have to take a little extra care to ensure that the order of those rules would result in the desired effect. Remember that the first rule that matches a packet is always triggered as the rule chains are traversed, so rule order can sometimes be critically important.
It should also be noted that rule order can have a performance impact in some circumstances. For example, the rule shown earlier that matches ESTABLISHED and RELATED states should be specified before any of the other rules, since that particular rule will be matched far more often than any of the rules that will match only on new connections. By putting that rule first, it will prevent any packets that are already associated with a connection from having to traverse the rest of the rule chain before finding a match.
To complete our firewall configuration, we'll want to enable packet forwarding. Run this command:
# echo 1 > /proc/sys/net/ipv4/ip_forward
This tells the kernel to forward packets between interfaces whenever appropriate. To have this done automatically at boot time, add the following line to /etc/sysctl.conf:
net.ipv4.ip_forward=1
If your system doesn't support /etc/sysctl.conf, you can put the preceding echo command in one of your startup rc scripts, such as /etc/rc.local. Another useful kernel parameter is rp_filter, which helps prevent IP spoofing. This enables source address verification by checking that the IP address for any given packet has arrived on the expected network interface. This can be enabled by running the following command:
# echo 1 > /proc/sys/net/ipv4/conf/default/rp_filter
Much like how we enabled IP forwarding, we can also enable source address verification by editing /etc/sysctl.conf on systems that support it, or else put the changes in your rc.local. To enable rp_filter in your sysctl.conf, add the following line:
net.ipv4.conf.all.rp_filter=1
To save all of our rules, we can either write all of our rules to a shell script or use our Linux distribution's particular way of saving them. We can do this in Red Hat by running the following command:
# /sbin/service iptables save
This will save all currently active filter rules to /etc/sysconfig/iptables. To achieve the same effect under Debian, edit /etc/default/iptables and set enable_iptables_initd=true.
After doing this, run the following command:
# /etc/init.d/iptables save_active
When the machine reboots, your iptables configuration will be automatically restored.