Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target
/*.yaml
*.pem
.idea/
20 changes: 20 additions & 0 deletions src/socket_util.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::mem::ManuallyDrop;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
#[cfg(unix)]
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd};
use std::path::Path;

Expand Down Expand Up @@ -80,12 +81,20 @@ pub fn new_socket2_udp_socket_with_buffer_size(
Ok(socket)
}

#[cfg(unix)]
fn into_tokio_udp_socket(socket: socket2::Socket) -> std::io::Result<tokio::net::UdpSocket> {
let raw_fd = socket.into_raw_fd();
let std_udp_socket = unsafe { std::net::UdpSocket::from_raw_fd(raw_fd) };
tokio::net::UdpSocket::from_std(std_udp_socket)
}

#[cfg(not(unix))]
fn into_tokio_udp_socket(socket: socket2::Socket) -> std::io::Result<tokio::net::UdpSocket> {
// On Windows, convert to std::net::UdpSocket first, then to tokio
let std_socket: std::net::UdpSocket = socket.into();
tokio::net::UdpSocket::from_std(std_socket)
}

pub fn new_tcp_socket(
bind_interface: Option<String>,
is_ipv6: bool,
Expand All @@ -108,6 +117,7 @@ pub fn new_tcp_socket(
Ok(tcp_socket)
}

#[cfg(unix)]
pub fn set_tcp_keepalive(
tcp_stream: &tokio::net::TcpStream,
idle_time: std::time::Duration,
Expand All @@ -127,6 +137,16 @@ pub fn set_tcp_keepalive(
Ok(())
}

#[cfg(not(unix))]
pub fn set_tcp_keepalive(
_tcp_stream: &tokio::net::TcpStream,
_idle_time: std::time::Duration,
_send_interval: std::time::Duration,
) -> std::io::Result<()> {
// TODO: Implement Windows TCP keepalive using socket2
Ok(())
}

// TODO: change backlog to Option<u32> and make configuration, backlog -1 uses somaxconn on linux
// https://github.com/rust-lang/rust/blob/3534594029ed1495290e013647a1f53da561f7f1/library/std/src/os/unix/net/listener.rs#L93
pub fn new_tcp_listener(
Expand Down
112 changes: 73 additions & 39 deletions src/tun/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@
//! └─────────────────┘ └─────────────────┘ └─────────────────┘
//! ```
//!
//! The smoltcp stack runs in a dedicated OS thread with direct fd access,
//! using `select()` for efficient event-driven I/O.
//! The smoltcp stack runs in a dedicated OS thread, using async I/O with
//! virtual device channels to communicate with the async TUN device.
//! This unified approach works across all platforms (Windows, Linux, macOS, Android, iOS).
//!
//! # Platform Support
//!
//! - **Windows**: Creates TUN device using WinTUN driver. Requires administrator
//! privileges for driver installation.
//!
//! - **Linux**: Creates TUN device with specified name/address. Requires root
//! privileges or `CAP_NET_ADMIN` capability.
//!
Expand Down Expand Up @@ -47,10 +51,10 @@ pub use platform::{
pub use tun_server::TunServerConfig;

use std::net::SocketAddr;
use std::os::unix::io::IntoRawFd;
use std::sync::Arc;

use log::{debug, info, warn};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::{mpsc, oneshot};
use tokio::task::JoinHandle;

Expand All @@ -61,55 +65,54 @@ use crate::config::selection::ConfigSelection;
use crate::resolver::{NativeResolver, Resolver};
use crate::tcp::tcp_client_handler_factory::create_tcp_client_proxy_selector;

use tcp_stack_direct::{NewTcpConnection, TcpStackDirect};
use tcp_stack_direct::{NewTcpConnection, PooledBuffer, TcpStackDirect};
use udp_manager::TunUdpManager;

type PacketBuffer = Vec<u8>;

/// Run the TUN server with the given configuration.
/// Run the TUN server with async device support (cross-platform).
///
/// This function:
/// 1. Creates/wraps a TUN device
/// 2. Sets up our smoltcp-based TCP/IP stack with direct fd access
/// 3. The stack thread reads packets directly from TUN using select()
/// 1. Creates an async TUN device (works with WinTUN on Windows, Linux, macOS, Android, iOS)
/// 2. Sets up our smoltcp-based TCP/IP stack with virtual device
/// 3. Uses tokio for reading/writing to the TUN device
/// 4. Handles TCP connections through the proxy chain
/// 5. Handles UDP packets through tokio (forwarded from stack thread)
/// 5. Handles UDP packets through tokio
pub async fn run_tun_server(
config: TunServerConfig,
proxy_selector: Arc<ClientProxySelector>,
resolver: Arc<dyn Resolver>,
mut shutdown_rx: oneshot::Receiver<()>,
) -> std::io::Result<()> {
info!(
"Starting TUN server (direct mode): mtu={}, tcp={}, udp={}, icmp={}",
"Starting TUN server (async mode): mtu={}, tcp={}, udp={}, icmp={}",
config.mtu, config.tcp_enabled, config.udp_enabled, config.icmp_enabled
);

let fd = if let Some(fd) = config.raw_fd {
info!("Using provided raw FD: {}", fd);
fd
} else {
let tun_device = config.create_sync_device()?;
let fd = tun_device.into_raw_fd();
info!("Created TUN device with FD: {}", fd);
fd
};

// Create async TUN device (supports Windows via WinTUN)
let mut tun_device = config.create_async_device()?;
let mtu = config.mtu as usize;

// Create the direct TCP stack (runs smoltcp in dedicated thread with select())
let mut tcp_stack = TcpStackDirect::new(fd, mtu);
info!("Async TUN device created: mtu={}", mtu);

// Keep a unified async data path for all platforms:
// Windows has no Unix-style raw fd, so we bridge via channels everywhere.
let mut tcp_stack = TcpStackDirect::new(mtu);

// Get UDP receiver (stack thread filters UDP and sends here)
// Get channels
let tun_to_stack_tx = tcp_stack.tun_to_stack_tx();
let mut stack_to_tun_rx = tcp_stack.take_stack_to_tun_rx();
let udp_from_stack_rx = tcp_stack.take_udp_rx().expect("udp_rx already taken");

// Channel for sending UDP responses back (stack thread will write to TUN)
// Channel for UDP responses
let (udp_to_stack_tx, udp_to_stack_rx) = mpsc::unbounded_channel::<PacketBuffer>();
tcp_stack.set_udp_response_tx(udp_to_stack_rx);

let (tcp_conn_tx, mut tcp_conn_rx) = mpsc::unbounded_channel::<NewTcpConnection>();
tcp_stack.set_new_conn_tx(tcp_conn_tx);

let mut buffer = vec![0u8; mtu + 100];

let tcp_task: Option<JoinHandle<()>> = if config.tcp_enabled {
let proxy_selector = proxy_selector.clone();
let resolver = resolver.clone();
Expand Down Expand Up @@ -153,20 +156,52 @@ pub async fn run_tun_server(
None
};

info!("TUN server started successfully");
info!("TUN server (async) started successfully");

// Main event loop - read from TUN and write responses
loop {
tokio::select! {
result = tun_device.read(&mut buffer) => {
let n = match result {
Ok(n) => n,
Err(e) => {
warn!("TUN read error: {}", e);
tcp_stack.stop();
break;
}
};

if n > 0 {
let mut pkt = PooledBuffer::with_capacity(n);
pkt.extend_from_slice(&buffer[..n]);
if tun_to_stack_tx.send(pkt).is_err() {
warn!("TUN input channel closed, stopping stack");
tcp_stack.stop();
break;
}
}
}

// Wait for shutdown signal or stack thread exit
tokio::select! {
_ = &mut shutdown_rx => {
info!("TUN server shutdown requested");
}
_ = async {
// Poll until stack stops running
while tcp_stack.is_running() {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
Some(pkt) = stack_to_tun_rx.recv() => {
if let Err(e) = tun_device.write(&pkt).await {
warn!("TUN write error: {}", e);
}
}

_ = &mut shutdown_rx => {
info!("TUN server shutdown requested");
tcp_stack.stop();
break;
}

_ = async {
while tcp_stack.is_running() {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
} => {
warn!("Stack thread ended unexpectedly");
break;
}
} => {
warn!("Stack thread ended unexpectedly");
}
}

Expand All @@ -177,9 +212,7 @@ pub async fn run_tun_server(
t.abort();
}

// tcp_stack is dropped here, which stops the stack thread

info!("TUN server stopped");
info!("TUN server (async) stopped");
Ok(())
}

Expand Down Expand Up @@ -339,6 +372,7 @@ pub async fn run_tun_from_config(
let resolver: Arc<dyn Resolver> = Arc::new(NativeResolver::new());
let client_proxy_selector = Arc::new(create_tcp_client_proxy_selector(rules, resolver.clone()));

// Unified async mode for all platforms
run_tun_server(
tun_server_config,
client_proxy_selector,
Expand Down
Loading