6,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)采用了前一种判别方法。 l sender.td_end = max((seq + len) from sender); l td_maxend则等价于max { (ack + max{win,1}) from sender},但在linux中,receiver.td_maxend = max((sack + max(win,1)) from sender);因为若报文中有SACK选项,那么实际上sender发出报文的实际最大ACK是在SACK选项中,并且是其中最大的。 l 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 >= ??唉推不下去了) 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)))) { } |
【转帖LINUX】netfilter中的conntrack内核阅读笔记(5)
最新推荐文章于 2024-04-03 11:34:58 发布
2008-07-07 22:09