欢迎光临
我们一直在努力

linux内核协议栈 TCP数据接收之ACK的处理,内核协议栈收发包

目录

1 ACK响应概述

2?ACK处理 tcp_ack()

2.1 参数 flag

2.2 tcp_ack()处理流程

2.2.1?更新发送窗口

2.2.2 删除已经确认的数据段+RTT采样

2.2.3 拥塞控制


1 ACK响应概述

在TCP输入数据段的处理过程中,如果发现输入段携带了ACK信息,则会调用tcp_ack()进行ACK相关的处理。实际中,ACK信息总是会携带的,因为携带ACK不需要任何的附加开销,所以对于输入的每一个段(除了RST【直接断链 】?等特殊段),这个过程总是要执行的,这篇笔记就来看看TCP对ACK确认的处理过程。

2?ACK处理 tcp_ack() 2.1 参数 flag

tcp_ack()有个非常重要的参数flag,其贯穿整个ACK的处理过程,它记录了从输入段中能够获取到的任何信息(比如是否携带了数据、是否重复ACK、是否是SACK等),供后面的拥塞控制、RTT采样等操作参考。flag可能是如下值的组合。

flag值描述FLAG_DATA0x01ACK段中携带了数据FLAG_WIN_UPDATE0x02收到ACK段后更新了发送窗口,可能更新了左边界,也有可能更新了右边界(通告窗口变大)FLAG_DATA_ACKED0x04ACK段确认了新数据FLAG_RETRANS_DATA_ACKED0x08ACK段携带的数据已经收到过了FLAG_SYN_ACKED0x10ACK段确认了SYN段FLAG_DATA_SACKED0x20ACK段确认了新的数据FLAG_ECE0x40该ACK段携带了ECE标志FLAG_DATA_LOST0x80SACK检测到了数据丢失FLAG_SLOWPATH0x100该ACK段是由慢速路径处理的FLAG_ONLY_ORIG_SACKED0x200?FLAG_SND_UNA_ADVANCED0x400ACK段更新了snd_una,即收到ACK后,发送窗口左边界可以右移FLAG_DSACKING_ACK0x800ACK段中包含有DSACK信息FLAG_NONHEAD_RETRANS_ACKED0x1000?FLAG_SACK_RENEGING0x2000检测到之前SACK确认过的数据段被对端丢弃了(这是协议允许的)

此外,还定义了一些基本flag的组合:

#define FLAG_ACKED(FLAG_DATA_ACKED|FLAG_SYN_ACKED)//用于判断输入的数据段是否为重复段#define FLAG_NOT_DUP(FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED)#define FLAG_CA_ALERT(FLAG_DATA_SACKED|FLAG_ECE)#define FLAG_FORWARD_PROGRESS(FLAG_ACKED|FLAG_DATA_SACKED)#define FLAG_ANY_PROGRESS(FLAG_FORWARD_PROGRESS|FLAG_SND_UNA_ADVANCED) 2.2 tcp_ack()处理流程

tcp_ack()的核心操作干了三件事:

更新发送窗口;清除发送队列中已经被确认的数据(包括重传数据)并进行RTT采样;进行拥塞控制。/* This routine deals with incoming acks, but not outgoing ones. */static int tcp_ack(struct sock *sk, struct sk_buff *skb, int flag){struct inet_connection_sock *icsk = inet_csk(sk);struct tcp_sock *tp = tcp_sk(sk);//TCB中尚未被确认的最小序号u32 prior_snd_una = tp->snd_una;//ACK段中的序号u32 ack_seq = TCP_SKB_CB(skb)->seq;//ACK段中的确认号u32 ack = TCP_SKB_CB(skb)->ack_seq;u32 prior_in_flight;u32 prior_fackets;int prior_packets;int frto_cwnd = 0;/* If the ack is newer than sent or older than previous acks * then we can probably ignore it. *///确认的是还没有发送的数据,这是无意义的确认,直接返回if (after(ack, tp->snd_nxt))goto uninteresting_ack;//该确认号已经收到过了。这种可能是重复ACK,也有可能是正常的,比如该AC段有延时。//这种ACK有可能还携带了有效的SACK信息if (before(ack, prior_snd_una))goto old_ack;//到这里,说明确认号在期望的范围内[snd_una, snd_nxt],//确认号确认了新数据,设置FLAG_SND_UNA_ADVANCED标记。//if判断是为了排除ack==prior_snd_una的情况if (after(ack, prior_snd_una))flag |= FLAG_SND_UNA_ADVANCED;//tcp_abc特性相关if (sysctl_tcp_abc) {if (icsk->icsk_ca_state < TCP_CA_CWR)tp->bytes_acked += ack – prior_snd_una;else if (icsk->icsk_ca_state == TCP_CA_Loss)/* we assume just one segment left network */tp->bytes_acked += min(ack – prior_snd_una, tp->mss_cache);}prior_fackets = tp->fackets_out;prior_in_flight = tcp_packets_in_flight(tp);//下面是更新发送窗口,按照快速路径和慢速路径分别处理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_seqtcp_update_wl(tp, ack, ack_seq);//更新发送窗口左边界tp->snd_una = ack;//设置发送窗口更新标记flag |= FLAG_WIN_UPDATE;//通知拥塞控制算法,发生了CA_EVENT_FAST_ACK事件tcp_ca_event(sk, CA_EVENT_FAST_ACK);NET_INC_STATS_BH(LINUX_MIB_TCPHPACKS);} else {//慢速路径处理//ACK段还携带了数据,设置FLAG_DATA标记if (ack_seq != TCP_SKB_CB(skb)->end_seq)flag |= FLAG_DATA;elseNET_INC_STATS_BH(LINUX_MIB_TCPPUREACKS);//更新发送窗口flag |= tcp_ack_update_window(sk, skb, ack, ack_seq);//SACK相关处理if (TCP_SKB_CB(skb)->sacked)flag |= tcp_sacktag_write_queue(sk, skb, prior_snd_una);//ECN相关处理if (TCP_ECN_rcv_ecn_echo(tp, tcp_hdr(skb)))flag |= FLAG_ECE;//通知拥塞控制算法,发生了CA_EVENT_SLOW_ACK事件tcp_ca_event(sk, CA_EVENT_SLOW_ACK);}//清除软件错误sk->sk_err_soft = 0;//更新最近一次接收到ACK段的时间戳tp->rcv_tstamp = tcp_time_stamp;//如果之前根本就没有待确认的段,那么无需后续的重传队列以及拥塞控制处理;//这种情况下需要做和持续定时器相关的操作,因为可能之前传送过探测报文prior_packets = tp->packets_out;if (!prior_packets)goto no_queue;//删除重传队列中已经确认的数据段,并进行时延采样flag |= tcp_clean_rtx_queue(sk, prior_fackets);//F-RTO算法相关内容if (tp->frto_counter)frto_cwnd = tcp_process_frto(sk, flag);/* Guarantee sacktag reordering detection against wrap-arounds */if (before(tp->frto_highmark, tp->snd_una))tp->frto_highmark = 0;//拥塞控制相关if (tcp_ack_is_dubious(sk, flag)) {/* Advance CWND, if state allows this. */if ((flag & FLAG_DATA_ACKED) && !frto_cwnd && tcp_may_raise_cwnd(sk, flag))tcp_cong_avoid(sk, ack, prior_in_flight);tcp_fastretrans_alert(sk, prior_packets – tp->packets_out, flag);} else {if ((flag & FLAG_DATA_ACKED) && !frto_cwnd)tcp_cong_avoid(sk, ack, prior_in_flight);}if ((flag & FLAG_FORWARD_PROGRESS) || !(flag & FLAG_NOT_DUP))dst_confirm(sk->sk_dst_cache);return 1;no_queue://之前没有未被确认的段,收到了ACK,进行持续定时器相关处理icsk->icsk_probes_out = 0;/* 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);return 1;old_ack://虽然该ACK已经收到过了,但是如果其携带了SACK信息,需要更新确认内容if (TCP_SKB_CB(skb)->sacked)tcp_sacktag_write_queue(sk, skb, prior_snd_una);uninteresting_ack:SOCK_DEBUG(sk, “Ack %u out of %u:%u\n”, ack, tp->snd_una, tp->snd_nxt);return 0;} 2.2.1?更新发送窗口

《linux内核协议栈 TCP数据发送之发送窗口》

2.2.2 删除已经确认的数据段+RTT采样

《linux内核协议栈 TCP数据接收之清除发送队列 + RTT采样》

2.2.3 拥塞控制

?

赞(0)
【声明】:本博客不参与任何交易,也非中介,仅记录个人感兴趣的主机测评结果和优惠活动,内容均不作直接、间接、法定、约定的保证。访问本博客请务必遵守有关互联网的相关法律、规定与规则。一旦您访问本博客,即表示您已经知晓并接受了此声明通告。