在了解 TCP 的基本机制后本文继续介绍 Linux 内核提供的链接队列、TW_REUSE、SO_REUSEPORT、SYN_COOKIES 等机制以优化生产环境中遇到的性能问题。

连接队列

Linux 内核会维护两个队列:

  • 半连接队列: syn_backlog, 服务端收到了 SYN 但未回复的连接, 队列的大小通过 net.ipv4.tcp_max_syn_backlog 指定
  • 全连接队列: accept_backlog, 三次握手完成但未调用 accept 的连接, 队列的大小为 min(net.core.somaxconn, backlog), 其中 backlog 是 listen(int sockfd,int backlog) 函数的参数

队列满后服务器会丢弃溢出的连接会导致的情况:

  • 半连接被丢弃后,客户端 SYN 会超时,客户端将重新尝试建立连接
  • 全连接被丢弃后,客户端认为连接存在,服务端认为不存在。客户端使用此连接发送数据包后服务端可以返回 RST (reset) 要求重置连接或者设置定时任务重传服务端SYN/ACK给客户端。

全连接队列溢出时服务器根据 net.ipv4.tcp_abort_on_overflow 参数决定如何处理:

  • 当 tcp_abort_on_overflow=0,服务端丢弃三次握手的ACK保持在 SYN_RECV 状态,设置一个定时任务重传服务端 SYN/ACK 包, 最大重试次数由 tcp_synack_retries 配置决定
  • 当 tcp_abort_on_overflow=1:服务端直接返回RST,要求重置连接

上述参数配置可以通过 sysctl -w 命令进行修改,例如:sysctl -w net.core.somaxconn=32768。机器重启后使用 sysctl -w 进行的修改会丢失,若需要持久化配置可以在 /etc/sysctl.conf 文件中增加一行 net.core.somaxconn= 4000 , 然后运行 sysctl -p 使修改生效。

连接队列溢出会导致无法与服务器建立新连接或者客户端出现大量 connection reset by peer 错误。

使用netstat -s | grep overflowed 可以检查是否出现全连接队列溢出的情况:

# netstat -s | grep overflowed
11451 times the listen queue of a socket overflowed

上面的输出表示某个 listen 状态的 socket 全连接队列溢出了 11451 次。这个数字是个累计值,可以多执行几次来判断溢出次数是否在上升。

使用 netstat -s | grep SYNs | grep dropped 可以检查是否出现半连接队列溢出的情况:

# netstat -s | grep SYNs | grep dropped
32404 SYNs to LISTEN sockets dropped

上面的输出表示有 32404 次 SYN 被丢弃,这个数字同样是累计值。

tw_reuse 和 tw_recycle

我们之前提到 time wait 状态会持续 60s, 过多 TIME_WAIT 状态的连接会占用非常有限的 TCP 端口导致无法建立新的连接。

net.ipv4.tcp_max_tw_buckets 参数控制系统中 TIME_WAIT 状态连接的最大数量。默认值是 NR_FILE*2,并且会根据系统的内存容量被调整。

检测 TIME_WAIT 是否过多

TIME_WAIT 状态的连接过多会在 dmesg 内核日志中报错: kernel: TCP: kernel: TCP: time wait bucket table overflow.

使用 netstat 命令可以查看各状态连接数:

#netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
CLOSE_WAIT 36
ESTABLISHED 35
TIME_WAIT 173

awk 命令不好记可以直接 wc 数行数:

# netstat -n | grep 'TIME_WAIT' | wc -l
172

tcp_timestamps

在正式介绍 tw_reuse 和 tw_recycle 之前我们先来介绍它们依赖的 tcp_timestamps 机制。

TCP 最早在 RFC1323 中引入了 timestamp 选项, timestamp 有两个目的:一是更精确地估算报文往返时间(round-trip-time, RTT) 二是防止陈旧的报文干扰正常的连接。

在引入 timestamps 机制之前,tcp 协议栈通过发送数据包和收到 ACK 的时间差来计算 RTT,在出现丢包时这一计算方式会出现问题。比如第一次发送的时间为 t1, 重传包的时间是 t2, 发送方在 t3 收到了 ack, 由于不知道这个 ack 包是确认第一个数据包还是确认重传包我们也无法确定 RTT 是 t3 - t2 还是 t3 - t1。

在设置 net.ipv4.tcp_timestamps=1 之后, 发送方在发送数据时将发送时间 timestamp 放在包里面, 接收方在收到数据包后返回的ACK包中将收到的timestamp返回给发送方(echo back),这样发送方就可以利用收到 ACK 包的时间和 ACK 包中的echo back timestamp 确定准确的 RTT。

TCP 序列号采用 32 位无符号整数存储,SEQ 在达到最大值后会从 0 开始再次递增,这种循环被称为序列号回绕。由于回绕现象存在ACK和重传机制无法通过序列号唯一确定数据包,从而导致错误。

上图中由于回绕出现了两个 SEQ=A 的包,接收方把上一次循环的 SEQ A 当做了当前的 SEQ A 丢弃了正常的数据包导致数据错误。

PAWS (Protection Against Wrapped Sequences,即防止序号回绕)就是为了避免这个问题而产生的,在开启 tcp_timestamps 选项情况下,一台机器发的所有 TCP 包都会带上发送时的时间戳,PAWS 要求连接双方维护最近一次收到的数据包的时间戳(Recent TSval),每收到一个新数据包都会读取数据包中的时间戳值跟 Recent TSval 值做比较,如果发现收到的数据包中时间戳不是递增的,则表示该数据包是过期的,就会直接丢弃这个数据包。

tw_reuse

开启 net.ipv4.tcp_tw_reuse 后客户端在调用 connect() 函数时,内核会随机找一个 time_wait 状态超过 1 秒的连接给新的连接复用,所以该选项只适用于连接发起方。

开启 tw_reuse 之后,tcp 协议栈通过 PAWS 机制来丢弃属于旧连接的数据包。因此必须打开 net.ipv4.tcp_timestamps 之后 tw_reuse 才会生效。

tw_recycle

net.ipv4.tw_recycle 同样利用 timestamp 来丢弃上一个连接的数据包从而不需要在 time_wait 状态等待太长时间即可关闭连接。

在打开 tw_recycle 后会自动启动 per-host paws 机制, 即对「对端 IP 做 PAWS 检查」,而非对「IP + 端口」四元组做 PAWS 检查。在开启 NAT 了网络中, 客户端 A 和 B 通过同一个 NAT 网关与服务器建立连接。 在服务器看来他们的 ip 地址相同,若 B 的 timestamp 比 客户端 A 的 小,那么由于服务端的 per-host 的 PAWS 机制的作用,服务端就会丢弃客户端主机 B 发来的 SYN 包。

由于 ipv4 地址紧张目前大多数设备均通过 NAT 接入网络(比如你的电脑和路由器), 所以在生产环境开启 tw_recycle极度危险。在 Linux 4.12 版本后,直接取消了 tw_recycle 参数。

SO_REUSEADDR 和 SO_REUSEPORT

在调用 bind 后可以使用 setsockopt 函数为 socket 设置 SO_REUSEPORT 或 SO_REUSEADDR 选项。

SO_REUSEADDR

因为服务进程关闭时服务器主动关闭了连接,进程关闭后有一些 Socket 处于 TIME_WAIT 状态,导致服务端重启后无法 bind 并 listen 原端口。

服务端在 bind 时设置 SO_REUSEADDR 则可以忽略 TIME_WAIT 状态的连接,重启后直接 bind 成功。SO_REUSEADDR 的作用仅限于让服务器重启后立即 bind 成功, 对性能无改善。

SO_REUSEPORT

SO_REUSEPORT 允许多个进程同时监听同一个ip:port。SO_REUSEPORT 允许多进程监听同一个端口避免只有一个 listen 进程成为系统的性能瓶颈,随着 CPU 核数的增加系统吞吐量会线性增加

主进程创建 socket、bind、 listen 之后,fork 出多个子进程,每个进程都在同一个 socket 上调用 accept 等待新连接进入:

这一模型利用了多核CPU的优势但仍有两个缺点:

  1. 单一 listener工作进程会成为瓶颈, 随着核数的扩展,性能并没有随着提升
  2. 很难做到CPU之间的负载均衡

在 Linux 3.9 引入 SO_REUSEPORT 之后允许多个进(线)程 listen 同一个端口,因此我们可以先 fork 多个进程然后在每个子进程中进行创建 socket、bind、listen、accept。 内核会负责在多个 CPU 之间进行负载均衡, 也解决了单一 listener 称为系统瓶颈的问题。

syn cookies

我们在前面提到当服务端收到来自客户端的 SYN 报文之后会向客户端回复 SYN + ACK 并将连接放入半连接队列中。若攻击者大量发送 SYN 报文服务端的半连接队列很快就会占满,导致服务器无法继续接收连接从而无法正常提供服务。这种攻击方式称为 SYN 洪泛(SYN Flood)攻击, 是一种典型的拒绝服务攻击方式。

syn cookies 的原理是服务端在握手过程中返回 SYN+ACK 后不分配资源存储半连接数据,而是根据 SYN 中的数据生成一个 Cookie 值作为自己的起始序列号。在收到客户端返回的 ACK 后通过其中的序列号判断 ACK 的合法性。由于建立连接的时候不需要保存半连接,从而可以有效规避 SYN Flood 攻击。

TCP连接建立时,双方的起始报文序号是可以任意的, SYN Cookies 利用这一特性构造初始序列号:

  1. 设t为一个缓慢增长的时间戳(典型实现是每64s递增一次)
  2. 设m为客户端发送的SYN报文中的MSS选项值
  3. 设s是连接的元组信息(源IP,目的IP,源端口,目的端口)和t经过密码学运算后的Hash值

则初始序列号n为:

  1. 高 5 位为t mod 32
  2. 接下来3位为m的编码值
  3. 低 24 位为s

客户端收到服务端的 SYN+ACK 后会向服务器返回 ACK, 且报文中ack = n + 1。接下来,服务器需要对 ack - 1 进行检查判断 t 是否超时以及 s 是否被篡改。若报文有效,则从中取出 mss 值建立连接。

SYN Cookies 同样存在一些缺点:

  • MSS的编码只有3位,因此最多只能使用 8 种MSS值
  • 服务器必须拒绝客户端SYN报文中的其他只在SYN和SYN+ACK中协商的选项,原因是服务器没有地方可以保存这些选项,比如Wscale和SACK

Linux 的 net.ipv4.tcp_syncookies 配置项可以开启 syn cookies 功能:

  • 0表示关闭SYN Cookies
  • 1表示在新连接压力比较大时启用SYN Cookies
  • 2表示始终使用SYN Cookies

实用TCP协议(2):TCP 参数优化的更多相关文章

  1. linux下TCP/IP及内核参数优化调优(转)

    Linux下TCP/IP及内核参数优化有多种方式,参数配置得当可以大大提高系统的性能,也可以根据特定场景进行专门的优化,如TIME_WAIT过高,DDOS攻击等等. 如下配置是写在sysctl.con ...

  2. TCP/IP及内核参数优化调优

    Linux下TCP/IP及内核参数优化有多种方式,参数配置得当可以大大提高系统的性能,也可以根据特定场景进行专门的优化,如TIME_WAIT过高,DDOS攻击等等.如下配置是写在sysctl.conf ...

  3. TCP/IP及内核参数优化调优(转)

    Linux下TCP/IP及内核参数优化有多种方式,参数配置得当可以大大提高系统的性能,也可以根据特定场景进行专门的优化,如TIME_WAIT过高,DDOS攻击等等.如下配置是写在sysctl.conf ...

  4. day28——C/S与B/S架构、网络通信原理、osi七层协议、UDP、TCP协议、TCP的三次握手与四次挥手

    day28 C/S B/S架构 C:client 客户端 B:browse浏览器 S:server 服务端 C/S C/S架构:基于客户端与服务端之间的通信 ​ QQ.游戏.皮皮虾 ​ 优点:个性化设 ...

  5. 计算机网络传输层之TCP协议(tcp协议特点、tcp报文段首部格式、tcp连接建立---三次握手、tcp连接释放---四次握手)

    文章转自:https://blog.csdn.net/weixin_43914604/article/details/105516090 学习课程:<2019王道考研计算机网络> 学习目的 ...

  6. Linux下tcp协议socket的recv函数返回时机分析(粘包)

    http://www.vckbase.com/index.php/wv/10http://blog.csdn.net/zlzlei/article/details/7689409 文章一: 当前在网络 ...

  7. python 全栈开发,Day35(TCP协议 粘包现象 和解决方案)

    一.TCP协议 粘包现象 和解决方案 黏包现象让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)执行远程命令的模块 需要用到模块subprocess sub ...

  8. 通信协议之HTTP,UDP,TCP协议

    1.UDP,TCP,HTTP之间的关系 tcp/ip是个协议组,它可以分为4个层次,即网路接口层,网络层,传输层,以及应用层, 在网络层有IP协议.ICMP协议.ARP协议.RARP协议和BOOTP协 ...

  9. 服务器编程入门(3)TCP协议详解

    问题聚焦:     本节从如下四个方面讨论TCP协议:     TCP头部信息:指定通信的源端端口号.目的端端口号.管理TCP连接,控制两个方向的数据流     TCP状态转移过程:TCP连接的任意一 ...

  10. 为什么MOBA、“吃鸡”游戏不推荐用tcp协议——实测数据

    欢迎大家前往云加社区,获取更多腾讯海量技术实践干货哦~ 作者:腾讯云游戏行业资深架构师 余国良 MOBA类和"吃鸡"游戏为什么对网络延迟要求高? 我们知道,不同类型的游戏因为玩法. ...

随机推荐

  1. 删除文件行末尾的^M符号方法

    有时发现某些文件的末尾总是带有^M符号 cat /etc/hosts.allow|col -b > /etc/hosts.allow.old

  2. 在js中如何区分深拷贝与浅拷贝?

    一.自我理解 简单来讲就是:深拷贝层层拷贝,浅拷贝只拷贝第一层. 在深拷贝中,新对象中的更改不会影响原对象,而在浅拷贝中,新对象中的更改,原对象中也会跟着改. 在深拷贝中,原对象与新对象不共享相同的属 ...

  3. 企业CICD规模化落地浅析

    本次分享的题目是<企业CICD规模化落地>,因此我们不会侧重讲解CICD是什么以及怎样做CICD,而是你已经知道怎样"玩转"CICD了,要如何在一个比较大的企业中规模化 ...

  4. 学习Java第17天

    今天差不多搞清了Javaweb的基本学习路线及网站开发的基本流程,以前好像走了不少弯路,不过还好,明天开始学习Javaweb sql 总结了许多 新的学习方法,记笔记很重要,学多少忘多少. 练习也很重 ...

  5. LeetCode673

    LeetCode每日一题2021.9.20 LeetCode673. 最长递增子序列的个数 思路 在最长上升子序列的转移时,维护一个 cnt 数组,表示 以 i 结尾的最长上升子序列个数 f[i] 表 ...

  6. qiankun 2.x 运行时沙箱 源码分析

    简介 从源码层面详细讲解了 qiankun 框架中的 JS 沙箱 和 样式沙箱的实现原理. 序言 沙箱 这个词想必大家应该不陌生,即使陌生,读完这篇文章也就不那么陌生了 沙箱 (Sandboxie) ...

  7. ApacheCN 计算机视觉译文集 20210218 更新

    新增了六个教程: OpenCV3 安卓应用编程 零.前言 一.设置 OpenCV 二.使用相机帧 三.应用图像效果 四.识别和跟踪图像 五.将图像跟踪与 3D 渲染相结合 六.通过 JNI 混合 Ja ...

  8. 报错 Illegal key size or default parameters

    简介: java中使用AES对称加密后,请求报错: Caused by: java.lang.RuntimeException: java.security.InvalidKeyException: ...

  9. C语言system函数

    我们今天来看看在windows操作系统下system () 函数详解(主要是在C语言中的应用) 注意:在windows下的system函数中命令可以不区别大小写! 函数名: system 功 能: 发 ...

  10. Servlet中@WebServlet属性详解

    感谢原文作者:想当一只小小攻城狮 原文链接:https://blog.csdn.net/weixin_45493751/article/details/100559683 在Servlet中,设置了@ ...