适用场景:家庭/小型办公网络,想让内网每台设备都拥有真·公网 IPv6 地址,实现直连、端口映射零配置、NAS/服务直接暴露互联网
前置:运营商已下发 IPv6(光猫/路由器 WAN 口能拿到 IPv6 地址/前缀)
核心目标:OpenWrt/iStoreOS 作为主路由/旁路网关,PD 下发前缀 → 内网 SLAAC/DHCPv6 自动获公网 IPv6 → 防火墙放行 → 服务监听 [::] → DDNS-AAAA 域名解析 → 可选 Cloudflare/HE 隧道兜底
—
0. IPv6 核心概念速查(只记这几个)
| 概念 | 说明 | 关键点 |
| 全球单播地址 (GUA) | 真·公网 IPv6,格式 2001:db8:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx | 运营商下发 /56/64 前缀,内网设备自动拼接后 64 位 |
| 链路本地地址 | fe80::/10,仅本链路通信,不路由 | 邻居发现协议 (NDP)、路由器通告 (RA) 依赖它 |
| 前缀委派 (PD, Prefix Delegation) | 上级路由器把 /56/60 前缀分给下级,下级再切 /64 给各 LAN | 开启 PD 是全家桶的关键 |
| SLAAC (Stateless Address Autoconfiguration) | 设备收到 RA → 自己拼凑 IPv6 地址,无需 DHCPv6 | 默认开启,隐私扩展 (RFC 4941) 会生成临时地址 |
| DHCPv6 (有状态/无状态) | 有状态 = 分配地址+DNS;无状态 = 仅下发 DNS/选项 | OpenWrt 默认「无状态 DHCPv6 + SLAAC」 |
| DUID / IAID | 设备/接口唯一标识,决定同一设备拿到的 IPv6 后缀是否固定 | 想固定后缀 → 绑定 DUID/IAID 或改用 DHCPv6 有状态 |
| NDP (Neighbor Discovery Protocol) | 替代 ARP,做地址解析、路由器发现、重复地址检测 (DAD) | 防火墙必须放行 icmpv6 类型 ½/¾/130-136 |
—
1. 光猫/上游路由模式确认(动手前必查)
| 运营商模式 | 现象 | 你的 OpenWrt 该怎么接 |
| 光猫桥接 + 路由器拨号 | 光猫不拨号,OpenWrt WAN 口 PPPoE 拨号获取 IPv4 + IPv6 | 最理想,OpenWrt 直接拿 PD 前缀 |
| 光猫路由模式 + OpenWrt 旁路网关 | 光猫拨号、DHCPv6/PD 已开启,OpenWrt 只做 LAN 网关 | OpenWrt WAN 口设为 DHCPv6 客户端 请求 PD,LAN 口开启 RA/DHCPv6 转发 |
| 光猫路由模式 + OpenWrt 二级路由 | 光猫下挂 OpenWrt WAN 口,双层 NAT | OpenWrt WAN 口 DHCPv6 客户端 请求 PD,需光猫支持 PD 透传/下发 |
| 仅 IPv4 / 运营商拦截 IPv6 | WAN 口完全拿不到 IPv6 地址/前缀 | 走隧道兜底(第 6 节:Cloudflare WARP / HE Tunnel / Tailscale IPv6) |
自测命令(在 OpenWrt SSH 执行):
# 1. WAN 口是否拿到 IPv6 地址/前缀
ip -6 addr show dev wan
# 应看到 2001:db8::/64 之类的 GUA,且有 `scope global`
2. 是否拿到 PD 前缀(关键)
ip -6 route show table all | grep -E ‘::/56|::/60|::/64’
或查看 odhcpd 状态
logread -e odhcpd | tail -20
---
### 2. OpenWrt / iStoreOS 核心配置(主路由模式为例)
#### 2.1 网络 → 接口 → WAN / WAN6
```text
【WAN】(IPv4 PPPoE)
协议: PPPoE
用户名/密码: 运营商给的
IPv6 设置: 勾选「请求 IPv6 前缀 (PD)」
PD 请求长度: 56 (或 60,视运营商下发)
PD 分配长度: 64 (每个 LAN 切 /64)
【WAN6】(IPv6 独立接口,可选)
协议: DHCPv6 客户端
请求 IPv6 地址: 勾选
请求 IPv6 前缀 (PD): 勾选
PD 请求长度: 56
自定义 DUID: 留空(或填固定值,防重拨前缀变)
关键:WAN 和 WAN6 二选一或仅保留 WAN6(iStoreOS 新版建议合并为单接口双栈)。必须勾选 PD 请求,否则内网设备拿不到公网前缀。
2.2 网络 → 接口 → LAN(及其它 LAN 接口)
协议: 静态地址
IPv6 分配长度: 64 (每个 LAN /64)
IPv6 分配提示: ::1 / ::2 / ::3 ... (不同 LAN 填不同 hint,避免冲突)
IPv6 分配模式: 「路由通告 (RA) + 无状态 DHCPv6」 (推荐)
或「有状态 DHCPv6」 (需固定后缀时)
RA 生命周期: 1800 秒
RA 最小间隔: 200 秒
IPv6 分配提示 很重要:::1 给主 LAN、::2 给 Guest、::3 给 IoT,不同 LAN 前缀自动区分。
2.3 网络 → DHCP/DNS → IPv6 设置(全局)
本地 IPv6 DNS 服务器: 勾选(OpenWrt 自身做 DNS)
IPv6 动态 DNS: 如需自动注册主机名 → 勾选「启用动态 DNS (DHCPv6)」
DHCPv6 服务: 「仅无状态」或「有状态」(按需)
RA 服务: 「服务器模式」
NDP 代理: 如有旁路网关/多子网需勾选
2.4 网络 → 防火墙 → 流量规则(必须放行的 ICMPv6)
| 名称 | 协议 | 来源区域 | 目标区域 | 动作 | ICMPv6 类型 |
| Allow-ICMPv6-Input | IPv6-ICMP | WAN | Device | 接受 | 1,2,3,4,128,129,130,131,132,133,134,135,136 |
| Allow-ICMPv6-Forward | IPv6-ICMP | WAN | LAN | 接受 | 1,2,3,4,128,129,130,131,132,133,134,135,136 |
| Allow-NDP | IPv6-ICMP | WAN | LAN | 接受 | 133,134,135,136 (Router Solicit/Advert, Neighbor Solicit/Advert) |
| Allow-DHCPv6 | UDP | WAN | Device | 接受 | 端口 546 (DHCPv6 客户端) |
| Allow-IPsec-ESP | ESP | WAN | Device | 接受 | (如跑 WireGuard/IPsec over IPv6) |
CLI 批量加规则(/etc/firewall.user 或自定义规则):
# 必放 ICMPv6 类型:1(Dest Unreach) 2(Pkt Too Big) 3(Time Exceeded) 4(Param Problem)
# 128(Echo Request) 129(Echo Reply) 130-136(NDP)
for t in 1 2 3 4 128 129 130 131 132 133 134 135 136; do
uci add firewall rule
uci set firewall.@rule[-1].name="Allow_ICMPv6_${t}"
uci set firewall.@rule[-1].src='wan'
uci set firewall.@rule[-1].dest='lan'
uci set firewall.@rule[-1].proto='icmp'
uci set firewall.@rule[-1].icmp_type="$t"
uci set firewall.@rule[-1].family='ipv6'
uci set firewall.@rule[-1].target='ACCEPT'
done
uci commit firewall && /etc/init.d/firewall restart
—
3. 内网设备自动获公网 IPv6(验证)
# Linux/macOS/Windows/手机连上 LAN 后,无需任何配置
ip -6 addr show scope global
# 应看到:2001:db8:abcd:1::xxxx:xxxx:xxxx:xxxx/64 (GUA)
# 以及:fe80::xxxx:xxxx:xxxx:xxxx/64 (Link-Local)
# 临时地址(隐私扩展):2001:db8:abcd:1::yyyy:yyyy:yyyy:yyyy/64 scope global temporary dynamic
# 测试直连
ping -6 ipv6.google.com
curl -6 https://ipv6.ip.sb
Windows 默认开启隐私扩展,每次重连后缀变,固定后缀两法:
- 网卡属性 → IPv6 → 取消「使用临时地址」
- 改用 有状态 DHCPv6(OpenWrt 开启 DHCPv6 Server → 有状态),配合 DUID/IAID 绑定
—
4. 服务端监听 [::](让服务同时支持 IPv4/IPv6)
4.1 Nginx / OpenResty / 1Panel / NPM
# 监听双栈
server {
listen 80;
listen [::]:80; # 关键:方括号括起 IPv6
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name nas.example.com;
# ...
}
4.2 Docker 容器网络模式
# docker-compose.yml
services:
jellyfin:
network_mode: host # 直接监听宿主机 [::]:8096
# 或 bridge 模式 + 端口映射
# ports:
# - "8096:8096"
# - "[::]:8096:8096" # 显式双栈映射
4.3 SSH / Samba / 其它服务
# /etc/ssh/sshd_config
ListenAddress 0.0.0.0
ListenAddress ::
# /etc/samba/smb.conf
[global]
bind interfaces only = yes
interfaces = lo eth0 # IPv6 自动包含
验证:ss -ltnp | grep -E ':80|:443|:8096' 应显示 :::8096 或 0.0.0.0:8096 与 :::8096 并存。
—
5. DDNS-AAAA:把域名解析到动态公网 IPv6
5.1 方案 A:OpenWrt 自带 DDNS + Cloudflare/阿里云/腾讯云 API
# 安装
opkg update && opkg install ddns-scripts_ cloudflare.com-v4v6 # 或 aliyun.com-v4v6
/etc/config/ddns(示例 Cloudflare):
config ddns 'global'
option date_format '%F %R'
option log_lines '250'
config service 'ipv6_cf'
option enabled '1'
option interface 'wan' # 或 wan6
option ip_source 'network'
option ip_network 'wan6'
option use_syslog '2'
option use_https '1'
option cacert '/etc/ssl/certs/ca-certificates.crt'
option service_name 'cloudflare.com-v4v6'
option domain 'nas.example.com'
option username 'your@email.com'
option password 'CF_Global_API_Key_或_Token'
option check_interval '10'
option force_interval '72'
option retry_interval '60'
option ip_version 'ipv6'
/etc/init.d/ddns enable && /etc/init.d/ddns start
logread -f -e ddns
5.2 方案 B:Docker 跑 qmcgaw/ddns-updater(多服务商、Web UI)
services:
ddns-updater:
image: qmcgaw/ddns-updater:latest
container_name: ddns-updater
restart: unless-stopped
environment:
- TZ=Asia/Shanghai
- PROVIDER=cloudflare
- DOMAINS=nas.example.com,ha.example.com
- TOKEN=your_cf_token
- IPV4=false
- IPV6=true
- SCHEDULE=@every 5m
volumes:
- /data/config/ddns-updater:/config
5.3 方案 C:Cloudflare Tunnel(零端口、自动证书、不需要 DDNS)
见第 6 篇「安全远程访问」第 C 方案,彻底不用管公网 IP 变不变。
—
6. 运营商不给/拦截 IPv6 → 隧道兜底三选一
| 方案 | 适用场景 | 优点 | 缺点 |
| Cloudflare WARP (wireguard-go / wgcf) | 仅需出站访问 IPv6 资源、或给容器加 IPv6 出口 | 免费、无限流量、全平台、自动换端点 | 仅出站、非固定 IPv6、不适合入站服务 |
| Hurricane Electric (HE) Tunnel Broker | 需要固定 /48 前缀、入站服务、BGP 玩家 | 免费 /48、稳定、支持 rDNS、全球 PoP | 需注册、ICMP 必须通、延迟取决于 PoP 距离 |
| Tailscale + IPv6 Subnet Router | 已有 Tailscale 组网、想给尾网设备分发 IPv6 | 复用尾网、自动 NAT64、零配置 | 仅尾网内可用、非公网原生 IPv6 |
6.1 HE Tunnel Broker 落地(OpenWrt)
# 1. HE 官网创建 Tunnel → 记下 Server IPv4、Client IPv6、Routed /48
# 2. OpenWrt 安装 6in4 包
opkg install 6in4
# 3. 网络 → 接口 → 新建接口 HE-IPv6
# 协议: 6in4
# 对端 IPv4: HE 给的 Server IPv4
# 本地 IPv6: HE 给的 Client IPv6 (如 2001:470:xxxx:xxxx::2/64)
# 路由前缀: HE 给的 Routed /48 (如 2001:470:yyyy::/48)
# 防火墙区域: wan
# 4. 启用 → 自动在 LAN 下发 /64 子网
HE 隧道必须放行协议 41 (IPv6-in-IPv4):
防火墙 → 流量规则 → 新增 → 协议 41、来源 WAN、目标 Device → 接受
—
7. 常见坑 & 一句话解决
| 现象 | 原因 | 修正 |
内网设备只有 fe80:: 链路本地地址 | PD 未生效 / RA 未发送 | 检查 WAN6 是否拿到 PD、odhcpd 日志、RA 服务 是否「服务器模式」 |
| 设备有公网 IPv6 但 Ping 不通外网 | 防火墙拦截 ICMPv6 / 转发规则缺失 | 必放 ICMPv6 类型 ½/¾/128/129/130-136,检查 FORWARD 链 |
| 重拨后前缀变、DDNS 还指向旧地址 | DUID 未固定 / 运营商强制换前缀 | OpenWrt WAN6 填「自定义 DUID」;DDNS 缩短检查间隔 |
Nginx 监听 [::]:443 报 Address already in use | IPv4/IPv6 双栈冲突、或 systemd-resolved 占 53 | listen [::]:443 ipv6only=on; 或关闭 net.ipv6.bindv6only=1 |
访问 http://[2001:db8::1] 浏览器报错 | 地址需加方括号、且证书 CN/SAN 必须含 IPv6 | 域名解析 AAAA 记录 → 走域名访问;自签证书需把 IPv6 加入 SAN |
| 运营商拦截 IPv6(只给 IPv4) | 地区/套餐限制 | 跑隧道兜底(HE / WARP / Tailscale) |
| 多 LAN 接口 IPv6 前缀冲突 | IPv6 分配提示 填相同值 | 每个 LAN 填不同 hint:::1、::2、::3… |
| Docker 容器拿不到 IPv6 | Docker daemon 未启用 IPv6 | /etc/docker/daemon.json 加 "ipv6": true, "fixed-cidr-v6": "2001:db8:abcd:ffff::/64" 重启 Docker |
—
8. 一键自检脚本(放 /usr/local/bin/check-ipv6.sh,定时跑)
#!/usr/bin/env bash
set -euo pipefail
LOG=/var/log/ipv6-check.log
exec >>"$LOG" 2>&1
echo "===== $(date -Is) IPv6 健康检查 ====="
# 1. WAN 口 IPv6
WAN6=$(ip -6 addr show dev wan scope global 2>/dev/null | head -1)
echo "WAN IPv6: ${WAN6:-无}"
# 2. PD 前缀
PD=$(ip -6 route show table all | grep -E '::/56|::/60' | head -1)
echo "PD 前缀: ${PD:-无}"
# 3. LAN 设备 IPv6
LAN6=$(ip -6 addr show dev br-lan scope global 2>/dev/null | head -1)
echo "LAN IPv6: ${LAN6:-无}"
# 4. 外网连通性
ping -6 -c 2 -W 3 ipv6.google.com >/dev/null 2>&1 && echo "IPv6 连通: ✅" || echo "IPv6 连通: ❌"
# 5. DDNS 解析是否匹配当前 WAN IPv6
CURRENT=$(ip -6 addr show dev wan scope global | awk '/inet6/{print $2}' | cut -d/ -f1 | head -1)
DNS=$(dig +short AAAA nas.example.com @1.1.1.1 | head -1)
if [[ "$CURRENT" == "$DNS" ]]; then
echo "DDNS 同步: ✅ ($CURRENT)"
else
echo "DDNS 同步: ❌ 当前 $CURRENT vs DNS $DNS"
fi
# 6. 防火墙关键规则
for t in 1 2 3 4 128 129 130 131 132 133 134 135 136; do
iptables -t filter -C forwarding_wan_rule -p ipv6-icmp --icmpv6-type $t -j ACCEPT 2>/dev/null || echo "缺失 ICMPv6 type $t 转发规则"
done
echo "===== 检查结束 ====="
chmod +x /usr/local/bin/check-ipv6.sh
# 加入 crontab 每 10 分钟
echo "*/10 * * * * /usr/local/bin/check-ipv6.sh" >> /etc/crontabs/root
/etc/init.d/cron restart
—
9. 进阶:给容器/虚拟机分发独立公网 IPv6(MACVLAN / IPVLAN / SR-IOV)
# docker-compose.yml:macvlan 让容器直接拿 LAN 侧公网 IPv6
networks:
macvlan_net:
driver: macvlan
driver_opts:
parent: br-lan # 或 eth0
mode: bridge
ipam:
config:
- subnet: "2001:db8:abcd:1::/64" # 必须是 LAN 实际前缀
gateway: "2001:db8:abcd:1::1"
services:
jellyfin:
image: jellyfin/jellyfin:latest
networks:
macvlan_net:
ipv4_address: 192.168.10.50
ipv6_address: 2001:db8:abcd:1::50
# 容器内直接拥有公网 IPv6,无需端口映射
前提:宿主机网卡支持混杂模式、交换机允许 MAC 地址透传(家用交换机通常默认允许)。
—
10. 资源 & 参考
—