feat: 添加 Mac 和 Linux 全量指纹验证脚本

This commit is contained in:
win 2026-03-25 13:00:52 +08:00
parent 324483eabd
commit 91600c4abe
2 changed files with 301 additions and 0 deletions

View File

@ -0,0 +1,184 @@
#!/bin/bash
# test-linux.sh — Linux 服务器全量指纹验证脚本
# 用途:验证所有 TCP/OS 层伪装 + Node.js proxy 状态
# 运行方式sudo bash test-linux.sh
#
# 注意sysctl 和 iptables 检查需要 sudo
set -euo pipefail
GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[1;33m' NC='\033[0m'
ok() { echo -e "${GREEN}$*${NC}"; }
fail() { echo -e "${RED}$*${NC}"; }
info() { echo -e "${YELLOW} $*${NC}"; }
PROXY_PORT="${PROXY_PORT:-3456}"
echo "══════════════════════════════════════════════"
echo " Linux 服务器指纹伪装验证"
echo "══════════════════════════════════════════════"
# ── 1. 时区 ────────────────────────────────────────────────
echo ""
echo "【1】系统时区"
TZ_NOW=$(timedatectl show -p Timezone --value 2>/dev/null || cat /etc/timezone 2>/dev/null || date +%Z)
echo " 当前时区: $TZ_NOW"
echo " 当前时间: $(date)"
if [[ "$TZ_NOW" == "America/New_York" ]]; then
ok "时区正确America/New_York"
else
fail "时区错误(应为 America/New_York当前: $TZ_NOW"
info "修复: sudo timedatectl set-timezone America/New_York"
fi
# ── 2. TCP 时间戳 ──────────────────────────────────────────
echo ""
echo "【2】TCP 时间戳(防 uptime 推算)"
TS=$(sysctl -n net.ipv4.tcp_timestamps 2>/dev/null || echo "unknown")
echo " net.ipv4.tcp_timestamps = $TS"
if [[ "$TS" == "0" ]]; then
ok "TCP 时间戳已禁用"
else
fail "TCP 时间戳未禁用(当前: $TS,应为 0"
info "修复: sudo sysctl -w net.ipv4.tcp_timestamps=0"
fi
# ── 3. TTL ─────────────────────────────────────────────────
echo ""
echo "【3】出站 TTLmacOS 特征)"
TTL=$(sysctl -n net.ipv4.ip_default_ttl 2>/dev/null || echo "unknown")
echo " net.ipv4.ip_default_ttl = $TTL"
if [[ "$TTL" == "64" ]]; then
ok "TTL=64macOS/Linux 标准值)"
else
fail "TTL 不为 64当前: $TTL"
fi
# ── 4. TCP Window Size ─────────────────────────────────────
echo ""
echo "【4】TCP Window SizemacOS 特征)"
RMEM=$(sysctl -n net.ipv4.tcp_rmem 2>/dev/null || echo "unknown")
echo " net.ipv4.tcp_rmem = $RMEM"
if [[ "$RMEM" == *"65535"* ]]; then
ok "Window Size 包含 65535macOS 特征)"
else
fail "Window Size 未伪装(应含 65535当前: $RMEM"
info "修复: sudo sysctl -w net.ipv4.tcp_rmem='4096 65535 6291456'"
fi
# ── 5. iptables 规则 ───────────────────────────────────────
echo ""
echo "【5】iptables 指纹防护链"
if iptables -L MG_FINGERPRINT -n 2>/dev/null | grep -q "MG:"; then
ok "MG_FINGERPRINT 链存在"
RULES=$(iptables -L MG_FINGERPRINT -n 2>/dev/null | grep -c "MG:" || echo 0)
echo " 规则数: $RULES"
else
fail "MG_FINGERPRINT 链不存在,运行 setup-firewall.sh apply"
fi
if iptables -t mangle -L MG_FINGERPRINT_TTL -n 2>/dev/null | grep -q "TTL"; then
ok "TTL mangle 链存在"
else
fail "TTL mangle 链不存在"
fi
# ── 6. QUIC 阻断验证 ───────────────────────────────────────
echo ""
echo "【6】QUIC/UDP 阻断"
if iptables -L MG_FINGERPRINT -n 2>/dev/null | grep -q "udp.*443.*DROP"; then
ok "UDP 443 QUIC 已阻断"
else
fail "UDP 443 未阻断"
fi
# ── 7. Node.js 版本 ────────────────────────────────────────
echo ""
echo "【7】Node.js 版本"
if command -v node &>/dev/null; then
NODE_VER=$(node --version)
echo " Node.js: $NODE_VER"
if [[ "$NODE_VER" == v22* ]]; then
ok "Node.js v22.x — 与 Claude CLI 版本匹配"
else
fail "Node.js 不是 v22.x当前: $NODE_VERJA4 指纹可能不匹配"
fi
else
info "Node.js 未在宿主机安装Docker 部署无需宿主机 Node"
fi
# ── 8. node-tls-proxy 健康 ─────────────────────────────────
echo ""
echo "【8】node-tls-proxy 健康(端口 $PROXY_PORT"
if curl -sf "http://127.0.0.1:${PROXY_PORT}/__health" -o /tmp/health.json 2>/dev/null; then
NODE_IN_PROXY=$(python3 -c "import json; d=json.load(open('/tmp/health.json')); print(d.get('node','?'))" 2>/dev/null)
SESSIONS=$(python3 -c "import json; d=json.load(open('/tmp/health.json')); print(d.get('sessions',0))" 2>/dev/null)
H2HOSTS=$(python3 -c "import json; d=json.load(open('/tmp/health.json')); print(','.join(d.get('h2Hosts',[])))" 2>/dev/null)
TELEMETRY=$(python3 -c "import json; d=json.load(open('/tmp/health.json')); print(d.get('telemetry','?'))" 2>/dev/null)
ok "Proxy 运行正常"
echo " Node版本: $NODE_IN_PROXY"
echo " Sessions: $SESSIONS"
echo " H2已建立: ${H2HOSTS:-(无,首次请求后会建立)}"
echo " 遥测: $TELEMETRY"
if [[ "$NODE_IN_PROXY" == v22* ]]; then
ok "Proxy 内置 Node.js v22.x ✅"
else
fail "Proxy 内置 Node 版本: $NODE_IN_PROXY(应为 v22.x"
fi
else
fail "Proxy 未响应(端口 $PROXY_PORT"
info "检查: docker ps | grep node-tls-proxy"
fi
# ── 9. Node.js JA4 指纹(在 proxy 容器内测) ──────────────
echo ""
echo "【9】Node.js JA4 TLS 指纹"
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "node-tls-proxy"; then
JA4=$(docker exec node-tls-proxy node -e "
const https = require('https');
https.get('https://tls.peet.ws/api/all', res => {
let d=''; res.on('data',c=>d+=c);
res.on('end',()=>{ try{console.log(JSON.parse(d).tls.ja4);}catch(e){console.log('err');} });
}).on('error',e=>console.log('err:'+e.message));
" 2>/dev/null || echo "exec_failed")
echo " Proxy JA4: $JA4"
if [[ "$JA4" == t13* ]]; then
ok "JA4 指纹正常TLS 1.3"
else
fail "JA4 获取失败: $JA4"
fi
elif command -v node &>/dev/null; then
JA4=$(node -e "
const https = require('https');
https.get('https://tls.peet.ws/api/all', res => {
let d=''; res.on('data',c=>d+=c);
res.on('end',()=>{ try{console.log(JSON.parse(d).tls.ja4);}catch(e){console.log('err');} });
}).on('error',e=>console.log('err:'+e.message));
" 2>/dev/null || echo "err")
echo " 宿主机 JA4: $JA4"
[[ "$JA4" == t13* ]] && ok "JA4 正常" || fail "JA4 失败"
else
info "跳过 JA4 测(无 docker exec 也无宿主机 node"
fi
# ── 10. 出口 IP 验证 ───────────────────────────────────────
echo ""
echo "【10】出口 IP 信息"
IP_INFO=$(curl -sf --max-time 5 "https://ipinfo.io/json" 2>/dev/null || echo '{}')
IP=$(echo "$IP_INFO" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('ip','?'))" 2>/dev/null)
ORG=$(echo "$IP_INFO" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('org','?'))" 2>/dev/null)
CITY=$(echo "$IP_INFO" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('city','?')+', '+d.get('region','?'))" 2>/dev/null)
echo " IP: $IP"
echo " ISP: $ORG"
echo " 城市: $CITY"
if echo "$ORG" | grep -qiE "residential|comcast|verizon|optimum|spectrum|altice|fios|att|xfinity"; then
ok "ISP 看起来是住宅宽带 ✅"
elif echo "$ORG" | grep -qiE "datacenter|hosting|cloud|amazon|google|microsoft|linode|vultr|digital"; then
fail "ISP 是数据中心 IP建议换住宅宽带"
else
info "ISP 未能自动判断,请人工核查: $ORG"
fi
echo ""
echo "══════════════════════════════════════════════"
echo " 验证完成"
echo "══════════════════════════════════════════════"

View File

@ -0,0 +1,117 @@
#!/bin/bash
# test-mac.sh — 本机Mac指纹验证脚本
# 用途:验证 Node.js TLS 指纹、H2 连通性
# 运行方式bash test-mac.sh
set -euo pipefail
GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[1;33m' NC='\033[0m'
ok() { echo -e "${GREEN}$*${NC}"; }
fail() { echo -e "${RED}$*${NC}"; }
info() { echo -e "${YELLOW} $*${NC}"; }
echo "══════════════════════════════════════"
echo " MAC 本地指纹验证"
echo "══════════════════════════════════════"
# ── 1. Node.js 版本 ────────────────────────────────────────
echo ""
echo "【1】Node.js 版本"
NODE_VER=$(node --version 2>/dev/null || echo "未安装")
echo " 本机 Node: $NODE_VER"
if [[ "$NODE_VER" == v22* ]]; then
ok "Node.js v22.x — 与 Claude CLI 内置版本匹配"
else
fail "Node.js 版本不是 v22.x当前: $NODE_VERJA4 指纹可能不一致"
info "安装 v22: nvm install 22 && nvm use 22"
fi
# ── 2. Node.js JA4 TLS 指纹 ────────────────────────────────
echo ""
echo "【2】Node.js TLS JA4 指纹(服务器实际产生的指纹)"
JA4=$(node -e "
const https = require('https');
https.get('https://tls.peet.ws/api/all', res => {
let d=''; res.on('data',c=>d+=c);
res.on('end',()=>{
try { console.log(JSON.parse(d).tls.ja4); } catch(e){ console.log('parse_error'); }
});
}).on('error', e => console.log('error:'+e.message));
" 2>/dev/null)
echo " JA4: $JA4"
if [[ "$JA4" == t13* ]]; then
ok "TLS 1.3,看起来是合法的 Node.js 指纹"
else
fail "JA4 获取失败或格式异常: $JA4"
fi
# ── 3. H2 连接 api.anthropic.com ───────────────────────────
echo ""
echo "【3】HTTP/2 连通性Anthropic"
H2_RESULT=$(node -e "
const http2 = require('http2');
const s = http2.connect('https://api.anthropic.com', {}, () => {
console.log('ok:' + s.socket.alpnProtocol);
s.close();
});
s.on('error', e => { console.log('err:'+e.message); process.exit(0); });
setTimeout(()=>{ console.log('timeout'); process.exit(0); }, 5000);
" 2>/dev/null)
if [[ "$H2_RESULT" == ok:h2 ]]; then
ok "Anthropic H2 连接成功alpnProtocol: h2"
else
fail "H2 连接失败: $H2_RESULT"
fi
# ── 4. H2 连接 googleapis.com ──────────────────────────────
echo ""
echo "【4】HTTP/2 连通性Google / Gemini"
H2G=$(node -e "
const http2 = require('http2');
const s = http2.connect('https://generativelanguage.googleapis.com', {}, () => {
console.log('ok:' + s.socket.alpnProtocol);
s.close();
});
s.on('error', e => { console.log('err:'+e.message); process.exit(0); });
setTimeout(()=>{ console.log('timeout'); process.exit(0); }, 5000);
" 2>/dev/null)
if [[ "$H2G" == ok:h2 ]]; then
ok "Google API H2 连接成功"
else
fail "Google API H2 连接失败: $H2G"
fi
# ── 5. 本地 proxy 健康检查(如果本地起了) ─────────────────
echo ""
echo "【5】本地 node-tls-proxy 健康(端口 3456"
PROXY_PORT="${PROXY_PORT:-3456}"
if curl -sf "http://127.0.0.1:${PROXY_PORT}/__health" -o /tmp/proxy_health.json 2>/dev/null; then
SESSIONS=$(python3 -c "import json,sys; d=json.load(open('/tmp/proxy_health.json')); print(d.get('sessions',0))" 2>/dev/null || echo "?")
H2HOSTS=$(python3 -c "import json,sys; d=json.load(open('/tmp/proxy_health.json')); print(','.join(d.get('h2Hosts',[])))" 2>/dev/null || echo "?")
ok "Proxy 运行中 | sessions=$SESSIONS | h2Hosts=$H2HOSTS"
else
info "本地未运行 node-tls-proxy端口 $PROXY_PORT),跳过"
fi
# ── 6. Jitter 延迟分布(如果 proxy 运行中)────────────────
echo ""
echo "【6】Jitter 延迟测试5次请求通过本地 proxy"
if curl -sf "http://127.0.0.1:${PROXY_PORT:-3456}/__health" -o /dev/null 2>/dev/null; then
for i in {1..5}; do
START=$(date +%s%3N)
curl -sf -X POST "http://127.0.0.1:${PROXY_PORT:-3456}/v1/messages" \
-H "x-forwarded-host: api.anthropic.com" \
-H "content-type: application/json" \
-d '{"model":"claude-opus-4-5","max_tokens":1}' -o /dev/null 2>/dev/null || true
ELAPSED=$(($(date +%s%3N) - START))
echo " 请求 $i: ${ELAPSED}ms"
done
ok "延迟应在 80-1200ms 之间,非均匀分布"
else
info "本地未运行 proxy跳过 Jitter 测试"
fi
echo ""
echo "══════════════════════════════════════"
echo " Mac 验证完成"
echo " 关键指纹: $JA4"
echo "══════════════════════════════════════"