在混合云协作和本地开发中,安全且稳定地访问远程私有 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 服务) │
└──────────────┬───────────────────┘

▼ (公网出口)
[ 目标互联网资源 ]

系统角色与网络拓扑

系统包含以下三个核心组件:

  1. 远程中转主机(物理 Server)
    部署在受信任公网或私有 VPC 中的主机。需运行标准的 OpenSSH Server 并启用端口转发,作为链路出口。
  2. 本地中继服务(项目“服务端”:svc.exe
    运行在本地局域网的主机上。对于远程中转主机,它是 SSH Client(主动拨号);对于本地网络,它是 SOCKS5 Server,提供安全的局域网代理侦听端口。
  3. 应用智能网关(项目“客户端”: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
// RunSSHTunnel 启动 SSH 拨号并建立本地 SOCKS5 服务的自愈循环
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)

// 重写 SOCKS5 Dialer 的连接方法,将 TCP 拨号重定向至 SSH 隧道内
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

// 专属保活协程:向 SSH 通道定时发送探针
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
}
}
}()

// 启动 SOCKS5 服务监听
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
// 针对长连接慢响应 API 定制的专属反向代理网关启动器
func startAPIProxy(port int, upstreamURL string, dialer *socks5Dialer, sem chan struct{}) {

// 重写 DialContext,向物理 TCP 拨号中强制注入 KeepAlive 定时检测
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)
}
// 完成 SOCKS5 的握手阶段校验
return dialer.completeHandshake(conn, addr)
}

transport := &http.Transport{
DialContext: keepaliveDialContext,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DisableKeepAlives: false,
ResponseHeaderTimeout: 0, // 设为 0:对于极长等待时间的慢请求,不设置超时
IdleConnTimeout: 0, // 设为 0:永不主动关闭空闲 TCP 链路
}
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 update
    sudo apt install openssh-server -y
    sudo 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
# Windows 服务注册名称
svc_name: "SSHAutoProxySvc"
# 本地 SOCKS5 侦听端口
local_port: 10888
# 远程 SSH 连接配置
ssh_addr: "182.92.105.77:22"
ssh_user: "tunnel_user"
ssh_pass: "SecurePassword123"
# 是否自动开闭 Windows 防火墙规则
firewall: true
# 异常断线后重连冷却时间 (秒)
reconnect_seconds: 10
# SSH 连接心跳保活间隔 (秒)
keepalive_seconds: 30

步骤 2:注册并启动系统服务

  1. 管理员身份 打开 CMD 或 PowerShell。
  2. 运行 sc.exe 命令,将程序一键注册为后台系统服务,并设为开机自动启动:
    1
    sc.exe create SSHAutoProxySvc binPath= "D:\network\svc.exe" start= auto
  3. 启动后台服务:
    1
    net start SSHAutoProxySvc

在客户端 clink.exe 同级目录下编辑 clink.yaml 配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 本地 HTTP 正向代理端口
http_proxy_port: 8088
# 本地 API 反向代理网关端口
api_proxy_port: 2026
# 上游 SOCKS5 服务地址列表(关联服务端的 local_port)
socks5_addrs:
- "127.0.0.1:10888"
# 目标 API 服务的远端上游地址
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 发出。