diff --git a/Cargo.lock b/Cargo.lock
index 9ea7d9f..b74f296 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2687,6 +2687,19 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "ostp-tun-helper"
+version = "0.1.43"
+dependencies = [
+ "anyhow",
+ "ostp-client",
+ "portable-atomic",
+ "serde",
+ "serde_json",
+ "tokio",
+ "winres",
+]
+
[[package]]
name = "pango"
version = "0.18.3"
@@ -4169,6 +4182,15 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "toml"
version = "0.8.2"
@@ -5204,6 +5226,15 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "winres"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
+dependencies = [
+ "toml 0.5.11",
+]
+
[[package]]
name = "wit-bindgen"
version = "0.51.0"
diff --git a/Cargo.toml b/Cargo.toml
index 09565b6..6c891d6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,7 +4,8 @@ members = [
"ostp-client",
"ostp-server",
"ostp-jni", "ostp",
- "ostp-gui/src-tauri"
+ "ostp-gui/src-tauri",
+ "ostp-tun-helper"
]
resolver = "2"
diff --git a/ostp-gui/package-lock.json b/ostp-gui/package-lock.json
new file mode 100644
index 0000000..7c2beac
--- /dev/null
+++ b/ostp-gui/package-lock.json
@@ -0,0 +1,232 @@
+{
+ "name": "ostp-gui",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "ostp-gui",
+ "version": "0.1.0",
+ "devDependencies": {
+ "@tauri-apps/cli": "^2"
+ }
+ },
+ "node_modules/@tauri-apps/cli": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.1.tgz",
+ "integrity": "sha512-rpEbaJ/HzNb6fwsquwoAbq29/Vt4gADhS423A8fdkwL4edJ0wZmoB8ar7O6JPDL834MUKOCm/rrJ7c9oAaEaYQ==",
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "bin": {
+ "tauri": "tauri.js"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/tauri"
+ },
+ "optionalDependencies": {
+ "@tauri-apps/cli-darwin-arm64": "2.11.1",
+ "@tauri-apps/cli-darwin-x64": "2.11.1",
+ "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.1",
+ "@tauri-apps/cli-linux-arm64-gnu": "2.11.1",
+ "@tauri-apps/cli-linux-arm64-musl": "2.11.1",
+ "@tauri-apps/cli-linux-riscv64-gnu": "2.11.1",
+ "@tauri-apps/cli-linux-x64-gnu": "2.11.1",
+ "@tauri-apps/cli-linux-x64-musl": "2.11.1",
+ "@tauri-apps/cli-win32-arm64-msvc": "2.11.1",
+ "@tauri-apps/cli-win32-ia32-msvc": "2.11.1",
+ "@tauri-apps/cli-win32-x64-msvc": "2.11.1"
+ }
+ },
+ "node_modules/@tauri-apps/cli-darwin-arm64": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.1.tgz",
+ "integrity": "sha512-6eEKMBXsQPCuM1EmvrjT2+aBuxWQuFdKdW8pzNuNQtpq45nEEpBlD5gr8pUeAyOU1DQKlkFaEc/MPBxb/Pfjtg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-darwin-x64": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.1.tgz",
+ "integrity": "sha512-LQUO7exfRWjWALNhetph5guWpMeHphRpokOLk0OIbTTExaNwJNFu3I4vb+CCM/4G/QGoZe/5XikZOJdNEFP1ig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.1.tgz",
+ "integrity": "sha512-5i/awiBCRRhOUG8yjn0fMHXIWD5Ez8eEk5LtvOxyQrKuJkRaZDvnbIjZbE183blAwkoA4xN3aO/prJiqscl02Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-arm64-gnu": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.1.tgz",
+ "integrity": "sha512-9LrwDw3S9Fygtw/Q6WDhOP+3svJRGAsejeE+GKrc0eO1ThMVhwi2LL6hw4dlKw93IfS7VY1G19sWGxJ/NcU4nA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-arm64-musl": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.1.tgz",
+ "integrity": "sha512-mNA5dbbqPqDUdTIwdUYYuhO2GvIe9UnB2r0VU2njxBOS3Opbx4gKNC5yP0Iu4rYmEmqdlwry9VzGZQ3wq9dyFg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.1.tgz",
+ "integrity": "sha512-fZj3Gwq+6fUs305T5WQiD5iSGJw+j/4w/HGmk4sHDAcy+rp9zU5eaxB7nOyz5/I/nkNAuKPqfp6uIbiUBXkBCw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-x64-gnu": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.1.tgz",
+ "integrity": "sha512-XFxGxOvHM7jjeD6ozCKdGfhzJ7lERYDGZl1/Kb4fsvchaJsfLJ981TlyTG8Qy/gFq+f5GitH3bfrX9JAkjPEyw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-x64-musl": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.1.tgz",
+ "integrity": "sha512-d5C2/Zm+68v7R9wTuTCjRQEVrWjcdMkJBZ1+rXse+QdMMlTB9+u9PDNDLw9PQflWxYLaYZ7tjxxL9Nb9II6PbA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-win32-arm64-msvc": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.1.tgz",
+ "integrity": "sha512-YdeVWFAR1pTXzUU6NLstPq4G6OLxuDrXCXEBdmBH+5EZIDXUx0D2kJlz3+YjpazkKvAzYpgziTsyRagls0OfRQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-win32-ia32-msvc": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.1.tgz",
+ "integrity": "sha512-VBGkuH0eB9K9LLSMv361Gzr5Ou72sCS4+ztpmkWEQ+wd/amhcYOsf3X6qn1RJZDzIhiOYHJEOysZUC3baD01rA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-win32-x64-msvc": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.1.tgz",
+ "integrity": "sha512-b3ORhIAKgp9ZYY+zBt7b7r0kLU2kjvyGF0+MS2SBym3emsweGPybEqocJcmtMuxyBhkOKHP4CiuEJEDuAlTx6A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ }
+ }
+}
diff --git a/ostp-gui/src-tauri/build.rs b/ostp-gui/src-tauri/build.rs
index b36ee7a..d860e1e 100644
--- a/ostp-gui/src-tauri/build.rs
+++ b/ostp-gui/src-tauri/build.rs
@@ -1,30 +1,3 @@
fn main() {
- let mut windows = tauri_build::WindowsAttributes::new();
-
- // Define the manifest with requireAdministrator to allow TUN mode without terminal
- // and include Common-Controls v6 for modern UI elements/dialogs.
- let manifest = r#"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- "#;
-
- windows = windows.app_manifest(manifest);
-
- tauri_build::try_build(
- tauri_build::Attributes::new()
- .windows_attributes(windows)
- )
- .expect("failed to run build script");
+ tauri_build::build()
}
diff --git a/ostp-gui/src-tauri/src/lib.rs b/ostp-gui/src-tauri/src/lib.rs
index fd9f317..57a780a 100644
--- a/ostp-gui/src-tauri/src/lib.rs
+++ b/ostp-gui/src-tauri/src/lib.rs
@@ -7,11 +7,12 @@ use anyhow::Result;
use ostp_client::bridge::BridgeMetrics;
use portable_atomic::Ordering;
-// Config deserialization matching ostp core
+// ── Config types ─────────────────────────────────────────────────────────────
+
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(tag = "mode", rename_all = "lowercase")]
enum AppMode {
- Server(serde_json::Value), // We ignore server config in GUI
+ Server(serde_json::Value),
Client(ClientConfigRaw),
}
@@ -69,26 +70,58 @@ struct UIMetrics {
bytes_recv: u64,
}
-struct AppStateInner {
+// ── Messages exchanged with the privileged helper ────────────────────────────
+
+#[derive(Deserialize, Clone)]
+#[serde(tag = "type", rename_all = "lowercase")]
+enum HelperMsg {
+ Status { value: u8 },
+ Log { message: String },
+ Metrics { bytes_sent: u64, bytes_recv: u64 },
+ Error { message: String },
+}
+
+// ── Application state ─────────────────────────────────────────────────────────
+
+// For proxy (non-TUN) mode: runs in-process.
+struct InProcessState {
shutdown_tx: Option>,
- metrics: Option>,
- handle: Option>>,
+ metrics: Arc,
+ handle: JoinHandle>,
+}
+
+// For TUN mode: communicates with the privileged helper via named pipe.
+struct HelperState {
+ /// Shared state updated by pipe reader task
+ pipe_state: Arc>,
+ /// Send commands to helper over named pipe
+ cmd_tx: tokio::sync::mpsc::Sender,
+}
+
+enum TunnelHandle {
+ InProcess(InProcessState),
+ Helper(HelperState),
+}
+
+struct AppStateInner {
+ tunnel: Option,
}
impl Drop for AppStateInner {
fn drop(&mut self) {
- // Send final signal to ensure the core background threads exit immediately
- // and activate Wintun routing cleanup Drop routines.
- if let Some(tx) = self.shutdown_tx.take() {
- let _ = tx.send(true);
+ if let Some(TunnelHandle::InProcess(ref mut s)) = self.tunnel {
+ if let Some(tx) = s.shutdown_tx.take() {
+ let _ = tx.send(true);
+ }
}
}
}
struct AppState(Mutex);
+// ── Config helpers ────────────────────────────────────────────────────────────
+
fn get_config_path() -> PathBuf {
- // Standard behavior: same dir as current exe, or fall back to current working dir
if let Ok(exe_path) = std::env::current_exe() {
if let Some(parent) = exe_path.parent() {
let path = parent.join("config.json");
@@ -100,11 +133,12 @@ fn get_config_path() -> PathBuf {
PathBuf::from("config.json")
}
+// ── Tauri commands ────────────────────────────────────────────────────────────
+
#[tauri::command]
async fn get_config() -> Result {
let path = get_config_path();
if !path.exists() {
- // Return default template if file missing
return Ok(r#"{
"mode": "client",
"log_level": "info",
@@ -124,10 +158,8 @@ async fn get_config() -> Result {
#[tauri::command]
async fn save_config(json_content: String) -> Result {
- // Validate formatting
let _parsed: UnifiedConfig = serde_json::from_str(&json_content)
.map_err(|e| format!("Invalid OSTP config JSON: {}", e))?;
-
let path = get_config_path();
std::fs::write(path, json_content).map_err(|e| format!("Write error: {}", e))?;
Ok(true)
@@ -136,74 +168,105 @@ async fn save_config(json_content: String) -> Result {
#[tauri::command]
async fn get_tunnel_status(state: tauri::State<'_, AppState>) -> Result {
let guard = state.0.lock().await;
- if let Some(ref handle) = guard.handle {
- if handle.is_finished() {
- return Ok(0);
+ match &guard.tunnel {
+ None => Ok(0),
+ Some(TunnelHandle::InProcess(s)) => {
+ if s.handle.is_finished() {
+ return Ok(0);
+ }
+ Ok(s.metrics.connection_state.load(Ordering::Relaxed))
}
- if let Some(ref metrics) = guard.metrics {
- return Ok(metrics.connection_state.load(Ordering::Relaxed));
+ Some(TunnelHandle::Helper(h)) => {
+ let ps = h.pipe_state.lock().await;
+ Ok(ps.connection_state)
}
- Ok(0)
- } else {
- Ok(0)
}
}
#[tauri::command]
async fn get_metrics(state: tauri::State<'_, AppState>) -> Result