Auto-select TAP adapters and persist edge settings

This commit is contained in:
2026-09-01 21:39:58 +08:00
parent 3694d157d1
commit 57e6ba8e1e
5 changed files with 187 additions and 14 deletions
+123 -1
View File
@@ -75,6 +75,20 @@ struct TapTraffic {
sent_bytes: u64,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct TapAdapterRequest {
tap_device: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct TapAdapter {
name: String,
description: String,
guid: String,
}
fn now_epoch() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
@@ -183,6 +197,65 @@ fn windows_tap_traffic(tap_device: &str) -> Result<TapTraffic, String> {
traffic
}
#[cfg(target_os = "windows")]
fn windows_tap_adapters() -> Result<Vec<TapAdapter>, String> {
use std::{ptr, slice};
use windows_sys::Win32::{
Foundation::NO_ERROR,
NetworkManagement::IpHelper::{FreeMibTable, GetIfTable2, MIB_IF_TABLE2},
};
fn wide_string(value: &[u16]) -> String {
let end = value
.iter()
.position(|unit| *unit == 0)
.unwrap_or(value.len());
String::from_utf16_lossy(&value[..end])
}
fn guid_string(guid: &windows_sys::core::GUID) -> String {
format!(
"{{{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}}}",
guid.data1,
guid.data2,
guid.data3,
guid.data4[0],
guid.data4[1],
guid.data4[2],
guid.data4[3],
guid.data4[4],
guid.data4[5],
guid.data4[6],
guid.data4[7]
)
}
let mut table: *mut MIB_IF_TABLE2 = ptr::null_mut();
let result = unsafe { GetIfTable2(&mut table) };
if result != NO_ERROR || table.is_null() {
return Err(format!("unable to read network adapter table: {result}"));
}
let adapters = unsafe {
let rows = slice::from_raw_parts((*table).Table.as_ptr(), (*table).NumEntries as usize);
rows.iter()
.filter_map(|row| {
let description = wide_string(&row.Description);
description
.to_ascii_lowercase()
.contains("tap-windows")
.then_some(TapAdapter {
name: wide_string(&row.Alias),
description,
guid: guid_string(&row.InterfaceGuid),
})
})
.collect()
};
unsafe { FreeMibTable(table.cast()) };
Ok(adapters)
}
#[tauri::command]
fn start_service(request: StartRequest, state: State<'_, AppState>) -> Result<ServiceStatus, String> {
let mut processes = state.processes.lock().map_err(|_| "process state is unavailable".to_string())?;
@@ -209,7 +282,7 @@ fn start_service(request: StartRequest, state: State<'_, AppState>) -> Result<Se
return Err(format!("required n2n binary is missing: {}", binary.display()));
}
let child = Command::new(relative_binary)
let mut child = Command::new(relative_binary)
.current_dir(&working_directory)
.args(&request.args)
.stdout(Stdio::null())
@@ -217,6 +290,13 @@ fn start_service(request: StartRequest, state: State<'_, AppState>) -> Result<Se
.spawn()
.map_err(|e| format!("unable to start {}: {}", binary.display(), e))?;
let pid = child.id();
std::thread::sleep(Duration::from_millis(250));
if let Some(status) = child.try_wait().map_err(|error| error.to_string())? {
return Err(format!(
"{} exited immediately after startup with status {}",
request.service, status
));
}
processes.insert(request.service.clone(), child);
Ok(ServiceStatus {
@@ -393,6 +473,47 @@ fn tap_traffic(request: TapTrafficRequest) -> Result<TapTraffic, String> {
Err("TAP traffic monitoring is not supported on this operating system".to_string())
}
#[tauri::command]
fn resolve_tap_adapter(request: TapAdapterRequest) -> Result<TapAdapter, String> {
let requested = request.tap_device.trim();
if requested.contains(['/', '\\']) || requested == "." || requested == ".." {
return Err("invalid TAP device name".to_string());
}
#[cfg(target_os = "windows")]
{
let adapters = windows_tap_adapters()?;
if adapters.is_empty() {
return Err(
"未找到 TAP-Windows 网卡。请先安装 TAP-Windows 驱动,然后重新连接。".to_string(),
);
}
if requested.is_empty() {
return Ok(adapters.into_iter().next().expect("non-empty adapter list"));
}
return adapters
.into_iter()
.find(|adapter| {
adapter.name.eq_ignore_ascii_case(requested)
|| adapter.description.eq_ignore_ascii_case(requested)
|| adapter.guid.eq_ignore_ascii_case(requested)
})
.ok_or_else(|| format!("找不到指定的 TAP 设备:{requested}"));
}
#[cfg(not(target_os = "windows"))]
{
if requested.is_empty() {
return Err("请输入 TAP 设备名称".to_string());
}
Ok(TapAdapter {
name: requested.to_string(),
description: requested.to_string(),
guid: String::new(),
})
}
}
#[tauri::command]
fn firewall_status() -> Result<ToolResult, String> {
#[cfg(target_os = "windows")]
@@ -497,6 +618,7 @@ pub fn run() {
run_ping,
run_tcping,
tap_traffic,
resolve_tap_adapter,
firewall_status,
export_log
])