传统的网络过滤方案往往需要在每一台主机上手动配置 iptables 规则,或者依赖复杂的用户态代理来实现细粒度的流量控制。这种方式在大规模容器化部署场景下面临着配置同步困难、规则变更延迟高、难以实现动态策略下发等痛点。Netfence 作为一款新兴的开源项目,尝试将 Envoy 中广为人知的 xDS 控制平面模式移植到 eBPF 领域,为内核级网络过滤提供一种声明式、可编程的解决方案。
架构概述与核心定位
Netfence 的设计理念可以简洁地概括为「Like Envoy xDS, but for eBPF filters」。这意味着它借鉴了 Envoy 中控制平面与数据平面分离的成熟架构,但在实现层面充分利用了 eBPF 程序在内核中直接处理网络数据包的能力。项目采用 Go 语言作为主要开发语言,占比接近 90%,辅以少量 C 代码用于编写 eBPF 程序本身。
从整体架构来看,Netfence 包含两个核心组件:运行在每台主机上的 Daemon 进程,以及由用户自行实现的 Control Plane。Daemon 负责与内核交互,完成 eBPF 程序的加载、挂载以及过滤规则的生效;Control Plane 则通过 gRPC 与所有 Daemon 建立双向流连接,接收来自 Daemon 的同步请求,并下发网络过滤策略。这种设计使得运维人员只需要在 Control Plane 上定义诸如「允许 *.pypi.org」或「允许 10.0.0.0/16 网段」的规则,Netfence 会自动将这些策略同步到所有相关主机的内核层面。
数据平面的注入机制
Netfence 支持两种 eBPF 过滤程序的挂载方式,分别针对不同的使用场景。第一种是挂载到网络接口的 TC(Traffic Control)钩子,这种方式适用于对虚拟机或物理机的物理网卡、虚拟网卡(如 veth)进行过滤,能够在数据包进出主机的第一时间完成处理。第二种是挂载到 cgroup,这种方式天然适合容器化环境,可以针对特定容器所属的 cgroup 施加过滤规则,而不影响同一主机上运行的其他容器。
在 TC 模式下,eBPF 程序被附加到网络接口的 ingress 或 egress 钩子处。当数据包经过接口时,内核会调用这些程序进行检查,根据预设的策略决定放行、丢弃或修改。TC 钩子的优势在于它位于网络协议栈的较底层,能够在数据包进入内核协议栈之前就完成过滤,从而避免了不必要的上下文切换开销。在 cgroup 模式下,过滤程序会附加到容器的 cgroup 路径上,所有属于该容器的网络数据包都会经过检查。这种方式与 Kubernetes 等容器编排系统的网络模型高度契合,能够方便地实现租户级别的网络隔离。
挂载过程本身由 Daemon 进程通过 netlink 接口完成。以 TC 模式为例,Daemon 首先需要创建或替换 clsact qdisc,这是 eBPF 程序能够被附加到网络接口的前提条件。随后,通过 netlink 的 FilterReplace 接口将编译好的 eBPF 字节码加载到内核,并将过滤器的句柄与目标网络接口关联。整个过程封装在 DaemonService.Attach RPC 方法中,用户只需要提供接口名称或 cgroup 路径即可完成挂载。
控制平面的 gRPC 协议设计
Netfence 的控制平面协议设计体现了现代云原生系统的典型风格。Daemon 与 Control Plane 之间的通信采用双向流(Bidirectional Stream)模式,这种设计既保证了实时性,又能够在单条 TCP 连接上复用多个消息类型的传输。当 Daemon 启动时,它会主动向 Control Plane 发起连接,并建立 ControlPlane.Connect 通道。连接建立后,双方可以通过同一条流发送各自的消息,无需维护多条连接。
Control Plane 接收到的消息类型包括 SyncRequest、Subscribed、Unsubscribed 和 Heartbeat。SyncRequest 在每次连接或重连时发送,包含了当前 Daemon 上所有已挂载附件的完整列表,这使得 Control Plane 能够准确掌握整个集群的过滤状态。Subscribed 消息在新的过滤附件被挂载时触发,携带了附件的标识信息以及附加的元数据(如 VM ID、容器 ID、租户标识等),Control Plane 可以根据这些信息查询对应的过滤策略并下发。Unsubscribed 消息则在附件被移除时发送,确保 Control Plane 能够及时清理不再需要的规则。Heartbeat 消息携带统计信息,用于监控和健康检查。
Control Plane 发送给 Daemon 的消息同样丰富多样。SyncAck 用于响应 SyncRequest,表示 Control Plane 已经处理完当前状态。SubscribedAck 是整个协议中最关键的消息,它携带了初始的过滤配置,包括策略模式(allowlist、denylist、block-all、disabled)、CIDR 规则列表以及 DNS 过滤规则。Netfence 协议规定,Daemon 在收到 Subscribed 消息后必须阻塞等待 SubscribedAck,只有在收到该确认后才会向调用者返回成功,这个设计确保了过滤规则在流量开始传输之前就已经生效,避免了可能出现的安全窗口。
除初始配置外,Control Plane 还可以随时下发运行时变更。SetMode 用于切换整体策略模式;AllowCIDR、DenyCIDR、RemoveCIDR 用于增删改 IP 网段规则;SetDnsMode、AllowDomain、DenyDomain、RemoveDomain 用于管理域名过滤规则;BulkUpdate 则提供了全量状态同步的能力,适用于需要一次性替换所有规则的场景。每个 CIDR 和域名规则都可以携带 TTL(生存时间),这使得临时性的访问授权能够自动过期,省去了手动清理的麻烦。
DNS 解析与 IP 允许列表的联动
Netfence 的一个显著特点是它将 DNS 解析与 IP 过滤深度整合。每个过滤附件都会被分配一个唯一的本地 DNS 地址(端口),容器或虚拟机需要配置使用这个地址进行 DNS 查询。当容器尝试访问一个被允许的域名时,Netfence 的内置 DNS 服务器会解析该域名并将结果返回,同时自动将解析出的 IP 地址添加到 eBPF 过滤器的允许列表中。这种设计巧妙地解决了动态 IP 地址场景下的过滤问题。
以访问 PyPI 官方仓库为例,运维人员在 Control Plane 上配置了一条规则:允许 *.pypi.org。当容器向 Netfence 的 DNS 服务器查询 pypi.org 的 A 记录时,DNS 服务器会执行真实的域名解析,获取一组 IP 地址。随后,Netfence 将这些 IP 地址写入 eBPF 过滤器的映射中,此后该容器访问这些 IP 的流量都会被放行。这种联动机制的优势在于,它不需要运维人员事先知道目标域名的所有 IP 地址,也不需要在 DNS 查询返回后手动更新防火墙规则,一切都是自动完成的。
域名规则还支持基于特异性的匹配逻辑。当同时存在「允许 *.pypi.org」和「允许 pypi.python.org」这两条规则时,后者作为更具体的规则会优先匹配。这种设计符合域名系统的实际层级结构,使得精细化的访问控制成为可能。DNS 过滤器本身也支持多种模式,包括仅允许列表模式、仅拒绝列表模式以及完全禁用,可以根据安全需求灵活配置。
策略模式与运行时行为
Netfence 定义了四种策略模式,每种模式决定了过滤器对未知流量的默认处理方式。disabled 模式下过滤器不产生任何作用,所有流量都会被放行,这通常用于调试或临时禁用过滤的场景。allowlist 模式下,只有被明确允许的流量才会通过,其他所有流量都会被丢弃,这是一种较为严格的默认拒绝策略。denylist 模式下,默认允许所有流量,只有被明确禁止的流量才会被丢弃,这适用于只需要拦截特定恶意地址的场景。block-all 模式下,所有流量都会被丢弃,包括已经被允许的流量,这通常用于紧急封禁或完全隔离。
策略模式的切换可以通过 Control Plane 下发 SetMode 消息实时完成,无需重新加载 eBPF 程序或重启 Daemon。eBPF 过滤器内部会维护一个映射,存储当前生效的策略模式以及所有具体的规则条目。当新规则被添加或旧规则被删除时,Daemon 会更新对应的映射,而 eBPF 程序在后续的数据包处理中会立即看到这些变化。这种设计保证了规则变更的原子性和一致性,不会出现部分生效或中间状态的问题。
对于 CIDR 规则,Netfence 同时支持 IPv4 和 IPv6 两种地址族。每个 CIDR 条目都可以独立设置 TTL,过期后自动从过滤器映射中移除。这一特性在云原生环境中尤为重要,因为工作负载的 IP 地址往往是动态分配的,旧的 IP 可能在迁移或销毁后被重新分配给其他用户,自动过期机制能够防止权限泄露。
元数据系统与多租户支持
为了支持复杂的多租户场景,Netfence 设计了一套元数据机制。在调用 Attach RPC 时,调用者可以附加任意数量的键值对作为元数据,例如 {vm_id: "abc123", tenant: "acme", env: "prod"}。这些元数据会随 Subscribed 消息一起发送给 Control Plane,使得后者能够根据资源的实际归属来决策应该下发哪些过滤规则。
元数据系统为自动化策略匹配提供了基础。假设一个企业的基础设施按照部门、项目、环境等多个维度进行隔离,运维团队可以在 Control Plane 上预先配置策略模板:当收到带有 tenant=acme 和 env=prod 元数据的附件时,自动应用生产环境的过滤规则;当收到 tenant=acme 和 env=dev 时,则应用开发环境的宽松规则。这种基于元数据的策略路由避免了为每个资源手动配置规则的繁琐,也减少了人为配置错误的可能性。
Daemon 本身也可以携带元数据,用于标识其所在的主机或区域信息。这在混合云或多区域部署场景中非常有用,Control Plane 可以根据 Daemon 的位置信息将过滤规则下发到特定区域的主机,或者实现跨区域的统一策略管理。
部署与运维考量
部署 Netfence 需要满足几个前提条件。首先是内核版本要求,eBPF 的许多特性在不同内核版本之间存在差异,TC eBPF 钩子和 cgroup skb 程序在 4.10 之后的内核中得到了较好的支持,但某些高级特性可能需要更新的内核版本。其次是内核配置要求,需要确保 CONFIG_BPF、CONFIG_CGROUP_BPF、CONFIG_NET_ACT_BPF 等选项被启用。最后是挂载点的权限要求,附加 eBPF 程序到网络接口或 cgroup 通常需要 root 权限或 CAP_SYS_ADMIN 能力。
Daemon 进程本身是轻量级的,主要占用内存用于存储 eBPF 映射和 DNS 缓存。它提供了 start、status、attach、detach、list 等命令行操作,分别用于启动服务、查询状态、挂载过滤器、卸载过滤器以及列出当前所有附件。Daemon 还暴露了一个本地 gRPC API(DaemonService),供本地的编排系统调用。这种设计使得 Netfence 可以与 Kubernetes、Mesos、Terraform 等各种编排工具集成。
在生产环境中,通常会部署多个 Daemon 实例以实现高可用,每个实例连接同一个 Control Plane。Control Plane 需要实现连接管理逻辑,包括处理 Daemon 的上线、下线、重连等情况,并在必要时重新下发规则。Netfence 协议通过 SyncRequest 和心跳机制保证了最终一致性,即使出现网络分区或 Control Plane 重启,Daemon 在恢复连接后也能够获取完整的规则状态。
性能特征与优化方向
由于 eBPF 程序运行在内核空间,Netfence 的过滤性能主要取决于 eBPF 程序的执行效率和数据路径的优化程度。相比于 iptables 或用户态代理,eBPF 过滤避免了内核态与用户态之间的上下文切换,也不需要为每个数据包创建内核缓冲区,因此在高并发场景下能够表现出更低的延迟和更高的吞吐量。XDP(eXpress Data Path)模式更进一步,将过滤点提前到了网卡驱动层面,甚至可以在数据包进入内核协议栈之前就完成处理。
当前版本的 Netfence 主要使用 Go 语言开发,Go 的垃圾回收机制在长时间运行的服务中可能产生一定的延迟波动。对于追求极致性能的场景,可以考虑将 Daemon 的核心路径使用 Rust 或 C++ 重写,或者使用 Go 的实时 goroutine 调度来降低 GC 的影响。此外,eBPF 程序本身的优化空间也值得关注,例如使用 tail call 实现复杂的过滤逻辑、使用 perf event 进行性能监控等。
与现有方案的对比
传统的网络过滤方案如 iptables、nftables 虽然成熟稳定,但规则变更需要写入内核表项,在大规模集群中同步效率较低。用户态代理方案如 Envoy、Cilium 能够提供丰富的功能,但需要将数据包从内核拷贝到用户态处理,存在一定的性能开销。Netfence 通过 eBPF 技术将过滤逻辑下沉到内核,同时通过控制平面实现了动态策略下发,兼具性能和灵活性。
与 Cilium 的 eBPF 网络策略相比,Netfence 的定位更偏向于通用网络过滤控制平面,而非完整的容器网络解决方案。Cilium 与 Kubernetes 深度集成,提供了 CNI 插件、服务网格、NetworkPolicy 等一系列功能;Netfence 则专注于提供一个轻量级的、可移植的过滤控制平面,用户可以根据自己的需求选择合适的编排方式和上层策略逻辑。
Netfence 的设计为云原生环境下的网络过滤提供了一种新的思路。它借鉴了 Envoy xDS 的成熟模式,结合 eBPF 的高性能特性,实现了控制平面与数据平面的分离。对于需要在大规模主机集群中统一管理网络过滤规则的团队,Netfence 提供了一个值得参考的开源方案。
参考资料
- Netfence GitHub 仓库:https://github.com/danthegoodman1/Netfence
- Linux TC eBPF 过滤程序示例:https://github.com/eunomia-bpf/bpf-developer-tutorial