diff --git a/clash_lib/src/app/outbound/manager.rs b/clash_lib/src/app/outbound/manager.rs index 64a4d3cd..c114e491 100644 --- a/clash_lib/src/app/outbound/manager.rs +++ b/clash_lib/src/app/outbound/manager.rs @@ -1,11 +1,20 @@ use anyhow::Result; +use http::Uri; use std::collections::HashMap; use std::sync::Arc; -use tokio::sync::RwLock; +use std::time::Duration; +use tokio::sync::{Mutex, RwLock}; use tracing::debug; -use crate::config::internal::proxy::{PROXY_DIRECT, PROXY_REJECT}; -use crate::proxy::{reject, shadowsocks, CommonOption}; +use crate::app::proxy_manager::healthcheck::HealthCheck; +use crate::app::proxy_manager::providers::file_vehicle; +use crate::app::proxy_manager::providers::http_vehicle::{self, Vehicle}; +use crate::app::proxy_manager::providers::plain_provider::PlainProvider; +use crate::app::proxy_manager::providers::proxy_provider::ThreadSafeProxyProvider; +use crate::app::proxy_manager::providers::proxy_set_provider::ProxySetProvider; +use crate::app::proxy_manager::ProxyManager; +use crate::config::internal::proxy::{OutboundProxyProvider, PROXY_DIRECT, PROXY_REJECT}; +use crate::proxy::{reject, relay}; use crate::{ app::ThreadSafeDNSResolver, config::internal::proxy::{OutboundGroupProtocol, OutboundProxyProtocol}, @@ -15,21 +24,42 @@ use crate::{ pub struct OutboundManager { handlers: HashMap, + proxy_manager: Arc>, } pub type ThreadSafeOutboundManager = Arc>; impl OutboundManager { - pub fn new( + pub async fn new( outbounds: Vec, outbound_groups: Vec, - dns_client: ThreadSafeDNSResolver, + proxy_providers: HashMap, + dns_resolver: ThreadSafeDNSResolver, ) -> Result { let mut handlers = HashMap::new(); + let mut provider_registry = HashMap::new(); + let proxy_manager = Arc::new(Mutex::new(ProxyManager::new())); - OutboundManager::load_handlers(outbounds, outbound_groups, dns_client, &mut handlers)?; + Self::load_proxy_providers( + proxy_providers, + proxy_manager.clone(), + dns_resolver.clone(), + &mut provider_registry, + ) + .await?; - Ok(Self { handlers }) + Self::load_handlers( + outbounds, + outbound_groups, + proxy_manager.clone(), + provider_registry, + &mut handlers, + )?; + + Ok(Self { + handlers, + proxy_manager, + }) } pub fn get(&self, name: &str) -> Option { @@ -39,7 +69,8 @@ impl OutboundManager { fn load_handlers( outbounds: Vec, outbound_groups: Vec, - _dns_client: ThreadSafeDNSResolver, + proxy_manager: Arc>, + provider_registry: HashMap, handlers: &mut HashMap, ) -> Result<(), Error> { for outbound in outbounds.iter() { @@ -65,7 +96,58 @@ impl OutboundManager { for outbound_group in outbound_groups.iter() { match outbound_group { OutboundGroupProtocol::Relay(proto) => { - handlers.insert(proto.name.clone(), proto.try_into()?); + let mut providers: Vec = vec![]; + + if let Some(proxies) = &proto.proxies { + let proxies = proxies + .into_iter() + .map(|x| { + handlers + .get(x) + .expect(format!("proxy {} not found", x).as_str()) + .clone() + }) + .collect::>(); + let hc = HealthCheck::new( + proxies.clone(), + proto.url.clone(), + proto.interval, + true, + proxy_manager.clone(), + ) + .map_err(|x| Error::InvalidConfig(format!("invalid hc config: {}", x)))?; + let provider = PlainProvider::new( + proto.name.clone(), + proxies, + hc, + proxy_manager.clone(), + ) + .map_err(|x| { + Error::InvalidConfig(format!("invalid provider config: {}", x)) + })?; + + providers.push(Arc::new(provider)); + } + + if let Some(provider_names) = &proto.use_provider { + for provider_name in provider_names { + let provider = provider_registry + .get(provider_name) + .expect(format!("provider {} not found", provider_name).as_str()) + .clone(); + providers.push(provider); + } + } + + let relay = relay::Handler::new( + relay::HandlerOptions { + name: proto.name.clone(), + ..Default::default() + }, + providers, + ); + + handlers.insert(proto.name.clone(), relay); } OutboundGroupProtocol::UrlTest(_proto) => todo!(), OutboundGroupProtocol::Fallback(_proto) => todo!(), @@ -75,4 +157,66 @@ impl OutboundManager { } Ok(()) } + + async fn load_proxy_providers( + proxy_providers: HashMap, + proxy_manager: Arc>, + resolver: ThreadSafeDNSResolver, + provider_registry: &mut HashMap, + ) -> Result<(), Error> { + for (name, provider) in proxy_providers.into_iter() { + match provider { + OutboundProxyProvider::Http(http) => { + let vehicle = http_vehicle::Vehicle::new( + http.url + .parse::() + .expect(format!("invalid provider url: {}", http.url).as_str()), + http.path, + resolver.clone(), + ); + let hc = HealthCheck::new( + vec![], + http.health_check.url, + http.health_check.interval, + true, + proxy_manager.clone(), + ) + .map_err(|e| Error::InvalidConfig(format!("invalid hc config {}", e)))?; + let provider = ProxySetProvider::new( + name.clone(), + Duration::from_secs(http.interval), + Arc::new(Mutex::new(vehicle)), + hc, + proxy_manager.clone(), + ) + .map_err(|x| Error::InvalidConfig(format!("invalid provider config: {}", x)))?; + + provider_registry.insert(name, Arc::new(provider)); + } + OutboundProxyProvider::File(file) => { + let vehicle = file_vehicle::Vehicle::new(&file.path); + let hc = HealthCheck::new( + vec![], + file.health_check.url, + file.health_check.interval, + true, + proxy_manager.clone(), + ) + .map_err(|e| Error::InvalidConfig(format!("invalid hc config {}", e)))?; + + let provider = ProxySetProvider::new( + name.clone(), + Duration::from_secs(file.interval.unwrap_or_default()), + Arc::new(Mutex::new(vehicle)), + hc, + proxy_manager.clone(), + ) + .map_err(|x| Error::InvalidConfig(format!("invalid provider config: {}", x)))?; + + provider_registry.insert(name, Arc::new(provider)); + } + } + } + Ok(()) + } } diff --git a/clash_lib/src/app/proxy_manager/mod.rs b/clash_lib/src/app/proxy_manager/mod.rs index baa6ebdf..0c1eb86e 100644 --- a/clash_lib/src/app/proxy_manager/mod.rs +++ b/clash_lib/src/app/proxy_manager/mod.rs @@ -2,22 +2,24 @@ use std::collections::{HashMap, VecDeque}; use crate::proxy::AnyOutboundHandler; -mod healthcheck; +use self::providers::proxy_provider::ThreadSafeProxyProvider; + +pub mod healthcheck; pub mod providers; type Latency = VecDeque; /// ProxyManager is only the latency registry. /// TODO: move all proxies here, too, maybe. +#[derive(Default)] pub struct ProxyManager { latency_map: HashMap, + proxy_provider: HashMap, } impl ProxyManager { pub fn new() -> Self { - Self { - latency_map: HashMap::new(), - } + Self::default() } pub async fn check(&mut self, _proxy: &Vec) { diff --git a/clash_lib/src/app/proxy_manager/providers/http_vehicle.rs b/clash_lib/src/app/proxy_manager/providers/http_vehicle.rs index 9d63a64f..c892bc73 100644 --- a/clash_lib/src/app/proxy_manager/providers/http_vehicle.rs +++ b/clash_lib/src/app/proxy_manager/providers/http_vehicle.rs @@ -1,7 +1,7 @@ use super::{ProviderVehicle, ProviderVehicleType}; use crate::app::ThreadSafeDNSResolver; use crate::common::errors::map_io_error; -use crate::common::http::{new_http_client, HttpClient, LocalConnector}; +use crate::common::http::{new_http_client, HttpClient}; use async_trait::async_trait; diff --git a/clash_lib/src/app/proxy_manager/providers/mod.rs b/clash_lib/src/app/proxy_manager/providers/mod.rs index 285a9a3e..f70249f6 100644 --- a/clash_lib/src/app/proxy_manager/providers/mod.rs +++ b/clash_lib/src/app/proxy_manager/providers/mod.rs @@ -5,11 +5,11 @@ use std::sync::Arc; use tokio::sync::Mutex; pub mod fether; -mod file_vehicle; -mod http_vehicle; -mod plain_provider; +pub mod file_vehicle; +pub mod http_vehicle; +pub mod plain_provider; pub mod proxy_provider; -mod proxy_set_provider; +pub mod proxy_set_provider; pub mod rule_provider; #[cfg(test)] diff --git a/clash_lib/src/app/proxy_manager/providers/plain_provider.rs b/clash_lib/src/app/proxy_manager/providers/plain_provider.rs index e860f83f..2d8bc0d7 100644 --- a/clash_lib/src/app/proxy_manager/providers/plain_provider.rs +++ b/clash_lib/src/app/proxy_manager/providers/plain_provider.rs @@ -11,7 +11,7 @@ use crate::{ use super::{proxy_provider::ProxyProvider, Provider, ProviderType, ProviderVehicleType}; -struct PlainProvider { +pub struct PlainProvider { name: String, proxies: Vec, healthcheck: HealthCheck, diff --git a/clash_lib/src/app/proxy_manager/providers/proxy_set_provider.rs b/clash_lib/src/app/proxy_manager/providers/proxy_set_provider.rs index 32d5da63..7b2cc6e5 100644 --- a/clash_lib/src/app/proxy_manager/providers/proxy_set_provider.rs +++ b/clash_lib/src/app/proxy_manager/providers/proxy_set_provider.rs @@ -28,7 +28,7 @@ struct FileProviderInner { proxies: Vec, } -struct ProxySetProvider { +pub struct ProxySetProvider { fetcher: Fetcher< Box) + Send + Sync + 'static>, Box anyhow::Result> + Send + Sync + 'static>, diff --git a/clash_lib/src/config/internal/proxy.rs b/clash_lib/src/config/internal/proxy.rs index 05d950ef..57f728b0 100644 --- a/clash_lib/src/config/internal/proxy.rs +++ b/clash_lib/src/config/internal/proxy.rs @@ -191,6 +191,13 @@ pub struct OutboundGroupRelay { pub proxies: Option>, #[serde(rename = "use")] pub use_provider: Option>, + + // hc + pub url: String, + #[serde(deserialize_with = "utils::deserialize_u64")] + pub interval: u64, + pub tolerance: Option, + pub lazy: Option, } #[derive(serde::Serialize, serde::Deserialize, Debug)] @@ -271,6 +278,7 @@ pub struct OutboundFileProvider { #[serde(skip)] pub name: String, pub path: String, + pub interval: Option, pub health_check: HealthCheck, } diff --git a/clash_lib/src/lib.rs b/clash_lib/src/lib.rs index afb6a2f3..1150e066 100644 --- a/clash_lib/src/lib.rs +++ b/clash_lib/src/lib.rs @@ -106,25 +106,29 @@ async fn start_async(opts: Options) -> Result<(), Error> { let default_dns_resolver = Arc::new(dns::Resolver::new(config.dns).await); - let outbound_manager = Arc::new(RwLock::new(OutboundManager::new( - config - .proxies - .into_values() - .filter_map(|x| match x { - OutboundProxy::ProxyServer(s) => Some(s), - _ => None, - }) - .collect(), - config - .proxy_groups - .into_values() - .filter_map(|x| match x { - OutboundProxy::ProxyGroup(g) => Some(g), - _ => None, - }) - .collect(), - default_dns_resolver.clone(), - )?)); + let outbound_manager = Arc::new(RwLock::new( + OutboundManager::new( + config + .proxies + .into_values() + .filter_map(|x| match x { + OutboundProxy::ProxyServer(s) => Some(s), + _ => None, + }) + .collect(), + config + .proxy_groups + .into_values() + .filter_map(|x| match x { + OutboundProxy::ProxyGroup(g) => Some(g), + _ => None, + }) + .collect(), + config.proxy_providers, + default_dns_resolver.clone(), + ) + .await?, + )); let router = Arc::new(RwLock::new( Router::new( diff --git a/clash_lib/src/proxy/converters/mod.rs b/clash_lib/src/proxy/converters/mod.rs index 481774dc..b7eb4e7b 100644 --- a/clash_lib/src/proxy/converters/mod.rs +++ b/clash_lib/src/proxy/converters/mod.rs @@ -1,2 +1 @@ -pub mod relay; pub mod shadowsocks; diff --git a/clash_lib/src/proxy/converters/relay.rs b/clash_lib/src/proxy/converters/relay.rs deleted file mode 100644 index 340d952b..00000000 --- a/clash_lib/src/proxy/converters/relay.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{ - config::internal::proxy::OutboundGroupRelay, - proxy::{ - relay::{Handler, HandlerOptions}, - AnyOutboundHandler, CommonOption, - }, -}; - -impl TryFrom for AnyOutboundHandler { - type Error = crate::Error; - - fn try_from(value: OutboundGroupRelay) -> Result { - (&value).try_into() - } -} - -impl TryFrom<&OutboundGroupRelay> for AnyOutboundHandler { - type Error = crate::Error; - - fn try_from(value: &OutboundGroupRelay) -> Result { - Ok(Handler::new( - HandlerOptions { - name: value.name.to_owned(), - common_opts: CommonOption::default(), - }, - vec![], - )) - } -} diff --git a/clash_lib/src/proxy/mod.rs b/clash_lib/src/proxy/mod.rs index 5c714e2a..147c8c32 100644 --- a/clash_lib/src/proxy/mod.rs +++ b/clash_lib/src/proxy/mod.rs @@ -26,7 +26,7 @@ pub mod utils; pub mod converters; // proxy groups -mod relay; +pub mod relay; mod transport; diff --git a/clash_lib/src/proxy/relay/mod.rs b/clash_lib/src/proxy/relay/mod.rs index f80ac374..43727202 100644 --- a/clash_lib/src/proxy/relay/mod.rs +++ b/clash_lib/src/proxy/relay/mod.rs @@ -13,6 +13,7 @@ use crate::{ use super::{AnyOutboundDatagram, AnyOutboundHandler, AnyStream, CommonOption, OutboundHandler}; +#[derive(Default)] pub struct HandlerOptions { pub name: String, pub common_opts: CommonOption,