
web-to-app 自定义 DNS 配置实战从 DoH 提供商选择到 strict/automatic 模式的源码解析web-to-app 允许为每个生成的应用独立覆盖 DNS 解析路径包括完整的 DNS-over-HTTPSDoH方案。本文以仓库文档 custom-dns.md 为主体结合 DnsManager.kt、GeckoViewEngine.kt 与 DnsConfigCard.kt 的实现讲清楚「编辑通用配置」编辑器中自定义 DNS卡片的每个选项dnsMode、dnsConfig、engineType在底层是如何生效的帮助你在弱网、公共 Wi-Fi 或对解析隐私有要求的场景下正确配置并验证 DNS 覆盖行为。功能入口与整体选项自定义 DNS 的入口在「编辑通用配置」编辑器见 edit-common-config 目录中的自定义 DNS卡片。原文档列出的四个核心选项与源码中的对应关系如下文档选项源码字段说明DNS 模式dnsModeSYSTEM默认或 DoH 提供商 / 自定义端点DoH 提供商DnsConfig.providerCloudflare、Google、AdGuard、NextDNS、CleanBrowsing、Quad9、Mullvad 或自定义端点DNS 配置dnsConfig提供商专属设置含自定义端点、解析模式等引擎类型engineType系统 WebView 或 GeckoView该卡片同时暴露此项UI 层由 DnsConfigCard.kt 实现卡片顶部的总开关直接把dnsMode在SYSTEM与DOH之间切换enabled dnsMode ! SYSTEM开启后依次展开提供商选择区、自定义 DoH 端点输入框仅当provider custom时显示以及 DoH 解析模式区。DNS 模式dnsModeSYSTEM 与 DOHdnsMode是整个功能的总闸。取值只有两类语义SYSTEM默认走系统默认 DNS 解析。此时运行时不注册任何 DoH 端点并且会主动清理已缓存的解析结果。在 DnsManager.kt 的applyDnsConfig中可以看到这一分支当provider SYSTEM或effectiveDohUrl为空时直接调用clearDnsConfig()把dnsCache清空并复位状态。非SYSTEM以dnsConfig.effectiveDohUrl作为 DoH 端点接管解析。数据模型定义在 WebApp.ktdata class DnsConfig( val provider: String cloudflare, // 默认提供商 val customDohUrl: String , // 自定义端点provider custom 时生效 val dohMode: String automatic, // automatic | strict val bypassSystemDns: Boolean false, // 严格绕过系统 DNS val echEnabled: Boolean false // 是否开启 ECH ) { val effectiveDohUrl: String get() when (provider) { custom - customDohUrl else - DnsProvider.entries.find { it.key provider }?.dohUrl ?: } val echEffective: Boolean get() echEnabled effectiveDohUrl.isNotBlank() }两个值得注意的细节其一effectiveDohUrl是派生值——非 custom 提供商直接映射到枚举中内置的端点custom 则完全依赖用户填写的customDohUrl填错或留空时该端点视为空白、等同于 SYSTEM其二DnsConfig的默认provider是cloudflare也就是说用户只要打开总开关、不改动提供商默认就用 Cloudflare 的 DoH 端点。DoH 提供商与内置端点dnsConfigWebApp.kt 中的DnsProvider枚举给出了全部内置提供商及其默认端点这也是 UI 上提供商 Chip 列表的数据源DnsConfigCard.kt 按每行 3 个 Chip 排列provider key显示名内置 DoH 端点cloudflareCloudflarehttps://cloudflare-dns.com/dns-querygoogleGooglehttps://dns.google/dns-queryadguardAdGuardhttps://dns.adguard-dns.com/dns-querynextdnsNextDNShttps://dns.nextdns.io/cleanbrowsingCleanBrowsinghttps://doh.cleanbrowsing.org/doh/family-filter/quad9Quad9https://dns.quad9.net/dns-querymullvadMullvadhttps://dns.mullvad.net/dns-querycustomCustom用户自行填写customDohUrl选择custom后卡片会显示一个单行输入框标签与占位文案来自 Strings.kt 的多语言资源填入你自己的 DNS-over-HTTPS 端点 URL例如自建 resolver 的dns-query地址。端点必须是合法的 HTTPS URL解析时 DnsManager.kt 通过dohUrl.toHttpUrlOrNull()校验解析失败会抛出UnknownHostException(Invalid DoH URL: ...)。解析模式automatic 与 strictdohMode原文档「说明」部分提到“严格模式的 DoH 把所有 DNS 走 HTTPS自动模式按需回退”。源码中两种模式的行为边界非常清晰分引擎来看系统 WebView / 通用 OkHttp 路径DnsManager.kt 的resolveWithDoh先用okhttp3.dnsoverhttps.DnsOverHttpsincludeIPv6(true)向端点发起查询并带一层ConcurrentHashMap进程内缓存命中缓存直接返回。关键在于失败分支val result try { dohDns.lookup(hostname) } catch (e: Exception) { AppLogger.e(TAG, DoH resolution failed for $hostname, e) if (config.dohMode strict) { throw UnknownHostException(DoH resolution failed in strict mode: ${e.message}) } Dns.SYSTEM.lookup(hostname) // automatic静默回退系统 DNS }strictDoH 查询失败即抛UnknownHostException绝不回落到系统 DNS保证「所有 DNS 走 HTTPS」的强约束代价是端点不可用时域名解析直接失败automaticDoH 失败时回退Dns.SYSTEM.lookup(hostname)保证可用性优先。GeckoView 路径GeckoViewEngine.kt 的applyDohToRuntime把同一份配置映射到 Gecko 的 TRRTrusted Recursive Resolver设置val trrMode if (config.dohMode strict || config.bypassSystemDns) { GeckoRuntimeSettings.TRR_MODE_ONLY } else { GeckoRuntimeSettings.TRR_MODE_FIRST } runtime.settings.setTrustedRecursiveResolverMode(trrMode) runtime.settings.setTrustedRecursiveResolverUri(dohUrl)即strict或开启bypassSystemDns时使用TRR_MODE_ONLY仅 DoH否则使用TRR_MODE_FIRSTDoH 优先、按需回退。若dohUrl为空则显式TRR_MODE_OFF交回系统 DNS。UI 上这两种模式以自动/严格两个 Chip 呈现并在下方用状态横幅展示对应描述文案DnsConfigCard.ktstrict 显示为警告色调提示用户其强约束特性。进阶选项绕过系统 DNS 与 ECH原文档只列了四个选项但同一张卡片在「高级选项」分区还暴露了两个开关DnsConfigCard.kt与dnsConfig直接对应绕过系统 DNSbypassSystemDns默认 false开启后 DnsManager 会执行setupBypassSystemDns()日志标记 “all DNS queries will use DoH”如前所述它还会把 GeckoView 的 TRR 模式提升到TRR_MODE_ONLY效果等同于甚至严于strict。ECHEncrypted Client HelloechEnabled默认 falseechEffective echEnabled effectiveDohUrl.isNotBlank()即必须同时有有效 DoH 端点才生效。在 GeckoView 侧GeckoViewEngine.kt 会把一组network.dns.echconfig.*/network.dns.upgrade_with_https_rr/force_use_https_rr等偏好写入geckoview-config.yaml并在下一次 GeckoRuntime 创建时生效源码日志也明确提示 ECH “only takes effect from runtime creation”。卡片逻辑中还有两条保护开启 ECH 时若dnsMode SYSTEM会自动切到DOH若当前引擎不是 GeckoViewisGecko false会显示一条引擎警告横幅。另外DnsManager.isDohSupported()以Build.VERSION.SDK_INT Build.VERSION_CODES.PAndroid 9作为 DoH 能力判断的基准DnsManager.kt可以推断实际生效以 Android 9 环境为前提。引擎类型engineType两套引擎如何各自落地文档指出“引擎类型——这里也可选择浏览器引擎engineType系统 WebView 或 GeckoView”。从 WebViewManager.kt 的配置装配流程看两个引擎的 DoH 是在同一次配置中被下发、但走不同机制if (config.dnsMode ! SYSTEM) { dnsManager.applyDnsConfig(config.dnsConfig) com.webtoapp.core.engine.GeckoViewEngine.applyDnsConfig(config.dnsConfig) } else { dnsManager.clearDnsConfig() GeckoViewEngine.applyDnsConfig(DnsConfig(provider custom, customDohUrl )) }系统 WebViewDoH 主要通过 DnsManager.createDohOkHttpClient() 提供的 OkHttp 客户端.dns(dohDns)承接应用内基于 OkHttp 的网络请求解析结果进入内存缓存GeckoView通过 GeckoRuntime 的 TRR 设置原生接管浏览器自身的 DNS系统引擎上的 ECH 则与强制 HTTP/3 一样“搭载”在 MITM 桥上上游腿使用 Cronet其 Chromium 栈原生拉取 HTTPS 记录并加密 ClientHello SNIneedsMitmBridge tlsFingerprintEnabled || forceHttp3 || echUpstream决定是否需要启动该桥WebViewManager.kt 注释与逻辑。因此engineType的选择会影响 DoH/ECH 的实现路径同样一份dnsConfig在系统 WebView 下经由 OkHttp DoH及 Cronet 上游生效在 GeckoView 下经由 TRR 偏好生效这也是卡片上 ECH 行带有 Gecko 徽标、且非 Gecko 引擎出现警告横幅的原因。适用限制与关联配置结合源码可确认以下使用前提与限制配置前建议逐一核对与代理模式互斥WebViewManager.kt 中dohCanApplyViaLocalProxy config.dnsMode ! SYSTEM effectiveDohUrl.isNotBlank() config.proxyMode NONE即本地代理桥路径要求proxyMode NONE系统引擎上启用 SOCKS 上游代理时 ECH 会被忽略并有警告日志ECH ignored on system engine: SOCKS upstream proxy takes precedence。Hosts 映射是另一层覆盖原文档指出 Hosts 映射host → IP 覆盖不在本卡片配置而是在 高级设置 中管理源码中它同样在configureWebView里与 DoH 一起被装配hostsMappingEnabled config.proxyMode NONE。缓存语义DnsManager的解析结果缓存在进程内dnsCache且只在结果非空时写入切换配置或清空配置clearDnsConfig/clearCache会立即清空缓存避免旧端点的解析结果残留。端点格式custom 端点必须是可解析的 HTTPS URLtoHttpUrlOrNull()校验且所有内置端点均为标准dns-query风格路径自建端点建议保持同样的 GET 型 DoH 路径格式。小结一份可验证的配置路径把原文档的选项串成实操路径就是打开「编辑通用配置」编辑器的自定义 DNS卡片 → 总开关切到DOHdnsMode→ 从 7 个内置提供商中选一个或选 Custom 并填入端点dnsConfig.provider/customDohUrl→ 选择automatic可用性优先或strict全量走 HTTPS失败不回退→ 视需要在高级选项开启绕过系统 DNS 或 ECH → 如需更强的 DNS 控制能力可在同一卡片确认engineType系统 WebView 或 GeckoView两者会按各自机制应用同一份 DoH 配置。相关实现可分别对照 DnsManager.kt、GeckoViewEngine.kt、DnsConfigCard.kt 与 WebApp.kt 中的定义复核。创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考