Greylisting with PF
by Dan Langille01/18/2007
This article will show you how I am using PF (the Packet Filter from the OpenBSD project) and spamd (also from OpenBSD) to implement greylisting and greatly reduce incoming spam. This solution is completely MTA agnostic. You do not have to make any changes to your existing mail server configuration to use spamd/PF. In fact, you can easily use PF and spamd to guard any SMTP mail server; even MS Exchange.
I first read about PF when reading Micheal Lucas's Absolute OpenBSD. If you have never read one of his books, I urge you to do so. His writing style is very clear and easy to follow. The book contains about thirty pages on PF, yet there is so much packed into that short chapter that when I read it I knew one day I'd start using PF. That day came a few weeks ago. My gateway at home has been happily running PF since early October. And more importantly, I've been happy. One of the best features of any software product is simplicity of use. PF is simple to start and easy to extend when you need the more advanced features.
A few weeks after implementing PF on my home gateway, I attended NYCBSDCon 2006 and listened to Bob Beck's talk on spamd. It was so easy that I just had to try it. So I did.
On a related note, I'll also give you a short introduction to OS fingerprinting and how you can use PF to block/pass packets based on the sending OS.
How Greylisting Works
spamd and PF work together. PF will direct any known good clients directly to the mail server. Similarly, it will send known bad clients to the tarpit, a slow-responding mail server that does not add much load to your system. Finally, it asks new clients, not known to be good or bad, to try again later.
This makes three lists of clients:
- Whitelist: known good clients.
- Blacklist: known bad clients.
- Greylist: we don't know if they are good or bad yet, but we will soon decide.
The key to greylisting is knowing that good and kind mail servers do not mind being politely asked to come back later. This is part of the STMP protocol. Spammers, however, are cheap and nasty. They don't like this. They probably won't come back later. Why? Queue management is tricky. If you're sending to millions of email addresses, why worry about a few messages that you cannot deliver? Just skip along to the next one. Spammers work on volume. Greylisting exploits that characteristic.
Implementing PF
My gateway was running FreeBSD 6.2 PRELREASE. PF is also available on DragonflyBSD, NetBSD, and OpenBSD (of course!). The FreeBSD Handbook has a good introduction to PF.
There are several ways to enable PF, including compiling it into the kernel, or loading the module. I added a few options to my /etc/rc.conf file to ensure PF starts up at boot time. It also starts the logging daemon.
pf_enable="YES"
pflog_enable="YES"
pf_rules="/etc/pf.rules"
Note that I have chosen a nondefault value for my PF rules. The default value, as found in /etc/default/rc.conf, is /etc/pf.conf. To avoid any merge conflicts with mergemaster(8), I chose a different filename. The default install comes with many fine examples in /etc/pf.conf and I urge you to read them.
Designing a PF rule set is beyond the scope of this article, but the OpenBSD project has a good PF rule set example.
Enabling PF
The primary interface between PF and the outside world is pfctl. To load PF on a running system, issue the command:
# kldload pf
# kldstat
Id Refs Address Size Name
1 8 0xc0400000 6721fc kernel
2 1 0xc0a73000 58554 acpi.ko
3 1 0xc4eb5000 16000 linux.ko
4 1 0xc5e20000 2d000 pf.ko
What does loading PF give you? To see all the filter parameters:
# pfctl -s all
No ALTQ support in kernel
ALTQ related functions disabled
FILTER RULES:
INFO:
Status: Disabled Debug: None
Hostid: 0x595cedd1
State Table Total Rate
current entries 0
searches 0 0.0/s
inserts 0 0.0/s
removals 0 0.0/s
Counters
match 0 0.0/s
bad-offset 0 0.0/s
fragment 0 0.0/s
short 0 0.0/s
normalize 0 0.0/s
memory 0 0.0/s
bad-timestamp 0 0.0/s
congestion 0 0.0/s
ip-option 0 0.0/s
proto-cksum 0 0.0/s
state-mismatch 0 0.0/s
state-insert 0 0.0/s
state-limit 0 0.0/s
src-limit 0 0.0/s
synproxy 0 0.0/s
TIMEOUTS:
tcp.first 120s
tcp.opening 30s
tcp.established 86400s
tcp.closing 900s
tcp.finwait 45s
tcp.closed 90s
tcp.tsdiff 30s
udp.first 60s
udp.single 30s
udp.multiple 60s
icmp.first 20s
icmp.error 10s
other.first 60s
other.single 30s
other.multiple 60s
frag 30s
interval 10s
adaptive.start 0 states
adaptive.end 0 states
src.track 0s
LIMITS:
states hard limit 10000
src-nodes hard limit 10000
frags hard limit 5000




