mirror of https://github.com/telemt/telemt.git
Normalize IP + Masking + TLS
This commit is contained in:
parent
be2ec4b9b4
commit
2ea4c83d9d
|
|
@ -194,6 +194,10 @@ impl ProxyConfig {
|
||||||
|
|
||||||
validate_network_cfg(&mut config.network)?;
|
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.
|
// Random fake_cert_len only when default is in use.
|
||||||
if !config.censorship.tls_emulation && config.censorship.fake_cert_len == default_fake_cert_len() {
|
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);
|
config.censorship.fake_cert_len = rand::rng().gen_range(1024..4096);
|
||||||
|
|
|
||||||
32
src/main.rs
32
src/main.rs
|
|
@ -3,6 +3,7 @@
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use rand::Rng;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
|
|
@ -275,11 +276,9 @@ async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||||
&config.censorship.tls_front_dir,
|
&config.censorship.tls_front_dir,
|
||||||
));
|
));
|
||||||
|
|
||||||
let cache_clone = cache.clone();
|
|
||||||
let domains = tls_domains.clone();
|
|
||||||
let port = config.censorship.mask_port;
|
let port = config.censorship.mask_port;
|
||||||
tokio::spawn(async move {
|
// Initial synchronous fetch to warm cache before serving clients.
|
||||||
for domain in domains {
|
for domain in tls_domains.clone() {
|
||||||
match crate::tls_front::fetcher::fetch_real_tls(
|
match crate::tls_front::fetcher::fetch_real_tls(
|
||||||
&domain,
|
&domain,
|
||||||
port,
|
port,
|
||||||
|
|
@ -288,10 +287,33 @@ async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(res) => cache_clone.update_from_fetch(&domain, res).await,
|
Ok(res) => cache.update_from_fetch(&domain, res).await,
|
||||||
Err(e) => warn!(domain = %domain, error = %e, "TLS emulation fetch failed"),
|
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();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(cache)
|
Some(cache)
|
||||||
|
|
|
||||||
|
|
@ -351,6 +351,9 @@ pub fn build_server_hello(
|
||||||
fake_cert_len: usize,
|
fake_cert_len: usize,
|
||||||
rng: &SecureRandom,
|
rng: &SecureRandom,
|
||||||
) -> Vec<u8> {
|
) -> Vec<u8> {
|
||||||
|
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);
|
let x25519_key = gen_fake_x25519_key(rng);
|
||||||
|
|
||||||
// Build ServerHello
|
// Build ServerHello
|
||||||
|
|
@ -373,7 +376,13 @@ pub fn build_server_hello(
|
||||||
app_data_record.push(TLS_RECORD_APPLICATION);
|
app_data_record.push(TLS_RECORD_APPLICATION);
|
||||||
app_data_record.extend_from_slice(&TLS_VERSION);
|
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_len as u16).to_be_bytes());
|
||||||
|
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);
|
app_data_record.extend_from_slice(&fake_cert);
|
||||||
|
}
|
||||||
|
|
||||||
// Combine all records
|
// Combine all records
|
||||||
let mut response = Vec::with_capacity(
|
let mut response = Vec::with_capacity(
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ use crate::stats::{ReplayChecker, Stats};
|
||||||
use crate::stream::{BufferPool, CryptoReader, CryptoWriter};
|
use crate::stream::{BufferPool, CryptoReader, CryptoWriter};
|
||||||
use crate::transport::middle_proxy::MePool;
|
use crate::transport::middle_proxy::MePool;
|
||||||
use crate::transport::{UpstreamManager, configure_client_socket, parse_proxy_protocol};
|
use crate::transport::{UpstreamManager, configure_client_socket, parse_proxy_protocol};
|
||||||
|
use crate::transport::socket::normalize_ip;
|
||||||
use crate::tls_front::TlsFrontCache;
|
use crate::tls_front::TlsFrontCache;
|
||||||
|
|
||||||
use crate::proxy::direct_relay::handle_via_direct;
|
use crate::proxy::direct_relay::handle_via_direct;
|
||||||
|
|
@ -55,7 +56,7 @@ where
|
||||||
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||||
{
|
{
|
||||||
stats.increment_connects_all();
|
stats.increment_connects_all();
|
||||||
let mut real_peer = peer;
|
let mut real_peer = normalize_ip(peer);
|
||||||
|
|
||||||
if config.server.proxy_protocol {
|
if config.server.proxy_protocol {
|
||||||
match parse_proxy_protocol(&mut stream, peer).await {
|
match parse_proxy_protocol(&mut stream, peer).await {
|
||||||
|
|
@ -66,7 +67,7 @@ where
|
||||||
version = info.version,
|
version = info.version,
|
||||||
"PROXY protocol header parsed"
|
"PROXY protocol header parsed"
|
||||||
);
|
);
|
||||||
real_peer = info.src_addr;
|
real_peer = normalize_ip(info.src_addr);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
stats.increment_connects_bad();
|
stats.increment_connects_bad();
|
||||||
|
|
@ -264,6 +265,7 @@ impl RunningClientHandler {
|
||||||
pub async fn run(mut self) -> Result<()> {
|
pub async fn run(mut self) -> Result<()> {
|
||||||
self.stats.increment_connects_all();
|
self.stats.increment_connects_all();
|
||||||
|
|
||||||
|
self.peer = normalize_ip(self.peer);
|
||||||
let peer = self.peer;
|
let peer = self.peer;
|
||||||
let ip_tracker = self.ip_tracker.clone();
|
let ip_tracker = self.ip_tracker.clone();
|
||||||
debug!(peer = %peer, "New connection");
|
debug!(peer = %peer, "New connection");
|
||||||
|
|
@ -310,7 +312,7 @@ impl RunningClientHandler {
|
||||||
version = info.version,
|
version = info.version,
|
||||||
"PROXY protocol header parsed"
|
"PROXY protocol header parsed"
|
||||||
);
|
);
|
||||||
self.peer = info.src_addr;
|
self.peer = normalize_ip(info.src_addr);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.stats.increment_connects_bad();
|
self.stats.increment_connects_bad();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! Masking - forward unrecognized traffic to mask host
|
//! Masking - forward unrecognized traffic to mask host
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::str;
|
use std::str;
|
||||||
|
use std::time::Duration;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use tokio::net::UnixStream;
|
use tokio::net::UnixStream;
|
||||||
|
|
@ -78,7 +78,9 @@ where
|
||||||
match connect_result {
|
match connect_result {
|
||||||
Ok(Ok(stream)) => {
|
Ok(Ok(stream)) => {
|
||||||
let (mask_read, mask_write) = stream.into_split();
|
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)) => {
|
Ok(Err(e)) => {
|
||||||
debug!(error = %e, "Failed to connect to mask unix socket");
|
debug!(error = %e, "Failed to connect to mask unix socket");
|
||||||
|
|
@ -110,7 +112,9 @@ where
|
||||||
match connect_result {
|
match connect_result {
|
||||||
Ok(Ok(stream)) => {
|
Ok(Ok(stream)) => {
|
||||||
let (mask_read, mask_write) = stream.into_split();
|
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)) => {
|
Ok(Err(e)) => {
|
||||||
debug!(error = %e, "Failed to connect to mask host");
|
debug!(error = %e, "Failed to connect to mask host");
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,28 @@ use crate::protocol::constants::{
|
||||||
use crate::protocol::tls::{TLS_DIGEST_LEN, TLS_DIGEST_POS, gen_fake_x25519_key};
|
use crate::protocol::tls::{TLS_DIGEST_LEN, TLS_DIGEST_POS, gen_fake_x25519_key};
|
||||||
use crate::tls_front::types::CachedTlsData;
|
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<usize> {
|
||||||
|
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.
|
/// Build a ServerHello + CCS + ApplicationData sequence using cached TLS metadata.
|
||||||
pub fn build_emulated_server_hello(
|
pub fn build_emulated_server_hello(
|
||||||
secret: &[u8],
|
secret: &[u8],
|
||||||
|
|
@ -76,6 +98,7 @@ pub fn build_emulated_server_hello(
|
||||||
if sizes.is_empty() {
|
if sizes.is_empty() {
|
||||||
sizes.push(cached.total_app_data_len.max(1024));
|
sizes.push(cached.total_app_data_len.max(1024));
|
||||||
}
|
}
|
||||||
|
let sizes = jitter_and_clamp_sizes(&sizes, rng);
|
||||||
|
|
||||||
let mut app_data = Vec::new();
|
let mut app_data = Vec::new();
|
||||||
for size in sizes {
|
for size in sizes {
|
||||||
|
|
@ -83,7 +106,14 @@ pub fn build_emulated_server_hello(
|
||||||
rec.push(TLS_RECORD_APPLICATION);
|
rec.push(TLS_RECORD_APPLICATION);
|
||||||
rec.extend_from_slice(&TLS_VERSION);
|
rec.extend_from_slice(&TLS_VERSION);
|
||||||
rec.extend_from_slice(&(size as u16).to_be_bytes());
|
rec.extend_from_slice(&(size as u16).to_be_bytes());
|
||||||
|
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));
|
rec.extend_from_slice(&rng.bytes(size));
|
||||||
|
}
|
||||||
app_data.extend_from_slice(&rec);
|
app_data.extend_from_slice(&rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
|
||||||
use rustls::{DigitallySignedStruct, Error as RustlsError};
|
use rustls::{DigitallySignedStruct, Error as RustlsError};
|
||||||
|
|
||||||
use crate::crypto::SecureRandom;
|
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};
|
use crate::tls_front::types::{ParsedServerHello, TlsExtension, TlsFetchResult};
|
||||||
|
|
||||||
/// No-op verifier: accept any certificate (we only need lengths and metadata).
|
/// No-op verifier: accept any certificate (we only need lengths and metadata).
|
||||||
|
|
@ -163,13 +163,16 @@ fn build_client_hello(sni: &str, rng: &SecureRandom) -> Vec<u8> {
|
||||||
exts.extend_from_slice(alpn_proto);
|
exts.extend_from_slice(alpn_proto);
|
||||||
|
|
||||||
// padding to reduce recognizability and keep length ~500 bytes
|
// padding to reduce recognizability and keep length ~500 bytes
|
||||||
if exts.len() < 180 {
|
const TARGET_EXT_LEN: usize = 180;
|
||||||
let pad_len = 180 - exts.len();
|
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(&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.extend_from_slice(&(pad_len as u16).to_be_bytes());
|
||||||
exts.resize(exts.len() + pad_len, 0);
|
exts.resize(exts.len() + pad_len, 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Extensions length prefix
|
// Extensions length prefix
|
||||||
body.extend_from_slice(&(exts.len() as u16).to_be_bytes());
|
body.extend_from_slice(&(exts.len() as u16).to_be_bytes());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue