ipfwadm: my first firewall rules

Last weekend I sat down and tried to write a sensible set of firewall rules for my Slackware box. The tool for this on the 2.0.x kernel is ipfwadm. The man page is dense, the examples are sparse, and almost every web page I found that promised "a beginner's guide" was wrong in some interesting way.

My first attempt was not good. I am going to walk through it deliberately, because the wrong shape of a thing is sometimes the best teacher.

What I started with

The machine has a single network card. It is on a small home LAN. It speaks to the wider internet through a modem-to-PPP-to-ISP setup, with the modem dialling on demand.

My first rules were essentially this:

ipfwadm -F -p deny
ipfwadm -I -p deny
ipfwadm -O -p deny
ipfwadm -I -a accept -P tcp -S 0.0.0.0/0 -D 0.0.0.0/0
ipfwadm -I -a accept -P udp -S 0.0.0.0/0 -D 0.0.0.0/0

I was very pleased with myself for setting the default to deny. Then I undid most of that pleasure by accepting all TCP and UDP traffic on the input chain, which made the default-deny essentially decorative.

This is the first thing I learned: a default policy without any restrictive specifics is the firewall equivalent of locking your front door but leaving every window wide open.

What I changed

The second attempt tried to think about what should actually be allowed in.

For the input chain:

# loopback is fine, do not interfere
ipfwadm -I -a accept -W lo

# allow established TCP connections back in
ipfwadm -I -a accept -P tcp -S 0.0.0.0/0 1024:65535 -D <my-ip> 1024:65535 -k

# allow DNS replies
ipfwadm -I -a accept -P udp -S 0.0.0.0/0 53 -D <my-ip> 1024:65535

The -k flag in the second rule says "only accept packets that are part of an already-established connection" — meaning the ACK bit is set. The third rule lets DNS replies back to the resolver. That little change tightened the rule set enormously, because now the firewall only lets in things I actually started.

The mistake I almost did not catch

I had a rule allowing DNS replies. I did not have an equivalent rule allowing the outgoing DNS query, because I had been lazy about the output chain. The test was lookup www.kernel.org. It failed. I spent twenty minutes blaming the resolver before I read my own rules carefully.

This is the second thing: the hardest thing about firewalling is not knowing what to block, it is knowing what you actually need.

What I would tell someone starting today

Three things, in this order.

First, draw the network. On paper. Including which ports leave the box and which ports come back. You cannot write good rules for a network you have not pictured.

Second, set the default to deny on every chain, then add rules in the same order that traffic actually flows. Outgoing first, incoming reply second.

Third, log everything dropped, at least at first. ipfwadm -I -a deny -o ... writes to syslog. You will be surprised at how much traffic you were silently shedding before, and grateful for it.

Next post: what I actually saw in those drop logs over a fortnight, and why the answer scared me into reading more about TCP wrappers.


Back to all writing