在 Docker 中运行 Cloudflare WARP
Cloudflare WARP 是 Cloudflare 提供的免费 VPN 服务,由于多数服务商都将其出口 IP 视作信誉良好的家宽 IP,许多人将其用于 IP 地址较脏的服务器,以便访问风控较严格的网站。然而当我们将它在自己的服务器上使用时,会遇到以下的问题:
- 官方 WARP 客户端在默认模式
1.1.1.1 with WARP
下会阻断所有入站连接,这意味着服务器上的网站和服务都无法被访问 - 官方 WARP 客户端在
Local Proxy
模式下尽管没有阻断入站连接的问题,其提供的 HTTPS/SOCKS5 代理并不能传输 UDP 数据包 - 为了防止滥用,Cloudflare 在部分地区阻止了第三方客户端 (wgcf 等) 访问 WARP 服务,暂不清楚它是否会扩大该措施的范围
本文将在 Docker 中运行官方 WARP 客户端,以解决上述问题。项目代码已经发布到 GitHub。
简短的使用说明
启动容器
要在 Docker 中运行 WARP 客户端,只需将以下内容写入 docker-compose.yml
,然后运行 docker-compose up -d
即可。
1 |
|
试一试它是否工作:
1 |
|
如果输出中包含 warp=on
或者 warp=plus
,容器已经正常工作。如果输出中包含 warp=off
,则说明容器未能连接到 WARP 服务。
配置
你可以通过以下环境变量来配置容器:
WARP_SLEEP
:等待 WARP 守护进程启动的时间,单位为秒,默认为 2 秒。如果时间过短,可能会导致 WARP 守护进程未启动就开始使用代理,从而导致代理无法正常工作。如果时间过长,可能会导致容器启动时间过长。如果你的服务器性能较差,可以适当增加该值。WARP_LICENSE_KEY
:WARP 客户端的许可证密钥,可选。如果你订阅了 WARP+ 服务,可以将密钥填入该环境变量中。如果你没有订阅 WARP+ 服务,可以忽略该环境变量。
数据持久化:使用主机卷 ./data
持久化 WARP 客户端的数据。你可以更改该目录的位置,或者使用其他类型的卷。如果修改了 WARP_LICENSE_KEY
,请删除 ./data
目录以便客户端重新进行注册。
更改代理类型
容器使用 GOST 来提供代理,环境变量 GOST_ARGS
是传递给 GOST 的参数。默认为 -L :1080
,即在容器内以 HTTP 和 SOCKS5 协议同时监听 1080 端口。如果你希望获得 UDP 支持或者使用其他协议提供的高级功能,可以修改该参数。具体参考 GOST 文档。
如果你修改了端口号,你可能需要同时修改 docker-compose.yml
中的端口映射。
容器的健康检查
容器的健康检查会检查容器内的 WARP 客户端是否正常工作。如果检查失败,容器将会自动重启。具体地,在启动 30 秒后,每隔 15 秒检查一次,如果连续 3 次检查失败,容器将会被标记为不健康并触发自动重启。
1 |
|
如果你不希望容器自动重启,可以删除 docker-compose.yml
中的 restart: always
。你也可以通过 docker-compose.yml
修改健康检查的参数。
工作原理
该镜像包括两个组件,Cloudflare WARP 和 GOST。WARP 客户端用于连接 WARP 服务,GOST 用于提供代理。其灵感最初来自于 hostloc 的一篇帖子。这篇帖子是对于在容器中运行 WARP 的(我能找到的)最早尝试,提供了很多有用的信息,但是有以下缺陷:
- 仍然使用了第三方客户端 WGCF,可能会被 Cloudflare 封禁
- 需要进行交互式操作,不便于部署
以其为基础,我进行了一系列的修改,代码托管在 GitHub。
WARP 官方客户端
下载和安装 WARP 官方客户端的过程在 WARP 文档中已经有很好的叙述了,这里不再赘述。关键在于如何将其在容器环境下运行起来。这些工作包括三部分:
- 为其创建 TUN 设备。容器中默认不存在 TUN 设备。
- 启动 WARP 守护进程。正常安装时,WARP 会将守护进程的相关配置写入
systemd
或者其类似物中,以在开机时自动启动。在容器中我们需要手动启动它。 - 给予足够的权限。
前两个问题在 entrypoint.sh
中解决:
1 |
|
需要注意的是
- TUN 设备的创建不能放在 Dockerfile 中,因为
/dev
被挂载为tmpfs
,并不在文件系统/
中。 - 我们需要等待守护进程启动后才能使用
warp-cli
命令。目前采用的是等待提前配置好的一段时间,如果各位有更好的方法,欢迎提出。
权限问题在 docker-compose.yml
中解决:
1 |
|
这赋予了容器足够但又不过多的权限。
夹带一点私货,我对于要求 --privileged=true
的容器持有强烈的抵触情绪,因为这意味着容器获得了宿主机的完整 root 权限,这是非常危险的。即使我们选择相信开发者,容器内的服务也有可能存在安全漏洞,一旦攻击者入侵了容器就能直接获得宿主机的全部控制权。通常情况下,容器并不需要如此高的权限,这样的要求往往是开发者懒得确认所需权限的偷懒行为,却严重危害了用户的安全。
GOST
GOST 是一个非常强大的代理软件,在本容器中我使用了它来提供代理服务,并且通过 GOST_ARGS
环境变量来实现了对于代理类型的配置。
健康检查
Docker 官方并不推荐在一个容器中运行多个进程,原因之一是子进程的崩溃可能无法被检测到。然而我通过一个巧妙的方式解决了这个问题。容器中运行了 WARP 守护进程和 GOST 代理,如果 GOST 代理崩溃,它将导致主进程(entrypoint.sh
)退出,从而触发自动重启;如果 WARP 守护进程崩溃,它将导致健康检查失败,从而触发自动重启。这样就实现了对于子进程崩溃的检测。
自动修复与宿主机的联通性
2024 年 7 月,GitHub 用户 @ostrolucky 报告由于 Cloudflare WARP 拦截了流量,宿主机可能会无法连接到容器。经过调查,我发现这是因为通过 Cloudflare Zero Trust 连接时,组织设置的 split tunnel 可能不包含 docker 网络所在的子网,导致流量被路由到 WARP 的网络接口中,容器发送的数据包无法到达宿主机。具体来说,Cloudflare WARP 通过设置路由规则列表 (通过 ip rule list
显示) 来将所有不包含特定 fwmask
的数据包转到其生成的路由表中处理,且通过 nftables 拦截了所有不属于 split tunnel 且不由 WARP 接口处理的数据包。
为了自动解决这一问题,我在容器的健康检查中增加了一个脚本,它将和健康检查一起运行,自动将必要的规则添加到 nftables 和路由规则列表,以确保容器的流量能够正常到达宿主机。用户可以通过环境变量 BETA_FIX_HOST_CONNECTIVITY=1
来启用这一功能。
References
- Hostloc 论坛 - 灵感来源
- StackOverFlow - How to create tun interface inside Docker container image?
- Cloudflare WARP docs
- GOST 文档
- Docker docs - Run multiple services in a container
讨论
本文章未启用评论功能,请在 GitHub 存储库中提交问题。
在 Docker 中运行 Cloudflare WARP