[network] in main
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -189,11 +189,18 @@ r#"# Telemt MTProxy — auto-generated config
|
|||||||
show_link = ["{username}"]
|
show_link = ["{username}"]
|
||||||
|
|
||||||
[general]
|
[general]
|
||||||
|
# prefer_ipv6 is deprecated; use [network].prefer
|
||||||
prefer_ipv6 = false
|
prefer_ipv6 = false
|
||||||
fast_mode = true
|
fast_mode = true
|
||||||
use_middle_proxy = false
|
use_middle_proxy = false
|
||||||
log_level = "normal"
|
log_level = "normal"
|
||||||
|
|
||||||
|
[network]
|
||||||
|
ipv4 = true
|
||||||
|
ipv6 = true
|
||||||
|
prefer = 4
|
||||||
|
multipath = false
|
||||||
|
|
||||||
[general.modes]
|
[general.modes]
|
||||||
classic = false
|
classic = false
|
||||||
secure = false
|
secure = false
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ fn default_metrics_whitelist() -> Vec<IpAddr> {
|
|||||||
vec!["127.0.0.1".parse().unwrap(), "::1".parse().unwrap()]
|
vec!["127.0.0.1".parse().unwrap(), "::1".parse().unwrap()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_prefer_4() -> u8 {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
|
||||||
fn default_unknown_dc_log_path() -> Option<String> {
|
fn default_unknown_dc_log_path() -> Option<String> {
|
||||||
Some("unknown-dc.txt".to_string())
|
Some("unknown-dc.txt".to_string())
|
||||||
}
|
}
|
||||||
@@ -185,6 +189,32 @@ impl std::fmt::Display for LogLevel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_network_cfg(net: &mut NetworkConfig) -> Result<()> {
|
||||||
|
if !net.ipv4 && matches!(net.ipv6, Some(false)) {
|
||||||
|
return Err(ProxyError::Config(
|
||||||
|
"Both ipv4 and ipv6 are disabled in [network]".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if net.prefer != 4 && net.prefer != 6 {
|
||||||
|
return Err(ProxyError::Config(
|
||||||
|
"network.prefer must be 4 or 6".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !net.ipv4 && net.prefer == 4 {
|
||||||
|
warn!("prefer=4 but ipv4=false; forcing prefer=6");
|
||||||
|
net.prefer = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(net.ipv6, Some(false)) && net.prefer == 6 {
|
||||||
|
warn!("prefer=6 but ipv6=false; forcing prefer=4");
|
||||||
|
net.prefer = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// ============= Sub-Configs =============
|
// ============= Sub-Configs =============
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@@ -207,6 +237,34 @@ impl Default for ProxyModes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct NetworkConfig {
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub ipv4: bool,
|
||||||
|
|
||||||
|
/// None = auto-detect IPv6 availability
|
||||||
|
#[serde(default)]
|
||||||
|
pub ipv6: Option<bool>,
|
||||||
|
|
||||||
|
/// 4 or 6
|
||||||
|
#[serde(default = "default_prefer_4")]
|
||||||
|
pub prefer: u8,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub multipath: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NetworkConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
ipv4: true,
|
||||||
|
ipv6: None,
|
||||||
|
prefer: 4,
|
||||||
|
multipath: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct GeneralConfig {
|
pub struct GeneralConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -609,6 +667,9 @@ pub struct ProxyConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub general: GeneralConfig,
|
pub general: GeneralConfig,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub network: NetworkConfig,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub server: ServerConfig,
|
pub server: ServerConfig,
|
||||||
|
|
||||||
@@ -697,6 +758,16 @@ impl ProxyConfig {
|
|||||||
config.censorship.mask_host = Some(config.censorship.tls_domain.clone());
|
config.censorship.mask_host = Some(config.censorship.tls_domain.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migration: prefer_ipv6 -> network.prefer
|
||||||
|
if config.general.prefer_ipv6 {
|
||||||
|
if config.network.prefer == 4 {
|
||||||
|
config.network.prefer = 6;
|
||||||
|
}
|
||||||
|
warn!("prefer_ipv6 is deprecated, use [network].prefer = 6");
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_network_cfg(&mut config.network)?;
|
||||||
|
|
||||||
// Random fake_cert_len
|
// Random fake_cert_len
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
config.censorship.fake_cert_len = rand::rng().gen_range(1024..4096);
|
config.censorship.fake_cert_len = rand::rng().gen_range(1024..4096);
|
||||||
|
|||||||
98
src/main.rs
98
src/main.rs
@@ -16,6 +16,7 @@ mod config;
|
|||||||
mod crypto;
|
mod crypto;
|
||||||
mod error;
|
mod error;
|
||||||
mod ip_tracker;
|
mod ip_tracker;
|
||||||
|
mod network;
|
||||||
mod metrics;
|
mod metrics;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
mod proxy;
|
mod proxy;
|
||||||
@@ -27,16 +28,14 @@ mod util;
|
|||||||
use crate::config::{LogLevel, ProxyConfig};
|
use crate::config::{LogLevel, ProxyConfig};
|
||||||
use crate::crypto::SecureRandom;
|
use crate::crypto::SecureRandom;
|
||||||
use crate::ip_tracker::UserIpTracker;
|
use crate::ip_tracker::UserIpTracker;
|
||||||
|
use crate::network::probe::{decide_network_capabilities, log_probe_result, run_probe};
|
||||||
use crate::proxy::ClientHandler;
|
use crate::proxy::ClientHandler;
|
||||||
use crate::stats::{ReplayChecker, Stats};
|
use crate::stats::{ReplayChecker, Stats};
|
||||||
use crate::stream::BufferPool;
|
use crate::stream::BufferPool;
|
||||||
use crate::transport::middle_proxy::{
|
use crate::transport::middle_proxy::{
|
||||||
MePool, fetch_proxy_config, run_me_ping, MePingFamily, MePingSample, format_sample_line,
|
MePool, fetch_proxy_config, run_me_ping, MePingFamily, MePingSample, format_sample_line,
|
||||||
stun_probe,
|
|
||||||
};
|
};
|
||||||
use crate::transport::{ListenOptions, UpstreamManager, create_listener};
|
use crate::transport::{ListenOptions, UpstreamManager, create_listener};
|
||||||
use crate::util::ip::detect_ip;
|
|
||||||
use crate::protocol::constants::{TG_MIDDLE_PROXIES_V4, TG_MIDDLE_PROXIES_V6};
|
|
||||||
|
|
||||||
fn parse_cli() -> (String, bool, Option<String>) {
|
fn parse_cli() -> (String, bool, Option<String>) {
|
||||||
let mut config_path = "config.toml".to_string();
|
let mut config_path = "config.toml".to_string();
|
||||||
@@ -219,8 +218,17 @@ async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
|||||||
warn!("Using default tls_domain. Consider setting a custom domain.");
|
warn!("Using default tls_domain. Consider setting a custom domain.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let prefer_ipv6 = config.general.prefer_ipv6;
|
let probe = run_probe(
|
||||||
let mut use_middle_proxy = config.general.use_middle_proxy;
|
&config.network,
|
||||||
|
config.general.middle_proxy_nat_stun.clone(),
|
||||||
|
config.general.middle_proxy_nat_probe,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let decision = decide_network_capabilities(&config.network, &probe);
|
||||||
|
log_probe_result(&probe, &decision);
|
||||||
|
|
||||||
|
let prefer_ipv6 = decision.prefer_ipv6();
|
||||||
|
let mut use_middle_proxy = config.general.use_middle_proxy && (decision.ipv4_me || decision.ipv6_me);
|
||||||
let config = Arc::new(config);
|
let config = Arc::new(config);
|
||||||
let stats = Arc::new(Stats::new());
|
let stats = Arc::new(Stats::new());
|
||||||
let rng = Arc::new(SecureRandom::new());
|
let rng = Arc::new(SecureRandom::new());
|
||||||
@@ -244,40 +252,10 @@ async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
|||||||
// Connection concurrency limit
|
// Connection concurrency limit
|
||||||
let _max_connections = Arc::new(Semaphore::new(10_000));
|
let _max_connections = Arc::new(Semaphore::new(10_000));
|
||||||
|
|
||||||
// STUN check before choosing transport
|
if use_middle_proxy && !decision.ipv4_me && !decision.ipv6_me {
|
||||||
if use_middle_proxy {
|
warn!("No usable IP family for Middle Proxy detected; falling back to direct DC");
|
||||||
match stun_probe(config.general.middle_proxy_nat_stun.clone()).await {
|
|
||||||
Ok(Some(probe)) => {
|
|
||||||
info!(
|
|
||||||
local_ip = %probe.local_addr.ip(),
|
|
||||||
reflected_ip = %probe.reflected_addr.ip(),
|
|
||||||
"STUN Autodetect:"
|
|
||||||
);
|
|
||||||
if probe.local_addr.ip() != probe.reflected_addr.ip()
|
|
||||||
&& !config.general.stun_iface_mismatch_ignore
|
|
||||||
{
|
|
||||||
match crate::transport::middle_proxy::detect_public_ip().await {
|
|
||||||
Some(ip) => {
|
|
||||||
info!(
|
|
||||||
local_ip = %probe.local_addr.ip(),
|
|
||||||
reflected_ip = %probe.reflected_addr.ip(),
|
|
||||||
public_ip = %ip,
|
|
||||||
"STUN mismatch but public IP auto-detected, continuing with middle proxy"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
warn!(
|
|
||||||
"STUN/IP-on-Interface mismatch and public IP auto-detect failed -> fallback to direct-DC"
|
|
||||||
);
|
|
||||||
use_middle_proxy = false;
|
use_middle_proxy = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None) => warn!("STUN probe returned no address; continuing"),
|
|
||||||
Err(e) => warn!(error = %e, "STUN probe failed; continuing"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// Middle Proxy initialization (if enabled)
|
// Middle Proxy initialization (if enabled)
|
||||||
@@ -351,6 +329,8 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai
|
|||||||
cfg_v4.map.clone(),
|
cfg_v4.map.clone(),
|
||||||
cfg_v6.map.clone(),
|
cfg_v6.map.clone(),
|
||||||
cfg_v4.default_dc.or(cfg_v6.default_dc),
|
cfg_v4.default_dc.or(cfg_v6.default_dc),
|
||||||
|
decision.clone(),
|
||||||
|
rng.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
match pool.init(2, &rng).await {
|
match pool.init(2, &rng).await {
|
||||||
@@ -482,7 +462,12 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai
|
|||||||
info!("================= Telegram DC Connectivity =================");
|
info!("================= Telegram DC Connectivity =================");
|
||||||
|
|
||||||
let ping_results = upstream_manager
|
let ping_results = upstream_manager
|
||||||
.ping_all_dcs(prefer_ipv6, &config.dc_overrides)
|
.ping_all_dcs(
|
||||||
|
prefer_ipv6,
|
||||||
|
&config.dc_overrides,
|
||||||
|
decision.ipv4_dc,
|
||||||
|
decision.ipv6_dc,
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
for upstream_result in &ping_results {
|
for upstream_result in &ping_results {
|
||||||
@@ -559,8 +544,15 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai
|
|||||||
|
|
||||||
// Background tasks
|
// Background tasks
|
||||||
let um_clone = upstream_manager.clone();
|
let um_clone = upstream_manager.clone();
|
||||||
|
let decision_clone = decision.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
um_clone.run_health_checks(prefer_ipv6).await;
|
um_clone
|
||||||
|
.run_health_checks(
|
||||||
|
prefer_ipv6,
|
||||||
|
decision_clone.ipv4_dc,
|
||||||
|
decision_clone.ipv6_dc,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
});
|
});
|
||||||
|
|
||||||
let rc_clone = replay_checker.clone();
|
let rc_clone = replay_checker.clone();
|
||||||
@@ -568,16 +560,31 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai
|
|||||||
rc_clone.run_periodic_cleanup().await;
|
rc_clone.run_periodic_cleanup().await;
|
||||||
});
|
});
|
||||||
|
|
||||||
let detected_ip = detect_ip().await;
|
let detected_ip_v4: Option<std::net::IpAddr> = probe
|
||||||
|
.reflected_ipv4
|
||||||
|
.map(|s| s.ip())
|
||||||
|
.or_else(|| probe.detected_ipv4.map(std::net::IpAddr::V4));
|
||||||
|
let detected_ip_v6: Option<std::net::IpAddr> = probe
|
||||||
|
.reflected_ipv6
|
||||||
|
.map(|s| s.ip())
|
||||||
|
.or_else(|| probe.detected_ipv6.map(std::net::IpAddr::V6));
|
||||||
debug!(
|
debug!(
|
||||||
"Detected IPs: v4={:?} v6={:?}",
|
"Detected IPs: v4={:?} v6={:?}",
|
||||||
detected_ip.ipv4, detected_ip.ipv6
|
detected_ip_v4, detected_ip_v6
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut listeners = Vec::new();
|
let mut listeners = Vec::new();
|
||||||
|
|
||||||
for listener_conf in &config.server.listeners {
|
for listener_conf in &config.server.listeners {
|
||||||
let addr = SocketAddr::new(listener_conf.ip, config.server.port);
|
let addr = SocketAddr::new(listener_conf.ip, config.server.port);
|
||||||
|
if addr.is_ipv4() && !decision.ipv4_dc {
|
||||||
|
warn!(%addr, "Skipping IPv4 listener: IPv4 disabled by [network]");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if addr.is_ipv6() && !decision.ipv6_dc {
|
||||||
|
warn!(%addr, "Skipping IPv6 listener: IPv6 disabled by [network]");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let options = ListenOptions {
|
let options = ListenOptions {
|
||||||
ipv6_only: listener_conf.ip.is_ipv6(),
|
ipv6_only: listener_conf.ip.is_ipv6(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -594,11 +601,11 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai
|
|||||||
} else if listener_conf.ip.is_unspecified() {
|
} else if listener_conf.ip.is_unspecified() {
|
||||||
// Auto-detect for unspecified addresses
|
// Auto-detect for unspecified addresses
|
||||||
if listener_conf.ip.is_ipv4() {
|
if listener_conf.ip.is_ipv4() {
|
||||||
detected_ip.ipv4
|
detected_ip_v4
|
||||||
.map(|ip| ip.to_string())
|
.map(|ip| ip.to_string())
|
||||||
.unwrap_or_else(|| listener_conf.ip.to_string())
|
.unwrap_or_else(|| listener_conf.ip.to_string())
|
||||||
} else {
|
} else {
|
||||||
detected_ip.ipv6
|
detected_ip_v6
|
||||||
.map(|ip| ip.to_string())
|
.map(|ip| ip.to_string())
|
||||||
.unwrap_or_else(|| listener_conf.ip.to_string())
|
.unwrap_or_else(|| listener_conf.ip.to_string())
|
||||||
}
|
}
|
||||||
@@ -626,9 +633,8 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai
|
|||||||
let (host, port) = if let Some(ref h) = config.general.links.public_host {
|
let (host, port) = if let Some(ref h) = config.general.links.public_host {
|
||||||
(h.clone(), config.general.links.public_port.unwrap_or(config.server.port))
|
(h.clone(), config.general.links.public_port.unwrap_or(config.server.port))
|
||||||
} else {
|
} else {
|
||||||
let ip = detected_ip
|
let ip = detected_ip_v4
|
||||||
.ipv4
|
.or(detected_ip_v6)
|
||||||
.or(detected_ip.ipv6)
|
|
||||||
.map(|ip| ip.to_string());
|
.map(|ip| ip.to_string());
|
||||||
if ip.is_none() {
|
if ip.is_none() {
|
||||||
warn!("show_link is configured but public IP could not be detected. Set public_host in config.");
|
warn!("show_link is configured but public IP could not be detected. Set public_host in config.");
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_dc_addr_static(dc_idx: i16, config: &ProxyConfig) -> Result<SocketAddr> {
|
fn get_dc_addr_static(dc_idx: i16, config: &ProxyConfig) -> Result<SocketAddr> {
|
||||||
let datacenters = if config.general.prefer_ipv6 {
|
let prefer_v6 = config.network.prefer == 6 && config.network.ipv6.unwrap_or(true);
|
||||||
|
let datacenters = if prefer_v6 {
|
||||||
&*TG_DATACENTERS_V6
|
&*TG_DATACENTERS_V6
|
||||||
} else {
|
} else {
|
||||||
&*TG_DATACENTERS_V4
|
&*TG_DATACENTERS_V4
|
||||||
@@ -90,7 +91,6 @@ fn get_dc_addr_static(dc_idx: i16, config: &ProxyConfig) -> Result<SocketAddr> {
|
|||||||
|
|
||||||
let dc_key = dc_idx.to_string();
|
let dc_key = dc_idx.to_string();
|
||||||
if let Some(addrs) = config.dc_overrides.get(&dc_key) {
|
if let Some(addrs) = config.dc_overrides.get(&dc_key) {
|
||||||
let prefer_v6 = config.general.prefer_ipv6;
|
|
||||||
let mut parsed = Vec::new();
|
let mut parsed = Vec::new();
|
||||||
for addr_str in addrs {
|
for addr_str in addrs {
|
||||||
match addr_str.parse::<SocketAddr>() {
|
match addr_str.parse::<SocketAddr>() {
|
||||||
|
|||||||
Reference in New Issue
Block a user