桥接与 NAT 并不是把同一台来宾机接入 VPN 的两种方式;它们决定的是, 在任何隧道介入之前,来宾机的数据包归谁所有。在 NAT 模式下,hypervisor 重写来宾机的流量,并由宿主侧的代码发出,因此来宾机走宿主交给这段代码的任意路由。在桥接模式下,来宾机在物理网段上拥有自己的链路层身份,并自行选择默认网关,这使宿主隧道对它毫无意义,除非做隧道的是 LAN 路由器本身。我不再认为 VM 在 VPN 上,而是把它当作一个问题:那台来宾机的默认路由指向哪里。下面的测试每种模式只需几分钟,不用猜也能定论。

NAT 把来宾机放在 hypervisor 代码之后,宿主路由在此生效

在 VirtualBox 中,NAT 来宾机位于 10.0.2.0/24,虚拟网关为 10.0.2.2,DNS 代理为 10.0.2.3,自身没有通往物理网络的路由。出站数据包由 NAT 引擎接管,被改写成源地址为宿主,然后交给宿主 IP 栈。此后来宾机的流量就是宿主的流量,路由查找按目的地址进行:在宿主上安装 0.0.0.0/1 和 128.0.0.0/1 的全隧道客户端会在毫不知情的情况下捕获它,根本不知道有 VM 存在。 Oracle 的 VirtualBox 网络章节 列出了这些默认值,如果你想在自己的版本上核对地址。

libvirt 的默认网络从 192.168.122.0/24 到达同一处,dnsmasq 在那里发放租约,宿主对该子网做地址伪装,因此来宾机的数据包再次抵达宿主的选路决策。例外是按进程而非按路由挑选流量的客户端。若分流隧道清单里只有浏览器,NAT 引擎就不在清单上,来宾机便在没有隧道的情况下离开这台机器,而宿主浏览器看上去仍被覆盖。同一个子网,结果相反。

桥接把来宾机放回物理 LAN 上属于自己的位置

桥接来宾机会得到一块连接真实网卡的虚拟网卡,获得自己的 MAC 地址,并从物理网络取得 DHCP 租约。它的默认网关是 LAN 路由器,而不是 hypervisor,它的帧通过那块网卡发到线上。宿主的隧道路由存在于宿主的表中,永远不会被查询,因为来宾机的帧从不进入宿主 IP 栈。

这把刀两面都切。mDNS 与 SMB 浏览开始可用,因为来宾机能看到 LAN 广播流量;来宾机也能被同一网段上的其他机器访问,中间没有宿主防火墙。桥接同样不是把来宾机塞进既有隧道的办法:把 VM 挂到宿主的 WireGuard 或 OpenVPN 接口上,VirtualBox 和 libvirt 都不会替你做。如果桥接来宾机必须走隧道,隧道必须在该 LAN 的某处终结。

终结争论的测试

在两种模式下做同样的检查,并把答案写下来:

  • 在宿主上, curl -s https://ifconfig.me 会给出公网 v4 地址。
  • 在来宾机中,同样的命令,再加 ip route 在 Linux 上或 Get-NetRoute -DestinationPrefix 0.0.0.0/0 在 Windows 上查看默认网关。
  • 解析器身份: resolvectl status 或 cat /etc/resolv.conf 查看来宾机指向 10.0.2.3 还是 LAN 服务器。
  • 地址族: ip -6 addr show scope global 以及一次 v6 请求,因为 v4 隧道对来宾机可能持有的全球 v6 地址只字未提。

公网地址相同说明来宾机走在宿主路径上。来宾机地址与 LAN 常规出口一致,说明它不在。人们会跳过的一步是对照:先关掉宿主隧道,再重发来宾机请求。如果来宾机仍能上网,那它从未依赖宿主隧道,不管模式名字暗示什么。在 NAT 模式下,关掉宿主隧道后来宾机应当断网,除非客户端被配置为允许隧道外流量,这一点本身就值得了解。

两种信号会说谎。ping 成功只能证明可达,不能证明出口路径。DNS 也可能由 10.0.2.3 代理经宿主解析器应答,而 TCP 走的是另一条路由,于是你就得到了页面能打开、登录却永远完不成的情形。

哪种模式适合这项工作

模式是一个路由决策,所以要按需求而不是按习惯来选:

RequirementMode that fits
Every guest flow must exit through the host tunnelNAT, with a host client that tunnels by route, verified from inside the guest
The guest needs its own VPN account or profileBridged, or NAT with a client running inside the guest, not both
The guest must serve SSH, SMB, or a dev server to the LANBridged; NAT needs a port forward, which does not change the outbound path
The guest must stay invisible to the LANNAT; a bridged guest is another host on the segment
The guest needs a real public IPv6 addressBridged; VirtualBox NAT mode has no IPv6 forwarding
Isolation from the LAN with access to the hostHost-only adapter, not NAT

第一行是常被绊倒的地方:NAT 加上宿主上的全隧道客户端确实覆盖来宾机,但前提是客户端按路由做隧道,且宿主的上行链路就是那条隧道。把客户端改成只允许浏览器的清单,覆盖就会消失,而且哪里都不报错。第四行是反向的失误:为了省事把来宾机桥接出去,它却开始在办公 LAN 上悄悄应答 SMB 或 SSH。

被算在 VPN 头上的故障

在 Windows 上,NAT 转换运行在 VirtualBox 的 VM 进程内,所以按应用分流的客户端需要把那个进程映像精确列入清单,而不是对虚拟机一概放行。当客户端更新重建进程白名单时,NAT 来宾机往往最先失去覆盖,报告读起来就像一台能到 LAN 却上不了网的 VM,而宿主一切正常。

桥接到 Wi-Fi 是另一个经典场景。接入点很少转发源 MAC 属于外部的帧,所以把桥接架在 wlan0 上要么直接失败,要么能用到 AP 的客户端隔离开始丢包为止。VirtualBox 仍然会在列表里提供无线网卡。如果宿主在 Wi-Fi 上而来宾机必须桥接,请安排测试,不要想当然。

第三是 MTU。桥接来宾机沿用 LAN 的 1500 字节 MTU,如果它自己跑 1420 的 WireGuard 客户端,大的 TLS 握手会卡住,而小请求能通过。NAT 下宿主隧道的 MTU 是宿主的问题,但来宾机仍相信 1500,带 don't-fragment 位的 UDP 照样掉进黑洞。

把模式记下来,任何网卡变更后都重新核对

记录模式、桥接所用的宿主接口、来宾机的默认网关、它的解析器,以及 v4 与 v6 出口地址。正是这份记录让下一次故障变得便宜:笔记本从扩展坞切到 Wi-Fi 时,桥接来宾机会跟着桥接到另一块物理网卡,并可能重新租到另一个子网;而 NAT 来宾机保留 10.0.2.x 地址,悄悄继承了一条不同的上行链路。

关掉工单之前,请逐条确认:

  • 来宾机出口地址与宿主出口地址不同,说明它不在宿主隧道上。
  • 解析器是 10.0.2.3 代理或 LAN 服务器,而且你知道是哪一个应答的。
  • 如果隧道设计只做 v4,来宾机里不存在全球 v6 地址。
  • 宿主隧道关闭时来宾机停止上网,或者你现在已经知道它从未依赖过隧道。
  • 来宾机看不到它本不该看到的 LAN 广播流量。

相关文章