Total Pageviews

Search: This Blog, Linked From Here, The Web, My fav sites, My Blogroll

26 July 2010

OpenBSD --- Packet Filtering(PF)

OpenBSD includes a very powerful in-kernel packet filter, pf(4), that not only performs standard stateless and stateful packet filtering, but can also inspect and reassemble packet fragments in several ways, redirect connections, translate addresses in several different directions simultaneously, authenticate users, and manage bandwidth. PF is one of the high points of OpenBSD. PF allows you to do some things that commercial firewall vendors still cannot manage reliably. PF will have features that aren't covered here. Be sure to read the pf.conf(5) man page for details on the nifty features available in your version of OpenBSD.

Packet Filtering(basics)

You can buy a firewall for your cable modem for under a hundred dollars, and you can purchase an enterprise firewall for a hundred thousand dollars.
   What differentiate firewalls are the features that they offer, the hardware they run on, and the robustness of the software.
    Your basic home firewalls perform the bare minimum to allow users to surf the web and keep outsiders out. Your enterprise firewall may do exactly the same thing, but also include application proxies and a hefty support contract. Some breeds of either type are quite resilient, while others can be out-thought by drunken squirrels. Frequently, price and quality have no sensible relationship to each other.
    OpenBSD can be used as the basis for a full-featured firewall. The integrated packet filtering software can perform any of the packet-level tasks that any commercial firewall provides.
    If you want application proxies, however, OpenBSD does not include them (with the exception of a FTP proxy, which is necessary for normal FTP operations through a packet filter). Several popular application proxies run quite well on OpenBSD, but they are not part of OpenBSD.
I've used Squid (/usr/ports/www/squid) quite easily to proxy the most common Internet applications and an assortment of other proxies to manage just about everything else.
A firewall is what you make it. You can send all your network traffic through a simple OpenBSD packet filter and honestly say you have a "firewall," or you can set up application proxies, authentication, and so on, and still say you have a "firewall." Remember this the next time someone says that they have a firewall.
    To build an effective firewall, you absolutely must understand TCP/IP.
If you don't understand as much TCP/IP as you'd like, allow me to recommend Stevens's TCP/IP Illustrated, volume 1 (Addison-Wesley). While you can set up a basic firewall knowing only the basics of TCP/IP, you're going to find that debugging problems can be quite difficult.
Throughout this section, we're going to talk about using your OpenBSD system as a firewall. This assumes that you have two or more network cards, and you want to pass traffic between them. While this is a popular application for OpenBSD, everything discussed here works just as well to protect an OpenBSD machine sitting naked on the Internet. Don't be afraid to implement packet filtering on your web server!


Enabling PF
PF is enabled at system boot by the following two /etc/rc.conf variables:
pf=YES
pf_rules=/etc/pf.conf
By changing the pf value to "NO", you disable the packet filter. Similarly, you can choose a different boot-time PF configuration file by changing the pf_rules variable.
If something is wrong with your PF configuration file and it won't parse, the OpenBSD startup routine will install some basic PF rules that will block almost all traffic to the machine, with the exception of SSH. You'll be able to connect to the machine and correct your rules, but that's about it. (And, as anyone who administers firewalls remotely can tell you, this ability is enough to save a lot of pain.)
If you want to forward packets between multiple interfaces (i.e., be a "firewall"), you need to tell OpenBSD to do this with the net.inet.ip.forwarding sysctl MIB. There's a commented-out entry for this in /etc/sysctl.conf.
#net.inet.ip.forwarding=1
Just remove the pound sign and reboot!
    If you want to have stop and start packet forwarding without rebooting your system, you can do this easily with sysctl(8). Setting this MIB to 0 stops packet forwarding; setting the MIB to 1 enables it.
    If you want to perform some basic system maintenance that may interfere with your network in some way you can stop packet forwarding, do your work, and restart forwarding.


What Is Packet Filtering?
Packet filtering is just comparing packets to a list of rules and accepting or rejecting the packets on the basis of those rules. As network administrator, you decide which packets are naughty and which are nice. When you filter packets for a single host, you could legitimately call that host hardened. (The word "hardened" means almost exactly what "firewall" means — i.e., nothing.)
When you force all packets on your network through a single host that filters packets, you have a basic firewall.
A basic packet filter may only allow you to filter based upon TCP or UDP protocol number. Some don't even allow you to filter by ICMP type or cannot cope with nonstandard protocols.
    PF can cope with almost anything you throw at it. If some client wants a host behind your firewall to speak IP protocol number 184 to their development server, PF will let you do that. Many commercial firewalls won't let you pass such traffic or claim that they will but throw a tantrum if you actually try to do it.


Basic Packet Filtering Concepts
TCP connections can be in a variety of states. A TCP connection that is just opening goes through a three-way handshake process.
  1. A client first requests a connection by sending a special "connection synchronization request" or SYN packet to the server. 
  2. The server responds by sending the client a "synchronization acknowledgment" or SYN+ACK packet, which contains some basic information about how to connect this particular TCP session. 
  3. Finally the client responds with an "acknowledgment" or ACK packet, which tells the server that the client understands and accepts the connection requirements. 
Every part of this three-way handshake must be completed for any actual data to be transferred between the two machines. Your packet filtering rules must permit each part of the three-way handshake and the data transmission itself to complete. Allowing your server to receive incoming messages is useless if your packet filter rules do not permit it to send back an acknowledgment.
    In the early 1990s, packet filters compared each packet to a list of static rules. If a packet matched a rule, it was allowed to pass.
The system did not record what came before and had no idea if a packet was part of a legitimate transaction or not. For example, if a packet arrived marked SYN+ACK and addressed to a machine in the middle of the network, the packet filter would let it pass. Such packets had to pass for internal systems to successfully establish outbound TCP connections. Because the packet filter didn't know who had sent a SYN packet, it couldn't reject such illegitimate packets. As a result, intruders could forge SYN+ACK packets and use them to circumvent seemingly secure devices. Once intruders got packets into the network they could usually trigger a response from some device and then start to worm their way in. This is just one example of a whole host of problems caused by such simple packet filtering. PF can do this sort of packet filtering, but you really don't want to bother with it.
Stateful inspection counteracts this problem. Packet filters that use state inspection maintain a table of every connection running through the firewall. When a client sends out a SYN packet, it records that packet in a table and waits for a corresponding SYN+ACK packet. If a SYN+ACK packet arrives at the packet filter, but no matching SYN packet was sent requesting such a response, the SYN+ACK packet is rejected. This also had the pleasant result of simplifying packet-filtering rulesets considerably. PF performs excellent stateful packet inspection, and it's much easier to manage, so we're going to spend most of our time focusing on that.
    UDP is technically stateless, but some applications expect a certain amount of state. When your system transmits a UDP packet, the application might well expect a UDP packet or ten to flow back in response, or no packets, depending on the application.
  • DNS queries are a popular example of UDP packets flowing back and forth; while UDP has no state, DNS certainly does. 
  • ICMP behaves similarly. You can tell PF to expect and accept ICMP replies, or to disallow them, as you choose.
While UDP and ICMP have no state, telling PF to certain types of replies with these protocols is also called stateful inspection. Packets can be mangled during transit, usually by fragmentation. Part of a packet filter's job is to reassemble those packets in a sensible manner. PF can reassemble and rationalize packets in a variety of ways, depending upon your needs, by using the scrub function.
One of the essential concepts in packet filtering is the question of default accept versus default deny. A default accept stance means that you allow any type of connection except for what you specifically disallow. A default deny stance means that you only allow connections from specific parts of the Internet; all other connection attempts are refused. Once you have chosen your default, you can adjust your rules to hide or reveal network services as you desire. This choice is really between whether you are offering services to the world or only to a select few. 
If your system is a corporate web server, you might want to make it only visible to the company network. If so, you've adopted a default deny security stance. This is especially appropriate when building firewalls to protect a company network — users should not be able to run web servers on their desktops! On the other hand, if you have a sitting out on the Internet, and only want to block a few certain services from a select handful of locations, you're using a default accept stance.
    In addition to packet filtering and packet reassembly, PF has several other important features: network address translation(NAT), connection redirection, bandwidth management, and authentication. We will consider each separately, although they are all configured in /etc/pf.conf and managed with pfctl(8).


Packet Filter Control Program
All packet filter management is handled via pfctl(8). This program will show you the current packet filtering settings, connections that are being processed by PF, the current state of various TCP/IP transactions, debugging information, and any other information you might possibly want to know. Here and there, you will see discussion of how the packet filter control program can be used in conjunction with various sorts of rules.


/etc/pf.conf


Macros
Macros are variables you can define for use within PF rules. They help keep your rules more maintainable, readable, and manageable. A macro name must begin with a letter, but can contain letters, numbers, and underscores. Frequent uses of macros include
  • interface names
  • network numbers, and 
  • host names
For example, network interfaces are generally identified by the name of the network card driver the interface uses: fxp, xl, ne, and so on. If you change your network card, you would have to rewrite all the rules that use that network card.
    By using a macro, you can change the interface name in one place and have it propagate throughout your rules.
External_if="fxp0"
If the IP addresses behind your firewall were 209.69.69.0 through 209.69.69.255, you could define a macro for these addresses. If you added more addresses, you could just add them to the macro.
Internal_ip="209.69.69.0/24"
Similarly, you can define macros for each IP address on your firewall, in case you have to renumber your network. Also, when reading PF rules, it's much easier to read well-named macros than dozens and dozens of IP addresses. Is fxp0 the internal or external network card? You'd have to look it up, but "$External_Int" is unambiguous.
    When a rule needs the term represented by the macro, just use the macro instead. This makes it easy to change information consistently throughout your rules and greatly reduces the risk of typos.
block in on $External_int from $Internal_ip
Similarly, you could write macros for groups of machines. You can easily group machines by purpose and trivially give them the same rulesets.
Webservers="{209.69.69.8, 209.69.69.12, 209.69.69.87}"
When you have a new web server, or one of your old web servers dies, just edit the macro appropriately to have your firewall rules apply to the correct machines. Similarly, we will keep repeating the port numbers 80 and 443 (the standard web ports) throughout our rules. Instead, you can just do this:
webports = "{80, 443}"
Macro names cannot contain any characters other than letters, numbers, and underscores, and they cannot consist entirely of a PF keyword such as "pass," "block," or "scrub." You could name a macro "nat1," but not "nat."
    Combined with braces, macros can make your rules much simpler. Here, we write one rule to allow access to all appropriate ports on all of our web servers:
pass in proto tcp from any to $webservers port $webports
When you add a new web server, you just have to add its IP address to the list in the "webservers" macro. This trivial example doesn't do the concept justice, but once you have dozens of servers with many rules applying to them, you'll see the usefulness of macros.


Tables
PF can store long lists of network addresses (both hosts and netblocks) in tables. These tables can be defined in /etc/pf.conf or can be kept in external files.
    For example, say you have a list of IP addresses of known spam sources that you don't want to allow to talk to your mail server. Such lists tend to be thousands of entries long and would be difficult to maintain inside /etc/pf.conf. Instead, you can create a table with a name like "spamhosts," load your list into this table, and keep your main pf.conf quite simple.
    PF checks IP addresses in a table much more quickly than it handles checking against a list of IP addresses in braces. Tables are handled in a slightly different way than lists, however, and can actually be edited by using pfctl(8) while the firewall is running.
    In general, you should use lists of hostnames for your standard network equipment such as your corporate web servers, and use tables for integration with other network programs such as spam-blockers or intrusion detection systems. (Of course, if your network has hundreds of web servers, you might want to use a table for those as well!)


Defining Tables
While you can create and edit tables entirely with pfctl(8), such usage is fairly advanced. To define a table entirely within /etc/pf.conf put the name of the table in angle brackets(without ". ), list the contents of the table in braces like so:
table "<"rfc1918">" {10.0.0.0/8, 176.16.0.0/12, 192.168.0.0/16}
This creates a table called rfc1918 that contains the private IP addresses that should never appear on the public Internet. You can use this table name later in your ruleset much as you would use a macro.
    If you have a long list of IPs to add to a table, you can just list them in an external file and direct /etc/pf.conf to pull the table from that file. Here's how we would specify that exact same list, using an external file:
table "<"rfc1918">" file "/etc/rfc1918list"
In /etc/rfc1918list, list one IP address or network block per line.
Any line with a leading pound sign is a comment.
Here's a file containing the RFC1918 IP addresses:
#rfc1918 addresses
#These should never arrive at the outside of our firewall!
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
This particular example is very simple, but you might have lists of IP addresses that you update on a regular basis, including spam blacklists and client IP addresses. You could simply
  1. download the new list to the firewall device, 
  2. update the list, and 
  3. reload the table without having to touch your carefully tuned /etc/pf.conf.
You can also specify multiple files in a single table statement like so:
table "<"illegaladdresses">" file "/etc/rfc1918list" file "/etc/weird-ip-list"
This single table includes every IP and network listed in each file.
Note
If you list a hostname in the file, PF will do a DNS lookup and get all of the IP addresses for that host and enter them in the table. For example, www.yahoo.com has eight IP addresses; if you list www.yahoo.com in your file, all eight IP addresses will be added to the table.

Table Attributes
By default, tables can be dynamically edited, added, or even removed. This may well conflict with your desired use for the table. Fortunately, PF allows you to modify this behavior with the special attributes const and persist.
    Some tables should never change. And since the RFC1918 address space is not going to increase or decrease any time soon, you wouldn't expect to see that table change. The const keyword prevents anything from changing the table via pfctl(8), and is used as follows:
table "<"rfc1918">" const {10.0.0.0/8, 176.16.0.0/12, 192.168.0.0/16}
When you load a ruleset into PF, the system automatically parses the file and performs some optimization. Tables that are not referred to in any rules are deleted before they are fed to the kernel. You can tell PF to keep these tables even when rules refer to them by using the persist keyword:
table "<"intruders">" persist
An empty table like this might be used by your intrusion detection system, allowing an OpenBSD firewall to dynamically block questionable traffic. (We'll discuss adding entries to these tables later, but getting your IDS to issue the proper commands will vary widely with the type of intrusion detection system you have.)



Exclusions
You can specifically exclude a section of network numbers from a table by using the negation (!) operator. This lets you make tables easily with entries such as "Every IP address beginning with 10, except for the 10.8.8.0/24 block."

table "<"remote">" {10.0.0.0/8, !10.8.8.0/24}

You can also use a similar entry in a file:
10.0.0.0/8
!10.8.8.0/24
What's more, these can be cascaded, with increasingly smaller areas being negated and included:
1 10.0.0.0/8
2 !10.0.0.0/16
3 10.0.0.0/24
4 !10.0.0.1
In this example, the 1 first line puts IP address that begins with 10 into the table. We then deliberately 2 exclude any entry that begins with exactly 10.0, effectively making 10.1.0.0 the first entry in this table. Then we complicate things further by putting 3 any IP that begins with exactly 10.0.0 in the table, and removing the 4 specific IP 10.0.0.1.
    This sequential inclusion and exclusion lets us build very complicated lists of IP addresses in a few lines of text.


Using Tables in Rules
Tables can be used in rules just like macros, network numbers, or braces. When you define a table, specify the table name in angle brackets:
block in from "<"intruders">" to any
You can also use multiple tables within braces, like so:
block in from { "<"rfc1918>, "<"intruders">" } to any
And tables can be used for packet source or destination, for NAT rules, or for scrub rules. You cannot, however, use tables for routing rules or for the NAT redirection address.


Options
Basic settings that affect how PF performs basic actions, such as
"how long does PF keep state on a TCP connection in the SYN+ACK state" and "do we have logging on?"
are classified as options. All option rules start with the "set" keyword.

Timing Options
You can exhaustively tweak the global timeouts for stateful inspection on TCP, UDP, and other protocols. For most users, this is just a complicated way to get a truly impressive migraine.
Adjusting TCP timeouts is a very tricky subject, and it generally causes network administrators more problems than it solves. Timeouts are all interrelated, and adjusting one without adjusting the others will not achieve your desired results unless you know exactly what you're doing and why. 
Similarly, PF's defaults for other protocols are almost certainly correct for the standard Internet.
    Problems certainly do happen when you're not running on the standard Internet, however. You might be behind an overloaded link, or perhaps you have a satellite connection between you and the rest of the world. PF includes a set of precalculated network timing options for particular sorts of networks. You can set these with the optimization keyword.
    OpenBSD supports four different sorts of timing optimization:
  • normal: The normal setting is for use on a system connected to the Internet via a modem, cable modem, T1, OC192, or other fairly standard method. If you don't specify an optimization, this is the default — in fact, "default" is an alias for "normal."
  • high-latency: The high-latency optimization option is for systems connected to the Internet via satellite or other extremely slow methods. Timeouts are turned up very high. "Satellite" is an alias for "high-latency."
  • aggressive: Aggressive optimization quickly discards idle connection information. This saves memory and CPU time, at the possible expense of cutting off legitimate connections. If you are on a high-speed network and have limited memory on your system, or you don't want people leaving idle connections hanging around to systems on the other side of the firewall, you might want to use aggressive optimization.
  • conservative: Conservative optimization extends timeouts coherently so as to not cut off any legitimate connections if at all possible. This can use more CPU time and memory.
Setting the optimization is very easy:
set optimization high-latency
If you're a real TCP/IP stud, feel free to look at pf.conf(5) for the "set timeout" keyword, and adjust the timeout on each individual stage of the protocol negotiation process as you see fit.


Enabling Logging
PF's logging functions can be enabled on an interface-by-interface basis with the "loginterface" keyword. Each interface that you want to enable logging on should be specified by this option:
set loginterface fxp0
You can disable logging entirely, on any interface, by setting the interface name to "none."




PF Memory Limits
One problem with filtering network traffic is that you may have very little control over how much traffic you receive. One day Slashdot might post a link to a web page behind your OpenBSD firewall, and you would start to receive tens of thousands of connections per minute. OpenBSD can handle this, if you have the proper hardware, but it may consume all your system memory if your hardware is inadequate. The two features that are most likely to use memory are stateful inspection and fragment reassembly. By limiting the amount of memory you will allocate to these, you can guarantee that your system will have memory left for such trivial things as a command prompt.
    The "set limits" keyword configures these limits. You can limit the number of connections you are performing stateful inspection on by using "set limits states" and limit the number of fragments with "set limit frags":
set limit states 5000
set limit frags 10000
To make your rules slightly shorter, you can set both of these on a single line using braces.

set limit { states 5000, frags 10000 }
Yes, you can use queuing to limit outbound traffic, but that won't help you if your system runs out of memory.
    Generally, speaking, one state uses about 1KB of memory, so a machine with 64MB RAM could handle about 65,000 states. The amount of memory used by fragments depends on the size of the fragments, but is generally quite small.


Blocked Packet Policy
When PF blocks packets, it can either send back an error message to the originating system or refuse the traffic without returning any errors. Configure this with the "set block-policy" keyword.
    It is generally considered polite to return an error, as clients will immediately realize that they cannot make this connection and (usually) tell the user that the connection has been refused. Your security policy may or may not involve politeness.
The "return" option tells PF to return polite errors: an RST for TCP connections and an ICMP unreachable message for UDP connections. 
All other connections will be silently dropped anyway, but this covers most situations:
set block-policy return
By default PF silently drops blocked packets. If your silently drops the traffic, applications will have to wait for the network protocol timeout to elapse before realizing that they cannot connect. The "drop" option tells PF to silently drop packets:
set block-policy drop
This blocked packet policy can be overridden on individual filter statements.


Packet Normalization
Packets can be shredded in transit, and processing these shards of data can increase system load. The system at one end of the connection might have a habit of breaking up transmitted packets into tiny bits, or perhaps some router in the middle thinks that your packets are too large and splits them up to digest them more easily. In either event, instead of nice whole packets your network will receive small bits, or fragments.
Different operating systems reassemble packets in slightly different ways. While these differences are usually trivial, a skilled attacker can use them to exploit the client operating system. 
PF's "scrub" rules allow you to decide how to handle packets, or even to perform some rearrangement of your own. (Other people "mangle" packets, but you "normalize" them.) PF's goal is to hand the client operating systems complete packets, so that the client doesn't have to do the work of reassembling the packet and so you don't have to worry about possible attacks on the client's packet reassembly code.
Packet normalization is done via "scrub" rules. The easiest way to perform the most common packet-fragment handling is to just put in a basic scrub rule:
scrub in all
This affects all packets entering the computer. While PF won't fragment or change packets unless you tell it to, you might want to also use a "scrub out all" rule. If you have a complex internal network some internal device might well mangle the packets. You could also add interface names to this rule, to only normalize packets coming from one network. In most cases, not scrubbing your own packets is an example of "My network can do no wrong."
scrub in all on fxp0

Customizing Fragment Handling
By default, a scrub rule reassembles fragments. The bits and pieces of each packet are cached locally until the entire packet arrives, and then the completed packet is passed on down to later rules. This simplifies the writing of rules and makes it easy for your clients to receive more complete packets than they would otherwise.  
If you are using network address translation, you must use fragment reassembly.
While this is the default behavior for scrub rules, you can explicitly set it with "fragment reassemble."
scrub in all fragment reassemble
You could also choose to crop fragments. One packet can be partially repeated in a few different fragments. Cropping eliminates all duplicates and trim out any content overlap within fragments. This results in one and only one copy of each piece of data traveling on down the rules and onto the network. PF normally keeps a very small cache of recent fragments for comparison purposes. Cropping reduces PF's memory requirements, which may be important if you're running on very old hardware. In most cases, reassembling packets is a better choice.
scrub in all fragment crop
If you're really short on memory (e.g., running OpenBSD on a VAX), you might want to perform even more drastic fragment trimming. You can drop overlaps and fragments alike with the "drop-ovl" scrub rule.
scrub in all fragment drop-ovl
If you're this desperate for memory, however, buy a Pentium 100 to do your packet filtering instead. If you're running thousands of states simultaneously, you can certainly afford a machine with more RAM!
In addition to handling fragmentation, scrub rules can perform some other basic packet checking and manipulation. One common setting on network packets is the "don't fragment" flag, which indicates that either the client or the server did not want this particular packet broken up. On occasion, however, a particular system or program will do something odd such as send a fragment with the don't-fragment flag set. This can confuse other computers on the network. You can choose to unset the don't-fragment flag on all packets that pass through PF by using the "no-df" modifier:
scrub in all no-df
Every IP packet has a time-to-live(TTL). You can insist that all packets that pass through your packet filter have a minimum TTL. You would probably want two different scrub rules for this, as packets that you are sending should probably have a very high TTL, while packets entering your network should have a TTL large enough to let them reach their destination within your network. Set this with the "min-ttl" modifier:
scrub in on fxp0 min-ttl 5
scrub in on fxp1 min-ttl 30
Finally, you can adjust the maximum segment size of packets that pass through PF. Some networks have a problem with an unusual maximum segment size, so use the "max-mss" option with care.
scrub in all max-mss 1500
You can combine scrub rules on a single line, or break them up by interface, or any combination of these. Here, we have a scrub rule that applies to all packets, and separate rules that apply to packets that enter the system on particular interfaces:
scrub in all max-mss 1440 fragment reassemble
scrub in on fxp0 min-ttl 5
scrub in on fxp1 min-ttl 30 no-df

Avoiding Fragment Processing
Fragment caching and reassembly uses memory, of course, and you might think that you can reduce memory usage by not reassembling fragments. Don't do this.  Buy memory instead.
    Fragments are generally the maximum size as the physical protocol allows, which is frequently much smaller than a large TCP/IP packet.
    Each fragment relies on information available in the first few fragments, such as the destination and source ports. This means that if you do not reassemble fragments, you will not be able to filter based on source or destination port, or on ICMP message or code. This means that you cannot use stateful inspection on fragments, which immediately increases the size of your rule base dramatically.  Packet descriptions that rely only upon source and destination IP address and protocol will catch fragments, but once you mention a port number the fragment will not match the rule. You can choose to either let all the packets in or block them without referring to port number, both of which dramatically reduce your security options. Do something with your fragments!


Packet Filtering
While packet filtering rules come last in /etc/pf.conf, understanding packet filtering is essential to using PF. People use PF without going near address translation, redirection, or any of the other nifty PF features, but packet filtering is the destination for many people. You could say that the entire function of PF is to "filter packets": allow packets with this TTL or higher, sieve out fragments, and so on. In this context, however, packet filtering has a very specific meaning:  providing access control for network packets by source, destination, protocol, and other packet characteristics.


What Packet Filtering Doesn't Do
Packet filtering controls network connections based entirely on TCP/IP protocols and protocol characteristics, such as ports.
  • If you want to stop all connections coming from a particular block of IP addresses, packet filtering is your friend. 
  • If you only want to allow connections to a particular TCP/IP port, packet filtering will work for you. 
  • If you want to allow entrance only to packets with the ECN flag set, but no other flags, PF will do that without even questioning why you would want to do such a weird thing. 
  • You can also filter other protocols that operate at a logical protocol layer such as IPSec, SKIP, VINES, and so on, but only on the logical network protocol. 
  • If it's a different protocol layer, PF cannot even see it.
One common question is, "How do I use PF to filter based on Ethernet MAC addresses?" The answer is, "You don't." MAC addresses are part of a physical protocol and are in a different layer. You might as well ask how one could use PF to filter dial-up connections. (Also, don't forget that MAC addresses are easily changed, and filtering based on them is more trouble than the security gained in almost all environments.)
    Mind you, OpenBSD does have a tool to filter based on MAC address, brconfig(8)(brconfig is now integrated into ifconfig). But it's not part of PF.
Also, PF doesn't know anything about applications or application protocols. If you allow TCP/IP connections to port 25 on a server within your network, you might think you're allowing connections to the mail server on that host. Actually, you're allowing the connection to whatever daemon happens to be running on port 25 on that host! PF does not and never will recognize a SMTP data stream; it only sees that the connection is going to port 25 on that host, and allows it.
At one time, I had a system on the Net running an ssh daemon on ports 25 (email), 80 (web), 110 (POP3), 443 (secure web), and several other popular TCP/IP ports so that I could saunter past whatever packet-filtering firewall I happened to be behind that day. It made a very effective demonstration of exactly why I thought that company's security system could stand improvement.


Packet Filtering Rule Design
We took a brief look at the design of a PF rule earlier this chapter. Let's completely dissect this same rule to identify each piece.
pass in on fxp0 proto tcp from any to 192.168.1.1 7 port 22 8 keep state
  1. The first part(pass) of the rule is the keyword that tells PF how to process this particular rule. Every packet-filtering rule begins with either "pass" or "block." 
  2. We then state if this rule applies to packets entering (in) or leaving (out) the system. 
  3. This rule applies to a particular interface(fxp0) .
  4. We then have several statements to define the characteristics of the connection that this rule matches — a regular expression for TCP/IP, as it were. This rule applies to TCP connections from any IP address, if the connection is made to the IP address 192.168.1.1 on 7 port 22. 
  5. Finally, if the rest of the rule matches and the connection is allowed, we keep state for the connection.
Each sort of rule has a slightly different syntax, depending on the type of protocol being filtered. For example, ICMP has no port numbers, so the rules are written slightly differently. Their own modifiers can follow some keywords.


Pass and Block
Each packet-filtering rule begins with one of two keywords: pass or block. Pass rules allow traffic that matches the pattern specified in the rule to continue. The trick lies in specifying the pattern that matches only the packets you want to pass.
    Block rules are similar, but they have a wide range of possible responses. What, exactly, should happen when a packet is rejected? PF can give several types of responses, depending on the protocol you are using and your desired behavior.
The default is to silently drop rejected packets, but this can be adjusted on a global level by the block-policy option (see "Blocked Packet Policy"). You can also decide how to respond to blocked packets on a rule-by-rule basis.
Dropped packets are simply rejected without notifying the client. The effects of this vary widely depending on the sort of connection and the type of client. In most cases, the client will wait until the protocol times out and then complain that it could not make a connection or that the connection was lost. While this is the default, you can set it explicitly to override a default policy.
block drop in all
You can make your system respond politely to TCP requests and say, "No, I'm not going to accept that connection" by using the "return-rst" response. As it implies, this returns a RST (reset) for any matching attempted connection.
This only works for rules that only match TCP packets — if you do not specify the protocol, you will get a syntax error. 
The following example sends a RST for every incoming TCP connection request:
block return-rst in proto tcp all
This only affects TCP packets, however. PF includes the "return" keyword, which returns an RST for TCP connections and an ICMP UNREACHABLE message for UDP requests and silently drops everything else. This is the same as the "block-policy" option.
block return in all
You can also specify particular types of ICMP responses for matching packets. This defaults to the standard "port unreachable" ICMP type 3 message, but you can choose to override it and return a more specific ICMP code. The effects of this will vary depending on the error code returned; if you are not conversant with ICMP error messages, either take the default and like it or learn the proper ICMP code to return in each circumstance. Getting this wrong is an excellent way to announce that you have a misconfigured firewall, you don't know what you're doing, and would some kind hacker please show you the error of your ways?
    If you just want the standard "icmp unreachable" message, use the "return-icmp" statement.
block return-icmp in all
Alternately, if you know which ICMP code you want to return, you can specify the code name or number in parenthesis after the return-icmp statement. If you to return a polite message telling clients that they may not connect to your network, for example, you might want to use ICMP code 13, "filter-prohib."
block return-icmp(filter-prohib) in all
If you're seriously interested in ICMP filtering, uses, and the effect of filtering and returning various sorts of ICMP responses, I recommend you check out Ofir Arkin's "ICMP Usage in Scanning".

Default Pass or Default Block
Out of the box, PF uses a "default pass" stance. This is very simple to change with two rules at the beginning of /etc/pf.conf.
block in all
block out all
Now, nothing will go in or out unless you explicitly create a rule allowing it.


Additional Actions in Rules
You can use a couple of keywords here to specify actions that the PF system should take upon matching a packet to a rule.
    If you specify the "log" keyword immediately after the "in" or "out," a log message is sent to the pflogd(8) daemon (see "PF Logging"). If pflogd is not running, no log is kept.
    If this rule includes a "keep state" or "modulate state" statement; only the packet that establishes state is logged.
    If you want to log all the packets in stateful inspection connections, use the "log-all" option instead of just plain "log." All packets that match that rule, not just the initial packet, will be logged.
block return in log-all
You can tell PF to stop processing the rules when a packet matches a particular rule.
Remember, all rules are processed in order: If you have a rule that allows a connection and a later rule that disallows that connection, you can use the "quick" keyword to prevent PF from ever reaching that later rule. This makes it safe to have a "block all" rule last in /etc/pf.conf, and it can accelerate PF on long rule lists. Remember, all rules are processed in order.
pass in quick proto tcp from any to $ext_if port 22 keep state
The "quick" keyword must appear after the "log" or "log-all" keyword or the "direction" keyword if you are not logging. The PF developers discourage use of the quick keyword, as you should be able to achieve the same results with a properly-written ruleset.


Packet Pattern Matching
One of the most intensive parts of PF is the syntax used to describe packets. The next several terms in a rule describe particular sorts of packet by protocol, port, direction, and various other characteristics. PF will compare each arriving packet to these rules. If the rule matches the packet description, it will be treated as you decide. These terms must be specified in the order presented here(A term can be skipped, but the terms that appear must be in order).


Interface Matching
The "on" keyword describes an interface that this rule applies to. You must specify an interface, either explicitly or with a macro. If you want a rule to match every interface on the machine, you can use the "all" interface name. Here we stop all traffic coming in on interface fxp0 and allow traffic out on whatever interface is represented by the macro $external_if:

block in on fxp0
pass out on $external_if

Address Families
You could list an protocol address family, either "inet" for IPv4 or "inet6" for IPv6, to state that this rule only applies to packets in that type of address. The inet address family includes the IP, ICMP, TCP, and UDP protocols. The inet6 family includes the IPv6, ICMPv6, TCP, and UDP protocols. While TCP and UDP are common to both families, IPv6 and ICMPv6 are extremely different from IP and ICMP. The following rules allow standard IP traffic but deny IPv6 traffic:

pass in inet
block in inet6


Network Protocol
In addition to the inet and inet6 families, PF can recognize almost any network protocol by number or name. The "proto" keyword tells PF to filter by protocol. Network protocols can be listed by name, as given in /etc/protocols, or by protocol number. For example, here is a rule that allows SKIP traffic (protocol 57) to pass through the packet filter:
pass in proto 57

Obviously, this functionality somewhat overlaps the inet and inet6 statements — you could have a statement that explicitly allowed the IP, ICMP, TCP, and UDP protocols.


Address and Port
The next set of statements is the most commonly used type of packet-filtering rule, identifying source and destination addresses and ports. The syntax is very simple:
from source-address port source-port to destination-address port destination-port

Addresses here can be specific IP addresses or netblocks in CIDR notation (such as /24, /22, and so on). You could also use the keyword "any," meaning "any address." For example, this rule allows connections from anywhere on the Internet to the IP address 192.168.1.5:
pass in from any to 192.168.1.5

You could also specify an address as "no-route," meaning "any address which is not currently routable." If your OpenBSD machine does not know how to get to an IP address, the no-route address matches. It's usually a good idea to drop packets for unreachable hosts — even if they are legitimate, your system cannot respond to them. (This can only happen on systems without a default route, of course.)
    Interface and host names can also be used in the address space. PF will automatically translate these to IP addresses when loading the rules. Here, for example, we are allowing the machine 192.168.1.200 to connect to anything on the fxp0 interface:
pass in from 192.168.1.200 to fxp0

When you activate these rules and check them within PF, you'll see that the live rules contain the actual IP addresses on interface fxp0.
    Ports can be specified as numbers or as names as found in /etc/services. Port numbers exist only in the TCP and UDP protocols, so when you specify a port you must specify the protocol being used. Use of the port keyword is optional in both source and destination; if you don't care which port a connection is coming from or going to, do not include a port statement. Here, we tighten the previous rule to specify that only TCP connections to port 22 are permitted:
pass in proto tcp from any to 192.168.1.5 port 22

Remember, this is not the same as only allowing SSH connections! PF doesn't know what application protocol you're using; it only knows about the TCP/IP port you are permitting.
    If you know the source port of a connection, you can easily specify that on the rule line. For example, many old firewalls make all of their outbound DNS queries from UDP port 53. Here, we allow the nameserver running on our PF-protected host to make queries of our ISP's nameserver. We're also specifying that packets may return back to this service:
pass out proto udp from 192.168.1.5 port 53 to 10.15.3.8 port 53
pass in proto udp from 10.15.3.8 port 53 to 192.168.1.5 port 53

Note that we specify "pass out" in the first rule so that packets can leave this system and "pass in" on the second rule so that the responses may come back. As DNS queries run over UDP, we restrict this to UDP queries only. (In a little bit, we'll see how to write this sort of rule more securely and as a single line with stateful inspection.)


User and Group
PF allows you to filter by the user and group of the socket opened by the program trying to access the network. This functionality can only work when connections originate with or terminate at the PF device; you cannot use this on a network-protecting firewall to protect hosts within the network.
In other words, if you're running a web server on your OpenBSD machine, you can use PF only allow connections to port 80 if the web server user has opened the connection, but if you're using your OpenBSD machine to protect another web server the user and group ID will not work. Also, this only works with TCP or UDP protocols; if you try to filter other protocols such as ICMP by user, the restriction will be ignored.
A "user" or a "group" keyword followed by a name or UID/GID activates this functionality, such as:
user username

Add this after your packet address and port statement. For example, here we allow anyone in the wheel group to make outbound SSH connections from this machine:
pass out on fxp0 proto tcp from any to any port 22 group wheel
Be careful restricting user functionality; you might be surprised what sorts of connectivity a shell user might need! If you don't have interactive shell users, and none of your users should be running programs on the server, you might consider a rule such as this:
block out on fxp0 proto tcp from any to any group customers

In the event of a user account compromise, or if some FTP-only customer even manages to break out into a shell, he won't be able to initiate any outbound connections from your system. This will seriously restrict the intruder's ability to use your server to attack other servers, which is always nice. The downside is, he'll have time to spend trying to break root on your server instead. Are you certain all of your programs are secure?(Of course, if the user account can use CGI programs, the intruder could just run a program with the permissions of the "www" user)


Packet Flags
PF allows you to filter based upon TCP packet flags. A packet flag is a flag that indicates the state of the connection that the packet claims to be a part of. For example, when a client sends a SYN packet to a server, that packet includes a flag that says "I'm a SYN packet." The returning SYN+ACK packet will have two flags set, the SYN flag and the ACK flag. TCP packets have many different possible flags, each with its own purpose.
    If you're going to start complex filtering based on TCP flags, you must be completely intimate with TCP/IP. You will see many sites on the Internet that claim that if you filter on such-and-such flags, you will get a particular useful behavior. Research these claims carefully. The problem is that every TCP/IP stack has been modified by vendors for their own purposes and behaves in a slightly different way. You may find a description of a neat filtering-on-flags trick that can prevent port scans from working, for example, but then discover that some of your desktop systems crash whenever they try to access the Internet.
If you want to really get into flag-based filtering, get a copy of all three volumes of TCP/IP Illustrated and don't just read it; assimilate it, live it, commune with it, become one with it. Ambitious flag-based filtering is of questionable efficacy, and (in my opinion) the single part of firewall construction most laden with uncertainty, error, and superstition.
Simple flag-based packet filtering help manage multi-interface firewalls. If you want to filter connections through one machine to your sales office, your financial network, your DMZ, the factory in Shaolin China, and the public Internet, the only way to differentiate traffic flows is on which flags are set in each packet. PF recognizes the flags in Fig.
    We will discuss certain precise combinations of flags that can be used in various situations. Using other combinations of flags leaves you on your own.
The general syntax of a flag statement is:
flags "setflag" / "flaglist"
This rule matches a packet that has certain flags set out of a list of flags. One popular example is:
flags S/SA
PF will check the "S" and "A" flags on every packet. If the "S" flag is set, and the "A" flag is not set, this rule matches. This rule doesn't care if, say, "R" or "E" or any other flag is set, because it's not in the list. This is a typical "connection creation" rule; packets that are sent by one machine requesting access to a another have the SYN flag set, but the ACK flag not set. This is commonly referred to as a "SYN packet."
You can also specify lists that cannot be set, such as this:
flags /SFRA
A matching packet has none of the flags S, F, R, and A set.
    If you're only concerned about one flag, you can specify it on both sides of the slash. Here, matching rules have the SYN flag set, and the other flags are irrelevant:
flags S/S
Add the flag statement after the address and port statement in your PF rule. Here, we are allowing any machine on the Internet to request a TCP connection to our web server:
pass in proto tcp from any to 192.168.1.5 port 80 flags S/SA
If the packet has the SYN flag set, and the ACK flag is not set, the connection will be allowed. If both the SYN and ACK flags are set, this rule does not match.


Filtering on Flags and Port Scanners
In most cases, filtering based on flags is just over-engineering with very little net benefit.
    One popular use for flag-based filtering is to confound network scanners such as nmap that use the characteristics of an operating system's responses to bad packets to identify an operating system. While this is of questionable efficiency — the intruder will just use some other method to identify the operating system — PF can provide some basic protection against these sorts of probes with the following rules:

block in quick proto tcp all flags SF/SFRA
block in quick proto tcp all flags SFUP/SFRAU
block in quick proto tcp all flags FPU/SFRAUP
block in quick proto tcp all flags /SFRA
block in quick proto tcp all flags F/SFRA
block in quick proto tcp all flags U/SFRAU
block in quick proto tcp all flags P

ICMP Types and Codes
Much like TCP flags, ICMP has types and codes. Despite what you might hear, it is not proper to unilaterally block ICMP. ICMP has a vital role to play in many sorts of connections. Fortunately, most ICMP needs are handled very easily by the provided block policies.
You can allow particular types of ICMP traffic with the "icmp-type" keyword.

icmp-type type code code

For example, here's how you allow incoming ping requests. This doesn't allow the responses, but it will let the requests into your system. (Adding stateful inspection would let the transaction complete, but we haven't discussed stateful inspection yet.)

pass in inet proto icmp icmp-type 8 code 0

Everything I said about filtering by TCP flags applies doubly to filtering ICMP. You must really understand TCP/IP before you start playing with ICMP filtering.


IP Options
Unless you specifically allow packets containing IP options, PF will block them. The "allow-opts" keyword tells PF to accept matching packets if they contain IP options.
    With the packet description language presented here, you can describe almost any TCP/IP packet and give basic descriptions of packets of most other protocols.
    While passing and blocking is theoretically all you need to do with packets, PF includes some options to make managing packets easier.


Type of Service
Every TCP packet includes a Type of Service field. The setting of this field varies with the application protocol; each application requests a particular Type of Service setting. You will need to study your particular application to determine what Type of Service your application uses (or, just drop a packet sniffer on the network and see what it says). PF can match packets on Type of Service with the "tos" keyword. Here we allow an application protocol to pass when it has a Type of Service of 0x10, but reject it when it has a Type of Service of 0x08:

pass  out on fxp0 proto tcp from any to any port 88 tos 0x10
block out on fxp0 proto tcp from any to any port 22 tos 0x08
Applications can change the requested Type of Service after the connection is set up, making filtering based on Type of Service very tricky. In "Bandwidth Management," we'll see an example of using Type of Service to offer different service levels to different parts of an application.


Labels
Your /etc/pf.conf file should certainly be well commented, with labels for each major section describing the desired effect so your coworkers can have some idea what was going through your mind when they get paged at 3 a.m. to diagnose a problem. But you can also use labels visible to pfctl(8).
    These labels have no operational effect whatsoever, but can help you manage your system. Labels can be useful for parsing rule output for billing purposes, for example. To use a label, add the "label" keyword and a label name to the end of your rule.
pass out proto tcp from any to any port 22 label ssh
We have the label at the end of the rule and a simple label name(ssh). In addition to these simple text names, you can use macros within label names. PF provides several macros for label names. When you use a macro, the label name must be in double quotes (").
$if  Interface name
$srcaddr  Source IP address
$dstaddr  Destination IP address
$srcport  Source port description
$dstport  Destination port description
$proto Protocol name
$nr  Rule number
These macros are parsed when the rules are first loaded; you can't dynamically build a list of labels from the packets that pass through your firewall. If a macro references a field with an "any" value, the macro also shows up as "any." For example, our sample label rule above doesn't list an interface name. Let's use the $if macro in the rule name:
pass out proto tcp from any to any port 22 label "$if:ssh"
When you load the rules and run them, the label will show up as "any:ssh." The rule doesn't mention an interface, so the rule applies to any interface, and the label macro knows it. Macros for IP address and port are most useful when applied to macros for groups of servers or ports: Each generated rule will be separately labeled.
pass in proto tcp from any to $Webservers port 22 label "ssh:$dstaddr"
This will generate a separate rule for each web server, each with its own label named after the IP address of the web server.
    One thing to note is that you can use the same label multiple times within a rule. I could put the string "label ftp" in several FTP-related rules, and they would each show up as a separate lines with the same name when viewing the statistics, like this. Or, if I have a rule with brackets to allow multiple protocols or addresses, it will expand to have multiple rules with the same label. For example, this rule creates multiple labels called "web":
pass out proto tcp from ($ExtIf) to any port {80, 443} label web
If you want separate labels for these, try combining the label with the macro for the destination port, like this:
pass out proto tcp from ($ExtIf) to any port {80, 443} label "web:$dstport"
This would create two labels, called "web:80" and "web:443."


Anchors and Named Rulesets
PF supports the ability to add attachment points for rules. You can define an attachment point, or anchor, so that when a packet reaches the anchor point in the rules list, the rules in that attachment point are interpreted. At first glance, this seems rather pointless — why not just put the rules you want to have executed where you want them executed, instead of using an attachment point and some fancy redirection?
    The clever bit is that outside programs can add rules into anchor points! Your IDS can add rules to block an IP address when it detects an attack, although with the current state of IDS technology this tends to block legitimate activities too frequently. Your mail server can use this to block known spam sources (see spamd(8)). Your authentication system can add rules to your firewall when a user logs in. We discuss OpenBSD's integrated authpf(8) features , which makes heavy use of anchors. Most of these functions require a bit of programming, or at least shell scripting. PF supports the following types of anchors.
nat-anchor  An anchor for NAT rules
rdr-anchor  An anchor for redirection rules
binat-anchor  An anchor for bi-directional NAT rules
anchor  An anchor for packet-filtering rules
In pf.conf, an anchor appears like this:
anchortype anchorname
For example, an anchor called "intrusion-detection" would appear as:
anchor intrusion-detection
You cannot put anchors within anchors.


Rules, Interfaces, and DHCP
All PF rules are set at the time the rules are parsed. This can be a problem if you're using DHCP or PPP, where your IP address will change frequently enough to cause annoyance. Your computer may be up for some time and change IP addresses several times. By putting the interface name in parentheses, you tell PF to adjust the rules whenever the IP address changes.
block in on (fxp0) from any to any


Using Stateful Inspection
You can now match particular sorts of packets, and allow them in or out. You can even set rules that will identify the first packet in a TCP/IP request. If you can identify the first packet of a permitted data stream, you can tell PF to not only allow that packet but to allow all packets related to that first packet.
    For example, suppose a SYN (connection request) packet arrives at port 80 on your OpenBSD web server. If you have a rule allowing stateful inspection, PF will examine that packet and record the source IP, source port, and various other connection characteristics such as sequence number. This information is stored in a state table, or a list of existing permitted connections. When your web server replies, PF will run that packet through the state table. When it sees that a permitted connection exists between the web server and the client requesting access, and that this packet matches the information it has for this connection, it allows the packet to pass without running it through the rules table.
    If an ICMP message related to a particular TCP connection arrives, stateful inspection will match the message to the proper connection and let it through.
If the connection is properly terminated, or if no traffic goes over it for a reasonable length of time, the entry in the state table is discarded.
You enable stateful inspection with the "keep state" keyword at the end of your rule.
pass in proto tcp from any to 192.168.1.5 port 80 flags S/SA keep state
Not only can packets arrive at your web server now, but also with the addition of stateful inspection("keep state") your web server will be able to respond. Note that this rule only matches initial TCP connection requests("flags S/SA"), so an intruder would not be able to send a packet that appeared to be part of an outgoing connection instead, or any of the other wide range of TCP/IP tricks that intruders have used through the years. (Without this flag, you could clear your state table and PF would automatically rebuild it from existing connections. We'll talk about that in "Managing Stateful Inspection.")


UDP State
UDP traffic is stateless, but in some cases you may well expect a response packet. As the firewall administrator, you must know if a particular application protocol that runs over UDP will send a response or not. If you are expecting a response, you can use stateful inspection to match based on hostname and port.
Because UDP packets don't have any of TCP's fancy flags, that's the best any filter can do. 
For example, systems expect replies to DNS queries, and you should know that. In this example, we allow anyone within the firewall to access the ISP's nameserver and receive the replies.
pass out proto udp from any to 10.0.0.5 port 53 keep state
With all the caveats, UDP stateful inspection might seem to be risky. The risks are not as high as they might seem, however. To breach UDP stateful inspection, the intruder would have to send UDP packets to port 53 on a particular client that appeared to be from port 53 on the nameserver, and do this before the stateful inspection timed out, and be able to use that packet to compromise the client in some way. Can it be done? Yes. Is it very difficult? Absolutely.
    In an ideal world, you would never permit UDP through your firewall. But that isn't very likely given the current design of the Internet.


ICMP States
While ICMP messages that are part of TCP connections are handled by stateful inspection in the TCP connection, ICMP messages used for connections such as ping(8) and traceroute(8) have separate stateful requirements. PF is aware of the most common of these and can use stateful inspection to allow the proper replies. We earlier saw how to allow ping requests into a packet filter; now, we can easily allow the replies:
pass in inet proto icmp icmp-type 8 code 0 keep state

State Modulation
State modulation is much like stateful inspection, but provides additional security enhancements for poor TCP/IP stacks. Every connection request contains an initial sequence number, or ISN, that the client and the server both use to track individual connections. If you can guess this ISN, you can hijack the connection and feed the client any information you like. This might sound unlikely, but the most common TCP/IP stack in the world chose astonishingly poor ISNs, making ISN hijacking a popular technique.
State modulation replaces the ISN sent by every machine involved in a connection by a highly random ISN, and translates that ISN as needed for each client. While it only works for TCP connections, it provides a much greater level of TCP protocol security than the naive TCP stack found in the most common desktop operating systems.
You enable state modulation with the "modulate state" keyword. This includes stateful inspection. Here, we enable state modulation for web browsing on every client behind our network:
pass out from any to any port {80, 443} flags S/SA modulate state

Inspection and Modulation Options
Stateful inspection and state modulation have two settings that can be tweaked:
  1. the number of states a particular rule will match and 
  2. the timeouts for the state table. 
Each option should be given in parenthesis after the "keep state" or "modulate state" statements. You can use the "max" keyword to limit the number of entries in the state table. You might be low on system resources and want to conserve memory, but this is also useful for connection rate limiting. If someone posts the script for the next Harry Potter movie on your web server, you could find your bandwidth devoured by hordes of rabid fans. The max keyword will restrict the state table to that many entries in the state table. Additional packets that would match the rule and keep state are rejected. When an existing connection times out or terminates, another connection may be opened. This example limits port 80 to 100 simultaneous connections:
pass in proto tcp from any to 192.168.1.5 port 80 flags S/SA keep state (max 100)
You can use this same technique to implement rate limits to any port, and it is much simpler than limiting rates via the program itself or by using bandwidth managers. I personally prefer to return requested information quickly for the users that are lucky enough to get in and get them off my server so I can serve other people, as opposed to making everyone's connections unbearably slow via bandwidth queues. Your preferences or requirements may be different, of course.
    Timeouts can be adjusted on a per-rule basis, much as the global stateful inspection timeouts can be set. While I find the defaults perfectly sensible for almost all situations, there are occasional rules where adjusting them makes sense.
    You can view the current timeout settings with "pfctl -s timeouts." This will spit out the stateful inspection timeouts for every state of a TCP connection. By default, idle TCP sessions time out after 24 hours(24h*3600s=86400s). Here I tell TCP connections to port 80 on my web server to time out in only one hour, or 3600 seconds:
pass in proto tcp from any to 192.168.1.5 port 80 flags S/SA keep state (tcp.established 3600)
This is generally not necessary, but you might have special circumstances.
Stateful inspection is absolutely necessary if you are using NAT. Without stateful inspection, your packet filter will be unable to match translated addresses to real addresses.


Filtering Spoofed Packets
One common way to defeat a less intelligent packet filter is to send fraudulent packets with a source address within the firewall. In theory, the firewall will pass those packets on into the network. If the intruder does everything exactly right, he could compute the likely response packets from packets he sends and compute the packets he would need to send in response to those, eventually building a sequence of packets to issue commands to a target machine without receiving any responses from it. This attack has been used before and will probably be used again.
    To prevent this, filter out packets from interfaces that they couldn't possibly be coming in legitimately. For one very typical example, suppose you have an OpenBSD machine with two network cards. One card has the IP address 192.168.1.1/24 and is connected to your internal network, while the other is connected to the Internet and has whatever IP address your ISP has assigned. Packets with a source address beginning with 192.168.1 should not be entering your network from the card attached to the Internet. Similarly, packets with a source address of 192.168.1.1 should not be entering the firewall from either network card. You could write rules for this, but this is such a common situation that PF includes a statement for it, "antispoof." Here we block spoofed packets on the fxp0 interface:
antispoof for fxp0
This statement automatically adds rules that block the Ethernet network directly attached to the interface from coming in on any other interface and that block the actual IP on that card from coming in over any network.
    It's a good idea to provide antispoof protection for every network interface on your firewall, including the loopback interfaces. Providing antispoof protection on loopback interfaces can cause problems if software on your system attempts to communicate with the system via a local IP address. You'll need to add rules to bypass the antispoof protection in this instance. For that reason, I also suggest logging spoofed packets by adding the "log" keyword.
antispoof log for fxp0
Now that you have a grip on the basics of packet filtering, let's consider some of PF's more interesting traffic-management abilities.


More on Packet Filtering

A common trick to make one IP address seem like many is Network Address Translation (NAT), a very popular feature among small network administrators.
    NAT requires the ability to arbitrarily redirect network connections, so we'll discuss that next.
    Another useful feature is the ability to manage bandwidth for particular tasks, which OpenBSD supports through queuing


Network Address Translation
Network Address Translation is like making soup out of a bone; it makes what you have go much further. If you only have a handful of IP addresses, or only one, you can use NAT to place a whole bunch of private IP addresses behind the public address and attach dozens, hundreds, or thousands of computers to the Internet with one "real" IP.
    NAT can cause problems — some VPN protocols do not work well with NAT, and it really confuses anyone who is trying to control machine access by IP address — but in general it solves more problems than it causes. If some of your machines cannot use NAT for whatever reason, you must give them real IP addresses (or possibly use binat, as discussed in "Bi-directional NAT").
All you need to know to configure NAT is
  1. the external interface
  2. the addresses you want translated, and 
  3. the address you want to translate them to
nat on external-interface from internal-network to any -> external-interface-or-real-IP-address
For example, if your external interface is fxp0, your internal IP addresses are 192.168.1.0/24, and your real IP address 10.0.5.4, you would write:
nat on fxp0 from 192.168.1.0/24 to any -> 10.0.5.4
PF rewrites outbound packets to give them a source IP of 10.0.5.4 and keep a state table of outgoing connections. When packets return it uses port numbers, IP addresses, and ISNs to match the returning packets to the real machine, rewrites the packets to give them the proper destination address, and send them back to the client.
    If you only have one IP address, and that address changes when you connect to the Internet, you may not want to hard-code the interface name in your rules.   That's fine. Instead of using the IP address as the translation address, use the interface name. PF will learn the IP on that interface and use it.
nat on fxp0 from 192.168.1.0/24 to any -> fxp0
If you're using DHCP to get your IP address, the IP may change while the system is still connected to the Internet. Just as you can put the interface name in parentheses to have a filter rule reevaluated when the IP address changes, use parentheses for NAT as well.
nat on fxp0 from 192.168.1.0/24 to any -> (fxp0)
You can also specify that NAT will take place only from certain networks or to certain networks. This is useful if you have several internal networks and don't want to translate traffic between them.
    As a NAT device many different devices sharing one IP address, and rewrites outbound and inbound connecting ports and IPs as necessary, it is nearly impossible to write packet-filtering rules that will properly redirect responses back to the correct client. As such, a NAT rule automatically implies stateful inspection to all connections made out through that rule.
You do not need to specify "keep state" on rules going out through the NAT, as that is applied automatically by the NAT. If you want to modulate state, you must specify that. But standard stateful inspection is enforced by the NAT rule.

NAT Rule Order
Unlike packet-filtering rules, NAT rules are processed on a first-fit basis. In most cases, you only have one NAT rule. If you're using binat rules (see "Bi-directional NAT") or if you have some special circumstances that require multiple NAT rules, keep this in mind. Put binat rules and exclusions before your default rule.


Private NAT Addresses
In theory, you could use any addresses behind your NAT device. If you just grab some IPs and use them, you won't be able to reach whoever has those IP addresses out in the real world. It's highly advisable to use some of the IP addresses reserved for private use. Those IP addresses are:
  • 10.0.0.0/8, (10.0.0.0-10.255.255.255)
  • 172.16.0.0/12 (172.16.0.0-172.31.255.255)
  • 192.168.0.0/16 (192.168.0.0-192.168.255.255)
You can subnet and rearrange these IP addresses in any way you wish, so long as you don't try to route them on the public Internet.


Exclusions from NAT
You may have a block of IPs that you do not want to allow to NAT, or a particular server that you do not want to allow on the Internet, or a protocol that chokes on NAT (such as IPSec). You can specifically exclude addresses or protocols from undergoing NAT by using the "no" keyword before the rule. Remember, in NAT the first matching rule is applied immediately! In this example, we first exclude protocol 51 (the AH protocol used in IPSec) from the NAT, then NAT everything else:

no nat on fxp0 proto 51 from 192.168.1.0/24 to any
nat on fxp0 from 192.168.1.0/24 to any -> 204.92.77.100


Bi-directional NAT
It's not uncommon to have several real IP addresses available, but need more than you have available.
Many ISPs will happily issue a block of 8 or 16 IP addresses to a company, and expect them to use NAT for most of their hosts and use the remainder of the address for DMZ hosts, nameservers, and so on.
You may have some hosts behind a firewall with special address-translation needs. Perhaps you need to allow Microsoft RPC (Allowing RPC to a host is almost saying "Here I am, hackers, take me now!" Get rid of the RPC box, tunnel RPC over HTTPS, or get a new job. With that box on your network, you'll be looking for a job soon anyway) through your firewall to a particular host or some other protocol that uses a wide range of almost-random port numbers. You want all of your protected hosts on the same network. Bi-directional NAT might solve your problem. Bi-directional NAT allows one system to monopolize a single real IP address in a one-to-one relationship. Incoming requests to that IP are always directed to the bi-directional NAT host, and outbound packets from that host are always translated to have the monopolized IP address.
    For example, suppose your firewall has two IP addresses bound to it — 10.0.5.4 and 10.0.5.5 — and you are using 192.168.1.0/24 for your machines. All of your office client machines and most of your servers use NAT through 10.0.5.4, but you reserve 10.0.5.5 for one special-purpose host at 192.168.1.66. Packets sent to 10.0.5.5 are directed to this 192.168.1.66, and packets from 192.168.1.66 are translated to have a source IP of 10.0.5.5. This allows you to make broad, sweeping port redirections and other rules that simply are not possible under many-to-one NAT. This is done via a "binat" rule.
binat on external-interface from private-ip to any -> real-ip

To continue our earlier example, if the external interface is fxp0, your private IP 192.168.1.66, and your reserved public IP 10.0.5.5, your binat rule would look like this:
binat on fxp0 from 192.168.1.66 to any -> 10.0.5.5
Put binat rules before other NAT rules. Using a binat rule is not necessary if you want to redirect particular connections to hosts within your NAT network; they're only helpful if you need broad swaths of ports open and you don't want to overlap other services provided by your main NAT IP. Use redirection to send the occasional port to servers inside your NAT.


Packet Filtering and NAT
You need a packet-filtering rule to allow traffic to a host behind the firewall. Use the actual, not translated, IP addresses for such rules. Here, we allow inbound connections to SSH to a server behind our NAT:
pass in on fxp0 proto tcp from any to 192.168.1.200 port 22

While this rule will allow the traffic, how will the firewall know to route new requests to this machine? That's where connection redirection comes in.


Managing PF

Now that you can configure the packet filter, let's take a look at how to actually manage the PF system. The pfctl program does most of the basic management of the packet filter and gives you a window into the entire system.
You can examine and edit the state and address tables separately. Finally, PF will allow you to authenticate users to the packet filter, giving Internet access only after a successful login.


pfctl(8)
All PF actions are controlled by the packet filter control program pfctl(8). You can manage your configuration as a whole, or each of the sub-functions independently. You can also kill individual connections or display statistics.
Stateful inspection has different management requirements, so we're going to consider those in "Managing State Tables"; here, we'll look at managing every other part of PF.
pfctl(8) has many different functions, which are documented in the manual page. New functions are being added all the time, and the syntax occasionally changes, so be sure to check the manual page if you have trouble.



General Commands
To disable PF and all its functions, run "pfctl -d".
Note that this will not turn off packet forwarding, so if you're running a PF firewall without NAT you have just exposed your network to the world. If you're using NAT, of course, disabling NAT turns off your private network's access to the Internet. 
To enable PF again, run "pfctl -e".
    Many pfctl(8) commands produce output when everything runs correctly. To make pfctl(8) run more quietly, only printing errors and warnings, add "-q" to your command line. Similarly, to produce more information add "-v" to the command. You can make pfctl(8) even more verbose by adding a second or third "-v."


Loading Rules
PF handles rule switching in such a way that there is no "window" where the system is not protected by packet filtering, unlike some older packet filters. The rules are loaded into system memory, then the switch between the old ruleset and the new ruleset is made in a single operation.
    If you want to load an entire rules file, use the "-f" flag and give the name of your rules file. Here, we load in the rules from /etc/pf.conf:
# pfctl -f /etc/pf.conf

If your configuration file has a syntax error, pfctl(8) will complain and won't load the rules. Your old rules will remain in effect.
# pfctl -f /etc/pf.conf
pf.conf: 1 6: syntax error
pfctl: Syntax error in file: pf rules not loaded

In this example, there's an 1 error on line 6 of the configuration. Personally, I like to know that my edited packet filter configuration is correct before the scheduled time to put it into production. (It's quite embarrassing to announce to the development team that "the new firewall configuration will be active during lunch," and then spend the whole time tracking down a misplaced comma or a parenthesis where you should have put in a curly brace.) You can parse your configuration file without loading it into the kernel with the "-n" flag.
# pfctl -n -f /etc/pf.conf


Loading a new ruleset will not remove any existing open connections. If my ruleset allows outbound SSH connections, and I remove that permission from the rules and reload them, existing SSH connections will remain. I can either specifically kill that connection or flush the state table as well.
If you want to reload your packet-handling rules without touching any of the other rules in your configuration file, you can use the "-R" option
# pfctl -R -f /etc/pf.conf
This leaves the rules for all options, NAT, and other PF features unchanged, and only reads the packet-filtering rules. You can change filter rules all you want, and the other functions will be unaffected. Of course, if you load rules that are incompatible with the configuration of those other functions, such as referring to nonexistent queues, you'll have other problems.

Similarly, you can load only the NAT rules with "-N," and only the option rules with "-O."
# pfctl -N -f /etc/pf.conf
# pfctl -O -f /etc/pf.conf


Flushing Rules
You can blow away all the existing configuration information with the "-F" (flush) flag. The "-F" flag takes an argument that indicates which part of the configuration should be flushed.
    To flush all the configuration you must add the "all" argument.
  1. Your NAT rules will go away, 
  2. existing state entries will disappear, and 
  3. your queue configuration will vanish.
If this is your firewall, your internal network's access to the Internet will fail and all existing connections will drop. This will pretty much hose your network, your connectivity, and your security. The flush command will identify each component as it flushes the information.
# pfctl -F all
rules cleared
nat cleared
altq cleared
states cleared
pf: statistics cleared


You can also flush only the packet-filtering rules, leaving all traffic free to go in any direction. Although any queues will still be in place, you won't have any rules to direct traffic to those queues. NAT will remain in place. Also, just flushing the rules will not interrupt any existing stateful connections.
# pfctl -F rules
rules cleared


The most common situation where I've had to flush the filtering rules is when troubleshooting a connection problem. It's neither pretty nor secure, but it's nice to have the option.
    Flushing queues leaves you without any bandwidth management. PF will not complain if packet-filtering rules direct packets at nonexistent queues, it will just process the packets as best it can. Flush your queues with the "queue" argument.
# pfctl -F queuealtq cleared

If you want to remove your NAT ability, you could flush your NAT rules. This will not interrupt existing connections in the state table, but will prevent further connections that require NAT.
# pfctl -F nat
nat cleared

Finally, PF also keeps statistics that are not part of any rule. You can reset all those counters to zero by running "-F info".


Viewing PF Information
You can check various information PF keeps with the "-s" (show) flag. You might also look at the configuration file to see what you loaded, but this might change if you're using authpf (see "Authenticated Access") or other anchor functions. The show function takes an argument, the part of the system whose rules you want to see.


Viewing Current Packet Filter Rules
To view the current PF rules, run "pfctl -s rules.". In 4.7 Openbsd release the default rules are:
# pfctl -s rules
pass all flags S/SA keep state
block drop in on ! lo0 proto tcp from any to any port 6000:6010

In a tuned system maybe are:
# pfctl -s rulesscrub in all fragment reassemble
pass in inet proto tcp from any to 192.168.1.4 port = www
pass in inet proto tcp from any to 192.168.1.4 port = https
pass in inet proto tcp from any to 192.168.1.5 port = www pass in inet proto tcp from any to 192.168.1.5 port = https
...


If you use verbose output ("-v") when viewing the rules, pfctl(8) will show how often a given rule is hit. This is extremely useful when debugging packet filter issues.
# pfctl -v -s rules
pass all flags S/SA keep state
  [ Evaluations: 103       Packets: 312       Bytes: 40165       States: 3     ]
  [ Inserted: uid 0 pid 2567 State Creations: 103   ]
block drop in on ! lo0 proto tcp from any to any port 6000:6010
  [ Evaluations: 103       Packets: 0         Bytes: 0           States: 0     ]
  [ Inserted: uid 0 pid 2567 State Creations: 0     ]

Each rule is displayed with four numbers:
  1. how many times this rule has been evaluated, 
  2. the number of packets that have matched the rule, 
  3. the number of bytes that the rule has processed through this rule, and 
  4. the number of states that are maintained via stateful inspection by this rule. 

# pfctl -v -s rules
...
block drop in on fxp1 inet all
[ Evaluations:   34984     Packets: 21      Bytes: 16330      States: 0 ]
...

Since the counters were last cleared, this generic "block all incoming traffic" rule has matched 21 out of 34,984 packets, containing 16330 bytes. To see what was in those packets, examine them via tcpdump (see "Logging").


Checking NAT Status
To view the active NAT translations pfctl(8) is used with the -s state option. This option will list all the current NAT sessions:
# pfctl -s state
all tcp 192.168.1.90:22 <- 192.168.1.10:34570  ESTABLISHED:ESTABLISHED
all tcp 192.168.1.90:22 <- 192.168.1.10:34662  ESTABLISHED:ESTABLISHED
all igmp 224.0.0.1 <- 192.168.1.1  NO_TRAFFIC:SINGLE



Viewing Current Queues
You can view the current queues with "-s queue". Verbosity has no effect upon viewing queues.
# pfctl -s queue
altq on fxp1 cbq bandwidth 100.00Mb tbrsize 12000
queue root_fxp1 bandwidth 100.00Mb priority 0 cbq(wrr root) {t1, local}
queue  t1 bandwidth 1.54Mb {ssh, http-in, http-out, mail, dns, ftp, misc}
queue   ssh bandwidth 1 77.20Kb cbq(borrow)
...

No matter how we specified bandwidth allowances, they are converted to a bits, kilobits, or megabits in this output. If you recall, in our examples we divided up the bandwidth of our T1 by percentages, but here our individual queue sizes are shown in 1 kilobits. PF does all of its internal operations in bits, of course.


Viewing Labels
Back when we set up labels, it was with the promise that you could view rule statistics per label. Do this with the "pfctl -s labels" command.
# pfctl -s label
ssh-out 1 38 2 126 3 24333
browsing 38 253 59599
browsing 26 0 0
  1. The first column is the number of times this rule has been evaluated
  2. The second is the number of packets that have been affected by this rule, and 
  3. the third is the number of bytes that have been affected by this rule.
One odd thing is this output is that we have two labels called "browsing!" What's more, they have different statistics. This can happen when you use braces within a rule and don't use a macro to label the differences between those rules. The "browsing" label was created by this rule.
pass out proto tcp from ($ExtIf) to any port {80, 443} label browsing

We are allowing two different ports, with two different statistics, and giving them the same label.


View State and Normalization Statistics
PF maintains statistics for state tables and packet normalization. You can view both of these statistics lists with "pfctl -s info". These statistics may be in terms of packets, or fragments, or entries in a table, or a number of times an action has been performed, as appropriate.
# pfctl -s info
Status: Enabled for 0 days 02:59:50              Debug: err

State Table                          Total             Rate
  current entries                        3               
  searches                             563            0.1/s
  inserts                              156            0.0/s
  removals                             153            0.0/s
Counters
  match                                156            0.0/s
  bad-offset                             0            0.0/s
  fragment                               0            0.0/s
  short                                  0            0.0/s
  normalize                              0            0.0/s
  memory                                 0            0.0/s
  bad-timestamp                          0            0.0/s
  congestion                             0            0.0/s
  ip-option                             86            0.0/s
  proto-cksum                            0            0.0/s
  state-mismatch                                     0.0/s
  state-insert                           0            0.0/s
  state-limit                            0            0.0/s
  src-limit                              0            0.0/s
  synproxy                               0            0.0/s

  1. The "enabled for" entry shows how long PF has been running for. This is usually the time since boot, although if someone has enabled and disabled the packet filter with pfctl(8) commands it will be reflected here as well.
  2. The first half of the output shows the statistics for the state table, the number of items that match a particular entry(Total column) and the number of items per second that PF processes(Rate column). 
  3. While state table entries per second is a largely meaningless value, we can see the number of current state table entries. (We discuss viewing the actual state table entries in "Managing Stateful Inspection.")
  4. The "searches" line indicates the number of times a packet has been compared to the state table. The state table is searched every time a packet passes an interface, so if you're forwarding packets between interfaces this is probably about twice the number of packets your system has passed. (Packets that originate on or terminate at your system are only searched once, of course.)
  5. The "inserts" entry shows how many times states have been created, and 
  6. "removals" shows how many have been destroyed. The difference between the two should equal the number of current states. As your system stays up the number of insertions and removals should get very close to each other — even if you have a few hundred entries in your state table, that's fairly insignificant compared to millions of states that will be created and destroyed over the state table's lifetime.
The "Counters" section tells how many times a packet has matched a particular rule.
  1. The "match" entry says how many packets find a last matching rule in PF. This should be a total of the number of packets that have passed through the system, unless you're relying on PF's implicit "pass all" to manage traffic. 
  2. The "bad-offset" entry shows how many packets were received with a bad offset, 
  3. the "fragment" column shows how many fragments have been received, and 
  4. the "short" entry tells how many unusually short packets were received.
  5. If a packet cannot be coherently reassembled, PF will drop the pieces. "Normalize" shows how many packets have been dropped after scrubbing. 
  6. Similarly, the "memory" entry shows how many packets have been dropped because PF doesn't have enough memory to hold on to the packet fragments before reassembling them. If you start to lose packets due to memory shortages, you need to increase the memory you have allocated to PF (see "PF Memory Limits").

Viewing Everything at Once
As this command also works for many different functions of PF, to view the entire configuration add the "all" argument.
# pfctl -s all (or pfctl -sa)

This provides a complete listing of the configuration for NAT, packet-filtering rules, queues, and statistics.


Clearing PF Statistics
Finally, PF keeps statistics on just about everything it does. You can clear these statistics with the "-z" (zero) flag.
# pfctl -z
pf: rule counters cleared



Managing Tables
Tables are handled slightly differently than other sorts of rules. The whole point of a table is to maintain a list of network numbers, so pfctl(8)'s table support is concerned with creating, editing, and removing table information. Whenever you work with a table in pfctl(8), you must specify the name of the table you are editing with the "-t" option. For example, if you're working with the "intruders" table, each pfctl(8) command should start with:
# pfctl -t intruders


If you just run this command like this, pfctl(8) won't actually do anything. You need to tell pfctl(8) what you want it to do with the table, using the "-T" flag before it will actually consult with PF and do something. The "-T" flag takes an argument, the action you want it to take. For example, to display the contents of the table, use the "show" argument.
# pfctl -t intruders -T show
209.69.178.26

The "intruders" table has one entry, 209.69.178.18. Add an entry to the table with the "add" argument.
# pfctl -t intruders -T add 10.0.0.9
1/1 addresses added

You can add entire networks to the table by specifying a netmask. Also, you can add multiple network numbers at a time.
# pfctl -t intruders -T add 88.88.0.0/16
1/1 addresses added
# pfctl -t intruders -T add 88.99.0.0/16 99.99.8.0/24
2/2 addresses added

If you add entries to a non-existent table, the table is automatically created. (You might use this with anchor rules, or with authpf(8).)
    Similarly, you can delete entries from a table with the "delete" argument. You can use any number of network numbers in a single delete statement.
# pfctl -t intruders -T delete 99.99.8.0/24
1/1 addresses deleted

To remove all the entries from a table, use the "flush" command.
# pfctl -t intruders -T flush
4 addresses deleted

The table is now empty. But perhaps deleting entries from a table is not enough, and you want to delete the table itself. Use the "kill" argument for this.
# pfctl -t intruders -T kill
1 table deleted

The next time you add an entry to the "intruders" table, PF will automatically recreate it.
    If you're using text files to maintain your tables, you might want to reload the text file into your tables without restarting PF. This might be common with spam blacklists, for example; when you download the latest list of spam-spewing hosts, you want to get this into your system as soon as possible. You also want to remove old entries from the table at the same time. Use the "replace" argument for this, and also use the "-f" flag to specify the file name.
# pfctl -t spamhosts -T replace -f /etc/spamhosts
99 addresses added
4 addresses deleted

You can also check if a particular IP address is inside a table, using the "test" argument. Here, we check to see if a particular IP address is in our running spamhosts table.
# pfctl -t spamhosts -T test 209.69.178.26
0/1 addresses match

It's not common to have to create and remove tables on the fly, but it's quite possible that you will want different types of tables for different circumstances. You can load table definitions from a file with the "load" argument. Use pfctl(8)'s -f argument to specify a file name.
# pfctl -T load -f /etc/pf.conf


Table Statistics
PF keeps statistics on each IP address in a table. If you add the -v flag to the "show" argument, you'll get a list of each entry in the table and the number of packets that have passed through it.
# pfctl -t rfc1918 -T show -v
10.0.0.0/8
        Cleared:      1 Sun May 11 09:55:20 2003
        2 In/Block:   [ Packets: 0       Bytes: 0 ]
        3 In/Pass:    [ Packets: 0       Bytes: 0 ]
        4 Out/Block:  [ Packets: 0       Bytes: 0 ]
        5 Out/Pass:   [ Packets: 0       Bytes: 0 ]
172.16.0.0/12
...

We can see how much data has been (2) blocked coming into the system, how much data has been (3) allowed into the system, how much data has been (4) blocked trying to go out, and how much data has been (5) allowed out. This particular system hasn't seen any traffic from the 10.0.0.0/8 network since 1 May 11 at 9:55 AM, so the counters are all zero.
    To reset the counters, use the "zero" argument
# pfctl -t rfc1918 -T zero
1 table/stats cleared


PF keeps all sorts of statistics on every part of the system, and can report on almost all of its actions. If you're interested, see pf.conf(5).


Managing State Tables
While PF uses rules to determine which connections may use stateful inspection, the actual state table is generated from the connections created by machines connecting through the firewall. Managing the state table therefore has two aspects:
  1. administering the rules for stateful inspection, and 
  2. viewing and editing the state table itself.
The rules for stateful inspection are actually managed as part of NAT and packet filtering, so we're left with viewing the state table itself.


Viewing the State Table
You can see the current contents of the state table with "pfctl -s state." Here's the output from a mostly idle network.
# pfctl -s state 
tcp 192.168.1.200:51276 ->209.69.178.18: 51276 ->209.69.178.22:22  7 ESTABLISHED: 8 ESTABLISHED

There is only one entry in this state table, for a 1 TCP connection. The host 2 192.168.1.200 has opened port 3 51276 for an outbound connection. At the firewall, this IP undergoes translation to the IP 4 209.69.178.18, port 5 51276. This IP is connecting to the IP address 6 209.69.178.22 on port 7 22. The connection from the first machine to the firewall is in the state 8 ESTABLISHED, and the connection from the firewall to the eventual destination is also in the state 9 ESTABLISHED. While it's possible to have connection states be different on either side of the firewall, this should only appear very briefly. If you have a large enough state table, you'll catch a few of them from time to time.
If you're looking for particular information in your state table, you might want to add the "-r" (reverse lookup) option. This will make the state table perform DNS lookups on every IP address in the table and display host names where possible. This can take much longer than displaying the state table without host names, but is more readable when you're just seeing who is talking to what. "209.81.7.23" doesn't mean much to most of us, but "www.sex.com" might hint that someone's having a little bit too much fun at work.



Removing States
If you just want to clear every entry in your state table, you can just flush the entire state table with "-F state." This will completely erase the state table.
# pfctl -F statestates cleared


Most connections will actually recover from having the state table cleared, if the PF rules allow the connection to continue. For example, if we clear the state table shown earlier and wait a moment, the SSH session will continue, and PF will create a new state entry.
We mentioned earlier that changing your packet-filtering rules to disallow a certain connection did not cut off connections that were already in place. If you clear the state table after loading your updated packet-filtering rules, the existing connections will try to recover. 
If PF does not allow the connection, the connection cannot recover, and both ends of the connection will time out.
If you are using state modulation instead of stateful inspection, connections cannot recover. State modulation changes the initial sequence number of the connection by a random amount, and flushing the table removes that random number. Flushing the state table will destroy all existing connections using state modulation and disconnect everyone accessing the network over that rule.


Killing States
At times, timing out the connection simply isn't good enough. You may want to kill a particular existing connection. You can do this with the "-k" (kill) option. This takes a single argument, the host name or IP address of the traffic source. For example, to delete all state entries for traffic originating from 192.168.1.200, you would use this command:
# pfctl -k 192.168.1.200
killed 1 states from 1 sources and 0 destinations


You could specify a second -k and a second host name or IP address, allowing you to narrow down the state you want to eliminate to those between two particular hosts.
# pfctl -k 192.168.1.200 -k 209.69.178.22
killed 1 states from 1 sources and 1 destinations


These connections can probably recover, however, so the best thing to do is put in a packet-filtering rule that will disallow undesired connections.



Resources

No comments:

Post a Comment