文章

(几乎)无感的校园网 VPN 开源方案

(几乎)无感的校园网 VPN 开源方案

在校园网环境中,有些资源必须走校园网内的 DNS 或代理访问,而其他流量则希望走普通代理。 为了实现 clash 和 EasierConnect 的同时运行,并且尽可能减少重复操作,笔者对 EasierConnect 进行修改, 使其可以通过 Clash 覆写功能和 HTTP API 自动切换策略组节点。

最终达到的效果是:

  • 本地 SOCKS5 代理,由 EasierConnect 提供。
  • Clash 中创建一个校园网策略组,用于控制校园网内资源的访问。
  • 自动在登录后将 EasierConnect 设置为策略组节点;在关闭时切换回 DIRECT 以避免可能的问题。

使用 Clash 覆写功能添加本地 SOCKS5 和校园网规则

大多数 Clash 客户端支持通过配置文件覆写代理组和规则,可以快速添加新的节点和策略组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   proxies:
     - name: "EasierConnect"
       type: socks5
       server: 127.0.0.1
       port: 1080

   proxy-groups:
     - name: "NJU"
       type: select
       proxies:
         - "EasierConnect"
         - "DIRECT"

   rules:
    - 'IP-CIDR,114.212.0.0/16,NJU'
    - 'DOMAIN-SUFFIX,nju.edu.cn,NJU'

修改 EasierConnect 源码,通过 Clash HTTP API 切换策略节点

EasierConnect 本身提供了 SOCKS5 服务,但无法自动通知 Clash 切换策略组节点。笔者修改了源代码,实现:

  • 登录 VPN 后调用 Clash HTTP API 设置策略组节点为 EasierConnect
  • 程序退出或异常时恢复 DIRECT 节点,保证校园网访问不受影响。

修改核心逻辑

  1. 新增 utils/clash.go 工具函数
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
func SetClashProxy(group string, proxyName string) {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("SetClashProxy recovered from panic: %v", r)
        }
    }()

    url := fmt.Sprintf("%s/proxies/%s", clashAPIHost, group)
    reqBody, _ := json.Marshal(ClashRequest{Name: proxyName})

    req, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(reqBody))
    if err != nil {
        log.Printf("SetClashProxy new request error: %v", err)
        return
    }

    req.Header.Set("Content-Type", "application/json")
    if clashSecret != "" {
        req.Header.Set("Authorization", "Bearer "+clashSecret)
    }

    client := http.Client{Timeout: 3 * time.Second}
    resp, err := client.Do(req)
    if err != nil {
        log.Printf("SetClashProxy error: %v", err)
        return
    }
    defer resp.Body.Close()

    log.Printf("SetClashProxy success: group=%s, proxy=%s (status=%d)", group, proxyName, resp.StatusCode)
}
  1. 修改 main.go,在登录成功后和程序退出时调用
1
2
utils.SetClashProxy("NJU", "EasierConnect") // 登录后
defer utils.SetClashProxy("NJU", "DIRECT")  // 退出时恢复

注意,启用这个功能需要开启 clash 核心的外部控制。

硬编码个人信息

运行 EasierConnect 需要指定账号密码等个人信息,虽然可以通过外部脚本传入参数来指定,但既然已经修改了源代码,不妨直接将个人信息硬编码为程序的默认值。

如果后续我有公开的打算,会考虑修改为从外部 config 文件读取这些信息。

结语

这个方案仅仅是一个折中的策略,在所有提供 IPV6 的地方笔者均推荐使用 tailscale。

总之,通过这套方案,在网络环境极端糟糕的地方(比如某为的茶思屋)也可以实现几乎无感和无副作用的连接校园网了,而且还不用担心 ITSC 查水表(但也许会被深信服查)。

当然,这个方案还是有不尽人意的地方,比如某些仅校内 DNS 才能查询的域名是无法访问的,但笔者要去享受国庆假期了,所以挖个坑之后忍不了了再填。

本文由作者按照 CC BY 4.0 进行授权