最近,我解决了 UniFi 网络下,客户端设备无法学习到 IPv6 网关地址的问题。在这篇文章中,我将记录问题的定位与解决过程。

背景

我家的无线路由器使用的是 UniFi Dream Machine(下文中简称 UDM)。去年搬家后,我又购入了 UniFi 的交换机无线 AP,实现了全屋 Wi-Fi 覆盖。

💡 Info
UniFi Dream Machine 是 Ubiquiti 推出的一款无线路由器。与普通无线路由器相比,UDM 集成了 UniFi 控制器,能够集中管理和控制 Ubiquiti 的无线 AP、交换机等设备,扩展网络的覆盖范围。

关于 UniFi Dream Machine 的具体使用体验,请参考我之前写的一系列文章:

目前,我使用的这款 UDM 已停止销售,被功能更加丰富、支持 Wi-Fi 6 的 UniFi Dream Router 取代:

但是,在搬家后,家中设备的 IPv6 就无法正常工作了。本来我以为这是运营商的问题,新家所在区域不支持 IPv6,就没有太在意。

几个月前,偶然间我发现 IPv6 恢复正常了。尝试重启光猫、PPPoE 重新拨号等操作,IPv6 仍能保持正常。但是重启 UDM 后,IPv6 又无法正常工作了......

回想了一下,在 IPv6 恢复正常之前,为了更换灯具,我切断过家中的总电源,导致所有设备断电重启。所以我又多次尝试切断总电源,并重新上电,IPv6 却又始终无法恢复。

由于家中的 IPv6 短暂地正常工作过,我开始怀疑运营商是支持 IPv6 的,而问题出在家中的网络设备上:上次断电后 ,正好因为设备启动顺序等特殊原因,导致 IPv6 短暂地恢复正常。于是,我决定开始详细分析并解决这个问题。

问题现象

为了定位这个问题,首先需要摸清网络设备的配置,以及问题更具体的现象。

检查 UDM 上的设置,发现互联网 WAN 侧的 IPv6 开关已打开:

UDM 上的互联网设置中,已经打开了 IPv6
UDM 的互联网设置中,已经打开了 IPv6

同时,UDM 也从运营商获取到了 IPv6 地址:

UDM WAN 侧已经获取到 IPv6 地址
UDM WAN 侧已经获取到 IPv6 地址

通过 SSH 登录 UDM,尝试通过 ping 工具测试,确认 IPv6 地址能 ping 通,说明在 UDM 上,IPv6 功能正常。初步排除运营商的问题:

root@UDM:~# ping6 apple.com
PING apple.com(apple.com (2620:149:af0::10)) 56 data bytes
64 bytes from apple.com (2620:149:af0::10): icmp_seq=1 ttl=55 time=163 ms
64 bytes from apple.com (2620:149:af0::10): icmp_seq=2 ttl=55 time=161 ms
^C
--- apple.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 160.862/162.032/163.203/1.170 ms

再检查内网 LAN 侧的配置,确认 IPv6 也是打开的:

UDM LAN 侧的 IPv6 已打开
UDM LAN 侧的 IPv6 已打开

在我的电脑上观察网络设置界面,发现 IPv6 地址只有一个 fd 开头的唯一本地地址,而没有运营商分配的全球单播地址。同时路由器地址(也就是网关地址)为空。

macOS 上仅获取到了 fd 开头的 IPv6 地址,且路由器地址为空
macOS 上仅获取到了 fd 开头的 IPv6 地址,且路由器地址为空

💡 Info
IPv6 地址有多种不同的类型,本文涉及如下三种类型:

  1. 链路本地地址:前缀为 fe80::/10,一般由 MAC 地址自动生成。用于在同一个二层网络内通信,来自此地址的报文不能被路由器转发;
  2. 唯一本地地址:前缀为 fc00::/7(本文中 fd 开头的地址,也在此范围内)。用于本地通信,路由器不能将来自此地址的报文转发到 Internet;
  3. 全球单播地址:类似我们常说的 IPv4 公网 IP,全球唯一,在 Internet 上可达。

延伸阅读:

我也尝试在 UDM 上,把 LAN 侧 IPv6 地址方式更改为 DHCPv6。修改后电脑能够获取到 IPv6 地址,但是仍然无法获取到网关地址。

根据上述现象,可以确认:运营商是支持 IPv6 的,UDM 已经从运营商获取到了 IPv6 地址,但无法正确向内网设备通知 IPv6 地址和网关地址。

怀疑点一:路由器没有发送 Router Advertisement?

首先,我怀疑客户端没有学到网关地址,是因为路由器没有发送 Router Advertisement 报文(下文中简称 RA)导致的。

💡 Info
Router Advertisement 路由器通告报文,是 ICMPv6 报文的一种,可用于路由器向客户端设备通知 IPv6 网关地址。(RFC 4861
同时,对于 SLAAC 这种 IPv6 地址分配方式,RA 还参与内网设备的 IPv6 地址分配。(RFC 4862

于是我打开 Wireshark,通过抓包观察是否有 RA 报文。一段时间后,Wireshark 中确实显示了 RA 报文,而且报文有三个不同的源 MAC 地址。这说明在网络中,有三台设备都在发送 RA。

根据 MAC 地址与设备的对应关系,我发现前两个 RA 分别来自 HomePodApple TV。两者都打开了家居中枢功能,做为 Thread 边界路由器发送 RA,符合 Thread 协议标准。所以可以忽略这两个报文。

来自 HomePod 和 Apple TV 的 RA 报文
来自 HomePod 和 Apple TV 的 RA 报文

💡 Info
Thread 是一种用于智能家居的通信协议,受到了 Apple、Google 等各大厂商的支持,能够将智能家居设备接入各个厂商的智能家居平台,并实现跨平台互通。

Thread 基于开放的 IPv6 协议。Thread Border Router(Thread 边界路由器),能够将低功耗的智能家居设备接入家庭网络(与国内常见的智能家居网关,例如小米中枢网关的作用类似):

关于 Thread 协议中的网络知识,这三篇文章也值得阅读:

继续分析第三个 RA,确认报文来自 UDM:

来自 UDM 的 RA 报文
来自 UDM 的 RA 报文

这说明路由器确实发送了 RA 报文。怀疑点一不成立。

怀疑点二:存在软件 bug,导致报文源地址错误?

接下来,就要分析客户端收到 Router Advertisement 报文,但没有学到网关地址的原因了。

进一步观察,发现来自路由器的 RA,与前两个报文有一些差异:来自 HomePod 和 Apple TV 的 RA,源 IPv6 地址是 fe80 开头的链路本地地址,而来自 UDM 的报文,源 IP 地址是运营商分配的全球单播地址。

我查了一下,RA 的源 IPv6 地址应该是链路本地地址。路由器发送的 RA 报文,地址不合法。

💡 Info
根据 RFC 4861 的 4.2 章节,Router Advertisement 报文的源 IPv6 地址必须是链路本地地址:

   Source Address
                 MUST be the link-local address assigned to the
                 interface from which this message is sent.

这时我怀疑是 UDM 存在软件 bug,导致发送了错误的报文。经过搜索,UDM 使用开源的 dnsmasq 发送 RA 报文。配置文件在 /run/dnsmasq.conf.d/ 目录下:

💡 Info
Dnsmasq 是一款开源软件,提供了 DNS 服务器、DHCP 服务器等小型网络需要的基础功能,也支持发送 Router Advertisement 报文。OpenWrt 等用于网络设备的开源操作系统,也使用了 dnsmasq。

对于开源软件最基本的功能,出 bug 的可能性不大。我更怀疑是 UniFi 自动生成的配置文件有问题。

进入配置目录 /run/dnsmasq.conf.d/,可以看到有两个公共配置文件 dns.confshared.conf,以及每个网络自己的配置文件。其中同一个网络的 IPv4 和 IPv6 配置拆分成了两个文件:

root@UDM:/run/dnsmasq.conf.d# ls
dhcp.dhcpServers-net_Default_br0_192-168-3-0-24.conf             dns.conf
dhcp.dhcpServers-net_Default_br0_192-168-3-0-24_IPV6.conf        shared.conf

查看 IPv6 网络对应的配置文件 dhcp.dhcpServers-net_Default_br0_192-168-3-0-24_IPV6.conf,发现其中有 enable-ra 字段,说明 RA 报文发送功能已开启。ra-param 字段为 RA 报文的参数,也没有发现问题:

#
# Generated automatically by ubios-udapi-server
#

# Configuration of DHCP Server 'net_Default_br0_192-168-3-0-24_IPV6'
no-dhcp-interface=lo
interface=br0

# 配置了 enable-ra,说明 RA 报文发送功能已开启
enable-ra

# ra-param 为 RA 报文的参数,也没有发现问题
ra-param=br0,high,600,1800
dhcp-range=set:net_Default_br0_192-168-3-0-24_IPV6,::2,::7d1,constructor:br0,slaac,64,86400
dhcp-option=tag:net_Default_br0_192-168-3-0-24_IPV6,option6:dns-server,[::]
exclude-from-lease=tag:net_Default_br0_192-168-3-0-24_IPV6,UBIOS6exclude_XXXXX

为了进一步分析,我临时修改了配置文件,增加 log-querieslog-facility,将 dnsmasq 的日志记录在 /var/log/dnsmasq.log 路径下。

#
# Generated automatically by ubios-udapi-server
#

# Configuration of DHCP Server 'net_Default_br0_192-168-3-0-24_IPV6'
no-dhcp-interface=lo
interface=br0
enable-ra
ra-param=br0,high,600,1800
dhcp-range=set:net_Default_br0_192-168-3-0-24_IPV6,::2,::7d1,constructor:br0,slaac,64,86400
dhcp-option=tag:net_Default_br0_192-168-3-0-24_IPV6,option6:dns-server,[::]
exclude-from-lease=tag:net_Default_br0_192-168-3-0-24_IPV6,UBIOS6exclude_XXXXX

# 临时增加 log-queries,将 dnsmasq 日志记录在 /var/log/dnsmasq.log 中
log-queries
log-facility=/var/log/dnsmasq.log

重启 dnsmasq,日志记录功能就会生效。分析日志,发现 RA 报文选择的接口 br0 正确,但使用的接口地址确实是全球单播地址,没有使用链路本地地址:

root@UDM:/run/dnsmasq.conf.d# cat /var/log/dnsmasq.log
Aug 25 23:12:28 dnsmasq[210688] started, version 2.86 cachesize 10008
Aug 25 23:12:28 dnsmasq[210688]: compile time options: IPv6 GNU-getopt DBus no-UBus no-i18n no-IDN DHCP DHCPv6 no-Lua TFTP conntrack ipset outh no-cryptohosh no-DNSSEC Loop-detect inotify dumpfile
Aug 25 23:12:28 dnsmasq-dhcp[210688]: DHCP, IP range 192.168.3.11 192.168.3.199, lease time 1d

# 此处的日志表示,在 br0 接口上发送 router advertisement
# 但是报文的源地址是 240e 开头的全球单播地址
Aug 25 23:12:20 dnsmasq-dhcp[210680]: router advertisement on br0
Aug 25 23:12:20 dnsmasq-dhcp[210680]: router advertisement on 240e:XXXX:XXXX:XXXX:XXXX::, constructed for br0

Aug 25 23:12:28 dnsmasq-dhcp[210680]: IPv6 router advertisement enabled.
Aug 25 23:12:28 dnsmasq[210688]: using nameserver 127.0.0.1#5053

上述分析也排除了怀疑点二:UDM 使用开源软件 dnsmasq,其基本功能出问题的可能性较小;而且 UDM 自动生成的 dnsmasq 配置文件也没有问题。

至于 dnsmasq 为什么选择使用全球单播地址发送 RA,就需要继续分析了。

怀疑点三:配置错误,导致地址冲突?

重新用 ifconfig 命令查看 br0 接口下的地址,发现接口下同时有全球单播地址和链路本地地址。但是,链路本地地址后面有 dadfailedtentative 标记:

root@UDM:/run/dnsmasq.conf.d# ip -6 addr show dev br0
15: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    inet6 240e:XXXX:XXXX:XXXX::1/64 scope global dynamic 
        valid_lft 2587630sec preferred_lft 600430sec
    # 链路本地地址后有 dadfailed, tentative 字样
    inet6 fe80::XXXX:XXXX:XXXX:XXXX/64 scope link dadfailed tentative 
        valid_lf forever preferred_lf forever

我怀疑问题与这两个标记有关,查阅资料后,发现 dadfailed 代表重复地址检测失败,说明网络中有两个或者多个设备的 IPv6 地址重复,或者存在环路。

💡 Info
IPv6 存在重复地址检测(Duplication Address Detection,简称 DAD)机制(RFC 4862 5.4)。在将单播 IPv6 地址分配给接口前,必须进行重复地址检测。
在 Linux 下,对于重复地址检测失败的接口,通过 ip -6 addr show 命令,能够看到 dadfailed 标记。

先检查一下家庭网络是否存在环路。梳理组网后,并没有发现环路。同时家中的交换机上都配置了 RSTP 协议,此协议也能避免环路。

💡 Info
生成树协议(Spanning Tree Protocol,STP)是用于二层网络的环路检测与消除协议,能够检测网络中的环路,并切断导致环路的链路,从而消除环路。
快速生成树协议(Rapid Spanning Tree Protocol,RSTP)在 STP 的基础上,优化了环路检测与消除的速度。

链路本地地址是根据 MAC 地址生成的,对于正规的设备,MAC 地址都是唯一的,链路本地地址也不会冲突。

所以我又把目光聚焦在家中的路由器、交换机、无线 AP 等网络设备上。与普通的手机、电脑等客户端设备相比,这些网络设备为了实现复杂的网络功能,通常会有多个接口,还会创建虚拟接口。需要检查不同的接口是否因为共用相同的 MAC 地址,导致链路本地地址也都相同。

先从 UniFi Dream Machine 下手,查看所有接口的链路本地地址,发现 switch0br0eth1@switch0switch0.1@switch0switch0.10@switch0 等接口的链路本地地址确实有重复。

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever

# 可以看到,switch0、br0、eth1@switch0、switch0.1@switch0、switch0.10@switch0 的链路本地地址相同,
# 都是 fe80::XXXX:XXXX:XXXX:1(考虑到隐私,此处未提供原始地址)
3: switch0: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::XXXX:XXXX:XXXX:1/64 scope link
       valid_lft forever preferred_lft forever
4: eth4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::XXXX:XXXX:XXXX:2/64 scope link
       valid_lft forever preferred_lft forever
11: ra0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::XXXX:XXXX:XXXX:3/64 scope link
       valid_lft forever preferred_lft forever
12: rai0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::XXXX:XXXX:XXXX:4/64 scope link
       valid_lft forever preferred_lft forever
14: ifb1: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 state UNKNOWN qlen 32
    inet6 fe80::XXXX:XXXX:XXXX:5/64 scope link
       valid_lft forever preferred_lft forever
15: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 240e:XXXX:XXXX:XXXX::1/64 scope global dynamic
       valid_lft 2587461sec preferred_lft 600261sec
    inet6 fe80::XXXX:XXXX:XXXX:1/64 scope link dadfailed tentative
       valid_lft forever preferred_lft forever
16: eth0@switch0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::7683:c2ff:fed7:8cc2/64 scope link
       valid_lft forever preferred_lft forever
17: eth1@switch0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::XXXX:XXXX:XXXX:1/64 scope link
       valid_lft forever preferred_lft forever
18: eth2@switch0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::XXXX:XXXX:XXXX:6/64 scope link
       valid_lft forever preferred_lft forever
19: eth3@switch0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::XXXX:XXXX:XXXX:7/64 scope link
       valid_lft forever preferred_lft forever
21: ra2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::XXXX:XXXX:XXXX:8/64 scope link
       valid_lft forever preferred_lft forever
22: ra3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::XXXX:XXXX:XXXX:9/64 scope link
       valid_lft forever preferred_lft forever
29: rai2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::XXXX:XXXX:XXXX:a/64 scope link
       valid_lft forever preferred_lft forever
41: switch0.1@switch0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::XXXX:XXXX:XXXX:1/64 scope link
       valid_lft forever preferred_lft forever
42: switch0.10@switch0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::XXXX:XXXX:XXXX:1/64 scope link
       valid_lft forever preferred_lft forever
43: br10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::XXXX:XXXX:XXXX:b/64 scope link
       valid_lft forever preferred_lft forever
44: honeypot0: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 state UNKNOWN qlen 1000
    inet6 fe80::XXXX:XXXX:XXXX:c/64 scope link
       valid_lft forever preferred_lft forever
48: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1492 state UNKNOWN qlen 1000
    inet6 240e:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX/64 scope global dynamic
       valid_lft 258690sec preferred_lft 172290sec
    inet6 fe80::XXXX:XXXX:XXXX:d peer fe80::XXXX:XXXX:XXXX:e/128 scope link
       valid_lft forever preferred_lft forever
49: dnsfilter: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 2001:XXXX:XXXX::1/64 scope global
       valid_lft forever preferred_lft forever

根据我的了解,不同接口的链路本地地址是可以相同的,只要这些接口不被桥接到同一个二层网络。接下来,用 brctl 命令分析这些接口的桥接情况:

💡 Info
Linux 网桥(Bridge)是一种虚拟的网络设备,类似交换机,可以将多个接口接入到同一个二层网络。
通过 brctl 命令,可以创建和维护网桥,或显示网桥的详细信息。

root@UDM:~# brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.7483c2d78cc7       no              ra0
                                                        ra2
                                                        ra3
                                                        rai0
                                                        rai2
                                                        rai3
                                                        switch0.1
br10            8000.7683c2b78cc8       no              rai3.10
                                                        switch0.10
dnsfilter       8000.fadf67cee296       no              dnsfilter-0

其中 br0 对应 UniFi 里的默认网络,br10 对应 VLAN ID 为 10 的网络。观察两个网桥中的接口,未发现地址相同的接口加入同一个网桥的情况。

根据上述分析,br0 接口的链路本地地址确实检测到了地址冲突,地址冲突可能就是导致 dnsmasq 发送错误 RA 报文的原因。但是,实际检查后,并没有发现导致地址冲突的组网和配置。所以此怀疑点也不成立。

最终确认:设备启动,初始化过程存在问题

尝试删除 br0 下的链路本地地址,然后重新添加:

root@UDM:~# ip -6 addr del fe80::XXXX:XXXX:XXXX:1/64 dev br0
root@UDM:~# ip -6 addr add fe80::XXXX:XXXX:XXXX:1/64 dev br0

重新添加后,发现 dadfailed 标记不再出现。即使等待几小时,也不会再出现 dadfailed。同时,客户端设备的 IPv6 都能恢复正常工作。

根据上述现象,可以怀疑设备只是短暂遇到过地址冲突,随后就恢复正常了。这样的地址冲突,大概率出现在设备刚刚启动,初始化的过程中。

由于问题可能出在设备启动的一小段时间里,不容易持续观察。所以最方便的方法,是先分析设备启动时的日志,观察是否存在有用的信息。

通过 dmesg 命令查看内核日志,在其中搜索 duplicate 等关键词,发现在内核启动的第 18 秒,br0 接口收到了 switch0 上的报文,而且报文的源 MAC 地址与 br0 的地址冲突。同时,日志中也看到了 IPv6 地址冲突:

💡 Info
dmesg 是 Linux 操作系统下的一个命令行工具,用于显示内核环形缓冲区中的日志。

  • dmesg(1) - Linux manual page
    在 Linux 中,网络设备驱动程序、网络协议栈等大多位于内核态,通过 dmesg 命令,能够找到其生成的部分日志。
[ 18.579056] br0: port 1(switch0) entered forwarding state
[ 18.579135] IPv6: ADDRCONF(NETDEV_CHANGE): br0: link becomes ready

# 检测到 MAC 地址冲突
[ 18.609106] br0: received packet on switch0 with own address as source address (addr:76:83:c2:XX:XX:XX, vlan:0)
[ 18.609132] br0: received packet on switch0 with own address as source address (addr:76:83:c2:XX:XX:XX, vlan:0)
[ 18.609140] br0: received packet on switch0 with own address as source address (addr:76:83:c2:XX:XX:XX, vlan:0)
[ 18.609151] br0: received packet on switch0 with own address as source address (addr:76:83:c2:XX:XX:XX, vlan:0)
[ 18.649079] br0: received packet on switch0 with own address as source address (addr:76:83:c2:XX:XX:XX, vlan:0)
[ 18.649103] br0: received packet on switch0 with own address as source address (addr:76:83:c2:XX:XX:XX, vlan:0)
[ 18.779062] br0: received packet on switch0 with own address as source address (addr:76:83:c2:XX:XX:XX, vlan:0)

# 检测到 IPv6 地址冲突
[ 18.779086] IPv6: br0: IPv6 duplicate address fe80::XXXX:XXXX:XXXX:1 used by 76:83:c2:XX:XX:XX detected!

[ 18.779095] br0: received packet on switch0 with own address as source address (addr:76:83:c2:XX:XX:XX, vlan:0)
[ 19.229076] br0: received packet on switch0 with own address as source address (addr:76:83:c2:XX:XX:XX, vlan:0)

这说明设备启动时检测到了 MAC 地址冲突和 IPv6 地址冲突,导致 br0 上的链路本地地址变为 dadfailed 状态。其中,br0 就是 UniFi 默认网络创建的网桥。那么 switch0 又是什么呢?

继续以 switch0 为关键词搜索 dmesg 日志,发现了这样一行:

[    0.867244] switch0: Atheros AR8337/Qualcomm QCA8337 rev. 2 switch registered on alpine_mdio_shared_8

搜索后可以确认,AR8337/QCA8337 是一款以太网交换芯片,那么 switch0 就是这款交换芯片对应的接口。

继续观察后面的日志,可以看到 switch0.1switch0.10 字样。由于我在 UDM 上创建了两个网络,一个是默认网络,另一个是 VLAN ID 为 10 的网络,可以猜测 switch0.1switch0.10 就是这两个网络就是交换机创建 VLAN 后,生成的子接口。根据上文中通过 brctl 收集到的信息,这两个子接口分别加入了 br0br10 两个网桥,与我的猜测对应。

# br0 的 switch0 端口分别进入 blocking、forwarding、disabled 状态
[ 18.579053] br0: port 1(switch0) entered blocking state
[ 18.579056] br0: port 1(switch0) entered forwarding state
[ 36.672678] br0: port 1(switch0) entered disabled state

# ra0 进入 forwarding 状态
[ 36.673052] br0: port 1(ra0) entered blocking state
[ 36.673055] br0: port 1(ra0) entered disabled state
[ 36.673126] device ra0 entered promiscuous mode
[ 36.673152] br0: port 1(ra0) entered blocking state
[ 36.673154] br0: port 1(ra0) entered forwarding state
..... # 其他端口 ra2、ra3、rai0、rai2、rai3 的状态变化与 ra0 相同,此处省略

# switch0.1 最终进入 forwarding 状态
[ 36.674390] br0: port 7(switch0.1) entered blocking state
[ 36.674392] br0: port 7(switch0.1) entered disabled state
[ 36.674452] device switch0.1 entered promiscuous mode
[ 36.674503] br0: port 7(switch0.1) entered blocking state
[ 36.674505] br0: port 7(switch0.1) entered forwarding state

# switch0.10 最终进入 forwarding 状态
[ 36.885656] br10: port 1(switch0.10) entered blocking state
[ 36.885661] br10: port 1(switch0.10) entered disabled state
[ 36.885776] device switch0.10 entered promiscuous mode
[ 36.885993] br10: port 2(rai3.10) entered blocking state
[ 36.885996] br10: port 2(rai3.10) entered disabled state
[ 36.886064] device rai3.10 entered promiscuous mode
[ 36.886182] br10: port 1(switch0.10) entered blocking state
[ 36.886185] br10: port 1(switch0.10) entered forwarding state

从上文中我们知道,switch0.1 加入了 br0 网桥,而 switch0 没有加入。那么为什么 br0 又收到了来自 switch0 的报文呢?进一步分析上面的日志,观察到在 br0 上,switch0 从 forwarding 状态最终进入 disabled 状态,而 switch0.1switch0.10 最终进入 forwarding 状态。

这基本可能说明,设备初始化时存在一定的时序问题。默认的 switch0 先加入 br0 网桥,后续用户自己的配置生效后,switch0 又从 br0 删除,然后 switch0.1 加入 br0

除了内核的 dmesg 日志,我也观察了 systemd-networkd 的日志,也能基本确认 switch0 是最先创建的,随后依次创建 switch0.1switch0.10

💡 Info
systemd-networkd 是 Linux 下的一个服务,用于自动配置网络设备。

root@UDM:~# sudo journalctl -u systemd-networkd | grep -E 'switch0|br0'

Aug 25 21:54:11 UDM systemd-networkd[380]: br0: Link UP
Aug 25 21:54:11 UDM systemd-networkd[380]: switch0: Link DOWN

# switch0 进入 UP 状态
Aug 25 21:54:11 UDM systemd-networkd[380]: switch0: Link UP
Aug 25 21:54:12 UDM systemd-networkd[380]: switch0: Gained carrier
Aug 25 21:54:12 UDM systemd-networkd[380]: br0: Gained carrier
Aug 25 21:54:23 UDM systemd-networkd[380]: switch0: Gained IPv6LL

# switch0.1 和 switch0.10 随后进入 UP 状态
Aug 25 21:54:30 UDM systemd-networkd[380]: switch0.1: Link UP
Aug 25 21:54:30 UDM systemd-networkd[380]: switch0.1: Gained carrier
Aug 25 21:54:30 UDM systemd-networkd[380]: switch0.10: Link UP
Aug 25 21:54:30 UDM systemd-networkd[380]: switch0.10: Gained carrier
Aug 25 21:54:31 UDM systemd-networkd[380]: switch0.1: Gained IPv6LL
Aug 25 21:54:31 UDM systemd-networkd[380]: switch0.10: Gained IPv6LL

最终我们可以确认,在设备启动时,交换芯片默认接口 switch0 先加入了默认网桥 br0。这个过程中,可能由于 MAC 地址冲突,或者网络短暂环路,导致 br0 接口检测到 IPv6 链路本地地址冲突,进入 dadfailed 状态。

这个 dadfailed 状态不会自动清除,导致 dnsmasq 发送 RA 报文时,获取不到接口上的链路本地地址,进而使用了全球单播地址。

解决(规避)方法

后续,我会尝试将问题反馈给 Ubiquiti,希望能够找到彻底解决此问题的方法。但是在官方正式解决之前,我们可以自己规避这个问题。

从问题现象可以看出,只有默认网络 br0 会出现这个问题,目前家中的大部分客户端设备都在 br0 下。所以,问题的规避方法,就是创建一个新的网络,家中的客户端设备都转移到这个新网络中,不再使用默认网络。

新创建一个名为 VID20_LAN_Main 的网络,VLAN ID 为 20。准备将默认网络中的客户端设备转移到此网络:

新建一个 VLAN ID 为 20 的网络
新建一个 VLAN ID 为 20 的网络

接下来配置交换机,对于路由器、交换机、无线 AP 之间的链路,「有标记 VLAN」中允许 VID20_LAN_Main (20)

配置交换机,允许 VLAN 20
配置交换机,允许 VLAN 20

交换机连接客户端设备的端口,「本机 VLAN/网络」设置为 VID20_LAN_Main (20)

连接客户端的端口,本机 VLAN 设置为 20
连接客户端的端口,本机 VLAN 设置为 20

设置无线网络,将 Wi-Fi 使用的网络改为 VID20_LAN_Main

Wi-Fi 设置为访问 VLAN 20
Wi-Fi 设置为访问 VLAN 20

对于有固定 IP 地址的客户端设备,还需要重新设置固定 IP 地址:

重新设置固定 IP
重新设置固定 IP

经过上述设置,客户端设备就能获取到 IPv6 地址了。经过测试,重启 UDM 后,IPv6 保持正常工作。问题解决。

补充信息

设备型号与软件版本:

考虑到个人隐私,部分 MAC 地址与 IP 地址进行了模糊处理;配置进行了一定程度的简化;日志信息中的时间日期进行了修改。如需获取更详细的组网信息,欢迎与我邮件交流。


题图来源:https://unsplash.com/photos/white-ethernet-switch-iSh9yqYSd08