Kroon Information Systems
       
    
Howto: IPTables: Personal Firewall

Last updated 9 August 2023

Introduction

I've had numerous requests in the recent past as to how I set up my iptables rules for my machines. Well, in this howto I will attempt to explain how I set up iptables for use on a personal computer. I use similar setups on router boxes, as well as servers. In fact, this setup is generic enough for almost any situation - I've only ever seen a few cases where the guidelines provided in this howto was not sufficient.

Installing IPTables

Each distribution has it's own method of installing packages, I use gentoo, on which a simple "emerge -uav iptables" is sufficient to install all userspace packages that you require (for this guide at least).

The appropriate kernel modules are however also required. These can be found under "Device Drivers", "Networking support", "Networking options", "Network packet filtering (replaces ipchains)" and finally "IP: Netfilter Configuration". At a minimum you will need these options:

  • Connection tracking: Provides the system with capabilities to track network connections, the state they are in and other generally useful stuff. We need this for network address translation when (if) doing routing from pprivate IP ranges such as 192.168.0.0/24. It is also required for Connection state match support, which is what I'm really after.
  • IP tables support: Obviously we need to have support for iptables.
  • Multiple port match support: Not strictly speaking required but really does make life a lot easier. This allows you to match more than one port number in a single rule for TCP and UDP.
  • Connection state match support: I use this extensively so that I don't have to worry about what kind of packets is allowed and which not. Surprisingly I can still maintain full-speed connections with this on even the slowest (Pentium 90) machines without it adversly affecting CPU usage on the system. Well done to the netfilter team.
  • Packet filtering: Well, the intent of a firewall is to filter packets, so I suppose this is required (in fact, without this you cannot do much).
  • Full NAT: This is used mostly on router boxes, so if you plan do to routing, say yes to this, otherwise you can safely say no.
  • MASQUERADE target support: Same argument as above.

The following modules are also useful, but probably won't be explained in this guide.

  • limit match support: Provides us with the ability to only match packets at a specific rate, say 5 per minute or something.
  • IP range match support: Useful if the IP-ranges you want to specify cannot be matched to a network/subnet combination. I haven't personally had need for this yet though but I can remember a few cases where this certainly would have made life easier.
  • MAC address match support: Very useful if you are doing ACL based stuff along with DHCP and/or have users that knows too much about IP configurations and knows how to often and quickly switch IPs. See my iptables and MAC-based access control howto.
  • FTP protocol support: If you find that your ftp connections just hang and sit there, try this module. If you are doing NATing and have Internet Explorer clients you will deffinately need this. It provides state matching for ftp connections, allowing the ftp server to connect back or send packets to other port numbers.
  • REDIRECT target support: If you've ever set up james, tomcat or jboss you will know that these applications cannot drop user privileges, and imho running them as root is just not an option. So run them on high-port numbers and this target will allow you to make it look like they are running on standard ports. Not explained in this howto.

Whether you want to compile a monolithic kernel or a modular kernel is up to you. I'm actually beginning to tend towards modular kernels, but still like to compile monolithic kernels as far as possible. This sounds like a bit of a paradox, but consider this: Not all modules are always used, as such, I compile these modules as modules, and modules that I always use I compile statically into the kernel. This would include those modules I specified under "must have".

Another really useful option in the kernel is SYN Cookies. Just compile it into the kernel and add "net.ipv4.tcp_syncookies = 1" to /etc/sysctl.conf and run "sysctl -p". Whilst you are at it, sommer add "net.ipv4.conf.default.rp_filter = 1" and "net.ipv4.ip_forward = 0" in there as well. The ip_forward rule should be set to 1 if you intend to run a router box.

Configuring the INPUT chain

Finally we get round to the real stuff. Actually, this is quite simple. To give you an idea of where we are headed, take a look at the following set of rules:

Chain INPUT (policy ACCEPT 1076K packets, 253M 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 1540K packets, 1625M bytes)
 pkts bytes target     prot opt in     out     source               destination

Which happens to be the default ruleset. You can obtain your current ruleset by issues "iptables -L". I like also adding -v (verbose) and -n (numerical). Verbosity just gives more (and mostly very important) information, -n just makes things quicker by not doing hostname lookups (should be the default imho). The above shows you the fundamentals of iptables, the filter table (which contains three chains, INPUT, FORWARD, OUTPUT).

The first thing to note is that by default all packets gets accepted - this is exactly what we don't want. In fact, I like the idea of "block everything but that which is needed". For this reason we change the default policy to DROP. At this stage I should probably mention that there are four special targets, two of which I actually use:

  1. DROP: Drop the packet on the floor like it's an overly hot potato. This is what we want to do with as many packets as possible.
  2. ACCEPT: Allow the packet to go on it's way unhindered.

There are other targets too, but these are the only two we'll require for this tutorial.

Back to changing that default policy. Policies are set using the -P parameter to iptables, to change the policy for INPUT to DROP, issue the following command:

# iptables -P INPUT DROP

And since we are not interrested in routing (yet), do the same for the FORWARD chain:

# iptables -P FORWARD DROP

At this point you were hopefully not working via ssh, as you will now not be able to talk to your box, not even via the lo device. No IP traffic can enter your box at this point of time. Perfectly secure? Hopefully, unless there is a bug in the netfilter code (I have discovered one in the ULOG target a long while back which under specific circumstances could have been used to DoS the box). Right, at this point we probably want to be able to start opening specific services, but hang on a little longer, let's first prep the rules a bit more.

# iptables -A FORWARD -m state --state INVALID -j DROP

This is strictly speaking not really needed, but I like putting it in anyway, it just tries to trap many invalid (midstream packets that isn't associated with any streams type of thing) as soon as possible and just DROP those packets without having to go through the entire chain.

Once a connection has been established, we want to accept all packets for that connection, we also want to accept related packets (for example ICMP port unreachable, ICMP echo response, SYN/ACK packets upon SYN, RST packets and so forth). This is where the state module steps in and saves the day:

# iptables -A INPUT -m state --state established,related -j ACCEPT

This says load the state module (-m state), then issues the --state flag with established and related. established does exactly what it says, all packets that form part of an established connection, related covers all other packets such as ICMP error messages et al. The state module also has invalid, snat and dnat parameters. I've never had a need for snat or dnat (read the man page if your interrested) and whilst it is probably a good idea to use the invalid flag with a DROP rule as the first rule I've never bothered with this as invalid packets will never (afaik) be accepted by any of my other rules.

Right, now we can maintain connections (but not yet establish them). That would obviously be the next step? Almost, first we want to state that we trust the lo device by saying that we want to accept all input packets from that device:

# iptables -A INPUT -i lo -j ACCEPT

This just says accept all packets that orriginated on the local machine. Now we are ready to establish new connections. This is in fact only required if you are hosting any services on your computer such as ssh, http or ftp. I do, so I issue the following command:

# iptables -A INPUT -i eth0 -p tcp -m multiport --dports 21,22,80,139,445,873,16384 --syn -j ACCEPT

Yes, I run lots of services:

  • 21: ftp (vsftpd) - for distributing distfiles.
  • 22: ssh (openssh) - for remote access.
  • 80: http (apache) - for serving up web pages :).
  • 139,445: smb (samba) - for file sharing with Windows machines.
  • 873: rsync (rsyncd) - for sync'ing portage.
  • 16384: torpage (tcpserver) - for asking my machine to fetch a distfile.

The --syn says to only accept pure SYN packets, ie, not SYN/ACK or ACK or any other packets, only packets for which the SYN bit is set and both the ACK and RST bits are unset.

I do something similar for UDP, on which ports 137 and 138 needs to be open in order for samba to work:

# iptables -A INPUT -i eth0 -p udp --dport 137:138 -j ACCEPT

Since there is only a single port range that requires to be open I don't bother with multiport this time round.

I'm running on a semi public network (which is why I tighten my rules as far as possible). But I own two other machines which I trust entirely - well, I'm the only user on them. And since the above is not the only networking protocols I use (think ldap, nfs, portmap, ntp, dhcp, cups) I still need to open some ports for those machines as well. Well, I could repeat the above with each of those machines MAC addresses as additional restriction, or I could just trust them with rules such as:

# iptables -A INPUT -m mac --mac-source 00:A0:C9:96:94:D3 -s 192.168.0.2 -j ACCEPT

Note that I specify both the MAC address and the IP addess, this is simply in case someone manages to bounce a packet off the machine which then contains a different IP (One of the machines serves as a router on the network). I do this because I myself have used similar techniques to bypass firewalls (I had the permission of the owner to perform penetration testing).

At this point in time my setup (for a non-router) machine is complete, and I can state that it is most likely the simplest ruleset to obtain maximum effectiveness from a firewall. "iptables -L -v -b" reports:

Chain INPUT (policy DROP 8 packets, 1153 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           state INVALID   
27868 4038K ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED
    8   480 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0
    5   300 ACCEPT     tcp  --  eth0   *       0.0.0.0/0            0.0.0.0/0           multiport dports 21,22,80,139,445,873,16384 tcp flags:0x16/0x02
    0     0 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0           udp dpts:137:138 
    0     0 ACCEPT     all  --  *      *       192.168.0.2          0.0.0.0/0           MAC 00:A0:C9:96:94:D3

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 1574K packets, 1627M bytes)
 pkts bytes target     prot opt in     out     source               destination

Hmm, just in the time it took me to configure this firewall I already dropped 8 packets. Right, if you would like to know what these packets are, you can use the LOG target (compile in LOG target support):

# iptables -A INPUT -j LOG

Will log all packets to syslog before sending them to oblivion. If you have a really high drop rate you will perhaps want something like:

# iptables -A INPUT -m limit --limit 1/min -j LOG

Which will log an average of 1 packet per minute (and simply proceed to drop the rest). If there are specific packets you would like to not have logged, simply DROP them explicitly before the LOG rule (you can use iptables -I for this purpose, for example:

# iptables -I INPUT 6 -p udp --sport 631 --dport 631 -j DROP

Will drop all cups traffic before the LOG statement above (presuming you've added the LOG statement to the rules above). This happened to be most of the DROP'ed traffic (cups broadcast traffic to browse for other printers on the network).

IMPORTANT NOTICE: This firewall ruleset breaks the samba browsing. For that to function properly a few specialised rules that seriously undermines security has to be set up. Other machines can still browse you (the rule does accept broadcast packets), but you cannot do broadcast lookups. One way to work around this is to set up a WINS server (highly recommended). You can also set up a rule to accept all packets comming from port 138 to your machine on udp port numbers greater than 1024:

# iptables -A INPUT -i eth0 -p udp --sport 138 --dport 1025: -j ACCEPT

But I don't like that a lot since you can now be port-scanned if the scannee uses port 138 to scan from :). The recent module might be able to help here but as of yet I could not figure out how. My finalised ruleset then looks like:

Chain INPUT (policy DROP 87 packets, 11470 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0           state INVALID   
46238 6659K ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED
   12   720 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0
   14   840 ACCEPT     tcp  --  eth0   *       0.0.0.0/0            0.0.0.0/0           multiport dports 21,22,80,139,445,873,16384 tcp flags:0x16/0x02
    3   696 ACCEPT     udp  --  *      *       0.0.0.0/0            0.0.0.0/0           udp dpts:137:138
    0     0 ACCEPT     udp  --  eth0   *       0.0.0.0/0            0.0.0.0/0           udp spt:138 dpts:1025:65535
   25  4107 ACCEPT     all  --  *      *       192.168.0.2          0.0.0.0/0           MAC 00:A0:C9:96:94:D3
   81 10578 DROP       udp  --  *      *       0.0.0.0/0            0.0.0.0/0           udp spt:631 dpt:631
   22  2833 LOG        all  --  *      *       0.0.0.0/0            0.0.0.0/0           LOG flags 0 level 4

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 1591K packets, 1628M bytes)
 pkts bytes target     prot opt in     out     source               destination

One last question may be something to the effect of "Why do you not firewall the OUTPUT chain?". Well, answer is simple, I trust the security of my machine, I doubt seriously that it'll get compromised so imho there is no reason to try to lock someone it. If someone does get in - well done, he'll probably be able to obtain root if (s)he's that good, so there is no real use. Just for the record, I have set up machines where it was important that the machine be as quiet as possible, where I have firewalled the OUTPUT chain too with great success.

Configuring a firewall router

Coming soon