@@ -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 {
@@ -125,8 +134,13 @@ enum MappingState {
125
134
Pending ,
126
135
/// Port mapping is active with the inner timeout.
127
136
Active ( Delay ) ,
128
- /// Port mapping failed, we will try again.
129
- Failed ,
137
+ /// Port mapping failed with retry information.
138
+ Failed {
139
+ /// Number of times we've tried to map this port.
140
+ retry_count : u32 ,
141
+ /// When we should try again (None means no more retries).
142
+ next_retry : Option < Delay > ,
143
+ } ,
130
144
}
131
145
132
146
/// Current state of the UPnP [`Gateway`].
@@ -174,7 +188,7 @@ impl MappingList {
174
188
fn renew ( & mut self , gateway : & mut Gateway , cx : & mut Context < ' _ > ) {
175
189
for ( mapping, state) in self . iter_mut ( ) {
176
190
match state {
177
- MappingState :: Inactive | MappingState :: Failed => {
191
+ MappingState :: Inactive => {
178
192
let duration = MAPPING_DURATION ;
179
193
if let Err ( err) = gateway. sender . try_send ( GatewayRequest :: AddMapping {
180
194
mapping : mapping. clone ( ) ,
@@ -185,8 +199,32 @@ impl MappingList {
185
199
"could not request port mapping for multiaddress on the gateway: {}" ,
186
200
err
187
201
) ;
202
+ } else {
203
+ * state = MappingState :: Pending ;
204
+ }
205
+ }
206
+ MappingState :: Failed {
207
+ retry_count,
208
+ next_retry,
209
+ } => {
210
+ if let Some ( delay) = next_retry {
211
+ if Pin :: new ( delay) . poll ( cx) . is_ready ( ) {
212
+ let duration = MAPPING_DURATION ;
213
+ if let Err ( err) = gateway. sender . try_send ( GatewayRequest :: AddMapping {
214
+ mapping : mapping. clone ( ) ,
215
+ duration,
216
+ } ) {
217
+ tracing:: debug!(
218
+ multiaddress=%mapping. multiaddr,
219
+ retry_count=%retry_count,
220
+ "could not retry port mapping for multiaddress on the gateway: {}" ,
221
+ err
222
+ ) ;
223
+ } else {
224
+ * state = MappingState :: Pending ;
225
+ }
226
+ }
188
227
}
189
- * state = MappingState :: Pending ;
190
228
}
191
229
MappingState :: Active ( timeout) => {
192
230
if Pin :: new ( timeout) . poll ( cx) . is_ready ( ) {
@@ -454,36 +492,64 @@ impl NetworkBehaviour for Behaviour {
454
492
}
455
493
}
456
494
GatewayEvent :: MapFailure ( mapping, err) => {
457
- match self
458
- . mappings
459
- . insert ( mapping. clone ( ) , MappingState :: Failed )
460
- . expect ( "mapping should exist" )
461
- {
462
- MappingState :: Active ( _) => {
463
- tracing:: debug!(
464
- address=%mapping. internal_addr,
465
- protocol=%mapping. protocol,
466
- "failed to remap UPnP mapped for protocol: {err}"
467
- ) ;
468
- let external_multiaddr =
469
- mapping. external_addr ( gateway. external_addr ) ;
470
- self . pending_events . push_back ( Event :: ExpiredExternalAddr (
471
- external_multiaddr. clone ( ) ,
472
- ) ) ;
473
- return Poll :: Ready ( ToSwarm :: ExternalAddrExpired (
474
- external_multiaddr,
475
- ) ) ;
476
- }
477
- MappingState :: Pending => {
478
- tracing:: debug!(
479
- address=%mapping. internal_addr,
480
- protocol=%mapping. protocol,
481
- "failed to map UPnP mapped for protocol: {err}"
482
- ) ;
483
- }
484
- _ => {
485
- unreachable ! ( )
495
+ let prev_state =
496
+ self . mappings . get ( & mapping) . expect ( "mapping should exist" ) ;
497
+
498
+ let ( retry_count, was_active) = match prev_state {
499
+ MappingState :: Active ( _) => ( 0 , true ) ,
500
+ MappingState :: Pending => ( 0 , false ) ,
501
+ MappingState :: Failed { retry_count, .. } => {
502
+ ( * retry_count, false )
486
503
}
504
+ _ => unreachable ! ( ) ,
505
+ } ;
506
+
507
+ let new_retry_count = retry_count + 1 ;
508
+ let next_retry = if new_retry_count < MAX_RETRY_ATTEMPTS {
509
+ let delay_secs = std:: cmp:: min (
510
+ BASE_RETRY_DELAY_SECS
511
+ . saturating_mul ( 2_u64 . pow ( retry_count) ) ,
512
+ MAX_RETRY_DELAY_SECS ,
513
+ ) ;
514
+ Some ( Delay :: new ( Duration :: from_secs ( delay_secs) ) )
515
+ } else {
516
+ tracing:: warn!(
517
+ address=%mapping. internal_addr,
518
+ protocol=%mapping. protocol,
519
+ "giving up on UPnP mapping after {new_retry_count} attempts"
520
+ ) ;
521
+ None
522
+ } ;
523
+
524
+ self . mappings . insert (
525
+ mapping. clone ( ) ,
526
+ MappingState :: Failed {
527
+ retry_count : new_retry_count,
528
+ next_retry,
529
+ } ,
530
+ ) ;
531
+
532
+ if was_active {
533
+ tracing:: debug!(
534
+ address=%mapping. internal_addr,
535
+ protocol=%mapping. protocol,
536
+ "failed to remap UPnP mapped for protocol: {err}"
537
+ ) ;
538
+ let external_multiaddr =
539
+ mapping. external_addr ( gateway. external_addr ) ;
540
+ self . pending_events . push_back ( Event :: ExpiredExternalAddr (
541
+ external_multiaddr. clone ( ) ,
542
+ ) ) ;
543
+ return Poll :: Ready ( ToSwarm :: ExternalAddrExpired (
544
+ external_multiaddr,
545
+ ) ) ;
546
+ } else {
547
+ tracing:: debug!(
548
+ address=%mapping. internal_addr,
549
+ protocol=%mapping. protocol,
550
+ retry_count=%new_retry_count,
551
+ "failed to map UPnP mapped for protocol: {err}"
552
+ ) ;
487
553
}
488
554
}
489
555
GatewayEvent :: Removed ( mapping) => {
0 commit comments