在混合云协作和本地开发中,安全且稳定地访问远程私有 API 是一项常见需求。本文介绍一套基于 Go 语言实现的通用网络中继与 API 反代网关系统。该系统由中继服务(svc.go,服务端)与网关通道(main.go,客户端)两部分组成,支持 SSH 动态通道自愈、Windows 系统后台服务集成以及防超时长连接保活。
系统整体架构设计 系统通过本地客户端拦截并分流流量,进行协议转换,随后通过 SSH 动态隧道进行加密中转:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 [ 本地客户端 / 应用 ] │ ▼ (HTTP CONNECT) ┌──────────────────────────────────┐ │ 客户端网关 (Clink Gateway) │ └──────────────┬───────────────────┘ │ (智能分流) ┌────────┴────────┐ ▼ (API 流量) ▼ (常规流量) ┌──────────────┐ ┌─────────────────┐ │ API 反代网关 │ │ HTTP CONNECT │ └────────┬─────┘ └──────┬──────────┘ └──────┬───────┘ │ (SOCKS5 链式协议) ▼ ┌──────────────────────────────────┐ │ 中继服务 (SOCKS5 本地监听) │ └──────────────┬───────────────────┘ │ (SSH 动态隧道加密) ▼ ┌──────────────────────────────────┐ │ 远程中转主机 (OpenSSH 服务) │ └──────────────┬───────────────────┘ │ ▼ (公网出口) [ 目标互联网资源 ]
系统角色与网络拓扑 系统包含以下三个核心组件:
远程中转主机(物理 Server) : 部署在受信任公网或私有 VPC 中的主机。需运行标准的 OpenSSH Server 并启用端口转发,作为链路出口。
本地中继服务(项目“服务端”:svc.exe) : 运行在本地局域网的主机上。对于远程中转主机,它是 SSH Client (主动拨号);对于本地网络,它是 SOCKS5 Server ,提供安全的局域网代理侦听端口。
应用智能网关(项目“客户端”:clink.exe) : 运行在本地开发机上。作为本地应用(如代码脚本、终端工具)与本地 SOCKS5 代理之间的桥梁,提供 HTTP 与 SOCKS5 之间的链式转换。
第一部分:服务端设计(基于 Go 的 SSH 隧道自愈 Windows 服务) 中继服务在本地建立 SOCKS5 端口,并将所有出口流量通过加密的 SSH 隧道重定向到远端服务器。
1. 核心设计亮点
Windows 系统服务化 :利用 golang.org/x/sys/windows/svc 接入 Windows 服务控制器。支持使用 sc.exe 注册为系统级后台服务,静默开机自启动。
断线自愈与心跳保活 :内建保活协程定期发送心跳探针(keepalive@golang.org),检测到异常时进入冷却重试机制,自动重建 SSH 动态隧道。
自动防火墙配置 :启动时自动检测并申请管理员权限,通过 netsh advfirewall 一键开启/清理对应端口的入站规则。
2. 服务端核心技术实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 func runSSHTunnel (cfg *Config, stopChan chan struct {}) { sshConfig := &ssh.ClientConfig{ User: cfg.SSHUser, Auth: []ssh.AuthMethod{ssh.Password(cfg.SSHPass)}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), Timeout: 10 * time.Second, } reconnectDur := time.Duration(cfg.ReconnectSeconds) * time.Second keepAliveDur := time.Duration(cfg.KeepAliveSeconds) * time.Second for { select { case <-stopChan: return default : log.Printf("[INFO] Connecting SSH to %s (user=%s) ..." , cfg.SSHAddr, cfg.SSHUser) client, err := ssh.Dial("tcp" , cfg.SSHAddr, sshConfig) if err != nil { log.Printf("[WARN] SSH connect failed: %v, retrying in %v" , err, reconnectDur) time.Sleep(reconnectDur) continue } log.Printf("[OK] SSH connected to %s" , cfg.SSHAddr) socks5Conf := &socks5.Config{ Dial: func (ctx context.Context, network, addr string ) (net.Conn, error ) { return client.Dial(network, addr) }, } server, err := socks5.New(socks5Conf) if err != nil { log.Printf("[ERROR] Failed to create SOCKS5 server: %v" , err) client.Close() time.Sleep(reconnectDur) continue } listenAddr := fmt.Sprintf("0.0.0.0:%d" , cfg.LocalPort) log.Printf("[OK] SOCKS5 listening on %s" , listenAddr) var closeOnce sync.Once go func () { ticker := time.NewTicker(keepAliveDur) defer ticker.Stop() for { select { case <-ticker.C: if _, _, err := client.SendRequest("keepalive@golang.org" , true , nil ); err != nil { log.Printf("[WARN] SSH keepalive failed: %v, closing connection" , err) closeOnce.Do(func () { client.Close() }) return } case <-stopChan: closeOnce.Do(func () { client.Close() }) return } } }() if err := server.ListenAndServe("tcp" , listenAddr); err != nil { log.Printf("[ERROR] SOCKS5 listen error: %v" , err) } closeOnce.Do(func () { client.Close() }) time.Sleep(reconnectDur) } } }
第二部分:客户端设计(协议链式转换与通用 API 反代网关) 客户端本地监听 HTTP 代理端口,拦截 TCP 握手并将其重构为 SOCKS5 RFC1928 数据包进行中继。同时,针对长连接慢响应 API 提供了底层网关控制。
1. 核心设计亮点
正向 HTTP CONNECT 协议转换 :透明拦截终端发出的 HTTP CONNECT 请求,手动组装上游 SOCKS5 握手阶段数据,实现链式协议互通。
防慢连接断开机制 :针对响应耗时极长的服务端请求(如慢查询、生成类流式 API 接口),直接重写 HTTP Transport 的 DialContext,注入 TCP 物理保活探测(KeepAlive: 30s),并将 HTTP IdleConnTimeout 设为 0,避免网关超时断连,保障数据完整度。
2. 客户端核心技术实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 func startAPIProxy (port int , upstreamURL string , dialer *socks5Dialer, sem chan struct {}) { keepaliveDialContext := func (ctx context.Context, network, addr string ) (net.Conn, error ) { nd := net.Dialer{KeepAlive: 30 * time.Second} conn, err := nd.DialContext(ctx, network, dialer.socks5Addr) if err != nil { return nil , fmt.Errorf("connect SOCKS5 proxy failed: %w" , err) } return dialer.completeHandshake(conn, addr) } transport := &http.Transport{ DialContext: keepaliveDialContext, TLSClientConfig: &tls.Config{InsecureSkipVerify: true }, DisableKeepAlives: false , ResponseHeaderTimeout: 0 , IdleConnTimeout: 0 , } client := &http.Client{Transport: transport, Timeout: 0 } mux := http.NewServeMux() mux.HandleFunc("/" , func (w http.ResponseWriter, r *http.Request) { sem <- struct {}{} defer func () { <-sem }() handleAPIRequest(w, r, upstreamURL, client) }) srv := &http.Server{Addr: fmt.Sprintf(":%d" , port), Handler: mux} log.Printf("[OK] API Gateway proxy started on :%d" , port) srv.ListenAndServe() }
第三部分:极速部署与实战使用指南 1. 前期准备(环境依赖) 远程中转主机(物理 Server)配置
安装 OpenSSH Server (以 Ubuntu/Debian 为例):1 2 3 sudo apt updatesudo apt install openssh-server -ysudo systemctl enable --now ssh
开启 TCP 端口转发 : 编辑远程主机的 /etc/ssh/sshd_config 文件,确保包含以下配置项:1 2 3 AllowTcpForwarding yes GatewayPorts yes TCPKeepAlive yes
修改完成后,重启远程 SSH 服务:1 sudo systemctl restart ssh
本地运行主机(Windows)配置
2. 服务端(Windows System Service)部署 步骤 1:配置配置文件 svc.yaml 在服务端可执行文件 svc.exe 同级目录下编辑 svc.yaml,写入远程 SSH 凭证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 svc_name: "SSHAutoProxySvc" local_port: 10888 ssh_addr: "182.92.105.77:22" ssh_user: "tunnel_user" ssh_pass: "SecurePassword123" firewall: true reconnect_seconds: 10 keepalive_seconds: 30
步骤 2:注册并启动系统服务
以 管理员身份 打开 CMD 或 PowerShell。
运行 sc.exe 命令,将程序一键注册为后台系统服务,并设为开机自动启动:1 sc.exe create SSHAutoProxySvc binPath= "D:\network\svc.exe" start= auto
启动后台服务:1 net start SSHAutoProxySvc
3. 客户端(Clink Proxy)配置与运行 步骤 1:编辑客户端配置 clink.yaml 在客户端 clink.exe 同级目录下编辑 clink.yaml 配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 http_proxy_port: 8088 api_proxy_port: 2026 socks5_addrs: - "127.0.0.1:10888" api_upstream: "https://api.github.com" max_conns: 1000 test_on_startup: true
步骤 2:启动客户端并测试 双击运行 clink.exe。当输出如下信息时,说明中继工作正常:
1 2 3 4 5 6 7 [INFO] HTTP proxy port: :8088 [INFO] API proxy port: :2026 [INFO] SOCKS5 upstream: 127.0.0.1:10888 [INFO] Test on startup: true [OK] SOCKS5 connectivity test PASSED [OK] Both proxies are running! [OK] Press Ctrl+C to stop
步骤 3:消费中继流量
设置终端全局代理 (以 Windows Command Prompt 为例):1 2 set HTTP_PROXY=http://127 .0 .0 .1 :8088 set HTTPS_PROXY=http://127 .0 .0 .1 :8088
将本地调用指向本地网关 : 直接在代码或客户端中将 API 端点更改为:http://127.0.0.1:2026 例如,在命令行运行以下测试命令:1 curl http://127.0.0.1:2026/v1/users
此时,所有请求将被客户端网关接收,并以 TCP 物理保活 的状态,通过本地加密的 SSH 隧道传输到远程服务器 safe 发出。