xmz7891
|
0ee91b68de
|
清理误提交的测试数据目录 + 扩展 .gitignore
|
2026-10-07 19:01:33 +08:00 |
|
xmz7891
|
da40f7572c
|
同步部署脚本智能体的收尾改动 + 恢复被误删的文件
== 部署脚本(另一个智能体的收尾)==
* deploy.sh 默认两个隧道 unit 都 enable:
主 lanremote-tunnel quic + 8 条 + --metrics :20241
副本 lanremote-tunnel-h2 http2 + 2 条 + --metrics :20242
--protocol 支持 both(默认)/ quic / http2,非法值退出码 2
* metrics 端口在两个 unit 里都显式钉死 ——
实测停掉主实例再重启副实例时,副实例会抢 20241,
于是 watchdog 和所有按端口读指标的脚本全部读错实例
* MTU/MSS 收成单一出处:net-tune.sh 唯一负责 MSS 1360 + 接口 MTU 1400,
deploy-tune.sh 只管 keepalive + 开机加速并调用 net-tune.sh。
这样就不存在"谁后跑谁赢"
* 按需重启:程序和 unit 都用 cmp 比对,一样就不落盘、不重启、
不 daemon-reload(幂等重跑不再打断正在服务的连接)
* 顺手修了一个会咬人的坑:单独用 --port 9090 时原来只改 unit 不改
config.yml,隧道会回源到 8080,现象是"隧道通了但一访问就 502"
* 域名:不传 --hostname 且 config.yml 里没有 hostname 时明确停下来
要输入,不猜、不写 config、不启隧道
测试(三套全绿,实测数字):
_build/test_deploy_sh.sh 110 通过 / 0 失败(Windows 沙箱)
_build/test_zip_deploy.sh 30 通过 / 0 失败(从发布 zip 解压再跑)
_build/test_deploy_device.sh 39 通过 / 0 失败(真机 Debian aarch64)
真机那套桩掉了 systemctl/iptables/ip/sysctl/nmcli
(--prefix 只隔离写文件,不隔离命令副作用),
第 8 节专门验证线上零影响:relay/tunnel/h2 全 active、
MTU=1400、MSS 规则数、keepalive 测前测后完全一致。
== 恢复被误删的文件 ==
排查过程中有人用通配符清了 $env:TEMP\tmp.*,而 Git Bash 的 /tmp
正映射到那个 Windows 目录,导致两个被 git 跟踪的文件被删:
_build/insert_stub_checks.py
_build/inspect_pathtest.sh
已从 git 恢复。
教训:用通配符删临时文件很危险,尤其是 /tmp 在 Windows 上
映射到共享目录时。改成只删明确列出的文件名。
== 清理 ==
删掉这轮调试产生的临时输出(reg3.txt / td1.txt / td2.txt 等),
不让它们进仓库。
|
2026-10-07 18:42:21 +08:00 |
|
xmz7891
|
e611b07fb1
|
纠正一个传出去的错误结论:不是「运营商封了 UDP」
我在给部署脚本的指示里写过:
实测这条线路上运营商对 UDP 有限制 ——
12 个 Cloudflare edge IP:TCP 12/12 通、UDP 0/12 通
它照这个把注释写进了 relay/deploy.sh。
**但这个测量是无效的。** relay-stability 后来自己纠正了:
那个 UDP 探测包是手搓的简化包,格式不对,
Cloudflare 本来就不该回应它 —— 「0/12 无回应」什么都证明不了。
用 cloudflared --loglevel debug 跑临时实例,真相是:
UDP Connectivity region1/region2 PASS QUIC connection successful
SUMMARY: Environment is healthy. cloudflared will use 'quic'
Registered tunnel connection (握手 1 秒)
**UDP 完全通。**
真因是:QUIC 建连要 6+ 个包往返,而 TCP 的 SYN 只有 3 个包、
内核还会按 1s/2s/4s 激进重传。约 5.4%/包 丢包下:
(1 - 0.054)^6 ≈ 72% <- 和实测的 72% 对得上
所以 QUIC 的弱点在「连不上」,不在「连上之后不好」;
一旦连上,它没有 TCP 的跨流队头阻塞,反而更稳。
结论的**用途**没变(还是要两条协议一起跑),但留着错误的根因注释会:
* 误导后人以为 UDP 被封 -> 可能做出「干脆别用 QUIC」的错误决定
* 掩盖真正可优化的方向(QUIC 握手对丢包敏感是链路物理属性,
只能靠 HTTP/2 兜底来绕,不是「运营商限制」)
改动:relay/deploy.sh 两处、_build/test_deploy_sh.sh 一处。
教训记一笔:**手搓协议包去探测可达性非常容易得出错误结论** ——
那个包格式不对,对方根本没义务回应。要么用官方工具的自检
(cloudflared 自己会打 UDP Connectivity PASS/FAIL),
要么别下结论。
|
2026-10-07 18:22:19 +08:00 |
|
xmz7891
|
cc5891e6d0
|
修局域网/中转对比基准:它自己有两个 bug,害产品背了黑锅
这个基准一直是红的(中转那条路报「连接阶段失败: timed out」),
而且报告里写着"这是中转链路的产品 bug"。**查下来不是产品的问题。**
== bug 1:两条路用了同一个配对码 ==
基准为了排除互相干扰,给两条路**各起一台全新被控端**(设计是对的),
但两台都用了写死的 code="246813"。
中转有「一个配对码只属于一台设备」的规则:
第 1 台(跑局域网)注册成功
-> stop_agent 杀掉进程,但**中转还以为它在线**(last_seen 没过期)
第 2 台(跑中转)用同一个码注册 -> **被拒**
中转日志:配对码 246813 已被 ... 占用,拒绝 ...
而 start_agent 只检查"有没有设备在线" —— 第 1 台僵尸还挂着,
于是**以为新的一台登记成功了**
-> 没有任何进程在回包 -> 超时
对照实验(新增 _build/verify_code_reuse.py):
场景 A 同一个码 -> **不通**
场景 B 各用不同码 -> 通
修法:两条路各用不同码;start_agent 的判据从"有没有在线设备"
改成"有没有**配对码正好是这一台要用的码**的在线设备"。
== bug 2:attach 用的还是旧码(我第一版补丁也漏了这处)==
relay_connect() 里 `client.attach(code)` 用的还是局域网那个码 ——
attach 到一台已经死掉、但中转以为在线的设备上,pair 自然没人回。
修成 `attach(relay_code)`。
== 顺带更新一段过时注释 ==
原文写着「当前版本经中转只稳定支撑前 1~2 次交互……这是中转链路的
产品 bug」,并据此把中转的轮数调小、超时调短。
那个 bug **已经修好了**(根因:一条 HTTP/1.1 连接被长轮询和发命令
两个线程同时用,撞状态机抛 CannotSendRequest("Idle"),
异常冒到 _client_loop 外面把 client-relay 线程打死)。
注释不更新会误导后人以为产品还有这个问题。
== 修好之后的实测(本地中转,30 次/路)==
局域网 握手 16ms 成功 30/30 (100%) 中位 0ms P90 0ms 最大 1ms
中转 握手 46ms 成功 30/30 (100%) 中位 15ms P90 29ms 最大 32ms
这是本地中转,量的是**协议本身的开销**(长轮询往返 vs 直连 TCP);
真实跨网络那条腿的 1.0~1.5 秒来自隧道和 4G,是另一回事。
结论:局域网确实是"毫秒级、不耗流量"的那条根本路径 ——
中位 0ms vs 15ms,而且不走任何外网。
踩坑记录:这两处都是"看起来像产品 bug、实际是测试自己的 bug"。
先怀疑测量工具,比先怀疑产品更省时间 ——
上一次(管理页缓存头)也是我拿 curl -I 测 GET 白查了半天。
|
2026-10-07 18:20:57 +08:00 |
|
xmz7891
|
aac43e559d
|
修正 MTU 测试的过时断言(1500 -> 1400)
test_net_tune.py 原来断言 net-tune.sh 里写着 mtu 1500,
依据是更早一次实测「接口 MTU 降到 1400 会掉到 50%」。
那个结论后来被推翻了 —— 当时漏了一件事:
**MSS 钳制只管 TCP,QUIC 走 UDP 完全不受保护。**
路径 MTU 实测 1472(1500 的包不通),QUIC 默认发 ~1452 的包会被
中间设备静默丢掉,而 UDP 丢了不回 ICMP,只能等超时。
把接口 MTU 压到 1400 之后,QUIC 建齐 10 条连接从 28 秒缩到 12 秒。
所以现在是**故意**设 1400。断言跟着改,
不然它会永久假失败,还会诱导后人把真正的改进改回 1500。
顺带记录:全量回归 38 个阶段全部通过。
新增的三个阶段(局域网优化 / 手表局域网切换 / 编码与换行 /
管理页设备控制)都是这轮加的。
|
2026-10-07 18:10:30 +08:00 |
|
xmz7891
|
8880e7033d
|
修「经中转几次后永久哑掉」——「按一下没反应」的真因
== 现象 ==
用户报:
* 局域网能搜到电脑,IP/端口/配对码都对,连上后**没有任何反应**
* 走中转「已配对」,但点按钮没反应
局域网那一半已经修过(preferLan)。这次抓到的是**经中转**那一半,
而且比预想的严重得多。
== 证据链(插桩复现出来的)==
被控端日志里直接抛了未捕获异常,线程当场死:
Exception in thread client-relay:
server.py:407 session.send(reply)
relay.py:166 self._link.send_to_client(data)
relay.py:357 self._post("/v1/agent/send", ...)
relay.py:766 raise RelayError(f"请求失败: {last_error}")
lanremote.relay.RelayError: 请求失败: Idle
之后的状态:
* client-relay 线程没了 —— 电脑再也不往手表回任何东西
* relay-link 线程还活着、还在长轮询 —— 中转那边看着「设备在线」
* **手表看到的就是「连上了,但按一下毫无反应」**
写入仓库的测试 _tests/test_relay_local.py 一直是这个形状:
前面几项 PASS、跑到经中转发命令就卡死。之前没人深挖。
== 根因 1:一条 HTTP/1.1 连接被两个线程同时用 ==
_post() 里锁只包住「取连接」那一段:
with self._http_lock:
connection = self._http # 锁到这里就放了
try:
connection.request("POST", ...) # 锁外发请求
于是两个线程会同时用同一条 connection:
* relay-link 线程跑 /v1/agent/poll —— 长轮询,一条请求挂 180 秒
* client-relay 线程同时 POST /v1/agent/send
HTTP/1.1 一条连接同一时刻只能有一个在途请求,第二个线程一进来
就撞上 http.client 状态机,抛 CannotSendRequest("Idle")。
_post 的「重试一次」也救不了:它 _drop_http() 之后,另一个线程
手里还攥着那条旧连接对象在发,照样坏。
修法:
* **发命令用独立连接**(_post 加 dedicated 参数)。
长轮询那条常年被占着,发命令不该去挤它,各走各的。
* 两条连接各自一把锁**锁住整个「发请求 + 读响应」周期**,
不再只锁取连接那一下。
* 普通请求若发现缓存连接正忙(长轮询在途),临时另开一条用完即关。
* _drop_http(dedicated=) 区分清哪条;finally 里一定归还占用
(否则那条连接会被永久标记为忙)。
== 根因 2:回包失败会打死整个会话线程 ==
server.py 的 _client_loop 里:
if reply is not None:
session.send(reply) # <- 不在任何 try 里
...
except OSError: # <- 外层只捕获 OSError
pass
而中转链路上 send 抛的是 RelayError,直接冒出去把线程打死。
**死一次就永久哑**,代价太大。
修法:session.send() 单独兜住所有异常,失败就断开这个会话
(让手表重连),绝不让线程消失。外层也补一个兜底 except。
== 修复效果 ==
修复前:pair OK -> ping OK -> ping 卡死 -> 线程崩溃,永久哑
修复后:pair 21ms -> ping 82ms -> ping 4ms -> ping 6ms,零异常
_tests/test_relay_local.py:**之前完全卡死 -> 现在 21/21 全过**
这个测试之前一直是红的,说明「35 个阶段的回归全绿」这件事
本身就不成立过 —— 某个阶段卡住时看起来像环境问题而不是缺陷。
== 同批修掉验证阶段找到的其它问题 ==
1. **TTL 清理 + 关掉接入开关 = 设备永久锁死**(严重)
register() 用 `device_id in self.devices` 判断老设备,但超时清理
把设备从 devices 和 codes 都删了。电脑关机过夜(必然 > 6 小时)
第二天开机就会被自己的开关挡住,而且**重启也救不回来**
(清 codes 时没落盘,重启后又当孤儿清一遍,自我维系)。
管理员在页面上还看不到这台设备,不知道该放开谁。
修法:状态文件里单独存 knownDevices 设备历史,超时清理绝不动它;
register 改查这份历史;清理后补 _save();delete_device 才真正忘记。
2. **管理页 JS 语法错误 -> 所有按钮失效**(严重)
admin.py 里 ADMIN_JS 是普通三引号字符串,我在 Python 源码里写的
`\n` 被变成真实换行,拼进 JS 就是字符串里的裸换行 ->
整段 <script> 不执行 -> 改码/删除/批量删除/全选全变幽灵按键。
元凶是我加改码功能那次提交。改成 `\\n`。
3. **--relay / --relay-status 崩溃**(严重)
RelayLink.start(url) 的 url 是必填参数,我把默认地址改空白时
run_agent.py 两处调用没跟着改。一处被 try 吞成「测试失败:missing
1 required positional argument」(用户以为地址填错了),
一处直接报栈。两处都补上 url。
4. 非法 deviceId(控制字符 / 路径分隔符)现在被拒(白名单:
字母数字 + - _ . :)
5. enable_relay() 第二次调用不再谎报成功(加 RelayLink.running 属性,
返回值反映真实运行状态;不再用「对象建过没有」判断)
6. snapshot() 清孤儿码后补 _save(),磁盘和内存不再脱节
== 局域网全面优化(task-2,另一个智能体完成)==
4 个真问题:
* **网段判断写死 /24** —— 在 /16 网络上 10.8.1.5 和 10.8.2.5
被判成不同网络,明明能直连却去走中转还费流量。
改成按网卡真实掩码比较。
* **广播只发一个包** —— 丢一个就整轮搜不到。改成 5 个时间点重发。
* **只探一个网段** —— 多网卡手表搜不到另一网段的电脑。改成全探。
* **走中转之后再也回不到局域网** —— 出门再回家永远挂中转。
新增定期回切(先便宜地 TCP ping,通了才动真格,
不破坏正在工作的中转连接)。
实测:局域网握手 2ms、50/50 = 100%、中位 0ms。
(跨网络那半之前拿不到数字,就是被上面那个线程崩溃挡住的 ——
现已修复。)
顺带:instance.py 单实例锁端口加 YKXMZ_LOCK_PORT 覆盖(默认不变)。
用户自己那台被控端正跑在这台机器上占着 8767/TCP + 8766/UDP,
是这次排查里好几个「测试怎么都不通」的根因。
== 服务端一键部署脚本(task-1,另一个智能体完成)==
relay/deploy.sh + relay/deploy-tune.sh,随服务端 zip 发布:
* 自动检查并安装 cloudflared
* 部署程序、装 4 个 systemd 服务、网络调优、配隧道、自检打公网地址
* **不传 --hostname 且没有 TUNNEL_TOKEN 时明确提示要填**,
不会静默用一个写死的域名(用户明确说过这个中转站是他自己用的,
不对外开放,绝不能把私人域名烧成默认值)
* 重跑 = 升级,已有数据/配对码/管理密码/凭据都不动
顺带修了发布包一直缺 site_shots/ 的老 bug
(package_remote.py 用 iterdir()+is_file() 收文件,目录进不去,
新机器装完官网截图全是 404)。
== 默认中转地址改成空白 ==
用户要求:软件默认不带任何中转地址,由用户自己填。
四处硬编码全部清空(server.py / MainActivity.java / run_agent.py /
relay.py 的注释和文档示例),全仓已无非注释的硬编码地址。
== 文档同步(task-5,另一个智能体完成)==
README / BEFORE-INSTALL / AFTER-INSTALL / 打包模板:
* 新增「软件里不带任何中转地址」告示,讲清跨网络要自己部署
* 修正 Android 版本(写的是 9.0/API28,实际 API 24 / Android 7.0)
* 删掉「地址已经内置好了」(现在是错的)
* AGENT_README 原来把作者自己的中转地址当成「内置地址」发给用户,已重写
* RELAY_README 原来写「两条隧道同时跑会互相争抢」,
和真机/CHANGELOG 矛盾;按实测改成「两种协议同时在跑(故意的)」
|
2026-10-07 17:59:39 +08:00 |
|