跳到主要内容

8.5 Autoconfiguration:无状态的魔法

上一节我们讲完 ipv6_rcv() 的时候,其实心里悬着一块石头。

如果你跟过我之前的调试步骤,可能会发现一个反直觉的现象:还没等你配置地址,甚至还没等你敲 ip addr addip addr show 有时候就已经能显示出 IPv6 地址了——而且是一个长得吓人的 128 位地址。

谁给它配的?没跑 DHCP,也没人手敲,这地址哪儿来的?

这正是 IPv6 最具「黑科技」色彩的特性:Autoconfiguration(自动配置)。在这套机制里,主机可以像个魔法师一样,从空气里变出一个全球唯一的地址。不需要 DHCP 服务器,不需要人工干预,只要网线插上,它自己就能活。

但这套魔法背后有一套严丝合缝的仪式,我们分四步拆解它。


第一阶段:先在本地低调起步

系统启动时,IPv6 协议栈苏醒的第一件事,不是问路由器要地址,而是先给自己造一个「临时身份证」。

这就是链路本地地址

生成逻辑非常直白:

  1. 取前缀 fe80::/10(通常写作 fe80::/64)。
  2. 接上自己的接口 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 会做两件事:

  1. 定期广播:每隔一段时间就往 ff02::1 发 RA,告诉新来的机器「这是我的地盘,前缀是这个」。
  2. 响应请求:监听 ff02::2,一旦听到 RS 请求,立马回一个 RA。

这个 RA 消息是整个自动配置的核心。它手里攥着主机最想要的两样东西:

  1. Prefix Information(前缀信息):比如 2001:db8:abcd::/64
  2. 各种标志位:告诉主机「你能用我给你的前缀自己配地址(无状态)」,还是「必须去找 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 地址。

这带来两个好处:

  1. 外界无法通过 IPv6 地址反推你的 MAC 地址。
  2. 这个临时地址会定期过期并生成新的,追踪难度极大。

回到我们的类比:

之前把自动配置比作「办身份证」。

普通模式:你的身份证号永远是 110101[你的MAC地址],不管你搬哪儿去,后半截都不变,谁都能查你的档案。

隐私模式:你每到一个新地方,就给自己起个新名字,比如 110101[随机字符串]。在这个地方(这个前缀下)大家认识你,但等你下次再搬走,谁也不知道刚才那个「随机字符串」是你。


时间的艺术:Lifetime(生命周期)

自动配置出来的地址不是永久的,它带有严格的生命倒计时。在 RA 消息里,路由器会给每个前缀挂上两个时间参数:

  1. Valid Lifetime(有效生命周期)

    • 这是这个地址的「保质期」。
    • 倒计时结束,这个地址会被内核直接删除,从接口上消失。
    • 在过期之前,已经建立的 TCP 连接还可以继续用,但新连接不能再用这个地址了。
  2. Preferred Lifetime(首选生命周期)

    • 这比 Valid Lifetime 要短。
    • 倒计时一结束,这个地址就进入「 Deprecated」状态。
    • 这时候,系统不会再主动用这个地址发起通信(比如你想 ping6 别人,系统不会选这个地址作为源地址),但别人发给这个地址的包,你还是会收(为了维持现有连接)。

在内核的 inet6_ifaddr 结构体(include/net/if_inet6.h)里,这两个参数对应 valid_lftprefered_lft 字段。

这两个设计是为了支持 Renumbering(重编号)

想象一下,你是全网管,想换掉整个公司的 IPv6 前缀(比如换了个 ISP)。你不用去每台机器上敲命令。 你只需要在 radvd 配置里:

  1. 加上新前缀,给它设置很长的 Lifetime。
  2. 旧前缀的 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 主机从「裸机」到「联网」的全过程:

  1. 自我主张:生成链路本地地址,并通过 DAD 确认唯一性。
  2. 寻找路标:发送 RS 请求路由器。
  3. 接收施舍:接收 RA,获取前缀。
  4. 组装身份:前缀 + Interface ID(可选隐私扩展)生成全球单播地址。
  5. 生老病死:随着 Preferred/Valid Lifetime 的推移,地址自然老化或更新。

现在,你的机器已经有一个合法的、可路由的 IPv6 地址了。

网线插好了,地址有了。 接下来,当一个真正的 IPv6 数据包从外面进来时,内核该怎么处理它?下一节,我们将深入 ipv6_rcv() 的内部,去看看接收路径上那些被我们略过的细节。