最快的 TCP 拥塞控制算法

Posted dog250

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最快的 TCP 拥塞控制算法相关的知识,希望对你有一定的参考价值。

声明:本文我并非表达这样的观点,即 “激进发包,就可以做出很好的协议”,我只是为想这么做的人提供一个如何这么做的方法。我说这样的算法是“快”的,因为它确实是快的,我并没有说它是“好”的,对于它的代价,我也提到了,但并不多说。

最快的 TCP 拥塞控制算法就是去掉拥塞控制算法。

给出最牛 X 的拥塞控制算法前,先交代背景。

先看 TCP 拥塞状态机的必要性。

为确保拥塞控制作用下界,防止拥塞控制算法违宗旨,从而加重拥塞或破坏公平,TCP 不得不收回一些拥塞控制权限,以悬崖勒马。比方说:

  • 发生丢包时接管算法,防止进一步拥塞。
  • 对激进传输的后果实施不可违抗的惩罚。

so?想维持硬编码的大 cwnd 是不可能的。巨大 cwnd 意味着激进传输,丢包后依然激进重传,这不是拥塞控制,这是制造拥塞。TCP 拥塞状态机阻止了拥塞控制算法的如此行为,它在你无法控制的逻辑里强制将 cwnd 拉低,这是高尚的。

之前说过 BBR 改变了这一切。

为使 BBR 靠采集 Delivery Rate 判断拥塞,不得不给 BBR 更多控制权,对丢包进行另一种解释而避开拥塞状态机的动作 。

但至少对 Linux TCP 而言,拥塞状态机和拥塞控制算法是分离的,为支持 BBR,不得不重构拥塞状态机实现,引入全权接管算法的 cong_control 回调函数,简而言之,一切都在该回调中完成。

对于 4.9 以上版本内核,终于可以写一个“固定 cwnd”的算法了,这将是最快的算法:

#include <linux/module.h>
#include <net/tcp.h>

static u32 const_cwnd = 1000;
module_param(const_cwnd, uint, 0664);

static void const_main(struct sock *sk, const struct rate_sample *rs)

        tcp_sk(sk)->snd_cwnd = const_cwnd;


static void const_cong_avoid(struct sock *sk, u32 ack, u32 acked)

        tcp_sk(sk)->snd_cwnd = const_cwnd;


static void const_init(struct sock *sk)

        tcp_sk(sk)->snd_cwnd = const_cwnd;


static u32 const_ssthresh(struct sock *sk)

        return const_cwnd;


static void const_set_state(struct sock *sk, u8 new_state)

        if (new_state == TCP_CA_Loss) 
                tcp_sk(sk)->snd_cwnd = const_cwnd;
        


static u32 const_undo(struct sock *sk)

        return tcp_sk(sk)->snd_cwnd;


static struct tcp_congestion_ops tcp_const_cong_ops __read_mostly = 
        .name                = "const",
        .undo_cwnd        = const_undo,
        .init                = const_init,
        .cong_control        = const_main,
        /*.cong_avoid        = const_cong_avoid,*/
        .ssthresh        = const_ssthresh,
        .set_state        = const_set_state,
;

static int __init const_register(void)

        return tcp_register_congestion_control(&tcp_const_cong_ops);


static void __exit const_unregister(void)

        tcp_unregister_congestion_control(&tcp_const_cong_ops);


module_init(const_register);
module_exit(const_unregister);

MODULE_LICENSE("GPL");

在 4.9 内核之前,若要如此效果,非要 kprobe/systemtap 强行 hack,给一个 stap 脚本 setcwnd.stp:

#!/usr/local/bin/stap -g

// 使用方法:./setcwnd.stp 10000
%
#include <linux/tcp.h>
%

function _set_cwnd(skk:long, ptype:long, pconst_cwnd:long)
%
        struct sock *sk = (struct sock *)STAP_ARG_skk;
        int const_cwnd = (int)STAP_ARG_pconst_cwnd;
        struct tcp_sock *tp = tcp_sk(sk);
        // 可设置更复杂过滤规则
        if (htons(inet_sk(sk)->inet_dport) == 1234) 
                tp->snd_cwnd = const_cwnd;
        
%

probe kernel.function("tcp_write_xmit")

        _set_cwnd($sk, 1, $1);


probe kernel.function("tcp_xmit_recovery")

        _set_cwnd($sk, 0, $1);

否则,把上面代码的注释打开,换把 cong_control 回调注释掉,设置的 const_cwnd 是维持不住的。
效果不展示,自己试,25 Gbps 带宽,丢包率调到 25% ,BBR/CUBIC 已经憋死,const 依然可保持 24 Gbps 无损带宽。Makefile 如下,直接make:

obj-m += tcp_const.o

all:
        make -C /lib/modules/`uname -r`/build M=`pwd` modules

使能命令:

sysctl -w net.ipv4.tcp_congestion_control=const

调整 cwnd 命令:

echo 50000 >/sys/module/tcp_const/parameters/const_cwnd

终极问题,可实用吗?

拥塞控制本即社会博弈,const 算法本质上是 “取消了拥塞控制”,预期任何使用者均不会设置一个保守 cwnd。

但除重传代价极大外,有另一个阻止部署 const 算法的因素,“所有参与方都付出大代价后,价值取向就反转了。”,流氓的淫威建立在大多数人总是退让基础上,不可能人人都是流氓,流氓只有一两个时,流氓才有力量。

不用担心,若不计带宽成本,少量人使用是毫无问题的。如今网络带宽承载力很高,即使最后一公里边缘带宽也不是凭一两条流能淹没的,何况运营商只是软限制超卖,就更别提骨干网了。因此:

  • 使用const算法不会制造拥塞,让你无愧。
  • 使用const算法可以带来收益,让你无悔。
  • 使用const算吧会增加重传率,让你知情。

可劲造。

很久以前我就想硬编码 cwnd,比如我有一条很牛的专线,又不想受到偶尔误码丢包对带宽的影响,但总是 hold 不住硬编码的 cwnd,因为除了在 cc 中以外,TCP 拥塞状态机也会设置 cwnd 想取消这些修改必须修改内核或采用 hack 手段。BBR 发布以后,做这件事成了可能。最近也有经理问到,所以就写了本文。不过还可以做得更好点,为每个连接硬编码一个 cwnd:增加一个 socket option,将这个硬编码的 cwnd 保存在每个 tcp_sock 对象里。

浙江温州皮鞋湿,下雨进水不会胖。

以上是关于最快的 TCP 拥塞控制算法的主要内容,如果未能解决你的问题,请参考以下文章

TCP拥塞控制及BBR原理分析

让人很容易误解的TCP拥塞控制算法

让人非常easy误解的TCP拥塞控制算法

面试热点|理解TCP/IP传输层拥塞控制算法

理解TCP/IP传输层拥塞控制算法

理解TCP/IP传输层拥塞控制算法