背景
最近在给 Kata Containers 适配 IPv6 网络。在 IPv4 的场景下,runtime 会把主机(host)路由直接镜像给容器(guest)。在我刚开始的实现中,保留了镜像这一操作,但观察到了更新路由失败的情况。在经过对比后,定位到添加失败的两条路由是: "FE80::/64" 和 "FF00::/8"。我慢慢发现了 IPv6 不仅是地址规则与 IPv4 不同,很多底层的协议也被重新设计了。
邻居发现协议
IPv6 与 IPv4 的区别之一是去掉了广播机制,而我们熟悉的 ARP 协议是使用 IPv4 的广播地址(255.255.255.255)实现的 [1]。那么 IPv6 是如何做到 IP 地址与 Mac 地址的转换?
答案已经写在标题上了:邻居发现协议(Neighbor Discovery Protocol, NDP)。
这样做的优点(我暂时还没参悟)[1]:
- 与链路层协议解耦,不需要为不同的链路层设计不同的协议。
- 安全,避免原来的 ARP 攻击。
- 组播不广播,提升了性能。
NDP 使用了两种 ICMPv6 报文,它们对地址解析有着非常重要的作用:
- 邻居请求报文(Neighbor Solicitation, NS):简单说就是主机 A 想要得到主机 B 链路层地址的时候发送的报文。
- 邻居通告报文(Neighbor Advertisement, NA):主机 B 回应主机 A 并携带链路层地址的报文。
我们用一个真实的小例子来解释 NDP 的运作方式,这里的图和部分描述参照了 [3],同时也补充了一些我自己的理解。
假设我们规定左边的主机名为 A,右边的为 B,现在的需求是 A 想知道 B 的 mac 地址。
此时在 A 的视角下,它只知道自己的 IP 地址和主机 B 的 IP 地址。按照上面的描述,主机 A 需要发送一个 NS 报文,源地址是 "3001::1/64",目标地址是 "FF02::1::FF00::2"。这时候故事就来了,这个目标地址是什么意思?
IPv6 组播地址的是 "FF00::/8",它的具体结构如下图所示 [4](这个图画的有点瑕疵:固定部分应该是 "FF")。所以我们再看 "FF02",它的意思是标志位(flags)是 0,范围(scope)是 2,表示本地链路。
再回看 "FF02::1::FF00::2" 这个地址,已经可以解释 "FF02" 了,还是没法解释后面的这些,我查阅了很多资料,都没有更详细的说明。这时候我就把 ChatGPT 请出来了,人家给了详尽且靠谱的回答:
Solicited-Node Multicast 地址的格式为:"FF02::1:FFXX:XXXX",其中 "XX:XXXX" 是 IPv6 目标地址的最后 24 位。这样可以确保只有目标节点接收到这个 NS 消息。
剩下的内容,包括 NS、NA 报文到底携带了哪些信息我已经不再感兴趣了,你可以自行查阅 [3] 中的内容。我们可以从中得出一个结论:Kata Containers 不应该镜像 "FF00::/8" 路由。
本地链路地址
IPv6 本地链路地址的介绍我查了很多资料,基本上都没有很详细的介绍,稍微详细的一点的介绍参见 [5]。
本地链路地址顾名思义,这个地址不会过路由器,即只会在本地链路传输时使用。这个地址是 IPv6 接口在启动时会自动为其分配一个本地链路地址,这个过程称为自动地址配置(Stateless Address Autoconfiguration, SLAAC)。
本地链路地址的结构如下所示,前十个比特位必须是 "1111111010",后面还有 54 个自由发挥的比特位,所以本地链路地址的空间是 "FE80::/16 ~ FEBE::/16",但是一般情况下使用 "FE80::/64",每一个链路内的接口对应唯一一个 64 位长度的 interface id。
由于这是接口自动配置,因此同样也不需要被 Kata Containers 镜像,同时也要注意接口更新的时候应该主动过滤这个链路本地地址。
References
- https://www.cnblogs.com/mysky007/p/11261559.html
- https://cshihong.github.io/2018/01/29/IPv6%E9%82%BB%E5%B1%85%E5%8F%91%E7%8E%B0%E5%8D%8F%E8%AE%AE/
- https://zhuanlan.zhihu.com/p/110407399
- https://blog.51cto.com/u_7658423/1337745
- http://cisco.num.edu.mn/CCNA_R&S1/course/module8/8.2.3.4/8.2.3.4.html