最大的困扰是,它在某种场景下根本不可用,而你却无法区分这是真的不可用还是正如文档中所说,某些设备会禁止ICMP所以不会返回结果。
我来直接切入正题,traceroute在VMWare的NAT模式下不可用。但是这是为什么?本文来解释。
1.VMWare的NAT是一个七层的NAT
我们先来做一个实验。在VMWare中telnet一个地址,我们仍以网络测试三大神器(ping,traceroute,baidu)之一的baidu为目标,在VMWare里面以及其宿主机同时抓包:首先展示一下GuestOS Linux上的命令和抓包结果:
然后我们看一下Windows宿主机上的抓包结果:
1).协议栈的IP层在每发出一个数据包时,其IPID字段在Linux实现中是针对每四层协议递增的
TCP,UDP等均会维护自身的IPID字段的空间,只要有一个TCP段发出,不管是哪个连接的,不管是不是重传的,其IPID字段均会递增地取TCP协议的ID字段空间的值。该字段是针对主机的,即IP报文被封装的那台设备。中间二层,三层路由器/交换机等均不会修改该字段,如果发现该字段在发送端和接收端对于同一个包不一致,那就有相当巨大的概率表明数据包在中间遇到了高层设备的”整改“。
2).IPTTL字段就不解释了,它只会沿途每经过IP层设备时均会递减
即便不经过IP层设备,它也不会递增。因此可以断定,宿主机肯定是对Linux发出的数据包进行了大大的整改。
3).端口号改变了
这个很正常,在经过NAT设备的时候,为了保证五元组的唯一性,源端口号是可以被修改的。
以上就可以表明VMWare的NAT确实不是一个IP层的NAT,而是一个更高层的NAT。
但是这不足以打消质疑者们的怀疑...其实,通过在Windows宿主机上执行netstat,可以很清楚地看到,是Windows宿主机自己与远端的baidu地址建立了TCP连接,而不仅仅是转换一个地址:
大家都能很清楚的看到,TCP连接的端到端是维持在baidu和Windows宿主机之间的,有了这个无需洪荒之力便可下结论:VMWare的NAT是一个七层的NAT!
然而,除了TCP之外的其它协议怎样呢?
2.VMWare的NAT只支持比较常见的协议
我们试一下ICMP,还是用baidu,这次用ping。
具体的实验自己做一下就知道。如果你分析ICMP Reply的话,就会发现,这个Reply事实上是Windows宿主发出的。
证明工作到此为止,下面开始阐释原理。细心深挖一下,我们会发现任务管理器中有一个叫做VMware_NAT_Service的服务,其实背后就是一个进程,进程的名字是vmnat,都是一回事。这个进程是干什么的呢?这就是实现NAT的所谓”代理“!
关于具体的原理,我还是希望能从我在2012年的时候几篇文章中得到启发:
《 深入浅出VMware的组网模式》
《 vmware的vmnet-感官和视觉上的效果》
《 VMware Fusion中使用迅雷的问题》
然而,你能指望vmnat这个进程实现所有的TCP/IP协议族的地址转换吗?事实上这是不可能的!即便是操作系统的协议栈,在NAT这个话题上也会面临不少问题,以Linux为例,其NAT依赖nf_conntrack这个Netfilter HOOK,它对大部分协议支持的很好,但是你要知道的是,之所以可以支持大部分协议是因为nf_conntrack维护的五元组信息可以映射大部分的双向流量,然而总有例外,我们来考虑一些带外流量,即中间设备或者目的地发出的那种”并非请求协议本身的但是与其相关的控制报文“,典型的如ICMP数据。比如如果一个telnet 1.1.1.1 80的SYN经过这么一个设备,如果返回了1.1.1.1 80的SYN-ACK,那么根据conntrack表项信息可以将地址很完美的映射回去,然而如果返回的不是SYN-ACK,而是ICMP Port unreachable信息的话,怎么确保这个ICMP可以跟之前保存的那个telnet 1.1.1.1 80的SYN创建的conntrack表项对应从而将其路由给正确的源,这就是问题。
Linux是怎么解决这个问题的呢?请思考!
...(此处我也不知道该略去多少字...虽然我可以自称Netfilter以及nf_conntrack专家,但是我却从来没有写过关于带外的ICMP数据包反向NAT的文章)
Linux完美解决了这个问题,总结一句话就是:基于状态的NAT要记录发起者的五元组信息,因此对于带外流量,困境就是“正向方向转换源地址容易,反向的带外流量转换目标地址难”,Linux的解决方案是,使用related表!
vmnat必须也面临这个问题!遗憾的是,vmnat并没有解决它。
我准备在另外的文章中详细阐述Linux nf_conntrack对带外ICMP信息的处理,因此本文就此打住!(对于好酒之徒,且距离相近,当面聊这些要比写文章轻松得多!)
3.VMWare在NAT模式下的traceroute
现在我们来解开谜底。首先,我来纠正几个错误。
很多人都发现了这个问题,但是解决的人并不多,也有人提出了自己的猜想,但是这种猜想是错误的。很多人发现了VMWare的NAT会整个的替换IP协议头,但是却不曾想过它替换头的操作实际上是通过socket完成的。正常的NAT是不会触动除了IP地址和端口之外的其它字段的!即便是在国外的论坛上,当有人提到VMWare下无法使用traceroute的时候,也没有人能点到痛处。
很多人的回答是“Windows使用ICMP作为探测,而Linux使用UDP,只要使用-I参数就好了...”但是这解决不了问题,不信你自己在VMWare的NAT模式下试试...
大部分人认为,作为宿主机的Windows(其实MacOS或者Linux也一样),其vmnat进程会替换掉整个IP头,忽略了这是一个traceroute探测这么“应用层”的语义,然后就把TTL换成自己的默认值了,比如这个在Windows上是128,因此你会发现,不管在Guest上traceroute谁,都是两跳直达!这个解释很合理,然而本着工匠精神,我们最有力的证据就是数据包,通过数据包,我们可以发现,vmnat并没有修改TTL,TTL exceeded这个ICMP error确实也回到了宿主机,之所以NAT后面的Guest无法正确显示,是因为:vmnat没有将这个信息转发给Guest!在VMWare的Guest主机内来执行traceroute,很自然,我们依然用baidu:
我们猜想一下,为什么vmnat没有将这个ICMP time exceeded信息路由给GuestOS。原因很简单,是因为这个并不是很容易!然而也不是很难的事。以Linux的做法为经验完全可以分分钟搞定。vmnat收到的ICMP time exceeded信息的发起源并不是真实目标,而是IP路径当中的某一个设备的IP地址,试问仅仅依靠ICMP头怎么跟“保存在vmnat内部的conntrack表对应”?其实很简单,了解ICMP的都知道,ICMP天生就携带了出错包的原始片段(ECHO除外!),只需要进一步解析其内容就好了嘛!下面是一个解释:
如此,我们知道了在使用VMWare NAT模式的时候,traceroute是不会正常工作的,而且我们也知道了其实VMWare的NAT是由一个应用进程完成的,这显然是它实现功能不完全的一个借口。
然而,也许你会进一步去怀疑Linux是怎么做的,它的做法完美吗,它又是怎么将一个ICMP错误信息关联到一个TCP流或者UDP包的,这个且听下回分解。