从一个fin 卡顿问题到 scapy 的使用
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 |
因为包是 scapy 绕过OS 够早的,导致 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超时,导致业务报错了,现在需要重现这个问题!
出现这种问题的场景:比如在 OVS/MOC 等网络场景下,SYN/FIN 等关键包需要更新路由(session flow),会走 slowpath(送到更高层的复杂逻辑处理,比如 SYN 就创建一个新的 session 记录,以后普通包直接命中这个 session 就快了;同样 FIN 需要走高层逻辑去释放这条 session 记录)
影响:因为 ack 比 FIN 先到,导致应用连接已被释放,但是后面的 FIN 被重传,从而可能使得应用记录的连接断开时间要晚甚至超时
原作者怎么分析定位,花了几周,这个过程大家可以去看上面的原因,本篇的目的是对这个问题用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了?
要想正好巧合在 seq 回绕的时候刚好回绕到 0 还是非常不容易的,不过好在用 scapy 来模拟这事就简单了
重现代码:
1 | from scapy.all import * |
对应的抓包:
从上面的实验来看内核能正确处理这种 seq 回绕到 0 的场景,所以问题不在内核处理上。进一步分析发现是交换机的 bug 导致的
最终确认原因:交换机bug 丢掉 seq=0的包
最后发现这个问题是中兴 9900系列交换机存在seq=0 push 发送模式下报文存在丢失缺陷, 丢包原因是中兴交换机从安全角度考量是支持antidos防攻击功能,该功能开启后会将 TCP seq 为 0 的报文作为非法报文并丢弃。中兴 9900交换机上该功能默认是关闭的但是未生效,需要重新触发关闭(现场看配置是关闭的,实际是开启的,现在执行将配置先打开再关闭)。
临时规避方案:(中兴内部验证测试针对此报文有效,现场环境可能有差异,需要现场验证确认)
1 | 先执行 (config)#anti-dos abnormal enable |
现场实施完毕后,发包验证恢复正常,后续持续观察业务。
彻底解决方案:将该版本升级至V2.00.00R8P16
华为交换机针对默认连接丢包 bug
借着中兴这个交换机问题,说一下华为交换机的的 bug,华为 CE12800系列,V200R022C00SPC500之前的版本
当大规格路由反复震荡场景下会小概率
- 出现优先级更高的(子网掩码范围更小)路由表项残留,导致整个子网不通
- 某个接口下发的路由表和其他接口(芯片)不一致,导致某条连接一直丢包
可以重启单板修复表项异常问题,该问题在新版本上(V200R022C00SPC500)已经补丁修复(SPH220)
根因:残留表项或表项异常导致,老版本在大规格路由反复震荡场景下会小概率触发,是软件多线程处理路由下发或删除时,出现线程读取数据异常,导致芯片表项错误。
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
开了一个星球,在里面讲解一些案例、知识、学习方法,肯定没法让大家称为顶尖程序员(我自己都不是),只是希望用我的方法、知识、经验、案例作为你的垫脚石,帮助你快速、早日成为一个基本合格的程序员。
争取在星球内:
- 养成基本动手能力
- 拥有起码的分析推理能力–按我接触的程序员,大多都是没有逻辑的
- 知识上教会你几个关键的知识点