主动关闭 tcp_timewait_state_process 处理
正常情况下主动关闭连接的一端在连接正常终止后,会进入TIME_WAIT状态,存在这个状态有以下两个原因(参考《Unix网络编程》):
      1、保证TCP连接关闭的可靠性。如果最终发送的ACK丢失,被动关闭的一端会重传最终的FIN包,如果执行主动关闭的一端没有维护这个连接的状态信息,会发送RST包响应,导致连接不正常关闭。
      2、允许老的重复分组在网络中消逝。假设在一个连接关闭后,发起建立连接的一端(客户端)立即重用原来的端口、IP地址和服务端建立新的连接。老的连接上的分组可能在新的连接建立后到达服务端,TCP必须防止来自某个连接的老的重复分组在连接终止后再现,从而被误解为同一个连接的化身。要实现这种功能,TCP不能给处于TIME_WAIT状态的连接启动新的连接。TIME_WAIT的持续时间是2MSL,保证在建立新的连接之前老的重复分组在网络中消逝。这个规则有一个例外:如果到达的SYN的序列号大于前一个连接的结束序列号,源自Berkeley的实现将给当前处于TIME_WAIT状态的连接启动新的化身。
服务器段进入TIME_WAIT后内核的处理,即服务器主动关闭连接。TCP层的接收函数是tcp_v4_rcv(),和TIME_WAIT状态相关的主要代码,
tcp_timewait_state_process这个函数具体作用,它就是分为两部分,一部分处理tw_substate== TCP_FIN_WAIT2的情况,
一部分是正常情况。在前一种情况,我们对于syn的相应是直接rst的。而后一种我们需要判断是否新建连接。
而对于fin的处理他们也是不一样的,wait2的话,它会将当前的tw重新加入到定时器列表
(inet_twsk_schedule).而后一种则只是重新发送ack。
do_time_wait:
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
inet_twsk_put(inet_twsk(sk));
goto discard_it;
} if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) {
TCP_INC_STATS_BH(net, TCP_MIB_INERRS);
inet_twsk_put(inet_twsk(sk));
goto discard_it;
}
switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
case TCP_TW_SYN: {
//取得一个sk。
struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),
&tcp_hashinfo,
iph->daddr, th->dest,
inet_iif(skb));
if (sk2) {
//从tw中删除,然后继续执行(也就是开始三次握手)。
inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);
inet_twsk_put(inet_twsk(sk));
sk = sk2;
goto process;
}
/* Fall through to ACK */
}
case TCP_TW_ACK://发送ack
tcp_v4_timewait_ack(sk, skb);
break;
case TCP_TW_RST://发送给对端rst。
goto no_tcp_socket;
case TCP_TW_SUCCESS:;//处理成功
}
goto discard_it;
接收到SKb包后,会调用__inet_lookup_skb()查找对应的sock结构。如果套接字状态是TIME_WAIT状态,会跳转到do_time_wait标签处处理。从代码中可以看到,主要由tcp_timewait_state_process()函数来处理SKB包,处理后根据返回值来做相应的处理。
  在看tcp_timewait_state_process()函数中的处理之前,需要先看一看不同的返回值会对应什么样的处理。
  如果返回值是TCP_TW_SYN,则说明接收到的是一个“合法”的SYN包(也就是说这个SYN包可以接受),这时会首先查找内核中是否有对应的监听套接字,如果存在相应的监听套接字,则会释放TIME_WAIT状态的传输控制结构,跳转到process处开始处理,开始建立一个新的连接。如果没有找到监听套接字会执行到TCP_TW_ACK分支。
  如果返回值是TCP_TW_ACK,则会调用tcp_v4_timewait_ack()发送ACK,然后跳转到discard_it标签处,丢掉数据包。
  如果返回值是TCP_TW_RST,则会调用tcp_v4_send_reset()给对端发送RST包,然后丢掉数据包。
  如果返回值是TCP_TW_SUCCESS,则会直接丢掉数据包。
  接下来我们通过tcp_timewait_state_process()函数来看TIME_WAIT状态下的数据包处理。
  为了方便讨论,假设数据包中没有时间戳选项,在这个前提下,tcp_timewait_state_process()中的局部变量paws_reject的值为0。
  如果需要保持在FIN_WAIT_2状态的时间小于等于TCP_TIMEWAIT_LEN,则会从FIN_WAIT_2状态直接迁移到TIME_WAIT状态,也就是使用描述TIME_WAIT状态的sock结构代替当前的传输控制块。虽然这时的sock结构处于TIME_WAIT结构,但是还要区分内部状态,这个内部状态存储在inet_timewait_sock结构的tw_substate成员中。
/*
* * 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.
*
* The algorithm below is based on FORMAL INTERPRETATION of RFCs.
* When you compare it to RFCs, please, read section SEGMENT ARRIVES
* from the very beginning.
*
* NOTE. With recycling (and later with fin-wait-2) TW bucket
* is _not_ stateless. It means, that strictly speaking we must
* spinlock it. I do not want! Well, probability of misbehaviour
* is ridiculously low and, seems, we could use some mb() tricks
* to avoid misread sequence numbers, states etc. --ANK
*/
enum tcp_tw_status
tcp_timewait_state_process(struct inet_timewait_sock *tw, struct sk_buff *skb,
const struct tcphdr *th)
{
struct tcp_options_received tmp_opt;
const u8 *hash_location;
struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw);
bool paws_reject = false; tmp_opt.saw_tstamp = 0;
if (th->doff > (sizeof(*th) >> 2) && tcptw->tw_ts_recent_stamp) {
tcp_parse_options(skb, &tmp_opt, &hash_location, 0, NULL); if (tmp_opt.saw_tstamp) {
tmp_opt.ts_recent = tcptw->tw_ts_recent;
tmp_opt.ts_recent_stamp = tcptw->tw_ts_recent_stamp;
paws_reject = tcp_paws_reject(&tmp_opt, th->rst);
}
}
// 如果内部状态为FIN_WAIT_2,tcp_timewait_state_process()中处理的关键代码片段如下所示:
if (tw->tw_substate == TCP_FIN_WAIT2) {
/* Just repeat all the checks of tcp_rcv_state_process() */ /* Out of window, send ACK
如果TCP段序号不完全在接收窗口内,则返回TCP_TW_ACK,表示需要给对端发送ACK。
*/
if (paws_reject ||
!tcp_in_window(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq,
tcptw->tw_rcv_nxt,
tcptw->tw_rcv_nxt + tcptw->tw_rcv_wnd))
return TCP_TW_ACK; if (th->rst)//收到的是RST包,则跳转到kill标签处处理,立即释放timewait控制块,并返回TCP_TW_SUCCESS。
goto kill;
/*
如果是SYN包,但是SYN包的序列号在要接收的序列号之前,
则表示这是一个过期的SYN包,则跳转到kill_with_rst标签处处理,
此时不仅会释放TIME_WAIT传输控制块,还会返回TCP_TW_RST,要给对端发送RST包。
*/
if (th->syn && !before(TCP_SKB_CB(skb)->seq, tcptw->tw_rcv_nxt))
goto kill_with_rst; /* Dup ACK?如果接收到DACK,则释放timewait控制块,并返回TCP_TW_SUCCESS */
if (!th->ack ||
!after(TCP_SKB_CB(skb)->end_seq, tcptw->tw_rcv_nxt) ||
TCP_SKB_CB(skb)->end_seq == TCP_SKB_CB(skb)->seq) {
inet_twsk_put(tw);
return TCP_TW_SUCCESS;
} /*
所以之后的处理是在数据包中的数据不为空的情况下处理。
前面的处理中已经处理了SYN包、RST包的情况,接下来就剩以下三种情况:
1、不带FIN标志的数据包
2、带FIN标志,但是还包含数据
3、FIN包,不包含数据 */
/* New data or FIN. If new data arrive after half-duplex close,
* reset. 在finwait2 状态下接收到非 fin段, 或者接收到的序列号和
预期的不符合 则释放timewait 并返回 rst
*/
if (!th->fin ||
TCP_SKB_CB(skb)->end_seq != tcptw->tw_rcv_nxt + 1) {
kill_with_rst:
inet_twsk_deschedule(tw, &tcp_death_row);
inet_twsk_put(tw);
return TCP_TW_RST;
} /* FIN arrived, enter true time-wait state.
如果接收的是对端的FIN包,即第3种情况,则将time_wait控制块的子状态设置为TCP_TIME_WAIT,
此时才是进入真正的TIME_WAIT状态。然后根据TIME_WAIT的持续时间的长短来确定是加入到
twcal_row队列还是启动一个定时器,最后会返回TCP_TW_ACK,给对端发送TCP连接关闭时最后
的ACK包。? 到这里,我们看到了对FIN_WAIT_2状态(传输控制块状态为TIME_WAIT状态下,
但是子状态为FIN_WAIT_2)的完整处
*/
tw->tw_substate = TCP_TIME_WAIT;
tcptw->tw_rcv_nxt = TCP_SKB_CB(skb)->end_seq;
if (tmp_opt.saw_tstamp) {
tcptw->tw_ts_recent_stamp = get_seconds();
tcptw->tw_ts_recent = tmp_opt.rcv_tsval;
} if (tcp_death_row.sysctl_tw_recycle &&
tcptw->tw_ts_recent_stamp &&
tcp_tw_remember_stamp(tw))
inet_twsk_schedule(tw, &tcp_death_row, tw->tw_timeout,
TCP_TIMEWAIT_LEN);
else
inet_twsk_schedule(tw, &tcp_death_row, TCP_TIMEWAIT_LEN,
TCP_TIMEWAIT_LEN);
return TCP_TW_ACK;
} /*
* Now real TIME-WAIT state.
*
* RFC 1122:
* "When a connection is [...] on TIME-WAIT state [...]
* [a TCP] MAY accept a new SYN from the remote TCP to
* reopen the connection directly, if it:
*
* (1) assigns its initial sequence number for the new
* connection to be larger than the largest sequence
* number it used on the previous connection incarnation,
* and
*
* (2) returns to TIME-WAIT state if the SYN turns out
* to be an old duplicate".
*/
/*
在TIME_WAIT状态下,接收到ACK包(不带数据)或RST包,
并且包的序列号刚好是下一个要接收的序列号
*/
if (!paws_reject &&
(TCP_SKB_CB(skb)->seq == tcptw->tw_rcv_nxt &&
(TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq || th->rst))) {
/* In window segment, it may be only reset or bare ack. */ if (th->rst) {
/* This is TIME_WAIT assassination, in two flavors.
* Oh well... nobody has a sufficient solution to this
* protocol bug yet.
如果是RST包的话,并且系统配置sysctl_tcp_rfc1337
(默认情况下为0,参见/proc/sys/net/ipv4/tcp_rfc1337)
的值为0,这时会立即释放time_wait传输控制块,
丢掉接收的RST包。
*/
if (sysctl_tcp_rfc1337 == 0) {
kill:
inet_twsk_deschedule(tw, &tcp_death_row);
inet_twsk_put(tw);
return TCP_TW_SUCCESS;
}
}//如果是ACK包,则会启动TIME_WAIT定时器后丢掉接收到的ACK包。
inet_twsk_schedule(tw, &tcp_death_row, TCP_TIMEWAIT_LEN,
TCP_TIMEWAIT_LEN); if (tmp_opt.saw_tstamp) {
tcptw->tw_ts_recent = tmp_opt.rcv_tsval;
tcptw->tw_ts_recent_stamp = get_seconds();
} inet_twsk_put(tw);
return TCP_TW_SUCCESS;
} /* Out of window segment. All the segments are ACKed immediately. The only exception is new SYN. We accept it, if it is
not old duplicate and we are not in danger to be killed
by delayed old duplicates. RFC check is that it has
newer sequence number works at rates <40Mbit/sec.
However, if paws works, it is reliable AND even more,
we even may relax silly seq space cutoff. RED-PEN: we violate main RFC requirement, if this SYN will appear
old duplicate (i.e. we receive RST in reply to SYN-ACK),
we must return socket to time-wait state. It is not good,
but not fatal yet. 接下来是对SYN包的处理。前面提到了,如果在TIME_WAIT状态下接收到序列号比
上一个连接的结束序列号大的SYN包,可以接受,并建立新的连接, */ if (th->syn && !th->rst && !th->ack && !paws_reject &&
(after(TCP_SKB_CB(skb)->seq, tcptw->tw_rcv_nxt) ||
(tmp_opt.saw_tstamp &&
(s32)(tcptw->tw_ts_recent - tmp_opt.rcv_tsval) < 0))) {
u32 isn = tcptw->tw_snd_nxt + 65535 + 2;
if (isn == 0)
isn++;
TCP_SKB_CB(skb)->when = isn;
return TCP_TW_SYN;
} if (paws_reject)
NET_INC_STATS_BH(twsk_net(tw), LINUX_MIB_PAWSESTABREJECTED);
/*
下面这段代码就是来处理这样的情况:
? 当返回TCP_TW_SYN时,在tcp_v4_rcv()中会立即释放time_wait控制块,
并且开始进行正常的连接建立过程。? 如果数据包不是上述几种类型的包,
可能的情况有:?
1、不是有效的SYN包。不考虑时间戳的话,
就是序列号在上一次连接的结束序列号之前?
2、ACK包,起始序列号不是下 一个要接收的序列号?
3、RST包,起始序列号不是下一个要接收的序列号?
4、带数据的SKB包 */
if (!th->rst) {
/*
如果带有ACK标志的话,则会启动TIME_WAIT定时器,然后给对端发送ACK。我们知道SYN包正常情况下不会设置ACK标志,
所以如果是SYN包不会启动TIME_WAIT定时器,只会给对端发送ACK,告诉对端已经收到SYN包,避免重传,但连接应该不会继续建立。
? 还有一个细节需要提醒下,就是我们看到在返回TCP_TW_ACK时,没有调用inet_twsk_put()释放对time_wait控制块的引用。
这时因为在tcp_v4_rcv()中调用tcp_v4_timewait_ack()发送ACK时会用到time_wait控制块,所以需要保持对time_wait控制块的引用。
在tcp_v4_timewait_ack()中发送完ACK后,会调用inet_twsk_put()释放对time_wait控制块的引用。 */
/* In this case we must reset the TIMEWAIT timer.
*
* If it is ACKless SYN it may be both old duplicate
* and new good SYN with random sequence number <rcv_nxt.
* Do not reschedule in the last case.
*/
if (paws_reject || th->ack)
inet_twsk_schedule(tw, &tcp_death_row, TCP_TIMEWAIT_LEN,
TCP_TIMEWAIT_LEN); /* Send ACK. Note, we do not put the bucket,
* it will be released by caller.
*/
return TCP_TW_ACK;
}
// 如果是RST包,即第3种情况,则直接返回TCP_TW_SUCCESS,丢掉RST包。
inet_twsk_put(tw);
return TCP_TW_SUCCESS;
}

这只是 站在服务端的角度看问题 !!!
那么站在客户端呢??????????
又是怎样
主动关闭 tcp_timewait_state_process 处理的更多相关文章
- 关于TCP主动关闭连接中的wait_timeout
		
首先我们先来回顾一下tcp关闭连接的过程: 假设A和B连接状态为EST,A需要主动关闭: A发送FIN给B,并将状态更改为FIN_WAIT1, B接收到FIN将状态更改为CLOSE_WAIT,并回复A ...
 - 主动关闭  tcp fin-wait-2     time-wait 定时器
		
后面整理相关信息 //后面整理相关信息 /* * This function implements the receiving procedure of RFC 793 for * all state ...
 - Dialog中显示倒计时,到时自己主动关闭
		
这里直接用系统Dialog中加入了倒计时的显示,假设用自己定义Dialog会更美观: private TextView mOffTextView; private Handler mOffHandle ...
 - [C++] 自己主动关闭右下角弹窗
		
近期腾讯.迅雷等各种client,都越发喜欢在屏幕的右下角弹框了. 有骨气的人当然能够把这些软件卸载了事,可是这些client在某些情况下却又还是实用的.怎么办呢? 作为码农,自己实现一个自己主动关闭 ...
 - 封装WebSocket(建立链接、主动关闭)
		
一.前言 近期项目里需做一个在线聊天功能,就想要在对话的时候建立socket链接.又因为聊天只是其中一个部分,在它外面还有一些全局的消息通知需要接收,因此也需要建立socket链接.在该项目里不仅一处 ...
 - 主动关闭 time wait结构体
		
/* * This is a TIME_WAIT sock. It works around the memory consumption * problems of sockets in such ...
 - vue使用layer主动关闭弹窗
		
关闭当前框的弹出层 layer.close(layer.index); 刷新父层 parent.location.reload(); // 父页面刷新 关闭iframe 弹出的全屏层 var inde ...
 - 主动关闭 time-wait 2msl 处理
		
先上传后面整理 /* * This routine is called by the ICMP module when it gets some * sort of error condition. ...
 - TCP的三次握手(建立连接)和四次挥手(关闭连接)
		
参照: http://course.ccniit.com/CSTD/Linux/reference/files/018.PDF http://hi.baidu.com/raycomer/item/94 ...
 
随机推荐
- C#数据结构-栈
			
栈的定义不需要多说,相信大家都非常熟悉,但是,在实际应用中栈的应用我们很少想到会去用栈结构,先上代码看下用法: Stack st = new Stack(); st.Push('A'); st.Pus ...
 - linux启动过程中建立临时页表
			
intel的x86这种架构为了兼容以前同系列的架构有一些很繁琐无用的东西.比如分段和分页两种机制都可以实现隔离进程的内存空间,在x86上两种机制都有,用起来比较繁琐.所以linux内核在启动的时候通过 ...
 - Docker Stack 笔记
			
Docker Compose (Docker Stack) image: Specify the image to start the container from. Can either be a ...
 - go ioutial 读取写入文件
			
package main import ( "fmt" "io/ioutil" "os" ) func main() { // 读取文件 / ...
 - 第十七章 DNS原理
			
一.DNS的相关介绍 1.主机名与IP地址映射需求 1)IP地址难于记忆 2)能否用便于记忆的名字来映射IP地址? 2.hosts文件 1)hosts文件记录了主机名和IP地址的对应信息 2)host ...
 - spring boot报错:Invalid bound statement (not found): com.
			
经检查发现mapper的namespace没写全导致的 正确应该写成这样就可以了:
 - 关于node回调函数中同步和异步操作的理解
			
1.node的回调函数:如果一个方法的参数是另一个函数的名字,则这个参数本身就要回调函数,这个函数就是回调函数 1).同步操作文件(阻塞I/O) 同步就是一个人干完这个再干那个-- 所 ...
 - Nacos快速入门
			
什么是 Nacos Nacos 是阿里巴巴推出来的一个新开源项目,这是一个更易于构建云原生应用的动态服务发现.配置管理和服务管理平台. Nacos 致力于帮助您发现.配置和管理微服务.Nacos 提供 ...
 - org.apache.rocketmq.client.exception.MQClientException: No route info of this topic, TopicTest异常解决
			
使用RocketMQ发送消息抛出异常,异常如下: 原因: Broker 禁止自动创建Topic,且用户没有通过手动创建此Topic,或者broker 和 Nameserver网络不通: 解决方案: 1 ...
 - 如何玩转Python? 一文总结30种Python的窍门和技巧
			
Python作为2019年必备语言之一,展现了不可替代作用.对于所有的数据科学工作者,如何提高使用Python的效率,这里,总结了30种Python的最佳实践.技巧和窍门.希望这些可以帮助大家在202 ...