TCP filter的原理:

当filter收到某个连接的第一个报文时,会为该连接在全局连接表中创建一个表项,并用报文中携带的源、目的IP和端口这个四元组创建original tuple和reply tuple,这两个tuple分别从不同方向来标识这个连接。后续的报文会根据其携带的四元组找到相应的连接表项,然后根据表项所记录的历史状态,检查报文所携带的ack、数据是否有效。

filter通过分析该连接所有的历史报文,计算出ack和数据相应的最大最小阀值,来检查新到达报文ack和数据的有效性。该连接相关的最大最小阀值是动态变化的,当新报文通过有效性检查后,阀值将使用新报文所携带的内容重新计算。在讨论如何确立阀值之前,先来看几条约定。假设A和B之间的报文都经过filter,那么:

l          filter可以看到A、B之间的所有报文数据;

l          filter可以看到每个报文中所声明的窗口大小;

l          如果B发送的报文的ACK标志位置位,且ACK = n,那么filter可以认为B已接收到的A数据,其长度至少为n。

1),连接项中当前有效数据边界的确立:

假设A向B发送的报文中,所含数据段为[seq,seq + len),即报文所含数据起始SEQ为seq,数据长度为len。由于A所发送的报文长度不能超过B当前窗口所能容纳的大小,因此有效数据的上限为:

A :seq + len <= B : max { ack + max{win,1}} (I)

A所发出报文数据的最大序号,要不大于从B接收到的ACK + max{win,1}的最大值。之所以取最大值而不使用最近接收到的报文的值,是因为报文的到达是无序的,较小的报文有可能因为其他原因较晚到达。另外,由于报文通告的窗口大小有可能为0,这种情况下,TCP的坚持定时器允许A间隔地发送长度为1的窗口探测报文,因次有效数据的上限需采用max{ win, 1}。上限的设置,可以防止B收到超过其窗口大小的报文,filter可以将这部分报文直接丢弃而不再转发到B。

有效数据的下限:

A : seq >= A : max{ seq + len} – B : max{ max{ win, 1}} (II)

假设B的最大窗口大小为n,那么B端最多可以缓存n个A的报文,因为A端所发送的报文最多有n个尚未确认,对于已经确认的报文再次重发是没有意义的。

2),连接项中当前有效ACK边界的确立:

因为A不可能为其未收到的数据进行确认,所以报文中的ACK不可能大于其所收到报文的最大SEQ,所以有效ACK的上限为:

A :ack <= B :max{ seq + len}    (III)

如何确立ACK的下限,则显得比较困难。因为报文到达的无序性,所以filter采用了一种较为宽松的方式,以避免有效的ACK被阻塞:

A : ack >= B : max{ seq + len} – MAXACKWINDOW (IV)

MAXACKWINDOW被定义为66000,即TCP允许的最大的窗口大小,该值的大小决定了有效ACK被阻塞的可能性。

7,LINUX的相关原理:

struct ip_ct_tcp_state {

     u_int32_t td_end;       /* max of seq + len */

     u_int32_t td_maxend;    /* max of ack + max(win, 1) */

     u_int32_t td_maxwin;    /* max(win) */

     u_int8_t td_scale; /* window scale factor */

     u_int8_t loose;        /* used when connection picked up from the middle */

     u_int8_t flags;        /* per direction options */

};

struct ip_ct_tcp

{

     struct ip_ct_tcp_state seen[2]; /* connection parameters per direction */

     …

};

ip_ct_tcp是用来记录一个连接TCP状态的数据结构,seen是个大小为2的数组,0用来记录和连接发起方original相关的内容,1则记录了reply的内容。TCP协议对待发送报文的限制有两类:RCV.ACK =< SEG.SEQ < RCV.ACK+RCV.WND或者RCV.ACK =< SEG.SEQ+SEG.LEN-1 < RCV.ACK+RCV.WND。在上述部分,公式(I)采用了后一种来判别,但在linux系统中,公式(I)采用了前一种判别方法。


sender.td_end = max((seq + len) from sender);

td_maxend则等价于max { (ack + max{win,1}) from receiver},但在linux中,

receiver.td_maxend = max((sack + max(win,1)) from sender);因为若报文中有SACK选项,那么实际上sender发出报文的实际最大ACK是在SACK选项中,并且是其中最大的。

td_maxwin等于max{win,1},linux中,对于报文的发送端而言

sender.td_maxwin = max(max(win, 1)) + (sack - ack);对于该报文的接收端而言如果当前报文中的

seq + len > sender.td_maxend,receiver.td_maxwin += seq + len - sender.td_maxend     


上述阀值公式等价于:

   I. 有效数据上限: seq <= sender.td_maxend

   II.有效数据下限: seq + len>= sender.td_end - receiver.td_maxwin 

(因为公式I用的

max(ack)<= seq <= sender.td_maxend 推导出的,所以

      seq + len >= max(ack) + len , 又 

max(ack)>= sender.td_maxend- receiver.td_maxwin >= max(seq) - receiver.td_maxwin, 所以 

max(ack) + len >= max(seq) + len - receiver.td_maxwin >=sender.td_end - receiver.td_maxwin

唉推不下去了)

   III.有效ACK上限: sack <= receiver.td_end

   IV. 有效ACK下限: ack >= receiver.td_end - MAXACKWINDOW

8,tcp_in_window:

/*1,从报文中获取seq,ack,win和end = seq + len*/

seq = ntohl(tcph->seq);

ack = sack = ntohl(tcph->ack_seq);

win = ntohs(tcph->window);

end = segment_seq_plus_len(seq, skb->len, iph, tcph);

/*2,如果存在SACK选项,获取SACK中的最右边沿*/

if (receiver->flags & IP_CT_TCP_FLAG_SACK_PERM)

     tcp_sack(skb, iph, tcph, &sack);

/*3,发送方td_end为0的情况对于original端并不适合,只有当reply方对original的syn回应的报文才会走到这个分支*/

if (sender->td_end == 0) {

     /* 报文是一个syn/ack报文,表明TCP连接是一个正常的从初始开始的连接,初始化连接状态 */

     if (tcph->syn && tcph->ack) {

          …

     } else {

     /*该TCP连接是对以前存在的但已断开的连接,重新开始连接*/

          …

     }

}else if (((state->state == TCP_CONNTRACK_SYN_SENT

          && dir == IP_CT_DIR_ORIGINAL)

         || (state->state == TCP_CONNTRACK_SYN_RECV

            && dir == IP_CT_DIR_REPLY))

         && after(end, sender->td_end)) {

/*RFC 793: "if a TCP is reinitialized ... then it need not wait at all; it must only be sure to use sequence numbers larger than those recently used."*/

}

/*4,这里是函数的主体部分,实现了上述四个公式的判别,并对连接状态的相应内容进行更新*/

if (sender->loose || receiver->loose ||

    (before(seq, sender->td_maxend + 1) &&

     after(end, sender->td_end - receiver->td_maxwin - 1) &&

     before(sack, receiver->td_end + 1) &&

     after(ack, receiver->td_end - MAXACKWINDOW(sender)))) {

}


http://hi.baidu.com/zkheartboy/item/22045e13b2c0c20eb98a1a2d

Logo

更多推荐