diff --git a/src/config/load.rs b/src/config/load.rs index 60a6bc2..eeda2b0 100644 --- a/src/config/load.rs +++ b/src/config/load.rs @@ -194,6 +194,10 @@ impl ProxyConfig { validate_network_cfg(&mut config.network)?; + if config.general.use_middle_proxy && config.network.ipv6 == Some(true) { + warn!("IPv6 with Middle Proxy is experimental and may cause KDF address mismatch; consider disabling IPv6 or ME"); + } + // Random fake_cert_len only when default is in use. if !config.censorship.tls_emulation && config.censorship.fake_cert_len == default_fake_cert_len() { config.censorship.fake_cert_len = rand::rng().gen_range(1024..4096); diff --git a/src/main.rs b/src/main.rs index 320c2c5..20e00db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use std::net::SocketAddr; use std::sync::Arc; use std::time::Duration; +use rand::Rng; use tokio::net::TcpListener; use tokio::signal; use tokio::sync::Semaphore; @@ -275,21 +276,42 @@ async fn main() -> std::result::Result<(), Box> { &config.censorship.tls_front_dir, )); + let port = config.censorship.mask_port; + // Initial synchronous fetch to warm cache before serving clients. + for domain in tls_domains.clone() { + match crate::tls_front::fetcher::fetch_real_tls( + &domain, + port, + &domain, + Duration::from_secs(5), + ) + .await + { + Ok(res) => cache.update_from_fetch(&domain, res).await, + Err(e) => warn!(domain = %domain, error = %e, "TLS emulation fetch failed"), + } + } + + // Periodic refresh with jitter. let cache_clone = cache.clone(); let domains = tls_domains.clone(); - let port = config.censorship.mask_port; tokio::spawn(async move { - for domain in domains { - match crate::tls_front::fetcher::fetch_real_tls( - &domain, - port, - &domain, - Duration::from_secs(5), - ) - .await - { - Ok(res) => cache_clone.update_from_fetch(&domain, res).await, - Err(e) => warn!(domain = %domain, error = %e, "TLS emulation fetch failed"), + loop { + let base_secs = rand::rng().random_range(4 * 3600..=6 * 3600); + let jitter_secs = rand::rng().random_range(0..=7200); + tokio::time::sleep(Duration::from_secs(base_secs + jitter_secs)).await; + for domain in &domains { + match crate::tls_front::fetcher::fetch_real_tls( + domain, + port, + domain, + Duration::from_secs(5), + ) + .await + { + Ok(res) => cache_clone.update_from_fetch(domain, res).await, + Err(e) => warn!(domain = %domain, error = %e, "TLS emulation refresh failed"), + } } } }); diff --git a/src/protocol/tls.rs b/src/protocol/tls.rs index 39eb7e6..c0efc78 100644 --- a/src/protocol/tls.rs +++ b/src/protocol/tls.rs @@ -351,6 +351,9 @@ pub fn build_server_hello( fake_cert_len: usize, rng: &SecureRandom, ) -> Vec { + const MIN_APP_DATA: usize = 64; + const MAX_APP_DATA: usize = 16640; // RFC 8446 §5.2 upper bound + let fake_cert_len = fake_cert_len.max(MIN_APP_DATA).min(MAX_APP_DATA); let x25519_key = gen_fake_x25519_key(rng); // Build ServerHello @@ -373,7 +376,13 @@ pub fn build_server_hello( app_data_record.push(TLS_RECORD_APPLICATION); app_data_record.extend_from_slice(&TLS_VERSION); app_data_record.extend_from_slice(&(fake_cert_len as u16).to_be_bytes()); - app_data_record.extend_from_slice(&fake_cert); + if fake_cert_len > 17 { + app_data_record.extend_from_slice(&fake_cert[..fake_cert_len - 17]); + app_data_record.push(0x16); // inner content type marker + app_data_record.extend_from_slice(&rng.bytes(16)); // AEAD-like tag mimic + } else { + app_data_record.extend_from_slice(&fake_cert); + } // Combine all records let mut response = Vec::with_capacity( diff --git a/src/proxy/client.rs b/src/proxy/client.rs index 8a8ae81..525c0e9 100644 --- a/src/proxy/client.rs +++ b/src/proxy/client.rs @@ -31,6 +31,7 @@ use crate::stats::{ReplayChecker, Stats}; use crate::stream::{BufferPool, CryptoReader, CryptoWriter}; use crate::transport::middle_proxy::MePool; use crate::transport::{UpstreamManager, configure_client_socket, parse_proxy_protocol}; +use crate::transport::socket::normalize_ip; use crate::tls_front::TlsFrontCache; use crate::proxy::direct_relay::handle_via_direct; @@ -55,7 +56,7 @@ where S: AsyncRead + AsyncWrite + Unpin + Send + 'static, { stats.increment_connects_all(); - let mut real_peer = peer; + let mut real_peer = normalize_ip(peer); if config.server.proxy_protocol { match parse_proxy_protocol(&mut stream, peer).await { @@ -66,7 +67,7 @@ where version = info.version, "PROXY protocol header parsed" ); - real_peer = info.src_addr; + real_peer = normalize_ip(info.src_addr); } Err(e) => { stats.increment_connects_bad(); @@ -264,6 +265,7 @@ impl RunningClientHandler { pub async fn run(mut self) -> Result<()> { self.stats.increment_connects_all(); + self.peer = normalize_ip(self.peer); let peer = self.peer; let ip_tracker = self.ip_tracker.clone(); debug!(peer = %peer, "New connection"); @@ -310,7 +312,7 @@ impl RunningClientHandler { version = info.version, "PROXY protocol header parsed" ); - self.peer = info.src_addr; + self.peer = normalize_ip(info.src_addr); } Err(e) => { self.stats.increment_connects_bad(); diff --git a/src/proxy/masking.rs b/src/proxy/masking.rs index 3d3039a..78ef806 100644 --- a/src/proxy/masking.rs +++ b/src/proxy/masking.rs @@ -1,7 +1,7 @@ //! Masking - forward unrecognized traffic to mask host -use std::time::Duration; use std::str; +use std::time::Duration; use tokio::net::TcpStream; #[cfg(unix)] use tokio::net::UnixStream; @@ -11,9 +11,9 @@ use tracing::debug; use crate::config::ProxyConfig; const MASK_TIMEOUT: Duration = Duration::from_secs(5); - /// Maximum duration for the entire masking relay. - /// Limits resource consumption from slow-loris attacks and port scanners. - const MASK_RELAY_TIMEOUT: Duration = Duration::from_secs(60); +/// Maximum duration for the entire masking relay. +/// Limits resource consumption from slow-loris attacks and port scanners. +const MASK_RELAY_TIMEOUT: Duration = Duration::from_secs(60); const MASK_BUFFER_SIZE: usize = 8192; /// Detect client type based on initial data @@ -78,7 +78,9 @@ where match connect_result { Ok(Ok(stream)) => { let (mask_read, mask_write) = stream.into_split(); - relay_to_mask(reader, writer, mask_read, mask_write, initial_data).await; + if timeout(MASK_RELAY_TIMEOUT, relay_to_mask(reader, writer, mask_read, mask_write, initial_data)).await.is_err() { + debug!("Mask relay timed out (unix socket)"); + } } Ok(Err(e)) => { debug!(error = %e, "Failed to connect to mask unix socket"); @@ -110,7 +112,9 @@ where match connect_result { Ok(Ok(stream)) => { let (mask_read, mask_write) = stream.into_split(); - relay_to_mask(reader, writer, mask_read, mask_write, initial_data).await; + if timeout(MASK_RELAY_TIMEOUT, relay_to_mask(reader, writer, mask_read, mask_write, initial_data)).await.is_err() { + debug!("Mask relay timed out"); + } } Ok(Err(e)) => { debug!(error = %e, "Failed to connect to mask host"); diff --git a/src/tls_front/emulator.rs b/src/tls_front/emulator.rs index 8328884..2bf6872 100644 --- a/src/tls_front/emulator.rs +++ b/src/tls_front/emulator.rs @@ -5,6 +5,28 @@ use crate::protocol::constants::{ use crate::protocol::tls::{TLS_DIGEST_LEN, TLS_DIGEST_POS, gen_fake_x25519_key}; use crate::tls_front::types::CachedTlsData; +const MIN_APP_DATA: usize = 64; +const MAX_APP_DATA: usize = 16640; // RFC 8446 §5.2 allows up to 2^14 + 256 + +fn jitter_and_clamp_sizes(sizes: &[usize], rng: &SecureRandom) -> Vec { + sizes + .iter() + .map(|&size| { + let base = size.max(MIN_APP_DATA).min(MAX_APP_DATA); + let jitter_range = ((base as f64) * 0.03).round() as i64; + if jitter_range == 0 { + return base; + } + let mut rand_bytes = [0u8; 2]; + rand_bytes.copy_from_slice(&rng.bytes(2)); + let span = 2 * jitter_range + 1; + let delta = (u16::from_le_bytes(rand_bytes) as i64 % span) - jitter_range; + let adjusted = (base as i64 + delta).clamp(MIN_APP_DATA as i64, MAX_APP_DATA as i64); + adjusted as usize + }) + .collect() +} + /// Build a ServerHello + CCS + ApplicationData sequence using cached TLS metadata. pub fn build_emulated_server_hello( secret: &[u8], @@ -76,6 +98,7 @@ pub fn build_emulated_server_hello( if sizes.is_empty() { sizes.push(cached.total_app_data_len.max(1024)); } + let sizes = jitter_and_clamp_sizes(&sizes, rng); let mut app_data = Vec::new(); for size in sizes { @@ -83,7 +106,14 @@ pub fn build_emulated_server_hello( rec.push(TLS_RECORD_APPLICATION); rec.extend_from_slice(&TLS_VERSION); rec.extend_from_slice(&(size as u16).to_be_bytes()); - rec.extend_from_slice(&rng.bytes(size)); + if size > 17 { + let body_len = size - 17; + rec.extend_from_slice(&rng.bytes(body_len)); + rec.push(0x16); // inner content type marker (handshake) + rec.extend_from_slice(&rng.bytes(16)); // AEAD-like tag + } else { + rec.extend_from_slice(&rng.bytes(size)); + } app_data.extend_from_slice(&rec); } diff --git a/src/tls_front/fetcher.rs b/src/tls_front/fetcher.rs index 0ce8d6b..d39ae81 100644 --- a/src/tls_front/fetcher.rs +++ b/src/tls_front/fetcher.rs @@ -15,7 +15,7 @@ use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; use rustls::{DigitallySignedStruct, Error as RustlsError}; use crate::crypto::SecureRandom; -use crate::protocol::constants::{TLS_RECORD_APPLICATION, TLS_RECORD_HANDSHAKE, TLS_VERSION}; +use crate::protocol::constants::{TLS_RECORD_APPLICATION, TLS_RECORD_HANDSHAKE}; use crate::tls_front::types::{ParsedServerHello, TlsExtension, TlsFetchResult}; /// No-op verifier: accept any certificate (we only need lengths and metadata). @@ -163,12 +163,15 @@ fn build_client_hello(sni: &str, rng: &SecureRandom) -> Vec { exts.extend_from_slice(alpn_proto); // padding to reduce recognizability and keep length ~500 bytes - if exts.len() < 180 { - let pad_len = 180 - exts.len(); - exts.extend_from_slice(&0x0015u16.to_be_bytes()); // padding extension - exts.extend_from_slice(&(pad_len as u16 + 2).to_be_bytes()); - exts.extend_from_slice(&(pad_len as u16).to_be_bytes()); - exts.resize(exts.len() + pad_len, 0); + const TARGET_EXT_LEN: usize = 180; + if exts.len() < TARGET_EXT_LEN { + let remaining = TARGET_EXT_LEN - exts.len(); + if remaining > 4 { + let pad_len = remaining - 4; // minus type+len + exts.extend_from_slice(&0x0015u16.to_be_bytes()); // padding extension + exts.extend_from_slice(&(pad_len as u16).to_be_bytes()); + exts.resize(exts.len() + pad_len, 0); + } } // Extensions length prefix