1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// SPDX-License-Identifier: Apache-2.0
use std::time::Duration;
use super::{
time::MIN_REBIND_RENEW_WAIT_TIME, DhcpV4Message, DhcpV4MessageType,
DhcpV4Socket,
};
use crate::{DhcpError, DhcpV4Client, DhcpV4State, ErrorKind};
impl DhcpV4Client {
// Broadcast to all DHCP servers requesting lease extension with ciaddr.
pub(crate) async fn rebind(&mut self) -> Result<(), DhcpError> {
loop {
let max_wait_time = self.rebind_max_wait_time()?;
if max_wait_time.is_zero() {
log::debug!("DHCP lease expired, entering init_reboot state");
self.lease = None;
self.state = DhcpV4State::InitReboot;
return Ok(());
}
match tokio::time::timeout(max_wait_time, self._rebind()).await {
Ok(Ok(())) => return Ok(()),
Ok(Err(e)) => {
if max_wait_time < MIN_REBIND_RENEW_WAIT_TIME {
log::info!(
"Rebind fails with error {e}, will fallback to
init_reboot state after {} seconds",
max_wait_time.as_secs()
);
} else {
log::info!(
"Retrying on error {e} after {} seconds",
max_wait_time.as_secs()
);
}
// We assume the failure is instant, so will not consider
// the time elapsed.
tokio::time::sleep(max_wait_time).await;
}
Err(_) => {
if max_wait_time < MIN_REBIND_RENEW_WAIT_TIME {
log::info!(
"Timeout on rebinding, fallback to init_reboot \
state",
);
} else {
log::info!(
"Timeout({}s) on waiting DHCP server DHCPACK \
reply for DHCPREQUEST rebind, retrying",
max_wait_time.as_secs(),
);
}
self.retry_count += 1;
}
}
}
}
// RFC 2131, section 4.4.5 Reacquisition and expiration
// In both RENEWING and REBINDING states, if the client receives no
// response to its DHCPREQUEST message, the client SHOULD wait
// one-half of the remaining time until T2 (in RENEWING state) and
// one-half of the remaining lease time (in REBINDING state), down to
// a minimum of 60 seconds, before retransmitting the DHCPREQUEST
// message.
//
// In practice, if there is less than 60 seconds till lease expired, we
// should only wait the lease remaining time.
//
// Return `Duration::ZERO` if lease expired.
fn rebind_max_wait_time(&self) -> Result<Duration, DhcpError> {
if let Some(lease) = self.lease_timer.as_ref() {
let remains = lease.remains()?;
if remains.is_zero() {
Ok(Duration::ZERO)
} else {
Ok(std::cmp::min(
remains,
std::cmp::max(remains / 2, MIN_REBIND_RENEW_WAIT_TIME),
))
}
} else {
Err(DhcpError::new(
ErrorKind::Bug,
format!(
"gen_rebind_max_wait_time() invoked without lease timer: \
{self:?}"
),
))
}
}
async fn _rebind(&mut self) -> Result<(), DhcpError> {
let lease = match self.lease.as_ref() {
Some(l) => l,
None => {
log::error!(
"BUG: Got empty lease but in DhcpV4State::Rebinding, \
rollback to DhcpV4State::InitReboot"
);
self.state = DhcpV4State::InitReboot;
return Ok(());
}
};
let dhcp_msg = DhcpV4Message::new_rebind(self.xid, &self.config, lease);
let xid = self.xid;
let raw_socket = self.get_raw_socket_or_init().await?;
log::debug!("Sending broadcast DHCPREQUEST for rebind");
raw_socket
.send(&dhcp_msg.to_eth_packet_broadcast()?)
.await?;
log::debug!("Waiting server reply with DHCPACK");
loop {
match raw_socket
.recv_dhcp_lease(DhcpV4MessageType::Ack, xid)
.await
{
Ok(Some(l)) => {
self.done(l)?;
return Ok(());
}
Ok(None) => (),
Err(e) => {
log::info!("Ignoring invalid DHCP package: {e}");
}
};
}
}
}