#![allow(unused_imports)]
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use pnet_packet::ip::IpNextHeaderProtocols;
use pnet_packet::Packet;
#[cfg(any(
target_os = "windows",
target_os = "macos",
all(target_os = "linux", not(target_env = "ohos")),
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
))]
use tun_rs::DeviceBuilder;
use tun_rs::SyncDevice;
#[cfg(any(
target_os = "windows",
target_os = "macos",
all(target_os = "linux", not(target_env = "ohos")),
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
))]
#[cfg(not(any(feature = "async_tokio", feature = "async_io")))]
#[test]
fn test_udp_v4() {
let test_msg = "test udp";
let device = DeviceBuilder::new()
.ipv4("10.26.1.100", 24, None)
.build_sync()
.unwrap();
let device = Arc::new(device);
let _device = device.clone();
let test_udp_v4 = Arc::new(AtomicBool::new(false));
let test_udp_v4_c = test_udp_v4.clone();
let recv_flag = Arc::new(AtomicBool::new(false));
let recv_flag_c = recv_flag.clone();
std::thread::spawn(move || {
let mut buf = [0; 65535];
loop {
let len = device.recv(&mut buf).unwrap();
if let Some(ipv4_packet) = pnet_packet::ipv4::Ipv4Packet::new(&buf[..len]) {
if ipv4_packet.get_next_level_protocol() == IpNextHeaderProtocols::Udp {
if let Some(udp_packet) =
pnet_packet::udp::UdpPacket::new(ipv4_packet.payload())
{
if udp_packet.payload() == test_msg.as_bytes() {
test_udp_v4.store(true, Ordering::Relaxed);
}
}
}
}
if test_udp_v4.load(Ordering::Relaxed) {
recv_flag.store(true, Ordering::Release);
break;
}
}
});
std::thread::sleep(Duration::from_secs(6));
let udp_socket = std::net::UdpSocket::bind("10.26.1.100:0").unwrap();
udp_socket
.send_to(test_msg.as_bytes(), "10.26.1.101:8080")
.unwrap();
let time_now = std::time::Instant::now();
while !recv_flag_c.load(Ordering::Acquire) {
if time_now.elapsed().as_secs() > 2 {
let v4 = test_udp_v4_c.load(Ordering::Relaxed);
assert!(v4, "timeout: test_udp_v4 = {v4}");
return;
}
}
let v4 = test_udp_v4_c.load(Ordering::Relaxed);
assert!(v4);
}
#[cfg(any(
target_os = "windows",
target_os = "macos",
all(target_os = "linux", not(target_env = "ohos")),
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
))]
#[cfg(not(any(feature = "async_tokio", feature = "async_io")))]
#[test]
fn test_udp_v6() {
let test_msg = "test udp";
let device = DeviceBuilder::new()
.ipv6("fd12:3456:789a:1111:2222:3333:4444:5555", 64)
.build_sync()
.unwrap();
let device = Arc::new(device);
let _device = device.clone();
let test_udp_v6 = Arc::new(AtomicBool::new(false));
let test_udp_v6_c = test_udp_v6.clone();
let recv_flag = Arc::new(AtomicBool::new(false));
let recv_flag_c = recv_flag.clone();
std::thread::spawn(move || {
let mut buf = [0; 65535];
loop {
let len = device.recv(&mut buf).unwrap();
if let Some(ipv6_packet) = pnet_packet::ipv6::Ipv6Packet::new(&buf[..len]) {
if ipv6_packet.get_next_header() == IpNextHeaderProtocols::Udp {
if let Some(udp_packet) =
pnet_packet::udp::UdpPacket::new(ipv6_packet.payload())
{
if udp_packet.payload() == test_msg.as_bytes() {
test_udp_v6.store(true, Ordering::Relaxed);
}
}
}
}
if test_udp_v6.load(Ordering::Relaxed) {
recv_flag.store(true, Ordering::Release);
break;
}
}
});
std::thread::sleep(Duration::from_secs(6));
let udp_socket =
std::net::UdpSocket::bind("[fd12:3456:789a:1111:2222:3333:4444:5555]:0").unwrap();
udp_socket
.send_to(
test_msg.as_bytes(),
"[fd12:3456:789a:1111:2222:3333:4444:5556]:8080",
)
.unwrap();
let time_now = std::time::Instant::now();
while !recv_flag_c.load(Ordering::Acquire) {
if time_now.elapsed().as_secs() > 2 {
let v6 = test_udp_v6_c.load(Ordering::Relaxed);
assert!(v6, "timeout: test_udp_v6 = {v6}");
return;
}
}
let v6 = test_udp_v6_c.load(Ordering::Relaxed);
assert!(v6);
}
#[cfg(any(
target_os = "windows",
target_os = "macos",
all(target_os = "linux", not(target_env = "ohos")),
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
))]
#[cfg(feature = "async_tokio")]
#[tokio::test]
async fn test_udp_v4() {
let test_msg = "test udp";
let device = DeviceBuilder::new()
.ipv4("10.26.1.100", 24, None)
.build_async()
.unwrap();
let device = Arc::new(device);
let _device = device.clone();
let test_udp_v4 = Arc::new(AtomicBool::new(false));
let test_udp_v4_c = test_udp_v4.clone();
let recv_flag = Arc::new(AtomicBool::new(false));
let recv_flag_c = recv_flag.clone();
let handler = tokio::spawn(async move {
let mut buf = [0; 65535];
loop {
let len = device.recv(&mut buf).await.unwrap();
if let Some(ipv4_packet) = pnet_packet::ipv4::Ipv4Packet::new(&buf[..len]) {
if ipv4_packet.get_next_level_protocol() == IpNextHeaderProtocols::Udp {
if let Some(udp_packet) =
pnet_packet::udp::UdpPacket::new(ipv4_packet.payload())
{
if udp_packet.payload() == test_msg.as_bytes() {
test_udp_v4.store(true, Ordering::Relaxed);
}
}
}
}
if test_udp_v4.load(Ordering::Relaxed) {
recv_flag.store(true, Ordering::Release);
break;
}
}
});
tokio::time::sleep(Duration::from_secs(6)).await;
let udp_socket = tokio::net::UdpSocket::bind("10.26.1.200:0").await.unwrap();
udp_socket
.send_to(test_msg.as_bytes(), "10.26.1.101:8080")
.await
.unwrap();
tokio::select! {
_=tokio::time::sleep(Duration::from_secs(2))=>{
let v4 = test_udp_v4_c.load(Ordering::Relaxed);
assert!(v4, "timeout: test_udp_v4 = {v4}");
}
_=handler=>{
let flag = recv_flag_c.load(Ordering::Acquire); assert!(flag, "recv_flag = {flag}");
let v4 = test_udp_v4_c.load(Ordering::Relaxed);
assert!(v4);
}
}
}
#[cfg(any(
target_os = "windows",
target_os = "macos",
all(target_os = "linux", not(target_env = "ohos")),
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
))]
#[cfg(feature = "async_tokio")]
#[tokio::test]
async fn test_udp_v6() {
let test_msg = "test udp";
let device = DeviceBuilder::new()
.ipv6("fd12:3456:789a:1111:2222:3333:4444:5555", 64)
.build_async()
.unwrap();
let device = Arc::new(device);
let _device = device.clone();
let test_udp_v6 = Arc::new(AtomicBool::new(false));
let test_udp_v6_c = test_udp_v6.clone();
let recv_flag = Arc::new(AtomicBool::new(false));
let recv_flag_c = recv_flag.clone();
let handler = tokio::spawn(async move {
let mut buf = [0; 65535];
loop {
let len = device.recv(&mut buf).await.unwrap();
if let Some(ipv6_packet) = pnet_packet::ipv6::Ipv6Packet::new(&buf[..len]) {
if ipv6_packet.get_next_header() == IpNextHeaderProtocols::Udp {
if let Some(udp_packet) =
pnet_packet::udp::UdpPacket::new(ipv6_packet.payload())
{
if udp_packet.payload() == test_msg.as_bytes() {
test_udp_v6.store(true, Ordering::Relaxed);
}
}
}
}
if test_udp_v6.load(Ordering::Relaxed) {
recv_flag.store(true, Ordering::Release);
break;
}
}
});
tokio::time::sleep(Duration::from_secs(6)).await;
let udp_socket = tokio::net::UdpSocket::bind("[fd12:3456:789a:1111:2222:3333:4444:5555]:0")
.await
.unwrap();
udp_socket
.send_to(
test_msg.as_bytes(),
"[fd12:3456:789a:1111:2222:3333:4444:5556]:8080",
)
.await
.unwrap();
tokio::select! {
_=tokio::time::sleep(Duration::from_secs(2))=>{
let v6 = test_udp_v6_c.load(Ordering::Relaxed);
assert!(v6, "timeout: test_udp_v6 = {v6}");
}
_=handler=>{
let flag = recv_flag_c.load(Ordering::Acquire); assert!(flag, "recv_flag = {flag}");
let v6 = test_udp_v6_c.load(Ordering::Relaxed);
assert!(v6 );
}
}
}
#[cfg(any(
target_os = "windows",
target_os = "macos",
all(target_os = "linux", not(target_env = "ohos")),
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
))]
#[test]
fn test_op() {
let device = DeviceBuilder::new()
.ipv4("10.26.2.100", 24, None)
.ipv6("fd12:3456:789a:5555:2222:3333:4444:5555", 120)
.build_sync()
.unwrap();
#[cfg(any(target_os = "macos", target_os = "openbsd"))]
device.set_ignore_packet_info(true);
#[cfg(any(target_os = "macos", target_os = "openbsd"))]
assert!(device.ignore_packet_info());
device.set_mtu(1500).unwrap();
assert_eq!(device.mtu().unwrap(), 1500);
#[cfg(target_os = "macos")]
device.set_associate_route(true);
#[cfg(target_os = "macos")]
assert!(device.associate_route());
let vec = device.addresses().unwrap();
assert!(vec
.iter()
.any(|ip| *ip == "10.26.2.100".parse::<std::net::Ipv4Addr>().unwrap()));
assert!(vec.iter().any(|ip| *ip
== "fd12:3456:789a:5555:2222:3333:4444:5555"
.parse::<std::net::Ipv6Addr>()
.unwrap()));
device.set_network_address("10.26.3.200", 24, None).unwrap();
let vec = device.addresses().unwrap();
assert!(vec
.iter()
.any(|ip| *ip == "10.26.3.200".parse::<std::net::Ipv4Addr>().unwrap()));
assert!(vec.iter().any(|ip| *ip
== "fd12:3456:789a:5555:2222:3333:4444:5555"
.parse::<std::net::Ipv6Addr>()
.unwrap()));
assert!(!vec.contains(&"10.26.2.100".parse::<std::net::IpAddr>().unwrap()));
device.add_address_v4("10.6.0.1", 24).unwrap();
let vec = device.addresses().unwrap();
assert!(vec.contains(&"10.6.0.1".parse::<std::net::IpAddr>().unwrap()));
assert!(vec.contains(&"10.26.3.200".parse::<std::net::IpAddr>().unwrap()));
device
.remove_address("10.6.0.1".parse::<std::net::IpAddr>().unwrap())
.unwrap();
let vec = device.addresses().unwrap();
assert!(!vec.contains(&"10.6.0.1".parse::<std::net::IpAddr>().unwrap()));
assert!(vec.contains(&"10.26.3.200".parse::<std::net::IpAddr>().unwrap()));
device
.add_address_v6("fdab:cdef:1234:5678:9abc:def0:1234:5678", 64)
.unwrap();
let vec = device.addresses().unwrap();
assert!(vec.contains(
&"fdab:cdef:1234:5678:9abc:def0:1234:5678"
.parse::<std::net::IpAddr>()
.unwrap()
));
device.enabled(true).unwrap();
#[cfg(any(
target_os = "windows",
all(target_os = "linux", not(target_env = "ohos"))
))]
device.set_name("tun66").unwrap();
std::thread::sleep(Duration::from_secs(3));
#[cfg(any(
target_os = "windows",
all(target_os = "linux", not(target_env = "ohos"))
))]
assert_eq!(device.name().unwrap(), "tun66");
assert!(device.if_index().is_ok());
#[cfg(target_os = "windows")]
{
assert!(device.if_luid().is_ok());
device
.set_metric(100)
.expect("set_metric(100) should succeed");
let v4_dns: [std::net::IpAddr; 2] =
["8.8.8.8".parse().unwrap(), "8.8.4.4".parse().unwrap()];
device.set_dns_servers(&v4_dns).unwrap();
let v6_dns: [std::net::IpAddr; 1] = ["2001:4860:4860::8888".parse().unwrap()];
device.set_dns_servers(&v6_dns).unwrap();
device.clear_dns_servers(true).unwrap();
device.clear_dns_servers(false).unwrap();
}
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
assert!(device.is_running().unwrap());
}
#[cfg(any(
target_os = "windows",
target_os = "macos",
all(target_os = "linux", not(target_env = "ohos")),
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
))]
#[test]
fn create_tun() {
#[cfg(not(target_os = "macos"))]
let name = "tun12";
#[cfg(target_os = "macos")]
let name = "utun12";
let device = DeviceBuilder::new().name(name).build_sync().unwrap();
let dev_name = device.name().unwrap();
assert_eq!(dev_name.as_str(), name);
#[cfg(unix)]
{
use std::os::fd::IntoRawFd;
let fd = device.into_raw_fd();
unsafe {
let sync_device = SyncDevice::from_fd(fd).unwrap();
let dev_name = sync_device.name().unwrap();
assert_eq!(dev_name, name);
}
}
}
#[cfg(any(
target_os = "windows",
target_os = "macos",
all(target_os = "linux", not(target_env = "ohos")),
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
))]
#[test]
fn create_tap() {
#[cfg(not(target_os = "macos"))]
let name = "tap12";
#[cfg(target_os = "macos")]
let name = "feth12";
let device = DeviceBuilder::new()
.name(name)
.layer(tun_rs::Layer::L2)
.build_sync()
.unwrap();
let dev_name = device.name().unwrap();
assert_eq!(dev_name.as_str(), name);
#[cfg(all(unix, not(target_os = "macos")))]
{
use std::os::fd::IntoRawFd;
let fd = device.into_raw_fd();
unsafe {
let sync_device = SyncDevice::from_fd(fd).unwrap();
let dev_name = sync_device.name().unwrap();
assert_eq!(dev_name, name);
}
}
}
#[cfg(target_os = "windows")]
#[test]
fn test_windows_new_apis() {
use std::net::IpAddr;
use tun_rs::DeviceBuilder;
let device = DeviceBuilder::new()
.ipv4("10.26.9.100", 24, None)
.ipv6("fd12:3456:789a:9999:2222:3333:4444:5555", 64)
.build_sync()
.unwrap();
let luid = device.if_luid().expect("if_luid() should succeed");
let luid_value = unsafe { luid.Value };
assert_ne!(luid_value, 0, "LUID must be non-zero for a live adapter");
device
.set_metric(50)
.expect("set_metric(50) should succeed");
device
.set_metric(100)
.expect("set_metric(100) should succeed");
device.set_mtu(1400).expect("set_mtu(1400) should succeed");
assert_eq!(
device.mtu().expect("mtu() should succeed"),
1400,
"mtu() read-back should match the value written by set_mtu()"
);
device
.set_mtu_v6(1380)
.expect("set_mtu_v6(1380) should succeed");
assert_eq!(
device.mtu_v6().expect("mtu_v6() should succeed"),
1380,
"mtu_v6() read-back should match the value written by set_mtu_v6()"
);
device
.set_network_address("10.26.9.200", 24, None)
.expect("set_network_address (no gateway) should succeed");
let addrs = device.addresses().unwrap();
assert!(
addrs.contains(&"10.26.9.200".parse::<IpAddr>().unwrap()),
"new address 10.26.9.200 should be present after set_network_address"
);
assert!(
!addrs.contains(&"10.26.9.100".parse::<IpAddr>().unwrap()),
"old address 10.26.9.100 should have been removed by set_network_address"
);
device
.set_network_address("10.26.9.150", 24, Some("10.26.9.1"))
.expect("set_network_address with gateway should succeed");
let addrs = device.addresses().unwrap();
assert!(
addrs.contains(&"10.26.9.150".parse::<IpAddr>().unwrap()),
"address 10.26.9.150 should be present after set_network_address with gateway"
);
assert!(
!addrs.contains(&"10.26.9.200".parse::<IpAddr>().unwrap()),
"previous address 10.26.9.200 should have been cleared"
);
device
.add_address_v6("fdab:cdef:1234:5678:9abc:def0:1234:0001", 64)
.expect("add_address_v6 should succeed");
let addrs = device.addresses().unwrap();
assert!(
addrs.contains(
&"fdab:cdef:1234:5678:9abc:def0:1234:0001"
.parse::<IpAddr>()
.unwrap()
),
"IPv6 address should be present after add_address_v6"
);
device
.remove_address(
"fdab:cdef:1234:5678:9abc:def0:1234:0001"
.parse::<IpAddr>()
.unwrap(),
)
.expect("remove_address (IPv6) should succeed");
let addrs = device.addresses().unwrap();
assert!(
!addrs.contains(
&"fdab:cdef:1234:5678:9abc:def0:1234:0001"
.parse::<IpAddr>()
.unwrap()
),
"IPv6 address should be absent after remove_address"
);
let ipv4_dns: &[IpAddr] = &["8.8.8.8".parse().unwrap(), "8.8.4.4".parse().unwrap()];
device
.set_dns_servers(ipv4_dns)
.expect("set_dns_servers (IPv4 primary+secondary) should succeed");
let ipv6_dns: &[IpAddr] = &["2001:4860:4860::8888".parse().unwrap()];
device
.set_dns_servers(ipv6_dns)
.expect("set_dns_servers (IPv6) should succeed");
assert!(
device.set_dns_servers(&[]).is_err(),
"set_dns_servers with an empty slice must return Err (InvalidInput)"
);
let mixed: &[IpAddr] = &[
"8.8.8.8".parse().unwrap(),
"2001:4860:4860::8888".parse().unwrap(),
];
assert!(
device.set_dns_servers(mixed).is_err(),
"set_dns_servers with mixed IPv4/IPv6 addresses must return Err (InvalidInput)"
);
device
.clear_dns_servers(true)
.expect("clear_dns_servers (IPv4) should succeed");
device
.clear_dns_servers(false)
.expect("clear_dns_servers (IPv6) should succeed");
}
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
#[cfg(not(any(feature = "async_tokio", feature = "async_io")))]
#[test]
#[ignore]
fn test_offload_mask_cleared_on_reattach_without_offload() {
use std::process::Command;
const NAME: &str = "tunoffldclr";
fn ethtool_feature(name: &str, feature: &str) -> bool {
let output = Command::new("ethtool")
.args(["-k", name])
.output()
.expect("run ethtool -k");
assert!(
output.status.success(),
"ethtool -k {name} failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix(feature) {
if let Some(value) = rest.strip_prefix(':') {
let value = value.trim();
if value.starts_with("on") {
return true;
}
if value.starts_with("off") {
return false;
}
}
}
}
panic!("feature `{feature}` not found in `ethtool -k {name}` output:\n{stdout}");
}
let _ = Command::new("ip").args(["link", "delete", NAME]).status();
let dev1 = DeviceBuilder::new()
.name(NAME)
.offload(true)
.build_sync()
.expect("attach #1 with offload=true");
dev1.persist().expect("set TUNSETPERSIST");
let tso_after_offload = ethtool_feature(NAME, "tx-tcp-segmentation");
let csum_after_offload = ethtool_feature(NAME, "tx-checksum-ip-generic");
drop(dev1);
let dev2 = DeviceBuilder::new()
.name(NAME)
.offload(false)
.build_sync()
.expect("attach #2 with offload=false");
let tso_after_clear = ethtool_feature(NAME, "tx-tcp-segmentation");
let csum_after_clear = ethtool_feature(NAME, "tx-checksum-ip-generic");
drop(dev2);
let _ = Command::new("ip").args(["link", "delete", NAME]).status();
assert!(
tso_after_offload,
"ethtool sanity: tx-tcp-segmentation should be ON after offload=true attach"
);
assert!(
csum_after_offload,
"ethtool sanity: tx-checksum-ip-generic should be ON after offload=true attach"
);
assert!(
!tso_after_clear,
"regression: tx-tcp-segmentation still ON after offload=false attach \
(device-wide TUN_F_TSO* not cleared)"
);
assert!(
!csum_after_clear,
"regression: tx-checksum-ip-generic still ON after offload=false attach \
(device-wide TUN_F_CSUM not cleared)"
);
}