From 05530a0a9e3b27c71a47e257fc3e32d19556b541 Mon Sep 17 00:00:00 2001 From: Zach Smith Date: Thu, 9 Apr 2026 16:37:05 -0700 Subject: [PATCH 1/6] feat: add iroh net-diagnostics integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrate iroh-services net-diagnostics into the desktop app to collect anonymous network diagnostic data (NAT type, UDP connectivity, relay latency, port mapping). Key changes: - Upgrade iroh 0.95 → 0.97, iroh-proxy-utils to crates.io 0.1.0 - Add iroh-services 0.12 with net_diagnostics and client_host - New diagnostics module with opt-in/out settings persisted to disk - ClientHost registered on ListenNode router for iroh-services dial-back - One-time prompt on first launch for user consent - Settings → Privacy toggle for runtime opt-out - API key loaded from IROH_SERVICES_API_KEY env var or BUILD_IROH_SERVICES_API_KEY at build time Closes datum-cloud/enhancements#676 --- Cargo.lock | 1169 +++++++++++++++++++++--------------- Cargo.toml | 12 +- lib/Cargo.toml | 5 +- lib/src/diagnostics.rs | 126 ++++ lib/src/gateway.rs | 15 +- lib/src/gateway/metrics.rs | 59 +- lib/src/lib.rs | 2 + lib/src/node.rs | 82 ++- lib/src/tests.rs | 17 +- n0des-local/Cargo.toml | 6 +- ui/src/main.rs | 120 ++++ ui/src/views/settings.rs | 67 ++- 12 files changed, 1097 insertions(+), 583 deletions(-) create mode 100644 lib/src/diagnostics.rs diff --git a/Cargo.lock b/Cargo.lock index ebc5345..0dac961 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-core", "futures-sink", @@ -29,7 +29,7 @@ dependencies = [ "actix-rt", "actix-service", "actix-utils", - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "bytestring", "derive_more 2.1.1", @@ -167,13 +167,37 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" -version = "0.6.0-rc.2" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8202ab55fcbf46ca829833f347a82a2a4ce0596f0304ac322c2d100030cd56" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "bytes", - "crypto-common 0.2.0-rc.4", - "inout", + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", ] [[package]] @@ -413,19 +437,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "async-compat" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ba85bc55464dcbf728b56d97e119d673f4cf9062be330a9a26f3acf504a590" -dependencies = [ - "futures-core", - "futures-io", - "once_cell", - "pin-project-lite", - "tokio", -] - [[package]] name = "async-stream" version = "0.3.6" @@ -643,9 +654,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core 0.5.6", "bytes", @@ -767,12 +778,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base16ct" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" - [[package]] name = "base32" version = "0.5.1" @@ -826,20 +831,20 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] [[package]] name = "bitstream-io" -version = "4.9.0" +version = "4.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" dependencies = [ - "core2", + "no_std_io2", ] [[package]] @@ -878,7 +883,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96eb4cdd6cf1b31d671e9efe75c5d1ec614776856cefbe109ca373554a6d514f" dependencies = [ "hybrid-array", - "zeroize", ] [[package]] @@ -891,10 +895,13 @@ dependencies = [ ] [[package]] -name = "btparse" -version = "0.2.0" +name = "built" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387e80962b798815a2b5c4bcfdb6bf626fa922ffe9f74e373103b858738e9f31" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" +dependencies = [ + "cargo-lock", +] [[package]] name = "built" @@ -950,7 +957,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cairo-sys-rs", "glib", "libc", @@ -969,6 +976,18 @@ dependencies = [ "system-deps", ] +[[package]] +name = "cargo-lock" +version = "10.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06acb4f71407ba205a07cb453211e0e6a67b21904e47f6ba1f9589e38f2e454" +dependencies = [ + "semver", + "serde", + "toml", + "url", +] + [[package]] name = "cc" version = "1.2.60" @@ -1020,18 +1039,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "chacha20" -version = "0.10.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd162f2b8af3e0639d83f28a637e4e55657b7a74508dba5a9bf4da523d5c9e9" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures 0.2.17", - "zeroize", -] - [[package]] name = "charset" version = "0.1.5" @@ -1085,21 +1092,19 @@ dependencies = [ [[package]] name = "cipher" -version = "0.5.0-rc.1" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e12a13eb01ded5d32ee9658d94f553a19e804204f2dc811df69ab4d9e0cb8c7" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "block-buffer 0.11.0", - "crypto-common 0.2.0-rc.4", + "crypto-common 0.1.7", "inout", - "zeroize", ] [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -1119,9 +1124,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -1159,7 +1164,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block", "cocoa-foundation", "core-foundation 0.10.1", @@ -1175,7 +1180,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block", "core-foundation 0.10.1", "core-graphics-types", @@ -1189,7 +1194,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308329d5d62e877ba02943db3a8e8c052de9fde7ab48283395ba0e6494efbabd" dependencies = [ "backtrace", - "btparse", "termcolor", ] @@ -1405,7 +1409,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.10.1", "core-graphics-types", "foreign-types 0.5.0", @@ -1418,7 +1422,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.10.1", "core-graphics-types", "foreign-types 0.5.0", @@ -1431,20 +1435,11 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.10.1", "libc", ] -[[package]] -name = "core2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" -dependencies = [ - "memchr", -] - [[package]] name = "cpufeatures" version = "0.2.17" @@ -1532,9 +1527,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -1542,44 +1537,11 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.0-rc.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8235645834fbc6832939736ce2f2d08192652269e11010a6240f61b908a1c6" -dependencies = [ - "hybrid-array", - "rand_core 0.9.5", -] - -[[package]] -name = "crypto_box" -version = "0.10.0-pre.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bda4de3e070830cf3a27a394de135b6709aefcc54d1e16f2f029271254a6ed9" -dependencies = [ - "aead", - "chacha20", - "crypto_secretbox", - "curve25519-dalek 5.0.0-pre.1", - "salsa20", - "serdect", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto_secretbox" -version = "0.2.0-pre.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54532aae6546084a52cef855593daf9555945719eeeda9974150e0def854873e" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ - "aead", - "chacha20", - "cipher", "hybrid-array", - "poly1305", - "salsa20", - "subtle", - "zeroize", ] [[package]] @@ -1609,6 +1571,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -1634,7 +1605,7 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest 0.11.0-rc.3", + "digest 0.11.0-rc.10", "fiat-crypto 0.3.0", "rand_core 0.9.5", "rustc_version", @@ -1654,6 +1625,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + [[package]] name = "darling" version = "0.21.3" @@ -1674,6 +1655,20 @@ dependencies = [ "darling_macro 0.23.0", ] +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + [[package]] name = "darling_core" version = "0.21.3" @@ -1701,6 +1696,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.117", +] + [[package]] name = "darling_macro" version = "0.21.3" @@ -1739,10 +1745,10 @@ dependencies = [ "hickory-proto", "hickory-server", "humantime", - "iroh-base", + "iroh-base 0.97.0", "lib", "n0-error", - "rand 0.9.3", + "rand 0.9.4", "sentry", "serde", "serde_yml", @@ -1768,8 +1774,8 @@ dependencies = [ "hex", "image", "iroh", - "iroh-base", - "iroh-metrics 0.38.3", + "iroh-base 0.97.0", + "iroh-metrics", "iroh-quinn", "lib", "n0-error", @@ -1778,7 +1784,7 @@ dependencies = [ "objc2-app-kit", "objc2-foundation", "open", - "rand 0.9.3", + "rand 0.9.4", "rustls", "sentry", "snafu", @@ -1857,6 +1863,37 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.117", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -1907,19 +1944,19 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "const-oid 0.9.6", - "crypto-common 0.1.6", + "crypto-common 0.1.7", "subtle", ] [[package]] name = "digest" -version = "0.11.0-rc.3" +version = "0.11.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac89f8a64533a9b0eaa73a68e424db0fb1fd6271c74cc0125336a05f090568d" +checksum = "afa94b64bfc6549e6e4b5a3216f22593224174083da7a90db47e951c4fb31725" dependencies = [ "block-buffer 0.11.0", "const-oid 0.10.2", - "crypto-common 0.2.0-rc.4", + "crypto-common 0.2.1", ] [[package]] @@ -2086,7 +2123,7 @@ dependencies = [ "objc", "objc_id", "percent-encoding", - "rand 0.9.3", + "rand 0.9.4", "rfd", "rustc-hash 2.1.2", "serde", @@ -2161,7 +2198,7 @@ dependencies = [ "anyhow", "async-stream", "async-tungstenite", - "axum 0.8.8", + "axum 0.8.9", "axum-core 0.5.6", "base64 0.22.1", "bytes", @@ -2190,7 +2227,7 @@ dependencies = [ "js-sys", "mime", "pin-project", - "reqwest 0.12.28", + "reqwest", "rustversion", "send_wrapper", "serde", @@ -2544,7 +2581,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -2740,7 +2777,7 @@ version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "base16ct 0.2.0", + "base16ct", "crypto-bigint", "digest 0.10.7", "ff", @@ -2788,6 +2825,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "enum-assoc" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed8956bd5c1f0415200516e78ff07ec9e16415ade83c056c230d7b7ea0d55b7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "enumset" version = "1.1.10" @@ -2870,6 +2918,18 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "fastbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" +dependencies = [ + "getrandom 0.3.4", + "libm", + "rand 0.9.4", + "siphasher 1.0.2", +] + [[package]] name = "fastrand" version = "2.4.1" @@ -3333,9 +3393,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -3405,6 +3465,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gif" version = "0.14.2" @@ -3459,7 +3529,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "futures-channel", "futures-core", "futures-executor", @@ -3720,7 +3790,7 @@ dependencies = [ "http 1.4.0", "httpdate", "mime", - "sha1 0.10.6", + "sha1", ] [[package]] @@ -3783,7 +3853,7 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.9.3", + "rand 0.9.4", "ring", "rustls", "serde", @@ -3808,7 +3878,7 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand 0.9.3", + "rand 0.9.4", "resolv-conf", "rustls", "smallvec", @@ -3960,7 +4030,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" dependencies = [ "typenum", - "zeroize", ] [[package]] @@ -3987,9 +4056,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.4.0", "hyper", @@ -3997,7 +4066,6 @@ dependencies = [ "log", "rustls", "rustls-native-certs", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -4051,7 +4119,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.3", - "system-configuration 0.7.0", + "system-configuration", "tokio", "tower-layer", "tower-service", @@ -4177,6 +4245,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "identity-hash" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdd7caa900436d8f13b2346fe10257e0c05c1f1f9e351f4f5d57c03bd5f45da" + [[package]] name = "idna" version = "1.1.0" @@ -4213,7 +4287,7 @@ dependencies = [ "hyper", "hyper-util", "log", - "rand 0.9.3", + "rand 0.9.4", "tokio", "url", "xmltree", @@ -4299,11 +4373,11 @@ dependencies = [ [[package]] name = "inout" -version = "0.2.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "hybrid-array", + "generic-array", ] [[package]] @@ -4315,18 +4389,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "interpolate_name" version = "0.2.4" @@ -4381,15 +4443,13 @@ dependencies = [ [[package]] name = "iroh" -version = "0.95.1" +version = "0.97.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2374ba3cdaac152dc6ada92d971f7328e6408286faab3b7350842b2ebbed4789" +checksum = "feb56e7e4b0ec7fba7efa6a236b016a52b5d927d50244aceb9e20566159b1a32" dependencies = [ - "aead", "backon", "bytes", "cfg_aliases", - "crypto_box", "data-encoding", "derive_more 2.1.1", "ed25519-dalek 3.0.0-pre.1", @@ -4397,32 +4457,33 @@ dependencies = [ "getrandom 0.3.4", "hickory-resolver", "http 1.4.0", - "igd-next", - "instant", - "iroh-base", - "iroh-metrics 0.37.0", - "iroh-quinn", - "iroh-quinn-proto", - "iroh-quinn-udp", + "ipnet", + "iroh-base 0.97.0", + "iroh-metrics", "iroh-relay", "n0-error", "n0-future", "n0-watcher", - "netdev", "netwatch", + "noq", + "noq-proto", + "noq-udp", + "papaya", "pin-project", "pkarr", "pkcs8 0.11.0-rc.11", + "portable-atomic", "portmapper", - "rand 0.9.3", - "reqwest 0.12.28", + "rand 0.9.4", + "reqwest", + "rustc-hash 2.1.2", "rustls", "rustls-pki-types", - "rustls-platform-verifier 0.5.3", "rustls-webpki", "serde", "smallvec", - "strum", + "strum 0.28.0", + "sync_wrapper", "time", "tokio", "tokio-stream", @@ -4431,7 +4492,6 @@ dependencies = [ "url", "wasm-bindgen-futures", "webpki-roots", - "z32", ] [[package]] @@ -4453,12 +4513,31 @@ dependencies = [ ] [[package]] -name = "iroh-blobs" +name = "iroh-base" version = "0.97.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c901304c1c28f257fcf9aae8c9149e54e0baf62f5eb2788cecde3bf1206a04e6" +checksum = "55a354e3396b62c14717ee807dfee9a7f43f6dad47e4ac0fd1d49f1ffad14ef0" +dependencies = [ + "curve25519-dalek 5.0.0-pre.1", + "data-encoding", + "derive_more 2.1.1", + "digest 0.11.0-rc.10", + "ed25519-dalek 3.0.0-pre.1", + "n0-error", + "rand_core 0.9.5", + "serde", + "sha2 0.11.0-rc.2", + "url", + "zeroize", + "zeroize_derive", +] + +[[package]] +name = "iroh-blobs" +version = "0.99.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b06914e77bd07bc1b3600096be66e2a63d391e8f4a901f61771630e20f2116" dependencies = [ - "anyhow", "arrayvec", "bao-tree", "bytes", @@ -4470,18 +4549,17 @@ dependencies = [ "genawaiter", "hex", "iroh", - "iroh-base", + "iroh-base 0.97.0", "iroh-io", - "iroh-metrics 0.37.0", - "iroh-quinn", - "iroh-tickets", + "iroh-metrics", + "iroh-tickets 0.4.0", "irpc", "n0-error", "n0-future", - "n0-snafu", "nested_enum_utils", + "noq", "postcard", - "rand 0.9.3", + "rand 0.9.4", "range-collections", "redb", "ref-cast", @@ -4489,7 +4567,6 @@ dependencies = [ "self_cell", "serde", "smallvec", - "snafu", "tokio", "tracing", ] @@ -4507,21 +4584,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "iroh-metrics" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e3381da7c93c12d353230c74bba26131d1c8bf3a4d8af0fec041546454582e" -dependencies = [ - "iroh-metrics-derive", - "itoa", - "n0-error", - "postcard", - "ryu", - "serde", - "tracing", -] - [[package]] name = "iroh-metrics" version = "0.38.3" @@ -4550,54 +4612,11 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "iroh-n0des" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49c691355d4b62e98a55e7d3fcf98ea3b800e7948c633cf937e7d31abe332f53" -dependencies = [ - "anyhow", - "bytes", - "derive_more 2.1.1", - "ed25519-dalek 3.0.0-pre.1", - "futures-buffered", - "getrandom 0.3.4", - "iroh", - "iroh-metrics 0.37.0", - "iroh-n0des-macro", - "iroh-tickets", - "irpc", - "irpc-iroh", - "n0-error", - "n0-future", - "postcard", - "rand 0.9.3", - "rcan", - "serde", - "serde_json", - "strum", - "thiserror 2.0.18", - "tokio", - "tracing", - "tracing-subscriber", - "uuid", -] - -[[package]] -name = "iroh-n0des-macro" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e15d38b6ae3d9480e49883bea72880f80d595276e34090f5096d844e6f7f5e40" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "iroh-proxy-utils" version = "0.1.0" -source = "git+https://github.com/n0-computer/iroh-proxy-utils?rev=38ef14f7bc215348d47987563bb1b5198cc91f40#38ef14f7bc215348d47987563bb1b5198cc91f40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250d4c136ea9b2226f7f50abc774129e8c59328eede75396d9dc618ec5548150" dependencies = [ "bytes", "derive_more 2.1.1", @@ -4609,11 +4628,11 @@ dependencies = [ "hyper-util", "iroh", "iroh-blobs", - "iroh-metrics 0.38.3", + "iroh-metrics", "n0-error", "n0-future", "pin-project", - "reqwest 0.12.28", + "reqwest", "tokio", "tokio-util", "tracing", @@ -4676,9 +4695,9 @@ dependencies = [ [[package]] name = "iroh-relay" -version = "0.95.1" +version = "0.97.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43fbdf2aeffa7d6ede1a31f6570866c2199b1cee96a0b563994623795d1bac2c" +checksum = "d786b260cadfe82ae0b6a9e372e8c78949096a06c857d1c3521355cefced0f55" dependencies = [ "blake3", "bytes", @@ -4691,36 +4710,70 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", - "iroh-base", - "iroh-metrics 0.37.0", - "iroh-quinn", - "iroh-quinn-proto", + "iroh-base 0.97.0", + "iroh-metrics", "lru", "n0-error", "n0-future", + "noq", + "noq-proto", "num_enum", "pin-project", "pkarr", "postcard", - "rand 0.9.3", - "reqwest 0.12.28", + "rand 0.9.4", + "reqwest", "rustls", "rustls-pki-types", "serde", "serde_bytes", - "sha1 0.11.0-rc.2", - "strum", + "strum 0.28.0", "tokio", "tokio-rustls", "tokio-util", "tokio-websockets", "tracing", "url", + "vergen-gitcl", "webpki-roots", "ws_stream_wasm", "z32", ] +[[package]] +name = "iroh-services" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "983bab90919b719b2f0c73277c78f7d77c9eccb5e9fdcbab4383be97a233c489" +dependencies = [ + "anyhow", + "built 0.7.7", + "bytes", + "derive_more 2.1.1", + "ed25519-dalek 3.0.0-pre.1", + "futures-buffered", + "getrandom 0.3.4", + "iroh", + "iroh-metrics", + "iroh-tickets 0.4.0", + "irpc", + "irpc-iroh", + "n0-error", + "n0-future", + "portmapper", + "postcard", + "rand 0.9.4", + "rcan", + "serde", + "serde_json", + "strum 0.27.2", + "thiserror 2.0.18", + "tokio", + "tracing", + "tracing-subscriber", + "uuid", +] + [[package]] name = "iroh-tickets" version = "0.2.0" @@ -4729,7 +4782,21 @@ checksum = "1a322053cacddeca222f0999ce3cf6aa45c64ae5ad8c8911eac9b66008ffbaa5" dependencies = [ "data-encoding", "derive_more 2.1.1", - "iroh-base", + "iroh-base 0.95.1", + "n0-error", + "postcard", + "serde", +] + +[[package]] +name = "iroh-tickets" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab64bac4bb573b9cfd2142bd2876ed65ca792efbc4398361a4ee51a0f9afbed6" +dependencies = [ + "data-encoding", + "derive_more 2.1.1", + "iroh-base 0.97.0", "n0-error", "postcard", "serde", @@ -4737,16 +4804,16 @@ dependencies = [ [[package]] name = "irpc" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bee97aaa18387c4f0aae61058195dc9f9dea3e41c0e272973fe3e9bf611563d" +checksum = "4f47b7c52662d673df377b5ac40c121c7ff56eb764e520fae6543686132f7957" dependencies = [ "futures-buffered", "futures-util", - "iroh-quinn", "irpc-derive", "n0-error", "n0-future", + "noq", "postcard", "rcgen", "rustls", @@ -4759,9 +4826,9 @@ dependencies = [ [[package]] name = "irpc-derive" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58148196d2230183c9679431ac99b57e172000326d664e8456fa2cd27af6505a" +checksum = "83c1a4b460634aeed6dc01236a0047867de70e30562d91a0ad031dcb3ac33fb4" dependencies = [ "proc-macro2", "quote", @@ -4770,13 +4837,13 @@ dependencies = [ [[package]] name = "irpc-iroh" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17b254105bdaf86bc63786a37f81ba40e84d861b870d7626b51e14ebbb2ba50" +checksum = "6dd882f8cc059ac28ff54195a4933226807206c939c14680281c0ddb8434b3a8" dependencies = [ "getrandom 0.3.4", "iroh", - "iroh-base", + "iroh-base 0.97.0", "irpc", "n0-error", "n0-future", @@ -4986,7 +5053,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "serde", "unicode-segmentation", ] @@ -5143,26 +5210,25 @@ dependencies = [ "hyper", "hyper-util", "iroh", - "iroh-base", + "iroh-base 0.97.0", "iroh-blobs", - "iroh-metrics 0.38.3", - "iroh-n0des", + "iroh-metrics", "iroh-proxy-utils", "iroh-quinn", "iroh-relay", - "iroh-tickets", + "iroh-services", + "iroh-tickets 0.2.0", "k8s-openapi", "kube", "log", "n0-error", "n0-future", "n0-tracing-test", - "n0des-local", "open", "openidconnect", "postcard", - "rand 0.9.3", - "reqwest 0.12.28", + "rand 0.9.4", + "reqwest", "secrecy", "semver", "sentry", @@ -5207,9 +5273,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.184" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libfuzzer-sys" @@ -5360,9 +5426,9 @@ dependencies = [ [[package]] name = "lru" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ "hashbrown 0.16.1", ] @@ -5379,6 +5445,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "mac-addr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d25b0e0b648a86960ac23b7ad4abb9717601dec6f66c165f5b037f3f03065f" + [[package]] name = "macro-string" version = "0.1.4" @@ -5690,19 +5762,6 @@ dependencies = [ "web-time", ] -[[package]] -name = "n0-snafu" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1815107e577a95bfccedb4cfabc73d709c0db6d12de3f14e0f284a8c5036dc4f" -dependencies = [ - "anyhow", - "btparse", - "color-backtrace", - "snafu", - "tracing-error", -] - [[package]] name = "n0-tracing-test" version = "0.3.0" @@ -5726,30 +5785,15 @@ dependencies = [ [[package]] name = "n0-watcher" -version = "0.5.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38acf13c1ddafc60eb7316d52213467f8ccb70b6f02b65e7d97f7799b1f50be4" +checksum = "38795f7932e6e9d1c6e989270ef5b3ff24ebb910e2c9d4bed2d28d8bae3007dc" dependencies = [ "derive_more 2.1.1", "n0-error", "n0-future", ] -[[package]] -name = "n0des-local" -version = "0.1.0" -dependencies = [ - "iroh", - "iroh-n0des", - "irpc", - "irpc-iroh", - "n0-error", - "rand 0.9.3", - "tokio", - "tracing", - "tracing-subscriber", -] - [[package]] name = "native-tls" version = "0.2.18" @@ -5773,7 +5817,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "jni-sys 0.3.1", "log", "ndk-sys", @@ -5811,18 +5855,23 @@ dependencies = [ [[package]] name = "netdev" -version = "0.38.2" +version = "0.40.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ab878b4c90faf36dab10ea51d48c69ae9019bcca47c048a7c9b273d5d7a823" +checksum = "1b0a0096d9613ee878dba89bbe595f079d373e3f1960d882e4f2f78ff9c30a0a" dependencies = [ + "block2", + "dispatch2", "dlopen2 0.5.0", "ipnet", "libc", + "mac-addr", "netlink-packet-core", "netlink-packet-route", "netlink-sys", + "objc2-core-foundation", + "objc2-system-configuration", "once_cell", - "system-configuration 0.6.1", + "plist", "windows-sys 0.59.0", ] @@ -5837,11 +5886,11 @@ dependencies = [ [[package]] name = "netlink-packet-route" -version = "0.25.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec2f5b6839be2a19d7fa5aab5bc444380f6311c2b693551cb80f45caaa7b5ef" +checksum = "df9854ea6ad14e3f4698a7f03b65bce0833dd2d81d594a0e4a984170537146b6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "log", "netlink-packet-core", @@ -5876,15 +5925,14 @@ dependencies = [ [[package]] name = "netwatch" -version = "0.12.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f2acd376ef48b6c326abf3ba23c449e0cb8aa5c2511d189dd8a8a3bfac889b" +checksum = "3b1b27babe89ef9f2237bc6c028bea24fa84163a1b6f8f17ff93573ebd7d861f" dependencies = [ "atomic-waker", "bytes", "cfg_aliases", "derive_more 2.1.1", - "iroh-quinn-udp", "js-sys", "libc", "n0-error", @@ -5895,6 +5943,9 @@ dependencies = [ "netlink-packet-route", "netlink-proto", "netlink-sys", + "noq-udp", + "objc2-core-foundation", + "objc2-system-configuration", "pin-project-lite", "serde", "socket2 0.6.3", @@ -5920,12 +5971,21 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", ] +[[package]] +name = "no_std_io2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b51ed7824b6e07d354605f4abb3d9d300350701299da96642ee084f5ce631550" +dependencies = [ + "memchr", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -5936,26 +5996,88 @@ checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "noq" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df966fb44ac763bc86da97fa6c811c54ae82ef656575949f93c6dae0c9f09bf" dependencies = [ - "memchr", - "minimal-lexical", + "bytes", + "cfg_aliases", + "noq-proto", + "noq-udp", + "pin-project-lite", + "rustc-hash 2.1.2", + "rustls", + "socket2 0.6.3", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "web-time", ] [[package]] -name = "nom" -version = "8.0.0" +name = "noq-proto" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +checksum = "5c61b72abd670eebc05b5cf720e077b04a3ef3354bc7bc19f1c3524cb424db7b" dependencies = [ - "memchr", + "aes-gcm", + "bytes", + "derive_more 2.1.1", + "enum-assoc", + "fastbloom", + "getrandom 0.3.4", + "identity-hash", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash 2.1.2", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier 0.6.2", + "slab", + "sorted-index-buffer", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", ] [[package]] -name = "noop_proc_macro" -version = "0.3.0" +name = "noq-udp" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +checksum = "bb9be4fedd6b98f3ba82ccd3506f4d0219fb723c3f97c67e12fe1494aa020e44" +dependencies = [ + "cfg_aliases", + "libc", + "socket2 0.6.3", + "tracing", + "windows-sys 0.61.2", +] [[package]] name = "ntimestamp" @@ -6107,7 +6229,7 @@ dependencies = [ "getrandom 0.2.17", "http 1.4.0", "rand 0.8.5", - "reqwest 0.12.28", + "reqwest", "serde", "serde_json", "serde_path_to_error", @@ -6141,7 +6263,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -6162,7 +6284,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-foundation", ] @@ -6173,7 +6295,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-foundation", ] @@ -6184,8 +6306,10 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", + "block2", "dispatch2", + "libc", "objc2", ] @@ -6195,7 +6319,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "dispatch2", "objc2", "objc2-core-foundation", @@ -6228,7 +6352,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-core-foundation", "objc2-core-graphics", @@ -6240,7 +6364,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-core-foundation", "objc2-core-graphics", @@ -6268,7 +6392,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -6281,7 +6405,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-core-foundation", ] @@ -6292,19 +6416,44 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "objc2", "objc2-core-foundation", "objc2-foundation", ] +[[package]] +name = "objc2-security" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" +dependencies = [ + "bitflags 2.11.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-system-configuration" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" +dependencies = [ + "bitflags 2.11.1", + "dispatch2", + "libc", + "objc2", + "objc2-core-foundation", + "objc2-security", +] + [[package]] name = "objc2-ui-kit" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "objc2", "objc2-cloud-kit", @@ -6335,7 +6484,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "objc2", "objc2-app-kit", @@ -6386,6 +6535,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "open" version = "5.3.3" @@ -6430,11 +6585,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "bfe4646e360ec77dff7dde40ed3d6c5fee52d156ef4a62f53973d38294dad87f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "foreign-types 0.3.2", "libc", @@ -6462,9 +6617,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "ad2f2c0eba47118757e4c6d2bff2838f3e0523380021356e7875e858372ce644" dependencies = [ "cc", "libc", @@ -6552,6 +6707,16 @@ dependencies = [ "system-deps", ] +[[package]] +name = "papaya" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "997ee03cd38c01469a7046643714f0ad28880bcb9e6679ff0666e24817ca19b7" +dependencies = [ + "equivalent", + "seize", +] + [[package]] name = "parking" version = "2.2.1" @@ -6838,29 +7003,17 @@ version = "5.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7bfb9143bbba379f246211eb68074d78db9cc048e4c5701f3b0e6cb1ec67ca2" dependencies = [ - "async-compat", "base32", "bytes", "cfg_aliases", "document-features", - "dyn-clone", "ed25519-dalek 3.0.0-pre.1", - "futures-buffered", - "futures-lite", "getrandom 0.4.2", - "log", - "lru", "ntimestamp", - "reqwest 0.13.2", "self_cell", "serde", - "sha1_smol", "simple-dns", "thiserror 2.0.18", - "tokio", - "tracing", - "url", - "wasm-bindgen-futures", ] [[package]] @@ -6896,9 +7049,22 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plist" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +dependencies = [ + "base64 0.22.1", + "indexmap 2.14.0", + "quick-xml", + "serde", + "time", +] [[package]] name = "png" @@ -6919,7 +7085,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "crc32fast", "fdeflate", "flate2", @@ -6933,12 +7099,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] -name = "poly1305" -version = "0.9.0-rc.2" +name = "polyval" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb78a635f75d76d856374961deecf61031c0b6f928c83dc9c0924ab6c019c298" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ + "cfg-if", "cpufeatures 0.2.17", + "opaque-debug", "universal-hash", ] @@ -6953,9 +7121,9 @@ dependencies = [ [[package]] name = "portmapper" -version = "0.12.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b575f975dcf03e258b0c7ab3f81497d7124f508884c37da66a7314aa2a8d467" +checksum = "74748bc706fa6b6aebac6bbe0bbe0de806b384cb5c557ea974f771360a4e3858" dependencies = [ "base64 0.22.1", "bytes", @@ -6964,12 +7132,12 @@ dependencies = [ "futures-util", "hyper-util", "igd-next", - "iroh-metrics 0.37.0", + "iroh-metrics", "libc", "n0-error", "netwatch", "num_enum", - "rand 0.9.3", + "rand 0.9.4", "serde", "smallvec", "socket2 0.6.3", @@ -7237,6 +7405,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.9" @@ -7263,11 +7440,10 @@ version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ - "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.3", + "rand 0.9.4", "ring", "rustc-hash 2.1.2", "rustls", @@ -7341,9 +7517,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -7450,7 +7626,7 @@ dependencies = [ "av-scenechange", "av1-grain", "bitstream-io", - "built", + "built 0.8.0", "cfg-if", "interpolate_name", "itertools 0.14.0", @@ -7464,7 +7640,7 @@ dependencies = [ "num-traits", "paste", "profiling", - "rand 0.9.3", + "rand 0.9.4", "rand_chacha 0.9.0", "simd_helpers", "thiserror 2.0.18", @@ -7501,9 +7677,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -7533,7 +7709,7 @@ dependencies = [ "hex", "n0-future", "postcard", - "rand 0.9.3", + "rand 0.9.4", "serde", ] @@ -7566,7 +7742,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -7709,41 +7885,6 @@ dependencies = [ "webpki-roots", ] -[[package]] -name = "reqwest" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "http 1.4.0", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier 0.6.2", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "resolv-conf" version = "0.7.6" @@ -7866,7 +8007,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -7875,9 +8016,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "aws-lc-rs", "log", @@ -7961,9 +8102,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.11" +version = "0.103.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20a6af516fea4b20eccceaf166e8aa666ac996208e8a644ce3ef5aa783bc7cd4" +checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" dependencies = [ "aws-lc-rs", "ring", @@ -7983,16 +8124,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" -[[package]] -name = "salsa20" -version = "0.11.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3ff3b81c8a6e381bc1673768141383f9328048a60edddcfc752a8291a138443" -dependencies = [ - "cfg-if", - "cipher", -] - [[package]] name = "same-file" version = "1.0.6" @@ -8066,7 +8197,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct 0.2.0", + "base16ct", "der 0.7.10", "generic-array", "pkcs8 0.10.2", @@ -8089,7 +8220,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -8106,6 +8237,16 @@ dependencies = [ "libc", ] +[[package]] +name = "seize" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "selectors" version = "0.24.0" @@ -8135,6 +8276,10 @@ name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "send_wrapper" @@ -8153,7 +8298,7 @@ checksum = "989425268ab5c011e06400187eed6c298272f8ef913e49fcadc3fda788b45030" dependencies = [ "httpdate", "native-tls", - "reqwest 0.12.28", + "reqwest", "sentry-actix", "sentry-backtrace", "sentry-contexts", @@ -8209,7 +8354,7 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "deaa38b94e70820ff3f1f9db3c8b0aef053b667be130f618e615e0ff2492cbcc" dependencies = [ - "rand 0.9.3", + "rand 0.9.4", "sentry-types", "serde", "serde_json", @@ -8242,7 +8387,7 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fac841c7050aa73fc2bec8f7d8e9cb1159af0b3095757b99820823f3e54e5080" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "sentry-backtrace", "sentry-core", "tracing-core", @@ -8257,7 +8402,7 @@ checksum = "e477f4d4db08ddb4ab553717a8d3a511bc9e81dde0c808c680feacbb8105c412" dependencies = [ "debugid", "hex", - "rand 0.9.3", + "rand 0.9.4", "serde", "serde_json", "thiserror 2.0.18", @@ -8473,16 +8618,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "serdect" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9af4a3e75ebd5599b30d4de5768e00b5095d518a79fefc3ecbaf77e665d1ec06" -dependencies = [ - "base16ct 1.0.0", - "serde", -] - [[package]] name = "servo_arc" version = "0.2.0" @@ -8504,23 +8639,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha1" -version = "0.11.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e046edf639aa2e7afb285589e5405de2ef7e61d4b0ac1e30256e3eab911af9" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.11.0-rc.3", -] - -[[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - [[package]] name = "sha2" version = "0.10.9" @@ -8540,7 +8658,7 @@ checksum = "d1e3878ab0f98e35b2df35fe53201d088299b41a6bb63e3e34dada2ac4abd924" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest 0.11.0-rc.3", + "digest 0.11.0-rc.10", ] [[package]] @@ -8631,7 +8749,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df350943049174c4ae8ced56c604e28270258faec12a6a48637a7655287c9ce0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -8706,7 +8824,6 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" dependencies = [ - "backtrace", "snafu-derive", ] @@ -8742,6 +8859,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "sorted-index-buffer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea06cc588e43c632923a55450401b8f25e628131571d4e1baea1bdfdb2b5ed06" + [[package]] name = "soup3" version = "0.5.0" @@ -8857,7 +8980,16 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", +] + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros 0.28.0", ] [[package]] @@ -8872,6 +9004,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "subsecond" version = "0.7.4" @@ -8959,24 +9103,13 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.11.0", - "core-foundation 0.9.4", - "system-configuration-sys", -] - [[package]] name = "system-configuration" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -9016,7 +9149,7 @@ version = "0.34.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "core-foundation 0.10.1", "core-graphics 0.25.0", @@ -9223,9 +9356,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.51.1" +version = "1.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" +checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776" dependencies = [ "bytes", "libc", @@ -9310,7 +9443,7 @@ dependencies = [ "getrandom 0.3.4", "http 1.4.0", "httparse", - "rand 0.9.3", + "rand 0.9.4", "ring", "rustls-pki-types", "simdutf8", @@ -9418,7 +9551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "base64 0.22.1", - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-util", "http 1.4.0", @@ -9489,16 +9622,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-error" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" -dependencies = [ - "tracing", - "tracing-subscriber", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -9600,9 +9723,9 @@ dependencies = [ "httparse", "log", "native-tls", - "rand 0.9.3", + "rand 0.9.4", "rustls", - "sha1 0.10.6", + "sha1", "thiserror 2.0.18", "utf-8", ] @@ -9618,8 +9741,8 @@ dependencies = [ "http 1.4.0", "httparse", "log", - "rand 0.9.3", - "sha1 0.10.6", + "rand 0.9.4", + "sha1", "thiserror 2.0.18", "utf-8", ] @@ -9671,11 +9794,11 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" -version = "0.6.0-rc.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55be643b40a21558f44806b53ee9319595bc7ca6896372e4e08e5d7d83c9cd6" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common 0.2.0-rc.4", + "crypto-common 0.1.7", "subtle", ] @@ -9792,6 +9915,54 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vergen" +version = "9.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b849a1f6d8639e8de261e81ee0fc881e3e3620db1af9f2e0da015d4382ceaf75" +dependencies = [ + "anyhow", + "derive_builder", + "rustversion", + "vergen-lib 9.1.0", +] + +[[package]] +name = "vergen-gitcl" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9dfc1de6eb2e08a4ddf152f1b179529638bedc0ea95e6d667c014506377aefe" +dependencies = [ + "anyhow", + "derive_builder", + "rustversion", + "time", + "vergen", + "vergen-lib 0.1.6", +] + +[[package]] +name = "vergen-lib" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b07e6010c0f3e59fcb164e0163834597da68d1f864e2b8ca49f74de01e9c166" +dependencies = [ + "anyhow", + "derive_builder", + "rustversion", +] + +[[package]] +name = "vergen-lib" +version = "9.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b34a29ba7e9c59e62f229ae1932fb1b8fb8a6fdcc99215a641913f5f5a59a569" +dependencies = [ + "anyhow", + "derive_builder", + "rustversion", +] + [[package]] name = "version-compare" version = "0.2.1" @@ -9971,7 +10142,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap 2.14.0", "semver", @@ -10698,7 +10869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags 2.11.1", "indexmap 2.14.0", "log", "serde", @@ -10730,9 +10901,9 @@ dependencies = [ [[package]] name = "wmi" -version = "0.17.3" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120d8c2b6a7c96c27bf4a7947fd7f02d73ca7f5958b8bd72a696e46cb5521ee6" +checksum = "7c81b85c57a57500e56669586496bf2abd5cf082b9d32995251185d105208b64" dependencies = [ "chrono", "futures", diff --git a/Cargo.toml b/Cargo.toml index 65a92c5..10fc977 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,13 +2,13 @@ members = [ "cli", "lib", - "n0des-local", "ui" ] +exclude = ["n0des-local"] resolver = "2" [workspace.dependencies] -iroh-proxy-utils = { git = "https://github.com/n0-computer/iroh-proxy-utils", rev = "38ef14f7bc215348d47987563bb1b5198cc91f40" } +iroh-proxy-utils = "0.1.0" lib = { path = "lib" } # n0-snafu 0.2.2 requires ^0.7.0 but color-backtrace 0.7.3 removed # `is_dependency_code` which n0-snafu uses. This constraint intersects with @@ -26,12 +26,12 @@ hex = "0.4.3" http = "1" hyper = "1" hyper-util = { version = "0.1.19", features = ["full"] } -iroh = { version = "0.95", default-features = false } -iroh-base = { version = "0.95" } +iroh = { version = "0.97", default-features = false } +iroh-base = { version = "0.97" } iroh-tickets = "0.2" iroh-metrics = "0.38" -iroh-n0des = { version = "0.8" } -iroh-relay = { version = "0.95" } +iroh-services = { version = "0.12", features = ["net_diagnostics", "client_host"] } +iroh-relay = { version = "0.97" } log = "0.4" open = "5" openidconnect = "4.0.1" diff --git a/lib/Cargo.toml b/lib/Cargo.toml index db8f1dc..9c96eda 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -18,7 +18,7 @@ hyper.workspace = true hyper-util.workspace = true iroh-base.workspace = true iroh-metrics.workspace = true -iroh-n0des.workspace = true +iroh-services.workspace = true iroh-proxy-utils.workspace = true iroh-tickets.workspace = true iroh.workspace = true @@ -46,7 +46,7 @@ tracing-subscriber.workspace = true tracing.workspace = true url.workspace = true uuid.workspace = true -iroh-blobs = "0.97.0" +iroh-blobs = "0.99.0" httparse = "1.10.1" ttl_cache = "0.5.1" askama = "0.15.1" @@ -60,7 +60,6 @@ http-body-util = "0.1.3" hyper = { version = "1.8.1", features = ["full"] } hyper-util = { version = "0.1.19", features = ["full"] } n0-tracing-test = "0.3.0" -n0des-local = { path = "../n0des-local" } tempfile = "3" [features] diff --git a/lib/src/diagnostics.rs b/lib/src/diagnostics.rs new file mode 100644 index 0000000..f9d6148 --- /dev/null +++ b/lib/src/diagnostics.rs @@ -0,0 +1,126 @@ +use std::{str::FromStr, sync::Arc}; + +use iroh::Endpoint; +use iroh_services::{ApiSecret, Client, ClientHost, caps::NetDiagnosticsCap}; +use n0_error::{Result, StackResultExt, StdResultExt}; +use serde::{Deserialize, Serialize}; +use tracing::{info, warn}; + +use crate::Repo; + +const IROH_SERVICES_API_KEY: &str = "IROH_SERVICES_API_KEY"; + +/// Persisted diagnostics preferences. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DiagnosticsSettings { + /// Whether network diagnostics collection is enabled. + #[serde(default = "default_enabled")] + pub enabled: bool, + /// Whether the user has seen the opt-in prompt. + #[serde(default)] + pub prompted: bool, +} + +fn default_enabled() -> bool { + true +} + +impl Default for DiagnosticsSettings { + fn default() -> Self { + Self { + enabled: true, + prompted: false, + } + } +} + +/// Read the iroh-services API key from the environment, falling back to a +/// build-time value. Returns `None` when neither is set. +pub(crate) fn iroh_services_api_key_from_env() -> Result> { + let key_str = match std::env::var(IROH_SERVICES_API_KEY) { + Ok(s) => s, + Err(_) => match option_env!("BUILD_IROH_SERVICES_API_KEY") { + None => return Ok(None), + Some(s) => s.to_string(), + }, + }; + let api_secret = + ApiSecret::from_str(&key_str).context("Failed to parse iroh-services API key from env")?; + Ok(Some(api_secret)) +} + +/// State returned after diagnostics are started, kept alive to maintain +/// the ClientHost connection. +#[derive(Debug, Clone)] +pub struct DiagnosticsHandle { + _client: Arc, +} + +/// Start net diagnostics: connect to iroh-services, grant the +/// `NetDiagnosticsCap::GetAny` capability, and return a [`ClientHost`] that +/// must be registered on the Router so the service can dial back in. +/// +/// Returns `(ClientHost, DiagnosticsHandle)` on success. The caller must +/// `.accept(CLIENT_HOST_ALPN, host)` on its Router builder. +pub async fn start_diagnostics( + endpoint: &Endpoint, + api_secret: ApiSecret, +) -> Result<(ClientHost, DiagnosticsHandle)> { + let remote_id = api_secret.addr().id; + info!(remote=%remote_id.fmt_short(), "connecting to iroh-services for net diagnostics"); + + let client = Client::builder(endpoint) + .api_secret(api_secret)? + .build() + .await + .std_context("Failed to connect to iroh-services for diagnostics")?; + + // Grant the capability in a background task so we don't block startup + // if the remote is temporarily unavailable. + let grant_client = client.clone(); + tokio::spawn(async move { + if let Err(err) = grant_client + .grant_capability(remote_id, vec![NetDiagnosticsCap::GetAny]) + .await + { + warn!("Failed to grant net diagnostics capability: {err:#}"); + } else { + info!("Granted NetDiagnosticsCap::GetAny to iroh-services"); + } + }); + + let host = ClientHost::new(endpoint); + let handle = DiagnosticsHandle { + _client: Arc::new(client), + }; + Ok((host, handle)) +} + +impl Repo { + const DIAGNOSTICS_SETTINGS_FILE: &str = "diagnostics_settings.yml"; + + /// Load diagnostics settings from disk, returning defaults if the file + /// does not exist. + pub async fn diagnostics_settings(&self) -> Result { + let path = self.path().join(Self::DIAGNOSTICS_SETTINGS_FILE); + if !path.exists() { + return Ok(DiagnosticsSettings::default()); + } + let data = tokio::fs::read_to_string(&path) + .await + .context("failed to read diagnostics settings")?; + let settings: DiagnosticsSettings = + serde_yml::from_str(&data).std_context("failed to parse diagnostics settings")?; + Ok(settings) + } + + /// Persist diagnostics settings to disk. + pub async fn write_diagnostics_settings(&self, settings: &DiagnosticsSettings) -> Result<()> { + let path = self.path().join(Self::DIAGNOSTICS_SETTINGS_FILE); + let data = serde_yml::to_string(settings).anyerr()?; + tokio::fs::write(&path, data) + .await + .context("failed to write diagnostics settings")?; + Ok(()) + } +} diff --git a/lib/src/gateway.rs b/lib/src/gateway.rs index a0f06e3..c238bab 100644 --- a/lib/src/gateway.rs +++ b/lib/src/gateway.rs @@ -300,15 +300,10 @@ impl ErrorResponseWriter { fn has_existing_peer_conn(endpoint: &Endpoint) -> bool { let endpoint_metrics = endpoint.metrics(); - let direct_current = endpoint_metrics - .magicsock - .num_direct_conns_added + let conns_current = endpoint_metrics + .socket + .num_conns_opened .get() - .saturating_sub(endpoint_metrics.magicsock.num_direct_conns_removed.get()); - let relay_current = endpoint_metrics - .magicsock - .num_relay_conns_added - .get() - .saturating_sub(endpoint_metrics.magicsock.num_relay_conns_removed.get()); - direct_current + relay_current > 0 + .saturating_sub(endpoint_metrics.socket.num_conns_closed.get()); + conns_current > 0 } diff --git a/lib/src/gateway/metrics.rs b/lib/src/gateway/metrics.rs index d8c626c..f2d928d 100644 --- a/lib/src/gateway/metrics.rs +++ b/lib/src/gateway/metrics.rs @@ -173,30 +173,21 @@ impl GatewayMetrics { fn render(&self, endpoint: &Endpoint, downstream_metrics: &Arc) -> String { let endpoint_metrics = endpoint.metrics(); - let direct_added = endpoint_metrics.magicsock.num_direct_conns_added.get(); - let direct_removed = endpoint_metrics.magicsock.num_direct_conns_removed.get(); - let relay_added = endpoint_metrics.magicsock.num_relay_conns_added.get(); - let relay_removed = endpoint_metrics.magicsock.num_relay_conns_removed.get(); - let relay_send_errors = endpoint_metrics.magicsock.send_relay_error.get(); - let relay_home_changes = endpoint_metrics.magicsock.relay_home_change.get(); - let handshake_success = endpoint_metrics - .magicsock - .connection_handshake_success - .get(); - let endpoints_contacted = endpoint_metrics.magicsock.endpoints_contacted.get(); - let endpoints_contacted_directly = endpoint_metrics - .magicsock - .endpoints_contacted_directly - .get(); - let path_ping_failures = endpoint_metrics.magicsock.path_ping_failures.get(); - let path_marked_outdated = endpoint_metrics.magicsock.path_marked_outdated.get(); - let path_failure_resets = endpoint_metrics.magicsock.path_failure_resets.get(); + let direct_added = endpoint_metrics.socket.transport_ip_paths_added.get(); + let direct_removed = endpoint_metrics.socket.transport_ip_paths_removed.get(); + let relay_added = endpoint_metrics.socket.transport_relay_paths_added.get(); + let relay_removed = endpoint_metrics.socket.transport_relay_paths_removed.get(); + let relay_home_changes = endpoint_metrics.socket.relay_home_change.get(); + let conns_opened = endpoint_metrics.socket.num_conns_opened.get(); + let holepunch_attempts = endpoint_metrics.socket.holepunch_attempts.get(); let direct_current = direct_added.saturating_sub(direct_removed); let relay_current = relay_added.saturating_sub(relay_removed); - let recv_total = endpoint_metrics.magicsock.recv_data_ipv4.get() - + endpoint_metrics.magicsock.recv_data_ipv6.get() - + endpoint_metrics.magicsock.recv_data_relay.get(); - let send_total = endpoint_metrics.magicsock.send_data.get(); + let recv_total = endpoint_metrics.socket.recv_data_ipv4.get() + + endpoint_metrics.socket.recv_data_ipv6.get() + + endpoint_metrics.socket.recv_data_relay.get(); + let send_total = endpoint_metrics.socket.send_ipv4.get() + + endpoint_metrics.socket.send_ipv6.get() + + endpoint_metrics.socket.send_relay.get(); let mut downstream_openmetrics = String::new(); let mut registry = Registry::default(); @@ -246,10 +237,10 @@ impl GatewayMetrics { "# TYPE iroh_gateway_upstream_failures_total counter\n", "iroh_gateway_upstream_failures_total{{class=\"5xx\",peer_conn_state=\"with_existing\"}} {}\n", "iroh_gateway_upstream_failures_total{{class=\"5xx\",peer_conn_state=\"without_existing\"}} {}\n", - "# HELP iroh_gateway_iroh_recv_bytes_total Total iroh magicsock bytes received.\n", + "# HELP iroh_gateway_iroh_recv_bytes_total Total iroh socket bytes received.\n", "# TYPE iroh_gateway_iroh_recv_bytes_total counter\n", "iroh_gateway_iroh_recv_bytes_total {}\n", - "# HELP iroh_gateway_iroh_send_bytes_total Total iroh magicsock bytes sent.\n", + "# HELP iroh_gateway_iroh_send_bytes_total Total iroh socket bytes sent.\n", "# TYPE iroh_gateway_iroh_send_bytes_total counter\n", "iroh_gateway_iroh_send_bytes_total {}\n\n", "# HELP iroh_gateway_quic_connections_opened_total QUIC peer connections opened by transport path.\n", @@ -264,16 +255,11 @@ impl GatewayMetrics { "# TYPE iroh_gateway_quic_connections_current gauge\n", "iroh_gateway_quic_connections_current{{path=\"direct\"}} {}\n", "iroh_gateway_quic_connections_current{{path=\"relay\"}} {}\n\n", - "# HELP iroh_gateway_tunnel_connectivity_events_total Tunnel connectivity events from iroh magicsock state.\n", + "# HELP iroh_gateway_tunnel_connectivity_events_total Tunnel connectivity events from iroh socket state.\n", "# TYPE iroh_gateway_tunnel_connectivity_events_total counter\n", - "iroh_gateway_tunnel_connectivity_events_total{{event=\"relay_send_error\"}} {}\n", "iroh_gateway_tunnel_connectivity_events_total{{event=\"relay_home_change\"}} {}\n", - "iroh_gateway_tunnel_connectivity_events_total{{event=\"connection_handshake_success\"}} {}\n", - "iroh_gateway_tunnel_connectivity_events_total{{event=\"endpoints_contacted\"}} {}\n", - "iroh_gateway_tunnel_connectivity_events_total{{event=\"endpoints_contacted_directly\"}} {}\n", - "iroh_gateway_tunnel_connectivity_events_total{{event=\"path_ping_failures\"}} {}\n", - "iroh_gateway_tunnel_connectivity_events_total{{event=\"path_marked_outdated\"}} {}\n", - "iroh_gateway_tunnel_connectivity_events_total{{event=\"path_failure_resets\"}} {}\n\n", + "iroh_gateway_tunnel_connectivity_events_total{{event=\"conns_opened\"}} {}\n", + "iroh_gateway_tunnel_connectivity_events_total{{event=\"holepunch_attempts\"}} {}\n\n", ), self.requests_tunnel_total.load(Ordering::Relaxed), self.requests_origin_total.load(Ordering::Relaxed), @@ -316,14 +302,9 @@ impl GatewayMetrics { relay_removed, direct_current, relay_current, - relay_send_errors, relay_home_changes, - handshake_success, - endpoints_contacted, - endpoints_contacted_directly, - path_ping_failures, - path_marked_outdated, - path_failure_resets, + conns_opened, + holepunch_attempts, ) + &downstream_openmetrics } } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 2a81243..39df5fc 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -2,6 +2,7 @@ mod auth; pub mod config; pub mod datum_apis; pub mod datum_cloud; +pub mod diagnostics; pub mod gateway; pub mod heartbeat; mod http_user_agent; @@ -13,6 +14,7 @@ pub mod tunnels; pub mod update; pub use config::{Config, DiscoveryMode, GatewayConfig}; +pub use diagnostics::DiagnosticsSettings; pub use heartbeat::HeartbeatAgent; pub use http_user_agent::datum_http_user_agent; pub use node::*; diff --git a/lib/src/node.rs b/lib/src/node.rs index 92e21d7..69bd4c6 100644 --- a/lib/src/node.rs +++ b/lib/src/node.rs @@ -1,11 +1,12 @@ use std::{fmt::Debug, net::SocketAddr, str::FromStr, sync::Arc, time::Duration}; use iroh::{ - Endpoint, EndpointId, SecretKey, discovery::dns::DnsDiscovery, endpoint::default_relay_mode, + Endpoint, EndpointId, SecretKey, + address_lookup::dns::DnsAddressLookup, + endpoint::{default_relay_mode, presets}, protocol::Router, }; use iroh_base::RelayUrl; -use iroh_n0des::ApiSecret; use iroh_proxy_utils::upstream::UpstreamMetrics; use iroh_proxy_utils::{ ALPN as IROH_HTTP_CONNECT_ALPN, Authority, HttpProxyRequest, HttpProxyRequestKind, @@ -16,6 +17,7 @@ use iroh_proxy_utils::{ }; use iroh_relay::dns::{DnsProtocol, DnsResolver}; use iroh_relay::{RelayConfig, RelayMap}; +use iroh_services::{ApiSecret, CLIENT_HOST_ALPN}; use n0_error::{Result, StackResultExt, StdResultExt}; use tokio::{ net::TcpListener, @@ -24,6 +26,7 @@ use tokio::{ }; use tracing::{Instrument, debug, error_span, info, instrument, warn}; +use crate::diagnostics::{self, DiagnosticsHandle}; use crate::{ProxyState, Repo, StateWrapper, TcpProxyData, config::Config}; #[derive(Debug, Clone)] @@ -52,7 +55,8 @@ pub struct ListenNode { state: StateWrapper, repo: Repo, metrics: Arc, - _n0des: Option>, + _n0des: Option>, + _diagnostics: Option, } impl ListenNode { @@ -75,6 +79,31 @@ impl ListenNode { let upstream_proxy = UpstreamProxy::new(state.clone())?; let metrics = upstream_proxy.metrics(); + // Optionally start net diagnostics based on persisted settings. + let diagnostics_handle = match setup_diagnostics(&repo, &endpoint).await { + Ok(Some((host, handle))) => { + let router = Router::builder(endpoint) + .accept(IROH_HTTP_CONNECT_ALPN, upstream_proxy) + .accept(CLIENT_HOST_ALPN, host) + .spawn(); + + let this = Self { + repo, + router, + state, + metrics, + _n0des: n0des, + _diagnostics: Some(handle), + }; + return Ok(this); + } + Ok(None) => None, + Err(err) => { + warn!("Failed to start net diagnostics, continuing without: {err:#}"); + None + } + }; + let router = Router::builder(endpoint) .accept(IROH_HTTP_CONNECT_ALPN, upstream_proxy) .spawn(); @@ -85,6 +114,7 @@ impl ListenNode { state, metrics, _n0des: n0des, + _diagnostics: diagnostics_handle, }; Ok(this) } @@ -217,7 +247,7 @@ impl AuthHandler for StateWrapper { pub struct ConnectNode { endpoint: Endpoint, proxy: DownstreamProxy, - _n0des: Option>, + _n0des: Option>, } impl ConnectNode { @@ -305,20 +335,20 @@ impl OutboundProxyHandle { pub(crate) async fn build_endpoint(secret_key: SecretKey, common: &Config) -> Result { let relay_mode = relay_mode_from_env_or_build().await?; let mut builder = match common.discovery_mode { - crate::config::DiscoveryMode::Dns => { - Endpoint::empty_builder(relay_mode).secret_key(secret_key) - } + crate::config::DiscoveryMode::Dns => Endpoint::empty_builder() + .relay_mode(relay_mode) + .secret_key(secret_key), crate::config::DiscoveryMode::Default | crate::config::DiscoveryMode::Hybrid => { - Endpoint::builder() + Endpoint::builder(presets::N0) .relay_mode(relay_mode) .secret_key(secret_key) } }; if let Some(addr) = common.ipv4_addr { - builder = builder.bind_addr_v4(addr); + builder = builder.bind_addr(addr)?; } if let Some(addr) = common.ipv6_addr { - builder = builder.bind_addr_v6(addr); + builder = builder.bind_addr(addr)?; } match common.discovery_mode { crate::config::DiscoveryMode::Default => {} @@ -335,7 +365,7 @@ pub(crate) async fn build_endpoint(secret_key: SecretKey, common: &Config) -> Re .build(); builder = builder.dns_resolver(resolver); } - builder = builder.discovery(DnsDiscovery::builder(origin)); + builder = builder.address_lookup(DnsAddressLookup::builder(origin)); } } let endpoint = builder.bind().await?; @@ -568,6 +598,30 @@ fn relays_to_map(relays: Vec) -> RelayMap { RelayMap::from_iter(relays.into_iter().map(RelayConfig::from)) } +/// Check diagnostics settings and API key availability; if enabled, start +/// the diagnostics client and return a ClientHost to register on the Router. +async fn setup_diagnostics( + repo: &Repo, + endpoint: &Endpoint, +) -> Result> { + let settings = repo.diagnostics_settings().await?; + if !settings.enabled { + info!("Net diagnostics disabled by user preference"); + return Ok(None); + } + + let api_secret = match diagnostics::iroh_services_api_key_from_env()? { + Some(s) => s, + None => { + info!("Net diagnostics disabled: IROH_SERVICES_API_KEY not set"); + return Ok(None); + } + }; + + let (host, handle) = diagnostics::start_diagnostics(endpoint, api_secret).await?; + Ok(Some((host, handle))) +} + pub(crate) fn n0des_api_secret_from_env() -> Result> { let api_secret_str = match std::env::var("N0DES_API_SECRET") { Ok(s) => s, @@ -584,7 +638,7 @@ pub(crate) fn n0des_api_secret_from_env() -> Result> { pub(crate) async fn build_n0des_client_opt( endpoint: &Endpoint, api_secret: Option, -) -> Option> { +) -> Option> { match api_secret { None => { info!("Disabling metrics collection: N0DES_API_SECRET is not set"); @@ -603,10 +657,10 @@ pub(crate) async fn build_n0des_client_opt( pub(crate) async fn build_n0des_client( endpoint: &Endpoint, api_secret: ApiSecret, -) -> Result> { +) -> Result> { let remote_id = api_secret.remote.id; debug!(remote=%remote_id.fmt_short(), "connecting to n0des endpoint"); - let client = iroh_n0des::Client::builder(endpoint) + let client = iroh_services::Client::builder(endpoint) .api_secret(api_secret)? .build() .await diff --git a/lib/src/tests.rs b/lib/src/tests.rs index b87cb00..4bf1f45 100644 --- a/lib/src/tests.rs +++ b/lib/src/tests.rs @@ -3,7 +3,7 @@ use std::net::Ipv4Addr; use http_body_util::BodyExt; use hyper::{Request, StatusCode, client::conn::http2}; use hyper_util::rt::{TokioExecutor, TokioIo}; -use iroh::{Endpoint, discovery::static_provider::StaticProvider}; +use iroh::{Endpoint, address_lookup::memory::MemoryLookup, endpoint::presets}; use n0_error::{Result, StdResultExt}; use n0_future::task::AbortOnDropHandle; use n0_tracing_test::traced_test; @@ -15,11 +15,14 @@ use tokio::{ use crate::{Advertisment, ListenNode, ProxyState, Repo, TcpProxyData, gateway}; #[derive(Default)] -struct TestDiscovery(StaticProvider); +struct TestDiscovery(MemoryLookup); impl TestDiscovery { fn add(&self, endpoint: &Endpoint) { - endpoint.discovery().add(self.0.clone()); + endpoint + .address_lookup() + .expect("address lookup not configured") + .add(self.0.clone()); self.0.add_endpoint_info(endpoint.addr()); } } @@ -49,7 +52,7 @@ async fn gateway_end_to_end_to_upstream_http() -> Result<()> { let (gateway_addr, _gateway_task) = { let listener = TcpListener::bind("127.0.0.1:0").await?; let addr = listener.local_addr()?; - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(presets::N0).await?; discovery.add(&endpoint); let task = tokio::task::spawn(gateway::serve(endpoint, listener)); (addr, AbortOnDropHandle::new(task)) @@ -102,7 +105,7 @@ async fn gateway_forward_connect_tunnel() -> Result<()> { let (gateway_addr, _gateway_task) = { let listener = TcpListener::bind("127.0.0.1:0").await?; let addr = listener.local_addr()?; - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(presets::N0).await?; discovery.add(&endpoint); let task = tokio::task::spawn(gateway::serve(endpoint, listener)); (addr, AbortOnDropHandle::new(task)) @@ -170,7 +173,7 @@ async fn gateway_forward_h2c_requests_are_stable() -> Result<()> { let (gateway_addr, _gateway_task) = { let listener = TcpListener::bind("127.0.0.1:0").await?; let addr = listener.local_addr()?; - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(presets::N0).await?; discovery.add(&endpoint); let task = tokio::task::spawn(gateway::serve(endpoint, listener)); (addr, AbortOnDropHandle::new(task)) @@ -242,7 +245,7 @@ async fn gateway_forward_h2c_handles_closed_origin_connections() -> Result<()> { let (gateway_addr, _gateway_task) = { let listener = TcpListener::bind("127.0.0.1:0").await?; let addr = listener.local_addr()?; - let endpoint = Endpoint::bind().await?; + let endpoint = Endpoint::bind(presets::N0).await?; discovery.add(&endpoint); let task = tokio::task::spawn(gateway::serve(endpoint, listener)); (addr, AbortOnDropHandle::new(task)) diff --git a/n0des-local/Cargo.toml b/n0des-local/Cargo.toml index df9d2a5..ffb4264 100644 --- a/n0des-local/Cargo.toml +++ b/n0des-local/Cargo.toml @@ -6,9 +6,9 @@ publish = false [dependencies] iroh = { workspace = true } -iroh-n0des = { workspace = true, features = ["tickets"] } -irpc = "0.11" -irpc-iroh = "0.11" +iroh-n0des = "0.10" +irpc = "0.13" +irpc-iroh = "0.13" n0-error = { workspace = true } rand = { workspace = true } tokio = { workspace = true } diff --git a/ui/src/main.rs b/ui/src/main.rs index fe36794..d3e1d7c 100644 --- a/ui/src/main.rs +++ b/ui/src/main.rs @@ -79,6 +79,15 @@ pub struct UpdateCheckContext { pub update_channel: Signal, } +/// Context for network diagnostics opt-in state. +#[derive(Clone)] +pub struct DiagnosticsContext { + /// Whether diagnostics collection is enabled. + pub enabled: Signal, + /// Whether the user has already seen the opt-in prompt. + pub prompted: Signal, +} + // Assets for favicons const FAVICON_DARK_196: Asset = asset!("/assets/icons/favicon-dark-196x196.png"); const FAVICON_LIGHT_196: Asset = asset!("/assets/icons/favicon-light-196x196.png"); @@ -570,6 +579,33 @@ fn App() -> Element { update_channel, }); + // Diagnostics opt-in state (loaded from repo settings). + let mut diag_enabled = use_signal(|| true); + let mut diag_prompted = use_signal(|| true); // default true to hide prompt until loaded + use_future(move || async move { + let repo = match lib::Repo::open_or_create(lib::Repo::default_location()).await { + Ok(r) => r, + Err(e) => { + tracing::warn!("Failed to open repo for diagnostics settings: {e:#}"); + return; + } + }; + match repo.diagnostics_settings().await { + Ok(s) => { + diag_enabled.set(s.enabled); + diag_prompted.set(s.prompted); + } + Err(e) => { + tracing::warn!("Failed to load diagnostics settings: {e:#}"); + } + } + }); + + provide_context(DiagnosticsContext { + enabled: diag_enabled, + prompted: diag_prompted, + }); + // Handle install now (toast or Settings). Read path from update_ready before clearing it — // the toast handler must not clear update_ready before this runs, or we rely only on disk. use_effect(move || { @@ -648,6 +684,90 @@ fn App() -> Element { } } } + // One-time diagnostics opt-in prompt + if !diag_prompted() { + DiagnosticsPrompt { + on_continue: move |_| { + // Accept defaults (enabled: true) and mark prompted + diag_prompted.set(true); + spawn(async move { + save_diagnostics_prompt(true).await; + }); + }, + on_opt_out: move |_| { + diag_enabled.set(false); + diag_prompted.set(true); + spawn(async move { + save_diagnostics_prompt(false).await; + }); + }, + } + } + } + } +} + +/// Save the diagnostics prompt response to disk. +async fn save_diagnostics_prompt(enabled: bool) { + let repo = match lib::Repo::open_or_create(lib::Repo::default_location()).await { + Ok(r) => r, + Err(e) => { + tracing::warn!("Failed to open repo for diagnostics prompt: {e:#}"); + return; + } + }; + let settings = lib::DiagnosticsSettings { + enabled, + prompted: true, + }; + if let Err(e) = repo.write_diagnostics_settings(&settings).await { + tracing::warn!("Failed to save diagnostics settings: {e:#}"); + } +} + +/// One-time prompt shown on first launch for network diagnostics opt-in. +#[component] +fn DiagnosticsPrompt( + on_continue: EventHandler, + on_opt_out: EventHandler, +) -> Element { + use crate::components::{Button, ButtonKind}; + + rsx! { + div { + class: "mt-[32px] fixed inset-0 z-50 flex items-center justify-center animate-in fade-in duration-100", + style: "background-color: rgba(0,0,0,0.2); -webkit-backdrop-filter: blur(1px); backdrop-filter: blur(1px);", + onclick: move |evt| { + evt.stop_propagation(); + on_continue.call(evt); + }, + div { + class: "w-full max-w-lg mx-auto p-8 bg-card-background rounded-lg border border-card-border shadow-card relative z-50", + onclick: move |evt| evt.stop_propagation(), + div { class: "mb-4", + h2 { class: "text-sm font-semibold text-foreground", + "Network diagnostics are enabled" + } + } + p { class: "text-1xs text-foreground/80 leading-relaxed", + "We collect network diagnostic data to help us identify and resolve connectivity issues faster. Your desktop identifier will always be anonymous." + } + p { class: "text-1xs text-foreground/60 leading-relaxed mt-4", + "Not willing to help us improve reliability? No worries! You can always opt out at any time in Settings \u{2192} Privacy \u{2192} Network Diagnostics." + } + div { class: "flex gap-2 mt-6", + Button { + text: "Continue", + kind: ButtonKind::Primary, + onclick: move |evt| on_continue.call(evt), + } + Button { + text: "Opt Out", + kind: ButtonKind::Outline, + onclick: move |evt| on_opt_out.call(evt), + } + } + } } } } diff --git a/ui/src/views/settings.rs b/ui/src/views/settings.rs index bd22a29..db05ac1 100644 --- a/ui/src/views/settings.rs +++ b/ui/src/views/settings.rs @@ -1,7 +1,7 @@ use crate::{ - components::{input::Input, Button, ButtonKind, Icon, IconSource}, + components::{input::Input, Button, ButtonKind, Icon, IconSource, Switch, SwitchThumb}, state::AppState, - Route, UpdateCheckContext, + DiagnosticsContext, Route, UpdateCheckContext, }; use dioxus::prelude::*; use lib::UpdateChannel; @@ -229,6 +229,69 @@ pub fn Settings() -> Element { } } } + // Privacy section + {PrivacySection()} + } + } +} + +#[component] +fn PrivacySection() -> Element { + let diag_ctx = consume_context::(); + let enabled = (diag_ctx.enabled)(); + + rsx! { + div { class: "bg-card-background border border-card-border rounded-lg", + div { class: "px-4 py-3 border-b border-card-border", + h2 { class: "text-sm text-foreground", "Privacy" } + } + div { class: "p-4 flex flex-col gap-4 max-w-md", + div { class: "flex flex-col gap-2", + p { class: "text-sm text-foreground font-medium", "Network Diagnostics" } + p { class: "text-1xs text-foreground/60", + "We collect network diagnostic data to help us identify and resolve connectivity issues faster. Your desktop identifier will always be anonymous." + } + div { class: "flex items-center gap-3 mt-1", + Switch { + checked: enabled, + on_checked_change: move |next: bool| { + let mut sig = diag_ctx.enabled; + spawn(async move { + let repo = match lib::Repo::open_or_create(lib::Repo::default_location()) + .await + { + Ok(r) => r, + Err(e) => { + tracing::warn!("Failed to open repo for diagnostics toggle: {e:#}"); + return; + } + }; + let mut settings = match repo.diagnostics_settings().await { + Ok(s) => s, + Err(e) => { + tracing::warn!("Failed to load diagnostics settings: {e:#}"); + return; + } + }; + settings.enabled = next; + if let Err(e) = repo.write_diagnostics_settings(&settings).await { + tracing::warn!("Failed to save diagnostics settings: {e:#}"); + return; + } + sig.set(next); + }); + }, + SwitchThumb {} + } + p { class: "text-1xs text-foreground/60", + if enabled { "Enabled" } else { "Disabled" } + } + } + p { class: "text-1xs text-foreground/40", + "Changes take effect on next app restart." + } + } + } } } } From df8b6bff673f10b03e911897c3b7be8110e2cd69 Mon Sep 17 00:00:00 2001 From: Zach Smith Date: Thu, 9 Apr 2026 16:38:44 -0700 Subject: [PATCH 2/6] fix: bump Dockerfile Rust version to 1.89 New dependencies (netwatch, portmapper, irpc, iroh-tickets) require rustc 1.89. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9fb60d9..ce87f96 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.88-bookworm AS builder +FROM rust:1.89-bookworm AS builder WORKDIR /app From a89a2c4627045190082ac0841a24da64279d1232 Mon Sep 17 00:00:00 2001 From: Zach Smith Date: Thu, 9 Apr 2026 16:43:03 -0700 Subject: [PATCH 3/6] fix: add back equivalent iroh 0.97 metrics Add conns_closed, conns_direct, paths_direct, and paths_relay metrics that have equivalents in iroh 0.97's SocketMetrics. --- lib/src/gateway/metrics.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/src/gateway/metrics.rs b/lib/src/gateway/metrics.rs index f2d928d..a19d73b 100644 --- a/lib/src/gateway/metrics.rs +++ b/lib/src/gateway/metrics.rs @@ -179,6 +179,10 @@ impl GatewayMetrics { let relay_removed = endpoint_metrics.socket.transport_relay_paths_removed.get(); let relay_home_changes = endpoint_metrics.socket.relay_home_change.get(); let conns_opened = endpoint_metrics.socket.num_conns_opened.get(); + let conns_closed = endpoint_metrics.socket.num_conns_closed.get(); + let conns_direct = endpoint_metrics.socket.num_conns_direct.get(); + let paths_direct = endpoint_metrics.socket.paths_direct.get(); + let paths_relay = endpoint_metrics.socket.paths_relay.get(); let holepunch_attempts = endpoint_metrics.socket.holepunch_attempts.get(); let direct_current = direct_added.saturating_sub(direct_removed); let relay_current = relay_added.saturating_sub(relay_removed); @@ -259,7 +263,13 @@ impl GatewayMetrics { "# TYPE iroh_gateway_tunnel_connectivity_events_total counter\n", "iroh_gateway_tunnel_connectivity_events_total{{event=\"relay_home_change\"}} {}\n", "iroh_gateway_tunnel_connectivity_events_total{{event=\"conns_opened\"}} {}\n", - "iroh_gateway_tunnel_connectivity_events_total{{event=\"holepunch_attempts\"}} {}\n\n", + "iroh_gateway_tunnel_connectivity_events_total{{event=\"conns_closed\"}} {}\n", + "iroh_gateway_tunnel_connectivity_events_total{{event=\"conns_direct\"}} {}\n", + "iroh_gateway_tunnel_connectivity_events_total{{event=\"holepunch_attempts\"}} {}\n", + "# HELP iroh_gateway_active_paths Current active paths by transport type.\n", + "# TYPE iroh_gateway_active_paths gauge\n", + "iroh_gateway_active_paths{{transport=\"direct\"}} {}\n", + "iroh_gateway_active_paths{{transport=\"relay\"}} {}\n\n", ), self.requests_tunnel_total.load(Ordering::Relaxed), self.requests_origin_total.load(Ordering::Relaxed), @@ -304,7 +314,11 @@ impl GatewayMetrics { relay_current, relay_home_changes, conns_opened, + conns_closed, + conns_direct, holepunch_attempts, + paths_direct, + paths_relay, ) + &downstream_openmetrics } } From 4e86780804dbeb6f8e3b0f22e850d8f78af7818c Mon Sep 17 00:00:00 2001 From: Zach Smith Date: Thu, 9 Apr 2026 16:57:19 -0700 Subject: [PATCH 4/6] fix: update relay URL tests for iroh 0.97 host_str format --- lib/src/node.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/node.rs b/lib/src/node.rs index 69bd4c6..ed80592 100644 --- a/lib/src/node.rs +++ b/lib/src/node.rs @@ -681,11 +681,11 @@ mod tests { assert_eq!(parsed[0].scheme(), "https"); assert_eq!( parsed[0].host_str(), - Some("iroh-relay.us-east-1.datumconnect.net.") + Some("iroh-relay.us-east-1.datumconnect.net") ); assert_eq!( parsed[1].host_str(), - Some("iroh-relay.us-west-1.datumconnect.net.") + Some("iroh-relay.us-west-1.datumconnect.net") ); } @@ -694,7 +694,7 @@ mod tests { let input = " relay-a.example.com, relay-a.example.com;;relay-b.example.com "; let parsed = parse_relay_urls(input).expect("should parse"); assert_eq!(parsed.len(), 2); - assert_eq!(parsed[0].host_str(), Some("relay-a.example.com.")); - assert_eq!(parsed[1].host_str(), Some("relay-b.example.com.")); + assert_eq!(parsed[0].host_str(), Some("relay-a.example.com")); + assert_eq!(parsed[1].host_str(), Some("relay-b.example.com")); } } From 07295238801566a5e1fae00573631f3d81db7257 Mon Sep 17 00:00:00 2001 From: Zach Smith Date: Wed, 15 Apr 2026 11:30:05 -0700 Subject: [PATCH 5/6] =?UTF-8?q?chore:=20remove=20gateway=20from=20app=20?= =?UTF-8?q?=E2=80=94=20moved=20to=20datum-cloud/iroh-gateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gateway server has been extracted into its own standalone repo at datum-cloud/iroh-gateway. Remove: - lib/src/gateway.rs and lib/src/gateway/metrics.rs - lib/templates/gateway_error.html - lib/src/tests.rs (all tests were gateway integration tests) - GatewayConfig from config.rs and repo.rs - gateway_key / gateway_config from Repo - Commands::Gateway subcommand from the CLI - askama dep from lib (only used by the gateway error template) --- Cargo.lock | 62 ----- cli/src/main.rs | 100 +------- lib/Cargo.toml | 1 - lib/src/config.rs | 23 -- lib/src/gateway.rs | 309 ------------------------- lib/src/gateway/metrics.rs | 369 ----------------------------- lib/src/lib.rs | 6 +- lib/src/repo.rs | 26 +-- lib/src/tests.rs | 383 ------------------------------- lib/templates/gateway_error.html | 67 ------ 10 files changed, 5 insertions(+), 1341 deletions(-) delete mode 100644 lib/src/gateway.rs delete mode 100644 lib/src/gateway/metrics.rs delete mode 100644 lib/src/tests.rs delete mode 100644 lib/templates/gateway_error.html diff --git a/Cargo.lock b/Cargo.lock index 0dac961..1bebb0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,58 +346,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "askama" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b8246bcbf8eb97abef10c2d92166449680d41d55c0fc6978a91dec2e3619608" -dependencies = [ - "askama_macros", - "itoa", - "percent-encoding", - "serde", - "serde_json", -] - -[[package]] -name = "askama_derive" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f9670bc84a28bb3da91821ef74226949ab63f1265aff7c751634f1dd0e6f97c" -dependencies = [ - "askama_parser", - "basic-toml", - "memchr", - "proc-macro2", - "quote", - "rustc-hash 2.1.2", - "serde", - "serde_derive", - "syn 2.0.117", -] - -[[package]] -name = "askama_macros" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0756b45480437dded0565dfc568af62ccce146fb6cfe902e808ba86e445f44f" -dependencies = [ - "askama_derive", -] - -[[package]] -name = "askama_parser" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0af3691ba3af77949c0b5a3925444b85cb58a0184cc7fec16c68ba2e7be868" -dependencies = [ - "rustc-hash 2.1.2", - "serde", - "serde_derive", - "unicode-ident", - "winnow 1.0.1", -] - [[package]] name = "asn1-rs" version = "0.7.1" @@ -802,15 +750,6 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" -[[package]] -name = "basic-toml" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" -dependencies = [ - "serde", -] - [[package]] name = "binary-merge" version = "0.1.2" @@ -5194,7 +5133,6 @@ name = "lib" version = "0.1.0" dependencies = [ "arc-swap", - "askama", "axum 0.7.9", "chrono", "color-backtrace", diff --git a/cli/src/main.rs b/cli/src/main.rs index c27eef3..f53e45a 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,17 +1,13 @@ //! Command line arguments. -use clap::{Parser, Subcommand, ValueEnum}; +use clap::{Parser, Subcommand}; mod dns_dev; mod tunnel_dev; use lib::{ - Advertisment, AdvertismentTicket, ConnectNode, DiscoveryMode, ListenNode, ProxyState, Repo, - TcpProxyData, + Advertisment, AdvertismentTicket, ConnectNode, ListenNode, ProxyState, Repo, TcpProxyData, datum_cloud::{ApiEnv, DatumCloudClient}, }; -use std::{ - net::{IpAddr, SocketAddr}, - path::PathBuf, -}; +use std::{net::SocketAddr, path::PathBuf}; use tracing::info; use tracing_subscriber::prelude::*; @@ -32,9 +28,6 @@ enum Commands { /// Join a proxy, i.e. connect to the proxy and expose the service locally. Connect(ConnectArgs), - /// Start a gateway server that forwards HTTP requests through a Datum Connect tunnel. - Gateway(ServeArgs), - /// Run a local DNS server for development TXT records. #[clap(subcommand)] DnsDev(DnsDevArgs), @@ -139,46 +132,6 @@ pub struct ConnectArgs { pub ticket: AdvertismentTicket, } -#[derive(Parser, Debug)] -pub struct ServeArgs { - #[clap(long, default_value = "0.0.0.0")] - pub bind_addr: IpAddr, - #[clap(long, default_value = "8080")] - pub port: u16, - /// Optional bind address for Prometheus metrics server. - #[clap(long)] - pub metrics_addr: Option, - /// Optional port for Prometheus metrics server. - #[clap(long)] - pub metrics_port: Option, - /// Also listen on a Unix domain socket at this path (e.g. for Envoy to forward via UDS). - #[cfg(unix)] - #[clap(long)] - pub uds: Option, - /// Discovery mode for connection details. - #[clap(long, value_enum)] - pub discovery: Option, - /// DNS origin for _iroh.. lookups. - #[clap(long)] - pub dns_origin: Option, - /// DNS resolver address for discovery (e.g. 127.0.0.1:53535). - #[clap(long)] - pub dns_resolver: Option, -} - -#[derive(Debug, Clone, Copy, ValueEnum)] -pub enum GatewayModeArg { - Reverse, - Forward, -} - -#[derive(Debug, Clone, Copy, ValueEnum)] -pub enum DiscoveryModeArg { - Default, - Dns, - Hybrid, -} - #[tokio::main] async fn main() -> n0_error::Result<()> { tracing_subscriber::registry() @@ -304,53 +257,6 @@ async fn main() -> n0_error::Result<()> { tokio::signal::ctrl_c().await?; handle.abort(); } - Commands::Gateway(args) => { - let bind_addr: SocketAddr = (args.bind_addr, args.port).into(); - let metrics_bind_addr = match (args.metrics_addr, args.metrics_port) { - (None, None) => None, - (Some(addr), Some(port)) => Some((addr, port).into()), - (Some(addr), None) => Some((addr, 9090).into()), - (None, Some(port)) => Some((args.bind_addr, port).into()), - }; - let secret_key = repo.gateway_key().await?; - let mut config = repo.gateway_config().await?; - if let Some(discovery) = args.discovery { - config.common.discovery_mode = match discovery { - DiscoveryModeArg::Default => DiscoveryMode::Default, - DiscoveryModeArg::Dns => DiscoveryMode::Dns, - DiscoveryModeArg::Hybrid => DiscoveryMode::Hybrid, - }; - } - if let Some(origin) = args.dns_origin { - config.common.dns_origin = Some(origin); - } - if let Some(resolver) = args.dns_resolver { - config.common.dns_resolver = Some(resolver); - } - #[cfg(unix)] - let uds_listener = if let Some(uds_path) = &args.uds { - if uds_path.exists() { - std::fs::remove_file(uds_path)?; - } - let listener = tokio::net::UnixListener::bind(uds_path)?; - println!("UDS gateway at {}", uds_path.display()); - Some(listener) - } else { - None - }; - println!("serving on port {bind_addr}"); - tokio::select! { - res = lib::gateway::bind_and_serve( - secret_key, - config, - bind_addr, - metrics_bind_addr, - #[cfg(unix)] - uds_listener, - ) => res?, - _ = tokio::signal::ctrl_c() => {} - } - } Commands::DnsDev(args) => match args { DnsDevArgs::Serve(args) => { dns_dev::serve( diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 9c96eda..b6ff8f2 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -49,7 +49,6 @@ uuid.workspace = true iroh-blobs = "0.99.0" httparse = "1.10.1" ttl_cache = "0.5.1" -askama = "0.15.1" k8s-openapi = { version = "0.26.1", features = ["v1_30"] } kube = { version = "2.0.1", default-features = false, features = ["client", "derive", "rustls-tls"] } gateway-api = "0.19.0" diff --git a/lib/src/config.rs b/lib/src/config.rs index 37217c0..386d7bb 100644 --- a/lib/src/config.rs +++ b/lib/src/config.rs @@ -51,13 +51,6 @@ pub struct Config { pub dns_resolver: Option, } -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub struct GatewayConfig { - #[serde(flatten)] - pub common: Config, -} - impl Config { pub async fn from_file(path: PathBuf) -> Result { let config = tokio::fs::read_to_string(path) @@ -73,19 +66,3 @@ impl Config { Ok(()) } } - -impl GatewayConfig { - pub async fn from_file(path: PathBuf) -> Result { - let config = tokio::fs::read_to_string(path) - .await - .context("reading config file")?; - let config = serde_yml::from_str(&config).std_context("parsing config file")?; - Ok(config) - } - - pub async fn write(&self, path: PathBuf) -> Result<()> { - let data = serde_yml::to_string(self).anyerr()?; - fs::write(path, data)?; - Ok(()) - } -} diff --git a/lib/src/gateway.rs b/lib/src/gateway.rs deleted file mode 100644 index c238bab..0000000 --- a/lib/src/gateway.rs +++ /dev/null @@ -1,309 +0,0 @@ -use std::{io, net::SocketAddr, str::FromStr, sync::Arc}; - -use askama::Template; -use http_body_util::{BodyExt, Full, combinators::BoxBody}; -use hyper::{ - StatusCode, - body::Bytes, - http::{self, HeaderMap, HeaderValue}, -}; -use iroh::{Endpoint, EndpointId, SecretKey}; -use iroh_proxy_utils::{ - Authority, HttpRequest, HttpRequestKind, - downstream::{ - Deny, DownstreamProxy, ErrorResponder, HttpProxyOpts, ProxyMode, RequestHandler, SrcAddr, - }, -}; -use n0_error::Result; -use tokio::net::TcpListener; -#[cfg(unix)] -use tokio::net::UnixListener; -use tracing::info; - -mod metrics; - -use self::metrics::{GatewayMetrics, MetricsHttpState, serve_metrics_http, shared_gateway_metrics}; -use crate::build_endpoint; - -pub async fn bind_and_serve( - secret_key: SecretKey, - config: crate::config::GatewayConfig, - tcp_bind_addr: SocketAddr, - metrics_bind_addr: Option, - #[cfg(unix)] uds_listener: Option, -) -> Result<()> { - let listener = TcpListener::bind(tcp_bind_addr).await?; - let endpoint = build_endpoint(secret_key, &config.common).await?; - serve_with_metrics( - endpoint, - listener, - metrics_bind_addr, - #[cfg(unix)] - uds_listener, - ) - .await -} - -pub async fn serve(endpoint: Endpoint, listener: TcpListener) -> Result<()> { - serve_with_metrics( - endpoint, - listener, - None, - #[cfg(unix)] - None, - ) - .await -} - -pub async fn serve_with_metrics( - endpoint: Endpoint, - listener: TcpListener, - metrics_bind_addr: Option, - #[cfg(unix)] uds_listener: Option, -) -> Result<()> { - let tcp_bind_addr = listener.local_addr()?; - info!( - ?tcp_bind_addr, - endpoint_id = %endpoint.id().fmt_short(), - "TCP proxy gateway started" - ); - - let metrics = shared_gateway_metrics(); - let proxy = DownstreamProxy::new(endpoint.clone(), Default::default()); - - if let Some(metrics_bind_addr) = metrics_bind_addr { - let state = - MetricsHttpState::new(endpoint.clone(), metrics.clone(), proxy.metrics().clone()); - tokio::spawn(async move { - if let Err(err) = serve_metrics_http(metrics_bind_addr, state).await { - tracing::warn!(%err, "gateway metrics server failed"); - } - }); - } - - #[cfg(unix)] - if let Some(uds_listener) = uds_listener { - let uds_proxy = proxy.clone(); - let uds_endpoint = endpoint.clone(); - let uds_metrics = metrics.clone(); - tokio::spawn(async move { - if let Err(err) = - serve_uds_with_proxy(uds_endpoint, uds_listener, uds_metrics, uds_proxy).await - { - tracing::warn!(%err, "UDS gateway task failed"); - } - }); - } - - let resolver_endpoint = endpoint.clone(); - let error_endpoint = endpoint.clone(); - let mode = ProxyMode::Http( - HttpProxyOpts::new(HeaderResolver::new(resolver_endpoint, metrics.clone())) - .error_responder(ErrorResponseWriter::new(error_endpoint, metrics)), - ); - proxy.forward_tcp_listener(listener, mode).await -} - -#[cfg(unix)] -async fn serve_uds_with_proxy( - endpoint: Endpoint, - listener: UnixListener, - metrics: Arc, - proxy: DownstreamProxy, -) -> Result<()> { - let uds_path = listener - .local_addr() - .ok() - .and_then(|a| a.as_pathname().map(|p| p.to_path_buf())); - info!( - ?uds_path, - endpoint_id = %endpoint.id().fmt_short(), - "UDS proxy gateway started" - ); - - let resolver_endpoint = endpoint.clone(); - let mode = ProxyMode::Http( - HttpProxyOpts::new(HeaderResolver::new(resolver_endpoint, metrics.clone())) - .error_responder(ErrorResponseWriter::new(endpoint, metrics)), - ); - proxy.forward_uds_listener(listener, mode).await -} - -const HEADER_NODE_ID: &str = "x-iroh-endpoint-id"; -const HEADER_TARGET_HOST: &str = "x-datum-target-host"; -const HEADER_TARGET_PORT: &str = "x-datum-target-port"; - -const DATUM_HEADERS: [&str; 3] = [HEADER_NODE_ID, HEADER_TARGET_HOST, HEADER_TARGET_PORT]; - -struct HeaderResolver { - endpoint: Endpoint, - metrics: Arc, -} - -impl RequestHandler for HeaderResolver { - async fn handle_request( - &self, - src_addr: SrcAddr, - req: &mut HttpRequest, - ) -> Result { - let is_tcp = matches!(src_addr, SrcAddr::Tcp(_)); - match src_addr { - SrcAddr::Tcp(_) => self.metrics.inc_tcp_requests(), - #[cfg(unix)] - SrcAddr::Unix(_) => self.metrics.inc_uds_requests(), - } - match req.classify()? { - HttpRequestKind::Tunnel => { - self.metrics.inc_tunnel_requests(); - self.metrics - .inc_tunnel_reuse_attempt(has_existing_peer_conn(&self.endpoint)); - if is_tcp { - self.metrics.inc_tunnel_tcp_requests(); - } else { - #[cfg(unix)] - self.metrics.inc_tunnel_uds_requests(); - } - let endpoint_id = self.endpoint_id_from_headers(&req.headers)?; - req.remove_headers(DATUM_HEADERS); - Ok(endpoint_id) - } - HttpRequestKind::Origin | HttpRequestKind::Http1Absolute => { - self.metrics.inc_origin_requests(); - self.metrics - .inc_origin_reuse_attempt(has_existing_peer_conn(&self.endpoint)); - if is_tcp { - self.metrics.inc_origin_tcp_requests(); - } else { - #[cfg(unix)] - self.metrics.inc_origin_uds_requests(); - } - let endpoint_id = self.endpoint_id_from_headers(&req.headers)?; - let host = self.header_value(&req.headers, HEADER_TARGET_HOST)?; - let port = self - .header_value(&req.headers, HEADER_TARGET_PORT)? - .parse::() - .map_err(|_| { - self.metrics.inc_denied_invalid_target_port(); - Deny::bad_request("invalid x-datum-target-port header") - })?; - // Rewrite the request target. - req.set_absolute_http_authority(Authority::new(host.to_string(), port))? - .remove_headers(DATUM_HEADERS); - Ok(endpoint_id) - } - } - } -} - -impl HeaderResolver { - fn new(endpoint: Endpoint, metrics: Arc) -> Self { - Self { endpoint, metrics } - } - - fn endpoint_id_from_headers( - &self, - headers: &HeaderMap, - ) -> Result { - let s = self.header_value(headers, HEADER_NODE_ID)?; - EndpointId::from_str(s).map_err(|_| { - self.metrics.inc_denied_invalid_endpoint(); - Deny::bad_request("invalid x-iroh-endpoint-id value") - }) - } - - fn header_value<'a>( - &self, - headers: &'a HeaderMap, - name: &str, - ) -> Result<&'a str, Deny> { - headers - .get(name) - .and_then(|value| value.to_str().ok()) - .ok_or_else(|| { - self.metrics.inc_denied_missing_header_name(name); - Deny::bad_request(format!("Missing header {name}")) - }) - } -} - -#[derive(Template)] -#[template(path = "gateway_error.html")] -struct GatewayErrorTemplate<'a> { - title: &'a str, - body: &'a str, -} - -struct ErrorResponseWriter { - endpoint: Endpoint, - metrics: Arc, -} - -impl ErrorResponder for ErrorResponseWriter { - async fn error_response( - &self, - status: StatusCode, - ) -> hyper::Response> { - self.metrics.inc_status_code(status); - if status.is_server_error() { - self.metrics - .inc_5xx_failure_by_peer_conn_state(has_existing_peer_conn(&self.endpoint)); - } - let title = format!( - "{} {}", - status.as_u16(), - status.canonical_reason().unwrap_or_default() - ); - let body = match status { - StatusCode::BAD_REQUEST => { - "The request could not be understood by the gateway. Please try again." - } - StatusCode::UNAUTHORIZED => { - "You are not logged in or your session has expired. Please sign in and try again." - } - StatusCode::FORBIDDEN => "Access to this resource is not allowed through the gateway.", - StatusCode::NOT_FOUND => "The requested page could not be found through the gateway.", - StatusCode::INTERNAL_SERVER_ERROR => { - "The gateway encountered an internal error. Please try again later." - } - StatusCode::BAD_GATEWAY => { - "The gateway could not get a valid response from the upstream service." - } - StatusCode::SERVICE_UNAVAILABLE => { - "The service is temporarily unavailable. Please try again shortly." - } - StatusCode::GATEWAY_TIMEOUT => "The upstream service took too long to respond.", - _ => "The service experienced an unexpected error.", - }; - let html = GatewayErrorTemplate { - body, - title: &title, - } - .render() - .unwrap_or(title); - hyper::Response::builder() - .status(status) - .header(http::header::CONTENT_LENGTH, html.len().to_string()) - .body( - Full::new(Bytes::from(html)) - .map_err(|err| match err {}) - .boxed(), - ) - .expect("infallible") - } -} - -impl ErrorResponseWriter { - fn new(endpoint: Endpoint, metrics: Arc) -> Self { - Self { endpoint, metrics } - } -} - -fn has_existing_peer_conn(endpoint: &Endpoint) -> bool { - let endpoint_metrics = endpoint.metrics(); - let conns_current = endpoint_metrics - .socket - .num_conns_opened - .get() - .saturating_sub(endpoint_metrics.socket.num_conns_closed.get()); - conns_current > 0 -} diff --git a/lib/src/gateway/metrics.rs b/lib/src/gateway/metrics.rs deleted file mode 100644 index a19d73b..0000000 --- a/lib/src/gateway/metrics.rs +++ /dev/null @@ -1,369 +0,0 @@ -use std::{ - net::SocketAddr, - sync::{ - Arc, OnceLock, - atomic::{AtomicU64, Ordering}, - }, -}; - -use axum::{Router, extract::State, routing::get}; -use hyper::http::header; -use iroh::Endpoint; -use iroh_metrics::Registry; -use iroh_proxy_utils::downstream::DownstreamMetrics; -use n0_error::Result; -use tokio::net::TcpListener; -use tracing::info; - -#[derive(Debug, Default)] -pub(super) struct GatewayMetrics { - requests_tunnel_total: AtomicU64, - requests_origin_total: AtomicU64, - requests_tcp_total: AtomicU64, - requests_uds_total: AtomicU64, - requests_tunnel_tcp_total: AtomicU64, - requests_tunnel_uds_total: AtomicU64, - requests_origin_tcp_total: AtomicU64, - requests_origin_uds_total: AtomicU64, - tunnel_reuse_attempts_with_existing_peer_conn_total: AtomicU64, - tunnel_reuse_attempts_without_existing_peer_conn_total: AtomicU64, - origin_reuse_attempts_with_existing_peer_conn_total: AtomicU64, - origin_reuse_attempts_without_existing_peer_conn_total: AtomicU64, - denied_missing_header_total: AtomicU64, - denied_missing_header_node_id_total: AtomicU64, - denied_invalid_endpoint_total: AtomicU64, - denied_invalid_target_port_total: AtomicU64, - responses_4xx_total: AtomicU64, - responses_5xx_total: AtomicU64, - responses_500_total: AtomicU64, - responses_502_total: AtomicU64, - responses_503_total: AtomicU64, - responses_504_total: AtomicU64, - responses_other_5xx_total: AtomicU64, - failures_5xx_with_existing_peer_conn_total: AtomicU64, - failures_5xx_without_existing_peer_conn_total: AtomicU64, -} - -static SHARED_METRICS: OnceLock> = OnceLock::new(); - -pub(super) fn shared_gateway_metrics() -> Arc { - SHARED_METRICS - .get_or_init(|| Arc::new(GatewayMetrics::default())) - .clone() -} - -impl GatewayMetrics { - pub(super) fn inc_tunnel_requests(&self) { - self.requests_tunnel_total.fetch_add(1, Ordering::Relaxed); - } - - pub(super) fn inc_origin_requests(&self) { - self.requests_origin_total.fetch_add(1, Ordering::Relaxed); - } - - pub(super) fn inc_tunnel_reuse_attempt(&self, has_existing_peer_conn: bool) { - if has_existing_peer_conn { - self.tunnel_reuse_attempts_with_existing_peer_conn_total - .fetch_add(1, Ordering::Relaxed); - } else { - self.tunnel_reuse_attempts_without_existing_peer_conn_total - .fetch_add(1, Ordering::Relaxed); - } - } - - pub(super) fn inc_origin_reuse_attempt(&self, has_existing_peer_conn: bool) { - if has_existing_peer_conn { - self.origin_reuse_attempts_with_existing_peer_conn_total - .fetch_add(1, Ordering::Relaxed); - } else { - self.origin_reuse_attempts_without_existing_peer_conn_total - .fetch_add(1, Ordering::Relaxed); - } - } - - pub(super) fn inc_tunnel_tcp_requests(&self) { - self.requests_tunnel_tcp_total - .fetch_add(1, Ordering::Relaxed); - } - - #[cfg(unix)] - pub(super) fn inc_tunnel_uds_requests(&self) { - self.requests_tunnel_uds_total - .fetch_add(1, Ordering::Relaxed); - } - - pub(super) fn inc_origin_tcp_requests(&self) { - self.requests_origin_tcp_total - .fetch_add(1, Ordering::Relaxed); - } - - #[cfg(unix)] - pub(super) fn inc_origin_uds_requests(&self) { - self.requests_origin_uds_total - .fetch_add(1, Ordering::Relaxed); - } - - pub(super) fn inc_tcp_requests(&self) { - self.requests_tcp_total.fetch_add(1, Ordering::Relaxed); - } - - #[cfg(unix)] - pub(super) fn inc_uds_requests(&self) { - self.requests_uds_total.fetch_add(1, Ordering::Relaxed); - } - - pub(super) fn inc_denied_missing_header(&self) { - self.denied_missing_header_total - .fetch_add(1, Ordering::Relaxed); - } - - pub(super) fn inc_denied_missing_header_name(&self, name: &str) { - self.inc_denied_missing_header(); - if name == "x-iroh-endpoint-id" { - self.denied_missing_header_node_id_total - .fetch_add(1, Ordering::Relaxed); - } - } - - pub(super) fn inc_denied_invalid_endpoint(&self) { - self.denied_invalid_endpoint_total - .fetch_add(1, Ordering::Relaxed); - } - - pub(super) fn inc_denied_invalid_target_port(&self) { - self.denied_invalid_target_port_total - .fetch_add(1, Ordering::Relaxed); - } - - pub(super) fn inc_status_code(&self, status: hyper::StatusCode) { - if status.is_client_error() { - self.responses_4xx_total.fetch_add(1, Ordering::Relaxed); - } else if status.is_server_error() { - self.responses_5xx_total.fetch_add(1, Ordering::Relaxed); - match status { - hyper::StatusCode::INTERNAL_SERVER_ERROR => { - self.responses_500_total.fetch_add(1, Ordering::Relaxed); - } - hyper::StatusCode::BAD_GATEWAY => { - self.responses_502_total.fetch_add(1, Ordering::Relaxed); - } - hyper::StatusCode::SERVICE_UNAVAILABLE => { - self.responses_503_total.fetch_add(1, Ordering::Relaxed); - } - hyper::StatusCode::GATEWAY_TIMEOUT => { - self.responses_504_total.fetch_add(1, Ordering::Relaxed); - } - _ => { - self.responses_other_5xx_total - .fetch_add(1, Ordering::Relaxed); - } - } - } - } - - pub(super) fn inc_5xx_failure_by_peer_conn_state(&self, has_existing_peer_conn: bool) { - if has_existing_peer_conn { - self.failures_5xx_with_existing_peer_conn_total - .fetch_add(1, Ordering::Relaxed); - } else { - self.failures_5xx_without_existing_peer_conn_total - .fetch_add(1, Ordering::Relaxed); - } - } - - fn render(&self, endpoint: &Endpoint, downstream_metrics: &Arc) -> String { - let endpoint_metrics = endpoint.metrics(); - let direct_added = endpoint_metrics.socket.transport_ip_paths_added.get(); - let direct_removed = endpoint_metrics.socket.transport_ip_paths_removed.get(); - let relay_added = endpoint_metrics.socket.transport_relay_paths_added.get(); - let relay_removed = endpoint_metrics.socket.transport_relay_paths_removed.get(); - let relay_home_changes = endpoint_metrics.socket.relay_home_change.get(); - let conns_opened = endpoint_metrics.socket.num_conns_opened.get(); - let conns_closed = endpoint_metrics.socket.num_conns_closed.get(); - let conns_direct = endpoint_metrics.socket.num_conns_direct.get(); - let paths_direct = endpoint_metrics.socket.paths_direct.get(); - let paths_relay = endpoint_metrics.socket.paths_relay.get(); - let holepunch_attempts = endpoint_metrics.socket.holepunch_attempts.get(); - let direct_current = direct_added.saturating_sub(direct_removed); - let relay_current = relay_added.saturating_sub(relay_removed); - let recv_total = endpoint_metrics.socket.recv_data_ipv4.get() - + endpoint_metrics.socket.recv_data_ipv6.get() - + endpoint_metrics.socket.recv_data_relay.get(); - let send_total = endpoint_metrics.socket.send_ipv4.get() - + endpoint_metrics.socket.send_ipv6.get() - + endpoint_metrics.socket.send_relay.get(); - - let mut downstream_openmetrics = String::new(); - let mut registry = Registry::default(); - registry.register(downstream_metrics.clone()); - let _ = registry.encode_openmetrics_to_writer(&mut downstream_openmetrics); - - format!( - concat!( - "# HELP iroh_gateway_requests_total Gateway request count by proxy request kind.\n", - "# TYPE iroh_gateway_requests_total counter\n", - "iroh_gateway_requests_total{{kind=\"tunnel\"}} {}\n", - "iroh_gateway_requests_total{{kind=\"origin\"}} {}\n", - "# HELP iroh_gateway_requests_by_source_total Gateway request count by ingress source.\n", - "# TYPE iroh_gateway_requests_by_source_total counter\n", - "iroh_gateway_requests_by_source_total{{source=\"tcp\"}} {}\n", - "iroh_gateway_requests_by_source_total{{source=\"uds\"}} {}\n", - "# HELP iroh_gateway_requests_by_source_and_kind_total Gateway request count by ingress source and request kind.\n", - "# TYPE iroh_gateway_requests_by_source_and_kind_total counter\n", - "iroh_gateway_requests_by_source_and_kind_total{{source=\"tcp\",kind=\"tunnel\"}} {}\n", - "iroh_gateway_requests_by_source_and_kind_total{{source=\"uds\",kind=\"tunnel\"}} {}\n", - "iroh_gateway_requests_by_source_and_kind_total{{source=\"tcp\",kind=\"origin\"}} {}\n", - "iroh_gateway_requests_by_source_and_kind_total{{source=\"uds\",kind=\"origin\"}} {}\n", - "# HELP iroh_gateway_upstream_reuse_attempts_total Gateway upstream attempt count by request kind and whether a peer connection already existed.\n", - "# TYPE iroh_gateway_upstream_reuse_attempts_total counter\n", - "iroh_gateway_upstream_reuse_attempts_total{{kind=\"tunnel\",peer_conn_state=\"with_existing\"}} {}\n", - "iroh_gateway_upstream_reuse_attempts_total{{kind=\"tunnel\",peer_conn_state=\"without_existing\"}} {}\n", - "iroh_gateway_upstream_reuse_attempts_total{{kind=\"origin\",peer_conn_state=\"with_existing\"}} {}\n", - "iroh_gateway_upstream_reuse_attempts_total{{kind=\"origin\",peer_conn_state=\"without_existing\"}} {}\n", - "# HELP iroh_gateway_denied_requests_total Gateway denied request count by reason.\n", - "# TYPE iroh_gateway_denied_requests_total counter\n", - "iroh_gateway_denied_requests_total{{reason=\"missing_header\"}} {}\n", - "iroh_gateway_denied_requests_total{{reason=\"missing_header_node_id\"}} {}\n", - "iroh_gateway_denied_requests_total{{reason=\"invalid_endpoint_id\"}} {}\n", - "iroh_gateway_denied_requests_total{{reason=\"invalid_target_port\"}} {}\n", - "# HELP iroh_gateway_error_responses_total Gateway error response count grouped by status class.\n", - "# TYPE iroh_gateway_error_responses_total counter\n", - "iroh_gateway_error_responses_total{{class=\"4xx\"}} {}\n", - "iroh_gateway_error_responses_total{{class=\"5xx\"}} {}\n", - "# HELP iroh_gateway_error_responses_by_status_total Gateway 5xx response count grouped by exact status code.\n", - "# TYPE iroh_gateway_error_responses_by_status_total counter\n", - "iroh_gateway_error_responses_by_status_total{{status=\"500\"}} {}\n", - "iroh_gateway_error_responses_by_status_total{{status=\"502\"}} {}\n", - "iroh_gateway_error_responses_by_status_total{{status=\"503\"}} {}\n", - "iroh_gateway_error_responses_by_status_total{{status=\"504\"}} {}\n", - "iroh_gateway_error_responses_by_status_total{{status=\"other_5xx\"}} {}\n", - "# HELP iroh_gateway_upstream_failures_total Gateway upstream 5xx failures grouped by whether a peer connection existed when the error was generated.\n", - "# TYPE iroh_gateway_upstream_failures_total counter\n", - "iroh_gateway_upstream_failures_total{{class=\"5xx\",peer_conn_state=\"with_existing\"}} {}\n", - "iroh_gateway_upstream_failures_total{{class=\"5xx\",peer_conn_state=\"without_existing\"}} {}\n", - "# HELP iroh_gateway_iroh_recv_bytes_total Total iroh socket bytes received.\n", - "# TYPE iroh_gateway_iroh_recv_bytes_total counter\n", - "iroh_gateway_iroh_recv_bytes_total {}\n", - "# HELP iroh_gateway_iroh_send_bytes_total Total iroh socket bytes sent.\n", - "# TYPE iroh_gateway_iroh_send_bytes_total counter\n", - "iroh_gateway_iroh_send_bytes_total {}\n\n", - "# HELP iroh_gateway_quic_connections_opened_total QUIC peer connections opened by transport path.\n", - "# TYPE iroh_gateway_quic_connections_opened_total counter\n", - "iroh_gateway_quic_connections_opened_total{{path=\"direct\"}} {}\n", - "iroh_gateway_quic_connections_opened_total{{path=\"relay\"}} {}\n", - "# HELP iroh_gateway_quic_connections_closed_total QUIC peer connections closed by transport path.\n", - "# TYPE iroh_gateway_quic_connections_closed_total counter\n", - "iroh_gateway_quic_connections_closed_total{{path=\"direct\"}} {}\n", - "iroh_gateway_quic_connections_closed_total{{path=\"relay\"}} {}\n", - "# HELP iroh_gateway_quic_connections_current Current QUIC peer connections by transport path.\n", - "# TYPE iroh_gateway_quic_connections_current gauge\n", - "iroh_gateway_quic_connections_current{{path=\"direct\"}} {}\n", - "iroh_gateway_quic_connections_current{{path=\"relay\"}} {}\n\n", - "# HELP iroh_gateway_tunnel_connectivity_events_total Tunnel connectivity events from iroh socket state.\n", - "# TYPE iroh_gateway_tunnel_connectivity_events_total counter\n", - "iroh_gateway_tunnel_connectivity_events_total{{event=\"relay_home_change\"}} {}\n", - "iroh_gateway_tunnel_connectivity_events_total{{event=\"conns_opened\"}} {}\n", - "iroh_gateway_tunnel_connectivity_events_total{{event=\"conns_closed\"}} {}\n", - "iroh_gateway_tunnel_connectivity_events_total{{event=\"conns_direct\"}} {}\n", - "iroh_gateway_tunnel_connectivity_events_total{{event=\"holepunch_attempts\"}} {}\n", - "# HELP iroh_gateway_active_paths Current active paths by transport type.\n", - "# TYPE iroh_gateway_active_paths gauge\n", - "iroh_gateway_active_paths{{transport=\"direct\"}} {}\n", - "iroh_gateway_active_paths{{transport=\"relay\"}} {}\n\n", - ), - self.requests_tunnel_total.load(Ordering::Relaxed), - self.requests_origin_total.load(Ordering::Relaxed), - self.requests_tcp_total.load(Ordering::Relaxed), - self.requests_uds_total.load(Ordering::Relaxed), - self.requests_tunnel_tcp_total.load(Ordering::Relaxed), - self.requests_tunnel_uds_total.load(Ordering::Relaxed), - self.requests_origin_tcp_total.load(Ordering::Relaxed), - self.requests_origin_uds_total.load(Ordering::Relaxed), - self.tunnel_reuse_attempts_with_existing_peer_conn_total - .load(Ordering::Relaxed), - self.tunnel_reuse_attempts_without_existing_peer_conn_total - .load(Ordering::Relaxed), - self.origin_reuse_attempts_with_existing_peer_conn_total - .load(Ordering::Relaxed), - self.origin_reuse_attempts_without_existing_peer_conn_total - .load(Ordering::Relaxed), - self.denied_missing_header_total.load(Ordering::Relaxed), - self.denied_missing_header_node_id_total - .load(Ordering::Relaxed), - self.denied_invalid_endpoint_total.load(Ordering::Relaxed), - self.denied_invalid_target_port_total - .load(Ordering::Relaxed), - self.responses_4xx_total.load(Ordering::Relaxed), - self.responses_5xx_total.load(Ordering::Relaxed), - self.responses_500_total.load(Ordering::Relaxed), - self.responses_502_total.load(Ordering::Relaxed), - self.responses_503_total.load(Ordering::Relaxed), - self.responses_504_total.load(Ordering::Relaxed), - self.responses_other_5xx_total.load(Ordering::Relaxed), - self.failures_5xx_with_existing_peer_conn_total - .load(Ordering::Relaxed), - self.failures_5xx_without_existing_peer_conn_total - .load(Ordering::Relaxed), - recv_total, - send_total, - direct_added, - relay_added, - direct_removed, - relay_removed, - direct_current, - relay_current, - relay_home_changes, - conns_opened, - conns_closed, - conns_direct, - holepunch_attempts, - paths_direct, - paths_relay, - ) + &downstream_openmetrics - } -} - -#[derive(Clone)] -pub(super) struct MetricsHttpState { - endpoint: Endpoint, - metrics: Arc, - downstream_metrics: Arc, -} - -impl MetricsHttpState { - pub(super) fn new( - endpoint: Endpoint, - metrics: Arc, - downstream_metrics: Arc, - ) -> Self { - Self { - endpoint, - metrics, - downstream_metrics, - } - } -} - -pub(super) async fn serve_metrics_http(addr: SocketAddr, state: MetricsHttpState) -> Result<()> { - let app = Router::new() - .route("/metrics", get(metrics_handler)) - .with_state(state); - let listener = TcpListener::bind(addr).await?; - info!(metrics_bind_addr = %addr, "gateway metrics server started"); - axum::serve(listener, app).await?; - Ok(()) -} - -async fn metrics_handler( - State(state): State, -) -> ([(header::HeaderName, &'static str); 1], String) { - ( - [( - header::CONTENT_TYPE, - "text/plain; version=0.0.4; charset=utf-8", - )], - state - .metrics - .render(&state.endpoint, &state.downstream_metrics), - ) -} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 39df5fc..7474e9f 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -3,7 +3,6 @@ pub mod config; pub mod datum_apis; pub mod datum_cloud; pub mod diagnostics; -pub mod gateway; pub mod heartbeat; mod http_user_agent; mod node; @@ -13,7 +12,7 @@ mod state; pub mod tunnels; pub mod update; -pub use config::{Config, DiscoveryMode, GatewayConfig}; +pub use config::{Config, DiscoveryMode}; pub use diagnostics::DiagnosticsSettings; pub use heartbeat::HeartbeatAgent; pub use http_user_agent::datum_http_user_agent; @@ -27,6 +26,3 @@ pub use update::{UpdateChannel, UpdateChecker, UpdateInfo, UpdateSettings}; /// The root domain for datum connect urls to subdomain from. A proxy URL will /// be a three-word-codename subdomain off this URL. eg: "https://vast-gold-mine.iroh.datum.net" pub const DATUM_CONNECT_GATEWAY_DOMAIN_NAME: &str = "iroh.datum.net"; - -#[cfg(test)] -mod tests; diff --git a/lib/src/repo.rs b/lib/src/repo.rs index 9f7dd88..c3975ab 100644 --- a/lib/src/repo.rs +++ b/lib/src/repo.rs @@ -4,13 +4,7 @@ use iroh::SecretKey; use log::{info, warn}; use n0_error::{Result, StackResultExt, StdResultExt}; -use crate::{ - StateWrapper, - auth::Auth, - config::{Config, GatewayConfig}, - datum_cloud::AuthState, - state::State, -}; +use crate::{StateWrapper, auth::Auth, config::Config, datum_cloud::AuthState, state::State}; // Repo builds up a series of file path conventions from a root directory path. #[derive(Debug, Clone)] @@ -23,7 +17,6 @@ impl Repo { } const CONNECT_KEY_FILE: &str = "connect_key"; const LISTEN_KEY_FILE: &str = "listen_key"; - const GATEWAY_KEY_FILE: &str = "gateway_key"; const CONFIG_FILE: &str = "config.yml"; const OAUTH_FILE: &str = "oauth.yml"; const AUTH_FILE: &str = "auth.yml"; @@ -62,18 +55,6 @@ impl Repo { Config::from_file(config_file_path).await } - pub async fn gateway_config(&self) -> Result { - let config_file_path = self.0.join(Self::CONFIG_FILE); - if !config_file_path.exists() { - warn!("gateway config does not exist. creating new config"); - let cfg = GatewayConfig::default(); - cfg.write(config_file_path).await?; - return Ok(cfg); - }; - - GatewayConfig::from_file(config_file_path).await - } - pub async fn load_state(&self) -> Result { let state_file_path = self.0.join(Self::STATE_FILE); let state = if !state_file_path.exists() { @@ -130,11 +111,6 @@ impl Repo { self.secret_key(key_file_path).await } - pub async fn gateway_key(&self) -> Result { - let key_file_path = self.0.join(Self::GATEWAY_KEY_FILE); - self.secret_key(key_file_path).await - } - pub async fn connect_key(&self) -> Result { let key_file_path = self.0.join(Self::CONNECT_KEY_FILE); self.secret_key(key_file_path).await diff --git a/lib/src/tests.rs b/lib/src/tests.rs deleted file mode 100644 index 4bf1f45..0000000 --- a/lib/src/tests.rs +++ /dev/null @@ -1,383 +0,0 @@ -use std::net::Ipv4Addr; - -use http_body_util::BodyExt; -use hyper::{Request, StatusCode, client::conn::http2}; -use hyper_util::rt::{TokioExecutor, TokioIo}; -use iroh::{Endpoint, address_lookup::memory::MemoryLookup, endpoint::presets}; -use n0_error::{Result, StdResultExt}; -use n0_future::task::AbortOnDropHandle; -use n0_tracing_test::traced_test; -use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - net::TcpListener, -}; - -use crate::{Advertisment, ListenNode, ProxyState, Repo, TcpProxyData, gateway}; - -#[derive(Default)] -struct TestDiscovery(MemoryLookup); - -impl TestDiscovery { - fn add(&self, endpoint: &Endpoint) { - endpoint - .address_lookup() - .expect("address lookup not configured") - .add(self.0.clone()); - self.0.add_endpoint_info(endpoint.addr()); - } -} - -#[tokio::test] -#[traced_test] -async fn gateway_end_to_end_to_upstream_http() -> Result<()> { - let discovery = TestDiscovery::default(); - - let temp_dir = tempfile::tempdir()?; - let repo = Repo::open_or_create(temp_dir.path()).await?; - - let (origin_addr, _origin_task) = origin_server::spawn("origin").await?; - - let proxy_state = { - let data = TcpProxyData::from_host_port_str(&origin_addr.to_string())?; - let advertisment = Advertisment::new(data, None); - ProxyState::new(advertisment) - }; - - let codename = proxy_state.info.codename(); - - let upstream = ListenNode::new(repo).await?; - discovery.add(upstream.endpoint()); - upstream.set_proxy(proxy_state).await?; - - let (gateway_addr, _gateway_task) = { - let listener = TcpListener::bind("127.0.0.1:0").await?; - let addr = listener.local_addr()?; - let endpoint = Endpoint::bind(presets::N0).await?; - discovery.add(&endpoint); - let task = tokio::task::spawn(gateway::serve(endpoint, listener)); - (addr, AbortOnDropHandle::new(task)) - }; - - let domain = format!("{codename}.localhost"); - let client = reqwest::Client::builder() - .resolve_to_addrs(&domain, &[(Ipv4Addr::LOCALHOST, 0).into()]) - .http2_prior_knowledge() - .build() - .unwrap(); - let res = client - .get(format!( - "http://{codename}.localhost:{}/hello", - gateway_addr.port() - )) - .header("x-datum-target-host", origin_addr.ip().to_string()) - .header("x-datum-target-port", origin_addr.port().to_string()) - .header("x-iroh-endpoint-id", upstream.endpoint_id().to_string()) - .send() - .await - .anyerr()?; - assert_eq!(res.status(), StatusCode::OK); - let body = res.text().await.anyerr()?; - assert_eq!(body, "origin GET /hello"); - - Ok(()) -} - -#[tokio::test] -#[traced_test] -async fn gateway_forward_connect_tunnel() -> Result<()> { - let discovery = TestDiscovery::default(); - - let temp_dir = tempfile::tempdir()?; - let repo = Repo::open_or_create(temp_dir.path()).await?; - - let (origin_addr, _origin_task) = origin_server::spawn("origin").await?; - - let proxy_state = { - let data = TcpProxyData::from_host_port_str(&origin_addr.to_string())?; - let advertisment = Advertisment::new(data, None); - ProxyState::new(advertisment) - }; - - let upstream = ListenNode::new(repo).await?; - discovery.add(upstream.endpoint()); - upstream.set_proxy(proxy_state).await?; - - let (gateway_addr, _gateway_task) = { - let listener = TcpListener::bind("127.0.0.1:0").await?; - let addr = listener.local_addr()?; - let endpoint = Endpoint::bind(presets::N0).await?; - discovery.add(&endpoint); - let task = tokio::task::spawn(gateway::serve(endpoint, listener)); - (addr, AbortOnDropHandle::new(task)) - }; - - let mut stream = tokio::net::TcpStream::connect(gateway_addr).await?; - let connect_request = format!( - "CONNECT {target} HTTP/1.1\r\nHost: {target}\r\nx-iroh-endpoint-id: {node_id}\r\n\r\n", - target = origin_addr, - node_id = upstream.endpoint_id(), - ); - stream.write_all(connect_request.as_bytes()).await?; - - let mut response = String::new(); - let mut buffer = [0u8; 512]; - loop { - let read = stream.read(&mut buffer).await?; - if read == 0 { - break; - } - response.push_str(&String::from_utf8_lossy(&buffer[..read])); - if response.contains("\r\n\r\n") { - break; - } - } - assert!( - response.contains("200"), - "unexpected CONNECT response: {response}" - ); - - stream - .write_all(b"GET /hello HTTP/1.1\r\nHost: origin\r\n\r\n") - .await?; - let mut body = vec![0u8; 1024]; - let read = stream.read(&mut body).await?; - let body = String::from_utf8_lossy(&body[..read]); - assert!( - body.contains("origin GET /hello"), - "unexpected tunneled response: {body}" - ); - - Ok(()) -} - -#[tokio::test] -#[traced_test] -async fn gateway_forward_h2c_requests_are_stable() -> Result<()> { - let discovery = TestDiscovery::default(); - - let temp_dir = tempfile::tempdir()?; - let repo = Repo::open_or_create(temp_dir.path()).await?; - - let (origin_addr, _origin_task) = origin_server::spawn("origin").await?; - - let proxy_state = { - let data = TcpProxyData::from_host_port_str(&origin_addr.to_string())?; - let advertisment = Advertisment::new(data, None); - ProxyState::new(advertisment) - }; - - let upstream = ListenNode::new(repo).await?; - discovery.add(upstream.endpoint()); - upstream.set_proxy(proxy_state).await?; - - let (gateway_addr, _gateway_task) = { - let listener = TcpListener::bind("127.0.0.1:0").await?; - let addr = listener.local_addr()?; - let endpoint = Endpoint::bind(presets::N0).await?; - discovery.add(&endpoint); - let task = tokio::task::spawn(gateway::serve(endpoint, listener)); - (addr, AbortOnDropHandle::new(task)) - }; - - let stream = tokio::net::TcpStream::connect(gateway_addr).await?; - let io = TokioIo::new(stream); - let (mut sender, conn) = http2::Builder::new(TokioExecutor::new()) - .handshake(io) - .await - .map_err(|err| n0_error::anyerr!(err))?; - tokio::spawn(async move { - if let Err(err) = conn.await { - tracing::warn!("h2c client connection error: {err:#}"); - } - }); - - for _ in 0..5 { - let req: Request> = Request::builder() - .method("GET") - .uri("/hello") - .header("x-iroh-endpoint-id", upstream.endpoint_id().to_string()) - .header("x-datum-target-host", origin_addr.ip().to_string()) - .header("x-datum-target-port", origin_addr.port().to_string()) - .body(http_body_util::Full::new(hyper::body::Bytes::new())) - .unwrap(); - - let res = sender - .send_request(req) - .await - .map_err(|err| n0_error::anyerr!(err))?; - assert_eq!(res.status(), StatusCode::OK); - let body = res - .into_body() - .collect() - .await - .map_err(|err| n0_error::anyerr!(err))? - .to_bytes(); - let body = String::from_utf8_lossy(&body); - assert!( - body.contains("origin GET /hello"), - "unexpected h2c response: {body}" - ); - } - - Ok(()) -} - -#[tokio::test] -#[traced_test] -async fn gateway_forward_h2c_handles_closed_origin_connections() -> Result<()> { - let discovery = TestDiscovery::default(); - - let temp_dir = tempfile::tempdir()?; - let repo = Repo::open_or_create(temp_dir.path()).await?; - - let (origin_addr, _origin_task) = origin_server::spawn_closing("origin").await?; - - let proxy_state = { - let data = TcpProxyData::from_host_port_str(&origin_addr.to_string())?; - let advertisment = Advertisment::new(data, None); - ProxyState::new(advertisment) - }; - - let upstream = ListenNode::new(repo).await?; - discovery.add(upstream.endpoint()); - upstream.set_proxy(proxy_state).await?; - - let (gateway_addr, _gateway_task) = { - let listener = TcpListener::bind("127.0.0.1:0").await?; - let addr = listener.local_addr()?; - let endpoint = Endpoint::bind(presets::N0).await?; - discovery.add(&endpoint); - let task = tokio::task::spawn(gateway::serve(endpoint, listener)); - (addr, AbortOnDropHandle::new(task)) - }; - - let stream = tokio::net::TcpStream::connect(gateway_addr).await?; - let io = TokioIo::new(stream); - let (mut sender, conn) = http2::Builder::new(TokioExecutor::new()) - .handshake(io) - .await - .map_err(|err| n0_error::anyerr!(err))?; - tokio::spawn(async move { - if let Err(err) = conn.await { - tracing::warn!("h2c client connection error: {err:#}"); - } - }); - - for _ in 0..3 { - let req: Request> = Request::builder() - .method("GET") - .uri("/hello") - .header("x-iroh-endpoint-id", upstream.endpoint_id().to_string()) - .header("x-datum-target-host", origin_addr.ip().to_string()) - .header("x-datum-target-port", origin_addr.port().to_string()) - .body(http_body_util::Full::new(hyper::body::Bytes::new())) - .unwrap(); - - let res = sender - .send_request(req) - .await - .map_err(|err| n0_error::anyerr!(err))?; - assert_eq!(res.status(), StatusCode::OK); - let body = res - .into_body() - .collect() - .await - .map_err(|err| n0_error::anyerr!(err))? - .to_bytes(); - let body = String::from_utf8_lossy(&body); - assert!( - body.contains("origin GET /hello"), - "unexpected h2c response: {body}" - ); - } - - Ok(()) -} - -mod origin_server { - use std::{convert::Infallible, net::SocketAddr, sync::Arc}; - - use http_body_util::Full; - use hyper::{Request, Response, body::Bytes, server::conn::http1, service::service_fn}; - use hyper_util::rt::TokioIo; - use n0_future::task::AbortOnDropHandle; - use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - net::TcpListener, - }; - use tracing::debug; - - /// Spawns a simple HTTP origin server that echoes back "{label} {method} {path}". - pub async fn spawn( - label: &'static str, - ) -> n0_error::Result<(SocketAddr, AbortOnDropHandle<()>)> { - let listener = TcpListener::bind("127.0.0.1:0").await?; - let tcp_addr = listener.local_addr()?; - debug!(%label, %tcp_addr, "spawned origin server"); - let task = tokio::spawn(async move { run(listener, label).await }); - Ok((tcp_addr, AbortOnDropHandle::new(task))) - } - - /// Spawns a raw HTTP/1.1 origin server that always closes after each response. - pub async fn spawn_closing( - label: &'static str, - ) -> n0_error::Result<(SocketAddr, AbortOnDropHandle<()>)> { - let listener = TcpListener::bind("127.0.0.1:0").await?; - let tcp_addr = listener.local_addr()?; - debug!(%label, %tcp_addr, "spawned closing origin server"); - let task = tokio::spawn(async move { run_closing(listener, label).await }); - Ok((tcp_addr, AbortOnDropHandle::new(task))) - } - - /// Returns "{label} {METHOD} {PATH}" as response body. - pub(super) async fn run(listener: TcpListener, label: &'static str) { - let label = Arc::new(label); - loop { - let Ok((stream, _)) = listener.accept().await else { - break; - }; - let io = TokioIo::new(stream); - let label = label.clone(); - tokio::task::spawn(async move { - let handler = move |req: Request| { - let label = label.clone(); - async move { - let body = format!("{} {} {}", *label, req.method(), req.uri().path()); - Ok::<_, Infallible>(Response::new(Full::new(Bytes::from(body)))) - } - }; - let _ = http1::Builder::new() - .serve_connection(io, service_fn(handler)) - .await; - }); - } - } - - async fn run_closing(listener: TcpListener, label: &'static str) { - loop { - let Ok((mut stream, _)) = listener.accept().await else { - break; - }; - tokio::task::spawn(async move { - let mut buf = [0u8; 4096]; - loop { - let read = match stream.read(&mut buf).await { - Ok(0) => return, - Ok(n) => n, - Err(_) => return, - }; - if buf[..read].windows(4).any(|w| w == b"\r\n\r\n") { - break; - } - } - let body = format!("{label} GET /hello"); - let response = format!( - "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", - body.len(), - body - ); - let _ = stream.write_all(response.as_bytes()).await; - }); - } - } -} diff --git a/lib/templates/gateway_error.html b/lib/templates/gateway_error.html deleted file mode 100644 index 4addd0b..0000000 --- a/lib/templates/gateway_error.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - {{ title }} - - - -
- - - - - - - - -
-

{{ title }}

-

{{ body }}

- - From b416f45f1898eb04116d5b0ede56059fa9e684be Mon Sep 17 00:00:00 2001 From: Zach Smith Date: Wed, 15 Apr 2026 13:09:28 -0700 Subject: [PATCH 6/6] =?UTF-8?q?chore:=20remove=20iroh-gateway=20Docker=20p?= =?UTF-8?q?ublish=20workflow=20=E2=80=94=20moved=20to=20datum-cloud/iroh-g?= =?UTF-8?q?ateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/publish-docker.yml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .github/workflows/publish-docker.yml diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml deleted file mode 100644 index 2de83c3..0000000 --- a/.github/workflows/publish-docker.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Publish Docker Image - -on: - pull_request: - push: - release: - types: - - published - -permissions: - contents: read - packages: write - -jobs: - publish: - uses: datum-cloud/actions/.github/workflows/publish-docker.yaml@v1.9.0 - with: - image-name: iroh-gateway - secrets: inherit