Files
ykxmz/_build/harden_tunnel.py
T
xmz7891 8dc47d0f48 2.1.2:隧道稳定性加固(继续修「网站还是容易挂」)
2.1.1 修掉了 nettune 死锁之后,公网上到 200,但**过一阵又 530**。
这次抓到的是隧道自己:

cloudflared 的 precheck 输出(tunnel.log):

    | UDP Connectivity  region1.v2.argotunnel.com  FAIL  QUIC connection failed                      |
    | TCP Connectivity  region1.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 不通**,而且是**间歇性**的(过几分钟自己又好了,
然后再次断)。Cloudflare 隧道的边缘连接走 7844(QUIC 走 UDP 7844、
HTTP/2 走 TCP 7844)。这条线路对它是间歇性阻断 —— 属于运营商那一侧,
设备改不了,但可以让 cloudflared 自己去适应。

== 两个改动 ==

1. **协议从写死改成 auto**
       lanremote-tunnel      --protocol quic    -> --protocol auto
       lanremote-tunnel-h2   --protocol http2   -> --protocol auto
   原来两条实例各赌一个协议:那一刻 7844 的 UDP 和 TCP 都不通就一起死,
   没有退路(实测连接数掉到 1/0、公网 530)。改成 auto 之后
   cloudflared 自己在 QUIC / HTTP/2 之间挑当前能通的那条,
   10 条连接全都有自适应能力。

2. **--retries 5 -> 1000000**
   原来失败 5 次就退出、靠 systemd Restart=always 拉起来,
   每次退出到重启之间网站都是断的。而这条线路的抖动是分钟级的 ——
   实测隧道每 3~10 分钟就被重启一次(19:37 / 19:40 / 19:43 / 19:50 / 19:52),
   公网跟着一起断。拉大之后抖动期间连接自己恢复,整条隧道不退出。

3. 顺手加日志轮转(tunnel.log 已经 2.98 MB,这台机器硬盘不大)。

== 实测 ==

    改前:连接数 1/0,公网 530
    改后:连接数 7+2(接近满配 10),公网连测 5 次全部 200

== 同步 ==

relay/deploy.sh 里的 MAIN_PROTO / ALT_PROTO 也改成 auto、
TUNNEL_BASE 的 --retries 也拉到 1000000,新机器装出来就是加固过的。

全量回归 46 个阶段全部通过。
2026-10-08 20:04:35 +08:00

163 lines
6.4 KiB
Python
Raw 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 -*-
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())