diff --git a/cac b/cac index 647aa34..06f50cd 100755 --- a/cac +++ b/cac @@ -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") @@ -1785,7 +1831,7 @@ cmd_env() { } # ━━━ cmd_relay.sh ━━━ -# ── cmd: relay(本地中转,绕过 TUN)────────────────────────────── +# ── relay: 本地中转 + TUN 路由管理 ───────────────────────────── _relay_start() { local name="${1:-$(_current_env)}" @@ -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 @@ -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") @@ -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" @@ -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 @@ -1925,76 +1967,41 @@ _detect_tun_active() { fi } -# ── 用户命令 ───────────────────────────────────────────────────── - -cmd_relay() { - _require_setup - local current; current=$(_current_env) - [[ -z "$current" ]] && { echo "错误:未激活环境,先运行 'cac '" >&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 ━━━ @@ -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)" diff --git a/src/cmd_check.sh b/src/cmd_check.sh index 0cc9c0a..5a090a3 100644 --- a/src/cmd_check.sh +++ b/src/cmd_check.sh @@ -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)" diff --git a/src/cmd_relay.sh b/src/cmd_relay.sh index 887264f..e5eada1 100644 --- a/src/cmd_relay.sh +++ b/src/cmd_relay.sh @@ -1,4 +1,4 @@ -# ── cmd: relay(本地中转,绕过 TUN)────────────────────────────── +# ── relay: 本地中转 + TUN 路由管理 ───────────────────────────── _relay_start() { local name="${1:-$(_current_env)}" @@ -45,7 +45,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 @@ -74,10 +73,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") @@ -87,12 +84,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" @@ -102,7 +98,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 @@ -138,74 +134,39 @@ _detect_tun_active() { fi } -# ── 用户命令 ───────────────────────────────────────────────────── - -cmd_relay() { - _require_setup - local current; current=$(_current_env) - [[ -z "$current" ]] && { echo "错误:未激活环境,先运行 'cac '" >&2; exit 1; } - - 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 - - # 如果 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 - - 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 - - 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 +# 检查上游代理路由是否正确(不需要 sudo) +_relay_route_ok() { + local proxy="$1" + local proxy_host; proxy_host=$(_proxy_host_port "$proxy") + proxy_host="${proxy_host%%:*}" + + [[ "$proxy_host" == "127."* || "$proxy_host" == "localhost" ]] && return 0 + + local proxy_ip + proxy_ip=$(python3 -c "import socket; print(socket.gethostbyname('$proxy_host'))" 2>/dev/null || echo "$proxy_host") + + 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 +} + +# 检测 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" } diff --git a/src/relay.js b/src/relay.js index 6ead38f..550dd00 100644 --- a/src/relay.js +++ b/src/relay.js @@ -117,7 +117,7 @@ function socks5Connect(targetHost, targetPort, cb) { } }); - sock.on('error', (err) => cb(err)); + sock.on('error', (err) => { log('socks5 socket error: ' + err.message); cb(err); }); } // ── HTTP CONNECT upstream ─────────────────────────────────────── @@ -154,12 +154,13 @@ function httpConnect(targetHost, targetPort, cb) { }); }); - sock.on('error', (err) => cb(err)); + sock.on('error', (err) => { log('http upstream error: ' + err.message); cb(err); }); } // ── Connect to upstream (protocol dispatch) ───────────────────── function connectUpstream(targetHost, targetPort, cb) { + log('CONNECT ' + targetHost + ':' + targetPort + ' via ' + upstreamHost + ':' + upstreamPort); if (isSocks5) { socks5Connect(targetHost, targetPort, cb); } else { @@ -226,6 +227,7 @@ function handleConnect(clientSock, targetHost, targetPort, headerRest) { function doConnect(trailingData) { connectUpstream(targetHost, targetPort, (err, upstreamSock, upstreamExtra) => { if (err) { + log('502 → ' + targetHost + ':' + targetPort + ' — ' + err.message + ' (code=' + (err.code || 'none') + ')'); clientSock.write('HTTP/1.1 502 Bad Gateway\r\n\r\n'); clientSock.destroy(); return; diff --git a/src/templates.sh b/src/templates.sh index 636866d..be3f240 100644 --- a/src/templates.sh +++ b/src/templates.sh @@ -312,6 +312,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")