在了解 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. 从Apache官网下载Jar包步骤

    第一步:在官网找寻需要的包 Apache网址:http://commons.apache.org/ 在官网中,可以直接看到不同jar包的分类,如下图所示: 也可以点击官网左侧栏目里的 Release, ...

  2. 微服务 架构 php+go

    p.p1 { margin: 0; font: 13px "Helvetica Neue"; color: rgba(0, 162, 255, 1) } 微服务  架构  php+ ...

  3. Java Thread.currentThread()和This的区别

    感谢原文作者:王婷婷-Smily 原文链接:https://blog.csdn.net/dfshsdr/article/details/92760135 缘由 很多人认为多线程中的Thread.cur ...

  4. Redis分布式锁实现原理

    关于Redis分布式锁网上有很多优秀的博文,这篇文章仅作为我这段时间遇到的新问题的记录. 1.什么是分布式锁: 在单机部署的情况下,为了保证数据的一致性,不出现脏数据等,就需要使用synchroniz ...

  5. spring 注解注入bean

    通过注解方式注入bean,需要在配置类下注入bean 第一步,配置扫描文件夹 首先要在spring.xml中配置需要扫描的配置类 <context:componenet-scan base-pa ...

  6. Apache——网页优化与安全

    Apache--网页优化与安全 1.Apache 网页优化概述 2.网页压缩 3.网页缓存 4.隐藏版本信息 5.Apache 防盗链 1.Apache 网页优化概述: 企业中,部署Apache后只采 ...

  7. 痞子衡嵌入式:揭秘i.MXRT1060,1010上串行NOR Flash冗余程序启动设计

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT1060,1010上串行NOR Flash冗余程序启动设计. 工业产品设计里经常会有冗余程序/备份程序设计的需求,因为在工业 ...

  8. HTML笔记整理--下节

    欢迎来到HTML基础笔记下节部分! 内联样式 当特殊的样式需要应用到个别元素时,就可以使用内联样式. 使用内联样式的方法是在相关的标签中使用样式属性.样式属性可以包含任何 CSS 属性.以下实例显示出 ...

  9. 《PHP程序员面试笔试宝典》——如何进行自我介绍?

    本文摘自<PHP程序员面试笔试宝典> PHP面试技巧分享,PHP面试题,PHP宝典尽在"琉忆编程库". 自我介绍是面试中至关重要的一个步骤,很多面试官对求职者提出的第一 ...

  10. 以鶸ice为例,手撸一个解释器(一)明确目标

    代码地址 # HelloWorld.ice print("hello, world") 前言(废话) 其实从开始学习编译原理到现在已经有快半年的时间了,但是其间常常不能坚持看下去龙 ...