use std::{
os::unix::net::UnixStream,
path::Path,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
time::{Duration, Instant},
};
use anyhow::{Context, Result, bail};
use objects::error::HeddleError;
use repo::daemon::{
EndpointState, IdleDecision, UnixDaemonHandler, bind_unix_socket,
handle_authenticated_unix_connection, load_endpoint, persist_endpoint, pid_alive,
remove_endpoint_if_owned, run_unix_server_loop,
};
use tracing::info;
use super::proto::{
NETWORK_DAEMON_PROTOCOL_VERSION, NetworkDaemonRequest, NetworkDaemonResponse,
network_daemon_endpoint_path, network_daemon_socket_path,
};
pub async fn run_network_daemon() -> Result<()> {
let heddle_home = repo::identity::heddle_home_dir();
let endpoint_path = network_daemon_endpoint_path(&heddle_home);
let socket_path = network_daemon_socket_path(&heddle_home);
if let Some(parent) = endpoint_path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("creating netd state directory {}", parent.display()))?;
}
refuse_if_another_daemon_is_live(&endpoint_path)?;
let listener = bind_unix_socket(&socket_path).context("binding netd control socket")?;
let endpoint = hosted_client::network::bind_persistent_endpoint(
hosted_client::network::default_relay_mode(),
)
.await
.context("binding persistent device endpoint")?;
let node_id = endpoint.id().to_string();
let claim_socket = hosted_client::network::claim_bridge_socket_path(&heddle_home);
let claim_router = hosted_client::network::mount_claim_router(endpoint.clone());
let claim_bridge = tokio::spawn(claim_router.serve_owner_root_bridge(claim_socket.clone()));
let advertised = EndpointState {
version: NETWORK_DAEMON_PROTOCOL_VERSION,
host: "iroh".to_string(),
port: 0,
pid: Some(std::process::id()),
socket_path: Some(socket_path.clone()),
node_id: Some(node_id.clone()),
};
persist_endpoint(&endpoint_path, &advertised).context("persisting netd endpoint discovery")?;
info!(
node_id = %node_id,
socket = %socket_path.display(),
pid = std::process::id(),
"heddle network daemon serving"
);
let started = Instant::now();
let shutdown = Arc::new(AtomicBool::new(false));
let loop_shutdown = Arc::clone(&shutdown);
let control = tokio::task::spawn_blocking(move || {
let mut handler = NetworkDaemonHandler {
started,
shutdown: loop_shutdown,
node_id,
};
run_unix_server_loop(&listener, &mut handler)
});
let loop_result = control.await;
claim_bridge.abort();
endpoint.close().await;
remove_endpoint_if_owned(&endpoint_path, &advertised);
let _ = std::fs::remove_file(&claim_socket);
let _ = std::fs::remove_file(&socket_path);
info!("heddle network daemon exiting");
match loop_result {
Ok(result) => result.map_err(Into::into),
Err(join_error) => bail!("netd control loop panicked: {join_error}"),
}
}
fn refuse_if_another_daemon_is_live(endpoint_path: &Path) -> Result<()> {
let Ok(existing) = load_endpoint(endpoint_path) else {
return Ok(());
};
if let Some(pid) = existing.pid
&& pid_alive(pid)
{
bail!(
"a heddle network daemon is already serving (pid {pid}); \
refusing to bind a second endpoint on the device node id"
);
}
remove_endpoint_if_owned(endpoint_path, &existing);
Ok(())
}
struct NetworkDaemonHandler {
started: Instant,
shutdown: Arc<AtomicBool>,
node_id: String,
}
impl UnixDaemonHandler for NetworkDaemonHandler {
fn handle(&mut self, stream: UnixStream) -> Result<(), HeddleError> {
let started = self.started;
let node_id = self.node_id.clone();
let shutdown = Arc::clone(&self.shutdown);
handle_authenticated_unix_connection(
stream,
move |request: NetworkDaemonRequest| match request {
NetworkDaemonRequest::Health {} => NetworkDaemonResponse::Health {
version: NETWORK_DAEMON_PROTOCOL_VERSION,
ok: true,
uptime_s: started.elapsed().as_secs(),
node_id,
},
NetworkDaemonRequest::Shutdown {} => {
shutdown.store(true, Ordering::Release);
NetworkDaemonResponse::Shutdown {
version: NETWORK_DAEMON_PROTOCOL_VERSION,
ok: true,
}
}
},
)
}
fn on_tick(&mut self, _idle_for: Duration) -> IdleDecision {
if self.shutdown.load(Ordering::Acquire) {
IdleDecision::Exit
} else {
IdleDecision::Continue
}
}
}