天天看点

BBR中乱序对带宽采集的影响

BBR算法中,网络带宽和最小延迟是两个最重要的变量,网络带宽是使用minmax变量进行十轮过滤出一个最大值,而各种情况下的带宽采集计算的准确性就十分重要,之前就有一篇关于delay-ack对BBR带宽采集影响的介绍, https://blog.csdn.net/qq_40894952/article/details/80626423 。如果有啥说的不对的地方,欢迎大家指正。 回到乱序对带宽采集的影响,首先,先看个结构体变量struct rate_sample,这个变量用来记录带宽计算所需要的参数。比如,delivered就为该轮采样时间内对端收到的数据包个数,interval_us就为带宽计算的传输间隔,delivered处于interval_us就为对应带宽值,只是bbr存储的Bw要进行一下单位转换。

struct rate_sample {
        struct skb_mstamp prior_mstamp; /* starting timestamp for interval */ 
        u32 prior_delivered;/* tp->delivered at "prior_mstamp" */ 
        s32 delivered;/* number of packets delivered over interval */ 
        long interval_us;/* time for tp->delivered to incr "delivered" */ 
        long rtt_us; /* RTT of last (S)ACKed packet (or -1) */ 
        int  losses; /* number of packets marked lost upon ACK */
        u32  acked_sacked;	/* number of packets newly (S)ACKed upon ACK */
        u32  prior_in_flight;	/* in flight before this ACK */
        bool is_app_limited;	/* is sample from packet with bubble in pipe? */
        bool is_retrans;	/* is sample from retransmission? */
}
           

而在每次收到sack或者ack数据包确认新数据包是,都会调用函数tcp_rate_skb_delivered(),该函数用来往rate_sample变量的里填参数。可以看到tcp_sock控制块中会入记录一个最近被(s)ack确认的数据包对应的发送时间到first_tx_mstamp变量里,每次对端回复的ack包 (s)ack确认了新的数据包,都会更新这个first_tx_mstamp变量为(s)ack确认的数据包对应的发送时间。当发生网络乱序,先sack确认了发送时间更晚的数据包B,再确认确认更早发送的数据包A,那此时first_tx_mstamp就先更新数据包B的发送时间,再更新为数据包B的发送时间,也就是说这里记录的first_tx_mstamp并不是单调递增的,它会在某些时刻变小。如果在first_tx_mstamp被更新之前的这段时间内发送数据包,对应skb记录的最近被确认的数据包的发送时间将偏小,当该skb被对端接收计算带宽时,发送时间间隔就会偏大,造成该ack计算出来的带宽值偏小。

void tcp_rate_skb_delivered(struct sock *sk, struct sk_buff *skb,
			    struct rate_sample *rs)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct tcp_skb_cb *scb = TCP_SKB_CB(skb);

	if (!scb->tx.delivered_mstamp.v64)
		return;

	if (!rs->prior_delivered ||
	    after(scb->tx.delivered, rs->prior_delivered)) {
		rs->prior_delivered  = scb->tx.delivered;
		rs->prior_mstamp     = scb->tx.delivered_mstamp;
		rs->is_app_limited   = scb->tx.is_app_limited;
		rs->is_retrans	     = scb->sacked & TCPCB_RETRANS;

		/* Find the duration of the "send phase" of this window: */
		rs->interval_us      = skb_mstamp_us_delta(
						&skb->skb_mstamp,
						&scb->tx.first_tx_mstamp);//delivered 与prior_delivered 发送时间差

		/* Record send time of most recently ACKed packet: */
		tp->first_tx_mstamp  = skb->skb_mstamp;
	}
	/* Mark off the skb delivered once it's sacked to avoid being
	 * used again when it's cumulatively acked. For acked packets
	 * we don't need to reset since it'll be freed soon.
	 */
	if (scb->sacked & TCPCB_SACKED_ACKED)
		scb->tx.delivered_mstamp.v64 = 0;
}
           

那现在分两种情况来分析这个带来带宽采集的问题 其一,如果发生乱序但是没有重传包, 假设发送端在T1到T5时间内分别发送了5个数据包,编号分别为skb1到skb5,接收端的接收顺序为skb1->skb3->skb4->skb2->skb5,那first_tx_mstamp值也是一样更新为T1->T3->T4->T2->T5,也就是在收到skb2的确认到skb5的确认这段时间内发出去的数据包记录的fisrt_tx_mstamp值都为T2,之后计算发送时间间隔会偏大。如果乱序越严重,那对应的发送时间间隔偏差越严重。

其二,发生乱序并且重传过数据包,但原始数据包并没有丢失。 假设接收端skb1包乱序了,发送端重传了skb1包,发送顺序为skb1->skb2->...->skb8->skb1->skb9,对应的时刻值为T1->T2->....->T9->T10。当重传完skb1,接收端紧接着返回了skb1的确认数据包,发送端并没有对是否是虚假重传进行判断,因此会认为是重传的skb1被接收了,那first_tx_mstamp值就更新为skb1的重传时间T9,之后计算的发送时间间隔就会偏小。 乱序对带宽采集的影响是会造成之后某些收到ack数据包时,发送时间间隔会发生偏大,一种情况会发送间隔偏大,一种情况会造成发送间隔偏小。而在带宽计算中有个注释说明,通常情况下,接收时间间隔是更大的,但是如果发生ack compression,发送间隔间隔会更大,而TCP层倾向于保守,所以取更大的值。tcp_sock里的变量delivered记录是当前时刻交付到对端的数据包个数,first_tx_mstamp想记录的是这些被交付到对端所有的数据包中最晚发送的时间戳值,而不是最近被接收的数据包对应的发送时间值这个概念。因为网络是个黑盒,tcp是个瞎子,如果发生了数据包重传并不能判断出是原始包还是重传包被收到了。好在BBR带宽值是用minmax过滤出一个最大值,网络的乱序并没有持续在tcp传输的全部时间内,所以乱序带来的带宽值并没有带来太大的影响。

     tcp_rate_skb_delivered()函数中对first_tx_mstamp的更新,我认为可以增加些判断,正常是要保证first_tx_mstamp是单调递增的,但是如果之前更新first_tx_mstamp是个重传包,当前的数据包不是重传过的包,那之前的前面的重传可能是无效重传,确认的是之前的一个数据包,因此可以更新first_tx_mstamp值。

void tcp_rate_skb_delivered(struct sock *sk, struct sk_buff *skb,
			    struct rate_sample *rs)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct tcp_skb_cb *scb = TCP_SKB_CB(skb);

	if (!scb->tx.delivered_mstamp.v64)
		return;

	if (!rs->prior_delivered ||
	    after(scb->tx.delivered, rs->prior_delivered)) {
		rs->prior_delivered  = scb->tx.delivered;
		rs->prior_mstamp     = scb->tx.delivered_mstamp;
		rs->is_app_limited   = scb->tx.is_app_limited;
		rs->is_retrans	     = scb->sacked & TCPCB_RETRANS;

		/* Find the duration of the "send phase" of this window: */
		rs->interval_us      = skb_mstamp_us_delta(
						&skb->skb_mstamp,
						&scb->tx.first_tx_mstamp);//delivered 与prior_delivered 发送时间差

		/* Record send time of most recently ACKed packet: */
                if (skb_mstamp_after(&skb->skb_mstamp, &tp->first_tx_mstamp) ||
                    (tp->update_is_retrans && rs->is_retrans)) {
		    tp->first_tx_mstamp  = skb->skb_mstamp;
                    tp->update_is_retrans = rs->is_retrans;
                }
	}
	/* Mark off the skb delivered once it's sacked to avoid being
	 * used again when it's cumulatively acked. For acked packets
	 * we don't need to reset since it'll be freed soon.
	 */
	if (scb->sacked & TCPCB_SACKED_ACKED)
		scb->tx.delivered_mstamp.v64 = 0;
}
           

继续阅读