Linux Netfilter

Contact

Please send suggestions to mattw@csh.rit.edu.

A few days before the release of this document, another tutorial(broken link) was released. The author of this document has an excellent description of the kernel configuration along with details of the actual filters. However, I believe my packet flow diagrams give a better idea of how NAT and filtering interact. I also think my rc.nat_firewall script is the most complete script to be released.

Abstract

The NetFilter package included in the Linux 2.4.x kernel is a replacement for ipchains. This document will show you how the netfilter software operates along with how-to construct a secure a trusted machine which sits between your home network and the public Internet.

Background

Starting in the 2.4.x kernel series, ipchains was depreciated in favor of Netfilter. To use Netfilter you will need to upgrade to a 2.4.x kernel. This document was written and tested using 2.3.99-pre9, 2.4.0, 2.4.2.

Options

The following options need to be enabled (*) in the kernel or made into modules (M):
Networking options:
Network Packet filtering (CONFIG_NETFILTER)=*
IP: Netfilter Configuration
Connection Tracking (CONFIG_IP_NF_CONNTRACK)=*
IP Tables Support (CONFIG_IP_NF_IPTABLES)=*
Limit Match Support (CONFIG_IP_NF_MATCH_LIMIT)=*
Netfilter MARK match support (CONFIG_IP_NF_MATCH_MARK)=*
Multiple Port Match Support (CONFIG_IP_NF_MATCH_MULTIPORT)=*
TOS Match Support (CONFIG_IP_NF_MATCH_TOS)=*
Connection Tracking Support (CONFIG_IP_NF_MATCH_STATE)=*
Unclean Match Support (CONFIG_IP_NF_MATCH_UNCLEAN)=*
Owner Match Support (CONFIG_IP_NF_MATCH_OWNER)=*
Packet Filtering (CONFIG_IP_NF_FILTER)=*
Reject Target Support (CONFIG_IP_NF_TARGET_REJECT)=*
Mirror Target Support (CONFIG_IP_NF_TARGET_MIRROR)=*
Full NAT (CONFIG_IP_NF_NAT)=*
Masquerade Target Support (CONFIG_IP_NF_TARGET_MASQUERADE)=*
Redirect Target Support (CONFIG_IP_NF_TARGET_REDIRECT)=*
Packet Mangling (CONFIG_IP_NF_MANGLE)=*
TOS Target Support (CONFIG_IP_NF_TARGET_TOS)=*
Mark Target Support (CONFIG_IP_NF_TARGET_MARK)=*
Log Target Support (CONFIG_IP_NF_TARGET_LOG)=*


NOTE: If you are using a 2.4.2 or greater kernel, turn on the following feature to support active ftp connection tracking. This will allow you to ftp from a DOS shell or web browsers that do not support passive ftp.

Connection Tracking (CONFIG_IP_NF_CONNTRACK)=*
FTP Protocol Support (CONFIG_IP_NF_FTP)=*

Feature Dependencies

Technically, the Netfilter Firewall could be installed on a standalone machine with only one network card, however, our example uses two network cards to create a home gateway.

The Netfilter Firewall

Netfilter provides flexibility for manipulating packets as they flow through the gateway. Depending upon the origin and destination of packets, they may be passed, blocked or redirected to another IP/port.

The logic path for Netfilter follows:


DNAT: Destination Network Address Translation
Routing Decision #1: Is the packet destined for a local process or external process?
Forward Filter: Packet Filtering
SNAT: Source Network Address Translation
Input Filter: Packets destined for a local process
Output Filter: Packets sourced from a local process
Routing Decision #2: Was the original packet in initiated via a local process?
DNAT #2: For packets associated with a local process, apply the proper destination NAT mapping.
Layer2 Function: Add appropriate MAC addresses to packet.

The Forward Chain

Packets routed between networks follow this path: DNAT Prerouting -> Forward -> SNAT Postrouting
This applies to all traffic not destined directly for a local process. For example, a SYN packet from internal network 192.168.42.0/24 destined for external network 129.21.60.0/24 passes through the prerouting DNAT transparently without address translation. Filtering is applied in the forward filter and source address translation occurs in SNAT postrouting. Return ACK packets follow the same path except destination address translation occurs in the DNAT prerouting module.

Since Netfilter is a stateful firewall, it maps outgoing connections to an anticipated return path, which means we won't have to apply additional NAT policies for the returned ACK's. In addition, the forward filter always sees network addresses from behind the NAT module (internal addresses). The forward filter is used to filter both inbound and outbound packets. Most inbound packets will usually be on the return data path of an established connection and cannot establish a new connection (unless you are running a service on the firewall). Therefore, blocking return packets is unnecessary, with the exception of maybe limiting ICMP echo-replies. An administrator could use the forward filter to prevent internal users from connecting to certain external sites or other networks attached to the firewall.

The forward filter applies mainly to host-to-host connections. Packets destined directly for the firewall are routed into the local process loop and pass through the INPUT / OUTPUT filters.

The Input / Output chains

Packets with a source address inside the home network destined for the firewall or to another internal network directly connected to it, will traverse the input and output filters. External connections seeking a service on the firewall also follow this path.

There are two different cases for using the local process loop. The first case deals with packets originating or destined for a network directly connected to the firewall. An inbound packet will traverse the DNAT Prerouting and Input modules, be serviced by the local process, and then get routed into the Output -> DNAT -> SNAT Postrouting modules.

In the second case, packets coming from a network not directly connected to the firewall. An inbound packet will traverse the DNAT Prerouting and Input modules, be serviced by the local process, and then get routed into the Output-postrouting -> Output -> DNAT -> SNAT Postrouting modules. It appears that the Output-postrouting filter provides flexibility for local verses non-local packets.

Downloads

Netfilter is included in the kernel sources found in the /usr/src/linux directory.
The kernel source code may be obtained via anonymous FTP at: ftp.us.kernel.org or kernel.csh.rit.edu
The user space tool, iptables, is available at: netfilter.kernelnotes.org

Documentation

A HOW-TO on compiling a kernel may be found atThe Linux Documentation Project.
Information on the Linux Netfilter software may be found at: netfilter.filewatcher.org.

Installation Procedure

After compiling and installing a kernel with the above configuration, install the user space iptables tool.

Installing Iptables Tool

Download iptabes-1.2.tar.bz2:
Unpack this by:
bunzip2 iptables-1.2.tar.bz2
tar xvf iptables-1.2.tar

Change into the iptables-1.2 directory and install by:
cd iptables-1.2
make
make install

Next remove any old ipchains modules from the current /lib/modules/2.4.0 directory.
cd /lib/modules/2.4.0/
rm -r ipchains.o ipfwadn.o
depmod -a

Configuration of Netfilter

Netfilter combines three network functions (forwarding, filtering, NAT) into one rule set using iptables. The series of commands that are used to set up iptables can be put into a shell script for ease of use.

The below script includes code to bring up a backup PPP line when my primary WAN (DSL line) goes down.

Create a new file named /usr/sbin/rc.nat_firewall that contains the following rules, which include NAT and firewall filtering.

The complete file looks like this:
#!/bin/sh
#netfilter chain set

IPTABLES=/usr/local/sbin/iptables
LOGGING=0
#change the shell's default delim. so we can do parsing.
OIFS="$IFS"
IFS=.

WANDEV=eth0
WANADDR=`/sbin/ifconfig $WANDEV | grep 'inet addr:' | awk '{print $2}' | cut -c6-20` set -- $WANADDR A="$1" B="$2" C="$3" D="$4" 
WANNET=$1.$2.$3
WANBCAST=$WANNET.255
WANNET=$WANNET.0/24

INTDEV=eth1
INTADDR=`/sbin/ifconfig $INTDEV | grep 'inet addr:' | awk '{print $2}' | cut -c6-20` set -- $INTADDR A="$1" B="$2" C="$3" D="$4" 
INTNET=$1.$2.$3
INTBCAST=$INTNET.255
INTNET=$INTNET.0/24

STATICDEV=eth2
STATICADDR=`/sbin/ifconfig $STATICDEV | grep 'inet addr:' | awk '{print $2}' | cut -c6-20` set -- $STATICADDR A="$1" B="$2" C="$3" D="$4" 
STATICNET=$1.$2.$3
STATICBCAST=$STATICNET.255
STATICNET=$STATICNET.0/24

PPPDEV=ppp0
PPPSTAT=`/sbin/ifconfig -a | grep $PPPDEV`

if ["$PPPSTAT" = ""]
then
	PPPSTAT="0"
else
	echo "PPP is up"
	PPPSTAT="1"
	PPPADDR=`/sbin/ifconfig $PPPDEV | grep 'inet addr:' | awk '{print $2}' | cut -c6-20` set -- $PPPADDR A="$1" B="$2" C="$3" D="$4" 
	PPPNET=$1.$2.$3
	PPPBCAST=$PPPNET.255
	PPPNET=$PPPNET.0/24
fi

IFS=$OIFS	#reset the IFS environment var.

#################################################
#Flush all filters and NAT tables.
$IPTABLES -t nat -F PREROUTING
$IPTABLES -t nat -F POSTROUTING
$IPTABLES -t nat -F OUTPUT
$IPTABLES -F INPUT
$IPTABLES -F OUTPUT
$IPTABLES -F FORWARD

if ["$LOGGING" -eq 1]
then
$IPTABLES -A FORWARD -j LOG --log-level 7 --log-prefix FORWARD
$IPTABLES -A INPUT -j LOG --log-level 7 --log-prefix INPUT
$IPTABLES -A OUTPUT -j LOG --log-level 7 --log-prefix OUTPUT
$IPTABLES -t nat -A POSTROUTING -j LOG --log-level 7 --log-prefix POSTROUTING
$IPTABLES -t nat -A PREROUTING -j LOG --log-level 7 --log-prefix PREROUTING
$IPTABLES -t nat -A OUTPUT -j LOG --log-level 7 --log-prefix OUTPUT-ROUTING
fi


#Turn NAT on.
$IPTABLES -t nat -A POSTROUTING -s $INTNET -o eth0 -j MASQUERADE
$IPTABLES -t nat -A POSTROUTING -s $STATICNET -o eth0 -j MASQUERADE
$IPTABLES -t nat -A POSTROUTING -s $INTNET -o ppp0 -j MASQUERADE
$IPTABLES -t nat -A POSTROUTING -s $STATICNET -o ppp0 -j MASQUERADE

#Default Policy
$IPTABLES -P INPUT DROP
$IPTABLES -P FORWARD ACCEPT
$IPTABLES -P OUTPUT ACCEPT

#INPUT Filter
#drop fragments & invalid packets
$IPTABLES -A INPUT -f -j DROP
$IPTABLES -A INPUT -m state --state INVALID -j DROP

#unclean match target (not stable in NETFILTER package)
$IPTABLES -A INPUT -m unclean -j DROP

#spoofing - drop packets with our address as source, except ICMP
$IPTABLES -A INPUT -s $WANADDR -i $WANDEV -p ! ICMP -j DROP

#smurf attacks - disallow ICMP to our broadcast.
$IPTABLES -A INPUT -p icmp -i $WANDEV -d $WANBCAST -j DROP

#stop syn-flood, ping-o-death, & fast port scanning
$IPTABLES -A INPUT -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP
$IPTABLES -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
$IPTABLES -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP

$IPTABLES -A INPUT -i $WANDEV -m limit --limit 1/s -p tcp --tcp-flags ALL RST -m multiport --dports 80,10000 -j ACCEPT
$IPTABLES -A INPUT -i $WANDEV -m limit --limit 1/s -p tcp --tcp-flags ALL FIN -m multiport --dports 80,10000 -j ACCEPT
$IPTABLES -A INPUT -i $WANDEV -m limit --limit 1/s -p tcp --tcp-flags ALL SYN -m multiport --dports 80,10000 -j ACCEPT

#ICMP
$IPTABLES -A INPUT -i $WANDEV -m limit --limit 1/s -p icmp --icmp-type 0 -j ACCEPT
$IPTABLES -A INPUT -i $WANDEV -m limit --limit 1/s -p icmp --icmp-type 3 -j ACCEPT
$IPTABLES -A INPUT -i $WANDEV -m limit --limit 1/s -p icmp --icmp-type 4 -j ACCEPT
$IPTABLES -A INPUT -i $WANDEV -m limit --limit 1/s -p icmp --icmp-type 8 -j ACCEPT
$IPTABLES -A INPUT -i $WANDEV -m limit --limit 1/s -p icmp --icmp-type 11 -j ACCEPT

#PPP protection, when it's up.
if [ "$PPPSTAT" -eq 1 ]
then
	echo "Configuring PPP interface with IPCHAIN rules."
	$IPTABLES -A INPUT -s $PPPADDR -i $PPPDEV -p ! ICMP -j DROP

	#smurf attacks - disallow ICMP to our broadcast.
	$IPTABLES -A INPUT -p icmp -i $PPPDEV -d $WANBCAST -j DROP

	#stop syn-flood, ping-o-death, & fast port scanning
	$IPTABLES -A INPUT -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP
	$IPTABLES -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
	$IPTABLES -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP

	$IPTABLES -A INPUT -i $PPPDEV -m limit --limit 1/s -p tcp --tcp-flags ALL RST -m multiport --dports 80,10000 -j ACCEPT
	$IPTABLES -A INPUT -i $PPPDEV -m limit --limit 1/s -p tcp --tcp-flags ALL FIN -m multiport --dports 80,10000 -j ACCEPT
	$IPTABLES -A INPUT -i $PPPDEV -m limit --limit 1/s -p tcp --tcp-flags ALL SYN -m multiport --dports 80,10000 -j ACCEPT

	#ICMP
	$IPTABLES -A INPUT -i $PPPDEV -m limit --limit 1/s -p icmp --icmp-type 0 -j ACCEPT
	$IPTABLES -A INPUT -i $PPPDEV -m limit --limit 1/s -p icmp --icmp-type 3 -j ACCEPT
	$IPTABLES -A INPUT -i $PPPDEV -m limit --limit 1/s -p icmp --icmp-type 4 -j ACCEPT
	$IPTABLES -A INPUT -i $PPPDEV -m limit --limit 1/s -p icmp --icmp-type 8 -j ACCEPT
	$IPTABLES -A INPUT -i $PPPDEV -m limit --limit 1/s -p icmp --icmp-type 11 -j ACCEPT

fi

#Allowing existing connections
$IPTABLES -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPTABLES -A INPUT -m state --state NEW -p ICMP -j ACCEPT

#allow incoming web traffic from WAN & LANs.
$IPTABLES -A INPUT -s 0/0 -p tcp -m multiport --dport 80,10000 -j ACCEPT
$IPTABLES -A INPUT -s 0/0 -p udp -m multiport --dport 80,10000 -j ACCEPT

#allow DNS queries from LAN
$IPTABLES -A INPUT -i $INTDEV -p tcp --dport 53 -j ACCEPT
$IPTABLES -A INPUT -i $INTDEV -p udp --dport 53 -j ACCEPT
$IPTABLES -A INPUT -i $STATICDEV -p tcp --dport 53 -j ACCEPT
$IPTABLES -A INPUT -i $STATICDEV -p udp --dport 53 -j ACCEPT

#allow SMB requests from LAN
$IPTABLES -A INPUT -i $INTDEV -p tcp --dport 137:139 -j ACCEPT
$IPTABLES -A INPUT -i $INTDEV -p udp --dport 137:139 -j ACCEPT
$IPTABLES -A INPUT -i $STATICDEV -p tcp --dport 137:139 -j ACCEPT
$IPTABLES -A INPUT -i $STATICDEV -p udp --dport 137:139 -j ACCEPT

#allow from lo
$IPTABLES -A INPUT -i lo -j ACCEPT
###########################################################################
#FORWARD Filter
#all forwards are allowed.

#end rc.nat_firewall
After creating this file, make it executable:
chmod 755 rc.nat_firewall

Next, in /etc/rc.d/rc.local add the following lines:
#IP NAT
echo "Starting IP NAT"
/usr/sbin/rc.nat_firewall

I was lucky enough to also have a dial-up back up line. To monitor the network connection use a script called net-check.pl.

Modify the following lines: Line 113:
sleep 40;
$fake = '/usr/sbin/rc.nat_firewall';
print "\n Refreshing NAT";


and line

Line 164:
# $fake = `/etc/rc.d/init.d/network restart`; # kills our
$fake = '/usr/sbin/rc.nat_firewall'; #restart NAT & netfilter
print "\n Refreshing NAT";


Be sure to run lilo before rebooting to initialize the new kernel and iptables: lilo
reboot


Debugging and Troubleshooting

Local clients cannot get to outside network
-Make sure that packet forwarding is enabled by:
echo 1 > /proc/sys/net/ipv4/ip_forward
echo 1 > /proc/sys/net/ipv4/ip_dynaddr


Check the filter configuration:
cat /proc/net/ip_conntrack
iptables -L


Checking the NAT configuration:
iptables -L -t nat

Turn debugging on:
Add this line to /etc/syslog.conf:
*.debug /var/log/iptables.log:

Restart syslog::
/etc/rc.d/init.d/syslog restart:

In /usr/sbin/rc.nat_firewall change::
LOGGING=0
to:
LOGGING=1

Rerun /usr/sbin/rc.nat_firewall. The log will show up in /var/log/iptables.log
To view a continuous trace of the log, use: tail -f /var/log/iptables.log

FTP from web browser fails
Open the options menu for the browser
Check the 'View FTP via browser' box. (name might vary slightly)

Known Caveats

An attacker directly connected to the WAN subnet may be able to send packets into the LAN if the attacker knows addresses on the LAN subnet. However, packets will not be returned to the attacker. This is still under investigation and only poses a small threat to internal users.

Active FTP does not work under the 2.4.0 kernel because the module (ip_conntrack_ftp) does not compile and link properly. 2.4.2 fixes this compilation issue and adds ftp connection tracking.