两端同一子网:把远端 LAN 映射到空闲网段
客户端侧地址映射可以把重叠的远端 LAN 呈现在空闲网段之下,但只有部分客户端会实现它,而且返回路径仍然必须存在。
家里和办公室都使用 192.168.1.0/24,因此发往 192.168.1.1 的请求有两个可能的答案,客户端不问一声就选了一个。给正在运行的办公网络重新编号很少受欢迎,而路由器地址常常已经写死在打印机、摄像头和文档里。客户端侧地址映射可以改为把远端网络呈现在一个空闲网段之下,主机部分可以保持不变,于是 192.168.1.20 显示为 10.203.41.20。问题在于只有部分客户端实现了它,而且它对返回路径毫无帮助。
同一台主机的两种视图
映射保留主机部分,只改变网络部分。同一台机器于是依你所在的位置在两个地址上应答。动手配置之前把两列都写下来,可以避免日后误读日志和防火墙规则。
| The application dials | The remote device is |
|---|---|
| 10.203.41.1 | 192.168.1.1 |
| 10.203.41.20 | 192.168.1.20 |
家里的本地设备保留自己的 192.168.1.x 地址,这正是这个练习的意义。隔壁房间的打印机仍然可达,而办公室出现在一个不冲突的网段之下。
参数顺序就是全部诀窍
这个选项接受方向、网络与掩码,以及一个别名:第一个网络是客户端看到的,别名是服务器看到的。对于位于远端网关背后的资源,方向是目标形式。因为最后一个参数叫别名就把两个网络对调,正是造出一条能连上却什么都不映射的隧道的错误。
push "route 10.203.41.0 255.255.255.0"
push "client-nat dnat 10.203.41.0 255.255.255.0 192.168.1.0"
路由负责把客户端发往新网段的报文送进隧道;转换在途中重写目标。来自 192.168.1.20 的应答会被写回 10.203.41.20,从而让客户端上的套接字保持一致。单台主机用全长度掩码也是同样的道理,只要路由和映射描述的是同一个地址。
这是地址映射,不是端口映射。它无法把一台主机上的两个服务分开,而把 IP 地址嵌进载荷的协议仍然会看到真实地址。
映射不会造出返回路径
转换发生在客户端,所以服务器仍然必须把应答路由进隧道,防火墙也仍然必须放行。如果远端网关不知道如何到达隧道子网,请求来了,应答却从错误的门出去;映射只是让这种失败看起来像客户端的问题。
服务器侧的规则应当描述转换之后服务器上真实存在的地址,而不是客户端可见的网段。把别名抄进放行规则会白白浪费一个下午,因为那一侧没有任何报文携带它。
挑一个不与客户端所路由的其他任何东西冲突的别名网段:另一个 VPN、一个容器网络、第二个办公室。这里一旦冲突,原来的歧义就会换一个编号重新出现,而且更难发现,因为这些地址看起来陌生。
名字与载荷会穿过转换
解析到真正 192.168.1.20 的主机名会把浏览器送到本地设备而不是远端设备,无论路由表怎么说。私有 DNS 的应答必须指向客户端可见的网段,而 TLS 证书仍然携带应用期望的名字。要用名字而不是地址来测试,因为地址可能通,名字却在悄悄解析到错误的方向。当远端网关也服务那个区域时,它自己的记录同样需要客户端可见的网段。
重定向、内嵌 URL 以及在载荷中交出自身地址的应用,可能以任何报文映射都修不好的方式出问题。把它们当作应用问题,并在承诺无需重新编号之前据此估算工作量。
提交之前的三项检查
第一,确认客户端实现。Community OpenVPN 支持这个选项;OpenVPN 3 代码库把它列在不支持的特性之中,因此推给 Connect 风格客户端的配置可能直接忽略它。一条被接受却从不生效的指令比报错更糟,因为隧道看起来是健康的。
第二,用高详细度的短诊断窗口分别验证路由和转换,然后测试真实服务而不是 ping。第三,确认本地路由器和远端资源从客户端看去仍然可以区分,因为那正是最初的目标。
如果客户端做不了转换,就请网关一侧帮忙,或者接受重新编号。为同一个地址添加更多路由不是设计。