SACK Reneging

TCP发送端不能够清除SACK序号块确认的数据,因为接收端很可能由于内存压力等原因,删除乱序队列中SACK确认过的报文。发送端重传队列中的报文只有在接收到ACK报文的Acknowledge序号字段确认之后,才能移除队列和释放。

接收端丢弃OFO报文

如下在检测的已用接收缓存大于套接口总的接收缓存sk_rcvbuf时,并且接收缓存已不能够再扩大,最后的措施就是释放乱序报文队列,参见函数tcp_prune_ofo_queue。

static int tcp_try_rmem_schedule(struct sock *sk, struct sk_buff *skb, unsigned int size)
{
    if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
        !sk_rmem_schedule(sk, skb, size)) {

        if (tcp_prune_queue(sk) < 0)
            return -1;

        while (!sk_rmem_schedule(sk, skb, size)) {
            if (!tcp_prune_ofo_queue(sk))
                return -1;
        }

由于释放了ofo报文,在函数tcp_prune_ofo_queue的最后,对于开启了SACK功能的连接,调用函数tcp_sack_reset,清空DSACK和SACK信息。

static inline void tcp_sack_reset(struct tcp_options_received *rx_opt)
{       
    rx_opt->dsack = 0;
    rx_opt->num_sacks = 0;
} 
static bool tcp_prune_ofo_queue(struct sock *sk)
{
    ...
    /* Reset SACK state.  A conforming SACK implementation will
     * do the same at a timeout based retransmit.  When a connection
     * is in a sad state like this, we care only about integrity
     * of the connection not performance.
     */
    if (tp->rx_opt.sack_ok)
        tcp_sack_reset(&tp->rx_opt);

SACK Reneging发生

函数tcp_clean_rtx_queue清除被确认的报文,如果在此之后,发现未在for循环中确认的报文,之前被SACK序号块确认过,表明接收端可能发生了SACK Reneging,设置标志FLAG_SACK_RENEGING。否则,如果接收端没有发生SACK Reneging,ACK报文的确认序号应当包含SACK确认的序号范围。

static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack,
                   u32 prior_snd_una, struct tcp_sacktag_state *sack)
{
    u32 prior_sacked = tp->sacked_out;
    u32 reord = tp->snd_nxt; /* lowest acked un-retx un-sacked seq */


    first_ackt = 0;

    for (skb = skb_rb_first(&sk->tcp_rtx_queue); skb; skb = next) {

    }

    if (skb && (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED))
        flag |= FLAG_SACK_RENEGING;

在ACK报文处理函数tcp_ack中,对于可疑的ACK(其中包含重复ACK等),由函数tcp_fastretrans_alert进行处理。

static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
{
    /* See if we can take anything off of the retransmit queue. */
    flag |= tcp_clean_rtx_queue(sk, prior_fack, prior_snd_una, &sack_state);

    if (tcp_ack_is_dubious(sk, flag)) {
        if (!(flag & (FLAG_SND_UNA_ADVANCED | FLAG_NOT_DUP))) {
            num_dupack = 1;
            /* Consider if pure acks were aggregated in tcp_add_backlog() */
            if (!(flag & FLAG_DATA))
                num_dupack = max_t(u16, 1, skb_shinfo(skb)->gso_segs);
        }
        tcp_fastretrans_alert(sk, prior_snd_una, num_dupack, &flag, &rexmit);
    }

以下函数tcp_fastretrans_alert中,调用tcp_check_sack_reneging检查SACK Reneging是否发生。如果为真,退出处理。

static void tcp_fastretrans_alert(struct sock *sk, const u32 prior_snd_una,
                  int num_dupack, int *ack_flag, int *rexmit)
{
    /* B. In all the states check for reneging SACKs. */
    if (tcp_check_sack_reneging(sk, flag))
        return;

如果接收到的ACK报文携带的确认序号,指向了之前记录的已经被SACK确认过的序号,意味着接收端很可能将SACK序号块对应的数据删除。但是,为了避免不必要的重传,我们等待一段很短的时间,等待接收端发送更多的ACK报文,期待后续ACK确认SACK中序号。

如下函数tcp_check_sack_reneging,定时器时长为RTT的一半,但是不超过10ms。注意套接口中变量srtt_us中保存的为RTT的8倍值。

static bool tcp_check_sack_reneging(struct sock *sk, int flag)
{
    if (flag & FLAG_SACK_RENEGING) {
        struct tcp_sock *tp = tcp_sk(sk);
        unsigned long delay = max(usecs_to_jiffies(tp->srtt_us >> 4),
                      msecs_to_jiffies(10));

        inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, delay, TCP_RTO_MAX);
        return true;
    }
    return false;
}

在超时处理函数tcp_retransmit_timer,将调用以下tcp_timeout_mark_lost函数处理重传队列的相关标记操作。如果队列的首个报文skb被SACK确认过,即sacked具有标记TCPCB_SACKED_ACKED,表明接收端发生了SACKRENEGING,清空了乱序队列。这里需要清除接收到的SACK信息。

清空SACK确认数据计数sacked_out,并设置套接口的标志is_sack_reneg。之后,遍历重传队列,清除其中所有报文的TCPCB_SACKED_ACKED标志位。

static void tcp_timeout_mark_lost(struct sock *sk)
{
    head = tcp_rtx_queue_head(sk);
    is_reneg = head && (TCP_SKB_CB(head)->sacked & TCPCB_SACKED_ACKED);
    if (is_reneg) {
        NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPSACKRENEGING);
        tp->sacked_out = 0;
        /* Mark SACK reneging until we recover from this loss event. */
        tp->is_sack_reneg = 1;
    } else if (tcp_is_reno(tp)) {
        tcp_reset_reno_sack(tp);
    }

    skb = head;
    skb_rbtree_walk_from(skb) {
        if (is_reneg)
            TCP_SKB_CB(skb)->sacked &= ~TCPCB_SACKED_ACKED;
        else if (tcp_is_rack(sk) && skb != head && tcp_rack_skb_timeout(tp, skb, 0) > 0)
            continue; /* Don't mark recently sent ones lost yet */
        tcp_mark_skb_lost(sk, skb);
    }

SACK Reneging标志清除

还是位于tcp_fastretrans_alert函数中,SACK Reneging检查通过。后续如果ACK确认的报文序号位于high_seq(丢包发生时的SND.NXT)序号之后(或相等),表明已经从丢包中恢复过来,撤销拥塞状态TCP_CA_Recovery。

static void tcp_fastretrans_alert(struct sock *sk, const u32 prior_snd_una,
                  int num_dupack, int *ack_flag, int *rexmit)
{
    /* B. In all the states check for reneging SACKs. */
    if (tcp_check_sack_reneging(sk, flag))
        return;

    /* D. Check state exit conditions. State can be terminated
     *    when high_seq is ACKed. */
    if (icsk->icsk_ca_state == TCP_CA_Open) {
        ...
    } else if (!before(tp->snd_una, tp->high_seq)) {
        switch (icsk->icsk_ca_state) {
        case TCP_CA_Recovery:
            if (tcp_is_reno(tp))
                tcp_reset_reno_sack(tp);
            if (tcp_try_undo_recovery(sk))
                return;
            tcp_end_cwnd_reduction(sk);
            break;

在撤销TCP_CA_Recovery状态函数中,拥塞状态设置为TCP_CA_Open,清空SACK Reneging标志。

/* People celebrate: "We love our President!" */
static bool tcp_try_undo_recovery(struct sock *sk)
{
    ...
    tcp_set_ca_state(sk, TCP_CA_Open);
    tp->is_sack_reneg = 0;
    return false;

另外,除了以上的快速恢复,对于F-RTO和Partial Loss恢复算法,在成功恢复之后,由函数tcp_try_undo_loss恢复到TCP_CA_Open状态,随之清空SACK Reneging标志。

/* Undo during loss recovery after partial ACK or using F-RTO. */
static bool tcp_try_undo_loss(struct sock *sk, bool frto_undo)
{
    struct tcp_sock *tp = tcp_sk(sk);

    if (frto_undo || tcp_may_undo(tp)) {
        tcp_undo_cwnd_reduction(sk, true);

        DBGUNDO(sk, "partial loss");

        inet_csk(sk)->icsk_retransmits = 0;
        if (frto_undo || tcp_is_sack(tp)) {
            tcp_set_ca_state(sk, TCP_CA_Open);
            tp->is_sack_reneg = 0;
        }

内核版本 5.0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章