7

一说起TCP, 就是什么三次握手, 四次挥手. 而这次想讨论的是:

在不重启各自socket程序情况下, 将ESTABLED链接断开 ???

情景模拟

简单点, 在同一个机器 通过 nc 来实现 serverclient

# Server
nc -l -p 5555
# Client
nc localhost 5555 -p 6666

上面的意思就是, server端在5555端口监听, 而client 通过 6666 端口去连接

为了更加清晰的看到流量, 咱们通过 tcpdump 来观察:

tcpdump -i lo  -xnn -S  # 因为是本机, 所以lo才能捕获
08:32:01.063394 IP 127.0.0.1.6666 > 127.0.0.1.5555: Flags [S], seq 1812097880, win 43690, options [mss 65495,sackOK,TS val 2762998 ecr 2761788,nop,wscale 7], length 0
    0x0000:  0000 0000 0000 0000 0000 0000 0800 4500
    0x0010:  003c 43ae 4000 4006 f90b 7f00 0001 7f00
    0x0020:  0001 1a0a 15b3 6c02 6b58 0000 0000 a002
    0x0030:  aaaa fe30 0000 0204 ffd7 0402 080a 002a
    0x0040:  28f6 002a 243c 0103 0307
08:32:01.063416 IP 127.0.0.1.5555 > 127.0.0.1.6666: Flags [S.], seq 1320008227, ack 1812097881, win 43690, options [mss 65495,sackOK,TS val 2762998 ecr 2762998,nop,wscale 7], length 0
    0x0000:  0000 0000 0000 0000 0000 0000 0800 4500
    0x0010:  003c 0000 4000 4006 3cba 7f00 0001 7f00
    0x0020:  0001 15b3 1a0a 4ead ba23 6c02 6b59 a012
    0x0030:  aaaa fe30 0000 0204 ffd7 0402 080a 002a
    0x0040:  28f6 002a 28f6 0103 0307
08:32:01.063431 IP 127.0.0.1.6666 > 127.0.0.1.5555: Flags [.], ack 1320008228, win 342, options [nop,nop,TS val 2762998 ecr 2762998], length 0
    0x0000:  0000 0000 0000 0000 0000 0000 0800 4500
    0x0010:  0034 43af 4000 4006 f912 7f00 0001 7f00
    0x0020:  0001 1a0a 15b3 6c02 6b59 4ead ba24 8010
    0x0030:  0156 fe28 0000 0101 080a 002a 28f6 002a
    0x0040:  28f6

ss的结果也证明了链接已经建立了:

[root@5464f8622628 /]# ss -ant
State      Recv-Q Send-Q Local Address:Port               Peer Address:Port
ESTAB      0      0      172.17.0.3:6666               172.17.0.3:5555
ESTAB      0      0      172.17.0.3:5555               172.17.0.3:6666

链接建立之后, 就能互相通信了
请在这里输入图片描述

那么如何断开这个链接呢?

错误姿势

现在来试下传统方法, 一般我们会上iptables:

[root@6913388a8a1e /]# iptables -A INPUT -p tcp --dport 5555 -j DROP
[root@6913388a8a1e /]# iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
DROP       tcp  --  anywhere             anywhere             tcp dpt:personal-agent

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

上面的规则, 意思就是将 目的端口为 5555 的请求丢弃了, 所以我们必须从客户端发信息, 因为客户端才能发到5555端口:
请在这里输入图片描述

这里已经看到, 信息已经发不出去了, 而通过tcpdump能看到一堆从client发送的报文:

08:43:44.459584 IP 127.0.0.1.6666 > 127.0.0.1.5555: Flags [P.], seq 327893533:327893535, ack 3568222208, win 342, options [nop,nop,TS val 2833338 ecr 2832362], length 2
    0x0000:  0000 0000 0000 0000 0000 0000 0800 4500
    0x0010:  0036 75f8 4000 4006 c6c7 7f00 0001 7f00
    0x0020:  0001 1a0a 15b3 138b 421d d4ae c000 8018
    0x0030:  0156 fe2a 0000 0101 080a 002b 3bba 002b
    0x0040:  37ea 330a
08:43:44.670096 IP 127.0.0.1.6666 > 127.0.0.1.5555: Flags [P.], seq 327893533:327893535, ack 3568222208, win 342, options [nop,nop,TS val 2833359 ecr 2832362], length 2
    0x0000:  0000 0000 0000 0000 0000 0000 0800 4500
    0x0010:  0036 75f9 4000 4006 c6c6 7f00 0001 7f00
    0x0020:  0001 1a0a 15b3 138b 421d d4ae c000 8018
    0x0030:  0156 fe2a 0000 0101 080a 002b 3bcf 002b
    0x0040:  37ea 330a
08:43:44.881782 IP 127.0.0.1.6666 > 127.0.0.1.5555: Flags [P.], seq 327893533:327893535, ack 3568222208, win 342, options [nop,nop,TS val 2833380 ecr 2832362], length 2
    0x0000:  0000 0000 0000 0000 0000 0000 0800 4500
    0x0010:  0036 75fa 4000 4006 c6c5 7f00 0001 7f00
    0x0020:  0001 1a0a 15b3 138b 421d d4ae c000 8018
    0x0030:  0156 fe2a 0000 0101 080a 002b 3be4 002b
    0x0040:  37ea 330a
.... (剩下还有大概 8 条左右)

tcpdump的输出告诉我们client真的已经在努力了, 但是server却不响应, 这真不怪server绝情, 而是它真的没有收到! 都被那可恶的iptables丢掉了.!

client会因为server不搭理而情绪低落放弃它们的连接么?

[root@6913388a8a1e /]# ss -ant
State       Recv-Q Send-Q  Local Address:Port                 Peer Address:Port
ESTAB       0      2           127.0.0.1:6666                    127.0.0.1:5555
ESTAB       0      0           127.0.0.1:5555                    127.0.0.1:6666

很明显, 它们之间是真爱, 尽管server不搭理, client也不会轻易放弃.

而且很有意思的是, tcpdump还在持续的输出:

.....(省略贼多的信息)
08:53:28.844326 IP 127.0.0.1.6666 > 127.0.0.1.5555: Flags [P.], seq 327893533:327893535, ack 3568222208, win 342, options [nop,nop,TS val 2891776 ecr 2832362], length 2
    0x0000:  0000 0000 0000 0000 0000 0000 0800 4500
    0x0010:  0036 7606 4000 4006 c6b9 7f00 0001 7f00
    0x0020:  0001 1a0a 15b3 138b 421d d4ae c000 8018
    0x0030:  0156 fe2a 0000 0101 080a 002c 2000 002b
    0x0040:  37ea 330a
08:55:31.721921 IP 127.0.0.1.6666 > 127.0.0.1.5555: Flags [P.], seq 327893533:327893535, ack 3568222208, win 342, options [nop,nop,TS val 2904064 ecr 2832362], length 2
    0x0000:  0000 0000 0000 0000 0000 0000 0800 4500
    0x0010:  0036 7607 4000 4006 c6b8 7f00 0001 7f00
    0x0020:  0001 1a0a 15b3 138b 421d d4ae c000 8018
    0x0030:  0156 fe2a 0000 0101 080a 002c 5000 002b
    0x0040:  37ea 330a

比较细心的同学, 可能就会发现, 它们通信的时间, 在不断的增加, 从一开始几毫秒, 到现在的2分钟, 这是由TCP协议中的RTTRTO所决定的.

RTT (round trip time)
在开启了TCP时间戳后,A记录下时间t1把包发给B,B收到包后记录下时间t2把包回给A ,这个过程里t2-t1就是RTT

RTO(Retransmission TimeOut)即重传超时时间

所以为了节省性能, client重试的时间, 会随着这套算法, 不断的增加~ 但是他们的链接, 已经会长存...至于长存多久, 这个真的取决很多因素, 例如keepalived保活机制等等, 在这里, nc大概13分钟就看不下去了...

那么假设, 还没到那么长时间 而且 iptables良心发现了, 放弃了阻扰, 它们又会怎样呢?

[root@6913388a8a1e /]# iptables -F
[root@6913388a8a1e /]# iptables -nL
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

在下次RTO结束时, server就能接收到相应的信息了, 从此clientServer又能愉快的玩耍了

花了很大的篇幅来证明clientserver的真爱, 事实证明它们的专一值得学习!

但是很多时候, 如果clientserver冷战, 谁也不理谁, 这就让我们很蛋疼了, 因为如果这样不必要的链接, 长时间保存, 会大量的占用资源, 很快就会出现资源瓶颈, 所以我们一定要扼杀掉这种行为!

正确姿势

首先, 我们得明白的是, 一般的重启程序, 重启机器, 实际上是发送了 fin标识去对端来触发四次挥手发生, 所以对待孽缘, 还是得遵循规律, 从内部攻破..

方法一

在刚才的实验中, iptabls无法阻扰, 但仅仅是因为姿势不对而已, 换个姿势分分钟一刀两段:

[root@6913388a8a1e /]# iptables -A INPUT -p tcp --dport 5555 -j REJECT --reject-with tcp-reset
[root@6913388a8a1e /]# iptables -nL
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
REJECT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:5555 reject-with tcp-reset

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

加了这个, client一发消息就不再是苦苦等待了, 直接就被iptables打耳刮子了

[root@6913388a8a1e /]# nc localhost 5555 -p 6666
p
Ncat: Connection reset by peer.

ss的结果是:

[root@6913388a8a1e /]# ss -ant
State       Recv-Q Send-Q  Local Address:Port                 Peer Address:Port
ESTAB       0      0           127.0.0.1:5555                    127.0.0.1:6666

tcpdump更是见证了这电光火石的瞬间, 第二个报文的R, 就是 reset的 flags, 这样会client那边的链接直接重置断开.

09:59:55.472340 IP 127.0.0.1.6666 > 127.0.0.1.5555: Flags [P.], seq 3009865367:3009865369, ack 1955226254, win 342, options [nop,nop,TS val 3290439 ecr 3289331], length 2
    0x0000:  0000 0000 0000 0000 0000 0000 0800 4500
    0x0010:  0036 d667 4000 4006 6658 7f00 0001 7f00
    0x0020:  0001 1a0a 15b3 b366 e697 748a 628e 8018
    0x0030:  0156 fe2a 0000 0101 080a 0032 3547 0032
    0x0040:  30f3 700a
09:59:55.472362 IP 127.0.0.1.5555 > 127.0.0.1.6666: Flags [R], seq 1955226254, win 0, length 0
    0x0000:  0000 0000 0000 0000 0000 0000 0800 4500
    0x0010:  0028 0000 4000 4006 3cce 7f00 0001 7f00
    0x0020:  0001 15b3 1a0a 748a 628e 0000 0000 5004
    0x0030:  0000 fe1c 0000

但是 TCP的全双工的呀, 刚才断的只是, clientserver的, 还有serverclient的,
按照刚才的方法, 反过来搞一发!

PS: 要注意先把刚才的 INPUT 的删掉, 要不然reset就会被自己禁掉, 无法发送给 5555了..

root@6913388a8a1e /]# iptables -F
[root@6913388a8a1e /]# iptables -A OUTPUT -p tcp --dport 6666 -j REJECT --reject-with tcp-reset
[root@6913388a8a1e /]# iptables -nL
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
REJECT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:6666 reject-with tcp-reset

等到server一发送消息, 也马上挂了

[root@6913388a8a1e /]# nc -l -p  5555
p
[root@6913388a8a1e /]#

ss的结果:

[root@6913388a8a1e /]# ss -nat
State       Recv-Q Send-Q  Local Address:Port                 Peer Address:Port

而一边吃瓜看戏的tcpdump..:

14:54:59.045584 IP 127.0.0.1.6666 > 127.0.0.1.5555: Flags [R], seq 379940499, win 0, length 0
    0x0000:  0000 0000 0000 0000 0000 0000 0800 4500
    0x0010:  0028 0000 4000 4006 3cce 7f00 0001 7f00
    0x0020:  0001 1a0a 15b3 16a5 6e93 0000 0000 5004
    0x0030:  0000 fe1c 0000


# 这里可能会有点疑问, 为什么从server 5555 发出的报文会没看到, 我猜测是因为这个报文还没到tcpdump就已经被iptables处理并直接返回了..

于是这对冤家就这样各奔东西, 相忘于江湖.

方法二

虽然这个方法比较好使, 但是操作起来真的挺恶心..而且还挺容易误伤, 所以有第二种方法, 简单又优雅: tcpkill
直接:

tcpkill -1 -i eth0 port 5555

等到它们互相发送消息, 就能直接干掉了..
请在这里输入图片描述

tcpkill的原理和刚才的iptables相似, 也是发送了一个链接重置的R标志报文, 迫使对方关闭断开连接, 只是相对而言会比较智能一点, 因为它会自动构造报文并发送.
详情可以看: https://github.com/stanzgy/wi...

总结

其实到这里, 大家应该有些印象, 不管是第一种方法, 还是第二种方法, 都离不开那个神奇的R, 但这些又是什么?

这些就是在TCP通信过程中, 起着决定性的作用标志位flags, 主要有下面几个:

  • SYN: 表示建立连接,
  • FIN: 表示关闭连接,
  • ACK: 表示响应,
  • PSH: 表示有 DATA数据传输,
  • RST: 表示连接重置。

上面的方法所用到就是最后一种标志:RST重置链接

所以总得而言, iptablesDROP行为, 能够阻止链接的建立, 但是对于已经建立起来的链接, 顶多只能阻止数据的传输, 但是不能断开链接, 链接的断开应该只有下面几种可能:

  1. socket 的主动close, 也就是发送 fin报文 ( 应用层程序或者内核 )
  2. TCP链接的超时自动断开 ( 这个过程可能会比较耗时 )
  3. 伪造报文发送RST

除了上面的条件, 还有一个点需要注意的, 那就是:

在某些情况下, 哪怕对方关闭了, 但是自己也是无法感知的, 还是需要send一次, 通信一次, 触发了socket的错误, 例如 Connection reset by peer. 或者 Broken pipe等等, 才能知道自己可以关闭, 否则大家都不通信, 这样有心无力, 只能听天由命了!

欢迎各位大神指点交流, QQ讨论群: 258498217, 转载请注明来源: https://segmentfault.com/a/11...


Lin_R
5.2k 声望334 粉丝

准则一:简单即美。