use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use anyhow::Context;
use tokio::task::JoinSet;
use tracing::{info, warn};
use fs_mistrust::Mistrust;
use tor_chanmgr::Dormancy;
use tor_config_path::CfgPathResolver;
use tor_keymgr::{
ArtiEphemeralKeystore, ArtiNativeKeystore, KeyMgr, KeyMgrBuilder, KeystoreSelector,
};
use tor_memquota::MemoryQuotaTracker;
use tor_netdir::params::NetParameters;
use tor_persist::state_dir::StateDirectory;
use tor_persist::{FsStateMgr, StateMgr};
use tor_relay_crypto::pk::{RelayIdentityKeypair, RelayIdentityKeypairSpecifier};
use tor_rtcompat::{NetStreamProvider, Runtime};
use crate::config::TorRelayConfig;
#[derive(Clone)]
pub(crate) struct InertTorRelay {
config: TorRelayConfig,
#[expect(unused)] path_resolver: CfgPathResolver,
#[expect(unused)] state_path: PathBuf,
#[expect(unused)] state_dir: StateDirectory,
#[expect(unused)] state_mgr: FsStateMgr,
keymgr: Arc<KeyMgr>,
}
impl InertTorRelay {
pub(crate) fn new(
config: TorRelayConfig,
path_resolver: CfgPathResolver,
) -> anyhow::Result<Self> {
let state_path = config.storage.state_dir(&path_resolver)?;
let state_dir = StateDirectory::new(&state_path, config.storage.permissions())
.context("Failed to create `StateDirectory`")?;
let state_mgr =
FsStateMgr::from_path_and_mistrust(&state_path, config.storage.permissions())
.context("Failed to create `FsStateMgr`")?;
let _ignore_status = state_mgr
.try_lock()
.context("Failed to try locking the state manager")?;
let keymgr = Self::create_keymgr(&state_path, config.storage.permissions())
.context("Failed to create key manager")?;
Ok(Self {
config,
path_resolver,
state_path,
state_dir,
state_mgr,
keymgr,
})
}
pub(crate) async fn bootstrap<R: Runtime>(self, runtime: R) -> anyhow::Result<TorRelay<R>> {
Self::try_generate_keys(&self.keymgr).context("Failed to generate keys")?;
TorRelay::bootstrap(runtime, self).await
}
fn create_keymgr(state_path: &Path, mistrust: &Mistrust) -> anyhow::Result<Arc<KeyMgr>> {
let key_store_dir = state_path.join("keystore");
let ephemeral_store = ArtiEphemeralKeystore::new("relay-ephemeral".into());
let persistent_store = ArtiNativeKeystore::from_path_and_mistrust(&key_store_dir, mistrust)
.context("Failed to construct the native keystore")?;
info!("Using relay keystore from {key_store_dir:?}");
let keymgr = KeyMgrBuilder::default()
.primary_store(Box::new(persistent_store))
.set_secondary_stores(vec![Box::new(ephemeral_store)])
.build()
.context("Failed to build the 'KeyMgr'")?;
let keymgr = Arc::new(keymgr);
Ok(keymgr)
}
fn try_generate_keys(keymgr: &KeyMgr) -> anyhow::Result<()> {
let mut rng = tor_llcrypto::rng::CautiousRng;
let _kp_relay_id = keymgr
.get_or_generate::<RelayIdentityKeypair>(
&RelayIdentityKeypairSpecifier::new(),
KeystoreSelector::default(),
&mut rng,
)
.context("Failed to get or generate the long-term identity key")?;
Ok(())
}
}
pub(crate) struct TorRelay<R: Runtime> {
runtime: R,
#[expect(unused)] memquota: Arc<MemoryQuotaTracker>,
chanmgr: Arc<tor_chanmgr::ChanMgr<R>>,
#[expect(unused)] keymgr: Arc<KeyMgr>,
or_listeners: Vec<<R as NetStreamProvider<SocketAddr>>::Listener>,
}
impl<R: Runtime> TorRelay<R> {
async fn bootstrap(runtime: R, inert: InertTorRelay) -> anyhow::Result<Self> {
let memquota = MemoryQuotaTracker::new(&runtime, inert.config.system.memory.clone())
.context("Failed to initialize memquota tracker")?;
let chanmgr = Arc::new(tor_chanmgr::ChanMgr::new(
runtime.clone(),
&inert.config.channel,
Dormancy::Active,
&NetParameters::default(),
memquota.clone(),
Some(inert.keymgr.clone()),
));
let or_listeners = inert.config.relay.listen.addrs().map(async |addr| {
match runtime.listen(addr).await {
Ok(x) => Some(Ok(x)),
#[cfg(unix)]
Err(ref e) if e.raw_os_error() == Some(libc::EAFNOSUPPORT) => {
let message =
format!("Could not listen at {addr}: address family not supported");
if addr.is_ipv6() {
warn!("{message}");
} else {
tor_error::warn_report!(e, "{message}");
}
None
}
Err(e) => {
Some(Err(e).with_context(|| format!("Failed to listen at address {addr}")))
}
}
});
let or_listeners = {
let mut awaited_listeners = vec![];
for listener in or_listeners {
match listener.await {
Some(Ok(x)) => awaited_listeners.push(x),
Some(Err(e)) => return Err(e),
None => {}
};
}
awaited_listeners
};
if or_listeners.is_empty() {
return Err(anyhow::anyhow!(
"Could not listen at any OR port addresses: {}",
crate::util::iter_join(", ", inert.config.relay.listen.addrs()),
));
}
Ok(Self {
runtime,
memquota,
chanmgr,
keymgr: inert.keymgr,
or_listeners,
})
}
pub(crate) async fn run(self) -> anyhow::Result<void::Void> {
let mut task_handles = JoinSet::new();
task_handles.spawn({
let mut t = crate::tasks::ChannelHouseKeepingTask::new(&self.chanmgr);
async move {
t.start()
.await
.context("Failed to run channel house keeping task")
}
});
task_handles.spawn({
let runtime = self.runtime.clone();
let chanmgr = Arc::clone(&self.chanmgr);
async {
crate::tasks::listeners::or_listener(runtime, chanmgr, self.or_listeners)
.await
.context("Failed to run OR listener task")
}
});
let void = task_handles
.join_next()
.await
.context("Relay task set is empty")?
.context("Relay task join failed")?
.context("Relay task stopped unexpectedly")?;
void::unreachable(void);
}
}