Linux内核中TCP的连接跟踪(2)
TCP filter的原理:当filter收到某个连接的第一个报文时,会为该连接在全局连接表中创建一个表项,并用报文中携带的源、目的IP和端口这个四元组创建original tuple和reply tuple,这两个tuple分别从不同方向来标识这个连接。后续的报文会根据其携带的四元组找到相应的连接表项,然后根据表项所记录的历史状态,检查报文所携带的ack、数据是否有效。filte
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
更多推荐
所有评论(0)