From 52c3ea7cd36c816e00a6057e91cd945afda177d0 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Fri, 5 Dec 2025 05:57:22 -0300 Subject: [PATCH] Switch over to a reverse connection scheme (bridge to a guest bus) --- README.md | 10 ++-- sidebus-agent/src/main.rs | 49 ++++++---------- sidebus-broker/src/main.rs | 70 ++++++++++------------- sidebus-broker/src/portal/file_chooser.rs | 1 + sidebus-broker/src/vsock.rs | 5 +- 5 files changed, 53 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 9b5aa48..c8ebf21 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,8 @@ A cross-domain smart D-Bus proxying system that makes (some) [XDG Desktop Portal - `sidebus-broker` host process: - to be launched alongside the VMM - - hosts D-Bus servers in-process, based on [busd](https://github.com/dbus2/busd): - - a "private" bus for VM-instance-specific daemons such as permission-store and document-portal - - a "VM" bus, the one actually exposed to the guest over vsock + - hosts a "private" bus for VM-instance-specific daemons such as permission-store and document-portal + - listens on vsock (or on a unix socket that muvm would proxy as vsock) and connects to the VM bus as a client when the agent connects - orchestrates the lifecycle of the aforementioned daemons + virtiofsd - (we are sharing the directory *provided by* the document-portal FUSE filesystem!) - provides portal front-end interfaces like `org.freedesktop.portal.FileChooser` on the VM bus @@ -24,11 +23,10 @@ A cross-domain smart D-Bus proxying system that makes (some) [XDG Desktop Portal - (not talking directly to impls: don't want to reimplement per-DE portal selection; also 1:1 mapping is nicer to code) - but with extra hooks like exposing files to the guest using our private (per-VM) document-portal! - `sidebus-agent` guest process: - - listens on a guest unix socket, proxies D-Bus messages to a vsock - - spawned on-demand by systemd via socket activation + - connects to the broker over vsock and splices the connection into the VM (session) bus + - can be spawned spawned on-demand by D-Bus - uses systemd credentials for config args like vsock port - (very convenient to pass via the VMM, e.g. qemu: `-smbios type=11,value=io.systemd.credential:sidebus.port=1337`) - - guest NixOS configuration exposed via the flake ## Development Notes diff --git a/sidebus-agent/src/main.rs b/sidebus-agent/src/main.rs index 984a59f..650cde3 100644 --- a/sidebus-agent/src/main.rs +++ b/sidebus-agent/src/main.rs @@ -1,5 +1,3 @@ -use eyre::OptionExt; -use tokio::net::UnixListener; use tracing::info; #[tokio::main] @@ -10,38 +8,23 @@ async fn main() -> eyre::Result<()> { let vsock_port = std::fs::read_to_string(creds_dir.join("sidebus.port"))? .trim() .parse::()?; + let vsock_addr = zbus::Address::from(zbus::address::Transport::Vsock( + zbus::address::transport::Vsock::new(2, vsock_port), + )); - let unix_listener = UnixListener::from_std( - listenfd::ListenFd::from_env() - .take_unix_listener(0)? - .ok_or_eyre("no unix listener provided")?, - )?; - info!("listening for unix clients"); + info!("connecting to session bus"); + let session_conn = zbus::connection::Builder::session()?.p2p().build().await?; + + let vsock_conn = zbus::connection::Builder::address(vsock_addr)? + .server(session_conn.server_guid()) + .unwrap() + .p2p() + .auth_mechanism(zbus::AuthMechanism::Anonymous) + .build() + .await + .unwrap(); + info!(guid = %vsock_conn.server_guid(), "connected to vsock bus"); + sidebus_common::raw::splice_conns(vsock_conn, session_conn).await; - while let Ok((unix_client, client_addr)) = unix_listener.accept().await { - info!(?client_addr, "new unix client"); - tokio::spawn(async move { - let vsock_addr = zbus::Address::from(zbus::address::Transport::Vsock( - zbus::address::transport::Vsock::new(2, vsock_port), - )); - let vsock_conn = zbus::connection::Builder::address(vsock_addr) - .unwrap() - .p2p() - .auth_mechanism(zbus::AuthMechanism::Anonymous) - .build() - .await - .unwrap(); - info!(guid = %vsock_conn.server_guid(), "connected to vsock bus"); - let client_conn = zbus::connection::Builder::unix_stream(unix_client) - .server(vsock_conn.server_guid()) - .unwrap() - .p2p() - .auth_mechanism(zbus::AuthMechanism::External) - .build() - .await - .unwrap(); - sidebus_common::raw::splice_conns(client_conn, vsock_conn).await; - }); - } Ok(()) } diff --git a/sidebus-broker/src/main.rs b/sidebus-broker/src/main.rs index 527e1d3..d0f46ab 100644 --- a/sidebus-broker/src/main.rs +++ b/sidebus-broker/src/main.rs @@ -5,7 +5,7 @@ mod vsock; use bus::SharedHostedBus; use clap::Parser; use futures::{TryFutureExt, stream::FuturesUnordered}; -use std::{path::PathBuf, sync::Arc}; +use std::{path::PathBuf, sync::Arc, time::Duration}; use tokio::{net::UnixListener, process::Command, sync::Mutex}; use tokio_stream::StreamExt as _; use tracing::{Instrument, debug, error, info_span}; @@ -71,7 +71,6 @@ async fn main() -> eyre::Result<()> { let cli = BrokerCli::parse(); - let (vm_bus, vm_bus_guid, _) = new_hosted_bus().await?; let (priv_bus, _, mut priv_lst) = new_hosted_bus().await?; let mut server_tasks = tokio::task::JoinSet::new(); @@ -88,12 +87,6 @@ async fn main() -> eyre::Result<()> { .clone() .run_unix_listener(priv_dbg_listener, zbus::AuthMechanism::External), ); - let vm_dbg_listener = UnixListener::bind(dir_path.join("vm.sock"))?; - server_tasks.spawn( - vm_bus - .clone() - .run_unix_listener(vm_dbg_listener, zbus::AuthMechanism::External), - ); // TODO: unlink sockets on exit } @@ -148,7 +141,6 @@ async fn main() -> eyre::Result<()> { .spawn()?, ); - let vm_bus_conn = vm_bus.lock().await.connect_channel(false).await?; let priv_bus_conn = priv_bus.lock().await.connect_channel(false).await?; let host_session_conn = zbus::connection::Builder::session()?.build().await?; let file_chooser_imp = portal::file_chooser::FileChooser::new( @@ -157,47 +149,49 @@ async fn main() -> eyre::Result<()> { cli.guest_mountpoint, ) .await?; - vm_bus_conn - .request_name("org.freedesktop.portal.Desktop") - .await?; - let true = vm_bus_conn - .object_server() - .at("/org/freedesktop/portal/desktop", file_chooser_imp) - .await? - else { - unreachable!("our own fresh bus can't have interfaces already provided"); - }; - // NOTE: Every individual D-Bus client inside of the VM is a new client on the VM bus listeners! + async fn on_vm_bus_connected( + vm_bus_conn: zbus::Connection, + file_chooser: portal::file_chooser::FileChooser, + ) -> Result<(), eyre::Report> { + vm_bus_conn + .request_name("org.freedesktop.portal.Desktop") + .await?; + if !vm_bus_conn + .object_server() + .at("/org/freedesktop/portal/desktop", file_chooser) + .await? + { + error!("org.freedesktop.portal.Desktop already provided"); + }; + // XXX: no method for "wait until the conn dies"? + loop { + tokio::time::sleep(Duration::from_millis(5000)).await; + } + } if let Some(path) = cli.unix_path { - // XXX: going through the channel just to strip fds let vm_unix_listener = UnixListener::bind(path)?; - server_tasks.spawn(enclose! { (vm_bus, vm_bus_guid) async move { + server_tasks.spawn(enclose!((file_chooser_imp) async move { while let Ok((socket, remote_addr)) = vm_unix_listener.accept().await { - let f = enclose! { (vm_bus, vm_bus_guid) async move { + let f = enclose!((file_chooser_imp) async move { let client_conn = zbus::connection::Builder::unix_stream(socket) - .server(&vm_bus_guid) - .unwrap() - .p2p() .auth_mechanism(zbus::AuthMechanism::Anonymous) .build() .await?; - let vmbus_conn = vm_bus.lock().await.connect_channel(true).await?; - sidebus_common::raw::splice_conns(client_conn, vmbus_conn).await; - Ok::<(), eyre::Report>(()) - } }; + on_vm_bus_connected(client_conn, file_chooser_imp).await + }); tokio::spawn( async { match f.await { - Ok(()) => debug!("done with client"), - Err(err) => error!(%err, "error dealing with client"), + Ok(()) => debug!("done with server"), + Err(err) => error!(%err, "error dealing with server"), } } .instrument(info_span!("serve", ?remote_addr)), ); } - } }); + })); } if let Some(port) = cli.vsock_port { @@ -206,13 +200,11 @@ async fn main() -> eyre::Result<()> { vsock::ListenerBuilder::new(vsock::VsockAddr::new(vsock::VMADDR_CID_HOST, port)) .with_label("VM Bus") .listen(move |client| { - enclose! { (vm_bus, vm_bus_guid) async move { + enclose!((file_chooser_imp) async move { // TODO: Not necessary to go through the channel, add vsock support to the Peer too? - let client_conn = client.build((&vm_bus_guid).into()).await?; - let vmbus_conn = vm_bus.lock().await.connect_channel(true).await?; - sidebus_common::raw::splice_conns(client_conn, vmbus_conn).await; - Ok(()) - } } + let client_conn = client.build().await?; + on_vm_bus_connected(client_conn, file_chooser_imp).await + }) }) .map_ok_or_else( |e| { diff --git a/sidebus-broker/src/portal/file_chooser.rs b/sidebus-broker/src/portal/file_chooser.rs index b3a0ff9..09662cc 100644 --- a/sidebus-broker/src/portal/file_chooser.rs +++ b/sidebus-broker/src/portal/file_chooser.rs @@ -8,6 +8,7 @@ use zbus::{Connection, ObjectServer, fdo::Result, zvariant}; use super::documents::DocumentsProxy; use super::request::{RESPONSE_SUCCESS, ReqHandler, ResultTransformer}; +#[derive(Clone)] pub struct FileChooser { host: FileChooserProxy<'static>, docs: DocumentsProxy<'static>, diff --git a/sidebus-broker/src/vsock.rs b/sidebus-broker/src/vsock.rs index f3524a0..162e1d6 100644 --- a/sidebus-broker/src/vsock.rs +++ b/sidebus-broker/src/vsock.rs @@ -13,11 +13,8 @@ impl ConnectionBuilder { &self.remote_addr } - pub async fn build<'a>(self, guid: zbus::Guid<'a>) -> eyre::Result { + pub async fn build<'a>(self) -> eyre::Result { zbus::connection::Builder::vsock_stream(self.socket) - .server(guid) - .unwrap() - .p2p() .auth_mechanism(zbus::AuthMechanism::Anonymous) .build() .await