mirror of https://github.com/ospab/ostp.git
feat: relay node system with HMAC pre-validation and key sync from upstream API
This commit is contained in:
parent
2228faa550
commit
d79b6f2384
|
|
@ -239,6 +239,12 @@ version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg_aliases"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chacha20"
|
name = "chacha20"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
|
|
@ -365,7 +371,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -528,8 +534,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
"wasip2",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -620,6 +642,23 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"want",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-rustls"
|
||||||
|
version = "0.27.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
|
||||||
|
dependencies = [
|
||||||
|
"http",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
|
"rustls",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
"tower-service",
|
||||||
|
"webpki-roots 1.0.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -628,13 +667,21 @@ version = "0.1.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"ipnet",
|
||||||
|
"libc",
|
||||||
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -773,6 +820,12 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ipnet"
|
||||||
|
version = "2.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.2"
|
version = "1.70.2"
|
||||||
|
|
@ -796,7 +849,7 @@ dependencies = [
|
||||||
"combine",
|
"combine",
|
||||||
"jni-sys 0.3.1",
|
"jni-sys 0.3.1",
|
||||||
"log",
|
"log",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
@ -871,6 +924,12 @@ version = "0.4.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lru-slab"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -961,7 +1020,7 @@ dependencies = [
|
||||||
"json_comments",
|
"json_comments",
|
||||||
"ostp-client",
|
"ostp-client",
|
||||||
"ostp-server",
|
"ostp-server",
|
||||||
"rand",
|
"rand 0.8.5",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"snow",
|
"snow",
|
||||||
|
|
@ -984,7 +1043,7 @@ dependencies = [
|
||||||
"json_comments",
|
"json_comments",
|
||||||
"ostp-core",
|
"ostp-core",
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
"rand",
|
"rand 0.8.5",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -1005,10 +1064,10 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"hmac",
|
"hmac",
|
||||||
"rand",
|
"rand 0.8.5",
|
||||||
"sha2",
|
"sha2",
|
||||||
"snow",
|
"snow",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1036,11 +1095,13 @@ dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"base64",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
"hmac",
|
"hmac",
|
||||||
"ostp-core",
|
"ostp-core",
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
"rand",
|
"rand 0.8.5",
|
||||||
"rcgen",
|
"rcgen",
|
||||||
|
"reqwest",
|
||||||
"rustls",
|
"rustls",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
@ -1150,6 +1211,61 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quinn"
|
||||||
|
version = "0.11.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"cfg_aliases",
|
||||||
|
"pin-project-lite",
|
||||||
|
"quinn-proto",
|
||||||
|
"quinn-udp",
|
||||||
|
"rustc-hash",
|
||||||
|
"rustls",
|
||||||
|
"socket2",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quinn-proto"
|
||||||
|
version = "0.11.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
"lru-slab",
|
||||||
|
"rand 0.9.4",
|
||||||
|
"ring",
|
||||||
|
"rustc-hash",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"slab",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"tinyvec",
|
||||||
|
"tracing",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quinn-udp"
|
||||||
|
version = "0.5.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"socket2",
|
||||||
|
"tracing",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.45"
|
version = "1.0.45"
|
||||||
|
|
@ -1159,6 +1275,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"
|
||||||
|
|
@ -1166,8 +1288,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha",
|
"rand_chacha 0.3.1",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha 0.9.0",
|
||||||
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1177,7 +1309,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1186,7 +1328,16 @@ 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 = "rand_core"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1219,6 +1370,44 @@ version = "0.8.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reqwest"
|
||||||
|
version = "0.12.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper",
|
||||||
|
"hyper-rustls",
|
||||||
|
"hyper-util",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"quinn",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
|
"tower",
|
||||||
|
"tower-http",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"webpki-roots 1.0.7",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.14"
|
version = "0.17.14"
|
||||||
|
|
@ -1227,12 +1416,18 @@ 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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "2.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
|
@ -1263,6 +1458,7 @@ version = "1.14.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
|
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"web-time",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1428,7 +1624,7 @@ dependencies = [
|
||||||
"blake2",
|
"blake2",
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"curve25519-dalek",
|
"curve25519-dalek",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"sha2",
|
"sha2",
|
||||||
"subtle",
|
"subtle",
|
||||||
|
|
@ -1478,6 +1674,9 @@ name = "sync_wrapper"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "synstructure"
|
name = "synstructure"
|
||||||
|
|
@ -1496,7 +1695,16 @@ version = "1.0.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1510,6 +1718,17 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.9"
|
version = "1.1.9"
|
||||||
|
|
@ -1548,6 +1767,21 @@ dependencies = [
|
||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.52.0"
|
version = "1.52.0"
|
||||||
|
|
@ -1618,10 +1852,14 @@ checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
|
"http-body",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"tower",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1698,6 +1936,12 @@ dependencies = [
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "try-lock"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.19.0"
|
version = "1.19.0"
|
||||||
|
|
@ -1772,12 +2016,30 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "want"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
||||||
|
dependencies = [
|
||||||
|
"try-lock",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.1+wasi-snapshot-preview1"
|
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"
|
||||||
|
|
@ -1791,6 +2053,16 @@ dependencies = [
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-futures"
|
||||||
|
version = "0.4.71"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.121"
|
version = "0.2.121"
|
||||||
|
|
@ -1823,6 +2095,26 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.98"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-time"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "0.26.11"
|
version = "0.26.11"
|
||||||
|
|
@ -2066,6 +2358,12 @@ 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"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
// OSTP Relay Node Configuration
|
||||||
|
// Этот узел принимает соединения от клиентов, проверяет их аутентификацию
|
||||||
|
// и пробрасывает трафик к целевому серверу.
|
||||||
|
//
|
||||||
|
// Архитектура цепочки:
|
||||||
|
// Клиент -> [Этот Relay] -> [Relay 2] -> ... -> [Target Server]
|
||||||
|
//
|
||||||
|
// Ключи синхронизируются напрямую с API Target Server каждые N секунд.
|
||||||
|
// Relay не знает содержимого трафика — только проверяет HMAC-подпись.
|
||||||
|
|
||||||
|
"mode": "relay",
|
||||||
|
|
||||||
|
// Адрес, на котором relay слушает входящие соединения от клиентов
|
||||||
|
"listen": "0.0.0.0:50000",
|
||||||
|
|
||||||
|
// Адрес следующего узла в цепочке (другой relay или конечный сервер) — TCP (UoT)
|
||||||
|
"upstream_tcp": "TARGET_SERVER_IP:50000",
|
||||||
|
|
||||||
|
// Адрес следующего узла в цепочке — UDP
|
||||||
|
"upstream_udp": "TARGET_SERVER_IP:50000",
|
||||||
|
|
||||||
|
// URL API конечного (целевого) сервера для синхронизации access_keys
|
||||||
|
// Должен быть доступен с этого relay-сервера (можно через SSH-туннель)
|
||||||
|
"upstream_api_url": "http://TARGET_SERVER_IP:9090",
|
||||||
|
|
||||||
|
// Bearer-токен для доступа к API целевого сервера
|
||||||
|
// Должен совпадать с api.token в конфиге target-сервера
|
||||||
|
"upstream_api_token": "YOUR_API_TOKEN_HERE",
|
||||||
|
|
||||||
|
// Интервал синхронизации ключей в секундах (по умолчанию: 30)
|
||||||
|
"sync_interval_secs": 30,
|
||||||
|
|
||||||
|
"debug": false
|
||||||
|
}
|
||||||
|
|
@ -23,3 +23,5 @@ base64 = "0.22"
|
||||||
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
|
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
|
||||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "logging", "tls12"] }
|
tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "logging", "tls12"] }
|
||||||
rcgen = "0.13"
|
rcgen = "0.13"
|
||||||
|
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] }
|
||||||
|
futures-util = "0.3"
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,14 @@ pub mod outbound;
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod fallback;
|
pub mod fallback;
|
||||||
pub mod transport;
|
pub mod transport;
|
||||||
|
pub mod relay_node;
|
||||||
mod relay;
|
mod relay;
|
||||||
mod signal;
|
mod signal;
|
||||||
|
|
||||||
pub use outbound::{OutboundAction, OutboundConfig, OutboundRule};
|
pub use outbound::{OutboundAction, OutboundConfig, OutboundRule};
|
||||||
pub use api::ApiConfig;
|
pub use api::ApiConfig;
|
||||||
pub use fallback::FallbackConfig;
|
pub use fallback::FallbackConfig;
|
||||||
|
pub use relay_node::RelayConfig;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RealityServerConfig {
|
pub struct RealityServerConfig {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,392 @@
|
||||||
|
//! Authenticated Relay Node
|
||||||
|
//!
|
||||||
|
//! Принимает входящие UDP/TCP (UoT) соединения от клиентов,
|
||||||
|
//! валидирует HMAC-подпись клиента, используя ключи синхронизированные с upstream-сервера,
|
||||||
|
//! и слепо пробрасывает авторизованный трафик к целевому upstream-серверу.
|
||||||
|
//!
|
||||||
|
//! Архитектура цепочек:
|
||||||
|
//! Клиент -> [Relay 1] -> [Relay 2] -> ... -> [Target Server]
|
||||||
|
//! Каждый Relay скачивает access_keys напрямую с Target Server API.
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use bytes::Bytes;
|
||||||
|
use hmac::{Hmac, Mac};
|
||||||
|
use sha2::Sha256;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use tokio::net::{TcpListener, TcpStream, UdpSocket};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
/// Конфигурация Relay-узла.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RelayConfig {
|
||||||
|
/// Адрес(а) для прослушивания входящих соединений (UDP + TCP).
|
||||||
|
pub listen_addrs: Vec<String>,
|
||||||
|
/// Адрес upstream TCP для пересылки (обычно тот же порт, что и у target-сервера).
|
||||||
|
pub upstream_tcp: String,
|
||||||
|
/// Адрес upstream UDP.
|
||||||
|
pub upstream_udp: String,
|
||||||
|
/// URL API target-сервера для получения access_keys.
|
||||||
|
/// Пример: "http://127.0.0.1:9090"
|
||||||
|
pub upstream_api_url: String,
|
||||||
|
/// Bearer-токен для аутентификации на API target-сервера.
|
||||||
|
pub upstream_api_token: String,
|
||||||
|
/// Интервал синхронизации ключей (секунды).
|
||||||
|
pub sync_interval_secs: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
type SharedKeys = Arc<RwLock<Vec<String>>>;
|
||||||
|
|
||||||
|
/// Точка входа Relay-узла.
|
||||||
|
pub async fn run_relay_node(cfg: RelayConfig) -> Result<()> {
|
||||||
|
let shared_keys: SharedKeys = Arc::new(RwLock::new(Vec::new()));
|
||||||
|
|
||||||
|
// Первоначальная синхронизация ключей
|
||||||
|
if let Err(e) = sync_keys(&cfg, &shared_keys).await {
|
||||||
|
tracing::warn!("Relay: initial key sync failed: {}. Will retry.", e);
|
||||||
|
} else {
|
||||||
|
let count = shared_keys.read().unwrap().len();
|
||||||
|
tracing::info!("Relay: synced {} access key(s) from upstream API", count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Фоновый синхронизатор ключей
|
||||||
|
let cfg_clone = cfg.clone();
|
||||||
|
let keys_clone = shared_keys.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
tokio::time::sleep(Duration::from_secs(cfg_clone.sync_interval_secs)).await;
|
||||||
|
match sync_keys(&cfg_clone, &keys_clone).await {
|
||||||
|
Ok(count) => tracing::debug!("Relay: refreshed {} access key(s)", count),
|
||||||
|
Err(e) => tracing::warn!("Relay: key sync error: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Запуск UDP relay
|
||||||
|
{
|
||||||
|
let cfg_udp = cfg.clone();
|
||||||
|
let keys_udp = shared_keys.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = run_udp_relay(cfg_udp, keys_udp).await {
|
||||||
|
tracing::error!("Relay UDP loop error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запуск TCP (UoT) relay
|
||||||
|
run_tcp_relay(cfg, shared_keys).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Синхронизация access_keys с upstream API.
|
||||||
|
async fn sync_keys(cfg: &RelayConfig, shared_keys: &SharedKeys) -> Result<usize> {
|
||||||
|
let url = format!("{}/api/keys", cfg.upstream_api_url.trim_end_matches('/'));
|
||||||
|
|
||||||
|
let client = reqwest::Client::builder()
|
||||||
|
.timeout(Duration::from_secs(10))
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let mut req = client.get(&url);
|
||||||
|
if !cfg.upstream_api_token.is_empty() {
|
||||||
|
req = req.header("Authorization", format!("Bearer {}", cfg.upstream_api_token));
|
||||||
|
}
|
||||||
|
|
||||||
|
let resp = req.send().await?;
|
||||||
|
if !resp.status().is_success() {
|
||||||
|
anyhow::bail!("API returned HTTP {}", resp.status());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct KeysResponse {
|
||||||
|
keys: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let body: KeysResponse = resp.json().await?;
|
||||||
|
let count = body.keys.len();
|
||||||
|
{
|
||||||
|
let mut lock = shared_keys.write().unwrap();
|
||||||
|
*lock = body.keys;
|
||||||
|
}
|
||||||
|
Ok(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Проверяет HMAC-подпись клиента по набору ключей.
|
||||||
|
/// Возвращает true если хотя бы один ключ подходит.
|
||||||
|
fn verify_hmac(ts_bytes: &[u8; 8], provided_mac: &[u8], keys: &[String]) -> bool {
|
||||||
|
let client_ts = u64::from_be_bytes(*ts_bytes);
|
||||||
|
let now = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs();
|
||||||
|
|
||||||
|
// Защита от replay: ±60 секунд
|
||||||
|
if client_ts > now + 30 || client_ts < now.saturating_sub(60) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in keys {
|
||||||
|
if let Ok(mut mac) = Hmac::<Sha256>::new_from_slice(key.as_bytes()) {
|
||||||
|
mac.update(ts_bytes);
|
||||||
|
if mac.verify_slice(provided_mac).is_ok() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── UDP Relay ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async fn run_udp_relay(cfg: RelayConfig, shared_keys: SharedKeys) -> Result<()> {
|
||||||
|
// NAT-таблица: client_addr -> (upstream_socket, last_seen)
|
||||||
|
let nat_table: Arc<Mutex<HashMap<SocketAddr, (Arc<UdpSocket>, Instant)>>> =
|
||||||
|
Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
|
for bind_addr in &cfg.listen_addrs {
|
||||||
|
let sock = UdpSocket::bind(bind_addr).await?;
|
||||||
|
tracing::info!("Relay UDP listening on {}", bind_addr);
|
||||||
|
let sock = Arc::new(sock);
|
||||||
|
let upstream_udp = cfg.upstream_udp.clone();
|
||||||
|
let keys = shared_keys.clone();
|
||||||
|
let nat = nat_table.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut buf = vec![0u8; 65535];
|
||||||
|
loop {
|
||||||
|
let (n, peer) = match sock.recv_from(&mut buf).await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let packet = Bytes::copy_from_slice(&buf[..n]);
|
||||||
|
|
||||||
|
// Быстрая проверка: первый UDP-пакет от нового клиента содержит Noise handshake.
|
||||||
|
// Мы берём из него первые 8 байт как timestamp + 32 байта MAC.
|
||||||
|
// Если пакет достаточно длинный, проверяем подпись.
|
||||||
|
// Для уже авторизованных клиентов (есть в NAT) — пропускаем проверку.
|
||||||
|
{
|
||||||
|
let nat_lock = nat.lock().await;
|
||||||
|
if !nat_lock.contains_key(&peer) {
|
||||||
|
drop(nat_lock);
|
||||||
|
|
||||||
|
// Пакет должен быть >= 40 байт (8 ts + 32 hmac) для первичной проверки
|
||||||
|
if packet.len() < 40 {
|
||||||
|
tracing::debug!("Relay UDP: dropping short packet from {}", peer);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ts_bytes: [u8; 8] = packet[0..8].try_into().unwrap();
|
||||||
|
let provided_mac = &packet[8..40];
|
||||||
|
let keys_guard = keys.read().unwrap();
|
||||||
|
|
||||||
|
if !verify_hmac(&ts_bytes, provided_mac, &keys_guard) {
|
||||||
|
tracing::debug!("Relay UDP: unauthorized probe from {}, dropped", peer);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tracing::debug!("Relay UDP: authorized new client {}", peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Находим или создаём upstream socket для этого клиента
|
||||||
|
let upstream_sock = {
|
||||||
|
let mut nat_lock = nat.lock().await;
|
||||||
|
if let Some(entry) = nat_lock.get_mut(&peer) {
|
||||||
|
entry.1 = Instant::now();
|
||||||
|
entry.0.clone()
|
||||||
|
} else {
|
||||||
|
// Новый upstream socket для этого клиента
|
||||||
|
let usock = match UdpSocket::bind("0.0.0.0:0").await {
|
||||||
|
Ok(s) => Arc::new(s),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Relay UDP: failed to bind upstream socket: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if usock.connect(&upstream_udp).await.is_err() {
|
||||||
|
tracing::warn!("Relay UDP: failed to connect to upstream {}", upstream_udp);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nat_lock.insert(peer, (usock.clone(), Instant::now()));
|
||||||
|
|
||||||
|
// Задача: читаем ответы от upstream и отправляем клиенту
|
||||||
|
let usock_rx = usock.clone();
|
||||||
|
let client_sock = sock.clone();
|
||||||
|
let peer_addr = peer;
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut rbuf = vec![0u8; 65535];
|
||||||
|
loop {
|
||||||
|
match usock_rx.recv(&mut rbuf).await {
|
||||||
|
Ok(n) => {
|
||||||
|
let _ = client_sock.send_to(&rbuf[..n], peer_addr).await;
|
||||||
|
}
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
usock
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Пересылаем пакет в upstream
|
||||||
|
let _ = upstream_sock.send(&packet).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Периодически чистим устаревшие NAT записи (timeout 120 сек)
|
||||||
|
loop {
|
||||||
|
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||||
|
let mut nat_lock = nat_table.lock().await;
|
||||||
|
let now = Instant::now();
|
||||||
|
nat_lock.retain(|_, (_, last)| now.duration_since(*last) < Duration::from_secs(120));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── TCP (UoT) Relay ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async fn run_tcp_relay(cfg: RelayConfig, shared_keys: SharedKeys) -> Result<()> {
|
||||||
|
for bind_addr in &cfg.listen_addrs {
|
||||||
|
let listener = TcpListener::bind(bind_addr).await?;
|
||||||
|
tracing::info!("Relay TCP (UoT) listening on {}", bind_addr);
|
||||||
|
|
||||||
|
let upstream_tcp = cfg.upstream_tcp.clone();
|
||||||
|
let keys = shared_keys.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
let (stream, peer_addr) = match listener.accept().await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Relay TCP accept error: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let upstream = upstream_tcp.clone();
|
||||||
|
let keys_clone = keys.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
if let Err(e) = handle_tcp_client(stream, peer_addr, upstream, keys_clone).await {
|
||||||
|
tracing::debug!("Relay TCP client {} closed: {}", peer_addr, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Держим поток живым
|
||||||
|
futures_util::future::pending::<()>().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Обработка одного TCP (UoT) соединения.
|
||||||
|
///
|
||||||
|
/// Алгоритм:
|
||||||
|
/// 1. Читаем HTTP-заголовки (фейковый WebSocket upgrade).
|
||||||
|
/// 2. Извлекаем HMAC-подпись из Authorization: Bearer.
|
||||||
|
/// 3. Проверяем подпись по синхронизированным ключам.
|
||||||
|
/// 4. Если авторизован — открываем соединение к upstream и пайпим потоки.
|
||||||
|
async fn handle_tcp_client(
|
||||||
|
mut client: TcpStream,
|
||||||
|
peer_addr: SocketAddr,
|
||||||
|
upstream_addr: String,
|
||||||
|
shared_keys: SharedKeys,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Читаем HTTP-заголовки (до \r\n\r\n)
|
||||||
|
let mut header_buf = vec![0u8; 4096];
|
||||||
|
let mut header_len = 0usize;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let n = client.read(&mut header_buf[header_len..]).await?;
|
||||||
|
if n == 0 {
|
||||||
|
anyhow::bail!("connection closed before handshake");
|
||||||
|
}
|
||||||
|
header_len += n;
|
||||||
|
if header_buf[..header_len].windows(4).any(|w| w == b"\r\n\r\n") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if header_len >= header_buf.len() {
|
||||||
|
anyhow::bail!("headers too large");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let headers_str = String::from_utf8_lossy(&header_buf[..header_len]);
|
||||||
|
|
||||||
|
// Быстрая проверка: должен быть GET /stream
|
||||||
|
if !headers_str.starts_with("GET /stream HTTP/1.1\r\n") {
|
||||||
|
// Возвращаем 404 как обычный сервер (anti-scan)
|
||||||
|
let _ = client.write_all(b"HTTP/1.1 404 Not Found\r\nContent-Length: 9\r\nConnection: close\r\n\r\nNot Found").await;
|
||||||
|
anyhow::bail!("invalid request from {}", peer_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Извлекаем HMAC-подпись
|
||||||
|
let mut sig_b64 = None;
|
||||||
|
for line in headers_str.lines() {
|
||||||
|
let lower = line.to_ascii_lowercase();
|
||||||
|
if lower.starts_with("authorization: bearer ") {
|
||||||
|
sig_b64 = Some(line[22..].trim().to_string());
|
||||||
|
} else if lower.starts_with("cookie: ostp_token=") {
|
||||||
|
sig_b64 = Some(line[19..].trim().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sig_b64 = match sig_b64 {
|
||||||
|
Some(s) => s,
|
||||||
|
None => {
|
||||||
|
let _ = client.write_all(b"HTTP/1.1 404 Not Found\r\nContent-Length: 9\r\nConnection: close\r\n\r\nNot Found").await;
|
||||||
|
anyhow::bail!("missing authorization from {}", peer_addr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let sig_bytes = base64::Engine::decode(
|
||||||
|
&base64::engine::general_purpose::STANDARD_NO_PAD,
|
||||||
|
&sig_b64,
|
||||||
|
)
|
||||||
|
.map_err(|_| anyhow::anyhow!("invalid base64 from {}", peer_addr))?;
|
||||||
|
|
||||||
|
if sig_bytes.len() < 40 {
|
||||||
|
let _ = client.write_all(b"HTTP/1.1 401 Unauthorized\r\nContent-Length: 12\r\nConnection: close\r\n\r\nUnauthorized").await;
|
||||||
|
anyhow::bail!("signature too short from {}", peer_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ts_bytes: [u8; 8] = sig_bytes[0..8].try_into().unwrap();
|
||||||
|
let provided_mac = &sig_bytes[8..];
|
||||||
|
|
||||||
|
// Проверяем по синхронизированным ключам
|
||||||
|
let authorized = {
|
||||||
|
let keys = shared_keys.read().unwrap();
|
||||||
|
verify_hmac(&ts_bytes, provided_mac, &keys)
|
||||||
|
};
|
||||||
|
|
||||||
|
if !authorized {
|
||||||
|
let _ = client.write_all(b"HTTP/1.1 404 Not Found\r\nContent-Length: 9\r\nConnection: close\r\n\r\nNot Found").await;
|
||||||
|
anyhow::bail!("unauthorized client {}", peer_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!("Relay TCP: authorized client {}, forwarding to {}", peer_addr, upstream_addr);
|
||||||
|
|
||||||
|
// Подключаемся к upstream
|
||||||
|
let mut upstream = TcpStream::connect(&upstream_addr).await
|
||||||
|
.map_err(|e| anyhow::anyhow!("failed to connect to upstream {}: {}", upstream_addr, e))?;
|
||||||
|
|
||||||
|
// Пересылаем upstream заголовки AS-IS (он сам проверит подпись)
|
||||||
|
upstream.write_all(&header_buf[..header_len]).await?;
|
||||||
|
|
||||||
|
// Пайпим оба потока: client <-> upstream
|
||||||
|
let (mut cr, mut cw) = client.into_split();
|
||||||
|
let (mut ur, mut uw) = upstream.into_split();
|
||||||
|
|
||||||
|
let c2u = tokio::spawn(async move {
|
||||||
|
let _ = tokio::io::copy(&mut cr, &mut uw).await;
|
||||||
|
});
|
||||||
|
let u2c = tokio::spawn(async move {
|
||||||
|
let _ = tokio::io::copy(&mut ur, &mut cw).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
let _ = tokio::join!(c2u, u2c);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -145,6 +145,7 @@ fn parse_outbound_action(value: Option<String>) -> ostp_server::OutboundAction {
|
||||||
enum AppMode {
|
enum AppMode {
|
||||||
Server(ServerConfig),
|
Server(ServerConfig),
|
||||||
Client(ClientConfig),
|
Client(ClientConfig),
|
||||||
|
Relay(RelayServerConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
|
@ -177,6 +178,14 @@ impl UnifiedConfig {
|
||||||
anyhow::bail!("Client configuration must contain an access_key.");
|
anyhow::bail!("Client configuration must contain an access_key.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AppMode::Relay(cfg) => {
|
||||||
|
if cfg.upstream_tcp.is_empty() {
|
||||||
|
anyhow::bail!("Relay configuration must specify upstream_tcp address.");
|
||||||
|
}
|
||||||
|
if cfg.upstream_api_url.is_empty() {
|
||||||
|
anyhow::bail!("Relay configuration must specify upstream_api_url.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -194,6 +203,28 @@ struct ServerConfig {
|
||||||
transport: Option<TransportConfigRaw>,
|
transport: Option<TransportConfigRaw>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Конфигурация Relay-узла в config.json
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
struct RelayServerConfig {
|
||||||
|
/// Адрес(а) прослушивания (UDP + TCP UoT)
|
||||||
|
listen: ListenConfig,
|
||||||
|
/// Адрес upstream для TCP (UoT) трафика
|
||||||
|
upstream_tcp: String,
|
||||||
|
/// Адрес upstream для UDP трафика
|
||||||
|
upstream_udp: String,
|
||||||
|
/// URL API целевого сервера для синхронизации ключей
|
||||||
|
upstream_api_url: String,
|
||||||
|
/// Bearer-токен для API целевого сервера
|
||||||
|
#[serde(default)]
|
||||||
|
upstream_api_token: String,
|
||||||
|
/// Интервал синхронизации ключей в секундах (по умолчанию 30)
|
||||||
|
#[serde(default = "default_sync_interval")]
|
||||||
|
sync_interval_secs: u64,
|
||||||
|
debug: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_sync_interval() -> u64 { 30 }
|
||||||
|
|
||||||
/// Supports both single string "0.0.0.0:50000" and array ["0.0.0.0:50000", "[::]:50000"]
|
/// Supports both single string "0.0.0.0:50000" and array ["0.0.0.0:50000", "[::]:50000"]
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
|
|
@ -469,6 +500,13 @@ async fn run_app() -> Result<()> {
|
||||||
println!(" Server: {}", c.server);
|
println!(" Server: {}", c.server);
|
||||||
println!(" Key: {}...", &c.access_key[..8.min(c.access_key.len())]);
|
println!(" Key: {}...", &c.access_key[..8.min(c.access_key.len())]);
|
||||||
}
|
}
|
||||||
|
AppMode::Relay(r) => {
|
||||||
|
println!("[ostp] Config OK: relay mode");
|
||||||
|
println!(" Listen: {:?}", r.listen.primary());
|
||||||
|
println!(" Upstream TCP: {}", r.upstream_tcp);
|
||||||
|
println!(" Upstream UDP: {}", r.upstream_udp);
|
||||||
|
println!(" API sync: {}", r.upstream_api_url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
@ -698,6 +736,9 @@ async fn run_app() -> Result<()> {
|
||||||
AppMode::Client(_) => {
|
AppMode::Client(_) => {
|
||||||
anyhow::bail!("The configuration file is in Client mode. The --links flag can only extract keys from a Server configuration.");
|
anyhow::bail!("The configuration file is in Client mode. The --links flag can only extract keys from a Server configuration.");
|
||||||
}
|
}
|
||||||
|
AppMode::Relay(_) => {
|
||||||
|
anyhow::bail!("The configuration file is in Relay mode. The --links flag only works with Server configuration.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -757,7 +798,22 @@ async fn run_app() -> Result<()> {
|
||||||
AppMode::Client(client_cfg) => {
|
AppMode::Client(client_cfg) => {
|
||||||
run_client_directly(client_cfg).await?;
|
run_client_directly(client_cfg).await?;
|
||||||
}
|
}
|
||||||
|
AppMode::Relay(relay_cfg) => {
|
||||||
|
let listen_addrs = relay_cfg.listen.addresses();
|
||||||
|
println!("[ostp] Starting relay node on {:?}", listen_addrs);
|
||||||
|
println!("[ostp] Upstream TCP: {}", relay_cfg.upstream_tcp);
|
||||||
|
println!("[ostp] Upstream UDP: {}", relay_cfg.upstream_udp);
|
||||||
|
println!("[ostp] Key sync API: {}", relay_cfg.upstream_api_url);
|
||||||
|
let relay_config = ostp_server::RelayConfig {
|
||||||
|
listen_addrs,
|
||||||
|
upstream_tcp: relay_cfg.upstream_tcp,
|
||||||
|
upstream_udp: relay_cfg.upstream_udp,
|
||||||
|
upstream_api_url: relay_cfg.upstream_api_url,
|
||||||
|
upstream_api_token: relay_cfg.upstream_api_token,
|
||||||
|
sync_interval_secs: relay_cfg.sync_interval_secs,
|
||||||
|
};
|
||||||
|
ostp_server::relay_node::run_relay_node(relay_config).await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue