From c17a870e1c8396ae22557040288e5f1feaf65e83 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 7 Jul 2025 13:01:21 +0200 Subject: [PATCH 01/44] feat: use quinn multipath --- Cargo.lock | 1222 +++++++++++++++++++++-------------------- Cargo.toml | 5 + iroh-relay/Cargo.toml | 4 +- iroh/Cargo.toml | 8 +- iroh/bench/Cargo.toml | 2 +- 5 files changed, 639 insertions(+), 602 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d23ac3bdd80..cfaec28b630 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,9 +27,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aead" @@ -44,15 +44,15 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.12" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom 0.3.3", + "getrandom 0.2.16", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -93,9 +93,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -108,36 +108,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", - "once_cell_polyfill", + "once_cell", "windows-sys 0.59.0", ] @@ -198,7 +198,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", "synstructure", ] @@ -210,7 +210,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -234,7 +234,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -276,9 +276,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" @@ -343,7 +343,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -370,9 +370,9 @@ dependencies = [ [[package]] name = "backon" -version = "1.5.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302eaff5357a264a2c42f127ecb8bac761cf99749fc3dc95677e2743991f99e7" +checksum = "fd0b50b1b78dbadd44ab18b3c794e496f3a139abb9fbc27d9c94c4eebbb96496" dependencies = [ "fastrand", "gloo-timers 0.3.0", @@ -423,9 +423,9 @@ dependencies = [ [[package]] name = "base64ct" -version = "1.8.0" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" [[package]] name = "bincode" @@ -453,9 +453,15 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.9.1" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "blake3" @@ -493,9 +499,15 @@ checksum = "387e80962b798815a2b5c4bcfdb6bf626fa922ffe9f74e373103b858738e9f31" [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bytemuck" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" [[package]] name = "byteorder" @@ -517,9 +529,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.29" +version = "1.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" dependencies = [ "shlex", ] @@ -532,9 +544,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" @@ -606,9 +618,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.41" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" dependencies = [ "clap_builder", "clap_derive", @@ -616,9 +628,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.41" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" dependencies = [ "anstream", "anstyle", @@ -628,30 +640,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.41" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "cobs" -version = "0.3.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" -dependencies = [ - "thiserror 2.0.12", -] +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" [[package]] name = "color-backtrace" @@ -666,9 +675,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "combine" @@ -682,15 +691,15 @@ dependencies = [ [[package]] name = "console" -version = "0.16.0" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", "libc", "once_cell", "unicode-width", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -717,9 +726,9 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "cordyceps" -version = "0.3.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" +checksum = "a0392f465ceba1713d30708f61c160ebf4dc1cf86bb166039d16b11ad4f3b5b6" dependencies = [ "loom", "tracing", @@ -737,9 +746,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" dependencies = [ "core-foundation-sys", "libc", @@ -762,9 +771,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.3.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] @@ -853,9 +862,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" @@ -926,7 +935,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -983,7 +992,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -1001,16 +1010,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "derive_more-impl 1.0.0", -] - -[[package]] -name = "derive_more" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" -dependencies = [ - "derive_more-impl 2.0.1", + "derive_more-impl", ] [[package]] @@ -1021,19 +1021,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", - "unicode-xid", -] - -[[package]] -name = "derive_more-impl" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", + "syn 2.0.101", "unicode-xid", ] @@ -1089,7 +1077,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -1131,9 +1119,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", @@ -1177,27 +1165,27 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] name = "enumflags2" -version = "0.7.12" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" dependencies = [ "enumflags2_derive", ] [[package]] name = "enumflags2_derive" -version = "0.7.12" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -1217,12 +1205,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.13" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -1231,6 +1219,18 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" +[[package]] +name = "fastbloom" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27cea6e7f512d43b098939ff4d5a5d6fe3db07971e1d05176fe26c642d33f5b8" +dependencies = [ + "getrandom 0.3.2", + "rand 0.9.1", + "siphasher", + "wide", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -1287,9 +1287,9 @@ dependencies = [ [[package]] name = "fs-err" -version = "3.1.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d7be93788013f265201256d58f04936a8079ad5dc898743aa20525f503b683" +checksum = "1f89bda4c2a21204059a977ed3bfe746677dfd137b83c339e702b0ac91d482aa" dependencies = [ "autocfg", "tokio", @@ -1377,7 +1377,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -1418,16 +1418,15 @@ dependencies = [ [[package]] name = "generator" -version = "0.8.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" dependencies = [ - "cc", "cfg-if", "libc", "log", "rustversion", - "windows", + "windows 0.58.0", ] [[package]] @@ -1450,15 +1449,15 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "js-sys", @@ -1669,7 +1668,7 @@ dependencies = [ "futures-sink", "futures-timer", "futures-util", - "getrandom 0.3.3", + "getrandom 0.3.2", "no-std-compat", "nonzero_ext", "parking_lot", @@ -1683,9 +1682,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.11" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" dependencies = [ "atomic-waker", "bytes", @@ -1727,9 +1726,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "allocator-api2", "equivalent", @@ -1768,9 +1767,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" [[package]] name = "hex" @@ -1882,9 +1881,9 @@ dependencies = [ [[package]] name = "hmac-sha256" -version = "1.1.12" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425" +checksum = "4a8575493d277c9092b988c780c94737fb9fd8651a1001e16bee3eccfc1baedb" [[package]] name = "hostname-validator" @@ -1988,10 +1987,11 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ + "futures-util", "http 1.3.1", "hyper", "hyper-util", @@ -2000,28 +2000,24 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.1", + "webpki-roots 0.26.11", ] [[package]] name = "hyper-util" -version = "0.1.15" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ - "base64", "bytes", "futures-channel", - "futures-core", "futures-util", "http 1.3.1", "http-body", "hyper", - "ipnet", "libc", - "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2", "tokio", "tower-service", "tracing", @@ -2039,7 +2035,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.61.0", ] [[package]] @@ -2053,22 +2049,21 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", - "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locale_core" -version = "2.0.0" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", @@ -2077,11 +2072,31 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", "icu_collections", @@ -2089,54 +2104,67 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", + "utf16_iter", + "utf8_iter", + "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" -version = "2.0.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", - "icu_locale_core", + "icu_locid_transform", "icu_properties_data", "icu_provider", - "potential_utf", - "zerotrie", + "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" -version = "2.0.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", - "icu_locale_core", + "icu_locid", + "icu_provider_macros", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", - "zerotrie", "zerovec", ] +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "idna" version = "1.0.3" @@ -2150,9 +2178,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", @@ -2181,25 +2209,25 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.15.3", ] [[package]] name = "indicatif" -version = "0.18.0" +version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" dependencies = [ "console", + "number_prefix", "portable-atomic", "tokio", "unicode-width", - "unit-prefix", "web-time", ] @@ -2224,24 +2252,13 @@ dependencies = [ "web-sys", ] -[[package]] -name = "io-uring" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] - [[package]] name = "ipconfig" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.10", + "socket2", "widestring", "windows-sys 0.48.0", "winreg", @@ -2256,19 +2273,9 @@ dependencies = [ "serde", ] -[[package]] -name = "iri-string" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "iroh" -version = "0.91.0" +version = "0.90.0" dependencies = [ "aead", "axum", @@ -2280,11 +2287,11 @@ dependencies = [ "crypto_box", "data-encoding", "der", - "derive_more 2.0.1", + "derive_more", "ed25519-dalek", "futures-buffered", "futures-util", - "getrandom 0.3.3", + "getrandom 0.3.2", "hickory-resolver", "http 1.3.1", "igd-next", @@ -2320,7 +2327,7 @@ dependencies = [ "smallvec", "snafu", "spki", - "strum 0.27.1", + "strum", "stun-rs", "surge-ping", "swarm-discovery", @@ -2341,11 +2348,11 @@ dependencies = [ [[package]] name = "iroh-base" -version = "0.91.0" +version = "0.90.0" dependencies = [ "curve25519-dalek", "data-encoding", - "derive_more 2.0.1", + "derive_more", "ed25519-dalek", "n0-snafu", "nested_enum_utils", @@ -2362,7 +2369,7 @@ dependencies = [ [[package]] name = "iroh-bench" -version = "0.91.0" +version = "0.90.0" dependencies = [ "bytes", "clap", @@ -2372,8 +2379,9 @@ dependencies = [ "iroh-quinn", "n0-future", "n0-snafu", + "n0-watcher", "rand 0.8.5", - "rcgen 0.14.2", + "rcgen", "rustls", "tokio", "tracing", @@ -2382,7 +2390,7 @@ dependencies = [ [[package]] name = "iroh-dns-server" -version = "0.91.0" +version = "0.90.0" dependencies = [ "async-trait", "axum", @@ -2392,7 +2400,7 @@ dependencies = [ "clap", "criterion", "data-encoding", - "derive_more 2.0.1", + "derive_more", "dirs-next", "governor", "hickory-resolver", @@ -2408,7 +2416,7 @@ dependencies = [ "pkarr", "rand 0.8.5", "rand_chacha 0.3.1", - "rcgen 0.13.2", + "rcgen", "redb", "regex", "rustls", @@ -2416,7 +2424,7 @@ dependencies = [ "serde", "snafu", "struct_iterable", - "strum 0.26.3", + "strum", "tokio", "tokio-rustls", "tokio-rustls-acme", @@ -2461,14 +2469,13 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] name = "iroh-quinn" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde160ebee7aabede6ae887460cd303c8b809054224815addf1469d54a6fcf7" +version = "0.13.0" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#773fceabb27f1e56132198dd960d4bd1493e0ed0" dependencies = [ "bytes", "cfg_aliases", @@ -2477,7 +2484,7 @@ dependencies = [ "pin-project-lite", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2", "thiserror 2.0.12", "tokio", "tracing", @@ -2487,12 +2494,13 @@ dependencies = [ [[package]] name = "iroh-quinn-proto" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "929d5d8fa77d5c304d3ee7cae9aede31f13908bd049f9de8c7c0094ad6f7c535" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#773fceabb27f1e56132198dd960d4bd1493e0ed0" dependencies = [ "bytes", - "getrandom 0.2.16", - "rand 0.8.5", + "fastbloom", + "getrandom 0.3.2", + "lru-slab", + "rand 0.9.1", "ring", "rustc-hash", "rustls", @@ -2507,21 +2515,20 @@ dependencies = [ [[package]] name = "iroh-quinn-udp" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53afaa1049f7c83ea1331f5ebb9e6ebc5fdd69c468b7a22dd598b02c9bcc973" +version = "0.5.12" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#773fceabb27f1e56132198dd960d4bd1493e0ed0" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2", "tracing", "windows-sys 0.59.0", ] [[package]] name = "iroh-relay" -version = "0.91.0" +version = "0.90.0" dependencies = [ "ahash", "blake3", @@ -2531,8 +2538,8 @@ dependencies = [ "crypto_box", "dashmap", "data-encoding", - "derive_more 2.0.1", - "getrandom 0.3.3", + "derive_more", + "getrandom 0.3.2", "governor", "hickory-proto", "hickory-resolver", @@ -2555,7 +2562,7 @@ dependencies = [ "proptest", "rand 0.8.5", "rand_chacha 0.3.1", - "rcgen 0.14.2", + "rcgen", "regex", "reloadable-state", "reqwest", @@ -2571,7 +2578,7 @@ dependencies = [ "sha1", "simdutf8", "snafu", - "strum 0.27.1", + "strum", "time", "tokio", "tokio-rustls", @@ -2660,17 +2667,17 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libredox" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags", + "bitflags 2.9.0", "libc", ] @@ -2688,9 +2695,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.8.0" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "litrs" @@ -2700,9 +2707,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -2733,7 +2740,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.3", ] [[package]] @@ -2787,9 +2794,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" @@ -2815,22 +2822,22 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.9" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", ] [[package]] @@ -2859,7 +2866,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bb0e5d99e681ab3c938842b96fcb41bf8a7bb4bfdb11ccbd653a7e83e06c794" dependencies = [ "cfg_aliases", - "derive_more 1.0.0", + "derive_more", "futures-buffered", "futures-lite", "futures-util", @@ -2888,11 +2895,11 @@ dependencies = [ [[package]] name = "n0-watcher" -version = "0.3.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31462392a10d5ada4b945e840cbec2d5f3fee752b96c4b33eb41414d8f45c2a" +checksum = "f216d4ebc5fcf9548244803cbb93f488a2ae160feba3706cd17040d69cf7a368" dependencies = [ - "derive_more 1.0.0", + "derive_more", "n0-future", "snafu", ] @@ -2911,19 +2918,19 @@ dependencies = [ [[package]] name = "netdev" -version = "0.36.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862209dce034f82a44c95ce2b5183730d616f2a68746b9c1959aa2572e77c0a1" +checksum = "f901362e84cd407be6f8cd9d3a46bccf09136b095792785401ea7d283c79b91d" dependencies = [ "dlopen2", "ipnet", "libc", "netlink-packet-core", - "netlink-packet-route 0.22.0", + "netlink-packet-route 0.17.1", "netlink-sys", "once_cell", "system-configuration", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2939,27 +2946,26 @@ dependencies = [ [[package]] name = "netlink-packet-route" -version = "0.22.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0e7987b28514adf555dc1f9a5c30dfc3e50750bbaffb1aec41ca7b23dcd8e4" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" dependencies = [ "anyhow", - "bitflags", + "bitflags 1.3.2", "byteorder", "libc", - "log", "netlink-packet-core", "netlink-packet-utils", ] [[package]] name = "netlink-packet-route" -version = "0.24.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d83370a96813d7c977f8b63054f1162df6e5784f1c598d689236564fb5a6f2" +checksum = "0800eae8638a299eaa67476e1c6b6692922273e0f7939fd188fc861c837b9cd2" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.9.0", "byteorder", "libc", "log", @@ -3008,14 +3014,13 @@ dependencies = [ [[package]] name = "netwatch" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901dbb408894af3df3fc51420ba0c6faf3a7d896077b797c39b7001e2f787bd" +version = "0.6.0" +source = "git+https://github.com/n0-computer/net-tools?branch=feat-multipath#b7ab98d4ff9cc947f2f084004b4cc2a979bb4d06" dependencies = [ "atomic-waker", "bytes", "cfg_aliases", - "derive_more 2.0.1", + "derive_more", "iroh-quinn-udp", "js-sys", "libc", @@ -3024,20 +3029,20 @@ dependencies = [ "nested_enum_utils", "netdev", "netlink-packet-core", - "netlink-packet-route 0.24.0", + "netlink-packet-route 0.23.0", "netlink-proto", "netlink-sys", "pin-project-lite", "serde", "snafu", - "socket2 0.6.0", + "socket2", "time", "tokio", "tokio-util", "tracing", "web-sys", - "windows", - "windows-result", + "windows 0.59.0", + "windows-result 0.3.2", "wmi", ] @@ -3136,24 +3141,23 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", - "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -3165,6 +3169,12 @@ dependencies = [ "libc", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "object" version = "0.36.7" @@ -3193,12 +3203,6 @@ dependencies = [ "portable-atomic", ] -[[package]] -name = "once_cell_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" - [[package]] name = "oorandom" version = "11.1.5" @@ -3231,9 +3235,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -3241,9 +3245,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", @@ -3291,9 +3295,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.8.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" +checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" dependencies = [ "memchr", "thiserror 2.0.12", @@ -3302,9 +3306,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" +checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" dependencies = [ "pest", "pest_generator", @@ -3312,23 +3316,24 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" +checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] name = "pest_meta" -version = "2.8.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" +checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" dependencies = [ + "once_cell", "pest", "sha2", ] @@ -3360,7 +3365,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -3377,9 +3382,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkarr" -version = "3.8.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a50f65a2b97031863fbdff2f085ba832360b4bef3106d1fcff9ab5bf4063fe" +checksum = "e32222ae3d617bf92414db29085f8a959a4515effce916e038e9399a335a0d6d" dependencies = [ "async-compat", "base32", @@ -3463,7 +3468,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -3500,19 +3505,19 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" [[package]] name = "portmapper" -version = "0.8.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f1975debe62a70557e42b9ff9466e4890cf9d3d156d296408a711f1c5f642b" +checksum = "2d82975dc029c00d566f4e0f61f567d31f0297a290cb5416b5580dd8b4b54ade" dependencies = [ "base64", "bytes", - "derive_more 2.0.1", + "derive_more", "futures-lite", "futures-util", "hyper-util", @@ -3522,11 +3527,11 @@ dependencies = [ "nested_enum_utils", "netwatch", "num_enum", - "rand 0.9.1", + "rand 0.8.5", "serde", "smallvec", "snafu", - "socket2 0.6.0", + "socket2", "time", "tokio", "tokio-util", @@ -3537,9 +3542,9 @@ dependencies = [ [[package]] name = "postcard" -version = "1.1.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c1de96e20f51df24ca73cafcc4690e044854d803259db27a00a461cb3b9d17a" +checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" dependencies = [ "cobs", "embedded-io 0.4.0", @@ -3551,22 +3556,13 @@ dependencies = [ [[package]] name = "postcard-derive" -version = "0.2.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f049d94cb6dda6938cc8a531d2898e7c08d71c6de63d8e67123cca6cdde2cc" +checksum = "0239fa9c1d225d4b7eb69925c25c5e082307a141e470573fbbe3a817ce6a7a37" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", -] - -[[package]] -name = "potential_utf" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" -dependencies = [ - "zerovec", + "syn 1.0.109", ] [[package]] @@ -3581,7 +3577,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy", + "zerocopy 0.8.25", ] [[package]] @@ -3658,17 +3654,17 @@ dependencies = [ [[package]] name = "proptest" -version = "1.7.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ "bit-set", "bit-vec", - "bitflags", + "bitflags 2.9.0", "lazy_static", "num-traits", - "rand 0.9.1", - "rand_chacha 0.9.0", + "rand 0.8.5", + "rand_chacha 0.3.1", "rand_xorshift", "regex-syntax 0.8.5", "rusty-fork", @@ -3678,15 +3674,15 @@ dependencies = [ [[package]] name = "quanta" -version = "0.12.6" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" dependencies = [ "crossbeam-utils", "libc", "once_cell", "raw-cpuid", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", "web-sys", "winapi", ] @@ -3699,9 +3695,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" dependencies = [ "bytes", "cfg_aliases", @@ -3710,7 +3706,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2", "thiserror 2.0.12", "tokio", "tracing", @@ -3719,13 +3715,12 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b" dependencies = [ "bytes", - "getrandom 0.3.3", - "lru-slab", + "getrandom 0.3.2", "rand 0.9.1", "ring", "rustc-hash", @@ -3740,14 +3735,14 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.13" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2", "tracing", "windows-sys 0.59.0", ] @@ -3773,9 +3768,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.3.0" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rand" @@ -3833,16 +3828,16 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.2", ] [[package]] name = "rand_xorshift" -version = "0.4.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core 0.9.3", + "rand_core 0.6.4", ] [[package]] @@ -3851,7 +3846,7 @@ version = "11.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] [[package]] @@ -3887,19 +3882,6 @@ dependencies = [ "yasna", ] -[[package]] -name = "rcgen" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49bc8ffa8a832eb1d7c8000337f8b0d2f4f2f5ec3cf4ddc26f125e3ad2451824" -dependencies = [ - "pem", - "ring", - "rustls-pki-types", - "time", - "yasna", -] - [[package]] name = "redb" version = "2.4.0" @@ -3911,11 +3893,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] [[package]] @@ -3998,9 +3980,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ "base64", "bytes", @@ -4012,12 +3994,16 @@ dependencies = [ "hyper", "hyper-rustls", "hyper-util", + "ipnet", "js-sys", "log", + "mime", + "once_cell", "percent-encoding", "pin-project-lite", "quinn", "rustls", + "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", @@ -4027,21 +4013,21 @@ dependencies = [ "tokio-rustls", "tokio-util", "tower", - "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.1", + "webpki-roots 0.26.11", + "windows-registry", ] [[package]] name = "resolv-conf" -version = "0.7.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" +checksum = "fc7c8f7f733062b66dc1c63f9db168ac0b97a9210e247fa90fdc9ad08f51b302" [[package]] name = "ring" @@ -4059,9 +4045,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -4093,7 +4079,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys", @@ -4102,9 +4088,8 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +version = "0.23.25" +source = "git+https://github.com/n0-computer/rustls?rev=be02113e7837df60953d02c2bdd0f4634fef3a80#be02113e7837df60953d02c2bdd0f4634fef3a80" dependencies = [ "log", "once_cell", @@ -4183,11 +4168,11 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.5.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" +checksum = "4937d110d34408e9e5ad30ba0b0ca3b6a8a390f8db3636db60144ac4fa792750" dependencies = [ - "core-foundation 0.10.1", + "core-foundation 0.10.0", "core-foundation-sys", "jni", "log", @@ -4210,9 +4195,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "7149975849f1abb3832b246010ef62ccc80d3a76169517ada7188252b9cfb437" dependencies = [ "ring", "rustls-pki-types", @@ -4221,9 +4206,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "rusty-fork" @@ -4243,6 +4228,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + [[package]] name = "salsa20" version = "0.10.2" @@ -4288,8 +4282,8 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags", - "core-foundation 0.10.1", + "bitflags 2.9.0", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -4370,7 +4364,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -4397,9 +4391,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.0" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -4508,20 +4502,29 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee851d0e5e7af3721faea1843e8015e820a234f81fda3dea9247e15bac9a86a" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" -version = "0.4.10" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "smol_str" @@ -4548,7 +4551,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -4561,16 +4564,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "socket2" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - [[package]] name = "spin" version = "0.9.8" @@ -4631,7 +4624,7 @@ dependencies = [ "proc-macro2", "quote", "struct_iterable_internal", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -4646,16 +4639,7 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros 0.26.4", -] - -[[package]] -name = "strum" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" -dependencies = [ - "strum_macros 0.27.1", + "strum_macros", ] [[package]] @@ -4668,20 +4652,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.104", -] - -[[package]] -name = "strum_macros" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -4724,7 +4695,7 @@ dependencies = [ "parking_lot", "pnet_packet", "rand 0.9.1", - "socket2 0.5.10", + "socket2", "thiserror 1.0.69", "tokio", "tracing", @@ -4739,7 +4710,7 @@ dependencies = [ "acto", "hickory-proto", "rand 0.9.1", - "socket2 0.5.10", + "socket2", "thiserror 2.0.12", "tokio", "tracing", @@ -4758,9 +4729,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -4784,7 +4755,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -4793,7 +4764,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags", + "bitflags 2.9.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -4816,12 +4787,12 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.20.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.2", "once_cell", "rustix", "windows-sys 0.59.0", @@ -4862,7 +4833,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -4873,16 +4844,17 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", + "once_cell", ] [[package]] @@ -4921,9 +4893,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", @@ -4956,20 +4928,18 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.46.1" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", - "socket2 0.5.10", + "socket2", "tokio-macros", "windows-sys 0.52.0", ] @@ -4982,7 +4952,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -5010,7 +4980,7 @@ dependencies = [ "num-bigint", "pem", "proc-macro2", - "rcgen 0.13.2", + "rcgen", "reqwest", "ring", "rustls", @@ -5046,7 +5016,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "hashbrown 0.15.4", + "hashbrown 0.15.3", "pin-project-lite", "tokio", ] @@ -5061,7 +5031,7 @@ dependencies = [ "bytes", "futures-core", "futures-sink", - "getrandom 0.3.3", + "getrandom 0.3.2", "http 1.3.1", "httparse", "rand 0.9.1", @@ -5075,59 +5045,44 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.2" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ - "indexmap", "serde", "serde_spanned", - "toml_datetime 0.7.0", - "toml_parser", - "toml_writer", - "winnow", + "toml_datetime", + "toml_edit", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" - -[[package]] -name = "toml_datetime" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", - "toml_datetime 0.6.11", - "winnow", -] - -[[package]] -name = "toml_parser" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" -dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", "winnow", ] [[package]] -name = "toml_writer" -version = "1.0.2" +name = "toml_write" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" [[package]] name = "tower" @@ -5147,18 +5102,15 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" dependencies = [ - "bitflags", + "bitflags 2.9.0", "bytes", - "futures-util", "http 1.3.1", "http-body", - "iri-string", "pin-project-lite", - "tower", "tower-layer", "tower-service", "tracing", @@ -5206,20 +5158,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -5294,7 +5246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -5356,9 +5308,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.2.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unicode-xid" @@ -5366,12 +5318,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "unit-prefix" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" - [[package]] name = "universal-hash" version = "0.5.1" @@ -5400,6 +5346,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -5414,13 +5366,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom 0.3.3", - "js-sys", - "wasm-bindgen", + "getrandom 0.3.2", ] [[package]] @@ -5465,9 +5415,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" @@ -5500,7 +5450,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", "wasm-bindgen-shared", ] @@ -5535,7 +5485,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5570,7 +5520,7 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] @@ -5612,14 +5562,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" dependencies = [ - "webpki-root-certs 1.0.1", + "webpki-root-certs 1.0.0", ] [[package]] name = "webpki-root-certs" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86138b15b2b7d561bc4469e77027b8dd005a43dc502e9031d1f5afc8ce1f280e" +checksum = "01a83f7e1a9f8712695c03eabe9ed3fbca0feff0152f33f12593e5a6303cb1a4" dependencies = [ "rustls-pki-types", ] @@ -5630,18 +5580,28 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.1", + "webpki-roots 1.0.0", ] [[package]] name = "webpki-roots" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" +checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" dependencies = [ "rustls-pki-types", ] +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "widestring" version = "1.2.0" @@ -5681,48 +5641,83 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.61.3" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-link", - "windows-numerics", + "windows-core 0.58.0", + "windows-targets 0.52.6", ] [[package]] -name = "windows-collections" -version = "0.2.0" +name = "windows" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" dependencies = [ - "windows-core", + "windows-core 0.59.0", + "windows-targets 0.53.0", ] [[package]] name = "windows-core" -version = "0.61.2" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", ] [[package]] -name = "windows-future" -version = "0.2.1" +name = "windows-core" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" dependencies = [ - "windows-core", + "windows-implement 0.59.0", + "windows-interface 0.59.1", + "windows-result 0.3.2", + "windows-strings 0.3.1", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", "windows-link", - "windows-threading", + "windows-result 0.3.2", + "windows-strings 0.4.0", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-implement" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", ] [[package]] @@ -5733,7 +5728,18 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", ] [[package]] @@ -5744,39 +5750,68 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] [[package]] name = "windows-link" -version = "0.1.3" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-registry" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result 0.3.2", + "windows-strings 0.3.1", + "windows-targets 0.53.0", +] [[package]] -name = "windows-numerics" +name = "windows-result" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-core", - "windows-link", + "windows-targets 0.52.6", ] [[package]] name = "windows-result" -version = "0.3.4" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ "windows-link", ] @@ -5817,15 +5852,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.2", -] - [[package]] name = "windows-targets" version = "0.42.2" @@ -5874,9 +5900,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", @@ -5888,15 +5914,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -6079,9 +6096,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] @@ -6102,35 +6119,41 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] [[package]] name = "wmi" -version = "0.17.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3de777dce4cbcdc661d5d18e78ce4b46a37adc2bb7c0078a556c7f07bcce2f" +checksum = "7787dacdd8e71cbc104658aade4009300777f9b5fda6a75f19145fedb8a18e71" dependencies = [ "chrono", "futures", "log", "serde", "thiserror 2.0.12", - "windows", - "windows-core", + "windows 0.59.0", + "windows-core 0.59.0", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + [[package]] name = "writeable" -version = "0.6.1" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "ws_stream_wasm" -version = "0.7.5" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" +checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" dependencies = [ "async_io_stream", "futures", @@ -6139,7 +6162,7 @@ dependencies = [ "pharos", "rustc_version", "send_wrapper", - "thiserror 2.0.12", + "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -6164,9 +6187,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.27" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" [[package]] name = "xmltree" @@ -6194,9 +6217,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.0" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -6206,13 +6229,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", "synstructure", ] @@ -6224,22 +6247,42 @@ checksum = "2164e798d9e3d84ee2c91139ace54638059a3b23e361f5c11781c2c6459bde0f" [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive 0.8.25", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", ] [[package]] @@ -6259,7 +6302,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", "synstructure", ] @@ -6269,22 +6312,11 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -[[package]] -name = "zerotrie" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - [[package]] name = "zerovec" -version = "0.11.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", @@ -6293,11 +6325,11 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.101", ] diff --git a/Cargo.toml b/Cargo.toml index 593f1d0ec73..d5d37a9b5d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,3 +40,8 @@ unexpected_cfgs = { level = "warn", check-cfg = ["cfg(iroh_docsrs)", "cfg(iroh_l [workspace.lints.clippy] unused-async = "warn" + + +[patch.crates-io] +rustls = { git = "https://github.com/n0-computer/rustls", rev = "be02113e7837df60953d02c2bdd0f4634fef3a80" } +netwatch = { git = "https://github.com/n0-computer/net-tools", branch = "feat-multipath" } diff --git a/iroh-relay/Cargo.toml b/iroh-relay/Cargo.toml index 9f7bc43c93b..8424cfeac94 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", version = "0.14.0", default-features = false, features = ["rustls-ring"] } -quinn-proto = { package = "iroh-quinn-proto", version = "0.13.0" } +quinn = { package = "iroh-quinn", git = "https://github.com/n0-computer/quinn", branch = "multipath-quinn-0.11.x", default-features = false, features = ["rustls-ring"] } +quinn-proto = { package = "iroh-quinn-proto", git = "https://github.com/n0-computer/quinn", branch = "multipath-quinn-0.11.x" } rand = "0.8" reqwest = { version = "0.12", default-features = false, features = [ "rustls-tls", diff --git a/iroh/Cargo.toml b/iroh/Cargo.toml index ce02aa4d2d3..713e0ebb67f 100644 --- a/iroh/Cargo.toml +++ b/iroh/Cargo.toml @@ -48,9 +48,9 @@ pin-project = "1" pkarr = { version = "3.7", default-features = false, features = [ "relays", ] } -quinn = { package = "iroh-quinn", version = "0.14.0", default-features = false, features = ["rustls-ring"] } -quinn-proto = { package = "iroh-quinn-proto", version = "0.13.0" } -quinn-udp = { package = "iroh-quinn-udp", version = "0.5.7" } +quinn = { package = "iroh-quinn", git = "https://github.com/n0-computer/quinn", branch = "multipath-quinn-0.11.x", default-features = false, features = ["rustls-ring"] } +quinn-proto = { package = "iroh-quinn-proto", git = "https://github.com/n0-computer/quinn", branch = "multipath-quinn-0.11.x" } +quinn-udp = { package = "iroh-quinn-udp", git = "https://github.com/n0-computer/quinn", branch = "multipath-quinn-0.11.x" } rand = "0.8" reqwest = { version = "0.12", default-features = false, features = [ "rustls-tls", @@ -109,7 +109,7 @@ hickory-resolver = "0.25.1" igd-next = { version = "0.16", features = ["aio_tokio"] } netdev = { version = "0.36.0" } portmapper = { version = "0.8", default-features = false } -quinn = { package = "iroh-quinn", version = "0.14.0", default-features = false, features = ["runtime-tokio", "rustls-ring"] } +quinn = { package = "iroh-quinn", git = "https://github.com/n0-computer/quinn", branch = "multipath-quinn-0.11.x", default-features = false, features = ["runtime-tokio", "rustls-ring"] } tokio = { version = "1", features = [ "io-util", "macros", diff --git a/iroh/bench/Cargo.toml b/iroh/bench/Cargo.toml index 8070e6259a2..97c46065a7e 100644 --- a/iroh/bench/Cargo.toml +++ b/iroh/bench/Cargo.toml @@ -12,7 +12,7 @@ iroh = { path = ".." } iroh-metrics = "0.35" n0-future = "0.1.1" n0-snafu = "0.2.0" -quinn = { package = "iroh-quinn", version = "0.14" } +quinn = { package = "iroh-quinn", git = "https://github.com/n0-computer/quinn", branch = "multipath-quinn-0.11.x" } rand = "0.8" rcgen = "0.14" rustls = { version = "0.23", default-features = false, features = ["ring"] } From 7fe570da1d6064ad83eb5ea22aa094dd8a269044 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 7 Jul 2025 15:27:42 +0200 Subject: [PATCH 02/44] update iroh-quinn --- Cargo.lock | 10 +++++----- iroh/src/magicsock.rs | 20 ++++++++++++-------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cfaec28b630..aab319073b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2474,8 +2474,8 @@ dependencies = [ [[package]] name = "iroh-quinn" -version = "0.13.0" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#773fceabb27f1e56132198dd960d4bd1493e0ed0" +version = "0.14.0" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#e84f16856ea3ac79ed6c206b258d33a30d87834f" dependencies = [ "bytes", "cfg_aliases", @@ -2494,7 +2494,7 @@ dependencies = [ [[package]] name = "iroh-quinn-proto" version = "0.13.0" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#773fceabb27f1e56132198dd960d4bd1493e0ed0" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#e84f16856ea3ac79ed6c206b258d33a30d87834f" dependencies = [ "bytes", "fastbloom", @@ -2516,7 +2516,7 @@ dependencies = [ [[package]] name = "iroh-quinn-udp" version = "0.5.12" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#773fceabb27f1e56132198dd960d4bd1493e0ed0" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#e84f16856ea3ac79ed6c206b258d33a30d87834f" dependencies = [ "cfg_aliases", "libc", @@ -5630,7 +5630,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index ba6f50f5073..fb2d0c2eec2 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -2624,10 +2624,12 @@ mod tests { info!("stats: {:#?}", stats); // TODO: ensure panics in this function are reported ok if matches!(loss, ExpectedLoss::AlmostNone) { - assert!( - stats.path.lost_packets < 10, - "[receiver] should not loose many packets", - ); + for (id, path) in &stats.paths { + assert!( + path.lost_packets < 10, + "[receiver] path {id:?} should not loose many packets", + ); + } } info!("close"); @@ -2675,10 +2677,12 @@ mod tests { let stats = conn.stats(); info!("stats: {:#?}", stats); if matches!(loss, ExpectedLoss::AlmostNone) { - assert!( - stats.path.lost_packets < 10, - "[sender] should not loose many packets", - ); + for (id, path) in &stats.paths { + assert!( + path.lost_packets < 10, + "[sender] path {id:?} should not loose many packets", + ); + } } info!("close"); From 2f469ac0e21b931ebe75699832d07638f59c85bd Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 8 Jul 2025 11:44:22 +0200 Subject: [PATCH 03/44] start opening paths --- iroh/src/endpoint.rs | 11 +++++++ iroh/src/magicsock.rs | 76 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 8 deletions(-) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index cc9a1cc1fd6..f8545edb212 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -1771,6 +1771,17 @@ impl Future for Connecting { Poll::Ready(Err(err)) => Poll::Ready(Err(err)), Poll::Ready(Ok(inner)) => { let conn = Connection { inner }; + + // Grab the remote identity and register this connection + + if let Some(remote) = *this.remote_node_id { + let weak_handle = conn.inner.weak_handle(); + this.ep.msock.register_connection(remote, weak_handle); + } else if let Ok(remote) = conn.remote_node_id() { + let weak_handle = conn.inner.weak_handle(); + this.ep.msock.register_connection(remote, weak_handle); + } + try_send_rtt_msg(&conn, this.ep, *this.remote_node_id); Poll::Ready(Ok(conn)) } diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index fb2d0c2eec2..f8454cfc1c8 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -43,7 +43,7 @@ use nested_enum_utils::common_fields; use netwatch::netmon; #[cfg(not(wasm_browser))] use netwatch::{UdpSocket, ip::LocalAddresses}; -use quinn::{AsyncUdpSocket, ServerConfig}; +use quinn::{AsyncUdpSocket, ServerConfig, WeakConnectionHandle}; use rand::Rng; use smallvec::SmallVec; use snafu::{ResultExt, Snafu}; @@ -195,6 +195,9 @@ pub(crate) struct MagicSock { ipv6_reported: Arc, /// Tracks the networkmap node entity for each node discovery key. node_map: NodeMap, + /// Tracks existing connections + connection_map: ConnectionMap, + /// Tracks the mapped IP addresses ip_mapped_addrs: IpMappedAddresses, /// Local addresses @@ -221,6 +224,22 @@ pub(crate) struct MagicSock { pub(crate) metrics: EndpointMetrics, } +#[derive(Default, Debug)] +struct ConnectionMap { + map: std::sync::Mutex>>, +} + +impl ConnectionMap { + fn insert(&self, remote: NodeId, handle: WeakConnectionHandle) { + self.map + .lock() + .expect("poisoned") + .entry(remote) + .or_default() + .push(handle); + } +} + #[allow(missing_docs)] #[common_fields({ backtrace: Option, @@ -271,6 +290,10 @@ impl MagicSock { self.local_addrs_watch.clone().get() } + pub(crate) fn register_connection(&self, remote: NodeId, conn: WeakConnectionHandle) { + self.connection_map.insert(remote, conn); + } + #[cfg(not(wasm_browser))] fn ip_bind_addrs(&self) -> &[SocketAddr] { &self.ip_bind_addrs @@ -393,8 +416,45 @@ impl MagicSock { } } if !addr.is_empty() { + // Add addr to the internal NodeMap self.node_map - .add_node_addr(addr, source, &self.metrics.magicsock); + .add_node_addr(addr.clone(), source, &self.metrics.magicsock); + + // Add paths to the existing connections + { + let mut map = self.connection_map.map.lock().expect("poisoned"); + let mut to_delete = Vec::new(); + if let Some(conns) = map.get_mut(&addr.node_id) { + for (i, conn) in conns.into_iter().enumerate() { + if let Some(conn) = conn.upgrade() { + for addr in addr.direct_addresses() { + let conn = conn.clone(); + let addr = *addr; + task::spawn(async move { + if let Err(err) = conn + .open_path(addr, quinn_proto::PathStatus::Available) + .await + { + warn!("failed to open path {:?}", err); + } + }); + } + // TODO: add relay path as mapped addr + } else { + to_delete.push(i); + } + } + // cleanup dead connections + let mut i = 0; + conns.retain(|_| { + let remove = to_delete.contains(&i); + i += 1; + + !remove + }); + } + } + Ok(()) } else if pruned != 0 { Err(EmptyPrunedSnafu { pruned }.build()) @@ -505,8 +565,8 @@ impl MagicSock { let mut active_paths = SmallVec::<[_; 3]>::new(); match MappedAddr::from(transmit.destination) { - MappedAddr::None(dest) => { - error!(%dest, "Cannot convert to a mapped address."); + MappedAddr::None(addr) => { + active_paths.push(transports::Addr::from(addr)); } MappedAddr::NodeId(dest) => { trace!( @@ -523,15 +583,14 @@ impl MagicSock { self.ipv6_reported.load(Ordering::Relaxed), &self.metrics.magicsock, ) { - Some((node_id, udp_addr, relay_url, ping_actions)) => { + Some((node_id, _udp_addr, relay_url, ping_actions)) => { if !ping_actions.is_empty() { self.actor_sender .try_send(ActorMessage::PingActions(ping_actions)) .ok(); } - if let Some(addr) = udp_addr { - active_paths.push(transports::Addr::from(addr)); - } + // NodeId mapped addrs are only used for relays, currently. + // IP based addrs will have been added as individual paths if let Some(url) = relay_url { active_paths.push(transports::Addr::Relay(url, node_id)); } @@ -1298,6 +1357,7 @@ impl Handle { actor_sender: actor_sender.clone(), ipv6_reported, node_map, + connection_map: Default::default(), ip_mapped_addrs: ip_mapped_addrs.clone(), discovery, discovery_user_data: RwLock::new(discovery_user_data), From 870716feba7e9b1fafef2685c0e73d95e311406e Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 8 Jul 2025 12:15:22 +0200 Subject: [PATCH 04/44] add more paths --- Cargo.lock | 8 ++--- Cargo.toml | 9 +++++ iroh/src/endpoint.rs | 8 ++--- iroh/src/magicsock.rs | 78 +++++++++++++++++++++++++------------------ 4 files changed, 61 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aab319073b6..a29794212ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2475,7 +2475,7 @@ dependencies = [ [[package]] name = "iroh-quinn" version = "0.14.0" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#e84f16856ea3ac79ed6c206b258d33a30d87834f" +source = "git+https://github.com//n0-computer/quinn?branch=multipath-misc#0d929df5f69ddc660c8ce81e9c348af7972862db" dependencies = [ "bytes", "cfg_aliases", @@ -2494,7 +2494,7 @@ dependencies = [ [[package]] name = "iroh-quinn-proto" version = "0.13.0" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#e84f16856ea3ac79ed6c206b258d33a30d87834f" +source = "git+https://github.com//n0-computer/quinn?branch=multipath-misc#0d929df5f69ddc660c8ce81e9c348af7972862db" dependencies = [ "bytes", "fastbloom", @@ -2516,14 +2516,14 @@ dependencies = [ [[package]] name = "iroh-quinn-udp" version = "0.5.12" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#e84f16856ea3ac79ed6c206b258d33a30d87834f" +source = "git+https://github.com//n0-computer/quinn?branch=multipath-misc#0d929df5f69ddc660c8ce81e9c348af7972862db" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d5d37a9b5d5..ac71aecc026 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,3 +45,12 @@ unused-async = "warn" [patch.crates-io] rustls = { git = "https://github.com/n0-computer/rustls", rev = "be02113e7837df60953d02c2bdd0f4634fef3a80" } netwatch = { git = "https://github.com/n0-computer/net-tools", branch = "feat-multipath" } + +[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" } + +iroh-quinn = { git = "https://github.com//n0-computer/quinn", branch = "multipath-misc" } +iroh-quinn-proto = { git = "https://github.com//n0-computer/quinn", branch = "multipath-misc" } +iroh-quinn-udp = { git = "https://github.com//n0-computer/quinn", branch = "multipath-misc" } diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index f8545edb212..3e1a7e90e6b 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -772,15 +772,13 @@ impl Endpoint { client_config }; + // TODO: race available addresses, this is currently only using the relay addr to connect + let dest_addr = mapped_addr.private_socket_addr(); let server_name = &tls::name::encode(node_id); let connect = self .msock .endpoint() - .connect_with( - client_config, - mapped_addr.private_socket_addr(), - server_name, - ) + .connect_with(client_config, dest_addr, server_name) .context(QuinnSnafu)?; Ok(Connecting { diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index f8454cfc1c8..d3f4bfe51bd 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -421,39 +421,7 @@ impl MagicSock { .add_node_addr(addr.clone(), source, &self.metrics.magicsock); // Add paths to the existing connections - { - let mut map = self.connection_map.map.lock().expect("poisoned"); - let mut to_delete = Vec::new(); - if let Some(conns) = map.get_mut(&addr.node_id) { - for (i, conn) in conns.into_iter().enumerate() { - if let Some(conn) = conn.upgrade() { - for addr in addr.direct_addresses() { - let conn = conn.clone(); - let addr = *addr; - task::spawn(async move { - if let Err(err) = conn - .open_path(addr, quinn_proto::PathStatus::Available) - .await - { - warn!("failed to open path {:?}", err); - } - }); - } - // TODO: add relay path as mapped addr - } else { - to_delete.push(i); - } - } - // cleanup dead connections - let mut i = 0; - conns.retain(|_| { - let remove = to_delete.contains(&i); - i += 1; - - !remove - }); - } - } + self.add_paths(addr); Ok(()) } else if pruned != 0 { @@ -463,6 +431,41 @@ impl MagicSock { } } + /// Adds all available addresses in the given `addr` as paths + fn add_paths(&self, addr: NodeAddr) { + let mut map = self.connection_map.map.lock().expect("poisoned"); + let mut to_delete = Vec::new(); + if let Some(conns) = map.get_mut(&addr.node_id) { + for (i, conn) in conns.into_iter().enumerate() { + if let Some(conn) = conn.upgrade() { + for addr in addr.direct_addresses() { + let conn = conn.clone(); + let addr = *addr; + task::spawn(async move { + if let Err(err) = conn + .open_path(addr, quinn_proto::PathStatus::Available) + .await + { + warn!("failed to open path {:?}", err); + } + }); + } + // TODO: add relay path as mapped addr + } else { + to_delete.push(i); + } + } + // cleanup dead connections + let mut i = 0; + conns.retain(|_| { + let remove = to_delete.contains(&i); + i += 1; + + !remove + }); + } + } + /// Stores a new set of direct addresses. /// /// If the direct addresses have changed from the previous set, they are published to @@ -873,9 +876,18 @@ impl MagicSock { return; } } + + // Add new addresses as paths + self.add_paths(NodeAddr { + node_id: sender, + relay_url: None, + direct_addresses: cm.my_numbers.iter().copied().collect(), + }); + let ping_actions = self.node_map .handle_call_me_maybe(sender, cm, &self.metrics.magicsock); + for action in ping_actions { match action { PingAction::SendCallMeMaybe { .. } => { From 346a7c2dafa1ce7eec5537b9125ede6ff53049b6 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 8 Jul 2025 12:36:22 +0200 Subject: [PATCH 05/44] set keep alive and idle timeouts for new paths --- iroh/src/magicsock.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index d3f4bfe51bd..ccddd91c01c 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -442,11 +442,20 @@ impl MagicSock { let conn = conn.clone(); let addr = *addr; task::spawn(async move { - if let Err(err) = conn + match conn .open_path(addr, quinn_proto::PathStatus::Available) .await { - warn!("failed to open path {:?}", err); + Ok(path) => { + path.set_max_idle_timeout(Some( + ENDPOINTS_FRESH_ENOUGH_DURATION, + )) + .ok(); + path.set_keep_alive_interval(Some(HEARTBEAT_INTERVAL)).ok(); + } + Err(err) => { + warn!("failed to open path {:?}", err); + } } }); } From 68b1769dc11e0101c102a88bbeb963fe3efa9cef Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 9 Jul 2025 17:34:32 +0200 Subject: [PATCH 06/44] insert relay path --- Cargo.lock | 6 +++--- Cargo.toml | 6 +++--- iroh/src/magicsock.rs | 23 ++++++++++++++++++++++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a29794212ff..f8b2e3e6c21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2475,7 +2475,7 @@ dependencies = [ [[package]] name = "iroh-quinn" version = "0.14.0" -source = "git+https://github.com//n0-computer/quinn?branch=multipath-misc#0d929df5f69ddc660c8ce81e9c348af7972862db" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#70e28875923db76f8dfbf4f058e682d56e6daea1" dependencies = [ "bytes", "cfg_aliases", @@ -2494,7 +2494,7 @@ dependencies = [ [[package]] name = "iroh-quinn-proto" version = "0.13.0" -source = "git+https://github.com//n0-computer/quinn?branch=multipath-misc#0d929df5f69ddc660c8ce81e9c348af7972862db" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#70e28875923db76f8dfbf4f058e682d56e6daea1" dependencies = [ "bytes", "fastbloom", @@ -2516,7 +2516,7 @@ dependencies = [ [[package]] name = "iroh-quinn-udp" version = "0.5.12" -source = "git+https://github.com//n0-computer/quinn?branch=multipath-misc#0d929df5f69ddc660c8ce81e9c348af7972862db" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#70e28875923db76f8dfbf4f058e682d56e6daea1" dependencies = [ "cfg_aliases", "libc", diff --git a/Cargo.toml b/Cargo.toml index ac71aecc026..edb51ff85b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,6 @@ netwatch = { git = "https://github.com/n0-computer/net-tools", branch = "feat-mu # iroh-quinn-proto = { path = "../iroh-quinn/quinn-proto" } # iroh-quinn-udp = { path = "../iroh-quinn/quinn-udp" } -iroh-quinn = { git = "https://github.com//n0-computer/quinn", branch = "multipath-misc" } -iroh-quinn-proto = { git = "https://github.com//n0-computer/quinn", branch = "multipath-misc" } -iroh-quinn-udp = { git = "https://github.com//n0-computer/quinn", branch = "multipath-misc" } +# iroh-quinn = { git = "https://github.com//n0-computer/quinn", branch = "multipath-misc" } +# iroh-quinn-proto = { git = "https://github.com//n0-computer/quinn", branch = "multipath-misc" } +# iroh-quinn-udp = { git = "https://github.com//n0-computer/quinn", branch = "multipath-misc" } diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index ccddd91c01c..b2e9be26491 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -459,7 +459,28 @@ impl MagicSock { } }); } - // TODO: add relay path as mapped addr + // Insert the relay addr + if let Some(addr) = self.get_mapping_addr(addr.node_id) { + let conn = conn.clone(); + let addr = addr.private_socket_addr(); + task::spawn(async move { + match conn + .open_path(addr, quinn_proto::PathStatus::Available) + .await + { + Ok(path) => { + path.set_max_idle_timeout(Some( + ENDPOINTS_FRESH_ENOUGH_DURATION, + )) + .ok(); + path.set_keep_alive_interval(Some(HEARTBEAT_INTERVAL)).ok(); + } + Err(err) => { + warn!("failed to open path {:?}", err); + } + } + }); + } } else { to_delete.push(i); } From 0eb3fde19268ff71988721aef992457c2d6e3f53 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 11 Jul 2025 12:35:28 +0200 Subject: [PATCH 07/44] set relay path as backup --- iroh/src/magicsock.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index b2e9be26491..f47bcdc996e 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -464,16 +464,11 @@ impl MagicSock { let conn = conn.clone(); let addr = addr.private_socket_addr(); task::spawn(async move { - match conn - .open_path(addr, quinn_proto::PathStatus::Available) - .await - { + match conn.open_path(addr, quinn_proto::PathStatus::Backup).await { Ok(path) => { - path.set_max_idle_timeout(Some( - ENDPOINTS_FRESH_ENOUGH_DURATION, - )) - .ok(); - path.set_keep_alive_interval(Some(HEARTBEAT_INTERVAL)).ok(); + // Keep the relay path open + path.set_max_idle_timeout(None).ok(); + path.set_keep_alive_interval(None).ok(); } Err(err) => { warn!("failed to open path {:?}", err); From 79ec17f4c668bcd61244ec472c57d2b5e0fbad3a Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 11 Jul 2025 13:25:33 +0200 Subject: [PATCH 08/44] start removing ping logic from the node_map --- Cargo.lock | 402 +++++--- iroh/src/magicsock.rs | 129 +-- iroh/src/magicsock/node_map.rs | 117 +-- iroh/src/magicsock/node_map/node_state.rs | 1036 ++++++--------------- iroh/src/magicsock/node_map/path_state.rs | 100 +- iroh/src/magicsock/node_map/udp_paths.rs | 3 +- 6 files changed, 568 insertions(+), 1219 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8b2e3e6c21..c3a6558f70f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -451,12 +451,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.9.0" @@ -691,15 +685,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.11" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" dependencies = [ "encode_unicode", "libc", "once_cell", "unicode-width", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1010,7 +1004,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "derive_more-impl", + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl 2.0.1", ] [[package]] @@ -1025,6 +1028,18 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "unicode-xid", +] + [[package]] name = "diatomic-waker" version = "0.2.3" @@ -2017,7 +2032,7 @@ dependencies = [ "hyper", "libc", "pin-project-lite", - "socket2", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -2035,7 +2050,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.0", + "windows-core 0.59.0", ] [[package]] @@ -2219,15 +2234,15 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.11" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" dependencies = [ "console", - "number_prefix", "portable-atomic", "tokio", "unicode-width", + "unit-prefix", "web-time", ] @@ -2258,7 +2273,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2", + "socket2 0.5.10", "widestring", "windows-sys 0.48.0", "winreg", @@ -2275,7 +2290,7 @@ dependencies = [ [[package]] name = "iroh" -version = "0.90.0" +version = "0.91.0" dependencies = [ "aead", "axum", @@ -2287,7 +2302,7 @@ dependencies = [ "crypto_box", "data-encoding", "der", - "derive_more", + "derive_more 2.0.1", "ed25519-dalek", "futures-buffered", "futures-util", @@ -2301,7 +2316,7 @@ dependencies = [ "iroh-metrics", "iroh-quinn", "iroh-quinn-proto", - "iroh-quinn-udp", + "iroh-quinn-udp 0.5.12", "iroh-relay", "n0-future", "n0-snafu", @@ -2327,7 +2342,7 @@ dependencies = [ "smallvec", "snafu", "spki", - "strum", + "strum 0.27.2", "stun-rs", "surge-ping", "swarm-discovery", @@ -2348,11 +2363,11 @@ dependencies = [ [[package]] name = "iroh-base" -version = "0.90.0" +version = "0.91.0" dependencies = [ "curve25519-dalek", "data-encoding", - "derive_more", + "derive_more 2.0.1", "ed25519-dalek", "n0-snafu", "nested_enum_utils", @@ -2369,7 +2384,7 @@ dependencies = [ [[package]] name = "iroh-bench" -version = "0.90.0" +version = "0.91.0" dependencies = [ "bytes", "clap", @@ -2379,9 +2394,8 @@ dependencies = [ "iroh-quinn", "n0-future", "n0-snafu", - "n0-watcher", "rand 0.8.5", - "rcgen", + "rcgen 0.14.3", "rustls", "tokio", "tracing", @@ -2390,7 +2404,7 @@ dependencies = [ [[package]] name = "iroh-dns-server" -version = "0.90.0" +version = "0.91.0" dependencies = [ "async-trait", "axum", @@ -2400,7 +2414,7 @@ dependencies = [ "clap", "criterion", "data-encoding", - "derive_more", + "derive_more 2.0.1", "dirs-next", "governor", "hickory-resolver", @@ -2416,7 +2430,7 @@ dependencies = [ "pkarr", "rand 0.8.5", "rand_chacha 0.3.1", - "rcgen", + "rcgen 0.13.2", "redb", "regex", "rustls", @@ -2424,7 +2438,7 @@ dependencies = [ "serde", "snafu", "struct_iterable", - "strum", + "strum 0.26.3", "tokio", "tokio-rustls", "tokio-rustls-acme", @@ -2475,16 +2489,16 @@ dependencies = [ [[package]] name = "iroh-quinn" version = "0.14.0" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#70e28875923db76f8dfbf4f058e682d56e6daea1" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#e84f16856ea3ac79ed6c206b258d33a30d87834f" dependencies = [ "bytes", "cfg_aliases", "iroh-quinn-proto", - "iroh-quinn-udp", + "iroh-quinn-udp 0.5.12", "pin-project-lite", "rustc-hash", "rustls", - "socket2", + "socket2 0.5.10", "thiserror 2.0.12", "tokio", "tracing", @@ -2494,7 +2508,7 @@ dependencies = [ [[package]] name = "iroh-quinn-proto" version = "0.13.0" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#70e28875923db76f8dfbf4f058e682d56e6daea1" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#e84f16856ea3ac79ed6c206b258d33a30d87834f" dependencies = [ "bytes", "fastbloom", @@ -2513,22 +2527,36 @@ dependencies = [ "web-time", ] +[[package]] +name = "iroh-quinn-udp" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c53afaa1049f7c83ea1331f5ebb9e6ebc5fdd69c468b7a22dd598b02c9bcc973" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.10", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "iroh-quinn-udp" version = "0.5.12" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#70e28875923db76f8dfbf4f058e682d56e6daea1" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#140fdf97bfb706b1cac591b9711818c5df8012f4" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.5.10", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "iroh-relay" -version = "0.90.0" +version = "0.91.0" dependencies = [ "ahash", "blake3", @@ -2538,7 +2566,7 @@ dependencies = [ "crypto_box", "dashmap", "data-encoding", - "derive_more", + "derive_more 2.0.1", "getrandom 0.3.2", "governor", "hickory-proto", @@ -2562,7 +2590,7 @@ dependencies = [ "proptest", "rand 0.8.5", "rand_chacha 0.3.1", - "rcgen", + "rcgen 0.14.3", "regex", "reloadable-state", "reqwest", @@ -2578,7 +2606,7 @@ dependencies = [ "sha1", "simdutf8", "snafu", - "strum", + "strum 0.27.2", "time", "tokio", "tokio-rustls", @@ -2677,7 +2705,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.0", + "bitflags", "libc", ] @@ -2866,7 +2894,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bb0e5d99e681ab3c938842b96fcb41bf8a7bb4bfdb11ccbd653a7e83e06c794" dependencies = [ "cfg_aliases", - "derive_more", + "derive_more 1.0.0", "futures-buffered", "futures-lite", "futures-util", @@ -2895,11 +2923,11 @@ dependencies = [ [[package]] name = "n0-watcher" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f216d4ebc5fcf9548244803cbb93f488a2ae160feba3706cd17040d69cf7a368" +checksum = "c31462392a10d5ada4b945e840cbec2d5f3fee752b96c4b33eb41414d8f45c2a" dependencies = [ - "derive_more", + "derive_more 1.0.0", "n0-future", "snafu", ] @@ -2918,19 +2946,19 @@ dependencies = [ [[package]] name = "netdev" -version = "0.31.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f901362e84cd407be6f8cd9d3a46bccf09136b095792785401ea7d283c79b91d" +checksum = "862209dce034f82a44c95ce2b5183730d616f2a68746b9c1959aa2572e77c0a1" dependencies = [ "dlopen2", "ipnet", "libc", "netlink-packet-core", - "netlink-packet-route 0.17.1", + "netlink-packet-route 0.22.0", "netlink-sys", "once_cell", "system-configuration", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2946,26 +2974,27 @@ dependencies = [ [[package]] name = "netlink-packet-route" -version = "0.17.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" +checksum = "fc0e7987b28514adf555dc1f9a5c30dfc3e50750bbaffb1aec41ca7b23dcd8e4" dependencies = [ "anyhow", - "bitflags 1.3.2", + "bitflags", "byteorder", "libc", + "log", "netlink-packet-core", "netlink-packet-utils", ] [[package]] name = "netlink-packet-route" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0800eae8638a299eaa67476e1c6b6692922273e0f7939fd188fc861c837b9cd2" +checksum = "56d83370a96813d7c977f8b63054f1162df6e5784f1c598d689236564fb5a6f2" dependencies = [ "anyhow", - "bitflags 2.9.0", + "bitflags", "byteorder", "libc", "log", @@ -3014,14 +3043,15 @@ dependencies = [ [[package]] name = "netwatch" -version = "0.6.0" -source = "git+https://github.com/n0-computer/net-tools?branch=feat-multipath#b7ab98d4ff9cc947f2f084004b4cc2a979bb4d06" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901dbb408894af3df3fc51420ba0c6faf3a7d896077b797c39b7001e2f787bd" dependencies = [ "atomic-waker", "bytes", "cfg_aliases", - "derive_more", - "iroh-quinn-udp", + "derive_more 2.0.1", + "iroh-quinn-udp 0.5.7", "js-sys", "libc", "n0-future", @@ -3029,20 +3059,20 @@ dependencies = [ "nested_enum_utils", "netdev", "netlink-packet-core", - "netlink-packet-route 0.23.0", + "netlink-packet-route 0.24.0", "netlink-proto", "netlink-sys", "pin-project-lite", "serde", "snafu", - "socket2", + "socket2 0.6.0", "time", "tokio", "tokio-util", "tracing", "web-sys", - "windows 0.59.0", - "windows-result 0.3.2", + "windows 0.61.3", + "windows-result 0.3.4", "wmi", ] @@ -3169,12 +3199,6 @@ dependencies = [ "libc", ] -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - [[package]] name = "object" version = "0.36.7" @@ -3511,13 +3535,13 @@ checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" [[package]] name = "portmapper" -version = "0.6.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d82975dc029c00d566f4e0f61f567d31f0297a290cb5416b5580dd8b4b54ade" +checksum = "62f1975debe62a70557e42b9ff9466e4890cf9d3d156d296408a711f1c5f642b" dependencies = [ "base64", "bytes", - "derive_more", + "derive_more 2.0.1", "futures-lite", "futures-util", "hyper-util", @@ -3527,11 +3551,11 @@ dependencies = [ "nested_enum_utils", "netwatch", "num_enum", - "rand 0.8.5", + "rand 0.9.1", "serde", "smallvec", "snafu", - "socket2", + "socket2 0.6.0", "time", "tokio", "tokio-util", @@ -3660,7 +3684,7 @@ checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.9.0", + "bitflags", "lazy_static", "num-traits", "rand 0.8.5", @@ -3706,7 +3730,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.5.10", "thiserror 2.0.12", "tokio", "tracing", @@ -3742,7 +3766,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.5.10", "tracing", "windows-sys 0.59.0", ] @@ -3846,7 +3870,7 @@ version = "11.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" dependencies = [ - "bitflags 2.9.0", + "bitflags", ] [[package]] @@ -3882,6 +3906,19 @@ dependencies = [ "yasna", ] +[[package]] +name = "rcgen" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0068c5b3cab1d4e271e0bb6539c87563c43411cad90b057b15c79958fbeb41f7" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + [[package]] name = "redb" version = "2.4.0" @@ -3897,7 +3934,7 @@ version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags 2.9.0", + "bitflags", ] [[package]] @@ -4079,7 +4116,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -4282,7 +4319,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags 2.9.0", + "bitflags", "core-foundation 0.10.0", "core-foundation-sys", "libc", @@ -4391,9 +4428,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ "serde", ] @@ -4502,7 +4539,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee851d0e5e7af3721faea1843e8015e820a234f81fda3dea9247e15bac9a86a" dependencies = [ - "bitflags 2.9.0", + "bitflags", ] [[package]] @@ -4564,6 +4601,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "spin" version = "0.9.8" @@ -4639,7 +4686,16 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros", + "strum_macros 0.26.4", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros 0.27.2", ] [[package]] @@ -4655,6 +4711,18 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "stun-rs" version = "0.1.11" @@ -4695,7 +4763,7 @@ dependencies = [ "parking_lot", "pnet_packet", "rand 0.9.1", - "socket2", + "socket2 0.5.10", "thiserror 1.0.69", "tokio", "tracing", @@ -4710,7 +4778,7 @@ dependencies = [ "acto", "hickory-proto", "rand 0.9.1", - "socket2", + "socket2 0.5.10", "thiserror 2.0.12", "tokio", "tracing", @@ -4764,7 +4832,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.0", + "bitflags", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -4939,7 +5007,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.10", "tokio-macros", "windows-sys 0.52.0", ] @@ -4980,7 +5048,7 @@ dependencies = [ "num-bigint", "pem", "proc-macro2", - "rcgen", + "rcgen 0.13.2", "reqwest", "ring", "rustls", @@ -5045,14 +5113,17 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.22" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1" dependencies = [ + "indexmap", "serde", "serde_spanned", - "toml_datetime", - "toml_edit", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] @@ -5060,6 +5131,12 @@ name = "toml_datetime" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" dependencies = [ "serde", ] @@ -5071,18 +5148,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", + "toml_datetime 0.6.9", "winnow", ] [[package]] -name = "toml_write" -version = "0.1.1" +name = "toml_parser" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" [[package]] name = "tower" @@ -5106,7 +5189,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" dependencies = [ - "bitflags 2.9.0", + "bitflags", "bytes", "http 1.3.1", "http-body", @@ -5318,6 +5401,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unit-prefix" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" + [[package]] name = "universal-hash" version = "0.5.1" @@ -5630,7 +5719,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -5651,12 +5740,24 @@ dependencies = [ [[package]] name = "windows" -version = "0.59.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-core 0.59.0", - "windows-targets 0.53.0", + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", ] [[package]] @@ -5680,22 +5781,33 @@ checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" dependencies = [ "windows-implement 0.59.0", "windows-interface 0.59.1", - "windows-result 0.3.2", + "windows-result 0.3.4", "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-targets 0.53.3", ] [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement 0.60.0", "windows-interface 0.59.1", "windows-link", - "windows-result 0.3.2", - "windows-strings 0.4.0", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link", + "windows-threading", ] [[package]] @@ -5755,9 +5867,19 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link", +] [[package]] name = "windows-registry" @@ -5765,9 +5887,9 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ - "windows-result 0.3.2", + "windows-result 0.3.4", "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-targets 0.53.3", ] [[package]] @@ -5781,9 +5903,9 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] @@ -5809,9 +5931,9 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -5852,6 +5974,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -5900,10 +6031,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -5914,6 +6046,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -6119,22 +6260,22 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags", ] [[package]] name = "wmi" -version = "0.14.5" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7787dacdd8e71cbc104658aade4009300777f9b5fda6a75f19145fedb8a18e71" +checksum = "3d3de777dce4cbcdc661d5d18e78ce4b46a37adc2bb7c0078a556c7f07bcce2f" dependencies = [ "chrono", "futures", "log", "serde", "thiserror 2.0.12", - "windows 0.59.0", - "windows-core 0.59.0", + "windows 0.61.3", + "windows-core 0.61.2", ] [[package]] @@ -6333,3 +6474,8 @@ dependencies = [ "quote", "syn 2.0.101", ] + +[[patch.unused]] +name = "netwatch" +version = "0.6.0" +source = "git+https://github.com/n0-computer/net-tools?branch=feat-multipath#b7ab98d4ff9cc947f2f084004b4cc2a979bb4d06" diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index f47bcdc996e..892000b2d3b 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -29,7 +29,6 @@ use std::{ }; use bytes::Bytes; -use data_encoding::HEXLOWER; use iroh_base::{NodeAddr, NodeId, PublicKey, RelayUrl, SecretKey}; use iroh_relay::RelayMap; use n0_future::{ @@ -59,7 +58,7 @@ use url::Url; use self::transports::IpTransport; use self::{ metrics::Metrics as MagicsockMetrics, - node_map::{NodeMap, PingAction, PingRole, SendPing}, + node_map::{NodeMap, PingAction}, transports::{RelayActorConfig, RelayTransport, Transports, UdpSender}, }; #[cfg(not(wasm_browser))] @@ -876,13 +875,8 @@ impl MagicSock { let _guard = span.enter(); trace!("receive disco message"); match dm { - disco::Message::Ping(ping) => { - self.metrics.magicsock.recv_disco_ping.inc(); - self.handle_ping(ping, sender, src); - } - disco::Message::Pong(pong) => { - self.metrics.magicsock.recv_disco_pong.inc(); - self.node_map.handle_pong(sender, src, pong); + disco::Message::Ping(..) | disco::Message::Pong(..) => { + unreachable!("not used anymore"); } disco::Message::CallMeMaybe(cm) => { self.metrics.magicsock.recv_disco_call_me_maybe.inc(); @@ -909,99 +903,19 @@ impl MagicSock { direct_addresses: cm.my_numbers.iter().copied().collect(), }); - let ping_actions = - self.node_map - .handle_call_me_maybe(sender, cm, &self.metrics.magicsock); - - for action in ping_actions { - match action { - PingAction::SendCallMeMaybe { .. } => { - warn!("Unexpected CallMeMaybe as response of handling a CallMeMaybe"); - } - PingAction::SendPing(ping) => { - self.send_ping_queued(ping); - } - } - } + self.node_map + .handle_call_me_maybe(sender, cm, &self.metrics.magicsock); } } trace!("disco message handled"); } - /// Handle a ping message. - fn handle_ping(&self, dm: disco::Ping, sender: NodeId, src: &transports::Addr) { - // Insert the ping into the node map, and return whether a ping with this tx_id was already - // received. - let addr: SendAddr = src.clone().into(); - let handled = self.node_map.handle_ping(sender, addr.clone(), dm.tx_id); - match handled.role { - PingRole::Duplicate => { - debug!(?src, tx = %HEXLOWER.encode(&dm.tx_id), "received ping: path already confirmed, skip"); - return; - } - PingRole::LikelyHeartbeat => {} - PingRole::NewPath => { - debug!(?src, tx = %HEXLOWER.encode(&dm.tx_id), "received ping: new path"); - } - PingRole::Activate => { - debug!(?src, tx = %HEXLOWER.encode(&dm.tx_id), "received ping: path active"); - } - } - - // Send a pong. - debug!(tx = %HEXLOWER.encode(&dm.tx_id), %addr, dstkey = %sender.fmt_short(), - "sending pong"); - let pong = disco::Message::Pong(disco::Pong { - tx_id: dm.tx_id, - ping_observed_addr: addr.clone(), - }); - event!( - target: "iroh::_events::pong::sent", - Level::DEBUG, - remote_node = %sender.fmt_short(), - dst = ?addr, - txn = ?dm.tx_id, - ); - - if !self.disco.try_send(addr.clone(), sender, pong) { - warn!(%addr, "failed to queue pong"); - } - - if let Some(ping) = handled.needs_ping_back { - debug!( - %addr, - dstkey = %sender.fmt_short(), - "sending direct ping back", - ); - self.send_ping_queued(ping); - } - } - - fn send_ping_queued(&self, ping: SendPing) { - let SendPing { - id, - dst, - dst_node, - tx_id, - purpose, - } = ping; - let msg = disco::Message::Ping(disco::Ping { - tx_id, - node_key: self.public_key, - }); - let sent = self.disco.try_send(dst.clone(), dst_node, msg); - if sent { - let msg_sender = self.actor_sender.clone(); - trace!(%dst, tx = %HEXLOWER.encode(&tx_id), ?purpose, "ping sent (queued)"); - self.node_map - .notify_ping_sent(id, dst, tx_id, purpose, msg_sender); - } else { - warn!(dst = ?dst, tx = %HEXLOWER.encode(&tx_id), ?purpose, "failed to send ping: queues full"); - } - } - /// Send the given ping actions out. - async fn send_ping_actions(&self, sender: &UdpSender, msgs: Vec) -> io::Result<()> { + async fn send_ping_actions( + &self, + _sender: &UdpSender, + msgs: Vec, + ) -> io::Result<()> { for msg in msgs { // Abort sending as soon as we know we are shutting down. if self.is_closing() || self.is_closed() { @@ -1046,25 +960,6 @@ impl MagicSock { } } } - PingAction::SendPing(SendPing { - id, - dst, - dst_node, - tx_id, - purpose, - }) => { - let msg = disco::Message::Ping(disco::Ping { - tx_id, - node_key: self.public_key, - }); - - self.send_disco_message(sender, dst.clone(), dst_node, msg) - .await?; - debug!(%dst, tx = %HEXLOWER.encode(&tx_id), ?purpose, "ping sent"); - let msg_sender = self.actor_sender.clone(); - self.node_map - .notify_ping_sent(id, dst, tx_id, purpose, msg_sender); - } } } Ok(()) @@ -1741,7 +1636,6 @@ impl AsyncUdpSocket for MagicUdpSocket { #[derive(Debug)] enum ActorMessage { PingActions(Vec), - EndpointPingExpired(usize, stun_rs::TransactionId), NetworkChange, ScheduleDirectAddrUpdate(UpdateReason, Option<(NodeId, RelayUrl)>), #[cfg(test)] @@ -2036,9 +1930,6 @@ impl Actor { /// Returns `true` if it was a shutdown. async fn handle_actor_message(&mut self, msg: ActorMessage, sender: &UdpSender) { match msg { - ActorMessage::EndpointPingExpired(id, txid) => { - self.msock.node_map.notify_ping_timeout(id, txid); - } ActorMessage::NetworkChange => { self.network_monitor.network_change().await.ok(); } diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index a67a570c3dd..11b135046e3 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -8,10 +8,9 @@ use std::{ use iroh_base::{NodeAddr, NodeId, PublicKey, RelayUrl}; use n0_future::time::Instant; use serde::{Deserialize, Serialize}; -use stun_rs::TransactionId; use tracing::{debug, info, instrument, trace, warn}; -use self::node_state::{NodeState, Options, PingHandled}; +use self::node_state::{NodeState, Options}; use super::{ActorMessage, NodeIdMappedAddr, metrics::Metrics, transports}; use crate::disco::{CallMeMaybe, Pong, SendAddr}; #[cfg(any(test, feature = "test-utils"))] @@ -22,8 +21,8 @@ mod path_state; mod path_validity; mod udp_paths; +pub(super) use node_state::PingAction; pub use node_state::{ConnectionType, ControlMsg, DirectAddrInfo, RemoteInfo}; -pub(super) use node_state::{DiscoPingPurpose, PingAction, PingRole, SendPing}; /// Number of nodes that are inactive for which we keep info about. This limit is enforced /// periodically via [`NodeMap::prune_inactive`]. @@ -68,7 +67,6 @@ pub(super) struct NodeMapInner { /// have for the node. These are all the keys the [`NodeMap`] can use. #[derive(Debug, Clone)] enum NodeStateKey { - Idx(usize), NodeId(NodeId), NodeIdMappedAddr(NodeIdMappedAddr), IpPort(IpPort), @@ -168,35 +166,6 @@ impl NodeMap { .receive_relay(relay_url, src) } - pub(super) fn notify_ping_sent( - &self, - id: usize, - dst: SendAddr, - tx_id: stun_rs::TransactionId, - purpose: DiscoPingPurpose, - msg_sender: tokio::sync::mpsc::Sender, - ) { - if let Some(ep) = self - .inner - .lock() - .expect("poisoned") - .get_mut(NodeStateKey::Idx(id)) - { - ep.ping_sent(dst, tx_id, purpose, msg_sender); - } - } - - pub(super) fn notify_ping_timeout(&self, id: usize, tx_id: stun_rs::TransactionId) { - if let Some(ep) = self - .inner - .lock() - .expect("poisoned") - .get_mut(NodeStateKey::Idx(id)) - { - ep.ping_timeout(tx_id, Instant::now()); - } - } - pub(super) fn get_quic_mapped_addr_for_node_key( &self, node_key: NodeId, @@ -208,38 +177,16 @@ impl NodeMap { .map(|ep| *ep.quic_mapped_addr()) } - /// Insert a received ping into the node map, and return whether a ping with this tx_id was already - /// received. - pub(super) fn handle_ping( - &self, - sender: PublicKey, - src: SendAddr, - tx_id: TransactionId, - ) -> PingHandled { - self.inner - .lock() - .expect("poisoned") - .handle_ping(sender, src, tx_id) - } - - pub(super) fn handle_pong(&self, sender: PublicKey, src: &transports::Addr, pong: Pong) { - self.inner - .lock() - .expect("poisoned") - .handle_pong(sender, src, pong) - } - - #[must_use = "actions must be handled"] pub(super) fn handle_call_me_maybe( &self, sender: PublicKey, cm: CallMeMaybe, metrics: &Metrics, - ) -> Vec { + ) { self.inner .lock() .expect("poisoned") - .handle_call_me_maybe(sender, cm, metrics) + .handle_call_me_maybe(sender, cm, metrics); } #[allow(clippy::type_complexity)] @@ -406,7 +353,6 @@ impl NodeMapInner { fn get_id(&self, id: NodeStateKey) -> Option { match id { - NodeStateKey::Idx(id) => Some(id), NodeStateKey::NodeId(node_key) => self.by_node_key.get(&node_key).copied(), NodeStateKey::NodeIdMappedAddr(addr) => self.by_quic_mapped_addr.get(&addr).copied(), NodeStateKey::IpPort(ipp) => self.by_ip_port.get(&ipp).copied(), @@ -502,25 +448,7 @@ impl NodeMapInner { .map(|ep| ep.conn_type()) } - fn handle_pong(&mut self, sender: NodeId, src: &transports::Addr, pong: Pong) { - if let Some(ns) = self.get_mut(NodeStateKey::NodeId(sender)).as_mut() { - let insert = ns.handle_pong(&pong, src.clone().into()); - if let Some((src, key)) = insert { - self.set_node_key_for_ip_port(src, &key); - } - trace!(?insert, "received pong") - } else { - warn!("received pong: node unknown, ignore") - } - } - - #[must_use = "actions must be handled"] - fn handle_call_me_maybe( - &mut self, - sender: NodeId, - cm: CallMeMaybe, - metrics: &Metrics, - ) -> Vec { + fn handle_call_me_maybe(&mut self, sender: NodeId, cm: CallMeMaybe, metrics: &Metrics) { let ns_id = NodeStateKey::NodeId(sender); if let Some(id) = self.get_id(ns_id.clone()) { for number in &cm.my_numbers { @@ -532,43 +460,13 @@ impl NodeMapInner { None => { debug!("received call-me-maybe: ignore, node is unknown"); metrics.recv_disco_call_me_maybe_bad_disco.inc(); - vec![] } Some(ns) => { debug!(endpoints = ?cm.my_numbers, "received call-me-maybe"); - ns.handle_call_me_maybe(cm) - } - } - } - - fn handle_ping(&mut self, sender: NodeId, src: SendAddr, tx_id: TransactionId) -> PingHandled { - #[cfg(any(test, feature = "test-utils"))] - let path_selection = self.path_selection; - let node_state = self.get_or_insert_with(NodeStateKey::NodeId(sender), || { - debug!("received ping: node unknown, add to node map"); - let source = if src.is_relay() { - Source::Relay - } else { - Source::Udp - }; - Options { - node_id: sender, - relay_url: src.relay_url(), - active: true, - source, - #[cfg(any(test, feature = "test-utils"))] - path_selection, - } - }); - - let handled = node_state.handle_ping(src.clone(), tx_id); - if let SendAddr::Udp(ref addr) = src { - if matches!(handled.role, PingRole::NewPath) { - self.set_node_key_for_ip_port(*addr, &sender); + ns.handle_call_me_maybe(cm); } } - handled } /// Inserts a new node into the [`NodeMap`]. @@ -706,6 +604,7 @@ mod tests { use tracing_test::traced_test; use super::{node_state::MAX_INACTIVE_DIRECT_ADDRESSES, *}; + use crate::disco::SendAddr; impl NodeMap { #[track_caller] @@ -838,7 +737,7 @@ mod tests { let txid = stun_rs::TransactionId::from([i as u8; 12]); // Note that this already invokes .prune_direct_addresses() because these are // new UDP paths. - endpoint.handle_ping(addr, txid); + // endpoint.handle_ping(addr, txid); } info!("Pruning addresses"); diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index 5a84f5ae378..26a4a6d63a5 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -1,19 +1,13 @@ use std::{ - collections::{BTreeSet, HashMap, btree_map::Entry}, - hash::Hash, + collections::{BTreeSet, HashMap}, net::{IpAddr, SocketAddr}, sync::atomic::AtomicBool, }; -use data_encoding::HEXLOWER; use iroh_base::{NodeAddr, NodeId, PublicKey, RelayUrl}; -use n0_future::{ - task::{self, AbortOnDropHandle}, - time::{self, Duration, Instant}, -}; +use n0_future::time::{Duration, Instant}; use n0_watcher::Watchable; use serde::{Deserialize, Serialize}; -use tokio::sync::mpsc; use tracing::{Level, debug, event, info, instrument, trace, warn}; use super::{ @@ -39,9 +33,6 @@ pub(super) const MAX_INACTIVE_DIRECT_ADDRESSES: usize = 20; /// How long since an endpoint path was last alive before it might be pruned. const LAST_ALIVE_PRUNE_DURATION: Duration = Duration::from_secs(120); -/// How long we wait for a pong reply before assuming it's never coming. -const PING_TIMEOUT_DURATION: Duration = Duration::from_secs(5); - /// 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); @@ -52,46 +43,12 @@ pub(super) const SESSION_ACTIVE_TIMEOUT: Duration = Duration::from_secs(45); /// How often we try to upgrade to a better patheven if we have some non-relay route that works. const UPGRADE_INTERVAL: Duration = Duration::from_secs(60); -/// How long until we send a stayin alive ping -const STAYIN_ALIVE_MIN_ELAPSED: Duration = Duration::from_secs(2); - #[derive(Debug)] pub(in crate::magicsock) enum PingAction { SendCallMeMaybe { relay_url: RelayUrl, dst_node: NodeId, }, - SendPing(SendPing), -} - -#[derive(Debug)] -pub(in crate::magicsock) struct SendPing { - pub id: usize, - pub dst: SendAddr, - pub dst_node: NodeId, - pub tx_id: stun_rs::TransactionId, - pub purpose: DiscoPingPurpose, -} - -/// Indicating an [`NodeState`] has handled a ping. -#[derive(Debug)] -pub struct PingHandled { - /// What this ping did to the [`NodeState`]. - pub role: PingRole, - /// Whether the sender path should also be pinged. - /// - /// This is the case if an [`NodeState`] does not yet have a direct path, i.e. it has no - /// best_addr. In this case we want to ping right back to open the direct path in this - /// direction as well. - pub needs_ping_back: Option, -} - -#[derive(Debug)] -pub enum PingRole { - Duplicate, - NewPath, - LikelyHeartbeat, - Activate, } /// An iroh node, which we can have connections with. @@ -109,14 +66,11 @@ pub(super) struct NodeState { quic_mapped_addr: NodeIdMappedAddr, /// The global identifier for this endpoint. node_id: NodeId, - /// The last time we pinged all endpoints. - last_full_ping: Option, /// The url of relay node that we can relay over to communicate. /// /// The fallback/bootstrap path, if non-zero (non-zero for well-behaved clients). relay_url: Option<(RelayUrl, PathState)>, udp_paths: NodeUdpPaths, - sent_pings: HashMap, /// Last time this node was used. /// /// A node is marked as in use when sending datagrams to them, or when having received @@ -174,7 +128,6 @@ impl NodeState { id, quic_mapped_addr, node_id: options.node_id, - last_full_ping: None, relay_url: options.relay_url.map(|url| { ( url.clone(), @@ -182,7 +135,6 @@ impl NodeState { ) }), udp_paths: NodeUdpPaths::new(), - sent_pings: HashMap::new(), last_used: options.active.then(Instant::now), last_call_me_maybe: None, conn_type: Watchable::new(ConnectionType::None), @@ -211,32 +163,7 @@ impl NodeState { /// Returns info about this node. pub(super) fn info(&self, now: Instant) -> RemoteInfo { let conn_type = self.conn_type.get(); - let latency = match conn_type { - ConnectionType::Direct(addr) => self - .udp_paths - .paths - .get(&addr.into()) - .and_then(|state| state.latency()), - ConnectionType::Relay(ref url) => self - .relay_url - .as_ref() - .filter(|(relay_url, _)| relay_url == url) - .and_then(|(_, state)| state.latency()), - ConnectionType::Mixed(addr, ref url) => { - let addr_latency = self - .udp_paths - .paths - .get(&addr.into()) - .and_then(|state| state.latency()); - let relay_latency = self - .relay_url - .as_ref() - .filter(|(relay_url, _)| relay_url == url) - .and_then(|(_, state)| state.latency()); - addr_latency.min(relay_latency) - } - ConnectionType::None => None, - }; + let latency = None; let addrs = self .udp_paths @@ -396,10 +323,6 @@ impl NodeState { #[instrument("want_call_me_maybe", skip_all)] fn want_call_me_maybe(&self, now: &Instant) -> bool { trace!("full ping: wanted?"); - let Some(last_full_ping) = self.last_full_ping else { - debug!("no previous full ping: need full ping"); - return true; - }; match &self.udp_paths.best { UdpSendAddr::None | UdpSendAddr::Unconfirmed(_) => { debug!("best addr not set: need full ping"); @@ -417,7 +340,7 @@ impl NodeState { .expect("send path not tracked?") .latency() .expect("send_addr marked valid incorrectly"); - if latency > GOOD_ENOUGH_LATENCY && *now - last_full_ping >= UPGRADE_INTERVAL { + if latency > GOOD_ENOUGH_LATENCY { debug!( "full ping interval expired and latency is only {}ms: need full ping", latency.as_millis() @@ -437,131 +360,6 @@ impl NodeState { false } - /// Cleanup the expired ping for the passed in txid. - #[instrument("disco", skip_all, fields(node = %self.node_id.fmt_short()))] - pub(super) fn ping_timeout(&mut self, txid: stun_rs::TransactionId, now: Instant) { - if let Some(sp) = self.sent_pings.remove(&txid) { - debug!(tx = %HEXLOWER.encode(&txid), addr = %sp.to, "pong not received in timeout"); - match sp.to { - SendAddr::Udp(addr) => { - if let Some(path_state) = self.udp_paths.paths.get_mut(&addr.into()) { - path_state.last_ping = None; - let consider_alive = path_state - .last_alive() - .map(|last_alive| last_alive.elapsed() <= PING_TIMEOUT_DURATION) - .unwrap_or(false); - if !consider_alive { - // If there was no sign of life from this path during the time - // which we should have received the pong, clear best addr and - // pong. Both are used to select this path again, but we know - // it's not a usable path now. - path_state.validity = PathValidity::empty(); - self.udp_paths.update_to_best_addr(now); - } - } else { - // If we have no state for the best addr it should have been cleared - // anyway. - self.udp_paths.update_to_best_addr(now); - } - } - SendAddr::Relay(ref url) => { - if let Some((home_relay, relay_state)) = self.relay_url.as_mut() { - if home_relay == url { - // lost connectivity via relay - relay_state.last_ping = None; - } - } - } - } - } - } - - #[must_use = "pings must be handled"] - fn start_ping(&self, dst: SendAddr, purpose: DiscoPingPurpose) -> Option { - #[cfg(any(test, feature = "test-utils"))] - if self.path_selection == PathSelection::RelayOnly && !dst.is_relay() { - // don't attempt any hole punching in relay only mode - warn!("in `RelayOnly` mode, ignoring request to start a hole punching attempt."); - return None; - } - #[cfg(wasm_browser)] - if !dst.is_relay() { - return None; // Similar to `RelayOnly` mode, we don't send UDP pings for hole-punching. - } - - let tx_id = stun_rs::TransactionId::default(); - trace!(tx = %HEXLOWER.encode(&tx_id), %dst, ?purpose, - dst = %self.node_id.fmt_short(), "start ping"); - event!( - target: "iroh::_events::ping::sent", - Level::DEBUG, - remote_node = %self.node_id.fmt_short(), - ?dst, - txn = ?tx_id, - ?purpose, - ); - Some(SendPing { - id: self.id, - dst, - dst_node: self.node_id, - tx_id, - purpose, - }) - } - - /// Record the fact that a ping has been sent out. - pub(super) fn ping_sent( - &mut self, - to: SendAddr, - tx_id: stun_rs::TransactionId, - purpose: DiscoPingPurpose, - sender: mpsc::Sender, - ) { - trace!(%to, tx = %HEXLOWER.encode(&tx_id), ?purpose, "record ping sent"); - - let now = Instant::now(); - let mut path_found = false; - match to { - SendAddr::Udp(addr) => { - if let Some(st) = self.udp_paths.paths.get_mut(&addr.into()) { - st.last_ping.replace(now); - path_found = true - } - } - SendAddr::Relay(ref url) => { - if let Some((home_relay, relay_state)) = self.relay_url.as_mut() { - if home_relay == url { - relay_state.last_ping.replace(now); - path_found = true - } - } - } - } - if !path_found { - // Shouldn't happen. But don't ping an endpoint that's not active for us. - warn!(%to, ?purpose, "unexpected attempt to ping no longer live path"); - return; - } - - let id = self.id; - let _expiry_task = AbortOnDropHandle::new(task::spawn(async move { - time::sleep(PING_TIMEOUT_DURATION).await; - sender - .send(ActorMessage::EndpointPingExpired(id, tx_id)) - .await - .ok(); - })); - self.sent_pings.insert( - tx_id, - SentPing { - to, - at: now, - purpose, - _expiry_task, - }, - ); - } - /// Send a DISCO call-me-maybe message to the peer. /// /// This takes care of sending the needed pings beforehand. This ensures that we open @@ -588,11 +386,7 @@ impl NodeState { } } } - // We send pings regardless of whether we have a RelayUrl. If we were given any - // direct address paths to contact but no RelayUrl, we still need to send a DISCO - // ping to the direct address paths so that the other node will learn about us and - // accepts the connection. - let mut msgs = self.send_pings(now); + let mut msgs = Vec::new(); if let Some(url) = self.relay_url() { debug!(%url, "queue call-me-maybe"); @@ -608,59 +402,6 @@ impl NodeState { msgs } - /// Send DISCO Pings to all the paths of this node. - /// - /// Any paths to the node which have not been recently pinged will be sent a disco - /// ping. - /// - /// The caller is responsible for sending the messages. - #[must_use = "actions must be handled"] - fn send_pings(&mut self, now: Instant) -> Vec { - // We allocate +1 in case the caller wants to add a call-me-maybe message. - let mut ping_msgs = Vec::with_capacity(self.udp_paths.paths.len() + 1); - - if let Some((url, state)) = self.relay_url.as_ref() { - if state.needs_ping(&now) { - debug!(%url, "relay path needs ping"); - if let Some(msg) = - self.start_ping(SendAddr::Relay(url.clone()), DiscoPingPurpose::Discovery) - { - ping_msgs.push(PingAction::SendPing(msg)) - } - } - } - - #[cfg(any(test, feature = "test-utils"))] - if self.path_selection == PathSelection::RelayOnly { - warn!("in `RelayOnly` mode, ignoring request to respond to a hole punching attempt."); - return ping_msgs; - } - - self.prune_direct_addresses(now); - let mut ping_dsts = String::from("["); - self.udp_paths - .paths - .iter() - .filter_map(|(ipp, state)| state.needs_ping(&now).then_some(*ipp)) - .filter_map(|ipp| { - self.start_ping(SendAddr::Udp(ipp.into()), DiscoPingPurpose::Discovery) - }) - .for_each(|msg| { - use std::fmt::Write; - write!(&mut ping_dsts, " {} ", msg.dst).ok(); - ping_msgs.push(PingAction::SendPing(msg)); - }); - ping_dsts.push(']'); - debug!( - %ping_dsts, - dst = %self.node_id.fmt_short(), - paths = %summarize_node_paths(&self.udp_paths.paths), - "sending pings to node", - ); - self.last_full_ping.replace(now); - ping_msgs - } - pub(super) fn update_from_node_addr( &mut self, new_relay_url: Option<&RelayUrl>, @@ -713,114 +454,6 @@ impl NodeState { debug!(new = ?new_addrs , %paths, "added new direct paths for endpoint"); } - /// Handle a received Disco Ping. - /// - /// - Ensures the paths the ping was received on is a known path for this endpoint. - /// - /// - If there is no best_addr for this endpoint yet, sends a ping itself to try and - /// establish one. - /// - /// This is called once we've already verified that we got a valid discovery message - /// from `self` via ep. - pub(super) fn handle_ping( - &mut self, - path: SendAddr, - tx_id: stun_rs::TransactionId, - ) -> PingHandled { - let now = Instant::now(); - - let role = match path { - SendAddr::Udp(addr) => match self.udp_paths.paths.entry(addr.into()) { - Entry::Occupied(mut occupied) => occupied.get_mut().handle_ping(tx_id, now), - Entry::Vacant(vacant) => { - info!(%addr, "new direct addr for node"); - vacant.insert(PathState::with_ping( - self.node_id, - path.clone(), - tx_id, - Source::Udp, - now, - )); - PingRole::NewPath - } - }, - SendAddr::Relay(ref url) => { - match self.relay_url.as_mut() { - Some((home_url, _state)) if home_url != url => { - // either the node changed relays or we didn't have a relay address for the - // node. In both cases, trust the new confirmed url - info!(%url, "new relay addr for node"); - self.relay_url = Some(( - url.clone(), - PathState::with_ping( - self.node_id, - path.clone(), - tx_id, - Source::Relay, - now, - ), - )); - PingRole::NewPath - } - Some((_home_url, state)) => state.handle_ping(tx_id, now), - None => { - info!(%url, "new relay addr for node"); - self.relay_url = Some(( - url.clone(), - PathState::with_ping( - self.node_id, - path.clone(), - tx_id, - Source::Relay, - now, - ), - )); - PingRole::NewPath - } - } - } - }; - event!( - target: "iroh::_events::ping::recv", - Level::DEBUG, - remote_node = %self.node_id.fmt_short(), - src = ?path, - txn = ?tx_id, - ?role, - ); - - if matches!(path, SendAddr::Udp(_)) && matches!(role, PingRole::NewPath) { - self.prune_direct_addresses(now); - } - - // if the endpoint does not yet have a best_addr - let needs_ping_back = if matches!(path, SendAddr::Udp(_)) - && matches!( - self.udp_paths.best, - UdpSendAddr::None | UdpSendAddr::Unconfirmed(_) | UdpSendAddr::Outdated(_) - ) { - // We also need to send a ping to make this path available to us as well. This - // is always sent together with a pong. So in the worst case the pong gets lost - // and this ping does not. In that case we ping-pong until both sides have - // received at least one pong. Once both sides have received one pong they both - // have a best_addr and this ping will stop being sent. - self.start_ping(path, DiscoPingPurpose::PingBack) - } else { - None - }; - - debug!( - ?role, - needs_ping_back = ?needs_ping_back.is_some(), - paths = %summarize_node_paths(&self.udp_paths.paths), - "endpoint handled ping", - ); - PingHandled { - role, - needs_ping_back, - } - } - /// Prune inactive paths. /// /// This trims the list of inactive paths for an endpoint. At most @@ -873,105 +506,6 @@ impl NodeState { self.udp_paths.update_to_best_addr(now); } - /// Handles a Pong message (a reply to an earlier ping). - /// - /// It reports the address and key that should be inserted for the endpoint if any. - #[instrument(skip(self))] - pub(super) fn handle_pong( - &mut self, - m: &disco::Pong, - src: SendAddr, - ) -> Option<(SocketAddr, PublicKey)> { - event!( - target: "iroh::_events::pong::recv", - Level::DEBUG, - remote_node = %self.node_id.fmt_short(), - ?src, - txn = ?m.tx_id, - ); - let is_relay = src.is_relay(); - match self.sent_pings.remove(&m.tx_id) { - None => { - // This is not a pong for a ping we sent. In reality however we probably - // did send this ping but it has timed-out by the time we receive this pong - // so we removed the state already. - debug!(tx = %HEXLOWER.encode(&m.tx_id), "received unknown pong (did it timeout?)"); - None - } - Some(sp) => { - let mut node_map_insert = None; - - let now = Instant::now(); - let latency = now - sp.at; - - debug!( - tx = %HEXLOWER.encode(&m.tx_id), - src = %src, - reported_ping_src = %m.ping_observed_addr, - ping_dst = %sp.to, - is_relay = %src.is_relay(), - latency = %latency.as_millis(), - "received pong", - ); - - match src { - SendAddr::Udp(addr) => { - match self.udp_paths.paths.get_mut(&addr.into()) { - None => { - warn!("ignoring pong: no state for src addr"); - // This is no longer an endpoint we care about. - return node_map_insert; - } - Some(st) => { - node_map_insert = Some((addr, self.node_id)); - st.add_pong_reply(PongReply { - latency, - pong_at: now, - from: src, - pong_src: m.ping_observed_addr.clone(), - }); - } - } - debug!( - paths = %summarize_node_paths(&self.udp_paths.paths), - "handled pong", - ); - } - SendAddr::Relay(ref url) => match self.relay_url.as_mut() { - Some((home_url, state)) if home_url == url => { - state.add_pong_reply(PongReply { - latency, - pong_at: now, - from: src, - pong_src: m.ping_observed_addr.clone(), - }); - } - other => { - // if we are here then we sent this ping, but the url changed - // waiting for the response. It was either set to None or changed to - // another relay. This should either never happen or be extremely - // unlikely. Log and ignore for now - warn!( - stored=?other, - received=?url, - "ignoring pong via relay for different relay from last one", - ); - } - }, - } - - // Promote this pong response to our current best address if it's lower latency. - // TODO(bradfitz): decide how latency vs. preference order affects decision - if let SendAddr::Udp(_to) = sp.to { - debug_assert!(!is_relay, "mismatching relay & udp"); - self.udp_paths.update_to_best_addr(now); - } - - node_map_insert - } - } - } - /// Handles a DISCO CallMeMaybe discovery message. /// /// The contract for use of this message is that the node has already pinged to us via @@ -982,7 +516,7 @@ impl NodeState { /// had any [`IpPort`]s to send pings to and our pings might end up blocked. But at /// least open the firewalls on our side, giving the other side another change of making /// it through when it pings in response. - pub(super) fn handle_call_me_maybe(&mut self, m: disco::CallMeMaybe) -> Vec { + pub(super) fn handle_call_me_maybe(&mut self, m: disco::CallMeMaybe) { let now = Instant::now(); let mut call_me_maybe_ipps = BTreeSet::new(); @@ -1035,7 +569,6 @@ impl NodeState { paths = %summarize_node_paths(&self.udp_paths.paths), "updated endpoint paths from call-me-maybe", ); - self.send_pings(now) } /// Marks this node as having received a UDP payload message. @@ -1114,29 +647,6 @@ impl NodeState { return self.send_call_me_maybe(now, SendCallMeMaybe::Always); } - // Send heartbeat ping to keep the current addr going as long as we need it. - if let Some(udp_addr) = self.udp_paths.best.get_addr() { - let elapsed = self.last_ping(&SendAddr::Udp(udp_addr)).map(|l| now - l); - // Send a ping if the last ping is older than 2 seconds. - let needs_ping = match elapsed { - Some(e) => e >= STAYIN_ALIVE_MIN_ELAPSED, - None => false, - }; - - if needs_ping { - debug!( - dst = %udp_addr, - since_last_ping=?elapsed, - "send stayin alive ping", - ); - if let Some(msg) = - self.start_ping(SendAddr::Udp(udp_addr), DiscoPingPurpose::StayinAlive) - { - return vec![PingAction::SendPing(msg)]; - } - } - } - Vec::new() } @@ -1214,26 +724,6 @@ enum SendCallMeMaybe { IfNoRecent, } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(super) struct PongReply { - pub(super) latency: Duration, - /// When we received the pong. - pub(super) pong_at: Instant, - /// The pong's src (usually same as endpoint map key). - pub(super) from: SendAddr, - /// What they reported they heard. - pub(super) pong_src: SendAddr, -} - -#[derive(Debug)] -pub(super) struct SentPing { - pub(super) to: SendAddr, - pub(super) at: Instant, - #[allow(dead_code)] - pub(super) purpose: DiscoPingPurpose, - pub(super) _expiry_task: AbortOnDropHandle<()>, -} - /// The reason why a discovery ping message was sent. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DiscoPingPurpose { @@ -1330,7 +820,7 @@ impl From<(RelayUrl, PathState)> for RelayUrlInfo { RelayUrlInfo { relay_url: value.0, last_alive: value.1.last_alive().map(|i| i.elapsed()), - latency: value.1.latency(), + latency: None, } } } @@ -1442,256 +932,262 @@ mod tests { use super::*; use crate::magicsock::node_map::{NodeMap, NodeMapInner}; - #[test] - fn test_remote_infos() { - let now = Instant::now(); - let elapsed = Duration::from_secs(3); - let later = now + elapsed; - let send_addr: RelayUrl = "https://my-relay.com".parse().unwrap(); - let pong_src = SendAddr::Udp("0.0.0.0:1".parse().unwrap()); - let latency = Duration::from_millis(50); - - let relay_and_state = |node_id: NodeId, url: RelayUrl| { - let relay_state = PathState::with_pong_reply( - node_id, - PongReply { - latency, - pong_at: now, - from: SendAddr::Relay(send_addr.clone()), - pong_src: pong_src.clone(), - }, - ); - Some((url, relay_state)) - }; - - // endpoint with a `best_addr` that has a latency but no relay - let (a_endpoint, a_socket_addr) = { - let key = SecretKey::generate(rand::thread_rng()); - let node_id = key.public(); - let ip_port = IpPort { - ip: Ipv4Addr::UNSPECIFIED.into(), - port: 10, - }; - let endpoint_state = BTreeMap::from([( - ip_port, - PathState::with_pong_reply( - node_id, - PongReply { - latency, - pong_at: now, - from: SendAddr::Udp(ip_port.into()), - pong_src: pong_src.clone(), - }, - ), - )]); - ( - NodeState { - id: 0, - quic_mapped_addr: NodeIdMappedAddr::generate(), - node_id: key.public(), - last_full_ping: None, - relay_url: None, - udp_paths: NodeUdpPaths::from_parts( - endpoint_state, - UdpSendAddr::Valid(ip_port.into()), - ), - sent_pings: HashMap::new(), - last_used: Some(now), - last_call_me_maybe: None, - conn_type: Watchable::new(ConnectionType::Direct(ip_port.into())), - has_been_direct: AtomicBool::new(true), - #[cfg(any(test, feature = "test-utils"))] - path_selection: PathSelection::default(), - }, - ip_port.into(), - ) - }; - // endpoint w/ no best addr but a relay w/ latency - let b_endpoint = { - // let socket_addr = "0.0.0.0:9".parse().unwrap(); - let key = SecretKey::generate(rand::thread_rng()); - NodeState { - id: 1, - quic_mapped_addr: NodeIdMappedAddr::generate(), - node_id: key.public(), - last_full_ping: None, - relay_url: relay_and_state(key.public(), send_addr.clone()), - udp_paths: NodeUdpPaths::new(), - sent_pings: HashMap::new(), - last_used: Some(now), - last_call_me_maybe: None, - conn_type: Watchable::new(ConnectionType::Relay(send_addr.clone())), - has_been_direct: AtomicBool::new(false), - #[cfg(any(test, feature = "test-utils"))] - path_selection: PathSelection::default(), - } - }; - - // endpoint w/ no best addr but a relay w/ no latency - let c_endpoint = { - // let socket_addr = "0.0.0.0:8".parse().unwrap(); - let key = SecretKey::generate(rand::thread_rng()); - NodeState { - id: 2, - quic_mapped_addr: NodeIdMappedAddr::generate(), - node_id: key.public(), - last_full_ping: None, - relay_url: Some(( - send_addr.clone(), - PathState::new( - key.public(), - SendAddr::from(send_addr.clone()), - Source::App, - now, - ), - )), - udp_paths: NodeUdpPaths::new(), - sent_pings: HashMap::new(), - last_used: Some(now), - last_call_me_maybe: None, - conn_type: Watchable::new(ConnectionType::Relay(send_addr.clone())), - has_been_direct: AtomicBool::new(false), - #[cfg(any(test, feature = "test-utils"))] - path_selection: PathSelection::default(), - } - }; - - // endpoint w/ expired best addr and relay w/ latency - let (d_endpoint, d_socket_addr) = { - let socket_addr: SocketAddr = "0.0.0.0:7".parse().unwrap(); - let key = SecretKey::generate(rand::thread_rng()); - let node_id = key.public(); - let endpoint_state = BTreeMap::from([( - IpPort::from(socket_addr), - PathState::with_pong_reply( - node_id, - PongReply { - latency, - pong_at: now, - from: SendAddr::Udp(socket_addr), - pong_src: pong_src.clone(), - }, - ), - )]); - ( - NodeState { - id: 3, - quic_mapped_addr: NodeIdMappedAddr::generate(), - node_id: key.public(), - last_full_ping: None, - relay_url: relay_and_state(key.public(), send_addr.clone()), - udp_paths: NodeUdpPaths::from_parts( - endpoint_state, - UdpSendAddr::Outdated(socket_addr), - ), - sent_pings: HashMap::new(), - last_used: Some(now), - last_call_me_maybe: None, - conn_type: Watchable::new(ConnectionType::Mixed( - socket_addr, - send_addr.clone(), - )), - has_been_direct: AtomicBool::new(false), - #[cfg(any(test, feature = "test-utils"))] - path_selection: PathSelection::default(), - }, - socket_addr, - ) - }; - - let mut expect = Vec::from([ - RemoteInfo { - node_id: a_endpoint.node_id, - relay_url: None, - addrs: Vec::from([DirectAddrInfo { - addr: a_socket_addr, - latency: Some(latency), - last_control: Some((elapsed, ControlMsg::Pong)), - last_payload: None, - last_alive: Some(elapsed), - sources: HashMap::new(), - }]), - conn_type: ConnectionType::Direct(a_socket_addr), - latency: Some(latency), - last_used: Some(elapsed), - }, - RemoteInfo { - node_id: b_endpoint.node_id, - relay_url: Some(RelayUrlInfo { - relay_url: b_endpoint.relay_url.as_ref().unwrap().0.clone(), - last_alive: None, - latency: Some(latency), - }), - addrs: Vec::new(), - conn_type: ConnectionType::Relay(send_addr.clone()), - latency: Some(latency), - last_used: Some(elapsed), - }, - RemoteInfo { - node_id: c_endpoint.node_id, - relay_url: Some(RelayUrlInfo { - relay_url: c_endpoint.relay_url.as_ref().unwrap().0.clone(), - last_alive: None, - latency: None, - }), - addrs: Vec::new(), - conn_type: ConnectionType::Relay(send_addr.clone()), - latency: None, - last_used: Some(elapsed), - }, - RemoteInfo { - node_id: d_endpoint.node_id, - relay_url: Some(RelayUrlInfo { - relay_url: d_endpoint.relay_url.as_ref().unwrap().0.clone(), - last_alive: None, - latency: Some(latency), - }), - addrs: Vec::from([DirectAddrInfo { - addr: d_socket_addr, - latency: Some(latency), - last_control: Some((elapsed, ControlMsg::Pong)), - last_payload: None, - last_alive: Some(elapsed), - sources: HashMap::new(), - }]), - conn_type: ConnectionType::Mixed(d_socket_addr, send_addr.clone()), - latency: Some(Duration::from_millis(50)), - last_used: Some(elapsed), - }, - ]); - - let node_map = NodeMap::from_inner(NodeMapInner { - by_node_key: HashMap::from([ - (a_endpoint.node_id, a_endpoint.id), - (b_endpoint.node_id, b_endpoint.id), - (c_endpoint.node_id, c_endpoint.id), - (d_endpoint.node_id, d_endpoint.id), - ]), - by_ip_port: HashMap::from([ - (a_socket_addr.into(), a_endpoint.id), - (d_socket_addr.into(), d_endpoint.id), - ]), - by_quic_mapped_addr: HashMap::from([ - (a_endpoint.quic_mapped_addr, a_endpoint.id), - (b_endpoint.quic_mapped_addr, b_endpoint.id), - (c_endpoint.quic_mapped_addr, c_endpoint.id), - (d_endpoint.quic_mapped_addr, d_endpoint.id), - ]), - by_id: HashMap::from([ - (a_endpoint.id, a_endpoint), - (b_endpoint.id, b_endpoint), - (c_endpoint.id, c_endpoint), - (d_endpoint.id, d_endpoint), - ]), - next_id: 5, - path_selection: PathSelection::default(), - }); - let mut got = node_map.list_remote_infos(later); - got.sort_by_key(|p| p.node_id); - expect.sort_by_key(|p| p.node_id); - remove_non_deterministic_fields(&mut got); - assert_eq!(expect, got); - } + // #[test] + // fn test_remote_infos() { + // let now = Instant::now(); + // let elapsed = Duration::from_secs(3); + // let later = now + elapsed; + // let send_addr: RelayUrl = "https://my-relay.com".parse().unwrap(); + // let pong_src = SendAddr::Udp("0.0.0.0:1".parse().unwrap()); + // let latency = Duration::from_millis(50); + + // let relay_and_state = |node_id: NodeId, url: RelayUrl| { + // let relay_state = PathState::with_pong_reply( + // node_id, + // PongReply { + // latency, + // pong_at: now, + // from: SendAddr::Relay(send_addr.clone()), + // pong_src: pong_src.clone(), + // }, + // ); + // Some((url, relay_state)) + // }; + + // // endpoint with a `best_addr` that has a latency but no relay + // let (a_endpoint, a_socket_addr) = { + // let key = SecretKey::generate(rand::thread_rng()); + // let node_id = key.public(); + // let ip_port = IpPort { + // ip: Ipv4Addr::UNSPECIFIED.into(), + // port: 10, + // }; + // let endpoint_state = BTreeMap::from([( + // ip_port, + // PathState::with_pong_reply( + // node_id, + // PongReply { + // latency, + // pong_at: now, + // from: SendAddr::Udp(ip_port.into()), + // pong_src: pong_src.clone(), + // }, + // ), + // )]); + // ( + // NodeState { + // id: 0, + // quic_mapped_addr: NodeIdMappedAddr::generate(), + // node_id: key.public(), + // last_full_ping: None, + // relay_url: None, + // udp_paths: NodeUdpPaths::from_parts( + // endpoint_state, + // BestAddr::from_parts( + // ip_port.into(), + // latency, + // now, + // now + Duration::from_secs(100), + // ), + // ), + // sent_pings: HashMap::new(), + // last_used: Some(now), + // last_call_me_maybe: None, + // conn_type: Watchable::new(ConnectionType::Direct(ip_port.into())), + // has_been_direct: true, + // #[cfg(any(test, feature = "test-utils"))] + // path_selection: PathSelection::default(), + // }, + // ip_port.into(), + // ) + // }; + // // endpoint w/ no best addr but a relay w/ latency + // let b_endpoint = { + // // let socket_addr = "0.0.0.0:9".parse().unwrap(); + // let key = SecretKey::generate(rand::thread_rng()); + // NodeState { + // id: 1, + // quic_mapped_addr: NodeIdMappedAddr::generate(), + // node_id: key.public(), + // last_full_ping: None, + // relay_url: relay_and_state(key.public(), send_addr.clone()), + // udp_paths: NodeUdpPaths::new(), + // sent_pings: HashMap::new(), + // last_used: Some(now), + // last_call_me_maybe: None, + // conn_type: Watchable::new(ConnectionType::Relay(send_addr.clone())), + // has_been_direct: false, + // #[cfg(any(test, feature = "test-utils"))] + // path_selection: PathSelection::default(), + // } + // }; + + // // endpoint w/ no best addr but a relay w/ no latency + // let c_endpoint = { + // // let socket_addr = "0.0.0.0:8".parse().unwrap(); + // let key = SecretKey::generate(rand::thread_rng()); + // NodeState { + // id: 2, + // quic_mapped_addr: NodeIdMappedAddr::generate(), + // node_id: key.public(), + // last_full_ping: None, + // relay_url: Some(( + // send_addr.clone(), + // PathState::new( + // key.public(), + // SendAddr::from(send_addr.clone()), + // Source::App, + // now, + // ), + // )), + // udp_paths: NodeUdpPaths::new(), + // sent_pings: HashMap::new(), + // last_used: Some(now), + // last_call_me_maybe: None, + // conn_type: Watchable::new(ConnectionType::Relay(send_addr.clone())), + // has_been_direct: false, + // #[cfg(any(test, feature = "test-utils"))] + // path_selection: PathSelection::default(), + // } + // }; + + // // endpoint w/ expired best addr and relay w/ latency + // let (d_endpoint, d_socket_addr) = { + // let socket_addr: SocketAddr = "0.0.0.0:7".parse().unwrap(); + // let expired = now.checked_sub(Duration::from_secs(100)).unwrap(); + // let key = SecretKey::generate(rand::thread_rng()); + // let node_id = key.public(); + // let endpoint_state = BTreeMap::from([( + // IpPort::from(socket_addr), + // PathState::with_pong_reply( + // node_id, + // PongReply { + // latency, + // pong_at: now, + // from: SendAddr::Udp(socket_addr), + // pong_src: pong_src.clone(), + // }, + // ), + // )]); + // ( + // NodeState { + // id: 3, + // quic_mapped_addr: NodeIdMappedAddr::generate(), + // node_id: key.public(), + // last_full_ping: None, + // relay_url: relay_and_state(key.public(), send_addr.clone()), + // udp_paths: NodeUdpPaths::from_parts( + // endpoint_state, + // BestAddr::from_parts(socket_addr, Duration::from_millis(80), now, expired), + // ), + // sent_pings: HashMap::new(), + // last_used: Some(now), + // last_call_me_maybe: None, + // conn_type: Watchable::new(ConnectionType::Mixed( + // socket_addr, + // send_addr.clone(), + // )), + // has_been_direct: false, + // #[cfg(any(test, feature = "test-utils"))] + // path_selection: PathSelection::default(), + // }, + // socket_addr, + // ) + // }; + + // let mut expect = Vec::from([ + // RemoteInfo { + // node_id: a_endpoint.node_id, + // relay_url: None, + // addrs: Vec::from([DirectAddrInfo { + // addr: a_socket_addr, + // latency: Some(latency), + // last_control: Some((elapsed, ControlMsg::Pong)), + // last_payload: None, + // last_alive: Some(elapsed), + // sources: HashMap::new(), + // }]), + // conn_type: ConnectionType::Direct(a_socket_addr), + // latency: Some(latency), + // last_used: Some(elapsed), + // }, + // RemoteInfo { + // node_id: b_endpoint.node_id, + // relay_url: Some(RelayUrlInfo { + // relay_url: b_endpoint.relay_url.as_ref().unwrap().0.clone(), + // last_alive: None, + // latency: Some(latency), + // }), + // addrs: Vec::new(), + // conn_type: ConnectionType::Relay(send_addr.clone()), + // latency: Some(latency), + // last_used: Some(elapsed), + // }, + // RemoteInfo { + // node_id: c_endpoint.node_id, + // relay_url: Some(RelayUrlInfo { + // relay_url: c_endpoint.relay_url.as_ref().unwrap().0.clone(), + // last_alive: None, + // latency: None, + // }), + // addrs: Vec::new(), + // conn_type: ConnectionType::Relay(send_addr.clone()), + // latency: None, + // last_used: Some(elapsed), + // }, + // RemoteInfo { + // node_id: d_endpoint.node_id, + // relay_url: Some(RelayUrlInfo { + // relay_url: d_endpoint.relay_url.as_ref().unwrap().0.clone(), + // last_alive: None, + // latency: Some(latency), + // }), + // addrs: Vec::from([DirectAddrInfo { + // addr: d_socket_addr, + // latency: Some(latency), + // last_control: Some((elapsed, ControlMsg::Pong)), + // last_payload: None, + // last_alive: Some(elapsed), + // sources: HashMap::new(), + // }]), + // conn_type: ConnectionType::Mixed(d_socket_addr, send_addr.clone()), + // latency: Some(Duration::from_millis(50)), + // last_used: Some(elapsed), + // }, + // ]); + + // let node_map = NodeMap::from_inner(NodeMapInner { + // by_node_key: HashMap::from([ + // (a_endpoint.node_id, a_endpoint.id), + // (b_endpoint.node_id, b_endpoint.id), + // (c_endpoint.node_id, c_endpoint.id), + // (d_endpoint.node_id, d_endpoint.id), + // ]), + // by_ip_port: HashMap::from([ + // (a_socket_addr.into(), a_endpoint.id), + // (d_socket_addr.into(), d_endpoint.id), + // ]), + // by_quic_mapped_addr: HashMap::from([ + // (a_endpoint.quic_mapped_addr, a_endpoint.id), + // (b_endpoint.quic_mapped_addr, b_endpoint.id), + // (c_endpoint.quic_mapped_addr, c_endpoint.id), + // (d_endpoint.quic_mapped_addr, d_endpoint.id), + // ]), + // by_id: HashMap::from([ + // (a_endpoint.id, a_endpoint), + // (b_endpoint.id, b_endpoint), + // (c_endpoint.id, c_endpoint), + // (d_endpoint.id, d_endpoint), + // ]), + // next_id: 5, + // path_selection: PathSelection::default(), + // }); + // let mut got = node_map.list_remote_infos(later); + // got.sort_by_key(|p| p.node_id); + // expect.sort_by_key(|p| p.node_id); + // remove_non_deterministic_fields(&mut got); + // assert_eq!(expect, got); + // } fn remove_non_deterministic_fields(infos: &mut [RemoteInfo]) { for info in infos.iter_mut() { @@ -1724,10 +1220,6 @@ mod tests { .collect(); let call_me_maybe = disco::CallMeMaybe { my_numbers }; - let ping_messages = ep.handle_call_me_maybe(call_me_maybe); - - // We have no relay server and no previous direct addresses, so we should get the same - // number of pings as direct addresses in the call-me-maybe. - assert_eq!(ping_messages.len(), my_numbers_count as usize); + ep.handle_call_me_maybe(call_me_maybe); } } diff --git a/iroh/src/magicsock/node_map/path_state.rs b/iroh/src/magicsock/node_map/path_state.rs index b5047129278..9277d0615ff 100644 --- a/iroh/src/magicsock/node_map/path_state.rs +++ b/iroh/src/magicsock/node_map/path_state.rs @@ -4,11 +4,10 @@ use std::collections::{BTreeMap, HashMap}; use iroh_base::NodeId; use n0_future::time::{Duration, Instant}; -use tracing::{Level, debug, event}; use super::{ - IpPort, PingRole, Source, - node_state::{ControlMsg, PongReply, SESSION_ACTIVE_TIMEOUT}, + IpPort, Source, + node_state::{ControlMsg, SESSION_ACTIVE_TIMEOUT}, }; use crate::{ disco::SendAddr, @@ -52,6 +51,7 @@ pub(super) struct PathState { /// /// See [`PathValidity`] docs. pub(super) validity: PathValidity, + /// When the last payload data was **received** via this path. /// /// This excludes DISCO messages. @@ -100,54 +100,6 @@ impl PathState { } } - pub(super) fn with_ping( - node_id: NodeId, - path: SendAddr, - tx_id: stun_rs::TransactionId, - source: Source, - now: Instant, - ) -> Self { - let mut new = PathState::new(node_id, path, source, now); - new.handle_ping(tx_id, now); - new - } - - pub(super) fn add_pong_reply(&mut self, r: PongReply) { - if let SendAddr::Udp(ref path) = self.path { - if self.validity.is_empty() { - event!( - target: "iroh::_events::holepunched", - Level::DEBUG, - remote_node = %self.node_id.fmt_short(), - path = ?path, - direction = "outgoing", - ); - } - } - - self.validity = PathValidity::new(r.pong_at, r.latency); - } - - pub(super) fn receive_payload(&mut self, now: Instant) { - self.last_payload_msg = Some(now); - self.validity - .receive_payload(now, path_validity::Source::QuicPayload); - } - - #[cfg(test)] - pub(super) fn with_pong_reply(node_id: NodeId, r: PongReply) -> Self { - PathState { - node_id, - path: r.from.clone(), - last_ping: None, - last_got_ping: None, - call_me_maybe_time: None, - validity: PathValidity::new(r.pong_at, r.latency), - last_payload_msg: None, - sources: HashMap::new(), - } - } - /// Check whether this path is considered active. /// /// Active means the path has received payload messages within the last @@ -167,6 +119,12 @@ impl PathState { self.last_got_ping.as_ref().map(|(time, _tx_id)| time) } + pub(super) fn receive_payload(&mut self, now: Instant) { + self.last_payload_msg = Some(now); + self.validity + .receive_payload(now, path_validity::Source::QuicPayload); + } + /// Reports the last instant this path was considered alive. /// /// Alive means the path is considered in use by the remote endpoint. Either because we @@ -198,10 +156,6 @@ impl PathState { /// Returns the time elapsed since the last control message, and the type of control message. pub(super) fn last_control_msg(&self, now: Instant) -> Option<(Duration, ControlMsg)> { // get every control message and assign it its kind - let last_pong = self - .validity - .latest_pong() - .map(|pong_at| (pong_at, ControlMsg::Pong)); let last_call_me_maybe = self .call_me_maybe_time .as_ref() @@ -210,9 +164,8 @@ impl PathState { .last_incoming_ping() .map(|ping| (*ping, ControlMsg::Ping)); - last_pong + last_call_me_maybe .into_iter() - .chain(last_call_me_maybe) .chain(last_ping) .max_by_key(|(instant, _kind)| *instant) .map(|(instant, kind)| (now.duration_since(instant), kind)) @@ -240,39 +193,6 @@ impl PathState { } } - pub(super) fn handle_ping(&mut self, tx_id: stun_rs::TransactionId, now: Instant) -> PingRole { - if Some(&tx_id) == self.last_got_ping.as_ref().map(|(_t, tx_id)| tx_id) { - PingRole::Duplicate - } else { - let prev = self.last_got_ping.replace((now, tx_id)); - let heartbeat_deadline = HEARTBEAT_INTERVAL + (HEARTBEAT_INTERVAL / 2); - match prev { - Some((prev_time, _tx)) if now.duration_since(prev_time) <= heartbeat_deadline => { - PingRole::LikelyHeartbeat - } - Some((prev_time, _tx)) => { - debug!( - elapsed = ?now.duration_since(prev_time), - "heartbeat missed, reactivating", - ); - PingRole::Activate - } - None => { - if let SendAddr::Udp(ref addr) = self.path { - event!( - target: "iroh::_events::holepunched", - Level::DEBUG, - remote_node = %self.node_id.fmt_short(), - path = ?addr, - direction = "incoming", - ); - } - PingRole::Activate - } - } - } - } - pub(super) fn add_source(&mut self, source: Source, now: Instant) { self.sources.insert(source, now); } diff --git a/iroh/src/magicsock/node_map/udp_paths.rs b/iroh/src/magicsock/node_map/udp_paths.rs index 29c43ccc99d..475f865e59b 100644 --- a/iroh/src/magicsock/node_map/udp_paths.rs +++ b/iroh/src/magicsock/node_map/udp_paths.rs @@ -7,7 +7,8 @@ //! [`NodeState`]: super::node_state::NodeState use std::{collections::BTreeMap, net::SocketAddr}; -use n0_future::time::Instant; +use n0_future::time::{Duration, Instant}; +use rand::seq::IteratorRandom; use tracing::{Level, event}; use super::{IpPort, path_state::PathState}; From c4baca831ace378aec27f3a55bb688cde0061802 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 11 Jul 2025 14:01:32 +0200 Subject: [PATCH 09/44] start tracking path events --- Cargo.lock | 45 +++++-------------- Cargo.toml | 6 +-- iroh/src/endpoint.rs | 13 ++++-- iroh/src/magicsock.rs | 24 +++++++++- iroh/src/magicsock/node_map/node_state.rs | 16 ------- iroh/src/magicsock/node_map/path_state.rs | 55 +---------------------- 6 files changed, 47 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3a6558f70f..aeb847038a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2050,7 +2050,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.59.0", + "windows-core 0.61.2", ] [[package]] @@ -2489,7 +2489,7 @@ dependencies = [ [[package]] name = "iroh-quinn" version = "0.14.0" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#e84f16856ea3ac79ed6c206b258d33a30d87834f" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#0dc50edf689ee5c6cf21b4ee5c0fea6af548680d" dependencies = [ "bytes", "cfg_aliases", @@ -2508,7 +2508,7 @@ dependencies = [ [[package]] name = "iroh-quinn-proto" version = "0.13.0" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#e84f16856ea3ac79ed6c206b258d33a30d87834f" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#0dc50edf689ee5c6cf21b4ee5c0fea6af548680d" dependencies = [ "bytes", "fastbloom", @@ -2544,7 +2544,7 @@ dependencies = [ [[package]] name = "iroh-quinn-udp" version = "0.5.12" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#140fdf97bfb706b1cac591b9711818c5df8012f4" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#79e3fcc710de68b40fd05be5421048bab658ddf4" dependencies = [ "cfg_aliases", "libc", @@ -5113,9 +5113,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" dependencies = [ "indexmap", "serde", @@ -5773,19 +5773,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" -dependencies = [ - "windows-implement 0.59.0", - "windows-interface 0.59.1", - "windows-result 0.3.4", - "windows-strings 0.3.1", - "windows-targets 0.53.3", -] - [[package]] name = "windows-core" version = "0.61.2" @@ -5821,17 +5808,6 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "windows-implement" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "windows-implement" version = "0.60.0" @@ -5889,7 +5865,7 @@ checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ "windows-result 0.3.4", "windows-strings 0.3.1", - "windows-targets 0.53.3", + "windows-targets 0.53.2", ] [[package]] @@ -5980,7 +5956,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.2", ] [[package]] @@ -6031,11 +6007,10 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ - "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", diff --git a/Cargo.toml b/Cargo.toml index edb51ff85b4..eb098793da4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,6 @@ netwatch = { git = "https://github.com/n0-computer/net-tools", branch = "feat-mu # iroh-quinn-proto = { path = "../iroh-quinn/quinn-proto" } # iroh-quinn-udp = { path = "../iroh-quinn/quinn-udp" } -# iroh-quinn = { git = "https://github.com//n0-computer/quinn", branch = "multipath-misc" } -# iroh-quinn-proto = { git = "https://github.com//n0-computer/quinn", branch = "multipath-misc" } -# iroh-quinn-udp = { git = "https://github.com//n0-computer/quinn", branch = "multipath-misc" } +# iroh-quinn = { git = "https://github.com//n0-computer/quinn", branch = "flub/quinn-path-events-status" } +# iroh-quinn-proto = { git = "https://github.com//n0-computer/quinn", branch = "flub/quinn-path-events-status" } +# iroh-quinn-udp = { git = "https://github.com//n0-computer/quinn", branch = "flub/quinn-path-events-status" } diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 3e1a7e90e6b..9d107511027 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -1771,13 +1771,20 @@ impl Future for Connecting { let conn = Connection { inner }; // Grab the remote identity and register this connection - if let Some(remote) = *this.remote_node_id { let weak_handle = conn.inner.weak_handle(); - this.ep.msock.register_connection(remote, weak_handle); + let path_events = conn.inner.path_events(); + this.ep + .msock + .register_connection(remote, weak_handle, path_events); } else if let Ok(remote) = conn.remote_node_id() { let weak_handle = conn.inner.weak_handle(); - this.ep.msock.register_connection(remote, weak_handle); + let path_events = conn.inner.path_events(); + this.ep + .msock + .register_connection(remote, weak_handle, path_events); + } else { + warn!("unable to determine node id for the remote"); } try_send_rtt_msg(&conn, this.ep, *this.remote_node_id); diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 892000b2d3b..3067730de6f 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -43,6 +43,7 @@ use netwatch::netmon; #[cfg(not(wasm_browser))] use netwatch::{UdpSocket, ip::LocalAddresses}; use quinn::{AsyncUdpSocket, ServerConfig, WeakConnectionHandle}; +use quinn_proto::PathEvent; use rand::Rng; use smallvec::SmallVec; use snafu::{ResultExt, Snafu}; @@ -289,8 +290,29 @@ impl MagicSock { self.local_addrs_watch.clone().get() } - pub(crate) fn register_connection(&self, remote: NodeId, conn: WeakConnectionHandle) { + pub(crate) fn register_connection( + &self, + remote: NodeId, + conn: WeakConnectionHandle, + mut path_events: tokio::sync::broadcast::Receiver, + ) { self.connection_map.insert(remote, conn); + + // TODO: track task + // TODO: find a good home for this + task::spawn(async move { + loop { + match path_events.recv().await { + Ok(event) => { + info!(remote = %remote, "path event: {:?}", event); + } + Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => { + warn!("lagged path events"); + } + Err(tokio::sync::broadcast::error::RecvError::Closed) => break, + } + } + }); } #[cfg(not(wasm_browser))] diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index 26a4a6d63a5..96162223663 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -549,7 +549,6 @@ impl NodeState { // it's been less than 5 seconds ago. Also clear pongs for direct addresses not // included in the updated set. for (ipp, st) in self.udp_paths.paths.iter_mut() { - st.last_ping = None; if !call_me_maybe_ipps.contains(ipp) { // TODO: This seems like a weird way to signal that the endpoint no longer // thinks it has this IpPort as an available path. @@ -607,21 +606,6 @@ impl NodeState { self.last_used = Some(now); } - pub(super) fn last_ping(&self, addr: &SendAddr) -> Option { - match addr { - SendAddr::Udp(addr) => self - .udp_paths - .paths - .get(&(*addr).into()) - .and_then(|ep| ep.last_ping), - SendAddr::Relay(url) => self - .relay_url - .as_ref() - .filter(|(home_url, _state)| home_url == url) - .and_then(|(_home_url, state)| state.last_ping), - } - } - /// Checks if this `Endpoint` is currently actively being used. pub(super) fn is_active(&self, now: &Instant) -> bool { match self.last_used { diff --git a/iroh/src/magicsock/node_map/path_state.rs b/iroh/src/magicsock/node_map/path_state.rs index 9277d0615ff..ce3121539b0 100644 --- a/iroh/src/magicsock/node_map/path_state.rs +++ b/iroh/src/magicsock/node_map/path_state.rs @@ -17,12 +17,6 @@ use crate::{ }, }; -/// The minimum time between pings to an endpoint. -/// -/// Except in the case of CallMeMaybe frames resetting the counter, as the first pings -/// likely didn't through the firewall. -const DISCO_PING_INTERVAL: Duration = Duration::from_secs(5); - /// State about a particular path to another [`NodeState`]. /// /// This state is used for both the relay path and any direct UDP paths. @@ -34,13 +28,6 @@ pub(super) struct PathState { node_id: NodeId, /// The path this applies for. path: SendAddr, - /// The last (outgoing) ping time. - pub(super) last_ping: Option, - - /// If non-zero, means that this was an endpoint that we learned about at runtime (from an - /// incoming ping). If so, we keep the time updated and use it to discard old candidates. - // NOTE: tx_id Originally added in tailscale due to . - last_got_ping: Option<(Instant, stun_rs::TransactionId)>, /// The time this endpoint was last advertised via a call-me-maybe DISCO message. pub(super) call_me_maybe_time: Option, @@ -71,8 +58,6 @@ impl PathState { Self { node_id, path, - last_ping: None, - last_got_ping: None, call_me_maybe_time: None, validity: PathValidity::empty(), last_payload_msg: None, @@ -91,8 +76,6 @@ impl PathState { PathState { node_id, path, - last_ping: None, - last_got_ping: None, call_me_maybe_time: None, validity: PathValidity::empty(), last_payload_msg: Some(now), @@ -114,17 +97,11 @@ impl PathState { .unwrap_or(false) } - /// Returns the instant the last incoming ping was received. - pub(super) fn last_incoming_ping(&self) -> Option<&Instant> { - self.last_got_ping.as_ref().map(|(time, _tx_id)| time) - } - pub(super) fn receive_payload(&mut self, now: Instant) { self.last_payload_msg = Some(now); self.validity .receive_payload(now, path_validity::Source::QuicPayload); } - /// Reports the last instant this path was considered alive. /// /// Alive means the path is considered in use by the remote endpoint. Either because we @@ -142,14 +119,12 @@ impl PathState { .into_iter() .chain(self.last_payload_msg) .chain(self.call_me_maybe_time) - .chain(self.last_incoming_ping().cloned()) .max() } /// The last control or DISCO message **about** this path. /// /// This is the most recent instant among: - /// - when last pong was received. /// - when this path was last advertised in a received CallMeMaybe message. /// - when the last ping from them was received. /// @@ -160,13 +135,9 @@ impl PathState { .call_me_maybe_time .as_ref() .map(|call_me| (*call_me, ControlMsg::CallMeMaybe)); - let last_ping = self - .last_incoming_ping() - .map(|ping| (*ping, ControlMsg::Ping)); last_call_me_maybe .into_iter() - .chain(last_ping) .max_by_key(|(instant, _kind)| *instant) .map(|(instant, kind)| (now.duration_since(instant), kind)) } @@ -176,30 +147,11 @@ impl PathState { self.validity.latency() } - pub(super) fn needs_ping(&self, now: &Instant) -> bool { - match self.last_ping { - None => true, - Some(last_ping) => { - let elapsed = now.duration_since(last_ping); - - // TODO: remove! - // This logs "ping is too new" for each send whenever the endpoint does *not* need - // a ping. Pretty sure this is not a useful log, but maybe there was a reason? - // if !needs_ping { - // debug!("ping is too new: {}ms", elapsed.as_millis()); - // } - elapsed > DISCO_PING_INTERVAL - } - } - } - pub(super) fn add_source(&mut self, source: Source, now: Instant) { self.sources.insert(source, now); } pub(super) fn clear(&mut self) { - self.last_ping = None; - self.last_got_ping = None; self.call_me_maybe_time = None; self.validity = PathValidity::empty(); } @@ -212,12 +164,7 @@ impl PathState { if let Some(pong_at) = self.validity.latest_pong() { write!(w, "pong-received({:?} ago) ", pong_at.elapsed())?; } - if let Some(when) = self.last_incoming_ping() { - write!(w, "ping-received({:?} ago) ", when.elapsed())?; - } - if let Some(ref when) = self.last_ping { - write!(w, "ping-sent({:?} ago) ", when.elapsed())?; - } + if let Some(last_source) = self.sources.iter().max_by_key(|&(_, instant)| instant) { write!( w, From 549adee0491610dc2ed74a1c27b09a5d2fa87eea Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Sat, 12 Jul 2025 15:42:27 +0200 Subject: [PATCH 10/44] start figuring out more details --- iroh/src/endpoint.rs | 46 +++++++++----- iroh/src/magicsock.rs | 112 ++++++++++++++++++++++++--------- iroh/src/magicsock/node_map.rs | 9 +++ 3 files changed, 125 insertions(+), 42 deletions(-) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 9d107511027..760673ad12f 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -735,14 +735,13 @@ impl Endpoint { self.add_node_addr(node_addr.clone())?; } let node_id = node_addr.node_id; - let direct_addresses = node_addr.direct_addresses.clone(); let relay_url = node_addr.relay_url.clone(); // Get the mapped IPv6 address from the magic socket. Quinn will connect to this // address. Start discovery for this node if it's enabled and we have no valid or // verified address information for this node. Dropping the discovery cancels any // still running task. - let (mapped_addr, _discovery_drop_guard) = self + let (mapped_addr, direct_addresses, _discovery_drop_guard) = self .get_mapping_addr_and_maybe_start_discovery(node_addr) .await .context(NoAddressSnafu)?; @@ -773,7 +772,11 @@ impl Endpoint { }; // TODO: race available addresses, this is currently only using the relay addr to connect - let dest_addr = mapped_addr.private_socket_addr(); + let dest_addr = if relay_url.is_none() && !direct_addresses.is_empty() { + direct_addresses[0] + } else { + mapped_addr.private_socket_addr() + }; let server_name = &tls::name::encode(node_id); let connect = self .msock @@ -781,10 +784,14 @@ impl Endpoint { .connect_with(client_config, dest_addr, server_name) .context(QuinnSnafu)?; + let mut paths = direct_addresses; + paths.push(mapped_addr.private_socket_addr()); + Ok(Connecting { inner: connect, ep: self.clone(), remote_node_id: Some(node_id), + paths, _discovery_drop_guard, }) } @@ -1379,18 +1386,20 @@ impl Endpoint { async fn get_mapping_addr_and_maybe_start_discovery( &self, node_addr: NodeAddr, - ) -> Result<(NodeIdMappedAddr, Option), GetMappingAddressError> { + ) -> Result<(NodeIdMappedAddr, Vec, Option), GetMappingAddressError> + { let node_id = node_addr.node_id; // Only return a mapped addr if we have some way of dialing this node, in other // words, we have either a relay URL or at least one direct address. let addr = if self.msock.has_send_address(node_id) { - self.msock.get_mapping_addr(node_id) + let maddr = self.msock.get_mapping_addr(node_id); + maddr.map(|maddr| (maddr, self.msock.get_direct_addrs(node_id))) } else { None }; match addr { - Some(addr) => { + Some((maddr, direct)) => { // We have some way of dialing this node, but that doesn't actually mean // we can actually connect to any of these addresses. // Therefore, we will invoke the discovery service if we haven't received from the @@ -1402,7 +1411,7 @@ impl Endpoint { let discovery = DiscoveryTask::maybe_start_after_delay(self, node_id, delay) .ok() .flatten(); - Ok((addr, discovery)) + Ok((maddr, direct, discovery)) } None => { @@ -1417,7 +1426,8 @@ impl Endpoint { .await .context(get_mapping_address_error::DiscoverSnafu)?; if let Some(addr) = self.msock.get_mapping_addr(node_id) { - Ok((addr, Some(discovery))) + let direct = self.msock.get_direct_addrs(node_id); + Ok((addr, direct, Some(discovery))) } else { Err(get_mapping_address_error::NoAddressSnafu.build()) } @@ -1646,6 +1656,8 @@ pub struct Connecting { inner: quinn::Connecting, ep: Endpoint, remote_node_id: Option, + /// Additional paths to open once a connection is created + paths: Vec, /// We run discovery as long as we haven't established a connection yet. #[debug("Option")] _discovery_drop_guard: Option, @@ -1774,15 +1786,21 @@ impl Future for Connecting { if let Some(remote) = *this.remote_node_id { let weak_handle = conn.inner.weak_handle(); let path_events = conn.inner.path_events(); - this.ep - .msock - .register_connection(remote, weak_handle, path_events); + this.ep.msock.register_connection( + remote, + weak_handle, + path_events, + this.paths.clone(), + ); } else if let Ok(remote) = conn.remote_node_id() { let weak_handle = conn.inner.weak_handle(); let path_events = conn.inner.path_events(); - this.ep - .msock - .register_connection(remote, weak_handle, path_events); + this.ep.msock.register_connection( + remote, + weak_handle, + path_events, + this.paths.clone(), + ); } else { warn!("unable to determine node id for the remote"); } diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 3067730de6f..af9f8501ee9 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -295,8 +295,24 @@ impl MagicSock { remote: NodeId, conn: WeakConnectionHandle, mut path_events: tokio::sync::broadcast::Receiver, + paths: Vec, ) { self.connection_map.insert(remote, conn); + task::spawn(async move { + let conn = conn.clone(); + for addr in paths { + match conn.open_path(addr, quinn_proto::PathStatus::Backup).await { + Ok(path) => { + path.set_max_idle_timeout(Some(ENDPOINTS_FRESH_ENOUGH_DURATION)) + .ok(); + path.set_keep_alive_interval(Some(HEARTBEAT_INTERVAL)).ok(); + } + Err(err) => { + warn!("failed to open path {:?}", err); + } + } + } + }); // TODO: track task // TODO: find a good home for this @@ -422,6 +438,10 @@ impl MagicSock { self.node_map.get_quic_mapped_addr_for_node_key(node_id) } + pub(crate) fn get_direct_addrs(&self, node_id: NodeId) -> Vec { + self.node_map.get_direct_addrs(node_id) + } + /// Add addresses for a node to the magic socket's addresbook. #[instrument(skip_all)] pub fn add_node_addr( @@ -1055,6 +1075,40 @@ impl MagicSock { } } +/// Definies the translation of addresses in quinn land vs iroh land. +/// +/// This is necessary, because quinn can only reason about `SocketAddr`s. +#[derive(Clone, Debug)] +pub(crate) enum MultipathMappedAddr { + /// Used for the initial connection. + /// - Only used for sending + /// - This means send on all known paths/transports + Mixed(NodeIdMappedAddr), + /// Relay based transport address + Relay(IpMappedAddr), // TODO: RelayMappedAddr? + /// IP based transport address + #[cfg(not(wasm_browser))] + Ip(SocketAddr), +} + +impl From for MultipathMappedAddr { + fn from(value: SocketAddr) -> Self { + match value.ip() { + IpAddr::V4(_) => Self::Ip(value), + IpAddr::V6(addr) => { + if let Ok(node_id_mapped_addr) = NodeIdMappedAddr::try_from(addr) { + return Self::Mixed(node_id_mapped_addr); + } + #[cfg(not(wasm_browser))] + if let Ok(ip_mapped_addr) = IpMappedAddr::try_from(addr) { + return Self::Relay(ip_mapped_addr); + } + MappedAddr::Self(value) + } + } + } +} + #[derive(Clone, Debug)] enum MappedAddr { NodeId(NodeIdMappedAddr), @@ -3188,17 +3242,18 @@ mod tests { let _accept_task = AbortOnDropHandle::new(accept_task); // Add an empty entry in the NodeMap of ep_1 - msock_1.node_map.add_node_addr( - NodeAddr { - node_id: node_id_2, - relay_url: None, - direct_addresses: Default::default(), - }, - Source::NamedApp { - name: "test".into(), - }, - &msock_1.metrics.magicsock, - ); + msock_1 + .add_node_addr( + NodeAddr { + node_id: node_id_2, + relay_url: None, + direct_addresses: Default::default(), + }, + Source::NamedApp { + name: "test".into(), + }, + ) + .unwrap(); let addr_2 = msock_1.get_mapping_addr(node_id_2).unwrap(); // Set a low max_idle_timeout so quinn gives up on this quickly and our test does @@ -3225,23 +3280,24 @@ mod tests { info!("first connect timed out as expected"); // Provide correct addressing information - msock_1.node_map.add_node_addr( - NodeAddr { - node_id: node_id_2, - relay_url: None, - direct_addresses: msock_2 - .direct_addresses() - .initialized() - .await - .into_iter() - .map(|x| x.addr) - .collect(), - }, - Source::NamedApp { - name: "test".into(), - }, - &msock_1.metrics.magicsock, - ); + msock_1 + .add_node_addr( + NodeAddr { + node_id: node_id_2, + relay_url: None, + direct_addresses: msock_2 + .direct_addresses() + .initialized() + .await + .into_iter() + .map(|x| x.addr) + .collect(), + }, + Source::NamedApp { + name: "test".into(), + }, + ) + .unwrap(); // We can now connect tokio::time::timeout(Duration::from_secs(10), async move { diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index 11b135046e3..4eb8e3df716 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -177,6 +177,15 @@ impl NodeMap { .map(|ep| *ep.quic_mapped_addr()) } + pub(super) fn get_direct_addrs(&self, node_key: NodeId) -> Vec { + self.inner + .lock() + .expect("poisoned") + .get(NodeStateKey::NodeId(node_key)) + .map(|ep| ep.direct_addresses().map(Into::into).collect()) + .unwrap_or_default() + } + pub(super) fn handle_call_me_maybe( &self, sender: PublicKey, From 9ef5765db433a149c10fe84fc1d069e4f1cf3d96 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 14 Jul 2025 12:27:01 +0200 Subject: [PATCH 11/44] wip --- iroh/src/endpoint.rs | 60 ++++++------------- iroh/src/magicsock.rs | 74 +++++++----------------- iroh/src/magicsock/relay_mapped_addrs.rs | 54 +++++++++++++++++ iroh/src/magicsock/transports/relay.rs | 2 +- iroh/src/net_report/ip_mapped_addrs.rs | 2 +- 5 files changed, 96 insertions(+), 96 deletions(-) create mode 100644 iroh/src/magicsock/relay_mapped_addrs.rs diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 760673ad12f..fdde0720611 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -34,9 +34,8 @@ use url::Url; #[cfg(wasm_browser)] use crate::discovery::pkarr::PkarrResolver; -#[cfg(not(wasm_browser))] -use crate::{discovery::dns::DnsDiscovery, dns::DnsResolver}; use crate::{ + RelayProtocol, discovery::{ ConcurrentDiscovery, Discovery, DiscoveryContext, DiscoveryError, DiscoveryItem, DiscoverySubscribers, DiscoveryTask, DynIntoDiscovery, IntoDiscovery, IntoDiscoveryError, @@ -47,6 +46,8 @@ use crate::{ net_report::Report, tls, }; +#[cfg(not(wasm_browser))] +use crate::{discovery::dns::DnsDiscovery, dns::DnsResolver}; mod rtt_actor; @@ -735,13 +736,12 @@ impl Endpoint { self.add_node_addr(node_addr.clone())?; } let node_id = node_addr.node_id; - let relay_url = node_addr.relay_url.clone(); // Get the mapped IPv6 address from the magic socket. Quinn will connect to this // address. Start discovery for this node if it's enabled and we have no valid or // verified address information for this node. Dropping the discovery cancels any // still running task. - let (mapped_addr, direct_addresses, _discovery_drop_guard) = self + let (mapped_addr, _discovery_drop_guard) = self .get_mapping_addr_and_maybe_start_discovery(node_addr) .await .context(NoAddressSnafu)?; @@ -753,12 +753,7 @@ impl Endpoint { // Start connecting via quinn. This will time out after 10 seconds if no reachable // address is available. - debug!( - ?mapped_addr, - ?direct_addresses, - ?relay_url, - "Attempting connection..." - ); + debug!(?mapped_addr, "Attempting connection..."); let client_config = { let mut alpn_protocols = vec![alpn.to_vec()]; alpn_protocols.extend(options.additional_alpns); @@ -771,12 +766,7 @@ impl Endpoint { client_config }; - // TODO: race available addresses, this is currently only using the relay addr to connect - let dest_addr = if relay_url.is_none() && !direct_addresses.is_empty() { - direct_addresses[0] - } else { - mapped_addr.private_socket_addr() - }; + let dest_addr = mapped_addr.private_socket_addr(); let server_name = &tls::name::encode(node_id); let connect = self .msock @@ -784,14 +774,10 @@ impl Endpoint { .connect_with(client_config, dest_addr, server_name) .context(QuinnSnafu)?; - let mut paths = direct_addresses; - paths.push(mapped_addr.private_socket_addr()); - Ok(Connecting { inner: connect, ep: self.clone(), remote_node_id: Some(node_id), - paths, _discovery_drop_guard, }) } @@ -1386,20 +1372,18 @@ impl Endpoint { async fn get_mapping_addr_and_maybe_start_discovery( &self, node_addr: NodeAddr, - ) -> Result<(NodeIdMappedAddr, Vec, Option), GetMappingAddressError> - { + ) -> Result<(NodeIdMappedAddr, Option), GetMappingAddressError> { let node_id = node_addr.node_id; // Only return a mapped addr if we have some way of dialing this node, in other // words, we have either a relay URL or at least one direct address. let addr = if self.msock.has_send_address(node_id) { - let maddr = self.msock.get_mapping_addr(node_id); - maddr.map(|maddr| (maddr, self.msock.get_direct_addrs(node_id))) + self.msock.get_mapping_addr(node_id) } else { None }; match addr { - Some((maddr, direct)) => { + Some(maddr) => { // We have some way of dialing this node, but that doesn't actually mean // we can actually connect to any of these addresses. // Therefore, we will invoke the discovery service if we haven't received from the @@ -1411,7 +1395,7 @@ impl Endpoint { let discovery = DiscoveryTask::maybe_start_after_delay(self, node_id, delay) .ok() .flatten(); - Ok((maddr, direct, discovery)) + Ok((maddr, discovery)) } None => { @@ -1426,8 +1410,7 @@ impl Endpoint { .await .context(get_mapping_address_error::DiscoverSnafu)?; if let Some(addr) = self.msock.get_mapping_addr(node_id) { - let direct = self.msock.get_direct_addrs(node_id); - Ok((addr, direct, Some(discovery))) + Ok((addr, Some(discovery))) } else { Err(get_mapping_address_error::NoAddressSnafu.build()) } @@ -1656,8 +1639,6 @@ pub struct Connecting { inner: quinn::Connecting, ep: Endpoint, remote_node_id: Option, - /// Additional paths to open once a connection is created - paths: Vec, /// We run discovery as long as we haven't established a connection yet. #[debug("Option")] _discovery_drop_guard: Option, @@ -1786,21 +1767,15 @@ impl Future for Connecting { if let Some(remote) = *this.remote_node_id { let weak_handle = conn.inner.weak_handle(); let path_events = conn.inner.path_events(); - this.ep.msock.register_connection( - remote, - weak_handle, - path_events, - this.paths.clone(), - ); + this.ep + .msock + .register_connection(remote, weak_handle, path_events); } else if let Ok(remote) = conn.remote_node_id() { let weak_handle = conn.inner.weak_handle(); let path_events = conn.inner.path_events(); - this.ep.msock.register_connection( - remote, - weak_handle, - path_events, - this.paths.clone(), - ); + this.ep + .msock + .register_connection(remote, weak_handle, path_events); } else { warn!("unable to determine node id for the remote"); } @@ -2287,6 +2262,7 @@ mod tests { }; use iroh_base::{NodeAddr, NodeId, SecretKey}; + use iroh_relay::http::Protocol; use n0_future::{StreamExt, task::AbortOnDropHandle}; use n0_snafu::{Error, Result, ResultExt}; use n0_watcher::Watcher; diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index af9f8501ee9..0c5ab9c3534 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -45,6 +45,7 @@ use netwatch::{UdpSocket, ip::LocalAddresses}; use quinn::{AsyncUdpSocket, ServerConfig, WeakConnectionHandle}; use quinn_proto::PathEvent; use rand::Rng; +use relay_mapped_addrs::RelayMappedAddresses; use smallvec::SmallVec; use snafu::{ResultExt, Snafu}; use tokio::sync::{Mutex as AsyncMutex, mpsc}; @@ -79,6 +80,7 @@ use crate::{ mod metrics; mod node_map; +mod relay_mapped_addrs; pub(crate) mod transports; @@ -200,6 +202,8 @@ pub(crate) struct MagicSock { /// Tracks the mapped IP addresses ip_mapped_addrs: IpMappedAddresses, + /// Tracks the mapped IP addresses + relay_mapped_addrs: RelayMappedAddresses, /// Local addresses local_addrs_watch: LocalAddrsWatch, /// Currently bound IP addresses of all sockets @@ -295,25 +299,10 @@ impl MagicSock { remote: NodeId, conn: WeakConnectionHandle, mut path_events: tokio::sync::broadcast::Receiver, - paths: Vec, ) { self.connection_map.insert(remote, conn); - task::spawn(async move { - let conn = conn.clone(); - for addr in paths { - match conn.open_path(addr, quinn_proto::PathStatus::Backup).await { - Ok(path) => { - path.set_max_idle_timeout(Some(ENDPOINTS_FRESH_ENOUGH_DURATION)) - .ok(); - path.set_keep_alive_interval(Some(HEARTBEAT_INTERVAL)).ok(); - } - Err(err) => { - warn!("failed to open path {:?}", err); - } - } - } - }); + // TODO: open additional paths // TODO: track task // TODO: find a good home for this task::spawn(async move { @@ -461,6 +450,11 @@ impl MagicSock { self.node_map .add_node_addr(addr.clone(), source, &self.metrics.magicsock); + if let Some(url) = addr.relay_url() { + self.relay_mapped_addrs + .get_or_register(url.clone(), addr.node_id); + } + // Add paths to the existing connections self.add_paths(addr); @@ -633,11 +627,8 @@ impl MagicSock { let mut active_paths = SmallVec::<[_; 3]>::new(); - match MappedAddr::from(transmit.destination) { - MappedAddr::None(addr) => { - active_paths.push(transports::Addr::from(addr)); - } - MappedAddr::NodeId(dest) => { + match MultipathMappedAddr::from(transmit.destination) { + MultipathMappedAddr::Mixed(dest) => { trace!( dst = %dest, src = ?transmit.src_ip, @@ -670,7 +661,10 @@ impl MagicSock { } } #[cfg(not(wasm_browser))] - MappedAddr::Ip(dest) => { + MultipathMappedAddr::Ip(addr) => { + active_paths.push(transports::Addr::Ip(addr)); + } + MultipathMappedAddr::Relay(dest) => { trace!( dst = %dest, src = ?transmit.src_ip, @@ -680,9 +674,9 @@ impl MagicSock { // Check if this is a known IpMappedAddr, and if so, send over UDP // Get the socket addr - match self.ip_mapped_addrs.get_ip_addr(&dest) { - Some(addr) => { - active_paths.push(transports::Addr::from(addr)); + match self.relay_mapped_addrs.get_url(&dest) { + Some((relay, node_id)) => { + active_paths.push(transports::Addr::Relay(relay, node_id)); } None => { error!(%dest, "unknown mapped address"); @@ -1103,33 +1097,7 @@ impl From for MultipathMappedAddr { if let Ok(ip_mapped_addr) = IpMappedAddr::try_from(addr) { return Self::Relay(ip_mapped_addr); } - MappedAddr::Self(value) - } - } - } -} - -#[derive(Clone, Debug)] -enum MappedAddr { - NodeId(NodeIdMappedAddr), - #[cfg(not(wasm_browser))] - Ip(IpMappedAddr), - None(SocketAddr), -} - -impl From for MappedAddr { - fn from(value: SocketAddr) -> Self { - match value.ip() { - IpAddr::V4(_) => MappedAddr::None(value), - IpAddr::V6(addr) => { - if let Ok(node_id_mapped_addr) = NodeIdMappedAddr::try_from(addr) { - return MappedAddr::NodeId(node_id_mapped_addr); - } - #[cfg(not(wasm_browser))] - if let Ok(ip_mapped_addr) = IpMappedAddr::try_from(addr) { - return MappedAddr::Ip(ip_mapped_addr); - } - MappedAddr::None(value) + Self::Ip(value) } } } @@ -1318,6 +1286,7 @@ impl Handle { bind_ip(addr_v4, addr_v6, &metrics).context(BindSocketsSnafu)?; let ip_mapped_addrs = IpMappedAddresses::default(); + let relay_mapped_addrs = RelayMappedAddresses::default(); let (actor_sender, actor_receiver) = mpsc::channel(256); @@ -1367,6 +1336,7 @@ impl Handle { node_map, connection_map: Default::default(), ip_mapped_addrs: ip_mapped_addrs.clone(), + relay_mapped_addrs, discovery, discovery_user_data: RwLock::new(discovery_user_data), direct_addrs: Default::default(), diff --git a/iroh/src/magicsock/relay_mapped_addrs.rs b/iroh/src/magicsock/relay_mapped_addrs.rs new file mode 100644 index 00000000000..32c8b866220 --- /dev/null +++ b/iroh/src/magicsock/relay_mapped_addrs.rs @@ -0,0 +1,54 @@ +use std::{collections::BTreeMap, sync::Arc}; + +use iroh_base::{NodeId, RelayUrl}; +use snafu::Snafu; + +use crate::net_report::IpMappedAddr; + +/// Can occur when converting a [`SocketAddr`] to an [`RelayMappedAddr`] +#[derive(Debug, Snafu)] +#[snafu(display("Failed to convert"))] +pub struct RelayMappedAddrError; + +/// A Map of [`RelayMappedAddresses`] to [`SocketAddr`]. +#[derive(Debug, Clone, Default)] +pub(crate) struct RelayMappedAddresses(Arc>); + +#[derive(Debug, Default)] +pub(super) struct Inner { + by_mapped_addr: BTreeMap, + by_url: BTreeMap<(RelayUrl, NodeId), IpMappedAddr>, +} + +impl RelayMappedAddresses { + /// Adds a [`RelayUrl`] to the map and returns the generated [`IpMappedAddr`]. + /// + /// If this [`RelayUrl`] already exists in the map, it returns its + /// associated [`IpMappedAddr`]. + /// + /// Otherwise a new [`IpMappedAddr`] is generated for it and returned. + pub(super) fn get_or_register(&self, relay: RelayUrl, node: NodeId) -> IpMappedAddr { + let mut inner = self.0.lock().expect("poisoned"); + if let Some(mapped_addr) = inner.by_url.get(&(relay.clone(), node)) { + return *mapped_addr; + } + let ip_mapped_addr = IpMappedAddr::generate(); + inner + .by_mapped_addr + .insert(ip_mapped_addr, (relay.clone(), node)); + inner.by_url.insert((relay, node), ip_mapped_addr); + ip_mapped_addr + } + + /// Returns the [`IpMappedAddr`] for the given [`RelayUrl`]. + pub(crate) fn get_mapped_addr(&self, relay: RelayUrl, node: NodeId) -> Option { + let inner = self.0.lock().expect("poisoned"); + inner.by_url.get(&(relay, node)).copied() + } + + /// Returns the [`RelayUrl`] for the given [`IpMappedAddr`]. + pub(crate) fn get_url(&self, mapped_addr: &IpMappedAddr) -> Option<(RelayUrl, NodeId)> { + let inner = self.0.lock().expect("poisoned"); + inner.by_mapped_addr.get(mapped_addr).cloned() + } +} diff --git a/iroh/src/magicsock/transports/relay.rs b/iroh/src/magicsock/transports/relay.rs index 295266ec559..33760fdcde3 100644 --- a/iroh/src/magicsock/transports/relay.rs +++ b/iroh/src/magicsock/transports/relay.rs @@ -126,7 +126,7 @@ impl RelayTransport { .segment_size .map_or(dm.datagrams.contents.len(), |s| u16::from(s) as usize); meta_out.ecn = None; - meta_out.dst_ip = None; // TODO: insert the relay url for this relay + meta_out.dst_ip = None; *addr = (dm.url, dm.src).into(); num_msgs += 1; diff --git a/iroh/src/net_report/ip_mapped_addrs.rs b/iroh/src/net_report/ip_mapped_addrs.rs index be7da1867ae..564134e7be9 100644 --- a/iroh/src/net_report/ip_mapped_addrs.rs +++ b/iroh/src/net_report/ip_mapped_addrs.rs @@ -38,7 +38,7 @@ impl IpMappedAddr { /// /// This generates a new IPv6 address in the Unique Local Address range (RFC 4193) /// which is recognised by iroh as an IP mapped address. - pub(super) fn generate() -> Self { + pub(crate) fn generate() -> Self { let mut addr = [0u8; 16]; addr[0] = Self::ADDR_PREFIXL; addr[1..6].copy_from_slice(&Self::ADDR_GLOBAL_ID); From 75d55252c64e4749b756b1c17f510bd5a48edf19 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 18 Jul 2025 17:21:25 +0200 Subject: [PATCH 12/44] get some stuff to work again --- iroh/src/endpoint.rs | 1 - iroh/src/magicsock.rs | 20 ++++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index fdde0720611..856ca358104 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -2262,7 +2262,6 @@ mod tests { }; use iroh_base::{NodeAddr, NodeId, SecretKey}; - use iroh_relay::http::Protocol; use n0_future::{StreamExt, task::AbortOnDropHandle}; use n0_snafu::{Error, Result, ResultExt}; use n0_watcher::Watcher; diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 0c5ab9c3534..cf034703259 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -643,17 +643,20 @@ impl MagicSock { self.ipv6_reported.load(Ordering::Relaxed), &self.metrics.magicsock, ) { - Some((node_id, _udp_addr, relay_url, ping_actions)) => { + Some((node_id, udp_addr, relay_url, ping_actions)) => { if !ping_actions.is_empty() { self.actor_sender .try_send(ActorMessage::PingActions(ping_actions)) .ok(); } - // NodeId mapped addrs are only used for relays, currently. - // IP based addrs will have been added as individual paths + // Mixed will send all available addrs + if let Some(url) = relay_url { active_paths.push(transports::Addr::Relay(url, node_id)); } + if let Some(addr) = udp_addr { + active_paths.push(transports::Addr::Ip(addr)); + } } None => { error!(%dest, "no NodeState for mapped address"); @@ -812,15 +815,16 @@ impl MagicSock { quic_packets_total += quic_datagram_count; quinn_meta.addr = ip_mapped_addr.private_socket_addr(); } else { - warn!( + trace!( src = %addr, count = %quic_datagram_count, len = quinn_meta.len, - "UDP recv quic packets: no node state found, skipping", + "UDP recv quic packets: no node state found", ); - // If we have no node state for the from addr, set len to 0 to make - // quinn skip the buf completely. - quinn_meta.len = 0; + + // TODO: register in node map + quic_packets_total += quic_datagram_count; + quinn_meta.addr = *addr; } } Some((node_id, quic_mapped_addr)) => { From 4f78898cdf7040636f82b16aa9b837f10db21a12 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 21 Jul 2025 12:14:27 +0200 Subject: [PATCH 13/44] remove ip_mapped_addresses --- iroh/src/magicsock.rs | 44 ++------ iroh/src/magicsock/relay_mapped_addrs.rs | 83 +++++++++++++- iroh/src/net_report.rs | 33 ++---- iroh/src/net_report/ip_mapped_addrs.rs | 134 ----------------------- iroh/src/net_report/reportgen.rs | 19 +--- 5 files changed, 105 insertions(+), 208 deletions(-) delete mode 100644 iroh/src/net_report/ip_mapped_addrs.rs diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index cf034703259..515be0ce9c6 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -45,7 +45,7 @@ use netwatch::{UdpSocket, ip::LocalAddresses}; use quinn::{AsyncUdpSocket, ServerConfig, WeakConnectionHandle}; use quinn_proto::PathEvent; use rand::Rng; -use relay_mapped_addrs::RelayMappedAddresses; +use relay_mapped_addrs::{IpMappedAddr, RelayMappedAddresses}; use smallvec::SmallVec; use snafu::{ResultExt, Snafu}; use tokio::sync::{Mutex as AsyncMutex, mpsc}; @@ -68,14 +68,14 @@ use crate::dns::DnsResolver; #[cfg(any(test, feature = "test-utils"))] use crate::endpoint::PathSelection; #[cfg(not(wasm_browser))] -use crate::net_report::{IpMappedAddr, QuicConfig}; +use crate::net_report::QuicConfig; use crate::{ defaults::timeouts::NET_REPORT_TIMEOUT, disco::{self, SendAddr}, discovery::{Discovery, DiscoveryItem, DiscoverySubscribers, NodeData, UserData}, key::{DecryptionError, SharedSecret, public_ed_box, secret_ed_box}, metrics::EndpointMetrics, - net_report::{self, IfStateDetails, IpMappedAddresses, Report}, + net_report::{self, IfStateDetails, Report}, }; mod metrics; @@ -200,8 +200,6 @@ pub(crate) struct MagicSock { /// Tracks existing connections connection_map: ConnectionMap, - /// Tracks the mapped IP addresses - ip_mapped_addrs: IpMappedAddresses, /// Tracks the mapped IP addresses relay_mapped_addrs: RelayMappedAddresses, /// Local addresses @@ -802,30 +800,15 @@ impl MagicSock { // Update the NodeMap and remap RecvMeta to the NodeIdMappedAddr. match self.node_map.receive_udp(*addr) { None => { - // Check if this address is mapped to an IpMappedAddr - if let Some(ip_mapped_addr) = - self.ip_mapped_addrs.get_mapped_addr(addr) - { - trace!( - src = %addr, - count = %quic_datagram_count, - len = quinn_meta.len, - "UDP recv QUIC address discovery packets", - ); - quic_packets_total += quic_datagram_count; - quinn_meta.addr = ip_mapped_addr.private_socket_addr(); - } else { - trace!( - src = %addr, - count = %quic_datagram_count, - len = quinn_meta.len, - "UDP recv quic packets: no node state found", - ); - - // TODO: register in node map - quic_packets_total += quic_datagram_count; - quinn_meta.addr = *addr; - } + trace!( + src = %addr, + count = %quic_datagram_count, + len = quinn_meta.len, + "UDP recv quic packets", + ); + + quic_packets_total += quic_datagram_count; + quinn_meta.addr = *addr; } Some((node_id, quic_mapped_addr)) => { trace!( @@ -1289,7 +1272,6 @@ impl Handle { let (ip_transports, port_mapper) = bind_ip(addr_v4, addr_v6, &metrics).context(BindSocketsSnafu)?; - let ip_mapped_addrs = IpMappedAddresses::default(); let relay_mapped_addrs = RelayMappedAddresses::default(); let (actor_sender, actor_receiver) = mpsc::channel(256); @@ -1339,7 +1321,6 @@ impl Handle { ipv6_reported, node_map, connection_map: Default::default(), - ip_mapped_addrs: ip_mapped_addrs.clone(), relay_mapped_addrs, discovery, discovery_user_data: RwLock::new(discovery_user_data), @@ -1412,7 +1393,6 @@ impl Handle { #[cfg(not(wasm_browser))] dns_resolver, #[cfg(not(wasm_browser))] - Some(ip_mapped_addrs), relay_map.clone(), net_report_config, metrics.net_report.clone(), diff --git a/iroh/src/magicsock/relay_mapped_addrs.rs b/iroh/src/magicsock/relay_mapped_addrs.rs index 32c8b866220..1ac1eaabacc 100644 --- a/iroh/src/magicsock/relay_mapped_addrs.rs +++ b/iroh/src/magicsock/relay_mapped_addrs.rs @@ -1,9 +1,88 @@ -use std::{collections::BTreeMap, sync::Arc}; +use std::{ + collections::BTreeMap, + net::{IpAddr, Ipv6Addr, SocketAddr}, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, +}; use iroh_base::{NodeId, RelayUrl}; use snafu::Snafu; -use crate::net_report::IpMappedAddr; +/// Can occur when converting a [`SocketAddr`] to an [`IpMappedAddr`] +#[derive(Debug, Snafu)] +#[snafu(display("Failed to convert"))] +pub struct IpMappedAddrError; + +/// A map fake Ipv6 address with an actual IP address. +/// +/// It is essentially a lookup key for an IP that iroh's magicsocket knows about. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub(crate) struct IpMappedAddr(Ipv6Addr); + +/// Counter to always generate unique addresses for [`IpMappedAddr`]. +static IP_ADDR_COUNTER: AtomicU64 = AtomicU64::new(1); + +impl IpMappedAddr { + /// The Prefix/L of our Unique Local Addresses. + const ADDR_PREFIXL: u8 = 0xfd; + /// The Global ID used in our Unique Local Addresses. + const ADDR_GLOBAL_ID: [u8; 5] = [21, 7, 10, 81, 11]; + /// The Subnet ID used in our Unique Local Addresses. + const ADDR_SUBNET: [u8; 2] = [0, 1]; + + /// The dummy port used for all mapped addresses. + const MAPPED_ADDR_PORT: u16 = 12345; + + /// Generates a globally unique fake UDP address. + /// + /// This generates a new IPv6 address in the Unique Local Address range (RFC 4193) + /// which is recognised by iroh as an IP mapped address. + pub(crate) fn generate() -> Self { + let mut addr = [0u8; 16]; + addr[0] = Self::ADDR_PREFIXL; + addr[1..6].copy_from_slice(&Self::ADDR_GLOBAL_ID); + addr[6..8].copy_from_slice(&Self::ADDR_SUBNET); + + let counter = IP_ADDR_COUNTER.fetch_add(1, Ordering::Relaxed); + addr[8..16].copy_from_slice(&counter.to_be_bytes()); + + Self(Ipv6Addr::from(addr)) + } + + /// Returns a consistent [`SocketAddr`] for the [`IpMappedAddr`]. + /// + /// This does not have a routable IP address. + /// + /// This uses a made-up, but fixed port number. The [IpMappedAddresses`] map this is + /// made for creates a unique [`IpMappedAddr`] for each IP+port and thus does not use + /// the port to map back to the original [`SocketAddr`]. + pub(crate) fn private_socket_addr(&self) -> SocketAddr { + SocketAddr::new(IpAddr::from(self.0), Self::MAPPED_ADDR_PORT) + } +} + +impl TryFrom for IpMappedAddr { + type Error = IpMappedAddrError; + + fn try_from(value: Ipv6Addr) -> std::result::Result { + let octets = value.octets(); + if octets[0] == Self::ADDR_PREFIXL + && octets[1..6] == Self::ADDR_GLOBAL_ID + && octets[6..8] == Self::ADDR_SUBNET + { + return Ok(Self(value)); + } + Err(IpMappedAddrError) + } +} + +impl std::fmt::Display for IpMappedAddr { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "IpMappedAddr({})", self.0) + } +} /// Can occur when converting a [`SocketAddr`] to an [`RelayMappedAddr`] #[derive(Debug, Snafu)] diff --git a/iroh/src/net_report.rs b/iroh/src/net_report.rs index e3ab4f5bb45..78b1c28c5e3 100644 --- a/iroh/src/net_report.rs +++ b/iroh/src/net_report.rs @@ -46,7 +46,6 @@ use self::reportgen::QadProbeReport; use self::reportgen::{ProbeFinished, ProbeReport}; mod defaults; -mod ip_mapped_addrs; mod metrics; mod probes; mod report; @@ -73,8 +72,6 @@ pub(crate) mod portmapper { } } -pub(crate) use ip_mapped_addrs::{IpMappedAddr, IpMappedAddresses}; - pub(crate) use self::reportgen::IfStateDetails; #[cfg(not(wasm_browser))] use self::reportgen::SocketState; @@ -215,7 +212,6 @@ impl Client { /// Creates a new net_report client. pub(crate) fn new( #[cfg(not(wasm_browser))] dns_resolver: DnsResolver, - #[cfg(not(wasm_browser))] ip_mapped_addrs: Option, relay_map: RelayMap, opts: Options, metrics: Arc, @@ -233,7 +229,6 @@ impl Client { let socket_state = SocketState { quic_client, dns_resolver, - ip_mapped_addrs, }; Client { @@ -438,7 +433,6 @@ impl Client { for relay_node in self.relay_map.nodes().take(MAX_RELAYS) { if if_state.have_v4 { debug!(?relay_node.url, "v4 QAD probe"); - let ip_mapped_addrs = self.socket_state.ip_mapped_addrs.clone(); let relay_node = relay_node.clone(); let dns_resolver = self.socket_state.dns_resolver.clone(); let quic_client = quic_client.clone(); @@ -448,7 +442,7 @@ impl Client { .child_token() .run_until_cancelled_owned(time::timeout( PROBES_TIMEOUT, - run_probe_v4(ip_mapped_addrs, relay_node, quic_client, dns_resolver), + run_probe_v4(relay_node, quic_client, dns_resolver), )) .instrument(info_span!("QAD-IPv4", %relay_url)), ); @@ -456,7 +450,6 @@ impl Client { if if_state.have_v6 { debug!(?relay_node.url, "v6 QAD probe"); - let ip_mapped_addrs = self.socket_state.ip_mapped_addrs.clone(); let relay_node = relay_node.clone(); let dns_resolver = self.socket_state.dns_resolver.clone(); let quic_client = quic_client.clone(); @@ -466,7 +459,7 @@ impl Client { .child_token() .run_until_cancelled_owned(time::timeout( PROBES_TIMEOUT, - run_probe_v6(ip_mapped_addrs, relay_node, quic_client, dns_resolver), + run_probe_v6(relay_node, quic_client, dns_resolver), )) .instrument(info_span!("QAD-IPv6", %relay_url)), ); @@ -682,20 +675,17 @@ impl Client { #[cfg(not(wasm_browser))] async fn run_probe_v4( - ip_mapped_addrs: Option, relay_node: Arc, quic_client: QuicClient, dns_resolver: DnsResolver, ) -> n0_snafu::Result<(QadProbeReport, QadConn)> { use n0_snafu::ResultExt; - let relay_addr_orig = reportgen::get_relay_addr_ipv4(&dns_resolver, &relay_node).await?; - let relay_addr = - reportgen::maybe_to_mapped_addr(ip_mapped_addrs.as_ref(), relay_addr_orig.into()); + let relay_addr = reportgen::get_relay_addr_ipv4(&dns_resolver, &relay_node).await?; - debug!(?relay_addr_orig, ?relay_addr, "relay addr v4"); + debug!(?relay_addr, "relay addr v4"); let host = relay_node.url.host_str().context("missing host url")?; - let conn = quic_client.create_conn(relay_addr, host).await?; + let conn = quic_client.create_conn(relay_addr.into(), host).await?; let mut receiver = conn.observed_external_addr(); // wait for an addr @@ -750,19 +740,16 @@ async fn run_probe_v4( #[cfg(not(wasm_browser))] async fn run_probe_v6( - ip_mapped_addrs: Option, relay_node: Arc, quic_client: QuicClient, dns_resolver: DnsResolver, ) -> n0_snafu::Result<(QadProbeReport, QadConn)> { use n0_snafu::ResultExt; - let relay_addr_orig = reportgen::get_relay_addr_ipv6(&dns_resolver, &relay_node).await?; - let relay_addr = - reportgen::maybe_to_mapped_addr(ip_mapped_addrs.as_ref(), relay_addr_orig.into()); + let relay_addr = reportgen::get_relay_addr_ipv6(&dns_resolver, &relay_node).await?; - debug!(?relay_addr_orig, ?relay_addr, "relay addr v6"); + debug!(?relay_addr, "relay addr v6"); let host = relay_node.url.host_str().context("missing host url")?; - let conn = quic_client.create_conn(relay_addr, host).await?; + let conn = quic_client.create_conn(relay_addr.into(), host).await?; let mut receiver = conn.observed_external_addr(); // wait for an addr @@ -885,7 +872,6 @@ mod tests { .insecure_skip_relay_cert_verify(true); let mut client = Client::new( resolver.clone(), - None, relay_map.clone(), opts.clone(), Default::default(), @@ -1086,8 +1072,7 @@ mod tests { println!("test: {}", tt.name); let relay_map = RelayMap::empty(); let opts = Options::default(); - let mut client = - Client::new(resolver.clone(), None, relay_map, opts, Default::default()); + let mut client = Client::new(resolver.clone(), relay_map, opts, Default::default()); for s in &mut tt.steps { // trigger the timer tokio::time::advance(Duration::from_secs(s.after)).await; diff --git a/iroh/src/net_report/ip_mapped_addrs.rs b/iroh/src/net_report/ip_mapped_addrs.rs deleted file mode 100644 index 564134e7be9..00000000000 --- a/iroh/src/net_report/ip_mapped_addrs.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::{ - collections::BTreeMap, - net::{IpAddr, Ipv6Addr, SocketAddr}, - sync::{ - Arc, - atomic::{AtomicU64, Ordering}, - }, -}; - -use snafu::Snafu; - -/// Can occur when converting a [`SocketAddr`] to an [`IpMappedAddr`] -#[derive(Debug, Snafu)] -#[snafu(display("Failed to convert"))] -pub struct IpMappedAddrError; - -/// A map fake Ipv6 address with an actual IP address. -/// -/// It is essentially a lookup key for an IP that iroh's magicsocket knows about. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub(crate) struct IpMappedAddr(Ipv6Addr); - -/// Counter to always generate unique addresses for [`IpMappedAddr`]. -static IP_ADDR_COUNTER: AtomicU64 = AtomicU64::new(1); - -impl IpMappedAddr { - /// The Prefix/L of our Unique Local Addresses. - const ADDR_PREFIXL: u8 = 0xfd; - /// The Global ID used in our Unique Local Addresses. - const ADDR_GLOBAL_ID: [u8; 5] = [21, 7, 10, 81, 11]; - /// The Subnet ID used in our Unique Local Addresses. - const ADDR_SUBNET: [u8; 2] = [0, 1]; - - /// The dummy port used for all mapped addresses. - const MAPPED_ADDR_PORT: u16 = 12345; - - /// Generates a globally unique fake UDP address. - /// - /// This generates a new IPv6 address in the Unique Local Address range (RFC 4193) - /// which is recognised by iroh as an IP mapped address. - pub(crate) fn generate() -> Self { - let mut addr = [0u8; 16]; - addr[0] = Self::ADDR_PREFIXL; - addr[1..6].copy_from_slice(&Self::ADDR_GLOBAL_ID); - addr[6..8].copy_from_slice(&Self::ADDR_SUBNET); - - let counter = IP_ADDR_COUNTER.fetch_add(1, Ordering::Relaxed); - addr[8..16].copy_from_slice(&counter.to_be_bytes()); - - Self(Ipv6Addr::from(addr)) - } - - /// Returns a consistent [`SocketAddr`] for the [`IpMappedAddr`]. - /// - /// This does not have a routable IP address. - /// - /// This uses a made-up, but fixed port number. The [IpMappedAddresses`] map this is - /// made for creates a unique [`IpMappedAddr`] for each IP+port and thus does not use - /// the port to map back to the original [`SocketAddr`]. - pub(crate) fn private_socket_addr(&self) -> SocketAddr { - SocketAddr::new(IpAddr::from(self.0), Self::MAPPED_ADDR_PORT) - } -} - -impl TryFrom for IpMappedAddr { - type Error = IpMappedAddrError; - - fn try_from(value: Ipv6Addr) -> std::result::Result { - let octets = value.octets(); - if octets[0] == Self::ADDR_PREFIXL - && octets[1..6] == Self::ADDR_GLOBAL_ID - && octets[6..8] == Self::ADDR_SUBNET - { - return Ok(Self(value)); - } - Err(IpMappedAddrError) - } -} - -impl std::fmt::Display for IpMappedAddr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "IpMappedAddr({})", self.0) - } -} - -/// A Map of [`IpMappedAddresses`] to [`SocketAddr`]. -// TODO(ramfox): before this is ready to be used beyond QAD, we should add -// mechanisms for keeping track of "aliveness" and pruning address, as we do -// with the `NodeMap` -#[derive(Debug, Clone, Default)] -pub(crate) struct IpMappedAddresses(Arc>); - -#[derive(Debug, Default)] -pub(super) struct Inner { - by_mapped_addr: BTreeMap, - /// Because [`std::net::SocketAddrV6`] contains extra fields besides the IP - /// address and port (ie, flow_info and scope_id), the a [`std::net::SocketAddrV6`] - /// with the same IP addr and port might Hash to something different. - /// So to get a hashable key for the map, we are using `(IpAddr, u6)`. - by_ip_port: BTreeMap<(IpAddr, u16), IpMappedAddr>, -} - -impl IpMappedAddresses { - /// Adds a [`SocketAddr`] to the map and returns the generated [`IpMappedAddr`]. - /// - /// If this [`SocketAddr`] already exists in the map, it returns its - /// associated [`IpMappedAddr`]. - /// - /// Otherwise a new [`IpMappedAddr`] is generated for it and returned. - pub(super) fn get_or_register(&self, socket_addr: SocketAddr) -> IpMappedAddr { - let ip_port = (socket_addr.ip(), socket_addr.port()); - let mut inner = self.0.lock().expect("poisoned"); - if let Some(mapped_addr) = inner.by_ip_port.get(&ip_port) { - return *mapped_addr; - } - let ip_mapped_addr = IpMappedAddr::generate(); - inner.by_mapped_addr.insert(ip_mapped_addr, socket_addr); - inner.by_ip_port.insert(ip_port, ip_mapped_addr); - ip_mapped_addr - } - - /// Returns the [`IpMappedAddr`] for the given [`SocketAddr`]. - pub(crate) fn get_mapped_addr(&self, socket_addr: &SocketAddr) -> Option { - let ip_port = (socket_addr.ip(), socket_addr.port()); - let inner = self.0.lock().expect("poisoned"); - inner.by_ip_port.get(&ip_port).copied() - } - - /// Returns the [`SocketAddr`] for the given [`IpMappedAddr`]. - pub(crate) fn get_ip_addr(&self, mapped_addr: &IpMappedAddr) -> Option { - let inner = self.0.lock().expect("poisoned"); - inner.by_mapped_addr.get(mapped_addr).copied() - } -} diff --git a/iroh/src/net_report/reportgen.rs b/iroh/src/net_report/reportgen.rs index 4c26bbea32d..2829b9ab8f1 100644 --- a/iroh/src/net_report/reportgen.rs +++ b/iroh/src/net_report/reportgen.rs @@ -45,6 +45,8 @@ use tokio_util::sync::CancellationToken; use tracing::{Instrument, debug, debug_span, error, info_span, trace, warn}; use url::Host; +#[cfg(not(wasm_browser))] +use super::defaults::timeouts::DNS_TIMEOUT; #[cfg(wasm_browser)] use super::portmapper; // We stub the library use super::{ @@ -52,8 +54,6 @@ use super::{ probes::{Probe, ProbePlan}, }; #[cfg(not(wasm_browser))] -use super::{defaults::timeouts::DNS_TIMEOUT, ip_mapped_addrs::IpMappedAddresses}; -#[cfg(not(wasm_browser))] use crate::discovery::dns::DNS_STAGGERING_MS; use crate::net_report::defaults::timeouts::{ CAPTIVE_PORTAL_DELAY, CAPTIVE_PORTAL_TIMEOUT, OVERALL_REPORT_TIMEOUT, PROBES_TIMEOUT, @@ -105,8 +105,6 @@ pub(crate) struct SocketState { pub(crate) quic_client: Option, /// The DNS resolver to use for probes that need to resolve DNS records. pub(crate) dns_resolver: DnsResolver, - /// Optional [`IpMappedAddresses`] used to enable QAD in iroh - pub(crate) ip_mapped_addrs: Option, } impl Client { @@ -518,17 +516,6 @@ impl Probe { } } -#[cfg(not(wasm_browser))] -pub(super) fn maybe_to_mapped_addr( - ip_mapped_addrs: Option<&IpMappedAddresses>, - addr: SocketAddr, -) -> SocketAddr { - if let Some(ip_mapped_addrs) = ip_mapped_addrs { - return ip_mapped_addrs.get_or_register(addr).private_socket_addr(); - } - addr -} - #[cfg(not(wasm_browser))] #[derive(Debug, Snafu)] #[snafu(module)] @@ -874,7 +861,7 @@ mod tests { let quic_client = iroh_relay::quic::QuicClient::new(ep.clone(), client_config); let dns_resolver = DnsResolver::default(); - let (report, conn) = super::super::run_probe_v4(None, relay, quic_client, dns_resolver) + let (report, conn) = super::super::run_probe_v4(relay, quic_client, dns_resolver) .await .unwrap(); From 130710fb43c8eca93b4db8165e71ae6a1d745d3e Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 21 Jul 2025 12:38:16 +0200 Subject: [PATCH 14/44] use correct relay addr on recv --- iroh/src/magicsock.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 515be0ce9c6..4729873c26d 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -825,8 +825,11 @@ impl MagicSock { } transports::Addr::Relay(src_url, src_node) => { // Relay - let quic_mapped_addr = self.node_map.receive_relay(src_url, *src_node); - quinn_meta.addr = quic_mapped_addr.private_socket_addr(); + let _quic_mapped_addr = self.node_map.receive_relay(src_url, *src_node); + let mapped_addr = self + .relay_mapped_addrs + .get_or_register(src_url.clone(), *src_node); + quinn_meta.addr = mapped_addr.private_socket_addr(); } } } else { From dba89df89ad29443d2c30d41fd84887fc8b45eb8 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 21 Jul 2025 12:45:42 +0200 Subject: [PATCH 15/44] ensure connection registration --- iroh/src/endpoint.rs | 45 ++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 856ca358104..22c0df41eaf 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -1623,7 +1623,7 @@ impl Future for IncomingFuture { Poll::Pending => Poll::Pending, Poll::Ready(Err(err)) => Poll::Ready(Err(err)), Poll::Ready(Ok(inner)) => { - let conn = Connection { inner }; + let conn = Connection::new(inner, None, &this.ep); try_send_rtt_msg(&conn, this.ep, None); Poll::Ready(Ok(conn)) } @@ -1711,11 +1711,12 @@ impl Connecting { pub fn into_0rtt(self) -> Result<(Connection, ZeroRttAccepted), Self> { match self.inner.into_0rtt() { Ok((inner, zrtt_accepted)) => { - let conn = Connection { inner }; + let conn = Connection::new(inner, self.remote_node_id, &self.ep); let zrtt_accepted = ZeroRttAccepted { inner: zrtt_accepted, _discovery_drop_guard: self._discovery_drop_guard, }; + // This call is why `self.remote_node_id` was introduced. // When we `Connecting::into_0rtt`, then we don't yet have `handshake_data` // in our `Connection`, thus `try_send_rtt_msg` won't be able to pick up @@ -1761,24 +1762,7 @@ impl Future for Connecting { Poll::Pending => Poll::Pending, Poll::Ready(Err(err)) => Poll::Ready(Err(err)), Poll::Ready(Ok(inner)) => { - let conn = Connection { inner }; - - // Grab the remote identity and register this connection - if let Some(remote) = *this.remote_node_id { - let weak_handle = conn.inner.weak_handle(); - let path_events = conn.inner.path_events(); - this.ep - .msock - .register_connection(remote, weak_handle, path_events); - } else if let Ok(remote) = conn.remote_node_id() { - let weak_handle = conn.inner.weak_handle(); - let path_events = conn.inner.path_events(); - this.ep - .msock - .register_connection(remote, weak_handle, path_events); - } else { - warn!("unable to determine node id for the remote"); - } + let conn = Connection::new(inner, *this.remote_node_id, &this.ep); try_send_rtt_msg(&conn, this.ep, *this.remote_node_id); Poll::Ready(Ok(conn)) @@ -1838,6 +1822,27 @@ pub struct RemoteNodeIdError { } impl Connection { + fn new(inner: quinn::Connection, remote_id: Option, ep: &Endpoint) -> Self { + let conn = Connection { inner }; + + // Grab the remote identity and register this connection + if let Some(remote) = remote_id { + let weak_handle = conn.inner.weak_handle(); + let path_events = conn.inner.path_events(); + ep.msock + .register_connection(remote, weak_handle, path_events); + } else if let Ok(remote) = conn.remote_node_id() { + let weak_handle = conn.inner.weak_handle(); + let path_events = conn.inner.path_events(); + ep.msock + .register_connection(remote, weak_handle, path_events); + } else { + warn!("unable to determine node id for the remote"); + } + + conn + } + /// Initiates a new outgoing unidirectional stream. /// /// Streams are cheap and instantaneous to open unless blocked by flow control. As a From aab083d5b0316153b532dffb2a2f19e9b0d5afe6 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 21 Jul 2025 12:50:42 +0200 Subject: [PATCH 16/44] remove rtt_actor, this is now done inside quinn on a per path basis --- iroh/src/endpoint.rs | 84 +++++----------- iroh/src/endpoint/rtt_actor.rs | 171 --------------------------------- 2 files changed, 24 insertions(+), 231 deletions(-) delete mode 100644 iroh/src/endpoint/rtt_actor.rs diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 22c0df41eaf..15ab21abda9 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -28,28 +28,6 @@ use n0_future::{Stream, time::Duration}; use n0_watcher::Watcher; use nested_enum_utils::common_fields; use pin_project::pin_project; -use snafu::{ResultExt, Snafu, ensure}; -use tracing::{debug, instrument, trace, warn}; -use url::Url; - -#[cfg(wasm_browser)] -use crate::discovery::pkarr::PkarrResolver; -use crate::{ - RelayProtocol, - discovery::{ - ConcurrentDiscovery, Discovery, DiscoveryContext, DiscoveryError, DiscoveryItem, - DiscoverySubscribers, DiscoveryTask, DynIntoDiscovery, IntoDiscovery, IntoDiscoveryError, - Lagged, UserData, pkarr::PkarrPublisher, - }, - magicsock::{self, Handle, NodeIdMappedAddr, OwnAddressSnafu}, - metrics::EndpointMetrics, - net_report::Report, - tls, -}; -#[cfg(not(wasm_browser))] -use crate::{discovery::dns::DnsDiscovery, dns::DnsResolver}; - -mod rtt_actor; // Missing still: SendDatagram and ConnectionClose::frame_type's Type. pub use quinn::{ @@ -67,12 +45,29 @@ pub use quinn_proto::{ ServerConfig as CryptoServerConfig, UnsupportedVersion, }, }; +use snafu::{ResultExt, Snafu, ensure}; +use tracing::{debug, instrument, trace, warn}; +use url::Url; -use self::rtt_actor::RttMessage; pub use super::magicsock::{ AddNodeAddrError, ConnectionType, ControlMsg, DirectAddr, DirectAddrInfo, DirectAddrType, RemoteInfo, Source, }; +#[cfg(wasm_browser)] +use crate::discovery::pkarr::PkarrResolver; +#[cfg(not(wasm_browser))] +use crate::{discovery::dns::DnsDiscovery, dns::DnsResolver}; +use crate::{ + discovery::{ + ConcurrentDiscovery, Discovery, DiscoveryContext, DiscoveryError, DiscoveryItem, + DiscoverySubscribers, DiscoveryTask, DynIntoDiscovery, IntoDiscovery, IntoDiscoveryError, + Lagged, UserData, pkarr::PkarrPublisher, + }, + magicsock::{self, Handle, NodeIdMappedAddr, OwnAddressSnafu}, + metrics::EndpointMetrics, + net_report::Report, + tls, +}; /// The delay to fall back to discovery when direct addresses fail. /// @@ -527,8 +522,6 @@ impl StaticConfig { pub struct Endpoint { /// Handle to the magicsocket/actor msock: Handle, - /// Handle to the actor that resets the quinn RTT estimator - rtt_actor: Arc, /// Configuration structs for quinn, holds the transport config, certificate setup, secret key etc. static_config: Arc, } @@ -638,7 +631,6 @@ impl Endpoint { let metrics = msock.metrics.magicsock.clone(); let ep = Self { msock, - rtt_actor: Arc::new(rtt_actor::RttHandle::new(metrics)), static_config: Arc::new(static_config), }; Ok(ep) @@ -1624,7 +1616,6 @@ impl Future for IncomingFuture { Poll::Ready(Err(err)) => Poll::Ready(Err(err)), Poll::Ready(Ok(inner)) => { let conn = Connection::new(inner, None, &this.ep); - try_send_rtt_msg(&conn, this.ep, None); Poll::Ready(Ok(conn)) } } @@ -1711,19 +1702,18 @@ impl Connecting { pub fn into_0rtt(self) -> Result<(Connection, ZeroRttAccepted), Self> { match self.inner.into_0rtt() { Ok((inner, zrtt_accepted)) => { + // This call is why `self.remote_node_id` was introduced. + // When we `Connecting::into_0rtt`, then we don't yet have `handshake_data` + // in our `Connection`, thus we won't be able to pick up + // `Connection::remote_node_id`. + // Instead, we provide `self.remote_node_id` here - we know it in advance, + // after all. let conn = Connection::new(inner, self.remote_node_id, &self.ep); let zrtt_accepted = ZeroRttAccepted { inner: zrtt_accepted, _discovery_drop_guard: self._discovery_drop_guard, }; - // This call is why `self.remote_node_id` was introduced. - // When we `Connecting::into_0rtt`, then we don't yet have `handshake_data` - // in our `Connection`, thus `try_send_rtt_msg` won't be able to pick up - // `Connection::remote_node_id`. - // Instead, we provide `self.remote_node_id` here - we know it in advance, - // after all. - try_send_rtt_msg(&conn, &self.ep, self.remote_node_id); Ok((conn, zrtt_accepted)) } Err(inner) => Err(Self { @@ -1763,8 +1753,6 @@ impl Future for Connecting { Poll::Ready(Err(err)) => Poll::Ready(Err(err)), Poll::Ready(Ok(inner)) => { let conn = Connection::new(inner, *this.remote_node_id, &this.ep); - - try_send_rtt_msg(&conn, this.ep, *this.remote_node_id); Poll::Ready(Ok(conn)) } } @@ -2139,30 +2127,6 @@ impl Connection { } } -/// Try send a message to the rtt-actor. -/// -/// If we can't notify the actor that will impact performance a little, but we can still -/// function. -fn try_send_rtt_msg(conn: &Connection, magic_ep: &Endpoint, remote_node_id: Option) { - // If we can't notify the rtt-actor that's not great but not critical. - let Some(node_id) = remote_node_id.or_else(|| conn.remote_node_id().ok()) else { - warn!(?conn, "failed to get remote node id"); - return; - }; - let Some(conn_type_changes) = magic_ep.conn_type(node_id) else { - warn!(?conn, "failed to create conn_type stream"); - return; - }; - let rtt_msg = RttMessage::NewConnection { - connection: conn.inner.weak_handle(), - conn_type_changes: conn_type_changes.stream(), - node_id, - }; - if let Err(err) = magic_ep.rtt_actor.msg_tx.try_send(rtt_msg) { - warn!(?conn, "rtt-actor not reachable: {err:#}"); - } -} - /// Read a proxy url from the environment, in this order /// /// - `HTTP_PROXY` diff --git a/iroh/src/endpoint/rtt_actor.rs b/iroh/src/endpoint/rtt_actor.rs deleted file mode 100644 index 5bc9e6310f8..00000000000 --- a/iroh/src/endpoint/rtt_actor.rs +++ /dev/null @@ -1,171 +0,0 @@ -//! Actor which coordinates the congestion controller for the magic socket - -use std::{pin::Pin, sync::Arc, task::Poll}; - -use iroh_base::NodeId; -use n0_future::{ - MergeUnbounded, Stream, StreamExt, - task::{self, AbortOnDropHandle}, -}; -use tokio::sync::mpsc; -use tracing::{Instrument, debug, info_span}; - -use crate::{magicsock::ConnectionType, metrics::MagicsockMetrics}; - -#[derive(Debug)] -pub(super) struct RttHandle { - // We should and some point use this to propagate panics and errors. - pub(super) _handle: AbortOnDropHandle<()>, - pub(super) msg_tx: mpsc::Sender, -} - -impl RttHandle { - pub(super) fn new(metrics: Arc) -> Self { - let mut actor = RttActor { - connection_events: Default::default(), - metrics, - }; - let (msg_tx, msg_rx) = mpsc::channel(16); - let handle = task::spawn( - async move { - actor.run(msg_rx).await; - } - .instrument(info_span!("rtt-actor")), - ); - Self { - _handle: AbortOnDropHandle::new(handle), - msg_tx, - } - } -} - -/// Messages to send to the [`RttActor`]. -#[derive(Debug)] -pub(super) enum RttMessage { - /// Informs the [`RttActor`] of a new connection is should monitor. - NewConnection { - /// The connection. - connection: quinn::WeakConnectionHandle, - /// Path changes for this connection from the magic socket. - conn_type_changes: n0_watcher::Stream>, - /// For reporting-only, the Node ID of this connection. - node_id: NodeId, - }, -} - -/// Actor to coordinate congestion controller state with magic socket state. -/// -/// The magic socket can change the underlying network path, between two nodes. If we can -/// inform the QUIC congestion controller of this event it will work much more efficiently. -#[derive(derive_more::Debug)] -struct RttActor { - /// Stream of connection type changes. - #[debug("MergeUnbounded>")] - connection_events: MergeUnbounded, - metrics: Arc, -} - -#[derive(Debug)] -struct MappedStream { - stream: n0_watcher::Stream>, - node_id: NodeId, - /// Reference to the connection. - connection: quinn::WeakConnectionHandle, - /// This an indiciator of whether this connection was direct before. - /// This helps establish metrics on number of connections that became direct. - was_direct_before: bool, -} - -struct ConnectionEvent { - became_direct: bool, -} - -impl Stream for MappedStream { - type Item = ConnectionEvent; - - /// Performs the congestion controller reset for a magic socket path change. - /// - /// Regardless of which kind of path we are changed to, the congestion controller needs - /// resetting. Even when switching to mixed we should reset the state as e.g. switching - /// from direct to mixed back to direct should be a rare exception and is a bug if this - /// happens commonly. - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll> { - match Pin::new(&mut self.stream).poll_next(cx) { - Poll::Ready(Some(new_conn_type)) => { - let mut became_direct = false; - if self.connection.network_path_changed() { - debug!( - node_id = %self.node_id.fmt_short(), - new_type = ?new_conn_type, - "Congestion controller state reset", - ); - if !self.was_direct_before && matches!(new_conn_type, ConnectionType::Direct(_)) - { - self.was_direct_before = true; - became_direct = true - } - }; - Poll::Ready(Some(ConnectionEvent { became_direct })) - } - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - } - } -} - -impl RttActor { - /// Runs the actor main loop. - /// - /// The main loop will finish when the sender is dropped. - async fn run(&mut self, mut msg_rx: mpsc::Receiver) { - loop { - tokio::select! { - biased; - msg = msg_rx.recv() => { - match msg { - Some(msg) => self.handle_msg(msg), - None => break, - } - } - event = self.connection_events.next(), if !self.connection_events.is_empty() => { - if event.map(|e| e.became_direct).unwrap_or(false) { - self.metrics.connection_became_direct.inc(); - } - } - } - } - debug!("rtt-actor finished"); - } - - /// Handle actor messages. - fn handle_msg(&mut self, msg: RttMessage) { - match msg { - RttMessage::NewConnection { - connection, - conn_type_changes, - node_id, - } => { - self.handle_new_connection(connection, conn_type_changes, node_id); - } - } - } - - /// Handles the new connection message. - fn handle_new_connection( - &mut self, - connection: quinn::WeakConnectionHandle, - conn_type_changes: n0_watcher::Stream>, - node_id: NodeId, - ) { - self.connection_events.push(MappedStream { - stream: conn_type_changes, - connection, - node_id, - was_direct_before: false, - }); - self.metrics.connection_handshake_success.inc(); - } -} From d4484dae0cfdd035f25a1ffb575bccf71b911c27 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 21 Jul 2025 13:07:11 +0200 Subject: [PATCH 17/44] open additional paths after the initial connection --- Cargo.lock | 4 ++-- iroh/src/endpoint.rs | 10 ++-------- iroh/src/magicsock.rs | 20 ++++++++++---------- iroh/src/magicsock/node_map.rs | 8 ++++++++ iroh/src/magicsock/node_map/node_state.rs | 11 +++++++++++ iroh/src/magicsock/node_map/udp_paths.rs | 3 +++ 6 files changed, 36 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aeb847038a4..45faf1e4dd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2489,7 +2489,7 @@ dependencies = [ [[package]] name = "iroh-quinn" version = "0.14.0" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#0dc50edf689ee5c6cf21b4ee5c0fea6af548680d" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#89df901286301de17aac88d42adc2aa7de32bc18" dependencies = [ "bytes", "cfg_aliases", @@ -2508,7 +2508,7 @@ dependencies = [ [[package]] name = "iroh-quinn-proto" version = "0.13.0" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#0dc50edf689ee5c6cf21b4ee5c0fea6af548680d" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#89df901286301de17aac88d42adc2aa7de32bc18" dependencies = [ "bytes", "fastbloom", diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 15ab21abda9..cb2c8dae87a 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -1815,15 +1815,9 @@ impl Connection { // Grab the remote identity and register this connection if let Some(remote) = remote_id { - let weak_handle = conn.inner.weak_handle(); - let path_events = conn.inner.path_events(); - ep.msock - .register_connection(remote, weak_handle, path_events); + ep.msock.register_connection(remote, &conn.inner); } else if let Ok(remote) = conn.remote_node_id() { - let weak_handle = conn.inner.weak_handle(); - let path_events = conn.inner.path_events(); - ep.msock - .register_connection(remote, weak_handle, path_events); + ep.msock.register_connection(remote, &conn.inner); } else { warn!("unable to determine node id for the remote"); } diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 4729873c26d..e5bc896672a 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -43,7 +43,6 @@ use netwatch::netmon; #[cfg(not(wasm_browser))] use netwatch::{UdpSocket, ip::LocalAddresses}; use quinn::{AsyncUdpSocket, ServerConfig, WeakConnectionHandle}; -use quinn_proto::PathEvent; use rand::Rng; use relay_mapped_addrs::{IpMappedAddr, RelayMappedAddresses}; use smallvec::SmallVec; @@ -292,18 +291,14 @@ impl MagicSock { self.local_addrs_watch.clone().get() } - pub(crate) fn register_connection( - &self, - remote: NodeId, - conn: WeakConnectionHandle, - mut path_events: tokio::sync::broadcast::Receiver, - ) { - self.connection_map.insert(remote, conn); + pub(crate) fn register_connection(&self, remote: NodeId, conn: &quinn::Connection) { + let weak_handle = conn.weak_handle(); + self.connection_map.insert(remote, weak_handle); - // TODO: open additional paths // TODO: track task // TODO: find a good home for this - task::spawn(async move { + let mut path_events = conn.path_events(); + let _task = task::spawn(async move { loop { match path_events.recv().await { Ok(event) => { @@ -316,6 +311,11 @@ impl MagicSock { } } }); + + // open additional paths + if let Some(addr) = self.node_map.get_current_addr(remote) { + self.add_paths(addr); + } } #[cfg(not(wasm_browser))] diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index 4eb8e3df716..bb0fd98734b 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -186,6 +186,14 @@ impl NodeMap { .unwrap_or_default() } + pub(super) fn get_current_addr(&self, node_key: NodeId) -> Option { + self.inner + .lock() + .expect("poisoned") + .get(NodeStateKey::NodeId(node_key)) + .map(|ep| ep.get_current_addr()) + } + pub(super) fn handle_call_me_maybe( &self, sender: PublicKey, diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index 96162223663..36ad6a0de13 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -667,6 +667,17 @@ impl NodeState { (udp_addr, relay_url, ping_msgs) } + pub(crate) fn get_current_addr(&self) -> NodeAddr { + // TODO: more selective? + let mut node_addr = + NodeAddr::new(self.node_id).with_direct_addresses(self.udp_paths.addrs()); + if let Some((url, _)) = &self.relay_url { + node_addr = node_addr.with_relay_url(url.clone()); + } + + node_addr + } + /// Get the direct addresses for this endpoint. pub(super) fn direct_addresses(&self) -> impl Iterator + '_ { self.udp_paths.paths.keys().copied() diff --git a/iroh/src/magicsock/node_map/udp_paths.rs b/iroh/src/magicsock/node_map/udp_paths.rs index 475f865e59b..d89cf10ed2c 100644 --- a/iroh/src/magicsock/node_map/udp_paths.rs +++ b/iroh/src/magicsock/node_map/udp_paths.rs @@ -106,6 +106,9 @@ impl NodeUdpPaths { best, } } + pub(super) fn addrs(&self) -> Vec { + self.paths.keys().map(|ip| (*ip).into()).collect() + } /// Returns the current UDP address to send on. pub(super) fn send_addr(&self, have_ipv6: bool) -> &UdpSendAddr { From 6cb94e4bdf2132a813ebee00f1e754a573daa695 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 22 Jul 2025 12:10:14 +0200 Subject: [PATCH 18/44] ensure path open --- Cargo.lock | 6 +++--- Cargo.toml | 6 +++--- iroh/src/magicsock.rs | 7 +++++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45faf1e4dd8..92171f43d7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2489,7 +2489,7 @@ dependencies = [ [[package]] name = "iroh-quinn" version = "0.14.0" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#89df901286301de17aac88d42adc2aa7de32bc18" +source = "git+https://github.com//n0-computer/quinn?branch=flub%2Fopen-path-ensure#646e849d540886ee58e25e3da509d6021ec94430" dependencies = [ "bytes", "cfg_aliases", @@ -2508,7 +2508,7 @@ dependencies = [ [[package]] name = "iroh-quinn-proto" version = "0.13.0" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#89df901286301de17aac88d42adc2aa7de32bc18" +source = "git+https://github.com//n0-computer/quinn?branch=flub%2Fopen-path-ensure#646e849d540886ee58e25e3da509d6021ec94430" dependencies = [ "bytes", "fastbloom", @@ -2544,7 +2544,7 @@ dependencies = [ [[package]] name = "iroh-quinn-udp" version = "0.5.12" -source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#79e3fcc710de68b40fd05be5421048bab658ddf4" +source = "git+https://github.com//n0-computer/quinn?branch=flub%2Fopen-path-ensure#646e849d540886ee58e25e3da509d6021ec94430" dependencies = [ "cfg_aliases", "libc", diff --git a/Cargo.toml b/Cargo.toml index eb098793da4..4e6324f349d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,6 @@ netwatch = { git = "https://github.com/n0-computer/net-tools", branch = "feat-mu # iroh-quinn-proto = { path = "../iroh-quinn/quinn-proto" } # iroh-quinn-udp = { path = "../iroh-quinn/quinn-udp" } -# iroh-quinn = { git = "https://github.com//n0-computer/quinn", branch = "flub/quinn-path-events-status" } -# iroh-quinn-proto = { git = "https://github.com//n0-computer/quinn", branch = "flub/quinn-path-events-status" } -# iroh-quinn-udp = { git = "https://github.com//n0-computer/quinn", branch = "flub/quinn-path-events-status" } +iroh-quinn = { git = "https://github.com//n0-computer/quinn", branch = "flub/open-path-ensure" } +iroh-quinn-proto = { git = "https://github.com//n0-computer/quinn", branch = "flub/open-path-ensure" } +iroh-quinn-udp = { git = "https://github.com//n0-computer/quinn", branch = "flub/open-path-ensure" } diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index e5bc896672a..626621ac37b 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -476,7 +476,7 @@ impl MagicSock { let addr = *addr; task::spawn(async move { match conn - .open_path(addr, quinn_proto::PathStatus::Available) + .open_path_ensure(addr, quinn_proto::PathStatus::Available) .await { Ok(path) => { @@ -497,7 +497,10 @@ impl MagicSock { let conn = conn.clone(); let addr = addr.private_socket_addr(); task::spawn(async move { - match conn.open_path(addr, quinn_proto::PathStatus::Backup).await { + match conn + .open_path_ensure(addr, quinn_proto::PathStatus::Backup) + .await + { Ok(path) => { // Keep the relay path open path.set_max_idle_timeout(None).ok(); From 998e2833a21b4dd47da6bb3b4d8e2ce2d77528c7 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 23 Jul 2025 11:44:28 +0200 Subject: [PATCH 19/44] some debugging --- iroh/src/magicsock.rs | 128 +++++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 57 deletions(-) diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 626621ac37b..c0cb0b0a256 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -292,25 +292,29 @@ impl MagicSock { } pub(crate) fn register_connection(&self, remote: NodeId, conn: &quinn::Connection) { + debug!(%remote, "register connection"); let weak_handle = conn.weak_handle(); self.connection_map.insert(remote, weak_handle); // TODO: track task // TODO: find a good home for this let mut path_events = conn.path_events(); - let _task = task::spawn(async move { - loop { - match path_events.recv().await { - Ok(event) => { - info!(remote = %remote, "path event: {:?}", event); - } - Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => { - warn!("lagged path events"); + let _task = task::spawn( + async move { + loop { + match path_events.recv().await { + Ok(event) => { + info!(remote = %remote, "path event: {:?}", event); + } + Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => { + warn!("lagged path events"); + } + Err(tokio::sync::broadcast::error::RecvError::Closed) => break, } - Err(tokio::sync::broadcast::error::RecvError::Closed) => break, } } - }); + .instrument(info_span!("path events", %remote)), + ); // open additional paths if let Some(addr) = self.node_map.get_current_addr(remote) { @@ -474,43 +478,51 @@ impl MagicSock { for addr in addr.direct_addresses() { let conn = conn.clone(); let addr = *addr; - task::spawn(async move { - match conn - .open_path_ensure(addr, quinn_proto::PathStatus::Available) - .await - { - Ok(path) => { - path.set_max_idle_timeout(Some( - ENDPOINTS_FRESH_ENOUGH_DURATION, - )) - .ok(); - path.set_keep_alive_interval(Some(HEARTBEAT_INTERVAL)).ok(); - } - Err(err) => { - warn!("failed to open path {:?}", err); + task::spawn( + async move { + debug!(%addr, "open path IP"); + match conn + .open_path_ensure(addr, quinn_proto::PathStatus::Available) + .await + { + Ok(path) => { + path.set_max_idle_timeout(Some( + ENDPOINTS_FRESH_ENOUGH_DURATION, + )) + .ok(); + path.set_keep_alive_interval(Some(HEARTBEAT_INTERVAL)).ok(); + } + Err(err) => { + warn!("failed to open path {:?}", err); + } } } - }); + .instrument(info_span!("open path IP")), + ); } // Insert the relay addr if let Some(addr) = self.get_mapping_addr(addr.node_id) { let conn = conn.clone(); let addr = addr.private_socket_addr(); - task::spawn(async move { - match conn - .open_path_ensure(addr, quinn_proto::PathStatus::Backup) - .await - { - Ok(path) => { - // Keep the relay path open - path.set_max_idle_timeout(None).ok(); - path.set_keep_alive_interval(None).ok(); - } - Err(err) => { - warn!("failed to open path {:?}", err); + task::spawn( + async move { + debug!(%addr, "open path relay"); + match conn + .open_path_ensure(addr, quinn_proto::PathStatus::Backup) + .await + { + Ok(path) => { + // Keep the relay path open + path.set_max_idle_timeout(None).ok(); + path.set_keep_alive_interval(None).ok(); + } + Err(err) => { + warn!("failed to open path {:?}", err); + } } } - }); + .instrument(info_span!("open path relay")), + ); } } else { to_delete.push(i); @@ -644,19 +656,21 @@ impl MagicSock { self.ipv6_reported.load(Ordering::Relaxed), &self.metrics.magicsock, ) { - Some((node_id, udp_addr, relay_url, ping_actions)) => { + Some((node_id, _udp_addr, _relay_url, ping_actions)) => { if !ping_actions.is_empty() { self.actor_sender .try_send(ActorMessage::PingActions(ping_actions)) .ok(); } - // Mixed will send all available addrs - if let Some(url) = relay_url { - active_paths.push(transports::Addr::Relay(url, node_id)); - } - if let Some(addr) = udp_addr { - active_paths.push(transports::Addr::Ip(addr)); + if let Some(addr) = self.node_map.get_current_addr(node_id) { + // Mixed will send all available addrs + if let Some(ref url) = addr.relay_url { + active_paths.push(transports::Addr::Relay(url.clone(), node_id)); + } + for ip in addr.direct_addresses() { + active_paths.push(transports::Addr::Ip(*ip)); + } } } None => { @@ -3202,18 +3216,18 @@ mod tests { let _accept_task = AbortOnDropHandle::new(accept_task); // Add an empty entry in the NodeMap of ep_1 - msock_1 - .add_node_addr( - NodeAddr { - node_id: node_id_2, - relay_url: None, - direct_addresses: Default::default(), - }, - Source::NamedApp { - name: "test".into(), - }, - ) - .unwrap(); + msock_1.node_map.add_node_addr( + NodeAddr { + node_id: node_id_2, + relay_url: None, + direct_addresses: Default::default(), + }, + Source::NamedApp { + name: "test".into(), + }, + &msock_1.metrics.magicsock, + ); + let addr_2 = msock_1.get_mapping_addr(node_id_2).unwrap(); // Set a low max_idle_timeout so quinn gives up on this quickly and our test does From 99cee6121e5d2047d3b7d129a7d967b7125e95f5 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 23 Jul 2025 15:21:20 +0200 Subject: [PATCH 20/44] update quinn branch --- Cargo.lock | 6 +++--- Cargo.toml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 92171f43d7f..88205723258 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2489,7 +2489,7 @@ dependencies = [ [[package]] name = "iroh-quinn" version = "0.14.0" -source = "git+https://github.com//n0-computer/quinn?branch=flub%2Fopen-path-ensure#646e849d540886ee58e25e3da509d6021ec94430" +source = "git+https://github.com//n0-computer/quinn?branch=server-migrations#bc86957aa4ccb72fad70e75a6ce9fc8198f09afc" dependencies = [ "bytes", "cfg_aliases", @@ -2508,7 +2508,7 @@ dependencies = [ [[package]] name = "iroh-quinn-proto" version = "0.13.0" -source = "git+https://github.com//n0-computer/quinn?branch=flub%2Fopen-path-ensure#646e849d540886ee58e25e3da509d6021ec94430" +source = "git+https://github.com//n0-computer/quinn?branch=server-migrations#bc86957aa4ccb72fad70e75a6ce9fc8198f09afc" dependencies = [ "bytes", "fastbloom", @@ -2544,7 +2544,7 @@ dependencies = [ [[package]] name = "iroh-quinn-udp" version = "0.5.12" -source = "git+https://github.com//n0-computer/quinn?branch=flub%2Fopen-path-ensure#646e849d540886ee58e25e3da509d6021ec94430" +source = "git+https://github.com//n0-computer/quinn?branch=server-migrations#bc86957aa4ccb72fad70e75a6ce9fc8198f09afc" dependencies = [ "cfg_aliases", "libc", diff --git a/Cargo.toml b/Cargo.toml index 4e6324f349d..8e7b8f11f49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,6 @@ netwatch = { git = "https://github.com/n0-computer/net-tools", branch = "feat-mu # iroh-quinn-proto = { path = "../iroh-quinn/quinn-proto" } # iroh-quinn-udp = { path = "../iroh-quinn/quinn-udp" } -iroh-quinn = { git = "https://github.com//n0-computer/quinn", branch = "flub/open-path-ensure" } -iroh-quinn-proto = { git = "https://github.com//n0-computer/quinn", branch = "flub/open-path-ensure" } -iroh-quinn-udp = { git = "https://github.com//n0-computer/quinn", branch = "flub/open-path-ensure" } +iroh-quinn = { git = "https://github.com//n0-computer/quinn", branch = "server-migrations" } +iroh-quinn-proto = { git = "https://github.com//n0-computer/quinn", branch = "server-migrations" } +iroh-quinn-udp = { git = "https://github.com//n0-computer/quinn", branch = "server-migrations" } From 4b60a9a2c47e986f1787b2f6dbdc833cd2df6076 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 28 Jul 2025 16:40:18 +0200 Subject: [PATCH 21/44] fixups --- Cargo.lock | 4 ++-- iroh/src/endpoint.rs | 1 - iroh/src/magicsock/relay_mapped_addrs.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88205723258..9d6f1e92b40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6452,5 +6452,5 @@ dependencies = [ [[patch.unused]] name = "netwatch" -version = "0.6.0" -source = "git+https://github.com/n0-computer/net-tools?branch=feat-multipath#b7ab98d4ff9cc947f2f084004b4cc2a979bb4d06" +version = "0.7.0" +source = "git+https://github.com/n0-computer/net-tools?branch=feat-multipath#5196858f5754f906e6d205a3f3623831c9236965" diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index cb2c8dae87a..e79efd77555 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -28,7 +28,6 @@ use n0_future::{Stream, time::Duration}; use n0_watcher::Watcher; use nested_enum_utils::common_fields; use pin_project::pin_project; - // Missing still: SendDatagram and ConnectionClose::frame_type's Type. pub use quinn::{ AcceptBi, AcceptUni, AckFrequencyConfig, ApplicationClose, Chunk, ClosedStream, diff --git a/iroh/src/magicsock/relay_mapped_addrs.rs b/iroh/src/magicsock/relay_mapped_addrs.rs index 1ac1eaabacc..b473501ea60 100644 --- a/iroh/src/magicsock/relay_mapped_addrs.rs +++ b/iroh/src/magicsock/relay_mapped_addrs.rs @@ -2,8 +2,8 @@ use std::{ collections::BTreeMap, net::{IpAddr, Ipv6Addr, SocketAddr}, sync::{ - atomic::{AtomicU64, Ordering}, Arc, + atomic::{AtomicU64, Ordering}, }, }; From 04b714cced1b5898c77b5408d2c8e14436ffa2ef Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Fri, 1 Aug 2025 11:27:16 +0200 Subject: [PATCH 22/44] update deps --- Cargo.lock | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d6f1e92b40..fc60e20fe7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2316,7 +2316,7 @@ dependencies = [ "iroh-metrics", "iroh-quinn", "iroh-quinn-proto", - "iroh-quinn-udp 0.5.12", + "iroh-quinn-udp", "iroh-relay", "n0-future", "n0-snafu", @@ -2494,7 +2494,7 @@ dependencies = [ "bytes", "cfg_aliases", "iroh-quinn-proto", - "iroh-quinn-udp 0.5.12", + "iroh-quinn-udp", "pin-project-lite", "rustc-hash", "rustls", @@ -2527,20 +2527,6 @@ dependencies = [ "web-time", ] -[[package]] -name = "iroh-quinn-udp" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53afaa1049f7c83ea1331f5ebb9e6ebc5fdd69c468b7a22dd598b02c9bcc973" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2 0.5.10", - "tracing", - "windows-sys 0.59.0", -] - [[package]] name = "iroh-quinn-udp" version = "0.5.12" @@ -3044,14 +3030,13 @@ dependencies = [ [[package]] name = "netwatch" version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901dbb408894af3df3fc51420ba0c6faf3a7d896077b797c39b7001e2f787bd" +source = "git+https://github.com/n0-computer/net-tools?branch=feat-multipath#e824fd0fb03839c4523c18cd3ab861d6b3e66a16" dependencies = [ "atomic-waker", "bytes", "cfg_aliases", "derive_more 2.0.1", - "iroh-quinn-udp 0.5.7", + "iroh-quinn-udp", "js-sys", "libc", "n0-future", @@ -5719,7 +5704,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -6449,8 +6434,3 @@ dependencies = [ "quote", "syn 2.0.101", ] - -[[patch.unused]] -name = "netwatch" -version = "0.7.0" -source = "git+https://github.com/n0-computer/net-tools?branch=feat-multipath#5196858f5754f906e6d205a3f3623831c9236965" From 59efdcf9f33f12358aafe4a2b44ed3c1d3d30695 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Thu, 28 Aug 2025 15:06:13 +0200 Subject: [PATCH 23/44] bunch of renames and doc updates, no functional changes --- iroh/src/endpoint.rs | 4 +- iroh/src/magicsock.rs | 71 +++++++++++++---------- iroh/src/magicsock/node_map.rs | 19 +++--- iroh/src/magicsock/node_map/node_state.rs | 9 +-- iroh/src/magicsock/relay_mapped_addrs.rs | 55 +++++++++--------- 5 files changed, 87 insertions(+), 71 deletions(-) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index e79efd77555..a6ba61c4bce 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -62,7 +62,7 @@ use crate::{ DiscoverySubscribers, DiscoveryTask, DynIntoDiscovery, IntoDiscovery, IntoDiscoveryError, Lagged, UserData, pkarr::PkarrPublisher, }, - magicsock::{self, Handle, NodeIdMappedAddr, OwnAddressSnafu}, + magicsock::{self, AllPathsMappedAddr, Handle, OwnAddressSnafu}, metrics::EndpointMetrics, net_report::Report, tls, @@ -1363,7 +1363,7 @@ impl Endpoint { async fn get_mapping_addr_and_maybe_start_discovery( &self, node_addr: NodeAddr, - ) -> Result<(NodeIdMappedAddr, Option), GetMappingAddressError> { + ) -> Result<(AllPathsMappedAddr, Option), GetMappingAddressError> { let node_id = node_addr.node_id; // Only return a mapped addr if we have some way of dialing this node, in other diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index c0cb0b0a256..c5c57055c0c 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -44,7 +44,7 @@ use netwatch::netmon; use netwatch::{UdpSocket, ip::LocalAddresses}; use quinn::{AsyncUdpSocket, ServerConfig, WeakConnectionHandle}; use rand::Rng; -use relay_mapped_addrs::{IpMappedAddr, RelayMappedAddresses}; +use relay_mapped_addrs::{RelayAddrMap, RelayMappedAddr}; use smallvec::SmallVec; use snafu::{ResultExt, Snafu}; use tokio::sync::{Mutex as AsyncMutex, mpsc}; @@ -200,7 +200,7 @@ pub(crate) struct MagicSock { connection_map: ConnectionMap, /// Tracks the mapped IP addresses - relay_mapped_addrs: RelayMappedAddresses, + relay_mapped_addrs: RelayAddrMap, /// Local addresses local_addrs_watch: LocalAddrsWatch, /// Currently bound IP addresses of all sockets @@ -291,6 +291,11 @@ impl MagicSock { self.local_addrs_watch.clone().get() } + /// Registers the connection in the connection map and opens additional paths. + /// + /// In addition to storing the connection reference this requests the current + /// [`NodeAddr`] for remote node from the [`NodeMap`] and adds all paths to the + /// connection. It also listens and logs path events. pub(crate) fn register_connection(&self, remote: NodeId, conn: &quinn::Connection) { debug!(%remote, "register connection"); let weak_handle = conn.weak_handle(); @@ -425,7 +430,7 @@ impl MagicSock { } /// Returns the socket address which can be used by the QUIC layer to dial this node. - pub(crate) fn get_mapping_addr(&self, node_id: NodeId) -> Option { + pub(crate) fn get_mapping_addr(&self, node_id: NodeId) -> Option { self.node_map.get_quic_mapped_addr_for_node_key(node_id) } @@ -616,7 +621,11 @@ impl MagicSock { } } - /// Searches the `node_map` to determine the current transports to be used. + /// Returns the transport addresses for the [`quinn_udp::Transmit`]'s destination. + /// + /// Because Quinn does only know about IP transports we map other transports to private + /// IPv6 Unique Local Address ranges. This extracts the transport addresses out of the + /// transmit's destination. #[instrument(skip_all)] fn prepare_send( &self, @@ -646,7 +655,7 @@ impl MagicSock { dst = %dest, src = ?transmit.src_ip, len = %transmit.contents.len(), - "sending", + "sending mixed", ); // Get the node's relay address and best direct address, as well @@ -680,6 +689,12 @@ impl MagicSock { } #[cfg(not(wasm_browser))] MultipathMappedAddr::Ip(addr) => { + trace!( + dst = %addr, + src = ?transmit.src_ip, + len = %transmit.contents.len(), + "sending IP", + ); active_paths.push(transports::Addr::Ip(addr)); } MultipathMappedAddr::Relay(dest) => { @@ -687,7 +702,7 @@ impl MagicSock { dst = %dest, src = ?transmit.src_ip, len = %transmit.contents.len(), - "sending", + "sending relay", ); // Check if this is a known IpMappedAddr, and if so, send over UDP @@ -706,12 +721,12 @@ impl MagicSock { Ok(active_paths) } - /// Process datagrams received from UDP sockets. + /// Process datagrams received from all the transports. /// /// All the `bufs` and `metas` should have initialized packets in them. /// - /// This fixes up the datagrams to use the correct [`NodeIdMappedAddr`] and extracts DISCO - /// packets, processing them inside the magic socket. + /// This fixes up the datagrams to use the correct [`MultipathMappedAddr`] and extracts + /// DISCO packets, processing them inside the magic socket. fn process_datagrams( &self, bufs: &mut [io::IoSliceMut<'_>], @@ -1084,9 +1099,9 @@ pub(crate) enum MultipathMappedAddr { /// Used for the initial connection. /// - Only used for sending /// - This means send on all known paths/transports - Mixed(NodeIdMappedAddr), + Mixed(AllPathsMappedAddr), /// Relay based transport address - Relay(IpMappedAddr), // TODO: RelayMappedAddr? + Relay(RelayMappedAddr), /// IP based transport address #[cfg(not(wasm_browser))] Ip(SocketAddr), @@ -1097,11 +1112,11 @@ impl From for MultipathMappedAddr { match value.ip() { IpAddr::V4(_) => Self::Ip(value), IpAddr::V6(addr) => { - if let Ok(node_id_mapped_addr) = NodeIdMappedAddr::try_from(addr) { + if let Ok(node_id_mapped_addr) = AllPathsMappedAddr::try_from(addr) { return Self::Mixed(node_id_mapped_addr); } #[cfg(not(wasm_browser))] - if let Ok(ip_mapped_addr) = IpMappedAddr::try_from(addr) { + if let Ok(ip_mapped_addr) = RelayMappedAddr::try_from(addr) { return Self::Relay(ip_mapped_addr); } Self::Ip(value) @@ -1292,7 +1307,7 @@ impl Handle { let (ip_transports, port_mapper) = bind_ip(addr_v4, addr_v6, &metrics).context(BindSocketsSnafu)?; - let relay_mapped_addrs = RelayMappedAddresses::default(); + let relay_mapped_addrs = RelayAddrMap::default(); let (actor_sender, actor_receiver) = mpsc::channel(256); @@ -2293,21 +2308,17 @@ impl DiscoveredDirectAddrs { } } -/// The fake address used by the QUIC layer to address a node. -/// -/// You can consider this as nothing more than a lookup key for a node the [`MagicSock`] knows -/// about. +/// An address used by the QUIC layer to address a node on all paths. /// -/// [`MagicSock`] can reach a node by several real socket addresses, or maybe even via the relay -/// node. The QUIC layer however needs to address a node by a stable [`SocketAddr`] so -/// that normal socket APIs can function. Thus when a new node is introduced to a [`MagicSock`] -/// it is given a new fake address. This is the type of that address. +/// This is only used for initially connecting to a remote node. We instruct Quinn to send +/// to this address, and duplicate all packets for this address to send on all paths we know +/// for the node. /// /// It is but a newtype. And in our QUIC-facing socket APIs like [`AsyncUdpSocket`] it /// comes in as the inner [`Ipv6Addr`], in those interfaces we have to be careful to do /// the conversion to this type. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub(crate) struct NodeIdMappedAddr(Ipv6Addr); +pub(crate) struct AllPathsMappedAddr(Ipv6Addr); /// Can occur when converting a [`SocketAddr`] to an [`NodeIdMappedAddr`] #[derive(Debug, Snafu)] @@ -2317,7 +2328,7 @@ pub struct NodeIdMappedAddrError; /// Counter to always generate unique addresses for [`NodeIdMappedAddr`]. static NODE_ID_ADDR_COUNTER: AtomicU64 = AtomicU64::new(1); -impl NodeIdMappedAddr { +impl AllPathsMappedAddr { /// The Prefix/L of our Unique Local Addresses. const ADDR_PREFIXL: u8 = 0xfd; /// The Global ID used in our Unique Local Addresses. @@ -2355,7 +2366,7 @@ impl NodeIdMappedAddr { } } -impl TryFrom for NodeIdMappedAddr { +impl TryFrom for AllPathsMappedAddr { type Error = NodeIdMappedAddrError; fn try_from(value: Ipv6Addr) -> Result { @@ -2370,7 +2381,7 @@ impl TryFrom for NodeIdMappedAddr { } } -impl std::fmt::Display for NodeIdMappedAddr { +impl std::fmt::Display for AllPathsMappedAddr { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "NodeIdMappedAddr({})", self.0) } @@ -2460,7 +2471,7 @@ mod tests { use tracing::{Instrument, error, info, info_span, instrument}; use tracing_test::traced_test; - use super::{NodeIdMappedAddr, Options}; + use super::{AllPathsMappedAddr, Options}; use crate::{ Endpoint, RelayMap, RelayMode, SecretKey, dns::DnsResolver, @@ -3037,7 +3048,7 @@ mod tests { async fn magicsock_connect( ep: &quinn::Endpoint, ep_secret_key: SecretKey, - addr: NodeIdMappedAddr, + addr: AllPathsMappedAddr, node_id: NodeId, ) -> Result { // Endpoint::connect sets this, do the same to have similar behaviour. @@ -3063,7 +3074,7 @@ mod tests { async fn magicsock_connect_with_transport_config( ep: &quinn::Endpoint, ep_secret_key: SecretKey, - mapped_addr: NodeIdMappedAddr, + mapped_addr: AllPathsMappedAddr, node_id: NodeId, transport_config: Arc, ) -> Result { @@ -3098,7 +3109,7 @@ mod tests { let msock_1 = magicsock_ep(secret_key_1.clone()).await.unwrap(); // Generate an address not present in the NodeMap. - let bad_addr = NodeIdMappedAddr::generate(); + let bad_addr = AllPathsMappedAddr::generate(); // 500ms is rather fast here. Running this locally it should always be the correct // timeout. If this is too slow however the test will not become flaky as we are diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index bb0fd98734b..36d4b9cdfba 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; use tracing::{debug, info, instrument, trace, warn}; use self::node_state::{NodeState, Options}; -use super::{ActorMessage, NodeIdMappedAddr, metrics::Metrics, transports}; +use super::{ActorMessage, AllPathsMappedAddr, metrics::Metrics, transports}; use crate::disco::{CallMeMaybe, Pong, SendAddr}; #[cfg(any(test, feature = "test-utils"))] use crate::endpoint::PathSelection; @@ -54,7 +54,7 @@ pub(super) struct NodeMap { pub(super) struct NodeMapInner { by_node_key: HashMap, by_ip_port: HashMap, - by_quic_mapped_addr: HashMap, + by_quic_mapped_addr: HashMap, by_id: HashMap, next_id: usize, #[cfg(any(test, feature = "test-utils"))] @@ -68,7 +68,7 @@ pub(super) struct NodeMapInner { #[derive(Debug, Clone)] enum NodeStateKey { NodeId(NodeId), - NodeIdMappedAddr(NodeIdMappedAddr), + NodeIdMappedAddr(AllPathsMappedAddr), IpPort(IpPort), } @@ -155,11 +155,11 @@ impl NodeMap { pub(super) fn receive_udp( &self, udp_addr: SocketAddr, - ) -> Option<(PublicKey, NodeIdMappedAddr)> { + ) -> Option<(PublicKey, AllPathsMappedAddr)> { self.inner.lock().expect("poisoned").receive_udp(udp_addr) } - pub(super) fn receive_relay(&self, relay_url: &RelayUrl, src: NodeId) -> NodeIdMappedAddr { + pub(super) fn receive_relay(&self, relay_url: &RelayUrl, src: NodeId) -> AllPathsMappedAddr { self.inner .lock() .expect("poisoned") @@ -169,7 +169,7 @@ impl NodeMap { pub(super) fn get_quic_mapped_addr_for_node_key( &self, node_key: NodeId, - ) -> Option { + ) -> Option { self.inner .lock() .expect("poisoned") @@ -186,6 +186,7 @@ impl NodeMap { .unwrap_or_default() } + /// Returns a [`NodeAddr`] with all the currently known direct addresses and the relay URL. pub(super) fn get_current_addr(&self, node_key: NodeId) -> Option { self.inner .lock() @@ -209,7 +210,7 @@ impl NodeMap { #[allow(clippy::type_complexity)] pub(super) fn get_send_addrs( &self, - addr: NodeIdMappedAddr, + addr: AllPathsMappedAddr, have_ipv6: bool, metrics: &Metrics, ) -> Option<( @@ -403,7 +404,7 @@ impl NodeMapInner { /// Marks the node we believe to be at `ipp` as recently used. #[cfg(not(wasm_browser))] - fn receive_udp(&mut self, udp_addr: SocketAddr) -> Option<(NodeId, NodeIdMappedAddr)> { + fn receive_udp(&mut self, udp_addr: SocketAddr) -> Option<(NodeId, AllPathsMappedAddr)> { let ip_port: IpPort = udp_addr.into(); let Some(node_state) = self.get_mut(NodeStateKey::IpPort(ip_port)) else { trace!(src=%udp_addr, "receive_udp: no node_state found for addr, ignore"); @@ -414,7 +415,7 @@ impl NodeMapInner { } #[instrument(skip_all, fields(src = %src.fmt_short()))] - fn receive_relay(&mut self, relay_url: &RelayUrl, src: NodeId) -> NodeIdMappedAddr { + fn receive_relay(&mut self, relay_url: &RelayUrl, src: NodeId) -> AllPathsMappedAddr { #[cfg(any(test, feature = "test-utils"))] let path_selection = self.path_selection; let node_state = self.get_or_insert_with(NodeStateKey::NodeId(src), || { diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index 36ad6a0de13..3f211ed359a 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -20,7 +20,7 @@ use crate::endpoint::PathSelection; use crate::{ disco::{self, SendAddr}, magicsock::{ - ActorMessage, HEARTBEAT_INTERVAL, MagicsockMetrics, NodeIdMappedAddr, + ActorMessage, AllPathsMappedAddr, HEARTBEAT_INTERVAL, MagicsockMetrics, node_map::path_validity::PathValidity, }, }; @@ -63,7 +63,7 @@ pub(super) struct NodeState { /// [`NodeMap`]: super::NodeMap id: usize, /// The UDP address used on the QUIC-layer to address this node. - quic_mapped_addr: NodeIdMappedAddr, + quic_mapped_addr: AllPathsMappedAddr, /// The global identifier for this endpoint. node_id: NodeId, /// The url of relay node that we can relay over to communicate. @@ -113,7 +113,7 @@ pub(super) struct Options { impl NodeState { pub(super) fn new(id: usize, options: Options) -> Self { - let quic_mapped_addr = NodeIdMappedAddr::generate(); + let quic_mapped_addr = AllPathsMappedAddr::generate(); // TODO(frando): I don't think we need to track the `num_relay_conns_added` // metric here. We do so in `Self::addr_for_send`. @@ -148,7 +148,7 @@ impl NodeState { &self.node_id } - pub(super) fn quic_mapped_addr(&self) -> &NodeIdMappedAddr { + pub(super) fn quic_mapped_addr(&self) -> &AllPathsMappedAddr { &self.quic_mapped_addr } @@ -667,6 +667,7 @@ impl NodeState { (udp_addr, relay_url, ping_msgs) } + /// Returns a [`NodeAddr`] with all the currently known direct addresses and the relay URL. pub(crate) fn get_current_addr(&self) -> NodeAddr { // TODO: more selective? let mut node_addr = diff --git a/iroh/src/magicsock/relay_mapped_addrs.rs b/iroh/src/magicsock/relay_mapped_addrs.rs index b473501ea60..1b21f1377e2 100644 --- a/iroh/src/magicsock/relay_mapped_addrs.rs +++ b/iroh/src/magicsock/relay_mapped_addrs.rs @@ -15,16 +15,19 @@ use snafu::Snafu; #[snafu(display("Failed to convert"))] pub struct IpMappedAddrError; -/// A map fake Ipv6 address with an actual IP address. +/// An Ipv6 ULA address, identifying a relay path for a [`NodeId`]. /// -/// It is essentially a lookup key for an IP that iroh's magicsocket knows about. +/// Since iroh nodes are reachable via a relay server we have a network path indicated by +/// the `(NodeId, RelayUrl)`. However Quinn can only handle socket addresses, so we use +/// IPv6 addresses in a private IPv6 Unique Local Address range, which map to a unique +/// `(NodeId, RelayUrl)` pair. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub(crate) struct IpMappedAddr(Ipv6Addr); +pub(crate) struct RelayMappedAddr(Ipv6Addr); /// Counter to always generate unique addresses for [`IpMappedAddr`]. static IP_ADDR_COUNTER: AtomicU64 = AtomicU64::new(1); -impl IpMappedAddr { +impl RelayMappedAddr { /// The Prefix/L of our Unique Local Addresses. const ADDR_PREFIXL: u8 = 0xfd; /// The Global ID used in our Unique Local Addresses. @@ -51,19 +54,19 @@ impl IpMappedAddr { Self(Ipv6Addr::from(addr)) } - /// Returns a consistent [`SocketAddr`] for the [`IpMappedAddr`]. + /// Returns a consistent [`SocketAddr`] for the [`RelayMappedAddr`]. /// /// This does not have a routable IP address. /// - /// This uses a made-up, but fixed port number. The [IpMappedAddresses`] map this is - /// made for creates a unique [`IpMappedAddr`] for each IP+port and thus does not use - /// the port to map back to the original [`SocketAddr`]. + /// This uses a made-up, but fixed port number. The [`RelayAddrMap`] creates a unique + /// [`RelayMappedAddr`] for each `(NodeId, RelayUrl)` pair and thus does not use the + /// port to map back to the original [`SocketAddr`]. pub(crate) fn private_socket_addr(&self) -> SocketAddr { SocketAddr::new(IpAddr::from(self.0), Self::MAPPED_ADDR_PORT) } } -impl TryFrom for IpMappedAddr { +impl TryFrom for RelayMappedAddr { type Error = IpMappedAddrError; fn try_from(value: Ipv6Addr) -> std::result::Result { @@ -78,7 +81,7 @@ impl TryFrom for IpMappedAddr { } } -impl std::fmt::Display for IpMappedAddr { +impl std::fmt::Display for RelayMappedAddr { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "IpMappedAddr({})", self.0) } @@ -87,31 +90,31 @@ impl std::fmt::Display for IpMappedAddr { /// Can occur when converting a [`SocketAddr`] to an [`RelayMappedAddr`] #[derive(Debug, Snafu)] #[snafu(display("Failed to convert"))] -pub struct RelayMappedAddrError; +pub struct RelayAddrMapError; -/// A Map of [`RelayMappedAddresses`] to [`SocketAddr`]. +/// A Map of [`RelayMappedAddr`] to `(RelayUrl, NodeId)`. #[derive(Debug, Clone, Default)] -pub(crate) struct RelayMappedAddresses(Arc>); +pub(crate) struct RelayAddrMap(Arc>); #[derive(Debug, Default)] pub(super) struct Inner { - by_mapped_addr: BTreeMap, - by_url: BTreeMap<(RelayUrl, NodeId), IpMappedAddr>, + by_mapped_addr: BTreeMap, + by_url: BTreeMap<(RelayUrl, NodeId), RelayMappedAddr>, } -impl RelayMappedAddresses { - /// Adds a [`RelayUrl`] to the map and returns the generated [`IpMappedAddr`]. +impl RelayAddrMap { + /// Adds a new entry to the map and returns the generated [`RelayMappedAddr`]. /// - /// If this [`RelayUrl`] already exists in the map, it returns its - /// associated [`IpMappedAddr`]. + /// If this `(RelayUrl, NodeId)` already exists in the map, it returns its associated + /// [`RelayMappedAddr`]. /// - /// Otherwise a new [`IpMappedAddr`] is generated for it and returned. - pub(super) fn get_or_register(&self, relay: RelayUrl, node: NodeId) -> IpMappedAddr { + /// Otherwise a new [`RelayMappedAddr`] is generated for it and returned. + pub(super) fn get_or_register(&self, relay: RelayUrl, node: NodeId) -> RelayMappedAddr { let mut inner = self.0.lock().expect("poisoned"); if let Some(mapped_addr) = inner.by_url.get(&(relay.clone(), node)) { return *mapped_addr; } - let ip_mapped_addr = IpMappedAddr::generate(); + let ip_mapped_addr = RelayMappedAddr::generate(); inner .by_mapped_addr .insert(ip_mapped_addr, (relay.clone(), node)); @@ -119,14 +122,14 @@ impl RelayMappedAddresses { ip_mapped_addr } - /// Returns the [`IpMappedAddr`] for the given [`RelayUrl`]. - pub(crate) fn get_mapped_addr(&self, relay: RelayUrl, node: NodeId) -> Option { + /// Returns the [`RelayMappedAddr`] for the given [`RelayUrl`] and [`NodeId`]. + pub(crate) fn get_mapped_addr(&self, relay: RelayUrl, node: NodeId) -> Option { let inner = self.0.lock().expect("poisoned"); inner.by_url.get(&(relay, node)).copied() } - /// Returns the [`RelayUrl`] for the given [`IpMappedAddr`]. - pub(crate) fn get_url(&self, mapped_addr: &IpMappedAddr) -> Option<(RelayUrl, NodeId)> { + /// Returns the [`RelayUrl`] and [`NodeId`] for the given [`IpMappedAddr`]. + pub(crate) fn get_url(&self, mapped_addr: &RelayMappedAddr) -> Option<(RelayUrl, NodeId)> { let inner = self.0.lock().expect("poisoned"); inner.by_mapped_addr.get(mapped_addr).cloned() } From f9924cd5c9e106a322122b01d67be2fa69f10d67 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Fri, 29 Aug 2025 15:08:57 +0200 Subject: [PATCH 24/44] switch to main multipath branch --- Cargo.lock | 61 ++++++++++++------------------------------------------ Cargo.toml | 6 +----- 2 files changed, 14 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc60e20fe7a..6c1feec38b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -497,12 +497,6 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" -[[package]] -name = "bytemuck" -version = "1.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" - [[package]] name = "byteorder" version = "1.5.0" @@ -1236,14 +1230,13 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastbloom" -version = "0.9.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27cea6e7f512d43b098939ff4d5a5d6fe3db07971e1d05176fe26c642d33f5b8" +checksum = "29ec576c163744bef8707859f6aeb322bcf56b8da61215d99f77d6e33160ff01" dependencies = [ "getrandom 0.3.2", "rand 0.9.1", "siphasher", - "wide", ] [[package]] @@ -2489,7 +2482,7 @@ dependencies = [ [[package]] name = "iroh-quinn" version = "0.14.0" -source = "git+https://github.com//n0-computer/quinn?branch=server-migrations#bc86957aa4ccb72fad70e75a6ce9fc8198f09afc" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#a68cb468bb44b1ec83a1b6ba110b2e7ecae00d91" dependencies = [ "bytes", "cfg_aliases", @@ -2498,7 +2491,7 @@ dependencies = [ "pin-project-lite", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2 0.6.0", "thiserror 2.0.12", "tokio", "tracing", @@ -2508,7 +2501,7 @@ dependencies = [ [[package]] name = "iroh-quinn-proto" version = "0.13.0" -source = "git+https://github.com//n0-computer/quinn?branch=server-migrations#bc86957aa4ccb72fad70e75a6ce9fc8198f09afc" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#a68cb468bb44b1ec83a1b6ba110b2e7ecae00d91" dependencies = [ "bytes", "fastbloom", @@ -2530,12 +2523,12 @@ dependencies = [ [[package]] name = "iroh-quinn-udp" version = "0.5.12" -source = "git+https://github.com//n0-computer/quinn?branch=server-migrations#bc86957aa4ccb72fad70e75a6ce9fc8198f09afc" +source = "git+https://github.com/n0-computer/quinn?branch=multipath-quinn-0.11.x#a68cb468bb44b1ec83a1b6ba110b2e7ecae00d91" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.0", "tracing", "windows-sys 0.59.0", ] @@ -4110,8 +4103,8 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.25" -source = "git+https://github.com/n0-computer/rustls?rev=be02113e7837df60953d02c2bdd0f4634fef3a80#be02113e7837df60953d02c2bdd0f4634fef3a80" +version = "0.23.27" +source = "git+https://github.com/n0-computer/rustls?rev=c636f89ae00aee19ddd5e6df4150cec5c031fa31#c636f89ae00aee19ddd5e6df4150cec5c031fa31" dependencies = [ "log", "once_cell", @@ -4190,9 +4183,9 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4937d110d34408e9e5ad30ba0b0ca3b6a8a390f8db3636db60144ac4fa792750" +checksum = "be59af91596cac372a6942530653ad0c3a246cdd491aaa9dcaee47f88d67d5a0" dependencies = [ "core-foundation 0.10.0", "core-foundation-sys", @@ -4205,7 +4198,7 @@ dependencies = [ "rustls-webpki", "security-framework", "security-framework-sys", - "webpki-root-certs 0.26.11", + "webpki-root-certs", "windows-sys 0.59.0", ] @@ -4250,15 +4243,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" -[[package]] -name = "safe_arch" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" -dependencies = [ - "bytemuck", -] - [[package]] name = "salsa20" version = "0.10.2" @@ -5630,15 +5614,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-root-certs" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" -dependencies = [ - "webpki-root-certs 1.0.0", -] - [[package]] name = "webpki-root-certs" version = "1.0.0" @@ -5666,16 +5641,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "wide" -version = "0.7.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" -dependencies = [ - "bytemuck", - "safe_arch", -] - [[package]] name = "widestring" version = "1.2.0" @@ -5704,7 +5669,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8e7b8f11f49..5d06c0f4eaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,14 +43,10 @@ unused-async = "warn" [patch.crates-io] -rustls = { git = "https://github.com/n0-computer/rustls", rev = "be02113e7837df60953d02c2bdd0f4634fef3a80" } +rustls = { git = "https://github.com/n0-computer/rustls", rev = "c636f89ae00aee19ddd5e6df4150cec5c031fa31" } netwatch = { git = "https://github.com/n0-computer/net-tools", branch = "feat-multipath" } [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" } - -iroh-quinn = { git = "https://github.com//n0-computer/quinn", branch = "server-migrations" } -iroh-quinn-proto = { git = "https://github.com//n0-computer/quinn", branch = "server-migrations" } -iroh-quinn-udp = { git = "https://github.com//n0-computer/quinn", branch = "server-migrations" } From 3058a8e2b1a28407eb170244f7c9eaf14c9a7ce7 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Fri, 29 Aug 2025 15:09:39 +0200 Subject: [PATCH 25/44] another rename --- iroh/src/endpoint.rs | 9 +++++---- iroh/src/magicsock.rs | 2 +- iroh/src/magicsock/node_map.rs | 17 ++++++++++------- iroh/src/magicsock/node_map/node_state.rs | 2 +- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index a6ba61c4bce..708510a5e34 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -728,10 +728,11 @@ impl Endpoint { } let node_id = node_addr.node_id; - // Get the mapped IPv6 address from the magic socket. Quinn will connect to this - // address. Start discovery for this node if it's enabled and we have no valid or - // verified address information for this node. Dropping the discovery cancels any - // still running task. + // When we start a connection we want to send the QUIC Initial packets on all the + // known paths for the remote node. For this we use an AllPathsMappedAddr as + // destination for Quinn. Start discovery for this node if it's enabled and we have + // no valid or verified address information for this node. Dropping the discovery + // cancels any still running task. let (mapped_addr, _discovery_drop_guard) = self .get_mapping_addr_and_maybe_start_discovery(node_addr) .await diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index c5c57055c0c..c453527df15 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -431,7 +431,7 @@ impl MagicSock { /// Returns the socket address which can be used by the QUIC layer to dial this node. pub(crate) fn get_mapping_addr(&self, node_id: NodeId) -> Option { - self.node_map.get_quic_mapped_addr_for_node_key(node_id) + self.node_map.get_all_paths_add_for_node(node_id) } pub(crate) fn get_direct_addrs(&self, node_id: NodeId) -> Vec { diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index 36d4b9cdfba..69d313b9717 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -166,7 +166,7 @@ impl NodeMap { .receive_relay(relay_url, src) } - pub(super) fn get_quic_mapped_addr_for_node_key( + pub(super) fn get_all_paths_add_for_node( &self, node_key: NodeId, ) -> Option { @@ -174,7 +174,7 @@ impl NodeMap { .lock() .expect("poisoned") .get(NodeStateKey::NodeId(node_key)) - .map(|ep| *ep.quic_mapped_addr()) + .map(|ep| *ep.all_paths_mapped_addr()) } pub(super) fn get_direct_addrs(&self, node_key: NodeId) -> Vec { @@ -359,7 +359,7 @@ impl NodeMapInner { node.remove_direct_addr(&ipp, now, why); if node.direct_addresses().count() == 0 { let node_id = node.public_key(); - let mapped_addr = node.quic_mapped_addr(); + let mapped_addr = node.all_paths_mapped_addr(); self.by_node_key.remove(node_id); self.by_quic_mapped_addr.remove(mapped_addr); debug!(node_id=%node_id.fmt_short(), why, "removing node"); @@ -411,7 +411,10 @@ impl NodeMapInner { return None; }; node_state.receive_udp(ip_port, Instant::now()); - Some((*node_state.public_key(), *node_state.quic_mapped_addr())) + Some(( + *node_state.public_key(), + *node_state.all_paths_mapped_addr(), + )) } #[instrument(skip_all, fields(src = %src.fmt_short()))] @@ -430,7 +433,7 @@ impl NodeMapInner { } }); node_state.receive_relay(relay_url, src, Instant::now()); - *node_state.quic_mapped_addr() + *node_state.all_paths_mapped_addr() } fn node_states(&self) -> impl Iterator { @@ -501,7 +504,7 @@ impl NodeMapInner { // update indices self.by_quic_mapped_addr - .insert(*node_state.quic_mapped_addr(), id); + .insert(*node_state.all_paths_mapped_addr(), id); self.by_node_key.insert(*node_state.public_key(), id); self.by_id.insert(id, node_state); @@ -572,7 +575,7 @@ impl NodeMapInner { self.by_ip_port.remove(&ip_port); } - self.by_quic_mapped_addr.remove(ep.quic_mapped_addr()); + self.by_quic_mapped_addr.remove(ep.all_paths_mapped_addr()); } } } diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index 3f211ed359a..ec28bc5969b 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -148,7 +148,7 @@ impl NodeState { &self.node_id } - pub(super) fn quic_mapped_addr(&self) -> &AllPathsMappedAddr { + pub(super) fn all_paths_mapped_addr(&self) -> &AllPathsMappedAddr { &self.quic_mapped_addr } From 11dd04d84cc9aaf0437c3bd5fdb55d21727ea857 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Fri, 29 Aug 2025 18:05:46 +0200 Subject: [PATCH 26/44] Set max_idle_time to a good value This is what we used to do --- iroh/src/endpoint.rs | 1 - iroh/src/magicsock.rs | 15 +++++++++++---- iroh/src/magicsock/node_map.rs | 4 ++-- iroh/src/magicsock/node_map/node_state.rs | 6 +++--- iroh/src/magicsock/node_map/path_state.rs | 5 +---- iroh/src/magicsock/node_map/udp_paths.rs | 3 +-- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 708510a5e34..c3a73b74ab7 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -627,7 +627,6 @@ impl Endpoint { trace!("created magicsock"); debug!(version = env!("CARGO_PKG_VERSION"), "iroh Endpoint created"); - let metrics = msock.metrics.magicsock.clone(); let ep = Self { msock, static_config: Arc::new(static_config), diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index c453527df15..165bf65d949 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -94,8 +94,18 @@ pub use self::{ /// expire at 30 seconds, so this is a few seconds shy of that. const ENDPOINTS_FRESH_ENOUGH_DURATION: Duration = Duration::from_secs(27); +/// The duration in which we send keep-alives. +/// +/// If a path is idle for this long, a PING frame will be sent to keep the connection +/// alive. const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); +/// The maximum time a path can stay idle before being closed. +/// +/// This is [`HEARTBEAT_INTERVAL`] + 1.5s. This gives us a chance to send a PING frame and +/// some retries. +const MAX_IDLE_TIMEOUT: Duration = Duration::from_millis(6500); + /// Contains options for `MagicSock::listen`. #[derive(derive_more::Debug)] pub(crate) struct Options { @@ -491,10 +501,7 @@ impl MagicSock { .await { Ok(path) => { - path.set_max_idle_timeout(Some( - ENDPOINTS_FRESH_ENOUGH_DURATION, - )) - .ok(); + path.set_max_idle_timeout(Some(MAX_IDLE_TIMEOUT)).ok(); path.set_keep_alive_interval(Some(HEARTBEAT_INTERVAL)).ok(); } Err(err) => { diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index 69d313b9717..e05d96029ff 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -11,8 +11,8 @@ use serde::{Deserialize, Serialize}; use tracing::{debug, info, instrument, trace, warn}; use self::node_state::{NodeState, Options}; -use super::{ActorMessage, AllPathsMappedAddr, metrics::Metrics, transports}; -use crate::disco::{CallMeMaybe, Pong, SendAddr}; +use super::{AllPathsMappedAddr, metrics::Metrics}; +use crate::disco::CallMeMaybe; #[cfg(any(test, feature = "test-utils"))] use crate::endpoint::PathSelection; diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index ec28bc5969b..b86d2f8802b 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -20,7 +20,7 @@ use crate::endpoint::PathSelection; use crate::{ disco::{self, SendAddr}, magicsock::{ - ActorMessage, AllPathsMappedAddr, HEARTBEAT_INTERVAL, MagicsockMetrics, + AllPathsMappedAddr, HEARTBEAT_INTERVAL, MagicsockMetrics, node_map::path_validity::PathValidity, }, }; @@ -921,12 +921,12 @@ pub enum ConnectionType { #[cfg(test)] mod tests { - use std::{collections::BTreeMap, net::Ipv4Addr}; + use std::net::Ipv4Addr; use iroh_base::SecretKey; use super::*; - use crate::magicsock::node_map::{NodeMap, NodeMapInner}; + // use crate::magicsock::node_map::{NodeMap, NodeMapInner}; // #[test] // fn test_remote_infos() { diff --git a/iroh/src/magicsock/node_map/path_state.rs b/iroh/src/magicsock/node_map/path_state.rs index ce3121539b0..7fd4fc7fe78 100644 --- a/iroh/src/magicsock/node_map/path_state.rs +++ b/iroh/src/magicsock/node_map/path_state.rs @@ -11,10 +11,7 @@ use super::{ }; use crate::{ disco::SendAddr, - magicsock::{ - HEARTBEAT_INTERVAL, - node_map::path_validity::{self, PathValidity}, - }, + magicsock::node_map::path_validity::{self, PathValidity}, }; /// State about a particular path to another [`NodeState`]. diff --git a/iroh/src/magicsock/node_map/udp_paths.rs b/iroh/src/magicsock/node_map/udp_paths.rs index d89cf10ed2c..2c72a95c842 100644 --- a/iroh/src/magicsock/node_map/udp_paths.rs +++ b/iroh/src/magicsock/node_map/udp_paths.rs @@ -7,8 +7,7 @@ //! [`NodeState`]: super::node_state::NodeState use std::{collections::BTreeMap, net::SocketAddr}; -use n0_future::time::{Duration, Instant}; -use rand::seq::IteratorRandom; +use n0_future::time::Instant; use tracing::{Level, event}; use super::{IpPort, path_state::PathState}; From 6869faaf04204e60864847d037367d939a540ae4 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Mon, 1 Sep 2025 14:49:17 +0200 Subject: [PATCH 27/44] fix typo --- iroh/src/magicsock.rs | 4 ++-- iroh/src/magicsock/node_map.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 165bf65d949..fe6efdf12d8 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -439,9 +439,9 @@ impl MagicSock { self.node_map.conn_type(node_id) } - /// Returns the socket address which can be used by the QUIC layer to dial this node. + /// Returns the socket address which can be used by the QUIC layer to *dial* this node. pub(crate) fn get_mapping_addr(&self, node_id: NodeId) -> Option { - self.node_map.get_all_paths_add_for_node(node_id) + self.node_map.get_all_paths_addr_for_node(node_id) } pub(crate) fn get_direct_addrs(&self, node_id: NodeId) -> Vec { diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index e05d96029ff..96acd7ea146 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -166,7 +166,7 @@ impl NodeMap { .receive_relay(relay_url, src) } - pub(super) fn get_all_paths_add_for_node( + pub(super) fn get_all_paths_addr_for_node( &self, node_key: NodeId, ) -> Option { From 539a514e83946a370f3bc4a8f6d9069ec752277b Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Tue, 16 Sep 2025 11:36:24 +0200 Subject: [PATCH 28/44] Start hooking up a new NodeStateActor This actor is responsible for sending Initial packets via the AllPathsMappedAddr (NodeIdMappedAddr). Split up Transports into MagicTransports (which replaces the UdpSender). This makes it possible to have Transports not depend on MagicSock. Which in turns allows us to have a TransportsSender in the NodeMap. --- iroh/src/magicsock.rs | 303 ++++++---------- iroh/src/magicsock/node_map.rs | 217 ++++++++++-- iroh/src/magicsock/node_map/node_state.rs | 109 +++++- iroh/src/magicsock/relay_mapped_addrs.rs | 1 + iroh/src/magicsock/transports.rs | 413 +++++++++++++++++----- 5 files changed, 730 insertions(+), 313 deletions(-) diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index fe6efdf12d8..8f8a75ff019 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -20,12 +20,10 @@ use std::{ fmt::Display, io, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, - pin::Pin, sync::{ Arc, Mutex, RwLock, atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}, }, - task::{Context, Poll}, }; use bytes::Bytes; @@ -42,17 +40,16 @@ use nested_enum_utils::common_fields; use netwatch::netmon; #[cfg(not(wasm_browser))] use netwatch::{UdpSocket, ip::LocalAddresses}; -use quinn::{AsyncUdpSocket, ServerConfig, WeakConnectionHandle}; +use quinn::{ServerConfig, WeakConnectionHandle}; use rand::Rng; use relay_mapped_addrs::{RelayAddrMap, RelayMappedAddr}; -use smallvec::SmallVec; use snafu::{ResultExt, Snafu}; use tokio::sync::{Mutex as AsyncMutex, mpsc}; use tokio_util::sync::CancellationToken; use tracing::{ - Instrument, Level, debug, error, event, info, info_span, instrument, trace, trace_span, warn, + Instrument, Level, debug, event, info, info_span, instrument, trace, trace_span, warn, }; -use transports::LocalAddrsWatch; +use transports::{LocalAddrsWatch, MagicTransport}; use url::Url; #[cfg(not(wasm_browser))] @@ -60,7 +57,7 @@ use self::transports::IpTransport; use self::{ metrics::Metrics as MagicsockMetrics, node_map::{NodeMap, PingAction}, - transports::{RelayActorConfig, RelayTransport, Transports, UdpSender}, + transports::{RelayActorConfig, RelayTransport, Transports, TransportsSender}, }; #[cfg(not(wasm_browser))] use crate::dns::DnsResolver; @@ -78,9 +75,9 @@ use crate::{ }; mod metrics; -mod node_map; mod relay_mapped_addrs; +pub(crate) mod node_map; pub(crate) mod transports; pub use node_map::Source; @@ -628,106 +625,6 @@ impl MagicSock { } } - /// Returns the transport addresses for the [`quinn_udp::Transmit`]'s destination. - /// - /// Because Quinn does only know about IP transports we map other transports to private - /// IPv6 Unique Local Address ranges. This extracts the transport addresses out of the - /// transmit's destination. - #[instrument(skip_all)] - fn prepare_send( - &self, - transmit: &quinn_udp::Transmit, - ) -> io::Result> { - self.metrics - .magicsock - .send_data - .inc_by(transmit.contents.len() as _); - - if self.is_closed() { - self.metrics - .magicsock - .send_data_network_down - .inc_by(transmit.contents.len() as _); - return Err(io::Error::new( - io::ErrorKind::NotConnected, - "connection closed", - )); - } - - let mut active_paths = SmallVec::<[_; 3]>::new(); - - match MultipathMappedAddr::from(transmit.destination) { - MultipathMappedAddr::Mixed(dest) => { - trace!( - dst = %dest, - src = ?transmit.src_ip, - len = %transmit.contents.len(), - "sending mixed", - ); - - // Get the node's relay address and best direct address, as well - // as any pings that need to be sent for hole-punching purposes. - match self.node_map.get_send_addrs( - dest, - self.ipv6_reported.load(Ordering::Relaxed), - &self.metrics.magicsock, - ) { - Some((node_id, _udp_addr, _relay_url, ping_actions)) => { - if !ping_actions.is_empty() { - self.actor_sender - .try_send(ActorMessage::PingActions(ping_actions)) - .ok(); - } - - if let Some(addr) = self.node_map.get_current_addr(node_id) { - // Mixed will send all available addrs - if let Some(ref url) = addr.relay_url { - active_paths.push(transports::Addr::Relay(url.clone(), node_id)); - } - for ip in addr.direct_addresses() { - active_paths.push(transports::Addr::Ip(*ip)); - } - } - } - None => { - error!(%dest, "no NodeState for mapped address"); - } - } - } - #[cfg(not(wasm_browser))] - MultipathMappedAddr::Ip(addr) => { - trace!( - dst = %addr, - src = ?transmit.src_ip, - len = %transmit.contents.len(), - "sending IP", - ); - active_paths.push(transports::Addr::Ip(addr)); - } - MultipathMappedAddr::Relay(dest) => { - trace!( - dst = %dest, - src = ?transmit.src_ip, - len = %transmit.contents.len(), - "sending relay", - ); - - // Check if this is a known IpMappedAddr, and if so, send over UDP - // Get the socket addr - match self.relay_mapped_addrs.get_url(&dest) { - Some((relay, node_id)) => { - active_paths.push(transports::Addr::Relay(relay, node_id)); - } - None => { - error!(%dest, "unknown mapped address"); - } - } - } - } - - Ok(active_paths) - } - /// Process datagrams received from all the transports. /// /// All the `bufs` and `metas` should have initialized packets in them. @@ -978,7 +875,7 @@ impl MagicSock { /// Send the given ping actions out. async fn send_ping_actions( &self, - _sender: &UdpSender, + _sender: &TransportsSender, msgs: Vec, ) -> io::Result<()> { for msg in msgs { @@ -1033,7 +930,7 @@ impl MagicSock { /// Sends out a disco message. async fn send_disco_message( &self, - sender: &UdpSender, + sender: &TransportsSender, dst: SendAddr, dst_key: PublicKey, msg: disco::Message, @@ -1104,6 +1001,7 @@ impl MagicSock { #[derive(Clone, Debug)] pub(crate) enum MultipathMappedAddr { /// Used for the initial connection. + /// /// - Only used for sending /// - This means send on all known paths/transports Mixed(AllPathsMappedAddr), @@ -1119,12 +1017,12 @@ impl From for MultipathMappedAddr { match value.ip() { IpAddr::V4(_) => Self::Ip(value), IpAddr::V6(addr) => { - if let Ok(node_id_mapped_addr) = AllPathsMappedAddr::try_from(addr) { - return Self::Mixed(node_id_mapped_addr); + if let Ok(addr) = AllPathsMappedAddr::try_from(addr) { + return Self::Mixed(addr); } #[cfg(not(wasm_browser))] - if let Ok(ip_mapped_addr) = RelayMappedAddr::try_from(addr) { - return Self::Relay(ip_mapped_addr); + if let Ok(addr) = RelayMappedAddr::try_from(addr) { + return Self::Relay(addr); } Self::Ip(value) } @@ -1318,13 +1216,6 @@ impl Handle { let (actor_sender, actor_receiver) = mpsc::channel(256); - // load the node data - let node_map = node_map.unwrap_or_default(); - #[cfg(any(test, feature = "test-utils"))] - let node_map = NodeMap::load_from_vec(node_map, path_selection, &metrics.magicsock); - #[cfg(not(any(test, feature = "test-utils")))] - let node_map = NodeMap::load_from_vec(node_map, &metrics.magicsock); - let my_relay = Watchable::new(None); let ipv6_reported = Arc::new(AtomicBool::new(false)); let max_receive_segments = Arc::new(AtomicUsize::new(1)); @@ -1352,6 +1243,21 @@ impl Handle { #[cfg(wasm_browser)] let transports = Transports::new(relay_transports, max_receive_segments); + let node_map = { + let node_map = node_map.unwrap_or_default(); + let sender = transports.create_sender(); + #[cfg(any(test, feature = "test-utils"))] + let nm = NodeMap::load_from_vec(node_map, path_selection, &metrics.magicsock, sender); + #[cfg(not(any(test, feature = "test-utils")))] + let nm = NodeMap::load_from_vec(node_map, &metrics.magicsock, sender); + nm + }; + // let node_map = node_map.unwrap_or_default(); + // #[cfg(any(test, feature = "test-utils"))] + // let node_map = NodeMap::load_from_vec(node_map, path_selection, &metrics.magicsock); + // #[cfg(not(any(test, feature = "test-utils")))] + // let node_map = NodeMap::load_from_vec(node_map, &metrics.magicsock); + let (disco, disco_receiver) = DiscoState::new(secret_encryption_key); let msock = Arc::new(MagicSock { @@ -1385,17 +1291,14 @@ impl Handle { // the packet if grease_quic_bit is set to false. endpoint_config.grease_quic_bit(false); - let sender = transports.create_sender(msock.clone()); + let sender = transports.create_sender(); let local_addrs_watch = transports.local_addrs_watch(); let network_change_sender = transports.create_network_change_sender(); let endpoint = quinn::Endpoint::new_with_abstract_socket( endpoint_config, Some(server_config), - Box::new(MagicUdpSocket { - socket: msock.clone(), - transports, - }), + Box::new(MagicTransport::new(msock.clone(), transports)), #[cfg(not(wasm_browser))] Arc::new(quinn::TokioRuntime), #[cfg(wasm_browser)] @@ -1645,65 +1548,65 @@ enum DiscoBoxError { }, } -#[derive(Debug)] -struct MagicUdpSocket { - socket: Arc, - transports: Transports, -} - -impl AsyncUdpSocket for MagicUdpSocket { - fn create_sender(&self) -> Pin> { - Box::pin(self.transports.create_sender(self.socket.clone())) - } - - /// NOTE: Receiving on a closed socket will return [`Poll::Pending`] indefinitely. - fn poll_recv( - &mut self, - cx: &mut Context, - bufs: &mut [io::IoSliceMut<'_>], - metas: &mut [quinn_udp::RecvMeta], - ) -> Poll> { - self.transports.poll_recv(cx, bufs, metas, &self.socket) - } - - #[cfg(not(wasm_browser))] - fn local_addr(&self) -> io::Result { - let addrs: Vec<_> = self - .transports - .local_addrs() - .into_iter() - .filter_map(|addr| { - let addr: SocketAddr = addr.into_socket_addr()?; - Some(addr) - }) - .collect(); - - if let Some(addr) = addrs.iter().find(|addr| addr.is_ipv6()) { - return Ok(*addr); - } - if let Some(SocketAddr::V4(addr)) = addrs.first() { - // Pretend to be IPv6, because our `MappedAddr`s need to be IPv6. - let ip = addr.ip().to_ipv6_mapped().into(); - return Ok(SocketAddr::new(ip, addr.port())); - } - - Err(io::Error::other("no valid address available")) - } - - #[cfg(wasm_browser)] - fn local_addr(&self) -> io::Result { - // Again, we need to pretend we're IPv6, because of our `MappedAddr`s. - Ok(SocketAddr::new(std::net::Ipv6Addr::LOCALHOST.into(), 0)) - } - - fn max_receive_segments(&self) -> usize { - self.transports.max_receive_segments() - } - - fn may_fragment(&self) -> bool { - self.transports.may_fragment() - } -} +// #[derive(Debug)] +// struct MagicUdpSocket { +// socket: Arc, +// transports: Transports, +// } + +// impl AsyncUdpSocket for MagicUdpSocket { +// fn create_sender(&self) -> Pin> { +// Box::pin(self.transports.create_sender(self.socket.clone())) +// } + +// /// NOTE: Receiving on a closed socket will return [`Poll::Pending`] indefinitely. +// fn poll_recv( +// &mut self, +// cx: &mut Context, +// bufs: &mut [io::IoSliceMut<'_>], +// metas: &mut [quinn_udp::RecvMeta], +// ) -> Poll> { +// self.transports.poll_recv(cx, bufs, metas, &self.socket) +// } + +// #[cfg(not(wasm_browser))] +// fn local_addr(&self) -> io::Result { +// let addrs: Vec<_> = self +// .transports +// .local_addrs() +// .into_iter() +// .filter_map(|addr| { +// let addr: SocketAddr = addr.into_socket_addr()?; +// Some(addr) +// }) +// .collect(); + +// if let Some(addr) = addrs.iter().find(|addr| addr.is_ipv6()) { +// return Ok(*addr); +// } +// if let Some(SocketAddr::V4(addr)) = addrs.first() { +// // Pretend to be IPv6, because our `MappedAddr`s need to be IPv6. +// let ip = addr.ip().to_ipv6_mapped().into(); +// return Ok(SocketAddr::new(ip, addr.port())); +// } + +// Err(io::Error::other("no valid address available")) +// } + +// #[cfg(wasm_browser)] +// fn local_addr(&self) -> io::Result { +// // Again, we need to pretend we're IPv6, because of our `MappedAddr`s. +// Ok(SocketAddr::new(std::net::Ipv6Addr::LOCALHOST.into(), 0)) +// } + +// fn max_receive_segments(&self) -> usize { +// self.transports.max_receive_segments() +// } + +// fn may_fragment(&self) -> bool { +// self.transports.may_fragment() +// } +// } #[derive(Debug)] enum ActorMessage { @@ -1788,7 +1691,7 @@ impl Actor { mut self, shutdown_token: CancellationToken, mut watcher: impl Watcher> + Send + Sync, - sender: UdpSender, + sender: TransportsSender, ) { // Initialize addresses #[cfg(not(wasm_browser))] @@ -1991,7 +1894,7 @@ impl Actor { } #[instrument(skip_all)] - async fn handle_ping_actions(&mut self, sender: &UdpSender, msgs: Vec) { + async fn handle_ping_actions(&mut self, sender: &TransportsSender, msgs: Vec) { if let Err(err) = self.msock.send_ping_actions(sender, msgs).await { warn!("Failed to send ping actions: {err:#}"); } @@ -2000,7 +1903,7 @@ impl Actor { /// Processes an incoming actor message. /// /// Returns `true` if it was a shutdown. - async fn handle_actor_message(&mut self, msg: ActorMessage, sender: &UdpSender) { + async fn handle_actor_message(&mut self, msg: ActorMessage, sender: &TransportsSender) { match msg { ActorMessage::NetworkChange => { self.network_monitor.network_change().await.ok(); @@ -2321,19 +2224,19 @@ impl DiscoveredDirectAddrs { /// to this address, and duplicate all packets for this address to send on all paths we know /// for the node. /// -/// It is but a newtype. And in our QUIC-facing socket APIs like [`AsyncUdpSocket`] it -/// comes in as the inner [`Ipv6Addr`], in those interfaces we have to be careful to do -/// the conversion to this type. +/// It is but a newtype around an IPv6 Unique Local Addr. And in our QUIC-facing socket +/// APIs like [`AsyncUdpSocket`] it comes in as the inner [`Ipv6Addr`], in those interfaces +/// we have to be careful to do the conversion to this type. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct AllPathsMappedAddr(Ipv6Addr); -/// Can occur when converting a [`SocketAddr`] to an [`NodeIdMappedAddr`] +/// Can occur when converting a [`SocketAddr`] to an [`AllPathsMappedAddr`] #[derive(Debug, Snafu)] #[snafu(display("Failed to convert"))] -pub struct NodeIdMappedAddrError; +pub struct AllPathsMappedAddrError; -/// Counter to always generate unique addresses for [`NodeIdMappedAddr`]. -static NODE_ID_ADDR_COUNTER: AtomicU64 = AtomicU64::new(1); +/// Counter to always generate unique addresses for [`AllPathsMappedAddr`]. +static ALL_PATHS_ADDR_COUNTER: AtomicU64 = AtomicU64::new(1); impl AllPathsMappedAddr { /// The Prefix/L of our Unique Local Addresses. @@ -2344,7 +2247,7 @@ impl AllPathsMappedAddr { const ADDR_SUBNET: [u8; 2] = [0; 2]; /// The dummy port used for all [`NodeIdMappedAddr`]s. - const NODE_ID_MAPPED_PORT: u16 = 12345; + const MAPPED_PORT: u16 = 12345; /// Generates a globally unique fake UDP address. /// @@ -2355,7 +2258,7 @@ impl AllPathsMappedAddr { addr[1..6].copy_from_slice(&Self::ADDR_GLOBAL_ID); addr[6..8].copy_from_slice(&Self::ADDR_SUBNET); - let counter = NODE_ID_ADDR_COUNTER.fetch_add(1, Ordering::Relaxed); + let counter = ALL_PATHS_ADDR_COUNTER.fetch_add(1, Ordering::Relaxed); addr[8..16].copy_from_slice(&counter.to_be_bytes()); Self(Ipv6Addr::from(addr)) @@ -2369,12 +2272,12 @@ impl AllPathsMappedAddr { /// the node in the [`NodeMap`]. This socket address is only to be used to pass into /// Quinn. pub(crate) fn private_socket_addr(&self) -> SocketAddr { - SocketAddr::new(IpAddr::from(self.0), Self::NODE_ID_MAPPED_PORT) + SocketAddr::new(IpAddr::from(self.0), Self::MAPPED_PORT) } } impl TryFrom for AllPathsMappedAddr { - type Error = NodeIdMappedAddrError; + type Error = AllPathsMappedAddrError; fn try_from(value: Ipv6Addr) -> Result { let octets = value.octets(); @@ -2384,7 +2287,7 @@ impl TryFrom for AllPathsMappedAddr { { return Ok(Self(value)); } - Err(NodeIdMappedAddrError) + Err(AllPathsMappedAddrError) } } diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index 96acd7ea146..c7919859684 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -6,12 +6,22 @@ use std::{ }; use iroh_base::{NodeAddr, NodeId, PublicKey, RelayUrl}; -use n0_future::time::Instant; +use n0_future::{task::AbortOnDropHandle, time::Instant}; +use node_state::NodeStateHandle; use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc; use tracing::{debug, info, instrument, trace, warn}; use self::node_state::{NodeState, Options}; -use super::{AllPathsMappedAddr, metrics::Metrics}; +#[cfg(any(test, feature = "test-utils"))] +use super::transports::TransportsSender; +#[cfg(not(any(test, feature = "test-utils")))] +use super::transports::TransportsSender; +use super::{ + AllPathsMappedAddr, + metrics::Metrics, + transports::{self, OwnedTransmit}, +}; use crate::disco::CallMeMaybe; #[cfg(any(test, feature = "test-utils"))] use crate::endpoint::PathSelection; @@ -21,7 +31,8 @@ mod path_state; mod path_validity; mod udp_paths; -pub(super) use node_state::PingAction; +pub(super) use node_state::{NodeStateMessage, PingAction}; + pub use node_state::{ConnectionType, ControlMsg, DirectAddrInfo, RemoteInfo}; /// Number of nodes that are inactive for which we keep info about. This limit is enforced @@ -45,13 +56,15 @@ const MAX_INACTIVE_NODES: usize = 30; /// These come and go as the node moves around on the internet /// /// An index of nodeInfos by node key, NodeIdMappedAddr, and discovered ip:port endpoints. -#[derive(Debug, Default)] +#[derive(Debug)] pub(super) struct NodeMap { inner: Mutex, } -#[derive(Default, Debug)] +#[derive(Debug)] pub(super) struct NodeMapInner { + /// Handle to an actor that can send over the transports. + transports_handle: TransportsSenderHandle, by_node_key: HashMap, by_ip_port: HashMap, by_quic_mapped_addr: HashMap, @@ -59,6 +72,14 @@ pub(super) struct NodeMapInner { next_id: usize, #[cfg(any(test, feature = "test-utils"))] path_selection: PathSelection, + /// The [`NodeStateActor`] for each remote node. + node_states: HashMap, + /// The [`AllPathsMappedAddr`] for each node. + node_addrs: HashMap, + /// The reverse of mapping of [`Self::node_addrs`]. + node_addrs_lookup: HashMap, + // /// The [`RelayMappedAddr`] for each node. + // relay_addrs: HashMap, } /// Identifier to look up a [`NodeState`] in the [`NodeMap`]. @@ -116,10 +137,19 @@ pub enum Source { } impl NodeMap { + #[cfg(any(test, feature = "test-utils"))] + pub(super) fn new(sender: TransportsSender) -> Self { + Self::from_inner(NodeMapInner::new(sender)) + } + #[cfg(not(any(test, feature = "test-utils")))] /// Create a new [`NodeMap`] from a list of [`NodeAddr`]s. - pub(super) fn load_from_vec(nodes: Vec, metrics: &Metrics) -> Self { - Self::from_inner(NodeMapInner::load_from_vec(nodes, metrics)) + pub(super) fn load_from_vec( + nodes: Vec, + metrics: &Metrics, + sender: TransportsSender, + ) -> Self { + Self::from_inner(NodeMapInner::load_from_vec(nodes, metrics, sender)) } #[cfg(any(test, feature = "test-utils"))] @@ -128,8 +158,14 @@ impl NodeMap { nodes: Vec, path_selection: PathSelection, metrics: &Metrics, + sender: TransportsSender, ) -> Self { - Self::from_inner(NodeMapInner::load_from_vec(nodes, path_selection, metrics)) + Self::from_inner(NodeMapInner::load_from_vec( + nodes, + path_selection, + metrics, + sender, + )) } fn from_inner(inner: NodeMapInner) -> Self { @@ -168,12 +204,12 @@ impl NodeMap { pub(super) fn get_all_paths_addr_for_node( &self, - node_key: NodeId, + node_id: NodeId, ) -> Option { self.inner .lock() .expect("poisoned") - .get(NodeStateKey::NodeId(node_key)) + .get(NodeStateKey::NodeId(node_id)) .map(|ep| *ep.all_paths_mapped_addr()) } @@ -282,29 +318,86 @@ impl NodeMap { .expect("poisoned") .on_direct_addr_discovered(discovered, Instant::now()); } + + /// Returns the sender for the [`NodeStateActor`]. + pub(super) fn get_node_state_actor( + &self, + addr: AllPathsMappedAddr, + // node_id: NodeId, + ) -> Option> { + // self + // .inner + // .lock() + // .expect("poisoned") + // .new + // .entry(node_id) + // .or_insert_with_key(|node_id| { + // let mut actor = NodeStateActor::new(*node_id, transports_sender, metrics); + // actor.start() + // }); + todo!() + } } impl NodeMapInner { + #[cfg(any(test, feature = "test-utils"))] + fn new(sender: TransportsSender) -> Self { + let transports_handle = Self::start_transports_sender(sender); + Self { + transports_handle, + by_node_key: Default::default(), + by_ip_port: Default::default(), + by_quic_mapped_addr: Default::default(), + by_id: Default::default(), + next_id: 0, + path_selection: Default::default(), + node_states: Default::default(), + node_addrs: Default::default(), + node_addrs_lookup: Default::default(), + } + } + + /// Creates a new [`NodeMap`] from a list of [`NodeAddr`]s. #[cfg(not(any(test, feature = "test-utils")))] - /// Create a new [`NodeMap`] from a list of [`NodeAddr`]s. - fn load_from_vec(nodes: Vec, metrics: &Metrics) -> Self { - let mut me = Self::default(); + fn load_from_vec(nodes: Vec, metrics: &Metrics, sender: TransportsSender) -> Self { + let transports_handle = Self::start_transports_sender(sender); + let mut me = Self { + transports_handle, + by_node_key: Default::default(), + by_ip_port: Default::default(), + by_quic_mapped_addr: Default::default(), + by_id: Default::default(), + next_id: 0, + node_states: Default::default(), + node_addrs: Default::default(), + node_addrs_lookup: Default::default(), + }; for node_addr in nodes { me.add_node_addr(node_addr, Source::Saved, metrics); } me } + /// Creates a new [`NodeMap`] from a list of [`NodeAddr`]s. #[cfg(any(test, feature = "test-utils"))] - /// Create a new [`NodeMap`] from a list of [`NodeAddr`]s. fn load_from_vec( nodes: Vec, path_selection: PathSelection, metrics: &Metrics, + sender: TransportsSender, ) -> Self { + let transports_handle = Self::start_transports_sender(sender); let mut me = Self { + transports_handle, + by_node_key: Default::default(), + by_ip_port: Default::default(), + by_quic_mapped_addr: Default::default(), + by_id: Default::default(), + next_id: 0, path_selection, - ..Default::default() + node_states: Default::default(), + node_addrs: Default::default(), + node_addrs_lookup: Default::default(), }; for node_addr in nodes { me.add_node_addr(node_addr, Source::Saved, metrics); @@ -312,6 +405,11 @@ impl NodeMapInner { me } + fn start_transports_sender(sender: TransportsSender) -> TransportsSenderHandle { + let actor = TransportsSenderActor::new(sender); + actor.start() + } + /// Add the contact information for a node. #[instrument(skip_all, fields(node = %node_addr.node_id.fmt_short()))] fn add_node_addr(&mut self, node_addr: NodeAddr, source: Source, metrics: &Metrics) { @@ -617,15 +715,87 @@ impl IpPort { } } +/// An actor that can send datagrams onto iroh transports. +/// +/// The [`NodeStateActor`]s want to be able to send datagrams. Because we can not create +/// [`TransportsSender`]s on demand we must share one for the entire [`NodeMap`], which +/// lives in this actor. +#[derive(Debug)] +struct TransportsSenderActor { + sender: TransportsSender, +} + +impl TransportsSenderActor { + fn new(sender: TransportsSender) -> Self { + Self { sender } + } + + fn start(self) -> TransportsSenderHandle { + // This actor gets an inbox size of exactly 1. This is the same as if they had the + // underlying sender directly: either you can send or not, or you await until you + // can. No need to introduce extra buffering. + let (tx, rx) = mpsc::channel(1); + + // No .instrument() on task, run method has an #[instrument] attribute. + let task = tokio::spawn(async move { + self.run(rx).await; + }); + TransportsSenderHandle { + inbox: tx, + _task: AbortOnDropHandle::new(task), + } + } + + #[instrument(name = "TransportsSenderActor", skip_all)] + async fn run(self, mut inbox: mpsc::Receiver) { + use TransportsSenderMessage::SendDatagram; + + loop { + if let Some(SendDatagram(dst, owned_transmit)) = inbox.recv().await { + let transmit = transports::Transmit { + ecn: owned_transmit.ecn, + contents: owned_transmit.contents.as_ref(), + segment_size: owned_transmit.segment_size, + }; + let len = transmit.contents.len(); + match self.sender.send(&dst, None, &transmit).await { + Ok(()) => { + trace!(?dst, %len, "sent transmit"); + } + Err(err) => { + trace!(?dst, %len, "transmit failed to send: {err:#}"); + } + }; + } else { + break; + } + } + trace!("actor terminating"); + } +} + +#[derive(Debug)] +struct TransportsSenderHandle { + inbox: mpsc::Sender, + _task: AbortOnDropHandle<()>, +} + +#[derive(Debug)] +enum TransportsSenderMessage { + SendDatagram(transports::Addr, OwnedTransmit), +} + #[cfg(test)] mod tests { use std::net::Ipv4Addr; + use std::sync::Arc; use iroh_base::SecretKey; use tracing_test::traced_test; use super::{node_state::MAX_INACTIVE_DIRECT_ADDRESSES, *}; use crate::disco::SendAddr; + use crate::magicsock::transports::Transports; impl NodeMap { #[track_caller] @@ -644,7 +814,8 @@ mod tests { #[tokio::test] #[traced_test] async fn restore_from_vec() { - let node_map = NodeMap::default(); + let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); + let node_map = NodeMap::new(transports.create_sender()); let mut rng = rand::thread_rng(); let node_a = SecretKey::generate(&mut rng).public(); @@ -681,8 +852,12 @@ mod tests { Some(addr) }) .collect(); - let loaded_node_map = - NodeMap::load_from_vec(addrs.clone(), PathSelection::default(), &Default::default()); + let loaded_node_map = NodeMap::load_from_vec( + addrs.clone(), + PathSelection::default(), + &Default::default(), + transports.create_sender(), + ); let mut loaded: Vec = loaded_node_map .list_remote_infos(Instant::now()) @@ -710,7 +885,8 @@ mod tests { #[test] #[traced_test] fn test_prune_direct_addresses() { - let node_map = NodeMap::default(); + let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); + let node_map = NodeMap::new(transports.create_sender()); let public_key = SecretKey::generate(rand::thread_rng()).public(); let id = node_map .inner @@ -783,7 +959,8 @@ mod tests { #[test] fn test_prune_inactive() { - let node_map = NodeMap::default(); + let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); + let node_map = NodeMap::new(transports.create_sender()); // add one active node and more than MAX_INACTIVE_NODES inactive nodes let active_node = SecretKey::generate(rand::thread_rng()).public(); let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 167); diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index b86d2f8802b..8cdb35a311f 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -1,13 +1,18 @@ use std::{ - collections::{BTreeSet, HashMap}, + collections::{BTreeMap, BTreeSet, HashMap}, net::{IpAddr, SocketAddr}, - sync::atomic::AtomicBool, + sync::{Arc, atomic::AtomicBool}, }; use iroh_base::{NodeAddr, NodeId, PublicKey, RelayUrl}; -use n0_future::time::{Duration, Instant}; +use n0_future::{ + task::AbortOnDropHandle, + time::{Duration, Instant}, +}; use n0_watcher::Watchable; +use quinn::WeakConnectionHandle; use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc; use tracing::{Level, debug, event, info, instrument, trace, warn}; use super::{ @@ -22,6 +27,7 @@ use crate::{ magicsock::{ AllPathsMappedAddr, HEARTBEAT_INTERVAL, MagicsockMetrics, node_map::path_validity::PathValidity, + transports::{self, OwnedTransmit, TransportsSender}, }, }; @@ -694,6 +700,103 @@ impl NodeState { } } +/// The state we need to know about a single remote node. +/// +/// This actor manages all connections to the remote node. It will trigger holepunching and +/// select the best path etc. +pub(super) struct NodeStateActor { + /// The node ID of the remote node. + node_id: NodeId, + transports_sender: TransportsSender, + // TODO: Turn this into a WeakConnectionHandle + connections: Vec, + paths: BTreeMap, + metrics: Arc, +} + +impl NodeStateActor { + pub(super) fn new( + node_id: NodeId, + transports_sender: TransportsSender, + metrics: Arc, + ) -> Self { + Self { + node_id, + transports_sender, + connections: Vec::new(), + paths: BTreeMap::new(), + metrics, + } + } + + pub(super) fn start(mut self) -> NodeStateHandle { + let (tx, rx) = mpsc::channel(16); + + // No .instrument() on the task, run method has an #[instrument] attribute. + let task = tokio::spawn(async move { + self.run(rx).await; + }); + NodeStateHandle { + sender: tx, + _task: AbortOnDropHandle::new(task), + } + } + + #[instrument( + name = "NodeStateActor", + skip_all, + fields(node_id = %self.node_id.fmt_short()) + )] + async fn run(&mut self, mut inbox: mpsc::Receiver) { + loop { + if let Some(msg) = inbox.recv().await { + match msg { + NodeStateMessage::SendDatagram(transmit) => todo!(), + NodeStateMessage::AddConnection(handle) => todo!(), + NodeStateMessage::PingReceived => todo!(), + } + } else { + break; + } + } + trace!("actor terminating"); + } +} + +/// Messages to send to the [`NodeStateActor`]. +pub(crate) enum NodeStateMessage { + /// Send a datagram to all known paths. + /// + /// Used to send QUIC Initial packets. If there is no working direct path this will + /// trigger holepunching. + /// + /// This is not acceptable to use on the normal send path, as it is an async send + /// operation with a bunch more copying. So it should only be used for sending QUIC + /// Initial packets. + SendDatagram(OwnedTransmit), + /// Add an active connection to this remote node. + /// + /// The connection will now be managed by this actor. Holepunching will happen when + /// needed, any new paths discovered via holepunching will be added. And closed paths + /// will be removed etc. + AddConnection(WeakConnectionHandle), + // TODO: Add the transaction ID. + PingReceived, +} + +/// A handle to a [`NodeStateActor`]. +/// +/// Dropping this will stop the actor. +#[derive(Debug)] +pub(super) struct NodeStateHandle { + sender: mpsc::Sender, + _task: AbortOnDropHandle<()>, +} + +struct NewPathState { + addr: transports::Addr, +} + impl From for NodeAddr { fn from(info: RemoteInfo) -> Self { let direct_addresses = info diff --git a/iroh/src/magicsock/relay_mapped_addrs.rs b/iroh/src/magicsock/relay_mapped_addrs.rs index 1b21f1377e2..608e154b53c 100644 --- a/iroh/src/magicsock/relay_mapped_addrs.rs +++ b/iroh/src/magicsock/relay_mapped_addrs.rs @@ -93,6 +93,7 @@ impl std::fmt::Display for RelayMappedAddr { pub struct RelayAddrMapError; /// A Map of [`RelayMappedAddr`] to `(RelayUrl, NodeId)`. +// TODO: this could be an RwLock, or even an dashmap #[derive(Debug, Clone, Default)] pub(crate) struct RelayAddrMap(Arc>); diff --git a/iroh/src/magicsock/transports.rs b/iroh/src/magicsock/transports.rs index c4703b303df..887f223fcb0 100644 --- a/iroh/src/magicsock/transports.rs +++ b/iroh/src/magicsock/transports.rs @@ -6,11 +6,12 @@ use std::{ task::{Context, Poll}, }; +use bytes::Bytes; use iroh_base::{NodeId, RelayUrl}; use n0_watcher::Watcher; use relay::{RelayNetworkChangeSender, RelaySender}; -use smallvec::SmallVec; -use tracing::{error, trace, warn}; +use tokio::sync::mpsc; +use tracing::{debug, error, instrument, trace, warn}; #[cfg(not(wasm_browser))] mod ip; @@ -21,8 +22,8 @@ pub(crate) use self::ip::IpTransport; #[cfg(not(wasm_browser))] use self::ip::{IpNetworkChangeSender, IpSender}; pub(crate) use self::relay::{RelayActorConfig, RelayTransport}; -use super::MagicSock; -use crate::net_report::Report; +use super::{MagicSock, node_map::NodeStateMessage}; +use crate::{magicsock::MultipathMappedAddr, net_report::Report}; /// Manages the different underlying data transports that the magicsock /// can support. @@ -228,16 +229,15 @@ impl Transports { false } - pub(crate) fn create_sender(&self, msock: Arc) -> UdpSender { + pub(crate) fn create_sender(&self) -> TransportsSender { #[cfg(not(wasm_browser))] let ip = self.ip.iter().map(|t| t.create_sender()).collect(); let relay = self.relay.iter().map(|t| t.create_sender()).collect(); let max_transmit_segments = self.max_transmit_segments(); - UdpSender { + TransportsSender { #[cfg(not(wasm_browser))] ip, - msock, relay, max_transmit_segments, } @@ -310,6 +310,24 @@ pub(crate) struct Transmit<'a> { pub(crate) segment_size: Option, } +/// An outgoing packet that can be sent across channels. +#[derive(Debug)] +pub(crate) struct OwnedTransmit { + pub(crate) ecn: Option, + pub(crate) contents: Bytes, + pub(crate) segment_size: Option, +} + +impl From<&quinn_udp::Transmit<'_>> for OwnedTransmit { + fn from(source: &quinn_udp::Transmit<'_>) -> Self { + Self { + ecn: source.ecn, + contents: Bytes::copy_from_slice(source.contents), + segment_size: source.segment_size, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum Addr { Ip(SocketAddr), @@ -353,16 +371,16 @@ impl Addr { } } +/// A sender that sends to all our transports. #[derive(Debug)] -pub(crate) struct UdpSender { - msock: Arc, // :( +pub(crate) struct TransportsSender { #[cfg(not(wasm_browser))] ip: Vec, relay: Vec, max_transmit_segments: usize, } -impl UdpSender { +impl TransportsSender { pub(crate) async fn send( &self, destination: &Addr, @@ -498,103 +516,318 @@ impl UdpSender { } } -impl quinn::UdpSender for UdpSender { - fn poll_send( - mut self: Pin<&mut Self>, - transmit: &quinn_udp::Transmit, - cx: &mut Context, - ) -> Poll> { - let active_paths = self.msock.prepare_send(transmit)?; - - if active_paths.is_empty() { - // Returning Ok here means we let QUIC timeout. - // Returning an error would immediately fail a connection. - // The philosophy of quinn-udp is that a UDP connection could - // come back at any time or missing should be transient so chooses to let - // these kind of errors time out. See test_try_send_no_send_addr to try - // this out. - error!("no paths available for node, voiding transmit"); - return Poll::Ready(Ok(())); - } +/// A [`Transports`] that works with [`MultipathMappedAddr`]s and their IPv6 representation. +/// +/// The [`MultipathMappedAddr`]s have an IPv6 representation that Quinn uses. This struct +/// knows about these and maps them back to the transport [`Addr`]s used by the wrapped +/// [`Transports`]. +#[derive(Debug)] +pub(crate) struct MagicTransport { + msock: Arc, + transports: Transports, +} - let mut results = SmallVec::<[_; 3]>::new(); +impl MagicTransport { + pub(crate) fn new(msock: Arc, transports: Transports) -> Self { + Self { msock, transports } + } +} - trace!(?active_paths, "attempting to send"); +impl quinn::AsyncUdpSocket for MagicTransport { + fn create_sender(&self) -> Pin> { + Box::pin(MagicSender { + msock: self.msock.clone(), + sender: self.transports.create_sender(), + }) + } - for destination in active_paths { - let src = transmit.src_ip; - let transmit = Transmit { - ecn: transmit.ecn, - contents: transmit.contents, - segment_size: transmit.segment_size, - }; + fn poll_recv( + &mut self, + cx: &mut Context, + bufs: &mut [IoSliceMut<'_>], + meta: &mut [quinn_udp::RecvMeta], + ) -> Poll> { + self.transports.poll_recv(cx, bufs, meta, &self.msock) + } - let res = self - .as_mut() - .inner_poll_send(cx, &destination, src, &transmit); - match res { - Poll::Ready(Ok(())) => { - trace!(dst = ?destination, "sent transmit"); - } - Poll::Ready(Err(ref err)) => { - warn!(dst = ?destination, "failed to send: {err:#}"); - } - Poll::Pending => {} - } - results.push(res); - } + #[cfg(not(wasm_browser))] + fn local_addr(&self) -> io::Result { + let addrs: Vec<_> = self + .transports + .local_addrs() + .into_iter() + .filter_map(|addr| { + let addr: SocketAddr = addr.into_socket_addr()?; + Some(addr) + }) + .collect(); - if results.iter().all(|p| matches!(p, Poll::Pending)) { - // Handle backpressure. - return Poll::Pending; + if let Some(addr) = addrs.iter().find(|addr| addr.is_ipv6()) { + return Ok(*addr); + } + if let Some(SocketAddr::V4(addr)) = addrs.first() { + // Pretend to be IPv6, because our `MappedAddr`s need to be IPv6. + let ip = addr.ip().to_ipv6_mapped().into(); + return Ok(SocketAddr::new(ip, addr.port())); } - Poll::Ready(Ok(())) + + Err(io::Error::other("no valid address available")) } - fn max_transmit_segments(&self) -> usize { - self.max_transmit_segments - } - - fn try_send(self: Pin<&mut Self>, transmit: &quinn_udp::Transmit) -> io::Result<()> { - let active_paths = self.msock.prepare_send(transmit)?; - if active_paths.is_empty() { - // Returning Ok here means we let QUIC timeout. - // Returning an error would immediately fail a connection. - // The philosophy of quinn-udp is that a UDP connection could - // come back at any time or missing should be transient so chooses to let - // these kind of errors time out. See test_try_send_no_send_addr to try - // this out. - error!("no paths available for node, voiding transmit"); - return Ok(()); - } + #[cfg(wasm_browser)] + fn local_addr(&self) -> io::Result { + // Again, we need to pretend we're IPv6, because of our `MappedAddr`s. + Ok(SocketAddr::new(std::net::Ipv6Addr::LOCALHOST.into(), 0)) + } + + fn max_receive_segments(&self) -> usize { + self.transports.max_receive_segments() + } + + fn may_fragment(&self) -> bool { + self.transports.may_fragment() + } +} - let mut results = SmallVec::<[_; 3]>::new(); +/// A sender for [`MagicTransport`]. +/// +/// This is special in that it handles [`MultipathMappedAddr::Mixed`] by delegating to the +/// [`MagicSock`] which expands it back to one or more [`transport::Addr`]s and sends it +/// using the underlying [`Transports`]. +// TODO: Can I just send the TransportsSender along in the NodeStateMessage::SendDatagram +// message?? That way you don't have to hook up the sender into the NodeMap! +#[derive(Debug)] +#[pin_project::pin_project] +pub(crate) struct MagicSender { + msock: Arc, + #[pin] + sender: TransportsSender, +} - trace!(?active_paths, "attempting to send"); +impl MagicSender { + /// Extracts the right [`transports::Addr`] from the [`quinn_udp::Transmit`]. + /// + /// Because Quinn does only know about IP transports we map other transports to private + /// IPv6 Unique Local Address ranges. This extracts the transport addresses out of the + /// transmit's destination. + fn mapped_addr(&self, transmit: &quinn_udp::Transmit) -> io::Result { + self.msock + .metrics + .magicsock + .send_data + .inc_by(transmit.contents.len() as _); + + if self.msock.is_closed() { + self.msock + .metrics + .magicsock + .send_data_network_down + .inc_by(transmit.contents.len() as _); + return Err(io::Error::new( + io::ErrorKind::NotConnected, + "connection closed", + )); + } - for destination in active_paths { - let src = transmit.src_ip; - let transmit = Transmit { - ecn: transmit.ecn, - contents: transmit.contents, - segment_size: transmit.segment_size, - }; + let addr = MultipathMappedAddr::from(transmit.destination); + trace!( + dst = ?addr, + src = ?transmit.src_ip, + len = %transmit.contents.len(), + "sending", + ); + Ok(addr) + } +} - let res = self.inner_try_send(&destination, src, &transmit); - match res { - Ok(()) => { - trace!(dst = ?destination, "sent transmit"); +impl quinn::UdpSender for MagicSender { + #[instrument( + skip_all, + fields(src = ?quinn_transmit.src_ip, len = quinn_transmit.contents.len(), dst), + )] + fn poll_send( + self: Pin<&mut Self>, + quinn_transmit: &quinn_udp::Transmit, + cx: &mut Context, + ) -> Poll> { + // On errors this methods prefers returning Ok(()) to Quinn. Returning an error + // should only happen if the error is permanent and fatal and it will never be + // possible to send anything again. Doing so kills the Quinn EndpointDriver. Most + // send errors are intermittent errors, returning Ok(()) in those cases will mean + // Quinn eventually considers the packets that had send errors as lost and will try + // and re-send them. + let mapped_addr = self.mapped_addr(quinn_transmit)?; + + let transport_addr = match mapped_addr { + MultipathMappedAddr::Mixed(mapped_addr) => { + // TODO: Would be nicer to log the NodeId of this, but we only get an actor + // sender for it. + tracing::Span::current().record("dst", tracing::field::debug(&mapped_addr)); + + // Note we drop the src_ip set in the Quinn Transmit. This is only the + // Initial packet we are sending, so we do not yet have an src address we + // need to respond from. + if let Some(src_ip) = quinn_transmit.src_ip { + warn!(?src_ip, "oops, flub didn't think this would happen"); } - Err(ref err) => { - warn!(dst = ?destination, "failed to send: {err:#}"); + return match self.msock.node_map.get_node_state_actor(mapped_addr) { + Some(sender) => { + let transmit = OwnedTransmit::from(quinn_transmit); + match sender.try_send(NodeStateMessage::SendDatagram(transmit)) { + Ok(()) => { + trace!("sent transmit",); + Poll::Ready(Ok(())) + } + Err(err) => { + // We do not want to block the next send which might be on a + // different transport. Instead we let Quinn handle this as + // a lost datagram. + // TODO: Revisit this: we might want to do something better. + debug!("NodeStateActor inbox full ({err:#}), dropped transmit"); + Poll::Ready(Ok(())) + } + } + } + None => { + error!("unknown AllPathsMappedAddr, dropped transmit"); + Poll::Ready(Ok(())) + } + }; + } + MultipathMappedAddr::Relay(relay_mapped_addr) => { + match self.msock.relay_mapped_addrs.get_url(&relay_mapped_addr) { + Some((relay_url, node_id)) => Addr::Relay(relay_url, node_id), + None => { + error!("unknown RelayMappedAddr, dropped transmit"); + return Poll::Ready(Ok(())); + } } } - results.push(res); + MultipathMappedAddr::Ip(socket_addr) => Addr::Ip(socket_addr), + }; + tracing::Span::current().record("dst", tracing::field::debug(&transport_addr)); + + let transmit = Transmit { + ecn: quinn_transmit.ecn, + contents: quinn_transmit.contents, + segment_size: quinn_transmit.segment_size, + }; + let this = self.project(); + + match this + .sender + .inner_poll_send(cx, &transport_addr, quinn_transmit.src_ip, &transmit) + { + Poll::Ready(Ok(())) => { + trace!("sent transmit",); + Poll::Ready(Ok(())) + } + Poll::Ready(Err(ref err)) => { + warn!("dropped transmit: {err:#}"); + Poll::Ready(Ok(())) + } + Poll::Pending => { + // We do not want to block the next send which might be on a + // different transport. Instead we let Quinn handle this as a lost + // datagram. + // TODO: Revisit this: we might want to do something better. + trace!("transport pending, dropped transmit"); + Poll::Ready(Ok(())) + } } + } - if results.iter().all(|p| p.is_err()) { - return Err(io::Error::other("all failed")); + fn max_transmit_segments(&self) -> usize { + self.sender.max_transmit_segments + } + + #[instrument( + skip_all, + fields(src = ?quinn_transmit.src_ip, len = quinn_transmit.contents.len(), dst), + )] + fn try_send(self: Pin<&mut Self>, quinn_transmit: &quinn_udp::Transmit) -> io::Result<()> { + // As opposed to poll_send this method does return normal IO errors. Calls to this + // are one-off fire-and-forget calls with no implications for the EndpointDriver. + let mapped_addr = self.mapped_addr(quinn_transmit)?; + + let transport_addr = match mapped_addr { + MultipathMappedAddr::Mixed(mapped_addr) => { + // TODO: Would be nicer to log the NodeId of this, but we only get an actor + // sender for it. + tracing::Span::current().record("dst", tracing::field::debug(&mapped_addr)); + + // Note we drop the src_ip set in the Quinn Transmit. This is only the + // Initial packet we are sending, so we do not yet have an src address we + // need to respond from. + if let Some(src_ip) = quinn_transmit.src_ip { + warn!(?src_ip, "oops, flub didn't think this would happen"); + } + return match self.msock.node_map.get_node_state_actor(mapped_addr) { + Some(sender) => { + let transmit = OwnedTransmit::from(quinn_transmit); + match sender.try_send(NodeStateMessage::SendDatagram(transmit)) { + Ok(()) => { + trace!("sent transmit",); + Ok(()) + } + Err(mpsc::error::TrySendError::Full(_)) => { + debug!("NodeStateActor inbox full, dropped transmit"); + Err(io::Error::new( + io::ErrorKind::WouldBlock, + "NodeStateActor inbox full", + )) + } + Err(mpsc::error::TrySendError::Closed(_)) => { + debug!("NodeStateActor inbox closed, dropped transmit"); + Err(io::Error::new( + io::ErrorKind::NetworkDown, + "NodeStateActor inbox closed", + )) + } + } + } + None => { + error!("unknown AllPathsMappedAddr, dropped transmit"); + Err(io::Error::new( + io::ErrorKind::HostUnreachable, + "unknown AllPathsMappedAddr", + )) + } + }; + } + MultipathMappedAddr::Relay(relay_mapped_addr) => { + match self.msock.relay_mapped_addrs.get_url(&relay_mapped_addr) { + Some((relay_url, node_id)) => Addr::Relay(relay_url, node_id), + None => { + error!("unknown RelayMappedAddr, dropped transmit"); + return Err(io::Error::new( + io::ErrorKind::HostUnreachable, + "unknown RelayMappedAddr", + )); + } + } + } + MultipathMappedAddr::Ip(socket_addr) => Addr::Ip(socket_addr), + }; + tracing::Span::current().record("dst", tracing::field::debug(&transport_addr)); + + let transmit = Transmit { + ecn: quinn_transmit.ecn, + contents: quinn_transmit.contents, + segment_size: quinn_transmit.segment_size, + }; + match self + .sender + .inner_try_send(&transport_addr, quinn_transmit.src_ip, &transmit) + { + Ok(()) => { + trace!("sent transmit",); + Ok(()) + } + Err(err) => { + warn!("transmit failed to send: {err:#}"); + Err(err) + } } - Ok(()) } } From a1a7d8936cfc24d26fb27810fe1ff2b92cd7c6d9 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Tue, 16 Sep 2025 11:50:15 +0200 Subject: [PATCH 29/44] Rename AllPathsMappedAddr to NodeIdMappedAddr Yes, it was called this before. The doc comment explains why this name makes more sense. Also fix up a bunch of doc links --- iroh/src/endpoint.rs | 4 +- iroh/src/magicsock.rs | 49 +++++++++++++---------- iroh/src/magicsock/node_map.rs | 35 ++++++++-------- iroh/src/magicsock/node_map/node_state.rs | 8 ++-- iroh/src/magicsock/relay_mapped_addrs.rs | 16 ++++---- iroh/src/magicsock/transports.rs | 4 +- 6 files changed, 63 insertions(+), 53 deletions(-) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index c3a73b74ab7..ae331f68f92 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -62,7 +62,7 @@ use crate::{ DiscoverySubscribers, DiscoveryTask, DynIntoDiscovery, IntoDiscovery, IntoDiscoveryError, Lagged, UserData, pkarr::PkarrPublisher, }, - magicsock::{self, AllPathsMappedAddr, Handle, OwnAddressSnafu}, + magicsock::{self, Handle, NodeIdMappedAddr, OwnAddressSnafu}, metrics::EndpointMetrics, net_report::Report, tls, @@ -1363,7 +1363,7 @@ impl Endpoint { async fn get_mapping_addr_and_maybe_start_discovery( &self, node_addr: NodeAddr, - ) -> Result<(AllPathsMappedAddr, Option), GetMappingAddressError> { + ) -> Result<(NodeIdMappedAddr, Option), GetMappingAddressError> { let node_id = node_addr.node_id; // Only return a mapped addr if we have some way of dialing this node, in other diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 8f8a75ff019..19f0537aee3 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -437,7 +437,7 @@ impl MagicSock { } /// Returns the socket address which can be used by the QUIC layer to *dial* this node. - pub(crate) fn get_mapping_addr(&self, node_id: NodeId) -> Option { + pub(crate) fn get_mapping_addr(&self, node_id: NodeId) -> Option { self.node_map.get_all_paths_addr_for_node(node_id) } @@ -1004,7 +1004,7 @@ pub(crate) enum MultipathMappedAddr { /// /// - Only used for sending /// - This means send on all known paths/transports - Mixed(AllPathsMappedAddr), + Mixed(NodeIdMappedAddr), /// Relay based transport address Relay(RelayMappedAddr), /// IP based transport address @@ -1017,7 +1017,7 @@ impl From for MultipathMappedAddr { match value.ip() { IpAddr::V4(_) => Self::Ip(value), IpAddr::V6(addr) => { - if let Ok(addr) = AllPathsMappedAddr::try_from(addr) { + if let Ok(addr) = NodeIdMappedAddr::try_from(addr) { return Self::Mixed(addr); } #[cfg(not(wasm_browser))] @@ -1392,9 +1392,11 @@ impl Handle { /// Closes the connection. /// - /// Only the first close does anything. Any later closes return nil. - /// Polling the socket ([`AsyncUdpSocket::poll_recv`]) will return [`Poll::Pending`] - /// indefinitely after this call. + /// Only the first close does anything. Any later closes return nil. Polling the socket + /// ([`quinn::AsyncUdpSocket::poll_recv`]) will return [`Poll::Pending`] indefinitely + /// after this call. + /// + /// [`Poll::Pending`]: std::task::Poll::Pending #[instrument(skip_all)] pub(crate) async fn close(&self) { trace!(me = ?self.public_key, "magicsock closing..."); @@ -2218,27 +2220,32 @@ impl DiscoveredDirectAddrs { } } -/// An address used by the QUIC layer to address a node on all paths. +/// An address used by the QUIC layer to address a node on any or all paths. /// /// This is only used for initially connecting to a remote node. We instruct Quinn to send -/// to this address, and duplicate all packets for this address to send on all paths we know -/// for the node. +/// to this address, and duplicate all packets for this address to send on all paths we +/// might want to send the initial on: +/// +/// - If this the first connection to the remote node we don't know which path will work and +/// send to all of them. +/// +/// - If there already is an active connection to this node we now which path to use. /// /// It is but a newtype around an IPv6 Unique Local Addr. And in our QUIC-facing socket -/// APIs like [`AsyncUdpSocket`] it comes in as the inner [`Ipv6Addr`], in those interfaces -/// we have to be careful to do the conversion to this type. +/// APIs like [`quinn::AsyncUdpSocket`] it comes in as the inner [`Ipv6Addr`], in those +/// interfaces we have to be careful to do the conversion to this type. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub(crate) struct AllPathsMappedAddr(Ipv6Addr); +pub(crate) struct NodeIdMappedAddr(Ipv6Addr); -/// Can occur when converting a [`SocketAddr`] to an [`AllPathsMappedAddr`] +/// Can occur when converting a [`SocketAddr`] to an [`NodeIdMappedAddr`] #[derive(Debug, Snafu)] #[snafu(display("Failed to convert"))] pub struct AllPathsMappedAddrError; -/// Counter to always generate unique addresses for [`AllPathsMappedAddr`]. +/// Counter to always generate unique addresses for [`NodeIdMappedAddr`]. static ALL_PATHS_ADDR_COUNTER: AtomicU64 = AtomicU64::new(1); -impl AllPathsMappedAddr { +impl NodeIdMappedAddr { /// The Prefix/L of our Unique Local Addresses. const ADDR_PREFIXL: u8 = 0xfd; /// The Global ID used in our Unique Local Addresses. @@ -2276,7 +2283,7 @@ impl AllPathsMappedAddr { } } -impl TryFrom for AllPathsMappedAddr { +impl TryFrom for NodeIdMappedAddr { type Error = AllPathsMappedAddrError; fn try_from(value: Ipv6Addr) -> Result { @@ -2291,7 +2298,7 @@ impl TryFrom for AllPathsMappedAddr { } } -impl std::fmt::Display for AllPathsMappedAddr { +impl std::fmt::Display for NodeIdMappedAddr { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "NodeIdMappedAddr({})", self.0) } @@ -2381,7 +2388,7 @@ mod tests { use tracing::{Instrument, error, info, info_span, instrument}; use tracing_test::traced_test; - use super::{AllPathsMappedAddr, Options}; + use super::{NodeIdMappedAddr, Options}; use crate::{ Endpoint, RelayMap, RelayMode, SecretKey, dns::DnsResolver, @@ -2958,7 +2965,7 @@ mod tests { async fn magicsock_connect( ep: &quinn::Endpoint, ep_secret_key: SecretKey, - addr: AllPathsMappedAddr, + addr: NodeIdMappedAddr, node_id: NodeId, ) -> Result { // Endpoint::connect sets this, do the same to have similar behaviour. @@ -2984,7 +2991,7 @@ mod tests { async fn magicsock_connect_with_transport_config( ep: &quinn::Endpoint, ep_secret_key: SecretKey, - mapped_addr: AllPathsMappedAddr, + mapped_addr: NodeIdMappedAddr, node_id: NodeId, transport_config: Arc, ) -> Result { @@ -3019,7 +3026,7 @@ mod tests { let msock_1 = magicsock_ep(secret_key_1.clone()).await.unwrap(); // Generate an address not present in the NodeMap. - let bad_addr = AllPathsMappedAddr::generate(); + let bad_addr = NodeIdMappedAddr::generate(); // 500ms is rather fast here. Running this locally it should always be the correct // timeout. If this is too slow however the test will not become flaky as we are diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index c7919859684..85317e3f5f4 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -18,7 +18,7 @@ use super::transports::TransportsSender; #[cfg(not(any(test, feature = "test-utils")))] use super::transports::TransportsSender; use super::{ - AllPathsMappedAddr, + NodeIdMappedAddr, metrics::Metrics, transports::{self, OwnedTransmit}, }; @@ -67,17 +67,19 @@ pub(super) struct NodeMapInner { transports_handle: TransportsSenderHandle, by_node_key: HashMap, by_ip_port: HashMap, - by_quic_mapped_addr: HashMap, + by_quic_mapped_addr: HashMap, by_id: HashMap, next_id: usize, #[cfg(any(test, feature = "test-utils"))] path_selection: PathSelection, /// The [`NodeStateActor`] for each remote node. + /// + /// [`NodeStateActor`]: node_state::NodeStateActor node_states: HashMap, - /// The [`AllPathsMappedAddr`] for each node. - node_addrs: HashMap, + /// The [`NodeIdMappedAddr`] for each node. + node_addrs: HashMap, /// The reverse of mapping of [`Self::node_addrs`]. - node_addrs_lookup: HashMap, + node_addrs_lookup: HashMap, // /// The [`RelayMappedAddr`] for each node. // relay_addrs: HashMap, } @@ -89,7 +91,7 @@ pub(super) struct NodeMapInner { #[derive(Debug, Clone)] enum NodeStateKey { NodeId(NodeId), - NodeIdMappedAddr(AllPathsMappedAddr), + NodeIdMappedAddr(NodeIdMappedAddr), IpPort(IpPort), } @@ -191,21 +193,18 @@ impl NodeMap { pub(super) fn receive_udp( &self, udp_addr: SocketAddr, - ) -> Option<(PublicKey, AllPathsMappedAddr)> { + ) -> Option<(PublicKey, NodeIdMappedAddr)> { self.inner.lock().expect("poisoned").receive_udp(udp_addr) } - pub(super) fn receive_relay(&self, relay_url: &RelayUrl, src: NodeId) -> AllPathsMappedAddr { + pub(super) fn receive_relay(&self, relay_url: &RelayUrl, src: NodeId) -> NodeIdMappedAddr { self.inner .lock() .expect("poisoned") .receive_relay(relay_url, src) } - pub(super) fn get_all_paths_addr_for_node( - &self, - node_id: NodeId, - ) -> Option { + pub(super) fn get_all_paths_addr_for_node(&self, node_id: NodeId) -> Option { self.inner .lock() .expect("poisoned") @@ -246,7 +245,7 @@ impl NodeMap { #[allow(clippy::type_complexity)] pub(super) fn get_send_addrs( &self, - addr: AllPathsMappedAddr, + addr: NodeIdMappedAddr, have_ipv6: bool, metrics: &Metrics, ) -> Option<( @@ -320,9 +319,11 @@ impl NodeMap { } /// Returns the sender for the [`NodeStateActor`]. + /// + /// [`NodeStateActor`]: node_state::NodeStateActor pub(super) fn get_node_state_actor( &self, - addr: AllPathsMappedAddr, + addr: NodeIdMappedAddr, // node_id: NodeId, ) -> Option> { // self @@ -502,7 +503,7 @@ impl NodeMapInner { /// Marks the node we believe to be at `ipp` as recently used. #[cfg(not(wasm_browser))] - fn receive_udp(&mut self, udp_addr: SocketAddr) -> Option<(NodeId, AllPathsMappedAddr)> { + fn receive_udp(&mut self, udp_addr: SocketAddr) -> Option<(NodeId, NodeIdMappedAddr)> { let ip_port: IpPort = udp_addr.into(); let Some(node_state) = self.get_mut(NodeStateKey::IpPort(ip_port)) else { trace!(src=%udp_addr, "receive_udp: no node_state found for addr, ignore"); @@ -516,7 +517,7 @@ impl NodeMapInner { } #[instrument(skip_all, fields(src = %src.fmt_short()))] - fn receive_relay(&mut self, relay_url: &RelayUrl, src: NodeId) -> AllPathsMappedAddr { + fn receive_relay(&mut self, relay_url: &RelayUrl, src: NodeId) -> NodeIdMappedAddr { #[cfg(any(test, feature = "test-utils"))] let path_selection = self.path_selection; let node_state = self.get_or_insert_with(NodeStateKey::NodeId(src), || { @@ -720,6 +721,8 @@ impl IpPort { /// The [`NodeStateActor`]s want to be able to send datagrams. Because we can not create /// [`TransportsSender`]s on demand we must share one for the entire [`NodeMap`], which /// lives in this actor. +/// +/// [`NodeStateActor`]: node_state::NodeStateActor #[derive(Debug)] struct TransportsSenderActor { sender: TransportsSender, diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index 8cdb35a311f..ead6be40894 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -25,7 +25,7 @@ use crate::endpoint::PathSelection; use crate::{ disco::{self, SendAddr}, magicsock::{ - AllPathsMappedAddr, HEARTBEAT_INTERVAL, MagicsockMetrics, + HEARTBEAT_INTERVAL, MagicsockMetrics, NodeIdMappedAddr, node_map::path_validity::PathValidity, transports::{self, OwnedTransmit, TransportsSender}, }, @@ -69,7 +69,7 @@ pub(super) struct NodeState { /// [`NodeMap`]: super::NodeMap id: usize, /// The UDP address used on the QUIC-layer to address this node. - quic_mapped_addr: AllPathsMappedAddr, + quic_mapped_addr: NodeIdMappedAddr, /// The global identifier for this endpoint. node_id: NodeId, /// The url of relay node that we can relay over to communicate. @@ -119,7 +119,7 @@ pub(super) struct Options { impl NodeState { pub(super) fn new(id: usize, options: Options) -> Self { - let quic_mapped_addr = AllPathsMappedAddr::generate(); + let quic_mapped_addr = NodeIdMappedAddr::generate(); // TODO(frando): I don't think we need to track the `num_relay_conns_added` // metric here. We do so in `Self::addr_for_send`. @@ -154,7 +154,7 @@ impl NodeState { &self.node_id } - pub(super) fn all_paths_mapped_addr(&self) -> &AllPathsMappedAddr { + pub(super) fn all_paths_mapped_addr(&self) -> &NodeIdMappedAddr { &self.quic_mapped_addr } diff --git a/iroh/src/magicsock/relay_mapped_addrs.rs b/iroh/src/magicsock/relay_mapped_addrs.rs index 608e154b53c..62623fefb54 100644 --- a/iroh/src/magicsock/relay_mapped_addrs.rs +++ b/iroh/src/magicsock/relay_mapped_addrs.rs @@ -10,10 +10,10 @@ use std::{ use iroh_base::{NodeId, RelayUrl}; use snafu::Snafu; -/// Can occur when converting a [`SocketAddr`] to an [`IpMappedAddr`] +/// Can occur when converting a [`SocketAddr`] to an [`RelayMappedAddr`] #[derive(Debug, Snafu)] #[snafu(display("Failed to convert"))] -pub struct IpMappedAddrError; +pub struct RelayMappedAddrError; /// An Ipv6 ULA address, identifying a relay path for a [`NodeId`]. /// @@ -24,8 +24,8 @@ pub struct IpMappedAddrError; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] pub(crate) struct RelayMappedAddr(Ipv6Addr); -/// Counter to always generate unique addresses for [`IpMappedAddr`]. -static IP_ADDR_COUNTER: AtomicU64 = AtomicU64::new(1); +/// Counter to always generate unique addresses for [`RelayMappedAddr`]. +static RELAY_ADDR_COUNTER: AtomicU64 = AtomicU64::new(1); impl RelayMappedAddr { /// The Prefix/L of our Unique Local Addresses. @@ -48,7 +48,7 @@ impl RelayMappedAddr { addr[1..6].copy_from_slice(&Self::ADDR_GLOBAL_ID); addr[6..8].copy_from_slice(&Self::ADDR_SUBNET); - let counter = IP_ADDR_COUNTER.fetch_add(1, Ordering::Relaxed); + let counter = RELAY_ADDR_COUNTER.fetch_add(1, Ordering::Relaxed); addr[8..16].copy_from_slice(&counter.to_be_bytes()); Self(Ipv6Addr::from(addr)) @@ -67,7 +67,7 @@ impl RelayMappedAddr { } impl TryFrom for RelayMappedAddr { - type Error = IpMappedAddrError; + type Error = RelayMappedAddrError; fn try_from(value: Ipv6Addr) -> std::result::Result { let octets = value.octets(); @@ -77,7 +77,7 @@ impl TryFrom for RelayMappedAddr { { return Ok(Self(value)); } - Err(IpMappedAddrError) + Err(RelayMappedAddrError) } } @@ -129,7 +129,7 @@ impl RelayAddrMap { inner.by_url.get(&(relay, node)).copied() } - /// Returns the [`RelayUrl`] and [`NodeId`] for the given [`IpMappedAddr`]. + /// Returns the [`RelayUrl`] and [`NodeId`] for the given [`RelayMappedAddr`]. pub(crate) fn get_url(&self, mapped_addr: &RelayMappedAddr) -> Option<(RelayUrl, NodeId)> { let inner = self.0.lock().expect("poisoned"); inner.by_mapped_addr.get(mapped_addr).cloned() diff --git a/iroh/src/magicsock/transports.rs b/iroh/src/magicsock/transports.rs index 887f223fcb0..2d684068691 100644 --- a/iroh/src/magicsock/transports.rs +++ b/iroh/src/magicsock/transports.rs @@ -592,7 +592,7 @@ impl quinn::AsyncUdpSocket for MagicTransport { /// A sender for [`MagicTransport`]. /// /// This is special in that it handles [`MultipathMappedAddr::Mixed`] by delegating to the -/// [`MagicSock`] which expands it back to one or more [`transport::Addr`]s and sends it +/// [`MagicSock`] which expands it back to one or more [`Addr`]s and sends it /// using the underlying [`Transports`]. // TODO: Can I just send the TransportsSender along in the NodeStateMessage::SendDatagram // message?? That way you don't have to hook up the sender into the NodeMap! @@ -605,7 +605,7 @@ pub(crate) struct MagicSender { } impl MagicSender { - /// Extracts the right [`transports::Addr`] from the [`quinn_udp::Transmit`]. + /// Extracts the right [`Addr`] from the [`quinn_udp::Transmit`]. /// /// Because Quinn does only know about IP transports we map other transports to private /// IPv6 Unique Local Address ranges. This extracts the transport addresses out of the From 4b00ad729eac6ab877ffd1ee031cfe9a7c5d9a56 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Tue, 16 Sep 2025 12:41:04 +0200 Subject: [PATCH 30/44] Move all mapped addrs to one module Should be a little tidier, probably, hopefully. --- iroh/src/endpoint.rs | 2 +- iroh/src/magicsock.rs | 135 +---------- iroh/src/magicsock/mapped_addrs.rs | 259 ++++++++++++++++++++++ iroh/src/magicsock/node_map.rs | 2 +- iroh/src/magicsock/node_map/node_state.rs | 3 +- iroh/src/magicsock/relay_mapped_addrs.rs | 137 ------------ iroh/src/magicsock/transports.rs | 7 +- 7 files changed, 276 insertions(+), 269 deletions(-) create mode 100644 iroh/src/magicsock/mapped_addrs.rs delete mode 100644 iroh/src/magicsock/relay_mapped_addrs.rs diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index ae331f68f92..741fdbab89b 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -62,7 +62,7 @@ use crate::{ DiscoverySubscribers, DiscoveryTask, DynIntoDiscovery, IntoDiscovery, IntoDiscoveryError, Lagged, UserData, pkarr::PkarrPublisher, }, - magicsock::{self, Handle, NodeIdMappedAddr, OwnAddressSnafu}, + magicsock::{self, Handle, OwnAddressSnafu, mapped_addrs::NodeIdMappedAddr}, metrics::EndpointMetrics, net_report::Report, tls, diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 19f0537aee3..8fb637322e0 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -22,7 +22,7 @@ use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, sync::{ Arc, Mutex, RwLock, - atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}, + atomic::{AtomicBool, AtomicUsize, Ordering}, }, }; @@ -42,7 +42,6 @@ use netwatch::netmon; use netwatch::{UdpSocket, ip::LocalAddresses}; use quinn::{ServerConfig, WeakConnectionHandle}; use rand::Rng; -use relay_mapped_addrs::{RelayAddrMap, RelayMappedAddr}; use snafu::{ResultExt, Snafu}; use tokio::sync::{Mutex as AsyncMutex, mpsc}; use tokio_util::sync::CancellationToken; @@ -75,17 +74,16 @@ use crate::{ }; mod metrics; -mod relay_mapped_addrs; +pub(crate) mod mapped_addrs; pub(crate) mod node_map; pub(crate) mod transports; -pub use node_map::Source; +use mapped_addrs::{NodeIdMappedAddr, RelayAddrMap}; -pub use self::{ - metrics::Metrics, - node_map::{ConnectionType, ControlMsg, DirectAddrInfo, RemoteInfo}, -}; +pub use metrics::Metrics; +pub use node_map::Source; +pub use node_map::{ConnectionType, ControlMsg, DirectAddrInfo, RemoteInfo}; /// How long we consider a QAD-derived endpoint valid for. UDP NAT mappings typically /// expire at 30 seconds, so this is a few seconds shy of that. @@ -631,6 +629,8 @@ impl MagicSock { /// /// This fixes up the datagrams to use the correct [`MultipathMappedAddr`] and extracts /// DISCO packets, processing them inside the magic socket. + /// + /// [`MultipathMappedAddr`]: mapped_addrs::MultipathMappedAddr fn process_datagrams( &self, bufs: &mut [io::IoSliceMut<'_>], @@ -995,41 +995,6 @@ impl MagicSock { } } -/// Definies the translation of addresses in quinn land vs iroh land. -/// -/// This is necessary, because quinn can only reason about `SocketAddr`s. -#[derive(Clone, Debug)] -pub(crate) enum MultipathMappedAddr { - /// Used for the initial connection. - /// - /// - Only used for sending - /// - This means send on all known paths/transports - Mixed(NodeIdMappedAddr), - /// Relay based transport address - Relay(RelayMappedAddr), - /// IP based transport address - #[cfg(not(wasm_browser))] - Ip(SocketAddr), -} - -impl From for MultipathMappedAddr { - fn from(value: SocketAddr) -> Self { - match value.ip() { - IpAddr::V4(_) => Self::Ip(value), - IpAddr::V6(addr) => { - if let Ok(addr) = NodeIdMappedAddr::try_from(addr) { - return Self::Mixed(addr); - } - #[cfg(not(wasm_browser))] - if let Ok(addr) = RelayMappedAddr::try_from(addr) { - return Self::Relay(addr); - } - Self::Ip(value) - } - } - } -} - /// Manages currently running direct addr discovery, aka net_report runs. /// /// Invariants: @@ -2220,90 +2185,6 @@ impl DiscoveredDirectAddrs { } } -/// An address used by the QUIC layer to address a node on any or all paths. -/// -/// This is only used for initially connecting to a remote node. We instruct Quinn to send -/// to this address, and duplicate all packets for this address to send on all paths we -/// might want to send the initial on: -/// -/// - If this the first connection to the remote node we don't know which path will work and -/// send to all of them. -/// -/// - If there already is an active connection to this node we now which path to use. -/// -/// It is but a newtype around an IPv6 Unique Local Addr. And in our QUIC-facing socket -/// APIs like [`quinn::AsyncUdpSocket`] it comes in as the inner [`Ipv6Addr`], in those -/// interfaces we have to be careful to do the conversion to this type. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub(crate) struct NodeIdMappedAddr(Ipv6Addr); - -/// Can occur when converting a [`SocketAddr`] to an [`NodeIdMappedAddr`] -#[derive(Debug, Snafu)] -#[snafu(display("Failed to convert"))] -pub struct AllPathsMappedAddrError; - -/// Counter to always generate unique addresses for [`NodeIdMappedAddr`]. -static ALL_PATHS_ADDR_COUNTER: AtomicU64 = AtomicU64::new(1); - -impl NodeIdMappedAddr { - /// The Prefix/L of our Unique Local Addresses. - const ADDR_PREFIXL: u8 = 0xfd; - /// The Global ID used in our Unique Local Addresses. - const ADDR_GLOBAL_ID: [u8; 5] = [21, 7, 10, 81, 11]; - /// The Subnet ID used in our Unique Local Addresses. - const ADDR_SUBNET: [u8; 2] = [0; 2]; - - /// The dummy port used for all [`NodeIdMappedAddr`]s. - const MAPPED_PORT: u16 = 12345; - - /// Generates a globally unique fake UDP address. - /// - /// This generates and IPv6 Unique Local Address according to RFC 4193. - pub(crate) fn generate() -> Self { - let mut addr = [0u8; 16]; - addr[0] = Self::ADDR_PREFIXL; - addr[1..6].copy_from_slice(&Self::ADDR_GLOBAL_ID); - addr[6..8].copy_from_slice(&Self::ADDR_SUBNET); - - let counter = ALL_PATHS_ADDR_COUNTER.fetch_add(1, Ordering::Relaxed); - addr[8..16].copy_from_slice(&counter.to_be_bytes()); - - Self(Ipv6Addr::from(addr)) - } - - /// Returns a consistent [`SocketAddr`] for the [`NodeIdMappedAddr`]. - /// - /// This socket address does not have a routable IP address. - /// - /// This uses a made-up port number, since the port does not play a role in looking up - /// the node in the [`NodeMap`]. This socket address is only to be used to pass into - /// Quinn. - pub(crate) fn private_socket_addr(&self) -> SocketAddr { - SocketAddr::new(IpAddr::from(self.0), Self::MAPPED_PORT) - } -} - -impl TryFrom for NodeIdMappedAddr { - type Error = AllPathsMappedAddrError; - - fn try_from(value: Ipv6Addr) -> Result { - let octets = value.octets(); - if octets[0] == Self::ADDR_PREFIXL - && octets[1..6] == Self::ADDR_GLOBAL_ID - && octets[6..8] == Self::ADDR_SUBNET - { - return Ok(Self(value)); - } - Err(AllPathsMappedAddrError) - } -} - -impl std::fmt::Display for NodeIdMappedAddr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "NodeIdMappedAddr({})", self.0) - } -} - fn disco_message_sent(msg: &disco::Message, metrics: &MagicsockMetrics) { match msg { disco::Message::Ping(_) => { diff --git a/iroh/src/magicsock/mapped_addrs.rs b/iroh/src/magicsock/mapped_addrs.rs new file mode 100644 index 00000000000..3c1eb3741a5 --- /dev/null +++ b/iroh/src/magicsock/mapped_addrs.rs @@ -0,0 +1,259 @@ +//! The various mapped addresses we use. +//! + +//! We use non-IP transports to carry datagrams. Yet Quinn needs to address those +//! transports using IPv6 addresses. These defines mappings of several IPv6 Unique Local +//! Address ranges we use to keep track of the various "fake" address types we use. + +use std::{ + collections::BTreeMap, + net::{IpAddr, Ipv6Addr, SocketAddr}, + sync::{ + Arc, + atomic::{AtomicU64, Ordering}, + }, +}; + +use iroh_base::{NodeId, RelayUrl}; +use snafu::Snafu; + +/// The Prefix/L of all Unique Local Addresses. +const ADDR_PREFIXL: u8 = 0xfd; + +/// The Global ID used in n0's Unique Local Addresses. +const ADDR_GLOBAL_ID: [u8; 5] = [21, 7, 10, 81, 11]; + +/// The Subnet ID for [`RelayMappedAddr]. +const RELAY_MAPPED_SUBNET: [u8; 2] = [0, 1]; + +/// The Subnet ID for [`NodeIdMappedAddr`]. +const NODE_ID_SUBNET: [u8; 2] = [0; 2]; + +/// The dummy port used for all mapped addresses. +/// +/// We map each entity, usually a [`NodeId`], to an IPv6 address. But socket addresses +/// involve ports, so we use a dummy fixed port when creating socket addresses. +const MAPPED_PORT: u16 = 12345; + +/// Counter to always generate unique addresses for [`RelayMappedAddr`]. +static RELAY_ADDR_COUNTER: AtomicU64 = AtomicU64::new(1); + +/// Counter to always generate unique addresses for [`NodeIdMappedAddr`]. +static NODE_ID_ADDR_COUNTER: AtomicU64 = AtomicU64::new(1); + +/// An enum encompassing all the mapped and unmapped addresses. +/// +/// This can consistently convert a socket address as we use them in Quinn and return a real +/// socket address or a mapped address. Note that this does not mean that the mapped +/// address exists, only that it is semantically a valid mapped address. +#[derive(Clone, Debug)] +pub(crate) enum MultipathMappedAddr { + /// An address for a [`NodeId`], via one or more paths. + Mixed(NodeIdMappedAddr), + /// An address for a particular [`NodeId`] via a particular relay. + Relay(RelayMappedAddr), + /// An IP based transport address. + #[cfg(not(wasm_browser))] + Ip(SocketAddr), +} + +impl From for MultipathMappedAddr { + fn from(value: SocketAddr) -> Self { + match value.ip() { + IpAddr::V4(_) => Self::Ip(value), + IpAddr::V6(addr) => { + if let Ok(addr) = NodeIdMappedAddr::try_from(addr) { + return Self::Mixed(addr); + } + #[cfg(not(wasm_browser))] + if let Ok(addr) = RelayMappedAddr::try_from(addr) { + return Self::Relay(addr); + } + Self::Ip(value) + } + } + } +} + +/// An address used to address a node on any or all paths. +/// +/// This is only used for initially connecting to a remote node. We instruct Quinn to send +/// to this address, and duplicate all packets for this address to send on all paths we +/// might want to send the initial on: +/// +/// - If this the first connection to the remote node we don't know which path will work and +/// send to all of them. +/// +/// - If there already is an active connection to this node we now which path to use. +/// +/// It is but a newtype around an IPv6 Unique Local Addr. And in our QUIC-facing socket +/// APIs like [`quinn::AsyncUdpSocket`] it comes in as the inner [`Ipv6Addr`], in those +/// interfaces we have to be careful to do the conversion to this type. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub(crate) struct NodeIdMappedAddr(Ipv6Addr); + +impl NodeIdMappedAddr { + /// Generates a globally unique fake UDP address. + /// + /// This generates and IPv6 Unique Local Address according to RFC 4193. + pub(crate) fn generate() -> Self { + let mut addr = [0u8; 16]; + addr[0] = ADDR_PREFIXL; + addr[1..6].copy_from_slice(&ADDR_GLOBAL_ID); + addr[6..8].copy_from_slice(&NODE_ID_SUBNET); + + let counter = NODE_ID_ADDR_COUNTER.fetch_add(1, Ordering::Relaxed); + addr[8..16].copy_from_slice(&counter.to_be_bytes()); + + Self(Ipv6Addr::from(addr)) + } + + /// Returns a consistent [`SocketAddr`] for the [`NodeIdMappedAddr`]. + /// + /// This socket address does not have a routable IP address. + /// + /// This uses a made-up port number, since the port does not play a role in the + /// addressing. This socket address is only to be used to pass into Quinn. + pub(crate) fn private_socket_addr(&self) -> SocketAddr { + SocketAddr::new(IpAddr::from(self.0), MAPPED_PORT) + } +} + +impl std::fmt::Display for NodeIdMappedAddr { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "NodeIdMappedAddr({})", self.0) + } +} + +impl TryFrom for NodeIdMappedAddr { + type Error = NodeIdMappedAddrError; + + fn try_from(value: Ipv6Addr) -> Result { + let octets = value.octets(); + if octets[0] == ADDR_PREFIXL + && octets[1..6] == ADDR_GLOBAL_ID + && octets[6..8] == NODE_ID_SUBNET + { + return Ok(Self(value)); + } + Err(NodeIdMappedAddrError) + } +} + +/// Can occur when converting a [`SocketAddr`] to an [`NodeIdMappedAddr`] +#[derive(Debug, Snafu)] +#[snafu(display("Failed to convert"))] +pub struct NodeIdMappedAddrError; + +/// An Ipv6 ULA address, identifying a relay path for a [`NodeId`]. +/// +/// Since iroh nodes are reachable via a relay server we have a network path indicated by +/// the `(NodeId, RelayUrl)`. However Quinn can only handle socket addresses, so we use +/// IPv6 addresses in a private IPv6 Unique Local Address range, which map to a unique +/// `(NodeId, RelayUrl)` pair. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub(crate) struct RelayMappedAddr(Ipv6Addr); + +impl RelayMappedAddr { + /// Generates a globally unique fake UDP address. + /// + /// This generates a new IPv6 address in the Unique Local Address range (RFC 4193) + /// which is recognised by iroh as an IP mapped address. + pub(crate) fn generate() -> Self { + let mut addr = [0u8; 16]; + addr[0] = ADDR_PREFIXL; + addr[1..6].copy_from_slice(&ADDR_GLOBAL_ID); + addr[6..8].copy_from_slice(&RELAY_MAPPED_SUBNET); + + let counter = RELAY_ADDR_COUNTER.fetch_add(1, Ordering::Relaxed); + addr[8..16].copy_from_slice(&counter.to_be_bytes()); + + Self(Ipv6Addr::from(addr)) + } + + /// Returns a consistent [`SocketAddr`] for the [`RelayMappedAddr`]. + /// + /// This does not have a routable IP address. + /// + /// This uses a made-up, but fixed port number. The [`RelayAddrMap`] creates a unique + /// [`RelayMappedAddr`] for each `(NodeId, RelayUrl)` pair and thus does not use the + /// port to map back to the original [`SocketAddr`]. + pub(crate) fn private_socket_addr(&self) -> SocketAddr { + SocketAddr::new(IpAddr::from(self.0), MAPPED_PORT) + } +} + +impl TryFrom for RelayMappedAddr { + type Error = RelayMappedAddrError; + + fn try_from(value: Ipv6Addr) -> std::result::Result { + let octets = value.octets(); + if octets[0] == ADDR_PREFIXL + && octets[1..6] == ADDR_GLOBAL_ID + && octets[6..8] == RELAY_MAPPED_SUBNET + { + return Ok(Self(value)); + } + Err(RelayMappedAddrError) + } +} + +impl std::fmt::Display for RelayMappedAddr { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "RelayMappedAddr({})", self.0) + } +} + +/// Can occur when converting a [`SocketAddr`] to an [`RelayMappedAddr`] +#[derive(Debug, Snafu)] +#[snafu(display("Failed to convert"))] +pub struct RelayMappedAddrError; + +/// A Map of [`RelayMappedAddr`] to `(RelayUrl, NodeId)`. +// TODO: this could be an RwLock, or even an dashmap +#[derive(Debug, Clone, Default)] +pub(crate) struct RelayAddrMap(Arc>); + +#[derive(Debug, Default)] +pub(super) struct Inner { + by_mapped_addr: BTreeMap, + by_url: BTreeMap<(RelayUrl, NodeId), RelayMappedAddr>, +} + +impl RelayAddrMap { + /// Adds a new entry to the map and returns the generated [`RelayMappedAddr`]. + /// + /// If this `(RelayUrl, NodeId)` already exists in the map, it returns its associated + /// [`RelayMappedAddr`]. + /// + /// Otherwise a new [`RelayMappedAddr`] is generated for it and returned. + pub(super) fn get_or_register(&self, relay: RelayUrl, node: NodeId) -> RelayMappedAddr { + let mut inner = self.0.lock().expect("poisoned"); + if let Some(mapped_addr) = inner.by_url.get(&(relay.clone(), node)) { + return *mapped_addr; + } + let ip_mapped_addr = RelayMappedAddr::generate(); + inner + .by_mapped_addr + .insert(ip_mapped_addr, (relay.clone(), node)); + inner.by_url.insert((relay, node), ip_mapped_addr); + ip_mapped_addr + } + + /// Returns the [`RelayMappedAddr`] for the given [`RelayUrl`] and [`NodeId`]. + pub(crate) fn get_mapped_addr(&self, relay: RelayUrl, node: NodeId) -> Option { + let inner = self.0.lock().expect("poisoned"); + inner.by_url.get(&(relay, node)).copied() + } + + /// Returns the [`RelayUrl`] and [`NodeId`] for the given [`RelayMappedAddr`]. + pub(crate) fn get_url(&self, mapped_addr: &RelayMappedAddr) -> Option<(RelayUrl, NodeId)> { + let inner = self.0.lock().expect("poisoned"); + inner.by_mapped_addr.get(mapped_addr).cloned() + } +} + +/// Can occur when converting a [`SocketAddr`] to an [`RelayMappedAddr`] +#[derive(Debug, Snafu)] +#[snafu(display("Failed to convert"))] +pub struct RelayAddrMapError; diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index 85317e3f5f4..aa784cc9de3 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -18,7 +18,7 @@ use super::transports::TransportsSender; #[cfg(not(any(test, feature = "test-utils")))] use super::transports::TransportsSender; use super::{ - NodeIdMappedAddr, + mapped_addrs::NodeIdMappedAddr, metrics::Metrics, transports::{self, OwnedTransmit}, }; diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index ead6be40894..ea348ff9ab2 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -25,7 +25,8 @@ use crate::endpoint::PathSelection; use crate::{ disco::{self, SendAddr}, magicsock::{ - HEARTBEAT_INTERVAL, MagicsockMetrics, NodeIdMappedAddr, + HEARTBEAT_INTERVAL, MagicsockMetrics, + mapped_addrs::NodeIdMappedAddr, node_map::path_validity::PathValidity, transports::{self, OwnedTransmit, TransportsSender}, }, diff --git a/iroh/src/magicsock/relay_mapped_addrs.rs b/iroh/src/magicsock/relay_mapped_addrs.rs deleted file mode 100644 index 62623fefb54..00000000000 --- a/iroh/src/magicsock/relay_mapped_addrs.rs +++ /dev/null @@ -1,137 +0,0 @@ -use std::{ - collections::BTreeMap, - net::{IpAddr, Ipv6Addr, SocketAddr}, - sync::{ - Arc, - atomic::{AtomicU64, Ordering}, - }, -}; - -use iroh_base::{NodeId, RelayUrl}; -use snafu::Snafu; - -/// Can occur when converting a [`SocketAddr`] to an [`RelayMappedAddr`] -#[derive(Debug, Snafu)] -#[snafu(display("Failed to convert"))] -pub struct RelayMappedAddrError; - -/// An Ipv6 ULA address, identifying a relay path for a [`NodeId`]. -/// -/// Since iroh nodes are reachable via a relay server we have a network path indicated by -/// the `(NodeId, RelayUrl)`. However Quinn can only handle socket addresses, so we use -/// IPv6 addresses in a private IPv6 Unique Local Address range, which map to a unique -/// `(NodeId, RelayUrl)` pair. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub(crate) struct RelayMappedAddr(Ipv6Addr); - -/// Counter to always generate unique addresses for [`RelayMappedAddr`]. -static RELAY_ADDR_COUNTER: AtomicU64 = AtomicU64::new(1); - -impl RelayMappedAddr { - /// The Prefix/L of our Unique Local Addresses. - const ADDR_PREFIXL: u8 = 0xfd; - /// The Global ID used in our Unique Local Addresses. - const ADDR_GLOBAL_ID: [u8; 5] = [21, 7, 10, 81, 11]; - /// The Subnet ID used in our Unique Local Addresses. - const ADDR_SUBNET: [u8; 2] = [0, 1]; - - /// The dummy port used for all mapped addresses. - const MAPPED_ADDR_PORT: u16 = 12345; - - /// Generates a globally unique fake UDP address. - /// - /// This generates a new IPv6 address in the Unique Local Address range (RFC 4193) - /// which is recognised by iroh as an IP mapped address. - pub(crate) fn generate() -> Self { - let mut addr = [0u8; 16]; - addr[0] = Self::ADDR_PREFIXL; - addr[1..6].copy_from_slice(&Self::ADDR_GLOBAL_ID); - addr[6..8].copy_from_slice(&Self::ADDR_SUBNET); - - let counter = RELAY_ADDR_COUNTER.fetch_add(1, Ordering::Relaxed); - addr[8..16].copy_from_slice(&counter.to_be_bytes()); - - Self(Ipv6Addr::from(addr)) - } - - /// Returns a consistent [`SocketAddr`] for the [`RelayMappedAddr`]. - /// - /// This does not have a routable IP address. - /// - /// This uses a made-up, but fixed port number. The [`RelayAddrMap`] creates a unique - /// [`RelayMappedAddr`] for each `(NodeId, RelayUrl)` pair and thus does not use the - /// port to map back to the original [`SocketAddr`]. - pub(crate) fn private_socket_addr(&self) -> SocketAddr { - SocketAddr::new(IpAddr::from(self.0), Self::MAPPED_ADDR_PORT) - } -} - -impl TryFrom for RelayMappedAddr { - type Error = RelayMappedAddrError; - - fn try_from(value: Ipv6Addr) -> std::result::Result { - let octets = value.octets(); - if octets[0] == Self::ADDR_PREFIXL - && octets[1..6] == Self::ADDR_GLOBAL_ID - && octets[6..8] == Self::ADDR_SUBNET - { - return Ok(Self(value)); - } - Err(RelayMappedAddrError) - } -} - -impl std::fmt::Display for RelayMappedAddr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "IpMappedAddr({})", self.0) - } -} - -/// Can occur when converting a [`SocketAddr`] to an [`RelayMappedAddr`] -#[derive(Debug, Snafu)] -#[snafu(display("Failed to convert"))] -pub struct RelayAddrMapError; - -/// A Map of [`RelayMappedAddr`] to `(RelayUrl, NodeId)`. -// TODO: this could be an RwLock, or even an dashmap -#[derive(Debug, Clone, Default)] -pub(crate) struct RelayAddrMap(Arc>); - -#[derive(Debug, Default)] -pub(super) struct Inner { - by_mapped_addr: BTreeMap, - by_url: BTreeMap<(RelayUrl, NodeId), RelayMappedAddr>, -} - -impl RelayAddrMap { - /// Adds a new entry to the map and returns the generated [`RelayMappedAddr`]. - /// - /// If this `(RelayUrl, NodeId)` already exists in the map, it returns its associated - /// [`RelayMappedAddr`]. - /// - /// Otherwise a new [`RelayMappedAddr`] is generated for it and returned. - pub(super) fn get_or_register(&self, relay: RelayUrl, node: NodeId) -> RelayMappedAddr { - let mut inner = self.0.lock().expect("poisoned"); - if let Some(mapped_addr) = inner.by_url.get(&(relay.clone(), node)) { - return *mapped_addr; - } - let ip_mapped_addr = RelayMappedAddr::generate(); - inner - .by_mapped_addr - .insert(ip_mapped_addr, (relay.clone(), node)); - inner.by_url.insert((relay, node), ip_mapped_addr); - ip_mapped_addr - } - - /// Returns the [`RelayMappedAddr`] for the given [`RelayUrl`] and [`NodeId`]. - pub(crate) fn get_mapped_addr(&self, relay: RelayUrl, node: NodeId) -> Option { - let inner = self.0.lock().expect("poisoned"); - inner.by_url.get(&(relay, node)).copied() - } - - /// Returns the [`RelayUrl`] and [`NodeId`] for the given [`RelayMappedAddr`]. - pub(crate) fn get_url(&self, mapped_addr: &RelayMappedAddr) -> Option<(RelayUrl, NodeId)> { - let inner = self.0.lock().expect("poisoned"); - inner.by_mapped_addr.get(mapped_addr).cloned() - } -} diff --git a/iroh/src/magicsock/transports.rs b/iroh/src/magicsock/transports.rs index 2d684068691..868447495b9 100644 --- a/iroh/src/magicsock/transports.rs +++ b/iroh/src/magicsock/transports.rs @@ -13,6 +13,10 @@ use relay::{RelayNetworkChangeSender, RelaySender}; use tokio::sync::mpsc; use tracing::{debug, error, instrument, trace, warn}; +use crate::net_report::Report; + +use super::{MagicSock, mapped_addrs::MultipathMappedAddr, node_map::NodeStateMessage}; + #[cfg(not(wasm_browser))] mod ip; mod relay; @@ -21,9 +25,8 @@ mod relay; pub(crate) use self::ip::IpTransport; #[cfg(not(wasm_browser))] use self::ip::{IpNetworkChangeSender, IpSender}; + pub(crate) use self::relay::{RelayActorConfig, RelayTransport}; -use super::{MagicSock, node_map::NodeStateMessage}; -use crate::{magicsock::MultipathMappedAddr, net_report::Report}; /// Manages the different underlying data transports that the magicsock /// can support. From 31ab033a0f4af98cc5d5bf31bcc90202f21b9309 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Tue, 16 Sep 2025 17:10:00 +0200 Subject: [PATCH 31/44] allow me to send via the NodeStateActor --- iroh/src/magicsock.rs | 5 +- iroh/src/magicsock/mapped_addrs.rs | 33 ++++++- iroh/src/magicsock/node_map.rs | 108 ++++++++++++---------- iroh/src/magicsock/node_map/node_state.rs | 19 ++-- iroh/src/magicsock/transports.rs | 95 ++++++++++--------- 5 files changed, 151 insertions(+), 109 deletions(-) diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 8fb637322e0..43d6bdb24e4 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -1212,9 +1212,10 @@ impl Handle { let node_map = node_map.unwrap_or_default(); let sender = transports.create_sender(); #[cfg(any(test, feature = "test-utils"))] - let nm = NodeMap::load_from_vec(node_map, path_selection, &metrics.magicsock, sender); + let nm = + NodeMap::load_from_vec(node_map, path_selection, metrics.magicsock.clone(), sender); #[cfg(not(any(test, feature = "test-utils")))] - let nm = NodeMap::load_from_vec(node_map, &metrics.magicsock, sender); + let nm = NodeMap::load_from_vec(node_map, metrics.magicsock.clone(), sender); nm }; // let node_map = node_map.unwrap_or_default(); diff --git a/iroh/src/magicsock/mapped_addrs.rs b/iroh/src/magicsock/mapped_addrs.rs index 3c1eb3741a5..0df09e8107a 100644 --- a/iroh/src/magicsock/mapped_addrs.rs +++ b/iroh/src/magicsock/mapped_addrs.rs @@ -6,7 +6,7 @@ //! Address ranges we use to keep track of the various "fake" address types we use. use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashMap}, net::{IpAddr, Ipv6Addr, SocketAddr}, sync::{ Arc, @@ -140,6 +140,37 @@ impl TryFrom for NodeIdMappedAddr { } } +/// A map between [`NodeId`] and [`NodeIdMappedAddr`]. +#[derive(Debug, Clone, Default)] +pub(super) struct NodeIdAddrMap { + inner: Arc>, +} + +impl NodeIdAddrMap { + pub(super) fn get(&self, node_id: NodeId) -> NodeIdMappedAddr { + let mut inner = self.inner.lock().expect("poisoned"); + match inner.node_addrs.get(&node_id) { + Some(addr) => *addr, + None => { + let addr = NodeIdMappedAddr::generate(); + inner.lookup.insert(addr, node_id); + addr + } + } + } + + pub(super) fn lookup(&self, addr: NodeIdMappedAddr) -> Option { + let inner = self.inner.lock().expect("poisoned"); + inner.lookup.get(&addr).copied() + } +} + +#[derive(Debug, Default)] +struct NodeIdAddrMapInner { + node_addrs: HashMap, + lookup: HashMap, +} + /// Can occur when converting a [`SocketAddr`] to an [`NodeIdMappedAddr`] #[derive(Debug, Snafu)] #[snafu(display("Failed to convert"))] diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index aa784cc9de3..6fed669912f 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -1,3 +1,5 @@ +#[cfg(any(test, feature = "test-utils"))] +use std::sync::Arc; use std::{ collections::{BTreeSet, HashMap, hash_map::Entry}, hash::Hash, @@ -7,7 +9,7 @@ use std::{ use iroh_base::{NodeAddr, NodeId, PublicKey, RelayUrl}; use n0_future::{task::AbortOnDropHandle, time::Instant}; -use node_state::NodeStateHandle; +use node_state::{NodeStateActor, NodeStateHandle}; use serde::{Deserialize, Serialize}; use tokio::sync::mpsc; use tracing::{debug, info, instrument, trace, warn}; @@ -18,8 +20,8 @@ use super::transports::TransportsSender; #[cfg(not(any(test, feature = "test-utils")))] use super::transports::TransportsSender; use super::{ - mapped_addrs::NodeIdMappedAddr, - metrics::Metrics, + MagicsockMetrics, + mapped_addrs::{NodeIdAddrMap, NodeIdMappedAddr}, transports::{self, OwnedTransmit}, }; use crate::disco::CallMeMaybe; @@ -59,10 +61,13 @@ const MAX_INACTIVE_NODES: usize = 30; #[derive(Debug)] pub(super) struct NodeMap { inner: Mutex, + /// The mapping between [`NodeId`]s and [`NodeIdMappedAddr`]s. + pub(super) node_mapped_addrs: NodeIdAddrMap, } #[derive(Debug)] pub(super) struct NodeMapInner { + metrics: Arc, /// Handle to an actor that can send over the transports. transports_handle: TransportsSenderHandle, by_node_key: HashMap, @@ -76,12 +81,6 @@ pub(super) struct NodeMapInner { /// /// [`NodeStateActor`]: node_state::NodeStateActor node_states: HashMap, - /// The [`NodeIdMappedAddr`] for each node. - node_addrs: HashMap, - /// The reverse of mapping of [`Self::node_addrs`]. - node_addrs_lookup: HashMap, - // /// The [`RelayMappedAddr`] for each node. - // relay_addrs: HashMap, } /// Identifier to look up a [`NodeState`] in the [`NodeMap`]. @@ -140,15 +139,15 @@ pub enum Source { impl NodeMap { #[cfg(any(test, feature = "test-utils"))] - pub(super) fn new(sender: TransportsSender) -> Self { - Self::from_inner(NodeMapInner::new(sender)) + pub(super) fn new(metrics: Arc, sender: TransportsSender) -> Self { + Self::from_inner(NodeMapInner::new(metrics, sender)) } #[cfg(not(any(test, feature = "test-utils")))] /// Create a new [`NodeMap`] from a list of [`NodeAddr`]s. pub(super) fn load_from_vec( nodes: Vec, - metrics: &Metrics, + metrics: Arc, sender: TransportsSender, ) -> Self { Self::from_inner(NodeMapInner::load_from_vec(nodes, metrics, sender)) @@ -159,7 +158,7 @@ impl NodeMap { pub(super) fn load_from_vec( nodes: Vec, path_selection: PathSelection, - metrics: &Metrics, + metrics: Arc, sender: TransportsSender, ) -> Self { Self::from_inner(NodeMapInner::load_from_vec( @@ -173,11 +172,17 @@ impl NodeMap { fn from_inner(inner: NodeMapInner) -> Self { Self { inner: Mutex::new(inner), + node_mapped_addrs: Default::default(), } } /// Add the contact information for a node. - pub(super) fn add_node_addr(&self, node_addr: NodeAddr, source: Source, metrics: &Metrics) { + pub(super) fn add_node_addr( + &self, + node_addr: NodeAddr, + source: Source, + metrics: &MagicsockMetrics, + ) { self.inner .lock() .expect("poisoned") @@ -234,7 +239,7 @@ impl NodeMap { &self, sender: PublicKey, cm: CallMeMaybe, - metrics: &Metrics, + metrics: &MagicsockMetrics, ) { self.inner .lock() @@ -247,7 +252,7 @@ impl NodeMap { &self, addr: NodeIdMappedAddr, have_ipv6: bool, - metrics: &Metrics, + metrics: &MagicsockMetrics, ) -> Option<( PublicKey, Option, @@ -321,30 +326,29 @@ impl NodeMap { /// Returns the sender for the [`NodeStateActor`]. /// /// [`NodeStateActor`]: node_state::NodeStateActor - pub(super) fn get_node_state_actor( - &self, - addr: NodeIdMappedAddr, - // node_id: NodeId, - ) -> Option> { - // self - // .inner - // .lock() - // .expect("poisoned") - // .new - // .entry(node_id) - // .or_insert_with_key(|node_id| { - // let mut actor = NodeStateActor::new(*node_id, transports_sender, metrics); - // actor.start() - // }); - todo!() + pub(super) fn node_state_actor(&self, node_id: NodeId) -> mpsc::Sender { + let mut inner = self.inner.lock().expect("poisoned"); + match inner.node_states.get(&node_id) { + Some(handle) => handle.sender.clone(), + None => { + let sender = inner.transports_handle.inbox.clone(); + let metrics = inner.metrics.clone(); + let actor = NodeStateActor::new(node_id, sender, metrics); + let handle = actor.start(); + let sender = handle.sender.clone(); + inner.node_states.insert(node_id, handle); + sender + } + } } } impl NodeMapInner { #[cfg(any(test, feature = "test-utils"))] - fn new(sender: TransportsSender) -> Self { + fn new(metrics: Arc, sender: TransportsSender) -> Self { let transports_handle = Self::start_transports_sender(sender); Self { + metrics, transports_handle, by_node_key: Default::default(), by_ip_port: Default::default(), @@ -353,16 +357,19 @@ impl NodeMapInner { next_id: 0, path_selection: Default::default(), node_states: Default::default(), - node_addrs: Default::default(), - node_addrs_lookup: Default::default(), } } /// Creates a new [`NodeMap`] from a list of [`NodeAddr`]s. #[cfg(not(any(test, feature = "test-utils")))] - fn load_from_vec(nodes: Vec, metrics: &Metrics, sender: TransportsSender) -> Self { + fn load_from_vec( + nodes: Vec, + metrics: Arc, + sender: TransportsSender, + ) -> Self { let transports_handle = Self::start_transports_sender(sender); let mut me = Self { + metrics: metrics.clone(), transports_handle, by_node_key: Default::default(), by_ip_port: Default::default(), @@ -370,11 +377,9 @@ impl NodeMapInner { by_id: Default::default(), next_id: 0, node_states: Default::default(), - node_addrs: Default::default(), - node_addrs_lookup: Default::default(), }; for node_addr in nodes { - me.add_node_addr(node_addr, Source::Saved, metrics); + me.add_node_addr(node_addr, Source::Saved, &metrics); } me } @@ -384,11 +389,12 @@ impl NodeMapInner { fn load_from_vec( nodes: Vec, path_selection: PathSelection, - metrics: &Metrics, + metrics: Arc, sender: TransportsSender, ) -> Self { let transports_handle = Self::start_transports_sender(sender); let mut me = Self { + metrics: metrics.clone(), transports_handle, by_node_key: Default::default(), by_ip_port: Default::default(), @@ -397,11 +403,9 @@ impl NodeMapInner { next_id: 0, path_selection, node_states: Default::default(), - node_addrs: Default::default(), - node_addrs_lookup: Default::default(), }; for node_addr in nodes { - me.add_node_addr(node_addr, Source::Saved, metrics); + me.add_node_addr(node_addr, Source::Saved, &metrics); } me } @@ -413,7 +417,8 @@ impl NodeMapInner { /// Add the contact information for a node. #[instrument(skip_all, fields(node = %node_addr.node_id.fmt_short()))] - fn add_node_addr(&mut self, node_addr: NodeAddr, source: Source, metrics: &Metrics) { + fn add_node_addr(&mut self, node_addr: NodeAddr, source: Source, metrics: &MagicsockMetrics) { + // TODO: Add to the NodeStateActor here. let source0 = source.clone(); let node_id = node_addr.node_id; let relay_url = node_addr.relay_url.clone(); @@ -568,7 +573,12 @@ impl NodeMapInner { .map(|ep| ep.conn_type()) } - fn handle_call_me_maybe(&mut self, sender: NodeId, cm: CallMeMaybe, metrics: &Metrics) { + fn handle_call_me_maybe( + &mut self, + sender: NodeId, + cm: CallMeMaybe, + metrics: &MagicsockMetrics, + ) { let ns_id = NodeStateKey::NodeId(sender); if let Some(id) = self.get_id(ns_id.clone()) { for number in &cm.my_numbers { @@ -818,7 +828,7 @@ mod tests { #[traced_test] async fn restore_from_vec() { let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); - let node_map = NodeMap::new(transports.create_sender()); + let node_map = NodeMap::new(Default::default(), transports.create_sender()); let mut rng = rand::thread_rng(); let node_a = SecretKey::generate(&mut rng).public(); @@ -858,7 +868,7 @@ mod tests { let loaded_node_map = NodeMap::load_from_vec( addrs.clone(), PathSelection::default(), - &Default::default(), + Default::default(), transports.create_sender(), ); @@ -889,7 +899,7 @@ mod tests { #[traced_test] fn test_prune_direct_addresses() { let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); - let node_map = NodeMap::new(transports.create_sender()); + let node_map = NodeMap::new(Default::default(), transports.create_sender()); let public_key = SecretKey::generate(rand::thread_rng()).public(); let id = node_map .inner @@ -963,7 +973,7 @@ mod tests { #[test] fn test_prune_inactive() { let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); - let node_map = NodeMap::new(transports.create_sender()); + let node_map = NodeMap::new(Default::default(), transports.create_sender()); // add one active node and more than MAX_INACTIVE_NODES inactive nodes let active_node = SecretKey::generate(rand::thread_rng()).public(); let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 167); diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index ea348ff9ab2..98cf7b2e282 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -15,11 +15,6 @@ use serde::{Deserialize, Serialize}; use tokio::sync::mpsc; use tracing::{Level, debug, event, info, instrument, trace, warn}; -use super::{ - IpPort, Source, - path_state::{PathState, summarize_node_paths}, - udp_paths::{NodeUdpPaths, UdpSendAddr}, -}; #[cfg(any(test, feature = "test-utils"))] use crate::endpoint::PathSelection; use crate::{ @@ -28,10 +23,16 @@ use crate::{ HEARTBEAT_INTERVAL, MagicsockMetrics, mapped_addrs::NodeIdMappedAddr, node_map::path_validity::PathValidity, - transports::{self, OwnedTransmit, TransportsSender}, + transports::{self, OwnedTransmit}, }, }; +use super::{ + IpPort, Source, TransportsSenderMessage, + path_state::{PathState, summarize_node_paths}, + udp_paths::{NodeUdpPaths, UdpSendAddr}, +}; + /// Number of addresses that are not active that we keep around per node. /// /// See [`NodeState::prune_direct_addresses`]. @@ -708,7 +709,7 @@ impl NodeState { pub(super) struct NodeStateActor { /// The node ID of the remote node. node_id: NodeId, - transports_sender: TransportsSender, + transports_sender: mpsc::Sender, // TODO: Turn this into a WeakConnectionHandle connections: Vec, paths: BTreeMap, @@ -718,7 +719,7 @@ pub(super) struct NodeStateActor { impl NodeStateActor { pub(super) fn new( node_id: NodeId, - transports_sender: TransportsSender, + transports_sender: mpsc::Sender, metrics: Arc, ) -> Self { Self { @@ -790,7 +791,7 @@ pub(crate) enum NodeStateMessage { /// Dropping this will stop the actor. #[derive(Debug)] pub(super) struct NodeStateHandle { - sender: mpsc::Sender, + pub(super) sender: mpsc::Sender, _task: AbortOnDropHandle<()>, } diff --git a/iroh/src/magicsock/transports.rs b/iroh/src/magicsock/transports.rs index 868447495b9..a66ce7555c8 100644 --- a/iroh/src/magicsock/transports.rs +++ b/iroh/src/magicsock/transports.rs @@ -646,7 +646,7 @@ impl MagicSender { impl quinn::UdpSender for MagicSender { #[instrument( skip_all, - fields(src = ?quinn_transmit.src_ip, len = quinn_transmit.contents.len(), dst), + fields(src = ?quinn_transmit.src_ip, len = quinn_transmit.contents.len(), dst, node_id), )] fn poll_send( self: Pin<&mut Self>, @@ -666,6 +666,12 @@ impl quinn::UdpSender for MagicSender { // TODO: Would be nicer to log the NodeId of this, but we only get an actor // sender for it. tracing::Span::current().record("dst", tracing::field::debug(&mapped_addr)); + let Some(node_id) = self.msock.node_map.node_mapped_addrs.lookup(mapped_addr) + else { + error!("unknown NodeIdMappedAddr, dropped transmit"); + return Poll::Ready(Ok(())); + }; + tracing::Span::current().record("node_id", node_id.fmt_short()); // Note we drop the src_ip set in the Quinn Transmit. This is only the // Initial packet we are sending, so we do not yet have an src address we @@ -673,26 +679,20 @@ impl quinn::UdpSender for MagicSender { if let Some(src_ip) = quinn_transmit.src_ip { warn!(?src_ip, "oops, flub didn't think this would happen"); } - return match self.msock.node_map.get_node_state_actor(mapped_addr) { - Some(sender) => { - let transmit = OwnedTransmit::from(quinn_transmit); - match sender.try_send(NodeStateMessage::SendDatagram(transmit)) { - Ok(()) => { - trace!("sent transmit",); - Poll::Ready(Ok(())) - } - Err(err) => { - // We do not want to block the next send which might be on a - // different transport. Instead we let Quinn handle this as - // a lost datagram. - // TODO: Revisit this: we might want to do something better. - debug!("NodeStateActor inbox full ({err:#}), dropped transmit"); - Poll::Ready(Ok(())) - } - } + + let sender = self.msock.node_map.node_state_actor(node_id); + let transmit = OwnedTransmit::from(quinn_transmit); + return match sender.try_send(NodeStateMessage::SendDatagram(transmit)) { + Ok(()) => { + trace!("sent transmit",); + Poll::Ready(Ok(())) } - None => { - error!("unknown AllPathsMappedAddr, dropped transmit"); + Err(err) => { + // We do not want to block the next send which might be on a + // different transport. Instead we let Quinn handle this as + // a lost datagram. + // TODO: Revisit this: we might want to do something better. + debug!("NodeStateActor inbox full ({err:#}), dropped transmit"); Poll::Ready(Ok(())) } }; @@ -746,7 +746,7 @@ impl quinn::UdpSender for MagicSender { #[instrument( skip_all, - fields(src = ?quinn_transmit.src_ip, len = quinn_transmit.contents.len(), dst), + fields(src = ?quinn_transmit.src_ip, len = quinn_transmit.contents.len(), dst, node_id), )] fn try_send(self: Pin<&mut Self>, quinn_transmit: &quinn_udp::Transmit) -> io::Result<()> { // As opposed to poll_send this method does return normal IO errors. Calls to this @@ -758,6 +758,15 @@ impl quinn::UdpSender for MagicSender { // TODO: Would be nicer to log the NodeId of this, but we only get an actor // sender for it. tracing::Span::current().record("dst", tracing::field::debug(&mapped_addr)); + let Some(node_id) = self.msock.node_map.node_mapped_addrs.lookup(mapped_addr) + else { + error!("unknown NodeIdMappedAddr, dropped transmit"); + return Err(io::Error::new( + io::ErrorKind::HostUnreachable, + "Unknown NodeIdMappedAddr", + )); + }; + tracing::Span::current().record("node_id", node_id.fmt_short()); // Note we drop the src_ip set in the Quinn Transmit. This is only the // Initial packet we are sending, so we do not yet have an src address we @@ -765,35 +774,25 @@ impl quinn::UdpSender for MagicSender { if let Some(src_ip) = quinn_transmit.src_ip { warn!(?src_ip, "oops, flub didn't think this would happen"); } - return match self.msock.node_map.get_node_state_actor(mapped_addr) { - Some(sender) => { - let transmit = OwnedTransmit::from(quinn_transmit); - match sender.try_send(NodeStateMessage::SendDatagram(transmit)) { - Ok(()) => { - trace!("sent transmit",); - Ok(()) - } - Err(mpsc::error::TrySendError::Full(_)) => { - debug!("NodeStateActor inbox full, dropped transmit"); - Err(io::Error::new( - io::ErrorKind::WouldBlock, - "NodeStateActor inbox full", - )) - } - Err(mpsc::error::TrySendError::Closed(_)) => { - debug!("NodeStateActor inbox closed, dropped transmit"); - Err(io::Error::new( - io::ErrorKind::NetworkDown, - "NodeStateActor inbox closed", - )) - } - } + let sender = self.msock.node_map.node_state_actor(node_id); + let transmit = OwnedTransmit::from(quinn_transmit); + return match sender.try_send(NodeStateMessage::SendDatagram(transmit)) { + Ok(()) => { + trace!("sent transmit",); + Ok(()) } - None => { - error!("unknown AllPathsMappedAddr, dropped transmit"); + Err(mpsc::error::TrySendError::Full(_)) => { + debug!("NodeStateActor inbox full, dropped transmit"); Err(io::Error::new( - io::ErrorKind::HostUnreachable, - "unknown AllPathsMappedAddr", + io::ErrorKind::WouldBlock, + "NodeStateActor inbox full", + )) + } + Err(mpsc::error::TrySendError::Closed(_)) => { + debug!("NodeStateActor inbox closed, dropped transmit"); + Err(io::Error::new( + io::ErrorKind::NetworkDown, + "NodeStateActor inbox closed", )) } }; From 5ae71edb7d39dbe8bd26d99fc266779ca22ba50d Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Tue, 16 Sep 2025 18:26:27 +0200 Subject: [PATCH 32/44] Unify NodeIdMappedAddr and RelayMappedAddr a bit more This moves all the mappings into the NodeMap and uses a generic struct for the mapping. --- iroh/src/endpoint.rs | 5 +- iroh/src/magicsock.rs | 93 +++---------- iroh/src/magicsock/mapped_addrs.rs | 153 ++++++++++------------ iroh/src/magicsock/node_map.rs | 9 +- iroh/src/magicsock/node_map/node_state.rs | 2 +- iroh/src/magicsock/transports.rs | 18 ++- 6 files changed, 110 insertions(+), 170 deletions(-) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 741fdbab89b..e517aec1584 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -62,7 +62,10 @@ use crate::{ DiscoverySubscribers, DiscoveryTask, DynIntoDiscovery, IntoDiscovery, IntoDiscoveryError, Lagged, UserData, pkarr::PkarrPublisher, }, - magicsock::{self, Handle, OwnAddressSnafu, mapped_addrs::NodeIdMappedAddr}, + magicsock::{ + self, Handle, OwnAddressSnafu, + mapped_addrs::{MappedAddr, NodeIdMappedAddr}, + }, metrics::EndpointMetrics, net_report::Report, tls, diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 43d6bdb24e4..cf9da756afe 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -51,13 +51,6 @@ use tracing::{ use transports::{LocalAddrsWatch, MagicTransport}; use url::Url; -#[cfg(not(wasm_browser))] -use self::transports::IpTransport; -use self::{ - metrics::Metrics as MagicsockMetrics, - node_map::{NodeMap, PingAction}, - transports::{RelayActorConfig, RelayTransport, Transports, TransportsSender}, -}; #[cfg(not(wasm_browser))] use crate::dns::DnsResolver; #[cfg(any(test, feature = "test-utils"))] @@ -73,13 +66,21 @@ use crate::{ net_report::{self, IfStateDetails, Report}, }; +#[cfg(not(wasm_browser))] +use self::transports::IpTransport; +use self::{ + metrics::Metrics as MagicsockMetrics, + node_map::{NodeMap, PingAction}, + transports::{RelayActorConfig, RelayTransport, Transports, TransportsSender}, +}; + mod metrics; pub(crate) mod mapped_addrs; pub(crate) mod node_map; pub(crate) mod transports; -use mapped_addrs::{NodeIdMappedAddr, RelayAddrMap}; +use mapped_addrs::{MappedAddr, NodeIdMappedAddr}; pub use metrics::Metrics; pub use node_map::Source; @@ -204,8 +205,6 @@ pub(crate) struct MagicSock { /// Tracks existing connections connection_map: ConnectionMap, - /// Tracks the mapped IP addresses - relay_mapped_addrs: RelayAddrMap, /// Local addresses local_addrs_watch: LocalAddrsWatch, /// Currently bound IP addresses of all sockets @@ -462,9 +461,8 @@ impl MagicSock { self.node_map .add_node_addr(addr.clone(), source, &self.metrics.magicsock); - if let Some(url) = addr.relay_url() { - self.relay_mapped_addrs - .get_or_register(url.clone(), addr.node_id); + if let Some(url) = addr.relay_url().cloned() { + self.node_map.relay_mapped_addrs.get(&(url, addr.node_id)); } // Add paths to the existing connections @@ -763,8 +761,9 @@ impl MagicSock { // Relay let _quic_mapped_addr = self.node_map.receive_relay(src_url, *src_node); let mapped_addr = self + .node_map .relay_mapped_addrs - .get_or_register(src_url.clone(), *src_node); + .get(&(src_url.clone(), *src_node)); quinn_meta.addr = mapped_addr.private_socket_addr(); } } @@ -1177,8 +1176,6 @@ impl Handle { let (ip_transports, port_mapper) = bind_ip(addr_v4, addr_v6, &metrics).context(BindSocketsSnafu)?; - let relay_mapped_addrs = RelayAddrMap::default(); - let (actor_sender, actor_receiver) = mpsc::channel(256); let my_relay = Watchable::new(None); @@ -1235,7 +1232,6 @@ impl Handle { ipv6_reported, node_map, connection_map: Default::default(), - relay_mapped_addrs, discovery, discovery_user_data: RwLock::new(discovery_user_data), direct_addrs: Default::default(), @@ -1516,66 +1512,6 @@ enum DiscoBoxError { }, } -// #[derive(Debug)] -// struct MagicUdpSocket { -// socket: Arc, -// transports: Transports, -// } - -// impl AsyncUdpSocket for MagicUdpSocket { -// fn create_sender(&self) -> Pin> { -// Box::pin(self.transports.create_sender(self.socket.clone())) -// } - -// /// NOTE: Receiving on a closed socket will return [`Poll::Pending`] indefinitely. -// fn poll_recv( -// &mut self, -// cx: &mut Context, -// bufs: &mut [io::IoSliceMut<'_>], -// metas: &mut [quinn_udp::RecvMeta], -// ) -> Poll> { -// self.transports.poll_recv(cx, bufs, metas, &self.socket) -// } - -// #[cfg(not(wasm_browser))] -// fn local_addr(&self) -> io::Result { -// let addrs: Vec<_> = self -// .transports -// .local_addrs() -// .into_iter() -// .filter_map(|addr| { -// let addr: SocketAddr = addr.into_socket_addr()?; -// Some(addr) -// }) -// .collect(); - -// if let Some(addr) = addrs.iter().find(|addr| addr.is_ipv6()) { -// return Ok(*addr); -// } -// if let Some(SocketAddr::V4(addr)) = addrs.first() { -// // Pretend to be IPv6, because our `MappedAddr`s need to be IPv6. -// let ip = addr.ip().to_ipv6_mapped().into(); -// return Ok(SocketAddr::new(ip, addr.port())); -// } - -// Err(io::Error::other("no valid address available")) -// } - -// #[cfg(wasm_browser)] -// fn local_addr(&self) -> io::Result { -// // Again, we need to pretend we're IPv6, because of our `MappedAddr`s. -// Ok(SocketAddr::new(std::net::Ipv6Addr::LOCALHOST.into(), 0)) -// } - -// fn max_receive_segments(&self) -> usize { -// self.transports.max_receive_segments() -// } - -// fn may_fragment(&self) -> bool { -// self.transports.may_fragment() -// } -// } - #[derive(Debug)] enum ActorMessage { PingActions(Vec), @@ -2270,7 +2206,6 @@ mod tests { use tracing::{Instrument, error, info, info_span, instrument}; use tracing_test::traced_test; - use super::{NodeIdMappedAddr, Options}; use crate::{ Endpoint, RelayMap, RelayMode, SecretKey, dns::DnsResolver, @@ -2279,6 +2214,8 @@ mod tests { tls, }; + use super::{NodeIdMappedAddr, Options, mapped_addrs::MappedAddr}; + const ALPN: &[u8] = b"n0/test/1"; impl Default for Options { diff --git a/iroh/src/magicsock/mapped_addrs.rs b/iroh/src/magicsock/mapped_addrs.rs index 0df09e8107a..d246aa84eff 100644 --- a/iroh/src/magicsock/mapped_addrs.rs +++ b/iroh/src/magicsock/mapped_addrs.rs @@ -6,7 +6,8 @@ //! Address ranges we use to keep track of the various "fake" address types we use. use std::{ - collections::{BTreeMap, HashMap}, + collections::HashMap, + hash::Hash, net::{IpAddr, Ipv6Addr, SocketAddr}, sync::{ Arc, @@ -14,7 +15,6 @@ use std::{ }, }; -use iroh_base::{NodeId, RelayUrl}; use snafu::Snafu; /// The Prefix/L of all Unique Local Addresses. @@ -41,6 +41,21 @@ static RELAY_ADDR_COUNTER: AtomicU64 = AtomicU64::new(1); /// Counter to always generate unique addresses for [`NodeIdMappedAddr`]. static NODE_ID_ADDR_COUNTER: AtomicU64 = AtomicU64::new(1); +/// Generic mapped address. +/// +/// Allows implementing [`AddrMap`]. +pub(crate) trait MappedAddr { + /// Generates a new mapped address in the IPv6 Unique Local Address space. + fn generate() -> Self; + + /// Returns a consistent [`SocketAddr`] for the mapped addr. + /// + /// This socket address does not have a routable IP address. It uses a fake but + /// consistent port number, since the port does not play a role in the addressing. This + /// socket address is only to be used to pass into Quinn. + fn private_socket_addr(&self) -> SocketAddr; +} + /// An enum encompassing all the mapped and unmapped addresses. /// /// This can consistently convert a socket address as we use them in Quinn and return a real @@ -92,11 +107,11 @@ impl From for MultipathMappedAddr { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct NodeIdMappedAddr(Ipv6Addr); -impl NodeIdMappedAddr { +impl MappedAddr for NodeIdMappedAddr { /// Generates a globally unique fake UDP address. /// /// This generates and IPv6 Unique Local Address according to RFC 4193. - pub(crate) fn generate() -> Self { + fn generate() -> Self { let mut addr = [0u8; 16]; addr[0] = ADDR_PREFIXL; addr[1..6].copy_from_slice(&ADDR_GLOBAL_ID); @@ -114,7 +129,7 @@ impl NodeIdMappedAddr { /// /// This uses a made-up port number, since the port does not play a role in the /// addressing. This socket address is only to be used to pass into Quinn. - pub(crate) fn private_socket_addr(&self) -> SocketAddr { + fn private_socket_addr(&self) -> SocketAddr { SocketAddr::new(IpAddr::from(self.0), MAPPED_PORT) } } @@ -140,41 +155,10 @@ impl TryFrom for NodeIdMappedAddr { } } -/// A map between [`NodeId`] and [`NodeIdMappedAddr`]. -#[derive(Debug, Clone, Default)] -pub(super) struct NodeIdAddrMap { - inner: Arc>, -} - -impl NodeIdAddrMap { - pub(super) fn get(&self, node_id: NodeId) -> NodeIdMappedAddr { - let mut inner = self.inner.lock().expect("poisoned"); - match inner.node_addrs.get(&node_id) { - Some(addr) => *addr, - None => { - let addr = NodeIdMappedAddr::generate(); - inner.lookup.insert(addr, node_id); - addr - } - } - } - - pub(super) fn lookup(&self, addr: NodeIdMappedAddr) -> Option { - let inner = self.inner.lock().expect("poisoned"); - inner.lookup.get(&addr).copied() - } -} - -#[derive(Debug, Default)] -struct NodeIdAddrMapInner { - node_addrs: HashMap, - lookup: HashMap, -} - /// Can occur when converting a [`SocketAddr`] to an [`NodeIdMappedAddr`] #[derive(Debug, Snafu)] #[snafu(display("Failed to convert"))] -pub struct NodeIdMappedAddrError; +pub(crate) struct NodeIdMappedAddrError; /// An Ipv6 ULA address, identifying a relay path for a [`NodeId`]. /// @@ -185,12 +169,12 @@ pub struct NodeIdMappedAddrError; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] pub(crate) struct RelayMappedAddr(Ipv6Addr); -impl RelayMappedAddr { +impl MappedAddr for RelayMappedAddr { /// Generates a globally unique fake UDP address. /// /// This generates a new IPv6 address in the Unique Local Address range (RFC 4193) /// which is recognised by iroh as an IP mapped address. - pub(crate) fn generate() -> Self { + fn generate() -> Self { let mut addr = [0u8; 16]; addr[0] = ADDR_PREFIXL; addr[1..6].copy_from_slice(&ADDR_GLOBAL_ID); @@ -209,7 +193,7 @@ impl RelayMappedAddr { /// This uses a made-up, but fixed port number. The [`RelayAddrMap`] creates a unique /// [`RelayMappedAddr`] for each `(NodeId, RelayUrl)` pair and thus does not use the /// port to map back to the original [`SocketAddr`]. - pub(crate) fn private_socket_addr(&self) -> SocketAddr { + fn private_socket_addr(&self) -> SocketAddr { SocketAddr::new(IpAddr::from(self.0), MAPPED_PORT) } } @@ -229,62 +213,65 @@ impl TryFrom for RelayMappedAddr { } } +/// Can occur when converting a [`SocketAddr`] to an [`RelayMappedAddr`] +#[derive(Debug, Snafu)] +#[snafu(display("Failed to convert"))] +pub(crate) struct RelayMappedAddrError; + impl std::fmt::Display for RelayMappedAddr { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "RelayMappedAddr({})", self.0) } } -/// Can occur when converting a [`SocketAddr`] to an [`RelayMappedAddr`] -#[derive(Debug, Snafu)] -#[snafu(display("Failed to convert"))] -pub struct RelayMappedAddrError; - -/// A Map of [`RelayMappedAddr`] to `(RelayUrl, NodeId)`. -// TODO: this could be an RwLock, or even an dashmap -#[derive(Debug, Clone, Default)] -pub(crate) struct RelayAddrMap(Arc>); - -#[derive(Debug, Default)] -pub(super) struct Inner { - by_mapped_addr: BTreeMap, - by_url: BTreeMap<(RelayUrl, NodeId), RelayMappedAddr>, +/// A bi-directional map between a key and a [`MappedAddr`]. +#[derive(Debug, Clone)] +pub(super) struct AddrMap { + inner: Arc>>, } -impl RelayAddrMap { - /// Adds a new entry to the map and returns the generated [`RelayMappedAddr`]. - /// - /// If this `(RelayUrl, NodeId)` already exists in the map, it returns its associated - /// [`RelayMappedAddr`]. - /// - /// Otherwise a new [`RelayMappedAddr`] is generated for it and returned. - pub(super) fn get_or_register(&self, relay: RelayUrl, node: NodeId) -> RelayMappedAddr { - let mut inner = self.0.lock().expect("poisoned"); - if let Some(mapped_addr) = inner.by_url.get(&(relay.clone(), node)) { - return *mapped_addr; +// Manual impl because derive ends up requiring T: Default. +impl Default for AddrMap { + fn default() -> Self { + Self { + inner: Default::default(), } - let ip_mapped_addr = RelayMappedAddr::generate(); - inner - .by_mapped_addr - .insert(ip_mapped_addr, (relay.clone(), node)); - inner.by_url.insert((relay, node), ip_mapped_addr); - ip_mapped_addr } +} - /// Returns the [`RelayMappedAddr`] for the given [`RelayUrl`] and [`NodeId`]. - pub(crate) fn get_mapped_addr(&self, relay: RelayUrl, node: NodeId) -> Option { - let inner = self.0.lock().expect("poisoned"); - inner.by_url.get(&(relay, node)).copied() +impl AddrMap { + /// Returns the [`MappedAddr`], generating one if needed. + pub(super) fn get(&self, key: &K) -> V { + let mut inner = self.inner.lock().expect("poisoned"); + match inner.addrs.get(&key) { + Some(addr) => *addr, + None => { + let addr = V::generate(); + inner.lookup.insert(addr, key.clone()); + addr + } + } } - /// Returns the [`RelayUrl`] and [`NodeId`] for the given [`RelayMappedAddr`]. - pub(crate) fn get_url(&self, mapped_addr: &RelayMappedAddr) -> Option<(RelayUrl, NodeId)> { - let inner = self.0.lock().expect("poisoned"); - inner.by_mapped_addr.get(mapped_addr).cloned() + /// Performs the reverse lookup. + pub(super) fn lookup(&self, addr: &V) -> Option { + let inner = self.inner.lock().expect("poisoned"); + inner.lookup.get(addr).cloned() } } -/// Can occur when converting a [`SocketAddr`] to an [`RelayMappedAddr`] -#[derive(Debug, Snafu)] -#[snafu(display("Failed to convert"))] -pub struct RelayAddrMapError; +#[derive(Debug)] +struct AddrMapInner { + addrs: HashMap, + lookup: HashMap, +} + +// Manual impl because derive ends up requiring T: Default. +impl Default for AddrMapInner { + fn default() -> Self { + Self { + addrs: Default::default(), + lookup: Default::default(), + } + } +} diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index 6fed669912f..d773586d54e 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -1,4 +1,3 @@ -#[cfg(any(test, feature = "test-utils"))] use std::sync::Arc; use std::{ collections::{BTreeSet, HashMap, hash_map::Entry}, @@ -15,13 +14,14 @@ use tokio::sync::mpsc; use tracing::{debug, info, instrument, trace, warn}; use self::node_state::{NodeState, Options}; +use super::mapped_addrs::{AddrMap, RelayMappedAddr}; #[cfg(any(test, feature = "test-utils"))] use super::transports::TransportsSender; #[cfg(not(any(test, feature = "test-utils")))] use super::transports::TransportsSender; use super::{ MagicsockMetrics, - mapped_addrs::{NodeIdAddrMap, NodeIdMappedAddr}, + mapped_addrs::NodeIdMappedAddr, transports::{self, OwnedTransmit}, }; use crate::disco::CallMeMaybe; @@ -62,7 +62,9 @@ const MAX_INACTIVE_NODES: usize = 30; pub(super) struct NodeMap { inner: Mutex, /// The mapping between [`NodeId`]s and [`NodeIdMappedAddr`]s. - pub(super) node_mapped_addrs: NodeIdAddrMap, + pub(super) node_mapped_addrs: AddrMap, + /// The mapping between nodes via a relay and their [`RelayMappedAddr`]s. + pub(super) relay_mapped_addrs: AddrMap<(RelayUrl, NodeId), RelayMappedAddr>, } #[derive(Debug)] @@ -173,6 +175,7 @@ impl NodeMap { Self { inner: Mutex::new(inner), node_mapped_addrs: Default::default(), + relay_mapped_addrs: Default::default(), } } diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index 98cf7b2e282..970284018b2 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -21,7 +21,7 @@ use crate::{ disco::{self, SendAddr}, magicsock::{ HEARTBEAT_INTERVAL, MagicsockMetrics, - mapped_addrs::NodeIdMappedAddr, + mapped_addrs::{MappedAddr, NodeIdMappedAddr}, node_map::path_validity::PathValidity, transports::{self, OwnedTransmit}, }, diff --git a/iroh/src/magicsock/transports.rs b/iroh/src/magicsock/transports.rs index a66ce7555c8..cdfe8c068fc 100644 --- a/iroh/src/magicsock/transports.rs +++ b/iroh/src/magicsock/transports.rs @@ -666,7 +666,7 @@ impl quinn::UdpSender for MagicSender { // TODO: Would be nicer to log the NodeId of this, but we only get an actor // sender for it. tracing::Span::current().record("dst", tracing::field::debug(&mapped_addr)); - let Some(node_id) = self.msock.node_map.node_mapped_addrs.lookup(mapped_addr) + let Some(node_id) = self.msock.node_map.node_mapped_addrs.lookup(&mapped_addr) else { error!("unknown NodeIdMappedAddr, dropped transmit"); return Poll::Ready(Ok(())); @@ -698,7 +698,12 @@ impl quinn::UdpSender for MagicSender { }; } MultipathMappedAddr::Relay(relay_mapped_addr) => { - match self.msock.relay_mapped_addrs.get_url(&relay_mapped_addr) { + match self + .msock + .node_map + .relay_mapped_addrs + .lookup(&relay_mapped_addr) + { Some((relay_url, node_id)) => Addr::Relay(relay_url, node_id), None => { error!("unknown RelayMappedAddr, dropped transmit"); @@ -758,7 +763,7 @@ impl quinn::UdpSender for MagicSender { // TODO: Would be nicer to log the NodeId of this, but we only get an actor // sender for it. tracing::Span::current().record("dst", tracing::field::debug(&mapped_addr)); - let Some(node_id) = self.msock.node_map.node_mapped_addrs.lookup(mapped_addr) + let Some(node_id) = self.msock.node_map.node_mapped_addrs.lookup(&mapped_addr) else { error!("unknown NodeIdMappedAddr, dropped transmit"); return Err(io::Error::new( @@ -798,7 +803,12 @@ impl quinn::UdpSender for MagicSender { }; } MultipathMappedAddr::Relay(relay_mapped_addr) => { - match self.msock.relay_mapped_addrs.get_url(&relay_mapped_addr) { + match self + .msock + .node_map + .relay_mapped_addrs + .lookup(&relay_mapped_addr) + { Some((relay_url, node_id)) => Addr::Relay(relay_url, node_id), None => { error!("unknown RelayMappedAddr, dropped transmit"); From 599f25db91eabb1fef9b8b6680e58d53050565fb Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Wed, 17 Sep 2025 16:03:18 +0200 Subject: [PATCH 33/44] start implementing AddConnection --- iroh/src/magicsock/node_map/node_state.rs | 25 ++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index 970284018b2..96be833a826 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -6,6 +6,7 @@ use std::{ use iroh_base::{NodeAddr, NodeId, PublicKey, RelayUrl}; use n0_future::{ + MergeUnbounded, task::AbortOnDropHandle, time::{Duration, Instant}, }; @@ -13,6 +14,7 @@ use n0_watcher::Watchable; use quinn::WeakConnectionHandle; use serde::{Deserialize, Serialize}; use tokio::sync::mpsc; +use tokio_stream::wrappers::BroadcastStream; use tracing::{Level, debug, event, info, instrument, trace, warn}; #[cfg(any(test, feature = "test-utils"))] @@ -712,6 +714,13 @@ pub(super) struct NodeStateActor { transports_sender: mpsc::Sender, // TODO: Turn this into a WeakConnectionHandle connections: Vec, + // TODO: Do we need to know which event comes from which connection? We could store + // connections in an FxHashMap using a u64 counter as index and map event streams to + // this index if so. Events only come with a PathId, so to know which actual path + // this refers to we need to know more. + path_events: MergeUnbounded>, + // TODO: We probably need some indexes from (Connection, PathId) pairs to + // transports::Addr. paths: BTreeMap, metrics: Arc, } @@ -726,6 +735,7 @@ impl NodeStateActor { node_id, transports_sender, connections: Vec::new(), + path_events: Default::default(), paths: BTreeMap::new(), metrics, } @@ -753,8 +763,17 @@ impl NodeStateActor { loop { if let Some(msg) = inbox.recv().await { match msg { - NodeStateMessage::SendDatagram(transmit) => todo!(), - NodeStateMessage::AddConnection(handle) => todo!(), + NodeStateMessage::SendDatagram(transmit) => { + // - do we have a currently selected path? + // - if not initiate holepunching + // - and then send along all paths + todo!(); + } + NodeStateMessage::AddConnection(handle) => { + let events = BroadcastStream::new(handle.path_events()); + self.path_events.push(events); + self.connections.push(handle); + } NodeStateMessage::PingReceived => todo!(), } } else { @@ -781,7 +800,7 @@ pub(crate) enum NodeStateMessage { /// The connection will now be managed by this actor. Holepunching will happen when /// needed, any new paths discovered via holepunching will be added. And closed paths /// will be removed etc. - AddConnection(WeakConnectionHandle), + AddConnection(quinn::Connection), // TODO: Add the transaction ID. PingReceived, } From 2417e6b0b29f5ccd7a431db25223a78ad62db11f Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Thu, 18 Sep 2025 13:33:35 +0200 Subject: [PATCH 34/44] make add_node_addr async and send it to the NodeStateActor Also cleans up some of the load_from_vec constructors between NodeMap and NodeMapInner. Some duplication deleted and the resulting constructors are simpler. --- iroh/src/discovery.rs | 4 +- iroh/src/endpoint.rs | 20 +-- iroh/src/magicsock.rs | 76 +++++++----- iroh/src/magicsock/node_map.rs | 142 ++++++---------------- iroh/src/magicsock/node_map/node_state.rs | 20 +-- 5 files changed, 112 insertions(+), 150 deletions(-) diff --git a/iroh/src/discovery.rs b/iroh/src/discovery.rs index a1907c2728b..c082cdb1488 100644 --- a/iroh/src/discovery.rs +++ b/iroh/src/discovery.rs @@ -648,7 +648,9 @@ impl DiscoveryTask { continue; } debug!(%provenance, addr = ?node_addr, "new address found"); - ep.add_node_addr_with_source(node_addr, provenance).ok(); + ep.add_node_addr_with_source(node_addr, provenance) + .await + .ok(); if let Some(tx) = on_first_tx.take() { tx.send(Ok(())).ok(); } diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index e517aec1584..21735528901 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -726,7 +726,7 @@ impl Endpoint { ensure!(node_addr.node_id != self.node_id(), SelfConnectSnafu); if !node_addr.is_empty() { - self.add_node_addr(node_addr.clone())?; + self.add_node_addr(node_addr.clone()).await?; } let node_id = node_addr.node_id; @@ -814,8 +814,9 @@ impl Endpoint { /// if the direct addresses are a subset of ours. /// /// [`StaticProvider`]: crate::discovery::static_provider::StaticProvider - pub fn add_node_addr(&self, node_addr: NodeAddr) -> Result<(), AddNodeAddrError> { + pub async fn add_node_addr(&self, node_addr: NodeAddr) -> Result<(), AddNodeAddrError> { self.add_node_addr_inner(node_addr, magicsock::Source::App) + .await } /// Informs this [`Endpoint`] about addresses of the iroh node, noting the source. @@ -837,7 +838,7 @@ impl Endpoint { /// if the direct addresses are a subset of ours. /// /// [`StaticProvider`]: crate::discovery::static_provider::StaticProvider - pub fn add_node_addr_with_source( + pub async fn add_node_addr_with_source( &self, node_addr: NodeAddr, source: &'static str, @@ -848,16 +849,17 @@ impl Endpoint { name: source.into(), }, ) + .await } - fn add_node_addr_inner( + async fn add_node_addr_inner( &self, node_addr: NodeAddr, source: magicsock::Source, ) -> Result<(), AddNodeAddrError> { // Connecting to ourselves is not supported. snafu::ensure!(node_addr.node_id != self.node_id(), OwnAddressSnafu); - self.msock.add_node_addr(node_addr, source) + self.msock.add_node_addr(node_addr, source).await } // # Getter methods for properties of this Endpoint itself. @@ -2258,7 +2260,7 @@ mod tests { let err = res.err().unwrap(); assert!(err.to_string().starts_with("Connecting to ourself")); - let res = ep.add_node_addr(my_addr); + let res = ep.add_node_addr(my_addr).await; assert!(res.is_err()); let err = res.err().unwrap(); assert!(err.to_string().starts_with("Adding our own address")); @@ -2394,7 +2396,7 @@ mod tests { // information for a peer let endpoint = new_endpoint(secret_key.clone(), None).await?; assert_eq!(endpoint.remote_info_iter().count(), 0); - endpoint.add_node_addr(node_addr.clone())?; + endpoint.add_node_addr(node_addr.clone()).await?; // Grab the current addrs let node_addrs: Vec = endpoint.remote_info_iter().map(Into::into).collect(); @@ -2589,8 +2591,8 @@ mod tests { let ep1_nodeaddr = ep1.node_addr().initialized().await; let ep2_nodeaddr = ep2.node_addr().initialized().await; - ep1.add_node_addr(ep2_nodeaddr.clone())?; - ep2.add_node_addr(ep1_nodeaddr.clone())?; + ep1.add_node_addr(ep2_nodeaddr.clone()).await?; + ep2.add_node_addr(ep1_nodeaddr.clone()).await?; let ep1_nodeid = ep1.node_id(); let ep2_nodeid = ep2.node_id(); eprintln!("node id 1 {ep1_nodeid}"); diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index cf9da756afe..6d8847142a4 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -442,9 +442,13 @@ impl MagicSock { self.node_map.get_direct_addrs(node_id) } - /// Add addresses for a node to the magic socket's addresbook. + /// Add potential addresses for a node to the [`NodeState`]. + /// + /// This is used to add possible paths that the remote node might be reachable on. They + /// will be used when there is no active connection to the node to attempt to establish + /// a connection. #[instrument(skip_all)] - pub fn add_node_addr( + pub(crate) async fn add_node_addr( &self, mut addr: NodeAddr, source: node_map::Source, @@ -452,21 +456,25 @@ impl MagicSock { let mut pruned: usize = 0; for my_addr in self.direct_addrs.sockaddrs() { if addr.direct_addresses.remove(&my_addr) { - warn!( node_id=addr.node_id.fmt_short(), %my_addr, %source, "not adding our addr for node"); + warn!( + node_id = addr.node_id.fmt_short(), + %my_addr, + %source, + "not adding our addr for node", + ); pruned += 1; } } if !addr.is_empty() { // Add addr to the internal NodeMap - self.node_map - .add_node_addr(addr.clone(), source, &self.metrics.magicsock); + self.node_map.add_node_addr(addr.clone(), source).await; if let Some(url) = addr.relay_url().cloned() { self.node_map.relay_mapped_addrs.get(&(url, addr.node_id)); } - // Add paths to the existing connections - self.add_paths(addr); + // // Add paths to the existing connections + // self.add_paths(addr); Ok(()) } else if pruned != 0 { @@ -1210,9 +1218,10 @@ impl Handle { let sender = transports.create_sender(); #[cfg(any(test, feature = "test-utils"))] let nm = - NodeMap::load_from_vec(node_map, path_selection, metrics.magicsock.clone(), sender); + NodeMap::load_from_vec(node_map, path_selection, metrics.magicsock.clone(), sender) + .await; #[cfg(not(any(test, feature = "test-utils")))] - let nm = NodeMap::load_from_vec(node_map, metrics.magicsock.clone(), sender); + let nm = NodeMap::load_from_vec(node_map, metrics.magicsock.clone(), sender).await; nm }; // let node_map = node_map.unwrap_or_default(); @@ -1758,7 +1767,7 @@ impl Actor { node_addr, Source::Discovery { name: provenance.to_string() - }) { + }).await { let node_addr = discovery_item.to_node_addr(); warn!(?node_addr, "unable to add discovered node address to the node map: {e:?}"); } @@ -2253,14 +2262,15 @@ mod tests { impl MagicSock { #[track_caller] - pub fn add_test_addr(&self, node_addr: NodeAddr) { + pub async fn add_test_addr(&self, node_addr: NodeAddr) { self.add_node_addr( node_addr, Source::NamedApp { name: "test".into(), }, ) - .unwrap() + .await + .ok(); } } @@ -2318,7 +2328,7 @@ mod tests { #[instrument(skip_all)] async fn mesh_stacks(stacks: Vec) -> Result> { /// Registers endpoint addresses of a node to all other nodes. - fn update_direct_addrs( + async fn update_direct_addrs( stacks: &[MagicStack], my_idx: usize, new_addrs: BTreeSet, @@ -2334,7 +2344,7 @@ mod tests { relay_url: None, direct_addresses: new_addrs.iter().map(|ep| ep.addr).collect(), }; - m.endpoint.magic_sock().add_test_addr(addr); + m.endpoint.magic_sock().add_test_addr(addr).await; } } @@ -2349,7 +2359,7 @@ mod tests { let mut stream = m.endpoint.direct_addresses().stream().filter_map(|i| i); while let Some(new_eps) = stream.next().await { info!(%me, "conn{} endpoints update: {:?}", my_idx + 1, new_eps); - update_direct_addrs(&stacks, my_idx, new_eps); + update_direct_addrs(&stacks, my_idx, new_eps).await; } }); } @@ -2909,6 +2919,7 @@ mod tests { name: "test".into(), }, ) + .await .unwrap(); let addr = msock_1.get_mapping_addr(node_id_2).unwrap(); let res = tokio::time::timeout( @@ -2963,17 +2974,19 @@ mod tests { let _accept_task = AbortOnDropHandle::new(accept_task); // Add an empty entry in the NodeMap of ep_1 - msock_1.node_map.add_node_addr( - NodeAddr { - node_id: node_id_2, - relay_url: None, - direct_addresses: Default::default(), - }, - Source::NamedApp { - name: "test".into(), - }, - &msock_1.metrics.magicsock, - ); + msock_1 + .node_map + .add_node_addr( + NodeAddr { + node_id: node_id_2, + relay_url: None, + direct_addresses: Default::default(), + }, + Source::NamedApp { + name: "test".into(), + }, + ) + .await; let addr_2 = msock_1.get_mapping_addr(node_id_2).unwrap(); @@ -3018,6 +3031,7 @@ mod tests { name: "test".into(), }, ) + .await .unwrap(); // We can now connect @@ -3058,6 +3072,7 @@ mod tests { .endpoint .magic_sock() .add_node_addr(empty_addr, node_map::Source::App) + .await .unwrap_err(); assert!( err.to_string() @@ -3074,7 +3089,8 @@ mod tests { stack .endpoint .magic_sock() - .add_node_addr(addr, node_map::Source::App)?; + .add_node_addr(addr, node_map::Source::App) + .await?; assert_eq!(stack.endpoint.magic_sock().node_map.node_count(), 1); // addrs only @@ -3086,7 +3102,8 @@ mod tests { stack .endpoint .magic_sock() - .add_node_addr(addr, node_map::Source::App)?; + .add_node_addr(addr, node_map::Source::App) + .await?; assert_eq!(stack.endpoint.magic_sock().node_map.node_count(), 2); // both @@ -3098,7 +3115,8 @@ mod tests { stack .endpoint .magic_sock() - .add_node_addr(addr, node_map::Source::App)?; + .add_node_addr(addr, node_map::Source::App) + .await?; assert_eq!(stack.endpoint.magic_sock().node_map.node_count(), 3); Ok(()) diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index d773586d54e..8ada4dc3838 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -147,28 +147,33 @@ impl NodeMap { #[cfg(not(any(test, feature = "test-utils")))] /// Create a new [`NodeMap`] from a list of [`NodeAddr`]s. - pub(super) fn load_from_vec( + pub(super) async fn load_from_vec( nodes: Vec, metrics: Arc, sender: TransportsSender, ) -> Self { - Self::from_inner(NodeMapInner::load_from_vec(nodes, metrics, sender)) + let me = Self::from_inner(NodeMapInner::new(metrics, sender)); + for addr in nodes { + me.add_node_addr(addr, Source::Saved).await; + } + me } #[cfg(any(test, feature = "test-utils"))] /// Create a new [`NodeMap`] from a list of [`NodeAddr`]s. - pub(super) fn load_from_vec( + pub(super) async fn load_from_vec( nodes: Vec, path_selection: PathSelection, metrics: Arc, sender: TransportsSender, ) -> Self { - Self::from_inner(NodeMapInner::load_from_vec( - nodes, - path_selection, - metrics, - sender, - )) + let mut inner = NodeMapInner::new(metrics, sender); + inner.path_selection = path_selection; + let me = Self::from_inner(inner); + for addr in nodes { + me.add_node_addr(addr, Source::Saved).await; + } + me } fn from_inner(inner: NodeMapInner) -> Self { @@ -179,17 +184,21 @@ impl NodeMap { } } - /// Add the contact information for a node. - pub(super) fn add_node_addr( - &self, - node_addr: NodeAddr, - source: Source, - metrics: &MagicsockMetrics, - ) { - self.inner - .lock() - .expect("poisoned") - .add_node_addr(node_addr, source, metrics) + /// Adds addresses where a node might be contactable. + pub(super) async fn add_node_addr(&self, node_addr: NodeAddr, source: Source) { + if let Some(ref relay_url) = node_addr.relay_url { + // Ensure we have a RelayMappedAddress for this. + self.relay_mapped_addrs + .get(&(relay_url.clone(), node_addr.node_id)); + } + let node_state = self.node_state_actor(node_addr.node_id); + + // This only fails if the sender is closed. That means the NodeStateActor has + // stopped, which only happens during shutdown. + node_state + .send(NodeStateMessage::AddNodeAddr(node_addr, source)) + .await + .ok(); } /// Number of nodes currently listed. @@ -328,18 +337,24 @@ impl NodeMap { /// Returns the sender for the [`NodeStateActor`]. /// + /// If needed a new actor is started on demand. + /// /// [`NodeStateActor`]: node_state::NodeStateActor pub(super) fn node_state_actor(&self, node_id: NodeId) -> mpsc::Sender { let mut inner = self.inner.lock().expect("poisoned"); match inner.node_states.get(&node_id) { Some(handle) => handle.sender.clone(), None => { + // Create a new NodeStateActor and insert it into the node map. let sender = inner.transports_handle.inbox.clone(); let metrics = inner.metrics.clone(); let actor = NodeStateActor::new(node_id, sender, metrics); let handle = actor.start(); let sender = handle.sender.clone(); inner.node_states.insert(node_id, handle); + + // Ensure there is a NodeMappedAddr for this NodeId. + self.node_mapped_addrs.get(&node_id); sender } } @@ -347,7 +362,6 @@ impl NodeMap { } impl NodeMapInner { - #[cfg(any(test, feature = "test-utils"))] fn new(metrics: Arc, sender: TransportsSender) -> Self { let transports_handle = Self::start_transports_sender(sender); Self { @@ -358,95 +372,17 @@ impl NodeMapInner { by_quic_mapped_addr: Default::default(), by_id: Default::default(), next_id: 0, + #[cfg(any(test, feature = "test-utils"))] path_selection: Default::default(), node_states: Default::default(), } } - /// Creates a new [`NodeMap`] from a list of [`NodeAddr`]s. - #[cfg(not(any(test, feature = "test-utils")))] - fn load_from_vec( - nodes: Vec, - metrics: Arc, - sender: TransportsSender, - ) -> Self { - let transports_handle = Self::start_transports_sender(sender); - let mut me = Self { - metrics: metrics.clone(), - transports_handle, - by_node_key: Default::default(), - by_ip_port: Default::default(), - by_quic_mapped_addr: Default::default(), - by_id: Default::default(), - next_id: 0, - node_states: Default::default(), - }; - for node_addr in nodes { - me.add_node_addr(node_addr, Source::Saved, &metrics); - } - me - } - - /// Creates a new [`NodeMap`] from a list of [`NodeAddr`]s. - #[cfg(any(test, feature = "test-utils"))] - fn load_from_vec( - nodes: Vec, - path_selection: PathSelection, - metrics: Arc, - sender: TransportsSender, - ) -> Self { - let transports_handle = Self::start_transports_sender(sender); - let mut me = Self { - metrics: metrics.clone(), - transports_handle, - by_node_key: Default::default(), - by_ip_port: Default::default(), - by_quic_mapped_addr: Default::default(), - by_id: Default::default(), - next_id: 0, - path_selection, - node_states: Default::default(), - }; - for node_addr in nodes { - me.add_node_addr(node_addr, Source::Saved, &metrics); - } - me - } - fn start_transports_sender(sender: TransportsSender) -> TransportsSenderHandle { let actor = TransportsSenderActor::new(sender); actor.start() } - /// Add the contact information for a node. - #[instrument(skip_all, fields(node = %node_addr.node_id.fmt_short()))] - fn add_node_addr(&mut self, node_addr: NodeAddr, source: Source, metrics: &MagicsockMetrics) { - // TODO: Add to the NodeStateActor here. - let source0 = source.clone(); - let node_id = node_addr.node_id; - let relay_url = node_addr.relay_url.clone(); - #[cfg(any(test, feature = "test-utils"))] - let path_selection = self.path_selection; - let node_state = self.get_or_insert_with(NodeStateKey::NodeId(node_id), || Options { - node_id, - relay_url, - active: false, - source, - #[cfg(any(test, feature = "test-utils"))] - path_selection, - }); - node_state.update_from_node_addr( - node_addr.relay_url.as_ref(), - &node_addr.direct_addresses, - source0, - metrics, - ); - let id = node_state.id(); - for addr in node_addr.direct_addresses() { - self.set_node_state_for_ip_port(*addr, id); - } - } - /// Prunes direct addresses from nodes that claim to share an address we know points to us. pub(super) fn on_direct_addr_discovered( &mut self, @@ -821,8 +757,7 @@ mod tests { Source::NamedApp { name: "test".into(), }, - &Default::default(), - ) + ); } } @@ -873,7 +808,8 @@ mod tests { PathSelection::default(), Default::default(), transports.create_sender(), - ); + ) + .await; let mut loaded: Vec = loaded_node_map .list_remote_infos(Instant::now()) diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index 96be833a826..aa8bebf1778 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -712,8 +712,7 @@ pub(super) struct NodeStateActor { /// The node ID of the remote node. node_id: NodeId, transports_sender: mpsc::Sender, - // TODO: Turn this into a WeakConnectionHandle - connections: Vec, + connections: Vec, // TODO: Do we need to know which event comes from which connection? We could store // connections in an FxHashMap using a u64 counter as index and map event streams to // this index if so. Events only come with a PathId, so to know which actual path @@ -770,11 +769,14 @@ impl NodeStateActor { todo!(); } NodeStateMessage::AddConnection(handle) => { - let events = BroadcastStream::new(handle.path_events()); - self.path_events.push(events); - self.connections.push(handle); + if let Some(conn) = handle.upgrade() { + let events = BroadcastStream::new(conn.path_events()); + self.path_events.push(events); + self.connections.push(handle); + } } NodeStateMessage::PingReceived => todo!(), + NodeStateMessage::AddNodeAddr(addr, source) => todo!(), } } else { break; @@ -786,7 +788,7 @@ impl NodeStateActor { /// Messages to send to the [`NodeStateActor`]. pub(crate) enum NodeStateMessage { - /// Send a datagram to all known paths. + /// Sends a datagram to all known paths. /// /// Used to send QUIC Initial packets. If there is no working direct path this will /// trigger holepunching. @@ -795,14 +797,16 @@ pub(crate) enum NodeStateMessage { /// operation with a bunch more copying. So it should only be used for sending QUIC /// Initial packets. SendDatagram(OwnedTransmit), - /// Add an active connection to this remote node. + /// Adds an active connection to this remote node. /// /// The connection will now be managed by this actor. Holepunching will happen when /// needed, any new paths discovered via holepunching will be added. And closed paths /// will be removed etc. - AddConnection(quinn::Connection), + AddConnection(WeakConnectionHandle), // TODO: Add the transaction ID. PingReceived, + /// Adds a [`NodeAddr`] with locations where the node might be reachable. + AddNodeAddr(NodeAddr, Source), } /// A handle to a [`NodeStateActor`]. From 9820e60880b3675d8fed5c8fa46912c7e9c0c317 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Thu, 18 Sep 2025 16:24:48 +0200 Subject: [PATCH 35/44] start handling AddNodeAddr message --- iroh/src/magicsock/node_map/node_state.rs | 21 +++++++++++++++------ iroh/src/magicsock/node_map/path_state.rs | 9 +++++++++ iroh/src/magicsock/transports.rs | 2 +- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index aa8bebf1778..a0d96e0bdfb 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -31,7 +31,7 @@ use crate::{ use super::{ IpPort, Source, TransportsSenderMessage, - path_state::{PathState, summarize_node_paths}, + path_state::{NewPathState, PathState, summarize_node_paths}, udp_paths::{NodeUdpPaths, UdpSendAddr}, }; @@ -776,7 +776,20 @@ impl NodeStateActor { } } NodeStateMessage::PingReceived => todo!(), - NodeStateMessage::AddNodeAddr(addr, source) => todo!(), + NodeStateMessage::AddNodeAddr(node_addr, source) => { + for sockaddr in node_addr.direct_addresses { + let addr = transports::Addr::from(sockaddr); + let path = self.paths.entry(addr).or_default(); + path.sources.insert(source.clone(), Instant::now()); + } + if let Some(relay_url) = node_addr.relay_url { + let addr = transports::Addr::from((relay_url, self.node_id)); + let path = self.paths.entry(addr).or_default(); + path.sources.insert(source, Instant::now()); + } + // TODO: Now check if we need to start holepunching or something for + // any existing connections. + } } } else { break; @@ -818,10 +831,6 @@ pub(super) struct NodeStateHandle { _task: AbortOnDropHandle<()>, } -struct NewPathState { - addr: transports::Addr, -} - impl From for NodeAddr { fn from(info: RemoteInfo) -> Self { let direct_addresses = info diff --git a/iroh/src/magicsock/node_map/path_state.rs b/iroh/src/magicsock/node_map/path_state.rs index 7fd4fc7fe78..93a07a699ae 100644 --- a/iroh/src/magicsock/node_map/path_state.rs +++ b/iroh/src/magicsock/node_map/path_state.rs @@ -190,3 +190,12 @@ pub(super) fn summarize_node_paths(paths: &BTreeMap) -> Strin write!(&mut w, "]").ok(); w } + +#[derive(Debug, Default)] +pub(super) struct NewPathState { + /// How we learned about this path, and when. + /// + /// 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, +} diff --git a/iroh/src/magicsock/transports.rs b/iroh/src/magicsock/transports.rs index cdfe8c068fc..b2d718f8c8e 100644 --- a/iroh/src/magicsock/transports.rs +++ b/iroh/src/magicsock/transports.rs @@ -331,7 +331,7 @@ impl From<&quinn_udp::Transmit<'_>> for OwnedTransmit { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum Addr { Ip(SocketAddr), Relay(RelayUrl, NodeId), From ef7a6f30e4e30736a381995e238b46ab442032f3 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Thu, 18 Sep 2025 17:46:30 +0200 Subject: [PATCH 36/44] start sending datagrams from the actor --- iroh/src/magicsock.rs | 1 - iroh/src/magicsock/node_map.rs | 6 ++ iroh/src/magicsock/node_map/node_state.rs | 84 ++++++++++++++++++----- iroh/src/magicsock/transports.rs | 2 +- 4 files changed, 74 insertions(+), 19 deletions(-) diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 6d8847142a4..d04b67eb44d 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -2261,7 +2261,6 @@ mod tests { } impl MagicSock { - #[track_caller] pub async fn add_test_addr(&self, node_addr: NodeAddr) { self.add_node_addr( node_addr, diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index 8ada4dc3838..0d595ef91ab 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -737,6 +737,12 @@ enum TransportsSenderMessage { SendDatagram(transports::Addr, OwnedTransmit), } +impl From<(transports::Addr, OwnedTransmit)> for TransportsSenderMessage { + fn from(source: (transports::Addr, OwnedTransmit)) -> Self { + Self::SendDatagram(source.0, source.1) + } +} + #[cfg(test)] mod tests { use std::net::Ipv4Addr; diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index a0d96e0bdfb..4f7f14076da 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -13,9 +13,10 @@ use n0_future::{ use n0_watcher::Watchable; use quinn::WeakConnectionHandle; use serde::{Deserialize, Serialize}; +use snafu::{ResultExt, Whatever}; use tokio::sync::mpsc; use tokio_stream::wrappers::BroadcastStream; -use tracing::{Level, debug, event, info, instrument, trace, warn}; +use tracing::{Instrument, Level, debug, error, event, info, info_span, instrument, trace, warn}; #[cfg(any(test, feature = "test-utils"))] use crate::endpoint::PathSelection; @@ -711,16 +712,32 @@ impl NodeState { pub(super) struct NodeStateActor { /// The node ID of the remote node. node_id: NodeId, + /// Allowing us to directly send datagrams. + /// + /// Used for handling [`NodeStateMessage::SendDatagram`] messages. transports_sender: mpsc::Sender, + /// All connections we have to this remote node. connections: Vec, + /// Events emitted by Quinn about path changes. // TODO: Do we need to know which event comes from which connection? We could store // connections in an FxHashMap using a u64 counter as index and map event streams to // this index if so. Events only come with a PathId, so to know which actual path // this refers to we need to know more. path_events: MergeUnbounded>, + /// All possible paths we are aware of. + /// + /// These paths might be entirely impossible to use, since they are added by discovery + /// mechanisms. The are only potentially usable. // TODO: We probably need some indexes from (Connection, PathId) pairs to // transports::Addr. paths: BTreeMap, + /// The path we currently consider the preferred path to the remote node. + /// + /// **We expect this path to work.** If we become aware this path is broken then it is + /// set back to `None`. Having a selected path does not mean we may not be able to get + /// a better path: e.g. when the selected path is a relay path we still need to trigger + /// holepunching regularly. + selected_path: Option, metrics: Arc, } @@ -736,6 +753,7 @@ impl NodeStateActor { connections: Vec::new(), path_events: Default::default(), paths: BTreeMap::new(), + selected_path: None, metrics, } } @@ -743,30 +761,42 @@ impl NodeStateActor { pub(super) fn start(mut self) -> NodeStateHandle { let (tx, rx) = mpsc::channel(16); - // No .instrument() on the task, run method has an #[instrument] attribute. - let task = tokio::spawn(async move { - self.run(rx).await; - }); + let task = tokio::spawn( + async move { + if let Err(err) = self.run(rx).await { + error!("actor failed: {err:#}"); + } + } + .instrument(info_span!("NodeStateActor")), + ); NodeStateHandle { sender: tx, _task: AbortOnDropHandle::new(task), } } - #[instrument( - name = "NodeStateActor", - skip_all, - fields(node_id = %self.node_id.fmt_short()) - )] - async fn run(&mut self, mut inbox: mpsc::Receiver) { + #[instrument(skip_all, fields(node_id = %self.node_id.fmt_short()))] + async fn run(&mut self, mut inbox: mpsc::Receiver) -> Result<(), Whatever> { loop { if let Some(msg) = inbox.recv().await { match msg { NodeStateMessage::SendDatagram(transmit) => { - // - do we have a currently selected path? - // - if not initiate holepunching - // - and then send along all paths - todo!(); + if let Some(ref addr) = self.selected_path { + self.transports_sender + .send((addr.clone(), transmit).into()) + .await + .whatever_context("TransportSenderActor stopped")?; + } else { + for addr in self.paths.keys() { + self.transports_sender + .send((addr.clone(), transmit.clone()).into()) + .await + .whatever_context("TransportSenerActor stopped")?; + if addr.is_relay() { + self.call_me_maybe(addr.clone()); + } + } + } } NodeStateMessage::AddConnection(handle) => { if let Some(conn) = handle.upgrade() { @@ -787,8 +817,6 @@ impl NodeStateActor { let path = self.paths.entry(addr).or_default(); path.sources.insert(source, Instant::now()); } - // TODO: Now check if we need to start holepunching or something for - // any existing connections. } } } else { @@ -796,6 +824,28 @@ impl NodeStateActor { } } trace!("actor terminating"); + Ok(()) + } + + /// Tries to select a new path out of the available ones. + /// + /// We only want to accept any path which we know can be used. If we do not know of any + /// working paths we must set it to `None`. + fn select_path(&mut self) { + // TODO + self.selected_path = None; + } + + /// Sends a call-me-maybe message to the remote node. + /// + /// This will first send pings to any paths the remote advertised in their own + /// call-me-maybe message. + /// + /// If a call-me-maybe message was recently sent it will instead schedule a new + /// call-me-maybe after a delay if by then it is still needed. + fn call_me_maybe(&mut self, dst: transports::Addr) { + debug_assert!(dst.is_relay(), "must send call-me-maybe via a relay server"); + todo!() } } diff --git a/iroh/src/magicsock/transports.rs b/iroh/src/magicsock/transports.rs index b2d718f8c8e..968347f3ca4 100644 --- a/iroh/src/magicsock/transports.rs +++ b/iroh/src/magicsock/transports.rs @@ -314,7 +314,7 @@ pub(crate) struct Transmit<'a> { } /// An outgoing packet that can be sent across channels. -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) struct OwnedTransmit { pub(crate) ecn: Option, pub(crate) contents: Bytes, From 76f376838d90a5ac3debcfc1ffda8993f799cd23 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Tue, 23 Sep 2025 12:27:49 +0200 Subject: [PATCH 37/44] Start adding state for holepunching decisions This plugs through the information we need from the magic socket via some watchers. --- iroh/src/magicsock.rs | 28 ++++-- iroh/src/magicsock/node_map.rs | 51 ++++++++-- iroh/src/magicsock/node_map/node_state.rs | 110 +++++++++++++++++----- 3 files changed, 148 insertions(+), 41 deletions(-) diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index d04b67eb44d..e66159f1dfa 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -1213,22 +1213,30 @@ impl Handle { #[cfg(wasm_browser)] let transports = Transports::new(relay_transports, max_receive_segments); + let direct_addrs = DiscoveredDirectAddrs::default(); + let node_map = { let node_map = node_map.unwrap_or_default(); let sender = transports.create_sender(); #[cfg(any(test, feature = "test-utils"))] - let nm = - NodeMap::load_from_vec(node_map, path_selection, metrics.magicsock.clone(), sender) - .await; + let nm = NodeMap::load_from_vec( + node_map, + path_selection, + metrics.magicsock.clone(), + sender, + direct_addrs.addrs.watch(), + ) + .await; #[cfg(not(any(test, feature = "test-utils")))] - let nm = NodeMap::load_from_vec(node_map, metrics.magicsock.clone(), sender).await; + let nm = NodeMap::load_from_vec( + node_map, + metrics.magicsock.clone(), + sender, + direct_addrs.addrs.watch(), + ) + .await; nm }; - // let node_map = node_map.unwrap_or_default(); - // #[cfg(any(test, feature = "test-utils"))] - // let node_map = NodeMap::load_from_vec(node_map, path_selection, &metrics.magicsock); - // #[cfg(not(any(test, feature = "test-utils")))] - // let node_map = NodeMap::load_from_vec(node_map, &metrics.magicsock); let (disco, disco_receiver) = DiscoState::new(secret_encryption_key); @@ -1243,7 +1251,7 @@ impl Handle { connection_map: Default::default(), discovery, discovery_user_data: RwLock::new(discovery_user_data), - direct_addrs: Default::default(), + direct_addrs, net_report: Watchable::new((None, UpdateReason::None)), #[cfg(not(wasm_browser))] dns_resolver: dns_resolver.clone(), diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index 0d595ef91ab..d5a86d3e66f 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -14,6 +14,7 @@ use tokio::sync::mpsc; use tracing::{debug, info, instrument, trace, warn}; use self::node_state::{NodeState, Options}; +use super::DirectAddr; use super::mapped_addrs::{AddrMap, RelayMappedAddr}; #[cfg(any(test, feature = "test-utils"))] use super::transports::TransportsSender; @@ -72,6 +73,7 @@ pub(super) struct NodeMapInner { metrics: Arc, /// Handle to an actor that can send over the transports. transports_handle: TransportsSenderHandle, + local_addrs: n0_watcher::Direct>>, by_node_key: HashMap, by_ip_port: HashMap, by_quic_mapped_addr: HashMap, @@ -137,12 +139,18 @@ pub enum Source { /// The name of the application that added the node name: String, }, + /// The address was advertised by a call-me-maybe DISCO message. + CallMeMaybe, } impl NodeMap { #[cfg(any(test, feature = "test-utils"))] - pub(super) fn new(metrics: Arc, sender: TransportsSender) -> Self { - Self::from_inner(NodeMapInner::new(metrics, sender)) + pub(super) fn new( + metrics: Arc, + sender: TransportsSender, + local_addrs: n0_watcher::Direct>>, + ) -> Self { + Self::from_inner(NodeMapInner::new(metrics, sender, local_addrs)) } #[cfg(not(any(test, feature = "test-utils")))] @@ -151,8 +159,9 @@ impl NodeMap { nodes: Vec, metrics: Arc, sender: TransportsSender, + local_addrs: n0_watcher::Direct>>, ) -> Self { - let me = Self::from_inner(NodeMapInner::new(metrics, sender)); + let me = Self::from_inner(NodeMapInner::new(metrics, sender, local_addrs)); for addr in nodes { me.add_node_addr(addr, Source::Saved).await; } @@ -166,8 +175,9 @@ impl NodeMap { path_selection: PathSelection, metrics: Arc, sender: TransportsSender, + local_addrs: n0_watcher::Direct>>, ) -> Self { - let mut inner = NodeMapInner::new(metrics, sender); + let mut inner = NodeMapInner::new(metrics, sender, local_addrs); inner.path_selection = path_selection; let me = Self::from_inner(inner); for addr in nodes { @@ -347,8 +357,9 @@ impl NodeMap { None => { // Create a new NodeStateActor and insert it into the node map. let sender = inner.transports_handle.inbox.clone(); + let local_addrs = inner.local_addrs.clone(); let metrics = inner.metrics.clone(); - let actor = NodeStateActor::new(node_id, sender, metrics); + let actor = NodeStateActor::new(node_id, sender, local_addrs, metrics); let handle = actor.start(); let sender = handle.sender.clone(); inner.node_states.insert(node_id, handle); @@ -362,11 +373,16 @@ impl NodeMap { } impl NodeMapInner { - fn new(metrics: Arc, sender: TransportsSender) -> Self { + fn new( + metrics: Arc, + sender: TransportsSender, + local_addrs: n0_watcher::Direct>>, + ) -> Self { let transports_handle = Self::start_transports_sender(sender); Self { metrics, transports_handle, + local_addrs, by_node_key: Default::default(), by_ip_port: Default::default(), by_quic_mapped_addr: Default::default(), @@ -753,6 +769,7 @@ mod tests { use super::{node_state::MAX_INACTIVE_DIRECT_ADDRESSES, *}; use crate::disco::SendAddr; + use crate::magicsock::DiscoveredDirectAddrs; use crate::magicsock::transports::Transports; impl NodeMap { @@ -772,7 +789,12 @@ mod tests { #[traced_test] async fn restore_from_vec() { let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); - let node_map = NodeMap::new(Default::default(), transports.create_sender()); + let direct_addrs = DiscoveredDirectAddrs::default(); + let node_map = NodeMap::new( + Default::default(), + transports.create_sender(), + direct_addrs.addrs.watch(), + ); let mut rng = rand::thread_rng(); let node_a = SecretKey::generate(&mut rng).public(); @@ -814,6 +836,7 @@ mod tests { PathSelection::default(), Default::default(), transports.create_sender(), + direct_addrs.addrs.watch(), ) .await; @@ -844,7 +867,12 @@ mod tests { #[traced_test] fn test_prune_direct_addresses() { let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); - let node_map = NodeMap::new(Default::default(), transports.create_sender()); + let direct_addrs = DiscoveredDirectAddrs::default(); + let node_map = NodeMap::new( + Default::default(), + transports.create_sender(), + direct_addrs.addrs.watch(), + ); let public_key = SecretKey::generate(rand::thread_rng()).public(); let id = node_map .inner @@ -918,7 +946,12 @@ mod tests { #[test] fn test_prune_inactive() { let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); - let node_map = NodeMap::new(Default::default(), transports.create_sender()); + let direct_addrs = DiscoveredDirectAddrs::default(); + let node_map = NodeMap::new( + Default::default(), + transports.create_sender(), + direct_addrs.addrs.watch(), + ); // add one active node and more than MAX_INACTIVE_NODES inactive nodes let active_node = SecretKey::generate(rand::thread_rng()).public(); let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 167); diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index 4f7f14076da..7007ea34b31 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -10,7 +10,7 @@ use n0_future::{ task::AbortOnDropHandle, time::{Duration, Instant}, }; -use n0_watcher::Watchable; +use n0_watcher::{Watchable, Watcher}; use quinn::WeakConnectionHandle; use serde::{Deserialize, Serialize}; use snafu::{ResultExt, Whatever}; @@ -22,6 +22,7 @@ use tracing::{Instrument, Level, debug, error, event, info, info_span, instrumen use crate::endpoint::PathSelection; use crate::{ disco::{self, SendAddr}, + endpoint::DirectAddr, magicsock::{ HEARTBEAT_INTERVAL, MagicsockMetrics, mapped_addrs::{MappedAddr, NodeIdMappedAddr}, @@ -712,10 +713,20 @@ impl NodeState { pub(super) struct NodeStateActor { /// The node ID of the remote node. node_id: NodeId, + + // Hooks into the rest of the MagicSocket. + // + /// Metrics. + metrics: Arc, /// Allowing us to directly send datagrams. /// /// Used for handling [`NodeStateMessage::SendDatagram`] messages. transports_sender: mpsc::Sender, + /// Our local addresses. + local_addrs: n0_watcher::Direct>>, + + // Internal state - Quinn Connections we are managing. + // /// All connections we have to this remote node. connections: Vec, /// Events emitted by Quinn about path changes. @@ -724,6 +735,9 @@ pub(super) struct NodeStateActor { // this index if so. Events only come with a PathId, so to know which actual path // this refers to we need to know more. path_events: MergeUnbounded>, + + // Internal state - Holepunching and path state. + // /// All possible paths we are aware of. /// /// These paths might be entirely impossible to use, since they are added by discovery @@ -731,6 +745,8 @@ pub(super) struct NodeStateActor { // TODO: We probably need some indexes from (Connection, PathId) pairs to // transports::Addr. paths: BTreeMap, + /// Information about the last holepunching attempt. + last_holepunch: Option, /// The path we currently consider the preferred path to the remote node. /// /// **We expect this path to work.** If we become aware this path is broken then it is @@ -738,23 +754,25 @@ pub(super) struct NodeStateActor { /// a better path: e.g. when the selected path is a relay path we still need to trigger /// holepunching regularly. selected_path: Option, - metrics: Arc, } impl NodeStateActor { pub(super) fn new( node_id: NodeId, transports_sender: mpsc::Sender, + local_addrs: n0_watcher::Direct>>, metrics: Arc, ) -> Self { Self { node_id, + metrics, transports_sender, + local_addrs, connections: Vec::new(), path_events: Default::default(), paths: BTreeMap::new(), + last_holepunch: None, selected_path: None, - metrics, } } @@ -792,10 +810,8 @@ impl NodeStateActor { .send((addr.clone(), transmit.clone()).into()) .await .whatever_context("TransportSenerActor stopped")?; - if addr.is_relay() { - self.call_me_maybe(addr.clone()); - } } + self.trigger_holepunching(); } } NodeStateMessage::AddConnection(handle) => { @@ -827,24 +843,58 @@ impl NodeStateActor { Ok(()) } - /// Tries to select a new path out of the available ones. + /// Triggers holepunching to the remote node. /// - /// We only want to accept any path which we know can be used. If we do not know of any - /// working paths we must set it to `None`. - fn select_path(&mut self) { - // TODO - self.selected_path = None; - } - - /// Sends a call-me-maybe message to the remote node. + /// This will manage the entire process of holepunching with the remote node. /// - /// This will first send pings to any paths the remote advertised in their own - /// call-me-maybe message. - /// - /// If a call-me-maybe message was recently sent it will instead schedule a new - /// call-me-maybe after a delay if by then it is still needed. - fn call_me_maybe(&mut self, dst: transports::Addr) { - debug_assert!(dst.is_relay(), "must send call-me-maybe via a relay server"); + /// - 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. + fn trigger_holepunching(&mut self) { + const CALL_ME_MAYBE_VALIDITY: Duration = Duration::from_secs(30); + const HOLEPUNCH_ATTEMPTS_INTERVAL: Duration = Duration::from_secs(5); + + let remote_addrs: BTreeSet = 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)| { + state + .sources + .get(&Source::CallMeMaybe) + .map(|when| when.elapsed() >= CALL_ME_MAYBE_VALIDITY) + .and(Some(*addr)) + }) + .collect(); + let local_addrs: BTreeSet = self + .local_addrs + .get() + .unwrap_or_default() + .iter() + .map(|daddr| daddr.addr) + .collect(); + // let local_addrs: BTreeSet = self.local_addrs.get(); + let mut do_hp = true; + if let Some(ref last_hp) = self.last_holepunch { + do_hp = last_hp.when.elapsed() >= HOLEPUNCH_ATTEMPTS_INTERVAL; + if remote_addrs != last_hp.remote_addrs { + do_hp = true; + } + if local_addrs != last_hp.local_addrs { + do_hp = true; + } + } + if !do_hp { + return; + } todo!() } } @@ -897,6 +947,22 @@ impl From for NodeAddr { } } +/// Information about a holepunch attempt. +#[derive(Debug)] +struct HolepunchAttempt { + when: Instant, + /// The set of local addresses which could take part in holepunching. + /// + /// This does not mean every address here participated in the holepunching. E.g. we + /// could have tried only a sub-set of the addresses because a previous attempt already + /// covered part of the range. + local_addrs: BTreeSet, + /// The set of remote addresses which could take part in holepunching. + /// + /// Like `local_addrs` we may not have used them. + remote_addrs: BTreeSet, +} + /// Whether to send a call-me-maybe message after sending pings to all known paths. /// /// `IfNoRecent` will only send a call-me-maybe if no previous one was sent in the last From 1366161841ab99c18d3c0db7066c08e4a3ddc5f0 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Tue, 23 Sep 2025 15:36:21 +0200 Subject: [PATCH 38/44] refactor to allow scheduling holepunching attempts --- iroh/src/magicsock.rs | 3 + iroh/src/magicsock/node_map/node_state.rs | 176 ++++++++++++++-------- 2 files changed, 114 insertions(+), 65 deletions(-) diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index e66159f1dfa..7728e2eeef0 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -2158,6 +2158,9 @@ fn disco_message_sent(msg: &disco::Message, metrics: &MagicsockMetrics) { /// Direct addresses are UDP socket addresses on which an iroh node could potentially be /// contacted. These can come from various sources depending on the network topology of the /// iroh node, see [`DirectAddrType`] for the several kinds of sources. +/// +/// This is essentially a combination of our local addresses combined with any reflexive +/// transport addresses we disovered using QAD. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct DirectAddr { /// The address. diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index 7007ea34b31..e5fff2a6917 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -29,6 +29,7 @@ use crate::{ node_map::path_validity::PathValidity, transports::{self, OwnedTransmit}, }, + util::MaybeFuture, }; use super::{ @@ -723,6 +724,8 @@ pub(super) struct NodeStateActor { /// Used for handling [`NodeStateMessage::SendDatagram`] messages. transports_sender: mpsc::Sender, /// Our local addresses. + /// + /// These are our local addresses and any reflexive transport addresses. local_addrs: n0_watcher::Direct>>, // Internal state - Quinn Connections we are managing. @@ -754,6 +757,8 @@ pub(super) struct NodeStateActor { /// a better path: e.g. when the selected path is a relay path we still need to trigger /// holepunching regularly. selected_path: Option, + /// Time at which we should schedule the next holepunch attempt. + scheduled_holepunch: Option, } impl NodeStateActor { @@ -773,6 +778,7 @@ impl NodeStateActor { paths: BTreeMap::new(), last_holepunch: None, selected_path: None, + scheduled_holepunch: None, } } @@ -796,53 +802,70 @@ impl NodeStateActor { #[instrument(skip_all, fields(node_id = %self.node_id.fmt_short()))] async fn run(&mut self, mut inbox: mpsc::Receiver) -> Result<(), Whatever> { loop { - if let Some(msg) = inbox.recv().await { - match msg { - NodeStateMessage::SendDatagram(transmit) => { - if let Some(ref addr) = self.selected_path { - self.transports_sender - .send((addr.clone(), transmit).into()) - .await - .whatever_context("TransportSenderActor stopped")?; - } else { - for addr in self.paths.keys() { - self.transports_sender - .send((addr.clone(), transmit.clone()).into()) - .await - .whatever_context("TransportSenerActor stopped")?; - } - self.trigger_holepunching(); - } - } - NodeStateMessage::AddConnection(handle) => { - if let Some(conn) = handle.upgrade() { - let events = BroadcastStream::new(conn.path_events()); - self.path_events.push(events); - self.connections.push(handle); - } - } - NodeStateMessage::PingReceived => todo!(), - NodeStateMessage::AddNodeAddr(node_addr, source) => { - for sockaddr in node_addr.direct_addresses { - let addr = transports::Addr::from(sockaddr); - let path = self.paths.entry(addr).or_default(); - path.sources.insert(source.clone(), Instant::now()); - } - if let Some(relay_url) = node_addr.relay_url { - let addr = transports::Addr::from((relay_url, self.node_id)); - let path = self.paths.entry(addr).or_default(); - path.sources.insert(source, Instant::now()); - } + let scheduled_hp = match self.scheduled_holepunch { + Some(when) => MaybeFuture::Some(tokio::time::sleep_until(when)), + None => MaybeFuture::None, + }; + let mut scheduled_hp = std::pin::pin!(scheduled_hp); + tokio::select! { + biased; + msg = inbox.recv() => { + match msg { + Some(msg) => self.handle_message(msg).await?, + None => break, } } - } else { - break; + _ = &mut scheduled_hp => { + self.trigger_holepunching(); + } } } trace!("actor terminating"); Ok(()) } + async fn handle_message(&mut self, msg: NodeStateMessage) -> Result<(), Whatever> { + match msg { + NodeStateMessage::SendDatagram(transmit) => { + if let Some(ref addr) = self.selected_path { + self.transports_sender + .send((addr.clone(), transmit).into()) + .await + .whatever_context("TransportSenderActor stopped")?; + } else { + for addr in self.paths.keys() { + self.transports_sender + .send((addr.clone(), transmit.clone()).into()) + .await + .whatever_context("TransportSenerActor stopped")?; + } + self.trigger_holepunching(); + } + } + NodeStateMessage::AddConnection(handle) => { + if let Some(conn) = handle.upgrade() { + let events = BroadcastStream::new(conn.path_events()); + self.path_events.push(events); + self.connections.push(handle); + } + } + NodeStateMessage::PingReceived => todo!(), + NodeStateMessage::AddNodeAddr(node_addr, source) => { + for sockaddr in node_addr.direct_addresses { + let addr = transports::Addr::from(sockaddr); + let path = self.paths.entry(addr).or_default(); + path.sources.insert(source.clone(), Instant::now()); + } + if let Some(relay_url) = node_addr.relay_url { + let addr = transports::Addr::from((relay_url, self.node_id)); + let path = self.paths.entry(addr).or_default(); + path.sources.insert(source, Instant::now()); + } + } + } + Ok(()) + } + /// Triggers holepunching to the remote node. /// /// This will manage the entire process of holepunching with the remote node. @@ -855,12 +878,45 @@ impl NodeStateActor { /// - 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. fn trigger_holepunching(&mut self) { - const CALL_ME_MAYBE_VALIDITY: Duration = Duration::from_secs(30); const HOLEPUNCH_ATTEMPTS_INTERVAL: Duration = Duration::from_secs(5); - let remote_addrs: BTreeSet = self - .paths + let remote_addrs: BTreeSet = self.remote_hp_addrs(); + let local_addrs: BTreeSet = self + .local_addrs + .get() + .unwrap_or_default() + .iter() + .map(|daddr| daddr.addr) + .collect(); + let addrs_changed = self + .last_holepunch + .as_ref() + .map(|last_hp| { + last_hp.remote_addrs != remote_addrs || last_hp.local_addrs != local_addrs + }) + .unwrap_or(true); + if !addrs_changed { + if let Some(ref last_hp) = self.last_holepunch { + let next_hp = last_hp.when + HOLEPUNCH_ATTEMPTS_INTERVAL; + if next_hp > Instant::now() { + self.scheduled_holepunch = Some(next_hp); + return; + } + } + } + + self.do_holepunching(); + } + + /// 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)), @@ -873,29 +929,16 @@ impl NodeStateActor { .map(|when| when.elapsed() >= CALL_ME_MAYBE_VALIDITY) .and(Some(*addr)) }) - .collect(); - let local_addrs: BTreeSet = self - .local_addrs - .get() - .unwrap_or_default() - .iter() - .map(|daddr| daddr.addr) - .collect(); - // let local_addrs: BTreeSet = self.local_addrs.get(); - let mut do_hp = true; - if let Some(ref last_hp) = self.last_holepunch { - do_hp = last_hp.when.elapsed() >= HOLEPUNCH_ATTEMPTS_INTERVAL; - if remote_addrs != last_hp.remote_addrs { - do_hp = true; - } - if local_addrs != last_hp.local_addrs { - do_hp = true; - } - } - if !do_hp { - return; - } - todo!() + .collect() + } + + /// 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. + fn do_holepunching(&mut self) { + todo!(); } } @@ -956,6 +999,9 @@ struct HolepunchAttempt { /// This does not mean every address here participated in the holepunching. E.g. we /// could have tried only a sub-set of the addresses because a previous attempt already /// covered part of the range. + /// + /// 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, /// The set of remote addresses which could take part in holepunching. /// From 3436423717867b72cd408173e12309602a8b3999 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Tue, 23 Sep 2025 18:53:49 +0200 Subject: [PATCH 39/44] plug in DiscoState to the NodeStateActor --- iroh/src/magicsock.rs | 13 +++--- iroh/src/magicsock/node_map.rs | 56 +++++++++++++++-------- iroh/src/magicsock/node_map/node_state.rs | 23 ++++++++-- 3 files changed, 62 insertions(+), 30 deletions(-) diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 7728e2eeef0..3baa8beaf72 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -1214,6 +1214,7 @@ impl Handle { let transports = Transports::new(relay_transports, max_receive_segments); let direct_addrs = DiscoveredDirectAddrs::default(); + let (disco, disco_receiver) = DiscoState::new(secret_encryption_key); let node_map = { let node_map = node_map.unwrap_or_default(); @@ -1225,6 +1226,7 @@ impl Handle { metrics.magicsock.clone(), sender, direct_addrs.addrs.watch(), + disco.clone(), ) .await; #[cfg(not(any(test, feature = "test-utils")))] @@ -1233,13 +1235,12 @@ impl Handle { metrics.magicsock.clone(), sender, direct_addrs.addrs.watch(), + disco.clone(), ) .await; nm }; - let (disco, disco_receiver) = DiscoState::new(secret_encryption_key); - let msock = Arc::new(MagicSock { public_key: secret_key.public(), closing: AtomicBool::new(false), @@ -1443,12 +1444,12 @@ fn default_quic_client_config() -> rustls::ClientConfig { .with_no_client_auth() } -#[derive(Debug)] +#[derive(Debug, Clone)] struct DiscoState { /// Encryption key for this node. - secret_encryption_key: crypto_box::SecretKey, + secret_encryption_key: Arc, /// The state for an active DiscoKey. - secrets: Mutex>, + secrets: Arc>>, /// Disco (ping) queue sender: mpsc::Sender<(SendAddr, PublicKey, disco::Message)>, } @@ -1461,7 +1462,7 @@ impl DiscoState { ( Self { - secret_encryption_key, + secret_encryption_key: Arc::new(secret_encryption_key), secrets: Default::default(), sender: disco_sender, }, diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index d5a86d3e66f..7be84668d4e 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -14,12 +14,12 @@ use tokio::sync::mpsc; use tracing::{debug, info, instrument, trace, warn}; use self::node_state::{NodeState, Options}; -use super::DirectAddr; use super::mapped_addrs::{AddrMap, RelayMappedAddr}; #[cfg(any(test, feature = "test-utils"))] use super::transports::TransportsSender; #[cfg(not(any(test, feature = "test-utils")))] use super::transports::TransportsSender; +use super::{DirectAddr, DiscoState}; use super::{ MagicsockMetrics, mapped_addrs::NodeIdMappedAddr, @@ -74,6 +74,7 @@ pub(super) struct NodeMapInner { /// Handle to an actor that can send over the transports. transports_handle: TransportsSenderHandle, local_addrs: n0_watcher::Direct>>, + disco: DiscoState, by_node_key: HashMap, by_ip_port: HashMap, by_quic_mapped_addr: HashMap, @@ -149,8 +150,9 @@ impl NodeMap { metrics: Arc, sender: TransportsSender, local_addrs: n0_watcher::Direct>>, + disco: DiscoState, ) -> Self { - Self::from_inner(NodeMapInner::new(metrics, sender, local_addrs)) + Self::from_inner(NodeMapInner::new(metrics, sender, local_addrs, disco)) } #[cfg(not(any(test, feature = "test-utils")))] @@ -160,8 +162,9 @@ impl NodeMap { metrics: Arc, sender: TransportsSender, local_addrs: n0_watcher::Direct>>, + disco: DiscoState, ) -> Self { - let me = Self::from_inner(NodeMapInner::new(metrics, sender, local_addrs)); + let me = Self::from_inner(NodeMapInner::new(metrics, sender, local_addrs, disco)); for addr in nodes { me.add_node_addr(addr, Source::Saved).await; } @@ -176,8 +179,9 @@ impl NodeMap { metrics: Arc, sender: TransportsSender, local_addrs: n0_watcher::Direct>>, + disco: DiscoState, ) -> Self { - let mut inner = NodeMapInner::new(metrics, sender, local_addrs); + let mut inner = NodeMapInner::new(metrics, sender, local_addrs, disco); inner.path_selection = path_selection; let me = Self::from_inner(inner); for addr in nodes { @@ -358,8 +362,9 @@ impl NodeMap { // Create a new NodeStateActor and insert it into the node map. let sender = inner.transports_handle.inbox.clone(); let local_addrs = inner.local_addrs.clone(); + let disco = inner.disco.clone(); let metrics = inner.metrics.clone(); - let actor = NodeStateActor::new(node_id, sender, local_addrs, metrics); + let actor = NodeStateActor::new(node_id, sender, local_addrs, disco, metrics); let handle = actor.start(); let sender = handle.sender.clone(); inner.node_states.insert(node_id, handle); @@ -377,12 +382,14 @@ impl NodeMapInner { metrics: Arc, sender: TransportsSender, local_addrs: n0_watcher::Direct>>, + disco: DiscoState, ) -> Self { let transports_handle = Self::start_transports_sender(sender); Self { metrics, transports_handle, local_addrs, + disco, by_node_key: Default::default(), by_ip_port: Default::default(), by_quic_mapped_addr: Default::default(), @@ -773,14 +780,14 @@ mod tests { use crate::magicsock::transports::Transports; impl NodeMap { - #[track_caller] - fn add_test_addr(&self, node_addr: NodeAddr) { + async fn add_test_addr(&self, node_addr: NodeAddr) { self.add_node_addr( node_addr, Source::NamedApp { name: "test".into(), }, - ); + ) + .await; } } @@ -790,10 +797,12 @@ mod tests { async fn restore_from_vec() { let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); let direct_addrs = DiscoveredDirectAddrs::default(); + let (disco, _) = DiscoState::new(crypto_box::SecretKey::generate(&mut rand::rngs::OsRng)); let node_map = NodeMap::new( Default::default(), transports.create_sender(), direct_addrs.addrs.watch(), + disco.clone(), ); let mut rng = rand::thread_rng(); @@ -815,10 +824,10 @@ mod tests { let node_addr_c = NodeAddr::new(node_c).with_direct_addresses(direct_addresses_c); let node_addr_d = NodeAddr::new(node_d); - node_map.add_test_addr(node_addr_a); - node_map.add_test_addr(node_addr_b); - node_map.add_test_addr(node_addr_c); - node_map.add_test_addr(node_addr_d); + node_map.add_test_addr(node_addr_a).await; + node_map.add_test_addr(node_addr_b).await; + node_map.add_test_addr(node_addr_c).await; + node_map.add_test_addr(node_addr_d).await; let mut addrs: Vec = node_map .list_remote_infos(Instant::now()) @@ -837,6 +846,7 @@ mod tests { Default::default(), transports.create_sender(), direct_addrs.addrs.watch(), + disco, ) .await; @@ -863,15 +873,17 @@ mod tests { (std::net::IpAddr::V4(Ipv4Addr::LOCALHOST), port).into() } - #[test] + #[tokio::test] #[traced_test] - fn test_prune_direct_addresses() { + async fn test_prune_direct_addresses() { let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); let direct_addrs = DiscoveredDirectAddrs::default(); + let (disco, _) = DiscoState::new(crypto_box::SecretKey::generate(&mut rand::rngs::OsRng)); let node_map = NodeMap::new( Default::default(), transports.create_sender(), direct_addrs.addrs.watch(), + disco, ); let public_key = SecretKey::generate(rand::thread_rng()).public(); let id = node_map @@ -899,7 +911,7 @@ mod tests { let addr = SocketAddr::new(LOCALHOST, 5000 + i as u16); let node_addr = NodeAddr::new(public_key).with_direct_addresses([addr]); // add address - node_map.add_test_addr(node_addr); + node_map.add_test_addr(node_addr).await; // make it active node_map.inner.lock().unwrap().receive_udp(addr); } @@ -908,7 +920,7 @@ mod tests { for i in 0..MAX_INACTIVE_DIRECT_ADDRESSES * 2 { let addr = SocketAddr::new(LOCALHOST, 6000 + i as u16); let node_addr = NodeAddr::new(public_key).with_direct_addresses([addr]); - node_map.add_test_addr(node_addr); + node_map.add_test_addr(node_addr).await; } let mut node_map_inner = node_map.inner.lock().unwrap(); @@ -943,19 +955,23 @@ mod tests { ) } - #[test] - fn test_prune_inactive() { + #[tokio::test] + async fn test_prune_inactive() { let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); let direct_addrs = DiscoveredDirectAddrs::default(); + let (disco, _) = DiscoState::new(crypto_box::SecretKey::generate(&mut rand::rngs::OsRng)); let node_map = NodeMap::new( Default::default(), transports.create_sender(), direct_addrs.addrs.watch(), + disco, ); // add one active node and more than MAX_INACTIVE_NODES inactive nodes let active_node = SecretKey::generate(rand::thread_rng()).public(); let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 167); - node_map.add_test_addr(NodeAddr::new(active_node).with_direct_addresses([addr])); + node_map + .add_test_addr(NodeAddr::new(active_node).with_direct_addresses([addr])) + .await; node_map .inner .lock() @@ -965,7 +981,7 @@ mod tests { for _ in 0..MAX_INACTIVE_NODES + 1 { let node = SecretKey::generate(rand::thread_rng()).public(); - node_map.add_test_addr(NodeAddr::new(node)); + node_map.add_test_addr(NodeAddr::new(node)).await; } assert_eq!(node_map.node_count(), MAX_INACTIVE_NODES + 2); diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index e5fff2a6917..e9c2912d4c6 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -24,7 +24,7 @@ use crate::{ disco::{self, SendAddr}, endpoint::DirectAddr, magicsock::{ - HEARTBEAT_INTERVAL, MagicsockMetrics, + DiscoState, HEARTBEAT_INTERVAL, MagicsockMetrics, mapped_addrs::{MappedAddr, NodeIdMappedAddr}, node_map::path_validity::PathValidity, transports::{self, OwnedTransmit}, @@ -727,6 +727,8 @@ pub(super) struct NodeStateActor { /// /// These are our local addresses and any reflexive transport addresses. local_addrs: n0_watcher::Direct>>, + /// Shared state to allow to encrypt DISCO messages to peers. + disco: DiscoState, // Internal state - Quinn Connections we are managing. // @@ -766,6 +768,7 @@ impl NodeStateActor { node_id: NodeId, transports_sender: mpsc::Sender, local_addrs: n0_watcher::Direct>>, + disco: DiscoState, metrics: Arc, ) -> Self { Self { @@ -773,6 +776,7 @@ impl NodeStateActor { metrics, transports_sender, local_addrs, + disco, connections: Vec::new(), path_events: Default::default(), paths: BTreeMap::new(), @@ -807,6 +811,8 @@ impl NodeStateActor { None => MaybeFuture::None, }; let mut scheduled_hp = std::pin::pin!(scheduled_hp); + // TODO: Watch our local direct addresses. If they change we need to holepunch + // again. tokio::select! { biased; msg = inbox.recv() => { @@ -815,6 +821,9 @@ impl NodeStateActor { None => break, } } + _ = self.local_addrs.updated() => { + self.trigger_holepunching(); + } _ = &mut scheduled_hp => { self.trigger_holepunching(); } @@ -849,7 +858,6 @@ impl NodeStateActor { self.connections.push(handle); } } - NodeStateMessage::PingReceived => todo!(), NodeStateMessage::AddNodeAddr(node_addr, source) => { for sockaddr in node_addr.direct_addresses { let addr = transports::Addr::from(sockaddr); @@ -862,6 +870,8 @@ impl NodeStateActor { path.sources.insert(source, Instant::now()); } } + NodeStateMessage::CallMeMaybeReceived => todo!(), + NodeStateMessage::PingReceived => todo!(), } Ok(()) } @@ -938,6 +948,7 @@ impl NodeStateActor { /// message. /// - A DISCO call-me-maybe message advertising our own addresses will be sent. fn do_holepunching(&mut self) { + // If direct addrs are out of date we need to schedule an update? todo!(); } } @@ -959,10 +970,14 @@ pub(crate) enum NodeStateMessage { /// needed, any new paths discovered via holepunching will be added. And closed paths /// will be removed etc. AddConnection(WeakConnectionHandle), - // TODO: Add the transaction ID. - PingReceived, /// Adds a [`NodeAddr`] with locations where the node might be reachable. AddNodeAddr(NodeAddr, Source), + /// Process a received DISCO CallMeMaybe message. + // TODO: Add the message contents. + CallMeMaybeReceived, + /// Process a received DISCO Ping message. + // TODO: Add the transaction ID. + PingReceived, } /// A handle to a [`NodeStateActor`]. From 01bf15c43b1c70f3433c7c579489e532a9da8f5d Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Tue, 23 Sep 2025 19:28:52 +0200 Subject: [PATCH 40/44] a sane method to send disco messages --- iroh/src/magicsock.rs | 21 ++++++++--------- iroh/src/magicsock/node_map.rs | 6 ++--- iroh/src/magicsock/node_map/node_state.rs | 28 +++++++++++++++++++++++ 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 3baa8beaf72..bb56d1e7f52 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -955,7 +955,7 @@ impl MagicSock { )); } - let pkt = self.disco.encode_and_seal(self.public_key, dst_key, &msg); + let pkt = self.disco.encode_and_seal(dst_key, &msg); let transmit = transports::Transmit { contents: &pkt, @@ -1204,7 +1204,6 @@ impl Handle { }); let relay_transports = vec![relay_transport]; - let secret_encryption_key = secret_ed_box(secret_key.secret()); #[cfg(not(wasm_browser))] let ipv6 = ip_transports.iter().any(|t| t.bind_addr().is_ipv6()); @@ -1214,7 +1213,7 @@ impl Handle { let transports = Transports::new(relay_transports, max_receive_segments); let direct_addrs = DiscoveredDirectAddrs::default(); - let (disco, disco_receiver) = DiscoState::new(secret_encryption_key); + let (disco, disco_receiver) = DiscoState::new(&secret_key); let node_map = { let node_map = node_map.unwrap_or_default(); @@ -1446,6 +1445,8 @@ fn default_quic_client_config() -> rustls::ClientConfig { #[derive(Debug, Clone)] struct DiscoState { + /// The NodeId/PublicKey of this node. + this_node_id: NodeId, /// Encryption key for this node. secret_encryption_key: Arc, /// The state for an active DiscoKey. @@ -1456,12 +1457,15 @@ struct DiscoState { impl DiscoState { fn new( - secret_encryption_key: crypto_box::SecretKey, + secret_key: &SecretKey, ) -> (Self, mpsc::Receiver<(SendAddr, PublicKey, disco::Message)>) { + let this_node_id = secret_key.public(); + let secret_encryption_key = secret_ed_box(secret_key.secret()); let (disco_sender, disco_receiver) = mpsc::channel(256); ( Self { + this_node_id, secret_encryption_key: Arc::new(secret_encryption_key), secrets: Default::default(), sender: disco_sender, @@ -1474,15 +1478,10 @@ impl DiscoState { self.sender.try_send((dst, node_id, msg)).is_ok() } - fn encode_and_seal( - &self, - this_node_id: NodeId, - other_node_id: NodeId, - msg: &disco::Message, - ) -> Bytes { + fn encode_and_seal(&self, other_node_id: NodeId, msg: &disco::Message) -> Bytes { let mut seal = msg.as_bytes(); self.get_secret(other_node_id, |secret| secret.seal(&mut seal)); - disco::encode_message(&this_node_id, seal).into() + disco::encode_message(&self.this_node_id, seal).into() } fn unseal_and_decode( diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index 7be84668d4e..1ea7716984e 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -797,7 +797,7 @@ mod tests { async fn restore_from_vec() { let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); let direct_addrs = DiscoveredDirectAddrs::default(); - let (disco, _) = DiscoState::new(crypto_box::SecretKey::generate(&mut rand::rngs::OsRng)); + let (disco, _) = DiscoState::new(&SecretKey::generate(&mut rand::rngs::OsRng)); let node_map = NodeMap::new( Default::default(), transports.create_sender(), @@ -878,7 +878,7 @@ mod tests { async fn test_prune_direct_addresses() { let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); let direct_addrs = DiscoveredDirectAddrs::default(); - let (disco, _) = DiscoState::new(crypto_box::SecretKey::generate(&mut rand::rngs::OsRng)); + let (disco, _) = DiscoState::new(&SecretKey::generate(&mut rand::rngs::OsRng)); let node_map = NodeMap::new( Default::default(), transports.create_sender(), @@ -959,7 +959,7 @@ mod tests { async fn test_prune_inactive() { let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); let direct_addrs = DiscoveredDirectAddrs::default(); - let (disco, _) = DiscoState::new(crypto_box::SecretKey::generate(&mut rand::rngs::OsRng)); + let (disco, _) = DiscoState::new(&SecretKey::generate(&mut rand::rngs::OsRng)); let node_map = NodeMap::new( Default::default(), transports.create_sender(), diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index e9c2912d4c6..a248aa557ea 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -951,6 +951,34 @@ impl NodeStateActor { // If direct addrs are out of date we need to schedule an update? todo!(); } + + #[instrument(skip(self, dst_key), fields(dst_key = %dst_key.fmt_short()))] + async fn send_disco_message( + &self, + dst: transports::Addr, + dst_key: PublicKey, + msg: disco::Message, + ) { + let pkt = self.disco.encode_and_seal(dst_key, &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.transports_sender.send((dst, transmit).into()).await { + Ok(()) => { + trace!("sent"); + counter.inc(); + } + Err(err) => { + warn!("failed to send disco message: {err:#}"); + } + } + } } /// Messages to send to the [`NodeStateActor`]. From 001d16d9fa8d9c59492e68f5aea2d8b3c0255ea4 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Wed, 24 Sep 2025 14:21:55 +0200 Subject: [PATCH 41/44] Implement starting of holepunching Hook up pings and pongs received to go back to the right place. Look how simple that is! --- iroh/src/disco.rs | 66 +++++++++++-------- iroh/src/magicsock.rs | 9 ++- iroh/src/magicsock/node_map.rs | 24 ++++++- iroh/src/magicsock/node_map/node_state.rs | 78 ++++++++++++++++++----- iroh/src/magicsock/node_map/path_state.rs | 4 +- 5 files changed, 134 insertions(+), 47 deletions(-) diff --git a/iroh/src/disco.rs b/iroh/src/disco.rs index e2fc2d26c21..20f44922032 100644 --- a/iroh/src/disco.rs +++ b/iroh/src/disco.rs @@ -24,7 +24,7 @@ use std::{ }; use data_encoding::HEXLOWER; -use iroh_base::{PublicKey, RelayUrl}; +use iroh_base::{NodeId, PublicKey, RelayUrl}; use nested_enum_utils::common_fields; use serde::{Deserialize, Serialize}; use snafu::{Snafu, ensure}; @@ -118,12 +118,46 @@ pub struct Ping { /// Random client-generated per-ping transaction ID. pub tx_id: stun_rs::TransactionId, - /// Allegedly the ping sender's wireguard public key. - /// It shouldn't be trusted by itself, but can be combined with - /// netmap data to reduce the discokey:nodekey relation from 1:N to 1:1. + /// Allegedly the ping sender's public key. + /// + /// It shouldn't be trusted by itself. pub node_key: PublicKey, } +impl Ping { + /// Creates a ping message to ping `node_id`. + /// + /// Uses a randomly generated STUN transaction ID. + pub(crate) fn new(node_id: NodeId) -> Self { + Self { + tx_id: stun_rs::TransactionId::default(), + node_key: node_id, + } + } + + fn from_bytes(p: &[u8]) -> Result { + // Deliberately lax on longer-than-expected messages, for future compatibility. + ensure!(p.len() >= PING_LEN, TooShortSnafu); + 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 node_key = PublicKey::try_from(raw_key).map_err(|_| InvalidEncodingSnafu.build())?; + let tx_id = stun_rs::TransactionId::from(tx_id); + + Ok(Ping { tx_id, node_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.node_key.as_ref()); + + out + } +} + /// A response a Ping. /// /// It includes the sender's source IP + port, so it's effectively a STUN response. @@ -213,30 +247,6 @@ pub struct CallMeMaybe { pub my_numbers: Vec, } -impl Ping { - fn from_bytes(p: &[u8]) -> Result { - // Deliberately lax on longer-than-expected messages, for future compatibility. - ensure!(p.len() >= PING_LEN, TooShortSnafu); - 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 node_key = PublicKey::try_from(raw_key).map_err(|_| InvalidEncodingSnafu.build())?; - let tx_id = stun_rs::TransactionId::from(tx_id); - - Ok(Ping { tx_id, node_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.node_key.as_ref()); - - out - } -} - #[allow(missing_docs)] #[common_fields({ backtrace: Option, diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index bb56d1e7f52..8f53080b8e8 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -844,8 +844,13 @@ impl MagicSock { let _guard = span.enter(); trace!("receive disco message"); match dm { - disco::Message::Ping(..) | disco::Message::Pong(..) => { - unreachable!("not used anymore"); + disco::Message::Ping(ping) => { + self.metrics.magicsock.recv_disco_ping.inc(); + self.node_map.handle_ping(ping, sender, src.clone()); + } + disco::Message::Pong(pong) => { + self.metrics.magicsock.recv_disco_pong.inc(); + self.node_map.handle_pong(pong, sender, src.clone()); } disco::Message::CallMeMaybe(cm) => { self.metrics.magicsock.recv_disco_call_me_maybe.inc(); diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index 1ea7716984e..1ce712bda91 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -25,7 +25,7 @@ use super::{ mapped_addrs::NodeIdMappedAddr, transports::{self, OwnedTransmit}, }; -use crate::disco::CallMeMaybe; +use crate::disco::{self, CallMeMaybe}; #[cfg(any(test, feature = "test-utils"))] use crate::endpoint::PathSelection; @@ -375,6 +375,28 @@ impl NodeMap { } } } + + pub(super) fn handle_ping(&self, msg: disco::Ping, sender: NodeId, src: transports::Addr) { + if msg.node_key != sender { + warn!("DISCO Ping NodeId mismatch, ignoring ping"); + return; + } + let node_state = self.node_state_actor(sender); + if let Err(err) = node_state.try_send(NodeStateMessage::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: NodeId, src: transports::Addr) { + let node_state = self.node_state_actor(sender); + if let Err(err) = node_state.try_send(NodeStateMessage::PongReceived(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 Pong dropped: {err:#}"); + } + } } impl NodeMapInner { diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index a248aa557ea..bd63f26a78e 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -871,7 +871,8 @@ impl NodeStateActor { } } NodeStateMessage::CallMeMaybeReceived => todo!(), - NodeStateMessage::PingReceived => todo!(), + NodeStateMessage::PingReceived(msg, src) => todo!(), + NodeStateMessage::PongReceived(msg, src) => todo!(), } Ok(()) } @@ -906,7 +907,10 @@ impl NodeStateActor { .last_holepunch .as_ref() .map(|last_hp| { - last_hp.remote_addrs != remote_addrs || last_hp.local_addrs != local_addrs + // Addrs are allowed to disappear, but if there are new ones we need to + // holepunch again. + !remote_addrs.is_subset(&last_hp.remote_addrs) + || !local_addrs.is_subset(&last_hp.local_addrs) }) .unwrap_or(true); if !addrs_changed { @@ -947,19 +951,62 @@ impl NodeStateActor { /// - 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. - fn do_holepunching(&mut self) { - // If direct addrs are out of date we need to schedule an update? - todo!(); + async fn do_holepunching(&mut self) { + let Some(relay_addr) = self + .paths + .iter() + .filter_map(|(addr, state)| match addr { + transports::Addr::Ip(_) => None, + transports::Addr::Relay(_, _) => Some(addr), + }) + .next() + .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 { + let msg = disco::Ping::new(self.node_id); + event!( + target: "iroh::_events::ping::sent", + Level::DEBUG, + remote_node = %self.node_id.fmt_short(), + ?dst, + txn = ?msg.tx_id, + ); + let addr = transports::Addr::Ip(dst); + self.paths.entry(addr.clone()).or_default().ping = Some(msg.clone()); + self.send_disco_message(addr, disco::Message::Ping(msg)) + .await; + } + + // Send the DISCO CallMeMaybe message over the relay. + let my_numbers: Vec = self + .local_addrs + .get() + .unwrap_or_default() + .iter() + .map(|daddr| daddr.addr) + .collect(); + let msg = disco::CallMeMaybe { my_numbers }; + event!( + target: "iroh::_events::call-me-maybe::sent", + Level::DEBUG, + remote_node = &self.node_id.fmt_short(), + dst = ?relay_addr, + my_numbers = ?msg.my_numbers, + ); + self.send_disco_message(relay_addr, disco::Message::CallMeMaybe(msg)) + .await; } - #[instrument(skip(self, dst_key), fields(dst_key = %dst_key.fmt_short()))] - async fn send_disco_message( - &self, - dst: transports::Addr, - dst_key: PublicKey, - msg: disco::Message, - ) { - let pkt = self.disco.encode_and_seal(dst_key, &msg); + /// Sends a DISCO message to *this* remote node. + #[instrument(skip(self), fields(dst_node = self.node_id.fmt_short()))] + async fn send_disco_message(&self, dst: transports::Addr, msg: disco::Message) { + let pkt = self.disco.encode_and_seal(self.node_id, &msg); let transmit = transports::OwnedTransmit { ecn: None, contents: pkt, @@ -1004,8 +1051,9 @@ pub(crate) enum NodeStateMessage { // TODO: Add the message contents. CallMeMaybeReceived, /// Process a received DISCO Ping message. - // TODO: Add the transaction ID. - PingReceived, + PingReceived(disco::Ping, transports::Addr), + /// Process a received DISCO Pong message. + PongReceived(disco::Pong, transports::Addr), } /// A handle to a [`NodeStateActor`]. diff --git a/iroh/src/magicsock/node_map/path_state.rs b/iroh/src/magicsock/node_map/path_state.rs index 93a07a699ae..f6e79f83600 100644 --- a/iroh/src/magicsock/node_map/path_state.rs +++ b/iroh/src/magicsock/node_map/path_state.rs @@ -10,7 +10,7 @@ use super::{ node_state::{ControlMsg, SESSION_ACTIVE_TIMEOUT}, }; use crate::{ - disco::SendAddr, + disco::{self, SendAddr}, magicsock::node_map::path_validity::{self, PathValidity}, }; @@ -198,4 +198,6 @@ pub(super) struct NewPathState { /// 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: Option, } From 7a9023f5409d10c11c3c984a5cf0f40aff191849 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Wed, 24 Sep 2025 15:45:49 +0200 Subject: [PATCH 42/44] handle receiving pings --- iroh/src/magicsock/node_map.rs | 2 + iroh/src/magicsock/node_map/node_state.rs | 89 ++++++++++++++++++++--- iroh/src/magicsock/node_map/path_state.rs | 2 +- iroh/src/magicsock/transports.rs | 4 + 4 files changed, 85 insertions(+), 12 deletions(-) diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index 1ce712bda91..6f887dc0a3e 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -142,6 +142,8 @@ pub enum Source { }, /// The address was advertised by a call-me-maybe DISCO message. CallMeMaybe, + /// We received a ping on the path. + Ping, } impl NodeMap { diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index bd63f26a78e..89f4711224a 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -871,8 +871,44 @@ impl NodeStateActor { } } NodeStateMessage::CallMeMaybeReceived => todo!(), - NodeStateMessage::PingReceived(msg, src) => todo!(), - NodeStateMessage::PongReceived(msg, src) => todo!(), + NodeStateMessage::PingReceived(ping, src) => { + let transports::Addr::Ip(addr) = src else { + warn!("received ping via relay transport, ignored"); + return Ok(()); + }; + event!( + target: "iroh::_events::ping::recv", + Level::DEBUG, + remote_node = self.node_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_node = self.node_id.fmt_short(), + dst = ?src, + txn = ?pong.tx_id, + ); + self.send_disco_message(src.clone(), disco::Message::Pong(pong)) + .await; + + let path = self.paths.entry(src).or_default(); + path.sources.insert(Source::Ping, Instant::now()); + + self.trigger_holepunching().await; + } + NodeStateMessage::PongReceived(msg, src) => { + for (path, state) in self.paths.iter() { + if let Some(ref ping) = state.ping_sent { + todo!(); + } + } + } } Ok(()) } @@ -881,6 +917,7 @@ impl NodeStateActor { /// /// This will manage the entire process of holepunching with the remote node. /// + /// - 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. @@ -892,9 +929,20 @@ impl NodeStateActor { /// /// If a next trigger needs to be scheduled the delay until when to call this again is /// returned. - fn trigger_holepunching(&mut self) { + async fn trigger_holepunching(&mut self) { const HOLEPUNCH_ATTEMPTS_INTERVAL: Duration = Duration::from_secs(5); + if self + .selected_path + .as_ref() + .map(|addr| addr.is_ip()) + .unwrap_or_default() + { + trace!("not holepunching, already have a direct connection"); + // TODO: If the latency is kind of bad we should retry holepunching at times. + return; + } + let remote_addrs: BTreeSet = self.remote_hp_addrs(); let local_addrs: BTreeSet = self .local_addrs @@ -903,7 +951,7 @@ impl NodeStateActor { .iter() .map(|daddr| daddr.addr) .collect(); - let addrs_changed = self + let new_addrs = self .last_holepunch .as_ref() .map(|last_hp| { @@ -913,17 +961,18 @@ impl NodeStateActor { || !local_addrs.is_subset(&last_hp.local_addrs) }) .unwrap_or(true); - if !addrs_changed { + if !new_addrs { if let Some(ref last_hp) = self.last_holepunch { let next_hp = last_hp.when + HOLEPUNCH_ATTEMPTS_INTERVAL; if next_hp > Instant::now() { + trace!(scheduled_in = ?next_hp, "not holepunching: no new addresses"); self.scheduled_holepunch = Some(next_hp); return; } } } - self.do_holepunching(); + self.do_holepunching().await; } /// Returns the remote addresses to holepunch against. @@ -937,11 +986,21 @@ impl NodeStateActor { transports::Addr::Relay(_, _) => None, }) .filter_map(|(addr, state)| { - state + if state .sources .get(&Source::CallMeMaybe) .map(|when| when.elapsed() >= CALL_ME_MAYBE_VALIDITY) - .and(Some(*addr)) + .unwrap_or_default() + || state + .sources + .get(&Source::Ping) + .map(|when| when.elapsed() >= CALL_ME_MAYBE_VALIDITY) + .unwrap_or_default() + { + Some(*addr) + } else { + None + } }) .collect() } @@ -952,6 +1011,7 @@ impl NodeStateActor { /// message. /// - A DISCO call-me-maybe message advertising our own addresses will be sent. async fn do_holepunching(&mut self) { + trace!("holepunching"); let Some(relay_addr) = self .paths .iter() @@ -968,7 +1028,7 @@ impl NodeStateActor { let remote_addrs = self.remote_hp_addrs(); // Send DISCO Ping messages to all CallMeMaybe-advertised paths. - for dst in remote_addrs { + for dst in remote_addrs.iter() { let msg = disco::Ping::new(self.node_id); event!( target: "iroh::_events::ping::sent", @@ -977,8 +1037,8 @@ impl NodeStateActor { ?dst, txn = ?msg.tx_id, ); - let addr = transports::Addr::Ip(dst); - self.paths.entry(addr.clone()).or_default().ping = Some(msg.clone()); + let addr = transports::Addr::Ip(*dst); + self.paths.entry(addr.clone()).or_default().ping_sent = Some(msg.clone()); self.send_disco_message(addr, disco::Message::Ping(msg)) .await; } @@ -991,6 +1051,7 @@ impl NodeStateActor { .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", @@ -1001,6 +1062,12 @@ impl NodeStateActor { ); 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 *this* remote node. diff --git a/iroh/src/magicsock/node_map/path_state.rs b/iroh/src/magicsock/node_map/path_state.rs index f6e79f83600..d75da7fc97d 100644 --- a/iroh/src/magicsock/node_map/path_state.rs +++ b/iroh/src/magicsock/node_map/path_state.rs @@ -199,5 +199,5 @@ pub(super) struct NewPathState { /// 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: Option, + pub(super) ping_sent: Option, } diff --git a/iroh/src/magicsock/transports.rs b/iroh/src/magicsock/transports.rs index 968347f3ca4..714023ef295 100644 --- a/iroh/src/magicsock/transports.rs +++ b/iroh/src/magicsock/transports.rs @@ -365,6 +365,10 @@ impl Addr { matches!(self, Self::Relay(..)) } + pub(crate) fn is_ip(&self) -> bool { + matches!(self, Self::Ip(_)) + } + /// Returns `None` if not an `Ip`. pub(crate) fn into_socket_addr(self) -> Option { match self { From 92dd37cb791fa7e0e243a8c62dadb2efc2bf479c Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Wed, 24 Sep 2025 18:21:21 +0200 Subject: [PATCH 43/44] handle receiving CallMeMaybe messages --- iroh/src/magicsock.rs | 5 +- iroh/src/magicsock/node_map.rs | 63 +++++++++++++++-------- iroh/src/magicsock/node_map/node_state.rs | 36 +++++++++++-- 3 files changed, 76 insertions(+), 28 deletions(-) diff --git a/iroh/src/magicsock.rs b/iroh/src/magicsock.rs index 8f53080b8e8..9e2d2886b5a 100644 --- a/iroh/src/magicsock.rs +++ b/iroh/src/magicsock.rs @@ -877,8 +877,7 @@ impl MagicSock { direct_addresses: cm.my_numbers.iter().copied().collect(), }); - self.node_map - .handle_call_me_maybe(sender, cm, &self.metrics.magicsock); + self.node_map.handle_call_me_maybe(sender, cm); } } trace!("disco message handled"); @@ -1225,6 +1224,7 @@ impl Handle { let sender = transports.create_sender(); #[cfg(any(test, feature = "test-utils"))] let nm = NodeMap::load_from_vec( + secret_key.public(), node_map, path_selection, metrics.magicsock.clone(), @@ -1235,6 +1235,7 @@ impl Handle { .await; #[cfg(not(any(test, feature = "test-utils")))] let nm = NodeMap::load_from_vec( + secret_key.public(), node_map, metrics.magicsock.clone(), sender, diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index 6f887dc0a3e..4adc9237aab 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -61,6 +61,8 @@ const MAX_INACTIVE_NODES: usize = 30; /// An index of nodeInfos by node key, NodeIdMappedAddr, and discovered ip:port endpoints. #[derive(Debug)] pub(super) struct NodeMap { + /// The node ID of the local node. + local_node_id: NodeId, inner: Mutex, /// The mapping between [`NodeId`]s and [`NodeIdMappedAddr`]s. pub(super) node_mapped_addrs: AddrMap, @@ -149,24 +151,28 @@ pub enum Source { impl NodeMap { #[cfg(any(test, feature = "test-utils"))] pub(super) fn new( + local_node_id: NodeId, metrics: Arc, sender: TransportsSender, local_addrs: n0_watcher::Direct>>, disco: DiscoState, ) -> Self { - Self::from_inner(NodeMapInner::new(metrics, sender, local_addrs, disco)) + let inner = NodeMapInner::new(metrics, sender, local_addrs, disco); + Self::from_inner(inner, local_node_id) } #[cfg(not(any(test, feature = "test-utils")))] /// Create a new [`NodeMap`] from a list of [`NodeAddr`]s. pub(super) async fn load_from_vec( + local_node_id: NodeId, nodes: Vec, metrics: Arc, sender: TransportsSender, local_addrs: n0_watcher::Direct>>, disco: DiscoState, ) -> Self { - let me = Self::from_inner(NodeMapInner::new(metrics, sender, local_addrs, disco)); + let inner = NodeMapInner::new(metrics, sender, local_addrs, disco); + let me = Self::from_inner(inner, local_node_id); for addr in nodes { me.add_node_addr(addr, Source::Saved).await; } @@ -176,6 +182,7 @@ impl NodeMap { #[cfg(any(test, feature = "test-utils"))] /// Create a new [`NodeMap`] from a list of [`NodeAddr`]s. pub(super) async fn load_from_vec( + local_node_id: NodeId, nodes: Vec, path_selection: PathSelection, metrics: Arc, @@ -185,15 +192,16 @@ impl NodeMap { ) -> Self { let mut inner = NodeMapInner::new(metrics, sender, local_addrs, disco); inner.path_selection = path_selection; - let me = Self::from_inner(inner); + let me = Self::from_inner(inner, local_node_id); for addr in nodes { me.add_node_addr(addr, Source::Saved).await; } me } - fn from_inner(inner: NodeMapInner) -> Self { + fn from_inner(inner: NodeMapInner, local_node_id: NodeId) -> Self { Self { + local_node_id, inner: Mutex::new(inner), node_mapped_addrs: Default::default(), relay_mapped_addrs: Default::default(), @@ -263,18 +271,6 @@ impl NodeMap { .map(|ep| ep.get_current_addr()) } - pub(super) fn handle_call_me_maybe( - &self, - sender: PublicKey, - cm: CallMeMaybe, - metrics: &MagicsockMetrics, - ) { - self.inner - .lock() - .expect("poisoned") - .handle_call_me_maybe(sender, cm, metrics); - } - #[allow(clippy::type_complexity)] pub(super) fn get_send_addrs( &self, @@ -366,7 +362,14 @@ impl NodeMap { let local_addrs = inner.local_addrs.clone(); let disco = inner.disco.clone(); let metrics = inner.metrics.clone(); - let actor = NodeStateActor::new(node_id, sender, local_addrs, disco, metrics); + let actor = NodeStateActor::new( + node_id, + self.local_node_id, + sender, + local_addrs, + disco, + metrics, + ); let handle = actor.start(); let sender = handle.sender.clone(); inner.node_states.insert(node_id, handle); @@ -394,11 +397,20 @@ impl NodeMap { pub(super) fn handle_pong(&self, msg: disco::Pong, sender: NodeId, src: transports::Addr) { let node_state = self.node_state_actor(sender); if let Err(err) = node_state.try_send(NodeStateMessage::PongReceived(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. + // 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, sender: NodeId, msg: CallMeMaybe) { + let node_state = self.node_state_actor(sender); + if let Err(err) = node_state.try_send(NodeStateMessage::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:#}"); + } + } } impl NodeMapInner { @@ -821,8 +833,10 @@ mod tests { async fn restore_from_vec() { let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); let direct_addrs = DiscoveredDirectAddrs::default(); - let (disco, _) = DiscoState::new(&SecretKey::generate(&mut rand::rngs::OsRng)); + let secret_key = SecretKey::generate(&mut rand::rngs::OsRng); + let (disco, _) = DiscoState::new(&secret_key); let node_map = NodeMap::new( + secret_key.public(), Default::default(), transports.create_sender(), direct_addrs.addrs.watch(), @@ -865,6 +879,7 @@ mod tests { }) .collect(); let loaded_node_map = NodeMap::load_from_vec( + secret_key.public(), addrs.clone(), PathSelection::default(), Default::default(), @@ -902,8 +917,10 @@ mod tests { async fn test_prune_direct_addresses() { let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); let direct_addrs = DiscoveredDirectAddrs::default(); - let (disco, _) = DiscoState::new(&SecretKey::generate(&mut rand::rngs::OsRng)); + let secret_key = SecretKey::generate(&mut rand::rngs::OsRng); + let (disco, _) = DiscoState::new(&secret_key); let node_map = NodeMap::new( + secret_key.public(), Default::default(), transports.create_sender(), direct_addrs.addrs.watch(), @@ -983,8 +1000,10 @@ mod tests { async fn test_prune_inactive() { let transports = Transports::new(Vec::new(), Vec::new(), Arc::new(1200.into())); let direct_addrs = DiscoveredDirectAddrs::default(); - let (disco, _) = DiscoState::new(&SecretKey::generate(&mut rand::rngs::OsRng)); + let secret_key = SecretKey::generate(&mut rand::rngs::OsRng); + let (disco, _) = DiscoState::new(&secret_key); let node_map = NodeMap::new( + secret_key.public(), Default::default(), transports.create_sender(), direct_addrs.addrs.watch(), diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index 89f4711224a..cf66cae6b62 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -714,6 +714,8 @@ impl NodeState { pub(super) struct NodeStateActor { /// The node ID of the remote node. node_id: NodeId, + /// The node ID of the local node. + local_node_id: NodeId, // Hooks into the rest of the MagicSocket. // @@ -766,6 +768,7 @@ pub(super) struct NodeStateActor { impl NodeStateActor { pub(super) fn new( node_id: NodeId, + local_node_id: NodeId, transports_sender: mpsc::Sender, local_addrs: n0_watcher::Direct>>, disco: DiscoState, @@ -773,6 +776,7 @@ impl NodeStateActor { ) -> Self { Self { node_id, + local_node_id, metrics, transports_sender, local_addrs, @@ -870,7 +874,32 @@ impl NodeStateActor { path.sources.insert(source, Instant::now()); } } - NodeStateMessage::CallMeMaybeReceived => todo!(), + NodeStateMessage::CallMeMaybeReceived(msg) => { + event!( + target: "iroh::_events::call-me-maybe::recv", + Level::DEBUG, + remote_node = self.node_id.fmt_short(), + addrs = ?msg.my_numbers, + ); + let now = Instant::now(); + for addr in msg.my_numbers { + let dst = transports::Addr::Ip(addr); + let ping = disco::Ping::new(self.local_node_id); + + let path = self.paths.entry(dst.clone()).or_default(); + path.sources.insert(Source::CallMeMaybe, now); + path.ping_sent = Some(ping.clone()); + + event!( + target: "iroh::_events::ping::sent", + Level::DEBUG, + remote_node = self.node_id.fmt_short(), + ?dst, + ); + self.send_disco_message(dst, disco::Message::Ping(ping)) + .await; + } + } NodeStateMessage::PingReceived(ping, src) => { let transports::Addr::Ip(addr) = src else { warn!("received ping via relay transport, ignored"); @@ -1029,7 +1058,7 @@ impl NodeStateActor { // Send DISCO Ping messages to all CallMeMaybe-advertised paths. for dst in remote_addrs.iter() { - let msg = disco::Ping::new(self.node_id); + let msg = disco::Ping::new(self.local_node_id); event!( target: "iroh::_events::ping::sent", Level::DEBUG, @@ -1115,8 +1144,7 @@ pub(crate) enum NodeStateMessage { /// Adds a [`NodeAddr`] with locations where the node might be reachable. AddNodeAddr(NodeAddr, Source), /// Process a received DISCO CallMeMaybe message. - // TODO: Add the message contents. - CallMeMaybeReceived, + CallMeMaybeReceived(disco::CallMeMaybe), /// Process a received DISCO Ping message. PingReceived(disco::Ping, transports::Addr), /// Process a received DISCO Pong message. From ac2db4d29277979354fb699f36101c9712e38aa3 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Thu, 25 Sep 2025 14:52:19 +0200 Subject: [PATCH 44/44] open a path when we receive a pong --- iroh/src/magicsock/node_map.rs | 1 + iroh/src/magicsock/node_map/node_state.rs | 68 ++++++++++++++++++++--- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/iroh/src/magicsock/node_map.rs b/iroh/src/magicsock/node_map.rs index 4adc9237aab..b59de1d22b8 100644 --- a/iroh/src/magicsock/node_map.rs +++ b/iroh/src/magicsock/node_map.rs @@ -368,6 +368,7 @@ impl NodeMap { sender, local_addrs, disco, + self.relay_mapped_addrs.clone(), metrics, ); let handle = actor.start(); diff --git a/iroh/src/magicsock/node_map/node_state.rs b/iroh/src/magicsock/node_map/node_state.rs index cf66cae6b62..c9d4886837a 100644 --- a/iroh/src/magicsock/node_map/node_state.rs +++ b/iroh/src/magicsock/node_map/node_state.rs @@ -12,6 +12,7 @@ use n0_future::{ }; use n0_watcher::{Watchable, Watcher}; use quinn::WeakConnectionHandle; +use quinn_proto::PathStatus; use serde::{Deserialize, Serialize}; use snafu::{ResultExt, Whatever}; use tokio::sync::mpsc; @@ -24,8 +25,8 @@ use crate::{ disco::{self, SendAddr}, endpoint::DirectAddr, magicsock::{ - DiscoState, HEARTBEAT_INTERVAL, MagicsockMetrics, - mapped_addrs::{MappedAddr, NodeIdMappedAddr}, + DiscoState, HEARTBEAT_INTERVAL, MAX_IDLE_TIMEOUT, MagicsockMetrics, + mapped_addrs::{AddrMap, MappedAddr, NodeIdMappedAddr, RelayMappedAddr}, node_map::path_validity::PathValidity, transports::{self, OwnedTransmit}, }, @@ -731,6 +732,8 @@ pub(super) struct NodeStateActor { local_addrs: n0_watcher::Direct>>, /// Shared state to allow to encrypt DISCO messages to peers. disco: DiscoState, + /// The mapping between nodes via a relay and their [`RelayMappedAddr`]s. + relay_mapped_addrs: AddrMap<(RelayUrl, NodeId), RelayMappedAddr>, // Internal state - Quinn Connections we are managing. // @@ -760,6 +763,8 @@ pub(super) struct NodeStateActor { /// set back to `None`. Having a selected path does not mean we may not be able to get /// a better path: e.g. when the selected path is a relay path we still need to trigger /// holepunching regularly. + /// + /// We only select a path once the path is functional in Quinn. selected_path: Option, /// Time at which we should schedule the next holepunch attempt. scheduled_holepunch: Option, @@ -772,6 +777,7 @@ impl NodeStateActor { transports_sender: mpsc::Sender, local_addrs: n0_watcher::Direct>>, disco: DiscoState, + relay_mapped_addrs: AddrMap<(RelayUrl, NodeId), RelayMappedAddr>, metrics: Arc, ) -> Self { Self { @@ -780,6 +786,7 @@ impl NodeStateActor { metrics, transports_sender, local_addrs, + relay_mapped_addrs, disco, connections: Vec::new(), path_events: Default::default(), @@ -931,12 +938,26 @@ impl NodeStateActor { self.trigger_holepunching().await; } - NodeStateMessage::PongReceived(msg, src) => { - for (path, state) in self.paths.iter() { - if let Some(ref ping) = state.ping_sent { - todo!(); - } + NodeStateMessage::PongReceived(pong, src) => { + let Some(state) = self.paths.get(&src) else { + warn!(path = ?src, "ignoring DISCO Pong for unknown path"); + return Ok(()); + }; + let ping_tx = state.ping_sent.as_ref().map(|ping| ping.tx_id); + if ping_tx != Some(pong.tx_id) { + debug!(path = ?src, ?ping_tx, pong_tx = ?pong.tx_id, + "ignoring unknown DISCO Pong for path"); + return Ok(()); } + event!( + target: "iroh::_events::pong::recv", + Level::DEBUG, + remote_node = self.node_id.fmt_short(), + ?src, + txn = ?pong.tx_id, + ); + + self.open_quic_path(src); } } Ok(()) @@ -1044,7 +1065,7 @@ impl NodeStateActor { let Some(relay_addr) = self .paths .iter() - .filter_map(|(addr, state)| match addr { + .filter_map(|(addr, _)| match addr { transports::Addr::Ip(_) => None, transports::Addr::Relay(_, _) => Some(addr), }) @@ -1122,6 +1143,37 @@ impl NodeStateActor { } } } + + /// Asks Quinn to open a new path on connections, but only if we are the client. + async fn open_quic_path(&self, addr: transports::Addr) { + let path_status = match addr { + transports::Addr::Ip(_) => PathStatus::Available, + transports::Addr::Relay(_, _) => PathStatus::Backup, + }; + let quic_addr = match &addr { + transports::Addr::Ip(socket_addr) => *socket_addr, + transports::Addr::Relay(relay_url, node_id) => self + .relay_mapped_addrs + .get(&(relay_url.clone(), *node_id)) + .private_socket_addr(), + }; + for conn in self + .connections + .iter() + .filter_map(|weak| weak.upgrade()) + .filter(|conn| conn.side().is_client()) + { + match conn.open_path(quic_addr, path_status).await { + Ok(path) => { + path.set_keep_alive_interval(Some(HEARTBEAT_INTERVAL)).ok(); + path.set_max_idle_timeout(Some(MAX_IDLE_TIMEOUT)).ok(); + } + Err(err) => { + warn!(?addr, "Failed to open path: {err:#}"); + } + } + } + } } /// Messages to send to the [`NodeStateActor`].