1#[cfg(feature = "server")]
15use axum::http::StatusCode;
16#[cfg(feature = "server")]
17use axum::response::{IntoResponse, Response};
18#[cfg(feature = "server")]
19use governor::{clock::Clock, DefaultKeyedRateLimiter, Quota, RateLimiter};
20#[cfg(feature = "server")]
21use std::num::NonZeroU32;
22#[cfg(feature = "server")]
23use std::sync::LazyLock;
24#[cfg(feature = "server")]
25use std::sync::Once;
26#[cfg(feature = "server")]
27use std::time::Duration;
28
29#[cfg(feature = "server")]
30pub(crate) fn nz(val: u32) -> NonZeroU32 {
31 NonZeroU32::new(val.max(1)).expect("val.max(1) 保证非零,NonZeroU32::new 不可能失败")
33}
34
35#[cfg(feature = "server")]
36static STRICT_LIMITER: LazyLock<DefaultKeyedRateLimiter<String>> = LazyLock::new(|| {
37 let c = crate::config::rate_limit();
38 RateLimiter::keyed(Quota::per_second(nz(c.strict_per_sec)).allow_burst(nz(c.strict_burst)))
39});
40
41#[cfg(feature = "server")]
42static UPLOAD_LIMITER: LazyLock<DefaultKeyedRateLimiter<String>> = LazyLock::new(|| {
43 let c = crate::config::rate_limit();
44 RateLimiter::keyed(Quota::per_second(nz(c.upload_per_sec)).allow_burst(nz(c.upload_burst)))
45});
46
47#[cfg(feature = "server")]
48static IMAGE_LIMITER: LazyLock<DefaultKeyedRateLimiter<String>> = LazyLock::new(|| {
49 let c = crate::config::rate_limit();
50 RateLimiter::keyed(Quota::per_second(nz(c.image_per_sec)).allow_burst(nz(c.image_burst)))
51});
52
53#[cfg(feature = "server")]
54static COMMENT_LIMITER: LazyLock<DefaultKeyedRateLimiter<String>> = LazyLock::new(|| {
55 let c = crate::config::rate_limit();
56 RateLimiter::keyed(Quota::per_second(nz(c.comment_per_sec)).allow_burst(nz(c.comment_burst)))
57});
58
59#[cfg(feature = "server")]
60static COMMENT_UPLOAD_LIMITER: LazyLock<DefaultKeyedRateLimiter<String>> = LazyLock::new(|| {
63 let c = crate::config::rate_limit();
64 RateLimiter::keyed(
65 Quota::per_second(nz(c.comment_upload_per_sec)).allow_burst(nz(c.comment_upload_burst)),
66 )
67});
68
69#[cfg(feature = "server")]
70static COMMENT_UPLOAD_DAILY_LIMITER: LazyLock<DefaultKeyedRateLimiter<String>> =
74 LazyLock::new(|| {
75 let c = crate::config::rate_limit();
76 RateLimiter::keyed(
77 Quota::with_period(Duration::from_secs(86_400))
78 .expect("with_period 仅在 Duration 为 0 时返回 None;86_400s 必然 Some")
79 .allow_burst(nz(c.comment_upload_daily)),
80 )
81 });
82
83#[cfg(feature = "server")]
84static CODE_EXEC_LIMITER: LazyLock<DefaultKeyedRateLimiter<String>> = LazyLock::new(|| {
87 let c = crate::config::rate_limit();
88 RateLimiter::keyed(
89 Quota::per_second(nz(c.code_exec_per_sec)).allow_burst(nz(c.code_exec_burst)),
90 )
91});
92
93#[cfg(feature = "server")]
94static CODE_EXEC_DAILY_LIMITER: LazyLock<DefaultKeyedRateLimiter<String>> = LazyLock::new(|| {
100 let c = crate::config::rate_limit();
101 RateLimiter::keyed(
102 Quota::with_period(Duration::from_secs(86_400))
103 .expect("with_period 仅在 Duration 为 0 时返回 None;86_400s 必然 Some")
104 .allow_burst(nz(c.code_exec_daily)),
105 )
106});
107
108#[cfg(feature = "server")]
109static SUGGEST_LIMITER: LazyLock<DefaultKeyedRateLimiter<String>> =
112 LazyLock::new(|| RateLimiter::keyed(Quota::per_second(nz(10)).allow_burst(nz(30))));
113
114#[cfg(feature = "server")]
115static MCP_PREAUTH_LIMITER: LazyLock<DefaultKeyedRateLimiter<String>> =
118 LazyLock::new(|| RateLimiter::keyed(Quota::per_second(nz(30)).allow_burst(nz(100))));
119
120#[cfg(feature = "server")]
121static UNKNOWN_BUCKET_LIMITER: LazyLock<DefaultKeyedRateLimiter<String>> = LazyLock::new(|| {
128 let c = crate::config::rate_limit();
129 RateLimiter::keyed(Quota::per_second(nz(c.unknown_per_sec)).allow_burst(nz(c.unknown_burst)))
130});
131
132#[cfg(feature = "server")]
140fn limiter_gc_interval() -> Duration {
141 let secs = crate::config::rate_limit().gc_interval_secs;
142 Duration::from_secs(u64::from(secs.max(1)))
143}
144
145#[cfg(feature = "server")]
153fn ensure_limiter_gc() {
154 static SPAWNED: std::sync::Once = std::sync::Once::new();
155 SPAWNED.call_once(|| {
156 if tokio::runtime::Handle::try_current().is_err() {
160 return;
161 }
162 let interval = limiter_gc_interval();
163 crate::tasks::spawn_supervised("rate_limit_gc", async move {
164 loop {
165 tokio::time::sleep(interval).await;
166 STRICT_LIMITER.retain_recent();
167 UPLOAD_LIMITER.retain_recent();
168 IMAGE_LIMITER.retain_recent();
169 COMMENT_LIMITER.retain_recent();
170 COMMENT_UPLOAD_LIMITER.retain_recent();
171 COMMENT_UPLOAD_DAILY_LIMITER.retain_recent();
172 CODE_EXEC_LIMITER.retain_recent();
173 CODE_EXEC_DAILY_LIMITER.retain_recent();
174 UNKNOWN_BUCKET_LIMITER.retain_recent();
175 SUGGEST_LIMITER.retain_recent();
176 MCP_PREAUTH_LIMITER.retain_recent();
177 }
178 });
179 });
180}
181
182#[cfg(feature = "server")]
183pub fn check_comment_limit(ip: &str) -> Result<(), String> {
185 ensure_limiter_gc();
186 COMMENT_LIMITER
187 .check_key(&ip.to_string())
188 .map(|_| ())
189 .map_err(|_| "评论过于频繁,请稍后再试".to_string())
190}
191
192#[cfg(feature = "server")]
193pub fn check_comment_upload_limit(ip: &str) -> Result<(), String> {
197 ensure_limiter_gc();
198 COMMENT_UPLOAD_LIMITER
199 .check_key(&ip.to_string())
200 .map_err(|_| "上传过于频繁,请稍后再试".to_string())?;
201 COMMENT_UPLOAD_DAILY_LIMITER
202 .check_key(&ip.to_string())
203 .map_err(|_| "今日上传次数已达上限,请明天再试".to_string())?;
204 Ok(())
205}
206
207#[cfg(feature = "server")]
208pub fn check_image_limit(ip: &str) -> Result<(), Box<Response>> {
214 ensure_limiter_gc();
215 IMAGE_LIMITER
216 .check_key(&ip.to_string())
217 .map(|_| ())
218 .map_err(|not_until| {
219 let wait = not_until.wait_time_from(governor::clock::DefaultClock::default().now());
220 let secs = (wait.as_millis() as u64).div_ceil(1000).max(1);
221 Box::new(
222 (
223 StatusCode::TOO_MANY_REQUESTS,
224 [(axum::http::header::RETRY_AFTER, secs.to_string())],
225 )
226 .into_response(),
227 )
228 })
229}
230
231#[cfg(feature = "server")]
232async fn trusted_proxy_count() -> usize {
233 crate::api::settings::runtime_security_settings()
234 .await
235 .trusted_proxy_count as usize
236}
237
238#[cfg(feature = "server")]
239fn is_valid_ip(ip: &str) -> bool {
240 ip.parse::<std::net::IpAddr>().is_ok()
241}
242
243#[cfg(feature = "server")]
261fn ip_from_x_forwarded_for(value: &str, trusted_proxy_count: usize) -> Option<String> {
262 let parts: Vec<&str> = value
265 .split(',')
266 .map(str::trim)
267 .filter(|s| !s.is_empty())
268 .collect();
269
270 if trusted_proxy_count == 0 || parts.len() <= trusted_proxy_count {
271 return None;
272 }
273
274 let idx = parts.len() - 1 - trusted_proxy_count;
276 let ip = parts[idx].to_string();
277 if is_valid_ip(&ip) {
278 Some(ip)
279 } else {
280 None
281 }
282}
283
284#[cfg(feature = "server")]
285fn ip_from_x_real_ip(value: &str) -> Option<String> {
286 let ip = value.trim().to_string();
287 if is_valid_ip(&ip) {
288 Some(ip)
289 } else {
290 None
291 }
292}
293
294#[cfg(feature = "server")]
295fn get_client_ip_internal(
296 headers: &http::HeaderMap,
297 trusted: usize,
298 peer: Option<std::net::SocketAddr>,
299) -> String {
300 if trusted > 0 {
301 if let Some(value) = headers.get("x-forwarded-for").and_then(|v| v.to_str().ok()) {
302 if let Some(ip) = ip_from_x_forwarded_for(value, trusted) {
303 return ip;
304 }
305 }
306
307 if let Some(ip) = headers
308 .get("x-real-ip")
309 .and_then(|v| v.to_str().ok())
310 .and_then(ip_from_x_real_ip)
311 {
312 return ip;
313 }
314 }
315
316 if let Some(addr) = peer {
317 return addr.ip().to_string();
318 }
319
320 static UNKNOWN_IP_WARNED: Once = Once::new();
323 UNKNOWN_IP_WARNED.call_once(|| {
324 tracing::warn!(
325 "无法获取客户端真实 IP(TRUSTED_PROXY_COUNT=0 不解析代理头,\
326 且当前上下文无 TCP 对端地址,通常为 server function),限流将按 'unknown' 键聚合"
327 );
328 });
329 "unknown".to_string()
330}
331
332#[cfg(feature = "server")]
333pub async fn get_client_ip_with_peer(
338 headers: &http::HeaderMap,
339 peer: Option<std::net::SocketAddr>,
340) -> String {
341 get_client_ip_internal(headers, trusted_proxy_count().await, peer)
342}
343#[cfg(feature = "server")]
344pub async fn get_client_ip(headers: &http::HeaderMap) -> String {
349 get_client_ip_internal(headers, trusted_proxy_count().await, None)
350}
351
352#[cfg(feature = "server")]
353pub fn check_strict_limit(ip: &str) -> Result<(), String> {
360 ensure_limiter_gc();
361 if ip == "unknown" {
362 UNKNOWN_BUCKET_LIMITER
363 .check_key(&ip.to_string())
364 .map(|_| ())
365 .map_err(|_| "服务繁忙,请稍后再试".to_string())
366 } else {
367 STRICT_LIMITER
368 .check_key(&ip.to_string())
369 .map(|_| ())
370 .map_err(|_| "请求过于频繁,请稍后再试".to_string())
371 }
372}
373
374#[cfg(feature = "server")]
375pub fn check_suggest_limit(ip: &str) -> Result<(), String> {
377 ensure_limiter_gc();
378 let limiter = if ip == "unknown" {
379 &*UNKNOWN_BUCKET_LIMITER
380 } else {
381 &*SUGGEST_LIMITER
382 };
383 limiter
384 .check_key(&ip.to_string())
385 .map(|_| ())
386 .map_err(|_| "搜索过于频繁,请稍后再试".to_string())
387}
388
389#[cfg(feature = "server")]
390pub fn check_mcp_preauth_limit(ip: &str) -> Result<(), &'static str> {
392 ensure_limiter_gc();
393 MCP_PREAUTH_LIMITER
394 .check_key(&ip.to_string())
395 .map(|_| ())
396 .map_err(|_| "请求过于频繁,请稍后再试")
397}
398
399#[cfg(feature = "server")]
400pub fn check_upload_limit(ip: &str) -> Result<(), String> {
402 ensure_limiter_gc();
403 UPLOAD_LIMITER
404 .check_key(&ip.to_string())
405 .map(|_| ())
406 .map_err(|_| "上传过于频繁,请稍后再试".to_string())
407}
408
409#[cfg(feature = "server")]
410pub fn check_code_exec_limit(ip: &str) -> Result<(), String> {
414 ensure_limiter_gc();
415 CODE_EXEC_LIMITER
416 .check_key(&ip.to_string())
417 .map_err(|_| "请求过于频繁,请稍后再试".to_string())?;
418 CODE_EXEC_DAILY_LIMITER
419 .check_key(&ip.to_string())
420 .map_err(|_| "今日运行次数已达上限,请明天再试".to_string())?;
421 Ok(())
422}
423
424#[cfg(all(test, feature = "server"))]
425mod tests {
426 use super::*;
427 use http::HeaderMap;
428 use std::net::{IpAddr, Ipv4Addr, SocketAddr};
429
430 #[test]
431 fn get_client_ip_from_x_forwarded_for_with_one_trusted_proxy() {
432 let mut headers = HeaderMap::new();
433 headers.insert("x-forwarded-for", "1.2.3.4, 5.6.7.8".parse().unwrap());
434 assert_eq!(
435 get_client_ip_with_trusted_and_peer(&headers, 1, None),
436 "1.2.3.4"
437 );
438 }
439
440 #[test]
441 fn get_client_ip_from_x_forwarded_for_with_two_trusted_proxies() {
442 let mut headers = HeaderMap::new();
443 headers.insert(
444 "x-forwarded-for",
445 "1.2.3.4, 5.6.7.8, 9.10.11.12".parse().unwrap(),
446 );
447 assert_eq!(
448 get_client_ip_with_trusted_and_peer(&headers, 2, None),
449 "1.2.3.4"
450 );
451 }
452
453 #[test]
454 fn get_client_ip_ignores_x_forwarded_for_when_no_trusted_proxies() {
455 let mut headers = HeaderMap::new();
456 headers.insert("x-forwarded-for", "1.2.3.4, 5.6.7.8".parse().unwrap());
457 assert_eq!(
458 get_client_ip_with_trusted_and_peer(&headers, 0, None),
459 "unknown"
460 );
461 }
462
463 #[test]
464 fn get_client_ip_falls_back_to_peer_when_no_trusted_proxies() {
465 let mut headers = HeaderMap::new();
466 headers.insert("x-forwarded-for", "1.2.3.4, 5.6.7.8".parse().unwrap());
467 let peer = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12345);
468 assert_eq!(
469 get_client_ip_with_trusted_and_peer(&headers, 0, Some(peer)),
470 "127.0.0.1"
471 );
472 }
473
474 #[test]
475 fn get_client_ip_from_x_real_ip_when_trusted() {
476 let mut headers = HeaderMap::new();
477 headers.insert("x-real-ip", "9.8.7.6".parse().unwrap());
478 assert_eq!(
479 get_client_ip_with_trusted_and_peer(&headers, 1, None),
480 "9.8.7.6"
481 );
482 }
483
484 #[test]
485 fn get_client_ip_x_real_ip_ignored_when_not_trusted() {
486 let mut headers = HeaderMap::new();
487 headers.insert("x-real-ip", "9.8.7.6".parse().unwrap());
488 let peer = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 12345);
489 assert_eq!(
490 get_client_ip_with_trusted_and_peer(&headers, 0, Some(peer)),
491 "192.168.1.1"
492 );
493 }
494
495 #[test]
496 fn get_client_ip_x_forwarded_for_takes_priority_over_x_real_ip() {
497 let mut headers = HeaderMap::new();
498 headers.insert("x-forwarded-for", "1.1.1.1, 2.2.2.2".parse().unwrap());
499 headers.insert("x-real-ip", "3.3.3.3".parse().unwrap());
500 assert_eq!(
501 get_client_ip_with_trusted_and_peer(&headers, 1, None),
502 "1.1.1.1"
503 );
504 }
505
506 #[test]
507 fn get_client_ip_no_headers_returns_unknown() {
508 let headers = HeaderMap::new();
509 assert_eq!(
510 get_client_ip_with_trusted_and_peer(&headers, 1, None),
511 "unknown"
512 );
513 }
514
515 #[test]
516 fn get_client_ip_ignores_short_x_forwarded_for_list() {
517 let mut headers = HeaderMap::new();
518 headers.insert("x-forwarded-for", "1.2.3.4".parse().unwrap());
519 assert_eq!(
520 get_client_ip_with_trusted_and_peer(&headers, 2, None),
521 "unknown"
522 );
523 }
524
525 #[test]
526 fn get_client_ip_ignores_x_forwarded_for_equal_to_proxy_count() {
527 let mut headers = HeaderMap::new();
528 headers.insert("x-forwarded-for", "1.2.3.4, 5.6.7.8".parse().unwrap());
529 assert_eq!(
530 get_client_ip_with_trusted_and_peer(&headers, 2, None),
531 "unknown"
532 );
533 }
534
535 #[test]
536 fn get_client_ip_ignores_empty_x_forwarded_for_entries() {
537 let mut headers = HeaderMap::new();
538 headers.insert(
539 "x-forwarded-for",
540 " , 1.2.3.4 , 5.6.7.8 , ".parse().unwrap(),
541 );
542 assert_eq!(
543 get_client_ip_with_trusted_and_peer(&headers, 1, None),
544 "1.2.3.4"
545 );
546 }
547
548 #[test]
549 fn get_client_ip_rejects_invalid_x_forwarded_for_value() {
550 let mut headers = HeaderMap::new();
551 headers.insert("x-forwarded-for", "not-an-ip, 5.6.7.8".parse().unwrap());
552 assert_eq!(
553 get_client_ip_with_trusted_and_peer(&headers, 1, None),
554 "unknown"
555 );
556 }
557
558 #[test]
559 fn get_client_ip_rejects_invalid_x_real_ip_value() {
560 let mut headers = HeaderMap::new();
561 headers.insert("x-real-ip", "not-an-ip".parse().unwrap());
562 assert_eq!(
563 get_client_ip_with_trusted_and_peer(&headers, 1, None),
564 "unknown"
565 );
566 }
567
568 #[test]
569 fn get_client_ip_prefers_xff_over_peer() {
570 let mut headers = HeaderMap::new();
571 headers.insert("x-forwarded-for", "1.2.3.4, 5.6.7.8".parse().unwrap());
572 let peer = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12345);
573 assert_eq!(
574 get_client_ip_with_trusted_and_peer(&headers, 1, Some(peer)),
575 "1.2.3.4"
576 );
577 }
578
579 #[tokio::test]
580 async fn get_client_ip_defaults_to_unknown_without_db() {
581 let mut headers = HeaderMap::new();
584 headers.insert("x-forwarded-for", "1.2.3.4, 5.6.7.8".parse().unwrap());
585 assert_eq!(get_client_ip(&headers).await, "unknown");
586 }
587
588 #[test]
589 #[serial_test::serial]
590 fn check_strict_unknown_ip_uses_lenient_bucket() {
591 for _ in 0..20 {
594 assert!(
595 super::check_strict_limit("unknown").is_ok(),
596 "unknown bucket should allow small bursts, not hit strict 1 req/s limit"
597 );
598 }
599 }
600
601 #[test]
602 #[serial_test::serial]
603 fn check_strict_real_ip_uses_strict_bucket() {
604 let unique_ip = "198.51.100.42";
607 let mut allowed = 0;
608 let mut blocked = false;
609 for _ in 0..50 {
610 match super::check_strict_limit(unique_ip) {
611 Ok(()) => allowed += 1,
612 Err(_) => blocked = true,
613 }
614 if blocked {
615 break;
616 }
617 }
618 assert!(
619 blocked,
620 "strict bucket should eventually block real IP burst"
621 );
622 assert!(
623 allowed <= 6,
624 "strict burst is 5, allowed should be <= 6, got {allowed}"
625 );
626 }
627
628 #[test]
629 #[serial_test::serial]
630 fn check_mcp_preauth_blocks_after_burst() {
631 let unique_ip = "198.51.100.77";
633 let mut allowed = 0;
634 let mut blocked = false;
635 for _ in 0..200 {
636 match super::check_mcp_preauth_limit(unique_ip) {
637 Ok(()) => allowed += 1,
638 Err(_) => blocked = true,
639 }
640 }
641 assert!(blocked, "MCP 预鉴权桶应在突发后限流");
642 assert!(
643 allowed <= 101,
644 "突发额度为 100,放行数应 <= 101,实际 {allowed}"
645 );
646 }
647
648 fn get_client_ip_with_trusted_and_peer(
650 headers: &HeaderMap,
651 trusted: usize,
652 peer: Option<SocketAddr>,
653 ) -> String {
654 get_client_ip_internal(headers, trusted, peer)
655 }
656}