本章序言
老实说,最开始排查网络问题,接触到的命令就是ping,当时也只是对ping有一个非常粗浅的理解,知道他可以探测本地到目标的链路是否是否正常,再往后就知道了traceroute和mtr,感叹网络的大佬们真的将三层、四层协议用的是淋漓尽致,很好的利用了ICMP、IP协议的各个字段,非常巧妙的实现了“窥一斑而知全豹”的能力,下面的内容,部分可能非常老套,但我建议大家可以一并参考一下,虽然这些工具都是老生常谈了,但每次碰到疑难问题去研究他们的实现时,都可以获得新的感悟。
探测元老——ping
说到ping,应该是无人不知无人不晓,最简单的方式,就是ping加上目标IP,在Windows下会连续发起4次ICMP request探测,类Linux下则会发起持续的ICMP request探测,直到Ctrl+C停止。但它其实还有很多高级功能,这里列举一二。
Flood ping
默认情况下,ping的探测逻辑是这样的:发出ICMP request-接收到ICMP reply-发送下一个ICMP request,每一个ICMP request都是串行的发送的,但有时候我们希望更快速的进行探测,这个时候就需要flood ping出马了,对应的参数为-f,加上后ping程序会并发的发出ICMP request,同时在接收远端回复的ICMP reply,每发出一个ICMP request,在屏幕上打印一个点,每收到一个ICMP reply,就打印一个退格,那么就可以很直观的知道到底丢了多少包,当然,这个方式下-i、-c参数依然可以使用,特别是-c参数,指定要发多少的ICMP包。
更直观的打印探测结果
默认情况下,ping程序只会打印出收到ICMP reply信息,如果其中丢了几个包,用肉眼很难在满屏的结果中找到,这个时候就需要让ping程序更直观的打印出每个sequence number的结果,对应参数-O,如果在规定的时间内没收到回复,会打印一行no answer yet for icmp_seq=xxx,这样可以非常直观的看到具体哪个包出现了丢包。另外,在使用这个参数时,一般还会合并使用-D参数,这个参数会在每一行输出结果前加上timestamp时间戳,可以用于一些持续性的网络监控。
mtr
相较于ping,mtr会更加实用一些,他利用了IP头字段里的TTL值,结合ICMP协议完成了整条网络链路的探测。默认情况下,他会使用ICMP type 0x00和0x08的包作为探测的报文,一般使用中我都会建议用户加上-n参数,禁止反解,这样可以最直观的看到每一跳的真实IP,另外,若需要将mtr的结果提供给第三方,建议可以使用-rc参数,r代表不使用交互界面,而是在最后给出一个探测结果报告;c参数指定需要作几次探测(一般建议是至少200个包,可以配合-i参数减少包间隔来加快得到结果的时间)。
除了ICMP,mtr也可以进行TCP和UDP协议的探测,当然依然得结合ICMP和TTL的特性来探测,不过由于链路哈希的存在,这个探测的结果不太可靠(读者朋友可以思考一下为什么,下一节讲到traceroute会详细说明),一般建议还是直接使用ICMP协议进行探测。
路由跟踪Traceroute
上一讲到了mtr,但其实traceroute才是更加“历史悠久”的路由跟踪程序,从mtr的全称My TraceRoute就可以看出来了,mtr是受到traceroute的启发诞生的,虽然mtr基本可以做到traceroute的绝大多数功能,但在一些特定的环境下(比如没有安装mtr),还是得要使用traceroute来进行排查。
traceroute的默认行为
和mtr不同的是,traceroute默认使用UDP作为四层协议,下层还是依靠IP头的TTL来控制中间的节点返回ICMP差错报文,来获得中间节点的IP和延时。唯一的区别是,在达到目标节点时,若是ICMP协议,目标大概率是会回复ICMP reply;如果是UDP协议,按照RFC协议规定,系统是要回复ICMP 端口不可达的差错报文,虽然三大平台Windows/MacOS/Linux都实现了这个行为,但出于某些原因,这个包可能还是会在链路上被丢弃,导致路由跟踪的结果无法显示出最后一跳。所以建议在一般的情况下,traceroute命令可以加上-I参数,让程序使用ICMP协议来发送探测数据包。
mtr的隐藏陷阱
我曾经接触过一个case,用户反馈业务上部署的监控,会时不时的出现“丢包”问题。我们查看了公网的大盘监控,反复查阅都没有看到任何匹配的异常报警,没法子只能和客户沟通,从问题表现着手分析,直到我们发现客户使用的是mtr给出报告的方式来监控是否丢包(mtr的报告最后一行是否包含目标IP),才发现客户一不小心踩到了mtr的一个陷阱中去了。
由于mtr本身存在一个缺陷,如果链路上有多跳都禁ICMP回显,那么MTR是大概率无法到达最终IP的(尽快实际测试,ping目标IP是通的)。这个问题在Linux上尤为突出,MacOS上的mtr没有这个问题,这个时候就需要traceroute出马了,加上-q参数,指定每一跳尝试发包的次数(最大10次),然后可以得到一个近似的mtr结果,若需要更多的结果,只能是不停的执行traceroute -q 10 -w 1 -n -I x.x.x.x ,来获取更准确的路由跟踪结果。站在这个角度,大家知道为什么mtr会诞生了吧:-)
传输层的Ping工具——tcpping2
上面说了很多基于ICMP的测试工具,但真实的业务下,多数都是基于TCP来实现的,比如业务上报Connection Timeout、Connection Reset By Peer等报错,对应到传输层面,往往是和建连、关闭连接相关,这个时候ping、mtr很难作为排查工具来辅助问题分析,同时当时市面上也未有比较方便的制造TCP建连、断连流量的工具,笔者利用空闲时间,基于Python写了一个tcpping2工具,可以实现一些高级功能,详细可以参考github的工具主页[1]
基本功能
- 连接指定的目标和端口(可指定本地源IP和源端口)
- 支持以FIN和RESET方式断开连接
- 默认情况下和linux下的ping类似,会持续的进行连接,可以通过-c参数来制定连接的次数
- 可输出日志文件,方便关闭程序后查看历史连接情况
- 还支持一些额外的参数,如-i可以调节连接的间隔,-D可以设置延迟发送FIN或RST包
程序的基本逻辑
- 以给定的参数构建五元组,使用TCP去连接目标
- 记录连接时间,打印在标准输出上,同时记录到同名的日志文件中(若指定了-c参数)
- 循环执行,或者在执行COUNT(给定参数)次退出
- 在收到TERM或INT信号后强制结束,同时输出此次运行的统计结果
命令示例
连接指定8.8.8.8的53端口
python tcpping2.py 8.8.8.8 53
指定本地源地址和源端口10086去连接8.8.8.8的53端口
python tcpping2.py -H 30.11.212.23 -P 10086 8.8.8.8 53
指定连接10次目标地址,同时输出日志文件到当前目录下
python tcpping2.py -c 10 -l 8.8.8.8 53
以RESET断开连接
python tcpping2.py -R 8.8.8.8 53
间隔1秒进行一次探测
python tcpping2.py -i 1 8.8.8.8 53
最佳实践
这个工具适合用于那些可以稳定复现的问题现场,根据我们的问题排查经验,绝大多数场景都是客户端和服务端建连出现了问题,比如SYN丢包、SYN_ACK丢包、特定五元组丢包等等,针对上面的场景,tcpping2提供了诸如-H、-P、-L等参数,可以非常灵活的决定使用什么源端口来进行连接。特别是“特定五元组丢包”这个场景,因为丢包的五元组不可知,使用这个工具传入-L参数后,tcpping2会快速的从指定的起始源端口开始,不停地自增源端口来连接,可以快速的发现不通的五元组,随后再使用-H和-P参数来固定这个不通的五元组,持续的打问题流量,以便在链路上通过抓包、流统来缩短问题链路,定位具体的问题模块。