本帖最后由 look_w 于 2018-5-23 15:38 编辑
正在开发哪些项目?Linux 网络团队已经着手这些代码的工作。在 2007 年 11 月 15 日,Ilpo Jarvinen 发布了对 SACK 处理路径的重大修订。在 2008 年 1 月 28 日的合并窗口工作中,这些代码放入 Linus 的 pre-2.6.25 树中。全部系列是 10 个补丁(参见 获取这些内容的链接)。这里我将重点放在感兴趣的三个关键补丁上。
编写这些补丁的目的是在一般情况下改善 SACK 的性能。它们也会对攻击场景有些帮助,但不能把它们看成是本文所评测场景的解决方案。
Ilpo Jarvinen [2]:“我认为我们无法阻止恶意用户找到一些方法绕开典型、合法的用例中的调整和优化。” 第一个变化(参见 中的 “Abstract tp->highest_sack accessing & point to next skb”)是在 SACK 选项只包含比已经选择性应答的数据的序列号高的数据信息时优化缓冲策略。通常来讲,这意味着在未完成的窗口中还存在一个已经报告的漏洞,而新数据在窗口的末端已经被选择性应答。对于常规操作这很常见。这个补丁通过把已缓存的引用从一个序列号转换为指向队列中最高的包(过去已经选择性应答)的指针从而对此情况加以优化。通过这一信息,接下来的补丁(参见 “Rewrite SACK block processing & sack_recv_cache use”)通过缓存的指针作为列表遍历的起点就可以在处理 SACK 时只处理比缓存的值更高的数据,这为遍历列表节省了大量的工作。
遗憾的是,此策略不能对恶意的测试客户机进行优化。来自此客户机的典型应答包括一个 SACK 选项,它指向比前面出现的数据更高的数据,但它也包含指向前一个相邻包的序列。2.6.25 的实现需要从起点遍历重传队列以便找到数据。
后一个补丁包含对将来补丁中不同的 “跳跃式” 队列遍历算法的识别和支持。而这不会对此处提到的测试结果立刻有所帮助 — 因为跳跃遍历仍然通过线性遍历实现 — 它所支持的未来改变将对攻击场景产生重大影响。
这些补丁中包含的一些注释表明将来两个主要的改变正在进行中。第一个可能的未来改变是对未应答的包列表进行修改,因此包列表的组织形式不再是当前的线性列表而是通过索引组织成红-黑树。这允许对 SACK 选项指向的包进行 log(N) 查找。这个改变引入一些索引,允许对大的重传队列中的元素进行随机访问,这个改变对于解决 TCP 协议栈的 find-first 攻击问题很重要。
另一个结构上的改变解决了另一个本文未明确提出的问题。索引结构可以为个别包查找提供良好的性能,但 SACK 选项可能会覆盖包括多个包在内的任意多的字节数。如果恶意客户机发送的选项覆盖窗口中几乎全部的数据,则无法对此加以阻止。这与我关注的 find-first 攻击有所不同。实际上,第一个包可能会是列表中的第一个包,找到它可能非常容易。可是,如果需要对整个队列线性遍历来对 SACK 选项加以处理的话,快速找到感兴趣的包可能意义不大。这里的代码改变是把当前列表重新组织成 2 个列表,一个是已经选择性应答的数据,另一个是未选择性应答的数据。这很有帮助,因为它把搜索区域压缩到以前未选择性应答的数据范围中。称为 DSACK(复制 SACK)的相关规范也会引入一些复杂性,但分割正是我们思考的方向。
最后一个补丁(参见 中的 “non-FACK SACK follows conservative SACK loss recovery”)对拥塞控制语义进行修改以利用 RFC3517 中的 SACK 规则。这些改变允许内核在更多的情况下避免基于超时的恢复场景。这些基于超时的恢复机制要求把发送窗口一直缩减然后随时间逐渐增大到当前带宽延迟产物支持的水平。恢复时间负责中断测试期间发生的激增行为。
评测 2.6.25-pre 准备好这些新代码后,现在开始重新测量使用定制的延迟引入客户机(启用 SACK 选项)的数据点。此时,测试在 2.6.25 开发代码上完成。表中包含了早期的三个数据点便于参考。
表2 服务器使用率评测[td]方法 | 已处理的应答 | SACK 处理中已经扫描的包 | 经历的时间 | CPU 使用率 | 每个应答的取样滴答数 | 每传输 1KB 的取样滴答数 | 基准 | 252,955 | 0 | 1:02 | 22% | 1.72 | 0.56 | 定制,不启用 SACK | 498,275 | 0 | 2:59 | 9% | 1.47 | 1.03 | 定制,启用 SACK | 534,202 | 755,368,500 | 12:47 | 33% | 10.87 | 8.13 | 定制,启用 pre-2.6.25 上的 SACK | 530,879 | 2,768,229,472 | 10:42 | 49% | 13.6 | 10.07 |
此图是 pre-2.6.25 开发内核上启用恶意 SACK 选项时使用定制大窗口客户机的情况下,CPU 随时间变化的使用率:
图 4. pre-2.6.25 内核上启用 SACK 的大窗口定制客户机此前,CPU 图中包含表示启动和停止的周期,现在则是一直占满。虽然内核代码的效率变得更高,但完成测试中同样多的文件传输占用更多的周期。这是一个不合理的结果。
新代码确实非常快,但是在每次测量中它都会长时间独占 CPU 并使用更多的全局 CPU 时间。这些情况的原因中缺少的一环是在新内核中减少了 TCP 基于超时的恢复机制,原因是与 RFC3517 有关的改变。在测试客户的每次运行中,2.6.22 代码平均有 17 次超时。而 2.5.25 代码平均仅为 2 次。超时事件间的空闲上升时间越来越少,把这个结果画成图很引人关注,结果造成停机时间更少。
超时更少意味着发送方要保持一个均值更大的窗口。大窗口对于高延迟链接中的正式吞吐量很重要。这个 TCP 栈的开发版在传输速度方面很有好处,完成时间比已经部署的栈快 2 分钟,原因是它可以保持更大的打开窗口。
可是,这些较大的平均窗口也意味着 SACK 接收代码需要对收到的每个包做很多工作,因为需要扫描的队列包含更多的包。文件传输中已扫描的 27 亿个包(是以前内核版本的 4 倍)以及每 KB 传输需要的 10.07 个取样滴答很精确地说明了需要做多少工作。
更快一些的处理器也无法显著改善这种情况。更快的处理器在同样的时间内会扫描更长的包链,但这样也会造成窗口略微增加,这使得对每个需要处理的新选项的工作量增大。为处理同样数目的 SACK 选项需要占用更多的处理周期数;更快的处理器带来自己的更大开销,而无法完成更多的工作。
结束语恶意手动创建的 SACK 选项对性能的影响非常大,但还不至于上升到通常可实际运用的 DoS 攻击的层次,但其他客户机可以调节自己的节奏,占用服务器但并不将其压制到超时点,这不难想象。
不需要发送大块数据的计算机无需关心这个问题,因为这些计算机从不会填满整个大窗口,而这正是这一问题的前提。尽管选择性应答在高带宽延迟的网络连接上对性能的影响很大,但也可将其作为一个可禁用的选项,这不会牺牲互操作性。把 sysctl 的变量 net.ipv4.tcp_sack设置为 0 即可禁用 TCP 协议栈中的 SACK 功能。
在当前的 Linux 内核开发树中,有一些关于普通 SACK 处理的工作进行得很好。这为将来的开发工作如包列表索引与分割打下了基础,而这些工作会减少一些攻击向量。 |