TCP用户超时(UTO)

Linux内核提供了可设置的TCP用户超时时长(TCP User Timeout),其控制发送的未确认数据可保持多长时间,之后强制关闭连接。但是,内核不支持RFC5482定义的TCP UTO选项(User Timeout Option),不会将此设置通告给对端,其为本地超时时长。

UTO用户接口

应用层可通过setsockopt选项TCP_USER_TIMEOUT设置超时时长,内核将其保存在icsk_user_timeout变量中,单位为毫秒。

static int do_tcp_setsockopt(struct sock *sk, int level,
        int optname, char __user *optval, unsigned int optlen)
{
    switch (optname) {
    case TCP_USER_TIMEOUT:
        /* Cap the max time in ms TCP will retry or probe the window
         * before giving up and aborting (ETIMEDOUT) a connection.
         */
        if (val < 0)
            err = -EINVAL;
        else
            icsk->icsk_user_timeout = val;
        break;

UTO超时

如下函数tcp_clamp_rto_to_user_timeout,其确保RTO值不超出UTO定义的时长。如果用户未设置UTO,或者当前连接也未进行过报文重传,使用变量icsk_rto中的RTO值,来设置TCP的重传超时定时器。否则,计算重传报文开始到当前经过的时长,之后计算到UTO时长剩余的时长,如果剩余时长小于等于零,取值1,否者,取RTO值和剩余时长两者之间的较小值,作为重传超时定时器的时长。

static u32 tcp_clamp_rto_to_user_timeout(const struct sock *sk)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    u32 elapsed, start_ts;
    s32 remaining;

    start_ts = tcp_retransmit_stamp(sk);
    if (!icsk->icsk_user_timeout || !start_ts)
        return icsk->icsk_rto;
    elapsed = tcp_time_stamp(tcp_sk(sk)) - start_ts;
    remaining = icsk->icsk_user_timeout - elapsed;
    if (remaining <= 0)
        return 1; /* user timeout has passed; fire ASAP */

    return min_t(u32, icsk->icsk_rto, msecs_to_jiffies(remaining));
}

函数tcp_retransmit_timer中的调用inet_csk_reset_xmit_timer,使用以上的函数计算要设置的ICSK_TIME_RETRANS定时器的时长。注意最后一个参数为TCP_RTO_MAX(120秒),其定义了超时的最大时间值,UTO的设置值如果过大,将不会生效。

void tcp_retransmit_timer(struct sock *sk)
{
    ...
    inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
                  tcp_clamp_rto_to_user_timeout(sk), TCP_RTO_MAX);
    if (retransmits_timed_out(sk, net->ipv4.sysctl_tcp_retries1 + 1, 0))
        __sk_dst_reset(sk);

UTO超时判断

在报文超时之后,函数tcp_write_timeout负责超时相关处理,判断此连接是否已不可用。

static int tcp_write_timeout(struct sock *sk)
{
    ...
    if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
        i...
    } else {
        ...
        expired = retransmits_timed_out(sk, retry_until, icsk->icsk_user_timeout);

在函数retransmits_timed_out中判断此链接是否已经超时,如果第三个参数UTO不为零,检测在报文重传之后,是否已经经过了UTO的时长,为真的话,返回1,表示此连接已经超时。否则,在UTO为零时,通过重传次数和退避算法计算超时时长。

static bool retransmits_timed_out(struct sock *sk,
                  unsigned int boundary, unsigned int timeout)
{
    const unsigned int rto_base = TCP_RTO_MIN;
    unsigned int linear_backoff_thresh, start_ts;

    if (!inet_csk(sk)->icsk_retransmits)
        return false;

    start_ts = tcp_retransmit_stamp(sk);
    if (!start_ts) return false;

    if (likely(timeout == 0)) {
        linear_backoff_thresh = ilog2(TCP_RTO_MAX/rto_base);

        if (boundary <= linear_backoff_thresh)
            timeout = ((2 << boundary) - 1) * rto_base;
        else
            timeout = ((2 << linear_backoff_thresh) - 1) * rto_base +
                (boundary - linear_backoff_thresh) * TCP_RTO_MAX;
        timeout = jiffies_to_msecs(timeout);
    }
    return (s32)(tcp_time_stamp(tcp_sk(sk)) - start_ts - timeout) >= 0;

UTO与窗口探测定时器

如下零窗口探测定时器函数tcp_probe_timer,如果用户设置了UTO(变量icsk_user_timeout的值),并且发送队列中还有待发送报文,此报文等待的时长不能超过UTO,否则,认为此连接已经出错。

static void tcp_probe_timer(struct sock *sk)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct sk_buff *skb = tcp_send_head(sk);
    ...
    start_ts = tcp_skb_timestamp(skb);
    if (!start_ts)
        skb->skb_mstamp_ns = tp->tcp_clock_cache;
    else if (icsk->icsk_user_timeout &&
         (s32)(tcp_time_stamp(tp) - start_ts) > icsk->icsk_user_timeout)
        goto abort;

    ...
    if (icsk->icsk_probes_out >= max_probes) {
abort:      tcp_write_err(sk);

UTO与Keepalive定时器

函数tcp_keepalive_timer,如果此连接自从最后一次接收到对端ACK或者数据报文,到当前时刻还没有接收到其它报文,如果时长超出UTO时长,并且本地已经发送过探测报文,还是没有收到相应,则判定此连接已经出错。

在未设置UTO的情况下,此处以发送探测报文的次数为判定连接出错的依据。

static void tcp_keepalive_timer (struct timer_list *t)
{
    struct sock *sk = from_timer(sk, t, sk_timer);
    ...
	
    elapsed = keepalive_time_elapsed(tp);
    
    if (elapsed >= keepalive_time_when(tp)) {
        if ((icsk->icsk_user_timeout != 0 &&
            elapsed >= msecs_to_jiffies(icsk->icsk_user_timeout) &&
            icsk->icsk_probes_out > 0) ||

            (icsk->icsk_user_timeout == 0 &&
            icsk->icsk_probes_out >= keepalive_probes(tp))) {
            tcp_send_active_reset(sk, GFP_ATOMIC);
            tcp_write_err(sk);
            goto out;
        }

内核版本 5.0

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值