mirror of https://github.com/telemt/telemt.git
ME/DC Method Detection fixes
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
parent
449a87d2e3
commit
fa2423dadf
11
src/main.rs
11
src/main.rs
|
|
@ -40,6 +40,7 @@ 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,
|
||||||
|
format_me_route,
|
||||||
};
|
};
|
||||||
use crate::transport::{ListenOptions, UpstreamManager, create_listener, find_listener_processes};
|
use crate::transport::{ListenOptions, UpstreamManager, create_listener, find_listener_processes};
|
||||||
use crate::tls_front::TlsFrontCache;
|
use crate::tls_front::TlsFrontCache;
|
||||||
|
|
@ -624,7 +625,15 @@ async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||||
} else {
|
} else {
|
||||||
info!(" No ME connectivity");
|
info!(" No ME connectivity");
|
||||||
}
|
}
|
||||||
info!(" via direct");
|
let me_route = format_me_route(
|
||||||
|
&config.upstreams,
|
||||||
|
&me_results,
|
||||||
|
prefer_ipv6,
|
||||||
|
v4_ok,
|
||||||
|
v6_ok,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
info!(" via {}", me_route);
|
||||||
info!("============================================================");
|
info!("============================================================");
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ use bytes::Bytes;
|
||||||
|
|
||||||
pub use health::me_health_monitor;
|
pub use health::me_health_monitor;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use ping::{run_me_ping, format_sample_line, MePingReport, MePingSample, MePingFamily};
|
pub use ping::{run_me_ping, format_sample_line, format_me_route, MePingReport, MePingSample, MePingFamily};
|
||||||
pub use pool::MePool;
|
pub use pool::MePool;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use pool_nat::{stun_probe, detect_public_ip};
|
pub use pool_nat::{stun_probe, detect_public_ip};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ use std::collections::HashMap;
|
||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use tokio::net::UdpSocket;
|
||||||
|
|
||||||
|
use crate::config::{UpstreamConfig, UpstreamType};
|
||||||
use crate::crypto::SecureRandom;
|
use crate::crypto::SecureRandom;
|
||||||
use crate::error::ProxyError;
|
use crate::error::ProxyError;
|
||||||
|
|
||||||
|
|
@ -50,6 +53,161 @@ pub fn format_sample_line(sample: &MePingSample) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_direct_with_config(
|
||||||
|
interface: &Option<String>,
|
||||||
|
bind_addresses: &Option<Vec<String>>,
|
||||||
|
) -> Option<String> {
|
||||||
|
let mut direct_parts: Vec<String> = Vec::new();
|
||||||
|
if let Some(dev) = interface.as_deref().filter(|v| !v.is_empty()) {
|
||||||
|
direct_parts.push(format!("dev={dev}"));
|
||||||
|
}
|
||||||
|
if let Some(src) = bind_addresses.as_ref().filter(|v| !v.is_empty()) {
|
||||||
|
direct_parts.push(format!("src={}", src.join(",")));
|
||||||
|
}
|
||||||
|
if direct_parts.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(format!("direct {}", direct_parts.join(" ")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pick_target_for_family(reports: &[MePingReport], family: MePingFamily) -> Option<SocketAddr> {
|
||||||
|
reports.iter().find_map(|report| {
|
||||||
|
if report.family != family {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
report
|
||||||
|
.samples
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.error.is_none() && s.handshake_ms.is_some())
|
||||||
|
.map(|s| s.addr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn detect_interface_for_ip(ip: IpAddr) -> Option<String> {
|
||||||
|
use nix::ifaddrs::getifaddrs;
|
||||||
|
|
||||||
|
if let Ok(addrs) = getifaddrs() {
|
||||||
|
for iface in addrs {
|
||||||
|
if let Some(address) = iface.address {
|
||||||
|
if let Some(v4) = address.as_sockaddr_in() {
|
||||||
|
if IpAddr::V4(v4.ip()) == ip {
|
||||||
|
return Some(iface.interface_name);
|
||||||
|
}
|
||||||
|
} else if let Some(v6) = address.as_sockaddr_in6() {
|
||||||
|
if IpAddr::V6(v6.ip()) == ip {
|
||||||
|
return Some(iface.interface_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn detect_interface_for_ip(_ip: IpAddr) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn detect_direct_route_details(
|
||||||
|
reports: &[MePingReport],
|
||||||
|
prefer_ipv6: bool,
|
||||||
|
v4_ok: bool,
|
||||||
|
v6_ok: bool,
|
||||||
|
) -> Option<String> {
|
||||||
|
let target_addr = if prefer_ipv6 && v6_ok {
|
||||||
|
pick_target_for_family(reports, MePingFamily::V6)
|
||||||
|
.or_else(|| pick_target_for_family(reports, MePingFamily::V4))
|
||||||
|
} else if v4_ok {
|
||||||
|
pick_target_for_family(reports, MePingFamily::V4)
|
||||||
|
.or_else(|| pick_target_for_family(reports, MePingFamily::V6))
|
||||||
|
} else {
|
||||||
|
pick_target_for_family(reports, MePingFamily::V6)
|
||||||
|
.or_else(|| pick_target_for_family(reports, MePingFamily::V4))
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let local_ip = if target_addr.is_ipv4() {
|
||||||
|
let sock = UdpSocket::bind("0.0.0.0:0").await.ok()?;
|
||||||
|
sock.connect(target_addr).await.ok()?;
|
||||||
|
sock.local_addr().ok().map(|a| a.ip())
|
||||||
|
} else {
|
||||||
|
let sock = UdpSocket::bind("[::]:0").await.ok()?;
|
||||||
|
sock.connect(target_addr).await.ok()?;
|
||||||
|
sock.local_addr().ok().map(|a| a.ip())
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
if let Some(ip) = local_ip {
|
||||||
|
if let Some(dev) = detect_interface_for_ip(ip) {
|
||||||
|
parts.push(format!("dev={dev}"));
|
||||||
|
}
|
||||||
|
parts.push(format!("src={ip}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if parts.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(format!("direct {}", parts.join(" ")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn format_me_route(
|
||||||
|
upstreams: &[UpstreamConfig],
|
||||||
|
reports: &[MePingReport],
|
||||||
|
prefer_ipv6: bool,
|
||||||
|
v4_ok: bool,
|
||||||
|
v6_ok: bool,
|
||||||
|
) -> String {
|
||||||
|
let enabled_upstreams: Vec<_> = upstreams.iter().filter(|u| u.enabled).collect();
|
||||||
|
if enabled_upstreams.is_empty() {
|
||||||
|
return detect_direct_route_details(reports, prefer_ipv6, v4_ok, v6_ok)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|| "direct".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if enabled_upstreams.len() == 1 {
|
||||||
|
return match &enabled_upstreams[0].upstream_type {
|
||||||
|
UpstreamType::Direct {
|
||||||
|
interface,
|
||||||
|
bind_addresses,
|
||||||
|
} => {
|
||||||
|
if let Some(route) = format_direct_with_config(interface, bind_addresses) {
|
||||||
|
route
|
||||||
|
} else {
|
||||||
|
detect_direct_route_details(reports, prefer_ipv6, v4_ok, v6_ok)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|| "direct".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UpstreamType::Socks4 { address, .. } => format!("socks4://{address}"),
|
||||||
|
UpstreamType::Socks5 { address, .. } => format!("socks5://{address}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let has_direct = enabled_upstreams
|
||||||
|
.iter()
|
||||||
|
.any(|u| matches!(u.upstream_type, UpstreamType::Direct { .. }));
|
||||||
|
let has_socks4 = enabled_upstreams
|
||||||
|
.iter()
|
||||||
|
.any(|u| matches!(u.upstream_type, UpstreamType::Socks4 { .. }));
|
||||||
|
let has_socks5 = enabled_upstreams
|
||||||
|
.iter()
|
||||||
|
.any(|u| matches!(u.upstream_type, UpstreamType::Socks5 { .. }));
|
||||||
|
let mut kinds = Vec::new();
|
||||||
|
if has_direct {
|
||||||
|
kinds.push("direct");
|
||||||
|
}
|
||||||
|
if has_socks4 {
|
||||||
|
kinds.push("socks4");
|
||||||
|
}
|
||||||
|
if has_socks5 {
|
||||||
|
kinds.push("socks5");
|
||||||
|
}
|
||||||
|
format!("mixed upstreams ({})", kinds.join(", "))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -707,8 +707,22 @@ impl UpstreamManager {
|
||||||
|
|
||||||
for (upstream_idx, upstream_config, bind_rr) in &upstreams {
|
for (upstream_idx, upstream_config, bind_rr) in &upstreams {
|
||||||
let upstream_name = match &upstream_config.upstream_type {
|
let upstream_name = match &upstream_config.upstream_type {
|
||||||
UpstreamType::Direct { interface, .. } => {
|
UpstreamType::Direct {
|
||||||
format!("direct{}", interface.as_ref().map(|i| format!(" ({})", i)).unwrap_or_default())
|
interface,
|
||||||
|
bind_addresses,
|
||||||
|
} => {
|
||||||
|
let mut direct_parts = Vec::new();
|
||||||
|
if let Some(dev) = interface.as_deref().filter(|v| !v.is_empty()) {
|
||||||
|
direct_parts.push(format!("dev={dev}"));
|
||||||
|
}
|
||||||
|
if let Some(src) = bind_addresses.as_ref().filter(|v| !v.is_empty()) {
|
||||||
|
direct_parts.push(format!("src={}", src.join(",")));
|
||||||
|
}
|
||||||
|
if direct_parts.is_empty() {
|
||||||
|
"direct".to_string()
|
||||||
|
} else {
|
||||||
|
format!("direct {}", direct_parts.join(" "))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
UpstreamType::Socks4 { address, .. } => format!("socks4://{}", address),
|
UpstreamType::Socks4 { address, .. } => format!("socks4://{}", address),
|
||||||
UpstreamType::Socks5 { address, .. } => format!("socks5://{}", address),
|
UpstreamType::Socks5 { address, .. } => format!("socks5://{}", address),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue