use core::{
net::{Ipv4Addr, Ipv6Addr},
time::Duration,
};
use std::sync::Arc;
use futures::StreamExt;
use kameo::{
actor::{ActorRef, Spawn},
message::{Context, StreamMessage},
prelude::{Message, ReplySender},
reply::DelegatedReply,
};
use ts_control::{AsyncControlClient, Error as ControlError, Node, StateUpdate};
use crate::derp_latency::{DerpLatencyMeasurement, DerpLatencyMeasurer};
pub struct ControlRunner {
client: AsyncControlClient,
params: Params,
self_node: Option<Node>,
pending: Vec<PendingRequest>,
}
#[derive(Clone)]
pub struct Params {
pub(crate) config: ts_control::Config,
pub(crate) auth_key: Option<String>,
pub(crate) env: crate::Env,
}
#[doc(hidden)]
#[derive(Debug, Clone, thiserror::Error)]
pub enum ControlRunnerError {
#[error(transparent)]
Control(#[from] ControlError),
#[error(transparent)]
Crate(#[from] crate::Error),
}
impl kameo::Actor for ControlRunner {
type Args = Params;
type Error = ControlRunnerError;
async fn on_start(params: Params, slf: ActorRef<Self>) -> Result<Self, Self::Error> {
loop {
match AsyncControlClient::check_auth(
¶ms.config,
¶ms.env.keys,
params.auth_key.as_deref(),
)
.await
{
Ok(()) => break,
Err(ControlError::MachineNotAuthorized(u)) => {
tracing::info!(auth_url = %u, "please authorize this machine or pass an auth key");
tokio::time::sleep(Duration::from_secs(5)).await;
}
Err(e) => return Err(e.into()),
}
}
let (client, stream) = AsyncControlClient::connect(
¶ms.config,
¶ms.env.keys,
params.auth_key.as_deref(),
)
.await?;
params.env.subscribe::<DerpLatencyMeasurement>(&slf).await?;
DerpLatencyMeasurer::supervise(&slf, params.env.clone())
.spawn()
.await;
slf.attach_stream(stream.boxed(), (), ());
params.env.register(None, &slf).await?;
Ok(Self {
client,
params,
self_node: None,
pending: Default::default(),
})
}
}
#[kameo::messages]
impl ControlRunner {
#[message(ctx)]
pub fn ipv4(
&mut self,
ctx: &mut Context<Self, DelegatedReply<Option<Ipv4Addr>>>,
) -> DelegatedReply<Option<Ipv4Addr>> {
if let Some(node) = &self.self_node {
return ctx.reply(Some(node.tailnet_address.ipv4.addr()));
}
let (deleg, replier) = ctx.reply_sender();
if let Some(replier) = replier {
self.pending.push(PendingRequest::Ipv4(replier));
}
deleg
}
#[message(ctx)]
pub fn ipv6(
&mut self,
ctx: &mut Context<Self, DelegatedReply<Option<Ipv6Addr>>>,
) -> DelegatedReply<Option<Ipv6Addr>> {
if let Some(node) = &self.self_node {
return ctx.reply(Some(node.tailnet_address.ipv6.addr()));
}
let (deleg, replier) = ctx.reply_sender();
if let Some(replier) = replier {
self.pending.push(PendingRequest::Ipv6(replier));
}
deleg
}
#[message(ctx)]
pub fn self_node(
&mut self,
ctx: &mut Context<Self, DelegatedReply<Option<Node>>>,
) -> DelegatedReply<Option<Node>> {
if let Some(node) = &self.self_node {
return ctx.reply(Some(node.clone()));
}
let (deleg, replier) = ctx.reply_sender();
if let Some(replier) = replier {
self.pending.push(PendingRequest::SelfNode(replier));
}
deleg
}
}
enum PendingRequest {
Ipv4(ReplySender<Option<Ipv4Addr>>),
Ipv6(ReplySender<Option<Ipv6Addr>>),
SelfNode(ReplySender<Option<Node>>),
}
impl PendingRequest {
fn respond(self, node: &Node) {
match self {
PendingRequest::Ipv4(sender) => {
sender.send(Some(node.tailnet_address.ipv4.addr()));
}
PendingRequest::Ipv6(sender) => {
sender.send(Some(node.tailnet_address.ipv6.addr()));
}
PendingRequest::SelfNode(sender) => {
sender.send(Some(node.clone()));
}
}
}
}
impl Message<StreamMessage<Arc<StateUpdate>, (), ()>> for ControlRunner {
type Reply = ();
async fn handle(
&mut self,
msg: StreamMessage<Arc<StateUpdate>, (), ()>,
_ctx: &mut Context<Self, Self::Reply>,
) {
match msg {
StreamMessage::Started(_) => {
tracing::trace!("started listening to state updates");
}
StreamMessage::Next(msg) => {
if let Some(node) = msg.node.as_ref() {
self.self_node = Some(node.clone());
}
if let Err(e) = self.params.env.publish(msg).await {
tracing::error!(error = %e, "publishing netmap update");
}
}
StreamMessage::Finished(_) => {
tracing::error!("state update stream terminated")
}
}
if let Some(node) = &self.self_node {
for req in self.pending.drain(..) {
req.respond(node);
}
}
}
}
impl Message<DerpLatencyMeasurement> for ControlRunner {
type Reply = ();
async fn handle(&mut self, msg: DerpLatencyMeasurement, _ctx: &mut Context<Self, Self::Reply>) {
let measurements = msg.measurement.as_ref().clone();
let Some(result) = measurements.first() else {
tracing::debug!("derp latency measurements empty");
return;
};
let iter = measurements.iter().map(|result| {
(
result.latency_map_key.as_str(),
result.latency.as_secs_f64(),
)
});
tracing::debug!(selected_region_id = ?result.id, "updating home region");
self.client.set_home_region(result.id, iter).await;
}
}