科学上网的碎碎念

2025 年 07 月 21 日

前几天写了一篇文章,里面尝试着做了一个 docker image,用于在 claw cloud 的免费 docker 里面做科学上网,不过现在已经不用了。

因为我重新写了一个。

为什么要重写呢,大概是因为我的执念吧。claw cloud 最小的实例内存是 64M。但 xray-core + cloudflared 两者加起来轻松爆表,所以很容易就 OOM 了。这个是真没辙。我还是有办法把内存控在 64M 以内的,但这个办法……感觉没办法简单说清楚啊。

那就碎碎念一下最近几天学到的东西吧。想到哪边就扯到哪边。

关于科学上网的思考

科学上网本质上就是代理,代理的作用实际上只是转发请求,因此需要的只是一台机器,网络有公开的入口出口

对于 Docker 平台而言,在上面运行应用时,一般都会有网络,跟任何一台能联网的机器一样。也就是说它有出口

但是,我们没办法直接连到这台机器的某个端口,因为它的网络是在一个局域网里,而且这个局域网你无法控制。

也就是说,这台机器的网络没有公开的入口

所以下面就要靠赛博善人 cloudflare 了。

通过 cloudflared 建立入口

Cloudflare 提供了 cloudflared,是一个可执行程序。该应用运行时会建立一条通道(tunnel),cloudflare 会将流量转移到这个通道中,让你本地的应用也能通过 internet 访问。这就相当于动态建立了一个入口。但该入口只支持 http/https 流量。

注1:cloudflare 会给 http 流量增加 tls。因此本地的 http 服务在 internet 上就是 https 服务。

注2:cloudflared 也支持 tcp/ssh/rdp 等协议的流量,但这些需要客户端同时运行 cloudflared,使用过于复杂。

注3:cloudflared 有快速通道完整通道两种。快速通道不需要注册账号,cloudflare 会在 trycloudflare.com 上生成一个临时二级域名,允许访问一个本地端口。完整通道需要注册域名,但部分配置需要在 cloudflare 的 dashboard 上完成。

因此,如果建立了 cloudflare 的通道,不需要另外暴露端口,docker 应用就可以完成代理功能。

现在,入口有了,但服务器收到的请求还需要解析,才能得到真正的目标地址并完成转发。这部分的工作就要靠代理工具了,比如 xray-core。

通过 xray-core 实现代理

xray-core 就是负责把进来的请求翻译成真正的请求,然后在真正的目标地址,和发起请求的地址之间,来回搬运数据。

从入口进来的是 http 请求,当然,可以升级成 websocket。虽然 xray-core 支持多种代理方式,但其中只有 ws 传输方式可以包裹在 http 流量中,其他传输方式基本都是基于 tcp 流量的。

注:实际上 grpc 也可以放在 http 流量中,但相比 ws 而言,兼容性和性能上都没有明显优势,因此不再考虑。

xray-core 实现了一个 vless 协议,通过 ws 传输时,就叫 vless over ws。本来还应该加上 tls 保证安全,但实际上 tls 已经被 cloudflared 处理完了。所以只用考虑 ws 就行。

vless over ws 实现起来相当简单,几百行代码就可以,所以仅仅为了这个功能的话,实在不值得用 xray-core,杀鸡焉用牛刀嘛。用 xray-core 基于 tcp,通过更复杂的协议(比如 reality)实现更稳定的代理,才是更好的选择。

vless over ws 虽然简单,跟 cloudflared 配合起来还是非常直接的。所以还是有必要实现一下。

vless over ws 的实现方式

这个方法参考的资源很多,比如这个,或是这个,这两个都是在 cloudflare worker 上建立 vless over ws 服务的。

但值得一提的是,cloudflare worker 有一个限制,就是没有对外的 udp 接口。这样会导致无法转发 udp 请求。

当然,作为变通的方式,大部分在 cloudflare worker 上的 vless 实现,都只处理了 dns 请求,并且是把 udp 的 dns 请求通过 doh 或是 tcp 方式转发出去。我自己的实现倒是不受这个限制,所以直接做了 udp 转发。

值得一提的是在 youtube 上看到的一位博主 yonggekkk,在他的 github 里提供了几个方案,一些实现方式的确匪夷所思,例如:

  • https://github.com/yonggekkk/Cloudflare-vless-trojan,虽然同样是在 cloudflare worker 中实现,但这个实现里利用了 cloudflare worker 的特性,修改了入口和出口,使得流量传送的方式更加灵活。
  • https://github.com/yonggekkk/ArgoSB,这个脚本能够在目标机器上灵活的安装 cloudflared/sing-box/xray-core/warp,并且进行适当的配置,也能调整出口走 warp。

事实上我的方案最初的出发点,是因为看不懂上面 ArgoSB 那一套配置方案,于是干脆就自己动手,顺便学习一下。

关于 ArgoSB 的一些源码的分析

ArgoSB 开放的源码实际上是不完整的,缺少了 docker 中的启动脚本部分。但这部分可以把 docker image 拖到本地启动后查看。

对于完整的 ArgoSB 来说:

  • docker 中的启动脚本在 3000 端口监听 http 请求,也完成了 vless over ws 功能。但由于使用 nodejs 实现,所以比较重,这也就是为什么项目说明中 64M 只能启动 vless over ws 的原因,因为没有足够的资源再去启动 cloudflared。
  • argosb.sh 主要是负责下载几个可执行程序和配置文件的生成。
  • 由于 cloudflared 无法转发 http 以外的流量,因此项目中提到的 tcp 方式,实际上是独立的 xray-core/sing-box 服务,需要将 tcp 的端口映射到外部公网端口。

实际上所谓的 Reality,可以认为是把 tls 的部分一并处理了。由于 cloudflared 已经处理了 tls 部分,无法接管原始的流量,因此 Reality 这种方式是没法走 cloudflared 的。

看懂了 ArgoSB 的源码,我大概理清了这部分实现所需要的概念,不过我觉得可以根据我的需求做一点点调整,比如:

  • 用 64M 内存完成 cloudflared + vless over ws 服务。

  • 允许 web 客户端通过 web terminal 对服务器进行简单操作。

于是我做了 plankrd

关于 plankrd

plankrd = plank road(栈道)。

应用本身是用 rust 写的,能学到的各种省资源的做法都用上了。这个 web 应用单独跑起来大约占 8M 内存,运行期有增长但不严重。也通过环境变量控制了一下 cloudflared 的资源。目前观察跑起来大约用了不到 60MB,长期稳定性还有待观察。

因为是练手,目前仅仅放出了一个 docker image(基础镜像是 alpine),支持的功能包括:

  • 在默认 8080 端口提供 http 服务,包括:
    • 查看 server 环境数据信息。
    • 通过 web terminal 操作服务器。
  • 集成 vless over ws。

由于是在 docker 中运行,目前仅支持通过环境变量配置(可执行程序也支持 .env 文件),包括:

可以通过环境变量来调整应用的行为。应用支持 .env 文件。

最少需要配置的环境变量和功能的对应关系如下:

  • PLANKRD_USERNAME + PLANKRD_PASSWORD 对应基础功能,也就是 http 服务所提供的功能。
  • CLOUDFLARED_TOKEN + CLOUDFLARED_PUBLIC_DOMAIN 对应 cloudflared 完整通道,这两个数据都要从 cloudflare 获取。
  • VLESS_CLIENT_ID + VLESS_WS_PATH 对应 vless over ws 的配置。

启动后,在 log 中,可以看到域名的链接,以及 vless 的地址。

完整的配置方案如下(大部分其实真心不用配,约定优于配置):

变量名功能默认备注
PLANKRD_USERNAME访问网页时所需的用户名必须设置
PLANKRD_PASSWORD访问网页时所需的密码必须设置
PLANKRD_LOCALHOST是否仅监听 localhostfalse影响 PLANKRD_ADDRESS 默认值
PLANKRD_ADDRESS配置监听地址0.0.0.0 或 127.0.0.1配置后 PLANKRD_LOCALHOST 配置无效
PLANKRD_PORT配置监听端口8080
CLOUDFLARED_ENABLED启用 cloudflaredtrue
CLOUDFLARED_SPECIFIED_RELEASE指定 cloudflared 版本""默认使用最新版本
CLOUDFLARED_TOKEN使用完整通道""通过 cloudflare 获取,为空则启用快速通道
CLOUDFLARED_QUICK_HTTP_PORT快速通道对应 http 端口$PLANKRD_PORT快速通道仅可对应一个端口
CLOUDFLARED_PUBLIC_DOMAIN完整通道对应域名""通过 cloudflare 获取,默认需要在 cloudflare 上配置为 http://localhost:8080,生成 https/vless 公网地址时需要
VLESS_CLIENT_IDVLESS 服务对应的 uuid(userid)0为空则不会启动 vless over ws 服务
VLESS_WS_PATHVLESS 服务对应的 ws path""为空则不会启动 vless over ws 服务,需要合法路径,建议使用隐晦值,如 /api/chat
VLESS_URL_COMMENT生成 URL 的备注名""最终 vless url 的备注名会组合此配置

项目还在进化中,未来应该会加上 xray-core,不过那样就需要 128MB 内存了。

Top