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