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