diff --git a/Cargo.lock b/Cargo.lock index 8077c21dc..8dac55353 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,13 +17,48 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[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]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "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]] name = "ahash" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom", + "getrandom 0.2.15", "once_cell", "version_check", ] @@ -61,11 +96,70 @@ dependencies = [ "winapi", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arc-swap" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] [[package]] name = "arrayvec" @@ -73,6 +167,45 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom 7.1.1", + "num-traits", + "rusticata-macros", + "thiserror 1.0.31", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.45", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.45", + "syn 2.0.117", +] + [[package]] name = "async-compression" version = "0.4.18" @@ -86,6 +219,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.45", + "syn 2.0.117", +] + [[package]] name = "async-speed-limit" version = "0.3.1" @@ -104,7 +248,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" dependencies = [ "proc-macro2 1.0.93", - "quote 1.0.38", + "quote 1.0.45", "syn 1.0.93", ] @@ -117,6 +261,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "atty" version = "0.2.14" @@ -146,7 +296,7 @@ dependencies = [ "bytes", "futures-util", "headers", - "http", + "http 0.2.7", "http-body", "hyper", "itoa", @@ -175,7 +325,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", + "http 0.2.7", "http-body", "mime", ] @@ -195,6 +345,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.0" @@ -213,6 +369,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bcrypt" version = "0.13.0" @@ -221,10 +383,19 @@ checksum = "a7e7c93a3fb23b2fdde989b2c9ec4dd153063ec81f408507f84c090cd91c6641" dependencies = [ "base64 0.13.0", "blowfish", - "getrandom", + "getrandom 0.2.15", "zeroize", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bindgen" version = "0.59.2" @@ -234,14 +405,14 @@ dependencies = [ "bitflags 1.3.2", "cexpr", "clang-sys", - "clap", + "clap 2.34.0", "env_logger 0.9.0", "lazy_static", "lazycell", "log", "peeking_take_while", "proc-macro2 1.0.93", - "quote 1.0.38", + "quote 1.0.45", "regex", "rustc-hash", "shlex", @@ -256,9 +427,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.7.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block-buffer" @@ -269,6 +440,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "blowfish" version = "0.9.1" @@ -293,13 +473,47 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.9.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.11.0", + "polling", + "rustix", + "slab", + "tracing", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop", + "rustix", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.9" @@ -311,6 +525,18 @@ dependencies = [ "shlex", ] +[[package]] +name = "ccm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae3c82e4355234767756212c570e29833699ab63e6ffd161887314cc5b43847" +dependencies = [ + "aead", + "cipher", + "ctr", + "subtle", +] + [[package]] name = "cesu8" version = "1.1.0" @@ -332,6 +558,30 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fc89c7c5b9e7a02dfe45cd2367bae382f9ed31c61ca8debe5f827c420a2f08" +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]] name = "chrono" version = "0.4.39" @@ -354,6 +604,7 @@ checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -376,12 +627,45 @@ dependencies = [ "ansi_term", "atty", "bitflags 1.3.2", - "strsim", + "strsim 0.8.0", "textwrap", "unicode-width", "vec_map", ] +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.1", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "combine" version = "4.6.7" @@ -392,6 +676,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "config" version = "0.11.0" @@ -410,10 +703,16 @@ source = "git+https://github.com/rustdesk-org/confy#83db9ec19a2f97e9718aef69e4fc dependencies = [ "directories-next", "serde", - "thiserror", + "thiserror 1.0.31", "toml 0.5.9", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "core-foundation" version = "0.9.4" @@ -424,6 +723,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -432,27 +741,27 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc" -version = "3.0.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" -version = "2.1.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" @@ -508,7 +817,7 @@ dependencies = [ "cfg-if", "crossbeam-utils", "lazy_static", - "memoffset", + "memoffset 0.6.5", "scopeguard", ] @@ -524,24 +833,80 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "cfg-if", - "lazy_static", + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.45", + "syn 2.0.117", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + [[package]] name = "deadpool" version = "0.8.2" @@ -565,14 +930,51 @@ dependencies = [ "winapi", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom 7.1.1", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "quickcheck", +] + [[package]] name = "digest" -version = "0.10.3" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -606,6 +1008,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.45", + "syn 2.0.117", +] + [[package]] name = "dlopen" version = "0.1.8" @@ -653,13 +1066,69 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dtls" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f531dd7c181beaf3cebab3716afa4d0d41ab888be85232583f56bbaf07ca208a" +dependencies = [ + "aes", + "aes-gcm", + "async-trait", + "bincode", + "byteorder", + "cbc", + "ccm", + "chacha20poly1305", + "der-parser", + "hmac", + "log", + "p256", + "p384", + "portable-atomic", + "rand 0.9.3", + "rand_core 0.6.4", + "rcgen", + "ring 0.17.14", + "rustls 0.23.37", + "sec1", + "serde", + "sha1", + "sha2", + "thiserror 1.0.31", + "tokio", + "webrtc-util", + "x25519-dalek", + "x509-parser", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature 2.2.0", + "spki", +] + [[package]] name = "ed25519" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d916019f70ae3a1faa1195685e290287f39207d38e6dfee727197cffcc002214" dependencies = [ - "signature", + "signature 1.5.0", ] [[package]] @@ -668,6 +1137,27 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -677,6 +1167,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" version = "0.9.0" @@ -692,15 +1192,15 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.2" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ - "humantime", - "is-terminal", + "anstream", + "anstyle", + "env_filter", + "jiff", "log", - "regex", - "termcolor", ] [[package]] @@ -709,6 +1209,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "event-listener" version = "2.5.2" @@ -724,6 +1234,22 @@ dependencies = [ "instant", ] +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "filetime" version = "0.2.16" @@ -732,7 +1258,7 @@ checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.13", "winapi", ] @@ -761,7 +1287,7 @@ dependencies = [ "log", "regex", "rustversion", - "thiserror", + "thiserror 1.0.31", "time", ] @@ -780,7 +1306,7 @@ dependencies = [ "log", "nu-ansi-term", "regex", - "thiserror", + "thiserror 1.0.31", ] [[package]] @@ -801,6 +1327,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -818,11 +1350,10 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ - "matches", "percent-encoding", ] @@ -843,9 +1374,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -853,9 +1384,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" @@ -881,32 +1412,32 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 1.0.93", + "quote 1.0.45", + "syn 2.0.117", ] [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" @@ -916,9 +1447,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -928,18 +1459,18 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -953,6 +1484,41 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "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]] name = "gimli" version = "0.31.1" @@ -965,6 +1531,17 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.3.26" @@ -976,7 +1553,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.7", "indexmap 2.7.0", "slab", "tokio", @@ -1004,6 +1581,9 @@ name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] [[package]] name = "hashlink" @@ -1019,16 +1599,18 @@ name = "hbb_common" version = "0.1.0" dependencies = [ "anyhow", + "async-recursion", "backtrace", "base64 0.22.1", "bytes", "chrono", + "clap 4.6.0", "confy", "default_net", "directories-next", "dirs-next", "dlopen", - "env_logger 0.10.2", + "env_logger 0.11.10", "filetime", "flexi_logger 0.27.4", "futures", @@ -1036,33 +1618,43 @@ dependencies = [ "httparse", "lazy_static", "libc", + "libloading", "log", "mac_address", "machine-uid 0.3.0", "osascript", "protobuf", "protobuf-codegen", - "rand", + "rand 0.8.5", "regex", + "rustls-native-certs 0.8.3", "rustls-pki-types", "rustls-platform-verifier", "serde", "serde_derive", "serde_json", "sha2", + "smithay-client-toolkit", "socket2 0.3.19", "sodiumoxide", "sysinfo", - "thiserror", + "thiserror 1.0.31", "tokio", "tokio-native-tls", "tokio-rustls 0.26.1", "tokio-socks 0.5.2-1", + "tokio-tungstenite 0.26.2", "tokio-util", "toml 0.7.8", + "tungstenite 0.26.2", "url", + "users", "uuid", + "webpki-roots 1.0.6", + "webrtc", + "whoami", "winapi", + "x11", "zstd", ] @@ -1076,14 +1668,14 @@ dependencies = [ "base64 0.13.0", "bcrypt", "chrono", - "clap", + "clap 2.34.0", "deadpool", "dns-lookup", "flate2", "flexi_logger 0.22.3", "hbb_common", "headers", - "http", + "http 0.2.7", "ipnetwork", "jsonwebtoken", "lazy_static", @@ -1101,9 +1693,9 @@ dependencies = [ "serde_json", "sodiumoxide", "sqlx", - "tokio-tungstenite", + "tokio-tungstenite 0.17.1", "tower-http", - "tungstenite", + "tungstenite 0.17.2", "uuid", "whoami", ] @@ -1118,7 +1710,7 @@ dependencies = [ "bitflags 1.3.2", "bytes", "headers-core", - "http", + "http 0.2.7", "httpdate", "mime", "sha-1", @@ -1130,7 +1722,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" dependencies = [ - "http", + "http 0.2.7", ] [[package]] @@ -1142,6 +1734,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1157,12 +1755,36 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "0.2.7" @@ -1174,6 +1796,16 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + [[package]] name = "http-body" version = "0.4.4" @@ -1181,7 +1813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" dependencies = [ "bytes", - "http", + "http 0.2.7", "pin-project-lite", ] @@ -1193,9 +1825,9 @@ checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -1220,7 +1852,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.7", "http-body", "httparse", "httpdate", @@ -1240,7 +1872,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", + "http 0.2.7", "hyper", "rustls 0.21.12", "tokio", @@ -1283,15 +1915,113 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "idna" -version = "0.2.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -1312,6 +2042,7 @@ checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown 0.15.2", + "serde", ] [[package]] @@ -1320,6 +2051,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ + "block-padding", "generic-array", ] @@ -1332,6 +2064,27 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "interceptor" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea51375727680dc15f06e8ad90fa31df75d79dd030100e8ad60eef1c27fe2c98" +dependencies = [ + "async-trait", + "bytes", + "futures", + "log", + "portable-atomic", + "rand 0.9.3", + "rtcp", + "rtp", + "thiserror 1.0.31", + "tokio", + "waitgroup", + "webrtc-srtp", + "webrtc-util", +] + [[package]] name = "ipnet" version = "2.10.1" @@ -1358,6 +2111,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.10.3" @@ -1373,18 +2132,44 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +[[package]] +name = "jiff" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.45", + "syn 2.0.117", +] + [[package]] name = "jni" -version = "0.19.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", + "cfg-if", "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.31", "walkdir", + "windows-sys 0.45.0", ] [[package]] @@ -1419,7 +2204,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc9051c17f81bae79440afa041b3a278e1de71bfb96d32454b477fd4703ccb6f" dependencies = [ "base64 0.13.0", - "pem", + "pem 1.0.2", "ring 0.16.20", "serde", "serde_json", @@ -1428,9 +2213,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" @@ -1438,6 +2223,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "lexical-core" version = "0.7.6" @@ -1453,9 +2244,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.169" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libloading" @@ -1467,6 +2258,18 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "bitflags 2.11.0", + "libc", + "plain", + "redox_syscall 0.7.4", +] + [[package]] name = "libsodium-sys" version = "0.2.7" @@ -1490,6 +2293,18 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + [[package]] name = "local-ip-address" version = "0.5.3" @@ -1498,7 +2313,7 @@ checksum = "2815836665de176ba66deaa449ada98fdf208d84730d1a84a22cbeed6151a6fa" dependencies = [ "libc", "neli", - "thiserror", + "thiserror 1.0.31", "windows-sys 0.48.0", ] @@ -1514,12 +2329,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "mac_address" @@ -1527,7 +2339,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4863ee94f19ed315bf3bc00299338d857d4b5bc856af375cc97d237382ad3856" dependencies = [ - "nix", + "nix 0.23.1", "winapi", ] @@ -1550,12 +2362,6 @@ dependencies = [ "winreg 0.11.0", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "matchit" version = "0.5.0" @@ -1563,11 +2369,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" [[package]] -name = "memchr" +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -1577,6 +2402,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.16" @@ -1620,13 +2454,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1638,10 +2472,10 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.5", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -1666,7 +2500,7 @@ checksum = "c168194d373b1e134786274020dae7fc5513d565ea2ebb9bc9ff17ffb69106d4" dependencies = [ "either", "proc-macro2 1.0.93", - "quote 1.0.38", + "quote 1.0.45", "serde", "syn 1.0.93", ] @@ -1681,7 +2515,20 @@ dependencies = [ "cc", "cfg-if", "libc", - "memoffset", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", + "pin-utils", ] [[package]] @@ -1733,6 +2580,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + [[package]] name = "num-integer" version = "0.1.46" @@ -1779,19 +2632,40 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags 2.7.0", + "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", @@ -1807,8 +2681,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "quote 1.0.45", + "syn 2.0.117", ] [[package]] @@ -1817,6 +2691,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "openssl-sys" version = "0.9.104" @@ -1850,6 +2730,30 @@ dependencies = [ "serde_json", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking_lot" version = "0.11.2" @@ -1880,7 +2784,7 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.13", "smallvec", "winapi", ] @@ -1893,7 +2797,7 @@ checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.13", "smallvec", "windows-sys 0.36.1", ] @@ -1919,11 +2823,30 @@ dependencies = [ "base64 0.13.0", ] +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project" @@ -1941,8 +2864,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" dependencies = [ "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "quote 1.0.45", + "syn 2.0.117", ] [[package]] @@ -1963,9 +2886,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69044d1c00894fc1f43d9485aadb6ab6e68df90608fa52cf1074cda6420c6b76" dependencies = [ - "rand", + "rand 0.8.5", "socket2 0.4.4", - "thiserror", + "thiserror 1.0.31", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", ] [[package]] @@ -1974,12 +2907,104 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[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]] +name = "portable-atomic-util" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2 1.0.93", + "syn 2.0.117", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "0.4.30" @@ -2007,7 +3032,7 @@ dependencies = [ "bytes", "once_cell", "protobuf-support", - "thiserror", + "thiserror 1.0.31", ] [[package]] @@ -2022,7 +3047,7 @@ dependencies = [ "protobuf-parse", "regex", "tempfile", - "thiserror", + "thiserror 1.0.31", ] [[package]] @@ -2037,7 +3062,7 @@ dependencies = [ "protobuf", "protobuf-support", "tempfile", - "thiserror", + "thiserror 1.0.31", "which", ] @@ -2047,7 +3072,7 @@ version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b088fd20b938a875ea00843b6faf48579462630015c3788d397ad6a786663252" dependencies = [ - "thiserror", + "thiserror 1.0.31", ] [[package]] @@ -2056,13 +3081,22 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e1dcb320d6839f6edb64f7a4a59d39b30480d4d1765b56873f7c858538a5fe" +[[package]] +name = "quick-xml" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +dependencies = [ + "memchr", +] + [[package]] name = "quickcheck" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ - "rand", + "rand 0.8.5", ] [[package]] @@ -2076,13 +3110,25 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.38" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2 1.0.93", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.5" @@ -2090,8 +3136,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -2101,16 +3157,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom", + "getrandom 0.3.4", ] [[package]] @@ -2133,6 +3208,20 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem 3.0.6", + "ring 0.17.14", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.2.13" @@ -2142,15 +3231,24 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +dependencies = [ + "bitflags 2.11.0", +] + [[package]] name = "redox_users" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom", - "redox_syscall", - "thiserror", + "getrandom 0.2.15", + "redox_syscall 0.2.13", + "thiserror 1.0.31", ] [[package]] @@ -2203,7 +3301,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.7", "http-body", "hyper", "hyper-rustls", @@ -2218,7 +3316,7 @@ dependencies = [ "pin-project-lite", "rustls 0.21.12", "rustls-native-certs 0.6.2", - "rustls-pemfile 1.0.0", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", @@ -2238,6 +3336,16 @@ dependencies = [ "winreg 0.50.0", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.16.20" @@ -2255,16 +3363,42 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.3" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babe80d5c16becf6594aa32ad2be8fe08498e7ae60b77de8df700e67f191d7e" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", - "getrandom", + "cfg-if", + "getrandom 0.2.15", "libc", - "spin 0.9.3", "untrusted 0.9.0", - "windows-sys 0.48.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rtcp" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81d30d1c4091644431c22acf9f8be6191b56805e0e977f15ca7104b4a6d6eaec" +dependencies = [ + "bytes", + "thiserror 1.0.31", + "webrtc-util", +] + +[[package]] +name = "rtp" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f126f38ea84c02480e32e547c1459a939052f74fb92117ac3eef23fdac6b023" +dependencies = [ + "bytes", + "memchr", + "portable-atomic", + "rand 0.9.3", + "serde", + "thiserror 1.0.31", + "webrtc-util", ] [[package]] @@ -2289,6 +3423,37 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom 7.1.1", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + [[package]] name = "rustls" version = "0.20.4" @@ -2308,22 +3473,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.3", + "ring 0.17.14", "rustls-webpki 0.101.7", "sct", ] [[package]] name = "rustls" -version = "0.23.21" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "log", "once_cell", - "ring 0.17.3", + "ring 0.17.14", "rustls-pki-types", - "rustls-webpki 0.102.8", + "rustls-webpki 0.103.11", "subtle", "zeroize", ] @@ -2334,23 +3499,22 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" dependencies = [ - "openssl-probe", - "rustls-pemfile 1.0.0", + "openssl-probe 0.1.5", + "rustls-pemfile", "schannel", - "security-framework", + "security-framework 2.11.1", ] [[package]] name = "rustls-native-certs" -version = "0.7.3" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe", - "rustls-pemfile 2.2.0", + "openssl-probe 0.2.1", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.7.0", ] [[package]] @@ -2363,39 +3527,33 @@ dependencies = [ ] [[package]] -name = "rustls-pemfile" -version = "2.2.0" +name = "rustls-pki-types" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ - "rustls-pki-types", + "zeroize", ] -[[package]] -name = "rustls-pki-types" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" - [[package]] name = "rustls-platform-verifier" -version = "0.3.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" dependencies = [ - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "jni", "log", "once_cell", - "rustls 0.23.21", - "rustls-native-certs 0.7.3", + "rustls 0.23.37", + "rustls-native-certs 0.8.3", "rustls-platform-verifier-android", - "rustls-webpki 0.102.8", - "security-framework", + "rustls-webpki 0.103.11", + "security-framework 3.7.0", "security-framework-sys", - "webpki-roots 0.26.7", - "winapi", + "webpki-root-certs", + "windows-sys 0.59.0", ] [[package]] @@ -2410,17 +3568,17 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.3", + "ring 0.17.14", "untrusted 0.9.0", ] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "20a6af516fea4b20eccceaf166e8aa666ac996208e8a644ce3ef5aa783bc7cd4" dependencies = [ - "ring 0.17.3", + "ring 0.17.14", "rustls-pki-types", "untrusted 0.9.0", ] @@ -2471,48 +3629,102 @@ dependencies = [ "untrusted 0.7.1", ] +[[package]] +name = "sdp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c374dceda16965d541c8800ce9cc4e1c14acfd661ddf7952feeedc3411e5c6" +dependencies = [ + "rand 0.9.3", + "substring", + "thiserror 1.0.31", + "url", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.7.0", - "core-foundation", + "bitflags 2.11.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", "core-foundation-sys", "libc", - "num-bigint", "security-framework-sys", ] [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" -version = "1.0.217" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "quote 1.0.45", + "syn 2.0.117", ] [[package]] @@ -2558,6 +3770,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.2" @@ -2590,6 +3813,16 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simple_asn1" version = "0.6.1" @@ -2598,21 +3831,57 @@ checksum = "4a762b1c38b9b990c694b9c2f8abe3372ce6a9ceaae6bca39cfc46e054f45745" dependencies = [ "num-bigint", "num-traits", - "thiserror", + "thiserror 1.0.31", "time", ] [[package]] name = "slab" -version = "0.4.6" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.8.0" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.11.0", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix", + "thiserror 2.0.18", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smol_str" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -2645,6 +3914,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "sodiumoxide" version = "0.2.7" @@ -2672,6 +3951,16 @@ 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]] name = "sqlformat" version = "0.1.8" @@ -2727,7 +4016,7 @@ dependencies = [ "paste", "percent-encoding", "rustls 0.20.4", - "rustls-pemfile 1.0.0", + "rustls-pemfile", "serde", "serde_json", "sha2", @@ -2735,7 +4024,7 @@ dependencies = [ "sqlformat", "sqlx-rt", "stringprep", - "thiserror", + "thiserror 1.0.31", "tokio-stream", "url", "webpki-roots 0.22.4", @@ -2749,10 +4038,10 @@ checksum = "f40c63177cf23d356b159b60acd27c54af7423f1736988502e36bae9a712118f" dependencies = [ "dotenv", "either", - "heck", + "heck 0.4.0", "once_cell", "proc-macro2 1.0.93", - "quote 1.0.38", + "quote 1.0.45", "serde_json", "sha2", "sqlx-core", @@ -2772,6 +4061,12 @@ dependencies = [ "tokio-rustls 0.23.4", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "static_assertions" version = "1.1.0" @@ -2794,6 +4089,40 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "stun" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a512c5d501e3e3b5a4bb3e8e31462d56d54a66b95a28b8596e14422bf21c32b" +dependencies = [ + "base64 0.22.1", + "crc", + "lazy_static", + "md-5", + "rand 0.9.3", + "ring 0.17.14", + "subtle", + "thiserror 1.0.31", + "tokio", + "url", + "webrtc-util", +] + +[[package]] +name = "substring" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" +dependencies = [ + "autocfg", +] + [[package]] name = "subtle" version = "2.6.1" @@ -2818,18 +4147,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04066589568b72ec65f42d65a1a52436e954b168773148893c020269563decf2" dependencies = [ "proc-macro2 1.0.93", - "quote 1.0.38", + "quote 1.0.45", "unicode-xid 0.2.3", ] [[package]] name = "syn" -version = "2.0.96" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2 1.0.93", - "quote 1.0.38", + "quote 1.0.45", "unicode-ident", ] @@ -2839,6 +4168,17 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.45", + "syn 2.0.117", +] + [[package]] name = "sysinfo" version = "0.29.10" @@ -2860,7 +4200,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -2883,7 +4223,7 @@ dependencies = [ "cfg-if", "fastrand", "libc", - "redox_syscall", + "redox_syscall 0.2.13", "remove_dir_all", "winapi", ] @@ -2912,7 +4252,16 @@ version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.31", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", ] [[package]] @@ -2922,28 +4271,64 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2 1.0.93", - "quote 1.0.38", + "quote 1.0.45", "syn 1.0.93", ] +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.45", + "syn 2.0.117", +] + [[package]] name = "time" -version = "0.3.9" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ + "deranged", "itoa", "libc", + "num-conv", "num_threads", + "powerfmt", "quickcheck", + "serde_core", + "time-core", "time-macros", ] +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + [[package]] name = "time-macros" -version = "0.2.4" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] [[package]] name = "tinyvec" @@ -2962,31 +4347,30 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.43.0" +version = "1.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" dependencies = [ - "backtrace", "bytes", "libc", "mio", "parking_lot 0.12.0", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.8", + "socket2 0.6.3", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "quote 1.0.45", + "syn 2.0.117", ] [[package]] @@ -3026,7 +4410,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls 0.23.21", + "rustls 0.23.37", "tokio", ] @@ -3041,7 +4425,7 @@ dependencies = [ "futures-sink", "futures-util", "pin-project", - "thiserror", + "thiserror 1.0.31", "tokio", "tokio-util", ] @@ -3054,7 +4438,7 @@ checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" dependencies = [ "either", "futures-util", - "thiserror", + "thiserror 1.0.31", "tokio", ] @@ -3078,7 +4462,26 @@ dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.17.2", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "native-tls", + "rustls 0.23.37", + "rustls-native-certs 0.8.3", + "rustls-pki-types", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.1", + "tungstenite 0.26.2", + "webpki-roots 0.26.7", ] [[package]] @@ -3168,7 +4571,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", + "http 0.2.7", "http-body", "http-range-header", "httpdate", @@ -3215,8 +4618,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "quote 1.0.45", + "syn 2.0.117", ] [[package]] @@ -3243,16 +4646,59 @@ dependencies = [ "base64 0.13.0", "byteorder", "bytes", - "http", + "http 0.2.7", "httparse", "log", - "rand", + "rand 0.8.5", "sha-1", - "thiserror", + "thiserror 1.0.31", "url", "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http 1.4.0", + "httparse", + "log", + "native-tls", + "rand 0.9.3", + "rustls 0.23.37", + "rustls-native-certs 0.8.3", + "rustls-pki-types", + "sha1", + "thiserror 2.0.18", + "utf-8", + "webpki-roots 0.26.7", +] + +[[package]] +name = "turn" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ed995882f66ab94238de77c62e5e778389698ab700afa4696f4754da8f457cb" +dependencies = [ + "async-trait", + "base64 0.22.1", + "futures", + "log", + "md-5", + "portable-atomic", + "rand 0.9.3", + "ring 0.17.14", + "stun", + "thiserror 1.0.31", + "tokio", + "tokio-util", + "webrtc-util", +] + [[package]] name = "typenum" version = "1.15.0" @@ -3261,12 +4707,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "unicase" -version = "2.6.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-bidi" @@ -3319,6 +4762,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[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]] name = "untrusted" version = "0.7.1" @@ -3333,14 +4786,24 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.2.2" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", + "serde", +] + +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", ] [[package]] @@ -3349,13 +4812,27 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" -version = "1.12.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ - "getrandom", + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -3376,6 +4853,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waitgroup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" +dependencies = [ + "atomic-waker", +] + [[package]] name = "walkdir" version = "2.3.2" @@ -3403,6 +4889,30 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -3424,8 +4934,8 @@ dependencies = [ "bumpalo", "log", "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "quote 1.0.45", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -3448,7 +4958,7 @@ version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ - "quote 1.0.38", + "quote 1.0.45", "wasm-bindgen-macro-support", ] @@ -3459,8 +4969,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "quote 1.0.45", + "syn 2.0.117", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3475,41 +4985,202 @@ dependencies = [ ] [[package]] -name = "web-sys" -version = "0.3.77" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ - "js-sys", - "wasm-bindgen", + "leb128fmt", + "wasmparser", ] [[package]] -name = "webpki" -version = "0.22.0" +name = "wasm-metadata" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "anyhow", + "indexmap 2.7.0", + "wasm-encoder", + "wasmparser", ] [[package]] -name = "webpki-roots" -version = "0.22.4" +name = "wasmparser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "webpki", + "bitflags 2.11.0", + "hashbrown 0.15.2", + "indexmap 2.7.0", + "semver", ] [[package]] -name = "webpki-roots" -version = "0.25.4" +name = "wayland-backend" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - -[[package]] +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +dependencies = [ + "bitflags 2.11.0", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.11.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2 1.0.93", + "quick-xml", + "quote 1.0.45", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" +dependencies = [ + "webpki", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] name = "webpki-roots" version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3518,6 +5189,184 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webrtc" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08fd686c0920ac08f3a57eacc48e31f0e4ca1ffefba4478784606f78c14e83ad" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "dtls", + "hex", + "interceptor", + "lazy_static", + "log", + "portable-atomic", + "rand 0.9.3", + "rcgen", + "regex", + "ring 0.17.14", + "rtcp", + "rtp", + "sdp", + "serde", + "serde_json", + "sha2", + "smol_str", + "stun", + "thiserror 1.0.31", + "tokio", + "turn", + "unicase", + "url", + "waitgroup", + "webrtc-data", + "webrtc-ice", + "webrtc-mdns", + "webrtc-media", + "webrtc-sctp", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "webrtc-data" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062a5438d63bb0756a221693d76cc0dd6119affee1dfdfe57abe3a2a8c8b3eea" +dependencies = [ + "bytes", + "log", + "portable-atomic", + "thiserror 1.0.31", + "tokio", + "webrtc-sctp", + "webrtc-util", +] + +[[package]] +name = "webrtc-ice" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cb13fd1a373e68addc4bba0c8ca058627518e54342583d024bdcbb8ae5d97d" +dependencies = [ + "arc-swap", + "async-trait", + "crc", + "log", + "portable-atomic", + "rand 0.9.3", + "serde", + "serde_json", + "stun", + "thiserror 1.0.31", + "tokio", + "turn", + "url", + "uuid", + "waitgroup", + "webrtc-mdns", + "webrtc-util", +] + +[[package]] +name = "webrtc-mdns" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17279a067e75df72ce923fdeb7f04cd808f6f5aa4910dc6bcb4fbe66b396ace" +dependencies = [ + "log", + "socket2 0.5.8", + "thiserror 1.0.31", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-media" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a84c910fec0848fd5a0d8a5651e0ddbdedaf25a7d3ae3f0b15f71ac73a1773" +dependencies = [ + "byteorder", + "bytes", + "rand 0.9.3", + "rtp", + "thiserror 1.0.31", +] + +[[package]] +name = "webrtc-sctp" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f985465467d8910c1f8ac4382cd64f83b1f6a1a75021a82b221546f6fb3b856f" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "crc", + "log", + "portable-atomic", + "rand 0.9.3", + "thiserror 1.0.31", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-srtp" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d8cdc33413f1d0192670a80ce93d17cb78d57fe3a2414be30d6f6dff121123" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "byteorder", + "bytes", + "ctr", + "hmac", + "log", + "rtcp", + "rtp", + "sha1", + "subtle", + "thiserror 1.0.31", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-util" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c0c7e0c8f280f2bbfae442701465777ac07adaf46ce0c5863cd58e13fe472a" +dependencies = [ + "async-trait", + "bitflags 1.3.2", + "bytes", + "ipnet", + "lazy_static", + "log", + "nix 0.26.4", + "portable-atomic", + "rand 0.9.3", + "thiserror 1.0.31", + "tokio", + "winapi", +] + [[package]] name = "which" version = "4.2.5" @@ -3531,11 +5380,12 @@ dependencies = [ [[package]] name = "whoami" -version = "1.2.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ - "wasm-bindgen", + "libredox", + "wasite", "web-sys", ] @@ -3598,6 +5448,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.36.1" @@ -3611,6 +5467,15 @@ dependencies = [ "windows_x86_64_msvc 0.36.1", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -3638,6 +5503,30 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -3669,6 +5558,12 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3687,6 +5582,12 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3705,6 +5606,12 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3729,6 +5636,12 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3747,6 +5660,12 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3759,6 +5678,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3777,6 +5702,12 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3827,11 +5758,257 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.7.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2 1.0.93", + "quote 1.0.45", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap 2.7.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.7.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid 0.2.3", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom 7.1.1", + "oid-registry", + "ring 0.17.14", + "rusticata-macros", + "thiserror 1.0.31", + "time", +] + +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.45", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.45", + "syn 2.0.117", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.45", + "syn 2.0.117", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.45", + "syn 2.0.117", +] [[package]] name = "zstd" diff --git a/db_v2.sqlite3 b/db_v2.sqlite3 index 93d9801b0..5e1c7814d 100644 Binary files a/db_v2.sqlite3 and b/db_v2.sqlite3 differ diff --git a/libs/hbb_common b/libs/hbb_common index 83419b654..36bf31de1 160000 --- a/libs/hbb_common +++ b/libs/hbb_common @@ -1 +1 @@ -Subproject commit 83419b6549636ee39dacef7776c473f5802e08d6 +Subproject commit 36bf31de1b8b40a257cff07d47e3da1c2e0497da diff --git a/src/common.rs b/src/common.rs index 9c76f41b8..9ebeff446 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,16 +1,50 @@ use clap::App; use hbb_common::{ - allow_err, anyhow::{Context, Result}, get_version_number, log, tokio, ResultType + allow_err, + anyhow::{Context, Result}, + get_version_number, log, tokio, ResultType, }; +use http::HeaderMap; use ini::Ini; +use once_cell::sync::Lazy; use sodiumoxide::crypto::sign; use std::{ + collections::HashMap, io::prelude::*, io::Read, - net::SocketAddr, + net::{IpAddr, SocketAddr}, + sync::Mutex, time::{Instant, SystemTime}, }; +const TRUST_PROXY_HEADERS_ENV: &str = "TRUST_PROXY_HEADERS"; +const CONN_RATE_WINDOW_SECONDS_ENV: &str = "CONNECTION_RATE_WINDOW_SECONDS"; +const MAX_CONN_PER_IP_PER_WINDOW_ENV: &str = "MAX_CONNECTIONS_PER_IP_PER_WINDOW"; +const MAX_CONNECTION_RATE_ENTRIES_ENV: &str = "MAX_CONNECTION_RATE_ENTRIES"; +const CONTROL_MSG_RATE_WINDOW_SECONDS_ENV: &str = "CONTROL_MESSAGE_RATE_WINDOW_SECONDS"; +const MAX_CONTROL_MSGS_PER_IP_PER_WINDOW_ENV: &str = "MAX_CONTROL_MESSAGES_PER_IP_PER_WINDOW"; +const UDP_RATE_WINDOW_SECONDS_ENV: &str = "UDP_RATE_WINDOW_SECONDS"; +const MAX_UDP_PACKETS_PER_IP_PER_WINDOW_ENV: &str = "MAX_UDP_PACKETS_PER_IP_PER_WINDOW"; +const DEFAULT_CONN_RATE_WINDOW_SECONDS: usize = 60; +const DEFAULT_MAX_CONN_PER_IP_PER_WINDOW: usize = 120; +const DEFAULT_MAX_CONNECTION_RATE_ENTRIES: usize = 8_192; +const DEFAULT_CONTROL_MSG_RATE_WINDOW_SECONDS: usize = 60; +const DEFAULT_MAX_CONTROL_MSGS_PER_IP_PER_WINDOW: usize = 1_200; +const DEFAULT_UDP_RATE_WINDOW_SECONDS: usize = 60; +const DEFAULT_MAX_UDP_PACKETS_PER_IP_PER_WINDOW: usize = 240; + +#[derive(Clone, Copy)] +struct ConnectionRateEntry { + window_started_at: Instant, + last_seen_at: Instant, + count: usize, +} + +static CONNECTION_RATE_LIMITS: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); +static PROTECTION_STATS: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); + #[allow(dead_code)] pub(crate) fn get_expired_time() -> Instant { let now = Instant::now(); @@ -95,6 +129,247 @@ pub fn get_arg_or(name: &str, default: String) -> String { std::env::var(arg_name(name)).unwrap_or(default) } +#[allow(dead_code)] +pub fn trust_proxy_headers() -> bool { + matches!( + std::env::var(TRUST_PROXY_HEADERS_ENV) + .unwrap_or_default() + .trim() + .to_ascii_lowercase() + .as_str(), + "y" | "yes" | "true" | "1" + ) +} + +fn env_usize_or(name: &str, default: usize) -> usize { + std::env::var(name) + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(default) +} + +fn conn_rate_window_seconds() -> usize { + env_usize_or( + CONN_RATE_WINDOW_SECONDS_ENV, + DEFAULT_CONN_RATE_WINDOW_SECONDS, + ) +} + +fn max_conn_per_ip_per_window() -> usize { + env_usize_or( + MAX_CONN_PER_IP_PER_WINDOW_ENV, + DEFAULT_MAX_CONN_PER_IP_PER_WINDOW, + ) +} + +fn max_connection_rate_entries() -> usize { + env_usize_or( + MAX_CONNECTION_RATE_ENTRIES_ENV, + DEFAULT_MAX_CONNECTION_RATE_ENTRIES, + ) +} + +fn udp_rate_window_seconds() -> usize { + env_usize_or(UDP_RATE_WINDOW_SECONDS_ENV, DEFAULT_UDP_RATE_WINDOW_SECONDS) +} + +fn control_msg_rate_window_seconds() -> usize { + env_usize_or( + CONTROL_MSG_RATE_WINDOW_SECONDS_ENV, + DEFAULT_CONTROL_MSG_RATE_WINDOW_SECONDS, + ) +} + +fn max_control_msgs_per_ip_per_window() -> usize { + env_usize_or( + MAX_CONTROL_MSGS_PER_IP_PER_WINDOW_ENV, + DEFAULT_MAX_CONTROL_MSGS_PER_IP_PER_WINDOW, + ) +} + +fn max_udp_packets_per_ip_per_window() -> usize { + env_usize_or( + MAX_UDP_PACKETS_PER_IP_PER_WINDOW_ENV, + DEFAULT_MAX_UDP_PACKETS_PER_IP_PER_WINDOW, + ) +} + +fn prune_connection_rate_limits( + entries: &mut HashMap, + now: Instant, + window_secs: usize, +) { + entries.retain(|_, entry| { + now.duration_since(entry.last_seen_at).as_secs() < (window_secs * 2) as u64 + }); +} + +fn evict_oldest_connection_rate_entry(entries: &mut HashMap) -> bool { + let oldest_key = entries + .iter() + .min_by_key(|(_, entry)| entry.last_seen_at) + .map(|(key, _)| key.clone()); + if let Some(key) = oldest_key { + entries.remove(&key); + return true; + } + false +} + +#[allow(dead_code)] +fn allow_ip_activity(scope: &str, addr: SocketAddr, window_secs: usize, max_events: usize) -> bool { + if addr.ip().is_loopback() { + return true; + } + let now = Instant::now(); + let mut lock = CONNECTION_RATE_LIMITS.lock().unwrap(); + prune_connection_rate_limits(&mut lock, now, window_secs); + let key = format!("{scope}|{}", addr.ip()); + if !lock.contains_key(&key) { + let max_entries = max_connection_rate_entries(); + if max_entries > 0 + && lock.len() >= max_entries + && evict_oldest_connection_rate_entry(&mut lock) + { + record_protection_event("connection_rate_entries_evicted"); + } + if max_entries > 0 && lock.len() >= max_entries { + record_protection_event("connection_rate_entries_rejected"); + return false; + } + lock.insert( + key.clone(), + ConnectionRateEntry { + window_started_at: now, + last_seen_at: now, + count: 0, + }, + ); + } + let entry = lock.get_mut(&key).expect("entry inserted above"); + if now.duration_since(entry.window_started_at).as_secs() >= window_secs as u64 { + entry.window_started_at = now; + entry.count = 0; + } + entry.last_seen_at = now; + if entry.count >= max_events { + return false; + } + entry.count += 1; + true +} + +#[allow(dead_code)] +pub fn allow_connection_from_ip(scope: &str, addr: SocketAddr) -> bool { + let allowed = allow_ip_activity( + scope, + addr, + conn_rate_window_seconds(), + max_conn_per_ip_per_window(), + ); + if !allowed { + record_protection_event("connection_rate_limit_hits"); + } + allowed +} + +#[allow(dead_code)] +pub fn allow_udp_packet_from_ip(scope: &str, addr: SocketAddr) -> bool { + let allowed = allow_ip_activity( + scope, + addr, + udp_rate_window_seconds(), + max_udp_packets_per_ip_per_window(), + ); + if !allowed { + record_protection_event("udp_rate_limit_hits"); + } + allowed +} + +#[allow(dead_code)] +pub fn allow_control_message_from_ip(scope: &str, addr: SocketAddr) -> bool { + let allowed = allow_ip_activity( + scope, + addr, + control_msg_rate_window_seconds(), + max_control_msgs_per_ip_per_window(), + ); + if !allowed { + record_protection_event("control_message_rate_limit_hits"); + } + allowed +} + +#[allow(dead_code)] +pub fn record_protection_event(name: &'static str) { + let mut lock = PROTECTION_STATS.lock().unwrap(); + *lock.entry(name).or_insert(0) += 1; +} + +#[allow(dead_code)] +pub fn protection_stats_snapshot() -> Vec<(String, u64)> { + let mut entries: Vec<(String, u64)> = PROTECTION_STATS + .lock() + .unwrap() + .iter() + .map(|(name, value)| ((*name).to_owned(), *value)) + .collect(); + entries.sort_by(|a, b| a.0.cmp(&b.0)); + entries +} + +#[allow(dead_code)] +pub fn protection_limits_summary() -> Vec { + vec![ + format!( + "connections_per_ip_per_window={}/{}s", + max_conn_per_ip_per_window(), + conn_rate_window_seconds() + ), + format!( + "connection_rate_entry_cap={}", + max_connection_rate_entries() + ), + format!( + "control_messages_per_ip_per_window={}/{}s", + max_control_msgs_per_ip_per_window(), + control_msg_rate_window_seconds() + ), + format!( + "udp_packets_per_ip_per_window={}/{}s", + max_udp_packets_per_ip_per_window(), + udp_rate_window_seconds() + ), + format!("trust_proxy_headers={}", trust_proxy_headers()), + ] +} + +#[allow(dead_code)] +pub fn apply_trusted_proxy_addr(addr: SocketAddr, headers: &HeaderMap) -> SocketAddr { + if !trust_proxy_headers() { + return addr; + } + let forwarded_ip = headers + .get("X-Real-IP") + .or_else(|| headers.get("X-Forwarded-For")) + .and_then(|header_value| header_value.to_str().ok()) + .and_then(parse_forwarded_ip); + forwarded_ip + .map(|ip| SocketAddr::new(ip, addr.port())) + .unwrap_or(addr) +} + +fn parse_forwarded_ip(value: &str) -> Option { + value + .split(',') + .next() + .map(str::trim) + .filter(|value| !value.is_empty()) + .and_then(|value| value.parse::().ok()) +} + #[allow(dead_code)] #[inline] pub fn now() -> u64 { @@ -189,7 +464,6 @@ pub async fn listen_signal() -> Result<()> { unreachable!(); } - pub fn check_software_update() { const ONE_DAY_IN_SECONDS: u64 = 60 * 60 * 24; std::thread::spawn(move || loop { @@ -200,8 +474,10 @@ pub fn check_software_update() { #[tokio::main(flavor = "current_thread")] async fn check_software_update_() -> hbb_common::ResultType<()> { - let (request, url) = hbb_common::version_check_request(hbb_common::VER_TYPE_RUSTDESK_SERVER.to_string()); - let latest_release_response = reqwest::Client::builder().build()? + let (request, url) = + hbb_common::version_check_request(hbb_common::VER_TYPE_RUSTDESK_SERVER.to_string()); + let latest_release_response = reqwest::Client::builder() + .build()? .post(url) .json(&request) .send() @@ -212,7 +488,193 @@ async fn check_software_update_() -> hbb_common::ResultType<()> { let response_url = resp.url; let latest_release_version = response_url.rsplit('/').next().unwrap_or_default(); if get_version_number(&latest_release_version) > get_version_number(crate::version::VERSION) { - log::info!("new version is available: {}", latest_release_version); + log::info!("new version is available: {}", latest_release_version); } Ok(()) -} \ No newline at end of file +} + +#[cfg(test)] +mod tests { + use super::{ + allow_connection_from_ip, allow_control_message_from_ip, allow_udp_packet_from_ip, + apply_trusted_proxy_addr, conn_rate_window_seconds, control_msg_rate_window_seconds, + max_conn_per_ip_per_window, max_connection_rate_entries, + max_control_msgs_per_ip_per_window, max_udp_packets_per_ip_per_window, + protection_limits_summary, protection_stats_snapshot, record_protection_event, + trust_proxy_headers, udp_rate_window_seconds, CONNECTION_RATE_LIMITS, + CONN_RATE_WINDOW_SECONDS_ENV, CONTROL_MSG_RATE_WINDOW_SECONDS_ENV, + MAX_CONNECTION_RATE_ENTRIES_ENV, MAX_CONN_PER_IP_PER_WINDOW_ENV, + MAX_CONTROL_MSGS_PER_IP_PER_WINDOW_ENV, MAX_UDP_PACKETS_PER_IP_PER_WINDOW_ENV, + PROTECTION_STATS, TRUST_PROXY_HEADERS_ENV, UDP_RATE_WINDOW_SECONDS_ENV, + }; + use http::HeaderMap; + use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + sync::Mutex, + time::Duration, + }; + + static TEST_PROXY_HEADERS_LOCK: Mutex<()> = Mutex::new(()); + + #[test] + fn trusted_proxy_headers_are_disabled_by_default() { + let _guard = TEST_PROXY_HEADERS_LOCK.lock().unwrap(); + std::env::remove_var(TRUST_PROXY_HEADERS_ENV); + assert!(!trust_proxy_headers()); + } + + #[test] + fn apply_trusted_proxy_addr_only_changes_addr_when_enabled() { + let _guard = TEST_PROXY_HEADERS_LOCK.lock().unwrap(); + let original = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 4)), 21117); + let mut headers = HeaderMap::new(); + headers.insert( + "X-Forwarded-For", + "198.51.100.10, 10.0.0.1".parse().unwrap(), + ); + + std::env::remove_var(TRUST_PROXY_HEADERS_ENV); + assert_eq!(apply_trusted_proxy_addr(original, &headers), original); + + std::env::set_var(TRUST_PROXY_HEADERS_ENV, "Y"); + assert_eq!( + apply_trusted_proxy_addr(original, &headers), + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(198, 51, 100, 10)), 21117) + ); + + std::env::remove_var(TRUST_PROXY_HEADERS_ENV); + } + + #[test] + fn connection_rate_limiter_enforces_per_ip_window_and_exempts_loopback() { + let _guard = TEST_PROXY_HEADERS_LOCK.lock().unwrap(); + CONNECTION_RATE_LIMITS.lock().unwrap().clear(); + PROTECTION_STATS.lock().unwrap().clear(); + std::env::set_var(MAX_CONN_PER_IP_PER_WINDOW_ENV, "2"); + std::env::set_var(CONN_RATE_WINDOW_SECONDS_ENV, "60"); + std::env::set_var(MAX_CONNECTION_RATE_ENTRIES_ENV, "4"); + std::env::set_var(MAX_CONTROL_MSGS_PER_IP_PER_WINDOW_ENV, "2"); + std::env::set_var(CONTROL_MSG_RATE_WINDOW_SECONDS_ENV, "60"); + std::env::set_var(MAX_UDP_PACKETS_PER_IP_PER_WINDOW_ENV, "3"); + std::env::set_var(UDP_RATE_WINDOW_SECONDS_ENV, "60"); + + let remote = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(198, 51, 100, 10)), 21117); + assert!(allow_connection_from_ip("hbbs-main", remote)); + assert!(allow_connection_from_ip("hbbs-main", remote)); + assert!(!allow_connection_from_ip("hbbs-main", remote)); + assert!(allow_control_message_from_ip("hbbs-control", remote)); + assert!(allow_control_message_from_ip("hbbs-control", remote)); + assert!(!allow_control_message_from_ip("hbbs-control", remote)); + assert!(allow_udp_packet_from_ip("hbbs-udp", remote)); + assert!(allow_udp_packet_from_ip("hbbs-udp", remote)); + assert!(allow_udp_packet_from_ip("hbbs-udp", remote)); + assert!(!allow_udp_packet_from_ip("hbbs-udp", remote)); + + let loopback = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 21117); + assert!(allow_connection_from_ip("hbbs-main", loopback)); + assert!(allow_connection_from_ip("hbbs-main", loopback)); + assert!(allow_connection_from_ip("hbbs-main", loopback)); + assert_eq!( + protection_stats_snapshot(), + vec![ + ("connection_rate_limit_hits".to_owned(), 1), + ("control_message_rate_limit_hits".to_owned(), 1), + ("udp_rate_limit_hits".to_owned(), 1), + ] + ); + record_protection_event("peer_records_pruned"); + assert_eq!( + protection_stats_snapshot(), + vec![ + ("connection_rate_limit_hits".to_owned(), 1), + ("control_message_rate_limit_hits".to_owned(), 1), + ("peer_records_pruned".to_owned(), 1), + ("udp_rate_limit_hits".to_owned(), 1), + ] + ); + + std::env::remove_var(MAX_CONN_PER_IP_PER_WINDOW_ENV); + std::env::remove_var(CONN_RATE_WINDOW_SECONDS_ENV); + std::env::remove_var(MAX_CONNECTION_RATE_ENTRIES_ENV); + std::env::remove_var(MAX_CONTROL_MSGS_PER_IP_PER_WINDOW_ENV); + std::env::remove_var(CONTROL_MSG_RATE_WINDOW_SECONDS_ENV); + std::env::remove_var(MAX_UDP_PACKETS_PER_IP_PER_WINDOW_ENV); + std::env::remove_var(UDP_RATE_WINDOW_SECONDS_ENV); + CONNECTION_RATE_LIMITS.lock().unwrap().clear(); + PROTECTION_STATS.lock().unwrap().clear(); + assert_eq!( + max_conn_per_ip_per_window(), + super::DEFAULT_MAX_CONN_PER_IP_PER_WINDOW + ); + assert_eq!( + conn_rate_window_seconds(), + super::DEFAULT_CONN_RATE_WINDOW_SECONDS + ); + assert_eq!( + max_connection_rate_entries(), + super::DEFAULT_MAX_CONNECTION_RATE_ENTRIES + ); + assert_eq!( + max_control_msgs_per_ip_per_window(), + super::DEFAULT_MAX_CONTROL_MSGS_PER_IP_PER_WINDOW + ); + assert_eq!( + control_msg_rate_window_seconds(), + super::DEFAULT_CONTROL_MSG_RATE_WINDOW_SECONDS + ); + assert_eq!( + max_udp_packets_per_ip_per_window(), + super::DEFAULT_MAX_UDP_PACKETS_PER_IP_PER_WINDOW + ); + assert_eq!( + udp_rate_window_seconds(), + super::DEFAULT_UDP_RATE_WINDOW_SECONDS + ); + assert_eq!( + protection_limits_summary(), + vec![ + "connections_per_ip_per_window=120/60s".to_owned(), + "connection_rate_entry_cap=8192".to_owned(), + "control_messages_per_ip_per_window=1200/60s".to_owned(), + "udp_packets_per_ip_per_window=240/60s".to_owned(), + "trust_proxy_headers=false".to_owned(), + ] + ); + } + + #[test] + fn connection_rate_limit_entries_are_bounded() { + let _guard = TEST_PROXY_HEADERS_LOCK.lock().unwrap(); + CONNECTION_RATE_LIMITS.lock().unwrap().clear(); + PROTECTION_STATS.lock().unwrap().clear(); + std::env::set_var(MAX_CONNECTION_RATE_ENTRIES_ENV, "2"); + std::env::set_var(MAX_CONN_PER_IP_PER_WINDOW_ENV, "10"); + std::env::set_var(CONN_RATE_WINDOW_SECONDS_ENV, "60"); + + let addr1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(198, 51, 100, 1)), 21117); + let addr2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(198, 51, 100, 2)), 21117); + let addr3 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(198, 51, 100, 3)), 21117); + assert!(allow_connection_from_ip("hbbs-main", addr1)); + std::thread::sleep(Duration::from_millis(2)); + assert!(allow_connection_from_ip("hbbs-main", addr2)); + std::thread::sleep(Duration::from_millis(2)); + assert!(allow_connection_from_ip("hbbs-main", addr3)); + + let snapshot = CONNECTION_RATE_LIMITS.lock().unwrap(); + assert_eq!(snapshot.len(), 2); + assert!(!snapshot.contains_key("hbbs-main|198.51.100.1")); + assert!(snapshot.contains_key("hbbs-main|198.51.100.2")); + assert!(snapshot.contains_key("hbbs-main|198.51.100.3")); + drop(snapshot); + assert_eq!( + protection_stats_snapshot(), + vec![("connection_rate_entries_evicted".to_owned(), 1)] + ); + + std::env::remove_var(MAX_CONNECTION_RATE_ENTRIES_ENV); + std::env::remove_var(MAX_CONN_PER_IP_PER_WINDOW_ENV); + std::env::remove_var(CONN_RATE_WINDOW_SECONDS_ENV); + CONNECTION_RATE_LIMITS.lock().unwrap().clear(); + PROTECTION_STATS.lock().unwrap().clear(); + } +} diff --git a/src/database.rs b/src/database.rs index fa1b6edcf..f56b4e20e 100644 --- a/src/database.rs +++ b/src/database.rs @@ -8,6 +8,8 @@ use std::{ops::DerefMut, str::FromStr}; //use sqlx::mysql::MySqlPoolOptions; type Pool = deadpool::managed::Pool; +const DEFAULT_MAX_TOTAL_PEER_RECORDS: usize = 100_000; +const DEFAULT_PEER_RECORD_RETENTION_DAYS: usize = 180; pub struct DbPool { url: String, @@ -33,6 +35,33 @@ impl deadpool::managed::Manager for DbPool { #[derive(Clone)] pub struct Database { pool: Pool, + max_total_peers: usize, + peer_record_retention_days: usize, +} + +pub enum InsertPeerResult { + Inserted(Vec), + PeerLimitReached, +} + +fn env_usize_or(name: &str, default: usize) -> usize { + std::env::var(name) + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(default) +} + +fn peer_limit_reached(total_peers: i64, max_total_peers: usize) -> bool { + max_total_peers > 0 && total_peers >= max_total_peers as i64 +} + +fn peer_retention_prune_enabled(days: usize) -> bool { + days > 0 +} + +fn peer_retention_cutoff_arg(days: usize) -> String { + format!("-{days} days") } #[derive(Default)] @@ -56,6 +85,14 @@ impl Database { .parse() .unwrap_or(1); log::debug!("MAX_DATABASE_CONNECTIONS={}", n); + let max_total_peers = + env_usize_or("MAX_TOTAL_PEER_RECORDS", DEFAULT_MAX_TOTAL_PEER_RECORDS); + let peer_record_retention_days = env_usize_or( + "PEER_RECORD_RETENTION_DAYS", + DEFAULT_PEER_RECORD_RETENTION_DAYS, + ); + log::info!("MAX_TOTAL_PEER_RECORDS={}", max_total_peers); + log::info!("PEER_RECORD_RETENTION_DAYS={}", peer_record_retention_days); let pool = Pool::new( DbPool { url: url.to_owned(), @@ -63,7 +100,11 @@ impl Database { n, ); let _ = pool.get().await?; // test - let db = Database { pool }; + let db = Database { + pool, + max_total_peers, + peer_record_retention_days, + }; db.create_tables().await?; Ok(db) } @@ -109,7 +150,25 @@ impl Database { uuid: &[u8], pk: &[u8], info: &str, - ) -> ResultType> { + ) -> ResultType { + if peer_limit_reached(self.peer_count().await?, self.max_total_peers) { + if peer_retention_prune_enabled(self.peer_record_retention_days) { + let deleted = self.prune_old_peer_records().await?; + if deleted > 0 { + crate::common::record_protection_event("peer_records_pruned"); + log::info!( + "pruned {} old peer records before inserting {}", + deleted, + id + ); + } + } + } + if peer_limit_reached(self.peer_count().await?, self.max_total_peers) { + crate::common::record_protection_event("peer_limit_reached"); + log::warn!("peer record limit reached, rejecting new peer {}", id); + return Ok(InsertPeerResult::PeerLimitReached); + } let guid = uuid::Uuid::new_v4().as_bytes().to_vec(); sqlx::query!( "insert into peer(guid, id, uuid, pk, info) values(?, ?, ?, ?, ?)", @@ -121,7 +180,7 @@ impl Database { ) .execute(self.pool.get().await?.deref_mut()) .await?; - Ok(guid) + Ok(InsertPeerResult::Inserted(guid)) } pub async fn update_pk( @@ -142,16 +201,101 @@ impl Database { .await?; Ok(()) } + + async fn peer_count(&self) -> ResultType { + let row = sqlx::query!("select count(*) as count from peer") + .fetch_one(self.pool.get().await?.deref_mut()) + .await?; + Ok(row.count as i64) + } + + async fn prune_old_peer_records(&self) -> ResultType { + let cutoff = peer_retention_cutoff_arg(self.peer_record_retention_days); + let result = sqlx::query("delete from peer where created_at < datetime('now', ?)") + .bind(cutoff) + .execute(self.pool.get().await?.deref_mut()) + .await?; + Ok(result.rows_affected()) + } } #[cfg(test)] mod tests { + use super::{peer_limit_reached, peer_retention_cutoff_arg, peer_retention_prune_enabled}; use hbb_common::tokio; + use sqlx::Connection as _; + use std::{ + path::PathBuf, + time::{SystemTime, UNIX_EPOCH}, + }; + + #[test] + fn peer_limit_helper_rejects_when_total_reaches_cap() { + assert!(!peer_limit_reached(99, 100)); + assert!(peer_limit_reached(100, 100)); + assert!(peer_limit_reached(101, 100)); + } + + #[test] + fn peer_retention_helpers_use_configured_day_window() { + assert!(peer_retention_prune_enabled(1)); + assert_eq!(peer_retention_cutoff_arg(180), "-180 days"); + } + + #[test] + fn insert_peer_prunes_expired_records_before_enforcing_cap() { + insert_peer_prunes_expired_records_before_enforcing_cap_(); + } + #[test] fn test_insert() { insert(); } + fn temp_db_path(name: &str) -> PathBuf { + let unique = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_nanos(); + std::env::temp_dir().join(format!("rustdesk-server-{name}-{unique}.sqlite")) + } + + #[tokio::main(flavor = "multi_thread")] + async fn insert_peer_prunes_expired_records_before_enforcing_cap_() { + let path = temp_db_path("peer-prune"); + let path_str = path.to_string_lossy().to_string(); + let mut db = super::Database::new(&path_str).await.unwrap(); + db.max_total_peers = 1; + db.peer_record_retention_days = 1; + + let guid = uuid::Uuid::new_v4().as_bytes().to_vec(); + let empty_uuid = Vec::::new(); + let empty_pk = Vec::::new(); + let mut conn = sqlx::SqliteConnection::connect(&path_str).await.unwrap(); + sqlx::query!( + "insert into peer(guid, id, uuid, pk, created_at, info) values(?, ?, ?, ?, datetime('now', '-2 days'), ?)", + guid, + "old-peer", + empty_uuid, + empty_pk, + "" + ) + .execute(&mut conn) + .await + .unwrap(); + + let result = db + .insert_peer("new-peer", &Vec::::new(), &Vec::::new(), "") + .await + .unwrap(); + assert!(matches!(result, super::InsertPeerResult::Inserted(_))); + assert_eq!(db.peer_count().await.unwrap(), 1); + assert!(db.get_peer("old-peer").await.unwrap().is_none()); + assert!(db.get_peer("new-peer").await.unwrap().is_some()); + + std::fs::remove_file(path).ok(); + } + #[tokio::main(flavor = "multi_thread")] async fn insert() { let db = super::Database::new("test.sqlite3").await.unwrap(); diff --git a/src/peer.rs b/src/peer.rs index 4ca87cfad..730deda76 100644 --- a/src/peer.rs +++ b/src/peer.rs @@ -22,6 +22,12 @@ pub const IP_CHANGE_DUR: u64 = 180; pub const IP_CHANGE_DUR_X2: u64 = IP_CHANGE_DUR * 2; pub const DAY_SECONDS: u64 = 3600 * 24; pub const IP_BLOCK_DUR: u64 = 60; +const DEFAULT_MAX_PEER_CACHE_SIZE: usize = 16_384; +const DEFAULT_MAX_PENDING_REGISTRATIONS_PER_IP: usize = 64; +const DEFAULT_MAX_IP_BLOCKER_ENTRIES: usize = 8_192; +const DEFAULT_MAX_IP_CHANGES_ENTRIES: usize = 8_192; +const DEFAULT_MAX_UNIQUE_IP_CHANGES_PER_ID: usize = 32; +const PEER_CACHE_INACTIVE_TIMEOUT_MS: i32 = 30_000; #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub(crate) struct PeerInfo { @@ -63,6 +69,163 @@ pub(crate) type LockPeer = Arc>; pub(crate) struct PeerMap { map: Arc>>, pub(crate) db: database::Database, + max_cached_peers: usize, + max_pending_registrations_per_ip: usize, +} + +struct PeerEvictionEntry { + id: String, + inactive: bool, + last_reg_time: Instant, +} + +fn env_usize_or(name: &str, default: usize) -> usize { + std::env::var(name) + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(default) +} + +fn pending_registration_limit_exceeded(pending_for_ip: usize, max_pending: usize) -> bool { + max_pending > 0 && pending_for_ip >= max_pending +} + +fn max_ip_blocker_entries() -> usize { + env_usize_or("MAX_IP_BLOCKER_ENTRIES", DEFAULT_MAX_IP_BLOCKER_ENTRIES) +} + +fn max_ip_changes_entries() -> usize { + env_usize_or("MAX_IP_CHANGES_ENTRIES", DEFAULT_MAX_IP_CHANGES_ENTRIES) +} + +fn max_unique_ip_changes_per_id() -> usize { + env_usize_or( + "MAX_UNIQUE_IP_CHANGES_PER_ID", + DEFAULT_MAX_UNIQUE_IP_CHANGES_PER_ID, + ) +} + +pub(crate) fn prune_ip_blocker_entries(entries: &mut IpBlockMap) { + entries.retain(|_, (short_window, daily_window)| { + short_window.1.elapsed().as_secs() <= IP_BLOCK_DUR + || daily_window.1.elapsed().as_secs() <= DAY_SECONDS + }); +} + +fn evict_oldest_ip_blocker_entry(entries: &mut IpBlockMap) -> bool { + let oldest_ip = entries + .iter() + .min_by_key(|(_, (short_window, daily_window))| short_window.1.max(daily_window.1)) + .map(|(ip, _)| ip.clone()); + if let Some(ip) = oldest_ip { + entries.remove(&ip); + return true; + } + false +} + +pub(crate) fn allow_ip_registration_attempt(entries: &mut IpBlockMap, ip: &str, id: &str) -> bool { + prune_ip_blocker_entries(entries); + if !entries.contains_key(ip) { + let max_entries = max_ip_blocker_entries(); + if max_entries > 0 && entries.len() >= max_entries && evict_oldest_ip_blocker_entry(entries) + { + record_protection_event("ip_blocker_entries_evicted"); + } + } + if let Some(old) = entries.get_mut(ip) { + let now = Instant::now(); + let counter = &mut old.0; + if counter.1.elapsed().as_secs() > IP_BLOCK_DUR { + counter.0 = 0; + } else if counter.0 > 30 { + return false; + } + counter.0 += 1; + counter.1 = now; + + let counter = &mut old.1; + let is_new = counter.0.get(id).is_none(); + if counter.1.elapsed().as_secs() > DAY_SECONDS { + counter.0.clear(); + } else if counter.0.len() > 300 { + return !is_new; + } + if is_new { + counter.0.insert(id.to_owned()); + } + counter.1 = now; + } else { + entries.insert( + ip.to_owned(), + ((0, Instant::now()), (Default::default(), Instant::now())), + ); + } + true +} + +pub(crate) fn prune_ip_change_entries(entries: &mut IpChangesMap) { + entries.retain(|_, value| value.0.elapsed().as_secs() < IP_CHANGE_DUR_X2 || value.1.len() > 1); +} + +fn evict_oldest_ip_change_entry(entries: &mut IpChangesMap) -> bool { + let oldest_id = entries + .iter() + .min_by_key(|(_, (tm, _))| *tm) + .map(|(id, _)| id.clone()); + if let Some(id) = oldest_id { + entries.remove(&id); + return true; + } + false +} + +pub(crate) fn track_ip_change(entries: &mut IpChangesMap, id: &str, ip: &str) { + prune_ip_change_entries(entries); + if let Some((tm, ips)) = entries.get_mut(id) { + if tm.elapsed().as_secs() > IP_CHANGE_DUR { + *tm = Instant::now(); + ips.clear(); + ips.insert(ip.to_owned(), 1); + return; + } + if let Some(value) = ips.get_mut(ip) { + *value += 1; + return; + } + if max_unique_ip_changes_per_id() > 0 && ips.len() >= max_unique_ip_changes_per_id() { + record_protection_event("ip_change_ip_limit_hits"); + return; + } + ips.insert(ip.to_owned(), 1); + return; + } + let max_entries = max_ip_changes_entries(); + if max_entries > 0 && entries.len() >= max_entries && evict_oldest_ip_change_entry(entries) { + record_protection_event("ip_changes_entries_evicted"); + } + entries.insert( + id.to_owned(), + (Instant::now(), HashMap::from([(ip.to_owned(), 1)])), + ); +} + +fn select_peer_ids_to_evict( + entries: Vec, + max_cached_peers: usize, +) -> Vec { + if max_cached_peers == 0 || entries.len() < max_cached_peers { + return vec![]; + } + let to_remove = entries.len() + 1 - max_cached_peers; + let mut entries = entries; + entries.sort_by_key(|entry| (!entry.inactive, entry.last_reg_time)); + entries + .into_iter() + .take(to_remove) + .map(|entry| entry.id) + .collect() } impl PeerMap { @@ -82,9 +245,21 @@ impl PeerMap { db }); log::info!("DB_URL={}", db); + let max_cached_peers = env_usize_or("MAX_PEER_CACHE_SIZE", DEFAULT_MAX_PEER_CACHE_SIZE); + let max_pending_registrations_per_ip = env_usize_or( + "MAX_PENDING_REGISTRATIONS_PER_IP", + DEFAULT_MAX_PENDING_REGISTRATIONS_PER_IP, + ); + log::info!("MAX_PEER_CACHE_SIZE={}", max_cached_peers); + log::info!( + "MAX_PENDING_REGISTRATIONS_PER_IP={}", + max_pending_registrations_per_ip + ); let pm = Self { map: Default::default(), db: database::Database::new(&db).await?, + max_cached_peers, + max_pending_registrations_per_ip, }; Ok(pm) } @@ -118,9 +293,12 @@ impl PeerMap { log::error!("db.insert_peer failed: {}", err); return register_pk_response::Result::SERVER_ERROR; } - Ok(guid) => { + Ok(database::InsertPeerResult::Inserted(guid)) => { peer.write().await.guid = guid; } + Ok(database::InsertPeerResult::PeerLimitReached) => { + return register_pk_response::Result::PEER_LIMIT_REACHED; + } } } else { if let Err(err) = self.db.update_pk(&guid, &id, &pk, &info_str).await { @@ -138,6 +316,7 @@ impl PeerMap { if p.is_some() { return p; } else if let Ok(Some(v)) = self.db.get_peer(id).await { + self.prune_cache_for_insert().await; let peer = Peer { guid: v.guid, uuid: v.uuid.into(), @@ -154,18 +333,52 @@ impl PeerMap { None } - #[inline] - pub(crate) async fn get_or(&self, id: &str) -> LockPeer { + pub(crate) async fn get_or_for_registration(&self, id: &str, ip: &str) -> Option { if let Some(p) = self.get(id).await { - return p; + return Some(p); } let mut w = self.map.write().await; if let Some(p) = w.get(id) { - return p.clone(); + return Some(p.clone()); + } + let snapshot: Vec<(String, LockPeer)> = w + .iter() + .map(|(id, peer)| (id.clone(), peer.clone())) + .collect(); + let mut entries = Vec::with_capacity(snapshot.len()); + let mut pending_ids_for_ip = HashSet::new(); + for (peer_id, peer) in snapshot { + let peer = peer.read().await; + if peer.info.ip == ip && peer.guid.is_empty() { + pending_ids_for_ip.insert(peer_id.clone()); + } + entries.push(PeerEvictionEntry { + id: peer_id, + inactive: peer.last_reg_time.elapsed().as_millis() as i32 + >= PEER_CACHE_INACTIVE_TIMEOUT_MS, + last_reg_time: peer.last_reg_time, + }); + } + let remove_ids = select_peer_ids_to_evict(entries, self.max_cached_peers); + if !remove_ids.is_empty() { + let remove_ids_set: HashSet = remove_ids.iter().cloned().collect(); + for id in &remove_ids { + w.remove(id); + } + pending_ids_for_ip.retain(|id| !remove_ids_set.contains(id)); } - let tmp = LockPeer::default(); + if pending_registration_limit_exceeded( + pending_ids_for_ip.len(), + self.max_pending_registrations_per_ip, + ) { + return None; + } + let tmp = Arc::new(RwLock::new(Peer { + info: PeerInfo { ip: ip.to_owned() }, + ..Default::default() + })); w.insert(id.to_owned(), tmp.clone()); - tmp + Some(tmp) } #[inline] @@ -177,4 +390,191 @@ impl PeerMap { pub(crate) async fn is_in_memory(&self, id: &str) -> bool { self.map.read().await.contains_key(id) } + + async fn prune_cache_for_insert(&self) { + let snapshot: Vec<(String, LockPeer)> = self + .map + .read() + .await + .iter() + .map(|(id, peer)| (id.clone(), peer.clone())) + .collect(); + let mut entries = Vec::with_capacity(snapshot.len()); + for (id, peer) in snapshot { + let peer = peer.read().await; + entries.push(PeerEvictionEntry { + id, + inactive: peer.last_reg_time.elapsed().as_millis() as i32 + >= PEER_CACHE_INACTIVE_TIMEOUT_MS, + last_reg_time: peer.last_reg_time, + }); + } + let remove_ids = select_peer_ids_to_evict(entries, self.max_cached_peers); + if remove_ids.is_empty() { + return; + } + let mut map = self.map.write().await; + for id in remove_ids { + map.remove(&id); + } + } +} + +#[cfg(test)] +mod tests { + use super::{ + allow_ip_registration_attempt, pending_registration_limit_exceeded, + prune_ip_change_entries, select_peer_ids_to_evict, track_ip_change, IpBlockMap, + IpChangesMap, PeerEvictionEntry, DAY_SECONDS, IP_CHANGE_DUR_X2, + }; + use std::{ + collections::{HashMap, HashSet}, + time::{Duration, Instant}, + }; + + #[test] + fn pending_registration_limit_flags_excessive_per_ip_growth() { + assert!(!pending_registration_limit_exceeded(0, 64)); + assert!(!pending_registration_limit_exceeded(63, 64)); + assert!(pending_registration_limit_exceeded(64, 64)); + } + + #[test] + fn peer_cache_eviction_prefers_inactive_then_oldest_entries() { + let now = Instant::now(); + let old = now.checked_sub(Duration::from_secs(120)).unwrap_or(now); + let newer = now.checked_sub(Duration::from_secs(30)).unwrap_or(now); + let remove = select_peer_ids_to_evict( + vec![ + PeerEvictionEntry { + id: "inactive-old".to_owned(), + inactive: true, + last_reg_time: old, + }, + PeerEvictionEntry { + id: "active-old".to_owned(), + inactive: false, + last_reg_time: old, + }, + PeerEvictionEntry { + id: "inactive-new".to_owned(), + inactive: true, + last_reg_time: newer, + }, + ], + 2, + ); + assert_eq!( + remove, + vec!["inactive-old".to_owned(), "inactive-new".to_owned()] + ); + } + + #[test] + fn allow_ip_registration_attempt_prunes_and_caps_entries() { + std::env::set_var("MAX_IP_BLOCKER_ENTRIES", "2"); + let now = Instant::now(); + let stale = now + .checked_sub(Duration::from_secs(DAY_SECONDS + 5)) + .unwrap_or(now); + let older = now.checked_sub(Duration::from_secs(10)).unwrap_or(now); + let newer = now.checked_sub(Duration::from_secs(1)).unwrap_or(now); + let mut entries: IpBlockMap = HashMap::from([ + ( + "198.51.100.1".to_owned(), + ((0, stale), (HashSet::new(), stale)), + ), + ( + "198.51.100.2".to_owned(), + ((1, older), (HashSet::from(["peer-a".to_owned()]), older)), + ), + ( + "198.51.100.3".to_owned(), + ((1, newer), (HashSet::from(["peer-b".to_owned()]), newer)), + ), + ]); + assert!(allow_ip_registration_attempt( + &mut entries, + "198.51.100.4", + "peer-c" + )); + assert_eq!(entries.len(), 2); + assert!(!entries.contains_key("198.51.100.1")); + assert!(!entries.contains_key("198.51.100.2")); + assert!(entries.contains_key("198.51.100.3")); + assert!(entries.contains_key("198.51.100.4")); + std::env::remove_var("MAX_IP_BLOCKER_ENTRIES"); + } + + #[test] + fn track_ip_change_prunes_and_limits_unique_ips() { + std::env::set_var("MAX_IP_CHANGES_ENTRIES", "2"); + std::env::set_var("MAX_UNIQUE_IP_CHANGES_PER_ID", "2"); + let now = Instant::now(); + let stale = now + .checked_sub(Duration::from_secs(IP_CHANGE_DUR_X2 + 5)) + .unwrap_or(now); + let mut entries: IpChangesMap = HashMap::from([ + ( + "stale-id".to_owned(), + (stale, HashMap::from([("198.51.100.1".to_owned(), 1)])), + ), + ( + "peer-a".to_owned(), + ( + now, + HashMap::from([ + ("198.51.100.2".to_owned(), 1), + ("198.51.100.3".to_owned(), 1), + ]), + ), + ), + ( + "peer-b".to_owned(), + ( + now, + HashMap::from([ + ("198.51.100.4".to_owned(), 1), + ("198.51.100.5".to_owned(), 1), + ]), + ), + ), + ]); + prune_ip_change_entries(&mut entries); + assert_eq!(entries.len(), 2); + track_ip_change(&mut entries, "peer-a", "198.51.100.9"); + assert_eq!(entries["peer-a"].1.len(), 2); + track_ip_change(&mut entries, "peer-c", "198.51.100.6"); + assert_eq!(entries.len(), 2); + assert!(entries.contains_key("peer-a")); + assert!(entries.contains_key("peer-c")); + std::env::remove_var("MAX_IP_CHANGES_ENTRIES"); + std::env::remove_var("MAX_UNIQUE_IP_CHANGES_PER_ID"); + } + + #[test] + fn prune_ip_change_entries_keeps_recent_single_ip_entries() { + let now = Instant::now(); + let recent = now + .checked_sub(Duration::from_secs(IP_CHANGE_DUR_X2 / 2)) + .unwrap_or(now); + let stale = now + .checked_sub(Duration::from_secs(IP_CHANGE_DUR_X2 + 5)) + .unwrap_or(now); + let mut entries: IpChangesMap = HashMap::from([ + ( + "recent-single".to_owned(), + (recent, HashMap::from([("198.51.100.1".to_owned(), 1)])), + ), + ( + "stale-single".to_owned(), + (stale, HashMap::from([("198.51.100.2".to_owned(), 1)])), + ), + ]); + + prune_ip_change_entries(&mut entries); + + assert!(entries.contains_key("recent-single")); + assert!(!entries.contains_key("stale-single")); + } } diff --git a/src/relay_server.rs b/src/relay_server.rs index 0ec190ace..56ad61503 100644 --- a/src/relay_server.rs +++ b/src/relay_server.rs @@ -26,12 +26,36 @@ use std::{ io::Error, net::SocketAddr, sync::atomic::{AtomicUsize, Ordering}, + time::Instant, }; type Usage = (usize, usize, usize, usize); +struct PendingRelay { + stream: Box, + ip: String, + created_at: Instant, +} + +impl PendingRelay { + fn new(stream: Box, ip: String) -> Self { + Self { + stream, + ip, + created_at: Instant::now(), + } + } +} + +struct ActiveRelay { + peer_a_ip: String, + peer_b_ip: String, + started_at: Instant, +} + lazy_static::lazy_static! { - static ref PEERS: Mutex>> = Default::default(); + static ref PEERS: Mutex> = Default::default(); + static ref ACTIVE_RELAYS: Mutex> = Default::default(); static ref USAGE: RwLock> = Default::default(); static ref BLACKLIST: RwLock> = Default::default(); static ref BLOCKLIST: RwLock> = Default::default(); @@ -42,8 +66,15 @@ static DOWNGRADE_START_CHECK: AtomicUsize = AtomicUsize::new(1_800_000); // in m static LIMIT_SPEED: AtomicUsize = AtomicUsize::new(32 * 1024 * 1024); // in bit/s static TOTAL_BANDWIDTH: AtomicUsize = AtomicUsize::new(1024 * 1024 * 1024); // in bit/s static SINGLE_BANDWIDTH: AtomicUsize = AtomicUsize::new(128 * 1024 * 1024); // in bit/s +static MAX_PENDING_RELAYS: AtomicUsize = AtomicUsize::new(4096); +static MAX_PENDING_RELAYS_PER_IP: AtomicUsize = AtomicUsize::new(64); +static MAX_ACTIVE_RELAYS: AtomicUsize = AtomicUsize::new(2048); +static MAX_ACTIVE_RELAYS_PER_IP: AtomicUsize = AtomicUsize::new(128); +static RELAY_IDLE_TIMEOUT_SECS: AtomicUsize = AtomicUsize::new(30); +static MAX_RELAY_SESSION_SECS: AtomicUsize = AtomicUsize::new(14_400); const BLACKLIST_FILE: &str = "blacklist.txt"; const BLOCKLIST_FILE: &str = "blocklist.txt"; +const PENDING_RELAY_HOLD_SECS: u64 = 30; #[tokio::main(flavor = "multi_thread")] pub async fn start(port: &str, key: &str) -> ResultType<()> { @@ -146,7 +177,67 @@ fn check_params() { log::info!( "SINGLE_BANDWIDTH: {}Mb/s", SINGLE_BANDWIDTH.load(Ordering::SeqCst) as f64 / 1024. / 1024. - ) + ); + let tmp = std::env::var("MAX_PENDING_RELAYS") + .map(|x| x.parse::().unwrap_or(0)) + .unwrap_or(0); + if tmp > 0 { + MAX_PENDING_RELAYS.store(tmp, Ordering::SeqCst); + } + log::info!( + "MAX_PENDING_RELAYS: {}", + MAX_PENDING_RELAYS.load(Ordering::SeqCst) + ); + let tmp = std::env::var("MAX_PENDING_RELAYS_PER_IP") + .map(|x| x.parse::().unwrap_or(0)) + .unwrap_or(0); + if tmp > 0 { + MAX_PENDING_RELAYS_PER_IP.store(tmp, Ordering::SeqCst); + } + log::info!( + "MAX_PENDING_RELAYS_PER_IP: {}", + MAX_PENDING_RELAYS_PER_IP.load(Ordering::SeqCst) + ); + let tmp = std::env::var("MAX_ACTIVE_RELAYS") + .map(|x| x.parse::().unwrap_or(0)) + .unwrap_or(0); + if tmp > 0 { + MAX_ACTIVE_RELAYS.store(tmp, Ordering::SeqCst); + } + log::info!( + "MAX_ACTIVE_RELAYS: {}", + MAX_ACTIVE_RELAYS.load(Ordering::SeqCst) + ); + let tmp = std::env::var("MAX_ACTIVE_RELAYS_PER_IP") + .map(|x| x.parse::().unwrap_or(0)) + .unwrap_or(0); + if tmp > 0 { + MAX_ACTIVE_RELAYS_PER_IP.store(tmp, Ordering::SeqCst); + } + log::info!( + "MAX_ACTIVE_RELAYS_PER_IP: {}", + MAX_ACTIVE_RELAYS_PER_IP.load(Ordering::SeqCst) + ); + let tmp = std::env::var("RELAY_IDLE_TIMEOUT_SECS") + .map(|x| x.parse::().unwrap_or(0)) + .unwrap_or(0); + if tmp > 0 { + RELAY_IDLE_TIMEOUT_SECS.store(tmp, Ordering::SeqCst); + } + log::info!( + "RELAY_IDLE_TIMEOUT_SECS: {}", + RELAY_IDLE_TIMEOUT_SECS.load(Ordering::SeqCst) + ); + let tmp = std::env::var("MAX_RELAY_SESSION_SECS") + .map(|x| x.parse::().unwrap_or(0)) + .unwrap_or(0); + if tmp > 0 { + MAX_RELAY_SESSION_SECS.store(tmp, Ordering::SeqCst); + } + log::info!( + "MAX_RELAY_SESSION_SECS: {}", + MAX_RELAY_SESSION_SECS.load(Ordering::SeqCst) + ); } async fn check_cmd(cmd: &str, limiter: Limiter) -> String { @@ -157,7 +248,7 @@ async fn check_cmd(cmd: &str, limiter: Limiter) -> String { match fds.next() { Some("h") => { res = format!( - "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n", + "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n", "blacklist-add(ba) ", "blacklist-remove(br) ", "blacklist(b) ", @@ -169,6 +260,8 @@ async fn check_cmd(cmd: &str, limiter: Limiter) -> String { "limit-speed(ls) [value(Mb/s)]", "total-bandwidth(tb) [value(Mb/s)]", "single-bandwidth(sb) [value(Mb/s)]", + "active-relays(ar)", + "protection-stats(ps)", "usage(u)" ) } @@ -318,6 +411,48 @@ async fn check_cmd(cmd: &str, limiter: Limiter) -> String { ); } } + Some("active-relays" | "ar") => { + let active_relays = ACTIVE_RELAYS.lock().await; + let _ = writeln!(res, "{}", active_relays.len()); + for (uuid, relay) in active_relays.iter() { + let _ = writeln!( + res, + "{}: {} <-> {} ({}s)", + uuid, + relay.peer_a_ip, + relay.peer_b_ip, + relay.started_at.elapsed().as_secs() + ); + } + } + Some("protection-stats" | "ps") => { + for line in crate::common::protection_limits_summary() { + let _ = writeln!(res, "{line}"); + } + let _ = writeln!( + res, + "max_active_relays={}", + MAX_ACTIVE_RELAYS.load(Ordering::SeqCst) + ); + let _ = writeln!( + res, + "max_active_relays_per_ip={}", + MAX_ACTIVE_RELAYS_PER_IP.load(Ordering::SeqCst) + ); + let _ = writeln!( + res, + "relay_idle_timeout_secs={}", + RELAY_IDLE_TIMEOUT_SECS.load(Ordering::SeqCst) + ); + let _ = writeln!( + res, + "max_relay_session_secs={}", + MAX_RELAY_SESSION_SECS.load(Ordering::SeqCst) + ); + for (name, value) in crate::common::protection_stats_snapshot() { + let _ = writeln!(res, "{name}={value}"); + } + } _ => {} } res @@ -331,6 +466,10 @@ async fn io_loop(listener: TcpListener, listener2: TcpListener, key: &str) { res = listener.accept() => { match res { Ok((stream, addr)) => { + if !crate::common::allow_connection_from_ip("hbbr-tcp", addr) { + log::warn!("Rate limit exceeded for hbbr-tcp from {}", addr.ip()); + continue; + } stream.set_nodelay(true).ok(); handle_connection(stream, addr, &limiter, key, false).await; } @@ -343,6 +482,10 @@ async fn io_loop(listener: TcpListener, listener2: TcpListener, key: &str) { res = listener2.accept() => { match res { Ok((stream, addr)) => { + if !crate::common::allow_connection_from_ip("hbbr-ws", addr) { + log::warn!("Rate limit exceeded for hbbr-ws from {}", addr.ip()); + continue; + } stream.set_nodelay(true).ok(); handle_connection(stream, addr, &limiter, key, true).await; } @@ -398,23 +541,22 @@ async fn make_pair( ws: bool, ) -> ResultType<()> { if ws { + let peer_addr = addr; use tokio_tungstenite::tungstenite::handshake::server::{Request, Response}; let callback = |req: &Request, response: Response| { - let headers = req.headers(); - let real_ip = headers - .get("X-Real-IP") - .or_else(|| headers.get("X-Forwarded-For")) - .and_then(|header_value| header_value.to_str().ok()); - if let Some(ip) = real_ip { - if ip.contains('.') { - addr = format!("{ip}:0").parse().unwrap_or(addr); - } else { - addr = format!("[{ip}]:0").parse().unwrap_or(addr); - } - } + addr = crate::common::apply_trusted_proxy_addr(addr, req.headers()); Ok(response) }; let ws_stream = tokio_tungstenite::accept_hdr_async(stream, callback).await?; + if addr.ip() != peer_addr.ip() + && !crate::common::allow_connection_from_ip("hbbr-ws-forwarded", addr) + { + log::warn!( + "Rate limit exceeded for hbbr-ws-forwarded from {}", + addr.ip() + ); + return Ok(()); + } make_pair_(ws_stream, addr, key, limiter).await; } else { make_pair_(FramedStream::from(stream, addr), addr, key, limiter).await; @@ -429,30 +571,112 @@ async fn make_pair_(stream: impl StreamTrait, addr: SocketAddr, key: &str, limit if let Some(rendezvous_message::Union::RequestRelay(rf)) = msg_in.union { if !key.is_empty() && rf.licence_key != key { log::warn!("Relay authentication failed from {} - invalid key", addr); + send_relay_refuse(&mut stream, "Key mismatch").await; return; } if !rf.uuid.is_empty() { - let mut peer = PEERS.lock().await.remove(&rf.uuid); - if let Some(peer) = peer.as_mut() { + let mut pending = PEERS.lock().await.remove(&rf.uuid); + if let Some(pending) = pending.as_mut() { log::info!("Relayrequest {} from {} got paired", rf.uuid, addr); + let peer_a_ip = pending.ip.clone(); + let peer_b_ip = addr.ip().to_string(); + let mut active_relays = ACTIVE_RELAYS.lock().await; + if active_relays.contains_key(&rf.uuid) { + drop(active_relays); + crate::common::record_protection_event("relay_uuid_in_use"); + log::warn!( + "Rejecting relay request {} from {}: uuid already active", + rf.uuid, + addr + ); + send_relay_refuse(&mut stream, "uuid_in_use").await; + send_relay_refuse(&mut *pending.stream, "uuid_in_use").await; + return; + } + let active_for_peer_a = + count_active_relays_for_ip(&active_relays, &peer_a_ip); + let active_for_peer_b = + count_active_relays_for_ip(&active_relays, &peer_b_ip); + if let Some(reason) = active_relay_limit_reason( + active_relays.len(), + active_for_peer_a, + active_for_peer_b, + ) { + drop(active_relays); + crate::common::record_protection_event("active_relay_rejected"); + log::warn!( + "Rejecting active relay {} between {} and {}: {}", + rf.uuid, + peer_a_ip, + peer_b_ip, + reason + ); + send_relay_refuse(&mut stream, reason).await; + send_relay_refuse(&mut *pending.stream, reason).await; + return; + } + active_relays.insert( + rf.uuid.clone(), + ActiveRelay { + peer_a_ip, + peer_b_ip, + started_at: Instant::now(), + }, + ); + drop(active_relays); let id = format!("{}:{}", addr.ip(), addr.port()); USAGE.write().await.insert(id.clone(), Default::default()); + let peer = &mut pending.stream; if !stream.is_ws() && !peer.is_ws() { peer.set_raw(); stream.set_raw(); log::info!("Both are raw"); } - if let Err(err) = relay(addr, &mut stream, peer, limiter, id.clone()).await + if let Err(err) = + relay(&rf.uuid, addr, &mut stream, peer, limiter, id.clone()).await { log::info!("Relay of {} closed: {}", addr, err); } else { log::info!("Relay of {} closed", addr); } + ACTIVE_RELAYS.lock().await.remove(&rf.uuid); USAGE.write().await.remove(&id); } else { log::info!("New relay request {} from {}", rf.uuid, addr); - PEERS.lock().await.insert(rf.uuid.clone(), Box::new(stream)); - sleep(30.).await; + let ip = addr.ip().to_string(); + let active_relays = ACTIVE_RELAYS.lock().await; + if active_relays.contains_key(&rf.uuid) { + drop(active_relays); + crate::common::record_protection_event("relay_uuid_in_use"); + log::warn!( + "Rejecting pending relay request {} from {}: uuid already active", + rf.uuid, + addr + ); + send_relay_refuse(&mut stream, "uuid_in_use").await; + return; + } + drop(active_relays); + let mut peers = PEERS.lock().await; + prune_expired_pending_relays(&mut peers); + let pending_for_ip = peers.values().filter(|peer| peer.ip == ip).count(); + if let Some(reason) = + pending_relay_limit_reason(peers.len(), pending_for_ip) + { + crate::common::record_protection_event("relay_pending_rejected"); + log::warn!( + "Rejecting relay request {} from {}: {}", + rf.uuid, + addr, + reason + ); + drop(peers); + send_relay_refuse(&mut stream, reason).await; + return; + } + peers.insert(rf.uuid.clone(), PendingRelay::new(Box::new(stream), ip)); + drop(peers); + sleep(PENDING_RELAY_HOLD_SECS as f32).await; PEERS.lock().await.remove(&rf.uuid); } } @@ -461,7 +685,56 @@ async fn make_pair_(stream: impl StreamTrait, addr: SocketAddr, key: &str, limit } } +fn prune_expired_pending_relays(peers: &mut HashMap) { + peers.retain(|_, pending| pending.created_at.elapsed().as_secs() < PENDING_RELAY_HOLD_SECS); +} + +fn pending_relay_limit_reason(total_pending: usize, pending_for_ip: usize) -> Option<&'static str> { + if total_pending >= MAX_PENDING_RELAYS.load(Ordering::SeqCst) { + return Some("Relay server busy"); + } + if pending_for_ip >= MAX_PENDING_RELAYS_PER_IP.load(Ordering::SeqCst) { + return Some("Too many pending relay requests"); + } + None +} + +fn count_active_relays_for_ip(active_relays: &HashMap, ip: &str) -> usize { + active_relays + .values() + .filter(|relay| relay.peer_a_ip == ip || relay.peer_b_ip == ip) + .count() +} + +fn active_relay_limit_reason( + total_active: usize, + active_for_peer_a: usize, + active_for_peer_b: usize, +) -> Option<&'static str> { + if total_active >= MAX_ACTIVE_RELAYS.load(Ordering::SeqCst) { + return Some("Relay server busy"); + } + if active_for_peer_a >= MAX_ACTIVE_RELAYS_PER_IP.load(Ordering::SeqCst) + || active_for_peer_b >= MAX_ACTIVE_RELAYS_PER_IP.load(Ordering::SeqCst) + { + return Some("Too many active relay sessions"); + } + None +} + +async fn send_relay_refuse(stream: &mut dyn StreamTrait, reason: &str) { + let mut msg_out = RendezvousMessage::new(); + msg_out.set_relay_response(RelayResponse { + refuse_reason: reason.to_owned(), + ..Default::default() + }); + if let Ok(bytes) = msg_out.write_to_bytes() { + let _ = stream.send_raw(bytes.into()).await; + } +} + async fn relay( + relay_uuid: &str, addr: SocketAddr, stream: &mut impl StreamTrait, peer: &mut Box, @@ -483,6 +756,7 @@ async fn relay( (sb * DOWNGRADE_THRESHOLD_100.load(Ordering::SeqCst) as f64 / 100. / 1000.) as usize; // in bit/ms let mut timer = interval(Duration::from_secs(3)); let mut last_recv_time = std::time::Instant::now(); + let session_started_at = std::time::Instant::now(); loop { tokio::select! { res = peer.recv() => { @@ -524,9 +798,18 @@ async fn relay( } }, _ = timer.tick() => { - if last_recv_time.elapsed().as_secs() > 30 { + if last_recv_time.elapsed().as_secs() + > RELAY_IDLE_TIMEOUT_SECS.load(Ordering::SeqCst) as u64 + { bail!("Timeout"); } + let max_session_secs = MAX_RELAY_SESSION_SECS.load(Ordering::SeqCst); + if max_session_secs > 0 + && session_started_at.elapsed().as_secs() > max_session_secs as u64 + { + crate::common::record_protection_event("relay_session_duration_limit_hits"); + bail!("Relay session duration limit reached"); + } } } @@ -554,7 +837,8 @@ async fn relay( { downgrade = true; log::info!( - "Downgrade {}, exceed downgrade threshold {}bit/ms in {}ms", + "Downgrade relay {} ({}), exceed downgrade threshold {}bit/ms in {}ms", + relay_uuid, id, downgrade_threshold, elapsed @@ -645,3 +929,79 @@ impl StreamTrait for tokio_tungstenite::WebSocketStream { fn set_raw(&mut self) {} } + +#[cfg(test)] +mod tests { + use super::{ + active_relay_limit_reason, pending_relay_limit_reason, ACTIVE_RELAYS, MAX_ACTIVE_RELAYS, + MAX_ACTIVE_RELAYS_PER_IP, MAX_PENDING_RELAYS, MAX_PENDING_RELAYS_PER_IP, + }; + use std::sync::{atomic::Ordering, Mutex}; + use std::time::Instant; + + static TEST_RELAY_LIMITS_LOCK: Mutex<()> = Mutex::new(()); + + #[test] + fn pending_relay_limits_enforce_global_and_per_ip_caps() { + let _guard = TEST_RELAY_LIMITS_LOCK.lock().unwrap(); + let saved_total = MAX_PENDING_RELAYS.load(Ordering::SeqCst); + let saved_per_ip = MAX_PENDING_RELAYS_PER_IP.load(Ordering::SeqCst); + + MAX_PENDING_RELAYS.store(2, Ordering::SeqCst); + MAX_PENDING_RELAYS_PER_IP.store(1, Ordering::SeqCst); + + assert_eq!(pending_relay_limit_reason(0, 0), None); + assert_eq!( + pending_relay_limit_reason(1, 1), + Some("Too many pending relay requests") + ); + assert_eq!(pending_relay_limit_reason(2, 0), Some("Relay server busy")); + + MAX_PENDING_RELAYS.store(saved_total, Ordering::SeqCst); + MAX_PENDING_RELAYS_PER_IP.store(saved_per_ip, Ordering::SeqCst); + } + + #[test] + fn active_relay_limits_enforce_global_and_per_ip_caps() { + let _guard = TEST_RELAY_LIMITS_LOCK.lock().unwrap(); + let saved_total = MAX_ACTIVE_RELAYS.load(Ordering::SeqCst); + let saved_per_ip = MAX_ACTIVE_RELAYS_PER_IP.load(Ordering::SeqCst); + + MAX_ACTIVE_RELAYS.store(2, Ordering::SeqCst); + MAX_ACTIVE_RELAYS_PER_IP.store(1, Ordering::SeqCst); + + assert_eq!(active_relay_limit_reason(0, 0, 0), None); + assert_eq!( + active_relay_limit_reason(1, 1, 0), + Some("Too many active relay sessions") + ); + assert_eq!( + active_relay_limit_reason(1, 0, 1), + Some("Too many active relay sessions") + ); + assert_eq!( + active_relay_limit_reason(2, 0, 0), + Some("Relay server busy") + ); + + MAX_ACTIVE_RELAYS.store(saved_total, Ordering::SeqCst); + MAX_ACTIVE_RELAYS_PER_IP.store(saved_per_ip, Ordering::SeqCst); + } + + #[tokio::test(flavor = "current_thread")] + async fn active_relay_uuid_reuse_is_detected() { + let _guard = TEST_RELAY_LIMITS_LOCK.lock().unwrap(); + let mut active_relays = ACTIVE_RELAYS.lock().await; + active_relays.clear(); + active_relays.insert( + "uuid-1".to_owned(), + super::ActiveRelay { + peer_a_ip: "198.51.100.1".to_owned(), + peer_b_ip: "198.51.100.2".to_owned(), + started_at: Instant::now(), + }, + ); + assert!(active_relays.contains_key("uuid-1")); + active_relays.clear(); + } +} diff --git a/src/rendezvous_server.rs b/src/rendezvous_server.rs index ff68441b6..a8bedc2c6 100644 --- a/src/rendezvous_server.rs +++ b/src/rendezvous_server.rs @@ -13,7 +13,9 @@ use hbb_common::{ log, protobuf::{Message as _, MessageField}, rendezvous_proto::{ - register_pk_response::Result::{TOO_FREQUENT, UUID_MISMATCH}, + register_pk_response::Result::{ + INVALID_ID_FORMAT, LICENSE_MISMATCH, TOO_FREQUENT, UUID_MISMATCH, + }, *, }, tcp::{listen_any, FramedStream}, @@ -33,7 +35,7 @@ use hbb_common::{ use ipnetwork::Ipv4Network; use sodiumoxide::crypto::sign; use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, sync::atomic::{AtomicBool, AtomicUsize, Ordering}, sync::Arc, @@ -48,12 +50,32 @@ enum Data { } const REG_TIMEOUT: i32 = 30_000; +const MIN_REGISTRATION_ID_LEN: usize = 6; +const MAX_REGISTRATION_ID_LEN: usize = 100; +const MAX_ONLINE_REQUEST_PEERS_ENV: &str = "MAX_ONLINE_REQUEST_PEERS"; +const FANOUT_WINDOW_SECONDS_ENV: &str = "FANOUT_WINDOW_SECONDS"; +const MAX_FANOUT_TRACKED_SOURCES_ENV: &str = "MAX_FANOUT_TRACKED_SOURCES"; +const MAX_PUNCH_TARGETS_PER_IP_PER_WINDOW_ENV: &str = "MAX_PUNCH_TARGETS_PER_IP_PER_WINDOW"; +const MAX_RELAY_TARGETS_PER_IP_PER_WINDOW_ENV: &str = "MAX_RELAY_TARGETS_PER_IP_PER_WINDOW"; +const TCP_PUNCH_ENTRY_TTL_SECS_ENV: &str = "TCP_PUNCH_ENTRY_TTL_SECS"; +const MAX_TCP_PUNCH_ENTRIES_ENV: &str = "MAX_TCP_PUNCH_ENTRIES"; +const DEFAULT_MAX_ONLINE_REQUEST_PEERS: usize = 4_096; +const DEFAULT_FANOUT_WINDOW_SECONDS: usize = 60; +const DEFAULT_MAX_FANOUT_TRACKED_SOURCES: usize = 8_192; +const DEFAULT_MAX_PUNCH_TARGETS_PER_IP_PER_WINDOW: usize = 256; +const DEFAULT_MAX_RELAY_TARGETS_PER_IP_PER_WINDOW: usize = 256; +const DEFAULT_TCP_PUNCH_ENTRY_TTL_SECS: usize = 30; +const DEFAULT_MAX_TCP_PUNCH_ENTRIES: usize = 4_096; type TcpStreamSink = SplitSink, Bytes>; type WsSink = SplitSink, tungstenite::Message>; enum Sink { TcpStream(TcpStreamSink), Ws(WsSink), } +struct TcpPunchEntry { + sink: Sink, + created_at: Instant, +} type Sender = mpsc::UnboundedSender; type Receiver = mpsc::UnboundedReceiver; static ROTATION_RELAY_SERVER: AtomicUsize = AtomicUsize::new(0); @@ -65,9 +87,26 @@ static ALWAYS_USE_RELAY: AtomicBool = AtomicBool::new(false); use once_cell::sync::Lazy; use tokio::sync::Mutex as TokioMutex; // differentiate if needed #[derive(Clone)] -struct PunchReqEntry { tm: Instant, from_ip: String, to_ip: String, to_id: String } +struct PunchReqEntry { + tm: Instant, + from_ip: String, + to_ip: String, + to_id: String, +} static PUNCH_REQS: Lazy>> = Lazy::new(|| TokioMutex::new(Vec::new())); const PUNCH_REQ_DEDUPE_SEC: u64 = 60; +const PUNCH_REQ_RETENTION_SECS: u64 = 600; +const MAX_PUNCH_REQS: usize = 8192; + +struct FanoutEntry { + window_started_at: Instant, + last_seen_at: Instant, + targets: HashSet, +} + +type FanoutMap = HashMap; +static PUNCH_FANOUT: Lazy> = Lazy::new(|| TokioMutex::new(HashMap::new())); +static RELAY_FANOUT: Lazy> = Lazy::new(|| TokioMutex::new(HashMap::new())); #[derive(Clone)] struct Inner { @@ -81,7 +120,7 @@ struct Inner { #[derive(Clone)] pub struct RendezvousServer { - tcp_punch: Arc>>, + tcp_punch: Arc>>, pm: PeerMap, tx: Sender, relay_servers: Arc, @@ -172,12 +211,13 @@ impl RendezvousServer { } else { test_addr.parse()? }; + let test_key = key.to_owned(); tokio::spawn(async move { - if let Err(err) = test_hbbs(test_addr).await { + if let Err(err) = test_hbbs(test_addr, test_key.clone()).await { if test_addr.is_ipv6() && test_addr.ip().is_unspecified() { let mut test_addr = test_addr; test_addr.set_ip(IpAddr::V4(Ipv4Addr::UNSPECIFIED)); - if let Err(err) = test_hbbs(test_addr).await { + if let Err(err) = test_hbbs(test_addr, test_key).await { log::error!("Failed to run hbbs test with {test_addr}: {err}"); std::process::exit(1); } @@ -259,7 +299,12 @@ impl RendezvousServer { res = socket.next() => { match res { Some(Ok((bytes, addr))) => { - if let Err(err) = self.handle_udp(&bytes, addr.into(), socket, key).await { + let addr: SocketAddr = addr.into(); + if !crate::common::allow_udp_packet_from_ip("hbbs-udp", addr) { + log::warn!("Rate limit exceeded for hbbs-udp from {}", addr.ip()); + continue; + } + if let Err(err) = self.handle_udp(&bytes, addr, socket, key).await { log::error!("udp failure: {}", err); return LoopFailure::UdpSocket; } @@ -276,8 +321,12 @@ impl RendezvousServer { res = listener2.accept() => { match res { Ok((stream, addr)) => { + if !crate::common::allow_connection_from_ip("hbbs-nat", addr) { + log::warn!("Rate limit exceeded for hbbs-nat from {}", addr.ip()); + continue; + } stream.set_nodelay(true).ok(); - self.handle_listener2(stream, addr).await; + self.handle_listener2(stream, addr, key).await; } Err(err) => { log::error!("listener2.accept failed: {}", err); @@ -288,6 +337,10 @@ impl RendezvousServer { res = listener3.accept() => { match res { Ok((stream, addr)) => { + if !crate::common::allow_connection_from_ip("hbbs-ws", addr) { + log::warn!("Rate limit exceeded for hbbs-ws from {}", addr.ip()); + continue; + } stream.set_nodelay(true).ok(); self.handle_listener(stream, addr, key, true).await; } @@ -300,6 +353,10 @@ impl RendezvousServer { res = listener.accept() => { match res { Ok((stream, addr)) => { + if !crate::common::allow_connection_from_ip("hbbs-main", addr) { + log::warn!("Rate limit exceeded for hbbs-main from {}", addr.ip()); + continue; + } stream.set_nodelay(true).ok(); self.handle_listener(stream, addr, key, false).await; } @@ -326,6 +383,27 @@ impl RendezvousServer { Some(rendezvous_message::Union::RegisterPeer(rp)) => { // B registered if !rp.id.is_empty() { + if !is_valid_server_key(key, &rp.licence_key) { + log::warn!( + "Authentication failed from {} for peer {} - invalid key", + addr, + rp.id + ); + return Ok(()); + } + if !is_valid_registration_id(&rp.id) { + log::warn!("Invalid peer registration id from {}: {:?}", addr, rp.id); + return Ok(()); + } + let ip = addr.ip().to_string(); + if !self.check_ip_blocker(&ip, &rp.id).await { + log::warn!( + "Peer registration rate-limited from {} for id {}", + addr, + rp.id + ); + return Ok(()); + } log::trace!("New peer registered: {:?} {:?}", &rp.id, &addr); self.update_addr(rp.id, addr, socket).await?; if self.inner.serial > rp.serial { @@ -343,14 +421,29 @@ impl RendezvousServer { if rk.uuid.is_empty() || rk.pk.is_empty() { return Ok(()); } + if !is_valid_server_key(key, &rk.licence_key) { + log::warn!( + "Authentication failed from {} for peer {} - invalid key", + addr, + rk.id + ); + return send_rk_res(socket, addr, LICENSE_MISMATCH).await; + } let id = rk.id; let ip = addr.ip().to_string(); - if id.len() < 6 { - return send_rk_res(socket, addr, UUID_MISMATCH).await; + if !is_valid_registration_id(&id) { + return send_rk_res(socket, addr, INVALID_ID_FORMAT).await; } else if !self.check_ip_blocker(&ip, &id).await { return send_rk_res(socket, addr, TOO_FREQUENT).await; } - let peer = self.pm.get_or(&id).await; + let Some(peer) = self.pm.get_or_for_registration(&id, &ip).await else { + log::warn!( + "Pending registration cache limit reached from {} for id {}", + addr, + id + ); + return send_rk_res(socket, addr, TOO_FREQUENT).await; + }; let (changed, ip_changed) = { let peer = peer.read().await; if peer.uuid.is_empty() { @@ -397,29 +490,16 @@ impl RendezvousServer { peer.write().await.reg_pk = req_pk; if ip_changed { let mut lock = IP_CHANGES.lock().await; - if let Some((tm, ips)) = lock.get_mut(&id) { - if tm.elapsed().as_secs() > IP_CHANGE_DUR { - *tm = Instant::now(); - ips.clear(); - ips.insert(ip.clone(), 1); - } else if let Some(v) = ips.get_mut(&ip) { - *v += 1; - } else { - ips.insert(ip.clone(), 1); - } - } else { - lock.insert( - id.clone(), - (Instant::now(), HashMap::from([(ip.clone(), 1)])), - ); - } - } - if changed { - self.pm.update_pk(id, peer, addr, rk.uuid, rk.pk, ip).await; + track_ip_change(&mut lock, &id, &ip); } + let result = if changed { + self.pm.update_pk(id, peer, addr, rk.uuid, rk.pk, ip).await + } else { + register_pk_response::Result::OK + }; let mut msg_out = RendezvousMessage::new(); msg_out.set_register_pk_response(RegisterPkResponse { - result: register_pk_response::Result::OK.into(), + result: result.into(), ..Default::default() }); socket.send(&msg_out, addr).await? @@ -489,19 +569,96 @@ impl RendezvousServer { ws: bool, ) -> bool { if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(bytes) { + let should_rate_limit = matches!( + msg_in.union.as_ref(), + Some(rendezvous_message::Union::PunchHoleRequest(_)) + | Some(rendezvous_message::Union::RequestRelay(_)) + | Some(rendezvous_message::Union::RelayResponse(_)) + | Some(rendezvous_message::Union::PunchHoleSent(_)) + | Some(rendezvous_message::Union::LocalAddr(_)) + | Some(rendezvous_message::Union::TestNatRequest(_)) + ); + if should_rate_limit + && !crate::common::allow_control_message_from_ip("hbbs-control", addr) + { + log::warn!("Control message rate limit exceeded from {}", addr.ip()); + return false; + } match msg_in.union { Some(rendezvous_message::Union::PunchHoleRequest(ph)) => { // there maybe several attempt, so sink can be none if let Some(sink) = sink.take() { - self.tcp_punch.lock().await.insert(try_into_v4(addr), sink); + let rejected_sink = { + let mut lock = self.tcp_punch.lock().await; + insert_tcp_punch_entry(&mut lock, try_into_v4(addr), sink) + }; + if let Some(rejected_sink) = rejected_sink { + crate::common::record_protection_event("tcp_punch_entry_limit_hits"); + log::warn!("tcp_punch entry limit exceeded for {}", addr); + let mut msg_out = RendezvousMessage::new(); + msg_out.set_punch_hole_response(PunchHoleResponse { + other_failure: "Too many pending TCP punch sessions".to_owned(), + ..Default::default() + }); + Self::send_to_sink(&mut Some(rejected_sink), msg_out).await; + return false; + } } allow_err!(self.handle_tcp_punch_hole_request(addr, ph, key, ws).await); return true; } Some(rendezvous_message::Union::RequestRelay(mut rf)) => { + let source_ip = try_into_v4(addr).ip().to_string(); + let target_id = rf.id.clone(); + let allowed = { + let mut lock = RELAY_FANOUT.lock().await; + allow_target_fanout( + &mut lock, + &source_ip, + &target_id, + max_relay_targets_per_ip_per_window(), + "relay_fanout_entries_evicted", + "relay_fanout_entries_rejected", + ) + }; + if !allowed { + crate::common::record_protection_event("relay_target_fanout_limit_hits"); + log::warn!( + "Relay target fan-out limit exceeded from {} toward {}", + addr, + target_id + ); + let mut msg_out = RendezvousMessage::new(); + msg_out.set_relay_response(RelayResponse { + uuid: rf.uuid.clone(), + refuse_reason: "Too many distinct relay targets".to_owned(), + ..Default::default() + }); + if sink.is_some() { + Self::send_to_sink(sink, msg_out).await; + } else { + allow_err!(self.send_to_tcp_sync(msg_out, addr).await); + } + return true; + } // there maybe several attempt, so sink can be none if let Some(sink) = sink.take() { - self.tcp_punch.lock().await.insert(try_into_v4(addr), sink); + let rejected_sink = { + let mut lock = self.tcp_punch.lock().await; + insert_tcp_punch_entry(&mut lock, try_into_v4(addr), sink) + }; + if let Some(rejected_sink) = rejected_sink { + crate::common::record_protection_event("tcp_punch_entry_limit_hits"); + log::warn!("tcp_punch entry limit exceeded for {}", addr); + let mut msg_out = RendezvousMessage::new(); + msg_out.set_relay_response(RelayResponse { + uuid: rf.uuid.clone(), + refuse_reason: "Too many pending TCP punch sessions".to_owned(), + ..Default::default() + }); + Self::send_to_sink(&mut Some(rejected_sink), msg_out).await; + return true; + } } if let Some(peer) = self.pm.get_in_memory(&rf.id).await { let mut msg_out = RendezvousMessage::new(); @@ -539,6 +696,10 @@ impl RendezvousServer { allow_err!(self.handle_local_addr(la, addr, None).await); } Some(rendezvous_message::Union::TestNatRequest(tar)) => { + if !is_valid_server_key(key, &tar.licence_key) { + log::warn!("Authentication failed from {} for nat probe", addr); + return true; + } let mut msg_out = RendezvousMessage::new(); let mut res = TestNatResponse { port: addr.port() as _, @@ -688,7 +849,11 @@ impl RendezvousServer { ) -> ResultType<(RendezvousMessage, Option)> { let mut ph = ph; if !key.is_empty() && ph.licence_key != key { - log::warn!("Authentication failed from {} for peer {} - invalid key", addr, ph.id); + log::warn!( + "Authentication failed from {} for peer {} - invalid key", + addr, + ph.id + ); let mut msg_out = RendezvousMessage::new(); msg_out.set_punch_hole_response(PunchHoleResponse { failure: punch_hole_response::Failure::LICENSE_MISMATCH.into(), @@ -697,6 +862,32 @@ impl RendezvousServer { return Ok((msg_out, None)); } let id = ph.id; + let source_ip = try_into_v4(addr).ip().to_string(); + let allowed = { + let mut lock = PUNCH_FANOUT.lock().await; + allow_target_fanout( + &mut lock, + &source_ip, + &id, + max_punch_targets_per_ip_per_window(), + "punch_fanout_entries_evicted", + "punch_fanout_entries_rejected", + ) + }; + if !allowed { + crate::common::record_protection_event("punch_target_fanout_limit_hits"); + log::warn!( + "Punch target fan-out limit exceeded from {} toward {}", + addr, + id + ); + let mut msg_out = RendezvousMessage::new(); + msg_out.set_punch_hole_response(PunchHoleResponse { + other_failure: "Too many distinct punch targets".to_owned(), + ..Default::default() + }); + return Ok((msg_out, None)); + } // punch hole request from A, relay to B, // check if in same intranet first, // fetch local addrs if in same intranet. @@ -715,21 +906,33 @@ impl RendezvousServer { }); return Ok((msg_out, None)); } - + // record punch hole request (from addr -> peer id/peer_addr) { let from_ip = try_into_v4(addr).ip().to_string(); let to_ip = try_into_v4(peer_addr).ip().to_string(); let to_id_clone = id.clone(); let mut lock = PUNCH_REQS.lock().await; + prune_punch_requests(&mut lock); let mut dup = false; - for e in lock.iter().rev().take(30) { // only check recent tail subset for speed + for e in lock.iter().rev().take(30) { + // only check recent tail subset for speed if e.from_ip == from_ip && e.to_id == to_id_clone { - if e.tm.elapsed().as_secs() < PUNCH_REQ_DEDUPE_SEC { dup = true; } + if e.tm.elapsed().as_secs() < PUNCH_REQ_DEDUPE_SEC { + dup = true; + } break; } } - if !dup { lock.push(PunchReqEntry { tm: Instant::now(), from_ip, to_ip, to_id: to_id_clone }); } + if !dup { + lock.push(PunchReqEntry { + tm: Instant::now(), + from_ip, + to_ip, + to_id: to_id_clone, + }); + prune_punch_requests(&mut lock); + } } let mut msg_out = RendezvousMessage::new(); @@ -795,8 +998,17 @@ impl RendezvousServer { stream: &mut FramedStream, peers: Vec, ) -> ResultType<()> { + let peer_lookup_limit = clamped_online_request_peer_count(peers.len()); + if peer_lookup_limit < peers.len() { + crate::common::record_protection_event("online_request_peer_limit_hits"); + log::warn!( + "Capping online request lookup from {} to {} peers", + peers.len(), + peer_lookup_limit + ); + } let mut states = BytesMut::zeroed((peers.len() + 7) / 8); - for (i, peer_id) in peers.iter().enumerate() { + for (i, peer_id) in peers.iter().take(peer_lookup_limit).enumerate() { if let Some(peer) = self.pm.get_in_memory(peer_id).await { let elapsed = peer.read().await.last_reg_time.elapsed().as_millis() as i32; // bytes index from left to right @@ -820,7 +1032,11 @@ impl RendezvousServer { #[inline] async fn send_to_tcp(&mut self, msg: RendezvousMessage, addr: SocketAddr) { - let mut tcp = self.tcp_punch.lock().await.remove(&try_into_v4(addr)); + let mut tcp = { + let mut lock = self.tcp_punch.lock().await; + prune_tcp_punch_entries(&mut lock); + lock.remove(&try_into_v4(addr)).map(|entry| entry.sink) + }; tokio::spawn(async move { Self::send_to_sink(&mut tcp, msg).await; }); @@ -848,7 +1064,11 @@ impl RendezvousServer { msg: RendezvousMessage, addr: SocketAddr, ) -> ResultType<()> { - let mut sink = self.tcp_punch.lock().await.remove(&try_into_v4(addr)); + let mut sink = { + let mut lock = self.tcp_punch.lock().await; + prune_tcp_punch_entries(&mut lock); + lock.remove(&try_into_v4(addr)).map(|entry| entry.sink) + }; Self::send_to_sink(&mut sink, msg).await; Ok(()) } @@ -890,32 +1110,7 @@ impl RendezvousServer { async fn check_ip_blocker(&self, ip: &str, id: &str) -> bool { let mut lock = IP_BLOCKER.lock().await; - let now = Instant::now(); - if let Some(old) = lock.get_mut(ip) { - let counter = &mut old.0; - if counter.1.elapsed().as_secs() > IP_BLOCK_DUR { - counter.0 = 0; - } else if counter.0 > 30 { - return false; - } - counter.0 += 1; - counter.1 = now; - - let counter = &mut old.1; - let is_new = counter.0.get(id).is_none(); - if counter.1.elapsed().as_secs() > DAY_SECONDS { - counter.0.clear(); - } else if counter.0.len() > 300 { - return !is_new; - } - if is_new { - counter.0.insert(id.to_owned()); - } - counter.1 = now; - } else { - lock.insert(ip.to_owned(), ((0, now), (Default::default(), now))); - } - true + allow_ip_registration_attempt(&mut lock, ip, id) } fn parse_relay_servers(&mut self, relay_servers: &str) { @@ -942,13 +1137,14 @@ impl RendezvousServer { match fds.next() { Some("h") => { res = format!( - "{}\n{}\n{}\n{}\n{}\n{}\n{}\n", + "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n", "relay-servers(rs) ", "reload-geo(rg)", "ip-blocker(ib) [|] [-]", "ip-changes(ic) [|] [-]", "punch-requests(pr) [] [-]", "always-use-relay(aur)", + "protection-stats(ps)", "test-geo(tg) " ) } @@ -963,10 +1159,7 @@ impl RendezvousServer { } Some("ip-blocker" | "ib") => { let mut lock = IP_BLOCKER.lock().await; - lock.retain(|&_, (a, b)| { - a.1.elapsed().as_secs() <= IP_BLOCK_DUR - || b.1.elapsed().as_secs() <= DAY_SECONDS - }); + prune_ip_blocker_entries(&mut lock); res = format!("{}\n", lock.len()); let ip = fds.next(); let mut start = ip.map(|x| x.parse::().unwrap_or(-1)).unwrap_or(-1); @@ -1015,7 +1208,7 @@ impl RendezvousServer { } Some("ip-changes" | "ic") => { let mut lock = IP_CHANGES.lock().await; - lock.retain(|&_, v| v.0.elapsed().as_secs() < IP_CHANGE_DUR_X2 && v.1.len() > 1); + prune_ip_change_entries(&mut lock); res = format!("{}\n", lock.len()); let id = fds.next(); let mut start = id.map(|x| x.parse::().unwrap_or(-1)).unwrap_or(-1); @@ -1051,17 +1244,28 @@ impl RendezvousServer { use std::fmt::Write as _; let mut lock = PUNCH_REQS.lock().await; let arg = fds.next(); - if let Some("-") = arg { lock.clear(); } - else { - let mut start = arg.and_then(|x| x.parse::().ok()).unwrap_or(0); - let mut page_size = fds.next().and_then(|x| x.parse::().ok()).unwrap_or(10); - if page_size == 0 { page_size = 10; } + if let Some("-") = arg { + lock.clear(); + } else { + prune_punch_requests(&mut lock); + let start = arg.and_then(|x| x.parse::().ok()).unwrap_or(0); + let mut page_size = fds + .next() + .and_then(|x| x.parse::().ok()) + .unwrap_or(10); + if page_size == 0 { + page_size = 10; + } for (_, e) in lock.iter().enumerate().skip(start).take(page_size) { let age = e.tm.elapsed(); let event_system = std::time::SystemTime::now() - age; let event_iso = chrono::DateTime::::from(event_system) .to_rfc3339_opts(chrono::SecondsFormat::Secs, true); - let _ = writeln!(res, "{} {} -> {}@{}", event_iso, e.from_ip, e.to_id, e.to_ip); + let _ = writeln!( + res, + "{} {} -> {}@{}", + event_iso, e.from_ip, e.to_id, e.to_ip + ); } } } @@ -1081,6 +1285,47 @@ impl RendezvousServer { ); } } + Some("protection-stats" | "ps") => { + for line in crate::common::protection_limits_summary() { + let _ = writeln!(res, "{line}"); + } + let tcp_punch_entries = { + let mut lock = self.tcp_punch.lock().await; + prune_tcp_punch_entries(&mut lock); + lock.len() + }; + let _ = writeln!( + res, + "tcp_punch_entry_ttl_secs={}", + tcp_punch_entry_ttl_secs() + ); + let _ = writeln!(res, "max_tcp_punch_entries={}", max_tcp_punch_entries()); + let _ = writeln!(res, "tcp_punch_entries={tcp_punch_entries}"); + let _ = writeln!(res, "fanout_window_seconds={}", fanout_window_seconds()); + let _ = writeln!( + res, + "max_fanout_tracked_sources={}", + max_fanout_tracked_sources() + ); + let _ = writeln!( + res, + "max_punch_targets_per_ip_per_window={}", + max_punch_targets_per_ip_per_window() + ); + let _ = writeln!( + res, + "max_relay_targets_per_ip_per_window={}", + max_relay_targets_per_ip_per_window() + ); + let _ = writeln!( + res, + "max_online_request_peers={}", + max_online_request_peers() + ); + for (name, value) in crate::common::protection_stats_snapshot() { + let _ = writeln!(res, "{name}={value}"); + } + } Some("test-geo" | "tg") => { if let Some(rs) = fds.next() { if let Ok(a) = rs.parse::() { @@ -1099,8 +1344,9 @@ impl RendezvousServer { res } - async fn handle_listener2(&self, stream: TcpStream, addr: SocketAddr) { + async fn handle_listener2(&self, stream: TcpStream, addr: SocketAddr, key: &str) { let mut rs = self.clone(); + let key = key.to_owned(); let ip = try_into_v4(addr).ip(); if ip.is_loopback() { tokio::spawn(async move { @@ -1121,7 +1367,11 @@ impl RendezvousServer { if let Some(Ok(bytes)) = stream.next_timeout(30_000).await { if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) { match msg_in.union { - Some(rendezvous_message::Union::TestNatRequest(_)) => { + Some(rendezvous_message::Union::TestNatRequest(tar)) => { + if !is_valid_server_key(&key, &tar.licence_key) { + log::warn!("Authentication failed from {} for nat probe", addr); + return; + } let mut msg_out = RendezvousMessage::new(); msg_out.set_test_nat_response(TestNatResponse { port: addr.port() as _, @@ -1130,6 +1380,10 @@ impl RendezvousServer { stream.send(&msg_out).await.ok(); } Some(rendezvous_message::Union::OnlineRequest(or)) => { + if !is_valid_server_key(&key, &or.licence_key) { + log::warn!("Authentication failed from {} for online query", addr); + return; + } allow_err!(rs.handle_online_request(&mut stream, or.peers).await); } _ => {} @@ -1158,23 +1412,22 @@ impl RendezvousServer { ) -> ResultType<()> { let mut sink; if ws { + let peer_addr = addr; use tokio_tungstenite::tungstenite::handshake::server::{Request, Response}; let callback = |req: &Request, response: Response| { - let headers = req.headers(); - let real_ip = headers - .get("X-Real-IP") - .or_else(|| headers.get("X-Forwarded-For")) - .and_then(|header_value| header_value.to_str().ok()); - if let Some(ip) = real_ip { - if ip.contains('.') { - addr = format!("{ip}:0").parse().unwrap_or(addr); - } else { - addr = format!("[{ip}]:0").parse().unwrap_or(addr); - } - } + addr = crate::common::apply_trusted_proxy_addr(addr, req.headers()); Ok(response) }; let ws_stream = tokio_tungstenite::accept_hdr_async(stream, callback).await?; + if addr.ip() != peer_addr.ip() + && !crate::common::allow_connection_from_ip("hbbs-ws-forwarded", addr) + { + log::warn!( + "Rate limit exceeded for hbbs-ws-forwarded from {}", + addr.ip() + ); + return Ok(()); + } let (a, mut b) = ws_stream.split(); sink = Some(Sink::Ws(a)); while let Ok(Some(Ok(msg))) = timeout(30_000, b.next()).await { @@ -1300,7 +1553,7 @@ async fn check_relay_servers(rs0: Arc, tx: Sender) { } // temp solution to solve udp socket failure -async fn test_hbbs(addr: SocketAddr) -> ResultType<()> { +async fn test_hbbs(addr: SocketAddr, key: String) -> ResultType<()> { let mut addr = addr; if addr.ip().is_unspecified() { addr.set_ip(if addr.is_ipv4() { @@ -1314,6 +1567,7 @@ async fn test_hbbs(addr: SocketAddr) -> ResultType<()> { let mut msg_out = RendezvousMessage::new(); msg_out.set_register_peer(RegisterPeer { id: "(:test_hbbs:)".to_owned(), + licence_key: key, ..Default::default() }); let mut last_time_recv = Instant::now(); @@ -1337,6 +1591,371 @@ async fn test_hbbs(addr: SocketAddr) -> ResultType<()> { } } +fn prune_punch_requests(entries: &mut Vec) { + let before = entries.len(); + entries.retain(|entry| entry.tm.elapsed().as_secs() < PUNCH_REQ_RETENTION_SECS); + if entries.len() > MAX_PUNCH_REQS { + let excess = entries.len() - MAX_PUNCH_REQS; + entries.drain(0..excess); + } + if before > entries.len() { + crate::common::record_protection_event("punch_requests_pruned"); + } +} + +fn fanout_window_seconds() -> usize { + std::env::var(FANOUT_WINDOW_SECONDS_ENV) + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(DEFAULT_FANOUT_WINDOW_SECONDS) +} + +fn max_fanout_tracked_sources() -> usize { + std::env::var(MAX_FANOUT_TRACKED_SOURCES_ENV) + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(DEFAULT_MAX_FANOUT_TRACKED_SOURCES) +} + +fn max_punch_targets_per_ip_per_window() -> usize { + std::env::var(MAX_PUNCH_TARGETS_PER_IP_PER_WINDOW_ENV) + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(DEFAULT_MAX_PUNCH_TARGETS_PER_IP_PER_WINDOW) +} + +fn max_relay_targets_per_ip_per_window() -> usize { + std::env::var(MAX_RELAY_TARGETS_PER_IP_PER_WINDOW_ENV) + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(DEFAULT_MAX_RELAY_TARGETS_PER_IP_PER_WINDOW) +} + +fn tcp_punch_entry_ttl_secs() -> usize { + std::env::var(TCP_PUNCH_ENTRY_TTL_SECS_ENV) + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(DEFAULT_TCP_PUNCH_ENTRY_TTL_SECS) +} + +fn max_tcp_punch_entries() -> usize { + std::env::var(MAX_TCP_PUNCH_ENTRIES_ENV) + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(DEFAULT_MAX_TCP_PUNCH_ENTRIES) +} + +fn prune_tcp_punch_entries(entries: &mut HashMap) { + let before = entries.len(); + let ttl_secs = tcp_punch_entry_ttl_secs() as u64; + entries.retain(|_, entry| entry.created_at.elapsed().as_secs() < ttl_secs); + if before > entries.len() { + crate::common::record_protection_event("tcp_punch_entries_pruned"); + } +} + +fn evict_oldest_tcp_punch_entry(entries: &mut HashMap) -> bool { + let oldest_addr = entries + .iter() + .min_by_key(|(_, entry)| entry.created_at) + .map(|(addr, _)| *addr); + if let Some(addr) = oldest_addr { + entries.remove(&addr); + return true; + } + false +} + +fn insert_tcp_punch_entry( + entries: &mut HashMap, + addr: SocketAddr, + sink: Sink, +) -> Option { + prune_tcp_punch_entries(entries); + if !entries.contains_key(&addr) { + let max_entries = max_tcp_punch_entries(); + if max_entries > 0 && entries.len() >= max_entries && evict_oldest_tcp_punch_entry(entries) + { + crate::common::record_protection_event("tcp_punch_entries_evicted"); + } + if max_entries > 0 && entries.len() >= max_entries { + crate::common::record_protection_event("tcp_punch_entries_rejected"); + return Some(sink); + } + } + entries.insert( + addr, + TcpPunchEntry { + sink, + created_at: Instant::now(), + }, + ); + None +} + +fn prune_target_fanout(entries: &mut FanoutMap) { + let window_secs = fanout_window_seconds() as u64; + entries + .retain(|_, entry| entry.last_seen_at.elapsed().as_secs() < window_secs.saturating_mul(2)); +} + +fn evict_oldest_target_fanout(entries: &mut FanoutMap) -> bool { + let oldest_ip = entries + .iter() + .min_by_key(|(_, entry)| entry.last_seen_at) + .map(|(ip, _)| ip.clone()); + if let Some(ip) = oldest_ip { + entries.remove(&ip); + return true; + } + false +} + +fn allow_target_fanout( + entries: &mut FanoutMap, + source_ip: &str, + target_id: &str, + max_targets_per_window: usize, + evicted_event: &'static str, + rejected_event: &'static str, +) -> bool { + let now = Instant::now(); + let window_secs = fanout_window_seconds() as u64; + prune_target_fanout(entries); + if let Some(entry) = entries.get_mut(source_ip) { + if now.duration_since(entry.window_started_at).as_secs() >= window_secs { + entry.window_started_at = now; + entry.targets.clear(); + } + entry.last_seen_at = now; + if entry.targets.contains(target_id) { + return true; + } + if max_targets_per_window > 0 && entry.targets.len() >= max_targets_per_window { + return false; + } + entry.targets.insert(target_id.to_owned()); + return true; + } + + let max_entries = max_fanout_tracked_sources(); + if max_entries > 0 && entries.len() >= max_entries && evict_oldest_target_fanout(entries) { + crate::common::record_protection_event(evicted_event); + } + if max_entries > 0 && entries.len() >= max_entries { + crate::common::record_protection_event(rejected_event); + return false; + } + + entries.insert( + source_ip.to_owned(), + FanoutEntry { + window_started_at: now, + last_seen_at: now, + targets: HashSet::from([target_id.to_owned()]), + }, + ); + true +} + +fn max_online_request_peers() -> usize { + std::env::var(MAX_ONLINE_REQUEST_PEERS_ENV) + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(DEFAULT_MAX_ONLINE_REQUEST_PEERS) +} + +fn clamped_online_request_peer_count(total_peers: usize) -> usize { + total_peers.min(max_online_request_peers()) +} + +#[inline] +fn is_valid_registration_id(id: &str) -> bool { + let len = id.chars().count(); + (MIN_REGISTRATION_ID_LEN..=MAX_REGISTRATION_ID_LEN).contains(&len) +} + +#[inline] +fn is_valid_server_key(configured_key: &str, supplied_key: &str) -> bool { + configured_key.is_empty() || supplied_key == configured_key +} + +#[cfg(test)] +mod tests { + use super::{ + allow_target_fanout, clamped_online_request_peer_count, is_valid_registration_id, + is_valid_server_key, prune_punch_requests, FanoutMap, PunchReqEntry, + DEFAULT_MAX_ONLINE_REQUEST_PEERS, FANOUT_WINDOW_SECONDS_ENV, + MAX_FANOUT_TRACKED_SOURCES_ENV, MAX_ONLINE_REQUEST_PEERS_ENV, MAX_PUNCH_REQS, + PUNCH_REQ_RETENTION_SECS, + }; + use std::{ + collections::HashMap, + time::{Duration, Instant}, + }; + + #[test] + fn server_key_validation_accepts_open_server_or_matching_key() { + assert!(is_valid_server_key("", "")); + assert!(is_valid_server_key("", "anything")); + assert!(is_valid_server_key("shared-secret", "shared-secret")); + } + + #[test] + fn server_key_validation_rejects_mismatched_key() { + assert!(!is_valid_server_key("shared-secret", "")); + assert!(!is_valid_server_key("shared-secret", "wrong")); + } + + #[test] + fn registration_id_validation_enforces_basic_length_bounds() { + assert!(!is_valid_registration_id("")); + assert!(!is_valid_registration_id("12345")); + assert!(is_valid_registration_id("123456")); + assert!(is_valid_registration_id(&"a".repeat(100))); + assert!(!is_valid_registration_id(&"a".repeat(101))); + } + + #[test] + fn prune_punch_requests_removes_old_entries_and_caps_growth() { + let now = Instant::now(); + let old = now + .checked_sub(Duration::from_secs(PUNCH_REQ_RETENTION_SECS + 1)) + .unwrap_or(now); + let mut entries = vec![PunchReqEntry { + tm: old, + from_ip: "192.0.2.10".to_owned(), + to_ip: "198.51.100.10".to_owned(), + to_id: "old".to_owned(), + }]; + for i in 0..(MAX_PUNCH_REQS + 5) { + entries.push(PunchReqEntry { + tm: now, + from_ip: format!("192.0.2.{i}"), + to_ip: "198.51.100.10".to_owned(), + to_id: format!("peer-{i}"), + }); + } + + prune_punch_requests(&mut entries); + + assert_eq!(entries.len(), MAX_PUNCH_REQS); + assert!(entries + .iter() + .all(|entry| entry.tm.elapsed().as_secs() < PUNCH_REQ_RETENTION_SECS)); + assert_eq!( + entries.first().map(|entry| entry.to_id.as_str()), + Some("peer-5") + ); + } + + #[test] + fn online_request_peer_lookup_count_is_bounded() { + std::env::remove_var(MAX_ONLINE_REQUEST_PEERS_ENV); + assert_eq!( + clamped_online_request_peer_count(DEFAULT_MAX_ONLINE_REQUEST_PEERS + 1), + DEFAULT_MAX_ONLINE_REQUEST_PEERS + ); + + std::env::set_var(MAX_ONLINE_REQUEST_PEERS_ENV, "3"); + assert_eq!(clamped_online_request_peer_count(2), 2); + assert_eq!(clamped_online_request_peer_count(3), 3); + assert_eq!(clamped_online_request_peer_count(4), 3); + std::env::remove_var(MAX_ONLINE_REQUEST_PEERS_ENV); + } + + #[test] + fn target_fanout_caps_distinct_targets_but_allows_repeats() { + std::env::set_var(FANOUT_WINDOW_SECONDS_ENV, "60"); + let mut entries: FanoutMap = HashMap::new(); + + assert!(allow_target_fanout( + &mut entries, + "198.51.100.10", + "peer-a", + 2, + "fanout_evicted", + "fanout_rejected" + )); + assert!(allow_target_fanout( + &mut entries, + "198.51.100.10", + "peer-a", + 2, + "fanout_evicted", + "fanout_rejected" + )); + assert!(allow_target_fanout( + &mut entries, + "198.51.100.10", + "peer-b", + 2, + "fanout_evicted", + "fanout_rejected" + )); + assert!(!allow_target_fanout( + &mut entries, + "198.51.100.10", + "peer-c", + 2, + "fanout_evicted", + "fanout_rejected" + )); + + std::env::remove_var(FANOUT_WINDOW_SECONDS_ENV); + } + + #[test] + fn target_fanout_tracked_sources_are_bounded() { + std::env::set_var(FANOUT_WINDOW_SECONDS_ENV, "60"); + std::env::set_var(MAX_FANOUT_TRACKED_SOURCES_ENV, "2"); + let now = Instant::now(); + let older = now.checked_sub(Duration::from_secs(10)).unwrap_or(now); + let newer = now.checked_sub(Duration::from_secs(1)).unwrap_or(now); + let mut entries: FanoutMap = HashMap::from([ + ( + "198.51.100.1".to_owned(), + super::FanoutEntry { + window_started_at: older, + last_seen_at: older, + targets: std::collections::HashSet::from(["peer-a".to_owned()]), + }, + ), + ( + "198.51.100.2".to_owned(), + super::FanoutEntry { + window_started_at: newer, + last_seen_at: newer, + targets: std::collections::HashSet::from(["peer-b".to_owned()]), + }, + ), + ]); + + assert!(allow_target_fanout( + &mut entries, + "198.51.100.3", + "peer-c", + 2, + "fanout_evicted", + "fanout_rejected" + )); + assert_eq!(entries.len(), 2); + assert!(!entries.contains_key("198.51.100.1")); + assert!(entries.contains_key("198.51.100.2")); + assert!(entries.contains_key("198.51.100.3")); + + std::env::remove_var(FANOUT_WINDOW_SECONDS_ENV); + std::env::remove_var(MAX_FANOUT_TRACKED_SOURCES_ENV); + } +} + #[inline] async fn send_rk_res( socket: &mut FramedSocket, diff --git a/tests/server_protection_process.rs b/tests/server_protection_process.rs new file mode 100644 index 000000000..2acf0c030 --- /dev/null +++ b/tests/server_protection_process.rs @@ -0,0 +1,681 @@ +use hbb_common::{ + anyhow::{bail, Context, Result}, + protobuf::Message, + rendezvous_proto::{ + register_pk_response, rendezvous_message, OnlineRequest, PunchHoleRequest, RegisterPk, + RegisterPkResponse, RendezvousMessage, RequestRelay, + }, + tcp::FramedStream, + udp::FramedSocket, +}; +use sqlx::{Connection, Row, SqliteConnection}; +use std::{ + fs, + io::{Read, Write}, + net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, TcpStream, UdpSocket}, + path::{Path, PathBuf}, + process::{Child, Command, Stdio}, + thread, + time::{Duration, Instant, SystemTime}, +}; + +struct ChildGuard { + child: Child, + temp_dir: PathBuf, +} + +impl ChildGuard { + fn try_wait(&mut self) -> Result> { + Ok(self.child.try_wait()?) + } +} + +impl Drop for ChildGuard { + fn drop(&mut self) { + let _ = self.child.kill(); + let _ = self.child.wait(); + if !self.temp_dir.as_os_str().is_empty() { + let _ = fs::remove_dir_all(&self.temp_dir); + } + } +} + +fn unique_temp_dir(prefix: &str) -> Result { + let mut path = std::env::temp_dir(); + let stamp = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or_default() + .as_nanos(); + path.push(format!("{prefix}-{}-{stamp}", std::process::id())); + fs::create_dir_all(&path)?; + Ok(path) +} + +fn pick_hbbs_port_candidate() -> Result { + for _ in 0..64 { + let main = TcpListener::bind((Ipv4Addr::UNSPECIFIED, 0))?; + let port = main.local_addr()?.port(); + if port <= 1024 || port >= (u16::MAX - 2) { + continue; + } + if TcpListener::bind((Ipv4Addr::UNSPECIFIED, port - 1)).is_ok() + && TcpListener::bind((Ipv4Addr::UNSPECIFIED, port + 2)).is_ok() + && UdpSocket::bind((Ipv4Addr::UNSPECIFIED, port)).is_ok() + { + return Ok(port); + } + } + bail!("failed to find hbbs port triplet"); +} + +fn pick_hbbr_port_candidate() -> Result { + for _ in 0..64 { + let main = TcpListener::bind((Ipv4Addr::UNSPECIFIED, 0))?; + let port = main.local_addr()?.port(); + if port >= (u16::MAX - 2) { + continue; + } + if TcpListener::bind((Ipv4Addr::UNSPECIFIED, port + 2)).is_ok() { + return Ok(port); + } + } + bail!("failed to find hbbr port pair"); +} + +fn wait_for_tcp_ready(child: &mut ChildGuard, addr: SocketAddr) -> Result<()> { + let deadline = Instant::now() + Duration::from_secs(10); + while Instant::now() < deadline { + match TcpStream::connect_timeout(&addr, Duration::from_millis(200)) { + Ok(_) => return Ok(()), + Err(_) => { + if let Some(status) = child.try_wait()? { + bail!("server exited before becoming ready: {status}"); + } + thread::sleep(Duration::from_millis(100)); + } + } + } + bail!("timed out waiting for {addr} to accept connections"); +} + +fn find_bin_path(bin_env: &str, bin_name: &str) -> Result { + if let Some(path) = std::env::var_os(bin_env) { + return Ok(PathBuf::from(path)); + } + let mut path = std::env::current_exe()?; + path.pop(); + path.pop(); + path.push(bin_name); + #[cfg(windows)] + path.set_extension("exe"); + if path.is_file() { + return Ok(path); + } + bail!("missing cargo bin env and fallback binary path for {bin_name}"); +} + +fn spawn_server( + bin_env: &str, + bin_name: &str, + args: &[String], + current_dir: &Path, + envs: &[(&str, &str)], +) -> Result { + let bin = find_bin_path(bin_env, bin_name)?; + let mut command = Command::new(bin); + command + .current_dir(current_dir) + .args(args) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + for (key, value) in envs { + command.env(key, value); + } + let child = command.spawn()?; + Ok(ChildGuard { + child, + temp_dir: current_dir.to_path_buf(), + }) +} + +fn spawn_hbbs(key: &str) -> Result<(u16, ChildGuard)> { + let mut last_err = None; + for _ in 0..16 { + let port = pick_hbbs_port_candidate()?; + let temp_dir = unique_temp_dir("hbbs-process-test")?; + match spawn_hbbs_in_dir_with_port(port, key, temp_dir, &[], false) { + Ok(child) => return Ok((port, child)), + Err(err) => last_err = Some(err), + } + } + Err(last_err.unwrap_or_else(|| hbb_common::anyhow::anyhow!("failed to spawn hbbs"))) +} + +fn spawn_hbbs_in_dir( + key: &str, + temp_dir: PathBuf, + envs: &[(&str, &str)], +) -> Result<(u16, ChildGuard)> { + let mut last_err = None; + for _ in 0..16 { + let port = pick_hbbs_port_candidate()?; + match spawn_hbbs_in_dir_with_port(port, key, temp_dir.clone(), envs, true) { + Ok(child) => return Ok((port, child)), + Err(err) => last_err = Some(err), + } + } + Err(last_err.unwrap_or_else(|| hbb_common::anyhow::anyhow!("failed to spawn hbbs"))) +} + +fn spawn_hbbs_in_dir_with_port( + port: u16, + key: &str, + temp_dir: PathBuf, + envs: &[(&str, &str)], + preserve_temp_dir_on_error: bool, +) -> Result { + let args = vec![ + "--port".to_owned(), + port.to_string(), + "--key".to_owned(), + key.to_owned(), + ]; + let mut child_envs = vec![("TEST_HBBS", "no")]; + child_envs.extend_from_slice(envs); + let mut child = spawn_server("CARGO_BIN_EXE_hbbs", "hbbs", &args, &temp_dir, &child_envs)?; + if let Err(err) = wait_for_tcp_ready( + &mut child, + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port - 1), + ) { + if preserve_temp_dir_on_error { + child.temp_dir = PathBuf::new(); + } + return Err(err); + } + Ok(child) +} + +fn spawn_hbbr(key: &str) -> Result<(u16, ChildGuard)> { + let mut last_err = None; + for _ in 0..16 { + let port = pick_hbbr_port_candidate()?; + let temp_dir = unique_temp_dir("hbbr-process-test")?; + let args = vec![ + "--port".to_owned(), + port.to_string(), + "--key".to_owned(), + key.to_owned(), + ]; + let mut child = match spawn_server("CARGO_BIN_EXE_hbbr", "hbbr", &args, &temp_dir, &[]) { + Ok(child) => child, + Err(err) => { + last_err = Some(err); + continue; + } + }; + match wait_for_tcp_ready( + &mut child, + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port), + ) { + Ok(()) => return Ok((port, child)), + Err(err) => last_err = Some(err), + } + } + Err(last_err.unwrap_or_else(|| hbb_common::anyhow::anyhow!("failed to spawn hbbr"))) +} + +fn admin_command(addr: SocketAddr, command: &str) -> Result { + let mut stream = TcpStream::connect_timeout(&addr, Duration::from_secs(2))?; + stream.set_read_timeout(Some(Duration::from_secs(2)))?; + stream.write_all(command.as_bytes())?; + stream.shutdown(std::net::Shutdown::Write)?; + let mut out = String::new(); + stream.read_to_string(&mut out)?; + Ok(out) +} + +fn non_loopback_local_ip() -> Option { + match local_ip_address::local_ip() { + Ok(ip) if !ip.is_loopback() && !ip.is_unspecified() => Some(ip), + _ => None, + } +} + +async fn send_online_request(addr: SocketAddr, key: &str) -> Result> { + let mut stream = FramedStream::new(addr, None, 1_500).await?; + let mut message = RendezvousMessage::new(); + message.set_online_request(OnlineRequest { + licence_key: key.to_owned(), + ..Default::default() + }); + stream.send(&message).await?; + let response = match stream.next_timeout(1_000).await { + Some(Ok(bytes)) => Some(RendezvousMessage::parse_from_bytes(&bytes)?), + Some(Err(err)) => return Err(err.into()), + None => None, + }; + Ok(response) +} + +async fn send_register_pk( + addr: SocketAddr, + id: &str, + key: &str, +) -> Result> { + let mut socket = FramedSocket::new((Ipv4Addr::UNSPECIFIED, 0)).await?; + let mut message = RendezvousMessage::new(); + message.set_register_pk(RegisterPk { + id: id.to_owned(), + uuid: vec![1, 2, 3, 4].into(), + pk: vec![9, 8, 7, 6].into(), + licence_key: key.to_owned(), + ..Default::default() + }); + socket.send(&message, addr).await?; + let response = match socket.next_timeout(1_000).await { + Some(Ok((bytes, _))) => Some(RendezvousMessage::parse_from_bytes(&bytes)?), + Some(Err(err)) => return Err(err), + None => None, + }; + Ok(response) +} + +async fn send_relay_request_on_stream( + stream: &mut FramedStream, + id: &str, + uuid: &str, + key: &str, +) -> Result> { + let mut message = RendezvousMessage::new(); + message.set_request_relay(RequestRelay { + id: id.to_owned(), + uuid: uuid.to_owned(), + licence_key: key.to_owned(), + ..Default::default() + }); + stream.send(&message).await?; + let response = match stream.next_timeout(1_000).await { + Some(Ok(bytes)) => Some(RendezvousMessage::parse_from_bytes(&bytes)?), + Some(Err(err)) => return Err(err.into()), + None => None, + }; + Ok(response) +} + +async fn send_relay_request(addr: SocketAddr, key: &str) -> Result> { + let mut stream = FramedStream::new(addr, None, 1_500).await?; + send_relay_request_on_stream(&mut stream, "peer-a", "relay-test-uuid", key).await +} + +async fn send_punch_hole_request_on_stream( + stream: &mut FramedStream, + id: &str, + key: &str, +) -> Result> { + let mut message = RendezvousMessage::new(); + message.set_punch_hole_request(PunchHoleRequest { + id: id.to_owned(), + licence_key: key.to_owned(), + ..Default::default() + }); + stream.send(&message).await?; + let response = match stream.next_timeout(1_000).await { + Some(Ok(bytes)) => Some(RendezvousMessage::parse_from_bytes(&bytes)?), + Some(Err(err)) => return Err(err.into()), + None => None, + }; + Ok(response) +} + +fn runtime() -> Result { + Ok(hbb_common::tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_all() + .build()?) +} + +fn temp_db_path(dir: &Path) -> PathBuf { + dir.join("db_v2.sqlite3") +} + +async fn create_peer_schema_and_insert_stale_row(db_path: &Path, id: &str) -> Result<()> { + if !db_path.exists() { + fs::File::create(db_path)?; + } + let mut conn = SqliteConnection::connect(db_path.to_string_lossy().as_ref()).await?; + sqlx::query( + " + create table if not exists peer ( + guid blob primary key not null, + id varchar(100) not null, + uuid blob not null, + pk blob not null, + created_at datetime not null default(current_timestamp), + user blob, + status tinyint, + note varchar(300), + info text not null + ) without rowid; + ", + ) + .execute(&mut conn) + .await?; + sqlx::query("create unique index if not exists index_peer_id on peer (id);") + .execute(&mut conn) + .await?; + sqlx::query("create index if not exists index_peer_created_at on peer (created_at);") + .execute(&mut conn) + .await?; + sqlx::query( + "insert into peer(guid, id, uuid, pk, created_at, info) values(?, ?, ?, ?, datetime('now', '-2 days'), ?)", + ) + .bind(vec![0u8; 16]) + .bind(id) + .bind(vec![1u8; 4]) + .bind(vec![2u8; 4]) + .bind("") + .execute(&mut conn) + .await?; + Ok(()) +} + +async fn peer_exists(db_path: &Path, id: &str) -> Result { + let mut conn = SqliteConnection::connect(db_path.to_string_lossy().as_ref()).await?; + let row = sqlx::query("select count(*) from peer where id = ?") + .bind(id) + .fetch_one(&mut conn) + .await?; + let count: i64 = row.try_get(0)?; + Ok(count > 0) +} + +#[test] +fn hbbs_admin_protection_stats_reports_limits() -> Result<()> { + let (port, _child) = spawn_hbbs("server-key")?; + let output = admin_command( + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port - 1), + "ps", + )?; + assert!(output.contains("connections_per_ip_per_window=")); + assert!(output.contains("udp_packets_per_ip_per_window=")); + assert!(output.contains("trust_proxy_headers=")); + Ok(()) +} + +#[test] +fn hbbr_admin_protection_stats_reports_limits() -> Result<()> { + let (port, _child) = spawn_hbbr("server-key")?; + let output = admin_command(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port), "ps")?; + assert!(output.contains("connections_per_ip_per_window=")); + assert!(output.contains("udp_packets_per_ip_per_window=")); + assert!(output.contains("trust_proxy_headers=")); + Ok(()) +} + +#[test] +fn hbbs_online_request_requires_configured_key_process() -> Result<()> { + let Some(ip) = non_loopback_local_ip() else { + eprintln!("skipping non-loopback hbbs auth process test: no non-loopback local IP"); + return Ok(()); + }; + let (port, _child) = spawn_hbbs("server-key")?; + let addr = SocketAddr::new(ip, port - 1); + + let runtime = runtime()?; + runtime.block_on(async move { + let unauthorized = send_online_request(addr, "").await?; + assert!( + unauthorized.is_none(), + "unauthorized online request unexpectedly received a response" + ); + + let authorized = send_online_request(addr, "server-key") + .await? + .context("authorized online request should receive a response")?; + match authorized.union { + Some(rendezvous_message::Union::OnlineResponse(response)) => { + assert!(response.states.is_empty()); + } + other => bail!("unexpected response to authorized online request: {other:?}"), + } + Ok::<(), hbb_common::anyhow::Error>(()) + })?; + Ok(()) +} + +#[test] +fn hbbs_register_pk_requires_configured_key_process() -> Result<()> { + let (port, _child) = spawn_hbbs("server-key")?; + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); + let runtime = runtime()?; + runtime.block_on(async move { + let unauthorized = send_register_pk(addr, "peeruna1", "") + .await? + .context("unauthorized register_pk should receive a response")?; + match unauthorized.union { + Some(rendezvous_message::Union::RegisterPkResponse(RegisterPkResponse { + result, + .. + })) => { + assert_eq!( + result, + register_pk_response::Result::LICENSE_MISMATCH.into() + ); + } + other => bail!("unexpected response to unauthorized register_pk: {other:?}"), + } + + let authorized = send_register_pk(addr, "peerauth1", "server-key") + .await? + .context("authorized register_pk should receive a response")?; + match authorized.union { + Some(rendezvous_message::Union::RegisterPkResponse(RegisterPkResponse { + result, + .. + })) => { + assert_eq!(result, register_pk_response::Result::OK.into()); + } + other => bail!("unexpected response to authorized register_pk: {other:?}"), + } + Ok::<(), hbb_common::anyhow::Error>(()) + })?; + Ok(()) +} + +#[test] +fn hbbr_request_relay_reports_key_mismatch_process() -> Result<()> { + let Some(ip) = non_loopback_local_ip() else { + eprintln!("skipping non-loopback hbbr auth process test: no non-loopback local IP"); + return Ok(()); + }; + let (port, _child) = spawn_hbbr("server-key")?; + let addr = SocketAddr::new(ip, port); + let runtime = runtime()?; + runtime.block_on(async move { + let response = send_relay_request(addr, "") + .await? + .context("relay key mismatch should receive a refusal response")?; + match response.union { + Some(rendezvous_message::Union::RelayResponse(response)) => { + assert_eq!(response.refuse_reason, "Key mismatch"); + } + other => bail!("unexpected response to unauthorized relay request: {other:?}"), + } + Ok::<(), hbb_common::anyhow::Error>(()) + })?; + Ok(()) +} + +#[test] +fn hbbs_register_pk_reports_peer_limit_reached_process() -> Result<()> { + let temp_dir = unique_temp_dir("hbbs-peer-limit-process-test")?; + let db_path = temp_db_path(&temp_dir); + let db_url = db_path.to_string_lossy().to_string(); + let (port, _child) = spawn_hbbs_in_dir( + "server-key", + temp_dir, + &[ + ("DB_URL", db_url.as_str()), + ("MAX_TOTAL_PEER_RECORDS", "1"), + ("PEER_RECORD_RETENTION_DAYS", "3650"), + ], + )?; + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); + let runtime = runtime()?; + runtime.block_on(async move { + let first = send_register_pk(addr, "limitpeer1", "server-key") + .await? + .context("first register_pk should receive a response")?; + match first.union { + Some(rendezvous_message::Union::RegisterPkResponse(RegisterPkResponse { + result, + .. + })) => { + assert_eq!(result, register_pk_response::Result::OK.into()); + } + other => bail!("unexpected first register_pk response: {other:?}"), + } + + let second = send_register_pk(addr, "limitpeer2", "server-key") + .await? + .context("second register_pk should receive a response")?; + match second.union { + Some(rendezvous_message::Union::RegisterPkResponse(RegisterPkResponse { + result, + .. + })) => { + assert_eq!( + result, + register_pk_response::Result::PEER_LIMIT_REACHED.into() + ); + } + other => bail!("unexpected second register_pk response: {other:?}"), + } + Ok::<(), hbb_common::anyhow::Error>(()) + })?; + Ok(()) +} + +#[test] +fn hbbs_register_pk_prunes_stale_peer_before_enforcing_cap_process() -> Result<()> { + let temp_dir = unique_temp_dir("hbbs-peer-retention-process-test")?; + let db_path = temp_db_path(&temp_dir); + let runtime = runtime()?; + runtime.block_on(create_peer_schema_and_insert_stale_row( + &db_path, "oldpeer1", + ))?; + let db_url = db_path.to_string_lossy().to_string(); + let (port, _child) = spawn_hbbs_in_dir( + "server-key", + temp_dir, + &[ + ("DB_URL", db_url.as_str()), + ("MAX_TOTAL_PEER_RECORDS", "1"), + ("PEER_RECORD_RETENTION_DAYS", "1"), + ], + )?; + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); + runtime.block_on(async move { + let response = send_register_pk(addr, "newpeer1", "server-key") + .await? + .context("register_pk should receive a response")?; + match response.union { + Some(rendezvous_message::Union::RegisterPkResponse(RegisterPkResponse { + result, + .. + })) => { + assert_eq!(result, register_pk_response::Result::OK.into()); + } + other => bail!("unexpected register_pk response after stale prune: {other:?}"), + } + Ok::<(), hbb_common::anyhow::Error>(()) + })?; + assert!(!runtime.block_on(peer_exists(&db_path, "oldpeer1"))?); + assert!(runtime.block_on(peer_exists(&db_path, "newpeer1"))?); + Ok(()) +} + +#[test] +fn hbbs_request_relay_fanout_limit_process() -> Result<()> { + let temp_dir = unique_temp_dir("hbbs-relay-fanout-process-test")?; + let (port, _child) = spawn_hbbs_in_dir( + "server-key", + temp_dir, + &[ + ("FANOUT_WINDOW_SECONDS", "60"), + ("MAX_RELAY_TARGETS_PER_IP_PER_WINDOW", "1"), + ], + )?; + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); + let runtime = runtime()?; + runtime.block_on(async move { + let mut stream = FramedStream::new(addr, None, 1_500).await?; + let first = + send_relay_request_on_stream(&mut stream, "peer-a", "relay-a", "server-key").await?; + assert!( + first.is_none(), + "first relay fan-out request should not be refused" + ); + + let second = send_relay_request_on_stream(&mut stream, "peer-b", "relay-b", "server-key") + .await? + .context("second relay fan-out request should receive a refusal")?; + match second.union { + Some(rendezvous_message::Union::RelayResponse(response)) => { + assert_eq!(response.refuse_reason, "Too many distinct relay targets"); + } + other => bail!("unexpected response to relay fan-out refusal: {other:?}"), + } + Ok::<(), hbb_common::anyhow::Error>(()) + })?; + Ok(()) +} + +#[test] +fn hbbs_tcp_punch_entries_prune_after_ttl_process() -> Result<()> { + let temp_dir = unique_temp_dir("hbbs-tcp-punch-prune-process-test")?; + let (port, _child) = spawn_hbbs_in_dir( + "server-key", + temp_dir, + &[ + ("TCP_PUNCH_ENTRY_TTL_SECS", "1"), + ("MAX_TCP_PUNCH_ENTRIES", "16"), + ], + )?; + let udp_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); + let admin_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port - 1); + let tcp_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); + let runtime = runtime()?; + runtime.block_on(async { + let response = send_register_pk(udp_addr, "target11", "server-key") + .await? + .context("register_pk should receive a response")?; + match response.union { + Some(rendezvous_message::Union::RegisterPkResponse(RegisterPkResponse { + result, + .. + })) => { + assert_eq!(result, register_pk_response::Result::OK.into()); + } + other => bail!("unexpected register_pk response: {other:?}"), + } + + let mut stream = FramedStream::new(tcp_addr, None, 1_500).await?; + let response = + send_punch_hole_request_on_stream(&mut stream, "target11", "server-key").await?; + assert!( + response.is_none(), + "live punch-hole request should not receive an immediate response" + ); + + std::thread::sleep(Duration::from_millis(1_250)); + let stats = admin_command(admin_addr, "ps")?; + assert!( + stats.contains("tcp_punch_entries=0"), + "unexpected protection stats: {stats}" + ); + Ok::<(), hbb_common::anyhow::Error>(()) + })?; + Ok(()) +}