bigywater.tech
网络协议 · 24

IPv6 地址:前缀、作用域、链路本地与缩写规则

IPv6 地址长什么样、怎么缩写、有哪些类型?为什么链路本地地址后面总要加“%en0”?邻居的地址是怎么生成的?这篇在本机上列出所有 IPv6 地址,枚举一个地址的所有写法,验证作用域规则,并向整条链路问一声“谁在”。

20936
2001:db8::1 的合法文本写法数(仅小写),RFC 5952 只规定其中一种
146
向 ff02::1 ping 3 次,应答的不同链路本地地址个数
80 / 172
邻居表里链路本地地址由 MAC 推出(EUI-64)的个数,本机自己的则不是

本机的地址

实测 · 本机执行输出
  有 IPv6 地址的接口 8 个,共 9 个地址
    lo0    回环 ::1 /128;链路本地 fe80::/10 /64 带作用域编号
    en0    链路本地 fe80::/10 /64 [secured] 带作用域编号
    awdl0  链路本地 fe80::/10 /64 带作用域编号
    llw0   链路本地 fe80::/10 /64 带作用域编号
    utun0  链路本地 fe80::/10 /64 带作用域编号
    utun1  链路本地 fe80::/10 /64 带作用域编号
    utun2  链路本地 fe80::/10 /64 带作用域编号
    utun3  链路本地 fe80::/10 /64 带作用域编号
  按类型统计:回环 ::1 1;链路本地 fe80::/10 8;全球单播 / 唯一本地地址 0 个——这台机器所在的网络没有给它分配可路由的 IPv6 地址,所以它没有公网 IPv6 连通性

每个接口至少有一个 fe80::/10 链路本地地址,lo0 另有 ::1。本机网络没有分配全球单播地址,所以后面的 IPv6 实验都在本地链路上做。

写法

实测 · 本机执行输出
    输入                                      → 压缩(RFC 5952)           完整展开
    2001:0db8:0000:0000:0000:ff00:0042:8329   → 2001:db8::ff00:42:8329  2001:0db8:0000:0000:0000:ff00:0042:8329
    2001:db8:0:0:1:0:0:1                      → 2001:db8::1:0:0:1       2001:0db8:0000:0000:0001:0000:0000:0001
    2001:db8:0:1:1:1:1:1                      → 2001:db8:0:1:1:1:1:1    2001:0db8:0000:0001:0001:0001:0001:0001
    0:0:0:0:0:0:0:1                           → ::1                     0000:0000:0000:0000:0000:0000:0000:0001
    0:0:0:0:0:0:0:0                           → ::                      0000:0000:0000:0000:0000:0000:0000:0000
    2001:db8::                                → 2001:db8::              2001:0db8:0000:0000:0000:0000:0000:0000
    FE80::0202:B3FF:FE1E:8329                 → fe80::202:b3ff:fe1e:8329fe80:0000:0000:0000:0202:b3ff:fe1e:8329
    ::ffff:192.0.2.128                        → ::ffff:192.0.2.128      0000:0000:0000:0000:0000:ffff:192.0.2.128
    64:ff9b::192.0.2.33                       → 64:ff9b::c000:221       0064:ff9b:0000:0000:0000:0000:c000:0221
  2001:db8::1 这一个地址,只算小写字母,按“每组可补前导 0 + 压缩任意一段连续的 0 组(或不压缩)”枚举出 20936 种写法,其中经 ipaddress 解析都等于 2001:db8::1 的有 20936 种;RFC 5952 规定输出时:去掉前导 0、小写、压缩最长的连续 0 组(等长取最左)、单个 0 组不压缩
  ::ffff:192.0.2.128 是 IPv4 映射地址,Python 里 .ipv4_mapped = 192.0.2.128;64:ff9b::/96 是 NAT64 的转换前缀

一个地址可以补前导 0 也可以压缩连续的 0 组,有两万多种写法;RFC 5952 规定输出时去前导 0、小写、压缩最长的 0 段(等长取最左)。

地址空间

实测 · 本机执行输出
    前缀               地址个数(2 的几次方)   占整个空间         用途
    ::/128             2^0                     2.939e-39       未指定地址
    ::1/128            2^0                     2.939e-39       回环
    ::ffff:0:0/96      2^32                    1.262e-29       IPv4 映射地址
    64:ff9b::/96       2^32                    1.262e-29       NAT64 转换前缀
    2000::/3           2^125                   1.250e-01       全球单播(目前分配的都在这里)
    2001:db8::/32      2^96                    2.328e-10       文档用
    fc00::/7           2^121                   7.812e-03       唯一本地(类似 IPv4 私有地址)
    fe80::/10          2^118                   9.766e-04       链路本地
    ff00::/8           2^120                   3.906e-03       组播
  全球单播 2000::/3 占整个 128 位空间的 1/8;IPv4 整个空间是 2^32,一个 /64 子网(2^64 个地址)就是整个 IPv4 空间的 4,294,967,296 倍
    样本地址                   global private linklocal multicast loopback unspecified
    ::                                -       是       -       -       -       是
    ::1                               -       是       -       -       是       -
    fe80::1                           -       是       是       -       -       -
    fd12:3456::1                      -       是       -       -       -       -
    2001:db8::1                       -       是       -       -       -       -
    2606:4700:4700::1111              是       -       -       -       -       -
    ff02::1                           是       -       -       是       -       -
    ::ffff:192.0.2.1                  -       是       -       -       -       -
    64:ff9b::192.0.2.1                是       -       -       -       -       -

2000::/3 是全球单播(占 1/8),fc00::/7 唯一本地,fe80::/10 链路本地,ff00::/8 组播。一个 /64 子网的地址数是整个 IPv4 空间的 43 亿倍。

作用域

实测 · 本机执行输出
  fe80::1%lo0 上收发:成功,收到 b'link-local-lo0';套接字地址是四元组 (地址, 端口, flowinfo, scope_id=1),scope_id 就是接口编号(lo0 的编号 1)
  往本机自己的链路本地地址(scope_id = 0(不带接口))发 UDP:OSError: No route to host
  往本机自己的链路本地地址(scope_id = en0 的编号)发 UDP:发送成功
  往邻居表里的一个邻居(链路本地地址,scope_id = 0)发 UDP:OSError: No route to host
  往邻居表里的一个邻居(链路本地地址,scope_id = en0 的编号)发 UDP:发送成功
  ping6 -c 3 -I en0 ff02::1:共收到 284 个应答,来自 146 个不同的链路本地地址;应答里的跳数限制取值 {64: 264, 255: 17, 63: 2, 254: 1};其中本机自己的地址也应答了
  ping6 -c 3 -I en0 ff02::2:共收到 51 个应答,来自 25 个不同的链路本地地址;应答里的跳数限制取值 {64: 49, 255: 2}
  en0 上加入的 IPv6 组播组 5 个,按作用域(ff0X 的 X):ff01:1;ff02:4(1 = 接口本地,2 = 链路本地)

链路本地地址在不同接口上可以重复,所以发送时必须带接口(scope_id);不带就是 No route to host。ff02::1 能一次问出整条链路上的几百个应答。

接口标识符

实测 · 本机执行输出
  本机:MAC 推出的 EUI-64 接口标识符与链路本地地址的后 64 位不同;地址标志里有 secured(= 稳定的不透明标识符,不是由 MAC 推出的)
  邻居表(ndp -an)里 en0 上有 MAC 的 172 个邻居:链路本地地址是 EUI-64(由 MAC 推出)的 80 个,不是的 92 个——地址只统计个数
  被请求节点组播地址 = ff02::1:ff + 地址的后 24 位 → 形如 ff02::1:ff__:____(本机一个);en0 上有没有加入:有
  en0 上加入的组播组里,被请求节点组(ff02::1:ffXX:XXXX 形式)有 1 个——每个地址对应一个,邻居发现用它代替广播

EUI-64 由 MAC 推出,已知邻居里约一半是这样,本机的 en0 是稳定的不透明标识符;被请求节点组播地址由地址的后 24 位决定。

分步演示

IPv6 地址
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。本机网络没有公网 IPv6,所有实验都在回环和本地链路上;邻居与应答者的地址一律不写出,只统计个数;ff02::1 / ff02::2 各 ping 3 次,应答数随当时在线的设备变化
    • 没有验证:全球单播地址的分配与前缀委派;临时地址(隐私扩展)的生成与轮换(本机没有全球地址);唯一本地地址 fd00::/8 的随机前缀算法;ff02::2 的应答者为什么有 25 个(不是路由器的设备也回应);跳数限制 63 / 254 的应答来自什么设备
    京ICP备2026067502号-1京公网安备11010502063487号