Skip to content

Commit d1c1dab

Browse files
Frandoflub
andauthored
refactor: improve path watching, add path stats (#3622)
## Description This improves how we expose paths and path stats for connections, and also updates feat-multipath to use n0-computer/quinn#168. * The watcher for open paths internally uses a SmallVec to not allocate in the common case of not-too-many paths * The path info for a path now includes a boolean whether this is the currently selected primary transmission path * We no longer expose PathIds to users * We expose stats for paths from `Connection::path_stats` ## Breaking Changes <!-- Optional, if there are any breaking changes document them, including how to migrate older code. --> ## Notes & open questions <!-- Any notes, remarks or open questions you have to make about the PR. --> ## Change checklist <!-- Remove any that are not relevant. --> - [ ] Self-review. - [ ] Documentation updates following the [style guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text), if relevant. - [ ] Tests if relevant. - [ ] All breaking changes documented. - [ ] List all breaking changes in the above "Breaking Changes" section. - [ ] Open an issue or PR on any number0 repos that are affected by this breaking change. Give guidance on how the updates should be handled or do the actual updates themselves. The major ones are: - [ ] [`quic-rpc`](https://github.com/n0-computer/quic-rpc) - [ ] [`iroh-gossip`](https://github.com/n0-computer/iroh-gossip) - [ ] [`iroh-blobs`](https://github.com/n0-computer/iroh-blobs) - [ ] [`dumbpipe`](https://github.com/n0-computer/dumbpipe) - [ ] [`sendme`](https://github.com/n0-computer/sendme) --------- Co-authored-by: Floris Bruynooghe <[email protected]>
1 parent 147e6bb commit d1c1dab

File tree

7 files changed

+288
-135
lines changed

7 files changed

+288
-135
lines changed

Cargo.lock

Lines changed: 16 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

iroh/Cargo.toml

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,7 @@ backon = { version = "1.4" }
2626
bytes = "1.7"
2727
crypto_box = { version = "0.10.0-pre.0", features = ["serde", "chacha20"] }
2828
data-encoding = "2.2"
29-
derive_more = { version = "2.0.1", features = [
30-
"debug",
31-
"display",
32-
"from",
33-
"try_into",
34-
"deref",
35-
"from_str"
36-
] }
29+
derive_more = { version = "2.0.1", features = ["debug", "display", "from", "try_into", "deref", "from_str", "into_iterator"] }
3730
ed25519-dalek = { version = "3.0.0-pre.1", features = ["serde", "rand_core", "zeroize", "pkcs8", "pem"] }
3831
http = "1"
3932
iroh-base = { version = "0.95.1", default-features = false, features = ["key", "relay"], path = "../iroh-base" }

iroh/src/endpoint.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ use tracing::{debug, instrument, trace, warn};
2525
use url::Url;
2626

2727
pub use super::magicsock::{
28-
AddEndpointAddrError, ConnectionType, DirectAddr, DirectAddrType, PathInfo, PathsInfo,
29-
endpoint_map::Source,
28+
AddEndpointAddrError, ConnectionType, DirectAddr, DirectAddrType, PathInfo,
29+
endpoint_map::{PathInfoList, Source},
3030
};
3131
#[cfg(wasm_browser)]
3232
use crate::discovery::pkarr::PkarrResolver;
@@ -54,12 +54,12 @@ pub mod presets;
5454
pub use quinn::{
5555
AcceptBi, AcceptUni, AckFrequencyConfig, ApplicationClose, Chunk, ClosedStream,
5656
ConnectionClose, ConnectionError, ConnectionStats, MtuDiscoveryConfig, OpenBi, OpenUni,
57-
ReadDatagram, ReadError, ReadExactError, ReadToEndError, RecvStream, ResetError, RetryError,
58-
SendDatagramError, SendStream, ServerConfig, StoppedError, StreamId, TransportConfig, VarInt,
59-
WeakConnectionHandle, WriteError,
57+
PathStats, ReadDatagram, ReadError, ReadExactError, ReadToEndError, RecvStream, ResetError,
58+
RetryError, SendDatagramError, SendStream, ServerConfig, StoppedError, StreamId,
59+
TransportConfig, VarInt, WeakConnectionHandle, WriteError,
6060
};
6161
pub use quinn_proto::{
62-
FrameStats, PathStats, TransportError, TransportErrorCode, UdpStats, Written,
62+
FrameStats, TransportError, TransportErrorCode, UdpStats, Written,
6363
congestion::{Controller, ControllerFactory},
6464
crypto::{
6565
AeadKey, CryptoError, ExportKeyingMaterialError, HandshakeTokenKey,
@@ -1802,11 +1802,11 @@ mod tests {
18021802
let conn = ep.connect(dst, TEST_ALPN).await?;
18031803
let mut send = conn.open_uni().await.anyerr()?;
18041804
send.write_all(b"hello").await.anyerr()?;
1805-
let mut paths = conn.paths_info().stream();
1805+
let mut paths = conn.paths().stream();
18061806
info!("Waiting for direct connection");
18071807
while let Some(infos) = paths.next().await {
18081808
info!(?infos, "new PathInfos");
1809-
if infos.keys().any(|addr| addr.is_ip()) {
1809+
if infos.iter().any(|info| info.is_ip()) {
18101810
break;
18111811
}
18121812
}
@@ -1893,15 +1893,15 @@ mod tests {
18931893

18941894
// We should be connected via IP, because it is faster than the relay server.
18951895
// TODO: Maybe not panic if this is not true?
1896-
let path_info = conn.paths_info().get();
1896+
let path_info = conn.paths().get();
18971897
assert_eq!(path_info.len(), 1);
1898-
assert!(path_info.keys().next().unwrap().is_ip());
1898+
assert!(path_info.iter().next().unwrap().is_ip());
18991899

1900-
let mut paths = conn.paths_info().stream();
1900+
let mut paths = conn.paths().stream();
19011901
time::timeout(Duration::from_secs(5), async move {
19021902
while let Some(infos) = paths.next().await {
19031903
info!(?infos, "new PathInfos");
1904-
if infos.keys().any(|a| a.is_relay()) {
1904+
if infos.iter().any(|info| info.is_relay()) {
19051905
break;
19061906
}
19071907
}
@@ -1942,11 +1942,11 @@ mod tests {
19421942
// Wait for a relay connection to be added. Client does all the asserting here,
19431943
// we just want to wait so we get to see all the mechanics of the connection
19441944
// being added on this side too.
1945-
let mut paths = conn.paths_info().stream();
1945+
let mut paths = conn.paths().stream();
19461946
time::timeout(Duration::from_secs(5), async move {
19471947
while let Some(infos) = paths.next().await {
19481948
info!(?infos, "new PathInfos");
1949-
if infos.keys().any(|a| a.is_relay()) {
1949+
if infos.iter().any(|path| path.is_relay()) {
19501950
break;
19511951
}
19521952
}

iroh/src/endpoint/connection.rs

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
//! [module docs]: crate
1919
use std::{
2020
any::Any,
21-
collections::HashMap,
2221
future::{Future, IntoFuture},
2322
net::{IpAddr, SocketAddr},
2423
pin::Pin,
@@ -31,19 +30,18 @@ use futures_util::{FutureExt, future::Shared};
3130
use iroh_base::EndpointId;
3231
use n0_error::{e, stack_error};
3332
use n0_future::time::Duration;
34-
use n0_watcher::{Watchable, Watcher};
33+
use n0_watcher::Watcher;
3534
use pin_project::pin_project;
3635
use quinn::{
3736
AcceptBi, AcceptUni, ConnectionError, ConnectionStats, OpenBi, OpenUni, ReadDatagram,
3837
RetryError, SendDatagramError, ServerConfig, VarInt,
3938
};
40-
use quinn_proto::PathId;
4139
use tracing::warn;
4240

4341
use crate::{
4442
Endpoint,
4543
discovery::DiscoveryTask,
46-
magicsock::{PathInfo, PathsInfo},
44+
magicsock::endpoint_map::{PathInfoList, PathsWatchable},
4745
};
4846

4947
/// Future produced by [`Endpoint::accept`].
@@ -239,38 +237,16 @@ fn conn_from_quinn_conn(
239237
}
240238
let remote_id = remote_id_from_quinn_conn(&conn)?;
241239
let alpn = alpn_from_quinn_conn(&conn).ok_or_else(|| e!(AuthenticationError::NoAlpn))?;
242-
let paths_info_watchable = init_paths_info_watcher(&conn, ep);
243-
let paths_info = paths_info_watchable.watch();
244240
// Register this connection with the magicsock.
245-
ep.msock
246-
.register_connection(remote_id, &conn, paths_info_watchable.clone());
241+
let paths = ep.msock.register_connection(remote_id, &conn);
247242
Ok(Connection {
248243
remote_id,
249244
alpn,
250245
inner: conn,
251-
paths_info,
246+
paths,
252247
})
253248
}
254249

255-
fn init_paths_info_watcher(conn: &quinn::Connection, ep: &Endpoint) -> Watchable<PathsInfo> {
256-
let mut paths_info = HashMap::with_capacity(5);
257-
if let Some(path0) = conn.path(PathId::ZERO) {
258-
// This all is supposed to be infallible, but anyway.
259-
if let Ok(remote) = path0.remote_address() {
260-
if let Some(remote) = ep.msock.endpoint_map.transport_addr_from_mapped(remote) {
261-
paths_info.insert(
262-
remote.clone(),
263-
PathInfo {
264-
remote,
265-
path_id: PathId::ZERO,
266-
},
267-
);
268-
}
269-
}
270-
}
271-
n0_watcher::Watchable::new(paths_info)
272-
}
273-
274250
/// Returns the [`EndpointId`] from the peer's TLS certificate.
275251
///
276252
/// The [`PublicKey`] of an endpoint is also known as an [`EndpointId`]. This [`PublicKey`] is
@@ -1245,7 +1221,7 @@ pub struct Connection {
12451221
inner: quinn::Connection,
12461222
remote_id: EndpointId,
12471223
alpn: Vec<u8>,
1248-
paths_info: n0_watcher::Direct<PathsInfo>,
1224+
paths: PathsWatchable,
12491225
}
12501226

12511227
#[allow(missing_docs)]
@@ -1480,13 +1456,21 @@ impl Connection {
14801456
self.inner.stable_id()
14811457
}
14821458

1483-
/// Returns information about the network paths in use by this connection.
1459+
/// Returns a [`Watcher`] for the network paths of this connection.
14841460
///
14851461
/// A connection can have several network paths to the remote endpoint, commonly there
1486-
/// will be a path via the relay server and a holepunched path. This returns all the
1487-
/// paths in use by this connection.
1488-
pub fn paths_info(&self) -> impl Watcher<Value = PathsInfo> {
1489-
self.paths_info.clone()
1462+
/// will be a path via the relay server and a holepunched path.
1463+
///
1464+
/// The watcher is updated whenever a path is opened or closed, or when the path selected
1465+
/// for transmission changes (see [`PathInfo::is_selected`]).
1466+
///
1467+
/// The [`PathInfoList`] returned from the watcher contains a [`PathInfo`] for each
1468+
/// transmission path.
1469+
///
1470+
/// [`PathInfo::is_selected`]: crate::magicsock::PathInfo::is_selected
1471+
/// [`PathInfo`]: crate::magicsock::PathInfo
1472+
pub fn paths(&self) -> impl Watcher<Value = PathInfoList> {
1473+
self.paths.watch(self.inner.weak_handle())
14901474
}
14911475

14921476
/// Derives keying material from this connection's TLS session secrets.

0 commit comments

Comments
 (0)