Skip to content

Commit e521208

Browse files
committed
Track path validity for all paths
1 parent 67ac9b0 commit e521208

File tree

5 files changed

+136
-75
lines changed

5 files changed

+136
-75
lines changed

iroh/src/magicsock/node_map.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use crate::{
2626
mod best_addr;
2727
mod node_state;
2828
mod path_state;
29+
mod path_validity;
2930
mod udp_paths;
3031

3132
pub use node_state::{ConnectionType, ControlMsg, DirectAddrInfo, RemoteInfo};

iroh/src/magicsock/node_map/node_state.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ use super::{
2525
use crate::endpoint::PathSelection;
2626
use crate::{
2727
disco::{self, SendAddr},
28-
magicsock::{ActorMessage, MagicsockMetrics, NodeIdMappedAddr, HEARTBEAT_INTERVAL},
28+
magicsock::{
29+
node_map::path_validity::PathValidity, ActorMessage, MagicsockMetrics, NodeIdMappedAddr,
30+
HEARTBEAT_INTERVAL,
31+
},
2932
watchable::{Watchable, Watcher},
3033
};
3134

@@ -242,7 +245,7 @@ impl NodeState {
242245
.iter()
243246
.map(|(addr, path_state)| DirectAddrInfo {
244247
addr: SocketAddr::from(*addr),
245-
latency: path_state.recent_pong.as_ref().map(|pong| pong.latency),
248+
latency: path_state.validity.get_pong().map(|pong| pong.latency),
246249
last_control: path_state.last_control_msg(now),
247250
last_payload: path_state
248251
.last_payload_msg
@@ -445,7 +448,7 @@ impl NodeState {
445448
// which we should have received the pong, clear best addr and
446449
// pong. Both are used to select this path again, but we know
447450
// it's not a usable path now.
448-
path_state.recent_pong = None;
451+
path_state.validity = PathValidity::empty();
449452
self.udp_paths.best_addr.clear_if_equals(
450453
addr,
451454
ClearReason::PongTimeout,
@@ -894,7 +897,7 @@ impl NodeState {
894897
event!(
895898
target: "iroh::_events::pong::recv",
896899
Level::DEBUG,
897-
remote_node = self.node_id.fmt_short(),
900+
remote_node = %self.node_id.fmt_short(),
898901
?src,
899902
txn = ?m.tx_id,
900903
);
@@ -1034,9 +1037,9 @@ impl NodeState {
10341037
if !call_me_maybe_ipps.contains(ipp) {
10351038
// TODO: This seems like a weird way to signal that the endpoint no longer
10361039
// thinks it has this IpPort as an available path.
1037-
if st.recent_pong.is_some() {
1040+
if !st.validity.is_empty() {
10381041
debug!(path=?ipp ,"clearing recent pong");
1039-
st.recent_pong = None;
1042+
st.validity = PathValidity::empty();
10401043
}
10411044
}
10421045
}
@@ -1067,7 +1070,9 @@ impl NodeState {
10671070
};
10681071
state.receive_payload(now);
10691072
self.last_used = Some(now);
1070-
if let Some((latency, confirmed_at)) = state.valid_best_addr_candidate(now) {
1073+
if state.validity.is_valid(now) {
1074+
let confirmed_at = state.validity.confirmed_at().unwrap();
1075+
let latency = state.validity.get_pong().unwrap().latency;
10711076
self.udp_paths
10721077
.best_addr
10731078
.insert_if_soon_outdated_or_reconfirm(

iroh/src/magicsock/node_map/path_state.rs

Lines changed: 29 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ use super::{
1616
};
1717
use crate::{
1818
disco::SendAddr,
19-
magicsock::{node_map::best_addr::TRUST_UDP_ADDR_DURATION, HEARTBEAT_INTERVAL},
19+
magicsock::{
20+
node_map::path_validity::{self, PathValidity},
21+
HEARTBEAT_INTERVAL,
22+
},
2023
};
2124

2225
/// The minimum time between pings to an endpoint.
@@ -30,7 +33,7 @@ const DISCO_PING_INTERVAL: Duration = Duration::from_secs(5);
3033
/// This state is used for both the relay path and any direct UDP paths.
3134
///
3235
/// [`NodeState`]: super::node_state::NodeState
33-
#[derive(Debug, Clone, PartialEq, Eq)]
36+
#[derive(Debug, Clone)]
3437
pub(super) struct PathState {
3538
/// The node for which this path exists.
3639
node_id: NodeId,
@@ -47,18 +50,19 @@ pub(super) struct PathState {
4750
/// The time this endpoint was last advertised via a call-me-maybe DISCO message.
4851
pub(super) call_me_maybe_time: Option<Instant>,
4952

50-
/// The most recent [`PongReply`].
53+
// /// The most recent [`PongReply`].
54+
// ///
55+
// /// Previous replies are cleared when they are no longer relevant to determine whether
56+
// /// this path can still be used to reach the remote node.
57+
// pub(super) recent_pong: Option<PongReply>,
58+
/// Tracks whether this path is valid.
5159
///
52-
/// Previous replies are cleared when they are no longer relevant to determine whether
53-
/// this path can still be used to reach the remote node.
54-
pub(super) recent_pong: Option<PongReply>,
60+
/// See [`PathValidity`] docs.
61+
pub(super) validity: PathValidity,
5562
/// When the last payload data was **received** via this path.
5663
///
5764
/// This excludes DISCO messages.
5865
pub(super) last_payload_msg: Option<Instant>,
59-
/// Whether the last payload msg was within [`TRUST_UDP_ADDR_DURATION`] of either
60-
/// the last trusted payload message or a recent pong.
61-
pub(super) last_payload_trusted: bool,
6266
/// Sources is a map of [`Source`]s to [`Instant`]s, keeping track of all the ways we have
6367
/// learned about this path
6468
///
@@ -77,9 +81,8 @@ impl PathState {
7781
last_ping: None,
7882
last_got_ping: None,
7983
call_me_maybe_time: None,
80-
recent_pong: None,
84+
validity: PathValidity::empty(),
8185
last_payload_msg: None,
82-
last_payload_trusted: false,
8386
sources,
8487
}
8588
}
@@ -105,9 +108,8 @@ impl PathState {
105108
last_ping: None,
106109
last_got_ping: None,
107110
call_me_maybe_time: None,
108-
recent_pong: None,
111+
validity: PathValidity::empty(),
109112
last_payload_msg: Some(now),
110-
last_payload_trusted: false,
111113
sources,
112114
}
113115
}
@@ -126,7 +128,7 @@ impl PathState {
126128

127129
pub(super) fn add_pong_reply(&mut self, r: PongReply) {
128130
if let SendAddr::Udp(ref path) = self.path {
129-
if self.recent_pong.is_none() {
131+
if self.validity.is_empty() {
130132
event!(
131133
target: "iroh::_events::holepunched",
132134
Level::DEBUG,
@@ -136,27 +138,16 @@ impl PathState {
136138
);
137139
}
138140
}
139-
self.recent_pong = Some(r);
140141

141-
if let Some(last_payload_msg) = self.last_payload_msg {
142-
self.last_payload_trusted = self.within_trusted_pong_duration(last_payload_msg);
143-
}
144-
}
145-
146-
fn within_trusted_pong_duration(&self, instant: Instant) -> bool {
147-
if let Some(pong) = &self.recent_pong {
148-
return pong.pong_at <= instant && instant < pong.pong_at + TRUST_UDP_ADDR_DURATION;
149-
}
150-
151-
false
142+
self.validity = PathValidity::new(r);
152143
}
153144

154145
pub(super) fn receive_payload(&mut self, now: Instant) {
155146
self.last_payload_msg = Some(now);
156-
157-
if self.within_trusted_pong_duration(now) {
158-
self.last_payload_trusted = true;
159-
}
147+
// TODO(matheus23): It's not necessarily UDP. Kinda weird to have this thing
148+
// Also, it all results in the same 6.5s timeout anyways, maybe we just remove it?
149+
self.validity
150+
.receive_payload(now, path_validity::Source::Udp);
160151
}
161152

162153
#[cfg(test)]
@@ -167,9 +158,8 @@ impl PathState {
167158
last_ping: None,
168159
last_got_ping: None,
169160
call_me_maybe_time: None,
170-
recent_pong: Some(r),
161+
validity: PathValidity::new(r),
171162
last_payload_msg: None,
172-
last_payload_trusted: false,
173163
sources: HashMap::new(),
174164
}
175165
}
@@ -205,8 +195,8 @@ impl PathState {
205195
/// - When the last payload transmission occurred.
206196
/// - when the last ping from them was received.
207197
pub(super) fn last_alive(&self) -> Option<Instant> {
208-
self.recent_pong
209-
.as_ref()
198+
self.validity
199+
.get_pong()
210200
.map(|pong| &pong.pong_at)
211201
.into_iter()
212202
.chain(self.last_payload_msg.as_ref())
@@ -216,21 +206,6 @@ impl PathState {
216206
.copied()
217207
}
218208

219-
fn last_confirmed_at(&self) -> Option<Instant> {
220-
let last_trusted_payload_msg = if self.last_payload_trusted {
221-
self.last_payload_msg
222-
} else {
223-
None
224-
};
225-
226-
self.recent_pong
227-
.as_ref()
228-
.map(|pong| pong.pong_at)
229-
.into_iter()
230-
.chain(last_trusted_payload_msg)
231-
.max()
232-
}
233-
234209
/// The last control or DISCO message **about** this path.
235210
///
236211
/// This is the most recent instant among:
@@ -242,8 +217,8 @@ impl PathState {
242217
pub(super) fn last_control_msg(&self, now: Instant) -> Option<(Duration, ControlMsg)> {
243218
// get every control message and assign it its kind
244219
let last_pong = self
245-
.recent_pong
246-
.as_ref()
220+
.validity
221+
.get_pong()
247222
.map(|pong| (pong.pong_at, ControlMsg::Pong));
248223
let last_call_me_maybe = self
249224
.call_me_maybe_time
@@ -263,19 +238,7 @@ impl PathState {
263238

264239
/// Returns the latency from the most recent pong, if available.
265240
pub(super) fn latency(&self) -> Option<Duration> {
266-
self.recent_pong.as_ref().map(|p| p.latency)
267-
}
268-
269-
/// Returns the latency and confirmed_at time if this path would make for a valid best addr `now`
270-
pub(crate) fn valid_best_addr_candidate(&self, now: Instant) -> Option<(Duration, Instant)> {
271-
let latency = self.recent_pong.as_ref()?.latency;
272-
let confirmed_at = self.last_confirmed_at()?;
273-
274-
if confirmed_at <= now && now < confirmed_at + TRUST_UDP_ADDR_DURATION {
275-
return Some((latency, confirmed_at));
276-
}
277-
278-
None
241+
self.validity.get_pong().map(|p| p.latency)
279242
}
280243

281244
pub(super) fn needs_ping(&self, now: &Instant) -> bool {
@@ -336,15 +299,15 @@ impl PathState {
336299
self.last_ping = None;
337300
self.last_got_ping = None;
338301
self.call_me_maybe_time = None;
339-
self.recent_pong = None;
302+
self.validity = PathValidity::empty();
340303
}
341304

342305
fn summary(&self, mut w: impl std::fmt::Write) -> std::fmt::Result {
343306
write!(w, "{{ ")?;
344307
if self.is_active() {
345308
write!(w, "active ")?;
346309
}
347-
if let Some(ref pong) = self.recent_pong {
310+
if let Some(pong) = self.validity.get_pong() {
348311
write!(w, "pong-received({:?} ago) ", pong.pong_at.elapsed())?;
349312
}
350313
if let Some(when) = self.last_incoming_ping() {
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use n0_future::time::{Duration, Instant};
2+
3+
use crate::magicsock::node_map::node_state::PongReply;
4+
5+
/// How long we trust a UDP address as the exclusive path (without using relay) without having heard a Pong reply.
6+
const TRUST_UDP_ADDR_DURATION: Duration = Duration::from_millis(6500);
7+
8+
/// Tracks a path's validity.
9+
///
10+
/// A path is valid:
11+
/// - For [`Source::trust_duration`] after a successful [`PongReply`].
12+
/// - For [`Source::trust_duration`] longer starting at the most recent
13+
/// received application payload *while the path was valid*.
14+
#[derive(Debug, Clone, Default)]
15+
pub(super) struct PathValidity(Option<Inner>);
16+
17+
#[derive(Debug, Clone)]
18+
struct Inner {
19+
recent_pong: PongReply,
20+
confirmed_at: Instant,
21+
trust_until: Instant,
22+
}
23+
24+
#[derive(Debug)]
25+
pub(super) enum Source {
26+
ReceivedPong,
27+
// BestCandidate,
28+
Udp,
29+
}
30+
31+
impl Source {
32+
fn trust_duration(&self) -> Duration {
33+
match self {
34+
Source::ReceivedPong => TRUST_UDP_ADDR_DURATION,
35+
// // TODO: Fix time
36+
// Source::BestCandidate => Duration::from_secs(60 * 60),
37+
Source::Udp => TRUST_UDP_ADDR_DURATION,
38+
}
39+
}
40+
}
41+
42+
impl PathValidity {
43+
pub(super) fn new(recent_pong: PongReply) -> Self {
44+
Self(Some(Inner {
45+
confirmed_at: recent_pong.pong_at,
46+
trust_until: recent_pong.pong_at + Source::ReceivedPong.trust_duration(),
47+
recent_pong,
48+
}))
49+
}
50+
51+
pub(super) fn empty() -> Self {
52+
Self(None)
53+
}
54+
55+
pub(super) fn is_empty(&self) -> bool {
56+
self.0.is_none()
57+
}
58+
59+
pub(super) fn is_valid(&self, now: Instant) -> bool {
60+
let Some(state) = self.0.as_ref() else {
61+
return false;
62+
};
63+
64+
state.is_valid(now)
65+
}
66+
67+
/// Reconfirms path validity, if a payload was received while the
68+
/// path was valid.
69+
pub(super) fn receive_payload(&mut self, now: Instant, source: Source) {
70+
let Some(state) = self.0.as_mut() else {
71+
return;
72+
};
73+
74+
if state.is_valid(now) {
75+
state.trust_until = now + source.trust_duration();
76+
}
77+
}
78+
79+
pub(super) fn get_pong(&self) -> Option<&PongReply> {
80+
self.0.as_ref().map(|inner| &inner.recent_pong)
81+
}
82+
83+
pub(super) fn confirmed_at(&self) -> Option<Instant> {
84+
self.0.as_ref().map(|inner| inner.confirmed_at)
85+
}
86+
}
87+
88+
impl Inner {
89+
fn is_valid(&self, now: Instant) -> bool {
90+
self.confirmed_at <= now && now < self.trust_until
91+
}
92+
}

iroh/src/magicsock/node_map/udp_paths.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,10 @@ impl NodeUdpPaths {
151151
let best_latency = best_pong
152152
.map(|p: &PongReply| p.latency)
153153
.unwrap_or(MAX_LATENCY);
154-
match state.recent_pong {
154+
match state.validity.get_pong() {
155155
// This pong is better if it has a lower latency, or if it has the same
156156
// latency but on an IPv6 path.
157-
Some(ref pong)
157+
Some(pong)
158158
if pong.latency < best_latency
159159
|| (pong.latency == best_latency && ipp.ip().is_ipv6()) =>
160160
{

0 commit comments

Comments
 (0)