iroh_quinn/
path.rs

1use std::future::Future;
2use std::net::SocketAddr;
3use std::pin::Pin;
4use std::sync::Arc;
5use std::task::{Context, Poll, ready};
6use std::time::Duration;
7
8use proto::{
9    ClosePathError, ClosedPath, PathError, PathEvent, PathId, PathStats, PathStatus,
10    SetPathStatusError, TransportErrorCode,
11};
12use tokio::sync::watch;
13use tokio_stream::{Stream, wrappers::WatchStream};
14
15use crate::connection::ConnectionRef;
16use crate::{Runtime, WeakConnectionHandle};
17
18/// Future produced by [`crate::Connection::open_path`]
19pub struct OpenPath(OpenPathInner);
20
21enum OpenPathInner {
22    /// Opening a path in underway
23    ///
24    /// This might fail later on.
25    Ongoing {
26        opened: WatchStream<Result<(), PathError>>,
27        path_id: PathId,
28        conn: ConnectionRef,
29    },
30    /// Opening a path failed immediately
31    Rejected {
32        /// The error that occurred
33        err: PathError,
34    },
35    /// The path is already open
36    Ready {
37        path_id: PathId,
38        conn: ConnectionRef,
39    },
40}
41
42impl OpenPath {
43    pub(crate) fn new(
44        path_id: PathId,
45        opened: watch::Receiver<Result<(), PathError>>,
46        conn: ConnectionRef,
47    ) -> Self {
48        Self(OpenPathInner::Ongoing {
49            opened: WatchStream::from_changes(opened),
50            path_id,
51            conn,
52        })
53    }
54
55    pub(crate) fn ready(path_id: PathId, conn: ConnectionRef) -> Self {
56        Self(OpenPathInner::Ready { path_id, conn })
57    }
58
59    pub(crate) fn rejected(err: PathError) -> Self {
60        Self(OpenPathInner::Rejected { err })
61    }
62
63    /// Returns the path ID of the new path being opened.
64    ///
65    /// If an error occurred before a path ID was allocated, `None` is returned.  In this
66    /// case the future is ready and polling it will immediately yield the error.
67    ///
68    /// The returned value remains the same for the entire lifetime of this future.
69    pub fn path_id(&self) -> Option<PathId> {
70        match self.0 {
71            OpenPathInner::Ongoing { path_id, .. } => Some(path_id),
72            OpenPathInner::Rejected { .. } => None,
73            OpenPathInner::Ready { path_id, .. } => Some(path_id),
74        }
75    }
76}
77
78impl Future for OpenPath {
79    type Output = Result<Path, PathError>;
80    fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
81        match self.get_mut().0 {
82            OpenPathInner::Ongoing {
83                ref mut opened,
84                path_id,
85                ref mut conn,
86            } => match ready!(Pin::new(opened).poll_next(ctx)) {
87                Some(value) => {
88                    Poll::Ready(value.map(|_| Path::new_unchecked(conn.clone(), path_id)))
89                }
90                None => {
91                    // This only happens if receiving a notification change failed, this means the
92                    // sender was dropped. This generally should not happen so we use a transient
93                    // error
94                    Poll::Ready(Err(PathError::ValidationFailed))
95                }
96            },
97            OpenPathInner::Ready {
98                path_id,
99                ref mut conn,
100            } => Poll::Ready(Ok(Path::new_unchecked(conn.clone(), path_id))),
101            OpenPathInner::Rejected { err } => Poll::Ready(Err(err)),
102        }
103    }
104}
105
106/// An open network transmission within a multipath-enabled connection.
107///
108/// As long as a [`Path`] or [`WeakPathHandle`] is alive, it is ensured that the [`PathStats`] for this path
109/// are not dropped even after the path is abandoned.
110#[derive(Debug)]
111pub struct Path {
112    id: PathId,
113    conn: ConnectionRef,
114}
115
116impl Clone for Path {
117    fn clone(&self) -> Self {
118        self.conn
119            .state
120            .lock("Path::clone")
121            .increment_path_refs(self.id);
122        Self {
123            id: self.id,
124            conn: self.conn.clone(),
125        }
126    }
127}
128
129impl Drop for Path {
130    fn drop(&mut self) {
131        let mut state = self.conn.state.lock("Path::drop");
132        state.decrement_path_refs(self.id);
133    }
134}
135
136impl Path {
137    /// Returns a [`Path`] for a path id, after checking that the path is not closed.
138    pub(crate) fn new(conn: &ConnectionRef, id: PathId) -> Option<Self> {
139        {
140            let mut state = conn.state.lock("Path::new");
141            // TODO(flub): Using this to know if the path still exists is... hacky.
142            state.inner.path_status(id).ok()?;
143            state.increment_path_refs(id);
144        }
145        Some(Self {
146            id,
147            conn: conn.clone(),
148        })
149    }
150
151    /// Returns a [`Path`] for a path id without checking if the path exists or is closed.
152    fn new_unchecked(conn: ConnectionRef, id: PathId) -> Self {
153        conn.state
154            .lock("Path::new_unchecked")
155            .increment_path_refs(id);
156        Self { id, conn }
157    }
158
159    /// Returns a [`WeakPathHandle`] for this path.
160    ///
161    /// Holding a [`WeakPathHandle`] does not keep a connection alive, but ensures that the
162    /// path's stats are not dropped until the underlying connection is dropped, even if the
163    /// path is abandoned.
164    pub fn weak_handle(&self) -> WeakPathHandle {
165        self.conn
166            .state
167            .lock("Path::weak_handle")
168            .increment_path_refs(self.id);
169        WeakPathHandle {
170            id: self.id,
171            conn: self.conn.weak_handle(),
172        }
173    }
174
175    /// The [`PathId`] of this path.
176    pub fn id(&self) -> PathId {
177        self.id
178    }
179
180    /// The current local [`PathStatus`] of this path.
181    pub fn status(&self) -> Result<PathStatus, ClosedPath> {
182        self.conn
183            .state
184            .lock("path status")
185            .inner
186            .path_status(self.id)
187    }
188
189    /// Sets the [`PathStatus`] of this path.
190    pub fn set_status(&self, status: PathStatus) -> Result<(), SetPathStatusError> {
191        self.conn
192            .state
193            .lock("set path status")
194            .inner
195            .set_path_status(self.id, status)?;
196        Ok(())
197    }
198
199    /// Returns the [`PathStats`] for this path.
200    pub fn stats(&self) -> PathStats {
201        // The `expect` is safe:
202        // - `Path` can only be created for non-closed paths.
203        // - `Path` and its clones or `WeakPathHandle`s all increment the connection state's `path_ref`
204        //   reference counter
205        // - As long as a path is not abandoned, its stats are available from `proto::Connection`
206        // - If a path is abandoned, the `crate::Connection` stores the final stats as long as
207        //   the path's refcount is not 0
208        // - Therefore, we always get stats here.
209        self.conn
210            .state
211            .lock("Path::stats")
212            .path_stats(self.id)
213            .expect("either path stats or discarded path stats are always set as long as Path is not dropped")
214    }
215
216    /// Closes this path.
217    ///
218    /// The path is immediately considered closed by the local endpoint. Once the state is removed,
219    /// after a short period of time for any in-flight packets, a [`PathEvent::Abandoned`] is
220    /// returned.
221    pub fn close(&self) -> Result<(), ClosePathError> {
222        let mut state = self.conn.state.lock("close_path");
223        state.inner.close_path(
224            crate::Instant::now(),
225            self.id,
226            TransportErrorCode::APPLICATION_ABANDON_PATH.into(),
227        )
228    }
229
230    /// Sets the keep_alive_interval for a specific path
231    ///
232    /// See [`TransportConfig::default_path_keep_alive_interval`] for details.
233    ///
234    /// Returns the previous value of the setting.
235    ///
236    /// [`TransportConfig::default_path_keep_alive_interval`]: crate::TransportConfig::default_path_keep_alive_interval
237    pub fn set_max_idle_timeout(
238        &self,
239        timeout: Option<Duration>,
240    ) -> Result<Option<Duration>, ClosedPath> {
241        let mut state = self.conn.state.lock("path_set_max_idle_timeout");
242        state.inner.set_path_max_idle_timeout(self.id, timeout)
243    }
244
245    /// Sets the keep_alive_interval for a specific path
246    ///
247    /// See [`TransportConfig::default_path_keep_alive_interval`] for details.
248    ///
249    /// Returns the previous value of the setting.
250    ///
251    /// [`TransportConfig::default_path_keep_alive_interval`]: crate::TransportConfig::default_path_keep_alive_interval
252    pub fn set_keep_alive_interval(
253        &self,
254        interval: Option<Duration>,
255    ) -> Result<Option<Duration>, ClosedPath> {
256        let mut state = self.conn.state.lock("path_set_keep_alive_interval");
257        state.inner.set_path_keep_alive_interval(self.id, interval)
258    }
259
260    /// Track changes on our external address as reported by the peer.
261    ///
262    /// If the address-discovery extension is not negotiated, the stream will never return.
263    pub fn observed_external_addr(&self) -> Result<AddressDiscovery, ClosedPath> {
264        let state = self.conn.state.lock("per_path_observed_address");
265        let path_events = state.path_events.subscribe();
266        let initial_value = state.inner.path_observed_address(self.id)?;
267        Ok(AddressDiscovery::new(
268            self.id,
269            path_events,
270            initial_value,
271            state.runtime.clone(),
272        ))
273    }
274
275    /// The peer's UDP address for this path.
276    pub fn remote_address(&self) -> Result<SocketAddr, ClosedPath> {
277        let state = self.conn.state.lock("per_path_remote_address");
278        Ok(state.inner.network_path(self.id)?.remote)
279    }
280
281    /// Ping the remote endpoint over this path.
282    pub fn ping(&self) -> Result<(), ClosedPath> {
283        let mut state = self.conn.state.lock("ping");
284        state.inner.ping_path(self.id)
285    }
286}
287
288impl PartialEq for Path {
289    fn eq(&self, other: &Self) -> bool {
290        self.id == other.id && self.conn.stable_id() == other.conn.stable_id()
291    }
292}
293
294/// Weak handle for a [`Path`] that does not keep the connection alive.
295///
296/// As long as a [`WeakPathHandle`] for a path exists, that path's final stats will not be dropped even if
297/// the path was abandoned.
298///
299/// The [`WeakPathHandle`] can be upgraded to a [`Path`] as long as its [`Connection`] has not been dropped.
300///
301/// [`Connection`]: crate::Connection
302#[derive(Debug)]
303pub struct WeakPathHandle {
304    id: PathId,
305    conn: WeakConnectionHandle,
306}
307
308impl Clone for WeakPathHandle {
309    fn clone(&self) -> Self {
310        if let Some(conn) = self.conn.upgrade_to_ref() {
311            conn.state
312                .lock("WeakPathHandle::clone")
313                .increment_path_refs(self.id);
314        }
315        Self {
316            id: self.id,
317            conn: self.conn.clone(),
318        }
319    }
320}
321
322impl PartialEq for WeakPathHandle {
323    fn eq(&self, other: &Self) -> bool {
324        self.id == other.id && self.conn.is_same_connection(&other.conn)
325    }
326}
327
328impl Eq for WeakPathHandle {}
329
330impl Drop for WeakPathHandle {
331    fn drop(&mut self) {
332        if let Some(conn) = self.conn.upgrade_to_ref() {
333            conn.state
334                .lock("WeakPathHandle::drop")
335                .decrement_path_refs(self.id);
336        }
337    }
338}
339
340impl WeakPathHandle {
341    /// Returns the [`PathId`] of this path.
342    pub fn id(&self) -> PathId {
343        self.id
344    }
345
346    /// Upgrades to a [`Path`].
347    ///
348    /// Returns `None` if the connection was dropped.
349    pub fn upgrade(&self) -> Option<Path> {
350        let conn = self.conn.upgrade_to_ref()?;
351        Some(Path::new_unchecked(conn, self.id))
352    }
353}
354
355/// Stream produced by [`Path::observed_external_addr`]
356///
357/// This will always return the external address most recently reported by the remote over this
358/// path. If the extension is not negotiated, this stream will never return.
359// TODO(@divma): provide a way to check if the extension is negotiated.
360pub struct AddressDiscovery {
361    watcher: WatchStream<SocketAddr>,
362}
363
364impl AddressDiscovery {
365    pub(super) fn new(
366        path_id: PathId,
367        mut path_events: tokio::sync::broadcast::Receiver<PathEvent>,
368        initial_value: Option<SocketAddr>,
369        runtime: Arc<dyn Runtime>,
370    ) -> Self {
371        let (tx, rx) = watch::channel(initial_value.unwrap_or_else(||
372                // if the dummy value is used, it will be ignored
373                SocketAddr::new([0, 0, 0, 0].into(), 0)));
374        let filter = async move {
375            loop {
376                match path_events.recv().await {
377                    Ok(PathEvent::ObservedAddr { id, addr: observed }) if id == path_id => {
378                        tx.send_if_modified(|addr| {
379                            let old = std::mem::replace(addr, observed);
380                            old != *addr
381                        });
382                    }
383                    Ok(PathEvent::Discarded { id, .. }) if id == path_id => {
384                        // If the path is closed, terminate the stream
385                        break;
386                    }
387                    Ok(_) => {
388                        // ignore any other event
389                    }
390                    Err(_) => {
391                        // A lagged error should never happen since this (detached) task is
392                        // constantly reading from the channel. Therefore, if an error does happen,
393                        // the stream can terminate
394                        break;
395                    }
396                }
397            }
398        };
399
400        let watcher = if initial_value.is_some() {
401            WatchStream::new(rx)
402        } else {
403            WatchStream::from_changes(rx)
404        };
405
406        runtime.spawn(Box::pin(filter));
407        // TODO(@divma): check if there's a way to ensure the future ends. AbortHandle is not an
408        // option
409        Self { watcher }
410    }
411}
412
413impl Stream for AddressDiscovery {
414    type Item = SocketAddr;
415
416    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
417        Pin::new(&mut self.watcher).poll_next(cx)
418    }
419}