Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 90 additions & 115 deletions cac
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,52 @@ if [[ -n "$PROXY" ]] && [[ -f "$CAC_DIR/relay.js" ]]; then
echo "$_rport" > "$_relay_port_file"
fi

# ── TUN 直连路由(自动检测 + 自动修复)──
_proxy_hp="${PROXY##*@}"; _proxy_hp="${_proxy_hp##*://}"
_proxy_host="${_proxy_hp%%:*}"
if [[ "$_proxy_host" != "127."* && "$_proxy_host" != "localhost" ]]; then
_tun_active=false
if [[ "$(uname -s)" == "Darwin" ]]; then
_tun_count=$(ifconfig 2>/dev/null | grep -cE '^utun[0-9]+' || echo 0)
[[ "$_tun_count" -gt 3 ]] && _tun_active=true
else
ip link show tun0 >/dev/null 2>&1 && _tun_active=true
fi

if [[ "$_tun_active" == "true" ]]; then
_need_route=false
if [[ "$(uname -s)" == "Darwin" ]]; then
_default_gw=$(route -n get default 2>/dev/null | awk '/gateway:/{print $2}')
_route_gw=$(route -n get "$_proxy_host" 2>/dev/null | awk '/gateway:/{print $2}')
[[ -n "$_default_gw" && "$_route_gw" != "$_default_gw" ]] && _need_route=true
else
_default_gw=$(ip route show default 2>/dev/null | awk '{print $3; exit}')
_default_iface=$(ip route show default 2>/dev/null | awk '{print $5; exit}')
if [[ -n "$_default_gw" ]] && ! ip route show "$_proxy_host/32" 2>/dev/null | grep -q via; then
_need_route=true
fi
fi

if [[ "$_need_route" == "true" ]]; then
echo "[cac] TUN detected, adding direct route for proxy ..." >&2
_route_ok=false
if [[ "$(uname -s)" == "Darwin" ]]; then
sudo route delete -host "$_proxy_host" >/dev/null 2>&1 || true
sudo route add -host "$_proxy_host" "$_default_gw" >/dev/null 2>&1 && _route_ok=true
else
sudo ip route del "$_proxy_host/32" 2>/dev/null || true
sudo ip route add "$_proxy_host/32" via "$_default_gw" dev "$_default_iface" 2>/dev/null && _route_ok=true
fi
if [[ "$_route_ok" == "true" ]]; then
echo "$_proxy_host" > "$CAC_DIR/relay_route_ip"
echo "[cac] ✓ route added: $_proxy_host → $_default_gw" >&2
else
echo "[cac] ⚠ route failed, proxy may not connect with TUN active" >&2
fi
fi
fi
fi

# 覆盖代理指向本地 relay
if [[ -f "$_relay_port_file" ]]; then
_rport=$(tr -d '[:space:]' < "$_relay_port_file")
Expand Down Expand Up @@ -1785,7 +1831,7 @@ cmd_env() {
}

# ━━━ cmd_relay.sh ━━━
# ── cmd: relay(本地中转,绕过 TUN)──────────────────────────────
# ── relay: 本地中转 + TUN 路由管理 ─────────────────────────────

_relay_start() {
local name="${1:-$(_current_env)}"
Expand Down Expand Up @@ -1832,7 +1878,6 @@ _relay_stop() {
local pid; pid=$(tr -d '[:space:]' < "$pid_file")
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
kill "$pid" 2>/dev/null
# 等待进程退出
local _i
for _i in {1..20}; do
kill -0 "$pid" 2>/dev/null || break
Expand Down Expand Up @@ -1861,10 +1906,8 @@ _relay_add_route() {
local proxy_host; proxy_host=$(_proxy_host_port "$proxy")
proxy_host="${proxy_host%%:*}"

# 跳过已是 IP 的回环地址
[[ "$proxy_host" == "127."* || "$proxy_host" == "localhost" ]] && return 0

# 解析为 IP
local proxy_ip
proxy_ip=$(python3 -c "import socket; print(socket.gethostbyname('$proxy_host'))" 2>/dev/null || echo "$proxy_host")

Expand All @@ -1874,12 +1917,11 @@ _relay_add_route() {
gateway=$(route -n get default 2>/dev/null | awk '/gateway:/{print $2}')
[[ -z "$gateway" ]] && return 1

# 检查是否已有直连路由
local current_gw
current_gw=$(route -n get "$proxy_ip" 2>/dev/null | awk '/gateway:/{print $2}')
[[ "$current_gw" == "$gateway" ]] && return 0

echo " 添加直连路由:$proxy_ip → $gateway(需要 sudo)"
sudo route delete -host "$proxy_ip" >/dev/null 2>&1 || true
sudo route add -host "$proxy_ip" "$gateway" >/dev/null 2>&1 || return 1
echo "$proxy_ip" > "$CAC_DIR/relay_route_ip"

Expand All @@ -1889,7 +1931,7 @@ _relay_add_route() {
iface=$(ip route show default 2>/dev/null | awk '{print $5; exit}')
[[ -z "$gateway" ]] && return 1

echo " 添加直连路由:$proxy_ip → $gateway dev $iface(需要 sudo)"
sudo ip route del "$proxy_ip/32" 2>/dev/null || true
sudo ip route add "$proxy_ip/32" via "$gateway" dev "$iface" 2>/dev/null || return 1
echo "$proxy_ip" > "$CAC_DIR/relay_route_ip"
fi
Expand Down Expand Up @@ -1925,76 +1967,41 @@ _detect_tun_active() {
fi
}

# ── 用户命令 ─────────────────────────────────────────────────────

cmd_relay() {
_require_setup
local current; current=$(_current_env)
[[ -z "$current" ]] && { echo "错误:未激活环境,先运行 'cac <name>'" >&2; exit 1; }
# 检查上游代理路由是否正确(不需要 sudo)
_relay_route_ok() {
local proxy="$1"
local proxy_host; proxy_host=$(_proxy_host_port "$proxy")
proxy_host="${proxy_host%%:*}"

local env_dir="$ENVS_DIR/$current"
local action="${1:-status}"
local flag="${2:-}"

case "$action" in
on)
echo "on" > "$env_dir/relay"
echo "$(_green "✓") Relay 已启用(环境:$(_bold "$current"))"

# --route 标志:添加直连路由
if [[ "$flag" == "--route" ]]; then
local proxy; proxy=$(_read "$env_dir/proxy")
_relay_add_route "$proxy"
fi
[[ "$proxy_host" == "127."* || "$proxy_host" == "localhost" ]] && return 0

# 如果 relay 没在运行,启动它
if ! _relay_is_running; then
printf " 启动 relay ... "
if _relay_start "$current"; then
local port; port=$(_read "$CAC_DIR/relay.port")
echo "$(_green "✓") 127.0.0.1:$port"
else
echo "$(_red "✗ 启动失败")"
fi
fi
echo " 下次启动 claude 时将自动通过本地中转连接代理"
;;
off)
rm -f "$env_dir/relay"
_relay_stop
echo "$(_green "✓") Relay 已停用(环境:$(_bold "$current"))"
;;
status)
if [[ -f "$env_dir/relay" ]] && [[ "$(_read "$env_dir/relay")" == "on" ]]; then
echo "Relay 模式:$(_green "已启用")"
else
echo "Relay 模式:未启用"
if _detect_tun_active; then
echo " $(_yellow "⚠") 检测到 TUN 模式,建议运行 'cac relay on'"
fi
return
fi
local proxy_ip
proxy_ip=$(python3 -c "import socket; print(socket.gethostbyname('$proxy_host'))" 2>/dev/null || echo "$proxy_host")

if _relay_is_running; then
local pid; pid=$(_read "$CAC_DIR/relay.pid")
local port; port=$(_read "$CAC_DIR/relay.port" "未知")
echo "Relay 进程:$(_green "运行中") (PID=$pid, 端口=$port)"
else
echo "Relay 进程:$(_yellow "未启动")(将在 claude 启动时自动启动)"
fi
local os; os=$(_detect_os)
if [[ "$os" == "macos" ]]; then
local default_gw route_gw
default_gw=$(route -n get default 2>/dev/null | awk '/gateway:/{print $2}')
route_gw=$(route -n get "$proxy_ip" 2>/dev/null | awk '/gateway:/{print $2}')
[[ -z "$default_gw" ]] && return 0
[[ "$route_gw" == "$default_gw" ]]
elif [[ "$os" == "linux" ]]; then
local default_gw
default_gw=$(ip route show default 2>/dev/null | awk '{print $3; exit}')
[[ -z "$default_gw" ]] && return 0
ip route show "$proxy_ip/32" 2>/dev/null | grep -q via
else
return 0
fi
}

if [[ -f "$CAC_DIR/relay_route_ip" ]]; then
local route_ip; route_ip=$(_read "$CAC_DIR/relay_route_ip")
echo "直连路由 :$route_ip"
fi
;;
*)
echo "用法:cac relay [on|off|status]" >&2
echo " on [--route] 启用本地中转(--route 添加直连路由绕过 TUN)" >&2
echo " off 停用本地中转" >&2
echo " status 查看状态" >&2
;;
esac
# 检测 TUN 并自动确保路由正确
_relay_ensure_route() {
local proxy="$1"
[[ -z "$proxy" ]] && return 0
_detect_tun_active || return 0
_relay_route_ok "$proxy" && return 0
_relay_add_route "$proxy"
}

# ━━━ cmd_check.sh ━━━
Expand Down Expand Up @@ -2093,51 +2100,19 @@ cmd_check() {
printf "\r $(_green "✓") exit IP $(_dim "run again to detect exit IP")\033[K\n"
fi

# TUN conflict detection
if [[ -n "$proxy_ip" ]]; then
local os; os=$(_detect_os)
local has_conflict=false
local tun_procs="clash|mihomo|sing-box|surge|shadowrocket|v2ray|xray|hysteria|tuic|nekoray"
local running
if [[ "$os" == "macos" ]]; then
running=$(ps aux 2>/dev/null | grep -iE "$tun_procs" | grep -v grep || true)
else
running=$(ps -eo comm 2>/dev/null | grep -iE "$tun_procs" || true)
fi
[[ -n "$running" ]] && has_conflict=true
if [[ "$os" == "macos" ]]; then
local tun_count; tun_count=$(ifconfig 2>/dev/null | grep -cE '^utun[0-9]+' || echo 0)
[[ "$tun_count" -gt 3 ]] && has_conflict=true
elif [[ "$os" == "linux" ]]; then
ip link show tun0 >/dev/null 2>&1 && has_conflict=true
fi

if [[ "$has_conflict" == "true" ]]; then
local relay_ok=false
if _relay_is_running 2>/dev/null; then
local rport; rport=$(_read "$CAC_DIR/relay.port" "")
local relay_ip; relay_ip=$(curl --proxy "http://127.0.0.1:$rport" --connect-timeout 8 --max-time 12 https://api.ipify.org 2>/dev/null || true)
[[ -n "$relay_ip" ]] && relay_ok=true
elif [[ -f "$CAC_DIR/relay.js" ]]; then
local _test_env; _test_env=$(_current_env)
if _relay_start "$_test_env" 2>/dev/null; then
local rport; rport=$(_read "$CAC_DIR/relay.port" "")
local relay_ip; relay_ip=$(curl --proxy "http://127.0.0.1:$rport" --connect-timeout 8 --max-time 12 https://api.ipify.org 2>/dev/null || true)
_relay_stop 2>/dev/null || true
[[ -n "$relay_ip" ]] && relay_ok=true
fi
fi

if [[ "$relay_ok" == "true" ]]; then
echo " $(_green "✓") TUN relay bypass active"
# TUN conflict detection — check route instead of relay connectivity
if _detect_tun_active 2>/dev/null; then
if _relay_route_ok "$proxy" 2>/dev/null; then
echo " $(_green "✓") TUN direct route OK"
else
local proxy_hp; proxy_hp=$(_proxy_host_port "$proxy")
local proxy_host="${proxy_hp%%:*}"
echo " $(_red "✗") TUN conflict — add DIRECT rule for $proxy_host"
problems+=("TUN conflict: add DIRECT rule for $proxy_host in proxy software")
if _relay_add_route "$proxy" 2>/dev/null; then
echo " $(_green "✓") TUN direct route $(_dim "added")"
else
echo " $(_red "✗") TUN route missing — may need sudo"
problems+=("TUN active but direct route missing for proxy")
fi
fi
fi
fi
fi
else
echo " $(_green "✓") mode API Key (no proxy)"
Expand Down
52 changes: 10 additions & 42 deletions src/cmd_check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -93,51 +93,19 @@ cmd_check() {
printf "\r $(_green "✓") exit IP $(_dim "run again to detect exit IP")\033[K\n"
fi

# TUN conflict detection
if [[ -n "$proxy_ip" ]]; then
local os; os=$(_detect_os)
local has_conflict=false
local tun_procs="clash|mihomo|sing-box|surge|shadowrocket|v2ray|xray|hysteria|tuic|nekoray"
local running
if [[ "$os" == "macos" ]]; then
running=$(ps aux 2>/dev/null | grep -iE "$tun_procs" | grep -v grep || true)
else
running=$(ps -eo comm 2>/dev/null | grep -iE "$tun_procs" || true)
fi
[[ -n "$running" ]] && has_conflict=true
if [[ "$os" == "macos" ]]; then
local tun_count; tun_count=$(ifconfig 2>/dev/null | grep -cE '^utun[0-9]+' || echo 0)
[[ "$tun_count" -gt 3 ]] && has_conflict=true
elif [[ "$os" == "linux" ]]; then
ip link show tun0 >/dev/null 2>&1 && has_conflict=true
fi

if [[ "$has_conflict" == "true" ]]; then
local relay_ok=false
if _relay_is_running 2>/dev/null; then
local rport; rport=$(_read "$CAC_DIR/relay.port" "")
local relay_ip; relay_ip=$(curl --proxy "http://127.0.0.1:$rport" --connect-timeout 8 --max-time 12 https://api.ipify.org 2>/dev/null || true)
[[ -n "$relay_ip" ]] && relay_ok=true
elif [[ -f "$CAC_DIR/relay.js" ]]; then
local _test_env; _test_env=$(_current_env)
if _relay_start "$_test_env" 2>/dev/null; then
local rport; rport=$(_read "$CAC_DIR/relay.port" "")
local relay_ip; relay_ip=$(curl --proxy "http://127.0.0.1:$rport" --connect-timeout 8 --max-time 12 https://api.ipify.org 2>/dev/null || true)
_relay_stop 2>/dev/null || true
[[ -n "$relay_ip" ]] && relay_ok=true
fi
fi

if [[ "$relay_ok" == "true" ]]; then
echo " $(_green "✓") TUN relay bypass active"
# TUN conflict detection — check route instead of relay connectivity
if _detect_tun_active 2>/dev/null; then
if _relay_route_ok "$proxy" 2>/dev/null; then
echo " $(_green "✓") TUN direct route OK"
else
local proxy_hp; proxy_hp=$(_proxy_host_port "$proxy")
local proxy_host="${proxy_hp%%:*}"
echo " $(_red "✗") TUN conflict — add DIRECT rule for $proxy_host"
problems+=("TUN conflict: add DIRECT rule for $proxy_host in proxy software")
if _relay_add_route "$proxy" 2>/dev/null; then
echo " $(_green "✓") TUN direct route $(_dim "added")"
else
echo " $(_red "✗") TUN route missing — may need sudo"
problems+=("TUN active but direct route missing for proxy")
fi
fi
fi
fi
fi
else
echo " $(_green "✓") mode API Key (no proxy)"
Expand Down
Loading
Loading