Networking / Beginners

Setting Up a Single-Host Firewall

You want to know how to build a firewall on a Linux computer that is running no public services. Just an ordinary PC that may be directly connected to the Internet, or it may be a laptop that travels a lot. You're careful with your application-level security and internal services, but you wisely believe in layered security and want a firewall.

You need to create an iptables script, and to edit the /etc/sysctl.conf file.

First, copy this iptables script, substituting your own IP addresses and interface names, and make it owned by root, mode 0700. In this section we'll call it /usr/local/bin/fw_host:

#!/bin/sh
##/usr/local/bin/fw_host
#iptables firewall script for
#a workstation or laptop
#chkconfig: 2345 01 99

#define variables
ipt="/sbin/iptables"
mod="/sbin/modprobe"

#Flush all rules, delete all chains
$ipt -F
$ipt -X
$ipt -t nat -F
$ipt -t nat -X
$ipt -t mangle -F
$ipt -t mangle -X

#Zero out all counters
$ipt -Z
$ipt -t nat -Z
$ipt -t mangle -Z

#basic set of kernel modules
$mod ip_tables
$mod ip_conntrack
$mod iptable_filter
$mod iptable_nat
$mod iptable_mangle
$mod ipt_LOG
$mod ipt_limit
$mod ipt_state
$mod ipt_MASQUERADE

#optional for irc and ftp
#$mod ip_conntrack_irc
#$mod ip_conntrack_ftp

#Set default policies
#Incoming is deny all,
#outgoing is unrestricted
$ipt -P INPUT DROP
$ipt -P FORWARD DROP
$ipt -P OUTPUT ACCEPT
$ipt -t nat -P OUTPUT ACCEPT
$ipt -t nat -P PREROUTING ACCEPT
$ipt -t nat -P POSTROUTING ACCEPT
$ipt -t mangle -P PREROUTING ACCEPT
$ipt -t mangle -P POSTROUTING ACCEPT

#this line is necessary for the loopback interface
#and internal socket-based services to work correctly
$ipt -A INPUT -i lo -j ACCEPT

#Reject connection attempts not initiated from the host
$ipt -A INPUT -p tcp --syn -j DROP

#Allow return traffic initiated from the host
$ipt -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Accept important ICMP packets
$ipt -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
$ipt -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT
$ipt -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT

Add this script to your desired runlevels. This command adds it to runlevels 2-5 on Debian:

# update-rc.d firewall start 01 2 3 4 5 . stop 99 0 1 6 .

On Fedora, use chkconfig:

# chkconfig firewall --add
# chkconfig firewall on

Note that both of these commands turn off the firewall on runlevels 0, 1, and 6. This is a standard practice, as typically networking is also shut down on these runlevels, and only a bare set of services are started.

Now, add these kernel parameters to /etc/sysctl.conf:

net.ipv4.ip_forward = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.accept_source_route = 0

If you are using dial-up networking or are on DHCP, add this parameter as well:

net.ipv4.conf.all.ip_dynaddr = 1

To activate everything without rebooting, run these commands:

# firewall_host
# /sbin/sysctl -p

And you now have a nice restrictive host firewall. See the previous sections in this tutorial to learn how to start the firewall at boot, manually stop and start it, and display its current status. All you do is follow the sections, replacing the fw_nat script with fw_host.

You may wish to add rules to allow various peer services such as instant messaging or BitTorrent, or to allow SSH. Use this rule with the appropriate port ranges for the protocol you want to allow incoming client requests from:

$ipt -A INPUT -p tcp --destination-port [port range] -j ACCEPT

Then, delete this rule:

#Reject connection attempts not initiated from the host
$ipt -A INPUT -p tcp --syn -j DROP

and add this one:

#Drop NEW tcp connections that are not SYN-flagged
$ipt -A INPUT -p tcp ! --syn -m state --state NEW -j DROP

To simplify maintaining the script, you may create whatever variables you like in the #define variables section. Commands, network interfaces, and IP addresses are the most common variables used in iptables scripts.

Flushing all the rules, deleting all chains, and resetting packet and byte counters to zero ensures that the firewall starts up with a clean slate, and no leftover rules or chains are hanging around to get in the way.

Necessary kernel modules must be loaded. Check your /boot/config-* file to see if your kernel was compiled with them already built-in, or as loadable modules, so you don't try to load modules that aren't needed. It doesn't really hurt anything to load unnecessary modules; it's just a bit of finicky housekeeping.

ip_tables and iptable_filter are essential for iptables to work at all. The ip_conntrack_irc and ip_conntrack_ftp modules assist in maintaining IRC and FTP connectivity through a NAT firewall. You can omit these if you don't use IRC or FTP.

The default policies operate on any packets that are not matched by any other rules. In this section, we have a "deny all incoming traffic, allow incoming as needed" policy combined with an unrestricted outbound policy.

The loopback interface must not be restricted, or many system functions will break. The next two rules are where the real action takes place. First of all, because you're not running any public services, there is no reason to accept incoming SYN packets. A SYN packet's only job is to initiate a new TCP session. The next rule ensures that you can initiate and maintain connections, such as web surfing, checking email, SSH sessions, and so forth, but still not allow incoming connection attempts.

While some folks advocate blocking all ICMP packets, it's not a good idea. You need the ones listed in the firewall scripts for network functions to operate correctly. The /etc/sysctl.conf directives are important kernel security measures. This is what the kernel parameters in the file mean:

net.ipv4.ip_forward = 0
This box is not a router, so make sure forwarding is turned off.

net.ipv4.icmp_echo_ignore_broadcasts = 1
Don't respond to ping broadcasts. Ping broadcasts and multicasts are usually an attack of some kind, like a Smurf attack. You may want to use a ping broadcast to see what hosts on your LAN are up, but there are other ways to do this. It is a lot safer to leave this disabled.

net.ipv4.tcp_syncookies = 1
This helps to protect from a syn flood attack. If your computer is flooded with SYN packets from different hosts, the syn backlog queue may overflow. So, this sends out cookies to test the validity of the SYN packets. This is not so useful on a heavily loaded server, and it may even cause problems, so it's better to use it only on workstations and laptops.

net.ipv4.conf.all.rp_filter = 1
This helps to maintain state and protect against source spoofing. It verifies that packets coming in on an interface also go out on the same interface. Obviously, this can confuse multihomed routers, which routinely forward packets from one interface to another, so don't use it on them.

net.ipv4.conf.all.send_redirects = 0
Only routers need this, so all others can turn it off.

net.ipv4.conf.all.accept_redirects = 0
ICMP redirects are important to routers, but can create security problems for servers and workstations, so turn it off.

net.ipv4.conf.all.accept_source_route = 0
Source-routed packets are a security risk because they make it all too easy to spoof trusted addresses. The legitimate uses of source-routed packets are few; they were originally intended as a route debugging tool, but their nefarious uses far outweigh the legitimate uses.

It is common to see kernel parameters set in iptables scripts, like this:

echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
echo 0 > /proc/sys/net/ipv4/conf/all/accept_redirects
echo 0 > /proc/sys/net/ipv4/conf/all/accept_source_route

Control these options with sysctl because that is what it is designed to do, and I like that they operate independently of my firewall. This is a question of taste; do it however you like.

Using the echo commands on the command line overrides configuration files, so they're great for testing. They go away with a reboot, which makes it easy to start over.

A common point of confusion is dots and slashes. You may use either, like this:

net.ipv4.tcp_syncookies = 1
net/ipv4/tcp_syncookies = 1
[Previous] [Contents] [Next]