为了完成这个圈子(并希望帮助可能正在寻找类似解决方案的人),我们通过使用libnetfilter_queue 解决了我们的问题。我们面临的挑战是,我们无法访问应用程序的源代码,否则我们可以在应用程序级别本身完成碎片。
这是由 Sriram Dharwadkar 编写的我们内部文档的相关摘录,他也进行了实施。其中一些引用是针对我们内部应用程序的名称,但请不要认为您在理解上有任何问题。
最终解决方案
NetFilter Queues 是用户空间库,提供 API 来处理已被内核包过滤器排队的数据包。
愿意使用此功能的应用程序应动态链接到 netfilter_queue 和 nfnetlink 并包含来自 sysroot-target/usr/include/libnetfilter_queue/ 和
sysroot-目标/usr/include/libnfnetlink/。
需要添加以NFQUEUE为目标的iptables。
NFQUEUE 是一个 iptables 和 ip6tables 目标,它将对数据包的决定委托给用户空间软件。例如,以下规则将要求用户空间程序对所有排队的数据包做出决定。
iptables -A INPUT -j NFQUEUE --queue-num 0
在用户空间中,软件必须使用 libnetfilter_queue api 连接到队列 0(默认队列)并从内核获取消息。然后它必须对数据包作出裁决。
当一个数据包到达一个 NFQUEUE 目标时,它被排入与 --queue-num 选项给出的数字相对应的队列中。数据包队列是一个链表,元素是数据包和元数据(Linux 内核 skb):
- 这是一个固定长度的队列,实现为数据包的链表
- 存储由整数索引的数据包
- 当用户空间对相应的索引整数发出判决时,一个数据包被释放
- 当队列已满时,没有数据包可以入队
- 用户空间可以读取多个数据包并等待给出判决。如果队列未满,则此行为没有影响
- 数据包可以不按顺序判定。用户空间可以读取数据包 1,2,3,4 并按顺序在 4,2,3,1 处判断
- 太慢的判决将导致队列满。然后内核将丢弃传入的数据包,而不是将它们排队。
- 内核和用户空间之间使用的协议是 nfnetlink。这是一个基于消息的协议,不涉及任何共享内存。当数据包入队时,内核将包含数据包数据和相关信息的 nfnetlink 格式化消息发送到套接字。用户空间阅读此消息并发出判决
预碎片逻辑在 AvPreFragApp(新应用程序)和 Security Broker(现有控制器应用程序)中实现。
在安全代理中,一旦建立隧道。在 RAW 表中添加了以下两条规则。
对于 TCP 预分片:
/usr/sbin/iptables -t raw -I OUTPUT 1 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360
上述规则在三向握手期间协商一个适当的 MSS 大小。
可以安全地假设,1360+TCPH+IPH+ESP+IPH
对于 UDP 预分片:
/usr/sbin/iptables -t raw -I OUTPUT 2 -s -p udp -m mark ! --mark 0xbeef0000/0xffff0000 -j NFQUEUE
上述规则将所有 src ip 为 TIA(隧道地址)的 udp 数据包排队,并将不等于 0xbeef0000 标记到 netfilter 队列以供应用程序处理。 AvPreFragApp 将在所有排队的 udp 数据包上标记 0xbeef0000。这样做是为了避免数据包的重复排队。
AvPreFragApp
AvPreFragApp 应用程序使用 netfilter 队列来处理由 NFQUEUE 目标排队的数据包。
如上所述,将具有 TIA 作为 src ip 的 udp 数据包排队的 iptables 规则已添加到安全代理中。此规则在隧道建立时添加,并在使用新 TIA 的隧道反弹时更新。所以所有以 TIA 为源 ip 的数据包都排队等待 AvPreFragApp 处理。
- AvPreFragApp 从 libnetfilter_queue 调用一组 api 来设置队列并将数据包从内核复制到应用程序
- 在创建队列时,传递回调函数地址,一旦数据包排队等待处理,就会调用该地址
- NFQNL_COPY_PACKET 模式需要设置,它将整个数据包从内核复制到应用程序
- 可以使用 netfilter 队列处理程序获取文件描述符。使用recv函数可以得到包缓冲区
- 在处理数据包时,AvPreFragApp 检查数据包的大小。如果数据包大小
- 如果数据包大小 > 1376 且未设置 DF 位,则给出 DROP 判决。这意味着原始数据包被丢弃。但在此之前,数据包将被复制到应用程序缓冲区。现在 AvPreFragApp 在应用程序中进行分段。所有这些片段都被写入带有标记 0xbeef0000 的原始套接字。 sendmsg 用于将数据包写入原始套接字
- 这些预先分段的数据包被加密并封装在内核中。
注意:TIA:隧道内部地址,逻辑 IPSec 接口。