in-state patch for FreeBSD/ipfw

What is it

This patch for FreeBSD 4.3 allows you to filter packets based on the information whether they match a dynamic rule or not when using stateful ip filtering with ipfw. That was the short version.

Here is the detailed one: FreeBSD ipfw allows you to do some cool things with ip filtering, one of them is the stateful ip filter. In short, the FreeBSD kernel remembers open and established TCP/UDP connections and creates dynamic rules according to them. Thus, you only have to allow one single packet and can have done the rest with one check-state rule. See the next section for an example.

Once a TCP/UDP session is established and the dynamic rule is created, ipfw does not have the capability to test incoming or outgoing packets for a matching connection it may belong to. Well, there is the option "established", but that checks for TCP flags only. And you have "check-state" which accepts all packets belonging to an established session. You can't do anything other than accept it, and that's what this patch is for.

This patch adds the "in-state" option to the ipfw command and does some minor changes to the ip_fw code in the kernel. The option "in-state" matches packets that belong to an established connection.

Most of you out there will never need this, but if you want to do some really weird routing and stateful IP filtering together you will.
Use with care. I didn't check it for performance losses. Yet.

Usage example

Or "what made me do it"
First of all, you really need to know what you are doing when using this. This really is not the "ipfw for newbies"-page.
I have two independent upstream Providers, Provider A and Provider B. Servers in the LAN have assigned IPs from both Providers, some of them at the same network interface. The default gateway for all of them is the FreeBSD box, default gateway for the FreeBSD box is ISP-A-gateway.
Here is my setup:
                       /---- ISP A
LAN-Servers --- FreeBSD 
                       \---- ISP B
First of all, I want FreeBSD to route packets originating from A-IPs to ISP-A-gateway and packets from B-IPs to ISP-B-gateway. This is source based IP routing. One could do that with the following statements:
ipfw add fwd ISP-A-gateway ip from ISP-A-network to any
ipfw add fwd ISP-B-gateway ip from ISP-B-network to any
This works. Good. But what about (stateful) filtering? Consider the following ruleset:
ipfw add 100 allow tcp from any to SERVER-A http,smtp setup keep-state
ipfw add 200 allow tcp from any to SERVER-B ssh setup keep-state
ipfw add 300 check-state
ipfw add 400 deny tcp from any to any established
I want to be protected against ACK scans, thus the setup statements and the deny established rule.
Say, someone somewhere in Nebraska sends out packet Charlie, which opens a connection to SERVER-B's ssh port, thus Charlie has set TCP flags SYN, not ACK. Charlie matches rule 200, the FreeBSD box remembers the session and creates a dynamic rule.
SERVER-B's sshd sends out Eva, the response to Charlie, which has SYN and ACK bits set. Eva does not match rules 100 and 200, but 300 as it matches the previously created dynamic rule. What does the FreeBSD kernel do? Accept the packet and send it to the default gateway, which is ISP-A-gateway. And there we have it, packets from provider B travel out provider A's line. Doh.

I don't want to do the "ipfw add fwd" trick from above, because that would open the whole internet to the servers behind the FreeBSD box. I don't want any trojans on those servers to connect anywhere to the internet and do something bad. But somehow I have to make sure that the packets go out to the correct ISPs, thus I have to do source based IP routing, thus I have to use "ipfw add fwd". What a mess.

This is where in-state comes in: What if I could just forward those packets that match an already established connection? That solves my problem by doing:

ipfw add fwd ISP-A-gateway ip from ISP-A-network to any in-state
ipfw add fwd ISP-B-gateway ip from ISP-B-network to any in-state

ipfw add allow tcp from any to SERVER-A http,smtp setup keep-state
ipfw add allow tcp from any to SERVER-B ssh setup keep-state
ipfw add check-state
ipfw add deny tcp from any to any established
Tadaa. This is it. Packets only are forwarded to the gateways if - and only if - they belong to an already established connections. Noone connects from my servers to the internet. I'm such a bitch.

When using this, please consider that every in-state rule looks up the whole dynamic ruleset which can grow quite large on some sites. And note that in-state checks only work before any keep-state or check-state rules, because those match all established connections and pass the packets to the default gateway if they get a match.

There is a more detailed example ruleset which you can use to start working with.

Download and Installation

Comments go to Martin Domig <md AT linuxpunk DOT org>. Don't use the mailto: link, it's a spamtrap.


Aaron Gifford has a nice patch for ipfw that allows you to control the expire time of dynamic rules when using stateful ip filtering. Comes in handy when using ssh and doing nothing all the time. Heh. Works quite well together with my in-state patch, just the ipfw manpage did not patch correctly. But heck, who needs mans anyway. If you are reading this I assume that you already know it by heart :o)

Regards to Heinrich Moser V. for stating "Es gehört mehr dazu." You're right.

Martin Domig <[email protected]>
Last update: Fri Aug 18
Patch written at and for INFO media systems.