Flutter中使用dio时如何配置自定义DNS或DNS-over-HTTPS
Flutter 应用自定义DNS/DoH解析实现方案
Flutter 没有提供跨平台的全局修改系统默认DNS的API,普通移动端/桌面端应用也没有权限修改系统全局网络配置,但是可以在应用层完全接管域名解析流程,配合Dio实现和你给出的curl命令完全一致的DoH解析效果,不需要Root或越狱权限。
方案1:DoH解析 + Dio IP直连(跨平台通用,无原生依赖)
核心逻辑和curl的--doh-url参数逻辑完全一致:发起业务请求前,先调用你自己的DoH接口拿到目标域名的A/AAAA记录IP,之后请求直接发往解析到的IP,手动携带原域名的Host头,绕过系统默认DNS。
- 第一步:实现独立的DoH查询逻辑
单独写一个DoH查询的方法,不要复用业务Dio实例,避免出现解析死循环。标准DoH GET接口只需要传域名和记录类型参数,指定Accept头即可拿到JSON格式的解析结果:import 'package:dio/dio.dart'; // DoH专用Dio实例,不添加业务拦截器 final _dohClient = Dio(BaseOptions( baseUrl: 'https://foo.domain.com/dns-query', headers: {'Accept': 'application/dns-json'}, connectTimeout: Duration(seconds: 5), receiveTimeout: Duration(seconds: 3), )); Future<List<String>> dohResolve(String domain) async { final resp = await _dohClient.get('', queryParameters: { 'name': domain, 'type': 'A', // 需要IPv6就替换为AAAA }); // 从返回结果中提取IPv4地址 return (resp.data['Answer'] as List) .where((record) => record['type'] == 1) // A记录的类型值为1 .map((record) => record['data'].toString()) .toList(); } - 第二步:给业务Dio添加解析拦截器
在请求发起前完成DoH解析,替换请求目标地址为解析到的IP,同时保存原域名用于后续HTTPS证书校验:dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) async { final originalHost = options.uri.host; // 如果目标已经是IP地址,直接放行(比如DoH接口自身的请求) if (RegExp(r'^(\d{1,3}\.){3}\d{1,3}$').hasMatch(originalHost)) { return handler.next(options); } try { final ipList = await dohResolve(originalHost); if (ipList.isEmpty) throw Exception('无可用解析结果'); // 可自行添加IP测速逻辑,选择延迟最低的IP使用 final targetIp = ipList.first; // 替换请求目标host为解析到的IP options.uri = options.uri.replace(host: targetIp); // 手动设置Host头为原域名,保证服务端正确路由 options.headers['Host'] = originalHost; // 保存原域名到extra,供证书校验使用 options.extra['originalHost'] = originalHost; handler.next(options); } catch (e) { handler.reject(DioException( requestOptions: options, error: 'DoH解析失败: $e', )); } }, )); - 第三步:适配HTTPS证书校验
因为请求目标换成了IP,默认的证书校验会因为Host不匹配失败,需要修改校验逻辑,校验证书是否匹配原域名即可。注意:生产环境不要直接返回true跳过校验,会存在中间人攻击风险,必须实现证书域名匹配、证书链校验逻辑,测试阶段可临时放开:import 'dart:io'; (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { client.badCertificateCallback = (cert, ipHost, port) { // 生产环境此处需要实现完整的证书校验逻辑: // 1. 从当前请求的options中取出originalHost // 2. 校验证书的CN、SAN字段是否匹配原域名 // 3. 校验证书链合法性 return true; // 仅测试阶段使用 }; return client; };
方案2:使用自定义普通DNS服务器
如果不需要DoH,要直接指定自定义普通DNS服务器地址(比如自建的UDP DNS),可以直接替换上面的解析方法,用dnspub包实现DNS查询即可,后续拦截器、证书校验逻辑和方案1完全一致:
import 'package:dns/dns.dart'; final dnsClient = DNSClient( dnsServers: [ // 替换为你自己的DNS服务器地址 InternetAddress('1.1.1.1'), ], ); Future<List<String>> customDnsResolve(String domain) async { final result = await dnsClient.lookup(domain, RecordType.A); return result?.answer ?.where((r) => r.type == RecordType.A) .map((r) => r.data.toString()) .toList() ?? []; }
注意事项
- 建议给解析结果加本地缓存,缓存时间参考DoH/DNS返回记录的TTL值,避免每次请求都发起DNS查询增加延迟
- 该方案仅适用于Android、iOS、Windows、macOS等原生端,Web端受浏览器安全沙箱限制,应用层无法接管DNS解析逻辑
- 如果需要支持HTTP/2、HTTP/3,只需要替换对应的Dio HttpClient适配器,核心的解析替换逻辑不需要改动
内容的提问来源于stack exchange,提问作者Mahan
相关产品推荐
相关产品推荐

