Android 上的 OpenVPN 没有隧道地址就无法添加路由
Android 的 OpenVPN 客户端在手机拿到隧道地址之前无法添加路由。看清谁在分配地址、哪些 pull 过滤器把它丢掉,以及什么时候需要手写。
你的 Android OpenVPN 配置无法添加路由,是因为手机在虚拟接口上还没有地址,路由也就没有可指向的目标。路由只回答数据包该往哪里走。隧道地址回答的是手机在隧道里是谁,客户端必须先在接口建立之前提供它。请按顺序走完下面四项检查,并在第一次失败处停下,因为在地址存在之前就改路由,只会把真正的问题藏起来。
按客户端的执行顺序读这个失败
Android 的 VpnService.Builder 把两个概念分得很清楚:addAddress() 把地址放到应用正在构建的接口上,addRoute() 添加目的前缀,而 establish() 在至少添加一个地址之前拒绝创建接口。OpenVPN 客户端会把配置和服务器下发的选项精确映射到这些调用上,所以一次从不解析地址的连接根本建不起接口。
这就是为什么你读到的那行日志可能在说路由,而原因却在前一步。只有接口存在之后,客户端才会走到路由阶段;没有地址,路由调用要么立即失败,要么根本不会执行。当日志说无法添加路由时,请从地址查起,而不是从路由语法查起。
检查 1:服务器在回复里放地址了吗?
在走路由的 OpenVPN 服务里,地址来自服务器地址池或按客户端分配,形态由拓扑决定。在 OpenVPN 2.4 或更新的服务器上,子网拓扑的服务大致是这样:
topology subnet
server 10.8.0.0 255.255.255.0
client-config-dir ccd
在这种形态下,服务器下发的内容类似 ifconfig 10.8.0.6 255.255.255.0。更老的 topology net30 部署则会下发对端地址,例如 ifconfig 10.8.0.6 10.8.0.5,所以一个在两套模型之间搬过的配置可能看起来没问题,却带着错误的网络掩码。
在客户端连接日志里找下发选项集合。如果连一行 ifconfig 都没有,那就是服务器从未下发地址,或者客户端拒绝了该选项。在使用 client-config-dir 的服务器上,按证书 common name 命名的文件必须存在并带有分配:
# ccd/phone-anna
ifconfig-push 10.8.0.6 255.255.255.0
检查 2:客户端留住了拿到的地址吗?
过滤器开得太宽是第二个常见原因。pull-filter 在 OpenVPN 2.4 及更新版本里存在,当多个过滤器命中同一个选项时,最后命中的那个生效,所以末尾的 catch-all 会把地址悄悄删掉而不报错:
# removes every pushed option, including the address
pull-filter ignore "ifconfig"
pull-filter ignore "route"
把过滤器收窄到你确实想去掉的那个选项,地址保持不动:
# keeps the pushed address, drops one unwanted LAN route
pull-filter ignore "route 10.60.0.0"
关掉所有 pull 这类建议通常来自只想去掉一条路由的人。route-nopull 是更窄的开关:它保留 ifconfig 和其他下发选项,只拒绝下发的路由和 DHCP 类选项。确实想要这个效果时才用它,地址的问题要单独回答。
检查 3:这个地址该由你手写吗?
只有运营商把值交给你时,手工写地址才算正当。如果你自己写,地址必须来自 VPN 客户端子网、在活动会话中唯一,并与服务器运行的拓扑相符。照抄桌面网桥或远程 LAN 上某台主机的地址则两头都不成立:手机会占用一个别处已存在的地址,网关也没有回程路径。
路由里的远程 LAN 前缀是目的地,从来不是手机的身份。针对 10.60.0.0/24 的路由只表示经隧道可达该网段;它没有说手机该持有哪个 10.60.0.x 地址,而手机一个都不该持有。静态分配属于服务器一侧,那里不会与动态池冲突。
同一身份上的两个会话会暴露同样的冲突。如果同一张证书连了两次,服务器给两边下发同一个固定地址,第二个会话就重复了第一个;解决办法是分开身份或分开分配,而不是在客户端改配置。
检查 4:是不是有别的东西占着隧道槽?
Android 同一时间只允许一个活动 VPN 服务。另一个 VPN 应用,或者为已经失效的配置留下的始终开启 VPN 条目,都可能在你客户端调用 establish() 之前占住这个位置。于是配置有效、接口却永不出现,所以在动配置之前先断开另一个 VPN、关掉始终开启,再重连。
坚持以太网桥接的服务是另一条死路。非 root 的 Android 应用只能建立 IP 隧道,不能建立桥接的以太网段,所以你写什么地址都不会让 dev tap 服务器工作;必须由运营商提供走路由的服务。报告问题时要带上客户端名称与版本、下发的选项集合、拓扑,以及地址本应出现的那个具体步骤。
如果四项检查全部通过,地址存在、唯一且掩码正确,路由问题就回到常规判断:服务器授权哪些前缀,客户端该如何限定范围。在改动别的东西之前,先把隧道地址、网络掩码和拓扑记下来,因为运营商正是拿这三个值与服务器侧对照。