feat: migrate TUN tunnel to native in-process smoltcp and refactor Android JNI layer

This commit is contained in:
ospab 2026-05-27 00:17:19 +03:00
parent 5722aaf2bc
commit 8ab49b84e9
11 changed files with 1041 additions and 356 deletions

374
Cargo.lock generated
View File

@ -181,6 +181,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.11.1" version = "2.11.1"
@ -211,12 +217,38 @@ version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.11.1" version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" 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]] [[package]]
name = "cc" name = "cc"
version = "1.2.62" version = "1.2.62"
@ -324,7 +356,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
] ]
[[package]] [[package]]
@ -407,7 +439,48 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "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]] [[package]]
@ -438,7 +511,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
] ]
[[package]] [[package]]
@ -507,9 +580,15 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "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]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.32" version = "0.3.32"
@ -589,6 +668,15 @@ dependencies = [
"polyval", "polyval",
] ]
[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.5" version = "0.15.5"
@ -604,6 +692,16 @@ version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" 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]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@ -736,7 +834,7 @@ dependencies = [
"js-sys", "js-sys",
"log", "log",
"wasm-bindgen", "wasm-bindgen",
"windows-core", "windows-core 0.62.2",
] ]
[[package]] [[package]]
@ -878,6 +976,12 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "ioctl-sys"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bd11f3a29434026f5ff98c730b668ba74b1033637b8817940b54d040696133c"
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.12.0" version = "2.12.0"
@ -937,7 +1041,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn 2.0.117",
] ]
[[package]] [[package]]
@ -976,6 +1080,16 @@ version = "0.2.185"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" 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]] [[package]]
name = "litemap" name = "litemap"
version = "0.8.2" version = "0.8.2"
@ -994,6 +1108,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "managed"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
[[package]] [[package]]
name = "matchers" name = "matchers"
version = "0.2.0" version = "0.2.0"
@ -1123,11 +1243,14 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
"smoltcp",
"socket2", "socket2",
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
"tracing", "tracing",
"tun",
"webpki-roots 0.26.11", "webpki-roots 0.26.11",
"wintun 0.4.0",
] ]
[[package]] [[package]]
@ -1287,7 +1410,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [ dependencies = [
"proc-macro2", "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]] [[package]]
@ -1536,7 +1681,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rust-embed-utils", "rust-embed-utils",
"syn", "syn 2.0.117",
"walkdir", "walkdir",
] ]
@ -1655,7 +1800,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
] ]
[[package]] [[package]]
@ -1742,6 +1887,20 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 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]] [[package]]
name = "snow" name = "snow"
version = "0.9.6" version = "0.9.6"
@ -1786,6 +1945,17 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 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]] [[package]]
name = "syn" name = "syn"
version = "2.0.117" version = "2.0.117"
@ -1814,7 +1984,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
] ]
[[package]] [[package]]
@ -1843,7 +2013,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
] ]
[[package]] [[package]]
@ -1854,7 +2024,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
] ]
[[package]] [[package]]
@ -1934,7 +2104,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
] ]
[[package]] [[package]]
@ -1947,6 +2117,19 @@ dependencies = [
"tokio", "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]] [[package]]
name = "toml" name = "toml"
version = "0.5.11" version = "0.5.11"
@ -1978,7 +2161,7 @@ version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.11.1",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http",
@ -2022,7 +2205,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
] ]
[[package]] [[package]]
@ -2070,6 +2253,24 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 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]] [[package]]
name = "typenum" name = "typenum"
version = "1.19.0" version = "1.19.0"
@ -2243,7 +2444,7 @@ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -2284,7 +2485,7 @@ version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.11.1",
"hashbrown 0.15.5", "hashbrown 0.15.5",
"indexmap", "indexmap",
"semver", "semver",
@ -2337,6 +2538,44 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "windows-core" name = "windows-core"
version = "0.62.2" version = "0.62.2"
@ -2358,7 +2597,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
] ]
[[package]] [[package]]
@ -2369,7 +2608,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
] ]
[[package]] [[package]]
@ -2438,6 +2677,21 @@ dependencies = [
"windows_x86_64_msvc 0.42.2", "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]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.6" version = "0.52.6"
@ -2460,6 +2714,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.52.6" version = "0.52.6"
@ -2472,6 +2732,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.6" version = "0.52.6"
@ -2484,6 +2750,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.6" version = "0.52.6"
@ -2502,6 +2774,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.6" version = "0.52.6"
@ -2514,6 +2792,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 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]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.6" version = "0.52.6"
@ -2526,6 +2810,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 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]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.6" version = "0.52.6"
@ -2538,6 +2828,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 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]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.52.6"
@ -2553,6 +2849,32 @@ dependencies = [
"toml", "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]] [[package]]
name = "wit-bindgen" name = "wit-bindgen"
version = "0.51.0" version = "0.51.0"
@ -2589,7 +2911,7 @@ dependencies = [
"heck", "heck",
"indexmap", "indexmap",
"prettyplease", "prettyplease",
"syn", "syn 2.0.117",
"wasm-metadata", "wasm-metadata",
"wit-bindgen-core", "wit-bindgen-core",
"wit-component", "wit-component",
@ -2605,7 +2927,7 @@ dependencies = [
"prettyplease", "prettyplease",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
"wit-bindgen-core", "wit-bindgen-core",
"wit-bindgen-rust", "wit-bindgen-rust",
] ]
@ -2617,7 +2939,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags", "bitflags 2.11.1",
"indexmap", "indexmap",
"log", "log",
"serde", "serde",
@ -2681,7 +3003,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
"synstructure", "synstructure",
] ]
@ -2702,7 +3024,7 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
] ]
[[package]] [[package]]
@ -2722,7 +3044,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
"synstructure", "synstructure",
] ]
@ -2762,7 +3084,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.117",
] ]
[[package]] [[package]]

View File

@ -25,3 +25,11 @@ sha2 = "0.10.8"
base64 = "0.22.1" base64 = "0.22.1"
webpki-roots = "0.26" webpki-roots = "0.26"
rustls-pki-types = "1.7" 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"] }

View File

@ -198,7 +198,6 @@ pub async fn run_client_core(
let (ui_tx, mut ui_rx) = mpsc::channel(512); let (ui_tx, mut ui_rx) = mpsc::channel(512);
let (cmd_tx, cmd_rx) = mpsc::channel(128); let (cmd_tx, cmd_rx) = mpsc::channel(128);
let (shutdown_tx, shutdown_rx) = watch::channel(false); let (shutdown_tx, shutdown_rx) = watch::channel(false);
let proxy_shutdown_rx = shutdown_tx.subscribe();
// Auto-connect on startup // Auto-connect on startup
@ -248,26 +247,29 @@ pub async fn run_client_core(
}); });
let config_clone = config.clone(); let config_clone = config.clone();
let mut proxy_task = tokio::spawn(async move { let (mut proxy_task, mut wintun_task) = if config.mode == "proxy" {
tunnel::run_local_proxy( let proxy_shutdown_rx = shutdown_tx.subscribe();
config.local_proxy, let t = tokio::spawn(async move {
config.ostp, tunnel::run_local_proxy(
config.exclusions, config.local_proxy,
config.debug, config.ostp,
proxy_shutdown_rx, config.exclusions,
proxy_events_tx, config.debug,
client_msgs_rx, proxy_shutdown_rx,
) proxy_events_tx,
.await client_msgs_rx,
}); )
.await
let wintun_shutdown_rx = shutdown_tx.subscribe(); });
let mut wintun_task = if config_clone.mode == "tun" { (Some(t), None)
Some(tokio::spawn(async move { } else if config.mode == "tun" {
tunnel::run_tun_tunnel(config_clone, wintun_shutdown_rx).await 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 { } else {
None (None, None)
}; };
// Wait for either external shutdown OR any task to fail // 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); let _ = shutdown_tx.send(true);
res.map_err(|e| anyhow::anyhow!("Bridge task panicked: {}", e))??; 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); let _ = shutdown_tx.send(true);
res.map_err(|e| anyhow::anyhow!("Proxy task panicked: {}", e))??; 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 // Final cleanup: wait for tasks to finish
let _ = bridge_task.await; let _ = bridge_task.await;
let _ = proxy_task.await; if let Some(task) = proxy_task {
let _ = task.await;
}
if let Some(task) = wintun_task { if let Some(task) = wintun_task {
let _ = task.await; let _ = task.await;
} }

View File

@ -1,27 +1,29 @@
use anyhow::{anyhow, Result}; 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")] #[cfg(target_os = "linux")]
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use std::process::{Command, Stdio, Child}; use std::process::Command;
#[cfg(target_os = "linux")]
use std::io::{BufRead, BufReader};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
struct LinuxRouteGuard { struct LinuxRouteGuard {
server_ip_str: String, server_ip_str: String,
default_gw: String, default_gw: String,
default_if: String, default_if: String,
child: Option<Child>,
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
impl Drop for LinuxRouteGuard { impl Drop for LinuxRouteGuard {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(mut child) = self.child.take() {
let _ = child.kill();
}
let cleanup_script = format!( let cleanup_script = format!(
"ip route del 0.0.0.0/1 dev ostp_tun || true; \ "ip route del 0.0.0.0/1 dev ostp_tun || true; \
ip route del 128.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")] #[cfg(target_os = "linux")]
pub async fn run_linux_tunnel( pub async fn run_linux_tunnel(
config: crate::config::ClientConfig, config: crate::config::ClientConfig,
proxy_events_tx: mpsc::Sender<ProxyEvent>,
client_msgs_rx: mpsc::UnboundedReceiver<(u16, ProxyToClientMsg)>,
mut shutdown: watch::Receiver<bool>, mut shutdown: watch::Receiver<bool>,
) -> Result<()> { ) -> Result<()> {
let debug = config.debug; info!("Initializing built-in Linux TUN tunnel...");
if debug {
println!("[ostp] Initializing TUN tunnel...");
}
let exe = std::env::current_exe()?; // 1. Pre-flight system checks
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") let is_root = Command::new("id")
.arg("-u") .arg("-u")
.output() .output()
@ -100,10 +76,7 @@ pub async fn run_linux_tunnel(
.ok_or_else(|| anyhow!("Could not resolve host IP for routing exclusion"))?; .ok_or_else(|| anyhow!("Could not resolve host IP for routing exclusion"))?;
let server_ip_str = server_ip.to_string(); 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 // 3. Detect current default gateway and interface
let route_output = Command::new("sh") 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 route_str = String::from_utf8_lossy(&route_output.stdout);
let parts: Vec<&str> = route_str.split_whitespace().collect(); 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_gw = String::new();
let mut default_if = 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.")); return Err(anyhow!("Failed to discover active default gateway or network interface on Linux system."));
} }
if debug { info!("Default route: gateway={} interface={}", default_gw, default_if);
println!("[ostp] Default route: gateway={} interface={}", default_gw, default_if);
}
// 4. Setup commands (Using standard /1 routing trick for fail-proof overriding) // Create the TunDevice inside the client process (creates the interface and sets up IP/MTU/Status)
let setup_script = format!( let tun_dev = create_tun_device("ostp_tun", config.ostp.mtu)?;
"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))?;
let mut _guard = LinuxRouteGuard { let mut _guard = LinuxRouteGuard {
server_ip_str: server_ip_str.clone(), server_ip_str: server_ip_str.clone(),
default_gw: default_gw.clone(), default_gw: default_gw.clone(),
default_if: default_if.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 out = Command::new("sh")
let stdout = child.stdout.take().unwrap(); .args(["-c", &setup_script])
let stderr = child.stderr.take().unwrap(); .output()?;
tokio::spawn(async move { if !out.status.success() {
let reader = BufReader::new(stdout); tracing::warn!("Warning: Setup routing returned: {}", String::from_utf8_lossy(&out.stderr));
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); 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; let _ = shutdown.changed().await;
println!("[ostp] Deactivating TUN tunnel..."); info!("Deactivating TUN tunnel...");
// Drop guard runs cleanup automatically
drop(_guard); drop(_guard);
println!("[ostp] TUN tunnel stopped."); // Terminate smoltcp stack
let _ = stack_handle.await;
info!("TUN tunnel stopped.");
Ok(()) Ok(())
} }
@ -227,6 +166,8 @@ pub async fn run_linux_tunnel(
#[allow(dead_code)] #[allow(dead_code)]
pub async fn run_linux_tunnel( pub async fn run_linux_tunnel(
_config: crate::config::ClientConfig, _config: crate::config::ClientConfig,
_proxy_events_tx: mpsc::Sender<ProxyEvent>,
_client_msgs_rx: mpsc::UnboundedReceiver<(u16, ProxyToClientMsg)>,
_shutdown: watch::Receiver<bool>, _shutdown: watch::Receiver<bool>,
) -> Result<()> { ) -> Result<()> {
Err(anyhow!("Linux tunnel driver executed on a non-Linux host!")) Err(anyhow!("Linux tunnel driver executed on a non-Linux host!"))

View File

@ -1,25 +1,31 @@
mod proxy; mod proxy;
mod wintun_handler; mod wintun_handler;
mod linux_handler; mod linux_handler;
mod tun_device;
mod smoltcp_stack;
pub async fn run_tun_tunnel( pub async fn run_tun_tunnel(
config: crate::config::ClientConfig, config: crate::config::ClientConfig,
proxy_events_tx: mpsc::Sender<ProxyEvent>,
client_msgs_rx: mpsc::UnboundedReceiver<(u16, ProxyToClientMsg)>,
shutdown: watch::Receiver<bool>, shutdown: watch::Receiver<bool>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
#[cfg(target_os = "windows")] #[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")] #[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")))] #[cfg(not(any(target_os = "windows", target_os = "linux")))]
{ {
let _ = shutdown; let _ = shutdown;
let _ = config; let _ = config;
let _ = proxy_events_tx;
let _ = client_msgs_rx;
anyhow::bail!("Operating system unsupported, text an issue at github."); 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 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;

View File

@ -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<Vec<u8>>,
tx_sender: mpsc::Sender<Vec<u8>>,
capabilities: DeviceCapabilities,
}
impl ChannelDevice {
fn new(tx_sender: mpsc::Sender<Vec<u8>>, 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<u8>);
impl RxToken for ChannelRxToken {
fn consume<R, F>(mut self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
f(&mut self.0)
}
}
struct ChannelTxToken(mpsc::Sender<Vec<u8>>);
impl TxToken for ChannelTxToken {
fn consume<R, F>(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<Self::TxToken<'_>> {
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<Vec<u8>>,
tun_tx: mpsc::Sender<Vec<u8>>,
mtu: usize,
proxy_events_tx: mpsc::Sender<ProxyEvent>,
mut client_msgs_rx: mpsc::UnboundedReceiver<(u16, ProxyToClientMsg)>,
mut shutdown: watch::Receiver<bool>,
) -> 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<u16, VirtualStream> = HashMap::new();
let mut handle_to_stream_id: HashMap<SocketHandle, u16> = 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::<TcpSocket>(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::<TcpSocket>(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(())
}

View File

@ -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<Vec<u8>>,
pub packet_tx: mpsc::Sender<Vec<u8>>,
_shutdown_tx: mpsc::Sender<()>,
}
#[cfg(target_os = "windows")]
pub fn create_tun_device(tun_name: &str, mtu: usize) -> Result<TunDevice> {
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::<Vec<u8>>(100000);
let (packet_tx, mut packet_rx_out) = mpsc::channel::<Vec<u8>>(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<TunDevice> {
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::<Vec<u8>>(100000);
let (packet_tx, mut packet_rx_out) = mpsc::channel::<Vec<u8>>(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<TunDevice> {
Err(anyhow!("Unsupported operating system for TUN device"))
}
#[cfg(unix)]
pub fn create_tun_device_from_fd(fd: i32, mtu: usize) -> Result<TunDevice> {
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::<Vec<u8>>(100000);
let (packet_tx, mut packet_rx_out) = mpsc::channel::<Vec<u8>>(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<TunDevice> {
Err(anyhow!("Raw fd TUN device is not supported on this operating system"))
}

View File

@ -1,29 +1,36 @@
use anyhow::{anyhow, Result}; 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")] #[cfg(target_os = "windows")]
pub async fn run_wintun_tunnel( pub async fn run_wintun_tunnel(
config: crate::config::ClientConfig, config: crate::config::ClientConfig,
proxy_events_tx: mpsc::Sender<ProxyEvent>,
client_msgs_rx: mpsc::UnboundedReceiver<(u16, ProxyToClientMsg)>,
mut shutdown: watch::Receiver<bool>, mut shutdown: watch::Receiver<bool>,
) -> Result<()> { ) -> Result<()> {
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use std::process::{Command, Stdio, Child}; use std::process::Command;
use std::os::windows::process::CommandExt;
const CREATE_NO_WINDOW: u32 = 0x08000000; const CREATE_NO_WINDOW: u32 = 0x08000000;
const TUN_NAME: &str = "ostp_tun"; const TUN_NAME: &str = "ostp_tun";
struct WintunGuard { struct WintunGuard {
server_ip_str: String, server_ip_str: String,
child: Option<Child>,
} }
impl Drop for WintunGuard { impl Drop for WintunGuard {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(mut child) = self.child.take() {
let _ = child.kill();
let _ = child.wait();
}
let cleanup_script = format!( let cleanup_script = format!(
"$remote_ip = '{}'\n\ "$remote_ip = '{}'\n\
Remove-NetRoute -DestinationPrefix \"$remote_ip/32\" -Confirm:$false -ErrorAction SilentlyContinue\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; info!("Initializing built-in Wintun TUN tunnel...");
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. // 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") let _ = Command::new("powershell")
.creation_flags(CREATE_NO_WINDOW) .creation_flags(CREATE_NO_WINDOW)
.args(["-NoProfile", "-Command", &format!( .args(["-NoProfile", "-Command", &format!(
@ -68,7 +57,6 @@ pub async fn run_wintun_tunnel(
netsh interface set interface \"{TUN_NAME}\" admin=disable 2>$null" netsh interface set interface \"{TUN_NAME}\" admin=disable 2>$null"
)]) )])
.output(); .output();
// Brief pause to let the driver release the adapter
tokio::time::sleep(std::time::Duration::from_millis(200)).await; tokio::time::sleep(std::time::Duration::from_millis(200)).await;
// 2. Resolve Server IP for routing table exclusion // 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"))?; .ok_or_else(|| anyhow!("Could not resolve host IP for routing exclusion"))?;
let server_ip_str = server_ip.to_string(); 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 // 3. Prepare routing and firewall setup script
let current_exe = std::env::current_exe()?.to_string_lossy().into_owned(); 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 server_ip_str, current_exe
); );
// 4. Launch tun2socks + route setup IN PARALLEL to save ~3 seconds // Create the TunDevice inside the client process
let proxy_url = format!("http://{}", config.local_proxy.bind_addr); let tun_dev = create_tun_device(TUN_NAME, config.ostp.mtu)?;
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 { let mut _guard = WintunGuard {
server_ip_str: server_ip_str.clone(), server_ip_str: server_ip_str.clone(),
child: None,
}; };
// Run route setup in parallel while tun2socks creates the adapter. // Run route setup in parallel
// Also poll for the adapter to appear (typically <1s).
let route_handle = { let route_handle = {
let script = setup_script.clone(); let script = setup_script.clone();
tokio::task::spawn_blocking(move || { 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 adapter_deadline = tokio::time::Instant::now() + tokio::time::Duration::from_secs(8);
let mut adapter_ready = false; let mut adapter_ready = false;
while tokio::time::Instant::now() < adapter_deadline { while tokio::time::Instant::now() < adapter_deadline {
@ -153,9 +124,6 @@ pub async fn run_wintun_tunnel(
.output(); .output();
if let Ok(out) = check { if let Ok(out) = check {
let status = String::from_utf8_lossy(&out.stdout).trim().to_string(); 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() { if status == "Up" || status == "Disconnected" || !status.is_empty() {
adapter_ready = true; adapter_ready = true;
break; break;
@ -167,11 +135,11 @@ pub async fn run_wintun_tunnel(
tracing::warn!("WARNING: TUN adapter did not appear within timeout. Proceeding anyway."); 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; let _ = route_handle.await;
// 6. Configure the adapter (IP, metric, MTU, DNS) // 6. Configure the adapter
tracing::info!("Applying network configuration..."); info!("Applying network configuration...");
let mut net_setup = format!( 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 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 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 let Some(ref dns) = config.dns_server {
if !dns.is_empty() { if !dns.is_empty() {
tracing::info!("DNS server: {}", dns); info!("DNS server: {}", dns);
net_setup.push_str(&format!( net_setup.push_str(&format!(
"netsh interface ipv4 set dnsservers name=\"{TUN_NAME}\" static {} primary\n", dns "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]) .args(["-NoProfile", "-Command", &net_setup])
.output()?; .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 // Run the smoltcp stack loop in the background
let mut stdout = child.stdout.take(); let stack_shutdown_rx = shutdown.clone();
let mut stderr = child.stderr.take(); let stack_handle = tokio::spawn(async move {
_guard.child = Some(child); if let Err(e) = run_smoltcp_stack(
tun_dev.packet_rx,
if debug { tun_dev.packet_tx,
std::thread::spawn(move || { config.ostp.mtu,
use std::io::{BufRead, BufReader}; proxy_events_tx,
if let Some(out) = stdout.take() { client_msgs_rx,
let reader = BufReader::new(out); stack_shutdown_rx,
for line in reader.lines().map_while(Result::ok) { ).await {
tracing::debug!("tun2socks: {}", line); tracing::error!("smoltcp stack loop failed: {:?}", e);
} }
} });
});
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 // 8. Wait for shutdown signal
let _ = shutdown.changed().await; let _ = shutdown.changed().await;
tracing::info!("Deactivating TUN tunnel..."); info!("Deactivating TUN tunnel...");
drop(_guard); drop(_guard);
tracing::info!("TUN tunnel stopped.");
// Terminate smoltcp stack
let _ = stack_handle.await;
info!("TUN tunnel stopped.");
Ok(()) Ok(())
} }
@ -235,6 +195,8 @@ pub async fn run_wintun_tunnel(
#[allow(dead_code)] #[allow(dead_code)]
pub async fn run_wintun_tunnel( pub async fn run_wintun_tunnel(
_config: crate::config::ClientConfig, _config: crate::config::ClientConfig,
_proxy_events_tx: mpsc::Sender<ProxyEvent>,
_client_msgs_rx: mpsc::UnboundedReceiver<(u16, ProxyToClientMsg)>,
_shutdown: watch::Receiver<bool>, _shutdown: watch::Receiver<bool>,
) -> Result<()> { ) -> Result<()> {
Err(anyhow!("Wintun driver executed on a non-Windows host!")) Err(anyhow!("Wintun driver executed on a non-Windows host!"))

View File

@ -32,7 +32,7 @@ class OstpClientSdk private constructor(private val context: Context) {
// ── Native JNI bindings ─────────────────────────────────────────────────── // ── 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 nativeStopClient(): Boolean
private external fun nativeGetMetrics(): String private external fun nativeGetMetrics(): String
private external fun nativeGetLogs(): 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. * @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)) { if (started.getAndSet(true)) {
emitLog("SDK already started; call stop() first to change config") emitLog("SDK already started; call stop() first to change config")
return false return false
@ -175,7 +175,7 @@ class OstpClientSdk private constructor(private val context: Context) {
_state.value = TunnelState.Connecting _state.value = TunnelState.Connecting
val json = config.toNativeJson() val json = config.toNativeJson()
val ok = nativeStartClient(json) val ok = nativeStartClient(json, fd)
if (!ok) { if (!ok) {
_state.value = TunnelState.Failed("Native layer rejected config") _state.value = TunnelState.Failed("Native layer rejected config")
started.set(false) started.set(false)

View File

@ -8,14 +8,12 @@ use tokio::runtime::Runtime;
use tokio::sync::{mpsc, watch}; use tokio::sync::{mpsc, watch};
use ostp_client::bridge::{Bridge, BridgeMetrics}; use ostp_client::bridge::{Bridge, BridgeMetrics};
use ostp_client::config::ClientConfig; use ostp_client::config::ClientConfig;
use ostp_client::tunnel;
use ostp_client::app::{BridgeCommand, UiEvent}; use ostp_client::app::{BridgeCommand, UiEvent};
struct SdkState { struct SdkState {
runtime: Option<Runtime>, runtime: Option<Runtime>,
shutdown_tx: Option<watch::Sender<bool>>, shutdown_tx: Option<watch::Sender<bool>>,
metrics: Option<Arc<BridgeMetrics>>, metrics: Option<Arc<BridgeMetrics>>,
tun_child: Option<std::process::Child>,
cmd_tx: Option<mpsc::Sender<BridgeCommand>>, cmd_tx: Option<mpsc::Sender<BridgeCommand>>,
} }
@ -24,7 +22,6 @@ lazy_static! {
runtime: None, runtime: None,
shutdown_tx: None, shutdown_tx: None,
metrics: None, metrics: None,
tun_child: None,
cmd_tx: None, cmd_tx: None,
}); });
static ref LOGS: Mutex<VecDeque<String>> = Mutex::new(VecDeque::new()); static ref LOGS: Mutex<VecDeque<String>> = Mutex::new(VecDeque::new());
@ -42,13 +39,11 @@ fn add_log(text: String) {
} }
#[no_mangle] #[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, mut env: JNIEnv,
_class: JClass, _class: JClass,
config_json: JString, config_json: JString,
fd: jni::sys::jint, fd: jni::sys::jint,
t2s_bin_path: JString,
local_proxy: JString,
) -> jboolean { ) -> jboolean {
let mut state = match STATE.lock() { let mut state = match STATE.lock() {
Ok(s) => s, Ok(s) => s,
@ -105,16 +100,6 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_startClient(
Err(_) => return jni::sys::JNI_FALSE, 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 // Parse config from JSON
let config: ClientConfig = match serde_json::from_str(&config_str) { let config: ClientConfig = match serde_json::from_str(&config_str) {
Ok(cfg) => cfg, Ok(cfg) => cfg,
@ -124,8 +109,6 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_startClient(
} }
}; };
let debug = config.debug;
// Create tokio runtime // Create tokio runtime
let rt = match Runtime::new() { let rt = match Runtime::new() {
Ok(r) => r, 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 (ui_tx, mut ui_rx) = mpsc::channel(512);
let (cmd_tx, cmd_rx) = mpsc::channel(128); let (cmd_tx, cmd_rx) = mpsc::channel(128);
let (shutdown_tx, shutdown_rx) = watch::channel(false); let (shutdown_tx, shutdown_rx) = watch::channel(false);
let proxy_shutdown_rx = shutdown_tx.subscribe();
let metrics_clone = Arc::clone(&metrics); 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 bridge.run(ui_tx, cmd_rx, shutdown_rx, proxy_events_rx, client_msgs_tx).await
}); });
let config_proxy = config.clone(); if config.mode == "tun" {
rt.spawn(async move { if fd < 0 {
tunnel::run_local_proxy( add_log("Error: TUN mode requested but invalid file descriptor provided".to_string());
config_proxy.local_proxy, return jni::sys::JNI_FALSE;
config_proxy.ostp, }
config_proxy.exclusions,
config_proxy.debug, let tun_dev = match ostp_client::tunnel::create_tun_device_from_fd(fd, config.ostp.mtu) {
proxy_shutdown_rx, Ok(d) => d,
proxy_events_tx, Err(e) => {
client_msgs_rx, add_log(format!("Failed to wrap TUN fd: {:?}", e));
) return jni::sys::JNI_FALSE;
.await }
}); };
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 // Start logs receiver task
rt.spawn(async move { 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; 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.runtime = Some(rt);
state.shutdown_tx = Some(shutdown_tx); state.shutdown_tx = Some(shutdown_tx);
state.metrics = Some(metrics_clone); state.metrics = Some(metrics_clone);
state.tun_child = Some(child);
state.cmd_tx = Some(cmd_tx); state.cmd_tx = Some(cmd_tx);
add_log("OSTP SDK: Client successfully started".to_string()); 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] #[no_mangle]
pub extern "system" fn Java_net_ostp_client_OstpClientSdk_stopClient( pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeStopClient(
_env: JNIEnv, _env: JNIEnv,
_class: JClass, _class: JClass,
) -> jboolean { ) -> jboolean {
@ -275,11 +230,6 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_stopClient(
Err(_) => return jni::sys::JNI_FALSE, 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() { if let Some(shutdown_tx) = state.shutdown_tx.take() {
let _ = shutdown_tx.send(true); let _ = shutdown_tx.send(true);
} }
@ -295,7 +245,7 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_stopClient(
} }
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_net_ostp_client_OstpClientSdk_getMetrics( pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeGetMetrics(
env: JNIEnv, env: JNIEnv,
_class: JClass, _class: JClass,
) -> jstring { ) -> jstring {
@ -329,7 +279,7 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_getMetrics(
} }
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_net_ostp_client_OstpClientSdk_getLogs( pub extern "system" fn Java_net_ostp_client_OstpClientSdk_nativeGetLogs(
env: JNIEnv, env: JNIEnv,
_class: JClass, _class: JClass,
) -> jstring { ) -> jstring {

View File

@ -20,10 +20,9 @@ use portable_atomic::AtomicU64;
use std::time::Instant; use std::time::Instant;
use axum::{ use axum::{
body::Body,
extract::{Path, State}, extract::{Path, State},
http::{header, Request, StatusCode, Uri}, http::{header, StatusCode, Uri},
response::{IntoResponse, Response}, response::IntoResponse,
routing::{get, post, put}, routing::{get, post, put},
Json, Router, Json, Router,
}; };