解析Snort的TCP流重组

关于TCP重组

TCP报文段在网络中传输常见的问题包括,因丢包造成的重传,因网络情况造成的报文乱序,为增加性能重传更大的报文段造成报文重叠,等等,通常情况下,内核TCP协议栈会解决这些问题,保证应用层数据的可靠有序。但对于运行在非应用层模块例如传输层以下或不经过内核的功能模块,就应该考虑是否需要解决TCP重传、重叠、乱序等问题,例如这些功能模块希望http协议被正常解析,那么必然要解决上述问题。

网上关于TCP重组策略的内容较少,所以将snort重组策略即Sturges/Novak模型进行翻译,并将snort重组流程(基于snort2.9.0版本)简单说明。

关于snort和stream

snort为C语言开发的开源入侵检测系统,进行实时数据流量分析和记录,协议分析,对数据包内容进行搜索匹配,检测不同攻击并实时报警。其模块包括数据包嗅探模块、预处理模块、检测模块、报警日志模块等,而stream模块在预处理阶段提供TCP重组功能,保证后续检测功能的可靠性,目前最新版snort的重组模块已经到了stream6。stream提供的TCP重组实现,完全遵循Steve Sturges和Judy Novak文章中提出的Sturges/Novak模型。

Sturges/Novak模型

在《Target-Based Fragmentation Reassembly》论文中, Steve Sturges和Judy Novak提出一种模型用于处理IP分片重组的问题,该模型IP分片覆盖的所有可能性。该模型对于TCP分段同样适用,该模型展示了许多现代操作系统对于TCP重组的解决办法。
解析Snort的TCP流重组_第1张图片
解析Snort的TCP流重组_第2张图片
现定义原始报文为在时序上先于后续报文到来,后续报文为在时序上后于原始报文到来,并且与原始报文存在覆盖。这里的原始报文和后续报文只是指在时序上的到来顺序,和TCP序列号无关。

来看表1,第一列的每一行列举了TCP分段重叠存在的不同情况,第二列展示了这种重叠情况在图一中的具体表现。例如,第一种情况为“Starts before, ends after”,并且包含了原始报文3.3和后续报文8,在图一中,原始报文3.3的起始序列号为15,结束序列号为17,而后续报文8的起始序列号为16,并且只有一字节大小,那么原始报文的起始位置在后续报文8之前,而结束位置在8之后。

从标签1到3,从标签3.1到3.6,从标签4到11,图一通过被17个颜色标注的TCP报文段,对Sturges/Novak模型进行解释,报文段的标签号代表着报文的到来顺序,原始报文1, 2, 3, 3.1, 3.2, 3.3, 3.4, 3.5和3.6(序列号从0到22)在第一行,它们在时序上先到来。后续报文段4-11(序列号从1到23)随后到来,并且和原始报文存在部分重叠。

分段1拥有起始序列号0,后续分段的序列号均基于它计算,那么以此类推,分段2的其实序列号为4,分段3的起始序列号为6。接收主机通过这些序列号,在TCP重组期间对报文段进行排序。

分段可能部分重叠也可能全部重叠。在这个模型中,分段5和分段3完全重叠,两者的起始序列号均是6,并且长度为3字节,区别只是这3字节的内容不同,而最终哪一个分段被选择完全取决于接收主机操作系统的特性。更多的报文段是部分重叠,也就是说并不是起始和结束序列号都相同。

该模型也包含了后续报文段和多个原始报文重叠的情况。报文段4较为特殊,因为它与两个原始报文存在重叠,其起始序列号为1,所以它的起始序列号大于原始报文1的起始序列号0(starts after),而小于原始报文2的其实序列号4(starts before)。

接收端操作系统将根据报文时序和重叠情况,在原始报文和后续报文间进行选择。时序即表示该报文为原始报文或后续报文,而时序,正如之前说的那样,起始和结尾都存在before、after和same的情况。例如,Windows倾向于选择原始报文,除了后续报文起始序列号在原始报文之前的情况。

在讨论重组策略前,有一个重要情况需要说明,一般的操作系统更倾向于选择完整的报文段而非重叠的报文段,换句话说,就是不会从一个报文段获取一些字节而从另一个报文段获取其他部分。但这也有特例,在报文段3.1和6之间,如果选择了3.1,那么TCP序列号就会产生一个
空洞,如果接收主机倾向于报文段3.1,那么它也需要同时选择报文段6的第一和第三字节以填补空洞。第二个例外是报文段4和两个原始报文(报文段1和2)存在重叠,接收端可能更倾向于从报文段4中获取部分字节,而不是分别从报文段1和2中获取。分段4是该模型中唯一存在的和两个报文段存在重叠的情况。

下面来列举几种不同的重组策略:

  • Windows/BSD倾向于原始报文,除了后续报文起始序列号在原始报文前这种情况。
  • First/Windows Vista倾向于原始报文段。
  • Linux倾向于原始报文,除了后续报文起始序列号在原始报文之前,或后续报文起始序列号相同但终止序列号在原始报文后的情况。
  • Solaris倾向于后续报文,除了原始报文终止序列号在后续报文之后,或起始序列号在原始序列号之前并且终止序列号相同或在其之后。
  • Linux-old倾向于后续报文,除了原始报文起始序列号在后续报文之前,或原始报文起始序列号和后续报文相同但终止序列号在其之后。

综上所述,图一中TCP分段的重组情况如下:

  • Windows/BSD: <1><1><1><4><4><2><3><3><3><6><6><6><7><7><7><3.3><3.3><3.3><3.4><3.4><3.5><3.5><3.6><11>
  • First/Windows Vista: <1><1><1><4><2><2><3><3><3><6><3.1><6><7><3.2><3.2><3.3><3.3><3.3><3.4><3.4><3.5><3.5><3.6><11>
  • Linux: <1><1><1><4><4><2><3><3><3><6><6><6><7><7><7><3.3><3.3><3.3><3.4><3.4><3.5><3.5><11><11>
  • Solaris: <1><4><4><4><2><2><5><5><5><6><6><6><7><7><7><3.3><3.3><3.3><3.4><3.4><3.5><3.5><11><11>
  • Linux-old: <1><1><1><4><4><2><5><5><5><6><6><6><7><7><7><3.3><3.3><3.3><3.4><3.4><3.5><3.5><11><11>

为了更好的说明重组策略,以Windows/BSD为例讲解重组的细节
解析Snort的TCP流重组_第3张图片

Snort流重组流程(Stream)

stream的流重组分为重组和排出(flush)两个阶段。重组阶段,根据重组策略将报文段裁剪后插入StreamTracker->seglist链表中;排出阶段,根据flush策略对seglist链表中的报文进行排出。下面以BSD为例,将重组过程中最复杂的部分即StreamQueue做一说明。
解析Snort的TCP流重组_第4张图片

  1. 如果新插入报文段(new)的起始序列号,大于链表尾结点的终止序列号,那么直接将new插入链表结尾。
  2. 分别计算new的起始序列号距离首节点起始序列号和尾结点起始序列号的距离。
  3. 根据(2)中结果,从更近的一端开始查找起始序列号和new的起始序列号相邻的左右节点位置。
  4. 这里需要注意,new的左侧最多只有一个和它产生重叠的分段,但右侧可以有多个和new重叠的报文段,所以这里需要循环遍历,直到不再存在右侧分段或右侧覆盖。

你可能感兴趣的:(传输层)