@@ -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
}
@@ -270,15 +313,14 @@ impl NetworkBehaviour for Behaviour {
270
313
return ;
271
314
} ;
272
315
273
- if let Some ( ( mapping, _state) ) = self
274
- . mappings
275
- . iter ( )
276
- . find ( |( mapping, _state) | mapping. internal_addr . port ( ) == addr. port ( ) )
277
- {
316
+ if let Some ( ( mapping, _state) ) = self . mappings . iter ( ) . find ( |( mapping, state) | {
317
+ matches ! ( state, MappingState :: Active ( _) )
318
+ && mapping. internal_addr . port ( ) == addr. port ( )
319
+ } ) {
278
320
tracing:: debug!(
279
321
multiaddress=%multiaddr,
280
322
mapped_multiaddress=%mapping. multiaddr,
281
- "port from multiaddress is already being mapped"
323
+ "port from multiaddress is already mapped on the gateway "
282
324
) ;
283
325
return ;
284
326
}
@@ -318,7 +360,8 @@ impl NetworkBehaviour for Behaviour {
318
360
) ;
319
361
}
320
362
321
- self . mappings . insert ( mapping, MappingState :: Pending ) ;
363
+ self . mappings
364
+ . insert ( mapping, MappingState :: Pending { retry_count : 0 } ) ;
322
365
}
323
366
GatewayState :: GatewayNotFound => {
324
367
tracing:: debug!(
@@ -352,7 +395,8 @@ impl NetworkBehaviour for Behaviour {
352
395
err
353
396
) ;
354
397
}
355
- self . mappings . insert ( mapping, MappingState :: Pending ) ;
398
+ self . mappings
399
+ . insert ( mapping, MappingState :: Pending { retry_count : 0 } ) ;
356
400
}
357
401
}
358
402
}
@@ -428,7 +472,7 @@ impl NetworkBehaviour for Behaviour {
428
472
. insert ( mapping. clone ( ) , new_state)
429
473
. expect ( "mapping should exist" )
430
474
{
431
- MappingState :: Pending => {
475
+ MappingState :: Pending { .. } => {
432
476
let external_multiaddr =
433
477
mapping. external_addr ( gateway. external_addr ) ;
434
478
self . pending_events . push_back ( Event :: NewExternalAddr (
@@ -454,36 +498,64 @@ impl NetworkBehaviour for Behaviour {
454
498
}
455
499
}
456
500
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 ! ( )
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 )
486
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
+ ) ;
487
559
}
488
560
}
489
561
GatewayEvent :: Removed ( mapping) => {
0 commit comments