Android VpnService在API 33以下实现路由排除的方案咨询
API 32及以下实现DNS流量绕过VPN代理
核心背景
你的需求是让DNS查询绕过仅支持TCP的VPN代理,其余流量全部走代理。在API 33+可以用excludeRoute快速实现,但API 32及以下无此方法,无需手动枚举大量路由段,可通过以下优雅方案解决。
优雅实现方案
方案:自动生成互补路由段
通过代码自动计算出除DNS服务器IP外的所有IPv4路由段,替代手动编写大量addRoute条目。核心思路是拆分0.0.0.0/0默认路由,排除目标DNS的IP段,将剩余路由添加到VPN配置中。
实现代码
首先定义一个路由工具类,用于计算互补路由段:
import android.net.IpPrefix; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Queue; public class IpPrefixUtils { public static List<IpPrefix> getComplementaryPrefixes(List<IpPrefix> excludePrefixes) { List<IpPrefix> result = new ArrayList<>(); Queue<IpPrefix> queue = new LinkedList<>(); try { queue.add(new IpPrefix(InetAddress.getByName("0.0.0.0"), 0)); } catch (UnknownHostException e) { e.printStackTrace(); return result; } for (IpPrefix exclude : excludePrefixes) { Queue<IpPrefix> tempQueue = new LinkedList<>(); while (!queue.isEmpty()) { IpPrefix current = queue.poll(); if (!current.contains(exclude)) { tempQueue.add(current); } else if (current.equals(exclude)) { continue; } else { List<IpPrefix> children = splitPrefix(current); for (IpPrefix child : children) { if (child.contains(exclude)) { queue.add(child); } else { tempQueue.add(child); } } } } queue = tempQueue; } result.addAll(queue); return result; } private static List<IpPrefix> splitPrefix(IpPrefix prefix) { List<IpPrefix> children = new ArrayList<>(2); int prefixLength = prefix.getPrefixLength(); if (prefixLength >= 32) { return children; } byte[] addrBytes = prefix.getAddress().getAddress(); int bitPos = prefixLength / 8; int bitOffset = 7 - (prefixLength % 8); // 生成第一个子前缀IP byte[] firstChildBytes = Arrays.copyOf(addrBytes, 4); firstChildBytes[bitPos] &= ~(1 << bitOffset); // 生成第二个子前缀IP byte[] secondChildBytes = Arrays.copyOf(firstChildBytes, 4); secondChildBytes[bitPos] |= (1 << bitOffset); try { children.add(new IpPrefix(InetAddress.getByAddress(firstChildBytes), prefixLength + 1)); children.add(new IpPrefix(InetAddress.getByAddress(secondChildBytes), prefixLength + 1)); } catch (UnknownHostException e) { e.printStackTrace(); } return children; } }
然后在VPN配置中使用该工具类:
import android.net.IpPrefix; import android.os.ParcelFileDescriptor; import android.net.VpnService; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Collections; // 初始化VPN Builder VpnService.Builder builder = new VpnService.Builder() .setMtu(1500) .addAddress("26.26.26.1", 24); // 目标DNS服务器IP String dnsIp = "8.8.8.8"; builder.addDnsServer(dnsIp); try { // 生成要排除的DNS前缀 IpPrefix dnsPrefix = new IpPrefix(InetAddress.getByName(dnsIp), 32); // 获取互补路由段(所有非DNS的IPv4路由) List<IpPrefix> includeRoutes = IpPrefixUtils.getComplementaryPrefixes(Collections.singletonList(dnsPrefix)); // 添加所有互补路由到VPN for (IpPrefix route : includeRoutes) { builder.addRoute(route.getAddress(), route.getPrefixLength()); } } catch (UnknownHostException e) { e.printStackTrace(); } // 建立VPN连接 ParcelFileDescriptor vpnInterface = builder.establish();
关键说明
- 该工具类会自动拆分
0.0.0.0/0为不包含目标DNS IP的路由段,无需手动编写大量路由条目。 - 支持多个DNS服务器:只需将所有DNS的
IpPrefix加入excludePrefixes列表即可。 - 可扩展支持IPv6:只需在工具类中添加IPv6地址的拆分逻辑。
- 路由数量可控:排除单个IPv4时,仅会生成约10个左右的路由段,性能影响可忽略。
内容的提问来源于stack exchange,提问作者Anton2319
相关产品推荐
相关产品推荐

