-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdns_text.py
More file actions
99 lines (81 loc) · 3.5 KB
/
Copy pathdns_text.py
File metadata and controls
99 lines (81 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import asyncio
import aiohttp
import ssl
import dns.message
import dns.rdatatype
import base64
from typing import List
class DoHWireFormatResolver:
"""
遵循 RFC 8484 的 DNS-over-HTTPS 解析器(wire format)
一次调用同时获取 A 和 AAAA 记录
"""
def __init__(self, doh_url: str, ssl_context: ssl.SSLContext | bool | None = None):
self.doh_url = doh_url.rstrip('/')
self.ssl_context = ssl_context
self._session: aiohttp.ClientSession | None = None
async def _get_session(self) -> aiohttp.ClientSession:
if self._session is None or self._session.closed:
connector = aiohttp.TCPConnector(ssl=self.ssl_context)
self._session = aiohttp.ClientSession(
connector=connector,
timeout=aiohttp.ClientTimeout(total=10)
)
return self._session
async def _resolve_single(self, domain: str, rdtype: int) -> List[str]:
"""
执行单一类型的 DNS 查询(A 或 AAAA)
"""
query = dns.message.make_query(domain, rdtype)
wire_data = query.to_wire()
headers = {
"Content-Type": "application/dns-message",
"Accept": "application/dns-message",
}
session = await self._get_session()
async with session.post(self.doh_url, data=wire_data, headers=headers) as resp:
if resp.status != 200:
raise RuntimeError(f"DoH {dns.rdatatype.to_text(rdtype)} 查询失败,HTTP {resp.status}")
response_wire = await resp.read()
response = dns.message.from_wire(response_wire)
ips = []
for answer in response.answer:
if answer.rdtype == rdtype:
for rdata in answer:
ips.append(str(rdata))
return ips
async def resolve(self, domain: str) -> List[str]:
"""
同时获取 A 和 AAAA 记录,返回合并后的 IP 列表
"""
# 并行查询 A 和 AAAA
task_a = asyncio.create_task(self._resolve_single(domain, dns.rdatatype.A))
task_aaaa = asyncio.create_task(self._resolve_single(domain, dns.rdatatype.AAAA))
results = await asyncio.gather(task_a, task_aaaa, return_exceptions=True)
# 合并结果,忽略异常(例如某些域名没有 AAAA 记录)
ips = []
for result in results:
if isinstance(result, Exception):
# 可以选择打印日志,但对于无记录的常见错误静默处理
# print(f"查询异常(可能无此记录): {result}")
continue
ips.extend(result)
return ips
async def close(self):
if self._session and not self._session.closed:
await self._session.close()
async def main():
# 你的本地 dnscrypt-proxy DoH 地址
doh_url = "https://127.0.0.1:8443/dns-query"
# 测试时跳过 SSL 验证(生产环境请配置证书)
resolver = DoHWireFormatResolver(doh_url, ssl_context=False)
domains = ["baidu.com", "taobao.com", "qq.com", "google.com"] # google.com 通常有 AAAA
for domain in domains:
try:
ips = await resolver.resolve(domain)
print(f"{domain:12} => {', '.join(ips) if ips else '<empty>'}")
except Exception as e:
print(f"{domain:12} ❌ {e}")
await resolver.close()
if __name__ == "__main__":
asyncio.run(main())