use std::time::Duration;
use arcbox_helper::client::{Client, ClientError};
use crate::bridge_discovery;
const CONTAINER_SUBNET: &str = "172.16.0.0/12";
const MAX_ROUTE_ATTEMPTS: u32 = 5;
const ROUTE_RETRY_INTERVAL: Duration = Duration::from_secs(2);
#[derive(Debug, thiserror::Error)]
pub enum RouteError {
#[error("bridge not found in kernel FDB")]
BridgeNotReady,
#[error("helper unavailable: {0}")]
HelperUnavailable(String),
#[error("route command failed: {0}")]
RouteFailed(String),
}
impl From<ClientError> for RouteError {
fn from(e: ClientError) -> Self {
match e {
ClientError::Connection(_) | ClientError::Rpc(_) => {
Self::HelperUnavailable(e.to_string())
}
ClientError::Helper(msg) => Self::RouteFailed(msg),
}
}
}
async fn ensure_route(bridge_mac: &str) -> Result<(), RouteError> {
let mac = bridge_mac.to_string();
let bridge = tokio::task::spawn_blocking(move || bridge_discovery::resolve_bridge_by_mac(&mac))
.await
.unwrap_or(None)
.ok_or(RouteError::BridgeNotReady)?;
let client = Client::connect().await?;
client.route_add(CONTAINER_SUBNET, &bridge.name).await?;
tracing::info!(
subnet = CONTAINER_SUBNET,
bridge = %bridge.name,
%bridge_mac,
"route ensured"
);
Ok(())
}
pub async fn ensure_route_with_retry(bridge_mac: &str) -> Result<(), RouteError> {
for attempt in 1..=MAX_ROUTE_ATTEMPTS {
match ensure_route(bridge_mac).await {
Ok(()) => return Ok(()),
Err(ref e) if attempt < MAX_ROUTE_ATTEMPTS => {
tracing::debug!(
attempt,
max_attempts = MAX_ROUTE_ATTEMPTS,
error = %e,
"route install failed, retrying"
);
tokio::time::sleep(ROUTE_RETRY_INTERVAL).await;
}
Err(e) => {
tracing::warn!(
attempt,
error = %e,
"route install failed after all attempts"
);
return Err(e);
}
}
}
unreachable!()
}
#[cfg(all(feature = "vmnet", target_os = "macos"))]
pub async fn ensure_route_for_bridge(bridge_name: &str) -> Result<(), RouteError> {
for attempt in 1..=2 {
match async {
let client = Client::connect().await?;
client.route_add(CONTAINER_SUBNET, bridge_name).await?;
Ok::<(), RouteError>(())
}
.await
{
Ok(()) => {
tracing::info!(
subnet = CONTAINER_SUBNET,
bridge = bridge_name,
"route ensured (vmnet direct)"
);
return Ok(());
}
Err(ref e) if attempt < 2 => {
tracing::debug!(attempt, error = %e, "vmnet route install retry");
tokio::time::sleep(ROUTE_RETRY_INTERVAL).await;
}
Err(e) => return Err(e),
}
}
unreachable!()
}
pub async fn remove_route() {
let client = match Client::connect().await {
Ok(c) => c,
Err(e) => {
tracing::debug!(error = %e, "arcbox-helper not reachable for route cleanup");
return;
}
};
match client.route_remove(CONTAINER_SUBNET).await {
Ok(()) => {
tracing::info!(subnet = CONTAINER_SUBNET, "route removed");
}
Err(ClientError::Helper(msg)) if msg.contains("not in table") => {
tracing::debug!(subnet = CONTAINER_SUBNET, "route already absent");
}
Err(e) => {
tracing::debug!(
subnet = CONTAINER_SUBNET,
error = %e,
"route remove failed"
);
}
}
}