三次握手四次挥手

三次握手

  1. 客户端通过向服务器端发送一个SYN来创建一个主动打开,作为三路握手的一部分。客户端把这段连接的序号设定为随机数 A。
  2. 服务器端应当为一个合法的SYN回送一个SYN/ACK。ACK 的确认码应为 A+1,SYN/ACK 包本身又有一个随机序号 B。
  3. 最后,客户端再发送一个ACK。当服务端受到这个ACK的时候,就完成了三路握手,并进入了连接创建状态。此时包序号被设定为收到的确认号 A+1,而响应则为 B+1。

 

四次挥手

注意: 中断连接端可以是客户端,也可以是服务器端. 下面仅以客户端断开连接举例, 反之亦然.

  1. 客户端发送一个数据分段, 其中的 FIN 标记设置为1. 客户端进入 FIN-WAIT 状态. 该状态下客户端只接收数据, 不再发送数据.
  2. 服务器接收到带有 FIN = 1 的数据分段, 发送带有 ACK = 1 的剩余数据分段, 确认收到客户端发来的 FIN 信息.
  3. 服务器等到所有数据传输结束, 向客户端发送一个带有 FIN = 1 的数据分段, 并进入 CLOSE-WAIT 状态, 等待客户端发来带有 ACK = 1 的确认报文.
  4. 客户端收到服务器发来带有 FIN = 1 的报文, 返回 ACK = 1 的报文确认, 为了防止服务器端未收到需要重发, 进入 TIME-WAIT 状态. 服务器接收到报文后关闭连接. 客户端等待 2MSL 后未收到回复, 则认为服务器成功关闭, 客户端关闭连接.

 

 

 

1、TIME_WAIT 状态也称为2MSL等待时间
2、MSL是最大段生存时间。
3、1MSL是一个方向的存活时间=2分钟 就是发送一个包,没有响应的最大时间
主动发起FIN的一方是主动关闭的一方,会发送最后一个ACK(最下面的ACK),会进入TIME_WAIT,2倍MSL=4分钟,为了防止最后一个ACK丢失,给足时间接收另外一端重传最后的FIN。
原因:主动关闭方的最后一个ACK有可能超时、丢包,服务器由于没有收到ACK,服务器会重传FIN过来,所以会停在这个两倍MSL
处于TIME_WAIT状态这个TCP的五元组不能被其他连接所使用,被HOLD住。
一般客户端会执行主动关闭,服务器通常执行被动关闭不会进入TIME_WAIT状
这儿有几个细节需要说明的。先看三次挥手的过程中。

客户端第一次请求服务端,服务端收到包后会将这个数据放入到一个队列中,叫作syn_table,以便于后期的验证需求。接着服务器第二次收到包,也就是三次握手的第三步,进行验证,没问题之后放入request_sock_queue队列中,完成三次握手。

那么这时候,所谓的syn攻击也就是客户端源源不断地发送第一个包请求,而不去发送第三个确认包。服务器尝试着进行多次重发(可以由tcp_synack_retries进行配置),需要大量额外的开销。更严重的是,上面提到的等待队列syn_table被占满,导致后来的请求不再被服务器接收,损失惨重。

想要应对这种问题呢,首先最先想到的就是加大syn_table的队列长度咯,这个可以通过tcp_max_syn_backlog进行配置。不过饮鸩不止渴。

更好的方法,其实我们可以通过配置tcp_syncookies来实现,默认为0,表示关闭,把它配成1打开它。意思是当syn等待队列满的时候,新来的请求不放入队列中,而是通过cookie的方式进行处理。这话说得比较抽象。形象点描述这个算法就是,tcp在第二次握手的seq上稍作了手脚,将用户请求的参数(包括请求的地址、端口等),加上服务器的一个序列号等做了一次运算,得到了一个32位的无符号整数,并将这个整数作为Seq值发送给客户端。这个时候出现两种状况:

  1. 客户端是攻击者:客户端不再做出第三次握手的响应。这个时候对服务器而言,并没有任何损失,因为他不占用服务器任何资源。
  2. 客户端是正常的用户:客户端会将这个Seq的值加一作为Ack返回给服务器。服务器拿着这个Ack值减一,进行刚才算法的逆运算进行校验,看是否得到和发出去的数据一致。如果能得到,则是一个有效的响应,否则就不是。

这要就可以有效地防止一些syn攻击,当然只是最初步的,等着你的还有DoS、DDoS、DRDoS。

接着把眼光转移到四次挥手上面,关注两个状态CLOSE_WAIT,TIME_WAIT。

首先来看前者,大量出现CLOSE_WAIT一般都是程序写的有问题,也就是程序员的锅。这种情况下,套接字是被动关闭的。举个例子,如果你在你的电脑上用httpclient请求服务器上一个资源,而那个资源刚好没有,服务器会主动发出关闭连接的请求,那么你就是属于被动关闭了,而恰好你有些健忘,忘了处理httpclient的连接释放,就会造成大量的CLOSE_WAIT。

针对这种情况,有两种补救措施,首先就是设置socket选项SO_REUSEADDR,表示重用端口和地址,避免不同的端口上堆积大量的CLOSE_WAIT。其次设置SO_LINGER,设为0,相当于强行关闭,便不用担心closesocket调用进行等待完成状态。当然这种情况,最需要的就是review代码。

接下来看关于TIME_WAIT的问题,这是由于需要可靠地实现TCP全双工连接的终止设计而成的,是一种正常的现象。但如果大量堆积,可以通过一些方法来优化。

首先同样是SO_LINGER,通过发送RST分组(而不是用正常的FIN|ACK|FIN|ACK四个分组)来关闭该连接,主动关闭一方的TCP状态则跳过TIMEWAIT,直接进入CLOSED。其实这种方法是有风险的。So_REUSEADDR,解决端口重用问题,我们可以不用等待一整个TIME_WAIT的周期,即2*MSL,就可以瞬时重用。

接着来说说tcp_max_tw_buckets这个参数,这个是控制并发的TIME_WAIT的数量,默认值是180000,如果超限,那么,系统会把多的给destory掉。于是,我们可以通过增大这个参数来避免time_wait堆积出现的问题。当然,这有点南辕北辙。

最后要说tcp_tw_reuse和tcp_tw_recyle这两个参数。前者表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,只对客户端起作用,开启后客户端在1s内回收。后者表示开启TCP连接中TIME-WAIT sockets的快速回收,对客户端和服务器同时起作用,开启后在 3.5*RTO 内回收,RTO 200ms~ 120s 具体时间视网络状况。内网状况比tw_reuse 稍快,公网尤其移动网络大多要比tw_reuse 慢,优点就是能够回收服务端的TIME_WAIT数量。这两者必须在tcp_timestamps打开的时候才生效。

那么这儿会有一个坑,如果在公司里,大部分都使用NAT的情况下,如果开启tcp_tw_recyle,会出现机器连不上的情况。

如果tcp_tw_recycle开启,同时tcp_timestamps也开启,tcp会记录每个连接的时间戳,如果后续时间戳比之前记录的时间戳小,就会认为这是错误的连接,拒绝这个连接。在lvs使用nat的情况,用户请求到lvs,LVS会修改地址数据后将请求转发给后端服务器,但不会修改时间戳(因为nat的机制就是只修改源地址和目的地址)。在后端服务器看来,请求的源地址永远都是LVS的地址,并且端口复用,原本不同客户端的请求经过LVS的转发,就可能会被认为是同一个连接,加之不同客户端的时间可能不一致,所以就会出现时间戳错乱的现象,于是后面的数据包就被丢弃了,就会出现部分用户能连接服务器,部分用户不能连接服务器的情况。一般线上,仅开启tcp_tw_reuse就够了。

当然TIME_WAIT一般是短连接导致,某些情况下可以改成长连接。但是长连接太多会影响服务器性能。

posted @ 2017-08-15 13:57  悉达多尊  阅读(297)  评论(0编辑  收藏  举报