本文属于《写给 Geek 们的 IPv6 组网最佳实践》系列,旨在为需要小范围 IPv6 组网(如家庭组网)的爱好者们提供最佳实践方案和常见问题解答,部分概念性内容为方便理解可能会过度简化,部分方案可能不适用企业宽带接入、大规模组网等场景。首发于 V2EX ,现重发到本人的个人网站《云行者》,转载请注明出处。

问题描述

IPv6 下 LAN 侧每一台机器都会获得独立的公网 IPv6 地址。

目前国内绝大多数地区的家庭带宽是通过 PPPoE 虚拟拨号接入的,下发的 IPv6 前缀会在每次重新拨号后发生变化。这就需要我们重新给 LAN 侧的设备分配 IPv6 地址,以及处理前缀变化造成的种种问题,如服务发现及防火墙配置。

前缀前化导致的断网问题

举例说明

假设光猫运行在桥接模式,路由器拨号获得 IPv6 PD 前缀 2001:db8:beef:1200::/56。路由器下联一个 LAN ,指定 PD 前缀的 SLA ID 为1(也就是从2001:db8:beef:1200::/56 挑选第 1 个(从 0 开始数)/64子网),路由器会将2001:db8:beef:1201::/64 这个子前缀划为给 LAN 。正确配置的情况下所有 LAN 下的设备都将获得2001:db8:beef:1201:xxxx:xxxx:xxxx:xxxx/64 格式的地址。

此时路由器上的 PPPoE 断开后重拨,ISP 下发新的 PD 前缀 2001:db8:dead:ab00::/56。此时路由器应该向 LAN 下的所有设备宣告原前缀2001:db8:beef:1201::/64失效,并重新下发 2001:db8:dead:ab01::/64 前缀,否则 LAN 下的设备可能会因为继续使用老前缀而导网络中断。

解决方法

  • 最佳实践:关闭 DHCPv6 服务,改用 SLAAC 无状态分配。DHCPv6 分配的地址存在一个租期,除非租期快到了,用户设备不会重新请求一个新的地址。而 SLAAC 可以由服务器主动通知用户设备前缀失效。如果无特殊理由,可以禁用 DHCPv6 。此外,现在无状态分配也可以通过 RDNSS 选项配置 DNS ,现代操作系统都支持。DHCPv6 已经没有必要使用了,尤其是 Android 不支持 DHCPv6 。
  • 次佳方案:将 DHCPv6 租期调短,比如 5 分钟。这样如果前缀变更,最长 5 分钟老前缀就会失效。

已经使用纯无状态分配了,但重新拨号后老前缀没有失效?

前面说了,SLAAC 可以由服务器主动通知用户设备前缀失效。但这只是协议支持。如果重新拨号后老前缀没有失效或者新前缀没有下发,说明你的路由器固件存在问题。在条件允许的情况下,换一个其他的的固件吧。

  • 近些年的新版 OpenWRT 是没有问题的。
  • 如果使用 VyOS 1.4 ,可以设置set service router-advert interface eth0 prefix ::/64 deprecate-prefix,其中 eth0 是 LAN 口网卡名。
  • 如果是在 Linux 上跑 radvd ,修改 radvd.conf,端号配置中加上 DeprecatePrefix on;,并配置脚本使 pppoe 重拨后重启 radvd 。

前缀变化后,所有 LAN 侧的 IPv6 地址都会变化。如何设定 DNS 以及避免服务中断?

如果你的服务仅仅在 LAN 侧使用,或者多个服务内部通信不涉及外网,你可以给你的 LAN 侧分配一个内网地址( ULA 地址)。IPv6 完美支持一张网卡多个 IPv6 地址,所以你的设备可以同时拥有固定的内网地址以及动态的公网地址。关于 ULA 地址,见我的另一篇文章:为你的 IPv6 局域网配置 ULA 吧

如果你的服务需要从外网访问,建议设置 DDNS (动态 DNS )。DDNS 软件可以运行在提供服务的主机上,也可以由单台设备一次为所有服务更新 DNS 记录,不过后者可能需要你保证这些服务的 IPv6 后缀不会变化。关于如何设定固定的 IPv6 后缀,下面会进行讨论。

防火墙设定问题

如果你想在网关路由器上为每台终端设备配置防火墙规则,但又无从下手(因为终端设备没有固定的 IPv6 地址),可以使用下面的方法。

首先确保这台终端设备的 IPv6 后缀是固定的,比如不管前缀如何变化,这台设备的后缀总是::1234 。 如果网关的防火墙基于 Linux nftables/ip6tables ,可以用负掩码匹配设备的后缀。即可以用 ::1234/::ffff:ffff:ffff:ffff 来匹配后 64 位地址( OpenWRT 支持::1234/-64 这种写法)。

如何给我的设备设定一个固定后缀?

首先需要明确的是,在使用 SLAAC 无状态地址分配时,路由器只宣告子网的前缀,而 64 位的后缀完全由终端设备自己决定。

目前大多数新的操作系统下,一个前缀会生成两个 IPv6 地址:永久地址和临时地址。

临时地址的后缀完全是随机生成的,用于发起访问,每隔一段时间会自动变化,在一定程度保护你的隐私(防追踪)。Linux 下使用ip a命令或者 mac 系统下 ifconfig 命令,如果一个地址后面有 temporary 标记,则表明这是个临时地址。iOS 系统下,第二个无状态地址是临时地址。中文 Windows 系统下 ipconfig 命令会直接标明哪个地址是临时地址。

永久地址的后缀是由特定的哈希算法生成的。这个哈希由网卡的 mac 地址和一个随机数共同决定。只要你的前缀不变并且 mac 地址不变,后缀就不会变。因此适合对外提供服务。但是如果前缀变了,则后缀也会跟着变化。(当你配置了 ULA 时,因为 ULA 的前缀是不变的,所以这个 ULA 地址是静态的)。iOS 系统连接 Wi-Fi 时,默认会使用随机 mac 地址,因此后缀会经常变化,这个功能可以在 Wi-Fi 设置中关闭。

部分老系统、老的网络管理软件的永久地址仅仅是 mac 地址的变形,这种生成方式叫做 Modified EUI-64 。判断方法为,如果永久地址的后 64 位形如:xxxx:xxff:fexx:xxxx ,即中间 16 位是 ff:fe ,则该地址是使用 Modified EUI-64 生成的。在这种配置下,只要 mac 地址不变,后缀就不会变,因此可以视作固定后缀。这种生成方式会对外暴露你的 mac 地址,看你介意不介意了。

SLAAC 下的后缀可以手动指定。

比如 Linux 下可以用 ip token set ::1234 dev eth0指定后缀固定为 1234 (其中 eth0 是网卡名)。这个命令是运行时命令,无法在系统重启后保持,实际使用时需要配置到网络管理软件(如 Network Manager, systemd-networkd, ifupdown 等)中,例如:

Network Manager

nmcli c mod eth0 ipv6.token ::1234
nmcli c mod eth0 ipv6.addr-gen-mode stable-privacy

####ifupdown

iface eth0 inet6 static
        autoconf 1
        accept_ra 2
        privext 1
        post-up /sbin/ip token set ::11 dev $IFACE

systemd-networkd

[Match]
Name=eth0

[Network]
IPv6AcceptRA=true

[IPv6AcceptRA]
Token=static:::1234

补充

  1. systemd-networkd .network 配置文件中,可以在[Network] 下设置 IPv6PrivacyExtensions=true 来启用临时地址生成。
  2. 此外还可以像 IPv4 一样给 IPv6 开启 NAT ,在 LAN 侧只使用内网地址,不过这对大多数人可能不是个好方法。

本文到此结束,如果各位有各种系统下的具体兼容情况和配置命令,可以在评论中补充,我会定期更新到附言中或者放到新帖中。