浏览代码

Implement DHCPv4 client + example.

Closes: #186
Approved by: whitequark
Astro 7 年之前
父节点
当前提交
657c9aa63a
共有 8 个文件被更改,包括 569 次插入4 次删除
  1. 3 1
      .travis.yml
  2. 5 1
      Cargo.toml
  3. 106 0
      examples/dhcp_client.rs
  4. 435 0
      src/dhcp/clientv4.rs
  5. 5 0
      src/dhcp/mod.rs
  6. 11 1
      src/iface/ethernet.rs
  7. 2 0
      src/lib.rs
  8. 2 1
      src/wire/dhcpv4.rs

+ 3 - 1
.travis.yml

@@ -19,6 +19,8 @@ matrix:
       env: FEATURES='std proto-ipv4 proto-igmp socket-raw' MODE='test'
     - rust: nightly
       env: FEATURES='std proto-ipv4 socket-udp socket-tcp' MODE='test'
+    - rust: nightly
+      env: FEATURES='std proto-ipv4 proto-dhcpv4 socket-udp' MODE='test'
     - rust: nightly
       env: FEATURES='std proto-ipv6 socket-udp' MODE='test'
     - rust: nightly
@@ -35,7 +37,7 @@ matrix:
       env: FEATURES='proto-ipv4 proto-ipv6 socket-raw socket-udp socket-tcp socket-icmp alloc'
         MODE='test'
     - rust: nightly
-      env: FEATURES='proto-ipv4 proto-ipv6 proto-igmp socket-raw socket-udp socket-tcp socket-icmp'
+      env: FEATURES='proto-ipv4 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp'
         MODE='build'
     - rust: nightly
       env: MODE='fuzz run' ARGS='packet_parser -- -max_len=1536 -max_total_time=30'

+ 5 - 1
Cargo.toml

@@ -35,12 +35,12 @@ verbose = []
 "phy-tap_interface" = ["std", "libc"]
 "proto-ipv4" = []
 "proto-igmp" = ["proto-ipv4"]
+"proto-dhcpv4" = ["proto-ipv4", "socket-raw"]
 "proto-ipv6" = []
 "socket-raw" = []
 "socket-udp" = []
 "socket-tcp" = []
 "socket-icmp" = []
-"proto-dhcpv4" = ["proto-ipv4"]
 default = [
   "std", "log", # needed for `cargo test --no-default-features --features default` :/
   "phy-raw_socket", "phy-tap_interface",
@@ -88,5 +88,9 @@ required-features = ["std", "phy-tap_interface", "proto-ipv4", "proto-igmp", "so
 name = "benchmark"
 required-features = ["std", "phy-tap_interface", "proto-ipv4", "socket-raw", "socket-udp"]
 
+[[example]]
+name = "dhcp_client"
+required-features = ["std", "phy-tap_interface", "proto-ipv4", "proto-dhcpv4", "socket-raw"]
+
 [profile.release]
 debug = 2

+ 106 - 0
examples/dhcp_client.rs

@@ -0,0 +1,106 @@
+#[macro_use]
+extern crate log;
+extern crate env_logger;
+extern crate getopts;
+extern crate smoltcp;
+
+mod utils;
+
+use std::collections::BTreeMap;
+use std::os::unix::io::AsRawFd;
+use smoltcp::phy::wait as phy_wait;
+use smoltcp::wire::{EthernetAddress, Ipv4Address, IpCidr, Ipv4Cidr};
+use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, Routes};
+use smoltcp::socket::{SocketSet, RawSocketBuffer, RawPacketMetadata};
+use smoltcp::time::Instant;
+use smoltcp::dhcp::Dhcpv4Client;
+
+fn main() {
+    #[cfg(feature = "log")]
+    utils::setup_logging("");
+
+    let (mut opts, mut free) = utils::create_options();
+    utils::add_tap_options(&mut opts, &mut free);
+    utils::add_middleware_options(&mut opts, &mut free);
+
+    let mut matches = utils::parse_options(&opts, free);
+    let device = utils::parse_tap_options(&mut matches);
+    let fd = device.as_raw_fd();
+    let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/false);
+
+    let neighbor_cache = NeighborCache::new(BTreeMap::new());
+    let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
+    let ip_addrs = [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 0)];
+    let mut routes_storage = [None; 1];
+    let routes = Routes::new(&mut routes_storage[..]);
+    let mut iface = EthernetInterfaceBuilder::new(device)
+            .ethernet_addr(ethernet_addr)
+            .neighbor_cache(neighbor_cache)
+            .ip_addrs(ip_addrs)
+            .routes(routes)
+            .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);
+    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);
+            match config.address {
+                Some(cidr) => if cidr != prev_cidr {
+                    iface.update_ip_addrs(|addrs| {
+                        addrs.iter_mut().nth(0)
+                            .map(|addr| {
+                                *addr = IpCidr::Ipv4(cidr);
+                            });
+                    });
+                    prev_cidr = cidr;
+                    println!("Assigned a new IPv4 address: {}", cidr);
+                }
+                _ => {}
+            }
+
+            config.router.map(|router| iface.routes_mut()
+                              .add_default_ipv4_route(router.into())
+                              .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);
+                }
+            }
+        });
+
+        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));;
+    }
+}

+ 435 - 0
src/dhcp/clientv4.rs

@@ -0,0 +1,435 @@
+use {Result, Error};
+use wire::{IpVersion, IpProtocol, IpEndpoint, IpAddress,
+           Ipv4Cidr, Ipv4Address, Ipv4Packet, Ipv4Repr,
+           UdpPacket, UdpRepr,
+           DhcpPacket, DhcpRepr, DhcpMessageType};
+use wire::dhcpv4::field as dhcpv4_field;
+use socket::{SocketSet, SocketHandle, RawSocket, RawSocketBuffer};
+use phy::{Device, ChecksumCapabilities};
+use iface::EthernetInterface as Interface;
+use 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 RENEW_INTERVAL: u64 = 60;
+const RENEW_RETRIES: u16 = 3;
+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)]
+pub struct Config {
+    pub address: Option<Ipv4Cidr>,
+    pub router: Option<Ipv4Address>,
+    pub dns_servers: [Option<Ipv4Address>; 3],
+}
+
+#[derive(Debug)]
+struct RequestState {
+    retry: u16,
+    endpoint_ip: Ipv4Address,
+    server_identifier: Ipv4Address,
+}
+
+#[derive(Debug)]
+struct RenewState {
+    retry: u16,
+    endpoint_ip: Ipv4Address,
+    server_identifier: Ipv4Address,
+}
+
+#[derive(Debug)]
+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,
+    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, 'b, 'c>(sockets: &mut SocketSet<'a, 'b, 'c>, rx_buffer: RawSocketBuffer<'b, 'c>, tx_buffer: RawSocketBuffer<'b, 'c>, now: Instant) -> Self
+    where 'b: 'c,
+    {
+        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,
+        }
+    }
+
+    /// 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. You must configure the new IPv4 address from the
+    /// returned `Config`. Otherwise, DHCP will not work.
+    ///
+    /// 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 = ChecksumCapabilities::default();  // ?
+        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);
+
+        let config = if (dhcp_repr.message_type == DhcpMessageType::Offer ||
+                         dhcp_repr.message_type == DhcpMessageType::Ack) &&
+                      dhcp_repr.your_ip != Ipv4Address::UNSPECIFIED {
+               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,
+                };
+                Some(ClientState::Requesting(r_state))
+            }
+            ClientState::Requesting(ref r_state)
+                if dhcp_repr.message_type == DhcpMessageType::Ack &&
+                   server_identifier == r_state.server_identifier =>
+            {
+                self.next_egress = now + Duration::from_secs(RENEW_INTERVAL);
+                let p_state = RenewState {
+                    retry: 0,
+                    endpoint_ip: *src_ip,
+                    server_identifier,
+                };
+                Some(ClientState::Renew(p_state))
+            }
+            ClientState::Renew(ref mut p_state)
+                if dhcp_repr.message_type == DhcpMessageType::Ack &&
+                server_identifier == p_state.server_identifier =>
+            {
+                self.next_egress = now + Duration::from_secs(RENEW_INTERVAL);
+                p_state.retry = 0;
+                None
+            }
+            _ => None
+        }.map(|new_state| self.state = new_state);
+
+        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
+            }
+            ClientState::Renew(ref mut r_state) if r_state.retry >= RENEW_RETRIES => {
+                net_debug!("DHCP renew retries exceeded, restarting discovery");
+                true
+            }
+            _ => false
+        };
+        if retries_exceeded {
+            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: 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,
+                };
+                let requested_ip = match iface.ipv4_addr() {
+                    Some(addr) if !addr.is_unspecified() =>
+                        Some(addr),
+                    _ =>
+                        None,
+                };
+                dhcp_repr.message_type = DhcpMessageType::Request;
+                dhcp_repr.broadcast = false;
+                dhcp_repr.requested_ip = requested_ip;
+                dhcp_repr.server_identifier = Some(r_state.server_identifier);
+                dhcp_repr.parameter_request_list = Some(PARAMETER_REQUEST_LIST);
+                net_trace!("DHCP send request to {} = {:?}", endpoint, dhcp_repr);
+                send_packet(iface, endpoint, dhcp_repr)
+            }
+            ClientState::Renew(ref mut p_state) => {
+                p_state.retry += 1;
+                self.next_egress = now + Duration::from_secs(RENEW_INTERVAL);
+
+                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;
+    }
+}
+
+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))
+}

+ 5 - 0
src/dhcp/mod.rs

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

+ 11 - 1
src/iface/ethernet.rs

@@ -433,6 +433,16 @@ impl<'b, 'c, 'e, DeviceT> Interface<'b, 'c, 'e, DeviceT>
         self.inner.ip_addrs.as_ref()
     }
 
+    /// Get the first IPv4 address if present.
+    #[cfg(feature = "proto-ipv4")]
+    pub fn ipv4_addr(&self) -> Option<Ipv4Address> {
+        self.ip_addrs().iter()
+            .filter_map(|cidr| match cidr.address() {
+                IpAddress::Ipv4(addr) => Some(addr),
+                _ => None,
+            }).next()
+    }
+
     /// Update the IP addresses of the interface.
     ///
     /// # Panics
@@ -698,7 +708,7 @@ impl<'b, 'c, 'e> InterfaceInner<'b, 'c, 'e> {
 
     fn check_ip_addrs(addrs: &[IpCidr]) {
         for cidr in addrs {
-            if !cidr.address().is_unicast() {
+            if !cidr.address().is_unicast() && !cidr.address().is_unspecified() {
                 panic!("IP address {} is not unicast", cidr.address())
             }
         }

+ 2 - 0
src/lib.rs

@@ -119,6 +119,8 @@ pub mod wire;
 pub mod iface;
 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)]

+ 2 - 1
src/wire/dhcpv4.rs

@@ -765,7 +765,8 @@ impl<'a> Repr<'a> {
         })
     }
 
-    /// Emit a high-level representation into a Transmission Control Protocol packet.
+    /// Emit a high-level representation into a Dynamic Host
+    /// Configuration Protocol packet.
     pub fn emit<T>(&self, packet: &mut Packet<&mut T>) -> Result<()>
             where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized {
         packet.set_sname_and_boot_file_to_zero();