@@ -6,7 +6,51 @@ use n0_future::time::{Duration, Instant};
6
6
use tracing:: { debug, info} ;
7
7
8
8
/// How long we trust a UDP address as the exclusive path (without using relay) without having heard a Pong reply.
9
- const TRUST_UDP_ADDR_DURATION : Duration = Duration :: from_millis ( 6500 ) ;
9
+ pub ( super ) const TRUST_UDP_ADDR_DURATION : Duration = Duration :: from_millis ( 6500 ) ;
10
+
11
+ /// The grace period at which we consider switching away from our best addr
12
+ /// to another address that we've received data on.
13
+ ///
14
+ /// The trusted address lifecycle goes as follows:
15
+ /// - A UDP DICSO pong is received, this validates that the path is for sure valid.
16
+ /// - The disco path that seems to have the lowest latency is the path we use to send on.
17
+ /// - We trust this path as a path to send on for at least TRUST_UDP_ADDR_DURATION.
18
+ /// - This time is extended every time we receive a UDP DISCO pong on the address.
19
+ /// - This time is *also* extended every time we receive *application* payloads on this
20
+ /// address (i.e. QUIC datagrams).
21
+ /// - If our best address becomes outdated (TRUST_UDP_ADDR_DURATION expires) without
22
+ /// another pong or payload data, then we'll start sending over the relay, too!
23
+ /// (we switch to ConnectionType::Mixed)
24
+ ///
25
+ /// However, we might not get any UDP DISCO pongs because they're UDP packets and get
26
+ /// lost under e.g. high load.
27
+ ///
28
+ /// This is usually fine, because we also receive on the best addr, and that extends its
29
+ /// "trust period" just as well.
30
+ ///
31
+ /// However, if *additionally* we send on a different address than the one we receive on,
32
+ /// then this extension doesn't happen.
33
+ ///
34
+ /// To fix this, we also apply the same path validation logic to non-best addresses.
35
+ /// I.e. we keep track of when they last received a pong, at which point we consider them
36
+ /// validated, and then extend this validation period when we receive application data
37
+ /// while they're valid (or when we receive another pong).
38
+ ///
39
+ /// Now, when our best address becomes outdated, we need to switch to another valid path.
40
+ ///
41
+ /// We could switch to another path once the best address becomes outdated, but then we'd
42
+ /// already start sending on the relay for a couple of iterations!
43
+ ///
44
+ /// So instead, we switch to another path when it looks like the best address becomes
45
+ /// outdated.
46
+ /// Not just any path, but the path that we're currently receiving from for this node.
47
+ ///
48
+ /// Since we might not be receiving constantly from the remote side (e.g. if it's a
49
+ /// one-sided transfer), we need to take care to do consider switching early enough.
50
+ ///
51
+ /// So this duration is chosen as at least 1 keep alive interval (1s default in iroh atm)
52
+ /// + at maximum 400ms of latency spike.
53
+ const TRUST_UDP_ADDR_SOON_OUTDATED : Duration = Duration :: from_millis ( 1400 ) ;
10
54
11
55
#[ derive( Debug , Default ) ]
12
56
pub ( super ) struct BestAddr ( Option < BestAddrInner > ) ;
@@ -38,12 +82,12 @@ pub(super) enum Source {
38
82
}
39
83
40
84
impl Source {
41
- fn trust_until ( & self , from : Instant ) -> Instant {
85
+ fn trust_duration ( & self ) -> Duration {
42
86
match self {
43
- Source :: ReceivedPong => from + TRUST_UDP_ADDR_DURATION ,
87
+ Source :: ReceivedPong => TRUST_UDP_ADDR_DURATION ,
44
88
// TODO: Fix time
45
- Source :: BestCandidate => from + Duration :: from_secs ( 60 * 60 ) ,
46
- Source :: Udp => from + TRUST_UDP_ADDR_DURATION ,
89
+ Source :: BestCandidate => Duration :: from_secs ( 60 * 60 ) ,
90
+ Source :: Udp => TRUST_UDP_ADDR_DURATION ,
47
91
}
48
92
}
49
93
}
@@ -115,30 +159,48 @@ impl BestAddr {
115
159
latency : Duration ,
116
160
source : Source ,
117
161
confirmed_at : Instant ,
162
+ now : Instant ,
118
163
) {
119
164
match self . 0 . as_mut ( ) {
120
165
None => {
121
166
self . insert ( addr, latency, source, confirmed_at) ;
122
167
}
123
168
Some ( state) => {
124
169
let candidate = AddrLatency { addr, latency } ;
125
- if !state. is_trusted ( confirmed_at ) || candidate. is_better_than ( & state. addr ) {
170
+ if !state. is_trusted ( now ) || candidate. is_better_than ( & state. addr ) {
126
171
self . insert ( addr, latency, source, confirmed_at) ;
127
172
} else if state. addr . addr == addr {
128
173
state. confirmed_at = confirmed_at;
129
- state. trust_until = Some ( source. trust_until ( confirmed_at ) ) ;
174
+ state. trust_until = Some ( confirmed_at + source. trust_duration ( ) ) ;
130
175
}
131
176
}
132
177
}
133
178
}
134
179
135
- /// Reset the expiry, if the passed in addr matches the currently used one.
136
- #[ cfg( not( wasm_browser) ) ]
137
- pub fn reconfirm_if_used ( & mut self , addr : SocketAddr , source : Source , confirmed_at : Instant ) {
138
- if let Some ( state) = self . 0 . as_mut ( ) {
139
- if state. addr . addr == addr {
140
- state. confirmed_at = confirmed_at;
141
- state. trust_until = Some ( source. trust_until ( confirmed_at) ) ;
180
+ pub fn insert_if_soon_outdated_or_reconfirm (
181
+ & mut self ,
182
+ addr : SocketAddr ,
183
+ latency : Duration ,
184
+ source : Source ,
185
+ confirmed_at : Instant ,
186
+ now : Instant ,
187
+ ) {
188
+ match self . 0 . as_mut ( ) {
189
+ None => {
190
+ self . insert ( addr, latency, source, confirmed_at) ;
191
+ }
192
+ Some ( state) => {
193
+ // If the current best addr will soon be outdated
194
+ // and the given candidate will be trusted for longer
195
+ if !state. is_trusted ( now + TRUST_UDP_ADDR_SOON_OUTDATED )
196
+ && state. confirmed_at < confirmed_at
197
+ {
198
+ println ! ( "best addr will soon not be trusted, let's switch" ) ;
199
+ self . insert ( addr, latency, source, confirmed_at) ;
200
+ } else if state. addr . addr == addr {
201
+ state. confirmed_at = confirmed_at;
202
+ state. trust_until = Some ( confirmed_at + source. trust_duration ( ) ) ;
203
+ }
142
204
}
143
205
}
144
206
}
@@ -150,7 +212,7 @@ impl BestAddr {
150
212
source : Source ,
151
213
confirmed_at : Instant ,
152
214
) {
153
- let trust_until = source. trust_until ( confirmed_at ) ;
215
+ let trust_until = confirmed_at + source. trust_duration ( ) ;
154
216
155
217
if self
156
218
. 0
0 commit comments