| 382 | } |
| 383 | |
| 384 | std::vector<GuestForward> RuntimeManager::EffectiveGuestForwards(const VmSpec& spec) const { |
| 385 | // Canonical guest-side endpoint that the LLM reverse proxy is |
| 386 | // exposed at. Hardcoded to match the console hint, the openclaw / |
| 387 | // hermes rootfs configs, and the manager-side launcher so guests |
| 388 | // can address the proxy at a fixed http://10.0.2.3/ regardless of |
| 389 | // which random host port the LlmProxyService grabbed. |
| 390 | constexpr uint32_t kLlmGuestIp = 0x0A000203; // 10.0.2.3 |
| 391 | constexpr uint16_t kLlmGuestPort = 80; |
| 392 | |
| 393 | std::vector<GuestForward> result; |
| 394 | result.reserve(spec.guest_forwards.size() + 1); |
| 395 | for (const auto& gf : spec.guest_forwards) { |
| 396 | // Drop any user-supplied guestfwd that collides on 10.0.2.3:80 |
| 397 | // so slirp does not reject the duplicate, and so a stale rule |
| 398 | // cannot accidentally divert LLM traffic away from the host |
| 399 | // proxy. |
| 400 | if (gf.guest_ip == kLlmGuestIp && gf.guest_port == kLlmGuestPort) continue; |
| 401 | result.push_back(gf); |
| 402 | } |
| 403 | |
| 404 | uint16_t llm_port = 0; |
| 405 | { |
| 406 | std::lock_guard<std::mutex> lock(callback_mutex_); |
| 407 | if (llm_proxy_port_provider_) llm_port = llm_proxy_port_provider_(); |
| 408 | } |
| 409 | if (llm_port != 0) { |
| 410 | GuestForward gf; |
| 411 | gf.guest_ip = kLlmGuestIp; |
| 412 | gf.guest_port = kLlmGuestPort; |
| 413 | gf.host_addr = "127.0.0.1"; |
| 414 | gf.host_port = llm_port; |
| 415 | result.push_back(std::move(gf)); |
| 416 | } |
| 417 | return result; |
| 418 | } |
| 419 | |
| 420 | bool RuntimeManager::StartVm(const std::string& vm_id, std::string* error) { |
| 421 | auto record = store_.Get(vm_id); |