From 6a8c5571413b1581a5609da4cd3a1e3483991f52 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 17 Aug 2026 01:45:35 -0700 Subject: [PATCH] refactor(vmm): name bridge prepare RPC explicitly --- dstack/vmm/src/app.rs | 11 +++++++---- dstack/vmm/src/netd.rs | 35 ++++++++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/dstack/vmm/src/app.rs b/dstack/vmm/src/app.rs index ec2164e6a..9336b8788 100644 --- a/dstack/vmm/src/app.rs +++ b/dstack/vmm/src/app.rs @@ -5,7 +5,7 @@ use crate::{ config::{Config, NetworkFilterMode, Networking, NetworkingMode, ProcessAnnotation, Protocol}, logrotate, - netd::{self, InterfaceIdentity, PrepareRequest, Request as NetdRequest}, + netd::{self, InterfaceIdentity, PrepareBridgeRequest, Request as NetdRequest}, }; use anyhow::{bail, Context, Result}; @@ -544,7 +544,7 @@ impl App { vm_id: vm.manifest.id.clone(), nic_index, }; - let request = PrepareRequest { + let request = PrepareBridgeRequest { identity: identity.clone(), bridge: network.bridge.clone(), mac: network::mac_address_for_vm_index( @@ -556,8 +556,11 @@ impl App { filter: self.config.cvm.network_filter.filter.clone(), parameters: self.config.cvm.network_filter.parameters.clone(), }; - if let Err(error) = - netd::request(&self.config.netd.socket, &NetdRequest::Prepare(request)).await + if let Err(error) = netd::request( + &self.config.netd.socket, + &NetdRequest::PrepareBridge(request), + ) + .await { // The client may have timed out while netd was still finishing // this Prepare. Remove the in-flight identity first; netd's diff --git a/dstack/vmm/src/netd.rs b/dstack/vmm/src/netd.rs index fd4decec1..e8ac8c820 100644 --- a/dstack/vmm/src/netd.rs +++ b/dstack/vmm/src/netd.rs @@ -46,7 +46,7 @@ pub struct InterfaceIdentity { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PrepareRequest { +pub struct PrepareBridgeRequest { #[serde(flatten)] pub identity: InterfaceIdentity, pub bridge: String, @@ -60,7 +60,7 @@ pub struct PrepareRequest { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "operation", rename_all = "snake_case")] pub enum Request { - Prepare(PrepareRequest), + PrepareBridge(PrepareBridgeRequest), Remove { #[serde(flatten)] identity: InterfaceIdentity, @@ -101,7 +101,7 @@ pub fn instance_id(configured: &str, run_path: &Path) -> String { pub async fn request(socket: &Path, request: &Request) -> Result { let operation = match request { - Request::Prepare(_) => "prepare", + Request::PrepareBridge(_) => "prepare_bridge", Request::Remove { .. } => "remove", Request::Check { .. } => "check", }; @@ -219,7 +219,7 @@ async fn read_request(stream: &mut UnixStream) -> Result { fn handle_request(libvirt_uri: &str, request: Request) -> Result { let _lock = OperationLock::acquire()?; match request { - Request::Prepare(request) => prepare_interface(libvirt_uri, &request), + Request::PrepareBridge(request) => prepare_bridge(libvirt_uri, &request), Request::Remove { identity } => { validate_identity(&identity)?; let tap = tap_name(&identity); @@ -266,8 +266,8 @@ impl Drop for OperationLock { } } -fn prepare_interface(libvirt_uri: &str, request: &PrepareRequest) -> Result { - validate_prepare(request)?; +fn prepare_bridge(libvirt_uri: &str, request: &PrepareBridgeRequest) -> Result { + validate_prepare_bridge(request)?; let tap = tap_name(&request.identity); // A failed VMM start may leave a deterministic resource behind. Replacing // it makes prepare idempotent without accepting a caller-selected TAP. @@ -324,7 +324,7 @@ fn delete_binding(uri: &str, tap: &str) -> Result<()> { bail!("virsh failed to delete binding {tap}: {}", error.trim()) } -fn binding_xml(request: &PrepareRequest, tap: &str) -> String { +fn binding_xml(request: &PrepareBridgeRequest, tap: &str) -> String { let owner_uuid = stable_uuid(&request.identity); let owner_name = format!( "dstack:{}:{}:{}", @@ -366,7 +366,7 @@ fn stable_uuid(identity: &InterfaceIdentity) -> Uuid { Uuid::from_bytes(bytes) } -fn validate_prepare(request: &PrepareRequest) -> Result<()> { +fn validate_prepare_bridge(request: &PrepareBridgeRequest) -> Result<()> { validate_identity(&request.identity)?; validate_name("bridge", &request.bridge, 15, "_.-")?; if !Path::new("/sys/class/net") @@ -564,7 +564,7 @@ mod tests { #[test] fn binding_xml_escapes_values() { - let request = PrepareRequest { + let request = PrepareBridgeRequest { identity: identity("instance<&", "vm", 0), bridge: "br0".into(), mac: "02:00:00:00:00:01".into(), @@ -598,6 +598,23 @@ mod tests { assert!(value.get("identity").is_none()); } + #[test] + fn bridge_prepare_protocol_is_named_explicitly() { + let request = Request::PrepareBridge(PrepareBridgeRequest { + identity: identity("instance", "vm", 0), + bridge: "br0".into(), + mac: "02:00:00:00:00:01".into(), + qemu_uid: 1000, + filter: "clean-traffic".into(), + parameters: BTreeMap::new(), + }); + let value = serde_json::to_value(request).unwrap(); + assert_eq!(value["operation"], "prepare_bridge"); + assert_eq!(value["instance_id"], "instance"); + assert_eq!(value["bridge"], "br0"); + assert!(value.get("identity").is_none()); + } + #[tokio::test] async fn disconnected_client_is_confined_to_one_connection() { let (mut server, client) = UnixStream::pair().unwrap();