一说起TCP
, 就是什么三次握手, 四次挥手. 而这次想讨论的是:
在不重启各自socket程序情况下, 将ESTABLED链接断开 ???
情景模拟
简单点, 在同一个机器 通过 nc
来实现 server 和 client 吧
# 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
协议中的RTT
和RTO
所决定的.
RTT (round trip time)
在开启了TCP时间戳后,A记录下时间t1把包发给B,B收到包后记录下时间t2把包回给A ,这个过程里t2-t1就是RTTRTO(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
就能接收到相应的信息了, 从此client
和 Server
又能愉快的玩耍了
花了很大的篇幅来证明client
和 server
的真爱, 事实证明它们的专一值得学习!
但是很多时候, 如果client
和server
冷战, 谁也不理谁, 这就让我们很蛋疼了, 因为如果这样不必要的链接, 长时间保存, 会大量的占用资源, 很快就会出现资源瓶颈, 所以我们一定要扼杀掉这种行为!
正确姿势
首先, 我们得明白的是, 一般的重启程序, 重启机器, 实际上是发送了 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
的全双工的呀, 刚才断的只是, client
到 server
的, 还有server
到client
的,
按照刚才的方法, 反过来搞一发!
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重置链接
所以总得而言, iptables
的DROP
行为, 能够阻止链接的建立, 但是对于已经建立起来的链接, 顶多只能阻止数据的传输, 但是不能断开链接, 链接的断开应该只有下面几种可能:
-
socket
的主动close
, 也就是发送fin
报文 ( 应用层程序或者内核 ) -
TCP
链接的超时自动断开 ( 这个过程可能会比较耗时 ) - 伪造报文发送
RST
除了上面的条件, 还有一个点需要注意的, 那就是:
在某些情况下, 哪怕对方关闭了, 但是自己也是无法感知的, 还是需要send
一次, 通信一次, 触发了socket的错误, 例如 Connection reset by peer.
或者 Broken pipe
等等, 才能知道自己可以关闭, 否则大家都不通信, 这样有心无力, 只能听天由命了!
欢迎各位大神指点交流, QQ讨论群: 258498217, 转载请注明来源: https://segmentfault.com/a/11...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。