Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding DHCP lease management #437

Merged
merged 8 commits into from
Mar 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

No unreleased changes yet
### New features
* dhcp: Updated DHCP client to respect lease times provided by the server.

## [0.7.0] - 2021-01-20

Expand Down
48 changes: 22 additions & 26 deletions src/dhcp/clientv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ use super::{UDP_SERVER_PORT, UDP_CLIENT_PORT};
const DISCOVER_TIMEOUT: u64 = 10;
const REQUEST_TIMEOUT: u64 = 1;
const REQUEST_RETRIES: u16 = 15;
const RENEW_INTERVAL: u64 = 60;
const RENEW_RETRIES: u16 = 3;
const DEFAULT_RENEW_INTERVAL: u32 = 60;
const PARAMETER_REQUEST_LIST: &[u8] = &[
dhcpv4_field::OPT_SUBNET_MASK,
dhcpv4_field::OPT_ROUTER,
Expand All @@ -39,7 +38,6 @@ struct RequestState {

#[derive(Debug)]
struct RenewState {
retry: u16,
endpoint_ip: Ipv4Address,
server_identifier: Ipv4Address,
}
Expand All @@ -59,6 +57,8 @@ pub struct Client {
raw_handle: SocketHandle,
/// When to send next request
next_egress: Instant,
/// When any existing DHCP address will expire.
lease_expiration: Option<Instant>,
transaction_id: u32,
}

Expand Down Expand Up @@ -104,6 +104,7 @@ impl Client {
raw_handle,
next_egress: now,
transaction_id: 1,
lease_expiration: None,
}
}

Expand Down Expand Up @@ -199,12 +200,18 @@ impl Client {

// once we receive the ack, we can pass the config to the user
let config = if dhcp_repr.message_type == DhcpMessageType::Ack {
let address = dhcp_repr.subnet_mask
.and_then(|mask| IpAddress::Ipv4(mask).to_prefix_len())
.map(|prefix_len| Ipv4Cidr::new(dhcp_repr.your_ip, prefix_len));
let router = dhcp_repr.router;
let dns_servers = dhcp_repr.dns_servers
.unwrap_or([None; 3]);
let lease_duration = dhcp_repr.lease_duration.unwrap_or(DEFAULT_RENEW_INTERVAL * 2);
self.lease_expiration = Some(now + Duration::from_secs(lease_duration.into()));

// RFC 2131 indicates clients should renew a lease halfway through its expiration.
self.next_egress = now + Duration::from_secs((lease_duration / 2).into());

let address = dhcp_repr.subnet_mask
.and_then(|mask| IpAddress::Ipv4(mask).to_prefix_len())
.map(|prefix_len| Ipv4Cidr::new(dhcp_repr.your_ip, prefix_len));
let router = dhcp_repr.router;
let dns_servers = dhcp_repr.dns_servers
.unwrap_or([None; 3]);
Some(Config { address, router, dns_servers })
} else {
None
Expand All @@ -227,22 +234,12 @@ impl Client {
if dhcp_repr.message_type == DhcpMessageType::Ack &&
server_identifier == r_state.server_identifier =>
{
self.next_egress = now + Duration::from_secs(RENEW_INTERVAL);
let p_state = RenewState {
retry: 0,
endpoint_ip: *src_ip,
server_identifier,
};
Some(ClientState::Renew(p_state))
}
ClientState::Renew(ref mut p_state)
if dhcp_repr.message_type == DhcpMessageType::Ack &&
server_identifier == p_state.server_identifier =>
{
self.next_egress = now + Duration::from_secs(RENEW_INTERVAL);
p_state.retry = 0;
None
}
_ => None
}.map(|new_state| self.state = new_state);

Expand All @@ -256,13 +253,12 @@ impl Client {
net_debug!("DHCP request retries exceeded, restarting discovery");
true
}
ClientState::Renew(ref mut r_state) if r_state.retry >= RENEW_RETRIES => {
net_debug!("DHCP renew retries exceeded, restarting discovery");
true
}
_ => false
};
if retries_exceeded {

let lease_expired = self.lease_expiration.map_or(false, |expiration| now >= expiration);

if lease_expired || retries_exceeded {
self.reset(now);
// Return a config now so that user code assigns the
// 0.0.0.0/0 address, which will be used sending a DHCP
Expand Down Expand Up @@ -294,6 +290,7 @@ impl Client {
server_identifier: None,
parameter_request_list: None,
max_size: Some(raw_socket.payload_recv_capacity() as u16),
lease_duration: None,
dns_servers: None,
};
let mut send_packet = |iface, endpoint, dhcp_repr| {
Expand Down Expand Up @@ -329,8 +326,7 @@ impl Client {
send_packet(iface, endpoint, dhcp_repr)
}
ClientState::Renew(ref mut p_state) => {
p_state.retry += 1;
self.next_egress = now + Duration::from_secs(RENEW_INTERVAL);
self.next_egress = now + Duration::from_secs(DEFAULT_RENEW_INTERVAL.into());

let endpoint = IpEndpoint {
addr: p_state.endpoint_ip.into(),
Expand Down
57 changes: 55 additions & 2 deletions src/wire/dhcpv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub enum DhcpOption<'a> {
RequestedIp(Ipv4Address),
ClientIdentifier(EthernetAddress),
ServerIdentifier(Ipv4Address),
IpLeaseTime(u32),
Router(Ipv4Address),
SubnetMask(Ipv4Address),
MaximumDhcpMessageSize(u16),
Expand Down Expand Up @@ -103,6 +104,9 @@ impl<'a> DhcpOption<'a> {
(field::OPT_MAX_DHCP_MESSAGE_SIZE, 2) => {
option = DhcpOption::MaximumDhcpMessageSize(u16::from_be_bytes([data[0], data[1]]));
}
(field::OPT_IP_LEASE_TIME, 4) => {
option = DhcpOption::IpLeaseTime(u32::from_be_bytes([data[0], data[1], data[2], data[3]]))
}
(_, _) => {
option = DhcpOption::Other { kind: kind, data: data };
}
Expand All @@ -129,6 +133,7 @@ impl<'a> DhcpOption<'a> {
&DhcpOption::MaximumDhcpMessageSize(_) => {
4
}
&DhcpOption::IpLeaseTime(_) => 6,
&DhcpOption::Other { data, .. } => 2 + data.len()
}
}
Expand Down Expand Up @@ -178,6 +183,10 @@ impl<'a> DhcpOption<'a> {
buffer[0] = field::OPT_MAX_DHCP_MESSAGE_SIZE;
buffer[2..4].copy_from_slice(&size.to_be_bytes()[..]);
}
DhcpOption::IpLeaseTime(lease_time) => {
buffer[0] = field::OPT_IP_LEASE_TIME;
buffer[2..6].copy_from_slice(&lease_time.to_be_bytes()[..]);
}
DhcpOption::Other { kind, data: provided } => {
buffer[0] = kind;
buffer[2..skip_length].copy_from_slice(provided);
Expand Down Expand Up @@ -674,6 +683,8 @@ pub struct Repr<'a> {
pub dns_servers: Option<[Option<Ipv4Address>; 3]>,
/// The maximum size dhcp packet the interface can receive
pub max_size: Option<u16>,
/// The DHCP IP lease duration, specified in seconds.
pub lease_duration: Option<u32>
}

impl<'a> Repr<'a> {
Expand Down Expand Up @@ -725,6 +736,7 @@ impl<'a> Repr<'a> {
let mut parameter_request_list = None;
let mut dns_servers = None;
let mut max_size = None;
let mut lease_duration = None;

let mut options = packet.options()?;
while !options.is_empty() {
Expand Down Expand Up @@ -755,6 +767,9 @@ impl<'a> Repr<'a> {
DhcpOption::MaximumDhcpMessageSize(size) => {
max_size = Some(size);
}
DhcpOption::IpLeaseTime(duration) => {
lease_duration = Some(duration);
}
DhcpOption::Other {kind: field::OPT_PARAMETER_REQUEST_LIST, data} => {
parameter_request_list = Some(data);
}
Expand All @@ -776,6 +791,7 @@ impl<'a> Repr<'a> {
transaction_id, client_hardware_address, client_ip, your_ip, server_ip, relay_agent_ip,
broadcast, requested_ip, server_identifier, router,
subnet_mask, client_identifier, parameter_request_list, dns_servers, max_size,
lease_duration,
message_type: message_type?,
})
}
Expand Down Expand Up @@ -820,6 +836,9 @@ impl<'a> Repr<'a> {
if let Some(size) = self.max_size {
let tmp = options; options = DhcpOption::MaximumDhcpMessageSize(size).emit(tmp);
}
if let Some(duration) = self.lease_duration {
let tmp = options; options = DhcpOption::IpLeaseTime(duration).emit(tmp);
}
if let Some(list) = self.parameter_request_list {
let option = DhcpOption::Other{ kind: field::OPT_PARAMETER_REQUEST_LIST, data: list };
let tmp = options; options = option.emit(tmp);
Expand Down Expand Up @@ -858,7 +877,7 @@ mod test {
0x00, 0x00, 0x39, 0x2, 0x5, 0xdc, 0x37, 0x04, 0x01, 0x03, 0x06, 0x2a, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];

static ACK_BYTES: &[u8] = &[
static ACK_DNS_SERVER_BYTES: &[u8] = &[
0x02, 0x01, 0x06, 0x00, 0xcc, 0x34, 0x75, 0xab, 0x00, 0x00, 0x80, 0x00, 0x0a, 0xff, 0x06, 0x91,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xff, 0x06, 0xfe, 0x34, 0x17, 0xeb, 0xc9,
0xaa, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
Expand All @@ -882,6 +901,28 @@ mod test {
0x01, 0x4a, 0x06, 0xa3, 0x01, 0x4a, 0x07, 0x2e, 0x01, 0x08, 0xff
];

static ACK_LEASE_TIME_BYTES: &[u8] = &[
0x02, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0a, 0x22, 0x10, 0x0b, 0x0a, 0x22, 0x10, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x04, 0x91, 0x62, 0xd2,
0xa8, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
0x35, 0x01, 0x05, 0x36, 0x04, 0x0a, 0x22, 0x10, 0x0a, 0x33, 0x04, 0x00, 0x00, 0x02, 0x56, 0x01,
0x04, 0xff, 0xff, 0xff, 0x00, 0x03, 0x04, 0x0a, 0x22, 0x10, 0x0a, 0xff, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];

const IP_NULL: Ipv4Address = Ipv4Address([0, 0, 0, 0]);
const CLIENT_MAC: EthernetAddress = EthernetAddress([0x0, 0x0b, 0x82, 0x01, 0xfc, 0x42]);
const DHCP_SIZE: u16 = 1500;
Expand Down Expand Up @@ -984,6 +1025,7 @@ mod test {
relay_agent_ip: IP_NULL,
broadcast: false,
max_size: Some(DHCP_SIZE),
lease_duration: None,
requested_ip: Some(IP_NULL),
client_identifier: Some(CLIENT_MAC),
server_identifier: None,
Expand Down Expand Up @@ -1031,8 +1073,9 @@ mod test {

#[test]
fn test_parse_ack_dns_servers() {
let packet = Packet::new_unchecked(ACK_BYTES);
let packet = Packet::new_unchecked(ACK_DNS_SERVER_BYTES);
let repr = Repr::parse(&packet).unwrap();

// The packet described by ACK_BYTES advertises 4 DNS servers
// Here we ensure that we correctly parse the first 3 into our fixed
// length-3 array (see issue #305)
Expand All @@ -1041,4 +1084,14 @@ mod test {
Some(Ipv4Address([163, 1, 74, 7])),
Some(Ipv4Address([163, 1, 74, 3]))]));
}

#[test]
fn test_parse_ack_lease_duration() {
let packet = Packet::new_unchecked(ACK_LEASE_TIME_BYTES);
let repr = Repr::parse(&packet).unwrap();

// Verify that the lease time in the ACK is properly parsed. The packet contains a lease
// duration of 598s.
assert_eq!(repr.lease_duration, Some(598));
}
}