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

已标记关键词 清除标记
【为什么还需要学习C++?】 你是否接触很多语言,但从来没有了解过编程语言的本质? 你是否想成为一名资深开发人员,想开发别人做不了的高性能程序? 你是否经常想要窥探大型企业级开发工程的思路,但苦于没有基础只能望洋兴叹?   那么C++就是你个人能力提升,职业之路进阶的不二之选。 【课程特色】 1.课程共19大章节,239课时内容,涵盖数据结构、函数、类、指针、标准库全部知识体系。 2.带你从知识与思想的层面从0构建C++知识框架,分析大型项目实践思路,为你打下坚实的基础。 3.李宁老师结合4大国外顶级C++著作的精华为大家推出的《征服C++11》课程。 【学完后我将达到什么水平?】 1.对C++的各个知识能够熟练配置、开发、部署; 2.吊打一切关于C++的笔试面试题; 3.面向物联网的“嵌入式”和面向大型化的“分布式”开发,掌握职业钥匙,把握行业先机。 【面向人群】 1.希望一站式快速入门的C++初学者; 2.希望快速学习 C++、掌握编程要义、修炼内功的开发者; 3.有志于挑战更高级的开发项目,成为资深开发的工程师。 【课程设计】 本课程包含3大模块 基础篇 本篇主要讲解c++的基础概念,包含数据类型、运算符等基本语法,数组、指针、字符串等基本词法,循环、函数、类等基本句法等。 进阶篇 本篇主要讲解编程中常用的一些技能,包含类的高级技术、类的继承、编译链接和命名空间等。 提升篇: 本篇可以帮助学员更加高效的进行c++开发,其中包含类型转换、文件操作、异常处理、代码重用等内容。
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页