#![doc = include_str!("../README.md")]
extern crate ts_netstack_smoltcp as netstack;
use core::time::Duration;
use std::sync::Arc;
use kameo::{
actor::{ActorRef, Spawn, WeakActorRef},
mailbox::Signal,
};
use netstack::netcore::Channel;
use tokio::sync::{Mutex, watch};
use crate::{
control_runner::ControlRunner, dataplane::DataplaneActor, multiderp::Multiderp,
netstack_actor::NetstackActor,
};
pub mod control_runner;
mod dataplane;
mod derp_latency;
mod env;
mod error;
mod multiderp;
mod netmon;
mod netstack_actor;
mod packetfilter;
pub mod peer_tracker;
mod registry;
mod retained_bus;
mod route_updater;
mod src_filter;
mod stunner;
mod task;
pub(crate) use env::Env;
pub use error::{Error, ErrorKind};
pub use registry::Registry;
pub use task::{ErasedTask, Task};
use crate::peer_tracker::PeerTracker;
macro_rules! try_join_startup {
($([$($optaref:ident)*] $(,)?)? $($aref:ident),* $(,)?) => {
{
tokio::try_join![
$(
$(
match $optaref.as_ref() {
Some(aref) => {
Box::pin(map_startup_result(aref)) as core::pin::Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>
},
None => {
Box::pin(core::future::ready(Ok(()))) as core::pin::Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>
}
},
)*
)?
$(
map_startup_result(&$aref),
)*
]
}
}
}
async fn map_startup_result<A>(aref: &ActorRef<A>) -> Result<(), Error>
where
A: kameo::Actor,
A::Error: core::error::Error,
{
aref.wait_for_startup_with_result(|r| {
r.map_err(|e| {
tracing::error!(actor = ?aref, "startup for actor failed");
Error::from(e).with_actor_info(aref.clone())
})
})
.await
}
pub struct Runtime {
pub control: ActorRef<ControlRunner>,
dataplane: ActorRef<DataplaneActor>,
netstack: WeakActorRef<NetstackActor>,
pub peer_tracker: WeakActorRef<PeerTracker>,
env: Env,
shutdown: watch::Sender<bool>,
}
impl Runtime {
pub async fn spawn(
config: ts_control::Config,
auth_key: Option<String>,
keys: ts_keys::NodeState,
) -> Result<Self, Error> {
let (shutdown_tx, shutdown_rx) = watch::channel(false);
let env = Env::new(keys, shutdown_rx);
let dataplane = DataplaneActor::spawn(env.clone());
let (netstack_id, netstack_up, netstack_down) =
dataplane.ask(dataplane::NewOverlayTransport).await?;
Multiderp::spawn((env.clone(), dataplane.clone()));
let rt_upd = route_updater::RouteUpdater::spawn((env.clone(), netstack_id));
let pf_upd = packetfilter::PacketfilterUpdater::spawn(env.clone());
let src_upd = src_filter::SourceFilterUpdater::spawn(env.clone());
let stunner = stunner::Stunner::spawn(env.clone());
let netmon = ts_netmon::platform_mon()
.map(|mon| netmon::NetmonActor::spawn((env.clone(), Arc::new(mon))));
let peer_tracker = PeerTracker::spawn(env.clone());
let netstack = NetstackActor::spawn((
env.clone(),
Default::default(),
netstack_up,
Arc::new(Mutex::new(netstack_down)),
));
let control = ControlRunner::spawn(control_runner::Params {
config,
auth_key,
env: env.clone(),
});
let rt = Self {
control: control.clone(),
dataplane: dataplane.clone(),
peer_tracker: peer_tracker.downgrade(),
netstack: netstack.downgrade(),
env,
shutdown: shutdown_tx,
};
tracing::trace!("waiting for actors to finish starting");
try_join_startup![
[netmon],
dataplane,
rt_upd,
pf_upd,
src_upd,
stunner,
peer_tracker,
netstack,
control,
]?;
tracing::trace!("all root actors started ok");
Ok(rt)
}
pub async fn channel(&self) -> Result<Channel, Error> {
let (channel,) = self
.netstack
.upgrade()
.ok_or(Error {
kind: ErrorKind::ActorGone,
target_actor: None,
message_ty: None,
})?
.ask(netstack_actor::GetChannel)
.await?;
Ok(channel)
}
pub async fn graceful_shutdown(self, timeout: Option<Duration>) -> bool {
self.shutdown.send_replace(true);
async fn _shutdown_all(runtime: Runtime) {
let _ignore = runtime.control.stop_gracefully().await;
let _ignore = runtime.dataplane.stop_gracefully().await;
let _ignore = runtime.env.bus.stop_gracefully().await;
let _ignore = runtime.env.scheduler.stop_gracefully().await;
tokio::join![
runtime.control.wait_for_shutdown(),
runtime.dataplane.wait_for_shutdown(),
runtime.env.bus.wait_for_shutdown(),
runtime.env.scheduler.wait_for_shutdown(),
];
}
let fut = _shutdown_all(self);
match timeout {
Some(timeout) => tokio::time::timeout(timeout, fut).await.is_ok(),
None => {
fut.await;
true
}
}
}
}
impl Drop for Runtime {
fn drop(&mut self) {
if *self.shutdown.borrow() {
self.control.kill();
self.dataplane.kill();
self.env.bus.kill();
self.env.scheduler.kill();
return;
}
self.shutdown.send_replace(true);
try_shutdown(&self.control);
try_shutdown(&self.dataplane);
try_shutdown(&self.env.bus);
try_shutdown(&self.env.scheduler);
}
}
fn try_shutdown(a: &ActorRef<impl kameo::Actor>) {
if let Err(e) = a.mailbox_sender().try_send(Signal::Stop) {
tracing::error!(error = %e, "graceful shutdown failed, killing actor");
a.kill();
}
}