在 Docker 中配置 IPV6 NAT

第 1 节 概述

由于公网 IP 的获取比较困难,本人的 NAS 上的各种 Docker 应用都是通过路由器的公网 IPV6 地址进行代理的,使用的是在 OpenWrt 上部署到一款名为 Lucky 的应用,Github项目地址, 这款应用聚合了端口转发、动态域名、自动证书等功能,一个应用就能解决将服务部署到公网的全部流程。

因为 Lucky 集成了 socat 的功能,可以进行 IPV6 到 IPV4 的端口转发,所以通常 NAS 上的应用只要能通过 IPV4 地址访问到就可以部署到公网。

但是这样会存在一个问题:

信息

Docker 默认情况下不会配置 IPV6 功能,因而在 Docker 容器内无法通过域名(IPV6)去访问别的容器,只能使用本地的 IPV4 地址来访问。这样不方便,并且有的应用需要SSL或者设置了BaseURL,直接用 IPV4 地址访问可能会有问题。

不过,一直以来,本人并不知道 Docker 默认没有 IPV6 ,主要原因如下:

  • 我在 OpenWrt 上安装了Clash,而 Clash 为了保证访问的私密性,配置了 FakeIP 功能。
  • 我没有设置 FakeIP Filter,所以即便是本地网络之间的访问,地址也会被转成 FakeIP。
  • 访问的 IPV6 地址也会被转成 FakeIP,并且 FakeIP 是保留 IP 段的 IPV4 地址。
  • 路由器负责解析 FakeIP,并且 Lucky 将解析后的结果代理到了对应的 IPV4 地址。

所以,阴差阳错之下,本人一直在容器内正常使用着 IPV6 功能。

https://img.papergate.top:5000/i/2025/08/68a0b70cc317c.webp

不过,也许是因为 Clash 时常可能访问不了,或者别的原因,容器内访问 IPV6 时常也会访问不了。并且用这种方式会增加通信之间的消耗,增加网络的复杂性,容易出现各种未知的问题,所以只好想办法来解决这一问题。

查看 OpenWrt 上的路由器网络,WAN6 IP 地址的掩码是 64 位的,这说明运营商只给我分配了这一个 IPV6 公网地址,我不可以通过前缀委派(Prefix Delegation,PD)给其他内网设备分配公网 IPV6 地址,所以从路由器开始,我的后续所有的设备的 IPV6 地址都只能通过网络地址转换(Network Address Translation,NAT)来分配。

https://img.papergate.top:5000/i/2025/08/689583678e125.webp

本人的 Docker 容器基本全都是桥接的,于是解决这个问题有 3 个步骤:

  • Nas 本身要配置 IPV6
  • Docker 的虚拟网口和 NAS 之间要配置 NAT
  • Docker 容器内要配置 IPV6 并且和 Docker 网口之间配置 NAT

在下文中,本人将依次讲述这些如何配置。

第 2 节 路由器网络

本人使用的是威联通的 NAS,登录 NAS 系统,来到控制台->网络与虚拟交换机->网络适配器,配置 IPV6 如下:

https://img.papergate.top:5000/i/2025/08/689583dddf657.webp

第 3 节 NAS 网络

通过 ssh 连接到 NAS,通过测试,威联通的 NAS 安装了iptablesip6talbes,但是没有安装ip6tables_nat,所以不可以进行 IPV6 的 NAT。

Github 上有人编译了威联通的 ip6tables_nat 包,项目地址。去 release 里头下载modules.zip并解压得到ip6table_nat.ko即可。

通过 ssh 连接到 NAS,执行以下命令,将对应目录挂载到/tmp/config,具体参照网址

1
sudo -i mount $(/sbin/hal_app --get_boot_pd port_id=0)6 /tmp/config

在目录下新建文件autorun.sh,填入以下内容

1
2
3
4
/sbin/modprobe ip6_tables
/sbin/modprobe nf_nat
/sbin/modprobe xt_MASQUERADE
insmod /「PATH」/ip6table_nat.ko

将文件修改为可执行,并解除挂载:

1
2
chmod +x /tmp/config/autorun.sh
umount /tmp/config

登录 NAS 系统,来到控制台->硬件->常规,对应选项上打勾:

https://img.papergate.top:5000/i/2025/08/689587f7ae60a.webp

重启 NAS 或者手动执行autorun.sh中的内容。

在 Docker 中创建支持 IPV6 的网络:

1
2
3
4
5
6
7
8
docker network create \
  --ipv6 \
  --subnet=xxxx:xxxx:xxxx::/64 \
  --gateway=xxxx:xxxx:xxxx::1 \
  --subnet=xx.xx.xx.xx/24 \
  --gateway=xx.xx.xx.1 \
  --opt com.docker.network.bridge.name=ipv6 \
  ipv6

NAT分为 SNAT 和 DNAT 两种:

  • DNAT 修改目标 IP 地址,主要用来进行端口转发,将公网IP端口映射到内网IP端口
  • SNAT 修改源 IP 地址,用来解决 IP 地址不足的问题,将内网IP端口修改为公网IP端口

这里要实现的目标不是通过内网的 IPV6 地址访问内网的服务,因为 Docker 原本就有 IPV4 的端口转发,更加简单好记。

所以,只考虑配置 SNAT,使得容器内访问 IPV6 地址,修改 IP 端口,变为 NAS 访问 IPV6 地址,进而交由路由器来处理,方法如下:

1
2
3
sudo ip6tables -A FORWARD -i ipv6 -o eth0 -j ACCEPT
sudo ip6tables -A FORWARD -i eth0 -o ipv6 -j ACCEPT
sudo ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

第 4 节 容器内部

Docker 在创建容器时,一般就自动创建好了容器内的防火墙和NAT规则,规则也比较简单,通常不需要进行任何设置就可以访问 NAS。

一个比较特殊的容器是 WireGuard,其内部可能存在多个网口,比如wg0、wg1、wg2等,需要配置相关的 NAT 规则,这种方式的目的是隔离多个网口,并且每个网口分别做防火墙,提供给不同的人使用。

Docker 部署方式如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
docker run -itd --name=WireGuard --restart always \
--network ipv6 \
--cap-add=NET_ADMIN \
-e PUID=「PUID」 \
-e PGID=「PGID」 \
-e TZ=Asia/Shanghai \
-e SERVERURL=「URL」 \
-e SERVERPORT=「POST」 \
-e PEERS=3 \
-e PEERDNS=「DNS」 \
-e INTERNAL_SUBNET=xx.xx.xx.0 \
-e INTERNAL_SUBNET6=xxxx:xxxx:xxxx:: \
-e ALLOWEDIPS=0.0.0.0/0,::/0 \
-e PERSISTENTKEEPALIVE_PEERS=25 \
-e LOG_CONFS=true \
-p 51820:51820/udp \
-p 51821:51821/udp \
-p 51822:51822/udp \
-v /「PATH」/config:/config \
--sysctl="net.ipv4.conf.all.src_valid_mark=1" \
--sysctl="net.ipv6.conf.all.disable_ipv6=0" \
--sysctl="net.ipv6.conf.all.forwarding=1" \
--sysctl="net.ipv6.conf.default.forwarding=1" \
linuxserver/wireguard:latest

需要在容器内使用 IPV6 时,需要添加的有:

1
2
3
--sysctl="net.ipv6.conf.all.disable_ipv6=0" \
--sysctl="net.ipv6.conf.all.forwarding=1" \
--sysctl="net.ipv6.conf.default.forwarding=1" \

其他项不是必备的配置,可以创建以后手动进行修改。

服务器端的配置如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[Interface]
Address = xx.xx.xx.1/24, xxxx:xxxx:xxxx::1/64
ListenPort = 51820
PrivateKey = xxxxx
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip6tables -A FORWARD -i %i -j ACCEPT; ip6tables -A FORWARD -o %i -j ACCEPT; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip6tables -D FORWARD -i %i -j ACCEPT; ip6tables -D FORWARD -o %i -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

[Peer]
PublicKey = xxxxx
PresharedKey = xxxxx
AllowedIPs = xx.xx.xx.2/32, xxxx:xxxx:xxxx::2/128