Featured image of post 使用 dae 配合 Clash 实现 Linux 网卡级全局代理,支持代理 Docker 容器

使用 dae 配合 Clash 实现 Linux 网卡级全局代理,支持代理 Docker 容器

探索 Linux 下使用 dae 配合 Clash 实现网卡级全局代理的方法,包括支持代理 Docker 容器。详细介绍配置 dae 和解决常见代理问题的技巧。

不讲武德的软件们

咱们国家的人最喜欢自己给自己创造困难,就比如墙这个东西,普通人可能感觉不太大,但对于开发者来说问题就大了。这几十年来诞生了不知道多少工具来解决各种围绕墙的难题,而几乎所有奇思妙想、奇技淫巧都是国内首创的(在这方面咱们国人可谓领先世界)。为啥?因为有需求才有市场,国外人家根本不会遇到这种难题,当然就不需要解决这些难题的工具了。

比如开发中一个很常见的场景,使用 Docker 拉镜像的时候,就需要出墙。从 docker.io 上拉镜像还好,虽然 Dockerhub 主域名被墙了,但 docker.io 这个存储镜像的域名却逃过一劫,就是在某些情况下速度极慢。不过倒是可以用国内镜像站解决大部分问题。但倘若要从 ghcr.io、谷歌云等地方拉镜像就不行了,这些域名和 IP 都被墙的死死的。这时候就不得不配置代理了。

Linux 下常见的代理配置方式是开个 Clash 之类的代理软件,然后export ALL_PROXY/HTTP_PROXY/HTTPS_PROXY设置环境变量,这样配置之后大多数「识相」的软件就会自动使用设置的代理地址。坏就坏在少数极端反 RFC 软件分子「不讲武德」,比如 Docker,比如 apt,比如 git,根本不理会这些环境变量,就是走直连。要给这些软件设置代理就费事了。

Docker 这样还情有可原,毕竟人家是 C/S 架构,你命令行操作的只是 docker-cli,人家 docker daemon 也没必要关照你的环境变量。因此咱们可以用 Docker 守护进程的配置文件来设置代理(来源):

1
2
3
4
5
6
7
8
9
{
 "proxies": {
   "default": {
     "httpProxy": "http://proxy.example.com:3128",
     "httpsProxy": "https://proxy.example.com:3129",
     "noProxy": "*.test.example.com,.example.org,127.0.0.0/8"
   }
 }
}

而 apt 身为一个在终端运行的命令行工具,配置代理居然也要在配置文件中加这一行让人摸不着头脑的配置(来源):

1
Acquire::http::Proxy "http://yourproxyaddress:proxyport";

git 则又有自己的配置文件。

一百种软件一百个样,为每个软件都在自己的配置文件中配置代理,可得累坏开发者了!

对于 apt 和各种包管理器(npm、go、pip 等)来说,自己用用倒还可以使用国内镜像,例如 tuna 镜像站等。但在 Docker build 镜像的时候,你总不能把的镜像地址和127.0.0.1:7890在 Dockerfile 里四处乱写吧?那样老外用你的镜像的时候,一运行 npm install 就得连到中国下软件,然后再报个 7890 端口无法连接的错误,岂不让人贻笑大方?实在太不国际化了。

半吊子的解决方案

之前偶然间发现了一个命令行工具gg,可以在一定程度解决代理问题。它用的是一种类似隧道的解决方案,可以在配置好代理节点后,强制在 shell 中运行的命令走代理,比如这样:

1
gg wget -O frp.tar.gz https://github.com/fatedier/frp/releases/download/v0.38.0/frp_0.38.0_linux_amd64.tar.gz

或者直接打开一个全局强制代理的 shell:

1
2
3
4
gg bash

git clone --depth=1 https://github.com/torvalds/linux.git
curl ipv4.appspot.com

但缺点也很明显,就是支持终端程序,像 Docker 这样的 C/S 架构的就不行了,Docker build 就更不行了。

dae:eBPF 网卡级代理

要解决这些问题,基本上只能用 tproxy 了。但我嫌 iptables 规则配置起来太麻烦,于是在网上搜有没有更简单的(配置更少的)方案,还真让我给找到了。

dae是一个用 Go 语言开发,基于 eBPF 实现的网卡级代理工具。而且不止代理本机,还能作为网关转发流量,让局域网内的设备也能科学上网。它能完美解决如上所述所有需求,堪称杀手级软件。

配置 dae

dae 支持多种代理协议,不仅限于 http 和 socks5,还支持常见的 trojan、vless 等。由于我还是习惯使用 Clash 管理代理规则,因此在安装之后,直接写了个最简配置,把除了 Clash 自己之外的流量都路由到 7890 端口上:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
root@phoenix:~# cat /usr/local/etc/dae/config.dae 
global{
        log_level: info
        wan_interface: ens160
        lan_interface: docker0
        auto_config_kernel_parameter: true
}
group {
        my_group{
                policy: fixed(0)
        }
}
routing{
        pname(clash.meta-linux-amd64-compatible-v1.16.0) -> must_direct
        fallback: my_group
}
node{
        local:'socks5://127.0.0.1:7890'
}

这里做一下简单解释。

WAN & LAN

1
2
wan_interface: ens160
lan_interface: docker0

WAN 是为本机绑定的网卡,填入 VPS 自身的网卡。我这台是 ESXI 的虚拟机,因此是 ens160,云服务器一般是 eth0,可以ip a自己看一下。LAN 是为局域网转发的网卡,这里因为需要为 Docker 管理的容器转发代理流量,因此就填 docker0,这个是固定的。

注意 Docker build 的时候其实也是开了一个容器在里面隔离构建,所以上游接到的也是 docker0 的虚拟网卡。

关于本机网卡和 Docker 网卡,可以看一下这篇解释:https://stackoverflow.com/questions/37536687/what-is-the-relation-between-docker0-and-eth0

pname

1
pname(clash.meta-linux-amd64-compatible-v1.16.0) -> must_direct

指定了走直连的进程,这里显然是直接让 Clash 本身走直连,避免回环。只需要在括号内填写进程名即可,不需要加完整路径。可以使用如下命令来看:

1
2
3
root@phoenix:~# ps aux | grep clash
root        2725  0.0  0.3 1272992 40472 ?       Ssl  Dec09  10:57 /root/clash/clash.meta-linux-amd64-compatible-v1.16.0 -d config/
root     2489415  0.0  0.0   6608  2264 pts/4    S+   14:14   0:00 grep --color=auto clash

可以根据官方文档的例子对配置文件进行优化,比如加入更多在 dae 层就可以完成的分流动作,而不必转发到 Clash 层,这样可以提高一点速度。比如这里给出一个更复杂一点的配置文件:

 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
global{
        log_level: info
        wan_interface: ens160
        lan_interface: docker0
        auto_config_kernel_parameter: true
}
dns {
  upstream {
    alidns: 'udp://223.5.5.5:53'
  }
  routing {
    request {
      fallback: alidns
    }
  }
}
group {
        my_group{
                policy: fixed(0)
        }
}
routing{
	pname(clash.meta-linux-amd64-compatible-v1.16.0) -> must_direct
	pname(systemd-resolved) -> must_direct
	domain(geosite:cn) -> direct
	ip(geoip:private) -> direct
	ip(geoip:cn) -> direct
	fallback: my_group
}
node{
        local:'socks5://127.0.0.1:7890'
}

配置 ufw

如果使用 ufw 作为防火墙的话,还有个坑,就是需要放行 tproxy 的 12345 端口和 mark 为 0x8000000 的转发流量。因为不知道这一点,我还专门去提了个issue。十分感谢热心开发者的解答~

先使用 ufw allow 放行 12345 端口(如果你的 ufw 是默认放行的话,就不需要了):

1
ufw allow 12345

然后编辑/etc/ufw/before.rules,找找看在大概这两行注释之间加入如下配置:

1
2
3
4
5
# End required lines

-A ufw-before-input -m mark --mark 0x8000000 -j ACCEPT

# allow all on loopback

还有一个 ipv6 的,/etc/ufw/before6.rules

1
2
3
4
5
# End required lines

-A ufw6-before-input -m mark --mark 0x8000000 -j ACCEPT

# allow all on loopback

然后输入ufw disable && ufw enable重启下 ufw 即可。必要时还需要重启下 dae:service dae restart

测试访问

1
2
3
4
root@phoenix:~# curl ip.sb
92.118.*.* [redacted]
root@phoenix:~# docker run --rm curlimages/curl:8.5.0 -v ip.sb
92.118.*.* [redacted]

既可以在本机上直接使用,又可以在容器中连上。这样就算成功了。

Licensed under CC BY-NC-SA 4.0