@@ -114,12 +114,17 @@ enum ClientState {
114
114
/// Timeout and retry configuration.
115
115
#[ derive( Debug , PartialEq , Eq , Copy , Clone ) ]
116
116
#[ cfg_attr( feature = "defmt" , derive( defmt:: Format ) ) ]
117
+ #[ non_exhaustive]
117
118
pub struct RetryConfig {
118
119
pub discover_timeout : Duration ,
119
120
/// The REQUEST timeout doubles every 2 tries.
120
121
pub initial_request_timeout : Duration ,
121
122
pub request_retries : u16 ,
122
123
pub min_renew_timeout : Duration ,
124
+ /// An upper bound on how long to wait between retrying a renew or rebind.
125
+ ///
126
+ /// Set this to [`Duration::MAX`] if you don't want to impose an upper bound.
127
+ pub max_renew_timeout : Duration ,
123
128
}
124
129
125
130
impl Default for RetryConfig {
@@ -129,6 +134,7 @@ impl Default for RetryConfig {
129
134
initial_request_timeout : Duration :: from_secs ( 5 ) ,
130
135
request_retries : 5 ,
131
136
min_renew_timeout : Duration :: from_secs ( 60 ) ,
137
+ max_renew_timeout : Duration :: MAX ,
132
138
}
133
139
}
134
140
}
@@ -214,6 +220,11 @@ impl<'a> Socket<'a> {
214
220
self . retry_config = config;
215
221
}
216
222
223
+ /// Gets the current retry/timeouts configuration
224
+ pub fn get_retry_config ( & self ) -> RetryConfig {
225
+ self . retry_config
226
+ }
227
+
217
228
/// Set the outgoing options.
218
229
pub fn set_outgoing_options ( & mut self , options : & ' a [ DhcpOption < ' a > ] ) {
219
230
self . outgoing_options = options;
@@ -682,14 +693,16 @@ impl<'a> Socket<'a> {
682
693
+ self
683
694
. retry_config
684
695
. min_renew_timeout
685
- . max ( ( state. expires_at - now) / 2 ) ;
696
+ . max ( ( state. expires_at - now) / 2 )
697
+ . min ( self . retry_config . max_renew_timeout ) ;
686
698
} else {
687
699
state. renew_at = now
688
700
+ self
689
701
. retry_config
690
702
. min_renew_timeout
691
703
. max ( ( state. rebind_at - now) / 2 )
692
- . min ( state. rebind_at - now) ;
704
+ . min ( state. rebind_at - now)
705
+ . min ( self . retry_config . max_renew_timeout ) ;
693
706
}
694
707
695
708
self . transaction_id = next_transaction_id;
@@ -1358,6 +1371,39 @@ mod test {
1358
1371
}
1359
1372
}
1360
1373
1374
+ #[ rstest]
1375
+ #[ case:: ip( Medium :: Ethernet ) ]
1376
+ #[ cfg( feature = "medium-ethernet" ) ]
1377
+ fn test_min_max_renew_timeout ( #[ case] medium : Medium ) {
1378
+ let mut s = socket_bound ( medium) ;
1379
+ // Set a minimum of 45s and a maximum of 120s
1380
+ let config = RetryConfig {
1381
+ max_renew_timeout : Duration :: from_secs ( 120 ) ,
1382
+ min_renew_timeout : Duration :: from_secs ( 45 ) ,
1383
+ ..s. get_retry_config ( )
1384
+ } ;
1385
+ s. set_retry_config ( config) ;
1386
+ recv ! ( s, [ ] ) ;
1387
+ // First renew attempt at T1
1388
+ recv ! ( s, time 499_999 , [ ] ) ;
1389
+ recv ! ( s, time 500_000 , [ ( IP_SEND , UDP_SEND , DHCP_RENEW ) ] ) ;
1390
+ // Next renew attempt 120s after T1 because we hit the max
1391
+ recv ! ( s, time 619_999 , [ ] ) ;
1392
+ recv ! ( s, time 620_000 , [ ( IP_SEND , UDP_SEND , DHCP_RENEW ) ] ) ;
1393
+ // Next renew attempt 120s after previous because we hit the max again
1394
+ recv ! ( s, time 739_999 , [ ] ) ;
1395
+ recv ! ( s, time 740_000 , [ ( IP_SEND , UDP_SEND , DHCP_RENEW ) ] ) ;
1396
+ // Next renew attempt half way to T2
1397
+ recv ! ( s, time 807_499 , [ ] ) ;
1398
+ recv ! ( s, time 807_500 , [ ( IP_SEND , UDP_SEND , DHCP_RENEW ) ] ) ;
1399
+ // Next renew attempt 45s after previous because we hit the min
1400
+ recv ! ( s, time 852_499 , [ ] ) ;
1401
+ recv ! ( s, time 852_500 , [ ( IP_SEND , UDP_SEND , DHCP_RENEW ) ] ) ;
1402
+ // Next is a rebind, because the min puts us after T2
1403
+ recv ! ( s, time 874_999 , [ ] ) ;
1404
+ recv ! ( s, time 875_000 , [ ( IP_BROADCAST_ADDRESSED , UDP_SEND , DHCP_REBIND ) ] ) ;
1405
+ }
1406
+
1361
1407
#[ rstest]
1362
1408
#[ case:: ip( Medium :: Ethernet ) ]
1363
1409
#[ cfg( feature = "medium-ethernet" ) ]
0 commit comments