www.apple.com 为伪装地址,前端警告消失

This commit is contained in:
2026-08-06 13:02:46 +08:00
parent e8cc0162af
commit 938df26cbb
5 changed files with 145 additions and 17 deletions
+73 -10
View File
@@ -234,6 +234,39 @@ generate_keys() {
fi
}
# ===== 候选伪装站的技术校验 =====
# 只看 xray tls ping 的「Pinging with SNI」那一段:REALITY 实际用的是带 SNI 的
# 握手结果,无 SNI 段返回的往往是另一张证书(如 Google 的 invalid2.invalid
# 才 895 字节),照它判会误判。
REALITY_DEST_CERT_LEN=""
check_reality_dest() {
local site="$1" out len
REALITY_DEST_CERT_LEN=""
local raw
raw=$(/usr/local/bin/xray tls ping "$site" 2>&1)
# 区分「当前 xray 没有 tls ping 子命令」和「这个域名校验不通过」:
# 前者不该阻断部署,后者必须判失败。有 "TLS ping" 头即说明子命令存在。
if ! echo "$raw" | grep -q "TLS ping"; then
warn " ${site}: 当前 xray 不支持 tls ping,跳过技术校验"
return 0
fi
out=$(echo "$raw" | sed -n '/Pinging with SNI/,$p')
echo "$out" | grep -q "Handshake succeeded" || return 1
echo "$out" | grep -q "TLS Version:.*TLS 1\.3" || return 1
len=$(echo "$out" | grep -oE "Certificate chain's total length:[[:space:]]*[0-9]+" \
| grep -oE '[0-9]+$' | head -1)
[ -n "$len" ] || return 1
# xtls/reality 对 target 握手报文有 8192 字节硬编码上限,超出直接握手失败
# XTLS/Xray-core#6356www.microsoft.com 的证书记录 8273 字节即触发)
[ "$len" -lt 8192 ] || return 1
REALITY_DEST_CERT_LEN="$len"
return 0
}
# ===== 3. 选择 Reality 伪装目标 =====
select_reality_dest() {
if [ "$MODE" != "reality" ]; then
@@ -242,7 +275,27 @@ select_reality_dest() {
step "4/8 选择 Reality 伪装目标"
local candidates=("www.microsoft.com" "dl.google.com" "www.apple.com" "www.amazon.com")
# 候选伪装站必须同时满足两类条件。
#
# 【技术条件】TLS 1.3 + 证书链总长 < 8192 字节。由 check_reality_dest 运行时实测。
#
# 【客户端路由条件】SNI 必须会被客户端判成「直连」——这条不直观,但踩过坑。
# TUN 模式下,客户端有时会把「连接本代理服务器」这件事本身也抓进代理
# v2rayN 的 sing-box+xray 双核心架构靠进程嗅探防回环,Windows 上有竞态,
# 实测约 2843:1584 的失败率)。这种回环连接的去向完全由 SNI 决定:
#
# SNI 被判直连 → 连接真的到达本服务器 → REALITY 握手成功 → 用户无感
# SNI 被判走代理 → 进隧道 → 服务器替客户端连了真正的伪装站
# → 客户端收到真证书 → 刷屏 REALITY: received real certificate
#
# 实测(v2rayN 默认规则模板,geosite:google → proxy 排在 geosite:cn → direct 之前):
# www.apple.com / *.apple.com 命中 geosite:cn 安全
# dl.google.com 命中 geosite:google 不安全(本仓库踩过)
# www.microsoft.com 两者都不命中 → final:proxy 不安全
# www.amazon.com 两者都不命中 → final:proxy 不安全
#
# 故候选只保留 Apple 系域名。更换前请先确认新域名命中客户端的直连规则。
local candidates=("www.apple.com" "swdist.apple.com" "updates.cdn-apple.com" "itunes.apple.com")
REALITY_DEST="${REALITY_DEST:-}"
REALITY_SNI="${REALITY_SNI:-}"
@@ -259,6 +312,10 @@ select_reality_dest() {
warn "注意:更换会改变 SNI,届时所有客户端链接都必须同步更新"
else
log "伪装目标连通性正常 (${ms}ms)"
if ! check_reality_dest "$REALITY_DEST"; then
warn "伪装目标 ${REALITY_DEST} 未通过 REALITY 技术校验"
warn "(要求 TLS 1.3 且证书链 < 8192 字节),客户端可能握手失败"
fi
fi
return
fi
@@ -267,29 +324,35 @@ select_reality_dest() {
warn "--redetect:将重新挑选伪装目标,原目标为 ${REALITY_DEST}"
fi
local best_dest=""
local best_ms=9999
# 按列表顺序优先,只有前面的不可用才往后顺延——不按延迟排序。
# 候选现在都是 Apple 系主机,彼此延迟差在噪声级(实测 1~2ms),
# 按延迟挑会让每台机器随机选出不同 SNI,徒增客户端管理成本;
# 而 SNI 在所有机器上保持一致,运维和排查都省事得多。
local best_dest="" best_ms=""
for site in "${candidates[@]}"; do
local ms
# curl 失败时 %{time_connect} 会输出 0.000000,必须要求 ms > 0
# 否则连不通的站点会被当成「延迟最低」选中
# 否则连不通的站点会被当成可用
ms=$(curl -so /dev/null -w '%{time_connect}' --max-time 3 "https://${site}" 2>/dev/null \
| awk '{printf "%d", $1*1000}') || true
if [ -z "$ms" ] || ! [[ "$ms" =~ ^[0-9]+$ ]] || [ "$ms" -le 0 ]; then
warn " ${site} 不可达,跳过"
continue
fi
log " ${site}${ms}ms"
if [ "$ms" -lt "$best_ms" ]; then
best_ms=$ms
best_dest=$site
if ! check_reality_dest "$site"; then
warn " ${site} (${ms}ms) 未通过技术校验,跳过"
continue
fi
log " ${site}${ms}ms,证书链 ${REALITY_DEST_CERT_LEN:-?} 字节 ✔"
best_dest="$site"
best_ms="$ms"
break
done
if [ -z "$best_dest" ]; then
error "有候选伪装目标都无法连接,请检查服务器网络 / DNS"
error "有候选伪装目标能同时满足「可达」与「REALITY 技术要求」"
error "候选列表: ${candidates[*]}"
error "可在 .env 中手动指定 REALITY_DEST / REALITY_SNI 后重试"
exit 1
fi