30.3. PF

Revised and updated by John Ferrell.

Since FreeBSD 5.3, a ported version of OpenBSD's PF firewall has been included as an integrated part of the base system. PF is a complete, full-featured firewall that has optional support for ALTQ (Alternate Queuing), which provides Quality of Service (QoS).

The OpenBSD Project maintains the definitive reference for PF in the PF FAQ. Peter Hansteen maintains a thorough PF tutorial at http://home.nuug.no/~peter/pf/.

Warning:

When reading the PF FAQ, keep in mind that FreeBSD uses the same version of PF as OpenBSD 4.5.

The FreeBSD packet filter mailing list is a good place to ask questions about configuring and running the PF firewall. Check the mailing list archives before asking a question as it may have already been answered.

More information about porting PF to FreeBSD can be found at http://pf4freebsd.love2party.net/.

This section of the Handbook focuses on PF as it pertains to FreeBSD. It demonstrates how to enable PF and ALTQ. It then provides several examples for creating rulesets on a FreeBSD system.

30.3.1. Enabling PF

In order to use PF, its kernel module must be first loaded. This section describes the entries that can be added to /etc/rc.conf in order to enable PF.

Start by adding the following line to /etc/rc.conf:

pf_enable="YES"

Additional options, described in pfctl(8), can be passed to PF when it is started. Add this entry to /etc/rc.conf and specify any required flags between the two quotes (""):

pf_flags=""                     # additional flags for pfctl startup

PF will not start if it cannot find its ruleset configuration file. The default ruleset is already created and is named /etc/pf.conf. If a custom ruleset has been saved somewhere else, add a line to /etc/rc.conf which specifies the full path to the file:

pf_rules="/path/to/pf.conf"

Logging support for PF is provided by pflog(4). To enable logging support, add this line to /etc/rc.conf:

pflog_enable="YES"

The following lines can also be added in order to change the default location of the log file or to specify any additional flags to pass to pflog(4) when it is started:

pflog_logfile="/var/log/pflog"  # where pflogd should store the logfile
pflog_flags=""                  # additional flags for pflogd startup

Finally, if there is a LAN behind the firewall and packets need to be forwarded for the computers on the LAN, or NAT is required, add the following option:

gateway_enable="YES"            # Enable as LAN gateway

After saving the needed edits, PF can be started with logging support by typing:

# service pf start
# service pflog start

By default, PF reads its configuration rules from /etc/pf.conf and modifies, drops, or passes packets according to the rules or definitions specified in this file. The FreeBSD installation includes several sample files located in /usr/share/examples/pf/. Refer to the PF FAQ for complete coverage of PF rulesets.

To control PF, use pfctl. Table 30.1, “Useful pfctl Options” summarizes some useful options to this command. Refer to pfctl(8) for a description of all available options:

Table 30.1. Useful pfctl Options
CommandPurpose
pfctl -eEnable PF.
pfctl -dDisable PF.
pfctl -F all -f /etc/pf.confFlush all NAT, filter, state, and table rules and reload /etc/pf.conf.
pfctl -s [ rules | nat | states ]Report on the filter rules, NAT rules, or state table.
pfctl -vnf /etc/pf.confCheck /etc/pf.conf for errors, but do not load ruleset.

Tip:

security/sudo is useful for running commands like pfctl that require elevated privileges. It can be installed from the Ports Collection.

To keep an eye on the traffic that passes through the PF firewall, consider installing the sysutils/pftop package or port. Once installed, pftop can be run to view a running snapshot of traffic in a format which is similar to top(1).

30.3.2. Enabling ALTQ

On FreeBSD, ALTQ can be used with PF to provide Quality of Service (QOS). Once ALTQ is enabled, queues can be defined in the ruleset which determine the processing priority of outbound packets.

Before enabling ALTQ, refer to altq(4) to determine if the drivers for the network cards installed on the system support it.

ALTQ is not available as a loadable kernel module. If the system's interfaces support ALTQ, create a custom kernel using the instructions in Chapter 8, Configuring the FreeBSD Kernel. The following kernel options are available. The first is needed to enable ALTQ. At least one of the other options is necessary to specify the queueing scheduler algorithm:

options         ALTQ
options         ALTQ_CBQ        # Class Based Queuing (CBQ)
options         ALTQ_RED        # Random Early Detection (RED)
options         ALTQ_RIO        # RED In/Out
options         ALTQ_HFSC       # Hierarchical Packet Scheduler (HFSC)
options         ALTQ_PRIQ       # Priority Queuing (PRIQ)

The following scheduler algorithms are available:

CBQ

Class Based Queuing (CBQ) is used to divide a connection's bandwidth into different classes or queues to prioritize traffic based on filter rules.

RED

Random Early Detection (RED) is used to avoid network congestion by measuring the length of the queue and comparing it to the minimum and maximum thresholds for the queue. When the queue is over the maximum, all new packets are randomly dropped.

RIO

In Random Early Detection In and Out (RIO) mode, RED maintains multiple average queue lengths and multiple threshold values, one for each QOS level.

HFSC

Hierarchical Fair Service Curve Packet Scheduler (HFSC) is described in http://www-2.cs.cmu.edu/~hzhang/HFSC/main.html.

PRIQ

Priority Queuing (PRIQ) always passes traffic that is in a higher queue first.

More information about the scheduling algorithms and example rulesets are available at the OpenBSD's web archive.

30.3.3. PF Rulesets

Contributed by Peter N. M. Hansteen.

This section demonstrates how to create a customized ruleset. It starts with the simplest of rulesets and builds upon its concepts using several examples to demonstrate real-world usage of PF's many features.

The simplest possible ruleset is for a single machine that does not run any services and which needs access to one network, which may be the Internet. To create this minimal ruleset, edit /etc/pf.conf so it looks like this:

block in all
pass out all keep state

The first rule denies all incoming traffic by default. The second rule allows connections created by this system to pass out, while retaining state information on those connections. This state information allows return traffic for those connections to pass back and should only be used on machines that can be trusted. The ruleset can be loaded with:

# pfctl -e ; pfctl -f /etc/pf.conf

In addition to keeping state, PF provides lists and macros which can be defined for use when creating rules. Macros can include lists and need to be defined before use. As an example, insert these lines at the very top of the ruleset:

tcp_services = "{ ssh, smtp, domain, www, pop3, auth, pop3s }"
udp_services = "{ domain }"

PF understands port names as well as port numbers, as long as the names are listed in /etc/services. This example creates two macros. The first is a list of seven TCP port names and the second is one UDP port name. Once defined, macros can be used in rules. In this example, all traffic is blocked except for the connections initiated by this system for the seven specified TCP services and the one specified UDP service:

tcp_services = "{ ssh, smtp, domain, www, pop3, auth, pop3s }"
udp_services = "{ domain }"
block all
pass out proto tcp to any port $tcp_services keep state
pass proto udp to any port $udp_services keep state

Even though UDP is considered to be a stateless protocol, PF is able to track some state information. For example, when a UDP request is passed which asks a name server about a domain name, PF will watch for the response in order to pass it back.

Whenever an edit is made to a ruleset, the new rules must be loaded so they can be used:

# pfctl -f /etc/pf.conf

If there are no syntax errors, pfctl will not output any messages during the rule load. Rules can also be tested before attempting to load them:

# pfctl -nf /etc/pf.conf

Including -n causes the rules to be interpreted only, but not loaded. This provides an opportunity to correct any errors. At all times, the last valid ruleset loaded will be enforced until either PF is disabled or a new ruleset is loaded.

Tip:

Adding -v to a pfctl ruleset verify or load will display the fully parsed rules exactly the way they will be loaded. This is extremely useful when debugging rules.

30.3.3.1. A Simple Gateway with NAT

This section demonstrates how to configure a FreeBSD system running PF to act as a gateway for at least one other machine. The gateway needs at least two network interfaces, each connected to a separate network. In this example, xl1 is connected to the Internet and xl0 is connected to the internal network.

First, enable the gateway in order to let the machine forward the network traffic it receives on one interface to another interface. This sysctl setting will forward IPv4 packets:

# sysctl net.inet.ip.forwarding=1

To forward IPv6 traffic, use:

# sysctl net.inet6.ip6.forwarding=1

To enable these settings at system boot, add the following to /etc/rc.conf:

gateway_enable="YES"		#for ipv4
ipv6_gateway_enable="YES"	#for ipv6

Verify with ifconfig that both of the interfaces are up and running.

Next, create the PF rules to allow the gateway to pass traffic. While the following rule allows stateful traffic to pass from the Internet to hosts on the network, the to keyword does not guarantee passage all the way from source to destination:

pass in on xl1 from xl1:network to xl0:network port $ports keep state

That rule only lets the traffic pass in to the gateway on the internal interface. To let the packets go further, a matching rule is needed:

pass out on xl0 from xl1:network to xl0:network port $ports keep state

While these two rules will work, rules this specific are rarely needed. For a busy network admin, a readable ruleset is a safer ruleset. The remainder of this section demonstrates how to keep the rules as simple as possible for readability. For example, those two rules could be replaced with one rule:

pass from xl1:network to any port $ports keep state

The interface:network notation can be replaced with a macro to make the ruleset even more readable. For example, a $localnet macro could be defined as the network directly attached to the internal interface ($xl1:network). Alternatively, the definition of $localnet could be changed to an IP address/netmask notation to denote a network, such as 192.168.100.1/24 for a subnet of private addresses.

If required, $localnet could even be defined as a list of networks. Whatever the specific needs, a sensible $localnet definition could be used in a typical pass rule as follows:

pass from $localnet to any port $ports keep state

The following sample ruleset allows all traffic initiated by machines on the internal network. It first defines two macros to represent the external and internal 3COM interfaces of the gateway.

Note:

For dialup users, the external interface will use tun0. For an ADSL connection, specifically those using PPP over Ethernet (PPPoE), the correct external interface is tun0, not the physical Ethernet interface.

ext_if = "xl0"	# macro for external interface - use tun0 for PPPoE
int_if = "xl1"	# macro for internal interface
localnet = $int_if:network
# ext_if IP address could be dynamic, hence ($ext_if)
nat on $ext_if from $localnet to any -> ($ext_if)
block all
pass from { lo0, $localnet } to any keep state

This ruleset introduces the nat rule which is used to handle the network address translation from the non-routable addresses inside the internal network to the IP address assigned to the external interface. The parentheses surrounding the last part of the nat rule ($ext_if) is included when the IP address of the external interface is dynamically assigned. It ensures that network traffic runs without serious interruptions even if the external IP address changes.

Note that this ruleset probably allows more traffic to pass out of the network than is needed. One reasonable setup could create this macro:

client_out = "{ ftp-data, ftp, ssh, domain, pop3, auth, nntp, http, \
    https, cvspserver, 2628, 5999, 8000, 8080 }"

to use in the main pass rule:

pass inet proto tcp from $localnet to any port $client_out \
    flags S/SA keep state

A few other pass rules may be needed. This one enables SSH on the external interface:

pass in inet proto tcp to $ext_if port ssh

This macro definition and rule allows DNS and NTP for internal clients:

udp_services = "{ domain, ntp }"
pass quick inet proto { tcp, udp } to any port $udp_services keep state

Note the quick keyword in this rule. Since the ruleset consists of several rules, it is important to understand the relationships between the rules in a ruleset. Rules are evaluated from top to bottom, in the sequence they are written. For each packet or connection evaluated by PF, the last matching rule in the ruleset is the one which is applied. However, when a packet matches a rule which contains the quick keyword, the rule processing stops and the packet is treated according to that rule. This is very useful when an exception to the general rules is needed.

30.3.3.2. Creating an FTP Proxy

Configuring working FTP rules can be problematic due to the nature of the FTP protocol. FTP pre-dates firewalls by several decades and is insecure in its design. The most common points against using FTP include:

  • Passwords are transferred in the clear.

  • The protocol demands the use of at least two TCP connections (control and data) on separate ports.

  • When a session is established, data is communicated using randomly selected ports.

All of these points present security challenges, even before considering any potential security weaknesses in client or server software. More secure alternatives for file transfer exist, such as sftp(1) or scp(1), which both feature authentication and data transfer over encrypted connections..

For those situations when FTP is required, PF provides redirection of FTP traffic to a small proxy program called ftp-proxy(8), which is included in the base system of FreeBSD. The role of the proxy is to dynamically insert and delete rules in the ruleset, using a set of anchors, in order to correctly handle FTP traffic.

To enable the FTP proxy, add this line to /etc/rc.conf:

ftpproxy_enable="YES"

Then start the proxy by running service ftp-proxy start.

For a basic configuration, three elements need to be added to /etc/pf.conf. First, the anchors which the proxy will use to insert the rules it generates for the FTP sessions:

nat-anchor "ftp-proxy/*"
rdr-anchor "ftp-proxy/*"

Second, a pass rule is needed to allow FTP traffic in to the proxy.

Third, redirection and NAT rules need to be defined before the filtering rules. Insert this rdr rule immediately after the nat rule:

rdr pass on $int_if proto tcp from any to any port ftp -> 127.0.0.1 port 8021

Finally, allow the redirected traffic to pass:

pass out proto tcp from $proxy to any port ftp

where $proxy expands to the address the proxy daemon is bound to.

Save /etc/pf.conf, load the new rules, and verify from a client that FTP connections are working:

# pfctl -f /etc/pf.conf

This example covers a basic setup where the clients in the local network need to contact FTP servers elsewhere. This basic configuration should work well with most combinations of FTP clients and servers. As shown in ftp-proxy(8), the proxy's behavior can be changed in various ways by adding options to the ftpproxy_flags= line. Some clients or servers may have specific quirks that must be compensated for in the configuration, or there may be a need to integrate the proxy in specific ways such as assigning FTP traffic to a specific queue.

For ways to run an FTP server protected by PF and ftp-proxy(8), configure a separate ftp-proxy in reverse mode, using -R, on a separate port with its own redirecting pass rule.

30.3.3.3. Managing ICMP

Many of the tools used for debugging or troubleshooting a TCP/IP network rely on the Internet Control Message Protocol (ICMP), which was designed specifically with debugging in mind.

The ICMP protocol sends and receives control messages between hosts and gateways, mainly to provide feedback to a sender about any unusual or difficult conditions enroute to the target host. Routers use ICMP to negotiate packet sizes and other transmission parameters in a process often referred to as path MTU discovery.

From a firewall perspective, some ICMP control messages are vulnerable to known attack vectors. Also, letting all diagnostic traffic pass unconditionally makes debugging easier, but it also makes it easier for others to extract information about the network. For these reasons, the following rule may not be optimal:

pass inet proto icmp from any to any

One solution is to let all ICMP traffic from the local network through while stopping all probes from outside the network:

pass inet proto icmp from $localnet to any keep state
pass inet proto icmp from any to $ext_if keep state

Additional options are available which demonstrate some of PF's flexibility. For example, rather than allowing all ICMP messages, one can specify the messages used by ping(8) and traceroute(8). Start by defining a macro for that type of message:

icmp_types = "echoreq"

and a rule which uses the macro:

pass inet proto icmp all icmp-type $icmp_types keep state

If other types of ICMP packets are needed, expand icmp_types to a list of those packet types. Type more /usr/src/sbin/pfctl/pfctl_parser.c to see the list of ICMP message types supported by PF. Refer to http://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml for an explanation of each message type.

Since Unix traceroute uses UDP by default, another rule is needed to allow Unix traceroute:

# allow out the default range for traceroute(8):
pass out on $ext_if inet proto udp from any to any port 33433 >< 33626 keep state

Since TRACERT.EXE on Microsoft Windows systems uses ICMP echo request messages, only the first rule is needed to allow network traces from those systems. Unix traceroute can be instructed to use other protocols as well, and will use ICMP echo request messages if -I is used. Check the traceroute(8) man page for details.

30.3.3.3.1. Path MTU Discovery

Internet protocols are designed to be device independent, and one consequence of device independence is that the optimal packet size for a given connection cannot always be predicted reliably. The main constraint on packet size is the Maximum Transmission Unit (MTU) which sets the upper limit on the packet size for an interface. Type ifconfig to view the MTUs for a system's network interfaces.

TCP/IP uses a process known as path MTU discovery to determine the right packet size for a connection. This process sends packets of varying sizes with the Do not fragment flag set, expecting an ICMP return packet of type 3, code 4 when the upper limit has been reached. Type 3 means destination unreachable, and code 4 is short for fragmentation needed, but the do-not-fragment flag is set. To allow path MTU discovery in order to support connections to other MTUs, add the destination unreachable type to the icmp_types macro:

icmp_types = "{ echoreq, unreach }"

Since the pass rule already uses that macro, it does not need to be modified in order to support the new ICMP type:

pass inet proto icmp all icmp-type $icmp_types keep state

PF allows filtering on all variations of ICMP types and codes. The list of possible types and codes are documented in icmp(4) and icmp6(4).

30.3.3.4. Using Tables

Some types of data are relevant to filtering and redirection at a given time, but their definition is too long to be included in the ruleset file. PF supports the use of tables, which are defined lists that can be manipulated without needing to reload the entire ruleset, and which can provide fast lookups. Table names are always enclosed within < >, like this:

table <clients> { 192.168.2.0/24, !192.168.2.5 }

In this example, the 192.168.2.0/24 network is part of the table, except for the address 192.168.2.5, which is excluded using the ! operator. It is also possible to load tables from files where each item is on a separate line, as seen in this example /etc/clients:

192.168.2.0/24
!192.168.2.5

To refer to the file, define the table like this:

table <clients> persist file "/etc/clients"

Once the table is defined, it can be referenced by a rule:

pass inet proto tcp from <clients> to any port $client_out flags S/SA keep state

A table's contents can be manipulated live, using pfctl. This example adds another network to the table:

# pfctl -t clients -T add 192.168.1.0/16

Note that any changes made this way will take affect now, making them ideal for testing, but will not survive a power failure or reboot. To make the changes permanent, modify the definition of the table in the ruleset or edit the file that the table refers to. One can maintain the on-disk copy of the table using a cron(8) job which dumps the table's contents to disk at regular intervals, using a command such as pfctl -t clients -T show >/etc/clients. Alternatively, /etc/clients can be updated with the in-memory table contents:

# pfctl -t clients -T replace -f /etc/clients

30.3.3.5. Using Overload Tables to Protect SSH

Those who run SSH on an external interface have probably seen something like this in the authentication logs:

Sep 26 03:12:34 skapet sshd[25771]: Failed password for root from 200.72.41.31 port 40992 ssh2
Sep 26 03:12:34 skapet sshd[5279]: Failed password for root from 200.72.41.31 port 40992 ssh2
Sep 26 03:12:35 skapet sshd[5279]: Received disconnect from 200.72.41.31: 11: Bye Bye
Sep 26 03:12:44 skapet sshd[29635]: Invalid user admin from 200.72.41.31
Sep 26 03:12:44 skapet sshd[24703]: input_userauth_request: invalid user admin
Sep 26 03:12:44 skapet sshd[24703]: Failed password for invalid user admin from 200.72.41.31 port 41484 ssh2

This is indicative of a brute force attack where somebody or some program is trying to discover the user name and password which will let them into the system.

If external SSH access is needed for legitimate users, changing the default port used by SSH can offer some protection. However, PF provides a more elegant solution. Pass rules can contain limits on what connecting hosts can do and violators can be banished to a table of addresses which are denied some or all access. It is even possible to drop all existing connections from machines which overreach the limits.

To configure this, create this table in the tables section of the ruleset:

table <bruteforce> persist

Then, somewhere early in the ruleset, add rules to block brute access while allowing legitimate access:

block quick from <bruteforce>
pass inet proto tcp from any to $localnet port $tcp_services \
    flags S/SA keep state \
    (max-src-conn 100, max-src-conn-rate 15/5, \
    overload <bruteforce> flush global)

The part in parentheses defines the limits and the numbers should be changed to meet local requirements. It can be read as follows:

max-src-conn is the number of simultaneous connections allowed from one host.

max-src-conn-rate is the rate of new connections allowed from any single host (15) per number of seconds (5).

overload <bruteforce> means that any host which exceeds these limits gets its address added to the bruteforce table. The ruleset blocks all traffic from addresses in the bruteforce table.

Finally, flush global says that when a host reaches the limit, that all (global) of that host's connections will be terminated (flush).

Note:

These rules will not block slow bruteforcers, as described in http://home.nuug.no/~peter/hailmary2013/.

This example ruleset is intended mainly as an illustration. For example, if a generous number of connections in general are wanted, but the desire is to be more restrictive when it comes to ssh, supplement the rule above with something like the one below, early on in the rule set:

pass quick proto { tcp, udp } from any to any port ssh \
    flags S/SA keep state \
    (max-src-conn 15, max-src-conn-rate 5/3, \
    overload <bruteforce> flush global)

It May Not be Necessary to Block All Overloaders:

It is worth noting that the overload mechanism is a general technique which does not apply exclusively to SSH, and it is not always optimal to entirely block all traffic from offenders.

For example, an overload rule could be used to protect a mail service or a web service, and the overload table could be used in a rule to assign offenders to a queue with a minimal bandwidth allocation or to redirect to a specific web page.

Over time, tables will be filled by overload rules and their size will grow incrementally, taking up more memory. Sometimes an IP address that is blocked is a dynamically assigned one, which has since been assigned to a host who has a legitimate reason to communicate with hosts in the local network.

For situations like these, pfctl provides the ability to expire table entries. For example, this command will remove <bruteforce> table entries which have not been referenced for 86400 seconds:

# pfctl -t bruteforce -T expire 86400

Similar functionality is provided by security/expiretable, which removes table entries which have not been accessed for a specified period of time.

Once installed, expiretable can be run to remove <bruteforce> table entries older than a specified age. This example removes all entries older than 24 hours:

/usr/local/sbin/expiretable -v -d -t 24h bruteforce

30.3.3.6. Protecting Against SPAM

Not to be confused with the spamd daemon which comes bundled with spamassassin, mail/spamd can be configured with PF to provide an outer defense against SPAM. This spamd hooks into the PF configuration using a set of redirections.

Spammers tend to send a large number of messages, and SPAM is mainly sent from a few spammer friendly networks and a large number of hijacked machines, both of which are reported to blacklists fairly quickly.

When an SMTP connection from an address in a blacklist is received, spamd presents its banner and immediately switches to a mode where it answers SMTP traffic one byte at a time. This technique, which is intended to waste as much time as possible on the spammer's end, is called tarpitting. The specific implementation which uses one byte SMTP replies is often referred to as stuttering.

This example demonstrates the basic procedure for setting up spamd with automatically updated blacklists. Refer to the man pages which are installed with mail/spamd for more information.

Procedure 30.1. Configuring spamd
  1. Install the mail/spamd package or port. In order to use spamd's greylisting features, fdescfs(5) must be mounted at /dev/fd. Add the following line to /etc/fstab:

     fdescfs /dev/fd fdescfs rw 0 0

    Then, mount the filesystem:

    # mount fdescfs
  2. Next, edit the PF ruleset to include:

    table <spamd> persist
    table <spamd-white> persist
    rdr pass on $ext_if inet proto tcp from <spamd> to \
        { $ext_if, $localnet } port smtp -> 127.0.0.1 port 8025
    rdr pass on $ext_if inet proto tcp from !<spamd-white> to \
        { $ext_if, $localnet } port smtp -> 127.0.0.1 port 8025

    The two tables <spamd> and <spamd-white> are essential. SMTP traffic from an address listed in <spamd> but not in <spamd-white> is redirected to the spamd daemon listening at port 8025.

  3. The next step is to configure spamd in /usr/local/etc/spamd.conf and to add some rc.conf parameters.

    The installation of mail/spamd includes a sample configuration file (/usr/local/etc/spamd.conf.sample) and a man page for spamd.conf. Refer to these for additional configuration options beyond those shown in this example.

    One of the first lines in the configuration file that does not begin with a # comment sign contains the block which defines the all list, which specifies the lists to use:

    all:\
        :traplist:whitelist:

    This entry adds the desired blacklists, separated by colons (:). To use a whitelist to subtract addresses from a blacklist, add the name of the whitelist immediately after the name of that blacklist. For example: :blacklist:whitelist:.

    This is followed by the specified blacklist's definition:

    traplist:\
        :black:\
        :msg="SPAM. Your address %A has sent spam within the last 24 hours":\
        :method=http:\
        :file=www.openbsd.org/spamd/traplist.gz

    where the first line is the name of the blacklist and the second line specifies the list type. The msg field contains the message to display to blacklisted senders during the SMTP dialogue. The method field specifies how spamd-setup fetches the list data; supported methods are http, ftp, from a file in a mounted file system, and via exec of an external program. Finally, the file field specifies the name of the file spamd expects to receive.

    The definition of the specified whitelist is similar, but omits the msg field since a message is not needed:

    whitelist:\
        :white:\
        :method=file:\
        :file=/var/mail/whitelist.txt

    Choose Data Sources with Care:

    Using all the blacklists in the sample spamd.conf will blacklist large blocks of the Internet. Administrators need to edit the file to create an optimal configuration which uses applicable data sources and, when necessary, uses custom lists.

    Next, add this entry to /etc/rc.conf. Additional flags are described in the man page specified by the comment:

    spamd_flags="-v" # use "" and see spamd-setup(8) for flags

    When finished, reload the ruleset, start spamd by typing service obspamd start, and complete the configuration using spamd-setup. Finally, create a cron(8) job which calls spamd-setup to update the tables at reasonable intervals.

On a typical gateway in front of a mail server, hosts will soon start getting trapped within a few seconds to several minutes.

PF also supports greylisting, which temporarily rejects messages from unknown hosts with 45n codes. Messages from greylisted hosts which try again within a reasonable time are let through. Traffic from senders which are set up to behave within the limits set by RFC 1123 and RFC 2821 are immediately let through.

More information about greylisting as a technique can be found at the greylisting.org web site. The most amazing thing about greylisting, apart from its simplicity, is that it still works. Spammers and malware writers have been very slow to adapt in order to bypass this technique.

The basic procedure for configuring greylisting is as follows:

Procedure 30.2. Configuring Greylisting
  1. Make sure that fdescfs(5) is mounted as described in Step 1 of the previous Procedure.

  2. To run spamd in greylisting mode, add this line to /etc/rc.conf:

    spamd_grey="YES"  # use spamd greylisting if YES

    Refer to the spamd man page for descriptions of additional related parameters.

  3. To complete the greylisting setup:

    # service obspamd restart
    # service obspamlogd start

Behind the scenes, the spamdb database tool and the spamlogd whitelist updater perform essential functions for the greylisting feature. spamdb is the administrator's main interface to managing the black, grey, and white lists via the contents of the /var/db/spamdb database.

30.3.3.7. Network Hygiene

This section describes how block-policy, scrub, and antispoof can be used to make the ruleset behave sanely.

The block-policy is an option which can be set in the options part of the ruleset, which precedes the redirection and filtering rules. This option determines which feedback, if any, PF sends to hosts that are blocked by a rule. The option has two possible values: drop drops blocked packets with no feedback, and return returns a status code such as Connection refused.

If not set, the default policy is drop. To change the block-policy, specify the desired value:

set block-policy return

In PF, scrub is a keyword which enables network packet normalization. This process reassembles fragmented packets and drops TCP packets that have invalid flag combinations. Enabling scrub provides a measure of protection against certain kinds of attacks based on incorrect handling of packet fragments. A number of options are available, but the simplest form is suitable for most configurations:

scrub in all

Some services, such as NFS, require specific fragment handling options. Refer to https://home.nuug.no/~peter/pf/en/scrub.html for more information.

This example reassembles fragments, clears the do not fragment bit, and sets the maximum segment size to 1440 bytes:

scrub in all fragment reassemble no-df max-mss 1440

The antispoof mechanism protects against activity from spoofed or forged IP addresses, mainly by blocking packets appearing on interfaces and in directions which are logically not possible.

These rules weed out spoofed traffic coming in from the rest of the world as well as any spoofed packets which originate in the local network:

antispoof for $ext_if
antispoof for $int_if

30.3.3.8. Handling Non-Routable Addresses

Even with a properly configured gateway to handle network address translation, one may have to compensate for other people's misconfigurations. A common misconfiguration is to let traffic with non-routable addresses out to the Internet. Since traffic from non-routeable addresses can play a part in several DoS attack techniques, consider explicitly blocking traffic from non-routeable addresses from entering the network through the external interface.

In this example, a macro containing non-routable addresses is defined, then used in blocking rules. Traffic to and from these addresses is quietly dropped on the gateway's external interface.

martians = "{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, \
	      10.0.0.0/8, 169.254.0.0/16, 192.0.2.0/24, \
	      0.0.0.0/8, 240.0.0.0/4 }"

block drop in quick on $ext_if from $martians to any
block drop out quick on $ext_if from any to $martians

All FreeBSD documents are available for download at https://download.freebsd.org/ftp/doc/

Questions that are not answered by the documentation may be sent to <freebsd-questions@FreeBSD.org>.
Send questions about this document to <freebsd-doc@FreeBSD.org>.