siphon_server/
router.rs

1use dashmap::DashMap;
2use siphon_protocol::{ServerMessage, TunnelType};
3use std::sync::Arc;
4use tokio::sync::mpsc;
5
6/// Handle to a tunnel connection
7pub struct TunnelHandle {
8    /// Channel to send messages to this tunnel
9    pub sender: mpsc::Sender<ServerMessage>,
10    /// Client identifier (from certificate CN)
11    #[allow(dead_code)]
12    pub client_id: String,
13    /// Type of tunnel
14    #[allow(dead_code)]
15    pub tunnel_type: TunnelType,
16    /// Cloudflare DNS record ID (for cleanup)
17    pub dns_record_id: Option<String>,
18}
19
20/// Routes incoming requests to appropriate tunnel connections
21pub struct Router {
22    /// Subdomain -> tunnel handle mapping
23    routes: DashMap<String, TunnelHandle>,
24    /// TCP port -> subdomain mapping (for TCP tunnels)
25    tcp_ports: DashMap<u16, String>,
26}
27
28impl Router {
29    pub fn new() -> Arc<Self> {
30        Arc::new(Self {
31            routes: DashMap::new(),
32            tcp_ports: DashMap::new(),
33        })
34    }
35
36    /// Register a new tunnel
37    pub fn register(
38        &self,
39        subdomain: String,
40        handle: TunnelHandle,
41        tcp_port: Option<u16>,
42    ) -> Result<(), RouterError> {
43        // Check if subdomain is already taken
44        if self.routes.contains_key(&subdomain) {
45            return Err(RouterError::SubdomainTaken(subdomain));
46        }
47
48        // Register TCP port if applicable
49        if let Some(port) = tcp_port {
50            self.tcp_ports.insert(port, subdomain.clone());
51        }
52
53        self.routes.insert(subdomain, handle);
54        Ok(())
55    }
56
57    /// Unregister a tunnel
58    pub fn unregister(&self, subdomain: &str) -> Option<TunnelHandle> {
59        if let Some((_, handle)) = self.routes.remove(subdomain) {
60            // Remove TCP port mapping if exists
61            self.tcp_ports.retain(|_, v| v != subdomain);
62            Some(handle)
63        } else {
64            None
65        }
66    }
67
68    /// Get a sender for a subdomain
69    pub fn get_sender(&self, subdomain: &str) -> Option<mpsc::Sender<ServerMessage>> {
70        self.routes.get(subdomain).map(|h| h.sender.clone())
71    }
72
73    /// Get subdomain for a TCP port
74    #[allow(dead_code)]
75    pub fn get_subdomain_for_port(&self, port: u16) -> Option<String> {
76        self.tcp_ports.get(&port).map(|s| s.clone())
77    }
78
79    /// Check if a subdomain is available
80    pub fn is_available(&self, subdomain: &str) -> bool {
81        !self.routes.contains_key(subdomain)
82    }
83
84    /// List all active subdomains
85    #[allow(dead_code)]
86    pub fn list_subdomains(&self) -> Vec<String> {
87        self.routes.iter().map(|r| r.key().clone()).collect()
88    }
89}
90
91impl Default for Router {
92    fn default() -> Self {
93        Self {
94            routes: DashMap::new(),
95            tcp_ports: DashMap::new(),
96        }
97    }
98}
99
100#[derive(Debug, thiserror::Error)]
101pub enum RouterError {
102    #[error("Subdomain already taken: {0}")]
103    SubdomainTaken(String),
104}