从一个fin 卡顿问题到 scapy 的使用
scapy 使用
使用比较简单,git clone https://github.com/secdev/scapy 然后在有python3的环境直接可以跑(python2官方说也支持)
注意:
scapy会触发内核发送reset,所以先要在iptables条件一条规则把内核的reset干掉,要不影响scapy的测试
1 | iptables -A OUTPUT -p tcp --tcp-flags RST RST -s 192.168.0.1 -j DROP |
OS不认scapy模拟发出的包,你他妈是谁你就乱发包,内核里面没有你这个socket记录,只能reset你,所以还得用iptables把这个OS 触发的reset,测试才能顺利进行
三次握手
用scapy 模拟客户端来进行3次握手
代码:
1 | sport=random.randint(1024,65535) |
完整案例:
1 | # ./run_scapy |
上面的代码是给对端22345 发了个syn包,然后收到了 syn+ack 包并展示在最后,这一来一回的两个握手包都可以用tcpdump 抓到
服务端的话可以起一个标准http server来验证:
1 | python3 -m http.server 22345 |
对应抓包:
1 | 15:58:43.301867 IP localhost.44633 > ky2.22345: Flags [S], seq 123451000, win 8192, length 0 |
fin 挥手端口卡顿案例
一个奇葩的tcp连接断开的卡顿问题(来自这里 https://mp.weixin.qq.com/s/BxU246Btm2FLt1pppBgYQg ),下面是我对这篇文章问题描述的总结:
1 两端几乎同时发fin, client收到fin回了一个ack
2 client发的ack先fin到达server,server收到ack直接进入time_wait
3 fin到达server被扔掉—-接下来就是要用scapy验证这个fin包被扔掉/忽略了,导致client不能立即断开要等200ms
4 client认为关闭失败,等了200ms重传fin然后关闭成功
这个问题的总结就是:TCP连接断开的四次挥手中,由于fin包和ack包乱序,导致等了一次timeout才关闭连接,但是上层业务设置了200ms超时,导致业务报错了,现在需要重现这个问题!
原作者怎么分析定位,花了几周,这个过程大家可以去看上面的原因,本篇的目的是对这个问题用Scapy 来重现,目标掌握好 Scapy 这个工具,以后对各种其他问题大家自己都能快速定位
用scapy来模拟这个问题,server端用python实现,重点注意server端的断开方式有两个shutdown/close:
1 | import socket |
对应的client 测试代码,引入了scapy,代码首先是和服务端3次握手,然后抓取(sniff)所有服务端的来包,看看是不是fin,是的话故意先回ack再回fin人为制造乱序:
1 | from scapy.all import * |
下图是服务端 shutdown时模拟挥手断开时ack包和fin包乱序了,也就是先回ack,sleep一段时间后再回fin包,如图:
如果server端代码中将shutdown改成close 并做个对比,关键是上面绿框回复ack是4322(challenge_ack 表示seq=4322的fin包被忽略了),而下面close时的seq=4322的fin包会被正确接收并回复ack 4323 确认,那么这时client 可以断开了。而上图绿框表示fin 被忽略了,那么内核要继续等200ms 再次发 fin,等收到ack后client 才能断开
server上通过 netstat 观察连接的状态变化:
1 | //shutdown,可以看到server 发fin进入FIN_WAIT1,然后收到ack 进入 FIN_WAIT2,此时收到fin了,但是被扔掉了,无法断开进入TIME_WAIT |
seq 回绕到0 会导致丢包吗?
首先学习下seq 是一个无符号32位的整数,最大值是4294967295
如图,有人发现探活连接不通,导致了一次非正常切换,所以需要分析连接为什么断开,抓包发现重传的时候正好seq 为0,于是他就奇怪了是不是这个seq溢出搞的鬼?怎么这么巧seq 刚好为0了?
重现代码:
1 | from scapy.all import * |
对应的抓包:
交换机丢掉seq=0的包
本次问题,验证为 中兴9900系列交换机存在seq=0 push 发送模式下报文存在丢失缺陷, 丢包原因是中兴交换机从安全角度考量是支持antidos防攻击功能的,该功能开启后会将TCP的该报文作为非法报文进行丢弃。目前XX确认该功能默认是关闭的但是未生效,需要重新触发关闭(现场看配置是关闭的,实际是开启的,XX操作即将改配置先打开再关闭)。
临时规避方案:(XX内部验证测试针对此报文有效,现场环境可能有差异,需要现场验证确认)
1 | 先执行 (config)#anti-dos abnormal enable |
现场实施完毕后,发包验证恢复正常,后续持续观察业务。
彻底解决方案:将该版本升级至V2.00.00R8P16
scapy 构造全连接队列溢出
server 端用python 起一个WEB 服务:
1 | nohup python3 -m http.server 22345 & |
然后client端用如下scapy 代码不断去3次握手建立连接,试几次后就抓到如下现象:
抓包效果:
总结
我觉得scapy还是挺好用的,比packetdrill好用一万倍,直观明了,还有命令行可以交互测试
但是要注意 scapy 是绕过内核在模拟发包,收包靠sniff,所以内核收到这些回包会认为连接不存在,直接reset,需要在iptables里处理一下
这个问题是别人推荐给我看的,一般10分钟就看完了,但是我差不多花了2天时间,不断地想和去实验重现
参考资料
https://wizardforcel.gitbooks.io/scapy-docs/content/3.html
https://www.osgeo.cn/scapy/usage.html
https://zhuanlan.zhihu.com/p/51002301
如果你觉得看完对你很有帮助可以通过如下方式找到我
find me on twitter: @plantegg
知识星球:https://t.zsxq.com/0cSFEUh2J
开了一个星球,在里面讲解一些案例、知识、学习方法,肯定没法让大家称为顶尖程序员(我自己都不是),只是希望用我的方法、知识、经验、案例作为你的垫脚石,帮助你快速、早日成为一个基本合格的程序员。
争取在星球内:
- 养成基本动手能力
- 拥有起码的分析推理能力–按我接触的程序员,大多都是没有逻辑的
- 知识上教会你几个关键的知识点