家用宽带 IPv6 实践笔记:固定地址、临时地址、DDNS 与公网访问
- Linux
- 5小时前
- 16热度
- 0评论
我在给 PVE 虚拟机配置 IPv6、ImmortalWrt、OPNsense 和 Cloudflare DDNS 时,连续遇到了地址变化、AAAA 记录选址、端口不通和 IPv4-only 客户端无法访问等问题。这篇笔记按我的实际排查顺序记录:先判断地址为什么变化,再确认哪个地址适合对外服务,最后处理 DDNS、路由、防火墙和协议兼容。
公网前缀、域名和部分拓扑信息已脱敏。示例统一使用
2001:db8::/32文档地址段,不可直接用于公网通信。
1. 我真正需要解决的不是“IPv6 会不会变”
最初看到同一张网卡上存在多个公网 IPv6,我把问题简单理解成了“一个固定、一个临时”。继续排查后发现,这种说法不够准确:完整 IPv6 由运营商前缀和主机接口标识符共同组成,任意一部分变化都会生成新地址;同一接口还可以同时保留稳定地址、临时地址、旧前缀地址和链路本地地址。
我遇到的现象主要有四类:
- 路由器重拨后,虚拟机的完整 IPv6 改变,但部分地址后缀看起来没变。
- 同一张网卡出现两个或更多全局 IPv6,不知道 DDNS 应该发布哪个。
- Cloudflare AAAA 已经指向当前地址,SSH、面板或 Minecraft 仍连接失败。
- ImmortalWrt 无法从邻居表获取 OPNsense 后方虚拟机的 IPv6 和 MAC。
排查后的核心结论可以压缩成下面几条:
| 结论 | 实际含义 |
|---|---|
| 家宽“固定 IPv6”通常不是完整地址永久不变 | 更多时候是 IID 稳定,或者依靠 DDNS 保持域名不变 |
| 临时地址主要用于出站连接 | 系统定期生成新地址,并逐步弃用、删除旧地址 |
| 服务器 AAAA 应指向稳定地址 | 地址必须为全局、首选、未过期,并排除 temporary |
| DDNS 只解决重新定位 | 它不会把运营商的动态前缀变成静态前缀 |
| DNS 正确不等于服务可达 | 后面仍有客户端路由、上级防火墙、本机防火墙和服务监听 |
2. 我先把一个 IPv6 地址拆开看
IPv6 地址长度为 128 位。全局单播地址通常包含运营商提供的 Global Routing Prefix、站点划分的 Subnet ID,以及主机生成的 Interface ID(IID)。在普通 SLAAC /64 链路中,可以先按“64 位前缀 + 64 位 IID”理解:
|<--------------- 128 bit --------------->|
| /64 前缀 | 64 位 IID |
|2001:db8:100:800 |abcd:1234:5678:9abc|
RFC 4862 描述了 SLAAC 的基本过程:路由器通过 RA 通告前缀和生命周期,主机自行生成 IID,再将两者组合成地址。一个接口同时拥有多个 IPv6 属于正常设计,并不表示地址冲突。RFC 4862 RFC 4291
家用宽带前缀通常由 DHCPv6-PD 动态委派。PPPoE 重拨、光猫或主路由重启、租约更新、运营商调整分配策略、下级路由重新申请子前缀,都可能让前缀变化。即使 IID 完全不变,完整地址也会改变:
变更前:2001:db8:100:800:be24:11ff:fe1c:782a
变更后:2001:db8:101:800:be24:11ff:fe1c:782a
└──── IID 未变化 ────┘
因此,我不再把“固定 IPv6”当成单一概念,而是区分以下四种情况:
| 口语中的“固定” | 实际含义 | 前缀变化后的结果 |
|---|---|---|
| 手工静态地址 | 管理员写死完整 128 位地址 | 旧前缀失效后通常不可达 |
| EUI-64 稳定后缀 | IID 由 MAC 推导 | 完整地址改变,IID 通常不变 |
RFC 7217 stable-privacy |
同一接口、同一子网和前缀内稳定的匿名 IID | 换前缀时 IID 也可能变化 |
| DDNS 稳定域名 | 持续更新 AAAA 记录 | 域名不变,记录转向新地址 |
3. 我如何区分稳定地址和临时地址
3.1 先用 EUI-64 验证一个确定案例
我检查过一张 MAC 为 bc:24:11:1c:78:2a 的虚拟网卡。Modified EUI-64 会在 MAC 中间插入 ff:fe,并将首字节的 U/L 位取反:
原始 MAC:bc:24:11:1c:78:2a
插入 ff:fe:bc:24:11:ff:fe:1c:78:2a
首字节取反:0xbc XOR 0x02 = 0xbe
最终 IID:be24:11ff:fe1c:782a
因此,be24:11ff:fe1c:782a 可以用计算证明来自这张网卡的 MAC。RFC 4291 附录 A 说明了插入 ff:fe 和翻转 U/L 位的过程。RFC 4291 Appendix A
EUI-64 的好处是后缀容易预测,前缀变化时 IID 仍可保持不变;缺点是会把稳定的链路层标识嵌入 IPv6。RFC 8064 已推荐使用 RFC 7217 生成稳定 SLAAC 地址,并反对继续默认嵌入稳定的链路层地址。RFC 8064
另一张网卡的 MAC 是 bc:24:11:c1:1d:f0,按 EUI-64 计算应得到:
be24:11ff:fec1:1df0
但我实际看到的 IID 是 47cc:5694:5d11:4789。两者没有 EUI-64 对应关系,所以我不能仅凭它“看起来随机”就断言它是固定地址或临时地址;它可能来自 RFC 7217、RFC 8981、NetworkManager、systemd-networkd,也可能是手工配置的 Token。
3.2 RFC 7217 和 RFC 8981 解决的是不同问题
RFC 7217 使用前缀、接口、网络标识、计数器和本机秘密值等信息生成不可直接反推出 MAC 的 IID。它的稳定范围是“同一接口、同一子网、同一前缀”,不是跨所有网络永远不变;运营商前缀变化后,IID 也可能重新生成。RFC 7217
RFC 8981 临时地址则使用不可预测 IID,并设置较短的首选与有效生命周期。旧地址会先从 preferred(首选)变成 deprecated(弃用),不再用于新连接;valid_lft(有效生命周期)到期后才会被删除。RFC 8981 给出的默认上限为 TEMP_PREFERRED_LIFETIME=1 day 和 TEMP_VALID_LIFETIME=2 days,实际值还受系统配置和 RA 前缀生命周期限制。RFC 8981
RFC 6724 的默认源地址选择规则会在适用时优先使用临时地址,所以服务器访问外部网站时,对方看到的出口 IPv6 可能与服务器 AAAA 记录不同。RFC 6724
3.3 我最终只相信标志、生命周期和生成配置
下面这种输出说明稳定地址和临时地址同时存在:
inet6 2001:db8:100:800:be24:11ff:fe1c:782a/64 scope global dynamic mngtmpaddr
inet6 2001:db8:100:800:47cc:5694:5d11:4789/64 scope global temporary dynamic
inet6 fe80::be24:11ff:fe1c:782a/64 scope link
| 标志 | 我在排查时的理解 |
|---|---|
temporary |
该地址是临时隐私地址 |
mngtmpaddr |
该地址可作为生成临时地址的模板,不表示它本身是临时地址 |
dynamic |
地址由动态机制管理,不等于马上会改变 |
deprecated |
不再用于新连接,但尚未完全失效 |
tentative |
DAD 重复地址检测尚未完成 |
valid_lft |
地址还可以存在多久 |
preferred_lft |
地址还能作为新连接首选源地址多久 |
我在 Linux 上集中使用下面几组命令,不再按地址外观猜测:
# 查看网卡上的全局地址、标志和生命周期
ip -6 addr show dev ens18 scope global
# 查看访问外部 IPv6 时,内核准备选择的源地址
ip -6 route get 2606:4700:4700::1111
# 查看临时地址策略
sysctl net.ipv6.conf.ens18.use_tempaddr
# 查看 IID 生成模式
cat /proc/sys/net/ipv6/conf/ens18/addr_gen_mode
# 连续监控地址新增、弃用和删除
ip monitor address dev ens18
use_tempaddr(临时地址策略)中,<= 0 表示禁用,1 表示启用但偏向稳定地址,> 1 表示启用并优先选择临时地址。Linux 还提供 temp_prefered_lft(临时地址首选时长)和 temp_valid_lft(临时地址有效时长)。addr_gen_mode(地址生成模式)的常见值如下:Linux IPv6 sysctl
| 值 | 生成方式 |
|---|---|
0 |
使用 EUI-64 |
1 |
不自动生成链路本地地址;SLAAC 地址仍使用 EUI-64 |
2 |
使用已配置的 stable_secret 生成 RFC 7217 地址 |
3 |
未设置秘密值时自动生成随机秘密值,再生成稳定隐私地址 |
4. 我在服务器上采用的地址配置
排查时我曾看到 dhcp6-privacy: false 这种写法,但 Netplan 官方配置中没有 dhcp6-privacy。与这个问题直接相关的字段是:
| 参数 | 中文解释 |
|---|---|
ipv6-privacy |
IPv6 隐私扩展:控制是否生成并优先使用临时地址 |
ipv6-address-generation |
IPv6 地址生成:可选 eui64 或 stable-privacy |
ipv6-address-token |
IPv6 地址 Token:手工指定 SLAAC 使用的固定 IID |
accept-ra |
接受路由通告:接收默认路由、前缀和生命周期 |
dhcp6 |
启用 DHCPv6:是否真正获取地址仍取决于 RA 和网络侧策略 |
字段定义以 Netplan YAML 官方文档 为准。
我的 DDNS 客户端直接运行在目标虚拟机中,不需要依赖“前缀 + 固定后缀”拼接,因此优先使用 stable-privacy:
network:
version: 2
renderer: networkd
ethernets:
ens18:
dhcp4: true
dhcp6: true
accept-ra: true
ipv6-privacy: false
ipv6-address-generation: stable-privacy
renderer: networkd(网络配置后端:由 systemd-networkd 应用配置);dhcp6: true(启用 DHCPv6:不代表一定由 DHCPv6 分配地址);accept-ra: true(接受 RA:获取默认路由、前缀和生命周期);ipv6-privacy: false(关闭临时地址:减少服务器和 DDNS 选址歧义);ipv6-address-generation: stable-privacy(稳定隐私地址:不直接暴露 MAC)。
服务器并非必须关闭临时地址。也可以保留临时地址用于出站,同时让 DDNS 明确排除 temporary。我关闭它只是为了减少服务器上的地址歧义,不是获得稳定服务地址的唯一方法。
如果确实需要 IID 在前缀变化后仍保持固定,我会改用管理员指定的 Token,而不是暴露 MAC:
network:
version: 2
renderer: networkd
ethernets:
ens18:
dhcp4: true
dhcp6: true
accept-ra: true
ipv6-privacy: false
ipv6-address-token: "::8a6f:2d91:4c30:7b10"
ipv6-address-token 与 ipv6-address-generation 互斥,不能同时配置;Token 还要确保同一 /64 内不重复。应用配置前,我会保留 PVE Console 等带外入口,再执行:
sudo netplan generate
sudo netplan try
sudo netplan apply
ip -6 addr show dev ens18
netplan generate(生成配置:检查语法和后端渲染);netplan try(试运行:连接中断时可回滚);netplan apply(正式应用:立即生效)。远程修改网络时直接跳过 netplan try,一旦 SSH 中断会增加恢复成本。
5. 我最终如何处理 DDNS
我给公开服务选择 AAAA 地址时,只接受同时满足以下条件的地址:scope global、仍处于 preferred、没有 temporary、属于实际业务网卡,并且上级路由和防火墙允许外部访问。fe80::/10 链路本地地址、ULA、Docker/Tunnel 地址、旧前缀地址和 deprecated 地址都不应被发布。
我最终更倾向于把 DDNS 客户端部署在目标虚拟机内,因为它能直接读取完整地址、标志和生命周期,不需要猜测下游 /64,也不会受 VLAN、OPNsense 路由边界和邻居表可见范围影响。即使 RFC 7217 在前缀改变时同时生成新 IID,本机 DDNS 仍能上报新的完整地址。Cloudflare 侧则使用仅具备指定 Zone DNS 编辑权限的 API Token,不在多台业务机上复用 Global API Key。
集中式获取并非完全不可用,但必须先确认网络边界:
ip -6 neigh show
ip -6 route get 2001:db8:100:800::1234
ip -6 neigh show 只能显示本机直连链路上的 NDP 邻居。虚拟机位于 OPNsense 后方、其他 VLAN 或其他三层网段时,ImmortalWrt 通常只能看到下一跳路由器,而不是目标虚拟机 MAC。邻居状态 STALE 只表示近期没有重新确认可达性,不表示 IPv6 已失效,所以不能把所有 STALE 项直接排除。
“提取前缀 + 拼接固定后缀”只有在控制器和目标机使用同一个 /64、目标 IID 明确跨前缀稳定、提取的是目标链路前缀且前缀长度确定为 /64 时才成立。运营商如果下发 /56 或 /60,主路由会给不同 LAN/VLAN 划分不同 /64;拿上级接口前四组拼接下游 IID,可能得到一个语法正确但根本没有路由到目标机的地址。
ip -6 route get 的输出如果只有 dev br-lan,并且邻居表中能看到目标,通常表示它位于直连链路;如果输出包含 via <下一跳>,目标位于路由器后方,不能依赖本机邻居表获取其 MAC。
6. AAAA 正确但服务不通时,我按层排查
我把公网访问拆成五层:DNS 只负责把域名解析成地址;客户端必须有可用 IPv6 路由;主路由或 OPNsense 必须放行入站;虚拟机本机防火墙必须允许目标端口;应用最后还要真正监听 IPv6。前一层正常,不能证明后一层正常。
域名 AAAA
↓
客户端 IPv6 路由
↓
主路由/OPNsense 入站策略
↓
虚拟机本机防火墙
↓
应用 IPv6 监听
我通常一次执行下面几组命令:
# 1. DNS 是否指向目标机当前的稳定地址
dig AAAA host.example.com +short
# 2. 地址是否仍为首选、是否误用了临时或旧前缀地址
ip -6 addr show dev ens18 scope global
# 3. 服务是否监听 IPv6 和目标端口
ss -lntup
ss -lntup | grep ':22|:25565|:18091'
# 4. 先从本机回环测试服务
ssh -6 root@::1
curl -g -6 -v 'http://[::1]:18091/'
# 5. 从网卡抓取 SSH 入站报文
sudo tcpdump -ni ens18 'ip6 and tcp port 22'
监听结果中的 :::22 通常表示 SSH 正在 IPv6 通配地址上监听;0.0.0.0:22 只代表 IPv4,还要检查是否另有 :::22 或具体 IPv6 地址;完全没有目标端口则说明服务未启动、端口写错或绑定到了其他地址。本机回环都无法访问时,我会先修复应用监听,不继续折腾公网路由。
抓包可以快速划分责任范围:
| 抓包结果 | 我接下来检查的位置 |
|---|---|
| 完全没有 SYN | 客户端 IPv6、运营商链路、上级路由或防火墙 |
| 收到 SYN,立即返回 RST | 目标端口无监听,或防火墙主动拒绝 |
| 收到 SYN,没有响应 | 本机防火墙丢弃、策略路由或服务异常 |
| 完成三次握手后断开 | 应用协议、认证、TLS 或服务端配置 |
Ping 通也不能代替端口测试。ICMPv6 Echo、TCP 22、TCP 443 和游戏端口是不同流量;ITDog 能 Ping 通,只能说明部分 ICMPv6 和路由路径可用。反过来,我也不会为了“安全”关闭全部 ICMPv6,因为邻居发现、RA、错误通知和 Path MTU Discovery 都依赖 ICMPv6。RFC 4890 建议按消息类型和方向过滤,而不是照搬 IPv4 的粗暴禁用方式。RFC 4890
IPv6 地址后面携带端口时必须使用方括号,避免与地址内部的冒号混淆:
http://[2001:db8:100:800::10]:18091/
[2001:db8:100:800::10]:25565
7. IPv4-only 客户端和 Minecraft 的处理
只有 AAAA 记录不能让 IPv4-only 客户端访问 IPv6 服务器,必须增加一个同时具备 IPv4 和 IPv6 的中间层。我根据服务类型选择方案,而不是把所有需求都塞进 Cloudflare Tunnel:
| 方案 | 适用服务 | 主要限制 |
|---|---|---|
| Cloudflare 橙云代理 | HTTP/HTTPS 网站 | 只支持 Cloudflare 列出的 Web 端口 |
| Cloudflare Tunnel | Web、受控 SSH/RDP/TCP | 非 HTTP 通常需要客户端侧 cloudflared 或受控客户端 |
| 双栈 VPS + FRP/socat/gost | SSH、Minecraft、自定义 TCP/UDP | 需要 VPS 和端口转发配置 |
| Tailscale/ZeroTier/WARP 私网 | 个人管理、NAS、SSH | 双方需要客户端,不适合任意公众访问 |
| Cloudflare Spectrum | 公开 TCP/UDP、部分 Minecraft 场景 | 协议能力和可用范围受套餐限制 |
Cloudflare 普通代理默认只处理文档列出的 HTTP/HTTPS 端口,其他公开 TCP/UDP 通常需要 Spectrum;Tunnel 的非 HTTP 服务通常要求客户端安装 cloudflared,不能直接当作无需客户端的免费 Minecraft 公网中转。Cloudflare 支持端口 Tunnel 协议说明 Spectrum 套餐协议
Minecraft Java Edition 可以直接使用 IPv6。出现 Connection refused: no further information 时,我先检查服务端口和监听,不会直接判断为“不支持 IPv6”。server.properties 保持:
server-ip=
server-port=25565
server-ip(监听地址:留空通常表示监听可用接口,避免误绑某个 IPv4);server-port(游戏端口:默认 TCP 25565)。服务端通过 ss -lntp | grep ':25565' 确认监听,客户端按 [2001:db8:100:800::10]:25565 输入地址。朋友的网络没有 IPv6 时,灰云 AAAA 无法解决兼容问题,只能增加双栈 VPS 转发、使用合适套餐的 Spectrum,或让固定成员通过 Tailscale/ZeroTier 进入私网。
8. ImmortalWrt、OPNsense 和级联 IPv6 的边界
排查旁路由时,我先确认它到底是“单臂旁路由”还是“真正的下级路由器”。这两个叫法在日常交流中经常混用,但 IPv6 配置完全不同:
| 拓扑 | 特征 | 配置重点 |
|---|---|---|
| 单臂旁路由 | ImmortalWrt 与终端都在主路由 LAN,仅提供代理或 DNS | 通常继续由主路由发送 RA,避免出现两个默认 IPv6 网关 |
| 下级路由器 | 终端位于 ImmortalWrt 的另一 LAN/VLAN 后方 | 上级需要通过 PD 委派子前缀,或受限使用 RA/NDP Relay |
我优先采用原生路由:上级委派 /56、/60 等前缀,下级再为每个 LAN/VLAN 分配独立 /64。上级无法委派时,才评估 RA Relay 或 NDP Proxy;NAT66 只作为兼容性兜底。RFC 4291 的基本模型是一个子网前缀对应一条链路,不能把同一个 /64 随意放到多个被路由器隔开的二层网络,再期待普通邻居发现自动跨越路由边界。RFC 4291
9. 最终配置与排错清单
我现在对动态前缀家宽中的 PVE 服务器采用以下组合:
- 通过 RA/SLAAC 正常获取前缀,不在虚拟机里写死运营商当前下发的完整地址。
- 服务地址使用
stable-privacy;确有跨前缀固定 IID 需求时,再配置ipv6-address-token。 - DDNS 优先运行在目标虚拟机内,并排除
temporary、deprecated、fe80::/10、ULA 和容器虚拟网卡地址。 - Cloudflare AAAA 使用最小权限 API Token 更新,不复用 Global API Key。
- 防火墙只开放实际服务端口,同时保留 IPv6 正常运行所需的 ICMPv6 类型。
- 地址不通时依次检查 DNS、地址状态、路由、防火墙、监听和抓包,不用 Ping 代替端口测试。
- IPv4-only 访问单独设计双栈代理或隧道,不与 DDNS 混为一谈。
最后保留一张误区对照表,方便以后排查时快速确认方向:
| 常见判断 | 修正后的理解 |
|---|---|
没有 ff:fe 就是临时地址 |
RFC 7217 稳定地址同样没有固定外观,要看 temporary 和生命周期 |
| 关闭隐私扩展就能写死 IPv6 | 只能停止生成临时地址,运营商前缀仍可能变化 |
| RFC 7217 后缀永远不变 | 只保证同一接口、同一前缀/子网内稳定 |
| DDNS 解析正确就应该能连 | DNS 后面还有路由、防火墙和服务监听 |
| 邻居表能看到整个内网 MAC | NDP 只覆盖直连链路,跨路由只能看到下一跳 |
STALE 表示 IPv6 失效 |
它是邻居可达性状态,不是地址生命周期 |
| Tunnel 可以公开代理任意 TCP/UDP | 非 HTTP 服务存在客户端要求、协议和套餐限制 |
| IPv6 没有 NAT,所以不需要防火墙 | 公网可路由不等于允许入站,仍需有状态防火墙 |
10. 参考资料
- RFC 4291:IPv6 Addressing Architecture
- RFC 4862:IPv6 Stateless Address Autoconfiguration
- RFC 7217:Stable, Semantically Opaque Interface Identifiers
- RFC 8064:Recommendation on Stable IPv6 Interface Identifiers
- RFC 8981:Temporary Address Extensions for SLAAC
- RFC 6724:Default Address Selection for IPv6
- RFC 4890:ICMPv6 Firewall Filtering Recommendations
- Linux Kernel:IPv6 IP Sysctl
- Netplan YAML Configuration
- Cloudflare Network Ports
- Cloudflare Tunnel Protocols
- Cloudflare Spectrum Protocols per Plan