适用场景
DNS劫持是运营商、恶意软件或中间人通过篡改DNS解析结果将用户导向错误服务器的攻击手段。本API通过向阿里AliDNS、腾讯DNSPod、360安全DNS、Cloudflare四家公共DoH(DNS over HTTPS)服务商并发查询同一域名,对比IP与CNAME的一致性,自动识别以下异常:
- 运营商劫持:多家DoH返回IP不一致,其中一家被篡改。
- Hosts文件篡改:本地解析被重定向。
- 异常GeoDNS分流:不同地域指向不同CDN但IP归属异常。
- 跨域CNAME跳转可见:提取完整CNAME链,辅助判断CDN配置是否被篡改。
典型使用者包括:CDN运维人员验证全球解析一致性、安全工程师部署域名监控、桌面工具开发者提供DNS健康检查功能。
接口能力边界
| 特性 | 说明 |
|---|---|
| 并发查询 | 四家DoH服务器同时发起请求,总耗时取决于最慢服务器(通常<2s,Cloudflare在中国大陆可能超时) |
| 记录类型 | A(IPv4)和AAAA(IPv6)同时查询 |
| CNAME链路 | 提取到最终IP前的全部CNAME跳转 |
| TTL汇总 | 返回各服务器最小TTL,用于缓存时间评估 |
| 输入自动剥离 | 支持domain、https://domain、domain:port、domain/path,统一提取纯域名(最长253字符) |
| 风险分级 | 基于联盟投票算法:safe / safe_with_geodns / suspicious / hijack_likely / unknown |
| QPS限制 | 每账号5次/秒,超出返回429或限速错误 |
注意:Cloudflare服务器位于美国,在中国大陆网络环境下大概率超时,该服务器的超时不会被判定为劫持,仅标记为
timeout并计入success_count减少;风险算法只基于成功响应的服务器做交叉验证。
参数详解与鉴权
Query参数
| 参数名 | 必填 | 类型 | 描述 |
|---|---|---|---|
domain | 是 | string | 待检测域名,最大253字符。自动去除http://、https://、路径、端口号,仅保留域名部分。 |
示例:
domain=baidu.comdomain=https://www.taobao.com/page→ 实际检测www.taobao.comdomain=example.com:8080→ 检测example.com
Header参数
| 参数名 | 必填 | 类型 | 描述 |
|---|---|---|---|
X-API-Key | 否 | string | API密钥。若不传,使用匿名额度(通常QPS更低或有次数上限,具体以官方文档为准)。 |
推荐在生产环境中始终携带有效Key,以避免被动限流。
curl示例:请求与响应
以下示例使用环境变量$APIZERO_API_KEY存放密钥,检测域名www.taobao.com:
curl -sS \ -X GET \ -H "X-API-Key: $APIZERO_API_KEY" \ "https://v1.apizero.cn/api/dns-check?domain=www.taobao.com"成功响应(截取关键字段):
{ "code": 0, "data": { "domain": "www.taobao.com", "exec_ms": 312, "input": "www.taobao.com", "servers": [ { "key": "alidns", "name": "AliDNS", "status": "ok", "ipv4": ["59.82.121.163", "59.82.122.130"], "ipv6": [], "cname_chain": ["www.taobao.com.danuoyi.tbcache.com"], "latency_ms": 105, "ttl_min": 60, "error": null }, { "key": "dnspod", "name": "DNSPod", "status": "ok", "ipv4": ["59.82.121.163"], ... }, { "key": "china360", "name": "360", "status": "ok", ... }, { "key": "cloudflare", "name": "Cloudflare", "status": "timeout", "error": "Operation timed out after 6000 ms", "latency_ms": 6000 } ], "summary": { "risk_level": "safe", "risk_score": 95, "status_text": "DNS解析正常", "explain": "4 家 DoH 全部相互验证通过,未检测到劫持迹象。", "success_count": 3, "total_count": 4, "unique_ipv4_count": 8, "unique_ipv6_count": 0 }, "unique_ipv4": ["59.82.121.163", "59.82.122.130", ...], "unique_ipv6": [] }, "msg": "成功", "request_id": "mota-xxxx" }返回字段逐层解读
顶层(data)
| 字段 | 类型 | 说明 |
|---|---|---|
domain | string | 实际检测的域名(剥离后) |
exec_ms | number | 整体执行耗时(毫秒),从请求发出到所有DoH响应或超时 |
input | string | 用户传入的原始字符串(未剥离前) |
servers | array | 四家DoH服务器的独立结果 |
unique_ipv4 | array | 所有服务器返回的去重IPv4地址列表 |
unique_ipv6 | array | 去重IPv6地址列表 |
summary | object | 风险聚合信息 |
servers子项
每个服务器的独立对象包含:
| 字段 | 类型 | 说明 |
|---|---|---|
key | string | 服务器标识:alidns、dnspod、china360、cloudflare |
name | string | 可读名称 |
status | string | ok(成功)、timeout(超时)、error(其他错误) |
ipv4 | array | 该服务器返回的IPv4地址列表(可能为空) |
ipv6 | array | IPv6地址列表 |
cname_chain | array | CNAME跳转链(如["www.taobao.com.danuoyi.tbcache.com"]),若无则为空数组 |
latency_ms | number | 该DoH请求的延迟(毫秒),超时则为超时值(如6000) |
ttl_min | number | 该服务器返回的最小TTL(秒),用于缓存时效参考 |
error | string/null | 当status不为ok时,此处为具体错误描述 |
summary风险等级
| 字段 | 类型 | 说明 |
|---|---|---|
risk_level | string | 风险等级:safe(安全)、safe_with_geodns(安全但存在GeoDNS差异)、suspicious(可疑)、hijack_likely(高度疑似劫持)、unknown(信息不足) |
risk_score | number | 0–100的分数,越高越安全(95以上极安全) |
status_text | string | 简短状态描述 |
explain | string | 自然语言解释,直接说明是否检测到劫持 |
success_count | number | 成功返回ok的服务器数量 |
total_count | number | 总服务器数量(固定4) |
unique_ipv4_count | number | 全局去重IPv4数量(多服务器返回相同IP时只计1) |
unique_ipv6_count | number | 去重IPv6数量 |
风险算法简析:系统对比各服务器的
ipv4集合。若所有成功服务器返回的IP完全一致,判为safe;若存在部分差异但均在合理CDN范围内(如阿里云和腾讯云指向同一CDN的不同节点),可能判为safe_with_geodns;若某服务器返回的IP完全不同于其他服务器且延迟异常低(疑似本地缓存劫持),则判为hijack_likely。suspicious对应少量IP差异但无法确认为合法CDN。
常见错误与处理
| HTTP状态码 | code值 | 可能原因 | 建议处理 |
|---|---|---|---|
| 400 | 400 | domain参数缺失或格式无效(空字符串、超过253字符) | 验证输入长度,先剥离协议/路径 |
| 401 | 401 | X-API-Key无效或过期 | 检查密钥配置,从安全环境变量读取 |
| 429 | 429 | 超过QPS限制(5/s) | 引入本地节流(令牌桶/固定窗口)或请求间隔≥200ms |
| 500 | 500 | 服务端内部错误或DoH集体故障 | 重试2–3次,若持续失败降级为手动检查 |
| 503 | 503 | 临时过载 | 指数退避重试 |
注意:超时(timeout)是服务器层面的预期行为,不应视为错误;当success_count为0时,risk_level会是unknown,此时应建议用户更换网络环境后重试。
工程化注意事项
1. QPS控制与并发管理
API限制为5次/秒。若需要在单进程内频繁检测多个域名,建议使用线程池或异步队列限制并发数:
import asyncio import aiohttp API_KEY = "your-key" BASE_URL = "https://v1.apizero.cn/api/dns-check" async def check_domain(session, domain): params = {"domain": domain} headers = {"X-API-Key": API_KEY} async with session.get(BASE_URL, params=params, headers=headers) as resp: return await resp.json() async def batch_check(domains): sem = asyncio.Semaphore(5) # 控制并发≤5 async with aiohttp.ClientSession() as session: async def bounded_check(d): async with sem: return await check_domain(session, d) tasks = [bounded_check(d) for d in domains] return await asyncio.gather(*tasks) # 使用 results = asyncio.run(batch_check(["baidu.com", "taobao.com", ...]))2. 超时处理与重试策略
由于Cloudflare服务器经常超时(6000ms),整体请求可能在6秒左右。生产环境中可设置客户端超时为10秒。对于status为timeout的服务器不做重试,因为超时是网络环境决定的;但若整个请求超时或返回5xx,应重试:
import requests from time import sleep def robust_check(domain, max_retries=3): for attempt in range(max_retries): try: resp = requests.get( "https://v1.apizero.cn/api/dns-check", params={"domain": domain}, headers={"X-API-Key": API_KEY}, timeout=10 ) if resp.status_code == 200: return resp.json() elif resp.status_code in (429, 503): sleep(2 ** attempt) continue else: resp.raise_for_status() except requests.exceptions.Timeout: # 请求级别超时,重试 sleep(2 ** attempt) raise Exception("Max retries exceeded")3. 缓存策略
对于中低风险域名(如safe或safe_with_geodns),解析结果在TTL内通常是稳定的。可以通过ttl_min字段决定缓存时长:
- 若
ttl_min为60,则缓存60秒后重新检测。
4. 输入预处理
API会自动剥离协议头和路径,但建议客户端自行预处理以避免无效请求:
def extract_domain(url_or_domain): # 去除协议头和路径 import re domain = re.sub(r'https?://', '', url_or_domain) domain = domain.split('/')[0] if '/' in domain else domain domain = domain.split(':')[0] if ':' in domain else domain return domain5. 多服务器结果对比逻辑
不要只依赖单个服务器的响应;即使4家中3家返回相同IP,Cloudflare超时,也应以多数派为准(success_count≥ 2时结果可靠)。当success_count < 2时,建议提示“检测数据不足”。
参考文档
- DNS劫持检测API官方文档
- 原始接口说明(Markdown)