Files
ykxmz/_tests/test_lan_path.py
xmz7891 b62ac865ea ykxmz 1.5.0:修复手表端 TLS 证书校验 + MTU 修复网络稳定性
被控端
- 修复 MTU 问题:夹 MSS + 路径 MTU 探测(公网可用率 48% -> 100%)
- 复用 HTTP 连接(免掉每次新建 TLS 的 30% 握手失败)
- 每分钟回头找局域网,自动切回快 400 倍的路径
- 画面心跳帧:静止时省流量,客户端仍知道流是活的
- 锁屏时给出可操作的提示

手表端
- 内置 GTS Root R4 / GlobalSign Root CA,修 SSLHandshakeException
- 连接错误翻译成人话(证书/时间/解析/超时分别提示)
- 经中转连上后用服务器报的地址刷新已配对记录
- 新增局域网连接页面、服务器延迟检测

服务器端
- 支持 chunked 请求体(修每 16 秒一次的连接错乱与大面积 502)
- 网络调优脚本 net-tune.sh(开机自动应用)
- 管理页:批量删除、网络体检、下载区
- 隧道改 HTTP/2 + 双连接

文档
- 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
2026-10-06 19:01:05 +08:00

195 lines
7.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""验证局域网直连这条路是通的,以及安卓端新加的东西在。
顺序很关键(踩过):
之前是先广播、再起测试实例。单独跑时本机真有个在跑的 agent,
广播能搜到,测试过;但在完整回归里,套件会先 Stop-Strays
把 agent 杀掉,广播就一台都搜不到 —— 于是失败。
改成:**先起测试实例(它自己会绑 UDP 8766 应答),再广播**。
这样不依赖环境里有没有别的 agent。
如果 8766 被别的 agent 占着(单独跑时就是这样),
退而验证"搜到的那台能直连"。
"""
from __future__ import annotations
import json
import re
import socket
import sys
import tempfile
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "windows"))
sys.path.insert(0, str(ROOT / "_tests"))
MAIN = (ROOT / "android" / "src" / "com" / "lanremote" / "watch"
/ "MainActivity.java")
passed = 0
failed = 0
def check(name, ok, detail=""):
global passed, failed
if ok:
passed += 1
print(" [PASS] " + name)
else:
failed += 1
print(" [FAIL] " + name + " " + str(detail))
def java_method(src: str, signature: str) -> str:
"""按花括号配平取 Java 方法体。"""
index = src.find(signature)
if index < 0:
return ""
start = src.find("{", index)
if start < 0:
return ""
depth = 0
for pos in range(start, len(src)):
if src[pos] == "{":
depth += 1
elif src[pos] == "}":
depth -= 1
if depth == 0:
return src[start:pos + 1]
return ""
def free_port() -> int:
with socket.socket() as probe:
probe.bind(("127.0.0.1", 0))
return probe.getsockname()[1]
def tcp_ok(host, port, timeout=6.0) -> bool:
sock = socket.socket()
sock.settimeout(timeout)
try:
sock.connect((str(host), int(port)))
return True
except Exception: # noqa: BLE001
return False
finally:
try:
sock.close()
except Exception: # noqa: BLE001
pass
def main() -> int:
from lanremote import discovery
from lanremote.config import Store
from lanremote.server import RemoteServer
from stub_support import StubUI, make_stub_actions
print("一、局域网发现的协议本身")
obj = json.loads(discovery.build_reply("测试电脑", 8765, "1.4.0")
.decode("utf-8"))
check("应答里有 magic", obj.get("lanremote") == discovery.REPLY_MAGIC, obj)
check("应答里有名字", obj.get("name") == "测试电脑", obj)
check("应答里有端口", int(obj.get("port") or 0) == 8765, obj)
probe = json.dumps({"lanremote": discovery.PROBE_MAGIC,
"proto": discovery.PROTOCOL}).encode()
check("探测包能被解析", discovery.parse_probe(probe) is not None)
check("乱码探测包被忽略", discovery.parse_probe(b"not json") is None)
check("伪造 magic 的包被忽略",
discovery.parse_probe(json.dumps({"lanremote": "x"}).encode())
is None)
print()
print("二、先起被控端实例,再广播搜它")
port = free_port()
store = Store(Path(tempfile.mkdtemp(prefix="lan_test_")))
store.set("port", port)
store.set("pairing_code", "482913")
ui = StubUI()
server = RemoteServer(store, make_stub_actions(ui, store), ui=ui)
server.start()
time.sleep(1.2)
check("被控端起来了", True, port)
found = []
try:
found = discovery.discover(timeout=3.0)
except Exception as exc: # noqa: BLE001
print(f" 搜索异常: {exc}")
listing = [f"{item.get('host')}:{item.get('port')}" for item in found[:5]]
print(f" 广播搜到 {len(found)} 台: {listing}")
mine = [item for item in found if int(item.get("port") or 0) == port]
if mine:
check("广播搜到了自己起的实例(UDP 应答正常)", True, listing)
target = mine[0]
check("搜到的名字不是空的", bool(target.get("name")), target)
else:
# UDP 8766 被别的 agent 占着时会出现这种情况(单独跑就是)。
# 那就退一步:验证搜索机制能返回结果、且能直连。
check("广播能返回结果(8766 可能被别的实例占用)",
len(found) >= 1, listing)
target = found[0] if found else None
if target is not None:
print(f" 对 {target.get('host')}:{target.get('port')} 做 TCP 直连")
check("搜到之后能直接 TCP 连上(不经中转)",
tcp_ok(target.get("host"), target.get("port")), target)
print()
print("三、本实例的 TCP 端口是开的(直连能进)")
check("测试实例端口可直连", tcp_ok("127.0.0.1", port), port)
try:
server.stop()
except Exception: # noqa: BLE001
pass
print()
print("四、安卓端源码里有这些新东西")
src = MAIN.read_text(encoding="utf-8")
code = re.sub(r"//[^\n]*", "", src)
code = re.sub(r"/\*.*?\*/", "", code, flags=re.S)
check("有局域网页面(buildLanPanel)",
"private void buildLanPanel()" in code)
check("能显示局域网页面", "private void showLanPanel()" in code)
check("有自动搜索", "private void startLanSearch()" in code)
check("首页有局域网入口按钮",
'bigButton("局域网连接(同一个 WiFi)"' in code)
check("局域网页面能返回", "private void hideLanPanel()" in code)
check("返回键会先退出局域网页面", "closeLanIfOpen()" in code)
check("用到了 Discovery.search(广播搜索)", "Discovery.search(" in code)
check("用到了 DiscoveryCore.Found(结果类型)",
"DiscoveryCore.Found" in code)
check("有服务器延迟检测", "private void checkServer()" in code)
check("首页有检查服务器按钮", '"检查服务器"' in code)
check("延迟结果显示在首页", "serverStatus" in code)
check("延迟按快慢变色", "ms < 300" in code and "C_DANGER" in code)
print()
print("五、局域网这条路不碰中转")
body = java_method(code, "private void connectLanTyped()")
check("connectLanTyped 方法体取到了", len(body) > 200, len(body))
print(f" 方法体 {len(body)} 字符")
check("没用 relayUrl(不走中转)", "relayUrl" not in body, body[:120])
check("没走 connectViaRelay", "connectViaRelay" not in body)
check("调的是直连 connect(", "connect(host, port, code" in body,
body[:200])
check("记住配对码(便于回退中转)", "rememberPairingCode(code)" in body)
check("断开后会自动切回中转", "lanDirect = true" in body)
print()
print("=" * 62)
print(f"通过 {passed} 项,失败 {failed} 项")
return 0 if failed == 0 else 1
if __name__ == "__main__":
raise SystemExit(main())