SlowStart阶段拥塞窗口增长

慢启动阶段的拥塞窗口增长函数tcp_slow_start如下,拥塞窗口以报文数量表示,参数acked表示当前ACK报文确认的数据包数量。如果拥塞窗口增加acked数量之后小于慢启动阈值ssthresh,使用二者相加结果作为新的拥塞窗口值。内核没有使用RFC3465中定义的ABC(Appropriate Byte Counting)算法,其最初设计的是针对按照字节表示拥塞窗口的系统。内核中规定了对数据报文的确认必须是对整个报文的确认,也可抵御ACK Division攻击。

u32 tcp_slow_start(struct tcp_sock *tp, u32 acked)
{
    u32 cwnd = min(tp->snd_cwnd + acked, tp->snd_ssthresh);
    
    acked -= cwnd - tp->snd_cwnd;
    tp->snd_cwnd = min(cwnd, tp->snd_cwnd_clamp);
    
    return acked;
}

如果当snd_cwnd增加acked值之后,大于慢启动阈值ssthresh时,将拥塞窗口CWND设置为ssthresh的值,以上函数tcp_slow_start将返回进入拥塞避免阶段接收到的ACK报文数量,交由拥塞算法处理。

ACK确认数据报文数量统计

如下函数tcp_clean_rtx_queue,遍历重传队列,计算当前ACK报文确认的数据报文数量(acked_pcount),SlowStart使用此数量决定拥塞窗口,不同于协议中规定的ACK报文数量,所以,对于Stretch-ACKs,内核当其为多个背靠背的ACK报文。

static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack,
                   u32 prior_snd_una, struct tcp_sacktag_state *sack)          
{ 

    for (skb = skb_rb_first(&sk->tcp_rtx_queue); skb; skb = next) {
        struct tcp_skb_cb *scb = TCP_SKB_CB(skb);
        const u32 start_seq = scb->seq;
        u8 sacked = scb->sacked;

        /* Determine how many packets and what bytes were acked, tso and else */
        if (after(scb->end_seq, tp->snd_una)) {
            if (tcp_skb_pcount(skb) == 1 || !after(tp->snd_una, scb->seq))
                break;

            acked_pcount = tcp_tso_acked(sk, skb);
            if (!acked_pcount) break;
            fully_acked = false;
        } else {
            acked_pcount = tcp_skb_pcount(skb);
        }

        if (sacked & TCPCB_SACKED_ACKED) {
            tp->sacked_out -= acked_pcount;
        } else if (tcp_is_sack(tp)) {
            tp->delivered += acked_pcount;
            ...
        }

        if (!fully_acked)
            break;

对于SACK连接,将delivered增加当前ACK确认的报文数量acked_pcount,但是,如果报文设置了TCPCB_SACKED_ACKED标志,表示其已经在被SACK确认的时候,增加了delivered值,此处不再增加。在如下的SACK序号块处理函数tcp_sacktag_one中,如果当前数据报文没有被SACK确认过,此为第一次被SACK确认,将确认报文数量增加到套接口变量delivered中。

static u8 tcp_sacktag_one(struct sock *sk, struct tcp_sacktag_state *state, u8 sacked,
              u32 start_seq, u32 end_seq, int dup_sack, int pcount, u64 xmit_time)
{
    ...
    if (!(sacked & TCPCB_SACKED_ACKED)) {
        ...
        sacked |= TCPCB_SACKED_ACKED;
        state->flag |= FLAG_DATA_SACKED;
        tp->sacked_out += pcount;
        tp->delivered += pcount;  /* Out-of-order packets delivered */

另外,对于Reno,以下是对于乱序情况下,每个接收到的重复ACK,认为是对端接收了一个数据报文,相应的增加delivered计数。

static void tcp_add_reno_sack(struct sock *sk, int num_dupack)
{
    if (num_dupack) {
        struct tcp_sock *tp = tcp_sk(sk);
        u32 prior_sacked = tp->sacked_out;
        s32 delivered;

        tp->sacked_out += num_dupack;
        tcp_check_reno_reordering(sk, 0);
        delivered = tp->sacked_out - prior_sacked;
        if (delivered > 0)
            tp->delivered += delivered;

注意在以上的函数tcp_clean_rtx_queue中,并没有增加确认报文数量(delivered),而是放到了以下函数tcp_remove_reno_sacks中。

如果没有重复ACK(sacked_out为零),将delivered增加确认的报文数量,与SACK处理相同。但是,如果sacked_out有值,即接收到了重复ACK,由于在以上函数tcp_add_reno_sack中,已经对重复ACK增加过delivered值,此处不在重复增加。变量delivered至少将增加1,即即使确认报文数量(acked)小于之前重复ACK数量,delivered也将增加1。

static void tcp_remove_reno_sacks(struct sock *sk, int acked)
{
    struct tcp_sock *tp = tcp_sk(sk);

    if (acked > 0) {
        /* One ACK acked hole. The rest eat duplicate ACKs. */
        tp->delivered += max_t(int, acked - tp->sacked_out, 1);
        if (acked - 1 >= tp->sacked_out)
            tp->sacked_out = 0;
        else
            tp->sacked_out -= acked - 1;
    }

计算确认报文数量

函数tcp_ack的最后使用tcp_newly_delivered计算S/ACK报文最新确认的报文数量.

static u32 tcp_newly_delivered(struct sock *sk, u32 prior_delivered, int flag)
{
    ...
    delivered = tp->delivered - prior_delivered;
    NET_ADD_STATS(net, LINUX_MIB_TCPDELIVERED, delivered);
    if (flag & FLAG_ECE) {
        tp->delivered_ce += delivered;
        NET_ADD_STATS(net, LINUX_MIB_TCPDELIVEREDCE, delivered);
    }
    return delivered;

将新确认报文数量传递到tcp_cong_control函数中。

static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
{
    u32 delivered = tp->delivered;

    delivered = tcp_newly_delivered(sk, delivered, flag);
    ...
    tcp_cong_control(sk, ack, delivered, flag, sack_state.rate);

函数tcp_cong_control最终将调用拥塞算法的函数指针cong_avoid。

static void tcp_cong_control(struct sock *sk, u32 ack, u32 acked_sacked,
                 int flag, const struct rate_sample *rs)
{
    ...
    if (tcp_in_cwnd_reduction(sk)) {
        ...
    } else if (tcp_may_raise_cwnd(sk, flag)) {
        /* Advance cwnd if state allows */
        tcp_cong_avoid(sk, ack, acked_sacked);
    }
    tcp_update_pacing_rate(sk);
}

以内核默认的Cubic拥塞算法为例,bictcp_cong_avoid将会调用开始时介绍的tcp_slow_start函数,传入新确认的报文数量acked。

static void bictcp_cong_avoid(struct sock *sk, u32 ack, u32 acked)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct bictcp *ca = inet_csk_ca(sk);

    if (!tcp_is_cwnd_limited(sk))
        return;

    if (tcp_in_slow_start(tp)) {
        if (hystart && after(ack, ca->end_seq))
            bictcp_hystart_reset(sk);
        acked = tcp_slow_start(tp, acked);
        if (!acked)
            return;
    }
    bictcp_update(ca, tp->snd_cwnd, acked);
    tcp_cong_avoid_ai(tp, ca->cnt, acked);

内核版本 5.0

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页