#![allow(dead_code)]
use std::{net::IpAddr, process::Command, thread, time::Duration};
use crate::interfaces;
#[cfg(bsd_like)]
mod bsd;
#[cfg(linux_like)]
mod linux;
struct TestInterface {
name: String,
local: IpAddr,
remote: IpAddr,
setup_cmds: Vec<Command>,
teardown_cmds: Vec<Command>,
}
impl TestInterface {
fn new(local: IpAddr, remote: IpAddr) -> Self {
Self {
name: String::new(),
local,
remote,
setup_cmds: Vec::new(),
teardown_cmds: Vec::new(),
}
}
fn setup(&mut self) -> std::io::Result<()> {
for cmd in &mut self.setup_cmds {
run_once(cmd)?;
}
Ok(())
}
fn teardown(&mut self) -> std::io::Result<()> {
for cmd in &mut self.teardown_cmds {
run_once(cmd)?;
}
Ok(())
}
fn try_teardown(&mut self) {
for cmd in &mut self.teardown_cmds {
let _ = cmd.output();
}
}
}
fn run_once(cmd: &mut Command) -> std::io::Result<()> {
let output = cmd.output()?;
if output.status.success() {
return Ok(());
}
let args: Vec<_> = cmd.get_args().collect();
Err(std::io::Error::other(format!(
"{:?} failed (status={:?}): {}",
args,
output.status.code(),
String::from_utf8_lossy(&output.stderr).trim()
)))
}
fn is_environmental_skip(msg: &str) -> bool {
msg.contains("No such device")
|| msg.contains("Cannot find device")
|| msg.contains("SIOCIFCREATE")
|| msg.contains("SIOCSIFFLAGS")
|| msg.contains("not supported")
|| msg.contains("Operation not supported")
|| msg.contains("module")
}
#[test]
#[cfg(all(not(apple), not(target_os = "netbsd"), unix))]
fn point_to_point_interface() {
#[cfg(bsd_like)]
let uid = unsafe { libc::getuid() };
#[cfg(linux_like)]
let uid = rustix::process::getuid().as_raw();
if uid != 0 {
return;
}
let local: IpAddr = "169.254.0.1".parse().unwrap();
let remote: IpAddr = "169.254.0.254".parse().unwrap();
for i in 0..3 {
let mut ti = TestInterface::new(local, remote);
if let Err(e) = ti.set_point_to_point(5963 + i) {
panic!("test requires external command: {}", e);
}
match ti.setup() {
Ok(_) => {
std::thread::sleep(Duration::from_millis(3));
}
Err(e) => {
ti.try_teardown();
let err_msg = e.to_string();
if is_environmental_skip(&err_msg) {
println!(
"skipping test; interface creation failed (likely missing kernel module): {err_msg}"
);
return;
}
panic!("{}", e);
}
}
match interfaces() {
Ok(interfaces) => {
for ifi in interfaces {
if ti.name != ifi.name {
continue;
}
let ifat = ifi.addrs().unwrap();
for ifa in &ifat {
if ifa.addr() == remote {
ti.teardown().unwrap();
panic!("got {ifa:?}");
}
}
}
}
Err(e) => {
ti.teardown().unwrap();
panic!("{}", e);
}
}
ti.teardown().unwrap();
std::thread::sleep(Duration::from_millis(3));
}
}
#[cfg(all(unix, not(target_os = "netbsd")))]
#[test]
fn test_interface_arrival_and_departure() {
if std::env::var("RUST_TEST_SHORT").is_ok() {
return;
}
#[cfg(bsd_like)]
let uid = unsafe { libc::getuid() };
#[cfg(linux_like)]
let uid = rustix::process::getuid().as_raw();
if uid != 0 {
return;
}
let local: IpAddr = "169.254.0.1".parse().unwrap();
let remote: IpAddr = "169.254.0.254".parse().unwrap();
let ip = remote;
for vid in [1002, 1003, 1004, 1005].iter() {
let ift1 = interfaces().unwrap();
let mut ti = TestInterface::new(local, remote);
if let Err(e) = ti.set_broadcast(*vid) {
println!("test requires external command: {e}");
return;
}
if let Err(e) = ti.setup() {
ti.try_teardown();
let err_msg = e.to_string();
if is_environmental_skip(&err_msg) {
println!(
"skipping test; interface creation failed (likely missing kernel module): {err_msg}"
);
return;
}
panic!("{}", e);
}
thread::sleep(Duration::from_millis(3));
let ift2 = match interfaces() {
Ok(interfaces) => interfaces,
Err(e) => {
ti.teardown().unwrap();
panic!("{}", e);
}
};
let _ = ift1;
if !ift2.iter().any(|ifi| ifi.name == ti.name) {
for ifi in &ift2 {
println!("after: {ifi:?}");
}
ti.teardown().unwrap();
panic!("interface {} not present after setup", ti.name);
}
for ifi in ift2.iter() {
if ti.name != ifi.name {
continue;
}
let addrs = ifi.addrs().unwrap();
for addr in addrs {
if let IpAddr::V4(addr_ip) = addr.addr() {
if ip == IpAddr::V4(addr_ip) {
ti.teardown().unwrap();
panic!("got {addr:?}");
}
}
}
}
ti.teardown().unwrap();
thread::sleep(Duration::from_millis(3));
let ift3 = interfaces().unwrap();
if ift3.iter().any(|ifi| ifi.name == ti.name) {
for ifi in &ift3 {
println!("after-teardown: {ifi:?}");
}
panic!("interface {} still present after teardown", ti.name);
}
}
}