feat: macOS 指纹伪装 — TCP TTL/时间戳/时区 + H2优先 + Jitter增强
proxy.js: - 主机身份全面改为 macOS (hostname: alex-MBP, osType: Darwin) - macOS 版本号 (Ventura/Sonoma/Sequoia), Darwin 内核 22/23/24.x - machineId 改为 IOPlatformUUID 格式(大写 UUID) - arch: 70% arm64 / 30% x64(Apple Silicon 主流) - 遥测 platform/ddtags 改为 darwin,路径改为 /Users/ - Jitter: 指数分布,80% 快(80-300ms) / 20% 慢(400-1200ms) - H2 优先: api.anthropic.com/cloudaicompanion/generativelanguage 直接走 H2 setup-firewall.sh: - 新增 TCP TTL 强制 = 64 (iptables mangle TTL) - 新增 TCP 时间戳禁用 (net.ipv4.tcp_timestamps=0 + 持久化) - 新增系统时区设置 America/Los_Angeles - 新增 timezone 子命令、完整 status 输出
This commit is contained in:
parent
e5d78f8e56
commit
6958b0dedb
@ -1,10 +1,16 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# sub2api 指纹防泄露 iptables 规则
|
# sub2api Antigravity — 指纹防泄露 + macOS 特征伪装规则
|
||||||
# 确保只有 Node.js TLS Proxy 能直连上游 HTTPS,
|
#
|
||||||
# sub2api Go 进程即使有 bug 也无法绕过。
|
# 功能:
|
||||||
|
# 1. QUIC/UDP 阻断 — 强制走 TCP/TLS
|
||||||
|
# 2. 出站 TCP 443 限制 — 只有 nodeproxy 用户能直连
|
||||||
|
# 3. IPv6 阻断 — 消除 IPv6 泄露
|
||||||
|
# 4. TCP TTL 伪装 — 改为 64,匹配 macOS/Linux(对抗 OS 识别)
|
||||||
|
# 5. TCP 时间戳重写 — 禁用内核时间戳,防止通过 TCP TS 推算 uptime/系统时间
|
||||||
|
# 6. 系统时区设置 — 设为 America/Los_Angeles(加州时区,匹配目标用户群)
|
||||||
#
|
#
|
||||||
# 用法:
|
# 用法:
|
||||||
# sudo bash setup-firewall.sh [apply|remove|status]
|
# sudo bash setup-firewall.sh [apply|remove|status|timezone]
|
||||||
#
|
#
|
||||||
# 前置条件:
|
# 前置条件:
|
||||||
# - Node.js proxy 以专用用户 "nodeproxy" 运行
|
# - Node.js proxy 以专用用户 "nodeproxy" 运行
|
||||||
@ -14,9 +20,50 @@ set -euo pipefail
|
|||||||
|
|
||||||
NODE_PROXY_USER="${MG_NODE_PROXY_USER:-nodeproxy}"
|
NODE_PROXY_USER="${MG_NODE_PROXY_USER:-nodeproxy}"
|
||||||
CHAIN_NAME="MG_FINGERPRINT"
|
CHAIN_NAME="MG_FINGERPRINT"
|
||||||
|
TARGET_TZ="America/Los_Angeles"
|
||||||
|
|
||||||
log() { echo "[$(date '+%H:%M:%S')] $*"; }
|
log() { echo "[$(date '+%H:%M:%S')] $*"; }
|
||||||
|
|
||||||
|
# ─── 时区设置 ────────────────────────────────────────────────────────
|
||||||
|
set_timezone() {
|
||||||
|
log "Setting system timezone to $TARGET_TZ ..."
|
||||||
|
if command -v timedatectl &>/dev/null; then
|
||||||
|
timedatectl set-timezone "$TARGET_TZ"
|
||||||
|
log " timedatectl: timezone set to $(timedatectl show -p Timezone --value)"
|
||||||
|
elif [ -f "/usr/share/zoneinfo/$TARGET_TZ" ]; then
|
||||||
|
ln -sf "/usr/share/zoneinfo/$TARGET_TZ" /etc/localtime
|
||||||
|
echo "$TARGET_TZ" > /etc/timezone
|
||||||
|
log " /etc/localtime -> $TARGET_TZ"
|
||||||
|
else
|
||||||
|
log " WARNING: Cannot set timezone — timedatectl not found and zoneinfo missing"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── TCP 时间戳禁用 ──────────────────────────────────────────────────
|
||||||
|
# Linux TCP 时间戳会随系统 uptime 线性增长,对方可通过测量 TS 差值
|
||||||
|
# 推算服务器启动时间,识破"全天候在线的服务器"特征。
|
||||||
|
# 禁用后 TCP TS 选项不再发送,无法通过 TS 推断 uptime。
|
||||||
|
disable_tcp_timestamps() {
|
||||||
|
log "Disabling TCP timestamps (anti-uptime fingerprinting)..."
|
||||||
|
sysctl -w net.ipv4.tcp_timestamps=0 > /dev/null
|
||||||
|
# 持久化(防止重启后恢复)
|
||||||
|
if ! grep -q "net.ipv4.tcp_timestamps" /etc/sysctl.conf 2>/dev/null; then
|
||||||
|
echo "net.ipv4.tcp_timestamps=0" >> /etc/sysctl.conf
|
||||||
|
log " Written to /etc/sysctl.conf"
|
||||||
|
else
|
||||||
|
sed -i 's/net.ipv4.tcp_timestamps=.*/net.ipv4.tcp_timestamps=0/' /etc/sysctl.conf
|
||||||
|
log " Updated in /etc/sysctl.conf"
|
||||||
|
fi
|
||||||
|
log " TCP timestamps: DISABLED"
|
||||||
|
}
|
||||||
|
|
||||||
|
enable_tcp_timestamps() {
|
||||||
|
sysctl -w net.ipv4.tcp_timestamps=1 > /dev/null
|
||||||
|
sed -i 's/net.ipv4.tcp_timestamps=.*/net.ipv4.tcp_timestamps=1/' /etc/sysctl.conf 2>/dev/null || true
|
||||||
|
log " TCP timestamps: ENABLED (restored)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── iptables 规则 ───────────────────────────────────────────────────
|
||||||
apply_rules() {
|
apply_rules() {
|
||||||
log "Applying fingerprint firewall rules..."
|
log "Applying fingerprint firewall rules..."
|
||||||
|
|
||||||
@ -30,13 +77,13 @@ apply_rules() {
|
|||||||
# 创建自定义链(幂等)
|
# 创建自定义链(幂等)
|
||||||
iptables -N "$CHAIN_NAME" 2>/dev/null || iptables -F "$CHAIN_NAME"
|
iptables -N "$CHAIN_NAME" 2>/dev/null || iptables -F "$CHAIN_NAME"
|
||||||
|
|
||||||
# === Rule 1: QUIC 阻断 — 丢弃所有出站 UDP 443/4433 ===
|
# === Rule 1: QUIC 阻断 ===
|
||||||
iptables -A "$CHAIN_NAME" -p udp --dport 443 -j DROP \
|
iptables -A "$CHAIN_NAME" -p udp --dport 443 -j DROP \
|
||||||
-m comment --comment "MG: block QUIC/HTTP3 UDP 443"
|
-m comment --comment "MG: block QUIC/HTTP3 UDP 443"
|
||||||
iptables -A "$CHAIN_NAME" -p udp --dport 4433 -j DROP \
|
iptables -A "$CHAIN_NAME" -p udp --dport 4433 -j DROP \
|
||||||
-m comment --comment "MG: block QUIC alt UDP 4433"
|
-m comment --comment "MG: block QUIC alt UDP 4433"
|
||||||
|
|
||||||
# === Rule 2: 允许 Node.js proxy 出站 TCP 443 ===
|
# === Rule 2: 允许 nodeproxy 出站 TCP 443 ===
|
||||||
iptables -A "$CHAIN_NAME" -p tcp --dport 443 \
|
iptables -A "$CHAIN_NAME" -p tcp --dport 443 \
|
||||||
-m owner --uid-owner "$NODE_PROXY_USER" -j ACCEPT \
|
-m owner --uid-owner "$NODE_PROXY_USER" -j ACCEPT \
|
||||||
-m comment --comment "MG: allow nodeproxy TCP 443"
|
-m comment --comment "MG: allow nodeproxy TCP 443"
|
||||||
@ -45,43 +92,66 @@ apply_rules() {
|
|||||||
iptables -A "$CHAIN_NAME" -p tcp --dport 443 -j REJECT --reject-with tcp-reset \
|
iptables -A "$CHAIN_NAME" -p tcp --dport 443 -j REJECT --reject-with tcp-reset \
|
||||||
-m comment --comment "MG: block non-proxy TCP 443"
|
-m comment --comment "MG: block non-proxy TCP 443"
|
||||||
|
|
||||||
# 将自定义链挂载到 OUTPUT(幂等)
|
# 挂载到 OUTPUT(幂等)
|
||||||
if ! iptables -C OUTPUT -j "$CHAIN_NAME" 2>/dev/null; then
|
if ! iptables -C OUTPUT -j "$CHAIN_NAME" 2>/dev/null; then
|
||||||
iptables -A OUTPUT -j "$CHAIN_NAME"
|
iptables -A OUTPUT -j "$CHAIN_NAME"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# === Rule 4: IPv6 全面阻断 ===
|
# === Rule 4: IPv6 全面阻断 ===
|
||||||
ip6tables -N "${CHAIN_NAME}_V6" 2>/dev/null || ip6tables -F "${CHAIN_NAME}_V6"
|
ip6tables -N "${CHAIN_NAME}_V6" 2>/dev/null || ip6tables -F "${CHAIN_NAME}_V6"
|
||||||
# 允许回环
|
|
||||||
ip6tables -A "${CHAIN_NAME}_V6" -o lo -j ACCEPT \
|
ip6tables -A "${CHAIN_NAME}_V6" -o lo -j ACCEPT \
|
||||||
-m comment --comment "MG: allow IPv6 loopback"
|
-m comment --comment "MG: allow IPv6 loopback"
|
||||||
# 阻断其他 IPv6 出站
|
|
||||||
ip6tables -A "${CHAIN_NAME}_V6" -j DROP \
|
ip6tables -A "${CHAIN_NAME}_V6" -j DROP \
|
||||||
-m comment --comment "MG: block all IPv6 outbound"
|
-m comment --comment "MG: block all IPv6 outbound"
|
||||||
|
|
||||||
if ! ip6tables -C OUTPUT -j "${CHAIN_NAME}_V6" 2>/dev/null; then
|
if ! ip6tables -C OUTPUT -j "${CHAIN_NAME}_V6" 2>/dev/null; then
|
||||||
ip6tables -A OUTPUT -j "${CHAIN_NAME}_V6"
|
ip6tables -A OUTPUT -j "${CHAIN_NAME}_V6"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# === Rule 5: TCP TTL 伪装 (macOS TTL = 64) ===
|
||||||
|
# macOS 和 Linux 默认 TTL 都是 64,但数据中心 Linux 有些发行版是 128。
|
||||||
|
# 强制设为 64 确保一致,并防止"服务器离对方 0 跳"露馅。
|
||||||
|
iptables -t mangle -N "${CHAIN_NAME}_TTL" 2>/dev/null || iptables -t mangle -F "${CHAIN_NAME}_TTL"
|
||||||
|
iptables -t mangle -A "${CHAIN_NAME}_TTL" -p tcp --dport 443 \
|
||||||
|
-j TTL --ttl-set 64 \
|
||||||
|
-m comment --comment "MG: spoof TTL=64 (macOS)"
|
||||||
|
if ! iptables -t mangle -C OUTPUT -j "${CHAIN_NAME}_TTL" 2>/dev/null; then
|
||||||
|
iptables -t mangle -A OUTPUT -j "${CHAIN_NAME}_TTL"
|
||||||
|
fi
|
||||||
|
|
||||||
log "Firewall rules applied successfully."
|
log "Firewall rules applied successfully."
|
||||||
log " - UDP 443/4433: BLOCKED (QUIC)"
|
log " - UDP 443/4433: BLOCKED (QUIC)"
|
||||||
log " - TCP 443: ONLY '$NODE_PROXY_USER' allowed"
|
log " - TCP 443: ONLY '$NODE_PROXY_USER' allowed"
|
||||||
log " - IPv6 outbound: BLOCKED"
|
log " - IPv6 outbound: BLOCKED"
|
||||||
|
log " - TCP TTL: FORCED to 64 (macOS spoof)"
|
||||||
|
|
||||||
|
# === TCP 时间戳禁用 ===
|
||||||
|
disable_tcp_timestamps
|
||||||
|
|
||||||
|
# === 时区设置 ===
|
||||||
|
set_timezone
|
||||||
|
|
||||||
|
log ""
|
||||||
|
log "=== All anti-fingerprint measures applied ==="
|
||||||
|
log " OS Fingerprint: TTL=64 (macOS/Linux)"
|
||||||
|
log " TCP Timestamps: Disabled (anti-uptime leak)"
|
||||||
|
log " Timezone: $TARGET_TZ"
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_rules() {
|
remove_rules() {
|
||||||
log "Removing fingerprint firewall rules..."
|
log "Removing fingerprint firewall rules..."
|
||||||
|
|
||||||
# 从 OUTPUT 移除引用
|
|
||||||
iptables -D OUTPUT -j "$CHAIN_NAME" 2>/dev/null || true
|
iptables -D OUTPUT -j "$CHAIN_NAME" 2>/dev/null || true
|
||||||
ip6tables -D OUTPUT -j "${CHAIN_NAME}_V6" 2>/dev/null || true
|
ip6tables -D OUTPUT -j "${CHAIN_NAME}_V6" 2>/dev/null || true
|
||||||
|
iptables -t mangle -D OUTPUT -j "${CHAIN_NAME}_TTL" 2>/dev/null || true
|
||||||
|
|
||||||
# 清空并删除自定义链
|
|
||||||
iptables -F "$CHAIN_NAME" 2>/dev/null || true
|
iptables -F "$CHAIN_NAME" 2>/dev/null || true
|
||||||
iptables -X "$CHAIN_NAME" 2>/dev/null || true
|
iptables -X "$CHAIN_NAME" 2>/dev/null || true
|
||||||
ip6tables -F "${CHAIN_NAME}_V6" 2>/dev/null || true
|
ip6tables -F "${CHAIN_NAME}_V6" 2>/dev/null || true
|
||||||
ip6tables -X "${CHAIN_NAME}_V6" 2>/dev/null || true
|
ip6tables -X "${CHAIN_NAME}_V6" 2>/dev/null || true
|
||||||
|
iptables -t mangle -F "${CHAIN_NAME}_TTL" 2>/dev/null || true
|
||||||
|
iptables -t mangle -X "${CHAIN_NAME}_TTL" 2>/dev/null || true
|
||||||
|
|
||||||
|
enable_tcp_timestamps
|
||||||
log "Firewall rules removed."
|
log "Firewall rules removed."
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,16 +159,29 @@ show_status() {
|
|||||||
log "=== IPv4 MG_FINGERPRINT chain ==="
|
log "=== IPv4 MG_FINGERPRINT chain ==="
|
||||||
iptables -L "$CHAIN_NAME" -n -v 2>/dev/null || echo "(not found)"
|
iptables -L "$CHAIN_NAME" -n -v 2>/dev/null || echo "(not found)"
|
||||||
echo
|
echo
|
||||||
|
log "=== IPv4 mangle TTL chain ==="
|
||||||
|
iptables -t mangle -L "${CHAIN_NAME}_TTL" -n -v 2>/dev/null || echo "(not found)"
|
||||||
|
echo
|
||||||
log "=== IPv6 MG_FINGERPRINT_V6 chain ==="
|
log "=== IPv6 MG_FINGERPRINT_V6 chain ==="
|
||||||
ip6tables -L "${CHAIN_NAME}_V6" -n -v 2>/dev/null || echo "(not found)"
|
ip6tables -L "${CHAIN_NAME}_V6" -n -v 2>/dev/null || echo "(not found)"
|
||||||
|
echo
|
||||||
|
log "=== TCP Timestamps ==="
|
||||||
|
sysctl net.ipv4.tcp_timestamps
|
||||||
|
echo
|
||||||
|
log "=== System Timezone ==="
|
||||||
|
timedatectl show -p Timezone --value 2>/dev/null || cat /etc/timezone 2>/dev/null || echo "(unknown)"
|
||||||
|
echo
|
||||||
|
log "=== Current TTL (outbound) ==="
|
||||||
|
sysctl net.ipv4.ip_default_ttl
|
||||||
}
|
}
|
||||||
|
|
||||||
case "${1:-apply}" in
|
case "${1:-apply}" in
|
||||||
apply) apply_rules ;;
|
apply) apply_rules ;;
|
||||||
remove) remove_rules ;;
|
remove) remove_rules ;;
|
||||||
status) show_status ;;
|
status) show_status ;;
|
||||||
|
timezone) set_timezone ;;
|
||||||
*)
|
*)
|
||||||
echo "Usage: $0 [apply|remove|status]"
|
echo "Usage: $0 [apply|remove|status|timezone]"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@ -39,88 +39,75 @@ const h2Sessions = new Map();
|
|||||||
// 2. 不同账号的特征互不相同(无共享池、无碰撞)
|
// 2. 不同账号的特征互不相同(无共享池、无碰撞)
|
||||||
// 3. 每个字段都像人手动设置的,不是程序生成的
|
// 3. 每个字段都像人手动设置的,不是程序生成的
|
||||||
|
|
||||||
// hostname 构造词表 — 组合后空间 > 100万,基本不碰撞
|
// ─── macOS 主机身份词表 ──────────────────────────────────────────
|
||||||
const HN_PREFIX = ['dev','code','work','build','my','home','lab','eng','hack','prog','desk','box','main','personal','linux'];
|
// macOS 用户 hostname 习惯: "alex-MBP", "sam-MacBook-Pro" 等
|
||||||
const HN_MIDDLE = ['','station','machine','server','node','pc','setup','rig','env','hub'];
|
const MBP_NAMES = ['alex','sam','chris','max','lee','kai','jamie','taylor','morgan','casey',
|
||||||
const HN_STYLE = ['dash','dot','bare']; // 连接风格
|
'drew','avery','riley','blake','jordan','ryan','parker','quinn','reese','cameron'];
|
||||||
|
const MBP_SUFFIX = ['-MBP','-MacBook','-MacBook-Pro','-MacBook-Air',"s-MBP","s-MacBook","s-MacBook-Pro"];
|
||||||
// 用户名词表 — 真实开发者常用,组合后也是高基数
|
|
||||||
const UN_FIRST = ['alex','sam','chris','jordan','max','lee','kai','pat','jamie','taylor','morgan','casey','drew','avery','riley','blake','quinn','reese','cameron','skyler','dev','coder','user','admin','ubuntu','runner'];
|
|
||||||
const UN_SUFFIX = ['','dev','eng','42','_dev','01','x','z','_','99','007'];
|
|
||||||
|
|
||||||
function generateHostIdentity(seed) {
|
function generateHostIdentity(seed) {
|
||||||
// 确定性哈希工具:同一 seed+suffix 永远返回同一结果
|
const h = (s) => crypto.createHash('sha256').update(seed + ':' + s).digest();
|
||||||
const h = (suffix) => crypto.createHash('sha256').update(seed + ':' + suffix).digest();
|
|
||||||
|
|
||||||
// ── hostname: 组合生成,如 "alex-devstation", "work-box-7f3a" ──
|
// ── hostname: macOS 风格 ──
|
||||||
const hb = h('hostname');
|
const hb = h('hostname');
|
||||||
const prefix = HN_PREFIX[hb.readUInt8(0) % HN_PREFIX.length];
|
const name = MBP_NAMES[hb.readUInt8(0) % MBP_NAMES.length];
|
||||||
const middle = HN_MIDDLE[hb.readUInt8(1) % HN_MIDDLE.length];
|
const sfx = MBP_SUFFIX[hb.readUInt8(1) % MBP_SUFFIX.length];
|
||||||
const style = HN_STYLE[hb.readUInt8(2) % HN_STYLE.length];
|
const hostname = `${name}${sfx}`;
|
||||||
const tail = hb.slice(3, 5).toString('hex'); // 4 hex chars 保证唯一
|
|
||||||
let hostname;
|
|
||||||
if (middle) {
|
|
||||||
const sep = style === 'dot' ? '.' : style === 'dash' ? '-' : '';
|
|
||||||
hostname = `${prefix}${sep}${middle}`;
|
|
||||||
} else {
|
|
||||||
// 无中间词时必须加 hex 尾缀,避免 hostname 太短(如裸 "my"、"dev")
|
|
||||||
hostname = `${prefix}-${tail}`;
|
|
||||||
}
|
|
||||||
// 有中间词时 50% 概率加 hex 尾缀(真实场景很多人用 hostname 如 "dev-box-a3f2")
|
|
||||||
if (middle && hb.readUInt8(5) % 2 === 0) hostname += `-${tail}`;
|
|
||||||
|
|
||||||
// ── username: 组合生成,如 "alexdev", "sam42", "chris_dev" ──
|
// ── username: 取自 hostname 名字(真实 Mac 行为) ──
|
||||||
const ub = h('username');
|
const username = name;
|
||||||
const first = UN_FIRST[ub.readUInt8(0) % UN_FIRST.length];
|
|
||||||
const suffix = UN_SUFFIX[ub.readUInt8(1) % UN_SUFFIX.length];
|
|
||||||
const username = `${first}${suffix}`;
|
|
||||||
|
|
||||||
// ── terminal & shell: 按权重分布(xterm-256color 占大多数) ──
|
// ── terminal: macOS 常见终端分布 ──
|
||||||
const termRoll = h('terminal').readUInt8(0) % 100;
|
const termRoll = h('terminal').readUInt8(0) % 100;
|
||||||
const terminal = termRoll < 60 ? 'xterm-256color' :
|
const terminal = termRoll < 75 ? 'xterm-256color' :
|
||||||
termRoll < 75 ? 'screen-256color' :
|
termRoll < 88 ? 'screen-256color' :
|
||||||
termRoll < 88 ? 'tmux-256color' :
|
termRoll < 96 ? 'alacritty' : 'kitty';
|
||||||
termRoll < 95 ? 'alacritty' : 'rxvt-unicode-256color';
|
|
||||||
|
|
||||||
|
// ── shell: macOS 默认 zsh(Catalina+);部分用 bash/fish ──
|
||||||
const shellRoll = h('shell').readUInt8(0) % 100;
|
const shellRoll = h('shell').readUInt8(0) % 100;
|
||||||
const shell = shellRoll < 55 ? '/bin/bash' :
|
const shell = shellRoll < 65 ? '/bin/zsh' :
|
||||||
shellRoll < 85 ? '/bin/zsh' :
|
shellRoll < 82 ? '/usr/local/bin/zsh' :
|
||||||
shellRoll < 95 ? '/usr/bin/bash' : '/usr/bin/zsh';
|
shellRoll < 93 ? '/bin/bash' : '/opt/homebrew/bin/fish';
|
||||||
|
|
||||||
// ── host.id: /etc/machine-id (32 hex chars, Linux 标准) ──
|
// ── host.id: macOS IOPlatformUUID 格式(大写 UUID) ──
|
||||||
const machineId = h('machine-id').slice(0, 16).toString('hex');
|
const mid = h('machine-id');
|
||||||
|
const machineId = [
|
||||||
|
mid.slice(0,4).toString('hex').toUpperCase(),
|
||||||
|
mid.slice(4,6).toString('hex').toUpperCase(),
|
||||||
|
mid.slice(6,8).toString('hex').toUpperCase(),
|
||||||
|
mid.slice(8,10).toString('hex').toUpperCase(),
|
||||||
|
mid.slice(10,16).toString('hex').toUpperCase(),
|
||||||
|
].join('-');
|
||||||
|
|
||||||
// ── PID: 每个 session 随机生成,模拟每次启动新进程 ──
|
// ── PID: macOS GUI 应用 PID 通常较小 ──
|
||||||
// 不用 seed 确定性生成,因为真实 CLI 每次启动都是新 PID
|
const pid = 500 + Math.floor(Math.random() * 8000);
|
||||||
const pid = 1000 + Math.floor(Math.random() * 64000);
|
|
||||||
|
|
||||||
// ── kernel version: 模拟真实 Linux 发行版 ──
|
// ── macOS 版本: 13(Ventura)/14(Sonoma)/15(Sequoia) ──
|
||||||
const kb = h('kernel');
|
const kb = h('kernel');
|
||||||
const kernelMajor = 5 + (kb.readUInt8(0) % 2); // 5 or 6
|
const macosMajor = 13 + (kb.readUInt8(0) % 3);
|
||||||
const kernelMinor = kb.readUInt8(1) % 20;
|
const macosMinor = kb.readUInt8(1) % 8;
|
||||||
const kernelPatch = kb.readUInt8(2) % 200;
|
const macosPatch = kb.readUInt8(2) % 5;
|
||||||
const ubuntuBuild = 50 + (kb.readUInt8(3) % 150);
|
// Darwin 内核: macOS 13=22.x, 14=23.x, 15=24.x
|
||||||
const osVersion = `#${ubuntuBuild}-Ubuntu SMP`;
|
const darwinMajor = 22 + (macosMajor - 13);
|
||||||
|
const darwinMinor = kb.readUInt8(3) % 7;
|
||||||
|
const darwinPatch = kb.readUInt8(4) % 5;
|
||||||
|
const osVersion = `${macosMajor}.${macosMinor}.${macosPatch}`;
|
||||||
|
|
||||||
// ── 可执行文件路径: 按安装方式分布 ──
|
// ── arch: Apple Silicon arm64 占 70%,Intel x64 占 30% ──
|
||||||
|
const arch = h('arch').readUInt8(0) % 100 < 70 ? 'arm64' : 'x64';
|
||||||
|
|
||||||
|
// ── 可执行文件路径: macOS 常见安装位置 ──
|
||||||
const pathRoll = h('execpath').readUInt8(0) % 100;
|
const pathRoll = h('execpath').readUInt8(0) % 100;
|
||||||
const executablePath = pathRoll < 40 ? `/home/${username}/.claude/local/claude` :
|
const executablePath = pathRoll < 50 ? `/Users/${username}/.claude/local/claude` :
|
||||||
pathRoll < 70 ? '/usr/local/bin/claude' :
|
pathRoll < 80 ? '/usr/local/bin/claude' :
|
||||||
pathRoll < 90 ? `/home/${username}/.local/bin/claude` :
|
pathRoll < 95 ? `/Users/${username}/.local/bin/claude` :
|
||||||
'/usr/bin/claude';
|
'/opt/homebrew/bin/claude';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hostname,
|
hostname, username, terminal, shell, machineId, pid, arch,
|
||||||
username,
|
osType: 'Darwin',
|
||||||
terminal,
|
|
||||||
shell,
|
|
||||||
machineId,
|
|
||||||
pid,
|
|
||||||
arch: 'x64',
|
|
||||||
osType: 'Linux',
|
|
||||||
osVersion,
|
osVersion,
|
||||||
kernelRelease: `${kernelMajor}.${kernelMinor}.${kernelPatch}-generic`,
|
kernelRelease: `${darwinMajor}.${darwinMinor}.${darwinPatch}`,
|
||||||
// service.instance.id: 每个 session 唯一(CLI 用 randomUUID)
|
|
||||||
serviceInstanceId: crypto.randomUUID(),
|
serviceInstanceId: crypto.randomUUID(),
|
||||||
executablePath,
|
executablePath,
|
||||||
executableName: 'claude',
|
executableName: 'claude',
|
||||||
@ -128,20 +115,21 @@ function generateHostIdentity(seed) {
|
|||||||
commandArgs: [],
|
commandArgs: [],
|
||||||
runtimeName: 'nodejs',
|
runtimeName: 'nodejs',
|
||||||
runtimeVersion: FAKE_NODE_VERSION.replace('v', ''),
|
runtimeVersion: FAKE_NODE_VERSION.replace('v', ''),
|
||||||
// ripgrep 信息也按 seed 生成,不同账号不一样
|
|
||||||
ripgrepVersion: (() => {
|
ripgrepVersion: (() => {
|
||||||
const rv = h('ripgrep');
|
const rv = h('ripgrep');
|
||||||
const versions = ['14.1.1','14.1.0','14.0.2','13.0.0','13.0.1','14.0.1','14.0.0'];
|
return ['14.1.1','14.1.0','14.0.2','13.0.0','13.0.1','14.0.1','14.0.0'][rv.readUInt8(0) % 7];
|
||||||
return versions[rv.readUInt8(0) % versions.length];
|
|
||||||
})(),
|
})(),
|
||||||
ripgrepPath: (() => {
|
ripgrepPath: (() => {
|
||||||
const rp = h('rgpath');
|
const rp = h('rgpath');
|
||||||
const paths = ['/usr/bin/rg','/usr/local/bin/rg','/home/'+username+'/.cargo/bin/rg','/snap/bin/rg','/usr/bin/rg','/usr/bin/rg'];
|
return [
|
||||||
return paths[rp.readUInt8(0) % paths.length];
|
'/opt/homebrew/bin/rg',
|
||||||
|
'/usr/local/bin/rg',
|
||||||
|
`/Users/${username}/.cargo/bin/rg`,
|
||||||
|
'/usr/local/opt/ripgrep/bin/rg',
|
||||||
|
][rp.readUInt8(0) % 4];
|
||||||
})(),
|
})(),
|
||||||
// MCP server 数量(真实用户 0~6 个,影响启动事件序列)
|
mcpServerCount: 1 + (h('mcp').readUInt8(0) % 5),
|
||||||
mcpServerCount: 1 + (h('mcp').readUInt8(0) % 5), // 1~5
|
mcpFailCount: h('mcp').readUInt8(1) % 3,
|
||||||
mcpFailCount: h('mcp').readUInt8(1) % 3, // 0~2 个失败
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,8 +161,9 @@ function generateDeviceId(accountSeed) {
|
|||||||
// ─── OTEL Resource Attributes (匹配 CLI 的 detectResources) ───
|
// ─── OTEL Resource Attributes (匹配 CLI 的 detectResources) ───
|
||||||
|
|
||||||
function buildEnvBlock(hostId) {
|
function buildEnvBlock(hostId) {
|
||||||
|
const platformStr = 'darwin';
|
||||||
return {
|
return {
|
||||||
platform: 'linux',
|
platform: platformStr,
|
||||||
node_version: FAKE_NODE_VERSION,
|
node_version: FAKE_NODE_VERSION,
|
||||||
terminal: hostId.terminal,
|
terminal: hostId.terminal,
|
||||||
package_managers: 'npm',
|
package_managers: 'npm',
|
||||||
@ -188,13 +177,13 @@ function buildEnvBlock(hostId) {
|
|||||||
version: CLI_VERSION,
|
version: CLI_VERSION,
|
||||||
arch: hostId.arch,
|
arch: hostId.arch,
|
||||||
is_claude_code_remote: false,
|
is_claude_code_remote: false,
|
||||||
deployment_environment: 'unknown-linux',
|
deployment_environment: `unknown-${platformStr}`,
|
||||||
is_conductor: false,
|
is_conductor: false,
|
||||||
version_base: CLI_VERSION,
|
version_base: CLI_VERSION,
|
||||||
build_time: BUILD_TIME,
|
build_time: BUILD_TIME,
|
||||||
is_local_agent_mode: false,
|
is_local_agent_mode: false,
|
||||||
vcs: 'git',
|
vcs: 'git',
|
||||||
platform_raw: 'linux',
|
platform_raw: platformStr,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,7 +312,7 @@ function sendDatadogLog(eventName, session, model) {
|
|||||||
|
|
||||||
const entry = {
|
const entry = {
|
||||||
ddsource: 'nodejs',
|
ddsource: 'nodejs',
|
||||||
ddtags: `event:${eventName},arch:${hostId.arch},client_type:cli,model:${model || 'claude-sonnet-4-6'},platform:linux,user_type:external,version:${CLI_VERSION},version_base:${CLI_VERSION}`,
|
ddtags: `event:${eventName},arch:${hostId.arch},client_type:cli,model:${model || 'claude-sonnet-4-6'},platform:darwin,user_type:external,version:${CLI_VERSION},version_base:${CLI_VERSION}`,
|
||||||
message: eventName,
|
message: eventName,
|
||||||
service: 'claude-code',
|
service: 'claude-code',
|
||||||
hostname: hostId.hostname,
|
hostname: hostId.hostname,
|
||||||
@ -335,14 +324,14 @@ function sendDatadogLog(eventName, session, model) {
|
|||||||
is_interactive: 'true',
|
is_interactive: 'true',
|
||||||
client_type: 'cli',
|
client_type: 'cli',
|
||||||
process_metrics: pm,
|
process_metrics: pm,
|
||||||
platform: 'linux',
|
platform: 'darwin',
|
||||||
platform_raw: 'linux',
|
platform_raw: 'darwin',
|
||||||
arch: hostId.arch,
|
arch: hostId.arch,
|
||||||
node_version: FAKE_NODE_VERSION,
|
node_version: FAKE_NODE_VERSION,
|
||||||
version: CLI_VERSION,
|
version: CLI_VERSION,
|
||||||
version_base: CLI_VERSION,
|
version_base: CLI_VERSION,
|
||||||
build_time: BUILD_TIME,
|
build_time: BUILD_TIME,
|
||||||
deployment_environment: 'unknown-linux',
|
deployment_environment: 'unknown-darwin',
|
||||||
vcs: 'git',
|
vcs: 'git',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -667,11 +656,29 @@ async function proxyRequest(req, res) {
|
|||||||
// 请求前发遥测(仅 /v1/messages 请求)
|
// 请求前发遥测(仅 /v1/messages 请求)
|
||||||
if (req.url.includes('/v1/messages') && TELEMETRY_ENABLED) {
|
if (req.url.includes('/v1/messages') && TELEMETRY_ENABLED) {
|
||||||
emitPreRequestTelemetry(savedHeaders, body);
|
emitPreRequestTelemetry(savedHeaders, body);
|
||||||
// 随机延迟 50-200ms 模拟真实 CLI 行为
|
|
||||||
await new Promise(r => setTimeout(r, 50 + Math.floor(Math.random() * 150)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (h2Hosts.has(targetHost)) {
|
// ── Jitter 注入 ──────────────────────────────────────────────────
|
||||||
|
// 模拟人类编码间歇:80% 快速响应(80-300ms),20% 慢速思考(400-1200ms)
|
||||||
|
// 使用 -log(rand) 指数衰减使延迟尾部更接近真实键盘输入节奏
|
||||||
|
const jitterMs = (() => {
|
||||||
|
if (Math.random() < 0.80) {
|
||||||
|
return Math.floor(80 + (-Math.log(Math.random()) * 90)); // 快:~80-300ms
|
||||||
|
}
|
||||||
|
return Math.floor(400 + Math.random() * 800); // 慢:400-1200ms
|
||||||
|
})();
|
||||||
|
await new Promise(r => setTimeout(r, jitterMs));
|
||||||
|
|
||||||
|
// ── H2 优先策略 ──────────────────────────────────────────────────
|
||||||
|
// Anthropic/Google API 均支持 HTTP/2。
|
||||||
|
// 直接走 H2 = Node.js 原生帧顺序,与真实 CLI 完全一致。
|
||||||
|
// 其他 host 维持原有 H1→H2 自动切换逻辑。
|
||||||
|
const H2_PREFER_HOSTS = new Set([
|
||||||
|
'api.anthropic.com',
|
||||||
|
'cloudaicompanion.googleapis.com',
|
||||||
|
'generativelanguage.googleapis.com',
|
||||||
|
]);
|
||||||
|
if (H2_PREFER_HOSTS.has(targetHost) || h2Hosts.has(targetHost)) {
|
||||||
await sendViaH2(targetHost, req.method, req.url, req.headers, body, res, savedHeaders);
|
await sendViaH2(targetHost, req.method, req.url, req.headers, body, res, savedHeaders);
|
||||||
} else {
|
} else {
|
||||||
await sendViaH1(targetHost, req.method, req.url, req.headers, body, res, savedHeaders);
|
await sendViaH1(targetHost, req.method, req.url, req.headers, body, res, savedHeaders);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user