对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包处理
就说这么多。。。。。