41

[译] TCP的SYN队列和Accept队列 | yoko blog

 4 years ago
source link: https://pengrl.com/p/46323/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

[译] TCP的SYN队列和Accept队列

2019-10-15 |0
| 1.8k

关于两个队列

整体流程图

首先我们必须明白,处于“LISTENING”状态的TCP socket,有两个独立的队列:

  • SYN队列(SYN Queue)
  • Accept队列(Accept Queue)

这两个术语有时也被称为“reqsk_queue”,“ACK backlog”,“listen backlog”,甚至“TCP backlog”,但是这篇文章中我们使用上面两个术语以免造成混淆。

SYN队列

SYN队列存储了收到SYN包的连接(对应内核代码的结构体:struct inet_request_sock)。它的职责是回复SYN+ACK包,并且在没有收到ACK包时重传,直到超时。在Linux下,重传的次数为:

1
2
$ sysctl net.ipv4.tcp_synack_retries
net.ipv4.tcp_synack_retries = 5

文档中对tcp_synack_retries的描述如下:

1
2
3
4
5
tcp_synack_retries - int整型

对于一个被动TCP连接,重传SYNACKs的次数。该值不能超过255。
默认值为5,如果初始RTO是1秒,那么对应的最后一次重传是31秒。
对应的最后一次超时是63秒之后。

发送完SYN+ACK之后,SYN队列等待从客户端发出的ACK包(也即三次握手的最后一个包)。当收到ACK包时,首先找到对应的SYN队列,再在对应的SYN队列中检查相关的数据看是否匹配,如果匹配,内核将该连接相关的数据从SYN队列中移除,创建一个完整的连接(对应内核代码的结构体:struct inet_sock),并将这个连接加入Accept队列。

Accept队列

Accept队列中存放的是已建立好的连接,也即等待被上层应用程序取走的连接。当进程调用accept(),这个socket从队列中取出,传递给上层应用程序。

这就是Linux处理SYN包的一个简单描述。顺便一提,当socket开启了TCP_DEFER_ACCEPTTCP_FASTOPEN时,工作方式将会有细微不同,本文不做介绍。

队列大小限制

应用程序通过调用系统调用listen(2),传入backlog参数,来设置SYN队列和Accept队列的最大大小。比如下面这样,将SYN队列和Accept队列的最大大小同时设置为1024:

1
listen(sfd, 1024)

注意,在4.3版本之前的内核,SYN队列的大小是用另一种方式计算。

SYN队列的最大大小以前是用net.ipv4.tcp_max_syn_backlog来配置,但是现在已经不再使用了。现在用net.core.somaxconn来同时表示SYN队列和Accept队列的最大大小。在我们的服务器上,我们将它设置为16k:

1
2
$ sysctl net.core.somaxconn
net.core.somaxconn = 16384

队列设置为多大合适

知道了上面这些信息后,你可能会问,队列设置为多大合适?

答案是:看情况。对于大多数的TCP服务来说,这并不太重要。比如,Go语言1.11版本之前,并没有提供设置队列大小的方法。

尽管如此,也存在一些合理的原因,需要增大队列的大小:

  • 当建立连接的请求速度确实很大时,即使是对于一个高性能的服务来说,SYN队列也可能需要设置的大一些。
  • SYN队列的大小,换言之就是等待ACK包的连接数。也即与客户端的平均往返时间越大,堆积在SYN队列中的连接就越多。对于那些大部分客户端都距离服务器很远的场景,比如说往返时间几百毫秒以上,可以将队列大小设置的大一些。
  • TCP_DEFER_ACCEPT选项如果打开了,会导致socket在SYN-RECV状态下维持更长的时间,也即增大了处于SYN队列中的时间。

但是,将backlog设置的过大也会带来不好的影响:

  • SYN队列中的每一个槽位都需要占用一些内存。当遇到SYN Flood攻击时,我们没有必要为这些发起攻击的包浪费资源。SYN队列中的inet_request_sock结构体,在4.14内核下,每个将占用256字节的内存。

linux下,如果想查看SYN队列的当前状态,我们可以使用ss命令来查询SYN-RECV状态的socket。比如如下执行结果,表示80端口的SYN队列中当前有119个元素,443端口则为78。

1
2
3
4
$ ss -n state syn-recv sport = :80 | wc -l
119
$ ss -n state syn-recv sport = :443 | wc -l
78

还可以通过我们的SystemTap脚本来观察这个数据:resq.stp

假如程序调用accept()不够快?

队列满图

如果程序调用accept()不够快会发生什么呢?

  • 后续收到的SYN包,不会被SYN队列处理
  • 后续收到的(用于建立连接的)ACK包,不会被SYN队列处理
  • TcpExtListenOverflows / LINUX_MIB_LISTENOVERFLOWS计数增加
  • TcpExtListenDrops / LINUX_MIB_LISTENDROPS计数增加

发生这种情况时,我们只能寄希望于程序的处理性能稍后能恢复正常,客户端重新发送被服务端丢弃的包。

内核的这种表现对于大部分服务来说是可接受的。顺便一提,可以通过调整net.ipv4.tcp_abort_on_overflow这个全局参数来修改这种表现,但是最好还是不要改这个参数。

可以通过查看nstat的计数来观察Accept队列溢出的状态:

1
2
$ nstat -az TcpExtListenDrops
TcpExtListenDrops 49199 0.0

但是这是一个全局的计数。观察起来不够直观,比如有时我们观察到它在增长,但是所有的服务程序看起来都是正常的。此时我们可以使用ss命令来观察单个监听端口的Accept队列大小:

1
2
3
$ ss -plnt sport = :6443|cat
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 1024 *:6443 *:*

Recv-Q这一列显示的是处于Accept队列中的socket数量,Send-Q显示的是队列的最大大小。在上面的例子中,我们发现并没有未被程序accept()的socket,但是我们依然发现ListenDrops计数在增长。

这是因为我们的程序只是周期性的短暂卡住不处理新的连接,而非永久性的不处理,过段时间程序又恢复了正常。这种情况下,用ss命令比较难观察这种现象,因此我们写了一个SystemTap脚本,它会hook进内核,把被丢弃的SYN包打印出来:

1
2
3
4
5
6
$ sudo stap -v acceptq.stp
time (us) acceptq qmax local addr remote_addr
1495634198449075 1025 1024 0.0.0.0:6443 10.0.1.92:28585
1495634198449253 1025 1024 0.0.0.0:6443 10.0.1.92:50500
1495634198450062 1025 1024 0.0.0.0:6443 10.0.1.92:65434
...

通过上面的操作,可以观察到哪些SYN包被ListenDrops影响了。从而我们也就可以知道哪些程序在丢失连接。

英文原文来自cloudflare的博客,地址如下:
SYN packet handling in the wild
英文原文在后半部分还介绍了SYN Cookies对于SYN Flood的影响,我在本文中没有翻译,感兴趣的可以看看原文。

原文链接: https://pengrl.com/p/46323/
原文出处: yoko blog (https://pengrl.com)
原文作者: yoko
版权声明: 本文欢迎任何形式转载,转载时完整保留本声明信息(包含原文链接、原文出处、原文作者、版权声明)即可。本文后续所有修改都会第一时间在原始地址更新。


Recommend

  • 45
    • www.tuicool.com 4 years ago
    • Cache

    [译] TCP的SYN队列和Accept队列

    首先我们必须明白,处于“LISTENING”状态的TCP socket,有两个独立的队列: SYN队列(SYN Queue) Accept队列(Accep...

  • 40
    • network.51cto.com 4 years ago
    • Cache

    TCP的SYN队列和Accept队列

    首先我们必须明白,处于“LISTENING”状态的TCP socket,有两个独立的队列: SYN队列(SYN Queue) Accept队列(Accept Q...

  • 11
    • studygolang.com 4 years ago
    • Cache

    TCP SYN队列与Accept队列详解

    李乐 尽信书,不如无书。 纸上得来终觉浅,绝知此事要躬行。 实验现象依赖于系统(如下)以及内核参数(附录);一切以实验结果为准。 cat /proc/version Linux version 3.10.0-693.el7.x86_64

  • 4
    • abcdxyzk.github.io 3 years ago
    • Cache

    TCP_NEW_SYN_RECV

    TCP_NEW_SYN_RECV 2020-09-10 04:40:00 https://git.kernel.org/pub/scm/linux/...

  • 6

    1. SYN Flood介绍    前段时间网站被攻击多次,其中最猛烈的就是TCP洪水攻击,即SYN Flood。    SYN Flood是当前最流行的DoS(拒绝服务攻击)与DDoS(分布式拒绝服务攻击)的方式之一,这是一种利用TCP协议缺陷,发送大量伪造的TCP连接请求,常用假冒的I...

  • 4
    • abcdxyzk.github.io 3 years ago
    • Cache

    nginx ipv6、TCP_DEFER_ACCEPT

    nginx ipv6、TCP_DEFER_ACCEPT 2019-01-28 04:06:00 curl 7.61.1 curl 7.61.1 TCP_DEFER_ACCEPT

  • 2
    • abcdxyzk.github.io 3 years ago
    • Cache

    tcp选项TCP_DEFER_ACCEPT

    tcp选项TCP_DEFER_ACCEPT 2018-07-09 00:24:00 http://www.pagefault.info/?p=346 TCP_DEFER_ACCEPT这个选项可能大家都知道,不过我这里会从源码和数据包来...

  • 39

    [译] Go语言使用TCP keepalive 2019-06-11 | 网络编程| 2.6k本篇文章首先简单介绍了TCP keepalive的机制以及运用场景。接着介绍...

  • 52

    [译] Go语言调度器 by povilas 2019-06-16 | Go| 1.4k英文原文:https...

  • 44

    yoko bloglearn and liveGo语言channel备忘录 2019-07-16 | ...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK