一台公网主机同时承载 VPN 与网站:如何划分角色
一个公网 IP 可以终止隧道、提供 HTTPS 并连通两个私有局域网。先确定地址、每个端口和转发策略归谁管。
一台机器可以同时满足三种需求:终止隧道、应答 HTTPS,以及在两个私有局域网之间转发。这样做是否合理,取决于三件可以在纸上定下的事:公网地址归谁管、每个端口与协议的组合归谁管、哪个进程被允许转发数据包。这三点对了,共用主机就平平无奇;错了,你就得用一个周末去伺候一个能干净启动却从不回应的服务。
下面是一套决策流程,而不是施工单。每一步都以一项检查收尾,用来证明刚加入的角色确实被隔离。
先选定地址的归属,再谈端口
隧道的终结点与 Web 服务器可以共用一个内核;同一时刻共用一个地址、端口和协议,才是失败的原因。WireGuard 通常占用 UDP 51820,OpenVPN 占用 UDP 1194 或 TCP 443,Web 服务器占用 TCP 443。真正难受的是 HTTPS 旁边那个跑在 TCP 443 上的 OpenVPN:两者都想要同一个套接字,内核先绑定的那个拿走。只有在前端放一个解复用器,例如 sslh 或能识别 TLS 的代理,才能让它们共用这个端口,而它同时引入一个组件,其故障表现和防火墙问题一模一样。
动手之前先看归属:ss -lntup 会打印每个监听套接字的本地地址、端口和进程。出现两行 0.0.0.0:443 就说明第二个服务根本没绑上,它的 init 脚本再欢快也没用。桥接同样会改变归属。当 eth0 加入 br0 之后,IP 会挪到 br0 上,所有点名 eth0 的防火墙规则都会失效,所以要把服务绑在网桥上,并用 ip -br addr 确认。
同一条归属规则也解释了虚拟机上出现的怪事。跑在同一局域网里某台客户机内部的隧道服务器,保留的是客户机的地址,因此发往宿主机自身地址的连接留在宿主机上,客户机的隧道永远看不到它们,无论客户端配置怎么说。
在两个局域网之间路由,或对一侧做 NAT
互相看不见的两个局域网可以各自拨号到公网主机,由它在其间转发。这种路由式设计需要三样东西齐备:开启转发、指向每个远端网段的路由,以及允许中转流量的转发策略。sysctl -w net.ipv4.ip_forward=1 覆盖第一项,ip route add 10.20.0.0/24 dev wg1 覆盖第二项,而一条形如 nft add rule inet filter forward iifname "wg0" oifname "wg1" accept 的 nftables 规则覆盖第三项。
源 NAT 更好装,更难查。伪装会改写客户端地址,于是远端看到的是中心而不是真实主机,它的访问规则和日志也就不再能用于追责。两个网络都是你的,就做路由。有一侧属于不会加回程路由的第三方,NAT 就是本周能上线的方案。无论如何,把地址规划写下来,因为两个重叠的 /24 靠任何配置都路由不了。
一台机器充当笔记本的上网出口,并不妨碍它同时终止入站隧道;妨碍的只有地址重叠,这也正是地址规划才是关键文档的原因。
在停掉两个角色的情况下测试拆分
有用的测试是减法:停掉两个角色,确认第三个仍然应答。隧道服务关掉时,Web 请求应当能完成;Web 服务器停掉时,隧道握手应当能完成。如果两者都只有在全部运行时才工作,你就有了意外依赖,比如共用的防火墙表、一条跑偏的路由,或者一个指向错误地址的名字。
三条命令把这项测试落到实处。curl -sk --resolve example.test:443:203.0.113.10 https://example.test/ 证明本地监听在没有外部 DNS 时也能应答,wg show 在客户端侧证明握手完成,tcpdump -ni any port 51820 在中心侧把路由问题和密钥问题分开。抓包里始终不出现的握手,是在到达主机之前就死了:运营商防火墙、错误的公网地址,或者途中的 NAT 规则。
对转发出去的局域网重做一次减法。断开一个对端时,跨中心的 ping 应当失败;重新接上后,ping 应当在不碰任何规则的情况下恢复。只有重启隧道才恢复的 ping 指向的是策略而不是可达性,那是另一种修法。
减法过程中要让规则集保持可读。nft list ruleset 应当为每个真正需要的方向显示一条放行规则;而一条全部放行的转发链不是隔离,只是能跑的演示。
只有 IPv6 的接入线路与 IPv4 服务
有些接入线路只给 IPv6 可达性,而服务仍要通过 IPv4 发布。隧道本身走的是仍能通流量的那个协议族,发布出去的服务则落在隧道内的地址上。这反过来了通常的假设,也会悄悄弄坏监控:为 IPv4 路径写的防火墙规则或可用性检查,现在盯的是一个什么都不承载的接口。
逐个协议族单独测试:curl -4 和 curl -6 都要用,因为一次成功并不能说明你没试过的那条路。端口归属不随协议族改变,监听器却会变:ss -lntup 会把 IPv4 与 IPv6 套接字列成不同的行,而 0.0.0.0:443 并不覆盖 [::]:443,除非进程要求了双栈套接字。
在一个协议族上应答、在另一个上拒绝的服务,通常是监听器的问题,而不是路由问题。在宣布设计完成之前,确认每个角色都拥有自己的地址或端口、转发已开启且被允许,并且在另外两个停掉时每个角色仍能应答。