安庆

导航

一个linux bbr存在的调用顺序bug

最近跟踪bbr的状态转换的代码,发现一个问题:

[11241.360364]mode=3,min_rtt_us=553,full_bw=0,cycle_idx=0,pacing_gain=0,cwnd_gain=0,rtt_cnt=0
[11241.360373] main mode=3,min_rtt_us=553,cur_bw=0,cycle_idx=0,pacing_gain=256,cwnd_gain=256,rtt_cnt=0,snd_cwnd=4
[11241.360377]mode=0,min_rtt_us=553,full_bw=0,cycle_idx=0,pacing_gain=256,cwnd_gain=256,rtt_cnt=0

可以看到,bbr的第一个处理,是mode=3,也就是执行链:

bbr_main-->bbr_update_model-->bbr_update_min_rtt-->
 
static void bbr_update_min_rtt(struct sock *sk, const struct rate_sample *rs)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct bbr *bbr = inet_csk_ca(sk);
    bool filter_expired;

    /* Track min RTT seen in the min_rtt_win_sec filter window: */
    filter_expired = after(tcp_jiffies32,
                   bbr->min_rtt_stamp + bbr_min_rtt_win_sec * HZ);
    if (rs->rtt_us >= 0 &&
        (rs->rtt_us <= bbr->min_rtt_us || filter_expired)) {
        bbr->min_rtt_us = rs->rtt_us;
        bbr->min_rtt_stamp = tcp_jiffies32;
    }

    if (bbr_probe_rtt_mode_ms > 0 && filter_expired &&
        !bbr->idle_restart && bbr->mode != BBR_PROBE_RTT) {
        bbr->mode = BBR_PROBE_RTT;  /* dip, drain queue */-----------------------第一个状态值

而按到正常的设计,一般来说,是先调用init再执行拥塞控制。按道理,filter_expired怎么会为1呢?因为链路刚建立,bbr->min_rtt_stamp  的初始化值是当前时间啊,还没有经过10s,

经过打点,发现bbr->min_rtt_stamp 是0,而不是bbr_init之后的值。然后继续分析代码:

static struct tcp_congestion_ops tcp_bbr_cong_ops __read_mostly = {
    .flags        = TCP_CONG_NON_RESTRICTED,
    .name        = "bbr",
    .owner        = THIS_MODULE,
    .init        = bbr_init,----------------------------初始化函数
    .cong_control    = bbr_main,------------------------bbr拥塞控制的主函数
    .sndbuf_expand    = bbr_sndbuf_expand,
    .undo_cwnd    = bbr_undo_cwnd,
    .cwnd_event    = bbr_cwnd_event,
    .ssthresh    = bbr_ssthresh,
    .tso_segs_goal    = bbr_tso_segs_goal,
    .get_info    = bbr_get_info,
    .set_state    = bbr_set_state,
};

这样就导致了,init函数的调用在bbr_main函数之后,而不是之前。

根据tcp链接的建立调用链:

tcp_rcv_synsent_state_process-->tcp_finish_connect-->tcp_init_transfer-->tcp_init_congestion_control-->icsk->icsk_ca_ops->init(sk);
这个时候发起请求的客户端会调用拥塞控制函数的init,
tcp_rcv_synsent_state_process-->tcp_ack-->tcp_cong_control-->
static void tcp_cong_control(struct sock *sk, u32 ack, u32 acked_sacked,
                 int flag, const struct rate_sample *rs)
{
    const struct inet_connection_sock *icsk = inet_csk(sk);

    if (icsk->icsk_ca_ops->cong_control) {------------------------bbr走这个分支
        icsk->icsk_ca_ops->cong_control(sk, rs);
        return;
    }

而在 tcp_rcv_synsent_state_process 函数中,

static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
                     const struct tcphdr *th)
{
。。。。
tcp_ack();
。。。。
smp_mb();

tcp_finish_connect(sk, skb);
。。。。
}

所以可以看出,拥塞函数调用是在拥塞init调用之前。

从github上最新的内核2019-2-21号的版本来看,也存在这个问题,不知道是google故意为之还是bug,个人认为应该是bug,毕竟影响了一段时间的状态以及初始值。

有心的童鞋可以去提交一个patch解决。

 

posted on 2019-02-21 17:25  _备忘录  阅读(642)  评论(0编辑  收藏  举报