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}