Files
telemt/src/transport/middle_proxy/pool_nat.rs
T

196 lines
6.6 KiB
Rust
Raw Normal View History

2026-02-14 04:19:44 +03:00
use std::net::{IpAddr, Ipv4Addr};
2026-02-17 04:16:16 +03:00
use std::time::Duration;
2026-02-14 04:19:44 +03:00
2026-02-24 03:40:59 +03:00
use tracing::{info, warn};
2026-02-14 04:19:44 +03:00
use crate::error::{ProxyError, Result};
2026-02-18 06:01:52 +03:00
use crate::network::probe::is_bogon;
use crate::network::stun::{stun_probe_dual, IpFamily, StunProbeResult};
2026-02-14 04:19:44 +03:00
use super::MePool;
2026-02-17 04:16:16 +03:00
use std::time::Instant;
2026-02-24 03:40:59 +03:00
#[allow(dead_code)]
2026-02-18 06:01:52 +03:00
pub async fn stun_probe(stun_addr: Option<String>) -> Result<crate::network::stun::DualStunResult> {
2026-02-15 23:30:21 +03:00
let stun_addr = stun_addr.unwrap_or_else(|| "stun.l.google.com:19302".to_string());
2026-02-18 06:01:52 +03:00
stun_probe_dual(&stun_addr).await
2026-02-15 23:30:21 +03:00
}
2026-02-24 03:40:59 +03:00
#[allow(dead_code)]
2026-02-17 04:16:16 +03:00
pub async fn detect_public_ip() -> Option<IpAddr> {
fetch_public_ipv4_with_retry().await.ok().flatten().map(IpAddr::V4)
}
2026-02-14 04:19:44 +03:00
impl MePool {
pub(super) fn translate_ip_for_nat(&self, ip: IpAddr) -> IpAddr {
let nat_ip = self
.nat_ip_cfg
2026-02-24 05:57:53 +03:00
.or_else(|| self.nat_ip_detected.try_read().ok().and_then(|g| *g));
2026-02-14 04:19:44 +03:00
let Some(nat_ip) = nat_ip else {
return ip;
};
match (ip, nat_ip) {
(IpAddr::V4(src), IpAddr::V4(dst))
2026-02-18 06:01:52 +03:00
if is_bogon(IpAddr::V4(src))
2026-02-14 04:19:44 +03:00
|| src.is_loopback()
|| src.is_unspecified() =>
{
IpAddr::V4(dst)
}
(IpAddr::V6(src), IpAddr::V6(dst)) if src.is_loopback() || src.is_unspecified() => {
IpAddr::V6(dst)
}
(orig, _) => orig,
}
}
2026-02-14 12:44:20 +03:00
pub(super) fn translate_our_addr_with_reflection(
&self,
addr: std::net::SocketAddr,
reflected: Option<std::net::SocketAddr>,
) -> std::net::SocketAddr {
let ip = if let Some(r) = reflected {
// Use reflected IP (not port) only when local address is non-public.
2026-02-18 06:01:52 +03:00
if is_bogon(addr.ip()) || addr.ip().is_loopback() || addr.ip().is_unspecified() {
2026-02-14 12:44:20 +03:00
r.ip()
} else {
self.translate_ip_for_nat(addr.ip())
}
} else {
self.translate_ip_for_nat(addr.ip())
};
// Keep the kernel-assigned TCP source port; STUN port can differ.
std::net::SocketAddr::new(ip, addr.port())
}
2026-02-14 04:19:44 +03:00
pub(super) async fn maybe_detect_nat_ip(&self, local_ip: IpAddr) -> Option<IpAddr> {
if self.nat_ip_cfg.is_some() {
return self.nat_ip_cfg;
}
2026-02-18 06:01:52 +03:00
if !(is_bogon(local_ip) || local_ip.is_loopback() || local_ip.is_unspecified()) {
2026-02-14 04:19:44 +03:00
return None;
}
2026-02-24 05:57:53 +03:00
if let Some(ip) = *self.nat_ip_detected.read().await {
2026-02-14 04:19:44 +03:00
return Some(ip);
}
2026-02-15 13:14:50 +03:00
match fetch_public_ipv4_with_retry().await {
2026-02-14 04:19:44 +03:00
Ok(Some(ip)) => {
2026-02-15 13:14:50 +03:00
{
let mut guard = self.nat_ip_detected.write().await;
*guard = Some(IpAddr::V4(ip));
}
2026-02-14 04:19:44 +03:00
info!(public_ip = %ip, "Auto-detected public IP for NAT translation");
Some(IpAddr::V4(ip))
}
Ok(None) => None,
Err(e) => {
warn!(error = %e, "Failed to auto-detect public IP");
None
}
}
}
2026-02-14 12:44:20 +03:00
2026-02-18 06:01:52 +03:00
pub(super) async fn maybe_reflect_public_addr(
&self,
family: IpFamily,
) -> Option<std::net::SocketAddr> {
2026-02-17 04:16:16 +03:00
const STUN_CACHE_TTL: Duration = Duration::from_secs(600);
2026-02-19 13:35:56 +03:00
// Backoff window
2026-02-24 05:57:53 +03:00
if let Some(until) = *self.stun_backoff_until.read().await
&& Instant::now() < until
{
if let Ok(cache) = self.nat_reflection_cache.try_lock() {
let slot = match family {
IpFamily::V4 => cache.v4,
IpFamily::V6 => cache.v6,
};
return slot.map(|(_, addr)| addr);
2026-02-18 19:49:19 +03:00
}
2026-02-24 05:57:53 +03:00
return None;
2026-02-18 19:49:19 +03:00
}
2026-02-17 04:16:16 +03:00
if let Ok(mut cache) = self.nat_reflection_cache.try_lock() {
2026-02-18 06:01:52 +03:00
let slot = match family {
IpFamily::V4 => &mut cache.v4,
IpFamily::V6 => &mut cache.v6,
};
2026-02-24 05:57:53 +03:00
if let Some((ts, addr)) = slot
&& ts.elapsed() < STUN_CACHE_TTL
{
return Some(*addr);
2026-02-17 04:16:16 +03:00
}
}
2026-02-18 19:49:19 +03:00
let attempt = self.nat_probe_attempts.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2026-02-19 13:35:56 +03:00
let servers = if !self.nat_stun_servers.is_empty() {
self.nat_stun_servers.clone()
} else if let Some(s) = &self.nat_stun {
vec![s.clone()]
} else {
vec!["stun.l.google.com:19302".to_string()]
};
2026-02-18 19:49:19 +03:00
2026-02-19 13:35:56 +03:00
for stun_addr in servers {
match stun_probe_dual(&stun_addr).await {
Ok(res) => {
let picked: Option<StunProbeResult> = match family {
IpFamily::V4 => res.v4,
IpFamily::V6 => res.v6,
};
if let Some(result) = picked {
info!(local = %result.local_addr, reflected = %result.reflected_addr, family = ?family, stun = %stun_addr, "NAT probe: reflected address");
self.nat_probe_attempts.store(0, std::sync::atomic::Ordering::Relaxed);
if let Ok(mut cache) = self.nat_reflection_cache.try_lock() {
let slot = match family {
IpFamily::V4 => &mut cache.v4,
IpFamily::V6 => &mut cache.v6,
};
*slot = Some((Instant::now(), result.reflected_addr));
}
return Some(result.reflected_addr);
2026-02-17 04:16:16 +03:00
}
2026-02-14 12:44:20 +03:00
}
2026-02-19 13:35:56 +03:00
Err(e) => {
warn!(error = %e, stun = %stun_addr, attempt = attempt + 1, "NAT probe failed, trying next server");
2026-02-18 19:49:19 +03:00
}
2026-02-14 12:44:20 +03:00
}
}
2026-02-19 13:35:56 +03:00
let backoff = Duration::from_secs(60 * 2u64.pow((attempt as u32).min(6)));
*self.stun_backoff_until.write().await = Some(Instant::now() + backoff);
None
2026-02-14 12:44:20 +03:00
}
2026-02-14 04:19:44 +03:00
}
2026-02-15 13:14:50 +03:00
async fn fetch_public_ipv4_with_retry() -> Result<Option<Ipv4Addr>> {
let providers = [
"https://checkip.amazonaws.com",
"http://v4.ident.me",
"http://ipv4.icanhazip.com",
];
for url in providers {
if let Ok(Some(ip)) = fetch_public_ipv4_once(url).await {
return Ok(Some(ip));
}
}
Ok(None)
}
async fn fetch_public_ipv4_once(url: &str) -> Result<Option<Ipv4Addr>> {
let res = reqwest::get(url).await.map_err(|e| {
2026-02-14 04:19:44 +03:00
ProxyError::Proxy(format!("public IP detection request failed: {e}"))
})?;
let text = res.text().await.map_err(|e| {
ProxyError::Proxy(format!("public IP detection read failed: {e}"))
})?;
let ip = text.trim().parse().ok();
Ok(ip)
}