每台 Mac 都附带两个人们称之为“防火墙”的东西。其中之一是系统设置中的应用程序防火墙,这是针对入站连接的每个应用程序切换。另一种更安静的是 pf,macOS 继承自 FreeBSD/OpenBSD 血统的 BSD 数据包过滤器,Apple 本身也使用它来实现互联网共享、VPN 和 NAT。高级用户和管理员可以直接使用 pfctl 与其对话,并且大量指南会显示 pf.conf 代码片段,然后就到此为止了。这些指南很少解释的是,pf 对于大多数人真正想要的东西不再有用:监视和控制他们自己的应用程序发送的内容。这是一个实验,而不是一个讲座——我们将打开 pf,编写规则,读取它保留的状态,然后确切地了解为什么该状态对于应用程序防火墙来说是错误的形状。
pf 实际上是什么
pf 是一个内核级数据包过滤器:它在数据包穿过网络接口时检查数据包,并逐条规则地决定是通过还是阻止它们。它没有“应用程序”的概念 - 它纯粹在数据包标头上工作:源和目标地址、端口、协议、接口、方向。这并不是有人忘记修复的限制;而是有人忘记了修复。这就是设计。 pf 是为了在网络层过滤流量而构建的,路由器和网关在同一层运行,并且它非常擅长这项工作。
您可以使用控制实用程序pfctl 与它对话。它的手册页明确说明了它所做的两件事之间的区别:它从配置文件加载规则集,并报告内核所持有的状态。用手册页自己的话来说,有两个标志对于启用和禁用过滤器最重要:-e(“启用数据包过滤器”)和-d(“禁用数据包过滤器”)。 pf 的其他内容都不是打开或关闭的——整个规则集一起移动。
打开它并读取其状态
在 Mac 上进行快速实验,您可以轻松地使用 sudo。首先,检查过滤器是否已启用并查看其计数器:
sudo pfctl -s info
# example output, trimmed — the real thing includes per-rule and per-source-tracking stats with -v
Status: Enabled for 0 days 02:14:07 Debug: err
State Table Total Rate
current entries 42
searches 88213 9.7/s
inserts 611 0.1/s
removals 569 0.1/s然后列出内核当前持有的规则。 pfctl -s rules 正是这样做的;手册页指出,使用-v,它还打印每个规则的评估计数、数据包和字节:
sudo pfctl -s rules
# example output, trimmed
scrub-anchor "com.apple/*" all fragment reassemble
anchor "com.apple/*" all
block drop in log quick from <blocklist> to anycom.apple/* 行不是装饰。苹果将自己的规则加载到命名锚中——pf 的术语是指独立的子规则集,可以在不重新加载其他所有内容的情况下进行换入和换出。 pfctl 的 -a 标志针对特定的锚点,并且根据手册页,将其与通配符一起使用可以递归打印嵌套锚点,这就是您如何查看 Apple 本身与您添加的任何内容一起加载的内容:
sudo pfctl -a '*' -s rules
# example output, trimmed to the anchors that exist on a stock Mac编写并加载测试规则
阻止一个 IP 地址出站的最小锚文件,保存为/etc/pf.anchors/test-block:
block drop out quick on en0 proto tcp to 203.0.113.10 port 443要加载单个锚文件,pfctl -f 根据其手册页从文件中读取规则,该文件将文件描述为包含宏、表、选项和过滤规则:
sudo pfctl -f /etc/pf.anchors/test-block
sudo pfctl -s rules
block drop out quick on en0 proto tcp from any to 203.0.113.10 port = 443为什么 pf 不能成为您的应用程序防火墙
接下来的内容都不是错误。当您将网络层数据包过滤器指向需要知道哪个进程发送数据包的作业时,就会发生这种情况。
它不知道哪个应用程序发送了数据包
pf 规则匹配 IP、端口、协议、接口和方向。没有“进程名称”、“包标识符”或“代码签名”字段,因为 pf 位于数据包存在但进程不存在的层。两个完全不同的应用程序打开到相同 IP 和端口的 TCP 连接对于 pf 来说是无法区分的。如果您希望允许 Slack 到达主机,同时阻止所有其他应用程序到达同一主机,则仅 pf 无法表达该规则。
主机名上的规则是主机名在加载时具有的任何 IP 上的规则
pf.conf 文件通常会引用主机名以提高可读性 — block from evil.example.com。实际加载的并不是那个名字;而是那个名字。它是它解析到的任何地址。 OpenBSD pf.conf 手册页清楚地说明了这一点:“主机名解析和地址转换接口是在规则集加载时完成的。”当流量流动时,不会进行运行时 DNS 查找 - 当您运行 pfctl -f 时,替换会发生一次,并且规则会一直匹配该地址,直到您重新加载它为止。对于具有静态 IP 的服务器来说这很好。当其背后的名称是 CDN、云负载均衡器或任何在多个地址之间轮换或负载均衡的服务时,它就分崩离析了——这描述了 2026 年互联网的大部分内容。一条旨在阻止“此服务”的规则悄悄地缩小为“当我加载规则时该服务的 IP 碰巧响应”,并且相同主机名解析到的每个其他地址的流量都会直接通过。
没有提示,没有对话——只有静态规则集
pf 没有交互模型。它无法暂停连接并询问“Mail 希望首次访问端口 993 上的 51.x.x.x — 允许吗?”它要么匹配您已经编写的规则,要么采用默认规则。每一个决定都必须在流量发生之前以 IP 和端口的形式提前预测和记录下来。没有相当于首次连接提示的功能,因为提示需要知道哪个应用程序正在询问,而 pf 一开始就没有该信息。
您的手写配置在更新后无法保存
Apple 将 /etc/pf.conf 及其加载的锚点视为与 macOS 内部相关的系统管理配置 - 互联网共享、VPN、应用程序防火墙自己的锚点都依赖于它。 macOS 更新可以免费重写或替换该文件。如果您手动编辑它以添加您自己的规则,则不能保证它们在下一次更新中仍然存在;事后你会发现,你的规则悄然停止适用。一个人手动维护且操作系统定期覆盖的配置文件不适合保存您真正关心的一件事——“我的 Mac 是否再次与该地址通信”。
没有日志查看器、没有历史记录、没有地图
如果规则包含 log 关键字,pf 可以将匹配的数据包记录到伪接口 pflog0 - 在前面的 -s rules 输出的阻止列表规则中可见。但该日志是一个数据包捕获流,可以使用 tcpdump -i pflog0 读取,而不是可搜索的历史记录。没有内置查看器,没有每个应用程序的被阻止内容和时间列表,没有附加到地址的国家或组织,您也不会向某人展示任何内容来回答“这台 Mac 上周试图到达什么地方”。您获得原始数据包,然后您可以自己构建其余的数据包。
苹果实际上为此工作构建的层
苹果自己对“我想过滤每个应用程序的 Mac 流量”的回答不是 pf,而是网络扩展框架,特别是其内容过滤器提供程序。苹果的开发者文档直接描述了该模型:“设备上的网络内容过滤器会在用户网络内容通过网络堆栈时检查它,并确定是否应该阻止该内容或允许其传递到最终目的地”,而过滤器数据提供者(NEFilterDataProvider)“接收用户网络内容并检查该内容以确定是阻止还是允许它。”流被表示为 NEFilterFlow 对象(以 NEFilterBrowserFlow 和 NEFilterSocketFlow 作为具体案例),这是我们从未拥有过的缺失部分:过滤应用程序可以在决定通过或阻止之前检查并回接到打开它的进程的流对象。
这也是为什么内置应用程序防火墙(系统设置中的切换开关)与 pf 是不同的动物,而不是它的前端。苹果自己的指南仅以入站术语描述它:它“可以保护您的 Mac 免受其他计算机发起的不必要的接触”,并且它的工作原理是让您“选择应用程序和服务,并指定它们是否可以通过防火墙进行访问”。每个应用程序,是的,但仅限于传入的连接,并且只能通过 Apple 为该作业构建的特定机制。它回答了与“我的应用程序发送什么”不同的问题。
| 方法 | 查看IP/端口 | 知道哪个应用程序 | 处理重命名/轮换 IP | 可以提示用户 | 方向 |
|---|---|---|---|---|---|
pf (pfctl) | 是的 | 不 | 否 - 在加载时解决一次 | 不 | 要么,按照规则 |
| 应用程序防火墙(系统设置) | 否(应用程序级切换) | 是的 | 不适用 | 不 | 仅限入境 |
| 网络扩展内容过滤器 | 是的 | 是的,通过流对象 | 是 - 根据实时流进行评估 | 是的,通过基于其构建的应用程序 | 出站和入站 |
这些都不会让pf变得毫无用处。如果您将 Mac 作为轻量级路由器运行,需要在内核级别拒绝已知的不良范围,无论哪个进程正在询问,或者想要了解 Apple 自己的互联网共享和 VPN 功能在幕后的用途,pf 是完成该工作的正确且唯一的工具,而 pfctl -s rules / -s info 是查看它的正确方法。它永远不会回答大多数人实际遇到的问题:我的哪个应用程序现在正在与谁对话,以及在新应用程序启动之前可以询问我吗?
FireAI 和 HisnLabs 在其中扮演的角色
pf and the built-in Application Firewall are both worth using — FireAI does not replace either; it fills the specific gap neither one can, by tying outbound decisions to the app’s code signature and asking before an unknown one gets a first connection.
FireAI 是 HisnLabs 自己的产品:一款直接在 Mac 上运行的 AI 防火墙。它用通俗易懂的语言显示你的应用建立的每一个连接,并让你决定哪些数据可以离开你的 Mac——它的 AI 在本地运行,因此你的流量永远不会发送给我们或任何其他人。HisnLabs 的安全研究团队负责让这些判断保持准确:归类哪些域名只是普通的遥测、哪些属于真正的服务,追踪连接背后的国家和网络,并用真实的流量模式训练设备端模型(Autopilot 功能)——这一切都不会离开你的 Mac。
你可以阅读它背后的技术决策,或试用 FireAI 17 天:HisnLabs 出品的 FireAI。
