From c85d1d41b310688a8d887881a895487e5b5a944c Mon Sep 17 00:00:00 2001 From: deddey Date: Fri, 30 Jan 2026 23:51:52 +0800 Subject: [PATCH] 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 --- easytier/src/instance/virtual_nic.rs | 159 +++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/easytier/src/instance/virtual_nic.rs b/easytier/src/instance/virtual_nic.rs index 8f9cc749..c96c9ee1 100644 --- a/easytier/src/instance/virtual_nic.rs +++ b/easytier/src/instance/virtual_nic.rs @@ -367,10 +367,151 @@ impl VirtualNic { 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, 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 = 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 { + 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 { + 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 { let mut config = Configuration::default(); 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")] { // Check and create TUN device node if necessary (Linux only) @@ -481,9 +622,27 @@ impl VirtualNic { pub async fn create_dev(&mut self) -> Result, Error> { let dev = self.create_tun().await?; + + #[cfg(not(target_os = "freebsd"))] let ifname = dev.tun_name()?; + + #[cfg(target_os = "freebsd")] + let mut ifname = dev.tun_name()?; 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")] { if let Ok(guid) = RegistryManager::find_interface_guid(&ifname) {