TCP建立连接的“三次握手”过程
上图就是tcp建联的三次握手过程。
- Server端需要先调用bind()方法,绑定ip和端口号,再调用listen()方法,然后就可以等待来自Client连接了
- Client 调用connect()后,就会发送SYN包到Server,此时Client端处理SYN_SENT状态
- Server收到SYN后,Server进入SYN_RCVD状态,回复SYN + ACK,此时该socket被放入半连接队列(SYN QUEUE)中。(这里要侧重说明一下,这里并不是说从Listen状态变成了SYN_RCVD,而是会生成一个新的(或者复用旧的)socket,新socket状态会变成SYN_RCVD状态,Server端的原来监听的socket状态还依然是LISTEN状态)
- Client 收到Server发回的ACK后,会再发送一次ACK到Server,Server收到ACK后三次连接建立,此时会把该socket从半连接队列中取出来放入全连接队列(ACCEPTED QUEUE)中,此时Client与Server端全部处理ESTABLISHED状态
- Server端调用accept()方法后,该连接会从全连接队列移出,交给应用层处理。
什么是“半连接队列(syn queue)" 和 "全连接队列(accept queue)"?
半连接队列:保存处于SYN_RCVD状态的socket的队列。
全连接队列:保存处于ESTABLISHED状态的socket的队列。(已经完成三次握手,Server还未调用accept()函数将socket交由应用层处理)
全连接队列(accept queue)
全连接对列最大长度计算公式:
min(net.core.somaxconn, backlog) |
ps:
- net.core.somaxconn为/proc/sys/net/core/somaxconn
- backlog,listen(int sockfd, int backlog)函数传入的参数。比如:nginx中的listen指令的backlog参数。例如:'listen 80 backlog=1000;'
查看全连接队列长度:
注意:连接状态不同,Recv-Q和Send-Q的含义不相同。
当连接在LISTEN状态下时,Recv-Q表示当前全连接队列的长度, Send-Q表示最大的全连接队列长度
# ss -lnt State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 32768 *:9999 *:* LISTEN 0 100 *:8080 *:* LISTEN 0 128 *:18822 *:* LISTEN 0 50 *:7879 *:* |
当连接在非LISTEN状态下时,Recv-Q表示已收到但未被应用进程读取的字节数;Send-Q表示的是已发送但未收到确认的字节数。
# ss -lnt | grep 80 State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 51 128 *:80 *:* LISTEN 0 128 :::80 :::* LISTEN 0 128 :::58803 :::* # ss -lnt | grep 80 LISTEN 129 128 *:80 *:* LISTEN 0 128 :::80 :::* LISTEN 0 128 :::58803 :::* |
查看全连接队列(accept queue)溢出的统计信息:
# netstat -s | grep overflowed 172308 times the listen queue of a socket overflowed # netstat -s | grep overflowed 175641 times the listen queue of a socket overflowed |
注意:“172308 ”,"175641 "是个累计值,常用后一次与前一次的取差值,若为正,则表示队列溢出。
全连接队列满了,怎么办?
当全连接队列满了,linux默认是丢弃连接,还可以通过/proc/sys/net/ipv4/tcp_abort_on_overflow值来控制。
若为0,则丢弃连接(默认)
若为1,则server 发送一个 reset 包给 client
如何调整全连接队列大小?
因为全连接队列大小=min(/proc/sys/net/core/somaxconn,backlog)。所以调整全连接队列大小时,两个都需要调。
比如:nginx默认backlog是511,linux 3.10.0内核/proc/sys/net/core/somaxconn 默认是128。
调整实例:
echo 2000 > /proc/sys/net/core/somaxconn | |
[root@rocketmq-server ipv4]# cat /etc/nginx/nginx.conf | grep listen listen 80 default_server backlog=1500; | |
[root@rocketmq-server ipv4]# ss -lnt | grep 80 LISTEN 0 1500 *:80 *:* | 所以min(2000,1500)全连接队列变成了1500. |
半连接队列
半连接队列最大长度计算公式:
很多博客说SYN队列的最大长度是由/proc/sys/net/ipv4/tcp_max_syn_backlog参数指定的。实际上,只有当linux内核版本早于2.6.20时,SYN队列才等于backlog的大小。
实际上:SYN队列的最大长度由三个参数指定:
- 当你调用listen()时,传入的积压参数(backlog)
- /proc/sys/net/core/somaxconn 的默认值为 128
- /proc/sys/net/ipv4/tcp_max_syn_backlog 的默认值为 1024
查看当前半连接队列长度(只能粗略估计,SYN-RECV的数量少于实际半连接队列的长度):
#ss -antp | grep SYN-RECV | wc -l |
查看半连接队列(syn queue)溢出的统计信息:
# netstat -s | grep dropped 166 SYNs to LISTEN sockets dropped |
注意:“166”是个累计值,常用后一次与前一次的取差值,若为正,则表示队列溢出。
半连接队列满了,怎么办?
若半连接队列满了,根据不同情况,对连接可能有不同的处理方式,可能是直接丢弃,也可能是发送reset包
分如下几种情况:
- 如果半连接队列满了,并且没有开启 tcp_syncookies,则会丢弃;
- 若全连接队列满了,且没有重传 SYN+ACK 包的连接请求多于 1 个,则会丢弃;
- 如果没有开启 tcp_syncookies,并且 max_syn_backlog 减去当前半连接队列长度小于 (max_syn_backlog >> 2),则会丢弃;
"max_syn_backlog >> 2" :相当于右移2位,即max_syn_backlog / (2*2)
如何防御syn洪水攻击?
- 增大半连接队列最大长度 (同时调大:/proc/sys/net/core/somaxconn,/proc/sys/net/ipv4/tcp_max_syn_backlog,listen(int sockfd, int backlog)中的backlog参数<每个服务都不一样>)
- 开启tcp_syncookies功能(echo 1 > /proc/sys/net/ipv4/tcp_syncookies )
- 减少syn + ack重传次数(echo 1 > /proc/sys/net/ipv4/tcp_synack_retries )
参考文章:
4.4 TCP 半连接队列和全连接队列 | 小林coding
TCP SYN Queue and Accept Queue Overflow Explained - Alibaba Cloud Community