Add new dependencies and implement TUN interface initialization

- Updated Cargo.toml to include `etherparse` and enable async feature for `tun-rs`.
- Added `ip_match_network` function in network module for IP matching.
- Implemented TUN interface initialization in `tun.rs`.
- Enhanced client handling in `client.rs` to support new features and improved message handling.
- Refactored router messages to use a structured `VpnPacket`.
- Updated settings for spell checking in VSCode.
This commit is contained in:
2026-02-27 11:30:08 +01:00
parent 0c5b860324
commit fb9d6ca9c8
8 changed files with 241 additions and 20 deletions

7
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"cSpell.words": [
"etherparse",
"nodelay",
"xvpn"
]
}

124
Cargo.lock generated
View File

@@ -67,6 +67,36 @@ version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "async-channel"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
dependencies = [
"concurrent-queue",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-task"
version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
version = "1.5.0"
@@ -85,6 +115,19 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "blocking"
version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
dependencies = [
"async-channel",
"async-task",
"futures-io",
"futures-lite",
"piper",
]
[[package]]
name = "bumpalo"
version = "3.20.2"
@@ -205,6 +248,15 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@@ -221,6 +273,12 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "encoding_rs"
version = "0.8.35"
@@ -246,6 +304,36 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "etherparse"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b119b9796ff800751a220394b8b3613f26dd30c48f254f6837e64c464872d1c7"
dependencies = [
"arrayvec",
]
[[package]]
name = "event-listener"
version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
dependencies = [
"event-listener",
"pin-project-lite",
]
[[package]]
name = "fastrand"
version = "2.3.0"
@@ -285,6 +373,22 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-io"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
[[package]]
name = "futures-lite"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
dependencies = [
"futures-core",
"pin-project-lite",
]
[[package]]
name = "futures-sink"
version = "0.3.32"
@@ -623,6 +727,12 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "parking"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "paste"
version = "1.0.15"
@@ -635,6 +745,17 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "piper"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
dependencies = [
"atomic-waker",
"fastrand",
"futures-io",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
@@ -927,6 +1048,7 @@ version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aed038a26d2b6a7906a62b3d0c42fb77541b0a695b9c2b9489a0c25c093ce8b"
dependencies = [
"blocking",
"byteorder",
"bytes",
"c2rust-bitfields",
@@ -940,6 +1062,7 @@ dependencies = [
"nix 0.31.1",
"route_manager",
"scopeguard",
"tokio",
"widestring",
"windows-sys 0.61.2",
"winreg",
@@ -1469,6 +1592,7 @@ dependencies = [
"base64",
"chrono",
"clap",
"etherparse",
"ipnet",
"serde",
"serde_json",

View File

@@ -22,5 +22,6 @@ 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"
tun-rs = { version = "2.8.2", features = ["async"] }
chrono = "0.4.44"
etherparse = "0.19.0"

View File

@@ -1,6 +1,5 @@
use std::net::IpAddr;
use anyhow::Result;
use chrono::Utc;
use clap::Args;
use ipnet::Ipv4Net;
use serde::{Deserialize, Serialize};
@@ -11,7 +10,21 @@ use tokio::{
time::Instant,
};
use crate::router::{CLIENT_REGISTER_TIMEOUT, CliRegMessages, RouterMessages, SERVER_PACKET_SIZE};
use crate::{
network::ip_match_network,
router::{CLIENT_REGISTER_TIMEOUT, CliRegMessages, RouterMessages, SERVER_PACKET_SIZE},
tun::inti_tun_interface,
};
pub struct ClientStaTistic {
pub last_keep_alive: Option<chrono::DateTime<Utc>>,
pub keep_alive_count: usize,
pub total_data_received: usize,
pub total_data_sent: usize,
pub last_data_received: Option<chrono::DateTime<Utc>>,
pub last_data_sent: Option<chrono::DateTime<Utc>>,
pub latency_ms: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Args)]
pub struct ClientCfg {
@@ -19,9 +32,9 @@ pub struct ClientCfg {
#[arg(long, short)]
pub server: String,
/// The local interface IP address (example: 10.8.0.2).
/// The local interface IP address (example: 10.8.0.2/32).
#[arg(long = "interface-ip")]
pub interface_ip: IpAddr,
pub interface_ip: Ipv4Net,
/// The local interface name.
#[arg(long = "interface-name", default_value = "xvpn0")]
@@ -31,6 +44,10 @@ pub struct ClientCfg {
/// 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>,
/// MTU for the TUN interface.
/// If not specified, the default MTU of the system will be used.
#[arg(long = "mtu", default_value = "1400")]
pub mtu: u16,
}
pub async fn start(config: ClientCfg) -> Result<()> {
@@ -41,27 +58,51 @@ pub async fn start(config: ClientCfg) -> Result<()> {
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?;
let mut vpn_buf = vec![0u8; SERVER_PACKET_SIZE];
let mut tun_buf = vec![0u8; config.mtu as usize];
register_client(&mut rx, &mut tx, &config, &mut vpn_buf).await?;
let tun_device = inti_tun_interface(&config).await?;
println!("Client registration successful. Entering main loop to receive messages from router...");
loop {
tokio::select! {
msg = rx.read(&mut buf) => {
msg = rx.read(&mut vpn_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]));
match RouterMessages::from_slice(&vpn_buf[..n]){
RouterMessages::KeepAlive(timestamp) => {
println!("Received keep-alive message from router with timestamp: {}, delta {} ms", timestamp, (Utc::now().timestamp_micros() - timestamp).abs() as f64 / 1000.0);
}
_ => println!("Received message from router: {:?}", RouterMessages::from_slice(&vpn_buf[..n]))
};
}
Err(e) => {
eprintln!("Error reading from router: {}", e);
return Err(anyhow::anyhow!(format!("Error reading from router: {}", e)));
}
}
}
}
data = tun_device.recv(&mut tun_buf) => {
match data {
Ok(n) => {
let packet = etherparse::Ipv4HeaderSlice::from_slice(&tun_buf[..n])?;
let src = packet.source_addr();
match ip_match_network(src, &config.local_routes).await {
Some(net) => println!("Source IP {} matches local route {}", src, net),
None => {},
}
}
Err(e) => {
eprintln!("Error reading from TUN interface: {}", e);
return Err(anyhow::anyhow!(format!("Error reading from TUN interface: {}", e)));
}
}
}
}
}
@@ -71,10 +112,10 @@ pub async fn start(config: ClientCfg) -> Result<()> {
pub async fn register_client(
rx: &mut OwnedReadHalf,
tx: &mut OwnedWriteHalf,
config: ClientCfg,
config: &ClientCfg,
buf: &mut [u8],
) -> Result<()> {
let register_msg = RouterMessages::CliReg(CliRegMessages::Reg(config));
let register_msg = RouterMessages::CliReg(CliRegMessages::Reg(config.clone()));
let mut client_registration_timeout =
tokio::time::interval_at(Instant::now() + CLIENT_REGISTER_TIMEOUT, CLIENT_REGISTER_TIMEOUT);
tx.write_all(&register_msg.to_bytes()).await?;

View File

@@ -1,6 +1,8 @@
pub mod client;
pub mod config;
pub mod network;
pub mod router;
pub mod tun;
use clap::{Parser, Subcommand};
@@ -54,7 +56,7 @@ impl Display for OpModes {
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Check if some commanline or check if config file.
// Check if some commandline or check if config file.
let commandline = if env::args().nth(1).is_some() {
Cli::parse()
} else {

11
src/network.rs Normal file
View File

@@ -0,0 +1,11 @@
use ipnet::Ipv4Net;
use std::net::Ipv4Addr;
pub async fn ip_match_network(ip: Ipv4Addr, networks: &[Ipv4Net]) -> Option<Ipv4Net> {
for net in networks {
if net.contains(&ip) {
return Some(*net);
}
}
None
}

View File

@@ -27,16 +27,23 @@ pub trait ReceiverTrait {}
pub enum RouterMessages {
CliReg(CliRegMessages),
KeepAlive(i64),
Data(Vec<u8>),
Data(VpnPacket),
Quit(String),
Uknown(String),
Unknown(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VpnPacket {
pub src_uuid: Uuid,
pub dst_uuid: Uuid,
pub payload: Vec<u8>,
}
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(
serde_json::from_slice(slice).unwrap_or(RouterMessages::Unknown(
String::from_utf8(slice.to_vec()).unwrap_or_else(|b| format!("Invalid UTF-8: {:?}", b.as_bytes())),
))
}
@@ -47,7 +54,7 @@ pub enum CliRegMessages {
Reg(ClientCfg),
RegOk(Uuid),
RegFailed(String),
Uknown(String),
Unknown(String),
}
impl CliRegMessages {
@@ -55,7 +62,7 @@ impl CliRegMessages {
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(
serde_json::from_slice(slice).unwrap_or(CliRegMessages::Unknown(
String::from_utf8(slice.to_vec()).unwrap_or_else(|b| format!("Invalid UTF-8: {:?}", b.as_bytes())),
))
}
@@ -202,7 +209,6 @@ pub async fn handle_client(router: Router, stream: TcpStream) -> Result<()> {
_= 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?;
}
}

29
src/tun.rs Normal file
View File

@@ -0,0 +1,29 @@
use anyhow::Result;
use tun_rs::{AsyncDevice, DeviceBuilder};
use crate::client::ClientCfg;
pub async fn inti_tun_interface(config: &ClientCfg) -> Result<AsyncDevice> {
println!(
"Initializing TUN interface with name: {}, IP: {}/{}, MTU: {}",
config.interface_name,
config.interface_ip.addr(),
config.interface_ip.netmask(),
config.mtu
);
let device = match DeviceBuilder::new()
.name(&config.interface_name)
.ipv4(config.interface_ip.addr(), config.interface_ip.netmask(), None)
.mtu(config.mtu)
.build_async()
{
Ok(dev) => dev,
Err(e) => {
let msg = format!("Failed to create TUN interface: {:#}", e);
eprintln!("{}", msg);
return Err(anyhow::anyhow!(msg));
}
};
Ok(device)
}