diff --git a/src/config/defaults.rs b/src/config/defaults.rs index fea8305..be540b0 100644 --- a/src/config/defaults.rs +++ b/src/config/defaults.rs @@ -65,6 +65,10 @@ pub(crate) fn default_tls_domain() -> String { "petrovich.ru".to_string() } +pub(crate) fn default_tls_fetch_scope() -> String { + String::new() +} + pub(crate) fn default_mask_port() -> u16 { 443 } diff --git a/src/config/hot_reload.rs b/src/config/hot_reload.rs index 1315f9c..4cf7676 100644 --- a/src/config/hot_reload.rs +++ b/src/config/hot_reload.rs @@ -623,6 +623,7 @@ fn warn_non_hot_changes(old: &ProxyConfig, new: &ProxyConfig, non_hot_changed: b } if old.censorship.tls_domain != new.censorship.tls_domain || old.censorship.tls_domains != new.censorship.tls_domains + || old.censorship.tls_fetch_scope != new.censorship.tls_fetch_scope || old.censorship.mask != new.censorship.mask || old.censorship.mask_host != new.censorship.mask_host || old.censorship.mask_port != new.censorship.mask_port diff --git a/src/config/load.rs b/src/config/load.rs index 14799ed..fbd2b33 100644 --- a/src/config/load.rs +++ b/src/config/load.rs @@ -779,6 +779,9 @@ impl ProxyConfig { config.censorship.mask_host = Some(config.censorship.tls_domain.clone()); } + // Normalize optional TLS fetch scope: whitespace-only values disable scoped routing. + config.censorship.tls_fetch_scope = config.censorship.tls_fetch_scope.trim().to_string(); + // Merge primary + extra TLS domains, deduplicate (primary always first). if !config.censorship.tls_domains.is_empty() { let mut all = Vec::with_capacity(1 + config.censorship.tls_domains.len()); @@ -2097,6 +2100,59 @@ mod tests { let _ = std::fs::remove_file(path); } + #[test] + fn tls_fetch_scope_default_is_empty() { + let toml = r#" + [censorship] + tls_domain = "example.com" + + [access.users] + user = "00000000000000000000000000000000" + "#; + let dir = std::env::temp_dir(); + let path = dir.join("telemt_tls_fetch_scope_default_test.toml"); + std::fs::write(&path, toml).unwrap(); + let cfg = ProxyConfig::load(&path).unwrap(); + assert!(cfg.censorship.tls_fetch_scope.is_empty()); + let _ = std::fs::remove_file(path); + } + + #[test] + fn tls_fetch_scope_is_trimmed_during_load() { + let toml = r#" + [censorship] + tls_domain = "example.com" + tls_fetch_scope = " me " + + [access.users] + user = "00000000000000000000000000000000" + "#; + let dir = std::env::temp_dir(); + let path = dir.join("telemt_tls_fetch_scope_trim_test.toml"); + std::fs::write(&path, toml).unwrap(); + let cfg = ProxyConfig::load(&path).unwrap(); + assert_eq!(cfg.censorship.tls_fetch_scope, "me"); + let _ = std::fs::remove_file(path); + } + + #[test] + fn tls_fetch_scope_whitespace_becomes_empty() { + let toml = r#" + [censorship] + tls_domain = "example.com" + tls_fetch_scope = " " + + [access.users] + user = "00000000000000000000000000000000" + "#; + let dir = std::env::temp_dir(); + let path = dir.join("telemt_tls_fetch_scope_blank_test.toml"); + std::fs::write(&path, toml).unwrap(); + let cfg = ProxyConfig::load(&path).unwrap(); + assert!(cfg.censorship.tls_fetch_scope.is_empty()); + let _ = std::fs::remove_file(path); + } + #[test] fn invalid_ad_tag_is_disabled_during_load() { let toml = r#" diff --git a/src/config/types.rs b/src/config/types.rs index d018187..c99000d 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -1308,6 +1308,11 @@ pub struct AntiCensorshipConfig { #[serde(default)] pub tls_domains: Vec, + /// Upstream scope used for TLS front metadata fetches. + /// Empty value keeps default upstream routing behavior. + #[serde(default = "default_tls_fetch_scope")] + pub tls_fetch_scope: String, + #[serde(default = "default_true")] pub mask: bool, @@ -1365,6 +1370,7 @@ impl Default for AntiCensorshipConfig { Self { tls_domain: default_tls_domain(), tls_domains: Vec::new(), + tls_fetch_scope: default_tls_fetch_scope(), mask: default_true(), mask_host: None, mask_port: default_mask_port(), diff --git a/src/maestro/tls_bootstrap.rs b/src/maestro/tls_bootstrap.rs index a0b0b5a..73eec4c 100644 --- a/src/maestro/tls_bootstrap.rs +++ b/src/maestro/tls_bootstrap.rs @@ -38,12 +38,15 @@ pub(crate) async fn bootstrap_tls_front( .clone() .unwrap_or_else(|| config.censorship.tls_domain.clone()); let mask_unix_sock = config.censorship.mask_unix_sock.clone(); + let tls_fetch_scope = (!config.censorship.tls_fetch_scope.is_empty()) + .then(|| config.censorship.tls_fetch_scope.clone()); let fetch_timeout = Duration::from_secs(5); let cache_initial = cache.clone(); let domains_initial = tls_domains.to_vec(); let host_initial = mask_host.clone(); let unix_sock_initial = mask_unix_sock.clone(); + let scope_initial = tls_fetch_scope.clone(); let upstream_initial = upstream_manager.clone(); tokio::spawn(async move { let mut join = tokio::task::JoinSet::new(); @@ -51,6 +54,7 @@ pub(crate) async fn bootstrap_tls_front( let cache_domain = cache_initial.clone(); let host_domain = host_initial.clone(); let unix_sock_domain = unix_sock_initial.clone(); + let scope_domain = scope_initial.clone(); let upstream_domain = upstream_initial.clone(); join.spawn(async move { match crate::tls_front::fetcher::fetch_real_tls( @@ -59,6 +63,7 @@ pub(crate) async fn bootstrap_tls_front( &domain, fetch_timeout, Some(upstream_domain), + scope_domain.as_deref(), proxy_protocol, unix_sock_domain.as_deref(), ) @@ -100,6 +105,7 @@ pub(crate) async fn bootstrap_tls_front( let domains_refresh = tls_domains.to_vec(); let host_refresh = mask_host.clone(); let unix_sock_refresh = mask_unix_sock.clone(); + let scope_refresh = tls_fetch_scope.clone(); let upstream_refresh = upstream_manager.clone(); tokio::spawn(async move { loop { @@ -112,6 +118,7 @@ pub(crate) async fn bootstrap_tls_front( let cache_domain = cache_refresh.clone(); let host_domain = host_refresh.clone(); let unix_sock_domain = unix_sock_refresh.clone(); + let scope_domain = scope_refresh.clone(); let upstream_domain = upstream_refresh.clone(); join.spawn(async move { match crate::tls_front::fetcher::fetch_real_tls( @@ -120,6 +127,7 @@ pub(crate) async fn bootstrap_tls_front( &domain, fetch_timeout, Some(upstream_domain), + scope_domain.as_deref(), proxy_protocol, unix_sock_domain.as_deref(), ) diff --git a/src/tls_front/fetcher.rs b/src/tls_front/fetcher.rs index 38872af..366e5d3 100644 --- a/src/tls_front/fetcher.rs +++ b/src/tls_front/fetcher.rs @@ -394,15 +394,17 @@ async fn connect_tcp_with_upstream( port: u16, connect_timeout: Duration, upstream: Option>, + scope: Option<&str>, ) -> Result { if let Some(manager) = upstream { if let Some(addr) = resolve_socket_addr(host, port) { - match manager.connect(addr, None, None).await { + match manager.connect(addr, None, scope).await { Ok(stream) => return Ok(stream), Err(e) => { warn!( host = %host, port = port, + scope = ?scope, error = %e, "Upstream connect failed, using direct connect" ); @@ -410,12 +412,13 @@ async fn connect_tcp_with_upstream( } } else if let Ok(mut addrs) = tokio::net::lookup_host((host, port)).await { if let Some(addr) = addrs.find(|a| a.is_ipv4()) { - match manager.connect(addr, None, None).await { + match manager.connect(addr, None, scope).await { Ok(stream) => return Ok(stream), Err(e) => { warn!( host = %host, port = port, + scope = ?scope, error = %e, "Upstream connect failed, using direct connect" ); @@ -537,6 +540,7 @@ async fn fetch_via_raw_tls( sni: &str, connect_timeout: Duration, upstream: Option>, + scope: Option<&str>, proxy_protocol: u8, unix_sock: Option<&str>, ) -> Result { @@ -572,7 +576,7 @@ async fn fetch_via_raw_tls( #[cfg(not(unix))] let _ = unix_sock; - let stream = connect_tcp_with_upstream(host, port, connect_timeout, upstream).await?; + let stream = connect_tcp_with_upstream(host, port, connect_timeout, upstream, scope).await?; fetch_via_raw_tls_stream(stream, sni, connect_timeout, proxy_protocol).await } @@ -675,6 +679,7 @@ async fn fetch_via_rustls( sni: &str, connect_timeout: Duration, upstream: Option>, + scope: Option<&str>, proxy_protocol: u8, unix_sock: Option<&str>, ) -> Result { @@ -710,7 +715,7 @@ async fn fetch_via_rustls( #[cfg(not(unix))] let _ = unix_sock; - let stream = connect_tcp_with_upstream(host, port, connect_timeout, upstream).await?; + let stream = connect_tcp_with_upstream(host, port, connect_timeout, upstream, scope).await?; fetch_via_rustls_stream(stream, host, sni, proxy_protocol).await } @@ -726,6 +731,7 @@ pub async fn fetch_real_tls( sni: &str, connect_timeout: Duration, upstream: Option>, + scope: Option<&str>, proxy_protocol: u8, unix_sock: Option<&str>, ) -> Result { @@ -735,6 +741,7 @@ pub async fn fetch_real_tls( sni, connect_timeout, upstream.clone(), + scope, proxy_protocol, unix_sock, ) @@ -753,6 +760,7 @@ pub async fn fetch_real_tls( sni, connect_timeout, upstream, + scope, proxy_protocol, unix_sock, )