Simple stateful firewall
This page explains how to set up a simple stateful firewall using iptables. It also tries to explain what the rules mean and why they are needed. For simplicity, this HOWTO is split into two parts. The first part deals with a single machine, the second part sets up a NAT gateway.
BIG FAT WARNING: For simplicity, the rules are given in the order that they are executed later. If you are logged into a remote machine, following the rules in the exact order they are given MAY result in your machine being unavailable to the network. Only do the steps below while you are logged in locally unless you know what you are doing. You have been warned.
- 1 Prerequisites
- 2 Setting up a single machine
- 2.1 Creating necessary chains
- 2.2 The INPUT chain
- 2.3 The FORWARD chain
- 2.4 The OUTPUT chain
- 2.5 The interfaces chain
- 2.6 The open chain
- 2.7 Protection against common attacks
- 2.8 "Hide" your computer
- 2.9 Saving the rules
- 3 Setting up a NAT gateway
- 4 Working with knockd
- 5 See Also
Before we start, we need to make sure that the necessary tools are available:
$ pacman -Q iptables iptables 1.3.5-1
If iptables is installed, you have to make sure that your kernel supports iptables. All Arch Linux stock kernels have iptables support.
This HOWTO assumes that there are currently no iptables rules set. To check this, try the command
# iptables -nvL Chain INPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 0K packets, 0 bytes) pkts bytes target prot opt in out source destination
If the output looks like the above (except the packet counters), there are no rules set and you are good to go. If not, reset the tables like this:
# iptables -P INPUT ACCEPT # iptables -P FORWARD ACCEPT # iptables -P OUTPUT ACCEPT # iptables -F # iptables -X
Setting up a single machine
Creating necessary chains
For this basic setup, we will create two custom chains. Their meaning is explained later:
# iptables -N open # iptables -N interfaces
The INPUT chain
Every packet that is received by any network interface and has one of the local host's IP addresses in the destination header will pass the INPUT chain first. In this chain we make sure that only the packets that we want are accepted.
The first rule will accept all ICMP messages. ICMP means Internet Control Message Protocol. Some ICMP messages are very important, some are less important (like echo requests (pings)), but none of them hurt, so it is generally a good idea not to block them:
# iptables -A INPUT -p icmp -j ACCEPT
The next rule will make sure that none of the traffic that belongs to already established connections will be dropped. This can be done with the state match. A package can have one of the four states ESTABLISHED, RELATED, NEW and INVALID. So, we want to accept all packets that are in state ESTABLISHED or RELATED, hence the name "stateful firewall":
# iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
In most cases, we do not want to deny all incoming connections, and that's why we set up the two custom chains open and interfaces. For now though, we will add a rule for each of them:
# iptables -A INPUT -j interfaces # iptables -A INPUT -j open
Now, with the last two rules, we drop everything that hasn't been explicitly accepted above. For TCP packets, we deny the connection with a tcp-reset. Incoming UDP packets are answered with an ICMP message. This way, we imitate Linux's default behaviour:
# iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset # iptables -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
All other protocols than TCP, UDP and ICMP are dropped (unless they matched the state match above). We do this by setting the policy for the INPUT chain to DROP
# iptables -P INPUT DROP
The FORWARD chain
If you want to set up your machine as a NAT gateway, please look at the second section of this HOWTO. For a single machine, however, we simply set the policy of the FORWARD chain to DROP and move on:
# iptables -P FORWARD DROP
The OUTPUT chain
We have no intention of filtering any outgoing traffic, as this would make the setup much more complicated and would require some extra thought. In this simple case, we set the OUTPUT policy to ACCEPT.
# iptables -P OUTPUT ACCEPT
The interfaces chain
We use the interfaces chain to accept any traffic from trusted interfaces. The first rule is absolutely necessary:
# iptables -A interfaces -i lo -j ACCEPT
This accepts every traffic from the loopback interface, which is necessary for many applications to work properly. You can add more interfaces here. For example, if you want to accept all incoming traffic from the interface eth0, add this rule:
# iptables -A interfaces -i eth0 -j ACCEPT
Incoming connections on other interfaces will be denied, unless they hit another exception in the open chain.
The open chain
The open chain contains rules for accepting incoming connections on specific ports or protocols. For example, if you want to accept ssh connections on every interface, add this rule:
# iptables -A open -p tcp --dport 22 -j ACCEPT
However, it is probably not a good idea to let the world connect to your machine on port 22 unless you run a public server. Therefore, you can limit which machines can connect to port 22 by modifying the /etc/hosts.allow file :
# Let local users connect via ssh sshd: 127.0.0.1 # Allow these adress to connect via ssh sshd: 192.168.0.1 sshd: 172.272.0.32
To accept incoming HTTP connections on the interface ppp0:
# iptables -A open -i ppp0 -p tcp --dport 80 -j ACCEPT
To accept all incoming tcp connections with destination ports 65000 to 65005 on interface foo:
# iptables -A open -i foo -p tcp --dport 65000:65005 -j ACCEPT
The same is of course possible with udp:
# iptables -A open -i foo -p udp --dport 65000:65005 -j ACCEPT
or with other protocols than tcp and udp:
# iptables -A open -i foo -p 123 -j ACCEPT
See man iptables for more advanced rules, like matching multiple ports or protocols.
Protection against common attacks
Force SYN packets check
Make sure NEW incoming tcp connections are SYN packets; otherwise, we need to drop them:
iptables -A INPUT -p tcp ! --syn -m state --state NEW -j DROP
Force Fragments packets check
Packets with incoming fragments. Drop them.
iptables -A INPUT -f -j DROP
Incoming malformed XMAS packets. Drop them:
iptables -A INPUT -p tcp --tcp-flags ALL ALL -j DROP
Drop all NULL packets
Incoming malformed NULL packets:
iptables -A INPUT -p tcp --tcp-flags ALL NONE -j DROP
Blocking reserved private networks incoming from the internet
iptables -I INPUT -i eth0 -s 10.0.0.0/8 -j DROP iptables -I INPUT -i eth0 -s 172.16.0.0/12 -j DROP iptables -I INPUT -i eth0 -s 192.168.0.0/16 -j DROP iptables -I INPUT -i eth0 -s 127.0.0.0/8 -j DROP
You can also add the following line to your /etc/sysctl.conf to enable source address verification which is built into Linux kernel itself.
net.ipv4.conf.all.rp_filter = 1
"Hide" your computer
If you are running a desktop machine, it might be a good idea to block some incoming requests.
Block Ping Request
A 'Ping' request is an ICMP packet sent to the destination address to ensure connectivity between the devices. If your network works well, you can safely block all ping requests.
iptables -A INPUT -p icmp --icmp-type echo-request -i eth0 -j DROP
You can also add the following line to your /etc/sysctl.conf file:
net.ipv4.icmp_echo_ignore_all = 1
ICMP type match blocking
If your computer is not a router (like most desktops):
iptables -I INPUT -p icmp --icmp-type redirect -j DROP iptables -I INPUT -p icmp --icmp-type router-advertisement -j DROP iptables -I INPUT -p icmp --icmp-type router-solicitation -j DROP iptables -I INPUT -p icmp --icmp-type address-mask-request -j DROP iptables -I INPUT -p icmp --icmp-type address-mask-reply -j DROP
Block nmap's uptime detection
Prevent uptime detection from port scanners like nmap
Add the following line to your /etc/sysctl.conf file
net.ipv4.tcp_timestamps = 0
Other kernel parameters
Add the following lines to your /etc/sysctl.conf file to prevent certain kinds of attacks:
net.ipv4.conf.all.accept_source_route = 0 net.ipv4.icmp_echo_ignore_broadcasts = 1 net.ipv4.icmp_ignore_bogus_error_responses = 1
Saving the rules
Now, the rules are ready and should be saved to your hard drive. First, we need edit the configuration file /etc/conf.d/iptables:
# Configuration for iptables rules IPTABLES=/usr/sbin/iptables IPTABLES_CONF=/etc/iptables/iptables.rules IPTABLES_FORWARD=0 # disable IP forwarding!!!
You can specify another filename than iptables.rules if you want.
Now, save the rules with the command:
# /etc/rc.d/iptables save
and make sure your rules are loaded when you boot by editing /etc/rc.conf, iptables should be added preferably before 'network'.
DAEMONS=(... iptables network ...)
Setting up a NAT gateway
This section of the HOWTO deals with NAT gateways. It is assumed that you already read the first part of the HOWTO and set up the INPUT, OUTPUT, open and interfaces chains like described above. All rules so far have been created in the filter table. In this section, we will also have to use the nat table.
Setting up the filter table
Before we start, make sure that the FORWARD chain is empty:
# iptables -F FORWARD
Creating necessary chains
In our setup, we will use another two chains in the filter table, the fw-interfaces and fw-open chains. Create them with the commands
# iptables -N fw-interfaces # iptables -N fw-open
Setting up the FORWARD chain
Setting up the FORWARD chain is similar to the INPUT chain in the first section.
Some networks and servers block ICMP messages and are preventing path MTU detection. If your outgoing interface's MTU is lower than the MTU on the local network (like with PPPoE connections), this may prevent you from communicating with such servers. The first rule works around this problem, at least for TCP connections:
# iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
Now we set up a rule with the state match, identical to the one in the INPUT chain:
# iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
The next step is to enable forwarding for trusted interfaces and to make all packets pass the fw-open chain.
# iptables -A FORWARD -j fw-interfaces # iptables -A FORWARD -j fw-open
The remaining packets are denied with an ICMP message:
# iptables -A FORWARD -j REJECT --reject-with icmp-host-unreachable # iptables -P FORWARD DROP
Setting up the fw-interfaces and fw-open chains
The meaning of the fw-interfaces and fw-open chains is explained later, when we deal with the POSTROUTING and PREROUTING chains in the nat table, respectively.
Setting up the nat table
All over this section, we assume that the outgoing interface (the one with the public internet IP) is ppp0. Keep in mind that you have to change the name in all following rules if your outgoing interface has another name.
Setting up the POSTROUTING chain
Now, we have to define who is allowed to connect to the internet. Let's assume we have the subnet 192.168.0.0/255.255.255.0 (which means all addresses that are of the form 192.168.0.*) on eth0. We first need to accept the machines on this interface in the FORWARD table, that's why we created the fw-interfaces chain above:
# iptables -A fw-interfaces -i eth0 -j ACCEPT
Now, we have to alter all outgoing packets so that they have our public IP address as the source address, instead of the local LAN address. To do this, we use the MASQUERADE target:
# iptables -t nat -A POSTROUTING -s 192.168.0.0/255.255.255.0 -o ppp0 -j MASQUERADE
Don't forget the -o ppp0 parameter above, if you omit it, your network will be screwed up.
Let's assume we have another subnet, 10.3.0.0/255.255.0.0 (which means all addresses 10.3.*.*) on interface eth1. We add the same rules as above again:
# iptables -A fw-interfaces -i eth1 -j ACCEPT # iptables -t nat -A POSTROUTING -s 10.3.0.0/255.255.0.0 -o ppp0 -j MASQUERADE
The last step is to enable IP Forwarding (if it isn't already enabled):
# echo 1 > /proc/sys/net/ipv4/ip_forward
Machines from these subnets can now use your new NAT machine as their gateway. Note that you may want to set up a DNS and DHCP server like dnsmasq or a combination of bind and dhcpd to simplify network settings DNS resolution on the client machines. This is not the topic of this HOWTO.
Setting up the PREROUTING chain
Sometimes, we want to forward an incoming connection from the gateway to a LAN machine. To do this, we use the fw-open chain defined above, as well as the PREROUTING chain in the nat table
I will give two simple examples: First, we want to forward all incoming SSH connections (port 22) to the ssh server in the machine 192.168.0.5:
# iptables -A fw-open -d 192.168.0.5 -p tcp --dport 22 -j ACCEPT # iptables -t nat -A PREROUTING -i ppp0 -p tcp --dport 22 -j DNAT --to 192.168.0.5
The second example will show you how to forward packets to a different port than the incoming port. We want to forward any incoming connection on port 8000 to our web server on 192.168.0.6, port 80:
# iptables -A fw-open -d 192.168.0.6 -p tcp --dport 80 -j ACCEPT # iptables -t nat -A PREROUTING -i ppp0 -p tcp --dport 8000 -j DNAT --to 192.168.0.6:80
The same setup also works with udp packets.
Saving the rules
Like above, we have to save the rules. Only this time, we have to enable IP forwarding in /etc/conf.d/iptables:
# Configuration for iptables rules IPTABLES=/usr/sbin/iptables IPTABLES_CONF=/etc/iptables/iptables.rules IPTABLES_FORWARD=1 # enable IP forwarding!!!
Save the rules
# /etc/rc.d/iptables save
and make sure your rules are loaded when you boot
DAEMONS=(... iptables ...)
Working with knockd
knockd is a port knocking daemon that can provide an added layer of security to your network. The knockd wiki provides three example port knocking configurations. These configs can be easily altered to intergrate properly with firewall described here. You should simply substitue the INPUT chain specification, with the custom open chain used in the firewall.
[options] logfile = /var/log/knockd.log [opencloseSSH] sequence = 2222:udp,3333:tcp,4444:udp seq_timeout = 15 tcpflags = syn,ack start_command = /usr/sbin/iptables -A open -s %IP% -p tcp --syn --dport 22 -j ACCEPT cmd_timeout = 10 stop_command = /usr/sbin/iptables -D open -s %IP% -p tcp --syn --dport 22 -j ACCEPT
It is wise to randomly select the ports that you use for the knock sequence. Random.org [link] can help you generate a selection of ports between 1 and 65535. Once you have selected your port range check that you haven't inadvertantly selected a commonly used port; this port database can help you check.