FreeBSD PF 安装与使用详解如下。PF 是 OpenBSD 开发的一款功能强大的防火墙和流量整形工具,FreeBSD 从 5.3 版本开始将其引入,并已成为系统首选的内置防火墙。
在 FreeBSD 13.0 及以后版本中,PF 已作为内核模块直接包含在基础系统中,无需额外安装。
1. 加载 PF 内核模块在 /boot/loader.conf 中添加一行,让系统启动时自动加载 PF 模块:
echo 'pf_load="YES"' >> /boot/loader.conf
或者手动立即加载(无需重启):
kldload pf
2. 启用 PF
编辑 /etc/rc.conf,启用 PF 并指定规则配置文件路径:
sysrc pf_enable="YES"
sysrc pf_rules="/etc/pf.conf" # 默认规则文件路径
sysrc pflog_enable="YES" # 启用 PF 日志
sysrc pflog_logfile="/var/log/pflog" # 日志文件位置
3. 启动 PF 服务
service pf start
常用管理命令:
service pf status # 查看状态
service pf stop # 停止
service pf restart # 重启(重新加载规则)
pfctl -s rules # 查看当前加载的规则
/etc/pf.conf)PF 规则文件语法清晰,主要由以下几部分组成:
1. 宏定义:定义变量,便于维护ext_if="em0" # 外网接口
int_if="em1" # 内网接口
localnet=$int_if:network # 内网网段
webserver="192.168.1.10"
ssh_port="22"
2. 表:用于存储一组 IP 地址,高效处理大量地址
table <spam> persist file "/etc/spammers.txt" # 从文件加载黑名单
table <goodguys> { 10.0.0.0/24, !10.0.0.5 } # 定义内网网段,排除某个 IP
3. 选项:调整 PF 行为
set skip on lo0 # 忽略本地回环接口
set block-policy return # 阻塞时返回 RST/ICMP,默认是 drop
set state-policy if-bound # 状态绑定到接口
4. 规则:核心部分,包括过滤规则、NAT、重定向等
规则顺序至关重要,从上到下逐条匹配。
block all # 默认拒绝所有流量
pass out quick on $ext_if # 允许所有外网出口流量
pass in on $int_if from $localnet to any # 允许所有内网到外网的流量
2. 按协议/端口控制
# 允许来自外网的入站 SSH (限制在特定 IP 段)
pass in on $ext_if proto tcp from { 192.168.2.0/24 } to $ext_if port $ssh_port
# 允许来自任何地方的 HTTPS
pass in on $ext_if proto tcp to any port 443
# 允许内网 DNS 查询
pass in on $int_if proto udp from $localnet to any port 53
3. 状态化过滤
PF 是状态防火墙,keep state 或 flags S/SA 可跟踪连接状态。
pass in on $ext_if proto tcp to $webserver port { 80, 443 } flags S/SA keep state
4. 网络地址转换
# 出站 NAT (经典 IP 伪装,内网共享公网 IP)
nat on $ext_if from $localnet to any -> ($ext_if)
# 入站端口转发 (将公网 IP 的 443 端口转发到内网服务器)
rdr on $ext_if proto tcp from any to $ext_if port 443 -> $webserver port 443
# 双向转发/1:1 NAT
nat on $ext_if from 192.168.1.100 to any -> 203.0.113.10
5. 流量整形与队列
PF 通过 ALTQ 系统进行 QoS。
# 在接口上启用 CBQ 队列
altq on $ext_if cbq bandwidth 100Mb queue { std, bulk, web }
queue std bandwidth 30% cbq(default) # 默认队列
queue bulk bandwidth 20% cbq # 大流量队列
queue web bandwidth 50% cbq # Web 流量队列
# 将流量分类到队列
pass in on $ext_if proto tcp to any port 80 queue web
pass in on $ext_if proto tcp to any port 22 queue std
nat-anchor "ftp-proxy/*"
rdr-anchor "ftp-proxy/*"
rdr pass on $int_if proto tcp to any port 21 -> 127.0.0.1 port 8021
2. 日志记录
# 记录被阻止的流量到 pflog
block in log (all) on $ext_if
# 使用 pflogd 服务捕获日志,用 tcpdump 查看:
tcpdump -n -e -ttt -r /var/log/pflog
3. 防止 IP 欺骗
antispoof for $ext_if
4. 规则集优化与调试
# 测试规则文件语法
pfctl -nf /etc/pf.conf
# 详细显示当前所有状态和规则
pfctl -s all
# 查看状态表
pfctl -s states
pfctl -s info # 查看统计信息
# 动态添加/删除规则(临时生效)
echo "block in on $ext_if from 1.2.3.4 to any" | pfctl -f -
适用于家庭或小型办公室网关:
# /etc/pf.conf
# 宏定义
ext_if="igb0" # 外网接口
int_if="igb1" # 内网接口
localnet="192.168.1.0/24"
webserver="192.168.1.10"
ssh_port="22"
# 选项
set skip on lo0
set block-policy return
# 默认规则:拒绝所有入站,允许所有出站
block all
pass out quick on $ext_if
pass in quick on $int_if from $localnet to any
# NAT:内网共享公网 IP
nat on $ext_if from $localnet to any -> ($ext_if)
# 入站规则
# 1. 允许来自外网的 SSH (限制 IP 范围)
pass in on $ext_if proto tcp from { 192.168.2.0/24 } to $ext_if port $ssh_port
# 2. 允许来自外网的 Web 访问(转发到内网服务器)
rdr pass on $ext_if proto tcp from any to $ext_if port { 80, 443 } -> $webserver
pass in on $ext_if proto tcp to $webserver port { 80, 443 } flags S/SA keep state
# 3. 允许 ICMP (Ping)
pass in on $ext_if inet proto icmp all icmp-type echoreq
| 特性 | PF | IPFW |
|---|---|---|
| 来源 | OpenBSD | FreeBSD |
| 语法 | 较简洁,类似配置文件 | 规则编号,类似 Cisco IOS |
| NAT/重定向 | 集成度高,语法直观 (nat, rdr) |
需要 libalias 或内核模块 |
| 流量整形 | 内置 ALTQ | 使用 dummynet |
| 状态检测 | 内置 | 需要 stateful 规则 |
| 学习曲线 | 中等 | 较简单 |
建议:如果你来自 OpenBSD 背景或需要复杂策略路由/流量整形,选 PF;如果你习惯 IPFW 语法或需要极简方案,选 IPFW。
规则不生效:
pfctl -nf /etc/pf.conf 检查语法。pf_enable="YES" 在 /etc/rc.conf 中。pfctl -s all 查看加载的规则。网络中断:
ssh 连接执行 pfctl -d 临时禁用 PF 进行排查。pass in quick on $int_if 确保内网管理不中断。查看日志:
tcpdump -n -e -ttt -r /var/log/pflog 分析日志。man pf.conf、man pfctlPF 功能极为丰富,建议从简单规则开始测试,逐步增加复杂性。在测试新规则时,始终在本地控制台操作,避免被锁在远程主机外。