mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-06 17:59:11 +00:00
59d4475743
Enable encryption for non-direct nodes requiring relay forwarding. When secure_mode is enabled, peers perform Noise IK handshake to establish an encrypted PeerSession. Relay packets are encrypted at the sender and decrypted at the receiver. Intermediate forwarding nodes cannot read plaintext data. --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: KKRainbow <5665404+KKRainbow@users.noreply.github.com>
333 lines
11 KiB
Rust
333 lines
11 KiB
Rust
use std::{net::IpAddr, ops::Deref, sync::Arc};
|
|
|
|
use pnet::datalink::NetworkInterface;
|
|
use tokio::{
|
|
sync::{Mutex, RwLock},
|
|
task::JoinSet,
|
|
};
|
|
|
|
use crate::proto::peer_rpc::GetIpListResponse;
|
|
|
|
use super::{netns::NetNS, stun::StunInfoCollectorTrait};
|
|
|
|
pub const CACHED_IP_LIST_TIMEOUT_SEC: u64 = 60;
|
|
|
|
struct InterfaceFilter {
|
|
iface: NetworkInterface,
|
|
}
|
|
|
|
#[cfg(any(
|
|
target_os = "android",
|
|
target_os = "ios",
|
|
all(target_os = "macos", feature = "macos-ne"),
|
|
target_env = "ohos"
|
|
))]
|
|
impl InterfaceFilter {
|
|
async fn filter_iface(&self) -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
|
impl InterfaceFilter {
|
|
async fn is_tun_tap_device(&self) -> bool {
|
|
let path = format!("/sys/class/net/{}/tun_flags", self.iface.name);
|
|
tokio::fs::metadata(&path).await.is_ok()
|
|
}
|
|
|
|
async fn has_valid_ip(&self) -> bool {
|
|
self.iface
|
|
.ips
|
|
.iter()
|
|
.map(|ip| ip.ip())
|
|
.any(|ip| !ip.is_loopback() && !ip.is_unspecified() && !ip.is_multicast())
|
|
}
|
|
|
|
async fn filter_iface(&self) -> bool {
|
|
tracing::trace!(
|
|
"filter linux iface: {:?}, is_point_to_point: {}, is_loopback: {}, is_up: {}, is_lower_up: {}, is_tun: {}, has_valid_ip: {}",
|
|
self.iface,
|
|
self.iface.is_point_to_point(),
|
|
self.iface.is_loopback(),
|
|
self.iface.is_up(),
|
|
self.iface.is_lower_up(),
|
|
self.is_tun_tap_device().await,
|
|
self.has_valid_ip().await
|
|
);
|
|
|
|
!self.iface.is_point_to_point()
|
|
&& !self.iface.is_loopback()
|
|
&& self.iface.is_up()
|
|
&& self.iface.is_lower_up()
|
|
&& !self.is_tun_tap_device().await
|
|
&& self.has_valid_ip().await
|
|
}
|
|
}
|
|
|
|
// Cache for networksetup command output
|
|
#[cfg(all(target_os = "macos", not(feature = "macos-ne")))]
|
|
static NETWORKSETUP_CACHE: std::sync::OnceLock<Mutex<(String, std::time::Instant)>> =
|
|
std::sync::OnceLock::new();
|
|
|
|
#[cfg(any(
|
|
all(target_os = "macos", not(feature = "macos-ne")),
|
|
target_os = "freebsd"
|
|
))]
|
|
impl InterfaceFilter {
|
|
#[cfg(all(target_os = "macos", not(feature = "macos-ne")))]
|
|
async fn get_networksetup_output() -> String {
|
|
use anyhow::Context;
|
|
use std::time::{Duration, Instant};
|
|
let cache = NETWORKSETUP_CACHE.get_or_init(|| Mutex::new((String::new(), Instant::now())));
|
|
let mut cache_guard = cache.lock().await;
|
|
|
|
// Check if cache is still valid (less than 1 minute old)
|
|
if cache_guard.1.elapsed() < Duration::from_secs(60) && !cache_guard.0.is_empty() {
|
|
return cache_guard.0.clone();
|
|
}
|
|
|
|
// Cache is expired or empty, fetch new data
|
|
let stdout = tokio::process::Command::new("networksetup")
|
|
.args(["-listallhardwareports"])
|
|
.output()
|
|
.await
|
|
.with_context(|| "Failed to execute networksetup command")
|
|
.and_then(|output| {
|
|
std::str::from_utf8(&output.stdout)
|
|
.map(|s| s.to_string())
|
|
.with_context(|| "Failed to convert networksetup output to string")
|
|
})
|
|
.unwrap_or_else(|e| {
|
|
tracing::error!("Failed to execute networksetup command: {:?}", e);
|
|
String::new()
|
|
});
|
|
|
|
// Update cache
|
|
cache_guard.0 = stdout.clone();
|
|
cache_guard.1 = Instant::now();
|
|
|
|
stdout
|
|
}
|
|
|
|
#[cfg(all(target_os = "macos", not(feature = "macos-ne")))]
|
|
async fn is_interface_physical(&self) -> bool {
|
|
let interface_name = &self.iface.name;
|
|
let stdout = Self::get_networksetup_output().await;
|
|
|
|
let lines: Vec<&str> = stdout.lines().collect();
|
|
|
|
for i in 0..lines.len() {
|
|
let line = lines[i];
|
|
|
|
if line.contains("Device:") && line.contains(interface_name) {
|
|
let next_line = lines[i + 1];
|
|
return !next_line.contains("Virtual Interface");
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
#[cfg(target_os = "freebsd")]
|
|
async fn is_interface_physical(&self) -> bool {
|
|
// if mac addr is not zero, then it's physical interface
|
|
self.iface.mac.map(|mac| !mac.is_zero()).unwrap_or(false)
|
|
}
|
|
|
|
async fn filter_iface(&self) -> bool {
|
|
!self.iface.is_point_to_point()
|
|
&& !self.iface.is_loopback()
|
|
&& self.iface.is_up()
|
|
&& self.is_interface_physical().await
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
impl InterfaceFilter {
|
|
async fn filter_iface(&self) -> bool {
|
|
tracing::debug!(
|
|
"iface_name: {:?}, p2p: {:?}, is_up: {:?}, iface: {:?}",
|
|
self.iface.name,
|
|
self.iface.is_point_to_point(),
|
|
self.iface.is_up(),
|
|
self.iface
|
|
);
|
|
!self.iface.is_point_to_point()
|
|
&& !self.iface.is_loopback()
|
|
&& self
|
|
.iface
|
|
.ips
|
|
.iter()
|
|
.map(|ip| ip.ip())
|
|
.any(|ip| !ip.is_loopback() && !ip.is_unspecified() && !ip.is_multicast())
|
|
&& self.iface.mac.map(|mac| !mac.is_zero()).unwrap_or(false)
|
|
}
|
|
}
|
|
|
|
pub async fn local_ipv4() -> std::io::Result<std::net::Ipv4Addr> {
|
|
let socket = tokio::net::UdpSocket::bind("0.0.0.0:0").await?;
|
|
socket.connect("8.8.8.8:80").await?;
|
|
let addr = socket.local_addr()?;
|
|
match addr.ip() {
|
|
std::net::IpAddr::V4(ip) => Ok(ip),
|
|
std::net::IpAddr::V6(_) => Err(std::io::Error::new(
|
|
std::io::ErrorKind::AddrNotAvailable,
|
|
"no ipv4 address",
|
|
)),
|
|
}
|
|
}
|
|
|
|
pub async fn local_ipv6() -> std::io::Result<std::net::Ipv6Addr> {
|
|
let socket = tokio::net::UdpSocket::bind("[::]:0").await?;
|
|
socket
|
|
.connect("[2001:4860:4860:0000:0000:0000:0000:8888]:80")
|
|
.await?;
|
|
let addr = socket.local_addr()?;
|
|
match addr.ip() {
|
|
std::net::IpAddr::V6(ip) => Ok(ip),
|
|
std::net::IpAddr::V4(_) => Err(std::io::Error::new(
|
|
std::io::ErrorKind::AddrNotAvailable,
|
|
"no ipv4 address",
|
|
)),
|
|
}
|
|
}
|
|
|
|
pub struct IPCollector {
|
|
cached_ip_list: Arc<RwLock<GetIpListResponse>>,
|
|
collect_ip_task: Mutex<JoinSet<()>>,
|
|
net_ns: NetNS,
|
|
stun_info_collector: Arc<Box<dyn StunInfoCollectorTrait>>,
|
|
}
|
|
|
|
impl IPCollector {
|
|
pub fn new<T: StunInfoCollectorTrait + 'static>(net_ns: NetNS, stun_info_collector: T) -> Self {
|
|
Self {
|
|
cached_ip_list: Arc::new(RwLock::new(GetIpListResponse::default())),
|
|
collect_ip_task: Mutex::new(JoinSet::new()),
|
|
net_ns,
|
|
stun_info_collector: Arc::new(Box::new(stun_info_collector)),
|
|
}
|
|
}
|
|
|
|
pub async fn collect_ip_addrs(&self) -> GetIpListResponse {
|
|
let mut task = self.collect_ip_task.lock().await;
|
|
if task.is_empty() {
|
|
let cached_ip_list = self.cached_ip_list.clone();
|
|
*cached_ip_list.write().await =
|
|
Self::do_collect_local_ip_addrs(self.net_ns.clone()).await;
|
|
let net_ns = self.net_ns.clone();
|
|
let stun_info_collector = self.stun_info_collector.clone();
|
|
let cached_ip_list = self.cached_ip_list.clone();
|
|
task.spawn(async move {
|
|
let mut last_fetch_iface_time = std::time::Instant::now();
|
|
loop {
|
|
if last_fetch_iface_time.elapsed().as_secs() > CACHED_IP_LIST_TIMEOUT_SEC {
|
|
let ifaces = Self::do_collect_local_ip_addrs(net_ns.clone()).await;
|
|
*cached_ip_list.write().await = ifaces;
|
|
last_fetch_iface_time = std::time::Instant::now();
|
|
}
|
|
|
|
let stun_info = stun_info_collector.get_stun_info();
|
|
for ip in stun_info.public_ip.iter() {
|
|
let Ok(ip_addr) = ip.parse::<IpAddr>() else {
|
|
continue;
|
|
};
|
|
|
|
match ip_addr {
|
|
IpAddr::V4(v) => {
|
|
cached_ip_list.write().await.public_ipv4.replace(v.into());
|
|
}
|
|
IpAddr::V6(v) => {
|
|
cached_ip_list.write().await.public_ipv6.replace(v.into());
|
|
}
|
|
}
|
|
}
|
|
|
|
tracing::debug!(
|
|
"got public ip: {:?}, {:?}",
|
|
cached_ip_list.read().await.public_ipv4,
|
|
cached_ip_list.read().await.public_ipv6
|
|
);
|
|
|
|
let sleep_sec = if cached_ip_list.read().await.public_ipv4.is_some() {
|
|
CACHED_IP_LIST_TIMEOUT_SEC
|
|
} else {
|
|
3
|
|
};
|
|
tokio::time::sleep(std::time::Duration::from_secs(sleep_sec)).await;
|
|
}
|
|
});
|
|
}
|
|
|
|
self.cached_ip_list.read().await.deref().clone()
|
|
}
|
|
|
|
pub async fn collect_interfaces(net_ns: NetNS, filter: bool) -> Vec<NetworkInterface> {
|
|
let _g = net_ns.guard();
|
|
let ifaces = pnet::datalink::interfaces();
|
|
let mut ret = vec![];
|
|
for iface in ifaces {
|
|
let f = InterfaceFilter {
|
|
iface: iface.clone(),
|
|
};
|
|
|
|
if filter && !f.filter_iface().await {
|
|
continue;
|
|
}
|
|
|
|
ret.push(iface);
|
|
}
|
|
|
|
ret
|
|
}
|
|
|
|
#[tracing::instrument(skip(net_ns))]
|
|
async fn do_collect_local_ip_addrs(net_ns: NetNS) -> GetIpListResponse {
|
|
let mut ret = GetIpListResponse::default();
|
|
|
|
let ifaces = Self::collect_interfaces(net_ns.clone(), true).await;
|
|
let _g = net_ns.guard();
|
|
for iface in ifaces {
|
|
for ip in iface.ips {
|
|
let ip: std::net::IpAddr = ip.ip();
|
|
if let std::net::IpAddr::V4(v4) = ip {
|
|
if ip.is_loopback() || ip.is_multicast() {
|
|
continue;
|
|
}
|
|
ret.interface_ipv4s.push(v4.into());
|
|
}
|
|
}
|
|
}
|
|
|
|
let ifaces = Self::collect_interfaces(net_ns.clone(), false).await;
|
|
let _g = net_ns.guard();
|
|
for iface in ifaces {
|
|
for ip in iface.ips {
|
|
let ip: std::net::IpAddr = ip.ip();
|
|
if let std::net::IpAddr::V6(v6) = ip {
|
|
if v6.is_multicast() || v6.is_loopback() || v6.is_unicast_link_local() {
|
|
continue;
|
|
}
|
|
ret.interface_ipv6s.push(v6.into());
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Ok(v4_addr) = local_ipv4().await {
|
|
tracing::trace!("got local ipv4: {}", v4_addr);
|
|
if !ret.interface_ipv4s.contains(&v4_addr.into()) {
|
|
ret.interface_ipv4s.push(v4_addr.into());
|
|
}
|
|
}
|
|
|
|
if let Ok(v6_addr) = local_ipv6().await {
|
|
tracing::trace!("got local ipv6: {}", v6_addr);
|
|
if !ret.interface_ipv6s.contains(&v6_addr.into()) {
|
|
ret.interface_ipv6s.push(v6_addr.into());
|
|
}
|
|
}
|
|
|
|
ret
|
|
}
|
|
}
|