Directory Not Empty, 删不掉的幽灵

无法删除的文件
最近部门NAS测试团队遇到一个非常诡异的问题,在删除一棵存在SMB共享文件夹的文件树时,删除完子目录的所有文件后,再删除这个子目录的时候居然系统报出“Directory Not Empty”的错误从而导致用例测试未通过,打开这个目录一看,确实有一个文件并未删除成功,再查看I/O工具的日志,报告所有的文件都已经成功删除。难道是I/O工具出了问题而没有正确报出错误,这个经过工具开发者的研究后确认后貌似工具没有任何问题,这个问题起初被开发团队踢皮球,死活不承认是SMB2服务器的问题。死活非得让抓取网络包来证实。

作为一个网络分析的伪专家,自己也厚着脸皮主动蹭入NAS测试团队强行出力,希望把这个悬案搞个谁落石出。

抓包重现
首先,设置好抓包参数,同时重现问题,这一步很顺利。由于测试参数没有任何更改,很顺利的重现了问题,并且将出问题对应时间点的网络包悉数抓到。

在Windows2106的客户端开启抓包工具wireshark(其对应的命令行工具为 tshark), 并且设置好相关参数:

$ tshark -i ens1 -B 4096 -s 1024 -w client-traffic.pcap

参数的含义就是在接口ens1上抓取长度为1024字节的每一个帧, 并把这些帧存在client-traffic.pcap的文件里

分析
在抓取到网络包以后,我们开始解包分析:

同样通过tshark命令解包分析, 并将揭开的内容分别重定向到摘要文件client-traffic.summary和详细展开格式的client-traffic.detail文件当中:

$ tshark -t ud -Y "ip.addr==" -r client-traffic.pcap >>client-traffic.summary

$ tshark -t ud -O smb2 -Y "ip.addr==" -r client-traffic.pcap >>client-traffic.detail

打开client-traffic.summary文件后的一步就是要找到对应未删除成功的文件(文件名: VCg8iMkGWgll2VJoEFMUa0FKp1DJHEG2)最后一次出现的帧,简单的通过文本搜索便可定位,找到对应的帧以后发现,这个操作是一个Create操作,根据协议[MS-SMB2], 删除操作是通过一组三元操作 Create/SetInfo/Close来实现的,称为Delete-On-Close,由于我们正要寻找的是删除操作,借此可以大胆推测,这个Create (Frame #210725)请求正是三元删除重的第一步, 再往后的帧一个个寻找,果然找到了第二步SetInfo (Frame #210727) 和第三步Close (Frame #210757, 通过该帧展开后确认其FileID属性与前两步操作对象一致) 两个请求以及对应的服务器端的回复:
最后Close并未得到服务器回复
clipboard.png

Delete-On-Close 操作中的前两步
Delete-On-Close 操作中的前两步
然而最后的一步的close 请求(Frame #210757)并未得到服务器端的回复,那么问题显然出在了这里,继续往下分析,为什么服务器没有对最后一步close操作作出响应呢?

最后Close并未得到服务器回复
clipboard.png

奇怪的是,这个close操作发出后在0.1秒内没有得到任何回复,于是出发了TCP层的超时重传(RTO)(Frame #210765),按道理说在同一个实验室的内部网络下,网络状况是十分好的,基本上不可能发生RTO的情况(即便连快速重传也是极为罕见的),除非服务器出现了宕机。此时我突然想到我们测试当中为了测试SMB2的CA功能(Continues Availability:高可靠性,容忍服务器重启或者故障转移), 经常会引入错误注入的测试用例,再次翻看测试日志,果然发现在创建和删除文件操作的同时有重启服务器节点的操作执行,查看时间戳和发生问题的时间点一直,现在基本可以明确的是,发生的问题和CA有关了。

删除文件同时服务器重启
Directory Not Empty, 删不掉的幽灵_第1张图片

现在来看看正常的CA流程,当客户端一个SMB2请求遇到服务器重启的情况下,网络会暂时断开,发出的请求在若干次超时重传以后会收到服务重启后发出的TCP重置请求(RST)(Frame #210769), 客户端在收到此请求后便可得知网络发生了断连,为是后续操作得以延续,必须再次建立TCP的连接(通过三次握手 Frame #210770, #210771, #210772), 重新协商(Negotiate. Frame #210773)建立会话(Session Setup. Frame #210776)和共享文件根目录的连接(Tree Connect. Frame #210785), 最后,因为客户端知道在网络断连之前最后一次没有响应的操作即目标文件,此时客户端会通过Create操作(Frame #210787)重新发起一个对目标文件的连接(在client-traffic.detail可看到展开后的Create操作的RECONNECT信息),从而接续上服务器从其前的步骤,实现CA的功能特性

Frame #210773 - #210786 重建会话
Directory Not Empty, 删不掉的幽灵_第2张图片

在client-traffic.detail中找到第210787帧的展开后详细信息,便可得知这是一个试图重连(RECONNECT)操作:

Frame #210787 展开详细信息
Directory Not Empty, 删不掉的幽灵_第3张图片

到目前为止,客户端的所有操作均是按协议规定来进行的,那么是不是这次重连之后文件就能接续服务器重启前的操作进而把目标文件删除成功呢?要知道答案的话我们必须要检查一下服务对于重连操作的回复响应,找到服务器回复的那个帧,展开:

Frame #210790
Directory Not Empty, 删不掉的幽灵_第4张图片

服务器回答说,你重连的文件没有找到(STATUS_OBJECT_NAME_NOT_FOUND),这时候客户端理所当然的认为在服务重启之前目标文件已经成功删除,只是因为重启导致网络断连导致回复并未成功发出。 所以客户端继续往下进行剩余文件的删除知道遇到删除目录出错(Frame #210838) 到这里我们可以肯定的是客户端是严格按照协议来进行请求的,而服务器这是在重连的回复里(Frame #210790) 错误地告知文件已经删除成功从而误导了客户端。所以这个已经非常明显是一个SMB2服务器的Bug。

Frame #210838
Directory Not Empty, 删不掉的幽灵_第5张图片

我们看看协议里对于这一个服务器回复(Frame #210790)的定义:

SMB2协议定义
clipboard.png

如果Persistent的文件句柄并未在全局句柄表里找到(假设已经被成功close),那么便返回STATUS_OBJECT_NAME_NOT_FOUND的回复

结尾
根据这些网络包,再与开发团队确实交流后,他们接受了这个Bug并进行了修复。

可以看到,借助于wireshark, 很多复杂且奇怪的问题都可以探究其背后秘密谈话,从而获取任何的蛛丝马迹找到问题的根源所在。

SMB2协议:

你可能感兴趣的:(nfs,nas,storage,wireshark,tcpdump)