摆脱多内网连接客户端冲突!基于 Hyper-V 虚机与 Proxifier 分流的多虚拟专用网连接解决方案

在我的工作中,需要同时负责多个项目的系统建设与运维,这意味着我要访问不同的内网服务器、数据库、网关、K8s 集群和应用系统。而这些单位都使用不同的 VPN,有的甚至使用同一家 VPN 厂商但版本不同。

当这些 VPN 同时安装在同一台电脑上时,各种问题开始出现:

  • 连 VPN A,VPN B 就被强制断开

  • 内网能访问了,但互联网完全打不开

  • DNS 被 VPN 改乱,GitHub、npm、VSCode 全都失效

  • 路由、网卡被搞乱,网络最终只能靠"重启电脑"恢复

  • 同款 VPN 客户端不同版本之间会反复升级/降级,甚至破坏网络驱动

如果你也遇到过这些问题,你就知道这是一个"多 VPN 共存需求"的典型痛点。


我已经在各个项目中见过很多VPN客户端,包括EasyConnect(经常多个客户单位都用这个但是存在版本不一致的情况),FortiClient,OpenVpn,ATrust(零信任),虎盾,iNode(也很容易有版本冲突问题)等。

鉴于我的工作笔记本资源配置比较充足,所以我决定把所有VPN都安装到Windows自带的Hyper-V创建的Windows虚拟机中,版本冲突的就安装到两台虚拟机中,实际场景大部分可以安装到同一台中,有些vpn甚至可以同时启用生效(根据VPN修改路由的情况决定)。此方案因为用到虚拟机,过程中会耗费一定系统资源,方案中使用的软件也有多种选择,有些可能需要破点小财 XD,下面我具体说说我的场景和实现方式。

PS. 值得一提的是,用这个方案顺带解决了WSL中使用大模型的问题。后边还会附带一个shell脚本用于在WSL中Ubuntu系统自动发现启用和停用Proxy配置。

方案用到的软件:

  • CCProxy(免费),代理服务端,安装在虚拟机
  • Proxifier (收费,花了**$39.95**, 买多个有优惠),代理分流,安装在宿主机
  • Hyper-V(免费),虚拟机软件,安装在宿主机
  • 其他上网客户端, 用于访问ClaudeCode(部分收费)安装在虚拟机
    需要满足启动后允许本地网络访问,因为有些客户端启动后修改了路由 0.0.0.0 ,导致主机无法通过虚机内网ip通信。有些客户端有配置项可以开启"允许本地网络访问"/"Allow local(LAN) Access when using",有些则可能需要手动修改路由。

方案用到的硬件:

  • 两个网卡( 如果你不需要WSL2中Ubuntu系统访问ClaudeCode或不需要通过各个单位VPN访问内部资源如Linux服务器,也可以忽略这个要求, 这主要是解决WSL内Ubuntu因为走了Nat网络,网关也被改过,如果虚机使用了主机相同的网卡,Ubuntu内无法联通虚机ip问题**)**
    • 如果你的工作环境有Wifi和有线,那一般笔记本就满足(一般都可以同时接有限和无线网络)
    • 如果你的工作环境只有无线,那需要花15-40块钱买一个USB无线网卡(购买时需要注意下,有些USB无线网卡不支持路由器的5G网络,如果你的无线网络没有非5G网络就浪费了)

核心思路:把 VPN"装进盒子里",让它们互不干扰

Hyper-V 本身是 Windows 自带、稳定成熟的虚拟化方案,并且对网络隔离非常友好。

结合我的设备配置比较充足(内存、CPU 都够),于是我设计了这样一个体系结构:

  • 虚拟机负责跑 VPN(以及 CCProxy),我跑的Win10,内存2GB即可。

  • 宿主机则永远不安装任何 VPN,或只安装常用VPN,比如自家公司的VPN客户端

  • 所有访问 VPN 内网的流量都通过 Proxifier 精准分流到对应的虚拟机

软件操作和配置

Hyper-V

启用 Hyper-V 功能(图形界面方式)需要windows10,11 (非Home版本)

  • 打开 控制面板 → 程序和功能 → 启用或关闭 Windows 功能

  • 找到 Hyper-V,并勾选以下项目:

    ✔ Hyper-V 管理工具

    ✔ Hyper-V 平台

  • 点击 确定,等待系统安装组件。

  • 根据提示 重启电脑

Proxifier

1.配置Proxy Server,需要将虚机ip和虚机上启动CCProxy监听的端口配置到Proxifier中。需要注意,如果你带着笔记本在家,到单位或其他地点办公,虚机的ip会跟着变化,对应也要手动修改下下面server的ip配置。

  1. 配置代理规则,可以通过ip或者域名方式配置使用哪个代理进行访问

CCProxy(安装在虚机上)

开启下图中三项就够了,大多数时候其实也不需要SOCKS/MMS方式,但是因为我在能连接单位VPN网络后还需要本地访问堡垒机内RDP桌面,所以使用了SOCKS方式

为了安全性,点击上图"高级"按钮,配置禁止局域网外部用户使用此代理与管理员密码

同样为了安全,记得创建CCProxy用户用于连接代理服务

以上配置完成后就可以在本地电脑上访问多个VPN连接的网络了。


另外说下WSL下Ubuntu通过此方式访问AI大模型(如Claude)的配置

  1. 基本思路一样,但是WSL2内Ubuntu的网络流量不受Proxifier控制,所以需要单独配置Ubuntu使用CCProxy的代理(具体方式在下面第3点说明),需要注意的是虚机内科学客户端需要支持本地网络访问,否则开启后Ubuntu无法连通虚机ip,也就连不上CCProxy,因为一般这类客户端都会配置0.0.0.0的路由规则,劫持了所有流量。一般不同软件的开启本地网络访问的配置关键词可能是下面这些:
  • Allow LAN

  • Allow Local Network

  • Bypass LAN

  • 仅代理远程流量,不代理本地流量

  1. 如果想要WSL2内Ubuntu 访问的到 Hyper-v 虚机的CCProxy,需要配置虚机使用的网卡不能是主机默认使用的网卡,这也是为什么我们需要第二块网卡。(使用同一块网卡我看到的现象是,从Ubuntu内通过nc -zv ip port 来测试ccproxy监听提示找不到路由,怀疑和Ubuntu给配置的Nat网关有关系), 若有同学能找到"不使用第二块网卡仍可实现 WSL2 访问虚机"的方式,非常欢迎分享!

以下是我的Hyper-V中网卡设置:

无线连接配置这里可以切换网卡分别连接到无线网络

3.修改Ubuntu内代理配置,来使网络流量走CCProxy代理, 因为在电脑在不同办公地点接入不同网络的时候虚机的ip会有变化(默认使用DHCP自动获取ip),所以每次我到了一个新的网络环境后都要修改其代理配置,有些麻烦,为了更方便我准备了一个shell脚本,用于自动检测,开启,禁用,刷新代理配置。

4.使用Ubuntu内管理代理配置的脚本

使用效果

将下方脚本保存到一个文件,命名proxy-manager.sh,放到Ubuntu内任意位置,我是放到了/root/shell/claude_shell/proxy-manager.sh,enable命令可以扫描指定网段的CCProxy监听端口,扫描到后将代理环境变量写入~/.bashrc文件,disable命令则删除代理配置。

  • 脚本开头的变量PROXY_PORT是ccproxy监听的端口,因为可能有多个虚机同时开启,所以不同虚机的ccproxy端口尽量设置不一样区分开。
  • PROXY_USERPROXY_PASS是CCProxy的用户密码
  • INTERNAL_SUBNETS是你常用的网络的ip段信息
  • PROIORITY_IPS是你最可能被分配到的ip第四位数字,用于提高扫描命中速度
bash 复制代码
#!/bin/bash

# Proxy Manager Script for Ubuntu WSL
# Automatically detects and configures proxy settings based on internal subnets

PROXY_PORT=809
PROXY_USER="proxy(用户名需要修改)"
PROXY_PASS="234233密码需要修改"
APT_PROXY_CONF="/etc/apt/apt.conf.d/95proxies"
SCRIPT_NAME="$(basename "$0")"
MAX_PARALLEL_JOBS=20
FOUND_PROXY_FILE="/tmp/proxy_found_$$"

# 虚机常用网络位置会分配的网段,脚本会用于自动扫描的网段定义
INTERNAL_SUBNETS=(
    "192.168.0"  # Most common home/office subnet
    "192.168.2"  # Secondary common subnet
    "10.0.0"     # Corporate networks
)

# 经常会被分配到的ip或固定ip最后一位,会被优先扫描,提高扫描速度,如果不确定这里只保留一个反倒会提高网络速度,因为后续其他ip是并行扫描的,下面这里定义的是逐条扫描
PRIORITY_IPS=(15 16 17 18 123 113 )

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

log_info() {
    echo -e "${BLUE}[INFO]${NC} $1" >&2
}

log_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1" >&2
}

log_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1" >&2
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1" >&2
}

show_progress() {
    local current=$1
    local total=$2
    local message=$3
    local percent=$(( current * 100 / total ))
    local filled=$(( percent / 2 ))
    local empty=$(( 50 - filled ))

    printf "\r${BLUE}[%3d%%]${NC} [" "$percent" >&2
    printf "%*s" $filled | tr ' ' '=' >&2
    printf "%*s" $empty | tr ' ' '-' >&2
    printf "] %s" "$message" >&2
}

check_proxy_server() {
    local ip=$1
    local timeout=1

    # Validate IP address format
    if ! [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
        return 1
    fi

    # Try to connect to proxy port
    if timeout $timeout bash -c "echo >/dev/tcp/$ip/$PROXY_PORT" 2>/dev/null; then
        return 0
    else
        return 1
    fi
}

check_proxy_server_parallel() {
    local ip=$1
    local job_id=$2

    if check_proxy_server "$ip"; then
        # Test if proxy actually works
        local test_url="http://www.google.com"
        if curl -s --connect-timeout 3 --proxy "http://${PROXY_USER}:${PROXY_PASS}@${ip}:${PROXY_PORT}" "$test_url" >/dev/null 2>&1; then
            echo "WORKING:$ip" > "$FOUND_PROXY_FILE"
        else
            echo "RESPONDING:$ip" > "$FOUND_PROXY_FILE"
        fi
        # Kill other background jobs
        jobs -p | xargs -r kill 2>/dev/null
        return 0
    fi
    return 1
}

detect_proxy_server() {
    log_info "Starting optimized proxy server detection..."
    local proxy_found=""

    # Clean up any existing temp files
    rm -f "$FOUND_PROXY_FILE" 2>/dev/null

    # Check if we have network connectivity first
    if ! ping -c 1 -W 2 223.5.5.5 >/dev/null 2>&1 && ! ping -c 1 -W 119.29.29.29 >/dev/null 2>&1; then
        log_warning "No internet connectivity detected - continuing with proxy detection"
    fi

    log_info "Using parallel scanning with $MAX_PARALLEL_JOBS concurrent connections"
    log_info "Scanning ${#INTERNAL_SUBNETS[@]} subnet(s): ${INTERNAL_SUBNETS[*]}"

    for subnet in "${INTERNAL_SUBNETS[@]}"; do
        log_info "Scanning subnet ${subnet}.x..."

        # First check priority IPs (common gateway addresses)
        log_info "Checking priority IPs first..."
        for priority_ip in "${PRIORITY_IPS[@]}"; do
            local ip="${subnet}.${priority_ip}"
            echo -n "Priority check $ip:$PROXY_PORT... " >&2

            if check_proxy_server "$ip"; then
                echo "FOUND!" >&2
                log_success "Proxy server found at $ip:$PROXY_PORT"

                # Test if proxy actually works
                log_info "Testing proxy connectivity..."
                local test_url="http://www.google.com"
                if curl -s --connect-timeout 5 --proxy "http://${PROXY_USER}:${PROXY_PASS}@${ip}:${PROXY_PORT}" "$test_url" >/dev/null 2>&1; then
                    log_success "Proxy server is functional"
                    proxy_found="$ip"
                    echo "$proxy_found"
                    return 0
                else
                    log_warning "Proxy server at $ip:$PROXY_PORT responds but may not be functional"
                    proxy_found="$ip"  # Still use it, might be filtering specific sites
                    echo "$proxy_found"
                    return 0
                fi
            else
                echo "no response" >&2
            fi
        done

        # If no priority IP worked, do parallel scan of remaining IPs
        log_info "Priority IPs not found, starting parallel scan..."
        local jobs_started=0
        local total_checked=0

        for i in {1..254}; do
            # Skip priority IPs already checked
            local skip=false
            for priority_ip in "${PRIORITY_IPS[@]}"; do
                if [[ $i -eq $priority_ip ]]; then
                    skip=true
                    break
                fi
            done
            [[ $skip == true ]] && continue

            # Wait if we have too many background jobs
            while [[ $(jobs -r | wc -l) -ge $MAX_PARALLEL_JOBS ]]; do
                sleep 0.1
                # Check if proxy was found
                if [[ -f "$FOUND_PROXY_FILE" ]]; then
                    break 2
                fi
            done

            # Check if proxy was found by any background job
            if [[ -f "$FOUND_PROXY_FILE" ]]; then
                break
            fi

            local ip="${subnet}.${i}"
            check_proxy_server_parallel "$ip" $jobs_started &
            ((jobs_started++))
            ((total_checked++))

            # Show progress every 10 IPs
            if (( total_checked % 10 == 0 )); then
                echo -n "." >&2
            fi
        done

        # Wait for all background jobs to complete
        echo >&2 # New line
        log_info "Waiting for parallel scans to complete..."
        wait

        # Check if proxy was found
        if [[ -f "$FOUND_PROXY_FILE" ]]; then
            local result=$(cat "$FOUND_PROXY_FILE")
            local status=${result%:*}
            proxy_found=${result#*:}

            rm -f "$FOUND_PROXY_FILE"

            if [[ $status == "WORKING" ]]; then
                log_success "Found working proxy server at $proxy_found:$PROXY_PORT"
            else
                log_success "Found responding proxy server at $proxy_found:$PROXY_PORT"
                log_warning "Proxy may not be fully functional but will be used"
            fi

            echo "$proxy_found"
            return 0
        fi
    done

    log_error "No proxy server found on port $PROXY_PORT in internal subnets"
    log_info "Checked subnets: ${INTERNAL_SUBNETS[*]}"
    return 1
}

set_environment_proxy() {
    local proxy_server=$1
    local proxy_url="http://${PROXY_USER}:${PROXY_PASS}@${proxy_server}:${PROXY_PORT}"

    log_info "Setting environment proxy variables..."

    echo "export http_proxy='$proxy_url'"
    echo "export https_proxy='$proxy_url'"
    echo "export ftp_proxy='$proxy_url'"
    echo "export HTTP_PROXY='$proxy_url'"
    echo "export HTTPS_PROXY='$proxy_url'"
    echo "export FTP_PROXY='$proxy_url'"
    echo "export no_proxy='localhost,127.0.0.1,::1,.local'"
    echo "export NO_PROXY='localhost,127.0.0.1,::1,.local'"
    # Remove existing proxy settings from bashrc first to avoid duplicates
    if [[ -f ~/.bashrc ]]; then
        # Create backup
        cp ~/.bashrc ~/.bashrc.bak.$(date +%Y%m%d_%H%M%S) 2>/dev/null || true

        # Remove existing proxy lines
        sed -i '/^export [A-Za-z_]*[Pp][Rr][Oo][Xx][Yy]/d' ~/.bashrc
    fi

    # Add proxy settings marker and variables to bashrc
    {
        echo ""
        echo "# Proxy settings - managed by $SCRIPT_NAME"
        echo "export http_proxy='$proxy_url'"
        echo "export https_proxy='$proxy_url'"
        echo "export ftp_proxy='$proxy_url'"
        echo "export HTTP_PROXY='$proxy_url'"
        echo "export HTTPS_PROXY='$proxy_url'"
        echo "export FTP_PROXY='$proxy_url'"
        echo "export no_proxy='localhost,127.0.0.1,::1,.local'"
        echo "export NO_PROXY='localhost,127.0.0.1,::1,.local'"
        echo "# End proxy settings"
    } >> ~/.bashrc

    log_success "Environment proxy variables set and saved to ~/.bashrc"
}

set_apt_proxy() {
    local proxy_server=$1
    local proxy_url="http://${PROXY_USER}:${PROXY_PASS}@${proxy_server}:${PROXY_PORT}"

    log_info "Configuring APT proxy..."

    # Create APT proxy configuration
    sudo tee "$APT_PROXY_CONF" > /dev/null << EOF
Acquire::http::Proxy "$proxy_url";
Acquire::https::Proxy "$proxy_url";
Acquire::ftp::Proxy "$proxy_url";
EOF

    log_success "APT proxy configured"
}

enable_proxy() {
    log_info "Enabling proxy settings..."

    local proxy_server
    proxy_server=$(detect_proxy_server)

    if [[ $? -eq 0 && -n "$proxy_server" ]]; then
        set_environment_proxy "$proxy_server"
        set_apt_proxy "$proxy_server"
        log_success "Proxy enabled successfully with server: $proxy_server:$PROXY_PORT"
    else
        log_error "Failed to detect proxy server. Proxy not enabled."
        return 1
    fi
}

disable_proxy() {
    log_info "Disabling proxy settings..."

    # Unset environment variables
    unset http_proxy https_proxy ftp_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY no_proxy NO_PROXY

    # Remove from bashrc using better pattern matching
    if [[ -f ~/.bashrc ]]; then
        # Create backup
        cp ~/.bashrc ~/.bashrc.bak.$(date +%Y%m%d_%H%M%S) 2>/dev/null || true

        # Remove proxy settings block completely
        sed -i '/^# Proxy settings - managed by/,/^# End proxy settings/d' ~/.bashrc

        # Remove any remaining individual proxy lines (fallback)
        sed -i '/^export [A-Za-z_]*[Pp][Rr][Oo][Xx][Yy]/d' ~/.bashrc
        sed -i '/^export [nN][oO]_[Pp][Rr][Oo][Xx][Yy]/d' ~/.bashrc
        sed -i '/^export NO_PROXY/d' ~/.bashrc

        # Remove any empty lines that might be left behind
        sed -i '/^$/N;/^\n$/d' ~/.bashrc
    fi

    # Remove APT proxy configuration
    if [[ -f "$APT_PROXY_CONF" ]]; then
        if sudo rm -f "$APT_PROXY_CONF" 2>/dev/null; then
            log_info "Removed APT proxy configuration"
        else
            log_warning "Failed to remove APT proxy configuration (permission denied?)"
        fi
    fi
    log_success "Proxy settings disabled"
    echo "unset http_proxy"
    echo "unset https_proxy"
    echo "unset ftp_proxy"
    echo "unset HTTP_PROXY"
    echo "unset HTTPS_PROXY"
    echo "unset FTP_PROXY"
    echo "export no_proxy='localhost,127.0.0.1,::1,.local'"
    echo "export NO_PROXY='localhost,127.0.0.1,::1,.local'"
}

refresh_proxy() {
    log_info "Refreshing proxy settings for new network location..."
    disable_proxy
    sleep 1
    enable_proxy
}

show_status() {
    log_info "Current proxy status:"

    if [[ -n "$http_proxy" ]]; then
        echo -e "  Environment proxy: ${GREEN}ENABLED${NC}"
        echo "    http_proxy: $http_proxy"
        echo "    https_proxy: $https_proxy"
        echo "    no_proxy: $no_proxy"
    else
        echo -e "  Environment proxy: ${RED}DISABLED${NC}"
    fi

    if [[ -f "$APT_PROXY_CONF" ]]; then
        echo -e "  APT proxy: ${GREEN}ENABLED${NC}"
        echo "    Config file: $APT_PROXY_CONF"
    else
        echo -e "  APT proxy: ${RED}DISABLED${NC}"
    fi
}

show_help() {
    cat << EOF
$SCRIPT_NAME - Proxy Manager for Ubuntu WSL

Usage: $SCRIPT_NAME [OPTION]

OPTIONS:
    enable      Enable proxy settings (auto-detect proxy server)
    disable     Disable all proxy settings
    refresh     Refresh proxy settings (disable then enable)
    status      Show current proxy status
    help        Show this help message

DESCRIPTION:
    This script automatically detects proxy servers on internal networks
    and configures system-wide proxy settings including environment
    variables and APT proxy configuration.

    Proxy server detection:
    - Scans common internal subnets for proxy servers on port $PROXY_PORT
    - Uses authentication: $PROXY_USER/$PROXY_PASS
    - Shows progress during detection

EXAMPLES:
    $SCRIPT_NAME enable    # Auto-detect and enable proxy
    $SCRIPT_NAME disable   # Disable all proxy settings
    $SCRIPT_NAME refresh   # Refresh when changing networks
    $SCRIPT_NAME status    # Check current settings

EOF
}

main() {
    case "${1:-help}" in
        enable)
            enable_proxy
            ;;
        disable)
            disable_proxy
            ;;
        refresh)
            refresh_proxy
            ;;
        status)
            show_status
            ;;
        help|--help|-h)
            show_help
            ;;
        *)
            log_error "Unknown option: $1"
            echo
            show_help
            exit 1
            ;;
    esac
}

# Check if running as root for APT configuration
check_sudo() {
    if [[ "$1" == "enable" || "$1" == "refresh" ]]; then
        if ! sudo -n true 2>/dev/null; then
            log_warning "This script requires sudo access for APT proxy configuration"
            log_info "You may be prompted for your password"
        fi
    fi
}

# Main execution
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    check_sudo "$1"
    main "$@"
fi

修改~/.bashrc文件新增一段proxy()函数,代码如下(注意:下面代码中proxy-manager.sh脚本路径要根据实际位置修改 /root/shell/claude_shell/proxy-manager.sh),增加这个函数配置是为了可以让上方脚本中增加的环境变量直接生效,而不用手动执行source ~/.bashrc或新开终端才能生效。也方便在终端中任何目录下直接调用。

bash 复制代码
proxy() {
    local valid_args=("enable" "disable" "refresh" "status" "help")

    # check if $1 is valid
    local is_valid=false
    for arg in "${valid_args[@]}"; do
        if [[ "$1" == "$arg" ]]; then
            is_valid=true
            break
        fi
    done

    # If invalid argument, run help
    if [[ "$is_valid" == false ]]; then
        /root/shell/claude_shell/proxy-manager.sh help
        return 1
    fi

    # status is special: do NOT eval
    if [[ "$1" == "status" ]]; then
        /root/shell/claude_shell/proxy-manager.sh status
        return
    fi

    # help also does not need eval
    if [[ "$1" == "help" ]]; then
        /root/shell/claude_shell/proxy-manager.sh help
        return
    fi

    # other commands must eval to apply env variables
    eval "$(/root/shell/claude_shell/proxy-manager.sh "$@")"
}
相关推荐
Flynt13 分钟前
接手28万行遗留代码:我用codebase-memory-mcp把代码理解时间从3天压到2小时
ai编程·claude·mcp
uccs11 小时前
流式响应的三次进化:EventSource → ReadableStream → TransformStream
openai·ai编程·claude
洛卡卡了17 小时前
我们在用 AI 写代码时,为什么建议要好好维护 AGENTS.md 呢?
面试·agent·claude
ZzT1 天前
让 AI 少写一半代码:拆解爆火的 ponytail
ai编程·claude
我不是外星人1 天前
我把 Claude Code 搬到网页!自研高颜值 Web 交互工作台
前端·ai编程·claude
counterxing3 天前
最近发现一个 Mac 工具,有点像把 Raycast、语音输入法、截图和录屏塞到了一起
macos·ai编程·claude
码哥字节3 天前
为什么 Claude Code 读你的代码库,光靠 embedding 根本不够?
claude·代码规范
用户223586218204 天前
Loop Engineering:从 Prompt 到 Loop
claude