服务在机器上跑着,隧道也通着,可你桌上的浏览器依然超时。按通常意义说,什么都没坏:应用监听在网络命名空间内部,而那个协议栈对局域网不可见。它的回环地址属于命名空间,所以另一台机器上的客户端没有任何地址能到达那个端口。一次刻意的跳转,或者一对 veth,就能解决,而不必搬动主机的其余部分。

为什么局域网地址上没有任何回应

命名空间是一套完整的网络协议栈:接口、路由、防火墙规则,以及一个私有的回环。通过 ip netns exec 启动的进程只看到这套栈,别的都看不到,因此它绑定的地址只在命名空间内解析。主机自己的 127.0.0.1 完全是另一个回环。

这就是为什么主机上的 curl 会失败,而命名空间内的同一条命令却成功。应用本身是健康的;只是这个地址不存在于主机的协议栈里。

用一次 socat 跳转发布一个端口

socat 在用户空间把两套栈桥接起来,当只有单个 TCP 端口需要跨越时,这就够了。主机一侧在局域网监听,对每个被接受的连接,都在命名空间内启动第二个 socat,连接到应用的回路地址。内部进程到达应用的方式,与本地客户端完全一样。命名空间的路由没有任何改变,因此应用仍把隧道当作唯一出口。

socat TCP-LISTEN:9100,reuseaddr,fork EXEC:'ip netns exec vpn socat STDIO tcp-connect:127.0.0.1:9100',nofork

外部进程需要 root,或者进入命名空间的权限。若 VPN 断开,应用失去通路,发布的端口随之关闭失败——这正是你希望牢笼具备的行为。

当不止一个端口需要跨越时使用 veth 对

当多个端口或协议都要跨越时,或者当其他主机需要主动发起连接时,veth 对才是正确选择。一端留在主机里,另一端移入命名空间,一个很小的 /30 给两侧稳定的地址。随后一条主机路由把命名空间地址从这对接口上送出去。

ip link add veth0 type veth peer name veth1
ip link set veth1 netns vpn
ip addr add 10.77.0.1/30 dev veth0
ip link set veth0 up
ip netns exec vpn ip addr add 10.77.0.2/30 dev veth1
ip netns exec vpn ip link set veth1 up

除非你打算打破牢笼,否则不要添加经由主机的默认路由。隧道应始终是出口,所以从这对接口上只能到达已发布的服务地址。同时给命名空间它自己的解析器文件,因为 ip netns exec 读取 /etc/netns/vpn/resolv.conf,没有它,牢笼里的域名解析就是空的。

从另一台机器测试,而不是从主机

从第二台机器上测试,因为主机可以经由某些路径到达本地监听者,而那些路径对局域网毫无说明。先检查应用在命名空间内实际绑定到了什么,再从外部连接已发布的地址。在那里绑定所有地址并不会扩大牢笼,因为通配地址仍属于命名空间,局域网看不到它。

ip netns exec vpn ss -lntp

如果从主机连接成功、从局域网连接失败,缺的那一环是跳转或路由,而不是绑定地址。在命名空间内启动 OpenVPN,而不是把正在运行的 tun 设备搬进去,因为搬动接口会让它下线,还可能清掉你之后没法从外面修复的路由。把恢复命令放在控制台上,而不是牢笼里。

相关文章