Skip to main content

lingxia_proxy/
router.rs

1use crate::error::ProxyError;
2use serde::{Deserialize, Serialize};
3
4/// Where to send the tunnelled traffic.
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(tag = "type", rename_all = "snake_case")]
7pub enum UpstreamConfig {
8    /// Connect directly to the target host.
9    Direct,
10    /// Route through an upstream SOCKS5 proxy.
11    Socks5 {
12        host: String,
13        port: u16,
14        /// Optional username/password credentials.
15        credentials: Option<Socks5Credentials>,
16    },
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub struct Socks5Credentials {
21    pub username: String,
22    pub password: String,
23}
24
25/// The router's verdict for a given target host.
26#[derive(Debug, Clone)]
27pub enum RouteDecision {
28    Upstream(UpstreamConfig),
29    /// Block the connection entirely.
30    Block,
31}
32
33/// Pluggable routing policy.  Implementations must be `Send + Sync` so the
34/// server can hold an `Arc<dyn ProxyRouter>`.
35pub trait ProxyRouter: Send + Sync {
36    /// Decide how to handle a CONNECT to `host:port`.
37    fn route(&self, host: &str, port: u16) -> Result<RouteDecision, ProxyError>;
38}
39
40// ── Built-in routers ───────────────────────────────────────────────────────
41
42/// Always routes to the same upstream (or direct).
43pub struct FixedRouter(pub UpstreamConfig);
44
45impl ProxyRouter for FixedRouter {
46    fn route(&self, _host: &str, _port: u16) -> Result<RouteDecision, ProxyError> {
47        Ok(RouteDecision::Upstream(self.0.clone()))
48    }
49}
50
51/// A named routing profile, similar to a SwitchyOmega profile.
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct Profile {
54    pub name: String,
55    pub upstream: UpstreamConfig,
56}
57
58/// Routes based on a list of named profiles, with one active at a time.
59/// Falls back to `Direct` when no profile is active.
60pub struct ProfileRouter {
61    profiles: Vec<Profile>,
62    active: Option<String>,
63}
64
65impl ProfileRouter {
66    pub fn new(profiles: Vec<Profile>) -> Self {
67        Self {
68            profiles,
69            active: None,
70        }
71    }
72
73    pub fn activate(&mut self, name: &str) -> bool {
74        let found = self.profiles.iter().any(|p| p.name == name);
75        if found {
76            self.active = Some(name.to_owned());
77        }
78        found
79    }
80
81    pub fn deactivate(&mut self) {
82        self.active = None;
83    }
84}
85
86impl ProxyRouter for ProfileRouter {
87    fn route(&self, _host: &str, _port: u16) -> Result<RouteDecision, ProxyError> {
88        let upstream = self
89            .active
90            .as_deref()
91            .and_then(|name| self.profiles.iter().find(|p| p.name == name))
92            .map(|p| p.upstream.clone())
93            .unwrap_or(UpstreamConfig::Direct);
94        Ok(RouteDecision::Upstream(upstream))
95    }
96}