use std::path::{Path, PathBuf};
use boxlite_shared::errors::{BoxliteError, BoxliteResult};
use super::ffi;
use super::logging;
use super::stats::NetworkStats;
#[derive(Debug)]
pub struct GvproxyInstance {
id: i64,
socket_path: PathBuf,
}
impl GvproxyInstance {
pub(crate) fn new(config: super::config::GvproxyConfig) -> BoxliteResult<Self> {
logging::init_logging();
let socket_path = config.socket_path.clone();
let id = ffi::create_instance(&config)?;
tracing::info!(id, ?socket_path, "Created GvproxyInstance");
Ok(Self { id, socket_path })
}
pub fn socket_path(&self) -> &Path {
&self.socket_path
}
pub fn from_config(
spec: &super::super::NetworkBackendSpec,
) -> BoxliteResult<(Self, super::super::NetworkBackendEndpoint)> {
let secrets = spec.secrets.iter().map(Into::into).collect();
let control_socket_path = super::control_socket_path(&spec.socket_path);
let mut config = crate::net::gvproxy::config::GvproxyConfig::new(spec.socket_path.clone())
.with_control_socket_path(control_socket_path)
.with_allow_net(spec.allow_net.clone())
.with_secrets(secrets)
.with_rate_limit(spec.rate_limit);
if let (Some(cert), Some(key)) = (spec.ca_cert_pem.as_deref(), spec.ca_key_pem.as_deref()) {
config = config.with_ca(cert.to_string(), key.to_string());
}
let instance = Self::new(config)?;
let connection_type = if cfg!(target_os = "macos") {
super::super::ConnectionType::UnixDgram
} else {
super::super::ConnectionType::UnixStream
};
use crate::net::constants::GUEST_MAC;
let endpoint = super::super::NetworkBackendEndpoint::UnixSocket {
path: spec.socket_path.clone(),
connection_type,
mac_address: GUEST_MAC,
};
Ok((instance, endpoint))
}
pub fn get_stats(&self) -> BoxliteResult<NetworkStats> {
let json_str = ffi::get_stats_json(self.id)?;
tracing::debug!("Received stats JSON: {}", json_str);
NetworkStats::from_json_str(&json_str).map_err(|e| {
BoxliteError::Network(format!(
"Failed to parse stats JSON from gvproxy: {} (JSON: {})",
e, json_str
))
})
}
pub fn version() -> BoxliteResult<String> {
ffi::get_version()
}
pub fn id(&self) -> i64 {
self.id
}
}
impl Drop for GvproxyInstance {
fn drop(&mut self) {
tracing::debug!(id = self.id, "Dropping GvproxyInstance");
match ffi::destroy_instance(self.id) {
Ok(()) => tracing::debug!(id = self.id, "Successfully destroyed gvproxy instance"),
Err(e) => tracing::error!(
id = self.id,
error = %e,
"Failed to destroy gvproxy instance"
),
}
}
}
unsafe impl Send for GvproxyInstance {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[ignore] fn test_gvproxy_version() {
let version = GvproxyInstance::version().unwrap();
assert!(!version.is_empty());
assert!(version.contains("gvproxy-bridge"));
}
#[test]
#[ignore] fn test_gvproxy_create_destroy() {
let socket_path = PathBuf::from("/tmp/test-gvproxy-instance.sock");
let instance = GvproxyInstance::new(crate::net::gvproxy::config::GvproxyConfig::new(
socket_path.clone(),
))
.unwrap();
assert_eq!(instance.socket_path(), socket_path);
}
#[test]
#[ignore] fn test_multiple_instances() {
let path1 = PathBuf::from("/tmp/test-gvproxy-1.sock");
let path2 = PathBuf::from("/tmp/test-gvproxy-2.sock");
let instance1 = GvproxyInstance::new(crate::net::gvproxy::config::GvproxyConfig::new(
path1.clone(),
))
.unwrap();
let instance2 = GvproxyInstance::new(crate::net::gvproxy::config::GvproxyConfig::new(
path2.clone(),
))
.unwrap();
assert_ne!(instance1.id(), instance2.id());
assert_ne!(instance1.socket_path(), instance2.socket_path());
}
}