linux syn 队列,TCP SYN队列与Accept队列详解

本文详细介绍了Linux系统中TCP连接处理的SYN队列和Accept队列,包括队列溢出时的处理机制。在SYN队列溢出时,服务端会丢弃客户端的SYN数据包;而Accept队列溢出可能导致服务端发送RST包。同时,文章探讨了内核参数如tcp_syncookies、tcp_abort_on_overflow等对连接处理的影响,并通过实验验证了相关行为。最后,文章讨论了HTTP请求在队列溢出时可能出现的"connection reset by peer"问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

李乐

尽信书,不如无书。

纸上得来终觉浅,绝知此事要躬行。

实验现象依赖于系统(如下)以及内核参数(附录);一切以实验结果为准。

cat /proc/version

Linux version 3.10.0-693.el7.x86_64

引子

线上服务(Golang)调用内网API服务(经由内网网关/Nginx转发)时,偶尔会出现"connection reset by peer"报警;为此梳理TCP RST包可能产生的几种情况:

目的主机防火墙拦截;

向已关闭的socket发送数据;

全连接队列溢出;

向已经"消逝"的连接发送数据。

情况说明:Golang服务作为客户端,内网网关Nginx作为服务端,HTTP请求默认基于长连接(连接池)。

情况1非常容易理解;同机房内网环境,基本可以排除。这里不做过多介绍。下面将详细介绍情况2/3/4。

Nginx关闭连接

Golang服务通过长连接向网关Nginx发起请求;当Nginx主动断开连接,而恰好很不幸的此时Golang发起HTTP请求并且是复用之前的长连接,便会出现情况2。那么什么时候Nginx会主动断开长连接呢?

1)keepalive_timeout:设置每个TCP长连接在Nginx可以保持的最大时间,默认75秒;

2)keepalive_requests:设置每个TCP长连接最多可以处理的请求数,默认100;

Golang目前有这几个措施应对连接关闭情况:1)底层检测连接关闭事件,标记连接不可用;2)ECONNRESET错误时,对部分请求进行重试,比如:GET请求,请求头中出现{X-,}Idempotency-Key。当然实际判断是否重试逻辑还是比较复杂的;

+Transport.roundTrip

+persistConn.shouldRetryRequest

+RequestisReplayable

func (r *Request) isReplayable() bool {

if r.Body == nil || r.Body == NoBody || r.GetBody != nil {

switch valueOrDefault(r.Method, "GET") {

case "GET", "HEAD", "OPTIONS", "TRACE":

return true

}

if r.Header.has("Idempotency-Key") || r.Header.has("X-Idempotency-Key") {

return true

}

}

return false

}

Transport.IdleConnTimeout可配置空闲连接超时时间;然而他与Nginx配置keepalive_timeout含义不同,因此无法保证Golang客户端主动关闭连接;

另外,也可以通过短连接方式避免。

Golang net/http库还有待深入研究。

参考资料:

SYN Queue与Accept Queue介绍

如下图所示(摘抄自网络),1)server端接受到SYN请求,创建socket,存储于SYN Queue(半连接队列),并向客户端返回SYN+ACK;2)server端接收到第三次握手的ACK,socket状态更新为ESTABLISHED,同时将socket移动到Accept Queue(全连接队列),等待应用程序执行accept()。

bVbFLyF

不管是SYN Queue还是Accept Queue,都有最大长度限制,超过限制时,内核或直接丢弃,或返回RST包。Queue大小计算方法如下:

注:下文使用的backlog指调系统用listen(fd, backlog) 的第二个参数。

Accept Queue:

min(backlog, net.core.somaxconn)

校验Accept Queue是否满的逻辑如下(注意大于号才返回ture,即最终可存储socket数目会加1):

return sk->sk_ack_backlog > sk->sk_max_ack_backlog

SYN Queue:

nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);

nr_table_entries = max_t(u32, nr_table_entries, 8);

nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);

//向上取满足2的指数倍的整数;比如10=》16

for (lopt->max_qlen_log = 3;

(1 << lopt->max_qlen_log) < nr_table_entries;

lopt->max_qlen_log++);

程序中的nr_table_entries初始值为min(backlog, net.core.somaxconn);sysctl_max_syn_backlog即内核参数net.ipv4.tcp_max_syn_backlog;变量lopt->max_qlen_log限制了SYN Queue大小。

需要注意的,变量lopt->max_qlen_log的类型为u8(8比特无符号整型),最终SYN Queue大小为2^(lopt->max_qlen_log),其上限为roundup_pow_of_two(sysctl_max_syn_backlog + 1),下限为16。

校验SYN Queue是否满的逻辑如下(qlen为当前SYN Queue长度,通过右移运算符判断):

return queue->listen_opt->qlen >> queue->listen_opt->max_qlen_log;

小知识:

可通过netstat或者ss命令查看socket信息;socket处于监听LISTEN状态时,Send-Q为Accept Queue最大长度,Recv-Q为Accept Queue累计的等待应用程序accept()的socket数目。(而当socket处于ESTABLISHED状态时,Send-Q与Recv-Q分别表示socket发送缓冲区与接收缓冲区数据大小)

# ss -lnt

State Recv-Q Send-Q Local Address:Port Peer Address:Port

LISTEN 0 128 *:10088 *:*

SYN Queue

那么当SYN Queue溢出时,服务端是怎么处理呢?丢弃还是回复RST包?我们将从实验验证与源码分析两个角度讲解。

SYN Queue溢出实验

我们利用hping3模拟SYN发包(需要注意的是,在利用hping3模拟时,客户端收到SYN+ACK会返回RST;本文通过iptables -A INPUT -s $ip -j DROP拦截服务端返回数据包,消除了客户端RST包影响)。服务端启动监听(此时SYN Queue限制为16):

sock=socket(AF_INET, SOCK_STREAM)

sock.bind(('', 8888))

sock.listen(1)

记得通过netstat查看初始TCP统计信息:

# netstat -s |grep -E 'listen| resets sent| LISTEN'

5236 resets sent //RST包发送数目

438 times the listen queue of a socket overflowed //Accept Queue溢出数目

2900 SYNs to LISTEN sockets dropped //三次握手过程丢弃数目

客户端启动发包;-S设置SYN标志,-p指定目标端口号,-i设置发送间隔1000微妙(即每毫秒发送1个SYN数据包)。同时在服务端启动tcpdum

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值