托管报告:路由通告、容器 IPv6、冷迁移

2026-10-30。里程碑 H3、H5、H6,以及测试中查出的网关转发问题。设计见 docs/hosting-h3-h5-h6-design.md。

改动范围内核、接口、重启

内核:只改了路由容器建立网络的几行(IPv6 地址、网关 fe80::1)。接口:模块 API 没有变。重启:内核有改动,发布时重启一次。

节点 API 新增:ct.export、ct.import;route.set 接受 IPv6;vrrp.set 增加 mac;ct.create 和 ct.set_addr 的 addr6 开始生效。

修正VRRP 主机不能真正做网关

路由和 NAT 模块只处理发给网卡自身 MAC 的帧,所以此前任何版本经虚拟地址都转发不了,只能 ping 通。原测试只 ping,没有发现。现在 VRRP 模块把发给虚拟 MAC 的帧交给后面的模块,转发出去的帧用虚拟 MAC 作源。这项修复已在 2026.10.30.7 发布。

同时加了:指定虚拟 MAC(<组>.mac),用于接管旧路由器时上游不用换绑;网卡不配自己的地址(ip nicN via 网关/掩码),这时上游只看到这个 MAC,备机完全静默。测试 vrrp_gw_test 用抓包确认了这两点。这种模式下同组路由器的优先级必须不同。

H3主机通告路由

新模块 hostroutes。主机每 5 秒、容器变化时立即,用带密钥的 UDP 包告诉路由器自己运行中的路由容器地址;路由器写成自己的路由,所有者是 learned。不用 BGP。

情况结果
另一台主机通告同一地址(容器迁移)路由立即改到新主机,写入日志
主机不再通告约 20 秒后撤掉
密钥错误、重放、来源不是下一跳、不在 allow 内不采纳
手工或面板设置的路由不覆盖

H5容器 IPv6

默认用 ULA 加网关上的 NAT66,路由容器也一样;公网 IPv6 是网关上的规则,换地址不用动容器。写全局地址时,网关不转换,直接路由。容器的 /128 地址配网关 fe80::1,也可以只有 IPv6。

测试 routed6_test 通过:全局地址从外部可达、出站保持自己的地址;ULA 容器经 NAT66 出站,对方看到的是我们的地址;路由模块自己的 IPv6 路由生效;只有 IPv6 的容器可达;链路本地地址被拒绝。

测试的限制:这个测试是一台虚拟机同时做主机和网关,没有跑两台分开的情况。通过路由把 ULA 送到另一台网关再做 NAT66,要到面板实际绑定时才会走到。节点 API 的 ct.create 仍要求 addr4,只有 IPv6 的容器现在只能用底层设置建立。

H6冷迁移

面板依次调用:源主机 ct.stop、ct.export,目标主机 ct.import。传输的是容器自己的上层目录,不写临时文件,源主机不需要额外空间。使用一次性密钥,ChaCha20 加密并校验整个数据流。源主机上的数据不会被删,面板确认后再 ct.delete。

node_test 迁移了一个真实的 Debian 容器:内容、属主、硬链接数、符号链接、稀疏文件、目录权限、文件时间、FIFO、被删除的镜像文件(whiteout)、SSH 主机密钥都保留,原来的密钥能登录;密钥错误时导入失败、不启动;源容器之后能再启动,删除副本不影响它。

测试的限制:这个测试在同一台虚拟机上导出、导入,没有跨两台机器。接收方失败后,发送方要等到发送超时(最长 120 秒)才结束,这期间源容器不能启动。

未解决已知问题

验证回归

完整回归 281 个单元测试通过,转发 TCP 19.0 Gbit/s,64 字节 372 kpps。完整回归里只有 cake 的整形下限失败过:机器繁忙时测试自己的发送方发不够量,单独运行正常;已放宽下限,它检查的上限没有变。