77![ ] ( https://img-blog.csdnimg.cn/ea1c6e0165f04232ab02046132e63d0f.jpg )
88
99
10- 大概意思是,一个已经建立的 TCP 连接,客户端中途宕机了,而服务端此时也没有数据要发送,一直处于 establish 状态,客户端恢复后,向服务端建立连接,此时服务端会怎么处理?
10+ 大概意思是,一个已经建立的 TCP 连接,客户端中途宕机了,而服务端此时也没有数据要发送,一直处于 Established 状态,客户端恢复后,向服务端建立连接,此时服务端会怎么处理?
1111
1212看过我的图解网络的读者都知道,TCP 连接是由「四元组」唯一确认的。
1313
3838
3939![ ] ( https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/est_syn.png )
4040
41+ ** 处于 Established 状态的服务端,如果收到了客户端的 SYN 报文(注意此时的 SYN 报文其实是乱序的,因为 SYN 报文的初始化序列号其实是一个随机数),会回复一个携带了正确序列号和确认号的 ACK 报文,这个 ACK 被称之为 Challenge ACK。**
4142
42- ** 处于 Established 状态的服务端如果收到了客户端的 SYN 报文(注意此时的 SYN 报文其实是乱序的,因为 SYN 报文的初始化序列号其实是一个随机数),会回复一个携带了正确序列号和确认号的 ACK 报文,这个 ACK 被称之为 Challenge ACK。**
43-
44- ** 接着,客户端收到这个 Challenge ACK,发现序列号并不是自己期望收到的,于是就会回 RST 报文,服务端收到后,就会释放掉该连接。**
43+ ** 接着,客户端收到这个 Challenge ACK,发现确认号(ack num)并不是自己期望收到的,于是就会回 RST 报文,服务端收到后,就会释放掉该连接。**
4544
4645## RFC 文档解释
4746
@@ -52,23 +51,24 @@ RFC 793 文档里的第 34 页里,有说到这个例子。
5251原文的解释我也贴出来给大家看看。
5352
5453- When the SYN arrives at line 3, TCP B, being in a synchronized state,
55- and the incoming segment outside the window, responds with an acknowledgment indicating what sequence it next expects to hear (ACK
54+ and the incoming segment outside the window, responds with an
55+ acknowledgment indicating what sequence it next expects to hear (ACK
5656100).
5757- TCP A sees that this segment does not acknowledge anything it
5858sent and, being unsynchronized, sends a reset (RST) because it has
5959detected a half-open connection.
6060- TCP B aborts at line 5.
61- - TCP A willcontinue to try to establish the connection;
61+ - TCP A willcontinue to try to Established the connection;
6262
6363我就不瞎翻译了,意思和我在前面用中文说的解释差不多。
6464
6565## 源码分析
66- 处于 establish 状态的服务端如果收到了客户端的 SYN 报文时,内核会调用这些函数:
66+ 处于 Established 状态的服务端如果收到了客户端的 SYN 报文时,内核会调用这些函数:
6767
6868``` csharp
6969tcp_v4_rcv
7070 -> tcp_v4_do_rcv
71- -> tcp_rcv_established
71+ -> tcp_rcv_Establisheded
7272 -> tcp_validate_incoming
7373 -> tcp_send_ack
7474```
@@ -78,7 +78,7 @@ tcp_v4_rcv
7878
7979![ ] ( https://img-blog.csdnimg.cn/780bc02c8fa940c0a320a5916b216c21.png )
8080
81- 从上面的代码实现可以看到,处于 establish 状态的服务端,在收到报文后,首先会判断序列号是否在窗口内,如果不在,则看看 RST 标记有没有被设置,如果有就会丢掉。然后如果没有 RST 标志,就会判断是否有 SYN 标记,如果有 SYN 标记就会跳转到 syn_challenge 标签,然后执行 tcp_send_challenge_ack 函数。
81+ 从上面的代码实现可以看到,处于 Established 状态的服务端,在收到报文后,首先会判断序列号是否在窗口内,如果不在,则看看 RST 标记有没有被设置,如果有就会丢掉。然后如果没有 RST 标志,就会判断是否有 SYN 标记,如果有 SYN 标记就会跳转到 syn_challenge 标签,然后执行 tcp_send_challenge_ack 函数。
8282
8383tcp_send_challenge_ack 函数里就会调用 tcp_send_ack 函数来回复一个携带了正确序列号和确认号的 ACK 报文。
8484
@@ -98,27 +98,33 @@ tcp_send_challenge_ack 函数里就会调用 tcp_send_ack 函数来回复一个
9898
9999这个思路很好,但是不要忘了还有个序列号的问题,你伪造的 RST 报文的序列号一定能被对方接受吗?
100100
101- 如果 RST 报文的序列号不能落在对方的滑动窗口内,这个 RST 报文会被对方丢弃的,就达不到关闭的连接的效果。
101+ 如果 RST 报文的序列号不是对方期望收到的序列号,这个 RST 报文会被对方丢弃的,就达不到关闭的连接的效果。
102+
103+ 举个例子,下面这个场景,客户端发送了一个长度为 100 的 TCP 数据报文,服务端收到后响应了 ACK 报文,表示收到了这个 TCP 数据报文。** 服务端响应的这个 ACK 报文中的确认号(ack = x + 100)就是表明服务端下一次期望收到的序列号是 x + 100** 。
104+
105+ ![ ] ( https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/rst合法.png )
102106
103- 所以,** 要伪造一个能关闭 TCP 连接的 RST 报文,必须同时满足「四元组相同」和「序列号正好落在对方的滑动窗口内 」这两个条件。**
107+ 所以,** 要伪造一个能关闭 TCP 连接的 RST 报文,必须同时满足「四元组相同」和「序列号是对方期望的 」这两个条件。**
104108
105- 直接伪造符合预期的序列号是比较困难,因为如果一个正在传输数据的 TCP 连接,滑动窗口时刻都在变化,因此很难刚好伪造一个刚好落在对方滑动窗口内的序列号的 RST 报文。
109+ 直接伪造符合预期的序列号是比较困难,因为如果一个正在传输数据的 TCP 连接,序列号都是时刻都在变化,因此很难刚好伪造一个正确序列号的 RST 报文。
110+
111+ ### killcx 的工具
106112
107113办法还是有的,** 我们可以伪造一个四元组相同的 SYN 报文,来拿到“合法”的序列号!**
108114
109- 正如我们最开始学到的,如果处于 establish 状态的服务端,收到四元组相同的 SYN 报文后,** 会回复一个 Challenge ACK,这个 ACK 报文里的「确认号」,正好是服务端下一次想要接收的序列号,说白了,就是可以通过这一步拿到服务端下一次预期接收的序列号。**
115+ 正如我们最开始学到的,如果处于 Established 状态的服务端,收到四元组相同的 SYN 报文后,** 会回复一个 Challenge ACK,这个 ACK 报文里的「确认号」,正好是服务端下一次想要接收的序列号,说白了,就是可以通过这一步拿到服务端下一次预期接收的序列号。**
110116
111117** 然后用这个确认号作为 RST 报文的序列号,发送给服务端,此时服务端会认为这个 RST 报文里的序列号是合法的,于是就会释放连接!**
112118
113119在 Linux 上有个叫 killcx 的工具,就是基于上面这样的方式实现的,它会主动发送 SYN 包获取 SEQ/ACK 号,然后利用 SEQ/ACK 号伪造两个 RST 报文分别发给客户端和服务端,这样双方的 TCP 连接都会被释放,这种方式活跃和非活跃的 TCP 连接都可以杀掉。
114120
115121
116- 使用方式也很简单, 只需指明客户端的 IP 和端口号。
122+ killcx 的工具使用方式也很简单,如果在服务端执行 killcx 工具, 只需指明客户端的 IP 和端口号,如果在客户端执行 killcx 工具,则就指明服务端的 IP 和端口号。
117123
118124``` csharp
119125./ killcx < IP 地址> : < 端口号>
120126```
121- killcx 工具的工作原理,如下图。
127+ killcx 工具的工作原理,如下图,下图是在客户端执行 killcx 工具 。
122128
123129![ ] ( https://img-blog.csdnimg.cn/95592346a9a747819cd27741a660213c.png )
124130
@@ -136,6 +142,90 @@ killcx 工具的工作原理,如下图。
136142
137143怎么样,很巧妙吧!
138144
145+ ### tcpkill 的工具
146+
147+ 除了 killcx 工具能关闭 TCP 连接,还有 tcpkill 工具也可以做到。
148+
149+ 这两个工具都是通过伪造 RST 报文来关闭指定的 TCP 连接,但是它们拿到正确的序列号的实现方式是不同的。
150+
151+ - tcpkill 工具是在双方进行 TCP 通信时,拿到对方下一次期望收到的序列号,然后将序列号填充到伪造的 RST 报文,并将其发送给对方,达到关闭 TCP 连接的效果。
152+ - killcx 工具是主动发送一个 SYN 报文,对方收到后会回复一个携带了正确序列号和确认号的 ACK 报文,这个 ACK 被称之为 Challenge ACK,这时就可以拿到对方下一次期望收到的序列号,然后将序列号填充到伪造的 RST 报文,并将其发送给对方,达到关闭 TCP 连接的效果。
153+
154+ 可以看到, 这两个工具在获取对方下一次期望收到的序列号的方式是不同的。
155+
156+ tcpkill 工具属于被动获取,就是在双方进行 TCP 通信的时候,才能获取到正确的序列号,很显然** 这种方式无法关闭非活跃的 TCP 连接** ,只能用于关闭活跃的 TCP 连接。因为如果这条 TCP 连接一直没有任何数据传输,则就永远获取不到正确的序列号。
157+
158+ killcx 工具则是属于主动获取,它是主动发送一个 SYN 报文,通过对方回复的 Challenge ACK 来获取正确的序列号,所以这种方式** 无论 TCP 连接是否活跃,都可以关闭** 。
159+
160+ 接下来,我就用这 tcpkill 工具来做个实验。
161+
162+ 在这里, 我用 nc 工具来模拟一个 TCP 服务端,监听 8888 端口。
163+
164+ ![ ] ( https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill1.png )
165+
166+ 接着,在客户端机子上,用 nc 工具模拟一个 TCP 客户端,连接我们刚才启动的服务端,并且指定了客户端的端口为 11111。
167+
168+ ![ ] ( https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill2.png )
169+
170+ 这时候, 服务端就可以看到这条 TCP 连接了。
171+
172+ ![ 图片] ( https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill3.png )
173+
174+ 注意,我这台服务端的公网 IP 地址是 121.43.173.240,私网 IP 地址是 172.19.11.21,在服务端通过 netstat 命令查看 TCP 连接的时候,则会将服务端的地址显示成私网 IP 地址 。至此,我们前期工作就做好了。
175+
176+ 接下来,我们在服务端执行 tcpkill 工具,来关闭这条 TCP 连接,看看会发生什么?
177+
178+ 在这里,我指定了要关闭的客户端 IP 为 114.132.166.90 和端口为 11111 的 TCP 连接。
179+
180+ ![ 图片] ( https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill4.png )
181+
182+ 可以看到,tcpkill 工具阻塞中,没有任何输出,而且此时的 TCP 连接还是存在的,并没有被干掉。
183+
184+ ![ 图片] ( https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill5.png )
185+
186+ 为什么 TCP 连接没用被干掉?
187+
188+ 因为在执行 tcpkill 工具后,这条 TCP 连接并没有传输任何数据,而 tcpkill 工具是需要拦截双方的 TCP 通信,才能获取到正确的序列号,从而才能伪装出正确的序列号的 RST 报文。
189+
190+ 所以,从这里也说明了,** tcpkill 工具不适合关闭非活跃的 TCP 连接** 。
191+
192+ 接下来,我们尝试在客户端发送一个数据。
193+
194+ ![ 图片] ( https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill8.png )
195+
196+ 可以看到,在发送了「hi」数据后,客户端就断开了,并且错误提示连接被对方关闭了。
197+
198+ 此时,服务端已经查看不到刚才那条 TCP 连接了。
199+
200+ ![ 图片] ( https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill7.png )
201+
202+ 然后,我们在服务端看看 tcpkill 工具输出的信息。
203+
204+ ![ 图片] ( https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill8.png )
205+
206+ 可以看到, ** tcpkill 工具给服务端和客户端都发送了伪造的 RST 报文,从而达到关闭一条 TCP 连接的效果** 。
207+
208+ 到这里我们知道了, 运行 tcpkill 工具后,只有目标连接有新 TCP 包发送/接收的时候,才能关闭一条 TCP 连接。因此,** tcpkill 只适合关闭活跃的 TCP 连接,不适合用来关闭非活跃的 TCP 连接** 。
209+
210+ 上面的实验过程,我也抓了数据包,流程如下:
211+
212+ ![ 图片] ( https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/网络/tcpkill/tcpkill9.png )
213+
214+ 最后一个 RST 报文就是 tcpkill 工具伪造的 RST 报文。
215+
216+ ## 总结
217+
218+ 要伪造一个能关闭 TCP 连接的 RST 报文,必须同时满足「四元组相同」和「序列号是对方期望的」这两个条件。
219+
220+ 今天给大家介绍了两种关闭 TCP 连接的工具:tcpkill 和 killcx 工具。
221+
222+ 这两种工具都是通过伪造 RST 报文来关闭 TCP 连接的,但是它们获取「对方下一次期望收到的序列号的方式是不同的,也正因此,造就了这两个工具的应用场景有区别。
223+
224+ - tcpkill 工具只能用来关闭活跃的 TCP 连接,无法关闭非活跃的 TCP 连接,因为 tcpkill 工具是等双方进行 TCP 通信后,才去获取正确的序列号,如果这条 TCP 连接一直没有任何数据传输,则就永远获取不到正确的序列号。
225+ - killcx 工具可以用来关闭活跃和非活跃的 TCP 连接,因为 killcx 工具是主动发送 SYN 报文,这时对方就会回复 Challenge ACK ,然后 killcx 工具就能从这个 ACK 获取到正确的序列号。
226+
227+ 完!
228+
139229---
140230
141231最新的图解文章都在公众号首发,别忘记关注哦!!如果你想加入百人技术交流群,扫码下方二维码回复「加群」。
0 commit comments