@@ -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 ( ) {
@@ -453,36 +491,64 @@ impl NetworkBehaviour for Behaviour {
453
491
}
454
492
}
455
493
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 ! ( )
494
+ let prev_state =
495
+ self . mappings . get ( & mapping) . expect ( "mapping should exist" ) ;
496
+
497
+ let ( retry_count, was_active) = match prev_state {
498
+ MappingState :: Active ( _) => ( 0 , true ) ,
499
+ MappingState :: Pending => ( 0 , false ) ,
500
+ MappingState :: Failed { retry_count, .. } => {
501
+ ( * retry_count, false )
485
502
}
503
+ _ => unreachable ! ( ) ,
504
+ } ;
505
+
506
+ let new_retry_count = retry_count + 1 ;
507
+ let next_retry = if new_retry_count < MAX_RETRY_ATTEMPTS {
508
+ let delay_secs = std:: cmp:: min (
509
+ BASE_RETRY_DELAY_SECS
510
+ . saturating_mul ( 2_u64 . pow ( retry_count) ) ,
511
+ MAX_RETRY_DELAY_SECS ,
512
+ ) ;
513
+ Some ( Delay :: new ( Duration :: from_secs ( delay_secs) ) )
514
+ } else {
515
+ tracing:: warn!(
516
+ address=%mapping. internal_addr,
517
+ protocol=%mapping. protocol,
518
+ "giving up on UPnP mapping after {new_retry_count} attempts"
519
+ ) ;
520
+ None
521
+ } ;
522
+
523
+ self . mappings . insert (
524
+ mapping. clone ( ) ,
525
+ MappingState :: Failed {
526
+ retry_count : new_retry_count,
527
+ next_retry,
528
+ } ,
529
+ ) ;
530
+
531
+ if was_active {
532
+ tracing:: debug!(
533
+ address=%mapping. internal_addr,
534
+ protocol=%mapping. protocol,
535
+ "failed to remap UPnP mapped for protocol: {err}"
536
+ ) ;
537
+ let external_multiaddr =
538
+ mapping. external_addr ( gateway. external_addr ) ;
539
+ self . pending_events . push_back ( Event :: ExpiredExternalAddr (
540
+ external_multiaddr. clone ( ) ,
541
+ ) ) ;
542
+ return Poll :: Ready ( ToSwarm :: ExternalAddrExpired (
543
+ external_multiaddr,
544
+ ) ) ;
545
+ } else {
546
+ tracing:: debug!(
547
+ address=%mapping. internal_addr,
548
+ protocol=%mapping. protocol,
549
+ retry_count=%new_retry_count,
550
+ "failed to map UPnP mapped for protocol: {err}"
551
+ ) ;
486
552
}
487
553
}
488
554
GatewayEvent :: Removed ( mapping) => {
0 commit comments