Skip to content

fix(sandbox): set a fixed guest MAC so udev does not rewrite eth0 - #409

Open
dushulin wants to merge 1 commit into
kvcache-ai:mainfrom
dushulin:fix/fixed-guest-mac
Open

dushulin wants to merge 1 commit into
kvcache-ai:mainfrom
dushulin:fix/fixed-guest-mac

Conversation

@dushulin

Copy link
Copy Markdown

What

冷启动(start_fresh)时给 eth0 配置一个固定的、本地管理的单播 MAC 02:fc:00:00:00:05(Firecracker guest_mac),不再让 guest 内核随机生成 MAC。同时在 docs/src/internals/networking.md 的地址规划里补充说明。

Why

详见 #407,简述如下:

  • start_fresh 添加 eth0 时 guest_mac 为 None,Firecracker 不提供 VIRTIO_NET_F_MAC,guest 的 virtio_net 随机分配 MAC(addr_assign_type = NET_ADDR_RANDOM)。
  • 用户镜像经 pivot-init 交给 systemd(>= 242)后,systemd-udevd 按自带的 99-default.link(MACAddressPolicy=persistent)把随机 MAC 改写成 persistent MAC,且不发 gratuitous ARP。
  • slot netns 里 tap0 上 169.254.0.21 的 neighbour 表项仍指向启动时的 MAC,host 发往 guest 的帧被丢弃。proxy 的连接一直等到 5s connect timeout 后返回 502,直到表项老化、广播重新解析为止(复现中约 43s)。这段时间里沙箱已经创建成功,envd 也已经应答过请求。
  • 是否触发取决于 guest 在 udev 改写之后多快主动发出单播流量,所以表现为"按镜像聚集、看起来随机"的 502。

配置了 MAC 之后,guest 报告 NET_ADDR_PERM,udev 的 persistent 策略不会再改写它。

Related issue

Closes #407

Scope and non-goals

Design and behavior changes

  • 所有 slot 共用同一个 MAC,原因如下:
    • 每个 slot 都有独立的 netns 和 tap0,guest IP 169.254.0.21 本来就在所有 slot 间相同。这个 MAC 只出现在 slot 内部的 TAP 私有链路上。
    • host / proxy 通过每个 slot 唯一的 host_interaction_ip 经 veth-<idx> 访问 guest,解析的是 veth 对端的 MAC(每个 slot 不同),与 guest MAC 无关。
    • 此前从同一个快照 fork / resume 出来的多个沙箱,也本来就共用同一个 MAC。
  • 02 前缀表示 locally administered 的单播地址,不会和厂商 OUI 冲突。
  • guest 内网卡名不受影响:99-default.link 的 NamePolicy=keep ... 仍保留 eth0。

Compatibility and operations

  • Public API or generated protocol:无变化。
  • Configuration or defaults:无新增配置项。
  • Snapshot manifest, artifact layout, or storage format:格式不变。变更之后拍的快照,其 guest MAC 是这个固定值;已有快照照旧携带原来的 MAC。
  • Upgrade and rollback:只影响冷启动。已暂停的沙箱和已提交的快照按保存的设备状态恢复,与现在一致。回滚后冷启动恢复为随机 MAC,不需要任何迁移。
  • Host requirements, permissions, ports, or dependencies:无。

Validation

  • make fmt
  • make clippy
  • make test-unit(只跑了其中的 cargo test -p agentenv --lib,见下)
  • Relevant Rust integration tests
  • make -C services test (required when services/ changes)
  • Generated clients/server regenerated with the documented make target
  • Documentation updated
  • Benchmarks or performance comparison completed

Commands and results:

make fmt                       -> ok
make clippy                    -> ok (0 warnings)
cargo test -p agentenv --lib   -> 1014 passed; 2 failed; 4 ignored
                                  failed: privileges::tests::terminate_unclaimed_child_kills_and_reaps_process
                                          sandbox::envd::user::tests::passwd_append_survives_a_locked_concurrent_rewrite
                                  (两者在不含本改动的 main 8428a32 上同样失败,见下)

线上验证:在一个下游部署里,把一台节点换成包含此改动的构建做 canary,环境与 #407 相同(KVM,Firecracker 1.15.1-patch-v1,guest kernel vmlinux-6.1.175)。使用 #407 中的 Image A(Ubuntu 24.04,systemd 255,avahi-daemon),方法与 #407 一致:在 slot netns 里运行 ip -timestamp monitor neigh,每 250 ms 请求一次 /proxy/health。

修改前(#407 复现) 修改后
冷启动次数 1 17(1 次单个,加 2 轮各 8 个并发)
proxy 探测 4×204 之后 154×502(每次 5.00 s,约 43 s) 2160 次全部 204,最大 5 ms
tap0 neighbour 启动时 MAC → STALE → FAILED → 新 MAC 始终 02:fc:00:00:00:05
guest addr_assign_type 3(NET_ADDR_SET) 0(NET_ADDR_PERM)
serial Assigned random MAC 有 无

guest 中 udevadm info 仍显示 ID_NET_LINK_FILE=/usr/lib/systemd/network/99-default.link,即 udev 照常处理了这块网卡,只是不再改写 MAC。

8 个沙箱并发运行时,还做了串流检查:通过每个沙箱的 envd 往 guest 串口写入带自身 sandbox ID 的标记,再到 host 上对应 sandbox 的 serial 日志里核对。8/8 都只出现在自己的日志里;8 个 guest 的 boot_id 各不相同。

Skipped checks and reasons:

  • make test-unit 中需要 capability runner 的 --ignored 测试,以及 Rust 集成测试:本地没有运行(需要 root / KVM 环境)。改动只是一个网卡配置参数,已用上面的 canary 做了端到端验证。
  • 上面 2 个失败的单元测试与本改动无关:把 sandbox.rs 还原为 main 8428a32 的版本后,在同一台机器上单独重跑,两者以同样的方式失败(child did not start / timed out waiting for .../passwd.lock)。它们依赖子进程与 tempdir 上的时序,是本地开发环境的问题,以 CI 结果为准。
  • 没有改动 services/,也没有生成代码;不涉及性能。

Risks and reviewer notes

  • 现在所有冷启动的沙箱 MAC 都相同。如果有 guest 内软件拿 MAC 作为跨实例的唯一标识,会受影响;但原来的随机 MAC 每次启动都会变,本来也不能作为稳定标识,所以预期没有这类依赖。
  • 重点看 src/sandbox/firecracker/sandbox.rs 中 start_fresh 的 add_network_interface 调用。

Checklist

  • The PR contains one coherent change and no unrelated formatting or refactoring.
  • New behavior is covered by tests, or I explained why testing is impractical.(行为依赖 Firecracker + 带 systemd 的 guest,单元测试无法覆盖,用上面的 canary 验证)
  • Logs and examples contain no credentials, tokens, or private registry information.
  • I did not manually edit generated code without updating its source and regenerating it.

Cold boots attached eth0 without guest_mac, so virtio_net picked a random
MAC. Guest images running systemd >= 242 with udevd rewrite such a MAC via
99-default.link (MACAddressPolicy=persistent) without a gratuitous ARP, and
the host tap0 neighbour entry kept the boot MAC until it aged out; proxied
connects hit the 5s timeout and returned 502 for up to ~40s.

A configured MAC is exposed via VIRTIO_NET_F_MAC and reported as
NET_ADDR_PERM, which udev leaves alone. Restore paths load the NIC from
vm_state.bin and are unchanged.

Closes kvcache-ai#407

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ OpenCodeReview: Review complete: 0 finding(s) across 1 selected item(s).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(network): guest udev rewrites the random eth0 MAC on cold boot, stale host neighbour makes the proxy 502 for ~40s

1 participant