This commit is contained in:
Alexey
2026-03-21 15:45:29 +03:00
parent 7a8f946029
commit d7bbb376c9
154 changed files with 6194 additions and 3775 deletions

View File

@@ -6,7 +6,7 @@ use std::time::{Duration, Instant, SystemTime};
use tokio::sync::RwLock;
use tokio::time::sleep;
use tracing::{debug, warn, info};
use tracing::{debug, info, warn};
use crate::tls_front::types::{
CachedTlsData, ParsedServerHello, TlsBehaviorProfile, TlsFetchResult,
@@ -59,7 +59,10 @@ impl TlsFrontCache {
pub async fn get(&self, sni: &str) -> Arc<CachedTlsData> {
let guard = self.memory.read().await;
guard.get(sni).cloned().unwrap_or_else(|| self.default.clone())
guard
.get(sni)
.cloned()
.unwrap_or_else(|| self.default.clone())
}
pub async fn contains_domain(&self, domain: &str) -> bool {
@@ -68,11 +71,7 @@ impl TlsFrontCache {
/// Returns true when full cert payload should be sent for client_ip
/// according to TTL policy.
pub async fn take_full_cert_budget_for_ip(
&self,
client_ip: IpAddr,
ttl: Duration,
) -> bool {
pub async fn take_full_cert_budget_for_ip(&self, client_ip: IpAddr, ttl: Duration) -> bool {
if ttl.is_zero() {
self.full_cert_sent
.write()
@@ -123,7 +122,10 @@ impl TlsFrontCache {
{
if cached.domain.is_empty()
|| cached.domain.len() > 255
|| !cached.domain.chars().all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-')
|| !cached
.domain
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-')
{
warn!(file = %name, "Skipping TLS cache entry with invalid domain");
continue;
@@ -166,12 +168,8 @@ impl TlsFrontCache {
}
/// Spawn background updater that periodically refreshes cached domains using provided fetcher.
pub fn spawn_updater<F>(
self: Arc<Self>,
domains: Vec<String>,
interval: Duration,
fetcher: F,
) where
pub fn spawn_updater<F>(self: Arc<Self>, domains: Vec<String>, interval: Duration, fetcher: F)
where
F: Fn(String) -> tokio::task::JoinHandle<()> + Send + Sync + 'static,
{
tokio::spawn(async move {
@@ -217,43 +215,25 @@ mod tests {
#[tokio::test]
async fn test_take_full_cert_budget_for_ip_uses_ttl() {
let cache = TlsFrontCache::new(
&["example.com".to_string()],
1024,
"tlsfront-test-cache",
);
let cache = TlsFrontCache::new(&["example.com".to_string()], 1024, "tlsfront-test-cache");
let ip: IpAddr = "127.0.0.1".parse().expect("ip");
let ttl = Duration::from_millis(80);
assert!(cache
.take_full_cert_budget_for_ip(ip, ttl)
.await);
assert!(!cache
.take_full_cert_budget_for_ip(ip, ttl)
.await);
assert!(cache.take_full_cert_budget_for_ip(ip, ttl).await);
assert!(!cache.take_full_cert_budget_for_ip(ip, ttl).await);
tokio::time::sleep(Duration::from_millis(90)).await;
assert!(cache
.take_full_cert_budget_for_ip(ip, ttl)
.await);
assert!(cache.take_full_cert_budget_for_ip(ip, ttl).await);
}
#[tokio::test]
async fn test_take_full_cert_budget_for_ip_zero_ttl_always_allows_full_payload() {
let cache = TlsFrontCache::new(
&["example.com".to_string()],
1024,
"tlsfront-test-cache",
);
let cache = TlsFrontCache::new(&["example.com".to_string()], 1024, "tlsfront-test-cache");
let ip: IpAddr = "127.0.0.1".parse().expect("ip");
let ttl = Duration::ZERO;
assert!(cache
.take_full_cert_budget_for_ip(ip, ttl)
.await);
assert!(cache
.take_full_cert_budget_for_ip(ip, ttl)
.await);
assert!(cache.take_full_cert_budget_for_ip(ip, ttl).await);
assert!(cache.take_full_cert_budget_for_ip(ip, ttl).await);
}
}

View File

@@ -1,7 +1,7 @@
use crate::crypto::{sha256_hmac, SecureRandom};
use crate::crypto::{SecureRandom, sha256_hmac};
use crate::protocol::constants::{
MAX_TLS_CIPHERTEXT_SIZE,
TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER, TLS_RECORD_HANDSHAKE, TLS_VERSION,
MAX_TLS_CIPHERTEXT_SIZE, TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER,
TLS_RECORD_HANDSHAKE, TLS_VERSION,
};
use crate::protocol::tls::{TLS_DIGEST_LEN, TLS_DIGEST_POS, gen_fake_x25519_key};
use crate::tls_front::types::{CachedTlsData, ParsedCertificateInfo, TlsProfileSource};
@@ -167,7 +167,13 @@ pub fn build_emulated_server_hello(
.app_data_records_sizes
.first()
.copied()
.or_else(|| cached.behavior_profile.app_data_record_sizes.first().copied())
.or_else(|| {
cached
.behavior_profile
.app_data_record_sizes
.first()
.copied()
})
.map(|size| vec![size])
.unwrap_or_else(|| vec![cached.total_app_data_len.max(1024)]),
_ => {
@@ -240,7 +246,9 @@ pub fn build_emulated_server_hello(
} else if size > 17 {
let body_len = size - 17;
let mut body = Vec::with_capacity(body_len);
if idx == 0 && let Some(marker) = &alpn_marker {
if idx == 0
&& let Some(marker) = &alpn_marker
{
if marker.len() <= body_len {
body.extend_from_slice(marker);
if body_len > marker.len() {
@@ -277,7 +285,9 @@ pub fn build_emulated_server_hello(
}
}
let mut response = Vec::with_capacity(server_hello.len() + change_cipher_spec.len() + app_data.len() + tickets.len());
let mut response = Vec::with_capacity(
server_hello.len() + change_cipher_spec.len() + app_data.len() + tickets.len(),
);
response.extend_from_slice(&server_hello);
response.extend_from_slice(&change_cipher_spec);
response.extend_from_slice(&app_data);
@@ -314,9 +324,11 @@ mod tests {
fn first_app_data_payload(response: &[u8]) -> &[u8] {
let hello_len = u16::from_be_bytes([response[3], response[4]]) as usize;
let ccs_start = 5 + hello_len;
let ccs_len = u16::from_be_bytes([response[ccs_start + 3], response[ccs_start + 4]]) as usize;
let ccs_len =
u16::from_be_bytes([response[ccs_start + 3], response[ccs_start + 4]]) as usize;
let app_start = ccs_start + 5 + ccs_len;
let app_len = u16::from_be_bytes([response[app_start + 3], response[app_start + 4]]) as usize;
let app_len =
u16::from_be_bytes([response[app_start + 3], response[app_start + 4]]) as usize;
&response[app_start + 5..app_start + 5 + app_len]
}
@@ -445,7 +457,8 @@ mod tests {
let hello_len = u16::from_be_bytes([response[3], response[4]]) as usize;
let ccs_start = 5 + hello_len;
let app_start = ccs_start + 6;
let app_len = u16::from_be_bytes([response[app_start + 3], response[app_start + 4]]) as usize;
let app_len =
u16::from_be_bytes([response[app_start + 3], response[app_start + 4]]) as usize;
assert_eq!(response[app_start], TLS_RECORD_APPLICATION);
assert_eq!(app_start + 5 + app_len, response.len());

View File

@@ -1,7 +1,7 @@
pub mod types;
pub mod cache;
pub mod fetcher;
pub mod emulator;
pub mod fetcher;
pub mod types;
pub use cache::TlsFrontCache;
#[allow(unused_imports)]

View File

@@ -1,7 +1,9 @@
use std::time::SystemTime;
use crate::crypto::SecureRandom;
use crate::protocol::constants::{TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER, TLS_RECORD_HANDSHAKE};
use crate::protocol::constants::{
TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER, TLS_RECORD_HANDSHAKE,
};
use crate::tls_front::emulator::build_emulated_server_hello;
use crate::tls_front::types::{
CachedTlsData, ParsedServerHello, TlsBehaviorProfile, TlsCertPayload, TlsProfileSource,

View File

@@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
use serde::{Serialize, Deserialize};
/// Parsed representation of an unencrypted TLS ServerHello.
#[derive(Debug, Clone, Serialize, Deserialize)]