Skip to content

Commit

Permalink
some progress
Browse files Browse the repository at this point in the history
  • Loading branch information
ibigbug committed Aug 26, 2023
1 parent a8d78d6 commit 7ecfaa9
Show file tree
Hide file tree
Showing 15 changed files with 334 additions and 22 deletions.
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ Just a toy for fun, don't use please :+|

TODOs
- [x] proxy rules (relay, select, ...)
- [ ] dashboard
- [ ] DNS server
- [ ] trojan proto
- [ ] hc and fetchers
- [ ] authentication
- [ ] other protos and exts
- [ ] trojan
- [ ] socks5 + tls
- [ ] grpc/tls/ws/http transport
- [ ] DNS
- [ ] server
- [ ] HTTP query
- [x] hc and fetchers
- [ ] API - connections
- [ ] linux support
- [ ] fwmark and bind interface impl
22 changes: 22 additions & 0 deletions clash_lib/src/app/api/handlers/dns.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use std::sync::Arc;

use axum::{response::IntoResponse, routing::get, Router};
use http::StatusCode;

use crate::app::{api::AppState, ThreadSafeDNSResolver};

#[derive(Clone)]
struct DNSState {
resolver: ThreadSafeDNSResolver,
}

pub fn routes(resolver: ThreadSafeDNSResolver) -> Router<Arc<AppState>> {
let state = DNSState { resolver };
Router::new()
.route("/dns", get(query_dns))
.with_state(state)
}

async fn query_dns() -> impl IntoResponse {
StatusCode::NOT_IMPLEMENTED
}
2 changes: 2 additions & 0 deletions clash_lib/src/app/api/handlers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
pub mod config;
pub mod connection;
pub mod dns;
pub mod hello;
pub mod log;
pub mod provider;
pub mod proxy;
pub mod rule;
pub mod version;
181 changes: 181 additions & 0 deletions clash_lib/src/app/api/handlers/provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
use std::{collections::HashMap, sync::Arc, time::Duration};

use axum::{
extract::{Path, Query, State},
http::Request,
http::StatusCode,
middleware::{self, Next},
response::{IntoResponse, Response},
routing::get,
Extension, Router,
};
use serde::Deserialize;

use crate::app::{api::AppState, outbound::manager::ThreadSafeOutboundManager};
use crate::{
app::proxy_manager::providers::proxy_provider::ThreadSafeProxyProvider,
proxy::AnyOutboundHandler,
};
#[derive(Clone)]
struct ProviderState {
outbound_manager: ThreadSafeOutboundManager,
}

pub fn routes(outbound_manager: ThreadSafeOutboundManager) -> Router<Arc<AppState>> {
let state = ProviderState { outbound_manager };
Router::new()
.route("/", get(get_providers))
.nest(
"/:provider_name",
Router::new()
.route("/", get(get_provider).put(update_provider))
.route("/healthcheck", get(provider_healthcheck))
.nest(
"/:proxy_name",
Router::new()
.route("/", get(get_proxy))
.route("/healthcheck", get(get_proxy_delay))
.layer(middleware::from_fn_with_state(
state.clone(),
find_provider_proxy_by_name,
))
.with_state(state.clone()),
)
.layer(middleware::from_fn_with_state(
state.clone(),
find_proxy_provider_by_name,
))
.with_state(state.clone()),
)
.with_state(state)
}

async fn get_providers(State(state): State<ProviderState>) -> impl IntoResponse {
let outbound_manager = state.outbound_manager.read().await;
let mut res = HashMap::new();

let mut providers = HashMap::new();

for (name, p) in outbound_manager.get_proxy_providers() {
let m = p.lock().await.as_map().await;
providers.insert(name, m);
}

res.insert("providers".to_owned(), providers);
axum::response::Json(res)
}

async fn find_proxy_provider_by_name<B>(
State(state): State<ProviderState>,
Path(name): Path<String>,
mut req: Request<B>,
next: Next<B>,
) -> Response {
let outbound_manager = state.outbound_manager.read().await;
if let Some(provider) = outbound_manager.get_proxy_provider(&name) {
req.extensions_mut().insert(provider);
next.run(req).await
} else {
(
StatusCode::NOT_FOUND,
format!("proxy provider {} not found", name),
)
.into_response()
}
}

async fn get_provider(
Extension(provider): Extension<ThreadSafeProxyProvider>,
) -> impl IntoResponse {
let provider = provider.lock().await;
axum::response::Json(provider.as_map().await)
}

async fn update_provider(
Extension(provider): Extension<ThreadSafeProxyProvider>,
) -> impl IntoResponse {
let provider = provider.lock().await;
match provider.update().await {
Ok(_) => (StatusCode::ACCEPTED, "provider update started").into_response(),
Err(err) => (
StatusCode::INTERNAL_SERVER_ERROR,
format!(
"update proxy provider {} failed with error {}",
provider.name(),
err.to_string()
),
)
.into_response(),
}
}

async fn provider_healthcheck(
Extension(provider): Extension<ThreadSafeProxyProvider>,
) -> impl IntoResponse {
let provider = provider.lock().await;
provider.healthcheck().await;
(StatusCode::ACCEPTED, "provider healthcheck started")
}

async fn find_provider_proxy_by_name<B>(
Extension(provider): Extension<ThreadSafeProxyProvider>,
Path(params): Path<HashMap<String, String>>,
mut req: Request<B>,
next: Next<B>,
) -> Response {
let proxy = provider.lock().await.proxies().await;
let proxy = proxy
.iter()
.find(|x| Some(&x.name().to_string()) == params.get("proxy_name"));

if let Some(proxy) = proxy {
req.extensions_mut().insert(proxy.clone());
next.run(req).await
} else {
(
StatusCode::NOT_FOUND,
format!(
"proxy {} not found in provider {}",
params.get("proxy_name").unwrap(),
provider.lock().await.name()
),
)
.into_response()
}
}

async fn get_proxy(
Extension(proxy): Extension<AnyOutboundHandler>,
State(state): State<ProviderState>,
) -> impl IntoResponse {
let outbound_manager = state.outbound_manager.read().await;
axum::response::Json(outbound_manager.get_proxy(&proxy).await)
}

#[derive(Deserialize)]
struct DelayRequest {
url: String,
timeout: u16,
}
async fn get_proxy_delay(
State(state): State<ProviderState>,
Extension(proxy): Extension<AnyOutboundHandler>,
Query(q): Query<DelayRequest>,
) -> impl IntoResponse {
let outbound_manager = state.outbound_manager.read().await;
let timeout = Duration::from_millis(q.timeout.into());
let n = proxy.name().to_owned();
match outbound_manager.url_test(proxy, &q.url, timeout).await {
Ok((delay, mean_delay)) => {
let mut r = HashMap::new();
r.insert("delay".to_owned(), delay);
r.insert("meanDelay".to_owned(), mean_delay);
axum::response::Json(delay).into_response()
}
Err(err) => (
StatusCode::BAD_REQUEST,
format!("get delay for {} failed with error: {}", n, err),
)
.into_response(),
}
}
2 changes: 1 addition & 1 deletion clash_lib/src/app/api/handlers/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ async fn find_proxy_by_name<B>(
next: Next<B>,
) -> Response {
let outbound_manager = state.outbound_manager.read().await;
if let Some(proxy) = outbound_manager.get(&name) {
if let Some(proxy) = outbound_manager.get_outbound(&name) {
req.extensions_mut().insert(proxy);
next.run(req).await
} else {
Expand Down
12 changes: 10 additions & 2 deletions clash_lib/src/app/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,20 @@ pub fn get_api_runner(
inbound_manager,
dispatcher,
global_state,
dns_resolver,
dns_resolver.clone(),
),
)
.nest("/rules", handlers::rule::routes(router))
.nest("/proxies", handlers::proxy::routes(outbound_manager))
.nest(
"/proxies",
handlers::proxy::routes(outbound_manager.clone()),
)
.nest("/connections", handlers::connection::routes())
.nest(
"/providers/proxies",
handlers::provider::routes(outbound_manager),
)
.nest("/dns", handlers::dns::routes(dns_resolver))
.layer(middlewares::auth::AuthMiddlewareLayer::new(
controller_cfg.secret.unwrap_or_default(),
))
Expand Down
4 changes: 2 additions & 2 deletions clash_lib/src/app/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ impl Dispatcher {
.outbound_manager
.read()
.await
.get(outbound_name.as_str())
.get_outbound(outbound_name.as_str())
.expect(format!("unknown rule: {}", outbound_name).as_str()); // should never happen

match handler.connect_stream(&sess, self.resolver.clone()).await {
Expand Down Expand Up @@ -140,7 +140,7 @@ impl Dispatcher {
let handler = outbound_manager
.read()
.await
.get(outbound_name.as_str())
.get_outbound(outbound_name.as_str())
.expect(format!("unknown rule: {}", outbound_name).as_str());

let mut outbound_handle_guard = outbound_handle_guard.lock().await;
Expand Down
11 changes: 11 additions & 0 deletions clash_lib/src/app/dns/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ use super::{

static TTL: Duration = Duration::from_secs(60);

pub enum ResolverKind {
Clash,
System,
}

/// A implementation of "anti-poisoning" Resolver
/// it can hold multiple clients in different protocols
/// each client can also hold a "default_resolver"
Expand All @@ -39,6 +44,8 @@ pub trait ClashResolver: Sync + Send {

fn ipv6(&self) -> bool;
fn set_ipv6(&self, enable: bool);

fn kind(&self) -> ResolverKind;
}

pub struct Resolver {
Expand Down Expand Up @@ -277,6 +284,10 @@ impl ClashResolver for Resolver {
fn set_ipv6(&self, enable: bool) {
self.ipv6.store(enable, Relaxed);
}

fn kind(&self) -> ResolverKind {
ResolverKind::Clash
}
}

impl Resolver {
Expand Down
8 changes: 6 additions & 2 deletions clash_lib/src/app/dns/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use async_trait::async_trait;
use rand::seq::IteratorRandom;
use trust_dns_resolver::TokioAsyncResolver;

use super::ClashResolver;
use super::{resolver::ResolverKind, ClashResolver};

pub(crate) struct SystemResolver {
resolver: TokioAsyncResolver,
Expand Down Expand Up @@ -47,7 +47,11 @@ impl ClashResolver for SystemResolver {
true
}

fn set_ipv6(&self, enabled: bool) {
fn set_ipv6(&self, _: bool) {
// NOOP
}

fn kind(&self) -> ResolverKind {
ResolverKind::System
}
}
16 changes: 13 additions & 3 deletions clash_lib/src/app/outbound/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use super::utils::proxy_groups_dag_sort;

pub struct OutboundManager {
handlers: HashMap<String, AnyOutboundHandler>,
proxy_providers: HashMap<String, ThreadSafeProxyProvider>,
proxy_manager: ThreadSafeProxyManager,
selector_control: HashMap<String, ThreadSafeSelectorControl>,
}
Expand Down Expand Up @@ -67,7 +68,7 @@ impl OutboundManager {
outbounds,
outbound_groups,
proxy_manager.clone(),
provider_registry,
&mut provider_registry,
&mut handlers,
&mut selector_control,
)
Expand All @@ -77,13 +78,18 @@ impl OutboundManager {
handlers,
proxy_manager,
selector_control,
proxy_providers: provider_registry,
})
}

pub fn get(&self, name: &str) -> Option<AnyOutboundHandler> {
pub fn get_outbound(&self, name: &str) -> Option<AnyOutboundHandler> {
self.handlers.get(name).map(Clone::clone)
}

pub fn get_proxy_provider(&self, name: &str) -> Option<ThreadSafeProxyProvider> {
self.proxy_providers.get(name).map(Clone::clone)
}

// API handles start
pub fn get_selector_control(&self, name: &str) -> Option<ThreadSafeSelectorControl> {
self.selector_control.get(name).map(Clone::clone)
Expand Down Expand Up @@ -144,13 +150,17 @@ impl OutboundManager {
proxy_manager.url_test(proxy, url, Some(timeout)).await
}

pub fn get_proxy_providers(&self) -> HashMap<String, ThreadSafeProxyProvider> {
self.proxy_providers.clone()
}

// API handlers end

async fn load_handlers(
outbounds: Vec<OutboundProxyProtocol>,
outbound_groups: Vec<OutboundGroupProtocol>,
proxy_manager: ThreadSafeProxyManager,
provider_registry: HashMap<String, ThreadSafeProxyProvider>,
provider_registry: &mut HashMap<String, ThreadSafeProxyProvider>,
handlers: &mut HashMap<String, AnyOutboundHandler>,
selector_control: &mut HashMap<String, ThreadSafeSelectorControl>,
) -> Result<(), Error> {
Expand Down
Loading

0 comments on commit 7ecfaa9

Please sign in to comment.