mirror of https://github.com/telemt/telemt.git
Merge dfa7dea41c into 5eff38eb82
This commit is contained in:
commit
e4163452fe
|
|
@ -0,0 +1,8 @@
|
||||||
|
.git
|
||||||
|
.github
|
||||||
|
target
|
||||||
|
.kilocode
|
||||||
|
cache
|
||||||
|
tlsfront
|
||||||
|
*.tar
|
||||||
|
*.tar.gz
|
||||||
|
|
@ -2,6 +2,16 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aead"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aes"
|
name = "aes"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
|
|
@ -13,6 +23,20 @@ dependencies = [
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aes-gcm"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
|
||||||
|
dependencies = [
|
||||||
|
"aead",
|
||||||
|
"aes",
|
||||||
|
"cipher",
|
||||||
|
"ctr",
|
||||||
|
"ghash",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
|
|
@ -55,6 +79,27 @@ version = "1.0.101"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arc-swap"
|
||||||
|
version = "1.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayref"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "asn1-rs"
|
name = "asn1-rs"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
|
@ -94,6 +139,17 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-trait"
|
||||||
|
version = "0.1.89"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atomic-waker"
|
name = "atomic-waker"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
|
|
@ -112,6 +168,12 @@ version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64ct"
|
||||||
|
version = "1.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|
@ -139,6 +201,20 @@ version = "2.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake3"
|
||||||
|
version = "1.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"arrayvec",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"constant_time_eq",
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
|
|
@ -163,6 +239,12 @@ version = "3.19.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byte_string"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11aade7a05aa8c3a351cedc44c3fc45806430543382fcc4743a9b757a2a0b4ed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
|
|
@ -212,6 +294,30 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cipher",
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20poly1305"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
|
||||||
|
dependencies = [
|
||||||
|
"aead",
|
||||||
|
"chacha20",
|
||||||
|
"cipher",
|
||||||
|
"poly1305",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.43"
|
version = "0.4.43"
|
||||||
|
|
@ -261,6 +367,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
"inout",
|
"inout",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -288,6 +395,18 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const-oid"
|
||||||
|
version = "0.9.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "constant_time_eq"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
|
|
@ -357,6 +476,12 @@ dependencies = [
|
||||||
"itertools",
|
"itertools",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "critical-section"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.15"
|
version = "0.5.15"
|
||||||
|
|
@ -413,6 +538,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
|
"rand_core 0.6.4",
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -444,6 +570,16 @@ version = "2.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
|
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "der"
|
||||||
|
version = "0.7.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||||
|
dependencies = [
|
||||||
|
"const-oid",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "der-parser"
|
name = "der-parser"
|
||||||
version = "8.2.0"
|
version = "8.2.0"
|
||||||
|
|
@ -489,12 +625,54 @@ dependencies = [
|
||||||
"syn 2.0.114",
|
"syn 2.0.114",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dynosaur"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a12303417f378f29ba12cb12fc78a9df0d8e16ccb1ad94abf04d48d96bdda532"
|
||||||
|
dependencies = [
|
||||||
|
"dynosaur_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dynosaur_derive"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b0713d5c1d52e774c5cd7bb8b043d7c0fc4f921abfb678556140bfbe6ab2364"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ed25519"
|
||||||
|
version = "2.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||||
|
dependencies = [
|
||||||
|
"pkcs8",
|
||||||
|
"signature",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum-as-inner"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
|
@ -709,6 +887,16 @@ dependencies = [
|
||||||
"wasip3",
|
"wasip3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ghash"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
|
||||||
|
dependencies = [
|
||||||
|
"opaque-debug",
|
||||||
|
"polyval",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
|
|
@ -783,6 +971,61 @@ version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hickory-proto"
|
||||||
|
version = "0.25.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"cfg-if",
|
||||||
|
"data-encoding",
|
||||||
|
"enum-as-inner",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-io",
|
||||||
|
"futures-util",
|
||||||
|
"idna",
|
||||||
|
"ipnet",
|
||||||
|
"once_cell",
|
||||||
|
"rand",
|
||||||
|
"ring",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"tinyvec",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hickory-resolver"
|
||||||
|
version = "0.25.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"futures-util",
|
||||||
|
"hickory-proto",
|
||||||
|
"ipconfig",
|
||||||
|
"moka",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot",
|
||||||
|
"rand",
|
||||||
|
"resolv-conf",
|
||||||
|
"smallvec",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hkdf"
|
||||||
|
version = "0.12.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
|
||||||
|
dependencies = [
|
||||||
|
"hmac",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hmac"
|
name = "hmac"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
|
@ -1055,6 +1298,17 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"inotify-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inotify-sys"
|
name = "inotify-sys"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
|
@ -1074,6 +1328,18 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ipconfig"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
|
||||||
|
dependencies = [
|
||||||
|
"socket2 0.5.10",
|
||||||
|
"widestring",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
"winreg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.11.0"
|
version = "2.11.0"
|
||||||
|
|
@ -1226,6 +1492,12 @@ version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lru_time_cache"
|
||||||
|
version = "0.11.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9106e1d747ffd48e6be5bb2d97fa706ed25b144fbee4d5c02eae110cd8d6badd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -1285,10 +1557,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
|
"log",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "moka"
|
||||||
|
version = "0.12.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85f8024e1c8e71c778968af91d43700ce1d11b219d127d79fb2934153b82b42b"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"equivalent",
|
||||||
|
"parking_lot",
|
||||||
|
"portable-atomic",
|
||||||
|
"smallvec",
|
||||||
|
"tagptr",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.28.0"
|
version = "0.28.0"
|
||||||
|
|
@ -1322,7 +1612,7 @@ dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"filetime",
|
"filetime",
|
||||||
"fsevent-sys",
|
"fsevent-sys",
|
||||||
"inotify",
|
"inotify 0.9.6",
|
||||||
"kqueue",
|
"kqueue",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -1331,6 +1621,33 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify"
|
||||||
|
version = "8.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"fsevent-sys",
|
||||||
|
"inotify 0.11.1",
|
||||||
|
"kqueue",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"mio 1.1.1",
|
||||||
|
"notify-types",
|
||||||
|
"walkdir",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify-types"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.50.3"
|
version = "0.50.3"
|
||||||
|
|
@ -1388,6 +1705,10 @@ name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
dependencies = [
|
||||||
|
"critical-section",
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oorandom"
|
name = "oorandom"
|
||||||
|
|
@ -1395,6 +1716,12 @@ version = "11.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
|
|
@ -1424,6 +1751,26 @@ version = "2.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project"
|
||||||
|
version = "1.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-internal",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-internal"
|
||||||
|
version = "1.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
|
|
@ -1436,6 +1783,16 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkcs8"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||||
|
dependencies = [
|
||||||
|
"der",
|
||||||
|
"spki",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plotters"
|
name = "plotters"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
|
|
@ -1464,6 +1821,35 @@ dependencies = [
|
||||||
"plotters-backend",
|
"plotters-backend",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "poly1305"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
|
||||||
|
dependencies = [
|
||||||
|
"cpufeatures",
|
||||||
|
"opaque-debug",
|
||||||
|
"universal-hash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "polyval"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"opaque-debug",
|
||||||
|
"universal-hash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "potential_utf"
|
name = "potential_utf"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
|
|
@ -1609,7 +1995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
"rand_core",
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1619,7 +2005,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core 0.9.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1637,7 +2032,7 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
|
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand_core",
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1745,6 +2140,12 @@ dependencies = [
|
||||||
"webpki-roots 1.0.6",
|
"webpki-roots 1.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "resolv-conf"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.14"
|
version = "0.17.14"
|
||||||
|
|
@ -1759,6 +2160,19 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring-compat"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccce7bae150b815f0811db41b8312fcb74bffa4cab9cee5429ee00f356dd5bd4"
|
||||||
|
dependencies = [
|
||||||
|
"aead",
|
||||||
|
"ed25519",
|
||||||
|
"generic-array",
|
||||||
|
"pkcs8",
|
||||||
|
"ring",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
|
|
@ -1870,12 +2284,33 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sealed"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.27"
|
version = "1.0.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sendfd"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b183bfd5b1bc64ab0c1ef3ee06b008a9ef1b68a7d3a99ba566fbfe7a7c6d745b"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
|
|
@ -1962,6 +2397,64 @@ dependencies = [
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shadowsocks"
|
||||||
|
version = "1.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "482831bf9d55acf3c98e211b6c852c3dfdf1d1b0d23fdf1d887c5a4b2acad4e4"
|
||||||
|
dependencies = [
|
||||||
|
"aes",
|
||||||
|
"arc-swap",
|
||||||
|
"base64",
|
||||||
|
"blake3",
|
||||||
|
"byte_string",
|
||||||
|
"bytes",
|
||||||
|
"cfg-if",
|
||||||
|
"dynosaur",
|
||||||
|
"futures",
|
||||||
|
"hickory-resolver",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"lru_time_cache",
|
||||||
|
"notify 8.2.0",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project",
|
||||||
|
"rand",
|
||||||
|
"sealed",
|
||||||
|
"sendfd",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"shadowsocks-crypto",
|
||||||
|
"socket2 0.6.2",
|
||||||
|
"spin",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"tokio",
|
||||||
|
"tokio-tfo",
|
||||||
|
"trait-variant",
|
||||||
|
"url",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shadowsocks-crypto"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d038a3d17586f1c1ab3c1c3b9e4d5ef8fba98fb3890ad740c8487038b2e2ca5"
|
||||||
|
dependencies = [
|
||||||
|
"aes",
|
||||||
|
"aes-gcm",
|
||||||
|
"blake3",
|
||||||
|
"bytes",
|
||||||
|
"cfg-if",
|
||||||
|
"chacha20poly1305",
|
||||||
|
"hkdf",
|
||||||
|
"md-5",
|
||||||
|
"rand",
|
||||||
|
"ring-compat",
|
||||||
|
"sha1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
|
|
@ -1987,6 +2480,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signature"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
|
|
@ -2019,6 +2518,25 @@ dependencies = [
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spki"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"der",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stable_deref_trait"
|
name = "stable_deref_trait"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
|
@ -2085,9 +2603,15 @@ dependencies = [
|
||||||
"syn 2.0.114",
|
"syn 2.0.114",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tagptr"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "telemt"
|
name = "telemt"
|
||||||
version = "3.3.15"
|
version = "3.3.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
@ -2113,7 +2637,7 @@ dependencies = [
|
||||||
"lru",
|
"lru",
|
||||||
"md-5",
|
"md-5",
|
||||||
"nix",
|
"nix",
|
||||||
"notify",
|
"notify 6.1.1",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
|
@ -2126,6 +2650,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha1",
|
"sha1",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"shadowsocks",
|
||||||
"socket2 0.5.10",
|
"socket2 0.5.10",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -2330,6 +2855,23 @@ dependencies = [
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-tfo"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6ad2c3b3bb958ad992354a7ebc468fc0f7cdc9af4997bf4d3fd3cb28bad36dc"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"futures",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"pin-project",
|
||||||
|
"socket2 0.6.2",
|
||||||
|
"tokio",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.18"
|
version = "0.7.18"
|
||||||
|
|
@ -2494,6 +3036,17 @@ dependencies = [
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "trait-variant"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "try-lock"
|
name = "try-lock"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
|
|
@ -2524,6 +3077,16 @@ version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "universal-hash"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
@ -2548,6 +3111,17 @@ 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 = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.4.1",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
|
@ -2743,6 +3317,12 @@ dependencies = [
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "widestring"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.11"
|
version = "0.1.11"
|
||||||
|
|
@ -3042,6 +3622,16 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winreg"
|
||||||
|
version = "0.50.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
version = "0.51.0"
|
version = "0.51.0"
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ zeroize = { version = "1.8", features = ["derive"] }
|
||||||
# Network
|
# Network
|
||||||
socket2 = { version = "0.5", features = ["all"] }
|
socket2 = { version = "0.5", features = ["all"] }
|
||||||
nix = { version = "0.28", default-features = false, features = ["net"] }
|
nix = { version = "0.28", default-features = false, features = ["net"] }
|
||||||
|
shadowsocks = { version = "1.24", features = ["aead-cipher-2022"] }
|
||||||
|
|
||||||
# Serialization
|
# Serialization
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
|
||||||
10
docs/API.md
10
docs/API.md
|
|
@ -497,13 +497,14 @@ Note: the request contract is defined, but the corresponding route currently ret
|
||||||
| `direct_total` | `usize` | Direct-route upstream entries. |
|
| `direct_total` | `usize` | Direct-route upstream entries. |
|
||||||
| `socks4_total` | `usize` | SOCKS4 upstream entries. |
|
| `socks4_total` | `usize` | SOCKS4 upstream entries. |
|
||||||
| `socks5_total` | `usize` | SOCKS5 upstream entries. |
|
| `socks5_total` | `usize` | SOCKS5 upstream entries. |
|
||||||
|
| `shadowsocks_total` | `usize` | Shadowsocks upstream entries. |
|
||||||
|
|
||||||
#### `RuntimeUpstreamQualityUpstreamData`
|
#### `RuntimeUpstreamQualityUpstreamData`
|
||||||
| Field | Type | Description |
|
| Field | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `upstream_id` | `usize` | Runtime upstream index. |
|
| `upstream_id` | `usize` | Runtime upstream index. |
|
||||||
| `route_kind` | `string` | `direct`, `socks4`, `socks5`. |
|
| `route_kind` | `string` | `direct`, `socks4`, `socks5`, `shadowsocks`. |
|
||||||
| `address` | `string` | Upstream address (`direct` literal for direct route kind). |
|
| `address` | `string` | Upstream address (`direct` literal for direct route kind, `host:port` only for proxied upstreams). |
|
||||||
| `weight` | `u16` | Selection weight. |
|
| `weight` | `u16` | Selection weight. |
|
||||||
| `scopes` | `string` | Configured scope selector. |
|
| `scopes` | `string` | Configured scope selector. |
|
||||||
| `healthy` | `bool` | Current health flag. |
|
| `healthy` | `bool` | Current health flag. |
|
||||||
|
|
@ -757,13 +758,14 @@ Note: the request contract is defined, but the corresponding route currently ret
|
||||||
| `direct_total` | `usize` | Number of direct upstream entries. |
|
| `direct_total` | `usize` | Number of direct upstream entries. |
|
||||||
| `socks4_total` | `usize` | Number of SOCKS4 upstream entries. |
|
| `socks4_total` | `usize` | Number of SOCKS4 upstream entries. |
|
||||||
| `socks5_total` | `usize` | Number of SOCKS5 upstream entries. |
|
| `socks5_total` | `usize` | Number of SOCKS5 upstream entries. |
|
||||||
|
| `shadowsocks_total` | `usize` | Number of Shadowsocks upstream entries. |
|
||||||
|
|
||||||
#### `UpstreamStatus`
|
#### `UpstreamStatus`
|
||||||
| Field | Type | Description |
|
| Field | Type | Description |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `upstream_id` | `usize` | Runtime upstream index. |
|
| `upstream_id` | `usize` | Runtime upstream index. |
|
||||||
| `route_kind` | `string` | Upstream route kind: `direct`, `socks4`, `socks5`. |
|
| `route_kind` | `string` | Upstream route kind: `direct`, `socks4`, `socks5`, `shadowsocks`. |
|
||||||
| `address` | `string` | Upstream address (`direct` for direct route kind). Authentication fields are intentionally omitted. |
|
| `address` | `string` | Upstream address (`direct` for direct route kind, `host:port` for Shadowsocks). Authentication fields are intentionally omitted. |
|
||||||
| `weight` | `u16` | Selection weight. |
|
| `weight` | `u16` | Selection weight. |
|
||||||
| `scopes` | `string` | Configured scope selector string. |
|
| `scopes` | `string` | Configured scope selector string. |
|
||||||
| `healthy` | `bool` | Current health flag. |
|
| `healthy` | `bool` | Current health flag. |
|
||||||
|
|
|
||||||
|
|
@ -113,3 +113,17 @@ password = "pass" # Password for Auth on SOCKS-server
|
||||||
weight = 1 # Set Weight for Scenarios
|
weight = 1 # Set Weight for Scenarios
|
||||||
enabled = true
|
enabled = true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Shadowsocks as Upstream
|
||||||
|
Requires `use_middle_proxy = false`.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[general]
|
||||||
|
use_middle_proxy = false
|
||||||
|
|
||||||
|
[[upstreams]]
|
||||||
|
type = "shadowsocks"
|
||||||
|
url = "ss://2022-blake3-aes-256-gcm:BASE64_KEY@1.2.3.4:8388"
|
||||||
|
weight = 1
|
||||||
|
enabled = true
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -113,3 +113,17 @@ password = "pass" # Password for Auth on SOCKS-server
|
||||||
weight = 1 # Set Weight for Scenarios
|
weight = 1 # Set Weight for Scenarios
|
||||||
enabled = true
|
enabled = true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Shadowsocks как Upstream
|
||||||
|
Требует `use_middle_proxy = false`.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[general]
|
||||||
|
use_middle_proxy = false
|
||||||
|
|
||||||
|
[[upstreams]]
|
||||||
|
type = "shadowsocks"
|
||||||
|
url = "ss://2022-blake3-aes-256-gcm:BASE64_KEY@1.2.3.4:8388"
|
||||||
|
weight = 1
|
||||||
|
enabled = true
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ Die unten angegebenen `Default`-Werte sind Code-Defaults (bei fehlendem Schlüss
|
||||||
|
|
||||||
| Feld | Gilt für | Typ | Pflicht | Default | Bedeutung |
|
| Feld | Gilt für | Typ | Pflicht | Default | Bedeutung |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| `[[upstreams]].type` | alle Upstreams | `"direct" \| "socks4" \| "socks5"` | ja | n/a | Upstream-Transporttyp. |
|
| `[[upstreams]].type` | alle Upstreams | `"direct" \| "socks4" \| "socks5" \| "shadowsocks"` | ja | n/a | Upstream-Transporttyp. |
|
||||||
| `[[upstreams]].weight` | alle Upstreams | `u16` | nein | `1` | Basisgewicht für weighted-random Auswahl. |
|
| `[[upstreams]].weight` | alle Upstreams | `u16` | nein | `1` | Basisgewicht für weighted-random Auswahl. |
|
||||||
| `[[upstreams]].enabled` | alle Upstreams | `bool` | nein | `true` | Deaktivierte Einträge werden beim Start ignoriert. |
|
| `[[upstreams]].enabled` | alle Upstreams | `bool` | nein | `true` | Deaktivierte Einträge werden beim Start ignoriert. |
|
||||||
| `[[upstreams]].scopes` | alle Upstreams | `String` | nein | `""` | Komma-separierte Scope-Tags für Request-Routing. |
|
| `[[upstreams]].scopes` | alle Upstreams | `String` | nein | `""` | Komma-separierte Scope-Tags für Request-Routing. |
|
||||||
|
|
@ -95,6 +95,8 @@ Die unten angegebenen `Default`-Werte sind Code-Defaults (bei fehlendem Schlüss
|
||||||
| `interface` | `socks5` | `Option<String>` | nein | `null` | Wird nur genutzt, wenn `address` als `ip:port` angegeben ist. |
|
| `interface` | `socks5` | `Option<String>` | nein | `null` | Wird nur genutzt, wenn `address` als `ip:port` angegeben ist. |
|
||||||
| `username` | `socks5` | `Option<String>` | nein | `null` | SOCKS5 Benutzername. |
|
| `username` | `socks5` | `Option<String>` | nein | `null` | SOCKS5 Benutzername. |
|
||||||
| `password` | `socks5` | `Option<String>` | nein | `null` | SOCKS5 Passwort. |
|
| `password` | `socks5` | `Option<String>` | nein | `null` | SOCKS5 Passwort. |
|
||||||
|
| `url` | `shadowsocks` | `String` | ja | n/a | Shadowsocks-SIP002-URL (`ss://...`). In Runtime-APIs wird nur `host:port` offengelegt. |
|
||||||
|
| `interface` | `shadowsocks` | `Option<String>` | nein | `null` | Optionales ausgehendes Bind-Interface oder lokale Literal-IP. |
|
||||||
|
|
||||||
### Runtime-Regeln (wichtig)
|
### Runtime-Regeln (wichtig)
|
||||||
|
|
||||||
|
|
@ -115,6 +117,7 @@ Die unten angegebenen `Default`-Werte sind Code-Defaults (bei fehlendem Schlüss
|
||||||
8. Im ME-Modus wird der gewählte Upstream auch für den ME-TCP-Dial-Pfad verwendet.
|
8. Im ME-Modus wird der gewählte Upstream auch für den ME-TCP-Dial-Pfad verwendet.
|
||||||
9. Im ME-Modus ist bei `direct` mit bind/interface die STUN-Reflection bind-aware für KDF-Adressmaterial.
|
9. Im ME-Modus ist bei `direct` mit bind/interface die STUN-Reflection bind-aware für KDF-Adressmaterial.
|
||||||
10. Im ME-Modus werden bei SOCKS-Upstream `BND.ADDR/BND.PORT` für KDF verwendet, wenn gültig/öffentlich und gleiche IP-Familie.
|
10. Im ME-Modus werden bei SOCKS-Upstream `BND.ADDR/BND.PORT` für KDF verwendet, wenn gültig/öffentlich und gleiche IP-Familie.
|
||||||
|
11. `shadowsocks`-Upstreams erfordern `general.use_middle_proxy = false`. Mit aktiviertem ME-Modus schlägt das Laden der Config sofort fehl.
|
||||||
|
|
||||||
## Upstream-Konfigurationsbeispiele
|
## Upstream-Konfigurationsbeispiele
|
||||||
|
|
||||||
|
|
@ -150,7 +153,20 @@ weight = 2
|
||||||
enabled = true
|
enabled = true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Beispiel 4: Gemischte Upstreams mit Scopes
|
### Beispiel 4: Shadowsocks-Upstream
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[general]
|
||||||
|
use_middle_proxy = false
|
||||||
|
|
||||||
|
[[upstreams]]
|
||||||
|
type = "shadowsocks"
|
||||||
|
url = "ss://2022-blake3-aes-256-gcm:BASE64_KEY@198.51.100.50:8388"
|
||||||
|
weight = 2
|
||||||
|
enabled = true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Beispiel 5: Gemischte Upstreams mit Scopes
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[[upstreams]]
|
[[upstreams]]
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ Defaults below are code defaults (used when a key is omitted), not necessarily v
|
||||||
|
|
||||||
| Field | Applies to | Type | Required | Default | Meaning |
|
| Field | Applies to | Type | Required | Default | Meaning |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| `[[upstreams]].type` | all upstreams | `"direct" \| "socks4" \| "socks5"` | yes | n/a | Upstream transport type. |
|
| `[[upstreams]].type` | all upstreams | `"direct" \| "socks4" \| "socks5" \| "shadowsocks"` | yes | n/a | Upstream transport type. |
|
||||||
| `[[upstreams]].weight` | all upstreams | `u16` | no | `1` | Base weight for weighted-random selection. |
|
| `[[upstreams]].weight` | all upstreams | `u16` | no | `1` | Base weight for weighted-random selection. |
|
||||||
| `[[upstreams]].enabled` | all upstreams | `bool` | no | `true` | Disabled entries are ignored at startup. |
|
| `[[upstreams]].enabled` | all upstreams | `bool` | no | `true` | Disabled entries are ignored at startup. |
|
||||||
| `[[upstreams]].scopes` | all upstreams | `String` | no | `""` | Comma-separated scope tags for request-level routing. |
|
| `[[upstreams]].scopes` | all upstreams | `String` | no | `""` | Comma-separated scope tags for request-level routing. |
|
||||||
|
|
@ -95,6 +95,8 @@ Defaults below are code defaults (used when a key is omitted), not necessarily v
|
||||||
| `interface` | `socks5` | `Option<String>` | no | `null` | Used only for SOCKS server `ip:port` dial path. |
|
| `interface` | `socks5` | `Option<String>` | no | `null` | Used only for SOCKS server `ip:port` dial path. |
|
||||||
| `username` | `socks5` | `Option<String>` | no | `null` | SOCKS5 username auth. |
|
| `username` | `socks5` | `Option<String>` | no | `null` | SOCKS5 username auth. |
|
||||||
| `password` | `socks5` | `Option<String>` | no | `null` | SOCKS5 password auth. |
|
| `password` | `socks5` | `Option<String>` | no | `null` | SOCKS5 password auth. |
|
||||||
|
| `url` | `shadowsocks` | `String` | yes | n/a | Shadowsocks SIP002 URL (`ss://...`). Only `host:port` is exposed in runtime APIs. |
|
||||||
|
| `interface` | `shadowsocks` | `Option<String>` | no | `null` | Optional outgoing bind interface or literal local IP. |
|
||||||
|
|
||||||
### Runtime rules (important)
|
### Runtime rules (important)
|
||||||
|
|
||||||
|
|
@ -115,6 +117,7 @@ Defaults below are code defaults (used when a key is omitted), not necessarily v
|
||||||
8. In ME mode, the selected upstream is also used for ME TCP dial path.
|
8. In ME mode, the selected upstream is also used for ME TCP dial path.
|
||||||
9. In ME mode for `direct` upstream with bind/interface, STUN reflection logic is bind-aware for KDF source material.
|
9. In ME mode for `direct` upstream with bind/interface, STUN reflection logic is bind-aware for KDF source material.
|
||||||
10. In ME mode for SOCKS upstream, SOCKS `BND.ADDR/BND.PORT` is used for KDF when it is valid/public for the same family.
|
10. In ME mode for SOCKS upstream, SOCKS `BND.ADDR/BND.PORT` is used for KDF when it is valid/public for the same family.
|
||||||
|
11. `shadowsocks` upstreams require `general.use_middle_proxy = false`. Config load fails fast if ME mode is enabled.
|
||||||
|
|
||||||
## Upstream Configuration Examples
|
## Upstream Configuration Examples
|
||||||
|
|
||||||
|
|
@ -150,7 +153,20 @@ weight = 2
|
||||||
enabled = true
|
enabled = true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example 4: Mixed upstreams with scopes
|
### Example 4: Shadowsocks upstream
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[general]
|
||||||
|
use_middle_proxy = false
|
||||||
|
|
||||||
|
[[upstreams]]
|
||||||
|
type = "shadowsocks"
|
||||||
|
url = "ss://2022-blake3-aes-256-gcm:BASE64_KEY@198.51.100.50:8388"
|
||||||
|
weight = 2
|
||||||
|
enabled = true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 5: Mixed upstreams with scopes
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[[upstreams]]
|
[[upstreams]]
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@
|
||||||
|
|
||||||
| Поле | Применимость | Тип | Обязательно | Default | Назначение |
|
| Поле | Применимость | Тип | Обязательно | Default | Назначение |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| `[[upstreams]].type` | все upstream | `"direct" \| "socks4" \| "socks5"` | да | n/a | Тип upstream транспорта. |
|
| `[[upstreams]].type` | все upstream | `"direct" \| "socks4" \| "socks5" \| "shadowsocks"` | да | n/a | Тип upstream транспорта. |
|
||||||
| `[[upstreams]].weight` | все upstream | `u16` | нет | `1` | Базовый вес в weighted-random выборе. |
|
| `[[upstreams]].weight` | все upstream | `u16` | нет | `1` | Базовый вес в weighted-random выборе. |
|
||||||
| `[[upstreams]].enabled` | все upstream | `bool` | нет | `true` | Выключенные записи игнорируются на старте. |
|
| `[[upstreams]].enabled` | все upstream | `bool` | нет | `true` | Выключенные записи игнорируются на старте. |
|
||||||
| `[[upstreams]].scopes` | все upstream | `String` | нет | `""` | Список scope-токенов через запятую для маршрутизации. |
|
| `[[upstreams]].scopes` | все upstream | `String` | нет | `""` | Список scope-токенов через запятую для маршрутизации. |
|
||||||
|
|
@ -95,6 +95,8 @@
|
||||||
| `interface` | `socks5` | `Option<String>` | нет | `null` | Используется только если `address` задан как `ip:port`. |
|
| `interface` | `socks5` | `Option<String>` | нет | `null` | Используется только если `address` задан как `ip:port`. |
|
||||||
| `username` | `socks5` | `Option<String>` | нет | `null` | Логин SOCKS5 auth. |
|
| `username` | `socks5` | `Option<String>` | нет | `null` | Логин SOCKS5 auth. |
|
||||||
| `password` | `socks5` | `Option<String>` | нет | `null` | Пароль SOCKS5 auth. |
|
| `password` | `socks5` | `Option<String>` | нет | `null` | Пароль SOCKS5 auth. |
|
||||||
|
| `url` | `shadowsocks` | `String` | да | n/a | Shadowsocks SIP002 URL (`ss://...`). В runtime API раскрывается только `host:port`. |
|
||||||
|
| `interface` | `shadowsocks` | `Option<String>` | нет | `null` | Необязательный исходящий bind-интерфейс или literal локальный IP. |
|
||||||
|
|
||||||
### Runtime-правила
|
### Runtime-правила
|
||||||
|
|
||||||
|
|
@ -115,6 +117,7 @@
|
||||||
8. В ME-режиме выбранный upstream также используется для ME TCP dial path.
|
8. В ME-режиме выбранный upstream также используется для ME TCP dial path.
|
||||||
9. В ME-режиме для `direct` upstream с bind/interface STUN-рефлексия выполняется bind-aware для KDF материала.
|
9. В ME-режиме для `direct` upstream с bind/interface STUN-рефлексия выполняется bind-aware для KDF материала.
|
||||||
10. В ME-режиме для SOCKS upstream используются `BND.ADDR/BND.PORT` для KDF, если адрес валиден/публичен и соответствует IP family.
|
10. В ME-режиме для SOCKS upstream используются `BND.ADDR/BND.PORT` для KDF, если адрес валиден/публичен и соответствует IP family.
|
||||||
|
11. `shadowsocks` upstream требует `general.use_middle_proxy = false`. При включенном ME-режиме конфиг отклоняется при загрузке.
|
||||||
|
|
||||||
## Примеры конфигурации Upstreams
|
## Примеры конфигурации Upstreams
|
||||||
|
|
||||||
|
|
@ -150,7 +153,20 @@ weight = 2
|
||||||
enabled = true
|
enabled = true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Пример 4: смешанные upstream с scopes
|
### Пример 4: Shadowsocks upstream
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[general]
|
||||||
|
use_middle_proxy = false
|
||||||
|
|
||||||
|
[[upstreams]]
|
||||||
|
type = "shadowsocks"
|
||||||
|
url = "ss://2022-blake3-aes-256-gcm:BASE64_KEY@198.51.100.50:8388"
|
||||||
|
weight = 2
|
||||||
|
enabled = true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Пример 5: смешанные upstream с scopes
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[[upstreams]]
|
[[upstreams]]
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@ pub(super) struct UpstreamSummaryData {
|
||||||
pub(super) direct_total: usize,
|
pub(super) direct_total: usize,
|
||||||
pub(super) socks4_total: usize,
|
pub(super) socks4_total: usize,
|
||||||
pub(super) socks5_total: usize,
|
pub(super) socks5_total: usize,
|
||||||
|
pub(super) shadowsocks_total: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,7 @@ pub(super) struct RuntimeUpstreamQualitySummaryData {
|
||||||
pub(super) direct_total: usize,
|
pub(super) direct_total: usize,
|
||||||
pub(super) socks4_total: usize,
|
pub(super) socks4_total: usize,
|
||||||
pub(super) socks5_total: usize,
|
pub(super) socks5_total: usize,
|
||||||
|
pub(super) shadowsocks_total: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
|
@ -404,7 +405,9 @@ pub(super) async fn build_runtime_upstream_quality_data(
|
||||||
connect_attempt_total: shared.stats.get_upstream_connect_attempt_total(),
|
connect_attempt_total: shared.stats.get_upstream_connect_attempt_total(),
|
||||||
connect_success_total: shared.stats.get_upstream_connect_success_total(),
|
connect_success_total: shared.stats.get_upstream_connect_success_total(),
|
||||||
connect_fail_total: shared.stats.get_upstream_connect_fail_total(),
|
connect_fail_total: shared.stats.get_upstream_connect_fail_total(),
|
||||||
connect_failfast_hard_error_total: shared.stats.get_upstream_connect_failfast_hard_error_total(),
|
connect_failfast_hard_error_total: shared
|
||||||
|
.stats
|
||||||
|
.get_upstream_connect_failfast_hard_error_total(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(snapshot) = shared.upstream_manager.try_api_snapshot() else {
|
let Some(snapshot) = shared.upstream_manager.try_api_snapshot() else {
|
||||||
|
|
@ -444,6 +447,7 @@ pub(super) async fn build_runtime_upstream_quality_data(
|
||||||
direct_total: snapshot.summary.direct_total,
|
direct_total: snapshot.summary.direct_total,
|
||||||
socks4_total: snapshot.summary.socks4_total,
|
socks4_total: snapshot.summary.socks4_total,
|
||||||
socks5_total: snapshot.summary.socks5_total,
|
socks5_total: snapshot.summary.socks5_total,
|
||||||
|
shadowsocks_total: snapshot.summary.shadowsocks_total,
|
||||||
}),
|
}),
|
||||||
upstreams: Some(
|
upstreams: Some(
|
||||||
snapshot
|
snapshot
|
||||||
|
|
@ -455,6 +459,7 @@ pub(super) async fn build_runtime_upstream_quality_data(
|
||||||
crate::transport::UpstreamRouteKind::Direct => "direct",
|
crate::transport::UpstreamRouteKind::Direct => "direct",
|
||||||
crate::transport::UpstreamRouteKind::Socks4 => "socks4",
|
crate::transport::UpstreamRouteKind::Socks4 => "socks4",
|
||||||
crate::transport::UpstreamRouteKind::Socks5 => "socks5",
|
crate::transport::UpstreamRouteKind::Socks5 => "socks5",
|
||||||
|
crate::transport::UpstreamRouteKind::Shadowsocks => "shadowsocks",
|
||||||
},
|
},
|
||||||
address: upstream.address,
|
address: upstream.address,
|
||||||
weight: upstream.weight,
|
weight: upstream.weight,
|
||||||
|
|
@ -474,7 +479,9 @@ pub(super) async fn build_runtime_upstream_quality_data(
|
||||||
crate::transport::upstream::IpPreference::PreferV6 => "prefer_v6",
|
crate::transport::upstream::IpPreference::PreferV6 => "prefer_v6",
|
||||||
crate::transport::upstream::IpPreference::PreferV4 => "prefer_v4",
|
crate::transport::upstream::IpPreference::PreferV4 => "prefer_v4",
|
||||||
crate::transport::upstream::IpPreference::BothWork => "both_work",
|
crate::transport::upstream::IpPreference::BothWork => "both_work",
|
||||||
crate::transport::upstream::IpPreference::Unavailable => "unavailable",
|
crate::transport::upstream::IpPreference::Unavailable => {
|
||||||
|
"unavailable"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
@ -512,14 +519,18 @@ pub(super) async fn build_runtime_nat_stun_data(shared: &ApiShared) -> RuntimeNa
|
||||||
live_total: snapshot.live_servers.len(),
|
live_total: snapshot.live_servers.len(),
|
||||||
},
|
},
|
||||||
reflection: RuntimeNatStunReflectionBlockData {
|
reflection: RuntimeNatStunReflectionBlockData {
|
||||||
v4: snapshot.reflection_v4.map(|entry| RuntimeNatStunReflectionData {
|
v4: snapshot
|
||||||
addr: entry.addr.to_string(),
|
.reflection_v4
|
||||||
age_secs: entry.age_secs,
|
.map(|entry| RuntimeNatStunReflectionData {
|
||||||
}),
|
addr: entry.addr.to_string(),
|
||||||
v6: snapshot.reflection_v6.map(|entry| RuntimeNatStunReflectionData {
|
age_secs: entry.age_secs,
|
||||||
addr: entry.addr.to_string(),
|
}),
|
||||||
age_secs: entry.age_secs,
|
v6: snapshot
|
||||||
}),
|
.reflection_v6
|
||||||
|
.map(|entry| RuntimeNatStunReflectionData {
|
||||||
|
addr: entry.addr.to_string(),
|
||||||
|
age_secs: entry.age_secs,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
stun_backoff_remaining_ms: snapshot.stun_backoff_remaining_ms,
|
stun_backoff_remaining_ms: snapshot.stun_backoff_remaining_ms,
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use std::net::IpAddr;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::net::IpAddr;
|
||||||
use std::sync::{Mutex, OnceLock};
|
use std::sync::{Mutex, OnceLock};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
|
@ -7,8 +7,8 @@ use serde::Serialize;
|
||||||
|
|
||||||
use crate::config::{ProxyConfig, UpstreamType};
|
use crate::config::{ProxyConfig, UpstreamType};
|
||||||
use crate::network::probe::{detect_interface_ipv4, detect_interface_ipv6, is_bogon};
|
use crate::network::probe::{detect_interface_ipv4, detect_interface_ipv6, is_bogon};
|
||||||
use crate::transport::middle_proxy::{bnd_snapshot, timeskew_snapshot, upstream_bnd_snapshots};
|
|
||||||
use crate::transport::UpstreamRouteKind;
|
use crate::transport::UpstreamRouteKind;
|
||||||
|
use crate::transport::middle_proxy::{bnd_snapshot, timeskew_snapshot, upstream_bnd_snapshots};
|
||||||
|
|
||||||
use super::ApiShared;
|
use super::ApiShared;
|
||||||
|
|
||||||
|
|
@ -262,8 +262,8 @@ fn update_kdf_ewma(now_epoch_secs: u64, total_errors: u64) -> f64 {
|
||||||
let delta_errors = total_errors.saturating_sub(guard.last_total_errors);
|
let delta_errors = total_errors.saturating_sub(guard.last_total_errors);
|
||||||
let instant_rate_per_min = (delta_errors as f64) * 60.0 / (dt_secs as f64);
|
let instant_rate_per_min = (delta_errors as f64) * 60.0 / (dt_secs as f64);
|
||||||
let alpha = 1.0 - f64::exp(-(dt_secs as f64) / KDF_EWMA_TAU_SECS);
|
let alpha = 1.0 - f64::exp(-(dt_secs as f64) / KDF_EWMA_TAU_SECS);
|
||||||
guard.ewma_errors_per_min = guard.ewma_errors_per_min
|
guard.ewma_errors_per_min =
|
||||||
+ alpha * (instant_rate_per_min - guard.ewma_errors_per_min);
|
guard.ewma_errors_per_min + alpha * (instant_rate_per_min - guard.ewma_errors_per_min);
|
||||||
guard.last_epoch_secs = now_epoch_secs;
|
guard.last_epoch_secs = now_epoch_secs;
|
||||||
guard.last_total_errors = total_errors;
|
guard.last_total_errors = total_errors;
|
||||||
guard.ewma_errors_per_min
|
guard.ewma_errors_per_min
|
||||||
|
|
@ -284,6 +284,7 @@ fn map_route_kind(value: UpstreamRouteKind) -> &'static str {
|
||||||
UpstreamRouteKind::Direct => "direct",
|
UpstreamRouteKind::Direct => "direct",
|
||||||
UpstreamRouteKind::Socks4 => "socks4",
|
UpstreamRouteKind::Socks4 => "socks4",
|
||||||
UpstreamRouteKind::Socks5 => "socks5",
|
UpstreamRouteKind::Socks5 => "socks5",
|
||||||
|
UpstreamRouteKind::Shadowsocks => "shadowsocks",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use crate::config::ApiConfig;
|
use crate::config::ApiConfig;
|
||||||
use crate::stats::Stats;
|
use crate::stats::Stats;
|
||||||
use crate::transport::upstream::IpPreference;
|
|
||||||
use crate::transport::UpstreamRouteKind;
|
use crate::transport::UpstreamRouteKind;
|
||||||
|
use crate::transport::upstream::IpPreference;
|
||||||
|
|
||||||
use super::ApiShared;
|
use super::ApiShared;
|
||||||
use super::model::{
|
use super::model::{
|
||||||
|
|
@ -136,7 +136,8 @@ fn build_zero_upstream_data(stats: &Stats) -> ZeroUpstreamData {
|
||||||
.get_upstream_connect_duration_success_bucket_501_1000ms(),
|
.get_upstream_connect_duration_success_bucket_501_1000ms(),
|
||||||
connect_duration_success_bucket_gt_1000ms: stats
|
connect_duration_success_bucket_gt_1000ms: stats
|
||||||
.get_upstream_connect_duration_success_bucket_gt_1000ms(),
|
.get_upstream_connect_duration_success_bucket_gt_1000ms(),
|
||||||
connect_duration_fail_bucket_le_100ms: stats.get_upstream_connect_duration_fail_bucket_le_100ms(),
|
connect_duration_fail_bucket_le_100ms: stats
|
||||||
|
.get_upstream_connect_duration_fail_bucket_le_100ms(),
|
||||||
connect_duration_fail_bucket_101_500ms: stats
|
connect_duration_fail_bucket_101_500ms: stats
|
||||||
.get_upstream_connect_duration_fail_bucket_101_500ms(),
|
.get_upstream_connect_duration_fail_bucket_101_500ms(),
|
||||||
connect_duration_fail_bucket_501_1000ms: stats
|
connect_duration_fail_bucket_501_1000ms: stats
|
||||||
|
|
@ -178,6 +179,7 @@ pub(super) fn build_upstreams_data(shared: &ApiShared, api_cfg: &ApiConfig) -> U
|
||||||
direct_total: snapshot.summary.direct_total,
|
direct_total: snapshot.summary.direct_total,
|
||||||
socks4_total: snapshot.summary.socks4_total,
|
socks4_total: snapshot.summary.socks4_total,
|
||||||
socks5_total: snapshot.summary.socks5_total,
|
socks5_total: snapshot.summary.socks5_total,
|
||||||
|
shadowsocks_total: snapshot.summary.shadowsocks_total,
|
||||||
};
|
};
|
||||||
let upstreams = snapshot
|
let upstreams = snapshot
|
||||||
.upstreams
|
.upstreams
|
||||||
|
|
@ -391,8 +393,7 @@ async fn get_minimal_payload_cached(
|
||||||
adaptive_floor_min_writers_multi_endpoint: runtime
|
adaptive_floor_min_writers_multi_endpoint: runtime
|
||||||
.adaptive_floor_min_writers_multi_endpoint,
|
.adaptive_floor_min_writers_multi_endpoint,
|
||||||
adaptive_floor_recover_grace_secs: runtime.adaptive_floor_recover_grace_secs,
|
adaptive_floor_recover_grace_secs: runtime.adaptive_floor_recover_grace_secs,
|
||||||
adaptive_floor_writers_per_core_total: runtime
|
adaptive_floor_writers_per_core_total: runtime.adaptive_floor_writers_per_core_total,
|
||||||
.adaptive_floor_writers_per_core_total,
|
|
||||||
adaptive_floor_cpu_cores_override: runtime.adaptive_floor_cpu_cores_override,
|
adaptive_floor_cpu_cores_override: runtime.adaptive_floor_cpu_cores_override,
|
||||||
adaptive_floor_max_extra_writers_single_per_core: runtime
|
adaptive_floor_max_extra_writers_single_per_core: runtime
|
||||||
.adaptive_floor_max_extra_writers_single_per_core,
|
.adaptive_floor_max_extra_writers_single_per_core,
|
||||||
|
|
@ -400,12 +401,9 @@ async fn get_minimal_payload_cached(
|
||||||
.adaptive_floor_max_extra_writers_multi_per_core,
|
.adaptive_floor_max_extra_writers_multi_per_core,
|
||||||
adaptive_floor_max_active_writers_per_core: runtime
|
adaptive_floor_max_active_writers_per_core: runtime
|
||||||
.adaptive_floor_max_active_writers_per_core,
|
.adaptive_floor_max_active_writers_per_core,
|
||||||
adaptive_floor_max_warm_writers_per_core: runtime
|
adaptive_floor_max_warm_writers_per_core: runtime.adaptive_floor_max_warm_writers_per_core,
|
||||||
.adaptive_floor_max_warm_writers_per_core,
|
adaptive_floor_max_active_writers_global: runtime.adaptive_floor_max_active_writers_global,
|
||||||
adaptive_floor_max_active_writers_global: runtime
|
adaptive_floor_max_warm_writers_global: runtime.adaptive_floor_max_warm_writers_global,
|
||||||
.adaptive_floor_max_active_writers_global,
|
|
||||||
adaptive_floor_max_warm_writers_global: runtime
|
|
||||||
.adaptive_floor_max_warm_writers_global,
|
|
||||||
adaptive_floor_cpu_cores_detected: runtime.adaptive_floor_cpu_cores_detected,
|
adaptive_floor_cpu_cores_detected: runtime.adaptive_floor_cpu_cores_detected,
|
||||||
adaptive_floor_cpu_cores_effective: runtime.adaptive_floor_cpu_cores_effective,
|
adaptive_floor_cpu_cores_effective: runtime.adaptive_floor_cpu_cores_effective,
|
||||||
adaptive_floor_global_cap_raw: runtime.adaptive_floor_global_cap_raw,
|
adaptive_floor_global_cap_raw: runtime.adaptive_floor_global_cap_raw,
|
||||||
|
|
@ -517,6 +515,7 @@ fn map_route_kind(value: UpstreamRouteKind) -> &'static str {
|
||||||
UpstreamRouteKind::Direct => "direct",
|
UpstreamRouteKind::Direct => "direct",
|
||||||
UpstreamRouteKind::Socks4 => "socks4",
|
UpstreamRouteKind::Socks4 => "socks4",
|
||||||
UpstreamRouteKind::Socks5 => "socks5",
|
UpstreamRouteKind::Socks5 => "socks5",
|
||||||
|
UpstreamRouteKind::Shadowsocks => "shadowsocks",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@ use std::net::{IpAddr, SocketAddr};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use shadowsocks::config::ServerConfig as ShadowsocksServerConfig;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
use crate::error::{ProxyError, Result};
|
use crate::error::{ProxyError, Result};
|
||||||
|
|
||||||
|
|
@ -122,13 +123,37 @@ fn sanitize_ad_tag(ad_tag: &mut Option<String>) {
|
||||||
};
|
};
|
||||||
|
|
||||||
if !is_valid_ad_tag(tag) {
|
if !is_valid_ad_tag(tag) {
|
||||||
warn!(
|
warn!("Invalid general.ad_tag value, expected exactly 32 hex chars; ad_tag is disabled");
|
||||||
"Invalid general.ad_tag value, expected exactly 32 hex chars; ad_tag is disabled"
|
|
||||||
);
|
|
||||||
*ad_tag = None;
|
*ad_tag = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_upstreams(config: &ProxyConfig) -> Result<()> {
|
||||||
|
let has_enabled_shadowsocks = config.upstreams.iter().any(|upstream| {
|
||||||
|
upstream.enabled && matches!(upstream.upstream_type, UpstreamType::Shadowsocks { .. })
|
||||||
|
});
|
||||||
|
|
||||||
|
if has_enabled_shadowsocks && config.general.use_middle_proxy {
|
||||||
|
return Err(ProxyError::Config(
|
||||||
|
"shadowsocks upstreams require general.use_middle_proxy = false".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
for upstream in &config.upstreams {
|
||||||
|
if let UpstreamType::Shadowsocks { url, .. } = &upstream.upstream_type {
|
||||||
|
let parsed = ShadowsocksServerConfig::from_url(url)
|
||||||
|
.map_err(|error| ProxyError::Config(format!("invalid shadowsocks url: {error}")))?;
|
||||||
|
if parsed.plugin().is_some() {
|
||||||
|
return Err(ProxyError::Config(
|
||||||
|
"shadowsocks plugins are not supported".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// ============= Main Config =============
|
// ============= Main Config =============
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
|
@ -180,7 +205,8 @@ impl ProxyConfig {
|
||||||
|
|
||||||
pub(crate) fn load_with_metadata<P: AsRef<Path>>(path: P) -> Result<LoadedConfig> {
|
pub(crate) fn load_with_metadata<P: AsRef<Path>>(path: P) -> Result<LoadedConfig> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let content = std::fs::read_to_string(path).map_err(|e| ProxyError::Config(e.to_string()))?;
|
let content =
|
||||||
|
std::fs::read_to_string(path).map_err(|e| ProxyError::Config(e.to_string()))?;
|
||||||
let base_dir = path.parent().unwrap_or(Path::new("."));
|
let base_dir = path.parent().unwrap_or(Path::new("."));
|
||||||
let mut source_files = BTreeSet::new();
|
let mut source_files = BTreeSet::new();
|
||||||
source_files.insert(normalize_config_path(path));
|
source_files.insert(normalize_config_path(path));
|
||||||
|
|
@ -207,15 +233,17 @@ impl ProxyConfig {
|
||||||
.map(|table| table.contains_key("stun_servers"))
|
.map(|table| table.contains_key("stun_servers"))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
let mut config: ProxyConfig =
|
let mut config: ProxyConfig = parsed_toml
|
||||||
parsed_toml.try_into().map_err(|e| ProxyError::Config(e.to_string()))?;
|
.try_into()
|
||||||
|
.map_err(|e| ProxyError::Config(e.to_string()))?;
|
||||||
|
|
||||||
if !update_every_is_explicit && (legacy_secret_is_explicit || legacy_config_is_explicit) {
|
if !update_every_is_explicit && (legacy_secret_is_explicit || legacy_config_is_explicit) {
|
||||||
config.general.update_every = None;
|
config.general.update_every = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let legacy_nat_stun = config.general.middle_proxy_nat_stun.take();
|
let legacy_nat_stun = config.general.middle_proxy_nat_stun.take();
|
||||||
let legacy_nat_stun_servers = std::mem::take(&mut config.general.middle_proxy_nat_stun_servers);
|
let legacy_nat_stun_servers =
|
||||||
|
std::mem::take(&mut config.general.middle_proxy_nat_stun_servers);
|
||||||
let legacy_nat_stun_used = legacy_nat_stun.is_some() || !legacy_nat_stun_servers.is_empty();
|
let legacy_nat_stun_used = legacy_nat_stun.is_some() || !legacy_nat_stun_servers.is_empty();
|
||||||
if stun_servers_is_explicit {
|
if stun_servers_is_explicit {
|
||||||
let mut explicit_stun_servers = Vec::new();
|
let mut explicit_stun_servers = Vec::new();
|
||||||
|
|
@ -225,7 +253,9 @@ impl ProxyConfig {
|
||||||
config.network.stun_servers = explicit_stun_servers;
|
config.network.stun_servers = explicit_stun_servers;
|
||||||
|
|
||||||
if legacy_nat_stun_used {
|
if legacy_nat_stun_used {
|
||||||
warn!("general.middle_proxy_nat_stun and general.middle_proxy_nat_stun_servers are ignored because network.stun_servers is explicitly set");
|
warn!(
|
||||||
|
"general.middle_proxy_nat_stun and general.middle_proxy_nat_stun_servers are ignored because network.stun_servers is explicitly set"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Keep the default STUN pool unless network.stun_servers is explicitly overridden.
|
// Keep the default STUN pool unless network.stun_servers is explicitly overridden.
|
||||||
|
|
@ -240,7 +270,9 @@ impl ProxyConfig {
|
||||||
config.network.stun_servers = unified_stun_servers;
|
config.network.stun_servers = unified_stun_servers;
|
||||||
|
|
||||||
if legacy_nat_stun_used {
|
if legacy_nat_stun_used {
|
||||||
warn!("general.middle_proxy_nat_stun and general.middle_proxy_nat_stun_servers are deprecated; use network.stun_servers");
|
warn!(
|
||||||
|
"general.middle_proxy_nat_stun and general.middle_proxy_nat_stun_servers are deprecated; use network.stun_servers"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -372,13 +404,15 @@ impl ProxyConfig {
|
||||||
|
|
||||||
if !(4096..=1024 * 1024).contains(&config.general.direct_relay_copy_buf_c2s_bytes) {
|
if !(4096..=1024 * 1024).contains(&config.general.direct_relay_copy_buf_c2s_bytes) {
|
||||||
return Err(ProxyError::Config(
|
return Err(ProxyError::Config(
|
||||||
"general.direct_relay_copy_buf_c2s_bytes must be within [4096, 1048576]".to_string(),
|
"general.direct_relay_copy_buf_c2s_bytes must be within [4096, 1048576]"
|
||||||
|
.to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !(8192..=2 * 1024 * 1024).contains(&config.general.direct_relay_copy_buf_s2c_bytes) {
|
if !(8192..=2 * 1024 * 1024).contains(&config.general.direct_relay_copy_buf_s2c_bytes) {
|
||||||
return Err(ProxyError::Config(
|
return Err(ProxyError::Config(
|
||||||
"general.direct_relay_copy_buf_s2c_bytes must be within [8192, 2097152]".to_string(),
|
"general.direct_relay_copy_buf_s2c_bytes must be within [8192, 2097152]"
|
||||||
|
.to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -588,7 +622,8 @@ impl ProxyConfig {
|
||||||
|
|
||||||
if !(1..=100).contains(&config.general.me_route_backpressure_high_watermark_pct) {
|
if !(1..=100).contains(&config.general.me_route_backpressure_high_watermark_pct) {
|
||||||
return Err(ProxyError::Config(
|
return Err(ProxyError::Config(
|
||||||
"general.me_route_backpressure_high_watermark_pct must be within [1, 100]".to_string(),
|
"general.me_route_backpressure_high_watermark_pct must be within [1, 100]"
|
||||||
|
.to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -750,11 +785,15 @@ impl ProxyConfig {
|
||||||
crate::network::dns_overrides::validate_entries(&config.network.dns_overrides)?;
|
crate::network::dns_overrides::validate_entries(&config.network.dns_overrides)?;
|
||||||
|
|
||||||
if config.general.use_middle_proxy && config.network.ipv6 == Some(true) {
|
if config.general.use_middle_proxy && config.network.ipv6 == Some(true) {
|
||||||
warn!("IPv6 with Middle Proxy is experimental and may cause KDF address mismatch; consider disabling IPv6 or ME");
|
warn!(
|
||||||
|
"IPv6 with Middle Proxy is experimental and may cause KDF address mismatch; consider disabling IPv6 or ME"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Random fake_cert_len only when default is in use.
|
// Random fake_cert_len only when default is in use.
|
||||||
if !config.censorship.tls_emulation && config.censorship.fake_cert_len == default_fake_cert_len() {
|
if !config.censorship.tls_emulation
|
||||||
|
&& config.censorship.fake_cert_len == default_fake_cert_len()
|
||||||
|
{
|
||||||
config.censorship.fake_cert_len = rand::rng().gen_range(1024..4096);
|
config.censorship.fake_cert_len = rand::rng().gen_range(1024..4096);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -764,8 +803,7 @@ impl ProxyConfig {
|
||||||
let listen_tcp = config.server.listen_tcp.unwrap_or_else(|| {
|
let listen_tcp = config.server.listen_tcp.unwrap_or_else(|| {
|
||||||
if config.server.listen_unix_sock.is_some() {
|
if config.server.listen_unix_sock.is_some() {
|
||||||
// Unix socket present: TCP only if user explicitly set addresses or listeners.
|
// Unix socket present: TCP only if user explicitly set addresses or listeners.
|
||||||
config.server.listen_addr_ipv4.is_some()
|
config.server.listen_addr_ipv4.is_some() || !config.server.listeners.is_empty()
|
||||||
|| !config.server.listeners.is_empty()
|
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
@ -773,7 +811,9 @@ impl ProxyConfig {
|
||||||
|
|
||||||
// Migration: Populate listeners if empty (skip when listen_tcp = false).
|
// Migration: Populate listeners if empty (skip when listen_tcp = false).
|
||||||
if config.server.listeners.is_empty() && listen_tcp {
|
if config.server.listeners.is_empty() && listen_tcp {
|
||||||
let ipv4_str = config.server.listen_addr_ipv4
|
let ipv4_str = config
|
||||||
|
.server
|
||||||
|
.listen_addr_ipv4
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.unwrap_or("0.0.0.0");
|
.unwrap_or("0.0.0.0");
|
||||||
if let Ok(ipv4) = ipv4_str.parse::<IpAddr>() {
|
if let Ok(ipv4) = ipv4_str.parse::<IpAddr>() {
|
||||||
|
|
@ -815,7 +855,10 @@ impl ProxyConfig {
|
||||||
// Migration: Populate upstreams if empty (Default Direct).
|
// Migration: Populate upstreams if empty (Default Direct).
|
||||||
if config.upstreams.is_empty() {
|
if config.upstreams.is_empty() {
|
||||||
config.upstreams.push(UpstreamConfig {
|
config.upstreams.push(UpstreamConfig {
|
||||||
upstream_type: UpstreamType::Direct { interface: None, bind_addresses: None },
|
upstream_type: UpstreamType::Direct {
|
||||||
|
interface: None,
|
||||||
|
bind_addresses: None,
|
||||||
|
},
|
||||||
weight: 1,
|
weight: 1,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
scopes: String::new(),
|
scopes: String::new(),
|
||||||
|
|
@ -829,6 +872,8 @@ impl ProxyConfig {
|
||||||
.entry("203".to_string())
|
.entry("203".to_string())
|
||||||
.or_insert_with(|| vec!["91.105.192.100:443".to_string()]);
|
.or_insert_with(|| vec!["91.105.192.100:443".to_string()]);
|
||||||
|
|
||||||
|
validate_upstreams(&config)?;
|
||||||
|
|
||||||
Ok(LoadedConfig {
|
Ok(LoadedConfig {
|
||||||
config,
|
config,
|
||||||
source_files: source_files.into_iter().collect(),
|
source_files: source_files.into_iter().collect(),
|
||||||
|
|
@ -875,6 +920,9 @@ impl ProxyConfig {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
const TEST_SHADOWSOCKS_URL: &str =
|
||||||
|
"ss://2022-blake3-aes-256-gcm:MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDE=@127.0.0.1:8388";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn serde_defaults_remain_unchanged_for_present_sections() {
|
fn serde_defaults_remain_unchanged_for_present_sections() {
|
||||||
let toml = r#"
|
let toml = r#"
|
||||||
|
|
@ -904,10 +952,7 @@ mod tests {
|
||||||
cfg.general.me_init_retry_attempts,
|
cfg.general.me_init_retry_attempts,
|
||||||
default_me_init_retry_attempts()
|
default_me_init_retry_attempts()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(cfg.general.me2dc_fallback, default_me2dc_fallback());
|
||||||
cfg.general.me2dc_fallback,
|
|
||||||
default_me2dc_fallback()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cfg.general.proxy_config_v4_cache_path,
|
cfg.general.proxy_config_v4_cache_path,
|
||||||
default_proxy_config_v4_cache_path()
|
default_proxy_config_v4_cache_path()
|
||||||
|
|
@ -1216,11 +1261,12 @@ mod tests {
|
||||||
let path = dir.join("telemt_dc_override_test.toml");
|
let path = dir.join("telemt_dc_override_test.toml");
|
||||||
std::fs::write(&path, toml).unwrap();
|
std::fs::write(&path, toml).unwrap();
|
||||||
let cfg = ProxyConfig::load(&path).unwrap();
|
let cfg = ProxyConfig::load(&path).unwrap();
|
||||||
assert!(cfg
|
assert!(
|
||||||
.dc_overrides
|
cfg.dc_overrides
|
||||||
.get("203")
|
.get("203")
|
||||||
.map(|v| v.contains(&"91.105.192.100:443".to_string()))
|
.map(|v| v.contains(&"91.105.192.100:443".to_string()))
|
||||||
.unwrap_or(false));
|
.unwrap_or(false)
|
||||||
|
);
|
||||||
let _ = std::fs::remove_file(path);
|
let _ = std::fs::remove_file(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1407,11 +1453,9 @@ mod tests {
|
||||||
let path = dir.join("telemt_me_adaptive_floor_min_writers_out_of_range_test.toml");
|
let path = dir.join("telemt_me_adaptive_floor_min_writers_out_of_range_test.toml");
|
||||||
std::fs::write(&path, toml).unwrap();
|
std::fs::write(&path, toml).unwrap();
|
||||||
let err = ProxyConfig::load(&path).unwrap_err().to_string();
|
let err = ProxyConfig::load(&path).unwrap_err().to_string();
|
||||||
assert!(
|
assert!(err.contains(
|
||||||
err.contains(
|
"general.me_adaptive_floor_min_writers_single_endpoint must be within [1, 32]"
|
||||||
"general.me_adaptive_floor_min_writers_single_endpoint must be within [1, 32]"
|
));
|
||||||
)
|
|
||||||
);
|
|
||||||
let _ = std::fs::remove_file(path);
|
let _ = std::fs::remove_file(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1997,6 +2041,124 @@ mod tests {
|
||||||
let _ = std::fs::remove_file(path);
|
let _ = std::fs::remove_file(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shadowsocks_upstream_url_loads_successfully() {
|
||||||
|
let toml = format!(
|
||||||
|
r#"
|
||||||
|
[general]
|
||||||
|
use_middle_proxy = false
|
||||||
|
|
||||||
|
[censorship]
|
||||||
|
tls_domain = "example.com"
|
||||||
|
|
||||||
|
[access.users]
|
||||||
|
user = "00000000000000000000000000000000"
|
||||||
|
|
||||||
|
[[upstreams]]
|
||||||
|
type = "shadowsocks"
|
||||||
|
url = "{url}"
|
||||||
|
interface = "127.0.0.2"
|
||||||
|
"#,
|
||||||
|
url = TEST_SHADOWSOCKS_URL,
|
||||||
|
);
|
||||||
|
let dir = std::env::temp_dir();
|
||||||
|
let path = dir.join("telemt_shadowsocks_valid_test.toml");
|
||||||
|
std::fs::write(&path, toml).unwrap();
|
||||||
|
let cfg = ProxyConfig::load(&path).unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
&cfg.upstreams[0].upstream_type,
|
||||||
|
UpstreamType::Shadowsocks { url, interface }
|
||||||
|
if url == TEST_SHADOWSOCKS_URL && interface.as_deref() == Some("127.0.0.2")
|
||||||
|
));
|
||||||
|
|
||||||
|
let _ = std::fs::remove_file(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shadowsocks_requires_direct_mode() {
|
||||||
|
let toml = format!(
|
||||||
|
r#"
|
||||||
|
[general]
|
||||||
|
use_middle_proxy = true
|
||||||
|
|
||||||
|
[censorship]
|
||||||
|
tls_domain = "example.com"
|
||||||
|
|
||||||
|
[access.users]
|
||||||
|
user = "00000000000000000000000000000000"
|
||||||
|
|
||||||
|
[[upstreams]]
|
||||||
|
type = "shadowsocks"
|
||||||
|
url = "{url}"
|
||||||
|
"#,
|
||||||
|
url = TEST_SHADOWSOCKS_URL,
|
||||||
|
);
|
||||||
|
let dir = std::env::temp_dir();
|
||||||
|
let path = dir.join("telemt_shadowsocks_me_reject_test.toml");
|
||||||
|
std::fs::write(&path, toml).unwrap();
|
||||||
|
let err = ProxyConfig::load(&path).unwrap_err().to_string();
|
||||||
|
|
||||||
|
assert!(err.contains("shadowsocks upstreams require general.use_middle_proxy = false"));
|
||||||
|
|
||||||
|
let _ = std::fs::remove_file(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_shadowsocks_url_is_rejected() {
|
||||||
|
let toml = r#"
|
||||||
|
[general]
|
||||||
|
use_middle_proxy = false
|
||||||
|
|
||||||
|
[censorship]
|
||||||
|
tls_domain = "example.com"
|
||||||
|
|
||||||
|
[access.users]
|
||||||
|
user = "00000000000000000000000000000000"
|
||||||
|
|
||||||
|
[[upstreams]]
|
||||||
|
type = "shadowsocks"
|
||||||
|
url = "not-a-valid-ss-url"
|
||||||
|
"#;
|
||||||
|
let dir = std::env::temp_dir();
|
||||||
|
let path = dir.join("telemt_shadowsocks_invalid_url_test.toml");
|
||||||
|
std::fs::write(&path, toml).unwrap();
|
||||||
|
let err = ProxyConfig::load(&path).unwrap_err().to_string();
|
||||||
|
|
||||||
|
assert!(err.contains("invalid shadowsocks url"));
|
||||||
|
|
||||||
|
let _ = std::fs::remove_file(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shadowsocks_plugins_are_rejected() {
|
||||||
|
let toml = format!(
|
||||||
|
r#"
|
||||||
|
[general]
|
||||||
|
use_middle_proxy = false
|
||||||
|
|
||||||
|
[censorship]
|
||||||
|
tls_domain = "example.com"
|
||||||
|
|
||||||
|
[access.users]
|
||||||
|
user = "00000000000000000000000000000000"
|
||||||
|
|
||||||
|
[[upstreams]]
|
||||||
|
type = "shadowsocks"
|
||||||
|
url = "{url}?plugin=obfs-local%3Bobfs%3Dhttp"
|
||||||
|
"#,
|
||||||
|
url = TEST_SHADOWSOCKS_URL,
|
||||||
|
);
|
||||||
|
let dir = std::env::temp_dir();
|
||||||
|
let path = dir.join("telemt_shadowsocks_plugin_reject_test.toml");
|
||||||
|
std::fs::write(&path, toml).unwrap();
|
||||||
|
let err = ProxyConfig::load(&path).unwrap_err().to_string();
|
||||||
|
|
||||||
|
assert!(err.contains("shadowsocks plugins are not supported"));
|
||||||
|
|
||||||
|
let _ = std::fs::remove_file(path);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_user_ad_tag_reports_access_user_ad_tags_key() {
|
fn invalid_user_ad_tag_reports_access_user_ad_tags_key() {
|
||||||
let toml = r#"
|
let toml = r#"
|
||||||
|
|
|
||||||
|
|
@ -916,24 +916,38 @@ impl Default for GeneralConfig {
|
||||||
me_reconnect_backoff_cap_ms: default_reconnect_backoff_cap_ms(),
|
me_reconnect_backoff_cap_ms: default_reconnect_backoff_cap_ms(),
|
||||||
me_reconnect_fast_retry_count: default_me_reconnect_fast_retry_count(),
|
me_reconnect_fast_retry_count: default_me_reconnect_fast_retry_count(),
|
||||||
me_single_endpoint_shadow_writers: default_me_single_endpoint_shadow_writers(),
|
me_single_endpoint_shadow_writers: default_me_single_endpoint_shadow_writers(),
|
||||||
me_single_endpoint_outage_mode_enabled: default_me_single_endpoint_outage_mode_enabled(),
|
me_single_endpoint_outage_mode_enabled: default_me_single_endpoint_outage_mode_enabled(
|
||||||
me_single_endpoint_outage_disable_quarantine: default_me_single_endpoint_outage_disable_quarantine(),
|
),
|
||||||
me_single_endpoint_outage_backoff_min_ms: default_me_single_endpoint_outage_backoff_min_ms(),
|
me_single_endpoint_outage_disable_quarantine:
|
||||||
me_single_endpoint_outage_backoff_max_ms: default_me_single_endpoint_outage_backoff_max_ms(),
|
default_me_single_endpoint_outage_disable_quarantine(),
|
||||||
me_single_endpoint_shadow_rotate_every_secs: default_me_single_endpoint_shadow_rotate_every_secs(),
|
me_single_endpoint_outage_backoff_min_ms:
|
||||||
|
default_me_single_endpoint_outage_backoff_min_ms(),
|
||||||
|
me_single_endpoint_outage_backoff_max_ms:
|
||||||
|
default_me_single_endpoint_outage_backoff_max_ms(),
|
||||||
|
me_single_endpoint_shadow_rotate_every_secs:
|
||||||
|
default_me_single_endpoint_shadow_rotate_every_secs(),
|
||||||
me_floor_mode: MeFloorMode::default(),
|
me_floor_mode: MeFloorMode::default(),
|
||||||
me_adaptive_floor_idle_secs: default_me_adaptive_floor_idle_secs(),
|
me_adaptive_floor_idle_secs: default_me_adaptive_floor_idle_secs(),
|
||||||
me_adaptive_floor_min_writers_single_endpoint: default_me_adaptive_floor_min_writers_single_endpoint(),
|
me_adaptive_floor_min_writers_single_endpoint:
|
||||||
me_adaptive_floor_min_writers_multi_endpoint: default_me_adaptive_floor_min_writers_multi_endpoint(),
|
default_me_adaptive_floor_min_writers_single_endpoint(),
|
||||||
|
me_adaptive_floor_min_writers_multi_endpoint:
|
||||||
|
default_me_adaptive_floor_min_writers_multi_endpoint(),
|
||||||
me_adaptive_floor_recover_grace_secs: default_me_adaptive_floor_recover_grace_secs(),
|
me_adaptive_floor_recover_grace_secs: default_me_adaptive_floor_recover_grace_secs(),
|
||||||
me_adaptive_floor_writers_per_core_total: default_me_adaptive_floor_writers_per_core_total(),
|
me_adaptive_floor_writers_per_core_total:
|
||||||
|
default_me_adaptive_floor_writers_per_core_total(),
|
||||||
me_adaptive_floor_cpu_cores_override: default_me_adaptive_floor_cpu_cores_override(),
|
me_adaptive_floor_cpu_cores_override: default_me_adaptive_floor_cpu_cores_override(),
|
||||||
me_adaptive_floor_max_extra_writers_single_per_core: default_me_adaptive_floor_max_extra_writers_single_per_core(),
|
me_adaptive_floor_max_extra_writers_single_per_core:
|
||||||
me_adaptive_floor_max_extra_writers_multi_per_core: default_me_adaptive_floor_max_extra_writers_multi_per_core(),
|
default_me_adaptive_floor_max_extra_writers_single_per_core(),
|
||||||
me_adaptive_floor_max_active_writers_per_core: default_me_adaptive_floor_max_active_writers_per_core(),
|
me_adaptive_floor_max_extra_writers_multi_per_core:
|
||||||
me_adaptive_floor_max_warm_writers_per_core: default_me_adaptive_floor_max_warm_writers_per_core(),
|
default_me_adaptive_floor_max_extra_writers_multi_per_core(),
|
||||||
me_adaptive_floor_max_active_writers_global: default_me_adaptive_floor_max_active_writers_global(),
|
me_adaptive_floor_max_active_writers_per_core:
|
||||||
me_adaptive_floor_max_warm_writers_global: default_me_adaptive_floor_max_warm_writers_global(),
|
default_me_adaptive_floor_max_active_writers_per_core(),
|
||||||
|
me_adaptive_floor_max_warm_writers_per_core:
|
||||||
|
default_me_adaptive_floor_max_warm_writers_per_core(),
|
||||||
|
me_adaptive_floor_max_active_writers_global:
|
||||||
|
default_me_adaptive_floor_max_active_writers_global(),
|
||||||
|
me_adaptive_floor_max_warm_writers_global:
|
||||||
|
default_me_adaptive_floor_max_warm_writers_global(),
|
||||||
upstream_connect_retry_attempts: default_upstream_connect_retry_attempts(),
|
upstream_connect_retry_attempts: default_upstream_connect_retry_attempts(),
|
||||||
upstream_connect_retry_backoff_ms: default_upstream_connect_retry_backoff_ms(),
|
upstream_connect_retry_backoff_ms: default_upstream_connect_retry_backoff_ms(),
|
||||||
upstream_connect_budget_ms: default_upstream_connect_budget_ms(),
|
upstream_connect_budget_ms: default_upstream_connect_budget_ms(),
|
||||||
|
|
@ -948,7 +962,8 @@ impl Default for GeneralConfig {
|
||||||
me_socks_kdf_policy: MeSocksKdfPolicy::Strict,
|
me_socks_kdf_policy: MeSocksKdfPolicy::Strict,
|
||||||
me_route_backpressure_base_timeout_ms: default_me_route_backpressure_base_timeout_ms(),
|
me_route_backpressure_base_timeout_ms: default_me_route_backpressure_base_timeout_ms(),
|
||||||
me_route_backpressure_high_timeout_ms: default_me_route_backpressure_high_timeout_ms(),
|
me_route_backpressure_high_timeout_ms: default_me_route_backpressure_high_timeout_ms(),
|
||||||
me_route_backpressure_high_watermark_pct: default_me_route_backpressure_high_watermark_pct(),
|
me_route_backpressure_high_watermark_pct:
|
||||||
|
default_me_route_backpressure_high_watermark_pct(),
|
||||||
me_health_interval_ms_unhealthy: default_me_health_interval_ms_unhealthy(),
|
me_health_interval_ms_unhealthy: default_me_health_interval_ms_unhealthy(),
|
||||||
me_health_interval_ms_healthy: default_me_health_interval_ms_healthy(),
|
me_health_interval_ms_healthy: default_me_health_interval_ms_healthy(),
|
||||||
me_admission_poll_ms: default_me_admission_poll_ms(),
|
me_admission_poll_ms: default_me_admission_poll_ms(),
|
||||||
|
|
@ -972,7 +987,8 @@ impl Default for GeneralConfig {
|
||||||
me_hardswap_warmup_delay_min_ms: default_me_hardswap_warmup_delay_min_ms(),
|
me_hardswap_warmup_delay_min_ms: default_me_hardswap_warmup_delay_min_ms(),
|
||||||
me_hardswap_warmup_delay_max_ms: default_me_hardswap_warmup_delay_max_ms(),
|
me_hardswap_warmup_delay_max_ms: default_me_hardswap_warmup_delay_max_ms(),
|
||||||
me_hardswap_warmup_extra_passes: default_me_hardswap_warmup_extra_passes(),
|
me_hardswap_warmup_extra_passes: default_me_hardswap_warmup_extra_passes(),
|
||||||
me_hardswap_warmup_pass_backoff_base_ms: default_me_hardswap_warmup_pass_backoff_base_ms(),
|
me_hardswap_warmup_pass_backoff_base_ms:
|
||||||
|
default_me_hardswap_warmup_pass_backoff_base_ms(),
|
||||||
me_config_stable_snapshots: default_me_config_stable_snapshots(),
|
me_config_stable_snapshots: default_me_config_stable_snapshots(),
|
||||||
me_config_apply_cooldown_secs: default_me_config_apply_cooldown_secs(),
|
me_config_apply_cooldown_secs: default_me_config_apply_cooldown_secs(),
|
||||||
me_snapshot_require_http_2xx: default_me_snapshot_require_http_2xx(),
|
me_snapshot_require_http_2xx: default_me_snapshot_require_http_2xx(),
|
||||||
|
|
@ -1008,8 +1024,10 @@ impl GeneralConfig {
|
||||||
/// Resolve the active updater interval for ME infrastructure refresh tasks.
|
/// Resolve the active updater interval for ME infrastructure refresh tasks.
|
||||||
/// `update_every` has priority, otherwise legacy proxy_*_auto_reload_secs are used.
|
/// `update_every` has priority, otherwise legacy proxy_*_auto_reload_secs are used.
|
||||||
pub fn effective_update_every_secs(&self) -> u64 {
|
pub fn effective_update_every_secs(&self) -> u64 {
|
||||||
self.update_every
|
self.update_every.unwrap_or_else(|| {
|
||||||
.unwrap_or_else(|| self.proxy_secret_auto_reload_secs.min(self.proxy_config_auto_reload_secs))
|
self.proxy_secret_auto_reload_secs
|
||||||
|
.min(self.proxy_config_auto_reload_secs)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve periodic zero-downtime reinit interval for ME writers.
|
/// Resolve periodic zero-downtime reinit interval for ME writers.
|
||||||
|
|
@ -1401,6 +1419,11 @@ pub enum UpstreamType {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
},
|
},
|
||||||
|
Shadowsocks {
|
||||||
|
url: String,
|
||||||
|
#[serde(default)]
|
||||||
|
interface: Option<String>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|
@ -1481,7 +1504,10 @@ impl ShowLink {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for ShowLink {
|
impl Serialize for ShowLink {
|
||||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
|
fn serialize<S: serde::Serializer>(
|
||||||
|
&self,
|
||||||
|
serializer: S,
|
||||||
|
) -> std::result::Result<S::Ok, S::Error> {
|
||||||
match self {
|
match self {
|
||||||
ShowLink::None => Vec::<String>::new().serialize(serializer),
|
ShowLink::None => Vec::<String>::new().serialize(serializer),
|
||||||
ShowLink::All => serializer.serialize_str("*"),
|
ShowLink::All => serializer.serialize_str("*"),
|
||||||
|
|
@ -1491,7 +1517,9 @@ impl Serialize for ShowLink {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for ShowLink {
|
impl<'de> Deserialize<'de> for ShowLink {
|
||||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
|
fn deserialize<D: serde::Deserializer<'de>>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> std::result::Result<Self, D::Error> {
|
||||||
use serde::de;
|
use serde::de;
|
||||||
|
|
||||||
struct ShowLinkVisitor;
|
struct ShowLinkVisitor;
|
||||||
|
|
@ -1507,14 +1535,14 @@ impl<'de> Deserialize<'de> for ShowLink {
|
||||||
if v == "*" {
|
if v == "*" {
|
||||||
Ok(ShowLink::All)
|
Ok(ShowLink::All)
|
||||||
} else {
|
} else {
|
||||||
Err(de::Error::invalid_value(
|
Err(de::Error::invalid_value(de::Unexpected::Str(v), &r#""*""#))
|
||||||
de::Unexpected::Str(v),
|
|
||||||
&r#""*""#,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> std::result::Result<ShowLink, A::Error> {
|
fn visit_seq<A: de::SeqAccess<'de>>(
|
||||||
|
self,
|
||||||
|
mut seq: A,
|
||||||
|
) -> std::result::Result<ShowLink, A::Error> {
|
||||||
let mut names = Vec::new();
|
let mut names = Vec::new();
|
||||||
while let Some(name) = seq.next_element::<String>()? {
|
while let Some(name) = seq.next_element::<String>()? {
|
||||||
names.push(name);
|
names.push(name);
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ use std::io::Write;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
|
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, ReadHalf, WriteHalf, split};
|
||||||
use tokio::net::TcpStream;
|
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
|
|
@ -15,7 +14,7 @@ use crate::protocol::constants::*;
|
||||||
use crate::proxy::handshake::{HandshakeSuccess, encrypt_tg_nonce_with_ciphers, generate_tg_nonce};
|
use crate::proxy::handshake::{HandshakeSuccess, encrypt_tg_nonce_with_ciphers, generate_tg_nonce};
|
||||||
use crate::proxy::relay::relay_bidirectional;
|
use crate::proxy::relay::relay_bidirectional;
|
||||||
use crate::proxy::route_mode::{
|
use crate::proxy::route_mode::{
|
||||||
RelayRouteMode, RouteCutoverState, ROUTE_SWITCH_ERROR_MSG, affected_cutover_state,
|
ROUTE_SWITCH_ERROR_MSG, RelayRouteMode, RouteCutoverState, affected_cutover_state,
|
||||||
cutover_stagger_delay,
|
cutover_stagger_delay,
|
||||||
};
|
};
|
||||||
use crate::stats::Stats;
|
use crate::stats::Stats;
|
||||||
|
|
@ -53,7 +52,11 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
let tg_stream = upstream_manager
|
let tg_stream = upstream_manager
|
||||||
.connect(dc_addr, Some(success.dc_idx), user.strip_prefix("scope_").filter(|s| !s.is_empty()))
|
.connect(
|
||||||
|
dc_addr,
|
||||||
|
Some(success.dc_idx),
|
||||||
|
user.strip_prefix("scope_").filter(|s| !s.is_empty()),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
debug!(peer = %success.peer, dc_addr = %dc_addr, "Connected, performing TG handshake");
|
debug!(peer = %success.peer, dc_addr = %dc_addr, "Connected, performing TG handshake");
|
||||||
|
|
@ -80,11 +83,9 @@ where
|
||||||
);
|
);
|
||||||
tokio::pin!(relay_result);
|
tokio::pin!(relay_result);
|
||||||
let relay_result = loop {
|
let relay_result = loop {
|
||||||
if let Some(cutover) = affected_cutover_state(
|
if let Some(cutover) =
|
||||||
&route_rx,
|
affected_cutover_state(&route_rx, RelayRouteMode::Direct, route_snapshot.generation)
|
||||||
RelayRouteMode::Direct,
|
{
|
||||||
route_snapshot.generation,
|
|
||||||
) {
|
|
||||||
let delay = cutover_stagger_delay(session_id, cutover.generation);
|
let delay = cutover_stagger_delay(session_id, cutover.generation);
|
||||||
warn!(
|
warn!(
|
||||||
user = %user,
|
user = %user,
|
||||||
|
|
@ -135,7 +136,9 @@ fn get_dc_addr_static(dc_idx: i16, config: &ProxyConfig) -> Result<SocketAddr> {
|
||||||
for addr_str in addrs {
|
for addr_str in addrs {
|
||||||
match addr_str.parse::<SocketAddr>() {
|
match addr_str.parse::<SocketAddr>() {
|
||||||
Ok(addr) => parsed.push(addr),
|
Ok(addr) => parsed.push(addr),
|
||||||
Err(_) => warn!(dc_idx = dc_idx, addr_str = %addr_str, "Invalid DC override address in config, ignoring"),
|
Err(_) => {
|
||||||
|
warn!(dc_idx = dc_idx, addr_str = %addr_str, "Invalid DC override address in config, ignoring")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,7 +160,10 @@ fn get_dc_addr_static(dc_idx: i16, config: &ProxyConfig) -> Result<SocketAddr> {
|
||||||
|
|
||||||
// Unknown DC requested by client without override: log and fall back.
|
// Unknown DC requested by client without override: log and fall back.
|
||||||
if !config.dc_overrides.contains_key(&dc_key) {
|
if !config.dc_overrides.contains_key(&dc_key) {
|
||||||
warn!(dc_idx = dc_idx, "Requested non-standard DC with no override; falling back to default cluster");
|
warn!(
|
||||||
|
dc_idx = dc_idx,
|
||||||
|
"Requested non-standard DC with no override; falling back to default cluster"
|
||||||
|
);
|
||||||
if config.general.unknown_dc_file_log_enabled
|
if config.general.unknown_dc_file_log_enabled
|
||||||
&& let Some(path) = &config.general.unknown_dc_log_path
|
&& let Some(path) = &config.general.unknown_dc_log_path
|
||||||
&& let Ok(handle) = tokio::runtime::Handle::try_current()
|
&& let Ok(handle) = tokio::runtime::Handle::try_current()
|
||||||
|
|
@ -191,15 +197,15 @@ fn get_dc_addr_static(dc_idx: i16, config: &ProxyConfig) -> Result<SocketAddr> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn do_tg_handshake_static(
|
async fn do_tg_handshake_static<S>(
|
||||||
mut stream: TcpStream,
|
mut stream: S,
|
||||||
success: &HandshakeSuccess,
|
success: &HandshakeSuccess,
|
||||||
config: &ProxyConfig,
|
config: &ProxyConfig,
|
||||||
rng: &SecureRandom,
|
rng: &SecureRandom,
|
||||||
) -> Result<(
|
) -> Result<(CryptoReader<ReadHalf<S>>, CryptoWriter<WriteHalf<S>>)>
|
||||||
CryptoReader<tokio::net::tcp::OwnedReadHalf>,
|
where
|
||||||
CryptoWriter<tokio::net::tcp::OwnedWriteHalf>,
|
S: AsyncRead + AsyncWrite + Unpin,
|
||||||
)> {
|
{
|
||||||
let (nonce, _tg_enc_key, _tg_enc_iv, _tg_dec_key, _tg_dec_iv) = generate_tg_nonce(
|
let (nonce, _tg_enc_key, _tg_enc_iv, _tg_dec_key, _tg_dec_iv) = generate_tg_nonce(
|
||||||
success.proto_tag,
|
success.proto_tag,
|
||||||
success.dc_idx,
|
success.dc_idx,
|
||||||
|
|
@ -222,7 +228,7 @@ async fn do_tg_handshake_static(
|
||||||
stream.write_all(&encrypted_nonce).await?;
|
stream.write_all(&encrypted_nonce).await?;
|
||||||
stream.flush().await?;
|
stream.flush().await?;
|
||||||
|
|
||||||
let (read_half, write_half) = stream.into_split();
|
let (read_half, write_half) = split(stream);
|
||||||
|
|
||||||
let max_pending = config.general.crypto_pending_buffer;
|
let max_pending = config.general.crypto_pending_buffer;
|
||||||
Ok((
|
Ok((
|
||||||
|
|
|
||||||
|
|
@ -7,33 +7,29 @@ use tokio::net::TcpStream;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use tokio::net::UnixStream;
|
use tokio::net::UnixStream;
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
use tokio_rustls::client::TlsStream;
|
|
||||||
use tokio_rustls::TlsConnector;
|
use tokio_rustls::TlsConnector;
|
||||||
|
use tokio_rustls::client::TlsStream;
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
|
|
||||||
use rustls::client::ClientConfig;
|
use rustls::client::ClientConfig;
|
||||||
|
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
|
||||||
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
|
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
|
||||||
use rustls::{DigitallySignedStruct, Error as RustlsError};
|
use rustls::{DigitallySignedStruct, Error as RustlsError};
|
||||||
|
|
||||||
use x509_parser::prelude::FromDer;
|
|
||||||
use x509_parser::certificate::X509Certificate;
|
use x509_parser::certificate::X509Certificate;
|
||||||
|
use x509_parser::prelude::FromDer;
|
||||||
|
|
||||||
use crate::crypto::SecureRandom;
|
use crate::crypto::SecureRandom;
|
||||||
use crate::network::dns_overrides::resolve_socket_addr;
|
use crate::network::dns_overrides::resolve_socket_addr;
|
||||||
use crate::protocol::constants::{
|
use crate::protocol::constants::{
|
||||||
TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER, TLS_RECORD_HANDSHAKE,
|
TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER, TLS_RECORD_HANDSHAKE,
|
||||||
};
|
};
|
||||||
use crate::transport::proxy_protocol::{ProxyProtocolV1Builder, ProxyProtocolV2Builder};
|
|
||||||
use crate::tls_front::types::{
|
use crate::tls_front::types::{
|
||||||
ParsedCertificateInfo,
|
ParsedCertificateInfo, ParsedServerHello, TlsBehaviorProfile, TlsCertPayload, TlsExtension,
|
||||||
ParsedServerHello,
|
TlsFetchResult, TlsProfileSource,
|
||||||
TlsBehaviorProfile,
|
|
||||||
TlsCertPayload,
|
|
||||||
TlsExtension,
|
|
||||||
TlsFetchResult,
|
|
||||||
TlsProfileSource,
|
|
||||||
};
|
};
|
||||||
|
use crate::transport::UpstreamStream;
|
||||||
|
use crate::transport::proxy_protocol::{ProxyProtocolV1Builder, ProxyProtocolV2Builder};
|
||||||
|
|
||||||
/// No-op verifier: accept any certificate (we only need lengths and metadata).
|
/// No-op verifier: accept any certificate (we only need lengths and metadata).
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -144,21 +140,27 @@ fn build_client_hello(sni: &str, rng: &SecureRandom) -> Vec<u8> {
|
||||||
exts.extend_from_slice(&0x000au16.to_be_bytes());
|
exts.extend_from_slice(&0x000au16.to_be_bytes());
|
||||||
exts.extend_from_slice(&((2 + groups.len() * 2) as u16).to_be_bytes());
|
exts.extend_from_slice(&((2 + groups.len() * 2) as u16).to_be_bytes());
|
||||||
exts.extend_from_slice(&(groups.len() as u16 * 2).to_be_bytes());
|
exts.extend_from_slice(&(groups.len() as u16 * 2).to_be_bytes());
|
||||||
for g in groups { exts.extend_from_slice(&g.to_be_bytes()); }
|
for g in groups {
|
||||||
|
exts.extend_from_slice(&g.to_be_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
// signature_algorithms
|
// signature_algorithms
|
||||||
let sig_algs: [u16; 4] = [0x0804, 0x0805, 0x0403, 0x0503]; // rsa_pss_rsae_sha256/384, ecdsa_secp256r1_sha256, rsa_pkcs1_sha256
|
let sig_algs: [u16; 4] = [0x0804, 0x0805, 0x0403, 0x0503]; // rsa_pss_rsae_sha256/384, ecdsa_secp256r1_sha256, rsa_pkcs1_sha256
|
||||||
exts.extend_from_slice(&0x000du16.to_be_bytes());
|
exts.extend_from_slice(&0x000du16.to_be_bytes());
|
||||||
exts.extend_from_slice(&((2 + sig_algs.len() * 2) as u16).to_be_bytes());
|
exts.extend_from_slice(&((2 + sig_algs.len() * 2) as u16).to_be_bytes());
|
||||||
exts.extend_from_slice(&(sig_algs.len() as u16 * 2).to_be_bytes());
|
exts.extend_from_slice(&(sig_algs.len() as u16 * 2).to_be_bytes());
|
||||||
for a in sig_algs { exts.extend_from_slice(&a.to_be_bytes()); }
|
for a in sig_algs {
|
||||||
|
exts.extend_from_slice(&a.to_be_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
// supported_versions (TLS1.3 + TLS1.2)
|
// supported_versions (TLS1.3 + TLS1.2)
|
||||||
let versions: [u16; 2] = [0x0304, 0x0303];
|
let versions: [u16; 2] = [0x0304, 0x0303];
|
||||||
exts.extend_from_slice(&0x002bu16.to_be_bytes());
|
exts.extend_from_slice(&0x002bu16.to_be_bytes());
|
||||||
exts.extend_from_slice(&((1 + versions.len() * 2) as u16).to_be_bytes());
|
exts.extend_from_slice(&((1 + versions.len() * 2) as u16).to_be_bytes());
|
||||||
exts.push((versions.len() * 2) as u8);
|
exts.push((versions.len() * 2) as u8);
|
||||||
for v in versions { exts.extend_from_slice(&v.to_be_bytes()); }
|
for v in versions {
|
||||||
|
exts.extend_from_slice(&v.to_be_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
// key_share (x25519)
|
// key_share (x25519)
|
||||||
let key = gen_key_share(rng);
|
let key = gen_key_share(rng);
|
||||||
|
|
@ -273,7 +275,10 @@ fn parse_server_hello(body: &[u8]) -> Option<ParsedServerHello> {
|
||||||
pos += 4;
|
pos += 4;
|
||||||
let data = body.get(pos..pos + elen)?.to_vec();
|
let data = body.get(pos..pos + elen)?.to_vec();
|
||||||
pos += elen;
|
pos += elen;
|
||||||
extensions.push(TlsExtension { ext_type: etype, data });
|
extensions.push(TlsExtension {
|
||||||
|
ext_type: etype,
|
||||||
|
data,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(ParsedServerHello {
|
Some(ParsedServerHello {
|
||||||
|
|
@ -394,7 +399,7 @@ async fn connect_tcp_with_upstream(
|
||||||
port: u16,
|
port: u16,
|
||||||
connect_timeout: Duration,
|
connect_timeout: Duration,
|
||||||
upstream: Option<std::sync::Arc<crate::transport::UpstreamManager>>,
|
upstream: Option<std::sync::Arc<crate::transport::UpstreamManager>>,
|
||||||
) -> Result<TcpStream> {
|
) -> Result<UpstreamStream> {
|
||||||
if let Some(manager) = upstream {
|
if let Some(manager) = upstream {
|
||||||
if let Some(addr) = resolve_socket_addr(host, port) {
|
if let Some(addr) = resolve_socket_addr(host, port) {
|
||||||
match manager.connect(addr, None, None).await {
|
match manager.connect(addr, None, None).await {
|
||||||
|
|
@ -408,23 +413,25 @@ async fn connect_tcp_with_upstream(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Ok(mut addrs) = tokio::net::lookup_host((host, port)).await {
|
} else if let Ok(mut addrs) = tokio::net::lookup_host((host, port)).await
|
||||||
if let Some(addr) = addrs.find(|a| a.is_ipv4()) {
|
&& let Some(addr) = addrs.find(|a| a.is_ipv4())
|
||||||
match manager.connect(addr, None, None).await {
|
{
|
||||||
Ok(stream) => return Ok(stream),
|
match manager.connect(addr, None, None).await {
|
||||||
Err(e) => {
|
Ok(stream) => return Ok(stream),
|
||||||
warn!(
|
Err(e) => {
|
||||||
host = %host,
|
warn!(
|
||||||
port = port,
|
host = %host,
|
||||||
error = %e,
|
port = port,
|
||||||
"Upstream connect failed, using direct connect"
|
error = %e,
|
||||||
);
|
"Upstream connect failed, using direct connect"
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
connect_with_dns_override(host, port, connect_timeout).await
|
Ok(UpstreamStream::Tcp(
|
||||||
|
connect_with_dns_override(host, port, connect_timeout).await?,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_tls13_certificate_message(cert_chain_der: &[Vec<u8>]) -> Option<Vec<u8>> {
|
fn encode_tls13_certificate_message(cert_chain_der: &[Vec<u8>]) -> Option<Vec<u8>> {
|
||||||
|
|
@ -443,9 +450,7 @@ fn encode_tls13_certificate_message(cert_chain_der: &[Vec<u8>]) -> Option<Vec<u8
|
||||||
}
|
}
|
||||||
|
|
||||||
// Certificate = context_len(1) + certificate_list_len(3) + entries
|
// Certificate = context_len(1) + certificate_list_len(3) + entries
|
||||||
let body_len = 1usize
|
let body_len = 1usize.checked_add(3)?.checked_add(certificate_list.len())?;
|
||||||
.checked_add(3)?
|
|
||||||
.checked_add(certificate_list.len())?;
|
|
||||||
|
|
||||||
let mut message = Vec::with_capacity(4 + body_len);
|
let mut message = Vec::with_capacity(4 + body_len);
|
||||||
message.push(0x0b); // HandshakeType::certificate
|
message.push(0x0b); // HandshakeType::certificate
|
||||||
|
|
@ -549,7 +554,8 @@ async fn fetch_via_raw_tls(
|
||||||
sock = %sock_path,
|
sock = %sock_path,
|
||||||
"Raw TLS fetch using mask unix socket"
|
"Raw TLS fetch using mask unix socket"
|
||||||
);
|
);
|
||||||
return fetch_via_raw_tls_stream(stream, sni, connect_timeout, proxy_protocol).await;
|
return fetch_via_raw_tls_stream(stream, sni, connect_timeout, proxy_protocol)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
warn!(
|
warn!(
|
||||||
|
|
@ -616,12 +622,13 @@ where
|
||||||
.map(|slice| slice.to_vec())
|
.map(|slice| slice.to_vec())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let cert_chain_der: Vec<Vec<u8>> = certs.iter().map(|c| c.as_ref().to_vec()).collect();
|
let cert_chain_der: Vec<Vec<u8>> = certs.iter().map(|c| c.as_ref().to_vec()).collect();
|
||||||
let cert_payload = encode_tls13_certificate_message(&cert_chain_der).map(|certificate_message| {
|
let cert_payload =
|
||||||
TlsCertPayload {
|
encode_tls13_certificate_message(&cert_chain_der).map(|certificate_message| {
|
||||||
cert_chain_der: cert_chain_der.clone(),
|
TlsCertPayload {
|
||||||
certificate_message,
|
cert_chain_der: cert_chain_der.clone(),
|
||||||
}
|
certificate_message,
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let total_cert_len = cert_payload
|
let total_cert_len = cert_payload
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use tokio::net::UdpSocket;
|
||||||
use crate::config::{UpstreamConfig, UpstreamType};
|
use crate::config::{UpstreamConfig, UpstreamType};
|
||||||
use crate::crypto::SecureRandom;
|
use crate::crypto::SecureRandom;
|
||||||
use crate::error::ProxyError;
|
use crate::error::ProxyError;
|
||||||
|
use crate::transport::shadowsocks::sanitize_shadowsocks_url;
|
||||||
use crate::transport::{UpstreamEgressInfo, UpstreamRouteKind};
|
use crate::transport::{UpstreamEgressInfo, UpstreamRouteKind};
|
||||||
|
|
||||||
use super::MePool;
|
use super::MePool;
|
||||||
|
|
@ -40,7 +41,11 @@ pub fn format_sample_line(sample: &MePingSample) -> String {
|
||||||
let sign = if sample.dc >= 0 { "+" } else { "-" };
|
let sign = if sample.dc >= 0 { "+" } else { "-" };
|
||||||
let addr = format!("{}:{}", sample.addr.ip(), sample.addr.port());
|
let addr = format!("{}:{}", sample.addr.ip(), sample.addr.port());
|
||||||
|
|
||||||
match (sample.connect_ms, sample.handshake_ms.as_ref(), sample.error.as_ref()) {
|
match (
|
||||||
|
sample.connect_ms,
|
||||||
|
sample.handshake_ms.as_ref(),
|
||||||
|
sample.error.as_ref(),
|
||||||
|
) {
|
||||||
(Some(conn), Some(hs), None) => format!(
|
(Some(conn), Some(hs), None) => format!(
|
||||||
" {sign} {addr}\tPing: {:.0} ms / RPC: {:.0} ms / OK",
|
" {sign} {addr}\tPing: {:.0} ms / RPC: {:.0} ms / OK",
|
||||||
conn, hs
|
conn, hs
|
||||||
|
|
@ -121,6 +126,7 @@ fn route_from_egress(egress: Option<UpstreamEgressInfo>) -> Option<String> {
|
||||||
None => route,
|
None => route,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
UpstreamRouteKind::Shadowsocks => Some("shadowsocks".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,6 +238,9 @@ pub async fn format_me_route(
|
||||||
}
|
}
|
||||||
UpstreamType::Socks4 { address, .. } => format!("socks4://{address}"),
|
UpstreamType::Socks4 { address, .. } => format!("socks4://{address}"),
|
||||||
UpstreamType::Socks5 { address, .. } => format!("socks5://{address}"),
|
UpstreamType::Socks5 { address, .. } => format!("socks5://{address}"),
|
||||||
|
UpstreamType::Shadowsocks { url, .. } => sanitize_shadowsocks_url(url)
|
||||||
|
.map(|address| format!("shadowsocks://{address}"))
|
||||||
|
.unwrap_or_else(|_| "shadowsocks://invalid".to_string()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,6 +263,12 @@ pub async fn format_me_route(
|
||||||
if has_socks5 {
|
if has_socks5 {
|
||||||
kinds.push("socks5");
|
kinds.push("socks5");
|
||||||
}
|
}
|
||||||
|
if enabled_upstreams
|
||||||
|
.iter()
|
||||||
|
.any(|u| matches!(u.upstream_type, UpstreamType::Shadowsocks { .. }))
|
||||||
|
{
|
||||||
|
kinds.push("shadowsocks");
|
||||||
|
}
|
||||||
format!("mixed upstreams ({})", kinds.join(", "))
|
format!("mixed upstreams ({})", kinds.join(", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,7 +350,10 @@ pub async fn run_me_ping(pool: &Arc<MePool>, rng: &SecureRandom) -> Vec<MePingRe
|
||||||
Ok((stream, conn_rtt, upstream_egress)) => {
|
Ok((stream, conn_rtt, upstream_egress)) => {
|
||||||
connect_ms = Some(conn_rtt);
|
connect_ms = Some(conn_rtt);
|
||||||
route = route_from_egress(upstream_egress);
|
route = route_from_egress(upstream_egress);
|
||||||
match pool.handshake_only(stream, addr, upstream_egress, rng).await {
|
match pool
|
||||||
|
.handshake_only(stream, addr, upstream_egress, rng)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(hs) => {
|
Ok(hs) => {
|
||||||
handshake_ms = Some(hs.handshake_ms);
|
handshake_ms = Some(hs.handshake_ms);
|
||||||
// drop halves to close
|
// drop halves to close
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
pub mod pool;
|
pub mod pool;
|
||||||
pub mod proxy_protocol;
|
pub mod proxy_protocol;
|
||||||
|
pub mod shadowsocks;
|
||||||
pub mod socket;
|
pub mod socket;
|
||||||
pub mod socks;
|
pub mod socks;
|
||||||
pub mod upstream;
|
pub mod upstream;
|
||||||
|
|
@ -14,5 +15,8 @@ pub use socket::*;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use socks::*;
|
pub use socks::*;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use upstream::{DcPingResult, StartupPingResult, UpstreamEgressInfo, UpstreamManager, UpstreamRouteKind};
|
pub use upstream::{
|
||||||
|
DcPingResult, StartupPingResult, UpstreamEgressInfo, UpstreamManager, UpstreamRouteKind,
|
||||||
|
UpstreamStream,
|
||||||
|
};
|
||||||
pub mod middle_proxy;
|
pub mod middle_proxy;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
use std::net::{IpAddr, SocketAddr};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use shadowsocks::{
|
||||||
|
ProxyClientStream,
|
||||||
|
config::{ServerConfig, ServerType},
|
||||||
|
context::Context,
|
||||||
|
net::ConnectOpts,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::error::{ProxyError, Result};
|
||||||
|
|
||||||
|
pub(crate) type ShadowsocksStream = ProxyClientStream<shadowsocks::net::TcpStream>;
|
||||||
|
|
||||||
|
fn parse_server_config(url: &str, connect_timeout: Duration) -> Result<ServerConfig> {
|
||||||
|
let mut config = ServerConfig::from_url(url)
|
||||||
|
.map_err(|error| ProxyError::Config(format!("invalid shadowsocks url: {error}")))?;
|
||||||
|
|
||||||
|
if config.plugin().is_some() {
|
||||||
|
return Err(ProxyError::Config(
|
||||||
|
"shadowsocks plugins are not supported".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
config.set_timeout(connect_timeout);
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sanitize_shadowsocks_url(url: &str) -> Result<String> {
|
||||||
|
Ok(parse_server_config(url, Duration::from_secs(1))?
|
||||||
|
.addr()
|
||||||
|
.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect_opts_for_interface(interface: &Option<String>) -> ConnectOpts {
|
||||||
|
let mut opts = ConnectOpts::default();
|
||||||
|
if let Some(interface) = interface {
|
||||||
|
if let Ok(ip) = interface.parse::<IpAddr>() {
|
||||||
|
opts.bind_local_addr = Some(SocketAddr::new(ip, 0));
|
||||||
|
} else {
|
||||||
|
opts.bind_interface = Some(interface.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn connect_shadowsocks(
|
||||||
|
url: &str,
|
||||||
|
interface: &Option<String>,
|
||||||
|
target: SocketAddr,
|
||||||
|
connect_timeout: Duration,
|
||||||
|
) -> Result<ShadowsocksStream> {
|
||||||
|
let config = parse_server_config(url, connect_timeout)?;
|
||||||
|
let context = Context::new_shared(ServerType::Local);
|
||||||
|
let opts = connect_opts_for_interface(interface);
|
||||||
|
|
||||||
|
ProxyClientStream::connect_with_opts(context, &config, target, &opts)
|
||||||
|
.await
|
||||||
|
.map_err(ProxyError::Io)
|
||||||
|
}
|
||||||
|
|
@ -4,22 +4,28 @@
|
||||||
|
|
||||||
#![allow(deprecated)]
|
#![allow(deprecated)]
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
use std::collections::{BTreeSet, HashMap};
|
use std::collections::{BTreeSet, HashMap};
|
||||||
use std::net::{SocketAddr, IpAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
|
||||||
|
use std::task::{Context, Poll};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
use rand::Rng;
|
use tracing::{debug, info, trace, warn};
|
||||||
use tracing::{debug, warn, info, trace};
|
|
||||||
|
|
||||||
use crate::config::{UpstreamConfig, UpstreamType};
|
use crate::config::{UpstreamConfig, UpstreamType};
|
||||||
use crate::error::{Result, ProxyError};
|
use crate::error::{ProxyError, Result};
|
||||||
use crate::network::dns_overrides::{resolve_socket_addr, split_host_port};
|
use crate::network::dns_overrides::{resolve_socket_addr, split_host_port};
|
||||||
use crate::protocol::constants::{TG_DATACENTERS_V4, TG_DATACENTERS_V6, TG_DATACENTER_PORT};
|
use crate::protocol::constants::{TG_DATACENTER_PORT, TG_DATACENTERS_V4, TG_DATACENTERS_V6};
|
||||||
use crate::stats::Stats;
|
use crate::stats::Stats;
|
||||||
|
use crate::transport::shadowsocks::{
|
||||||
|
ShadowsocksStream, connect_shadowsocks, sanitize_shadowsocks_url,
|
||||||
|
};
|
||||||
use crate::transport::socket::{create_outgoing_socket_bound, resolve_interface_ip};
|
use crate::transport::socket::{create_outgoing_socket_bound, resolve_interface_ip};
|
||||||
use crate::transport::socks::{connect_socks4, connect_socks5};
|
use crate::transport::socks::{connect_socks4, connect_socks5};
|
||||||
|
|
||||||
|
|
@ -47,7 +53,10 @@ struct LatencyEma {
|
||||||
|
|
||||||
impl LatencyEma {
|
impl LatencyEma {
|
||||||
const fn new(alpha: f64) -> Self {
|
const fn new(alpha: f64) -> Self {
|
||||||
Self { value_ms: None, alpha }
|
Self {
|
||||||
|
value_ms: None,
|
||||||
|
alpha,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, sample_ms: f64) {
|
fn update(&mut self, sample_ms: f64) {
|
||||||
|
|
@ -131,11 +140,17 @@ impl UpstreamState {
|
||||||
return Some(ms);
|
return Some(ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (sum, count) = self.dc_latency.iter()
|
let (sum, count) = self
|
||||||
|
.dc_latency
|
||||||
|
.iter()
|
||||||
.filter_map(|l| l.get())
|
.filter_map(|l| l.get())
|
||||||
.fold((0.0, 0u32), |(s, c), v| (s + v, c + 1));
|
.fold((0.0, 0u32), |(s, c), v| (s + v, c + 1));
|
||||||
|
|
||||||
if count > 0 { Some(sum / count as f64) } else { None }
|
if count > 0 {
|
||||||
|
Some(sum / count as f64)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,11 +173,78 @@ pub struct StartupPingResult {
|
||||||
pub both_available: bool,
|
pub both_available: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum UpstreamStream {
|
||||||
|
Tcp(TcpStream),
|
||||||
|
Shadowsocks(Box<ShadowsocksStream>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for UpstreamStream {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Tcp(_) => f.write_str("UpstreamStream::Tcp(..)"),
|
||||||
|
Self::Shadowsocks(_) => f.write_str("UpstreamStream::Shadowsocks(..)"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpstreamStream {
|
||||||
|
pub fn into_tcp(self) -> Result<TcpStream> {
|
||||||
|
match self {
|
||||||
|
Self::Tcp(stream) => Ok(stream),
|
||||||
|
Self::Shadowsocks(_) => Err(ProxyError::Config(
|
||||||
|
"shadowsocks upstreams are not supported when general.use_middle_proxy = true"
|
||||||
|
.to_string(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncRead for UpstreamStream {
|
||||||
|
fn poll_read(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &mut ReadBuf<'_>,
|
||||||
|
) -> Poll<std::io::Result<()>> {
|
||||||
|
match self.get_mut() {
|
||||||
|
Self::Tcp(stream) => Pin::new(stream).poll_read(cx, buf),
|
||||||
|
Self::Shadowsocks(stream) => Pin::new(stream.as_mut()).poll_read(cx, buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncWrite for UpstreamStream {
|
||||||
|
fn poll_write(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<std::io::Result<usize>> {
|
||||||
|
match self.get_mut() {
|
||||||
|
Self::Tcp(stream) => Pin::new(stream).poll_write(cx, buf),
|
||||||
|
Self::Shadowsocks(stream) => Pin::new(stream.as_mut()).poll_write(cx, buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
|
||||||
|
match self.get_mut() {
|
||||||
|
Self::Tcp(stream) => Pin::new(stream).poll_flush(cx),
|
||||||
|
Self::Shadowsocks(stream) => Pin::new(stream.as_mut()).poll_flush(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
|
||||||
|
match self.get_mut() {
|
||||||
|
Self::Tcp(stream) => Pin::new(stream).poll_shutdown(cx),
|
||||||
|
Self::Shadowsocks(stream) => Pin::new(stream.as_mut()).poll_shutdown(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum UpstreamRouteKind {
|
pub enum UpstreamRouteKind {
|
||||||
Direct,
|
Direct,
|
||||||
Socks4,
|
Socks4,
|
||||||
Socks5,
|
Socks5,
|
||||||
|
Shadowsocks,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -194,6 +276,7 @@ pub struct UpstreamApiSummarySnapshot {
|
||||||
pub direct_total: usize,
|
pub direct_total: usize,
|
||||||
pub socks4_total: usize,
|
pub socks4_total: usize,
|
||||||
pub socks5_total: usize,
|
pub socks5_total: usize,
|
||||||
|
pub shadowsocks_total: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -253,7 +336,8 @@ impl UpstreamManager {
|
||||||
connect_failfast_hard_errors: bool,
|
connect_failfast_hard_errors: bool,
|
||||||
stats: Arc<Stats>,
|
stats: Arc<Stats>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let states = configs.into_iter()
|
let states = configs
|
||||||
|
.into_iter()
|
||||||
.filter(|c| c.enabled)
|
.filter(|c| c.enabled)
|
||||||
.map(UpstreamState::new)
|
.map(UpstreamState::new)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
@ -311,20 +395,13 @@ impl UpstreamManager {
|
||||||
summary.unhealthy_total += 1;
|
summary.unhealthy_total += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (route_kind, address) = match &upstream.config.upstream_type {
|
let (route_kind, address) = Self::describe_upstream(&upstream.config.upstream_type);
|
||||||
UpstreamType::Direct { .. } => {
|
match route_kind {
|
||||||
summary.direct_total += 1;
|
UpstreamRouteKind::Direct => summary.direct_total += 1,
|
||||||
(UpstreamRouteKind::Direct, "direct".to_string())
|
UpstreamRouteKind::Socks4 => summary.socks4_total += 1,
|
||||||
}
|
UpstreamRouteKind::Socks5 => summary.socks5_total += 1,
|
||||||
UpstreamType::Socks4 { address, .. } => {
|
UpstreamRouteKind::Shadowsocks => summary.shadowsocks_total += 1,
|
||||||
summary.socks4_total += 1;
|
}
|
||||||
(UpstreamRouteKind::Socks4, address.clone())
|
|
||||||
}
|
|
||||||
UpstreamType::Socks5 { address, .. } => {
|
|
||||||
summary.socks5_total += 1;
|
|
||||||
(UpstreamRouteKind::Socks5, address.clone())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut dc = Vec::with_capacity(NUM_DCS);
|
let mut dc = Vec::with_capacity(NUM_DCS);
|
||||||
for dc_idx in 0..NUM_DCS {
|
for dc_idx in 0..NUM_DCS {
|
||||||
|
|
@ -352,6 +429,18 @@ impl UpstreamManager {
|
||||||
Some(UpstreamApiSnapshot { summary, upstreams })
|
Some(UpstreamApiSnapshot { summary, upstreams })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn describe_upstream(upstream_type: &UpstreamType) -> (UpstreamRouteKind, String) {
|
||||||
|
match upstream_type {
|
||||||
|
UpstreamType::Direct { .. } => (UpstreamRouteKind::Direct, "direct".to_string()),
|
||||||
|
UpstreamType::Socks4 { address, .. } => (UpstreamRouteKind::Socks4, address.clone()),
|
||||||
|
UpstreamType::Socks5 { address, .. } => (UpstreamRouteKind::Socks5, address.clone()),
|
||||||
|
UpstreamType::Shadowsocks { url, .. } => (
|
||||||
|
UpstreamRouteKind::Shadowsocks,
|
||||||
|
sanitize_shadowsocks_url(url).unwrap_or_else(|_| "invalid".to_string()),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn api_policy_snapshot(&self) -> UpstreamApiPolicySnapshot {
|
pub fn api_policy_snapshot(&self) -> UpstreamApiPolicySnapshot {
|
||||||
UpstreamApiPolicySnapshot {
|
UpstreamApiPolicySnapshot {
|
||||||
connect_retry_attempts: self.connect_retry_attempts,
|
connect_retry_attempts: self.connect_retry_attempts,
|
||||||
|
|
@ -539,44 +628,44 @@ impl UpstreamManager {
|
||||||
// Scope filter:
|
// Scope filter:
|
||||||
// If scope is set: only scoped and matched items
|
// If scope is set: only scoped and matched items
|
||||||
// If scope is not set: only unscoped items
|
// If scope is not set: only unscoped items
|
||||||
let filtered_upstreams : Vec<usize> = upstreams.iter()
|
let filtered_upstreams: Vec<usize> = upstreams
|
||||||
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(_, u)| {
|
.filter(|(_, u)| {
|
||||||
scope.map_or(
|
scope.map_or(u.config.scopes.is_empty(), |req_scope| {
|
||||||
u.config.scopes.is_empty(),
|
u.config
|
||||||
|req_scope| {
|
.scopes
|
||||||
u.config.scopes
|
.split(',')
|
||||||
.split(',')
|
.map(str::trim)
|
||||||
.map(str::trim)
|
.any(|s| s == req_scope)
|
||||||
.any(|s| s == req_scope)
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.map(|(i, _)| i)
|
.map(|(i, _)| i)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Healthy filter
|
// Healthy filter
|
||||||
let healthy: Vec<usize> = filtered_upstreams.iter()
|
let healthy: Vec<usize> = filtered_upstreams
|
||||||
|
.iter()
|
||||||
.filter(|&&i| upstreams[i].healthy)
|
.filter(|&&i| upstreams[i].healthy)
|
||||||
.copied()
|
.copied()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if filtered_upstreams.is_empty() {
|
if filtered_upstreams.is_empty() {
|
||||||
if Self::should_emit_warn(
|
if Self::should_emit_warn(self.no_upstreams_warn_epoch_ms.as_ref(), 5_000) {
|
||||||
self.no_upstreams_warn_epoch_ms.as_ref(),
|
warn!(
|
||||||
5_000,
|
scope = scope,
|
||||||
) {
|
"No upstreams available! Using first (direct?)"
|
||||||
warn!(scope = scope, "No upstreams available! Using first (direct?)");
|
);
|
||||||
}
|
}
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if healthy.is_empty() {
|
if healthy.is_empty() {
|
||||||
if Self::should_emit_warn(
|
if Self::should_emit_warn(self.no_healthy_warn_epoch_ms.as_ref(), 5_000) {
|
||||||
self.no_healthy_warn_epoch_ms.as_ref(),
|
warn!(
|
||||||
5_000,
|
scope = scope,
|
||||||
) {
|
"No healthy upstreams available! Using random."
|
||||||
warn!(scope = scope, "No healthy upstreams available! Using random.");
|
);
|
||||||
}
|
}
|
||||||
return Some(filtered_upstreams[rand::rng().gen_range(0..filtered_upstreams.len())]);
|
return Some(filtered_upstreams[rand::rng().gen_range(0..filtered_upstreams.len())]);
|
||||||
}
|
}
|
||||||
|
|
@ -585,14 +674,18 @@ impl UpstreamManager {
|
||||||
return Some(healthy[0]);
|
return Some(healthy[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let weights: Vec<(usize, f64)> = healthy.iter().map(|&i| {
|
let weights: Vec<(usize, f64)> = healthy
|
||||||
let base = upstreams[i].config.weight as f64;
|
.iter()
|
||||||
let latency_factor = upstreams[i].effective_latency(dc_idx)
|
.map(|&i| {
|
||||||
.map(|ms| if ms > 1.0 { 1000.0 / ms } else { 1000.0 })
|
let base = upstreams[i].config.weight as f64;
|
||||||
.unwrap_or(1.0);
|
let latency_factor = upstreams[i]
|
||||||
|
.effective_latency(dc_idx)
|
||||||
|
.map(|ms| if ms > 1.0 { 1000.0 / ms } else { 1000.0 })
|
||||||
|
.unwrap_or(1.0);
|
||||||
|
|
||||||
(i, base * latency_factor)
|
(i, base * latency_factor)
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let total: f64 = weights.iter().map(|(_, w)| w).sum();
|
let total: f64 = weights.iter().map(|(_, w)| w).sum();
|
||||||
|
|
||||||
|
|
@ -620,8 +713,34 @@ impl UpstreamManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Connect to target through a selected upstream.
|
/// Connect to target through a selected upstream.
|
||||||
pub async fn connect(&self, target: SocketAddr, dc_idx: Option<i16>, scope: Option<&str>) -> Result<TcpStream> {
|
pub async fn connect(
|
||||||
let (stream, _) = self.connect_with_details(target, dc_idx, scope).await?;
|
&self,
|
||||||
|
target: SocketAddr,
|
||||||
|
dc_idx: Option<i16>,
|
||||||
|
scope: Option<&str>,
|
||||||
|
) -> Result<UpstreamStream> {
|
||||||
|
let idx = self
|
||||||
|
.select_upstream(dc_idx, scope)
|
||||||
|
.await
|
||||||
|
.ok_or_else(|| ProxyError::Config("No upstreams available".to_string()))?;
|
||||||
|
|
||||||
|
let mut upstream = {
|
||||||
|
let guard = self.upstreams.read().await;
|
||||||
|
guard[idx].config.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(s) = scope {
|
||||||
|
upstream.selected_scope = s.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let bind_rr = {
|
||||||
|
let guard = self.upstreams.read().await;
|
||||||
|
guard.get(idx).map(|u| u.bind_rr.clone())
|
||||||
|
};
|
||||||
|
|
||||||
|
let (stream, _) = self
|
||||||
|
.connect_selected_upstream(idx, upstream, target, dc_idx, bind_rr)
|
||||||
|
.await?;
|
||||||
Ok(stream)
|
Ok(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -632,7 +751,9 @@ impl UpstreamManager {
|
||||||
dc_idx: Option<i16>,
|
dc_idx: Option<i16>,
|
||||||
scope: Option<&str>,
|
scope: Option<&str>,
|
||||||
) -> Result<(TcpStream, UpstreamEgressInfo)> {
|
) -> Result<(TcpStream, UpstreamEgressInfo)> {
|
||||||
let idx = self.select_upstream(dc_idx, scope).await
|
let idx = self
|
||||||
|
.select_upstream(dc_idx, scope)
|
||||||
|
.await
|
||||||
.ok_or_else(|| ProxyError::Config("No upstreams available".to_string()))?;
|
.ok_or_else(|| ProxyError::Config("No upstreams available".to_string()))?;
|
||||||
|
|
||||||
let mut upstream = {
|
let mut upstream = {
|
||||||
|
|
@ -650,6 +771,20 @@ impl UpstreamManager {
|
||||||
guard.get(idx).map(|u| u.bind_rr.clone())
|
guard.get(idx).map(|u| u.bind_rr.clone())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (stream, egress) = self
|
||||||
|
.connect_selected_upstream(idx, upstream, target, dc_idx, bind_rr)
|
||||||
|
.await?;
|
||||||
|
Ok((stream.into_tcp()?, egress))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn connect_selected_upstream(
|
||||||
|
&self,
|
||||||
|
idx: usize,
|
||||||
|
upstream: UpstreamConfig,
|
||||||
|
target: SocketAddr,
|
||||||
|
dc_idx: Option<i16>,
|
||||||
|
bind_rr: Option<Arc<AtomicUsize>>,
|
||||||
|
) -> Result<(UpstreamStream, UpstreamEgressInfo)> {
|
||||||
let connect_started_at = Instant::now();
|
let connect_started_at = Instant::now();
|
||||||
let mut last_error: Option<ProxyError> = None;
|
let mut last_error: Option<ProxyError> = None;
|
||||||
let mut attempts_used = 0u32;
|
let mut attempts_used = 0u32;
|
||||||
|
|
@ -662,8 +797,8 @@ impl UpstreamManager {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let remaining_budget = self.connect_budget.saturating_sub(elapsed);
|
let remaining_budget = self.connect_budget.saturating_sub(elapsed);
|
||||||
let attempt_timeout = Duration::from_secs(DIRECT_CONNECT_TIMEOUT_SECS)
|
let attempt_timeout =
|
||||||
.min(remaining_budget);
|
Duration::from_secs(DIRECT_CONNECT_TIMEOUT_SECS).min(remaining_budget);
|
||||||
if attempt_timeout.is_zero() {
|
if attempt_timeout.is_zero() {
|
||||||
last_error = Some(ProxyError::ConnectionTimeout {
|
last_error = Some(ProxyError::ConnectionTimeout {
|
||||||
addr: target.to_string(),
|
addr: target.to_string(),
|
||||||
|
|
@ -786,9 +921,12 @@ impl UpstreamManager {
|
||||||
target: SocketAddr,
|
target: SocketAddr,
|
||||||
bind_rr: Option<Arc<AtomicUsize>>,
|
bind_rr: Option<Arc<AtomicUsize>>,
|
||||||
connect_timeout: Duration,
|
connect_timeout: Duration,
|
||||||
) -> Result<(TcpStream, UpstreamEgressInfo)> {
|
) -> Result<(UpstreamStream, UpstreamEgressInfo)> {
|
||||||
match &config.upstream_type {
|
match &config.upstream_type {
|
||||||
UpstreamType::Direct { interface, bind_addresses } => {
|
UpstreamType::Direct {
|
||||||
|
interface,
|
||||||
|
bind_addresses,
|
||||||
|
} => {
|
||||||
let bind_ip = Self::resolve_bind_address(
|
let bind_ip = Self::resolve_bind_address(
|
||||||
interface,
|
interface,
|
||||||
bind_addresses,
|
bind_addresses,
|
||||||
|
|
@ -796,9 +934,7 @@ impl UpstreamManager {
|
||||||
bind_rr.as_deref(),
|
bind_rr.as_deref(),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
if bind_ip.is_none()
|
if bind_ip.is_none() && bind_addresses.as_ref().is_some_and(|v| !v.is_empty()) {
|
||||||
&& bind_addresses.as_ref().is_some_and(|v| !v.is_empty())
|
|
||||||
{
|
|
||||||
return Err(ProxyError::Config(format!(
|
return Err(ProxyError::Config(format!(
|
||||||
"No valid bind_addresses for target family {target}"
|
"No valid bind_addresses for target family {target}"
|
||||||
)));
|
)));
|
||||||
|
|
@ -813,8 +949,10 @@ impl UpstreamManager {
|
||||||
|
|
||||||
socket.set_nonblocking(true)?;
|
socket.set_nonblocking(true)?;
|
||||||
match socket.connect(&target.into()) {
|
match socket.connect(&target.into()) {
|
||||||
Ok(()) => {},
|
Ok(()) => {}
|
||||||
Err(err) if err.raw_os_error() == Some(libc::EINPROGRESS) || err.kind() == std::io::ErrorKind::WouldBlock => {},
|
Err(err)
|
||||||
|
if err.raw_os_error() == Some(libc::EINPROGRESS)
|
||||||
|
|| err.kind() == std::io::ErrorKind::WouldBlock => {}
|
||||||
Err(err) => return Err(ProxyError::Io(err)),
|
Err(err) => return Err(ProxyError::Io(err)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -836,7 +974,7 @@ impl UpstreamManager {
|
||||||
|
|
||||||
let local_addr = stream.local_addr().ok();
|
let local_addr = stream.local_addr().ok();
|
||||||
Ok((
|
Ok((
|
||||||
stream,
|
UpstreamStream::Tcp(stream),
|
||||||
UpstreamEgressInfo {
|
UpstreamEgressInfo {
|
||||||
upstream_id,
|
upstream_id,
|
||||||
route_kind: UpstreamRouteKind::Direct,
|
route_kind: UpstreamRouteKind::Direct,
|
||||||
|
|
@ -846,8 +984,12 @@ impl UpstreamManager {
|
||||||
socks_proxy_addr: None,
|
socks_proxy_addr: None,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
},
|
}
|
||||||
UpstreamType::Socks4 { address, interface, user_id } => {
|
UpstreamType::Socks4 {
|
||||||
|
address,
|
||||||
|
interface,
|
||||||
|
user_id,
|
||||||
|
} => {
|
||||||
// Try to parse as SocketAddr first (IP:port), otherwise treat as hostname:port
|
// Try to parse as SocketAddr first (IP:port), otherwise treat as hostname:port
|
||||||
let mut stream = if let Ok(proxy_addr) = address.parse::<SocketAddr>() {
|
let mut stream = if let Ok(proxy_addr) = address.parse::<SocketAddr>() {
|
||||||
// IP:port format - use socket with optional interface binding
|
// IP:port format - use socket with optional interface binding
|
||||||
|
|
@ -863,8 +1005,10 @@ impl UpstreamManager {
|
||||||
|
|
||||||
socket.set_nonblocking(true)?;
|
socket.set_nonblocking(true)?;
|
||||||
match socket.connect(&proxy_addr.into()) {
|
match socket.connect(&proxy_addr.into()) {
|
||||||
Ok(()) => {},
|
Ok(()) => {}
|
||||||
Err(err) if err.raw_os_error() == Some(libc::EINPROGRESS) || err.kind() == std::io::ErrorKind::WouldBlock => {},
|
Err(err)
|
||||||
|
if err.raw_os_error() == Some(libc::EINPROGRESS)
|
||||||
|
|| err.kind() == std::io::ErrorKind::WouldBlock => {}
|
||||||
Err(err) => return Err(ProxyError::Io(err)),
|
Err(err) => return Err(ProxyError::Io(err)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -888,14 +1032,16 @@ impl UpstreamManager {
|
||||||
// Hostname:port format - use tokio DNS resolution
|
// Hostname:port format - use tokio DNS resolution
|
||||||
// Note: interface binding is not supported for hostnames
|
// Note: interface binding is not supported for hostnames
|
||||||
if interface.is_some() {
|
if interface.is_some() {
|
||||||
warn!("SOCKS4 interface binding is not supported for hostname addresses, ignoring");
|
warn!(
|
||||||
|
"SOCKS4 interface binding is not supported for hostname addresses, ignoring"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Self::connect_hostname_with_dns_override(address, connect_timeout).await?
|
Self::connect_hostname_with_dns_override(address, connect_timeout).await?
|
||||||
};
|
};
|
||||||
|
|
||||||
// replace socks user_id with config.selected_scope, if set
|
// replace socks user_id with config.selected_scope, if set
|
||||||
let scope: Option<&str> = Some(config.selected_scope.as_str())
|
let scope: Option<&str> =
|
||||||
.filter(|s| !s.is_empty());
|
Some(config.selected_scope.as_str()).filter(|s| !s.is_empty());
|
||||||
let _user_id: Option<&str> = scope.or(user_id.as_deref());
|
let _user_id: Option<&str> = scope.or(user_id.as_deref());
|
||||||
|
|
||||||
let bound = match tokio::time::timeout(
|
let bound = match tokio::time::timeout(
|
||||||
|
|
@ -915,7 +1061,7 @@ impl UpstreamManager {
|
||||||
let local_addr = stream.local_addr().ok();
|
let local_addr = stream.local_addr().ok();
|
||||||
let socks_proxy_addr = stream.peer_addr().ok();
|
let socks_proxy_addr = stream.peer_addr().ok();
|
||||||
Ok((
|
Ok((
|
||||||
stream,
|
UpstreamStream::Tcp(stream),
|
||||||
UpstreamEgressInfo {
|
UpstreamEgressInfo {
|
||||||
upstream_id,
|
upstream_id,
|
||||||
route_kind: UpstreamRouteKind::Socks4,
|
route_kind: UpstreamRouteKind::Socks4,
|
||||||
|
|
@ -925,8 +1071,13 @@ impl UpstreamManager {
|
||||||
socks_proxy_addr,
|
socks_proxy_addr,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
},
|
}
|
||||||
UpstreamType::Socks5 { address, interface, username, password } => {
|
UpstreamType::Socks5 {
|
||||||
|
address,
|
||||||
|
interface,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
} => {
|
||||||
// Try to parse as SocketAddr first (IP:port), otherwise treat as hostname:port
|
// Try to parse as SocketAddr first (IP:port), otherwise treat as hostname:port
|
||||||
let mut stream = if let Ok(proxy_addr) = address.parse::<SocketAddr>() {
|
let mut stream = if let Ok(proxy_addr) = address.parse::<SocketAddr>() {
|
||||||
// IP:port format - use socket with optional interface binding
|
// IP:port format - use socket with optional interface binding
|
||||||
|
|
@ -942,8 +1093,10 @@ impl UpstreamManager {
|
||||||
|
|
||||||
socket.set_nonblocking(true)?;
|
socket.set_nonblocking(true)?;
|
||||||
match socket.connect(&proxy_addr.into()) {
|
match socket.connect(&proxy_addr.into()) {
|
||||||
Ok(()) => {},
|
Ok(()) => {}
|
||||||
Err(err) if err.raw_os_error() == Some(libc::EINPROGRESS) || err.kind() == std::io::ErrorKind::WouldBlock => {},
|
Err(err)
|
||||||
|
if err.raw_os_error() == Some(libc::EINPROGRESS)
|
||||||
|
|| err.kind() == std::io::ErrorKind::WouldBlock => {}
|
||||||
Err(err) => return Err(ProxyError::Io(err)),
|
Err(err) => return Err(ProxyError::Io(err)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -967,15 +1120,17 @@ impl UpstreamManager {
|
||||||
// Hostname:port format - use tokio DNS resolution
|
// Hostname:port format - use tokio DNS resolution
|
||||||
// Note: interface binding is not supported for hostnames
|
// Note: interface binding is not supported for hostnames
|
||||||
if interface.is_some() {
|
if interface.is_some() {
|
||||||
warn!("SOCKS5 interface binding is not supported for hostname addresses, ignoring");
|
warn!(
|
||||||
|
"SOCKS5 interface binding is not supported for hostname addresses, ignoring"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Self::connect_hostname_with_dns_override(address, connect_timeout).await?
|
Self::connect_hostname_with_dns_override(address, connect_timeout).await?
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!(config = ?config, "Socks5 connection");
|
debug!(config = ?config, "Socks5 connection");
|
||||||
// replace socks user:pass with config.selected_scope, if set
|
// replace socks user:pass with config.selected_scope, if set
|
||||||
let scope: Option<&str> = Some(config.selected_scope.as_str())
|
let scope: Option<&str> =
|
||||||
.filter(|s| !s.is_empty());
|
Some(config.selected_scope.as_str()).filter(|s| !s.is_empty());
|
||||||
let _username: Option<&str> = scope.or(username.as_deref());
|
let _username: Option<&str> = scope.or(username.as_deref());
|
||||||
let _password: Option<&str> = scope.or(password.as_deref());
|
let _password: Option<&str> = scope.or(password.as_deref());
|
||||||
|
|
||||||
|
|
@ -996,7 +1151,7 @@ impl UpstreamManager {
|
||||||
let local_addr = stream.local_addr().ok();
|
let local_addr = stream.local_addr().ok();
|
||||||
let socks_proxy_addr = stream.peer_addr().ok();
|
let socks_proxy_addr = stream.peer_addr().ok();
|
||||||
Ok((
|
Ok((
|
||||||
stream,
|
UpstreamStream::Tcp(stream),
|
||||||
UpstreamEgressInfo {
|
UpstreamEgressInfo {
|
||||||
upstream_id,
|
upstream_id,
|
||||||
route_kind: UpstreamRouteKind::Socks5,
|
route_kind: UpstreamRouteKind::Socks5,
|
||||||
|
|
@ -1006,7 +1161,22 @@ impl UpstreamManager {
|
||||||
socks_proxy_addr,
|
socks_proxy_addr,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
},
|
}
|
||||||
|
UpstreamType::Shadowsocks { url, interface } => {
|
||||||
|
let stream = connect_shadowsocks(url, interface, target, connect_timeout).await?;
|
||||||
|
let local_addr = stream.get_ref().local_addr().ok();
|
||||||
|
Ok((
|
||||||
|
UpstreamStream::Shadowsocks(Box::new(stream)),
|
||||||
|
UpstreamEgressInfo {
|
||||||
|
upstream_id,
|
||||||
|
route_kind: UpstreamRouteKind::Shadowsocks,
|
||||||
|
local_addr,
|
||||||
|
direct_bind_ip: None,
|
||||||
|
socks_bound_addr: None,
|
||||||
|
socks_proxy_addr: None,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1023,7 +1193,9 @@ impl UpstreamManager {
|
||||||
) -> Vec<StartupPingResult> {
|
) -> Vec<StartupPingResult> {
|
||||||
let upstreams: Vec<(usize, UpstreamConfig, Arc<AtomicUsize>)> = {
|
let upstreams: Vec<(usize, UpstreamConfig, Arc<AtomicUsize>)> = {
|
||||||
let guard = self.upstreams.read().await;
|
let guard = self.upstreams.read().await;
|
||||||
guard.iter().enumerate()
|
guard
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
.map(|(i, u)| (i, u.config.clone(), u.bind_rr.clone()))
|
.map(|(i, u)| (i, u.config.clone(), u.bind_rr.clone()))
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
@ -1051,6 +1223,11 @@ impl UpstreamManager {
|
||||||
}
|
}
|
||||||
UpstreamType::Socks4 { address, .. } => format!("socks4://{}", address),
|
UpstreamType::Socks4 { address, .. } => format!("socks4://{}", address),
|
||||||
UpstreamType::Socks5 { address, .. } => format!("socks5://{}", address),
|
UpstreamType::Socks5 { address, .. } => format!("socks5://{}", address),
|
||||||
|
UpstreamType::Shadowsocks { url, .. } => {
|
||||||
|
let address =
|
||||||
|
sanitize_shadowsocks_url(url).unwrap_or_else(|_| "invalid".to_string());
|
||||||
|
format!("shadowsocks://{address}")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut v6_results = Vec::with_capacity(NUM_DCS);
|
let mut v6_results = Vec::with_capacity(NUM_DCS);
|
||||||
|
|
@ -1061,8 +1238,14 @@ impl UpstreamManager {
|
||||||
|
|
||||||
let result = tokio::time::timeout(
|
let result = tokio::time::timeout(
|
||||||
Duration::from_secs(DC_PING_TIMEOUT_SECS),
|
Duration::from_secs(DC_PING_TIMEOUT_SECS),
|
||||||
self.ping_single_dc(*upstream_idx, upstream_config, Some(bind_rr.clone()), addr_v6)
|
self.ping_single_dc(
|
||||||
).await;
|
*upstream_idx,
|
||||||
|
upstream_config,
|
||||||
|
Some(bind_rr.clone()),
|
||||||
|
addr_v6,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let ping_result = match result {
|
let ping_result = match result {
|
||||||
Ok(Ok(rtt_ms)) => {
|
Ok(Ok(rtt_ms)) => {
|
||||||
|
|
@ -1112,8 +1295,14 @@ impl UpstreamManager {
|
||||||
|
|
||||||
let result = tokio::time::timeout(
|
let result = tokio::time::timeout(
|
||||||
Duration::from_secs(DC_PING_TIMEOUT_SECS),
|
Duration::from_secs(DC_PING_TIMEOUT_SECS),
|
||||||
self.ping_single_dc(*upstream_idx, upstream_config, Some(bind_rr.clone()), addr_v4)
|
self.ping_single_dc(
|
||||||
).await;
|
*upstream_idx,
|
||||||
|
upstream_config,
|
||||||
|
Some(bind_rr.clone()),
|
||||||
|
addr_v4,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let ping_result = match result {
|
let ping_result = match result {
|
||||||
Ok(Ok(rtt_ms)) => {
|
Ok(Ok(rtt_ms)) => {
|
||||||
|
|
@ -1162,7 +1351,7 @@ impl UpstreamManager {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
warn!(dc = %dc_key, "Invalid dc_overrides key, skipping");
|
warn!(dc = %dc_key, "Invalid dc_overrides key, skipping");
|
||||||
continue;
|
continue;
|
||||||
},
|
}
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
let dc_idx = dc_num as usize;
|
let dc_idx = dc_num as usize;
|
||||||
|
|
@ -1175,8 +1364,14 @@ impl UpstreamManager {
|
||||||
}
|
}
|
||||||
let result = tokio::time::timeout(
|
let result = tokio::time::timeout(
|
||||||
Duration::from_secs(DC_PING_TIMEOUT_SECS),
|
Duration::from_secs(DC_PING_TIMEOUT_SECS),
|
||||||
self.ping_single_dc(*upstream_idx, upstream_config, Some(bind_rr.clone()), addr)
|
self.ping_single_dc(
|
||||||
).await;
|
*upstream_idx,
|
||||||
|
upstream_config,
|
||||||
|
Some(bind_rr.clone()),
|
||||||
|
addr,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let ping_result = match result {
|
let ping_result = match result {
|
||||||
Ok(Ok(rtt_ms)) => DcPingResult {
|
Ok(Ok(rtt_ms)) => DcPingResult {
|
||||||
|
|
@ -1205,7 +1400,9 @@ impl UpstreamManager {
|
||||||
v4_results.push(ping_result);
|
v4_results.push(ping_result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => warn!(dc = %dc_idx, addr = %addr_str, "Invalid dc_overrides address, skipping"),
|
Err(_) => {
|
||||||
|
warn!(dc = %dc_idx, addr = %addr_str, "Invalid dc_overrides address, skipping")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1381,12 +1578,8 @@ impl UpstreamManager {
|
||||||
ipv6_enabled: bool,
|
ipv6_enabled: bool,
|
||||||
dc_overrides: HashMap<String, Vec<String>>,
|
dc_overrides: HashMap<String, Vec<String>>,
|
||||||
) {
|
) {
|
||||||
let groups = Self::build_health_check_groups(
|
let groups =
|
||||||
prefer_ipv6,
|
Self::build_health_check_groups(prefer_ipv6, ipv4_enabled, ipv6_enabled, &dc_overrides);
|
||||||
ipv4_enabled,
|
|
||||||
ipv6_enabled,
|
|
||||||
&dc_overrides,
|
|
||||||
);
|
|
||||||
let required_healthy_groups = Self::required_healthy_group_count(groups.len());
|
let required_healthy_groups = Self::required_healthy_group_count(groups.len());
|
||||||
let mut endpoint_rotation: HashMap<(usize, i16, bool), usize> = HashMap::new();
|
let mut endpoint_rotation: HashMap<(usize, i16, bool), usize> = HashMap::new();
|
||||||
|
|
||||||
|
|
@ -1416,13 +1609,16 @@ impl UpstreamManager {
|
||||||
let mut group_ok = false;
|
let mut group_ok = false;
|
||||||
let mut group_rtt_ms = None;
|
let mut group_rtt_ms = None;
|
||||||
|
|
||||||
for (is_primary, endpoints) in [(true, &group.primary), (false, &group.fallback)] {
|
for (is_primary, endpoints) in
|
||||||
|
[(true, &group.primary), (false, &group.fallback)]
|
||||||
|
{
|
||||||
if endpoints.is_empty() {
|
if endpoints.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let rotation_key = (i, group.dc_idx, is_primary);
|
let rotation_key = (i, group.dc_idx, is_primary);
|
||||||
let start_idx = *endpoint_rotation.entry(rotation_key).or_insert(0) % endpoints.len();
|
let start_idx =
|
||||||
|
*endpoint_rotation.entry(rotation_key).or_insert(0) % endpoints.len();
|
||||||
let mut next_idx = (start_idx + 1) % endpoints.len();
|
let mut next_idx = (start_idx + 1) % endpoints.len();
|
||||||
|
|
||||||
for step in 0..endpoints.len() {
|
for step in 0..endpoints.len() {
|
||||||
|
|
@ -1544,8 +1740,7 @@ impl UpstreamManager {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpstreamState::dc_array_idx(dc_idx)
|
UpstreamState::dc_array_idx(dc_idx).map(|idx| guard[0].dc_ip_pref[idx])
|
||||||
.map(|idx| guard[0].dc_ip_pref[idx])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get preferred DC address based on config preference
|
/// Get preferred DC address based on config preference
|
||||||
|
|
@ -1566,6 +1761,12 @@ impl UpstreamManager {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::stats::Stats;
|
||||||
|
|
||||||
|
const TEST_SHADOWSOCKS_URL: &str =
|
||||||
|
"ss://2022-blake3-aes-256-gcm:MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDE=@127.0.0.1:8388";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn required_healthy_group_count_applies_three_group_threshold() {
|
fn required_healthy_group_count_applies_three_group_threshold() {
|
||||||
|
|
@ -1596,15 +1797,18 @@ mod tests {
|
||||||
|
|
||||||
assert!(dc2.primary.iter().all(|addr| addr.is_ipv6()));
|
assert!(dc2.primary.iter().all(|addr| addr.is_ipv6()));
|
||||||
assert!(dc2.fallback.iter().all(|addr| addr.is_ipv4()));
|
assert!(dc2.fallback.iter().all(|addr| addr.is_ipv4()));
|
||||||
assert!(dc2
|
assert!(
|
||||||
.primary
|
dc2.primary
|
||||||
.contains(&"[2001:db8::10]:443".parse::<SocketAddr>().unwrap()));
|
.contains(&"[2001:db8::10]:443".parse::<SocketAddr>().unwrap())
|
||||||
assert!(dc2
|
);
|
||||||
.fallback
|
assert!(
|
||||||
.contains(&"203.0.113.10:443".parse::<SocketAddr>().unwrap()));
|
dc2.fallback
|
||||||
assert!(dc2
|
.contains(&"203.0.113.10:443".parse::<SocketAddr>().unwrap())
|
||||||
.fallback
|
);
|
||||||
.contains(&"203.0.113.11:443".parse::<SocketAddr>().unwrap()));
|
assert!(
|
||||||
|
dc2.fallback
|
||||||
|
.contains(&"203.0.113.11:443".parse::<SocketAddr>().unwrap())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1626,12 +1830,14 @@ mod tests {
|
||||||
.expect("override-only dc group must be present");
|
.expect("override-only dc group must be present");
|
||||||
|
|
||||||
assert_eq!(dc9.primary.len(), 2);
|
assert_eq!(dc9.primary.len(), 2);
|
||||||
assert!(dc9
|
assert!(
|
||||||
.primary
|
dc9.primary
|
||||||
.contains(&"198.51.100.1:443".parse::<SocketAddr>().unwrap()));
|
.contains(&"198.51.100.1:443".parse::<SocketAddr>().unwrap())
|
||||||
assert!(dc9
|
);
|
||||||
.primary
|
assert!(
|
||||||
.contains(&"198.51.100.2:443".parse::<SocketAddr>().unwrap()));
|
dc9.primary
|
||||||
|
.contains(&"198.51.100.2:443".parse::<SocketAddr>().unwrap())
|
||||||
|
);
|
||||||
assert!(dc9.fallback.is_empty());
|
assert!(dc9.fallback.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1678,4 +1884,36 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(bind, None);
|
assert_eq!(bind, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn api_snapshot_reports_shadowsocks_as_sanitized_route() {
|
||||||
|
let manager = UpstreamManager::new(
|
||||||
|
vec![UpstreamConfig {
|
||||||
|
upstream_type: UpstreamType::Shadowsocks {
|
||||||
|
url: TEST_SHADOWSOCKS_URL.to_string(),
|
||||||
|
interface: None,
|
||||||
|
},
|
||||||
|
weight: 2,
|
||||||
|
enabled: true,
|
||||||
|
scopes: String::new(),
|
||||||
|
selected_scope: String::new(),
|
||||||
|
}],
|
||||||
|
1,
|
||||||
|
100,
|
||||||
|
1000,
|
||||||
|
1,
|
||||||
|
false,
|
||||||
|
Arc::new(Stats::new()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let snapshot = manager.try_api_snapshot().expect("snapshot");
|
||||||
|
assert_eq!(snapshot.summary.configured_total, 1);
|
||||||
|
assert_eq!(snapshot.summary.shadowsocks_total, 1);
|
||||||
|
assert_eq!(snapshot.upstreams.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
snapshot.upstreams[0].route_kind,
|
||||||
|
UpstreamRouteKind::Shadowsocks
|
||||||
|
);
|
||||||
|
assert_eq!(snapshot.upstreams[0].address, "127.0.0.1:8388");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue