1. Introduction
扩展阅读:
- Packet Filtering HOWTO
- NAT HOWTO
- Rusty’s Unreliable Guide to Kernel Hacking
- Rusty’s Unreliable Guide to Kernel Locking
1.1 What is netfilter?
netfilter 是一套位于标准伯克利套接字 (Berkeley socket) 接口之外的数据包处理框架。
它包含四个部分:
每种网络协议 (protocol) 都会定义若干 “钩子 (hooks)”(IPv4 协议定义了 5 个),这些是数据包在协议栈中传输路径上的固定节点。在每个这样的节点处,协议都会将当前数据包与钩子编号一并传递给 netfilter 框架进行处理。
内核中的部分模块可以注册监听各协议对应的不同钩子。因此,当数据包被传递给 netfilter 框架时,框架会检查是否有模块注册了该协议与对应的钩子;若有,这些模块将按顺序依次获得检查(并可能修改)数据包的机会,随后可以选择丢弃数据包(NF_DROP)、允许其通过(NF_ACCEPT)、告知 netfilter 不再处理该数据包(NF_STOLEN),或请求 netfilter 将数据包排队交由用户空间处理(NF_QUEUE)。
那些被排入队列的数据包会由
ip_queue驱动程序收集,并发送至用户空间;这些数据包采用异步方式处理。体现在代码与文档中那些精妙有趣的注释里。这对任何实验性项目都至关重要。
除了这个基础框架本身,还开发了多种模块,用以提供与早期(netfilter 之前的)内核相似的功能,其中尤其包括可扩展的 NAT 系统,以及可扩展的数据包过滤系统(iptables)。
1.2 What’s wrong with what we had in 2.0 and 2.2?
1.3 Who are you?
1.4 Why does it crash?
2. Where Can I Get The Latest?
3. Netfilter Architecture
Netfilter 只是在协议栈的不同位置上设置的一系列钩子(目前支持 IPv4、IPv6 和 DECnet 协议)。下面是(理想化的)IPv4 数据包流转示意图:
1 | A Packet Traversing the Netfilter System: |
左侧是数据包的流入端:在通过简单的完整性检查(即未被截断、IP 校验和正确、非混杂模式接收)后,数据包会被传递至 netfilter 框架的 NF_IP_PRE_ROUTING [1] 钩子。
接下来,数据包进入路由处理模块,由其判断该数据包是要发往另一个网络接口,还是交给本地进程。对于无法路由的数据包,路由模块会将其丢弃。
如果数据包的目的地是本机,在传递给对应进程(如果存在)之前,会再次调用 netfilter 框架的 NF_IP_LOCAL_IN [2] 钩子。
如果数据包需要转发到其他接口,则会调用 netfilter 框架的 NF_IP_FORWARD [3] 钩子。
随后,数据包在重新发送到网络线路之前,会经过最后一个 netfilter 钩子:NF_IP_POST_ROUTING [4] 钩子。
NF_IP_LOCAL_OUT [5] 钩子用于处理本机主动创建的数据包。可以看到,路由决策是在该钩子调用之后执行的:实际上路由代码会先被调用(用于确定源 IP 地址及部分 IP 选项);如果你想要修改路由,必须像 NAT 代码中那样,自行修改 skb->dst 字段。
3.1 Netfilter Base
现在我们来看一个 IPv4 环境下 netfilter 的示例,你可以清楚看到每个钩子在何时被触发。这就是 netfilter 的核心原理。
内核模块可以注册并监听这些钩子中的任意一个。注册函数的模块必须指定该函数在钩子中的优先级;当核心网络代码调用该 netfilter 钩子时,在此处注册的每个模块会按照优先级顺序被调用,并可以自由处理数据包。
之后,模块可以指示 netfilter 执行以下五种操作之一:
- NF_ACCEPT:正常继续数据包的流转流程。
- NF_DROP:丢弃该数据包,终止流转。
- NF_STOLEN:已接管该数据包,终止流转。
- NF_QUEUE:将数据包入队(通常交由用户空间处理)。
- NF_REPEAT:重新调用当前钩子。
netfilter 的其他部分(队列数据包处理、精妙的注释)将在后面的内核章节中介绍。
在这个基础之上,我们可以构建相当复杂的数据包处理逻辑,接下来两节将会对此进行说明。
3.2 Packet Selection: IP Tables
在 netfilter 框架之上构建了一套名为 IP 表(iptables) 的数据包筛选系统。它直接继承自 ipchains(ipchains 源自 ipfwadm,而 ipfwadm 据我记忆则源自 BSD 系统的 ipfw),并具备可扩展能力。内核模块可以注册新的表,并让数据包按指定表进行规则遍历。这种数据包筛选机制被用于数据包过滤(filter 表)、网络地址转换(nat 表)以及通用的路由前数据包处理(mangle 表)。
向 netfilter 注册的钩子如下所示(每个钩子中的函数按实际调用顺序排列):
1 | --->PRE------>[ROUTE]--->FWD---------->POST------> |
Packet Filtering
iptables 过滤机制相较于 ipchains 的优势之一在于它小巧高效,并且在 netfilter 的 NF_IP_LOCAL_IN、NF_IP_FORWARD 和 NF_IP_LOCAL_OUT 这几个节点挂载钩子。这意味着对于任意一个给定的数据包,有且仅有一个合适的位置对其进行过滤。这对用户而言,比使用 ipchains 要简单得多。此外,netfilter 框架为 NF_IP_FORWARD 钩子同时提供了入站与出站接口,这也使得多种过滤操作实现起来更为简便。
注意:我已经将 ipchains 和 ipfwadm 的内核部分都移植为基于 netfilter 之上的模块,从而可以直接使用旧版的 ipfwadm 与 ipchains 用户态工具,而无需进行升级。
NAT
这就是 nat 表 的作用域,该表接收来自两个 netfilter 钩子的数据包:
- 对于非本地数据包,NF_IP_PRE_ROUTING 和 NF_IP_POST_ROUTING 钩子分别非常适合用于修改目的地址和源地址。
- 如果定义了 CONFIG_IP_NF_NAT_LOCAL,则会使用 NF_IP_LOCAL_OUT 和 NF_IP_LOCAL_IN 钩子来修改本地数据包的目的地址。
这张表与 filter 表略有不同:只有新连接的第一个数据包会经过这张表,此次匹配的结果会应用到该连接后续的所有数据包上。
Masquerading, Port Forwarding, Transparent Proxying
- 伪装、端口转发、透明代理
我将网络地址转换(NAT)分为源NAT(对首个数据包的源地址进行修改)和目的NAT(对首个数据包的目的地址进行修改)。
IP 伪装(Masquerading)是源 NAT 的一种特殊形式;端口转发与透明代理则是目的 NAT 的特殊形式。如今这些功能均通过 NAT 框架实现,而非作为独立模块存在。
Packet Mangling
- 数据包修改
数据包修改表(mangle 表)用于对数据包信息进行实际修改。典型的应用场景包括 TOS(服务类型)和 TCPMSS(最大分段大小)等规则目标。
mangle 表挂载在全部五个 netfilter 钩子上。(请注意,这一特性在内核 2.4.18 版本后发生了变更,更早版本的内核并未将 mangle 表关联到所有钩子)
3.3 Connection Tracking
连接跟踪是 NAT 的基础,但它以独立模块的形式实现;这使得数据包过滤代码的扩展模块(如 state 模块)能够简洁、清晰地使用连接跟踪功能。
3.4 Other Additions
这种全新的灵活性既为实现各种极具创意的功能提供了可能,也让开发者能够编写增强模块或完整替代方案,并且这些模块可以自由组合使用。
4. Information for Programmers
4.1 Understanding ip_tables
iptables 只是在内存中提供一个带名称的规则数组(这也是“iptables”名称的由来),以及诸如来自各个钩子的数据包应从何处开始遍历规则等信息。在表注册完成后,用户空间可以通过 getsockopt() 和 setsockopt() 读取并替换其内容。
iptables 本身并不向任何 netfilter 钩子注册;它依靠其他模块来完成注册,并在合适的时候将数据包交给它处理。一个模块必须分别注册 netfilter 钩子和 ip_tables,并提供在钩子触发时调用 ip_tables 的机制。
ip_tables Data Structures
每条规则由以下部分组成:
- 一个
struct ipt_entry结构体。 - 零个或多个
struct ipt_entry_match结构体,每个结构体后可附加可变长度(0 字节或更多字节)的数据。 - 一个
struct ipt_entry_target结构体,其后可附加可变长度(0 字节或更多字节)的数据。