NAT with pf
by Jacek Artymiak03/06/2003
Catching Up with Changes in pf(4): Macros, Options, Scrubbing, and Network Address Translation
Despite what some
doomsayers predicted when the OpenBSD project abandoned
ipf (see the
famous CVS log entry), the new pf(4)
packet filter is doing its job very well and is steadily growing in
functionality. A lot has changed since its official appearance in OpenBSD
3.0, and it is high time to have another look at what Daniel Hartmeier and
others have been working on.
pf(4)
has undergone numerous changes since I last wrote about it. It is now
easier to use and has a greater range of available filtering options. If
you have never used pf(4)
before, you'll be delighted to know that it does packet filtering, network
address translation (NAT), load
balancing, protects against spam (coming in OpenBSD 3.3), and
provides--after the merger with
ALTQ--resource-sharing and quality of service control (QoS). In its
current shape, pf(4)
can be used to secure networks, connect multiple hosts to an external
network through a single network interface, balance load between multiple
servers, manage bandwidth, and keep logs of its activity. Because of
this, pf(4)
is quickly becoming an advanced tool for network management and security
practitioners.
|
Related Reading
TCP/IP Network Administration |
This article assumes that you are using OpenBSD 3.2 with all of the latest security patches applied. (If you still don't know how to keep your system up to date, here are some hints to get you started.) The examples presented use the DMZ network configuration described earlier in this series. (Additional sources of information on DMZ design can be found in [Zwicky, Cooper, and Chapman 2000] and [Bishop 2003].) I will also answer some recent reader questions. Because there is so much new ground to cover, this article is split into four parts: (1) network address translation, (2) packet filtering (including transparent filtering), (3) ALTQ, and (4) load balancing and a preview of what's coming in OpenBSD 3.3. For the last installment you'll need to be running OpenBSD-current, as not all of the features I will be writing about are available in OpenBSD 3.2.
It also advisable (but by no means required) that you keep a copy of a good book on the workings of TCP/IP at hand while you are reading this. When I get stuck, I usually look things up in [Hunt 2002] or [Stevens 1994]. If these don't answer my questions, I dig through man pages and RFCs. (Browsing through their book catalog, I noticed that O'Reilly & Associates have a book on Internet protocols [Hall 2000], which could be helpful, but I have not read it. Another good book is [McKusick 1996], which focuses on the design of 4.4BSD and includes a lot of information about TCP/IP itself).
General Setup Tips
A firewall does not need expensive hardware to run, just a reasonably fast machine capable of running OpenBSD 3.2, with two or more network interfaces. (Depending on the internal network topology, it could even be just one interface; for example, the DMZ design described in one of my earlier articles uses three network interfaces on the firewall.) An old Pentium or Pentium II PC should be enough to cope with traffic up to 10Mbps even with packet logging enabled. As for the system memory, 32MB of RAM is quite enough although 64MB of RAM gives the operating system more breathing space. External storage is a more important consideration when the firewall is logging large amounts of traffic. This issue was discussed earlier, but I will return to it later in this series. Finally, on the hardware side, make sure that the firewall's network interfaces are capable of working at the speeds of their connected networks, i.e. a 100Mbps card connected to a network working at speeds up to 100Mbps, not a 10Mbps card when the network is working at 100Mbps. In most cases, a 10/100Mbps card solves this problem rather well, but if you want more information about Ethernet, read [Spurgeon 2000].
|
Related Reading Ethernet: The Definitive Guide |
Remember not to run any network services and not to store any tools
beyond what's needed to run the firewall and the intrusion detection
software. The basic set of archives (base32 and
etc32) should be enough in most cases. If you need the C/C++
compiler and other software development tools (comp32), run
them on a workstation machine and transfer the binaries you create on it
to the firewall via scp(1). Similarly,
the game32, man32, misc32,
xbase32, xfont32, xserv32, and
xshare32 archives can be left uninstalled, as they only take
up valuable storage space (not to mention the fact that, in case of a
break-in, they can be used by the intruder for potential attacks).
/etc/rc.conf
Start the OpenBSD packet filter with pfctl(8),
its control tool:
$ sudo pfctl -e -f /etc/pf.conf
To start pf(4)
automatically on every system reboot, edit /etc/rc.conf, the
"system daemon configuration database", with vi(1)
or any other plain text editor:
$ sudo vi /etc/rc.conf
After you open /etc/rc.conf, change the following line:
pf=NO # Packet filter / NAT
to:
pf=YES # Packet filter / NAT
This file contains two more variables file related to pf(4). First
is the rule file location variable:
pf_rules=/etc/pf.conf # Packet filter rules file
Here you can change the location of the rule file from the default
/etc/pf.conf to whatever you like, but it's probably best to
leave it unchanged. Another variable of interest is:
pflogd_flags= # add more flags, ie. "-s 256"
This variable lists options for the pflogd(8)
packet filter logging daemon. We'll look at it more closely a little
later.
/etc/pf.conf
pf(4)
stores its filtering rules in /etc/pf.conf, a plain ASCII
file that you can edit with vi(1)
or other plain text editor. Every rule is a single, continuous line that
begins with a special keyword. Keywords define a macro, set some global
variable, or describe an action to take for packets that match a rule.
These keywords are
setscrubrdrnatbinatblockpass
Note that /etc/pf.conf contains all rules for
both NAT and filtering. (They were previously stored in two separate
files.) The rule file is divided into five sections: macro definitions,
options, scrub, NAT, and filter. Any of these sections may be missing,
but the order of sections must be maintained, as in the following:
#################################################################
# macro definitions
#################################################################
# options: "set"
#################################################################
# scrub rules: "scrub"
#################################################################
# NAT rules: "rdr", "nat", "binat"
#################################################################
# filtering rules: "antispoof", "block", "pass"
When you think about it, this grouping is quite practical: later sections rely on the previous ones, so it is natural to use the order that simplifies the work of the firewall administrator. The order of the rules inside each section is a different story. It depends on your packet filtering policy and will be the subject of interest throughout this series.
Basic pfctl(8) Operations
Here are a few basic pfctl(8)
operations that you should memorize, as you'll use them often while
designing your own rules:
enable
pf(4):$ sudo pfctl -eenable
pf(4)and load rule file:$ sudo pfctl -e -f /etc/pf.confonly load
pf(4)rule file (no need to disable the packet filter):$ sudo pfctl -f /etc/pf.confparse rules, but do not load them:
$ sudo pfctl -n -f /etc/pf.confdisable
pf(4):$ sudo pfctl -d
Macro Definitions
NAT and filter rules can quickly become complex, so it is very
convenient to define macros to use in place of real names of network
interfaces, addresses, protocols, ports, and other repetitive information
found in filter rules. Life is much simpler with macros; they make it
easier to adapt existing rule sets to changes in hardware configuration.
For example, the only necessary change after modifying the external
interface on the firewall machine is to edit the macro definition. Macro
names must start with a letter from the a-zA-Z range of the
lower part of the ascii(7)
set and may contain letters from the same range, digits, and underscores.
The string that the macro expands to must be enclosed in a pair of double
quotes ("). Macros are not expanded recursively for
simplicity and security. When you're referring to a macro, precede its
name with a dollar sign ($), as in:
#################################################################
# macro definitions
ext_if = "ne1"
dmz1_if = "ne2"
dmz2_if = "ne3"
#################################################################
# options: "set"
#################################################################
# scrub rules: "scrub"
scrub in on $ext_if all
scrub in on $dmz1_if all
scrub in on $dmz2_if all
