Veghead's Guide to OpenBSD Firewalls

Updated for OpenBSD 4.x and pf

Introduction

If you are thinking of implementing a firewall for your network, you will probably be bombarded by many vendors trying to sell you a variety of off-the-shelf "firewall solutions" for large amounts of money. However, with a small investment in effort you can have one of the most effective and secure firewalls in a small amount of time, just for the cost of a PC.

Everyone should know that OpenBSD is the most secure operating system around. That is one of the main reasons why it makes the perfect firewall. Its simple, free, open-source, secure, reliable and will help you sleep soundly at night.

OpenBSD will work on a variety of platforms but this document is mainly concerned with Intel architecture. Not for any other reason than it is widely available and cheap. Essentially this small introduction can be applied to any architecture without much difficulty.

This tutorial explains the process of constructing a bridging firewall with OpenBSD.

First off

Install OpenBSD.

This is relatively straightforward, to start off with - go to http://www.openbsd.org/, find your closest mirror, and follow the install instructions.

In a nutshell, you download a floppy disk image (currently named floppy40.fs), put it onto a disk using dd or rawrite.exe and boot from it. You'll get asked lots of simple questions, given the opportunity to partition your hard-disk and asked how you want to install. HTTP is a good method and if you decide to use it, you'll get offered a list of HTTP servers to use. Choose the nearest to you and let it happen. Hey presto - installed.

When you reboot into OpenBSD, type:

man afterboot


which has got lots of suggestions for what to do initially. Personally I do the following:

Finally - send some hardware or money to the openbsd project by buying something - a cd-rom or t-shirt. Its a wonderful operating system and needs your support.

pf

These days I'd highly recommend using OpenBSD's powerful built in firewall system, pf. If you're used to using ipfilter (ipf) in the past then the transition will not be too painful as the rule and command-line syntax is very similar. There are minor differences in the way the rules work, but pf has such a logical, intuitive, approach that converting your rulesets may actually prove a pleasurable experience; especially if you've experienced problems with fine-tuning ipf.

Kernel Support

Nowadays things are much simpler. pf is compiled into the GENERIC kernel and any fine tuning of kernel parameters can be achieved by using the 'config' tool. You don't need to build a new kernel, in fact it's probably better if you don't. Kernel hackers are going to find it far easier to help you debug problems if you're using GENERIC.

You may have read many articles showing you how to increase a value in the kernel known as 'NMBCLUSTERS' which controls the number of mbuf clusters. From OpenBSD 3.6 upwards, this value is controlled by a sysctl parameter called kern.maxclusters. Again, its default setting is probably fine.

To summarise - leave the kernel alone unless you have an astonishingly good reason.

Constructing a bridge

The first step in making the firewall is to turn the BSD box into a bridge. This means that all traffic on each interface will be forwarded to the other interface. Without any filtering this would allow the joining together of two ethernet networks.

The simplest configuration is to use one of your two ethernet cards as your main interface, which will be given an IP address. The other card will be brought up but will not have any IP addresses associated with it.

Lets assume you have two ethernet adapters, which manifest themselves in the kernel as 'fxp0' and 'fxp1'. Arbitrarily we'll make 'fxp0' the main interface. The commands to configure the cards and the bridge would be:

ifconfig fxp0 <ipaddress> netmask <netmask> up
ipconfig fxp1 up
brconfig bridge0 add fxp0 add fxp1 maxaddr 2000 up

This would configure both interfaces, add them to the bridge0 device, and make the bridge active. The 'maxaddr 2000' term specifies how many MAC addresses are to be remembered by the bridging cache. Set this to the upper limit of how many mac addresses will be available on the network.

OpenBSD provides a simple way to configure the network adapters at boot up. To do this, create a file called '/etc/hostname.fxp0' and place the following inside using your favourite text editor:

inet <ipaddress> <netmask> NONE
for example:
inet 10.0.0.1 255.0.0.0 NONE

All we want to do to fxp1 is to bring it up. So we create /etc/hostname.fxp1 simply containing the word:

up
Next, the bridge device. Place the following into /etc/bridgename.bridge0

add fxp0
add fxp1
blocknonip fxp0
blocknonip fxp1
maxaddr 2000
up

Notice that we've added 'blocknonip' options to each interface. This option stops the interface forwarding anything but IP (IPv4, IPv6, ARP and RARP).

Finally, we edit /etc/rc.conf.
Look for the line that starts
pf=
and modify it to say
pf=YES
This is also a good time to turn off any other junk you won't need. eg
inetd=NO
portmap=NO

At boot time, these files will be consulted by /etc/netstart and the interfaces configured accordingly as if you had typed the commands manually. When the OS comes up, the interfaces will be bridging.

The man pages for brconfig(8), bridge(4) and ifconfig(8) are well worth reading. In fact man pages are generally worth reading - especially with BSD systems. This is something that is too often forgotten. People do a lot of work creating them, and I for one appreciate it. Remember: man is your friend.

Filtering Traffic

Whether you use ethernet, Frame Relay, ATM or PPP, IP filtering rules will all apply. This is due to the wonderful BSD networking architecture.

In a nutshell, you write a set of rules in /etc/pf.conf and load them into pf with pfctl -f /etc/pf.conf. This happens automatically at boot time but you can happily replace, modify and add rules while it's running using pfctl. OpenBSD generously give you some example rulesets to play with in /usr/share/examples/pf. Start with one of these and modify it for your needs. Far easier than trying to do it from scratch.

Rather than try to replicate some of the excellent texts available which describe how to construct pf rules, here is a set of links:

Odds and Sods

pf and states

pf has the ability to keep "state" information on pretty much everything. In other words, it can keep track of every single connection passing through it. This is an attractive feature and it is highly advisable to keep state on all TCP traffic (see the section on window scaling) but it does have overheads. If your firewalling policy keeps state on many connections and you have a very high-traffic network you may find that your state table fills up. To users, this can manifest itself in many ways. A dead giveaway is that TCP connections appear to time-out consistently, whilst pings and traffic passed without state information passes freely. To check the activity of the state tables, type:

pfctl -s info
This will produce something like:
State Table                          Total             Rate
  current entries                    13104
  searches                      1159009994         4217.1/s
  inserts                         10677054           38.8/s
  removals                        10666478           38.8/s
Counters
  match                          604810842         2200.6/s
  bad-offset                             0            0.0/s
  fragment                         3364884           12.2/s
  short                                406            0.0/s
  normalize                            979            0.0/s
  memory                                 0            0.0/s
  bad-timestamp                          0            0.0/s
  congestion                             0            0.0/s
  ip-option                         134562            0.5/s
  proto-cksum                        14936            0.1/s
  state-mismatch                    219220            0.8/s
  state-insert                           0            0.0/s
  state-limit                            0            0.0/s
  src-limit                              0            0.0/s
  synproxy                               0            0.0/s

The highlighted line shows how many states are currently in use. If this reaches the default maximum limit (10000) you will need to increase the limit by adding a set limit state directive to your pf.conf. pf is highly configurable and it's a good idea to familiarise yourself with all of the available options.

Keep an eye on the output from pfctl -s info for a while after you install the firewall. It can alert you to dwindling resources before they run-out altogether.

pf and Window Scaling

"Window scaling" is a TCP option that can cause all sorts of problems with network devices that don't know about it; sadly there's a lot of them about. More and more operating systems are coming on-line that use it by default including Vista and Linux, so if your firewall or router can't deal with it, you're going to get a series of painful headaches.

The good news: pf handles it perfectly well, providing you follow these simple rules:

As pf's author Daniel Hartmeier explains it:

Subject: Re: PF and TCP Window Scaling in NetBSD 3.0
To: Joerg Roedel
From: Daniel Hartmeier
List: tech-net
Date: 07/11/2006 14:57:03

On Tue, Jul 11, 2006 at 02:04:33PM +0200, Joerg Roedel wrote:

> So it must be a problem in the filter code.

... or the ruleset :)

To properly support TCP window scaling, pf must create state on the
initial SYN packet of a connection.

If state is created on a subsequent packet (like when the SYN is
accidentally passed without creating state, and state is created on
the returning SYN+ACK), pf has missed the window scaling negotiation
containing the scaling factors, and will eventually stall
connections. Each peer's scaling factor is only seen in its SYN packet,
and can't be deduced later on.

Check your ruleset and verify that

  a) there is a default block policy
  b) all 'pass' rules applying to TCP have both 'keep state' and
     'flags S/SA'.

Daniel

This text is subject to perpetual tinkering.

<< Back  | Original Version

Valid XHTML 1.0!