坑边闲话:现代软件的开发、测试、部署基本离不开容器技术,然而大陆地区的防火墙屏蔽了 Docker 官方的 Registry,这无疑给普通开发者带来了很大的负担和痛苦。本文介绍一般场景下绕过防火墙的阻断的方法,帮助开发者较快地访问官方 Docker 镜像平台。

1. 使用 OpenClash 规则集·

作为一个成熟的程序员,应该学会使用 Clash 等代理工具,毕竟良好的技术网站访问体验是增进技术的重要支撑。笔者选择使用 OpenClash 这款 OpenWrt 插件作为主代理工具,用以给局域网中的所有设备提供透明代理服务。

图 1. 正确添加要被代理的规则集合。其中「🔰国外流量」是我的配置文件中走代理的选择器,读者的 OpenClash 配置中也会有类似的选择器。

如图 1 所示,在 OpenWrt 的「服务」→「OpenClash」→「覆写设置」→「规则设置」页面,勾选「自定义规则」,随后将如下内容按照图示添加进去。

1
2
- DOMAIN-SUFFIX,docker.com,🔰国外流量
- DOMAIN-SUFFIX,docker.io,🔰国外流量

提醒:请查阅你 Clash 配置文件中的国外分流规则的名字,用以替换示例中的「🔰国外流量」。

重启 OpenClash 后,以 docker.comdocker.io 为后缀的域名皆可被 OpenClash 代理。

其他代理软件

现在的代理软件基本都是在本地做 DNS 劫持,所以它们的原理是类似的:

  1. 根据 GeoSite 数据进行域名、IP 分流,国内的走直连,GFW_list 的走代理;
  • 对于 GFW_IP,会直接通过代理进行会话建立;
  • 对于 GFW_Domain,Clash 等代理工具会将域名发送至代理端进行二次解析,防止本地污染。
  1. 使用代理节点进行加密通信。

Clash For Windows,Sing-Box 等工具应该有类似的修改界面。

2. 解决 IPv6 问题·

Docker Hub 镜像仓库支持双栈访问,如果你的机器支持 IPv6 上网,那么大多数情况下系统会自动解析出 Docker Hub 的 IPv6 地址并优先尝试建立 IPv6 连接。然而 Docker Hub 的 IPv6 地址同 IPv4 地址一样也被屏蔽了,而绝大多数代理节点均不支持 IPv6 代理,此时必然会出现建立连接失败的问题。那么该如何处理呢?方法有两种:

  1. 关闭本地机器的 IPv6 功能。这个代价太大,有些得不偿失的味道。
  2. 在本地的 DNS 服务器上做修改,当本机发起 docker.comdocker.io 相关的解析时,本地 DNS 服务器只返回 IPv4 地址,自动丢弃 IPv6 解析结果。

这里我们选择后者。笔者选择使用 AdguardHome 作为本地的 DNS 解析服务器。可在 /etc/adguardhome.yaml 中做如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
dns:
rewrites:
- domain: '*.docker.com'
answer: A
- domain: download.docker.com
answer: A
- domain: '*.docker.io'
answer: A
- domain: d2h67oheeuigaw.cloudfront.net
answer: A
- domain: registry-1.docker.io
answer: A

其中的 answer: A 指的是只返回 A 记录,丢弃 AAAA 记录,重启 AdguradHome 即可生效。

随后,将本地的 systemd-resolved 配置文件作如下修改:

1
sudo vim /etc/systemd/resolved.conf
1
2
DNS=${YOUR_ADGUARDHOME_ADDR}
FallbackDNS=1.1.1.1

随后重启 systemd-resolved 服务:

1
sudo systemctl restart systemd-resolved.service

在单网口并开启 IPv6 的场景下,该问题至此应该就迎刃而解了。

3. 解决多网口 DNS 解析问题·

3.1 查看接口的 DNS 解析配置·

在某些特殊场景下,服务器可能接入了多个网络。比如在实验室环境下,笔者的服务器会同时接入内网校园网,而这两个网络均可以访问互联网。此时就又出现麻烦了。尽管默认网关是单一的或有不同的优先级,但 Linux 一般会默认从两个网口的 DHCP DNS 服务器下发内容中读取信息,并配置本地的 DNS 解析请求策略。

使用如下命令列出 NetworkManager 的连接项:

1
sudo nmcli connection show

单网口输出应该类似:

1
2
3
4
5
NAME          UUID                                  TYPE      DEVICE
Ethernet_LAN b4d69a56-83ad-49ab-8b3b-af73181f2faa ethernet eth0
tailscale0 9dd76ec4-d16d-435c-99e2-5a33f690e7a4 tun tailscale0
docker0 f78a355e-25c5-4baa-bd7a-f581e3ddcaa6 bridge docker0
lo f3de2332-4b59-4dce-920f-880df8543779 loopback lo

其中 NAME 列是 NetworkManager 的连接项名称。

而在校园网双线接入情况下,一般会多出一个 ethernet 连接项,如下所示

1
2
3
4
5
6
NAME           UUID                                  TYPE      DEVICE
Ethernet_LAN b4d69a56-83ad-49ab-8b3b-af73181f2faa ethernet eth0
Ethernet_UCAS 6c6b0e8a-af23-48c3-bd76-0c8e1a9f0343 ethernet eth1
tailscale0 9dd76ec4-d16d-435c-99e2-5a33f690e7a4 tun tailscale0
docker0 f78a355e-25c5-4baa-bd7a-f581e3ddcaa6 bridge docker0
lo f3de2332-4b59-4dce-920f-880df8543779 loopback lo

由于 Linux 也会生成非默认出口接口上的 DNS 解析服务器,如执行下列命令进行 DNS 服务查询:

1
sudo resolvectl status

可以发现校园网接口上也出现了 DNS 服务器配置:

1
2
3
4
5
Link 9 (eth1)
Current Scopes: DNS LLMNR/IPv4 LLMNR/IPv6 mDNS/IPv4 mDNS/IPv6
Protocols: +DefaultRoute +LLMNR +mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 210.77.16.1
DNS Servers: 210.77.16.1 159.226.8.7 202.106.196.115 210.76.203.36 202.38.64.1

我们执行 resolvectl 命令查看 DNS 查询的细节。注意不要使用 nslookup,因为它会走系统接口,导致我们看不到系统具体的查询细节。

1
resolvectl query registry.hub.docker.com

输出结果如下:

1
2
registry.hub.docker.com: 157.240.9.36          -- link: eth0
2001::caa0:8028 -- link: eth1

果然,Docker Hub 的 IPv6 地址 2001::caa0:8028 是解析自 Ethernet_UCAS,即网卡 eth1. 由此断定,IPv6 的 Docker Hub 地址就是校园网解析出来的!罪魁祸首竟然是你

3.2 调整不同接口 DNS 配置优先级·

原则上 DNS 服务器是系统层级的配置,而非 interface 层级的配置。然而诡异的是 Linux 确实会进行多网口同时查询 DNS. 为此,我们使用下面的命令将校园网接口的自动 DNS 优先级调低.

1
2
3
4
5
6
7
8
sudo nmcli connection modify Ethernet_LAN ipv4.dns-priority -50
sudo nmcli connection modify Ethernet_LAN ipv6.dns-priority -50

sudo nmcli connection modify Ethernet_UCAS ipv4.dns-priority 100
sudo nmcli connection modify Ethernet_UCAS ipv6.dns-priority 100

sudo nmcli connection up Ethernet_LAN
sudo nmcli connection up Ethernet_UCAS

优先级准则

优先级值是整数,可以是负数、零或正数。没有严格的数值范围限制,但通常在 -1000 到 1000 之间。当系统有多个激活的网络连接,并且这些连接都有 DNS 服务器配置时,NetworkManager 会根据 dns-priority 的值来决定使用哪个连接的 DNS 服务器。数值越低(越负),优先级越高。

该命令与直接编辑 NetworkManager connection 配置文件等价。手动编辑其中之一的过程如下:

1
sudo vim /etc/NetworkManager/system-connections/Ethernet_UCAS.nmconnection

编辑后内容如下,特别注意 dns-priority 字段,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[connection]
id=Ethernet_UCAS
uuid=44f3b665-4971-4091-9f80-6cd0e409659e
type=ethernet
permissions=user:newton:;
timestamp=1727293688

[ethernet]
auto-negotiate=true
mac-address=55:66:44:C1:3B:41

[ipv4]
dns-priority=100
method=auto

[ipv6]
addr-gen-mode=stable-privacy
dns-priority=100
method=auto

[proxy]

另一个网口调高 DNS 的过程类似,只需注意将 100 改为 -50 即可。至此,多网口 DNS 查询请求处理完毕。

提醒

不要随便把网口的 DNS 自动配置关闭,因为这样做会失去灵活性和多活能力。

调节优先级的过程比较简单,而且在高优先级接口断掉的时候,次优先级接口的 DNS 能顶上去,使 Tailscale 这种服务可以保持连接。

总结·

本文详细说明了如何在复杂的网络环境下配置 Docker Hub 代理,其中 IPv6 绕过和多网口 DNS 选取是两个较为棘手的问题,好在都被一一发现并解决。