iptables-nfqueue 的使用
文章目录
本文主要翻译自 Using NFQUEUE and libnetfilter_queue,将 C 代码例子换成 Go 代码例子。
介绍
NFQUEUE 是一种 iptables 和 ip6tables 的目标(an iptables and ip6tables target),将网络包处理决定委托给用户态软件。比如,下面的规则将所有接收到的网络包(all packet going to the box)委托给监听中的用户态程序去决策。
|
|
用户态软件必须监听队列 0(connect to queue 0),并从内核获取消息;然后给每个网络包给出判决(verdict)。
内部实现
为了理解 NFQUEUE,最简单的方式是理解 Linux 内核内部架构。当一个网络包触发 NFQUEUE 目标,它将被插入到 --queue-num
指定的队列里。网络包队列是一个链式列表,列表元素是网络包和元数据(Linux 内核 skb):
- 固定长度的链式列表
- 使用整数索引网络包
- 当用户态程序对指定整数索引的网络包做出判决后,该网络包将被释放掉
- 当队列满了,队列不再接收网络包
用户态程序会有如下影响:
- 批量读取消息,并批量给出判决。当队列未满时,批量处理不会产生影响。
- 可以无序地给网络包做出判决。比如,得到 1、2、3、4 网络包,以 4、2、3、1 顺序做出判决。
- 过慢的判决会导致队列充满。此时,内核会丢掉新来的网络包,而不是插入队列。
内核和用户态之间的通信协议
内核和用户态之间的通信协议使用的是 nfnetlink。这是一种不共享内存的基于消息的通信协议。当一个网络包入队时,内核将包含了网络包数据和相关信息的 nfnetlink 消息发送到某个套接字(socket),用户态程序就能够读取到这条消息。用户态程序进行判决的做法是,将包含了网络包整数索引的 nfnetlink 消息发送到那个套接字。
Go 例子
高级特性
多队列
--queue-balance
是 NFQUEUE 选项,由 Florian Westphal 实现,实现了同一条 iptables 规则的网络包负载均衡到多个队列。用法非常简单。比如,负载均衡 INPUT 流量到 0-3 队列的规则如下:
|
|
注意,负载均衡是基于流实现的(made with respect to the flow),一条流的所有网络包会发送到同一个队列。
可用范围:Linux 内核 >= 2.6.31,iptables >= 1.4.5。
--queue-bypass
--queue-bypass
也是由 Florian Westphal 实现的 NFQUEUE 选项。当没有用户态程序监听队列时,不再是丢包,而是放行。
适用范围:Linux 内核 >= 2.6.39,iptables >= 1.4.11。
该特性不适用于内核 3.10 到 3.12 版本。
失败开关
从 Linux 内核 3.6 版本开始,当队列满了,该选项允许放行而不是丢包。
批量判决
从 Linux 内核 3.1 版本开始,能够使用批量判决了。不再是一个网络包做一次判决,能够对 ID 范围的所有网络包只做一次判决(send a verdict to all packets with an id inferior to a given id)。
这能够提高系统处理网络包的性能,但会增大网络包的延迟。所以,用户态程序需要更快地去发出判决消息,以此减小延迟;此时需要注意很少网络包的场景。
杂项
/proc 下的 nfnetlink_queue
nfnetlink_queue 在 /proc 下的路径:/proc/net/netfilter/nfnetlink_queue
|
|
-
队列号:由
--queue-number
指定 -
对端接口 ID:监听队列的 pid
-
队列数量:当前在队列里等待的网络包数量
-
复制模式:0 和 1 只提供元数据;2 同时还会提供限定复制范围的部分网络包内容(a part of packet of size copy range)。
-
复制范围:放进消息里的网络包长度
-
队列丢包数量:因队列满了而丢包的网络包数量
-
用户丢包数量:因不能发送到用户态而丢包的网络包数量。如果该数值不为 0,尝试增大 netlink 缓存大小(increase netlink buffer size)。在程序侧,如果发生丢包,可以看到整数索引不连续了(see gap in packet id)。
-
ID 序列:最后一个网络包的整数索引
-
1
FAQ
多线程
收包/发包(nfnetlink 包)操作需要被锁保护,防止并发写。
收包和发包是完全分开的操作,它们不共享内存。判决只用到包 ID(packet index)。与此同时,上锁能让不同线程对任何包进行判决。
包重排序
NFQUEUE 很容易地为任意入队的包做包重排序。然而,需要注意的是内核是使用链式列表来排队包的;所以,乱序判决会带来一定的损耗。
零拷贝
内核和用户态之间使用 netlink 进行通信,不存在零拷贝这样的好事。Patrick McHardy 已开始 memory mapped implementation of netlink,所以未来可能会有零拷贝。
文章作者 Leon Hwang
上次更新 2021-12-12