gitbook/网络排查案例课/docs/481042.md

251 lines
22 KiB
Markdown
Raw Permalink Normal View History

2022-09-03 22:05:03 +08:00
# 05 | 定位防火墙(一):传输层的对比分析
你好,我是胜辉。今天我们来聊一个有趣的话题:防火墙。
在网络排查中,防火墙作为一个隐形神秘的存在,时常给排查工作带来一定的不确定性。有时候,你不知道为什么一些网络包能正常发出,但对端就是没收到。有时候,同样的两端之间,有些连接可以通信,有些就是不行。
这个时候,你很可能会怀疑是防火墙在从中作祟了,但是你有什么证据吗?
你不是防火墙工程师,就没有查看它的配置的权限。但是这样一个看不见摸不着的东西,却可能正在影响着你的应用。相信你也一定想彻底转变被动的状态,把问题搞定。
其实,无论防火墙有多么神秘,**它本质上是一种网络设备**。既然是网络设备,那么它必然同样遵循我们知道的技术原理和网络规范。所以,防火墙的踪迹,虽然表面上给人一种虚无缥缈的感觉,但从理论上说,总是有迹可循的。所以这次,我就帮助你抓住防火墙的蛛丝马迹。
当然,防火墙的排查技巧确实并不简单,为了把这个主题讲透,我将用两节课的时间,来给你专门讲解这方面的排查技巧:一节是结合传输层和应用层的分析推理,一节是聚焦在网络层的精确打击。相信你通过这两讲的学习,一定能掌握不少独门秘技,从而为你的应用保驾护航。
今天这一讲,我们先学第一种方法:结合传输层和应用层的分析推理。
## 结合传输层和应用层的分析推理
这里传输层当然就是指TCP/UDP应用层就是问题表象比如超时、报错之类。我们来看一个具体的例子。
这是我在2017年处理的一个案例。当时eBay内部的一个应用A访问应用B的时候经常耗时过长甚至有时候事务无法在限定时间内完成就导致报错。而且我们发现问题都是在访问B的HTTPS时发生的访问B的HTTP就一切正常。
因为应用A对时间比较敏感开发团队希望能既解决事务失败的问题也改善事务处理慢的问题于是我们运维团队开始调查。
那么,我们先来看一下这个案例中,应用请求路径的大体示意图:
![](https://static001.geekbang.org/resource/image/11/de/11yy1ab28e81ba7062c909fd16668fde.jpg?wh=2000x546)
应用A和应用B各自都是一组独立的集群多台服务器。A的众多机器都访问B的位于负载均衡LB上的VIP虚拟IP然后LB再把请求转发给B的机器。这里的HTTP和HTTPS都位于同一个虚拟IP只是服务端口不同而已一个是80一个是443
既然问题是“A觉得B很慢”那么我们除了**听听A的抱怨是否也该问问B的解释才显得比较公正呢**这就是我们选择做“两侧抓包”的背后的考量了。
否则假如我们只是在客户端即A应用上抓包看到的报文显然也属片面。比如客户端看到自己发出的报文迟迟未被服务端确认的话那么这个报文究竟是丢失在网络路径途中还是已经到达服务端但是被服务端丢弃了呢显然只在客户端抓包是无法把这些事实弄得很清楚的。
所以我们就在A中选择了一台机器即客户端做tcpdump抓包同时在LB的HTTPS VIP即服务端上也进行抓包。
这里我要给你友情提醒一下,做这种双向抓包,需要注意:
* **各端的抓包过滤条件一般以对端IP作为条件**比如tcpdump dst host {对端IP},这样可以过滤掉无关的流量。
* **两端的抓包应该差不多在同时开始和结束**,这样两端的报文就有尽量多的时间是重合的,便于对比分析。
* 在同时抓包的时间段内,要把问题重现,也就是**边重现,边抓包**。至于如何重现,又分两种情况:一种是我们知道触发条件,那么直接操作发起就好了;另一种是触发条件未知,那么只有在抓包的同时,耐心等待问题出现,然后再停止抓包。
这次我们也是如此处理一边抓包一边跟开发团队配合观察应用日志。当观察到了日志中有意外事件exception出现后停止了抓包。那么在这段抓包里应该就含有跟这个意外事件相关的报文了。
**我们先来看一下客户端的报文情况。**打开抓包文件后,我一般会按部就班地做以下几件事:
* 查看Expert Information
* 重点关注可疑报文比如Warining级别追踪其整个TCP流
* 深入分析这个可疑TCP流的第二到四层的报文再结合应用层表象综合分析其根因
* 结合两侧的抓包文件找到属于同一个TCP流的数据包对比分析得出结论。
![](https://static001.geekbang.org/resource/image/dd/ac/dd1958be82b3ea019ffef41ffbc120ac.jpg?wh=2000x360)
那么接下来,我们就根据以上的步骤,来具体分析分析当前这个抓包文件。
### 查看Expert Information
这一步主要是为了获取整体的网络传送情况,这对于我们大体判断问题方向很有帮助。
那么在查看的时候一种方式是打开Analyze菜单选择最底部的Expert Information如下图所示
![](https://static001.geekbang.org/resource/image/bb/ea/bb90fc78160c081efb2ab5625ce813ea.jpg?wh=1176x694)
另一种方式是直接在窗口左下角,点击那个黄色的小圆圈,如下图所示:
![](https://static001.geekbang.org/resource/image/a9/96/a928335e53603a514123754a72f40b96.jpg?wh=1158x702)
不过我们要怎么理解Expert Information里面的各种信息呢我来给你挨个介绍一下
* Warning条目的底色是黄色意味着可能有问题应重点关注。
* Note条目的底色是浅蓝色是在允许范围内的小问题也要关注。什么叫“允许范围内的小问题”呢举个例子TCP本身就是容许一定程度的重传的那么这些重传报文就属于“允许范围内”。
* Chat条目的底色是正常蓝色属于TCP/UDP的正常行为可以作为参考。比如你可以了解到这次通信里TCP握手和挥手分别有多少次等等。
![](https://static001.geekbang.org/resource/image/5f/43/5fed46d83eaa6yy40a0f57801794d643.jpg?wh=941x233)
上图展示的就是客户端抓包文件的情况我们逐个来解读这三种不同级别的Severity严重级别
* Warning有7个乱序Out-of-Order的TCP报文6个未抓到的报文如果是在抓包开始阶段这种未抓到报文的情况也属正常
* Note有1个怀疑是快速重传5个是重传一般是超时重传6个重复确认。
* Chat有TCP挥手阶段的20个FIN包握手阶段的10个SYN包和10个SYN+ACK包。
一般来说,**乱序**是应该被重点关注的。因为正常情况下,发送端的报文是按照先后顺序发送的,如果到了接收端发生了乱序,那么很可能是中间设备出现了问题,比如中间的交换机路由器(以及这节课的主角防火墙)做了一些处理,引发了报文乱序。
所以自然,这个乱序也容易引发应用层异常。
### 重点关注问题报文
理解了这几个严重级别所代表的含义之后我们还是回到Expert Information的进一步解读上来。点击Warning左边的小箭头展开乱序的报文集合我们就能看到这些报文的概览信息如下所示
![](https://static001.geekbang.org/resource/image/b4/6f/b44d3f3b10e24ee47088bfcbe479be6f.jpg?wh=1854x326)
我习惯上会选择靠后一点的报文因为相对靠后的报文所属的TCP流相对更完整然后跟踪这些报文Follow -> TCP Stream找到所属的TCP流来进一步分析。比如选中191号报文这时主窗口自动定位到了这条报文我们在主窗口中选中该报文后右单击选择Follow在次级菜单中点击TCP Stream
![](https://static001.geekbang.org/resource/image/7y/eb/7yyd9d3eb185a05f3f2e86f7e9645beb.jpg?wh=550x602)
然后我们就能看到过滤出来的这个TCP流的全部报文了
![](https://static001.geekbang.org/resource/image/03/a4/03c89f95e198deb4c6a44d772da267a4.jpg?wh=1156x472)
细心的你可能会发现界面里很多字段不是默认有的比如Seq、NextSeq、TCP Seglen等等这些其实都是我自定义添加的目的就是**便于分析**(添加的办法我会在下一讲里介绍)。
这时Wireshark的显示过滤器栏出现了`tcp.stream eq 8`这个过滤器这是我们刚刚点击Follow -> TCP Stream后自动生成的。
一眼看去整串数据流确实有点问题因为有好几个被Wireshark标注红色的报文。我们重点关注下189、190、191、193、195这几个报文。
* 189服务端HTTPS回复给客户端的报文TCP previous segment not captured意思是它之前的报文没有在应该出现的位置上被抓到并不排除这些报文在之后被抓到
* 190客户端回复给服务端的重复确认报文DupAck可能DupAck报文数量多的话会引起重传。
* 191服务端HTTPS给客户端的报文是TCP Out-of-Order即乱序报文。
* 193服务端HTTPS给客户端的TCP Retransmission即重传报文。
* 195也是服务端HTTPS给客户端的重传报文。
以上都是根据Wireshark给我们提示的信息所做的一些解读主要是针对TCP**行为**方面的这也是从Wireshark中读取出来的重要信息之一。另外一个重要的信息源是**耗时**也就是时间列展示的时间间隔。显然在192和193号报文之间有1.020215秒的时间间隔。
要知道,对于内网通信来说,时间是以毫秒计算的。**一般内网的微服务的处理时间,等于网络往返时间+应用处理时间**。比如同机房环境内往返时间Round Trip Time一般在1ms以内。比如一个应用本身的处理时间是10ms内网往返时间是1ms那么整体耗时就是11ms。
然而这里单单一个193号报文就引发了1秒的耗时确实出乎意料。因此我们可以基本判定**这个超长的耗时,很可能就是导致问题的直接原因。**
### 结合应用层做深入分析
那么为什么会有这个1秒的耗时呢
TCP里面有**重传超时**的设计也就是如果发送端发送了一个数据包之后对方迟迟没有回应的话可以在一定时间内重传。这个“一定时间”就是TCP重传超时Retransmission Timeout。显然这里的1秒很可能是这个重传超时的设计导致的。
为了确认这件事,我们就需要做这次分析里最为关键的部分了:**两端报文的对比分析**。我们最好有一个大一点的显示屏打开这两个抓包文件并且把两个Wireshark窗口靠近一些更方便我们肉眼对两边报文进行比较。
不过当我们打开服务端LB的抓包文件看到的却也是一大片报文。而要比较必然要找到同样的报文才能做比较。这也是一个不小的难点如何才能在服务端抓包文件里定位到客户端的TCP流呢我们接着往下看。
### 对比两侧文件
其实找到另一端的对应TCP流的技巧是**用TCP序列号**。
我们知道TCP序列号的长度是4个字节其本质含义就是网络IO的字节位置等价于文件IO的字节位置。因为是4个字节最大值可代表4GB即2的32次方的数据也就是如果一个流的数据超过4GB其序列号就要回绕复用了。不过一般来说因为这个值的范围足够大在短时间比如几分钟碰巧相同的概率几乎为零因此我们可以以它作为线索来精确定位这个TCP流在两端抓包文件中的位置。
**首先**我们可以记录下客户端侧抓包文件中那条TCP流的某个报文的TCP序列号。比如选择SYN包的序列号是4022234701
![](https://static001.geekbang.org/resource/image/fd/82/fd2c6380e0a21fd78514c57276de0c82.jpg?wh=636x178)
注意,这里必须选**裸序列号**Raw Sequence Number。Wireshark主窗口里显示的序列号是处理过的“相对序列号”也就是为了方便我们阅读把握手阶段的初始序列号当1处理后续序列号相应地也都减去初始序列号从而变成了相对序列号。
但是显然这样处理后无论在哪个TCP流里面Wireshark展示的握手阶段序列号都是1后续序列号也都是1+载荷字节数。相对序列号肯定是到处“撞车”的,所以不能作为选取的条件。
那么,查看裸序列号的方法是怎么样的呢?
* 打开Wireshark的Preference配置菜单
![](https://static001.geekbang.org/resource/image/9b/f5/9b4f554de149411f8549f94b190460f5.jpg?wh=284x209)
* 在弹出菜单的左侧选择Protocols选中其中的TCP然后在右侧的选项中把“Relative sequence numbers”前面的勾去掉就可以显示裸序列号了
![](https://static001.geekbang.org/resource/image/ab/3d/ab2dd24667f0f6b2d7eaaa8bf114923d.jpg?wh=729x505)
**然后**,我们再到服务端抓包文件里输入过滤器:`tcp.seq_raw eq 4022234701`得到同样的这个SYN包
![](https://static001.geekbang.org/resource/image/4a/b4/4a48ebb360a52a1e37b96d23809ef0b4.jpg?wh=970x78)
正是我们的搜索条件是裸序列号所以打开服务端抓包文件的那个Wireshark窗口里才可以搜到这个报文。这也是利用了裸序列号在网络上传输是不会发生变化的这一特性。
**接下来**还是Follow -> TCP Stream翻出来这个SYN包所属的服务端抓包里的TCP流。
好了,现在我们睁大眼睛,来仔细比对这些报文的对应情况:
![](https://static001.geekbang.org/resource/image/86/a5/867c14e8167d2877ba76b38e87dc12a5.jpg?wh=1303x303)
左侧是服务端抓包右侧是客户端抓包。前4个报文的顺序没有任何变化但服务端随后一口气发送的4个包这里叫它们1、2、3、4吧到了客户端却变成了4、1、2、3这也就是Wireshark提示我们的
* Out-of-Order包1、2、3。
* TCP previous segment not captured包4。
下面,**我们再从服务端的角度**来看一下报文顺序、重传、1秒耗时这三者间的关系
![](https://static001.geekbang.org/resource/image/56/ec/56607756479b51f1005894dd7b4cbbec.jpg?wh=1029x291)
前面刚说到“服务端发送4个报文后客户端收到的是4、1、2、3”。因为后面3个报文的顺序还是正确的真正乱序的其实只是4所以就导致了这样一个状况乱序是乱序的但是“不够乱”也就是不能满足快速重传的条件“3个重复确认”。
这样的话,服务端就不得不用另外一种方式做重传,即**超时重传**。当然这里的1秒超时是硬件LB的设置值而Linux的默认设置是200毫秒。
不过,撇开这些细节不谈,我们现在知道了一个重要的事实:**客户端和服务端之间,有报文乱序的情况**。
我们查看了其他TCP流也有很多类似的乱序报文而这种程度的乱序发生在内网是不应该的因为内网比公网要稳定很多。以我个人的经验内网环境常见的丢包率在万分之一上下乱序的几率我没有严格考证过因为跟各个环境的具体拓扑和配置的关系太大了。但从经验上看乱序几率大概在百分之一以下到千分之一左右都属正常。
我们把这两个抓包文件以及分析过程和推论,发给了网络安全部门。他们对于实际的抓包信息也很重视,经过排查,发回了一个我们“期待已久”但一直无法证实的推测:问题出现在防火墙上!
具体来说,是这样两个事实:
1. 在客户端和服务端之间,各有一道防火墙,两者之间设立有隧道;
2. 因为软件Bug的问题这个隧道在大包的封包拆包的过程中很容易发生乱序。
就像下图这样:
![](https://static001.geekbang.org/resource/image/52/37/52f8f2bc8b73a2391fef44caa2d82837.jpg?wh=2000x397)
**两侧抓包,对比分析**。就是这样一个方法,最终让我们发现了防火墙方面的问题。我们做了设备升级,效果是立竿见影,事务慢的问题完全消失了。开发团队也非常感谢我们的排查工作。
那么换位思考你如果是开发团队你会查哪些层面呢一般是集中在应用程序上可能也会做一点ping、telnet、traceroute之类的检查。但术业有专攻难以把排查工作下探到传输层和网络层。
而如果你是网络安全团队,你又会怎么查呢?没错,也是集中在网络层或者传输层上面,不太会把排查上升到应用层。
![](https://static001.geekbang.org/resource/image/d4/e6/d4b3afb2f47cd5e7fdfb7071c0584ee6.jpg?wh=2000x806)
显然,两边团队没法“会合”。这种状况,在很多组织里并不少见。不过还好,我所在的团队正好既熟悉网络也熟悉应用,能把网络和应用之间的联动细节搞清楚,进而排查出根因。故事也算有了一个圆满的结局。你了解了整个排查过程,是否也有所启发呢?
不过,这里我们还有两个小的疑问没有解决:
* **为什么隧道会引发乱序?**
首先隧道本身并不直接引起乱序。隧道是在原有的网络封装上再加上一层额外的封装比如IPIP隧道就是在IP头部外面再包上一层IP头部于是形成了在原有IP层面里的又一个IP层即“隧道”各种隧道技术也是SDN技术的核心基础。由于这个封装和拆封都会消耗系统资源加上代码方面处理不好那么出Bug的概率就大大增加了。这就是在这个案例里隧道会引发乱序的原因。
* **为什么HTTP事务没有被影响只有HTTPS被影响**
在这个案例里HTTP确实一直没有被影响到。因为从抓包来看这个场景的HTTP的TCP载荷其实远没有达到一个MSS的大小。我们来看一下当时的HTTP抓包
![](https://static001.geekbang.org/resource/image/93/75/93176ff385b0984d91144eb725160675.jpg?wh=2048x424)
TCP载荷只有两三百字节远小于MSS的1460字节。这个跟隧道的关系是很大的因为**隧道会增加报文的大小**。
比如通常MTU为1500字节的IP报文做了IPIP隧道封装后就会达到1520字节所以一般有隧道的场景下主机的MTU都需要改小以适配隧道需求。如果网络没有启用Jumbo Frame那这个1520字节的报文就会被路由器/防火墙拆分为2个报文。而到了接收端又得把这两个报文合并起来。这一拆一合出问题的概率就大大增加了。
> 补充在Linux中设置了ipip隧道后这个隧道接口的MTU会自动降低20字节也就是从默认1500降低到1480字节。
> 这个案例里是特殊的防火墙它的MTU的逻辑跟Linux有所不同。
事实上,在大包情况下,这个隧道引发的是两种不同的开销:
* IPIP本身的隧道头的封包和拆包
* IP层因为超过MTU而引发的报文分片和合片。
因为HTTPS是基于TLS加密的TLS握手阶段的多个TCP段segment就都撑满了MSS也就是前面分析的1、2、3的数据包于是就触发了防火墙隧道的Bug。
到这里,你可能又会问了:这个例子中的丢包和乱序问题,其实也不限于防火墙,在路由器交换机层面也是有可能发生的,有没有办法可以更加确定地定位到防火墙,而不是其他网络设备呢?
这就是我们在下节课要进行深入解析的内容了,即聚焦在网络层的精确打击,你可以期待一下。
## 小结
这次的“两侧抓包”,实际上就起到了决定性的作用。那么除了当前这个案例,总的来说,还有**哪些情况下适合做“两侧抓包”**呢?我个人的看法是这样的:
1. **有条件的话(比如对两侧设备都有权限),就尽量做两侧抓包。**这样可以收集到更多的信息。有更全面的信息,也就更容易作出更准确的判断。这好比我们做数学或者物理题,条件越充足,解题也相对越顺利。即使信息有所富余,也不会干扰到排查工作的正确性。当然这会损失一些效率,就看你怎么权衡了。
2. **有丢包或者重传的情况的话,更应该做两侧抓包。**因为只有通过比较两侧报文,才能确定具体的丢包位置等信息,而这些信息对于排查工作十分关键。我们经常会出现的情况是,完全不同的两种故障原因,在一侧(比如客户端)看起来很可能是相同的现象。这就好比,一个一半黑一半白的球体,当其中的一面正对着我们的时候,我们是完全不知道另外一面可能是完全不同的颜色。对于网络排查也是如此。
3. **有些信息在单侧抓包里就能明确下来的,一般就没必要做两侧抓包了。**比如下节课要讲的方法就是这样,这里先给你卖个关子,下节课我们再深入探讨。
另外,在课程中我还介绍了**在两个不同的抓包文件中如何定位到同个报文的方法,也就是使用裸序列号**。
在一侧的文件中找到某个报文的裸序列号作为搜索条件在另外一侧的报文中搜索得到同样这个报文。这正是利用了TCP裸序列号在网络中传输的一致性不变性。后面的课程中我还将介绍更多这种“寻找同样报文”的方法 ,基本思想也都是基于某些信息在网络传输的一致性。
下次你遇到乱序、重传等情况时,也都可以运用我介绍的排查方法。当然,未必要照搬这里的每个步骤,但整体思想是类似的,希望通过我自己的经验总结,能给你一些启发。
## 思考题
这节课里,我介绍了使用裸序列号作为定位两侧同个报文的手段。那么要定位两侧的同个报文,除了这个方法,还有哪些方法呢?你可以从网络七层模型出发,给出自己的思考。
欢迎在留言区分享你的答案,我们一起讨论。如果觉得有收获,也欢迎你把今天的内容分享给更多的朋友,我们下节课再见。