DC=203 by default + IP Autodetect by STUN
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
use crate::error::{ProxyError, Result};
|
use crate::error::{ProxyError, Result};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde::de::Deserializer;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -53,6 +54,36 @@ 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()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============= Custom Deserializers =============
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum OneOrMany {
|
||||||
|
One(String),
|
||||||
|
Many(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_dc_overrides<'de, D>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> std::result::Result<HashMap<String, Vec<String>>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let raw: HashMap<String, OneOrMany> = HashMap::deserialize(deserializer)?;
|
||||||
|
let mut out = HashMap::new();
|
||||||
|
for (dc, val) in raw {
|
||||||
|
let mut addrs = match val {
|
||||||
|
OneOrMany::One(s) => vec![s],
|
||||||
|
OneOrMany::Many(v) => v,
|
||||||
|
};
|
||||||
|
addrs.retain(|s| !s.trim().is_empty());
|
||||||
|
if !addrs.is_empty() {
|
||||||
|
out.insert(dc, addrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
// ============= Log Level =============
|
// ============= Log Level =============
|
||||||
|
|
||||||
/// Logging verbosity level
|
/// Logging verbosity level
|
||||||
@@ -95,6 +126,50 @@ impl LogLevel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dc_overrides_allow_string_and_array() {
|
||||||
|
let toml = r#"
|
||||||
|
[dc_overrides]
|
||||||
|
"201" = "149.154.175.50:443"
|
||||||
|
"202" = ["149.154.167.51:443", "149.154.175.100:443"]
|
||||||
|
"#;
|
||||||
|
let cfg: ProxyConfig = toml::from_str(toml).unwrap();
|
||||||
|
assert_eq!(cfg.dc_overrides["201"], vec!["149.154.175.50:443"]);
|
||||||
|
assert_eq!(
|
||||||
|
cfg.dc_overrides["202"],
|
||||||
|
vec!["149.154.167.51:443", "149.154.175.100:443"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dc_overrides_inject_dc203_default() {
|
||||||
|
let toml = r#"
|
||||||
|
[general]
|
||||||
|
use_middle_proxy = false
|
||||||
|
|
||||||
|
[censorship]
|
||||||
|
tls_domain = "example.com"
|
||||||
|
|
||||||
|
[access.users]
|
||||||
|
user = "00000000000000000000000000000000"
|
||||||
|
"#;
|
||||||
|
let dir = std::env::temp_dir();
|
||||||
|
let path = dir.join("telemt_dc_override_test.toml");
|
||||||
|
std::fs::write(&path, toml).unwrap();
|
||||||
|
let cfg = ProxyConfig::load(&path).unwrap();
|
||||||
|
assert!(cfg
|
||||||
|
.dc_overrides
|
||||||
|
.get("203")
|
||||||
|
.map(|v| v.contains(&"91.105.192.100:443".to_string()))
|
||||||
|
.unwrap_or(false));
|
||||||
|
let _ = std::fs::remove_file(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for LogLevel {
|
impl std::fmt::Display for LogLevel {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@@ -163,6 +238,10 @@ pub struct GeneralConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub middle_proxy_nat_stun: Option<String>,
|
pub middle_proxy_nat_stun: Option<String>,
|
||||||
|
|
||||||
|
/// Ignore STUN/interface IP mismatch (keep using Middle Proxy even if NAT detected).
|
||||||
|
#[serde(default)]
|
||||||
|
pub stun_iface_mismatch_ignore: bool,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub log_level: LogLevel,
|
pub log_level: LogLevel,
|
||||||
|
|
||||||
@@ -183,6 +262,7 @@ impl Default for GeneralConfig {
|
|||||||
middle_proxy_nat_ip: None,
|
middle_proxy_nat_ip: None,
|
||||||
middle_proxy_nat_probe: false,
|
middle_proxy_nat_probe: false,
|
||||||
middle_proxy_nat_stun: None,
|
middle_proxy_nat_stun: None,
|
||||||
|
stun_iface_mismatch_ignore: false,
|
||||||
log_level: LogLevel::Normal,
|
log_level: LogLevel::Normal,
|
||||||
disable_colors: false,
|
disable_colors: false,
|
||||||
}
|
}
|
||||||
@@ -499,13 +579,13 @@ pub struct ProxyConfig {
|
|||||||
pub show_link: ShowLink,
|
pub show_link: ShowLink,
|
||||||
|
|
||||||
/// DC address overrides for non-standard DCs (CDN, media, test, etc.)
|
/// DC address overrides for non-standard DCs (CDN, media, test, etc.)
|
||||||
/// Keys are DC indices as strings, values are "ip:port" addresses.
|
/// Keys are DC indices as strings, values are one or more \"ip:port\" addresses.
|
||||||
/// Matches the C implementation's `proxy_for <dc_id> <ip>:<port>` config directive.
|
/// Matches the C implementation's `proxy_for <dc_id> <ip>:<port>` config directive.
|
||||||
/// Example in config.toml:
|
/// Example in config.toml:
|
||||||
/// [dc_overrides]
|
/// [dc_overrides]
|
||||||
/// "203" = "149.154.175.100:443"
|
/// \"203\" = [\"149.154.175.100:443\", \"91.105.192.100:443\"]
|
||||||
#[serde(default)]
|
#[serde(default, deserialize_with = "deserialize_dc_overrides")]
|
||||||
pub dc_overrides: HashMap<String, String>,
|
pub dc_overrides: HashMap<String, Vec<String>>,
|
||||||
|
|
||||||
/// Default DC index (1-5) for unmapped non-standard DCs.
|
/// Default DC index (1-5) for unmapped non-standard DCs.
|
||||||
/// Matches the C implementation's `default <dc_id>` config directive.
|
/// Matches the C implementation's `default <dc_id>` config directive.
|
||||||
@@ -599,6 +679,12 @@ impl ProxyConfig {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure default DC203 override is present.
|
||||||
|
config
|
||||||
|
.dc_overrides
|
||||||
|
.entry("203".to_string())
|
||||||
|
.or_insert_with(|| vec!["91.105.192.100:443".to_string()]);
|
||||||
|
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
171
src/main.rs
171
src/main.rs
@@ -8,7 +8,6 @@ use tokio::signal;
|
|||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
use tracing_subscriber::{EnvFilter, fmt, prelude::*, reload};
|
use tracing_subscriber::{EnvFilter, fmt, prelude::*, reload};
|
||||||
use tokio::net::UnixListener;
|
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod config;
|
mod config;
|
||||||
@@ -28,7 +27,7 @@ use crate::ip_tracker::UserIpTracker;
|
|||||||
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::{MePool, fetch_proxy_config};
|
use crate::transport::middle_proxy::{MePool, fetch_proxy_config, stun_probe};
|
||||||
use crate::transport::{ListenOptions, UpstreamManager, create_listener};
|
use crate::transport::{ListenOptions, UpstreamManager, create_listener};
|
||||||
use crate::util::ip::detect_ip;
|
use crate::util::ip::detect_ip;
|
||||||
use crate::protocol::constants::{TG_MIDDLE_PROXIES_V4, TG_MIDDLE_PROXIES_V6};
|
use crate::protocol::constants::{TG_MIDDLE_PROXIES_V4, TG_MIDDLE_PROXIES_V6};
|
||||||
@@ -184,7 +183,7 @@ async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let prefer_ipv6 = config.general.prefer_ipv6;
|
let prefer_ipv6 = config.general.prefer_ipv6;
|
||||||
let use_middle_proxy = config.general.use_middle_proxy;
|
let mut use_middle_proxy = config.general.use_middle_proxy;
|
||||||
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());
|
||||||
@@ -208,6 +207,31 @@ 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 {
|
||||||
|
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 detected public address"
|
||||||
|
);
|
||||||
|
if probe.local_addr.ip() != probe.reflected_addr.ip()
|
||||||
|
&& !config.general.stun_iface_mismatch_ignore
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
local_ip = %probe.local_addr.ip(),
|
||||||
|
reflected_ip = %probe.reflected_addr.ip(),
|
||||||
|
"STUN/interface IP mismatch; falling back to direct DC (set stun_iface_mismatch_ignore=true to force Middle Proxy)"
|
||||||
|
);
|
||||||
|
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)
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
@@ -331,84 +355,81 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai
|
|||||||
info!("Transport: Direct TCP (standard DCs only)");
|
info!("Transport: Direct TCP (standard DCs only)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Startup DC ping (only meaningful in direct mode)
|
info!("================= Telegram DC Connectivity =================");
|
||||||
if me_pool.is_none() {
|
|
||||||
info!("================= Telegram DC Connectivity =================");
|
|
||||||
|
|
||||||
let ping_results = upstream_manager.ping_all_dcs(prefer_ipv6).await;
|
let ping_results = upstream_manager.ping_all_dcs(prefer_ipv6).await;
|
||||||
|
|
||||||
for upstream_result in &ping_results {
|
for upstream_result in &ping_results {
|
||||||
let v6_works = upstream_result
|
let v6_works = upstream_result
|
||||||
.v6_results
|
.v6_results
|
||||||
.iter()
|
.iter()
|
||||||
.any(|r| r.rtt_ms.is_some());
|
.any(|r| r.rtt_ms.is_some());
|
||||||
let v4_works = upstream_result
|
let v4_works = upstream_result
|
||||||
.v4_results
|
.v4_results
|
||||||
.iter()
|
.iter()
|
||||||
.any(|r| r.rtt_ms.is_some());
|
.any(|r| r.rtt_ms.is_some());
|
||||||
|
|
||||||
if upstream_result.both_available {
|
if upstream_result.both_available {
|
||||||
if prefer_ipv6 {
|
if prefer_ipv6 {
|
||||||
info!(" IPv6 in use and IPv4 is fallback");
|
info!(" IPv6 in use and IPv4 is fallback");
|
||||||
} else {
|
|
||||||
info!(" IPv4 in use and IPv6 is fallback");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if v6_works && !v4_works {
|
info!(" IPv4 in use and IPv6 is fallback");
|
||||||
info!(" IPv6 only (IPv4 unavailable)");
|
|
||||||
} else if v4_works && !v6_works {
|
|
||||||
info!(" IPv4 only (IPv6 unavailable)");
|
|
||||||
} else if !v6_works && !v4_works {
|
|
||||||
info!(" No connectivity!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
info!(" via {}", upstream_result.upstream_name);
|
if v6_works && !v4_works {
|
||||||
info!("============================================================");
|
info!(" IPv6 only (IPv4 unavailable)");
|
||||||
|
} else if v4_works && !v6_works {
|
||||||
// Print IPv6 results first (only if IPv6 is available)
|
info!(" IPv4 only (IPv6 unavailable)");
|
||||||
if v6_works {
|
} else if !v6_works && !v4_works {
|
||||||
for dc in &upstream_result.v6_results {
|
info!(" No connectivity!");
|
||||||
let addr_str = format!("{}:{}", dc.dc_addr.ip(), dc.dc_addr.port());
|
|
||||||
match &dc.rtt_ms {
|
|
||||||
Some(rtt) => {
|
|
||||||
info!(" DC{} [IPv6] {}:\t\t{:.0} ms", dc.dc_idx, addr_str, rtt);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let err = dc.error.as_deref().unwrap_or("fail");
|
|
||||||
info!(" DC{} [IPv6] {}:\t\tFAIL ({})", dc.dc_idx, addr_str, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("============================================================");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print IPv4 results (only if IPv4 is available)
|
|
||||||
if v4_works {
|
|
||||||
for dc in &upstream_result.v4_results {
|
|
||||||
let addr_str = format!("{}:{}", dc.dc_addr.ip(), dc.dc_addr.port());
|
|
||||||
match &dc.rtt_ms {
|
|
||||||
Some(rtt) => {
|
|
||||||
info!(
|
|
||||||
" DC{} [IPv4] {}:\t\t\t\t{:.0} ms",
|
|
||||||
dc.dc_idx, addr_str, rtt
|
|
||||||
);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let err = dc.error.as_deref().unwrap_or("fail");
|
|
||||||
info!(
|
|
||||||
" DC{} [IPv4] {}:\t\t\t\tFAIL ({})",
|
|
||||||
dc.dc_idx, addr_str, err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("============================================================");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
info!(" via {}", upstream_result.upstream_name);
|
||||||
|
info!("============================================================");
|
||||||
|
|
||||||
|
// Print IPv6 results first (only if IPv6 is available)
|
||||||
|
if v6_works {
|
||||||
|
for dc in &upstream_result.v6_results {
|
||||||
|
let addr_str = format!("{}:{}", dc.dc_addr.ip(), dc.dc_addr.port());
|
||||||
|
match &dc.rtt_ms {
|
||||||
|
Some(rtt) => {
|
||||||
|
info!(" DC{} [IPv6] {}:\t\t{:.0} ms", dc.dc_idx, addr_str, rtt);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let err = dc.error.as_deref().unwrap_or("fail");
|
||||||
|
info!(" DC{} [IPv6] {}:\t\tFAIL ({})", dc.dc_idx, addr_str, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("============================================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print IPv4 results (only if IPv4 is available)
|
||||||
|
if v4_works {
|
||||||
|
for dc in &upstream_result.v4_results {
|
||||||
|
let addr_str = format!("{}:{}", dc.dc_addr.ip(), dc.dc_addr.port());
|
||||||
|
match &dc.rtt_ms {
|
||||||
|
Some(rtt) => {
|
||||||
|
info!(
|
||||||
|
" DC{} [IPv4] {}:\t\t\t\t{:.0} ms",
|
||||||
|
dc.dc_idx, addr_str, rtt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let err = dc.error.as_deref().unwrap_or("fail");
|
||||||
|
info!(
|
||||||
|
" DC{} [IPv4] {}:\t\t\t\tFAIL ({})",
|
||||||
|
dc.dc_idx, addr_str, err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("============================================================");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Background tasks
|
// Background tasks
|
||||||
let um_clone = upstream_manager.clone();
|
let um_clone = upstream_manager.clone();
|
||||||
|
|||||||
@@ -87,17 +87,25 @@ fn get_dc_addr_static(dc_idx: i16, config: &ProxyConfig) -> Result<SocketAddr> {
|
|||||||
let num_dcs = datacenters.len();
|
let num_dcs = datacenters.len();
|
||||||
|
|
||||||
let dc_key = dc_idx.to_string();
|
let dc_key = dc_idx.to_string();
|
||||||
if let Some(addr_str) = config.dc_overrides.get(&dc_key) {
|
if let Some(addrs) = config.dc_overrides.get(&dc_key) {
|
||||||
match addr_str.parse::<SocketAddr>() {
|
let prefer_v6 = config.general.prefer_ipv6;
|
||||||
Ok(addr) => {
|
let mut parsed = Vec::new();
|
||||||
debug!(dc_idx = dc_idx, addr = %addr, "Using DC override from config");
|
for addr_str in addrs {
|
||||||
return Ok(addr);
|
match addr_str.parse::<SocketAddr>() {
|
||||||
}
|
Ok(addr) => parsed.push(addr),
|
||||||
Err(_) => {
|
Err(_) => warn!(dc_idx = dc_idx, addr_str = %addr_str, "Invalid DC override address in config, ignoring"),
|
||||||
warn!(dc_idx = dc_idx, addr_str = %addr_str,
|
|
||||||
"Invalid DC override address in config, ignoring");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(addr) = parsed
|
||||||
|
.iter()
|
||||||
|
.find(|a| a.is_ipv6() == prefer_v6)
|
||||||
|
.or_else(|| parsed.first())
|
||||||
|
.copied()
|
||||||
|
{
|
||||||
|
debug!(dc_idx = dc_idx, addr = %addr, count = parsed.len(), "Using DC override from config");
|
||||||
|
return Ok(addr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let abs_dc = dc_idx.unsigned_abs() as usize;
|
let abs_dc = dc_idx.unsigned_abs() as usize;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use bytes::Bytes;
|
|||||||
|
|
||||||
pub use health::me_health_monitor;
|
pub use health::me_health_monitor;
|
||||||
pub use pool::MePool;
|
pub use pool::MePool;
|
||||||
|
pub use pool_nat::{stun_probe, StunProbeResult};
|
||||||
pub use registry::ConnRegistry;
|
pub use registry::ConnRegistry;
|
||||||
pub use secret::fetch_proxy_secret;
|
pub use secret::fetch_proxy_secret;
|
||||||
pub use config_updater::{fetch_proxy_config, me_config_updater};
|
pub use config_updater::{fetch_proxy_config, me_config_updater};
|
||||||
|
|||||||
@@ -6,6 +6,17 @@ use crate::error::{ProxyError, Result};
|
|||||||
|
|
||||||
use super::MePool;
|
use super::MePool;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct StunProbeResult {
|
||||||
|
pub local_addr: std::net::SocketAddr,
|
||||||
|
pub reflected_addr: std::net::SocketAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn stun_probe(stun_addr: Option<String>) -> Result<Option<StunProbeResult>> {
|
||||||
|
let stun_addr = stun_addr.unwrap_or_else(|| "stun.l.google.com:19302".to_string());
|
||||||
|
fetch_stun_binding(&stun_addr).await
|
||||||
|
}
|
||||||
|
|
||||||
impl MePool {
|
impl MePool {
|
||||||
pub(super) fn translate_ip_for_nat(&self, ip: IpAddr) -> IpAddr {
|
pub(super) fn translate_ip_for_nat(&self, ip: IpAddr) -> IpAddr {
|
||||||
let nat_ip = self
|
let nat_ip = self
|
||||||
@@ -88,10 +99,12 @@ impl MePool {
|
|||||||
.unwrap_or_else(|| "stun.l.google.com:19302".to_string());
|
.unwrap_or_else(|| "stun.l.google.com:19302".to_string());
|
||||||
match fetch_stun_binding(&stun_addr).await {
|
match fetch_stun_binding(&stun_addr).await {
|
||||||
Ok(sa) => {
|
Ok(sa) => {
|
||||||
if let Some(sa) = sa {
|
if let Some(result) = sa {
|
||||||
info!(%sa, "NAT probe: reflected address");
|
info!(local = %result.local_addr, reflected = %result.reflected_addr, "NAT probe: reflected address");
|
||||||
|
Some(result.reflected_addr)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
sa
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!(error = %e, "NAT probe failed");
|
warn!(error = %e, "NAT probe failed");
|
||||||
@@ -128,7 +141,7 @@ async fn fetch_public_ipv4_once(url: &str) -> Result<Option<Ipv4Addr>> {
|
|||||||
Ok(ip)
|
Ok(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_stun_binding(stun_addr: &str) -> Result<Option<std::net::SocketAddr>> {
|
async fn fetch_stun_binding(stun_addr: &str) -> Result<Option<StunProbeResult>> {
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
use tokio::net::UdpSocket;
|
use tokio::net::UdpSocket;
|
||||||
|
|
||||||
@@ -196,10 +209,17 @@ async fn fetch_stun_binding(stun_addr: &str) -> Result<Option<std::net::SocketAd
|
|||||||
} else {
|
} else {
|
||||||
(u16::from_be_bytes(port_bytes), ip_bytes)
|
(u16::from_be_bytes(port_bytes), ip_bytes)
|
||||||
};
|
};
|
||||||
return Ok(Some(std::net::SocketAddr::new(
|
let reflected = std::net::SocketAddr::new(
|
||||||
IpAddr::V4(Ipv4Addr::new(ip[0], ip[1], ip[2], ip[3])),
|
IpAddr::V4(Ipv4Addr::new(ip[0], ip[1], ip[2], ip[3])),
|
||||||
port,
|
port,
|
||||||
)));
|
);
|
||||||
|
let local_addr = socket.local_addr().map_err(|e| {
|
||||||
|
ProxyError::Proxy(format!("STUN local_addr failed: {e}"))
|
||||||
|
})?;
|
||||||
|
return Ok(Some(StunProbeResult {
|
||||||
|
local_addr,
|
||||||
|
reflected_addr: reflected,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,12 +285,17 @@ where
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::io::ErrorKind;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_pool_basic() {
|
async fn test_pool_basic() {
|
||||||
// Start a test server
|
// Start a test server
|
||||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
let listener = match TcpListener::bind("127.0.0.1:0").await {
|
||||||
|
Ok(l) => l,
|
||||||
|
Err(e) if e.kind() == ErrorKind::PermissionDenied => return,
|
||||||
|
Err(e) => panic!("bind failed: {e}"),
|
||||||
|
};
|
||||||
let addr = listener.local_addr().unwrap();
|
let addr = listener.local_addr().unwrap();
|
||||||
|
|
||||||
// Accept connections in background
|
// Accept connections in background
|
||||||
@@ -303,7 +308,11 @@ mod tests {
|
|||||||
let pool = ConnectionPool::new();
|
let pool = ConnectionPool::new();
|
||||||
|
|
||||||
// Get a connection
|
// Get a connection
|
||||||
let conn1 = pool.get(addr).await.unwrap();
|
let conn1 = match pool.get(addr).await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(ProxyError::Io(e)) if e.kind() == ErrorKind::PermissionDenied => return,
|
||||||
|
Err(e) => panic!("connect failed: {e}"),
|
||||||
|
};
|
||||||
|
|
||||||
// Return it to pool
|
// Return it to pool
|
||||||
pool.put(addr, conn1).await;
|
pool.put(addr, conn1).await;
|
||||||
@@ -335,4 +344,4 @@ mod tests {
|
|||||||
assert_eq!(stats.endpoints, 0);
|
assert_eq!(stats.endpoints, 0);
|
||||||
assert_eq!(stats.total_connections, 0);
|
assert_eq!(stats.total_connections, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,15 +205,29 @@ pub fn create_listener(addr: SocketAddr, options: &ListenOptions) -> Result<Sock
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::io::ErrorKind;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_configure_socket() {
|
async fn test_configure_socket() {
|
||||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
let listener = match TcpListener::bind("127.0.0.1:0").await {
|
||||||
|
Ok(l) => l,
|
||||||
|
Err(e) if e.kind() == ErrorKind::PermissionDenied => return,
|
||||||
|
Err(e) => panic!("bind failed: {e}"),
|
||||||
|
};
|
||||||
let addr = listener.local_addr().unwrap();
|
let addr = listener.local_addr().unwrap();
|
||||||
|
|
||||||
let stream = TcpStream::connect(addr).await.unwrap();
|
let stream = match TcpStream::connect(addr).await {
|
||||||
configure_tcp_socket(&stream, true, Duration::from_secs(30)).unwrap();
|
Ok(s) => s,
|
||||||
|
Err(e) if e.kind() == ErrorKind::PermissionDenied => return,
|
||||||
|
Err(e) => panic!("connect failed: {e}"),
|
||||||
|
};
|
||||||
|
if let Err(e) = configure_tcp_socket(&stream, true, Duration::from_secs(30)) {
|
||||||
|
if e.kind() == ErrorKind::PermissionDenied {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
panic!("configure_tcp_socket failed: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -234,4 +248,4 @@ mod tests {
|
|||||||
assert!(opts.reuse_port);
|
assert!(opts.reuse_port);
|
||||||
assert_eq!(opts.backlog, 1024);
|
assert_eq!(opts.backlog, 1024);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user