Files

100 lines
3.0 KiB
Python
Raw Permalink Normal View History

"""proxy_dns 降级路径实测(模拟云通道挂掉)。
验证两条降级链:
1. 通道失败 -> 直连 fallback_dns 解析成功(全屋 DNS 不断)
2. 通道失败 + 直连也失败 -> 回 SERVFAIL(客户端立即切备用 DNS)
用法: python3 scripts/test_dns_fallback.py
"""
from __future__ import annotations
import socket
import sys
import threading
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from channel.client import ChannelClient, ChannelError # noqa: E402
from channel.proxy_dns import DnsSplitProxy # noqa: E402
QUERY = bytes.fromhex(
"abcd01000001000000000000037777770674616f62616f03636f6d0000010001"
)
class DownClient(ChannelClient):
"""模拟云通道不可用(所有请求抛 ChannelError)。"""
def __init__(self) -> None:
super().__init__(host="127.0.0.1", port=1, token="test-stub")
def send_dns_query(self, raw_query: bytes) -> bytes | None:
raise ChannelError("simulated channel down")
def ensure_connected(self) -> None:
raise ChannelError("simulated channel down")
def ask(port: int, timeout: float = 6.0) -> bytes:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(timeout)
try:
s.sendto(QUERY, ("127.0.0.1", port))
resp, _ = s.recvfrom(4096)
return resp
finally:
s.close()
def rcode(resp: bytes) -> int:
return resp[3] & 0x0F if len(resp) >= 12 else -1
def case_fallback_ok() -> bool:
"""通道挂 + 直连上游可用 -> 应返回正常解析(RCODE=0)。"""
proxy = DnsSplitProxy(
DownClient(), listen_port=18053, school_dns="223.5.5.5",
internal_suffixes=[], fallback_dns="223.5.5.5",
)
threading.Thread(target=proxy.serve, daemon=True).start()
time.sleep(0.3)
resp = ask(18053)
ok = len(resp) > 12 and rcode(resp) == 0 and proxy.fallback_resolved >= 1
print(
f" 用例1 通道挂->直连降级: 响应 {len(resp)}B RCODE={rcode(resp)} "
f"fallback_resolved={proxy.fallback_resolved} -> "
f"{'PASS' if ok else 'FAIL'}"
)
return ok
def case_servfail() -> bool:
"""通道挂 + 直连上游也不可达 -> 应回 SERVFAIL(RCODE=2), 不静默丢弃。"""
proxy = DnsSplitProxy(
DownClient(), listen_port=18054, school_dns="192.0.2.1",
internal_suffixes=[], fallback_dns="192.0.2.1",
)
threading.Thread(target=proxy.serve, daemon=True).start()
time.sleep(0.3)
resp = ask(18054)
ok = len(resp) >= 12 and rcode(resp) == 2
print(
f" 用例2 通道+直连全挂->SERVFAIL: 响应 {len(resp)}B "
f"RCODE={rcode(resp)} -> {'PASS' if ok else 'FAIL'}"
)
return ok
def main() -> int:
print("[DNS 降级测试] 模拟云通道不可用")
r1 = case_fallback_ok()
r2 = case_servfail()
print(f"结果: {'全部 PASS' if r1 and r2 else '有 FAIL'}")
return 0 if (r1 and r2) else 1
if __name__ == "__main__":
raise SystemExit(main())