فهرست منبع

Merge pull request #437 from ryan-summers/feature/dhcp-lease-updates

Adding DHCP lease management
Dario Nieuwenhuis 4 سال پیش
والد
کامیت
1bff999a6f
3فایلهای تغییر یافته به همراه79 افزوده شده و 29 حذف شده
  1. 2 1
      CHANGELOG.md
  2. 22 26
      src/dhcp/clientv4.rs
  3. 55 2
      src/wire/dhcpv4.rs

+ 2 - 1
CHANGELOG.md

@@ -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
 

+ 22 - 26
src/dhcp/clientv4.rs

@@ -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,
@@ -39,7 +38,6 @@ struct RequestState {
 
 #[derive(Debug)]
 struct RenewState {
-    retry: u16,
     endpoint_ip: Ipv4Address,
     server_identifier: Ipv4Address,
 }
@@ -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,
 }
 
@@ -104,6 +104,7 @@ impl Client {
             raw_handle,
             next_egress: now,
             transaction_id: 1,
+            lease_expiration: None,
         }
     }
 
@@ -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
@@ -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);
 
@@ -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
@@ -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| {
@@ -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(),

+ 55 - 2
src/wire/dhcpv4.rs

@@ -50,6 +50,7 @@ pub enum DhcpOption<'a> {
     RequestedIp(Ipv4Address),
     ClientIdentifier(EthernetAddress),
     ServerIdentifier(Ipv4Address),
+    IpLeaseTime(u32),
     Router(Ipv4Address),
     SubnetMask(Ipv4Address),
     MaximumDhcpMessageSize(u16),
@@ -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 };
                     }
@@ -129,6 +133,7 @@ impl<'a> DhcpOption<'a> {
             &DhcpOption::MaximumDhcpMessageSize(_) => {
                 4
             }
+            &DhcpOption::IpLeaseTime(_) => 6,
             &DhcpOption::Other { data, .. } => 2 + data.len()
         }
     }
@@ -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);
@@ -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> {
@@ -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() {
@@ -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);
                 }
@@ -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?,
         })
     }
@@ -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);
@@ -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,
@@ -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;
@@ -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,
@@ -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)
@@ -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));
+    }
 }