环境centos7内核版本3.10.0-327.el7.x86_64、nginx1.10.3
一、先来回顾下三次握手里面涉及到的问题:
Linux内核协议栈为一个tcp连接管理使用两个队列,一个是半链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求),一个是accpetd队列(用来保存处于established状态,但是应用层没有调用accept取走的请求)。
1.半连接队列 syn squeue (roundup_pow_of_two(max_t(u32,min(somaxconn,sysctl_max_syn_backlog,backlog),8) +1)) 用来保存 SYN_SENT 以及 SYN_RECV 的信息。
2.全连接队列 accept queue(min(somaxconn, backlog)), 保存 ESTAB 的状态。
a. tcp_max_syn_backlog参数位于/proc/sys/net/ipv4/tcp_max_syn_backlog,默认是128,可以通过/etc/sysctl.conf文件调整。
b. somaxconn参数位与/proc/sys/net/core/somaxconn,默认是128, 表示最多有129个established链接等待accept。可以通过/etc/sysctl.conf文件调整。
c. backlog参数这个和具体的应用程序有关,比如nginx默认为511
这个可以通过 ss -lnt 的 Send-Q 确认:
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 511 *:80 *:*
可以通过适当的增大 nginx 的 backlog 以及 somaxconn 来增大队列:
listen 80 backlog=1638;
Tomcat默认为100,也可通过server.xml中的<Connector acceptCount="300"/>来调整
ss获取到的 Recv-Q/Send-Q 在 LISTEN 状态以及非 LISTEN 状态所表达的含义是不同的。从 / source/net/ipv4/tcp_diag.c源码中可以看到二者的区别:
LISTEN 状态: Recv-Q 表示的当前等待服务端调用 accept 完成三次握手的 listen backlog 数值,也就是说,当客户端通过 connect() 去连接正在 listen() 的服务端时,这些连接会一直处于这个 queue 里面直到被服务端 accept();Send-Q 表示的则是最大的 listen backlog 数值,这就就是上面提到的 min(backlog, somaxconn) 的值。
其余状态: 非 LISTEN 状态。Recv-Q 表示 receive queue 中的 bytes 数量;Send-Q 表示 send queue 中的 bytes 数值
当用户进程调用服务器程序的listen()时,内核将创建一个队列来存储积压连接。
listen() -> inet_listen() -> inet_csk_listen_start() -> reqsk_queue_alloc()
二、我们先来看半连接数,内核版本为3.10.0-327.el7.x86_64
首先定位到tcp_v4_conn_request函数, (source/net/ipv4/tcp_ipv4.c)
跟进关键函数inet_csk_reqsk_queue_is_full,(source/include/net/inet_connection_sock.h)
跟进关键函数reqsk_queue_is_full, (/source/include/net/request_sock.h)
查找qlen和max_qlen_log的定义, (/source/include/net/request_sock.h)
关键是如何计算max_qlen_log, (/source/net/socket.c)
sock->ops->listen其实是inet_listen, (/source/net/ipv4/af_inet.c)
跟进inet_csk_listen_start, (/source/net/ipv4/inet_connection_sock.c)
跟进reqsk_queue_alloc, (/source/net/core/request_sock.c)
然后我们计算一下为何在Server端的SYN_RECV状态数量会是256
nr_table_entries = listen的第二个参数int backlog ,上限是系统的somaxconn
若 somaxconn = 128,max_syn_backlog = 4096, backlog = 511 则nr_table_entries = 128
nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
取两者较小的一个 nr_table_entries = 128
nr_table_entries = max_t(u32, nr_table_entries, 8);
取两者较大的一个 nr_table_entries = 128
nr_table_entries = roundup_pow_of_two(nr_table_entries + 1); //roundup_pow_of_two - round the given value up to nearest power of two
roundup_pow_of_two(128 + 1) = 256
for (lopt->max_qlen_log = 3;
(1 << lopt->max_qlen_log) < nr_table_entries;
lopt->max_qlen_log++);
max_qlen_log = 8
总结:半连接队列长其实为 half open queue roundup_pow_of_two(max_t(u32,min(somaxconn,sysctl_max_syn_backlog,backlog),8) +1)
判断半连接队列是否满 queue->listen_opt->qlen >> queue->listen_opt->max_qlen_log;
queue->listen_opt->qlen = 256 时reqsk_queue_is_full返回1 , 进入drop
所以queue->listen_opt->qlen 取值 0~255, 因此SYN_RECV状态数量会是 256
三、现在我们再来看看全连接队列
查了nginx文档关于ListenBackLog 指令的说明,默认值是511. 可见最终全连接队列(backlog)应该是net.core.somaxconn = 258.
从/source/net/socket.c源码中也可以看出。
用慢连接攻击测试观察到虚拟机S的80端口ESTABLISHED=1016。基本等于nginx中的work_connections.
现在增加work_connections到1018,慢连接攻击测试观察到虚拟机S的80端口ESTABLISHED=1278(约等于1018+258).
总结,nginx单线程的情况下worker_processes=1,work_connections 约等于1018,
ESTABLISHED基本等于连接数,大于1018的情况下ESTABLISHED的数量要加上backlog;
多线程的情况下ESTABLISHED 2倍的work_connections。至于ESTABLISHED会不会加上backlog,
还是看work_connections是否大于1016。
接下来我们来看一个例子,也好验证一下我们的推断。
net.ipv4.tcp_max_syn_backlog=258
net.core.somaxconn=128
nginx
worker_processes 1
worker_connections 1016
listen 80 backlog=512
那么SYN_RECV = ?
ESTABLISHED = ?