Tue, 15 Aug 2006

pf vs iptables

There's been some coverage on LWN of my netdev musings on netfilter and the Grand Unified Flow Cache. There was a comment on the article along the lines that this would be a good thing if Linux moved closer to OpenBSD's pf, which made me bump "learn more about pf" up my TODO list where it's been lingering for years.

Now, from the glance I'd had at pf before, I already had a fondness for it. It has some minor warts, but it does many things really nicely. Googling for pre-canned comparisons revealed a some BSD types who really hate iptables, no iptables users who really hate pf, but generally few people well-versed in both.

I've always thought of iptables as the assembler language of packet filters (although CISC not RISC). Hence I always write my filters as shell scripts, which provides the variables ('macros' in pf-speak), expansion etc. pf takes a very different route, using a higher-level domain-specific language, and this is reflected in the features offered. "scrub" for example does various cleaning/dropping of packets, all in one command. "keep state" on a rule implies that all reply and related packets should bypass the filtering and be accepted, which sums up the pf approach quite well. They also implement a syn proxy, timestamp randomization, and other features missing in iptables. Their handling of FTP is via a proxy, and this is a rare occasion where their implementation is more awkward than the iptables "modprobe ip_nat_ftp" approach.

By feature count, iptables wins, but that's not really fair: most of these features are extensions which hardly anyone uses, and iptables misses some nice "all-in-one" pf features. Looks like they do NAT by port binding (ie. the way everyone else does), meaning you can't cram as many clients behind a NAT box, but again, most people won't care.

There's one place where pf would make Linux users green with envy, it's rate limiting and queueing. pf isn't exactly a barrel of simplicity here, but it's integrated with the rest of the syntax: I could probably remember how to do it with minimal prompting, rather than having to digest the Advanced Routing HOWTO as I always have to for Linux. (BTW, I'm still waiting for a good gui tool for this: it'd be awesome, because I think of ratelimiting in visual terms already.)

Finally, some advice for people who are translating from one into the other: it's not a trivial conversion. I (roughly) translated the example at the end of the pf FAQ, and then rewrote it how you would really do this in iptables. The NAT stuff is fairly directly mapped (although be aware that iptables filtering is always on "real" addresses, ie. after DNAT/rdr, before SNAT/nat). For filtering, iptables makes you consider the problem in three distinct parts: INPUT (destined for this box), FORWARD (passing through this box) and OUTPUT (generated by this box). Consider the following pf rules:

pass in on $ext_if inet proto tcp from any to ($ext_if) \
   port $tcp_services flags S/SA keep state
pass in on $ext_if inet proto tcp from any to $comp3 port 80 \
    flags S/SA synproxy state

In iptables, these two rules are fundamentally different, and indeed, would unlikely be listed consecutively. The first is an INPUT rule, the second a FORWARD rule:

iptables -A INPUT -i $ext_if -p tcp --dport $tcp_services --syn -j ACCEPT
iptables -A FORWARD -i $ext_if -p tcp -d $comp3 --dport 80 --syn -j ACCEPT

This was one change I made from ipchains, which I feel is a significant improvement over the "by destination IP address" approach, because it matches a fundamental distinction in filtering.

Finally, here is the rough iptables equivalent of the pf example. (Note: no scrubbing, but we do fragment reassembly because we use "-m state" and NAT, either of which requires connection tracking).

[/tech] permanent link