幕布,二婚网,假如爱有天意-策马篮球,篮球青年队,专业青训计划,nba信息分享

admin 2019-05-16 阅读:286

在排查网络问题的时分,经常会遇见TCP衔接树立不成功的场景。假如能获取到两头抓包,两头抓包看起来如下:

  • 客户端在一向依照指数退避重传TCP SYN (因为首包没有获取到RTT及RTO,会在1, 2, 4, 8秒... 重传,直到完结net.ipv4.tcp_syn_retries次重传)
  • 服务器端能看到TCP SYN报文现已抵达网卡,可是TCP协议栈没有任何回包。

因为这样的问题呈现的频率不小,本文会从TCP协议栈方面总结常见原因。所谓的TCP协议栈方面的原因,便是TCP SYN报文现已到了内核的TCP处理模块,但在服务器端内核逻辑中不给客户端回SYNACK。客户端一向重传TCP SYN也或许由其他原因形成,比方服务器端有多块网卡形成的收支途径不一致,或许SYN报文被iptables规矩阻挠,这些场景都不在本文的评论规模之内。

Listen状况下处理TCP SYN的代码逻辑

本文以许多用户运用的CentOS 7的内核版别为根底,看看下TCP处理SYN的首要逻辑,结合事例处理的经历来剖析首要或许出问题的点。处于listen状况的socket处理第一个TCP SYN报文的逻辑大约如下:

tcp_v4_do_rcv() @net/ipv4/tcp_ipv4.c
|--> tcp_rcv_state_process() @net/ipv4/tcp_input.c // 这个函数完结了绝大TCP状况下的承受报文的处理进程 (ESTABLISHED和TIME_WAIT在外),当然包含了咱们重视的LISTEN状况
|--> tcp_v4_conn_request() @@net/ipv4/tcp_ipv4.c // 当TCP socket出于LISTEN状况,且接纳报文中TCP SYN flag是置位的,就来到这个函数中处理

CentOS中内核代码或许会有些调整,假如你需求盯梢源代码的切当行数,systemtap是一个很好的办法,如下:

# uname -r
3.10.0-693.2.2.el7.x86_64
# stap -l 'kernel.function("tcp_v4_conn_request")'
kernel.function("tcp_v4_conn_request@net/ipv4/tcp_ipv4.c:1303")

来到tcp_v4_conn_request()的逻辑里,函数逻辑的前几行如下:

进入到这个函数的前提条件是TCP socket出于LISTEN状况,且接纳报文中TCP SYN flag是置位的。在进入函数逻辑后,能够发现函数要考虑各种或许发作的异常状况,但在实践中许多并不常见。比方咱们在前几行看到的这两种状况:

  • 1482行:回绝播送和组播报文。
  • 1490行:假如request queue (寄存SYN报文的行列)满了,且isn为0,且want_cookie为flase, 则drop掉SYN报文。

第一种状况意思比较清晰,在实践中也没见过,在这里不评论。第二种状况略为杂乱,并且有小概率或许会碰到,下面简略看看:

第一个条件request queue 满实践是很简略发作的工作,syn flood进犯很简略完结这件工作。而isn在函数开端被赋值成TCP_SKB_CB(skb)->when,这个是TCP操控块结构体中用于核算RTT的字段。want_cookie则代表这syn syncookies的运用与否。在tcp_syn_flood_action()中的界说如下,假如ifdef了CONFIG_SYN_COOKIES, 内核参数的net.ipv4.tcp_syncookies也设置成1,则概述的回来是true, want_cookie则为true。

所以在上面这种drop SYN报文的状况中,真实的前提条件是没有敞开net.ipv4.tcp_syncookies这个内核参数。而在实践出产体系中,net.ipv4.tcp_syncookies默许是翻开的。Syn syncookies是一种时刻(CPU核算)换空间(request queue行列)来抵挡syn flood进犯的办法,在实践出产中看不到任何场景需求显现地封闭这个开关。所以总的来讲,1490行中这种恳求在实践中也不太常见。

内核drop SYN报文的首要场景

本文的首要意图不是依照代码逻辑顺次描绘drop SYN报文的一切场景,而是结合之前的实践经历描绘两种首要或许丢SYN报文的场景以及怎么敏捷判别的办法,协助我们了解为什么服务器端会不回SYNACK。

1. Per-host PAWS查看形成drop SYN报文

问题现象

这是在实践出产环境中最常见的一种问题:关于net.ipv4.tcp_tw_recycle和net.ipv4.tcp_timestamps都敞开的服务器,并且有NAT客户端拜访时,这个问题呈现的概率十分大。在客户端看来,问题现象一般新建衔接时通时不通。

Per-host PAWS原理

PAWS是Protect Against Wrapped Sequences的简写,字面意思是防止sequence number环绕。per-host, 是相对per-connection来讲的,便是对对端主机IP做查看而非对IP端口四元组做查看。

Per-host PAWS查看的办法是:关于被快速收回掉的TIME_WAIT socket的五元组对端主机IP, 为了防止来自同一主机的旧数据搅扰,需求在60秒内新来的SYN报文TCP option中的timestamp是添加的。当客户端是在NAT环境里时这个条件往往不简略满意。

理论上只需求记住上面这句就能解掉许多客户端的三次握手时通时不通的问题。假如想要了解得更多,请看下文的具体解说。

为什么有per-host PAWS?

在RFC 1323中说到了per-host PAWS,如下:

(b) Allow old duplicate segments to expire.

To replace this function of TIME-WAIT state, a mechanism

would have to operate across connections. PAWS is defined

strictly within a single connection; the last timestamp is

TS.Recent is kept in the connection control block, and

discarded when a connection is closed.

An additional mechanism could be added to the TCP, a per-host

cache of the last timestamp received from any connection.

This value could then be used in the PAWS mechanism to reject

old duplicate segments from earlier incarnations of the

connection, if the timestamp clock can be guaranteed to have

ticked at least once since the old connection was open. This

would require that the TIME-WAIT delay plus the RTT together

must be at least one tick of the sender's timestamp clock.

Such an extension is not part of the proposal of this RFC.

Note that this is a variant on the mechanism proposed by

Garlick, Rom, and Postel [Garlick77], which required each

host to maintain connection records containing the highest

sequence numbers on every connection. Using timestamps

instead, it is only necessary to keep one quantity per remote

host, regardless of the number of simultaneous connections to

that host.

在tcp_minisocks.c的代码注释中也论述了需求TIME_WAIT的原因,和快速收回TIME_WAIT的理论根底:PAWS机制,如下:

Main purpose of TIME-WAIT state is to close connection gracefully,

when one of ends sits in LAST-ACK or CLOSING retransmitting FIN

(and, probably, tail of data) and one or more our ACKs are lost.

What is TIME-WAIT timeout? It is associated with maximal packet

lifetime in the internet, which results in wrong conclusion, that

it is set to catch "old duplicate segments" wandering out of their path.

It is not quite correct. This timeout is calculated so that it exceeds

maximal retransmission timeout enough to allow to lose one (or more)

segments sent by peer and our ACKs. This time may be calculated from RTO.

When TIME-WAIT socket receives RST, it means that another end

finally closed and we are allowed to kill TIME-WAIT too.

Second purpose of TIME-WAIT is catching old duplicate segments.

Well, certainly it is pure paranoia, but if we load TIME-WAIT

with this semantics, we MUST NOT kill TIME-WAIT state with RSTs.

If we invented some more clever way to catch duplicates

(f.e. based on PAWS), we could truncate TIME-WAIT to several RTOs.

依据上面RFC的描绘和内核代码的注释描绘,能够看出Linux kernel完结了TIME-WAIT状况的快速收回机制,快速收回的细节能够参阅文章《为何客户端忽然呈现很多TIME_WAIT堆积》中的“TCP TIME_WAIT的快速收回”部分。而Linux能够扔掉60秒TIME-WAIT时刻,直接缩短到3.5倍RTO时刻,是因为Linux运用了一些“聪明”的办法来捕捉旧重复报文(例如:依据PAWS机制),而Linux中的确运用了per-host PAWS来防止前面衔接中的报文串扰到新的衔接中。

Linux内核的完结

在tcp_ipv4.c中,在接纳SYN之前,假如契合如下两个条件,需求查看peer是不是proven,即per-host PAWS查看:

  • 收到的报文有TCP option timestamp时刻戳
  • 本机敞开了内核参数net.ipv4.tcp_tw_recycle
...
else if (!isn) {
/* VJ's idea. We save last timestamp seen
* from the destination in peer table, when entering
* state TIME-WAIT, and check against it before
* accepting new connection request.
*
* If "isn" is not zero, this request hit alive
* timewait bucket, so that all the necessary checks
* are made in the function processing timewait state.
*/
if (tmp_opt.saw_tstamp && // 收到的报文中有TCP timestamp option
tcp_death_row.sysctl_tw_recycle && // 敞开了net.ipv4.tcp_tw_recycle内核参数
(dst = inet_csk_route_req(sk, &fl4, req)) != NULL &&
fl4.daddr == saddr) {
if (!tcp_peer_is_proven(req, dst, true)) { // peer查看(per-host PAWS查看)
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
goto drop_and_release;
}
}

在tcp_metrics.c中,是Linux per-host PAWS的完结逻辑,如下。简略描绘下便是在这一节开端说到的:需求在60秒内新来的SYN报文TCP option中的timestamp是添加的。

bool tcp_peer_is_proven(struct request_sock *req, struct dst_entry *dst, bool paws_check)
{
struct tcp_metrics_block *tm;
bool ret;
...

tm = __tcp_get_metrics_req(req, dst);
if (paws_check) {
if (tm &&
// peer 信息保存的时刻离现在在60秒(TCP_PAWS_MSL)之内
(u32)get_seconds() - tm->tcpm_ts_stamp < TCP_PAWS_MSL &&
// peer 信息中保存的timestamp 比其时收到的SYN报文中的timestamp大1(TCP_PAWS_WINDOW)
(s32)(tm->tcpm_ts - req->ts_recent) > TCP_PAWS_WINDOW)
ret = false;
else
ret = true;
}
}

对NAT环境中客户端的影响

在Linux创造这个per-host PAWS机制来让TIME-WAIT状况快速收回时,以为这是"clever way",是依据IPv4地址池数量足够的网络环境下来做的处理方案。而跟着Internet的快速开展,NAT的运用越来越遍及,客户端在SNAT设备内部的来拜访同个服务器的环境十分遍及。

Per-host PAWS机制运用TCP option里的timestamp字段的添加来判别串扰数据,而timestamp是依据客户端各自的CPU tick得出的值,关于NAT内部的设备而言能够说是彻底随机。当客户端主机1经过NAT和服务器树立TCP衔接,然后服务器自动封闭并且快速收回TIME-WAIT状况socket后,其他客户端主机的新衔接源IP和服务器peer table里记载的相同,可是TCP option里的timestamp和其时服务器记载的主机1的timestamp比较是彻底随机的,或许了解为50%概率。假如timestamp比主机1的小,则这个新建衔接在60秒内就会被回绝,60秒后新建衔接又能够成功;假如timestamp比主机1的大,则新建衔接直接成功。所以在客户端看来,问题现象便是新建衔接时通时不通。

这便是运用TIME-WAIT快速收回机制对NAT环境客户端带来的副作用。这个副作用不是在规划per-host PAWS机制之初就能意料到了,因为其时的网络环境和现在大为不同。而在现在的网络环境下,仅有的主张便是封闭TIME-WAIT快速收回,即让net.ipv4.tcp_tw_recycle=0。封闭net.ipv4.tcp_timestamps来去掉TCP option中的timestamp时刻戳也能够处理此问题,可是因为timestamp是核算RTT和RTO的根底,一般不主张封闭。

Troubleshooting

在实践出产中,troubleshoot这个问题是一件不太简略的工作。可是关于net.ipv4.tcp_tw_recycle和net.ipv4.tcp_timestamps都敞开的服务器,并且有NAT客户端拜访时,这个问题呈现的概率十分大,所以假如获取到这两个内核参数的设置和客户端网络的NAT环境,就能够做个根本断定。

别的能够参阅netstat -s中的计算,这个计算会聚集从/proc/net/snmp,/proc/net/netstat和/proc/net/sctp/snmp拿到的数据。如下,下面这个计算值表明因为timestamp的原因多少新建衔接被回绝,这是一个前史计算总值,所以两个时刻点的差值对问题排查愈加有意义。

xx passive connections rejected because of time stamp

2. Accept queue满形成drop SYN报文

问题现象

没有一致且有规则的现象,发作在TCP accept queue满的时分。这种状况往往发作在用户空间的运用程序有问题的时分,整体来说发作的概率不是很大。

原理

Accept queue 翻译成彻底衔接行列或许接纳行列,为了防止歧义,本文一致用英文原名。新的衔接完结3次握手后进入accept queue, 用户空间的运用调用accept体系调用来获取这个衔接,并创立一个新的socket,回来与socket相关的文件描绘符(fd)。在用户空间能够运用poll等机制经过readable event来获取到有新完结3次握手的衔接进入到了accept queue, 取得告诉后当即调用accept体系调用来获取新的衔接。

Accept queue的长度自身是有限的,它的长度取决于min [backlog, net.core.somaxconn],即这个两个参数中较小的值。

  • backlog 是运用调用listen体系调用时的第2个参数。参阅#include 中的int listen(int sockfd, int backlog)。
  • net.core.somaxconn 是体系内核参数,默许是128。运用listen的时分假如设置的backlog比较大,如NGINX默许512,可是这个大局内核参数不调整的话,accept queue的长度仍是会决定于其间较小的net.core.somaxconn。

即使是并发衔接量很大的状况,运用程序正常运用accpet体系调用取accept queue里的衔接都不会因为功率问题而获取不及时。可是假如因为运用程序堵塞,发作取衔接不及时的状况或许就或许会导致accept queue满的状况的,从而对新来的SYN报文进行丢掉。

Linux内核的完结

在tcp_ipv4中,accept queue满回绝SYN报文的完结很简略,如下:

/* Accept backlog is full. If we have already queued enough
* of warm entries in syn queue, drop request. It is better than
* clogging syn queue with openreqs with exponentially increasing
* timeout.
*/
// 假如accept queue满了,并且SYN queue中有未SYNACK重传过的半衔接,则丢掉SYN恳求
if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
goto drop;
}

在sock.h中界说了accept queue满的inline函数:

static inline bool sk_acceptq_is_full(const struct sock *sk)
{
return sk->sk_ack_backlog > sk->sk_max_ack_backlog;
}

在inet_connection_sock.h和request_sock.h中界说了判别SYN queue中有未SYNACK重传过的半衔接的办法:

static inline int inet_csk_reqsk_queue_young(const struct sock *sk)
{
return reqsk_queue_len_young(&inet_csk(sk)->icsk_accept_queue);
}
static inline int reqsk_queue_len_young(const struct request_sock_queue *queue)
{
return queue->listen_opt->qlen_young;
}

如上是3.10中的完结,其实需求判别两个条件,“accept queue满”是一个,“SYN queue中有未SYNACK重传过的半衔接”是别的一个,因为一般accept queue满的时分都是有很多新进衔接的时分,所以第二个条件是一般是一起满意的。假如accept queue满的时分,SYN queue中不存在未SYNACK重传过的半衔接,则Linux内核仍是会承受这个SYN并回来SYNACK。这种状况在实践出产中十分罕见,除非发作运用进程彻底阻滞的状况,比方用SIGSTOP信号来停进程,这样在accept queue满的时分TCP内核协议栈依然不会直接drop SYN报文。

因为accept queue满而drop SYN的逻辑,在比较新的内核版别中稍微有改变。比方4.10的版别,内核的判别条件从两个变成了一个,即只判别accept queue是不是满,所以在这些版别中,accept queue满了后内核一定会直接drop SYN报文。

Troubleshooting

这类问题往往发作在用户空间的运用程序有问题的时分,整体来说发作的概率不是很大。有如下两种办法承认:

运用ss指令查看实时问题

运用ss指令的选项-l查看listening socket,能够看到Recv-Q和Send-Q,其间Recv-Q表明其时accept queue中的衔接数量,Send-Q表明accept queue的最大长度。如下:能够看到几个进程的accept queue默许是128,因为遭到体系net.core.somaxconn=128的约束。

netstat -s 计算

能够参阅netstat -s中的计算,下面这个计算值表明因为socket overflowed原因多少新建衔接被回绝,相同这是一个前史计算总值,两个时刻点的差值对问题排查愈加有意义。

xx times the listen queue of a socket overflowed

处理主张

假如承认是因为accept queue引起的SYN报文被drop的问题,很自然会想到的处理方案是添加accept queue的长度,一起增大backlog和net.core.somaxconn两个参数能添加accept queue长度。可是一般这个只能“缓解”,并且最有或许呈现的局势是accept queue在增大后又敏捷被填满。所以处理这个问题最主张的办法是从运用程序看下为什么accept新衔接慢,从本源上处理问题。

总结

上面总结了per-host PAWS查看和accept queue满形成SYN被丢掉这两种最首要的场景,并别离介绍了现象,原理,代码逻辑和排查办法。这两种场景能掩盖平常的绝大部分TCP协议栈丢SYN的问题,假如遇到其他协议栈里丢SYN的状况,需求结合参数装备和代码逻辑进一步case by case地排查。