Skip to main content

smoltcp/socket/
dns.rs

1use core::cmp::min;
2#[cfg(feature = "async")]
3use core::task::Waker;
4
5use heapless::Vec;
6use managed::ManagedSlice;
7
8use crate::config::{DNS_MAX_NAME_SIZE, DNS_MAX_RESULT_COUNT, DNS_MAX_SERVER_COUNT};
9use crate::socket::{Context, PollAt};
10use crate::time::{Duration, Instant};
11use crate::wire::dns::{Flags, Opcode, Packet, Question, Rcode, Record, RecordData, Repr, Type};
12use crate::wire::{self, IpAddress, IpProtocol, IpRepr, UdpRepr};
13
14#[cfg(feature = "async")]
15use super::WakerRegistration;
16
17const DNS_PORT: u16 = 53;
18const MDNS_DNS_PORT: u16 = 5353;
19const RETRANSMIT_DELAY: Duration = Duration::from_millis(1_000);
20const MAX_RETRANSMIT_DELAY: Duration = Duration::from_millis(10_000);
21const RETRANSMIT_TIMEOUT: Duration = Duration::from_millis(10_000); // Should generally be 2-10 secs
22
23#[cfg(feature = "proto-ipv6")]
24#[allow(unused)]
25const MDNS_IPV6_ADDR: IpAddress = IpAddress::Ipv6(crate::wire::Ipv6Address::new(
26    0xff02, 0, 0, 0, 0, 0, 0, 0xfb,
27));
28
29#[cfg(feature = "proto-ipv4")]
30#[allow(unused)]
31const MDNS_IPV4_ADDR: IpAddress = IpAddress::Ipv4(crate::wire::Ipv4Address::new(224, 0, 0, 251));
32
33/// Error returned by [`Socket::start_query`]
34#[derive(Debug, PartialEq, Eq, Clone, Copy)]
35#[cfg_attr(feature = "defmt", derive(defmt::Format))]
36pub enum StartQueryError {
37    NoFreeSlot,
38    InvalidName,
39    NameTooLong,
40}
41
42impl core::fmt::Display for StartQueryError {
43    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
44        match self {
45            StartQueryError::NoFreeSlot => write!(f, "No free slot"),
46            StartQueryError::InvalidName => write!(f, "Invalid name"),
47            StartQueryError::NameTooLong => write!(f, "Name too long"),
48        }
49    }
50}
51
52impl core::error::Error for StartQueryError {}
53
54/// Error returned by [`Socket::get_query_result`]
55#[derive(Debug, PartialEq, Eq, Clone, Copy)]
56#[cfg_attr(feature = "defmt", derive(defmt::Format))]
57pub enum GetQueryResultError {
58    /// Query is not done yet.
59    Pending,
60    /// Query failed.
61    Failed,
62}
63
64impl core::fmt::Display for GetQueryResultError {
65    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
66        match self {
67            GetQueryResultError::Pending => write!(f, "Query is not done yet"),
68            GetQueryResultError::Failed => write!(f, "Query failed"),
69        }
70    }
71}
72
73impl core::error::Error for GetQueryResultError {}
74
75/// State for an in-progress DNS query.
76///
77/// The only reason this struct is public is to allow the socket state
78/// to be allocated externally.
79#[derive(Debug)]
80pub struct DnsQuery {
81    state: State,
82
83    #[cfg(feature = "async")]
84    waker: WakerRegistration,
85}
86
87impl DnsQuery {
88    fn set_state(&mut self, state: State) {
89        self.state = state;
90        #[cfg(feature = "async")]
91        self.waker.wake();
92    }
93}
94
95#[derive(Debug)]
96#[allow(clippy::large_enum_variant)]
97enum State {
98    Pending(PendingQuery),
99    Completed(CompletedQuery),
100    Failure,
101}
102
103#[derive(Debug)]
104struct PendingQuery {
105    name: Vec<u8, DNS_MAX_NAME_SIZE>,
106    type_: Type,
107
108    port: u16, // UDP port (src for request, dst for response)
109    txid: u16, // transaction ID
110
111    timeout_at: Option<Instant>,
112    retransmit_at: Instant,
113    delay: Duration,
114
115    server_idx: usize,
116    mdns: MulticastDns,
117}
118
119#[derive(Debug)]
120pub enum MulticastDns {
121    Disabled,
122    #[cfg(feature = "socket-mdns")]
123    Enabled,
124}
125
126#[derive(Debug)]
127struct CompletedQuery {
128    addresses: Vec<IpAddress, DNS_MAX_RESULT_COUNT>,
129}
130
131/// A handle to an in-progress DNS query.
132#[derive(Clone, Copy)]
133pub struct QueryHandle(usize);
134
135/// A Domain Name System socket.
136///
137/// A UDP socket is bound to a specific endpoint, and owns transmit and receive
138/// packet buffers.
139#[derive(Debug)]
140pub struct Socket<'a> {
141    servers: Vec<IpAddress, DNS_MAX_SERVER_COUNT>,
142    queries: ManagedSlice<'a, Option<DnsQuery>>,
143
144    /// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
145    hop_limit: Option<u8>,
146}
147
148impl<'a> Socket<'a> {
149    /// Create a DNS socket.
150    ///
151    /// Truncates the server list if `servers.len() > MAX_SERVER_COUNT`
152    pub fn new<Q>(servers: &[IpAddress], queries: Q) -> Socket<'a>
153    where
154        Q: Into<ManagedSlice<'a, Option<DnsQuery>>>,
155    {
156        let truncated_servers = &servers[..min(servers.len(), DNS_MAX_SERVER_COUNT)];
157
158        Socket {
159            servers: Vec::from_slice(truncated_servers).unwrap(),
160            queries: queries.into(),
161            hop_limit: None,
162        }
163    }
164
165    /// Update the list of DNS servers, will replace all existing servers
166    ///
167    /// Truncates the server list if `servers.len() > MAX_SERVER_COUNT`
168    pub fn update_servers(&mut self, servers: &[IpAddress]) {
169        if servers.len() > DNS_MAX_SERVER_COUNT {
170            net_trace!("Max DNS Servers exceeded. Increase MAX_SERVER_COUNT");
171            self.servers = Vec::from_slice(&servers[..DNS_MAX_SERVER_COUNT]).unwrap();
172        } else {
173            self.servers = Vec::from_slice(servers).unwrap();
174        }
175    }
176
177    /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
178    ///
179    /// See also the [set_hop_limit](#method.set_hop_limit) method
180    pub fn hop_limit(&self) -> Option<u8> {
181        self.hop_limit
182    }
183
184    /// Set the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
185    ///
186    /// A socket without an explicitly set hop limit value uses the default [IANA recommended]
187    /// value (64).
188    ///
189    /// # Panics
190    ///
191    /// This function panics if a hop limit value of 0 is given. See [RFC 1122 § 3.2.1.7].
192    ///
193    /// [IANA recommended]: https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml
194    /// [RFC 1122 § 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7
195    pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) {
196        // A host MUST NOT send a datagram with a hop limit value of 0
197        if let Some(0) = hop_limit {
198            panic!("the time-to-live value of a packet must not be zero")
199        }
200
201        self.hop_limit = hop_limit
202    }
203
204    fn find_free_query(&mut self) -> Option<QueryHandle> {
205        for (i, q) in self.queries.iter().enumerate() {
206            if q.is_none() {
207                return Some(QueryHandle(i));
208            }
209        }
210
211        match &mut self.queries {
212            ManagedSlice::Borrowed(_) => None,
213            #[cfg(feature = "alloc")]
214            ManagedSlice::Owned(queries) => {
215                queries.push(None);
216                let index = queries.len() - 1;
217                Some(QueryHandle(index))
218            }
219        }
220    }
221
222    /// Start a query.
223    ///
224    /// `name` is specified in human-friendly format, such as `"rust-lang.org"`.
225    /// It accepts names both with and without trailing dot, and they're treated
226    /// the same (there's no support for DNS search path).
227    pub fn start_query(
228        &mut self,
229        cx: &mut Context,
230        name: &str,
231        query_type: Type,
232    ) -> Result<QueryHandle, StartQueryError> {
233        let mut name = name.as_bytes();
234
235        if name.is_empty() {
236            net_trace!("invalid name: zero length");
237            return Err(StartQueryError::InvalidName);
238        }
239
240        // Remove trailing dot, if any
241        if name[name.len() - 1] == b'.' {
242            name = &name[..name.len() - 1];
243        }
244
245        let mut raw_name: Vec<u8, DNS_MAX_NAME_SIZE> = Vec::new();
246
247        let mut mdns = MulticastDns::Disabled;
248        #[cfg(feature = "socket-mdns")]
249        if name.split(|&c| c == b'.').next_back().unwrap() == b"local" {
250            net_trace!("Starting a mDNS query");
251            mdns = MulticastDns::Enabled;
252        }
253
254        for s in name.split(|&c| c == b'.') {
255            if s.len() > 63 {
256                net_trace!("invalid name: too long label");
257                return Err(StartQueryError::InvalidName);
258            }
259            if s.is_empty() {
260                net_trace!("invalid name: zero length label");
261                return Err(StartQueryError::InvalidName);
262            }
263
264            // Push label
265            raw_name
266                .push(s.len() as u8)
267                .map_err(|_| StartQueryError::NameTooLong)?;
268            raw_name
269                .extend_from_slice(s)
270                .map_err(|_| StartQueryError::NameTooLong)?;
271        }
272
273        // Push terminator.
274        raw_name
275            .push(0x00)
276            .map_err(|_| StartQueryError::NameTooLong)?;
277
278        self.start_query_raw(cx, &raw_name, query_type, mdns)
279    }
280
281    /// Start a query with a raw (wire-format) DNS name.
282    /// `b"\x09rust-lang\x03org\x00"`
283    ///
284    /// You probably want to use [`start_query`](Self::start_query) instead.
285    pub fn start_query_raw(
286        &mut self,
287        cx: &mut Context,
288        raw_name: &[u8],
289        query_type: Type,
290        mdns: MulticastDns,
291    ) -> Result<QueryHandle, StartQueryError> {
292        let handle = self.find_free_query().ok_or(StartQueryError::NoFreeSlot)?;
293
294        self.queries[handle.0] = Some(DnsQuery {
295            state: State::Pending(PendingQuery {
296                name: Vec::from_slice(raw_name).map_err(|_| StartQueryError::NameTooLong)?,
297                type_: query_type,
298                txid: cx.rand().rand_u16(),
299                port: cx.rand().rand_source_port(),
300                delay: RETRANSMIT_DELAY,
301                timeout_at: None,
302                retransmit_at: Instant::ZERO,
303                server_idx: 0,
304                mdns,
305            }),
306            #[cfg(feature = "async")]
307            waker: WakerRegistration::new(),
308        });
309        Ok(handle)
310    }
311
312    /// Get the result of a query.
313    ///
314    /// If the query is completed, the query slot is automatically freed.
315    ///
316    /// # Panics
317    /// Panics if the QueryHandle corresponds to a free slot.
318    pub fn get_query_result(
319        &mut self,
320        handle: QueryHandle,
321    ) -> Result<Vec<IpAddress, DNS_MAX_RESULT_COUNT>, GetQueryResultError> {
322        let slot = &mut self.queries[handle.0];
323        let q = slot.as_mut().unwrap();
324        match &mut q.state {
325            // Query is not done yet.
326            State::Pending(_) => Err(GetQueryResultError::Pending),
327            // Query is done
328            State::Completed(q) => {
329                let res = q.addresses.clone();
330                *slot = None; // Free up the slot for recycling.
331                Ok(res)
332            }
333            State::Failure => {
334                *slot = None; // Free up the slot for recycling.
335                Err(GetQueryResultError::Failed)
336            }
337        }
338    }
339
340    /// Cancels a query, freeing the slot.
341    ///
342    /// # Panics
343    ///
344    /// Panics if the QueryHandle corresponds to an already free slot.
345    pub fn cancel_query(&mut self, handle: QueryHandle) {
346        let slot = &mut self.queries[handle.0];
347        if slot.is_none() {
348            panic!("Canceling query in a free slot.")
349        }
350        *slot = None; // Free up the slot for recycling.
351    }
352
353    /// Assign a waker to a query slot
354    ///
355    /// The waker will be woken when the query completes, either successfully or failed.
356    ///
357    /// # Panics
358    ///
359    /// Panics if the QueryHandle corresponds to an already free slot.
360    #[cfg(feature = "async")]
361    pub fn register_query_waker(&mut self, handle: QueryHandle, waker: &Waker) {
362        self.queries[handle.0]
363            .as_mut()
364            .unwrap()
365            .waker
366            .register(waker);
367    }
368
369    pub(crate) fn accepts(&self, ip_repr: &IpRepr, udp_repr: &UdpRepr) -> bool {
370        (udp_repr.src_port == DNS_PORT
371            && self
372                .servers
373                .iter()
374                .any(|server| *server == ip_repr.src_addr()))
375            || (udp_repr.src_port == MDNS_DNS_PORT)
376    }
377
378    pub(crate) fn process(
379        &mut self,
380        _cx: &mut Context,
381        ip_repr: &IpRepr,
382        udp_repr: &UdpRepr,
383        payload: &[u8],
384    ) {
385        debug_assert!(self.accepts(ip_repr, udp_repr));
386
387        let size = payload.len();
388
389        net_trace!(
390            "receiving {} octets from {:?}:{}",
391            size,
392            ip_repr.src_addr(),
393            udp_repr.dst_port
394        );
395
396        let p = match Packet::new_checked(payload) {
397            Ok(x) => x,
398            Err(_) => {
399                net_trace!("dns packet malformed");
400                return;
401            }
402        };
403        if p.opcode() != Opcode::Query {
404            net_trace!("unwanted opcode {:?}", p.opcode());
405            return;
406        }
407
408        if !p.flags().contains(Flags::RESPONSE) {
409            net_trace!("packet doesn't have response bit set");
410            return;
411        }
412
413        if p.question_count() != 1 {
414            net_trace!("bad question count {:?}", p.question_count());
415            return;
416        }
417
418        // Find pending query
419        for q in self.queries.iter_mut().flatten() {
420            if let State::Pending(pq) = &mut q.state {
421                if udp_repr.dst_port != pq.port || p.transaction_id() != pq.txid {
422                    continue;
423                }
424
425                if p.rcode() == Rcode::NXDomain {
426                    net_trace!("rcode NXDomain");
427                    q.set_state(State::Failure);
428                    continue;
429                }
430
431                let payload = p.payload();
432                let (mut payload, question) = match Question::parse(payload) {
433                    Ok(x) => x,
434                    Err(_) => {
435                        net_trace!("question malformed");
436                        return;
437                    }
438                };
439
440                if question.type_ != pq.type_ {
441                    net_trace!("question type mismatch");
442                    return;
443                }
444
445                match eq_names(p.parse_name(question.name), p.parse_name(&pq.name)) {
446                    Ok(true) => {}
447                    Ok(false) => {
448                        net_trace!("question name mismatch");
449                        return;
450                    }
451                    Err(_) => {
452                        net_trace!("dns question name malformed");
453                        return;
454                    }
455                }
456
457                let mut addresses = Vec::new();
458
459                for _ in 0..p.answer_record_count() {
460                    let (payload2, r) = match Record::parse(payload) {
461                        Ok(x) => x,
462                        Err(_) => {
463                            net_trace!("dns answer record malformed");
464                            return;
465                        }
466                    };
467                    payload = payload2;
468
469                    match eq_names(p.parse_name(r.name), p.parse_name(&pq.name)) {
470                        Ok(true) => {}
471                        Ok(false) => {
472                            net_trace!("answer name mismatch: {:?}", r);
473                            continue;
474                        }
475                        Err(_) => {
476                            net_trace!("dns answer record name malformed");
477                            return;
478                        }
479                    }
480
481                    match r.data {
482                        #[cfg(feature = "proto-ipv4")]
483                        RecordData::A(addr) => {
484                            net_trace!("A: {:?}", addr);
485                            if addresses.push(addr.into()).is_err() {
486                                net_trace!("too many addresses in response, ignoring {:?}", addr);
487                            }
488                        }
489                        #[cfg(feature = "proto-ipv6")]
490                        RecordData::Aaaa(addr) => {
491                            net_trace!("AAAA: {:?}", addr);
492                            if addresses.push(addr.into()).is_err() {
493                                net_trace!("too many addresses in response, ignoring {:?}", addr);
494                            }
495                        }
496                        RecordData::Cname(name) => {
497                            net_trace!("CNAME: {:?}", name);
498
499                            // When faced with a CNAME, recursive resolvers are supposed to
500                            // resolve the CNAME and append the results for it.
501                            //
502                            // We update the query with the new name, so that we pick up the A/AAAA
503                            // records for the CNAME when we parse them later.
504                            // I believe it's mandatory the CNAME results MUST come *after* in the
505                            // packet, so it's enough to do one linear pass over it.
506                            if copy_name(&mut pq.name, p.parse_name(name)).is_err() {
507                                net_trace!("dns answer cname malformed");
508                                return;
509                            }
510                        }
511                        RecordData::Other(type_, data) => {
512                            net_trace!("unknown: {:?} {:?}", type_, data)
513                        }
514                    }
515                }
516
517                q.set_state(if addresses.is_empty() {
518                    State::Failure
519                } else {
520                    State::Completed(CompletedQuery { addresses })
521                });
522
523                // If we get here, packet matched the current query, stop processing.
524                return;
525            }
526        }
527
528        // If we get here, packet matched with no query.
529        net_trace!("no query matched");
530    }
531
532    pub(crate) fn dispatch<F, E>(&mut self, cx: &mut Context, emit: F) -> Result<(), E>
533    where
534        F: FnOnce(&mut Context, (IpRepr, UdpRepr, &[u8])) -> Result<(), E>,
535    {
536        let hop_limit = self.hop_limit.unwrap_or(64);
537
538        for q in self.queries.iter_mut().flatten() {
539            if let State::Pending(pq) = &mut q.state {
540                // As per RFC 6762 any DNS query ending in .local. MUST be sent as mdns
541                // so we internally overwrite the servers for any of those queries
542                // in this function.
543                let servers = match pq.mdns {
544                    #[cfg(feature = "socket-mdns")]
545                    MulticastDns::Enabled => &[
546                        #[cfg(feature = "proto-ipv6")]
547                        MDNS_IPV6_ADDR,
548                        #[cfg(feature = "proto-ipv4")]
549                        MDNS_IPV4_ADDR,
550                    ],
551                    MulticastDns::Disabled => self.servers.as_slice(),
552                };
553
554                let timeout = if let Some(timeout) = pq.timeout_at {
555                    timeout
556                } else {
557                    let v = cx.now() + RETRANSMIT_TIMEOUT;
558                    pq.timeout_at = Some(v);
559                    v
560                };
561
562                // Check timeout
563                if timeout < cx.now() {
564                    // DNS timeout
565                    pq.timeout_at = Some(cx.now() + RETRANSMIT_TIMEOUT);
566                    pq.retransmit_at = Instant::ZERO;
567                    pq.delay = RETRANSMIT_DELAY;
568
569                    // Try next server. We check below whether we've tried all servers.
570                    pq.server_idx += 1;
571                }
572                // Check if we've run out of servers to try.
573                if pq.server_idx >= servers.len() {
574                    net_trace!("already tried all servers.");
575                    q.set_state(State::Failure);
576                    continue;
577                }
578
579                // Check so the IP address is valid
580                if servers[pq.server_idx].is_unspecified() {
581                    net_trace!("invalid unspecified DNS server addr.");
582                    q.set_state(State::Failure);
583                    continue;
584                }
585
586                if pq.retransmit_at > cx.now() {
587                    // query is waiting for retransmit
588                    continue;
589                }
590
591                let repr = Repr {
592                    transaction_id: pq.txid,
593                    flags: Flags::RECURSION_DESIRED,
594                    opcode: Opcode::Query,
595                    question: Question {
596                        name: &pq.name,
597                        type_: pq.type_,
598                    },
599                };
600
601                let mut payload = [0u8; 512];
602                let payload = &mut payload[..repr.buffer_len()];
603                repr.emit(&mut Packet::new_unchecked(payload));
604
605                let dst_port = match pq.mdns {
606                    #[cfg(feature = "socket-mdns")]
607                    MulticastDns::Enabled => MDNS_DNS_PORT,
608                    MulticastDns::Disabled => DNS_PORT,
609                };
610
611                let udp_repr = UdpRepr {
612                    src_port: pq.port,
613                    dst_port,
614                };
615
616                let dst_addr = servers[pq.server_idx];
617                let src_addr = match cx.get_source_address(&dst_addr) {
618                    Some(src_addr) => src_addr,
619                    None => {
620                        net_trace!("no source address for destination {}", dst_addr);
621                        q.set_state(State::Failure);
622                        continue;
623                    }
624                };
625
626                let ip_repr = IpRepr::new(
627                    src_addr,
628                    dst_addr,
629                    IpProtocol::Udp,
630                    udp_repr.header_len() + payload.len(),
631                    hop_limit,
632                );
633
634                net_trace!(
635                    "sending {} octets to {} from port {}",
636                    payload.len(),
637                    ip_repr.dst_addr(),
638                    udp_repr.src_port
639                );
640
641                emit(cx, (ip_repr, udp_repr, payload))?;
642
643                pq.retransmit_at = cx.now() + pq.delay;
644                pq.delay = MAX_RETRANSMIT_DELAY.min(pq.delay * 2);
645
646                return Ok(());
647            }
648        }
649
650        // Nothing to dispatch
651        Ok(())
652    }
653
654    pub(crate) fn poll_at(&self, _cx: &Context) -> PollAt {
655        self.queries
656            .iter()
657            .flatten()
658            .filter_map(|q| match &q.state {
659                State::Pending(pq) => Some(PollAt::Time(pq.retransmit_at)),
660                State::Completed(_) => None,
661                State::Failure => None,
662            })
663            .min()
664            .unwrap_or(PollAt::Ingress)
665    }
666}
667
668fn eq_names<'a>(
669    mut a: impl Iterator<Item = wire::Result<&'a [u8]>>,
670    mut b: impl Iterator<Item = wire::Result<&'a [u8]>>,
671) -> wire::Result<bool> {
672    loop {
673        match (a.next(), b.next()) {
674            // Handle errors
675            (Some(Err(e)), _) => return Err(e),
676            (_, Some(Err(e))) => return Err(e),
677
678            // Both finished -> equal
679            (None, None) => return Ok(true),
680
681            // One finished before the other -> not equal
682            (None, _) => return Ok(false),
683            (_, None) => return Ok(false),
684
685            // Got two labels, check if they're equal
686            (Some(Ok(la)), Some(Ok(lb))) => {
687                if la != lb {
688                    return Ok(false);
689                }
690            }
691        }
692    }
693}
694
695fn copy_name<'a, const N: usize>(
696    dest: &mut Vec<u8, N>,
697    name: impl Iterator<Item = wire::Result<&'a [u8]>>,
698) -> Result<(), wire::Error> {
699    dest.truncate(0);
700
701    for label in name {
702        let label = label?;
703        dest.push(label.len() as u8).map_err(|_| wire::Error)?;
704        dest.extend_from_slice(label).map_err(|_| wire::Error)?;
705    }
706
707    // Write terminator 0x00
708    dest.push(0).map_err(|_| wire::Error)?;
709
710    Ok(())
711}