#!/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】出站 TTL(macOS 特征)" 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=64(macOS/Linux 标准值)" else fail "TTL 不为 64(当前: $TTL)" fi # ── 4. TCP Window Size ───────────────────────────────────── echo "" echo "【4】TCP Window Size(macOS 特征)" 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 包含 65535(macOS 特征)" 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_VER),JA4 指纹可能不匹配" 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 "══════════════════════════════════════════════"