懒人凳子网站建设策划书,申请域名后如何发布网站,东南融通网站建设,大数据培训班需要多少钱记得刚毕业找工作面试的时候#xff0c;经常会被问到#xff1a;你知道“3次握手#xff0c;4次挥手”吗#xff1f;这时候我会“胸有成竹”地“背诵”前期准备好的“答案”#xff0c;第一次怎么怎么#xff0c;第二次……答完就没有下文了#xff0c;面试官貌似也没有… 记得刚毕业找工作面试的时候经常会被问到你知道“3次握手4次挥手”吗这时候我会“胸有成竹”地“背诵”前期准备好的“答案”第一次怎么怎么第二次……答完就没有下文了面试官貌似也没有深入下去的意思深入下去我也不懂皆大欢喜作为程序员要有“刨根问底”的精神。知其然更要知其所以然。这篇文章希望能抽丝剥茧还原背后的原理。为了便于整体把握文章手动加上文章目录什么是“3次握手4次挥手”TCP是一种面向连接的单播协议在发送数据前通信双方必须在彼此间建立一条连接。所谓的“连接”其实是客户端和服务器的内存里保存的一份关于对方的信息如ip地址、端口号等。TCP可以看成是一种字节流它会处理IP层或以下的层的丢包、重复以及错误问题。在连接的建立过程中双方需要交换一些连接的参数。这些参数可以放在TCP头部。TCP提供了一种可靠、面向连接、字节流、传输层的服务采用三次握手建立一个连接。采用4次挥手来关闭一个连接。TCP服务模型在了解了建立连接、关闭连接的“三次握手和四次挥手”后我们再来看下TCP相关的东西。一个TCP连接由一个4元组构成分别是两个IP地址和两个端口号。一个TCP连接通常分为三个阶段启动、数据传输、退出关闭。当TCP接收到另一端的数据时它会发送一个确认但这个确认不会立即发送一般会延迟一会儿。ACK是累积的一个确认字节号N的ACK表示所有直到N的字节不包括N已经成功被接收了。这样的好处是如果一个ACK丢失很可能后续的ACK就足以确认前面的报文段了。一个完整的TCP连接是双向和对称的数据可以在两个方向上平等地流动。给上层应用程序提供一种双工服务。一旦建立了一个连接这个连接的一个方向上的每个TCP报文段都包含了相反方向上的报文段的一个ACK。序列号的作用是使得一个TCP接收端可丢弃重复的报文段记录以杂乱次序到达的报文段。因为TCP使用IP来传输报文段而IP不提供重复消除或者保证次序正确的功能。另一方面TCP是一个字节流协议绝不会以杂乱的次序给上层程序发送数据。因此TCP接收端会被迫先保持大序列号的数据不交给应用程序直到缺失的小序列号的报文段被填满。TCP头部源端口和目的端口在TCP层确定双方进程序列号表示的是报文段数据中的第一个字节号ACK表示确认号该确认号的发送方期待接收的下一个序列号即最后被成功接收的数据字节序列号加1这个字段只有在ACK位被启用的时候才有效。当新建一个连接时从客户端发送到服务端的第一个报文段的SYN位被启用这称为SYN报文段这时序列号字段包含了在本次连接的这个方向上要使用的第一个序列号即初始序列号ISN之后发送的数据是ISN加1因此SYN位字段会消耗一个序列号这意味着使用重传进行可靠传输。而不消耗序列号的ACK则不是。头部长度图中的数据偏移以32位字为单位也就是以4bytes为单位它只有4位最大为15因此头部最大长度为60字节而其最小为5也就是头部最小为20字节可变选项为空。ACK —— 确认使得确认号有效。 RST —— 重置连接经常看到的reset by peer就是此字段搞的鬼。 SYN —— 用于初如化一个连接的序列号。 FIN —— 该报文段的发送方已经结束向对方发送数据。当一个连接被建立或被终止时交换的报文段只包含TCP头部而没有数据。状态转换三次握手和四次挥手的状态转换如下图。为什么要“三次握手四次挥手”三次握手换个易于理解的视角来看为什么要3次握手。客户端和服务端通信前要进行连接“3次握手”的作用就是双方都能明确自己和对方的收、发能力是正常的。第一次握手客户端发送网络包服务端收到了。这样服务端就能得出结论客户端的发送能力、服务端的接收能力是正常的。第二次握手服务端发包客户端收到了。这样客户端就能得出结论服务端的接收、发送能力客户端的接收、发送能力是正常的。 从客户端的视角来看我接到了服务端发送过来的响应数据包说明服务端接收到了我在第一次握手时发送的网络包并且成功发送了响应数据包这就说明服务端的接收、发送能力正常。而另一方面我收到了服务端的响应数据包说明我第一次发送的网络包成功到达服务端这样我自己的发送和接收能力也是正常的。第三次握手客户端发包服务端收到了。这样服务端就能得出结论客户端的接收、发送能力服务端的发送、接收能力是正常的。 第一、二次握手后服务端并不知道客户端的接收能力以及自己的发送能力是否正常。而在第三次握手时服务端收到了客户端对第二次握手作的回应。从服务端的角度我在第二次握手时的响应数据发送出去了客户端接收到了。所以我的发送能力是正常的。而客户端的接收能力也是正常的。经历了上面的三次握手过程客户端和服务端都确认了自己的接收、发送能力是正常的。之后就可以正常通信了。每次都是接收到数据包的一方可以得到一些结论发送的一方其实没有任何头绪。我虽然有发包的动作但是我怎么知道我有没有发出去而对方有没有接收到呢而从上面的过程可以看到最少是需要三次握手过程的。两次达不到让双方都得出自己、对方的接收、发送能力都正常的结论。其实每次收到网络包的一方至少是可以得到对方的发送、我方的接收是正常的。而每一步都是有关联的下一次的“响应”是由于第一次的“请求”触发因此每次握手其实是可以得到额外的结论的。比如第三次握手时服务端收到数据包表明看服务端只能得到客户端的发送能力、服务端的接收能力是正常的但是结合第二次说明服务端在第二次发送的响应包客户端接收到了并且作出了响应从而得到额外的结论客户端的接收、服务端的发送是正常的。用表格总结一下四次挥手TCP连接是双向传输的对等的模式就是说双方都可以同时向对方发送或接收数据。当有一方要关闭连接时会发送指令告知对方我要关闭连接了。这时对方会回一个ACK此时一个方向的连接关闭。但是另一个方向仍然可以继续传输数据等到发送完了所有的数据后会发送一个FIN段来关闭此方向上的连接。接收方发送ACK确认关闭连接。注意接收到FIN报文的一方只能回复一个ACK, 它是无法马上返回对方一个FIN报文段的因为结束数据传输的“指令”是上层应用层给出的我只是一个“搬运工”我无法了解“上层的意志”。“三次握手四次挥手”怎么完成其实3次握手的目的并不只是让通信双方都了解到一个连接正在建立还在于利用数据包的选项来传输特殊的信息交换初始序列号ISN。3次握手是指发送了3个报文段4次挥手是指发送了4个报文段。注意SYN和FIN段都是会利用重传进行可靠传输的。三次握手客户端发送一个SYN段并指明客户端的初始序列号即ISN(c).服务端发送自己的SYN段作为应答同样指明自己的ISN(s)。为了确认客户端的SYN将ISN(c)1作为ACK数值。这样每发送一个SYN序列号就会加1. 如果有丢失的情况则会重传。为了确认服务器端的SYN客户端将ISN(s)1作为返回的ACK数值。四次挥手1. 客户端发送一个FIN段并包含一个希望接收者看到的自己当前的序列号K. 同时还包含一个ACK表示确认对方最近一次发过来的数据。 2. 服务端将K值加1作为ACK序号值表明收到了上一个包。这时上层的应用程序会被告知另一端发起了关闭操作通常这将引起应用程序发起自己的关闭操作。3. 服务端发起自己的FIN段ACKK1, SeqL 4. 客户端确认。ACKL1为什么建立连接是三次握手而关闭连接却是四次挥手呢这是因为服务端在LISTEN状态下收到建立连接请求的SYN报文后把ACK和SYN放在一个报文里发送给客户端。而关闭连接时当收到对方的FIN报文时仅仅表示对方不再发送数据了但是还能接收数据己方是否现在关闭发送数据通道需要上层应用来决定因此己方ACK和FIN一般都会分开发送。“三次握手四次挥手”进阶ISN三次握手的一个重要功能是客户端和服务端交换ISN(Initial Sequence Number), 以便让对方知道接下来接收数据的时候如何按序列号组装数据。如果ISN是固定的攻击者很容易猜出后续的确认号。ISN M F(localhost, localport, remotehost, remoteport)ISN M F(localhost, localport, remotehost, remoteport)M是一个计时器每隔4微秒加1。 F是一个Hash算法根据源IP、目的IP、源端口、目的端口生成一个随机数值。要保证hash算法不能被外部轻易推算得出。序列号回绕因为ISN是随机的所以序列号容易就会超过2^31-1. 而tcp对于丢包和乱序等问题的判断都是依赖于序列号大小比较的。此时就出现了所谓的tcp序列号回绕sequence wraparound问题。怎么解决/** The next routines deal with comparing 32 bit unsigned ints * and worry about wraparound (automatic with unsigned arithmetic).*/static inline int before(__u32 seq1, __u32 seq2){return (__s32)(seq1-seq2) 0;}#define after(seq2, seq1) before(seq1, seq2)/** The next routines deal with comparing 32 bit unsigned ints * and worry about wraparound (automatic with unsigned arithmetic).*/static inline int before(__u32 seq1, __u32 seq2){return (__s32)(seq1-seq2) 0;}#define after(seq2, seq1) before(seq1, seq2)上述代码是内核中的解决回绕问题代码。s32是有符号整型的意思而u32则是无符号整型。序列号发生回绕后序列号变小相减之后把结果变成有符号数了因此结果成了负数。假设seq1255 seq21发生了回绕。
seq1 1111 1111 seq2 0000 0001
我们希望比较结果是seq1 - seq21111 1111
-0000 0001
-----------1111 1110由于我们将结果转化成了有符号数由于最高位是1因此结果是一个负数负数的绝对值为0000 0001 1 0000 0010 2因此seq1 - seq2 0假设seq1255 seq21发生了回绕。
seq1 1111 1111 seq2 0000 0001
我们希望比较结果是seq1 - seq21111 1111
-0000 0001
-----------1111 1110由于我们将结果转化成了有符号数由于最高位是1因此结果是一个负数负数的绝对值为0000 0001 1 0000 0010 2因此seq1 - seq2 0syn flood攻击最基本的DoS攻击就是利用合理的服务请求来占用过多的服务资源从而使合法用户无法得到服务的响应。syn flood属于Dos攻击的一种。如果恶意的向某个服务器端口发送大量的SYN包则可以使服务器打开大量的半开连接分配TCBTransmission Control Block, 从而消耗大量的服务器资源同时也使得正常的连接请求无法被相应。当开放了一个TCP端口后该端口就处于Listening状态不停地监视发到该端口的Syn报文一 旦接收到Client发来的Syn报文就需要为该请求分配一个TCB通常一个TCB至少需要280个字节在某些操作系统中TCB甚至需要1300个字节并返回一个SYN ACK命令立即转为SYN-RECEIVED即半开连接状态。系统会为此耗尽资源。常见的防攻击方法有无效连接的监视释放监视系统的半开连接和不活动连接当达到一定阈值时拆除这些连接从而释放系统资源。这种方法对于所有的连接一视同仁而且由于SYN Flood造成的半开连接数量很大正常连接请求也被淹没在其中被这种方式误释放掉因此这种方法属于入门级的SYN Flood方法。延缓TCB分配方法消耗服务器资源主要是因为当SYN数据报文一到达系统立即分配TCB从而占用了资源。而SYN Flood由于很难建立起正常连接因此当正常连接建立起来后再分配TCB则可以有效地减轻服务器资源的消耗。常见的方法是使用Syn Cache和Syn Cookie技术。Syn Cache技术系统在收到一个SYN报文时在一个专用HASH表中保存这种半连接信息直到收到正确的回应ACK报文再分配TCB。这个开销远小于TCB的开销。当然还需要保存序列号。Syn Cookie技术Syn Cookie技术则完全不使用任何存储资源这种方法比较巧妙它使用一种特殊的算法生成Sequence Number这种算法考虑到了对方的IP、端口、己方IP、端口的固定信息以及对方无法知道而己方比较固定的一些信息如MSS(Maximum Segment Size最大报文段大小指的是TCP报文的最大数据报长度其中不包括TCP首部长度。)、时间等在收到对方 的ACK报文后重新计算一遍看其是否与对方回应报文中的Sequence Number-1相同从而决定是否分配TCB资源。使用SYN Proxy防火墙一种方式是防止墙dqywb连接的有效性后防火墙才会向内部服务器发起SYN请求。防火墙代服务器发出的SYN ACK包使用的序列号为c, 而真正的服务器回应的序列号为c, 这样在每个数据报文经过防火墙的时候进行序列号的修改。另一种方式是防火墙确定了连接的安全后会发出一个safe reset命令client会进行重新连接这时出现的syn报文会直接放行。这样不需要修改序列号了。但是client需要发起两次握手过程因此建立连接的时间将会延长。连接队列在外部请求到达时被服务程序最终感知到前连接可能处于SYN_RCVD状态或是ESTABLISHED状态但还未被应用程序接受。对应地服务器端也会维护两种队列处于SYN_RCVD状态的半连接队列而处于ESTABLISHED状态但仍未被应用程序accept的为全连接队列。如果这两个队列满了之后就会出现各种丢包的情形。查看是否有连接溢出
netstat -s | grep LISTEN查看是否有连接溢出
netstat -s | grep LISTEN半连接队列满了在三次握手协议中服务器维护一个半连接队列该队列为每个客户端的SYN包开设一个条目(服务端在接收到SYN包的时候就已经创建了request_sock结构存储在半连接队列中)该条目表明服务器已收到SYN包并向客户发出确认正在等待客户的确认包。这些条目所标识的连接在服务器处于Syn_RECV状态当服务器收到客户的确认包时删除该条目服务器进入ESTABLISHED状态。目前Linux下默认会进行5次重发SYN-ACK包重试的间隔时间从1s开始下次的重试间隔时间是前一次的双倍5次的重试时间间隔为1s, 2s, 4s, 8s, 16s, 总共31s, 称为指数退避第5次发出后还要等32s才知道第5次也超时了所以总共需要 1s 2s 4s 8s 16s 32s 63s, TCP才会把断开这个连接。由于SYN超时需要63秒那么就给攻击者一个攻击服务器的机会攻击者在短时间内发送大量的SYN包给Server(俗称SYN flood攻击)用于耗尽Server的SYN队列。对于应对SYN 过多的问题linux提供了几个TCP参数tcp_syncookies、tcp_synack_retries、tcp_max_syn_backlog、tcp_abort_on_overflow 来调整应对。全连接队列满当第三次握手时当server接收到ACK包之后会进入一个新的叫 accept 的队列。当accept队列满了之后即使client继续向server发送ACK的包也会不被响应此时ListenOverflows1同时server通过tcp_abort_on_overflow来决定如何返回0表示直接丢弃该ACK1表示发送RST通知client相应的client则会分别返回read timeout 或者 connection reset by peer。另外tcp_abort_on_overflow是0的话server过一段时间再次发送synack给client也就是重新走握手的第二步如果client超时等待比较短就很容易异常了。而客户端收到多个 SYN ACK 包则会认为之前的 ACK 丢包了。于是促使客户端再次发送 ACK 在 accept队列有空闲的时候最终完成连接。若 accept队列始终满员则最终客户端收到 RST 包此时服务端发送synack的次数超出了tcp_synack_retries。服务端仅仅只是创建一个定时器以固定间隔重传syn和ack到服务端命令netstat -s命令[rootserver ~]# netstat -s | egrep listen|LISTEN 上面看到的 667399 times 表示全连接队列溢出的次数隔几秒钟执行下如果这个数字一直在增加的话肯定全连接队列偶尔满了。[rootserver ~]# netstat -s | grep TCPBacklogDrop 查看 Accept queue 是否有溢出ss命令[rootserver ~]# ss -lnt如果State是listen状态Send-Q 表示第三列的listen端口上的全连接队列最大为50第一列Recv-Q为全连接队列当前使用了多少。 非 LISTEN 状态中 Recv-Q 表示 receive queue 中的 bytes 数量Send-Q 表示 send queue 中的 bytes 数值。小结当外部连接请求到来时TCP模块会首先查看max_syn_backlog如果处于SYN_RCVD状态的连接数目超过这一阈值进入的连接会被拒绝。根据tcp_abort_on_overflow字段来决定是直接丢弃还是直接reset.从服务端来说三次握手中第一步server接受到client的syn后把相关信息放到半连接队列中同时回复synack给client. 第三步当收到客户端的ack, 将连接加入到全连接队列。一般全连接队列比较小会先满此时半连接队列还没满。如果这时收到syn报文则会进入半连接队列没有问题。但是如果收到了三次握手中的第3步(ACK)则会根据tcp_abort_on_overflow字段来决定是直接丢弃还是直接reset.此时客户端发送了ACK, 那么客户端认为三次握手完成它认为服务端已经准备好了接收数据的准备。但此时服务端可能因为全连接队列满了而无法将连接放入会重新发送第2步的synack, 如果这时有数据到来服务器TCP模块会将数据存入队列中。一段时间后client端没收到回复超时连接异常client会主动关闭连接。“三次握手四次挥手”redis实例分析我在dev机器上部署redis服务端口号为6379,通过tcpdump工具获取数据包使用如下命令tcpdump -w /tmp/a.cap port 6379 -s0
-w把数据写入文件-s0设置每个数据包的大小默认为68字节如果用-S 0则会抓到完整数据包tcpdump -w /tmp/a.cap port 6379 -s0
-w把数据写入文件-s0设置每个数据包的大小默认为68字节如果用-S 0则会抓到完整数据包在dev2机器上用redis-cli访问dev:6379, 发送一个ping, 得到回复pong停止抓包用tcpdump读取捕获到的数据包tcpdump -r /tmp/a.cap -n -nn -A -x| vim -
-x 以16进制形式展示便于后面分析tcpdump -r /tmp/a.cap -n -nn -A -x| vim -
-x 以16进制形式展示便于后面分析共收到了7个包。抓到的是IP数据包IP数据包分为IP头部和IP数据部分IP数据部分是TCP头部加TCP数据部分。IP的数据格式为它由固定长度20B可变长度构成。0x0000: 4500 003c 08cf 4000 3606 14a5 0ab3 b561 0x0010: 0a60 5cd4 989e 18eb f65a ebff 0000 0000 0x0020: a002 7210 872f 0000 0204 05b4 0402 080a 0x0030: b062 e330 0000 0000 0103 03070x0000: 4500 003c 08cf 4000 3606 14a5 0ab3 b561 0x0010: 0a60 5cd4 989e 18eb f65a ebff 0000 0000 0x0020: a002 7210 872f 0000 0204 05b4 0402 080a 0x0030: b062 e330 0000 0000 0103 0307对着IP头部格式来拆解数据包的具体含义。剩余的数据部分即为TCP协议相关的。TCP也是20B固定长度可变长度部分。可变长度部分协议如下这样第一个包分析完了。dev2向dev发送SYN请求。也就是三次握手中的第一次了。 SYN seq(c)4133153791第二个包dev响应连接ack4133153792. 表明dev下次准备接收这个序号的包用于tcp字节注的顺序控制。dev也就是server端的初始序号为seq4264776963, syn1. SYN ackseq(c)1 seq(s)4264776963第三个包client包确认这里使用了相对值应答。seq4133153792, 等于第二个包的ack. ack4264776964. ackseq(s)1, seqseq(c)1 至此三次握手完成。接下来就是发送ping和pong的数据了。接着第四个包。0x0000: 4500 0042 08d1 4000 3606 149d 0ab3 b561 0x0010: 0a60 5cd4 989e 18eb f65a ec00 fe33 5504 0x0020: 8018 00e5 4b5f 0000 0101 080a b062 ecac 0x0030: bab2 6fe6 2a31 0d0a 2434 0d0a 7069 6e67 0x0040: 0d0a0x0000: 4500 0042 08d1 4000 3606 149d 0ab3 b561 0x0010: 0a60 5cd4 989e 18eb f65a ec00 fe33 5504 0x0020: 8018 00e5 4b5f 0000 0101 080a b062 ecac 0x0030: bab2 6fe6 2a31 0d0a 2434 0d0a 7069 6e67 0x0040: 0d0atcp首部长度为32B, 可选长度为12B. IP报文的总长度为66B, 首部长度为20B, 因此TCP数据部分长度为14B. seq0xf65a ec004133153792 ACK, PSH. 数据部分为2a31 0d0a 2434 0d0a 7069 6e67 0d0a0x2a31 - *1
0x0d0a - \r\n
0x2434 - $4
0x0d0a - \r\n
0x7069 0x6e67 - ping
0x0d0a - \r\n0x2a31 - *1
0x0d0a - \r\n
0x2434 - $4
0x0d0a - \r\n
0x7069 0x6e67 - ping
0x0d0a - \r\ndev2向dev发送了ping数据第四个包完毕。第五个包dev2向dev发送ack响应。 序列号为0xfe33 55044264776964, ack确认号为0xf65a ec0e4133153806(413315379214).第六个包dev向dev2响应pong消息。序列号fe33 5504确认号f65a ec0e, TCP头部可选长度为12B, IP数据报总长度为59B, 首部长度为20B, 因此TCP数据长度为7B. 数据部分2b50 4f4e 470d 0a, 翻译过来就是PONG\r\n.至此Redis客户端和Server端的三次握手过程分析完毕。总结“三次握手四次挥手”看似简单但是深究进去还是可以延伸出很多知识点的。比如半连接队列、全连接队列等等。以前关于TCP建立连接、关闭连接的过程很容易就会忘记可能是因为只是死记硬背了几个过程没有深入研究背后的原理。所以“三次握手四次挥手”你真的懂了吗参考资料【redis】https://segmentfault.com/a/1190000015044878【tcp option】https://blog.csdn.net/wdscq1234/article/details/52423272【滑动窗口】https://www.zhihu.com/question/32255109【全连接队列】http://jm.taobao.org/2017/05/25/525-1/【client fooling】 https://github.com/torvalds/linux/commit/5ea8ea2cb7f1d0db15762c9b0bb9e7330425a071【backlog RECV_Q】http://blog.51cto.com/59090939/1947443【定时器】https://www.cnblogs.com/menghuanbiao/p/5212131.html【队列图示】https://www.itcodemonkey.com/article/5834.html【tcp flood攻击】https://www.cnblogs.com/hubavyn/p/4477883.html【MSS MTU】https://blog.csdn.net/LoseInVain/article/details/53694265注本文首发于博客园个人博客https://www.cnblogs.com/qcrao-2018/p/10182185.html本文为原创文章转载注明出处欢迎长按以下图片关注公众号CoderPark 第一时间阅读后续精彩文章。觉得好的话请猛击文章右下角「好看」感谢支持。一线互联网工程师带你“精进”职业技能赶快来吧!本文作者饶全成中科院计算所硕士滴滴出行后端研发工程师。点击「阅读原文」给本文作者饶全成点赞