From 9d4d50f3497a910531af0d432ee9331df76dfcfb Mon Sep 17 00:00:00 2001 From: Banyc <36535895+Banyc@users.noreply.github.com> Date: Sat, 21 Mar 2026 09:14:27 +0000 Subject: [PATCH 01/12] split `Connection` trait into `ConnRead` and `ConnWrite` for future ownership split --- src/client/relay.rs | 108 +++++++++++++++++++--------------- src/network/mod.rs | 69 ++++++++-------------- src/network/tcp_connection.rs | 56 ++++++------------ src/network/tcp_listener.rs | 8 +-- src/server/server.rs | 38 ++++++------ 5 files changed, 129 insertions(+), 150 deletions(-) diff --git a/src/client/relay.rs b/src/client/relay.rs index ae1f0c4..34a6319 100644 --- a/src/client/relay.rs +++ b/src/client/relay.rs @@ -1,13 +1,13 @@ use crate::client::Args; -use crate::client::prettylog::log_handshake_success; use crate::client::http::SelfInfo; +use crate::client::prettylog::log_handshake_success; use crate::codec::frame::{Frame, HandshakeFrame, HandshakeReplyFrame, KeepAliveFrame}; use crate::crypto::Block; -use crate::network::{create_connection, Connection, ConnectionConfig, TCPConnectionConfig}; +use crate::network::{ConnectionConfig, ConnManage, TCPConnectionConfig, create_connection}; use crate::utils; use std::sync::Arc; use std::time::Instant; -use tokio::sync::{mpsc, RwLock}; +use tokio::sync::{RwLock, mpsc}; use tokio::time::{Duration, interval}; const CHANNEL_BUFFER_SIZE: usize = 1000; @@ -48,10 +48,10 @@ impl RelayClient { } } - pub async fn run(&mut self, mut conn: Box) -> crate::Result<()> { + pub async fn run(&mut self, mut conn: Box) -> crate::Result<()> { let mut keepalive_ticker = interval(self.cfg.keepalive_interval); let mut keepalive_wait: u8 = 0; - + // IPv6 update interval (check every 5 minutes) let mut ipv6_update_ticker = interval(Duration::from_secs(300)); ipv6_update_ticker.tick().await; // Skip first immediate tick @@ -62,7 +62,8 @@ impl RelayClient { let stun_port = self.cfg.stun_port; let mut last_active = Instant::now(); - let timeout_secs = (self.cfg.keep_alive_thresh - 1) as u64 * self.cfg.keepalive_interval.as_secs(); + let timeout_secs = + (self.cfg.keep_alive_thresh - 1) as u64 * self.cfg.keepalive_interval.as_secs(); loop { tokio::select! { _ = keepalive_ticker.tick() => { @@ -95,7 +96,7 @@ impl RelayClient { } } } - + // Periodic IPv6 address update check _ = ipv6_update_ticker.tick() => { tracing::debug!("ipv6 update tick"); @@ -107,7 +108,7 @@ impl RelayClient { } // TODO:get stun port } - + // inbound result = conn.read_frame() => { last_active = Instant::now(); @@ -162,17 +163,24 @@ impl RelayClient { Ok(()) } - async fn connect(&self) -> crate::Result> { - let conn = create_connection(ConnectionConfig::TCP(TCPConnectionConfig { - server_addr: self.cfg.server_addr.clone(), - }), self.block.clone()).await; + async fn connect(&self) -> crate::Result> { + let conn = create_connection( + ConnectionConfig::TCP(TCPConnectionConfig { + server_addr: self.cfg.server_addr.clone(), + }), + self.block.clone(), + ) + .await; match conn { Ok(conn) => Ok(conn), - Err(e) => Err(e) + Err(e) => Err(e), } } - async fn handshake(&self, conn: &mut Box) -> crate::Result { + async fn handshake( + &self, + conn: &mut Box, + ) -> crate::Result { conn.write_frame(Frame::Handshake(HandshakeFrame { identity: self.cfg.identity.clone(), })) @@ -187,8 +195,7 @@ impl RelayClient { } } -#[derive(Clone)] -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct RelayStatus { pub rx_error: u64, pub rx_frame: u64, @@ -198,7 +205,7 @@ pub struct RelayStatus { impl Default for RelayStatus { fn default() -> Self { - Self{ + Self { rx_error: 0, tx_error: 0, rx_frame: 0, @@ -231,33 +238,34 @@ impl RelayHandler { handshake_reply: Arc::new(RwLock::new(None)), } } - + /// Get self information pub async fn get_self_info(&self) -> Option { let reply_guard = self.handshake_reply.read().await; match (&self.config, reply_guard.as_ref()) { - (Some(cfg), Some(reply)) => { - Some(SelfInfo { - identity: cfg.identity.clone(), - private_ip: reply.private_ip.clone(), - mask: reply.mask.clone(), - gateway: reply.gateway.clone(), - ciders: reply.ciders.clone(), - ipv6: cfg.ipv6.clone(), - port: cfg.port, - stun_ip: cfg.stun_ip.clone(), - stun_port: cfg.stun_port, - }) - } + (Some(cfg), Some(reply)) => Some(SelfInfo { + identity: cfg.identity.clone(), + private_ip: reply.private_ip.clone(), + mask: reply.mask.clone(), + gateway: reply.gateway.clone(), + ciders: reply.ciders.clone(), + ipv6: cfg.ipv6.clone(), + port: cfg.port, + stun_ip: cfg.stun_ip.clone(), + stun_port: cfg.stun_port, + }), _ => None, } } - pub fn run_client(&mut self, cfg: RelayClientConfig, - on_ready: mpsc::Sender) { + pub fn run_client( + &mut self, + cfg: RelayClientConfig, + on_ready: mpsc::Sender, + ) { // Store config self.config = Some(cfg.clone()); - + let (outbound_tx, outbound_rx) = mpsc::channel(cfg.outbound_buffer_size); let mut client = RelayClient::new( cfg.clone(), @@ -266,7 +274,7 @@ impl RelayHandler { self.block.clone(), ); self.outbound_tx = Some(outbound_tx); - + // Store handshake reply when received let handshake_reply = self.handshake_reply.clone(); @@ -289,15 +297,15 @@ impl RelayHandler { continue; } }; - + tracing::info!("Handshake complete with {} peers", frame.peer_details.len()); - + // Store handshake reply in handler { let mut guard = handshake_reply.write().await; *guard = Some(frame.clone()); } - + if let Err(e) = on_ready.send(frame.clone()).await { tracing::error!("on ready send fail: {}", e); } @@ -310,7 +318,7 @@ impl RelayHandler { }); } - pub fn get_outbound_tx(&self)-> Option> { + pub fn get_outbound_tx(&self) -> Option> { self.outbound_tx.clone() } @@ -322,7 +330,7 @@ impl RelayHandler { Err(e) => { // self.metrics.tx_error += 1; Err(format!("device=> server fail {:?}", e).into()) - }, + } } } @@ -332,11 +340,11 @@ impl RelayHandler { Some(frame) => { self.metrics.rx_frame += 1; Ok(frame) - }, + } None => { self.metrics.rx_error += 1; Err("server => device fail for closed channel".into()) - }, + } } } @@ -345,10 +353,14 @@ impl RelayHandler { } } -pub async fn new_relay_handler(args: &Args, block: Arc>, - ipv6: String, port: u16, - stun_ip: String, stun_port: u16) - ->crate::Result<(RelayHandler, HandshakeReplyFrame)> { +pub async fn new_relay_handler( + args: &Args, + block: Arc>, + ipv6: String, + port: u16, + stun_ip: String, + stun_port: u16, +) -> crate::Result<(RelayHandler, HandshakeReplyFrame)> { let client_config = RelayClientConfig { server_addr: args.server.clone(), keepalive_interval: Duration::from_secs(args.keepalive_interval), @@ -358,7 +370,7 @@ pub async fn new_relay_handler(args: &Args, block: Arc>, ipv6, port, stun_ip, - stun_port + stun_port, }; let mut handler = RelayHandler::new(block); @@ -373,4 +385,4 @@ pub async fn new_relay_handler(args: &Args, block: Arc>, log_handshake_success(&device_config); Ok((handler, device_config)) -} \ No newline at end of file +} diff --git a/src/network/mod.rs b/src/network/mod.rs index 851d051..33ca2e9 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -4,9 +4,9 @@ pub mod tcp_listener; use crate::codec::frame::Frame; use crate::crypto::Block; +use crate::network::ListenerConfig::TCP; use crate::network::tcp_connection::TcpConnection; use crate::network::tcp_listener::TCPListener; -use crate::network::ListenerConfig::TCP; use async_trait::async_trait; use ipnet::IpNet; use std::fmt::Display; @@ -21,47 +21,24 @@ use tokio::time::timeout; /// Default timeout for TCP connection establishment const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(10); -/// Network connection abstraction for reading/writing frames -/// -/// This trait provides a protocol-agnostic interface for connection operations. -/// Implementations handle the underlying transport (TCP, UDP, etc.) and frame -/// marshaling/unmarshaling with encryption/decryption. #[async_trait] -pub trait Connection: Send + Sync { - /// Read a frame from the connection - /// - /// Blocks until a complete frame is received and decoded. - /// - /// # Returns - /// - `Ok(Frame)` - Successfully received and decoded frame - /// - `Err` - Connection error or frame parsing failure +pub trait ConnRead: Send + Sync { async fn read_frame(&mut self) -> crate::Result; +} - /// Write a frame to the connection - /// - /// Encodes and sends the frame over the connection. - /// - /// # Arguments - /// - `frame` - The frame to send - /// - /// # Returns - /// - `Ok(())` - Frame sent successfully - /// - `Err` - Connection error or encoding failure +#[async_trait] +pub trait ConnWrite: Send + Sync { async fn write_frame(&mut self, frame: Frame) -> crate::Result<()>; - - // async fn send_frame_to(&mut self, frame: &Frame, to: SocketAddr) -> crate::Result<()>; - - /// Close the connection gracefully async fn close(&mut self); +} - /// Get the peer's socket address - /// - /// # Returns - /// - `Ok(SocketAddr)` - Peer's address - /// - `Err` - Connection not established or closed +#[async_trait] +pub trait HasPeerAddr { fn peer_addr(&mut self) -> io::Result; } +pub trait ConnManage: ConnRead + ConnWrite + HasPeerAddr {} + /// Network listener abstraction for accepting connections /// /// This trait provides a protocol-agnostic interface for server-side operations. @@ -86,7 +63,7 @@ pub trait Listener: Send + Sync { /// # Returns /// - `Ok(Receiver)` - Channel for receiving new connections /// - `Err` - Failed to create subscription channel - async fn subscribe_on_conn(&mut self) -> crate::Result>>; + async fn subscribe_on_conn(&mut self) -> crate::Result>>; /// Close the listener /// @@ -135,7 +112,10 @@ impl PartialEq for &ConnectionMeta { impl ConnectionMeta { pub fn dump(&self) -> String { - format!("{},{},{},{}", self.cluster, self.identity, self.private_ip, self.last_active) + format!( + "{},{},{},{}", + self.cluster, self.identity, self.private_ip, self.last_active + ) } /// Check if a destination IP matches this connection's routing rules /// @@ -160,7 +140,8 @@ impl ConnectionMeta { for cidr in &self.ciders { if let Ok(network) = cidr.parse::() - && network.contains(&dst_ip) { + && network.contains(&dst_ip) + { return true; } } @@ -205,23 +186,25 @@ pub fn create_listener( } pub struct TCPConnectionConfig { - pub(crate) server_addr: String + pub(crate) server_addr: String, } pub enum ConnectionConfig { TCP(TCPConnectionConfig), } -pub async fn create_connection(config: ConnectionConfig, - block: Arc>, -) -> crate::Result> { +pub async fn create_connection( + config: ConnectionConfig, + block: Arc>, +) -> crate::Result> { match config { ConnectionConfig::TCP(config) => { // Connect with timeout let connect_result = timeout( DEFAULT_CONNECT_TIMEOUT, - TcpStream::connect(&config.server_addr) - ).await; + TcpStream::connect(&config.server_addr), + ) + .await; match connect_result { Ok(Ok(stream)) => { @@ -233,4 +216,4 @@ pub async fn create_connection(config: ConnectionConfig, } } } -} \ No newline at end of file +} diff --git a/src/network/tcp_connection.rs b/src/network/tcp_connection.rs index 63d0067..b2b726c 100644 --- a/src/network/tcp_connection.rs +++ b/src/network/tcp_connection.rs @@ -2,7 +2,7 @@ use crate::codec::frame::Frame; use crate::codec::parser::Parser; use crate::crypto::Block; use crate::crypto::plain::PlainBlock; -use crate::network::Connection; +use crate::network::{ConnManage, ConnRead, ConnWrite, HasPeerAddr}; use async_trait::async_trait; use bytes::{Buf, BytesMut}; use std::io; @@ -11,7 +11,7 @@ use std::sync::Arc; use std::time::Duration; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; -use tokio::time::{timeout, Instant}; +use tokio::time::{Instant, timeout}; /// Default timeout for read operations const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(20); @@ -114,15 +114,7 @@ impl TcpConnection { } #[async_trait] -impl Connection for TcpConnection { - /// Read a complete frame from the connection - /// - /// Reads data from the socket into a buffer and attempts to parse - /// complete frames. Blocks until a frame is available or error occurs. - /// - /// # Returns - /// - `Ok(Frame)` - Successfully received frame - /// - `Err` - Connection error, EOF, parse error, or timeout +impl ConnRead for TcpConnection { async fn read_frame(&mut self) -> crate::Result { let deadline = Instant::now() + self.read_timeout; @@ -139,10 +131,8 @@ impl Connection for TcpConnection { let remaining = deadline.saturating_duration_since(Instant::now()); - let read_result = timeout( - remaining, - self.socket.read_buf(&mut self.input_stream) - ).await; + let read_result = + timeout(remaining, self.socket.read_buf(&mut self.input_stream)).await; match read_result { Ok(Ok(0)) => { @@ -154,23 +144,16 @@ impl Connection for TcpConnection { } Ok(Ok(n)) => { tracing::debug!("read {} bytes", n) - }, + } Ok(Err(e)) => return Err(e.into()), Err(_) => return Err("read timeout".into()), } } } +} - /// Write a frame to the connection - /// - /// Marshals the frame with encryption and sends it over the socket. - /// - /// # Arguments - /// - `frame` - Frame to send - /// - /// # Returns - /// - `Ok(())` - Frame sent successfully - /// - `Err` - Marshal error, write error, or timeout +#[async_trait] +impl ConnWrite for TcpConnection { async fn write_frame(&mut self, frame: Frame) -> crate::Result<()> { let result = Parser::marshal(frame, self.block.as_ref()); let buf = match result { @@ -180,15 +163,12 @@ impl Connection for TcpConnection { } }; - // Write with timeout - let write_result = timeout( - self.write_timeout, - async { - self.socket.write_all(buf.as_slice()).await?; - self.socket.flush().await?; - Ok::<(), std::io::Error>(()) - } - ).await; + let write_result = timeout(self.write_timeout, async { + self.socket.write_all(buf.as_slice()).await?; + self.socket.flush().await?; + Ok::<(), std::io::Error>(()) + }) + .await; match write_result { Ok(Ok(())) => Ok(()), @@ -197,13 +177,15 @@ impl Connection for TcpConnection { } } - /// Close the connection gracefully async fn close(&mut self) { let _ = self.socket.shutdown().await; } +} - /// Get the peer's socket address +impl HasPeerAddr for TcpConnection { fn peer_addr(&mut self) -> io::Result { self.socket.peer_addr() } } + +impl ConnManage for TcpConnection {} diff --git a/src/network/tcp_listener.rs b/src/network/tcp_listener.rs index d07ec5b..95b6ef8 100644 --- a/src/network/tcp_listener.rs +++ b/src/network/tcp_listener.rs @@ -1,6 +1,6 @@ use crate::crypto::Block; use crate::network::tcp_connection::TcpConnection; -use crate::network::{Connection, Listener}; +use crate::network::{ConnManage, Listener}; use async_trait::async_trait; use std::io::ErrorKind; use std::sync::Arc; @@ -21,7 +21,7 @@ pub struct TCPListener { /// Underlying tokio TCP listener listener: Option, /// Channel sender for broadcasting new connections - on_conn_tx: Option>>, + on_conn_tx: Option>>, /// Crypto Block block: Arc>, } @@ -121,8 +121,8 @@ impl Listener for TCPListener { /// /// # Returns /// - `Ok(Receiver)` - Channel receiver for new connections - async fn subscribe_on_conn(&mut self) -> crate::Result>> { - let (tx, rx) = mpsc::channel::>(DEFAULT_ON_CONNECTION_QUEUE); + async fn subscribe_on_conn(&mut self) -> crate::Result>> { + let (tx, rx) = mpsc::channel::>(DEFAULT_ON_CONNECTION_QUEUE); self.on_conn_tx = Some(tx); Ok(rx) } diff --git a/src/server/server.rs b/src/server/server.rs index dbbe621..5113cf4 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,9 +1,9 @@ use crate::codec::frame::Frame::HandshakeReply; use crate::codec::frame::{Frame, HandshakeFrame, HandshakeReplyFrame, KeepAliveFrame, PeerDetail}; use crate::crypto::Block; +use crate::network::ConnectionMeta; use crate::network::connection_manager::ConnectionManager; -use crate::network::{Connection, ListenerConfig, create_listener, TCPListenerConfig}; -use crate::network::{ConnectionMeta}; +use crate::network::{ListenerConfig, ConnManage, TCPListenerConfig, create_listener}; use crate::server::client_manager::ClientManager; use crate::server::config::ServerConfig; use std::sync::Arc; @@ -78,7 +78,7 @@ impl Server { } } - fn handle_conn(&self, mut conn: Box) -> crate::Result<()> { + fn handle_conn(&self, mut conn: Box) -> crate::Result<()> { let peer_addr = conn.peer_addr().unwrap(); tracing::debug!("new connection from {}", conn.peer_addr().unwrap()); @@ -98,7 +98,7 @@ impl Server { pub struct Handler { connection_manager: Arc, client_manager: Arc, - conn: Box, + conn: Box, outbound_tx: mpsc::Sender, outbound_rx: mpsc::Receiver, cluster: Option, @@ -108,7 +108,7 @@ impl Handler { pub fn new( connection_manager: Arc, client_manager: Arc, - conn: Box, + conn: Box, ) -> Handler { let (tx, rx) = mpsc::channel(OUTBOUND_BUFFER_SIZE); Self { @@ -228,20 +228,16 @@ impl Handler { /// fn build_others(&self, cluster: &str, my_id: &String) -> Vec { // reply handshake with other clients info - let others = self - .client_manager - .get_cluster_clients_exclude(my_id); + let others = self.client_manager.get_cluster_clients_exclude(my_id); others .iter() .map(|client| { - let (ipv6, port, stun_ip, stun_port, last_active) = match self.connection_manager - .get_connection_by_identity(cluster, &client.identity) { - Some(c) => { - (c.ipv6, c.port, c.stun_ip, c.stun_port, c.last_active) - }, - None => { - ("".to_string(), 0, "".to_string(), 0, 0) - } + let (ipv6, port, stun_ip, stun_port, last_active) = match self + .connection_manager + .get_connection_by_identity(cluster, &client.identity) + { + Some(c) => (c.ipv6, c.port, c.stun_ip, c.stun_port, c.last_active), + None => ("".to_string(), 0, "".to_string(), 0, 0), }; PeerDetail { @@ -262,8 +258,14 @@ impl Handler { async fn handle_frame(&mut self, frame: Frame) { match frame { Frame::KeepAlive(frame) => { - tracing::info!("on keepalive from {} {}:{} {}:{}", - frame.identity, frame.ipv6, frame.port, frame.stun_ip, frame.stun_port); + tracing::info!( + "on keepalive from {} {}:{} {}:{}", + frame.identity, + frame.ipv6, + frame.port, + frame.stun_ip, + frame.stun_port + ); let client = self.client_manager.get_client(&frame.identity); let mut name = String::new(); From 9dd21842e22d615450376ba2a064a5af7a3e1427 Mon Sep 17 00:00:00 2001 From: Banyc <36535895+Banyc@users.noreply.github.com> Date: Sat, 21 Mar 2026 09:35:47 +0000 Subject: [PATCH 02/12] combine `stun_ip` and `stun_port` into `StunAddr` --- examples/read_async.rs | 5 +- src/client/http/models.rs | 1 - src/client/main.rs | 114 +++++++------- src/client/p2p/peer.rs | 214 +++++++++++++++++---------- src/client/p2p/stun.rs | 2 +- src/client/p2p/udp_server.rs | 10 +- src/client/prettylog.rs | 101 ++++++++----- src/client/relay.rs | 42 ++---- src/codec/frame.rs | 23 ++- src/codec/parser.rs | 33 +++-- src/network/connection_manager.rs | 60 +++++--- src/network/mod.rs | 6 +- src/network/tcp_connection.rs | 9 +- src/server/{server.rs => handler.rs} | 26 ++-- src/server/main.rs | 16 +- src/server/mod.rs | 6 +- src/utils/device.rs | 4 +- src/utils/mod.rs | 15 +- src/utils/sys_route.rs | 12 +- 19 files changed, 398 insertions(+), 301 deletions(-) rename src/server/{server.rs => handler.rs} (93%) diff --git a/examples/read_async.rs b/examples/read_async.rs index 78bf767..4dfdac8 100644 --- a/examples/read_async.rs +++ b/examples/read_async.rs @@ -12,10 +12,8 @@ // // 0. You just DO WHAT THE FUCK YOU WANT TO. -use std::thread::{sleep, Builder}; use std::time::Duration; use tokio::io::AsyncReadExt; -use tokio_util::sync::CancellationToken; use tun::{AbstractDevice, BoxError}; #[tokio::main] @@ -51,5 +49,4 @@ async fn main_entry() -> Result<(), BoxError> { } }; } - Ok(()) -} \ No newline at end of file +} diff --git a/src/client/http/models.rs b/src/client/http/models.rs index 3ab03f5..d475028 100644 --- a/src/client/http/models.rs +++ b/src/client/http/models.rs @@ -92,4 +92,3 @@ pub struct ClusterPeerInfo { pub last_active: u64, pub status: String, // "online", "warning", "inactive", "offline" } - diff --git a/src/client/main.rs b/src/client/main.rs index c4153ad..20d6256 100644 --- a/src/client/main.rs +++ b/src/client/main.rs @@ -1,18 +1,18 @@ -use clap::Parser; -use std::sync::{Arc}; -use std::time::Duration; -use tokio::sync::{mpsc, RwLock}; -use tokio::time::interval; -use crate::client::{Args, P2P_HOLE_PUNCH_PORT, P2P_UDP_PORT}; -use crate::client::relay::{RelayHandler, new_relay_handler}; -use crate::client::p2p::peer::{PeerHandler}; -use crate::client::prettylog::{get_status, log_startup_banner}; -use crate::client::p2p::stun::StunClient; use crate::client::http::server; +use crate::client::p2p::peer::PeerHandler; +use crate::client::p2p::stun::StunClient; +use crate::client::prettylog::{get_status, log_startup_banner}; +use crate::client::relay::{RelayHandler, new_relay_handler}; +use crate::client::{Args, P2P_HOLE_PUNCH_PORT, P2P_UDP_PORT}; use crate::codec::frame::{DataFrame, Frame, HandshakeReplyFrame}; use crate::crypto::{self, Block}; -use crate::utils; -use crate::utils::device::{DeviceHandler}; +use crate::utils::device::DeviceHandler; +use crate::utils::{self, StunAddr}; +use clap::Parser; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::{RwLock, mpsc}; +use tokio::time::interval; pub async fn run_client() { let args = Args::parse(); @@ -35,38 +35,35 @@ pub async fn run_client() { let block = crypto::new_block(&crypto_config); let crypto_block: Arc> = Arc::new(block); - let ipv6 = utils::get_ipv6().unwrap_or(String::new()); + let ipv6 = utils::get_ipv6().unwrap_or_default(); let stun_result = StunClient::new().discover(P2P_HOLE_PUNCH_PORT).await; - let (stun_ip, stun_port) = match stun_result { - Ok(result) => (result.public_ip.to_string(), result.public_port), - Err(_) => { - ("".to_string(), 0) - } + let stun = match stun_result { + Ok(result) => Some(StunAddr { + ip: result.public_ip.to_string(), + port: result.public_port, + }), + Err(_) => None, }; // create relay handler - let (mut relay_handler, device_config) = match new_relay_handler(&args, - crypto_block.clone(), - ipv6, P2P_UDP_PORT, - stun_ip, - stun_port).await { - Ok(result) => result, - Err(e) => { - tracing::error!("Failed to setup client: {}", e); - return; - } - }; + let (mut relay_handler, device_config) = + match new_relay_handler(&args, crypto_block.clone(), ipv6, P2P_UDP_PORT, stun).await { + Ok(result) => result, + Err(e) => { + tracing::error!("Failed to setup client: {}", e); + return; + } + }; // Initialize P2P handler if enabled (wrapped in Arc> for sharing with device packet task) let p2p_handler = if args.enable_p2p { tracing::info!("P2P mode enabled"); - let mut handler = PeerHandler::new( - crypto_block.clone(), - args.identity.clone(), - ); + let mut handler = PeerHandler::new(crypto_block.clone(), args.identity.clone()); handler.run_peer_service(); - handler.rewrite_peers(device_config.peer_details.clone()).await; + handler + .rewrite_peers(device_config.peer_details.clone()) + .await; handler.start_probe_timer().await; Some(Arc::new(RwLock::new(handler))) } else { @@ -77,12 +74,12 @@ pub async fn run_client() { // Check iptables availability if masq is enabled (Linux only) #[cfg(target_os = "linux")] { - if args.masq { - if let Err(e) = crate::utils::sys_route::SysRoute::check_iptables_available() { - eprintln!("❌ Error: {}", e); - eprintln!("\nPlease install iptables or run without --masq option."); - std::process::exit(1); - } + if args.masq + && let Err(e) = crate::utils::sys_route::SysRoute::check_iptables_available() + { + eprintln!("❌ Error: {}", e); + eprintln!("\nPlease install iptables or run without --masq option."); + std::process::exit(1); } } @@ -91,7 +88,7 @@ pub async fn run_client() { let enable_masq = args.masq; #[cfg(not(target_os = "linux"))] let enable_masq = false; - + let mut dev = match init_device(&device_config, enable_masq).await { Ok(d) => d, Err(e) => { @@ -113,7 +110,10 @@ pub async fn run_client() { run_event_loop(&mut relay_handler, p2p_handler, &mut dev).await; } -async fn init_device(device_config: &HandshakeReplyFrame, enable_masq: bool) -> crate::Result { +async fn init_device( + device_config: &HandshakeReplyFrame, + enable_masq: bool, +) -> crate::Result { tracing::info!("Initializing device with config: {:?}", device_config); let mut dev = DeviceHandler::new(); let tun_index = dev.run(device_config, enable_masq).await?; @@ -124,16 +124,16 @@ async fn init_device(device_config: &HandshakeReplyFrame, enable_masq: bool) -> } dev.reload_route(device_config.peer_details.clone()).await; - + // Setup CIDR mapping DNAT rules - if !device_config.cider_mapping.is_empty() { - if let Err(e) = dev.setup_cidr_mapping(&device_config.cider_mapping) { - tracing::error!("Failed to setup CIDR mapping DNAT rules: {}", e); - // Don't fail initialization, just log the error - // This allows the client to continue even if DNAT setup fails - } + if !device_config.cider_mapping.is_empty() + && let Err(e) = dev.setup_cidr_mapping(&device_config.cider_mapping) + { + tracing::error!("Failed to setup CIDR mapping DNAT rules: {}", e); + // Don't fail initialization, just log the error + // This allows the client to continue even if DNAT setup fails } - + Ok(dev) } @@ -215,13 +215,18 @@ async fn handle_device_packet( p2p_handler: Option>>, packet: Vec, ) { - let data_frame = DataFrame { payload: packet.clone() }; + let data_frame = DataFrame { + payload: packet.clone(), + }; // Try P2P first if available if let Some(arc) = &p2p_handler { let dst = data_frame.dst(); let guard = arc.read().await; - match guard.send_frame(Frame::Data(data_frame.clone()), dst.as_str()).await { + match guard + .send_frame(Frame::Data(data_frame.clone()), dst.as_str()) + .await + { Ok(_) => { tracing::debug!("Device -> P2P: {} bytes", packet.len()); return; @@ -253,7 +258,10 @@ async fn handle_relay_frame( } } Frame::KeepAlive(keepalive) => { - tracing::debug!("Received keepalive with {:?} peer details", keepalive.peer_details); + tracing::debug!( + "Received keepalive with {:?} peer details", + keepalive.peer_details + ); // Update routes in device handler dev.reload_route(keepalive.peer_details.clone()).await; diff --git a/src/client/p2p/peer.rs b/src/client/p2p/peer.rs index edd2c5e..7cb9003 100644 --- a/src/client/p2p/peer.rs +++ b/src/client/p2p/peer.rs @@ -1,15 +1,16 @@ - +use crate::client::p2p::udp_server::UDPServer; +use crate::client::p2p::{ + CONNECTION_TIMEOUT, KEEPALIVE_INTERVAL, OUTBOUND_BUFFER_SIZE, PeerMeta, PeerStatus, +}; +use crate::client::{P2P_HOLE_PUNCH_PORT, P2P_UDP_PORT}; +use crate::codec::frame::{Frame, PeerDetail, ProbeHolePunchFrame, ProbeIPv6Frame}; +use crate::codec::parser::Parser; +use crate::crypto::Block; use std::collections::HashMap; use std::net::SocketAddr; use std::sync::Arc; use std::time::{Duration, Instant}; -use tokio::sync::{mpsc, RwLock}; -use crate::client::{P2P_HOLE_PUNCH_PORT, P2P_UDP_PORT}; -use crate::client::p2p::{PeerMeta, PeerStatus, CONNECTION_TIMEOUT, KEEPALIVE_INTERVAL, OUTBOUND_BUFFER_SIZE}; -use crate::client::p2p::udp_server::UDPServer; -use crate::codec::frame::{Frame, ProbeHolePunchFrame, ProbeIPv6Frame, PeerDetail}; -use crate::codec::parser::Parser; -use crate::crypto::Block; +use tokio::sync::{RwLock, mpsc}; pub struct PeerHandler { /// Map of peer identity to peer metadata (shared with keepalive task) @@ -48,8 +49,7 @@ enum SendResult { } impl PeerHandler { - pub fn new(block: Arc>, - identity: String) -> Self { + pub fn new(block: Arc>, identity: String) -> Self { Self { peers: Arc::new(RwLock::new(HashMap::new())), outbound_tx: None, @@ -62,11 +62,10 @@ impl PeerHandler { } /// run peer service listen udp socket for p2p - pub fn run_peer_service(&mut self) { + pub fn run_peer_service(&mut self) { let (output_tx, output_rx) = mpsc::channel(OUTBOUND_BUFFER_SIZE); let (inbound_tx, inbound_rx) = mpsc::channel(OUTBOUND_BUFFER_SIZE); - let mut udp_server = UDPServer::new(self.port, self.stun_port, - inbound_tx, output_rx); + let mut udp_server = UDPServer::new(self.port, self.stun_port, inbound_tx, output_rx); tokio::spawn(async move { if let Err(e) = udp_server.serve().await { @@ -113,7 +112,12 @@ impl PeerHandler { false, // is_ipv4 ); if stun_remote.is_some() { - tracing::info!("Added Hole Punch peer: {} at {}:{}", p.identity, p.ipv6, p.port); + tracing::info!( + "Added Hole Punch peer: {} at {}:{}", + p.identity, + p.ipv6, + p.port + ); } // Add or update peer in the map @@ -193,7 +197,12 @@ impl PeerHandler { true, // is_ipv6 ); if ipv6_remote.is_some() { - tracing::info!("Added IPv6 peer: {} at {}:{}", peer.identity, peer.ipv6, peer.port); + tracing::info!( + "Added IPv6 peer: {} at {}:{}", + peer.identity, + peer.ipv6, + peer.port + ); } let stun_remote = self.parse_address( @@ -203,7 +212,12 @@ impl PeerHandler { false, // is_ipv4 ); if stun_remote.is_some() { - tracing::info!("Added Hole Punch peer: {} at {}:{}", peer.identity, peer.ipv6, peer.port); + tracing::info!( + "Added Hole Punch peer: {} at {}:{}", + peer.identity, + peer.ipv6, + peer.port + ); } // Add or update peer in the map @@ -224,7 +238,6 @@ impl PeerHandler { ); } } - } } @@ -240,7 +253,12 @@ impl PeerHandler { Ok(addr) => addr, Err(e) => { let protocol = if is_ipv6 { "IPv6" } else { "STUN" }; - tracing::warn!("Invalid new {} address for peer {}: {}", protocol, peer.identity, e); + tracing::warn!( + "Invalid new {} address for peer {}: {}", + protocol, + peer.identity, + e + ); return; } }; @@ -256,7 +274,9 @@ impl PeerHandler { "Update {} address for peer {}: {} -> {}", protocol, peer.identity, - old_addr.map(|a| a.to_string()).unwrap_or_else(|| "None".to_string()), + old_addr + .map(|a| a.to_string()) + .unwrap_or_else(|| "None".to_string()), new_addr ); @@ -278,19 +298,26 @@ impl PeerHandler { /// - update last_active, this is for p2p send_frame healthy checker /// - remote address, most of the time this is not changed. pub async fn recv_frame(&mut self) -> crate::Result { - let inbound_rx = self.inbound_rx.as_mut().ok_or("inbound_rx not initialized")?; - + let inbound_rx = self + .inbound_rx + .as_mut() + .ok_or("inbound_rx not initialized")?; + loop { let (buf, remote) = inbound_rx .recv() .await .ok_or("recv from peers channel closed")?; - let (frame, _) = Parser::unmarshal(&buf, self.block.as_ref())?; + let (frame, _) = Parser::unmarshal(&buf, self.block.as_ref().as_ref())?; match frame { Frame::ProbeIPv6(probe) => { - tracing::info!("Received probe ipv6 from peer {} at {}", probe.identity, remote); + tracing::info!( + "Received probe ipv6 from peer {} at {}", + probe.identity, + remote + ); let mut peers = self.peers.write().await; if let Some(peer) = peers.get_mut(&probe.identity) { @@ -299,7 +326,11 @@ impl PeerHandler { } } Frame::ProbeHolePunch(probe) => { - tracing::info!("Received probe hole punch from peer {} at {}", probe.identity, remote); + tracing::info!( + "Received probe hole punch from peer {} at {}", + probe.identity, + remote + ); let mut peers = self.peers.write().await; if let Some(peer) = peers.get_mut(&probe.identity) { peer.stun_addr = Some(remote); @@ -322,11 +353,16 @@ impl PeerHandler { /// pub async fn send_frame(&self, frame: Frame, dest_ip: &str) -> crate::Result<()> { let peers = self.peers.read().await; - let peer = self.find_peer_by_ip_locked(&peers, dest_ip) + let peer = self + .find_peer_by_ip_locked(&peers, dest_ip) .ok_or("No peer found for destination")?; if peer.remote_addr.is_none() && peer.stun_addr.is_none() { - return Err(format!("Peer {} has no available address (IPv6 or STUN)", peer.identity).into()); + return Err(format!( + "Peer {} has no available address (IPv6 or STUN)", + peer.identity + ) + .into()); } let peer_identity = peer.identity.clone(); @@ -338,23 +374,30 @@ impl PeerHandler { drop(peers); // Marshal frame once for potential multiple attempts - let data = Parser::marshal(frame, self.block.as_ref())?; - let outbound_tx = self.outbound_tx.as_ref().ok_or("outbound_tx not initialized")?; + let data = Parser::marshal(frame, self.block.as_ref().as_ref())?; + let outbound_tx = self + .outbound_tx + .as_ref() + .ok_or("outbound_tx not initialized")?; // Attempt 1: Try IPv6 direct connection - match self.try_send_via( - outbound_tx, - &data, - remote_addr, - ipv6_last_active, - &peer_identity, - "IPv6" - ).await { + match self + .try_send_via( + outbound_tx, + &data, + remote_addr, + ipv6_last_active, + &peer_identity, + "IPv6", + ) + .await + { SendResult::Success => return Ok(()), SendResult::Expired(elapsed) => { tracing::debug!( "IPv6 connection to {} expired ({:?} ago), trying STUN", - peer_identity, elapsed + peer_identity, + elapsed ); } SendResult::NeverResponded => { @@ -366,21 +409,23 @@ impl PeerHandler { } // Attempt 2: Try STUN address - match self.try_send_via( - outbound_tx, - &data, - stun_addr, - stun_last_active, - &peer_identity, - "STUN" - ).await { + match self + .try_send_via( + outbound_tx, + &data, + stun_addr, + stun_last_active, + &peer_identity, + "STUN", + ) + .await + { SendResult::Success => Ok(()), - SendResult::Expired(elapsed) => { - Err(format!( - "Peer {} STUN connection also expired ({:?} ago)", - peer_identity, elapsed - ).into()) - } + SendResult::Expired(elapsed) => Err(format!( + "Peer {} STUN connection also expired ({:?} ago)", + peer_identity, elapsed + ) + .into()), SendResult::NeverResponded => { Err(format!("Peer {} STUN address never responded", peer_identity).into()) } @@ -389,7 +434,8 @@ impl PeerHandler { Err(format!( "Failed to send to peer {}: IPv6 unavailable/expired, STUN unavailable/expired", peer_identity - ).into()) + ) + .into()) } } } @@ -423,7 +469,12 @@ impl PeerHandler { // Connection is valid, send the packet match outbound_tx.send((data.to_vec(), addr)).await { Ok(_) => { - tracing::debug!("Sent frame to peer {} via {}: {}", peer_identity, protocol, addr); + tracing::debug!( + "Sent frame to peer {} via {}: {}", + peer_identity, + protocol, + addr + ); SendResult::Success } Err(e) => { @@ -455,7 +506,8 @@ impl PeerHandler { // Check if destination falls within peer's CIDR ranges for cidr in &peer.ciders { if let Ok(network) = cidr.parse::() - && network.contains(&dest_ip_addr) { + && network.contains(&dest_ip_addr) + { return Some(peer); } } @@ -466,27 +518,27 @@ impl PeerHandler { async fn update_peer_active(&mut self, remote_addr: SocketAddr) { let mut peers = self.peers.write().await; - + for peer in peers.values_mut() { // Check if this is from IPv6 address - if let Some(ipv6_addr) = peer.remote_addr { - if ipv6_addr == remote_addr { - peer.last_active = Some(Instant::now()); - tracing::debug!("Updated IPv6 last_active for peer: {}", peer.identity); - return; - } + if let Some(ipv6_addr) = peer.remote_addr + && ipv6_addr == remote_addr + { + peer.last_active = Some(Instant::now()); + tracing::debug!("Updated IPv6 last_active for peer: {}", peer.identity); + return; } - + // Check if this is from STUN address - if let Some(stun_addr) = peer.stun_addr { - if stun_addr == remote_addr { - peer.stun_last_active = Some(Instant::now()); - tracing::debug!("Updated STUN last_active for peer: {}", peer.identity); - return; - } + if let Some(stun_addr) = peer.stun_addr + && stun_addr == remote_addr + { + peer.stun_last_active = Some(Instant::now()); + tracing::debug!("Updated STUN last_active for peer: {}", peer.identity); + return; } } - + tracing::warn!("Received packet from unknown peer address: {}", remote_addr); } @@ -534,7 +586,8 @@ impl PeerHandler { &block, &identity, true, // is_ipv6 - ).await; + ) + .await; // Send STUN hole punch probes Self::send_probes( @@ -543,7 +596,8 @@ impl PeerHandler { &block, &identity, false, // is_ipv4/stun - ).await; + ) + .await; } }); } @@ -559,13 +613,7 @@ impl PeerHandler { let peers_guard = peers.read().await; peers_guard .values() - .filter_map(|p| { - if is_ipv6 { - p.remote_addr - } else { - p.stun_addr - } - }) + .filter_map(|p| if is_ipv6 { p.remote_addr } else { p.stun_addr }) .collect() }; @@ -586,7 +634,7 @@ impl PeerHandler { }; // Marshal once, reuse for all peers - let probe_data = match Parser::marshal(probe_frame, block.as_ref()) { + let probe_data = match Parser::marshal(probe_frame, block.as_ref().as_ref()) { Ok(data) => data, Err(e) => { let protocol = if is_ipv6 { "IPv6" } else { "STUN" }; @@ -599,11 +647,15 @@ impl PeerHandler { let protocol = if is_ipv6 { "IPv6" } else { "hole punch" }; for remote_addr in peer_addrs { if let Err(e) = outbound_tx.send((probe_data.clone(), remote_addr)).await { - tracing::warn!("Failed to send {} probe to {}: {}", protocol, remote_addr, e); + tracing::warn!( + "Failed to send {} probe to {}: {}", + protocol, + remote_addr, + e + ); } else { tracing::info!("Sent {} probe to {}", protocol, remote_addr); } } } - } diff --git a/src/client/p2p/stun.rs b/src/client/p2p/stun.rs index 05d9f60..f379880 100644 --- a/src/client/p2p/stun.rs +++ b/src/client/p2p/stun.rs @@ -235,7 +235,7 @@ impl StunClient { // Step 2: Detect NAT type // For now, use a simplified detection based on address comparison - let nat_type = self.detect_nat_type_simple(local_addr.clone(), public_ip, public_port).await; + let nat_type = self.detect_nat_type_simple(local_addr, public_ip, public_port).await; tracing::info!( "NAT type detected: {:?} ({})", diff --git a/src/client/p2p/udp_server.rs b/src/client/p2p/udp_server.rs index 47d4ec9..9990a05 100644 --- a/src/client/p2p/udp_server.rs +++ b/src/client/p2p/udp_server.rs @@ -138,17 +138,13 @@ impl UDPServer { // Handle IPv6 inbound packets: Network -> PeerHandler // Direct P2P connections or responses to our keepalives result = socket_ipv6.recv_from(&mut buf_ipv6) => { - if let Err(e) = self.handle_inbound(result, &mut buf_ipv6, "IPv6").await { - return Err(e); - } + self.handle_inbound(result, &mut buf_ipv6, "IPv6").await? } // Handle IPv4 inbound packets: Network -> PeerHandler // STUN-hole-punched connections or responses result = socket_ipv4.recv_from(&mut buf_ipv4) => { - if let Err(e) = self.handle_inbound(result, &mut buf_ipv4, "IPv4").await { - return Err(e); - } + self.handle_inbound(result, &mut buf_ipv4, "IPv4").await? } } } @@ -223,7 +219,7 @@ impl UDPServer { async fn handle_inbound( &self, result: std::io::Result<(usize, SocketAddr)>, - buffer: &mut Vec, + buffer: &mut [u8], protocol: &str, ) -> crate::Result<()> { match result { diff --git a/src/client/prettylog.rs b/src/client/prettylog.rs index 0dd6042..ebb325e 100644 --- a/src/client/prettylog.rs +++ b/src/client/prettylog.rs @@ -3,10 +3,13 @@ // ============================================================================ use crate::client::Args; +use crate::client::http::cache; +use crate::client::http::{ + ClusterPeerInfo, IPv6ConnectionInfo, P2PPeerInfo, P2PStatus, RelayStatusInfo, + STUNConnectionInfo, StatusResponse, TrafficStats, +}; use crate::client::p2p::peer::PeerHandler; use crate::client::relay::RelayHandler; -use crate::client::http::{StatusResponse, TrafficStats, RelayStatusInfo, P2PStatus, P2PPeerInfo, IPv6ConnectionInfo, STUNConnectionInfo, ClusterPeerInfo}; -use crate::client::http::cache; use crate::codec::frame::HandshakeReplyFrame; use crate::utils::device::DeviceHandler; use std::time::{SystemTime, UNIX_EPOCH}; @@ -38,7 +41,6 @@ pub fn log_handshake_success(config: &HandshakeReplyFrame) { println!("Ready to forward traffic"); } - pub async fn get_status(relay: &RelayHandler, peer: Option<&PeerHandler>, dev: &DeviceHandler) { println!("\n╔══════════════════════════════════════════════════════════════════════╗"); println!("║ CONNECTION STATUS ║"); @@ -46,32 +48,38 @@ pub async fn get_status(relay: &RelayHandler, peer: Option<&PeerHandler>, dev: & // traffic status // device receive is the traffic outbound - println!("Receive Bytes: {}MB", dev.tx_bytes/1024/1024); - println!("Send Bytes: {}MB", dev.rx_bytes/1024/1024); + println!("Receive Bytes: {}MB", dev.tx_bytes / 1024 / 1024); + println!("Send Bytes: {}MB", dev.rx_bytes / 1024 / 1024); // Relay Status let relay_status = relay.get_status(); println!("\n📡 Relay Connection (TCP)"); - println!(" ├─ RX Frames: {} (Errors: {})", relay_status.rx_frame, relay_status.rx_error); - println!(" └─ TX Frames: {} (Errors: {})", relay_status.tx_frame, relay_status.tx_error); - + println!( + " ├─ RX Frames: {} (Errors: {})", + relay_status.rx_frame, relay_status.rx_error + ); + println!( + " └─ TX Frames: {} (Errors: {})", + relay_status.tx_frame, relay_status.tx_error + ); + // P2P Status if let Some(peer_handler) = peer { let peer_status = peer_handler.get_status().await; - + if peer_status.is_empty() { println!("\n🔗 P2P Connections (UDP)"); println!(" └─ No peers configured"); } else { println!("\n🔗 P2P Connections (UDP): {} peers", peer_status.len()); - + for (idx, status) in peer_status.iter().enumerate() { let is_last = idx == peer_status.len() - 1; let prefix = if is_last { "└─" } else { "├─" }; let continuation = if is_last { " " } else { "│" }; - + println!(" {} Peer: {}", prefix, status.name); - + // IPv6 Direct Connection let ipv6_state = match (&status.ipv6_addr, &status.ipv6_last_active) { (None, _) => "❌ No Address".to_string(), @@ -86,7 +94,7 @@ pub async fn get_status(relay: &RelayHandler, peer: Option<&PeerHandler>, dev: & } }; println!(" {} ├─ IPv6: {}", continuation, ipv6_state); - + // STUN Hole-Punched Connection let stun_state = match (&status.stun_addr, &status.stun_last_active) { (None, _) => "❌ No Address".to_string(), @@ -106,66 +114,79 @@ pub async fn get_status(relay: &RelayHandler, peer: Option<&PeerHandler>, dev: & } else { println!("\n🔗 P2P Mode: Disabled"); } - + // Cluster Peers Status (from device handler) let others = dev.get_peer_details(); if !others.is_empty() { println!("\n👥 Cluster Peers: {} total", others.len()); - + use std::time::{SystemTime, UNIX_EPOCH}; let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs(); - + for (idx, peer) in others.iter().enumerate() { let is_last = idx == others.len() - 1; let prefix = if is_last { "└─" } else { "├─" }; let continuation = if is_last { " " } else { "│" }; - + // Online/Offline status let status_icon = if peer.last_active == 0 { - "⚪" // Offline + "⚪" // Offline } else { let elapsed = now.saturating_sub(peer.last_active); if elapsed < 30 { - "🟢" // Online + "🟢" // Online } else if elapsed < 120 { - "🟡" // Warning + "🟡" // Warning } else { - "🔴" // Inactive + "🔴" // Inactive } }; - + let online_info = if peer.last_active == 0 { "Offline".to_string() } else { let elapsed = now.saturating_sub(peer.last_active); format!("{}s ago", elapsed) }; - - println!(" {} {} {} ({})", prefix, status_icon, peer.name, online_info); + + println!( + " {} {} {} ({})", + prefix, status_icon, peer.name, online_info + ); println!(" {} ├─ Private IP: {}", continuation, peer.private_ip); - + if !peer.ciders.is_empty() { - println!(" {} ├─ Routes: {}", continuation, peer.ciders.join(", ")); + println!( + " {} ├─ Routes: {}", + continuation, + peer.ciders.join(", ") + ); } - + if !peer.ipv6.is_empty() { - println!(" {} ├─ IPv6: {}:{}", continuation, peer.ipv6, peer.port); + println!( + " {} ├─ IPv6: {}:{}", + continuation, peer.ipv6, peer.port + ); } - + if !peer.stun_ip.is_empty() { - println!(" {} └─ STUN: {}:{}", continuation, peer.stun_ip, peer.stun_port); + println!( + " {} └─ STUN: {}:{}", + continuation, peer.stun_ip, peer.stun_port + ); } else { // Adjust last item if no stun_ip println!(" {} └─ STUN: Not configured", continuation); } } } - + println!(); - + // Update HTTP cache let status = build_status_response(relay, peer, dev).await; cache::update(status).await; @@ -179,7 +200,7 @@ pub async fn build_status_response( ) -> StatusResponse { // Self information from relay let self_info = relay.get_self_info().await; - + // Traffic stats let traffic = TrafficStats { receive_bytes: dev.tx_bytes as u64, @@ -201,12 +222,12 @@ pub async fn build_status_response( let p2p = if let Some(peer_handler) = peer { let peer_statuses = peer_handler.get_status().await; let mut peers = Vec::new(); - + for status in peer_statuses { let ipv6 = status.ipv6_addr.map(|addr| { - let last_active_seconds = status.ipv6_last_active.map(|instant| { - instant.elapsed().as_secs() - }); + let last_active_seconds = status + .ipv6_last_active + .map(|instant| instant.elapsed().as_secs()); IPv6ConnectionInfo { address: addr.to_string(), connected: last_active_seconds.is_some() && last_active_seconds.unwrap() < 30, @@ -215,9 +236,9 @@ pub async fn build_status_response( }); let stun = status.stun_addr.map(|addr| { - let last_active_seconds = status.stun_last_active.map(|instant| { - instant.elapsed().as_secs() - }); + let last_active_seconds = status + .stun_last_active + .map(|instant| instant.elapsed().as_secs()); STUNConnectionInfo { address: addr.to_string(), connected: last_active_seconds.is_some(), diff --git a/src/client/relay.rs b/src/client/relay.rs index 34a6319..8d68a29 100644 --- a/src/client/relay.rs +++ b/src/client/relay.rs @@ -3,8 +3,8 @@ use crate::client::http::SelfInfo; use crate::client::prettylog::log_handshake_success; use crate::codec::frame::{Frame, HandshakeFrame, HandshakeReplyFrame, KeepAliveFrame}; use crate::crypto::Block; -use crate::network::{ConnectionConfig, ConnManage, TCPConnectionConfig, create_connection}; -use crate::utils; +use crate::network::{ConnManage, ConnectionConfig, TCPConnectionConfig, create_connection}; +use crate::utils::{self, StunAddr}; use std::sync::Arc; use std::time::Instant; use tokio::sync::{RwLock, mpsc}; @@ -22,8 +22,7 @@ pub struct RelayClientConfig { pub identity: String, pub ipv6: String, pub port: u16, - pub stun_ip: String, - pub stun_port: u16, + pub stun: Option, } pub struct RelayClient { @@ -58,8 +57,7 @@ impl RelayClient { let mut current_ipv6 = self.cfg.ipv6.clone(); let port = self.cfg.port; - let stun_ip = self.cfg.stun_ip.clone(); - let stun_port = self.cfg.stun_port; + let stun = self.cfg.stun.clone(); let mut last_active = Instant::now(); let timeout_secs = @@ -78,8 +76,9 @@ impl RelayClient { identity: self.cfg.identity.clone(), ipv6: current_ipv6.clone(), port, - stun_ip: stun_ip.clone(), - stun_port, + #[allow(clippy::unwrap_or_default)] + stun_ip: stun.as_ref().map(|stun| stun.ip.clone()).unwrap_or(String::new()), + stun_port: stun.as_ref().map(|stun| stun.port).unwrap_or(0), peer_details: vec![], // Client doesn't need to send peer info }); match conn.write_frame(keepalive_frame).await { @@ -195,7 +194,7 @@ impl RelayClient { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct RelayStatus { pub rx_error: u64, pub rx_frame: u64, @@ -203,17 +202,6 @@ pub struct RelayStatus { pub tx_error: u64, } -impl Default for RelayStatus { - fn default() -> Self { - Self { - rx_error: 0, - tx_error: 0, - rx_frame: 0, - tx_frame: 0, - } - } -} - pub struct RelayHandler { outbound_tx: Option>, inbound_rx: mpsc::Receiver, @@ -251,8 +239,12 @@ impl RelayHandler { ciders: reply.ciders.clone(), ipv6: cfg.ipv6.clone(), port: cfg.port, - stun_ip: cfg.stun_ip.clone(), - stun_port: cfg.stun_port, + stun_ip: cfg + .stun + .as_ref() + .map(|stun| stun.ip.clone()) + .unwrap_or(String::new()), + stun_port: cfg.stun.as_ref().map(|stun| stun.port).unwrap_or(0), }), _ => None, } @@ -358,8 +350,7 @@ pub async fn new_relay_handler( block: Arc>, ipv6: String, port: u16, - stun_ip: String, - stun_port: u16, + stun: Option, ) -> crate::Result<(RelayHandler, HandshakeReplyFrame)> { let client_config = RelayClientConfig { server_addr: args.server.clone(), @@ -369,8 +360,7 @@ pub async fn new_relay_handler( identity: args.identity.clone(), ipv6, port, - stun_ip, - stun_port, + stun, }; let mut handler = RelayHandler::new(block); diff --git a/src/codec/frame.rs b/src/codec/frame.rs index c0b622f..627cc54 100644 --- a/src/codec/frame.rs +++ b/src/codec/frame.rs @@ -15,9 +15,9 @@ //! - Type: Frame type identifier (1 byte) //! - Payload Length: Length of the payload in bytes (2 bytes, big-endian) -use std::collections::HashMap; -pub(crate) use crate::codec::errors::FrameError; +use crate::codec::errors::FrameError; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::fmt::Display; /// Frame type identifiers @@ -99,13 +99,20 @@ impl Display for Frame { match self { Frame::Handshake(frame) => write!(f, "handshake with {}", frame.identity), Frame::HandshakeReply(frame) => { - write!(f, "handshake reply with {} peer_details", frame.peer_details.len()) + write!( + f, + "handshake reply with {} peer_details", + frame.peer_details.len() + ) } - Frame::KeepAlive(frame) => write!(f, "keepalive, ipv6 {}:{} stun: {}:{}", - frame.ipv6, frame.port, frame.stun_ip, frame.stun_port), + Frame::KeepAlive(frame) => write!( + f, + "keepalive, ipv6 {}:{} stun: {}:{}", + frame.ipv6, frame.port, frame.stun_ip, frame.stun_port, + ), Frame::Data(frame) => write!(f, "data with payload size {}", frame.payload.len()), - Frame::ProbeIPv6(frame)=> write!(f, "{} probe ipv6", frame.identity), - Frame::ProbeHolePunch(frame)=>write!(f, "{} probe hole punch", frame.identity), + Frame::ProbeIPv6(frame) => write!(f, "{} probe ipv6", frame.identity), + Frame::ProbeHolePunch(frame) => write!(f, "{} probe hole punch", frame.identity), } } } @@ -175,7 +182,7 @@ pub struct HandshakeReplyFrame { #[derive(Debug, Serialize, Deserialize, Clone)] pub struct PeerDetail { pub name: String, - + /// Unique identifier of the peer pub identity: String, diff --git a/src/codec/parser.rs b/src/codec/parser.rs index 715b34a..4571c0b 100644 --- a/src/codec/parser.rs +++ b/src/codec/parser.rs @@ -4,11 +4,12 @@ //! of VPN protocol frames. It manages the frame header format, payload encryption/decryption, //! and JSON serialization of frame data. +use crate::codec::errors::FrameError; use crate::codec::frame::*; use crate::crypto::Block; use anyhow::Context; -use serde::Serialize; use serde::de::DeserializeOwned; +use serde::Serialize; /// Protocol magic number for frame validation const MAGIC: u32 = 0x91929394; @@ -30,7 +31,7 @@ impl Parser { /// # Returns /// * `Ok((Frame, usize))` - Parsed frame and total bytes consumed /// * `Err` - If frame is invalid, too short, or decryption/parsing fails - pub fn unmarshal(buf: &[u8], block: &Box) -> crate::Result<(Frame, usize)> { + pub fn unmarshal(buf: &[u8], block: &dyn Block) -> crate::Result<(Frame, usize)> { if buf.len() < HDR_LEN { return Err(FrameError::TooShort.into()); } @@ -41,7 +42,13 @@ impl Parser { let payload_size = u16::from_be_bytes([buf[6], buf[7]]); if !Self::validate(magic, version, payload_size, buf) { - tracing::debug!("validate header fail: magic = {} version={} payload_size={} buf size={}", magic, version, payload_size, buf.len()); + tracing::debug!( + "validate header fail: magic = {} version={} payload_size={} buf size={}", + magic, + version, + payload_size, + buf.len() + ); return Err(FrameError::Invalid.into()); } @@ -115,7 +122,7 @@ impl Parser { /// Deserialized frame data of type T fn decrypt_and_deserialize( payload: &mut Vec, - block: &Box, + block: &dyn Block, ) -> crate::Result { block .decrypt(payload) @@ -137,7 +144,7 @@ impl Parser { /// Encrypted payload bytes fn serialize_and_encrypt( data: &T, - block: &Box, + block: &dyn Block, context_msg: &str, ) -> crate::Result> { let msg = context_msg.to_string(); @@ -178,7 +185,7 @@ impl Parser { /// # Returns /// * `Ok(Vec)` - Complete frame bytes (header + encrypted payload) /// * `Err` - If serialization or encryption fails - pub fn marshal(frame: Frame, block: &Box) -> crate::Result> { + pub fn marshal(frame: Frame, block: &dyn Block) -> crate::Result> { match frame { Frame::Handshake(hs) => { let payload = @@ -200,11 +207,8 @@ impl Parser { } Frame::KeepAlive(keepalive) => { - let payload = Self::serialize_and_encrypt( - &keepalive, - block, - "failed to marshal keepalive", - )?; + let payload = + Self::serialize_and_encrypt(&keepalive, block, "failed to marshal keepalive")?; let mut buf = Self::build_header(FrameType::KeepAlive, payload.len() as u16); buf.extend_from_slice(&payload); Ok(buf) @@ -218,11 +222,8 @@ impl Parser { } Frame::ProbeIPv6(frame) => { - let payload = Self::serialize_and_encrypt( - &frame, - block, - "failed to marshal probe ipv6", - )?; + let payload = + Self::serialize_and_encrypt(&frame, block, "failed to marshal probe ipv6")?; let mut buf = Self::build_header(FrameType::ProbeIPv6, payload.len() as u16); buf.extend_from_slice(&payload); Ok(buf) diff --git a/src/network/connection_manager.rs b/src/network/connection_manager.rs index 9e86d0f..2898680 100644 --- a/src/network/connection_manager.rs +++ b/src/network/connection_manager.rs @@ -1,4 +1,4 @@ -use crate::network::ConnectionMeta; +use crate::network::{ConnectionMeta, StunAddr}; use std::collections::HashMap; use std::sync::RwLock; use std::time::{SystemTime, UNIX_EPOCH}; @@ -84,7 +84,11 @@ impl ConnectionManager { }) } - pub fn get_connection_by_identity(&self, cluster: &str, identity: &String) -> Option { + pub fn get_connection_by_identity( + &self, + cluster: &str, + identity: &String, + ) -> Option { let guard = self .cluster_connections .read() @@ -92,7 +96,7 @@ impl ConnectionManager { guard.get(cluster).and_then(|connections| { connections .iter() - .find(|conn| conn.identity ==*identity) + .find(|conn| conn.identity == *identity) .cloned() }) } @@ -105,17 +109,24 @@ impl ConnectionManager { /// # Returns /// * `Some(Vec)` - List of other connections in the cluster if the address changed /// * `None` - If the address didn't change or the connection wasn't found - pub fn update_connection_info(&self, cluster: &str, identity: &String, - ciders: Vec, - ipv6: String, port: u16, - stun_ip: String, stun_port: u16) -> Option> { + pub fn update_connection_info( + &self, + cluster: &str, + identity: &String, + ciders: Vec, + ipv6: String, + port: u16, + stun: StunAddr, + ) -> Option> { let mut cluster_map = self .cluster_connections .write() .unwrap_or_else(|e| e.into_inner()); if let Some(connections) = cluster_map.get_mut(cluster) - && let Some(conn) = connections.iter_mut().find(|c| c.identity == *identity) { + && let Some(conn) = connections.iter_mut().find(|c| c.identity == *identity) + { + let prev_conn = conn.clone(); // Always update last_active timestamp on keepalive conn.last_active = now_timestamp(); @@ -126,10 +137,13 @@ impl ConnectionManager { changed = true; } - if conn.stun_ip!= stun_ip || conn.stun_port != stun_port { + let stun_changed = match conn.stun.as_ref() { + Some(conn_stun) => conn_stun != &stun, + None => true, + }; + if stun_changed { changed = true; - conn.stun_ip = stun_ip.clone(); - conn.stun_port = stun_port; + conn.stun = Some(stun.clone()); } if conn.ciders != ciders { @@ -141,17 +155,16 @@ impl ConnectionManager { return None; } + let prev_stun = match prev_conn.stun.as_ref() { + Some(conn_stun) => conn_stun.to_string(), + None => "None".to_string(), + }; tracing::info!( - "Updated connection info for {}: {}:{} -> {}:{} stun: {}:{} -> {}:{}", - identity, - conn.ipv6, - conn.port, + "Updated connection info for {identity}: {}:{} -> {}:{} stun: {prev_stun} -> {stun}", + prev_conn.ipv6, + prev_conn.port, ipv6, port, - conn.stun_ip, - conn.stun_port, - stun_ip, - stun_port ); // Return other connections in the cluster (excluding the updated one) let others: Vec = connections @@ -161,13 +174,16 @@ impl ConnectionManager { .collect(); return Some(others); } - + None } pub fn dump_connection_info(&self) -> Vec { let mut result = Vec::new(); - let guard = self.cluster_connections.read().unwrap_or_else(|e| e.into_inner()); + let guard = self + .cluster_connections + .read() + .unwrap_or_else(|e| e.into_inner()); for (_, connections) in guard.iter() { for conn in connections { result.push(conn.clone()); @@ -181,4 +197,4 @@ impl Default for ConnectionManager { fn default() -> Self { Self::new() } -} \ No newline at end of file +} diff --git a/src/network/mod.rs b/src/network/mod.rs index 33ca2e9..c15bce0 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -7,6 +7,7 @@ use crate::crypto::Block; use crate::network::ListenerConfig::TCP; use crate::network::tcp_connection::TcpConnection; use crate::network::tcp_listener::TCPListener; +use crate::utils::StunAddr; use async_trait::async_trait; use ipnet::IpNet; use std::fmt::Display; @@ -95,12 +96,9 @@ pub struct ConnectionMeta { pub ciders: Vec, /// Channel for sending outbound frames to this client pub(crate) outbound_tx: mpsc::Sender, - /// ipv6 pub ipv6: String, pub port: u16, - // hole punch address - pub stun_ip: String, - pub stun_port: u16, + pub stun: Option, pub last_active: u64, } diff --git a/src/network/tcp_connection.rs b/src/network/tcp_connection.rs index b2b726c..4cf8052 100644 --- a/src/network/tcp_connection.rs +++ b/src/network/tcp_connection.rs @@ -102,7 +102,7 @@ impl TcpConnection { /// - `Ok(None)` - Incomplete data, need more bytes /// - `Err` - Parse error (invalid frame format) fn parse_frame(&mut self) -> crate::Result> { - let result = Parser::unmarshal(self.input_stream.as_ref(), self.block.as_ref()); + let result = Parser::unmarshal(self.input_stream.as_ref(), self.block.as_ref().as_ref()); match result { Ok((frame, total_len)) => { self.input_stream.advance(total_len); @@ -123,11 +123,10 @@ impl ConnRead for TcpConnection { return Err("read timeout".into()); } - if let Ok(frame) = self.parse_frame() { - if let Some(frame) = frame { + if let Ok(frame) = self.parse_frame() + && let Some(frame) = frame { return Ok(frame); } - } let remaining = deadline.saturating_duration_since(Instant::now()); @@ -155,7 +154,7 @@ impl ConnRead for TcpConnection { #[async_trait] impl ConnWrite for TcpConnection { async fn write_frame(&mut self, frame: Frame) -> crate::Result<()> { - let result = Parser::marshal(frame, self.block.as_ref()); + let result = Parser::marshal(frame, self.block.as_ref().as_ref()); let buf = match result { Ok(buf) => buf, Err(e) => { diff --git a/src/server/server.rs b/src/server/handler.rs similarity index 93% rename from src/server/server.rs rename to src/server/handler.rs index 5113cf4..07eece8 100644 --- a/src/server/server.rs +++ b/src/server/handler.rs @@ -3,9 +3,10 @@ use crate::codec::frame::{Frame, HandshakeFrame, HandshakeReplyFrame, KeepAliveF use crate::crypto::Block; use crate::network::ConnectionMeta; use crate::network::connection_manager::ConnectionManager; -use crate::network::{ListenerConfig, ConnManage, TCPListenerConfig, create_listener}; +use crate::network::{ConnManage, ListenerConfig, TCPListenerConfig, create_listener}; use crate::server::client_manager::ClientManager; use crate::server::config::ServerConfig; +use crate::utils::StunAddr; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; use tokio::sync::mpsc; @@ -162,8 +163,7 @@ impl Handler { outbound_tx: self.outbound_tx.clone(), ipv6: "".to_string(), // Do not set, it will be set in the keepalive frame port: 0, - stun_ip: "".to_string(), - stun_port: 0, + stun: None, last_active: now_timestamp(), }; tracing::debug!("handshake completed with {:?}", meta); @@ -232,12 +232,12 @@ impl Handler { others .iter() .map(|client| { - let (ipv6, port, stun_ip, stun_port, last_active) = match self + let (ipv6, port, stun, last_active) = match self .connection_manager .get_connection_by_identity(cluster, &client.identity) { - Some(c) => (c.ipv6, c.port, c.stun_ip, c.stun_port, c.last_active), - None => ("".to_string(), 0, "".to_string(), 0, 0), + Some(c) => (c.ipv6, c.port, c.stun.clone(), c.last_active), + None => ("".to_string(), 0, None, 0), }; PeerDetail { @@ -247,8 +247,11 @@ impl Handler { ciders: client.ciders.clone(), ipv6, port, - stun_ip, - stun_port, + stun_ip: stun + .as_ref() + .map(|stun| stun.ip.clone()) + .unwrap_or(String::new()), + stun_port: stun.map(|stun| stun.port).unwrap_or(0), last_active, } }) @@ -270,14 +273,17 @@ impl Handler { let client = self.client_manager.get_client(&frame.identity); let mut name = String::new(); if let Some(client) = client { + let stun = StunAddr { + ip: frame.stun_ip.clone(), + port: frame.stun_port, + }; let _ = self.connection_manager.update_connection_info( &client.cluster, &frame.identity, client.ciders.clone(), frame.ipv6.clone(), frame.port, - frame.stun_ip.clone(), - frame.stun_port, + stun, ); name = client.name.clone(); } diff --git a/src/server/main.rs b/src/server/main.rs index af06025..9119a6e 100644 --- a/src/server/main.rs +++ b/src/server/main.rs @@ -1,11 +1,11 @@ +use crate::network::connection_manager::ConnectionManager; use crate::server::client_manager::ClientManager; +use crate::server::conf_agent::ConfAgent; use crate::server::config; -use crate::server::server::Server; +use crate::server::config_watcher::ConfigWatcher; +use crate::server::handler::Server; use crate::{crypto, utils}; use std::sync::Arc; -use crate::network::connection_manager::ConnectionManager; -use crate::server::config_watcher::ConfigWatcher; -use crate::server::conf_agent::ConfAgent; pub async fn run_server() { let args = std::env::args().collect::>(); @@ -28,10 +28,10 @@ pub async fn run_server() { watcher.reload(); let block = crypto::new_block(&cfg.crypto_config); - + // Create connection manager let connection_manager = Arc::new(ConnectionManager::new()); - + // Create conf-agent if configured if let Some(ref conf_agent_config) = cfg.conf_agent { let agent = Arc::new(ConfAgent::new( @@ -40,7 +40,7 @@ pub async fn run_server() { connection_manager.clone(), routes_file.clone(), )); - + // Start conf-agent background task let agent_clone = agent.clone(); tokio::spawn(async move { @@ -49,7 +49,7 @@ pub async fn run_server() { } }); } - + let mut server = Server::new( cfg.server_config.clone(), client_manager, diff --git a/src/server/mod.rs b/src/server/mod.rs index b960719..b51add2 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,6 +1,6 @@ mod client_manager; +pub mod conf_agent; pub mod config; -pub mod main; -mod server; mod config_watcher; -pub mod conf_agent; +mod handler; +pub mod main; diff --git a/src/utils/device.rs b/src/utils/device.rs index ef609da..c04c3e3 100644 --- a/src/utils/device.rs +++ b/src/utils/device.rs @@ -197,8 +197,8 @@ impl DeviceHandler { }; let result = inbound_rx.recv().await; - if result.is_some() { - self.rx_bytes += result.as_ref().unwrap().len(); + if let Some(ref data) = result { + self.rx_bytes += data.len(); } result } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 62cab10..c43a803 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -5,12 +5,23 @@ use tracing_subscriber::EnvFilter; pub mod device; pub mod sys_route; +#[derive(Debug, Clone, PartialEq)] +pub struct StunAddr { + pub ip: String, + pub port: u16, +} +impl std::fmt::Display for StunAddr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.ip, self.port) + } +} + pub fn init_tracing() -> Result<(), Box> { // On Windows, disable ANSI colors to avoid garbage characters in console // On Unix systems, keep ANSI colors for better readability #[cfg(target_os = "windows")] let use_ansi = false; - + #[cfg(not(target_os = "windows"))] let use_ansi = true; @@ -21,7 +32,7 @@ pub fn init_tracing() -> Result<(), Box> { .with_default_directive(LevelFilter::INFO.into()) .from_env_lossy(), ) - .with_ansi(use_ansi) // Disable ANSI colors on Windows + .with_ansi(use_ansi) // Disable ANSI colors on Windows .with_line_number(true) .with_file(true) .finish(), diff --git a/src/utils/sys_route.rs b/src/utils/sys_route.rs index e21e057..580ff7a 100644 --- a/src/utils/sys_route.rs +++ b/src/utils/sys_route.rs @@ -49,12 +49,10 @@ impl SysRoute { match output { Ok(_) => Ok(()), Err(e) if e.kind() == std::io::ErrorKind::NotFound => { - Err(format!( - "iptables command not found. The --masq option requires iptables.\n\ + Err("iptables command not found. The --masq option requires iptables.\n\ Please either:\n\ 1. Install iptables: sudo apt-get install iptables (Debian/Ubuntu) or sudo yum install iptables (RHEL/CentOS)\n\ - 2. Run without --masq option" - ).into()) + 2. Run without --masq option".to_string().into()) } Err(e) => Err(format!("Failed to check iptables: {}", e).into()), } @@ -443,11 +441,9 @@ impl SysRoute { .output() .map_err(|e| { if e.kind() == std::io::ErrorKind::NotFound { - format!( - "iptables command not found. CIDR mapping requires iptables with NETMAP support.\n\ + "iptables command not found. CIDR mapping requires iptables with NETMAP support.\n\ Please install iptables and ensure your kernel supports NETMAP target.\n\ - NETMAP requires Linux kernel 2.6.32+ with netfilter NETMAP module." - ) + NETMAP requires Linux kernel 2.6.32+ with netfilter NETMAP module.".to_string() } else { format!("Failed to execute iptables command: {}", e) } From 1a34dde3a9df36a2764f36550ef27503450b6069 Mon Sep 17 00:00:00 2001 From: Banyc <36535895+Banyc@users.noreply.github.com> Date: Sat, 21 Mar 2026 10:40:41 +0000 Subject: [PATCH 03/12] standardize fn error using anyhow --- Cargo.toml | 8 - src/bin/client.rs | 6 + src/bin/server.rs | 6 + src/client/main.rs | 21 +- src/client/p2p/peer.rs | 44 ++-- src/client/p2p/udp_server.rs | 4 +- src/client/relay.rs | 20 +- src/cmd/client.rs | 7 - src/cmd/server.rs | 6 - src/codec/errors.rs | 2 +- src/codec/parser.rs | 8 +- src/crypto/aes256.rs | 10 +- src/crypto/chacha20.rs | 12 +- src/crypto/mod.rs | 4 +- src/crypto/plain.rs | 4 +- src/crypto/xor.rs | 4 +- src/lib.rs | 3 - src/network/mod.rs | 16 +- src/network/tcp_connection.rs | 23 ++- src/network/tcp_listener.rs | 8 +- src/server/conf_agent.rs | 16 +- src/server/config.rs | 4 +- src/server/handler.rs | 10 +- src/server/main.rs | 8 +- src/utils/device.rs | 22 +- src/utils/sys_route.rs | 370 ++++++++++++++++++++++------------ 26 files changed, 367 insertions(+), 279 deletions(-) create mode 100644 src/bin/client.rs create mode 100644 src/bin/server.rs delete mode 100644 src/cmd/client.rs delete mode 100644 src/cmd/server.rs diff --git a/Cargo.toml b/Cargo.toml index c4207f3..15efa9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,14 +36,6 @@ once_cell = "1.20" [dev-dependencies] tokio-test = "0.4" -[[bin]] -name = "server" -path = "src/cmd/server.rs" - -[[bin]] -name = "client" -path = "src/cmd/client.rs" - [[example]] name = "stun_discover" path = "examples/stun_discover.rs" diff --git a/src/bin/client.rs b/src/bin/client.rs new file mode 100644 index 0000000..6115bd1 --- /dev/null +++ b/src/bin/client.rs @@ -0,0 +1,6 @@ +use rustun::client::main; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + main::run_client().await +} diff --git a/src/bin/server.rs b/src/bin/server.rs new file mode 100644 index 0000000..0944bba --- /dev/null +++ b/src/bin/server.rs @@ -0,0 +1,6 @@ +use rustun::server::main; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + main::run_server().await +} diff --git a/src/client/main.rs b/src/client/main.rs index 20d6256..c8e3f81 100644 --- a/src/client/main.rs +++ b/src/client/main.rs @@ -14,12 +14,11 @@ use std::time::Duration; use tokio::sync::{RwLock, mpsc}; use tokio::time::interval; -pub async fn run_client() { +pub async fn run_client() -> anyhow::Result<()> { let args = Args::parse(); if let Err(e) = utils::init_tracing() { - eprintln!("Failed to initialize logging: {}", e); - return; + anyhow::bail!("Failed to initialize logging: {}", e); } log_startup_banner(&args); @@ -28,8 +27,7 @@ pub async fn run_client() { let crypto_config = match crypto::parse_crypto_config(&args.crypto) { Ok(cfg) => cfg, Err(e) => { - tracing::error!("Invalid crypto configuration: {}", e); - return; + anyhow::bail!("Invalid crypto configuration: {}", e); } }; let block = crypto::new_block(&crypto_config); @@ -50,8 +48,7 @@ pub async fn run_client() { match new_relay_handler(&args, crypto_block.clone(), ipv6, P2P_UDP_PORT, stun).await { Ok(result) => result, Err(e) => { - tracing::error!("Failed to setup client: {}", e); - return; + anyhow::bail!("Failed to setup client: {}", e); } }; @@ -77,9 +74,7 @@ pub async fn run_client() { if args.masq && let Err(e) = crate::utils::sys_route::SysRoute::check_iptables_available() { - eprintln!("❌ Error: {}", e); - eprintln!("\nPlease install iptables or run without --masq option."); - std::process::exit(1); + anyhow::bail!("❌ Error: {e}\nPlease install iptables or run without --masq option."); } } @@ -92,8 +87,7 @@ pub async fn run_client() { let mut dev = match init_device(&device_config, enable_masq).await { Ok(d) => d, Err(e) => { - tracing::error!("Failed to initialize device: {}", e); - return; + anyhow::bail!("Failed to initialize device: {}", e); } }; @@ -108,12 +102,13 @@ pub async fn run_client() { // Run main event loop run_event_loop(&mut relay_handler, p2p_handler, &mut dev).await; + Ok(()) } async fn init_device( device_config: &HandshakeReplyFrame, enable_masq: bool, -) -> crate::Result { +) -> anyhow::Result { tracing::info!("Initializing device with config: {:?}", device_config); let mut dev = DeviceHandler::new(); let tun_index = dev.run(device_config, enable_masq).await?; diff --git a/src/client/p2p/peer.rs b/src/client/p2p/peer.rs index 7cb9003..3b0835e 100644 --- a/src/client/p2p/peer.rs +++ b/src/client/p2p/peer.rs @@ -297,17 +297,17 @@ impl PeerHandler { /// **ProbeIPv6** /// - update last_active, this is for p2p send_frame healthy checker /// - remote address, most of the time this is not changed. - pub async fn recv_frame(&mut self) -> crate::Result { + pub async fn recv_frame(&mut self) -> anyhow::Result { let inbound_rx = self .inbound_rx .as_mut() - .ok_or("inbound_rx not initialized")?; + .ok_or(anyhow::anyhow!("inbound_rx not initialized"))?; loop { let (buf, remote) = inbound_rx .recv() .await - .ok_or("recv from peers channel closed")?; + .ok_or(anyhow::anyhow!("recv from peers channel closed"))?; let (frame, _) = Parser::unmarshal(&buf, self.block.as_ref().as_ref())?; @@ -351,18 +351,17 @@ impl PeerHandler { /// /// secondary try p2p hole punch, if peers is healthy(base on stun_last_active) /// - pub async fn send_frame(&self, frame: Frame, dest_ip: &str) -> crate::Result<()> { + pub async fn send_frame(&self, frame: Frame, dest_ip: &str) -> anyhow::Result<()> { let peers = self.peers.read().await; let peer = self .find_peer_by_ip_locked(&peers, dest_ip) - .ok_or("No peer found for destination")?; + .ok_or_else(|| anyhow::anyhow!("No peer found for destination"))?; if peer.remote_addr.is_none() && peer.stun_addr.is_none() { - return Err(format!( + return Err(anyhow::anyhow!( "Peer {} has no available address (IPv6 or STUN)", peer.identity - ) - .into()); + )); } let peer_identity = peer.identity.clone(); @@ -378,7 +377,7 @@ impl PeerHandler { let outbound_tx = self .outbound_tx .as_ref() - .ok_or("outbound_tx not initialized")?; + .ok_or(anyhow::anyhow!("outbound_tx not initialized"))?; // Attempt 1: Try IPv6 direct connection match self @@ -421,22 +420,19 @@ impl PeerHandler { .await { SendResult::Success => Ok(()), - SendResult::Expired(elapsed) => Err(format!( + SendResult::Expired(elapsed) => Err(anyhow::anyhow!( "Peer {} STUN connection also expired ({:?} ago)", - peer_identity, elapsed - ) - .into()), - SendResult::NeverResponded => { - Err(format!("Peer {} STUN address never responded", peer_identity).into()) - } - SendResult::NoAddress => { - // Both attempts failed - Err(format!( - "Failed to send to peer {}: IPv6 unavailable/expired, STUN unavailable/expired", - peer_identity - ) - .into()) - } + peer_identity, + elapsed + )), + SendResult::NeverResponded => Err(anyhow::anyhow!( + "Peer {} STUN address never responded", + peer_identity + )), + SendResult::NoAddress => Err(anyhow::anyhow!( + "Failed to send to peer {}: IPv6 unavailable/expired, STUN unavailable/expired", + peer_identity + )), } } diff --git a/src/client/p2p/udp_server.rs b/src/client/p2p/udp_server.rs index 9990a05..27a0270 100644 --- a/src/client/p2p/udp_server.rs +++ b/src/client/p2p/udp_server.rs @@ -112,7 +112,7 @@ impl UDPServer { /// # Note /// /// This method never returns under normal operation. It only exits on error. - pub async fn serve(&mut self) -> crate::Result<()> { + pub async fn serve(&mut self) -> anyhow::Result<()> { // Bind IPv6 socket for direct connections // [::] means all IPv6 interfaces (equivalent to 0.0.0.0 for IPv4) let socket_ipv6 = UdpSocket::bind(format!("[::]:{}", self.listen_port)).await?; @@ -221,7 +221,7 @@ impl UDPServer { result: std::io::Result<(usize, SocketAddr)>, buffer: &mut [u8], protocol: &str, - ) -> crate::Result<()> { + ) -> anyhow::Result<()> { match result { Ok((len, remote)) => { // Copy only the received bytes (not the entire buffer) diff --git a/src/client/relay.rs b/src/client/relay.rs index 8d68a29..55bc6a0 100644 --- a/src/client/relay.rs +++ b/src/client/relay.rs @@ -47,7 +47,7 @@ impl RelayClient { } } - pub async fn run(&mut self, mut conn: Box) -> crate::Result<()> { + pub async fn run(&mut self, mut conn: Box) -> anyhow::Result<()> { let mut keepalive_ticker = interval(self.cfg.keepalive_interval); let mut keepalive_wait: u8 = 0; @@ -162,7 +162,7 @@ impl RelayClient { Ok(()) } - async fn connect(&self) -> crate::Result> { + async fn connect(&self) -> anyhow::Result> { let conn = create_connection( ConnectionConfig::TCP(TCPConnectionConfig { server_addr: self.cfg.server_addr.clone(), @@ -179,7 +179,7 @@ impl RelayClient { async fn handshake( &self, conn: &mut Box, - ) -> crate::Result { + ) -> anyhow::Result { conn.write_frame(Frame::Handshake(HandshakeFrame { identity: self.cfg.identity.clone(), })) @@ -190,7 +190,7 @@ impl RelayClient { return Ok(frame); } - Err("invalid frame".into()) + Err(anyhow::anyhow!("invalid frame")) } } @@ -314,19 +314,19 @@ impl RelayHandler { self.outbound_tx.clone() } - pub async fn send_frame(outbound_tx: mpsc::Sender, frame: Frame) -> crate::Result<()> { + pub async fn send_frame(outbound_tx: mpsc::Sender, frame: Frame) -> anyhow::Result<()> { // self.metrics.tx_frame += 1; let result = outbound_tx.send(frame).await; match result { Ok(()) => Ok(()), Err(e) => { // self.metrics.tx_error += 1; - Err(format!("device=> server fail {:?}", e).into()) + Err(anyhow::anyhow!("device=> server fail {:?}", e)) } } } - pub async fn recv_frame(&mut self) -> crate::Result { + pub async fn recv_frame(&mut self) -> anyhow::Result { let result = self.inbound_rx.recv().await; match result { Some(frame) => { @@ -335,7 +335,7 @@ impl RelayHandler { } None => { self.metrics.rx_error += 1; - Err("server => device fail for closed channel".into()) + Err(anyhow::anyhow!("server => device fail for closed channel")) } } } @@ -351,7 +351,7 @@ pub async fn new_relay_handler( ipv6: String, port: u16, stun: Option, -) -> crate::Result<(RelayHandler, HandshakeReplyFrame)> { +) -> anyhow::Result<(RelayHandler, HandshakeReplyFrame)> { let client_config = RelayClientConfig { server_addr: args.server.clone(), keepalive_interval: Duration::from_secs(args.keepalive_interval), @@ -370,7 +370,7 @@ pub async fn new_relay_handler( let device_config = config_ready_rx .recv() .await - .ok_or("Failed to receive device config from server")?; + .ok_or_else(|| anyhow::anyhow!("Failed to receive device config from server"))?; log_handshake_success(&device_config); diff --git a/src/cmd/client.rs b/src/cmd/client.rs deleted file mode 100644 index db95542..0000000 --- a/src/cmd/client.rs +++ /dev/null @@ -1,7 +0,0 @@ -use rustun::client::main; - -#[tokio::main] -async fn main() { - let err = main::run_client().await; - panic!("{:?}", err); -} diff --git a/src/cmd/server.rs b/src/cmd/server.rs deleted file mode 100644 index 2b05c45..0000000 --- a/src/cmd/server.rs +++ /dev/null @@ -1,6 +0,0 @@ -use rustun::server::main; - -#[tokio::main] -async fn main() { - main::run_server().await; -} diff --git a/src/codec/errors.rs b/src/codec/errors.rs index 2048c6f..0af8a1c 100644 --- a/src/codec/errors.rs +++ b/src/codec/errors.rs @@ -42,7 +42,7 @@ pub(crate) enum FrameError { /// - Data was tampered with during transmission /// - Wrong encryption key is being used /// - Payload is too short for the cipher's requirements - DecryptionFailed(crate::Error), + DecryptionFailed(anyhow::Error), } impl std::error::Error for FrameError {} diff --git a/src/codec/parser.rs b/src/codec/parser.rs index 4571c0b..744eb76 100644 --- a/src/codec/parser.rs +++ b/src/codec/parser.rs @@ -31,7 +31,7 @@ impl Parser { /// # Returns /// * `Ok((Frame, usize))` - Parsed frame and total bytes consumed /// * `Err` - If frame is invalid, too short, or decryption/parsing fails - pub fn unmarshal(buf: &[u8], block: &dyn Block) -> crate::Result<(Frame, usize)> { + pub fn unmarshal(buf: &[u8], block: &dyn Block) -> anyhow::Result<(Frame, usize)> { if buf.len() < HDR_LEN { return Err(FrameError::TooShort.into()); } @@ -123,7 +123,7 @@ impl Parser { fn decrypt_and_deserialize( payload: &mut Vec, block: &dyn Block, - ) -> crate::Result { + ) -> anyhow::Result { block .decrypt(payload) .map_err(FrameError::DecryptionFailed)?; @@ -146,7 +146,7 @@ impl Parser { data: &T, block: &dyn Block, context_msg: &str, - ) -> crate::Result> { + ) -> anyhow::Result> { let msg = context_msg.to_string(); let json = serde_json::to_string(data).with_context(|| msg)?; let mut payload = json.as_bytes().to_vec(); @@ -185,7 +185,7 @@ impl Parser { /// # Returns /// * `Ok(Vec)` - Complete frame bytes (header + encrypted payload) /// * `Err` - If serialization or encryption fails - pub fn marshal(frame: Frame, block: &dyn Block) -> crate::Result> { + pub fn marshal(frame: Frame, block: &dyn Block) -> anyhow::Result> { match frame { Frame::Handshake(hs) => { let payload = diff --git a/src/crypto/aes256.rs b/src/crypto/aes256.rs index 7989090..044d787 100644 --- a/src/crypto/aes256.rs +++ b/src/crypto/aes256.rs @@ -74,14 +74,14 @@ impl Block for Aes256Block { /// # Returns /// * `Ok(())` on success /// * `Err` if encryption fails - fn encrypt(&self, data: &mut Vec) -> crate::Result<()> { + fn encrypt(&self, data: &mut Vec) -> anyhow::Result<()> { let nonce_bytes = Self::generate_nonce(); let nonce = Nonce::from_slice(&nonce_bytes); let ciphertext = self .cipher .encrypt(nonce, data.as_ref()) - .map_err(|e| format!("AES-256-GCM encryption failed: {}", e))?; + .map_err(|e| anyhow::anyhow!("AES-256-GCM encryption failed: {}", e))?; // Replace data with: nonce || ciphertext (ciphertext already includes auth tag) data.clear(); @@ -102,10 +102,10 @@ impl Block for Aes256Block { /// # Returns /// * `Ok(())` on success /// * `Err` if data is too short, decryption fails, or authentication fails - fn decrypt(&self, data: &mut Vec) -> crate::Result<()> { + fn decrypt(&self, data: &mut Vec) -> anyhow::Result<()> { // Minimum length: 12 (nonce) + 16 (tag) = 28 bytes if data.len() < 28 { - return Err("Data too short for AES-256-GCM decryption".into()); + return Err(anyhow::anyhow!("Data too short for AES-256-GCM decryption")); } let nonce = Nonce::from_slice(&data[0..12]); @@ -114,7 +114,7 @@ impl Block for Aes256Block { let plaintext = self .cipher .decrypt(nonce, ciphertext) - .map_err(|e| format!("AES-256-GCM decryption failed: {}", e))?; + .map_err(|e| anyhow::anyhow!("AES-256-GCM decryption failed: {}", e))?; // Replace data with plaintext data.clear(); diff --git a/src/crypto/chacha20.rs b/src/crypto/chacha20.rs index 1916cbc..e3cdb11 100644 --- a/src/crypto/chacha20.rs +++ b/src/crypto/chacha20.rs @@ -73,14 +73,14 @@ impl Block for ChaCha20Poly1305Block { /// # Returns /// * `Ok(())` on success /// * `Err` if encryption fails - fn encrypt(&self, data: &mut Vec) -> crate::Result<()> { + fn encrypt(&self, data: &mut Vec) -> anyhow::Result<()> { let nonce_bytes = Self::generate_nonce(); let nonce = Nonce::from_slice(&nonce_bytes); let ciphertext = self .cipher .encrypt(nonce, data.as_ref()) - .map_err(|e| format!("ChaCha20-Poly1305 encryption failed: {}", e))?; + .map_err(|e| anyhow::anyhow!("ChaCha20-Poly1305 encryption failed: {}", e))?; // Replace data with: nonce || ciphertext (ciphertext already includes auth tag) data.clear(); @@ -101,10 +101,12 @@ impl Block for ChaCha20Poly1305Block { /// # Returns /// * `Ok(())` on success /// * `Err` if data is too short, decryption fails, or authentication fails - fn decrypt(&self, data: &mut Vec) -> crate::Result<()> { + fn decrypt(&self, data: &mut Vec) -> anyhow::Result<()> { // Minimum length: 12 (nonce) + 16 (tag) = 28 bytes if data.len() < 28 { - return Err("Data too short for ChaCha20-Poly1305 decryption".into()); + return Err(anyhow::anyhow!( + "Data too short for ChaCha20-Poly1305 decryption" + )); } let nonce = Nonce::from_slice(&data[0..12]); @@ -113,7 +115,7 @@ impl Block for ChaCha20Poly1305Block { let plaintext = self .cipher .decrypt(nonce, ciphertext) - .map_err(|e| format!("ChaCha20-Poly1305 decryption failed: {}", e))?; + .map_err(|e| anyhow::anyhow!("ChaCha20-Poly1305 decryption failed: {}", e))?; // Replace data with plaintext data.clear(); diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index e040cb2..18b4c00 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -31,7 +31,7 @@ pub trait Block: Send + Sync { /// # Returns /// * `Ok(())` on success /// * `Err` if encryption fails - fn encrypt(&self, data: &mut Vec) -> crate::Result<()>; + fn encrypt(&self, data: &mut Vec) -> anyhow::Result<()>; /// Decrypts data in-place /// @@ -41,7 +41,7 @@ pub trait Block: Send + Sync { /// # Returns /// * `Ok(())` on success /// * `Err` if decryption fails - fn decrypt(&self, data: &mut Vec) -> crate::Result<()>; + fn decrypt(&self, data: &mut Vec) -> anyhow::Result<()>; } /// Factory function to create cipher blocks from configuration diff --git a/src/crypto/plain.rs b/src/crypto/plain.rs index 1b579fe..3052813 100644 --- a/src/crypto/plain.rs +++ b/src/crypto/plain.rs @@ -38,7 +38,7 @@ impl Block for PlainBlock { /// /// # Returns /// * Always returns `Ok(())` - fn encrypt(&self, _data: &mut Vec) -> crate::Result<()> { + fn encrypt(&self, _data: &mut Vec) -> anyhow::Result<()> { // No encryption performed Ok(()) } @@ -50,7 +50,7 @@ impl Block for PlainBlock { /// /// # Returns /// * Always returns `Ok(())` - fn decrypt(&self, _data: &mut Vec) -> crate::Result<()> { + fn decrypt(&self, _data: &mut Vec) -> anyhow::Result<()> { // No decryption performed Ok(()) } diff --git a/src/crypto/xor.rs b/src/crypto/xor.rs index 6a3f805..8066693 100644 --- a/src/crypto/xor.rs +++ b/src/crypto/xor.rs @@ -84,7 +84,7 @@ impl Block for XorBlock { /// /// # Returns /// * Always returns `Ok(())` - fn encrypt(&self, data: &mut Vec) -> crate::Result<()> { + fn encrypt(&self, data: &mut Vec) -> anyhow::Result<()> { self.xor_data(data); Ok(()) } @@ -99,7 +99,7 @@ impl Block for XorBlock { /// /// # Returns /// * Always returns `Ok(())` - fn decrypt(&self, data: &mut Vec) -> crate::Result<()> { + fn decrypt(&self, data: &mut Vec) -> anyhow::Result<()> { // XOR encryption is symmetric: decrypt is the same as encrypt self.xor_data(data); Ok(()) diff --git a/src/lib.rs b/src/lib.rs index fab42ba..57db330 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,3 @@ pub mod crypto; pub mod network; pub mod server; pub mod utils; - -pub type Error = Box; -pub type Result = std::result::Result; diff --git a/src/network/mod.rs b/src/network/mod.rs index c15bce0..5a2d931 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -24,12 +24,12 @@ const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(10); #[async_trait] pub trait ConnRead: Send + Sync { - async fn read_frame(&mut self) -> crate::Result; + async fn read_frame(&mut self) -> anyhow::Result; } #[async_trait] pub trait ConnWrite: Send + Sync { - async fn write_frame(&mut self, frame: Frame) -> crate::Result<()>; + async fn write_frame(&mut self, frame: Frame) -> anyhow::Result<()>; async fn close(&mut self); } @@ -54,7 +54,7 @@ pub trait Listener: Send + Sync { /// # Returns /// - `Ok(())` - Listener closed gracefully /// - `Err` - Failed to bind or accept connections - async fn listen_and_serve(&mut self) -> crate::Result<()>; + async fn listen_and_serve(&mut self) -> anyhow::Result<()>; /// Subscribe to new connections /// @@ -64,7 +64,7 @@ pub trait Listener: Send + Sync { /// # Returns /// - `Ok(Receiver)` - Channel for receiving new connections /// - `Err` - Failed to create subscription channel - async fn subscribe_on_conn(&mut self) -> crate::Result>>; + async fn subscribe_on_conn(&mut self) -> anyhow::Result>>; /// Close the listener /// @@ -73,7 +73,7 @@ pub trait Listener: Send + Sync { /// # Returns /// - `Ok(())` - Listener closed successfully /// - `Err` - Error during shutdown - async fn close(&mut self) -> crate::Result<()>; + async fn close(&mut self) -> anyhow::Result<()>; } /// Metadata for a client connection @@ -177,7 +177,7 @@ pub enum ListenerConfig { pub fn create_listener( config: ListenerConfig, block: Arc>, -) -> crate::Result> { +) -> anyhow::Result> { match config { TCP(config) => Ok(Box::new(TCPListener::new(config.listen_addr, block))), } @@ -194,7 +194,7 @@ pub enum ConnectionConfig { pub async fn create_connection( config: ConnectionConfig, block: Arc>, -) -> crate::Result> { +) -> anyhow::Result> { match config { ConnectionConfig::TCP(config) => { // Connect with timeout @@ -210,7 +210,7 @@ pub async fn create_connection( Ok(Box::new(conn)) } Ok(Err(e)) => Err(e.into()), - Err(_) => Err("connection timeout".into()), + Err(_) => Err(anyhow::anyhow!("connection timeout")), } } } diff --git a/src/network/tcp_connection.rs b/src/network/tcp_connection.rs index 4cf8052..8599b31 100644 --- a/src/network/tcp_connection.rs +++ b/src/network/tcp_connection.rs @@ -101,7 +101,7 @@ impl TcpConnection { /// - `Ok(Some(Frame))` - Successfully parsed frame /// - `Ok(None)` - Incomplete data, need more bytes /// - `Err` - Parse error (invalid frame format) - fn parse_frame(&mut self) -> crate::Result> { + fn parse_frame(&mut self) -> anyhow::Result> { let result = Parser::unmarshal(self.input_stream.as_ref(), self.block.as_ref().as_ref()); match result { Ok((frame, total_len)) => { @@ -115,18 +115,19 @@ impl TcpConnection { #[async_trait] impl ConnRead for TcpConnection { - async fn read_frame(&mut self) -> crate::Result { + async fn read_frame(&mut self) -> anyhow::Result { let deadline = Instant::now() + self.read_timeout; loop { if Instant::now() > deadline { - return Err("read timeout".into()); + return Err(anyhow::anyhow!("read timeout")); } if let Ok(frame) = self.parse_frame() - && let Some(frame) = frame { - return Ok(frame); - } + && let Some(frame) = frame + { + return Ok(frame); + } let remaining = deadline.saturating_duration_since(Instant::now()); @@ -136,16 +137,16 @@ impl ConnRead for TcpConnection { match read_result { Ok(Ok(0)) => { return if self.input_stream.is_empty() { - Err("EOF".into()) + Err(anyhow::anyhow!("EOF")) } else { - Err("connection reset by peer".into()) + Err(anyhow::anyhow!("connection reset by peer")) }; } Ok(Ok(n)) => { tracing::debug!("read {} bytes", n) } Ok(Err(e)) => return Err(e.into()), - Err(_) => return Err("read timeout".into()), + Err(_) => return Err(anyhow::anyhow!("read timeout")), } } } @@ -153,7 +154,7 @@ impl ConnRead for TcpConnection { #[async_trait] impl ConnWrite for TcpConnection { - async fn write_frame(&mut self, frame: Frame) -> crate::Result<()> { + async fn write_frame(&mut self, frame: Frame) -> anyhow::Result<()> { let result = Parser::marshal(frame, self.block.as_ref().as_ref()); let buf = match result { Ok(buf) => buf, @@ -172,7 +173,7 @@ impl ConnWrite for TcpConnection { match write_result { Ok(Ok(())) => Ok(()), Ok(Err(e)) => Err(e.into()), - Err(_) => Err("write timeout".into()), + Err(_) => Err(anyhow::anyhow!("write timeout")), } } diff --git a/src/network/tcp_listener.rs b/src/network/tcp_listener.rs index 95b6ef8..ee486cc 100644 --- a/src/network/tcp_listener.rs +++ b/src/network/tcp_listener.rs @@ -50,7 +50,7 @@ impl TCPListener { /// # Returns /// - `Ok(TcpStream)` - Accepted connection /// - `Err` - Fatal accept error or retries exhausted - async fn accept(&mut self) -> crate::Result { + async fn accept(&mut self) -> anyhow::Result { let listener = self.listener.as_ref().ok_or_else(|| { std::io::Error::new(ErrorKind::NotConnected, "listener not initialized") })?; @@ -91,7 +91,7 @@ impl Listener for TCPListener { /// /// Runs in a loop, accepting connections and sending them to subscribers /// via the channel. Continues accepting even if sending fails. - async fn listen_and_serve(&mut self) -> crate::Result<()> { + async fn listen_and_serve(&mut self) -> anyhow::Result<()> { let listener = TcpListener::bind(self.addr.clone()).await?; tracing::info!("Server listening on {}", self.addr); self.listener = Some(listener); @@ -121,14 +121,14 @@ impl Listener for TCPListener { /// /// # Returns /// - `Ok(Receiver)` - Channel receiver for new connections - async fn subscribe_on_conn(&mut self) -> crate::Result>> { + async fn subscribe_on_conn(&mut self) -> anyhow::Result>> { let (tx, rx) = mpsc::channel::>(DEFAULT_ON_CONNECTION_QUEUE); self.on_conn_tx = Some(tx); Ok(rx) } /// Close the listener and clean up resources - async fn close(&mut self) -> crate::Result<()> { + async fn close(&mut self) -> anyhow::Result<()> { if let Some(listener) = self.listener.take() { drop(listener); tracing::info!("TCP listener closed"); diff --git a/src/server/conf_agent.rs b/src/server/conf_agent.rs index 6374b28..960069d 100644 --- a/src/server/conf_agent.rs +++ b/src/server/conf_agent.rs @@ -53,7 +53,7 @@ impl ConfAgent { } /// Start the conf-agent service - pub async fn start(self: Arc) -> crate::Result<()> { + pub async fn start(self: Arc) -> anyhow::Result<()> { tracing::info!("Starting conf-agent"); tracing::info!("Control plane URL: {}", self.config.control_plane_url); tracing::info!("Routes file: {}", self.config.routes_file); @@ -88,7 +88,7 @@ impl ConfAgent { } /// Report connections from connection manager - async fn report_connections(&self) -> crate::Result<()> { + async fn report_connections(&self) -> anyhow::Result<()> { // Get connections from connection manager let connections = self.connection_manager.dump_connection_info(); @@ -132,7 +132,7 @@ impl ConfAgent { } /// Fetch routes from control plane and update local routes file - async fn fetch_and_update_routes(&self) -> crate::Result<()> { + async fn fetch_and_update_routes(&self) -> anyhow::Result<()> { tracing::debug!("Fetching routes from control plane..."); let url = format!("{}/api/sync/clients", self.config.control_plane_url); @@ -155,7 +155,7 @@ impl ConfAgent { async fn fetch_routes( url: &str, token: Option<&str>, - ) -> crate::Result> { + ) -> anyhow::Result> { let mut request = ureq::get(url).timeout(Duration::from_secs(30)); if let Some(token) = token { @@ -168,7 +168,7 @@ impl ConfAgent { let body = response.into_string()?; if status != 200 { - return Err(format!("Control plane returned error: {} - {}", status, body).into()); + return Err(anyhow::anyhow!("Control plane returned error: {} - {}", status, body)); } let routes: Vec = serde_json::from_str(&body)?; @@ -192,7 +192,7 @@ impl ConfAgent { } /// Write routes to file atomically - async fn write_routes(file_path: &str, routes: &[ClientConfig]) -> crate::Result<()> { + async fn write_routes(file_path: &str, routes: &[ClientConfig]) -> anyhow::Result<()> { // Ensure parent directory exists if let Some(parent) = std::path::Path::new(file_path).parent() { fs::create_dir_all(parent).await?; @@ -215,7 +215,7 @@ impl ConfAgent { url: &str, token: Option<&str>, updates: &[ConnectionUpdateRequest], - ) -> crate::Result<()> { + ) -> anyhow::Result<()> { let json_data = serde_json::to_string(updates)?; let mut request = ureq::post(url) @@ -231,7 +231,7 @@ impl ConfAgent { let body = response.into_string()?; if status != 200 { - return Err(format!("Backend returned error: {} - {}", status, body).into()); + return Err(anyhow::anyhow!("Backend returned error: {} - {}", status, body)); } Ok(()) diff --git a/src/server/config.rs b/src/server/config.rs index 6ffa991..07433a1 100644 --- a/src/server/config.rs +++ b/src/server/config.rs @@ -47,13 +47,13 @@ pub struct RouteConfig { pub routes_file: String, } -pub fn load_main(path: &str) -> crate::Result { +pub fn load_main(path: &str) -> anyhow::Result { let content = fs::read_to_string(path)?; let config: Config = toml::from_str(&content)?; Ok(config) } -pub fn load_routes(path: &str) -> crate::Result> { +pub fn load_routes(path: &str) -> anyhow::Result> { let content = fs::read_to_string(path)?; let clients: Vec = serde_json::from_str(&content)?; Ok(clients) diff --git a/src/server/handler.rs b/src/server/handler.rs index 07eece8..b6af2c3 100644 --- a/src/server/handler.rs +++ b/src/server/handler.rs @@ -46,7 +46,7 @@ impl Server { } impl Server { - pub async fn run(&mut self) -> crate::Result<()> { + pub async fn run(&mut self) -> anyhow::Result<()> { // only for tcp now, may support multi listener type let listener_config = ListenerConfig::TCP(TCPListenerConfig { listen_addr: self.server_config.listen_addr.clone(), @@ -79,7 +79,7 @@ impl Server { } } - fn handle_conn(&self, mut conn: Box) -> crate::Result<()> { + fn handle_conn(&self, mut conn: Box) -> anyhow::Result<()> { let peer_addr = conn.peer_addr().unwrap(); tracing::debug!("new connection from {}", conn.peer_addr().unwrap()); @@ -122,7 +122,7 @@ impl Handler { } } - pub async fn run(&mut self) -> crate::Result<()> { + pub async fn run(&mut self) -> anyhow::Result<()> { // handshake let hs = match self.handle_handshake().await { Ok(hs) => hs, @@ -206,7 +206,7 @@ impl Handler { Ok(()) } - async fn handle_handshake(&mut self) -> crate::Result { + async fn handle_handshake(&mut self) -> anyhow::Result { let frame = self.conn.read_frame().await; match frame { Ok(frame) => { @@ -214,7 +214,7 @@ impl Handler { if let Frame::Handshake(handshake) = frame { Ok(handshake) } else { - Err("unexpected frame type when handshaking".into()) + Err(anyhow::anyhow!("unexpected frame type when handshaking")) } } Err(e) => Err(e), diff --git a/src/server/main.rs b/src/server/main.rs index 9119a6e..797e26b 100644 --- a/src/server/main.rs +++ b/src/server/main.rs @@ -7,13 +7,12 @@ use crate::server::handler::Server; use crate::{crypto, utils}; use std::sync::Arc; -pub async fn run_server() { +pub async fn run_server() -> anyhow::Result<()> { let args = std::env::args().collect::>(); let cfg = config::load_main(args.get(1).unwrap_or(&"server.toml".to_string())).unwrap(); if let Err(e) = utils::init_tracing() { - eprintln!("Failed to initialize logging: {}", e); - return; + anyhow::bail!("Failed to initialize logging: {}", e); } let routes_file = cfg.route_config.routes_file.clone(); @@ -57,6 +56,7 @@ pub async fn run_server() { Arc::new(block), ); if let Err(e) = server.run().await { - tracing::error!("Server error: {}", e); + anyhow::bail!("Server error: {}", e); } + Ok(()) } diff --git a/src/utils/device.rs b/src/utils/device.rs index c04c3e3..d75688e 100644 --- a/src/utils/device.rs +++ b/src/utils/device.rs @@ -43,7 +43,7 @@ impl Device { } } - pub async fn run(&mut self, ready: oneshot::Sender>, name: oneshot::Sender>) -> crate::Result<()> { + pub async fn run(&mut self, ready: oneshot::Sender>, name: oneshot::Sender>) -> anyhow::Result<()> { let mut config = tun::Configuration::default(); config .address(self.ip.clone()) @@ -140,7 +140,7 @@ impl DeviceHandler { } } - pub async fn run(&mut self, cfg: &HandshakeReplyFrame, enable_masq: bool) -> crate::Result> { + pub async fn run(&mut self, cfg: &HandshakeReplyFrame, enable_masq: bool) -> anyhow::Result> { let (inbound_tx, inbound_rx) = mpsc::channel(1000); let (outbound_tx, outbound_rx) = mpsc::channel(1000); self.inbound_rx = Some(inbound_rx); @@ -203,11 +203,11 @@ impl DeviceHandler { result } - pub async fn send(&mut self, packet: Vec) -> crate::Result<()> { + pub async fn send(&mut self, packet: Vec) -> anyhow::Result<()> { let outbound_tx = match self.outbound_tx.as_ref() { Some(tx) => tx, None => { - return Err("device handler send none".into()); + return Err(anyhow::anyhow!("device handler send none")); } }; self.tx_bytes+=packet.len(); @@ -273,7 +273,7 @@ impl DeviceHandler { /// Enable MASQUERADE (NAT) for VPN interface (Linux only) /// Uses source network address instead of interface name for better reliability - pub fn enable_masquerade(&mut self) -> crate::Result<()> { + pub fn enable_masquerade(&mut self) -> anyhow::Result<()> { let cidr = self.ip_mask_to_cidr(&self.private_ip, &self.mask)?; let sys_route = SysRoute::new(); @@ -282,7 +282,7 @@ impl DeviceHandler { } /// Disable MASQUERADE (NAT) for VPN interface (Linux only) - pub fn disable_masquerade(&mut self) -> crate::Result<()> { + pub fn disable_masquerade(&mut self) -> anyhow::Result<()> { let cidr = self.ip_mask_to_cidr(&self.private_ip, &self.mask)?; let sys_route = SysRoute::new(); @@ -291,7 +291,7 @@ impl DeviceHandler { } /// Convert IP address and subnet mask to CIDR notation - fn ip_mask_to_cidr(&self, ip: &str, mask: &str) -> crate::Result { + fn ip_mask_to_cidr(&self, ip: &str, mask: &str) -> anyhow::Result { // Parse subnet mask to prefix length let prefix_len = mask_to_prefix_length(mask)?; let network = ip_to_network(ip, mask)?; @@ -300,7 +300,7 @@ impl DeviceHandler { /// Enable SNAT for local network segments to use virtual IP (Linux only) /// This makes packets from local ciders appear as coming from virtual IP - pub fn enable_snat(&mut self) -> crate::Result<()> { + pub fn enable_snat(&mut self) -> anyhow::Result<()> { let sys_route = SysRoute::new(); for cidr in &self.local_ciders { @@ -311,7 +311,7 @@ impl DeviceHandler { } /// Disable SNAT for local network segments (Linux only) - pub fn disable_snat(&mut self) -> crate::Result<()> { + pub fn disable_snat(&mut self) -> anyhow::Result<()> { let sys_route = SysRoute::new(); for cidr in &self.local_ciders { @@ -324,7 +324,7 @@ impl DeviceHandler { /// This should be called after receiving HandshakeReplyFrame /// Maps destination IPs from mapped CIDR to real CIDR using iptables NETMAP #[cfg(target_os = "linux")] - pub fn setup_cidr_mapping(&mut self, cidr_mapping: &HashMap) -> crate::Result<()> { + pub fn setup_cidr_mapping(&mut self, cidr_mapping: &HashMap) -> anyhow::Result<()> { let sys_route = SysRoute::new(); for (mapped_cidr, real_cidr) in cidr_mapping { @@ -344,7 +344,7 @@ impl DeviceHandler { } #[cfg(not(target_os = "linux"))] - pub fn setup_cidr_mapping(&mut self, _cidr_mapping: &HashMap) -> crate::Result<()> { + pub fn setup_cidr_mapping(&mut self, _cidr_mapping: &HashMap) -> anyhow::Result<()> { Ok(()) } } diff --git a/src/utils/sys_route.rs b/src/utils/sys_route.rs index 580ff7a..9c3c232 100644 --- a/src/utils/sys_route.rs +++ b/src/utils/sys_route.rs @@ -1,35 +1,38 @@ -use std::process::Command; -use std::net::Ipv4Addr; use ipnet::Ipv4Net; +use std::net::Ipv4Addr; +use std::process::Command; pub struct SysRoute; /// Convert subnet mask to prefix length /// Example: "255.255.255.0" -> 24 -pub(crate) fn mask_to_prefix_length(mask: &str) -> crate::Result { - let mask_addr: Ipv4Addr = mask.parse() - .map_err(|_| format!("Invalid subnet mask format: {}", mask))?; - +pub(crate) fn mask_to_prefix_length(mask: &str) -> anyhow::Result { + let mask_addr: Ipv4Addr = mask + .parse() + .map_err(|_| anyhow::anyhow!("Invalid subnet mask format: {}", mask))?; + // Use ipnet library to convert mask to prefix length // We use UNSPECIFIED (0.0.0.0) as the base IP since we only care about the mask let net = Ipv4Net::with_netmask(Ipv4Addr::UNSPECIFIED, mask_addr) - .map_err(|e| format!("Invalid subnet mask {}: {}", mask, e))?; - + .map_err(|e| anyhow::anyhow!("Invalid subnet mask {}: {}", mask, e))?; + Ok(net.prefix_len()) } /// Convert IP address and subnet mask to network address /// Example: ("10.0.0.1", "255.255.255.0") -> "10.0.0.0" -pub(crate) fn ip_to_network(ip: &str, mask: &str) -> crate::Result { - let ip_addr: Ipv4Addr = ip.parse() - .map_err(|_| format!("Invalid IP address: {}", ip))?; - let mask_addr: Ipv4Addr = mask.parse() - .map_err(|_| format!("Invalid subnet mask: {}", mask))?; - +pub(crate) fn ip_to_network(ip: &str, mask: &str) -> anyhow::Result { + let ip_addr: Ipv4Addr = ip + .parse() + .map_err(|_| anyhow::anyhow!("Invalid IP address: {}", ip))?; + let mask_addr: Ipv4Addr = mask + .parse() + .map_err(|_| anyhow::anyhow!("Invalid subnet mask: {}", mask))?; + // Use ipnet library to get network address let net = Ipv4Net::with_netmask(ip_addr, mask_addr) - .map_err(|e| format!("Invalid IP/mask combination ({} / {}): {}", ip, mask, e))?; - + .map_err(|e| anyhow::anyhow!("Invalid IP/mask combination ({} / {}): {}", ip, mask, e))?; + Ok(net.network().to_string()) } @@ -41,25 +44,23 @@ impl SysRoute { /// Check if iptables command is available (Linux only) /// This should be called before enabling MASQUERADE/SNAT features #[cfg(target_os = "linux")] - pub fn check_iptables_available() -> crate::Result<()> { - let output = Command::new("iptables") - .args(["--version"]) - .output(); - + pub fn check_iptables_available() -> anyhow::Result<()> { + let output = Command::new("iptables").args(["--version"]).output(); + match output { Ok(_) => Ok(()), - Err(e) if e.kind() == std::io::ErrorKind::NotFound => { - Err("iptables command not found. The --masq option requires iptables.\n\ + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err(anyhow::anyhow!( + "iptables command not found. The --masq option requires iptables.\n\ Please either:\n\ 1. Install iptables: sudo apt-get install iptables (Debian/Ubuntu) or sudo yum install iptables (RHEL/CentOS)\n\ - 2. Run without --masq option".to_string().into()) - } - Err(e) => Err(format!("Failed to check iptables: {}", e).into()), + 2. Run without --masq option" + )), + Err(e) => Err(anyhow::anyhow!("Failed to check iptables: {}", e)), } } #[cfg(not(target_os = "linux"))] - pub fn check_iptables_available() -> crate::Result<()> { + pub fn check_iptables_available() -> anyhow::Result<()> { Ok(()) } @@ -67,7 +68,12 @@ impl SysRoute { /// - dsts: destination CIDR addresses (e.g., ["192.168.1.0/24", "10.0.0.0/8"]) /// - gateway: gateway IP address /// - interface_idx: optional interface index (Windows only) - pub fn add(&self, dsts: Vec, gateway: String, interface_idx: Option) -> crate::Result<()> { + pub fn add( + &self, + dsts: Vec, + gateway: String, + interface_idx: Option, + ) -> anyhow::Result<()> { for dst in dsts { self.add_route(&dst, &gateway, interface_idx)? } @@ -79,7 +85,12 @@ impl SysRoute { /// - gateway: gateway IP address /// - interface_idx: optional interface index (Windows only) #[allow(unused)] - pub fn del(&self, dsts: Vec, gateway: String, interface_idx: Option) -> crate::Result<()> { + pub fn del( + &self, + dsts: Vec, + gateway: String, + interface_idx: Option, + ) -> anyhow::Result<()> { for dst in dsts { self.del_route(&dst, &gateway, interface_idx)? } @@ -87,68 +98,93 @@ impl SysRoute { } #[cfg(target_os = "linux")] - fn add_route(&self, dst: &str, gateway: &str, _interface_idx: Option) -> crate::Result<()> { + fn add_route( + &self, + dst: &str, + gateway: &str, + _interface_idx: Option, + ) -> anyhow::Result<()> { let output = Command::new("ip") .args(["route", "add", dst, "via", gateway]) .output() - .map_err(|e| format!("Failed to execute ip command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute ip command: {}", e))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); - return Err(format!("Failed to add route: {}", stderr).into()); + return Err(anyhow::anyhow!("Failed to add route: {}", stderr)); } Ok(()) } #[cfg(target_os = "linux")] - fn del_route(&self, dst: &str, gateway: &str, _interface_idx: Option) -> crate::Result<()> { + fn del_route( + &self, + dst: &str, + gateway: &str, + _interface_idx: Option, + ) -> anyhow::Result<()> { let output = Command::new("ip") .args(["route", "del", dst, "via", gateway]) .output() - .map_err(|e| format!("Failed to execute ip command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute ip command: {}", e))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); - return Err(format!("Failed to delete route: {}", stderr).into()); + return Err(anyhow::anyhow!("Failed to delete route: {}", stderr)); } Ok(()) } #[cfg(target_os = "macos")] - fn add_route(&self, dst: &str, gateway: &str, _interface_idx: Option) -> crate::Result<()> { + fn add_route( + &self, + dst: &str, + gateway: &str, + _interface_idx: Option, + ) -> anyhow::Result<()> { let output = Command::new("route") .args(["-n", "add", "-net", dst, gateway]) .output() - .map_err(|e| format!("Failed to execute route command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute route command: {}", e))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); - return Err(format!("Failed to add route: {}", stderr).into()); + return Err(anyhow::anyhow!("Failed to add route: {}", stderr)); } Ok(()) } #[cfg(target_os = "macos")] - fn del_route(&self, dst: &str, gateway: &str, _interface_idx: Option) -> crate::Result<()> { + fn del_route( + &self, + dst: &str, + gateway: &str, + _interface_idx: Option, + ) -> anyhow::Result<()> { let output = Command::new("route") .args(["-n", "delete", "-net", dst, gateway]) .output() - .map_err(|e| format!("Failed to execute route command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute route command: {}", e))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); - return Err(format!("Failed to delete route: {}", stderr).into()); + return Err(anyhow::anyhow!("Failed to delete route: {}", stderr)); } Ok(()) } #[cfg(target_os = "windows")] - fn add_route(&self, dst: &str, gateway: &str, interface_idx: Option) -> crate::Result<()> { + fn add_route( + &self, + dst: &str, + gateway: &str, + interface_idx: Option, + ) -> anyhow::Result<()> { // Windows route command format: route add mask if metric 1 let (network, mask) = self.parse_cidr(dst)?; let mut args = vec!["add", &network, "mask", &mask, gateway]; - + // Add interface index if provided let idx_str; if let Some(idx) = interface_idx { @@ -156,7 +192,7 @@ impl SysRoute { args.push("if"); args.push(&idx_str); } - + // Always use metric 1 for highest priority args.push("metric"); args.push("1"); @@ -164,7 +200,7 @@ impl SysRoute { let output = Command::new("route") .args(&args) .output() - .map_err(|e| format!("Failed to execute route command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute route command: {}", e))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); @@ -173,21 +209,31 @@ impl SysRoute { tracing::debug!("Route already exists: {} via {}", dst, gateway); return Ok(()); } - return Err(format!("Failed to add route: {}", stderr).into()); + return Err(anyhow::anyhow!("Failed to add route: {}", stderr)); } - - tracing::debug!("Added route: {} via {} (interface: {:?})", dst, gateway, interface_idx); + + tracing::debug!( + "Added route: {} via {} (interface: {:?})", + dst, + gateway, + interface_idx + ); Ok(()) } #[cfg(target_os = "windows")] - fn del_route(&self, dst: &str, _gateway: &str, _interface_idx: Option) -> crate::Result<()> { + fn del_route( + &self, + dst: &str, + _gateway: &str, + _interface_idx: Option, + ) -> anyhow::Result<()> { let (network, mask) = self.parse_cidr(dst)?; let output = Command::new("route") .args(&["delete", &network, "mask", &mask]) .output() - .map_err(|e| format!("Failed to execute route command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute route command: {}", e))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); @@ -196,31 +242,31 @@ impl SysRoute { tracing::debug!("Route not found (already deleted): {}", dst); return Ok(()); } - return Err(format!("Failed to delete route: {}", stderr).into()); + return Err(anyhow::anyhow!("Failed to delete route: {}", stderr)); } Ok(()) } #[allow(unused)] - fn parse_cidr(&self, cidr: &str) -> crate::Result<(String, String)> { + fn parse_cidr(&self, cidr: &str) -> anyhow::Result<(String, String)> { let parts: Vec<&str> = cidr.split('/').collect(); if parts.len() != 2 { - return Err(format!("Invalid CIDR format: {}", cidr).into()); + return Err(anyhow::anyhow!("Invalid CIDR format: {}", cidr)); } let network = parts[0].to_string(); let prefix_len: u8 = parts[1] .parse() - .map_err(|_| format!("Invalid prefix length: {}", parts[1]))?; + .map_err(|_| anyhow::anyhow!("Invalid prefix length: {}", parts[1]))?; // Convert prefix length to netmask let mask = Self::prefix_to_netmask(prefix_len)?; Ok((network, mask)) } - fn prefix_to_netmask(prefix_len: u8) -> crate::Result { + fn prefix_to_netmask(prefix_len: u8) -> anyhow::Result { if prefix_len > 32 { - return Err("Invalid prefix length: must be 0-32".into()); + return Err(anyhow::anyhow!("Invalid prefix length: must be 0-32")); } let mask_int = (!0u32) << (32 - prefix_len); @@ -238,50 +284,59 @@ impl SysRoute { } #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] - fn add_route(&self, _dst: &str, _gateway: &str) -> crate::Result<()> { - Err("Route management is not supported on this platform".into()) + fn add_route(&self, _dst: &str, _gateway: &str) -> anyhow::Result<()> { + Err(anyhow::anyhow!( + "Route management is not supported on this platform" + )) } #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] - fn del_route(&self, _dst: &str, _gateway: &str) -> crate::Result<()> { - Err("Route management is not supported on this platform".into()) + fn del_route(&self, _dst: &str, _gateway: &str) -> anyhow::Result<()> { + Err(anyhow::anyhow!( + "Route management is not supported on this platform" + )) } /// Enable MASQUERADE (NAT) for VPN interface using source network address (Linux only) /// This allows VPN clients to access external networks through the VPN gateway /// Uses source network CIDR instead of interface name for better reliability #[cfg(target_os = "linux")] - pub fn enable_masquerade_by_source(&self, source_cidr: &str) -> crate::Result<()> { + pub fn enable_masquerade_by_source(&self, source_cidr: &str) -> anyhow::Result<()> { // Check if rule already exists: iptables -t nat -C POSTROUTING -s -j MASQUERADE let check_output = Command::new("iptables") .args([ - "-t", "nat", - "-C", "POSTROUTING", - "-s", source_cidr, - "-j", "MASQUERADE" + "-t", + "nat", + "-C", + "POSTROUTING", + "-s", + source_cidr, + "-j", + "MASQUERADE", ]) .output() - .map_err(|e| format!("Failed to execute iptables check command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute iptables check command: {}", e))?; if check_output.status.success() { tracing::debug!("MASQUERADE rule already exists for source {}", source_cidr); return Ok(()); } + #[rustfmt::skip] // Add iptables rule: iptables -t nat -A POSTROUTING -s -j MASQUERADE let output = Command::new("iptables") .args([ "-t", "nat", "-A", "POSTROUTING", "-s", source_cidr, - "-j", "MASQUERADE" + "-j", "MASQUERADE", ]) .output() - .map_err(|e| format!("Failed to execute iptables command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute iptables command: {}", e))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); - return Err(format!("Failed to enable MASQUERADE: {}", stderr).into()); + return Err(anyhow::anyhow!("Failed to enable MASQUERADE: {}", stderr)); } tracing::info!("Enabled MASQUERADE for source network: {}", source_cidr); @@ -290,21 +345,22 @@ impl SysRoute { /// Disable MASQUERADE (NAT) for VPN interface using source network address (Linux only) #[cfg(target_os = "linux")] - pub fn disable_masquerade_by_source(&self, source_cidr: &str) -> crate::Result<()> { + pub fn disable_masquerade_by_source(&self, source_cidr: &str) -> anyhow::Result<()> { + #[rustfmt::skip] // Remove iptables rule: iptables -t nat -D POSTROUTING -s -j MASQUERADE let output = Command::new("iptables") .args([ "-t", "nat", "-D", "POSTROUTING", "-s", source_cidr, - "-j", "MASQUERADE" + "-j", "MASQUERADE", ]) .output() - .map_err(|e| format!("Failed to execute iptables command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute iptables command: {}", e))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); - return Err(format!("Failed to disable MASQUERADE: {}", stderr).into()); + return Err(anyhow::anyhow!("Failed to disable MASQUERADE: {}", stderr)); } tracing::info!("Disabled MASQUERADE for source network: {}", source_cidr); @@ -312,12 +368,12 @@ impl SysRoute { } #[cfg(not(target_os = "linux"))] - pub fn enable_masquerade_by_source(&self, _interface: &str) -> crate::Result<()> { + pub fn enable_masquerade_by_source(&self, _interface: &str) -> anyhow::Result<()> { Ok(()) } #[cfg(not(target_os = "linux"))] - pub fn disable_masquerade_by_source(&self, _interface: &str) -> crate::Result<()> { + pub fn disable_masquerade_by_source(&self, _interface: &str) -> anyhow::Result<()> { Ok(()) } @@ -325,39 +381,58 @@ impl SysRoute { /// This allows packets from local ciders to appear as coming from virtual IP /// Rule: iptables -t nat -A POSTROUTING -s -j SNAT --to-source #[cfg(target_os = "linux")] - pub fn enable_snat_for_local_network(&self, local_cidr: &str, _tun_interface: &str, virtual_ip: &str) -> crate::Result<()> { + pub fn enable_snat_for_local_network( + &self, + local_cidr: &str, + _tun_interface: &str, + virtual_ip: &str, + ) -> anyhow::Result<()> { // Check if rule already exists: iptables -t nat -C POSTROUTING -s -j SNAT --to-source let check_output = Command::new("iptables") .args([ - "-t", "nat", - "-C", "POSTROUTING", - "-s", local_cidr, - "-j", "SNAT", - "--to-source", virtual_ip + "-t", + "nat", + "-C", + "POSTROUTING", + "-s", + local_cidr, + "-j", + "SNAT", + "--to-source", + virtual_ip, ]) .output() - .map_err(|e| format!("Failed to execute iptables check command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute iptables check command: {}", e))?; if check_output.status.success() { - tracing::debug!("SNAT rule already exists for {} -> {}", local_cidr, virtual_ip); + tracing::debug!( + "SNAT rule already exists for {} -> {}", + local_cidr, + virtual_ip + ); return Ok(()); } // Add iptables rule: iptables -t nat -A POSTROUTING -s -j SNAT --to-source let output = Command::new("iptables") .args([ - "-t", "nat", - "-A", "POSTROUTING", - "-s", local_cidr, - "-j", "SNAT", - "--to-source", virtual_ip + "-t", + "nat", + "-A", + "POSTROUTING", + "-s", + local_cidr, + "-j", + "SNAT", + "--to-source", + virtual_ip, ]) .output() - .map_err(|e| format!("Failed to execute iptables command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute iptables command: {}", e))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); - return Err(format!("Failed to enable SNAT: {}", stderr).into()); + return Err(anyhow::anyhow!("Failed to enable SNAT: {}", stderr)); } tracing::info!("Enabled SNAT for {} -> {}", local_cidr, virtual_ip); @@ -366,21 +441,27 @@ impl SysRoute { /// Disable SNAT for local network segments (Linux only) #[cfg(target_os = "linux")] - pub fn disable_snat_for_local_network(&self, local_cidr: &str, _tun_interface: &str, virtual_ip: &str) -> crate::Result<()> { + pub fn disable_snat_for_local_network( + &self, + local_cidr: &str, + _tun_interface: &str, + virtual_ip: &str, + ) -> anyhow::Result<()> { + #[rustfmt::skip] let output = Command::new("iptables") .args([ - "-t", "nat", - "-D", "POSTROUTING", - "-s", local_cidr, - "-j", "SNAT", - "--to-source", virtual_ip + "-t", "nat", + "-D", "POSTROUTING", + "-s", local_cidr, + "-j", "SNAT", + "--to-source", virtual_ip, ]) .output() - .map_err(|e| format!("Failed to execute iptables command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute iptables command: {}", e))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); - return Err(format!("Failed to disable SNAT: {}", stderr).into()); + return Err(anyhow::anyhow!("Failed to disable SNAT: {}", stderr)); } tracing::info!("Disabled SNAT for {} -> {}", local_cidr, virtual_ip); @@ -388,35 +469,46 @@ impl SysRoute { } #[cfg(not(target_os = "linux"))] - pub fn enable_snat_for_local_network(&self, _local_cidr: &str, _tun_interface: &str, _virtual_ip: &str) -> crate::Result<()> { + pub fn enable_snat_for_local_network( + &self, + _local_cidr: &str, + _tun_interface: &str, + _virtual_ip: &str, + ) -> anyhow::Result<()> { Ok(()) } #[cfg(not(target_os = "linux"))] - pub fn disable_snat_for_local_network(&self, _local_cidr: &str, _tun_interface: &str, _virtual_ip: &str) -> crate::Result<()> { + pub fn disable_snat_for_local_network( + &self, + _local_cidr: &str, + _tun_interface: &str, + _virtual_ip: &str, + ) -> anyhow::Result<()> { Ok(()) } /// Enable DNAT/NETMAP for CIDR mapping (Linux only) /// Maps destination IPs from mapped CIDR to real CIDR /// Uses NETMAP target: iptables -t nat -A PREROUTING -d -j NETMAP --to - /// + /// /// # Arguments /// * `mapped_cidr` - The CIDR that other clients see (e.g., "192.168.11.0/24") /// * `real_cidr` - The real CIDR network (e.g., "192.168.10.0/24") - /// + /// /// # Example /// When a packet arrives with destination IP in `mapped_cidr`, it will be translated /// to the corresponding IP in `real_cidr` before being forwarded to the local network. #[cfg(target_os = "linux")] - pub fn enable_cidr_dnat(&self, mapped_cidr: &str, real_cidr: &str) -> crate::Result<()> { + pub fn enable_cidr_dnat(&self, mapped_cidr: &str, real_cidr: &str) -> anyhow::Result<()> { + #[rustfmt::skip] // Check if NETMAP rule already exists: iptables -t nat -C PREROUTING -d -j NETMAP --to let check_output = Command::new("iptables") .args([ - "-t", "nat", - "-C", "PREROUTING", - "-d", mapped_cidr, - "-j", "NETMAP", + "-t", "nat", + "-C", "PREROUTING", + "-d", mapped_cidr, + "-j", "NETMAP", "--to", real_cidr, ]) .output(); @@ -429,23 +521,24 @@ impl SysRoute { _ => {} } + #[rustfmt::skip] // Add NETMAP rule: iptables -t nat -A PREROUTING -d -j NETMAP --to let output = Command::new("iptables") .args([ - "-t", "nat", - "-A", "PREROUTING", - "-d", mapped_cidr, - "-j", "NETMAP", + "-t", "nat", + "-A", "PREROUTING", + "-d", mapped_cidr, + "-j", "NETMAP", "--to", real_cidr, ]) .output() .map_err(|e| { if e.kind() == std::io::ErrorKind::NotFound { - "iptables command not found. CIDR mapping requires iptables with NETMAP support.\n\ + anyhow::anyhow!("iptables command not found. CIDR mapping requires iptables with NETMAP support.\n\ Please install iptables and ensure your kernel supports NETMAP target.\n\ - NETMAP requires Linux kernel 2.6.32+ with netfilter NETMAP module.".to_string() + NETMAP requires Linux kernel 2.6.32+ with netfilter NETMAP module.") } else { - format!("Failed to execute iptables command: {}", e) + anyhow::anyhow!("Failed to execute iptables command: {}", e) } })?; @@ -453,13 +546,14 @@ impl SysRoute { let stderr = String::from_utf8_lossy(&output.stderr); // Check if NETMAP is not supported if stderr.contains("No chain/target/match") || stderr.contains("NETMAP") { - return Err(format!( + return Err(anyhow::anyhow!( "NETMAP target not supported. CIDR mapping requires kernel support for NETMAP.\n\ Please ensure your kernel has NETMAP support (Linux 2.6.32+) or use a different approach.\n\ - Error: {}", stderr - ).into()); + Error: {}", + stderr + )); } - return Err(format!("Failed to add DNAT rule: {}", stderr).into()); + return Err(anyhow::anyhow!("Failed to add DNAT rule: {}", stderr)); } tracing::info!("Added DNAT rule: {} -> {}", mapped_cidr, real_cidr); @@ -468,31 +562,39 @@ impl SysRoute { /// Disable DNAT/NETMAP for CIDR mapping (Linux only) /// Removes the NETMAP rule that was previously added - /// + /// /// # Arguments /// * `mapped_cidr` - The mapped CIDR (e.g., "192.168.11.0/24") /// * `real_cidr` - The real CIDR (e.g., "192.168.10.0/24") #[cfg(target_os = "linux")] - pub fn disable_cidr_dnat(&self, mapped_cidr: &str, real_cidr: &str) -> crate::Result<()> { + pub fn disable_cidr_dnat(&self, mapped_cidr: &str, real_cidr: &str) -> anyhow::Result<()> { + #[rustfmt::skip] let output = Command::new("iptables") .args([ - "-t", "nat", - "-D", "PREROUTING", - "-d", mapped_cidr, - "-j", "NETMAP", + "-t", "nat", + "-D", "PREROUTING", + "-d", mapped_cidr, + "-j", "NETMAP", "--to", real_cidr, ]) .output() - .map_err(|e| format!("Failed to execute iptables command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute iptables command: {}", e))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); // Ignore "not found" error (rule already deleted) - if stderr.contains("not found") || stderr.contains("找不到") || stderr.contains("No rule") { - tracing::debug!("DNAT rule not found (already deleted): {} -> {}", mapped_cidr, real_cidr); + if stderr.contains("not found") + || stderr.contains("找不到") + || stderr.contains("No rule") + { + tracing::debug!( + "DNAT rule not found (already deleted): {} -> {}", + mapped_cidr, + real_cidr + ); return Ok(()); } - return Err(format!("Failed to delete DNAT rule: {}", stderr).into()); + return Err(anyhow::anyhow!("Failed to delete DNAT rule: {}", stderr)); } tracing::info!("Deleted DNAT rule: {} -> {}", mapped_cidr, real_cidr); @@ -500,13 +602,17 @@ impl SysRoute { } #[cfg(not(target_os = "linux"))] - pub fn enable_cidr_dnat(&self, _mapped_cidr: &str, _real_cidr: &str) -> crate::Result<()> { - Err("CIDR mapping DNAT is only supported on Linux".into()) + pub fn enable_cidr_dnat(&self, _mapped_cidr: &str, _real_cidr: &str) -> anyhow::Result<()> { + Err(anyhow::anyhow!( + "CIDR mapping DNAT is only supported on Linux" + )) } #[cfg(not(target_os = "linux"))] - pub fn disable_cidr_dnat(&self, _mapped_cidr: &str, _real_cidr: &str) -> crate::Result<()> { - Err("CIDR mapping DNAT is only supported on Linux".into()) + pub fn disable_cidr_dnat(&self, _mapped_cidr: &str, _real_cidr: &str) -> anyhow::Result<()> { + Err(anyhow::anyhow!( + "CIDR mapping DNAT is only supported on Linux" + )) } } From b65c2db87860ec81c93864f4da7857cbf885116e Mon Sep 17 00:00:00 2001 From: Banyc <36535895+Banyc@users.noreply.github.com> Date: Sat, 21 Mar 2026 11:24:26 +0000 Subject: [PATCH 04/12] denest tokio selects --- examples/read_async.rs | 7 +- src/client/main.rs | 9 +- src/client/relay.rs | 227 ++++++++++++++++++++++-------------- src/network/tcp_listener.rs | 28 +++-- src/server/handler.rs | 174 ++++++++++++++------------- 5 files changed, 246 insertions(+), 199 deletions(-) diff --git a/examples/read_async.rs b/examples/read_async.rs index 4dfdac8..b4e5259 100644 --- a/examples/read_async.rs +++ b/examples/read_async.rs @@ -43,10 +43,7 @@ async fn main_entry() -> Result<(), BoxError> { let size = dev.mtu()? as usize + tun::PACKET_INFORMATION_LENGTH; let mut buf = vec![0; size]; loop { - tokio::select! { - len = dev.read(&mut buf) => { - println!("pkt: {:?}", &buf[..len?]); - } - }; + let len = dev.read(&mut buf).await?; + println!("pkt: {:?}", &buf[..len]); } } diff --git a/src/client/main.rs b/src/client/main.rs index c8e3f81..4965513 100644 --- a/src/client/main.rs +++ b/src/client/main.rs @@ -152,12 +152,9 @@ async fn run_event_loop( tokio::spawn(async move { loop { - tokio::select! { - packet = dev_inbound.recv() => { - if let Some(packet) = packet { - handle_device_packet(relay_outbound.clone(), p2p_for_spawn.clone(), packet).await; - } - } + let packet = dev_inbound.recv().await; + if let Some(packet) = packet { + handle_device_packet(relay_outbound.clone(), p2p_for_spawn.clone(), packet).await; } } }); diff --git a/src/client/relay.rs b/src/client/relay.rs index 55bc6a0..1acef69 100644 --- a/src/client/relay.rs +++ b/src/client/relay.rs @@ -5,6 +5,7 @@ use crate::codec::frame::{Frame, HandshakeFrame, HandshakeReplyFrame, KeepAliveF use crate::crypto::Block; use crate::network::{ConnManage, ConnectionConfig, TCPConnectionConfig, create_connection}; use crate::utils::{self, StunAddr}; +use std::ops::ControlFlow; use std::sync::Arc; use std::time::Instant; use tokio::sync::{RwLock, mpsc}; @@ -65,35 +66,20 @@ impl RelayClient { loop { tokio::select! { _ = keepalive_ticker.tick() => { - if last_active.elapsed().as_secs() > timeout_secs { - tracing::warn!("keepalive threshold {:?} exceeded", last_active.elapsed()); + if let ControlFlow::Break(_) = self + .keep_alive( + &mut conn, + &mut keepalive_wait, + ¤t_ipv6, + port, + stun.as_ref(), + last_active, + timeout_secs, + ) + .await + { break; } - - tracing::debug!("sending keepalive frame"); - let keepalive_frame = Frame::KeepAlive(KeepAliveFrame { - name: "".to_string(), - identity: self.cfg.identity.clone(), - ipv6: current_ipv6.clone(), - port, - #[allow(clippy::unwrap_or_default)] - stun_ip: stun.as_ref().map(|stun| stun.ip.clone()).unwrap_or(String::new()), - stun_port: stun.as_ref().map(|stun| stun.port).unwrap_or(0), - peer_details: vec![], // Client doesn't need to send peer info - }); - match conn.write_frame(keepalive_frame).await { - Ok(_) => { - keepalive_wait = 0; - } - Err(e) => { - tracing::error!("Failed to send keepalive: {}", e); - keepalive_wait+=1; - if keepalive_wait > self.cfg.keep_alive_thresh { - tracing::error!("keepalive max retry, close connection"); - break; - } - } - } } // Periodic IPv6 address update check @@ -110,35 +96,8 @@ impl RelayClient { // inbound result = conn.read_frame() => { - last_active = Instant::now(); - match result { - Ok(frame) => { - tracing::debug!("received frame {}", frame); - let beg = Instant::now(); - match frame { - Frame::KeepAlive(keepalive) => { - keepalive_wait = keepalive_wait.saturating_sub(1); - - tracing::debug!("Received keepalive from server"); - if let Err(e) = self.inbound_tx.send(Frame::KeepAlive(keepalive)).await { - tracing::error!("Failed to forward keepalive: {}", e); - break; - } - } - Frame::Data(data) => { - if let Err(e) = self.inbound_tx.send(Frame::Data(data)).await { - tracing::error!("server => device inbound: {}", e); - break; - } - } - _ => {} - } - tracing::debug!("handle frame cost {}", beg.elapsed().as_millis()); - } - Err(e) => { - tracing::error!("Read error: {}", e); - break; - } + if let ControlFlow::Break(_) = self.read_frame(&mut keepalive_wait, &mut last_active, result).await { + break; } } // outbound @@ -162,6 +121,90 @@ impl RelayClient { Ok(()) } + async fn read_frame( + &mut self, + keepalive_wait: &mut u8, + last_active: &mut Instant, + result: anyhow::Result, + ) -> ControlFlow<()> { + *last_active = Instant::now(); + match result { + Ok(frame) => { + tracing::debug!("received frame {}", frame); + let beg = Instant::now(); + match frame { + Frame::KeepAlive(keepalive) => { + *keepalive_wait = keepalive_wait.saturating_sub(1); + + tracing::debug!("Received keepalive from server"); + if let Err(e) = self.inbound_tx.send(Frame::KeepAlive(keepalive)).await { + tracing::error!("Failed to forward keepalive: {}", e); + return ControlFlow::Break(()); + } + } + Frame::Data(data) => { + if let Err(e) = self.inbound_tx.send(Frame::Data(data)).await { + tracing::error!("server => device inbound: {}", e); + return ControlFlow::Break(()); + } + } + _ => {} + } + tracing::debug!("handle frame cost {}", beg.elapsed().as_millis()); + } + Err(e) => { + tracing::error!("Read error: {}", e); + return ControlFlow::Break(()); + } + } + ControlFlow::Continue(()) + } + + async fn keep_alive( + &mut self, + conn: &mut Box, + keepalive_wait: &mut u8, + current_ipv6: &str, + port: u16, + stun: Option<&StunAddr>, + last_active: Instant, + timeout_secs: u64, + ) -> ControlFlow<()> { + if last_active.elapsed().as_secs() > timeout_secs { + tracing::warn!("keepalive threshold {:?} exceeded", last_active.elapsed()); + return ControlFlow::Break(()); + } + tracing::debug!("sending keepalive frame"); + let keepalive_frame = Frame::KeepAlive(KeepAliveFrame { + name: "".to_string(), + identity: self.cfg.identity.clone(), + ipv6: current_ipv6.to_string(), + port, + #[allow(clippy::unwrap_or_default)] + stun_ip: stun + .as_ref() + .map(|stun| stun.ip.clone()) + .unwrap_or(String::new()), + stun_port: stun.as_ref().map(|stun| stun.port).unwrap_or(0), + peer_details: vec![], // Client doesn't need to send peer info + }); + + match conn.write_frame(keepalive_frame).await { + Ok(_) => { + *keepalive_wait = 0; + } + Err(e) => { + tracing::error!("Failed to send keepalive: {}", e); + *keepalive_wait += 1; + if *keepalive_wait > self.cfg.keep_alive_thresh { + tracing::error!("keepalive max retry, close connection"); + return ControlFlow::Break(()); + } + } + } + ControlFlow::Continue(()) + } + async fn connect(&self) -> anyhow::Result> { let conn = create_connection( ConnectionConfig::TCP(TCPConnectionConfig { @@ -272,39 +315,7 @@ impl RelayHandler { tokio::spawn(async move { loop { - let mut conn = match client.connect().await { - Ok(socket) => socket, - Err(e) => { - tracing::error!("connect error: {}", e); - tokio::time::sleep(Duration::from_secs(5)).await; - continue; - } - }; - - let frame = match client.handshake(&mut conn).await { - Ok(frame) => frame, - Err(e) => { - tracing::warn!("handshake fail {:?}, reconnecting", e); - tokio::time::sleep(Duration::from_secs(5)).await; - continue; - } - }; - - tracing::info!("Handshake complete with {} peers", frame.peer_details.len()); - - // Store handshake reply in handler - { - let mut guard = handshake_reply.write().await; - *guard = Some(frame.clone()); - } - - if let Err(e) = on_ready.send(frame.clone()).await { - tracing::error!("on ready send fail: {}", e); - } - - let result = client.run(conn).await; - - tracing::warn!("run client fail {:?}, reconnecting", result); + run_client_session(&on_ready, &mut client, &handshake_reply).await; tokio::time::sleep(Duration::from_secs(5)).await; } }); @@ -345,6 +356,44 @@ impl RelayHandler { } } +async fn run_client_session( + on_ready: &mpsc::Sender, + client: &mut RelayClient, + handshake_reply: &Arc>>, +) { + let mut conn = match client.connect().await { + Ok(socket) => socket, + Err(e) => { + tracing::error!("connect error: {}", e); + return; + } + }; + + let frame = match client.handshake(&mut conn).await { + Ok(frame) => frame, + Err(e) => { + tracing::warn!("handshake fail {:?}, reconnecting", e); + return; + } + }; + + tracing::info!("Handshake complete with {} peers", frame.peer_details.len()); + + // Store handshake reply in handler + { + let mut guard = handshake_reply.write().await; + *guard = Some(frame.clone()); + } + + if let Err(e) = on_ready.send(frame.clone()).await { + tracing::error!("on ready send fail: {}", e); + } + + let result = client.run(conn).await; + + tracing::warn!("run client fail {:?}, reconnecting", result); +} + pub async fn new_relay_handler( args: &Args, block: Arc>, diff --git a/src/network/tcp_listener.rs b/src/network/tcp_listener.rs index ee486cc..3c3dc96 100644 --- a/src/network/tcp_listener.rs +++ b/src/network/tcp_listener.rs @@ -97,21 +97,19 @@ impl Listener for TCPListener { self.listener = Some(listener); loop { - tokio::select! { - socket = self.accept() => { - match socket { - Ok(socket) => { - let conn = TcpConnection::new(socket, self.block.clone()); - if let Some(tx) = &self.on_conn_tx - && let Err(e) = tx.send(Box::new(conn)).await { - tracing::warn!("Failed to send new connection: {}", e); - } - }, - Err(e) => { - tracing::error!("Accept error: {}", e); - return Err(e); - } - }; + let socket = self.accept().await; + match socket { + Ok(socket) => { + let conn = TcpConnection::new(socket, self.block.clone()); + if let Some(tx) = &self.on_conn_tx + && let Err(e) = tx.send(Box::new(conn)).await + { + tracing::warn!("Failed to send new connection: {}", e); + } + } + Err(e) => { + tracing::error!("Accept error: {}", e); + return Err(e); } } } diff --git a/src/server/handler.rs b/src/server/handler.rs index b6af2c3..7053b07 100644 --- a/src/server/handler.rs +++ b/src/server/handler.rs @@ -1,5 +1,7 @@ use crate::codec::frame::Frame::HandshakeReply; -use crate::codec::frame::{Frame, HandshakeFrame, HandshakeReplyFrame, KeepAliveFrame, PeerDetail}; +use crate::codec::frame::{ + DataFrame, Frame, HandshakeFrame, HandshakeReplyFrame, KeepAliveFrame, PeerDetail, +}; use crate::crypto::Block; use crate::network::ConnectionMeta; use crate::network::connection_manager::ConnectionManager; @@ -69,12 +71,9 @@ impl Server { }); loop { - tokio::select! { - conn = on_conn_rx.recv() => { - if let Some(conn) = conn { - let _ = self.handle_conn(conn); - } - } + let conn = on_conn_rx.recv().await; + if let Some(conn) = conn { + let _ = self.handle_conn(conn); } } } @@ -261,90 +260,97 @@ impl Handler { async fn handle_frame(&mut self, frame: Frame) { match frame { Frame::KeepAlive(frame) => { - tracing::info!( - "on keepalive from {} {}:{} {}:{}", - frame.identity, - frame.ipv6, - frame.port, - frame.stun_ip, - frame.stun_port - ); - - let client = self.client_manager.get_client(&frame.identity); - let mut name = String::new(); - if let Some(client) = client { - let stun = StunAddr { - ip: frame.stun_ip.clone(), - port: frame.stun_port, - }; - let _ = self.connection_manager.update_connection_info( - &client.cluster, - &frame.identity, - client.ciders.clone(), - frame.ipv6.clone(), - frame.port, - stun, - ); - name = client.name.clone(); - } - - // Reply keepalive with full peer details for route sync - let peer_details = if let Some(cluster) = &self.cluster { - self.build_others(cluster, &frame.identity) - } else { - vec![] - }; - - let reply_frame = Frame::KeepAlive(KeepAliveFrame { - name: name.clone(), - identity: frame.identity, - ipv6: frame.ipv6, - port: frame.port, - stun_ip: frame.stun_ip, - stun_port: frame.stun_port, - peer_details, - }); - - if let Err(e) = self.outbound_tx.send(reply_frame).await { - tracing::error!("reply keepalive frame failed with {:?}", e); - } + self.handle_keepalive_frame(frame).await; } Frame::Data(frame) => { - if frame.invalid() { - tracing::warn!("receive invalid ip packet"); - return; - } - - if frame.version() != 4 { - tracing::warn!("receive invalid ipv4 packet"); - return; - } - tracing::debug!("on data: {} => {}", frame.src(), frame.dst()); - - // route within cluster (tenant isolation) - let dst_ip = frame.dst(); - let cluster = match &self.cluster { - Some(c) => c, - None => { - tracing::error!("cluster not set"); - return; - } - }; - - let dst_client = self.connection_manager.get_connection(cluster, &dst_ip); - if let Some(dst_client) = dst_client { - let result = dst_client.outbound_tx.send(Frame::Data(frame)).await; - if result.is_err() { - tracing::warn!("dst client {} not online", dst_ip); - } - } else { - tracing::warn!("no route to {} in cluster {}", dst_ip, cluster); - } + self.handle_data_frame(frame).await; } _ => { tracing::warn!("unknown frame: {:?}", frame); } } } + + async fn handle_data_frame(&mut self, frame: DataFrame) { + if frame.invalid() { + tracing::warn!("receive invalid ip packet"); + return; + } + if frame.version() != 4 { + tracing::warn!("receive invalid ipv4 packet"); + return; + } + tracing::debug!("on data: {} => {}", frame.src(), frame.dst()); + let dst_ip = frame.dst(); + let cluster = match &self.cluster { + Some(c) => c, + None => { + tracing::error!("cluster not set"); + return; + } + }; + let dst_client = self.connection_manager.get_connection(cluster, &dst_ip); + + // route within cluster (tenant isolation) + + if let Some(dst_client) = dst_client { + let result = dst_client.outbound_tx.send(Frame::Data(frame)).await; + if result.is_err() { + tracing::warn!("dst client {} not online", dst_ip); + } + } else { + tracing::warn!("no route to {} in cluster {}", dst_ip, cluster); + } + } + + async fn handle_keepalive_frame(&mut self, frame: KeepAliveFrame) { + tracing::info!( + "on keepalive from {} {}:{} {}:{}", + frame.identity, + frame.ipv6, + frame.port, + frame.stun_ip, + frame.stun_port + ); + + let client = self.client_manager.get_client(&frame.identity); + let mut name = String::new(); + if let Some(client) = client { + let stun = StunAddr { + ip: frame.stun_ip.clone(), + port: frame.stun_port, + }; + let _ = self.connection_manager.update_connection_info( + &client.cluster, + &frame.identity, + client.ciders.clone(), + frame.ipv6.clone(), + frame.port, + stun, + ); + name = client.name.clone(); + } + + // Reply keepalive with full peer details for route sync + let peer_details = if let Some(cluster) = &self.cluster { + self.build_others(cluster, &frame.identity) + } else { + vec![] + }; + + let reply_frame = Frame::KeepAlive(KeepAliveFrame { + name: name.clone(), + identity: frame.identity, + ipv6: frame.ipv6, + port: frame.port, + stun_ip: frame.stun_ip, + stun_port: frame.stun_port, + peer_details, + }); + + if let Err(e) = self.outbound_tx.send(reply_frame).await { + tracing::error!("reply keepalive frame failed with {:?}", e); + } + } } From 6dda3a416f2cd5adf60841fc851cce07e6b7f6e3 Mon Sep 17 00:00:00 2001 From: Banyc <36535895+Banyc@users.noreply.github.com> Date: Sat, 21 Mar 2026 11:53:43 +0000 Subject: [PATCH 05/12] bump crates --- Cargo.lock | 732 ++++++++++++------- Cargo.toml | 50 +- examples/{stun_discover.rs => tun_reader.rs} | 44 +- 3 files changed, 532 insertions(+), 294 deletions(-) rename examples/{stun_discover.rs => tun_reader.rs} (93%) diff --git a/Cargo.lock b/Cargo.lock index 3671c4a..4c540a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,7 +26,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -69,15 +69,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -104,9 +104,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "async-channel" @@ -120,28 +120,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.110", -] - [[package]] name = "async-task" version = "4.7.1" @@ -156,7 +134,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -193,7 +171,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tower 0.5.2", + "tower 0.5.3", "tower-layer", "tower-service", "tracing", @@ -228,9 +206,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" @@ -240,9 +218,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block-buffer" @@ -284,35 +262,35 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "c2rust-bitfields" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46dc7d2bffa0d0b3d47eb2dc69973466858281446c2ac9f6d8a10e92ab1017df" +checksum = "dcee50917f9de1a018e3f4f9a8f2ff3d030a288cffa4b18d9b391e97c12e4cfb" dependencies = [ "c2rust-bitfields-derive", ] [[package]] name = "c2rust-bitfields-derive" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe1117afa5937ce280034e31fa1e84ed1824a252f75380327eed438535333f8" +checksum = "3b457277798202ccd365b9c112ebee08ddd57f1033916c8b8ea52f222e5b715d" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] name = "cc" -version = "1.2.49" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "shlex", @@ -338,7 +316,18 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", ] [[package]] @@ -348,7 +337,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20", + "chacha20 0.9.1", "cipher", "poly1305", "zeroize", @@ -367,9 +356,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -377,9 +366,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -389,27 +378,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "concurrent-queue" @@ -435,6 +424,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -492,7 +490,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c336ff0159b4b18f0414b51060ee7a41ef7f93703da0862b5a655490be02aab4" dependencies = [ "log", - "nix", + "nix 0.30.1", "windows-sys 0.60.2", ] @@ -503,7 +501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest", "fiat-crypto", @@ -520,7 +518,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -535,9 +533,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] @@ -561,7 +559,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -595,6 +593,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "event-listener" version = "5.4.1" @@ -630,32 +638,37 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.60.2", ] [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", ] +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -676,9 +689,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -691,9 +704,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -701,15 +714,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -718,9 +731,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -734,32 +747,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -769,7 +782,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -785,9 +797,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -802,10 +814,24 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.0", + "wasip2", + "wasip3", +] + [[package]] name = "ghash" version = "0.5.1" @@ -816,6 +842,15 @@ dependencies = [ "polyval", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -905,12 +940,11 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "bytes", - "futures-core", "http", "http-body", "hyper", @@ -1000,6 +1034,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "idna" version = "1.1.0" @@ -1023,12 +1063,14 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -1071,9 +1113,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "is_terminal_polyfill" @@ -1083,9 +1125,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "kqueue" @@ -1113,17 +1155,23 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" -version = "0.2.177" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libloading" -version = "0.8.9" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" dependencies = [ "cfg-if", "windows-link", @@ -1131,13 +1179,14 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", - "redox_syscall 0.7.0", + "plain", + "redox_syscall 0.7.3", ] [[package]] @@ -1184,9 +1233,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mime" @@ -1206,9 +1255,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", @@ -1222,7 +1271,19 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nix" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +dependencies = [ + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -1234,7 +1295,7 @@ version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "filetime", "fsevent-sys", "inotify", @@ -1267,15 +1328,15 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -1326,9 +1387,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -1338,9 +1399,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", "fastrand", @@ -1357,13 +1418,19 @@ dependencies = [ "spki", ] +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "poly1305" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -1375,7 +1442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -1404,20 +1471,30 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -1428,6 +1505,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.9.2" @@ -1435,7 +1518,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", - "rand_core 0.9.3", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20 0.10.0", + "getrandom 0.4.2", + "rand_core 0.10.0", ] [[package]] @@ -1445,7 +1539,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1454,41 +1548,47 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] name = "redox_syscall" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1497,9 +1597,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "ring" @@ -1509,7 +1609,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -1526,9 +1626,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "log", "once_cell", @@ -1541,18 +1641,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "ring", "rustls-pki-types", @@ -1576,7 +1676,7 @@ dependencies = [ "ipnet", "notify", "once_cell", - "rand", + "rand 0.10.0", "serde", "serde_json", "socket2", @@ -1602,9 +1702,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -1654,20 +1754,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -1683,9 +1783,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -1709,7 +1809,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -1720,7 +1820,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -1741,10 +1841,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -1765,9 +1866,9 @@ checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -1777,12 +1878,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1829,7 +1930,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e579d7f3e1a395521774b72830c867906dbec15cf7696be4e0a30ee01b4046e" dependencies = [ "bytecodec", - "rand", + "rand 0.9.2", "stun_codec", "tokio", ] @@ -1853,9 +1954,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.110" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1876,27 +1977,27 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -1910,22 +2011,22 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "tinystr" @@ -1939,9 +2040,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.48.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -1956,20 +2057,20 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -1978,12 +2079,10 @@ dependencies = [ [[package]] name = "tokio-test" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +checksum = "3f6d24790a10a7af737693a3e8f1d03faef7e6ca0cc99aae5066f533766de545" dependencies = [ - "async-stream", - "bytes", "futures-core", "tokio", "tokio-stream", @@ -1991,9 +2090,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -2004,9 +2103,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap", "serde_core", @@ -2014,32 +2113,32 @@ dependencies = [ "toml_datetime", "toml_parser", "toml_writer", - "winnow", + "winnow 0.7.15", ] [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" dependencies = [ - "winnow", + "winnow 1.0.0", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.7+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" [[package]] name = "tower" @@ -2054,9 +2153,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -2074,7 +2173,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "bytes", "http", "http-body", @@ -2098,9 +2197,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -2116,14 +2215,14 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -2142,9 +2241,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -2179,9 +2278,9 @@ dependencies = [ [[package]] name = "tun" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35f176015650e3bd849e85808d809e5b54da2ba7df983c5c3b601a2a8f1095e" +checksum = "87dce40a7bfb165d8eb8e96f7463230f3d91b56aae21e0f1e56db60161962e1a" dependencies = [ "bytes", "cfg-if", @@ -2190,7 +2289,7 @@ dependencies = [ "ipnet", "libc", "log", - "nix", + "nix 0.31.2", "thiserror", "tokio", "tokio-util", @@ -2206,9 +2305,15 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" @@ -2244,9 +2349,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -2296,27 +2401,70 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "webpki-roots" version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.4", + "webpki-roots 1.0.6", ] [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -2503,9 +2651,15 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" [[package]] name = "winreg" @@ -2519,9 +2673,9 @@ dependencies = [ [[package]] name = "wintun-bindings" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88303b411e20a1319b368dcd04db1480003ed46ac35193e139f542720b15fbf" +checksum = "27ae04d34b8569174e849128d2e36538329a27daa79c06ed0375f2c5d6704461" dependencies = [ "blocking", "c2rust-bitfields", @@ -2529,15 +2683,97 @@ dependencies = [ "libloading", "log", "thiserror", - "windows-sys 0.60.2", + "windows-sys 0.61.2", "winreg", ] [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" @@ -2564,28 +2800,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] [[package]] @@ -2605,7 +2841,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", "synstructure", ] @@ -2645,5 +2881,11 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.110", + "syn 2.0.117", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 15efa9a..15698a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,42 +4,34 @@ version = "0.0.1" edition = "2024" [dependencies] -serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.145" -bytes = "1.11.0" -anyhow = "1.0.100" -tokio = { version = "1.48.0", features = ["full"] } -tracing = "*" -tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } -time = "0.3.44" -tun= { version = "0.8.5", features = ["futures-core", "async"] } -async-trait = "0.1.89" -ed25519-dalek = { version = "2.1", features = ["rand_core"] } -rand = "0.9.2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +bytes = "1" +anyhow = "1" +tokio = { version = "1", features = ["full"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +time = "0.3" +tun= { version = "0.8", features = ["futures-core", "async"] } +async-trait = "0.1" +ed25519-dalek = { version = "2", features = ["rand_core"] } +rand = "0.10" base64 = "0.22" aes-gcm = "0.10" chacha20poly1305 = "0.10" -toml = "0.9.8" -ipnet = "2.10" -clap = { version = "4.5", features = ["derive"] } -ureq = "2.10" +toml = "0.9" +ipnet = "2" +clap = { version = "4", features = ["derive"] } +ureq = "2" stunclient = "0.4" -socket2 = "0.6.1" -notify = "7.0" -tokio-util = "0.7.17" -ctrlc2 = "3.7.3" +socket2 = "0.6" +notify = "7" +tokio-util = "0.7" +ctrlc2 = "3" axum = "0.7" tower = "0.4" tower-http = { version = "0.5", features = ["cors"] } -once_cell = "1.20" +once_cell = "1" [dev-dependencies] tokio-test = "0.4" - -[[example]] -name = "stun_discover" -path = "examples/stun_discover.rs" - -[[example]] -name = "tun_reader" -path = "examples/read_async.rs" diff --git a/examples/stun_discover.rs b/examples/tun_reader.rs similarity index 93% rename from examples/stun_discover.rs rename to examples/tun_reader.rs index 7836a19..b019c5d 100644 --- a/examples/stun_discover.rs +++ b/examples/tun_reader.rs @@ -9,7 +9,7 @@ //! cargo run --example stun_discover -- --port 51258 //! ``` -use rustun::client::p2p::stun::{StunClient, NatType}; +use rustun::client::p2p::stun::{NatType, StunClient}; use std::time::Duration; #[derive(clap::Parser, Debug)] @@ -19,15 +19,15 @@ struct Args { /// Local UDP port to bind (0 for automatic) #[arg(short, long, default_value = "0")] port: u16, - + /// Custom STUN server (can be specified multiple times) #[arg(short, long)] stun_server: Vec, - + /// Request timeout in seconds #[arg(short, long, default_value = "5")] timeout: u64, - + /// Enable verbose logging #[arg(short, long)] verbose: bool, @@ -37,16 +37,14 @@ struct Args { async fn main() { use clap::Parser; let args = Args::parse(); - + // Setup logging let log_level = if args.verbose { "debug" } else { "info" }; - tracing_subscriber::fmt() - .with_env_filter(log_level) - .init(); - + tracing_subscriber::fmt().with_env_filter(log_level).init(); + println!("🔍 STUN Discovery Tool"); println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); - + // Create STUN client let stun_client = if args.stun_server.is_empty() { println!("📡 Using default Google STUN servers"); @@ -55,35 +53,42 @@ async fn main() { println!("📡 Using custom STUN servers: {:?}", args.stun_server); StunClient::with_servers(args.stun_server) }; - + let stun_client = stun_client.with_timeout(Duration::from_secs(args.timeout)); - - println!("🔌 Local port: {}", if args.port == 0 { "auto".to_string() } else { args.port.to_string() }); + + println!( + "🔌 Local port: {}", + if args.port == 0 { + "auto".to_string() + } else { + args.port.to_string() + } + ); println!(); - + // Perform discovery println!("⏳ Discovering public address..."); match stun_client.discover(args.port).await { Ok(result) => { println!("✅ STUN Discovery Successful!\n"); - + println!("📍 Results:"); println!(" Local Address: {}", result.local_addr); println!(" Public Address: {}", result.public_addr()); println!(" Public IP: {}", result.public_ip); println!(" Public Port: {}", result.public_port); println!(); - + println!("🌐 NAT Information:"); println!(" Type: {:?}", result.nat_type); println!(" Description: {}", result.nat_type.description()); println!(); - + // Show P2P compatibility println!("🔗 P2P Compatibility:"); show_p2p_compatibility(&result.nat_type); println!(); - + // Recommendations println!("💡 Recommendations:"); match result.nat_type { @@ -137,7 +142,7 @@ fn show_p2p_compatibility(nat_type: &NatType) { ("Port-Restricted", NatType::PortRestricted), ("Symmetric NAT", NatType::Symmetric), ]; - + println!(" Success rates with different peer NAT types:"); for (name, peer_nat) in &scenarios { let rate = nat_type.hole_punch_success_rate(peer_nat); @@ -146,4 +151,3 @@ fn show_p2p_compatibility(nat_type: &NatType) { println!(" {:18} {:3}% {}", name, percentage, bar); } } - From c195ea0dda263a836a21bec93083d6ee4898ec63 Mon Sep 17 00:00:00 2001 From: Banyc <36535895+Banyc@users.noreply.github.com> Date: Sat, 21 Mar 2026 11:58:32 +0000 Subject: [PATCH 06/12] fix: replace `ureq` with `reqwest` inside async context --- Cargo.lock | 831 ++++++++++++++++++++++++++++++++------- Cargo.toml | 10 +- src/client/main.rs | 2 +- src/client/relay.rs | 2 +- src/server/conf_agent.rs | 99 +++-- src/utils/mod.rs | 19 +- 6 files changed, 753 insertions(+), 210 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c540a6..68d710c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "aead" version = "0.5.2" @@ -143,15 +137,37 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "aws-lc-rs" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "axum" -version = "0.7.9" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ - "async-trait", "axum-core", "bytes", + "form_urlencoded", "futures-util", "http", "http-body", @@ -164,14 +180,13 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", + "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", "sync_wrapper", "tokio", - "tower 0.5.3", + "tower", "tower-layer", "tower-service", "tracing", @@ -179,19 +194,17 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ - "async-trait", "bytes", - "futures-util", + "futures-core", "http", "http-body", "http-body-util", "mime", "pin-project-lite", - "rustversion", "sync_wrapper", "tower-layer", "tower-service", @@ -244,6 +257,12 @@ dependencies = [ "piper", ] +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + [[package]] name = "bytecodec" version = "0.5.0" @@ -293,9 +312,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" @@ -394,12 +421,31 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -415,6 +461,32 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -448,15 +520,6 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -562,6 +625,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ed25519" version = "2.2.3" @@ -587,6 +656,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -636,17 +714,6 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" -[[package]] -name = "filetime" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" -dependencies = [ - "cfg-if", - "libc", - "libredox", -] - [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -654,14 +721,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] -name = "flate2" -version = "1.1.9" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" @@ -678,6 +741,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -802,8 +871,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -813,9 +884,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 5.3.0", "wasip2", + "wasm-bindgen", ] [[package]] @@ -842,6 +915,25 @@ dependencies = [ "polyval", ] +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -927,6 +1019,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -936,6 +1029,23 @@ dependencies = [ "pin-utils", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", ] [[package]] @@ -944,13 +1054,23 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64", "bytes", + "futures-channel", + "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", + "socket2", + "system-configuration", "tokio", "tower-service", + "tracing", + "windows-registry", ] [[package]] @@ -1075,11 +1195,11 @@ dependencies = [ [[package]] name = "inotify" -version = "0.10.2" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" +checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.11.0", "inotify-sys", "libc", ] @@ -1102,21 +1222,22 @@ dependencies = [ "generic-array", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "ipnet" version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1129,6 +1250,48 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "kqueue" version = "1.1.1" @@ -1177,18 +1340,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "libredox" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" -dependencies = [ - "bitflags 2.11.0", - "libc", - "plain", - "redox_syscall 0.7.3", -] - [[package]] name = "litemap" version = "0.8.1" @@ -1210,6 +1361,12 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "matchers" version = "0.2.0" @@ -1221,9 +1378,9 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md5" @@ -1243,16 +1400,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - [[package]] name = "mio" version = "1.1.1" @@ -1291,12 +1438,11 @@ dependencies = [ [[package]] name = "notify" -version = "7.0.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ "bitflags 2.11.0", - "filetime", "fsevent-sys", "inotify", "kqueue", @@ -1305,16 +1451,16 @@ dependencies = [ "mio", "notify-types", "walkdir", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] name = "notify-types" -version = "1.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "instant", + "bitflags 2.11.0", ] [[package]] @@ -1350,6 +1496,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "parking" version = "2.2.1" @@ -1374,7 +1526,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] @@ -1418,12 +1570,6 @@ dependencies = [ "spki", ] -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - [[package]] name = "poly1305" version = "0.8.0" @@ -1490,6 +1636,62 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.45" @@ -1575,15 +1777,6 @@ dependencies = [ "bitflags 2.11.0", ] -[[package]] -name = "redox_syscall" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" -dependencies = [ - "bitflags 2.11.0", -] - [[package]] name = "regex-automata" version = "0.4.14" @@ -1601,6 +1794,44 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "ring" version = "0.17.14" @@ -1615,6 +1846,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -1630,30 +1867,70 @@ version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ - "log", + "aws-lc-rs", "once_cell", - "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ + "web-time", "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -1677,6 +1954,7 @@ dependencies = [ "notify", "once_cell", "rand 0.10.0", + "reqwest", "serde", "serde_json", "socket2", @@ -1686,12 +1964,11 @@ dependencies = [ "tokio-test", "tokio-util", "toml", - "tower 0.4.13", + "tower", "tower-http", "tracing", "tracing-subscriber", "tun", - "ureq", ] [[package]] @@ -1715,12 +1992,44 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.27" @@ -1858,12 +2167,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "simd-adler32" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" - [[package]] name = "slab" version = "0.4.12" @@ -1968,6 +2271,9 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -1980,13 +2286,54 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -2038,6 +2385,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.50.0" @@ -2066,6 +2428,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.18" @@ -2140,17 +2512,6 @@ version = "1.0.7+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tower" version = "0.5.3" @@ -2169,16 +2530,18 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.5.2" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags 2.11.0", "bytes", + "futures-util", "http", "http-body", - "http-body-util", + "iri-string", "pin-project-lite", + "tower", "tower-layer", "tower-service", ] @@ -2276,6 +2639,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "tun" version = "0.8.6" @@ -2290,7 +2659,7 @@ dependencies = [ "libc", "log", "nix 0.31.2", - "thiserror", + "thiserror 2.0.18", "tokio", "tokio-util", "windows-sys 0.61.2", @@ -2331,22 +2700,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "ureq" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" -dependencies = [ - "base64", - "flate2", - "log", - "once_cell", - "rustls", - "rustls-pki-types", - "url", - "webpki-roots 0.26.11", -] - [[package]] name = "url" version = "2.5.8" @@ -2393,6 +2746,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2417,6 +2779,65 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + [[package]] name = "wasm-encoder" version = "0.244.0" @@ -2452,19 +2873,30 @@ dependencies = [ ] [[package]] -name = "webpki-roots" -version = "0.26.11" +name = "web-sys" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ - "webpki-roots 1.0.6", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "webpki-roots" +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" dependencies = [ "rustls-pki-types", ] @@ -2484,6 +2916,44 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -2520,6 +2990,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -2553,6 +3038,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -2565,6 +3056,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -2577,6 +3074,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2601,6 +3104,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -2613,6 +3122,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -2625,6 +3140,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -2637,6 +3158,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -2682,7 +3209,7 @@ dependencies = [ "futures", "libloading", "log", - "thiserror", + "thiserror 2.0.18", "windows-sys 0.61.2", "winreg", ] diff --git a/Cargo.toml b/Cargo.toml index 15698a5..42c6876 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,16 +22,16 @@ chacha20poly1305 = "0.10" toml = "0.9" ipnet = "2" clap = { version = "4", features = ["derive"] } -ureq = "2" stunclient = "0.4" socket2 = "0.6" -notify = "7" +notify = "8" tokio-util = "0.7" ctrlc2 = "3" -axum = "0.7" -tower = "0.4" -tower-http = { version = "0.5", features = ["cors"] } +axum = "0.8" +tower = "0.5" +tower-http = { version = "0.6", features = ["cors"] } once_cell = "1" +reqwest = "0.13" [dev-dependencies] tokio-test = "0.4" diff --git a/src/client/main.rs b/src/client/main.rs index 4965513..b9807cd 100644 --- a/src/client/main.rs +++ b/src/client/main.rs @@ -33,7 +33,7 @@ pub async fn run_client() -> anyhow::Result<()> { let block = crypto::new_block(&crypto_config); let crypto_block: Arc> = Arc::new(block); - let ipv6 = utils::get_ipv6().unwrap_or_default(); + let ipv6 = utils::get_ipv6().await.unwrap_or_default(); let stun_result = StunClient::new().discover(P2P_HOLE_PUNCH_PORT).await; let stun = match stun_result { Ok(result) => Some(StunAddr { diff --git a/src/client/relay.rs b/src/client/relay.rs index 1acef69..6d9277f 100644 --- a/src/client/relay.rs +++ b/src/client/relay.rs @@ -85,7 +85,7 @@ impl RelayClient { // Periodic IPv6 address update check _ = ipv6_update_ticker.tick() => { tracing::debug!("ipv6 update tick"); - if let Some(new_ipv6) = utils::get_ipv6() { + if let Some(new_ipv6) = utils::get_ipv6().await { tracing::info!("IPv6 address updated: {} -> {}", current_ipv6, new_ipv6); current_ipv6 = new_ipv6.clone(); } else { diff --git a/src/server/conf_agent.rs b/src/server/conf_agent.rs index 960069d..fd1e185 100644 --- a/src/server/conf_agent.rs +++ b/src/server/conf_agent.rs @@ -1,11 +1,12 @@ -use std::collections::HashMap; +use crate::network::connection_manager::ConnectionManager; use crate::server::client_manager::{ClientConfig, ClientManager}; use crate::server::config::ConfAgentConfig; -use crate::network::connection_manager::ConnectionManager; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::sync::Arc; +use std::time::Instant; use tokio::fs; -use tokio::time::{interval, Duration}; +use tokio::time::{Duration, interval}; /// Connection update request for backend API #[derive(Serialize, Debug)] @@ -36,7 +37,6 @@ pub struct ConfAgent { routes_file: String, } - impl ConfAgent { pub fn new( config: ConfAgentConfig, @@ -152,26 +152,9 @@ impl ConfAgent { } /// Fetch routes from control plane API - async fn fetch_routes( - url: &str, - token: Option<&str>, - ) -> anyhow::Result> { - let mut request = ureq::get(url).timeout(Duration::from_secs(30)); - - if let Some(token) = token { - request = request.set("Authorization", &format!("Bearer {}", token)); - } - - let response = request.call()?; - - let status = response.status(); - let body = response.into_string()?; - - if status != 200 { - return Err(anyhow::anyhow!("Control plane returned error: {} - {}", status, body)); - } - - let routes: Vec = serde_json::from_str(&body)?; + async fn fetch_routes(url: &str, token: Option<&str>) -> anyhow::Result> { + let body = http_json(url, token, Input::Get).await?.unwrap(); + let routes: Vec = serde_json::from_value(body)?; // Convert to ClientConfig format let client_configs: Vec = routes @@ -209,32 +192,62 @@ impl ConfAgent { Ok(()) } - /// Send connection updates to control plane API async fn send_connection_updates( url: &str, token: Option<&str>, updates: &[ConnectionUpdateRequest], ) -> anyhow::Result<()> { - let json_data = serde_json::to_string(updates)?; - - let mut request = ureq::post(url) - .set("Content-Type", "application/json") - .timeout(Duration::from_secs(30)); - - if let Some(token) = token { - request = request.set("Authorization", &format!("Bearer {}", token)); - } - - let response = request.send_string(&json_data)?; - let status = response.status(); - let body = response.into_string()?; - - if status != 200 { - return Err(anyhow::anyhow!("Backend returned error: {} - {}", status, body)); - } - + let updates = serde_json::to_value(updates)?; + http_json(url, token, Input::Post(updates)).await?; Ok(()) } } +#[derive(Debug, Clone)] +enum Input { + Get, + Post(serde_json::Value), +} +async fn http_json( + url: &str, + token: Option<&str>, + input: Input, +) -> anyhow::Result> { + use tokio::time::timeout_at; + let method = match &input { + Input::Get => reqwest::Method::GET, + Input::Post(_) => reqwest::Method::POST, + }; + let mut request = reqwest::Request::new(method, url.parse()?); + if let Input::Post(input) = &input { + request + .headers_mut() + .insert("Content-Type", "application/json".try_into().unwrap()); + let body = reqwest::Body::wrap(input.to_string()); + *request.body_mut() = Some(body); + } + if let Some(token) = token { + request + .headers_mut() + .insert("Authorization", format!("Bearer {}", token).try_into()?); + } + let deadline = Instant::now() + Duration::from_secs(30); + let client = reqwest::Client::new(); + let response = timeout_at(deadline.into(), client.execute(request)).await??; + let status = response.status(); + let body = timeout_at(deadline.into(), response.text()).await; + if status != 200 { + return Err(anyhow::anyhow!( + "Control plane returned error: {} - {:?}", + status, + body + )); + } + let body = body??; + let body = match &input { + Input::Get => None, + Input::Post(_) => serde_json::from_str(&body)?, + }; + Ok(body) +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index c43a803..bf8af53 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,4 +1,7 @@ -use std::net::Ipv6Addr; +use std::{ + net::Ipv6Addr, + time::{Duration, Instant}, +}; use tracing::level_filters::LevelFilter; use tracing_subscriber::EnvFilter; @@ -41,7 +44,7 @@ pub fn init_tracing() -> Result<(), Box> { } /// Get public IPv6 address from external API -pub fn get_ipv6() -> Option { +pub async fn get_ipv6() -> Option { let apis = [ "https://api64.ipify.org", "https://ifconfig.co", @@ -49,7 +52,7 @@ pub fn get_ipv6() -> Option { ]; for api in &apis { - if let Ok(ipv6) = fetch_ipv6_from_url(api) { + if let Ok(ipv6) = fetch_ipv6_from_url(api).await { return Some(ipv6); } } @@ -57,11 +60,11 @@ pub fn get_ipv6() -> Option { None } -fn fetch_ipv6_from_url(url: &str) -> Result> { - let response = ureq::get(url) - .timeout(std::time::Duration::from_secs(5)) - .call()? - .into_string()?; +async fn fetch_ipv6_from_url(url: &str) -> anyhow::Result { + use tokio::time::timeout_at; + let deadline = Instant::now() + Duration::from_secs(5); + let get = timeout_at(deadline.into(), reqwest::get(url)).await??; + let response = timeout_at(deadline.into(), get.text()).await??; let ipv6_str = response.trim(); From c98ee1f7590b2045987b090b5340effb4f80812a Mon Sep 17 00:00:00 2001 From: Banyc <36535895+Banyc@users.noreply.github.com> Date: Sat, 21 Mar 2026 12:44:45 +0000 Subject: [PATCH 07/12] make IPv6 addresses strong types --- src/client/main.rs | 2 +- src/client/p2p/mod.rs | 9 ----- src/client/p2p/peer.rs | 78 ++++++++++++------------------------------ src/client/relay.rs | 30 +++++++++------- src/utils/mod.rs | 10 ++---- 5 files changed, 42 insertions(+), 87 deletions(-) diff --git a/src/client/main.rs b/src/client/main.rs index b9807cd..3751103 100644 --- a/src/client/main.rs +++ b/src/client/main.rs @@ -33,7 +33,7 @@ pub async fn run_client() -> anyhow::Result<()> { let block = crypto::new_block(&crypto_config); let crypto_block: Arc> = Arc::new(block); - let ipv6 = utils::get_ipv6().await.unwrap_or_default(); + let ipv6 = utils::get_ipv6().await; let stun_result = StunClient::new().discover(P2P_HOLE_PUNCH_PORT).await; let stun = match stun_result { Ok(result) => Some(StunAddr { diff --git a/src/client/p2p/mod.rs b/src/client/p2p/mod.rs index 3e5e08c..ec8f6ac 100644 --- a/src/client/p2p/mod.rs +++ b/src/client/p2p/mod.rs @@ -5,7 +5,6 @@ pub mod peer; pub mod stun; mod udp_server; - /// Buffer size for outbound/inbound channels (2KB) const OUTBOUND_BUFFER_SIZE: usize = 2048; @@ -33,14 +32,6 @@ struct PeerMeta { /// Traffic destined for these ranges will be routed to this peer ciders: Vec, - /// Public IPv6 address of the peer for P2P connection - #[allow(unused)] - ipv6: String, - - /// UDP port number for P2P communication - #[allow(unused)] - port: u16, - /// Resolved socket address combining IPv6 and port ([ipv6]:port) remote_addr: Option, diff --git a/src/client/p2p/peer.rs b/src/client/p2p/peer.rs index 3b0835e..36f0d19 100644 --- a/src/client/p2p/peer.rs +++ b/src/client/p2p/peer.rs @@ -7,7 +7,7 @@ use crate::codec::frame::{Frame, PeerDetail, ProbeHolePunchFrame, ProbeIPv6Frame use crate::codec::parser::Parser; use crate::crypto::Block; use std::collections::HashMap; -use std::net::SocketAddr; +use std::net::{IpAddr, SocketAddr}; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::{RwLock, mpsc}; @@ -95,22 +95,12 @@ impl PeerHandler { async fn add_peer(&self, p: PeerDetail) { let mut peers = self.peers.write().await; - let ipv6_remote = self.parse_address( - &p.identity, - &p.ipv6, - p.port, - true, // is_ipv6 - ); + let ipv6_remote = parse_address(&p.identity, &p.ipv6, p.port); if ipv6_remote.is_some() { tracing::info!("Added IPv6 peer: {} at {}:{}", p.identity, p.ipv6, p.port); } - let stun_remote = self.parse_address( - &p.identity, - &p.stun_ip, - p.stun_port, - false, // is_ipv4 - ); + let stun_remote = parse_address(&p.identity, &p.stun_ip, p.stun_port); if stun_remote.is_some() { tracing::info!( "Added Hole Punch peer: {} at {}:{}", @@ -128,8 +118,6 @@ impl PeerHandler { identity: p.identity.clone(), private_ip: p.private_ip.clone(), ciders: p.ciders.clone(), - ipv6: p.ipv6.clone(), - port: p.port, remote_addr: ipv6_remote, stun_addr: stun_remote, last_active: None, @@ -138,34 +126,6 @@ impl PeerHandler { ); } - fn parse_address( - &self, - identity: &str, - ip: &str, - port: u16, - is_ipv6: bool, - ) -> Option { - if ip.is_empty() { - return None; - } - - let addr_str = if is_ipv6 { - format!("[{}]:{}", ip, port) - } else { - format!("{}:{}", ip, port) - }; - - let addr = match addr_str.parse::() { - Ok(addr) => addr, - Err(e) => { - let protocol = if is_ipv6 { "IPv6" } else { "IPv4" }; - tracing::warn!("Invalid {} address for peer {}: {}", protocol, identity, e); - return None; - } - }; - Some(addr) - } - /// insert or update peers /// /// if peer exist, and the ipv6/stun_ip changed, @@ -190,12 +150,7 @@ impl PeerHandler { } } None => { - let ipv6_remote = self.parse_address( - &peer.identity, - &peer.ipv6, - peer.port, - true, // is_ipv6 - ); + let ipv6_remote = parse_address(&peer.identity, &peer.ipv6, peer.port); if ipv6_remote.is_some() { tracing::info!( "Added IPv6 peer: {} at {}:{}", @@ -205,12 +160,7 @@ impl PeerHandler { ); } - let stun_remote = self.parse_address( - &peer.identity, - &peer.stun_ip, - peer.stun_port, - false, // is_ipv4 - ); + let stun_remote = parse_address(&peer.identity, &peer.stun_ip, peer.stun_port); if stun_remote.is_some() { tracing::info!( "Added Hole Punch peer: {} at {}:{}", @@ -228,8 +178,6 @@ impl PeerHandler { identity: peer.identity.clone(), private_ip: peer.private_ip.clone(), ciders: peer.ciders.clone(), - ipv6: peer.ipv6.clone(), - port: peer.port, remote_addr: ipv6_remote, stun_addr: stun_remote, last_active: None, @@ -655,3 +603,19 @@ impl PeerHandler { } } } + +fn parse_address(identity: &str, ip: &str, port: u16) -> Option { + if ip.is_empty() { + return None; + } + let ip = match ip.parse::() { + Ok(ip) => ip, + Err(e) => { + tracing::warn!("Invalid address for peer {}: {}", identity, e); + return None; + } + }; + + let addr = SocketAddr::new(ip, port); + Some(addr) +} diff --git a/src/client/relay.rs b/src/client/relay.rs index 6d9277f..99862b5 100644 --- a/src/client/relay.rs +++ b/src/client/relay.rs @@ -5,6 +5,7 @@ use crate::codec::frame::{Frame, HandshakeFrame, HandshakeReplyFrame, KeepAliveF use crate::crypto::Block; use crate::network::{ConnManage, ConnectionConfig, TCPConnectionConfig, create_connection}; use crate::utils::{self, StunAddr}; +use std::net::{Ipv6Addr, SocketAddr}; use std::ops::ControlFlow; use std::sync::Arc; use std::time::Instant; @@ -21,7 +22,7 @@ pub struct RelayClientConfig { pub outbound_buffer_size: usize, pub keep_alive_thresh: u8, pub identity: String, - pub ipv6: String, + pub ipv6: Option, pub port: u16, pub stun: Option, } @@ -56,8 +57,7 @@ impl RelayClient { let mut ipv6_update_ticker = interval(Duration::from_secs(300)); ipv6_update_ticker.tick().await; // Skip first immediate tick - let mut current_ipv6 = self.cfg.ipv6.clone(); - let port = self.cfg.port; + let mut current_ipv6: Option = self.cfg.ipv6; let stun = self.cfg.stun.clone(); let mut last_active = Instant::now(); @@ -70,8 +70,7 @@ impl RelayClient { .keep_alive( &mut conn, &mut keepalive_wait, - ¤t_ipv6, - port, + current_ipv6.map(|ipv6| SocketAddr::new(ipv6.into(), self.cfg.port)), stun.as_ref(), last_active, timeout_secs, @@ -86,8 +85,12 @@ impl RelayClient { _ = ipv6_update_ticker.tick() => { tracing::debug!("ipv6 update tick"); if let Some(new_ipv6) = utils::get_ipv6().await { - tracing::info!("IPv6 address updated: {} -> {}", current_ipv6, new_ipv6); - current_ipv6 = new_ipv6.clone(); + let curr_display = match current_ipv6 { + None => "None".to_string(), + Some(ipv6) => ipv6.to_string(), + }; + tracing::info!("IPv6 address updated: {curr_display} -> {new_ipv6}"); + current_ipv6 = Some(new_ipv6); } else { tracing::debug!("Failed to retrieve IPv6 address during update check"); } @@ -164,8 +167,7 @@ impl RelayClient { &mut self, conn: &mut Box, keepalive_wait: &mut u8, - current_ipv6: &str, - port: u16, + current_ipv6: Option, stun: Option<&StunAddr>, last_active: Instant, timeout_secs: u64, @@ -178,8 +180,10 @@ impl RelayClient { let keepalive_frame = Frame::KeepAlive(KeepAliveFrame { name: "".to_string(), identity: self.cfg.identity.clone(), - ipv6: current_ipv6.to_string(), - port, + ipv6: current_ipv6 + .map(|ipv6| ipv6.ip().to_string()) + .unwrap_or_default(), + port: current_ipv6.map(|ipv6| ipv6.port()).unwrap_or_default(), #[allow(clippy::unwrap_or_default)] stun_ip: stun .as_ref() @@ -280,7 +284,7 @@ impl RelayHandler { mask: reply.mask.clone(), gateway: reply.gateway.clone(), ciders: reply.ciders.clone(), - ipv6: cfg.ipv6.clone(), + ipv6: cfg.ipv6.map(|ipv6| ipv6.to_string()).unwrap_or_default(), port: cfg.port, stun_ip: cfg .stun @@ -397,7 +401,7 @@ async fn run_client_session( pub async fn new_relay_handler( args: &Args, block: Arc>, - ipv6: String, + ipv6: Option, port: u16, stun: Option, ) -> anyhow::Result<(RelayHandler, HandshakeReplyFrame)> { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index bf8af53..152048e 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -44,7 +44,7 @@ pub fn init_tracing() -> Result<(), Box> { } /// Get public IPv6 address from external API -pub async fn get_ipv6() -> Option { +pub async fn get_ipv6() -> Option { let apis = [ "https://api64.ipify.org", "https://ifconfig.co", @@ -60,16 +60,12 @@ pub async fn get_ipv6() -> Option { None } -async fn fetch_ipv6_from_url(url: &str) -> anyhow::Result { +async fn fetch_ipv6_from_url(url: &str) -> anyhow::Result { use tokio::time::timeout_at; let deadline = Instant::now() + Duration::from_secs(5); let get = timeout_at(deadline.into(), reqwest::get(url)).await??; let response = timeout_at(deadline.into(), get.text()).await??; let ipv6_str = response.trim(); - - // Validate it's a proper IPv6 address - ipv6_str.parse::()?; - - Ok(ipv6_str.to_string()) + Ok(ipv6_str.parse::()?) } From 8cfb8d085c1fac9a46c0a1034f69efd8f9dca055 Mon Sep 17 00:00:00 2001 From: Banyc <36535895+Banyc@users.noreply.github.com> Date: Sat, 21 Mar 2026 13:28:31 +0000 Subject: [PATCH 08/12] remove async locks --- src/client/http/cache.rs | 13 +- src/client/http/handlers.rs | 19 +- src/client/http/server.rs | 13 +- src/client/main.rs | 105 +++-- src/client/p2p/mod.rs | 39 +- src/client/p2p/peer.rs | 765 ++++++++++++++++++----------------- src/client/p2p/udp_server.rs | 40 +- src/client/prettylog.rs | 29 +- src/client/relay.rs | 7 +- 9 files changed, 547 insertions(+), 483 deletions(-) diff --git a/src/client/http/cache.rs b/src/client/http/cache.rs index 97ac43f..cdc4d59 100644 --- a/src/client/http/cache.rs +++ b/src/client/http/cache.rs @@ -1,8 +1,8 @@ //! Status cache management -use std::sync::Arc; -use tokio::sync::RwLock; use super::models::StatusResponse; +use std::sync::Arc; +use std::sync::RwLock; /// Global status cache (shared between HTTP server and event loop) static STATUS_CACHE: once_cell::sync::Lazy>>> = @@ -14,14 +14,13 @@ pub fn get_cache() -> Arc>> { } /// Update the status cache (called from event loop) -pub async fn update(status: StatusResponse) { - let mut cache = STATUS_CACHE.write().await; +pub fn update(status: StatusResponse) { + let mut cache = STATUS_CACHE.write().unwrap(); *cache = Some(status); } /// Get the current cached status -pub async fn get() -> Option { - let cache = STATUS_CACHE.read().await; +pub fn get() -> Option { + let cache = STATUS_CACHE.read().unwrap(); cache.clone() } - diff --git a/src/client/http/handlers.rs b/src/client/http/handlers.rs index afd4e34..3783008 100644 --- a/src/client/http/handlers.rs +++ b/src/client/http/handlers.rs @@ -1,18 +1,14 @@ //! HTTP request handlers -use axum::{ - extract::State, - http::StatusCode, - response::Json, -}; -use serde_json; -use super::models::StatusResponse; use super::cache::get_cache; +use super::models::StatusResponse; +use axum::{extract::State, http::StatusCode, response::Json}; +use serde_json; /// Shared state for the HTTP server #[derive(Clone)] pub struct AppState { - status_cache: std::sync::Arc>>, + status_cache: std::sync::Arc>>, } impl AppState { @@ -32,13 +28,10 @@ pub async fn health() -> Json { } /// Status endpoint handler -pub async fn status( - State(state): State, -) -> Result, StatusCode> { - let cache = state.status_cache.read().await; +pub async fn status(State(state): State) -> Result, StatusCode> { + let cache = state.status_cache.read().unwrap(); match cache.as_ref() { Some(status) => Ok(Json(status.clone())), None => Err(StatusCode::SERVICE_UNAVAILABLE), } } - diff --git a/src/client/http/server.rs b/src/client/http/server.rs index f71b82f..2e395b0 100644 --- a/src/client/http/server.rs +++ b/src/client/http/server.rs @@ -1,10 +1,7 @@ //! HTTP server setup and management -use axum::{ - routing::get, - Router, -}; use super::handlers::{AppState, health, status}; +use axum::{Router, routing::get}; /// Start the HTTP server pub async fn start(port: u16) -> Result<(), Box> { @@ -16,9 +13,11 @@ pub async fn start(port: u16) -> Result<(), Box anyhow::Result<()> { @@ -56,13 +56,12 @@ pub async fn run_client() -> anyhow::Result<()> { let p2p_handler = if args.enable_p2p { tracing::info!("P2P mode enabled"); - let mut handler = PeerHandler::new(crypto_block.clone(), args.identity.clone()); - handler.run_peer_service(); - handler - .rewrite_peers(device_config.peer_details.clone()) - .await; - handler.start_probe_timer().await; - Some(Arc::new(RwLock::new(handler))) + let handler = PeerHandler::start_peer_service( + crypto_block.clone(), + args.identity.clone(), + device_config.peer_details.clone(), + ); + Some(handler) } else { tracing::info!("P2P mode disabled, using relay only"); None @@ -134,9 +133,23 @@ async fn init_device( async fn run_event_loop( client_handler: &mut RelayHandler, - p2p_handler: Option>>, + p2p_handler: Option, dev: &mut DeviceHandler, ) { + let ( + p2p_handler_new_peers, + mut p2p_handler_recv_frame, + p2p_handler_get_status, + p2p_handler_send_frame, + ) = match p2p_handler { + Some(p) => ( + Some(p.new_peers), + Some(p.new_frame), + Some(p.get_status), + Some(p.send_frame), + ), + None => (None, None, None, None), + }; let mut refresh_ticker = interval(Duration::from_secs(30)); let relay_outbound = match client_handler.get_outbound_tx() { Some(tx) => tx, @@ -148,13 +161,16 @@ async fn run_event_loop( None => return, }; - let p2p_for_spawn = p2p_handler.clone(); - tokio::spawn(async move { loop { let packet = dev_inbound.recv().await; if let Some(packet) = packet { - handle_device_packet(relay_outbound.clone(), p2p_for_spawn.clone(), packet).await; + handle_device_packet( + relay_outbound.clone(), + p2p_handler_send_frame.as_ref(), + packet, + ) + .await; } } }); @@ -164,38 +180,39 @@ async fn run_event_loop( // Server -> TUN device or route update frame = client_handler.recv_frame() => { if let Ok(frame) = frame { - handle_relay_frame(frame, &p2p_handler, dev).await; + handle_relay_frame(frame, p2p_handler_new_peers.as_ref(), dev).await; } } // P2P -> TUN device (only if P2P enabled) - frame = async { - match p2p_handler.as_ref() { - Some(arc) => { - let arc = arc.clone(); - let mut guard = arc.write().await; - guard.recv_frame().await - } + Some(frame) = async { + match p2p_handler_recv_frame.as_mut() { + Some(rx) => rx.0.recv().await, None => std::future::pending().await, // Never resolves if no P2P } } => { - if let Ok(Frame::Data(data_frame)) = frame { - tracing::debug!("P2P -> Device: {} bytes", data_frame.payload.len()); - if let Err(e) = dev.send(data_frame.payload).await { - tracing::error!("Failed to write to device: {}", e); - } + let Frame::Data(data_frame) = frame else { + continue; + }; + tracing::debug!("P2P -> Device: {} bytes", data_frame.payload.len()); + if let Err(e) = dev.send(data_frame.payload).await { + tracing::error!("Failed to write to device: {}", e); } } // refresh config and status _ = refresh_ticker.tick() => { - match &p2p_handler { - Some(arc) => { - let guard = arc.read().await; - get_status(client_handler, Some(&*guard), dev).await; - } - None => get_status(client_handler, None, dev).await, - } + let peer_status = match p2p_handler_get_status.as_ref() { + None => None, + Some(p) => match p.get().await { + Ok(s) => Some(s), + Err(e) => { + tracing::error!("failed to get peer status: {e}"); + None + } + }, + }; + get_status(client_handler, peer_status.as_deref(), dev).await; } } } @@ -204,7 +221,7 @@ async fn run_event_loop( /// Handle outbound packet from TUN device: try P2P first if available, then fallback to relay. async fn handle_device_packet( relay_outbound: mpsc::Sender, - p2p_handler: Option>>, + p2p_handler: Option<&SendFrameTx>, packet: Vec, ) { let data_frame = DataFrame { @@ -212,13 +229,14 @@ async fn handle_device_packet( }; // Try P2P first if available - if let Some(arc) = &p2p_handler { + if let Some(tx) = &p2p_handler { let dst = data_frame.dst(); - let guard = arc.read().await; - match guard - .send_frame(Frame::Data(data_frame.clone()), dst.as_str()) - .await - { + let frame = SendFrame { + frame: Frame::Data(data_frame.clone()), + dst, + }; + + match tx.0.send(frame).await { Ok(_) => { tracing::debug!("Device -> P2P: {} bytes", packet.len()); return; @@ -239,7 +257,7 @@ async fn handle_device_packet( /// Handle frame received from relay server async fn handle_relay_frame( frame: Frame, - p2p_handler: &Option>>, + p2p_handler: Option<&NewPeersTx>, dev: &mut DeviceHandler, ) { match frame { @@ -259,9 +277,8 @@ async fn handle_relay_frame( dev.reload_route(keepalive.peer_details.clone()).await; // Update P2P peer information if P2P is enabled - if let Some(arc) = p2p_handler { - let mut guard = arc.write().await; - guard.insert_or_update(keepalive.peer_details).await; + if let Some(tx) = p2p_handler { + let _ = tx.0.send(keepalive.peer_details).await; } } _ => {} diff --git a/src/client/p2p/mod.rs b/src/client/p2p/mod.rs index ec8f6ac..7739418 100644 --- a/src/client/p2p/mod.rs +++ b/src/client/p2p/mod.rs @@ -19,6 +19,7 @@ const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(10); /// the connection is considered invalid and data sending will be rejected. const CONNECTION_TIMEOUT: Duration = Duration::from_secs(15); +#[derive(Debug)] struct PeerMeta { name: String, /// Unique identifier of the peer (e.g., client name) @@ -33,21 +34,37 @@ struct PeerMeta { ciders: Vec, /// Resolved socket address combining IPv6 and port ([ipv6]:port) - remote_addr: Option, + remote_addr: LastActive>, /// Stun socket address - stun_addr: Option, + stun_addr: LastActive>, +} - /// Timestamp of last received packet from this peer - /// - /// - `None`: Never received any response (connection not established) - /// - `Some(instant)`: Last successful communication time - /// - /// This is used to validate connection health before sending data. +#[derive(Debug, Clone)] +struct LastActive { + value: T, last_active: Option, - - /// last_hole_punch_active - stun_last_active: Option, +} +impl LastActive { + pub fn dormant(value: T) -> Self { + Self { + value, + last_active: None, + } + } + pub fn restart(&mut self) { + self.last_active = Some(Instant::now()); + } + pub fn activate(&mut self, value: T) { + self.value = value; + self.last_active = Some(Instant::now()); + } + pub fn get(&self) -> &T { + &self.value + } + pub fn last_active(&self) -> Option { + self.last_active + } } #[derive(Debug)] diff --git a/src/client/p2p/peer.rs b/src/client/p2p/peer.rs index 36f0d19..c15e0b5 100644 --- a/src/client/p2p/peer.rs +++ b/src/client/p2p/peer.rs @@ -1,6 +1,6 @@ use crate::client::p2p::udp_server::UDPServer; use crate::client::p2p::{ - CONNECTION_TIMEOUT, KEEPALIVE_INTERVAL, OUTBOUND_BUFFER_SIZE, PeerMeta, PeerStatus, + CONNECTION_TIMEOUT, KEEPALIVE_INTERVAL, LastActive, OUTBOUND_BUFFER_SIZE, PeerMeta, PeerStatus, }; use crate::client::{P2P_HOLE_PUNCH_PORT, P2P_UDP_PORT}; use crate::codec::frame::{Frame, PeerDetail, ProbeHolePunchFrame, ProbeIPv6Frame}; @@ -10,120 +10,129 @@ use std::collections::HashMap; use std::net::{IpAddr, SocketAddr}; use std::sync::Arc; use std::time::{Duration, Instant}; -use tokio::sync::{RwLock, mpsc}; +use tokio::sync::{mpsc, oneshot}; -pub struct PeerHandler { - /// Map of peer identity to peer metadata (shared with keepalive task) - /// - /// Wrapped in Arc> to allow dynamic peer updates while - /// keepalive timer is running. - peers: Arc>>, - - /// Channel sender to PeerService for outbound packets - /// - /// Initialized when `run_peer()` is called. None before initialization. - outbound_tx: Option, SocketAddr)>>, - - /// Channel receiver for inbound packets from PeerService - inbound_rx: Option, SocketAddr)>>, - - /// Encryption/decryption block for frame marshaling - block: Arc>, - - /// Local peer identity - identity: String, - - /// Local peer UDP port - port: u16, +pub struct PeerHandlerApi { + pub new_peers: NewPeersTx, + pub new_frame: NewFrameRx, + pub send_frame: SendFrameTx, + pub get_status: GetStatusTx, +} - // Hole punch UDP Port - stun_port: u16, +struct PeerHandlerPrivateRxApi { + pub new_peers: NewPeersRx, + pub send_frame: SendFrameRx, + pub get_status: GetStatusRx, + pub inbound_rx: mpsc::Receiver<(Vec, SocketAddr)>, +} +struct PeerHandlerPrivateTxApi { + pub new_frame: NewFrameTx, + pub outbound_tx: mpsc::Sender<(Vec, Vec)>, } -/// Result of attempting to send data via a specific address -enum SendResult { - Success, - Expired(Duration), - NeverResponded, - NoAddress, +#[derive(Debug)] +pub struct NewPeersTx(pub mpsc::Sender>); +#[derive(Debug)] +pub struct NewPeersRx(mpsc::Receiver>); +#[derive(Debug)] +pub struct NewFrameTx(mpsc::Sender); +#[derive(Debug)] +pub struct NewFrameRx(pub mpsc::Receiver); +#[derive(Debug)] +pub struct SendFrameTx(pub mpsc::Sender); +#[derive(Debug)] +pub struct SendFrameRx(mpsc::Receiver); +#[derive(Debug)] +pub struct SendFrame { + pub frame: Frame, + pub dst: String, } +#[derive(Debug)] +pub struct GetStatusTx(mpsc::Sender>>); +impl GetStatusTx { + pub async fn get(&self) -> anyhow::Result> { + let (a, b) = oneshot::channel::>(); + self.0.send(a).await?; + Ok(b.await?) + } +} +#[derive(Debug)] +pub struct GetStatusRx(mpsc::Receiver>>); -impl PeerHandler { - pub fn new(block: Arc>, identity: String) -> Self { +#[derive(Debug)] +struct PeerSet { + peers: HashMap, +} +impl PeerSet { + pub fn new() -> Self { Self { - peers: Arc::new(RwLock::new(HashMap::new())), - outbound_tx: None, - inbound_rx: None, - block, - identity, - port: P2P_UDP_PORT, - stun_port: P2P_HOLE_PUNCH_PORT, + peers: HashMap::new(), } } - /// run peer service listen udp socket for p2p - pub fn run_peer_service(&mut self) { - let (output_tx, output_rx) = mpsc::channel(OUTBOUND_BUFFER_SIZE); - let (inbound_tx, inbound_rx) = mpsc::channel(OUTBOUND_BUFFER_SIZE); - let mut udp_server = UDPServer::new(self.port, self.stun_port, inbound_tx, output_rx); - - tokio::spawn(async move { - if let Err(e) = udp_server.serve().await { - tracing::error!("PeerService error: {}", e); - } - }); - - self.outbound_tx = Some(output_tx); - self.inbound_rx = Some(inbound_rx); - tracing::info!("Running p2p peer service"); + pub fn all_peer_addrs(&self, protocol: Protocol) -> Vec { + self.peers + .values() + .filter_map(|p| match protocol { + Protocol::Ipv6 => *p.remote_addr.get(), + Protocol::Stun => *p.stun_addr.get(), + }) + .collect() } - /// rewrite peers with new peers details for route - /// - /// this function will update peer's ipv6 and stun address - /// - pub async fn rewrite_peers(&mut self, peer_details: Vec) { - { - let mut peers = self.peers.write().await; - *peers = HashMap::new(); + pub fn update_peer_active(&mut self, identity: &str, addr: SocketAddr, protocol: Protocol) { + let Some(peer) = self.peers.get_mut(identity) else { + return; + }; + match protocol { + Protocol::Stun => peer.stun_addr.activate(Some(addr)), + Protocol::Ipv6 => peer.remote_addr.activate(Some(addr)), } - - for p in peer_details { - self.add_peer(p).await; + } + pub fn update_peer_active_by_addr(&mut self, remote_addr: SocketAddr) { + for peer in self.peers.values_mut() { + // Check if this is from IPv6 address + if *peer.remote_addr.get() == Some(remote_addr) { + peer.remote_addr.restart(); + tracing::debug!("Updated IPv6 last_active for peer: {}", peer.identity); + return; + } + // Check if this is from STUN address + if *peer.stun_addr.get() == Some(remote_addr) { + peer.stun_addr.restart(); + tracing::debug!("Updated STUN last_active for peer: {}", peer.identity); + return; + } } + tracing::warn!("Received packet from unknown peer address: {}", remote_addr); } - async fn add_peer(&self, p: PeerDetail) { - let mut peers = self.peers.write().await; - let ipv6_remote = parse_address(&p.identity, &p.ipv6, p.port); - if ipv6_remote.is_some() { - tracing::info!("Added IPv6 peer: {} at {}:{}", p.identity, p.ipv6, p.port); - } + pub fn find_peer_by_ip_locked<'a>(&'a self, dest_ip: &str) -> Option<&'a PeerMeta> { + use ipnet::IpNet; + use std::net::IpAddr; - let stun_remote = parse_address(&p.identity, &p.stun_ip, p.stun_port); - if stun_remote.is_some() { - tracing::info!( - "Added Hole Punch peer: {} at {}:{}", - p.identity, - p.ipv6, - p.port - ); + let dest_ip_addr = match dest_ip.parse::() { + Ok(ip) => ip, + Err(_) => return None, + }; + + for peer in self.peers.values() { + // Check exact match with peer's private IP + if peer.private_ip == dest_ip { + return Some(peer); + } + + // Check if destination falls within peer's CIDR ranges + for cidr in &peer.ciders { + if let Ok(network) = cidr.parse::() + && network.contains(&dest_ip_addr) + { + return Some(peer); + } + } } - // Add or update peer in the map - peers.insert( - p.identity.clone(), - PeerMeta { - name: p.name.clone(), - identity: p.identity.clone(), - private_ip: p.private_ip.clone(), - ciders: p.ciders.clone(), - remote_addr: ipv6_remote, - stun_addr: stun_remote, - last_active: None, - stun_last_active: None, - }, - ); + None } /// insert or update peers @@ -134,19 +143,22 @@ impl PeerHandler { /// /// if peer not exist, add it. /// - pub async fn insert_or_update(&mut self, peer_details: Vec) { - let mut peers = self.peers.write().await; + pub fn insert_or_update_dormant(&mut self, peer_details: Vec) { + let peers = &mut self.peers; for peer in peer_details { match peers.get_mut(&peer.identity) { Some(existing_peer) => { - if !peer.ipv6.is_empty() { - // update ipv6 if changed - self.update_address(existing_peer, &peer.ipv6, peer.port, true); + if !peer.ipv6.is_empty() + && let Some(addr) = parse_address(&peer.identity, &peer.ipv6, peer.port) + { + update_address(existing_peer, addr, Protocol::Ipv6); } - if !peer.stun_ip.is_empty() { - // update stun_ip if changed - self.update_address(existing_peer, &peer.stun_ip, peer.stun_port, false); + if !peer.stun_ip.is_empty() + && let Some(addr) = + parse_address(&peer.identity, &peer.stun_ip, peer.stun_port) + { + update_address(existing_peer, addr, Protocol::Stun); } } None => { @@ -178,10 +190,8 @@ impl PeerHandler { identity: peer.identity.clone(), private_ip: peer.private_ip.clone(), ciders: peer.ciders.clone(), - remote_addr: ipv6_remote, - stun_addr: stun_remote, - last_active: None, - stun_last_active: None, + remote_addr: LastActive::dormant(ipv6_remote), + stun_addr: LastActive::dormant(stun_remote), }, ); } @@ -189,55 +199,168 @@ impl PeerHandler { } } - fn update_address(&self, peer: &mut PeerMeta, ip: &str, port: u16, is_ipv6: bool) { - // Format and parse address - let addr_str = if is_ipv6 { - format!("[{}]:{}", ip, port) - } else { - format!("{}:{}", ip, port) - }; + pub fn add_peer(&mut self, p: PeerDetail) { + let ipv6_remote = parse_address(&p.identity, &p.ipv6, p.port); + if ipv6_remote.is_some() { + tracing::info!("Added IPv6 peer: {} at {}:{}", p.identity, p.ipv6, p.port); + } - let new_addr = match addr_str.parse::() { - Ok(addr) => addr, - Err(e) => { - let protocol = if is_ipv6 { "IPv6" } else { "STUN" }; - tracing::warn!( - "Invalid new {} address for peer {}: {}", - protocol, - peer.identity, - e - ); - return; + let stun_remote = parse_address(&p.identity, &p.stun_ip, p.stun_port); + if stun_remote.is_some() { + tracing::info!( + "Added Hole Punch peer: {} at {}:{}", + p.identity, + p.ipv6, + p.port + ); + } + + // Add or update peer in the map + let peers = &mut self.peers; + peers.insert( + p.identity.clone(), + PeerMeta { + name: p.name.clone(), + identity: p.identity.clone(), + private_ip: p.private_ip.clone(), + ciders: p.ciders.clone(), + remote_addr: LastActive::dormant(ipv6_remote), + stun_addr: LastActive::dormant(stun_remote), + }, + ); + } + + pub fn get_status(&self) -> Vec { + let mut result: Vec = Vec::new(); + for peer in self.peers.values() { + let status = PeerStatus { + name: peer.name.clone(), + identity: peer.identity.clone(), + ipv6_addr: *peer.remote_addr.get(), + ipv6_last_active: peer.remote_addr.last_active(), + stun_addr: *peer.stun_addr.get(), + stun_last_active: peer.stun_addr.last_active(), + }; + result.push(status); + } + result + } +} + +pub struct PeerHandler { + peers: PeerSet, + block: Arc>, + identity: String, + tx_api: PeerHandlerPrivateTxApi, +} + +/// Result of attempting to send data via a specific address +enum SendResult { + Success, + Expired(Duration), + NeverResponded, + NoAddress, +} + +impl PeerHandler { + /// run peer service listen udp socket for p2p + pub fn start_peer_service( + block: Arc>, + identity: String, + peer_details: Vec, + ) -> PeerHandlerApi { + let (outbound_tx, output_rx) = mpsc::channel(OUTBOUND_BUFFER_SIZE); + let (inbound_tx, inbound_rx) = mpsc::channel(OUTBOUND_BUFFER_SIZE); + let mut udp_server = + UDPServer::new(P2P_UDP_PORT, P2P_HOLE_PUNCH_PORT, inbound_tx, output_rx); + tokio::spawn(async move { + if let Err(e) = udp_server.serve().await { + tracing::error!("PeerService error: {}", e); } + }); + let (new_pears_tx, new_pears_rx) = mpsc::channel(1024); + let (new_frame_tx, new_frame_rx) = mpsc::channel(1024); + let (send_frame_tx, send_frame_rx) = mpsc::channel(1024); + let (get_status_tx, get_status_rx) = mpsc::channel(1024); + let private_rx_api = PeerHandlerPrivateRxApi { + new_peers: NewPeersRx(new_pears_rx), + send_frame: SendFrameRx(send_frame_rx), + get_status: GetStatusRx(get_status_rx), + inbound_rx, }; - - let (old_addr, protocol) = if is_ipv6 { - (peer.remote_addr, "IPv6") - } else { - (peer.stun_addr, "STUN") + let mut this = Self { + peers: PeerSet::new(), + block, + identity, + tx_api: PeerHandlerPrivateTxApi { + new_frame: NewFrameTx(new_frame_tx), + outbound_tx, + }, }; + this.rewrite_peers(peer_details); + tokio::spawn(async move { + if let Err(e) = this.run_peer_service(private_rx_api).await { + tracing::error!("peer service failed: {e}"); + } + }); + tracing::info!("Running p2p peer service"); + PeerHandlerApi { + new_peers: NewPeersTx(new_pears_tx), + new_frame: NewFrameRx(new_frame_rx), + send_frame: SendFrameTx(send_frame_tx), + get_status: GetStatusTx(get_status_tx), + } + } + async fn run_peer_service(mut self, rx_api: PeerHandlerPrivateRxApi) -> anyhow::Result<()> { + let mut send_probes_interval = tokio::time::interval(KEEPALIVE_INTERVAL); + let PeerHandlerPrivateRxApi { + mut new_peers, + mut send_frame, + mut get_status, + mut inbound_rx, + } = rx_api; - if old_addr != Some(new_addr) { - tracing::info!( - "Update {} address for peer {}: {} -> {}", - protocol, - peer.identity, - old_addr - .map(|a| a.to_string()) - .unwrap_or_else(|| "None".to_string()), - new_addr - ); - - if is_ipv6 { - peer.remote_addr = Some(new_addr); - peer.last_active = None; - } else { - peer.stun_addr = Some(new_addr); - peer.stun_last_active = None; + loop { + tokio::select! { + _ = send_probes_interval.tick() => { + self.send_probes().await; + } + Some(peer_details) = new_peers.0.recv() => { + self.insert_or_update(peer_details); + } + Some(sf) = send_frame.0.recv() => { + if let Err(e) = self.send_frame(sf.frame, &sf.dst).await { + tracing::warn!("send_frame failed: {}", e); + } + } + Some(reply_tx) = get_status.0.recv() => { + let _ = reply_tx.send(self.get_status()); + } + opt = inbound_rx.recv() => { + let opt = opt.ok_or(anyhow::anyhow!("recv from peers channel closed"))?; + if let Err(e) = self.recv_frame(opt).await { + tracing::warn!("recv_frame failed: {e}"); + }; + } } } } + /// rewrite peers with new peers details for route + /// + /// this function will update peer's ipv6 and stun address + /// + fn rewrite_peers(&mut self, peer_details: Vec) { + self.peers = PeerSet::new(); + for p in peer_details { + self.peers.add_peer(p); + } + } + + fn insert_or_update(&mut self, peer_details: Vec) { + self.peers.insert_or_update_dormant(peer_details); + } + /// recv_frame to recv from local p2p socket to get peers frame /// /// only support for ProbeIPv6, ProbeStun, Data @@ -245,52 +368,36 @@ impl PeerHandler { /// **ProbeIPv6** /// - update last_active, this is for p2p send_frame healthy checker /// - remote address, most of the time this is not changed. - pub async fn recv_frame(&mut self) -> anyhow::Result { - let inbound_rx = self - .inbound_rx - .as_mut() - .ok_or(anyhow::anyhow!("inbound_rx not initialized"))?; + async fn recv_frame(&mut self, msg: (Vec, SocketAddr)) -> anyhow::Result<()> { + let (buf, remote) = msg; - loop { - let (buf, remote) = inbound_rx - .recv() - .await - .ok_or(anyhow::anyhow!("recv from peers channel closed"))?; - - let (frame, _) = Parser::unmarshal(&buf, self.block.as_ref().as_ref())?; - - match frame { - Frame::ProbeIPv6(probe) => { - tracing::info!( - "Received probe ipv6 from peer {} at {}", - probe.identity, - remote - ); + let (frame, _) = Parser::unmarshal(&buf, self.block.as_ref().as_ref())?; - let mut peers = self.peers.write().await; - if let Some(peer) = peers.get_mut(&probe.identity) { - peer.remote_addr = Some(remote); - peer.last_active = Some(Instant::now()); - } - } - Frame::ProbeHolePunch(probe) => { - tracing::info!( - "Received probe hole punch from peer {} at {}", - probe.identity, - remote - ); - let mut peers = self.peers.write().await; - if let Some(peer) = peers.get_mut(&probe.identity) { - peer.stun_addr = Some(remote); - peer.stun_last_active = Some(Instant::now()); - } - } - _ => { - self.update_peer_active(remote).await; - return Ok(frame); - } + match frame { + Frame::ProbeIPv6(probe) => { + tracing::info!( + "Received probe ipv6 from peer {} at {}", + probe.identity, + remote + ); + self.peers + .update_peer_active(&probe.identity, remote, Protocol::Ipv6); + } + Frame::ProbeHolePunch(probe) => { + tracing::info!( + "Received probe hole punch from peer {} at {}", + probe.identity, + remote + ); + self.peers + .update_peer_active(&probe.identity, remote, Protocol::Stun); + } + _ => { + self.peers.update_peer_active_by_addr(remote); + let _ = self.tx_api.new_frame.0.send(frame).await; } } + Ok(()) } /// send_frame tries to get peers that contains dest_ip in ciders or private_ip @@ -299,41 +406,31 @@ impl PeerHandler { /// /// secondary try p2p hole punch, if peers is healthy(base on stun_last_active) /// - pub async fn send_frame(&self, frame: Frame, dest_ip: &str) -> anyhow::Result<()> { - let peers = self.peers.read().await; + async fn send_frame(&self, frame: Frame, dest_ip: &str) -> anyhow::Result<()> { let peer = self - .find_peer_by_ip_locked(&peers, dest_ip) + .peers + .find_peer_by_ip_locked(dest_ip) .ok_or_else(|| anyhow::anyhow!("No peer found for destination"))?; - if peer.remote_addr.is_none() && peer.stun_addr.is_none() { + if peer.remote_addr.get().is_none() && peer.stun_addr.get().is_none() { return Err(anyhow::anyhow!( "Peer {} has no available address (IPv6 or STUN)", peer.identity )); } - let peer_identity = peer.identity.clone(); - let remote_addr = peer.remote_addr; - let stun_addr = peer.stun_addr; - let ipv6_last_active = peer.last_active; - let stun_last_active = peer.stun_last_active; - - drop(peers); // Marshal frame once for potential multiple attempts let data = Parser::marshal(frame, self.block.as_ref().as_ref())?; - let outbound_tx = self - .outbound_tx - .as_ref() - .ok_or(anyhow::anyhow!("outbound_tx not initialized"))?; + let outbound_tx = &self.tx_api.outbound_tx; // Attempt 1: Try IPv6 direct connection match self .try_send_via( outbound_tx, &data, - remote_addr, - ipv6_last_active, + *peer.remote_addr.get(), + peer.remote_addr.last_active(), &peer_identity, "IPv6", ) @@ -360,8 +457,8 @@ impl PeerHandler { .try_send_via( outbound_tx, &data, - stun_addr, - stun_last_active, + *peer.stun_addr.get(), + peer.stun_addr.last_active(), &peer_identity, "STUN", ) @@ -386,7 +483,7 @@ impl PeerHandler { async fn try_send_via( &self, - outbound_tx: &mpsc::Sender<(Vec, SocketAddr)>, + outbound_tx: &mpsc::Sender<(Vec, Vec)>, data: &[u8], addr: Option, last_active: Option, @@ -411,7 +508,7 @@ impl PeerHandler { } // Connection is valid, send the packet - match outbound_tx.send((data.to_vec(), addr)).await { + match outbound_tx.send((data.to_vec(), vec![addr])).await { Ok(_) => { tracing::debug!( "Sent frame to peer {} via {}: {}", @@ -428,179 +525,68 @@ impl PeerHandler { } } - fn find_peer_by_ip_locked<'a>( - &self, - peers: &'a HashMap, - dest_ip: &str, - ) -> Option<&'a PeerMeta> { - use ipnet::IpNet; - use std::net::IpAddr; - - let dest_ip_addr = match dest_ip.parse::() { - Ok(ip) => ip, - Err(_) => return None, - }; - - for peer in peers.values() { - // Check exact match with peer's private IP - if peer.private_ip == dest_ip { - return Some(peer); - } - - // Check if destination falls within peer's CIDR ranges - for cidr in &peer.ciders { - if let Ok(network) = cidr.parse::() - && network.contains(&dest_ip_addr) - { - return Some(peer); - } - } - } - - None + fn get_status(&self) -> Vec { + self.peers.get_status() } - async fn update_peer_active(&mut self, remote_addr: SocketAddr) { - let mut peers = self.peers.write().await; + async fn send_probes(&self) { + let outbound_tx = &self.tx_api.outbound_tx; - for peer in peers.values_mut() { - // Check if this is from IPv6 address - if let Some(ipv6_addr) = peer.remote_addr - && ipv6_addr == remote_addr - { - peer.last_active = Some(Instant::now()); - tracing::debug!("Updated IPv6 last_active for peer: {}", peer.identity); - return; - } - - // Check if this is from STUN address - if let Some(stun_addr) = peer.stun_addr - && stun_addr == remote_addr - { - peer.stun_last_active = Some(Instant::now()); - tracing::debug!("Updated STUN last_active for peer: {}", peer.identity); - return; - } - } + let block = &self.block; + let identity = &self.identity; - tracing::warn!("Received packet from unknown peer address: {}", remote_addr); - } + // Send IPv6 probes + send_probes(&self.peers, outbound_tx, block, identity, Protocol::Ipv6).await; - pub async fn get_status(&self) -> Vec { - let guard = self.peers.read().await; - let mut result: Vec = Vec::new(); - for peer in guard.values() { - let status = PeerStatus { - name: peer.name.clone(), - identity: peer.identity.clone(), - ipv6_addr: peer.remote_addr, - ipv6_last_active: peer.last_active, - stun_addr: peer.stun_addr, - stun_last_active: peer.stun_last_active, - }; - result.push(status); - } - result + // Send STUN hole punch probes + send_probes(&self.peers, outbound_tx, block, identity, Protocol::Stun).await; } } -impl PeerHandler { - pub async fn start_probe_timer(&self) { - let outbound_tx = match &self.outbound_tx { - Some(tx) => tx.clone(), - None => { - tracing::error!("Cannot start probe timer: outbound_tx not initialized"); - return; - } - }; - - let block = self.block.clone(); - let peers = self.peers.clone(); // Clone Arc, not the data - let identity = self.identity.clone(); - - tokio::spawn(async move { - let mut interval = tokio::time::interval(KEEPALIVE_INTERVAL); - loop { - interval.tick().await; - - // Send IPv6 probes - Self::send_probes( - &peers, - &outbound_tx, - &block, - &identity, - true, // is_ipv6 - ) - .await; - - // Send STUN hole punch probes - Self::send_probes( - &peers, - &outbound_tx, - &block, - &identity, - false, // is_ipv4/stun - ) - .await; - } - }); +async fn send_probes( + peers: &PeerSet, + outbound_tx: &mpsc::Sender<(Vec, Vec)>, + block: &Arc>, + identity: &str, + protocol: Protocol, +) { + let peer_addrs = peers.all_peer_addrs(protocol); + + // Skip if no peers have this type of address + if peer_addrs.is_empty() { + return; } - async fn send_probes( - peers: &Arc>>, - outbound_tx: &mpsc::Sender<(Vec, SocketAddr)>, - block: &Arc>, - identity: &str, - is_ipv6: bool, - ) { - let peer_addrs: Vec = { - let peers_guard = peers.read().await; - peers_guard - .values() - .filter_map(|p| if is_ipv6 { p.remote_addr } else { p.stun_addr }) - .collect() - }; + // Create appropriate probe frame + let probe_frame = match protocol { + Protocol::Ipv6 => Frame::ProbeIPv6(ProbeIPv6Frame { + identity: identity.to_string(), + }), + Protocol::Stun => Frame::ProbeHolePunch(ProbeHolePunchFrame { + identity: identity.to_string(), + }), + }; - // Skip if no peers have this type of address - if peer_addrs.is_empty() { + // Marshal once, reuse for all peers + let probe_data = match Parser::marshal(probe_frame, block.as_ref().as_ref()) { + Ok(data) => data, + Err(e) => { + tracing::error!("Failed to marshal {} probe: {}", protocol, e); return; } + }; - // Create appropriate probe frame - let probe_frame = if is_ipv6 { - Frame::ProbeIPv6(ProbeIPv6Frame { - identity: identity.to_string(), - }) - } else { - Frame::ProbeHolePunch(ProbeHolePunchFrame { - identity: identity.to_string(), - }) - }; - - // Marshal once, reuse for all peers - let probe_data = match Parser::marshal(probe_frame, block.as_ref().as_ref()) { - Ok(data) => data, - Err(e) => { - let protocol = if is_ipv6 { "IPv6" } else { "STUN" }; - tracing::error!("Failed to marshal {} probe: {}", protocol, e); - return; - } - }; - - // Send to all peers - let protocol = if is_ipv6 { "IPv6" } else { "hole punch" }; - for remote_addr in peer_addrs { - if let Err(e) = outbound_tx.send((probe_data.clone(), remote_addr)).await { - tracing::warn!( - "Failed to send {} probe to {}: {}", - protocol, - remote_addr, - e - ); - } else { - tracing::info!("Sent {} probe to {}", protocol, remote_addr); - } - } + // Send to all peers + let peer_addrs_display = format!("{peer_addrs:?}"); + if let Err(e) = outbound_tx.send((probe_data.clone(), peer_addrs)).await { + tracing::warn!( + "Failed to send {} probe to {}: {:?}", + protocol, + peer_addrs_display, + e + ); + } else { + tracing::info!("Sent {} probe to {:?}", protocol, peer_addrs_display); } } @@ -619,3 +605,42 @@ fn parse_address(identity: &str, ip: &str, port: u16) -> Option { let addr = SocketAddr::new(ip, port); Some(addr) } + +fn update_address(peer: &mut PeerMeta, new_addr: SocketAddr, protocol: Protocol) { + let old_addr = match protocol { + Protocol::Stun => *peer.stun_addr.get(), + Protocol::Ipv6 => *peer.remote_addr.get(), + }; + + if old_addr != Some(new_addr) { + tracing::info!( + "Update {} address for peer {}: {} -> {}", + protocol, + peer.identity, + old_addr + .map(|a| a.to_string()) + .unwrap_or_else(|| "None".to_string()), + new_addr + ); + + let new_addr = LastActive::dormant(Some(new_addr)); + match protocol { + Protocol::Stun => peer.stun_addr = new_addr, + Protocol::Ipv6 => peer.stun_addr = new_addr, + } + } +} + +#[derive(Debug, Clone, Copy)] +enum Protocol { + Stun, + Ipv6, +} +impl std::fmt::Display for Protocol { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Protocol::Stun => write!(f, "STUN"), + Protocol::Ipv6 => write!(f, "IPv6"), + } + } +} diff --git a/src/client/p2p/udp_server.rs b/src/client/p2p/udp_server.rs index 27a0270..f1baeb3 100644 --- a/src/client/p2p/udp_server.rs +++ b/src/client/p2p/udp_server.rs @@ -3,7 +3,7 @@ use tokio::net::UdpSocket; use tokio::sync::mpsc; /// UDP packet buffer size -/// +/// /// 2048 bytes is sufficient for: /// - Typical VPN frames (MTU 1500 + headers) /// - Control frames (handshake, keepalive, etc.) @@ -56,7 +56,7 @@ pub struct UDPServer { /// /// PeerHandler sends encrypted packets through this channel. /// The server selects the appropriate socket based on destination address type. - output_rx: mpsc::Receiver<(Vec, SocketAddr)>, + output_rx: mpsc::Receiver<(Vec, Vec)>, } impl UDPServer { @@ -79,7 +79,7 @@ impl UDPServer { listen_port: u16, stun_port: u16, input_tx: mpsc::Sender<(Vec, SocketAddr)>, - output_rx: mpsc::Receiver<(Vec, SocketAddr)>, + output_rx: mpsc::Receiver<(Vec, Vec)>, ) -> Self { UDPServer { listen_port, @@ -121,7 +121,10 @@ impl UDPServer { // Bind IPv4 socket for STUN hole punching // This socket uses the port discovered by STUN client let socket_ipv4 = UdpSocket::bind(format!("0.0.0.0:{}", self.stun_port)).await?; - tracing::info!("P2P IPv4 UDP (STUN) listening on {}", socket_ipv4.local_addr()?); + tracing::info!( + "P2P IPv4 UDP (STUN) listening on {}", + socket_ipv4.local_addr()? + ); // Separate buffers for each socket to avoid data races let mut buf_ipv6 = vec![0u8; BUFFER_SIZE]; @@ -176,17 +179,19 @@ impl UDPServer { socket_ipv6: &UdpSocket, socket_ipv4: &UdpSocket, data: &[u8], - remote: SocketAddr, + remotes: Vec, ) { - // Select socket based on destination address family - let (socket, protocol) = if remote.is_ipv4() { - (socket_ipv4, "IPv4") - } else { - (socket_ipv6, "IPv6") - }; - - if let Err(e) = socket.send_to(data, remote).await { - tracing::error!("Failed to send {} packet to {}: {:?}", protocol, remote, e); + for remote in &remotes { + // Select socket based on destination address family + let (socket, protocol) = if remote.is_ipv4() { + (socket_ipv4, "IPv4") + } else { + (socket_ipv6, "IPv6") + }; + + if let Err(e) = socket.send_to(data, remote).await { + tracing::error!("Failed to send {} packet to {}: {:?}", protocol, remote, e); + } } } @@ -229,7 +234,12 @@ impl UDPServer { // Forward to PeerHandler for decryption and protocol processing if let Err(e) = self.input_tx.send((packet, remote)).await { - tracing::error!("Failed to forward {} packet from {}: {:?}", protocol, remote, e); + tracing::error!( + "Failed to forward {} packet from {}: {:?}", + protocol, + remote, + e + ); } // Reset buffer for next packet (optional but good practice) diff --git a/src/client/prettylog.rs b/src/client/prettylog.rs index ebb325e..1da552d 100644 --- a/src/client/prettylog.rs +++ b/src/client/prettylog.rs @@ -8,7 +8,7 @@ use crate::client::http::{ ClusterPeerInfo, IPv6ConnectionInfo, P2PPeerInfo, P2PStatus, RelayStatusInfo, STUNConnectionInfo, StatusResponse, TrafficStats, }; -use crate::client::p2p::peer::PeerHandler; +use crate::client::p2p::PeerStatus; use crate::client::relay::RelayHandler; use crate::codec::frame::HandshakeReplyFrame; use crate::utils::device::DeviceHandler; @@ -41,7 +41,7 @@ pub fn log_handshake_success(config: &HandshakeReplyFrame) { println!("Ready to forward traffic"); } -pub async fn get_status(relay: &RelayHandler, peer: Option<&PeerHandler>, dev: &DeviceHandler) { +pub async fn get_status(relay: &RelayHandler, peer: Option<&[PeerStatus]>, dev: &DeviceHandler) { println!("\n╔══════════════════════════════════════════════════════════════════════╗"); println!("║ CONNECTION STATUS ║"); println!("╚══════════════════════════════════════════════════════════════════════╝"); @@ -64,9 +64,7 @@ pub async fn get_status(relay: &RelayHandler, peer: Option<&PeerHandler>, dev: & ); // P2P Status - if let Some(peer_handler) = peer { - let peer_status = peer_handler.get_status().await; - + if let Some(peer_status) = peer { if peer_status.is_empty() { println!("\n🔗 P2P Connections (UDP)"); println!(" └─ No peers configured"); @@ -188,16 +186,22 @@ pub async fn get_status(relay: &RelayHandler, peer: Option<&PeerHandler>, dev: & println!(); // Update HTTP cache - let status = build_status_response(relay, peer, dev).await; - cache::update(status).await; + let status = match build_status_response(relay, peer, dev).await { + Ok(s) => s, + Err(e) => { + eprintln!("failed: {e}"); + return; + } + }; + cache::update(status); } /// Build status response for HTTP API pub async fn build_status_response( relay: &RelayHandler, - peer: Option<&PeerHandler>, + peer: Option<&[PeerStatus]>, dev: &DeviceHandler, -) -> StatusResponse { +) -> anyhow::Result { // Self information from relay let self_info = relay.get_self_info().await; @@ -219,8 +223,7 @@ pub async fn build_status_response( }; // P2P status - let p2p = if let Some(peer_handler) = peer { - let peer_statuses = peer_handler.get_status().await; + let p2p = if let Some(peer_statuses) = peer { let mut peers = Vec::new(); for status in peer_statuses { @@ -315,11 +318,11 @@ pub async fn build_status_response( }) .collect(); - StatusResponse { + Ok(StatusResponse { self_info, traffic, relay, p2p, cluster_peers, - } + }) } diff --git a/src/client/relay.rs b/src/client/relay.rs index 99862b5..5577c3a 100644 --- a/src/client/relay.rs +++ b/src/client/relay.rs @@ -8,8 +8,9 @@ use crate::utils::{self, StunAddr}; use std::net::{Ipv6Addr, SocketAddr}; use std::ops::ControlFlow; use std::sync::Arc; +use std::sync::RwLock; use std::time::Instant; -use tokio::sync::{RwLock, mpsc}; +use tokio::sync::mpsc; use tokio::time::{Duration, interval}; const CHANNEL_BUFFER_SIZE: usize = 1000; @@ -276,7 +277,7 @@ impl RelayHandler { /// Get self information pub async fn get_self_info(&self) -> Option { - let reply_guard = self.handshake_reply.read().await; + let reply_guard = self.handshake_reply.read().unwrap(); match (&self.config, reply_guard.as_ref()) { (Some(cfg), Some(reply)) => Some(SelfInfo { identity: cfg.identity.clone(), @@ -385,7 +386,7 @@ async fn run_client_session( // Store handshake reply in handler { - let mut guard = handshake_reply.write().await; + let mut guard = handshake_reply.write().unwrap(); *guard = Some(frame.clone()); } From 3fd8db6e3a7f4d18100ff0b2cccd36e3ba33e401 Mon Sep 17 00:00:00 2001 From: Banyc <36535895+Banyc@users.noreply.github.com> Date: Sat, 21 Mar 2026 17:44:51 +0000 Subject: [PATCH 09/12] inline format args --- examples/tun_reader.rs | 2 +- src/client/http/server.rs | 7 +- src/client/main.rs | 19 +++--- src/client/p2p/peer.rs | 4 +- src/client/relay.rs | 22 +++--- src/codec/errors.rs | 2 +- src/crypto/aes256.rs | 4 +- src/crypto/chacha20.rs | 4 +- src/network/tcp_listener.rs | 4 +- src/server/client_manager.rs | 15 ++-- src/server/conf_agent.rs | 8 +-- src/server/config_watcher.rs | 8 +-- src/server/handler.rs | 4 +- src/server/main.rs | 8 +-- src/utils/device.rs | 128 +++++++++++++++++++++-------------- src/utils/sys_route.rs | 30 ++++---- 16 files changed, 147 insertions(+), 122 deletions(-) diff --git a/examples/tun_reader.rs b/examples/tun_reader.rs index b019c5d..24dda34 100644 --- a/examples/tun_reader.rs +++ b/examples/tun_reader.rs @@ -122,7 +122,7 @@ async fn main() { } } Err(e) => { - eprintln!("❌ STUN Discovery Failed: {}", e); + eprintln!("❌ STUN Discovery Failed: {e}"); eprintln!(); eprintln!("Possible reasons:"); eprintln!(" - No internet connection"); diff --git a/src/client/http/server.rs b/src/client/http/server.rs index 2e395b0..0bca9e0 100644 --- a/src/client/http/server.rs +++ b/src/client/http/server.rs @@ -12,11 +12,8 @@ pub async fn start(port: u16) -> Result<(), Box anyhow::Result<()> { let args = Args::parse(); if let Err(e) = utils::init_tracing() { - anyhow::bail!("Failed to initialize logging: {}", e); + anyhow::bail!("Failed to initialize logging: {e}"); } log_startup_banner(&args); @@ -27,7 +27,7 @@ pub async fn run_client() -> anyhow::Result<()> { let crypto_config = match crypto::parse_crypto_config(&args.crypto) { Ok(cfg) => cfg, Err(e) => { - anyhow::bail!("Invalid crypto configuration: {}", e); + anyhow::bail!("Invalid crypto configuration: {e}"); } }; let block = crypto::new_block(&crypto_config); @@ -48,11 +48,10 @@ pub async fn run_client() -> anyhow::Result<()> { match new_relay_handler(&args, crypto_block.clone(), ipv6, P2P_UDP_PORT, stun).await { Ok(result) => result, Err(e) => { - anyhow::bail!("Failed to setup client: {}", e); + anyhow::bail!("Failed to setup client: {e}"); } }; - // Initialize P2P handler if enabled (wrapped in Arc> for sharing with device packet task) let p2p_handler = if args.enable_p2p { tracing::info!("P2P mode enabled"); @@ -86,7 +85,7 @@ pub async fn run_client() -> anyhow::Result<()> { let mut dev = match init_device(&device_config, enable_masq).await { Ok(d) => d, Err(e) => { - anyhow::bail!("Failed to initialize device: {}", e); + anyhow::bail!("Failed to initialize device: {e}"); } }; @@ -94,7 +93,7 @@ pub async fn run_client() -> anyhow::Result<()> { if let Some(http_port) = args.http_port { tokio::spawn(async move { if let Err(e) = server::start(http_port).await { - tracing::error!("HTTP server error: {}", e); + tracing::error!("HTTP server error: {e}"); } }); } @@ -123,7 +122,7 @@ async fn init_device( if !device_config.cider_mapping.is_empty() && let Err(e) = dev.setup_cidr_mapping(&device_config.cider_mapping) { - tracing::error!("Failed to setup CIDR mapping DNAT rules: {}", e); + tracing::error!("Failed to setup CIDR mapping DNAT rules: {e}"); // Don't fail initialization, just log the error // This allows the client to continue even if DNAT setup fails } @@ -196,7 +195,7 @@ async fn run_event_loop( }; tracing::debug!("P2P -> Device: {} bytes", data_frame.payload.len()); if let Err(e) = dev.send(data_frame.payload).await { - tracing::error!("Failed to write to device: {}", e); + tracing::error!("Failed to write to device: {e}"); } } @@ -250,7 +249,7 @@ async fn handle_device_packet( // Fallback to relay (or direct if no P2P) let frame = Frame::Data(DataFrame { payload: packet }); if let Err(e) = RelayHandler::send_frame(relay_outbound, frame).await { - tracing::error!("Failed to send via relay: {}", e); + tracing::error!("Failed to send via relay: {e}"); } } @@ -264,7 +263,7 @@ async fn handle_relay_frame( Frame::Data(data_frame) => { tracing::debug!("Relay -> Device: {} bytes", data_frame.payload.len()); if let Err(e) = dev.send(data_frame.payload).await { - tracing::error!("Failed to write to device: {}", e); + tracing::error!("Failed to write to device: {e}"); } } Frame::KeepAlive(keepalive) => { diff --git a/src/client/p2p/peer.rs b/src/client/p2p/peer.rs index c15e0b5..92361a2 100644 --- a/src/client/p2p/peer.rs +++ b/src/client/p2p/peer.rs @@ -275,7 +275,7 @@ impl PeerHandler { UDPServer::new(P2P_UDP_PORT, P2P_HOLE_PUNCH_PORT, inbound_tx, output_rx); tokio::spawn(async move { if let Err(e) = udp_server.serve().await { - tracing::error!("PeerService error: {}", e); + tracing::error!("PeerService error: {e}"); } }); let (new_pears_tx, new_pears_rx) = mpsc::channel(1024); @@ -330,7 +330,7 @@ impl PeerHandler { } Some(sf) = send_frame.0.recv() => { if let Err(e) = self.send_frame(sf.frame, &sf.dst).await { - tracing::warn!("send_frame failed: {}", e); + tracing::warn!("send_frame failed: {e}"); } } Some(reply_tx) = get_status.0.recv() => { diff --git a/src/client/relay.rs b/src/client/relay.rs index 5577c3a..26e23a0 100644 --- a/src/client/relay.rs +++ b/src/client/relay.rs @@ -113,7 +113,7 @@ impl RelayClient { let now = Instant::now(); if let Err(e) = conn.write_frame(frame.unwrap()).await { - tracing::error!("device => server write frame: {}", e); + tracing::error!("device => server write frame: {e}"); } tracing::debug!("send to server cost {}", now.elapsed().as_millis()); } @@ -134,7 +134,7 @@ impl RelayClient { *last_active = Instant::now(); match result { Ok(frame) => { - tracing::debug!("received frame {}", frame); + tracing::debug!("received frame {frame}"); let beg = Instant::now(); match frame { Frame::KeepAlive(keepalive) => { @@ -142,13 +142,13 @@ impl RelayClient { tracing::debug!("Received keepalive from server"); if let Err(e) = self.inbound_tx.send(Frame::KeepAlive(keepalive)).await { - tracing::error!("Failed to forward keepalive: {}", e); + tracing::error!("Failed to forward keepalive: {e}"); return ControlFlow::Break(()); } } Frame::Data(data) => { if let Err(e) = self.inbound_tx.send(Frame::Data(data)).await { - tracing::error!("server => device inbound: {}", e); + tracing::error!("server => device inbound: {e}"); return ControlFlow::Break(()); } } @@ -157,7 +157,7 @@ impl RelayClient { tracing::debug!("handle frame cost {}", beg.elapsed().as_millis()); } Err(e) => { - tracing::error!("Read error: {}", e); + tracing::error!("Read error: {e}"); return ControlFlow::Break(()); } } @@ -199,7 +199,7 @@ impl RelayClient { *keepalive_wait = 0; } Err(e) => { - tracing::error!("Failed to send keepalive: {}", e); + tracing::error!("Failed to send keepalive: {e}"); *keepalive_wait += 1; if *keepalive_wait > self.cfg.keep_alive_thresh { tracing::error!("keepalive max retry, close connection"); @@ -337,7 +337,7 @@ impl RelayHandler { Ok(()) => Ok(()), Err(e) => { // self.metrics.tx_error += 1; - Err(anyhow::anyhow!("device=> server fail {:?}", e)) + Err(anyhow::anyhow!("device=> server fail {e:?}")) } } } @@ -369,7 +369,7 @@ async fn run_client_session( let mut conn = match client.connect().await { Ok(socket) => socket, Err(e) => { - tracing::error!("connect error: {}", e); + tracing::error!("connect error: {e}"); return; } }; @@ -377,7 +377,7 @@ async fn run_client_session( let frame = match client.handshake(&mut conn).await { Ok(frame) => frame, Err(e) => { - tracing::warn!("handshake fail {:?}, reconnecting", e); + tracing::warn!("handshake fail {e:?}, reconnecting"); return; } }; @@ -391,12 +391,12 @@ async fn run_client_session( } if let Err(e) = on_ready.send(frame.clone()).await { - tracing::error!("on ready send fail: {}", e); + tracing::error!("on ready send fail: {e}"); } let result = client.run(conn).await; - tracing::warn!("run client fail {:?}, reconnecting", result); + tracing::warn!("run client fail {result:?}, reconnecting"); } pub async fn new_relay_handler( diff --git a/src/codec/errors.rs b/src/codec/errors.rs index 0af8a1c..41ef992 100644 --- a/src/codec/errors.rs +++ b/src/codec/errors.rs @@ -55,7 +55,7 @@ impl Display for FrameError { match self { FrameError::TooShort => "stream ended early".fmt(fmt), FrameError::Invalid => "invalid frame".fmt(fmt), - FrameError::DecryptionFailed(e) => write!(fmt, "decryption failed: {}", e), + FrameError::DecryptionFailed(e) => write!(fmt, "decryption failed: {e}"), } } } diff --git a/src/crypto/aes256.rs b/src/crypto/aes256.rs index 044d787..d80c0b8 100644 --- a/src/crypto/aes256.rs +++ b/src/crypto/aes256.rs @@ -81,7 +81,7 @@ impl Block for Aes256Block { let ciphertext = self .cipher .encrypt(nonce, data.as_ref()) - .map_err(|e| anyhow::anyhow!("AES-256-GCM encryption failed: {}", e))?; + .map_err(|e| anyhow::anyhow!("AES-256-GCM encryption failed: {e}"))?; // Replace data with: nonce || ciphertext (ciphertext already includes auth tag) data.clear(); @@ -114,7 +114,7 @@ impl Block for Aes256Block { let plaintext = self .cipher .decrypt(nonce, ciphertext) - .map_err(|e| anyhow::anyhow!("AES-256-GCM decryption failed: {}", e))?; + .map_err(|e| anyhow::anyhow!("AES-256-GCM decryption failed: {e}"))?; // Replace data with plaintext data.clear(); diff --git a/src/crypto/chacha20.rs b/src/crypto/chacha20.rs index e3cdb11..e8eea6f 100644 --- a/src/crypto/chacha20.rs +++ b/src/crypto/chacha20.rs @@ -80,7 +80,7 @@ impl Block for ChaCha20Poly1305Block { let ciphertext = self .cipher .encrypt(nonce, data.as_ref()) - .map_err(|e| anyhow::anyhow!("ChaCha20-Poly1305 encryption failed: {}", e))?; + .map_err(|e| anyhow::anyhow!("ChaCha20-Poly1305 encryption failed: {e}"))?; // Replace data with: nonce || ciphertext (ciphertext already includes auth tag) data.clear(); @@ -115,7 +115,7 @@ impl Block for ChaCha20Poly1305Block { let plaintext = self .cipher .decrypt(nonce, ciphertext) - .map_err(|e| anyhow::anyhow!("ChaCha20-Poly1305 decryption failed: {}", e))?; + .map_err(|e| anyhow::anyhow!("ChaCha20-Poly1305 decryption failed: {e}"))?; // Replace data with plaintext data.clear(); diff --git a/src/network/tcp_listener.rs b/src/network/tcp_listener.rs index 3c3dc96..ed23c77 100644 --- a/src/network/tcp_listener.rs +++ b/src/network/tcp_listener.rs @@ -104,11 +104,11 @@ impl Listener for TCPListener { if let Some(tx) = &self.on_conn_tx && let Err(e) = tx.send(Box::new(conn)).await { - tracing::warn!("Failed to send new connection: {}", e); + tracing::warn!("Failed to send new connection: {e}"); } } Err(e) => { - tracing::error!("Accept error: {}", e); + tracing::error!("Accept error: {e}"); return Err(e); } } diff --git a/src/server/client_manager.rs b/src/server/client_manager.rs index 519732a..001010b 100644 --- a/src/server/client_manager.rs +++ b/src/server/client_manager.rs @@ -46,7 +46,7 @@ impl ClientManager { .unwrap_or_else(|e| e.into_inner()); for client in clients { - tracing::debug!("add client config {:?}", client); + tracing::debug!("add client config {client:?}"); clients_map.insert(client.identity.clone(), client.clone()); cluster_map .entry(client.cluster.clone()) @@ -57,14 +57,20 @@ impl ClientManager { pub fn rewrite_clients_config(&self, clients: Vec) { let mut clients_map = self.clients.write().unwrap_or_else(|e| e.into_inner()); - let mut cluster_map = self.cluster_clients.write().unwrap_or_else(|e| e.into_inner()); + let mut cluster_map = self + .cluster_clients + .write() + .unwrap_or_else(|e| e.into_inner()); let mut new_clients_map = HashMap::new(); let mut new_cluster_map: HashMap> = HashMap::new(); for client in clients { - tracing::debug!("add client config {:?}", client); + tracing::debug!("add client config {client:?}"); new_clients_map.insert(client.identity.clone(), client.clone()); - new_cluster_map.entry(client.cluster.clone()).or_default().push(client.clone()); + new_cluster_map + .entry(client.cluster.clone()) + .or_default() + .push(client.clone()); } *clients_map = new_clients_map; @@ -96,7 +102,6 @@ impl ClientManager { .unwrap_or_default() } - pub fn get_client(&self, identity: &String) -> Option { self.clients .read() diff --git a/src/server/conf_agent.rs b/src/server/conf_agent.rs index fd1e185..b2f0a65 100644 --- a/src/server/conf_agent.rs +++ b/src/server/conf_agent.rs @@ -61,10 +61,10 @@ impl ConfAgent { // Initial fetch and report if let Err(e) = self.fetch_and_update_routes().await { - tracing::error!("Failed to fetch routes: {:?}", e); + tracing::error!("Failed to fetch routes: {e:?}"); } if let Err(e) = self.report_connections().await { - tracing::error!("Failed to report connections: {:?}", e); + tracing::error!("Failed to report connections: {e:?}"); } // Periodic tasks: route fetching and connection reporting @@ -75,12 +75,12 @@ impl ConfAgent { tokio::select! { _ = route_ticker.tick() => { if let Err(e) = self.fetch_and_update_routes().await { - tracing::error!("Failed to fetch routes: {:?}", e); + tracing::error!("Failed to fetch routes: {e:?}"); } } _ = report_ticker.tick() => { if let Err(e) = self.report_connections().await { - tracing::error!("Failed to report connections: {:?}", e); + tracing::error!("Failed to report connections: {e:?}"); } } } diff --git a/src/server/config_watcher.rs b/src/server/config_watcher.rs index f3bef23..9ba8b20 100644 --- a/src/server/config_watcher.rs +++ b/src/server/config_watcher.rs @@ -1,7 +1,7 @@ +use crate::server::client_manager::ClientManager; +use crate::server::config; use std::sync::Arc; use std::time::Duration; -use crate::server::client_manager::{ClientManager}; -use crate::server::config; const RELOAD_INTERVAL: Duration = Duration::from_secs(10); @@ -31,11 +31,11 @@ impl ConfigWatcher { client_manager.rewrite_clients_config(client_routes); } Err(e) => { - tracing::error!("load client routes error: {}", e); + tracing::error!("load client routes error: {e}"); } } tokio::time::sleep(RELOAD_INTERVAL).await; } }); } -} \ No newline at end of file +} diff --git a/src/server/handler.rs b/src/server/handler.rs index 7053b07..ce6665a 100644 --- a/src/server/handler.rs +++ b/src/server/handler.rs @@ -192,7 +192,7 @@ impl Handler { if let Some(frame) = frame { tracing::debug!("send frame {}", frame); if let Err(e) = self.conn.write_frame(frame).await { - tracing::debug!("connection closed with {:?}", e); + tracing::debug!("connection closed with {e:?}"); break; }; } @@ -350,7 +350,7 @@ impl Handler { }); if let Err(e) = self.outbound_tx.send(reply_frame).await { - tracing::error!("reply keepalive frame failed with {:?}", e); + tracing::error!("reply keepalive frame failed with {e:?}"); } } } diff --git a/src/server/main.rs b/src/server/main.rs index 797e26b..3a44bbd 100644 --- a/src/server/main.rs +++ b/src/server/main.rs @@ -12,12 +12,12 @@ pub async fn run_server() -> anyhow::Result<()> { let cfg = config::load_main(args.get(1).unwrap_or(&"server.toml".to_string())).unwrap(); if let Err(e) = utils::init_tracing() { - anyhow::bail!("Failed to initialize logging: {}", e); + anyhow::bail!("Failed to initialize logging: {e}"); } let routes_file = cfg.route_config.routes_file.clone(); let client_routes = config::load_routes(routes_file.as_str()).unwrap(); - tracing::debug!("config: {:?}, routes: {:?}", cfg, client_routes); + tracing::debug!("config: {cfg:?}, routes: {client_routes:?}"); let client_manager = Arc::new(ClientManager::new()); client_manager.add_clients_config(client_routes.clone()); @@ -44,7 +44,7 @@ pub async fn run_server() -> anyhow::Result<()> { let agent_clone = agent.clone(); tokio::spawn(async move { if let Err(e) = agent_clone.start().await { - tracing::error!("Conf-agent error: {:?}", e); + tracing::error!("Conf-agent error: {e:?}"); } }); } @@ -56,7 +56,7 @@ pub async fn run_server() -> anyhow::Result<()> { Arc::new(block), ); if let Err(e) = server.run().await { - anyhow::bail!("Server error: {}", e); + anyhow::bail!("Server error: {e}"); } Ok(()) } diff --git a/src/utils/device.rs b/src/utils/device.rs index d75688e..1a9a68c 100644 --- a/src/utils/device.rs +++ b/src/utils/device.rs @@ -1,12 +1,12 @@ -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::sync::{mpsc, oneshot}; -#[allow(unused_imports)] -use tun::AbstractDevice; use crate::codec::frame::{HandshakeReplyFrame, PeerDetail}; use crate::utils::sys_route::SysRoute; -use std::collections::{HashSet, HashMap}; #[allow(unused_imports)] use crate::utils::sys_route::{ip_to_network, mask_to_prefix_length}; +use std::collections::{HashMap, HashSet}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::sync::{mpsc, oneshot}; +#[allow(unused_imports)] +use tun::AbstractDevice; const DEFAULT_MTU: u16 = 1430; @@ -43,7 +43,11 @@ impl Device { } } - pub async fn run(&mut self, ready: oneshot::Sender>, name: oneshot::Sender>) -> anyhow::Result<()> { + pub async fn run( + &mut self, + ready: oneshot::Sender>, + name: oneshot::Sender>, + ) -> anyhow::Result<()> { let mut config = tun::Configuration::default(); config .address(self.ip.clone()) @@ -67,7 +71,7 @@ impl Device { // Get TUN interface index (Windows only) #[cfg(target_os = "windows")] let tun_index = dev.tun_index().ok(); - + #[cfg(not(target_os = "windows"))] let tun_index: Option = None; @@ -77,7 +81,7 @@ impl Device { let interface_name = dev.tun_name().ok(); let _ = name.send(interface_name); } - + #[cfg(not(target_os = "linux"))] let _ = name.send(None); @@ -89,12 +93,12 @@ impl Device { let amount = match amount { Ok(amount) => amount, Err(e) => { - tracing::error!("read device fail: {:?}", e); + tracing::error!("read device fail: {e:?}"); continue; } }; if let Err(e) = self.inbound_tx.send(buf[0..amount].to_vec()).await { - tracing::error!("device => server fail: {}", e); + tracing::error!("device => server fail: {e}"); } } packet = self.outbound_rx.recv() => { @@ -102,7 +106,7 @@ impl Device { tracing::debug!("server => device {} bytes", packet.len()); let result = dev.write(packet.as_slice()).await; if let Err(e) = result { - tracing::error!("write device fail: {:?}", e); + tracing::error!("write device fail: {e:?}"); } } } @@ -140,7 +144,11 @@ impl DeviceHandler { } } - pub async fn run(&mut self, cfg: &HandshakeReplyFrame, enable_masq: bool) -> anyhow::Result> { + pub async fn run( + &mut self, + cfg: &HandshakeReplyFrame, + enable_masq: bool, + ) -> anyhow::Result> { let (inbound_tx, inbound_rx) = mpsc::channel(1000); let (outbound_tx, outbound_rx) = mpsc::channel(1000); self.inbound_rx = Some(inbound_rx); @@ -149,17 +157,20 @@ impl DeviceHandler { self.mask = cfg.mask.clone(); self.local_ciders = cfg.ciders.clone(); - let mut dev = Device::new(cfg.private_ip.clone(), - cfg.mask.clone(), - DEFAULT_MTU, - inbound_tx, outbound_rx); + let mut dev = Device::new( + cfg.private_ip.clone(), + cfg.mask.clone(), + DEFAULT_MTU, + inbound_tx, + outbound_rx, + ); let (ready_tx, ready_rx) = oneshot::channel(); let (name_tx, name_rx) = oneshot::channel(); tokio::spawn(async move { let res = dev.run(ready_tx, name_tx).await; match res { Ok(_) => (), - Err(e) => tracing::error!("device handler fail: {:?}", e), + Err(e) => tracing::error!("device handler fail: {e:?}"), } }); @@ -170,13 +181,12 @@ impl DeviceHandler { self.interface_name = Some(name); } - if enable_masq { if let Err(e) = self.enable_masquerade() { - tracing::error!("Failed to enable MASQUERADE: {:?}", e); + tracing::error!("Failed to enable MASQUERADE: {e:?}"); } if let Err(e) = self.enable_snat() { - tracing::warn!("Failed to enable SNAT: {:?}", e); + tracing::warn!("Failed to enable SNAT: {e:?}"); } } @@ -210,7 +220,7 @@ impl DeviceHandler { return Err(anyhow::anyhow!("device handler send none")); } }; - self.tx_bytes+=packet.len(); + self.tx_bytes += packet.len(); tracing::debug!("device => server outbound tx len: {}", packet.len()); let result = outbound_tx.send(packet).await; match result { @@ -226,45 +236,53 @@ impl DeviceHandler { pub async fn reload_route(&mut self, new_routes: Vec) { let sys_route = SysRoute::new(); - + let mut old_ciders: HashSet = HashSet::new(); for route in &self.peer_details { for cidr in &route.ciders { old_ciders.insert(cidr.clone()); } } - + let mut new_ciders: HashSet = HashSet::new(); for route in &new_routes { for cidr in &route.ciders { new_ciders.insert(cidr.clone()); } } - - tracing::info!("Reloading routes: old={}, new={}", old_ciders.len(), new_ciders.len()); - + + tracing::info!( + "Reloading routes: old={}, new={}", + old_ciders.len(), + new_ciders.len() + ); + // Find routes to delete (in old but not in new) let to_delete: Vec = old_ciders.difference(&new_ciders).cloned().collect(); - + // Find routes to add (in new but not in old) let to_add: Vec = new_ciders.difference(&old_ciders).cloned().collect(); - + // Delete old routes for cidr in to_delete { - tracing::info!("Deleting route: {}", cidr); - if let Err(e) = sys_route.del(vec![cidr.clone()], self.private_ip.clone(), self.tun_index) { - tracing::error!("Failed to delete route {}: {}", cidr, e); + tracing::info!("Deleting route: {cidr}"); + if let Err(e) = + sys_route.del(vec![cidr.clone()], self.private_ip.clone(), self.tun_index) + { + tracing::error!("Failed to delete route {cidr}: {e}"); } } - + // Add new routes for cidr in to_add { - tracing::info!("Adding route: {} via {}", cidr, self.private_ip); - if let Err(e) = sys_route.add(vec![cidr.clone()], self.private_ip.clone(), self.tun_index) { - tracing::error!("Failed to add route {}: {}", cidr, e); + tracing::info!("Adding route: {cidr} via {}", self.private_ip); + if let Err(e) = + sys_route.add(vec![cidr.clone()], self.private_ip.clone(), self.tun_index) + { + tracing::error!("Failed to add route {cidr}: {e}"); } } - + // Update stored routes self.peer_details = new_routes; @@ -275,7 +293,7 @@ impl DeviceHandler { /// Uses source network address instead of interface name for better reliability pub fn enable_masquerade(&mut self) -> anyhow::Result<()> { let cidr = self.ip_mask_to_cidr(&self.private_ip, &self.mask)?; - + let sys_route = SysRoute::new(); sys_route.enable_masquerade_by_source(&cidr)?; Ok(()) @@ -284,7 +302,7 @@ impl DeviceHandler { /// Disable MASQUERADE (NAT) for VPN interface (Linux only) pub fn disable_masquerade(&mut self) -> anyhow::Result<()> { let cidr = self.ip_mask_to_cidr(&self.private_ip, &self.mask)?; - + let sys_route = SysRoute::new(); sys_route.disable_masquerade_by_source(&cidr)?; Ok(()) @@ -302,10 +320,13 @@ impl DeviceHandler { /// This makes packets from local ciders appear as coming from virtual IP pub fn enable_snat(&mut self) -> anyhow::Result<()> { let sys_route = SysRoute::new(); - + for cidr in &self.local_ciders { sys_route.enable_snat_for_local_network(cidr, "", &self.private_ip)?; - tracing::info!("Enabled SNAT for local network {} -> {}", cidr, self.private_ip); + tracing::info!( + "Enabled SNAT for local network {cidr} -> {}", + self.private_ip + ); } Ok(()) } @@ -313,7 +334,7 @@ impl DeviceHandler { /// Disable SNAT for local network segments (Linux only) pub fn disable_snat(&mut self) -> anyhow::Result<()> { let sys_route = SysRoute::new(); - + for cidr in &self.local_ciders { sys_route.disable_snat_for_local_network(cidr, "", &self.private_ip)?; } @@ -324,27 +345,30 @@ impl DeviceHandler { /// This should be called after receiving HandshakeReplyFrame /// Maps destination IPs from mapped CIDR to real CIDR using iptables NETMAP #[cfg(target_os = "linux")] - pub fn setup_cidr_mapping(&mut self, cidr_mapping: &HashMap) -> anyhow::Result<()> { + pub fn setup_cidr_mapping( + &mut self, + cidr_mapping: &HashMap, + ) -> anyhow::Result<()> { let sys_route = SysRoute::new(); - + for (mapped_cidr, real_cidr) in cidr_mapping { // Add DNAT rule (iptables will check if it already exists) if let Err(e) = sys_route.enable_cidr_dnat(mapped_cidr, real_cidr) { - tracing::error!( - "Failed to add DNAT rule for {} -> {}: {}", - mapped_cidr, real_cidr, e - ); + tracing::error!("Failed to add DNAT rule for {mapped_cidr} -> {real_cidr}: {e}"); return Err(e); } - - tracing::info!("Added DNAT rule for CIDR mapping: {} -> {}", mapped_cidr, real_cidr); + + tracing::info!("Added DNAT rule for CIDR mapping: {mapped_cidr} -> {real_cidr}"); } - + Ok(()) } #[cfg(not(target_os = "linux"))] - pub fn setup_cidr_mapping(&mut self, _cidr_mapping: &HashMap) -> anyhow::Result<()> { + pub fn setup_cidr_mapping( + &mut self, + _cidr_mapping: &HashMap, + ) -> anyhow::Result<()> { Ok(()) } } @@ -353,4 +377,4 @@ impl Default for DeviceHandler { fn default() -> Self { Self::new() } -} \ No newline at end of file +} diff --git a/src/utils/sys_route.rs b/src/utils/sys_route.rs index 9c3c232..1c5fdbb 100644 --- a/src/utils/sys_route.rs +++ b/src/utils/sys_route.rs @@ -55,7 +55,7 @@ impl SysRoute { 1. Install iptables: sudo apt-get install iptables (Debian/Ubuntu) or sudo yum install iptables (RHEL/CentOS)\n\ 2. Run without --masq option" )), - Err(e) => Err(anyhow::anyhow!("Failed to check iptables: {}", e)), + Err(e) => Err(anyhow::anyhow!("Failed to check iptables: {e}")), } } @@ -107,7 +107,7 @@ impl SysRoute { let output = Command::new("ip") .args(["route", "add", dst, "via", gateway]) .output() - .map_err(|e| anyhow::anyhow!("Failed to execute ip command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute ip command: {e}"))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); @@ -126,7 +126,7 @@ impl SysRoute { let output = Command::new("ip") .args(["route", "del", dst, "via", gateway]) .output() - .map_err(|e| anyhow::anyhow!("Failed to execute ip command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute ip command: {e}"))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); @@ -145,7 +145,7 @@ impl SysRoute { let output = Command::new("route") .args(["-n", "add", "-net", dst, gateway]) .output() - .map_err(|e| anyhow::anyhow!("Failed to execute route command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute route command: {e}"))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); @@ -164,7 +164,7 @@ impl SysRoute { let output = Command::new("route") .args(["-n", "delete", "-net", dst, gateway]) .output() - .map_err(|e| anyhow::anyhow!("Failed to execute route command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute route command: {e}"))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); @@ -200,7 +200,7 @@ impl SysRoute { let output = Command::new("route") .args(&args) .output() - .map_err(|e| anyhow::anyhow!("Failed to execute route command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute route command: {e}"))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); @@ -233,7 +233,7 @@ impl SysRoute { let output = Command::new("route") .args(&["delete", &network, "mask", &mask]) .output() - .map_err(|e| anyhow::anyhow!("Failed to execute route command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute route command: {e}"))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); @@ -315,7 +315,7 @@ impl SysRoute { "MASQUERADE", ]) .output() - .map_err(|e| anyhow::anyhow!("Failed to execute iptables check command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute iptables check command: {e}"))?; if check_output.status.success() { tracing::debug!("MASQUERADE rule already exists for source {}", source_cidr); @@ -332,7 +332,7 @@ impl SysRoute { "-j", "MASQUERADE", ]) .output() - .map_err(|e| anyhow::anyhow!("Failed to execute iptables command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute iptables command: {e}"))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); @@ -356,7 +356,7 @@ impl SysRoute { "-j", "MASQUERADE", ]) .output() - .map_err(|e| anyhow::anyhow!("Failed to execute iptables command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute iptables command: {e}"))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); @@ -402,7 +402,7 @@ impl SysRoute { virtual_ip, ]) .output() - .map_err(|e| anyhow::anyhow!("Failed to execute iptables check command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute iptables check command: {e}"))?; if check_output.status.success() { tracing::debug!( @@ -428,7 +428,7 @@ impl SysRoute { virtual_ip, ]) .output() - .map_err(|e| anyhow::anyhow!("Failed to execute iptables command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute iptables command: {e}"))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); @@ -457,7 +457,7 @@ impl SysRoute { "--to-source", virtual_ip, ]) .output() - .map_err(|e| anyhow::anyhow!("Failed to execute iptables command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute iptables command: {e}"))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); @@ -538,7 +538,7 @@ impl SysRoute { Please install iptables and ensure your kernel supports NETMAP target.\n\ NETMAP requires Linux kernel 2.6.32+ with netfilter NETMAP module.") } else { - anyhow::anyhow!("Failed to execute iptables command: {}", e) + anyhow::anyhow!("Failed to execute iptables command: {e}") } })?; @@ -578,7 +578,7 @@ impl SysRoute { "--to", real_cidr, ]) .output() - .map_err(|e| anyhow::anyhow!("Failed to execute iptables command: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute iptables command: {e}"))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); From 120d739d94e9a67c13aacc83053749b9f9e4745f Mon Sep 17 00:00:00 2001 From: Banyc <36535895+Banyc@users.noreply.github.com> Date: Sun, 22 Mar 2026 04:03:24 +0000 Subject: [PATCH 10/12] fix: `http_json` returning `Option` value --- src/server/conf_agent.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/server/conf_agent.rs b/src/server/conf_agent.rs index b2f0a65..35b23d0 100644 --- a/src/server/conf_agent.rs +++ b/src/server/conf_agent.rs @@ -224,30 +224,28 @@ async fn http_json( request .headers_mut() .insert("Content-Type", "application/json".try_into().unwrap()); - let body = reqwest::Body::wrap(input.to_string()); - *request.body_mut() = Some(body); + let body = serde_json::to_vec(input)?; + *request.body_mut() = Some(body.into()); } if let Some(token) = token { request .headers_mut() - .insert("Authorization", format!("Bearer {}", token).try_into()?); + .insert("Authorization", format!("Bearer {token}").try_into()?); } let deadline = Instant::now() + Duration::from_secs(30); let client = reqwest::Client::new(); let response = timeout_at(deadline.into(), client.execute(request)).await??; let status = response.status(); - let body = timeout_at(deadline.into(), response.text()).await; + let body = timeout_at(deadline.into(), response.bytes()).await; if status != 200 { return Err(anyhow::anyhow!( - "Control plane returned error: {} - {:?}", - status, - body + "Control plane returned error: {status} - {body:?}" )); } let body = body??; let body = match &input { - Input::Get => None, - Input::Post(_) => serde_json::from_str(&body)?, + Input::Get => serde_json::from_slice(&body)?, + Input::Post(_) => None, }; Ok(body) } From 99354c68b5e93fb5a38358d3ea1dc00a0d8d0e0d Mon Sep 17 00:00:00 2001 From: Banyc <36535895+Banyc@users.noreply.github.com> Date: Sun, 22 Mar 2026 04:44:19 +0000 Subject: [PATCH 11/12] =?UTF-8?q?fix:=20peer.rs:629=20=E2=80=94=20`Protoco?= =?UTF-8?q?l::Ipv6`=20incorrectly=20assigns=20to=20`stun=5Faddr`=20instead?= =?UTF-8?q?=20of=20`remote=5Faddr`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/p2p/peer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/p2p/peer.rs b/src/client/p2p/peer.rs index 92361a2..9db1068 100644 --- a/src/client/p2p/peer.rs +++ b/src/client/p2p/peer.rs @@ -626,7 +626,7 @@ fn update_address(peer: &mut PeerMeta, new_addr: SocketAddr, protocol: Protocol) let new_addr = LastActive::dormant(Some(new_addr)); match protocol { Protocol::Stun => peer.stun_addr = new_addr, - Protocol::Ipv6 => peer.stun_addr = new_addr, + Protocol::Ipv6 => peer.remote_addr = new_addr, } } } From a8c4adeb5246461e3ce336af0b972d4082dda652 Mon Sep 17 00:00:00 2001 From: Banyc <36535895+Banyc@users.noreply.github.com> Date: Sun, 22 Mar 2026 04:49:57 +0000 Subject: [PATCH 12/12] styling --- src/client/http/mod.rs | 4 +- src/client/main.rs | 6 +- src/client/mod.rs | 8 +-- src/client/p2p/peer.rs | 52 +++++---------- src/client/p2p/stun.rs | 121 +++++++++++++++++----------------- src/client/p2p/udp_server.rs | 11 +--- src/client/prettylog.rs | 41 +++++------- src/client/relay.rs | 8 +-- src/codec/parser.rs | 2 +- src/network/tcp_connection.rs | 2 +- 10 files changed, 111 insertions(+), 144 deletions(-) diff --git a/src/client/http/mod.rs b/src/client/http/mod.rs index 73f6f10..57ba5ec 100644 --- a/src/client/http/mod.rs +++ b/src/client/http/mod.rs @@ -1,7 +1,7 @@ -pub mod models; pub mod cache; mod handlers; +pub mod models; pub mod server; // Re-export commonly used types -pub use models::*; \ No newline at end of file +pub use models::*; diff --git a/src/client/main.rs b/src/client/main.rs index 2bae471..de29873 100644 --- a/src/client/main.rs +++ b/src/client/main.rs @@ -107,13 +107,13 @@ async fn init_device( device_config: &HandshakeReplyFrame, enable_masq: bool, ) -> anyhow::Result { - tracing::info!("Initializing device with config: {:?}", device_config); + tracing::info!("Initializing device with config: {device_config:?}"); let mut dev = DeviceHandler::new(); let tun_index = dev.run(device_config, enable_masq).await?; // Log TUN index (Windows only) if let Some(idx) = tun_index { - tracing::info!("TUN interface index: {}", idx); + tracing::info!("TUN interface index: {idx}"); } dev.reload_route(device_config.peer_details.clone()).await; @@ -241,7 +241,7 @@ async fn handle_device_packet( return; } Err(e) => { - tracing::debug!("P2P send failed: {}, fallback to relay", e); + tracing::debug!("P2P send failed: {e}, fallback to relay"); } } } diff --git a/src/client/mod.rs b/src/client/mod.rs index 4076bab..c60e9d3 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,10 +1,10 @@ use clap::Parser; -mod relay; +pub mod http; pub mod main; -mod prettylog; pub mod p2p; -pub mod http; +mod prettylog; +mod relay; /// Default P2P UDP port for client-to-client direct connections /// @@ -51,4 +51,4 @@ pub struct Args { #[cfg(target_os = "linux")] #[arg(long)] pub masq: bool, -} \ No newline at end of file +} diff --git a/src/client/p2p/peer.rs b/src/client/p2p/peer.rs index 9db1068..076156c 100644 --- a/src/client/p2p/peer.rs +++ b/src/client/p2p/peer.rs @@ -376,18 +376,16 @@ impl PeerHandler { match frame { Frame::ProbeIPv6(probe) => { tracing::info!( - "Received probe ipv6 from peer {} at {}", - probe.identity, - remote + "Received probe ipv6 from peer {} at {remote}", + probe.identity ); self.peers .update_peer_active(&probe.identity, remote, Protocol::Ipv6); } Frame::ProbeHolePunch(probe) => { tracing::info!( - "Received probe hole punch from peer {} at {}", - probe.identity, - remote + "Received probe hole punch from peer {} at {remote}", + probe.identity ); self.peers .update_peer_active(&probe.identity, remote, Protocol::Stun); @@ -439,13 +437,11 @@ impl PeerHandler { SendResult::Success => return Ok(()), SendResult::Expired(elapsed) => { tracing::debug!( - "IPv6 connection to {} expired ({:?} ago), trying STUN", - peer_identity, - elapsed + "IPv6 connection to {peer_identity} expired ({elapsed:?} ago), trying STUN" ); } SendResult::NeverResponded => { - tracing::debug!("Peer {} IPv6 never responded, trying STUN", peer_identity); + tracing::debug!("Peer {peer_identity} IPv6 never responded, trying STUN"); } SendResult::NoAddress => { // No IPv6 address, try STUN @@ -466,17 +462,13 @@ impl PeerHandler { { SendResult::Success => Ok(()), SendResult::Expired(elapsed) => Err(anyhow::anyhow!( - "Peer {} STUN connection also expired ({:?} ago)", - peer_identity, - elapsed + "Peer {peer_identity} STUN connection also expired ({elapsed:?} ago)" )), SendResult::NeverResponded => Err(anyhow::anyhow!( - "Peer {} STUN address never responded", - peer_identity + "Peer {peer_identity} STUN address never responded" )), SendResult::NoAddress => Err(anyhow::anyhow!( - "Failed to send to peer {}: IPv6 unavailable/expired, STUN unavailable/expired", - peer_identity + "Failed to send to peer {peer_identity}: IPv6 unavailable/expired, STUN unavailable/expired" )), } } @@ -510,16 +502,11 @@ impl PeerHandler { // Connection is valid, send the packet match outbound_tx.send((data.to_vec(), vec![addr])).await { Ok(_) => { - tracing::debug!( - "Sent frame to peer {} via {}: {}", - peer_identity, - protocol, - addr - ); + tracing::debug!("Sent frame to peer {peer_identity} via {protocol}: {addr}"); SendResult::Success } Err(e) => { - tracing::error!("Failed to send via {}: {}", protocol, e); + tracing::error!("Failed to send via {protocol}: {e}"); SendResult::NeverResponded // Treat send error as connection problem } } @@ -571,7 +558,7 @@ async fn send_probes( let probe_data = match Parser::marshal(probe_frame, block.as_ref().as_ref()) { Ok(data) => data, Err(e) => { - tracing::error!("Failed to marshal {} probe: {}", protocol, e); + tracing::error!("Failed to marshal {protocol} probe: {e}"); return; } }; @@ -579,14 +566,9 @@ async fn send_probes( // Send to all peers let peer_addrs_display = format!("{peer_addrs:?}"); if let Err(e) = outbound_tx.send((probe_data.clone(), peer_addrs)).await { - tracing::warn!( - "Failed to send {} probe to {}: {:?}", - protocol, - peer_addrs_display, - e - ); + tracing::warn!("Failed to send {protocol} probe to {peer_addrs_display}: {e:?}"); } else { - tracing::info!("Sent {} probe to {:?}", protocol, peer_addrs_display); + tracing::info!("Sent {protocol} probe to {peer_addrs_display:?}"); } } @@ -597,7 +579,7 @@ fn parse_address(identity: &str, ip: &str, port: u16) -> Option { let ip = match ip.parse::() { Ok(ip) => ip, Err(e) => { - tracing::warn!("Invalid address for peer {}: {}", identity, e); + tracing::warn!("Invalid address for peer {identity}: {e}"); return None; } }; @@ -614,13 +596,11 @@ fn update_address(peer: &mut PeerMeta, new_addr: SocketAddr, protocol: Protocol) if old_addr != Some(new_addr) { tracing::info!( - "Update {} address for peer {}: {} -> {}", - protocol, + "Update {protocol} address for peer {}: {} -> {new_addr}", peer.identity, old_addr .map(|a| a.to_string()) .unwrap_or_else(|| "None".to_string()), - new_addr ); let new_addr = LastActive::dormant(Some(new_addr)); diff --git a/src/client/p2p/stun.rs b/src/client/p2p/stun.rs index f379880..2ad8fe9 100644 --- a/src/client/p2p/stun.rs +++ b/src/client/p2p/stun.rs @@ -4,9 +4,9 @@ //! to discover the client's public IP address and NAT type, which is essential for //! P2P connection establishment. -use std::net::{SocketAddr, IpAddr}; -use std::time::Duration; use anyhow::{Context, Result}; +use std::net::{IpAddr, SocketAddr}; +use std::time::Duration; /// NAT type classifications based on RFC 3489 and RFC 5780 /// @@ -20,23 +20,23 @@ use anyhow::{Context, Result}; pub enum NatType { /// No NAT detected, client is directly on the public internet OpenInternet, - + /// Full Cone NAT: Once an internal address is mapped to an external address, /// any external host can send packets to the internal host by sending to the mapped address FullCone, - + /// Restricted Cone NAT: External hosts can send packets only if the internal host /// has previously sent a packet to that external IP (port doesn't matter) RestrictedCone, - + /// Port-Restricted Cone NAT: External hosts can send packets only if the internal host /// has previously sent a packet to that specific external IP:port combination PortRestricted, - + /// Symmetric NAT: Different external mapping for each destination. /// Most difficult for P2P hole punching Symmetric, - + /// Unable to determine NAT type Unknown, } @@ -57,7 +57,7 @@ impl NatType { _ => 0.30, } } - + /// Returns human-readable description of the NAT type pub fn description(&self) -> &'static str { match self { @@ -76,13 +76,13 @@ impl NatType { pub struct StunDiscoveryResult { /// Public IP address as seen by the STUN server pub public_ip: IpAddr, - + /// Public port as seen by the STUN server pub public_port: u16, - + /// Detected NAT type pub nat_type: NatType, - + /// Local address used for the STUN query pub local_addr: SocketAddr, } @@ -101,7 +101,7 @@ impl StunDiscoveryResult { pub struct StunClient { /// List of STUN servers to query (format: "host:port") stun_servers: Vec, - + /// Timeout for STUN requests timeout: Duration, } @@ -122,7 +122,7 @@ impl StunClient { "global.stun.twilio.com:3478".to_string(), ]) } - + /// Creates a new STUN client with custom STUN servers /// /// # Arguments @@ -140,7 +140,7 @@ impl StunClient { timeout: Duration::from_secs(5), } } - + /// Sets the timeout for STUN requests /// /// # Arguments @@ -149,7 +149,7 @@ impl StunClient { self.timeout = timeout; self } - + /// Discovers public IP address and port by querying STUN servers /// /// This performs a simple STUN binding request to discover the client's @@ -170,17 +170,20 @@ impl StunClient { /// .await?; /// println!("Public address: {}:{}", public_ip, public_port); /// ``` - pub async fn discover_public_address(&self, local_port: u16) -> Result<(SocketAddr, IpAddr, u16)> { + pub async fn discover_public_address( + &self, + local_port: u16, + ) -> Result<(SocketAddr, IpAddr, u16)> { let local_addr = if local_port == 0 { "0.0.0.0:0" } else { &format!("0.0.0.0:{}", local_port) }; - + // Try each STUN server until one succeeds for stun_server in &self.stun_servers { tracing::debug!("Querying STUN server: {}", stun_server); - + match self.query_stun_server(local_addr, stun_server).await { Ok((local, ip, port)) => { tracing::info!( @@ -197,10 +200,10 @@ impl StunClient { } } } - + anyhow::bail!("All STUN servers failed") } - + /// Performs full STUN discovery including NAT type detection /// /// This performs a comprehensive STUN discovery that includes: @@ -222,7 +225,8 @@ impl StunClient { /// ``` pub async fn discover(&self, local_port: u16) -> Result { // Step 1: Discover public address - let (local_addr, public_ip, public_port) = self.discover_public_address(local_port) + let (local_addr, public_ip, public_port) = self + .discover_public_address(local_port) .await .context("Failed to discover public address")?; @@ -232,11 +236,13 @@ impl StunClient { public_port, local_addr.to_string(), ); - + // Step 2: Detect NAT type // For now, use a simplified detection based on address comparison - let nat_type = self.detect_nat_type_simple(local_addr, public_ip, public_port).await; - + let nat_type = self + .detect_nat_type_simple(local_addr, public_ip, public_port) + .await; + tracing::info!( "NAT type detected: {:?} ({})", nat_type, @@ -250,7 +256,7 @@ impl StunClient { local_addr, }) } - + /// Queries a single STUN server using the stunclient library async fn query_stun_server( &self, @@ -258,15 +264,15 @@ impl StunClient { stun_server: &str, ) -> Result<(SocketAddr, IpAddr, u16)> { use std::net::UdpSocket; - + // Create UDP socket - let socket = UdpSocket::bind(local_addr) - .context("Failed to bind UDP socket")?; + let socket = UdpSocket::bind(local_addr).context("Failed to bind UDP socket")?; let local_addr = socket.local_addr()?; // Set socket timeout - socket.set_read_timeout(Some(self.timeout)) + socket + .set_read_timeout(Some(self.timeout)) .context("Failed to set socket timeout")?; - + // Resolve STUN server address (may be hostname or IP) let server_addr: SocketAddr = if let Ok(addr) = stun_server.parse() { // Already a valid SocketAddr @@ -281,21 +287,20 @@ impl StunClient { .next() .context("No addresses resolved for STUN server")? }; - + // Create STUN client let stun_client = stunclient::StunClient::new(server_addr); - + // Query external address - let external_addr = tokio::task::spawn_blocking(move || { - stun_client.query_external_address(&socket) - }) - .await - .context("STUN query task panicked")? - .context("Failed to get external address")?; - + let external_addr = + tokio::task::spawn_blocking(move || stun_client.query_external_address(&socket)) + .await + .context("STUN query task panicked")? + .context("Failed to get external address")?; + Ok((local_addr, external_addr.ip(), external_addr.port())) } - + /// Simplified NAT type detection based on address comparison /// /// This is a basic heuristic: @@ -314,24 +319,24 @@ impl StunClient { ) -> NatType { let local_ip = local_addr.ip(); let local_port = local_addr.port(); - + // Check if we're directly on the public internet if local_ip == public_ip && local_port == public_port { return NatType::OpenInternet; } - + // If port is preserved, likely Full Cone NAT if local_port == public_port { tracing::debug!("Port preserved, likely Full Cone NAT"); return NatType::FullCone; } - + // Port changed, conservatively assume Port-Restricted Cone // (most common in modern routers) tracing::debug!("Port changed, assuming Port-Restricted Cone NAT"); NatType::PortRestricted } - + /// Advanced NAT type detection using RFC 5780 behavioral tests /// /// This would require: @@ -345,7 +350,7 @@ impl StunClient { // TODO: Implement full RFC 5780 NAT type detection // This requires a STUN server that supports RFC 5780 extensions // Most public STUN servers only support basic RFC 5389 - + tracing::warn!("Advanced NAT detection not yet implemented, using simplified detection"); Ok(NatType::Unknown) } @@ -360,13 +365,16 @@ impl Default for StunClient { #[cfg(test)] mod tests { use super::*; - + #[test] fn test_nat_type_descriptions() { - assert_eq!(NatType::OpenInternet.description(), "No NAT (Public Internet)"); + assert_eq!( + NatType::OpenInternet.description(), + "No NAT (Public Internet)" + ); assert_eq!(NatType::FullCone.description(), "Full Cone NAT (Easy P2P)"); } - + #[test] fn test_hole_punch_success_rates() { // Best case: both on public internet @@ -374,24 +382,20 @@ mod tests { NatType::OpenInternet.hole_punch_success_rate(&NatType::OpenInternet), 1.0 ); - + // Worst case: both symmetric NAT - assert!( - NatType::Symmetric.hole_punch_success_rate(&NatType::Symmetric) < 0.2 - ); - + assert!(NatType::Symmetric.hole_punch_success_rate(&NatType::Symmetric) < 0.2); + // Good case: both full cone - assert!( - NatType::FullCone.hole_punch_success_rate(&NatType::FullCone) > 0.9 - ); + assert!(NatType::FullCone.hole_punch_success_rate(&NatType::FullCone) > 0.9); } - + #[tokio::test] #[ignore] // Requires network access async fn test_stun_discovery() { let client = StunClient::new(); let result = client.discover_public_address(0).await; - + // This test is ignored by default as it requires internet access // Run with: cargo test test_stun_discovery -- --ignored if let Ok((_, ip, port)) = result { @@ -400,4 +404,3 @@ mod tests { } } } - diff --git a/src/client/p2p/udp_server.rs b/src/client/p2p/udp_server.rs index f1baeb3..2fae6b9 100644 --- a/src/client/p2p/udp_server.rs +++ b/src/client/p2p/udp_server.rs @@ -190,7 +190,7 @@ impl UDPServer { }; if let Err(e) = socket.send_to(data, remote).await { - tracing::error!("Failed to send {} packet to {}: {:?}", protocol, remote, e); + tracing::error!("Failed to send {protocol} packet to {remote}: {e:?}"); } } } @@ -234,12 +234,7 @@ impl UDPServer { // Forward to PeerHandler for decryption and protocol processing if let Err(e) = self.input_tx.send((packet, remote)).await { - tracing::error!( - "Failed to forward {} packet from {}: {:?}", - protocol, - remote, - e - ); + tracing::error!("Failed to forward {protocol} packet from {remote}: {e:?}"); } // Reset buffer for next packet (optional but good practice) @@ -248,7 +243,7 @@ impl UDPServer { } Err(e) => { // Socket errors are fatal - we can't recover from a broken socket - tracing::error!("UDP {} recv_from error: {}", protocol, e); + tracing::error!("UDP {protocol} recv_from error: {e}"); Err(e.into()) } } diff --git a/src/client/prettylog.rs b/src/client/prettylog.rs index 1da552d..704363d 100644 --- a/src/client/prettylog.rs +++ b/src/client/prettylog.rs @@ -76,18 +76,18 @@ pub async fn get_status(relay: &RelayHandler, peer: Option<&[PeerStatus]>, dev: let prefix = if is_last { "└─" } else { "├─" }; let continuation = if is_last { " " } else { "│" }; - println!(" {} Peer: {}", prefix, status.name); + println!(" {prefix} Peer: {}", status.name); // IPv6 Direct Connection let ipv6_state = match (&status.ipv6_addr, &status.ipv6_last_active) { (None, _) => "❌ No Address".to_string(), - (Some(addr), None) => format!("⏳ Connecting... ({})", addr), + (Some(addr), None) => format!("⏳ Connecting... ({addr})"), (Some(addr), Some(last)) => { let elapsed = last.elapsed().as_secs(); if elapsed < 15 { - format!("✅ Active ({}s ago, {})", elapsed, addr) + format!("✅ Active ({elapsed}s ago, {addr})") } else { - format!("⚠️ Inactive ({}s ago, {})", elapsed, addr) + format!("⚠️ Inactive ({elapsed}s ago, {addr})") } } }; @@ -96,17 +96,17 @@ pub async fn get_status(relay: &RelayHandler, peer: Option<&[PeerStatus]>, dev: // STUN Hole-Punched Connection let stun_state = match (&status.stun_addr, &status.stun_last_active) { (None, _) => "❌ No Address".to_string(), - (Some(addr), None) => format!("⏳ Connecting... ({})", addr), + (Some(addr), None) => format!("⏳ Connecting... ({addr})"), (Some(addr), Some(last)) => { let elapsed = last.elapsed().as_secs(); if elapsed < 15 { - format!("✅ Active ({}s ago, {})", elapsed, addr) + format!("✅ Active ({elapsed}s ago, {addr})") } else { - format!("⚠️ Inactive ({}s ago, {})", elapsed, addr) + format!("⚠️ Inactive ({elapsed}s ago, {addr})") } } }; - println!(" {} └─ STUN: {}", continuation, stun_state); + println!(" {continuation} └─ STUN: {stun_state}"); } } } else { @@ -147,38 +147,31 @@ pub async fn get_status(relay: &RelayHandler, peer: Option<&[PeerStatus]>, dev: "Offline".to_string() } else { let elapsed = now.saturating_sub(peer.last_active); - format!("{}s ago", elapsed) + format!("{elapsed}s ago") }; - println!( - " {} {} {} ({})", - prefix, status_icon, peer.name, online_info - ); - println!(" {} ├─ Private IP: {}", continuation, peer.private_ip); + println!(" {prefix} {status_icon} {} ({online_info})", peer.name); + println!(" {continuation} ├─ Private IP: {}", peer.private_ip); if !peer.ciders.is_empty() { - println!( - " {} ├─ Routes: {}", - continuation, - peer.ciders.join(", ") - ); + println!(" {continuation} ├─ Routes: {}", peer.ciders.join(", ")); } if !peer.ipv6.is_empty() { println!( - " {} ├─ IPv6: {}:{}", - continuation, peer.ipv6, peer.port + " {continuation} ├─ IPv6: [{}]:{}", + peer.ipv6, peer.port ); } if !peer.stun_ip.is_empty() { println!( - " {} └─ STUN: {}:{}", - continuation, peer.stun_ip, peer.stun_port + " {continuation} └─ STUN: {}:{}", + peer.stun_ip, peer.stun_port ); } else { // Adjust last item if no stun_ip - println!(" {} └─ STUN: Not configured", continuation); + println!(" {continuation} └─ STUN: Not configured"); } } } diff --git a/src/client/relay.rs b/src/client/relay.rs index 26e23a0..97c7592 100644 --- a/src/client/relay.rs +++ b/src/client/relay.rs @@ -211,17 +211,13 @@ impl RelayClient { } async fn connect(&self) -> anyhow::Result> { - let conn = create_connection( + create_connection( ConnectionConfig::TCP(TCPConnectionConfig { server_addr: self.cfg.server_addr.clone(), }), self.block.clone(), ) - .await; - match conn { - Ok(conn) => Ok(conn), - Err(e) => Err(e), - } + .await } async fn handshake( diff --git a/src/codec/parser.rs b/src/codec/parser.rs index 744eb76..0cfc0de 100644 --- a/src/codec/parser.rs +++ b/src/codec/parser.rs @@ -8,8 +8,8 @@ use crate::codec::errors::FrameError; use crate::codec::frame::*; use crate::crypto::Block; use anyhow::Context; -use serde::de::DeserializeOwned; use serde::Serialize; +use serde::de::DeserializeOwned; /// Protocol magic number for frame validation const MAGIC: u32 = 0x91929394; diff --git a/src/network/tcp_connection.rs b/src/network/tcp_connection.rs index 8599b31..fc8ba44 100644 --- a/src/network/tcp_connection.rs +++ b/src/network/tcp_connection.rs @@ -143,7 +143,7 @@ impl ConnRead for TcpConnection { }; } Ok(Ok(n)) => { - tracing::debug!("read {} bytes", n) + tracing::debug!("read {n} bytes") } Ok(Err(e)) => return Err(e.into()), Err(_) => return Err(anyhow::anyhow!("read timeout")),