diff --git a/chv-guest/79-chv-cell.network b/chv-guest/79-chv-cell.network new file mode 100644 index 0000000..3b55520 --- /dev/null +++ b/chv-guest/79-chv-cell.network @@ -0,0 +1,12 @@ +# NOTE: must sort before /usr/lib/systemd/network/80-container-ve.network (systemd built-in container-veth config, matches ve-*), otherwise that file wins and networkd DHCPs the interface. +# chv-cell veth: managed entirely by chv-cell (static 10.200.0.1/24 + NAT). +# systemd-networkd must not touch it: it would DHCP/link-local the +# interface and flush the static address, breaking all cell networking +# (2026-11-07 live diagnosis: 10.200.0.1 vanished within seconds of +# the veth being created, cell gateway unreachable, FORWARD/NAT +# counters stayed 0 for both TCP and UDP). +[Match] +Name=ve-chv-cell + +[Link] +Unmanaged=yes diff --git a/chv-guest/build.sh b/chv-guest/build.sh index f1d3365..dd04991 100755 --- a/chv-guest/build.sh +++ b/chv-guest/build.sh @@ -306,6 +306,8 @@ VC_ARGS=(-a "$OUT/disk.raw" --copy-in "$SCRIPT_DIR/chv-execd.py:/usr/local/bin/" --copy-in "$SCRIPT_DIR/chv-syncd.py:/usr/local/bin/" --run-command 'mv /usr/local/bin/chv-daemon.py /usr/local/bin/chv-daemon && mv /usr/local/bin/chv-execd.py /usr/local/bin/chv-execd && mv /usr/local/bin/chv-syncd.py /usr/local/bin/chv-syncd && chmod +x /usr/local/bin/chv-daemon /usr/local/bin/chv-execd /usr/local/bin/chv-syncd' + --copy-in "$SCRIPT_DIR/79-chv-cell.network:/etc/systemd/network/" + --run-command 'test -f /etc/systemd/network/79-chv-cell.network && grep -q "^Unmanaged=yes" /etc/systemd/network/79-chv-cell.network && grep -q "^Name=ve-chv-cell" /etc/systemd/network/79-chv-cell.network' --run-command 'printf "runcmd:\n - systemctl restart ssh || service ssh restart || true\n - test -f /etc/chv/guest-env && chown chv:chv /etc/chv/guest-env || true\n - /usr/local/bin/chv-data-mount >>/var/log/chv-data-mount.log 2>&1 || true\n - /usr/local/bin/chv-sync boot >>/var/log/chv-sync.log 2>&1 || true\n" > /etc/cloud/cloud.cfg.d/99-chv-sync.cfg' --run-command 'git config --system user.name chv && git config --system user.email chv@localhost && git config --system credential.helper store && git config --system init.defaultBranch main' --run-command "$GT_INSTALL_CMD" diff --git a/chv-guest/buildinfo.py b/chv-guest/buildinfo.py index e2b7fb9..bbae164 100644 --- a/chv-guest/buildinfo.py +++ b/chv-guest/buildinfo.py @@ -43,6 +43,9 @@ BASE_CONTENTS = [ "chv-data-mount", # feat-chv-opt-landing(阶段 2):客机运行时三分服务。 "chv-daemon", "chv-execd", "chv-syncd", + # feat-chv-opt-landing(阶段 2):cell veth 的 networkd + # 不管配置(须排在 80-container-ve.network 之前)。 + "79-chv-cell.network", ] # 执行器默认内核 cmdline(config.py),构建产物须与它一起记录(B-12), diff --git a/chv-guest/check_lockfile.py b/chv-guest/check_lockfile.py index 8ec8f0d..c1005c9 100644 --- a/chv-guest/check_lockfile.py +++ b/chv-guest/check_lockfile.py @@ -38,6 +38,9 @@ DECLARED_SOURCES = { "chv-daemon": "repo_commit", "chv-execd": "repo_commit", "chv-syncd": "repo_commit", + # feat-chv-opt-landing(阶段 2):cell veth 的 networkd + # 不管配置,版本随 repo_commit。 + "79-chv-cell.network": "repo_commit", } diff --git a/chv-guest/chv-cell.py b/chv-guest/chv-cell.py index b4e805f..07f331c 100644 --- a/chv-guest/chv-cell.py +++ b/chv-guest/chv-cell.py @@ -543,6 +543,19 @@ def _wait_for_iface(iface, timeout=10.0): return False +def _iface_has_ip(iface, ip): + """接口上是否有所给 IPv4。配网后复核:2026-11-07 实测 + networkd(80-container-ve.network)会在接口建好后几秒内 + 刷掉静态 10.200.0.1、改 DHCP/链路本地,致单元网关不可达; + 镜像里有 79-chv-cell.network(Unmanaged)兜底,这里是 + 第二道防线——地址不见即大声失败,不跑无网单元。""" + proc = subprocess.run(["ip", "addr", "show", iface], + capture_output=True, text=True) + if proc.returncode != 0: + return False + return ("inet %s/" % ip) in proc.stdout + + def _run_cmds(cmds, what): for cmd in cmds: print("chv-cell: + %s" % " ".join(cmd), file=sys.stderr) @@ -806,6 +819,10 @@ def run_cell(command, network="veth"): "chv-cell: veth 接口 %s 未出现,拒绝回落共享网络" % HOST_IFACE) _run_cmds(cell_network_commands(cell_pid), "单元内配网") + if not _iface_has_ip(HOST_IFACE, HOST_IP): + raise RuntimeError( + "chv-cell: veth %s 上 %s 不见了(疑似 networkd " + "刷掉),拒绝跑无网单元" % (HOST_IFACE, HOST_IP)) # 放行单元 init 去 exec payload。 try: os.write(go_w, b"1") diff --git a/chv-guest/generate_lockfile.py b/chv-guest/generate_lockfile.py index 0d70611..8d26820 100644 --- a/chv-guest/generate_lockfile.py +++ b/chv-guest/generate_lockfile.py @@ -59,6 +59,9 @@ DECLARED_SOURCES = { "chv-daemon": "repo_commit", "chv-execd": "repo_commit", "chv-syncd": "repo_commit", + # feat-chv-opt-landing(阶段 2):cell veth 的 networkd + # 不管配置,版本随 repo_commit。 + "79-chv-cell.network": "repo_commit", } diff --git a/cloud/tests/test_chv_muse_p2.py b/cloud/tests/test_chv_muse_p2.py index c70a0f6..0f35576 100644 --- a/cloud/tests/test_chv_muse_p2.py +++ b/cloud/tests/test_chv_muse_p2.py @@ -322,6 +322,14 @@ class CellNetworkTest(unittest.TestCase): self.assertEqual(CELL.find_uplink_iface(run=fake_run), "tap0") + def test_iface_has_ip_uses_real_loopback(self): + # 真实接口断言(lo 一定有 127.0.0.1):校验 _iface_has_ip + # 的解析逻辑;不存在的接口/地址返回 False。 + self.assertTrue(CELL._iface_has_ip("lo", "127.0.0.1")) + self.assertFalse(CELL._iface_has_ip("lo", "10.200.0.1")) + self.assertFalse(CELL._iface_has_ip("no-such-iface-xyz", + "127.0.0.1")) + class BrokerTest(unittest.TestCase): def _fake_proc(self, root, mapping):