First
This commit is contained in:
2
.config/vpn_config.toml
Normal file
2
.config/vpn_config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[mode.Router]
|
||||
bind_address = "0.0.0.0:443"
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
1485
Cargo.lock
generated
Normal file
1485
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
Cargo.toml
Normal file
26
Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "xvpn"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.37", features = ["derive"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
toml = "1.0.3"
|
||||
tokio = { version = "1.49.0", features = [
|
||||
"macros",
|
||||
"rt-multi-thread",
|
||||
"time",
|
||||
"fs",
|
||||
"net",
|
||||
"io-util",
|
||||
"sync",
|
||||
"signal",
|
||||
] }
|
||||
anyhow = "1.0.102"
|
||||
uuid = { version = "1.21.0", features = ["v4", "serde"] }
|
||||
ipnet = { version = "2.11.0", features = ["serde"] }
|
||||
base64 = "0.22.1"
|
||||
tun-rs = "2.8.2"
|
||||
chrono = "0.4.44"
|
||||
131
src/client.rs
Normal file
131
src/client.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
use std::net::IpAddr;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use ipnet::Ipv4Net;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use crate::router::{CLIENT_REGISTER_TIMEOUT, CliRegMessages, RouterMessages, SERVER_PACKET_SIZE};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Args)]
|
||||
pub struct ClientCfg {
|
||||
/// The server endpoint to connect to in host:port or ip:port format.
|
||||
#[arg(long, short)]
|
||||
pub server: String,
|
||||
|
||||
/// The local interface IP address (example: 10.8.0.2).
|
||||
#[arg(long = "interface-ip")]
|
||||
pub interface_ip: IpAddr,
|
||||
|
||||
/// The local interface name.
|
||||
#[arg(long = "interface-name", default_value = "xvpn0")]
|
||||
pub interface_name: String,
|
||||
|
||||
/// Local routes in CIDR format.
|
||||
/// Example: --local-route 1.1.1.1/32,10.0.0.0/24
|
||||
#[arg(long = "local-route", visible_alias = "lr", value_delimiter = ',')]
|
||||
pub local_routes: Vec<Ipv4Net>,
|
||||
}
|
||||
|
||||
pub async fn start(config: ClientCfg) -> Result<()> {
|
||||
println!("Starting client with config: {:?}", config);
|
||||
|
||||
let stream = tokio::net::TcpStream::connect(&config.server).await?;
|
||||
//stream.set_nodelay(true)?;
|
||||
let (mut rx, mut tx) = stream.into_split();
|
||||
// let client_stream = ClientStream::new(tx);
|
||||
|
||||
let mut buf = vec![0u8; SERVER_PACKET_SIZE];
|
||||
register_client(&mut rx, &mut tx, config, &mut buf).await?;
|
||||
|
||||
println!("Client registration successful. Entering main loop to receive messages from router...");
|
||||
loop {
|
||||
tokio::select! {
|
||||
msg = rx.read(&mut buf) => {
|
||||
match msg {
|
||||
Ok(0) => {
|
||||
println!("Connection to router closed by peer.");
|
||||
return Ok(());
|
||||
}
|
||||
Ok(n) => {
|
||||
println!("Received {} bytes from router: {:?}", n, RouterMessages::from_slice(&buf[..n]));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error reading from router: {}", e);
|
||||
return Err(anyhow::anyhow!(format!("Error reading from router: {}", e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ok(())
|
||||
}
|
||||
|
||||
pub async fn register_client(
|
||||
rx: &mut OwnedReadHalf,
|
||||
tx: &mut OwnedWriteHalf,
|
||||
config: ClientCfg,
|
||||
buf: &mut [u8],
|
||||
) -> Result<()> {
|
||||
let register_msg = RouterMessages::CliReg(CliRegMessages::Reg(config));
|
||||
let mut client_registration_timeout =
|
||||
tokio::time::interval_at(Instant::now() + CLIENT_REGISTER_TIMEOUT, CLIENT_REGISTER_TIMEOUT);
|
||||
tx.write_all(®ister_msg.to_bytes()).await?;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
msg = rx.read(buf) => {
|
||||
match msg {
|
||||
Ok(0) => {
|
||||
let msg = "Connection closed by router while waiting for registration confirmation.";
|
||||
eprintln!("{}", msg);
|
||||
return Err(anyhow::anyhow!(msg));
|
||||
}
|
||||
Ok(n) => {
|
||||
let response = RouterMessages::from_slice(&buf[..n]);
|
||||
println!("Received registration response from router: {:?}", response);
|
||||
match response {
|
||||
RouterMessages::CliReg(CliRegMessages::RegOk(uuid)) => {
|
||||
println!("Client registration successful with UUID: {}", uuid);
|
||||
return Ok(());
|
||||
}
|
||||
RouterMessages::CliReg(CliRegMessages::RegFailed(err_msg)) => {
|
||||
eprintln!("Client registration failed: {}", err_msg);
|
||||
return Err(anyhow::anyhow!(format!("Client registration failed: {}", err_msg)));
|
||||
}
|
||||
_ => {
|
||||
let msg = "Unexpected message type received during client registration.";
|
||||
eprintln!("{}", msg);
|
||||
return Err(anyhow::anyhow!(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error reading from router during client registration: {}", e);
|
||||
return Err(anyhow::anyhow!(format!("Error reading from router: {}", e)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
_= client_registration_timeout.tick() => {
|
||||
let msg = "Client registration timed out waiting for confirmation from router.";
|
||||
eprintln!("{}", msg);
|
||||
eprintln!("Closing connection with Server");
|
||||
tx.shutdown().await?;
|
||||
return Err(anyhow::anyhow!(msg));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
// Ok(())
|
||||
}
|
||||
82
src/config.rs
Normal file
82
src/config.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use anyhow::Result;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub static CONFIG_DIR: &str = ".config";
|
||||
pub static CONFIG_FILE_NAME: &str = "vpn_config.toml";
|
||||
|
||||
pub async fn file_path<T>() -> PathBuf {
|
||||
let full_name = std::any::type_name::<T>();
|
||||
let struct_name = full_name.split("::").last().unwrap();
|
||||
let mut path = PathBuf::from(CONFIG_DIR);
|
||||
path.push(struct_name);
|
||||
path.set_extension("toml");
|
||||
path
|
||||
}
|
||||
|
||||
pub async fn file_path_with_name(name: &str) -> PathBuf {
|
||||
let mut path = PathBuf::from(CONFIG_DIR);
|
||||
path.push(name);
|
||||
if path.extension().is_none() {
|
||||
path.set_extension("toml");
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
pub async fn config_load_or_default<T>() -> T
|
||||
where
|
||||
T: Default + DeserializeOwned,
|
||||
{
|
||||
let path = file_path::<T>().await;
|
||||
match tokio::fs::read(&path).await {
|
||||
Ok(content) => match toml::from_slice(&content) {
|
||||
Ok(config) => config,
|
||||
Err(_) => T::default(),
|
||||
},
|
||||
Err(_) => T::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn config_load<T>() -> Result<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let path = file_path::<T>().await;
|
||||
let content = tokio::fs::read(&path).await?;
|
||||
let config = toml::from_slice(&content)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub async fn config_load_from_file<T>(name: &str) -> Result<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let path = file_path_with_name(name).await;
|
||||
let content = tokio::fs::read(&path).await?;
|
||||
let config = toml::from_slice(&content)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub async fn save<T>(data: &T) -> std::io::Result<()>
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
let path = file_path::<T>().await;
|
||||
if let Some(parent) = path.parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
}
|
||||
let content = toml::to_string(data).unwrap();
|
||||
tokio::fs::write(path, content).await
|
||||
}
|
||||
|
||||
pub async fn save_to_file<T>(name: &str, data: &T) -> std::io::Result<()>
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
let path = file_path_with_name(name).await;
|
||||
if let Some(parent) = path.parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
}
|
||||
let content = toml::to_string(data).unwrap();
|
||||
tokio::fs::write(path, content).await
|
||||
}
|
||||
90
src/main.rs
Normal file
90
src/main.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
pub mod router;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use std::fmt::Display;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use crate::client::ClientCfg;
|
||||
use crate::config::{CONFIG_FILE_NAME, config_load_from_file, file_path_with_name, save_to_file};
|
||||
|
||||
#[derive(Parser, Debug, Serialize, Deserialize)]
|
||||
#[command(name = "x_vpn", version, about = "VPN mesh CLI (step 1: options only)")]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
mode: OpModes,
|
||||
#[arg(long, global = true, help = "Enable debug logs", default_value = "false")]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
debug: bool,
|
||||
#[arg(long, short, help = "Save the configuration to a file", default_value = "false")]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
save_config: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand, Clone, Serialize, Deserialize)]
|
||||
pub enum OpModes {
|
||||
/// Run the node in client mode.
|
||||
Client(ClientCfg),
|
||||
|
||||
/// Run the node in router mode.
|
||||
Router {
|
||||
/// The local address to listen on in ip:port format.
|
||||
#[arg(
|
||||
long = "bind-address",
|
||||
visible_alias = "bind_address",
|
||||
short,
|
||||
default_value = "0.0.0.0:443"
|
||||
)]
|
||||
bind_address: SocketAddr,
|
||||
},
|
||||
}
|
||||
|
||||
impl Display for OpModes {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
OpModes::Client { .. } => write!(f, "Client"),
|
||||
OpModes::Router { .. } => write!(f, "Router"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
// Check if some commanline or check if config file.
|
||||
let commandline = if env::args().nth(1).is_some() {
|
||||
Cli::parse()
|
||||
} else {
|
||||
match config_load_from_file::<Cli>(CONFIG_FILE_NAME).await {
|
||||
Ok(config) => {
|
||||
println!(
|
||||
"Loaded configuration from file {}.",
|
||||
file_path_with_name(CONFIG_FILE_NAME).await.to_str().unwrap()
|
||||
);
|
||||
config
|
||||
}
|
||||
Err(_) => Cli::parse(),
|
||||
}
|
||||
};
|
||||
|
||||
if commandline.save_config {
|
||||
if let Err(e) = save_to_file(CONFIG_FILE_NAME, &commandline).await {
|
||||
eprintln!("Failed to save configuration: {}", e);
|
||||
} else {
|
||||
println!("Configuration saved successfully.");
|
||||
}
|
||||
}
|
||||
println!("Parsed command line arguments: {:?}", commandline);
|
||||
|
||||
match commandline.mode {
|
||||
OpModes::Client(client) => client::start(client).await?,
|
||||
OpModes::Router { bind_address } => {
|
||||
router::start(bind_address).await;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
264
src/router.rs
Normal file
264
src/router.rs
Normal file
@@ -0,0 +1,264 @@
|
||||
use anyhow::Result;
|
||||
use chrono::Utc;
|
||||
|
||||
use ipnet::Ipv4Net;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, net::SocketAddr, sync::Arc, time::Duration};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::{
|
||||
TcpStream,
|
||||
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||
},
|
||||
sync::{Mutex, RwLock},
|
||||
time::Instant,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::client::ClientCfg;
|
||||
|
||||
pub static KEEP_ALIVE_INTERVAL: Duration = tokio::time::Duration::from_secs(30);
|
||||
pub static CLIENT_REGISTER_TIMEOUT: Duration = tokio::time::Duration::from_millis(100);
|
||||
pub const SERVER_PACKET_SIZE: usize = 1024 * 9;
|
||||
|
||||
pub trait ReceiverTrait {}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum RouterMessages {
|
||||
CliReg(CliRegMessages),
|
||||
KeepAlive(i64),
|
||||
Data(Vec<u8>),
|
||||
Quit(String),
|
||||
Uknown(String),
|
||||
}
|
||||
impl RouterMessages {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
serde_json::to_vec(self).expect("Unable to serialize RouteMessages")
|
||||
}
|
||||
pub fn from_slice(slice: &[u8]) -> Self {
|
||||
serde_json::from_slice(slice).unwrap_or(RouterMessages::Uknown(
|
||||
String::from_utf8(slice.to_vec()).unwrap_or_else(|b| format!("Invalid UTF-8: {:?}", b.as_bytes())),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum CliRegMessages {
|
||||
Reg(ClientCfg),
|
||||
RegOk(Uuid),
|
||||
RegFailed(String),
|
||||
Uknown(String),
|
||||
}
|
||||
|
||||
impl CliRegMessages {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
serde_json::to_vec(self).expect("Unable to serialize RegisterMessages")
|
||||
}
|
||||
pub fn from_slice(slice: &[u8]) -> Self {
|
||||
serde_json::from_slice(slice).unwrap_or(CliRegMessages::Uknown(
|
||||
String::from_utf8(slice.to_vec()).unwrap_or_else(|b| format!("Invalid UTF-8: {:?}", b.as_bytes())),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VPNClient {
|
||||
id: Uuid,
|
||||
stream: ClientStream,
|
||||
}
|
||||
|
||||
impl VPNClient {
|
||||
pub fn new(id: Uuid, stream: ClientStream) -> Self {
|
||||
Self { id, stream }
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Uuid {
|
||||
self.id
|
||||
}
|
||||
pub async fn send(&self, msg: RouterMessages) -> Result<()> {
|
||||
self.stream.send(msg).await
|
||||
}
|
||||
|
||||
pub async fn close(&self) -> Result<()> {
|
||||
self.stream.close().await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClientStream {
|
||||
tx: Arc<Mutex<OwnedWriteHalf>>,
|
||||
}
|
||||
impl ClientStream {
|
||||
pub fn new(tx: OwnedWriteHalf) -> Self {
|
||||
Self {
|
||||
// write can be shared
|
||||
tx: Arc::new(Mutex::new(tx)),
|
||||
// read is done only by one task
|
||||
}
|
||||
}
|
||||
pub async fn send(&self, msg: RouterMessages) -> Result<()> {
|
||||
let bytes = msg.to_bytes();
|
||||
let mut tx = self.tx.lock().await;
|
||||
tx.write_all(&bytes).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn close(&self) -> Result<()> {
|
||||
let mut tx = self.tx.lock().await;
|
||||
tx.shutdown().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Router {
|
||||
clients: Arc<RwLock<HashMap<Uuid, VPNClient>>>,
|
||||
routing_table: Arc<RwLock<HashMap<Ipv4Net, Uuid>>>,
|
||||
}
|
||||
impl Router {
|
||||
pub async fn register_client(&self, routing_table: &[Ipv4Net], vpn_client: VPNClient) -> Result<()> {
|
||||
let id = Uuid::new_v4();
|
||||
for net in routing_table {
|
||||
self.routing_table.write().await.insert(*net, id);
|
||||
}
|
||||
|
||||
self.clients.write().await.insert(id, vpn_client);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start(bind_address: SocketAddr) {
|
||||
println!("Starting router on {}...", bind_address);
|
||||
|
||||
let router = Router::default();
|
||||
|
||||
let socket = tokio::net::TcpListener::bind(bind_address).await.unwrap();
|
||||
println!("Router is listening on {}...", bind_address);
|
||||
|
||||
loop {
|
||||
match socket.accept().await {
|
||||
Ok((tcp_stream, addr)) => {
|
||||
println!("Accepted connection from {}", addr);
|
||||
//Clone the router for the new task
|
||||
let router = router.clone();
|
||||
tokio::spawn(async move {
|
||||
println!("Handling connection from {}", addr);
|
||||
match handle_client(router.clone(), tcp_stream).await {
|
||||
Ok(_) => println!("Finished handling connection from {}", addr),
|
||||
Err(e) => eprintln!("Error handling connection from {}: {}", addr, e),
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to accept connection: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_client(router: Router, stream: TcpStream) -> Result<()> {
|
||||
let (mut rx, tx) = stream.into_split();
|
||||
let vpn_client = VPNClient::new(Uuid::new_v4(), ClientStream::new(tx));
|
||||
|
||||
let mut keep_alive_tick = tokio::time::interval_at(Instant::now() + KEEP_ALIVE_INTERVAL, KEEP_ALIVE_INTERVAL);
|
||||
let mut buf = vec![0u8; SERVER_PACKET_SIZE];
|
||||
|
||||
match client_init(&mut rx, &vpn_client, &mut buf).await {
|
||||
Ok(client) => {
|
||||
println!(
|
||||
"Client {} registered with routing table: {:?}",
|
||||
vpn_client.id(),
|
||||
client.local_routes
|
||||
);
|
||||
println!("Registering client {} with router...", vpn_client.id());
|
||||
router.register_client(&client.local_routes, vpn_client.clone()).await?;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to initialize client {}: {}", vpn_client.id(), e);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
loop {
|
||||
tokio::select! {
|
||||
msg = rx.read(&mut buf) => {
|
||||
match msg {
|
||||
Ok(0) => {
|
||||
println!("Client {} closed the connection", vpn_client.id());
|
||||
return Ok(());
|
||||
}
|
||||
Ok(n) => {
|
||||
let msg = RouterMessages::from_slice(&buf[..n]);
|
||||
println!("Received message from client {}: {:?}", vpn_client.id(), msg);
|
||||
// Here you would implement the logic to handle messages from the client, such as routing data to other clients based on the routing table
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error reading from client {}: {}", vpn_client.id(), e);
|
||||
return Err(anyhow::anyhow!(format!("Error reading from client: {}", e)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_= keep_alive_tick.tick() => {
|
||||
// Send keep-alive message to the client
|
||||
println!("Sent keep-alive message to client");
|
||||
vpn_client.send(RouterMessages::KeepAlive(Utc::now().timestamp_micros())).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Ok(())
|
||||
}
|
||||
|
||||
pub async fn client_init(rx: &mut OwnedReadHalf, vpn_client: &VPNClient, buf: &mut [u8]) -> Result<ClientCfg> {
|
||||
let mut client_registration_timeout =
|
||||
tokio::time::interval_at(Instant::now() + CLIENT_REGISTER_TIMEOUT, CLIENT_REGISTER_TIMEOUT);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
msg = rx.read(buf) => {
|
||||
match msg {
|
||||
Ok(0) => {
|
||||
println!("Client {} closed the connection during registration", vpn_client.id());
|
||||
return Err(anyhow::anyhow!("Client closed the connection during registration"));
|
||||
}
|
||||
Ok(n) => {
|
||||
let msg = RouterMessages::from_slice(&buf[..n]);
|
||||
match msg {
|
||||
RouterMessages::CliReg(CliRegMessages::Reg(client))=> {
|
||||
println!("Received client registration with routing table: {:?}", client.local_routes);
|
||||
let uuid = Uuid::new_v4();
|
||||
vpn_client.send(RouterMessages::CliReg(CliRegMessages::RegOk(uuid))).await?;
|
||||
return Ok(client);
|
||||
}
|
||||
router_msg => {
|
||||
let msg = format!("Expected client registration message, but received: {:?}", router_msg);
|
||||
eprintln!("{}", msg);
|
||||
eprintln!("Closing connection with client {}", vpn_client.id());
|
||||
vpn_client.close().await?;
|
||||
return Err(anyhow::anyhow!(msg));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error reading from client {} during registration: {}", vpn_client.id(), e);
|
||||
return Err(anyhow::anyhow!(format!("Error reading from client during registration: {}", e)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
_ = client_registration_timeout.tick() => {
|
||||
let msg = format!("Client registration timed out after {}ms", (CLIENT_REGISTER_TIMEOUT.as_millis()));
|
||||
vpn_client.send(RouterMessages::Quit(msg.clone())).await?;
|
||||
vpn_client.close().await?;
|
||||
return Err(anyhow::anyhow!(msg));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user