mirror of
https://github.com/telemt/telemt.git
synced 2026-05-24 04:31:43 +03:00
Cap scanner-sensitive Caches and IP-Tracker Cardinality
This commit is contained in:
@@ -14,6 +14,20 @@ use crate::tls_front::types::{
|
||||
};
|
||||
|
||||
const FULL_CERT_SENT_SWEEP_INTERVAL_SECS: u64 = 30;
|
||||
const FULL_CERT_SENT_MAX_IPS: usize = 65_536;
|
||||
|
||||
static FULL_CERT_SENT_IPS_GAUGE: AtomicU64 = AtomicU64::new(0);
|
||||
static FULL_CERT_SENT_CAP_DROPS: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Current number of IPs tracked by the TLS full-cert budget gate.
|
||||
pub(crate) fn full_cert_sent_ips_for_metrics() -> u64 {
|
||||
FULL_CERT_SENT_IPS_GAUGE.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Number of new IPs denied a full-cert budget slot because the cap was reached.
|
||||
pub(crate) fn full_cert_sent_cap_drops_for_metrics() -> u64 {
|
||||
FULL_CERT_SENT_CAP_DROPS.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Lightweight in-memory + optional on-disk cache for TLS fronting data.
|
||||
#[derive(Debug)]
|
||||
@@ -104,7 +118,7 @@ impl TlsFrontCache {
|
||||
guard.retain(|_, seen_at| now.duration_since(*seen_at) < ttl);
|
||||
}
|
||||
|
||||
match guard.get_mut(&client_ip) {
|
||||
let allowed = match guard.get_mut(&client_ip) {
|
||||
Some(seen_at) => {
|
||||
if now.duration_since(*seen_at) >= ttl {
|
||||
*seen_at = now;
|
||||
@@ -114,10 +128,17 @@ impl TlsFrontCache {
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if guard.len() >= FULL_CERT_SENT_MAX_IPS {
|
||||
FULL_CERT_SENT_CAP_DROPS.fetch_add(1, Ordering::Relaxed);
|
||||
FULL_CERT_SENT_IPS_GAUGE.store(guard.len() as u64, Ordering::Relaxed);
|
||||
return false;
|
||||
}
|
||||
guard.insert(client_ip, now);
|
||||
true
|
||||
}
|
||||
}
|
||||
};
|
||||
FULL_CERT_SENT_IPS_GAUGE.store(guard.len() as u64, Ordering::Relaxed);
|
||||
allowed
|
||||
}
|
||||
|
||||
pub async fn set(&self, domain: &str, data: CachedTlsData) {
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
use dashmap::DashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
@@ -144,12 +146,37 @@ enum FetchErrorKind {
|
||||
Other,
|
||||
}
|
||||
|
||||
const PROFILE_CACHE_MAX_ENTRIES: usize = 4096;
|
||||
|
||||
static PROFILE_CACHE: OnceLock<DashMap<ProfileCacheKey, ProfileCacheValue>> = OnceLock::new();
|
||||
static PROFILE_CACHE_INSERT_GUARD: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
static PROFILE_CACHE_CAP_DROPS: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
fn profile_cache() -> &'static DashMap<ProfileCacheKey, ProfileCacheValue> {
|
||||
PROFILE_CACHE.get_or_init(DashMap::new)
|
||||
}
|
||||
|
||||
fn profile_cache_insert_guard() -> &'static Mutex<()> {
|
||||
PROFILE_CACHE_INSERT_GUARD.get_or_init(|| Mutex::new(()))
|
||||
}
|
||||
|
||||
fn sweep_expired_profile_cache(ttl: Duration, now: Instant) {
|
||||
if ttl.is_zero() {
|
||||
return;
|
||||
}
|
||||
profile_cache().retain(|_, value| now.saturating_duration_since(value.updated_at) <= ttl);
|
||||
}
|
||||
|
||||
/// Current number of adaptive TLS fetch profile-cache entries.
|
||||
pub(crate) fn profile_cache_entries_for_metrics() -> usize {
|
||||
profile_cache().len()
|
||||
}
|
||||
|
||||
/// Number of fresh profile-cache winners skipped because the cache was full.
|
||||
pub(crate) fn profile_cache_cap_drops_for_metrics() -> u64 {
|
||||
PROFILE_CACHE_CAP_DROPS.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn route_hint(
|
||||
upstream: Option<&std::sync::Arc<crate::transport::UpstreamManager>>,
|
||||
unix_sock: Option<&str>,
|
||||
@@ -267,6 +294,43 @@ fn remember_profile_success(
|
||||
let Some(key) = cache_key else {
|
||||
return;
|
||||
};
|
||||
remember_profile_success_with_cap(strategy, key, profile, now, PROFILE_CACHE_MAX_ENTRIES);
|
||||
}
|
||||
|
||||
fn remember_profile_success_with_cap(
|
||||
strategy: &TlsFetchStrategy,
|
||||
key: ProfileCacheKey,
|
||||
profile: TlsFetchProfile,
|
||||
now: Instant,
|
||||
max_entries: usize,
|
||||
) {
|
||||
let Ok(_guard) = profile_cache_insert_guard().lock() else {
|
||||
PROFILE_CACHE_CAP_DROPS.fetch_add(1, Ordering::Relaxed);
|
||||
return;
|
||||
};
|
||||
if max_entries == 0 {
|
||||
PROFILE_CACHE_CAP_DROPS.fetch_add(1, Ordering::Relaxed);
|
||||
return;
|
||||
}
|
||||
if profile_cache().contains_key(&key) {
|
||||
profile_cache().insert(
|
||||
key,
|
||||
ProfileCacheValue {
|
||||
profile,
|
||||
updated_at: now,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
if profile_cache().len() >= max_entries {
|
||||
// TLS fetch is control-plane work; sweeping under a tiny mutex keeps
|
||||
// profile-cache cardinality hard-bounded without touching relay hot paths.
|
||||
sweep_expired_profile_cache(strategy.profile_cache_ttl, now);
|
||||
}
|
||||
if profile_cache().len() >= max_entries {
|
||||
PROFILE_CACHE_CAP_DROPS.fetch_add(1, Ordering::Relaxed);
|
||||
return;
|
||||
}
|
||||
profile_cache().insert(
|
||||
key,
|
||||
ProfileCacheValue {
|
||||
|
||||
Reference in New Issue
Block a user