Эх сурвалжийг харах

Merge pull request #459 from smoltcp-rs/dhcp-socket

DHCP refactor
Dario Nieuwenhuis 3 жил өмнө
parent
commit
7e937c49e4

+ 2 - 2
.github/workflows/test.yml

@@ -73,9 +73,9 @@ jobs:
         include:
           # defmt doesn't support 1.40
           - rust: stable
-            features: defmt medium-ip medium-ethernet proto-ipv6 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp async
+            features: defmt defmt-trace medium-ip medium-ethernet proto-ipv6 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp async
           - rust: nightly
-            features: defmt medium-ip medium-ethernet proto-ipv6 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp async
+            features: defmt defmt-trace medium-ip medium-ethernet proto-ipv6 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp async
 
     steps:
       - uses: actions/checkout@v2

+ 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"
 ]
 

+ 54 - 57
examples/dhcp_client.rs

@@ -3,12 +3,13 @@ mod utils;
 
 use std::collections::BTreeMap;
 use std::os::unix::io::AsRawFd;
-use smoltcp::phy::{Device, Medium, wait as phy_wait};
+use log::*;
+
+use smoltcp::{phy::{Device, Medium, wait as phy_wait}, time::Duration};
 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,61 @@ 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 mut dhcp_socket = Dhcpv4Socket::new();
+
+    // Set a ridiculously short max lease time to show DHCP renews work properly.
+    // This will cause the DHCP client to start renewing after 5 seconds, and give up the
+    // lease after 10 seconds if renew hasn't succeeded.
+    // IMPORTANT: This should be removed in production.
+    dhcp_socket.set_max_lease_duration(Some(Duration::from_secs(10)));
+
+    let dhcp_handle = sockets.add(dhcp_socket);
+
     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() {
+            None => {}
+            Some(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);
+                    }
                 }
             }
-        });
+            Some(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 - 428
src/dhcp/clientv4.rs

@@ -1,428 +0,0 @@
-use crate::{Result, Error};
-use crate::wire::{IpVersion, IpProtocol, IpEndpoint, IpAddress,
-           Ipv4Cidr, Ipv4Address, Ipv4Packet, Ipv4Repr,
-           UdpPacket, UdpRepr,
-           DhcpPacket, DhcpRepr, DhcpMessageType};
-use crate::wire::dhcpv4::field as dhcpv4_field;
-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 mut dhcp_payload_buf = [0; 320];
-    assert!(dhcp_repr.buffer_len() <= dhcp_payload_buf.len());
-    let dhcp_payload = &mut dhcp_payload_buf[0..dhcp_repr.buffer_len()];
-    {
-        let mut dhcp_packet = DhcpPacket::new_checked(&mut dhcp_payload[..])?;
-        dhcp_repr.emit(&mut dhcp_packet)?;
-    }
-
-    let udp_repr = UdpRepr {
-        src_port: UDP_CLIENT_PORT,
-        dst_port: endpoint.port,
-        payload: dhcp_payload,
-    };
-
-    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.buffer_len(),
-        hop_limit: 64,
-    };
-
-    let mut packet = raw_socket.send(
-        ipv4_repr.buffer_len() + udp_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(),
-                      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_repr.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};

+ 121 - 52
src/iface/interface.rs

@@ -263,9 +263,11 @@ pub(crate) enum IpPacket<'a> {
     #[cfg(feature = "socket-raw")]
     Raw((IpRepr, &'a [u8])),
     #[cfg(feature = "socket-udp")]
-    Udp((IpRepr, UdpRepr<'a>)),
+    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> {
@@ -280,16 +282,18 @@ impl<'a> IpPacket<'a> {
             #[cfg(feature = "socket-raw")]
             IpPacket::Raw((ip_repr, _)) => ip_repr.clone(),
             #[cfg(feature = "socket-udp")]
-            IpPacket::Udp((ip_repr, _)) => ip_repr.clone(),
+            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),
         }
     }
 
     pub(crate) fn emit_payload(&self, _ip_repr: IpRepr, payload: &mut [u8], caps: &DeviceCapabilities) {
         match self {
             #[cfg(feature = "proto-ipv4")]
-            IpPacket::Icmpv4((_, icmpv4_repr)) => 
+            IpPacket::Icmpv4((_, icmpv4_repr)) =>
                 icmpv4_repr.emit(&mut Icmpv4Packet::new_unchecked(payload), &caps.checksum),
             #[cfg(feature = "proto-igmp")]
             IpPacket::Igmp((_, igmp_repr)) =>
@@ -302,9 +306,11 @@ impl<'a> IpPacket<'a> {
             IpPacket::Raw((_, raw_packet)) =>
                 payload.copy_from_slice(raw_packet),
             #[cfg(feature = "socket-udp")]
-            IpPacket::Udp((_, udp_repr)) =>
+            IpPacket::Udp((_, udp_repr, inner_payload)) =>
                 udp_repr.emit(&mut UdpPacket::new_unchecked(payload),
-                              &_ip_repr.src_addr(), &_ip_repr.dst_addr(), &caps.checksum),
+                              &_ip_repr.src_addr(), &_ip_repr.dst_addr(),
+                              inner_payload.len(), |buf| buf.copy_from_slice(inner_payload),
+                              &caps.checksum),
             #[cfg(feature = "socket-tcp")]
             IpPacket::Tcp((_, mut tcp_repr)) => {
                 // This is a terrible hack to make TCP performance more acceptable on systems
@@ -329,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),
         }
     }
 }
@@ -660,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")]
@@ -685,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) {
@@ -880,7 +900,7 @@ impl<'a> InterfaceInner<'a> {
                         self.neighbor_cache.as_mut().unwrap().fill(ip_addr, eth_frame.src_addr(), timestamp);
                     }
                 }
-        
+
                 self.process_ipv6(sockets, timestamp, &ipv6_packet).map(|o| o.map(EthernetPacket::Ip))
             }
             // Drop all other traffic.
@@ -1061,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) {
@@ -1439,11 +1487,12 @@ impl<'a> InterfaceInner<'a> {
         let udp_packet = UdpPacket::new_checked(ip_payload)?;
         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();
 
         for mut udp_socket in sockets.iter_mut().filter_map(UdpSocket::downcast) {
             if !udp_socket.accepts(&ip_repr, &udp_repr) { continue }
 
-            match udp_socket.process(&ip_repr, &udp_repr) {
+            match udp_socket.process(&ip_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.
@@ -1787,6 +1836,13 @@ mod test {
     use crate::socket::SocketSet;
     use crate::phy::{Loopback, ChecksumCapabilities};
 
+    #[allow(unused)]
+    fn fill_slice(s: &mut [u8], val: u8) {
+        for x in s.iter_mut() {
+            *x = val
+        }
+    }
+
     fn create_loopback<'a>() -> (Interface<'a, Loopback>, SocketSet<'a>) {
         #[cfg(feature = "medium-ethernet")]
         return create_loopback_ethernet();
@@ -2035,20 +2091,21 @@ mod test {
         let udp_repr = UdpRepr {
             src_port: 67,
             dst_port: 68,
-            payload:  &UDP_PAYLOAD
         };
 
         let ip_repr = IpRepr::Ipv4(Ipv4Repr {
             src_addr:    Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
             dst_addr:    Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
             protocol:    IpProtocol::Udp,
-            payload_len: udp_repr.buffer_len(),
+            payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
             hop_limit:   64
         });
 
         // Emit the representations to a packet
         udp_repr.emit(&mut packet_unicast, &ip_repr.src_addr(),
-                      &ip_repr.dst_addr(), &ChecksumCapabilities::default());
+                      &ip_repr.dst_addr(),
+                      UDP_PAYLOAD.len(), |buf| buf.copy_from_slice( &UDP_PAYLOAD),
+                      &ChecksumCapabilities::default());
 
         let data = packet_unicast.into_inner();
 
@@ -2060,7 +2117,7 @@ mod test {
                 src_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
                 dst_addr: Ipv4Address([0x7f, 0x00, 0x00, 0x01]),
                 protocol: IpProtocol::Udp,
-                payload_len: udp_repr.buffer_len(),
+                payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
                 hop_limit: 64
             },
             data: &data
@@ -2085,13 +2142,14 @@ mod test {
             src_addr:    Ipv4Address([0x7f, 0x00, 0x00, 0x02]),
             dst_addr:    Ipv4Address::BROADCAST,
             protocol:    IpProtocol::Udp,
-            payload_len: udp_repr.buffer_len(),
+            payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
             hop_limit:   64
         });
 
         // Emit the representations to a packet
         udp_repr.emit(&mut packet_broadcast, &ip_repr.src_addr(),
                       &IpAddress::Ipv4(Ipv4Address::BROADCAST),
+                      UDP_PAYLOAD.len(), |buf| buf.copy_from_slice( &UDP_PAYLOAD),
                       &ChecksumCapabilities::default());
 
         // Ensure that the port unreachable error does not trigger an
@@ -2129,7 +2187,6 @@ mod test {
         let udp_repr = UdpRepr {
             src_port: 67,
             dst_port: 68,
-            payload:  &UDP_PAYLOAD
         };
 
         #[cfg(feature = "proto-ipv6")]
@@ -2137,7 +2194,7 @@ mod test {
             src_addr:    src_ip,
             dst_addr:    Ipv6Address::LINK_LOCAL_ALL_NODES,
             next_header: IpProtocol::Udp,
-            payload_len: udp_repr.buffer_len(),
+            payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
             hop_limit:   0x40
         });
         #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))]
@@ -2145,7 +2202,7 @@ mod test {
             src_addr:    src_ip,
             dst_addr:    Ipv4Address::BROADCAST,
             protocol:    IpProtocol::Udp,
-            payload_len: udp_repr.buffer_len(),
+            payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(),
             hop_limit:   0x40
         });
 
@@ -2158,6 +2215,7 @@ mod test {
         }
 
         udp_repr.emit(&mut packet, &ip_repr.src_addr(), &ip_repr.dst_addr(),
+                      UDP_PAYLOAD.len(), |buf| buf.copy_from_slice( &UDP_PAYLOAD),
                       &ChecksumCapabilities::default());
 
         // Packet should be handled by bound UDP socket
@@ -2260,18 +2318,19 @@ mod test {
         let udp_repr = UdpRepr {
             src_port: 67,
             dst_port: 68,
-            payload: &[0x2a; MAX_PAYLOAD_LEN]
         };
-        let mut bytes = vec![0xff; udp_repr.buffer_len()];
+        let mut bytes = vec![0xff; udp_repr.header_len() + MAX_PAYLOAD_LEN];
         let mut packet = UdpPacket::new_unchecked(&mut bytes[..]);
-        udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(), &ChecksumCapabilities::default());
+        udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(),
+                      MAX_PAYLOAD_LEN, |buf| fill_slice(buf, 0x2a),
+                      &ChecksumCapabilities::default());
         #[cfg(all(feature = "proto-ipv4", not(feature = "proto-ipv6")))]
         let ip_repr = Ipv4Repr {
             src_addr: src_addr,
             dst_addr: dst_addr,
             protocol: IpProtocol::Udp,
             hop_limit: 64,
-            payload_len: udp_repr.buffer_len()
+            payload_len: udp_repr.header_len() + MAX_PAYLOAD_LEN
         };
         #[cfg(feature = "proto-ipv6")]
         let ip_repr = Ipv6Repr {
@@ -2279,7 +2338,7 @@ mod test {
             dst_addr: dst_addr,
             next_header: IpProtocol::Udp,
             hop_limit: 64,
-            payload_len: udp_repr.buffer_len()
+            payload_len: udp_repr.header_len() + MAX_PAYLOAD_LEN
         };
         let payload = packet.into_inner();
 
@@ -2619,7 +2678,7 @@ mod test {
             recv_all(&mut iface, timestamp)
                 .iter()
                 .filter_map(|frame| {
-                    
+
                     let ipv4_packet = match caps.medium {
                         #[cfg(feature = "medium-ethernet")]
                         Medium::Ethernet => {
@@ -2666,11 +2725,11 @@ mod test {
         // General query
         let timestamp = Instant::now();
         const GENERAL_QUERY_BYTES: &[u8] = &[
-            0x46, 0xc0, 0x00, 0x24, 0xed, 0xb4, 0x00, 0x00, 
-            0x01, 0x02, 0x47, 0x43, 0xac, 0x16, 0x63, 0x04, 
-            0xe0, 0x00, 0x00, 0x01, 0x94, 0x04, 0x00, 0x00, 
-            0x11, 0x64, 0xec, 0x8f, 0x00, 0x00, 0x00, 0x00, 
-            0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+            0x46, 0xc0, 0x00, 0x24, 0xed, 0xb4, 0x00, 0x00,
+            0x01, 0x02, 0x47, 0x43, 0xac, 0x16, 0x63, 0x04,
+            0xe0, 0x00, 0x00, 0x01, 0x94, 0x04, 0x00, 0x00,
+            0x11, 0x64, 0xec, 0x8f, 0x00, 0x00, 0x00, 0x00,
+            0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00
         ];
         {
@@ -2722,25 +2781,28 @@ mod test {
         let src_addr = Ipv4Address([127, 0, 0, 2]);
         let dst_addr = Ipv4Address([127, 0, 0, 1]);
 
+        const PAYLOAD_LEN: usize = 10;
+
         let udp_repr = UdpRepr {
             src_port: 67,
             dst_port: 68,
-            payload: &[0x2a; 10]
         };
-        let mut bytes = vec![0xff; udp_repr.buffer_len()];
+        let mut bytes = vec![0xff; udp_repr.header_len() + PAYLOAD_LEN];
         let mut packet = UdpPacket::new_unchecked(&mut bytes[..]);
-        udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(), &ChecksumCapabilities::default());
+        udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(),
+                      PAYLOAD_LEN, |buf| fill_slice(buf, 0x2a),
+                      &ChecksumCapabilities::default());
         let ipv4_repr = Ipv4Repr {
             src_addr: src_addr,
             dst_addr: dst_addr,
             protocol: IpProtocol::Udp,
             hop_limit: 64,
-            payload_len: udp_repr.buffer_len()
+            payload_len: udp_repr.header_len() + PAYLOAD_LEN
         };
 
         // Emit to frame
         let mut bytes = vec![0u8;
-            ipv4_repr.buffer_len() + udp_repr.buffer_len()
+            ipv4_repr.buffer_len() + udp_repr.header_len() + PAYLOAD_LEN
         ];
         let frame = {
             ipv4_repr.emit(
@@ -2751,6 +2813,7 @@ mod test {
                     &mut bytes[ipv4_repr.buffer_len()..]),
                 &src_addr.into(),
                 &dst_addr.into(),
+                PAYLOAD_LEN, |buf| fill_slice(buf, 0x2a),
                 &ChecksumCapabilities::default());
             Ipv4Packet::new_unchecked(&bytes)
         };
@@ -2776,25 +2839,28 @@ mod test {
         let src_addr = Ipv4Address([127, 0, 0, 2]);
         let dst_addr = Ipv4Address([127, 0, 0, 1]);
 
+        const PAYLOAD_LEN: usize = 49; // 49 > 48, hence packet will be truncated
+
         let udp_repr = UdpRepr {
             src_port: 67,
             dst_port: 68,
-            payload: &[0x2a; 49] // 49 > 48, hence packet will be truncated
         };
-        let mut bytes = vec![0xff; udp_repr.buffer_len()];
+        let mut bytes = vec![0xff; udp_repr.header_len() + PAYLOAD_LEN];
         let mut packet = UdpPacket::new_unchecked(&mut bytes[..]);
-        udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(), &ChecksumCapabilities::default());
+        udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(),
+                      PAYLOAD_LEN, |buf| fill_slice(buf, 0x2a),
+                      &ChecksumCapabilities::default());
         let ipv4_repr = Ipv4Repr {
             src_addr: src_addr,
             dst_addr: dst_addr,
             protocol: IpProtocol::Udp,
             hop_limit: 64,
-            payload_len: udp_repr.buffer_len()
+            payload_len: udp_repr.header_len() + PAYLOAD_LEN
         };
 
         // Emit to frame
         let mut bytes = vec![0u8;
-            ipv4_repr.buffer_len() + udp_repr.buffer_len()
+            ipv4_repr.buffer_len() + udp_repr.header_len() + PAYLOAD_LEN
         ];
         let frame = {
             ipv4_repr.emit(
@@ -2802,9 +2868,10 @@ mod test {
                 &ChecksumCapabilities::default());
             udp_repr.emit(
                 &mut UdpPacket::new_unchecked(
-                    &mut bytes[ipv4_repr.buffer_len()..]),
+                &mut bytes[ipv4_repr.buffer_len()..]),
                 &src_addr.into(),
                 &dst_addr.into(),
+                PAYLOAD_LEN, |buf| fill_slice(buf, 0x2a),
                 &ChecksumCapabilities::default());
             Ipv4Packet::new_unchecked(&bytes)
         };
@@ -2812,7 +2879,7 @@ mod test {
         let frame = iface.inner.process_ipv4(&mut socket_set, Instant::from_millis(0), &frame);
 
         // because the packet could not be handled we should send an Icmp message
-        assert!(match frame {  
+        assert!(match frame {
             Ok(Some(IpPacket::Icmpv4(_))) => true,
             _ => false,
         });
@@ -2853,22 +2920,23 @@ mod test {
         let udp_repr = UdpRepr {
             src_port: 67,
             dst_port: 68,
-            payload: &UDP_PAYLOAD
         };
-        let mut bytes = vec![0xff; udp_repr.buffer_len()];
+        let mut bytes = vec![0xff; udp_repr.header_len() + UDP_PAYLOAD.len()];
         let mut packet = UdpPacket::new_unchecked(&mut bytes[..]);
-        udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(), &ChecksumCapabilities::default());
+        udp_repr.emit(&mut packet, &src_addr.into(), &dst_addr.into(),
+                      UDP_PAYLOAD.len(), |buf| buf.copy_from_slice( &UDP_PAYLOAD),
+                      &ChecksumCapabilities::default());
         let ipv4_repr = Ipv4Repr {
             src_addr: src_addr,
             dst_addr: dst_addr,
             protocol: IpProtocol::Udp,
             hop_limit: 64,
-            payload_len: udp_repr.buffer_len()
+            payload_len: udp_repr.header_len() + UDP_PAYLOAD.len()
         };
 
         // Emit to frame
         let mut bytes = vec![0u8;
-            ipv4_repr.buffer_len() + udp_repr.buffer_len()
+            ipv4_repr.buffer_len() + udp_repr.header_len() + UDP_PAYLOAD.len()
         ];
         let frame = {
             ipv4_repr.emit(
@@ -2876,9 +2944,10 @@ mod test {
                 &ChecksumCapabilities::default());
             udp_repr.emit(
                 &mut UdpPacket::new_unchecked(
-                    &mut bytes[ipv4_repr.buffer_len()..]),
+                &mut bytes[ipv4_repr.buffer_len()..]),
                 &src_addr.into(),
                 &dst_addr.into(),
+                UDP_PAYLOAD.len(), |buf| buf.copy_from_slice( &UDP_PAYLOAD),
                 &ChecksumCapabilities::default());
             Ipv4Packet::new_unchecked(&bytes)
         };

+ 18 - 0
src/iface/route.rs

@@ -106,6 +106,24 @@ impl<'a> Routes<'a> {
         }
     }
 
+    /// Remove the default ipv4 gateway
+    ///
+    /// On success, returns the previous default route, if any.
+    #[cfg(feature = "proto-ipv4")]
+    pub fn remove_default_ipv4_route(&mut self) -> Option<Route> {
+        let cidr = IpCidr::new(IpAddress::v4(0, 0, 0, 0), 0);
+        self.storage.remove(&cidr)
+    }
+
+    /// Remove the default ipv6 gateway
+    ///
+    /// On success, returns the previous default route, if any.
+    #[cfg(feature = "proto-ipv6")]
+    pub fn remove_default_ipv6_route(&mut self) -> Option<Route> {
+        let cidr = IpCidr::new(IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 0), 0);
+        self.storage.remove(&cidr)
+    }
+
     pub(crate) fn lookup(&self, addr: &IpAddress, timestamp: Instant) ->
             Option<IpAddress> {
         assert!(addr.is_unicast());

+ 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)]

+ 0 - 1
src/macros.rs

@@ -12,7 +12,6 @@ macro_rules! net_log {
 }
 
 #[cfg(not(any(feature = "log", feature = "defmt")))]
-#[macro_use]
 macro_rules! net_log {
     ($level:ident, $($arg:expr),*) => { $( let _ = $arg; )* }
 }

+ 458 - 0
src/socket/dhcpv4.rs

@@ -0,0 +1,458 @@
+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, DHCP_MAX_DNS_SERVER_COUNT};
+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);
+
+// timeout doubles every 2 tries.
+// total time 5 + 5 + 10 + 10 + 20 = 50s
+const REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
+const REQUEST_RETRIES: u16 = 5;
+
+const MIN_RENEW_TIMEOUT: Duration = Duration::from_secs(60);
+
+const DEFAULT_LEASE_DURATION: Duration = Duration::from_secs(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>; DHCP_MAX_DNS_SERVER_COUNT],
+}
+
+/// 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 network 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> {
+    /// 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,
+
+    /// Max lease duration. If set, it sets a maximum cap to the server-provided lease duration.
+    /// Useful to react faster to IP configuration changes and to test whether renews work correctly.
+    max_lease_duration: Option<Duration>,
+}
+
+/// 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,
+            max_lease_duration: None,
+        }
+    }
+
+    pub fn max_lease_duration(&self) -> Option<Duration> {
+        self.max_lease_duration
+    }
+
+    pub fn set_max_lease_duration(&mut self, max_lease_duration: Option<Duration>) {
+        self.max_lease_duration = max_lease_duration;
+    }
+
+    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;
+
+        // This is enforced in interface.rs.
+        assert!(repr.src_port == DHCP_SERVER_PORT && repr.dst_port == DHCP_CLIENT_PORT);
+
+        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, &dhcp_repr, self.max_lease_duration) {
+                    self.config_changed = true;
+                    self.state = ClientState::Renewing(RenewState{
+                        server: state.server,
+                        config,
+                        renew_at,
+                        expires_at,
+                    });
+                }
+            }
+            (ClientState::Requesting(_), DhcpMessageType::Nak) => {
+                self.reset();
+            }
+            (ClientState::Renewing(state), DhcpMessageType::Ack) => {
+                if let Some((config, renew_at, expires_at)) = Self::parse_ack(now, &dhcp_repr, self.max_lease_duration) {
+                    state.renew_at = renew_at;
+                    state.expires_at = expires_at;
+                    if state.config != config {
+                        self.config_changed = true;
+                        state.config = config;
+                    }
+                }
+            }
+            (ClientState::Renewing(_), DhcpMessageType::Nak) => {
+                self.reset();
+            }
+            _ => {
+                net_debug!("DHCP ignoring {:?}: unexpected in current state", dhcp_repr.message_type);
+            }
+        }
+
+        Ok(())
+    }
+
+    fn parse_ack(now: Instant, dhcp_repr: &DhcpRepr, max_lease_duration: Option<Duration>) -> 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 mut lease_duration = dhcp_repr.lease_duration.map(|d| Duration::from_secs(d as _)).unwrap_or(DEFAULT_LEASE_DURATION);
+        if let Some(max_lease_duration) = max_lease_duration {
+            lease_duration = lease_duration.min(max_lease_duration);
+        }
+
+        // Cleanup the DNS servers list, keeping only unicasts/
+        // TP-Link TD-W8970 sends 0.0.0.0 as second DNS server if there's only one configured :(
+        let mut dns_servers = [None; DHCP_MAX_DNS_SERVER_COUNT];
+        if let Some(received) = dhcp_repr.dns_servers {
+            let mut i = 0;
+            for addr in received.iter() {
+                if let Some(addr) = addr{
+                    if addr.is_unicast() {
+                        // This can never be out-of-bounds since both arrays have length DHCP_MAX_DNS_SERVER_COUNT
+                        dns_servers[i] = Some(*addr);
+                        i += 1;
+                    }
+                }
+            }
+        }
+        let config = Config{
+            address: Ipv4Cidr::new(dhcp_repr.your_ip, prefix_len),
+            router: dhcp_repr.router,
+            dns_servers: dns_servers
+        };
+
+        // RFC 2131 indicates clients should renew a lease halfway through its expiration.
+        let renew_at = now + lease_duration / 2;
+        let expires_at = now + lease_duration;
+
+        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: Double every 2 retries.
+                state.retry_at = now + (REQUEST_TIMEOUT << (state.retry as u32 / 2));
+                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) -> Option<Event<'_>> {
+        if !self.config_changed {
+            None
+        } else if let ClientState::Renewing(state) = &self.state {
+            self.config_changed = false;
+            Some(Event::Configured(&state.config))
+        } else {
+            self.config_changed = false;
+            Some(Event::Deconfigured)
+        }
+    }
+}
+
+impl<'a> Into<Socket<'a>> for Dhcpv4Socket {
+    fn into(self) -> Socket<'a> {
+        Socket::Dhcpv4(self)
+    }
+}

+ 21 - 8
src/socket/icmp.rs

@@ -94,12 +94,12 @@ impl<'a> IcmpSocket<'a> {
     ///
     /// The waker is woken on state changes that might affect the return value
     /// of `recv` method calls, such as receiving data, or the socket closing.
-    /// 
+    ///
     /// Notes:
     ///
     /// - Only one waker can be registered at a time. If another waker was previously registered,
     ///   it is overwritten and will no longer be woken.
-    /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. 
+    /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
     /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `recv` has
     ///   necessarily changed.
     #[cfg(feature = "async")]
@@ -112,12 +112,12 @@ impl<'a> IcmpSocket<'a> {
     /// The waker is woken on state changes that might affect the return value
     /// of `send` method calls, such as space becoming available in the transmit
     /// buffer, or the socket closing.
-    /// 
+    ///
     /// Notes:
     ///
     /// - Only one waker can be registered at a time. If another waker was previously registered,
     ///   it is overwritten and will no longer be woken.
-    /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. 
+    /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
     /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `send` has
     ///   necessarily changed.
     #[cfg(feature = "async")]
@@ -440,7 +440,7 @@ impl<'a> IcmpSocket<'a> {
                 _ => Err(Error::Unaddressable)
             }
         })?;
-        
+
         #[cfg(feature = "async")]
         self.tx_waker.wake();
 
@@ -482,8 +482,9 @@ mod tests_common {
     pub static UDP_REPR: UdpRepr = UdpRepr {
         src_port: 53,
         dst_port: 9090,
-        payload:  &[0xff; 10]
     };
+
+    pub static UDP_PAYLOAD: &[u8] = &[0xff; 10];
 }
 
 #[cfg(all(test, feature = "proto-ipv4"))]
@@ -644,7 +645,13 @@ mod test_ipv4 {
 
         let mut bytes = [0xff; 18];
         let mut packet = UdpPacket::new_unchecked(&mut bytes);
-        UDP_REPR.emit(&mut packet, &REMOTE_IPV4.into(), &LOCAL_IPV4.into(), &checksum);
+        UDP_REPR.emit(
+            &mut packet,
+            &REMOTE_IPV4.into(),
+            &LOCAL_IPV4.into(),
+            UDP_PAYLOAD.len(),
+            |buf| buf.copy_from_slice(UDP_PAYLOAD),
+            &checksum);
 
         let data = &packet.into_inner()[..];
 
@@ -843,7 +850,13 @@ mod test_ipv6 {
 
         let mut bytes = [0xff; 18];
         let mut packet = UdpPacket::new_unchecked(&mut bytes);
-        UDP_REPR.emit(&mut packet, &REMOTE_IPV6.into(), &LOCAL_IPV6.into(), &checksum);
+        UDP_REPR.emit(
+            &mut packet,
+            &REMOTE_IPV6.into(),
+            &LOCAL_IPV6.into(),
+            UDP_PAYLOAD.len(),
+            |buf| buf.copy_from_slice(UDP_PAYLOAD),
+            &checksum);
 
         let data = &packet.into_inner()[..];
 

+ 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 {

+ 23 - 23
src/socket/udp.rs

@@ -54,12 +54,12 @@ impl<'a> UdpSocket<'a> {
     ///
     /// The waker is woken on state changes that might affect the return value
     /// of `recv` method calls, such as receiving data, or the socket closing.
-    /// 
+    ///
     /// Notes:
     ///
     /// - Only one waker can be registered at a time. If another waker was previously registered,
     ///   it is overwritten and will no longer be woken.
-    /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. 
+    /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
     /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `recv` has
     ///   necessarily changed.
     #[cfg(feature = "async")]
@@ -72,12 +72,12 @@ impl<'a> UdpSocket<'a> {
     /// The waker is woken on state changes that might affect the return value
     /// of `send` method calls, such as space becoming available in the transmit
     /// buffer, or the socket closing.
-    /// 
+    ///
     /// Notes:
     ///
     /// - Only one waker can be registered at a time. If another waker was previously registered,
     ///   it is overwritten and will no longer be woken.
-    /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. 
+    /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes.
     /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `send` has
     ///   necessarily changed.
     #[cfg(feature = "async")]
@@ -277,13 +277,13 @@ impl<'a> UdpSocket<'a> {
         true
     }
 
-    pub(crate) fn process(&mut self, ip_repr: &IpRepr, repr: &UdpRepr) -> Result<()> {
+    pub(crate) fn process(&mut self, ip_repr: &IpRepr, repr: &UdpRepr, payload: &[u8]) -> Result<()> {
         debug_assert!(self.accepts(ip_repr, repr));
 
-        let size = repr.payload.len();
+        let size = payload.len();
 
         let endpoint = IpEndpoint { addr: ip_repr.src_addr(), port: repr.src_port };
-        self.rx_buffer.enqueue(size, endpoint)?.copy_from_slice(repr.payload);
+        self.rx_buffer.enqueue(size, endpoint)?.copy_from_slice(payload);
 
         net_trace!("{}:{}:{}: receiving {} octets",
                    self.meta.handle, self.endpoint,
@@ -296,7 +296,7 @@ impl<'a> UdpSocket<'a> {
     }
 
     pub(crate) fn dispatch<F>(&mut self, emit: F) -> Result<()>
-            where F: FnOnce((IpRepr, UdpRepr)) -> Result<()> {
+            where F: FnOnce((IpRepr, UdpRepr, &[u8])) -> Result<()> {
         let handle    = self.handle();
         let endpoint  = self.endpoint;
         let hop_limit = self.hop_limit.unwrap_or(64);
@@ -309,16 +309,15 @@ impl<'a> UdpSocket<'a> {
             let repr = UdpRepr {
                 src_port: endpoint.port,
                 dst_port: remote_endpoint.port,
-                payload:  payload_buf,
             };
             let ip_repr = IpRepr::Unspecified {
                 src_addr:    endpoint.addr,
                 dst_addr:    remote_endpoint.addr,
                 protocol:    IpProtocol::Udp,
-                payload_len: repr.buffer_len(),
+                payload_len: repr.header_len() + payload_buf.len(),
                 hop_limit:   hop_limit,
             };
-            emit((ip_repr, repr))
+            emit((ip_repr, repr, payload_buf))
         })?;
 
         #[cfg(feature = "async")]
@@ -379,15 +378,15 @@ mod test {
     const LOCAL_UDP_REPR: UdpRepr = UdpRepr {
         src_port: LOCAL_PORT,
         dst_port: REMOTE_PORT,
-        payload: b"abcdef"
     };
 
     const REMOTE_UDP_REPR: UdpRepr = UdpRepr {
         src_port: REMOTE_PORT,
         dst_port: LOCAL_PORT,
-        payload: b"abcdef"
     };
 
+    const PAYLOAD: &[u8] = b"abcdef";
+
     fn remote_ip_repr() -> IpRepr {
         match (MOCK_IP_ADDR_2, MOCK_IP_ADDR_1) {
             #[cfg(feature = "proto-ipv4")]
@@ -457,16 +456,18 @@ mod test {
         assert_eq!(socket.send_slice(b"123456", REMOTE_END), Err(Error::Exhausted));
         assert!(!socket.can_send());
 
-        assert_eq!(socket.dispatch(|(ip_repr, udp_repr)| {
+        assert_eq!(socket.dispatch(|(ip_repr, udp_repr, payload)| {
             assert_eq!(ip_repr, LOCAL_IP_REPR);
             assert_eq!(udp_repr, LOCAL_UDP_REPR);
+            assert_eq!(payload, PAYLOAD);
             Err(Error::Unaddressable)
         }), Err(Error::Unaddressable));
         assert!(!socket.can_send());
 
-        assert_eq!(socket.dispatch(|(ip_repr, udp_repr)| {
+        assert_eq!(socket.dispatch(|(ip_repr, udp_repr, payload)| {
             assert_eq!(ip_repr, LOCAL_IP_REPR);
             assert_eq!(udp_repr, LOCAL_UDP_REPR);
+            assert_eq!(payload, PAYLOAD);
             Ok(())
         }), Ok(()));
         assert!(socket.can_send());
@@ -481,12 +482,12 @@ mod test {
         assert_eq!(socket.recv(), Err(Error::Exhausted));
 
         assert!(socket.accepts(&remote_ip_repr(), &REMOTE_UDP_REPR));
-        assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR),
+        assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR, PAYLOAD),
                    Ok(()));
         assert!(socket.can_recv());
 
         assert!(socket.accepts(&remote_ip_repr(), &REMOTE_UDP_REPR));
-        assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR),
+        assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR, PAYLOAD),
                    Err(Error::Exhausted));
         assert_eq!(socket.recv(), Ok((&b"abcdef"[..], REMOTE_END)));
         assert!(!socket.can_recv());
@@ -499,7 +500,7 @@ mod test {
 
         assert_eq!(socket.peek(), Err(Error::Exhausted));
 
-        assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR),
+        assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR, PAYLOAD),
                    Ok(()));
         assert_eq!(socket.peek(), Ok((&b"abcdef"[..], &REMOTE_END)));
         assert_eq!(socket.recv(), Ok((&b"abcdef"[..], REMOTE_END)));
@@ -512,7 +513,7 @@ mod test {
         assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
 
         assert!(socket.accepts(&remote_ip_repr(), &REMOTE_UDP_REPR));
-        assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR),
+        assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR, PAYLOAD),
                    Ok(()));
 
         let mut slice = [0; 4];
@@ -525,7 +526,7 @@ mod test {
         let mut socket = socket(buffer(1), buffer(0));
         assert_eq!(socket.bind(LOCAL_PORT), Ok(()));
 
-        assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR),
+        assert_eq!(socket.process(&remote_ip_repr(), &REMOTE_UDP_REPR, PAYLOAD),
                    Ok(()));
 
         let mut slice = [0; 4];
@@ -543,7 +544,7 @@ mod test {
 
         s.set_hop_limit(Some(0x2a));
         assert_eq!(s.send_slice(b"abcdef", REMOTE_END), Ok(()));
-        assert_eq!(s.dispatch(|(ip_repr, _)| {
+        assert_eq!(s.dispatch(|(ip_repr, _, _)| {
             assert_eq!(ip_repr, IpRepr::Unspecified{
                 src_addr: MOCK_IP_ADDR_1,
                 dst_addr: MOCK_IP_ADDR_2,
@@ -619,9 +620,8 @@ mod test {
         let repr = UdpRepr {
             src_port: REMOTE_PORT,
             dst_port: LOCAL_PORT,
-            payload: &[]
         };
-        assert_eq!(socket.process(&remote_ip_repr(), &repr), Ok(()));
+        assert_eq!(socket.process(&remote_ip_repr(), &repr, &[]), Ok(()));
         assert_eq!(socket.recv(), Ok((&[][..], REMOTE_END)));
     }
 }

+ 28 - 0
src/time.rs

@@ -232,6 +232,34 @@ impl ops::DivAssign<u32> for Duration {
     }
 }
 
+impl ops::Shl<u32> for Duration {
+    type Output = Duration;
+    
+    fn shl(self, rhs: u32) -> Duration {
+        Duration::from_millis(self.millis << rhs)
+    }
+}
+
+impl ops::ShlAssign<u32> for Duration {
+    fn shl_assign(&mut self, rhs: u32) {
+        self.millis <<= rhs;
+    }
+}
+
+impl ops::Shr<u32> for Duration {
+    type Output = Duration;
+    
+    fn shr(self, rhs: u32) -> Duration {
+        Duration::from_millis(self.millis >> rhs)
+    }
+}
+
+impl ops::ShrAssign<u32> for Duration {
+    fn shr_assign(&mut self, rhs: u32) {
+        self.millis >>= rhs;
+    }
+}
+
 impl From<::core::time::Duration> for Duration {
     fn from(other: ::core::time::Duration) -> Duration {
         Duration::from_millis(

+ 6 - 2
src/wire/dhcpv4.rs

@@ -6,6 +6,10 @@ 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;
+pub const MAX_DNS_SERVER_COUNT: usize = 3;
+
 const DHCP_MAGIC_NUMBER: u32 = 0x63825363;
 
 enum_with_unknown! {
@@ -683,7 +687,7 @@ pub struct Repr<'a> {
     /// the client is interested in.
     pub parameter_request_list: Option<&'a [u8]>,
     /// DNS servers
-    pub dns_servers: Option<[Option<Ipv4Address>; 3]>,
+    pub dns_servers: Option<[Option<Ipv4Address>; MAX_DNS_SERVER_COUNT]>,
     /// The maximum size dhcp packet the interface can receive
     pub max_size: Option<u16>,
     /// The DHCP IP lease duration, specified in seconds.
@@ -777,7 +781,7 @@ impl<'a> Repr<'a> {
                     parameter_request_list = Some(data);
                 }
                 DhcpOption::Other {kind: field::OPT_DOMAIN_NAME_SERVER, data} => {
-                    let mut servers = [None; 3];
+                    let mut servers = [None; MAX_DNS_SERVER_COUNT];
                     for (server, chunk) in servers.iter_mut().zip(data.chunks(4)) {
                         *server = Some(Ipv4Address::from_bytes(chunk));
                     }

+ 1 - 1
src/wire/ip.rs

@@ -887,7 +887,7 @@ pub fn pretty_print_ip_payload<T: Into<Repr>>(f: &mut fmt::Formatter, indent: &m
                                          &repr.dst_addr(), &checksum_caps) {
                         Err(err) => write!(f, "{}{} ({})", indent, udp_packet, err),
                         Ok(udp_repr) => {
-                            write!(f, "{}{}", indent, udp_repr)?;
+                            write!(f, "{}{} len={}", indent, udp_repr, udp_packet.payload().len())?;
                             let valid = udp_packet.verify_checksum(&repr.src_addr(),
                                                                    &repr.dst_addr());
                             format_checksum(f, valid)

+ 6 - 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,7 @@ 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,
+                       MAX_DNS_SERVER_COUNT as DHCP_MAX_DNS_SERVER_COUNT};

+ 21 - 19
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.
@@ -55,13 +57,13 @@ impl<T: AsRef<[u8]>> Packet<T> {
     /// [set_len]: #method.set_len
     pub fn check_len(&self) -> Result<()> {
         let buffer_len = self.buffer.as_ref().len();
-        if buffer_len < field::CHECKSUM.end {
+        if buffer_len < HEADER_LEN {
             Err(Error::Truncated)
         } else {
             let field_len = self.len() as usize;
             if buffer_len < field_len {
                 Err(Error::Truncated)
-            } else if field_len < field::CHECKSUM.end {
+            } else if field_len < HEADER_LEN {
                 Err(Error::Malformed)
             } else {
                 Ok(())
@@ -201,16 +203,15 @@ impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
 /// A high-level representation of an User Datagram Protocol packet.
 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct Repr<'a> {
+pub struct Repr {
     pub src_port: u16,
     pub dst_port: u16,
-    pub payload:  &'a [u8]
 }
 
-impl<'a> Repr<'a> {
+impl Repr {
     /// Parse an User Datagram Protocol packet and return a high-level representation.
-    pub fn parse<T>(packet: &Packet<&'a T>, src_addr: &IpAddress, dst_addr: &IpAddress,
-                    checksum_caps: &ChecksumCapabilities) -> Result<Repr<'a>>
+    pub fn parse<T>(packet: &Packet<&T>, src_addr: &IpAddress, dst_addr: &IpAddress,
+                    checksum_caps: &ChecksumCapabilities) -> Result<Repr>
             where T: AsRef<[u8]> + ?Sized {
         // Destination port cannot be omitted (but source port can be).
         if packet.dst_port() == 0 { return Err(Error::Malformed) }
@@ -230,25 +231,26 @@ impl<'a> Repr<'a> {
         Ok(Repr {
             src_port: packet.src_port(),
             dst_port: packet.dst_port(),
-            payload:  packet.payload()
         })
     }
 
-    /// Return the length of a packet that will be emitted from this high-level representation.
-    pub fn buffer_len(&self) -> usize {
-        field::CHECKSUM.end + self.payload.len()
+    /// Return the length of the packet header that will be emitted from this high-level representation.
+    pub fn header_len(&self) -> usize {
+        HEADER_LEN
     }
 
     /// Emit a high-level representation into an User Datagram Protocol packet.
     pub fn emit<T: ?Sized>(&self, packet: &mut Packet<&mut T>,
                            src_addr: &IpAddress,
                            dst_addr: &IpAddress,
+                           payload_len: usize,
+                           emit_payload: impl FnOnce(&mut [u8]),
                            checksum_caps: &ChecksumCapabilities)
             where T: AsRef<[u8]> + AsMut<[u8]> {
         packet.set_src_port(self.src_port);
         packet.set_dst_port(self.dst_port);
-        packet.set_len((field::CHECKSUM.end + self.payload.len()) as u16);
-        packet.payload_mut().copy_from_slice(self.payload);
+        packet.set_len((HEADER_LEN + payload_len) as u16);
+        emit_payload(packet.payload_mut());
 
         if checksum_caps.udp.tx() {
             packet.fill_checksum(src_addr, dst_addr)
@@ -268,10 +270,9 @@ impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
     }
 }
 
-impl<'a> fmt::Display for Repr<'a> {
+impl fmt::Display for Repr {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "UDP src={} dst={} len={}",
-               self.src_port, self.dst_port, self.payload.len())
+        write!(f, "UDP src={} dst={}", self.src_port, self.dst_port)
     }
 }
 
@@ -361,11 +362,10 @@ mod test {
     }
 
     #[cfg(feature = "proto-ipv4")]
-    fn packet_repr() -> Repr<'static> {
+    fn packet_repr() -> Repr {
         Repr {
             src_port: 48896,
             dst_port: 53,
-            payload:  &PAYLOAD_BYTES
         }
     }
 
@@ -382,9 +382,11 @@ mod test {
     #[cfg(feature = "proto-ipv4")]
     fn test_emit() {
         let repr = packet_repr();
-        let mut bytes = vec![0xa5; repr.buffer_len()];
+        let mut bytes = vec![0xa5; repr.header_len() + PAYLOAD_BYTES.len()];
         let mut packet = Packet::new_unchecked(&mut bytes);
         repr.emit(&mut packet, &SRC_ADDR.into(), &DST_ADDR.into(),
+                  PAYLOAD_BYTES.len(),
+                  |payload| payload.copy_from_slice(&PAYLOAD_BYTES),
                   &ChecksumCapabilities::default());
         assert_eq!(&packet.into_inner()[..], &PACKET_BYTES[..]);
     }