How to Use `pfctl` to Manage Firewall Rules on FreeBSD

This article explains how to use the pfctl command to manage firewall rules on FreeBSD, including enabling, disabling, and modifying rules.

FreeBSD includes the powerful Packet Filter (PF) firewall, originally developed for OpenBSD and ported to FreeBSD for robust network security. At the heart of PF administration is the pfctl utility, which provides comprehensive control over firewall rules, network address translation, traffic shaping, and more. This article explores how to effectively use pfctl to manage firewall rules on FreeBSD systems, providing administrators with the knowledge to implement and maintain a secure network environment.

Understanding PF and pfctl

PF is a stateful packet filter that examines individual packets in the context of traffic flows. The pfctl utility is the command-line interface for managing PF, allowing administrators to:

  • Load firewall rule sets
  • Enable/disable the firewall
  • View statistics and rule status
  • Test configurations
  • Manage tables of addresses dynamically
  • Control NAT (Network Address Translation) and redirections
  • Implement traffic shaping

Before diving into specific commands, it’s essential to understand that PF uses a configuration file (typically /etc/pf.conf) containing rules that determine how the firewall processes network traffic.

Enabling PF on FreeBSD

To use PF on a FreeBSD system, you first need to enable it:

  1. Add the following lines to /etc/rc.conf:
pf_enable="YES"
pf_rules="/etc/pf.conf"  # Default configuration file location
pflog_enable="YES"       # Enable logging
pflog_logfile="/var/log/pflog" # Log file location
  1. Start the PF services:
service pf start
service pflog start

Basic pfctl Syntax and Options

The general syntax for pfctl is:

pfctl [options] [-f config_file]

Common options include:

OptionDescription
-eEnable PF
-dDisable PF
-f fileLoad rules from specified file
-nParse rules but don’t load (test syntax)
-vVerbose output
-qQuiet (suppress output)
-s [modifier]Show specific information (rules, states, etc.)
-t table -T commandManipulate tables
-F [modifier]Flush (clear) various aspects of PF (rules, states, etc.)

Creating a Basic PF Configuration

Before using pfctl, you need a PF configuration file. Here’s a simple /etc/pf.conf example:

# Define macros for interfaces and networks
ext_if = "em0"  # External interface
int_if = "em1"  # Internal interface
internal_net = "192.168.1.0/24"

# Default deny policy
set block-policy drop
set skip on lo0

# Normalize incoming traffic
scrub in all

# NAT internal network to external interface
nat on $ext_if from $internal_net to any -> ($ext_if)

# Allow specific traffic and block everything else
block all
pass from { self $internal_net } to any keep state
pass inet proto tcp from any to ($ext_if) port 22 flags S/SA keep state
pass inet proto tcp from any to ($ext_if) port 80 flags S/SA keep state
pass inet proto tcp from any to ($ext_if) port 443 flags S/SA keep state

This basic configuration:

  1. Defines macros for interfaces and networks
  2. Sets a default deny policy
  3. Normalizes incoming traffic
  4. Sets up NAT for the internal network
  5. Blocks all traffic by default
  6. Allows outbound traffic from the internal network
  7. Allows inbound SSH, HTTP, and HTTPS traffic

Loading and Testing Rules with pfctl

Before applying rules to a production system, you should test them:

pfctl -nf /etc/pf.conf

This parses the rule set and reports errors without actually loading the rules. If no errors are reported, load the rules:

pfctl -f /etc/pf.conf

To load rules and enable PF in one command:

pfctl -ef /etc/pf.conf

Viewing PF Status and Information

pfctl provides several options to view PF’s current status and configuration:

General Status

pfctl -s info

This displays general information about PF’s operation, including interface statistics, timeouts, and memory usage.

Viewing Current Rules

pfctl -s rules

Add -v for more detailed output:

pfctl -s rules -v

Viewing NAT Rules

pfctl -s nat

Viewing State Table

pfctl -s state

For a specific state count:

pfctl -s info | grep states

Viewing Statistics

pfctl -s all

This comprehensive command shows all available information about PF’s current state.

Flushing Rules and States

Sometimes you need to clear existing rules or states:

Flush All Rules

pfctl -F rules

Flush NAT Rules

pfctl -F nat

Flush State Table

pfctl -F state

Flush Everything

pfctl -F all

Be cautious with this command as it can disrupt active connections.

Working with Tables

PF tables provide a mechanism for grouping addresses or networks, making rule sets more efficient and easier to manage. Tables can be manipulated in real-time using pfctl:

Defining Tables in pf.conf

table <blocklist> persist file "/etc/pf.blocklist"
table <admins> { 192.168.1.10, 192.168.1.11 }
block in quick from <blocklist> to any
pass in quick from <admins> to any

Viewing Tables

pfctl -s Tables

Viewing Table Contents

pfctl -t blocklist -T show

Adding Addresses to a Table

pfctl -t blocklist -T add 10.0.0.5

Add multiple addresses:

pfctl -t blocklist -T add 10.0.0.5 10.0.0.6 10.0.0.7

Add a network:

pfctl -t blocklist -T add 10.0.0.0/24

Removing Addresses from a Table

pfctl -t blocklist -T delete 10.0.0.5

Testing if an Address is in a Table

pfctl -t blocklist -T test 10.0.0.5

Replacing Table Contents

pfctl -t blocklist -T replace 10.0.0.0/24 10.0.1.0/24

Flushing Table Contents

pfctl -t blocklist -T flush

Loading Table from a File

pfctl -t blocklist -T load -f /path/to/blocklist

Debugging and Testing Rules

PF includes tools to help debug and test your rule set:

Verbose Logging

Enable verbose logging in /etc/pf.conf:

set debug verbose

Reload rules and check logs:

pfctl -f /etc/pf.conf
tail -f /var/log/pflog

Packet Matching Tests

Test which rule a particular packet would match:

pfctl -sr -v | grep -A2 -B2 "port 80"

Using tcpdump with pflog

The pflog interface captures packets logged by PF rules. You can use tcpdump to analyze this traffic:

tcpdump -n -e -ttt -i pflog0

To filter for specific traffic:

tcpdump -n -e -ttt -i pflog0 port 80

Managing PF During System Updates

When performing system updates or maintenance, you may need to temporarily disable or modify PF:

Temporarily Disable PF

pfctl -d

Re-enable PF

pfctl -e

Temporarily Allow All Traffic

Add a rule to the top of your ruleset:

# First line in /etc/pf.conf during maintenance
pass quick all

Then reload:

pfctl -f /etc/pf.conf

Remember to remove this rule and reload when maintenance is complete.

Advanced pfctl Usage Scenarios

Real-time Rule Modification

While editing the full /etc/pf.conf file is the conventional approach, you can make temporary rule changes:

echo "block in from 10.0.0.100" | pfctl -a temp -f -

This adds a rule to a separate “temp” ruleset. View these rules with:

pfctl -a temp -s rules

Remove the temporary ruleset:

pfctl -a temp -F rules

Traffic Shaping with ALTQ

PF supports traffic shaping through ALTQ (Alternate Queueing). To manage ALTQ queues:

pfctl -s queue

State Table Optimization

For servers with many connections, you may need to increase state table limits in /etc/sysctl.conf:

net.pf.states_max=200000

Then reload with:

sysctl net.pf.states_max=200000

Monitor state table usage:

pfctl -s info | grep states

Advanced Rule Testing

For complex rule sets, you can test multiple aspects:

Test Rule Compilation Performance

time pfctl -nf /etc/pf.conf

Measure Rule Load Time

time pfctl -f /etc/pf.conf

Securing Your PF Configuration

Rule Set Security Best Practices

  1. Default Deny Policy: Start with blocking all traffic, then explicitly allow what’s needed.
block all
pass [specific rules here]
  1. Stateful Filtering: Use keep state to track legitimate connections.
pass in on $ext_if proto tcp to ($ext_if) port 80 flags S/SA keep state
  1. Protect Against Spoofing: Block packets claiming to come from private IPs on your external interface.
block in quick on $ext_if from { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } to any
  1. Limit Connection Rates: Prevent DoS attacks by limiting connection rates.
table <bruteforce> persist
block quick from <bruteforce>
pass in on $ext_if proto tcp to ($ext_if) port 22 flags S/SA \
    keep state (max-src-conn 10, max-src-conn-rate 3/5, \
    overload <bruteforce> flush global)
  1. Log Important Events: Log denied packets and suspicious activity.
block in log all

Backup and Recovery

Always backup your working PF configuration:

cp /etc/pf.conf /etc/pf.conf.bak

Consider keeping versions in a version control system or creating dated backups:

cp /etc/pf.conf /etc/pf.conf.$(date +%Y%m%d)

If a new configuration causes issues, quickly revert:

cp /etc/pf.conf.bak /etc/pf.conf
pfctl -f /etc/pf.conf

Performance Considerations

For high-traffic environments:

  1. Optimize State Table Size: Adjust based on your server’s memory and connection load.

  2. Use Tables for Large IP Sets: Tables are more efficient than listing IPs in rules.

  3. Position Rules Efficiently: Place frequently matched rules earlier in your ruleset.

  4. Use Quick Keyword: The quick keyword stops rule processing when a match is found.

pass in quick from <trusted> to any
  1. Minimize Logging: Excessive logging can impact performance.

Conclusion

pfctl provides comprehensive control over FreeBSD’s PF firewall, allowing administrators to implement robust security policies. By understanding the various options and techniques presented in this article, you can effectively manage firewall rules, troubleshoot issues, and maintain secure network environments.

Remember that firewall management is an ongoing process that requires regular monitoring, updates, and optimization. As security threats evolve, your PF rule set should also evolve to address new challenges. Regular testing, validation, and refinement of your ruleset will ensure that your FreeBSD systems remain secure while delivering the required network services.

For more detailed information, consult the FreeBSD Handbook and PF user guide, which provide extensive documentation on PF’s capabilities and advanced configuration options.