You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 15:42:17