96d44569fd
- proto.py: 帧协议 [type][req_id][len][payload], req_id 多路复用 - client.py: TLS 长连 + AUTH + 断线自动重连(全标准库, OpenWrt 可跑) - proxy_http.py: 本地 HTTP 透明代理(明文 80 走通道, 失败回 502) - proxy_dns.py: DNS 分流(内网域 -> 学校 DNS, 公网域 -> 云通道递归) - server.py: 云端 AUTH + HTTPS 套壳(统一 UA, 剔 hop-by-hop)+ DNS 递归 - deploy/hook.sh: REDIRECT 规则(内网段放行, 认证 eportal 绝不走通道) - 30 个新单测(40 总)+ smoke_channel 端到端冒烟 PASS
47 lines
1.4 KiB
Python
47 lines
1.4 KiB
Python
"""DNS 报文解析(纯函数, 只取分流需要的字段)。
|
|
|
|
客户端 query 原样透传(不重新生成 ID): 本地 -> 云端 -> 上游递归,
|
|
响应沿原路返回, ID 全程不变, 客户端按 (ID, question) 匹配天然成立。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import struct
|
|
|
|
DNS_HEADER_LEN = 12
|
|
|
|
|
|
def parse_qname(pkt: bytes) -> str | None:
|
|
"""提取 query 的 question 域名(小写, 无尾点)。
|
|
|
|
只处理标准 query(12B 头 + 一个 question, qname 无压缩指针)。
|
|
畸形/非 query 返回 None(调用方按"不分流"处理)。
|
|
"""
|
|
if len(pkt) < DNS_HEADER_LEN + 5:
|
|
return None
|
|
flags = struct.unpack("!H", pkt[2:4])[0]
|
|
qdcount = struct.unpack("!H", pkt[4:6])[0]
|
|
if qdcount != 1:
|
|
return None
|
|
i = DNS_HEADER_LEN
|
|
labels: list[str] = []
|
|
end = len(pkt)
|
|
while i < end:
|
|
length = pkt[i]
|
|
if length == 0:
|
|
break
|
|
if length & 0xC0: # 压缩指针(应答里才出现, query 不应有)
|
|
return None
|
|
i += 1
|
|
if i + length > end:
|
|
return None
|
|
try:
|
|
labels.append(pkt[i : i + length].decode("ascii").lower())
|
|
except UnicodeDecodeError:
|
|
return None
|
|
i += length
|
|
else:
|
|
return None # 没遇到 0 结尾
|
|
if i + 4 > end: # qtype + qclass
|
|
return None
|
|
return ".".join(labels)
|