Basic packet filtering

Getting started

Before we begin it is a good idea to make sure that we are starting from the same point. To that end let's start by creating a simple script which we shall use to configure our firewall.

lisa nano -w /usr/local/sbin/config-firewall

Type the following into the new script and save it.

/usr/local/sbin/config-firewall
#!/bin/bash

iptables -F
iptables -X

Then make it executable and execute it.

lisa chmod +x /usr/local/sbin/config-firewall
lisa config-firewall

The first command in the script will flush all the rules in all the chains. The second command will remove any user-defined chains. Chains cannot be removed if they contain rules hence the flush command.

Information:
Although we haven't mentioned it yet, unless a table is specified using the -t command line switch, all iptables commands will operate on the FILTER table by default. As that is the only table we are interested in working with at this stage there is no need to specify it on the command line.
 

We can now run the command below to display the status of the chains.

lisa iptables -L -v
Chain INPUT (policy ACCEPT 546 packets, 47801 bytes) 
 pkts bytes target     prot opt in     out     source               destination 
 
Chain FORWARD (policy DROP 0 packets, 0 bytes) 
 pkts bytes target     prot opt in     out     source               destination 
 
Chain OUTPUT (policy ACCEPT 448 packets, 58973 bytes) 
 pkts bytes target     prot opt in     out     source               destination 

As you can see there has been some activity on both the INPUT and OUTPUT chains, in this case because I'm connected to the system using ssh, and their policy is set to ACCEPT. The FORWARD chain however has seen no activity and has its policy set to DROP.

If your FORWARD chain has a policy of ACCEPT, and it probably does as that is the default setting, you can change it to DROP by issuing the following command. You should also add it to the top of the script above so that it will be set when the script is run.

lisa iptables -P FORWARD DROP

Logging Netfilter activity

Before we start to configure our firewall it would probably be a good idea to cover how we can generate a log of exactly what effect our rules are having. After all, when we make a mistake, and we will, it would be nice to know what is happening. It is also a relatively simple task which makes it a good introduction to the basic concepts which we shall be building upon as we progress with our configuration.

Add the following lines to the end of the script which we created earlier.

/usr/local/sbin/config-firewall
iptables -N LOGDROP
iptables -A LOGDROP -j LOG --log-prefix 'FIREWALL - DROP:' --log-level info
iptables -A LOGDROP -j DROP
Warning:
If you are connected to the system you are configuring the firewall on over ssh then it is probably a very good idea to comment out the DROP rule above until you are sure your configuration works as a mistake could easily render the system unreachable. You have been warned.
 

Then execute the script and display the Netfilter status as shown below.

lisa config-firewall
lisa iptables -L -v
Chain INPUT (policy ACCEPT 5505 packets, 582K bytes) 
 pkts bytes target     prot opt in     out     source               destination 
 
Chain FORWARD (policy DROP 0 packets, 0 bytes) 
 pkts bytes target     prot opt in     out     source               destination 
 
Chain OUTPUT (policy ACCEPT 2073 packets, 772K bytes) 
 pkts bytes target     prot opt in     out     source               destination 
 
Chain LOGDROP (0 references) 
 pkts bytes target     prot opt in     out     source               destination 
    0     0 LOG        all  --  any    any     anywhere             anywhere            LOG level info prefix `FIREWALL - DROP:' 
    0     0 DROP       all  --  any    any     anywhere             anywhere 

As you can see the first command in our scriptlet above created a new user-defined chain called LOGDROP. We then added two rules, neither of which has a match specification, to our new chain with the target of LOG and DROP respectively. The output above also shows that this chain is not referenced from any other chains. Also of note is that we specified a prefix for the log messages so that we can easily identify them visually or when using the a system logger such as syslog-ng.

Caution:
If you get an error message when trying to add the LOG rule you almost certainly didn't include the LOG extension module when you built the kernel. If you built the extension modules as loadable kernel modules then you should make sure that it is loaded, and that it will be loaded automatically on start-up as it will be needed when the iptables init script is executed.
 

While the rule above is good for packets that we just want to drop and pretend we never saw, sometimes it is useful to send a response indicating why we refused the connection. This can be accomplished using the REJECT target. All the standard ICMP error codes are supported, as well as other responses such as a TCP reset. If the response type is not specified then an icmp-port-unreachable message is sent. We shall be covering the REJECT target in more detail later.

Filtering invalid packets

Now that we have the ability to log the results of our activity we can get our hands dirty with some real rules. Lets start with a very simple rule which will allow us to filter any packets which are obviously malformed or invalid in the context which they are received. A good example of such a packet would be a TCP packet without the SYN flag set which is not part of an already established stream.

We can begin to achieve this goal by adding the lines below to the end of our script.

/usr/local/sbin/config-firewall
iptables -N TCPCHECK
iptables -A TCPCHECK -p tcp ! --syn -m state --state NEW -j LOGDROP

Now execute the script and display the Netfilter status again as shown below.

lisa config-firewall
lisa iptables -L -v
... 
Chain LOGDROP (1 references) 
 pkts bytes target     prot opt in     out     source               destination 
    0     0 LOG        all  --  any    any     anywhere             anywhere            LOG level warning prefix `FIREWALL - DROP:' 
    0     0 DROP       all  --  any    any     anywhere             anywhere 
 
Chain TCPCHECK (0 references) 
 pkts bytes target     prot opt in     out     source               destination 
    0     0 LOGDROP    tcp  --  any    any     anywhere             anywhere            tcp flags:!FIN,SYN,RST,ACK/SYN state NEW 

When you execute the above commands you should see output similar to that shown above. As you can see the first line of our additions simply created a new user-defined chain called TCPCHECK, just as we did before when creating the LOGDROP chain.

Information:
We could have added these rules to the INPUT chain and achieved the same result. Adding them to a chain of their own, however, allows them to be reused in more than one place without adding more rules, and by extension, more potential places to make mistakes, to the system.
 

The second line is more complex as it is composed of two match specifiers and a jump target. The first match specifier in this case is composed of two parts. The first part, -p tcp, checks to see if this packet is a TCP packet. If it is then the second part, ! --syn, checks to see if it does not have just the SYN flag set. Any packets which have matched the first specifier will go on to be tested against the second. This match uses the state matcher to see if this packet is considered to be starting a NEW connection. If the packet also matches this second condition then the -j part of the command will cause it to be sent to the LOGDROP chain that we created earlier. As it will encounter a terminating rule in that chain processing of that packet will cease.

Caution:
As the ! --syn part of the above matcher is in fact an extension to the -p tcp matcher we have to include this portion again even though we shall only ever be inspecting TCP packets with this chain.
 

The Netfilter connection tracking subsystem comes with a matcher which can be used to detect a limited range of invalid packets. We can include it in the TCPCHECK chain by adding the following line to the end of our script.

/usr/local/sbin/config-firewall
iptables -A TCPCHECK -m state --state INVALID -j LOGDROP

Putting it all together

Since we have just created a chain to filter malformed TCP packets it would be nice to put it to work as soon as possible. We can achieve this using only the skills we have learnt so far too, so lets get on with it. Add the lines below to the bottom of the script as usual.

/usr/local/sbin/config-firewall
iptables -N TCP-IN-FILTER
iptables -A TCP-IN-FILTER -j TCPCHECK

iptables -A INPUT -p tcp -j TCP-IN-FILTER

When you execute the script again and then show the Netfilter status you should see output similar to that given below.

lisa config-firewall
lisa iptables -L -v
Chain INPUT (policy ACCEPT 26209 packets, 3007K bytes) 
 pkts bytes target         prot opt in     out     source           destination 
   21  1572 TCP-IN-FILTER  tcp  --  any    any     anywhere         anywhere 
 
Chain FORWARD (policy DROP 0 packets, 0 bytes) 
 pkts bytes target         prot opt in     out     source           destination 
 
Chain OUTPUT (policy ACCEPT 6872 packets, 1918K bytes) 
 pkts bytes target         prot opt in     out     source           destination 
 
Chain LOGDROP (1 references) 
 pkts bytes target         prot opt in     out     source           destination 
    0     0 LOG            all  --  any    any     anywhere         anywhere            LOG level warning prefix `FIREWALL - DROP:' 
    0     0 DROP           all  --  any    any     anywhere         anywhere 
 
Chain TCP-IN-FILTER (1 references) 
 pkts bytes target         prot opt in     out     source           destination 
   21  1572 TCPCHECK       all  --  any    any     anywhere         anywhere 
 
Chain TCPCHECK (1 references) 
 pkts bytes target         prot opt in     out     source           destination 
    0     0 LOGDROP        tcp  --  any    any     anywhere         anywhere            tcp flags:!FIN,SYN,RST,ACK/SYN state NEW 
    0     0 LOGDROP        all  --  any    any     anywhere         anywhere            state INVALID 

As you can see from the above output we have achieved a lot with very little work. We have created a new chain called TCP-IN-FILTER, to which we shall add jumps to our input filtering subroutines. We have added such a jump to include the TCPCHECK chain. We have also added a rule to the INPUT chain so that all incoming TCP packets will be sent to the TCP-IN-FILTER chain for processing. You can see from the numbers in the pkts column that it is working. In the above example there have been 21 packets processed so far, none of which were found wanting.

Congratulations! You have just set your first functional Netfilter policy.