Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

The State Machine

0.00/5 (No votes)
1 Apr 2006 1  
The State Machine as in a connection tracking machine

 Introduction :

 The state machine is a special part within iptables that should really not be called the state machine at all, since it is really a connection tracking machine. However, most people recognize it under the first name.  Connection tracking is done to let the Netfilter framework know the state of a specific connection. Firewalls that implement this are generally called stateful firewalls. A stateful firewall is generally much more secure than non-stateful firewalls since it allows us to write much tighter rule-sets.

 

Within iptables, packets can be related to tracked connections in four different so called states. These are known as NEW, ESTABLISHED, RELATED and INVALID. 

 

All of the connection tracking is done by special framework within the kernel called conntrack. Conntrack may be loaded either as a module, or as an internal part of the kernel itself. Most of the time, we need and want more specific connection tracking than the default conntrack engine can maintain. Because of this, there are also more specific parts of conntrack that handles the TCP, UDP or ICMP protocols among others. These modules grab specific, unique, information from the packets, so that they may keep track of each stream of data. The information that conntrack gathers is then used to tell conntrack in which state the stream is currently in. For example, UDP streams are, generally, uniquely identified by their destination IP address, source IP address, destination port and source port.

 

In previous kernels, we had the possibility to turn on and off defragmentation. However, since iptables and Netfilter were introduced and connection tracking in particular, this option was gotten rid of. The reason for this is that connection tracking can not work properly without defragmenting packets, and hence defragmenting has been incorporated into conntrack and is carried out automatically. It can not be turned off, except by turning off connection tracking. Defragmentation is always carried out if connection tracking is turned on.

 

All connection tracking is handled in the PREROUTING chain, except locally generated packets which are handled in the OUTPUT chain. What this means is that iptables will do all recalculation of states and so on within the PREROUTING chain. If we send the initial packet in a stream, the state gets set to NEW within the OUTPUT chain, and when we receive a return packet, the state gets changed in the PREROUTING chain to ESTABLISHED, and so on. If the first packet is not originated by us, the NEW state is set within the PREROUTING chain of course. So, all state changes and calculations are done within the PREROUTING and OUTPUT chains of the NAT table. 

 

The NEW state tells us that the packet is new in the connection. This means that the first packet that the conntrack module sees will be matched. For example, if we see a SYN packet and it is the first packet in a connection that we see, it will match. However, the packet may as well not be a SYN packet and still be considered NEW. This may lead to certain problems in some instances, but it may also be extremely helpful when we need to pick up lost connections from other firewalls, or when a connection has already timed out, but in reality is not closed.

 

The ESTABLISHED state has seen traffic in both directions and will then continuously match those packets. ESTABLISHED connections are fairly easy to understand. The only requirement to get into an ESTABLISHED state is that one host sends a packet, and that it later on gets a reply from the other host. The NEW state will upon receipt of the reply packet to or through the firewall change to the ESTABLISHED state. ICMP error messages and redirects etc can also be considered as ESTABLISHED, if we have generated a packet that in turn generated the ICMP message.

 

The RELATED state is one of the more tricky states. A connection is considered RELATED when it is related to another already ESTABLISHED connection. What this means, is that for a connection to be considered as RELATED, we must first have a connection that is considered ESTABLISHED. The ESTABLISHED connection will then spawn a connection outside of the main connection. The newly spawned connection will then be considered RELATED, if the conntrack module is able to understand that it is RELATED. 

 

The INVALID state means that the packet can not be identified or that it does not have any state. This may be due to several reasons, such as the system running out of memory or ICMP error messages that do not respond to any known connections. Generally, it is a good idea to DROP everything in this state.

 

iptables-save :

The iptables-save command is, as we have already explained, a tool to save the current rule-set into a file that iptables-restore can use. This command is quite simple really, and takes only two arguments. Take a look at the following example to understand the syntax of the command.

 

<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">iptables-save [-c] [-t table] 

 

The -c argument tells iptables-save to keep the values specified in the byte and packet counters. This could for example be useful if we would like to reboot our main firewall, but not lose byte and packet counters which we may use for statistical purposes. Issuing an iptables-save command with the -c argument would then make it possible for us to reboot but without breaking our statistical and accounting routines. The default value is, of course, to not keep the counters intact when issuing this command.

 

The -t argument tells the iptables-save command which tables to save. Without this argument the command will automatically save all tables available into the file. The following is an example on what output you can expect from the iptables-save command if you do not have any rule-set loaded.

 

<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"># Generated by iptables-save v1.2.6a on Wed Apr 24 <?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /><st1:time Hour="10" Minute="19"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">10:19:17</st1:time><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"> 2002 <o:p></o:p>
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">*filter 

<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">:INPUT ACCEPT [404:19766] <o:p></o:p>

<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">:FORWARD ACCEPT [0:0] <o:p></o:p>
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">:OUTPUT ACCEPT [530:43376] 
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">COMMIT <o:p></o:p>

<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"># Completed on Wed Apr 24 <st1:time Hour="10" Minute="19"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">10:19:17</st1:time><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"> 2002 <o:p></o:p>
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"># Generated by iptables-save v1.2.6a on Wed Apr 24 <st1:time Hour="10" Minute="19"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">10:19:17</st1:time><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"> 2002 
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">*mangle <o:p></o:p>

<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">:PREROUTING ACCEPT [451:22060] <o:p></o:p>
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">:INPUT ACCEPT [451:22060] <o:p></o:p>
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">:FORWARD ACCEPT [0:0] 
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">:OUTPUT ACCEPT [594:47151] 
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">:POSTROUTING ACCEPT [594:47151] 

"mso-bidi-font-weight: normal"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">COMMIT <o:p></o:p>

"mso-bidi-font-weight: normal"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"># Completed on Wed Apr 24 <st1:time Hour="10" Minute="19"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">10:19:17</st1:time><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"> 2002 
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"># 
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">Generated by iptables-save v1.2.6a on Wed Apr 24 <st1:time Hour="10" Minute="19"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">10:19:17</st1:time><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"> 2002 

<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">*nat 

<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">:PREROUTING ACCEPT [0:0] 
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">:POSTROUTING ACCEPT [3:450] 
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">:OUTPUT ACCEPT [3:450] 

<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">COMMIT 

<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"># Completed on Wed Apr 24 <st1:time Hour="10" Minute="19"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">10:19:17</st1:time><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"> 2002 <o:p></o:p>
<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"><SPAN style="mso-spacerun: yes"> <o:p></o:p>

This contains a few comments starting with a # sign. Each table is marked like *<table-name>, for example *mangle. Then within each table we have the chain specifications and rules. A chain specification looks like:

<chain-name> <chain-policy> [<packet-counter>:<byte-counter>].

 

 

 The chain-name may be for example PREROUTING, the policy is described previously and can for example be ACCEPT. Finally the packet-counter and byte-counters are the same counters as in the output from iptables -L -v. Finally, each table declaration ends in a COMMIT keyword. The COMMIT keyword tells us that at this point we should commit all rules currently in the pipeline to kernel.

 

To save it into any file, it is only a matter of piping the command output on to the file that you would like to save it as. This could look like the following:

 

<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"><SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'">iptables-save -c > /etc/iptables-save<SPAN style="FONT-FAMILY: Arial; mso-bidi-font-family: 'Times New Roman'"> 

 

The above command will in other words save the whole rule-set to a file called /etc/iptables-save with byte and packet counters still intact. 

 

Iptables-restore :

The iptables-restore command is used to restore the iptables rule-set that was saved with the iptables-save command. It takes all the input from standard input and can not load from files as of writing this, unfortunately. This is the command syntax for iptables-restore:

<SPAN style="LETTER-SPACING: 0pt"><SPAN style="LETTER-SPACING: 0pt">iptables-restore [-c] [-n]

The -c argument restores the byte and packet counters and must be used if you want to restore counters that were previously saved with iptables-save. This argument may also be written in its long form --counters.

The -n argument tells iptables-restore to not overwrite the previously written rules in the table, or tables, that it is writing to. The default behavior of iptables-restore is to flush and destroy all previously inserted rules. The short -n argument may also be replaced with the longer format --noflush.

 

To load rule-set with the iptables-restore command, we could do this in several ways, but we will mainly look at the simplest and most common way here.

<SPAN style="LETTER-SPACING: 0pt"><SPAN style="LETTER-SPACING: 0pt">cat /etc/iptables-save | iptables-restore -c 

This would cat the rule-set located within the /etc/iptables-save file and then pipe it to iptables-restore which takes the rule-set on the standard input and then restores it, including byte and packet counters. It is that simple to begin with.

The rule-set should now be loaded properly to kernel and everything should work. If not, you may possibly have run into a bug in these commands, however likely that sounds.

The example given below makes everything clear :

For instance, if your computer was to send out a packet to   http://www.yahoo.com/

     To request an HTML page, it would first pass through the OUTPUT chain. The kernel would look through the rules in the chain and see if any of them match. The first one that matches will decide the outcome of that packet. If none of the rules match, then the policy of the whole chain will be the final decision maker. Then whatever reply Yahoo! sent back would pass through the INPUT chain. It's no more complicated than that.

Now that we have the basics out of the way, we can start working on putting all this to practical use. Suppose you wanted to block all packets coming from 200.200.200.1. First of all, -s is used to specify a source IP or DNS name. So from that, to refer to traffic coming from this address, we'd use this:

iptables -s 200.200.200.1

But that doesn't tell what to do with the packets. The -j option is used to specify what happens to the packet. The most common three are ACCEPT, DENY, and DROP. Now you can probably figure out what ACCEPT does and it's not what we want. DENY sends a message back that this computer isn't accepting connections. DROP just totally ignores the packet. If we're really suspicious about this certain IP address, we'd probably prefer DROP over DENY. So here is the command with the result:

iptables -s 200.200.200.1 -j DROP

But the computer still won't understand this. There's one more thing we need to add and that's which chain it goes on. You use -A for this. It just appends the rule to the end of whichever chain you specify. Since we want to keep the computer from talking to us, we'd put it on INPUT. So here's the entire command:

iptables -A INPUT -s 200.200.200.1 -j DROP

This single command would ignore everything coming from 200.200.200.1 (with exceptions, but we'll get into that later). The order of the options doesn't matter; the -j DROP could go before -s 200.200.200.1. I just like to put the outcome part at the end of the command. Ok, we're now capable of ignoring a certain computer on a network. If you wanted to keep your computer from talking to it, you'd simply change INPUT to OUTPUT and change the -s to -d for destination. Now that's not too hard, is it?

So what if we only wanted to ignore telnet requests from this computer? Well that's not very hard either. You might know that port 23 is for telnet, but you can just use the word telnet if you like. There are at least 3 protocols that can be specified: TCP, UDP, and ICMP. Telnet, like most services, runs on TCP so we're going with it. The -p option specifies the protocol. But TCP doesn't tell it everything; telnet is only a specific protocol used on the larger protocol of TCP. After we specify that the protocol is TCP, we can use --destination-port to denote the port that they're trying to contact us on. Make sure you don't get source and destination ports mixed up. Remember, the client can run on any port, it's the server that will be running the service on port 23. Any time you want to block out a certain service, you'll use --destination-port. The opposite is --source-port in case you need it. So let's put this all together. This should be the command that accomplishes what we want:

iptables -A INPUT -s 200.200.200.1 -p tcp --destination-port telnet -j DROP

And there you go. If you wanted to specify a range of IP's, you could use 200.200.200.0/24. This would specify any IP that matched 200.200.200.*. Now it's time to fry some bigger fish. Let's say that, like me, you have a local area network and then you have a connection to the internet. We're going to also say that the LAN is eth0 while the internet connection is called ppp0. Now suppose we wanted to allow telnet to run as a service to computers on the LAN but not on the insecure internet. Well there is an easy way to do this. We can use -i for the input interface and -o for the output interface. You could always block it on the OUTPUT chain, but we'd rather block it on the INPUT so that the telnet daemon never even sees the request. Therefore we'll use -i. This should set up just the rule:

iptables -A INPUT -p tcp --destination-port telnet -i ppp0 -j DROP

So this should close off the port to anyone on the internet yet kept it open to the LAN. Now before we go on to more intense stuff, I'd like to briefly explain other ways to manipulate rules. The -A option appends a rule to the end of the list, meaning any matching rule before it will have say before this one does. If we wanted to put a rule before the end of the chain, we use -I for insert. This will put the rule in a numerical location in the chain. For example, if we wanted to put it at the top of the INPUT chain, we'd use "-I INPUT 1" along with the rest of the command. Just change the 1 to whatever place you want it to be in.

Now let's say we wanted to replace whatever rule was already in that location. Just use -R to replace a rule. It has the same syntax as -I and works the same way except that it deletes the rule at that position rather than bumping everything down. And finally, if you just want to delete a rule, use -D. This also has a similar syntax but you can either use a number for the rule or type out all the options that you would if you created the rule. The number method is usually the optimal choice. There are two more simple options to learn though. -L lists all the rules set so far. This is obviously helpful when you forget where you're at. AND -F flushes a certain chain. (It removes all of the rules on the chain.) If you don't specify a chain, it will basically flush everything.

Well let's get a bit more advanced. We know that these packets use a certain protocol, and if that protocol is TCP, then it also uses a certain port. Now you might be compelled to just close all ports to incoming traffic, but remember, after your computer talks to another computer, that computer must talk back. If you close all of your incoming ports, you'll essentially render your connection useless. And for most non-service programs, you can't predict which port they're going to be communicating on.

But there's still a way. Whenever two computers are talking over a TCP connection, that connection must first be initialized. This is the job of a SYN packet. A SYN packet simply tells the other computer that it's ready to talk. Now only the computer requesting the service sends a SYN packet.

So if you only block incoming SYN packets, it stops other computers from opening services on your computer but doesn't stop you from communicating with them. It roughly makes your computer ignore anything that it didn't speak to first. It's mean but it gets the job done. Well the option for this is --syn after you've specified the TCP protocol. So to make a rule that would block all incoming connections on only the internet:

iptables -A INPUT -i ppp0 -p tcp --syn -j DROP

That's a likely rule that you'll be using unless you have a web service running. If you want to leave one port open, for example 80 (HTTP), there's a simple way to do this too. As with many programming languages, an exclamation mark means not. For instance, if you wanted to block all SYN packets on all ports except 80, I believe it would look something like this:

iptables -A INPUT -i ppp0 -p tcp --syn --destination-port ! 80 -j DROP

It's somewhat complicated but it's not so hard to comprehend. There's one last thing I'd like to cover and that's changing the policy for a chain. The chains INPUT and OUTPUT are usually set to ACCEPT by default and FORWARD is set to DENY. Well if you want to use this computer as a router, you would probably want to set the FORWARD policy to ACCEPT. Well it's really very simple as to how we do this. All you have to do is use the -P option. Just follow it by the chain name and the new policy and you have it made. To change the FORWARD chain to an ACCEPT policy, we'd do this:

iptables -P FORWARD ACCEPT

 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here