1use axum::{
2 extract::ws::{Message, WebSocketUpgrade},
3 extract::{Path, Query, Request, State},
4 http::{header, StatusCode},
5 middleware::{self, Next},
6 response::{IntoResponse, Json, Response},
7 routing::{delete, get, post, put},
8 Router,
9};
10use dashmap::DashMap;
11use meow_common::{Proxy, TunnelMode};
12use meow_config::{
13 proxy_provider::ProxyProvider,
14 raw::{RawConfig, RawProxyGroup, RawSubscription},
15 rule_provider::RuleProvider,
16 NamedListener,
17};
18use meow_tunnel::Tunnel;
19use parking_lot::RwLock;
20use serde::{Deserialize, Serialize};
21use std::collections::{BTreeMap, HashMap};
22use std::sync::Arc;
23use std::time::Duration;
24use tokio::sync::broadcast;
25use tower_http::cors::CorsLayer;
26use tracing::{debug, info, warn};
27
28use crate::log_stream::{parse_log_level, LogMessage};
29use crate::ui;
30
31pub struct AppState {
32 pub tunnel: Tunnel,
33 pub secret: Option<String>,
35 pub config_path: String,
36 pub raw_config: Arc<RwLock<RawConfig>>,
37 pub log_tx: broadcast::Sender<LogMessage>,
39 pub proxy_providers: Arc<DashMap<String, Arc<ProxyProvider>>>,
41 pub rule_providers: Arc<RwLock<HashMap<String, Arc<RuleProvider>>>>,
42 pub listeners: Vec<NamedListener>,
44 pub external_ui: Option<std::path::PathBuf>,
47}
48
49impl AppState {
50 fn auth_required(&self) -> bool {
51 self.secret.as_deref().is_some_and(|s| !s.is_empty())
52 }
53}
54
55async fn require_auth(State(state): State<Arc<AppState>>, req: Request, next: Next) -> Response {
60 if !state.auth_required() {
61 return next.run(req).await;
62 }
63
64 let Some(expected) = state.secret.as_deref() else {
65 return next.run(req).await;
66 };
67
68 let provided = req
69 .headers()
70 .get(header::AUTHORIZATION)
71 .and_then(|v| v.to_str().ok())
72 .and_then(|v| {
73 v.strip_prefix("Bearer ")
74 .or_else(|| v.strip_prefix("bearer "))
75 });
76
77 let ok = match provided {
81 Some(token) if token.len() == expected.len() => {
82 use subtle::ConstantTimeEq;
83 token.as_bytes().ct_eq(expected.as_bytes()).into()
84 }
85 _ => false,
86 };
87 if ok {
88 next.run(req).await
89 } else {
90 (StatusCode::UNAUTHORIZED, "unauthorized").into_response()
91 }
92}
93
94async fn require_auth_ws(
98 State(state): State<Arc<AppState>>,
99 Query(query): Query<HashMap<String, String>>,
100 req: Request,
101 next: Next,
102) -> Response {
103 if !state.auth_required() {
104 return next.run(req).await;
105 }
106 let expected = state.secret.as_deref().unwrap_or("");
107
108 let bearer = req
109 .headers()
110 .get(header::AUTHORIZATION)
111 .and_then(|v| v.to_str().ok())
112 .and_then(|v| {
113 v.strip_prefix("Bearer ")
114 .or_else(|| v.strip_prefix("bearer "))
115 });
116
117 let token_param = query.get("token").map(std::string::String::as_str);
118 let provided = bearer.or(token_param);
119
120 let ok = match provided {
121 Some(t) if t.len() == expected.len() => {
122 use subtle::ConstantTimeEq;
123 t.as_bytes().ct_eq(expected.as_bytes()).into()
124 }
125 _ => false,
126 };
127 if ok {
128 next.run(req).await
129 } else {
130 (StatusCode::UNAUTHORIZED, "unauthorized").into_response()
131 }
132}
133
134pub fn create_router(state: Arc<AppState>) -> Router {
135 let ws_routes = Router::new()
137 .route("/logs", get(get_logs))
138 .route("/memory", get(get_memory))
139 .route_layer(middleware::from_fn_with_state(
140 Arc::clone(&state),
141 require_auth_ws,
142 ));
143
144 let api = Router::new()
146 .route("/", get(hello))
147 .route("/version", get(version))
148 .route("/proxies", get(get_proxies))
149 .route("/proxies/{name}", get(get_proxy).put(update_proxy))
150 .route("/proxies/{name}/delay", get(get_proxy_delay))
151 .route("/group/{name}/delay", get(get_group_delay))
152 .route(
153 "/rules",
154 get(get_rules).post(replace_rules).put(update_rule_at_index),
155 )
156 .route("/rules/{index}", delete(delete_rule))
157 .route("/rules/reorder", post(reorder_rules))
158 .route("/connections", get(get_connections))
159 .route("/connections/{id}", delete(close_connection))
160 .route("/connections", delete(close_all_connections))
161 .route(
162 "/configs",
163 get(get_configs).patch(update_configs).put(put_configs),
164 )
165 .route("/metrics", get(get_metrics))
166 .route("/traffic", get(get_traffic))
167 .route("/dns/query", get(dns_query_get).post(dns_query))
168 .route("/cache/dns/flush", post(flush_dns_cache))
169 .route("/cache/fakeip/flush", post(flush_fakeip_cache))
170 .route("/api/config/save", post(save_config))
172 .route(
174 "/api/subscriptions",
175 get(get_subscriptions).post(add_subscription),
176 )
177 .route("/api/subscriptions/{name}", delete(delete_subscription))
178 .route(
179 "/api/subscriptions/{name}/refresh",
180 post(refresh_subscription),
181 )
182 .route(
184 "/api/proxy-groups",
185 get(get_proxy_groups).post(create_proxy_group),
186 )
187 .route(
188 "/api/proxy-groups/{name}",
189 put(update_proxy_group).delete(delete_proxy_group),
190 )
191 .route(
192 "/api/proxy-groups/{name}/select",
193 put(select_proxy_in_group),
194 )
195 .route("/providers/proxies", get(get_providers))
197 .route(
198 "/providers/proxies/{name}",
199 get(get_provider).put(refresh_provider),
200 )
201 .route(
202 "/providers/proxies/{name}/healthcheck",
203 get(provider_healthcheck),
204 )
205 .route("/providers/rules", get(get_rule_providers))
207 .route(
208 "/providers/rules/{name}",
209 get(get_rule_provider).put(refresh_rule_provider),
210 )
211 .route("/listeners", get(get_listeners))
213 .route_layer(middleware::from_fn_with_state(
214 Arc::clone(&state),
215 require_auth,
216 ));
217
218 let router = api.merge(ws_routes);
225 let router = if let Some(dir) = state.external_ui.clone() {
226 router.nest_service("/ui", tower_http::services::ServeDir::new(dir))
231 } else {
232 router
233 .route("/ui", get(ui::serve_ui))
234 .route("/ui/{*rest}", get(ui::serve_ui))
235 };
236
237 router.layer(CorsLayer::permissive()).with_state(state)
238}
239
240async fn hello() -> &'static str {
243 "meow-rs"
244}
245
246#[derive(Serialize)]
247struct VersionResponse {
248 version: String,
249 meta: bool,
250}
251
252async fn version() -> Json<VersionResponse> {
253 Json(VersionResponse {
254 version: env!("CARGO_PKG_VERSION").to_string(),
255 meta: true,
256 })
257}
258
259#[derive(Serialize)]
260struct ProxyInfo {
261 name: String,
262 #[serde(rename = "type")]
263 proxy_type: String,
264 alive: bool,
265 history: Vec<meow_common::DelayHistory>,
266 udp: bool,
267 #[serde(skip_serializing_if = "Option::is_none")]
269 all: Option<Vec<String>>,
270 #[serde(skip_serializing_if = "Option::is_none")]
272 now: Option<String>,
273}
274
275impl ProxyInfo {
276 fn from_proxy(proxy: &Arc<dyn meow_common::Proxy>) -> Self {
277 let members = proxy.members();
278 let current = proxy.current();
279 debug!(
280 name = proxy.name(),
281 proxy_type = %proxy.adapter_type(),
282 member_count = members.as_ref().map(std::vec::Vec::len),
283 current = ?current,
284 "building ProxyInfo",
285 );
286 Self {
287 name: proxy.name().to_string(),
288 proxy_type: proxy.adapter_type().to_string(),
289 alive: proxy.alive(),
290 history: proxy.delay_history(),
291 udp: proxy.support_udp(),
292 all: members,
293 now: current,
294 }
295 }
296}
297
298#[derive(Serialize)]
299struct ProxiesResponse {
300 proxies: std::collections::HashMap<String, ProxyInfo>,
301}
302
303async fn get_proxies(State(state): State<Arc<AppState>>) -> Json<ProxiesResponse> {
304 let route = state.tunnel.route_snapshot();
305 let mut result = std::collections::HashMap::new();
306 for (name, proxy) in &route.proxies {
307 result.insert(name.to_string(), ProxyInfo::from_proxy(proxy));
308 }
309 Json(ProxiesResponse { proxies: result })
310}
311
312async fn get_proxy(
313 State(state): State<Arc<AppState>>,
314 Path(name): Path<String>,
315) -> Result<Json<ProxyInfo>, StatusCode> {
316 let route = state.tunnel.route_snapshot();
317 let proxy = route
318 .proxies
319 .get(name.as_str())
320 .ok_or(StatusCode::NOT_FOUND)?;
321 Ok(Json(ProxyInfo::from_proxy(proxy)))
322}
323
324#[derive(Deserialize)]
325struct UpdateProxyRequest {
326 name: String,
327}
328
329async fn update_proxy(
330 State(state): State<Arc<AppState>>,
331 Path(group_name): Path<String>,
332 Json(body): Json<UpdateProxyRequest>,
333) -> StatusCode {
334 let route = state.tunnel.route_snapshot();
335 let Some(proxy) = route.proxies.get(group_name.as_str()).cloned() else {
336 return StatusCode::NOT_FOUND;
337 };
338 match select_proxy_member_async(proxy, body.name.clone()).await {
339 Ok(Some(true)) => {
340 info!("Selector '{}' switched to '{}'", group_name, body.name);
341 StatusCode::NO_CONTENT
342 }
343 Ok(Some(false)) => StatusCode::BAD_REQUEST,
344 Ok(None) => StatusCode::NOT_FOUND,
345 Err(e) => {
346 warn!("Selector '{}' update task failed: {}", group_name, e);
347 StatusCode::INTERNAL_SERVER_ERROR
348 }
349 }
350}
351
352#[derive(Serialize)]
353struct RuleInfo<'a> {
354 #[serde(rename = "type")]
355 rule_type: &'static str,
356 payload: &'a str,
357 proxy: &'a str,
358}
359
360#[derive(Serialize)]
361struct RulesResponse<'a> {
362 rules: Vec<RuleInfo<'a>>,
363}
364
365async fn get_rules(State(state): State<Arc<AppState>>) -> Response {
366 let route = state.tunnel.route_snapshot();
369 let result: Vec<RuleInfo> = route
370 .rules
371 .iter()
372 .map(|r| RuleInfo {
373 rule_type: r.rule_type().as_str(),
374 payload: r.payload(),
375 proxy: r.adapter(),
376 })
377 .collect();
378 Json(RulesResponse { rules: result }).into_response()
379}
380
381#[derive(Serialize)]
382struct ConnectionsResponse<'a> {
383 upload_total: i64,
384 download_total: i64,
385 connections: meow_tunnel::statistics::ActiveConnectionsView<'a>,
390}
391
392async fn get_connections(State(state): State<Arc<AppState>>) -> Response {
393 let stats = state.tunnel.statistics();
394 let (up, down) = stats.snapshot();
395 Json(ConnectionsResponse {
396 upload_total: up,
397 download_total: down,
398 connections: stats.active_connections_view(),
399 })
400 .into_response()
401}
402
403async fn close_connection(
404 State(state): State<Arc<AppState>>,
405 Path(id): Path<String>,
406) -> StatusCode {
407 match uuid::Uuid::parse_str(&id) {
408 Ok(uuid) => {
409 state.tunnel.statistics().close_connection(uuid);
410 StatusCode::NO_CONTENT
411 }
412 Err(_) => StatusCode::BAD_REQUEST,
413 }
414}
415
416#[derive(Serialize)]
417struct ConfigResponse {
418 mode: String,
419 #[serde(rename = "log-level")]
420 log_level: String,
421 #[serde(rename = "mixed-port", skip_serializing_if = "Option::is_none")]
422 mixed_port: Option<u16>,
423 #[serde(rename = "socks-port", skip_serializing_if = "Option::is_none")]
424 socks_port: Option<u16>,
425 #[serde(rename = "port", skip_serializing_if = "Option::is_none")]
426 http_port: Option<u16>,
427 #[serde(
428 rename = "external-controller",
429 skip_serializing_if = "Option::is_none"
430 )]
431 external_controller: Option<String>,
432}
433
434async fn get_configs(State(state): State<Arc<AppState>>) -> Json<ConfigResponse> {
435 let raw = state.raw_config.read();
436 Json(ConfigResponse {
437 mode: state.tunnel.mode().to_string(),
438 log_level: "info".to_string(),
439 mixed_port: raw.mixed_port,
440 socks_port: raw.socks_port,
441 http_port: raw.port,
442 external_controller: raw.external_controller.clone(),
443 })
444}
445
446#[derive(Deserialize)]
447struct UpdateConfigRequest {
448 mode: Option<String>,
449 #[serde(rename = "log-level")]
450 log_level: Option<String>,
451}
452
453async fn update_configs(
454 State(state): State<Arc<AppState>>,
455 Json(body): Json<UpdateConfigRequest>,
456) -> StatusCode {
457 if let Some(mode_str) = body.mode {
458 match mode_str.parse::<TunnelMode>() {
459 Ok(mode) => {
460 state.tunnel.set_mode(mode);
461 info!("Mode changed to {}", mode);
462 }
463 Err(_) => return StatusCode::BAD_REQUEST,
464 }
465 }
466 let _ = body.log_level;
467 StatusCode::NO_CONTENT
468}
469
470#[derive(Serialize)]
471struct TrafficResponse {
472 up: i64,
473 down: i64,
474}
475
476async fn get_traffic(State(state): State<Arc<AppState>>) -> Json<TrafficResponse> {
477 let (up, down) = state.tunnel.statistics().snapshot();
478 Json(TrafficResponse { up, down })
479}
480
481#[derive(Deserialize)]
482struct DnsQueryRequest {
483 name: String,
484 #[serde(rename = "type")]
485 qtype: Option<String>,
486}
487
488async fn dns_query(
489 State(state): State<Arc<AppState>>,
490 Json(body): Json<DnsQueryRequest>,
491) -> Json<serde_json::Value> {
492 let resolver = state.tunnel.resolver();
493 let result = resolver.resolve_ip(&body.name).await;
494 let _ = body.qtype;
495 Json(serde_json::json!({ "name": body.name, "answer": result.map(|ip| ip.to_string()) }))
496}
497
498async fn dns_query_get(
501 State(state): State<Arc<AppState>>,
502 Query(params): Query<DnsQueryRequest>,
503) -> Json<serde_json::Value> {
504 let resolver = state.tunnel.resolver();
505 let result = resolver.resolve_ip(¶ms.name).await;
506 Json(serde_json::json!({ "name": params.name, "answer": result.map(|ip| ip.to_string()) }))
507}
508
509async fn flush_dns_cache(State(state): State<Arc<AppState>>) -> StatusCode {
510 state.tunnel.resolver().clear_cache();
511 StatusCode::NO_CONTENT
512}
513
514async fn flush_fakeip_cache(
518 State(state): State<Arc<AppState>>,
519) -> Result<StatusCode, (StatusCode, Json<serde_json::Value>)> {
520 match state.tunnel.resolver().flush_fake_ip() {
521 Ok(()) => Ok(StatusCode::NO_CONTENT),
522 Err(e) => Err((
523 StatusCode::BAD_REQUEST,
524 Json(serde_json::json!({ "message": e.to_string() })),
525 )),
526 }
527}
528
529async fn close_all_connections(State(state): State<Arc<AppState>>) -> StatusCode {
530 state.tunnel.statistics().close_all_connections();
531 StatusCode::NO_CONTENT
532}
533
534async fn save_config(
537 State(state): State<Arc<AppState>>,
538) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
539 let raw = state.raw_config.read().clone();
540 meow_config::save_raw_config_async(&state.config_path, &raw)
541 .await
542 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
543 Ok(Json(serde_json::json!({"message": "config saved"})))
544}
545
546async fn apply_raw_to_tunnel(
553 mut raw: RawConfig,
554 tunnel: &Tunnel,
555) -> Result<(), (StatusCode, String)> {
556 if let Some(ps) = raw.proxies.as_mut() {
557 meow_config::ech_dns::preresolve_ech(ps).await;
558 }
559 let (proxies, rules) = rebuild_from_raw_with_resolver_async(raw, Arc::clone(tunnel.resolver()))
560 .await
561 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e))?;
562 tunnel.update_proxies(proxies);
563 tunnel.update_rules(rules);
564 Ok(())
565}
566
567async fn rebuild_from_raw_with_resolver_async(
568 raw: RawConfig,
569 resolver: Arc<meow_dns::Resolver>,
570) -> Result<meow_config::RebuildResult, String> {
571 tokio::task::spawn_blocking(move || {
572 meow_config::rebuild_from_raw_with_resolver(&raw, Some(resolver))
573 })
574 .await
575 .map_err(|e| format!("config rebuild task failed: {e}"))?
576 .map_err(|e| e.to_string())
577}
578
579async fn select_proxy_member_async(
580 proxy: Arc<dyn Proxy>,
581 member: String,
582) -> Result<Option<bool>, tokio::task::JoinError> {
583 tokio::task::spawn_blocking(move || {
584 use meow_proxy::SelectorGroup;
585 proxy
586 .as_any()
587 .and_then(|a| a.downcast_ref::<SelectorGroup>())
588 .map(|selector| selector.select(&member))
589 })
590 .await
591}
592
593#[derive(Serialize)]
597struct SubscriptionInfo {
598 name: String,
599 url: String,
600 interval: Option<u64>,
601 last_updated: Option<i64>,
602 proxy_count: usize,
603 group_count: usize,
604 rule_count: usize,
605}
606
607async fn get_subscriptions(State(state): State<Arc<AppState>>) -> Json<Vec<SubscriptionInfo>> {
608 let raw = state.raw_config.read();
609 let subs = raw.subscriptions.as_deref().unwrap_or(&[]);
610 let result: Vec<SubscriptionInfo> = subs
611 .iter()
612 .map(|s| SubscriptionInfo {
613 name: s.name.clone(),
614 url: s.url.clone(),
615 interval: s.interval,
616 last_updated: s.last_updated,
617 proxy_count: raw.proxies.as_ref().map_or(0, std::vec::Vec::len),
618 group_count: raw.proxy_groups.as_ref().map_or(0, std::vec::Vec::len),
619 rule_count: raw.rules.as_ref().map_or(0, std::vec::Vec::len),
620 })
621 .collect();
622 Json(result)
623}
624
625#[derive(Deserialize)]
626struct AddSubscriptionRequest {
627 name: String,
628 url: String,
629 interval: Option<u64>,
630}
631
632async fn add_subscription(
633 State(state): State<Arc<AppState>>,
634 Json(body): Json<AddSubscriptionRequest>,
635) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
636 let fetched = meow_config::subscription::fetch_subscription(&body.url)
637 .await
638 .map_err(|e| (StatusCode::BAD_REQUEST, format!("fetch failed: {e}")))?;
639
640 let now = std::time::SystemTime::now()
641 .duration_since(std::time::UNIX_EPOCH)
642 .unwrap_or_default()
643 .as_secs() as i64;
644
645 let pc = fetched.proxies.len();
646 let gc = fetched.proxy_groups.len();
647 let rc = fetched.rules.len();
648
649 let snapshot = {
650 let mut raw = state.raw_config.write();
651
652 if let Some(ref subs) = raw.subscriptions {
653 if subs.iter().any(|s| s.name == body.name) {
654 return Err((
655 StatusCode::CONFLICT,
656 "subscription name already exists".into(),
657 ));
658 }
659 }
660
661 let sub = RawSubscription {
662 name: body.name.clone(),
663 url: body.url.clone(),
664 interval: body.interval,
665 last_updated: Some(now),
666 };
667 raw.subscriptions.get_or_insert_with(Vec::new).push(sub);
668
669 raw.proxies = Some(fetched.proxies);
671 raw.proxy_groups = Some(fetched.proxy_groups);
672 raw.rules = Some(fetched.rules);
673
674 raw.clone()
675 };
676 apply_raw_to_tunnel(snapshot, &state.tunnel).await?;
677
678 let raw = state.raw_config.read().clone();
680 let _ = meow_config::save_raw_config_async(&state.config_path, &raw).await;
681
682 Ok(Json(serde_json::json!({
683 "message": "subscription added",
684 "proxy_count": pc, "group_count": gc, "rule_count": rc
685 })))
686}
687
688async fn delete_subscription(
689 State(state): State<Arc<AppState>>,
690 Path(name): Path<String>,
691) -> Result<StatusCode, (StatusCode, String)> {
692 let snapshot = {
693 let mut raw = state.raw_config.write();
694
695 if let Some(ref mut subs) = raw.subscriptions {
696 let before = subs.len();
697 subs.retain(|s| s.name != name);
698 if subs.len() == before {
699 return Err((StatusCode::NOT_FOUND, "subscription not found".into()));
700 }
701 } else {
702 return Err((StatusCode::NOT_FOUND, "no subscriptions".into()));
703 }
704
705 raw.proxies = Some(Vec::new());
707 raw.proxy_groups = Some(Vec::new());
708 raw.rules = Some(Vec::new());
709
710 raw.clone()
711 };
712 apply_raw_to_tunnel(snapshot, &state.tunnel).await?;
713 let raw = state.raw_config.read().clone();
714 let _ = meow_config::save_raw_config_async(&state.config_path, &raw).await;
715 Ok(StatusCode::NO_CONTENT)
716}
717
718async fn refresh_subscription(
719 State(state): State<Arc<AppState>>,
720 Path(name): Path<String>,
721) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
722 let url = {
723 let raw = state.raw_config.read();
724 raw.subscriptions
725 .as_ref()
726 .and_then(|subs| subs.iter().find(|s| s.name == name))
727 .map(|s| s.url.clone())
728 .ok_or_else(|| (StatusCode::NOT_FOUND, "subscription not found".into()))?
729 };
730
731 let fetched = meow_config::subscription::fetch_subscription(&url)
732 .await
733 .map_err(|e| (StatusCode::BAD_REQUEST, format!("fetch failed: {e}")))?;
734
735 let now = std::time::SystemTime::now()
736 .duration_since(std::time::UNIX_EPOCH)
737 .unwrap_or_default()
738 .as_secs() as i64;
739
740 let pc = fetched.proxies.len();
741 let gc = fetched.proxy_groups.len();
742 let rc = fetched.rules.len();
743
744 let snapshot = {
745 let mut raw = state.raw_config.write();
746
747 if let Some(ref mut subs) = raw.subscriptions {
748 if let Some(sub) = subs.iter_mut().find(|s| s.name == name) {
749 sub.last_updated = Some(now);
750 }
751 }
752
753 raw.proxies = Some(fetched.proxies);
754 raw.proxy_groups = Some(fetched.proxy_groups);
755 raw.rules = Some(fetched.rules);
756
757 raw.clone()
758 };
759 apply_raw_to_tunnel(snapshot, &state.tunnel).await?;
760
761 let raw = state.raw_config.read().clone();
763 let _ = meow_config::save_raw_config_async(&state.config_path, &raw).await;
764
765 Ok(Json(serde_json::json!({
766 "message": "subscription refreshed",
767 "proxy_count": pc, "group_count": gc, "rule_count": rc
768 })))
769}
770
771#[derive(Serialize)]
774struct ProxyGroupInfo {
775 name: String,
776 #[serde(rename = "type")]
777 group_type: String,
778 proxies: Vec<String>,
779 now: Option<String>,
780 url: Option<String>,
781 interval: Option<u64>,
782 tolerance: Option<u16>,
783}
784
785async fn get_proxy_groups(State(state): State<Arc<AppState>>) -> Json<Vec<ProxyGroupInfo>> {
786 let raw = state.raw_config.read();
787 let groups = raw.proxy_groups.as_deref().unwrap_or(&[]);
788 let route = state.tunnel.route_snapshot();
789 let tunnel_proxies = &route.proxies;
790
791 let result: Vec<ProxyGroupInfo> = groups
792 .iter()
793 .map(|g| {
794 let runtime = tunnel_proxies.get(g.name.as_str());
795 let now = runtime.and_then(|p| p.current());
796 let proxies = runtime
797 .and_then(|p| p.members())
798 .unwrap_or_else(|| g.proxies.clone().unwrap_or_default());
799 ProxyGroupInfo {
800 name: g.name.clone(),
801 group_type: g.group_type.clone(),
802 proxies,
803 now,
804 url: g.url.clone(),
805 interval: g.interval,
806 tolerance: g.tolerance,
807 }
808 })
809 .collect();
810 Json(result)
811}
812
813#[derive(Deserialize)]
814struct CreateProxyGroupRequest {
815 name: String,
816 #[serde(rename = "type")]
817 group_type: String,
818 proxies: Vec<String>,
819 url: Option<String>,
820 interval: Option<u64>,
821 tolerance: Option<u16>,
822}
823
824async fn create_proxy_group(
825 State(state): State<Arc<AppState>>,
826 Json(body): Json<CreateProxyGroupRequest>,
827) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
828 let group_name = body.name.clone();
829 let snapshot = {
830 let mut raw = state.raw_config.write();
831 if let Some(ref groups) = raw.proxy_groups {
832 if groups.iter().any(|g| g.name == body.name) {
833 return Err((StatusCode::CONFLICT, "group name already exists".into()));
834 }
835 }
836 let group = RawProxyGroup {
837 name: body.name,
838 group_type: body.group_type,
839 proxies: Some(body.proxies),
840 url: body.url,
841 interval: body.interval,
842 tolerance: body.tolerance,
843 ..Default::default()
844 };
845 raw.proxy_groups.get_or_insert_with(Vec::new).push(group);
846 raw.clone()
847 };
848 apply_raw_to_tunnel(snapshot, &state.tunnel).await?;
849 Ok(Json(
850 serde_json::json!({"message": "group created", "name": group_name}),
851 ))
852}
853
854async fn update_proxy_group(
855 State(state): State<Arc<AppState>>,
856 Path(name): Path<String>,
857 Json(body): Json<CreateProxyGroupRequest>,
858) -> Result<StatusCode, (StatusCode, String)> {
859 let snapshot = {
860 let mut raw = state.raw_config.write();
861 let group = raw
862 .proxy_groups
863 .as_mut()
864 .and_then(|groups| groups.iter_mut().find(|g| g.name == name))
865 .ok_or_else(|| (StatusCode::NOT_FOUND, "group not found".into()))?;
866 group.group_type = body.group_type;
867 group.proxies = Some(body.proxies);
868 group.url = body.url;
869 group.interval = body.interval;
870 group.tolerance = body.tolerance;
871 raw.clone()
872 };
873 apply_raw_to_tunnel(snapshot, &state.tunnel).await?;
874 Ok(StatusCode::NO_CONTENT)
875}
876
877async fn delete_proxy_group(
878 State(state): State<Arc<AppState>>,
879 Path(name): Path<String>,
880) -> Result<StatusCode, (StatusCode, String)> {
881 let snapshot = {
882 let mut raw = state.raw_config.write();
883 if let Some(ref mut groups) = raw.proxy_groups {
884 let before = groups.len();
885 groups.retain(|g| g.name != name);
886 if groups.len() == before {
887 return Err((StatusCode::NOT_FOUND, "group not found".into()));
888 }
889 } else {
890 return Err((StatusCode::NOT_FOUND, "no groups".into()));
891 }
892 if let Some(ref mut rules) = raw.rules {
893 rules.retain(|r| {
894 let parts: Vec<&str> = r.split(',').collect();
895 parts.last().is_none_or(|target| target.trim() != name)
896 });
897 }
898 raw.clone()
899 };
900 apply_raw_to_tunnel(snapshot, &state.tunnel).await?;
901 Ok(StatusCode::NO_CONTENT)
902}
903
904#[derive(Deserialize)]
905struct SelectProxyRequest {
906 name: String,
907}
908
909async fn select_proxy_in_group(
910 State(state): State<Arc<AppState>>,
911 Path(group_name): Path<String>,
912 Json(body): Json<SelectProxyRequest>,
913) -> StatusCode {
914 let route = state.tunnel.route_snapshot();
915 let Some(proxy) = route.proxies.get(group_name.as_str()).cloned() else {
916 return StatusCode::NOT_FOUND;
917 };
918 match select_proxy_member_async(proxy, body.name.clone()).await {
919 Ok(Some(true)) => {
920 info!("Selector '{}' switched to '{}'", group_name, body.name);
921 StatusCode::NO_CONTENT
922 }
923 Ok(Some(false)) => StatusCode::BAD_REQUEST,
924 Ok(None) => StatusCode::NOT_FOUND,
925 Err(e) => {
926 warn!("Selector '{}' update task failed: {}", group_name, e);
927 StatusCode::INTERNAL_SERVER_ERROR
928 }
929 }
930}
931
932#[derive(Deserialize)]
935struct ReplaceRulesRequest {
936 rules: Vec<String>,
937}
938
939async fn replace_rules(
940 State(state): State<Arc<AppState>>,
941 Json(body): Json<ReplaceRulesRequest>,
942) -> Result<StatusCode, (StatusCode, String)> {
943 let snapshot = {
944 let mut raw = state.raw_config.write();
945 raw.rules = Some(body.rules);
946 raw.clone()
947 };
948 apply_raw_to_tunnel(snapshot, &state.tunnel).await?;
949 Ok(StatusCode::NO_CONTENT)
950}
951
952#[derive(Deserialize)]
953struct UpdateRuleRequest {
954 index: usize,
955 rule: String,
956}
957
958async fn update_rule_at_index(
959 State(state): State<Arc<AppState>>,
960 Json(body): Json<UpdateRuleRequest>,
961) -> Result<StatusCode, (StatusCode, String)> {
962 let snapshot = {
963 let mut raw = state.raw_config.write();
964 let rules = raw.rules.get_or_insert_with(Vec::new);
965 if body.index >= rules.len() {
966 return Err((StatusCode::BAD_REQUEST, "index out of range".into()));
967 }
968 rules[body.index] = body.rule;
969 raw.clone()
970 };
971 apply_raw_to_tunnel(snapshot, &state.tunnel).await?;
972 Ok(StatusCode::NO_CONTENT)
973}
974
975async fn delete_rule(
976 State(state): State<Arc<AppState>>,
977 Path(index): Path<usize>,
978) -> Result<StatusCode, (StatusCode, String)> {
979 let snapshot = {
980 let mut raw = state.raw_config.write();
981 let rules = raw.rules.get_or_insert_with(Vec::new);
982 if index >= rules.len() {
983 return Err((StatusCode::BAD_REQUEST, "index out of range".into()));
984 }
985 rules.remove(index);
986 raw.clone()
987 };
988 apply_raw_to_tunnel(snapshot, &state.tunnel).await?;
989 Ok(StatusCode::NO_CONTENT)
990}
991
992#[derive(Deserialize)]
993struct ReorderRulesRequest {
994 from: usize,
995 to: usize,
996}
997
998async fn reorder_rules(
999 State(state): State<Arc<AppState>>,
1000 Json(body): Json<ReorderRulesRequest>,
1001) -> Result<StatusCode, (StatusCode, String)> {
1002 let snapshot = {
1003 let mut raw = state.raw_config.write();
1004 let rules = raw.rules.get_or_insert_with(Vec::new);
1005 if body.from >= rules.len() || body.to >= rules.len() {
1006 return Err((StatusCode::BAD_REQUEST, "index out of range".into()));
1007 }
1008 let rule = rules.remove(body.from);
1009 rules.insert(body.to, rule);
1010 raw.clone()
1011 };
1012 apply_raw_to_tunnel(snapshot, &state.tunnel).await?;
1013 Ok(StatusCode::NO_CONTENT)
1014}
1015
1016#[derive(Deserialize)]
1024struct DelayParams {
1025 url: Option<String>,
1026 timeout: Option<String>,
1027 expected: Option<String>,
1028}
1029
1030#[derive(Serialize)]
1031struct DelayResp {
1032 delay: u16,
1033}
1034
1035fn msg_err(status: StatusCode, message: &'static str) -> Response {
1037 (status, Json(serde_json::json!({ "message": message }))).into_response()
1038}
1039
1040fn parse_delay_params(params: &DelayParams) -> Result<Duration, Box<Response>> {
1044 let url = params.url.as_deref().unwrap_or("").trim();
1047 if url.is_empty() {
1048 return Err(Box::new(msg_err(StatusCode::BAD_REQUEST, "Body invalid")));
1049 }
1050
1051 let timeout_str = params
1054 .timeout
1055 .as_deref()
1056 .ok_or_else(|| Box::new(msg_err(StatusCode::BAD_REQUEST, "Body invalid")))?;
1057 let timeout_ms: u16 = timeout_str
1058 .trim()
1059 .parse()
1060 .map_err(|_| Box::new(msg_err(StatusCode::BAD_REQUEST, "Body invalid")))?;
1061 if timeout_ms == 0 {
1062 return Err(Box::new(msg_err(StatusCode::BAD_REQUEST, "Body invalid")));
1063 }
1064 Ok(Duration::from_millis(timeout_ms as u64))
1065}
1066
1067async fn probe_and_record(
1071 proxy: &Arc<dyn meow_common::Proxy>,
1072 url: &str,
1073 expected: Option<&str>,
1074 timeout: Duration,
1075) -> Result<u16, meow_proxy::health::UrlTestError> {
1076 meow_proxy::health::probe_and_record(proxy, url, expected, timeout).await
1077}
1078
1079async fn get_proxy_delay(
1080 State(state): State<Arc<AppState>>,
1081 Path(name): Path<String>,
1082 Query(params): Query<DelayParams>,
1083) -> Response {
1084 let timeout = match parse_delay_params(¶ms) {
1085 Ok(t) => t,
1086 Err(resp) => return *resp,
1087 };
1088 let url = params.url.as_deref().unwrap_or("").to_string();
1089 let expected = params.expected.clone();
1090
1091 let route = state.tunnel.route_snapshot();
1092 let Some(proxy) = route.proxies.get(name.as_str()).cloned() else {
1094 return msg_err(StatusCode::NOT_FOUND, "resource not found");
1095 };
1096 drop(route);
1097
1098 match probe_and_record(&proxy, &url, expected.as_deref(), timeout).await {
1099 Ok(delay) => Json(DelayResp { delay }).into_response(),
1100 Err(meow_proxy::health::UrlTestError::Timeout) => {
1102 msg_err(StatusCode::GATEWAY_TIMEOUT, "Timeout")
1103 }
1104 Err(meow_proxy::health::UrlTestError::Transport(_)) => msg_err(
1106 StatusCode::SERVICE_UNAVAILABLE,
1107 "An error occurred in the delay test",
1108 ),
1109 }
1110}
1111
1112async fn get_group_delay(
1113 State(state): State<Arc<AppState>>,
1114 Path(name): Path<String>,
1115 Query(params): Query<DelayParams>,
1116) -> Response {
1117 let timeout = match parse_delay_params(¶ms) {
1118 Ok(t) => t,
1119 Err(resp) => return *resp,
1120 };
1121 let url = params.url.as_deref().unwrap_or("").to_string();
1122 let expected = params.expected.clone();
1123
1124 let route = state.tunnel.route_snapshot();
1125 let Some(group) = route.proxies.get(name.as_str()).cloned() else {
1126 return msg_err(StatusCode::NOT_FOUND, "resource not found");
1127 };
1128 let Some(member_names) = group.members() else {
1130 return msg_err(StatusCode::NOT_FOUND, "resource not found");
1131 };
1132
1133 let members: Vec<(String, Arc<dyn meow_common::Proxy>)> = member_names
1136 .into_iter()
1137 .filter_map(|n| route.proxies.get(n.as_str()).cloned().map(|p| (n, p)))
1138 .collect();
1139 drop(route);
1140
1141 let collected = tokio::time::timeout(
1144 timeout,
1145 meow_proxy::health::probe_many_bounded_detailed(
1146 members,
1147 &url,
1148 expected.as_deref(),
1149 timeout,
1150 meow_proxy::health::GROUP_DELAY_CONCURRENCY,
1151 ),
1152 )
1153 .await;
1154
1155 let Ok(pairs) = collected else {
1156 return msg_err(StatusCode::GATEWAY_TIMEOUT, "Timeout");
1159 };
1160
1161 let mut result: BTreeMap<String, u16> = BTreeMap::new();
1162 for pair in pairs {
1163 if matches!(pair.error, Some(meow_proxy::health::UrlTestError::Timeout)) {
1164 return msg_err(StatusCode::GATEWAY_TIMEOUT, "Timeout");
1165 }
1166 result.insert(pair.name, pair.delay);
1167 }
1168 Json(result).into_response()
1169}
1170
1171#[derive(Deserialize)]
1177struct PutConfigsBody {
1178 path: Option<String>,
1179 payload: Option<String>,
1180}
1181
1182async fn put_configs(
1183 State(state): State<Arc<AppState>>,
1184 Query(params): Query<HashMap<String, String>>,
1185 Json(body): Json<PutConfigsBody>,
1186) -> Response {
1187 let force = params.get("force").is_some_and(|v| v == "true");
1188
1189 let yaml =
1190 match (body.path, body.payload) {
1191 (Some(p), _) => match tokio::fs::read_to_string(&p).await {
1192 Ok(s) => s,
1193 Err(e) => {
1194 return (
1195 StatusCode::BAD_REQUEST,
1196 Json(serde_json::json!({"message": e.to_string()})),
1197 )
1198 .into_response()
1199 }
1200 },
1201 (_, Some(b64)) => {
1202 use base64::engine::general_purpose::STANDARD;
1203 use base64::Engine as _;
1204 let Ok(bytes) = STANDARD.decode(&b64) else {
1205 return (
1206 StatusCode::BAD_REQUEST,
1207 Json(serde_json::json!({"message": "payload is not valid base64"})),
1208 )
1209 .into_response();
1210 };
1211 match String::from_utf8(bytes) {
1212 Ok(s) => s,
1213 Err(_) => {
1214 return (
1215 StatusCode::BAD_REQUEST,
1216 Json(serde_json::json!({"message": "payload is not valid UTF-8"})),
1217 )
1218 .into_response()
1219 }
1220 }
1221 }
1222 _ => return (
1223 StatusCode::BAD_REQUEST,
1224 Json(
1225 serde_json::json!({"message": "request body must contain 'path' or 'payload'"}),
1226 ),
1227 )
1228 .into_response(),
1229 };
1230
1231 let mut raw_config: RawConfig = match serde_yaml::from_str(&yaml) {
1233 Ok(c) => c,
1234 Err(e) => {
1235 return (
1236 StatusCode::BAD_REQUEST,
1237 Json(serde_json::json!({"message": format!("config parse error: {e}")})),
1238 )
1239 .into_response()
1240 }
1241 };
1242
1243 if let Some(ps) = raw_config.proxies.as_mut() {
1245 meow_config::ech_dns::preresolve_ech(ps).await;
1246 }
1247
1248 let resolver = Arc::clone(state.tunnel.resolver());
1250 let (proxies, rules) = match rebuild_from_raw_with_resolver_async(raw_config.clone(), resolver)
1251 .await
1252 {
1253 Ok(r) => r,
1254 Err(e) => {
1255 if force {
1256 tracing::error!("config reload forced despite validation error: {e}");
1257 (Default::default(), Vec::new())
1258 } else {
1259 return (
1260 StatusCode::BAD_REQUEST,
1261 Json(serde_json::json!({"message": format!("config validation error: {e}")})),
1262 )
1263 .into_response();
1264 }
1265 }
1266 };
1267
1268 let stats = state.tunnel.statistics();
1270 let dropped = stats.active_connection_count();
1271 stats.close_all_connections();
1272 if dropped > 0 {
1273 tracing::warn!(
1274 connections_dropped = dropped,
1275 "connections force-closed after reload drain timeout"
1276 );
1277 }
1278
1279 state.tunnel.update_proxies(proxies);
1280 state.tunnel.update_rules(rules);
1281 if let Some(mode_str) = &raw_config.mode {
1282 if let Ok(mode) = mode_str.parse::<TunnelMode>() {
1283 state.tunnel.set_mode(mode);
1284 }
1285 }
1286 *state.raw_config.write() = raw_config;
1287
1288 StatusCode::NO_CONTENT.into_response()
1289}
1290
1291async fn get_metrics(State(state): State<Arc<AppState>>) -> Response {
1295 use prometheus_client::encoding::text::encode;
1296 use prometheus_client::metrics::counter::Counter;
1297 use prometheus_client::metrics::family::Family;
1298 use prometheus_client::metrics::gauge::Gauge;
1299 use prometheus_client::registry::Registry;
1300 use std::sync::atomic::{AtomicI64, AtomicU64};
1301
1302 let mut registry = Registry::default();
1303 let stats = state.tunnel.statistics();
1304 let (upload_total, download_total) = stats.snapshot();
1305
1306 let traffic = Family::<Vec<(String, String)>, Counter<u64, AtomicU64>>::default();
1308 traffic
1309 .get_or_create(&vec![("direction".to_string(), "upload".to_string())])
1310 .inc_by(upload_total.max(0) as u64);
1311 traffic
1312 .get_or_create(&vec![("direction".to_string(), "download".to_string())])
1313 .inc_by(download_total.max(0) as u64);
1314 registry.register(
1315 "meow_traffic_bytes",
1316 "Cumulative bytes transferred since process start",
1317 traffic,
1318 );
1319
1320 let connections_active = Gauge::<i64, AtomicI64>::default();
1322 connections_active.set(stats.active_connection_count() as i64);
1323 registry.register(
1324 "meow_connections_active",
1325 "Number of currently open connections",
1326 connections_active,
1327 );
1328
1329 let proxy_alive = Family::<Vec<(String, String)>, Gauge<i64, AtomicI64>>::default();
1331 let proxy_delay = Family::<Vec<(String, String)>, Gauge<i64, AtomicI64>>::default();
1332 let route = state.tunnel.route_snapshot();
1333 for (name, proxy) in &route.proxies {
1334 let labels = vec![
1335 ("proxy_name".to_string(), name.to_string()),
1336 ("adapter_type".to_string(), proxy.adapter_type().to_string()),
1337 ];
1338 proxy_alive
1339 .get_or_create(&labels)
1340 .set(if proxy.alive() { 1 } else { 0 });
1341 if !proxy.delay_history().is_empty() {
1344 proxy_delay
1345 .get_or_create(&labels)
1346 .set(proxy.last_delay() as i64);
1347 }
1348 }
1349 registry.register(
1350 "meow_proxy_alive",
1351 "Proxy alive status (1=alive, 0=dead)",
1352 proxy_alive,
1353 );
1354 registry.register(
1355 "meow_proxy_delay_ms",
1356 "Last measured proxy round-trip delay in milliseconds",
1357 proxy_delay,
1358 );
1359
1360 let rules_matched = Family::<Vec<(String, String)>, Counter<u64, AtomicU64>>::default();
1362 for ((rule_type, action), count) in stats.rule_match.snapshot() {
1363 rules_matched
1364 .get_or_create(&vec![
1365 ("rule_type".to_string(), rule_type.to_string()),
1366 ("action".to_string(), action.to_string()),
1367 ])
1368 .inc_by(count);
1369 }
1370 registry.register(
1371 "meow_rules_matched",
1372 "Cumulative rule matches by type and action",
1373 rules_matched,
1374 );
1375
1376 let memory_rss = Gauge::<i64, AtomicI64>::default();
1378 memory_rss.set(read_rss_bytes().await as i64);
1379 registry.register(
1380 "meow_memory_rss_bytes",
1381 "Current process RSS in bytes",
1382 memory_rss,
1383 );
1384
1385 let info = Family::<Vec<(String, String)>, Gauge<i64, AtomicI64>>::default();
1387 info.get_or_create(&vec![
1388 ("version".to_string(), env!("CARGO_PKG_VERSION").to_string()),
1389 ("mode".to_string(), state.tunnel.mode().to_string()),
1390 ])
1391 .set(1);
1392 registry.register("meow_info", "meow-rs runtime info", info);
1393
1394 let mut body = String::new();
1395 encode(&mut body, ®istry).expect("prometheus text encoding is infallible");
1396 (
1397 StatusCode::OK,
1398 [(
1399 header::CONTENT_TYPE,
1400 "text/plain; version=0.0.4; charset=utf-8",
1401 )],
1402 body,
1403 )
1404 .into_response()
1405}
1406
1407#[derive(Deserialize)]
1410struct LogsParams {
1411 level: Option<String>,
1412}
1413
1414async fn get_logs(
1416 State(state): State<Arc<AppState>>,
1417 Query(params): Query<LogsParams>,
1418 ws: WebSocketUpgrade,
1419) -> Response {
1420 let level = parse_log_level(params.level.as_deref().unwrap_or("info"));
1421 let mut rx = state.log_tx.subscribe();
1422 ws.on_upgrade(move |mut socket| async move {
1423 loop {
1424 match rx.recv().await {
1425 Ok(msg) if msg.level >= level => {
1426 let json = serde_json::to_string(&msg).unwrap_or_default();
1427 if socket.send(Message::Text(json.into())).await.is_err() {
1428 break;
1429 }
1430 }
1431 Ok(_) => {}
1432 Err(broadcast::error::RecvError::Lagged(n)) => {
1433 let lag_msg = format!("{{\"type\":\"lagged\",\"missed\":{n}}}");
1434 if socket.send(Message::Text(lag_msg.into())).await.is_err() {
1435 break;
1436 }
1437 }
1438 Err(broadcast::error::RecvError::Closed) => break,
1439 }
1440 }
1441 })
1442}
1443
1444static MEMORY_FEED: std::sync::Mutex<Option<broadcast::Sender<Arc<str>>>> =
1455 std::sync::Mutex::new(None);
1456
1457fn subscribe_memory_feed() -> broadcast::Receiver<Arc<str>> {
1458 let mut guard = MEMORY_FEED.lock().expect("memory feed lock poisoned");
1459 if let Some(tx) = guard.as_ref() {
1460 return tx.subscribe();
1462 }
1463 let (tx, rx) = broadcast::channel(2);
1464 *guard = Some(tx.clone());
1465 tokio::spawn(async move {
1466 let mut interval = tokio::time::interval(Duration::from_secs(1));
1467 loop {
1468 interval.tick().await;
1469 if tx.receiver_count() == 0 {
1470 let mut guard = MEMORY_FEED.lock().expect("memory feed lock poisoned");
1474 if tx.receiver_count() == 0 {
1475 *guard = None;
1476 break;
1477 }
1478 }
1479 let inuse = read_rss_bytes().await;
1480 let oslimit = read_os_memory_limit().await;
1481 let msg: Arc<str> = Arc::from(format!("{{\"inuse\":{inuse},\"oslimit\":{oslimit}}}"));
1482 let _ = tx.send(msg);
1483 }
1484 });
1485 rx
1486}
1487
1488async fn get_memory(State(_state): State<Arc<AppState>>, ws: WebSocketUpgrade) -> Response {
1489 ws.on_upgrade(|mut socket| async move {
1490 let mut feed = subscribe_memory_feed();
1491 loop {
1492 let msg = match feed.recv().await {
1493 Ok(msg) => msg,
1494 Err(broadcast::error::RecvError::Lagged(_)) => continue,
1496 Err(broadcast::error::RecvError::Closed) => break,
1497 };
1498 if socket
1499 .send(Message::Text(msg.as_ref().into()))
1500 .await
1501 .is_err()
1502 {
1503 break;
1504 }
1505 }
1506 })
1507}
1508
1509async fn read_rss_bytes() -> u64 {
1510 tokio::task::spawn_blocking(|| {
1511 use sysinfo::{Pid, ProcessesToUpdate, System};
1512 let pid = Pid::from_u32(std::process::id());
1513 let mut sys = System::new();
1514 sys.refresh_processes(ProcessesToUpdate::Some(&[pid]), false);
1515 sys.process(pid).map_or(0, sysinfo::Process::memory)
1516 })
1517 .await
1518 .unwrap_or(0)
1519}
1520
1521async fn read_os_memory_limit() -> u64 {
1522 #[cfg(target_os = "linux")]
1523 {
1524 read_os_memory_limit_linux().await
1525 }
1526 #[cfg(not(target_os = "linux"))]
1527 {
1528 0
1529 }
1530}
1531
1532#[cfg(target_os = "linux")]
1533async fn read_os_memory_limit_linux() -> u64 {
1534 if let Ok(s) = tokio::fs::read_to_string("/sys/fs/cgroup/memory.max").await {
1536 if let Ok(n) = s.trim().parse::<u64>() {
1537 return n;
1538 }
1539 }
1540 unsafe {
1542 let mut rl = libc::rlimit {
1543 rlim_cur: 0,
1544 rlim_max: 0,
1545 };
1546 if libc::getrlimit(libc::RLIMIT_AS, &mut rl) == 0 && rl.rlim_cur != libc::RLIM_INFINITY {
1547 return rl.rlim_cur;
1548 }
1549 }
1550 0
1551}
1552
1553#[derive(Serialize)]
1556#[serde(rename_all = "camelCase")]
1557struct ProviderInfo {
1558 name: String,
1559 #[serde(rename = "type")]
1560 provider_type: String,
1561 vehicle_type: String,
1562 proxies: Vec<ProxyInfo>,
1563}
1564
1565fn provider_to_info(name: &str, provider: &ProxyProvider) -> ProviderInfo {
1566 let proxies = provider
1567 .proxies()
1568 .iter()
1569 .map(ProxyInfo::from_proxy)
1570 .collect();
1571 ProviderInfo {
1572 name: name.to_string(),
1573 provider_type: "Proxy".to_string(),
1574 vehicle_type: provider.vehicle_type.to_string(),
1575 proxies,
1576 }
1577}
1578
1579async fn get_providers(State(state): State<Arc<AppState>>) -> Json<serde_json::Value> {
1580 let mut map = serde_json::Map::new();
1581 for entry in state.proxy_providers.iter() {
1582 let info = provider_to_info(entry.key(), entry.value());
1583 map.insert(
1584 entry.key().clone(),
1585 serde_json::to_value(info).unwrap_or_default(),
1586 );
1587 }
1588 Json(serde_json::json!({ "providers": map }))
1589}
1590
1591async fn get_provider(State(state): State<Arc<AppState>>, Path(name): Path<String>) -> Response {
1592 match state.proxy_providers.get(&name) {
1593 Some(entry) => Json(provider_to_info(&name, entry.value())).into_response(),
1594 None => msg_err(StatusCode::NOT_FOUND, "resource not found"),
1595 }
1596}
1597
1598async fn refresh_provider(
1599 State(state): State<Arc<AppState>>,
1600 Path(name): Path<String>,
1601) -> Response {
1602 let provider = match state.proxy_providers.get(&name) {
1603 Some(entry) => Arc::clone(entry.value()),
1604 None => return msg_err(StatusCode::NOT_FOUND, "resource not found"),
1605 };
1606 provider.refresh().await;
1607 StatusCode::NO_CONTENT.into_response()
1608}
1609
1610async fn provider_healthcheck(
1613 State(state): State<Arc<AppState>>,
1614 Path(name): Path<String>,
1615 Query(params): Query<DelayParams>,
1616) -> Response {
1617 let timeout = match parse_delay_params(¶ms) {
1618 Ok(t) => t,
1619 Err(resp) => return *resp,
1620 };
1621 let url = params.url.as_deref().unwrap_or("").to_string();
1622 let expected = params.expected.clone();
1623
1624 let provider = match state.proxy_providers.get(&name) {
1625 Some(entry) => Arc::clone(entry.value()),
1626 None => return msg_err(StatusCode::NOT_FOUND, "resource not found"),
1627 };
1628
1629 let members = provider
1630 .proxies()
1631 .into_iter()
1632 .map(|proxy| (proxy.name().to_string(), proxy))
1633 .collect();
1634
1635 let mut results = serde_json::Map::new();
1636 for (pname, delay) in meow_proxy::health::probe_many_bounded(
1637 members,
1638 &url,
1639 expected.as_deref(),
1640 timeout,
1641 meow_proxy::health::PROVIDER_HEALTHCHECK_CONCURRENCY,
1642 )
1643 .await
1644 {
1645 results.insert(pname, serde_json::Value::Number(delay.into()));
1646 }
1647
1648 Json(serde_json::Value::Object(results)).into_response()
1649}
1650
1651#[derive(Serialize)]
1654struct RuleProviderInfo {
1655 name: String,
1656 #[serde(rename = "type")]
1657 provider_type: String,
1658 behavior: String,
1659 #[serde(rename = "ruleCount")]
1660 rule_count: usize,
1661 #[serde(rename = "updatedAt")]
1662 updated_at: u64,
1663 #[serde(rename = "vehicleType")]
1664 vehicle_type: String,
1665}
1666
1667impl RuleProviderInfo {
1668 fn from_provider(p: &Arc<RuleProvider>) -> Self {
1669 Self {
1670 name: p.name.clone(),
1671 provider_type: p.provider_type.to_string(),
1672 behavior: p.behavior.to_string(),
1673 rule_count: p.rule_count(),
1674 updated_at: p.updated_at_secs(),
1675 vehicle_type: p.vehicle.clone(),
1676 }
1677 }
1678}
1679
1680#[derive(Serialize)]
1681struct RuleProvidersResponse {
1682 providers: HashMap<String, RuleProviderInfo>,
1683}
1684
1685async fn get_rule_providers(State(state): State<Arc<AppState>>) -> Json<RuleProvidersResponse> {
1686 let providers = state.rule_providers.read();
1687 let map: HashMap<String, RuleProviderInfo> = providers
1688 .iter()
1689 .map(|(name, p): (&String, &Arc<RuleProvider>)| {
1690 (name.clone(), RuleProviderInfo::from_provider(p))
1691 })
1692 .collect();
1693 Json(RuleProvidersResponse { providers: map })
1694}
1695
1696async fn get_rule_provider(
1697 State(state): State<Arc<AppState>>,
1698 Path(name): Path<String>,
1699) -> Result<Json<RuleProviderInfo>, StatusCode> {
1700 let providers = state.rule_providers.read();
1701 let p = providers.get(&name).ok_or(StatusCode::NOT_FOUND)?;
1702 Ok(Json(RuleProviderInfo::from_provider(p)))
1703}
1704
1705async fn refresh_rule_provider(
1706 State(state): State<Arc<AppState>>,
1707 Path(name): Path<String>,
1708) -> StatusCode {
1709 let provider = {
1710 let providers = state.rule_providers.read();
1711 providers.get(&name).cloned()
1712 };
1713 let Some(p) = provider else {
1714 return StatusCode::NOT_FOUND;
1715 };
1716 let ctx = meow_rules::ParserContext::empty();
1717 match p.refresh(&ctx).await {
1718 Ok(()) => StatusCode::NO_CONTENT,
1719 Err(e) => {
1720 tracing::warn!(provider = %name, "rule-provider refresh failed: {:#}", e);
1721 StatusCode::SERVICE_UNAVAILABLE
1722 }
1723 }
1724}
1725
1726async fn get_listeners(State(state): State<Arc<AppState>>) -> Json<serde_json::Value> {
1729 let items: Vec<serde_json::Value> = state
1730 .listeners
1731 .iter()
1732 .map(|l| {
1733 serde_json::json!({
1734 "name": l.name,
1735 "type": l.listener_type.to_string(),
1736 "port": l.port,
1737 "listen": l.listen,
1738 })
1739 })
1740 .collect();
1741 Json(serde_json::json!(items))
1742}