@@ -53,6 +53,15 @@ const MAPPING_DURATION: u32 = 3600;
53
53
/// Renew the Mapping every half of `MAPPING_DURATION` to avoid the port being unmapped.
54
54
const MAPPING_TIMEOUT : u64 = MAPPING_DURATION as u64 / 2 ;
55
55
56
+ /// Maximum number of retry attempts for failed mappings.
57
+ const MAX_RETRY_ATTEMPTS : u32 = 5 ;
58
+
59
+ /// Base delay in seconds for exponential backoff (will be multiplied by 2^retry_count).
60
+ const BASE_RETRY_DELAY_SECS : u64 = 30 ;
61
+
62
+ /// Maximum delay in seconds between retry attempts.
63
+ const MAX_RETRY_DELAY_SECS : u64 = 1800 ;
64
+
56
65
/// A [`Gateway`] Request.
57
66
#[ derive( Debug ) ]
58
67
pub ( crate ) enum GatewayRequest {
@@ -122,11 +131,19 @@ enum MappingState {
122
131
/// Port mapping is inactive, will be requested or re-requested on the next iteration.
123
132
Inactive ,
124
133
/// Port mapping/removal has been requested on the gateway.
125
- Pending ,
134
+ Pending {
135
+ /// Number of times we've tried to map this port.
136
+ retry_count : u32 ,
137
+ } ,
126
138
/// Port mapping is active with the inner timeout.
127
139
Active ( Delay ) ,
128
- /// Port mapping failed, we will try again.
129
- Failed ,
140
+ /// Port mapping failed with retry information.
141
+ Failed {
142
+ /// Number of times we've tried to map this port.
143
+ retry_count : u32 ,
144
+ /// When we should try again (None means no more retries).
145
+ next_retry : Option < Delay > ,
146
+ } ,
130
147
}
131
148
132
149
/// Current state of the UPnP [`Gateway`].
@@ -174,7 +191,7 @@ impl MappingList {
174
191
fn renew ( & mut self , gateway : & mut Gateway , cx : & mut Context < ' _ > ) {
175
192
for ( mapping, state) in self . iter_mut ( ) {
176
193
match state {
177
- MappingState :: Inactive | MappingState :: Failed => {
194
+ MappingState :: Inactive => {
178
195
let duration = MAPPING_DURATION ;
179
196
if let Err ( err) = gateway. sender . try_send ( GatewayRequest :: AddMapping {
180
197
mapping : mapping. clone ( ) ,
@@ -185,8 +202,34 @@ impl MappingList {
185
202
"could not request port mapping for multiaddress on the gateway: {}" ,
186
203
err
187
204
) ;
205
+ } else {
206
+ * state = MappingState :: Pending { retry_count : 0 } ;
207
+ }
208
+ }
209
+ MappingState :: Failed {
210
+ retry_count,
211
+ next_retry,
212
+ } => {
213
+ if let Some ( delay) = next_retry {
214
+ if Pin :: new ( delay) . poll ( cx) . is_ready ( ) {
215
+ let duration = MAPPING_DURATION ;
216
+ if let Err ( err) = gateway. sender . try_send ( GatewayRequest :: AddMapping {
217
+ mapping : mapping. clone ( ) ,
218
+ duration,
219
+ } ) {
220
+ tracing:: debug!(
221
+ multiaddress=%mapping. multiaddr,
222
+ retry_count=%retry_count,
223
+ "could not retry port mapping for multiaddress on the gateway: {}" ,
224
+ err
225
+ ) ;
226
+ } else {
227
+ * state = MappingState :: Pending {
228
+ retry_count : * retry_count,
229
+ } ;
230
+ }
231
+ }
188
232
}
189
- * state = MappingState :: Pending ;
190
233
}
191
234
MappingState :: Active ( timeout) => {
192
235
if Pin :: new ( timeout) . poll ( cx) . is_ready ( ) {
@@ -203,7 +246,7 @@ impl MappingList {
203
246
}
204
247
}
205
248
}
206
- MappingState :: Pending => { }
249
+ MappingState :: Pending { .. } => { }
207
250
}
208
251
}
209
252
}
@@ -317,7 +360,8 @@ impl NetworkBehaviour for Behaviour {
317
360
) ;
318
361
}
319
362
320
- self . mappings . insert ( mapping, MappingState :: Pending ) ;
363
+ self . mappings
364
+ . insert ( mapping, MappingState :: Pending { retry_count : 0 } ) ;
321
365
}
322
366
GatewayState :: GatewayNotFound => {
323
367
tracing:: debug!(
@@ -351,7 +395,8 @@ impl NetworkBehaviour for Behaviour {
351
395
err
352
396
) ;
353
397
}
354
- self . mappings . insert ( mapping, MappingState :: Pending ) ;
398
+ self . mappings
399
+ . insert ( mapping, MappingState :: Pending { retry_count : 0 } ) ;
355
400
}
356
401
}
357
402
}
@@ -427,7 +472,7 @@ impl NetworkBehaviour for Behaviour {
427
472
. insert ( mapping. clone ( ) , new_state)
428
473
. expect ( "mapping should exist" )
429
474
{
430
- MappingState :: Pending => {
475
+ MappingState :: Pending { .. } => {
431
476
let external_multiaddr =
432
477
mapping. external_addr ( gateway. external_addr ) ;
433
478
self . pending_events . push_back ( Event :: NewExternalAddr (
@@ -453,36 +498,64 @@ impl NetworkBehaviour for Behaviour {
453
498
}
454
499
}
455
500
GatewayEvent :: MapFailure ( mapping, err) => {
456
- match self
457
- . mappings
458
- . insert ( mapping. clone ( ) , MappingState :: Failed )
459
- . expect ( "mapping should exist" )
460
- {
461
- MappingState :: Active ( _) => {
462
- tracing:: debug!(
463
- address=%mapping. internal_addr,
464
- protocol=%mapping. protocol,
465
- "failed to remap UPnP mapped for protocol: {err}"
466
- ) ;
467
- let external_multiaddr =
468
- mapping. external_addr ( gateway. external_addr ) ;
469
- self . pending_events . push_back ( Event :: ExpiredExternalAddr (
470
- external_multiaddr. clone ( ) ,
471
- ) ) ;
472
- return Poll :: Ready ( ToSwarm :: ExternalAddrExpired (
473
- external_multiaddr,
474
- ) ) ;
475
- }
476
- MappingState :: Pending => {
477
- tracing:: debug!(
478
- address=%mapping. internal_addr,
479
- protocol=%mapping. protocol,
480
- "failed to map UPnP mapped for protocol: {err}"
481
- ) ;
482
- }
483
- _ => {
484
- unreachable ! ( )
501
+ let prev_state =
502
+ self . mappings . get ( & mapping) . expect ( "mapping should exist" ) ;
503
+
504
+ let ( retry_count, was_active) = match prev_state {
505
+ MappingState :: Active ( _) => ( 0 , true ) ,
506
+ MappingState :: Pending { retry_count } => ( * retry_count, false ) ,
507
+ MappingState :: Failed { retry_count, .. } => {
508
+ ( * retry_count, false )
485
509
}
510
+ _ => unreachable ! ( ) ,
511
+ } ;
512
+
513
+ let new_retry_count = retry_count + 1 ;
514
+ let next_retry = if new_retry_count < MAX_RETRY_ATTEMPTS {
515
+ let delay_secs = std:: cmp:: min (
516
+ BASE_RETRY_DELAY_SECS
517
+ . saturating_mul ( 2_u64 . pow ( retry_count) ) ,
518
+ MAX_RETRY_DELAY_SECS ,
519
+ ) ;
520
+ Some ( Delay :: new ( Duration :: from_secs ( delay_secs) ) )
521
+ } else {
522
+ tracing:: warn!(
523
+ address=%mapping. internal_addr,
524
+ protocol=%mapping. protocol,
525
+ "giving up on UPnP mapping after {new_retry_count} attempts"
526
+ ) ;
527
+ None
528
+ } ;
529
+
530
+ self . mappings . insert (
531
+ mapping. clone ( ) ,
532
+ MappingState :: Failed {
533
+ retry_count : new_retry_count,
534
+ next_retry,
535
+ } ,
536
+ ) ;
537
+
538
+ if was_active {
539
+ tracing:: debug!(
540
+ address=%mapping. internal_addr,
541
+ protocol=%mapping. protocol,
542
+ "failed to remap UPnP mapped for protocol: {err}"
543
+ ) ;
544
+ let external_multiaddr =
545
+ mapping. external_addr ( gateway. external_addr ) ;
546
+ self . pending_events . push_back ( Event :: ExpiredExternalAddr (
547
+ external_multiaddr. clone ( ) ,
548
+ ) ) ;
549
+ return Poll :: Ready ( ToSwarm :: ExternalAddrExpired (
550
+ external_multiaddr,
551
+ ) ) ;
552
+ } else {
553
+ tracing:: debug!(
554
+ address=%mapping. internal_addr,
555
+ protocol=%mapping. protocol,
556
+ retry_count=%new_retry_count,
557
+ "failed to map UPnP mapped for protocol: {err}"
558
+ ) ;
486
559
}
487
560
}
488
561
GatewayEvent :: Removed ( mapping) => {
0 commit comments