Iptables
Previous  Top  Next

I do not consider the Internet a lovely play ground with friendly people anymore, something it might have been 10 years ago. Instead it's more like an uncontrolled and compressed combination of good and evil. You have the get the good stuff yourself, but the evil stuff comes into your machine by itself. I look my door all the time, but when I imagine that every human being could arrive at my doorstep from any place in the world within seconds, that does sound slightly scary. But that's the way it is with the Internet, everybody out there can come in anytime without knocking, if you're not prepared. High bandwidth connections via cable or DSL are very convenient but naturally require security. I don't like desktop security applications and installing them on every machine on my network does not make sense anyhow. IP Masquerading is another nice thing when your stingy ISP only gives you one ip address for an already expensive DSL connection.

So what is the solution? Certainly commercial firewall solutions would be the best choice if you had a lot of money, but do you have a $5000 in your yearly firewall budget? I don't, and that's why I take an old Pentium machine with 32MB RAM and a 1Gb HD and make it the masquerading firewall.

The Iptables can be a little bit confusing to understand and I certainly recommend some thorough understanding of TCP/IP before you read on. Check my website for networking books that I recommend. It took me quite a while until I had a grasp on the Iptables and I will try to pass that on to you. Don't let configuration files scare you, at first glance they look very confusing (like most things on Linux if you're not used to them) but turn out to be quite tame once you understand them. I will use a simple but hopefully helpful example to explain iptables. Some basics now:

Our beloved iptables use several so-called chains to configure its behavior: INPUT, OUTPUT, FORWARD and for NAT: PREROUTING and POSTROUTING.
inputoutput

The input chain will handle a packet that is destined for our host above, a packet that leaves via an interface will be handled by the output chain. Very simple, but what is the forward chain good for then? If our box is routing a packet, essentially meaning that it is not really being processed by any service at our then the forward chain comes into play. So forwarding really only makes sense if you use your Linux box as some kind of router. Please note that a packet only traverses one of the mentioned chains, it's incoming, outgoing or forwarded.

We will then apply rules to those chains to customize and control the behavior of our Linux Bastille.

Please note that, without mentioning it again in the examples later, I assume that all traffic has been disallowed by using –F chain and –P chain DROP, example:

iptables –F INPUT
# FLUSH THE INPUT CHAIN (remove all rules)
iptables –P INPUT DROP
# SET THE DEFAULT POLICY TO DROP
iptables -F OUTPUT
# FLUSH THE OUTPUT CHAIN
iptables –P OUTPUT DROP
# SET THE DEFAULT POLICY TO DROP
iptables –F FORWARD
# …
iptables –P FORWARD DROP
# …

The above lines need some explanation. As already stated we apply rules to the existing INPUT, OUTPUT or FORWARD chains. Whenever we configure our box I want to make sure that the chains are empty since rules will be followed step-by-step.
The –F switch accomplishes this by removing all rules of a specific chain, lines 1,3 and 5 are explained.
The –P switch on the other hand sets the default policy for a chain. Right after we flush the chain it is empty, packets will not match a single rule since there aren't any rules. A default policy of a chain can either be ACCEPT or DROP and I prefer DROP. This means that every packet that has arrived at the end of a chain will be matched against the default policy, DROP in the example above. It is usually more secure to deny everything that has not been explicitly allowed.

Additionally we want to set up environment variables to make our script(s) easier to maintain:

EXT_IFACE="eth1"
LAN_IP="172.24.10.0/24"


Example 1:
Let's imagine a simple example, we set up a host and only want it to accept http connections since this is the only service that should be accessed. Of course we already deactivated all services, but additionally we want to allow only TCP packets that were sent to the local port 80. Clearly this will be configured on the input chain since we are talking about an incoming packet. Good, now since our remote http client would probably appreciate some kind of feedback, we also need to allow packets leaving the interface, via the output chain. Of course we don't want any packet to leave the output chain, since an intruder could use that to his advantage and send data from our host, no matter if that is a Trojan horse or a simple ftp client. The iptables have a very nice feature as they now support the statefulness of packets, by using –m state –state ESTABLISHED, RELATED. Sounds good, it sure is! If we could actually talk to iptables (maybe in the 4.14 kernel?) we would say:

- Allow all packets that are TCP and are sent to port 80 on this interface.
- Allow all packets that refer to a previous packet (
-ESTABLISHED) or to an existing connection (like ICMP replies, FTP data sessions …, -RELATED) to leave the interface.

All other packets would be denied since they were not specifically allowed. This is the "code":

iptables –A INPUT –p TCP -–dport 80 –i eth0 –j ACCEPT
iptables –A OUTPUT –p TCP –o eth0 –m state -–state ESTABLISHED –j ACCEPT

Line 1:
-A INPUT
means that we want to configure the input chain
-p TCP
refers to only TCP packets (and not UDP packets)
--dport 80
matches only packets that were sent to port 80
-i eth0
applies only to the eth0 interface
-j ACCEPT
jumps to the ACCEPT target, hence lets the packet pass

Line 2:
-A OUTPUT
-p TCP
-o eth0
-m state
--state ESTABLISHED
-j ACCEPT
packets leaving our interface
TCP only
packets leaving interface eth0
specify a state
only related packets are allowed, not new connections
let the packet go trough

Example 2:
This might be a little bit more complicated than the previous example. We just set up our new DSL line and are mighty happy, except for one thing: We are only supposed to use one computer to connect to the internet! Dang, how boring! Of course we have several computers around that all want to connect to the Internet which at this point doesn't work. I always feel a little badly to fool my ISP who, in endless generosity, has granted me the privilege to connect to the Internet in such high speed. Yet I really only use one computer at the time to connect to the Internet, but from different machines.

In plaintext, we need a firewall that supports IP masquerading. No problem for the iptables since they support connection tracking. Now what is exactly that we want?

- All packets originating from our internal LAN should be sent to the correct host on the Internet; all packets coming from the Internet that relate to a connection that was initiated previously from the internal LAN should be allowed to enter our network.
- Additionally, the
ip headers need to be modified by iptables so that our firewall appears to be the requesting and receiving host, instead of our machines in the LAN that probably have ip addresses not even valid on the Internet (10.0.0., 172.24.0.0, 192.168.0.0).

That's what we call masquerading. Iptables accomplishes this by keeping track which host (from the LAN) requested which connection, and by subsequently, upon arrival of the response (from the Internet), sends the packet back to the internal host. Puh, cool stuff.

This is the first example where we need to look at another chain, the FORWARD and POSTROUTING chain. I found the POSTROUTING (and PREROUTING) chain a little confusing, so read the following if you find it confusing too.

Let's forget the iptables stuff for a while and think of our Linux box as a plain router. We have our little LAN, and we have the endless space of the Internet. To send packets from our internal LAN we need to route the packets through our Linux box. So if we request http://www.netikus.net
then our machine from the LAN will send a packet to the Linux box (assuming that the Linux box is configured as the default router for that host), destined for the ip address of www.netikus.net. Let's also assume that the ip address of our host in the LAN is 172.24.10.50. Now without NATing, our Linux box would forward this packet to the next router on the path to www.netikus.net. The next router however would most likely discard the packet since 172.24.10.50 is not really a valid address on the Internet since it's part of the address space reserved for internal usage. So what did our router do wrong? It correctly accepted the packet and correctly sent it to its own default router, but that was simply not enough.
For all this to work the source ip address of the packet we sent needs to be modified to something valid, in fact to the ip address of the external Linux host's NIC. This is why we need to use the POSTROUTING chain, the chain that modifies our source packet after the OS has made its routing decision. If we were to modify the packet before the routing decision would be made, then no routing decision would be made at all since the packet would appear to come from itself (remember, we change the source address of our packet to the address of the firewall's external interface) – not a good idea. So we need to make Linux make its routing decision and then, after routing (POSTROUTING), change the source ip address. So we write:

iptables –t nat –A POSTROUTING –o $EXT_IFACE –s $LAN_IP –j MASQUERADE

The "-t nat" specifies that we want to use the nat table, which enables us to use the POSTROUTING chain. If we don't specify a table with –t, then the default filter table is being used (which contains the already described INPUT, OUTPUT and FORWARD default chains). Then we want to masquerade packets as the leave the $EXT_IFACE interface (this should be set to whatever your external interface is, like eth1 or ppp0) and come from an address from our internal network. We haven't seen the MASQUERADE target before, that's because it's only available in the nat table. You should only use MASQUERADE if you are using a dial up connection or a connection that gets the ip address assigned dynamically. This is because all stored states of packets will be forgotten (erased) once the interface goes down. If you have a permanent ip address then use the SNAT target with the –to-source ipaddress option instead where ipaddress would be the ip address of your external interface. Good, with this one rule Linux modifies packets that:

-Originate from our internal networks ip address space  
-Leave the firewall on its external interface  

This figure might explain the POSTROUTING (and PREROUTING) chain better:

routing

This may sound like this is all we want, but it's not really. Our firewall at this point (since previously only configured to drop everything) will not accept any packet. So what are we missing? Packets coming in on the internal interface need to be accepted, provided they only pass through, right? We need to apply another rule that utilizes the FORWARD chain, since packets are being forwarded. Iptables must simply look at the destination ip address of the packet and then decide if it should use the INPUT or FORWARD chain. In iptables syntax this should look something like this:

iptables –A FORWARD –i $INT_IFACE –o $EXT_IFACE –s $LAN_IP –p TCP –j ACCEPT

Remember, -i is the incoming interface and -o is the outgoing interface. We already used a simpler rule above. Good, now packets are accept from the internal network and correctly masqueraded. Packets sent by an internal host will now arrive at the destination host on the Internet, which would think that our firewall had actually sent the packet. Then the Internet host would reply to the packet by sending it to the firewall. And yes, the firewall would drop the packet since it doesn't allow incoming packets on the external interface. So we write another rule, using the –m state option again.

iptables –A FORWARD –i $EXT_IFACE –o $INT_IFACE –p TCP –m state -–state ESTABLISHED,RELATED –j ACCEPT

This is very similar to the first example, except that I added RELATED to the state flags. That's because I might end up enabling ICMP and ftp packets as well, that won't work without the RELATED flag. You might wonder why I didn't use INPUT here, since the packets coming from the Internet are certainly addressed to our firewall. That's a good question and I haven't found documentation about that yet. The only explanation that makes sense for me is that iptables recognize the incoming packet(s) to belong to the nat table and change the destination address immediately in some kind of prerouting chain, before the OS does it's routing decision.

Please note that you still need to allow UDP packets to port 53 to allow your internal hosts to resolve host names, I will leave it up to you to figure that rule out.

There is a lot more about the iptables and I suggest you visit netfilter.samba.org; they have a lot of good documents there. When you try to set up iptables rules, first picture where exactly the packets go, like:

-Where are the packets originating from  
-Where are the packets going to  
-What protocol will be used  


Once you figured that out, go on by determining the chains that will be used:

-Will packets be sent to the firewall or will they be forwarded?  
-If the packets will be modified (NAT), will they be changed before or after routing  


I added a colorful picture that shows the chains again:
iptables

This chapter got a lot longer than I had anticipated but should give you a good understanding of the iptables.