Browse Source

dhcp: convert to socket

Dario Nieuwenhuis 4 years ago
parent
commit
b791ef535e
13 changed files with 554 additions and 509 deletions
  1. 3 2
      Cargo.toml
  2. 45 56
      examples/dhcp_client.rs
  3. 0 422
      src/dhcp/clientv4.rs
  4. 0 5
      src/dhcp/mod.rs
  5. 54 8
      src/iface/interface.rs
  6. 0 2
      src/lib.rs
  7. 422 0
      src/socket/dhcpv4.rs
  8. 11 0
      src/socket/mod.rs
  9. 6 12
      src/socket/ref_.rs
  10. 3 0
      src/socket/set.rs
  11. 3 0
      src/wire/dhcpv4.rs
  12. 5 2
      src/wire/mod.rs
  13. 2 0
      src/wire/udp.rs

+ 3 - 2
Cargo.toml

@@ -39,13 +39,14 @@ verbose = []
 "phy-tuntap_interface" = ["std", "libc", "medium-ethernet"]
 "proto-ipv4" = []
 "proto-igmp" = ["proto-ipv4"]
-"proto-dhcpv4" = ["proto-ipv4", "socket-raw", "medium-ethernet"]
+"proto-dhcpv4" = ["proto-ipv4"]
 "proto-ipv6" = []
 "socket" = []
 "socket-raw" = ["socket"]
 "socket-udp" = ["socket"]
 "socket-tcp" = ["socket"]
 "socket-icmp" = ["socket"]
+"socket-dhcpv4" = ["socket", "medium-ethernet", "proto-dhcpv4"]
 "async" = []
 
 defmt-trace = []
@@ -59,7 +60,7 @@ default = [
   "medium-ethernet", "medium-ip",
   "phy-raw_socket", "phy-tuntap_interface",
   "proto-ipv4", "proto-igmp", "proto-dhcpv4", "proto-ipv6",
-  "socket-raw", "socket-icmp", "socket-udp", "socket-tcp",
+  "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-dhcpv4",
   "async"
 ]
 

+ 45 - 56
examples/dhcp_client.rs

@@ -3,12 +3,13 @@ mod utils;
 
 use std::collections::BTreeMap;
 use std::os::unix::io::AsRawFd;
+use log::*;
+
 use smoltcp::phy::{Device, Medium, wait as phy_wait};
 use smoltcp::wire::{EthernetAddress, Ipv4Address, IpCidr, Ipv4Cidr};
-use smoltcp::iface::{NeighborCache, InterfaceBuilder, Routes};
-use smoltcp::socket::{SocketSet, RawSocketBuffer, RawPacketMetadata};
+use smoltcp::iface::{NeighborCache, InterfaceBuilder, Interface, Routes};
+use smoltcp::socket::{SocketSet, Dhcpv4Socket, Dhcpv4Event};
 use smoltcp::time::Instant;
-use smoltcp::dhcp::Dhcpv4Client;
 
 fn main() {
     #[cfg(feature = "log")]
@@ -41,65 +42,53 @@ fn main() {
     let mut iface = builder.finalize();
 
     let mut sockets = SocketSet::new(vec![]);
-    let dhcp_rx_buffer = RawSocketBuffer::new(
-        [RawPacketMetadata::EMPTY; 1],
-        vec![0; 900]
-    );
-    let dhcp_tx_buffer = RawSocketBuffer::new(
-        [RawPacketMetadata::EMPTY; 1],
-        vec![0; 600]
-    );
-    let mut dhcp = Dhcpv4Client::new(&mut sockets, dhcp_rx_buffer, dhcp_tx_buffer, Instant::now());
-    let mut prev_cidr = Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0);
+    let dhcp_handle = sockets.add(Dhcpv4Socket::new());
+
     loop {
         let timestamp = Instant::now();
-        iface.poll(&mut sockets, timestamp)
-            .map(|_| ())
-            .unwrap_or_else(|e| println!("Poll: {:?}", e));
-        let config = dhcp.poll(&mut iface, &mut sockets, timestamp)
-            .unwrap_or_else(|e| {
-                println!("DHCP: {:?}", e);
-                None
-            });
-        config.map(|config| {
-            println!("DHCP config: {:?}", config);
-            if let Some(cidr) = config.address {
-                if cidr != prev_cidr {
-                    iface.update_ip_addrs(|addrs| {
-                        addrs.iter_mut().next()
-                            .map(|addr| {
-                                *addr = IpCidr::Ipv4(cidr);
-                            });
-                    });
-                    prev_cidr = cidr;
-                    println!("Assigned a new IPv4 address: {}", cidr);
+        if let Err(e) = iface.poll(&mut sockets, timestamp) {
+            debug!("poll error: {}", e);
+        }
+
+        match sockets.get::<Dhcpv4Socket>(dhcp_handle).poll() {
+            Dhcpv4Event::NoChange => {}
+            Dhcpv4Event::Configured(config) => {
+                debug!("DHCP config acquired!");
+
+                debug!("IP address:      {}", config.address);
+                set_ipv4_addr(&mut iface, config.address);
+
+                if let Some(router) = config.router {
+                    debug!("Default gateway: {}", router);
+                    iface.routes_mut().add_default_ipv4_route(router).unwrap();
+                } else {
+                    debug!("Default gateway: None");
+                    iface.routes_mut().remove_default_ipv4_route();
                 }
-            }
 
-            config.router.map(|router| iface.routes_mut()
-                              .add_default_ipv4_route(router)
-                              .unwrap()
-            );
-            iface.routes_mut()
-                .update(|routes_map| {
-                    routes_map.get(&IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 0))
-                        .map(|default_route| {
-                            println!("Default gateway: {}", default_route.via_router);
-                        });
-                });
-
-            if config.dns_servers.iter().any(|s| s.is_some()) {
-                println!("DNS servers:");
-                for dns_server in config.dns_servers.iter().filter_map(|s| *s) {
-                    println!("- {}", dns_server);
+                for (i, s) in config.dns_servers.iter().enumerate() {
+                    if let Some(s) = s {
+                        debug!("DNS server {}:    {}", i, s);
+                    }
                 }
             }
-        });
+            Dhcpv4Event::Deconfigured => {
+                debug!("DHCP lost config!");
+                set_ipv4_addr(&mut iface, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0));
+                iface.routes_mut().remove_default_ipv4_route();
+            }
+        }
 
-        let mut timeout = dhcp.next_poll(timestamp);
-        iface.poll_delay(&sockets, timestamp)
-            .map(|sockets_timeout| timeout = sockets_timeout);
-        phy_wait(fd, Some(timeout))
-            .unwrap_or_else(|e| println!("Wait: {:?}", e));
+        phy_wait(fd, iface.poll_delay(&sockets, timestamp)).expect("wait error");
     }
 }
+
+fn set_ipv4_addr<DeviceT>(iface: &mut Interface<'_, DeviceT>, cidr: Ipv4Cidr)
+    where DeviceT: for<'d> Device<'d>
+{
+    iface.update_ip_addrs(|addrs| {
+        let dest = addrs.iter_mut().next().unwrap();
+        *dest = IpCidr::Ipv4(cidr);
+    });
+}
+

+ 0 - 422
src/dhcp/clientv4.rs

@@ -1,422 +0,0 @@
-use crate::{Error, Result};
-use crate::wire::{IpVersion, IpProtocol, IpEndpoint, IpAddress,
-           Ipv4Cidr, Ipv4Address, Ipv4Packet, Ipv4Repr,
-           UdpPacket, UdpRepr,
-           DhcpPacket, DhcpRepr, DhcpMessageType};
-use crate::wire::dhcpv4::{field as dhcpv4_field, Packet as Dhcpv4Packet};
-use crate::socket::{SocketSet, SocketHandle, RawSocket, RawSocketBuffer};
-use crate::phy::{Device, ChecksumCapabilities};
-use crate::iface::Interface;
-use crate::time::{Instant, Duration};
-use super::{UDP_SERVER_PORT, UDP_CLIENT_PORT};
-
-const DISCOVER_TIMEOUT: u64 = 10;
-const REQUEST_TIMEOUT: u64 = 1;
-const REQUEST_RETRIES: u16 = 15;
-const DEFAULT_RENEW_INTERVAL: u32 = 60;
-const PARAMETER_REQUEST_LIST: &[u8] = &[
-    dhcpv4_field::OPT_SUBNET_MASK,
-    dhcpv4_field::OPT_ROUTER,
-    dhcpv4_field::OPT_DOMAIN_NAME_SERVER,
-];
-
-/// IPv4 configuration data returned by `client.poll()`
-#[derive(Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct Config {
-    pub address: Option<Ipv4Cidr>,
-    pub router: Option<Ipv4Address>,
-    pub dns_servers: [Option<Ipv4Address>; 3],
-}
-
-#[derive(Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-struct RequestState {
-    retry: u16,
-    endpoint_ip: Ipv4Address,
-    server_identifier: Ipv4Address,
-    requested_ip: Ipv4Address,
-}
-
-#[derive(Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-struct RenewState {
-    endpoint_ip: Ipv4Address,
-    server_identifier: Ipv4Address,
-}
-
-#[derive(Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-enum ClientState {
-    /// Discovering the DHCP server
-    Discovering,
-    /// Requesting an address
-    Requesting(RequestState),
-    /// Having an address, refresh it periodically
-    Renew(RenewState),
-}
-
-pub struct Client {
-    state: ClientState,
-    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,
-}
-
-/// DHCP client with a RawSocket.
-///
-/// To provide memory for the dynamic IP address, configure your
-/// `Interface` with one of `ip_addrs` and the `ipv4_gateway` being
-/// `Ipv4Address::UNSPECIFIED`. You must also assign this `0.0.0.0/0`
-/// while the client's state is `Discovering`. Hence, the `poll()`
-/// method returns a corresponding `Config` struct in this case.
-///
-/// You must call `dhcp_client.poll()` after `iface.poll()` to send
-/// and receive DHCP packets.
-impl Client {
-    /// # Usage
-    /// ```rust
-    /// use smoltcp::socket::{SocketSet, RawSocketBuffer, RawPacketMetadata};
-    /// use smoltcp::dhcp::Dhcpv4Client;
-    /// use smoltcp::time::Instant;
-    ///
-    /// let mut sockets = SocketSet::new(vec![]);
-    /// let dhcp_rx_buffer = RawSocketBuffer::new(
-    ///     [RawPacketMetadata::EMPTY; 1],
-    ///     vec![0; 600]
-    /// );
-    /// let dhcp_tx_buffer = RawSocketBuffer::new(
-    ///     [RawPacketMetadata::EMPTY; 1],
-    ///     vec![0; 600]
-    /// );
-    /// let mut dhcp = Dhcpv4Client::new(
-    ///     &mut sockets,
-    ///     dhcp_rx_buffer, dhcp_tx_buffer,
-    ///     Instant::now()
-    /// );
-    /// ```
-    pub fn new<'a>(sockets: &mut SocketSet<'a>, rx_buffer: RawSocketBuffer<'a>, tx_buffer: RawSocketBuffer<'a>, now: Instant) -> Self
-    {
-        let raw_socket = RawSocket::new(IpVersion::Ipv4, IpProtocol::Udp, rx_buffer, tx_buffer);
-        let raw_handle = sockets.add(raw_socket);
-
-        Client {
-            state: ClientState::Discovering,
-            raw_handle,
-            next_egress: now,
-            transaction_id: 1,
-            lease_expiration: None,
-        }
-    }
-
-    /// When to send next packet
-    ///
-    /// Useful for suspending execution after polling.
-    pub fn next_poll(&self, now: Instant) -> Duration {
-        self.next_egress - now
-    }
-
-    /// Process incoming packets on the contained RawSocket, and send
-    /// DHCP requests when timeouts are ready.
-    ///
-    /// Applying the obtained network configuration is left to the
-    /// user.
-    ///
-    /// A Config can be returned from any valid DHCP reply. The client
-    /// performs no bookkeeping on configuration or their changes.
-    pub fn poll<DeviceT>(&mut self,
-                         iface: &mut Interface<DeviceT>, sockets: &mut SocketSet,
-                         now: Instant
-                        ) -> Result<Option<Config>>
-    where
-        DeviceT: for<'d> Device<'d>,
-    {
-        let checksum_caps = iface.device().capabilities().checksum;
-        let mut raw_socket = sockets.get::<RawSocket>(self.raw_handle);
-
-        // Process incoming
-        let config = {
-            match raw_socket.recv()
-                .and_then(|packet| parse_udp(packet, &checksum_caps)) {
-                    Ok((IpEndpoint {
-                        addr: IpAddress::Ipv4(src_ip),
-                        port: UDP_SERVER_PORT,
-                    }, IpEndpoint {
-                        addr: _,
-                        port: UDP_CLIENT_PORT,
-                    }, payload)) =>
-                        self.ingress(iface, now, payload, &src_ip),
-                    Ok(_) =>
-                        return Err(Error::Unrecognized),
-                    Err(Error::Exhausted) =>
-                        None,
-                    Err(e) =>
-                        return Err(e),
-                }
-        };
-
-        if config.is_some() {
-            // Return a new config immediately so that addresses can
-            // be configured that are required by egress().
-            Ok(config)
-        } else {
-            // Send requests
-            if raw_socket.can_send() && now >= self.next_egress {
-                self.egress(iface, &mut *raw_socket, &checksum_caps, now)
-            } else {
-                Ok(None)
-            }
-        }
-    }
-
-    fn ingress<DeviceT>(&mut self,
-                        iface: &mut Interface<DeviceT>, now: Instant,
-                        data: &[u8], src_ip: &Ipv4Address
-                       ) -> Option<Config>
-    where
-        DeviceT: for<'d> Device<'d>,
-    {
-        let dhcp_packet = match DhcpPacket::new_checked(data) {
-            Ok(dhcp_packet) => dhcp_packet,
-            Err(e) => {
-                net_debug!("DHCP invalid pkt from {}: {:?}", src_ip, e);
-                return None;
-            }
-        };
-        let dhcp_repr = match DhcpRepr::parse(&dhcp_packet) {
-            Ok(dhcp_repr) => dhcp_repr,
-            Err(e) => {
-                net_debug!("DHCP error parsing pkt from {}: {:?}", src_ip, e);
-                return None;
-            }
-        };
-        let mac = iface.ethernet_addr();
-        if dhcp_repr.client_hardware_address != mac { return None }
-        if dhcp_repr.transaction_id != self.transaction_id { return None }
-        let server_identifier = match dhcp_repr.server_identifier {
-            Some(server_identifier) => server_identifier,
-            None => return None,
-        };
-        net_debug!("DHCP recv {:?} from {} ({})", dhcp_repr.message_type, src_ip, server_identifier);
-
-        // once we receive the ack, we can pass the config to the user
-        let config = if dhcp_repr.message_type == DhcpMessageType::Ack {
-            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
-        };
-
-        match self.state {
-            ClientState::Discovering
-                if dhcp_repr.message_type == DhcpMessageType::Offer =>
-            {
-                self.next_egress = now;
-                let r_state = RequestState {
-                    retry: 0,
-                    endpoint_ip: *src_ip,
-                    server_identifier,
-                    requested_ip: dhcp_repr.your_ip // use the offered ip
-                };
-                Some(ClientState::Requesting(r_state))
-            }
-            ClientState::Requesting(ref r_state)
-                if dhcp_repr.message_type == DhcpMessageType::Ack &&
-                   server_identifier == r_state.server_identifier =>
-            {
-                let p_state = RenewState {
-                    endpoint_ip: *src_ip,
-                    server_identifier,
-                };
-                Some(ClientState::Renew(p_state))
-            }
-            _ => None
-        }.map(|new_state| self.state = new_state);
-
-        config
-    }
-
-    fn egress<DeviceT: for<'d> Device<'d>>(&mut self, iface: &mut Interface<DeviceT>, raw_socket: &mut RawSocket, checksum_caps: &ChecksumCapabilities, now: Instant) -> Result<Option<Config>> {
-        // Reset after maximum amount of retries
-        let retries_exceeded = match self.state {
-            ClientState::Requesting(ref mut r_state) if r_state.retry >= REQUEST_RETRIES => {
-                net_debug!("DHCP request retries exceeded, restarting discovery");
-                true
-            }
-            _ => false
-        };
-
-        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
-            // discovery packet in the next call to egress().
-            return Ok(Some(Config {
-                address: Some(Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0)),
-                router: None,
-                dns_servers: [None; 3],
-            }));
-        }
-
-        // Prepare sending next packet
-        self.transaction_id += 1;
-        let mac = iface.ethernet_addr();
-
-        let mut dhcp_repr = DhcpRepr {
-            message_type: DhcpMessageType::Discover,
-            transaction_id: self.transaction_id,
-            client_hardware_address: mac,
-            client_ip: Ipv4Address::UNSPECIFIED,
-            your_ip: Ipv4Address::UNSPECIFIED,
-            server_ip: Ipv4Address::UNSPECIFIED,
-            router: None,
-            subnet_mask: None,
-            relay_agent_ip: Ipv4Address::UNSPECIFIED,
-            broadcast: true,
-            requested_ip: None,
-            client_identifier: Some(mac),
-            server_identifier: None,
-            parameter_request_list: Some(PARAMETER_REQUEST_LIST),
-            max_size: Some(raw_socket.payload_recv_capacity() as u16),
-            lease_duration: None,
-            dns_servers: None,
-        };
-        let mut send_packet = |iface, endpoint, dhcp_repr| {
-            send_packet(iface, raw_socket, &endpoint, &dhcp_repr, checksum_caps)
-                .map(|()| None)
-        };
-
-
-        match self.state {
-            ClientState::Discovering => {
-                self.next_egress = now + Duration::from_secs(DISCOVER_TIMEOUT);
-                let endpoint = IpEndpoint {
-                    addr: Ipv4Address::BROADCAST.into(),
-                    port: UDP_SERVER_PORT,
-                };
-                net_trace!("DHCP send discover to {}: {:?}", endpoint, dhcp_repr);
-                send_packet(iface, endpoint, dhcp_repr)
-            }
-            ClientState::Requesting(ref mut r_state) => {
-                r_state.retry += 1;
-                self.next_egress = now + Duration::from_secs(REQUEST_TIMEOUT);
-
-                let endpoint = IpEndpoint {
-                    addr: Ipv4Address::BROADCAST.into(),
-                    port: UDP_SERVER_PORT,
-                };
-                dhcp_repr.message_type = DhcpMessageType::Request;
-                dhcp_repr.broadcast = false;
-                dhcp_repr.requested_ip = Some(r_state.requested_ip);
-                dhcp_repr.server_identifier = Some(r_state.server_identifier);
-                net_trace!("DHCP send request to {} = {:?}", endpoint, dhcp_repr);
-                send_packet(iface, endpoint, dhcp_repr)
-            }
-            ClientState::Renew(ref mut p_state) => {
-                self.next_egress = now + Duration::from_secs(DEFAULT_RENEW_INTERVAL.into());
-
-                let endpoint = IpEndpoint {
-                    addr: p_state.endpoint_ip.into(),
-                    port: UDP_SERVER_PORT,
-                };
-                let client_ip = iface.ipv4_addr().unwrap_or(Ipv4Address::UNSPECIFIED);
-                dhcp_repr.message_type = DhcpMessageType::Request;
-                dhcp_repr.client_ip = client_ip;
-                dhcp_repr.broadcast = false;
-                net_trace!("DHCP send renew to {}: {:?}", endpoint, dhcp_repr);
-                send_packet(iface, endpoint, dhcp_repr)
-            }
-        }
-    }
-
-    /// Reset state and restart discovery phase.
-    ///
-    /// Use this to speed up acquisition of an address in a new
-    /// network if a link was down and it is now back up.
-    ///
-    /// You *must* configure a `0.0.0.0` address on your interface
-    /// before the next call to `poll()`!
-    pub fn reset(&mut self, now: Instant) {
-        net_trace!("DHCP reset");
-        self.state = ClientState::Discovering;
-        self.next_egress = now;
-        self.lease_expiration = None;
-    }
-}
-
-fn send_packet<DeviceT: for<'d> Device<'d>>(iface: &mut Interface<DeviceT>, raw_socket: &mut RawSocket, endpoint: &IpEndpoint, dhcp_repr: &DhcpRepr, checksum_caps: &ChecksumCapabilities) -> Result<()> {
-
-    let udp_repr = UdpRepr {
-        src_port: UDP_CLIENT_PORT,
-        dst_port: endpoint.port,
-    };
-
-    let src_addr = iface.ipv4_addr().unwrap();
-    let dst_addr = match endpoint.addr {
-        IpAddress::Ipv4(addr) => addr,
-        _ => return Err(Error::Illegal),
-    };
-    let ipv4_repr = Ipv4Repr {
-        src_addr,
-        dst_addr,
-        protocol: IpProtocol::Udp,
-        payload_len: udp_repr.header_len() + dhcp_repr.buffer_len(),
-        hop_limit: 64,
-    };
-
-    let mut packet = raw_socket.send(
-        ipv4_repr.buffer_len() + udp_repr.header_len() + dhcp_repr.buffer_len()
-    )?;
-    {
-        let mut ipv4_packet = Ipv4Packet::new_unchecked(&mut packet);
-        ipv4_repr.emit(&mut ipv4_packet, &checksum_caps);
-    }
-    {
-        let mut udp_packet = UdpPacket::new_unchecked(
-            &mut packet[ipv4_repr.buffer_len()..]
-        );
-        udp_repr.emit(&mut udp_packet,
-                      &src_addr.into(), &dst_addr.into(),
-                      dhcp_repr.buffer_len(),
-                      |buf| dhcp_repr.emit(&mut Dhcpv4Packet::new_unchecked(buf)).unwrap(),
-                      checksum_caps);
-    }
-    Ok(())
-}
-
-fn parse_udp<'a>(data: &'a [u8], checksum_caps: &ChecksumCapabilities) -> Result<(IpEndpoint, IpEndpoint, &'a [u8])> {
-    let ipv4_packet = Ipv4Packet::new_checked(data)?;
-    let ipv4_repr = Ipv4Repr::parse(&ipv4_packet, &checksum_caps)?;
-    let udp_packet = UdpPacket::new_checked(ipv4_packet.payload())?;
-    let udp_repr = UdpRepr::parse(
-        &udp_packet,
-        &ipv4_repr.src_addr.into(), &ipv4_repr.dst_addr.into(),
-        checksum_caps
-    )?;
-    let src = IpEndpoint {
-        addr: ipv4_repr.src_addr.into(),
-        port: udp_repr.src_port,
-    };
-    let dst = IpEndpoint {
-        addr: ipv4_repr.dst_addr.into(),
-        port: udp_repr.dst_port,
-    };
-    let data = udp_packet.payload();
-    Ok((src, dst, data))
-}

+ 0 - 5
src/dhcp/mod.rs

@@ -1,5 +0,0 @@
-pub const UDP_SERVER_PORT: u16 = 67;
-pub const UDP_CLIENT_PORT: u16 = 68;
-
-mod clientv4;
-pub use self::clientv4::{Client as Dhcpv4Client, Config as Dhcpv4Config};

+ 54 - 8
src/iface/interface.rs

@@ -265,7 +265,9 @@ pub(crate) enum IpPacket<'a> {
     #[cfg(feature = "socket-udp")]
     Udp((IpRepr, UdpRepr, &'a [u8])),
     #[cfg(feature = "socket-tcp")]
-    Tcp((IpRepr, TcpRepr<'a>))
+    Tcp((IpRepr, TcpRepr<'a>)),
+    #[cfg(feature = "socket-dhcpv4")]
+    Dhcpv4((Ipv4Repr, UdpRepr, DhcpRepr<'a>)),
 }
 
 impl<'a> IpPacket<'a> {
@@ -283,6 +285,8 @@ impl<'a> IpPacket<'a> {
             IpPacket::Udp((ip_repr, _, _)) => ip_repr.clone(),
             #[cfg(feature = "socket-tcp")]
             IpPacket::Tcp((ip_repr, _)) => ip_repr.clone(),
+            #[cfg(feature = "socket-dhcpv4")]
+            IpPacket::Dhcpv4((ipv4_repr, _, _)) => IpRepr::Ipv4(*ipv4_repr),
         }
     }
 
@@ -331,6 +335,13 @@ impl<'a> IpPacket<'a> {
                                 &_ip_repr.src_addr(), &_ip_repr.dst_addr(),
                                 &caps.checksum);
             }
+            #[cfg(feature = "socket-dhcpv4")]
+            IpPacket::Dhcpv4((_, udp_repr, dhcp_repr)) =>
+                udp_repr.emit(&mut UdpPacket::new_unchecked(payload),
+                              &_ip_repr.src_addr(), &_ip_repr.dst_addr(),
+                              dhcp_repr.buffer_len(),
+                              |buf| dhcp_repr.emit(&mut DhcpPacket::new_unchecked(buf)).unwrap(),
+                              &caps.checksum),
         }
     }
 }
@@ -662,6 +673,14 @@ impl<'a, DeviceT> Interface<'a, DeviceT>
                 })
             }
 
+            let _ip_mtu = match _caps.medium {
+                #[cfg(feature = "medium-ethernet")]
+                Medium::Ethernet => _caps.max_transmission_unit - EthernetFrame::<&[u8]>::header_len(),
+                #[cfg(feature = "medium-ip")]
+                Medium::Ip => _caps.max_transmission_unit,
+            };
+
+
             let socket_result =
                 match *socket {
                     #[cfg(feature = "socket-raw")]
@@ -687,15 +706,14 @@ impl<'a, DeviceT> Interface<'a, DeviceT>
                             respond!(IpPacket::Udp(response))),
                     #[cfg(feature = "socket-tcp")]
                     Socket::Tcp(ref mut socket) => {
-                        let ip_mtu = match _caps.medium {
-                            #[cfg(feature = "medium-ethernet")]
-                            Medium::Ethernet => _caps.max_transmission_unit - EthernetFrame::<&[u8]>::header_len(),
-                            #[cfg(feature = "medium-ip")]
-                            Medium::Ip => _caps.max_transmission_unit,
-                        };
-                        socket.dispatch(timestamp, ip_mtu, |response|
+                        socket.dispatch(timestamp, _ip_mtu, |response|
                             respond!(IpPacket::Tcp(response)))
                     }
+                    #[cfg(feature = "socket-dhcpv4")]
+                    Socket::Dhcpv4(ref mut socket) =>
+                        // todo don't unwrap
+                        socket.dispatch(timestamp, inner.ethernet_addr.unwrap(), _ip_mtu, |response|
+                            respond!(IpPacket::Dhcpv4(response))),
                 };
 
             match (device_result, socket_result) {
@@ -1063,6 +1081,34 @@ impl<'a> InterfaceInner<'a> {
         #[cfg(not(feature = "socket-raw"))]
         let handled_by_raw_socket = false;
 
+
+        #[cfg(feature = "socket-dhcpv4")]
+        {
+            if ipv4_repr.protocol == IpProtocol::Udp && self.ethernet_addr.is_some() {
+                // First check for source and dest ports, then do `UdpRepr::parse` if they match.
+                // This way we avoid validating the UDP checksum twice for all non-DHCP UDP packets (one here, one in `process_udp`)
+                let udp_packet = UdpPacket::new_checked(ip_payload)?;
+                if udp_packet.src_port() == DHCP_SERVER_PORT && udp_packet.dst_port() == DHCP_CLIENT_PORT {
+                    if let Some(mut dhcp_socket) = sockets.iter_mut().filter_map(Dhcpv4Socket::downcast).next() {
+                        let (src_addr, dst_addr) = (ip_repr.src_addr(), ip_repr.dst_addr());
+                        let checksum_caps = self.device_capabilities.checksum.clone();
+                        let udp_repr = UdpRepr::parse(&udp_packet, &src_addr, &dst_addr, &checksum_caps)?;
+                        let udp_payload = udp_packet.payload();
+
+                        // NOTE(unwrap): we checked for is_some above.
+                        let ethernet_addr = self.ethernet_addr.unwrap();
+
+                        match dhcp_socket.process(timestamp, ethernet_addr, &ipv4_repr, &udp_repr, udp_payload) {
+                            // The packet is valid and handled by socket.
+                            Ok(()) => return Ok(None),
+                            // The packet is malformed, or the socket buffer is full.
+                            Err(e) => return Err(e)
+                        }
+                    }
+                }
+            }
+        }
+
         if !self.has_ip_addr(ipv4_repr.dst_addr) &&
            !self.has_multicast_group(ipv4_repr.dst_addr) &&
            !self.is_broadcast_v4(ipv4_repr.dst_addr) {

+ 0 - 2
src/lib.rs

@@ -121,8 +121,6 @@ pub mod iface;
 #[cfg(feature = "socket")]
 pub mod socket;
 pub mod time;
-#[cfg(feature = "proto-dhcpv4")]
-pub mod dhcp;
 
 /// The error type for the networking stack.
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]

+ 422 - 0
src/socket/dhcpv4.rs

@@ -0,0 +1,422 @@
+use crate::{Error, Result};
+use crate::wire::{EthernetAddress, IpProtocol, IpAddress,
+           Ipv4Cidr, Ipv4Address, Ipv4Repr,
+           UdpRepr, UDP_HEADER_LEN,
+           DhcpPacket, DhcpRepr, DhcpMessageType, DHCP_CLIENT_PORT, DHCP_SERVER_PORT};
+use crate::wire::dhcpv4::{field as dhcpv4_field};
+use crate::socket::SocketMeta;
+use crate::time::{Instant, Duration};
+
+use super::{PollAt, Socket};
+
+const DISCOVER_TIMEOUT: Duration = Duration::from_secs(10);
+
+const REQUEST_TIMEOUT: Duration = Duration::from_secs(1);
+const REQUEST_RETRIES: u16 = 15;
+
+const MIN_RENEW_TIMEOUT: Duration = Duration::from_secs(60);
+
+const DEFAULT_LEASE_DURATION: u32 = 120;
+
+const PARAMETER_REQUEST_LIST: &[u8] = &[
+    dhcpv4_field::OPT_SUBNET_MASK,
+    dhcpv4_field::OPT_ROUTER,
+    dhcpv4_field::OPT_DOMAIN_NAME_SERVER,
+];
+
+/// IPv4 configuration data provided by the DHCP server.
+#[derive(Debug, Eq, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Config {
+    /// IP address 
+    pub address: Ipv4Cidr,
+    /// Router address, also known as default gateway. Does not necessarily
+    /// match the DHCP server's address.
+    pub router: Option<Ipv4Address>,
+    /// DNS servers
+    pub dns_servers: [Option<Ipv4Address>; 3],
+}
+
+/// Information on how to reach a DHCP server.
+#[derive(Debug, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct ServerInfo {
+    /// IP address to use as destination in outgoing packets
+    address: Ipv4Address,
+    /// Server identifier to use in outgoing packets. Usually equal to server_address,
+    /// but may differ in some situations (eg DHCP relays)
+    identifier: Ipv4Address,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct DiscoverState {
+    /// When to send next request
+    retry_at: Instant,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct RequestState {
+    /// When to send next request
+    retry_at: Instant,
+    /// How many retries have been done
+    retry: u16,
+    /// Server we're trying to request from
+    server: ServerInfo,
+    /// IP address that we're trying to request.
+    requested_ip: Ipv4Address,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct RenewState {
+    /// Server that gave us the lease
+    server: ServerInfo,
+    /// Active networkc config
+    config: Config,
+
+    /// Renew timer. When reached, we will start attempting
+    /// to renew this lease with the DHCP server.
+    /// Must be less or equal than `expires_at`.
+    renew_at: Instant,
+    /// Expiration timer. When reached, this lease is no longer valid, so it must be
+    /// thrown away and the ethernet interface deconfigured.
+    expires_at: Instant,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+enum ClientState {
+    /// Discovering the DHCP server
+    Discovering(DiscoverState),
+    /// Requesting an address
+    Requesting(RequestState),
+    /// Having an address, refresh it periodically.
+    Renewing(RenewState),
+}
+
+/// Return value for the `Dhcpv4Socket::poll` function
+pub enum Event<'a> {
+    /// No change has occured to the configuration.
+    NoChange,
+    /// Configuration has been lost (for example, the lease has expired)
+    Deconfigured,
+    /// Configuration has been newly acquired, or modified.
+    Configured(&'a Config),
+}
+
+#[derive(Debug)]
+pub struct Dhcpv4Socket {
+    pub(crate) meta: SocketMeta,
+    /// State of the DHCP client.
+    state: ClientState,
+    /// Set to true on config/state change, cleared back to false by the `config` function.
+    config_changed: bool,
+    /// xid of the last sent message.
+    transaction_id: u32,
+}
+
+/// DHCP client socket.
+///
+/// The socket acquires an IP address configuration through DHCP autonomously.
+/// You must query the configuration with `.poll()` after every call to `Interface::poll()`,
+/// and apply the configuration to the `Interface`.
+impl Dhcpv4Socket {
+    /// Create a DHCPv4 socket
+    #[allow(clippy::new_without_default)]
+    pub fn new() -> Self {
+        Dhcpv4Socket {
+            meta: SocketMeta::default(),
+            state: ClientState::Discovering(DiscoverState{
+                retry_at: Instant::from_millis(0),
+            }),
+            config_changed: true,
+            transaction_id: 1,
+        }
+    }
+
+    pub(crate) fn poll_at(&self) -> PollAt {
+        let t = match &self.state {
+            ClientState::Discovering(state) => state.retry_at,
+            ClientState::Requesting(state) => state.retry_at,
+            ClientState::Renewing(state) => state.renew_at.min(state.expires_at),
+        };
+        PollAt::Time(t)
+    }
+
+    pub(crate) fn process(&mut self, now: Instant, ethernet_addr: EthernetAddress, ip_repr: &Ipv4Repr, repr: &UdpRepr, payload: &[u8]) -> Result<()> {
+        let src_ip = ip_repr.src_addr;
+
+        if repr.src_port != DHCP_SERVER_PORT || repr.dst_port != DHCP_CLIENT_PORT {
+            return Ok(())
+        }
+
+        let dhcp_packet = match DhcpPacket::new_checked(payload) {
+            Ok(dhcp_packet) => dhcp_packet,
+            Err(e) => {
+                net_debug!("DHCP invalid pkt from {}: {:?}", src_ip, e);
+                return Ok(());
+            }
+        };
+        let dhcp_repr = match DhcpRepr::parse(&dhcp_packet) {
+            Ok(dhcp_repr) => dhcp_repr,
+            Err(e) => {
+                net_debug!("DHCP error parsing pkt from {}: {:?}", src_ip, e);
+                return Ok(());
+            }
+        };
+        if dhcp_repr.client_hardware_address != ethernet_addr { return Ok(()) }
+        if dhcp_repr.transaction_id != self.transaction_id { return Ok(()) }
+        let server_identifier = match dhcp_repr.server_identifier {
+            Some(server_identifier) => server_identifier,
+            None => {
+                net_debug!("DHCP ignoring {:?} because missing server_identifier", dhcp_repr.message_type);
+                return Ok(());
+            }
+        };
+
+        net_debug!("DHCP recv {:?} from {} ({})", dhcp_repr.message_type, src_ip, server_identifier);
+        
+        match (&mut self.state, dhcp_repr.message_type){
+            (ClientState::Discovering(_state), DhcpMessageType::Offer) => {
+                if !dhcp_repr.your_ip.is_unicast() {
+                    net_debug!("DHCP ignoring OFFER because your_ip is not unicast");
+                    return Ok(())
+                }
+                
+                self.state = ClientState::Requesting(RequestState {
+                    retry_at: now,
+                    retry: 0,
+                    server: ServerInfo {
+                        address: src_ip,
+                        identifier: server_identifier,
+                    },
+                    requested_ip: dhcp_repr.your_ip // use the offered ip
+                });
+            }
+            (ClientState::Requesting(state), DhcpMessageType::Ack) => {
+                if let Some((config, renew_at, expires_at)) = Self::parse_ack(now, ip_repr, &dhcp_repr) {
+                    self.config_changed = true;
+                    self.state = ClientState::Renewing(RenewState{
+                        server: state.server,
+                        config,
+                        renew_at,
+                        expires_at,
+                    });
+                }
+            }
+            (ClientState::Renewing(state), DhcpMessageType::Ack) => {
+                if let Some((config, renew_at, expires_at)) = Self::parse_ack(now, ip_repr, &dhcp_repr) {
+                    state.renew_at = renew_at;
+                    state.expires_at = expires_at;
+                    if state.config != config {
+                        self.config_changed = true;
+                        state.config = config;
+                    }
+                }
+            }
+            _ => {
+                net_debug!("DHCP ignoring {:?}: unexpected in current state", dhcp_repr.message_type);
+            }
+        }
+
+        Ok(())
+    }
+
+    fn parse_ack(now: Instant, _ip_repr: &Ipv4Repr, dhcp_repr: &DhcpRepr) -> Option<(Config, Instant, Instant)> {
+        let subnet_mask = match dhcp_repr.subnet_mask {
+            Some(subnet_mask) => subnet_mask,
+            None => {
+                net_debug!("DHCP ignoring ACK because missing subnet_mask");
+                return None
+            }
+        };
+
+        let prefix_len = match IpAddress::Ipv4(subnet_mask).to_prefix_len() {
+            Some(prefix_len) => prefix_len,
+            None => {
+                net_debug!("DHCP ignoring ACK because subnet_mask is not a valid mask");
+                return None
+            }
+        };
+
+        if !dhcp_repr.your_ip.is_unicast() {
+            net_debug!("DHCP ignoring ACK because your_ip is not unicast");
+            return None
+        }
+
+        let lease_duration = dhcp_repr.lease_duration.unwrap_or(DEFAULT_LEASE_DURATION);
+
+        let config = Config{
+            address: Ipv4Cidr::new(dhcp_repr.your_ip, prefix_len),
+            router: dhcp_repr.router,
+            dns_servers: dhcp_repr.dns_servers.unwrap_or([None; 3]),
+        };
+
+        // RFC 2131 indicates clients should renew a lease halfway through its expiration.
+        let renew_at = now + Duration::from_secs((lease_duration / 2).into());
+        let expires_at = now + Duration::from_secs(lease_duration.into());
+
+        Some((config, renew_at, expires_at))
+    }
+
+    pub(crate) fn dispatch<F>(&mut self, now: Instant, ethernet_addr: EthernetAddress, ip_mtu: usize, emit: F) -> Result<()>
+            where F: FnOnce((Ipv4Repr, UdpRepr, DhcpRepr)) -> Result<()> {
+
+        // Worst case biggest IPv4 header length.
+        // 0x0f * 4 = 60 bytes.
+        const MAX_IPV4_HEADER_LEN: usize = 60;
+
+        // We don't directly increment transaction_id because sending the packet
+        // may fail. We only want to update state after succesfully sending.
+        let next_transaction_id = self.transaction_id + 1;
+
+        let mut dhcp_repr = DhcpRepr {
+            message_type: DhcpMessageType::Discover,
+            transaction_id: next_transaction_id,
+            client_hardware_address: ethernet_addr,
+            client_ip: Ipv4Address::UNSPECIFIED,
+            your_ip: Ipv4Address::UNSPECIFIED,
+            server_ip: Ipv4Address::UNSPECIFIED,
+            router: None,
+            subnet_mask: None,
+            relay_agent_ip: Ipv4Address::UNSPECIFIED,
+            broadcast: true,
+            requested_ip: None,
+            client_identifier: Some(ethernet_addr),
+            server_identifier: None,
+            parameter_request_list: Some(PARAMETER_REQUEST_LIST),
+            max_size: Some((ip_mtu - MAX_IPV4_HEADER_LEN - UDP_HEADER_LEN) as u16),
+            lease_duration: None,
+            dns_servers: None,
+        };
+
+        let udp_repr = UdpRepr {
+            src_port: DHCP_CLIENT_PORT,
+            dst_port: DHCP_SERVER_PORT,
+        };
+    
+        let mut ipv4_repr = Ipv4Repr {
+            src_addr: Ipv4Address::UNSPECIFIED,
+            dst_addr: Ipv4Address::BROADCAST,
+            protocol: IpProtocol::Udp,
+            payload_len: 0, // filled right before emit
+            hop_limit: 64,
+        };
+
+        match &mut self.state {
+            ClientState::Discovering(state) => {
+                if now < state.retry_at {
+                    return Err(Error::Exhausted)
+                }
+
+                // send packet
+                net_debug!("DHCP send DISCOVER to {}: {:?}", ipv4_repr.dst_addr, dhcp_repr);
+                ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len();
+                emit((ipv4_repr, udp_repr, dhcp_repr))?;
+
+                // Update state AFTER the packet has been successfully sent.
+                state.retry_at = now + DISCOVER_TIMEOUT;
+                self.transaction_id = next_transaction_id;
+                Ok(())
+            }
+            ClientState::Requesting(state) => {
+                if now < state.retry_at {
+                    return Err(Error::Exhausted)
+                }
+
+                if state.retry >= REQUEST_RETRIES {
+                    net_debug!("DHCP request retries exceeded, restarting discovery");
+                    self.reset();
+                    // return Ok so we get polled again
+                    return Ok(())
+                }
+
+                dhcp_repr.message_type = DhcpMessageType::Request;
+                dhcp_repr.broadcast = false;
+                dhcp_repr.requested_ip = Some(state.requested_ip);
+                dhcp_repr.server_identifier = Some(state.server.identifier);
+
+                net_debug!("DHCP send request to {}: {:?}", ipv4_repr.dst_addr, dhcp_repr);
+                ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len();
+                emit((ipv4_repr, udp_repr, dhcp_repr))?;
+
+                // Exponential backoff
+                state.retry_at = now + REQUEST_TIMEOUT;
+                state.retry += 1;
+
+                self.transaction_id = next_transaction_id;
+                Ok(())
+            }
+            ClientState::Renewing(state) => {
+                if state.expires_at <= now {
+                    net_debug!("DHCP lease expired");
+                    self.reset();
+                    // return Ok so we get polled again
+                    return Ok(())
+                }
+    
+                if now < state.renew_at {
+                    return Err(Error::Exhausted)
+                }
+
+                ipv4_repr.src_addr = state.config.address.address();
+                ipv4_repr.dst_addr = state.server.address;
+                dhcp_repr.message_type = DhcpMessageType::Request;
+                dhcp_repr.client_ip = state.config.address.address();
+                dhcp_repr.broadcast = false;
+
+                net_debug!("DHCP send renew to {}: {:?}", ipv4_repr.dst_addr, dhcp_repr);
+                ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len();
+                emit((ipv4_repr, udp_repr, dhcp_repr))?;
+        
+                // 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.
+                state.renew_at = now + MIN_RENEW_TIMEOUT.max((state.expires_at - now) / 2);
+
+                self.transaction_id = next_transaction_id;
+                Ok(())
+            }
+        }
+    }
+
+    /// Reset state and restart discovery phase.
+    ///
+    /// Use this to speed up acquisition of an address in a new
+    /// network if a link was down and it is now back up.
+    pub fn reset(&mut self) {
+        net_trace!("DHCP reset");
+        if let ClientState::Renewing(_) = &self.state {
+            self.config_changed = true;
+        }
+        self.state = ClientState::Discovering(DiscoverState{
+            retry_at: Instant::from_millis(0),
+        });
+    }
+
+    /// Query the socket for configuration changes.
+    ///
+    /// The socket has an internal "configuration changed" flag. If
+    /// set, this function returns the configuration and resets the flag.
+    pub fn poll(&mut self) -> Event<'_> {
+        if !self.config_changed {
+            Event::NoChange
+        } else if let ClientState::Renewing(state) = &self.state {
+            self.config_changed = false;
+            Event::Configured(&state.config)
+        } else {
+            self.config_changed = false;
+            Event::Deconfigured
+        }
+    }
+}
+
+impl<'a> Into<Socket<'a>> for Dhcpv4Socket {
+    fn into(self) -> Socket<'a> {
+        Socket::Dhcpv4(self)
+    }
+}

+ 11 - 0
src/socket/mod.rs

@@ -22,6 +22,8 @@ mod icmp;
 mod udp;
 #[cfg(feature = "socket-tcp")]
 mod tcp;
+#[cfg(feature = "socket-dhcpv4")]
+mod dhcpv4;
 mod set;
 mod ref_;
 
@@ -53,6 +55,9 @@ pub use self::tcp::{SocketBuffer as TcpSocketBuffer,
                     State as TcpState,
                     TcpSocket};
 
+#[cfg(feature = "socket-dhcpv4")]
+pub use self::dhcpv4::{Dhcpv4Socket, Config as Dhcpv4Config, Event as Dhcpv4Event};
+
 pub use self::set::{Set as SocketSet, Item as SocketSetItem, Handle as SocketHandle};
 pub use self::set::{Iter as SocketSetIter, IterMut as SocketSetIterMut};
 
@@ -91,6 +96,8 @@ pub enum Socket<'a> {
     Udp(UdpSocket<'a>),
     #[cfg(feature = "socket-tcp")]
     Tcp(TcpSocket<'a>),
+    #[cfg(feature = "socket-dhcpv4")]
+    Dhcpv4(Dhcpv4Socket),
 }
 
 macro_rules! dispatch_socket {
@@ -110,6 +117,8 @@ macro_rules! dispatch_socket {
             &$( $mut_ )* Socket::Udp(ref $( $mut_ )* $socket) => $code,
             #[cfg(feature = "socket-tcp")]
             &$( $mut_ )* Socket::Tcp(ref $( $mut_ )* $socket) => $code,
+            #[cfg(feature = "socket-dhcpv4")]
+            &$( $mut_ )* Socket::Dhcpv4(ref $( $mut_ )* $socket) => $code,
         }
     };
 }
@@ -169,3 +178,5 @@ from_socket!(IcmpSocket<'a>, Icmp);
 from_socket!(UdpSocket<'a>, Udp);
 #[cfg(feature = "socket-tcp")]
 from_socket!(TcpSocket<'a>, Tcp);
+#[cfg(feature = "socket-dhcpv4")]
+from_socket!(Dhcpv4Socket, Dhcpv4);

+ 6 - 12
src/socket/ref_.rs

@@ -1,13 +1,5 @@
 use core::ops::{Deref, DerefMut};
 
-#[cfg(feature = "socket-raw")]
-use crate::socket::RawSocket;
-#[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))]
-use crate::socket::IcmpSocket;
-#[cfg(feature = "socket-udp")]
-use crate::socket::UdpSocket;
-#[cfg(feature = "socket-tcp")]
-use crate::socket::TcpSocket;
 
 /// A trait for tracking a socket usage session.
 ///
@@ -20,13 +12,15 @@ pub trait Session {
 }
 
 #[cfg(feature = "socket-raw")]
-impl<'a> Session for RawSocket<'a> {}
+impl<'a> Session for crate::socket::RawSocket<'a> {}
 #[cfg(all(feature = "socket-icmp", any(feature = "proto-ipv4", feature = "proto-ipv6")))]
-impl<'a> Session for IcmpSocket<'a> {}
+impl<'a> Session for crate::socket::IcmpSocket<'a> {}
 #[cfg(feature = "socket-udp")]
-impl<'a> Session for UdpSocket<'a> {}
+impl<'a> Session for crate::socket::UdpSocket<'a> {}
 #[cfg(feature = "socket-tcp")]
-impl<'a> Session for TcpSocket<'a> {}
+impl<'a> Session for crate::socket::TcpSocket<'a> {}
+#[cfg(feature = "socket-dhcpv4")]
+impl Session for crate::socket::Dhcpv4Socket {}
 
 /// A smart pointer to a socket.
 ///

+ 3 - 0
src/socket/set.rs

@@ -156,6 +156,9 @@ impl<'a> Set<'a> {
                         } else {
                             socket.close()
                         },
+                    #[cfg(feature = "socket-dhcpv4")]
+                    Socket::Dhcpv4(_) =>
+                        may_remove = true,
                 }
             }
             if may_remove {

+ 3 - 0
src/wire/dhcpv4.rs

@@ -6,6 +6,9 @@ use crate::{Error, Result};
 use crate::wire::{EthernetAddress, Ipv4Address};
 use crate::wire::arp::Hardware;
 
+pub const SERVER_PORT: u16 = 67;
+pub const CLIENT_PORT: u16 = 68;
+
 const DHCP_MAGIC_NUMBER: u32 = 0x63825363;
 
 enum_with_unknown! {

+ 5 - 2
src/wire/mod.rs

@@ -211,7 +211,8 @@ pub use self::mld::{AddressRecord as MldAddressRecord,
                     Repr as MldRepr};
 
 pub use self::udp::{Packet as UdpPacket,
-                    Repr as UdpRepr};
+                    Repr as UdpRepr,
+                    HEADER_LEN as UDP_HEADER_LEN};
 
 pub use self::tcp::{SeqNumber as TcpSeqNumber,
                     Packet as TcpPacket,
@@ -222,4 +223,6 @@ pub use self::tcp::{SeqNumber as TcpSeqNumber,
 #[cfg(feature = "proto-dhcpv4")]
 pub use self::dhcpv4::{Packet as DhcpPacket,
                        Repr as DhcpRepr,
-                       MessageType as DhcpMessageType};
+                       MessageType as DhcpMessageType,
+                       CLIENT_PORT as DHCP_CLIENT_PORT,
+                       SERVER_PORT as DHCP_SERVER_PORT};

+ 2 - 0
src/wire/udp.rs

@@ -28,6 +28,8 @@ mod field {
     }
 }
 
+pub const HEADER_LEN: usize = field::CHECKSUM.end;
+
 #[allow(clippy::len_without_is_empty)]
 impl<T: AsRef<[u8]>> Packet<T> {
     /// Imbue a raw octet buffer with UDP packet structure.