8.5 Autoconfiguration:无状态的魔法
上一节我们讲完 ipv6_rcv() 的时候,其实心里悬着一块石头。
如果你跟过我之前的调试步骤,可能会发现一个反直觉的现象:还没等你配置地址,甚至还没等你敲 ip addr add,ip addr show 有时候就已经能显示出 IPv6 地址了——而且是一个长得吓人的 128 位地址。
谁给它配的?没跑 DHCP,也没人手敲,这地址哪儿来的?
这正是 IPv6 最具「黑科技」色彩的特性:Autoconfiguration(自动配置)。在这套机制里,主机可以像个魔法师一样,从空气里变出一个全球唯一的地址。不需要 DHCP 服务器,不需要人工干预,只要网线插上,它自己就能活。
但这套魔法背后有一套严丝合缝的仪式,我们分四步拆解它。
第一阶段:先在本地低调起步
系统启动时,IPv6 协议栈苏醒的第一件事,不是问路由器要地址,而是先给自己造一个「临时身份证」。
这就是链路本地地址。
生成逻辑非常直白:
- 取前缀
fe80::/10(通常写作fe80::/64)。 - 接上自己的接口 ID(通常是 EUI-64 格式的 MAC 地址)。
此时,这个地址在内核内部会被打上一个标记:IFA_F_TENTATIVE(试探性)。
这个标签至关重要。
在这个状态下,接口虽然有了地址,但它是「残疾」的——它不能收发普通流量,只能处理邻居发现协议的消息。
为什么?因为这就好比你走进房间大喊一声「我叫张三」,在你确信屋里没有另一个「张三」之前,你不敢真的以这个名字开展社交。这就是 DAD(Duplicate Address Detection,重复地址检测)的职责,我们在邻居发现那一章已经详细折腾过这个机制了。
只有当 DAD 通过,IFA_F_TENTATIVE 标志被移除,这个地址才能真正上岗。
⚠️ 踩坑预警 如果 DAD 失败(检测到地址冲突),自动配置过程会直接终止。内核不会给你分配第二个地址,也不会报错,只会静默失败。这时候你只能手动配置,或者换个 MAC 地址。 这在虚拟机克隆场景里特别容易出现——两台虚拟机 MAC 一样,IPv6 地址就撞了,结果谁也连不上,连 ping 都不通,看起来就像网卡坏了。
第二阶段:寻找指路人
一旦链路本地地址确立了(证明「我是唯一的」),如果这台机器不是路由器,它就会开始寻找出口。
在这个阶段,主机会有个明确的需求:我想知道外面的网络长什么样,有没有前缀给我用?
它不会干等,而是主动向链路上喊话。它在内核里会调用 ndisc_send_rs() —— 发送 Router Solicitation(路由器请求) 消息。
- 目标地址:
ff02::2(所有路由器的组播地址)。 - 协议类型:ICMPv6,Type 为
NDISC_ROUTER_SOLICITATION (133)。
这一步就像是主机在走廊里喊了一嗓子:「这儿有台机器需要上网,有没有路由器听到?」
第三阶段:路由器的布道
路由器听到了。作为回应,它会发送 Router Advertisement(路由器公告) 消息。
- 源地址:路由器的链路本地地址。
- 目标地址:
ff02::1(所有主机的组播地址)。 - 协议类型:ICMPv6,Type 为
NDISC_ROUTER_ADVERTISEMENT (134)。
在 Linux 环境里,这个「喊话」的角色通常由一个叫 radvd 的用户空间守护进程来扮演。
你可以在路由器上装一个 radvd,配置文件里写上你想广播的前缀(比如 2001:db8:abcd::/64)。radvd 会做两件事:
- 定期广播:每隔一段时间就往
ff02::1发 RA,告诉新来的机器「这是我的地盘,前缀是这个」。 - 响应请求:监听
ff02::2,一旦听到 RS 请求,立马回一个 RA。
这个 RA 消息是整个自动配置的核心。它手里攥着主机最想要的两样东西:
- Prefix Information(前缀信息):比如
2001:db8:abcd::/64。 - 各种标志位:告诉主机「你能用我给你的前缀自己配地址(无状态)」,还是「必须去找 DHCPv6 服务器(有状态)」。
第四阶段:地址合成与「隐私模式」
主机收到 RA 后,看到里面的前缀,心里就有底了。
它现在干的事就是简单的拼装:
IPv6 Address = Prefix (from RA) + Interface ID
- Prefix:由路由器指定(必须 64 位)。
- Interface ID:通常是自己的 MAC 地址(EUI-64)。
拼出来的就是一个全球单播地址。
但是,这里有个隐患。
如果你的 Interface ID 直接用 MAC 地址,那么你走到哪儿,你的 IPv6 地址的后 64 位都不变。这意味着 Google、广告商甚至隔壁老王,只要看你发出的 IPv6 包,就能追踪你的物理设备——你换个 Wi-Fi,前缀变了,但后 64 位还在,依然认出是你。
为了解决这个问题,Linux 引入了 Privacy Extensions(隐私扩展)(内核配置 CONFIG_IPV6_PRIVACY)。
开启这个功能后,内核在生成 IPv6 地址时,会在前缀后面加上一个随机生成的 Interface ID,而不是 MAC 地址。
这带来两个好处:
- 外界无法通过 IPv6 地址反推你的 MAC 地址。
- 这个临时地址会定期过期并生成新的,追踪难度极大。
回到我们的类比:
之前把自动配置比作「办身份证」。
普通模式:你的身份证号永远是
110101[你的MAC地址],不管你搬哪儿去,后半截都不变,谁都能查你的档案。隐私模式:你每到一个新地方,就给自己起个新名字,比如
110101[随机字符串]。在这个地方(这个前缀下)大家认识你,但等你下次再搬走,谁也不知道刚才那个「随机字符串」是你。
时间的艺术:Lifetime(生命周期)
自动配置出来的地址不是永久的,它带有严格的生命倒计时。在 RA 消息里,路由器会给每个前缀挂上两个时间参数:
-
Valid Lifetime(有效生命周期):
- 这是这个地址的「保质期」。
- 倒计时结束,这个地址会被内核直接删除,从接口上消失。
- 在过期之前,已经建立的 TCP 连接还可以继续用,但新连接不能再用这个地址了。
-
Preferred Lifetime(首选生命周期):
- 这比 Valid Lifetime 要短。
- 倒计时一结束,这个地址就进入「 Deprecated」状态。
- 这时候,系统不会再主动用这个地址发起通信(比如你想
ping6别人,系统不会选这个地址作为源地址),但别人发给这个地址的包,你还是会收(为了维持现有连接)。
在内核的 inet6_ifaddr 结构体(include/net/if_inet6.h)里,这两个参数对应 valid_lft 和 prefered_lft 字段。
这两个设计是为了支持 Renumbering(重编号)。
想象一下,你是全网管,想换掉整个公司的 IPv6 前缀(比如换了个 ISP)。你不用去每台机器上敲命令。
你只需要在 radvd 配置里:
- 加上新前缀,给它设置很长的 Lifetime。
- 把旧前缀的 Lifetime 设得很短(或者设为 0)。
主机们收到新的 RA 后,会自动:
- 让旧地址慢慢过期。
- 配上新地址。
- 后续的流量平滑切换到新地址上。
这就是 RFC 4192 描述的无缝重编号过程。
无状态 vs. 有状态(DHCPv6)
到目前为止,我们说的都是 Stateless Address Autoconfiguration(SLAAC,无状态自动配置)。它的核心特点是:路由器只管广播前缀,主机自己算地址。路由器不需要记谁用了哪个地址——它是「无状态」的。
但有时候,网络管理员(也就是你)想要更精细的控制。你想知道谁拿了哪个 IP,或者想强制下发 DNS 服务器地址(RA 虽然也能下 DNS,但不是所有场景都支持)。
这时候就需要 DHCPv6 登场了。
- Stateful(有状态):主机从 DHCPv6 服务器拿地址,服务器有个数据库记录了
MAC -> IP的映射。这就像 IPv4 的老路子。 - Stateless(无状态):主机还是自己算地址,但可以通过 DHCPv6 拿点别的配置信息(比如 DNS)。这叫「无状态 DHCPv6」。
内核如果收到 RA 里带有「Managed」标志(M flag),它就会明白:「这地方得找 DHCPv6 服务器拿地址」,然后才会去启动 DHCPv6 客户端。
小结一下
这一节我们走完了 IPv6 主机从「裸机」到「联网」的全过程:
- 自我主张:生成链路本地地址,并通过 DAD 确认唯一性。
- 寻找路标:发送 RS 请求路由器。
- 接收施舍:接收 RA,获取前缀。
- 组装身份:前缀 + Interface ID(可选隐私扩展)生成全球单播地址。
- 生老病死:随着 Preferred/Valid Lifetime 的推移,地址自然老化或更新。
现在,你的机器已经有一个合法的、可路由的 IPv6 地址了。
网线插好了,地址有了。
接下来,当一个真正的 IPv6 数据包从外面进来时,内核该怎么处理它?下一节,我们将深入 ipv6_rcv() 的内部,去看看接收路径上那些被我们略过的细节。