use std::process::Command;
use std::time::Duration;
use triglav::tun::{TunConfig, TunDevice};
fn check_root() -> bool {
unsafe { libc::getuid() == 0 }
}
#[tokio::main]
async fn main() {
println!("=== Triglav TUN Device Test ===\n");
if !check_root() {
println!("ERROR: This test requires root privileges.");
println!("Run with: sudo cargo run --test tun_test");
std::process::exit(1);
}
println!("[1/6] Creating TUN device...");
let config = TunConfig {
name: "utun".to_string(), mtu: 1420,
ipv4_addr: Some("10.0.85.1".parse().unwrap()),
ipv4_netmask: 24,
ipv6_addr: None,
ipv6_prefix: 64,
set_default_route: false,
queue_size: 512,
};
let mut tun = match TunDevice::create(config) {
Ok(t) => {
println!(" SUCCESS: Created TUN device: {}", t.name());
t
}
Err(e) => {
println!(" FAILED: {}", e);
std::process::exit(1);
}
};
println!("\n[2/6] Configuring IP address...");
match tun.configure_addresses() {
Ok(_) => println!(" SUCCESS: Configured IP 10.0.85.1/24"),
Err(e) => {
println!(" FAILED: {}", e);
std::process::exit(1);
}
}
println!("\n[3/6] Bringing interface up...");
match tun.up() {
Ok(_) => println!(" SUCCESS: Interface is up"),
Err(e) => {
println!(" FAILED: {}", e);
std::process::exit(1);
}
}
println!("\n[4/6] Verifying interface in system...");
let output = Command::new("ifconfig")
.arg(tun.name())
.output()
.expect("Failed to run ifconfig");
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
println!(" SUCCESS: Interface found in system");
println!(" ---");
for line in stdout.lines() {
println!(" {}", line);
}
println!(" ---");
} else {
println!(" FAILED: Interface not found");
std::process::exit(1);
}
println!("\n[5/6] Testing packet read/write...");
let handle = tun.handle();
let read_handle = tokio::spawn(async move {
let mut buf = vec![0u8; 1500];
match tokio::time::timeout(Duration::from_secs(3), handle.read(&mut buf)).await {
Ok(Ok(len)) => {
println!(" SUCCESS: Read {} bytes from TUN", len);
if len > 0 {
let version = (buf[0] >> 4) & 0x0f;
let protocol = if version == 4 && len >= 20 { buf[9] } else { 0 };
let proto_name = match protocol {
1 => "ICMP",
6 => "TCP",
17 => "UDP",
_ => "Other",
};
println!(
" Packet: IPv{}, Protocol: {} ({})",
version, protocol, proto_name
);
let hex: Vec<String> = buf[..len.min(40)]
.iter()
.map(|b| format!("{:02x}", b))
.collect();
println!(" Hex: {}", hex.join(" "));
}
true
}
Ok(Err(e)) => {
println!(" Read error: {}", e);
false
}
Err(_) => {
println!(" TIMEOUT: No packets received within 3 seconds");
println!(" (This may be normal if no traffic is sent to 10.0.85.1)");
true }
}
});
tokio::time::sleep(Duration::from_millis(100)).await;
println!(" Sending ping to 10.0.85.1...");
let ping_output = Command::new("ping")
.args(["-c", "1", "-t", "1", "10.0.85.1"])
.output();
match ping_output {
Ok(o) => {
if o.status.success() {
println!(" Ping sent successfully");
} else {
println!(" Ping command completed (no response expected)");
}
}
Err(e) => {
println!(" Ping failed to execute: {}", e);
}
}
let _ = read_handle.await;
println!("\n[6/6] Cleaning up...");
match tun.down() {
Ok(_) => println!(" SUCCESS: Interface brought down"),
Err(e) => println!(" Warning: Failed to bring down interface: {}", e),
}
drop(tun);
tokio::time::sleep(Duration::from_millis(500)).await;
let verify = Command::new("ifconfig")
.arg("utun99") .output();
println!(" Device cleaned up");
println!("\n=== All tests passed! ===");
}