From bdf966ef04de18966c6cced7c32983675bea1471 Mon Sep 17 00:00:00 2001 From: Kasey Date: Fri, 1 Sep 2023 03:32:19 -0400 Subject: [PATCH] feat: get list of `ConnectionInfo`s or an individual node's `ConnectionInfo` (#1435) ## Description Display connection information for all nodes we have connected with or have supplied our node with addresses for. Display connection information on a particular node, using its node id. ## Change checklist - [x] Self-review. - [x] Documentation updates if relevant. --- Cargo.lock | 315 ++++++++++++++-------- iroh-net/src/magic_endpoint.rs | 30 +++ iroh-net/src/magicsock.rs | 53 ++-- iroh-net/src/magicsock/endpoint.rs | 412 +++++++++++++++++++++++++---- iroh/Cargo.toml | 4 +- iroh/src/client.rs | 23 +- iroh/src/commands.rs | 95 ++++++- iroh/src/node.rs | 50 +++- iroh/src/rpc_protocol.rs | 48 +++- 9 files changed, 838 insertions(+), 192 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2873935436..b2282bf978 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -41,9 +41,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] @@ -77,24 +77,23 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" [[package]] name = "anstyle-parse" @@ -116,9 +115,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -232,9 +231,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -282,9 +281,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" [[package]] name = "base64ct" @@ -409,9 +408,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] @@ -435,15 +434,15 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "serde", - "winapi", + "windows-targets 0.48.5", ] [[package]] @@ -486,20 +485,19 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.23" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03aef18ddf7d879c15ce20f04826ef8418101c7e528014c3eeea13321047dca3" +checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.3.23" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ce6fffb678c9b80a70b6b6de0aad31df727623a70fd9a842c30cd573e2fa98" +checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" dependencies = [ "anstream", "anstyle", @@ -509,9 +507,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.12" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" dependencies = [ "heck", "proc-macro2", @@ -521,9 +519,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "clipboard-win" @@ -559,6 +557,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "comfy-table" +version = "7.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab77dbd8adecaf3f0db40581631b995f312a8a5ae3aa9993188bb8f23d83a5b" +dependencies = [ + "crossterm", + "strum", + "strum_macros", + "unicode-width", +] + [[package]] name = "config" version = "0.13.3" @@ -764,6 +774,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossterm" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crypto-bigint" version = "0.5.2" @@ -884,9 +919,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "5.5.0" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", "hashbrown 0.14.0", @@ -1188,9 +1223,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] @@ -1234,9 +1269,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc978899517288e3ebbd1a3bfc1d9537dbb87eeab149e53ea490e63bcdff561a" +checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" dependencies = [ "serde", ] @@ -1249,9 +1284,9 @@ checksum = "76a5aa24577083f8190ad401e376b55887c7cd9083ae95d83ceec5d28ea78125" [[package]] name = "errno" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", @@ -1497,9 +1532,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "glob" @@ -1538,9 +1573,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -1678,6 +1713,25 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "human-time" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "259822ea527bd0d5ebf3108d84e98ac4f20769e2e8b2f3ab76e1dd6e21de7f3c" +dependencies = [ + "human-time-macros", +] + +[[package]] +name = "human-time-macros" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a04129f85bfd960234ed3fa53212a4904881e024322e804ac5a48da239e44a09" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "hyper" version = "0.14.27" @@ -1874,6 +1928,7 @@ dependencies = [ "bytes", "clap", "colored", + "comfy-table", "config", "console", "data-encoding", @@ -1885,6 +1940,7 @@ dependencies = [ "futures", "genawaiter", "hex", + "human-time", "indicatif", "iroh-bytes", "iroh-gossip", @@ -2303,9 +2359,9 @@ checksum = "df39d232f5c40b0891c10216992c2f250c054105cb1e56f0fc9032db6203ecc1" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" [[package]] name = "memoffset" @@ -2353,6 +2409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.48.0", ] @@ -2390,9 +2447,9 @@ dependencies = [ [[package]] name = "netlink-packet-route" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6de2fe935f44cbdfcab77dce2150d68eda75be715cd42d4d6f52b0bd4dcc5b1" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" dependencies = [ "anyhow", "bitflags 1.3.2", @@ -2453,16 +2510,15 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", "memoffset 0.7.1", "pin-utils", - "static_assertions", ] [[package]] @@ -2547,9 +2603,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", @@ -2643,9 +2699,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.31.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" dependencies = [ "memchr", ] @@ -2805,7 +2861,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b13fe415cdf3c8e44518e18a7c95a13431d9bdf6d15367d82b23c377fdd441a" dependencies = [ - "base64 0.21.2", + "base64 0.21.3", "serde", ] @@ -2826,19 +2882,20 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" dependencies = [ + "memchr", "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" dependencies = [ "pest", "pest_generator", @@ -2846,9 +2903,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" +checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" dependencies = [ "pest", "pest_meta", @@ -2859,9 +2916,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" +checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" dependencies = [ "once_cell", "pest", @@ -2890,9 +2947,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -2923,9 +2980,9 @@ dependencies = [ [[package]] name = "platforms" -version = "3.0.2" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" +checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8" [[package]] name = "plotters" @@ -3010,9 +3067,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e" +checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" [[package]] name = "positioned-io" @@ -3026,9 +3083,9 @@ dependencies = [ [[package]] name = "postcard" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9ee729232311d3cd113749948b689627618133b1c5012b77342c1950b25eaeb" +checksum = "d534c6e61df1c7166e636ca612d9820d486fe96ddad37f7abc671517b297488e" dependencies = [ "cobs", "const_format", @@ -3294,9 +3351,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c8bb234e70c863204303507d841e7fa2295e95c822b2bb4ca8ebf57f17b1cb" +checksum = "e13f81c9a9d574310b8351f8666f5a93ac3b0069c45c28ad52c10291389a7cf9" dependencies = [ "bytes", "rand", @@ -3312,9 +3369,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df19e284d93757a9fb91d63672f7741b129246a669db09d1c0063071debc0c0" +checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" dependencies = [ "bytes", "libc", @@ -3507,14 +3564,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.3" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.6", - "regex-syntax 0.7.4", + "regex-automata 0.3.7", + "regex-syntax 0.7.5", ] [[package]] @@ -3528,13 +3585,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.4", + "regex-syntax 0.7.5", ] [[package]] @@ -3545,17 +3602,17 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "reqwest" -version = "0.11.19" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20b9b67e2ca7dd9e9f9285b759de30ff538aab981abaaf7bc9bd90b84a0126c3" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" dependencies = [ - "base64 0.21.2", + "base64 0.21.3", "bytes", "encoding_rs", "futures-core", @@ -3708,9 +3765,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.8" +version = "0.38.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" dependencies = [ "bitflags 2.4.0", "errno", @@ -3721,9 +3778,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.6" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", "ring", @@ -3749,7 +3806,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.2", + "base64 0.21.3", ] [[package]] @@ -3762,6 +3819,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "rusty-fork" version = "0.3.0" @@ -3900,9 +3963,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.171" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] @@ -3918,9 +3981,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.171" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", @@ -3975,7 +4038,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" dependencies = [ - "base64 0.21.2", + "base64 0.21.3", "chrono", "hex", "indexmap 1.9.3", @@ -4059,6 +4122,27 @@ dependencies = [ "dirs", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -4080,9 +4164,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] @@ -4258,6 +4342,25 @@ dependencies = [ "syn 2.0.29", ] +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + [[package]] name = "stun-rs" version = "0.1.4" @@ -4459,9 +4562,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a79d09ac6b08c1ab3906a2f7cc2e81a0e27c7ae89c63812df75e52bef0751e07" +checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" dependencies = [ "deranged", "itoa", @@ -4478,9 +4581,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75c65469ed6b3a4809d987a41eb1dc918e9bc1d92211cbad7ae82931846f7451" +checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" dependencies = [ "time-core", ] @@ -4557,7 +4660,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfb6f50b5523d014ba161512c37457acb16fd8218c883c7152e0a67ab763f2d4" dependencies = [ "async-trait", - "base64 0.21.2", + "base64 0.21.3", "chrono", "futures", "log", @@ -4888,9 +4991,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna 0.4.0", @@ -5263,9 +5366,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d09770118a7eb1ccaf4a594a221334119a44a814fcb0d31c5b85e83e97227a97" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" dependencies = [ "memchr", ] diff --git a/iroh-net/src/magic_endpoint.rs b/iroh-net/src/magic_endpoint.rs index 50c4c836b8..bd41183156 100644 --- a/iroh-net/src/magic_endpoint.rs +++ b/iroh-net/src/magic_endpoint.rs @@ -20,6 +20,8 @@ use crate::{ tls, }; +pub use super::magicsock::EndpointInfo as ConnectionInfo; + /// Builder for [MagicEndpoint] #[derive(Debug)] pub struct MagicEndpointBuilder { @@ -287,6 +289,31 @@ impl MagicEndpoint { self.msock.my_derp().await } + /// Get information on all the nodes we have connection information about. + /// + /// Includes the node's [`PublicKey`], potential DERP region, its addresses with any known + /// latency, and its [`crate::magicsock::ConnectionType`], which let's us know if we are + /// currently communicating with that node over a `Direct` (UDP) or `Relay` (DERP) connection. + /// + /// Connections are currently only pruned on user action (when we explicitly add a new address + /// to the internal [`crate::netmap::NetworkMap`] in [`MagicEndpoint::add_known_addrs`]), so + /// these connections are not necessarily active connections. + pub async fn connection_infos(&self) -> anyhow::Result> { + self.msock.tracked_endpoints().await + } + + /// Get connection information about a specific node. + /// + /// Includes the node's [`PublicKey`], potential DERP region, its addresses with any known + /// latency, and its [`crate::magicsock::ConnectionType`], which let's us know if we are + /// currently communicating with that node over a `Direct` (UDP) or `Relay` (DERP) connection. + pub async fn connection_info( + &self, + node_id: PublicKey, + ) -> anyhow::Result> { + self.msock.tracked_endpoint(node_id).await + } + /// Connect to a remote endpoint. /// /// The PublicKey and the ALPN protocol are required. If you happen to know dialable addresses of @@ -346,6 +373,9 @@ impl MagicEndpoint { /// This updates the magic socket's *netmap* with these addresses, which are used as candidates /// when connecting to this peer (in addition to addresses obtained from a derp server). /// + /// Note: updating the magic socket's *netmap* will also prune any connections that are *not* + /// present in the netmap. + /// /// If no UDP addresses are added, and `derp_region` is `None`, it will error. /// If no UDP addresses are added, and the given `derp_region` cannot be dialed, it will error. pub async fn add_known_addrs( diff --git a/iroh-net/src/magicsock.rs b/iroh-net/src/magicsock.rs index dfb2e433e3..10a5b35904 100644 --- a/iroh-net/src/magicsock.rs +++ b/iroh-net/src/magicsock.rs @@ -68,6 +68,7 @@ mod rebinding_conn; mod timer; mod udp_actor; +pub use self::endpoint::ConnectionType; pub use self::endpoint::EndpointInfo; pub use self::metrics::Metrics; pub use self::timer::Timer; @@ -445,7 +446,7 @@ impl MagicSock { Ok(c) } - /// Retrieve information about known peers' endpoints in the network. + /// Retrieve connection information about nodes in the network. pub async fn tracked_endpoints(&self) -> Result> { let (s, r) = sync::oneshot::channel(); self.inner @@ -456,6 +457,16 @@ impl MagicSock { Ok(res) } + /// Retrieve connection information about a node in the network. + pub async fn tracked_endpoint(&self, node_key: PublicKey) -> Result> { + let (s, r) = sync::oneshot::channel(); + self.inner + .actor_sender + .send(ActorMessage::TrackedEndpoint(node_key, s)) + .await?; + let res = r.await?; + Ok(res) + } /// Query for the local endpoints discovered during the last endpoint discovery. pub async fn local_endpoints(&self) -> Result> { let (s, r) = sync::oneshot::channel(); @@ -831,6 +842,7 @@ impl Drop for WgGuard { #[allow(clippy::large_enum_variant)] enum ActorMessage { TrackedEndpoints(sync::oneshot::Sender>), + TrackedEndpoint(PublicKey, sync::oneshot::Sender>), LocalEndpoints(sync::oneshot::Sender>), GetMappingAddr(PublicKey, sync::oneshot::Sender>), SetPreferredPort(u16, sync::oneshot::Sender<()>), @@ -839,7 +851,7 @@ enum ActorMessage { CloseOrReconnect(u16, &'static str), ReStun(&'static str), EnqueueCallMeMaybe { - derp_addr: u16, + derp_region: u16, endpoint_id: usize, }, SendDiscoMessage { @@ -981,9 +993,12 @@ impl Actor { async fn handle_actor_message(&mut self, msg: ActorMessage) -> bool { match msg { ActorMessage::TrackedEndpoints(s) => { - let eps: Vec<_> = self.peer_map.endpoints().map(|(_, ep)| ep.info()).collect(); + let eps: Vec<_> = self.peer_map.endpoint_infos(); let _ = s.send(eps); } + ActorMessage::TrackedEndpoint(node_key, s) => { + let _ = s.send(self.peer_map.endpoint_info(&node_key)); + } ActorMessage::LocalEndpoints(s) => { let eps: Vec<_> = self.last_endpoints.clone(); let _ = s.send(eps); @@ -1028,10 +1043,10 @@ impl Actor { self.re_stun(reason).await; } ActorMessage::EnqueueCallMeMaybe { - derp_addr, + derp_region, endpoint_id, } => { - self.enqueue_call_me_maybe(derp_addr, endpoint_id).await; + self.enqueue_call_me_maybe(derp_region, endpoint_id).await; } ActorMessage::RebindAll(s) => { self.rebind_all().await; @@ -1135,8 +1150,8 @@ impl Actor { let ep_quic_mapped_addr = match self.peer_map.endpoint_for_node_key_mut(&dm.src) { Some(ep) => { - if ep.derp_addr().is_none() { - ep.add_derp_addr(region_id); + if ep.derp_region().is_none() { + ep.add_derp_region(region_id); } ep.quic_mapped_addr } @@ -1146,7 +1161,7 @@ impl Actor { msock_sender: self.inner.actor_sender.clone(), msock_public_key: self.inner.public_key(), public_key: dm.src, - derp_addr: Some(region_id), + derp_region: Some(region_id), }); self.peer_map.set_endpoint_for_ip_port(&ipp, id); let ep = self.peer_map.by_id_mut(&id).expect("inserted"); @@ -1252,16 +1267,16 @@ impl Actor { ); match ep.get_send_addrs().await { - Ok((Some(udp_addr), Some(derp_addr))) => { + Ok((Some(udp_addr), Some(derp_region))) => { let res = self.send_raw(udp_addr, transmits.clone()).await; - self.send_derp(derp_addr, public_key, Self::split_packets(transmits)); + self.send_derp(derp_region, public_key, Self::split_packets(transmits)); if let Err(err) = res { warn!("failed to send UDP: {:?}", err); } } - Ok((None, Some(derp_addr))) => { - self.send_derp(derp_addr, public_key, Self::split_packets(transmits)); + Ok((None, Some(derp_region))) => { + self.send_derp(derp_region, public_key, Self::split_packets(transmits)); } Ok((Some(udp_addr), None)) => { if let Err(err) = self.send_raw(udp_addr, transmits).await { @@ -1710,12 +1725,12 @@ impl Actor { } #[instrument(skip_all, fields(self.name = %self.inner.name))] - async fn enqueue_call_me_maybe(&mut self, derp_addr: u16, endpoint_id: usize) { + async fn enqueue_call_me_maybe(&mut self, derp_region: u16, endpoint_id: usize) { let endpoint = self.peer_map.by_id(&endpoint_id); if endpoint.is_none() { warn!( "enqueue_call_me_maybe with invalid endpoint_id called: {} - {}", - derp_addr, endpoint_id + derp_region, endpoint_id ); return; } @@ -1738,7 +1753,7 @@ impl Actor { info!("STUN done; sending call-me-maybe",); msg_sender .send(ActorMessage::EnqueueCallMeMaybe { - derp_addr, + derp_region, endpoint_id, }) .await @@ -1761,13 +1776,13 @@ impl Actor { warn!("sending call me maybe to {public_key:?}"); if let Err(err) = msg_sender .send(ActorMessage::SendDiscoMessage { - dst: SendAddr::Derp(derp_addr), + dst: SendAddr::Derp(derp_region), dst_key: public_key, msg, }) .await { - warn!("failed to send disco message to {}: {:?}", derp_addr, err); + warn!("failed to send disco message to {}: {:?}", derp_region, err); } }); } @@ -2049,7 +2064,7 @@ impl Actor { msock_sender: self.inner.actor_sender.clone(), msock_public_key: self.inner.public_key(), public_key: sender, - derp_addr: src.derp_region(), + derp_region: src.derp_region(), }); } self.handle_ping(ping, &sender, src, derp_node_src).await; @@ -2215,7 +2230,7 @@ impl Actor { msock_sender: self.inner.actor_sender.clone(), msock_public_key: self.inner.public_key(), public_key: n.key, - derp_addr: n.derp, + derp_region: n.derp, }); } diff --git a/iroh-net/src/magicsock/endpoint.rs b/iroh-net/src/magicsock/endpoint.rs index efcb181654..7be1663876 100644 --- a/iroh-net/src/magicsock/endpoint.rs +++ b/iroh-net/src/magicsock/endpoint.rs @@ -9,6 +9,7 @@ use std::{ use futures::future::BoxFuture; use iroh_metrics::inc; use rand::seq::IteratorRandom; +use serde::{Deserialize, Serialize}; use tokio::{sync::mpsc, time::Instant}; use tracing::{debug, info, trace, warn}; @@ -58,9 +59,9 @@ pub(super) struct Endpoint { pub(super) public_key: PublicKey, /// Last time we pinged all endpoints last_full_ping: Option, - /// Fallback/bootstrap path, if non-zero (non-zero for well-behaved clients) - /// Only the region_id/port of the mapped derp addr. - derp_addr: Option, + /// The region id of DERP node that we can relay over to communicate. + /// The fallback/bootstrap path, if non-zero (non-zero for well-behaved clients). + derp_region: Option, /// Best non-DERP path. best_addr: Option, /// Time best address re-confirmed. @@ -94,14 +95,14 @@ pub(super) struct Options { pub(super) msock_sender: mpsc::Sender, pub(super) msock_public_key: PublicKey, pub(super) public_key: PublicKey, - pub(super) derp_addr: Option, + pub(super) derp_region: Option, } impl Endpoint { pub fn new(id: usize, options: Options) -> Self { let quic_mapped_addr = QuicMappedAddr::generate(); - if options.derp_addr.is_some() { + if options.derp_region.is_some() { // we potentially have a relay connection to the peer inc!(MagicsockMetrics, num_relay_conns_added); } @@ -113,7 +114,7 @@ impl Endpoint { conn_public_key: options.msock_public_key, public_key: options.public_key, last_full_ping: None, - derp_addr: options.derp_addr, + derp_region: options.derp_region, best_addr: None, best_addr_at: None, trust_best_addr_until: None, @@ -132,32 +133,47 @@ impl Endpoint { /// Returns info about this endpoint pub fn info(&self) -> EndpointInfo { + let (conn_type, latency) = if self.is_best_addr_valid(Instant::now()) { + let addr_info = self.best_addr.as_ref().expect("checked"); + (ConnectionType::Direct(addr_info.addr), addr_info.latency) + } else if let Some(region_id) = self.derp_region { + let latency = match self.endpoint_state.get(&SendAddr::Derp(region_id)) { + Some(endpoint_state) => endpoint_state.recent_pong().map(|pong| pong.latency), + None => None, + }; + (ConnectionType::Relay(region_id), latency) + } else { + (ConnectionType::None, None) + }; let addrs = self .endpoint_state - .keys() - .filter_map(|addr| match addr { - SendAddr::Udp(addr) => Some(*addr), + .iter() + .filter_map(|(addr, endpoint_state)| match addr { + SendAddr::Udp(addr) => { + Some((*addr, endpoint_state.recent_pong().map(|pong| pong.latency))) + } _ => None, }) .collect(); EndpointInfo { + id: self.id, public_key: self.public_key, - derp_addr: self.derp_addr, + derp_region: self.derp_region, addrs, - has_direct_connection: self.is_best_addr_valid(Instant::now()), - latency: self.best_addr.as_ref().and_then(|a| a.latency), + conn_type, + latency, } } - /// Returns the derp addr of this endpoint - pub fn derp_addr(&self) -> Option { - self.derp_addr + /// Returns the derp region of this endpoint + pub fn derp_region(&self) -> Option { + self.derp_region } - /// Adds a derp addr for this endpoint - pub fn add_derp_addr(&mut self, region: u16) { - self.derp_addr = Some(region); + /// Adds a derp region for this endpoint + pub fn add_derp_region(&mut self, region: u16) { + self.derp_region = Some(region); } /// Returns the address(es) that should be used for sending the next packet. @@ -165,7 +181,7 @@ impl Endpoint { fn addr_for_send(&mut self, now: &Instant) -> (Option, Option, bool) { if derp_only_mode() { debug!("in `DEV_DERP_ONLY` mode, giving the DERP address as the only viable address for this endpoint"); - return (None, self.derp_addr, false); + return (None, self.derp_region, false); } match self.best_addr { Some(ref best_addr) => { @@ -176,7 +192,7 @@ impl Endpoint { now, best_addr, self.trust_best_addr_until ); - (Some(best_addr.addr), self.derp_addr, true) + (Some(best_addr.addr), self.derp_region, true) } else { // Address is current and can be used (Some(best_addr.addr), None, false) @@ -185,15 +201,15 @@ impl Endpoint { None => { let (addr, should_ping) = self.get_candidate_udp_addr(); - // Provide backup derp addr if no known latency or no addr. - let derp_addr = if should_ping || addr.is_none() { - self.derp_addr + // Provide backup derp region if no known latency or no addr. + let derp_region = if should_ping || addr.is_none() { + self.derp_region } else { None }; - debug!("using candidate addr {addr:?}, derp addr: {derp_addr:?}"); - (addr, derp_addr, should_ping) + debug!("using candidate addr {addr:?}, derp addr: {derp_region:?}"); + (addr, derp_region, should_ping) } } } @@ -221,7 +237,7 @@ impl Endpoint { if self.best_addr.is_none() { // we now have a direct connection, adjust direct connection count inc!(MagicsockMetrics, num_direct_conns_added); - if self.derp_addr.is_some() { + if self.derp_region.is_some() { // we no longer rely on the relay connection, decrease the relay connection // count inc!(MagicsockMetrics, num_relay_conns_removed); @@ -304,9 +320,9 @@ impl Endpoint { }); let now = Instant::now(); - let (udp_addr, derp_addr, _should_ping) = self.addr_for_send(&now); - if let Some(derp_addr) = derp_addr { - self.start_ping(SendAddr::Derp(derp_addr), now, DiscoPingPurpose::Cli) + let (udp_addr, derp_region, _should_ping) = self.addr_for_send(&now); + if let Some(derp_region) = derp_region { + self.start_ping(SendAddr::Derp(derp_region), now, DiscoPingPurpose::Cli) .await; } if let Some(udp_addr) = udp_addr { @@ -327,7 +343,7 @@ impl Endpoint { // NOTE: this should be checked for before dialing // In our current set up, there is no way to report an error. // TODO(ramfox): figure out method of reporting dial errors this far down into the stack - if udp_addr.is_none() && derp_addr.is_none() { + if udp_addr.is_none() && derp_region.is_none() { tracing::error!( "unable to ping endpoint {} {:?}, no UDP or DERP addresses known.", self.id, @@ -352,7 +368,7 @@ impl Endpoint { if sp.to == addr.addr { // we had a direct connection that is no longer valid inc!(MagicsockMetrics, num_direct_conns_removed); - if self.derp_addr.is_some() { + if self.derp_region.is_some() { // we can only connect through a relay connection inc!(MagicsockMetrics, num_relay_conns_added); } @@ -474,7 +490,7 @@ impl Endpoint { // we no longer rely on a direct connection if self.best_addr.is_some() { inc!(MagicsockMetrics, num_direct_conns_removed); - if self.derp_addr.is_some() { + if self.derp_region.is_some() { inc!(MagicsockMetrics, num_relay_conns_added); } } @@ -514,14 +530,14 @@ impl Endpoint { self.start_ping(ep, now, DiscoPingPurpose::Discovery).await; } - let derp_addr = self.derp_addr; + let derp_region = self.derp_region; if send_call_me_maybe && (sent_any || !have_endpoints) { // If we have no endpoints, we use the CallMeMaybe to trigger an exchange // of potential UDP addresses. // // Otherwise it is used for hole punching, as described below. - if let Some(derp_addr) = derp_addr { + if let Some(derp_region) = derp_region { // Have our magicsock.Conn figure out its STUN endpoint (if // it doesn't know already) and then send a CallMeMaybe // message to our peer via DERP informing them that we've @@ -531,7 +547,7 @@ impl Endpoint { let sender = self.conn_sender.clone(); if let Err(err) = sender .send(ActorMessage::EnqueueCallMeMaybe { - derp_addr, + derp_region, endpoint_id: id, }) .await @@ -546,19 +562,19 @@ impl Endpoint { if self.best_addr.is_none() { // we do not have a direct connection, so changing the derp information may // have an effect on our connection status - if self.derp_addr.is_none() && n.derp.is_some() { + if self.derp_region.is_none() && n.derp.is_some() { // we did not have a relay connection before, but now we do inc!(MagicsockMetrics, num_relay_conns_added) - } else if self.derp_addr.is_some() && n.derp.is_none() { + } else if self.derp_region.is_some() && n.derp.is_none() { // we had a relay connection before but do not have one now inc!(MagicsockMetrics, num_relay_conns_removed) } } debug!( "Changing derp region for {:?} from {:?} to {:?}", - self.public_key, self.derp_addr, n.derp + self.public_key, self.derp_region, n.derp ); - self.derp_addr = n.derp; + self.derp_region = n.derp; for st in self.endpoint_state.values_mut() { st.index = Index::Deleted; // assume deleted until updated in next loop @@ -592,7 +608,7 @@ impl Endpoint { if self.best_addr.is_some() { // we no long rely on a direct connection inc!(MagicsockMetrics, num_direct_conns_removed); - if self.derp_addr.is_some() { + if self.derp_region.is_some() { // we only have a relay connection to the peer inc!(MagicsockMetrics, num_relay_conns_added); } @@ -610,7 +626,7 @@ impl Endpoint { if self.best_addr.is_some() { // we no longer rely on a direct connection inc!(MagicsockMetrics, num_relay_conns_removed); - if self.derp_addr.is_some() { + if self.derp_region.is_some() { // we are now relying on a relay connection inc!(MagicsockMetrics, num_direct_conns_added); } @@ -676,7 +692,7 @@ impl Endpoint { // no longer relying on a direct connection, remove conn count if self.best_addr.is_some() { inc!(MagicsockMetrics, num_direct_conns_removed); - if self.derp_addr.is_some() { + if self.derp_region.is_some() { // we now rely on a relay connection, add a relay count inc!(MagicsockMetrics, num_relay_conns_added); } @@ -811,7 +827,7 @@ impl Endpoint { if self.best_addr.is_none() { // we now have direct connection! inc!(MagicsockMetrics, num_direct_conns_added); - if self.derp_addr.is_some() { + if self.derp_region.is_some() { // no long relying on a relay connection, remove a relay conn inc!(MagicsockMetrics, num_relay_conns_removed); } @@ -881,7 +897,7 @@ impl Endpoint { if self.best_addr.is_some() { // no longer relying on the direct connection inc!(MagicsockMetrics, num_direct_conns_removed); - if self.derp_addr.is_some() { + if self.derp_region.is_some() { // we are now relying on the relay connection, add a relay conn inc!(MagicsockMetrics, num_relay_conns_added); } @@ -958,16 +974,16 @@ impl Endpoint { let now = Instant::now(); self.last_active = now; - let (udp_addr, derp_addr, should_ping) = self.addr_for_send(&now); + let (udp_addr, derp_region, should_ping) = self.addr_for_send(&now); // Trigger a round of pings if we haven't had any full pings yet. if should_ping && self.want_full_ping(&now) { self.send_pings(now, true).await; } - debug!("sending UDP: {:?}, DERP: {:?}", udp_addr, derp_addr); + debug!("sending UDP: {:?}, DERP: {:?}", udp_addr, derp_region); - Ok((udp_addr, derp_addr)) + Ok((udp_addr, derp_region)) } fn is_best_addr_valid(&self, instant: Instant) -> bool { @@ -1068,6 +1084,16 @@ impl PeerMap { self.by_id.iter_mut() } + /// Get the [`EndpointInfo`]s for each endpoint + pub(super) fn endpoint_infos(&self) -> Vec { + self.endpoints().map(|(_, ep)| ep.info()).collect() + } + + /// Get the [`EndpointInfo`]s for each endpoint + pub(super) fn endpoint_info(&self, public_key: &PublicKey) -> Option { + self.endpoint_for_node_key(public_key).map(|ep| ep.info()) + } + /// Inserts a new endpoint into the [`PeerMap`]. pub(super) fn insert_endpoint(&mut self, options: Options) -> usize { let id = self.next_id; @@ -1147,18 +1173,35 @@ struct EndpointState { index: Index, } +/// The type of connection we have to the endpoint. +#[derive(derive_more::Display, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum ConnectionType { + /// Direct UDP connection + #[display("direct")] + Direct(SocketAddr), + /// Relay connection over DERP + #[display("relay")] + Relay(u16), + /// We have no verified connection to this PublicKey + #[display("none")] + None, +} + /// Details about an Endpoint -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct EndpointInfo { + /// The id in the peer_map + pub id: usize, /// The public key of the endpoint. pub public_key: PublicKey, /// Derp region, if available. - pub derp_addr: Option, - /// List of addresses this node might be reachable under. - pub addrs: Vec, - /// Is this node currently direcly reachable? - pub has_direct_connection: bool, - /// Current latency information, for a direct connection if available. + pub derp_region: Option, + /// List of addresses at which this node might be reachable, plus any latency information we + /// have about that address. + pub addrs: Vec<(SocketAddr, Option)>, + /// The type of connection we have to the peer, either direct or over relay. + pub conn_type: ConnectionType, + /// The latency of the `conn_type`. pub latency: Option, } @@ -1263,3 +1306,264 @@ impl AddrLatency { self.latency < other.latency } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::key::SecretKey; + + #[test] + fn test_endpoint_infos() { + // endpoint with a `best_addr` that has a latency + let pong_src = "0.0.0.0:1".parse().unwrap(); + let latency = Duration::from_millis(50); + let (a_endpoint, a_socket_addr) = { + let socket_addr = "0.0.0.0:10".parse().unwrap(); + let now = Instant::now(); + let endpoint_state = HashMap::from([( + SendAddr::Udp(socket_addr), + EndpointState { + index: Index::Some(0), + last_ping: None, + last_got_ping: None, + last_got_ping_tx_id: None, + call_me_maybe_time: None, + recent_pongs: vec![PongReply { + latency, + pong_at: now, + from: SendAddr::Udp(socket_addr), + pong_src, + }], + recent_pong: 0, + }, + )]); + let (send, _) = mpsc::channel(1); + let key = SecretKey::generate(); + ( + Endpoint { + id: 0, + conn_sender: send, + quic_mapped_addr: QuicMappedAddr::generate(), + conn_public_key: key.public(), + public_key: key.public(), + last_full_ping: None, + derp_region: Some(0), + best_addr: Some(AddrLatency { + addr: socket_addr, + latency: Some(latency), + }), + best_addr_at: Some(now), + trust_best_addr_until: now.checked_add(Duration::from_secs(100)), + endpoint_state, + is_call_me_maybe_ep: HashMap::new(), + pending_cli_pings: Vec::new(), + expired: false, + sent_ping: HashMap::new(), + last_active: now, + }, + socket_addr, + ) + }; + // endpoint w/ no best addr but a derp w/ latency + let b_endpoint = { + // let socket_addr = "0.0.0.0:9".parse().unwrap(); + let now = Instant::now(); + let endpoint_state = HashMap::from([( + SendAddr::Derp(0), + EndpointState { + index: Index::Some(1), + last_ping: None, + last_got_ping: None, + last_got_ping_tx_id: None, + call_me_maybe_time: None, + recent_pongs: vec![PongReply { + latency, + pong_at: now, + from: SendAddr::Derp(0), + pong_src, + }], + recent_pong: 0, + }, + )]); + let (send, _) = mpsc::channel(1); + let key = SecretKey::generate(); + Endpoint { + id: 1, + conn_sender: send, + quic_mapped_addr: QuicMappedAddr::generate(), + conn_public_key: key.public(), + public_key: key.public(), + last_full_ping: None, + derp_region: Some(0), + best_addr: None, + best_addr_at: None, + trust_best_addr_until: now.checked_sub(Duration::from_secs(100)), + endpoint_state, + is_call_me_maybe_ep: HashMap::new(), + pending_cli_pings: Vec::new(), + expired: false, + sent_ping: HashMap::new(), + last_active: now, + } + }; + + // endpoint w/ no best addr but a derp w/ no latency + let c_endpoint = { + // let socket_addr = "0.0.0.0:8".parse().unwrap(); + let now = Instant::now(); + let endpoint_state = HashMap::new(); + let (send, _) = mpsc::channel(1); + let key = SecretKey::generate(); + Endpoint { + id: 2, + conn_sender: send, + quic_mapped_addr: QuicMappedAddr::generate(), + conn_public_key: key.public(), + public_key: key.public(), + last_full_ping: None, + derp_region: Some(0), + best_addr: None, + best_addr_at: None, + trust_best_addr_until: now.checked_sub(Duration::from_secs(100)), + endpoint_state, + is_call_me_maybe_ep: HashMap::new(), + pending_cli_pings: Vec::new(), + expired: false, + sent_ping: HashMap::new(), + last_active: now, + } + }; + + // endpoint w/ expired best addr + let (d_endpoint, d_socket_addr) = { + let socket_addr = "0.0.0.0:7".parse().unwrap(); + let now = Instant::now(); + let expired = now.checked_sub(Duration::from_secs(100)).unwrap(); + let endpoint_state = HashMap::from([ + ( + SendAddr::Udp(socket_addr), + EndpointState { + index: Index::Some(0), + last_ping: None, + last_got_ping: None, + last_got_ping_tx_id: None, + call_me_maybe_time: None, + recent_pongs: vec![PongReply { + latency, + pong_at: now, + from: SendAddr::Udp(socket_addr), + pong_src, + }], + recent_pong: 0, + }, + ), + ( + SendAddr::Derp(0), + EndpointState { + index: Index::Some(1), + last_ping: None, + last_got_ping: None, + last_got_ping_tx_id: None, + call_me_maybe_time: None, + recent_pongs: vec![PongReply { + latency, + pong_at: now, + from: SendAddr::Derp(0), + pong_src, + }], + recent_pong: 0, + }, + ), + ]); + let (send, _) = mpsc::channel(1); + let key = SecretKey::generate(); + ( + Endpoint { + id: 3, + conn_sender: send, + quic_mapped_addr: QuicMappedAddr::generate(), + conn_public_key: key.public(), + public_key: key.public(), + last_full_ping: None, + derp_region: Some(0), + best_addr: Some(AddrLatency { + addr: socket_addr, + latency: Some(Duration::from_millis(80)), + }), + best_addr_at: Some(now), + trust_best_addr_until: Some(expired), + endpoint_state, + is_call_me_maybe_ep: HashMap::new(), + pending_cli_pings: Vec::new(), + expired: false, + sent_ping: HashMap::new(), + last_active: now, + }, + socket_addr, + ) + }; + let expect = Vec::from([ + EndpointInfo { + id: a_endpoint.id, + public_key: a_endpoint.public_key, + derp_region: a_endpoint.derp_region, + addrs: Vec::from([(a_socket_addr, Some(latency))]), + conn_type: ConnectionType::Direct(a_socket_addr), + latency: Some(latency), + }, + EndpointInfo { + id: b_endpoint.id, + public_key: b_endpoint.public_key, + derp_region: b_endpoint.derp_region, + addrs: Vec::new(), + conn_type: ConnectionType::Relay(0), + latency: Some(latency), + }, + EndpointInfo { + id: c_endpoint.id, + public_key: c_endpoint.public_key, + derp_region: c_endpoint.derp_region, + addrs: Vec::new(), + conn_type: ConnectionType::Relay(0), + latency: None, + }, + EndpointInfo { + id: d_endpoint.id, + public_key: d_endpoint.public_key, + derp_region: d_endpoint.derp_region, + addrs: Vec::from([(d_socket_addr, Some(latency))]), + conn_type: ConnectionType::Relay(0), + latency: Some(latency), + }, + ]); + + let peer_map = PeerMap { + by_node_key: HashMap::from([ + (a_endpoint.public_key, a_endpoint.id), + (b_endpoint.public_key, b_endpoint.id), + (c_endpoint.public_key, c_endpoint.id), + (d_endpoint.public_key, d_endpoint.id), + ]), + by_ip_port: HashMap::from([ + (SendAddr::Udp(a_socket_addr), a_endpoint.id), + (SendAddr::Udp(d_socket_addr), d_endpoint.id), + ]), + by_quic_mapped_addr: HashMap::from([ + (a_endpoint.quic_mapped_addr, a_endpoint.id), + (b_endpoint.quic_mapped_addr, b_endpoint.id), + (c_endpoint.quic_mapped_addr, c_endpoint.id), + (d_endpoint.quic_mapped_addr, d_endpoint.id), + ]), + by_id: HashMap::from([ + (a_endpoint.id, a_endpoint), + (b_endpoint.id, b_endpoint), + (c_endpoint.id, c_endpoint), + (d_endpoint.id, d_endpoint), + ]), + next_id: 5, + }; + let mut got = peer_map.endpoint_infos(); + got.sort_by_key(|p| p.id); + assert_eq!(expect, got); + } +} diff --git a/iroh/Cargo.toml b/iroh/Cargo.toml index 1197c70b66..0fc847d075 100644 --- a/iroh/Cargo.toml +++ b/iroh/Cargo.toml @@ -48,10 +48,12 @@ walkdir = "2" # CLI clap = { version = "4", features = ["derive"], optional = true } +comfy-table = { version = "7.0.1", optional = true } config = { version = "0.13.1", default-features = false, features = ["toml", "preserve_order"], optional = true } console = { version = "0.15.5", optional = true } dirs-next = { version = "2.0.0", optional = true } indicatif = { version = "0.17", features = ["tokio"], optional = true } +human-time = { version = "0.1.6", optional = true } multibase = { version = "0.9.1", optional = true } rustyline = { version = "12.0.0", optional = true } shell-words = { version = "1.1.0", optional = true } @@ -67,7 +69,7 @@ ed25519-dalek = { version = "2.0.0", features = ["serde", "rand_core"], optional [features] default = ["cli", "metrics"] -cli = ["clap", "config", "console", "dirs-next", "indicatif", "multibase", "quic-rpc/quinn-transport", "tempfile", "tokio/rt-multi-thread", "tracing-subscriber", "flat-db", "mem-db", "iroh-collection", "shell-words", "shellexpand", "rustyline", "colored", "toml"] +cli = ["clap", "config", "console", "dirs-next", "indicatif", "multibase", "quic-rpc/quinn-transport", "tempfile", "tokio/rt-multi-thread", "tracing-subscriber", "flat-db", "mem-db", "iroh-collection", "shell-words", "shellexpand", "rustyline", "colored", "toml", "human-time", "comfy-table"] metrics = ["iroh-metrics"] mem-db = [] flat-db = [] diff --git a/iroh/src/client.rs b/iroh/src/client.rs index bdcffd09c4..d3fa9ae486 100644 --- a/iroh/src/client.rs +++ b/iroh/src/client.rs @@ -9,16 +9,18 @@ use anyhow::{anyhow, Result}; use bytes::Bytes; use futures::{Stream, StreamExt, TryStreamExt}; use iroh_bytes::Hash; +use iroh_net::{key::PublicKey, magic_endpoint::ConnectionInfo}; use iroh_sync::store::GetFilter; use iroh_sync::sync::{AuthorId, NamespaceId}; use iroh_sync::Entry; use quic_rpc::{RpcClient, ServiceConnection}; use crate::rpc_protocol::{ - AuthorCreateRequest, AuthorListRequest, BytesGetRequest, CounterStats, DocCreateRequest, - DocGetOneRequest, DocGetRequest, DocImportRequest, DocInfoRequest, DocListRequest, - DocSetRequest, DocShareRequest, DocStartSyncRequest, DocStopSyncRequest, DocSubscribeRequest, - DocTicket, ProviderService, ShareMode, StatsGetRequest, + AuthorCreateRequest, AuthorListRequest, BytesGetRequest, ConnectionInfoRequest, + ConnectionInfoResponse, ConnectionsRequest, CounterStats, DocCreateRequest, DocGetOneRequest, + DocGetRequest, DocImportRequest, DocInfoRequest, DocListRequest, DocSetRequest, + DocShareRequest, DocStartSyncRequest, DocStopSyncRequest, DocSubscribeRequest, DocTicket, + ProviderService, ShareMode, StatsGetRequest, }; use crate::sync_engine::{LiveEvent, LiveStatus, PeerSource}; @@ -113,6 +115,19 @@ where let res = self.rpc.rpc(StatsGetRequest {}).await??; Ok(res.stats) } + + /// Get information about the different connections we have made + pub async fn connections(&self) -> Result>> { + let stream = self.rpc.server_streaming(ConnectionsRequest {}).await?; + Ok(flatten(stream).map_ok(|res| res.conn_info)) + } + + /// Get connection information about a node + pub async fn connection_info(&self, node_id: PublicKey) -> Result> { + let ConnectionInfoResponse { conn_info } = + self.rpc.rpc(ConnectionInfoRequest { node_id }).await??; + Ok(conn_info) + } } /// Document handle diff --git a/iroh/src/commands.rs b/iroh/src/commands.rs index f7ea393cfa..53f03a852d 100644 --- a/iroh/src/commands.rs +++ b/iroh/src/commands.rs @@ -1,15 +1,26 @@ use std::str::FromStr; -use std::{net::SocketAddr, path::PathBuf}; +use std::{net::SocketAddr, path::PathBuf, time::Duration}; use anyhow::Result; use clap::{Args, Parser, Subcommand}; +use comfy_table::presets::NOTHING; +use comfy_table::{Cell, Table}; +use futures::stream::BoxStream; use futures::StreamExt; +use human_time::ToHumanTimeString; use iroh::client::quic::{Iroh, RpcClient}; use iroh::dial::Ticket; use iroh::rpc_protocol::*; +use iroh_bytes::util::RpcError; use iroh_bytes::{protocol::RequestToken, util::runtime, Hash}; -use iroh_net::key::{PublicKey, SecretKey}; +use iroh_net::{ + key::{PublicKey, SecretKey}, + magic_endpoint::ConnectionInfo, +}; +use quic_rpc::client::StreamingResponseItemError; +use quic_rpc::transport::ConnectionErrors; +use crate::commands::sync::fmt_short; use crate::config::{ConsoleEnv, NodeConfig}; use self::provide::{ProvideOptions, ProviderRpcPort}; @@ -299,7 +310,12 @@ pub enum RpcCommands { } #[derive(Subcommand, Debug, Clone)] +#[allow(clippy::large_enum_variant)] pub enum NodeCommands { + /// Get information about the different connections we have made + Connections, + /// Get connection information about a particular node + Connection { node_id: PublicKey }, /// Get status of the running node. Status, /// Get statistics and metrics from the running node. @@ -318,6 +334,20 @@ pub enum NodeCommands { impl NodeCommands { pub async fn run(self, client: RpcClient) -> Result<()> { match self { + Self::Connections => { + let connections = client.server_streaming(ConnectionsRequest).await?; + println!("{}", fmt_connections(connections).await); + } + Self::Connection { node_id } => { + let conn_info = client + .rpc(ConnectionInfoRequest { node_id }) + .await?? + .conn_info; + match conn_info { + Some(info) => println!("{}", fmt_connection(info)), + None => println!("Not Found"), + } + } Self::Shutdown { force } => { client.rpc(ShutdownRequest { force }).await?; } @@ -522,3 +552,64 @@ impl FromStr for RequestTokenOptions { Ok(Self::Token(token)) } } + +fn bold_cell(s: &str) -> Cell { + Cell::new(s).add_attribute(comfy_table::Attribute::Bold) +} + +async fn fmt_connections( + mut infos: BoxStream< + 'static, + Result, StreamingResponseItemError>, + >, +) -> String { + let mut table = Table::new(); + table.load_preset(NOTHING).set_header( + vec!["node id", "region", "conn type", "latency"] + .into_iter() + .map(bold_cell), + ); + while let Some(Ok(Ok(ConnectionsResponse { conn_info }))) = infos.next().await { + let node_id = conn_info.public_key.to_string(); + let region = conn_info + .derp_region + .map_or(String::new(), |region| region.to_string()); + let conn_type = conn_info.conn_type.to_string(); + let latency = match conn_info.latency { + Some(latency) => latency.to_human_time_string(), + None => String::from("unknown"), + }; + table.add_row(vec![node_id, region, conn_type, latency]); + } + table.to_string() +} + +fn fmt_connection(info: ConnectionInfo) -> String { + format!( + "node_id: {}\nderp_region: {}\nconnection type: {}\nlatency: {}\n\n{}", + fmt_short(info.public_key), + info.derp_region + .map_or(String::from("unknown"), |r| r.to_string()), + info.conn_type, + fmt_latency(info.latency), + fmt_addrs(info.addrs) + ) +} + +fn fmt_addrs(addrs: Vec<(SocketAddr, Option)>) -> String { + let mut table = Table::new(); + table + .load_preset(NOTHING) + .set_header(vec!["addr", "latency"].into_iter().map(bold_cell)); + for addr in addrs { + table.add_row(vec![addr.0.to_string(), fmt_latency(addr.1)]); + } + table.to_string() +} + +fn fmt_latency(latency: Option) -> String { + match latency { + Some(latency) => latency.to_human_time_string(), + None => String::from("unknown"), + } +} diff --git a/iroh/src/node.rs b/iroh/src/node.rs index 1080f9d92b..0ba3142663 100644 --- a/iroh/src/node.rs +++ b/iroh/src/node.rs @@ -58,11 +58,13 @@ use tracing::{debug, error, info, trace, warn}; use crate::dial::Ticket; use crate::download::Downloader; use crate::rpc_protocol::{ - BytesGetRequest, BytesGetResponse, ListBlobsRequest, ListBlobsResponse, ListCollectionsRequest, - ListCollectionsResponse, ListIncompleteBlobsRequest, ListIncompleteBlobsResponse, - ProvideRequest, ProviderRequest, ProviderResponse, ProviderService, ShareRequest, - ShutdownRequest, StatsGetRequest, StatsGetResponse, StatusRequest, StatusResponse, - ValidateRequest, VersionRequest, VersionResponse, WatchRequest, WatchResponse, + BytesGetRequest, BytesGetResponse, ConnectionInfoRequest, ConnectionInfoResponse, + ConnectionsRequest, ConnectionsResponse, ListBlobsRequest, ListBlobsResponse, + ListCollectionsRequest, ListCollectionsResponse, ListIncompleteBlobsRequest, + ListIncompleteBlobsResponse, ProvideRequest, ProviderRequest, ProviderResponse, + ProviderService, ShareRequest, ShutdownRequest, StatsGetRequest, StatsGetResponse, + StatusRequest, StatusResponse, ValidateRequest, VersionRequest, VersionResponse, WatchRequest, + WatchResponse, }; use crate::sync_engine::{SyncEngine, SYNC_ALPN}; @@ -1163,6 +1165,39 @@ impl RpcHandler { .map_err(|_err| anyhow::anyhow!("task failed to complete"))??; Ok(BytesGetResponse { data }) } + + fn connections( + self, + _: ConnectionsRequest, + ) -> impl Stream> + Send + 'static { + // provide a little buffer so that we don't slow down the sender + let (tx, rx) = flume::bounded(32); + self.rt().local_pool().spawn_pinned(|| async move { + match self.inner.endpoint.connection_infos().await { + Ok(mut conn_infos) => { + conn_infos.sort_by_key(|n| n.public_key.to_string()); + for conn_info in conn_infos { + tx.send_async(Ok(ConnectionsResponse { conn_info })) + .await + .ok(); + } + } + Err(e) => { + tx.send_async(Err(e.into())).await.ok(); + } + } + }); + rx.into_stream() + } + + async fn connection_info( + self, + req: ConnectionInfoRequest, + ) -> RpcResult { + let ConnectionInfoRequest { node_id } = req; + let conn_info = self.inner.endpoint.connection_info(node_id).await?; + Ok(ConnectionInfoResponse { conn_info }) + } } fn handle_rpc_request< @@ -1291,6 +1326,11 @@ fn handle_rpc_request< }) .await } + Connections(msg) => { + chan.server_streaming(msg, handler, RpcHandler::connections) + .await + } + ConnectionInfo(msg) => chan.rpc(msg, handler, RpcHandler::connection_info).await, // TODO: make streaming BytesGet(msg) => chan.rpc(msg, handler, RpcHandler::bytes_get).await, } diff --git a/iroh/src/rpc_protocol.rs b/iroh/src/rpc_protocol.rs index 3ca58ed232..fc911552f0 100644 --- a/iroh/src/rpc_protocol.rs +++ b/iroh/src/rpc_protocol.rs @@ -13,7 +13,7 @@ use bytes::Bytes; use derive_more::{From, TryInto}; use iroh_bytes::{protocol::RequestToken, provider::ShareProgress, Hash}; use iroh_gossip::proto::util::base32; -use iroh_net::key::PublicKey; +use iroh_net::{key::PublicKey, magic_endpoint::ConnectionInfo}; use iroh_sync::{ store::GetFilter, @@ -194,6 +194,46 @@ impl RpcMsg for VersionRequest { type Response = VersionResponse; } +/// List connection information about all the nodes we know about +/// +/// These can be nodes that we have explicitly connected to or nodes +/// that have initiated connections to us. +#[derive(Debug, Serialize, Deserialize)] +pub struct ConnectionsRequest; + +/// A response to a connections request +#[derive(Debug, Serialize, Deserialize)] +pub struct ConnectionsResponse { + /// Information about a connection + pub conn_info: ConnectionInfo, +} + +impl Msg for ConnectionsRequest { + type Pattern = ServerStreaming; +} + +impl ServerStreamingMsg for ConnectionsRequest { + type Response = RpcResult; +} + +/// Get connection information about a specific node +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ConnectionInfoRequest { + /// The node identifier + pub node_id: PublicKey, +} + +/// A response to a connection request +#[derive(Debug, Serialize, Deserialize)] +pub struct ConnectionInfoResponse { + /// Information about a connection to a node + pub conn_info: Option, +} + +impl RpcMsg for ConnectionInfoRequest { + type Response = RpcResult; +} + /// A request to shutdown the node #[derive(Serialize, Deserialize, Debug)] pub struct ShutdownRequest { @@ -648,6 +688,9 @@ pub enum ProviderRequest { BytesGet(BytesGetRequest), + Connections(ConnectionsRequest), + ConnectionInfo(ConnectionInfoRequest), + Stats(StatsGetRequest), } @@ -682,6 +725,9 @@ pub enum ProviderResponse { DocStopSync(RpcResult), DocSubscribe(RpcResult), + Connections(RpcResult), + ConnectionInfo(RpcResult), + BytesGet(RpcResult), Stats(RpcResult),