Android VPN应用中如何绕开VPN进行DNS解析?
解决OkHttp在VPN活跃时绕过VPN执行DNS查询的方案
要让OkHttp的DNS查询绕开VPN,核心是绕过系统默认的DNS解析逻辑,改用绑定到非VPN网络的方式执行域名解析,同时配合套接字绑定确保整个请求都不走VPN。具体实现步骤如下:
第一步:获取非VPN的网络实例
先通过系统服务筛选出没有被VPN占用的网络,后续所有的DNS解析和套接字连接都绑定到这个网络:ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); Network nonVpnNetwork = null; if (connManager != null) { for (Network network : connManager.getAllNetworks()) { NetworkCapabilities caps = connManager.getNetworkCapabilities(network); // 过滤掉VPN传输类型的网络 if (caps != null && !caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) { nonVpnNetwork = network; break; } } }第二步:给OkHttp配置自定义DNS解析器
OkHttp允许自定义Dns接口实现,在解析域名时直接使用非VPN网络的getAllByName方法,这个方法会强制在指定网络上执行DNS查询,完全绕开VPN的路由:OkHttpClient okHttpClient = new OkHttpClient.Builder() .dns(hostname -> { if (nonVpnNetwork == null) { // 无可用非VPN网络时,降级到系统默认解析 return Arrays.asList(InetAddress.getAllByName(hostname)); } // 用非VPN网络执行DNS解析 InetAddress[] addresses = nonVpnNetwork.getAllByName(hostname); return Arrays.asList(addresses); }) .build();第三步:额外配置套接字绑定(可选但推荐)
为了确保整个HTTP请求的套接字也不走VPN,需要自定义SocketFactory,让创建的套接字绑定到非VPN网络:OkHttpClient okHttpClient = new OkHttpClient.Builder() .dns(hostname -> { if (nonVpnNetwork == null) { return Arrays.asList(InetAddress.getAllByName(hostname)); } return Arrays.asList(nonVpnNetwork.getAllByName(hostname)); }) .socketFactory(new SocketFactory() { @Override public Socket createSocket() throws IOException { if (nonVpnNetwork == null) { return new Socket(); } // 创建绑定到非VPN网络的Socket return nonVpnNetwork.createSocket(); } @Override public Socket createSocket(String host, int port) throws IOException { Socket socket = createSocket(); socket.connect(new InetSocketAddress(host, port)); return socket; } // 其余重载方法可直接调用默认Socket的实现,示例如下: @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { Socket socket = createSocket(); socket.bind(new InetSocketAddress(localHost, localPort)); socket.connect(new InetSocketAddress(host, port)); return socket; } @Override public Socket createSocket(InetAddress host, int port) throws IOException { Socket socket = createSocket(); socket.connect(new InetSocketAddress(host, port)); return socket; } @Override public Socket createSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException { Socket socket = createSocket(); socket.bind(new InetSocketAddress(localHost, localPort)); socket.connect(new InetSocketAddress(host, port)); return socket; } }) .build();注意事项
- 权限要求:需要在Manifest中声明
ACCESS_NETWORK_STATE权限,Android 6.0及以上需动态申请该权限。 - 版本兼容:
Network.getAllByName()和Network.createSocket()是API 24(Android 7.0)引入的,若需兼容更低版本,需添加版本判断并做降级处理。 - 异常处理:务必处理
nonVpnNetwork为null的情况,避免无可用非VPN网络时出现崩溃。
- 权限要求:需要在Manifest中声明
内容的提问来源于stack exchange,提问作者Mark Smith
相关产品推荐
相关产品推荐

