diff --git a/Cargo.lock b/Cargo.lock index ed9e6f9..717a88d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,6 +181,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.11.1" @@ -211,12 +217,38 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" 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" @@ -324,7 +356,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -407,7 +439,48 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.1.0", +] + +[[package]] +name = "defmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", ] [[package]] @@ -438,7 +511,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -507,9 +580,15 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + [[package]] name = "futures-task" version = "0.3.32" @@ -589,6 +668,15 @@ dependencies = [ "polyval", ] +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -604,6 +692,16 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.5.0" @@ -736,7 +834,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.62.2", ] [[package]] @@ -878,6 +976,12 @@ 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" @@ -937,7 +1041,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -976,6 +1080,16 @@ version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "litemap" version = "0.8.2" @@ -994,6 +1108,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + [[package]] name = "matchers" version = "0.2.0" @@ -1123,11 +1243,14 @@ dependencies = [ "serde", "serde_json", "sha2", + "smoltcp", "socket2", "tokio", "tokio-rustls", "tracing", + "tun", "webpki-roots 0.26.11", + "wintun 0.4.0", ] [[package]] @@ -1287,7 +1410,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -1536,7 +1681,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn", + "syn 2.0.117", "walkdir", ] @@ -1655,7 +1800,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1742,6 +1887,20 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "smoltcp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a1a996951e50b5971a2c8c0fa05a381480d70a933064245c4a223ddc87ccc97" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "cfg-if", + "defmt 0.3.100", + "heapless", + "managed", +] + [[package]] name = "snow" version = "0.9.6" @@ -1786,6 +1945,17 @@ 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" @@ -1814,7 +1984,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1843,7 +2013,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1854,7 +2024,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1934,7 +2104,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1947,6 +2117,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.5.11" @@ -1978,7 +2161,7 @@ version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" dependencies = [ - "bitflags", + "bitflags 2.11.1", "bytes", "futures-util", "http", @@ -2022,7 +2205,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2070,6 +2253,24 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tun" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0adb9992bbd5ca76f3847ed579ad4ee8defb2ec2eea918cceef17ccc66fa4fd4" +dependencies = [ + "byteorder", + "bytes", + "futures-core", + "ioctl-sys", + "libc", + "log", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "wintun 0.3.2", +] + [[package]] name = "typenum" version = "1.19.0" @@ -2243,7 +2444,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -2284,7 +2485,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", @@ -2337,6 +2538,44 @@ 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", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -2358,7 +2597,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2369,7 +2608,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2438,6 +2677,21 @@ 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" @@ -2460,6 +2714,12 @@ 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" @@ -2472,6 +2732,12 @@ 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" @@ -2484,6 +2750,12 @@ 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" @@ -2502,6 +2774,12 @@ 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" @@ -2514,6 +2792,12 @@ 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" @@ -2526,6 +2810,12 @@ 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" @@ -2538,6 +2828,12 @@ 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" @@ -2553,6 +2849,32 @@ dependencies = [ "toml", ] +[[package]] +name = "wintun" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29b83b0eca06dd125dbcd48a45327c708a6da8aada3d95a3f06db0ce4b17e0d4" +dependencies = [ + "c2rust-bitfields", + "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", +] + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -2589,7 +2911,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -2605,7 +2927,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -2617,7 +2939,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.11.1", "indexmap", "log", "serde", @@ -2681,7 +3003,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -2702,7 +3024,7 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2722,7 +3044,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -2762,7 +3084,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/ostp-client/Cargo.toml b/ostp-client/Cargo.toml index ef029f3..a52ac08 100644 --- a/ostp-client/Cargo.toml +++ b/ostp-client/Cargo.toml @@ -25,3 +25,11 @@ 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"] } + diff --git a/ostp-client/src/runner.rs b/ostp-client/src/runner.rs index bf48eda..b3507b2 100644 --- a/ostp-client/src/runner.rs +++ b/ostp-client/src/runner.rs @@ -198,7 +198,6 @@ 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 @@ -248,26 +247,29 @@ pub async fn run_client_core( }); let config_clone = config.clone(); - 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 - })) + 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)) } else { - None + (None, None) }; // Wait for either external shutdown OR any task to fail @@ -280,7 +282,9 @@ pub async fn run_client_core( let _ = shutdown_tx.send(true); res.map_err(|e| anyhow::anyhow!("Bridge task panicked: {}", e))??; } - res = &mut proxy_task => { + res = async { + if let Some(ref mut t) = proxy_task { t.await } else { std::future::pending().await } + } => { let _ = shutdown_tx.send(true); res.map_err(|e| anyhow::anyhow!("Proxy task panicked: {}", e))??; } @@ -294,7 +298,9 @@ pub async fn run_client_core( // Final cleanup: wait for tasks to finish let _ = bridge_task.await; - let _ = proxy_task.await; + if let Some(task) = proxy_task { + let _ = 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 db9714c..032ada5 100644 --- a/ostp-client/src/tunnel/linux_handler.rs +++ b/ostp-client/src/tunnel/linux_handler.rs @@ -1,27 +1,29 @@ use anyhow::{anyhow, Result}; -use tokio::sync::watch; +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; #[cfg(target_os = "linux")] use std::net::ToSocketAddrs; #[cfg(target_os = "linux")] -use std::process::{Command, Stdio, Child}; -#[cfg(target_os = "linux")] -use std::io::{BufRead, BufReader}; +use std::process::Command; #[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; \ @@ -39,39 +41,13 @@ 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<()> { - let debug = config.debug; - if debug { - println!("[ostp] Initializing TUN tunnel..."); - } + info!("Initializing built-in Linux TUN tunnel..."); - 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 + // 1. Pre-flight system checks let is_root = Command::new("id") .arg("-u") .output() @@ -100,10 +76,7 @@ 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(); - - if debug { - println!("[ostp] Resolved server IP: {}", server_ip_str); - } + info!("Resolved server IP: {}", server_ip_str); // 3. Detect current default gateway and interface let route_output = Command::new("sh") @@ -114,7 +87,6 @@ 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(); @@ -131,95 +103,62 @@ pub async fn run_linux_tunnel( return Err(anyhow!("Failed to discover active default gateway or network interface on Linux system.")); } - if debug { - println!("[ostp] Default route: gateway={} interface={}", default_gw, default_if); - } + info!("Default route: gateway={} interface={}", default_gw, default_if); - // 4. Setup commands (Using standard /1 routing trick for fail-proof overriding) - let setup_script = format!( - "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", - 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() && debug { - println!("[ostp] Warning: Setup routing returned: {}", String::from_utf8_lossy(&out.stderr)); - } - - // 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); - } - - 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))?; + // 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(), - child: None, }; - println!("[ostp] TUN tunnel active. All traffic is routed through OSTP."); + // 4. Setup routing rules + let setup_script = format!( + "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, + default_gw, default_if + ); - 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); - } - }); + 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)); } - _guard.child = Some(child); + info!("TUN tunnel active. Direct in-process packets handling started."); - // 6. Wait for shutdown signal + // 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); + } + }); + + // 5. Wait for shutdown signal let _ = shutdown.changed().await; - println!("[ostp] Deactivating TUN tunnel..."); - - // Drop guard runs cleanup automatically + info!("Deactivating TUN tunnel..."); drop(_guard); - println!("[ostp] TUN tunnel stopped."); - + // Terminate smoltcp stack + let _ = stack_handle.await; + + info!("TUN tunnel stopped."); Ok(()) } @@ -227,6 +166,8 @@ 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 adfb8bc..3b98c5e 100644 --- a/ostp-client/src/tunnel/mod.rs +++ b/ostp-client/src/tunnel/mod.rs @@ -1,25 +1,31 @@ mod proxy; mod wintun_handler; mod linux_handler; +mod tun_device; +mod smoltcp_stack; 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<()> { #[cfg(target_os = "windows")] { - wintun_handler::run_wintun_tunnel(config, shutdown).await + wintun_handler::run_wintun_tunnel(config, proxy_events_tx, client_msgs_rx, shutdown).await } #[cfg(target_os = "linux")] { - linux_handler::run_linux_tunnel(config, shutdown).await + linux_handler::run_linux_tunnel(config, proxy_events_tx, client_msgs_rx, 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."); } } @@ -66,4 +72,7 @@ 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/smoltcp_stack.rs b/ostp-client/src/tunnel/smoltcp_stack.rs new file mode 100644 index 0000000..7cf97ca --- /dev/null +++ b/ostp-client/src/tunnel/smoltcp_stack.rs @@ -0,0 +1,272 @@ +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 new file mode 100644 index 0000000..6424d1f --- /dev/null +++ b/ostp-client/src/tunnel/tun_device.rs @@ -0,0 +1,216 @@ +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 9e5f7dc..5423eae 100644 --- a/ostp-client/src/tunnel/wintun_handler.rs +++ b/ostp-client/src/tunnel/wintun_handler.rs @@ -1,29 +1,36 @@ use anyhow::{anyhow, Result}; -use tokio::sync::watch; +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; #[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, Stdio, Child}; - use std::os::windows::process::CommandExt; + use std::process::Command; 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\ @@ -39,27 +46,9 @@ pub async fn run_wintun_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() - )); - } + info!("Initializing built-in Wintun TUN tunnel..."); // 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!( @@ -68,7 +57,6 @@ 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 @@ -79,7 +67,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(); - tracing::info!("Resolved server IP: {}", server_ip_str); + 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(); @@ -106,31 +94,14 @@ pub async fn run_wintun_tunnel( server_ip_str, current_exe ); - // 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))?; + // Create the TunDevice inside the client process + let tun_dev = create_tun_device(TUN_NAME, config.ostp.mtu)?; let mut _guard = WintunGuard { server_ip_str: server_ip_str.clone(), - child: None, }; - // Run route setup in parallel while tun2socks creates the adapter. - // Also poll for the adapter to appear (typically <1s). + // Run route setup in parallel let route_handle = { let script = setup_script.clone(); tokio::task::spawn_blocking(move || { @@ -141,7 +112,7 @@ pub async fn run_wintun_tunnel( }) }; - // 5. Wait for TUN adapter to appear (poll with timeout instead of fixed 2s sleep) + // 5. Wait for TUN adapter to appear 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 { @@ -153,9 +124,6 @@ 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; @@ -167,11 +135,11 @@ pub async fn run_wintun_tunnel( tracing::warn!("WARNING: TUN adapter did not appear within timeout. Proceeding anyway."); } - // Wait for route setup to finish (should already be done by now) + // Wait for route setup to finish let _ = route_handle.await; - // 6. Configure the adapter (IP, metric, MTU, DNS) - tracing::info!("Applying network configuration..."); + // 6. Configure the adapter + 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\ @@ -181,7 +149,7 @@ pub async fn run_wintun_tunnel( if let Some(ref dns) = config.dns_server { if !dns.is_empty() { - tracing::info!("DNS server: {}", dns); + info!("DNS server: {}", dns); net_setup.push_str(&format!( "netsh interface ipv4 set dnsservers name=\"{TUN_NAME}\" static {} primary\n", dns )); @@ -193,41 +161,33 @@ pub async fn run_wintun_tunnel( .args(["-NoProfile", "-Command", &net_setup]) .output()?; - tracing::info!("TUN tunnel active. All traffic is routed through OSTP."); + info!("TUN tunnel active. Direct in-process packets handling started."); - // 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); - } - } - }); - } + // 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); + } + }); // 8. Wait for shutdown signal let _ = shutdown.changed().await; - tracing::info!("Deactivating TUN tunnel..."); + info!("Deactivating TUN tunnel..."); drop(_guard); - tracing::info!("TUN tunnel stopped."); + // Terminate smoltcp stack + let _ = stack_handle.await; + + info!("TUN tunnel stopped."); Ok(()) } @@ -235,6 +195,8 @@ 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-jni/OstpClientSdk.kt b/ostp-jni/OstpClientSdk.kt index 4d8d7e3..55e0ea4 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): Boolean + private external fun nativeStartClient(configJson: String, fd: Int): 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): Boolean { + fun start(config: Config, fd: Int = -1): 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) + val ok = nativeStartClient(json, fd) 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 70f94a3..58ccbbc 100644 --- a/ostp-jni/src/lib.rs +++ b/ostp-jni/src/lib.rs @@ -8,14 +8,12 @@ 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}; struct SdkState { runtime: Option, shutdown_tx: Option>, metrics: Option>, - tun_child: Option, cmd_tx: Option>, } @@ -24,7 +22,6 @@ lazy_static! { runtime: None, shutdown_tx: None, metrics: None, - tun_child: None, cmd_tx: None, }); static ref LOGS: Mutex> = Mutex::new(VecDeque::new()); @@ -42,13 +39,11 @@ fn add_log(text: String) { } #[no_mangle] -pub extern "system" fn Java_net_ostp_client_OstpClientSdk_startClient( +pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeStartClient( 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, @@ -105,16 +100,6 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_startClient( 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, @@ -124,8 +109,6 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_startClient( } }; - let debug = config.debug; - // Create tokio runtime let rt = match Runtime::new() { Ok(r) => r, @@ -156,7 +139,6 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_startClient( 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); @@ -165,19 +147,51 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_startClient( bridge.run(ui_tx, cmd_rx, shutdown_rx, proxy_events_rx, client_msgs_tx).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 - }); + 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; + }); + } // Start logs receiver task rt.spawn(async move { @@ -197,68 +211,9 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_startClient( let _ = cmd_tx_clone.send(BridgeCommand::ToggleTunnel).await; }); - // 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 = child.stdout.take().unwrap(); - let stderr = child.stderr.take().unwrap(); - - // 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.runtime = Some(rt); state.shutdown_tx = Some(shutdown_tx); state.metrics = Some(metrics_clone); - state.tun_child = Some(child); state.cmd_tx = Some(cmd_tx); add_log("OSTP SDK: Client successfully started".to_string()); @@ -266,7 +221,7 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_startClient( } #[no_mangle] -pub extern "system" fn Java_net_ostp_client_OstpClientSdk_stopClient( +pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeStopClient( _env: JNIEnv, _class: JClass, ) -> jboolean { @@ -275,11 +230,6 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_stopClient( Err(_) => return jni::sys::JNI_FALSE, }; - if let Some(mut child) = state.tun_child.take() { - let _ = child.kill(); - add_log("Killed tun2socks process".to_string()); - } - if let Some(shutdown_tx) = state.shutdown_tx.take() { let _ = shutdown_tx.send(true); } @@ -295,7 +245,7 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_stopClient( } #[no_mangle] -pub extern "system" fn Java_net_ostp_client_OstpClientSdk_getMetrics( +pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeGetMetrics( env: JNIEnv, _class: JClass, ) -> jstring { @@ -329,7 +279,7 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_getMetrics( } #[no_mangle] -pub extern "system" fn Java_net_ostp_client_OstpClientSdk_getLogs( +pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeGetLogs( env: JNIEnv, _class: JClass, ) -> jstring { diff --git a/ostp-server/src/api.rs b/ostp-server/src/api.rs index 9b45989..c71d175 100644 --- a/ostp-server/src/api.rs +++ b/ostp-server/src/api.rs @@ -20,10 +20,9 @@ use portable_atomic::AtomicU64; use std::time::Instant; use axum::{ - body::Body, extract::{Path, State}, - http::{header, Request, StatusCode, Uri}, - response::{IntoResponse, Response}, + http::{header, StatusCode, Uri}, + response::IntoResponse, routing::{get, post, put}, Json, Router, };