HttpDns原理及对比
HTTPDNS 原理简述
HTTPDNS 是一种利用 HTTPS 协议(而非传统的 UDP/53)向特定的 DNS 服务器发起域名解析请求的技术。
其核心机制是 :
绕过 LocalDNS:客户端直接通过 IP 地址向 HTTPDNS 服务器请求域名的解析结果(通常为 JSON 格式)。这一做法彻底规避了运营商 LocalDNS 可能存在的域名劫持、缓存污染以及跨网调度不准等问题,确保 App 能够获取最精准、最快的业务服务器 IP。
与 DoH / DoT 的区别
虽然三者都利用加密通道传输 DNS 数据,但设计目标与应用场景截然不同:
HTTPDNS (App 专用):
核心目标:“高可用与精准调度”。主要由云服务商提供,由 App 业务层主动接入。它不仅可以防劫持,还可以用户引导至最优节点。
协议形态:通常是 RESTful API,返回 JSON 等自定义格式。
DoH (DNS over HTTPS) / DoT (DNS over TLS) (标准协议):
核心目标:“隐私与安全”。属于 IETF 标准,主要为了防止 DNS 查询被监听或篡改。通常由 操作系统(Android/iOS)或 浏览器 底层支持。
协议形态:传输的是标准 DNS 协议报文,只是给它套了一层 HTTPS 或 TLS 的“外壳”。
与LocalDNS的区别
---
config:
look: neo
theme: neutral
---
sequenceDiagram
participant App as 手机App
participant LocalDNS as 运营商LocalDNS/系统DNS
participant HTTPDNS as HTTPDNS服务器
participant Server as 业务服务器(Target)
note over App, Server: 场景:LocalDNS 被劫持/污染
rect rgb(255, 230, 230)
note right of App: 传统 LocalDNS 路径
App->>LocalDNS: 1. 查询 offertoday.com (UDP/53)
LocalDNS-->>App: 2. 返回假IP (2.2.2.2)
App->>App: 3. 尝试连接 2.2.2.2
App--xServer: 4. 连接超时/失败
end
rect rgb(230, 255, 230)
note right of App: HTTPDNS 路径
App->>HTTPDNS: 1. HTTP请求: /resolve?host=offertoday.com
note right of HTTPDNS: 此请求走 TCP/80 或 443<br/>绕过 UDP 劫持
HTTPDNS-->>App: 2. JSON返回真IP (8.212.x.x)
App->>Server: 3. 直连真IP (Host头保持域名)
Server-->>App: 4. 业务数据交互成功
end
具体实现
![image]()
客户端差异
---
config:
look: neo
theme: neutral
---
graph LR
%% ==========================================
%% 统一入口
%% ==========================================
Start(App 发起请求<br/>https://api.domain.com):::start --> Split{操作系统?}
%% ==========================================
%% iOS 路径:侵入式 URL 替换
%% ==========================================
subgraph iOS_Path [iOS: 上层 URL 替换模式]
direction TB
iOS_Step1[业务层代码]:::blue
iOS_Action1[HTTPDNS 获取 IP]:::blue_light
iOS_Action2[<B>URL 替换</B><br/>域名被替换为 IP<br/>https://1.2.3.4/...]:::orange
iOS_Fix1[<B>SNI 修复</B><br/>需处理 HTTPS 握手]:::red
iOS_Fix2[<B>Host 修正</B><br/>Header 补填域名]:::red
iOS_Result[<B>建立连接</B>]:::blue_solid
iOS_Step1 --> iOS_Action1
iOS_Action1 --> iOS_Action2
iOS_Action2 --> iOS_Fix1
iOS_Fix1 --> iOS_Fix2
iOS_Fix2 --> iOS_Result
end
Split -->|Android| And_Step1
Split -->|iOS| iOS_Step1
%% ==========================================
%% Android 路径:优雅的原生注入
%% ==========================================
subgraph Android_Path [Android: 底层 DNS 注入模式]
direction TB
And_Step1[OkHttp 网络库]:::green
And_Action1[<B>DNS 注入</B><br/>拦截 DNS 解析过程]:::green_light
And_Action2[HTTPDNS 获取 IP]:::green_light
And_Result[<B>建立连接</B><br/>URL 保持域名不变<br/>SNI/Host 自动正确]:::green_solid
And_Step1 --> And_Action1
And_Action1 --> And_Action2
And_Action2 --> And_Result
end
%% ==========================================
%% 样式定义 (追求简洁美观)
%% ==========================================
classDef start fill:#333,stroke:#333,stroke-width:2px,color:#fff,rx:5,ry:5;
%% Android 绿色系 (代表顺畅、自动)
classDef green fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;
classDef green_light fill:#c8e6c9,stroke:#2e7d32,stroke-width:1px,stroke-dasharray: 5 5;
classDef green_solid fill:#4caf50,stroke:#2e7d32,stroke-width:2px,color:#fff;
%% iOS 蓝色/橙红色系 (代表手动、风险)
classDef blue fill:#e3f2fd,stroke:#1565c0,stroke-width:2px;
classDef blue_light fill:#bbdefb,stroke:#1565c0,stroke-width:1px,stroke-dasharray: 5 5;
classDef orange fill:#fff3e0,stroke:#ef6c00,stroke-width:2px;
classDef red fill:#ffebee,stroke:#c62828,stroke-width:2px,color:#b71c1c;
classDef blue_solid fill:#2196f3,stroke:#1565c0,stroke-width:2px,color:#fff;
%% 连接线
linkStyle default stroke:#666,stroke-width:2px;
差异主要源于操作系统底层网络栈的开放程度。
简单来说:Android 允许你“修改 DNS 解析过程”,而 iOS 强迫你“修改请求 URL”。
Android 的优势:OkHttp Dns 方法
在您提供的 Android 实现中,核心在于 OkHttp 允许开发者通过 .dns(new MyHttpDns()) 注入自己的解析逻辑。
非侵入性:上层业务代码只管请求 https://api.domain.com,完全不需要知道 HTTPDNS 的存在。
原生支持 HTTPS:因为 OkHttp 知道原始域名是 domain.com,虽然它底层连接的是 HTTPDNS 返回的 IP,但它在 TLS 握手时,会自动把原始域名填入 SNI 字段。这彻底消除了 iOS 上最头疼的 SNI/证书 兼容性问题。
Happy Eyeballs (RFC 8305):这是一个工业级的连接尝试算法。当 HTTPDNS 返回了 IPv6 和 IPv4 两个地址时,OkHttp 不会死等,而是优先尝试 IPv6,如果稍有延迟立即并发尝试 IPv4,谁先通就用谁。这保证了在“双栈网络”下的极速体验。
iOS 的的困境:URL 替换
相比之下,iOS 的 NSURLSession 没有开放 DNS 注入。
后果:开发者必须在发起请求前,自己手动把 URL 里的域名抠掉换成 IP。
副作用:一旦 URL 变成了 IP,系统网络库就“失忆”了,它不知道这个 IP 对应的原本域名是谁,导致 Host 头缺失、SNI 字段错误。开发者必须手动把这些信息补回去,就像在给系统打补丁。
![image]()
测试方法
见后文