w5500_dns/
lib.rs

1//! DNS client for the [Wiznet W5500] SPI internet offload chip.
2//!
3//! # Warning
4//!
5//! Please proceed with caution, and review the code before use in a production
6//! environment.
7//!
8//! This code was developed for one-off hobby projects.
9//!
10//! # Limitations
11//!
12//! * No DNS caching.
13//! * Only supports A queries.
14//! * Only supports a single outstanding query.
15//! * Only supports a single question in a query.
16//!
17//! # Example
18//!
19//! ```no_run
20//! # let mut w5500 = w5500_regsim::W5500::default();
21//! # let random_number: u64 = 0;
22//! use w5500_dns::{hl::block, ll::Sn, servers, Client as DnsClient, Hostname, Response};
23//!
24//! const DNS_SOCKET: Sn = Sn::Sn3;
25//! const DNS_SRC_PORT: u16 = 45917;
26//!
27//! let mut dns_client: DnsClient =
28//!     DnsClient::new(DNS_SOCKET, DNS_SRC_PORT, servers::CLOUDFLARE, random_number);
29//! let hostname: Hostname = Hostname::new("docs.rs").expect("hostname is invalid");
30//!
31//! let mut hostname_buffer: [u8; 16] = [0; 16];
32//!
33//! let query_id: u16 = dns_client.a_question(&mut w5500, &hostname)?;
34//! let mut response: Response<_> =
35//!     block!(dns_client.response(&mut w5500, &mut hostname_buffer, query_id))?;
36//!
37//! while let Some(rr) = response.next_rr()? {
38//!     println!("name: {:?}", rr.name);
39//!     println!("TTL: {}", rr.ttl);
40//!     println!("IP: {:?}", rr.rdata);
41//! }
42//! response.done()?;
43//! # Ok::<(), w5500_hl::Error<std::io::ErrorKind>>(())
44//! ```
45//!
46//! # Relevant Specifications
47//!
48//! * [RFC 1035](https://www.rfc-editor.org/rfc/rfc1035)
49//!
50//! # Feature Flags
51//!
52//! All features are disabled by default.
53//!
54//! * `eh0`: Passthrough to [`w5500-hl`].
55//! * `eh1`: Passthrough to [`w5500-hl`].
56//! * `defmt`: Enable logging with `defmt`. Also a passthrough to [`w5500-hl`].
57//! * `log`: Enable logging with `log`.
58//!
59//! [`w5500-hl`]: https://crates.io/crates/w5500-hl
60//! [Wiznet W5500]: https://www.wiznet.io/product-item/w5500/
61#![cfg_attr(docsrs, feature(doc_cfg), feature(doc_auto_cfg))]
62#![cfg_attr(not(test), no_std)]
63#![deny(unsafe_code)]
64#![warn(missing_docs)]
65
66// This mod MUST go first, so that the others see its macros.
67pub(crate) mod fmt;
68
69mod header;
70pub mod mdns;
71mod qclass;
72mod qtype;
73mod rand;
74
75pub use header::ResponseCode;
76use header::{Header, Qr};
77pub use hl::Hostname;
78use hl::{
79    io::{Read, Seek, SeekFrom, Write},
80    Error, Udp, UdpReader, UdpWriter,
81};
82use ll::{
83    net::{Ipv4Addr, SocketAddrV4},
84    Sn,
85};
86pub use qclass::Qclass;
87pub use qtype::Qtype;
88pub use w5500_hl as hl;
89pub use w5500_hl::ll;
90
91/// DNS destination port.
92pub const DST_PORT: u16 = 53;
93
94/// Commonly used public DNS servers.
95///
96/// When using DHCP it is typically a good idea to use the DNS server provided
97/// by the DHCP server.
98pub mod servers {
99    use super::Ipv4Addr;
100
101    /// Cloudflare's public DNS.
102    ///
103    /// More information: <https://www.cloudflare.com/en-gb/learning/dns/what-is-1.1.1.1/>
104    pub const CLOUDFLARE: Ipv4Addr = Ipv4Addr::new(1, 1, 1, 1);
105    /// Google's public DNS.
106    ///
107    /// More information: <https://developers.google.com/speed/public-dns>
108    pub const GOOGLE_1: Ipv4Addr = Ipv4Addr::new(8, 8, 8, 8);
109    /// Google's alternate public DNS.
110    ///
111    /// More information: <https://developers.google.com/speed/public-dns>
112    pub const GOOGLE_2: Ipv4Addr = Ipv4Addr::new(8, 8, 4, 4);
113}
114
115/// Decoded fields from SRV rdata.
116#[derive(Debug, PartialEq, Eq)]
117#[cfg_attr(feature = "defmt", derive(defmt::Format))]
118pub struct ServiceData {
119    /// The priority of this target host.
120    pub priority: u16,
121    /// A server selection mechanism.
122    pub weight: u16,
123    /// The port on this target host of this service.
124    pub port: u16,
125}
126
127/// Decoded rdata.
128#[derive(Debug, PartialEq, Eq, Default)]
129#[cfg_attr(feature = "defmt", derive(defmt::Format))]
130pub enum RData {
131    /// Decoded rdata for A records.
132    A(Ipv4Addr),
133    /// Decoded rdata for SVR records.
134    SVR(ServiceData),
135    /// Record type's rdata is unsupported.
136    #[default]
137    Unsupported,
138}
139
140/// DNS server resource records.
141///
142/// This is created by [`Response::next_rr`].
143///
144/// # References
145///
146/// * [RFC 1035 Section 3.2](https://datatracker.ietf.org/doc/html/rfc1035#section-3.2)
147/// * [RFC 1035 Section 4.1.4](https://www.rfc-editor.org/rfc/rfc1035#section-4.1.4)
148#[derive(Debug, PartialEq, Eq)]
149#[cfg_attr(feature = "defmt", derive(defmt::Format))]
150pub struct ResourceRecord<'a> {
151    /// A domain name to which this resource record pertains.
152    ///
153    /// This will be `None` if the domain name contains invalid characters or if
154    /// the provided buffer was not large enough to contain the entire name.
155    pub name: Option<&'a str>,
156    /// Resource record type.
157    ///
158    /// If the value from the DNS server does not match a valid [`Qtype`] the
159    /// value will be returned in the `Err` variant.
160    pub qtype: Result<Qtype, u16>,
161    /// Resource record class.
162    ///
163    /// Only internet records are supported at the moment, which means we can
164    /// assume this is `Ok(Qclass::IN)` if the DNS server is operating correctly.
165    ///
166    /// If the value from the DNS server does not match a valid [`Qtype`] the
167    /// value will be returned in the `Err` variant.
168    pub class: Result<Qclass, u16>,
169    /// Time to live.
170    ///
171    /// The time interval that the resource record may be cached before the
172    /// source of the information should again be consulted.
173    /// Zero values are interpreted to mean that the RR can only be used for the
174    /// transaction in progress, and should not be cached.
175    /// For example, SOA records are always distributed with a zero TTL to
176    /// prohibit caching.  Zero values can also be used for extremely volatile
177    /// data.
178    pub ttl: u32,
179    /// Resource record data.
180    ///
181    /// Only the RDATA in A and SVR records is supported at the moment.
182    ///
183    /// This is `RData::Unsupported` if the rdata is not from a supported record type.
184    pub rdata: RData,
185}
186
187// https://www.rfc-editor.org/rfc/rfc1035#section-4.1.4
188fn read_labels<'l, E, Reader: Read<E> + Seek>(
189    reader: &mut Reader,
190    labels: &'l mut [u8],
191) -> Result<Option<&'l str>, Error<E>> {
192    const NAME_PTR_MASK: u16 = 0xC0_00;
193
194    let mut labels_idx: usize = 0;
195    let mut seek_to: u16 = 0;
196
197    loop {
198        let mut buf: [u8; 2] = [0; 2];
199        let n: u16 = reader.read(&mut buf)?;
200        if n == 0 {
201            return Err(Error::UnexpectedEof);
202        }
203
204        let sequence: u16 = u16::from_be_bytes(buf);
205
206        // if pointer
207        if n == 2 && sequence & NAME_PTR_MASK != 0 {
208            let ptr: u16 = sequence & !NAME_PTR_MASK;
209            if seek_to == 0 {
210                seek_to = reader.stream_position();
211            }
212            reader.seek(SeekFrom::Start(ptr))?;
213        } else {
214            // seek back to the start of the label
215            reader.seek(SeekFrom::Current(-1))?;
216
217            let len: u8 = buf[0] & 0x3F;
218            if len == 0 {
219                break;
220            }
221
222            if labels_idx != 0 {
223                if let Some(b) = labels.get_mut(labels_idx) {
224                    *b = b'.';
225                }
226                labels_idx += 1;
227            }
228
229            if let Some(label_buf) = labels.get_mut(labels_idx..(labels_idx + usize::from(len))) {
230                reader.read_exact(label_buf)?;
231            } else {
232                reader.seek(SeekFrom::Current(len.into()))?;
233            }
234            labels_idx += usize::from(len);
235        }
236    }
237
238    if seek_to != 0 {
239        reader.seek(SeekFrom::Start(seek_to))?;
240    }
241
242    if let Some(name_buf) = labels.get(..labels_idx) {
243        Ok(core::str::from_utf8(name_buf).ok())
244    } else {
245        Ok(None)
246    }
247}
248
249fn read_u16<E, Reader: Read<E>>(reader: &mut Reader) -> Result<u16, Error<E>> {
250    let v: u16 = {
251        let mut buf: [u8; 2] = [0; core::mem::size_of::<u16>()];
252        reader.read_exact(&mut buf)?;
253        u16::from_be_bytes(buf)
254    };
255    Ok(v)
256}
257
258fn read_u32<E, Reader: Read<E>>(reader: &mut Reader) -> Result<u32, Error<E>> {
259    let v: u32 = {
260        let mut buf: [u8; 4] = [0; 4];
261        reader.read_exact(&mut buf)?;
262        u32::from_be_bytes(buf)
263    };
264    Ok(v)
265}
266
267/// DNS response from the server.
268///
269/// This is created with [`Client::response`].
270#[derive(Debug)]
271#[cfg_attr(feature = "defmt", derive(defmt::Format))]
272pub struct Response<'a, W5500: Udp> {
273    reader: UdpReader<'a, W5500>,
274    header: Header,
275    buf: &'a mut [u8],
276    rr_idx: u16,
277}
278
279impl<'a, W5500: Udp> Response<'a, W5500> {
280    /// Response code from the server.
281    ///
282    /// This will return `Err(u8)` if the server uses a reserved value.
283    pub fn response_code(&self) -> Result<ResponseCode, u8> {
284        self.header.rcode()
285    }
286
287    /// Number of answers in the response.
288    #[must_use]
289    pub fn answer_count(&self) -> u16 {
290        self.header.ancount()
291    }
292
293    /// Number of resource records in the response.
294    #[must_use]
295    pub fn rr_count(&self) -> u16 {
296        self.header
297            .ancount()
298            .saturating_add(self.header.nscount())
299            .saturating_add(self.header.arcount())
300    }
301
302    /// Get the next resource record from the DNS response.
303    ///
304    /// # Errors
305    ///
306    /// This method can only return:
307    ///
308    /// * [`Error::Other`]
309    /// * [`Error::UnexpectedEof`]
310    ///
311    /// If any error occurs the entire response should be discarded,
312    /// and you should not continue to call `next_rr`.
313    pub fn next_rr(&mut self) -> Result<Option<ResourceRecord>, Error<W5500::Error>> {
314        if self.rr_idx >= self.rr_count() {
315            Ok(None)
316        } else {
317            self.rr_idx = self.rr_idx.saturating_add(1);
318
319            let name: Option<&str> = read_labels(&mut self.reader, self.buf)?;
320
321            let qtype = read_u16(&mut self.reader)?;
322            let class = read_u16(&mut self.reader)?;
323            let ttl = read_u32(&mut self.reader)?;
324            let rdlength = read_u16(&mut self.reader)?;
325
326            let qtype = qtype.try_into();
327
328            let before_rdata_pos = self.reader.stream_position();
329
330            let rdata = match qtype {
331                Ok(Qtype::A) => {
332                    let mut buf: [u8; 4] = [0; 4];
333                    self.reader.read_exact(&mut buf)?;
334                    RData::A(Ipv4Addr::from(buf))
335                }
336                Ok(Qtype::SVR) => {
337                    let priority = read_u16(&mut self.reader)?;
338                    let weight = read_u16(&mut self.reader)?;
339                    let port = read_u16(&mut self.reader)?;
340                    RData::SVR(ServiceData {
341                        priority,
342                        weight,
343                        port,
344                    })
345                }
346                _ => RData::Unsupported,
347            };
348
349            if rdlength != 0 {
350                self.reader
351                    .seek(SeekFrom::Start(before_rdata_pos.saturating_add(rdlength)))?;
352            }
353
354            // now we are at the rest of the answer.
355            Ok(Some(ResourceRecord {
356                name,
357                qtype,
358                class: class.try_into(),
359                ttl,
360                rdata,
361            }))
362        }
363    }
364
365    /// Mark this response as done, removing it from the queue.
366    ///
367    /// If this is not called the same response will appear on the next
368    /// call to [`Client::response`].
369    pub fn done(self) -> Result<(), W5500::Error> {
370        self.reader.done()
371    }
372}
373
374/// DNS query sent by the client.
375///
376/// This is created with [`Client::query`].
377#[derive(Debug)]
378#[cfg_attr(feature = "defmt", derive(defmt::Format))]
379struct Query<'a, W5500: Udp> {
380    writer: UdpWriter<'a, W5500>,
381    header: Header,
382}
383
384impl<'a, W5500: Udp> Query<'a, W5500> {
385    /// Add a question to the query.
386    ///
387    /// # References
388    ///
389    /// * [RFC 1035 Section 4.1.2](https://www.rfc-editor.org/rfc/rfc1035#section-4.1.2)
390    pub fn question(mut self, qtype: Qtype, qname: &Hostname) -> Result<Self, Error<W5500::Error>> {
391        const REMAIN_LEN: u16 = 5;
392
393        if self.writer.remain() < u16::from(qname.len()) + REMAIN_LEN {
394            return Err(Error::OutOfMemory);
395        }
396
397        for label in qname.labels() {
398            // truncation from usize to u8 will not loose precision
399            // Hostname is validated to have labels with 63 or fewer bytes
400            let label_len: u8 = label.len() as u8;
401
402            self.writer.write_all(&[label_len])?;
403            self.writer.write_all(label.as_bytes())?;
404        }
405
406        let question_remainder: [u8; REMAIN_LEN as usize] = [
407            0, // null terminator for above labels
408            qtype.high_byte(),
409            qtype.low_byte(),
410            Qclass::IN.high_byte(),
411            Qclass::IN.low_byte(),
412        ];
413
414        self.writer.write_all(&question_remainder)?;
415
416        self.header.increment_qdcount();
417
418        Ok(self)
419    }
420
421    /// Send the DNS query.
422    pub fn send(mut self) -> Result<u16, Error<W5500::Error>> {
423        if self.header.qdcount() == 0 {
424            Ok(self.header.id())
425        } else {
426            let restore: u16 = self.writer.stream_position();
427            self.writer.rewind();
428            self.writer.write_all(self.header.as_bytes())?;
429            self.writer.seek(SeekFrom::Start(restore))?;
430            self.writer.send()?;
431            Ok(self.header.id())
432        }
433    }
434}
435
436/// W5500 DNS client.
437#[derive(Debug)]
438#[cfg_attr(feature = "defmt", derive(defmt::Format))]
439pub struct Client {
440    sn: Sn,
441    port: u16,
442    server: SocketAddrV4,
443    rng: rand::Rand,
444}
445
446impl Client {
447    /// Create a new DNS client.
448    ///
449    /// # Arguments
450    ///
451    /// * `sn` - The socket number to use for DNS queries.
452    /// * `port` - DNS source port.
453    ///   Use any free port greater than 1024 not in use by other W5500 sockets.
454    /// * `server` - The DNS server IPv4 address.
455    ///   Typically this is a DNS server provided by your DHCP client, but you
456    ///   can also use any server from the [`servers`] module.
457    /// * `seed` - A random `u64` to seed the random number generator used to
458    ///   create transaction IDs.
459    ///
460    /// # Example
461    ///
462    /// ```
463    /// # let random_number: u64 = 0;
464    /// use w5500_dns::{ll::Sn, servers, Client};
465    ///
466    /// const DNS_SRC_PORT: u16 = 45917;
467    ///
468    /// let dns_client: Client = Client::new(Sn::Sn3, DNS_SRC_PORT, servers::CLOUDFLARE, random_number);
469    /// ```
470    #[must_use]
471    pub const fn new(sn: Sn, port: u16, server: Ipv4Addr, seed: u64) -> Self {
472        Self {
473            sn,
474            port,
475            server: SocketAddrV4::new(server, DST_PORT),
476            rng: rand::Rand::new(seed),
477        }
478    }
479
480    /// Set the DNS server.
481    ///
482    /// # Example
483    ///
484    /// ```
485    /// # let random_number: u64 = 0;
486    /// use w5500_dns::{ll::Sn, servers, Client};
487    ///
488    /// const DNS_SRC_PORT: u16 = 45917;
489    ///
490    /// let mut dns_client: Client =
491    ///     Client::new(Sn::Sn3, DNS_SRC_PORT, servers::CLOUDFLARE, random_number);
492    /// assert_eq!(dns_client.server(), servers::CLOUDFLARE);
493    ///
494    /// dns_client.set_server(servers::GOOGLE_1);
495    /// assert_eq!(dns_client.server(), servers::GOOGLE_1);
496    /// ```
497    #[inline]
498    pub fn set_server(&mut self, server: Ipv4Addr) {
499        self.server.set_ip(server)
500    }
501
502    /// Get the current DNS server.
503    ///
504    /// # Example
505    ///
506    /// ```
507    /// # let random_number: u64 = 0;
508    /// use w5500_dns::{ll::Sn, servers, Client};
509    ///
510    /// const DNS_SRC_PORT: u16 = 45917;
511    ///
512    /// let dns_client: Client = Client::new(Sn::Sn3, DNS_SRC_PORT, servers::CLOUDFLARE, random_number);
513    /// assert_eq!(dns_client.server(), servers::CLOUDFLARE);
514    /// ```
515    #[inline]
516    #[must_use]
517    pub fn server(&self) -> Ipv4Addr {
518        *self.server.ip()
519    }
520
521    /// A simple DNS query.
522    ///
523    /// This will only send a DNS or MDNS query, it will not wait for a reply.
524    fn query<'a, W5500: Udp>(
525        &mut self,
526        w5500: &'a mut W5500,
527    ) -> Result<Query<'a, W5500>, Error<W5500::Error>> {
528        w5500.udp_bind(self.sn, self.port)?;
529        w5500.set_sn_dest(self.sn, &self.server)?;
530        const HEADER_SEEK: SeekFrom = SeekFrom::Start(Header::LEN);
531        let mut writer: UdpWriter<W5500> = w5500.udp_writer(self.sn)?;
532        writer.seek(HEADER_SEEK)?;
533        let id: u16 = self.rng.next_u16();
534        Ok(Query {
535            writer,
536            header: Header::new_query(id),
537        })
538    }
539
540    /// Send a DNS A record query.
541    ///
542    /// This will only send a DNS query, it will not wait for a reply from the
543    /// DNS server.
544    ///
545    /// The return `u16` is the transaction ID, use that get the response with
546    /// [`response`](Self::response).
547    ///
548    /// # Errors
549    ///
550    /// This method can only return:
551    ///
552    /// * [`Error::Other`]
553    /// * [`Error::OutOfMemory`]
554    pub fn a_question<W5500: Udp>(
555        &mut self,
556        w5500: &mut W5500,
557        hostname: &Hostname,
558    ) -> Result<u16, Error<W5500::Error>> {
559        self.query(w5500)?.question(Qtype::A, hostname)?.send()
560    }
561
562    /// Retrieve a DNS response after sending an [`a_question`]
563    ///
564    /// # Arguments
565    ///
566    /// * `w5500`: The W5500 device that implements the [`Udp`] trait.
567    /// * `buf`: A buffer for reading the hostname.
568    ///   Hostnames can be up to 255 bytes.
569    /// * `id`: The DNS query ID as provided by `query`.
570    ///
571    /// # Errors
572    ///
573    /// This method can only return:
574    ///
575    /// * [`Error::Other`]
576    /// * [`Error::UnexpectedEof`]
577    /// * [`Error::WouldBlock`]
578    ///
579    /// [`a_question`]: Self::a_question
580    pub fn response<'a, W5500: Udp>(
581        &self,
582        w5500: &'a mut W5500,
583        buf: &'a mut [u8],
584        id: u16,
585    ) -> Result<Response<'a, W5500>, Error<W5500::Error>> {
586        let mut reader: UdpReader<W5500> = w5500.udp_reader(self.sn)?;
587
588        let mut dns_header_buf = Header::new_buf();
589        let n: u16 = reader.read(&mut dns_header_buf)?;
590        if n != Header::LEN {
591            reader.done()?;
592            return Err(Error::WouldBlock);
593        }
594
595        let header: Header = dns_header_buf.into();
596
597        if header.qr() != Qr::Response {
598            reader.done()?;
599            return Err(Error::WouldBlock);
600        }
601
602        if header.id() != id {
603            reader.done()?;
604            return Err(Error::WouldBlock);
605        }
606
607        // ignore all the questions
608        for _ in 0..header.qdcount() {
609            // seek over labels
610            read_labels(&mut reader, &mut [])?;
611
612            // seek over type and class
613            reader.seek(SeekFrom::Current(4))?;
614        }
615
616        Ok(Response {
617            reader,
618            header,
619            buf,
620            rr_idx: 0,
621        })
622    }
623}