Auto-select TAP adapters and persist edge settings
This commit is contained in:
+123
-1
@@ -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
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user