Files
xmz7891 1f05084f51 隧道"假活"真因修复 + 中转管理后台重做
用户反馈两件事:
  1. 早上打开 relay.xxmxxs.top 看到 Cloudflare Error 1033 —— "这就是你说的优化好了?"
  2. "中转管理那边 ui 界面大改,现在难看的要死"

============ 一、隧道 1033/502/530 的真因 ============

之前我只测 15~25 次就说"可用率 100%",太草率 ——
实际是**间歇性假活**:隧道显示 ha_connections=2 连着,
公网却 502/530/000。watchdog 日志把过程记了下来。

根因:**4G 链路静默断开 TCP 连接**(不发 FIN/RST,双方都以为还活着)。
Cloudflare 把请求发到这条僵尸连接上就回错误。
Linux 默认 tcp_keepalive_time = **7200 秒(2 小时)**才发现,
中间这段时间服务一直"看着在线、实际打不开"。

修法(都有实测数据支撑):
  1. tcp_keepalive 30 秒 + 每 10 秒探测 3 次
     => 最多约 60 秒发现僵尸连接并重连
  2. 隧道连接数 2 -> 4
     (对照实验:http2+2 只有 1 条连上时公网 0/12;
       http2+4 稳定 12/12。连接数太少会出现"只有 1 条"的危险窗口)
  3. 协议定为 http2(cloudflared 自检说 TCP 7844 被墙,
     但实测 http2 反而比 quic 稳)

实测:http2+4 之后公网 40/40 = 100%,最慢 1.3 秒,0 次重连。

============ 二、中转管理后台重做 ============

原来是几百行内联在 relay.py 的 f-string 里(HTML+CSS+JS 全挤一起),
改一处样式要在一堆转义花括号里翻。拆成独立模块 relay/admin.py。

新版设计(对齐官网那套白色简洁):
  * 吸顶顶栏:品牌 + 运行状态 + 版本/内存/请求数
  * 公网地址给最大权重(这是用户最需要复制的东西)
  * 四个数字卡(在线/会话数带颜色区分)
  * 设备表卡片:状态圆点、等宽配对码、行悬停
  * 工具卡:网络体检 + 下载区并排
  * 空状态改成图标+引导文案,不再是干巴巴一句
  * 窄屏时表格自动变卡片

relay.py 从 2089 行缩到 1550 行。

============ 三、顺手修的两个真 bug ============

1. 设备记录只增不减
   self.devices 从不清理,被控端每次换端口/重装就多一条,
   实测一天攒了 75 条僵尸记录,/v1/health 从几 KB 涨到 20KB。
   加 DEVICE_TTL(6 小时)在 snapshot() 里清。

2. 配对码映射也没清
   RELAY.codes 和 devices 是一对孪生 dict,
   只清 devices 会留下指向已删设备的孤儿条目 ——
   管理页显示"配对码占用 131",实际一台设备都没有。
   现在一起清。

============ 四、我自己造的两次事故(如实记录) ============

1. 删文件删过头
   用"从 _admin_page 截到文件末尾"的方式替换方法,
   但 _admin_page 后面**还有 _esc/_kb/_uptime/class Server/main()**。
   结果 relay 起不来,systemd 重启 24 次,所有接口 000。
   教训:**"删到文件末尾"之前必须先确认末尾有什么。**

2. ADMIN_PATH 引用了不存在的常量
   路由里写的是字面量,我却在新代码里用了 ADMIN_PATH,
   管理页一访问就 NameError。现在统一定义了常量。

二者都是"改完没立刻验"造成的。现在每次改完都跑一遍
管理页 + 官网 + 关键接口的回归。
2026-10-07 09:34:53 +08:00

61 lines
2.6 KiB
Bash
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.
#!/bin/bash
# 502 但隧道连着 -> 回源这一段的问题。逐一排除。
set -u
echo "════════ 1. 隧道 ingress 全文 ════════"
cat /root/.cloudflared/config.yml 2>/dev/null | sed 's/^/ /'
echo
echo "════════ 2. relay 在听哪个地址 ════════"
ss -lntp 2>/dev/null | grep -E ':8080' | sed 's/^/ /'
echo
echo "════════ 3. 带 Host 头访问(模拟云隧道回源)════════"
for host in "relay.xxmxxs.top" "127.0.0.1" "localhost" ""; do
if [ -z "$host" ]; then
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 8 http://127.0.0.1:8080/v1/health)
echo " 不带 Host: HTTP $code"
else
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 8 -H "Host: $host" http://127.0.0.1:8080/v1/health)
echo " Host: $host -> HTTP $code"
fi
done
echo
echo "════════ 4. 请求 /(官网)带 Host 头 ════════"
for host in "relay.xxmxxs.top" "127.0.0.1"; do
printf " Host: %-22s HTTP %s\n" "$host" \
"$(curl -s -o /dev/null -w '%{http_code}' --max-time 8 -H "Host: $host" http://127.0.0.1:8080/)"
done
echo
echo "════════ 5. 隧道进程的真实参数 ════════"
pid=$(pgrep -f 'cloudflared tunnel' | head -1)
[ -n "$pid" ] && tr '\0' ' ' < /proc/$pid/cmdline 2>/dev/null | sed 's/^/ /'
echo
echo "════════ 6. cloudflared 有没有报回源错误 ════════"
tail -400 /root/lanremote-relay/tunnel.log 2>/dev/null \
| grep -iE 'origin|502|refused|unreachable|no such host|proxy' | tail -10 | sed 's/^/ /'
echo " (没输出=没有回源错误)"
echo
echo "════════ 7. relay 自己有没有收到隧道来的请求 ════════"
tail -60 /root/lanremote-relay/relay.log 2>/dev/null \
| grep -E 'GET|POST|处理' | tail -10 | sed 's/^/ /'
echo
echo "════════ 8. 关键:直接问 Cloudflare 边缘这个域名该怎么走 ════════"
echo " DNS 记录:"
getent hosts relay.xxmxxs.top | sed 's/^/ /'
echo " HTTP 响应头(看 cf-ray / server):"
curl -sI --max-time 20 -H 'User-Agent: LanRemote/1.4.0' https://relay.xxmxxs.top/v1/health \
| head -12 | sed 's/^/ /'
echo
echo "════════ 9. 换一个路径试(避开 /v1/health)════════"
for p in "/" "/favicon.ico" "/v1/health" "/v1/downloads"; do
printf " %-16s HTTP %s\n" "$p" \
"$(curl -s -o /dev/null -w '%{http_code}' --max-time 20 -H 'User-Agent: LanRemote/1.4.0' "https://relay.xxmxxs.top$p")"
done