Pure Rust implementation of the libvirt client library. This project provides a native Rust client for communicating with libvirt daemons using the libvirt RPC protocol, without requiring the C libvirt library.
- Pure Rust: No C dependencies, fully native Rust implementation
- Auto-generated API: All 453+ libvirt RPC methods are automatically generated from
.xprotocol definition files - Multi-protocol Support: Supports remote, QEMU, and LXC protocols
- Async/Await: Built on Tokio for async I/O
- Type-safe: Strong typing with serde-based XDR serialization
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β .x Protocol Files (proto/remote_protocol.x) β
β β β
β βΌ (build.rs) β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β libvirt-codegen β β
β β β’ parser.rs: Parse .x files using nom β β
β β β’ generator.rs: Generate Rust code using quote β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ (OUT_DIR/generated.rs) β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Generated Code β β
β β β’ Structs, Enums, Unions, Typedefs β β
β β β’ Constants (REMOTE_PROGRAM, REMOTE_PROTOCOL_VERSION) β β
β β β’ LibvirtRpc trait + GeneratedClient<T> β β
β β β’ 453+ async RPC methods β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β libvirt crate β β
β β β’ Connection: Unix socket transport + LibvirtRpc impl β β
β β β’ Client: High-level API wrapper β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
libvirt-rs/
βββ Cargo.toml # Workspace configuration
βββ crates/
β βββ libvirt-xdr/ # XDR serialization (serde-based)
β β βββ src/
β β βββ ser.rs # XDR Serializer
β β βββ de.rs # XDR Deserializer
β β βββ opaque.rs # Fixed-length opaque (UUID) handling
β β
β βββ libvirt-codegen/ # Code generator
β β βββ src/
β β βββ parser.rs # .x file parser (nom)
β β βββ ast.rs # AST definitions
β β βββ generator.rs # Rust code generator (quote)
β β
β βββ libvirt/ # Main library (libvirt-pure on crates.io)
β βββ build.rs # Invokes codegen at build time
β βββ proto/ # Protocol definition files
β β βββ remote_protocol.x # Main remote protocol (453 methods)
β β βββ qemu_protocol.x # QEMU-specific protocol (7 methods)
β β βββ lxc_protocol.x # LXC-specific protocol (1 method)
β βββ examples/ # Usage examples
β β βββ domain_info.rs
β β βββ domain_lifecycle.rs
β βββ src/
β βββ lib.rs # Public API (Client, types)
β βββ connection.rs # RPC connection management
β βββ packet.rs # RPC packet encoding/decoding
β βββ transport/ # Transport implementations
The libvirt RPC protocol is defined in XDR (External Data Representation) format in .x files. This project automatically generates Rust code from these definitions at build time.
The proto/remote_protocol.x file contains:
/* Type definitions */
struct remote_nonnull_domain {
remote_nonnull_string name;
remote_uuid uuid;
int id;
};
/* Procedure arguments and returns */
struct remote_connect_list_all_domains_args {
int need_results;
unsigned int flags;
};
struct remote_connect_list_all_domains_ret {
remote_nonnull_domain domains<REMOTE_DOMAIN_LIST_MAX>;
unsigned int ret;
};
/* Procedure definitions */
enum remote_procedure {
REMOTE_PROC_CONNECT_OPEN = 1,
REMOTE_PROC_CONNECT_CLOSE = 2,
REMOTE_PROC_CONNECT_LIST_ALL_DOMAINS = 273,
/* ... 453+ procedures */
};The parser uses nom to parse .x files into an AST:
// Parses: struct remote_domain { ... }
fn parse_struct(input: &str) -> IResult<&str, TypeDef>
// Parses: enum remote_procedure { ... }
fn parse_enum(input: &str) -> IResult<&str, TypeDef>
// Parses: typedef opaque remote_uuid[16];
fn parse_typedef(input: &str) -> IResult<&str, TypeDef>The generator uses quote to produce Rust code:
Type Generation:
// Input: struct remote_nonnull_domain { name; uuid; id; }
// Output:
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NonnullDomain {
pub name: String,
pub uuid: FixedOpaque16,
pub id: i32,
}RPC Method Generation:
// Input: REMOTE_PROC_CONNECT_LIST_ALL_DOMAINS = 273
// args: remote_connect_list_all_domains_args
// ret: remote_connect_list_all_domains_ret
// Output:
pub async fn connect_list_all_domains(
&self,
args: ConnectListAllDomainsArgs
) -> Result<ConnectListAllDomainsRet, RpcError> {
let payload = libvirt_xdr::to_bytes(&args)?;
let response = self.inner.rpc_call(
Procedure::ProcConnectListAllDomains as u32,
payload
).await?;
libvirt_xdr::from_bytes(&response)
}fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
// Parse .x protocol file
let protocol = libvirt_codegen::parse_file("proto/remote_protocol.x")
.expect("failed to parse protocol");
// Generate Rust code
let code = libvirt_codegen::generate(&protocol);
// Write to OUT_DIR
let dest = Path::new(&out_dir).join("generated.rs");
fs::write(&dest, code).unwrap();
println!("cargo:rerun-if-changed=proto/remote_protocol.x");
}#[allow(dead_code)]
pub mod generated {
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
}
pub use generated::*;| XDR Type | Rust Type | Notes |
|---|---|---|
int |
i32 |
4 bytes, big-endian |
unsigned int |
u32 |
4 bytes, big-endian |
hyper |
i64 |
8 bytes, big-endian |
unsigned hyper |
u64 |
8 bytes, big-endian |
bool |
bool |
4 bytes (0 or 1) |
string<N> |
String |
Length prefix + data + padding |
opaque<N> |
Vec<u8> |
Variable length with prefix |
opaque[N] |
FixedOpaque16 |
Fixed length, no prefix (for N=16) |
T<N> |
Vec<T> |
Variable length array |
T[N] |
[T; N] |
Fixed length array |
T * |
Option<T> |
Optional (discriminant + value) |
struct |
struct |
Fields in order |
enum |
enum |
#[repr(i32)] |
union |
enum |
Tagged union |
use libvirt_pure::{Client, ConnectListAllDomainsArgs};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to libvirt daemon
let client = Client::connect("qemu:///system").await?;
// Use auto-generated API
let version = client.rpc().connect_get_version().await?;
println!("Hypervisor version: {}", version.hv_ver);
// List all domains
let args = ConnectListAllDomainsArgs {
need_results: 1,
flags: 0,
};
let ret = client.rpc().connect_list_all_domains(args).await?;
for dom in &ret.domains {
println!("Domain: {} ({})", dom.name, dom.uuid);
}
client.close().await?;
Ok(())
}use libvirt_pure::{Client, DomainLookupByNameArgs, DomainSuspendArgs};
async fn suspend_vm(client: &Client, name: &str) -> Result<(), Box<dyn std::error::Error>> {
// Lookup domain by name
let args = DomainLookupByNameArgs { name: name.to_string() };
let ret = client.rpc().domain_lookup_by_name(args).await?;
// Suspend the domain
let args = DomainSuspendArgs { dom: ret.dom };
client.rpc().domain_suspend(args).await?;
Ok(())
}# Build the library
cargo build
# Run examples
cargo run --example domain_info
cargo run --example domain_lifecycle -- list
cargo run --example domain_lifecycle -- suspend <vm-name>To update the generated code when the libvirt protocol changes:
-
Download the latest
.xfiles from libvirt source:curl -o crates/libvirt/proto/remote_protocol.x \ https://raw.githubusercontent.com/libvirt/libvirt/master/src/remote/remote_protocol.x
-
Rebuild the project:
cargo build
The code generator will automatically parse the new protocol and regenerate all types and methods.
- go-libvirt - Reference implementation in Go
- libvirt RPC Protocol - Official protocol documentation
- XDR RFC 4506 - XDR specification
MIT License