The FreeBSD Diary

The FreeBSD Diary (TM)

Providing practical examples since 1998

If you buy from Amazon USA, please support us by using this link.
[ HOME | TOPICS | INDEX | WEB RESOURCES | BOOKS | CONTRIBUTE | SEARCH | FEEDBACK | FAQ | FORUMS ]
setting up rules for ipfilter
This article was written by Brendan Conoboy.   My thanks to him for allowing me to publish this.

The home page for ipfilter is http://coombs.anu.edu.au/~avalon/.

IP Filter is a neat little firewall package.  It does just about everything other free firewalls (ipfwadm, ipchains, ipfw, etc) do, but it's also portable and does neat stuff the others don't.  This document is intended to make some cohesive sense of the sparse documentation presently available for ipfilter.   Some prior familiarity with packet filtering will be useful (too much familiarity may make this document a waste of your time).   There's probably numerous books on the subject that would be beneficial to read.  I used TCP/IP illustrated, myself.  On we go. 

Config File Dynamics, Order and Precedence
IP Filter (ipf ) has a config file (as opposed to say, running some command again and again for each new rule).  The config file drips with Unix: there's one rule per line, the "#" mark denotes a comment, and you can have a rule and a comment on the same line.  Extraneous whitespace is allowed, I use it to keep my rules readable. 

The rules are read from top to bottom, each one appended after another.  This quite simply means that if the entirety of your config file is:

block in all
pass in all

The computer sees it as:

block in all
pass in all

Which is to say that when a packet comes in, the first thing ipf applies is:

block in all

Should ipf deem it necessary to move on to the next rule, it would then apply the second rule:

pass in all

At this point, you might want to ask yourself "would ipf move on to the second rule?".  If you're familiar with ipfwadm or ipfw, you probably won't ask yourself this.  Shortly after, you will become bewildered at the weird way packets are always getting denied or passed when they shouldn't.  Many packet filters stop comparing packets to rulesets the moment the first match is made- ipf is not one of them. 

Unlike the other packet filters, ipf keeps a flag on whether or not it's going to pass the packet.  Unless you interrupt it, it'll go through the entire ruleset, making its decision on whether or not to pass or drop the packet based on the last matching rule.  The scene:

IP Filter's on duty.  It's been been scheduled a slice of CPU time.  It has a checkpoint clipboard that reads:

block in all
pass in all

A packet comes in the gateway and it's time to go to work.  It takes a look at the packet, it takes a look at the first rule:

block in all

"So far I think I will block this packet" says ipf.  It takes a look at the second rule:

"So far I think I will pass this packet" says ipf.  It takes a look at a third rule.  There is no third rule (a null pointer is dereferenced, signal 12, kernel panic- AIiiyyee!), so it goes with what its last motivation was, to pass the packet onward. 

It's a good time to point out that even if the ruleset had been

block in all
block in all
block in all
block in all
pass in all

that the packet still would have gone through.  There is no cumulative effect.   The last matching rule always takes precedence. 

I'm sure there's a good use for this method of layout, but I must confess that I don't yet grasp it.  It has some horrible aspects in the area of portability and speed.   Imagine converting your old filters to this format. Imagine if you had 100 rules and most of the applicable ones were the first 10.  There would be a terrible overhead for every packet coming in to go through 100 rules every time.  Fortunately, there's a simple keyword you can add to any rule that makes it take action at that match.   The keyword is "quick".

Here's a modified copy of the original ruleset using the quick keyword:

block in quick all
pass in all

In this case, ipf looks at the first rule:

block in quick all

The packet matches and the search is over.  The packet is expunged without a peep.   There are no notices, no logs, no memorial service.  Cake will not be served.   So what about the next rule?

pass in all

This rule is never encountered.  It could just as easily not be in the config file at all.  The sweeping match of "all" and the terminal keyword "quick" from the previous rule make certain that no rules are followed afterward. 

Having half a config file laid to waste is rarely a desirable state.  On the other hand, ipf is here to block packets and as configured, it's doing a heck of a good job.  Nonetheless, ipf is also here to let *some* packets through, so a change to the ruleset to make this possible is called for. 

ipf will match packets on numerous criteria.  The one that we most commonly think of is the IP address.  There are some blocks of address from which we should never get traffic.  One such block are the unrouteable networks such as 192.168.0.0/16 (/16 being a netmask.  Perhaps you are more familiar with the wordier version of 255.255.0.0).  If you wanted to block 192.168.0.0/16, this is one way to do it:

block in quick from 192.168.0.0/16 to any pass in all

Now we have a less stringent ruleset that actually does something for us.  Lets imagine a packet comes in from 1.2.3.4.  The first rule is applied:

block in quick from 192.168.0.0/16 to any

The packet is from 1.2.3.4, not 192.168.*.*, so there is no match.  The second rule is applied:

pass in all

The packet from 1.2.3.4 is definitely a part of all, so the packet is sent to wherever it happened to be going. 

On the other hand, suppose we have a packet that comes in from 192.168.1.2.   The first rule is applied:

block in quick from 192.168.0.0/16 to any

There's a match, the packet is dropped, and that's the end.  Again, it doesn't move to the second rule because the first rule matches and contains the "quick" keyword. 

Every packet you receive comes from a network interface.  From a modem speaking PPP to ethernet cards talking 10 base-T, there's an interface involved.  Say your machine has 3 interfaces, lo0 (loopback), xl0 (3com ethernet), and tun0 (FreeBSD's generic tunnel interface that ppp uses), but you don't want packets coming in on the tun0 interface?

block in quick on tun0 pass in all

In this case, the "on" keyword means that that data is coming in on the named interface.  If a packet comes in on tun0, the first rule will block it.  If a packet comes in on lo0 or xl0, the first rule will not match, the second rule will, the packet will be passed. 

It's an odd state of affairs when one decides it best to have the tun0 interface up, but not allow any data to be received from it.  The more criteria the firewall matches against, the tighter (or looser) the firewall can become.  Maybe you want data from tun0, but not from 192.168.0.0/16? This is the start of a powerful firewall.  

block in quick on tun0 from 192.168.0.0/16 to any pass in all

A packet from 192.168.1.1 on the xl0 interface will get passed.  A packet from 1.2.3.4 on the tun0 interface will get passed.  In fact, every packet from everywhere will be allowed in, except for packets from 192. 160.0.0/16 on tun0. 

At this point you can build a fairly extensive set of definitive addresses which are passed or blocked.  Since we've already started blocking private address space from entering tun0, lets take care of the rest of it:

block in quick on tun0 from 192.168.0.0/16 to any
block in quick on tun0 from 172.16.0.0/12 to any
block in quick on tun0 from 10.0.0.0/8 to any
block in quick on tun0 from 127.0.0.0/8 to any
pass  in all

The first three address blocks are the unrouteable IP space.  The fourth is a largely wasted class-A network used for loopback.  Much software communicates with itself on 127. 0.0.1 so blocking it from external entrance is a good idea. 

It seems very frequent that companies have internal networks before they want a link to the outside world.  I'd even go so far as to say that's the main reason people consider firewalls in the first place, though they're just as important for a single machine as for an office network.  The machine that bridges the outside world to the inside world and vice versa is the router.  What separates the router from any other machine is simple: It has two interfaces.  We'll call them tun0 for the external link and xl0 for the internal link. 

There's a very important principle in packet filtering which has only been alluded to with the private network blocking and that is this: When you know there's certain types of data that only comes from certain places, you setup the system to only allow that kind of data from those places.  In the case of the unrouteable addresses, you know that nothing from 10.0.0.0/8 should be arriving on tun0 because you have no way to reply to it.  It's an illegitimate packet.  The same goes for the other unroutables as well as 127.0.0.0/8. 

Many pieces of software do all their authentication based upon the packet's originating IP address.  When you have an internal network, say 200.200.200.0/24, you know that the only traffic for that internal network is going to come off the local ethernet.   Should a packet from 200.200.200.0/24 arrive over a PPP dialup, it's perfectly reasonable to drop it on the floor, or put it in a dark room for interrogation.  It should by no means be allowed to get to its final destination.  You can accomplish this particularly easily with what you already know of ipf.  The new ruleset would be:

block in quick on tun0 from 192.168.0.0/16 to any
block in quick on tun0 from 172.16.0.0/12 to any
block in quick on tun0 from 10.0.0.0/8 to any
block in quick on tun0 from 127.0.0.0/8 to any
block in quick on tun0 from 200.200.200.0/24 to any
pass  in all

Up to this point, all blocked and passed packets have been silently blocked and silently passed.  Usually you want to know if you're being attacked rather than wonder if that firewall is really buying you any added benefits.  While I wouldn't want to log every passed packet, and in some cases every blocked packet, I would want to know about the blocked packets from 200.200.200.0/24.  To do this, we add the "log" keyword:

block in quick on tun0 from 192.168.0.0/16 to any
block in quick on tun0 from 172.16.0.0/12 to any
block in quick on tun0 from 10.0.0.0/8 to any
block in quick on tun0 from 127.0.0.0/8 to any
block in log quick on tun0 from 200.200.200.0/24 to any
pass  in all

[a month passes while I become fascinated with thingamajigs, gadgets and other non-ipf related gadgets]

There's a couple other things you may need to do, too.  If you're running FreeBSD, you'll need to have IPFILTER_LOG compiled into your kernel.  Second, ipf requires ipmon to log, no matter what OS you're using.  I prefer to run ipmon with as ipmon -s so it syslogs logged packets instead of having them dump to stdout. 

So far, our firewall is pretty good at blocking packets coming to it from suspect places, but there's still more to be done.  For one thing, we're accepting packets destined anywhere.  One thing we ought to do is make sure packets to 200.200.200.0/32 and 200.200.200.255/32 get dropped on the floor.  To do otherwise opens the internal network for a smurf attack.  These two lines would prevent our hypothetical network from being used as a smurf relay:

block in log quick on tun0 from any to 200.200.200.0/32
block in log quick on tun0 from any to 200.200.200.255/32

This brings our total ruleset to look something like this:

block in     quick on tun0 from 192.168.0.0/16 to any
block in     quick on tun0 from 172.16.0.0/12 to any
block in     quick on tun0 from 10.0.0.0/8 to any
block in     quick on tun0 from 127.0.0.0/8 to any
block in log quick on tun0 from 200.200.200.0/24 to any
block in log quick on tun0 from any to 200.200.200.0/32
block in log quick on tun0 from any to 200.200.200.255/32
pass  in all

Denial of Service attacks (DOS) are almost as rampant as buffer overflows.  Many DOS attacks rely on glitches in the OS's TCP/IP stack.  Frequently, this has come in the form of ICMP packets.  Why not block them entirely?

block in log quick on tun0 proto icmp from any to any

Now any icmp traffic coming in from tun0 will be logged and discarded.  Of course, this isn't really an ideal situation.  Why not drop all ICMP? Well, because it's useful.  So maybe you want to keep some types of ICMP traffic.  If you want ping and traceroute to work, you need to let in icmp types 8 and 11.  Strictly speaking, this might not be a good idea, but if you need to weigh security verses convenience, ipf lets you do it. 

pass in quick on tun0 proto icmp from any to 200.200.200.0/24 
                                                icmp-type 8
pass in quick on tun0 proto icmp from any to 200.200.200.0/24 
                                                icmp-type 11

Remember that ruleset order is important.  Since we're doing everything "quick" we must have our passes before our blocks, so we really want the last three rules in this order:

pass  in quick on tun0 proto icmp from any to 200.200.200.0/24 
                                                icmp-type 8
pass  in quick on tun0 proto icmp from any to 200.200.200.0/24 
                                                icmp-type 11
block in log quick on tun0 proto icmp from any to any

Now to put it all together:

block in     quick on tun0 from 192.168.0.0/16 to any
block in     quick on tun0 from 172.16.0.0/12 to any
block in     quick on tun0 from 10.0.0.0/8 to any
block in     quick on tun0 from 127.0.0.0/8 to any
block in log quick on tun0 from 200.200.200.0/24 to any
block in log quick on tun0 from any to 200.200.200.0/32
block in log quick on tun0 from any to 200.200.200.255/32
pass  in     quick on tun0 proto icmp from any to 200.200.200.0/24 
                                                icmp-type 8
pass  in     quick on tun0 proto icmp from any to 200.200.200.0/24 
                                                icmp-type 11
block in log quick on tun0 proto icmp from any to any
pass  in all

Please note the location of the icmp rules.  They're intentionally placed behind the first 7 blocking rules because we don't want to accidentally pass some icmp traffic that would have been dropped by the anti-spoofing and anti-smurfing rules.  It's very important to keep a close eye on what you "pass" before all the relevant "block" rules are run. 

Now that we've started blocking packets based on protocol, we can start blocking packets based on protocol ports.  Services such as rsh, rlogin, and telnet are all very convenient to have.  They're also hideously insecure against network sniffing and spoofing.  One great compromise is to only allow the services to run internally, then block them externally.  Both rlogin, rsh, and telnet use specific TCP ports (513, 514, and 23 respectively).  Creating rules to block them is easy:

block in     quick on tun0 proto tcp from any to 200.200.200.0/24 
                                                port = 513
block in     quick on tun0 proto tcp from any to 200.200.200.0/24 
                                                port = 514
block in     quick on tun0 proto tcp from any to 200.200.200.0/24 
                                                port = 23

Make sure all 3 are before the "pass in all" and those ports are good to not go:

block in     quick on tun0 from 192.168.0.0/16 to any
block in     quick on tun0 from 172.16.0.0/12 to any
block in     quick on tun0 from 10.0.0.0/8 to any
block in     quick on tun0 from 127.0.0.0/8 to any
block in log quick on tun0 from 200.200.200.0/24 to any
block in log quick on tun0 from any to 200.200.200.0/32
block in log quick on tun0 from any to 200.200.200.255/32
pass  in     quick on tun0 proto icmp from any to 200.200.200.0/24 
                                                icmp-type 8
pass  in     quick on tun0 proto icmp from any to 200.200.200.0/24 
                                                icmp-type 11
block in log quick on tun0 proto icmp from any to any
block in     quick on tun0 proto tcp from any to 200.200.200.0/24 
                                                port = 513
block in     quick on tun0 proto tcp from any to 200.200.200.0/24 
                                                port = 514
block in     quick on tun0 proto tcp from any to 200.200.200.0/24 
                                                port = 23
pass  in     all

You might also want to block udp/111 (portmap), tcp/515 (lpd), tcp/udp (2049), tcp/6000 (X) and so on and so forth.  You can get a complete listing of the ports being listed to by using "netstat -a" or lsof, if you have it installed.  Blocking udp is the same as tcp.  The rule for portmap would be:

block in     quick on tun0 proto udp from any to 200.200.200.0/24 
                                                port = 111

There's a big problem with blocking services by the port: sometimes they move.   RPC based programs are terrible about this, lockd, statd, even nfsd listens places other than 2049.  It's awfully hard to predict, and even worse to automate adjusting all the time.  What if you miss a service? Instead of dealing with all that hassle, lets start over with a clean slate.  The current ruleset looks like this:

 

Yes, we really are starting over.  The first rule we're going to use is this:

block in quick

No network traffic gets through.  None.  Not a peep.  You're rather secure with this setup.  Not terribly useful, but quite secure.  The great thing is that it doesn't take much more to make your box rather secure, yet useful too.   Lets say the machine this is running on is a web server, nothing more, nothing less.  It doesn't even do DNS lookups.  It just wants to take connections on tcp/80 and that's it.  We can do that.  We can do that with a second rule, and you already know how:

pass in quick on ed0 proto tcp from any to 200.200.200.1/32 
                                      port = 80 block in quick on ed0

This machine will pass in port 80 traffic for 200.200.200.1, and deny everything else.  Perhaps this is all one needs?

Well, I usually find myself needing more.  I want to be able to telnet out over the ed0 interface.  I want convenience and security in one.  Lots of people seem to, that's why ciscos have an "established" clause that lets established tcp sessions go through.  ipfw has established.  ipfwadm has setup/established.  They all have this feature, but the name is very misleading.  

When I first saw it, I thought it meant my packet filter was keeping track of what was going on, that it knew if a connection was really established or not.  The fact is, they're all taking the packet's word for it.  That's why they only support established TCP connections, that's the only protocol that has flags which the router can extrapolate the established state of the connection.  Anybody who can create a packet with bogus flags can get by such a firewall. 

Where does ipf come in to play here, you ask? Well, unlike the other firewalls, ipf really can keep track of whether or not a connection is established.  And it'll do it with udp and icmp, not just tcp.  The only problem is this: the way it does it is non-intuitive.  ipf calls it "keeping state".  The keyword for the ruleset is "keep state" (There is also "keep frags", which is advisable as well). 

Keeping state's setup is weird.  Normally, we know that when we want a packet to come in, we used "pass in" and when we want one coming in to be blocked we use "block in. ".  State is different in that, if you want a packet to come in, you say "pass out".  By passing the packet out, a reciprocal rule is created that allows a reply to come back in the other direction.  Lets show this as an actual rule:

pass out quick proto tcp from 200.200.200.1/32 to any keep state

Thought it doesn't say so, you must imagine there being an auxiliary rule that exists in the same place in line that reads:

pass in quick proto tco from ThatIpISentTo to 200.200.20.1/32

There is no way to express the previous line, don't even try putting it in there, it won't work.  The "keep state" option makes it implicit. 

Here's what our ruleset looks like now:

pass out quick in ed0 proto tcp from 200.200.200.1/32 to any 
                                                keep state
pass in quick on ed0 proto tcp from any to 200.200.200.1/32 
                                                port = 80
block in quick on ed0

The workings of the keep state ruleset is much like the workings of the saying "do not speak until spoken to. " It's just not permitted (except on port 80:-).   Actually, lets keep state on udp and icmp packets as well:

pass out quick in ed0 proto tcp/udp from 200.200.200.1/32 to any 
                                                keep state
pass out quick in ed0 proto icmp    from 200.200.200.1/32 to any 
                                                keep state
pass  in quick on ed0 proto tcp from any to 200.200.200.1/32 
                                                port = 80
block in quick on ed0

ipf has a tcp/udp shorthand.  It's actually two rules, but you can write it as one.  Now we're keeping state on tcp, udp, icmp.  That means we can make outgoing connections with ease.  Nothing is denied to us, but the outside world can only send packets to our port 80 with any sort of freedom. 

Lets take a look at what happens, rule by rule, if I ping to www.3com.com:

First, a DNS packet is generated, because I don't know the IP address of www. whitehouse. gov.  DNS uses UDP, so the first rule matches, the packet is passed, and ipf lets my nameservers IP reply back to me (since I spoke to it before it spoke to me).   It told me www.3com.com is at 192.156.136.22.  Now I send an icmp packet to 192.156.136.22.  The second rule matches this since it's an icmp packet.   The packet goes out, an exception is made for a reply to come back, the reply comes back, the exception is removed.  This is very handy because there's no need to track down what ports we're listening to, only the ports we want people to be able to get to.  

For added scrutiny, we might consider changing

pass in quick on ed0 proto tcp from any to 200.200.200.1/32 port = 80

to:

pass in quick on ed0 proto tcp from any to 200.200.200.1/32 port = 80
                                                flags S keep frags

This will only let packets through to port 80 if they have a Syn flag.  This is handy in detecting Fin scans (where the Fin flag is set). 

So now we have a pretty tight firewall:

But it can still be tighter.  Some of the original ruleset we wiped clean is actually very useful.  I'd suggest bringing back all the anti-spoofing stuff.   This leaves us with:

block in     quick on tun0 from 192.168.0.0/16 to any
block in     quick on tun0 from 172.16.0.0/12 to any
block in     quick on tun0 from 10.0.0.0/8 to any
block in     quick on tun0 from 127.0.0.0/8 to any
block in log quick on tun0 from 200.200.200.0/24 to any
block in log quick on tun0 from any to 200.200.200.0/32
block in log quick on tun0 from any to 200.200.200.255/32
pass out     quick in ed0 proto tcp/udp from 200.200.200.1/32 to any
                                                         keep state
pass out     quick in ed0 proto icmp    from 200.200.200.1/32 to any
                                                         keep state
pass in      quick on ed0 proto tcp from any to 200.200.200.1/32 
                                        port = 80 flags S keep frags
block in     quick on ed0

Now we're looking pretty good.