feat(rust-server, rust-client, ts-interfaces): add configurable packet forwarding with TUN and userspace NAT modes
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use anyhow::Result;
|
||||
use bytes::BytesMut;
|
||||
use serde::Deserialize;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, watch, RwLock};
|
||||
use tracing::{info, error, warn, debug};
|
||||
@@ -12,6 +13,7 @@ use crate::telemetry::ConnectionQuality;
|
||||
use crate::transport;
|
||||
use crate::transport_trait::{self, TransportSink, TransportStream};
|
||||
use crate::quic_transport;
|
||||
use crate::tunnel::{self, TunConfig};
|
||||
|
||||
/// Client configuration (matches TS IVpnClientConfig).
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
@@ -30,6 +32,9 @@ pub struct ClientConfig {
|
||||
pub transport: Option<String>,
|
||||
/// For QUIC: SHA-256 hash of server certificate (base64) for cert pinning.
|
||||
pub server_cert_hash: Option<String>,
|
||||
/// Forwarding mode: "tun" (TUN device, requires root) or "testing" (no TUN).
|
||||
/// Default: "testing".
|
||||
pub forwarding_mode: Option<String>,
|
||||
}
|
||||
|
||||
/// Client statistics.
|
||||
@@ -234,6 +239,31 @@ impl VpnClient {
|
||||
|
||||
info!("Connected to VPN, assigned IP: {}", assigned_ip);
|
||||
|
||||
// Optionally create TUN device for IP packet forwarding (requires root)
|
||||
let tun_enabled = config.forwarding_mode.as_deref() == Some("tun");
|
||||
let (tun_reader, tun_writer, tun_subnet) = if tun_enabled {
|
||||
let client_tun_ip: Ipv4Addr = assigned_ip.parse()?;
|
||||
let mtu = ip_info["mtu"].as_u64().unwrap_or(1420) as u16;
|
||||
let tun_config = TunConfig {
|
||||
name: "svpn-client0".to_string(),
|
||||
address: client_tun_ip,
|
||||
netmask: Ipv4Addr::new(255, 255, 255, 0),
|
||||
mtu,
|
||||
};
|
||||
let tun_device = tunnel::create_tun(&tun_config)?;
|
||||
|
||||
// Add route for VPN subnet through the TUN device
|
||||
let gateway_str = ip_info["gateway"].as_str().unwrap_or("10.8.0.1");
|
||||
let gateway: Ipv4Addr = gateway_str.parse().unwrap_or(Ipv4Addr::new(10, 8, 0, 1));
|
||||
let subnet = format!("{}/24", Ipv4Addr::from(u32::from(gateway) & 0xFFFFFF00));
|
||||
tunnel::add_route(&subnet, &tun_config.name).await?;
|
||||
|
||||
let (reader, writer) = tokio::io::split(tun_device);
|
||||
(Some(reader), Some(writer), Some(subnet))
|
||||
} else {
|
||||
(None, None, None)
|
||||
};
|
||||
|
||||
// Create adaptive keepalive monitor (use custom interval if configured)
|
||||
let ka_config = config.keepalive_interval_secs.map(|secs| {
|
||||
let mut cfg = keepalive::AdaptiveKeepaliveConfig::default();
|
||||
@@ -260,6 +290,9 @@ impl VpnClient {
|
||||
handle.signal_rx,
|
||||
handle.ack_tx,
|
||||
link_health,
|
||||
tun_reader,
|
||||
tun_writer,
|
||||
tun_subnet,
|
||||
));
|
||||
|
||||
Ok(assigned_ip_clone)
|
||||
@@ -356,8 +389,14 @@ async fn client_loop(
|
||||
mut signal_rx: mpsc::Receiver<KeepaliveSignal>,
|
||||
ack_tx: mpsc::Sender<()>,
|
||||
link_health: Arc<RwLock<LinkHealth>>,
|
||||
mut tun_reader: Option<tokio::io::ReadHalf<tun::AsyncDevice>>,
|
||||
mut tun_writer: Option<tokio::io::WriteHalf<tun::AsyncDevice>>,
|
||||
tun_subnet: Option<String>,
|
||||
) {
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
let mut buf = vec![0u8; 65535];
|
||||
let mut tun_buf = vec![0u8; 65536];
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
@@ -373,6 +412,14 @@ async fn client_loop(
|
||||
let mut s = stats.write().await;
|
||||
s.bytes_received += len as u64;
|
||||
s.packets_received += 1;
|
||||
drop(s);
|
||||
|
||||
// Write decrypted packet to TUN device (if enabled)
|
||||
if let Some(ref mut writer) = tun_writer {
|
||||
if let Err(e) = writer.write_all(&buf[..len]).await {
|
||||
warn!("TUN write error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Decrypt error: {}", e);
|
||||
@@ -407,6 +454,50 @@ async fn client_loop(
|
||||
}
|
||||
}
|
||||
}
|
||||
// Read outbound packets from TUN and send to server (only when TUN enabled)
|
||||
result = async {
|
||||
match tun_reader {
|
||||
Some(ref mut reader) => reader.read(&mut tun_buf).await,
|
||||
None => std::future::pending::<std::io::Result<usize>>().await,
|
||||
}
|
||||
} => {
|
||||
match result {
|
||||
Ok(0) => {
|
||||
info!("TUN device closed");
|
||||
break;
|
||||
}
|
||||
Ok(n) => {
|
||||
match noise_transport.write_message(&tun_buf[..n], &mut buf) {
|
||||
Ok(len) => {
|
||||
let frame = Frame {
|
||||
packet_type: PacketType::IpPacket,
|
||||
payload: buf[..len].to_vec(),
|
||||
};
|
||||
let mut frame_bytes = BytesMut::new();
|
||||
if <FrameCodec as tokio_util::codec::Encoder<Frame>>::encode(
|
||||
&mut FrameCodec, frame, &mut frame_bytes
|
||||
).is_ok() {
|
||||
if sink.send_reliable(frame_bytes.to_vec()).await.is_err() {
|
||||
warn!("Failed to send TUN packet to server");
|
||||
break;
|
||||
}
|
||||
let mut s = stats.write().await;
|
||||
s.bytes_sent += n as u64;
|
||||
s.packets_sent += 1;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Noise encrypt error: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("TUN read error: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
signal = signal_rx.recv() => {
|
||||
match signal {
|
||||
Some(KeepaliveSignal::SendPing(timestamp_ms)) => {
|
||||
@@ -456,6 +547,13 @@ async fn client_loop(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup: remove TUN route if enabled
|
||||
if let Some(ref subnet) = tun_subnet {
|
||||
if let Err(e) = tunnel::remove_route(subnet, "svpn-client0").await {
|
||||
warn!("Failed to remove client TUN route: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to connect via QUIC. Returns transport halves on success.
|
||||
|
||||
Reference in New Issue
Block a user