被抛弃的tcp_recycle

本文从一次巧合发现高版本Linux再也不支持tcp_tw_recycle,深刻研究了链接状态TIME_WAIT的原理,进而分析了tcp_tw族内核参数和如何应用它们对Linux的链接进行调优。
上篇文章回顾: 任播、自治系统与全球负载均衡


一、背景

最近准备搭建一个新的kubernetes集群,将内核从3.18更新到了4.14版本,并执行一些常规的优化操做。在执行sysctl -p操做时忽然报错以下:html

sysctl: cannot stat /proc/sys/net/ipv4/tcp_tw_recycle: No such file or directory复制代码


二、问题缘由

Linux 从4.12内核版本开始移除了 tcp_tw_recycle 配置。node

参考:[1]tcp:remove tcp_tw_recycle 4396e460linux

移除sysctl.conf中关于net.ipv4.tcp_tw_recycle的配置内容,再次尝试sysctl -p就再也不提示报错了。nginx


三、深刻解析

tcp_tw_recycle一般会和tcp_tw_reuse参数一块儿使用,用于解决服务器TIME_WAIT状态链接过多的问题。git


3.一、TIME_WAIT状态出现缘由与查看

让咱们回顾一下四次挥手的流程:面试

TIME_WAIT永远是出如今主动发送断开链接请求的一方(下文中咱们称之为客户),划重点:这一点面试的时候常常会被问到。
windows

客户在收到服务器端发送的FIN(表示"咱们也要断开链接了")后发送ACK报文,而且进入TIME_WAIT状态,等待2MSL(MaximumSegmentLifetime 最大报文生存时间)。对于Linux,字段为TCP_TIMEWAIT_LEN硬编码为30秒,对于windows为2分钟(可自行调整)。后端

为何客户端不直接进入CLOSED状态,而是要在TIME_WAIT等待那么久呢,基于以下考虑:安全

1.确保远程端处于关闭状态。也就是说须要确保客户端发出的最后一个ACK报文可以到达服务器。因为网络不可靠,有可能最后一个ACK报文丢失,若是服务器没有收到客户端的ACK,则会从新发送FIN报文,客户端就能够在2MSL时间段内收到这个这个重发的报文,而且重发ACK报文。但若是客户端跳过TIME_WAIT阶段进入了CLOSED,服务端始终没法获得响应,就会处于LAST-ACK状态,此时假如客户端发起了一个新链接,则会以失败了结。bash

异常流程以下:

2.防止上一次链接中的包,迷路后从新出现,影响新链接(通过2MSL,上一次链接中全部的重复包都会消失),这一点和为啥要执行三次握手而不是两次的缘由是同样的。

异常流程以下:

查看方式有两种:

(1)ss -tan state time-wait|wc -l

(2)netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'


3.二、TIME_WAIT的危害

对于一个处理大量链接的处理器TIME_WAIT是有危害的,表现以下:

1.占用链接资源

TIME_WAIT占用的1分钟时间内,相同四元组(源地址,源端口,目标地址,目标端口)的链接没法建立,一般一个ip能够开启的端口为net.ipv4.ip_local_port_range指定的32768-61000,若是TIME_WAIT状态过多,会致使没法建立新链接。

2.占用内存资源

这个占用资源并非不少,能够不用担忧。


3.三、TIME_WAIT的解决

能够考虑以下方式:

1.修改成长链接,代价较大,长链接对服务器性能有影响。

2.增长可用端口范围(修改net.ipv4.ip_local_port_range); 增长服务端口,好比采用80,81等多个端口提供服务; 增长客户端ip(适用于负载均衡,好比nginx,采用多个ip链接后端服务器); 增长服务端ip; 这些方式治标不治本,只能缓解问题。

3.将net.ipv4.tcp_max_tw_buckets设置为很小的值(默认是18000). 当TIME_WAIT链接数量达到给定的值时,全部的TIME_WAIT链接会被马上清除,并打印警告信息。但这种粗暴的清理掉全部的链接,意味着有些链接并无成功等待2MSL,就会形成通信异常。

4.修改TCP_TIMEWAIT_LEN值,减小等待时间,但这个须要修改内核并从新编译。

5.打开tcp_tw_recycle和tcp_timestamps选项。

6.打开tcp_tw_reuse和tcp_timestamps选项。


3.四、net.ipv4.tcp_tw_{reuse,recycle}

须要明确两个点:

解决方式已经给出,那咱们须要了解一下net.ipv4.tcp_tw_reuse和net.ipv4.tcp_tw_recycle有啥区别

1.两个选项都须要打开对TCP时间戳的支持,即net.ipv4.tcp_timestamps=1(默认即为1)。

RFC 1323中实现了TCP拓展规范,以便保证网络繁忙的状况下的高可用。并定义了一个新的TCP选项-两个四字节的timestamp字段,第一个是TCP发送方的当前时钟时间戳,第二个是从远程主机接收到的最新时间戳。

2.两个选项默认都是关闭状态,即等于0。


3.4.1 - net.ipv4.tcp_tw_reuse:更安全的设置

将处于TIME_WAIT状态的socket用于新的TCP链接,影响连出的链接。

[2]kernel sysctl 官方指南中是这么写的:

Allow to reuse TIME-WAIT sockets for new connections when it is safe from protocol viewpoint. Default value is 0.

It should not be changed without advice/request of technical experts.

协议安全主要指的是两点:

1.只适用于客户端(链接发起方)

net/ipv4/inet_hashtables.c

static int __inet_check_established(struct inet_timewait_death_row *death_row,
                    struct sock *sk, __u16 lport,
                    struct inet_timewait_sock **twp)
{
    /* ……省略…… */
    sk_nulls_for_each(sk2, node, &head->chain) {
            if (sk2->sk_hash != hash)
                        continue;
                        
            if (likely(INET_MATCH(sk2, net, acookie,
                    saddr, daddr, ports, dif))) {
                        if (sk2->sk_state == TCP_TIME_WAIT) {
                            tw = inet_twsk(sk2);
                            if (twsk_unique(sk, sk2, twp))
                                break;
            }
            goto not_unique;
        }
    }
    /* ……省略…… */
}复制代码

2.TIME_WAIT建立时间超过1秒才能够被复用

net/ipv4/tcp_ipv4.c

int tcp_twsk_unique(struct sock *sk, struct sock *sktw, void *twp)
{
    /* ……省略…… */
    if (tcptw->tw_ts_recent_stamp &&
        (!twp || (sock_net(sk)->ipv4.sysctl_tcp_tw_reuse &&
         get_seconds() - tcptw->tw_ts_recent_stamp > 1))) {
         /* ……省略…… */
         return 1;
    }
    return 0;
}复制代码

知足以上两个条件才会被认为是"safe from protocol viewpoint"的情况。启用net.ipv4.tcp_tw_reuse后,若是新的时间戳比以前存储的时间戳更大,那么Linux将会从TIME-WAIT状态的存活链接中选取一个,从新分配给新的链接出去的的TCP链接,这种状况下,TIME-WAIT的链接至关于只须要1秒就能够被复用了。

从新回顾为何要引入TIME-WAIT:

第一个做用就是避免新链接接收到重复的数据包,因为使用了时间戳,重复的数据包会由于时间戳过时被丢弃。

第二个做用是确保远端不是处于LAST-ACK状态,若是ACK包丢失,远端没有成功获取到最后一个ACK包,则会重发FIN包。直到:

1.放弃(链接断开)

2.收到ACK包

3.收到RST包

若是FIN包被及时接收到,而且本地端仍然是TIME-WAIT状态,那ACK包会被发送,此时就是正常的四次挥手流程。

若是TIME-WAIT的条目已经被新链接所复用,则新链接的SYN包会被忽略掉,而且会收到FIN包的重传,本地会回复一个RST包(由于此时本地链接为SYN-SENT状态),这会让远程端跳出LAST-ACK状态,最初的SYN包也会在1秒后从新发送,而后完成链接的创建,整个过程不会中断,只是有轻微的延迟。流程以下:

须要注意,链接被复用后,TWrecycled计数器会增长(/proc/net/netstat中TWrecycled值)


3.4.2 - net.ipv4.tcp_tw_recycle:更激进的设置

启用TIME_WAIT 状态的sockets的快速回收,影响全部连入和连出的链接

[3]kernel sysctl 官方指南是这么写的:

Enable fast recycling TIME-WAIT sockets. Default value is 0. It should not be changed without advice/request of technical experts.

此次表述的更加模糊,继续翻看源码:

net/ipv4/tcp_input.c

int tcp_conn_request(struct request_sock_ops *rsk_ops,
            const struct tcp_request_sock_ops *af_ops,
            struct sock *sk, struct sk_buff *skb)
{
 /* ……省略…… */
 if (!want_cookie && !isn) {
     /* ……省略…… */
     if (net->ipv4.tcp_death_row.sysctl_tw_recycle) {
         bool strict;

dst = af_ops->route_req(sk, &fl, req, &strict);
 
if (dst && strict &&
              !tcp_peer_is_proven(req, dst, true,
                      tmp_opt.saw_tstamp)) {
              NET_INC_STATS(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
              goto drop_and_release;
       }
     }
     /* ……省略…… */
     isn = af_ops->init_seq(skb, &tcp_rsk(req)->ts_off);
   }
/* ……省略…… */

drop_and_release:
            dst_release(dst);
       drop_and_free:
            reqsk_free(req);
       drop:
            tcp_listendrop(sk);
            return 0;
}复制代码

简单来讲就是,Linux会丢弃全部来自远端的timestramp时间戳小于上次记录的时间戳(由同一个远端发送的)的任何数据包。也就是说要使用该选项,则必须保证数据包的时间戳是单调递增的。

问题在于,此处的时间戳并非咱们一般意义上面的绝对时间,而是一个相对时间。不少状况下,咱们是无法保证时间戳单调递增的,好比使用了nat,lvs等状况。

而这也是不少优化文章中并无说起的一点,大部分文章都是简单的推荐将net.ipv4.tcp_tw_recycle设置为1,却忽略了该选项的局限性,最终形成严重的后果(好比咱们以前就遇到过部署在nat后端的业务网站有的用户访问没有问题,但有的用户就是打不开网页)。


3.五、被抛弃的tcp_tw_recycle

若是说以前内核中tcp_tw_recycle仅仅不适用于nat和lvs环境,那么从4.10内核开始,官方修改了时间戳的生成机制。

参考:[4]tcp: randomize tcp timestamp offsets for each connection 95a22ca

在这种状况下,不管任什么时候候,tcp_tw_recycle都不该该开启。故被抛弃也是理所应当的了。


四、总结

  • tcp_tw_recycle 选项在4.10内核以前还只是不适用于NAT/LB的状况(其余状况下,咱们也很是不推荐开启该选项),但4.10内核后完全没有了用武之地,而且在4.12内核中被移除.

  • tcp_tw_reuse 选项仍然可用。在服务器上面,启用该选项对于连入的TCP链接来讲不起做用,可是对于客户端(好比服务器上面某个服务以客户端形式运行,好比nginx反向代理)等是一个能够考虑的方案。

  • 修改TCP_TIMEWAIT_LEN是很是不建议的行为。


五、参考连接

[1]https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4396e46187ca5070219b81773c4e65088dac50cc

[2]https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/networking/ip-sysctl.txt?h=v4.11#n648

[3]https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/networking/ip-sysctl.txt?h=v4.11#n643

[4]https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=95a22caee396cef0bb2ca8fafdd82966a49367bb

[5]Coping with the TCP TIME-WAIT state on busy Linux servers:https://vincent.bernat.ch/en/blog/2014-tcp-time-wait-state-linux

[6]net.ipv4.tcp_tw_recycle は廃止されました ― その危険性を理解する:https://qiita.com/tmshn/items/b49f1b51bfc472968b30

[7]tcp_tw_reuse、tcp_tw_recycle 使用场景及注意事项:https://www.cnblogs.com/lulu/p/4149312.html


本文首发于公众号“小米运维”,点击查看原文