use crate::{ capture::{Capture, CaptureType, ICaptureEvent}, client::ClientManager, config::Config, connect::LanMouseConnection, crypto, dns::{DnsEvent, DnsResolver}, emulation::{Emulation, EmulationEvent}, listen::{LanMouseListener, ListenerCreationError}, }; use futures::StreamExt; use hickory_resolver::error::ResolveError; use lan_mouse_ipc::{ AsyncFrontendListener, ClientConfig, ClientHandle, ClientState, FrontendEvent, FrontendRequest, IpcError, IpcListenerCreationError, Position, Status, }; use log; use std::{ collections::{HashMap, HashSet, VecDeque}, io, net::{IpAddr, SocketAddr}, sync::{Arc, RwLock}, }; use thiserror::Error; use tokio::{process::Command, signal, sync::Notify}; #[derive(Debug, Error)] pub enum ServiceError { #[error(transparent)] Dns(#[from] ResolveError), #[error(transparent)] IpcListen(#[from] IpcListenerCreationError), #[error(transparent)] Io(#[from] io::Error), #[error(transparent)] ListenError(#[from] ListenerCreationError), #[error("failed to load certificate: `{0}`")] Certificate(#[from] crypto::Error), } pub struct Service { /// input capture capture: Capture, /// input emulation emulation: Emulation, /// dns resolver resolver: DnsResolver, /// frontend listener frontend_listener: AsyncFrontendListener, /// authorized public key sha256 fingerprints authorized_keys: Arc>>, /// (outgoing) client information client_manager: ClientManager, /// current port port: u16, /// the public key fingerprint for (D)TLS public_key_fingerprint: String, /// notify for pending frontend events frontend_event_pending: Notify, /// frontend events queued for sending pending_frontend_events: VecDeque, /// status of input capture (enabled / disabled) capture_status: Status, /// status of input emulation (enabled / disabled) emulation_status: Status, /// keep track of registered connections to avoid duplicate barriers incoming_conns: HashSet, /// map from capture handle to connection info incoming_conn_info: HashMap, next_trigger_handle: u64, } #[derive(Debug)] struct Incoming { fingerprint: String, addr: SocketAddr, pos: Position, } impl Service { pub async fn new(config: Config) -> Result { let client_manager = ClientManager::default(); for client in config.clients() { let config = ClientConfig { hostname: client.hostname, fix_ips: client.ips.into_iter().collect(), port: client.port, pos: client.pos, cmd: client.enter_hook, }; let state = ClientState { active: client.active, ips: HashSet::from_iter(config.fix_ips.iter().cloned()), ..Default::default() }; let handle = client_manager.add_client(); client_manager.set_config(handle, config); client_manager.set_state(handle, state); } // load certificate let cert = crypto::load_or_generate_key_and_cert(config.cert_path())?; let public_key_fingerprint = crypto::certificate_fingerprint(&cert); // create frontend communication adapter, exit if already running let frontend_listener = AsyncFrontendListener::new().await?; let authorized_keys = Arc::new(RwLock::new(config.authorized_fingerprints())); // listener + connection let listener = LanMouseListener::new(config.port(), cert.clone(), authorized_keys.clone()).await?; let conn = LanMouseConnection::new(cert.clone(), client_manager.clone()); // input capture + emulation let capture_backend = config.capture_backend().map(|b| b.into()); let capture = Capture::new(capture_backend, conn, config.release_bind()); let emulation_backend = config.emulation_backend().map(|b| b.into()); let emulation = Emulation::new(emulation_backend, listener); // create dns resolver let resolver = DnsResolver::new()?; let port = config.port(); let service = Self { capture, emulation, frontend_listener, resolver, authorized_keys, public_key_fingerprint, client_manager, frontend_event_pending: Default::default(), port, pending_frontend_events: Default::default(), capture_status: Default::default(), emulation_status: Default::default(), incoming_conn_info: Default::default(), incoming_conns: Default::default(), next_trigger_handle: 0, }; Ok(service) } pub async fn run(&mut self) -> Result<(), ServiceError> { let active = self.client_manager.active_clients(); for handle in active.iter() { // small hack: `activate_client()` checks, if the client // is already active in client_manager and does not create a // capture barrier in that case so we have to deactivate it first self.client_manager.deactivate_client(*handle); } for handle in active { self.activate_client(handle); } loop { tokio::select! { request = self.frontend_listener.next() => self.handle_frontend_request(request), _ = self.frontend_event_pending.notified() => self.handle_frontend_pending().await, event = self.emulation.event() => self.handle_emulation_event(event), event = self.capture.event() => self.handle_capture_event(event), event = self.resolver.event() => self.handle_resolver_event(event), r = signal::ctrl_c() => break r.expect("failed to wait for CTRL+C"), } } log::info!("terminating service ..."); log::debug!("terminating capture ..."); self.capture.terminate().await; log::debug!("terminating emulation ..."); self.emulation.terminate().await; log::debug!("terminating dns resolver ..."); self.resolver.terminate().await; Ok(()) } fn handle_frontend_request(&mut self, request: Option>) { let request = match request.expect("frontend listener closed") { Ok(r) => r, Err(e) => return log::error!("error receiving request: {e}"), }; match request { FrontendRequest::Activate(handle, active) => self.set_client_active(handle, active), FrontendRequest::AuthorizeKey(desc, fp) => self.add_authorized_key(desc, fp), FrontendRequest::ChangePort(port) => self.change_port(port), FrontendRequest::Create => self.add_client(), FrontendRequest::Delete(handle) => self.remove_client(handle), FrontendRequest::EnableCapture => self.capture.reenable(), FrontendRequest::EnableEmulation => self.emulation.reenable(), FrontendRequest::Enumerate() => self.enumerate(), FrontendRequest::UpdateFixIps(handle, fix_ips) => self.update_fix_ips(handle, fix_ips), FrontendRequest::UpdateHostname(handle, host) => self.update_hostname(handle, host), FrontendRequest::UpdatePort(handle, port) => self.update_port(handle, port), FrontendRequest::UpdatePosition(handle, pos) => self.update_pos(handle, pos), FrontendRequest::ResolveDns(handle) => self.resolve(handle), FrontendRequest::Sync => self.sync_frontend(), FrontendRequest::RemoveAuthorizedKey(key) => self.remove_authorized_key(key), FrontendRequest::UpdateEnterHook(handle, enter_hook) => { self.update_enter_hook(handle, enter_hook) } } } async fn handle_frontend_pending(&mut self) { while let Some(event) = self.pending_frontend_events.pop_front() { self.frontend_listener.broadcast(event).await; } } fn handle_emulation_event(&mut self, event: EmulationEvent) { match event { EmulationEvent::ConnectionAttempt { fingerprint } => { self.notify_frontend(FrontendEvent::ConnectionAttempt { fingerprint }); } EmulationEvent::Entered { addr, pos, fingerprint, } => { // check if already registered if !self.incoming_conns.contains(&addr) { self.add_incoming(addr, pos, fingerprint.clone()); self.notify_frontend(FrontendEvent::DeviceEntered { fingerprint, addr, pos, }); } else { self.update_incoming(addr, pos, fingerprint); } } EmulationEvent::Disconnected { addr } => { if let Some(addr) = self.remove_incoming(addr) { self.notify_frontend(FrontendEvent::IncomingDisconnected(addr)); } } EmulationEvent::PortChanged(port) => match port { Ok(port) => { self.port = port; self.notify_frontend(FrontendEvent::PortChanged(port, None)); } Err(e) => self .notify_frontend(FrontendEvent::PortChanged(self.port, Some(format!("{e}")))), }, EmulationEvent::EmulationDisabled => { self.emulation_status = Status::Disabled; self.notify_frontend(FrontendEvent::EmulationStatus(self.emulation_status)); } EmulationEvent::EmulationEnabled => { self.emulation_status = Status::Enabled; self.notify_frontend(FrontendEvent::EmulationStatus(self.emulation_status)); } EmulationEvent::ReleaseNotify => self.capture.release(), EmulationEvent::Connected { addr, fingerprint } => { self.notify_frontend(FrontendEvent::DeviceConnected { addr, fingerprint }); } } } fn handle_capture_event(&mut self, event: ICaptureEvent) { match event { ICaptureEvent::CaptureBegin(handle) => { // we entered the capture zone for an incoming connection // => notify it that its capture should be released if let Some(incoming) = self.incoming_conn_info.get(&handle) { self.emulation.send_leave_event(incoming.addr); } } ICaptureEvent::CaptureDisabled => { self.capture_status = Status::Disabled; self.notify_frontend(FrontendEvent::CaptureStatus(self.capture_status)); } ICaptureEvent::CaptureEnabled => { self.capture_status = Status::Enabled; self.notify_frontend(FrontendEvent::CaptureStatus(self.capture_status)); } ICaptureEvent::ClientEntered(handle) => { log::info!("entering client {handle} ..."); self.spawn_hook_command(handle); } } } fn handle_resolver_event(&mut self, event: DnsEvent) { let handle = match event { DnsEvent::Resolving(handle) => { self.client_manager.set_resolving(handle, true); handle } DnsEvent::Resolved(handle, hostname, ips) => { self.client_manager.set_resolving(handle, false); if let Err(e) = &ips { log::warn!("could not resolve {hostname}: {e}"); } let ips = ips.unwrap_or_default(); self.client_manager.set_dns_ips(handle, ips); handle } }; self.broadcast_client(handle); } fn resolve(&self, handle: ClientHandle) { if let Some(hostname) = self.client_manager.get_hostname(handle) { self.resolver.resolve(handle, hostname); } } fn sync_frontend(&mut self) { self.enumerate(); self.notify_frontend(FrontendEvent::EmulationStatus(self.emulation_status)); self.notify_frontend(FrontendEvent::CaptureStatus(self.capture_status)); self.notify_frontend(FrontendEvent::PortChanged(self.port, None)); self.notify_frontend(FrontendEvent::PublicKeyFingerprint( self.public_key_fingerprint.clone(), )); let keys = self.authorized_keys.read().expect("lock").clone(); self.notify_frontend(FrontendEvent::AuthorizedUpdated(keys)); } const ENTER_HANDLE_BEGIN: u64 = u64::MAX / 2 + 1; fn add_incoming(&mut self, addr: SocketAddr, pos: Position, fingerprint: String) { let handle = Self::ENTER_HANDLE_BEGIN + self.next_trigger_handle; self.next_trigger_handle += 1; self.capture.create(handle, pos, CaptureType::EnterOnly); self.incoming_conns.insert(addr); self.incoming_conn_info.insert( handle, Incoming { fingerprint, addr, pos, }, ); } fn update_incoming(&mut self, addr: SocketAddr, pos: Position, fingerprint: String) { let incoming = self .incoming_conn_info .iter_mut() .find(|(_, i)| i.addr == addr) .map(|(_, i)| i) .expect("no such client"); let mut changed = false; if incoming.fingerprint != fingerprint { incoming.fingerprint = fingerprint.clone(); changed = true; } if incoming.pos != pos { incoming.pos = pos; changed = true; } if changed { self.remove_incoming(addr); self.add_incoming(addr, pos, fingerprint.clone()); self.notify_frontend(FrontendEvent::IncomingDisconnected(addr)); self.notify_frontend(FrontendEvent::DeviceEntered { fingerprint, addr, pos, }); } } fn remove_incoming(&mut self, addr: SocketAddr) -> Option { let handle = self .incoming_conn_info .iter() .find(|(_, incoming)| incoming.addr == addr) .map(|(k, _)| *k)?; self.capture.destroy(handle); self.incoming_conns.remove(&addr); self.incoming_conn_info .remove(&handle) .map(|incoming| incoming.addr) } fn notify_frontend(&mut self, event: FrontendEvent) { self.pending_frontend_events.push_back(event); self.frontend_event_pending.notify_one(); } fn add_authorized_key(&mut self, desc: String, fp: String) { self.authorized_keys.write().expect("lock").insert(fp, desc); let keys = self.authorized_keys.read().expect("lock").clone(); self.notify_frontend(FrontendEvent::AuthorizedUpdated(keys)); } fn remove_authorized_key(&mut self, fp: String) { self.authorized_keys.write().expect("lock").remove(&fp); let keys = self.authorized_keys.read().expect("lock").clone(); self.notify_frontend(FrontendEvent::AuthorizedUpdated(keys)); } fn enumerate(&mut self) { let clients = self.client_manager.get_client_states(); self.notify_frontend(FrontendEvent::Enumerate(clients)); } fn add_client(&mut self) { let handle = self.client_manager.add_client(); log::info!("added client {handle}"); let (c, s) = self.client_manager.get_state(handle).unwrap(); self.notify_frontend(FrontendEvent::Created(handle, c, s)); } fn set_client_active(&mut self, handle: ClientHandle, active: bool) { if active { self.activate_client(handle); } else { self.deactivate_client(handle); } } fn deactivate_client(&mut self, handle: ClientHandle) { log::debug!("deactivating client {handle}"); if self.client_manager.deactivate_client(handle) { self.capture.destroy(handle); self.broadcast_client(handle); log::info!("deactivated client {handle}"); } } fn activate_client(&mut self, handle: ClientHandle) { log::debug!("activating client"); /* resolve dns on activate */ self.resolve(handle); /* deactivate potential other client at this position */ let Some(pos) = self.client_manager.get_pos(handle) else { return; }; if let Some(other) = self.client_manager.client_at(pos) { if other != handle { self.deactivate_client(other); } } /* activate the client */ if self.client_manager.activate_client(handle) { /* notify capture and frontends */ self.capture.create(handle, pos, CaptureType::Default); self.broadcast_client(handle); log::info!("activated client {handle} ({pos})"); } } fn change_port(&mut self, port: u16) { if self.port != port { self.emulation.request_port_change(port); } else { self.notify_frontend(FrontendEvent::PortChanged(self.port, None)); } } fn remove_client(&mut self, handle: ClientHandle) { if self .client_manager .remove_client(handle) .map(|(_, s)| s.active) .unwrap_or(false) { self.capture.destroy(handle); } self.notify_frontend(FrontendEvent::Deleted(handle)); } fn update_fix_ips(&mut self, handle: ClientHandle, fix_ips: Vec) { self.client_manager.set_fix_ips(handle, fix_ips); self.broadcast_client(handle); } fn update_hostname(&mut self, handle: ClientHandle, hostname: Option) { log::info!("hostname changed: {hostname:?}"); if self.client_manager.set_hostname(handle, hostname.clone()) { self.resolve(handle); } self.broadcast_client(handle); } fn update_port(&mut self, handle: ClientHandle, port: u16) { self.client_manager.set_port(handle, port); self.broadcast_client(handle); } fn update_pos(&mut self, handle: ClientHandle, pos: Position) { // update state in event input emulator & input capture if self.client_manager.set_pos(handle, pos) { self.deactivate_client(handle); self.activate_client(handle); } self.broadcast_client(handle); } fn update_enter_hook(&mut self, handle: ClientHandle, enter_hook: Option) { self.client_manager.set_enter_hook(handle, enter_hook); self.broadcast_client(handle); } fn broadcast_client(&mut self, handle: ClientHandle) { let event = self .client_manager .get_state(handle) .map(|(c, s)| FrontendEvent::State(handle, c, s)) .unwrap_or(FrontendEvent::NoSuchClient(handle)); self.notify_frontend(event); } fn spawn_hook_command(&self, handle: ClientHandle) { let Some(cmd) = self.client_manager.get_enter_cmd(handle) else { return; }; tokio::task::spawn_local(async move { log::info!("spawning command!"); let mut child = match Command::new("sh").arg("-c").arg(cmd.as_str()).spawn() { Ok(c) => c, Err(e) => { log::warn!("could not execute cmd: {e}"); return; } }; match child.wait().await { Ok(s) => { if s.success() { log::info!("{cmd} exited successfully"); } else { log::warn!("{cmd} exited with {s}"); } } Err(e) => log::warn!("{cmd}: {e}"), } }); } }