浏览代码

Merge pull request #465 from smoltcp-rs/dns

DNS
Dario Nieuwenhuis 2 年之前
父节点
当前提交
7728660e47
共有 11 个文件被更改,包括 1521 次插入16 次删除
  1. 7 7
      .github/workflows/test.yml
  2. 9 2
      Cargo.toml
  3. 99 0
      examples/dns.rs
  4. 28 6
      src/iface/interface.rs
  5. 2 1
      src/lib.rs
  6. 14 0
      src/rand.rs
  7. 555 0
      src/socket/dns.rs
  8. 10 0
      src/socket/mod.rs
  9. 2 0
      src/time.rs
  10. 793 0
      src/wire/dns.rs
  11. 2 0
      src/wire/mod.rs

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

@@ -32,18 +32,18 @@ jobs:
           - std proto-ipv4
 
           # Test features chosen to be as orthogonal as possible.
-          - std medium-ethernet phy-raw_socket proto-ipv6 socket-udp
+          - std medium-ethernet phy-raw_socket proto-ipv6 socket-udp socket-dns
           - std medium-ethernet phy-tuntap_interface proto-ipv6 socket-udp
-          - std medium-ethernet proto-ipv4 proto-igmp socket-raw
-          - std medium-ethernet proto-ipv4 socket-udp socket-tcp
+          - std medium-ethernet proto-ipv4 proto-igmp socket-raw socket-dns
+          - std medium-ethernet proto-ipv4 socket-udp socket-tcp socket-dns
           - std medium-ethernet proto-ipv4 proto-dhcpv4 socket-udp
-          - std medium-ethernet medium-ip medium-ieee802154 proto-ipv6 socket-udp
+          - std medium-ethernet medium-ip medium-ieee802154 proto-ipv6 socket-udp socket-dns
           - std medium-ethernet proto-ipv6 socket-tcp
           - std medium-ethernet medium-ip proto-ipv4 socket-icmp socket-tcp
           - std medium-ip proto-ipv6 socket-icmp socket-tcp
 
           # Test features chosen to be as aggressive as possible.
-          - std medium-ethernet medium-ip medium-ieee802154 proto-ipv4 proto-ipv6 socket-raw socket-udp socket-tcp socket-icmp async
+          - std medium-ethernet medium-ip medium-ieee802154 proto-ipv4 proto-ipv6 socket-raw socket-udp socket-tcp socket-icmp socket-dns async
 
         include:
           # Test alloc feature which requires nightly.
@@ -73,8 +73,8 @@ jobs:
 
         features:
           # These feature sets cannot run tests, so we only check they build.
-          - medium-ip medium-ethernet medium-ieee802154 proto-ipv6 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp async
-          - defmt medium-ip medium-ethernet proto-ipv6 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp async
+          - medium-ip medium-ethernet medium-ieee802154 proto-ipv6 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp socket-dns async
+          - defmt medium-ip medium-ethernet proto-ipv6 proto-ipv6 proto-igmp proto-dhcpv4 socket-raw socket-udp socket-tcp socket-icmp socket-dns async
 
     steps:
       - uses: actions/checkout@v2

+ 9 - 2
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.8"
 
 [dev-dependencies]
 env_logger = "0.9"
@@ -46,6 +47,7 @@ verbose = []
 "proto-dhcpv4" = ["proto-ipv4"]
 "proto-ipv6" = []
 "proto-sixlowpan" = ["proto-ipv6"]
+"proto-dns" = []
 
 "socket" = []
 "socket-raw" = ["socket"]
@@ -53,6 +55,7 @@ verbose = []
 "socket-tcp" = ["socket"]
 "socket-icmp" = ["socket"]
 "socket-dhcpv4" = ["socket", "medium-ethernet", "proto-dhcpv4"]
+"socket-dns" = ["socket", "proto-dns"]
 
 "async" = []
 
@@ -60,8 +63,8 @@ default = [
   "std", "log", # needed for `cargo test --no-default-features --features default` :/
   "medium-ethernet", "medium-ip", "medium-ieee802154",
   "phy-raw_socket", "phy-tuntap_interface",
-  "proto-ipv4", "proto-igmp", "proto-dhcpv4", "proto-ipv6", "proto-sixlowpan",
-  "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-dhcpv4",
+  "proto-ipv4", "proto-igmp", "proto-dhcpv4", "proto-ipv6", "proto-sixlowpan", "proto-dns",
+  "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-dhcpv4", "socket-dns",
   "async"
 ]
 
@@ -113,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

+ 99 - 0
examples/dns.rs

@@ -0,0 +1,99 @@
+#[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);
+    free.push("ADDRESS");
+
+    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 name = &matches.free[0];
+
+    let neighbor_cache = NeighborCache::new(BTreeMap::new());
+
+    let servers = &[
+        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 (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");
+    }
+}

+ 28 - 6
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>)),
@@ -384,7 +384,7 @@ impl<'a> IpPacket<'a> {
             IpPacket::Icmpv6((ipv6_repr, _)) => IpRepr::Ipv6(*ipv6_repr),
             #[cfg(feature = "socket-raw")]
             IpPacket::Raw((ip_repr, _)) => ip_repr.clone(),
-            #[cfg(feature = "socket-udp")]
+            #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
             IpPacket::Udp((ip_repr, _, _)) => ip_repr.clone(),
             #[cfg(feature = "socket-tcp")]
             IpPacket::Tcp((ip_repr, _)) => ip_repr.clone(),
@@ -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)
             }
@@ -1681,7 +1685,7 @@ impl<'a> InterfaceInner<'a> {
             #[cfg(feature = "proto-igmp")]
             IpProtocol::Igmp => self.process_igmp(ipv4_repr, ip_payload),
 
-            #[cfg(feature = "socket-udp")]
+            #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
             IpProtocol::Udp => {
                 self.process_udp(sockets, ip_repr, 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,
@@ -2130,6 +2134,7 @@ impl<'a> InterfaceInner<'a> {
         let udp_repr = UdpRepr::parse(&udp_packet, &src_addr, &dst_addr, &self.caps.checksum)?;
         let udp_payload = udp_packet.payload();
 
+        #[cfg(feature = "socket-udp")]
         for udp_socket in sockets
             .iter_mut()
             .filter_map(|i| UdpSocket::downcast(&mut i.socket))
@@ -2146,6 +2151,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")]

+ 2 - 1
src/lib.rs

@@ -112,9 +112,10 @@ compile_error!("You must enable at least one of the following features: proto-ip
         feature = "socket-tcp",
         feature = "socket-icmp",
         feature = "socket-dhcp",
+        feature = "socket-dns",
     ))
 ))]
-compile_error!("If you enable the socket feature, you must enable at least one of the following features: socket-raw, socket-udp, socket-tcp, socket-icmp, socket-dhcp");
+compile_error!("If you enable the socket feature, you must enable at least one of the following features: socket-raw, socket-udp, socket-tcp, socket-icmp, socket-dhcp, socket-dns");
 
 #[cfg(all(
     feature = "socket",

+ 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;
+            }
+        }
+    }
 }

+ 555 - 0
src/socket/dns.rs

@@ -0,0 +1,555 @@
+#[cfg(feature = "async")]
+use core::task::Waker;
+
+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, Rcode, Record, RecordData, Repr, Type};
+use crate::wire::{IpAddress, IpProtocol, IpRepr, UdpRepr};
+use crate::{Error, Result};
+
+#[cfg(feature = "async")]
+use super::WakerRegistration;
+
+const DNS_PORT: u16 = 53;
+const MAX_NAME_LEN: usize = 255;
+const MAX_ADDRESS_COUNT: usize = 4;
+const MAX_SERVER_COUNT: usize = 4;
+const RETRANSMIT_DELAY: Duration = Duration::from_millis(1_000);
+const MAX_RETRANSMIT_DELAY: Duration = Duration::from_millis(10_000);
+const RETRANSMIT_TIMEOUT: Duration = Duration::from_millis(10_000); // Should generally be 2-10 secs
+
+/// 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,
+
+    #[cfg(feature = "async")]
+    waker: WakerRegistration,
+}
+
+impl DnsQuery {
+    fn set_state(&mut self, state: State) {
+        self.state = state;
+        #[cfg(feature = "async")]
+        self.waker.wake();
+    }
+}
+
+#[derive(Debug)]
+#[allow(clippy::large_enum_variant)]
+enum State {
+    Pending(PendingQuery),
+    Completed(CompletedQuery),
+    Failure,
+}
+
+#[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
+
+    timeout_at: Option<Instant>,
+    retransmit_at: Instant,
+    delay: Duration,
+
+    server_idx: usize,
+}
+
+#[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: Vec<IpAddress, MAX_SERVER_COUNT>,
+    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.
+    ///
+    /// # Panics
+    ///
+    /// Panics if `servers.len() > MAX_SERVER_COUNT`
+    pub fn new<Q>(servers: &[IpAddress], queries: Q) -> DnsSocket<'a>
+    where
+        Q: Into<ManagedSlice<'a, Option<DnsQuery>>>,
+    {
+        DnsSocket {
+            servers: Vec::from_slice(servers).unwrap(),
+            queries: queries.into(),
+            hop_limit: None,
+        }
+    }
+
+    /// Update the list of DNS servers, will replace all existing servers
+    ///
+    /// # Panics
+    ///
+    /// Panics if `servers.len() > MAX_SERVER_COUNT`
+    pub fn update_servers(&mut self, servers: &[IpAddress]) {
+        self.servers = Vec::from_slice(servers).unwrap();
+    }
+
+    /// 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))
+            }
+        }
+    }
+
+    /// Start a query.
+    ///
+    /// `name` is specified in human-friendly format, such as `"rust-lang.org"`.
+    /// It accepts names both with and without trailing dot, and they're treated
+    /// the same (there's no support for DNS search path).
+    pub fn start_query(&mut self, cx: &mut Context, name: &str) -> Result<QueryHandle> {
+        let mut name = name.as_bytes();
+
+        if name.is_empty() {
+            net_trace!("invalid name: zero length");
+            return Err(Error::Illegal);
+        }
+
+        // Remove trailing dot, if any
+        if name[name.len() - 1] == b'.' {
+            name = &name[..name.len() - 1];
+        }
+
+        let mut raw_name: Vec<u8, MAX_NAME_LEN> = Vec::new();
+
+        for s in name.split(|&c| c == b'.') {
+            if s.len() > 255 {
+                net_trace!("invalid name: too long label");
+                return Err(Error::Illegal);
+            }
+            if s.is_empty() {
+                net_trace!("invalid name: zero length label");
+                return Err(Error::Illegal);
+            }
+
+            // Push label
+            raw_name.push(s.len() as u8).map_err(|_| Error::Exhausted)?;
+            raw_name
+                .extend_from_slice(s)
+                .map_err(|_| Error::Exhausted)?;
+        }
+
+        // Push terminator.
+        raw_name.push(0x00).map_err(|_| Error::Exhausted)?;
+
+        self.start_query_raw(cx, &raw_name)
+    }
+
+    /// Start a query with a raw (wire-format) DNS name.
+    /// `b"\x09rust-lang\x03org\x00"`
+    ///
+    /// You probably want to use [`start_query`] instead.
+    pub fn start_query_raw(&mut self, cx: &mut Context, raw_name: &[u8]) -> Result<QueryHandle> {
+        let handle = self.find_free_query()?;
+
+        self.queries[handle.0] = Some(DnsQuery {
+            state: State::Pending(PendingQuery {
+                name: Vec::from_slice(raw_name).map_err(|_| Error::Exhausted)?,
+                type_: Type::A,
+                txid: cx.rand().rand_u16(),
+                port: cx.rand().rand_source_port(),
+                delay: RETRANSMIT_DELAY,
+                timeout_at: None,
+                retransmit_at: Instant::ZERO,
+                server_idx: 0,
+            }),
+            #[cfg(feature = "async")]
+            waker: WakerRegistration::new(),
+        });
+        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)
+            }
+            State::Failure => {
+                *slot = None; // Free up the slot for recycling.
+                Err(Error::Unaddressable)
+            }
+        }
+    }
+
+    pub fn cancel_query(&mut self, handle: QueryHandle) -> Result<()> {
+        let slot = self.queries.get_mut(handle.0).ok_or(Error::Illegal)?;
+        if slot.is_none() {
+            return Err(Error::Illegal);
+        }
+        *slot = None; // Free up the slot for recycling.
+        Ok(())
+    }
+
+    #[cfg(feature = "async")]
+    pub fn register_query_waker(&mut self, handle: QueryHandle, waker: &Waker) -> Result<()> {
+        let slot = self.queries.get_mut(handle.0).ok_or(Error::Illegal)?;
+        slot.as_mut().ok_or(Error::Illegal)?.waker.register(waker);
+        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;
+                }
+
+                if p.rcode() == Rcode::NXDomain {
+                    net_trace!("rcode NXDomain");
+                    q.set_state(State::Failure);
+                    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 {
+                        #[cfg(feature = "proto-ipv4")]
+                        RecordData::A(addr) => {
+                            net_trace!("A: {:?}", addr);
+                            if addresses.push(addr.into()).is_err() {
+                                net_trace!("too many addresses in response, ignoring {:?}", addr);
+                            }
+                        }
+                        #[cfg(feature = "proto-ipv6")]
+                        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);
+
+                            // When faced with a CNAME, recursive resolvers are supposed to
+                            // resolve the CNAME and append the results for it.
+                            //
+                            // We update the query with the new name, so that we pick up the A/AAAA
+                            // records for the CNAME when we parse them later.
+                            // I believe it's mandatory the CNAME results MUST come *after* in the
+                            // packet, so it's enough to do one linear pass over it.
+                            copy_name(&mut pq.name, p.parse_name(name))?;
+                        }
+                        RecordData::Other(type_, data) => {
+                            net_trace!("unknown: {:?} {:?}", type_, data)
+                        }
+                    }
+                }
+
+                q.set_state(if addresses.is_empty() {
+                    State::Failure
+                } else {
+                    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 {
+                let timeout = if let Some(timeout) = pq.timeout_at {
+                    timeout
+                } else {
+                    let v = cx.now() + RETRANSMIT_TIMEOUT;
+                    pq.timeout_at = Some(v);
+                    v
+                };
+
+                // Check timeout
+                if timeout < cx.now() {
+                    // DNS timeout
+                    pq.timeout_at = Some(cx.now() + RETRANSMIT_TIMEOUT);
+                    pq.retransmit_at = Instant::ZERO;
+                    pq.delay = RETRANSMIT_DELAY;
+
+                    // Try next server. We check below whether we've tried all servers.
+                    pq.server_idx += 1;
+                }
+
+                // Check if we've run out of servers to try.
+                if pq.server_idx >= self.servers.len() {
+                    net_trace!("already tried all servers.");
+                    q.set_state(State::Failure);
+                    continue;
+                }
+
+                // Check so the IP address is valid
+                if self.servers[pq.server_idx].is_unspecified() {
+                    net_trace!("invalid unspecified DNS server addr.");
+                    q.set_state(State::Failure);
+                    continue;
+                }
+
+                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[pq.server_idx];
+                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,
+                State::Failure => 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, QueryHandle as DnsQueryHandle};
 #[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 {

+ 793 - 0
src/wire/dns.rs

@@ -0,0 +1,793 @@
+#![allow(dead_code)]
+
+use bitflags::bitflags;
+use byteorder::{ByteOrder, NetworkEndian};
+use core::iter;
+use core::iter::Iterator;
+
+#[cfg(feature = "proto-ipv4")]
+use crate::wire::Ipv4Address;
+#[cfg(feature = "proto-ipv6")]
+use crate::wire::Ipv6Address;
+use crate::{Error, Result};
+
+enum_with_unknown! {
+    /// DNS OpCodes
+    pub enum Opcode(u8) {
+        Query  = 0x00,
+        Status = 0x01,
+    }
+}
+enum_with_unknown! {
+    /// DNS OpCodes
+    pub enum Rcode(u8) {
+        NoError  = 0x00,
+        FormErr  = 0x01,
+        ServFail = 0x02,
+        NXDomain = 0x03,
+        NotImp   = 0x04,
+        Refused  = 0x05,
+        YXDomain = 0x06,
+        YXRRSet  = 0x07,
+        NXRRSet  = 0x08,
+        NotAuth  = 0x09,
+        NotZone  = 0x0a,
+    }
+}
+
+enum_with_unknown! {
+    /// DNS record types
+    pub enum Type(u16) {
+        A     = 0x0001,
+        Ns    = 0x0002,
+        Cname = 0x0005,
+        Soa   = 0x0006,
+        Aaaa  = 0x001c,
+    }
+}
+
+bitflags! {
+    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+    pub struct Flags: u16 {
+        const RESPONSE            = 0b1000_0000_0000_0000;
+        const AUTHORITATIVE       = 0b0000_0100_0000_0000;
+        const TRUNCATED           = 0b0000_0010_0000_0000;
+        const RECURSION_DESIRED   = 0b0000_0001_0000_0000;
+        const RECURSION_AVAILABLE = 0b0000_0000_1000_0000;
+        const AUTHENTIC_DATA      = 0b0000_0000_0010_0000;
+        const CHECK_DISABLED      = 0b0000_0000_0001_0000;
+    }
+}
+
+mod field {
+    use crate::wire::field::*;
+
+    pub const ID: Field = 0..2;
+    pub const FLAGS: Field = 2..4;
+    pub const QDCOUNT: Field = 4..6;
+    pub const ANCOUNT: Field = 6..8;
+    pub const NSCOUNT: Field = 8..10;
+    pub const ARCOUNT: Field = 10..12;
+
+    pub const HEADER_END: usize = 12;
+}
+
+// DNS class IN (Internet)
+const CLASS_IN: u16 = 1;
+
+/// A read/write wrapper around a DNS packet buffer.
+#[derive(Debug, PartialEq)]
+pub struct Packet<T: AsRef<[u8]>> {
+    buffer: T,
+}
+
+impl<T: AsRef<[u8]>> Packet<T> {
+    /// Imbue a raw octet buffer with DNS packet structure.
+    pub fn new_unchecked(buffer: T) -> Packet<T> {
+        Packet { buffer }
+    }
+
+    /// Shorthand for a combination of [new_unchecked] and [check_len].
+    ///
+    /// [new_unchecked]: #method.new_unchecked
+    /// [check_len]: #method.check_len
+    pub fn new_checked(buffer: T) -> Result<Packet<T>> {
+        let packet = Self::new_unchecked(buffer);
+        packet.check_len()?;
+        Ok(packet)
+    }
+
+    /// Ensure that no accessor method will panic if called.
+    /// Returns `Err(Error::Malformed)` if the buffer is smaller than
+    /// the header length.
+    pub fn check_len(&self) -> Result<()> {
+        let len = self.buffer.as_ref().len();
+        if len < field::HEADER_END {
+            Err(Error::Malformed)
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Consume the packet, returning the underlying buffer.
+    pub fn into_inner(self) -> T {
+        self.buffer
+    }
+
+    pub fn payload(&self) -> &[u8] {
+        &self.buffer.as_ref()[field::HEADER_END..]
+    }
+
+    pub fn transaction_id(&self) -> u16 {
+        let field = &self.buffer.as_ref()[field::ID];
+        NetworkEndian::read_u16(field)
+    }
+
+    pub fn flags(&self) -> Flags {
+        let field = &self.buffer.as_ref()[field::FLAGS];
+        Flags::from_bits_truncate(NetworkEndian::read_u16(field))
+    }
+
+    pub fn opcode(&self) -> Opcode {
+        let field = &self.buffer.as_ref()[field::FLAGS];
+        let flags = NetworkEndian::read_u16(field);
+        Opcode::from((flags >> 11 & 0xF) as u8)
+    }
+
+    pub fn rcode(&self) -> Rcode {
+        let field = &self.buffer.as_ref()[field::FLAGS];
+        let flags = NetworkEndian::read_u16(field);
+        Rcode::from((flags & 0xF) as u8)
+    }
+
+    pub fn question_count(&self) -> u16 {
+        let field = &self.buffer.as_ref()[field::QDCOUNT];
+        NetworkEndian::read_u16(field)
+    }
+
+    pub fn answer_record_count(&self) -> u16 {
+        let field = &self.buffer.as_ref()[field::ANCOUNT];
+        NetworkEndian::read_u16(field)
+    }
+
+    pub fn authority_record_count(&self) -> u16 {
+        let field = &self.buffer.as_ref()[field::NSCOUNT];
+        NetworkEndian::read_u16(field)
+    }
+
+    pub fn additional_record_count(&self) -> u16 {
+        let field = &self.buffer.as_ref()[field::ARCOUNT];
+        NetworkEndian::read_u16(field)
+    }
+
+    /// Parse part of a name from `bytes`, following pointers if any.
+    pub fn parse_name<'a>(&'a self, mut bytes: &'a [u8]) -> impl Iterator<Item = Result<&'a [u8]>> {
+        let mut packet = self.buffer.as_ref();
+
+        iter::from_fn(move || loop {
+            if bytes.is_empty() {
+                return Some(Err(Error::Malformed));
+            }
+            match bytes[0] {
+                0x00 => return None,
+                x if x & 0xC0 == 0x00 => {
+                    let len = (x & 0x3F) as usize;
+                    if bytes.len() < 1 + len {
+                        return Some(Err(Error::Malformed));
+                    }
+                    let label = &bytes[1..1 + len];
+                    bytes = &bytes[1 + len..];
+                    return Some(Ok(label));
+                }
+                x if x & 0xC0 == 0xC0 => {
+                    if bytes.len() < 2 {
+                        return Some(Err(Error::Malformed));
+                    }
+                    let y = bytes[1];
+                    let ptr = ((x & 0x3F) as usize) << 8 | (y as usize);
+                    if packet.len() <= ptr {
+                        return Some(Err(Error::Malformed));
+                    }
+
+                    // RFC1035 says: "In this scheme, an entire domain name or a list of labels at
+                    //      the end of a domain name is replaced with a pointer to a ***prior*** occurance
+                    //      of the same name.
+                    //
+                    // Is it unclear if this means the pointer MUST point backwards in the packet or not. Either way,
+                    // pointers that don't point backwards are never seen in the fields, so use this to check that
+                    // there are no pointer loops.
+
+                    // Split packet into parts before and after `ptr`.
+                    // parse the part after, keep only the part before in `packet`. This ensure we never
+                    // parse the same byte twice, therefore eliminating pointer loops.
+
+                    bytes = &packet[ptr..];
+                    packet = &packet[..ptr];
+                }
+                _ => return Some(Err(Error::Malformed)),
+            }
+        })
+    }
+}
+
+impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
+    pub fn payload_mut(&mut self) -> &mut [u8] {
+        let data = self.buffer.as_mut();
+        &mut data[field::HEADER_END..]
+    }
+
+    pub fn set_transaction_id(&mut self, val: u16) {
+        let field = &mut self.buffer.as_mut()[field::ID];
+        NetworkEndian::write_u16(field, val)
+    }
+
+    pub fn set_flags(&mut self, val: Flags) {
+        let field = &mut self.buffer.as_mut()[field::FLAGS];
+        let mask = Flags::all().bits;
+        let old = NetworkEndian::read_u16(field);
+        NetworkEndian::write_u16(field, (old & !mask) | val.bits());
+    }
+
+    pub fn set_opcode(&mut self, val: Opcode) {
+        let field = &mut self.buffer.as_mut()[field::FLAGS];
+        let mask = 0x3800;
+        let val: u8 = val.into();
+        let val = (val as u16) << 11;
+        let old = NetworkEndian::read_u16(field);
+        NetworkEndian::write_u16(field, (old & !mask) | val);
+    }
+
+    pub fn set_question_count(&mut self, val: u16) {
+        let field = &mut self.buffer.as_mut()[field::QDCOUNT];
+        NetworkEndian::write_u16(field, val)
+    }
+    pub fn set_answer_record_count(&mut self, val: u16) {
+        let field = &mut self.buffer.as_mut()[field::ANCOUNT];
+        NetworkEndian::write_u16(field, val)
+    }
+    pub fn set_authority_record_count(&mut self, val: u16) {
+        let field = &mut self.buffer.as_mut()[field::NSCOUNT];
+        NetworkEndian::write_u16(field, val)
+    }
+    pub fn set_additional_record_count(&mut self, val: u16) {
+        let field = &mut self.buffer.as_mut()[field::ARCOUNT];
+        NetworkEndian::write_u16(field, val)
+    }
+}
+
+/// Parse part of a name from `bytes`, not following pointers.
+/// Returns the unused part of `bytes`, and the pointer offset if the sequence ends with a pointer.
+fn parse_name_part<'a>(
+    mut bytes: &'a [u8],
+    mut f: impl FnMut(&'a [u8]),
+) -> Result<(&'a [u8], Option<usize>)> {
+    loop {
+        let x = *bytes.get(0).ok_or(Error::Malformed)?;
+        bytes = &bytes[1..];
+        match x {
+            0x00 => return Ok((bytes, None)),
+            x if x & 0xC0 == 0x00 => {
+                let len = (x & 0x3F) as usize;
+                let label = bytes.get(..len).ok_or(Error::Malformed)?;
+                bytes = &bytes[len..];
+                f(label);
+            }
+            x if x & 0xC0 == 0xC0 => {
+                let y = *bytes.get(0).ok_or(Error::Malformed)?;
+                bytes = &bytes[1..];
+
+                let ptr = ((x & 0x3F) as usize) << 8 | (y as usize);
+                return Ok((bytes, Some(ptr)));
+            }
+            _ => return Err(Error::Malformed),
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Question<'a> {
+    pub name: &'a [u8],
+    pub type_: Type,
+}
+
+impl<'a> Question<'a> {
+    pub fn parse(buffer: &'a [u8]) -> Result<(&'a [u8], Question<'a>)> {
+        let (rest, _) = parse_name_part(buffer, |_| ())?;
+        let name = &buffer[..buffer.len() - rest.len()];
+
+        if rest.len() < 4 {
+            return Err(Error::Malformed);
+        }
+        let type_ = NetworkEndian::read_u16(&rest[0..2]).into();
+        let class = NetworkEndian::read_u16(&rest[2..4]);
+        let rest = &rest[4..];
+
+        if class != CLASS_IN {
+            return Err(Error::Malformed);
+        }
+
+        Ok((rest, Question { name, type_ }))
+    }
+
+    /// Return the length of a packet that will be emitted from this high-level representation.
+    pub fn buffer_len(&self) -> usize {
+        self.name.len() + 4
+    }
+
+    /// Emit a high-level representation into a DNS packet.
+    pub fn emit(&self, packet: &mut [u8]) {
+        packet[..self.name.len()].copy_from_slice(self.name);
+        let rest = &mut packet[self.name.len()..];
+        NetworkEndian::write_u16(&mut rest[0..2], self.type_.into());
+        NetworkEndian::write_u16(&mut rest[2..4], CLASS_IN);
+    }
+}
+
+#[derive(Debug, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Record<'a> {
+    pub name: &'a [u8],
+    pub ttl: u32,
+    pub data: RecordData<'a>,
+}
+
+impl<'a> RecordData<'a> {
+    pub fn parse(type_: Type, data: &'a [u8]) -> Result<RecordData<'a>> {
+        match type_ {
+            #[cfg(feature = "proto-ipv4")]
+            Type::A => {
+                if data.len() != 4 {
+                    return Err(Error::Malformed);
+                }
+                Ok(RecordData::A(Ipv4Address::from_bytes(data)))
+            }
+            #[cfg(feature = "proto-ipv6")]
+            Type::Aaaa => {
+                if data.len() != 16 {
+                    return Err(Error::Malformed);
+                }
+                Ok(RecordData::Aaaa(Ipv6Address::from_bytes(data)))
+            }
+            Type::Cname => Ok(RecordData::Cname(data)),
+            x => Ok(RecordData::Other(x, data)),
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum RecordData<'a> {
+    #[cfg(feature = "proto-ipv4")]
+    A(Ipv4Address),
+    #[cfg(feature = "proto-ipv6")]
+    Aaaa(Ipv6Address),
+    Cname(&'a [u8]),
+    Other(Type, &'a [u8]),
+}
+
+impl<'a> Record<'a> {
+    pub fn parse(buffer: &'a [u8]) -> Result<(&'a [u8], Record<'a>)> {
+        let (rest, _) = parse_name_part(buffer, |_| ())?;
+        let name = &buffer[..buffer.len() - rest.len()];
+
+        if rest.len() < 10 {
+            return Err(Error::Malformed);
+        }
+        let type_ = NetworkEndian::read_u16(&rest[0..2]).into();
+        let class = NetworkEndian::read_u16(&rest[2..4]);
+        let ttl = NetworkEndian::read_u32(&rest[4..8]);
+        let len = NetworkEndian::read_u16(&rest[8..10]) as usize;
+        let rest = &rest[10..];
+
+        if class != CLASS_IN {
+            return Err(Error::Malformed);
+        }
+
+        let data = rest.get(..len).ok_or(Error::Malformed)?;
+        let rest = &rest[len..];
+
+        Ok((
+            rest,
+            Record {
+                name,
+                ttl,
+                data: RecordData::parse(type_, data)?,
+            },
+        ))
+    }
+}
+
+/// High-level DNS packet representation.
+///
+/// Currently only supports query packets.
+#[derive(Debug, PartialEq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub struct Repr<'a> {
+    pub transaction_id: u16,
+    pub opcode: Opcode,
+    pub flags: Flags,
+    pub question: Question<'a>,
+}
+
+impl<'a> Repr<'a> {
+    /// Return the length of a packet that will be emitted from this high-level representation.
+    pub fn buffer_len(&self) -> usize {
+        field::HEADER_END + self.question.buffer_len()
+    }
+
+    /// Emit a high-level representation into a DNS packet.
+    pub fn emit<T: ?Sized>(&self, packet: &mut Packet<&mut T>)
+    where
+        T: AsRef<[u8]> + AsMut<[u8]>,
+    {
+        packet.set_transaction_id(self.transaction_id);
+        packet.set_flags(self.flags);
+        packet.set_opcode(self.opcode);
+        packet.set_question_count(1);
+        packet.set_answer_record_count(0);
+        packet.set_authority_record_count(0);
+        packet.set_additional_record_count(0);
+        self.question.emit(packet.payload_mut())
+    }
+}
+
+#[cfg(feature = "proto-ipv4")] // tests assume ipv4
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::vec::Vec;
+
+    #[test]
+    fn test_parse_name() {
+        let bytes = &[
+            0x78, 0x6c, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77,
+            0x77, 0x77, 0x08, 0x66, 0x61, 0x63, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x03, 0x63, 0x6f,
+            0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00,
+            0x05, 0xf3, 0x00, 0x11, 0x09, 0x73, 0x74, 0x61, 0x72, 0x2d, 0x6d, 0x69, 0x6e, 0x69,
+            0x04, 0x63, 0x31, 0x30, 0x72, 0xc0, 0x10, 0xc0, 0x2e, 0x00, 0x01, 0x00, 0x01, 0x00,
+            0x00, 0x00, 0x05, 0x00, 0x04, 0x1f, 0x0d, 0x53, 0x24,
+        ];
+        let packet = Packet::new_unchecked(bytes);
+
+        let name_vec = |bytes| {
+            let mut v = Vec::new();
+            packet
+                .parse_name(bytes)
+                .try_for_each(|label| label.map(|label| v.push(label)))
+                .map(|_| v)
+        };
+
+        //assert_eq!(parse_name_len(bytes, 0x0c), Ok(18));
+        assert_eq!(
+            name_vec(&bytes[0x0c..]),
+            Ok(vec![&b"www"[..], &b"facebook"[..], &b"com"[..]])
+        );
+        //assert_eq!(parse_name_len(bytes, 0x22), Ok(2));
+        assert_eq!(
+            name_vec(&bytes[0x22..]),
+            Ok(vec![&b"www"[..], &b"facebook"[..], &b"com"[..]])
+        );
+        //assert_eq!(parse_name_len(bytes, 0x2e), Ok(17));
+        assert_eq!(
+            name_vec(&bytes[0x2e..]),
+            Ok(vec![
+                &b"star-mini"[..],
+                &b"c10r"[..],
+                &b"facebook"[..],
+                &b"com"[..]
+            ])
+        );
+        //assert_eq!(parse_name_len(bytes, 0x3f), Ok(2));
+        assert_eq!(
+            name_vec(&bytes[0x3f..]),
+            Ok(vec![
+                &b"star-mini"[..],
+                &b"c10r"[..],
+                &b"facebook"[..],
+                &b"com"[..]
+            ])
+        );
+    }
+
+    struct Parsed<'a> {
+        packet: Packet<&'a [u8]>,
+        questions: Vec<Question<'a>>,
+        answers: Vec<Record<'a>>,
+        authorities: Vec<Record<'a>>,
+        additionals: Vec<Record<'a>>,
+    }
+
+    impl<'a> Parsed<'a> {
+        fn parse(bytes: &'a [u8]) -> Result<Self> {
+            let packet = Packet::new_unchecked(bytes);
+            let mut questions = Vec::new();
+            let mut answers = Vec::new();
+            let mut authorities = Vec::new();
+            let mut additionals = Vec::new();
+
+            let mut payload = &bytes[12..];
+
+            for _ in 0..packet.question_count() {
+                let (p, r) = Question::parse(payload)?;
+                questions.push(r);
+                payload = p;
+            }
+            for _ in 0..packet.answer_record_count() {
+                let (p, r) = Record::parse(payload)?;
+                answers.push(r);
+                payload = p;
+            }
+            for _ in 0..packet.authority_record_count() {
+                let (p, r) = Record::parse(payload)?;
+                authorities.push(r);
+                payload = p;
+            }
+            for _ in 0..packet.additional_record_count() {
+                let (p, r) = Record::parse(payload)?;
+                additionals.push(r);
+                payload = p;
+            }
+
+            // Check that there are no bytes left
+            assert_eq!(payload.len(), 0);
+
+            Ok(Parsed {
+                packet,
+                questions,
+                answers,
+                authorities,
+                additionals,
+            })
+        }
+    }
+
+    #[test]
+    fn test_parse_request() {
+        let p = Parsed::parse(&[
+            0x51, 0x84, 0x01, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x67,
+            0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
+        ])
+        .unwrap();
+
+        assert_eq!(p.packet.transaction_id(), 0x5184);
+        assert_eq!(
+            p.packet.flags(),
+            Flags::RECURSION_DESIRED | Flags::AUTHENTIC_DATA
+        );
+        assert_eq!(p.packet.opcode(), Opcode::Query);
+        assert_eq!(p.packet.question_count(), 1);
+        assert_eq!(p.packet.answer_record_count(), 0);
+        assert_eq!(p.packet.authority_record_count(), 0);
+        assert_eq!(p.packet.additional_record_count(), 0);
+
+        assert_eq!(p.questions.len(), 1);
+        assert_eq!(
+            p.questions[0].name,
+            &[0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00]
+        );
+        assert_eq!(p.questions[0].type_, Type::A);
+
+        assert_eq!(p.answers.len(), 0);
+        assert_eq!(p.authorities.len(), 0);
+        assert_eq!(p.additionals.len(), 0);
+    }
+
+    #[test]
+    fn test_parse_response() {
+        let p = Parsed::parse(&[
+            0x51, 0x84, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x67,
+            0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
+            0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xca, 0x00, 0x04, 0xac, 0xd9,
+            0xa8, 0xae,
+        ])
+        .unwrap();
+
+        assert_eq!(p.packet.transaction_id(), 0x5184);
+        assert_eq!(
+            p.packet.flags(),
+            Flags::RESPONSE | Flags::RECURSION_DESIRED | Flags::RECURSION_AVAILABLE
+        );
+        assert_eq!(p.packet.opcode(), Opcode::Query);
+        assert_eq!(p.packet.rcode(), Rcode::NoError);
+        assert_eq!(p.packet.question_count(), 1);
+        assert_eq!(p.packet.answer_record_count(), 1);
+        assert_eq!(p.packet.authority_record_count(), 0);
+        assert_eq!(p.packet.additional_record_count(), 0);
+
+        assert_eq!(
+            p.questions[0].name,
+            &[0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00]
+        );
+        assert_eq!(p.questions[0].type_, Type::A);
+
+        assert_eq!(p.answers[0].name, &[0xc0, 0x0c]);
+        assert_eq!(p.answers[0].ttl, 202);
+        assert_eq!(
+            p.answers[0].data,
+            RecordData::A(Ipv4Address::new(0xac, 0xd9, 0xa8, 0xae))
+        );
+    }
+
+    #[test]
+    fn test_parse_response_multiple_a() {
+        let p = Parsed::parse(&[
+            0x4b, 0x9e, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x09, 0x72,
+            0x75, 0x73, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x03, 0x6f, 0x72, 0x67, 0x00, 0x00,
+            0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00,
+            0x04, 0x0d, 0xe0, 0x77, 0x35, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
+            0x09, 0x00, 0x04, 0x0d, 0xe0, 0x77, 0x28, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
+            0x00, 0x00, 0x09, 0x00, 0x04, 0x0d, 0xe0, 0x77, 0x43, 0xc0, 0x0c, 0x00, 0x01, 0x00,
+            0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x04, 0x0d, 0xe0, 0x77, 0x62,
+        ])
+        .unwrap();
+
+        assert_eq!(p.packet.transaction_id(), 0x4b9e);
+        assert_eq!(
+            p.packet.flags(),
+            Flags::RESPONSE | Flags::RECURSION_DESIRED | Flags::RECURSION_AVAILABLE
+        );
+        assert_eq!(p.packet.opcode(), Opcode::Query);
+        assert_eq!(p.packet.rcode(), Rcode::NoError);
+        assert_eq!(p.packet.question_count(), 1);
+        assert_eq!(p.packet.answer_record_count(), 4);
+        assert_eq!(p.packet.authority_record_count(), 0);
+        assert_eq!(p.packet.additional_record_count(), 0);
+
+        assert_eq!(
+            p.questions[0].name,
+            &[
+                0x09, 0x72, 0x75, 0x73, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x03, 0x6f, 0x72, 0x67,
+                0x00
+            ]
+        );
+        assert_eq!(p.questions[0].type_, Type::A);
+
+        assert_eq!(p.answers[0].name, &[0xc0, 0x0c]);
+        assert_eq!(p.answers[0].ttl, 9);
+        assert_eq!(
+            p.answers[0].data,
+            RecordData::A(Ipv4Address::new(0x0d, 0xe0, 0x77, 0x35))
+        );
+
+        assert_eq!(p.answers[1].name, &[0xc0, 0x0c]);
+        assert_eq!(p.answers[1].ttl, 9);
+        assert_eq!(
+            p.answers[1].data,
+            RecordData::A(Ipv4Address::new(0x0d, 0xe0, 0x77, 0x28))
+        );
+
+        assert_eq!(p.answers[2].name, &[0xc0, 0x0c]);
+        assert_eq!(p.answers[2].ttl, 9);
+        assert_eq!(
+            p.answers[2].data,
+            RecordData::A(Ipv4Address::new(0x0d, 0xe0, 0x77, 0x43))
+        );
+
+        assert_eq!(p.answers[3].name, &[0xc0, 0x0c]);
+        assert_eq!(p.answers[3].ttl, 9);
+        assert_eq!(
+            p.answers[3].data,
+            RecordData::A(Ipv4Address::new(0x0d, 0xe0, 0x77, 0x62))
+        );
+    }
+
+    #[test]
+    fn test_parse_response_cname() {
+        let p = Parsed::parse(&[
+            0x78, 0x6c, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77,
+            0x77, 0x77, 0x08, 0x66, 0x61, 0x63, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x03, 0x63, 0x6f,
+            0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00,
+            0x05, 0xf3, 0x00, 0x11, 0x09, 0x73, 0x74, 0x61, 0x72, 0x2d, 0x6d, 0x69, 0x6e, 0x69,
+            0x04, 0x63, 0x31, 0x30, 0x72, 0xc0, 0x10, 0xc0, 0x2e, 0x00, 0x01, 0x00, 0x01, 0x00,
+            0x00, 0x00, 0x05, 0x00, 0x04, 0x1f, 0x0d, 0x53, 0x24,
+        ])
+        .unwrap();
+
+        assert_eq!(p.packet.transaction_id(), 0x786c);
+        assert_eq!(
+            p.packet.flags(),
+            Flags::RESPONSE | Flags::RECURSION_DESIRED | Flags::RECURSION_AVAILABLE
+        );
+        assert_eq!(p.packet.opcode(), Opcode::Query);
+        assert_eq!(p.packet.rcode(), Rcode::NoError);
+        assert_eq!(p.packet.question_count(), 1);
+        assert_eq!(p.packet.answer_record_count(), 2);
+        assert_eq!(p.packet.authority_record_count(), 0);
+        assert_eq!(p.packet.additional_record_count(), 0);
+
+        assert_eq!(
+            p.questions[0].name,
+            &[
+                0x03, 0x77, 0x77, 0x77, 0x08, 0x66, 0x61, 0x63, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x03,
+                0x63, 0x6f, 0x6d, 0x00
+            ]
+        );
+        assert_eq!(p.questions[0].type_, Type::A);
+
+        // cname
+        assert_eq!(p.answers[0].name, &[0xc0, 0x0c]);
+        assert_eq!(p.answers[0].ttl, 1523);
+        assert_eq!(
+            p.answers[0].data,
+            RecordData::Cname(&[
+                0x09, 0x73, 0x74, 0x61, 0x72, 0x2d, 0x6d, 0x69, 0x6e, 0x69, 0x04, 0x63, 0x31, 0x30,
+                0x72, 0xc0, 0x10
+            ])
+        );
+        // a
+        assert_eq!(p.answers[1].name, &[0xc0, 0x2e]);
+        assert_eq!(p.answers[1].ttl, 5);
+        assert_eq!(
+            p.answers[1].data,
+            RecordData::A(Ipv4Address::new(0x1f, 0x0d, 0x53, 0x24))
+        );
+    }
+
+    #[test]
+    fn test_parse_response_nxdomain() {
+        let p = Parsed::parse(&[
+            0x63, 0xc4, 0x81, 0x83, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x13, 0x61,
+            0x68, 0x61, 0x73, 0x64, 0x67, 0x68, 0x6c, 0x61, 0x6b, 0x73, 0x6a, 0x68, 0x62, 0x61,
+            0x61, 0x73, 0x6c, 0x64, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0,
+            0x20, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x03, 0x83, 0x00, 0x3d, 0x01, 0x61, 0x0c,
+            0x67, 0x74, 0x6c, 0x64, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x03, 0x6e,
+            0x65, 0x74, 0x00, 0x05, 0x6e, 0x73, 0x74, 0x6c, 0x64, 0x0c, 0x76, 0x65, 0x72, 0x69,
+            0x73, 0x69, 0x67, 0x6e, 0x2d, 0x67, 0x72, 0x73, 0xc0, 0x20, 0x5f, 0xce, 0x8b, 0x85,
+            0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x03, 0x84, 0x00, 0x09, 0x3a, 0x80, 0x00, 0x01,
+            0x51, 0x80,
+        ])
+        .unwrap();
+
+        assert_eq!(p.packet.transaction_id(), 0x63c4);
+        assert_eq!(
+            p.packet.flags(),
+            Flags::RESPONSE | Flags::RECURSION_DESIRED | Flags::RECURSION_AVAILABLE
+        );
+        assert_eq!(p.packet.opcode(), Opcode::Query);
+        assert_eq!(p.packet.rcode(), Rcode::NXDomain);
+        assert_eq!(p.packet.question_count(), 1);
+        assert_eq!(p.packet.answer_record_count(), 0);
+        assert_eq!(p.packet.authority_record_count(), 1);
+        assert_eq!(p.packet.additional_record_count(), 0);
+
+        assert_eq!(p.questions[0].type_, Type::A);
+
+        // SOA authority
+        assert_eq!(p.authorities[0].name, &[0xc0, 0x20]); // com.
+        assert_eq!(p.authorities[0].ttl, 899);
+        assert!(matches!(
+            p.authorities[0].data,
+            RecordData::Other(Type::Soa, _)
+        ));
+    }
+
+    #[test]
+    fn test_emit() {
+        let name = &[
+            0x09, 0x72, 0x75, 0x73, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x03, 0x6f, 0x72, 0x67,
+            0x00,
+        ];
+
+        let repr = Repr {
+            transaction_id: 0x1234,
+            flags: Flags::RECURSION_DESIRED,
+            opcode: Opcode::Query,
+            question: Question {
+                name,
+                type_: Type::A,
+            },
+        };
+
+        let mut buf = Vec::new();
+        buf.resize(repr.buffer_len(), 0);
+        repr.emit(&mut Packet::new_unchecked(&mut buf));
+
+        let want = &[
+            0x12, 0x34, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x72,
+            0x75, 0x73, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x03, 0x6f, 0x72, 0x67, 0x00, 0x00,
+            0x01, 0x00, 0x01,
+        ];
+        assert_eq!(&buf, want);
+    }
+}

+ 2 - 0
src/wire/mod.rs

@@ -81,6 +81,8 @@ pub mod pretty_print;
 mod arp;
 #[cfg(feature = "proto-dhcpv4")]
 pub(crate) mod dhcpv4;
+#[cfg(feature = "proto-dns")]
+pub(crate) mod dns;
 #[cfg(feature = "medium-ethernet")]
 mod ethernet;
 #[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]