> For the complete documentation index, see [llms.txt](https://book.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://book.bsdcn.org/ask/flat/chapter-31-firewalls/di-31.4-jie-packet-filter-pf.md).

# 31.4 Packet Filter (PF)

> **Warning**
>
> The content in this section has not been tested in practice and has only been verified theoretically. Do not use it directly in a production environment! It is recommended to verify it in a test environment before deploying to production.

PF (Packet Filter) is a firewall originating from OpenBSD that features queue management and many other capabilities.

It should be noted that the FreeBSD version of PF has diverged significantly from the upstream OpenBSD version over the years. Not all features behave the same on FreeBSD as they do on OpenBSD, and vice versa.

This section focuses on using PF on FreeBSD, demonstrates how to enable PF, and provides several examples of creating rule sets on a FreeBSD system.

## Enabling PF

The `pf` module is distributed with the base system. Before enabling PF, the kernel module must be loaded first:

```sh
# kldload pf
```

> **Note**
>
> If the PF kernel module is not loaded, running `pfctl` will display `pfctl: /dev/pf: No such file or directory`.

By default, PF reads its configuration rules from **/etc/pf.conf** and modifies, drops, or passes packets based on the rules or definitions specified in that file. Copy the sample file as the default configuration rule set, otherwise pf cannot start:

```sh
# cp /usr/share/examples/pf/pf.conf /etc/
```

> **Note**
>
> If the configuration file is not copied, the following message will be displayed:
>
> ```sh
> /etc/rc.d/pf: WARNING: /etc/pf.conf is not readable.
> ```

Additional options can also be passed when starting PF, which are detailed in pfctl(8). Add this entry to **/etc/rc.conf** or modify its content, specifying the desired flags between quotes (`""`):

```sh
pf_flags="" # Additional flags for pfctl at startup
```

If PF cannot find its rule set configuration file, it will not start. By default, FreeBSD does not ship with a rule set, nor does it have an **/etc/pf.conf** file. Sample rule sets can be found in **/usr/share/examples/pf/**. If a custom rule set is saved in a different location, add a line to **/etc/rc.conf** specifying the full path to the file:

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

PF logging support is provided by pflog(4). To enable logging support, execute the command:

```sh
# service pflog enable
```

You can also add the following lines to change the default location of the log file or specify additional flags to pass when starting pflog(4):

```ini
pflog_enable="YES"              # Enable pflogd logging daemon
pflog_logfile="/var/log/pflog"  # Where pflogd should store the log file
pflog_flags=""                  # Additional flags for pflogd at startup
```

Finally, if there is a LAN behind the firewall and packets need to be forwarded to computers on the LAN, or if NAT is needed, enable the following option in **/etc/rc.conf**:

```ini
gateway_enable="YES" # Enable as LAN gateway
```

After saving the necessary edits, PF can be started with the following commands (with logging support enabled):

```sh
# service pf enable # Set pf to start automatically at system boot
# service pf start # Start the pf service
# service pflog start # Enable logging support
```

To control PF, use `pfctl`. The following summarizes some common options for this command. For a description of all available options, refer to pfctl(8):

**pfctl Common Options Table**

| Command                               | Description                                                                 |
| ------------------------------------- | --------------------------------------------------------------------------- |
| `pfctl -e`                            | Enable PF.                                                                  |
| `pfctl -d`                            | Disable PF.                                                                 |
| `pfctl -F all -f /etc/pf.conf`        | Flush all NAT, filter, state, and table rules, and reload **/etc/pf.conf**. |
| `pfctl -s [ rules \| nat \| states ]` | Report filter rules, NAT rules, or the state table.                         |
| `pfctl -vnf /etc/pf.conf`             | Check **/etc/pf.conf** for errors without loading the rule set.             |

To monitor traffic passing through the PF firewall, consider installing the package or port **sysutils/pftop**. After installation, pftop can be run to view a real-time snapshot of traffic in a format similar to top(1).

## PF Rule Sets

This section demonstrates how to create custom rule sets. Starting with the simplest rule set, concepts are gradually built up, and several examples show the practical application of PF's many features.

The simplest rule set is for a single computer that does not run any services and only needs to access one network (possibly the Internet). To create this minimal rule set, edit **/etc/pf.conf** so its contents are as follows:

```sh
block in all
pass out all keep state
```

The first rule `block in all` denies all incoming traffic by default.

The second rule `pass out all keep state` allows connections initiated by this system to pass while retaining state information for those connections. This state information allows return traffic for the connection to pass, and this feature should only be used on trusted computers. In PF, `pass` rules implicitly create state by default, so `keep state` can be omitted.

The rule set can be loaded with the following command:

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

In addition to maintaining state, PF also provides **lists** and **macros** that can be used when creating rules. Macros can contain lists and must be defined before use. For example, insert the following lines at the top of the rule set:

```sh
# TCP services allowed for outbound access
tcp_services = "{ ssh, submission, domain, www, https }"
# UDP services allowed for outbound access
udp_services = "{ domain }"
```

PF recognizes port names as well as port numbers, as long as those names are listed in **/etc/services**. This example creates two macros: the first is a list of five TCP port names (`ssh, submission, domain, www, https`), and the second is a single UDP port name (`domain`). Once defined, the macros can be used in rules. In this example, all traffic is blocked except for connections initiated by this system to the five specified TCP services and one specified UDP service:

```sh
# Macro definitions: TCP and UDP services allowed for outbound access
tcp_services = "{ ssh, submission, domain, www, https }"
udp_services = "{ domain }"
# Block all traffic by default
block all
# Allow outbound TCP connections, keep state
pass out proto tcp to any port $tcp_services keep state
# Allow outbound UDP connections, keep state
pass proto udp to any port $udp_services keep state
```

Although UDP is generally considered a stateless protocol, PF can track certain state information. For example, when a UDP request is sent to query a domain name server, PF monitors the return traffic to allow it through.

Whenever the rule set is edited, the new rules must be loaded to take effect:

```sh
# pfctl -f /etc/pf.conf
```

If there are no syntax errors, `pfctl` will not output any messages when loading rules. You can also test before attempting to load:

```sh
# pfctl -nf /etc/pf.conf
```

Specifying the `-n` option causes the rules to be interpreted but not loaded, making it easy to correct errors. At any time, the last valid rule set loaded takes effect until PF is disabled or a new rule set is loaded.

> **Tip**
>
> Adding the `-v` option during `pfctl` rule set validation or loading will display the fully parsed rules, showing exactly how they will be loaded. This is very useful when debugging rules.

### Simple Gateway and NAT

This section demonstrates how to configure a FreeBSD system running PF to serve as a gateway for at least one other computer. A gateway requires at least two network interfaces, each connected to a different network. In this example, **em0** is connected to the Internet and **em1** is connected to the LAN.

First, enable gateway mode so the system can forward network traffic received on one interface to another. This sysctl setting will forward IPv4 packets:

```sh
# sysctl net.inet.ip.forwarding=1
```

To forward IPv6 traffic, use:

```sh
# sysctl net.inet6.ip6.forwarding=1
```

To enable these settings at system boot, use sysrc(8) to add them to **/etc/rc.conf**:

```sh
# sysrc gateway_enable=yes
# sysrc ipv6_gateway_enable=yes
```

Use `ifconfig` to verify that both interfaces are up and running.

Next, create PF rules to allow the gateway to forward traffic. The following rule allows stateful traffic from internal network hosts through the gateway, but the `to` keyword does not guarantee that packets will travel intact from source to destination:

```sh
pass in on em1 from em1:network to em0:network port $ports keep state
```

This rule only allows traffic to enter the gateway through the internal interface. If packets need to continue onward, a matching rule is also required:

```sh
pass out on em0 from em1:network to em0:network port $ports keep state
```

While these two rules work, such specific rules are typically unnecessary. For busy network administrators, a more readable rule set is safer. The rest of this section demonstrates how to make rules as simple as possible for easy reading. For example, these two rules can be replaced by a single rule:

```sh
pass from em1:network to any port $ports keep state
```

The `interface:network` notation can be replaced with macros to make the rule set more readable. For example, a `$localnet` macro can be defined to represent the network directly connected to the internal interface (`$em1:network`). Alternatively, the definition of `$localnet` can be changed to *IP address/subnet mask* notation to represent the network, such as `192.168.100.1/24` representing a private address subnet.

If needed, `$localnet` can even be defined as a list of networks. Regardless of the specific requirements, a reasonable `$localnet` definition can be used in a typical pass rule, as shown below:

```sh
pass from $localnet to any port $ports keep state
```

The following example rule set allows all traffic initiated by machines on the internal network. It first defines two macros representing the gateway's external and internal 3COM interfaces.

> **Note**
>
> For dial-up users, the external interface will use **tun0**. For ADSL connections, especially those using PPPoE (PPP over Ethernet), the correct external interface is **tun0**, not the physical Ethernet interface.

```sh
ext_if = "em0"    # External interface (PPPoE dial-up connections should use tun0)
int_if = "em1"    # Internal interface
localnet = $int_if:network
# The external interface IP address may be dynamically assigned, so use ($ext_if) to follow interface changes
block all
# Perform NAT on outbound traffic from the internal network
match out on $ext_if from $localnet to any nat-to ($ext_if)
# Allow connections initiated by the loopback interface and internal network hosts
pass from { lo0, $localnet } to any keep state
```

This rule set uses the `nat-to` option to translate non-routable addresses from the internal network to the IP address assigned to the external interface, i.e., Network Address Translation (NAT). The parenthesized portion `($ext_if)` in the `nat-to` option accounts for the case where the external interface's IP address is dynamically assigned, ensuring that network traffic continues to function properly even if the external IP address changes.

It should be noted that this rule set may allow more traffic than actually needed. A more reasonable approach is to create this macro:

```sh
# List of TCP services that clients are allowed to access outbound
client_out = "{ ssh, domain, http, \
    https, 8000, 8080 }"
```

For use in the main pass rule:

```sh
# Allow internal network clients to access common Internet TCP services
pass inet proto tcp from $localnet to any port $client_out \
    flags S/SA keep state
```

Some other pass rules may also be needed. This rule enables SSH on the external interface:

```sh
# Allow SSH connections on the external interface
pass in inet proto tcp to $ext_if port ssh
```

This macro definition and rule allow internal clients to use DNS and NTP:

```sh
# UDP services allowed for internal clients (DNS, NTP)
udp_services = "{ domain, ntp }"
# quick means match and pass immediately, without matching subsequent rules
pass quick inet proto { tcp, udp } to any port $udp_services keep state
```

Note the `quick` keyword in this rule. Since the rule set contains multiple rules, understanding the relationship between rules is important. Rules are evaluated from top to bottom in the order they appear in the rule set. For each packet or connection evaluated by PF, the *last matching rule* is the one that takes effect. However, when a packet matches a rule containing the `quick` keyword, rule processing stops and the packet is handled according to that rule. This feature is very useful when exceptions to general rules are needed.

### Creating an FTP Proxy

> **Note**
>
> The FTP protocol has gradually been replaced by secure alternatives such as SFTP and SCP. FTP usage scenarios in modern networks have significantly decreased, and the following FTP proxy configuration is only applicable to legacy environments that still need to support FTP. This section is retained because ftp-proxy serves as a good example of demonstrating PF's anchor mechanism.

Due to the nature of the FTP protocol, configuring effective FTP rules can be complex. FTP predates firewalls by decades and has inherent security issues in its design. The most common problems with using FTP include:

* Passwords are transmitted in plaintext.
* The protocol requires at least two TCP connections (control connection and data connection), and they use different ports.
* After a session is established, data is transmitted through randomly selected ports.

Even without considering potential security vulnerabilities in client or server software, these issues alone pose security challenges. More secure file transfer alternatives include sftp(1) or scp(1), both of which provide authentication and transmit data over encrypted connections.

When FTP must be used, PF can redirect FTP traffic to a small proxy program ftp-proxy(8), which is included in the FreeBSD base system. The proxy's role is to dynamically insert and remove rules in the rule set through a set of anchors to properly handle FTP traffic.

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

```sh
ftp_proxy_enable="YES"
```

Also, define a macro for the proxy's listening address in **/etc/pf.conf** (ftp-proxy listens on **127.0.0.1** by default):

```sh
proxy = "127.0.0.1"
```

Then start the proxy by running the following command:

```sh
# service ftp-proxy start
```

For a basic configuration, three elements need to be added to **/etc/pf.conf**. First, the anchor where the proxy inserts rules generated for FTP sessions:

```sh
# FTP proxy rule anchor: the proxy dynamically inserts session rules here
anchor "ftp-proxy/*"
```

Second, a pass rule that redirects FTP traffic to the proxy:

```sh
# Redirect FTP control traffic from the internal network to the local ftp-proxy (port 8021)
pass in on $int_if proto tcp from any to any port 21 rdr-to 127.0.0.1 port 8021
```

Finally, allow the redirected traffic to pass:

```sh
# Allow outbound FTP traffic resolved by ftp-proxy
pass out proto tcp from $proxy to any port ftp
```

Here, `$proxy` expands to the address the proxy daemon is bound to.

Save **/etc/pf.conf**, load the new rules, and verify FTP connectivity from a client:

```sh
# pfctl -f /etc/pf.conf
```

This example covers a basic configuration where a client on the local network needs to connect to a remote FTP server. This basic configuration should work for most combinations of FTP clients and servers. As shown in ftp-proxy(8), the proxy's behavior can be adjusted in various ways by adding options to the `ftp_proxy_flags=` line. Some clients or servers may have specific differences that require corresponding adjustments in the configuration, or the proxy may need to be integrated in a specific way, such as assigning FTP traffic to a particular queue.

If you need to protect an FTP server and configure PF to work with ftp-proxy(8), you can run `ftp-proxy` in reverse mode on a separate port using the `-R` option with a dedicated redirection pass rule.

### Managing ICMP

Many tools used for debugging or troubleshooting TCP/IP networks rely on the Internet Control Message Protocol (ICMP), which was originally designed for debugging purposes.

The ICMP protocol sends and receives **Control Messages** between hosts and gateways, primarily providing feedback to the sender about anomalies or difficulties encountered en route to the destination host. Routers use ICMP to negotiate packet sizes and other transmission parameters, a process commonly known as **Path MTU Discovery**.

From a firewall perspective, some ICMP control messages are vulnerable to known attack vectors. Additionally, allowing all diagnostic traffic to pass unrestricted simplifies debugging but also makes it easier for others to obtain network information. Therefore, the following rule may not be optimal:

```sh
pass inet proto icmp from any to any
```

One solution is to allow all ICMP traffic from the local network while blocking probe requests from outside the network:

```sh
# Allow all ICMP traffic initiated from the internal network
pass inet proto icmp from $localnet to any keep state
# Allow ICMP traffic from outside to the external interface (external ping to this host)
pass inet proto icmp from any to $ext_if keep state
```

PF also provides other options that demonstrate its flexibility. For example, instead of allowing all ICMP messages, specific message types used by ping(8) and traceroute(8) can be specified. First, define a macro representing these message types:

```sh
# Allowed ICMP message types: echo request (ping)
icmp_types = "echoreq"
```

Then, the rule using this macro:

```sh
# Only allow specified types of ICMP packets to pass
pass inet proto icmp all icmp-type $icmp_types keep state
```

If other types of ICMP packets are needed, `icmp_types` can be expanded to include a list of those packet types. Refer to the icmp(4) and icmp6(4) manual pages for the ICMP message types supported by PF. You can also refer to [IANA's ICMP Parameters document](https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml) for an explanation of each message type.

Unix `traceroute` uses UDP by default, so an additional rule is needed to allow the use of Unix `traceroute`:

```sh
# Allow UDP port range used by default in Unix traceroute(8)
pass out on $ext_if inet proto udp from any to any port 33433 >< 33626 keep state
```

Unix `traceroute` uses UDP by default but can also be configured to use other protocols. If the `-I` parameter is specified, it uses ICMP echo request messages.

#### Path MTU Discovery

The Internet Protocol is designed to be device-independent, and one consequence of this device independence is that the optimal packet size for a given connection cannot always be reliably predicted. The primary constraint on packet size is the **Maximum Transmission Unit** (MTU), which sets the maximum packet size limit for an interface. You can enter `ifconfig` to view the MTU values of the system's network interfaces.

TCP/IP uses a process called Path MTU Discovery to determine the correct packet size for a connection. This process sends packets of varying sizes with the "don't fragment" flag set, expecting to receive an ICMP return packet of type "3, code 4" when the limit is reached. Type 3 indicates "destination unreachable" and code 4 indicates "fragmentation needed but don't-fragment flag set." If Path MTU Discovery needs to be allowed to support connections to links with different MTUs, the `destination unreachable` type must be added to the `icmp_types` macro:

```sh
icmp_types = "{ echoreq, unreach }"
```

Since the `pass` rule already uses this macro, no modification to the rule is needed to support the new ICMP type:

```sh
# Use a macro to uniformly control allowed ICMP types; modifying the macro requires no rule changes
pass inet proto icmp all icmp-type $icmp_types keep state
```

PF allows filtering based on all ICMP type and code variants. The list of possible types and codes is described in icmp(4) and icmp6(4).

### Using Tables

Certain types of data are important for filtering and redirection at times, but their definitions are too long to include in the rule set file. PF supports the use of tables, which are definable lists that can be manipulated without reloading the entire rule set and support fast lookups. Table names are always enclosed in `< >`, as shown below:

```sh
# Define clients table, allow 192.168.2.0/24 network but exclude 192.168.2.5
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, but the address `192.168.2.5` is excluded using the `!` operator. Tables can also be loaded from a file, where each entry is on a separate line, as in this example **/etc/clients**:

```sh
# /etc/clients example: each entry on its own line
192.168.2.0/24
!192.168.2.5
```

To reference the file, define the table like this:

```sh
# Persistently load table from file; table contents are not cleared when the rule set is reloaded
table <clients> persist file "/etc/clients"
```

Once the table is defined, it can be referenced in rules:

```sh
# Allow clients in the table to access common outbound TCP services
pass inet proto tcp from <clients> to any port $client_out flags S/SA keep state
```

The contents of a table can be dynamically manipulated using `pfctl`. The following example adds another network to the table:

```sh
# Dynamically add 192.168.1.0/16 network to the clients table
# pfctl -t clients -T add 192.168.1.0/16
```

Note that any changes made in this way take effect immediately, which is suitable for testing, but will not persist after a power loss or reboot. To make changes permanent, modify the table definition in the rule set or edit the file referenced by the table. A cron(8) job can be used to periodically save the table contents to disk, with a command example such as `pfctl -t clients -T show >/etc/clients`. Alternatively, **/etc/clients** can be updated with the in-memory table contents using the following command:

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

### Protecting SSH with Overload Tables

Users running SSH on an external interface may see entries like the following in their authentication logs:

```sh
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 indicates a brute-force attack, where someone or some program is trying to obtain the correct username and password to break into the system.

If legitimate users need to be allowed external SSH access, changing the default port used by SSH can provide some protection. However, PF offers a more elegant solution. By limiting the operations of connecting hosts, violators are moved to an address table where addresses are denied partial or complete access. Existing connections from the offending machine can even be dropped.

To configure this functionality, create the table in the table section of the rule set:

```sh
# Persistent table for brute-force source addresses
table <bruteforce> persist
```

Then, add rules earlier in the rule set to block brute-force access and allow legitimate access:

```sh
# First block all traffic from addresses in the brute-force table (quick means immediate effect)
block quick from <bruteforce>
# Allow internal network access to common TCP services, and set connection rate limits; source addresses exceeding limits are added to the <bruteforce> table and their existing connections are terminated
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 parenthesized portion defines the limits, and the numbers should be adjusted according to local requirements. It can be interpreted as follows:

`max-src-conn` is the number of simultaneous connections allowed from a single host.

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

`overload <bruteforce>` means that any host exceeding these limits will have its address added to the `bruteforce` table. The rule set blocks all traffic from addresses in the `bruteforce` table.

Finally, `flush global` means that when a host reaches the limit, all (`global`) connections from that host will be terminated (`flush`).

> **Note**
>
> These rules do *not* stop slow brute-forcers, as described at <http://home.nuug.no/~peter/hailmary2013/>.

This example rule set is primarily illustrative. For example, if you want to allow a large number of connections but want to be stricter regarding SSH, you can supplement with a rule like the following earlier in the rule set:

```sh
# Apply stricter connection rate limits for the SSH port
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)
```

> **Note**
>
> The overload mechanism is a general technique that applies to SSH but is not limited to it, and it is not always necessary to completely block all violators.
>
> For example, overload rules can be used to protect mail services or web services, and overload tables can be used in rules to assign violators to a queue with minimum bandwidth or redirect them to a specific web page.

Over time, the table will be populated by overload rules and will gradually increase in size, consuming more memory. Sometimes blocked IP addresses are dynamically assigned addresses that may have been reassigned to a host with a legitimate reason to communicate with the local network host.

For such situations, `pfctl` provides the ability to expire table entries. For example, the following command will remove entries from the `<bruteforce>` table that have not been referenced within `86400` seconds:

```sh
# pfctl -t bruteforce -T expire 86400
```

### Network Hygiene

This section explains how to use `block-policy`, `scrub`, and `antispoof` to make the rule set behave more reasonably.

`block-policy` is an option that can be set in the `options` section of the rule set, which is located before redirection and filter rules. This option determines whether PF sends feedback when a rule blocks a host. The option has two possible values: `drop` drops blocked packets without sending feedback; `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:

```sh
# Return status code when blocked (e.g., Connection refused) instead of silently dropping
set block-policy return
```

In PF, `scrub` is a keyword that enables normalization of network packets. This process drops TCP packets with invalid flag combinations. Enabling `scrub` provides some protection against certain attacks based on improper handling of packet fragmentation.

It is recommended to use `scrub` as an option of a `match` rule rather than as a standalone statement. The simplest form is suitable for most configurations:

```sh
# Normalize all inbound traffic: clear DF flag and randomize IP ID
match in all scrub (no-df random-id)
```

For fragment reassembly, it is recommended to use `set reassemble yes` in the `options` section of the rule set:

```sh
# Enable fragment reassembly, also reassemble fragments with the DF flag set (and clear the flag)
set reassemble yes no-df
```

The `no-df` option means that fragments with the "don't fragment" flag set will also be reassembled, and the flag will be cleared in the reassembled packet.

Some services such as NFS require specific fragment handling options. For more information, refer to [Firewalling with OpenBSD's PF packet filter](https://home.nuug.no/~peter/pf/en/scrub.html).

The following example sets fragment reassembly, clears the DF flag, and sets the maximum segment size to 1440 bytes through a `match` rule:

```sh
# Enable fragment reassembly, clear DF flag, and limit TCP maximum segment size to 1440
set reassemble yes no-df
match in all scrub (no-df max-mss 1440)
```

The `antispoof` mechanism prevents spoofed or forged IP address attacks, primarily by blocking packets that logically should not appear on a specific interface and direction.

These rules filter spoofed traffic from the outside world as well as any spoofed packets originating from the local network:

```sh
# Anti-spoofing: block logically impossible source/destination addresses on the external interface
antispoof for $ext_if
# Anti-spoofing: block logically impossible source/destination addresses on the internal interface
antispoof for $int_if
```

### Handling Non-Routable Addresses

Even when a gateway is properly configured to handle network address translation, sometimes it is necessary to compensate for others' misconfigurations. A common misconfiguration is allowing traffic from non-routable addresses to access the Internet. Since traffic from non-routable addresses can be involved in various denial-of-service (DoS) attack techniques, it is recommended to explicitly block traffic from non-routable addresses entering the network's external interface.

In this example, a macro containing non-routable addresses is first defined, then used in a block rule. Traffic to and from these addresses is silently dropped at the gateway's external interface.

```sh
# Non-routable reserved addresses ("martian addresses")
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, 224.0.0.0/3 }"

# Block inbound traffic from these addresses on the external interface
block drop in quick on $ext_if from $martians to any
# Block outbound traffic to these addresses on the external interface
block drop out quick on $ext_if from any to $martians
```

## Other PF Features

### SYN Cookie Protection

PF provides the `set syncookies` option for defending against SYN flood attacks. When SYN cookies are active, PF responds to each inbound TCP SYN with a syncookie SYNACK without allocating any resources. Upon receiving the client's ACK, PF evaluates the rule set and creates state if allowed.

```sh
# Never send syncookies (default behavior)
set syncookies never
# Always send syncookies (no state allocated for any SYN)
set syncookies always
# Adaptive mode: enable when state table is 25% full, disable when it drops to 12%
set syncookies adaptive (start 25%, end 12%)
```

Adaptive mode enables syncookie mode when half-open TCP connections occupy the specified percentage of the state table, and disables it when the percentage falls back to the lower threshold.

### Packet Rate Limiting

The `max-pkt-rate` option can limit packet rates on a per-rule basis. When the specified rate is exceeded, the rule stops matching:

```sh
# Limit inbound ICMP rate to a maximum of 100 packets per 10 seconds; when exceeded, this rule stops matching
pass in proto icmp max-pkt-rate 100/10
```

This rule allows a maximum of 100 ICMP packets per 10 seconds; when the rate is exceeded, the rule stops matching.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://book.bsdcn.org/ask/flat/chapter-31-firewalls/di-31.4-jie-packet-filter-pf.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
