shixudong@163.com
本人使用WSL2已有一段时间,近日在学习linux5.0新内核的UDP GRO功能时,偶尔发现WSL2在NAT模式下eth0的MTU值默认只有1420,顺便检查了一下Vmware和VirtualBox虚拟机,发现其NAT模式下以太网卡的MTU默认都是1500。感觉WSL2有点怪异,NAT从不涉及数据包的二次封装,怎么会莫名其妙降低MTU呢,手工将其改为1500,似乎也没出现不良症状。好奇之下,网上搜了一下相关资料,WSL2(NAT)降低默认MTU,居然和所在Windows主机上的VPN有关。综合网上资料,最初WSL2(NAT)的MTU也是1500,后来发现不能顺利访问主机VPN网络,便将其改为1280(IPv6最小MTU),再后来考虑WSL2内部有时也需要跑VPN,导致该VPN的MTU小于1280,内部VPN上不能跑IPv6,这才把WSL2(NAT)的MTU改到了1420(wg的默认MTU),而且理论上为了让WSL2(NAT)能顺利访问主机VPN网络,WSL2(NAT)的MTU不应超过主机VPN。所以针对不同的VPN,如发现网络访问不正常,还需要进一步缩小WSL2(NAT)的MTU。我也试着在Windows主机上启用了wg(AllowedIPs = 0.0.0.0/1,128.0.0.0/1),WSL2(NAT)在MTU=1500的情况下确实会出现ssh不能连接的问题,进一步测试发现在启用wg的kill-switch功能后(AllowedIPs = 0.0.0.0/0),即使MTU=1420也必定会出现无法访问互联网现象。网上有位老外对WSL2(NAT)和主机VPN相互之间的影响分析得比较全面,网络访问异常主要由3个原因引起,分别是MTU、路由和DNS,但由于只考虑了WSL2的NAT,分析未免不够透彻。本人同时对比了Vmware和VirtualBox下NAT和主机VPN的互动,做了进一步的分析,有助于更深刻地了解虚拟机的NAT模式,特此记录,以免遗忘。
一、MTU问题
对于ICMP和UDP数据包,WSL2(NAT)的网关(对应主机上的vEthernet (WSL)网卡)功能太弱鸡,不能处理IP碎片包(即使数据包不带“不分段”标志)。测试发现,即使主机上没有开启VPN,由于WSL2(NAT)的默认MTU设置为1420,和主机以太网卡的MTU(1500)已经不匹配,在WSL2(NAT)内ping外部网络,当包长度超过MTU-28时,由于需要拆包导致ping不通。对于UDP数据包同样如此,使用iperf测试UDP包,因UDP包默认长度1470,已超过WSL2(NAT)的默认MTU(1420),WSL2在IP层对UDP包分片,但因网关不支持,分片后的UDP无法发送出去,实际上无法进行UDP性能测试。要使iperf能顺利测试UDP,要么修改iperf的UDP包默认长度参数不超过MTU-28,要么将WSL2的MTU重新设置为1500。
TCP数据包能够自动协商MSS,故不存在上述IP碎片包问题。但当WSL2(NAT)的MTU设置为1500,和主机VPN的MTU不匹配时,首先依赖VPN的MSS clamping机制,并将PMTU发现机制作为备用手段,如两者都不起作用,就会引发TCP不通问题。
在VPN不能提供MSS clamping机制时,PMTU发现机制尤显重要,而且双向都必须有效。如Win11防火墙没有放行“虚拟机监控(回显请求)”,WSL2(NAT)很大可能收不到网关主动发来的通告Path MTU的ICMP包,意味着本地PMTU发现机制失效,导致TCP大包发不出去,具体表现为ssh不通。如主机VPN对端VPN Server出于安全考虑,阻塞了ICMP包,将导致远端PMTU发现机制失效,对端TCP大包无法回来,具体表现为无法访问网页。鉴于这么多不利因素可能影响TCP双向通信,干脆将WSL2(NAT)的MTU设置值不超过主机VPN不失为一种简易且有效的方法。
Vmware和VirtualBox的NAT模式,在主机层面,其实质就是代理模式,主机上netstat能看到主机代理发起的相关TCP连接。虚拟机内TCP数据包通过主机VPN路由出去时,代理模式发起的TCP连接将以VPN的MTU去协商TCP的MSS,此外如发现MTU不匹配,代理模式自动对虚拟机发来的大包进行拆分合并成小包(非IP碎片包),使之适配VPN的MTU,顺利通过主机VPN发送出去。代理过程对虚拟机完全透明,虚拟机认为自己处于NAT模式,也感觉不到两者MTU不匹配这一事实。
Vmware和VirtualBox的NAT对于UDP和ICMP数据包的处理,和TCP一样也采用了代理模式,不同之处在于MTU不匹配时,代理模式对虚拟机大包的拆分合并采用了IP碎片包方式。此外,当虚拟机和主机VPN之间MTU不匹配时,VirtualBox(NAT)存在一个小缺陷,当数据包长度落在两个MTU之间时,虚拟机发出的UDP和ICMP数据包默认带“不分段”标志,但对于主机来说,数据包长度已超过VPN的MTU,认为需要进行拆包处理,此时VirtualBox(NAT)完全无能为力,数据包将无法到达主机。针对这一缺陷,可将虚拟机linux的ip_no_pmtu_disc设置为1,使得数据包默认都不带“不分段”标志,允许VirtualBox(NAT)用IP碎片包方式进行拆包处理,顺利通过主机转发出去。
前面分析了Vmware和VirtualBox的NAT,最后还得提一下WSL2的本家Hyper-v(NAT),本自同根生,因此存在的问题和分析与WSL2(NAT)完全一致。至于前文所提的WSL2(NAT)网关功能太烂,不支持IP碎片包,实际上是Win11配套Hyper-v的NAT功能太烂,无法对IP碎片包进行NAT转换(事实上,IP碎片包的转换对基于端口的NAT也确实是个挑战),而不是网关对应的虚拟网卡弱鸡。经验证,只要不使用NAT转换功能,Hyper-v虚拟网卡对IP碎片包的处理完全没有问题(原计划准备本机验证的,结果发现Win11的23H2版本突然不支持通过注册表IPEnableRouter开启路由转发功能了,尚不清楚今后是否继续支持抑或纯粹BUG)。
二、路由和DNS问题
当Windows主机启用wg的kill-switch功能后(AllowedIPs = 0.0.0.0/0),即使WSL2(NAT)的MTU已设为1420,依旧无法访问互联网,初步测试发现,是WSL2(NAT)网关作为DNS不能提供解析服务,如直接访问外部IP或换用VPN提供的DNS,就没有问题。经深入研究,WSL2(NAT)网关不能解析DNS只是表象,本质上是由wg的kill-switch机制衍生的路由问题所致。wg启用kill-switch功能后,主机和外部网络的通信数据包,只允许来自或去往wg通道,本地局域网通信则不被允许。因为WSL2(NAT)的网关就是主机上的vEthernet (WSL)网卡,WSL2(NAT)访问网关的DNS通信被视为本地局域网流量而丢弃,导致无法提供DNS解析。同理,主机和WSL2(NAT)之间也无法ping通(通过localhost地址互访则不受影响)。
新版WSL2引入了dnsTunneling开关,启用后,WSL2(NAT)不再使用网关作为DNS,新的DNS地址127.0.0.42属于loopback地址,不受kill-switch机制影响。但主机在启用kill-switch功能时如不指定VPN对应的DNS,即使主机原DNS能通过VPN通道访问,使用新的127.0.0.42仍然无法解析DNS。目前尚不清楚其内在机制,因为主机上包括浏览器在内的部分程序都能使用主机原DNS正常访问域名,nslookup也能正常查询,但在cmd下也无法ping域名。
同样,我们检查一下Vmware和VirtualBox的NAT模式在主机wg启用kill-switch功能后的表现,VirtualBox(NAT)在主机上没有对应的网卡,直接使用主机的DNS作为自己的DNS,使用末位为2的同网段IP地址作为自己的网关。Vmware(NAT)表面上类似WSL2(NAT),在主机上生成了对应的网卡(IP末位地址为1),但该网卡IP并不充当虚拟机的网关和DNS,甚至可以设置不要在主机上生成该网卡,虚拟机同样使用末位为2的同网段IP地址作为自己的网关和DNS。如同WSL2(NAT),Vmware(NAT)和末位地址为1的IP之间无法通信,但Vmware(NAT)和DNS之间的通信完全属于虚拟机内部通信,故不受kill-switch机制影响。
显然Vmware和VirtualBox在NAT模式下的DNS解析完全符合kill-switch允许通信的要求,故没有类似WSL2(NAT)的DNS解析问题。另外一方面,也完美解释了Vmware(NAT)为什么使用.2而不是.1作为网关和DNS的疑惑。
Wg的kill-switch机制比较科学,对底层网络包进行了截获,以判断其通信流量是否通过VPN来控制放行与否,但有些VPN单纯采用了路由技巧实现kill-switch机制,如前面老外在路由问题中所提到的VPN,此类VPN通过新增InterfaceMetric为1的多条路由覆盖本地局域网路由(包括虚拟机对应的局域网络),以引导流量完全指向VPN通道。由于纯粹使用路由机制实现kill-switch,导致外部网络回复WSL2(NAT)的数据包又重新路由到VPN通道,最终WSL2(NAT)彻底无法与外部网络通信。针对此类VPN,只有参照老外给出的方案,删除新增的针对虚拟机的局域网络,才能在启用kill-switch的同时解决WSL2(NAT)与外部网络的通信问题。至于Vmware和VirtualBox的NAT,虚拟机和外部网络通信时,由于采用代理模式,取消或绕过了虚拟机对应的主机网卡,同样不会受到此类VPN启用kill-switch功能的影响。
至于Hyper-v(NAT),主机wg启用kill-switch功能后,针对局域网通信,仅放开了主机作为DHCP客户端的通信,对于Hyper-v(NAT)来说,主机作为DHCP服务器,DHCP响应包发不出去,导致Hyper-v(NAT)根本无法获取DHCP。而WSL(NAT)的DHCP不是通过网络分配(虚拟机里也无需跑DHCP客户端),所以不存在无法获取DHCP问题。
三、后话
WSL2(NAT)的底层网络机制和KVM(NAT)一致,不采用代理模式,主机上netstat看不到虚拟机和外部设备的TCP连接,NAT在主机层面既不修改或重新协商虚拟机发出TCP的MSS,也不对数据包进行拆分合并操作。理论上KVM(NAT)在虚拟机和主机VPN的MTU不匹配时,也会出现同样的TCP连接问题,模拟测试(不使用MSS clamping并禁用本地PMTU)证实了这一推理,但由于linux主机防火墙行为不像Win11那样难以捉摸,而且更主要的是KVM(NAT)使用了主机iptables实现NAT功能,很容易启用MSS clamping机制,因而基本对用户无感,无需像WSL2(NAT)那样只能在虚拟机网卡层面进行MTU调优。
顺便提一下,最新版WSL2新增了mirrored模式,使用和主机完全相同的IP地址,外部网络访问WSL2(mirrored)不再需要另行配置NAT映射,相较bridged模式,主机也无需额外安装Hyper-v功能。VPN在WSL2内部被视为另一块网卡,MTU直接继承自主机VPN,压根没有前面那些破事。但经初步使用,目前发现mirrored模式存在如下不足:
1、在WSL2(mirrored)上使用tcpdump捕获进来的UDP包时,如WSL2没有事先打开相应的UDP接收端口,主机根本不会把外部网络发来的UDP包镜像到虚拟机,你将会一无所获。
2、WSL2(mirrored)号称支持multicast,但其官方内核CONFIG_IP_MULTICAST is not set,如主机使用有线网卡,问题倒是不大。然而如主机笔记本无线网卡固件具有multicast filter特性,由于WSL2内核缺失组播加入功能,导致外部发来的组播包在主机无线网卡层面就被拦截,此时WSL2的multicast receive大概率无法工作。不过,mirrored在支持multicast方面确实存在优势,譬如,使用CONFIG_IP_MULTICAST=y重新编译内核后,由于目前无线AP大都已强制启用multicast_to_unicast功能,通过主机WiFi进来的multicast包采用主机WiFi的MAC作为目标MAC,能够顺利被WSL2(mirrored)接收,但无法被WSL2(bridged)接收(Vmware和VirtualBox的无线桥接模式进一步扩展了桥接功能,可根据三层多址IP对multicast_to_unicast进行逆转换,将目标MAC恢复成标准多址MAC,故不存在无线multicast receive的瑕疵)。
3、WSL2(mirrored)尚不支持内核sockets镜像,因此,内核态udp隧道如wireguard、vxlan等都无法工作(无法接收UDP包)。经过验证,可以借助用户态socket工具(如iperf -u -s -p port)重新bind一下内核态隧道已经打开的UDP接收端口,虽然会报地址已被使用的错误,但此后上述内核态udp隧道就能正常工作,也算是一种临时解决方案吧。