From 36a325997cf6fa5dfac7a33e31098cee4a33ea4f Mon Sep 17 00:00:00 2001 From: ospab Date: Wed, 27 May 2026 18:17:11 +0300 Subject: [PATCH] fix(server): generate correct public IP for client configs instead of 0.0.0.0 --- Cargo.lock | 455 +++++++++++++---------- ostp-client/Cargo.toml | 12 +- ostp-client/src/config.rs | 7 + ostp-client/src/runner.rs | 50 ++- ostp-client/src/tunnel/linux_handler.rs | 165 +++++--- ostp-client/src/tunnel/mod.rs | 18 +- ostp-client/src/tunnel/proxy.rs | 20 +- ostp-client/src/tunnel/smoltcp_stack.rs | 272 -------------- ostp-client/src/tunnel/tun_device.rs | 216 ----------- ostp-client/src/tunnel/wintun_handler.rs | 153 +++++--- ostp-core/src/congestion.rs | 17 +- ostp-core/src/protocol.rs | 2 + ostp-gui/src-tauri/src/lib.rs | 2 + ostp-gui/src/index.html | 78 ++-- ostp-gui/src/main.js | 108 +++--- ostp-gui/src/styles.css | 169 +++++---- ostp-jni/Cargo.toml | 2 + ostp-jni/OstpClientSdk.kt | 6 +- ostp-jni/src/lib.rs | 236 +++++++++--- ostp-server/src/api.rs | 5 +- ostp-server/src/lib.rs | 3 +- ostp/src/main.rs | 4 +- 22 files changed, 903 insertions(+), 1097 deletions(-) delete mode 100644 ostp-client/src/tunnel/smoltcp_stack.rs delete mode 100644 ostp-client/src/tunnel/tun_device.rs diff --git a/Cargo.lock b/Cargo.lock index e658986..f12e47e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,6 +111,30 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "atomic-waker" version = "1.1.2" @@ -211,6 +235,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bumpalo" version = "3.20.2" @@ -229,26 +266,6 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -[[package]] -name = "c2rust-bitfields" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b43c3f07ab0ef604fa6f595aa46ec2f8a22172c975e186f6f5bf9829a3b72c41" -dependencies = [ - "c2rust-bitfields-derive", -] - -[[package]] -name = "c2rust-bitfields-derive" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3cbc102e2597c9744c8bd8c15915d554300601c91a079430d309816b0912545" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "cc" version = "1.2.62" @@ -356,7 +373,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -381,6 +398,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -396,6 +422,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crypto-common" version = "0.1.7" @@ -439,7 +471,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -471,7 +503,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -511,7 +543,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -527,9 +559,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] +[[package]] +name = "etherparse" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d8a704b617484e9d867a0423cd45f7577f008c4068e2e33378f8d3860a6d73" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -557,6 +625,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -564,6 +647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -572,6 +656,33 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.32" @@ -580,7 +691,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -601,9 +712,13 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "slab", ] @@ -834,7 +949,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -976,12 +1091,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "ioctl-sys" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd11f3a29434026f5ff98c730b668ba74b1033637b8817940b54d040696133c" - [[package]] name = "ipnet" version = "2.12.0" @@ -1041,7 +1150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1076,15 +1185,15 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" -version = "0.8.9" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" dependencies = [ "cfg-if", "windows-link", @@ -1096,6 +1205,15 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" @@ -1162,6 +1280,34 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "netstack-smoltcp" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398691cef792b89eb5d29e6ea6b3999def706b908d355e29815ba8101cf5c4c8" +dependencies = [ + "etherparse", + "futures", + "rand 0.8.5", + "smoltcp", + "spin", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.11.1", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1232,9 +1378,12 @@ dependencies = [ "base64", "bytes", "chrono", + "futures", "futures-util", "hmac", "json_comments", + "libc", + "netstack-smoltcp", "ostp-core", "portable-atomic", "rand 0.8.5", @@ -1243,14 +1392,12 @@ dependencies = [ "serde", "serde_json", "sha2", - "smoltcp", "socket2", "tokio", "tokio-rustls", "tracing", "tun", "webpki-roots 0.26.11", - "wintun 0.4.0", ] [[package]] @@ -1282,6 +1429,8 @@ dependencies = [ "serde", "serde_json", "tokio", + "tracing", + "tracing-subscriber", ] [[package]] @@ -1328,6 +1477,12 @@ dependencies = [ "winres", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "pem" version = "3.0.6" @@ -1350,6 +1505,17 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "poly1305" version = "0.8.0" @@ -1410,7 +1576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn", ] [[package]] @@ -1432,7 +1598,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1681,7 +1847,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.117", + "syn", "walkdir", ] @@ -1767,6 +1933,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "1.0.28" @@ -1800,7 +1972,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1889,15 +2061,16 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smoltcp" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a1a996951e50b5971a2c8c0fa05a381480d70a933064245c4a223ddc87ccc97" +checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb" dependencies = [ "bitflags 1.3.2", "byteorder", "cfg-if", "defmt 0.3.100", "heapless", + "log", "managed", ] @@ -1927,6 +2100,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -1945,17 +2127,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.117" @@ -1984,7 +2155,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2013,7 +2184,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2024,7 +2195,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2082,9 +2253,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -2104,7 +2275,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2205,7 +2376,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2255,20 +2426,23 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tun" -version = "0.6.1" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0adb9992bbd5ca76f3847ed579ad4ee8defb2ec2eea918cceef17ccc66fa4fd4" +checksum = "bb51dd4da3b9e08057abbaed588d4244bccb0a4122788aff2775b9a4b9aec489" dependencies = [ - "byteorder", "bytes", + "cfg-if", + "futures", "futures-core", - "ioctl-sys", + "ipnet", "libc", "log", - "thiserror 1.0.69", + "nix", + "thiserror 2.0.18", "tokio", "tokio-util", - "wintun 0.3.2", + "windows-sys 0.61.2", + "wintun-bindings", ] [[package]] @@ -2444,7 +2618,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wasm-bindgen-shared", ] @@ -2535,45 +2709,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "windows" -version = "0.51.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" -dependencies = [ - "windows-core 0.51.1", - "windows-targets 0.48.5", -] - -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core 0.52.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.51.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", + "windows-sys 0.52.0", ] [[package]] @@ -2597,7 +2733,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2608,7 +2744,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2677,21 +2813,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -2714,12 +2835,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -2732,12 +2847,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -2750,12 +2859,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2774,12 +2877,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -2792,12 +2889,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -2810,12 +2901,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -2828,12 +2913,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -2850,29 +2929,17 @@ dependencies = [ ] [[package]] -name = "wintun" -version = "0.3.2" +name = "wintun-bindings" +version = "0.7.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29b83b0eca06dd125dbcd48a45327c708a6da8aada3d95a3f06db0ce4b17e0d4" +checksum = "4d94caf41ce60c37c9215c68f124835c401d2ec4f9b3b6431b491c79b3cdd335" dependencies = [ - "c2rust-bitfields", + "blocking", + "futures", "libloading", "log", - "thiserror 1.0.69", - "windows 0.51.1", -] - -[[package]] -name = "wintun" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b3c8c8876c686f8a2d6376999ac1c9a24c74d2968551c9394f7e89127783685" -dependencies = [ - "c2rust-bitfields", - "libloading", - "log", - "thiserror 1.0.69", - "windows 0.52.0", + "thiserror 2.0.18", + "windows-sys 0.61.2", ] [[package]] @@ -2911,7 +2978,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.117", + "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -2927,7 +2994,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -3003,7 +3070,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -3024,7 +3091,7 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3044,7 +3111,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -3084,7 +3151,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] diff --git a/ostp-client/Cargo.toml b/ostp-client/Cargo.toml index a52ac08..f20bb3e 100644 --- a/ostp-client/Cargo.toml +++ b/ostp-client/Cargo.toml @@ -25,11 +25,7 @@ sha2 = "0.10.8" base64 = "0.22.1" webpki-roots = "0.26" rustls-pki-types = "1.7" -smoltcp = { version = "0.11", default-features = false, features = ["std", "medium-ip", "proto-ipv4", "socket-tcp", "socket-udp"] } - -[target.'cfg(target_os = "windows")'.dependencies] -wintun = "0.4" - -[target.'cfg(target_os = "linux")'.dependencies] -tun = { version = "0.6", features = ["async"] } - +tun = { version = "0.8.9", features = ["async"] } +netstack-smoltcp = "0.2.2" +futures = "0.3.32" +libc = "0.2.186" diff --git a/ostp-client/src/config.rs b/ostp-client/src/config.rs index 4583986..4677998 100644 --- a/ostp-client/src/config.rs +++ b/ostp-client/src/config.rs @@ -20,8 +20,12 @@ pub struct ClientConfig { #[serde(default)] pub multiplex: MultiplexConfig, pub dns_server: Option, + #[serde(default = "default_tun_stack")] + pub tun_stack: String, } +fn default_tun_stack() -> String { "system".to_string() } + #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ExclusionConfig { #[serde(default)] @@ -142,6 +146,7 @@ impl Default for ClientConfig { exclusions: ExclusionConfig::default(), multiplex: MultiplexConfig::default(), dns_server: None, + tun_stack: "system".to_string(), } } } @@ -184,6 +189,7 @@ struct RawTransportSection { struct RawTunSection { enable: Option, dns: Option, + stack: Option, } #[derive(Debug, Deserialize)] @@ -275,6 +281,7 @@ impl ClientConfig { sessions: mux.sessions.unwrap_or(1), }, dns_server: raw.tun.as_ref().and_then(|t| t.dns.clone()), + tun_stack: raw.tun.as_ref().and_then(|t| t.stack.clone()).unwrap_or_else(|| "system".to_string()), }) } diff --git a/ostp-client/src/runner.rs b/ostp-client/src/runner.rs index b3507b2..bf48eda 100644 --- a/ostp-client/src/runner.rs +++ b/ostp-client/src/runner.rs @@ -198,6 +198,7 @@ pub async fn run_client_core( let (ui_tx, mut ui_rx) = mpsc::channel(512); let (cmd_tx, cmd_rx) = mpsc::channel(128); let (shutdown_tx, shutdown_rx) = watch::channel(false); + let proxy_shutdown_rx = shutdown_tx.subscribe(); // Auto-connect on startup @@ -247,29 +248,26 @@ pub async fn run_client_core( }); let config_clone = config.clone(); - let (mut proxy_task, mut wintun_task) = if config.mode == "proxy" { - let proxy_shutdown_rx = shutdown_tx.subscribe(); - let t = tokio::spawn(async move { - tunnel::run_local_proxy( - config.local_proxy, - config.ostp, - config.exclusions, - config.debug, - proxy_shutdown_rx, - proxy_events_tx, - client_msgs_rx, - ) - .await - }); - (Some(t), None) - } else if config.mode == "tun" { - let wintun_shutdown_rx = shutdown_tx.subscribe(); - let t = tokio::spawn(async move { - tunnel::run_tun_tunnel(config_clone, proxy_events_tx, client_msgs_rx, wintun_shutdown_rx).await - }); - (None, Some(t)) + let mut proxy_task = tokio::spawn(async move { + tunnel::run_local_proxy( + config.local_proxy, + config.ostp, + config.exclusions, + config.debug, + proxy_shutdown_rx, + proxy_events_tx, + client_msgs_rx, + ) + .await + }); + + let wintun_shutdown_rx = shutdown_tx.subscribe(); + let mut wintun_task = if config_clone.mode == "tun" { + Some(tokio::spawn(async move { + tunnel::run_tun_tunnel(config_clone, wintun_shutdown_rx).await + })) } else { - (None, None) + None }; // Wait for either external shutdown OR any task to fail @@ -282,9 +280,7 @@ pub async fn run_client_core( let _ = shutdown_tx.send(true); res.map_err(|e| anyhow::anyhow!("Bridge task panicked: {}", e))??; } - res = async { - if let Some(ref mut t) = proxy_task { t.await } else { std::future::pending().await } - } => { + res = &mut proxy_task => { let _ = shutdown_tx.send(true); res.map_err(|e| anyhow::anyhow!("Proxy task panicked: {}", e))??; } @@ -298,9 +294,7 @@ pub async fn run_client_core( // Final cleanup: wait for tasks to finish let _ = bridge_task.await; - if let Some(task) = proxy_task { - let _ = task.await; - } + let _ = proxy_task.await; if let Some(task) = wintun_task { let _ = task.await; } diff --git a/ostp-client/src/tunnel/linux_handler.rs b/ostp-client/src/tunnel/linux_handler.rs index 032ada5..db9714c 100644 --- a/ostp-client/src/tunnel/linux_handler.rs +++ b/ostp-client/src/tunnel/linux_handler.rs @@ -1,29 +1,27 @@ use anyhow::{anyhow, Result}; -use tokio::sync::{mpsc, watch}; -#[cfg(target_os = "linux")] -use tracing::info; - -use crate::tunnel::{ProxyEvent, ProxyToClientMsg}; -#[cfg(target_os = "linux")] -use crate::tunnel::tun_device::create_tun_device; -#[cfg(target_os = "linux")] -use crate::tunnel::smoltcp_stack::run_smoltcp_stack; +use tokio::sync::watch; #[cfg(target_os = "linux")] use std::net::ToSocketAddrs; #[cfg(target_os = "linux")] -use std::process::Command; +use std::process::{Command, Stdio, Child}; +#[cfg(target_os = "linux")] +use std::io::{BufRead, BufReader}; #[cfg(target_os = "linux")] struct LinuxRouteGuard { server_ip_str: String, default_gw: String, default_if: String, + child: Option, } #[cfg(target_os = "linux")] impl Drop for LinuxRouteGuard { fn drop(&mut self) { + if let Some(mut child) = self.child.take() { + let _ = child.kill(); + } let cleanup_script = format!( "ip route del 0.0.0.0/1 dev ostp_tun || true; \ ip route del 128.0.0.0/1 dev ostp_tun || true; \ @@ -41,13 +39,39 @@ impl Drop for LinuxRouteGuard { #[cfg(target_os = "linux")] pub async fn run_linux_tunnel( config: crate::config::ClientConfig, - proxy_events_tx: mpsc::Sender, - client_msgs_rx: mpsc::UnboundedReceiver<(u16, ProxyToClientMsg)>, mut shutdown: watch::Receiver, ) -> Result<()> { - info!("Initializing built-in Linux TUN tunnel..."); + let debug = config.debug; + if debug { + println!("[ostp] Initializing TUN tunnel..."); + } - // 1. Pre-flight system checks + let exe = std::env::current_exe()?; + let dir = exe.parent().ok_or_else(|| anyhow!("failed to get binary directory"))?; + + let mut tun2socks_exe = dir.join("tun2socks"); + if !tun2socks_exe.exists() { + // Try system PATH via standard command check + let in_path = Command::new("which") + .arg("tun2socks") + .output() + .map(|o| o.status.success()) + .unwrap_or(false); + + if in_path { + tun2socks_exe = std::path::PathBuf::from("tun2socks"); + } else { + return Err(anyhow!( + "CRITICAL: 'tun2socks' binary is missing!\n\ + OSTP requires tun2socks for TUN mode on Linux. Please download the appropriate binary for your architecture from: \n\ + https://github.com/xjasonlyu/tun2socks/releases \n\ + and place it in the same directory as the ostp executable ({}), or install it globally in your PATH.", + dir.display() + )); + } + } + + // 1.5. Pre-flight system checks let is_root = Command::new("id") .arg("-u") .output() @@ -76,7 +100,10 @@ pub async fn run_linux_tunnel( .ok_or_else(|| anyhow!("Could not resolve host IP for routing exclusion"))?; let server_ip_str = server_ip.to_string(); - info!("Resolved server IP: {}", server_ip_str); + + if debug { + println!("[ostp] Resolved server IP: {}", server_ip_str); + } // 3. Detect current default gateway and interface let route_output = Command::new("sh") @@ -87,6 +114,7 @@ pub async fn run_linux_tunnel( let route_str = String::from_utf8_lossy(&route_output.stdout); let parts: Vec<&str> = route_str.split_whitespace().collect(); + // Expected: "default via 192.168.1.1 dev eth0 ..." let mut default_gw = String::new(); let mut default_if = String::new(); @@ -103,62 +131,95 @@ pub async fn run_linux_tunnel( return Err(anyhow!("Failed to discover active default gateway or network interface on Linux system.")); } - info!("Default route: gateway={} interface={}", default_gw, default_if); + if debug { + println!("[ostp] Default route: gateway={} interface={}", default_gw, default_if); + } - // Create the TunDevice inside the client process (creates the interface and sets up IP/MTU/Status) - let tun_dev = create_tun_device("ostp_tun", config.ostp.mtu)?; - - let mut _guard = LinuxRouteGuard { - server_ip_str: server_ip_str.clone(), - default_gw: default_gw.clone(), - default_if: default_if.clone(), - }; - - // 4. Setup routing rules + // 4. Setup commands (Using standard /1 routing trick for fail-proof overriding) let setup_script = format!( - "ip route add {} via {} dev {}; \ + "ip tuntap add name ostp_tun mode tun || true; \ + ip link set dev ostp_tun mtu {}; \ + ip addr add 10.1.0.2/24 dev ostp_tun || true; \ + ip link set dev ostp_tun up; \ + ip route add {} via {} dev {}; \ ip route add 1.1.1.1 via {} dev {}; \ ip route add 0.0.0.0/1 dev ostp_tun; \ ip route add 128.0.0.0/1 dev ostp_tun", - server_ip_str, default_gw, default_if, + config.ostp.mtu, server_ip_str, default_gw, default_if, default_gw, default_if ); + if debug { + println!("[ostp] Executing Linux network config: {}", setup_script); + } + let out = Command::new("sh") .args(["-c", &setup_script]) .output()?; - if !out.status.success() { - tracing::warn!("Warning: Setup routing returned: {}", String::from_utf8_lossy(&out.stderr)); + if !out.status.success() && debug { + println!("[ostp] Warning: Setup routing returned: {}", String::from_utf8_lossy(&out.stderr)); } - info!("TUN tunnel active. Direct in-process packets handling started."); + // 5. Prepare and launch tun2socks + // Using HTTP Proxy natively avoids any UDP Associate requests, + // providing clean TCP proxying with maximum reliability. + let proxy_url = format!("http://{}", config.local_proxy.bind_addr); + + if debug { + println!("[ostp] Spawning {} -device ostp_tun -proxy {}", tun2socks_exe.display(), proxy_url); + } - // Run the smoltcp stack loop in the background - let stack_shutdown_rx = shutdown.clone(); - let stack_handle = tokio::spawn(async move { - if let Err(e) = run_smoltcp_stack( - tun_dev.packet_rx, - tun_dev.packet_tx, - config.ostp.mtu, - proxy_events_tx, - client_msgs_rx, - stack_shutdown_rx, - ).await { - tracing::error!("smoltcp stack loop failed: {:?}", e); - } - }); + let mut child = Command::new(&tun2socks_exe) + .args([ + "-device", "ostp_tun", + "-proxy", &proxy_url, + ]) + .stdout(if debug { Stdio::piped() } else { Stdio::null() }) + .stderr(if debug { Stdio::piped() } else { Stdio::null() }) + .spawn() + .map_err(|e| anyhow!("Failed to spawn tun2socks process: {}", e))?; - // 5. Wait for shutdown signal + let mut _guard = LinuxRouteGuard { + server_ip_str: server_ip_str.clone(), + default_gw: default_gw.clone(), + default_if: default_if.clone(), + child: None, + }; + + println!("[ostp] TUN tunnel active. All traffic is routed through OSTP."); + + if debug { + let stdout = child.stdout.take().unwrap(); + let stderr = child.stderr.take().unwrap(); + + tokio::spawn(async move { + let reader = BufReader::new(stdout); + for line in reader.lines().map_while(Result::ok) { + println!("[tun2socks] {}", line); + } + }); + + tokio::spawn(async move { + let reader = BufReader::new(stderr); + for line in reader.lines().map_while(Result::ok) { + tracing::warn!("tun2socks: {}", line); + } + }); + } + + _guard.child = Some(child); + + // 6. Wait for shutdown signal let _ = shutdown.changed().await; - info!("Deactivating TUN tunnel..."); + println!("[ostp] Deactivating TUN tunnel..."); + + // Drop guard runs cleanup automatically drop(_guard); - // Terminate smoltcp stack - let _ = stack_handle.await; - - info!("TUN tunnel stopped."); + println!("[ostp] TUN tunnel stopped."); + Ok(()) } @@ -166,8 +227,6 @@ pub async fn run_linux_tunnel( #[allow(dead_code)] pub async fn run_linux_tunnel( _config: crate::config::ClientConfig, - _proxy_events_tx: mpsc::Sender, - _client_msgs_rx: mpsc::UnboundedReceiver<(u16, ProxyToClientMsg)>, _shutdown: watch::Receiver, ) -> Result<()> { Err(anyhow!("Linux tunnel driver executed on a non-Linux host!")) diff --git a/ostp-client/src/tunnel/mod.rs b/ostp-client/src/tunnel/mod.rs index 3b98c5e..d348733 100644 --- a/ostp-client/src/tunnel/mod.rs +++ b/ostp-client/src/tunnel/mod.rs @@ -1,31 +1,30 @@ mod proxy; mod wintun_handler; mod linux_handler; -mod tun_device; -mod smoltcp_stack; +pub mod native_handler; pub async fn run_tun_tunnel( config: crate::config::ClientConfig, - proxy_events_tx: mpsc::Sender, - client_msgs_rx: mpsc::UnboundedReceiver<(u16, ProxyToClientMsg)>, shutdown: watch::Receiver, ) -> anyhow::Result<()> { + if config.tun_stack == "ostp" { + return native_handler::run_native_tunnel(config, shutdown).await; + } + #[cfg(target_os = "windows")] { - wintun_handler::run_wintun_tunnel(config, proxy_events_tx, client_msgs_rx, shutdown).await + wintun_handler::run_wintun_tunnel(config, shutdown).await } #[cfg(target_os = "linux")] { - linux_handler::run_linux_tunnel(config, proxy_events_tx, client_msgs_rx, shutdown).await + linux_handler::run_linux_tunnel(config, shutdown).await } #[cfg(not(any(target_os = "windows", target_os = "linux")))] { let _ = shutdown; let _ = config; - let _ = proxy_events_tx; - let _ = client_msgs_rx; anyhow::bail!("Operating system unsupported, text an issue at github."); } } @@ -72,7 +71,4 @@ pub async fn run_local_proxy( run_local_socks5_proxy(cfg, ostp, exclusions, debug, shutdown, proxy_events_tx, client_msgs_rx).await } -pub use tun_device::create_tun_device_from_fd; -pub use smoltcp_stack::run_smoltcp_stack; - diff --git a/ostp-client/src/tunnel/proxy.rs b/ostp-client/src/tunnel/proxy.rs index 6c8731b..878c495 100644 --- a/ostp-client/src/tunnel/proxy.rs +++ b/ostp-client/src/tunnel/proxy.rs @@ -10,7 +10,7 @@ use crate::tunnel::{ProxyEvent, ProxyToClientMsg}; pub async fn run_local_socks5_proxy( cfg: LocalProxyConfig, - _ostp: OstpConfig, + ostp: OstpConfig, exclusions: ExclusionConfig, debug: bool, mut shutdown: watch::Receiver, @@ -29,6 +29,7 @@ pub async fn run_local_socks5_proxy( let matcher = ExclusionMatcher::new(&exclusions); let (connect_tx, mut connect_rx) = mpsc::channel(128); + let max_chunk = ostp.mtu.saturating_sub(150).max(512); let mut next_stream_id: u16 = 1; let mut active_streams: HashMap> = HashMap::new(); @@ -66,6 +67,7 @@ pub async fn run_local_socks5_proxy( connect_timeout, debug, matcher_clone, + max_chunk, ).await { let msg = err.to_string(); // Suppress routine disconnects and unsupported SOCKS5 command attempts (like UDP) from spam logs @@ -147,6 +149,7 @@ async fn handle_proxy_client( connect_timeout: Duration, debug: bool, matcher: ExclusionMatcher, + max_chunk: usize, ) -> Result<()> { let _guard = StreamGuard { stream_id, close_tx: close_tx.clone() }; @@ -340,7 +343,7 @@ async fn handle_proxy_client( } // ── Bidirectional raw data forwarding ───────────────────────────── - let mut tcp_buf = vec![0_u8; 1024]; + let mut tcp_buf = vec![0_u8; 65536]; loop { tokio::select! { read_res = client.read(&mut tcp_buf) => { @@ -353,10 +356,15 @@ async fn handle_proxy_client( break; } Ok(n) => { - let _ = event_tx.send(ProxyEvent::Data { - stream_id, - payload: bytes::Bytes::copy_from_slice(&tcp_buf[..n]), - }).await; + let mut offset = 0; + while offset < n { + let end = (offset + max_chunk).min(n); + let _ = event_tx.send(ProxyEvent::Data { + stream_id, + payload: bytes::Bytes::copy_from_slice(&tcp_buf[offset..end]), + }).await; + offset = end; + } } Err(_) => { let _ = event_tx.send(ProxyEvent::Close { stream_id }).await; diff --git a/ostp-client/src/tunnel/smoltcp_stack.rs b/ostp-client/src/tunnel/smoltcp_stack.rs deleted file mode 100644 index 7cf97ca..0000000 --- a/ostp-client/src/tunnel/smoltcp_stack.rs +++ /dev/null @@ -1,272 +0,0 @@ -use std::collections::{HashMap, VecDeque}; -use std::time::Instant; -use anyhow::Result; -use tokio::sync::mpsc; -use tokio::sync::watch; -use bytes::Bytes; -use tracing::{info, error, debug}; - -use smoltcp::iface::{Config, Interface, SocketSet, SocketHandle}; -use smoltcp::phy::{Device, DeviceCapabilities, Medium, RxToken, TxToken}; -use smoltcp::socket::tcp::{Socket as TcpSocket, State as TcpState}; -use smoltcp::wire::{IpAddress, IpCidr, Ipv4Packet, IpProtocol, TcpPacket}; - -use crate::tunnel::{ProxyEvent, ProxyToClientMsg}; - -// Custom smoltcp device that bridges to tokio channels -struct ChannelDevice { - rx_queue: VecDeque>, - tx_sender: mpsc::Sender>, - capabilities: DeviceCapabilities, -} - -impl ChannelDevice { - fn new(tx_sender: mpsc::Sender>, mtu: usize) -> Self { - let mut caps = DeviceCapabilities::default(); - caps.medium = Medium::Ip; - caps.max_transmission_unit = mtu; - Self { - rx_queue: VecDeque::new(), - tx_sender, - capabilities: caps, - } - } -} - -struct ChannelRxToken(Vec); - -impl RxToken for ChannelRxToken { - fn consume(mut self, f: F) -> R - where - F: FnOnce(&mut [u8]) -> R, - { - f(&mut self.0) - } -} - -struct ChannelTxToken(mpsc::Sender>); - -impl TxToken for ChannelTxToken { - fn consume(self, len: usize, f: F) -> R - where - F: FnOnce(&mut [u8]) -> R, - { - let mut buffer = vec![0; len]; - let result = f(&mut buffer); - let _ = self.0.try_send(buffer); - result - } -} - -impl Device for ChannelDevice { - type RxToken<'a> = ChannelRxToken where Self: 'a; - type TxToken<'a> = ChannelTxToken where Self: 'a; - - fn receive(&mut self, _timestamp: smoltcp::time::Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { - self.rx_queue.pop_front().map(|packet| { - ( - ChannelRxToken(packet), - ChannelTxToken(self.tx_sender.clone()), - ) - }) - } - - fn transmit(&mut self, _timestamp: smoltcp::time::Instant) -> Option> { - Some(ChannelTxToken(self.tx_sender.clone())) - } - - fn capabilities(&self) -> DeviceCapabilities { - self.capabilities.clone() - } -} - -struct VirtualStream { - _stream_id: u16, - socket_handle: SocketHandle, - last_activity: Instant, - target: String, - established: bool, -} - -pub async fn run_smoltcp_stack( - mut tun_rx: mpsc::Receiver>, - tun_tx: mpsc::Sender>, - mtu: usize, - proxy_events_tx: mpsc::Sender, - mut client_msgs_rx: mpsc::UnboundedReceiver<(u16, ProxyToClientMsg)>, - mut shutdown: watch::Receiver, -) -> Result<()> { - let mut device = ChannelDevice::new(tun_tx, mtu); - - let config = Config::new(smoltcp::wire::HardwareAddress::Ip); - - let mut interface = Interface::new(config, &mut device, smoltcp::time::Instant::now()); - interface.set_any_ip(true); // Required to intercept all packets - interface.update_ip_addrs(|addrs| { - let _ = addrs.push(IpCidr::new(IpAddress::v4(10, 1, 0, 2), 24)); - }); - - let mut sockets = SocketSet::new(vec![]); - let mut stream_id_counter: u16 = 1; - let mut streams: HashMap = HashMap::new(); - let mut handle_to_stream_id: HashMap = HashMap::new(); - - // Map to route incoming data from client_msgs_rx to target sockets - let mut pending_client_msgs: VecDeque<(u16, ProxyToClientMsg)> = VecDeque::new(); - - let mut ticker = tokio::time::interval(std::time::Duration::from_millis(5)); - - info!("smoltcp virtual TCP/IP stack runner active."); - - loop { - tokio::select! { - _ = shutdown.changed() => { - break; - } - _ = ticker.tick() => { - // Periodical stack poll - } - pkt_opt = tun_rx.recv() => { - if let Some(pkt) = pkt_opt { - // Check if it's a new TCP connection (SYN) and dynamically spawn a listener - if let Ok(ipv4_packet) = Ipv4Packet::new_checked(&pkt) { - if ipv4_packet.next_header() == IpProtocol::Tcp { - if let Ok(tcp_packet) = TcpPacket::new_checked(ipv4_packet.payload()) { - if tcp_packet.syn() && !tcp_packet.ack() { - let dst_ip = ipv4_packet.dst_addr(); - let dst_port = tcp_packet.dst_port(); - - // Allocate a stream_id - let stream_id = stream_id_counter; - stream_id_counter = stream_id_counter.wrapping_add(1); - if stream_id_counter == 0 { - stream_id_counter = 1; - } - - let target = format!("{}:{}", dst_ip, dst_port); - debug!("Intercepted TCP SYN to {}, creating virtual socket. Stream ID: {}", target, stream_id); - - // Create socket inside smoltcp - let mut tcp_socket = TcpSocket::new( - smoltcp::socket::tcp::SocketBuffer::new(vec![0; 65535]), - smoltcp::socket::tcp::SocketBuffer::new(vec![0; 65535]), - ); - - if let Err(e) = tcp_socket.listen((dst_ip, dst_port)) { - error!("Failed to set TCP socket to listen: {:?}", e); - } else { - let handle = sockets.add(tcp_socket); - streams.insert(stream_id, VirtualStream { - _stream_id: stream_id, - socket_handle: handle, - last_activity: Instant::now(), - target: target.clone(), - established: false, - }); - handle_to_stream_id.insert(handle, stream_id); - } - } - } - } - } - device.rx_queue.push_back(pkt); - } - } - msg_opt = client_msgs_rx.recv() => { - if let Some((stream_id, msg)) = msg_opt { - pending_client_msgs.push_back((stream_id, msg)); - } - } - } - - // Process pending client messages (responses from OSTP bridge) - let mut unhandled = VecDeque::new(); - while let Some((stream_id, msg)) = pending_client_msgs.pop_front() { - if let Some(stream) = streams.get_mut(&stream_id) { - let socket = sockets.get_mut::(stream.socket_handle); - match msg { - ProxyToClientMsg::ConnectOk => { - stream.established = true; - debug!("Stream ID {} connected successfully via OSTP.", stream_id); - } - ProxyToClientMsg::Data(data) => { - if socket.can_send() { - let _ = socket.send_slice(&data); - stream.last_activity = Instant::now(); - } - } - ProxyToClientMsg::Close | ProxyToClientMsg::Error(_) => { - socket.close(); - // Socket clean-up will occur below - } - } - } else { - unhandled.push_back((stream_id, msg)); - } - } - pending_client_msgs = unhandled; - - // Poll the virtual interface - let timestamp = smoltcp::time::Instant::now(); - let _ = interface.poll(timestamp, &mut device, &mut sockets); - - // Process data transfer from virtual sockets -> OSTP client bridge - let mut closed_streams = Vec::new(); - for (&stream_id, stream) in streams.iter_mut() { - let socket = sockets.get_mut::(stream.socket_handle); - - // 1. Handshake detection & initiation - if socket.is_active() && !stream.established { - if socket.state() == TcpState::Established { - // Send Connect request to the bridge - if proxy_events_tx.try_send(ProxyEvent::NewStream { - stream_id, - target: stream.target.clone(), - }).is_ok() { - stream.established = true; - } - } - } - - // 2. Read inbound data from client OS applications - if socket.may_recv() { - let mut buf = vec![0; 4096]; - if let Ok(n) = socket.recv_slice(&mut buf) { - if n > 0 { - stream.last_activity = Instant::now(); - let _ = proxy_events_tx.try_send(ProxyEvent::Data { - stream_id, - payload: Bytes::from(buf[..n].to_vec()), - }); - } - } - } - - // 3. Connection termination detection - let mut should_close = false; - if !socket.is_active() || socket.state() == TcpState::Closed || socket.state() == TcpState::TimeWait { - should_close = true; - } else if stream.last_activity.elapsed() > std::time::Duration::from_secs(120) { - // Timeout inactive streams - should_close = true; - socket.abort(); - } - - if should_close { - closed_streams.push(stream_id); - } - } - - // Clean up closed streams - for stream_id in closed_streams { - if let Some(stream) = streams.remove(&stream_id) { - debug!("Cleaning up virtual socket for stream ID: {}", stream_id); - handle_to_stream_id.remove(&stream.socket_handle); - sockets.remove(stream.socket_handle); - let _ = proxy_events_tx.try_send(ProxyEvent::Close { stream_id }); - } - } - } - - Ok(()) -} diff --git a/ostp-client/src/tunnel/tun_device.rs b/ostp-client/src/tunnel/tun_device.rs deleted file mode 100644 index 6424d1f..0000000 --- a/ostp-client/src/tunnel/tun_device.rs +++ /dev/null @@ -1,216 +0,0 @@ -use std::sync::Arc; -use anyhow::{anyhow, Result}; -use tokio::sync::mpsc; -use tracing::{info, error, debug}; - -pub struct TunDevice { - pub packet_rx: mpsc::Receiver>, - pub packet_tx: mpsc::Sender>, - _shutdown_tx: mpsc::Sender<()>, -} - -#[cfg(target_os = "windows")] -pub fn create_tun_device(tun_name: &str, mtu: usize) -> Result { - let exe = std::env::current_exe()?; - let dir = exe.parent().ok_or_else(|| anyhow!("failed to get binary directory"))?; - let wintun_dll = dir.join("wintun.dll"); - - if !wintun_dll.exists() { - return Err(anyhow!( - "CRITICAL: 'wintun.dll' is missing at {}!\n\ - Please make sure wintun.dll is present in the binary directory.", - dir.display() - )); - } - - info!("Loading wintun.dll from: {:?}", wintun_dll); - let wintun = unsafe { wintun::load_from_path(wintun_dll)? }; - - // Open or create adapter - let adapter = match wintun::Adapter::open(&wintun, tun_name) { - Ok(a) => a, - Err(_) => { - info!("TUN adapter '{}' not found, creating a new one...", tun_name); - wintun::Adapter::create(&wintun, "Wintun", tun_name, None)? - } - }; - - let session = Arc::new(adapter.start_session(wintun::MAX_RING_CAPACITY)?); - - let (packet_tx_in, packet_rx) = mpsc::channel::>(100000); - let (packet_tx, mut packet_rx_out) = mpsc::channel::>(100000); - let (shutdown_tx, _shutdown_rx) = mpsc::channel::<()>(1); - - // Spawning blocking read loop in a dedicated thread - let session_read = session.clone(); - std::thread::spawn(move || { - loop { - match session_read.receive_blocking() { - Ok(packet) => { - let bytes: &[u8] = packet.bytes(); - if packet_tx_in.blocking_send(bytes.to_vec()).is_err() { - break; - } - } - Err(e) => { - error!("Wintun receive packet error: {:?}", e); - break; - } - } - } - }); - - // Spawning blocking write loop in a dedicated thread - let session_write = session.clone(); - std::thread::spawn(move || { - while let Some(pkt) = packet_rx_out.blocking_recv() { - if pkt.len() > mtu { - debug!("Dropped packet exceeding MTU: {} > {}", pkt.len(), mtu); - continue; - } - match session_write.allocate_send_packet(pkt.len() as u16) { - Ok(mut send_packet) => { - send_packet.bytes_mut().copy_from_slice(&pkt); - session_write.send_packet(send_packet); - } - Err(e) => { - error!("Wintun allocate send packet error: {:?}", e); - } - } - } - }); - - Ok(TunDevice { - packet_rx, - packet_tx, - _shutdown_tx: shutdown_tx, - }) -} - -#[cfg(target_os = "linux")] -pub fn create_tun_device(tun_name: &str, mtu: usize) -> Result { - use tokio::io::{AsyncReadExt, AsyncWriteExt}; - - let mut config = tun::Configuration::default(); - config - .name(tun_name) - .address("10.1.0.2") - .netmask("255.255.255.0") - .mtu(mtu as i32) - .up(); - - let device = tun::create_as_async(&config)?; - let (mut reader, mut writer) = tokio::io::split(device); - - let (packet_tx_in, packet_rx) = mpsc::channel::>(100000); - let (packet_tx, mut packet_rx_out) = mpsc::channel::>(100000); - let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1); - - // Read loop - tokio::spawn(async move { - let mut buf = vec![0_u8; 65535]; - loop { - match reader.read(&mut buf).await { - Ok(0) => break, - Ok(n) => { - if packet_tx_in.send(buf[..n].to_vec()).await.is_err() { - break; - } - } - Err(e) => { - error!("TUN read error: {:?}", e); - break; - } - } - } - }); - - // Write loop - tokio::spawn(async move { - loop { - tokio::select! { - _ = shutdown_rx.recv() => { - break; - } - pkt_opt = packet_rx_out.recv() => { - if let Some(pkt) = pkt_opt { - if let Err(e) = writer.write_all(&pkt).await { - error!("TUN write error: {:?}", e); - break; - } - } else { - break; - } - } - } - } - }); - - Ok(TunDevice { - packet_rx, - packet_tx, - _shutdown_tx: shutdown_tx, - }) -} - -#[cfg(not(any(target_os = "windows", target_os = "linux")))] -pub fn create_tun_device(_tun_name: &str, _mtu: usize) -> Result { - Err(anyhow!("Unsupported operating system for TUN device")) -} - -#[cfg(unix)] -pub fn create_tun_device_from_fd(fd: i32, mtu: usize) -> Result { - use std::os::unix::io::FromRawFd; - use std::io::{Read, Write}; - - let file = unsafe { std::fs::File::from_raw_fd(fd) }; - let mut file_read = file.try_clone()?; - let mut file_write = file; - - let (packet_tx_in, packet_rx) = mpsc::channel::>(100000); - let (packet_tx, mut packet_rx_out) = mpsc::channel::>(100000); - let (shutdown_tx, _shutdown_rx) = mpsc::channel::<()>(1); - - // Read loop thread - std::thread::spawn(move || { - let mut buf = vec![0_u8; 65535]; - loop { - match file_read.read(&mut buf) { - Ok(0) => break, - Ok(n) => { - if packet_tx_in.blocking_send(buf[..n].to_vec()).is_err() { - break; - } - } - Err(e) => { - error!("TUN fd read error: {:?}", e); - break; - } - } - } - }); - - // Write loop thread - std::thread::spawn(move || { - while let Some(pkt) = packet_rx_out.blocking_recv() { - if pkt.len() > mtu { - continue; - } - if let Err(e) = file_write.write_all(&pkt) { - error!("TUN fd write error: {:?}", e); - break; - } - } - }); - - Ok(TunDevice { - packet_rx, - packet_tx, - _shutdown_tx: shutdown_tx, - }) -} - -#[cfg(not(unix))] -pub fn create_tun_device_from_fd(_fd: i32, _mtu: usize) -> Result { - Err(anyhow!("Raw fd TUN device is not supported on this operating system")) -} diff --git a/ostp-client/src/tunnel/wintun_handler.rs b/ostp-client/src/tunnel/wintun_handler.rs index 5423eae..493e434 100644 --- a/ostp-client/src/tunnel/wintun_handler.rs +++ b/ostp-client/src/tunnel/wintun_handler.rs @@ -1,36 +1,29 @@ use anyhow::{anyhow, Result}; -use tokio::sync::{mpsc, watch}; -#[cfg(target_os = "windows")] -use tracing::info; - -#[cfg(target_os = "windows")] -use std::os::windows::process::CommandExt; - -use crate::tunnel::{ProxyEvent, ProxyToClientMsg}; -#[cfg(target_os = "windows")] -use crate::tunnel::tun_device::create_tun_device; -#[cfg(target_os = "windows")] -use crate::tunnel::smoltcp_stack::run_smoltcp_stack; +use tokio::sync::watch; #[cfg(target_os = "windows")] pub async fn run_wintun_tunnel( config: crate::config::ClientConfig, - proxy_events_tx: mpsc::Sender, - client_msgs_rx: mpsc::UnboundedReceiver<(u16, ProxyToClientMsg)>, mut shutdown: watch::Receiver, ) -> Result<()> { use std::net::ToSocketAddrs; - use std::process::Command; + use std::process::{Command, Stdio, Child}; + use std::os::windows::process::CommandExt; const CREATE_NO_WINDOW: u32 = 0x08000000; const TUN_NAME: &str = "ostp_tun"; struct WintunGuard { server_ip_str: String, + child: Option, } impl Drop for WintunGuard { fn drop(&mut self) { + if let Some(mut child) = self.child.take() { + let _ = child.kill(); + let _ = child.wait(); + } let cleanup_script = format!( "$remote_ip = '{}'\n\ Remove-NetRoute -DestinationPrefix \"$remote_ip/32\" -Confirm:$false -ErrorAction SilentlyContinue\n\ @@ -46,9 +39,27 @@ pub async fn run_wintun_tunnel( } } - info!("Initializing built-in Wintun TUN tunnel..."); + let debug = config.debug; + + tracing::info!("Initializing TUN tunnel..."); + + let exe = std::env::current_exe()?; + let dir = exe.parent().ok_or_else(|| anyhow!("failed to get binary directory"))?; + let tun2socks_exe = dir.join("tun2socks.exe"); + + if !tun2socks_exe.exists() { + return Err(anyhow!( + "CRITICAL: 'tun2socks.exe' binary is missing!\n\ + OSTP requires tun2socks for TUN mode on Windows. Please download the appropriate binary from: \n\ + https://github.com/xjasonlyu/tun2socks/releases \n\ + and place it in the same directory as the ostp executable ({}).", + dir.display() + )); + } // 1. Delete stale TUN adapter if it exists from a previous run. + // This prevents wintun from creating "ostp_tun 2", "ostp_tun 3", etc. + tracing::info!("Cleaning up stale TUN adapter..."); let _ = Command::new("powershell") .creation_flags(CREATE_NO_WINDOW) .args(["-NoProfile", "-Command", &format!( @@ -57,6 +68,7 @@ pub async fn run_wintun_tunnel( netsh interface set interface \"{TUN_NAME}\" admin=disable 2>$null" )]) .output(); + // Brief pause to let the driver release the adapter tokio::time::sleep(std::time::Duration::from_millis(200)).await; // 2. Resolve Server IP for routing table exclusion @@ -67,7 +79,7 @@ pub async fn run_wintun_tunnel( .ok_or_else(|| anyhow!("Could not resolve host IP for routing exclusion"))?; let server_ip_str = server_ip.to_string(); - info!("Resolved server IP: {}", server_ip_str); + tracing::info!("Resolved server IP: {}", server_ip_str); // 3. Prepare routing and firewall setup script let current_exe = std::env::current_exe()?.to_string_lossy().into_owned(); @@ -75,33 +87,42 @@ pub async fn run_wintun_tunnel( let setup_script = format!( "$remote_ip = '{}'\n\ $exe_path = '{}'\n\ - $route = Find-NetRoute -RemoteIPAddress $remote_ip -ErrorAction SilentlyContinue | Select-Object -First 1\n\ - if (-not $route) {{\n\ - $route = Get-NetRoute -DestinationPrefix '0.0.0.0/0' | Where-Object {{ $_.InterfaceAlias -notmatch 'tun' -and $_.InterfaceAlias -notmatch 'wintun' }} | Sort-Object RouteMetric | Select-Object -First 1\n\ + $route = Get-NetRoute -DestinationPrefix '0.0.0.0/0' | Where-Object {{ $_.InterfaceAlias -notmatch 'tun' -and $_.InterfaceAlias -notmatch 'wintun' }} | Sort-Object RouteMetric | Select-Object -First 1\n\ + if ($route) {{\n\ + $gw = $route.NextHop\n\ + $ifIndex = $route.InterfaceIndex\n\ + New-NetRoute -DestinationPrefix \"$remote_ip/32\" -NextHop $gw -InterfaceIndex $ifIndex -RouteMetric 1 -ErrorAction SilentlyContinue\n\ }}\n\ - $gw = $route.NextHop\n\ - $ifIndex = $route.InterfaceIndex\n\ - New-NetRoute -DestinationPrefix \"$remote_ip/32\" -NextHop $gw -InterfaceIndex $ifIndex -RouteMetric 1 -ErrorAction SilentlyContinue\n\ - $dns_ips = Get-DnsClientServerAddress -InterfaceIndex $ifIndex | Select-Object -ExpandProperty ServerAddresses\n\ - foreach ($dns in $dns_ips) {{\n\ - if ($dns -match '^\\d+\\.\\d+\\.\\d+\\.\\d+$') {{\n\ - New-NetRoute -DestinationPrefix \"$dns/32\" -NextHop $gw -InterfaceIndex $ifIndex -RouteMetric 1 -ErrorAction SilentlyContinue\n\ - }}\n\ - }}\n\ - New-NetRoute -DestinationPrefix \"1.1.1.1/32\" -NextHop $gw -InterfaceIndex $ifIndex -RouteMetric 1 -ErrorAction SilentlyContinue\n\ New-NetFirewallRule -DisplayName 'OSTP Tunnel In' -Direction Inbound -Program $exe_path -Action Allow -Enabled True -ErrorAction SilentlyContinue\n\ New-NetFirewallRule -DisplayName 'OSTP Tunnel Out' -Direction Outbound -Program $exe_path -Action Allow -Enabled True -ErrorAction SilentlyContinue\n", server_ip_str, current_exe ); - // Create the TunDevice inside the client process - let tun_dev = create_tun_device(TUN_NAME, config.ostp.mtu)?; + // 4. Launch tun2socks + route setup IN PARALLEL to save ~3 seconds + let proxy_url = format!("http://{}", config.local_proxy.bind_addr); + tracing::info!("Starting tun2socks (proxy={})", proxy_url); + + // Spawn tun2socks immediately — it creates the adapter on its own + let mut child = Command::new(&tun2socks_exe) + .creation_flags(CREATE_NO_WINDOW) + .args([ + "-device", TUN_NAME, + "-proxy", &proxy_url, + "-loglevel", if debug { "debug" } else { "error" } + ]) + .current_dir(dir) + .stdout(if debug { Stdio::piped() } else { Stdio::null() }) + .stderr(if debug { Stdio::piped() } else { Stdio::null() }) + .spawn() + .map_err(|e| anyhow!("Failed to launch tun2socks.exe: {}", e))?; let mut _guard = WintunGuard { server_ip_str: server_ip_str.clone(), + child: None, }; - // Run route setup in parallel + // Run route setup in parallel while tun2socks creates the adapter. + // Also poll for the adapter to appear (typically <1s). let route_handle = { let script = setup_script.clone(); tokio::task::spawn_blocking(move || { @@ -112,7 +133,7 @@ pub async fn run_wintun_tunnel( }) }; - // 5. Wait for TUN adapter to appear + // 5. Wait for TUN adapter to appear (poll with timeout instead of fixed 2s sleep) let adapter_deadline = tokio::time::Instant::now() + tokio::time::Duration::from_secs(8); let mut adapter_ready = false; while tokio::time::Instant::now() < adapter_deadline { @@ -124,6 +145,9 @@ pub async fn run_wintun_tunnel( .output(); if let Ok(out) = check { let status = String::from_utf8_lossy(&out.stdout).trim().to_string(); + if debug { + tracing::info!("Adapter status: '{}'", status); + } if status == "Up" || status == "Disconnected" || !status.is_empty() { adapter_ready = true; break; @@ -135,21 +159,22 @@ pub async fn run_wintun_tunnel( tracing::warn!("WARNING: TUN adapter did not appear within timeout. Proceeding anyway."); } - // Wait for route setup to finish + // Wait for route setup to finish (should already be done by now) let _ = route_handle.await; - // 6. Configure the adapter - info!("Applying network configuration..."); + // 6. Configure the adapter (IP, metric, MTU, DNS) + tracing::info!("Applying network configuration..."); let mut net_setup = format!( "netsh interface ipv4 set address name=\"{TUN_NAME}\" static 10.1.0.2 255.255.255.0 10.1.0.1\n\ netsh interface ipv4 set subinterface \"{TUN_NAME}\" mtu={} store=persistent\n\ - netsh interface ipv4 set interface name=\"{TUN_NAME}\" metric=5\n", + netsh interface ipv4 set interface name=\"{TUN_NAME}\" metric=1\n\ + New-NetRoute -DestinationPrefix '0.0.0.0/0' -InterfaceAlias '{TUN_NAME}' -NextHop '10.1.0.1' -RouteMetric 1 -ErrorAction SilentlyContinue\n", config.ostp.mtu ); if let Some(ref dns) = config.dns_server { if !dns.is_empty() { - info!("DNS server: {}", dns); + tracing::info!("DNS server: {}", dns); net_setup.push_str(&format!( "netsh interface ipv4 set dnsservers name=\"{TUN_NAME}\" static {} primary\n", dns )); @@ -161,33 +186,41 @@ pub async fn run_wintun_tunnel( .args(["-NoProfile", "-Command", &net_setup]) .output()?; - info!("TUN tunnel active. Direct in-process packets handling started."); + tracing::info!("TUN tunnel active. All traffic is routed through OSTP."); - // Run the smoltcp stack loop in the background - let stack_shutdown_rx = shutdown.clone(); - let stack_handle = tokio::spawn(async move { - if let Err(e) = run_smoltcp_stack( - tun_dev.packet_rx, - tun_dev.packet_tx, - config.ostp.mtu, - proxy_events_tx, - client_msgs_rx, - stack_shutdown_rx, - ).await { - tracing::error!("smoltcp stack loop failed: {:?}", e); - } - }); + // 7. Spawn debug log readers for tun2socks output + let mut stdout = child.stdout.take(); + let mut stderr = child.stderr.take(); + _guard.child = Some(child); + + if debug { + std::thread::spawn(move || { + use std::io::{BufRead, BufReader}; + if let Some(out) = stdout.take() { + let reader = BufReader::new(out); + for line in reader.lines().map_while(Result::ok) { + tracing::debug!("tun2socks: {}", line); + } + } + }); + std::thread::spawn(move || { + use std::io::{BufRead, BufReader}; + if let Some(err) = stderr.take() { + let reader = BufReader::new(err); + for line in reader.lines().map_while(Result::ok) { + tracing::warn!("tun2socks: {}", line); + } + } + }); + } // 8. Wait for shutdown signal let _ = shutdown.changed().await; - info!("Deactivating TUN tunnel..."); + tracing::info!("Deactivating TUN tunnel..."); drop(_guard); + tracing::info!("TUN tunnel stopped."); - // Terminate smoltcp stack - let _ = stack_handle.await; - - info!("TUN tunnel stopped."); Ok(()) } @@ -195,8 +228,6 @@ pub async fn run_wintun_tunnel( #[allow(dead_code)] pub async fn run_wintun_tunnel( _config: crate::config::ClientConfig, - _proxy_events_tx: mpsc::Sender, - _client_msgs_rx: mpsc::UnboundedReceiver<(u16, ProxyToClientMsg)>, _shutdown: watch::Receiver, ) -> Result<()> { Err(anyhow!("Wintun driver executed on a non-Windows host!")) diff --git a/ostp-core/src/congestion.rs b/ostp-core/src/congestion.rs index c25eedc..48016d5 100644 --- a/ostp-core/src/congestion.rs +++ b/ostp-core/src/congestion.rs @@ -165,16 +165,8 @@ impl CongestionController { } } Phase::ProbeBandwidth => { - // BBR-style: target cwnd = BDP * gain - let bdp = self.bandwidth_delay_product(); - // Apply gain of 1.25 during probe bandwidth - let target = (bdp * 5 / 4).max(MIN_CWND_PACKETS * self.mtu); - // Smooth transition - if self.cwnd < target { - self.cwnd = self.cwnd.saturating_add(bytes * self.mtu / self.cwnd.max(1)); - } else { - self.cwnd = target; - } + // TCP Reno Additive Increase: increase cwnd by ~1 MTU per RTT + self.cwnd = self.cwnd.saturating_add(bytes * self.mtu / self.cwnd.max(1)); } Phase::ProbeRtt => { // Drain down to 4 packets to measure true min RTT @@ -184,20 +176,21 @@ impl CongestionController { // ProbeRTT complete, return to ProbeBandwidth self.phase = Phase::ProbeBandwidth; self.probe_rtt_timer = None; - let bdp = self.bandwidth_delay_product(); - self.cwnd = bdp.max(MIN_CWND_PACKETS * self.mtu); + self.cwnd = (MIN_CWND_PACKETS * self.mtu * 4).max(self.cwnd); tracing::debug!(cwnd = self.cwnd, min_rtt = ?self.min_rtt, "congestion: probe RTT complete"); } } } } + /* // Periodically enter ProbeRTT to refresh min_rtt if now.duration_since(self.min_rtt_stamp) >= MIN_RTT_EXPIRY && self.phase != Phase::ProbeRtt { self.phase = Phase::ProbeRtt; self.probe_rtt_timer = Some(now); tracing::debug!("congestion: entering probe RTT phase"); } + */ self.update_pacing_rate(); self.last_ack_time = now; diff --git a/ostp-core/src/protocol.rs b/ostp-core/src/protocol.rs index acafffe..4f2a4fd 100644 --- a/ostp-core/src/protocol.rs +++ b/ostp-core/src/protocol.rs @@ -100,6 +100,7 @@ pub struct ProtocolMachine { /// Key-derived handshake padding range handshake_pad_min: usize, handshake_pad_max: usize, + mtu: usize, } #[derive(Debug, Clone)] @@ -145,6 +146,7 @@ impl ProtocolMachine { cc: CongestionController::new(config.mtu as u64), handshake_pad_min: config.handshake_pad_min.max(8), handshake_pad_max: config.handshake_pad_max.max(config.handshake_pad_min + 16), + mtu: config.mtu, }) } diff --git a/ostp-gui/src-tauri/src/lib.rs b/ostp-gui/src-tauri/src/lib.rs index 8333f8a..dc461e6 100644 --- a/ostp-gui/src-tauri/src/lib.rs +++ b/ostp-gui/src-tauri/src/lib.rs @@ -42,6 +42,7 @@ struct TunConfig { wintun_path: Option, ipv4_address: Option, dns: Option, + stack: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] @@ -179,6 +180,7 @@ fn map_to_client_config(raw: &ClientConfigRaw, mode: &str) -> ostp_client::confi sessions: raw.mux.as_ref().and_then(|m| m.sessions).unwrap_or(1), }, dns_server: raw.tun.as_ref().and_then(|t| t.dns.clone()), + tun_stack: raw.tun.as_ref().and_then(|t| t.stack.clone()).unwrap_or_else(|| "system".to_string()), } } diff --git a/ostp-gui/src/index.html b/ostp-gui/src/index.html index 779d08e..b3fb663 100644 --- a/ostp-gui/src/index.html +++ b/ostp-gui/src/index.html @@ -16,7 +16,6 @@ @@ -29,9 +28,6 @@ OSTP
-
- - - -
- -
-
- - - -
-
- Ping - — ms -
-
- -
- -
-
- - - - -
-
- Mode - -
-
@@ -243,6 +224,14 @@ +
+ + +
+
Multiplexing (Mux) @@ -291,8 +280,7 @@
- - + diff --git a/ostp-gui/src/main.js b/ostp-gui/src/main.js index 2c4eba7..eafca38 100644 --- a/ostp-gui/src/main.js +++ b/ostp-gui/src/main.js @@ -24,21 +24,18 @@ const orbitWrap = $('orbit-wrap'); const brandDot = $('brand-dot'); const statusLabel = $('status-text'); const statusSub = $('uptime-text'); -const serverBadge = $('server-badge'); +const connInfo = $('connection-info'); const serverBadgeTxt = $('server-badge-text'); const metricDown = $('metric-down'); const metricUp = $('metric-up'); -const metricMode = $('metric-mode'); -const metricPing = $('metric-ping'); -const pingIconBlock = $('ping-icon-block'); +const pingValueTxt = $('ping-text-value'); +const btnTestPing = $('btn-test-ping'); const toast = $('toast'); const btnGoSettings = $('btn-go-settings'); const btnBack = $('btn-back'); -const btnLang = $('btn-lang'); const btnImport = $('btn-import-url'); const btnPeekKey = $('btn-peek-key'); -const btnSave = $('btn-save-config'); const importInput = $('in-import-url'); const inServer = $('in-server'); const inKey = $('in-key'); @@ -50,6 +47,8 @@ const inPbk = $('in-pbk'); const inSid = $('in-sid'); const inMtu = $('in-mtu'); const inTun = $('in-tun-mode'); +const inTunStack = $('in-tun-stack'); +const groupTunStack = $('group-tun-stack'); const inMux = $('in-mux-mode'); const inMuxSessions = $('in-mux-sessions'); const inDebug = $('in-debug'); @@ -105,10 +104,11 @@ function setState(next) { statusLabel.textContent = t('status_disconnected'); statusSub.textContent = t('hint_tap'); statusLabel.classList.add(''); - serverBadge.classList.add('hidden'); + connInfo.classList.add('hidden'); metricDown.textContent = '0 B'; metricUp.textContent = '0 B'; - metricMode.textContent = '—'; + pingValueTxt.textContent = 'Target Ping: -- ms'; + pingValueTxt.className = 'ping-test-value'; clearInterval(pollTimer); clearInterval(uptimeTimer); pollTimer = uptimeTimer = null; @@ -121,7 +121,7 @@ function setState(next) { statusLabel.classList.add('is-connecting'); statusLabel.textContent = t('status_connecting'); statusSub.textContent = t('hint_connecting'); - serverBadge.classList.add('hidden'); + connInfo.classList.add('hidden'); clearInterval(uptimeTimer); uptimeSecs = 0; @@ -132,10 +132,10 @@ function setState(next) { statusLabel.classList.add('is-connected'); statusLabel.textContent = t('status_connected'); - // Show server badge + // Show connection info if (serverAddr) { serverBadgeTxt.textContent = serverAddr; - serverBadge.classList.remove('hidden'); + connInfo.classList.remove('hidden'); } // Start uptime counter @@ -161,29 +161,6 @@ async function poll() { if (metrics) { metricDown.textContent = fmtBytes(metrics.bytes_recv); metricUp.textContent = fmtBytes(metrics.bytes_sent); - - const isTun = rawConfig?.tun?.enable; - metricMode.textContent = isTun ? 'TUN' : 'SOCKS5'; - - const rtt = metrics.rtt_ms || 0; - if (rtt > 0) { - metricPing.textContent = rtt + ' ms'; - // Color code: green < 80ms, yellow < 200ms, red >= 200ms - if (rtt < 80) { - metricPing.style.color = 'var(--ping-good, #4ade80)'; - pingIconBlock.style.color = 'var(--ping-good, #4ade80)'; - } else if (rtt < 200) { - metricPing.style.color = 'var(--ping-mid, #facc15)'; - pingIconBlock.style.color = 'var(--ping-mid, #facc15)'; - } else { - metricPing.style.color = 'var(--ping-bad, #f87171)'; - pingIconBlock.style.color = 'var(--ping-bad, #f87171)'; - } - } else { - metricPing.textContent = '— ms'; - metricPing.style.color = ''; - pingIconBlock.style.color = ''; - } } } catch { setState('disconnected'); @@ -199,15 +176,10 @@ function startPolling() { // ── Connect / Disconnect ───────────────────────────────────────────────────── async function handleToggle() { if (appState === 'disconnected') { - // Read server address for badge before connecting try { const raw = await invoke('get_config'); const cfg = JSON.parse(raw); serverAddr = cfg.server || ''; - - // Determine mode label - const isTun = cfg.tun?.enable; - metricMode.textContent = isTun ? 'TUN' : 'SOCKS5'; } catch { serverAddr = ''; } setState('connecting'); @@ -260,8 +232,11 @@ async function loadConfigIntoForm() { inSid.value = c.reality?.sid || ''; inMtu.value = c.mtu || ''; inTun.checked = !!c.tun?.enable; + inTunStack.value = c.tun?.stack || 'system'; inMux.checked = !!c.mux?.enabled; inMuxSessions.value = c.mux?.sessions || ''; + + groupTunStack.style.display = inTun.checked ? 'block' : 'none'; inDns.value = c.tun?.dns || ''; inDebug.checked = !!c.debug; @@ -275,14 +250,20 @@ async function loadConfigIntoForm() { } // ── Config — save ───────────────────────────────────────────────────────────── -async function handleSave() { +let autoSaveTimer = null; +function scheduleAutoSave() { + clearTimeout(autoSaveTimer); + autoSaveTimer = setTimeout(() => handleSave(true), 600); +} + +async function handleSave(silent = false) { if (!rawConfig) rawConfig = { mode: 'client', log_level: 'info' }; const server = inServer.value.trim(); const key = inKey.value.trim(); - if (!server) { showToast(t('err_server_req') || 'Server address required', 'error'); return; } - if (!key) { showToast(t('err_key_req') || 'Access key required', 'error'); return; } + if (!server) { if (!silent) showToast(t('err_server_req') || 'Server address required', 'error'); return; } + if (!key) { if (!silent) showToast(t('err_key_req') || 'Access key required', 'error'); return; } rawConfig.mode = 'client'; rawConfig.server = server; @@ -324,6 +305,7 @@ async function handleSave() { } rawConfig.tun.enable = inTun.checked; rawConfig.tun.dns = inDns.value.trim() || null; + rawConfig.tun.stack = inTunStack.value; rawConfig.exclude = { domains: splitLines(inDomains.value), @@ -333,14 +315,11 @@ async function handleSave() { try { const ok = await invoke('save_config', { jsonContent: JSON.stringify(rawConfig, null, 2) }); - if (ok) { - showToast(t('toast_saved'), 'ok'); - setTimeout(() => showScreen('home'), 700); - } else { + if (!ok && !silent) { showToast(t('toast_error'), 'error'); } } catch (err) { - showToast(String(err), 'error'); + if (!silent) showToast(String(err), 'error'); } } @@ -366,6 +345,7 @@ function handleImport() { importInput.value = ''; showToast(t('toast_imported'), 'ok'); + handleSave(false); } catch (err) { showToast(err.message, 'error'); } @@ -390,19 +370,35 @@ window.addEventListener('DOMContentLoaded', async () => { btnConnect.addEventListener('click', handleToggle); btnGoSettings.addEventListener('click', () => showScreen('settings')); btnBack.addEventListener('click', () => showScreen('home')); - btnSave.addEventListener('click', handleSave); btnImport.addEventListener('click', handleImport); btnPeekKey.addEventListener('click', togglePeek); + inTun.addEventListener('change', () => { groupTunStack.style.display = inTun.checked ? 'block' : 'none'; }); importInput.addEventListener('keydown', e => { if (e.key === 'Enter') handleImport(); }); - btnLang.addEventListener('click', () => { - toggleLang(); - // Refresh dynamic text without losing state - const cur = appState; - appState = ''; - setState(cur); - document.getElementById('lang-label').textContent = - localStorage.getItem('ostp_lang') === 'ru' ? 'RU' : 'EN'; + // Auto-save wiring + const formInputs = document.querySelectorAll('#settings-screen input:not(#in-import-url), #settings-screen textarea, #settings-screen select'); + formInputs.forEach(el => { + el.addEventListener('input', scheduleAutoSave); + el.addEventListener('change', scheduleAutoSave); + }); + + btnTestPing.addEventListener('click', async () => { + pingValueTxt.textContent = 'Testing...'; + pingValueTxt.className = 'ping-test-value'; + try { + const metrics = await invoke('get_metrics'); + if (metrics && metrics.rtt_ms > 0) { + const rtt = metrics.rtt_ms; + pingValueTxt.textContent = `Target Ping: ${rtt} ms`; + if (rtt < 80) pingValueTxt.classList.add('good'); + else if (rtt < 200) pingValueTxt.classList.add('warn'); + else pingValueTxt.classList.add('bad'); + } else { + pingValueTxt.textContent = 'Target Ping: -- ms'; + } + } catch { + pingValueTxt.textContent = 'Target Ping: Error'; + } }); // Restore status on app open diff --git a/ostp-gui/src/styles.css b/ostp-gui/src/styles.css index 49176d1..109830e 100644 --- a/ostp-gui/src/styles.css +++ b/ostp-gui/src/styles.css @@ -84,35 +84,26 @@ input, textarea { font-family: inherit; } .blob { position: absolute; border-radius: 50%; - filter: blur(80px); + filter: blur(100px); will-change: transform; } .blob-1 { - width: 360px; height: 360px; + width: 400px; height: 400px; background: var(--c-accent); - opacity: 0.055; - top: -140px; right: -80px; + opacity: 0.15; + top: -150px; right: -100px; animation: blob-drift 28s infinite alternate ease-in-out; } .blob-2 { - width: 280px; height: 280px; + width: 350px; height: 350px; background: var(--c-green); - opacity: 0.04; - bottom: -100px; left: -60px; + opacity: 0.10; + bottom: -100px; left: -100px; animation: blob-drift 22s infinite alternate-reverse ease-in-out; } -.blob-3 { - width: 200px; height: 200px; - background: var(--c-accent-2); - opacity: 0.035; - top: 50%; left: 50%; - transform: translate(-50%, -50%); - animation: blob-drift 35s infinite alternate ease-in-out; -} - @keyframes blob-drift { from { transform: translate(0, 0); } to { transform: translate(30px, 20px); } @@ -379,38 +370,100 @@ input, textarea { font-family: inherit; } opacity: 0.7; } -/* ── Server badge ─────────────────────────────────────────────────────────── */ +/* ── Connection info ──────────────────────────────────────────────────────── */ +.connection-info { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + width: 100%; + transition: all var(--t-med); +} + +.connection-info.hidden { + opacity: 0; pointer-events: none; transform: translateY(4px); +} + .server-badge { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 12px 20px; + border-radius: 30px; + background: rgba(255,255,255,0.08); + border: 1px solid rgba(255,255,255,0.15); + font-size: 0.95rem; + color: rgba(255,255,255,0.7); + font-family: 'JetBrains Mono', monospace; + font-weight: 600; + backdrop-filter: blur(8px); +} + +.ping-test-box { + display: flex; + justify-content: space-between; + align-items: center; + width: calc(100% - 64px); + max-width: 400px; + padding: 12px 16px; + background: rgba(255,255,255,0.03); + border-radius: 20px; + border: 1px solid rgba(255,255,255,0.06); +} + +.ping-test-left { + display: flex; + flex-direction: column; + gap: 4px; +} + +.ping-test-title { + font-size: 0.62rem; + font-weight: 700; + color: rgba(255,255,255,0.38); + letter-spacing: 0.8px; +} + +.ping-test-value { + font-size: 0.9rem; + font-weight: 700; + color: rgba(255,255,255,0.7); + transition: color var(--t-fast); +} + +.ping-test-value.good { color: var(--c-green); } +.ping-test-value.warn { color: var(--c-amber); } +.ping-test-value.bad { color: var(--c-red); } + +.ping-btn { display: flex; align-items: center; gap: 6px; - padding: 5px 12px; - border-radius: var(--r-full); - background: var(--c-card); - border: 1px solid var(--c-card-border); - font-size: 0.7rem; - color: var(--c-txt-2); - font-family: 'JetBrains Mono', monospace; - backdrop-filter: blur(8px); - transition: all var(--t-med); - max-width: 200px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; + background: var(--c-accent-dim); + border: none; + padding: 8px 12px; + border-radius: 12px; + color: var(--c-accent); + font-size: 0.8rem; + font-weight: 700; + transition: all var(--t-fast); } -.server-badge.hidden { opacity: 0; pointer-events: none; transform: translateY(4px); } +.ping-btn:hover { + background: rgba(108,114,255,0.2); + transform: scale(1.03); +} +.ping-btn:active { transform: scale(0.96); } /* ── Metrics bar ──────────────────────────────────────────────────────────── */ .metrics-bar { display: flex; align-items: center; gap: 0; - padding: 12px 4px 16px; + padding: 24px 20px; flex-shrink: 0; - background: rgba(255,255,255,0.02); - border-top: 1px solid var(--c-card-border); - border-radius: var(--r-lg) var(--r-lg) 0 0; + background: rgba(255,255,255,0.04); + border-top: 1px solid rgba(255,255,255,0.08); margin: 0 -18px; padding-inline: 18px; } @@ -419,27 +472,27 @@ input, textarea { font-family: inherit; } flex: 1; display: flex; align-items: center; - gap: 8px; + justify-content: center; + gap: 12px; } .metric-sep { width: 1px; - height: 28px; - background: var(--c-card-border); + height: 40px; + background: rgba(255,255,255,0.15); flex-shrink: 0; margin: 0 4px; } .metric-icon { - width: 26px; height: 26px; - border-radius: var(--r-xs); + width: 36px; height: 36px; + border-radius: 10px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; } .down-icon { background: var(--c-green-dim); color: var(--c-green); } .up-icon { background: var(--c-accent-dim); color: var(--c-accent); } -.ping-icon { background: rgba(245,158,11,0.10); color: var(--c-amber); } .metric-body { display: flex; @@ -449,16 +502,16 @@ input, textarea { font-family: inherit; } } .metric-label { - font-size: 0.58rem; - color: var(--c-txt-3); + font-size: 0.75rem; + color: rgba(255,255,255,0.54); text-transform: uppercase; - letter-spacing: 0.6px; - font-weight: 600; + letter-spacing: 0.8px; + font-weight: 700; } .metric-value { - font-size: 0.76rem; - font-weight: 600; + font-size: 1rem; + font-weight: 700; font-variant-numeric: tabular-nums; color: var(--c-txt-1); font-family: 'JetBrains Mono', monospace; @@ -694,29 +747,7 @@ input, textarea { font-family: inherit; } text-transform: none; } -/* ── Save button ──────────────────────────────────────────────────────────── */ -.save-btn { - width: 100%; - height: 44px; - border-radius: var(--r-md); - border: 1px solid var(--c-accent); - background: var(--c-accent-dim); - color: var(--c-accent); - font-size: 0.82rem; - font-weight: 600; - letter-spacing: 0.3px; - transition: all var(--t-fast); - flex-shrink: 0; - backdrop-filter: blur(8px); -} -.save-btn:hover { - background: var(--c-accent); - color: #fff; - box-shadow: 0 4px 20px var(--c-accent-glow); -} - -.save-btn:active { transform: scale(0.98); } /* ── Toast ────────────────────────────────────────────────────────────────── */ .toast { diff --git a/ostp-jni/Cargo.toml b/ostp-jni/Cargo.toml index 63b6d8d..bb1e0a3 100644 --- a/ostp-jni/Cargo.toml +++ b/ostp-jni/Cargo.toml @@ -18,3 +18,5 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" lazy_static = "1.4" portable-atomic = { workspace = true } +tracing-subscriber = "0.3.23" +tracing.workspace = true diff --git a/ostp-jni/OstpClientSdk.kt b/ostp-jni/OstpClientSdk.kt index 55e0ea4..4d8d7e3 100644 --- a/ostp-jni/OstpClientSdk.kt +++ b/ostp-jni/OstpClientSdk.kt @@ -32,7 +32,7 @@ class OstpClientSdk private constructor(private val context: Context) { // ── Native JNI bindings ─────────────────────────────────────────────────── - private external fun nativeStartClient(configJson: String, fd: Int): Boolean + private external fun nativeStartClient(configJson: String): Boolean private external fun nativeStopClient(): Boolean private external fun nativeGetMetrics(): String private external fun nativeGetLogs(): String @@ -165,7 +165,7 @@ class OstpClientSdk private constructor(private val context: Context) { * * @return `true` if the native layer accepted the start command. */ - fun start(config: Config, fd: Int = -1): Boolean { + fun start(config: Config): Boolean { if (started.getAndSet(true)) { emitLog("SDK already started; call stop() first to change config") return false @@ -175,7 +175,7 @@ class OstpClientSdk private constructor(private val context: Context) { _state.value = TunnelState.Connecting val json = config.toNativeJson() - val ok = nativeStartClient(json, fd) + val ok = nativeStartClient(json) if (!ok) { _state.value = TunnelState.Failed("Native layer rejected config") started.set(false) diff --git a/ostp-jni/src/lib.rs b/ostp-jni/src/lib.rs index 58ccbbc..e4021ef 100644 --- a/ostp-jni/src/lib.rs +++ b/ostp-jni/src/lib.rs @@ -8,12 +8,49 @@ use tokio::runtime::Runtime; use tokio::sync::{mpsc, watch}; use ostp_client::bridge::{Bridge, BridgeMetrics}; use ostp_client::config::ClientConfig; +use ostp_client::tunnel; use ostp_client::app::{BridgeCommand, UiEvent}; +use std::io::Write; + +struct JniLogWriter; + +impl Write for JniLogWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let s = String::from_utf8_lossy(buf).trim().to_string(); + if !s.is_empty() { + add_log(s); + } + Ok(buf.len()) + } + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for JniLogWriter { + type Writer = JniLogWriter; + fn make_writer(&'a self) -> Self::Writer { + JniLogWriter + } +} + +static TRACING_INIT: std::sync::Once = std::sync::Once::new(); + +fn init_tracing() { + TRACING_INIT.call_once(|| { + let subscriber = tracing_subscriber::fmt() + .with_writer(JniLogWriter) + .with_ansi(false) + .finish(); + let _ = tracing::subscriber::set_global_default(subscriber); + }); +} struct SdkState { runtime: Option, shutdown_tx: Option>, metrics: Option>, + tun_child: Option, cmd_tx: Option>, } @@ -22,6 +59,7 @@ lazy_static! { runtime: None, shutdown_tx: None, metrics: None, + tun_child: None, cmd_tx: None, }); static ref LOGS: Mutex> = Mutex::new(VecDeque::new()); @@ -39,11 +77,13 @@ fn add_log(text: String) { } #[no_mangle] -pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeStartClient( +pub extern "system" fn Java_net_ostp_client_OstpClientSdk_startClient( mut env: JNIEnv, _class: JClass, config_json: JString, fd: jni::sys::jint, + t2s_bin_path: JString, + local_proxy: JString, ) -> jboolean { let mut state = match STATE.lock() { Ok(s) => s, @@ -55,6 +95,8 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeStartClient( return jni::sys::JNI_TRUE; } + init_tracing(); + if let Ok(jvm) = env.get_java_vm() { if let Ok(mut guard) = JVM.lock() { *guard = Some(jvm); @@ -100,6 +142,16 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeStartClient( Err(_) => return jni::sys::JNI_FALSE, }; + let t2s_path: String = match env.get_string(&t2s_bin_path) { + Ok(s) => s.into(), + Err(_) => return jni::sys::JNI_FALSE, + }; + + let proxy_addr: String = match env.get_string(&local_proxy) { + Ok(s) => s.into(), + Err(_) => return jni::sys::JNI_FALSE, + }; + // Parse config from JSON let config: ClientConfig = match serde_json::from_str(&config_str) { Ok(cfg) => cfg, @@ -109,6 +161,8 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeStartClient( } }; + let debug = config.debug; + // Create tokio runtime let rt = match Runtime::new() { Ok(r) => r, @@ -139,6 +193,7 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeStartClient( let (ui_tx, mut ui_rx) = mpsc::channel(512); let (cmd_tx, cmd_rx) = mpsc::channel(128); let (shutdown_tx, shutdown_rx) = watch::channel(false); + let proxy_shutdown_rx = shutdown_tx.subscribe(); let metrics_clone = Arc::clone(&metrics); @@ -147,51 +202,19 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeStartClient( bridge.run(ui_tx, cmd_rx, shutdown_rx, proxy_events_rx, client_msgs_tx).await }); - if config.mode == "tun" { - if fd < 0 { - add_log("Error: TUN mode requested but invalid file descriptor provided".to_string()); - return jni::sys::JNI_FALSE; - } - - let tun_dev = match ostp_client::tunnel::create_tun_device_from_fd(fd, config.ostp.mtu) { - Ok(d) => d, - Err(e) => { - add_log(format!("Failed to wrap TUN fd: {:?}", e)); - return jni::sys::JNI_FALSE; - } - }; - - let stack_shutdown_rx = shutdown_tx.subscribe(); - let proxy_events_tx_clone = proxy_events_tx.clone(); - let mtu = config.ostp.mtu; - rt.spawn(async move { - if let Err(e) = ostp_client::tunnel::run_smoltcp_stack( - tun_dev.packet_rx, - tun_dev.packet_tx, - mtu, - proxy_events_tx_clone, - client_msgs_rx, - stack_shutdown_rx, - ).await { - add_log(format!("smoltcp stack loop failed: {:?}", e)); - } - }); - } else { - let config_proxy = config.clone(); - let proxy_shutdown_rx = shutdown_tx.subscribe(); - rt.spawn(async move { - let _ = ostp_client::tunnel::run_local_proxy( - config_proxy.local_proxy, - config_proxy.ostp, - config_proxy.exclusions, - config_proxy.debug, - proxy_shutdown_rx, - proxy_events_tx, - client_msgs_rx, - ) - .await; - }); - } + let config_proxy = config.clone(); + rt.spawn(async move { + tunnel::run_local_proxy( + config_proxy.local_proxy, + config_proxy.ostp, + config_proxy.exclusions, + config_proxy.debug, + proxy_shutdown_rx, + proxy_events_tx, + client_msgs_rx, + ) + .await + }); // Start logs receiver task rt.spawn(async move { @@ -211,6 +234,90 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeStartClient( let _ = cmd_tx_clone.send(BridgeCommand::ToggleTunnel).await; }); + if config.tun_stack == "system" { + // Spawn tun2socks + let fd_str = format!("fd://{}", fd); + let proxy_str = format!("socks5://{}", proxy_addr); + + if debug { + add_log(format!("Spawning tun2socks: {} -device {} -proxy {}", t2s_path, fd_str, proxy_str)); + } + + let mut cmd = std::process::Command::new(&t2s_path); + cmd.arg("-device") + .arg(&fd_str) + .arg("-proxy") + .arg(&proxy_str); + + if config.ostp.mtu > 0 { + cmd.arg("-mtu").arg(config.ostp.mtu.to_string()); + } + + cmd.stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + let mut child = match cmd.spawn() { + Ok(c) => c, + Err(e) => { + add_log(format!("Failed to spawn tun2socks from Rust: {e}")); + return jni::sys::JNI_FALSE; + } + }; + + let stdout = match child.stdout.take() { + Some(s) => s, + None => { + add_log("Failed to capture tun2socks stdout".to_string()); + return jni::sys::JNI_FALSE; + } + }; + let stderr = match child.stderr.take() { + Some(s) => s, + None => { + add_log("Failed to capture tun2socks stderr".to_string()); + return jni::sys::JNI_FALSE; + } + }; + + // Read stdout + std::thread::spawn(move || { + use std::io::{BufRead, BufReader}; + let reader = BufReader::new(stdout); + for line in reader.lines() { + if let Ok(l) = line { + if debug { + add_log(format!("tun2socks: {}", l)); + } + } + } + }); + + // Read stderr & wait + std::thread::spawn(move || { + use std::io::{BufRead, BufReader}; + let reader = BufReader::new(stderr); + for line in reader.lines() { + if let Ok(l) = line { + if debug { + add_log(format!("tun2socks ERROR: {}", l)); + } + } + } + }); + state.tun_child = Some(child); + } else { + if debug { + add_log("Using OSTP native TUN stack. Bypassing tun2socks.".to_string()); + } + let shutdown_rx_clone = shutdown_tx.subscribe(); + let config_clone = config.clone(); + rt.spawn(async move { + if let Err(e) = tunnel::native_handler::run_native_tunnel_from_fd(config_clone, shutdown_rx_clone, fd).await { + add_log(format!("Native TUN exited with error: {}", e)); + } + }); + } + state.runtime = Some(rt); state.shutdown_tx = Some(shutdown_tx); state.metrics = Some(metrics_clone); @@ -221,31 +328,42 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeStartClient( } #[no_mangle] -pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeStopClient( +pub extern "system" fn Java_net_ostp_client_OstpClientSdk_stopClient( _env: JNIEnv, _class: JClass, ) -> jboolean { - let mut state = match STATE.lock() { - Ok(s) => s, - Err(_) => return jni::sys::JNI_FALSE, + let (tun_child, shutdown_tx, runtime) = { + let mut state = match STATE.lock() { + Ok(s) => s, + Err(_) => return jni::sys::JNI_FALSE, + }; + let c = state.tun_child.take(); + let s = state.shutdown_tx.take(); + let r = state.runtime.take(); + state.cmd_tx = None; + state.metrics = None; + (c, s, r) }; - if let Some(shutdown_tx) = state.shutdown_tx.take() { - let _ = shutdown_tx.send(true); + if let Some(mut child) = tun_child { + let _ = child.kill(); + add_log("Killed tun2socks process".to_string()); } - if let Some(rt) = state.runtime.take() { + if let Some(s) = shutdown_tx { + let _ = s.send(true); + } + + if let Some(rt) = runtime { rt.shutdown_timeout(std::time::Duration::from_secs(3)); } - state.cmd_tx = None; - state.metrics = None; add_log("OSTP SDK: Client successfully stopped".to_string()); jni::sys::JNI_TRUE } #[no_mangle] -pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeGetMetrics( +pub extern "system" fn Java_net_ostp_client_OstpClientSdk_getMetrics( env: JNIEnv, _class: JClass, ) -> jstring { @@ -266,7 +384,7 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeGetMetrics( r#"{{"bytes_sent": {}, "bytes_recv": {}, "connection_state": {}, "rtt_ms": {}}}"#, sent, recv, conn_state, rtt ); - match env.new_string(json) { + match env.new_string(json.replace('\0', "")) { Ok(s) => s.into_raw(), Err(_) => std::ptr::null_mut(), } @@ -279,7 +397,7 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeGetMetrics( } #[no_mangle] -pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeGetLogs( +pub extern "system" fn Java_net_ostp_client_OstpClientSdk_getLogs( env: JNIEnv, _class: JClass, ) -> jstring { @@ -293,7 +411,7 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeGetLogs( Err(_) => "[]".to_string(), }; - match env.new_string(json) { + match env.new_string(json.replace('\0', "")) { Ok(s) => s.into_raw(), Err(_) => std::ptr::null_mut(), } diff --git a/ostp-server/src/api.rs b/ostp-server/src/api.rs index c71d175..9b45989 100644 --- a/ostp-server/src/api.rs +++ b/ostp-server/src/api.rs @@ -20,9 +20,10 @@ use portable_atomic::AtomicU64; use std::time::Instant; use axum::{ + body::Body, extract::{Path, State}, - http::{header, StatusCode, Uri}, - response::IntoResponse, + http::{header, Request, StatusCode, Uri}, + response::{IntoResponse, Response}, routing::{get, post, put}, Json, Router, }; diff --git a/ostp-server/src/lib.rs b/ostp-server/src/lib.rs index 9a3f69e..f0cc051 100644 --- a/ostp-server/src/lib.rs +++ b/ostp-server/src/lib.rs @@ -65,6 +65,7 @@ pub(crate) struct RemoteState { pub async fn run_server( bind_addrs: Vec, + server_public_ip: Option, access_keys: Vec<(String, crate::api::UserMeta)>, outbound: Option, api_config: Option, @@ -248,7 +249,7 @@ pub async fn run_server( let primary = bind_addrs.first().cloned().unwrap_or_else(|| "0.0.0.0:50000".to_string()); let parts: Vec<&str> = primary.rsplitn(2, ':').collect(); let server_port: u16 = parts.first().and_then(|p| p.parse().ok()).unwrap_or(50000); - let server_host = parts.get(1).unwrap_or(&"0.0.0.0").to_string(); + let server_host = server_public_ip.unwrap_or_else(|| parts.get(1).unwrap_or(&"0.0.0.0").to_string()); let rq = reality_query.clone().unwrap_or_default(); let config_path_api = config_path.clone(); tokio::spawn(async move { diff --git a/ostp/src/main.rs b/ostp/src/main.rs index ef6bf38..8fa1b19 100644 --- a/ostp/src/main.rs +++ b/ostp/src/main.rs @@ -894,8 +894,9 @@ async fn run_app() -> Result<()> { limit_bytes: uc.limit(), }) }).collect::>(); + let host = get_or_ask_public_ip(&args.config); // Pass all listen addresses for multi-listener support - ostp_server::run_server(listen_addrs, access_keys_meta, outbound, api_config, fallback_config, debug, rq, rc, Some(args.config)).await?; + ostp_server::run_server(listen_addrs, Some(host), access_keys_meta, outbound, api_config, fallback_config, debug, rq, rc, Some(args.config)).await?; } AppMode::Client(client_cfg) => { run_client_directly(client_cfg).await?; @@ -1008,6 +1009,7 @@ async fn run_client_directly(client_cfg: ClientConfig) -> Result<()> { let reality_cfg = client_cfg.reality.as_ref(); let client_conf = ostp_client::config::ClientConfig { mode: if is_tun_enabled { "tun".to_string() } else { "proxy".to_string() }, + tun_stack: "native".to_string(), debug: client_cfg.debug.unwrap_or(false), ostp: ostp_client::config::OstpConfig { server_addr: client_cfg.server.clone(),