浏览代码

socket: add DNS

Dario Nieuwenhuis 3 年之前
父节点
当前提交
da1a2b2df0
共有 7 个文件被更改,包括 571 次插入5 次删除
  1. 7 1
      Cargo.toml
  2. 102 0
      examples/dns.rs
  3. 25 4
      src/iface/interface.rs
  4. 14 0
      src/rand.rs
  5. 411 0
      src/socket/dns.rs
  6. 10 0
      src/socket/mod.rs
  7. 2 0
      src/time.rs

+ 7 - 1
Cargo.toml

@@ -23,6 +23,7 @@ libc = { version = "0.2.18", optional = true }
 bitflags = { version = "1.0", default-features = false }
 defmt = { version = "0.3", optional = true }
 cfg-if = "1.0.0"
+heapless = "0.7.7"
 
 [dev-dependencies]
 env_logger = "0.9"
@@ -54,6 +55,7 @@ verbose = []
 "socket-tcp" = ["socket"]
 "socket-icmp" = ["socket"]
 "socket-dhcpv4" = ["socket", "medium-ethernet", "proto-dhcpv4"]
+"socket-dns" = ["socket", "proto-dns"]
 
 "async" = []
 
@@ -62,7 +64,7 @@ default = [
   "medium-ethernet", "medium-ip", "medium-ieee802154",
   "phy-raw_socket", "phy-tuntap_interface",
   "proto-ipv4", "proto-igmp", "proto-dhcpv4", "proto-ipv6", "proto-sixlowpan", "proto-dns",
-  "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-dhcpv4",
+  "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-dhcpv4", "socket-dns",
   "async"
 ]
 
@@ -114,5 +116,9 @@ required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interfac
 name = "sixlowpan"
 required-features = ["std", "medium-ieee802154", "phy-raw_socket", "proto-sixlowpan", "socket-udp"]
 
+[[example]]
+name = "dns"
+required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "socket-dns"]
+
 [profile.release]
 debug = 2

+ 102 - 0
examples/dns.rs

@@ -0,0 +1,102 @@
+#[macro_use]
+extern crate log;
+extern crate byteorder;
+extern crate env_logger;
+extern crate getopts;
+extern crate smoltcp;
+
+mod utils;
+
+use smoltcp::iface::{InterfaceBuilder, NeighborCache, Routes};
+use smoltcp::phy::Device;
+use smoltcp::phy::{wait as phy_wait, Medium};
+use smoltcp::socket::DnsSocket;
+use smoltcp::time::Instant;
+use smoltcp::wire::{
+    EthernetAddress, HardwareAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address,
+};
+use smoltcp::Error;
+use std::collections::BTreeMap;
+use std::os::unix::io::AsRawFd;
+
+fn main() {
+    utils::setup_logging("warn");
+
+    let (mut opts, mut free) = utils::create_options();
+    utils::add_tuntap_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_tuntap_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 servers = vec![
+        Ipv4Address::new(8, 8, 4, 4).into(),
+        Ipv4Address::new(8, 8, 8, 8).into(),
+    ];
+    let dns_socket = DnsSocket::new(servers, vec![]);
+
+    let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]);
+    let src_ipv6 = IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1);
+    let ip_addrs = [
+        IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24),
+        IpCidr::new(src_ipv6, 64),
+        IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64),
+    ];
+    let default_v4_gw = Ipv4Address::new(192, 168, 69, 100);
+    let default_v6_gw = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100);
+    let mut routes_storage = [None; 2];
+    let mut routes = Routes::new(&mut routes_storage[..]);
+    routes.add_default_ipv4_route(default_v4_gw).unwrap();
+    routes.add_default_ipv6_route(default_v6_gw).unwrap();
+
+    let medium = device.capabilities().medium;
+    let mut builder = InterfaceBuilder::new(device, vec![])
+        .ip_addrs(ip_addrs)
+        .routes(routes);
+    if medium == Medium::Ethernet {
+        builder = builder
+            .hardware_addr(HardwareAddress::Ethernet(ethernet_addr))
+            .neighbor_cache(neighbor_cache);
+    }
+    let mut iface = builder.finalize();
+
+    let dns_handle = iface.add_socket(dns_socket);
+
+    //let name = b"\x08facebook\x03com\x00";
+    //let name = b"\x03www\x08facebook\x03com\x00";
+    //let name = b"\x06reddit\x03com\x00";
+    let name = b"\x09rust-lang\x03org\x00";
+
+    let (socket, cx) = iface.get_socket_and_context::<DnsSocket>(dns_handle);
+    let query = socket.start_query(cx, name).unwrap();
+
+    loop {
+        let timestamp = Instant::now();
+        debug!("timestamp {:?}", timestamp);
+
+        match iface.poll(timestamp) {
+            Ok(_) => {}
+            Err(e) => {
+                debug!("poll error: {}", e);
+            }
+        }
+
+        match iface
+            .get_socket::<DnsSocket>(dns_handle)
+            .get_query_result(query)
+        {
+            Ok(addrs) => {
+                println!("Query done: {:?}", addrs);
+                break;
+            }
+            Err(Error::Exhausted) => {} // not done yet
+            Err(e) => panic!("query failed: {:?}", e),
+        }
+
+        phy_wait(fd, iface.poll_delay(timestamp)).expect("wait error");
+    }
+}

+ 25 - 4
src/iface/interface.rs

@@ -365,7 +365,7 @@ pub(crate) enum IpPacket<'a> {
     Icmpv6((Ipv6Repr, Icmpv6Repr<'a>)),
     #[cfg(feature = "socket-raw")]
     Raw((IpRepr, &'a [u8])),
-    #[cfg(feature = "socket-udp")]
+    #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
     Udp((IpRepr, UdpRepr, &'a [u8])),
     #[cfg(feature = "socket-tcp")]
     Tcp((IpRepr, TcpRepr<'a>)),
@@ -417,7 +417,7 @@ impl<'a> IpPacket<'a> {
             ),
             #[cfg(feature = "socket-raw")]
             IpPacket::Raw((_, raw_packet)) => payload.copy_from_slice(raw_packet),
-            #[cfg(feature = "socket-udp")]
+            #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
             IpPacket::Udp((_, udp_repr, inner_payload)) => udp_repr.emit(
                 &mut UdpPacket::new_unchecked(payload),
                 &_ip_repr.src_addr(),
@@ -944,6 +944,10 @@ where
                 Socket::Dhcpv4(socket) => socket.dispatch(inner, |inner, response| {
                     respond!(inner, IpPacket::Dhcpv4(response))
                 }),
+                #[cfg(feature = "socket-dns")]
+                Socket::Dns(ref mut socket) => socket.dispatch(inner, |inner, response| {
+                    respond!(inner, IpPacket::Udp(response))
+                }),
             };
 
             match (device_result, socket_result) {
@@ -1575,7 +1579,7 @@ impl<'a> InterfaceInner<'a> {
         match nxt_hdr {
             IpProtocol::Icmpv6 => self.process_icmpv6(sockets, ipv6_repr.into(), ip_payload),
 
-            #[cfg(feature = "socket-udp")]
+            #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
             IpProtocol::Udp => {
                 self.process_udp(sockets, ipv6_repr.into(), handled_by_raw_socket, ip_payload)
             }
@@ -2117,7 +2121,7 @@ impl<'a> InterfaceInner<'a> {
         }
     }
 
-    #[cfg(feature = "socket-udp")]
+    #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
     fn process_udp<'frame>(
         &mut self,
         sockets: &mut SocketSet,
@@ -2146,6 +2150,23 @@ impl<'a> InterfaceInner<'a> {
             }
         }
 
+        #[cfg(feature = "socket-dns")]
+        for dns_socket in sockets
+            .iter_mut()
+            .filter_map(|i| DnsSocket::downcast(&mut i.socket))
+        {
+            if !dns_socket.accepts(&ip_repr, &udp_repr) {
+                continue;
+            }
+
+            match dns_socket.process(self, &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.
+                Err(e) => return Err(e),
+            }
+        }
+
         // The packet wasn't handled by a socket, send an ICMP port unreachable packet.
         match ip_repr {
             #[cfg(feature = "proto-ipv4")]

+ 14 - 0
src/rand.rs

@@ -23,4 +23,18 @@ impl Rand {
         let shift = 29 - (s >> 61);
         (s >> shift) as u32
     }
+
+    pub(crate) fn rand_u16(&mut self) -> u16 {
+        let n = self.rand_u32();
+        (n ^ (n >> 16)) as u16
+    }
+
+    pub(crate) fn rand_source_port(&mut self) -> u16 {
+        loop {
+            let res = self.rand_u16();
+            if res > 1024 {
+                return res;
+            }
+        }
+    }
 }

+ 411 - 0
src/socket/dns.rs

@@ -0,0 +1,411 @@
+#![allow(dead_code, unused)]
+use heapless::Vec;
+use managed::ManagedSlice;
+
+use crate::socket::{Context, PollAt, Socket};
+use crate::time::{Duration, Instant};
+use crate::wire::dns::{Flags, Opcode, Packet, Question, Record, RecordData, Repr, Type};
+use crate::wire::{IpAddress, IpEndpoint, IpProtocol, IpRepr, Ipv4Address, UdpRepr};
+use crate::{rand, Error, Result};
+
+const DNS_PORT: u16 = 53;
+const MAX_NAME_LEN: usize = 255;
+const MAX_ADDRESS_COUNT: usize = 4;
+const RETRANSMIT_DELAY: Duration = Duration::from_millis(1000);
+const MAX_RETRANSMIT_DELAY: Duration = Duration::from_millis(10000);
+
+/// State for an in-progress DNS query.
+///
+/// The only reason this struct is public is to allow the socket state
+/// to be allocated externally.
+#[derive(Debug)]
+pub struct DnsQuery {
+    state: State,
+}
+
+#[derive(Debug)]
+#[allow(clippy::large_enum_variant)]
+enum State {
+    Pending(PendingQuery),
+    Completed(CompletedQuery),
+}
+
+#[derive(Debug)]
+struct PendingQuery {
+    name: Vec<u8, MAX_NAME_LEN>,
+    type_: Type,
+
+    port: u16, // UDP port (src for request, dst for response)
+    txid: u16, // transaction ID
+
+    retransmit_at: Instant,
+    delay: Duration,
+}
+
+#[derive(Debug)]
+struct CompletedQuery {
+    addresses: Vec<IpAddress, MAX_ADDRESS_COUNT>,
+}
+
+/// A handle to an in-progress DNS query.
+#[derive(Clone, Copy)]
+pub struct QueryHandle(usize);
+
+/// A Domain Name System socket.
+///
+/// A UDP socket is bound to a specific endpoint, and owns transmit and receive
+/// packet buffers.
+#[derive(Debug)]
+pub struct DnsSocket<'a> {
+    servers: ManagedSlice<'a, IpAddress>,
+    queries: ManagedSlice<'a, Option<DnsQuery>>,
+
+    /// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
+    hop_limit: Option<u8>,
+}
+
+impl<'a> DnsSocket<'a> {
+    /// Create a DNS socket with the given buffers.
+    pub fn new<Q, S>(servers: S, queries: Q) -> DnsSocket<'a>
+    where
+        S: Into<ManagedSlice<'a, IpAddress>>,
+        Q: Into<ManagedSlice<'a, Option<DnsQuery>>>,
+    {
+        DnsSocket {
+            servers: servers.into(),
+            queries: queries.into(),
+            hop_limit: None,
+        }
+    }
+
+    /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
+    ///
+    /// See also the [set_hop_limit](#method.set_hop_limit) method
+    pub fn hop_limit(&self) -> Option<u8> {
+        self.hop_limit
+    }
+
+    /// Set the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
+    ///
+    /// A socket without an explicitly set hop limit value uses the default [IANA recommended]
+    /// value (64).
+    ///
+    /// # Panics
+    ///
+    /// This function panics if a hop limit value of 0 is given. See [RFC 1122 § 3.2.1.7].
+    ///
+    /// [IANA recommended]: https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml
+    /// [RFC 1122 § 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7
+    pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) {
+        // A host MUST NOT send a datagram with a hop limit value of 0
+        if let Some(0) = hop_limit {
+            panic!("the time-to-live value of a packet must not be zero")
+        }
+
+        self.hop_limit = hop_limit
+    }
+
+    fn find_free_query(&mut self) -> Result<QueryHandle> {
+        for (i, q) in self.queries.iter().enumerate() {
+            if q.is_none() {
+                return Ok(QueryHandle(i));
+            }
+        }
+
+        match self.queries {
+            ManagedSlice::Borrowed(_) => Err(Error::Exhausted),
+            #[cfg(any(feature = "std", feature = "alloc"))]
+            ManagedSlice::Owned(ref mut queries) => {
+                queries.push(None);
+                let index = queries.len() - 1;
+                Ok(QueryHandle(index))
+            }
+        }
+    }
+
+    pub fn start_query(&mut self, cx: &mut Context, name: &[u8]) -> Result<QueryHandle> {
+        let handle = self.find_free_query()?;
+
+        self.queries[handle.0] = Some(DnsQuery {
+            state: State::Pending(PendingQuery {
+                name: Vec::from_slice(name).map_err(|_| Error::Truncated)?,
+                type_: Type::A,
+                txid: cx.rand().rand_u16(),
+                port: cx.rand().rand_source_port(),
+                delay: RETRANSMIT_DELAY,
+                retransmit_at: Instant::ZERO,
+            }),
+        });
+        Ok(handle)
+    }
+
+    pub fn get_query_result(
+        &mut self,
+        handle: QueryHandle,
+    ) -> Result<Vec<IpAddress, MAX_ADDRESS_COUNT>> {
+        let slot = self.queries.get_mut(handle.0).ok_or(Error::Illegal)?;
+        let q = slot.as_mut().ok_or(Error::Illegal)?;
+        match &mut q.state {
+            // Query is not done yet.
+            State::Pending(_) => Err(Error::Exhausted),
+            // Query is done
+            State::Completed(q) => {
+                let res = q.addresses.clone();
+                *slot = None; // Free up the slot for recycling.
+                Ok(res)
+            }
+        }
+    }
+
+    pub fn cancel_query(&mut self, handle: QueryHandle) -> Result<()> {
+        let slot = self.queries.get_mut(handle.0).ok_or(Error::Illegal)?;
+        let q = slot.as_mut().ok_or(Error::Illegal)?;
+        *slot = None; // Free up the slot for recycling.
+        Ok(())
+    }
+
+    pub(crate) fn accepts(&self, ip_repr: &IpRepr, udp_repr: &UdpRepr) -> bool {
+        udp_repr.src_port == DNS_PORT
+            && self
+                .servers
+                .iter()
+                .any(|server| *server == ip_repr.src_addr())
+    }
+
+    pub(crate) fn process(
+        &mut self,
+        cx: &mut Context,
+        ip_repr: &IpRepr,
+        udp_repr: &UdpRepr,
+        payload: &[u8],
+    ) -> Result<()> {
+        debug_assert!(self.accepts(ip_repr, udp_repr));
+
+        let size = payload.len();
+
+        net_trace!(
+            "receiving {} octets from {:?}:{}",
+            size,
+            ip_repr.src_addr(),
+            udp_repr.dst_port
+        );
+
+        let p = Packet::new_checked(payload)?;
+        if p.opcode() != Opcode::Query {
+            net_trace!("unwanted opcode {:?}", p.opcode());
+            return Err(Error::Malformed);
+        }
+
+        if !p.flags().contains(Flags::RESPONSE) {
+            net_trace!("packet doesn't have response bit set");
+            return Err(Error::Malformed);
+        }
+
+        if p.question_count() != 1 {
+            net_trace!("bad question count {:?}", p.question_count());
+            return Err(Error::Malformed);
+        }
+
+        // Find pending query
+        for q in self.queries.iter_mut().flatten() {
+            if let State::Pending(pq) = &mut q.state {
+                if udp_repr.dst_port != pq.port || p.transaction_id() != pq.txid {
+                    continue;
+                }
+
+                let payload = p.payload();
+                let (mut payload, question) = Question::parse(payload)?;
+
+                if question.type_ != pq.type_ {
+                    net_trace!("question type mismatch");
+                    return Err(Error::Malformed);
+                }
+                if !eq_names(p.parse_name(question.name), p.parse_name(&pq.name))? {
+                    net_trace!("question name mismatch");
+                    return Err(Error::Malformed);
+                }
+
+                let mut addresses = Vec::new();
+
+                for _ in 0..p.answer_record_count() {
+                    let (payload2, r) = Record::parse(payload)?;
+                    payload = payload2;
+
+                    if !eq_names(p.parse_name(r.name), p.parse_name(&pq.name))? {
+                        net_trace!("answer name mismatch: {:?}", r);
+                        continue;
+                    }
+
+                    match r.data {
+                        RecordData::A(addr) => {
+                            net_trace!("A: {:?}", addr);
+                            if addresses.push(addr.into()).is_err() {
+                                net_trace!("too many addresses in response, ignoring {:?}", addr);
+                            }
+                        }
+                        RecordData::Aaaa(addr) => {
+                            net_trace!("AAAA: {:?}", addr);
+                            if addresses.push(addr.into()).is_err() {
+                                net_trace!("too many addresses in response, ignoring {:?}", addr);
+                            }
+                        }
+                        RecordData::Cname(name) => {
+                            net_trace!("CNAME: {:?}", name);
+                            copy_name(&mut pq.name, p.parse_name(name))?;
+
+                            // Relaunch query with the new name.
+                            // If the server has bundled A records for the CNAME in the same packet,
+                            // we'll process them in next iterations, and cancel the query relaunch.
+                            pq.retransmit_at = Instant::ZERO;
+                            pq.delay = RETRANSMIT_DELAY;
+
+                            pq.txid = cx.rand().rand_u16();
+                            pq.port = cx.rand().rand_source_port();
+                        }
+                        RecordData::Other(type_, data) => {
+                            net_trace!("unknown: {:?} {:?}", type_, data)
+                        }
+                    }
+                }
+
+                if !addresses.is_empty() {
+                    q.state = State::Completed(CompletedQuery { addresses })
+                }
+                // If we get here, packet matched the current query, stop processing.
+                return Ok(());
+            }
+        }
+
+        // If we get here, packet matched with no query.
+        net_trace!("no query matched");
+        Ok(())
+    }
+
+    pub(crate) fn dispatch<F>(&mut self, cx: &mut Context, emit: F) -> Result<()>
+    where
+        F: FnOnce(&mut Context, (IpRepr, UdpRepr, &[u8])) -> Result<()>,
+    {
+        let hop_limit = self.hop_limit.unwrap_or(64);
+
+        for q in self.queries.iter_mut().flatten() {
+            if let State::Pending(pq) = &mut q.state {
+                if pq.retransmit_at > cx.now() {
+                    // query is waiting for retransmit
+                    continue;
+                }
+
+                let repr = Repr {
+                    transaction_id: pq.txid,
+                    flags: Flags::RECURSION_DESIRED,
+                    opcode: Opcode::Query,
+                    question: Question {
+                        name: &pq.name,
+                        type_: Type::A,
+                    },
+                };
+
+                let mut payload = [0u8; 512];
+                let payload = &mut payload[..repr.buffer_len()];
+                repr.emit(&mut Packet::new_unchecked(payload));
+
+                let udp_repr = UdpRepr {
+                    src_port: pq.port,
+                    dst_port: 53,
+                };
+
+                let dst_addr = self.servers[0];
+                let src_addr = cx.get_source_address(dst_addr).unwrap(); // TODO remove unwrap
+                let ip_repr = IpRepr::new(
+                    src_addr,
+                    dst_addr,
+                    IpProtocol::Udp,
+                    udp_repr.header_len() + payload.len(),
+                    hop_limit,
+                );
+
+                net_trace!(
+                    "sending {} octets to {:?}:{}",
+                    payload.len(),
+                    ip_repr.dst_addr(),
+                    udp_repr.src_port
+                );
+
+                if let Err(e) = emit(cx, (ip_repr, udp_repr, payload)) {
+                    net_trace!("DNS emit error {:?}", e);
+                    return Ok(());
+                }
+
+                pq.retransmit_at = cx.now() + pq.delay;
+                pq.delay = MAX_RETRANSMIT_DELAY.min(pq.delay * 2);
+
+                return Ok(());
+            }
+        }
+
+        // Nothing to dispatch
+        Err(Error::Exhausted)
+    }
+
+    pub(crate) fn poll_at(&self, _cx: &Context) -> PollAt {
+        self.queries
+            .iter()
+            .flatten()
+            .filter_map(|q| match &q.state {
+                State::Pending(pq) => Some(PollAt::Time(pq.retransmit_at)),
+                State::Completed(_) => None,
+            })
+            .min()
+            .unwrap_or(PollAt::Ingress)
+    }
+}
+
+impl<'a> From<DnsSocket<'a>> for Socket<'a> {
+    fn from(val: DnsSocket<'a>) -> Self {
+        Socket::Dns(val)
+    }
+}
+
+fn eq_names<'a>(
+    mut a: impl Iterator<Item = Result<&'a [u8]>>,
+    mut b: impl Iterator<Item = Result<&'a [u8]>>,
+) -> Result<bool> {
+    loop {
+        match (a.next(), b.next()) {
+            // Handle errors
+            (Some(Err(e)), _) => return Err(e),
+            (_, Some(Err(e))) => return Err(e),
+
+            // Both finished -> equal
+            (None, None) => return Ok(true),
+
+            // One finished before the other -> not equal
+            (None, _) => return Ok(false),
+            (_, None) => return Ok(false),
+
+            // Got two labels, check if they're equal
+            (Some(Ok(la)), Some(Ok(lb))) => {
+                if la != lb {
+                    return Ok(false);
+                }
+            }
+        }
+    }
+}
+
+fn copy_name<'a, const N: usize>(
+    dest: &mut Vec<u8, N>,
+    name: impl Iterator<Item = Result<&'a [u8]>>,
+) -> Result<()> {
+    dest.truncate(0);
+
+    for label in name {
+        let label = label?;
+        dest.push(label.len() as u8).map_err(|_| Error::Truncated);
+        dest.extend_from_slice(label).map_err(|_| Error::Truncated);
+    }
+
+    // Write terminator 0x00
+    dest.push(0).map_err(|_| Error::Truncated);
+
+    Ok(())
+}

+ 10 - 0
src/socket/mod.rs

@@ -16,6 +16,8 @@ use crate::time::Instant;
 
 #[cfg(feature = "socket-dhcpv4")]
 mod dhcpv4;
+#[cfg(feature = "socket-dns")]
+mod dns;
 #[cfg(feature = "socket-icmp")]
 mod icmp;
 #[cfg(feature = "socket-raw")]
@@ -30,6 +32,8 @@ mod waker;
 
 #[cfg(feature = "socket-dhcpv4")]
 pub use self::dhcpv4::{Config as Dhcpv4Config, Dhcpv4Socket, Event as Dhcpv4Event};
+#[cfg(feature = "socket-dns")]
+pub use self::dns::{DnsQuery, DnsSocket};
 #[cfg(feature = "socket-icmp")]
 pub use self::icmp::{Endpoint as IcmpEndpoint, IcmpPacketMetadata, IcmpSocket, IcmpSocketBuffer};
 #[cfg(feature = "socket-raw")]
@@ -76,6 +80,8 @@ pub enum Socket<'a> {
     Tcp(TcpSocket<'a>),
     #[cfg(feature = "socket-dhcpv4")]
     Dhcpv4(Dhcpv4Socket),
+    #[cfg(feature = "socket-dns")]
+    Dns(DnsSocket<'a>),
 }
 
 impl<'a> Socket<'a> {
@@ -91,6 +97,8 @@ impl<'a> Socket<'a> {
             Socket::Tcp(s) => s.poll_at(cx),
             #[cfg(feature = "socket-dhcpv4")]
             Socket::Dhcpv4(s) => s.poll_at(cx),
+            #[cfg(feature = "socket-dns")]
+            Socket::Dns(s) => s.poll_at(cx),
         }
     }
 }
@@ -129,3 +137,5 @@ from_socket!(UdpSocket<'a>, Udp);
 from_socket!(TcpSocket<'a>, Tcp);
 #[cfg(feature = "socket-dhcpv4")]
 from_socket!(Dhcpv4Socket, Dhcpv4);
+#[cfg(feature = "socket-dns")]
+from_socket!(DnsSocket<'a>, Dns);

+ 2 - 0
src/time.rs

@@ -28,6 +28,8 @@ pub struct Instant {
 }
 
 impl Instant {
+    pub const ZERO: Instant = Instant::from_micros_const(0);
+
     /// Create a new `Instant` from a number of microseconds.
     pub fn from_micros<T: Into<i64>>(micros: T) -> Instant {
         Instant {