李乐

尽信书,不如无书。

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

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

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()。

不管是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数据包)。同时在服务端启动tcpdump抓包。

hping3 -S -p 8888 -i u1000 $ip

542 packets tramitted

总发包数目为542;再次查看服务端

TCP统计信息:

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

5236 resets sent

438 times the listen queue of a socket overflowed

3426 SYNs to LISTEN sockets dropped

可以看到SYN丢弃数目增加了526=542-16(16为SYN Queue长度限制),服务端发送RST数目没有变化。

查看tcpdump结尾的抓包情况,可以看到只有客户端的SYN请求,服务端没有给客户端返回SYN+ACK。

13:40:48.230881 IP xxxx.ms-sql-s > xxxx.8888: Flags [S], seq 340595037, win 512, length 0

13:40:48.231880 IP xxxx.ms-sql-m > xxxx.8888: Flags [S], seq 580674513, win 512, length 0

13:40:48.232920 IP xxxx.ibm-cics > xxxx.8888: Flags [S], seq 1559804617, win 512, length 0

13:40:48.233896 IP xxxx.saism > xxxx.8888: Flags [S], seq 2102270179, win 512, length 0

SYN Queue溢出时,服务端只是丢弃客户端的SYN数据包。

tcp_syncookies

其实还有一个内核参数tcp_syncookies可以影响SYN Queue行为。

tcp_syncookies (Boolean; since Linux 2.2)

Enable TCP syncookies. The kernel must be compiled with CONFIG_SYN_COOKIES. Send out syncookies when the syn backlog

queue of a socket overflows. The syncookies feature attempts to protect a socket from a SYN flood attack. This should be

used as a last resort, if at all. This is a violation of the TCP protocol, and conflicts with other areas of TCP such as

TCP extensions. It can cause problems for clients and relays. It is not recommended as a tuning mechanism for heavily

loaded servers to help with overloaded or misconfigured conditions. For recommended alternatives see tcp_max_syn_backlog,

tcp_synack_retries, and tcp_abort_on_overflow

tcp_syncookies是一种专门防御 SYN Flood 攻击的方法,其 基于连接信息(包括源地址、源端口、目的地址、目的端口等)以及一个加密种子(如系统启动时间),计算出一个哈希值(SHA1),这个哈希值称为cookie。

该cookie被用作TCP初始序列号,来应答SYN+ACK 包,并释放连接状态。当客户端发送完三次握手的最后一次ACK 后,服务端就会再次计算这个哈希值,确认是上次返回的 SYN+ACK 的返回包,才会进入TCP 的连接状态。

即,开启 SYN Cookies 后,服务端就不需要维护半开连接状态了,从而也就不存在SYN Queue溢出情况了。

是这样吗?我们来实验验证下。

修改内核参数:

# sysctl -w net.ipv4.tcp_syncookies=1

net.ipv4.tcp_syncookies = 1

记录初始TCP统计信息:

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

5236 resets sent

438 times the listen queue of a socket overflowed

4473 SYNs to LISTEN sockets dropped

客户端启动hping3开始发送SYN包,服务端开启tcpdump抓包:

hping3 -S -p 8888 -i u1000 10.90.101.6

282 packets tramitted

总发包数目为282;再次查看服务端 TCP统计信息:

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

5236 resets sent

438 times the listen queue of a socket overflowed

4739 SYNs to LISTEN sockets dropped

可以很明显的看到,SYN包丢弃的数目依然有变化,增长266=282-16。怎么回事?为什么服务端还会丢弃SYN包呢?难道tcp_syncookies与我们理解的不一致?

但是查看服务端tcpdump抓包情况,我们发现结尾服务端依然在向客户端返回SYN+ACK(不是SYN+ACK重试包,整个实验过程非常短,而SYN+ACK重试间隔初始为1秒)

15:10:27.895666 IP xxxx.8888 > xxxx.pxc-sapxom: Flags [S.], seq 2552938291, ack 277155377, win 29200, options [mss 1460], length 0

15:10:27.895670 IP xxxx.8888 > xxxx.syncserverssl: Flags [S.], seq 132109634, ack 1320827335, win 29200, options [mss 1460], length 0

15:10:28.095641 IP xxxx.8888 > xxxx.md-cg-http: Flags [S.], seq 571952037, ack 1550190463, win 29200, options [mss 1460], length 0

15:10:28.095680 IP xxxx.8888 > xxxx.ncdloadbalance: Flags [S.], seq 3043329827, ack 1288412213, win 29200, options [mss 1460], length 0

服务端一直应答SYN+ACK,说明这些连接请求并没有丢弃,是生效的。(另外,在使用hping3模拟大量SYN请求的同时,可以发起正常连接请求,验证是否可以正常建立连接)。

源码分析

上述实验看到,在开启tcp_syncookies之后,依然有SYN请求丢弃发生,但是服务端却依然在反馈SYN+ACK。下面将从源码角度分析。

在接收到SYN请求时,服务端处理逻辑如下:

+tcp_v4_do_rcv

+tcp_v4_hnd_req

+tcp_rcv_state_process

+tcp_v4_conn_request

函数tcp_v4_conn_request处理客户端连接请求,校验SYN Queue逻辑如下:

if (inet_csk_reqsk_queue_is_full(sk)) {

want_cookie = tcp_syn_flood_action(sk, skb, "TCP");

if (!want_cookie)

goto drop;

}

drop:

NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);

//统计ListenDrops

可以看到,在SYN Queue队列溢出时,根据want_cookie处理,如果配置tcp_syncookies=1,则want_cookie=true,同时继续处理(SYN Queue长度限制失效);否则会执行drop逻辑丢弃SYN包。

//配置tcp_syncookies=1,且SYN Queue溢出,want_cookie=true

skb_synack = tcp_make_synack(sk, dst, req,

fastopen_cookie_present(&valid_foc) ? &valid_foc : NULL);

err = ip_build_and_send_pkt(skb_synack, sk, ireq->loc_addr,

ireq->rmt_addr, ireq->opt);

if (err || want_cookie)

goto drop_and_free;

//添加socket信息到SYN Queue

inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);

drop_and_free:

reqsk_free(req);

drop:

NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);

return 0;

可以看到,在want_cookie时(tcp_syncookies=1),跳转到drop_and_free处理(没有添加加socket信息到SYN Queue);drop标签同时累加ListenDrops。

上文实验netstat -s |grep -E 'LISTEN'统计的数据,是从/proc/net/netstat获取,即对应ListenDrops。

通过这两段逻辑,我们明白了tcp_syncookies的处理过程:1)SYN Queue没有溢出时,与普通流程相同;2)SYN Queue溢出时,才真正开启SYN Cookie功能,开启后会丢弃所有SYN包,同时累加ListenDrops。

补充:

函数tcp_syn_flood_action还会做一些统计需要我们关注下:

bool tcp_syn_flood_action(struct sock *sk,

const struct sk_buff *skb,

const char *proto)

{

bool want_cookie = false;

if (sysctl_tcp_syncookies) {

want_cookie = true;

NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPREQQFULLDOCOOKIES);

//TCPReqQFullDoCookies,发送cookie

} else

NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPREQQFULLDROP);

//TCPReqQFullDrop,SYN丢弃

return want_cookie;

}

上述实验我们再执行两次:

开启tcp_syncookies:

# netstat -s | grep -E 'TCPReqQFullDrop|TCPReqQFullDoCookies'

TCPReqQFullDoCookies: 1194

TCPReqQFullDrop: 3265

455 packets tramitted

# netstat -s | grep -E 'TCPReqQFullDrop|TCPReqQFullDoCookies'

TCPReqQFullDoCookies: 1633

TCPReqQFullDrop: 3265

//drop新增0;cookie新增439=455-16

关闭tcp_syncookies:

# netstat -s | grep -E 'TCPReqQFullDrop|TCPReqQFullDoCookies'

TCPReqQFullDoCookies: 1633

TCPReqQFullDrop: 3265

358 packets tramitted

# netstat -s | grep -E 'TCPReqQFullDrop|TCPReqQFullDoCookies'

TCPReqQFullDoCookies: 1633

TCPReqQFullDrop: 3607

//drop新增342=358-16;cookie新增0

Accept Queue

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

Accept Queue溢出实验

服务端通过如下方式启动监听,此时Accept Queue最大长度为2:

sock=socket(AF_INET, SOCK_STREAM)

sock.bind(('', 8888))

sock.listen(1)

发送2个连接请求,ss查看Accept Queue统计情况,Accept Queue已达到最大长度:

# ss -lnt

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

LISTEN 2 1 *:8888 *:*

netstat查看初始TCP状态统计数据:

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

5244 resets sent

448 times the listen queue of a socket overflowed

5896 SYNs to LISTEN sockets dropped

发起新的连接请求,同时启动tcpdump抓包:

netstat再次查看初始TCP状态统计数据:

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

5244 resets sent

451 times the listen queue of a socket overflowed

5899 SYNs to LISTEN sockets dropped

可以看到,RST包发送统计没有增加;listenoverflow以及listendrop均有增加,且同时增加3。这里留个疑问,为什么会增加3呢?不是只发起一个请求吗?

ss查看新的连接状态为SYN-RECV:

ss -nat | grep -E 'State|8888'

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

LISTEN 2 1 *:8888 *:*

SYN-RECV 0 0 xxxx:8888 xxxx:35453

查看tcpdump抓包数据:

16:52:33.942358 IP xxxx.35453 > xxxx.8888: Flags [S], seq 2051886524, win 29200, length 0

16:52:33.942588 IP xxxx.8888 > xxxx.35453: Flags [S.], seq 3268637378, ack 2051886525, win 28960, length 0

16:52:33.942916 IP xxxx.35453 > xxxx.8888: Flags [.], ack 3268637379, win 58, length 0

16:52:35.345579 IP xxxx.8888 > xxxx.35453: Flags [S.], seq 3268637378, ack 2051886525, win 28960, length 0

16:52:35.345953 IP xxxx.35453 > xxxx.8888: Flags [.], ack 3268637379, win 58, length 0

16:52:37.345598 IP xxxx.8888 > xxxx.35453: Flags [S.], seq 3268637378, ack 2051886525, win 28960, length 0

16:52:37.346078 IP xxxx.35453 > xxxx.8888: Flags [.], ack 3268637379, win 58, length 0

可以看到,再服务端接收到客户端第三次ACK之后(参照ss结果,由于Accept Queue溢出,丢弃了ACK包,连接状态依然为SYN-RECV);服务端超时后还发送了两次SYN+ACK包,客户端均应答ACK。

通过tcpdump抓包结果可以看到,由于重试机制,服务端总共收到了三次客户端的第三次握手ACK,而三次都由于Accept Queue溢出丢弃,因此上面说的listenoverflow以及listendrop增加3。(至于为何两者同时增加,待会源码分析)。

知识补充

服务端的SYN+ACK重试次数,由内核参数tcp_synack_retries决定。

tcp_synack_retries (integer; default: 5; since Linux 2.2)

The maximum number of times a SYN/ACK segment for a passive TCP connection will be retransmitted. This number should not

be higher than 255.

HTTP请求验证

上面实验我们只是发起了连接请求,HTTP请求时,服务端丢弃第三次ACK导致连接状态为SYN-RECV,但是此时客户端状态已经为ESTABLISHED,当客户端此时传输HTTP请求数据时,会导致RST吗?

其他同上面的实验,curl请求:

curl http://xxxx:8888/user/login

curl: (56) Recv failure: Connection timed out

tcpdump抓包结果如下:

17:18:27.820307 IP xxxx.35515 > xxxx.8888: Flags [S], seq 3685002386, win 29200, length 0

17:18:27.820378 IP xxxx.8888 > xxxx.35515: Flags [S.], seq 1886008256, ack 3685002387, win 28960, length 0

17:18:27.820672 IP xxxx.35515 > xxxx.8888: Flags [.], ack 1886008257, win 58, length 0

//发起HTTP请求

17:18:27.820680 IP xxxx.35515 > xxxx.8888: Flags [P.], seq 3685002387:3685002477, ack 1886008257, win 58, length 90

//HTTP请求重试

17:18:28.020543 IP xxxx.35515 > xxxx.8888: Flags [P.], seq 3685002387:3685002477, ack 1886008257, win 58, length 90

//HTTP请求重试

17:18:28.220471 IP xxxx.35515 > xxxx.8888: Flags [P.], seq 3685002387:3685002477, ack 1886008257, win 58, length 90

//HTTP请求重试

17:18:28.621487 IP xxxx.35515 > xxxx.8888: Flags [P.], seq 3685002387:3685002477, ack 1886008257, win 58, length 90

//SYN+ACK重试

17:18:29.021763 IP xxxx.8888 > xxxx.35515: Flags [S.], seq 1886008256, ack 3685002387, win 28960, length 0

17:18:29.022193 IP xxxx.35515 > xxxx.8888: Flags [.], ack 1886008257, win 58, length 0

//HTTP请求重试

17:18:29.424432 IP xxxx.35515 > xxxx.8888: Flags [P.], seq 3685002387:3685002477, ack 1886008257, win 5

//SYN+ACK重试8, length 90

17:18:31.221631 IP xxxx.8888 > xxxx.35515: Flags [S.], seq 1886008256, ack 3685002387, win 28960, length 0

//客户端超时,发起RST

17:18:31.221942 IP xxxx.35515 > xxxx.8888: Flags [R], seq 3685002387, win 0, length 0

可以看到服务端对于客户端的HTTP请求数据,并没有响应(直接丢弃);客户端连接状态为ESTABLISHED,服务端为SYN-RECV,客户端一直在重试HTTP请求,服务端一直在重试SYN+ACK。最后,客户端HTTP请求传输超时(TCP重传失败),客户端发起RST包。TCP重传失败时,上层错误信息为Connection timed out,与curl失败报错相对应。

知识补充

TCP数据传输重试次数由内核参数tcp_retries2决定。

tcp_retries2 (integer; default: 15; since Linux 2.2)

The maximum number of times a TCP packet is retransmitted in established state before giving up. The default value is 15,

which corresponds to a duration of approximately between 13 to 30 minutes, depending on the retransmission timeout. The

RFC 1122 specified minimum limit of 100 seconds is typically deemed too short.

tcp_abort_on_overflow

其实,服务端Accept Queue溢出的行为还受到内核参数tcp_abort_on_overflow决定。而我们的系统配置tcp_abort_on_overflow=0。

tcp_abort_on_overflow (Boolean; default: disabled; since Linux 2.4)

Enable resetting connections if the listening service is too slow and unable to keep up and accept them. It means that if

overflow occurred due to a burst, the connection will recover. Enable this option only if you are really sure that the

listening daemon cannot be tuned to accept connections faster. Enabling this option can harm the clients of your server.

修改配置tcp_abort_on_overflow=1,重试上面实验:

# sysctl -w net.ipv4.tcp_abort_on_overflow=1

net.ipv4.tcp_abort_on_overflow = 1

客户端curl请求立即报错:

time curl http://10.90.101.6:8888/user/login

curl: (56) Recv failure: Connection reset by peer

real 0m0.005s

tcpdump抓包情况如下,服务端在接收到第三次握手ACK时,立即返回RST包:

17:35:02.063694 IP xxxx.35547 > xxxx.8888: Flags [S], seq 1965671248, win 29200, length 0

17:35:02.063804 IP xxxx.8888 > xxxx.35547: Flags [S.], seq 3965903705, ack 1965671249, win 28960, length 0

17:35:02.064200 IP xxxx.35547 > xxxx.8888: Flags [.], ack 3965903706, win 58, length 0

17:35:02.064228 IP xxxx.8888 > xxxx.35547: Flags [R], seq 3965903706, win 0, length 0

源码分析

在接收到第三次握手ACK时,服务端处理逻辑如下:

+tcp_v4_do_rcv

+tcp_v4_hnd_req

+tcp_check_req

+tcp_v4_syn_recv_sock

tcp_v4_syn_recv_sock函数判断Accept Queue是否溢出:

if (sk_acceptq_is_full(sk))

goto exit_overflow;

exit_overflow:

NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);

//统计ListenOverflows

exit_nonewsk:

dst_release(dst);

exit:

NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);

//统计ListenDrops

return NULL;

可以看到,在溢出时,同时修改ListenOverflows以及ListenDrops。(与上面实验同时增加3相对应)。

函数tcp_check_req根据tcp_v4_syn_recv_sock返回结果,以及tcp_abort_on_overflow,决定是否发送RST包:

child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);

if (child == NULL)

goto listen_overflow;

embryonic_reset:

req->rsk_ops->send_reset(sk, skb);

//实现函数为:tcp_v4_send_reset

NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_EMBRYONICRSTS);

//统计EmbryonicRsts

return NULL;

Accept Queue再验证

为什么有些人在实验Accept Queue溢出时,哪怕配置的tcp_abort_on_overflow=0,依然客户端会收到RST包,这是为什么呢?其实还是与系统配置有关。

另外,本文最开始提到向已经"消逝"的连接发送数据,同样会导致RST。

当tcp_synack_retries配置非常小时,由于Accept Queue溢出,服务端的SYN-RECV状态很快超时,连接被释放;而客户端的tcp_retries2配置的比较大时,客户端还在一直重试发送HTTP请求,此时服务端便会返回RST包。

修改tcp_retries2:

# sysctl -w net.ipv4.tcp_retries2=15

net.ipv4.tcp_retries2 = 15

再次发起curl请求:

# time curl http://10.90.101.6:8888/user/login

curl: (56) Recv failure: Connection reset by peer

real 0m12.844s

tcpdump抓包情况如下:

17:58:33.522067 IP xxxx.35603 > xxxx.8888: Flags [S], seq 2997388295, win 29200, olength 0

17:58:33.522182 IP xxxx.8888 > xxxx.35603: Flags [S.], seq 2883911494, ack 2997388296, win 28960, length 0

17:58:33.522463 IP xxxx.35603 > xxxx.8888: Flags [.], ack 2883911495, win 58, length 0

//发起HTTP请求

17:58:33.522583 IP xxxx.35603 > xxxx.8888: Flags [P.], seq 2997388296:2997388386, ack 2883911495, win 58, length 90

//HTTP请求重试

17:58:33.723351 IP xxxx.35603 > xxxx.8888: Flags [P.], seq 2997388296:2997388386, ack 2883911495, win 58, length 90

//HTTP请求重试

17:58:33.924422 IP xxxx.35603 > xxxx.8888: Flags [P.], seq 2997388296:2997388386, ack 2883911495, win 58, length 90

//HTTP请求重试

17:58:34.327366 IP xxxx.35603 > xxxx.8888: Flags [P.], seq 2997388296:2997388386, ack 2883911495, win 58, length 90

//服务端重试SYNc+ACK

17:58:34.523613 IP xxxx.8888 > xxxx.35603: Flags [S.], seq 2883911494, ack 2997388296, win 28960, length 0

17:58:34.523916 IP xxxx.35603 > xxxx.8888: Flags [.], ack 2883911495, win 58, length 0

//HTTP请求重试

17:58:35.133451 IP xxxx.35603 > xxxx.8888: Flags [P.], seq 2997388296:2997388386, ack 2883911495, win 58, length 90

//服务端重试SYNc+ACK

17:58:36.723600 IP xxxx.8888 > xxxx.35603: Flags [S.], seq 2883911494, ack 2997388296, win 28960, length 0

17:58:36.723987 IP xxxx.35603 > xxxx.8888: Flags [.], ack 2883911495, win 58, length 0

//HTTP请求重试

17:58:36.743318 IP xxxx.35603 > xxxx.8888: Flags [P.], seq 2997388296:2997388386, ack 2883911495, win 58, length 90

//HTTP请求重试

17:58:39.967405 IP xxxx.35603 > xxxx.8888: Flags [P.], seq 2997388296:2997388386, ack 2883911495, win 58, length 90

//HTTP请求重试

17:58:46.423467 IP xxxx.35603 > xxxx.8888: Flags [P.], seq 2997388296:2997388386, ack 2883911495, win 58, length 90

//服务端返回RST

17:58:46.423716 IP xxxx.8888 > xxxx.35603: Flags [R], seq 2883911495, win 0, length 0

可以看到,在客户端TCP多次重试的过程中,服务端的连接SYN-RECV已经超时释放,导致服务端最终返回RST包。

总结

还是那句话:尽信书,不如无书。

很多人的实验现象,是与其系统以及内核参数息息相关。不能简简单单的认为TCP队列溢出就会导致RST或者不会RST。

只是在本文的系统配置下,HTTP请求异常"connection reset by peer"(服务端RST)不是由TCP队列溢出导致的。

Golang为了避免"connection reset by peer"情况,目前可以通过短链接方式避免,或者异常时重试。而本文重点介绍了TCP SYN Queue以及Accept Queue,至于Golang长连接RST情况还有待深究。

附录

net.ipv4.tcp_timestamps = 1

net.ipv4.tcp_window_scaling = 1

net.ipv4.tcp_sack = 0

net.ipv4.tcp_retrans_collapse = 1

net.ipv4.ip_default_ttl = 64

net.ipv4.ip_nonlocal_bind = 0

net.ipv4.tcp_syn_retries = 2

net.ipv4.tcp_synack_retries = 2

net.ipv4.tcp_max_orphans = 262144

net.ipv4.tcp_max_tw_buckets = 1600000

net.ipv4.ip_dynaddr = 0

net.ipv4.tcp_keepalive_time = 300

net.ipv4.tcp_keepalive_probes = 9

net.ipv4.tcp_keepalive_intvl = 75

net.ipv4.tcp_retries1 = 3

net.ipv4.tcp_retries2 = 2

net.ipv4.tcp_fin_timeout = 5

net.ipv4.tcp_syncookies = 1

net.ipv4.tcp_tw_recycle = 0

net.ipv4.tcp_abort_on_overflow = 0

net.ipv4.tcp_stdurg = 0

net.ipv4.tcp_rfc1337 = 0

net.ipv4.tcp_max_syn_backlog = 81920

net.core.somaxconn = 65535

参考资料

linux syn 队列,TCP SYN队列与Accept队列详解相关推荐

  1. TCP/IP编程之accept函数详解

    accept函数由TCP服务器调用,用于从已完成连接队列返回下一个已完成连接.如果已完成连接队列为空,那么进程被投入睡眠(假定套接字默为默认的阻塞方式) 函数原型: ACCEPT(2) Linux P ...

  2. 1 linux下tcp并发服务器的几种设计的模式套路,Linux下几种并发服务器的实现模式(详解)...

    1>单线程或者单进程 相当于短链接,当accept之后,就开始数据的接收和数据的发送,不接受新的连接,即一个server,一个client 不存在并发. 2>循环服务器和并发服务器 1.循 ...

  3. TCP/IP 协议栈及 OSI 参考模型详解

    TCP/IP 协议栈及 OSI 参考模型详解 转载地址:http://www.codeceo.com/article/tcp-ip-osi-model.html OSI参考模型 OSI RM:开放系统 ...

  4. 创建三个并发进程linux,Linux下几种并发服务器的实现模式(详解)

    1>单线程或者单进程 相当于短链接,当accept之后,就开始数据的接收和数据的发送,不接受新的连接,即一个server,一个client 不存在并发. 2>循环服务器和并发服务器 1.循 ...

  5. 深入学习Linux摄像头(四)三星平台fimc驱动详解

    深入学习Linux摄像头系列 深入学习Linux摄像头(一)v4l2应用编程 深入学习Linux摄像头(二)v4l2驱动框架 深入学习Linux摄像头(三)虚拟摄像头驱动分析 深入学习Linux摄像头 ...

  6. TCP与UDP的区别(详解)

    TCP与UDP的区别(详解) 说区别之前先了解了解这两个协议工作机制以及工作原理 UDP: UDP是一种面向无连接的传输层协议,无法提供可靠的传输服务 通过以上报文格式可以看出UDP报文可以分为UDP ...

  7. Linux Shell脚本入门--wget 命令用法详解

    Linux Shell脚本入门--wget 命令用法详解 wget是在Linux下开发的开放源代码的软件,作者是Hrvoje Niksic,后来被移植到包括Windows在内的各个平台上.它有以下功能 ...

  8. linux Shell(脚本)编程入门实例讲解详解

    linux Shell(脚本)编程入门实例讲解详解 为什么要进行shell编程 在Linux系统中,虽然有各种各样的图形化接口工具,但是sell仍然是一个非常灵活的工具.Shell不仅仅是命令的收集, ...

  9. linux中apache配置文件在哪,linux网站服务Apache的安装与配置方法详解

    这篇文章介绍下linux网站服务apache的安装与配置方法,包括挂载光盘,安装http服务,管理httpd服务,httpd的配置文件几大部分.具体详情可以参考下文. 1.挂载光盘 自己习惯将光盘挂载 ...

  10. linux c多进程多线程,linux下的C\C++多进程多线程编程实例详解

    linux下的C\C++多进程多线程编程实例详解 1.多进程编程 #include #include #include int main() { pid_t child_pid; /* 创建一个子进程 ...

最新文章

  1. 借助Redis锁,完美解决高并发秒杀问题
  2. 运城学院数学与计算机系,运城学院数学与信息技术学院.doc
  3. 转:[C#]获取某年指定周的开始日期和结束日期的通用方法
  4. WMRouter:美团外卖Android开源路由框架
  5. 想写好前端,先练好内功
  6. python operator.itemgetter
  7. Ubuntu18.04+Halcon18.11安装教程
  8. python cpk计算器_Python进行CPK计算
  9. Redis迭代查询详解及其使用:Scan命令、Sscan命令、Hscan命令、Zscan命令
  10. 《凤凰项目》读书笔记一
  11. 华为云计算hcip证书有效期_华为云计算HCIP V4.0认证要发布了!
  12. 从《硅谷传奇》看微软和苹果
  13. Mac book pro 10.14.6 mojava 屏幕闪烁、像素点闪烁问题解决
  14. 国外 计算机专业 网站,国外计算机类核心期刊及其网站
  15. 献给盲目追谁IT的管理者的话,转载:活着就是王道---- -一年
  16. 【Rust日报】2020-11-09 构建可测试性的 Rust 工程
  17. 驱动认知-驱动代码编写与执行
  18. Android陀螺仪应用:平衡球小游戏
  19. UNCTF2022 wp Re ezzzzre
  20. Mal-PEG1-acid,Mal-PEG1-COOH,760952-64-5含有马来酰亚胺基团

热门文章

  1. 手机电源管理芯片一般在哪
  2. EZEMC测试软件_EMI 和 EMC 仿真、测量和预兼容性测试
  3. android:persistent属性研究
  4. 小学-综合素质【6】
  5. 34、BDS B1I星历处理实现
  6. 用电视上网——网络电视机顶盒
  7. 区块链技术应用与安全发展
  8. linux编译一直失败,linux编译安装时常见错误解决办法
  9. Unity材质偏移(贴图纹理偏移)实现流动效果【记录一下】
  10. 腾讯云通信IM集成踩坑记