网络配置之TCP建连
Linux 内核往往需要采取不同的策略去处理各种各样的网络场景,在一些情况下,Linux 内核的默认网络配置可能未必会适用我们的场景,这就可能导致我们的业务出现一些莫名其妙的行为,比如我们可能会遇到以下问题:
- Client 为什么无法和 Server 建立连接呢?
- 三次握手都完成了,为什么会收到 Server 的 reset 呢?
- 建立 TCP 连接怎么会消耗这么多时间?
- 系统中为什么会有这么多处于 time-wait 的连接?该这么处理?
- 系统中为什么会有这么多 close-wait 的连接?
- 针对我的业务场景,这么多的网络配置项,应该要怎么配置呢?
所以,要想让业务行为符合预期,我们需要了解 Linux 的相关网络配置,让这些配置更加适用于我们的业务。
TCP 建连的相关配置项
TCP建连过程
TCP 连接的建立是一个从 Client 侧调用 connect(),到 Server 侧 accept() 成功返回的过程。
在整个 TCP 建立连接的过程中,各个行为都有配置选项来进行控制。
三次握手:
1. tcp_syn_retries SYN超时重传
首先,Client 调用 connect() 后,Linux 内核就开始进行三次握手,首先Client会发送一个Syn包给Server,如果过了 1 秒 ,还没有收到 Server 的响应,那么就会进行第一次重传;如果经过 2s 的时间还没有收到 Server 的响应,就会进行第二次重传;一直重传 tcp_syn_retries 次。
tcp_syn_retries 的默认值是 6,也就是说如果 SYN 一直发送失败,会在(1 + 2 + 4 + 8 + 16+ 32 + 64)秒,即 127 秒后产生 ETIMEOUT 的错误。
我们可以将数据中心内部服务器的 tcp_syn_retries 给调小,这里推荐设置为 2,来减少阻塞的时间。因为对于数据中心而言,它的网络质量是很好的,如果得不到 Server 的响应,很可能是 Server 本身出了问题。
net.ipv4.tcp_syn_retries = 2
Tips: 有些情况下 1s 的阻塞时间可能都很久,所以有的时候也可以将三次握手的初始超时时间从默认值 1s 调整为一个较小的值,比如 100ms,这样整体的阻塞时间就会小很多。
2. tcp_max_syn_backlog 防止大量SYN 包被丢弃
如果 Server 没有响应 Client 的 SYN,还有可能是因为 Server 太忙没有来得及响应,或者是Server 已经积压了太多的半连接(incomplete)而无法及时去处理。
半连接
即收到了 SYN 后还没有回复 SYNACK 的连接,Server 每收到一个新的 SYN 包,都会创建一个半连接,然后把该半连接加入到半连接队列(syn queue)中。
syn queue 的长度就是 tcp_max_syn_backlog 这个配置项来决定的,当系统中积压的半连接个数超过了该值后,新的 SYN 包就会被丢弃。
Tips: 我们可以适当增加该值,防止建立连接时Servcer端创建大量的半连接而导致一部分SYN包被丢弃:
net.ipv4.tcp_max_syn_backlog = 16384
3. tcp_syncookies 防止SYN Flood 攻击
Server 中积压的半连接较多的另一个原因是: 有些恶意的 Client 在进行 SYN Flood 攻击。
SYN Flood 攻击
Client 高频地向 Server 发 SYN 包,并且这个 SYN 包的源 IP 地址不停地变换,那么 Server 每次接收到一个新的 SYN 后,都会给它分配一个半连接,Server 的 SYNACK 根据之前的 SYN 包找到的是错误的 Client IP, 所以也就无法收到 Client 的 ACK 包,导致无法正确建立 TCP 连接,这就会让Server 的半连接队列耗尽,无法响应正常的 SYN 包。
SYN Cookies 机制
在 Server 收到 SYN 包时,不去分配资源来保存 Client 的信息,而是根据这个 SYN 包计算出一个 Cookie 值,然后将 Cookie 记录到 SYNACK 包中发送出去。
对于正常的连接,该 Cookies 值会随着 Client 的 ACK 报文被带回来。然后 Server 再根据这个 Cookie 检查这个 ACK 包的合法性,如果合法,才去创建新的 TCP 连接。通过这种处理,SYN Cookies 可以防止部分 SYN Flood 攻击。
Tips: 对于 Linux 服务器而言,推荐开启 SYN Cookies:
net.ipv4.tcp_syncookies = 1
5. tcp_synack_retries SYNARK超时重传
第一次握手成功之后,Server会向Client发送SYNARK包来进行第二次握手。
Server 向 Client 发送的 SYNACK 包也可能会被丢弃,或者因为某些原因而收不到 Client 的响应,这个时候 Server 也会重传 SYNACK 包。
**Tips: **重传过程跟SYN重传机制一样,不同的是默认值为5,所以这里我们也推荐设置为2:
net.ipv4.tcp_synack_retries = 2
6. backlog/somaxconn
Client 在收到 Serve 的 SYNACK 包后,就会发出 ACK,Server 收到该 ACK 后,三次握手就完成了,即产生了一个 TCP 全连接(complete),它会被添加到全连接队列(accept queue)中。然后 Server 就会调用 accept() 来完成 TCP 连接的建立。
全连接队列(accept queue)
全连接队列(accept queue)的长度也有限制,目的就是为了防止 Server 不能及时调用 accept() 而浪费太多的系统资源。
全连接队列的长度由Server端的listen(socketfd, backlog)的backlog的参数控制,其最大值为somaxconn。
Tips: somaxconn 在 5.4 之前的内核中,默认都是 128(5.4 开始调整为了默认 4096),建议将该值适当调大一些:
net.core.somaxconn = 16384
7. tcp_abort_on_overflow 允许重试
当服务器中积压的全连接个数超过该值后,新的全连接就会被丢弃掉。Server 在将新连接丢弃时,有的时候需要发送 reset 来通知 Client,这样 Client 就不会再次重试了。
Server 如果来不及 accept() 而导致全连接队列满,这往往是由瞬间有大量新建连接请求导致的,正常情况下 Server 很快就能恢复,然后 Client 再次重试后就可以建连成功了, 我们可以将tcp_abort_on_overflow参数设置为0,给Client一次重试机会:
net.ipv4.tcp_abort_on_overflow = 0 #default is 0
当accept() 成功返回后,一个新的 TCP 连接就建立完成了,TCP 连接进入到了 ESTABLISHED 状态。
建连全过程:
从 Client 调用 connect(),到 Server 侧 accept() 成功返回这一过程中的 TCP 状态转换。这些状态都可以通过 netstat 或者 ss 命令来看。至此,Client 和 Server 两边就可以正常通信了。