# -*- coding: utf-8 -*- r'''增强隧道稳定性 —— 「网站还是容易挂」的真因修法。 == 查到什么 == cloudflared 自己的 precheck 输出(tunnel.log): | UDP Connectivity region1.v2.argotunnel.com FAIL QUIC connection failed | | UDP Connectivity region2.v2.argotunnel.com FAIL QUIC connection failed | | TCP Connectivity region1.v2.argotunnel.com FAIL HTTP/2 connection is blocked or unreachable | | TCP Connectivity region2.v2.argotunnel.com FAIL HTTP/2 connection is blocked or unreachable | | Cloudflare API api.cloudflare.com:443 PASS API is reachable | | ERROR: Allow outbound QUIC traffic on port 7844 or use HTTP2. | 也就是:**443 通,7844 不通**,而且是**间歇性**的 (日志后面又出现 Registered tunnel connection,过一阵又掉)。 Cloudflare 隧道的边缘连接走 7844(QUIC 走 UDP 7844、HTTP/2 走 TCP 7844)。 这条线路对 7844 是间歇性阻断的 —— 这不是设备能修的, 但**可以让 cloudflared 自己去适应**。 == 现在的配置有两个问题 == 1. **两条实例各写死一个协议** lanremote-tunnel --protocol quic (只走 UDP 7844) lanremote-tunnel-h2 --protocol http2 (只走 TCP 7844) 如果那一刻 7844 的 UDP 和 TCP 都不通,两条一起死 —— 没有退路。 实测就是这样:连接数掉到 1/0,公网 530。 2. **--retries 5** cloudflared 重试 5 次就**退出**,靠 systemd Restart=always 拉起来。 每次退出/重启期间网站都是断的。而这个线路的抖动是分钟级的, 5 次远远不够扛过去。 == 改法 == 1. `--protocol auto` —— 让 cloudflared **自己**在 QUIC 和 HTTP/2 之间选, 哪条通走哪条。两条实例都改成 auto,等于 10 条连接全都能自适应。 (而不是现在这样"8 条赌 UDP、2 条赌 TCP") 2. `--retries` 提到很大(相当于"一直重试,别退出")—— 抖动期间连接会自己恢复,不会整条隧道退出让网站断掉。 3. 顺手加日志轮转:tunnel.log 已经涨到 2.98 MB, 这台机器只有 460MB 内存 + 小硬盘,无限增长不合适。 ''' from __future__ import annotations import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT / "_build")) from devssh import run # noqa: E402 def patch_unit(name: str, proto: str) -> str: """读设备上现有的 unit,改协议和重试次数。 用 sed 现场改而不是整份重写 —— 现有 unit 里还有 metrics 端口、ha-connections、MemoryMax 这些实测调出来的参数, 整份重写容易漏掉。 """ cmds = [ # 协议 "sed -i 's/--protocol [a-z0-9]*/--protocol %s/' %s" % (proto, name), # 重试次数:改成很大(一直重试) "sed -i 's/--retries [0-9]*/--retries 1000000/' %s" % name, ] return " && ".join(cmds) def main() -> int: print("=" * 62) print(" 隧道稳定性:协议改 auto + 加大重试 + 日志轮转") print("=" * 62) UNIT_DIR = "/etc/systemd/system" print() print(" ── 1. 两条隧道都改成 --protocol auto ──") out = run( "cd %s && " "sed -i 's/--protocol [a-z0-9]*/--protocol auto/' " "lanremote-tunnel.service lanremote-tunnel-h2.service && " "echo ' tunnel:'; grep -o -- '--protocol [a-z0-9]*' " "lanremote-tunnel.service; " "echo ' h2:'; grep -o -- '--protocol [a-z0-9]*' " "lanremote-tunnel-h2.service; " # 重试次数 "sed -i 's/--retries [0-9]*/--retries 1000000/' " "lanremote-tunnel.service lanremote-tunnel-h2.service && " "echo ' 重试:'; grep -o -- '--retries [0-9]*' " "lanremote-tunnel.service lanremote-tunnel-h2.service" % UNIT_DIR, timeout=120) for ln in out[1].strip().splitlines(): print(" " + ln) print() print(" ── 2. 日志轮转(tunnel.log 已经 2.98 MB)──") out = run( "cat > /etc/logrotate.d/lanremote <<'EOF'\n" "/root/lanremote-relay/*.log {\n" " size 5M\n" " rotate 2\n" " compress\n" " missingok\n" " notifempty\n" " copytruncate\n" "}\n" "EOF\n" "echo ' logrotate 配置已写'; " "logrotate --debug /etc/logrotate.d/lanremote 2>&1 | tail -3", timeout=120) for ln in out[1].strip().splitlines(): print(" " + ln) print() print(" ── 3. 重启两条隧道让新参数生效 ──") out = run( "systemctl daemon-reload && " "systemctl restart lanremote-tunnel lanremote-tunnel-h2 && " "echo ' 等 40 秒让连接建立…' && sleep 40 && " "echo \" tunnel: $(systemctl is-active lanremote-tunnel)\"; " "echo \" h2: $(systemctl is-active lanremote-tunnel-h2)\"", timeout=300) for ln in out[1].strip().splitlines(): print(" " + ln) print() print(" ── 4. 连接数 ──") out = run( "for p in 20241 20242; do " "n=$(curl -s --max-time 5 http://127.0.0.1:$p/metrics 2>/dev/null " "| grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}' " "| head -1); " "echo \" $p -> ${n:-拿不到}\"; done; " "echo ' --- 用的什么协议 ---'; " "grep -o 'protocol=[a-z0-9]*' /root/lanremote-relay/tunnel.log 2>/dev/null " "| tail -6 | sort | uniq -c | sed 's/^/ /'", timeout=120) for ln in out[1].strip().splitlines(): print(" " + ln) print() print(" ── 5. 公网(连测 5 次看稳不稳)──") out = run( "for i in 1 2 3 4 5; do " "c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 " "https://relay.xxmxxs.top/v1/health); " "echo \" 第 $i 次: $c\"; sleep 3; done", timeout=200) for ln in out[1].strip().splitlines(): print(" " + ln) print() print(" 说明:3844/7844 那条线路封不封是运营商的事,设备改不了。") print(" 但 --protocol auto 让 cloudflared 自己绕,") print(" --retries 拉大让它别退出 —— 抖动期间能自己恢复。") return 0 if __name__ == "__main__": sys.exit(main())