/ TCP

对 Linux TCP 的若干疑点和误会

整理了一下 Linux 的 TCP 相关的几个疑点和对以往错误认识的纠正。主要是系统出现大量 TIME_WAIT 和 SYN 请求的一些问题,以及一些 TCP 内核参数的意义。

提到的参数(选项)大都在 /proc/sys/net/ipv4/,如果需要永久生效,希望做到重启也不变,可以修改 /etc/sysctl.confnet.ipv4.tcp_*

1. 关于 TIME_WAIT

按照网络上很多文章的说法,当 Linux 服务器上出现大量 TIME_WAIT 状态时,可以通过对 TCP 相关的几个内核参数的修改,来减少 TIME_WAIT 状态。最常看到的是这两个

  • tcp_tw_reuse
  • tcp_tw_recycle

tcp_tw_reuse 设置为 1 时,就允许系统重用处于 TIME_WAIT 状态的 socket。如果 tcp_timestamp 没有设置为 1,只把 tcp_tw_reuse 设置为 1 是无效的。

tcp_tw_recycle 设置为 1 会开启系统对 TIME_WAIT 状态的 socket 的快速回收。开启这个功能,系统就会存下 TCP 连接的时间戳,当同一个 IP 地址过来的包的时间戳小于缓存的时间戳,系统就直接丢包,“回收”这个 socket。这个选项同样需要开启 tcp_timestamp 才生效。

开启这个功能是有很大风险的,如前面所说,会根据同一个 IP 来的包得时间戳来判断是否丢包,而时间戳是根据发包的客户端的系统时间得来的,如果服务端收到的包是同一出口 IP 而系统时间不一样的两个客户端的包,就有可能会丢包,可能出现的情况就是一个局域网内有的客户端能连接服务端,有的不能。

相对 tcp_tw_recycletcp_tw_reuse 是比较安全的一个选项,但是也可能会导致问题。按照官方文档的说法

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

还有一个与 TIME_WAIT密切相关的参数,tcp_max_tw_buckets 指定系统在同一时间最多能有多少 TIME_WAIT 状态,当超过这个值时,系统会直接干掉这个 TIME_WAIT 的 socket。不要为了减少 TIME_WAIT 就把这个值改小。

2. SYN 和 listen 函数的 backlog

先从 backlog 说起,根据 Linux 的文档,listen 函数的 backlog 等待应用程序 accept 的连接队列的长度,这个队列里是 ESTABLISHED 状态的连接,而不是还未完成三次握手的连接。如果要设置未完成连接的队列长度,可以设置 tcp_max_syn_backlog 这个参数。

未完成连接,也就是系统可能收到对端的 SYN,已经发送了 SYN/ACK,在等待对端的 ACK。DDOS 攻击中的 SYN 攻击,就是发送大量的 SYN,但是不发送 ACK,服务器大量的资源在维持这些未完成的连接,等待 ACK,这时 netstat 可以看到大量的 SYN_RECV 状态。

不少文章里提到为了应对这种现象,为了能让服务端尽量提供服务,就增大 tcp_max_syn_backlog 的值,以使服务器能够接受更多的连接请求,这样做是合情合理的。但是也有不少文章里建议开启 tcp_syncookies 来减少 SYN 攻击的影响。tcp_syncookies 可以说是一个无奈的参数,它使严重违反 TCP 协议的。开启这个选项,系统会根据缓存的未完成的连接的信息算一个值,也就是 cookie,返回给客户端,如果客户端是正常的连接请求,再往服务端发包时,会把这个 cookie 带回来,服务端就会查看这个 cookie,类似加了一个验证的过程。

如果服务器收到大量的正常请求,导致服务器负载非常高,不要为了应对这种情况开启 tcp_syncookies。文档里用了大写的 “MUST NOT” 来提醒我们,然后很良心地建议去修改 tcp_max_syn_backlog, tcp_synack_retries, tcp_abort_on_overflow

tcp_max_syn_backlog 已经提到过,设置更高的值,允许系统保持更多的“未完成连接”。按照在 listen 函数的文档所说,当开启了 tcp_syncookies 之后,tcp_max_syn_backlog 的值就失去了原本意义,该值被忽略

系统发回给客户端 SYN/ACK 后,如果没收到客户端的 ACK,会重发 SYN/ACK,tcp_synack_retries 用于指定重发 SYN/ACK 的次数,默认是 5 次,可以适当减小这个值。

tcp_abort_on_overflow 是个跟应用程序更相关的参数,当被设置为 1 时,如果应用程序处理速度比较慢,来不及接受新的连接,系统就直接丢弃这个连接,给对端发个 RST。这个选项也要谨慎开启。

有时候会看到 somaxconn (/proc/sys/net/core/somaxconn) 这个值跟 tcp_max_syn_backlog 一起出现,其实 somaxconnlistenbacklog 参数的意义是基本一样的,指定用于保存已完成三次握手等待 accept 的队列长度,somaxconnbacklog 可以设置的最大值。当在应用程序里设置 backlog 的值大于 somaxconn 参数时,系统也会默默地把 backlog 减小为 somaxconn 指定的值。