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) 接口之外的数据包处理框架。

它包含四个部分:

  1. 每种网络协议 (protocol) 都会定义若干 “钩子 (hooks)”(IPv4 协议定义了 5 个),这些是数据包在协议栈中传输路径上的固定节点。在每个这样的节点处,协议都会将当前数据包与钩子编号一并传递给 netfilter 框架进行处理。

  2. 内核中的部分模块可以注册监听各协议对应的不同钩子。因此,当数据包被传递给 netfilter 框架时,框架会检查是否有模块注册了该协议与对应的钩子;若有,这些模块将按顺序依次获得检查(并可能修改)数据包的机会,随后可以选择丢弃数据包(NF_DROP)、允许其通过(NF_ACCEPT)、告知 netfilter 不再处理该数据包(NF_STOLEN),或请求 netfilter 将数据包排队交由用户空间处理(NF_QUEUE)。

  3. 那些被排入队列的数据包会由 ip_queue 驱动程序收集,并发送至用户空间;这些数据包采用异步方式处理。

  4. 体现在代码与文档中那些精妙有趣的注释里。这对任何实验性项目都至关重要。

除了这个基础框架本身,还开发了多种模块,用以提供与早期(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
2
3
4
5
6
7
8
9
10
11
A Packet Traversing the Netfilter System:

--->[1]--->[ROUTE]--->[3]--->[4]--->
| ^
| |
| [ROUTE]
v |
[2] [5]
| ^
| |
v |

左侧是数据包的流入端:在通过简单的完整性检查(即未被截断、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 执行以下五种操作之一:

  1. NF_ACCEPT:正常继续数据包的流转流程。
  2. NF_DROP:丢弃该数据包,终止流转。
  3. NF_STOLEN:已接管该数据包,终止流转。
  4. NF_QUEUE:将数据包入队(通常交由用户空间处理)。
  5. NF_REPEAT:重新调用当前钩子。

netfilter 的其他部分(队列数据包处理、精妙的注释)将在后面的内核章节中介绍。

在这个基础之上,我们可以构建相当复杂的数据包处理逻辑,接下来两节将会对此进行说明。

3.2 Packet Selection: IP Tables

在 netfilter 框架之上构建了一套名为 IP 表(iptables) 的数据包筛选系统。它直接继承自 ipchains(ipchains 源自 ipfwadm,而 ipfwadm 据我记忆则源自 BSD 系统的 ipfw),并具备可扩展能力。内核模块可以注册新的表,并让数据包按指定表进行规则遍历。这种数据包筛选机制被用于数据包过滤filter 表)、网络地址转换nat 表)以及通用的路由前数据包处理mangle 表)。

向 netfilter 注册的钩子如下所示(每个钩子中的函数按实际调用顺序排列):

1
2
3
4
5
6
7
8
9
10
--->PRE------>[ROUTE]--->FWD---------->POST------>
Conntrack | Mangle ^ Mangle
Mangle | Filter | NAT (Src)
NAT (Dst) | | Conntrack
(QDisc) | [ROUTE]
v |
IN Filter OUT Conntrack
| Conntrack ^ Mangle
| Mangle | NAT (Dst)
v | Filter

Packet Filtering

iptables 过滤机制相较于 ipchains 的优势之一在于它小巧高效,并且在 netfilter 的 NF_IP_LOCAL_INNF_IP_FORWARDNF_IP_LOCAL_OUT 这几个节点挂载钩子。这意味着对于任意一个给定的数据包,有且仅有一个合适的位置对其进行过滤。这对用户而言,比使用 ipchains 要简单得多。此外,netfilter 框架为 NF_IP_FORWARD 钩子同时提供了入站与出站接口,这也使得多种过滤操作实现起来更为简便。

注意:我已经将 ipchains 和 ipfwadm 的内核部分都移植为基于 netfilter 之上的模块,从而可以直接使用旧版的 ipfwadm 与 ipchains 用户态工具,而无需进行升级。

NAT

这就是 nat 表 的作用域,该表接收来自两个 netfilter 钩子的数据包:

  • 对于非本地数据包,NF_IP_PRE_ROUTINGNF_IP_POST_ROUTING 钩子分别非常适合用于修改目的地址和源地址。
  • 如果定义了 CONFIG_IP_NF_NAT_LOCAL,则会使用 NF_IP_LOCAL_OUTNF_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 字节或更多字节)的数据。