xplico TCP流重组算法

      对xplico的研究,断断续续,之前有人在github上咨询我问题,只简单的解答了一些粗浅的问题,下面主要针对TCP流重组。下面为xplico一条流重组的关键数据结构(没包含流表,所以不考虑流表的设计):

包:

struct seq {
    packet *pkt;            /* packet 包*/
    unsigned long seq;      /* seq order, 当前包序号 */
    unsigned long nxt_seq;  /* next in seq order, 在队列中下一个序列号*/
    struct seq *next;       /* next in seq order , 下一个序列号结构(即数据包)*/
    bool ack;               /* acknowled,序号得到了确认 */
    bool cng;               /* time sequence ,时间序列*/
};

链表:

/** data to order a tcp stream */
typedef struct _order order;
struct _order {
    bool port_diff;       /* different src and dst port  源端口和目的端口不同 是一个完整的双向流*/
    unsigned short port;  /* source port  作为一个源端口,作为客户端,一般一条流第一个syn能确定流 */
    bool ipv6;            /* ipv6 or ipv4 */
    ftval ip;             /* ip source */
#if SNIFFER_EVASION
    bool first;           /* first packet , 第一个包*/
    bool hl_s_on;         /* enabled ttl check */
    ftval ttl_hl_s;       /* time to live or hop limit from src to dst*/
    ftval id_s1;          /* ip identification src */
    ftval id_s2;          /* ip identification src */
    ftval id_d1;          /* ip identification dst */
    ftval id_d2;          /* ip identification dst */
#endif
    bool src_put;         /* queue in put ,是否将包放入到队列*/
    unsigned long seq_s;  /* last seq source sent to flow ,客户端方向上一个包序号*/
    unsigned long seq_d;  /* last seq destination sent to flow ,服务端方向上一个包序号*/
    bool mono;            /* stream monodirectional , 流是单向的*/
    struct seq *src;      /* source packet list ordered ,客户端方向已排序链表,但是存在空洞等,数据包插入到链表时已经根据seq进行了排序,当某种条件满足时,将包发送到流表中*/
struct seq *dst;      /* destination packet list ordered ,服务端方向已排序链表,但是存在空洞等,数据包插入到链表时已经根据seq进行了排序,当某种条件满足时,将包发送到流表中*/
/*  每个方向排好序的数据包链表,且链表插入方法是头插法,后面来的节点在前面 */
    unsigned long num;    /* number of packet in queues,在待处理队列中的包数目*/
    unsigned long fin_s;  /* seq fin source , 客户端 fin标识序列号*/
    unsigned long fin_d;  /* seq fin destination,服务端 fin标识序列号*/
    bool rst;             /* reset  ,是否收到rst包,收到rst包,代表此条流结束,清空相关结构*/
    struct seq *last_src; /* last in src queue inserted  ,最后插入到队列的客户端数据包*/
    struct seq *last_dst; /* last in dst queue inserted  ,最后插入到队列的服务端数据包*/
    bool lins_src;        /* last in the queue inserd type  ,最后插入到队列类型,是客户端方向,还是服务端方向*/
    pstack_f *stk_s;      /* stack frame src , 客户端的协议栈结构*/
    pstack_f *stk_d;      /* stack frame dst ,服务端的协议栈结构*/
    packet *ack_d;        /* last dst ack packet,服务端最后一个ack包,记录确认位置 */
    packet *ack_s;        /* last src ack packet ,客户端最后一个ack包,记录确认位置*/
};

封包序号(Sequence Number) 4字节:

由于 TCP 封包必须要带入 IP 封包当中,所以如果 TCP 数据太大时(大于 IP 封包的容许程度),就得要进行分段。这个 Sequence Number 就是记录每个封包的序号,可以让收受端重新将 TCP 的数据组合起来。

序号字段的值则指的是本报文段所发送的数据的第一个字节的序号。

 

确认号(Acknowledge Number) 4字节:

为了确认主机端确实有收到我们 client 端所送出的封包数据,我们 client 端当然希望能够收到主机方面的响应,那就是这个 Acknowledge Number 的用途了。当 client 端收到这个确认码时,就能够确定之前传递的封包已经被正确的收下了。这个号是期望收到对方的下一个报文段的数据的第一个字节的序号。

 

TCP流:

Tcp三次握手后,流才会完整的建立,否则会造成hash表浪费;

TCP流分为双向:假如入访问一个web服务,分为客户端和服务端两个方向。客户端到服务端方向的流,包的顺序可以通过序号(seq)来进行排序,确认序号是用来确认我本端用户数据被对端正确接收。服务端情况与之类似。

 

流重组异常情况如下:

包覆盖、包重传、丢包(空洞处理)、流乱序(考虑流调整)。

 

流处理结束策略:

是否遇到fin/rst/等标志将整条流处理?

是否尽快的发出,如果明显是第一个包,没有空洞,没乱序,直接将数据包发送到流处理队列?

 

1正常流处理流程:

         看下面此条流没有任何异常情况,通过每个包的处理方式进行:

        

 

客户端:

         包1(SYN = 1, Seq= 0): 握手包发起;

         包3(ACK = 1, Seq = 1 , Ack = 1): 客户端确认服务端,收到可以建立;

         包4(ACK = 1,PUSH = 1, Seq = 1 , Ack = 1, Len = 201):客户发送数据包,以四元组建立hash,认为这个方向为客户端流,将数据包插入src待重组链表中。记录序号,以及根据len+seq= 202算出下一个序号;

包8(ACK = 1, Seq= 202, Ack = 2881,len = 0):客户端发确认给客户端,告诉它已经手动Ack个数据;

包11(ACK = 1, Seq= 202, Ack = 5761,len = 0):客户端发确认给客户端,告诉它已经手动Ack个数据;

包13(ACK = 1, Seq= 202, Ack = 6890,len = 0):客户端发确认给客户端,告诉它已经手动Ack个数据;


服务端:

         包2(SYN = 1 , ACK= 1, Seq = 0 , Ack = 1): 服务端确认握手包,可以建立连接;

         包5(ACK = 1, Seq= 1, Ack = 202):服务端确认收到客户端;

         包6(ACK = 1, Seq = 1, Ack = 202, len = 1440):服务器端给客户端发送数据;将数据包插入dst待重组链表中。记录序号,以及根据len+seq= 1441算出下一个序号;

包7(ACK = 1, Seq = 1441, Ack = 202, len = 1440):服务器端给客户端发送数据;将数据包插入dst待重组链表中。记录序号,以及根据len+seq= 2881算出下一个序号;

包9(ACK = 1, Seq = 2881, Ack = 202, len = 1440):服务器端给客户端发送数据;将数据包插入dst待重组链表中。记录序号,以及根据len+seq= 4321算出下一个序号;

包10(ACK = 1, Seq = 4321, Ack = 202, len = 1440):服务器端给客户端发送数据;将数据包插入dst待重组链表中。记录序号,以及根据len+seq= 5761算出下一个序号;

包12(ACK = 1, Seq = 5761, Ack = 202, len = 1129):服务器端给客户端发送数据;将数据包插入dst待重组链表中。记录序号,以及根据len+seq= 6890算出下一个序号;

 

2丢包:

        

客户端:

         包1(SYN = 1, Seq= 0): 握手包发起;

         包3(ACK = 1, Seq = 1 , Ack = 1): 客户端确认服务端,收到可以建立;

         包4(ACK = 1,PUSH = 1, Seq = 1 , Ack = 1, Len = 201):客户发送数据包,以四元组建立hash,认为这个方向为客户端流,将数据包插入src待重组链表中。记录序号,以及根据len+seq= 202算出下一个序号;

包8(ACK = 1, Seq= 202, Ack = 2881,len = 0):客户端发确认给客户端,告诉它已经手动Ack个数据;

包11(ACK = 1, Seq= 202, Ack = 5761,len = 0):客户端发确认给客户端,告诉它已经手动Ack个数据;

包13(ACK = 1, Seq= 202, Ack = 6890,len = 0):客户端发确认给客户端,告诉它已经手动Ack个数据;

 

服务端:

         包2(SYN = 1 , ACK= 1, Seq = 0 , Ack = 1): 服务端确认握手包,可以建立连接;

         包5(ACK = 1, Seq= 1, Ack = 202):服务端确认收到客户端;

         包6(ACK = 1, Seq = 1, Ack = 202, len = 1440):服务器端给客户端发送数据;将数据包插入dst待重组链表中。记录序号,以及根据len+seq= 1441算出下一个序号;

包7(ACK = 1, Seq = 1441, Ack = 202, len = 1440):服务器端给客户端发送数据;将数据包插入dst待重组链表中。记录序号,以及根据len+seq= 2881算出下一个序号;

空洞大小:4321 – 2881 = 1440 空洞大小;

包10(ACK = 1, Seq = 4321, Ack = 202, len = 1440):服务器端给客户端发送数据;将数据包插入dst待重组链表中。记录序号,以及根据len+seq= 5761算出下一个序号;

包12(ACK = 1, Seq = 5761, Ack = 202, len = 1129):服务器端给客户端发送数据;将数据包插入dst待重组链表中。记录序号,以及根据len+seq= 6890算出下一个序号;

 

Xplico 空洞处理:TCP_SOFT_ACK: 如果出现空洞,将空洞处也提交;

排序过程,如果发现已经有序就把包放入;

处理空洞的方式:

a)        发现空洞,只记录序号,然后直接伪造一个空数据包,送入处理队列中(TCP_SOFT_ACK);

b)        等待数据包的到来,得到明确的确认;如果某个时间内等不到,就超时删除流;

 

3覆盖的处理

覆盖有几种情况,1种包重传,一种覆盖部分(前、后)等多种情况;

3.1包覆盖情况


客户端:

         包1(SYN = 1, Seq= 0): 握手包发起;

         包3(ACK = 1, Seq = 1 , Ack = 1): 客户端确认服务端,收到可以建立;

         包4(ACK = 1,PUSH = 1, Seq = 1 , Ack = 1, Len = 201):客户发送数据包,以四元组建立hash,认为这个方向为客户端流,将数据包插入src待重组链表中。记录序号,以及根据len+seq= 202算出下一个序号;

包8(ACK = 1, Seq= 202, Ack = 2881,len = 0):客户端发确认给客户端,告诉它已经手动Ack个数据;

包11(ACK = 1, Seq= 202, Ack = 5761,len = 0):客户端发确认给客户端,告诉它已经手动Ack个数据;

包13(ACK = 1, Seq= 202, Ack = 6890,len = 0):客户端发确认给客户端,告诉它已经手动Ack个数据;

 

服务端:

         包2(SYN = 1 , ACK= 1, Seq = 0 , Ack = 1): 服务端确认握手包,可以建立连接;

         包5(ACK = 1, Seq= 1, Ack = 202):服务端确认收到客户端;

         包6(ACK = 1, Seq = 1, Ack = 202, len = 1440):服务器端给客户端发送数据;将数据包插入dst待重组链表中。记录序号,以及根据len+seq= 1441算出下一个序号;

包7(ACK = 1, Seq = 1441, Ack = 202, len = 1440):服务器端给客户端发送数据;将数据包插入dst待重组链表中。记录序号,以及根据len+seq= 2881算出下一个序号;

包9(ACK = 1, Seq = 2881, Ack = 202, len = 1440):服务器端给客户端发送数据;将数据包插入dst待重组链表中。记录序号,以及根据len+seq= 4321算出下一个序号;

重复:发现seq = 2881 但是预期的是4321 ,那么 4321 – 2881 = len = 1440刚好一个包重复,丢弃。

包9(ACK = 1, Seq = 2881, Ack = 202, len = 1440):服务器端给客户端发送数据;将数据包插入dst待重组链表中。记录序号,以及根据len+seq= 4321算出下一个序号;

包10(ACK = 1, Seq = 4321, Ack = 202, len = 1440):服务器端给客户端发送数据;将数据包插入dst待重组链表中。记录序号,以及根据len+seq= 5761算出下一个序号;

包12(ACK = 1, Seq = 5761, Ack = 202, len = 1129):服务器端给客户端发送数据;将数据包插入dst待重组链表中。记录序号,以及根据len+seq= 6890算出下一个序号;

 

3.2其他重复情况:

         就要去除重复的部分,保留不重复的部分,更新状态;

 

4、乱序

         根据seq进行调序

 

一个方向上的包,把他送到流表中的条件是:

Client方向:

         If  (根据客户端最后一个包计算出的下一个包序号(seq+ len ) == 当前数据包的序号)

                   放入流表中

Else

         放入到待排序队列中(做空洞,排序,覆盖处理)

 

服务端方向:

         If  (根据服务端最后一个包计算出的下一个包序号(seq+ len ) == 当前数据包的序号)

                   放入流表中

Else

         放入到待排序队列中(做空洞,排序,覆盖处理)

        

 

一条流分两个方向,可能就要考虑组成一条整流时需要对插,或者就提取服务端和客户端然后找这样的对应关系;

场景1:完整的一条流的处理过程

如果是按序到达,则可以利用常规算法,不用处理异常;

If  (根据最后一个包计算出的下一个包序号(seq+ len ) == 当前数据包的序号)

                   放入流表中,通知相关进程处理

Else

         放入到待排序队列中(做空洞,排序,覆盖处理)

 

场景2:完整的一条流,但是存在乱序的情况

 

排序

 

场景3:不完整的流,但是存在丢包的情况

a.      中间某个包丢了

b.      Syn包丢了

c.      Fin包丢了

d.      中间丢了几个包

 

 

场景4:完整的一条流,但是存在包覆盖情况

a.      全部覆盖

b.      覆盖前面部分

c.      覆盖后面一部分

d.      两端覆盖,中间没覆盖

 

场景5:完整的一条流,但是存在重传的情况

a.      重传syn

 

 

场景6:完整的一条流,rst包处理、urg包处理

 

 就说这么多。。。。。

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值