TCP截包分段重组的设计
功能
-------
TCP报文段会有失序,重复,对于截包还会有丢包。
在进行上层协议分析之前,需要对TCP报文进行重组。
分段重组是对TCP数据进行重新排序,丢序重复的数据,并指示数据的丢失。
输入
-------
重组只处理单向数据流,所以一个TCP连接需要分别处理两个方向的数据流。
重组的数据假定已经检查了检验和。
截包重组忽略TCP窗口的大小。
简单地讲,重组仅关心TCP序号,应答号,及数据,还有几个特殊的TCP标志。
特殊的TCP标志有:SYN, ACK, RST, FIN,需要特别处理。
重组需要一个启始序号,从SYN包获取。ACK标志表示应答序号有效。
RST,FIN将设置流的结束标志,待所有数据接收完成,流才关闭。
TCP连接的关闭只能从双向流的关闭判断。
输出
-------
重组不进行上层协议的处理,因为上层协议处理需要结合双向数据流,甚至多个TCP流。
只进行缓存,对数据仅仅是压入/弹出。弹出意味着缓存数据在处理完之后进行删除。
输出为重组后的数据分段。弹出数据不必进行分段合并,可以保持原有的分段。
如果严格地实现TCP重组,应该在数据得到确认后(Acknowledged)再弹出。
但在截包数据处理中,并不是这样,只要序号连续就直接处理数据,而不进行缓存。
这是出于以下考虑:
* 等待确认就必须缓存,而多数TCP包是连续的,不需要排序,直接处理可大大提高效率。
* 数据在稍后肯定会有应答确认,只有TCP中断的情况下才有可能没有确认,这样处理影响不大。
* 对于可能的单向数据流截包分析,只能忽略确认号。
(在这种情况下,丢包会造成大量分段的缓存,需要限制分段缓存数。)
接口定义
----------
* 缓存一个TCP段。
如果这个TCP段是失序的,就要进行缓存。
大多数情况下,没有失序,就不必缓存,可以直接进行上层协议的分析。
返回布尔值指示了该数据是否进行了缓存。
* 弹出下一个TCP段。
接受一个TCP段后,或确认一个序号后,
都有可能有多个TCP段数据可以弹出进行上层协议分析。
弹出后需要删除,所以弹出操作可能是组合操作,
如判断是否有数据,取数据,删除数据,下一个数据等等。
还有可能弹出一个丢失的TCP段,即指示某段数据已丢失。
* 确认一个序号。
对未到达序号进行确认表示有数据丢失。
丢包会造成后序的TCP段都进入缓存,确认丢包可跳过丢失的序号。
* 开始,结束,重置一个序号。
对应SYN/FIN/RST标志。
* 是否已关闭。
一个TCP的关闭 = 双向数据流都已关闭。
单向数据流的关闭,除了看是否接收到FIN标志,
还要看FIN标志序号前的数据是否已全部收到,即FIN包有可能超前。
* 强制结束。在TCP结束包丢包的时候,需要强制结束一个TCP连接。
强制结束一个数据流,会造成所有缓存的数据弹出。
代码示例:
class CTcpSegments
{
public:
void Syn(u_int32_t seq);
void Ack(u_int32_t ack);
void Rst(u_int32_t seq);
void Fin(u_int32_t seq);
bool Push(const u_char * data,
unsigned int len,
u_int32_t seq);
// Maybe several methods:CanPop(), GetTop(), DelTop(), Next()...
const TcpSegment_t & Pop();
void ForceFin(); // ack all the buffered and set fin
bool IsClosed() const;
}
使用示例
-----------
假设有一个CTcpConnection对象,处理一个IP:Port地址对上的TCP流。
CTcpConnection有两个CTcpSegments对象,分别重组客户端和服务端的TCP数据流。
class CTcpConnection
{
...
CTcpSegments m_clt, m_svr;
}
例如对于一个客户端发往服务端的TCP包数据,则如下处理:
1. 处理被应答的服务端数据
m_svr.Acknowledge(nAck);
while (m_svr.CanPop())
DealSvrData(m_svr.Pop());
2. 处理当前TCP包的数据
m_clt.Push(...);
while (m_clt.CanPop())
DealCltData(m_clt.Pop());
3. 处理FIN/RST标志
if (TCP_FIN & cTcpFlags)
m_clt.Fin(nSeq);
if (TCP_RST & cTcpFlags)
m_clt.Rst(nSeq);