diff --git a/Cargo.lock b/Cargo.lock index 37c99d8..bf3a8a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,9 +99,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "approx" @@ -114,9 +114,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" dependencies = [ "rustversion", ] @@ -312,9 +312,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -323,14 +323,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" dependencies = [ "cc", "cmake", "dunce", "fs_extra", + "pkg-config", ] [[package]] @@ -448,6 +449,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "blocking" version = "1.6.2" @@ -538,9 +548,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "bytesize" @@ -631,9 +641,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -848,6 +858,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const-str" version = "1.1.0" @@ -988,9 +1004,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossfire" -version = "3.1.16" +version = "3.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d8c4de3db833e7ef74050bae09d5f3fa8f9d1507d3c2689c6e8c50b71208b18" +checksum = "df981decccf9b846c396ae513b3d2c6a1770d9593c8e47853d8fdb336c7bcb46" dependencies = [ "crossbeam-utils", "embed-collections", @@ -1017,20 +1033,23 @@ dependencies = [ ] [[package]] -name = "ctor" -version = "0.6.3" +name = "crypto-common" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "424e0138278faeb2b401f174ad17e715c829512d74f3d1e81eb43365c2e0590e" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ - "ctor-proc-macro", - "dtor", + "hybrid-array", ] [[package]] -name = "ctor-proc-macro" -version = "0.0.7" +name = "ctor" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" +checksum = "01334b89b69ff726750c5ce5073fc8bd860e99aa9a8fc5ca11b04730e3aee97a" +dependencies = [ + "link-section", + "linktime-proc-macro", +] [[package]] name = "darling" @@ -1172,7 +1191,6 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "powerfmt", "serde_core", ] @@ -1236,8 +1254,19 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", ] [[package]] @@ -1257,21 +1286,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" -[[package]] -name = "dtor" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" -dependencies = [ - "dtor-proc-macro", -] - -[[package]] -name = "dtor-proc-macro" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" - [[package]] name = "dunce" version = "1.0.5" @@ -1452,7 +1466,6 @@ dependencies = [ "foldhash 0.2.0", "libm", "portable-atomic", - "rand 0.9.4", "siphasher", ] @@ -1797,11 +1810,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", "wasip2", "wasip3", + "wasm-bindgen", ] [[package]] @@ -2057,9 +2072,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -2116,6 +2131,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.10.1" @@ -2611,12 +2635,24 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "link-section" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24670b639492630905459a6c7d47f063d33c2d4fcd5362f6e5827c5613976c9f" + [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linktime-proc-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7b0a3383c2a1002d11349c92c85a666a5fb679e96c79d782cf0dbe557fd6ee" + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -2690,9 +2726,9 @@ checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "memmap2" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] @@ -3171,7 +3207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ "pest", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -3598,13 +3634,13 @@ dependencies = [ [[package]] name = "quinn" version = "0.12.0" -source = "git+https://github.com/Tipuch/quinn.git?branch=bbrv3#ce60e5b5c115db2a6053f4e0ca7fc52103cb76b9" +source = "git+https://github.com/Tipuch/quinn.git?branch=bbrv3#b87bf72a644f42592ebb64caf4303f86d260a538" dependencies = [ "bytes", "cfg_aliases", "pin-project-lite", "quinn-proto 0.12.0", - "quinn-udp 0.6.1", + "quinn-udp 0.6.1 (git+https://github.com/Tipuch/quinn.git?branch=bbrv3)", "rustc-hash", "rustls", "socket2 0.6.4", @@ -3648,14 +3684,14 @@ dependencies = [ [[package]] name = "quinn-proto" version = "0.12.0" -source = "git+https://github.com/Tipuch/quinn.git?branch=bbrv3#ce60e5b5c115db2a6053f4e0ca7fc52103cb76b9" +source = "git+https://github.com/Tipuch/quinn.git?branch=bbrv3#b87bf72a644f42592ebb64caf4303f86d260a538" dependencies = [ "aws-lc-rs", "bytes", "fastbloom", - "getrandom 0.3.4", + "getrandom 0.4.2", "lru-slab", - "rand 0.9.4", + "rand 0.10.1", "rand_pcg", "ring", "rustc-hash", @@ -3680,13 +3716,26 @@ dependencies = [ "once_cell", "socket2 0.6.4", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.59.0", +] + +[[package]] +name = "quinn-udp" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76150b617afc75e6e21ac5f39bc196e80b65415ae48d62dbef8e2519d040ce42" +dependencies = [ + "cfg_aliases", + "libc", + "socket2 0.6.4", + "tracing", + "windows-sys 0.61.2", ] [[package]] name = "quinn-udp" version = "0.6.1" -source = "git+https://github.com/Tipuch/quinn.git?branch=bbrv3#ce60e5b5c115db2a6053f4e0ca7fc52103cb76b9" +source = "git+https://github.com/Tipuch/quinn.git?branch=bbrv3#b87bf72a644f42592ebb64caf4303f86d260a538" dependencies = [ "cfg_aliases", "libc", @@ -3803,11 +3852,11 @@ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rand_pcg" -version = "0.9.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b48ac3f7ffaab7fac4d2376632268aa5f89abdb55f7ebf8f4d11fffccb2320f7" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" dependencies = [ - "rand_core 0.9.5", + "rand_core 0.10.1", ] [[package]] @@ -3908,9 +3957,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -3931,9 +3980,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rend" @@ -4096,9 +4145,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" dependencies = [ "aws-lc-rs", "log", @@ -4482,7 +4531,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -4628,34 +4688,13 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "snafu" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" -dependencies = [ - "snafu-derive 0.8.9", -] - [[package]] name = "snafu" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1a012328be2e3f5d5f6f3218147ca02588cea4cb865e876849ab6debcf36522" dependencies = [ - "snafu-derive 0.9.1", -] - -[[package]] -name = "snafu-derive" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.117", + "snafu-derive", ] [[package]] @@ -4690,29 +4729,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "socks5-proto" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d1cbdbbffd5e2418d31ae833045a219062f1740f5f0f34baefa8e5bb2053ff" -dependencies = [ - "byteorder", - "bytes", - "tokio", -] - -[[package]] -name = "socks5-server" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7260a3af0411620eca9c63032069ec9f0d92ef4214e017672c864f573d6a21" -dependencies = [ - "async-trait", - "bytes", - "socks5-proto", - "tokio", -] - [[package]] name = "spinning_top" version = "0.3.0" @@ -4957,9 +4973,9 @@ dependencies = [ [[package]] name = "tikv-jemalloc-sys" -version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +version = "0.7.1+5.3.1-0-g81034ce1f1373e37dc865038e1bc8eeecf559ce8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" +checksum = "1a2825c78386b4ae0314074867860ba9577875de945f05992c38815cbec327f0" dependencies = [ "cc", "libc", @@ -4967,9 +4983,9 @@ dependencies = [ [[package]] name = "tikv-jemallocator" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" +checksum = "249f09e49ab1609436f34c776e84231bead18d6a955f119f939bdc1d847561bd" dependencies = [ "libc", "tikv-jemalloc-sys", @@ -4977,12 +4993,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", - "itoa", "libc", "num-conv", "num_threads", @@ -4994,15 +5009,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -5477,6 +5492,7 @@ dependencies = [ "crossbeam-utils", "educe", "eyre", + "fast-socks5", "figment", "figment-json5", "humantime", @@ -5491,8 +5507,6 @@ dependencies = [ "serde", "serde_yaml 0.9.34+deprecated", "socket2 0.6.4", - "socks5-proto", - "socks5-server", "tempfile", "thiserror 2.0.18", "tikv-jemallocator", @@ -5519,7 +5533,7 @@ dependencies = [ "moka", "nom 8.0.0", "num_enum", - "snafu 0.9.1", + "snafu", "test-log", "tokio", "tokio-stream", @@ -5559,7 +5573,7 @@ dependencies = [ "rustls-pemfile", "serde", "serde_json", - "sha2", + "sha2 0.11.0", "socket2 0.6.4", "tempfile", "thiserror 2.0.18", @@ -5740,9 +5754,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.2" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -6082,8 +6096,8 @@ dependencies = [ "pin-project", "pin-project-lite", "quinn 0.12.0", - "quinn-udp 0.5.14", - "rand 0.9.4", + "quinn-udp 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.10.1", "regex", "serde", "serde_json", @@ -6176,7 +6190,7 @@ dependencies = [ "eyre", "fast-socks5", "futures-util", - "snafu 0.8.9", + "snafu", "socket2 0.6.4", "tokio", "tokio-stream", @@ -6197,7 +6211,7 @@ dependencies = [ "eyre", "fast-socks5", "futures-util", - "rcgen 0.13.2", + "rcgen 0.14.8", "rustls", "tokio", "tokio-stream", @@ -6325,7 +6339,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -6334,16 +6348,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -6361,31 +6366,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -6394,96 +6382,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" version = "0.7.15" diff --git a/crates/tuic-client/Cargo.toml b/crates/tuic-client/Cargo.toml index 6b7ee57..ba856b6 100644 --- a/crates/tuic-client/Cargo.toml +++ b/crates/tuic-client/Cargo.toml @@ -37,8 +37,7 @@ figment-json5 = "0.1.1" educe = { version = "0.6", default-features = false, features = ["Default"] } humantime-serde = "1" socket2 = { version = "0.6", default-features = false } -socks5-proto = { version = "0.3", default-features = false } -socks5-server = { version = "0.8", default-features = false } +fast-socks5 = { version = "1" } uuid = { version = "1", default-features = false, features = ["serde", "std"] } @@ -69,7 +68,7 @@ tracing-subscriber = { version = "0.3", default-features = false, features = ["t chrono = "0.4" tracing = "0.1" -tikv-jemallocator = { version = "0.6", optional = true } +tikv-jemallocator = { version = "0.7", optional = true } [dev-dependencies] tempfile = "3" \ No newline at end of file diff --git a/crates/tuic-client/src/socks5/handle_task.rs b/crates/tuic-client/src/socks5/handle_task.rs index f3ca6ae..00f74c4 100644 --- a/crates/tuic-client/src/socks5/handle_task.rs +++ b/crates/tuic-client/src/socks5/handle_task.rs @@ -1,206 +1,181 @@ -use socks5_proto::{Address, Reply}; -use socks5_server::{ - Associate, Bind, Connect, - connection::{associate, bind, connect}, +use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + sync::Arc, }; + +use fast_socks5::{ + ReplyError, + server::{Socks5ServerProtocol, states, wait_on_tcp}, + util::target_addr::TargetAddr as SocksTargetAddr, +}; +use tokio::{net::TcpStream, sync::mpsc}; use tracing::{debug, warn}; use wind_core::{ AbstractOutbound, - types::TargetAddr, udp::{UdpPacket, UdpStream}, }; -use super::{Server, UDP_SESSIONS, udp_session::UdpSession}; -use crate::wind_adapter::get_connection; +use super::{ + Server, + udp_session::{UdpSession, convert_target_addr, reply_origin_socket}, +}; +use crate::wind_adapter::{TuicOutboundAdapter, get_connection}; + +type CommandProto = Socks5ServerProtocol; impl Server { + /// Handle a SOCKS5 `CONNECT`: reply success and hand the raw stream to the + /// wind-tuic outbound, which relays it through the QUIC tunnel. + pub async fn handle_connect(proto: CommandProto, addr: SocksTargetAddr, peer_addr: SocketAddr) { + let target_addr = convert_target_addr(&addr); + + let inner = match proto.reply_success(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0)).await { + Ok(stream) => stream, + Err(err) => { + warn!("[socks5] [{peer_addr}] [connect] [{target_addr}] command reply error: {err}"); + return; + } + }; + + match get_connection() { + Some(adapter) => { + if let Err(err) = adapter + .handle_tcp(target_addr.clone(), inner, None::) + .await + { + warn!("[socks5] [{peer_addr}] [connect] [{target_addr}] TCP stream relay error: {err}"); + } + } + None => { + warn!("[socks5] [{peer_addr}] [connect] [{target_addr}] no wind connection available"); + } + } + } + + /// Handle a SOCKS5 `UDP ASSOCIATE`: bind a relay socket, reply with its + /// address, then bridge SOCKS5 UDP datagrams to/from the wind-tuic outbound + /// via a channel-backed [`UdpStream`]. The association ends when the client + /// tears down the controlling TCP connection. pub async fn handle_associate( - assoc: Associate, + proto: CommandProto, assoc_id: u16, + peer_addr: SocketAddr, + reply_ip: IpAddr, dual_stack: Option, max_pkt_size: usize, ) { - let peer_addr = assoc.peer_addr().unwrap(); - let local_ip = assoc.local_addr().unwrap().ip(); - - match UdpSession::new(assoc_id, peer_addr, local_ip, dual_stack, max_pkt_size) { - Ok(session) => { - let local_addr = session.local_addr().unwrap(); - debug!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] bound to {local_addr}"); + let session = match UdpSession::new(assoc_id, peer_addr, reply_ip, dual_stack, max_pkt_size) { + Ok(session) => session, + Err(err) => { + warn!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] failed setting up UDP associate session: {err}"); + if let Err(err) = proto.reply_error(&ReplyError::GeneralFailure).await { + warn!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] command reply error: {err}"); + } + return; + } + }; - let mut assoc = match assoc.reply(Reply::Succeeded, Address::SocketAddress(local_addr)).await { - Ok(assoc) => assoc, - Err(err) => { - warn!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] command reply error: {err}"); - return; - } - }; - - UDP_SESSIONS.get().unwrap().write().await.insert(assoc_id, session.clone()); - - // Create wind UdpStream channels - // wind_tx: packets from local SOCKS5 client → send to remote (outbound reads - // rx) wind_rx: packets from remote → send back to local SOCKS5 client - // (outbound writes tx) - let (local_to_remote_tx, local_to_remote_rx) = tokio::sync::mpsc::channel::(128); - let (remote_to_local_tx, mut remote_to_local_rx) = tokio::sync::mpsc::channel::(128); - - let wind_udp_stream = UdpStream { - tx: remote_to_local_tx, - rx: local_to_remote_rx, - }; - - // Task: receive from local SOCKS5 UDP socket → forward to wind outbound - let session_recv = session.clone(); - let local_to_remote_tx_clone = local_to_remote_tx.clone(); - let handle_local_incoming_pkt = async move { - loop { - let (pkt, target_addr) = match session_recv.recv().await { - Ok(res) => res, - Err(err) => { - warn!( - "[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] failed to receive UDP packet: {err}" - ); - continue; - } - }; + let local_addr = match session.local_addr() { + Ok(addr) => addr, + Err(err) => { + warn!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] failed reading relay socket addr: {err}"); + let _ = proto.reply_error(&ReplyError::GeneralFailure).await; + return; + } + }; - let target = match target_addr { - Address::DomainAddress(domain, port) => TargetAddr::Domain(domain, port), - Address::SocketAddress(addr) => TargetAddr::from(addr), - }; + // Reply with the relay's reachable address; `reply_success` returns the + // underlying control stream, whose EOF signals association teardown. + let mut ctrl = match proto.reply_success(SocketAddr::new(reply_ip, local_addr.port())).await { + Ok(ctrl) => ctrl, + Err(err) => { + warn!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] command reply error: {err}"); + return; + } + }; - let udp_pkt = UdpPacket { - source: None, - target, - payload: pkt, - }; + debug!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] bound to {local_addr}"); - if let Err(err) = local_to_remote_tx_clone.send(udp_pkt).await { - warn!( - "[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] failed forwarding UDP packet to wind: \ - {err}" - ); - } - } - }; - - // Task: receive from wind outbound → forward back to local SOCKS5 UDP client - let session_send = session.clone(); - let handle_remote_incoming_pkt = async move { - while let Some(pkt) = remote_to_local_rx.recv().await { - let src_addr = match pkt.source.or(Some(pkt.target.clone())) { - Some(TargetAddr::Domain(domain, port)) => Address::DomainAddress(domain, port), - Some(TargetAddr::IPv4(ip, port)) => { - Address::SocketAddress(std::net::SocketAddr::new(std::net::IpAddr::V4(ip), port)) - } - Some(TargetAddr::IPv6(ip, port)) => { - Address::SocketAddress(std::net::SocketAddr::new(std::net::IpAddr::V6(ip), port)) - } - None => Address::unspecified(), - }; + let session = Arc::new(session); - if let Err(err) = session_send.send(pkt.payload, src_addr).await { - warn!( - "[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] failed sending UDP packet to local: \ - {err}" - ); - } - } - }; - - let outbound_handle = tokio::spawn(async move { - match get_connection() { - Some(adapter) => { - if let Err(err) = adapter - .handle_udp(wind_udp_stream, None::) - .await - { - warn!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] wind UDP handler error: {err}"); - } - } - None => { - warn!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] no wind connection available"); - } - } - }); + // wind UdpStream channels. + // local_to_remote: packets from the local SOCKS5 client → outbound (reads rx) + // remote_to_local: packets from remote → local SOCKS5 client (outbound writes + // tx) + let (local_to_remote_tx, local_to_remote_rx) = mpsc::channel::(128); + let (remote_to_local_tx, mut remote_to_local_rx) = mpsc::channel::(128); - tokio::spawn(handle_remote_incoming_pkt); + let wind_udp_stream = UdpStream { + tx: remote_to_local_tx, + rx: local_to_remote_rx, + }; - match tokio::select! { - res = assoc.wait_until_closed() => res, - _ = handle_local_incoming_pkt => unreachable!(), - } { - Ok(()) => {} - Err(err) => { - warn!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] associate connection error: {err}") + // Drive the outbound UDP relay through the wind-tuic connection. + let outbound_handle = tokio::spawn(async move { + match get_connection() { + Some(adapter) => { + if let Err(err) = adapter.handle_udp(wind_udp_stream, None::).await { + warn!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] wind UDP handler error: {err}"); } } - - debug!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] stopped associating"); - - // `assoc_id` is `u16` and the allocator does fetch_add(Relaxed) - // without wraparound protection, so two concurrent SOCKS5 - // connections can land on the same id. If a remote-side close - // path got there first this `remove` returns `None`; previously - // the trailing `.unwrap()` panicked and tore the entire SOCKS5 - // connection handler down. Now drop the result quietly — the - // session is gone either way. - let _ = UDP_SESSIONS.get().unwrap().write().await.remove(&assoc_id); - - outbound_handle.abort(); + None => { + warn!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] no wind connection available"); + } } - Err(err) => { - warn!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] failed setting up UDP associate session: {err}"); - - match assoc.reply(Reply::GeneralFailure, Address::unspecified()).await { - Ok(mut assoc) => { - let _ = assoc.shutdown().await; + }); + + // remote → local: wrap replies in a SOCKS5 UDP header and send to the client. + let session_send = session.clone(); + let remote_handle = tokio::spawn(async move { + while let Some(pkt) = remote_to_local_rx.recv().await { + let origin = reply_origin_socket(&pkt); + if let Err(err) = session_send.send(&pkt.payload, origin).await { + warn!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] failed sending UDP packet to local: {err}"); + } + } + }); + + // local → remote: parse the SOCKS5 UDP header and forward payloads to the + // outbound. + let session_recv = session.clone(); + let local_incoming = async move { + loop { + match session_recv.recv().await { + Ok((payload, target_addr)) => { + let packet = UdpPacket { + source: None, + target: convert_target_addr(&target_addr), + payload, + }; + if local_to_remote_tx.send(packet).await.is_err() { + break; + } } + // Dropped datagram (spoofed source or malformed header): keep + // serving the association rather than tearing it down. + Err(crate::error::Error::WrongPacketSource) | Err(crate::error::Error::Socks5(_)) => continue, Err(err) => { - warn!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] command reply error: {err}") + warn!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] failed to receive UDP packet: {err}"); + break; } } } - } - } - - pub async fn handle_bind(bind: Bind) { - let peer_addr = bind.peer_addr().unwrap(); - warn!("[socks5] [{peer_addr}] [bind] command not supported"); - - match bind.reply(Reply::CommandNotSupported, Address::unspecified()).await { - Ok(mut bind) => { - let _ = bind.shutdown().await; - } - Err(err) => warn!("[socks5] [{peer_addr}] [bind] command reply error: {err}"), - } - } - - pub async fn handle_connect(conn: Connect, addr: Address) { - let peer_addr = conn.peer_addr().unwrap(); - let target_addr = match addr { - Address::DomainAddress(domain, port) => TargetAddr::Domain(domain, port), - Address::SocketAddress(addr) => TargetAddr::from(addr), }; - match conn.reply(Reply::Succeeded, Address::unspecified()).await { - Ok(conn) => match get_connection() { - Some(adapter) => { - if let Err(err) = adapter - .handle_tcp(target_addr.clone(), conn, None::) - .await - { - warn!("[socks5] [{peer_addr}] [connect] [{target_addr}] TCP stream relay error: {err}"); - } - } - None => { - warn!("[socks5] [{peer_addr}] [connect] [{target_addr}] no wind connection available"); + // The association lives until either the client closes the control TCP + // connection or the relay loop stops. + tokio::select! { + _ = local_incoming => {} + res = wait_on_tcp(&mut ctrl) => { + if let Err(err) = res { + debug!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] control connection closed: {err}"); } - }, - Err(err) => { - warn!("[socks5] [{peer_addr}] [connect] [{target_addr}] command reply error: {err}"); } } + + debug!("[socks5] [{peer_addr}] [associate] [{assoc_id:#06x}] stopped associating"); + + outbound_handle.abort(); + remote_handle.abort(); } } diff --git a/crates/tuic-client/src/socks5/mod.rs b/crates/tuic-client/src/socks5/mod.rs index f840ae4..27ddf21 100644 --- a/crates/tuic-client/src/socks5/mod.rs +++ b/crates/tuic-client/src/socks5/mod.rs @@ -1,20 +1,13 @@ use std::{ - collections::HashMap, - net::{SocketAddr, TcpListener as StdTcpListener}, - sync::{ - Arc, - atomic::{AtomicU16, Ordering}, - }, + net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener as StdTcpListener}, + sync::atomic::{AtomicU16, Ordering}, }; +use fast_socks5::{ReplyError, Socks5Command, server::Socks5ServerProtocol}; use once_cell::sync::OnceCell; use socket2::{Domain, Protocol, SockAddr, Socket, Type}; -use socks5_server::{ - Auth, Connection, Server as Socks5Server, - auth::{NoAuth, Password}, -}; -use tokio::{net::TcpListener, sync::RwLock as AsyncRwLock}; -use tokio_util::{sync::CancellationToken, task::TaskTracker}; +use tokio::net::{TcpListener, TcpStream}; +use tokio_util::sync::CancellationToken; use tracing::{Instrument, debug, info, warn}; use crate::{config::Local, error::Error}; @@ -22,15 +15,20 @@ use crate::{config::Local, error::Error}; mod handle_task; mod udp_session; -pub use self::udp_session::UDP_SESSIONS; - static SERVER: OnceCell = OnceCell::new(); +/// SOCKS5 authentication configuration derived from the client `Local` config. +enum AuthConfig { + NoAuth, + Password { username: String, password: String }, +} + pub struct Server { - inner: Socks5Server, + listener: TcpListener, dual_stack: Option, max_pkt_size: usize, next_assoc_id: AtomicU16, + auth: AuthConfig, } impl Server { @@ -46,11 +44,6 @@ impl Server { .map_err(|_| "failed initializing socks5 server") .unwrap(); - UDP_SESSIONS - .set(AsyncRwLock::new(HashMap::new())) - .map_err(|_| "failed initializing socks5 UDP session pool") - .unwrap(); - Ok(()) } @@ -61,7 +54,7 @@ impl Server { username: Option>, password: Option>, ) -> Result { - let socket = { + let listener = { let domain = match addr { SocketAddr::V4(_) => Domain::IPV4, SocketAddr::V6(_) => Domain::IPV6, @@ -98,17 +91,24 @@ impl Server { .map_err(|err| Error::Socket("failed to create socks5 server socket", err))? }; - let auth: Arc = match (username, password) { - (Some(username), Some(password)) => Arc::new(Password::new(username, password)), - (None, None) => Arc::new(NoAuth), + // `fast_socks5` verifies credentials via a closure, so the username and + // password are kept as UTF-8 strings. Config supplies raw bytes; lossy + // decoding preserves the previous behaviour for well-formed credentials. + let auth = match (username, password) { + (Some(username), Some(password)) => AuthConfig::Password { + username: String::from_utf8_lossy(&username).into_owned(), + password: String::from_utf8_lossy(&password).into_owned(), + }, + (None, None) => AuthConfig::NoAuth, _ => return Err(Error::InvalidSocks5Auth), }; Ok(Self { - inner: Socks5Server::new(socket, auth), + listener, dual_stack, max_pkt_size, next_assoc_id: AtomicU16::new(0), + auth, }) } @@ -118,17 +118,20 @@ impl Server { pub async fn start(cancel: CancellationToken) { let server = SERVER.get().unwrap(); - warn!("[socks5] server started, listening on {}", server.inner.local_addr().unwrap()); + warn!( + "[socks5] server started, listening on {}", + server.listener.local_addr().unwrap() + ); - let conn_tasks = TaskTracker::new(); + let conn_tasks = tokio_util::task::TaskTracker::new(); loop { tokio::select! { _ = cancel.cancelled() => { info!("[socks5] cancellation received, shutting down"); break; } - res = server.inner.accept() => match res { - Ok((conn, addr)) => { + res = server.listener.accept() => match res { + Ok((stream, addr)) => { let span = tracing::info_span!("socks5", peer = %addr); let conn_cancel = cancel.child_token(); conn_tasks.spawn( @@ -137,7 +140,7 @@ impl Server { _ = conn_cancel.cancelled() => { debug!("session aborted by shutdown"); } - _ = Self::handle_socks5_conn(server, conn) => {} + _ = Self::handle_socks5_conn(server, stream, addr) => {} } } .instrument(span), @@ -151,24 +154,58 @@ impl Server { conn_tasks.wait().await; } - async fn handle_socks5_conn(server: &Server, conn: socks5_server::IncomingConnection) { + async fn handle_socks5_conn(server: &Server, stream: TcpStream, peer_addr: SocketAddr) { debug!("connection established"); - match conn.handshake().await { - Ok(Connection::Associate(associate, _)) => { - let assoc_id = server.next_assoc_id.fetch_add(1, Ordering::Relaxed); - info!(assoc_id = format_args!("{assoc_id:#06x}"), "associate"); - Self::handle_associate(associate, assoc_id, server.dual_stack, server.max_pkt_size).await; + // The relay UDP socket (for UDP ASSOCIATE) is bound on and reported with + // the local IP of the accepted control connection, mirroring the previous + // behaviour. Captured before the stream is consumed by the handshake. + let local_ip = stream.local_addr().map(|a| a.ip()).unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)); + + // SOCKS5 authentication handshake. + let proto = match &server.auth { + AuthConfig::NoAuth => match Socks5ServerProtocol::accept_no_auth(stream).await { + Ok(proto) => proto, + Err(err) => { + warn!(error = %err, "handshake error"); + return; + } + }, + AuthConfig::Password { username, password } => { + match Socks5ServerProtocol::accept_password_auth(stream, |u, p| u == *username && p == *password).await { + Ok((proto, _)) => proto, + Err(err) => { + warn!(error = %err, "authentication error"); + return; + } + } } - Ok(Connection::Bind(bind, _)) => { - info!("bind"); - Self::handle_bind(bind).await; + }; + + let (proto, cmd, target_addr) = match proto.read_command().await { + Ok(res) => res, + Err(err) => { + warn!(error = %err, "reading command error"); + return; } - Ok(Connection::Connect(connect, target_addr)) => { + }; + + match cmd { + Socks5Command::TCPConnect => { info!(target = %target_addr, "connect"); - Self::handle_connect(connect, target_addr).await; + Self::handle_connect(proto, target_addr, peer_addr).await; + } + Socks5Command::UDPAssociate => { + let assoc_id = server.next_assoc_id.fetch_add(1, Ordering::Relaxed); + info!(assoc_id = format_args!("{assoc_id:#06x}"), "associate"); + Self::handle_associate(proto, assoc_id, peer_addr, local_ip, server.dual_stack, server.max_pkt_size).await; + } + Socks5Command::TCPBind => { + warn!("[socks5] [{peer_addr}] [bind] command not supported"); + if let Err(err) = proto.reply_error(&ReplyError::CommandNotSupported).await { + warn!("[socks5] [{peer_addr}] [bind] command reply error: {err}"); + } } - Err(err) => warn!(error = %err, "handshake error"), } debug!("connection closed"); diff --git a/crates/tuic-client/src/socks5/udp_session.rs b/crates/tuic-client/src/socks5/udp_session.rs index e561604..5e107d1 100644 --- a/crates/tuic-client/src/socks5/udp_session.rs +++ b/crates/tuic-client/src/socks5/udp_session.rs @@ -1,27 +1,29 @@ use std::{ - collections::HashMap, io::Error as IoError, - net::{IpAddr, SocketAddr, UdpSocket as StdUdpSocket}, - sync::Arc, + net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket as StdUdpSocket}, }; use bytes::Bytes; -use once_cell::sync::OnceCell; +use fast_socks5::{new_udp_header, parse_udp_request, util::target_addr::TargetAddr as SocksTargetAddr}; use socket2::{Domain, Protocol, SockAddr, Socket, Type}; -use socks5_proto::Address; -use socks5_server::AssociatedUdpSocket; -use tokio::{net::UdpSocket, sync::RwLock as AsyncRwLock}; +use tokio::net::UdpSocket; use tracing::{debug, warn}; +use wind_core::{types::TargetAddr, udp::UdpPacket}; use crate::error::Error; -pub static UDP_SESSIONS: OnceCell>> = OnceCell::new(); - -#[derive(Clone)] +/// A SOCKS5 UDP-associate relay socket. +/// +/// The socket is `connect()`-latched to the first client that sends a datagram +/// whose source IP matches the controlling TCP connection's peer (RFC 1928 §6), +/// after which the kernel drops datagrams from any other source. SOCKS5 UDP +/// framing (RFC 1928 §7) is handled with `fast_socks5`'s `parse_udp_request` / +/// `new_udp_header` helpers. pub struct UdpSession { - socket: Arc, + socket: UdpSocket, assoc_id: u16, ctrl_addr: SocketAddr, + max_pkt_size: usize, } impl UdpSession { @@ -58,93 +60,28 @@ impl UdpSession { .map_err(|err| Error::Socket("failed to create socks5 server UDP associate socket", err))?; Ok(Self { - socket: Arc::new(AssociatedUdpSocket::from((socket, max_pkt_size))), + socket, assoc_id, ctrl_addr, + max_pkt_size, }) } - pub async fn send(&self, pkt: Bytes, mut src_addr: Address) -> Result<(), Error> { - if let Address::SocketAddress(SocketAddr::V6(v6)) = src_addr { - if let Some(v4) = v6.ip().to_ipv4_mapped() { - src_addr = Address::SocketAddress(SocketAddr::new(IpAddr::V4(v4), v6.port())); - } - } - - let src_addr_display = src_addr.to_string(); - // `peer_addr` fails if the socket has not yet been connected (no client - // packet has arrived) or if the socket has been disconnected. Fall back - // to a placeholder rather than panicking inside a log line. - let dst_addr_display = self - .socket - .peer_addr() - .map(|a| a.to_string()) - .unwrap_or_else(|_| "".to_string()); - - debug!( - "[socks5] [{ctrl_addr}] [associate] [{assoc_id:#06x}] send packet from {src_addr_display} to {dst_addr}", - ctrl_addr = self.ctrl_addr, - assoc_id = self.assoc_id, - dst_addr = dst_addr_display, - ); - - if let Err(err) = self.socket.send(pkt, 0, src_addr).await { - warn!( - "[socks5] [{ctrl_addr}] [associate] [{assoc_id:#06x}] send packet from {src_addr_display} to {dst_addr} \ - error: {err}", - ctrl_addr = self.ctrl_addr, - assoc_id = self.assoc_id, - dst_addr = dst_addr_display, - ); - - return Err(Error::Io(err)); - } - - Ok(()) - } - - pub async fn recv(&self) -> Result<(Bytes, Address), Error> { - let (pkt, frag, mut dst_addr, src_addr) = self.socket.recv_from().await?; - - if let Address::SocketAddress(SocketAddr::V6(v6)) = dst_addr { - if let Some(v4) = v6.ip().to_ipv4_mapped() { - dst_addr = Address::SocketAddress(SocketAddr::new(IpAddr::V4(v4), v6.port())); - } - } - - if let Ok(connected_addr) = self.socket.peer_addr() { - let connected_addr = match connected_addr { - SocketAddr::V4(addr) => { - if let SocketAddr::V6(_) = src_addr { - SocketAddr::new(addr.ip().to_ipv6_mapped().into(), addr.port()) - } else { - connected_addr - } - } - SocketAddr::V6(addr) => { - if let SocketAddr::V4(_) = src_addr { - if let Some(ip) = addr.ip().to_ipv4_mapped() { - SocketAddr::new(IpAddr::V4(ip), addr.port()) - } else { - connected_addr - } - } else { - connected_addr - } - } - }; - if src_addr != connected_addr { - Err(IoError::other(format!("invalid source address: {src_addr}")))?; - } - } else { - // First-packet race: previously the UDP associate socket was - // `connect()`-bound to whoever sent the first datagram, with no - // authentication. Any off-path attacker that could send a UDP - // packet to the relay's bound port before the legitimate client - // did would permanently own the association. We now require the - // source IP to match the TCP control connection's peer IP before - // latching it in. - if src_addr.ip() != self.ctrl_addr.ip() { + /// Receive one SOCKS5 UDP datagram and return its payload and target. + /// + /// On the first packet the source IP is checked against the control + /// connection's peer IP; a mismatch yields [`Error::WrongPacketSource`] and + /// the socket is left unlatched so a legitimate client can still connect. + pub async fn recv(&self) -> Result<(Bytes, SocksTargetAddr), Error> { + let mut buf = vec![0u8; self.max_pkt_size]; + let (len, src_addr) = self.socket.recv_from(&mut buf).await?; + + // First-packet race hardening: before latching the association onto a + // peer, require its source IP to match the TCP control connection's peer + // IP. Without this an off-path attacker racing the legitimate client + // could permanently own the association. + if self.socket.peer_addr().is_err() { + if unmap_v4_mapped(src_addr.ip()) != unmap_v4_mapped(self.ctrl_addr.ip()) { warn!( "[socks5] [{ctrl_addr}] [associate] [{assoc_id:#06x}] dropping initial UDP packet from unexpected source \ {src_addr} (expected client IP {ctrl_ip})", @@ -157,17 +94,41 @@ impl UdpSession { self.socket.connect(src_addr).await?; } + let (frag, target_addr, payload) = parse_udp_request(&buf[..len]) + .await + .map_err(|err| Error::Socks5(err.to_string()))?; + if frag != 0 { - Err(IoError::other("fragmented packet is not supported"))?; + return Err(Error::Socks5("fragmented packet is not supported".to_string())); } debug!( - "[socks5] [{ctrl_addr}] [associate] [{assoc_id:#06x}] receive packet from {src_addr} to {dst_addr}", + "[socks5] [{ctrl_addr}] [associate] [{assoc_id:#06x}] receive packet from {src_addr} to {target_addr}", ctrl_addr = self.ctrl_addr, assoc_id = self.assoc_id ); - Ok((pkt, dst_addr)) + Ok((Bytes::copy_from_slice(payload), target_addr)) + } + + /// Send a payload back to the latched client, prefixed with a SOCKS5 UDP + /// header whose address identifies the remote origin. No-op until a client + /// has latched in (nothing to reply to yet). + pub async fn send(&self, payload: &[u8], src_addr: SocketAddr) -> Result<(), Error> { + if self.socket.peer_addr().is_err() { + debug!( + "[socks5] [{ctrl_addr}] [associate] [{assoc_id:#06x}] dropping reply, no client latched yet", + ctrl_addr = self.ctrl_addr, + assoc_id = self.assoc_id, + ); + return Ok(()); + } + + let mut packet = new_udp_header(src_addr).map_err(|err| Error::Socks5(err.to_string()))?; + packet.extend_from_slice(payload); + + self.socket.send(&packet).await?; + Ok(()) } pub fn local_addr(&self) -> Result { @@ -175,6 +136,40 @@ impl UdpSession { } } +/// Convert a `fast_socks5` target address into wind's [`TargetAddr`]. +pub fn convert_target_addr(addr: &SocksTargetAddr) -> TargetAddr { + match addr { + SocksTargetAddr::Ip(SocketAddr::V4(addr)) => TargetAddr::IPv4(*addr.ip(), addr.port()), + SocksTargetAddr::Ip(SocketAddr::V6(addr)) => TargetAddr::IPv6(*addr.ip(), addr.port()), + SocksTargetAddr::Domain(domain, port) => TargetAddr::Domain(domain.clone(), *port), + } +} + +/// Best-effort origin address for a reply's SOCKS5 UDP header (RFC 1928 §7: +/// ATYP/DST identify the remote host). Prefers the packet's recorded source, +/// falling back to its target. Domains have no RFC codepoint here, so they are +/// reported as `0.0.0.0:port`. +pub fn reply_origin_socket(pkt: &UdpPacket) -> SocketAddr { + let origin = pkt.source.as_ref().unwrap_or(&pkt.target); + match origin { + TargetAddr::IPv4(ip, port) => SocketAddr::new((*ip).into(), *port), + TargetAddr::IPv6(ip, port) => SocketAddr::new((*ip).into(), *port), + TargetAddr::Domain(_, port) => SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), *port), + } +} + +/// Unwrap an IPv4-mapped IPv6 address (`::ffff:V4`) back to IPv4 so dual-stack +/// relay sockets compare source IPs on equal footing with IPv4 expectations. +fn unmap_v4_mapped(ip: IpAddr) -> IpAddr { + match ip { + IpAddr::V6(v6) => match v6.to_ipv4_mapped() { + Some(v4) => IpAddr::V4(v4), + None => IpAddr::V6(v6), + }, + v4 => v4, + } +} + #[cfg(test)] mod tests { use std::net::{Ipv4Addr, SocketAddr}; @@ -253,7 +248,7 @@ mod tests { .expect("matching-IP packet must be accepted"); match addr { - Address::SocketAddress(SocketAddr::V4(v4)) => { + SocksTargetAddr::Ip(SocketAddr::V4(v4)) => { assert_eq!(*v4.ip(), Ipv4Addr::new(1, 1, 1, 1)); assert_eq!(v4.port(), 53); } diff --git a/crates/tuic-server/Cargo.toml b/crates/tuic-server/Cargo.toml index 42ea9ab..cb950df 100644 --- a/crates/tuic-server/Cargo.toml +++ b/crates/tuic-server/Cargo.toml @@ -39,7 +39,7 @@ clap = { version = "4", features = ["derive"] } socket2 = { version = "0.6", default-features = false } arc-swap = "1" uuid = { version = "1", default-features = false, features = ["serde", "std", "v4"] } -sha2 = "0.10" +sha2 = "0.11" # QUIC quinn = { workspace = true, default-features = false, features = ["runtime-tokio"] } @@ -83,7 +83,7 @@ tracing = "0.1" thiserror = { version = "2", default-features = false } eyre = { version = "0.6" } -tikv-jemallocator = { version = "0.6", optional = true } +tikv-jemallocator = { version = "0.7", optional = true } rand = "0.10" [dev-dependencies] diff --git a/crates/wind-base/src/direct.rs b/crates/wind-base/src/direct.rs index 784bdba..c9ac230 100644 --- a/crates/wind-base/src/direct.rs +++ b/crates/wind-base/src/direct.rs @@ -112,10 +112,10 @@ pub async fn connect_direct_tcp(addr: SocketAddr, opts: &DirectOutboundOpts) -> // Enable TCP keepalive so the OS detects dead peers (e.g. network // partition, host crash) even when the relay is idle. Without this an // idle connection appears healthy to `copy_bidirectional` forever. - if let Some(ref ka) = opts.tcp_keepalive { - if let Err(e) = apply_tcp_keepalive(&stream, ka) { - tracing::debug!(error = %e, "failed to set TCP keepalive on direct outbound"); - } + if let Some(ref ka) = opts.tcp_keepalive + && let Err(e) = apply_tcp_keepalive(&stream, ka) + { + tracing::debug!(error = %e, "failed to set TCP keepalive on direct outbound"); } Ok(stream) diff --git a/crates/wind-core/Cargo.toml b/crates/wind-core/Cargo.toml index 4a30c07..7f4cc43 100644 --- a/crates/wind-core/Cargo.toml +++ b/crates/wind-core/Cargo.toml @@ -32,10 +32,10 @@ regex = "1" pin-project-lite = "0.2" tracing = "0.1" -rand = "0.9" +rand = "0.10" async-trait = "0.1" bytesize = "2" [dev-dependencies] -quinn-udp = "0.5" +quinn-udp = "0.6" serde_json = "1" \ No newline at end of file diff --git a/crates/wind-core/src/udp_tests.rs b/crates/wind-core/src/udp_tests.rs index cebfb50..bf19a97 100644 --- a/crates/wind-core/src/udp_tests.rs +++ b/crates/wind-core/src/udp_tests.rs @@ -7,13 +7,13 @@ mod tests { }; use quinn_udp::{RecvMeta, Transmit, UdpSocketState}; - use rand::RngCore; + use rand::RngExt; use socket2::Socket; /// Generate a vector of random bytes with the specified length fn generate_random_data(len: usize) -> Vec { let mut data = vec![0u8; len]; - rand::rng().fill_bytes(&mut data); + rand::rng().fill(&mut data[..]); data } diff --git a/crates/wind-socks/Cargo.toml b/crates/wind-socks/Cargo.toml index 7d1f6ba..0d8b45f 100644 --- a/crates/wind-socks/Cargo.toml +++ b/crates/wind-socks/Cargo.toml @@ -19,7 +19,7 @@ fast-socks5 = "1.0.0-rc.0" arc-swap = "1" socket2 = "0.6" -snafu = "0.8" +snafu = "0.9" eyre = "0.6" tracing = "0.1" async-trait = "0.1" diff --git a/crates/wind-socks/src/action.rs b/crates/wind-socks/src/action.rs index aec9afa..7844aa0 100644 --- a/crates/wind-socks/src/action.rs +++ b/crates/wind-socks/src/action.rs @@ -100,10 +100,10 @@ async fn connect_socks5_tcp( } // Enable TCP keepalive with the same rationale as the direct path. - if let Some(ref ka) = opts.tcp_keepalive { - if let Err(e) = apply_socks_keepalive(stream.get_socket_ref(), ka) { - tracing::debug!(error = %e, "failed to set TCP keepalive on socks5 outbound"); - } + if let Some(ref ka) = opts.tcp_keepalive + && let Err(e) = apply_socks_keepalive(stream.get_socket_ref(), ka) + { + tracing::debug!(error = %e, "failed to set TCP keepalive on socks5 outbound"); } Ok(stream) diff --git a/crates/wind-test/Cargo.toml b/crates/wind-test/Cargo.toml index c38998a..311e710 100644 --- a/crates/wind-test/Cargo.toml +++ b/crates/wind-test/Cargo.toml @@ -22,7 +22,7 @@ wind-socks = { version = "0.1.1", path = "../wind-socks"} wind-tuic = { version = "0.1.1", path = "../wind-tuic", features = ["quinn"] } bytes = "1" -ctor = "0.6" +ctor = "1.0" # Async tokio = { version = "1", default-features = false, features = ["net"] } @@ -33,7 +33,7 @@ futures-util = { version = "0.3", default-features = false, features = ["sink"] # TLS rustls = "0.23" -rcgen = "0.13" +rcgen = "0.14" aws-lc-rs = { version = "*", optional = true, default-features = false } fast-socks5 = "1.0.0-rc.0" eyre = "0.6" diff --git a/crates/wind-test/src/socks5.rs b/crates/wind-test/src/socks5.rs index 9ef8888..981edb0 100644 --- a/crates/wind-test/src/socks5.rs +++ b/crates/wind-test/src/socks5.rs @@ -8,7 +8,7 @@ use tokio::{ net::{TcpStream, UdpSocket}, }; -#[ctor::ctor] +#[ctor::ctor(unsafe)] fn setup_crypto() { #[cfg(feature = "aws-lc-rs")] { @@ -437,7 +437,7 @@ async fn run_test_proxy(ctx: Arc, config: TestConfig) -> let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap(); let cert_der = cert.cert.der().to_vec(); - let key_der = cert.key_pair.serialize_der(); + let key_der = cert.signing_key.serialize_der(); let tuic_opts = wind_tuic::quinn::inbound::TuicInboundOpts { listen_addr: format!("127.0.0.1:{}", config.tuic_port).parse()?, diff --git a/crates/wind-test/src/tuic.rs b/crates/wind-test/src/tuic.rs index c591a83..4c19611 100644 --- a/crates/wind-test/src/tuic.rs +++ b/crates/wind-test/src/tuic.rs @@ -129,7 +129,7 @@ impl InboundCallback for DirectCallback { pub fn generate_tuic_test_cert() -> (Vec>, PrivateKeyDer<'static>) { let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap(); let cert_der = CertificateDer::from(cert.cert); - let key_der = PrivatePkcs8KeyDer::from(cert.key_pair.serialize_der()); + let key_der = PrivatePkcs8KeyDer::from(cert.signing_key.serialize_der()); (vec![cert_der], PrivateKeyDer::Pkcs8(key_der)) } diff --git a/crates/wind-tuic/src/quinn/inbound.rs b/crates/wind-tuic/src/quinn/inbound.rs index f12583e..ca85de1 100644 --- a/crates/wind-tuic/src/quinn/inbound.rs +++ b/crates/wind-tuic/src/quinn/inbound.rs @@ -336,7 +336,7 @@ async fn handle_connection( let conn = if zero_rtt { match connecting.into_0rtt() { - Ok((conn, _)) => { + Ok(conn) => { info!("accepted 0-RTT connection"); conn }