SACK報文亂序級別reordering

內核默認的初始亂序級別爲TCP_FASTRETRANS_THRESH(3),最大值爲300。即當接收到三個重複ACK報文時,觸發快速重傳。

#define TCP_FASTRETRANS_THRESH 3

static int __net_init tcp_sk_init(struct net *net)
{
    net->ipv4.sysctl_tcp_reordering = TCP_FASTRETRANS_THRESH;

    net->ipv4.sysctl_tcp_retrans_collapse = 1;
    net->ipv4.sysctl_tcp_max_reordering = 300;

1 - 手動調整亂序級別

可通過如下的PROC文件tcp_reordering和tcp_max_reordering調整亂序當前級別和最大亂序級別。

$ cat /proc/sys/net/ipv4/tcp_reordering
3
$ cat /proc/sys/net/ipv4/tcp_max_reordering 
300

套接口在初始化時,使用所在網絡命名空間對應的初始亂序級別,即變量sysctl_tcp_reordering的值。

void tcp_init_sock(struct sock *sk)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct tcp_sock *tp = tcp_sk(sk);

    tp->reordering = sock_net(sk)->ipv4.sysctl_tcp_reordering;

2 - SACK亂序調整

如果當前未確認序號low_seq(也未重傳)不在最高的SACK確認序號之前,表明沒有發生亂序,不調整reordering。否則,亂序度由最高SACK確認序號減去當前序號low_seq而得。套接口亂序級別reordering的單位爲MSS,檢查當前的亂序metric是否超出記錄值,如果爲真,更新亂序級別reordering(MSS的整數倍),亂序級別不超過限定的最大值(tcp_max_reordering)。

static void tcp_check_sack_reordering(struct sock *sk, const u32 low_seq, const int ts)
{
    const u32 mss = tp->mss_cache;

    fack = tcp_highest_sack_seq(tp);
    if (!before(low_seq, fack)) return;

    metric = fack - low_seq;
    if ((metric > tp->reordering * mss) && mss) {
        ...
        tp->reordering = min_t(u32, (metric + mss - 1) / mss,
                       sock_net(sk)->ipv4.sysctl_tcp_max_reordering);
    }

    /* This exciting event is worth to be remembered. 8) */
    tp->reord_seen++;
    NET_INC_STATS(sock_net(sk), ts ? LINUX_MIB_TCPTSREORDER : LINUX_MIB_TCPSACKREORDER);

3 - SACK亂序檢測

在SACK處理函數tcp_sacktag_write_queue中,如果當前的擁塞狀態等於TCP_CA_Loss,並且沒有處在可撤銷階段,不必更新亂序級別。否則,進行更新。

static int tcp_sacktag_write_queue(struct sock *sk, const struct sk_buff *ack_skb,
            u32 prior_snd_una, struct tcp_sacktag_state *state)
{

    state->reord = tp->snd_nxt;
    ...
    if (inet_csk(sk)->icsk_ca_state != TCP_CA_Loss || tp->undo_marker)
        tcp_check_sack_reordering(sk, state->reord, 0);

初始情況下reord的值設置爲SND.NXT的值,即發送報文的最大序號,這裏首先假設不會發生亂序的情況。在SACK處理函數tcp_sacktag_one中,如果接收到D-SACK,並且報文被重傳過(重傳導致的DSACK,排除網絡中錯誤的複製報文等情況),並且報文的起始序號在reord之前,更新reord作爲亂序的源頭。

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)
{
    struct tcp_sock *tp = tcp_sk(sk);

    /* Account D-SACK for retransmitted packet. */
    if (dup_sack && (sacked & TCPCB_RETRANS)) {
        if (tp->undo_marker && tp->undo_retrans > 0 &&
            after(end_seq, tp->undo_marker))
            tp->undo_retrans--;
        if ((sacked & TCPCB_SACKED_ACKED) &&
            before(start_seq, state->reord))
                state->reord = start_seq;
    }

另外,如果報文沒有被重傳過,這裏接收到對其SACK確認,如果其序號小於已經存在的最高SACK確認序號,表明發生了亂序,因爲其本應當已經被SACK確認。更新亂序源頭reord。

    if (!(sacked & TCPCB_SACKED_ACKED)) {

        if (sacked & TCPCB_SACKED_RETRANS) {
            ...
        } else {
            if (!(sacked & TCPCB_RETRANS)) {
                /* New sack for not retransmitted frame,
                 * which was in hole. It is reordering.
                 */
                if (before(start_seq, tcp_highest_sack_seq(tp)) &&
                    before(start_seq, state->reord))
                    state->reord = start_seq;

4 - 擁塞撤銷與SACK亂序

在TCP_CA_Recovery擁塞狀態,如果接收到推進SND.UNA的ACK確認報文,執行部分擁塞撤銷,如下函數tcp_try_undo_partial。如果此ACK報文由於延時,導致了套接口進入TCP_CA_Recovery狀態,而其又未能確認進入TCP_CA_Recovery狀態時的全部發送報文。這裏,將亂序的源頭設置爲接收到此ACK報文之前的SND.UNA,也即此ACK報文的起始序號。

static bool tcp_try_undo_partial(struct sock *sk, u32 prior_snd_una)
{
    struct tcp_sock *tp = tcp_sk(sk);

    if (tp->undo_marker && tcp_packet_delayed(tp)) {
        /* Plain luck! Hole if filled with delayed
         * packet, rather than with a retransmit. Check reordering.
         */
        tcp_check_sack_reordering(sk, prior_snd_una, 1);

5 - 重傳隊列中檢查亂序

在清理重傳隊列時,如果其中的某個報文即沒有被重傳過,也沒有被SACK確認過,而是直接由TCP頭部的ACK字段所確認,更新reord亂序源頭。如果此序號值小於在接收到此報文之前的最大SACK確認序號,表明發生了亂序。

static int tcp_clean_rtx_queue(struct sock *sk, u32 prior_fack,
                   u32 prior_snd_una, struct tcp_sacktag_state *sack)
{
    u32 reord = tp->snd_nxt; /* lowest acked un-retx un-sacked seq */
	 
    for (skb = skb_rb_first(&sk->tcp_rtx_queue); skb; skb = next) {
        ...

        if (unlikely(sacked & TCPCB_RETRANS)) {
            ...
        } else if (!(sacked & TCPCB_SACKED_ACKED)) {
            ...
            if (before(start_seq, reord))
                reord = start_seq;
        }
    ...
    if (flag & FLAG_ACKED) {
        if (tcp_is_reno(tp)) {
            ...
        } else {
            /* Non-retransmitted hole got filled? That's reordering */
            if (before(reord, prior_fack))
                tcp_check_sack_reordering(sk, reord, 0);

內核版本 5.0

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