说人话读论文--实时有状态 TCP 数据包过滤在 IP Filter 中的实现
Real Stateful TCP Packet Filtering in IP Filter 论文精读
资料来源:
更新
1
2024.11.17 初始
导语
OVS 实现用户态 conntrack tcp 状态追踪来源于 Real Stateful TCP Packet Filtering in IP Filter, 这篇 blog 其阅读笔记, 接下来应该还有对 ovs conntrack-tcp.c
的详细分析 ^i2ly
摘要
旧 ip filter 过滤引擎的 3 个问题
- 始终假定通过的数据包一定能到达目的地
([[Real Stateful TCP Packet Filtering in Ip-filter .pdf#page=1&selection=64,0,69,38&color=yellow|Real Stateful TCP Packet Filtering in Ip-filter , p.1]])
The main problem being that IP Filter assumed, when it detected packets traversing through it, that the destination would also see the packets
- 对称的处理了窗口大小
([[Real Stateful TCP Packet Filtering in Ip-filter .pdf#page=1&selection=70,44,72,14&color=yellow|Real Stateful TCP Packet Filtering in Ip-filter , p.1]])
Furthermore, it previously looked at window sizes symmetrically:
- 数据有效 (seq) 范围,错误的使用了当前窗口大小作为上下界
- 已确认序号 1000, window 500: 正确上下界
[1000, 1000+500]
, 这里错误的取了下界:[1000 - 500, 1000+500]
- 没有理由这样做.
- 已确认序号 1000, window 500: 正确上下界
- 始终使用最后一次看到的 window 尺寸处理 之后的 新数据
([[Real Stateful TCP Packet Filtering in Ip-filter .pdf#page=1&selection=79,22,81,35&color=yellow|Real Stateful TCP Packet Filtering in Ip-filter , p.1]])
Additionally, the window size taken as the upper bound for new data was the last window advertisement seen.
- 例如最后一个 pkt 信息是: 窗口从 5000 -> 300
- 但是在窗口变化之前的 pkt 有丢失 -> 重传, 此时 在
[300, 5000]
范围的重传包可能被判定为非法
新 ip filter 引擎, 解决了上述问题,主要设计标准是 只接收验证后的信息从不做假设.
([[Real Stateful TCP Packet Filtering in Ip-filter .pdf#page=1&selection=85,7,87,44&color=yellow|Real Stateful TCP Packet Filtering in Ip-filter , p.1]])
he main design criteria was to never assume anything, but to only take information for granted when it can be proven to be correct.
但是为了实现设计和保持向前兼容, 在一些标准上会放宽, 论文中有详细讨论;
旧过滤引擎
旧的 IP Filter 的状态过滤器, 精简代码如下:
1 | /* |
每个连接有一个跟踪条目 is
, 从源创建记录有 4 个数据:
- is_seq: c-s 方向最后一个 seq OR s-c 反向最后一个 ack
- is_ack: c-s 方向最后一个 ack OR s-c 方向的最后一个 seq
- is_swin: c-s 方向的 win
- is_dwin: s-c 方向的 win
以 c-s 方向看
- 计算当前 pkt 的 seq ack 与 上一个 c-s 方向 pkt 的 seq ack 的差值 seqskew ackskew, 取绝对值.
- seqskew 比较上次有效的 c-s 方向的 server 的接收窗口, ackskew 比较 s-c 方向的 client 的接收窗口. 两者同时成立则 pkt 合法.
([[Real Stateful TCP Packet Filtering in Ip-filter .pdf#page=5&selection=294,0,310,12&color=yellow|Real Stateful TCP Packet Filtering in Ip-filter , p.5]])
Both examples show that the state engine is not coping well with out of order packets and packet loss.
旧引擎无法很好的处理乱序和丢包
旧过滤引擎最大问题: 当出现 tcp 乱序/丢包时, filter 的状态不再与 client server 同步,一步错步步错.
问题示例
来看两个例子, 前提条件
- A->B 已经建立连接
- 不考虑分片等情况
Example 1
B→A 确认 (A→B, 0:1000) , win 1048 ack 1000 发生了 网络延迟, 其原本应该是 第 3 个包, 延迟到了第 7 个包
From | Content | Nr |
---|---|---|
B→A | win 2048 ack 0 | 1 |
A→B | 0:1000 | 2 |
B→A | win 1048 ack 1000 | 7 |
A→B | 1000:2000 | 3 |
B→A | win 2048 ack 2000 | 4 |
A→B | 2000:3000 | 5 |
B→A | win 2048 ack 3000 | 6 |
旧过滤引擎中状态的变化 (状态条目值是在 数据包 判定后,更新 is_seq
is_ack
之前的值)
#todo
nr | state entry | packet content | code | ||||||
---|---|---|---|---|---|---|---|---|---|
is_seq | is_ack | is_swin | is_dwin | seq | ack | win | seqskew | ackskew | |
1 | n/a | n/a | n/a | n/a | 0 | 2048 | - | - | |
2 | 0 | 2048 | 0 | 0 | |||||
3 | 0 | 2048 | 1000 | 1000 | |||||
4 | 1000 | 2048 | 2000 | 2048 | 999 | ||||
5 | 2000 | 2048 | 2000 | 0 | |||||
6 | 2000 | 2048 | 3000 | 2048 | 999 | ||||
7 | 3000 | 2048 | 1000 | 1048 | 2001 |
此时发送 8a 或 8b(重传原来的 6 号 pkt), 8a 8b 都是合法的数据包.
A→B | 3000:4000 | 8a |
---|---|---|
B→A | win 2048 ack 3000 | 8b |
nr | state entry | packet content | code | ||||||
---|---|---|---|---|---|---|---|---|---|
is_seq | is_ack | is_swin | is_dwin | seq | ack | win | seqskew | ackskew | |
8a | 1000 | 1048 | 3000 | 2000 | |||||
8b | 1000 | 1048 | 3000 | 2048 | 1999 |
8a 8b 对应的状态表:
- 8a 的 seqskew = 2000 大于 is_dwin 的 1048,非法.
- 8b 的 seqskew = 1999 大于 1048 , 非法
Example2
假定第 2 3 个 ack 出现了网络延迟
From | Content | Nr |
---|---|---|
A→B | 0:1000 | 1 |
B→A | win 4000 ack 1000 | 2 |
A→B | 1000:2000 | 3 |
A→B | 2000:3000 | 4 |
A→B | 3000:4000 | 5 |
A→B | 4000:5000 | 6 |
B→A | win 2000 ack 5000 | 8 |
B→A | win 4000 ack 5000 | 7 |
目前为止所有的包都会通过过滤器 ( seqskew < is_dwin ) |
nr | state entry | packet content | code seqskew | |||
---|---|---|---|---|---|---|
is_seq | is_dwin | seq | ack | win | ||
1 | not relevant | 0 | - | |||
2 | 0 | 1000 | 4000 | 999 | ||
3 | 1000 | 4000 | 1000 | 0 | ||
4 | 1000 | 4000 | 2000 | 1000 | ||
5 | 2000 | 4000 | 3000 | 1000 | ||
6 | 3000 | 4000 | 4000 | 1000 | ||
7 | 4000 | 4000 | 5000 | 4000 | 999 | |
8 | 5000 | 4000 | 5000 | 2000 | 1 |
A 发现 [1000:2000]
还未确认, 触发重传, 很不幸 4000 > 2000 重传包被阻止
A→B | 1000:2000 | 9 |
nr | state entry | packet content | code seqskew | |||
---|---|---|---|---|---|---|
is_seq | is_dwin | seq | ack | win | ||
9 | 5000 | 2000 | 1000 | 4000 |
新过滤引擎
所谓过滤过程/有状态防火墙 等等, 可以简化思考:
- 输入: pkt 的 方向 + seq ack win
- 输出: pkt 合法 or 非法
- pkt 合法条件: seq 在窗口内 AND ack 在窗口内
- 难点在于: 如何确认 seq / ack 合法的上下界, 不至于过于宽泛/紧张 导致错判 漏判.
- 有时理论可能不得不让步于实现
([[Real Stateful TCP Packet Filtering in Ip-filter .pdf#page=6&selection=4,0,5,38&color=yellow|Real Stateful TCP Packet Filtering in Ip-filter , p.6]])
Never assume anything: the state administration should only be based on facts.
永远不能假定条件, 状态管理只基于事实
- 旧的状态引擎就是假定所有包都能送达才出现了 bug.
有效的数据边界 (seq)
对于 A->B 方向 seq 的限制
上界
A -> B 发送了一个数据包 区间是 [s, s+n)
, 那么按照 tcp 协议
代入 s+n
同时所有 A-B 的数据包都会经过 F 过滤系统
对于公式 (Ia) 只有一种例外, 但 B 通告接收窗口 win = 0
, 此时 A 进入窗口探测模式, A 向 B 发送第一个未确认的数据, 这是唯一允许发送超出 B 接收窗口边界的情况.[1]
BSD 系统上总是使用 1 字节的 pkt 进行 0 窗口探测.那么公式 (Ia) -> (I)
下界
理论下界 s 的最小值
公式 (I) (i) 并列,代入 s 的最大值 可以得到 s+n
的最大值的约束 公式 (ii)
公式 (i) (ii)
有效的 确认边界 (ack)
对于 A->B 方向 ack 值的限制
上界
非常明确 ack 不能确认发送方没有发送的数据, 假设 A 是接收, B 发送那么 公式 (III)
下界
首先不能以 最后收到的 ack 作为下限:
- tcp 乱序抵达, ack 10 和 ack 11 ^gqaj
- ack 11 先到, ack 10 就会被误判非法
那么放宽下限制呢?
- 规则 F 看到的 A 最大的 ack 作为下界, 出现 tcp 乱序 时,仍然会有 ack 被阻止.
- 另一条, 数据有效就忽略 ack 检查, 完全没必要.
规则需要进一步放宽 -> 取到一个最小的下界 = 公式 (IV)
- A->B 方向的 ack 最小值必须大于 F 看到的 B 发送数据包的
s+n
-MAXACKWINDOW
, MAXACKWINDOW
是一个略大于 tcp 最大窗口 (66000) 的值.
这样的好处:
- 不会错误将 乱序 ack 判定为非法
- F 判定 ack 的窗口可以相当大. 当然这样会造成 接收到过期的 ack 的问题, 但是过期 ack 不会对 tcp 连接造成任何影响.
- 已经确认到 ack 100 了, 突然一个因为网络拥堵来了个 ack10, 到就到吧, 不会产生任何影响.
总结
实现
1 | struct tcpstate { |
初始化
显然 说人话读论文–实时有状态 TCP 数据包过滤在 IP Filter 中的实现#总结 是在 tcp 传输建立后的中间过程成立, tcp 建立连接时还需要特殊处理:
([[Real Stateful TCP Packet Filtering in Ip-filter .pdf#page=9&selection=0,9,2,31&color=yellow|Real Stateful TCP Packet Filtering in Ip-filter , p.9]])
The possibilities for the next packet in this session are retransmission of the SYN and the receiver sending a SYN/ACK.
- 首个 syn 包, syn 包亦可能发生重传.
- 首个 syn-ack 包, 其也可能发生重传
为初始化给出特定值, 使其符合边界条件 I II III IV.
重传 Syn
1 | ts_data[0].td_end = SEQ + 1 |
此时发生了 syn 重传
1 | // 对于 I II 重传 syn 自然符合边界条件 |
接收方 Syn-ack
1 | ts_data[1].td_end = SEQ + 1 |
1 | // s+n = SEQ + 1 |
([[Real Stateful TCP Packet Filtering in Ip-filter .pdf#page=9&selection=139,1,142,39&color=yellow|Real Stateful TCP Packet Filtering in Ip-filter , p.9]])
he above analysis is correct for connections that enter the state table when being setup.
上述分析仅在这个连接开始到结束 filter 全程没有重启. 每一步 filter 连接表状态都是正确.
filter 重启,历史状态都没了, 完蛋了. 如果 filter 重启且需要保留已有会话? 需要以某种方式保留历史状态
- 始终记录状态,重启就恢复.
- 先让其通过, 然后随着连接一步步重建历史状态.
([[Real Stateful TCP Packet Filtering in Ip-filter .pdf#page=10&selection=0,0,13,10&color=yellow|Real Stateful TCP Packet Filtering in Ip-filter , p.10]])
Neither of these methods have been implemented yet
两者都没有实现 2333
^bx7v
代码
1 | // syn 包 |
未来工作
([[Real Stateful TCP Packet Filtering in Ip-filter .pdf#page=13&selection=347,32,359,41&color=yellow|Real Stateful TCP Packet Filtering in Ip-filter , p.13]])
For connections involving the TCP window scale option [RFC1323], the results are thus incorrect
目前尚未支持 tcp 窗口缩放
- 这一点在 ovs 中已经实现了
([[Real Stateful TCP Packet Filtering in Ip-filter .pdf#page=13&selection=364,6,367,23&color=yellow|Real Stateful TCP Packet Filtering in Ip-filter , p.13]])
also the timestamp option is to be taken into account so that the state engine will be able to handle wrapped sequence numbers within high speed connections.
需要考虑支持 tcp 时间戳
([[Real Stateful TCP Packet Filtering in Ip-filter .pdf#page=14&selection=0,0,2,14&color=yellow|Real Stateful TCP Packet Filtering in Ip-filter , p.14]])
Of course, sessions that enter the state table, when they are already established, should be handled better
更好的处理 filter 重启情况
([[Real Stateful TCP Packet Filtering in Ip-filter .pdf#page=14&selection=4,13,8,11&color=yellow|Real Stateful TCP Packet Filtering in Ip-filter , p.14]])
the workarounds in the implementation for dealing with fragments should be eliminated.
处理 IP 分片
https://www.rfc-editor.org/rfc/rfc793#section-1.5 ↩︎