allow set TUN dev name on FreeBSD (#1823)

Also rename stale interfaces from previous runs before creating new ones.
Works around rust-tun reusing existing tun0 instead of configured name.

Tested on FreeBSD 14.1
This commit is contained in:
deddey
2026-01-30 23:51:52 +08:00
committed by GitHub
parent 9e3c9228bb
commit c85d1d41b3
+159
View File
@@ -367,10 +367,151 @@ impl VirtualNic {
Ok(()) Ok(())
} }
/// FreeBSD specific: Rename a TUN interface
#[cfg(target_os = "freebsd")]
async fn rename_tun_interface(old_name: &str, new_name: &str) -> Result<(), Error> {
let output = tokio::process::Command::new("ifconfig")
.arg(old_name)
.arg("name")
.arg(new_name)
.output()
.await?;
if output.status.success() {
tracing::info!(
"Successfully renamed interface {} to {}",
old_name,
new_name
);
Ok(())
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
tracing::warn!(
"Failed to rename interface {} to {}: {}",
old_name,
new_name,
stderr
);
// Return Ok even if rename fails, as it's not critical
Ok(())
}
}
/// FreeBSD specific: List all TUN interface names
#[cfg(target_os = "freebsd")]
async fn list_tun_names() -> Result<Vec<String>, Error> {
let output = tokio::process::Command::new("ifconfig")
.arg("-g")
.arg("tun")
.output()
.await?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
let tun_names: Vec<String> = stdout
.trim()
.split_whitespace()
.map(|s| s.to_string())
.collect();
tracing::debug!("Found TUN interfaces: {:?}", tun_names);
Ok(tun_names)
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
tracing::warn!("Failed to list TUN interfaces: {}", stderr);
Ok(Vec::new())
}
}
/// FreeBSD specific: Get interface information
#[cfg(target_os = "freebsd")]
async fn get_interface_info(ifname: &str) -> Result<String, Error> {
let output = tokio::process::Command::new("ifconfig")
.arg("-v")
.arg(ifname)
.output()
.await?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(
anyhow::anyhow!("Failed to get interface details for {}: {}", ifname, stderr)
.into(),
)
}
}
/// FreeBSD specific: Extract original name from interface information
#[cfg(target_os = "freebsd")]
fn extract_original_name(ifinfo: &str) -> Option<String> {
ifinfo
.lines()
.find(|line| line.trim().starts_with("drivername:"))
.and_then(|line| line.trim().split_whitespace().nth(1))
.map(|name| name.to_string())
}
/// FreeBSD specific: Check if interface is used by any process
#[cfg(target_os = "freebsd")]
fn is_interface_used(ifinfo: &str) -> bool {
ifinfo.contains("Opened by PID")
}
/// FreeBSD specific: Restore TUN interface name to its original value
#[cfg(target_os = "freebsd")]
async fn restore_tun_name(dev_name: &str) -> Result<(), Error> {
let tun_names = Self::list_tun_names().await?;
// Check if desired dev_name is in use
if tun_names.iter().any(|name| name == dev_name) {
tracing::debug!(
"Desired dev_name {} is in TUN interfaces list, checking if it can be renamed",
dev_name
);
let ifinfo = Self::get_interface_info(dev_name).await?;
// Check if interface is not occupied
if !Self::is_interface_used(&ifinfo) {
// Extract original name
if let Some(orig_name) = Self::extract_original_name(&ifinfo) {
if orig_name != dev_name {
tracing::info!(
"Restoring dev_name {} to original name {}",
dev_name,
orig_name
);
// Rename interface
Self::rename_tun_interface(dev_name, &orig_name).await?;
}
}
} else {
tracing::debug!(
"Interface {} is opened by a process, skipping rename",
dev_name
);
}
}
Ok(())
}
async fn create_tun(&self) -> Result<tun::platform::Device, Error> { async fn create_tun(&self) -> Result<tun::platform::Device, Error> {
let mut config = Configuration::default(); let mut config = Configuration::default();
config.layer(Layer::L3); config.layer(Layer::L3);
// FreeBSD specific: Check and restore TUN interfaces before creating new one
#[cfg(target_os = "freebsd")]
{
let dev_name = self.global_ctx.get_flags().dev_name;
if !dev_name.is_empty() {
// Restore TUN interface name if needed, ignoring errors as it's not critical
let _ = Self::restore_tun_name(&dev_name).await;
}
}
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
// Check and create TUN device node if necessary (Linux only) // Check and create TUN device node if necessary (Linux only)
@@ -481,9 +622,27 @@ impl VirtualNic {
pub async fn create_dev(&mut self) -> Result<Box<dyn Tunnel>, Error> { pub async fn create_dev(&mut self) -> Result<Box<dyn Tunnel>, Error> {
let dev = self.create_tun().await?; let dev = self.create_tun().await?;
#[cfg(not(target_os = "freebsd"))]
let ifname = dev.tun_name()?; let ifname = dev.tun_name()?;
#[cfg(target_os = "freebsd")]
let mut ifname = dev.tun_name()?;
self.ifcfg.wait_interface_show(ifname.as_str()).await?; self.ifcfg.wait_interface_show(ifname.as_str()).await?;
// FreeBSD TUN interface rename functionality
#[cfg(target_os = "freebsd")]
{
let dev_name = self.global_ctx.get_flags().dev_name;
if !dev_name.is_empty() && dev_name != ifname {
// Use ifconfig to rename the TUN interface
if Self::rename_tun_interface(&ifname, &dev_name).await.is_ok() {
ifname = dev_name;
}
}
}
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
if let Ok(guid) = RegistryManager::find_interface_guid(&ifname) { if let Ok(guid) = RegistryManager::find_interface_guid(&ifname) {