diff --git a/.config/nextest.toml b/.config/nextest.toml index d2ae495e9a..a549d4068f 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -15,4 +15,4 @@ test-group = 'run-in-isolation' threads-required = 32 [profile.default] -slow-timeout = { period = "20s", terminate-after = 3 } +slow-timeout = { period = "10s", terminate-after = 3 } diff --git a/Cargo.lock b/Cargo.lock index 197ad1f591..9e5abcb14f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,6 +16,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aead" version = "0.6.0-rc.2" @@ -34,7 +49,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.4", + "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", @@ -42,9 +57,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -339,15 +354,30 @@ dependencies = [ [[package]] name = "backon" -version = "1.6.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +checksum = "592277618714fbcecda9a02ba7a8781f319d26532a88553bbacc77ba5d2b3a8d" dependencies = [ "fastrand", "gloo-timers 0.3.0", "tokio", ] +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link 0.2.1", +] + [[package]] name = "base16ct" version = "0.3.0" @@ -368,9 +398,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64-url" -version = "3.0.2" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5b428e9fb429c6fda7316e9b006f993e6b4c33005e4659339fb5214479dddec" +checksum = "38e2b6c78c06f7288d5e3c3d683bde35a79531127c83b087e5d0d77c974b4b28" dependencies = [ "base64", ] @@ -407,9 +437,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "blake3" @@ -460,9 +490,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.43" +version = "1.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" dependencies = [ "find-msvc-tools", "shlex", @@ -476,9 +506,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "cfg_aliases" @@ -551,9 +581,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.51" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", "clap_derive", @@ -561,9 +591,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstream", "anstyle", @@ -573,9 +603,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" dependencies = [ "heck", "proc-macro2", @@ -585,9 +615,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cobs" @@ -624,7 +654,7 @@ dependencies = [ "libc", "once_cell", "unicode-width", - "windows-sys 0.61.2", + "windows-sys 0.61.1", ] [[package]] @@ -906,9 +936,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", ] @@ -1023,9 +1053,9 @@ dependencies = [ [[package]] name = "document-features" -version = "0.2.12" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" dependencies = [ "litrs", ] @@ -1121,7 +1151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.1", ] [[package]] @@ -1130,7 +1160,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18c1ddb9231d8554c2d6bdf4cfaabf0c59251658c68b6c95cd52dd0c513a912a" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.3.3", "libm", "rand", "siphasher", @@ -1150,9 +1180,9 @@ checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" [[package]] name = "flume" @@ -1177,12 +1207,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1356,24 +1380,30 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasip2", + "wasi 0.14.7+wasi-0.2.4", "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "gloo" version = "0.8.1" @@ -1563,7 +1593,7 @@ dependencies = [ "futures-sink", "futures-timer", "futures-util", - "getrandom 0.3.4", + "getrandom 0.3.3", "hashbrown 0.15.5", "nonzero_ext", "parking_lot", @@ -1596,13 +1626,12 @@ dependencies = [ [[package]] name = "half" -version = "2.7.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", - "zerocopy", ] [[package]] @@ -1628,18 +1657,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.1.5", -] - -[[package]] -name = "hashbrown" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", + "foldhash", ] [[package]] @@ -1896,7 +1914,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.0", "tokio", "tower-service", "tracing", @@ -1928,9 +1946,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", "potential_utf", @@ -1941,9 +1959,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -1954,10 +1972,11 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ + "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -1968,38 +1987,42 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", + "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", "icu_locale_core", + "stable_deref_trait", + "tinystr", "writeable", "yoke", "zerofrom", @@ -2057,19 +2080,19 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.15.5", ] [[package]] name = "indicatif" -version = "0.18.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade6dfcba0dfb62ad59e59e7241ec8912af34fd29e0e743e3db992bd278e8b65" +checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" dependencies = [ "console", "portable-atomic", @@ -2081,9 +2104,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.2.1" +version = "0.2.0-rc.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7357b6e7aa75618c7864ebd0634b115a7218b0615f4cb1df33ac3eca23943d4" +checksum = "1603f76010ff924b616c8f44815a42eb10fb0b93d308b41deaa8da6d4251fd4b" dependencies = [ "hybrid-array", ] @@ -2100,6 +2123,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "ipconfig" version = "0.3.2" @@ -2147,7 +2181,7 @@ dependencies = [ "derive_more 2.0.1", "ed25519-dalek", "futures-util", - "getrandom 0.3.4", + "getrandom 0.3.3", "hickory-resolver", "http 1.3.1", "igd-next", @@ -2192,7 +2226,7 @@ dependencies = [ "tracing", "tracing-subscriber", "tracing-subscriber-wasm", - "tracing-test", + "tracing-test 0.2.5 (git+https://github.com/Frando/tracing-test?branch=feat%2Fcolor-and-filter-on-cli)", "url", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -2264,7 +2298,7 @@ dependencies = [ "humantime-serde", "iroh", "iroh-metrics", - "lru 0.16.2", + "lru 0.16.1", "n0-error", "n0-future", "pkarr", @@ -2288,7 +2322,7 @@ dependencies = [ "tower_governor", "tracing", "tracing-subscriber", - "tracing-test", + "tracing-test 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "ttl_cache", "url", "z32", @@ -2329,7 +2363,7 @@ dependencies = [ [[package]] name = "iroh-quinn" version = "0.14.0" -source = "git+https://github.com/n0-computer/quinn?branch=main-iroh#246779c6f8a704db8a7e291257bb099cbf6a8eb6" +source = "git+https://github.com/n0-computer/quinn?branch=protocol-simplification#7a094bd12f5ee643be3fd873d6d6eef3fafbc0e7" dependencies = [ "bytes", "cfg_aliases", @@ -2338,7 +2372,7 @@ dependencies = [ "pin-project-lite", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2 0.6.0", "thiserror 2.0.17", "tokio", "tokio-stream", @@ -2349,11 +2383,11 @@ dependencies = [ [[package]] name = "iroh-quinn-proto" version = "0.13.0" -source = "git+https://github.com/n0-computer/quinn?branch=main-iroh#246779c6f8a704db8a7e291257bb099cbf6a8eb6" +source = "git+https://github.com/n0-computer/quinn?branch=protocol-simplification#7a094bd12f5ee643be3fd873d6d6eef3fafbc0e7" dependencies = [ "bytes", "fastbloom", - "getrandom 0.3.4", + "getrandom 0.3.3", "identity-hash", "lru-slab", "rand", @@ -2361,7 +2395,7 @@ dependencies = [ "rustc-hash", "rustls", "rustls-pki-types", - "rustls-platform-verifier 0.6.2", + "rustls-platform-verifier 0.6.1", "slab", "thiserror 2.0.17", "tinyvec", @@ -2372,14 +2406,14 @@ dependencies = [ [[package]] name = "iroh-quinn-udp" version = "0.5.12" -source = "git+https://github.com/n0-computer/quinn?branch=main-iroh#246779c6f8a704db8a7e291257bb099cbf6a8eb6" +source = "git+https://github.com/n0-computer/quinn?branch=protocol-simplification#7a094bd12f5ee643be3fd873d6d6eef3fafbc0e7" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.0", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -2395,7 +2429,7 @@ dependencies = [ "dashmap", "data-encoding", "derive_more 2.0.1", - "getrandom 0.3.4", + "getrandom 0.3.3", "hickory-resolver", "http 1.3.1", "http-body-util", @@ -2405,7 +2439,7 @@ dependencies = [ "iroh-metrics", "iroh-quinn", "iroh-quinn-proto", - "lru 0.16.2", + "lru 0.16.1", "n0-error", "n0-future", "num_enum", @@ -2438,7 +2472,7 @@ dependencies = [ "toml", "tracing", "tracing-subscriber", - "tracing-test", + "tracing-test 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "url", "webpki-roots", "ws_stream_wasm", @@ -2447,9 +2481,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.2" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -2490,9 +2524,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -2506,9 +2540,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libm" @@ -2540,15 +2574,15 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" -version = "1.0.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "lock_api" @@ -2586,11 +2620,11 @@ checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" [[package]] name = "lru" -version = "0.16.2" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" +checksum = "bfe949189f46fabb938b3a9a0be30fdd93fd8a09260da863399a8cf3db756ec8" dependencies = [ - "hashbrown 0.16.0", + "hashbrown 0.15.5", ] [[package]] @@ -2617,8 +2651,8 @@ dependencies = [ "ed25519-dalek", "flume", "futures-lite", - "getrandom 0.3.4", - "lru 0.16.2", + "getrandom 0.3.3", + "lru 0.16.1", "serde", "serde_bencode", "serde_bytes", @@ -2670,15 +2704,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + [[package]] name = "mio" -version = "1.1.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi", - "windows-sys 0.61.2", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] @@ -2840,7 +2883,7 @@ dependencies = [ "netlink-sys", "pin-project-lite", "serde", - "socket2 0.6.1", + "socket2 0.6.0", "time", "tokio", "tokio-util", @@ -2890,11 +2933,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.50.3" +version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2933,9 +2976,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.5" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", "rustversion", @@ -2943,9 +2986,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.5" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2962,6 +3005,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "oid-registry" version = "0.8.1" @@ -2983,9 +3035,9 @@ dependencies = [ [[package]] name = "once_cell_polyfill" -version = "1.70.2" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "oorandom" @@ -3042,12 +3094,12 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pem" -version = "3.0.6" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" dependencies = [ "base64", - "serde_core", + "serde", ] [[package]] @@ -3122,7 +3174,7 @@ dependencies = [ "ed25519-dalek", "futures-buffered", "futures-lite", - "getrandom 0.3.4", + "getrandom 0.3.3", "log", "lru 0.13.0", "mainline", @@ -3214,7 +3266,7 @@ dependencies = [ "rand", "serde", "smallvec", - "socket2 0.6.1", + "socket2 0.6.0", "time", "tokio", "tokio-util", @@ -3250,9 +3302,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] @@ -3303,22 +3355,23 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.9.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce" dependencies = [ "bit-set", "bit-vec", "bitflags", + "lazy_static", "num-traits", "rand", "rand_chacha", @@ -3339,7 +3392,7 @@ dependencies = [ "libc", "once_cell", "raw-cpuid", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "web-sys", "winapi", ] @@ -3363,7 +3416,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2 0.6.0", "thiserror 2.0.17", "tokio", "tracing", @@ -3377,7 +3430,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", - "getrandom 0.3.4", + "getrandom 0.3.3", "lru-slab", "rand", "ring", @@ -3400,9 +3453,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.0", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -3446,7 +3499,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.3.3", ] [[package]] @@ -3532,9 +3585,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" dependencies = [ "aho-corasick", "memchr", @@ -3544,9 +3597,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" dependencies = [ "aho-corasick", "memchr", @@ -3555,9 +3608,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "reloadable-core" @@ -3578,9 +3631,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64", "bytes", @@ -3637,6 +3690,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -3671,14 +3730,14 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.1", ] [[package]] name = "rustls" -version = "0.23.34" +version = "0.23.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" +checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c" dependencies = [ "log", "once_cell", @@ -3726,9 +3785,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -3747,9 +3806,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", "zeroize", @@ -3773,14 +3832,14 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs 0.26.11", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustls-platform-verifier" -version = "0.6.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "be59af91596cac372a6942530653ad0c3a246cdd491aaa9dcaee47f88d67d5a0" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", @@ -3793,8 +3852,8 @@ dependencies = [ "rustls-webpki", "security-framework", "security-framework-sys", - "webpki-root-certs 1.0.3", - "windows-sys 0.52.0", + "webpki-root-certs 1.0.2", + "windows-sys 0.59.0", ] [[package]] @@ -3805,9 +3864,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "ring", "rustls-pki-types", @@ -3863,7 +3922,7 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.61.1", ] [[package]] @@ -3903,9 +3962,9 @@ dependencies = [ [[package]] name = "self_cell" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" +checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" [[package]] name = "semver" @@ -4006,9 +4065,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" dependencies = [ "serde_core", ] @@ -4153,12 +4212,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -4208,9 +4267,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "strsim" @@ -4276,9 +4335,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "swarm-discovery" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790d8444f7db1e88f70aed3234cab8e42c48e05360bfc86ca7dce0d9a5d95d26" +checksum = "4eae338a4551897c6a50fa2c041c4b75f578962d9fca8adb828cf81f7158740f" dependencies = [ "acto", "hickory-proto", @@ -4291,9 +4350,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.108" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -4354,10 +4413,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.1", ] [[package]] @@ -4445,9 +4504,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -4480,26 +4539,29 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ + "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "slab", + "socket2 0.6.0", "tokio-macros", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", @@ -4581,7 +4643,7 @@ dependencies = [ "bytes", "futures-core", "futures-sink", - "getrandom 0.3.4", + "getrandom 0.3.3", "http 1.3.1", "httparse", "rand", @@ -4595,9 +4657,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" dependencies = [ "indexmap", "serde_core", @@ -4610,18 +4672,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" dependencies = [ "indexmap", "toml_datetime", @@ -4631,18 +4693,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" [[package]] name = "tower" @@ -4789,7 +4851,17 @@ checksum = "557b891436fe0d5e0e363427fc7f217abf9ccd510d5136549847bdcbcd011d68" dependencies = [ "tracing-core", "tracing-subscriber", - "tracing-test-macro", + "tracing-test-macro 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracing-test" +version = "0.2.5" +source = "git+https://github.com/Frando/tracing-test?branch=feat%2Fcolor-and-filter-on-cli#ea24bef699ef9770602636d72ce1bf76fe4fb0aa" +dependencies = [ + "tracing-core", + "tracing-subscriber", + "tracing-test-macro 0.2.5 (git+https://github.com/Frando/tracing-test?branch=feat%2Fcolor-and-filter-on-cli)", ] [[package]] @@ -4802,6 +4874,15 @@ dependencies = [ "syn", ] +[[package]] +name = "tracing-test-macro" +version = "0.2.5" +source = "git+https://github.com/Frando/tracing-test?branch=feat%2Fcolor-and-filter-on-cli#ea24bef699ef9770602636d72ce1bf76fe4fb0aa" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -4831,15 +4912,15 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-width" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "unicode-xid" @@ -4899,7 +4980,7 @@ version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.3.3", "js-sys", "wasm-bindgen", ] @@ -4950,6 +5031,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -4961,9 +5051,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", @@ -4972,11 +5062,25 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + [[package]] name = "wasm-bindgen-futures" -version = "0.4.55" +version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", @@ -4987,9 +5091,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4997,31 +5101,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ - "bumpalo", "proc-macro2", "quote", "syn", + "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.55" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc379bfb624eb59050b509c13e77b4eb53150c350db69628141abce842f2373" +checksum = "4e381134e148c1062f965a42ed1f5ee933eef2927c3f70d1812158f711d39865" dependencies = [ "js-sys", "minicov", @@ -5032,9 +5136,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.55" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "085b2df989e1e6f9620c1311df6c996e83fe16f57792b272ce1e024ac16a90f1" +checksum = "b673bca3298fe582aeef8352330ecbad91849f85090805582400850f8270a2e8" dependencies = [ "proc-macro2", "quote", @@ -5056,9 +5160,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", @@ -5080,14 +5184,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" dependencies = [ - "webpki-root-certs 1.0.3", + "webpki-root-certs 1.0.2", ] [[package]] name = "webpki-root-certs" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d651ec480de84b762e7be71e6efa7461699c19d9e2c272c8d93455f567786e" +checksum = "4e4ffd8df1c57e87c325000a3d6ef93db75279dc3a231125aac571650f22b12a" dependencies = [ "rustls-pki-types", ] @@ -5103,9 +5207,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" [[package]] name = "winapi" @@ -5129,7 +5233,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -5361,14 +5465,14 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.5", + "windows-targets 0.53.4", ] [[package]] name = "windows-sys" -version = "0.61.2" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" dependencies = [ "windows-link 0.2.1", ] @@ -5421,19 +5525,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.5" +version = "0.53.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" dependencies = [ "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -5474,9 +5578,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" @@ -5498,9 +5602,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" @@ -5522,9 +5626,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] name = "windows_i686_gnullvm" @@ -5534,9 +5638,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" @@ -5558,9 +5662,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" @@ -5582,9 +5686,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" @@ -5606,9 +5710,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" @@ -5630,9 +5734,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" @@ -5676,9 +5780,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "ws_stream_wasm" @@ -5719,9 +5823,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.28" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" +checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" [[package]] name = "xmltree" @@ -5749,10 +5853,11 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ + "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -5760,9 +5865,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -5839,9 +5944,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ "displaydoc", "yoke", @@ -5850,9 +5955,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", @@ -5861,9 +5966,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 89721a6363..e7859a7576 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,19 +43,17 @@ unused-async = "warn" [patch.crates-io] -iroh-quinn = { git = "https://github.com/n0-computer/quinn", branch = "main-iroh" } -iroh-quinn-proto = { git = "https://github.com/n0-computer/quinn", branch = "main-iroh" } -iroh-quinn-udp = { git = "https://github.com/n0-computer/quinn", branch = "main-iroh" } +iroh-quinn = { git = "https://github.com/n0-computer/quinn", branch = "protocol-simplification" } +iroh-quinn-proto = { git = "https://github.com/n0-computer/quinn", branch = "protocol-simplification" } +iroh-quinn-udp = { git = "https://github.com/n0-computer/quinn", branch = "protocol-simplification" } netwatch = { git = "https://github.com/n0-computer/net-tools", branch = "main" } -# iroh-quinn = { path = "../iroh-quinn/quinn" } -# iroh-quinn-proto = { path = "../iroh-quinn/quinn-proto" } -# iroh-quinn-udp = { path = "../iroh-quinn/quinn-udp" } +# iroh-quinn = { path = "../quinn/quinn" } +# iroh-quinn-proto = { path = "../quinn/quinn-proto" } +# iroh-quinn-udp = { path = "../quinn/quinn-udp" } - -[patch."https://github.com/n0-computer/quinn"] - -# iroh-quinn = { path = "../iroh-quinn/quinn" } -# iroh-quinn-proto = { path = "../iroh-quinn/quinn-proto" } -# iroh-quinn-udp = { path = "../iroh-quinn/quinn-udp" } +# [patch."https://github.com/n0-computer/quinn"] +# iroh-quinn = { path = "../quinn/quinn" } +# iroh-quinn-proto = { path = "../quinn/quinn-proto" } +# iroh-quinn-udp = { path = "../quinn/quinn-udp" } diff --git a/iroh-relay/Cargo.toml b/iroh-relay/Cargo.toml index 7a69c4a112..3b03936601 100644 --- a/iroh-relay/Cargo.toml +++ b/iroh-relay/Cargo.toml @@ -42,8 +42,8 @@ postcard = { version = "1", default-features = false, features = [ "use-std", "experimental-derive", ] } -quinn = { package = "iroh-quinn", git = "https://github.com/n0-computer/quinn", branch = "main-iroh", default-features = false, features = ["rustls-ring"] } -quinn-proto = { package = "iroh-quinn-proto", git = "https://github.com/n0-computer/quinn", branch = "main-iroh" } +quinn = { package = "iroh-quinn", git = "https://github.com/n0-computer/quinn", branch = "protocol-simplification", default-features = false, features = ["rustls-ring"] } +quinn-proto = { package = "iroh-quinn-proto", git = "https://github.com/n0-computer/quinn", branch = "protocol-simplification" } rand = "0.9.2" reqwest = { version = "0.12", default-features = false, features = [ "rustls-tls", diff --git a/iroh/Cargo.toml b/iroh/Cargo.toml index f06fff30bc..79db7270d7 100644 --- a/iroh/Cargo.toml +++ b/iroh/Cargo.toml @@ -37,9 +37,9 @@ n0-watcher = "0.6" netwatch = { version = "0.12" } pin-project = "1" pkarr = { version = "5", default-features = false, features = ["relays"] } -quinn = { package = "iroh-quinn", git = "https://github.com/n0-computer/quinn", branch = "main-iroh", default-features = false, features = ["rustls-ring"] } -quinn-proto = { package = "iroh-quinn-proto", git = "https://github.com/n0-computer/quinn", branch = "main-iroh" } -quinn-udp = { package = "iroh-quinn-udp", git = "https://github.com/n0-computer/quinn", branch = "main-iroh" } +quinn = { package = "iroh-quinn", git = "https://github.com/n0-computer/quinn", branch = "protocol-simplification", default-features = false, features = ["rustls-ring"] } +quinn-proto = { package = "iroh-quinn-proto", git = "https://github.com/n0-computer/quinn", branch = "protocol-simplification" } +quinn-udp = { package = "iroh-quinn-udp", git = "https://github.com/n0-computer/quinn", branch = "protocol-simplification" } rand = "0.9.2" reqwest = { version = "0.12", default-features = false, features = [ "rustls-tls", @@ -84,7 +84,7 @@ hickory-resolver = "0.25.1" igd-next = { version = "0.16", features = ["aio_tokio"] } netdev = { version = "0.39.0" } portmapper = { version = "0.12", default-features = false } -quinn = { package = "iroh-quinn", git = "https://github.com/n0-computer/quinn", branch = "main-iroh", default-features = false, features = ["runtime-tokio", "rustls-ring"] } +quinn = { package = "iroh-quinn", git = "https://github.com/n0-computer/quinn", branch = "protocol-simplification", default-features = false, features = ["runtime-tokio", "rustls-ring"] } tokio = { version = "1", features = [ "io-util", "macros", @@ -129,7 +129,8 @@ tokio = { version = "1", features = [ ] } serde_json = "1" iroh-relay = { path = "../iroh-relay", default-features = false, features = ["test-utils", "server"] } -tracing-test = "0.2.5" +# tracing-test = "0.2.5" +tracing-test = { git = "https://github.com/Frando/tracing-test", branch = "feat/color-and-filter-on-cli", features = ["pretty-log-printing"] } clap = { version = "4", features = ["derive"] } tracing-subscriber = { version = "0.3", features = [ "env-filter", diff --git a/iroh/bench/Cargo.toml b/iroh/bench/Cargo.toml index 086d1ebc31..8108a22c5e 100644 --- a/iroh/bench/Cargo.toml +++ b/iroh/bench/Cargo.toml @@ -12,7 +12,7 @@ iroh = { path = "..", default-features = false } iroh-metrics = { version = "0.37", optional = true } n0-future = "0.3.0" n0-error = "0.1.0" -quinn = { package = "iroh-quinn", git = "https://github.com/n0-computer/quinn", branch = "main-iroh" } +quinn = { package = "iroh-quinn", git = "https://github.com/n0-computer/quinn", branch = "protocol-simplification" } rand = "0.9.2" rcgen = "0.14" rustls = { version = "0.23.33", default-features = false, features = ["ring"] } diff --git a/iroh/src/disco.rs b/iroh/src/disco.rs deleted file mode 100644 index 3da109bd00..0000000000 --- a/iroh/src/disco.rs +++ /dev/null @@ -1,619 +0,0 @@ -//! Contains the discovery message types. -//! -//! A discovery message is: -//! -//! Header: -//! -//! ```ignore -//! magic: [u8; 6] // “TS💬” (0x54 53 f0 9f 92 ac) -//! sender_disco_pub: [u8; 32] // nacl public key -//! nonce: [u8; 24] -//! ```` -//! The recipient then decrypts the bytes following (the nacl secretbox) -//! and then the inner payload structure is: -//! -//! ```ignore -//! message_type: u8 // (the MessageType constants below) -//! message_version: u8 // (0 for now; but always ignore bytes at the end) -//! message_payload: &[u8] -//! ``` - -use std::{ - fmt::{self, Display}, - net::{IpAddr, SocketAddr}, -}; - -use data_encoding::HEXLOWER; -use iroh_base::{EndpointId, PublicKey, RelayUrl}; -use n0_error::{e, ensure, stack_error}; -use rand::Rng; -use serde::{Deserialize, Serialize}; -use url::Url; - -use crate::magicsock::transports; - -// TODO: custom magicn -/// The 6 byte header of all discovery messages. -pub const MAGIC: &str = "TS💬"; // 6 bytes: 0x54 53 f0 9f 92 ac -pub const MAGIC_LEN: usize = MAGIC.len(); - -/// Current Version. -const V0: u8 = 0; - -pub(crate) const KEY_LEN: usize = 32; -const TX_LEN: usize = 12; - -// Sizes for the inner message structure. - -/// Header: Type | Version -const HEADER_LEN: usize = 2; - -const PING_LEN: usize = TX_LEN + iroh_base::PublicKey::LENGTH; -const EP_LENGTH: usize = 16 + 2; // 16 byte IP address + 2 byte port - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(u8)] -pub enum MessageType { - Ping = 0x01, - Pong = 0x02, - CallMeMaybe = 0x03, -} - -impl TryFrom for MessageType { - type Error = u8; - - fn try_from(value: u8) -> std::result::Result { - match value { - 0x01 => Ok(MessageType::Ping), - 0x02 => Ok(MessageType::Pong), - 0x03 => Ok(MessageType::CallMeMaybe), - _ => Err(value), - } - } -} - -const MESSAGE_HEADER_LEN: usize = MAGIC_LEN + KEY_LEN; - -pub fn encode_message(sender: &PublicKey, seal: Vec) -> Vec { - let mut out = Vec::with_capacity(MESSAGE_HEADER_LEN); - out.extend_from_slice(MAGIC.as_bytes()); - out.extend_from_slice(sender.as_bytes()); - out.extend(seal); - - out -} - -/// Reports whether p looks like it's a packet containing an encrypted disco message. -pub fn looks_like_disco_wrapper(p: &[u8]) -> bool { - if p.len() < MESSAGE_HEADER_LEN { - return false; - } - - &p[..MAGIC_LEN] == MAGIC.as_bytes() -} - -/// If `p` looks like a disco message it returns the slice of `p` that represents the disco public key source, -/// and the part that is the box. -pub fn source_and_box(p: &[u8]) -> Option<(PublicKey, &[u8])> { - if !looks_like_disco_wrapper(p) { - return None; - } - - let source = &p[MAGIC_LEN..MAGIC_LEN + KEY_LEN]; - let sender = PublicKey::try_from(source).ok()?; - let sealed_box = &p[MAGIC_LEN + KEY_LEN..]; - Some((sender, sealed_box)) -} - -/// A discovery message. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Message { - Ping(Ping), - Pong(Pong), - CallMeMaybe(CallMeMaybe), -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Ping { - /// Random client-generated per-ping transaction ID. - pub tx_id: TransactionId, - - /// Allegedly the ping sender's public key. - /// - /// It shouldn't be trusted by itself. - pub endpoint_key: PublicKey, -} - -impl Ping { - /// Creates a ping message to ping `node_id`. - /// - /// Uses a randomly generated STUN transaction ID. - pub(crate) fn new(endpoint_id: EndpointId) -> Self { - Self { - tx_id: TransactionId::default(), - endpoint_key: endpoint_id, - } - } - - fn from_bytes(p: &[u8]) -> Result { - // Deliberately lax on longer-than-expected messages, for future compatibility. - ensure!(p.len() >= PING_LEN, ParseError::TooShort); - let tx_id: [u8; TX_LEN] = p[..TX_LEN].try_into().expect("length checked"); - let raw_key = &p[TX_LEN..TX_LEN + iroh_base::PublicKey::LENGTH]; - let endpoint_key = - PublicKey::try_from(raw_key).map_err(|_| e!(ParseError::InvalidEncoding))?; - let tx_id = TransactionId::from(tx_id); - - Ok(Ping { - tx_id, - endpoint_key, - }) - } - - fn as_bytes(&self) -> Vec { - let header = msg_header(MessageType::Ping, V0); - let mut out = vec![0u8; PING_LEN + HEADER_LEN]; - - out[..HEADER_LEN].copy_from_slice(&header); - out[HEADER_LEN..HEADER_LEN + TX_LEN].copy_from_slice(&self.tx_id); - out[HEADER_LEN + TX_LEN..].copy_from_slice(self.endpoint_key.as_ref()); - - out - } -} - -/// A response a Ping. -/// -/// It includes the sender's source IP + port, so it's effectively a STUN response. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Pong { - pub tx_id: TransactionId, - /// The observed address off the ping sender. - /// - /// 18 bytes (16+2) on the wire; v4-mapped ipv6 for IPv4. - pub ping_observed_addr: SendAddr, -} - -/// Addresses to which we can send. This is either a UDP or a relay address. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum SendAddr { - /// UDP, the ip addr. - Udp(SocketAddr), - /// Relay Url. - Relay(RelayUrl), -} - -impl From for SendAddr { - fn from(addr: transports::Addr) -> Self { - match addr { - transports::Addr::Ip(addr) => SendAddr::Udp(addr), - transports::Addr::Relay(url, _) => SendAddr::Relay(url), - } - } -} - -impl From for SendAddr { - fn from(source: SocketAddr) -> Self { - SendAddr::Udp(source) - } -} - -impl From for SendAddr { - fn from(source: RelayUrl) -> Self { - SendAddr::Relay(source) - } -} - -impl PartialEq for SendAddr { - fn eq(&self, other: &SocketAddr) -> bool { - match self { - Self::Relay(_) => false, - Self::Udp(addr) => addr.eq(other), - } - } -} - -impl Display for SendAddr { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - SendAddr::Relay(id) => write!(f, "Relay({id})"), - SendAddr::Udp(addr) => write!(f, "UDP({addr})"), - } - } -} - -/// Message sent only over the relay to request that the recipient try -/// to open up a magicsock path back to the sender. -/// -/// The sender should've already sent UDP packets to the peer to open -/// up the stateful firewall mappings inbound. -/// -/// The recipient may choose to not open a path back, if it's already happy with its path. -/// But usually it will. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct CallMeMaybe { - /// What the peer believes its endpoints are. - pub my_numbers: Vec, -} - -#[allow(missing_docs)] -#[stack_error(derive, add_meta)] -#[non_exhaustive] -pub enum ParseError { - #[error("message is too short")] - TooShort, - #[error("invalid encoding")] - InvalidEncoding, - #[error("unknown format")] - UnknownFormat, -} - -fn send_addr_from_bytes(p: &[u8]) -> Result { - ensure!(p.len() > 2, ParseError::TooShort); - match p[0] { - 0u8 => { - let bytes: [u8; EP_LENGTH] = p[1..].try_into().map_err(|_| e!(ParseError::TooShort))?; - let addr = socket_addr_from_bytes(bytes); - Ok(SendAddr::Udp(addr)) - } - 1u8 => { - let s = std::str::from_utf8(&p[1..]).map_err(|_| e!(ParseError::InvalidEncoding))?; - let u: Url = s.parse().map_err(|_| e!(ParseError::InvalidEncoding))?; - Ok(SendAddr::Relay(u.into())) - } - _ => Err(e!(ParseError::UnknownFormat)), - } -} - -fn send_addr_to_vec(addr: &SendAddr) -> Vec { - match addr { - SendAddr::Relay(url) => { - let mut out = vec![1u8]; - out.extend_from_slice(url.to_string().as_bytes()); - out - } - SendAddr::Udp(ip) => { - let mut out = vec![0u8]; - out.extend_from_slice(&socket_addr_as_bytes(ip)); - out - } - } -} - -// Assumes p.len() == EP_LENGTH -fn socket_addr_from_bytes(p: [u8; EP_LENGTH]) -> SocketAddr { - debug_assert_eq!(EP_LENGTH, 16 + 2); - - let raw_src_ip: [u8; 16] = p[..16].try_into().expect("array long enough"); - let raw_port: [u8; 2] = p[16..].try_into().expect("array long enough"); - - let src_ip = IpAddr::from(raw_src_ip).to_canonical(); - let src_port = u16::from_le_bytes(raw_port); - - SocketAddr::new(src_ip, src_port) -} - -fn socket_addr_as_bytes(addr: &SocketAddr) -> [u8; EP_LENGTH] { - let mut out = [0u8; EP_LENGTH]; - let ipv6 = match addr.ip() { - IpAddr::V4(v4) => v4.to_ipv6_mapped(), - IpAddr::V6(v6) => v6, - }; - out[..16].copy_from_slice(&ipv6.octets()); - out[16..].copy_from_slice(&addr.port().to_le_bytes()); - - out -} - -impl Pong { - fn from_bytes(p: &[u8]) -> Result { - let tx_id: [u8; TX_LEN] = p[..TX_LEN] - .try_into() - .map_err(|_| e!(ParseError::TooShort))?; - - let tx_id = TransactionId::from(tx_id); - let src = send_addr_from_bytes(&p[TX_LEN..])?; - - Ok(Pong { - tx_id, - ping_observed_addr: src, - }) - } - - fn as_bytes(&self) -> Vec { - let header = msg_header(MessageType::Pong, V0); - let mut out = header.to_vec(); - out.extend_from_slice(&self.tx_id); - - let src_bytes = send_addr_to_vec(&self.ping_observed_addr); - out.extend(src_bytes); - out - } -} - -impl CallMeMaybe { - fn from_bytes(p: &[u8]) -> Result { - ensure!(p.len() % EP_LENGTH == 0, ParseError::InvalidEncoding); - - let num_entries = p.len() / EP_LENGTH; - let mut m = CallMeMaybe { - my_numbers: Vec::with_capacity(num_entries), - }; - - for chunk in p.chunks_exact(EP_LENGTH) { - let bytes: [u8; EP_LENGTH] = chunk - .try_into() - .map_err(|_| e!(ParseError::InvalidEncoding))?; - let src = socket_addr_from_bytes(bytes); - m.my_numbers.push(src); - } - - Ok(m) - } - - fn as_bytes(&self) -> Vec { - let header = msg_header(MessageType::CallMeMaybe, V0); - let mut out = vec![0u8; HEADER_LEN + self.my_numbers.len() * EP_LENGTH]; - out[..HEADER_LEN].copy_from_slice(&header); - - for (m, chunk) in self - .my_numbers - .iter() - .zip(out[HEADER_LEN..].chunks_exact_mut(EP_LENGTH)) - { - let raw = socket_addr_as_bytes(m); - chunk.copy_from_slice(&raw); - } - - out - } -} - -impl Message { - /// Parses the encrypted part of the message from inside the nacl secretbox. - pub fn from_bytes(p: &[u8]) -> Result { - ensure!(p.len() >= 2, ParseError::TooShort); - - let t = MessageType::try_from(p[0]).map_err(|_| e!(ParseError::UnknownFormat))?; - let version = p[1]; - ensure!(version == V0, ParseError::UnknownFormat); - - let p = &p[2..]; - match t { - MessageType::Ping => { - let ping = Ping::from_bytes(p)?; - Ok(Message::Ping(ping)) - } - MessageType::Pong => { - let pong = Pong::from_bytes(p)?; - Ok(Message::Pong(pong)) - } - MessageType::CallMeMaybe => { - let cm = CallMeMaybe::from_bytes(p)?; - Ok(Message::CallMeMaybe(cm)) - } - } - } - - /// Serialize this message to bytes. - pub fn as_bytes(&self) -> Vec { - match self { - Message::Ping(ping) => ping.as_bytes(), - Message::Pong(pong) => pong.as_bytes(), - Message::CallMeMaybe(cm) => cm.as_bytes(), - } - } -} - -impl Display for Message { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Message::Ping(ping) => { - write!(f, "Ping(tx={})", HEXLOWER.encode(&ping.tx_id)) - } - Message::Pong(pong) => { - write!(f, "Pong(tx={})", HEXLOWER.encode(&pong.tx_id)) - } - Message::CallMeMaybe(_) => { - write!(f, "CallMeMaybe") - } - } - } -} - -const fn msg_header(t: MessageType, ver: u8) -> [u8; HEADER_LEN] { - [t as u8, ver] -} - -const TRANSACTION_ID_SIZE: usize = 12; - -/// The transaction ID is a 96-bit identifier -/// -/// It is used to uniquely identify STUN transactions. -/// It primarily serves to correlate requests with responses, -/// though it also plays a small role in helping to prevent -/// certain types of attacks. The server also uses the transaction ID as -/// a key to identify each transaction uniquely across all clients. -#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub(crate) struct TransactionId([u8; TRANSACTION_ID_SIZE]); - -impl fmt::Debug for TransactionId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "TransactionId(0x")?; - fmt_transcation_id(self.as_ref(), f) - } -} - -impl fmt::Display for TransactionId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "transaction id (0x")?; - fmt_transcation_id(self.as_ref(), f) - } -} - -fn fmt_transcation_id(bytes: &[u8], f: &mut fmt::Formatter) -> fmt::Result { - for byte in bytes { - write!(f, "{:02X}", byte)?; - } - write!(f, ")") -} - -impl std::ops::Deref for TransactionId { - type Target = [u8]; - - fn deref(&self) -> &[u8] { - &self.0 - } -} - -impl AsRef<[u8]> for TransactionId { - fn as_ref(&self) -> &[u8] { - &self.0[..] - } -} - -impl From<&[u8; TRANSACTION_ID_SIZE]> for TransactionId { - fn from(buff: &[u8; TRANSACTION_ID_SIZE]) -> Self { - Self(*buff) - } -} - -impl From<[u8; TRANSACTION_ID_SIZE]> for TransactionId { - fn from(buff: [u8; TRANSACTION_ID_SIZE]) -> Self { - Self(buff) - } -} - -impl rand::distr::Distribution for rand::distr::StandardUniform { - fn sample(&self, rng: &mut R) -> TransactionId { - let mut buffer = [0u8; TRANSACTION_ID_SIZE]; - rng.fill_bytes(&mut buffer); - TransactionId::from(buffer) - } -} - -impl Default for TransactionId { - /// Creates a cryptographically random transaction ID chosen from the interval 0 .. 2**96-1. - fn default() -> Self { - let mut rng = rand::rng(); - rng.random() - } -} - -#[cfg(test)] -mod tests { - use iroh_base::SecretKey; - use rand::SeedableRng; - - use super::*; - use crate::key::{SharedSecret, public_ed_box, secret_ed_box}; - - #[test] - fn test_to_from_bytes() { - struct Test { - name: &'static str, - m: Message, - want: &'static str, - } - let tests = [ - Test { - name: "ping_with_endpointkey_src", - m: Message::Ping(Ping { - tx_id: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].into(), - endpoint_key: PublicKey::try_from( - &[ - 190, 243, 65, 104, 37, 102, 175, 75, 243, 22, 69, 200, 167, 107, 24, - 63, 216, 140, 120, 43, 4, 112, 16, 62, 117, 155, 45, 215, 72, 175, 40, - 189, - ][..], - ) - .unwrap(), - }), - want: "01 00 01 02 03 04 05 06 07 08 09 0a 0b 0c be f3 41 68 25 66 af 4b f3 16 45 c8 a7 6b 18 3f d8 8c 78 2b 04 70 10 3e 75 9b 2d d7 48 af 28 bd", - }, - Test { - name: "pong", - m: Message::Pong(Pong { - tx_id: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].into(), - ping_observed_addr: SendAddr::Udp("2.3.4.5:1234".parse().unwrap()), - }), - want: "02 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 00 00 00 00 00 00 00 00 00 00 00 ff ff 02 03 04 05 d2 04", - }, - Test { - name: "pongv6", - m: Message::Pong(Pong { - tx_id: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].into(), - ping_observed_addr: SendAddr::Udp("[fed0::12]:6666".parse().unwrap()), - }), - want: "02 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 00 fe d0 00 00 00 00 00 00 00 00 00 00 00 00 00 12 0a 1a", - }, - Test { - name: "call_me_maybe", - m: Message::CallMeMaybe(CallMeMaybe { - my_numbers: Vec::new(), - }), - want: "03 00", - }, - Test { - name: "call_me_maybe_endpoints", - m: Message::CallMeMaybe(CallMeMaybe { - my_numbers: vec![ - "1.2.3.4:567".parse().unwrap(), - "[2001::3456]:789".parse().unwrap(), - ], - }), - want: "03 00 00 00 00 00 00 00 00 00 00 00 ff ff 01 02 03 04 37 02 20 01 00 00 00 00 00 00 00 00 00 00 00 00 34 56 15 03", - }, - ]; - for test in tests { - println!("{}", test.name); - - let got = test.m.as_bytes(); - assert_eq!( - got, - data_encoding::HEXLOWER - .decode(test.want.replace(' ', "").as_bytes()) - .unwrap(), - "wrong as_bytes" - ); - - let back = Message::from_bytes(&got).expect("failed to parse"); - assert_eq!(test.m, back, "wrong from_bytes"); - } - } - - #[test] - fn test_extraction() { - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); - let sender_key = SecretKey::generate(&mut rng); - let recv_key = SecretKey::generate(&mut rng); - - let msg = Message::Ping(Ping { - tx_id: TransactionId::default(), - endpoint_key: sender_key.public(), - }); - - let sender_secret = secret_ed_box(&sender_key); - let shared = SharedSecret::new(&sender_secret, &public_ed_box(&recv_key.public())); - let mut seal = msg.as_bytes(); - shared.seal(&mut seal); - - let bytes = encode_message(&sender_key.public(), seal.clone()); - - assert!(looks_like_disco_wrapper(&bytes)); - assert_eq!(source_and_box(&bytes).unwrap().0, sender_key.public()); - - let (raw_key, seal_back) = source_and_box(&bytes).unwrap(); - assert_eq!(raw_key, sender_key.public()); - assert_eq!(seal_back, seal); - - let recv_secret = secret_ed_box(&recv_key); - let shared_recv = SharedSecret::new(&recv_secret, &public_ed_box(&sender_key.public())); - let mut open_seal = seal_back.to_vec(); - shared_recv - .open(&mut open_seal) - .expect("failed to open seal_back"); - let msg_back = Message::from_bytes(&open_seal).unwrap(); - assert_eq!(msg_back, msg); - } -} diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index b608091411..0bff432fc1 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -174,7 +174,9 @@ impl Builder { self.transport_config .default_path_max_idle_timeout(Some(PATH_MAX_IDLE_TIMEOUT)); self.transport_config - .max_concurrent_multipath_paths(MAX_MULTIPATH_PATHS); + .max_concurrent_multipath_paths(MAX_MULTIPATH_PATHS + 1); + self.transport_config + .set_max_remote_nat_traversal_addresses(MAX_MULTIPATH_PATHS as u8); let static_config = StaticConfig { transport_config: Arc::new(self.transport_config), diff --git a/iroh/src/key.rs b/iroh/src/key.rs deleted file mode 100644 index c6b9f6ee48..0000000000 --- a/iroh/src/key.rs +++ /dev/null @@ -1,158 +0,0 @@ -//! The private and public keys of an endpoint. - -use std::fmt::Debug; - -use aead::{AeadCore, AeadInOut, Buffer}; -use iroh_base::{PublicKey, SecretKey}; -use n0_error::{e, ensure, stack_error}; - -pub(crate) const NONCE_LEN: usize = 24; - -const AEAD_DATA: &[u8] = &[]; - -pub(super) fn public_ed_box(key: &PublicKey) -> crypto_box::PublicKey { - let key = key.as_verifying_key(); - crypto_box::PublicKey::from(key.to_montgomery()) -} - -pub(super) fn secret_ed_box(key: &SecretKey) -> crypto_box::SecretKey { - let key = key.as_signing_key(); - crypto_box::SecretKey::from(key.to_scalar()) -} - -/// Shared Secret. -pub struct SharedSecret(crypto_box::ChaChaBox); - -/// Errors that can occur during [`SharedSecret::open`]. -#[stack_error(derive, add_meta, from_sources, std_sources)] -#[non_exhaustive] -pub enum DecryptionError { - /// The nonce had the wrong size. - #[error("Invalid nonce")] - InvalidNonce, - /// AEAD decryption failed. - #[error("Aead error")] - Aead { - #[error(std_err)] - source: aead::Error, - }, -} - -impl Debug for SharedSecret { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "SharedSecret(crypto_box::ChaChaBox)") - } -} - -impl SharedSecret { - pub fn new(this: &crypto_box::SecretKey, other: &crypto_box::PublicKey) -> Self { - SharedSecret(crypto_box::ChaChaBox::new_from_clamped(other, this)) - } - - /// Seals the provided cleartext. - pub fn seal(&self, buffer: &mut dyn Buffer) { - let nonce = crypto_box::ChaChaBox::try_generate_nonce_with_rng(&mut rand::rng()) - .expect("not enough randomness"); - - self.0 - .encrypt_in_place(&nonce, AEAD_DATA, buffer) - .expect("encryption failed"); - - buffer.extend_from_slice(&nonce).expect("buffer too small"); - } - - /// Opens the ciphertext, which must have been created using `Self::seal`, and places the clear text into the provided buffer. - pub fn open(&self, buffer: &mut dyn Buffer) -> Result<(), DecryptionError> { - ensure!(buffer.len() >= NONCE_LEN, DecryptionError::InvalidNonce); - - let offset = buffer.len() - NONCE_LEN; - let nonce: [u8; NONCE_LEN] = buffer.as_ref()[offset..] - .try_into() - .map_err(|_| e!(DecryptionError::InvalidNonce))?; - - buffer.truncate(offset); - self.0.decrypt_in_place(&nonce.into(), AEAD_DATA, buffer)?; - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use rand::SeedableRng; - - use super::*; - - fn shared(this: &iroh_base::SecretKey, other: &iroh_base::PublicKey) -> SharedSecret { - let secret_key = secret_ed_box(this); - let public_key = public_ed_box(other); - - SharedSecret::new(&secret_key, &public_key) - } - - #[test] - fn test_seal_open_roundtrip() { - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); - let key_a = iroh_base::SecretKey::generate(&mut rng); - let key_b = iroh_base::SecretKey::generate(&mut rng); - - println!("a -> a"); - seal_open_roundtrip(&key_a, &key_a); - println!("b -> b"); - seal_open_roundtrip(&key_b, &key_b); - - println!("a -> b"); - seal_open_roundtrip(&key_a, &key_b); - println!("b -> a"); - seal_open_roundtrip(&key_b, &key_a); - } - - fn seal_open_roundtrip(key_a: &iroh_base::SecretKey, key_b: &iroh_base::SecretKey) { - let msg = b"super secret message!!!!".to_vec(); - let shared_a = shared(key_a, &key_b.public()); - let mut sealed_message = msg.clone(); - shared_a.seal(&mut sealed_message); - - let shared_b = shared(key_b, &key_a.public()); - let mut decrypted_message = sealed_message.clone(); - shared_b.open(&mut decrypted_message).unwrap(); - assert_eq!(&msg[..], &decrypted_message); - } - - #[test] - fn test_roundtrip_public_key() { - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); - let key = crypto_box::SecretKey::generate(&mut rng); - let public_bytes = *key.public_key().as_bytes(); - let public_key_back = crypto_box::PublicKey::from(public_bytes); - assert_eq!(key.public_key(), public_key_back); - } - - #[test] - fn test_same_public_key_api() { - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); - let key = iroh_base::SecretKey::generate(&mut rng); - let public_key1: crypto_box::PublicKey = public_ed_box(&key.public()); - let public_key2: crypto_box::PublicKey = secret_ed_box(&key).public_key(); - - assert_eq!(public_key1, public_key2); - } - - #[test] - fn test_same_public_key_low_level() { - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); - let key = ed25519_dalek::SigningKey::generate(&mut rng); - let public_key1 = { - let m = key.verifying_key().to_montgomery(); - crypto_box::PublicKey::from(m) - }; - - let public_key2 = { - let s = key.to_scalar(); - let cs = crypto_box::SecretKey::from(s); - cs.public_key() - }; - - assert_eq!(public_key1, public_key2); - } -} diff --git a/iroh/src/lib.rs b/iroh/src/lib.rs index b6504381d6..a2be352d20 100644 --- a/iroh/src/lib.rs +++ b/iroh/src/lib.rs @@ -253,8 +253,6 @@ #![cfg_attr(not(test), deny(clippy::unwrap_used))] #![cfg_attr(iroh_docsrs, feature(doc_cfg))] -mod disco; -mod key; mod magicsock; mod tls; diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 6f97fa3e81..ad5c5c0c8a 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -16,7 +16,7 @@ //! however, read any packets that come off the UDP sockets. use std::{ - collections::{BTreeMap, BTreeSet, HashMap}, + collections::{BTreeMap, BTreeSet}, fmt::Display, io, net::{IpAddr, SocketAddr}, @@ -26,7 +26,6 @@ use std::{ }, }; -use bytes::Bytes; use iroh_base::{EndpointAddr, EndpointId, PublicKey, RelayUrl, SecretKey, TransportAddr}; use iroh_relay::{RelayConfig, RelayMap}; use n0_error::{bail, e, stack_error}; @@ -49,7 +48,7 @@ use url::Url; use self::{ metrics::Metrics as MagicsockMetrics, remote_map::{RemoteMap, RemoteStateMessage}, - transports::{RelayActorConfig, Transports, TransportsSender}, + transports::{RelayActorConfig, Transports}, }; #[cfg(not(wasm_browser))] use crate::dns::DnsResolver; @@ -57,9 +56,7 @@ use crate::dns::DnsResolver; use crate::net_report::QuicConfig; use crate::{ defaults::timeouts::NET_REPORT_TIMEOUT, - disco::{self, SendAddr}, discovery::{ConcurrentDiscovery, Discovery, DiscoveryError, EndpointData, UserData}, - key::{DecryptionError, SharedSecret, public_ed_box, secret_ed_box}, magicsock::remote_map::PathsWatcher, metrics::EndpointMetrics, net_report::{self, IfStateDetails, Report}, @@ -97,7 +94,7 @@ pub(crate) const PATH_MAX_IDLE_TIMEOUT: Duration = Duration::from_millis(6500); /// Maximum number of concurrent QUIC multipath paths per connection. /// /// Pretty arbitrary and high right now. -pub(crate) const MAX_MULTIPATH_PATHS: u32 = 16; +pub(crate) const MAX_MULTIPATH_PATHS: u32 = 12; /// Error returned when the endpoint state actor stopped while waiting for a reply. #[stack_error(add_meta, derive)] @@ -203,9 +200,6 @@ pub(crate) struct MagicSock { dns_resolver: DnsResolver, relay_map: RelayMap, - /// Disco - disco: DiscoState, - // - Discovery /// Optional discovery service discovery: ConcurrentDiscovery, @@ -515,208 +509,59 @@ impl MagicSock { #[cfg(windows)] let dst_ip = None; - let mut quic_packets_total = 0; - // zip is slow :( for i in 0..metas.len() { let quinn_meta = &mut metas[i]; - let buf = &mut bufs[i]; let source_addr = &source_addrs[i]; - let mut buf_contains_quic_datagrams = false; - let mut quic_datagram_count = 0; + let datagram_count = quinn_meta.len.div_ceil(quinn_meta.stride); + self.metrics + .magicsock + .recv_datagrams + .inc_by(datagram_count as _); if quinn_meta.len > quinn_meta.stride { - trace!(%quinn_meta.len, %quinn_meta.stride, "GRO datagram received"); + trace!( + src = ?source_addr, + len = quinn_meta.len, + stride = %quinn_meta.stride, + datagram_count = quinn_meta.len.div_ceil(quinn_meta.stride), + "GRO datagram received", + ); self.metrics.magicsock.recv_gro_datagrams.inc(); + } else { + trace!(src = ?source_addr, len = quinn_meta.len, "datagram received"); } - - // Chunk through the datagrams in this GRO payload to find disco - // packets and forward them to the actor - for datagram in buf[..quinn_meta.len].chunks_mut(quinn_meta.stride) { - if datagram.len() < quinn_meta.stride { - trace!( - len = %datagram.len(), - %quinn_meta.stride, - "Last GRO datagram smaller than stride", - ); + match source_addr { + transports::Addr::Ip(SocketAddr::V4(..)) => { + self.metrics + .magicsock + .recv_data_ipv4 + .inc_by(quinn_meta.len as _); } - - // Detect DISCO datagrams and process them. Overwrite the first - // byte of those packets with zero to make Quinn ignore the packet. This - // relies on quinn::EndpointConfig::grease_quic_bit being set to `false`, - // which we do in Endpoint::bind. - if let Some((sender, sealed_box)) = disco::source_and_box(datagram) { - self.handle_disco_message(sender, sealed_box, source_addr); - datagram[0] = 0u8; - } else { - match source_addr { - transports::Addr::Ip(SocketAddr::V4(..)) => { - self.metrics - .magicsock - .recv_data_ipv4 - .inc_by(datagram.len() as _); - } - transports::Addr::Ip(SocketAddr::V6(..)) => { - self.metrics - .magicsock - .recv_data_ipv6 - .inc_by(datagram.len() as _); - } - transports::Addr::Relay(..) => { - self.metrics - .magicsock - .recv_data_relay - .inc_by(datagram.len() as _); - } - } - - quic_datagram_count += 1; - buf_contains_quic_datagrams = true; + transports::Addr::Ip(SocketAddr::V6(..)) => { + self.metrics + .magicsock + .recv_data_ipv6 + .inc_by(quinn_meta.len as _); } - } - - if buf_contains_quic_datagrams { - match source_addr { - #[cfg(wasm_browser)] - transports::Addr::Ip(_addr) => { - panic!("cannot use IP based addressing in the browser"); - } - #[cfg(not(wasm_browser))] - transports::Addr::Ip(_addr) => { - quic_packets_total += quic_datagram_count; - } - transports::Addr::Relay(src_url, src_endpoint) => { - let mapped_addr = self - .remote_map - .relay_mapped_addrs - .get(&(src_url.clone(), *src_endpoint)); - quinn_meta.addr = mapped_addr.private_socket_addr(); - } + transports::Addr::Relay(src_url, src_node) => { + self.metrics + .magicsock + .recv_data_relay + .inc_by(quinn_meta.len as _); + + // Fill in the correct mapped address + let mapped_addr = self + .remote_map + .relay_mapped_addrs + .get(&(src_url.clone(), *src_node)); + quinn_meta.addr = mapped_addr.private_socket_addr(); } - } else { - // If all datagrams in this buf are DISCO, set len to zero to make - // Quinn skip the buf completely. - quinn_meta.len = 0; } + // Normalize local_ip quinn_meta.dst_ip = dst_ip; } - - if quic_packets_total > 0 { - self.metrics - .magicsock - .recv_datagrams - .inc_by(quic_packets_total as _); - trace!("UDP recv: {} packets", quic_packets_total); - } - } - - /// Handles a discovery message. - #[instrument("disco_in", skip_all, fields(endpoint = %sender.fmt_short(), ?src))] - fn handle_disco_message(&self, sender: PublicKey, sealed_box: &[u8], src: &transports::Addr) { - if self.is_closed() { - return; - } - - if let transports::Addr::Relay(_, endpoint_id) = src { - if endpoint_id != &sender { - // TODO: return here? - warn!( - "Received relay disco message from connection for {}, but with message from {}", - endpoint_id.fmt_short(), - sender.fmt_short() - ); - } - } - - // We're now reasonably sure we're expecting communication from - // this endpoint, do the heavy crypto lifting to see what they want. - let dm = match self.disco.unseal_and_decode(sender, sealed_box) { - Ok(dm) => dm, - Err(DiscoBoxError::Open { source, .. }) => { - warn!(?source, "failed to open disco box"); - self.metrics.magicsock.recv_disco_bad_key.inc(); - return; - } - Err(DiscoBoxError::Parse { source, .. }) => { - // Couldn't parse it, but it was inside a correctly - // signed box, so just ignore it, assuming it's from a - // newer version of Tailscale that we don't - // understand. Not even worth logging about, lest it - // be too spammy for old clients. - - self.metrics.magicsock.recv_disco_bad_parse.inc(); - debug!(?source, "failed to parse disco message"); - return; - } - }; - - if src.is_relay() { - self.metrics.magicsock.recv_disco_relay.inc(); - } else { - self.metrics.magicsock.recv_disco_udp.inc(); - } - - trace!(?dm, "receive disco message"); - match dm { - disco::Message::Ping(ping) => { - self.metrics.magicsock.recv_disco_ping.inc(); - self.remote_map.handle_ping(ping, sender, src.clone()); - } - disco::Message::Pong(pong) => { - self.metrics.magicsock.recv_disco_pong.inc(); - self.remote_map.handle_pong(pong, sender, src.clone()); - } - disco::Message::CallMeMaybe(cm) => { - self.metrics.magicsock.recv_disco_call_me_maybe.inc(); - self.remote_map - .handle_call_me_maybe(cm, sender, src.clone()); - } - } - } - - /// Sends out a disco message. - async fn send_disco_message( - &self, - sender: &TransportsSender, - dst: SendAddr, - dst_key: PublicKey, - msg: disco::Message, - ) -> io::Result<()> { - let dst = match dst { - SendAddr::Udp(addr) => transports::Addr::Ip(addr), - SendAddr::Relay(url) => transports::Addr::Relay(url, dst_key), - }; - - trace!(?dst, %msg, "send disco message (UDP)"); - if self.is_closed() { - return Err(io::Error::new( - io::ErrorKind::NotConnected, - "connection closed", - )); - } - - let pkt = self.disco.encode_and_seal(dst_key, &msg); - - let transmit = transports::Transmit { - contents: &pkt, - ecn: None, - segment_size: None, - }; - - let dst2 = dst.clone(); - match sender.send(&dst2, None, &transmit).await { - Ok(()) => { - trace!(?dst, %msg, "sent disco message"); - self.metrics.magicsock.sent_disco_udp.inc(); - disco_message_sent(&msg, &self.metrics.magicsock); - Ok(()) - } - Err(err) => { - warn!(?dst, ?msg, ?err, "failed to send disco message"); - Err(err) - } - } } /// Publishes our address to a discovery service, if configured. @@ -995,14 +840,12 @@ impl Handle { .any(|addr| addr.is_ipv6()); let direct_addrs = DiscoveredDirectAddrs::default(); - let (disco, disco_receiver) = DiscoState::new(&secret_key); let remote_map = { RemoteMap::new( secret_key.public(), metrics.magicsock.clone(), direct_addrs.addrs.watch(), - disco.clone(), transports.create_sender(), discovery.clone(), ) @@ -1012,7 +855,6 @@ impl Handle { public_key: secret_key.public(), closing: AtomicBool::new(false), closed: AtomicBool::new(false), - disco, actor_sender: actor_sender.clone(), ipv6_reported, remote_map, @@ -1037,7 +879,6 @@ impl Handle { // the packet if grease_quic_bit is set to false. endpoint_config.grease_quic_bit(false); - let sender = transports.create_sender(); let local_addrs_watch = transports.local_addrs_watch(); let network_change_sender = transports.create_network_change_sender(); @@ -1110,8 +951,6 @@ impl Handle { direct_addr_update_state, network_change_sender, direct_addr_done_rx, - pending_call_me_maybes: Default::default(), - disco_receiver, }; // Initialize addresses #[cfg(not(wasm_browser))] @@ -1119,7 +958,7 @@ impl Handle { let actor_task = task::spawn( actor - .run(shutdown_token.child_token(), local_addrs_watch, sender) + .run(shutdown_token.child_token(), local_addrs_watch) .instrument(info_span!("actor")), ); @@ -1212,82 +1051,6 @@ fn default_quic_client_config() -> rustls::ClientConfig { .with_no_client_auth() } -#[derive(Debug, Clone)] -struct DiscoState { - /// The EndpointId/PublikeKey of this endpoint. - this_id: EndpointId, - /// Encryption key for this endpoint. - secret_encryption_key: Arc, - /// The state for an active DiscoKey. - secrets: Arc>>, - /// Disco (ping) queue - sender: mpsc::Sender<(SendAddr, PublicKey, disco::Message)>, -} - -impl DiscoState { - fn new( - secret_key: &SecretKey, - ) -> (Self, mpsc::Receiver<(SendAddr, PublicKey, disco::Message)>) { - let this_id = secret_key.public(); - let secret_encryption_key = secret_ed_box(secret_key); - let (disco_sender, disco_receiver) = mpsc::channel(256); - - ( - Self { - this_id, - secret_encryption_key: Arc::new(secret_encryption_key), - secrets: Default::default(), - sender: disco_sender, - }, - disco_receiver, - ) - } - - fn try_send(&self, dst: SendAddr, dst_key: PublicKey, msg: disco::Message) -> bool { - self.sender.try_send((dst, dst_key, msg)).is_ok() - } - - fn encode_and_seal(&self, other_key: PublicKey, msg: &disco::Message) -> Bytes { - let mut seal = msg.as_bytes(); - self.get_secret(other_key, |secret| secret.seal(&mut seal)); - disco::encode_message(&self.this_id, seal).into() - } - - fn unseal_and_decode( - &self, - endpoint_key: PublicKey, - sealed_box: &[u8], - ) -> Result { - let mut sealed_box = sealed_box.to_vec(); - self.get_secret(endpoint_key, |secret| secret.open(&mut sealed_box)) - .map_err(|source| e!(DiscoBoxError::Open { source }))?; - disco::Message::from_bytes(&sealed_box) - .map_err(|source| e!(DiscoBoxError::Parse { source })) - } - - fn get_secret(&self, endpoint_id: PublicKey, cb: F) -> T - where - F: FnOnce(&mut SharedSecret) -> T, - { - let mut inner = self.secrets.lock().expect("poisoned"); - let x = inner.entry(endpoint_id).or_insert_with(|| { - let public_key = public_ed_box(&endpoint_id); - SharedSecret::new(&self.secret_encryption_key, &public_key) - }); - cb(x) - } -} - -#[allow(missing_docs)] -#[stack_error(derive, add_meta)] -#[non_exhaustive] -enum DiscoBoxError { - #[error("Failed to open crypto box")] - Open { source: DecryptionError }, - #[error("Failed to parse disco message")] - Parse { source: disco::ParseError }, -} - #[derive(Debug)] #[allow(clippy::enum_variant_names)] enum ActorMessage { @@ -1309,11 +1072,6 @@ struct Actor { /// Indicates the direct addr update state. direct_addr_update_state: DirectAddrUpdateState, direct_addr_done_rx: mpsc::Receiver<()>, - - /// List of CallMeMaybe disco messages that should be sent out after - /// the next endpoint update completes - pending_call_me_maybes: HashMap, - disco_receiver: mpsc::Receiver<(SendAddr, PublicKey, disco::Message)>, } impl Actor { @@ -1321,7 +1079,6 @@ impl Actor { mut self, shutdown_token: CancellationToken, mut watcher: impl Watcher> + Send + Sync, - sender: TransportsSender, ) { // Setup network monitoring let mut current_netmon_state = self.netmon_watcher.get(); @@ -1450,11 +1207,6 @@ impl Actor { self.msock.metrics.magicsock.actor_link_change.inc(); self.handle_network_change(is_major).await; } - Some((dst, dst_key, msg)) = self.disco_receiver.recv() => { - if let Err(err) = self.msock.send_disco_message(&sender, dst.clone(), dst_key, msg).await { - warn!(%dst, endpoint = %dst_key.fmt_short(), ?err, "failed to send disco message (UDP)"); - } - } _ = remote_map_gc.tick() => { self.msock.remote_map.remove_closed_remote_state_actors(); } @@ -1582,7 +1334,6 @@ impl Actor { }) .collect(), ); - self.send_queued_call_me_maybes(); } #[cfg(not(wasm_browser))] @@ -1649,22 +1400,6 @@ impl Actor { } } - fn send_queued_call_me_maybes(&mut self) { - let msg = self.msock.direct_addrs.to_call_me_maybe_message(); - let msg = disco::Message::CallMeMaybe(msg); - // allocate, to minimize locking duration - - for (public_key, url) in self.pending_call_me_maybes.drain() { - if !self - .msock - .disco - .try_send(SendAddr::Relay(url), public_key, msg.clone()) - { - warn!(endpoint = %public_key.fmt_short(), "relay channel full, dropping call-me-maybe"); - } - } - } - fn handle_net_report_report(&mut self, mut report: Option) { if let Some(ref mut r) = report { self.msock.ipv6_reported.store(r.udp_v6, Ordering::Relaxed); @@ -1734,25 +1469,6 @@ impl DiscoveredDirectAddrs { fn sockaddrs(&self) -> impl Iterator { self.addrs.get().into_iter().map(|da| da.addr) } - - fn to_call_me_maybe_message(&self) -> disco::CallMeMaybe { - let my_numbers = self.addrs.get().into_iter().map(|da| da.addr).collect(); - disco::CallMeMaybe { my_numbers } - } -} - -fn disco_message_sent(msg: &disco::Message, metrics: &MagicsockMetrics) { - match msg { - disco::Message::Ping(_) => { - metrics.sent_disco_ping.inc(); - } - disco::Message::Pong(_) => { - metrics.sent_disco_pong.inc(); - } - disco::Message::CallMeMaybe(_) => { - metrics.sent_disco_call_me_maybe.inc(); - } - } } /// A *direct address* on which an iroh-endpoint might be contactable. diff --git a/iroh/src/magicsock/metrics.rs b/iroh/src/magicsock/metrics.rs index e6f53d95c7..1bc9b9eb0b 100644 --- a/iroh/src/magicsock/metrics.rs +++ b/iroh/src/magicsock/metrics.rs @@ -27,27 +27,15 @@ pub struct Metrics { /// Number of datagrams received using GRO pub recv_gro_datagrams: Counter, - // Disco packets - pub send_disco_udp: Counter, - pub send_disco_relay: Counter, - pub sent_disco_udp: Counter, - pub sent_disco_relay: Counter, - pub sent_disco_ping: Counter, - pub sent_disco_pong: Counter, - pub sent_disco_call_me_maybe: Counter, - pub recv_disco_bad_key: Counter, - pub recv_disco_bad_parse: Counter, - - pub recv_disco_udp: Counter, - pub recv_disco_relay: Counter, - pub recv_disco_ping: Counter, - pub recv_disco_pong: Counter, - pub recv_disco_call_me_maybe: Counter, - pub recv_disco_call_me_maybe_bad_disco: Counter, - // How many times our relay home endpoint DI has changed from non-zero to a different non-zero. pub relay_home_change: Counter, + /* + * Holepunching metrics + */ + /// The number of NAT traversal attempts initiated. + pub nat_traversal: Counter, + /* * Connection Metrics */ diff --git a/iroh/src/magicsock/remote_map.rs b/iroh/src/magicsock/remote_map.rs index 76d8fdf0a4..007aaeef42 100644 --- a/iroh/src/magicsock/remote_map.rs +++ b/iroh/src/magicsock/remote_map.rs @@ -10,18 +10,17 @@ use iroh_base::{EndpointId, RelayUrl}; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use tokio::sync::mpsc; -use tracing::warn; pub(crate) use self::remote_state::PathsWatcher; pub(super) use self::remote_state::RemoteStateMessage; pub use self::remote_state::{PathInfo, PathInfoList}; use self::remote_state::{RemoteStateActor, RemoteStateHandle}; use super::{ - DirectAddr, DiscoState, MagicsockMetrics, + DirectAddr, MagicsockMetrics, mapped_addrs::{AddrMap, EndpointIdMappedAddr, RelayMappedAddr}, - transports::{self, TransportsSender}, + transports::TransportsSender, }; -use crate::{disco, discovery::ConcurrentDiscovery}; +use crate::discovery::ConcurrentDiscovery; mod remote_state; @@ -59,7 +58,6 @@ pub(crate) struct RemoteMap { metrics: Arc, /// The "direct" addresses known for our local endpoint local_direct_addrs: n0_watcher::Direct>, - disco: DiscoState, sender: TransportsSender, discovery: ConcurrentDiscovery, } @@ -69,9 +67,7 @@ impl RemoteMap { pub(super) fn new( local_endpoint_id: EndpointId, metrics: Arc, - local_direct_addrs: n0_watcher::Direct>, - disco: DiscoState, sender: TransportsSender, discovery: ConcurrentDiscovery, ) -> Self { @@ -82,7 +78,6 @@ impl RemoteMap { local_endpoint_id, metrics, local_direct_addrs, - disco, sender, discovery, } @@ -140,7 +135,6 @@ impl RemoteMap { eid, self.local_endpoint_id, self.local_direct_addrs.clone(), - self.disco.clone(), self.relay_mapped_addrs.clone(), self.metrics.clone(), self.sender.clone(), @@ -150,46 +144,6 @@ impl RemoteMap { let sender = handle.sender.get().expect("just created"); (handle, sender) } - - pub(super) fn handle_ping(&self, msg: disco::Ping, sender: EndpointId, src: transports::Addr) { - if msg.endpoint_key != sender { - warn!("DISCO Ping EndpointId mismatch, ignoring ping"); - return; - } - let remote_state = self.remote_state_actor(sender); - if let Err(err) = remote_state.try_send(RemoteStateMessage::PingReceived(msg, src)) { - // TODO: This is really, really bad and will drop pings under load. But - // DISCO pings are going away with QUIC-NAT-TRAVERSAL so I don't care. - warn!("DISCO Ping dropped: {err:#}"); - } - } - - pub(super) fn handle_pong(&self, msg: disco::Pong, sender: EndpointId, src: transports::Addr) { - let actor = self.remote_state_actor(sender); - if let Err(err) = actor.try_send(RemoteStateMessage::PongReceived(msg, src)) { - // TODO: This is really, really bad and will drop pongs under load. But - // DISCO pongs are going away with QUIC-NAT-TRAVERSAL so I don't care. - warn!("DISCO Pong dropped: {err:#}"); - } - } - - pub(super) fn handle_call_me_maybe( - &self, - msg: disco::CallMeMaybe, - sender: EndpointId, - src: transports::Addr, - ) { - if !src.is_relay() { - warn!("DISCO CallMeMaybe packets should only come via relay"); - return; - } - let actor = self.remote_state_actor(sender); - if let Err(err) = actor.try_send(RemoteStateMessage::CallMeMaybeReceived(msg)) { - // TODO: This is bad and will drop call-me-maybe's under load. But - // DISCO CallMeMaybe going away with QUIC-NAT-TRAVERSAL so I don't care. - warn!("DISCO CallMeMaybe dropped: {err:#}"); - } - } } /// The origin or *source* through which an address associated with a remote endpoint @@ -236,11 +190,6 @@ pub enum Source { _0: Private, }, /// We established a connection on this address. - /// - /// Currently this means the path was in uses as [`PathId::ZERO`] when the a connection - /// was added to the `RemoteStateActor`. - /// - /// [`PathId::ZERO`]: quinn_proto::PathId::ZERO #[strum(serialize = "Connection")] Connection { /// private marker diff --git a/iroh/src/magicsock/remote_map/remote_state.rs b/iroh/src/magicsock/remote_map/remote_state.rs index 6fda36e3c9..1521853118 100644 --- a/iroh/src/magicsock/remote_map/remote_state.rs +++ b/iroh/src/magicsock/remote_map/remote_state.rs @@ -16,7 +16,7 @@ use n0_future::{ }; use n0_watcher::{Watchable, Watcher}; use quinn::{PathStats, WeakConnectionHandle}; -use quinn_proto::{PathError, PathEvent, PathId, PathStatus}; +use quinn_proto::{PathError, PathEvent, PathId, PathStatus, iroh_hp}; use rustc_hash::FxHashMap; use smallvec::SmallVec; use sync_wrapper::SyncStream; @@ -30,11 +30,10 @@ use self::{ }; use super::Source; use crate::{ - disco::{self}, discovery::{ConcurrentDiscovery, Discovery, DiscoveryError, DiscoveryItem}, endpoint::DirectAddr, magicsock::{ - DiscoState, HEARTBEAT_INTERVAL, MagicsockMetrics, PATH_MAX_IDLE_TIMEOUT, + HEARTBEAT_INTERVAL, MagicsockMetrics, PATH_MAX_IDLE_TIMEOUT, mapped_addrs::{AddrMap, MappedAddr, RelayMappedAddr}, remote_map::Private, transports::{self, OwnedTransmit, TransportsSender}, @@ -42,19 +41,15 @@ use crate::{ util::MaybeFuture, }; +/// How often to attempt holepunching. +/// +/// If there have been no changes to the NAT address candidates, holepunching will not be +/// attempted more frequently than at this interval. +const HOLEPUNCH_ATTEMPTS_INTERVAL: Duration = Duration::from_secs(5); + mod guarded_channel; mod path_state; -// TODO: use this -// /// Number of addresses that are not active that we keep around per endpoint. -// /// -// /// See [`RemoteState::prune_direct_addresses`]. -// pub(super) const MAX_INACTIVE_DIRECT_ADDRESSES: usize = 20; - -// TODO: use this -// /// How long since an endpoint path was last alive before it might be pruned. -// const LAST_ALIVE_PRUNE_DURATION: Duration = Duration::from_secs(120); - // TODO: use this // /// The latency at or under which we don't try to upgrade to a better path. // const GOOD_ENOUGH_LATENCY: Duration = Duration::from_millis(5); @@ -93,6 +88,19 @@ type PathEvents = MergeUnbounded< >, >; +/// A stream of events of announced NAT traversal candidate addresses for all connections. +/// +/// The connection is identified using [`ConnId`]. +type AddrEvents = MergeUnbounded< + Pin< + Box< + dyn Stream)> + + Send + + Sync, + >, + >, +>; + /// Either a stream of incoming results from [`ConcurrentDiscovery::resolve`] or infinitely pending. /// /// Set to [`Either::Left`] with an always-pending stream while discovery is not running, and to @@ -126,10 +134,7 @@ pub(super) struct RemoteStateActor { /// Our local addresses. /// /// These are our local addresses and any reflexive transport addresses. - /// They are called "direct addresses" in the magic socket actor. local_direct_addrs: n0_watcher::Direct>, - /// Shared state to allow to encrypt DISCO messages to peers. - disco: DiscoState, /// The mapping between endpoints via a relay and their [`RelayMappedAddr`]s. relay_mapped_addrs: AddrMap<(RelayUrl, EndpointId), RelayMappedAddr>, /// Discovery service, cloned from the magicsock. @@ -143,6 +148,8 @@ pub(super) struct RemoteStateActor { connections_close: FuturesUnordered, /// Events emitted by Quinn about path changes, for all paths, all connections. path_events: PathEvents, + /// A stream of events of announced NAT traversal candidate addresses for all connections. + addr_events: AddrEvents, // Internal state - Holepunching and path state. // @@ -183,7 +190,6 @@ impl RemoteStateActor { endpoint_id: EndpointId, local_endpoint_id: EndpointId, local_direct_addrs: n0_watcher::Direct>, - disco: DiscoState, relay_mapped_addrs: AddrMap<(RelayUrl, EndpointId), RelayMappedAddr>, metrics: Arc, sender: TransportsSender, @@ -196,10 +202,10 @@ impl RemoteStateActor { local_direct_addrs, relay_mapped_addrs, discovery, - disco, connections: FxHashMap::default(), connections_close: Default::default(), path_events: Default::default(), + addr_events: Default::default(), paths: Default::default(), last_holepunch: None, selected_path: Default::default(), @@ -269,6 +275,10 @@ impl RemoteStateActor { Some((id, evt)) = self.path_events.next() => { self.handle_path_event(id, evt); } + Some((id, evt)) = self.addr_events.next() => { + trace!(?id, ?evt, "remote addrs updated, triggering holepunching"); + self.trigger_holepunching().await; + } Some(conn_id) = self.connections_close.next(), if !self.connections_close.is_empty() => { self.connections.remove(&conn_id); if self.connections.is_empty() { @@ -281,6 +291,7 @@ impl RemoteStateActor { trace!("direct address watcher disconnected, shutting down"); break; } + self.local_addrs_updated(); trace!("local addrs updated, triggering holepunching"); self.trigger_holepunching().await; } @@ -327,15 +338,6 @@ impl RemoteStateActor { RemoteStateMessage::AddConnection(handle, tx) => { self.handle_msg_add_connection(handle, tx).await; } - RemoteStateMessage::CallMeMaybeReceived(msg) => { - self.handle_msg_call_me_maybe_received(msg).await; - } - RemoteStateMessage::PingReceived(ping, src) => { - self.handle_msg_ping_received(ping, src).await; - } - RemoteStateMessage::PongReceived(pong, src) => { - self.handle_msg_pong_received(pong, src); - } RemoteStateMessage::ResolveRemote(addrs, tx) => { self.handle_msg_resolve_remote(addrs, tx); } @@ -419,11 +421,25 @@ impl RemoteStateActor { let conn_id = ConnId(conn.stable_id()); self.connections.remove(&conn_id); - // Store the connection and hook up paths events stream. - let events = BroadcastStream::new(conn.path_events()); - let stream = events.map(move |evt| (conn_id, evt)); - self.path_events.push(Box::pin(stream)); + // Hook up paths, NAT addresses and connection closed event streams. + self.path_events.push(Box::pin( + BroadcastStream::new(conn.path_events()).map(move |evt| (conn_id, evt)), + )); + self.addr_events.push(Box::pin( + BroadcastStream::new(conn.nat_traversal_updates()).map(move |evt| (conn_id, evt)), + )); self.connections_close.push(OnClosed::new(&conn)); + + // Add local addrs to the connection + let local_addrs = self + .local_direct_addrs + .get() + .iter() + .map(|d| d.addr) + .collect::>(); + Self::set_local_addrs(&conn, &local_addrs); + + // Store the connection let conn_state = self .connections .entry(conn_id) @@ -451,8 +467,10 @@ impl RemoteStateActor { path.set_status(status).ok(); conn_state.add_open_path(path_remote.clone(), PathId::ZERO); self.paths - .insert(path_remote, Source::Connection { _0: Private }); + .insert(path_remote.clone(), Source::Connection { _0: Private }); + self.paths.opened_path(&path_remote); self.select_path(); + self.prune_paths(); if path_remote_is_ip { // We may have raced this with a relay address. Try and add any @@ -478,81 +496,6 @@ impl RemoteStateActor { .ok(); } - /// Handles [`RemoteStateMessage::CallMeMaybeReceived`]. - async fn handle_msg_call_me_maybe_received(&mut self, msg: disco::CallMeMaybe) { - event!( - target: "iroh::_events::call_me_maybe::recv", - Level::DEBUG, - remote = %self.endpoint_id.fmt_short(), - addrs = ?msg.my_numbers, - ); - for addr in msg.my_numbers { - let dst = transports::Addr::Ip(addr); - let ping = disco::Ping::new(self.local_endpoint_id); - - self.paths - .insert(dst.clone(), Source::CallMeMaybe { _0: Private }); - self.paths.disco_ping_sent(dst.clone(), ping.tx_id); - - event!( - target: "iroh::_events::ping::sent", - Level::DEBUG, - remote = %self.endpoint_id.fmt_short(), - ?dst, - ); - self.send_disco_message(dst, disco::Message::Ping(ping)) - .await; - } - } - - /// Handles [`RemoteStateMessage::PingReceived`]. - async fn handle_msg_ping_received(&mut self, ping: disco::Ping, src: transports::Addr) { - let transports::Addr::Ip(addr) = src else { - warn!("received ping via relay transport, ignored"); - return; - }; - event!( - target: "iroh::_events::ping::recv", - Level::DEBUG, - remote = %self.endpoint_id.fmt_short(), - ?src, - txn = ?ping.tx_id, - ); - let pong = disco::Pong { - tx_id: ping.tx_id, - ping_observed_addr: addr.into(), - }; - event!( - target: "iroh::_events::pong::sent", - Level::DEBUG, - remote = %self.endpoint_id.fmt_short(), - dst = ?src, - txn = ?pong.tx_id, - ); - self.send_disco_message(src.clone(), disco::Message::Pong(pong)) - .await; - - self.paths.insert(src, Source::Ping { _0: Private }); - - trace!("ping received, triggering holepunching"); - self.trigger_holepunching().await; - } - - /// Handles [`RemoteStateMessage::PongReceived`]. - fn handle_msg_pong_received(&mut self, pong: disco::Pong, src: transports::Addr) { - if self.paths.disco_pong_received(&src, pong.tx_id) { - event!( - target: "iroh::_events::pong::recv", - Level::DEBUG, - remote_endpoint = %self.endpoint_id.fmt_short(), - ?src, - txn = ?pong.tx_id, - ); - - self.open_path(&src); - } - } - /// Handles [`RemoteStateMessage::ResolveRemote`]. fn handle_msg_resolve_remote( &mut self, @@ -564,6 +507,7 @@ impl RemoteStateActor { self.paths.resolve_remote(tx); // Start discovery if we have no selected path. self.trigger_discovery(); + self.prune_paths(); } /// Handles [`RemoteStateMessage::Latency`]. @@ -610,6 +554,7 @@ impl RemoteStateActor { let addrs = to_transports_addr(self.endpoint_id, item.into_endpoint_addr().addrs); self.paths.insert_multiple(addrs, source); + self.prune_paths(); } } } @@ -628,62 +573,101 @@ impl RemoteStateActor { } } + /// Sets the current local addresses to QNT's state to all connections + fn local_addrs_updated(&mut self) { + let local_addrs = self + .local_direct_addrs + .get() + .iter() + .map(|d| d.addr) + .collect::>(); + + for conn in self.connections.values().filter_map(|s| s.handle.upgrade()) { + Self::set_local_addrs(&conn, &local_addrs); + } + // todo: trace + } + + /// Sets the current local addresses to QNT's state + fn set_local_addrs(conn: &quinn::Connection, local_addrs: &BTreeSet) { + let quinn_local_addrs = match conn.get_local_nat_traversal_addresses() { + Ok(addrs) => BTreeSet::from_iter(addrs), + Err(err) => { + warn!("failed to get local nat candidates: {err:#}"); + return; + } + }; + for addr in local_addrs.difference(&quinn_local_addrs) { + if let Err(err) = conn.add_nat_traversal_address(*addr) { + warn!("failed adding local addr: {err:#}",); + } + } + for addr in quinn_local_addrs.difference(local_addrs) { + if let Err(err) = conn.remove_nat_traversal_address(*addr) { + warn!("failed removing local addr: {err:#}"); + } + } + trace!(?local_addrs, "updated local QNT addresses"); + } + /// Triggers holepunching to the remote endpoint. /// /// This will manage the entire process of holepunching with the remote endpoint. /// - /// - If there already is a direct connection, nothing happens. - /// - If there is no relay address known, nothing happens. - /// - If there was a recent attempt, it will schedule holepunching instead. - /// - Unless there are new addresses to try. - /// - The scheduled attempt will only run if holepunching has not yet succeeded by - /// then. - /// - DISCO pings will be sent to addresses recently advertised in a call-me-maybe - /// message. - /// - A DISCO call-me-maybe message advertising our own addresses will be sent. - /// - /// If a next trigger needs to be scheduled the delay until when to call this again is - /// returned. + /// - Holepunching happens on the Connection with the lowest [`ConnId`] which is a + /// client. + /// - Both endpoints may initiate holepunching if both have a client connection. + /// - Any opened paths are opened on all other connections without holepunching. + /// - If there are no changes in local or remote candidate addresses since the + /// last attempt **and** there was a recent attempt, a trigger_holepunching call + /// will be scheduled instead. async fn trigger_holepunching(&mut self) { - const HOLEPUNCH_ATTEMPTS_INTERVAL: Duration = Duration::from_secs(5); - if self.connections.is_empty() { trace!("not holepunching: no connections"); return; } - if self - .selected_path - .get() - .map(|addr| addr.is_ip()) - .unwrap_or_default() - { - // TODO: We should ping this path to make sure it still works. Because we now - // know things could be broken. - trace!("not holepunching: already have a direct connection"); - // TODO: If the latency is kind of bad we should retry holepunching at times. + let Some(conn) = self + .connections + .iter() + .filter_map(|(id, state)| state.handle.upgrade().map(|conn| (*id, conn))) + .filter(|(_, conn)| conn.side().is_client()) + .min_by_key(|(id, _)| *id) + .map(|(_, conn)| conn) + else { + trace!("not holepunching: no client connection"); return; - } - - let remote_addrs: BTreeSet = self.remote_hp_addrs(); - let local_addrs: BTreeSet = self + }; + let remote_candidates = match conn.get_remote_nat_traversal_addresses() { + Ok(addrs) => BTreeSet::from_iter(addrs), + Err(err) => { + warn!("failed to get nat candidate addresses: {err:#}"); + return; + } + }; + let local_candidates: BTreeSet = self .local_direct_addrs .get() .iter() .map(|daddr| daddr.addr) .collect(); - let new_addrs = self + let new_candidates = self .last_holepunch .as_ref() .map(|last_hp| { // Addrs are allowed to disappear, but if there are new ones we need to // holepunch again. - trace!(?last_hp, ?local_addrs, ?remote_addrs, "addrs to holepunch?"); - !remote_addrs.is_subset(&last_hp.remote_addrs) - || !local_addrs.is_subset(&last_hp.local_addrs) + trace!( + ?last_hp, + ?local_candidates, + ?remote_candidates, + "candidates to holepunch?" + ); + !remote_candidates.is_subset(&last_hp.remote_candidates) + || !local_candidates.is_subset(&last_hp.local_candidates) }) .unwrap_or(true); - if !new_addrs { + if !new_candidates { if let Some(ref last_hp) = self.last_holepunch { let next_hp = last_hp.when + HOLEPUNCH_ATTEMPTS_INTERVAL; let now = Instant::now(); @@ -695,114 +679,40 @@ impl RemoteStateActor { } } - self.do_holepunching().await; - } - - /// Returns the remote addresses to holepunch against. - fn remote_hp_addrs(&self) -> BTreeSet { - const CALL_ME_MAYBE_VALIDITY: Duration = Duration::from_secs(30); - - self.paths - .iter() - .filter_map(|(addr, state)| match addr { - transports::Addr::Ip(socket_addr) => Some((socket_addr, state)), - transports::Addr::Relay(_, _) => None, - }) - .filter_map(|(addr, state)| { - if state - .sources - .get(&Source::CallMeMaybe { _0: Private }) - .map(|when| when.elapsed() <= CALL_ME_MAYBE_VALIDITY) - .unwrap_or_default() - || state - .sources - .get(&Source::Ping { _0: Private }) - .map(|when| when.elapsed() <= CALL_ME_MAYBE_VALIDITY) - .unwrap_or_default() - { - Some(*addr) - } else { - None - } - }) - .collect() + self.do_holepunching(conn).await; } /// Unconditionally perform holepunching. - /// - /// - DISCO pings will be sent to addresses recently advertised in a call-me-maybe - /// message. - /// - A DISCO call-me-maybe message advertising our own addresses will be sent. #[instrument(skip_all)] - async fn do_holepunching(&mut self) { - let Some(relay_addr) = self.paths.addrs().find(|addr| addr.is_relay()).cloned() else { - warn!("holepunching requested but have no relay address"); - return; - }; - let remote_addrs = self.remote_hp_addrs(); - - // Send DISCO Ping messages to all CallMeMaybe-advertised paths. - for dst in remote_addrs.iter() { - let msg = disco::Ping::new(self.local_endpoint_id); - event!( - target: "iroh::_events::ping::sent", - Level::DEBUG, - remote = %self.endpoint_id.fmt_short(), - ?dst, - txn = ?msg.tx_id, - ); - let addr = transports::Addr::Ip(*dst); - self.paths.disco_ping_sent(addr.clone(), msg.tx_id); - self.send_disco_message(addr, disco::Message::Ping(msg)) - .await; - } - - // Send the DISCO CallMeMaybe message over the relay. - let my_numbers: Vec = self + async fn do_holepunching(&mut self, conn: quinn::Connection) { + self.metrics.nat_traversal.inc(); + let local_candidates = self .local_direct_addrs .get() .iter() .map(|daddr| daddr.addr) - .collect(); - let local_addrs: BTreeSet = my_numbers.iter().copied().collect(); - let msg = disco::CallMeMaybe { my_numbers }; - event!( - target: "iroh::_events::call_me_maybe::sent", - Level::DEBUG, - remote = %self.endpoint_id.fmt_short(), - dst = ?relay_addr, - my_numbers = ?msg.my_numbers, - ); - self.send_disco_message(relay_addr, disco::Message::CallMeMaybe(msg)) - .await; - - self.last_holepunch = Some(HolepunchAttempt { - when: Instant::now(), - local_addrs, - remote_addrs, - }); - } - - /// Sends a DISCO message to the remote endpoint this actor manages. - #[instrument(skip(self), fields(remote = %self.endpoint_id.fmt_short()))] - async fn send_disco_message(&self, dst: transports::Addr, msg: disco::Message) { - let pkt = self.disco.encode_and_seal(self.endpoint_id, &msg); - let transmit = transports::OwnedTransmit { - ecn: None, - contents: pkt, - segment_size: None, - }; - let counter = match dst { - transports::Addr::Ip(_) => &self.metrics.send_disco_udp, - transports::Addr::Relay(_, _) => &self.metrics.send_disco_relay, - }; - match self.send_datagram(dst, transmit).await { - Ok(()) => { - trace!("sent"); - counter.inc(); + .collect::>(); + match conn.initiate_nat_traversal_round() { + Ok(remote_candidates) => { + let remote_candidates = remote_candidates + .iter() + .map(|addr| SocketAddr::new(addr.ip().to_canonical(), addr.port())) + .collect(); + event!( + target: "iroh::_events::qnt::init", + Level::DEBUG, + remote = %self.endpoint_id.fmt_short(), + ?local_candidates, + ?remote_candidates, + ); + self.last_holepunch = Some(HolepunchAttempt { + when: Instant::now(), + local_candidates, + remote_candidates, + }); } Err(err) => { - warn!("failed to send disco message: {err:#}"); + warn!("failed to initiate NAT traversal: {err:#}"); } } } @@ -902,15 +812,19 @@ impl RemoteStateActor { ); conn_state.add_open_path(path_remote.clone(), path_id); self.paths - .insert(path_remote, Source::Connection { _0: Private }); + .insert(path_remote.clone(), Source::Connection { _0: Private }); + self.paths.opened_path(&path_remote); } self.select_path(); + self.prune_paths(); } PathEvent::Abandoned { id, path_stats } => { trace!(?path_stats, "path abandoned"); // This is the last event for this path. - conn_state.remove_path(&id); + if let Some(addr) = conn_state.remove_path(&id) { + self.paths.abandoned_path(&addr); + } } PathEvent::Closed { id, .. } | PathEvent::LocallyClosed { id, .. } => { let Some(path_remote) = conn_state.paths.get(&id).cloned() else { @@ -1057,6 +971,12 @@ impl RemoteStateActor { } } } + + /// TODO: fix up docs once review indicates this is actually + /// the criteria for pruning. + fn prune_paths(&mut self) { + self.paths.prune_paths(); + } } /// Messages to send to the [`RemoteStateActor`]. @@ -1079,15 +999,7 @@ pub(crate) enum RemoteStateMessage { /// will be removed etc. #[debug("AddConnection(..)")] AddConnection(WeakConnectionHandle, oneshot::Sender), - /// Process a received DISCO CallMeMaybe message. - CallMeMaybeReceived(disco::CallMeMaybe), - /// Process a received DISCO Ping message. - #[debug("PingReceived({:?}, src: {_1:?})", _0.tx_id)] - PingReceived(disco::Ping, transports::Addr), - /// Process a received DISCO Pong message. - #[debug("PongReceived({:?}, src: {_1:?})", _0.tx_id)] - PongReceived(disco::Pong, transports::Addr), - /// Ensure we have at least one transport address for a remote. + /// Asks if there is any possible path that could be used. /// /// This adds the provided transport addresses to the list of potential paths for this remote /// and starts discovery if needed. @@ -1123,6 +1035,8 @@ pub(super) struct RemoteStateHandle { } /// Information about a holepunch attempt. +/// +/// Addresses are always stored in canonical form. #[derive(Debug)] struct HolepunchAttempt { when: Instant, @@ -1134,18 +1048,18 @@ struct HolepunchAttempt { /// /// We do not store this as a [`DirectAddr`] because this is checked for equality and we /// do not want to compare the sources of these addresses. - local_addrs: BTreeSet, + local_candidates: BTreeSet, /// The set of remote addresses which could take part in holepunching. /// - /// Like `local_addrs` we may not have used them. - remote_addrs: BTreeSet, + /// Like [`Self::local_candidates`] we may not have used them. + remote_candidates: BTreeSet, } /// Newtype to track Connections. /// /// The wrapped value is the [`quinn::Connection::stable_id`] value, and is thus only valid /// for active connections. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] struct ConnId(usize); /// State about one connection. @@ -1184,11 +1098,13 @@ impl ConnectionState { } /// Completely removes a path from this connection. - fn remove_path(&mut self, path_id: &PathId) { - if let Some(addr) = self.paths.remove(path_id) { - self.path_ids.remove(&addr); + fn remove_path(&mut self, path_id: &PathId) -> Option { + let addr = self.paths.remove(path_id); + if let Some(ref addr) = addr { + self.path_ids.remove(addr); } self.open_paths.remove(path_id); + addr } /// Removes the path from the open paths. diff --git a/iroh/src/magicsock/remote_map/remote_state/path_state.rs b/iroh/src/magicsock/remote_map/remote_state/path_state.rs index a35c947650..c44fdd8dcf 100644 --- a/iroh/src/magicsock/remote_map/remote_state/path_state.rs +++ b/iroh/src/magicsock/remote_map/remote_state/path_state.rs @@ -1,15 +1,23 @@ //! The state kept for each network path to a remote endpoint. -use std::collections::{HashMap, VecDeque}; +use std::collections::{HashMap, HashSet, VecDeque}; use n0_error::e; use n0_future::time::Instant; use rustc_hash::FxHashMap; use tokio::sync::oneshot; -use tracing::{debug, trace, warn}; +use tracing::trace; use super::Source; -use crate::{disco::TransactionId, discovery::DiscoveryError, magicsock::transports}; +use crate::{discovery::DiscoveryError, magicsock::transports}; + +/// Maximum number of IP paths we keep around per endpoint. +pub(super) const MAX_IP_PATHS: usize = 30; + +/// Maximum number of inactive IP paths we keep around per endpoint. +/// +/// These are paths that at one point been opened and are now closed. +pub(super) const MAX_INACTIVE_IP_PATHS: usize = 10; /// Map of all paths that we are aware of for a remote endpoint. /// @@ -39,6 +47,27 @@ impl RemotePathState { self.emit_pending_resolve_requests(None); } + /// Mark a path as abandoned. + /// + /// If this path does not exist, it does nothing to the + /// `RemotePathState` + pub(super) fn abandoned_path(&mut self, addr: &transports::Addr) { + if let Some(state) = self.paths.get_mut(addr) { + state.abandoned = Some(Instant::now()); + } + } + + /// Mark a path as opened. + /// + /// If this path does not exist, it does nothing to the + /// `RemotePathState` + pub(super) fn opened_path(&mut self, addr: &transports::Addr) { + if let Some(state) = self.paths.get_mut(addr) { + state.usable = true; + state.abandoned = None; + } + } + /// Inserts multiple addresses into our list of potential paths. /// /// This will emit pending resolve requests. @@ -74,32 +103,6 @@ impl RemotePathState { } } - /// Records a sent disco ping for a path. - pub(super) fn disco_ping_sent(&mut self, addr: transports::Addr, tx_id: TransactionId) { - let path = self.paths.entry(addr.clone()).or_default(); - path.ping_sent = Some(tx_id); - } - - /// Records a received disco pong for a path. - /// - /// Returns `true` if we have sent a ping with `tx_id` on the same path. - pub(super) fn disco_pong_received( - &mut self, - src: &transports::Addr, - tx_id: TransactionId, - ) -> bool { - let Some(state) = self.paths.get(src) else { - warn!(path = ?src, ?self.paths, "ignoring DISCO Pong for unknown path"); - return false; - }; - if state.ping_sent != Some(tx_id) { - debug!(path = ?src, ?state.ping_sent, pong_tx = ?tx_id, "ignoring unknown DISCO Pong for path"); - false - } else { - true - } - } - /// Notifies that a discovery run has finished. /// /// This will emit pending resolve requests. @@ -107,11 +110,6 @@ impl RemotePathState { self.emit_pending_resolve_requests(result.err()); } - /// Returns an iterator over all paths and their state. - pub(super) fn iter(&self) -> impl Iterator { - self.paths.iter() - } - /// Returns an iterator over the addresses of all paths. pub(super) fn addrs(&self) -> impl Iterator { self.paths.keys() @@ -139,6 +137,13 @@ impl RemotePathState { tx.send(result.clone()).ok(); } } + + /// TODO: fix up docs once review indicates this is actually + /// the criteria for pruning. + pub(super) fn prune_paths(&mut self) { + // right now we only prune IP paths + prune_ip_paths(&mut self.paths); + } } /// The state of a single path to the remote endpoint. @@ -154,6 +159,319 @@ pub(super) struct PathState { /// We keep track of only the latest [`Instant`] for each [`Source`], keeping the size /// of the map of sources down to one entry per type of source. pub(super) sources: HashMap, - /// The last ping sent on this path. - pub(super) ping_sent: Option, + /// The last time this path was proven usable. + /// + /// If this is `false` and closed is `Some`, than we attempted to open this path, but + /// it did not work. + /// + /// If this is `false` and closed is `None`, than we do not know yet if this path is + /// usable. + pub(super) usable: bool, + /// The last time a path with this addr was abandoned. + /// + /// If this is `Some` and usable is `None`, than we attempted to use this path and it + /// did not work. + pub(super) abandoned: Option, +} + +/// Prunes the IP paths in the paths HashMap. +/// +/// Only prunes if the number of IP paths is above [`MAX_IP_PATHS`]. +/// +/// Keeps paths that are active or have never been holepunched. +/// +/// Always prunes paths that have unsuccessfully holepunched. +/// +/// Keeps [`MAX_INACTIVE_IP_PATHS`] of the most recently closed paths +/// that are not currently being used but have successfully been +/// holepunched previously. +/// +/// This all ensures that: +/// +/// - We do not have unbounded growth of paths. +/// - If we have many paths for this remote, we prune the paths that cannot hole punch. +/// - We do not prune holepunched paths that are currently not in use too quickly. For example, if a large number of untested paths are added at once, we will not immediately prune all of the unused, but valid, paths at once. +fn prune_ip_paths(paths: &mut FxHashMap) { + // if the total number of paths is less than the max, bail early + if paths.len() < MAX_IP_PATHS { + return; + } + + let ip_paths: Vec<_> = paths.iter().filter(|(addr, _)| addr.is_ip()).collect(); + + // if the total number of ip paths is less than the max, bail early + if ip_paths.len() < MAX_IP_PATHS { + return; + } + + // paths that were opened at one point but have previously been closed + let mut inactive = Vec::new(); + // paths where we attempted hole punching but it not successful + let mut failed = Vec::new(); + + for (addr, state) in ip_paths { + match state.abandoned { + // If a path has never been abandoned, it is either + // open currently or has never been dialed. + // Keep it. + None => {} + Some(abandoned) => { + if state.usable { + // These are paths where hole punching succeeded at one point, but the path was closed. + inactive.push((addr.clone(), abandoned)); + } else { + // These are paths where hole punching has been attempted and failed. + failed.push(addr.clone()); + } + } + } + } + + // All paths are bad, don't prune all of them. + // + // This implies that `inactive` is empty. + if failed.len() == paths.len() { + failed.truncate(MAX_IP_PATHS); + } + + // sort the potentially prunable from most recently closed to least recently closed + inactive.sort_by(|a, b| b.1.cmp(&a.1)); + + // Prune the "oldest" closed paths. + let old_inactive = inactive.split_off(inactive.len().saturating_sub(MAX_INACTIVE_IP_PATHS)); + + // collect all the paths that should be pruned + let must_prune: HashSet<_> = failed + .into_iter() + .chain(old_inactive.into_iter().map(|(addr, _)| addr)) + .collect(); + + paths.retain(|addr, _| !must_prune.contains(addr)); +} + +#[cfg(test)] +mod tests { + use std::{ + net::{Ipv4Addr, SocketAddrV4}, + time::Duration, + }; + + use iroh_base::{RelayUrl, SecretKey}; + use rand::SeedableRng; + + use super::*; + + fn ip_addr(port: u16) -> transports::Addr { + transports::Addr::Ip(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port).into()) + } + + fn path_state_usable_abandoned(abandoned: Instant) -> PathState { + PathState { + sources: HashMap::new(), + usable: true, + abandoned: Some(abandoned), + } + } + + fn path_state_failed_abandoned(abandoned: Instant) -> PathState { + PathState { + sources: HashMap::new(), + usable: false, + abandoned: Some(abandoned), + } + } + + #[test] + fn test_prune_under_max_paths() { + let mut paths = FxHashMap::default(); + for i in 0..20 { + paths.insert(ip_addr(i), PathState::default()); + } + + prune_ip_paths(&mut paths); + assert_eq!(20, paths.len(), "should not prune when under MAX_IP_PATHS"); + } + + #[test] + fn test_prune_at_max_paths_no_prunable() { + let mut paths = FxHashMap::default(); + // All paths are active (never abandoned), so none should be pruned + for i in 0..MAX_IP_PATHS { + paths.insert(ip_addr(i as u16), PathState::default()); + } + + prune_ip_paths(&mut paths); + assert_eq!(MAX_IP_PATHS, paths.len(), "should not prune active paths"); + } + + #[test] + fn test_prune_failed_holepunch() { + let mut paths = FxHashMap::default(); + let now = Instant::now(); + + // Add 20 active paths + for i in 0..20 { + paths.insert(ip_addr(i), PathState::default()); + } + + // Add 15 failed holepunch paths (must_prune) + for i in 20..35 { + paths.insert(ip_addr(i), path_state_failed_abandoned(now)); + } + + prune_ip_paths(&mut paths); + + // All failed holepunch paths should be pruned + assert_eq!(20, paths.len()); + for i in 0..20 { + assert!(paths.contains_key(&ip_addr(i))); + } + for i in 20..35 { + assert!(!paths.contains_key(&ip_addr(i))); + } + } + + #[test] + fn test_prune_keeps_most_recent_inactive() { + let mut paths = FxHashMap::default(); + let now = Instant::now(); + + // Add 15 active paths + for i in 0..15 { + paths.insert(ip_addr(i), PathState::default()); + } + + // Add 20 usable but abandoned paths with different abandon times + // Ports 15-34, with port 34 being most recently abandoned + for i in 0..20 { + let abandoned_time = now - Duration::from_secs((20 - i) as u64); + paths.insert( + ip_addr(15 + i as u16), + path_state_usable_abandoned(abandoned_time), + ); + } + + assert_eq!(35, paths.len()); + prune_ip_paths(&mut paths); + + // Should keep 15 active + 10 most recently abandoned + assert_eq!(25, paths.len()); + + // Active paths should remain + for i in 0..15 { + assert!(paths.contains_key(&ip_addr(i))); + } + + // Most recently abandoned (ports 25-34) should remain + for i in 25..35 { + assert!(paths.contains_key(&ip_addr(i)), "port {} should be kept", i); + } + + // Oldest abandoned (ports 15-24) should be pruned + for i in 15..25 { + assert!( + !paths.contains_key(&ip_addr(i)), + "port {} should be pruned", + i + ); + } + } + + #[test] + fn test_prune_mixed_must_and_can_prune() { + let mut paths = FxHashMap::default(); + let now = Instant::now(); + + // Add 15 active paths + for i in 0..15 { + paths.insert(ip_addr(i), PathState::default()); + } + + // Add 5 failed holepunch paths + for i in 15..20 { + paths.insert(ip_addr(i), path_state_failed_abandoned(now)); + } + + // Add 15 usable but abandoned paths + for i in 0..15 { + let abandoned_time = now - Duration::from_secs((15 - i) as u64); + paths.insert( + ip_addr(20 + i as u16), + path_state_usable_abandoned(abandoned_time), + ); + } + + assert_eq!(35, paths.len()); + prune_ip_paths(&mut paths); + + // Remove all failed paths -> down to 30 + // Keep MAX_INACTIVE_IP_PATHS, eg remove 5 usable but abandoned paths -> down to 20 + assert_eq!(20, paths.len()); + + // Active paths should remain + for i in 0..15 { + assert!(paths.contains_key(&ip_addr(i))); + } + + // Failed holepunch should be pruned + for i in 15..20 { + assert!(!paths.contains_key(&ip_addr(i))); + } + + // Most recently abandoned (ports 30-34) should remain + for i in 30..35 { + assert!(paths.contains_key(&ip_addr(i)), "port {} should be kept", i); + } + } + + #[test] + fn test_prune_non_ip_paths_not_counted() { + let mut paths = FxHashMap::default(); + let now = Instant::now(); + + // Add 25 IP paths (under MAX_IP_PATHS) + for i in 0..25 { + paths.insert(ip_addr(i), path_state_failed_abandoned(now)); + } + + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64); + let relay_url: RelayUrl = url::Url::parse("https://localhost") + .expect("should be valid url") + .into(); + // Add 10 relay addresses + for _ in 0..10 { + let id = SecretKey::generate(&mut rng).public(); + let relay_addr = transports::Addr::Relay(relay_url.clone(), id); + paths.insert(relay_addr, PathState::default()); + } + + assert_eq!(35, paths.len()); // 25 IP + 10 relay + prune_ip_paths(&mut paths); + + // Should not prune since IP paths < MAX_IP_PATHS + assert_eq!(35, paths.len()); + } + + #[test] + fn test_prune_preserves_never_dialed() { + let mut paths = FxHashMap::default(); + let now = Instant::now(); + + // Add 20 never-dialed paths (abandoned = None, usable = false) + for i in 0..20 { + paths.insert(ip_addr(i), PathState::default()); + } + + // Add 15 failed paths to trigger pruning + for i in 20..35 { + paths.insert(ip_addr(i), path_state_failed_abandoned(now)); + } + + prune_ip_paths(&mut paths); + + // Never-dialed paths should be preserved + for i in 0..20 { + assert!(paths.contains_key(&ip_addr(i))); + } + } }