mirror of
https://github.com/telemt/telemt.git
synced 2026-04-18 02:54:10 +03:00
STUN switch + Ad-tag fixes + DNS-overrides
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
//! | `general` | `me_pool_drain_ttl_secs` | Applied on next ME map update |
|
||||
//! | `general` | `me_pool_min_fresh_ratio` | Applied on next ME map update |
|
||||
//! | `general` | `me_reinit_drain_timeout_secs`| Applied on next ME map update |
|
||||
//! | `network` | `dns_overrides` | Applied immediately |
|
||||
//! | `access` | All user/quota fields | Effective immediately |
|
||||
//!
|
||||
//! Fields that require re-binding sockets (`server.port`, `censorship.*`,
|
||||
@@ -39,6 +40,7 @@ use super::load::ProxyConfig;
|
||||
pub struct HotFields {
|
||||
pub log_level: LogLevel,
|
||||
pub ad_tag: Option<String>,
|
||||
pub dns_overrides: Vec<String>,
|
||||
pub middle_proxy_pool_size: usize,
|
||||
pub desync_all_full: bool,
|
||||
pub update_every_secs: u64,
|
||||
@@ -58,6 +60,7 @@ impl HotFields {
|
||||
Self {
|
||||
log_level: cfg.general.log_level.clone(),
|
||||
ad_tag: cfg.general.ad_tag.clone(),
|
||||
dns_overrides: cfg.network.dns_overrides.clone(),
|
||||
middle_proxy_pool_size: cfg.general.middle_proxy_pool_size,
|
||||
desync_all_full: cfg.general.desync_all_full,
|
||||
update_every_secs: cfg.general.effective_update_every_secs(),
|
||||
@@ -189,6 +192,13 @@ fn log_changes(
|
||||
);
|
||||
}
|
||||
|
||||
if old_hot.dns_overrides != new_hot.dns_overrides {
|
||||
info!(
|
||||
"config reload: network.dns_overrides updated ({} entries)",
|
||||
new_hot.dns_overrides.len()
|
||||
);
|
||||
}
|
||||
|
||||
if old_hot.middle_proxy_pool_size != new_hot.middle_proxy_pool_size {
|
||||
info!(
|
||||
"config reload: middle_proxy_pool_size: {} → {}",
|
||||
@@ -354,6 +364,16 @@ fn reload_config(
|
||||
return;
|
||||
}
|
||||
|
||||
if old_hot.dns_overrides != new_hot.dns_overrides
|
||||
&& let Err(e) = crate::network::dns_overrides::install_entries(&new_hot.dns_overrides)
|
||||
{
|
||||
error!(
|
||||
"config reload: invalid network.dns_overrides: {}; keeping old config",
|
||||
e
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
warn_non_hot_changes(&old_cfg, &new_cfg);
|
||||
log_changes(&old_hot, &new_hot, &new_cfg, log_tx, detected_ip_v4, detected_ip_v6);
|
||||
config_tx.send(Arc::new(new_cfg)).ok();
|
||||
|
||||
@@ -75,6 +75,23 @@ fn push_unique_nonempty(target: &mut Vec<String>, value: String) {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_ad_tag(tag: &str) -> bool {
|
||||
tag.len() == 32 && tag.chars().all(|ch| ch.is_ascii_hexdigit())
|
||||
}
|
||||
|
||||
fn sanitize_ad_tag(ad_tag: &mut Option<String>) {
|
||||
let Some(tag) = ad_tag.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !is_valid_ad_tag(tag) {
|
||||
warn!(
|
||||
"Invalid general.ad_tag value, expected exactly 32 hex chars; ad_tag is disabled"
|
||||
);
|
||||
*ad_tag = None;
|
||||
}
|
||||
}
|
||||
|
||||
// ============= Main Config =============
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
@@ -184,6 +201,8 @@ impl ProxyConfig {
|
||||
}
|
||||
}
|
||||
|
||||
sanitize_ad_tag(&mut config.general.ad_tag);
|
||||
|
||||
if let Some(update_every) = config.general.update_every {
|
||||
if update_every == 0 {
|
||||
return Err(ProxyError::Config(
|
||||
@@ -380,6 +399,7 @@ impl ProxyConfig {
|
||||
}
|
||||
|
||||
validate_network_cfg(&mut config.network)?;
|
||||
crate::network::dns_overrides::validate_entries(&config.network.dns_overrides)?;
|
||||
|
||||
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");
|
||||
@@ -482,14 +502,18 @@ impl ProxyConfig {
|
||||
|
||||
if let Some(tag) = &self.general.ad_tag {
|
||||
let zeros = "00000000000000000000000000000000";
|
||||
if !is_valid_ad_tag(tag) {
|
||||
return Err(ProxyError::Config(
|
||||
"general.ad_tag must be exactly 32 hex characters".to_string(),
|
||||
));
|
||||
}
|
||||
if tag == zeros {
|
||||
warn!("ad_tag is all zeros; register a valid proxy tag via @MTProxybot to enable sponsored channel");
|
||||
}
|
||||
if tag.len() != 32 || tag.chars().any(|c| !c.is_ascii_hexdigit()) {
|
||||
warn!("ad_tag is not a 32-char hex string; ensure you use value issued by @MTProxybot");
|
||||
}
|
||||
}
|
||||
|
||||
crate::network::dns_overrides::validate_entries(&self.network.dns_overrides)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -509,6 +533,7 @@ mod tests {
|
||||
let cfg: ProxyConfig = toml::from_str(toml).unwrap();
|
||||
|
||||
assert_eq!(cfg.network.ipv6, default_network_ipv6());
|
||||
assert_eq!(cfg.network.stun_use, default_true());
|
||||
assert_eq!(cfg.network.stun_tcp_fallback, default_stun_tcp_fallback());
|
||||
assert_eq!(
|
||||
cfg.general.middle_proxy_warm_standby,
|
||||
@@ -532,6 +557,7 @@ mod tests {
|
||||
fn impl_defaults_are_sourced_from_default_helpers() {
|
||||
let network = NetworkConfig::default();
|
||||
assert_eq!(network.ipv6, default_network_ipv6());
|
||||
assert_eq!(network.stun_use, default_true());
|
||||
assert_eq!(network.stun_tcp_fallback, default_stun_tcp_fallback());
|
||||
|
||||
let general = GeneralConfig::default();
|
||||
@@ -934,4 +960,87 @@ mod tests {
|
||||
assert_eq!(cfg.general.me_reinit_drain_timeout_secs, 90);
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_ad_tag_is_disabled_during_load() {
|
||||
let toml = r#"
|
||||
[general]
|
||||
ad_tag = "not_hex"
|
||||
|
||||
[censorship]
|
||||
tls_domain = "example.com"
|
||||
|
||||
[access.users]
|
||||
user = "00000000000000000000000000000000"
|
||||
"#;
|
||||
let dir = std::env::temp_dir();
|
||||
let path = dir.join("telemt_invalid_ad_tag_test.toml");
|
||||
std::fs::write(&path, toml).unwrap();
|
||||
let cfg = ProxyConfig::load(&path).unwrap();
|
||||
assert!(cfg.general.ad_tag.is_none());
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_ad_tag_is_preserved_during_load() {
|
||||
let toml = r#"
|
||||
[general]
|
||||
ad_tag = "00112233445566778899aabbccddeeff"
|
||||
|
||||
[censorship]
|
||||
tls_domain = "example.com"
|
||||
|
||||
[access.users]
|
||||
user = "00000000000000000000000000000000"
|
||||
"#;
|
||||
let dir = std::env::temp_dir();
|
||||
let path = dir.join("telemt_valid_ad_tag_test.toml");
|
||||
std::fs::write(&path, toml).unwrap();
|
||||
let cfg = ProxyConfig::load(&path).unwrap();
|
||||
assert_eq!(
|
||||
cfg.general.ad_tag.as_deref(),
|
||||
Some("00112233445566778899aabbccddeeff")
|
||||
);
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_dns_override_is_rejected() {
|
||||
let toml = r#"
|
||||
[network]
|
||||
dns_overrides = ["example.com:443:2001:db8::10"]
|
||||
|
||||
[censorship]
|
||||
tls_domain = "example.com"
|
||||
|
||||
[access.users]
|
||||
user = "00000000000000000000000000000000"
|
||||
"#;
|
||||
let dir = std::env::temp_dir();
|
||||
let path = dir.join("telemt_invalid_dns_override_test.toml");
|
||||
std::fs::write(&path, toml).unwrap();
|
||||
let err = ProxyConfig::load(&path).unwrap_err().to_string();
|
||||
assert!(err.contains("must be bracketed"));
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_dns_override_is_accepted() {
|
||||
let toml = r#"
|
||||
[network]
|
||||
dns_overrides = ["example.com:443:127.0.0.1", "example.net:443:[2001:db8::10]"]
|
||||
|
||||
[censorship]
|
||||
tls_domain = "example.com"
|
||||
|
||||
[access.users]
|
||||
user = "00000000000000000000000000000000"
|
||||
"#;
|
||||
let dir = std::env::temp_dir();
|
||||
let path = dir.join("telemt_valid_dns_override_test.toml");
|
||||
std::fs::write(&path, toml).unwrap();
|
||||
let cfg = ProxyConfig::load(&path).unwrap();
|
||||
assert_eq!(cfg.network.dns_overrides.len(), 2);
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,11 @@ pub struct NetworkConfig {
|
||||
#[serde(default)]
|
||||
pub multipath: bool,
|
||||
|
||||
/// Global switch for STUN probing.
|
||||
/// When false, STUN is fully disabled and only non-STUN detection remains.
|
||||
#[serde(default = "default_true")]
|
||||
pub stun_use: bool,
|
||||
|
||||
/// STUN servers list for public IP discovery.
|
||||
#[serde(default = "default_stun_servers")]
|
||||
pub stun_servers: Vec<String>,
|
||||
@@ -112,6 +117,11 @@ pub struct NetworkConfig {
|
||||
/// Cache file path for detected public IP.
|
||||
#[serde(default = "default_cache_public_ip_path")]
|
||||
pub cache_public_ip_path: String,
|
||||
|
||||
/// Runtime DNS overrides in `host:port:ip` format.
|
||||
/// IPv6 IP values must be bracketed: `[2001:db8::1]`.
|
||||
#[serde(default)]
|
||||
pub dns_overrides: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for NetworkConfig {
|
||||
@@ -121,10 +131,12 @@ impl Default for NetworkConfig {
|
||||
ipv6: default_network_ipv6(),
|
||||
prefer: default_prefer_4(),
|
||||
multipath: false,
|
||||
stun_use: default_true(),
|
||||
stun_servers: default_stun_servers(),
|
||||
stun_tcp_fallback: default_stun_tcp_fallback(),
|
||||
http_ip_detect_urls: default_http_ip_detect_urls(),
|
||||
cache_public_ip_path: default_cache_public_ip_path(),
|
||||
dns_overrides: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user