tcp_ack 以及拥塞控制
之前的拥塞控制文档有 :

//这个说明当前的输入帧包含有数据。 #define FLAG_DATA 0x01 /* Incoming frame contained data. */ //这个说明当前的ack是一个窗口更新的ack #define FLAG_WIN_UPDATE 0x02 /* Incoming ACK was a window update. */ //这个ack确认了一些数据 #define FLAG_DATA_ACKED 0x04 /* This ACK acknowledged new data. */ //这个表示ack确认了一些我们重传的段。 #define FLAG_RETRANS_DATA_ACKED 0x08 /* "" "" some of which was retransmitted. */ #define FLAG_SYN_ACKED 0x10 /* This ACK acknowledged SYN. */ //新的sack #define FLAG_DATA_SACKED 0x20 /* New SACK. */ #define FLAG_ECE 0x40 /* ECE in this ACK */ //sack检测到了数据丢失。 #define FLAG_LOST_RETRANS 0x80 /* This ACK marks some retransmission lost */ //此ack由慢速路径处理 #define FLAG_SLOWPATH 0x100 /* Do not skip RFC checks for window update.*/ #define FLAG_ORIG_SACK_ACKED 0x200 /* Never retransmitted data are (s)acked */ //ack 更新了snd_una 收到ack后窗口向右移动 #define FLAG_SND_UNA_ADVANCED 0x400 /* Snd_una was changed (!= FLAG_DATA_ACKED) */ //ack 中包含dsack #define FLAG_DSACKING_ACK 0x800 /* SACK blocks contained D-SACK info */ //检测到之前的sack确认过的数据段被对端丢弃 #define FLAG_SACK_RENEGING 0x2000 /* snd_una advanced to a sacked seq */ #define FLAG_UPDATE_TS_RECENT 0x4000 /* tcp_replace_ts_recent() */ #define FLAG_NO_CHALLENGE_ACK 0x8000 /* do not call tcp_send_challenge_ack() */ #define FLAG_ACKED (FLAG_DATA_ACKED|FLAG_SYN_ACKED) #define FLAG_NOT_DUP (FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED) //收到sack则说明可能有的段丢失了。而ECE则是路由器提示我们有拥塞了。我们必须处理。 #define FLAG_CA_ALERT (FLAG_DATA_SACKED|FLAG_ECE) #define FLAG_FORWARD_PROGRESS (FLAG_ACKED|FLAG_DATA_SACKED) #define TCP_REMNANT (TCP_FLAG_FIN|TCP_FLAG_URG|TCP_FLAG_SYN|TCP_FLAG_PSH) #define TCP_HP_BITS (~(TCP_RESERVED_BITS|TCP_FLAG_PSH))
static inline bool tcp_ack_is_dubious(const struct sock *sk, const int flag) { //说明flag不能是 FLAG_NOT_DUP的, FLAG_NOT_DUP表示我们的ack不是重复的 //flag是CA_ALERT,它的意思是我们是否在我们进入拥塞状态时被alert //拥塞状态不能为TCP_CA_OPEN不为这个,就说明我们已经进入了拥塞状态 return !(flag & FLAG_NOT_DUP) || (flag & FLAG_CA_ALERT) || inet_csk(sk)->icsk_ca_state != TCP_CA_Open; } /* Decide wheather to run the increase function of congestion control. */ static inline bool tcp_may_raise_cwnd(const struct sock *sk, const int flag) { if (tcp_in_cwnd_reduction(sk)) //cwr recovery状态下不行 return false; /* If reordering is high then always grow cwnd whenever data is * delivered regardless of its ordering. Otherwise stay conservative * and only grow cwnd on in-order delivery (RFC5681). A stretched ACK w/ * new SACK or ECE mark may first advance cwnd here and later reduce * cwnd in tcp_fastretrans_alert() based on more states. *///触发快速重传标志 if (tcp_sk(sk)->reordering > sysctl_tcp_reordering) return flag & FLAG_FORWARD_PROGRESS; return flag & FLAG_DATA_ACKED; }
* * "Open" Normal state, no dubious events, fast path. * "Disorder" In all the respects it is "Open", * but requires a bit more attention. It is entered when * we see some SACKs or dupacks. It is split of "Open" * mainly to move some processing from fast path to slow one. * "CWR" CWND was reduced due to some Congestion Notification event. * It can be ECN, ICMP source quench, local device congestion. * "Recovery" CWND was reduced, we are fast-retransmitting. * "Loss" CWND was reduced due to RTO timeout or SACK reneging.

CP_CA_Recovery状态到TCP_CA_Open状态
从TCP_CA_Recovery状态到TCP_CA_Open状态的条件:
当snd_una >= high_seq时 : 表示重传队列中的skb全部被重传并得到了ACK,可以开始正常数据传输了,且snd_cwnd值保持原值不变,按当前状态进行增长
TCP_CA_Loss状态到TCP_CA_Open状态
从TCP_CA_Loss状态到TCP_CA_Open状态的条件:
1)snd_una >= high_seq : 表示重传队列中的skb是否全部被重传并得到了ACK,snd_cwnd值保持原值不变,按当前状态进行增长
2)虚假RTO(frto):不是所有的DATA被丢失,并且从来没有被重传过的DATA被ACK

TCP_CA_Open状态到TCP_CA_Disorder状态
从TCP_CA_Open状态到TCP_CA_Disorder状态的条件:出现一个可疑的ACK。
1)收到DUP_ACK
2)出现新的DATA被SACK
3)当前状态不为Open状态


TCP_CA_Open状态到TCP_CA_Recovery状态
1)已经存在情况,且丢包数大于1
2)RACK功能被关闭, 且收到3 个以上的DUP_ACK包注:
以上跟TCP_CA_Disorder状态到TCP_CA_Recovery及 TCP_CA_CWR状态到TCP_CA_Recovery条件一样

从TCP_CA_Open状态到TCP_CA_CWR状态的条件:
1)当发送一个数据包时,使用tcp_write_xmit发送,调用tcp_transmit_skb()后转调ip_queue_xmit将ip数据包发送出去时,如果失败了,则进入CRW状态。当处于CWR状态时,tcp_cong_avoid()函数不会被调用,由于cwnd在这个函数中被快速增长,所以在CWR状态中,cwnd值不会被快速增长,只会被缓慢减少在一个范围内
2)收到一个带有ECN信息的包
/* This routine deals with incoming acks, but not outgoing ones. */ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) { struct inet_connection_sock *icsk = inet_csk(sk); struct tcp_sock *tp = tcp_sk(sk); struct tcp_sacktag_state sack_state; u32 prior_snd_una = tp->snd_una; u32 ack_seq = TCP_SKB_CB(skb)->seq; u32 ack = TCP_SKB_CB(skb)->ack_seq; bool is_dupack = false; u32 prior_fackets; int prior_packets = tp->packets_out; const int prior_unsacked = tp->packets_out - tp->sacked_out; int acked = 0; /* Number of packets newly acked */ sack_state.first_sackt.v64 = 0; /* We very likely will need to access write queue head. */ prefetchw(sk->sk_write_queue.next); /* If the ack is older than previous acks * then we can probably ignore it. //如果ack的序列号小于发送未确认的,也就是说可能这个ack只是重传老的ack,因此我们忽略它。 */ if (before(ack, prior_snd_una)) { /* RFC 5961 5.2 [Blind Data Injection Attack].[Mitigation] The ACK value is considered acceptable only if it is in the range of ((SND.UNA - MAX.SND.WND) <= SEG.ACK <= SND.NXT). All incoming segments whose ACK value doesn't satisfy the above condition MUST be discarded and an ACK sent back. It needs to be noted that RFC 793 on page 72 (fifth check) says: "If the ACK is a duplicate (SEG.ACK < SND.UNA), it can be ignored. If the ACK acknowledges something not yet sent (SEG.ACK > SND.NXT) then send an ACK, drop the segment, and return". The "ignored" above implies that the processing of the incoming data segment continues, which means the ACK value is treated as acceptable. This mitigation makes the ACK check more stringent since any ACK < SND.UNA wouldn't be accepted, instead only ACKs that are in the range ((SND.UNA - MAX.SND.WND) <= SEG.ACK <= SND.NXT) get through. A new state variable MAX.SND.WND is defined as the largest window that the local sender has ever received from its peer. This window may be scaled to a value larger than 65,535 bytes ([RFC1323]). This small check will reduce the vulnerability to an attacker guessing a valid sequence number, since, not only one must guess the in-window sequence number, but also guess a proper ACK value within a scoped range. This mitigation reduces, but does not eliminate, the ability to generate false segments. It does however reduce the probability that invalid data will be injected.如果接收报文的确认序号小于本地套接口待确认序号, 表明为一个已经确认过的序号,并且其确认序号在套接口当前待确认序号减去本地发送窗口之前, 内核认为此报文很可能并非对端发送,即其为攻击者构造出来的报文, 合法的报文确认序号ACK的范围:((SND.UNA - MAX.SND.WND) <= SEG.ACK <= SND.NXT)。否则, 不在此范围内认为是盲数据注入攻击Blind Data Injection Attack。但是,有可能并非攻击者发送, 而是来自对端的报文,此时回复挑战ACK报文,使对端有机会修正其确认序号ACK。 */ if (before(ack, prior_snd_una - tp->max_window)) { if (!(flag & FLAG_NO_CHALLENGE_ACK)) tcp_send_challenge_ack(sk, skb);//挑战ACK发送控制 return -1; } goto old_ack; } /* If the ack includes data we haven't sent yet, discard * this segment (RFC793 Section 3.9). //如果ack大于snd_nxt,也就是它确认了我们还没发送的数据段,因此我们discard这个段。 */ if (after(ack, tp->snd_nxt)) goto invalid_ack; if (icsk->icsk_pending == ICSK_TIME_EARLY_RETRANS || icsk->icsk_pending == ICSK_TIME_LOSS_PROBE) tcp_rearm_rto(sk); if (after(ack, prior_snd_una)) { flag |= FLAG_SND_UNA_ADVANCED; icsk->icsk_retransmits = 0; } //得到fack的数据包的字节数 prior_fackets = tp->fackets_out; /* ts_recent update must be made after we are sure that the packet * is in window. */ if (flag & FLAG_UPDATE_TS_RECENT) tcp_replace_ts_recent(tp, TCP_SKB_CB(skb)->seq); //更新发送窗口,按照快速路径和慢速路径分别处理 //是slow_path中,我们需要update窗口的大小,而在fast模式中,我们不需要 if (!(flag & FLAG_SLOWPATH) && after(ack, prior_snd_una)) { /* Window is constant, pure forward advance. * No more checks are required. * Note, we use the fact that SND.UNA>=SND.WL2. */ //记录最近一次导致发送窗口更新的ACK段的序号,即tp->snd_wl1=ack_seq tcp_update_wl(tp, ack_seq); //更新发送窗口左边界 tcp_snd_una_update(tp, ack); flag |= FLAG_WIN_UPDATE;//设置发送窗口更新标记 //通知拥塞控制算法,发生了CA_EVENT_FAST_ACK 也就是 ack updated window 事件 tcp_in_ack_event(sk, CA_ACK_WIN_UPDATE); NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPACKS); } else {//慢速路径处理 u32 ack_ev_flags = CA_ACK_SLOWPATH; //ACK段还携带了数据,设置FLAG_DATA标记 if (ack_seq != TCP_SKB_CB(skb)->end_seq) flag |= FLAG_DATA; else NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPPUREACKS); //更新发送窗口 flag |= tcp_ack_update_window(sk, skb, ack, ack_seq); if (TCP_SKB_CB(skb)->sacked)//SACK相关处理 flag |= tcp_sacktag_write_queue(sk, skb, prior_snd_una, &sack_state); if (tcp_ecn_rcv_ecn_echo(tp, tcp_hdr(skb))) { flag |= FLAG_ECE; ack_ev_flags |= CA_ACK_ECE; } if (flag & FLAG_WIN_UPDATE) ack_ev_flags |= CA_ACK_WIN_UPDATE; //通知拥塞控制算法,发生了 CA_ACK_SLOWPATH 事件 tcp_in_ack_event(sk, ack_ev_flags); } /* We passed data and got it acked, remove any soft error * log. Something worked... */ sk->sk_err_soft = 0;//清除软件错误 icsk->icsk_probes_out = 0; //更新最近一次接收到ACK段的时间戳 tp->rcv_tstamp = tcp_time_stamp; //如果之前根本就没有待确认的段,那么无需后续的重传队列以及拥塞控制处理; if (!prior_packets)//prior_packets = tp->packets_out; goto no_queue; /* See if we can take anything off of the retransmit queue. */ acked = tp->packets_out; //删除重传队列中已经确认的数据段,并进行时延采样 flag |= tcp_clean_rtx_queue(sk, prior_fackets, prior_snd_una, &sack_state); acked -= tp->packets_out; ////判断ack是否是可疑的。它主要是检测我们是否进入拥塞状态,或者已经处于拥塞状态。 if (tcp_ack_is_dubious(sk, flag)) { is_dupack = !(flag & (FLAG_SND_UNA_ADVANCED | FLAG_NOT_DUP)); tcp_fastretrans_alert(sk, acked, prior_unsacked, is_dupack, flag);//这里进入拥塞状态的处理 } if (tp->tlp_high_seq) tcp_process_tlp_ack(sk, ack, flag); /* Advance cwnd if state allows 判断是否需要增大拥塞窗口*/ if (tcp_may_raise_cwnd(sk, flag)) tcp_cong_avoid(sk, ack, acked);//用来实现慢开始和快重传的拥塞算法 if ((flag & FLAG_FORWARD_PROGRESS) || !(flag & FLAG_NOT_DUP)) sk_dst_confirm(sk); if (icsk->icsk_pending == ICSK_TIME_RETRANS) tcp_schedule_loss_probe(sk); tcp_update_pacing_rate(sk); return 1; no_queue: /* If data was DSACKed, see if we can undo a cwnd reduction. */ if (flag & FLAG_DSACKING_ACK) tcp_fastretrans_alert(sk, acked, prior_unsacked, is_dupack, flag); /* If this ack opens up a zero window, clear backoff. It was * being used to time the probes, and is probably far higher than * it needs to be for normal retransmission. */ if (tcp_send_head(sk)) tcp_ack_probe(sk); if (tp->tlp_high_seq) tcp_process_tlp_ack(sk, ack, flag); return 1; invalid_ack: SOCK_DEBUG(sk, "Ack %u after %u:%u\n", ack, tp->snd_una, tp->snd_nxt); return -1; old_ack: /* If data was SACKed, tag it and see if we should send more data. * If data was DSACKed, see if we can undo a cwnd reduction. *///虽然该ACK已经收到过了,但是如果其携带了SACK信息,需要更新确认内容 if (TCP_SKB_CB(skb)->sacked) { flag |= tcp_sacktag_write_queue(sk, skb, prior_snd_una, &sack_state); tcp_fastretrans_alert(sk, acked, prior_unsacked, is_dupack, flag); } SOCK_DEBUG(sk, "Ack %u before %u:%u\n", ack, tp->snd_una, tp->snd_nxt); return 0; }
http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!!
但行好事 莫问前程
--身高体重180的胖子


浙公网安备 33010602011771号