diff --git a/src/config/hot_reload.rs b/src/config/hot_reload.rs index c4a4f44..841bc1a 100644 --- a/src/config/hot_reload.rs +++ b/src/config/hot_reload.rs @@ -620,6 +620,7 @@ fn warn_non_hot_changes(old: &ProxyConfig, new: &ProxyConfig, non_hot_changed: b || 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_dynamic != new.censorship.mask_dynamic || old.censorship.mask_host != new.censorship.mask_host || old.censorship.mask_port != new.censorship.mask_port || old.censorship.exclusive_mask != new.censorship.exclusive_mask diff --git a/src/config/load.rs b/src/config/load.rs index c6ad74e..b428079 100644 --- a/src/config/load.rs +++ b/src/config/load.rs @@ -372,6 +372,7 @@ const CENSORSHIP_CONFIG_KEYS: &[&str] = &[ "tls_fetch_scope", "tls_fetch", "mask", + "mask_dynamic", "mask_host", "mask_port", "exclusive_mask", @@ -2047,11 +2048,6 @@ impl ProxyConfig { *mask_host = normalize_mask_host_to_ascii(mask_host, "censorship.mask_host")?; } - // Default mask_host to tls_domain if not set and no unix socket configured. - if config.censorship.mask_host.is_none() && config.censorship.mask_unix_sock.is_none() { - config.censorship.mask_host = Some(config.censorship.tls_domain.clone()); - } - for (domain, target) in &config.censorship.exclusive_mask { if !is_valid_tls_domain_name(domain) { return Err(ProxyError::Config(format!( diff --git a/src/config/types.rs b/src/config/types.rs index e810240..d007428 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -1726,6 +1726,10 @@ pub struct AntiCensorshipConfig { #[serde(default = "default_true")] pub mask: bool, + /// Use the ClientHello SNI as the mask TCP target for configured TLS domains. + #[serde(default = "default_true")] + pub mask_dynamic: bool, + #[serde(default)] pub mask_host: Option, @@ -1861,6 +1865,7 @@ impl Default for AntiCensorshipConfig { tls_fetch_scope: default_tls_fetch_scope(), tls_fetch: TlsFetchConfig::default(), mask: default_true(), + mask_dynamic: default_true(), mask_host: None, mask_port: default_mask_port(), exclusive_mask: HashMap::new(), diff --git a/src/proxy/masking.rs b/src/proxy/masking.rs index e0631c8..7e73eb8 100644 --- a/src/proxy/masking.rs +++ b/src/proxy/masking.rs @@ -385,7 +385,7 @@ mod tls_domain_mask_host_tests { let mut config = ProxyConfig::default(); config.censorship.tls_domain = "a.com".to_string(); config.censorship.tls_domains = vec!["b.com".to_string(), "c.com".to_string()]; - config.censorship.mask_host = Some("a.com".to_string()); + config.censorship.mask_host = None; config } @@ -419,6 +419,15 @@ mod tls_domain_mask_host_tests { assert_eq!(mask_host_for_initial_data(&config, &initial_data), "b.com"); } + #[test] + fn mask_host_uses_primary_domain_when_dynamic_masking_is_disabled() { + let mut config = config_with_tls_domains(); + config.censorship.mask_dynamic = false; + let initial_data = client_hello_with_sni("b.com"); + + assert_eq!(mask_host_for_initial_data(&config, &initial_data), "a.com"); + } + #[test] fn exclusive_mask_target_overrides_only_matching_sni() { let mut config = config_with_tls_domains(); @@ -577,24 +586,32 @@ fn default_mask_tcp_target_for_initial_data<'a>( .as_deref() .unwrap_or(&config.censorship.tls_domain); - if !configured_mask_host.eq_ignore_ascii_case(&config.censorship.tls_domain) { + if config.censorship.mask_host.is_none() && config.censorship.mask_dynamic { + let extracted_sni = if sni.is_none() { + tls::extract_sni_from_client_hello(initial_data) + } else { + None + }; + if let Some(host) = sni + .or(extracted_sni.as_deref()) + .and_then(|sni| matching_tls_domain_for_sni(config, sni)) + { + return MaskTcpTarget { + host, + port: config.censorship.mask_port, + }; + } + } + + if let Some(mask_host) = config.censorship.mask_host.as_deref() { return MaskTcpTarget { - host: configured_mask_host, + host: mask_host, port: config.censorship.mask_port, }; } - let extracted_sni = if sni.is_none() { - tls::extract_sni_from_client_hello(initial_data) - } else { - None - }; - let host = sni - .or(extracted_sni.as_deref()) - .and_then(|sni| matching_tls_domain_for_sni(config, sni)) - .unwrap_or(configured_mask_host); MaskTcpTarget { - host, + host: configured_mask_host, port: config.censorship.mask_port, } }