Files
ykxmz/_build/fix_keepalive.sh
T
xmz7891 8c4ee6df08 修真凶:4G 链路静默断连导致隧道"假活"(用户撞上 Error 1033)
现象:用户早上打开 relay.xxmxxs.top 看到 Cloudflare Error 1033。
      watchdog 日志把过程记下来了:
        公网不通(HTTP 000,ha_connections=2)  <- 隧道显示连着,公网不通
        公网不通(HTTP 502,ha_connections=2)
        公网恢复(HTTP 200)                     <- 自己又好了
      也就是说:**间歇性假活**,短样本测不出来。
      我之前只测 15~25 次就说"可用率 100%",太草率了。

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

      tunnel.log 最近 3000 行的佐证:
        i/o timeout              212 次
        Retrying connection      127 次
        Lost connection           22 次

修法:
  1. TCP keepalive 调到 30 秒 + 每 10 秒探测 3 次
     => 最多约 60 秒发现僵尸连接,cloudflared 立刻重连
     (/etc/sysctl.d/97-ykxmz-keepalive.conf)
  2. 隧道连接数 2 -> 4,多几条冗余
  3. 修 relay 从不清理设备记录的问题:
     self.devices 只增不减,被控端每换端口/重装就多一条,
     实测一天攒了 75 条僵尸记录,/v1/health 从几 KB 涨到 20KB。
     加 DEVICE_TTL(6 小时)在 snapshot() 里清掉。

实测(修完立刻验证):
    设备侧  公网 15/15 HTTP 200
    Windows 公网 25/25,中位 520 ms,最快 463 ms
    health  20931 字节 / 75 台  ->  152 字节 / 0 台
2026-10-07 09:15:27 +08:00

138 lines
5.4 KiB
Bash
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.
#!/bin/bash
# 真修:让死掉的 TCP 连接被**快速发现**
#
# 根因(watchdog 日志 + 隧道日志看出来的):
# 隧道显示 ha_connections=2(本地认为连着),但公网 HTTP 000/502/530。
# 隧道日志最近 3000 行里 i/o timeout 212 次、Retrying 127 次。
#
# => 4G 链路会把 TCP 连接**静默断开**:连接实际死了,
# 但双方都没有收到 FIN/RST,于是都以为还连着。
# Cloudflare 把请求发到这条"僵尸连接"上,就回 502/530/1033。
#
# Linux 默认参数:
# tcp_keepalive_time = 7200 秒(2 小时!)才发现连接死了
# tcp_keepalive_intvl = 75 秒
# tcp_keepalive_probes = 9 次
# => 最坏要 2 小时才知道连接没了。中间这段时间服务就是"假活"。
#
# 修法:
# 1. 把 keepalive 调到 30 秒 + 10 秒探测 3 次
# => 最多 ~60 秒发现僵尸连接,cloudflared 立刻重连
# 2. ha-connections 从 2 提到 4,多几条冗余,一条死了还有别的
# 3. 顺手清掉测试留下的 ~90 台僵尸设备(health 接口都被撑到 20KB 了)
set -u
RELAY_DIR=/root/lanremote-relay
echo "════════ 1. 改 TCP keepalive(核心修复)════════"
cat > /etc/sysctl.d/97-ykxmz-keepalive.conf <<'EOF'
# ykxmz:快速发现"静默断开"的 TCP 连接
#
# 背景:这台设备走 4G,运营商链路会静默丢连接(不发 FIN/RST)。
# Linux 默认 7200 秒才发现,期间 cloudflared 以为自己连着、
# Cloudflare 也以为隧道活着,但请求发过来就是 502/1033。
# 把 keepalive 调到 30 秒,最多一分钟就能发现并重连。
net.ipv4.tcp_keepalive_time = 30
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.tcp_keepalive_probes = 3
EOF
sysctl -p /etc/sysctl.d/97-ykxmz-keepalive.conf 2>&1 | sed 's/^/ /'
echo
echo " 生效确认:"
for k in tcp_keepalive_time tcp_keepalive_intvl tcp_keepalive_probes; do
printf " %-26s = %s\n" "$k" "$(sysctl -n net.ipv4.$k)"
done
echo
echo "════════ 2. 隧道连接数提到 4 ════════"
CURRENT=$(grep -o 'ha-connections [0-9]*' /etc/systemd/system/lanremote-tunnel.service)
echo " 当前: $CURRENT"
sed -i 's/--ha-connections [0-9]*/--ha-connections 4/' /etc/systemd/system/lanremote-tunnel.service
NEW=$(grep -o 'ha-connections [0-9]*' /etc/systemd/system/lanremote-tunnel.service)
echo " 改为: $NEW"
echo
echo "════════ 3. 清掉测试留下的僵尸设备 ════════"
python3 - <<'PY'
import json
import os
import time
from pathlib import Path
# 中转把设备登记在 data 目录里,找找在哪个文件
base = Path("/root/lanremote-relay/data")
if not base.is_dir():
print(" 没有 data 目录")
raise SystemExit(0)
for path in sorted(base.glob("*.json")):
try:
data = json.loads(path.read_text(encoding="utf-8"))
except Exception as exc:
print(" %s 读不了: %s" % (path.name, exc))
continue
if not isinstance(data, dict):
print(" %s 不是字典,跳过" % path.name)
continue
# 找设备表
for key in ("devices", "deviceMap", "device_map", "clients"):
if key in data and isinstance(data[key], (dict, list)):
items = data[key]
before = len(items)
now = time.time()
if isinstance(items, dict):
keep = {k: v for k, v in items.items()
if isinstance(v, dict)
and now - float(v.get("lastSeen", 0) or 0) < 3600}
removed = before - len(keep)
if removed:
data[key] = keep
else:
keep = [v for v in items
if isinstance(v, dict)
and now - float(v.get("lastSeen", 0) or 0) < 3600]
removed = before - len(keep)
if removed:
data[key] = keep
if removed:
path.write_text(json.dumps(data, ensure_ascii=False),
encoding="utf-8")
print(" %s: %s 从 %d 清到 %d(删了 %d)"
% (path.name, key, before, len(keep), removed))
else:
print(" %s: %s 共 %d 条,都是新的" % (path.name, key, before))
PY
echo
echo " 清理后 health 大小:"
curl -s --max-time 10 http://127.0.0.1:8080/v1/health -o /tmp/h.json
echo " $(wc -c < /tmp/h.json) 字节"
python3 -c "
import json
d = json.load(open('/tmp/h.json'))
print(' 设备数:', len(d.get('devices', [])))
" 2>/dev/null | sed 's/^/ /'
echo
echo "════════ 4. 重启隧道让新参数生效 ════════"
systemctl daemon-reload
systemctl restart lanremote-tunnel
sleep 30
echo " 连接数: $(curl -s --max-time 5 http://127.0.0.1:20241/metrics 2>/dev/null | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}')"
echo " 落点:"
grep -E 'Registered tunnel connection' "$RELAY_DIR/tunnel.log" 2>/dev/null | tail -4 \
| grep -oE 'location=[a-z0-9]+ protocol=[a-z0-9]+' | sed 's/^/ /'
echo
echo "════════ 5. 立刻测公网 ════════"
ok=0
for i in $(seq 1 10); do
c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 \
-H 'User-Agent: LanRemote/1.4.0' https://relay.xxmxxs.top/v1/health)
[ "$c" = "200" ] && ok=$((ok+1))
printf " %2d: %s\n" "$i" "$c"
sleep 1
done
echo " 可用 $ok/10"