mirror of https://github.com/ospab/ostp.git
Implement XTLS-Reality masquerade for UoT/TCP and fix MTU/config settings
This commit is contained in:
parent
ef242bf6f4
commit
3e511f1fc5
|
|
@ -123,6 +123,28 @@ version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aws-lc-rs"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
|
||||||
|
dependencies = [
|
||||||
|
"aws-lc-sys",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aws-lc-sys"
|
||||||
|
version = "0.41.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cmake",
|
||||||
|
"dunce",
|
||||||
|
"fs_extra",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
|
|
@ -224,6 +246,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
|
"jobserver",
|
||||||
|
"libc",
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -327,6 +351,15 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cmake"
|
||||||
|
version = "0.1.58"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
|
|
@ -404,6 +437,15 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.5.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||||
|
dependencies = [
|
||||||
|
"powerfmt",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
|
@ -426,6 +468,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dunce"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.14"
|
version = "0.3.14"
|
||||||
|
|
@ -457,6 +505,12 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fs_extra"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.32"
|
version = "0.3.32"
|
||||||
|
|
@ -523,6 +577,18 @@ dependencies = [
|
||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
"wasip2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ghash"
|
name = "ghash"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|
@ -820,6 +886,16 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jobserver"
|
||||||
|
version = "0.1.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.98"
|
version = "0.3.98"
|
||||||
|
|
@ -909,6 +985,12 @@ dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
|
|
@ -970,6 +1052,7 @@ dependencies = [
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
"rand",
|
"rand",
|
||||||
"rustls",
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
|
@ -977,6 +1060,7 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"webpki-roots 0.26.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1022,11 +1106,14 @@ dependencies = [
|
||||||
"ostp-core",
|
"ostp-core",
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
"rand",
|
"rand",
|
||||||
|
"rcgen",
|
||||||
|
"rustls",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
@ -1045,6 +1132,16 @@ dependencies = [
|
||||||
"winres",
|
"winres",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "3.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
|
|
@ -1095,6 +1192,12 @@ dependencies = [
|
||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
|
|
@ -1122,6 +1225,12 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "5.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
|
|
@ -1149,7 +1258,20 @@ version = "0.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.17",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rcgen"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2"
|
||||||
|
dependencies = [
|
||||||
|
"pem",
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"time",
|
||||||
|
"yasna",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1177,7 +1299,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"getrandom",
|
"getrandom 0.2.17",
|
||||||
"libc",
|
"libc",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
|
|
@ -1198,6 +1320,8 @@ version = "0.23.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
|
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aws-lc-rs",
|
||||||
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"ring",
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
|
|
@ -1221,6 +1345,7 @@ version = "0.103.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aws-lc-rs",
|
||||||
"ring",
|
"ring",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
|
|
@ -1468,6 +1593,25 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"num-conv",
|
||||||
|
"powerfmt",
|
||||||
|
"serde_core",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
|
|
@ -1708,6 +1852,15 @@ version = "0.11.1+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip2"
|
||||||
|
version = "1.0.3+wasi-0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.121"
|
version = "0.2.121"
|
||||||
|
|
@ -1753,6 +1906,24 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "0.26.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
|
||||||
|
dependencies = [
|
||||||
|
"webpki-roots 1.0.7",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
|
||||||
|
dependencies = [
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.11"
|
version = "0.1.11"
|
||||||
|
|
@ -1978,12 +2149,27 @@ dependencies = [
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.57.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
version = "0.6.3"
|
version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yasna"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
|
||||||
|
dependencies = [
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,11 @@ json_comments = "0.2"
|
||||||
portable-atomic.workspace = true
|
portable-atomic.workspace = true
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
socket2 = "0.6.3"
|
socket2 = "0.6.3"
|
||||||
rustls = { version = "0.23.40", default-features = false, features = ["ring", "std"] }
|
rustls = { version = "0.23", default-features = false, features = ["ring", "std"] }
|
||||||
tokio-rustls = { version = "0.26.0", default-features = false, features = ["ring"] }
|
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
||||||
futures-util = "0.3.32"
|
futures-util = "0.3.32"
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
|
webpki-roots = "0.26"
|
||||||
|
rustls-pki-types = "1.7"
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,16 @@ pub struct BridgeMetrics {
|
||||||
pub connection_state: AtomicU8,
|
pub connection_state: AtomicU8,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_datagram(socket: &crate::transport::Transport, frame: &Bytes, turn_enabled: bool) -> std::io::Result<usize> {
|
async fn send_datagram(socket: &crate::transport::Transport, frame: &Bytes, webrtc_masquerade: bool) -> std::io::Result<usize> {
|
||||||
if turn_enabled {
|
if webrtc_masquerade {
|
||||||
let mut out = bytes::BytesMut::with_capacity(4 + frame.len());
|
let mut out = bytes::BytesMut::with_capacity(12 + frame.len());
|
||||||
bytes::BufMut::put_u16(&mut out, 0x4000);
|
// Fake SRTP Header:
|
||||||
bytes::BufMut::put_u16(&mut out, frame.len() as u16);
|
// [0] 0x80 (Version 2)
|
||||||
|
// [1] 0x60 (Payload Type 96 - dynamic video)
|
||||||
|
// [2..3] Sequence number (dummy 0x1234)
|
||||||
|
// [4..7] Timestamp (dummy)
|
||||||
|
// [8..11] SSRC (dummy)
|
||||||
|
out.extend_from_slice(&[0x80, 0x60, 0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44]);
|
||||||
out.extend_from_slice(frame);
|
out.extend_from_slice(frame);
|
||||||
socket.send(&out.freeze()).await
|
socket.send(&out.freeze()).await
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -66,10 +71,7 @@ pub struct Bridge {
|
||||||
handshake_timeout_ms: u64,
|
handshake_timeout_ms: u64,
|
||||||
io_timeout_ms: u64,
|
io_timeout_ms: u64,
|
||||||
|
|
||||||
pub turn_enabled: bool,
|
pub keepalive_interval_sec: u64,
|
||||||
pub turn_server: String,
|
|
||||||
pub turn_username: String,
|
|
||||||
pub turn_password: String,
|
|
||||||
pub mode: String,
|
pub mode: String,
|
||||||
pub mux_enabled: bool,
|
pub mux_enabled: bool,
|
||||||
pub mux_sessions: usize,
|
pub mux_sessions: usize,
|
||||||
|
|
@ -78,6 +80,7 @@ pub struct Bridge {
|
||||||
pub stealth_sni: String,
|
pub stealth_sni: String,
|
||||||
pub stealth_port: u16,
|
pub stealth_port: u16,
|
||||||
pub mtu: usize,
|
pub mtu: usize,
|
||||||
|
pub reality_enabled: bool,
|
||||||
|
|
||||||
metrics: Arc<BridgeMetrics>,
|
metrics: Arc<BridgeMetrics>,
|
||||||
sample_sent: u64,
|
sample_sent: u64,
|
||||||
|
|
@ -100,10 +103,7 @@ impl Bridge {
|
||||||
handshake_timeout_ms: config.ostp.handshake_timeout_ms,
|
handshake_timeout_ms: config.ostp.handshake_timeout_ms,
|
||||||
io_timeout_ms: config.ostp.io_timeout_ms,
|
io_timeout_ms: config.ostp.io_timeout_ms,
|
||||||
|
|
||||||
turn_enabled: config.turn.enabled,
|
keepalive_interval_sec: config.ostp.keepalive_interval_sec,
|
||||||
turn_server: config.turn.server_addr.clone(),
|
|
||||||
turn_username: config.turn.username.clone(),
|
|
||||||
turn_password: config.turn.access_key.clone(),
|
|
||||||
mode: config.mode.clone(),
|
mode: config.mode.clone(),
|
||||||
mux_enabled: config.multiplex.enabled,
|
mux_enabled: config.multiplex.enabled,
|
||||||
mux_sessions: config.multiplex.sessions.max(1),
|
mux_sessions: config.multiplex.sessions.max(1),
|
||||||
|
|
@ -112,6 +112,7 @@ impl Bridge {
|
||||||
stealth_sni: config.transport.stealth_sni.clone(),
|
stealth_sni: config.transport.stealth_sni.clone(),
|
||||||
stealth_port: config.transport.stealth_port,
|
stealth_port: config.transport.stealth_port,
|
||||||
mtu: config.ostp.mtu,
|
mtu: config.ostp.mtu,
|
||||||
|
reality_enabled: !config.reality.pbk.is_empty(),
|
||||||
|
|
||||||
metrics,
|
metrics,
|
||||||
sample_sent: 0,
|
sample_sent: 0,
|
||||||
|
|
@ -131,7 +132,7 @@ impl Bridge {
|
||||||
proxy_tx: mpsc::UnboundedSender<(u16, ProxyToClientMsg)>,
|
proxy_tx: mpsc::UnboundedSender<(u16, ProxyToClientMsg)>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut metrics_tick = interval(Duration::from_millis(500));
|
let mut metrics_tick = interval(Duration::from_millis(500));
|
||||||
let mut keepalive_tick = tokio::time::interval(Duration::from_secs(5));
|
let mut keepalive_tick = tokio::time::interval(Duration::from_secs(self.keepalive_interval_sec.max(1)));
|
||||||
let mut retransmit_tick = tokio::time::interval(Duration::from_millis(50));
|
let mut retransmit_tick = tokio::time::interval(Duration::from_millis(50));
|
||||||
let init_msg = if self.mode == "tun" {
|
let init_msg = if self.mode == "tun" {
|
||||||
"Bridge initialized (TUN mode)".to_string()
|
"Bridge initialized (TUN mode)".to_string()
|
||||||
|
|
@ -220,7 +221,7 @@ impl Bridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProtocolAction::SendDatagram(frame) => {
|
ProtocolAction::SendDatagram(frame) => {
|
||||||
let _ = send_datagram(&session.socket, &frame, self.turn_enabled).await;
|
let _ = send_datagram(&session.socket, &frame, (self.transport_mode == "udp")).await;
|
||||||
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -273,19 +274,14 @@ impl Bridge {
|
||||||
let session_index = sessions.len();
|
let session_index = sessions.len();
|
||||||
let socket_clone = sock.clone();
|
let socket_clone = sock.clone();
|
||||||
let udp_tx_clone = udp_tx.clone();
|
let udp_tx_clone = udp_tx.clone();
|
||||||
let is_turn = self.turn_enabled;
|
let is_webrtc = (self.transport_mode == "udp");
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut buf = vec![0_u8; 65535];
|
let mut buf = vec![0_u8; 65535];
|
||||||
loop {
|
loop {
|
||||||
match socket_clone.recv(&mut buf).await {
|
match socket_clone.recv(&mut buf).await {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
let inbound = if is_turn && n >= 4 && buf[0] == 0x40 && buf[1] == 0x00 {
|
let inbound = if is_webrtc && n >= 12 && buf[0] == 0x80 {
|
||||||
let len = u16::from_be_bytes([buf[2], buf[3]]) as usize;
|
Bytes::copy_from_slice(&buf[12..n])
|
||||||
if 4 + len <= n {
|
|
||||||
Bytes::copy_from_slice(&buf[4..4+len])
|
|
||||||
} else {
|
|
||||||
Bytes::copy_from_slice(&buf[..n])
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Bytes::copy_from_slice(&buf[..n])
|
Bytes::copy_from_slice(&buf[..n])
|
||||||
};
|
};
|
||||||
|
|
@ -368,15 +364,14 @@ impl Bridge {
|
||||||
let session_index = new_sessions.len();
|
let session_index = new_sessions.len();
|
||||||
let socket_clone = sock.clone();
|
let socket_clone = sock.clone();
|
||||||
let udp_tx_clone = udp_tx.clone();
|
let udp_tx_clone = udp_tx.clone();
|
||||||
let is_turn = self.turn_enabled;
|
let is_webrtc = (self.transport_mode == "udp");
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut buf = vec![0_u8; 65535];
|
let mut buf = vec![0_u8; 65535];
|
||||||
loop {
|
loop {
|
||||||
match socket_clone.recv(&mut buf).await {
|
match socket_clone.recv(&mut buf).await {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
let inbound = if is_turn && n >= 4 && buf[0] == 0x40 && buf[1] == 0x00 {
|
let inbound = if is_webrtc && n >= 12 && buf[0] == 0x80 {
|
||||||
let len = u16::from_be_bytes([buf[2], buf[3]]) as usize;
|
Bytes::copy_from_slice(&buf[12..n])
|
||||||
if 4 + len <= n { Bytes::copy_from_slice(&buf[4..4+len]) } else { Bytes::copy_from_slice(&buf[..n]) }
|
|
||||||
} else {
|
} else {
|
||||||
Bytes::copy_from_slice(&buf[..n])
|
Bytes::copy_from_slice(&buf[..n])
|
||||||
};
|
};
|
||||||
|
|
@ -480,19 +475,14 @@ impl Bridge {
|
||||||
let session_index = new_sessions.len();
|
let session_index = new_sessions.len();
|
||||||
let socket_clone = sock.clone();
|
let socket_clone = sock.clone();
|
||||||
let udp_tx_clone = udp_tx.clone();
|
let udp_tx_clone = udp_tx.clone();
|
||||||
let is_turn = self.turn_enabled;
|
let is_webrtc = (self.transport_mode == "udp");
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut buf = vec![0_u8; 65535];
|
let mut buf = vec![0_u8; 65535];
|
||||||
loop {
|
loop {
|
||||||
match socket_clone.recv(&mut buf).await {
|
match socket_clone.recv(&mut buf).await {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
let inbound = if is_turn && n >= 4 && buf[0] == 0x40 && buf[1] == 0x00 {
|
let inbound = if is_webrtc && n >= 12 && buf[0] == 0x80 {
|
||||||
let len = u16::from_be_bytes([buf[2], buf[3]]) as usize;
|
Bytes::copy_from_slice(&buf[12..n])
|
||||||
if 4 + len <= n {
|
|
||||||
Bytes::copy_from_slice(&buf[4..4+len])
|
|
||||||
} else {
|
|
||||||
Bytes::copy_from_slice(&buf[..n])
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Bytes::copy_from_slice(&buf[..n])
|
Bytes::copy_from_slice(&buf[..n])
|
||||||
};
|
};
|
||||||
|
|
@ -539,14 +529,14 @@ impl Bridge {
|
||||||
if let Ok(ProtocolAction::SendDatagram(frame)) = session.machine.on_event(OstpEvent::Outbound(0, ping_payload)) {
|
if let Ok(ProtocolAction::SendDatagram(frame)) = session.machine.on_event(OstpEvent::Outbound(0, ping_payload)) {
|
||||||
// Must go through send_datagram() for TURN-mode wrapping;
|
// Must go through send_datagram() for TURN-mode wrapping;
|
||||||
// raw socket.send() bypasses the ChannelData header and breaks RTT in TURN.
|
// raw socket.send() bypasses the ChannelData header and breaks RTT in TURN.
|
||||||
let _ = send_datagram(&session.socket, &frame, self.turn_enabled).await;
|
let _ = send_datagram(&session.socket, &frame, (self.transport_mode == "udp")).await;
|
||||||
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send Relay KeepAlive (Force NAT/Server Persistence)
|
// Send Relay KeepAlive (Force NAT/Server Persistence)
|
||||||
let ka_payload = Bytes::from(RelayMessage::KeepAlive.encode());
|
let ka_payload = Bytes::from(RelayMessage::KeepAlive.encode());
|
||||||
if let Ok(ProtocolAction::SendDatagram(frame)) = session.machine.on_event(OstpEvent::Outbound(0, ka_payload)) {
|
if let Ok(ProtocolAction::SendDatagram(frame)) = session.machine.on_event(OstpEvent::Outbound(0, ka_payload)) {
|
||||||
let _ = send_datagram(&session.socket, &frame, self.turn_enabled).await;
|
let _ = send_datagram(&session.socket, &frame, (self.transport_mode == "udp")).await;
|
||||||
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -569,7 +559,7 @@ impl Bridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProtocolAction::SendDatagram(frame) => {
|
ProtocolAction::SendDatagram(frame) => {
|
||||||
let _ = send_datagram(&session.socket, &frame, self.turn_enabled).await;
|
let _ = send_datagram(&session.socket, &frame, (self.transport_mode == "udp")).await;
|
||||||
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -632,7 +622,7 @@ impl Bridge {
|
||||||
let out_payload = Bytes::from(relay_msg.encode());
|
let out_payload = Bytes::from(relay_msg.encode());
|
||||||
match session.machine.on_event(OstpEvent::Outbound(stream_id, out_payload)) {
|
match session.machine.on_event(OstpEvent::Outbound(stream_id, out_payload)) {
|
||||||
Ok(ProtocolAction::SendDatagram(frame)) => {
|
Ok(ProtocolAction::SendDatagram(frame)) => {
|
||||||
if send_datagram(&session.socket, &frame, self.turn_enabled).await.is_ok() {
|
if send_datagram(&session.socket, &frame, (self.transport_mode == "udp")).await.is_ok() {
|
||||||
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
||||||
if self.debug {
|
if self.debug {
|
||||||
let _ = tx.send(UiEvent::Log(format!(
|
let _ = tx.send(UiEvent::Log(format!(
|
||||||
|
|
@ -646,7 +636,7 @@ impl Bridge {
|
||||||
let mut sent = 0usize;
|
let mut sent = 0usize;
|
||||||
for item in list {
|
for item in list {
|
||||||
if let ProtocolAction::SendDatagram(frame) = item {
|
if let ProtocolAction::SendDatagram(frame) = item {
|
||||||
if send_datagram(&session.socket, &frame, self.turn_enabled).await.is_ok() {
|
if send_datagram(&session.socket, &frame, (self.transport_mode == "udp")).await.is_ok() {
|
||||||
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
self.metrics.bytes_sent.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
||||||
sent += 1;
|
sent += 1;
|
||||||
}
|
}
|
||||||
|
|
@ -817,64 +807,7 @@ impl Bridge {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.turn_enabled && self.transport_mode != "wss" {
|
// Connection to remote is handled inside try_connect_transport
|
||||||
let udp_socket = match &socket {
|
|
||||||
crate::transport::Transport::Udp(sock) => sock,
|
|
||||||
_ => return Err(anyhow::anyhow!("TURN requires UDP transport")),
|
|
||||||
};
|
|
||||||
let turn_addr = if self.turn_server.contains(':') {
|
|
||||||
self.turn_server.clone()
|
|
||||||
} else {
|
|
||||||
format!("{}:3478", self.turn_server)
|
|
||||||
};
|
|
||||||
tx.send(UiEvent::Log(format!("Allocating TURN relay via {}", turn_addr))).await.ok();
|
|
||||||
|
|
||||||
match crate::turn::perform_turn_allocation(udp_socket, &turn_addr, &self.turn_username, &self.turn_password, &self.server_addr).await {
|
|
||||||
Ok(relay_addr) => {
|
|
||||||
tx.send(UiEvent::Log(format!("TURN relay allocated ({})", relay_addr))).await.ok();
|
|
||||||
|
|
||||||
let resolved_turn: Vec<std::net::SocketAddr> = tokio::net::lookup_host(&turn_addr).await
|
|
||||||
.map_err(|e| anyhow::anyhow!("failed to resolve TURN {}: {}", turn_addr, e))?
|
|
||||||
.collect();
|
|
||||||
let turn_target = resolved_turn.first().ok_or_else(|| anyhow::anyhow!("no IP resolved for TURN {}", turn_addr))?;
|
|
||||||
|
|
||||||
let connect_ip = if udp_socket.local_addr().map(|a| a.is_ipv6()).unwrap_or(false) && turn_target.is_ipv4() {
|
|
||||||
if let std::net::IpAddr::V4(ipv4) = turn_target.ip() {
|
|
||||||
std::net::IpAddr::V6(synthesize_nat64(ipv4))
|
|
||||||
} else {
|
|
||||||
turn_target.ip()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
turn_target.ip()
|
|
||||||
};
|
|
||||||
|
|
||||||
let connect_addr = std::net::SocketAddr::new(connect_ip, turn_target.port());
|
|
||||||
udp_socket
|
|
||||||
.connect(connect_addr)
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("failed to re-connect to TURN {}", connect_addr))?;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tx.send(UiEvent::Log(format!("TURN allocation failed: {}. Using direct UDP.", e))).await.ok();
|
|
||||||
let connect_ip = if udp_socket.local_addr().map(|a| a.is_ipv6()).unwrap_or(false) && target_ip.is_ipv4() {
|
|
||||||
if let std::net::IpAddr::V4(ipv4) = target_ip {
|
|
||||||
std::net::IpAddr::V6(synthesize_nat64(ipv4))
|
|
||||||
} else {
|
|
||||||
target_ip
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
target_ip
|
|
||||||
};
|
|
||||||
let connect_addr = std::net::SocketAddr::new(connect_ip, port);
|
|
||||||
udp_socket
|
|
||||||
.connect(connect_addr)
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("failed to connect udp to {}", connect_addr))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connection to remote is handled inside the TURN/direct branches above
|
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let action = machine.on_event(OstpEvent::Start)?;
|
let action = machine.on_event(OstpEvent::Start)?;
|
||||||
|
|
@ -897,7 +830,7 @@ impl Bridge {
|
||||||
if attempt > 0 {
|
if attempt > 0 {
|
||||||
tx.send(UiEvent::Log(format!("Handshake attempt {} lost. Retransmitting...", attempt))).await.ok();
|
tx.send(UiEvent::Log(format!("Handshake attempt {} lost. Retransmitting...", attempt))).await.ok();
|
||||||
}
|
}
|
||||||
send_datagram(&socket, &handshake_frame, self.turn_enabled).await?;
|
send_datagram(&socket, &handshake_frame, (self.transport_mode == "udp")).await?;
|
||||||
self.metrics.bytes_sent.fetch_add(handshake_frame.len() as u64, Ordering::Relaxed);
|
self.metrics.bytes_sent.fetch_add(handshake_frame.len() as u64, Ordering::Relaxed);
|
||||||
|
|
||||||
match timeout(Duration::from_millis(attempt_timeout_ms), socket.recv(&mut buf)).await {
|
match timeout(Duration::from_millis(attempt_timeout_ms), socket.recv(&mut buf)).await {
|
||||||
|
|
@ -923,7 +856,7 @@ impl Bridge {
|
||||||
if attempt > 0 {
|
if attempt > 0 {
|
||||||
tx.send(UiEvent::Log(format!("NAT64 handshake attempt {} lost. Retransmitting...", attempt))).await.ok();
|
tx.send(UiEvent::Log(format!("NAT64 handshake attempt {} lost. Retransmitting...", attempt))).await.ok();
|
||||||
}
|
}
|
||||||
send_datagram(&fallback_socket, &handshake_frame, self.turn_enabled).await?;
|
send_datagram(&fallback_socket, &handshake_frame, (self.transport_mode == "udp")).await?;
|
||||||
match timeout(Duration::from_millis(1200), fallback_socket.recv(&mut buf)).await {
|
match timeout(Duration::from_millis(1200), fallback_socket.recv(&mut buf)).await {
|
||||||
Ok(Ok(n)) => {
|
Ok(Ok(n)) => {
|
||||||
size = n;
|
size = n;
|
||||||
|
|
@ -950,13 +883,8 @@ impl Bridge {
|
||||||
self.metrics.bytes_recv.fetch_add(size as u64, Ordering::Relaxed);
|
self.metrics.bytes_recv.fetch_add(size as u64, Ordering::Relaxed);
|
||||||
tracing::info!("Handshake response received: {} bytes", size);
|
tracing::info!("Handshake response received: {} bytes", size);
|
||||||
|
|
||||||
let inbound = if self.turn_enabled && size >= 4 && buf[0] == 0x40 && buf[1] == 0x00 {
|
let inbound = if (self.transport_mode == "udp") && size >= 12 && buf[0] == 0x80 {
|
||||||
let len = u16::from_be_bytes([buf[2], buf[3]]) as usize;
|
Bytes::copy_from_slice(&buf[12..size])
|
||||||
if 4 + len <= size {
|
|
||||||
Bytes::copy_from_slice(&buf[4..4+len])
|
|
||||||
} else {
|
|
||||||
Bytes::copy_from_slice(&buf[..size])
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Bytes::copy_from_slice(&buf[..size])
|
Bytes::copy_from_slice(&buf[..size])
|
||||||
};
|
};
|
||||||
|
|
@ -975,15 +903,12 @@ impl Bridge {
|
||||||
self.handshake_timeout_ms = cfg.ostp.handshake_timeout_ms;
|
self.handshake_timeout_ms = cfg.ostp.handshake_timeout_ms;
|
||||||
self.io_timeout_ms = cfg.ostp.io_timeout_ms;
|
self.io_timeout_ms = cfg.ostp.io_timeout_ms;
|
||||||
self.mode = cfg.mode.clone(); // Bug fix: mode was never updated on hot-reload
|
self.mode = cfg.mode.clone(); // Bug fix: mode was never updated on hot-reload
|
||||||
self.turn_enabled = cfg.turn.enabled;
|
|
||||||
self.turn_server = cfg.turn.server_addr.clone();
|
|
||||||
self.turn_username = cfg.turn.username.clone();
|
|
||||||
self.turn_password = cfg.turn.access_key.clone();
|
|
||||||
self.mux_enabled = cfg.multiplex.enabled;
|
self.mux_enabled = cfg.multiplex.enabled;
|
||||||
self.mux_sessions = cfg.multiplex.sessions.max(1);
|
self.mux_sessions = cfg.multiplex.sessions.max(1);
|
||||||
self.transport_mode = cfg.transport.mode.clone();
|
self.transport_mode = cfg.transport.mode.clone();
|
||||||
self.stealth_sni = cfg.transport.stealth_sni.clone();
|
self.stealth_sni = cfg.transport.stealth_sni.clone();
|
||||||
self.stealth_port = cfg.transport.stealth_port;
|
self.stealth_port = cfg.transport.stealth_port;
|
||||||
|
self.reality_enabled = !cfg.reality.pbk.is_empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn try_connect_transport(
|
async fn try_connect_transport(
|
||||||
|
|
@ -1002,7 +927,7 @@ impl Bridge {
|
||||||
port
|
port
|
||||||
};
|
};
|
||||||
let (tx, rx) = crate::transport::xhttp::connect_xhttp(
|
let (tx, rx) = crate::transport::xhttp::connect_xhttp(
|
||||||
target_ip, uot_port, &self.stealth_sni, &self.access_key
|
target_ip, uot_port, &self.stealth_sni, &self.access_key, self.reality_enabled
|
||||||
).await?;
|
).await?;
|
||||||
Ok(crate::transport::Transport::Uot { tx, rx })
|
Ok(crate::transport::Transport::Uot { tx, rx })
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1053,3 +978,4 @@ fn synthesize_nat64(ip: std::net::Ipv4Addr) -> std::net::Ipv6Addr {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ pub struct ClientConfig {
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
pub ostp: OstpConfig,
|
pub ostp: OstpConfig,
|
||||||
pub local_proxy: LocalProxyConfig,
|
pub local_proxy: LocalProxyConfig,
|
||||||
pub turn: TurnConfig,
|
pub reality: RealityConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub transport: TransportConfig,
|
pub transport: TransportConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
@ -48,8 +48,12 @@ pub struct OstpConfig {
|
||||||
pub io_timeout_ms: u64,
|
pub io_timeout_ms: u64,
|
||||||
#[serde(default = "default_mtu")]
|
#[serde(default = "default_mtu")]
|
||||||
pub mtu: usize,
|
pub mtu: usize,
|
||||||
|
#[serde(default = "default_keepalive")]
|
||||||
|
pub keepalive_interval_sec: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_keepalive() -> u64 { 5 }
|
||||||
|
|
||||||
fn default_mtu() -> usize { 1350 }
|
fn default_mtu() -> usize { 1350 }
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|
@ -88,11 +92,17 @@ impl Default for TransportConfig {
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
pub struct TurnConfig {
|
pub struct RealityConfig {
|
||||||
pub enabled: bool,
|
#[serde(default)]
|
||||||
pub server_addr: String,
|
pub sni: String,
|
||||||
pub username: String,
|
#[serde(default)]
|
||||||
pub access_key: String,
|
pub fp: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub pbk: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub sid: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub spx: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -105,6 +115,7 @@ impl Default for OstpConfig {
|
||||||
handshake_timeout_ms: 5000,
|
handshake_timeout_ms: 5000,
|
||||||
io_timeout_ms: 2500,
|
io_timeout_ms: 2500,
|
||||||
mtu: default_mtu(),
|
mtu: default_mtu(),
|
||||||
|
keepalive_interval_sec: default_keepalive(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -126,7 +137,7 @@ impl Default for ClientConfig {
|
||||||
debug: false,
|
debug: false,
|
||||||
ostp: OstpConfig::default(),
|
ostp: OstpConfig::default(),
|
||||||
local_proxy: LocalProxyConfig::default(),
|
local_proxy: LocalProxyConfig::default(),
|
||||||
turn: TurnConfig::default(),
|
reality: RealityConfig::default(),
|
||||||
transport: TransportConfig::default(),
|
transport: TransportConfig::default(),
|
||||||
exclusions: ExclusionConfig::default(),
|
exclusions: ExclusionConfig::default(),
|
||||||
multiplex: MultiplexConfig::default(),
|
multiplex: MultiplexConfig::default(),
|
||||||
|
|
@ -158,7 +169,7 @@ struct RawUnifiedConfig {
|
||||||
tun: Option<RawTunSection>,
|
tun: Option<RawTunSection>,
|
||||||
exclude: Option<RawExcludeSection>,
|
exclude: Option<RawExcludeSection>,
|
||||||
mux: Option<RawMuxSection>,
|
mux: Option<RawMuxSection>,
|
||||||
turn: Option<RawTurnSection>,
|
reality: Option<RawRealitySection>,
|
||||||
transport: Option<RawTransportSection>,
|
transport: Option<RawTransportSection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,11 +200,12 @@ struct RawMuxSection {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct RawTurnSection {
|
struct RawRealitySection {
|
||||||
enabled: Option<bool>,
|
sni: Option<String>,
|
||||||
server_addr: Option<String>,
|
fp: Option<String>,
|
||||||
username: Option<String>,
|
pbk: Option<String>,
|
||||||
access_key: Option<String>,
|
sid: Option<String>,
|
||||||
|
spx: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientConfig {
|
impl ClientConfig {
|
||||||
|
|
@ -235,16 +247,18 @@ impl ClientConfig {
|
||||||
handshake_timeout_ms: 5000,
|
handshake_timeout_ms: 5000,
|
||||||
io_timeout_ms: 2500,
|
io_timeout_ms: 2500,
|
||||||
mtu,
|
mtu,
|
||||||
|
keepalive_interval_sec: default_keepalive(),
|
||||||
},
|
},
|
||||||
local_proxy: LocalProxyConfig {
|
local_proxy: LocalProxyConfig {
|
||||||
bind_addr: socks5,
|
bind_addr: socks5,
|
||||||
connect_timeout_ms: 15000,
|
connect_timeout_ms: 15000,
|
||||||
},
|
},
|
||||||
turn: TurnConfig {
|
reality: RealityConfig {
|
||||||
enabled: raw.turn.as_ref().and_then(|t| t.enabled).unwrap_or(false),
|
sni: raw.reality.as_ref().and_then(|t| t.sni.clone()).unwrap_or_default(),
|
||||||
server_addr: raw.turn.as_ref().and_then(|t| t.server_addr.clone()).unwrap_or_default(),
|
fp: raw.reality.as_ref().and_then(|t| t.fp.clone()).unwrap_or_default(),
|
||||||
username: raw.turn.as_ref().and_then(|t| t.username.clone()).unwrap_or_default(),
|
pbk: raw.reality.as_ref().and_then(|t| t.pbk.clone()).unwrap_or_default(),
|
||||||
access_key: raw.turn.as_ref().and_then(|t| t.access_key.clone()).unwrap_or_default(),
|
sid: raw.reality.as_ref().and_then(|t| t.sid.clone()).unwrap_or_default(),
|
||||||
|
spx: raw.reality.as_ref().and_then(|t| t.spx.clone()).unwrap_or_default(),
|
||||||
},
|
},
|
||||||
transport: TransportConfig {
|
transport: TransportConfig {
|
||||||
mode: raw.transport.as_ref().and_then(|t| t.mode.clone()).unwrap_or_else(|| "udp".to_string()),
|
mode: raw.transport.as_ref().and_then(|t| t.mode.clone()).unwrap_or_else(|| "udp".to_string()),
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,5 @@ pub mod sysproxy;
|
||||||
pub mod transport;
|
pub mod transport;
|
||||||
pub mod tunnel;
|
pub mod tunnel;
|
||||||
|
|
||||||
pub mod turn;
|
|
||||||
pub mod runner;
|
pub mod runner;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,62 @@ use tokio::sync::mpsc;
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
|
use rustls::ClientConfig;
|
||||||
|
use rustls::pki_types::ServerName;
|
||||||
|
use std::sync::Arc as StdArc;
|
||||||
|
use tokio_rustls::TlsConnector;
|
||||||
|
|
||||||
|
mod danger {
|
||||||
|
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
|
||||||
|
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
|
||||||
|
use rustls::DigitallySignedStruct;
|
||||||
|
use rustls::crypto::CryptoProvider;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NoCertificateVerification;
|
||||||
|
|
||||||
|
impl ServerCertVerifier for NoCertificateVerification {
|
||||||
|
fn verify_server_cert(
|
||||||
|
&self,
|
||||||
|
_end_entity: &CertificateDer<'_>,
|
||||||
|
_intermediates: &[CertificateDer<'_>],
|
||||||
|
_server_name: &ServerName<'_>,
|
||||||
|
_ocsp_response: &[u8],
|
||||||
|
_now: UnixTime,
|
||||||
|
) -> Result<ServerCertVerified, rustls::Error> {
|
||||||
|
Ok(ServerCertVerified::assertion())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_tls12_signature(
|
||||||
|
&self,
|
||||||
|
_message: &[u8],
|
||||||
|
_cert: &CertificateDer<'_>,
|
||||||
|
_dss: &DigitallySignedStruct,
|
||||||
|
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||||
|
Ok(HandshakeSignatureValid::assertion())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_tls13_signature(
|
||||||
|
&self,
|
||||||
|
_message: &[u8],
|
||||||
|
_cert: &CertificateDer<'_>,
|
||||||
|
_dss: &DigitallySignedStruct,
|
||||||
|
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||||
|
Ok(HandshakeSignatureValid::assertion())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
|
||||||
|
vec![
|
||||||
|
rustls::SignatureScheme::RSA_PKCS1_SHA256,
|
||||||
|
rustls::SignatureScheme::RSA_PKCS1_SHA384,
|
||||||
|
rustls::SignatureScheme::RSA_PKCS1_SHA512,
|
||||||
|
rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
|
||||||
|
rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
|
||||||
|
rustls::SignatureScheme::ED25519,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type HmacSha256 = Hmac<Sha256>;
|
type HmacSha256 = Hmac<Sha256>;
|
||||||
|
|
||||||
|
|
@ -16,12 +72,45 @@ pub async fn connect_xhttp(
|
||||||
port: u16,
|
port: u16,
|
||||||
sni: &str,
|
sni: &str,
|
||||||
access_key: &[u8],
|
access_key: &[u8],
|
||||||
|
tls_enabled: bool,
|
||||||
) -> Result<(mpsc::Sender<Bytes>, Arc<tokio::sync::Mutex<mpsc::Receiver<Bytes>>>)> {
|
) -> Result<(mpsc::Sender<Bytes>, Arc<tokio::sync::Mutex<mpsc::Receiver<Bytes>>>)> {
|
||||||
let addr = std::net::SocketAddr::new(target_ip, port);
|
let addr = std::net::SocketAddr::new(target_ip, port);
|
||||||
let mut tcp_stream = TcpStream::connect(addr).await
|
let tcp_stream = TcpStream::connect(addr).await
|
||||||
.with_context(|| format!("failed to connect to {}", addr))?;
|
.with_context(|| format!("failed to connect to {}", addr))?;
|
||||||
tcp_stream.set_nodelay(true)?;
|
tcp_stream.set_nodelay(true)?;
|
||||||
|
|
||||||
|
if tls_enabled {
|
||||||
|
// Setup rustls client skipping cert validation (Reality self-signed certs)
|
||||||
|
let mut config = ClientConfig::builder_with_provider(rustls::crypto::ring::default_provider().into())
|
||||||
|
.with_safe_default_protocol_versions()
|
||||||
|
.unwrap()
|
||||||
|
.dangerous()
|
||||||
|
.with_custom_certificate_verifier(StdArc::new(danger::NoCertificateVerification))
|
||||||
|
.with_no_client_auth();
|
||||||
|
config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||||
|
|
||||||
|
let connector = TlsConnector::from(StdArc::new(config));
|
||||||
|
let server_name = ServerName::try_from(sni.to_string())
|
||||||
|
.unwrap_or_else(|_| ServerName::try_from("www.microsoft.com").unwrap())
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
let tls_stream = connector.connect(server_name, tcp_stream).await
|
||||||
|
.with_context(|| "TLS handshake failed")?;
|
||||||
|
xhttp_handshake_and_loop(tls_stream, target_ip, sni, access_key).await
|
||||||
|
} else {
|
||||||
|
xhttp_handshake_and_loop(tcp_stream, target_ip, sni, access_key).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn xhttp_handshake_and_loop<S>(
|
||||||
|
mut stream: S,
|
||||||
|
target_ip: IpAddr,
|
||||||
|
sni: &str,
|
||||||
|
access_key: &[u8],
|
||||||
|
) -> Result<(mpsc::Sender<Bytes>, Arc<tokio::sync::Mutex<mpsc::Receiver<Bytes>>>)>
|
||||||
|
where
|
||||||
|
S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
|
||||||
|
{
|
||||||
// 1. Generate auth token: [8-byte timestamp BE] ++ [HMAC-SHA256]
|
// 1. Generate auth token: [8-byte timestamp BE] ++ [HMAC-SHA256]
|
||||||
let timestamp = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs();
|
let timestamp = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs();
|
||||||
let ts_bytes = timestamp.to_be_bytes();
|
let ts_bytes = timestamp.to_be_bytes();
|
||||||
|
|
@ -38,8 +127,6 @@ pub async fn connect_xhttp(
|
||||||
let http_host = if sni.is_empty() { target_ip.to_string() } else { sni.to_string() };
|
let http_host = if sni.is_empty() { target_ip.to_string() } else { sni.to_string() };
|
||||||
|
|
||||||
// 2. Send fake WebSocket upgrade — looks like a legit browser request to bypass DPI/proxies.
|
// 2. Send fake WebSocket upgrade — looks like a legit browser request to bypass DPI/proxies.
|
||||||
// The server responds with 101 Switching Protocols and we stream raw UoT frames after that.
|
|
||||||
// NOTE: always plain TCP — TLS is NOT used. Obfuscation comes from the fake WS headers.
|
|
||||||
let req = format!(
|
let req = format!(
|
||||||
"GET /stream HTTP/1.1\r\n\
|
"GET /stream HTTP/1.1\r\n\
|
||||||
Host: {}\r\n\
|
Host: {}\r\n\
|
||||||
|
|
@ -52,14 +139,14 @@ pub async fn connect_xhttp(
|
||||||
http_host, auth_token
|
http_host, auth_token
|
||||||
);
|
);
|
||||||
|
|
||||||
tcp_stream.write_all(req.as_bytes()).await?;
|
stream.write_all(req.as_bytes()).await?;
|
||||||
tcp_stream.flush().await?;
|
stream.flush().await?;
|
||||||
|
|
||||||
// 3. Read server response headers
|
// 3. Read server response headers
|
||||||
let mut buf = vec![0u8; 4096];
|
let mut buf = vec![0u8; 4096];
|
||||||
let mut header_len = 0;
|
let mut header_len = 0;
|
||||||
loop {
|
loop {
|
||||||
let n = tcp_stream.read(&mut buf[header_len..]).await?;
|
let n = stream.read(&mut buf[header_len..]).await?;
|
||||||
if n == 0 { anyhow::bail!("connection closed before handshake complete"); }
|
if n == 0 { anyhow::bail!("connection closed before handshake complete"); }
|
||||||
header_len += n;
|
header_len += n;
|
||||||
if buf[..header_len].windows(4).any(|w| w == b"\r\n\r\n") { break; }
|
if buf[..header_len].windows(4).any(|w| w == b"\r\n\r\n") { break; }
|
||||||
|
|
@ -80,7 +167,7 @@ pub async fn connect_xhttp(
|
||||||
let leftover = buf[headers_end..header_len].to_vec();
|
let leftover = buf[headers_end..header_len].to_vec();
|
||||||
|
|
||||||
// 5. Split into read/write halves and start UoT loops
|
// 5. Split into read/write halves and start UoT loops
|
||||||
let (rx, tx) = tcp_stream.into_split();
|
let (rx, tx) = tokio::io::split(stream);
|
||||||
start_uot_loops(rx, tx, leftover)
|
start_uot_loops(rx, tx, leftover)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ pub async fn run_linux_tunnel(
|
||||||
// 4. Setup commands (Using standard /1 routing trick for fail-proof overriding)
|
// 4. Setup commands (Using standard /1 routing trick for fail-proof overriding)
|
||||||
let setup_script = format!(
|
let setup_script = format!(
|
||||||
"ip tuntap add name ostp_tun mode tun || true; \
|
"ip tuntap add name ostp_tun mode tun || true; \
|
||||||
ip link set dev ostp_tun mtu 1300; \
|
ip link set dev ostp_tun mtu {}; \
|
||||||
ip addr add 10.1.0.2/24 dev ostp_tun || true; \
|
ip addr add 10.1.0.2/24 dev ostp_tun || true; \
|
||||||
ip link set dev ostp_tun up; \
|
ip link set dev ostp_tun up; \
|
||||||
ip route add {} via {} dev {}; \
|
ip route add {} via {} dev {}; \
|
||||||
|
|
|
||||||
|
|
@ -174,8 +174,9 @@ pub async fn run_wintun_tunnel(
|
||||||
tracing::info!("Applying network configuration...");
|
tracing::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=1300 store=persistent\n\
|
netsh interface ipv4 set subinterface \"{TUN_NAME}\" mtu={} store=persistent\n\
|
||||||
netsh interface ipv4 set interface name=\"{TUN_NAME}\" metric=5\n"
|
netsh interface ipv4 set interface name=\"{TUN_NAME}\" metric=5\n",
|
||||||
|
config.ostp.mtu
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(ref dns) = config.dns_server {
|
if let Some(ref dns) = config.dns_server {
|
||||||
|
|
|
||||||
|
|
@ -1,397 +0,0 @@
|
||||||
//! TURN (RFC 5766) allocation and channel binding for NAT traversal.
|
|
||||||
//!
|
|
||||||
//! Implements the minimal STUN/TURN message flow needed to allocate a relay
|
|
||||||
//! address and bind a channel to the OSTP server. All crypto (MD5, SHA-1,
|
|
||||||
//! HMAC-SHA1) is implemented inline to avoid external dependencies.
|
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use tokio::net::UdpSocket;
|
|
||||||
use tokio::time::timeout;
|
|
||||||
|
|
||||||
/// Real RFC-5766 TURN allocation with HMAC-SHA1 long-term credentials.
|
|
||||||
///
|
|
||||||
/// Flow:
|
|
||||||
/// 1. Send Allocate (unauthenticated) -> get 401 with realm + nonce
|
|
||||||
/// 2. Compute HMAC-SHA1 key = MD5(username:realm:password)
|
|
||||||
/// 3. Re-send Allocate with MESSAGE-INTEGRITY
|
|
||||||
/// 4. Extract XOR-RELAYED-ADDRESS from success response
|
|
||||||
/// 5. Send ChannelBind to bind channel 0x4000 to the OSTP server addr
|
|
||||||
///
|
|
||||||
/// Returns the relay address string like "1.2.3.4:12345".
|
|
||||||
pub async fn perform_turn_allocation(
|
|
||||||
socket: &UdpSocket,
|
|
||||||
turn_addr: &str,
|
|
||||||
username: &str,
|
|
||||||
password: &str,
|
|
||||||
ostp_server_addr: &str,
|
|
||||||
) -> Result<String> {
|
|
||||||
use std::net::ToSocketAddrs;
|
|
||||||
|
|
||||||
let turn_sock: std::net::SocketAddr = turn_addr
|
|
||||||
.to_socket_addrs()
|
|
||||||
.map_err(|e| anyhow::anyhow!("TURN DNS resolution failed: {e}"))?
|
|
||||||
.next()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("TURN addr resolved to nothing"))?;
|
|
||||||
|
|
||||||
let transaction_id = {
|
|
||||||
use rand::Rng;
|
|
||||||
let mut id = [0u8; 12];
|
|
||||||
rand::thread_rng().fill(&mut id);
|
|
||||||
id
|
|
||||||
};
|
|
||||||
|
|
||||||
// ── Step 1: unauthenticated Allocate ─────────────────────────────
|
|
||||||
// REQUESTED-TRANSPORT attr: 0x0019, value = 17 (UDP) + 3 reserved bytes
|
|
||||||
let req_transport = stun_attr(0x0019, &[17u8, 0, 0, 0]);
|
|
||||||
let alloc_req = build_stun_msg(0x0003, &transaction_id, &req_transport);
|
|
||||||
|
|
||||||
socket.send_to(&alloc_req, turn_sock).await
|
|
||||||
.map_err(|e| anyhow::anyhow!("TURN send Allocate failed: {e}"))?;
|
|
||||||
|
|
||||||
let mut buf = [0u8; 2048];
|
|
||||||
let (n, _) = timeout(Duration::from_millis(3000), socket.recv_from(&mut buf))
|
|
||||||
.await
|
|
||||||
.map_err(|_| anyhow::anyhow!("TURN Allocate response timed out"))?
|
|
||||||
.map_err(|e| anyhow::anyhow!("TURN recv failed: {e}"))?;
|
|
||||||
|
|
||||||
let resp = &buf[..n];
|
|
||||||
if resp.len() < 20 {
|
|
||||||
anyhow::bail!("TURN response too short");
|
|
||||||
}
|
|
||||||
|
|
||||||
let msg_type = u16::from_be_bytes([resp[0], resp[1]]);
|
|
||||||
|
|
||||||
// 0x0113 = Allocate Error Response
|
|
||||||
if msg_type != 0x0113 {
|
|
||||||
anyhow::bail!("Expected TURN 401 error response, got type 0x{:04x}", msg_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse realm and nonce from the error response attributes
|
|
||||||
let mut realm: Option<String> = None;
|
|
||||||
let mut nonce: Option<String> = None;
|
|
||||||
{
|
|
||||||
let mut idx = 20usize;
|
|
||||||
while idx + 4 <= n {
|
|
||||||
let atype = u16::from_be_bytes([resp[idx], resp[idx + 1]]);
|
|
||||||
let alen = u16::from_be_bytes([resp[idx + 2], resp[idx + 3]]) as usize;
|
|
||||||
idx += 4;
|
|
||||||
if idx + alen > n { break; }
|
|
||||||
let val = &resp[idx..idx + alen];
|
|
||||||
match atype {
|
|
||||||
0x0014 => realm = Some(String::from_utf8_lossy(val).to_string()), // REALM
|
|
||||||
0x0015 => nonce = Some(String::from_utf8_lossy(val).to_string()), // NONCE
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
idx += alen;
|
|
||||||
let pad = (4 - (alen % 4)) % 4;
|
|
||||||
idx += pad;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let realm = realm.ok_or_else(|| anyhow::anyhow!("TURN 401: no REALM in response"))?;
|
|
||||||
let nonce = nonce.ok_or_else(|| anyhow::anyhow!("TURN 401: no NONCE in response"))?;
|
|
||||||
|
|
||||||
// ── Step 2: Compute long-term credential key per RFC 5389 §15.4 ──
|
|
||||||
// key = MD5(username ":" realm ":" password)
|
|
||||||
let key_input = format!("{}:{}:{}", username, realm, password);
|
|
||||||
let key = md5_hash(key_input.as_bytes());
|
|
||||||
|
|
||||||
// HMAC-SHA1 of the message (MESSAGE-INTEGRITY attribute, RFC 5389 §15.4)
|
|
||||||
let mut attrs2 = Vec::new();
|
|
||||||
attrs2.extend_from_slice(&stun_attr(0x0006, username.as_bytes())); // USERNAME
|
|
||||||
attrs2.extend_from_slice(&stun_attr(0x0014, realm.as_bytes())); // REALM
|
|
||||||
attrs2.extend_from_slice(&stun_attr(0x0015, nonce.as_bytes())); // NONCE
|
|
||||||
attrs2.extend_from_slice(&req_transport); // REQUESTED-TRANSPORT
|
|
||||||
|
|
||||||
// For MESSAGE-INTEGRITY we need the full message length including the MI attr (24 bytes)
|
|
||||||
let mi_placeholder_len = attrs2.len() + 4 + 20; // +4 header, +20 HMAC-SHA1
|
|
||||||
let mut msg_for_hmac = build_stun_msg(0x0003, &transaction_id, &attrs2);
|
|
||||||
// Set length field to include the upcoming MI attr
|
|
||||||
let new_len = (mi_placeholder_len - 20) as u16; // total attrs length including MI
|
|
||||||
msg_for_hmac[2..4].copy_from_slice(&new_len.to_be_bytes());
|
|
||||||
// Append MI header (without value)
|
|
||||||
msg_for_hmac.extend_from_slice(&0x0008_u16.to_be_bytes()); // attr type
|
|
||||||
msg_for_hmac.extend_from_slice(&20_u16.to_be_bytes()); // attr len
|
|
||||||
|
|
||||||
let hmac = hmac_sha1(&key, &msg_for_hmac);
|
|
||||||
let mut final_attrs = attrs2.clone();
|
|
||||||
final_attrs.extend_from_slice(&stun_attr(0x0008, &hmac)); // MESSAGE-INTEGRITY
|
|
||||||
|
|
||||||
let alloc_req2 = build_stun_msg(0x0003, &transaction_id, &final_attrs);
|
|
||||||
|
|
||||||
socket.send_to(&alloc_req2, turn_sock).await
|
|
||||||
.map_err(|e| anyhow::anyhow!("TURN authenticated Allocate send failed: {e}"))?;
|
|
||||||
|
|
||||||
let (n2, _) = timeout(Duration::from_millis(5000), socket.recv_from(&mut buf))
|
|
||||||
.await
|
|
||||||
.map_err(|_| anyhow::anyhow!("TURN authenticated Allocate timed out"))?
|
|
||||||
.map_err(|e| anyhow::anyhow!("TURN recv2 failed: {e}"))?;
|
|
||||||
|
|
||||||
let resp2 = &buf[..n2];
|
|
||||||
if resp2.len() < 20 {
|
|
||||||
anyhow::bail!("TURN auth response too short");
|
|
||||||
}
|
|
||||||
let msg_type2 = u16::from_be_bytes([resp2[0], resp2[1]]);
|
|
||||||
// 0x0103 = Allocate Success Response
|
|
||||||
if msg_type2 != 0x0103 {
|
|
||||||
anyhow::bail!("TURN Allocate auth failed, response type 0x{:04x}", msg_type2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Step 3: Parse XOR-RELAYED-ADDRESS ────────────────────────────
|
|
||||||
let relay_addr_str = {
|
|
||||||
let mut relayed: Option<String> = None;
|
|
||||||
let mut idx = 20usize;
|
|
||||||
while idx + 4 <= n2 {
|
|
||||||
let atype = u16::from_be_bytes([resp2[idx], resp2[idx + 1]]);
|
|
||||||
let alen = u16::from_be_bytes([resp2[idx + 2], resp2[idx + 3]]) as usize;
|
|
||||||
idx += 4;
|
|
||||||
if idx + alen > n2 { break; }
|
|
||||||
let val = &resp2[idx..idx + alen];
|
|
||||||
if atype == 0x0016 && alen >= 8 { // XOR-RELAYED-ADDRESS
|
|
||||||
let x_port = u16::from_be_bytes([val[2], val[3]]) ^ 0x2112;
|
|
||||||
let x_ip = [val[4], val[5], val[6], val[7]];
|
|
||||||
let ip = std::net::Ipv4Addr::new(
|
|
||||||
x_ip[0] ^ 0x21, x_ip[1] ^ 0x12, x_ip[2] ^ 0xA4, x_ip[3] ^ 0x42,
|
|
||||||
);
|
|
||||||
relayed = Some(format!("{}:{}", ip, x_port));
|
|
||||||
}
|
|
||||||
idx += alen;
|
|
||||||
let pad = (4 - (alen % 4)) % 4;
|
|
||||||
idx += pad;
|
|
||||||
}
|
|
||||||
relayed.ok_or_else(|| anyhow::anyhow!("TURN: no XOR-RELAYED-ADDRESS in response"))?
|
|
||||||
};
|
|
||||||
|
|
||||||
// ── Step 4: ChannelBind to the OSTP server ────────────────────────
|
|
||||||
let ostp_sock: std::net::SocketAddr = ostp_server_addr
|
|
||||||
.to_socket_addrs()
|
|
||||||
.map_err(|e| anyhow::anyhow!("OSTP server DNS resolution failed: {e}"))?
|
|
||||||
.next()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("OSTP server addr resolved to nothing"))?;
|
|
||||||
|
|
||||||
let channel_number: u16 = 0x4000;
|
|
||||||
let mut peer_addr_attr = Vec::new();
|
|
||||||
peer_addr_attr.push(0u8); // reserved
|
|
||||||
peer_addr_attr.push(0x01u8); // family IPv4
|
|
||||||
peer_addr_attr.extend_from_slice(&(ostp_sock.port() ^ 0x2112).to_be_bytes()); // XOR port
|
|
||||||
if let std::net::IpAddr::V4(ipv4) = ostp_sock.ip() {
|
|
||||||
let octets = ipv4.octets();
|
|
||||||
peer_addr_attr.push(octets[0] ^ 0x21);
|
|
||||||
peer_addr_attr.push(octets[1] ^ 0x12);
|
|
||||||
peer_addr_attr.push(octets[2] ^ 0xA4);
|
|
||||||
peer_addr_attr.push(octets[3] ^ 0x42);
|
|
||||||
} else {
|
|
||||||
anyhow::bail!("TURN ChannelBind: IPv6 OSTP server not yet supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut cb_attrs = Vec::new();
|
|
||||||
// CHANNEL-NUMBER attr: 0x000C
|
|
||||||
cb_attrs.extend_from_slice(&stun_attr(0x000C, &[
|
|
||||||
(channel_number >> 8) as u8, channel_number as u8, 0, 0
|
|
||||||
]));
|
|
||||||
// XOR-PEER-ADDRESS attr: 0x0012
|
|
||||||
cb_attrs.extend_from_slice(&stun_attr(0x0012, &peer_addr_attr));
|
|
||||||
cb_attrs.extend_from_slice(&stun_attr(0x0006, username.as_bytes()));
|
|
||||||
cb_attrs.extend_from_slice(&stun_attr(0x0014, realm.as_bytes()));
|
|
||||||
cb_attrs.extend_from_slice(&stun_attr(0x0015, nonce.as_bytes()));
|
|
||||||
|
|
||||||
// Compute MESSAGE-INTEGRITY for ChannelBind too
|
|
||||||
let mi_len2 = cb_attrs.len() + 4 + 20;
|
|
||||||
let mut cb_for_hmac = build_stun_msg(0x0009, &transaction_id, &cb_attrs);
|
|
||||||
cb_for_hmac[2..4].copy_from_slice(&((mi_len2 - 20) as u16).to_be_bytes());
|
|
||||||
cb_for_hmac.extend_from_slice(&0x0008_u16.to_be_bytes());
|
|
||||||
cb_for_hmac.extend_from_slice(&20_u16.to_be_bytes());
|
|
||||||
let cb_hmac = hmac_sha1(&key, &cb_for_hmac);
|
|
||||||
cb_attrs.extend_from_slice(&stun_attr(0x0008, &cb_hmac));
|
|
||||||
|
|
||||||
let cb_req = build_stun_msg(0x0009, &transaction_id, &cb_attrs);
|
|
||||||
socket.send_to(&cb_req, turn_sock).await
|
|
||||||
.map_err(|e| anyhow::anyhow!("TURN ChannelBind send failed: {e}"))?;
|
|
||||||
|
|
||||||
let (n3, _) = timeout(Duration::from_millis(3000), socket.recv_from(&mut buf))
|
|
||||||
.await
|
|
||||||
.map_err(|_| anyhow::anyhow!("TURN ChannelBind response timed out"))?
|
|
||||||
.map_err(|e| anyhow::anyhow!("TURN ChannelBind recv failed: {e}"))?;
|
|
||||||
|
|
||||||
let resp3 = &buf[..n3];
|
|
||||||
if resp3.len() < 4 {
|
|
||||||
anyhow::bail!("TURN ChannelBind response too short");
|
|
||||||
}
|
|
||||||
let cb_resp_type = u16::from_be_bytes([resp3[0], resp3[1]]);
|
|
||||||
// 0x0109 = ChannelBind Success Response
|
|
||||||
if cb_resp_type != 0x0109 {
|
|
||||||
anyhow::bail!("TURN ChannelBind failed, response type 0x{:04x}", cb_resp_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(relay_addr_str)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── STUN message helpers ─────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
fn build_stun_msg(msg_type: u16, tx_id: &[u8; 12], attrs: &[u8]) -> Vec<u8> {
|
|
||||||
let mut msg = Vec::with_capacity(20 + attrs.len());
|
|
||||||
msg.extend_from_slice(&msg_type.to_be_bytes());
|
|
||||||
msg.extend_from_slice(&(attrs.len() as u16).to_be_bytes());
|
|
||||||
msg.extend_from_slice(&0x2112A442_u32.to_be_bytes()); // Magic Cookie
|
|
||||||
msg.extend_from_slice(tx_id);
|
|
||||||
msg.extend_from_slice(attrs);
|
|
||||||
msg
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stun_attr(attr_type: u16, value: &[u8]) -> Vec<u8> {
|
|
||||||
let mut out = Vec::new();
|
|
||||||
out.extend_from_slice(&attr_type.to_be_bytes());
|
|
||||||
out.extend_from_slice(&(value.len() as u16).to_be_bytes());
|
|
||||||
out.extend_from_slice(value);
|
|
||||||
// Pad to 4-byte boundary
|
|
||||||
let pad = (4 - (value.len() % 4)) % 4;
|
|
||||||
out.extend(std::iter::repeat_n(0u8, pad));
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Cryptographic primitives (inline, zero external deps) ────────────────────
|
|
||||||
|
|
||||||
/// Pure-Rust MD5 hash (16 bytes). Used for TURN long-term credential key derivation.
|
|
||||||
fn md5_hash(input: &[u8]) -> [u8; 16] {
|
|
||||||
// RFC 1321 MD5 constants
|
|
||||||
const S: [u32; 64] = [
|
|
||||||
7,12,17,22, 7,12,17,22, 7,12,17,22, 7,12,17,22,
|
|
||||||
5, 9,14,20, 5, 9,14,20, 5, 9,14,20, 5, 9,14,20,
|
|
||||||
4,11,16,23, 4,11,16,23, 4,11,16,23, 4,11,16,23,
|
|
||||||
6,10,15,21, 6,10,15,21, 6,10,15,21, 6,10,15,21,
|
|
||||||
];
|
|
||||||
const K: [u32; 64] = [
|
|
||||||
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a,
|
|
||||||
0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
|
|
||||||
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340,
|
|
||||||
0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
|
|
||||||
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
|
|
||||||
0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
|
|
||||||
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
|
|
||||||
0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
|
|
||||||
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
|
|
||||||
0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
|
|
||||||
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
|
|
||||||
];
|
|
||||||
|
|
||||||
let msg_len = input.len();
|
|
||||||
let bit_len = (msg_len as u64) * 8;
|
|
||||||
|
|
||||||
let mut padded = input.to_vec();
|
|
||||||
padded.push(0x80);
|
|
||||||
while padded.len() % 64 != 56 {
|
|
||||||
padded.push(0);
|
|
||||||
}
|
|
||||||
padded.extend_from_slice(&bit_len.to_le_bytes());
|
|
||||||
|
|
||||||
let mut a0: u32 = 0x67452301;
|
|
||||||
let mut b0: u32 = 0xefcdab89;
|
|
||||||
let mut c0: u32 = 0x98badcfe;
|
|
||||||
let mut d0: u32 = 0x10325476;
|
|
||||||
|
|
||||||
for chunk in padded.chunks(64) {
|
|
||||||
let mut m = [0u32; 16];
|
|
||||||
for (i, item) in m.iter_mut().enumerate() {
|
|
||||||
*item = u32::from_le_bytes([chunk[i*4], chunk[i*4+1], chunk[i*4+2], chunk[i*4+3]]);
|
|
||||||
}
|
|
||||||
let (mut a, mut b, mut c, mut d) = (a0, b0, c0, d0);
|
|
||||||
for i in 0..64usize {
|
|
||||||
let (f, g) = match i {
|
|
||||||
0..=15 => ((b & c) | (!b & d), i),
|
|
||||||
16..=31 => ((d & b) | (!d & c), (5*i + 1) % 16),
|
|
||||||
32..=47 => (b ^ c ^ d, (3*i + 5) % 16),
|
|
||||||
_ => (c ^ (b | !d), (7*i) % 16),
|
|
||||||
};
|
|
||||||
let temp = d;
|
|
||||||
d = c;
|
|
||||||
c = b;
|
|
||||||
b = b.wrapping_add((a.wrapping_add(f).wrapping_add(K[i]).wrapping_add(m[g])).rotate_left(S[i]));
|
|
||||||
a = temp;
|
|
||||||
}
|
|
||||||
a0 = a0.wrapping_add(a);
|
|
||||||
b0 = b0.wrapping_add(b);
|
|
||||||
c0 = c0.wrapping_add(c);
|
|
||||||
d0 = d0.wrapping_add(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut result = [0u8; 16];
|
|
||||||
result[0..4].copy_from_slice(&a0.to_le_bytes());
|
|
||||||
result[4..8].copy_from_slice(&b0.to_le_bytes());
|
|
||||||
result[8..12].copy_from_slice(&c0.to_le_bytes());
|
|
||||||
result[12..16].copy_from_slice(&d0.to_le_bytes());
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// HMAC-SHA1 for TURN MESSAGE-INTEGRITY (RFC 2104 + RFC 5389 §15.4).
|
|
||||||
fn hmac_sha1(key: &[u8], message: &[u8]) -> [u8; 20] {
|
|
||||||
const BLOCK_SIZE: usize = 64;
|
|
||||||
|
|
||||||
let mut k = [0u8; BLOCK_SIZE];
|
|
||||||
if key.len() > BLOCK_SIZE {
|
|
||||||
let h = sha1_hash(key);
|
|
||||||
k[..20].copy_from_slice(&h);
|
|
||||||
} else {
|
|
||||||
k[..key.len()].copy_from_slice(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut ipad = [0u8; BLOCK_SIZE];
|
|
||||||
let mut opad = [0u8; BLOCK_SIZE];
|
|
||||||
for i in 0..BLOCK_SIZE {
|
|
||||||
ipad[i] = k[i] ^ 0x36;
|
|
||||||
opad[i] = k[i] ^ 0x5C;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut inner = ipad.to_vec();
|
|
||||||
inner.extend_from_slice(message);
|
|
||||||
let inner_hash = sha1_hash(&inner);
|
|
||||||
|
|
||||||
let mut outer = opad.to_vec();
|
|
||||||
outer.extend_from_slice(&inner_hash);
|
|
||||||
sha1_hash(&outer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pure-Rust SHA-1 (RFC 3174).
|
|
||||||
fn sha1_hash(input: &[u8]) -> [u8; 20] {
|
|
||||||
let msg_len = input.len();
|
|
||||||
let bit_len = (msg_len as u64) * 8;
|
|
||||||
let mut padded = input.to_vec();
|
|
||||||
padded.push(0x80);
|
|
||||||
while padded.len() % 64 != 56 {
|
|
||||||
padded.push(0);
|
|
||||||
}
|
|
||||||
padded.extend_from_slice(&bit_len.to_be_bytes());
|
|
||||||
|
|
||||||
let mut h: [u32; 5] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
|
|
||||||
|
|
||||||
for chunk in padded.chunks(64) {
|
|
||||||
let mut w = [0u32; 80];
|
|
||||||
for i in 0..16 {
|
|
||||||
w[i] = u32::from_be_bytes([chunk[i*4], chunk[i*4+1], chunk[i*4+2], chunk[i*4+3]]);
|
|
||||||
}
|
|
||||||
for i in 16..80 {
|
|
||||||
w[i] = (w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]).rotate_left(1);
|
|
||||||
}
|
|
||||||
let (mut a, mut b, mut c, mut d, mut e) = (h[0], h[1], h[2], h[3], h[4]);
|
|
||||||
for i in 0..80usize {
|
|
||||||
let (f, k) = match i {
|
|
||||||
0..=19 => ((b & c) | (!b & d), 0x5A827999u32),
|
|
||||||
20..=39 => (b ^ c ^ d, 0x6ED9EBA1),
|
|
||||||
40..=59 => ((b & c) | (b & d) | (c & d), 0x8F1BBCDC),
|
|
||||||
_ => (b ^ c ^ d, 0xCA62C1D6),
|
|
||||||
};
|
|
||||||
let temp = a.rotate_left(5).wrapping_add(f).wrapping_add(e).wrapping_add(k).wrapping_add(w[i]);
|
|
||||||
e = d; d = c; c = b.rotate_left(30); b = a; a = temp;
|
|
||||||
}
|
|
||||||
h[0] = h[0].wrapping_add(a); h[1] = h[1].wrapping_add(b);
|
|
||||||
h[2] = h[2].wrapping_add(c); h[3] = h[3].wrapping_add(d);
|
|
||||||
h[4] = h[4].wrapping_add(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out = [0u8; 20];
|
|
||||||
for (i, &v) in h.iter().enumerate() {
|
|
||||||
out[i*4..(i+1)*4].copy_from_slice(&v.to_be_bytes());
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
@ -20,3 +20,6 @@ portable-atomic.workspace = true
|
||||||
hmac.workspace = true
|
hmac.workspace = true
|
||||||
sha2.workspace = true
|
sha2.workspace = true
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
|
rustls = "0.23"
|
||||||
|
tokio-rustls = "0.26"
|
||||||
|
rcgen = "0.13"
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,8 @@ pub struct ApiState {
|
||||||
pub api_token: String,
|
pub api_token: String,
|
||||||
/// Server address for subscription links (e.g. "example.com")
|
/// Server address for subscription links (e.g. "example.com")
|
||||||
pub server_host: String,
|
pub server_host: String,
|
||||||
/// Server listen port
|
|
||||||
pub server_port: u16,
|
pub server_port: u16,
|
||||||
|
pub reality_query: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── API configuration ────────────────────────────────────────────────────────
|
// ── API configuration ────────────────────────────────────────────────────────
|
||||||
|
|
@ -137,6 +137,7 @@ pub async fn start_api_server(
|
||||||
user_stats: Arc<RwLock<HashMap<String, Arc<UserStats>>>>,
|
user_stats: Arc<RwLock<HashMap<String, Arc<UserStats>>>>,
|
||||||
server_host: String,
|
server_host: String,
|
||||||
server_port: u16,
|
server_port: u16,
|
||||||
|
reality_query: String,
|
||||||
) {
|
) {
|
||||||
let state = ApiState {
|
let state = ApiState {
|
||||||
access_keys,
|
access_keys,
|
||||||
|
|
@ -145,6 +146,7 @@ pub async fn start_api_server(
|
||||||
api_token: config.token.clone(),
|
api_token: config.token.clone(),
|
||||||
server_host,
|
server_host,
|
||||||
server_port,
|
server_port,
|
||||||
|
reality_query,
|
||||||
};
|
};
|
||||||
|
|
||||||
let app = create_api_router(state);
|
let app = create_api_router(state);
|
||||||
|
|
@ -416,7 +418,7 @@ async fn handle_subscribe(
|
||||||
|
|
||||||
// If client requests plain text, return ostp:// share link
|
// If client requests plain text, return ostp:// share link
|
||||||
if accept.contains("text/plain") {
|
if accept.contains("text/plain") {
|
||||||
let link = format!("ostp://{}@{}:{}", key, state.server_host, state.server_port);
|
let link = format!("ostp://{}@{}:{}{}", key, state.server_host, state.server_port, state.reality_query);
|
||||||
return (StatusCode::OK, Json(serde_json::json!({
|
return (StatusCode::OK, Json(serde_json::json!({
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"data": link
|
"data": link
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,15 @@ pub use outbound::{OutboundAction, OutboundConfig, OutboundRule};
|
||||||
pub use api::ApiConfig;
|
pub use api::ApiConfig;
|
||||||
pub use fallback::FallbackConfig;
|
pub use fallback::FallbackConfig;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RealityServerConfig {
|
||||||
|
pub dest: String,
|
||||||
|
pub private_key: String,
|
||||||
|
pub pbk: String,
|
||||||
|
pub sid: String,
|
||||||
|
pub sni_list: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
// ── Internal event types ─────────────────────────────────────────────────────
|
// ── Internal event types ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -59,6 +68,8 @@ pub async fn run_server(
|
||||||
api_config: Option<ApiConfig>,
|
api_config: Option<ApiConfig>,
|
||||||
fallback_config: Option<FallbackConfig>,
|
fallback_config: Option<FallbackConfig>,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
|
reality_query: Option<String>,
|
||||||
|
reality_config: Option<RealityServerConfig>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut keys_map = HashMap::new();
|
let mut keys_map = HashMap::new();
|
||||||
for key in access_keys {
|
for key in access_keys {
|
||||||
|
|
@ -161,8 +172,9 @@ pub async fn run_server(
|
||||||
let parts: Vec<&str> = primary.rsplitn(2, ':').collect();
|
let parts: Vec<&str> = primary.rsplitn(2, ':').collect();
|
||||||
let server_port: u16 = parts.first().and_then(|p| p.parse().ok()).unwrap_or(50000);
|
let server_port: u16 = parts.first().and_then(|p| p.parse().ok()).unwrap_or(50000);
|
||||||
let server_host = parts.get(1).unwrap_or(&"0.0.0.0").to_string();
|
let server_host = parts.get(1).unwrap_or(&"0.0.0.0").to_string();
|
||||||
|
let rq = reality_query.clone().unwrap_or_default();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
api::start_api_server(api_cfg, api_keys, api_stats, server_host, server_port).await;
|
api::start_api_server(api_cfg, api_keys, api_stats, server_host, server_port, rq).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -213,8 +225,25 @@ pub async fn run_server(
|
||||||
let key_count = shared_keys.read().unwrap().len();
|
let key_count = shared_keys.read().unwrap().len();
|
||||||
tracing::info!(listeners = bind_addrs.len(), keys = key_count, "server started");
|
tracing::info!(listeners = bind_addrs.len(), keys = key_count, "server started");
|
||||||
tracing::info!("ARQ config: max_reorder=16384, reorder_buf=8192, sent_history=32768, rto=100ms");
|
tracing::info!("ARQ config: max_reorder=16384, reorder_buf=8192, sent_history=32768, rto=100ms");
|
||||||
|
let tls_config = if let Some(rc) = reality_config {
|
||||||
|
let subject_alt_names = rc.sni_list.clone();
|
||||||
|
let cert = rcgen::generate_simple_self_signed(subject_alt_names)?;
|
||||||
|
let cert_der = cert.cert.der().to_vec();
|
||||||
|
let priv_key = cert.key_pair.serialize_der();
|
||||||
|
|
||||||
|
let server_config = rustls::ServerConfig::builder()
|
||||||
|
.with_no_client_auth()
|
||||||
|
.with_single_cert(
|
||||||
|
vec![rustls::pki_types::CertificateDer::from(cert_der)],
|
||||||
|
rustls::pki_types::PrivatePkcs8KeyDer::from(priv_key).into(),
|
||||||
|
)?;
|
||||||
|
Some(std::sync::Arc::new(server_config))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
res = run_server_loop(bind_addrs.clone(), primary_socket, sockets, dispatcher, ui_cmd_rx, ui_event_tx, shared_keys, outbound, debug) => {
|
res = run_server_loop(bind_addrs.clone(), primary_socket, sockets, dispatcher, ui_cmd_rx, ui_event_tx, shared_keys, outbound, debug, tls_config) => {
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
tracing::error!("Server error: {e}");
|
tracing::error!("Server error: {e}");
|
||||||
}
|
}
|
||||||
|
|
@ -239,6 +268,7 @@ async fn run_server_loop(
|
||||||
shared_keys: std::sync::Arc<std::sync::RwLock<HashMap<String, ()>>>,
|
shared_keys: std::sync::Arc<std::sync::RwLock<HashMap<String, ()>>>,
|
||||||
outbound: Option<OutboundConfig>,
|
outbound: Option<OutboundConfig>,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
|
tls_config: Option<std::sync::Arc<rustls::ServerConfig>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut remotes: HashMap<(u32, u16), RemoteState> = HashMap::new();
|
let mut remotes: HashMap<(u32, u16), RemoteState> = HashMap::new();
|
||||||
let (stream_tx, mut stream_rx) = mpsc::unbounded_channel::<(u32, u16, Vec<u8>)>();
|
let (stream_tx, mut stream_rx) = mpsc::unbounded_channel::<(u32, u16, Vec<u8>)>();
|
||||||
|
|
@ -257,7 +287,11 @@ async fn run_server_loop(
|
||||||
loop {
|
loop {
|
||||||
match sock_clone.recv_from(&mut buf).await {
|
match sock_clone.recv_from(&mut buf).await {
|
||||||
Ok((size, peer)) => {
|
Ok((size, peer)) => {
|
||||||
let packet = Bytes::copy_from_slice(&buf[..size]);
|
let packet = if size >= 12 && buf[0] == 0x80 {
|
||||||
|
Bytes::copy_from_slice(&buf[12..size])
|
||||||
|
} else {
|
||||||
|
Bytes::copy_from_slice(&buf[..size])
|
||||||
|
};
|
||||||
if tx.send((packet, peer)).await.is_err() {
|
if tx.send((packet, peer)).await.is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -275,6 +309,7 @@ async fn run_server_loop(
|
||||||
let shared_keys_clone = shared_keys.clone();
|
let shared_keys_clone = shared_keys.clone();
|
||||||
let udp_tx_clone = udp_tx.clone();
|
let udp_tx_clone = udp_tx.clone();
|
||||||
|
|
||||||
|
let tls_cfg = tls_config.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Ok(listener) = tokio::net::TcpListener::bind(&addr).await {
|
if let Ok(listener) = tokio::net::TcpListener::bind(&addr).await {
|
||||||
tracing::info!("TCP (UoT) listener bound to {}", addr);
|
tracing::info!("TCP (UoT) listener bound to {}", addr);
|
||||||
|
|
@ -312,10 +347,25 @@ async fn run_server_loop(
|
||||||
let tm = tcp_map_clone.clone();
|
let tm = tcp_map_clone.clone();
|
||||||
let keys = shared_keys_clone.clone();
|
let keys = shared_keys_clone.clone();
|
||||||
let tx = udp_tx_clone.clone();
|
let tx = udp_tx_clone.clone();
|
||||||
|
let tls = tls_cfg.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
if let Some(cfg) = tls {
|
||||||
|
let acceptor = tokio_rustls::TlsAcceptor::from(cfg);
|
||||||
|
match acceptor.accept(stream).await {
|
||||||
|
Ok(tls_stream) => {
|
||||||
|
if let Err(e) = crate::transport::uot::handle_tcp_connection(tls_stream, peer_addr, keys, tx, tm).await {
|
||||||
|
tracing::warn!("UoT TLS connection from {} closed: {}", peer_addr, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("UoT TLS handshake from {} failed: {}", peer_addr, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if let Err(e) = crate::transport::uot::handle_tcp_connection(stream, peer_addr, keys, tx, tm).await {
|
if let Err(e) = crate::transport::uot::handle_tcp_connection(stream, peer_addr, keys, tx, tm).await {
|
||||||
tracing::warn!("UoT connection from {} closed: {}", peer_addr, e);
|
tracing::warn!("UoT connection from {} closed: {}", peer_addr, e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -391,7 +441,10 @@ async fn run_server_loop(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !sent_tcp {
|
if !sent_tcp {
|
||||||
let _ = socket.send_to(&resp, peer_addr).await?;
|
let mut out = bytes::BytesMut::with_capacity(12 + resp.len());
|
||||||
|
out.extend_from_slice(&[0x80, 0x60, 0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44]);
|
||||||
|
out.extend_from_slice(&resp);
|
||||||
|
let _ = socket.send_to(&out.freeze(), peer_addr).await?;
|
||||||
}
|
}
|
||||||
let _ = ui_event_tx.send(UiEvent::Tx { peer: peer_ip, bytes: resp_len });
|
let _ = ui_event_tx.send(UiEvent::Tx { peer: peer_ip, bytes: resp_len });
|
||||||
}
|
}
|
||||||
|
|
@ -475,7 +528,10 @@ async fn run_server_loop(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !sent_tcp {
|
if !sent_tcp {
|
||||||
let _ = socket.send_to(&frame, peer_addr).await?;
|
let mut out = bytes::BytesMut::with_capacity(12 + frame.len());
|
||||||
|
out.extend_from_slice(&[0x80, 0x60, 0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44]);
|
||||||
|
out.extend_from_slice(&frame);
|
||||||
|
let _ = socket.send_to(&out.freeze(), peer_addr).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for sid in dropped_sessions {
|
for sid in dropped_sessions {
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,16 @@ use tokio::net::TcpStream;
|
||||||
use tokio::sync::{mpsc, RwLock};
|
use tokio::sync::{mpsc, RwLock};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
pub async fn handle_tcp_connection(
|
pub async fn handle_tcp_connection<S>(
|
||||||
mut stream: TcpStream,
|
mut stream: S,
|
||||||
peer_addr: SocketAddr,
|
peer_addr: SocketAddr,
|
||||||
shared_keys: Arc<StdRwLock<HashMap<String, ()>>>,
|
shared_keys: Arc<StdRwLock<HashMap<String, ()>>>,
|
||||||
udp_tx: mpsc::Sender<(Bytes, SocketAddr)>,
|
udp_tx: mpsc::Sender<(Bytes, SocketAddr)>,
|
||||||
tcp_map: Arc<RwLock<HashMap<SocketAddr, mpsc::Sender<Bytes>>>>,
|
tcp_map: Arc<RwLock<HashMap<SocketAddr, mpsc::Sender<Bytes>>>>,
|
||||||
) -> Result<()> {
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
|
||||||
|
{
|
||||||
// 1. Read HTTP Handshake
|
// 1. Read HTTP Handshake
|
||||||
let mut buf = [0u8; 4096];
|
let mut buf = [0u8; 4096];
|
||||||
let mut header_len = 0;
|
let mut header_len = 0;
|
||||||
|
|
@ -123,7 +126,7 @@ pub async fn handle_tcp_connection(
|
||||||
let leftover = &buf[headers_end..header_len];
|
let leftover = &buf[headers_end..header_len];
|
||||||
|
|
||||||
// Process streams
|
// Process streams
|
||||||
let (mut read_half, mut write_half) = stream.into_split();
|
let (mut read_half, mut write_half) = tokio::io::split(stream);
|
||||||
|
|
||||||
// Spawn writer task
|
// Spawn writer task
|
||||||
let peer_clone = peer_addr;
|
let peer_clone = peer_addr;
|
||||||
|
|
@ -181,7 +184,10 @@ pub async fn handle_tcp_connection(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_404(stream: &mut TcpStream) -> Result<()> {
|
async fn send_404<S>(stream: &mut S) -> Result<()>
|
||||||
|
where
|
||||||
|
S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
|
||||||
|
{
|
||||||
let body = "Not Found";
|
let body = "Not Found";
|
||||||
let resp = format!(
|
let resp = format!(
|
||||||
"HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
|
"HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
|
||||||
|
|
|
||||||
141
ostp/src/main.rs
141
ostp/src/main.rs
|
|
@ -55,20 +55,48 @@ fn parse_ostp_link(link: &str) -> Result<ClientConfig> {
|
||||||
let host = parsed.host_str().ok_or_else(|| anyhow!("Missing host in share link"))?;
|
let host = parsed.host_str().ok_or_else(|| anyhow!("Missing host in share link"))?;
|
||||||
let port = parsed.port().ok_or_else(|| anyhow!("Missing port in share link"))?;
|
let port = parsed.port().ok_or_else(|| anyhow!("Missing port in share link"))?;
|
||||||
let server = format!("{host}:{port}");
|
let server = format!("{host}:{port}");
|
||||||
|
let mut sni = String::new();
|
||||||
|
let mut fp = String::new();
|
||||||
|
let mut pbk = String::new();
|
||||||
|
let mut sid = String::new();
|
||||||
|
let mut spx = String::new();
|
||||||
|
let mut transport_mode = String::from("udp");
|
||||||
|
|
||||||
|
for (k, v) in parsed.query_pairs() {
|
||||||
|
match k.as_ref() {
|
||||||
|
"sni" => sni = v.into_owned(),
|
||||||
|
"fp" => fp = v.into_owned(),
|
||||||
|
"pbk" => pbk = v.into_owned(),
|
||||||
|
"sid" => sid = v.into_owned(),
|
||||||
|
"spx" => spx = v.into_owned(),
|
||||||
|
"type" => transport_mode = v.into_owned(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(ClientConfig {
|
Ok(ClientConfig {
|
||||||
server,
|
server,
|
||||||
access_key,
|
access_key,
|
||||||
mtu: None,
|
mtu: None,
|
||||||
transport: None,
|
transport: Some(TransportConfigRaw {
|
||||||
socks5_bind: Some("127.0.0.1:1088".to_string()), // Fallback to standard SOCKS5 port
|
mode: Some(transport_mode),
|
||||||
|
stealth_sni: Some(sni.clone()),
|
||||||
|
stealth_port: Some(443),
|
||||||
|
}),
|
||||||
|
socks5_bind: Some("127.0.0.1:1088".to_string()),
|
||||||
tun: Some(TunConfig {
|
tun: Some(TunConfig {
|
||||||
enable: false, // Default to proxy, configurable via settings GUI
|
enable: false,
|
||||||
wintun_path: Some("./wintun.dll".to_string()),
|
wintun_path: Some("./wintun.dll".to_string()),
|
||||||
ipv4_address: Some("10.1.0.2/24".to_string()),
|
ipv4_address: Some("10.1.0.2/24".to_string()),
|
||||||
dns: None,
|
dns: None,
|
||||||
}),
|
}),
|
||||||
turn: None,
|
reality: Some(RealityConfigRaw {
|
||||||
|
sni,
|
||||||
|
fp,
|
||||||
|
pbk,
|
||||||
|
sid,
|
||||||
|
spx,
|
||||||
|
}),
|
||||||
debug: Some(false),
|
debug: Some(false),
|
||||||
exclude: None,
|
exclude: None,
|
||||||
mux: None,
|
mux: None,
|
||||||
|
|
@ -141,7 +169,7 @@ impl UnifiedConfig {
|
||||||
struct ServerConfig {
|
struct ServerConfig {
|
||||||
listen: ListenConfig,
|
listen: ListenConfig,
|
||||||
access_keys: Vec<String>,
|
access_keys: Vec<String>,
|
||||||
turn_server: Option<String>,
|
reality: Option<RealityServerConfigRaw>,
|
||||||
debug: Option<bool>,
|
debug: Option<bool>,
|
||||||
outbound: Option<OutboundConfig>,
|
outbound: Option<OutboundConfig>,
|
||||||
api: Option<ApiConfig>,
|
api: Option<ApiConfig>,
|
||||||
|
|
@ -193,7 +221,7 @@ struct ClientConfig {
|
||||||
mtu: Option<usize>,
|
mtu: Option<usize>,
|
||||||
socks5_bind: Option<String>,
|
socks5_bind: Option<String>,
|
||||||
tun: Option<TunConfig>,
|
tun: Option<TunConfig>,
|
||||||
turn: Option<TurnConfigRaw>,
|
reality: Option<RealityConfigRaw>,
|
||||||
debug: Option<bool>,
|
debug: Option<bool>,
|
||||||
exclude: Option<ExcludeConfig>,
|
exclude: Option<ExcludeConfig>,
|
||||||
mux: Option<MuxConfig>,
|
mux: Option<MuxConfig>,
|
||||||
|
|
@ -215,12 +243,24 @@ struct TunConfig {
|
||||||
dns: Option<String>,
|
dns: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
struct TurnConfigRaw {
|
struct RealityConfigRaw {
|
||||||
|
sni: String,
|
||||||
|
fp: String,
|
||||||
|
pbk: String,
|
||||||
|
sid: String,
|
||||||
|
spx: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
struct RealityServerConfigRaw {
|
||||||
|
#[serde(default)]
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
server_addr: String,
|
dest: String,
|
||||||
username: Option<String>,
|
private_key: String,
|
||||||
access_key: Option<String>,
|
pbk: String,
|
||||||
|
sid: String,
|
||||||
|
sni_list: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
|
@ -469,6 +509,16 @@ async fn run_app() -> Result<()> {
|
||||||
// Target web server (e.g., local nginx or caddy)
|
// Target web server (e.g., local nginx or caddy)
|
||||||
"target": "127.0.0.1:8080"
|
"target": "127.0.0.1:8080"
|
||||||
}},
|
}},
|
||||||
|
|
||||||
|
// Reality (XTLS) / UoT Masquerade parameters
|
||||||
|
"reality": {{
|
||||||
|
"enabled": false,
|
||||||
|
"dest": "www.microsoft.com:443",
|
||||||
|
"private_key": "",
|
||||||
|
"pbk": "",
|
||||||
|
"sid": "",
|
||||||
|
"sni_list": ["www.microsoft.com"]
|
||||||
|
}},
|
||||||
"debug": false
|
"debug": false
|
||||||
}}"#, key)
|
}}"#, key)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -501,18 +551,19 @@ async fn run_app() -> Result<()> {
|
||||||
"processes": []
|
"processes": []
|
||||||
}},
|
}},
|
||||||
|
|
||||||
// STUN/TURN server settings to bypass UDP blocks by mimicking WebRTC call traffic
|
// Reality (XTLS) / WebRTC Masquerade parameters
|
||||||
"turn": {{
|
"reality": {{
|
||||||
"enabled": false,
|
"dest": "www.microsoft.com:443",
|
||||||
"server_addr": "127.0.0.1:3478",
|
"private_key": "",
|
||||||
"username": "ostpuser",
|
"pbk": "",
|
||||||
"access_key": "ostppassword"
|
"sid": "",
|
||||||
|
"sni_list": ["www.microsoft.com"]
|
||||||
}},
|
}},
|
||||||
|
|
||||||
// Transport Mode: "udp" (default) or "uot" (xHTTP Stealth / UDP over TCP)
|
// Transport Mode: "udp" (default WebRTC masquerade) or "uot" (TCP XTLS-Reality)
|
||||||
"transport": {{
|
"transport": {{
|
||||||
"mode": "udp",
|
"mode": "udp",
|
||||||
"stealth_sni": "vk.com",
|
"stealth_sni": "www.microsoft.com",
|
||||||
"stealth_port": 443
|
"stealth_port": 443
|
||||||
}},
|
}},
|
||||||
|
|
||||||
|
|
@ -529,11 +580,15 @@ async fn run_app() -> Result<()> {
|
||||||
if is_server {
|
if is_server {
|
||||||
let mut stripped = json_comments::StripComments::new(content.as_bytes());
|
let mut stripped = json_comments::StripComments::new(content.as_bytes());
|
||||||
if let Ok(config) = serde_json::from_reader::<_, UnifiedConfig>(&mut stripped) {
|
if let Ok(config) = serde_json::from_reader::<_, UnifiedConfig>(&mut stripped) {
|
||||||
if let AppMode::Server(s) = config.mode {
|
if let AppMode::Server(s) = &config.mode {
|
||||||
let key = &s.access_keys[0];
|
let key = &s.access_keys[0];
|
||||||
let host = get_or_ask_public_ip(&args.config);
|
let host = get_or_ask_public_ip(&args.config);
|
||||||
|
let mut link = format!("ostp://{}@{}:50000", key, host);
|
||||||
|
if let Some(r) = &s.reality {
|
||||||
|
link = format!("{}?security=reality&sni={}&pbk={}&sid={}&type=udp", link, r.sni_list.first().unwrap_or(&String::new()), r.pbk, r.sid);
|
||||||
|
}
|
||||||
println!("\n Share link for client distribution:");
|
println!("\n Share link for client distribution:");
|
||||||
println!(" ostp://{}@{}:50000", key, host);
|
println!(" {}", link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -575,7 +630,11 @@ async fn run_app() -> Result<()> {
|
||||||
|
|
||||||
println!("\n Client share links from {:?}:", args.config);
|
println!("\n Client share links from {:?}:", args.config);
|
||||||
for (idx, key) in server_cfg.access_keys.iter().enumerate() {
|
for (idx, key) in server_cfg.access_keys.iter().enumerate() {
|
||||||
println!(" [{}] ostp://{}@{}:{}", idx + 1, key, host, port);
|
let mut link = format!("ostp://{}@{}:{}", key, host, port);
|
||||||
|
if let Some(r) = &server_cfg.reality {
|
||||||
|
link = format!("{}?security=reality&sni={}&pbk={}&sid={}&type=udp", link, r.sni_list.first().unwrap_or(&String::new()), r.pbk, r.sid);
|
||||||
|
}
|
||||||
|
println!(" [{}] {}", idx + 1, link);
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
@ -589,8 +648,10 @@ async fn run_app() -> Result<()> {
|
||||||
AppMode::Server(server_cfg) => {
|
AppMode::Server(server_cfg) => {
|
||||||
let listen_addrs = server_cfg.listen.addresses();
|
let listen_addrs = server_cfg.listen.addresses();
|
||||||
println!("[ostp] Starting server on {:?}", listen_addrs);
|
println!("[ostp] Starting server on {:?}", listen_addrs);
|
||||||
if let Some(turn) = server_cfg.turn_server {
|
if let Some(ref reality) = server_cfg.reality {
|
||||||
println!("[ostp] TURN relay enabled: {}", turn);
|
if reality.enabled {
|
||||||
|
println!("[ostp] Reality mode enabled (dest: {})", reality.dest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let debug = server_cfg.debug.unwrap_or(false);
|
let debug = server_cfg.debug.unwrap_or(false);
|
||||||
let outbound = server_cfg.outbound.map(|o| ostp_server::OutboundConfig {
|
let outbound = server_cfg.outbound.map(|o| ostp_server::OutboundConfig {
|
||||||
|
|
@ -619,8 +680,22 @@ async fn run_app() -> Result<()> {
|
||||||
listen: f.listen.unwrap_or_else(|| "0.0.0.0:443".to_string()),
|
listen: f.listen.unwrap_or_else(|| "0.0.0.0:443".to_string()),
|
||||||
target: f.target.unwrap_or_else(|| "127.0.0.1:8080".to_string()),
|
target: f.target.unwrap_or_else(|| "127.0.0.1:8080".to_string()),
|
||||||
});
|
});
|
||||||
|
let mut rq = None;
|
||||||
|
let mut rc = None;
|
||||||
|
if let Some(r) = server_cfg.reality {
|
||||||
|
if r.enabled {
|
||||||
|
rq = Some(format!("?security=reality&sni={}&pbk={}&sid={}&type=udp", r.sni_list.first().unwrap_or(&String::new()), r.pbk, r.sid));
|
||||||
|
rc = Some(ostp_server::RealityServerConfig {
|
||||||
|
sni_list: r.sni_list.clone(),
|
||||||
|
dest: r.dest,
|
||||||
|
private_key: r.private_key,
|
||||||
|
pbk: r.pbk,
|
||||||
|
sid: r.sid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
// Pass all listen addresses for multi-listener support
|
// Pass all listen addresses for multi-listener support
|
||||||
ostp_server::run_server(listen_addrs, server_cfg.access_keys, outbound, api_config, fallback_config, debug).await?;
|
ostp_server::run_server(listen_addrs, server_cfg.access_keys, outbound, api_config, fallback_config, debug, rq, rc).await?;
|
||||||
}
|
}
|
||||||
AppMode::Client(client_cfg) => {
|
AppMode::Client(client_cfg) => {
|
||||||
run_client_directly(client_cfg).await?;
|
run_client_directly(client_cfg).await?;
|
||||||
|
|
@ -635,7 +710,7 @@ async fn run_client_directly(client_cfg: ClientConfig) -> Result<()> {
|
||||||
let is_tun_enabled = client_cfg.tun.as_ref().map(|t| t.enable).unwrap_or(false);
|
let is_tun_enabled = client_cfg.tun.as_ref().map(|t| t.enable).unwrap_or(false);
|
||||||
let mode_str = if is_tun_enabled { "tun" } else { "proxy" };
|
let mode_str = if is_tun_enabled { "tun" } else { "proxy" };
|
||||||
println!("[ostp] Starting client (mode={}, server={})", mode_str, client_cfg.server);
|
println!("[ostp] Starting client (mode={}, server={})", mode_str, client_cfg.server);
|
||||||
let turn_cfg = client_cfg.turn.as_ref();
|
let reality_cfg = client_cfg.reality.as_ref();
|
||||||
let client_conf = ostp_client::config::ClientConfig {
|
let client_conf = ostp_client::config::ClientConfig {
|
||||||
mode: if is_tun_enabled { "tun".to_string() } else { "proxy".to_string() },
|
mode: if is_tun_enabled { "tun".to_string() } else { "proxy".to_string() },
|
||||||
debug: client_cfg.debug.unwrap_or(false),
|
debug: client_cfg.debug.unwrap_or(false),
|
||||||
|
|
@ -644,18 +719,20 @@ async fn run_client_directly(client_cfg: ClientConfig) -> Result<()> {
|
||||||
local_bind_addr: "0.0.0.0:0".to_string(),
|
local_bind_addr: "0.0.0.0:0".to_string(),
|
||||||
access_key: client_cfg.access_key.clone(),
|
access_key: client_cfg.access_key.clone(),
|
||||||
handshake_timeout_ms: 5000,
|
handshake_timeout_ms: 5000,
|
||||||
io_timeout_ms: 5000,
|
io_timeout_ms: 2500,
|
||||||
mtu: client_cfg.mtu.unwrap_or(1350),
|
mtu: client_cfg.mtu.unwrap_or(1350),
|
||||||
|
keepalive_interval_sec: 5,
|
||||||
},
|
},
|
||||||
local_proxy: ostp_client::config::LocalProxyConfig {
|
local_proxy: ostp_client::config::LocalProxyConfig {
|
||||||
bind_addr: client_cfg.socks5_bind.clone().unwrap_or_else(|| "127.0.0.1:1088".to_string()),
|
bind_addr: client_cfg.socks5_bind.clone().unwrap_or_else(|| "127.0.0.1:1088".to_string()),
|
||||||
connect_timeout_ms: 5000,
|
connect_timeout_ms: 5000,
|
||||||
},
|
},
|
||||||
turn: ostp_client::config::TurnConfig {
|
reality: ostp_client::config::RealityConfig {
|
||||||
enabled: turn_cfg.map(|t| t.enabled).unwrap_or(false),
|
sni: reality_cfg.map(|t| t.sni.clone()).unwrap_or_default(),
|
||||||
server_addr: turn_cfg.map(|t| t.server_addr.clone()).unwrap_or_default(),
|
fp: reality_cfg.map(|t| t.fp.clone()).unwrap_or_default(),
|
||||||
username: turn_cfg.and_then(|t| t.username.clone()).unwrap_or_default(),
|
pbk: reality_cfg.map(|t| t.pbk.clone()).unwrap_or_default(),
|
||||||
access_key: turn_cfg.and_then(|t| t.access_key.clone()).unwrap_or_default(),
|
sid: reality_cfg.map(|t| t.sid.clone()).unwrap_or_default(),
|
||||||
|
spx: reality_cfg.map(|t| t.spx.clone()).unwrap_or_default(),
|
||||||
},
|
},
|
||||||
exclusions: ostp_client::config::ExclusionConfig {
|
exclusions: ostp_client::config::ExclusionConfig {
|
||||||
domains: client_cfg.exclude.as_ref().and_then(|e| e.domains.clone()).unwrap_or_default(),
|
domains: client_cfg.exclude.as_ref().and_then(|e| e.domains.clone()).unwrap_or_default(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue