Skip to main content

hickory_resolver/
name_server.rs

1// Copyright 2015-2019 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8#[cfg(not(test))]
9use std::time::{Duration, Instant};
10use std::{
11    cmp,
12    fmt::Debug,
13    marker::PhantomData,
14    net::IpAddr,
15    sync::{
16        Arc,
17        atomic::{AtomicU8, AtomicU32, Ordering},
18    },
19};
20
21use futures_util::lock::Mutex as AsyncMutex;
22use parking_lot::Mutex as SyncMutex;
23#[cfg(test)]
24use tokio::time::{Duration, Instant};
25use tracing::{debug, error, warn};
26
27#[cfg(all(feature = "metrics", any(feature = "__tls", feature = "__quic")))]
28use crate::metrics::opportunistic_encryption::ProbeMetrics;
29#[cfg(feature = "metrics")]
30use crate::metrics::{NameServerAddr, ResolverMetrics};
31use crate::{
32    config::{
33        ConnectionConfig, NameServerConfig, OpportunisticEncryption, ResolverOpts,
34        ServerOrderingStrategy,
35    },
36    connection_provider::ConnectionProvider,
37    name_server_pool::{NameServerTransportState, PoolContext},
38    net::{
39        DnsError, NetError, NoRecords,
40        runtime::{RuntimeProvider, Spawn},
41        xfer::{DnsHandle, FirstAnswer, Protocol},
42    },
43    proto::{
44        op::{DnsRequest, DnsRequestOptions, DnsResponse, Query, ResponseCode},
45        rr::{Name, RecordType},
46    },
47};
48
49/// A remote DNS server, identified by its IP address.
50///
51/// This potentially holds multiple open connections to the server, according to the
52/// configured protocols, and will make new connections as needed.
53pub struct NameServer<P: ConnectionProvider> {
54    config: NameServerConfig,
55    connections: AsyncMutex<Vec<ConnectionState<P>>>,
56    /// Metrics related to opportunistic encryption probes.
57    #[cfg(all(feature = "metrics", any(feature = "__tls", feature = "__quic")))]
58    opportunistic_probe_metrics: ProbeMetrics,
59    /// Metrics related to outgoing queries.
60    #[cfg(feature = "metrics")]
61    resolver_metrics: ResolverMetrics,
62    server_srtt: DecayingSrtt,
63    connection_provider: P,
64}
65
66impl<P: ConnectionProvider> NameServer<P> {
67    /// Create a new [`NameServer`] with the given connections and configuration.
68    ///
69    /// The `connections` will usually be empty.
70    pub fn new(
71        connections: impl IntoIterator<Item = (Protocol, P::Conn)>,
72        config: NameServerConfig,
73        options: &ResolverOpts,
74        connection_provider: P,
75    ) -> Self {
76        let mut connections = connections
77            .into_iter()
78            .map(|(protocol, handle)| ConnectionState::new(handle, protocol))
79            .collect::<Vec<_>>();
80
81        // Unless the user specified that we should follow the configured order,
82        // re-order the connections to prioritize UDP.
83        if options.server_ordering_strategy != ServerOrderingStrategy::UserProvidedOrder {
84            connections.sort_by_key(|ns| ns.protocol != Protocol::Udp);
85        }
86
87        #[cfg(feature = "metrics")]
88        let resolver_metrics = ResolverMetrics::new(match options.enable_per_name_server_metrics {
89            true => NameServerAddr::Individual(config.ip),
90            false => NameServerAddr::Aggregated,
91        });
92
93        Self {
94            config,
95            connections: AsyncMutex::new(connections),
96            server_srtt: DecayingSrtt::new(Duration::from_micros(rand::random_range(1..32))),
97            #[cfg(all(feature = "metrics", any(feature = "__tls", feature = "__quic")))]
98            opportunistic_probe_metrics: ProbeMetrics::default(),
99            #[cfg(feature = "metrics")]
100            resolver_metrics,
101            connection_provider,
102        }
103    }
104
105    // TODO: there needs to be some way of customizing the connection based on EDNS options from the server side...
106    pub(crate) async fn send(
107        self: Arc<Self>,
108        request: DnsRequest,
109        policy: ConnectionPolicy,
110        cx: &Arc<PoolContext>,
111    ) -> Result<DnsResponse, NetError> {
112        let (_protocol, result) = self.send_inner(request, policy, cx).await;
113        #[cfg(feature = "metrics")]
114        if let Some(protocol) = _protocol {
115            self.resolver_metrics
116                .increment_query_result(&protocol, result.as_ref().map(|_| ()));
117        }
118
119        result
120    }
121
122    async fn send_inner(
123        &self,
124        request: DnsRequest,
125        policy: ConnectionPolicy,
126        cx: &Arc<PoolContext>,
127    ) -> (Option<Protocol>, Result<DnsResponse, NetError>) {
128        // Reconnect and retry once if a reused connection was closed mid-flight.
129        // Caps total retries even when the pool holds several reusable connections.
130        let mut reconnect_budget = 1u8;
131        loop {
132            let ConnectedClient {
133                handle,
134                meta,
135                protocol,
136                reuse,
137            } = match self.connected_mut_client(policy, cx).await {
138                Ok(v) => v,
139                Err((err, protocol)) => {
140                    debug!(config = ?self.config, ?protocol, %err, "failed to establish connection to name server");
141                    return (protocol, Err(err));
142                }
143            };
144            #[cfg(feature = "metrics")]
145            self.resolver_metrics.increment_outgoing_query(&protocol);
146            let now = Instant::now();
147            let response = handle.send(request.clone()).first_answer().await;
148            let rtt = now.elapsed();
149
150            match response {
151                Ok(response) => {
152                    meta.set_status(Status::Established);
153                    let result = DnsError::from_response(response);
154                    let error = match result {
155                        Ok(response) => {
156                            meta.srtt.record(rtt);
157                            self.server_srtt.record(rtt);
158                            if cx.opportunistic_encryption.is_enabled() && protocol.is_encrypted() {
159                                cx.transport_state()
160                                    .await
161                                    .response_received(self.config.ip, protocol);
162                            }
163                            return (Some(protocol), Ok(response));
164                        }
165                        Err(error) => error,
166                    };
167
168                    let update = match error {
169                        DnsError::NoRecordsFound(NoRecords {
170                            response_code: ResponseCode::ServFail,
171                            ..
172                        }) => Some(true),
173                        DnsError::NoRecordsFound(NoRecords { .. }) => Some(false),
174                        _ => None,
175                    };
176
177                    match update {
178                        Some(true) => {
179                            meta.srtt.record(rtt);
180                            self.server_srtt.record(rtt);
181                        }
182                        Some(false) => {
183                            // record the failure
184                            meta.srtt.record_failure();
185                            self.server_srtt.record_failure();
186                        }
187                        None => {}
188                    }
189
190                    let err = NetError::from(error);
191                    if cx.opportunistic_encryption.is_enabled() && protocol.is_encrypted() {
192                        cx.transport_state()
193                            .await
194                            .error_received(self.config.ip, protocol, &err)
195                    }
196                    return (Some(protocol), Err(err));
197                }
198                Err(error) => {
199                    debug!(config = ?self.config, %error, "failed to connect to name server");
200
201                    // this transitions the state to failure, so the next acquire drops it
202                    meta.set_status(Status::Failed);
203
204                    // A reused connection the peer had already closed isn't a server fault.
205                    // Reconnect and retry once without penalizing server selection. The resend
206                    // is at-least-once; the resolver only sends read-only queries through this pool.
207                    if reuse == ConnectionReuse::Reused
208                        && reconnect_budget > 0
209                        && error.is_connection_closed()
210                    {
211                        #[cfg(feature = "metrics")]
212                        // The increment in send() won't occur since we're retrying.
213                        self.resolver_metrics
214                            .increment_query_result(&protocol, Err(&error));
215                        reconnect_budget -= 1;
216                        continue;
217                    }
218
219                    // record the failure on both the per-connection and server-level SRTTs.
220                    // updating server_srtt ensures the server is deprioritized in pool
221                    // ordering (decayed_srtt) so other servers get a chance to be tried.
222                    match &error {
223                        NetError::Busy | NetError::Io(_) | NetError::Timeout => {
224                            meta.srtt.record_failure();
225                            self.server_srtt.record_failure();
226                        }
227                        #[cfg(feature = "__quic")]
228                        NetError::QuinnConfigError(_)
229                        | NetError::QuinnConnect(_)
230                        | NetError::QuinnConnection(_)
231                        | NetError::QuinnTlsConfigError(_) => {
232                            meta.srtt.record_failure();
233                            self.server_srtt.record_failure();
234                        }
235                        #[cfg(feature = "__tls")]
236                        NetError::RustlsError(_) => {
237                            meta.srtt.record_failure();
238                            self.server_srtt.record_failure();
239                        }
240                        _ => {}
241                    }
242
243                    if cx.opportunistic_encryption.is_enabled() && protocol.is_encrypted() {
244                        cx.transport_state()
245                            .await
246                            .error_received(self.config.ip, protocol, &error);
247                    }
248
249                    // These are connection failures, not lookup failures, that is handled in the resolver layer
250                    return (Some(protocol), Err(error));
251                }
252            }
253        }
254    }
255
256    /// This will return a mutable client to allows for sending messages.
257    ///
258    /// If the connection is in a failed state, then this will establish a new connection.
259    ///
260    async fn connected_mut_client(
261        &self,
262        policy: ConnectionPolicy,
263        cx: &Arc<PoolContext>,
264    ) -> Result<ConnectedClient<P>, (NetError, Option<Protocol>)> {
265        // Check for an existing usable connection (short lock)
266        {
267            let mut connections = self.connections.lock().await;
268            connections
269                .retain(|conn| matches!(conn.meta.status(), Status::Init | Status::Established));
270            if let Some(conn) = policy.select_connection(
271                self.config.ip,
272                &*cx.transport_state().await,
273                &cx.opportunistic_encryption,
274                &connections,
275            ) {
276                return Ok(ConnectedClient {
277                    handle: conn.handle.clone(),
278                    meta: conn.meta.clone(),
279                    protocol: conn.protocol,
280                    reuse: ConnectionReuse::Reused,
281                });
282            }
283        }
284
285        // Select connection config and update transport state (no lock)
286        debug!(config = ?self.config, "connecting");
287        let config = policy
288            .select_connection_config(
289                self.config.ip,
290                &*cx.transport_state().await,
291                &cx.opportunistic_encryption,
292                &self.config.connections,
293            )
294            .ok_or((NetError::NoConnections, None))?;
295
296        let protocol = config.protocol.to_protocol();
297        if cx.opportunistic_encryption.is_enabled() && protocol.is_encrypted() {
298            cx.transport_state()
299                .await
300                .initiate_connection(self.config.ip, protocol);
301        } else if cx.opportunistic_encryption.is_enabled() && !protocol.is_encrypted() {
302            self.consider_probe_encrypted_transport(&policy, cx).await;
303        }
304
305        // Establish connection
306        let handle_fut = self
307            .connection_provider
308            .new_connection(self.config.ip, config, cx)
309            .map_err(|e| (e, Some(protocol)))?;
310
311        let handle = Box::pin(handle_fut)
312            .await
313            .map_err(|e| (e, Some(protocol)))?;
314
315        if cx.opportunistic_encryption.is_enabled() && protocol.is_encrypted() {
316            cx.transport_state()
317                .await
318                .complete_connection(self.config.ip, protocol);
319        }
320
321        // Store the new connection (with lock)
322        let state = ConnectionState::new(handle.clone(), protocol);
323        let meta = state.meta.clone();
324        self.connections.lock().await.push(state);
325        Ok(ConnectedClient {
326            handle,
327            meta,
328            protocol,
329            reuse: ConnectionReuse::Fresh,
330        })
331    }
332
333    pub(super) fn protocols(&self) -> impl Iterator<Item = Protocol> + '_ {
334        self.config
335            .connections
336            .iter()
337            .map(|conn| conn.protocol.to_protocol())
338    }
339
340    pub(super) fn ip(&self) -> IpAddr {
341        self.config.ip
342    }
343
344    pub(crate) fn decayed_srtt(&self) -> f64 {
345        self.server_srtt.current()
346    }
347
348    /// Records an SRTT observation for a server whose in-flight request was
349    /// cancelled because a parallel request to another server succeeded first.
350    ///
351    /// Records the winner's RTT plus a small penalty (`CANCEL_PENALTY`) as the
352    /// observation: the cancelled server was *at least* that slow (it hadn't
353    /// responded yet), and the penalty ensures the winner retains a sorting
354    /// advantage in the next round. This avoids the full `FAILURE_PENALTY`
355    /// which would be too harsh for a server that's merely slightly slower.
356    ///
357    /// A truly unreachable server will be cancelled on every query and its SRTT
358    /// will ratchet up as the EWMA repeatedly incorporates the winner's RTT
359    /// without ever recording a real (successful) measurement to bring it back
360    /// down.
361    pub(super) fn record_cancelled(&self, winner_rtt: Duration) {
362        const CANCEL_PENALTY: Duration = Duration::from_millis(5);
363        self.server_srtt.record(winner_rtt + CANCEL_PENALTY);
364    }
365
366    #[cfg(test)]
367    pub(crate) fn test_record_failure(&self) {
368        self.server_srtt.record_failure();
369    }
370
371    #[cfg(test)]
372    #[allow(dead_code)]
373    pub(crate) fn is_connected(&self) -> bool {
374        let Some(connections) = self.connections.try_lock() else {
375            // assuming that if someone has it locked it will be or is connected
376            return true;
377        };
378
379        connections.iter().any(|conn| match conn.meta.status() {
380            Status::Established | Status::Init => true,
381            Status::Failed => false,
382        })
383    }
384
385    pub(crate) fn trust_negative_responses(&self) -> bool {
386        self.config.trust_negative_responses
387    }
388
389    async fn consider_probe_encrypted_transport(
390        &self,
391        policy: &ConnectionPolicy,
392        cx: &Arc<PoolContext>,
393    ) {
394        let Some(probe_config) =
395            policy.select_encrypted_connection_config(&self.config.connections)
396        else {
397            warn!("no encrypted connection configs available for probing");
398            return;
399        };
400
401        let probe_protocol = probe_config.protocol.to_protocol();
402        let should_probe = {
403            let state = cx.transport_state().await;
404            state.should_probe_encrypted(
405                self.config.ip,
406                probe_protocol,
407                &cx.opportunistic_encryption,
408            )
409        };
410
411        if !should_probe {
412            return;
413        }
414
415        if let Err(err) = self.probe_encrypted_transport(cx, probe_config) {
416            error!(%err, "opportunistic encrypted probe attempt failed");
417        }
418    }
419
420    fn probe_encrypted_transport(
421        &self,
422        cx: &Arc<PoolContext>,
423        probe_config: &ConnectionConfig,
424    ) -> Result<(), NetError> {
425        let mut budget = cx.opportunistic_probe_budget.load(Ordering::Relaxed);
426        #[cfg(all(feature = "metrics", any(feature = "__tls", feature = "__quic")))]
427        self.opportunistic_probe_metrics.probe_budget.set(budget);
428        loop {
429            if budget == 0 {
430                debug!("no remaining budget for opportunistic probing");
431                return Ok(());
432            }
433            match cx.opportunistic_probe_budget.compare_exchange_weak(
434                budget,
435                budget - 1,
436                Ordering::AcqRel,
437                Ordering::Relaxed,
438            ) {
439                Ok(_) => break,
440                Err(current) => budget = current,
441            }
442        }
443
444        let connect = ProbeRequest::new(
445            probe_config,
446            self,
447            cx,
448            #[cfg(all(feature = "metrics", any(feature = "__tls", feature = "__quic")))]
449            self.opportunistic_probe_metrics.clone(),
450        )?;
451        self.connection_provider
452            .runtime_provider()
453            .create_handle()
454            .spawn_bg(connect.run());
455
456        Ok(())
457    }
458}
459
460struct ProbeRequest<P: ConnectionProvider> {
461    ip: IpAddr,
462    proto: Protocol,
463    connecting: P::FutureConn,
464    context: Arc<PoolContext>,
465    #[cfg(all(feature = "metrics", any(feature = "__tls", feature = "__quic")))]
466    metrics: ProbeMetrics,
467    provider: PhantomData<P>,
468}
469
470impl<P: ConnectionProvider> ProbeRequest<P> {
471    fn new(
472        config: &ConnectionConfig,
473        ns: &NameServer<P>,
474        cx: &Arc<PoolContext>,
475        #[cfg(all(feature = "metrics", any(feature = "__tls", feature = "__quic")))]
476        metrics: ProbeMetrics,
477    ) -> Result<Self, NetError> {
478        Ok(Self {
479            ip: ns.config.ip,
480            proto: config.protocol.to_protocol(),
481            connecting: ns
482                .connection_provider
483                .new_connection(ns.config.ip, config, cx)?,
484            context: cx.clone(),
485            #[cfg(all(feature = "metrics", any(feature = "__tls", feature = "__quic")))]
486            metrics,
487            provider: PhantomData,
488        })
489    }
490
491    async fn run(self) {
492        let Self {
493            ip,
494            proto,
495            connecting,
496            context,
497            #[cfg(all(feature = "metrics", any(feature = "__tls", feature = "__quic")))]
498            metrics,
499            provider: _,
500        } = self;
501
502        #[cfg(all(feature = "metrics", any(feature = "__tls", feature = "__quic")))]
503        let start = Instant::now();
504
505        context
506            .transport_state()
507            .await
508            .initiate_connection(ip, proto);
509        #[cfg(all(feature = "metrics", any(feature = "__tls", feature = "__quic")))]
510        metrics.increment_attempts(proto);
511
512        let conn = match connecting.await {
513            Ok(conn) => conn,
514            Err(err) => {
515                debug!(?proto, "probe connection failed");
516                let _prev = context
517                    .opportunistic_probe_budget
518                    .fetch_add(1, Ordering::Relaxed);
519                #[cfg(all(feature = "metrics", any(feature = "__tls", feature = "__quic")))]
520                {
521                    metrics.increment_errors(proto, &err);
522                    metrics.probe_budget.set(_prev + 1);
523                    metrics.record_probe_duration(proto, start.elapsed());
524                }
525                context
526                    .transport_state()
527                    .await
528                    .error_received(ip, proto, &err);
529                return;
530            }
531        };
532
533        debug!(?proto, "probe connection succeeded");
534        context
535            .transport_state()
536            .await
537            .complete_connection(ip, proto);
538
539        match conn
540            .send(DnsRequest::from_query(
541                Query::query(Name::root(), RecordType::NS),
542                DnsRequestOptions::default(),
543            ))
544            .first_answer()
545            .await
546        {
547            Ok(_) => {
548                debug!(?proto, "probe query succeeded");
549                #[cfg(all(feature = "metrics", any(feature = "__tls", feature = "__quic")))]
550                metrics.increment_successes(proto);
551                context.transport_state().await.response_received(ip, proto);
552            }
553            Err(err) => {
554                debug!(?proto, ?err, "probe query failed");
555                #[cfg(all(feature = "metrics", any(feature = "__tls", feature = "__quic")))]
556                metrics.increment_errors(proto, &err);
557                context
558                    .transport_state()
559                    .await
560                    .error_received(ip, proto, &err);
561            }
562        }
563
564        let _prev = context
565            .opportunistic_probe_budget
566            .fetch_add(1, Ordering::Relaxed);
567        #[cfg(all(feature = "metrics", any(feature = "__tls", feature = "__quic")))]
568        {
569            metrics.probe_budget.set(_prev + 1);
570            metrics.record_probe_duration(proto, start.elapsed());
571        }
572    }
573}
574
575/// Whether `connected_mut_client` returned an existing pooled connection or established a new one.
576#[derive(Clone, Copy, PartialEq, Eq)]
577enum ConnectionReuse {
578    Reused,
579    Fresh,
580}
581
582/// A connection selected by [`NameServer::connected_mut_client`] for sending a request.
583struct ConnectedClient<P: ConnectionProvider> {
584    handle: P::Conn,
585    meta: Arc<ConnectionMeta>,
586    protocol: Protocol,
587    reuse: ConnectionReuse,
588}
589
590struct ConnectionState<P: ConnectionProvider> {
591    protocol: Protocol,
592    handle: P::Conn,
593    meta: Arc<ConnectionMeta>,
594}
595
596impl<P: ConnectionProvider> ConnectionState<P> {
597    fn new(handle: P::Conn, protocol: Protocol) -> Self {
598        Self {
599            protocol,
600            handle,
601            meta: Arc::new(ConnectionMeta::default()),
602        }
603    }
604}
605
606struct ConnectionMeta {
607    status: AtomicU8,
608    srtt: DecayingSrtt,
609}
610
611impl ConnectionMeta {
612    fn set_status(&self, status: Status) {
613        self.status.store(status.into(), Ordering::Release);
614    }
615
616    fn status(&self) -> Status {
617        Status::from(self.status.load(Ordering::Acquire))
618    }
619}
620
621impl Default for ConnectionMeta {
622    fn default() -> Self {
623        // Initialize the SRTT to a randomly generated value that represents a
624        // very low RTT. Such a value helps ensure that each server is attempted
625        // early.
626        Self {
627            status: AtomicU8::new(Status::Init.into()),
628            srtt: DecayingSrtt::new(Duration::from_micros(rand::random_range(1..32))),
629        }
630    }
631}
632
633struct DecayingSrtt {
634    /// The smoothed round-trip time (SRTT).
635    ///
636    /// This value represents an exponentially weighted moving average (EWMA) of
637    /// recorded latencies. The algorithm for computing this value is based on
638    /// the following:
639    ///
640    /// <https://en.wikipedia.org/wiki/Moving_average#Application_to_measuring_computer_performance>
641    ///
642    /// It is also partially inspired by the BIND and PowerDNS implementations:
643    ///
644    /// - <https://github.com/isc-projects/bind9/blob/7bf8a7ab1b280c1021bf1e762a239b07aac3c591/lib/dns/adb.c#L3487>
645    /// - <https://github.com/PowerDNS/pdns/blob/7c5f9ae6ae4fb17302d933eaeebc8d6f0249aab2/pdns/syncres.cc#L123>
646    ///
647    /// The algorithm for computing and using this value can be summarized as
648    /// follows:
649    ///
650    /// 1. The value is initialized to a random value that represents a very low
651    ///    latency.
652    /// 2. If the round-trip time (RTT) was successfully measured for a query,
653    ///    then it is incorporated into the EWMA using the formula linked above.
654    /// 3. If the RTT could not be measured (i.e. due to a connection failure),
655    ///    then a constant penalty factor is applied to the EWMA.
656    /// 4. When comparing EWMA values, a time-based decay is applied to each
657    ///    value. Note that this decay is only applied at read time.
658    ///
659    /// For the original discussion regarding this algorithm, see
660    /// <https://github.com/hickory-dns/hickory-dns/issues/1702>.
661    srtt_microseconds: AtomicU32,
662
663    /// The last time the `srtt_microseconds` value was updated.
664    last_update: SyncMutex<Option<Instant>>,
665}
666
667impl DecayingSrtt {
668    fn new(initial_srtt: Duration) -> Self {
669        Self {
670            srtt_microseconds: AtomicU32::new(initial_srtt.as_micros() as u32),
671            last_update: SyncMutex::new(None),
672        }
673    }
674
675    fn record(&self, rtt: Duration) {
676        // If the cast on the result does overflow (it shouldn't), then the
677        // value is saturated to u32::MAX, which is above the `MAX_SRTT_MICROS`
678        // limit (meaning that any potential overflow is inconsequential).
679        // See https://github.com/rust-lang/rust/issues/10184.
680        self.update(
681            rtt.as_micros() as u32,
682            |cur_srtt_microseconds, last_update| {
683                // An arbitrarily low weight is used when computing the factor
684                // to ensure that recent RTT measurements are weighted more
685                // heavily.
686                let factor = compute_srtt_factor(last_update, 3);
687                let new_srtt = (1.0 - factor) * (rtt.as_micros() as f64)
688                    + factor * f64::from(cur_srtt_microseconds);
689                new_srtt.round() as u32
690            },
691        );
692    }
693
694    /// Records a connection failure for a particular query.
695    fn record_failure(&self) {
696        self.update(
697            Self::FAILURE_PENALTY,
698            |cur_srtt_microseconds, _last_update| {
699                cur_srtt_microseconds.saturating_add(Self::FAILURE_PENALTY)
700            },
701        );
702    }
703
704    /// Returns the SRTT value after applying a time based decay.
705    ///
706    /// The decay exponentially decreases the SRTT value. The primary reasons
707    /// for applying a downwards decay are twofold:
708    ///
709    /// 1. It helps distribute query load.
710    /// 2. It helps detect positive network changes. For example, decreases in
711    ///    latency or a server that has recovered from a failure.
712    fn current(&self) -> f64 {
713        let srtt = f64::from(self.srtt_microseconds.load(Ordering::Acquire));
714        self.last_update.lock().map_or(srtt, |last_update| {
715            // In general, if the time between queries is relatively short, then
716            // the server ordering algorithm will approximate a spike
717            // distribution where the servers with the lowest latencies are
718            // chosen much more frequently. Conversely, if the time between
719            // queries is relatively long, then the query distribution will be
720            // more uniform. A larger weight widens the window in which servers
721            // with historically lower latencies will be heavily preferred. On
722            // the other hand, a larger weight may also increase the time it
723            // takes to recover from a failure or to observe positive changes in
724            // latency.
725            srtt * compute_srtt_factor(last_update, 180)
726        })
727    }
728
729    /// Updates the SRTT value.
730    ///
731    /// If the `last_update` value has not been set, then uses the `default`
732    /// value to update the SRTT. Otherwise, invokes the `update_fn` with the
733    /// current SRTT value and the `last_update` timestamp.
734    fn update(&self, default: u32, update_fn: impl Fn(u32, Instant) -> u32) {
735        let last_update = self.last_update.lock().replace(Instant::now());
736        let _ = self.srtt_microseconds.fetch_update(
737            Ordering::SeqCst,
738            Ordering::SeqCst,
739            move |cur_srtt_microseconds| {
740                Some(
741                    last_update
742                        .map_or(default, |last_update| {
743                            update_fn(cur_srtt_microseconds, last_update)
744                        })
745                        .min(Self::MAX_SRTT_MICROS),
746                )
747            },
748        );
749    }
750
751    /// Returns the raw SRTT value.
752    ///
753    /// Prefer to use `decayed_srtt` when ordering name servers.
754    #[cfg(all(test, feature = "tokio"))]
755    fn as_duration(&self) -> Duration {
756        Duration::from_micros(u64::from(self.srtt_microseconds.load(Ordering::Acquire)))
757    }
758
759    const FAILURE_PENALTY: u32 = Duration::from_millis(150).as_micros() as u32;
760    const MAX_SRTT_MICROS: u32 = Duration::from_secs(5).as_micros() as u32;
761}
762
763/// Returns an exponentially weighted value in the range of 0.0 < x < 1.0
764///
765/// Computes the value using the following formula:
766///
767/// e<sup>(-t<sub>now</sub> - t<sub>last</sub>) / weight</sup>
768///
769/// As the duration since the `last_update` approaches the provided `weight`,
770/// the returned value decreases.
771fn compute_srtt_factor(last_update: Instant, weight: u32) -> f64 {
772    let exponent = (-last_update.elapsed().as_secs_f64().max(1.0)) / f64::from(weight);
773    exponent.exp()
774}
775
776/// State of a connection with a remote NameServer.
777#[derive(Debug, Eq, PartialEq, Copy, Clone)]
778#[repr(u8)]
779enum Status {
780    /// For some reason the connection failed. For UDP this would generally be a timeout
781    ///  for TCP this could be either Connection could never be established, or it
782    ///  failed at some point after. The Failed state should *not* be entered due to an
783    ///  error contained in a Message received from the server. In All cases to reestablish
784    ///  a new connection will need to be created.
785    Failed = 0,
786    /// Initial state, if Edns is not none, then Edns will be requested
787    Init = 1,
788    /// There has been successful communication with the remote.
789    ///  if no Edns is associated, then the remote does not support Edns
790    Established = 2,
791}
792
793impl From<Status> for u8 {
794    /// used for ordering purposes. The highest priority is placed on open connections
795    fn from(val: Status) -> Self {
796        val as Self
797    }
798}
799
800impl From<u8> for Status {
801    fn from(val: u8) -> Self {
802        match val {
803            2 => Self::Established,
804            1 => Self::Init,
805            _ => Self::Failed,
806        }
807    }
808}
809
810#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
811pub(crate) struct ConnectionPolicy {
812    pub(crate) disable_udp: bool,
813}
814
815impl ConnectionPolicy {
816    /// Checks if the given server has any protocols compatible with current policy.
817    pub(crate) fn allows_server<P: ConnectionProvider>(&self, server: &NameServer<P>) -> bool {
818        server.protocols().any(|p| self.allows_protocol(p))
819    }
820
821    /// Select the best pre-existing connection to use.
822    ///
823    /// This choice is made based on opportunistic encryption policy & probe history,
824    /// protocol policy, and the SRTT performance metrics.
825    fn select_connection<'a, P: ConnectionProvider>(
826        &self,
827        ip: IpAddr,
828        encrypted_transport_state: &NameServerTransportState,
829        opportunistic_encryption: &OpportunisticEncryption,
830        connections: &'a [ConnectionState<P>],
831    ) -> Option<&'a ConnectionState<P>> {
832        let selected = connections
833            .iter()
834            .filter(|conn| self.allows_protocol(conn.protocol))
835            .min_by(|a, b| self.compare_connections(opportunistic_encryption.is_enabled(), a, b));
836
837        let selected = selected?;
838
839        // If we're using opportunistic encryption and selected a pre-existing unencrypted connection,
840        // and have successfully probed on any supported encrypted protocol, we should _not_ reuse the
841        // existing connection and instead return `None`. This will result in a new encrypted connection
842        // being made to the successfully probed protocol and added to the connection list for future
843        // re-use.
844        match opportunistic_encryption.is_enabled()
845            && !selected.protocol.is_encrypted()
846            && encrypted_transport_state.any_recent_success(ip, opportunistic_encryption)
847        {
848            true => None,
849            false => Some(selected),
850        }
851    }
852
853    /// Select the best connection configuration to use for a new connection.
854    ///
855    /// This choice is made based on opportunistic encryption policy & probe history,
856    /// and protocol policy.
857    fn select_connection_config<'a>(
858        &self,
859        ip: IpAddr,
860        encrypted_transport_state: &NameServerTransportState,
861        opportunistic_encryption: &OpportunisticEncryption,
862        connection_configs: &'a [ConnectionConfig],
863    ) -> Option<&'a ConnectionConfig> {
864        connection_configs
865            .iter()
866            .filter(|c| self.allows_protocol(c.protocol.to_protocol()))
867            .min_by(|a, b| {
868                self.compare_connection_configs(
869                    ip,
870                    encrypted_transport_state,
871                    opportunistic_encryption,
872                    a,
873                    b,
874                )
875            })
876    }
877
878    /// Select the first protocol allowed by current policy that uses an encrypted transport.
879    fn select_encrypted_connection_config<'a>(
880        &self,
881        connection_config: &'a [ConnectionConfig],
882    ) -> Option<&'a ConnectionConfig> {
883        connection_config
884            .iter()
885            .filter(|c| self.allows_protocol(c.protocol.to_protocol()))
886            .find(|c| c.protocol.to_protocol().is_encrypted())
887    }
888
889    /// Checks if the given protocol is allowed by current policy.
890    fn allows_protocol(&self, protocol: Protocol) -> bool {
891        !(self.disable_udp && protocol == Protocol::Udp)
892    }
893
894    /// Compare two connections according to policy, protocol, and performance.
895    /// If opportunistic encryption is enabled we make an effort to select an encrypted connection.
896    fn compare_connections<P: ConnectionProvider>(
897        &self,
898        opportunistic_encryption: bool,
899        a: &ConnectionState<P>,
900        b: &ConnectionState<P>,
901    ) -> cmp::Ordering {
902        // When opportunistic encryption is in-play, we want to consider encrypted
903        // connections with the greatest priority.
904        if opportunistic_encryption {
905            match (a.protocol.is_encrypted(), b.protocol.is_encrypted()) {
906                (true, false) => return cmp::Ordering::Less,
907                (false, true) => return cmp::Ordering::Greater,
908                // When _both_ are encrypted, then decide on ordering based on other properties (like SRTT).
909                _ => {}
910            }
911        }
912
913        match (a.protocol, b.protocol) {
914            (ap, bp) if ap == bp => a.meta.srtt.current().total_cmp(&b.meta.srtt.current()),
915            (Protocol::Udp, _) => cmp::Ordering::Less,
916            (_, Protocol::Udp) => cmp::Ordering::Greater,
917            _ => a.meta.srtt.current().total_cmp(&b.meta.srtt.current()),
918        }
919    }
920
921    fn compare_connection_configs(
922        &self,
923        ip: IpAddr,
924        encrypted_transport_state: &NameServerTransportState,
925        opportunistic_encryption: &OpportunisticEncryption,
926        a: &ConnectionConfig,
927        b: &ConnectionConfig,
928    ) -> cmp::Ordering {
929        let a_protocol = a.protocol.to_protocol();
930        let b_protocol = b.protocol.to_protocol();
931
932        // When opportunistic encryption is in-play, prioritize encrypted protocols
933        // that have recent successful connections
934        if opportunistic_encryption.is_enabled() {
935            let a_recent_enc_success = a_protocol.is_encrypted()
936                && encrypted_transport_state.recent_success(
937                    ip,
938                    a_protocol,
939                    opportunistic_encryption,
940                );
941            let b_recent_enc_success = b_protocol.is_encrypted()
942                && encrypted_transport_state.recent_success(
943                    ip,
944                    b_protocol,
945                    opportunistic_encryption,
946                );
947
948            match (a_recent_enc_success, b_recent_enc_success) {
949                (true, false) => return cmp::Ordering::Less,
950                (false, true) => return cmp::Ordering::Greater,
951                // When both have recent success or neither do, continue with normal ordering
952                _ => {}
953            }
954        }
955
956        // Default protocol ordering: UDP first, then others
957        match (a_protocol, b_protocol) {
958            (ap, bp) if ap == bp => cmp::Ordering::Equal,
959            (Protocol::Udp, _) => cmp::Ordering::Less,
960            (_, Protocol::Udp) => cmp::Ordering::Greater,
961            _ => cmp::Ordering::Equal,
962        }
963    }
964}
965
966#[cfg(all(test, feature = "tokio"))]
967mod tests {
968    use std::cmp;
969    use std::net::{IpAddr, Ipv4Addr};
970    use std::str::FromStr;
971    use std::time::Duration;
972
973    use test_support::subscribe;
974    use tokio::net::UdpSocket;
975    use tokio::spawn;
976
977    use super::*;
978    use crate::config::{ConnectionConfig, ProtocolConfig};
979    use crate::connection_provider::TlsConfig;
980    use crate::net::runtime::TokioRuntimeProvider;
981    use crate::proto::op::{DnsRequest, DnsRequestOptions, Message, Query, ResponseCode};
982    use crate::proto::rr::rdata::NULL;
983    use crate::proto::rr::{Name, RData, Record, RecordType};
984
985    #[tokio::test]
986    async fn test_name_server() {
987        subscribe();
988
989        let options = ResolverOpts::default();
990        let config = NameServerConfig::udp(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)));
991        let name_server = Arc::new(NameServer::new(
992            [].into_iter(),
993            config,
994            &options,
995            TokioRuntimeProvider::default(),
996        ));
997
998        let cx = Arc::new(PoolContext::new(options, TlsConfig::new().unwrap()));
999        let name = Name::parse("www.example.com.", None).unwrap();
1000        let response = name_server
1001            .send(
1002                DnsRequest::from_query(
1003                    Query::query(name.clone(), RecordType::A),
1004                    DnsRequestOptions::default(),
1005                ),
1006                ConnectionPolicy::default(),
1007                &cx,
1008            )
1009            .await
1010            .expect("query failed");
1011        assert_eq!(response.response_code, ResponseCode::NoError);
1012    }
1013
1014    #[tokio::test]
1015    async fn test_failed_name_server() {
1016        subscribe();
1017
1018        let options = ResolverOpts {
1019            timeout: Duration::from_millis(1), // this is going to fail, make it fail fast...
1020            ..ResolverOpts::default()
1021        };
1022
1023        let config = NameServerConfig::udp(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 252)));
1024        let name_server = Arc::new(NameServer::new(
1025            [],
1026            config,
1027            &options,
1028            TokioRuntimeProvider::default(),
1029        ));
1030
1031        let cx = Arc::new(PoolContext::new(options, TlsConfig::new().unwrap()));
1032        let name = Name::parse("www.example.com.", None).unwrap();
1033        assert!(
1034            name_server
1035                .send(
1036                    DnsRequest::from_query(
1037                        Query::query(name.clone(), RecordType::A),
1038                        DnsRequestOptions::default(),
1039                    ),
1040                    ConnectionPolicy::default(),
1041                    &cx
1042                )
1043                .await
1044                .is_err()
1045        );
1046    }
1047
1048    #[tokio::test]
1049    async fn case_randomization_query_preserved() {
1050        subscribe();
1051
1052        let provider = TokioRuntimeProvider::default();
1053        let server = UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).await.unwrap();
1054        let server_addr = server.local_addr().unwrap();
1055        let name = Name::from_str("dead.beef.").unwrap();
1056        let data = b"DEADBEEF";
1057
1058        spawn({
1059            let name = name.clone();
1060            async move {
1061                let mut buffer = [0_u8; 512];
1062                let (len, addr) = server.recv_from(&mut buffer).await.unwrap();
1063                let request = Message::from_vec(&buffer[0..len]).unwrap();
1064                let mut response = Message::response(request.id, request.op_code);
1065                response.add_queries(request.queries.to_vec());
1066                response.add_answer(Record::from_rdata(
1067                    name,
1068                    0,
1069                    RData::NULL(NULL::with(data.to_vec())),
1070                ));
1071                let response_buffer = response.to_vec().unwrap();
1072                server.send_to(&response_buffer, addr).await.unwrap();
1073            }
1074        });
1075
1076        let config = NameServerConfig {
1077            ip: server_addr.ip(),
1078            trust_negative_responses: true,
1079            connections: vec![ConnectionConfig {
1080                port: server_addr.port(),
1081                protocol: ProtocolConfig::Udp,
1082                bind_addr: None,
1083            }],
1084        };
1085
1086        let resolver_opts = ResolverOpts {
1087            case_randomization: true,
1088            ..Default::default()
1089        };
1090
1091        let cx = Arc::new(PoolContext::new(resolver_opts, TlsConfig::new().unwrap()));
1092        let mut request_options = DnsRequestOptions::default();
1093        request_options.case_randomization = true;
1094        let ns = Arc::new(NameServer::new([], config, &cx.options, provider));
1095        let response = ns
1096            .send(
1097                DnsRequest::from_query(
1098                    Query::query(name.clone(), RecordType::NULL),
1099                    request_options,
1100                ),
1101                ConnectionPolicy::default(),
1102                &cx,
1103            )
1104            .await
1105            .unwrap();
1106
1107        let response_query_name = response.queries.first().unwrap().name();
1108        assert!(response_query_name.eq_case(&name));
1109    }
1110
1111    #[allow(clippy::extra_unused_type_parameters)]
1112    fn is_send_sync<S: Sync + Send>() -> bool {
1113        true
1114    }
1115
1116    #[test]
1117    fn stats_are_sync() {
1118        assert!(is_send_sync::<ConnectionMeta>());
1119    }
1120
1121    #[tokio::test(start_paused = true)]
1122    async fn test_stats_cmp() {
1123        use std::cmp::Ordering;
1124        let srtt_a = DecayingSrtt::new(Duration::from_micros(10));
1125        let srtt_b = DecayingSrtt::new(Duration::from_micros(20));
1126
1127        // No RTTs or failures have been recorded. The initial SRTTs should be
1128        // compared.
1129        assert_eq!(cmp(&srtt_a, &srtt_b), Ordering::Less);
1130
1131        // Server A was used. Unused server B should now be preferred.
1132        srtt_a.record(Duration::from_millis(30));
1133        tokio::time::advance(Duration::from_secs(5)).await;
1134        assert_eq!(cmp(&srtt_a, &srtt_b), Ordering::Greater);
1135
1136        // Both servers have been used. Server A has a lower SRTT and should be
1137        // preferred.
1138        srtt_b.record(Duration::from_millis(50));
1139        tokio::time::advance(Duration::from_secs(5)).await;
1140        assert_eq!(cmp(&srtt_a, &srtt_b), Ordering::Less);
1141
1142        // Server A experiences a connection failure, which results in Server B
1143        // being preferred.
1144        srtt_a.record_failure();
1145        tokio::time::advance(Duration::from_secs(5)).await;
1146        assert_eq!(cmp(&srtt_a, &srtt_b), Ordering::Greater);
1147
1148        // Server A should eventually recover and once again be preferred.
1149        while cmp(&srtt_a, &srtt_b) != Ordering::Less {
1150            srtt_b.record(Duration::from_millis(50));
1151            tokio::time::advance(Duration::from_secs(5)).await;
1152        }
1153
1154        srtt_a.record(Duration::from_millis(30));
1155        tokio::time::advance(Duration::from_secs(3)).await;
1156        assert_eq!(cmp(&srtt_a, &srtt_b), Ordering::Less);
1157    }
1158
1159    fn cmp(a: &DecayingSrtt, b: &DecayingSrtt) -> cmp::Ordering {
1160        a.current().total_cmp(&b.current())
1161    }
1162
1163    #[tokio::test(start_paused = true)]
1164    async fn test_record_rtt() {
1165        let srtt = DecayingSrtt::new(Duration::from_micros(10));
1166
1167        let first_rtt = Duration::from_millis(50);
1168        srtt.record(first_rtt);
1169
1170        // The first recorded RTT should replace the initial value.
1171        assert_eq!(srtt.as_duration(), first_rtt);
1172
1173        tokio::time::advance(Duration::from_secs(3)).await;
1174
1175        // Subsequent RTTs should factor in previously recorded values.
1176        srtt.record(Duration::from_millis(100));
1177        assert_eq!(srtt.as_duration(), Duration::from_micros(81606));
1178    }
1179
1180    #[test]
1181    fn test_record_rtt_maximum_value() {
1182        let srtt = DecayingSrtt::new(Duration::from_micros(10));
1183
1184        srtt.record(Duration::MAX);
1185        // Updates to the SRTT are capped at a maximum value.
1186        assert_eq!(
1187            srtt.as_duration(),
1188            Duration::from_micros(DecayingSrtt::MAX_SRTT_MICROS.into())
1189        );
1190    }
1191
1192    #[tokio::test(start_paused = true)]
1193    async fn test_record_connection_failure() {
1194        let srtt = DecayingSrtt::new(Duration::from_micros(10));
1195
1196        // Verify that the SRTT value is initially replaced with the penalty and
1197        // subsequent failures result in the penalty being added.
1198        for failure_count in 1..4 {
1199            srtt.record_failure();
1200            assert_eq!(
1201                srtt.as_duration(),
1202                Duration::from_micros(
1203                    DecayingSrtt::FAILURE_PENALTY
1204                        .checked_mul(failure_count)
1205                        .expect("checked_mul overflow")
1206                        .into()
1207                )
1208            );
1209            tokio::time::advance(Duration::from_secs(3)).await;
1210        }
1211
1212        // Verify that the `last_update` timestamp was updated for a connection
1213        // failure and is used in subsequent calculations.
1214        srtt.record(Duration::from_millis(50));
1215        assert_eq!(srtt.as_duration(), Duration::from_micros(197152));
1216    }
1217
1218    #[test]
1219    fn test_record_connection_failure_maximum_value() {
1220        let srtt = DecayingSrtt::new(Duration::from_micros(10));
1221
1222        let num_failures = (DecayingSrtt::MAX_SRTT_MICROS / DecayingSrtt::FAILURE_PENALTY) + 1;
1223        for _ in 0..num_failures {
1224            srtt.record_failure();
1225        }
1226
1227        // Updates to the SRTT are capped at a maximum value.
1228        assert_eq!(
1229            srtt.as_duration(),
1230            Duration::from_micros(DecayingSrtt::MAX_SRTT_MICROS.into())
1231        );
1232    }
1233
1234    #[tokio::test(start_paused = true)]
1235    async fn test_decayed_srtt() {
1236        let initial_srtt = 10;
1237        let srtt = DecayingSrtt::new(Duration::from_micros(initial_srtt));
1238
1239        // No decay should be applied to the initial value.
1240        assert_eq!(srtt.current() as u32, initial_srtt as u32);
1241
1242        tokio::time::advance(Duration::from_secs(5)).await;
1243        srtt.record(Duration::from_millis(100));
1244
1245        // The decay function should assume a minimum of one second has elapsed
1246        // since the last update.
1247        tokio::time::advance(Duration::from_millis(500)).await;
1248        assert_eq!(srtt.current() as u32, 99445);
1249
1250        tokio::time::advance(Duration::from_secs(5)).await;
1251        assert_eq!(srtt.current() as u32, 96990);
1252    }
1253}
1254
1255#[cfg(all(test, feature = "__tls"))]
1256mod opportunistic_enc_tests {
1257    use std::io;
1258    use std::net::{IpAddr, Ipv4Addr};
1259    use std::sync::Arc;
1260    use std::time::{Duration, SystemTime};
1261
1262    #[cfg(feature = "metrics")]
1263    use metrics::{Label, Unit, with_local_recorder};
1264    #[cfg(feature = "metrics")]
1265    use metrics_util::debugging::DebuggingRecorder;
1266    use mock_provider::{MockClientHandle, MockProvider};
1267    use test_support::subscribe;
1268    #[cfg(feature = "metrics")]
1269    use test_support::{assert_counter_eq, assert_gauge_eq, assert_histogram_sample_count_eq};
1270
1271    use crate::config::{
1272        NameServerConfig, OpportunisticEncryption, OpportunisticEncryptionConfig, ProtocolConfig,
1273        ResolverOpts,
1274    };
1275    use crate::connection_provider::TlsConfig;
1276    #[cfg(feature = "metrics")]
1277    use crate::metrics::opportunistic_encryption::{
1278        PROBE_ATTEMPTS_TOTAL, PROBE_BUDGET_TOTAL, PROBE_DURATION_SECONDS, PROBE_ERRORS_TOTAL,
1279        PROBE_SUCCESSES_TOTAL, PROBE_TIMEOUTS_TOTAL,
1280    };
1281    use crate::name_server::{ConnectionPolicy, ConnectionState, NameServer, mock_provider};
1282    use crate::name_server_pool::{NameServerTransportState, PoolContext};
1283    use crate::net::NetError;
1284    use crate::net::xfer::Protocol;
1285
1286    #[tokio::test]
1287    async fn test_select_connection_opportunistic_enc_disabled() {
1288        let mut policy = ConnectionPolicy::default();
1289        let connections = vec![
1290            mock_connection(Protocol::Udp),
1291            mock_connection(Protocol::Tcp),
1292        ];
1293
1294        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1295        let state = NameServerTransportState::default();
1296        let opp_enc = OpportunisticEncryption::Disabled;
1297
1298        // When opportunistic encryption is disabled, and disable_udp isn't active,
1299        // we should select the UDP conn.
1300        let selected = policy.select_connection(ns_ip, &state, &opp_enc, &connections);
1301        assert!(selected.is_some());
1302        assert_eq!(selected.unwrap().protocol, Protocol::Udp);
1303
1304        // When opportunistic encryption is disabled, and disable_udp is active,
1305        // we should select the TCP conn.
1306        policy.disable_udp = true;
1307        let selected = policy.select_connection(ns_ip, &state, &opp_enc, &connections);
1308        assert!(selected.is_some());
1309        assert_eq!(selected.unwrap().protocol, Protocol::Tcp);
1310    }
1311
1312    #[tokio::test]
1313    async fn test_select_connection_opportunistic_enc_enabled() {
1314        let policy = ConnectionPolicy::default();
1315        let connections = [
1316            mock_connection(Protocol::Udp),
1317            mock_connection(Protocol::Tcp),
1318            // Include a pre-existing encrypted protocol connection.
1319            mock_connection(Protocol::Tls),
1320        ];
1321
1322        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1323        let state = NameServerTransportState::default();
1324        let opp_enc = &OpportunisticEncryption::Enabled {
1325            config: OpportunisticEncryptionConfig::default(),
1326        };
1327
1328        // When opportunistic encryption is enabled, and there is an encrypted connection available,
1329        // we should always choose it as the most preferred.
1330        let selected = policy.select_connection(ns_ip, &state, opp_enc, &connections);
1331        assert!(selected.is_some());
1332        assert_eq!(selected.unwrap().protocol, Protocol::Tls);
1333    }
1334
1335    #[tokio::test]
1336    async fn test_select_connection_opportunistic_enc_enabled_no_state() {
1337        let mut policy = ConnectionPolicy::default();
1338        let connections = [
1339            mock_connection(Protocol::Udp),
1340            mock_connection(Protocol::Tcp),
1341            // No pre-existing encrypted protocol connection is available.
1342        ];
1343
1344        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1345        let state = NameServerTransportState::default();
1346        let opp_enc = &OpportunisticEncryption::Enabled {
1347            config: OpportunisticEncryptionConfig::default(),
1348        };
1349
1350        // When opportunistic encryption is enabled, but there are no encrypted connections available,
1351        // and we have no probe state, we should select the UDP conn.
1352        let selected = policy.select_connection(ns_ip, &state, opp_enc, &connections);
1353        assert!(selected.is_some());
1354        assert_eq!(selected.unwrap().protocol, Protocol::Udp);
1355
1356        // When opportunistic encryption is enabled, but there are no encrypted connections available,
1357        // and we have no probe state, we should select the TCP conn.
1358        policy.disable_udp = true;
1359        let selected = policy.select_connection(ns_ip, &state, opp_enc, &connections);
1360        assert!(selected.is_some());
1361        assert_eq!(selected.unwrap().protocol, Protocol::Tcp);
1362    }
1363
1364    #[tokio::test]
1365    async fn test_select_connection_opportunistic_enc_enabled_failed_probe() {
1366        let policy = ConnectionPolicy::default();
1367        let connections = [
1368            mock_connection(Protocol::Udp),
1369            mock_connection(Protocol::Tcp),
1370            // No pre-existing encrypted protocol connection is available.
1371        ];
1372
1373        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1374        let mut state = NameServerTransportState::default();
1375        let opp_enc = &OpportunisticEncryption::Enabled {
1376            config: OpportunisticEncryptionConfig::default(),
1377        };
1378
1379        // Update the state to reflect that we failed a previous probe attempt.
1380        state.error_received(
1381            ns_ip,
1382            Protocol::Tls,
1383            &NetError::from(io::Error::new(
1384                io::ErrorKind::ConnectionRefused,
1385                "nameserver refused TLS connection",
1386            )),
1387        );
1388
1389        // When opportunistic encryption is enabled, but there are no encrypted connections available,
1390        // and our probe state indicates a failure, we should select the UDP conn.
1391        let selected = policy.select_connection(ns_ip, &state, opp_enc, &connections);
1392        assert!(selected.is_some());
1393        assert_eq!(selected.unwrap().protocol, Protocol::Udp);
1394    }
1395
1396    #[tokio::test]
1397    async fn test_select_connection_opportunistic_enc_enabled_in_progress_probe() {
1398        let policy = ConnectionPolicy::default();
1399        let connections = [
1400            mock_connection(Protocol::Udp),
1401            mock_connection(Protocol::Tcp),
1402            // No pre-existing encrypted protocol connection is available.
1403        ];
1404
1405        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1406        let mut state = NameServerTransportState::default();
1407        let opp_enc = &OpportunisticEncryption::Enabled {
1408            config: OpportunisticEncryptionConfig::default(),
1409        };
1410
1411        // Update the state to reflect that we have an in-progress probe in-flight.
1412        state.initiate_connection(ns_ip, Protocol::Tls);
1413
1414        // When opportunistic encryption is enabled, but there are no encrypted connections available,
1415        // and our probe state indicates an in-flight probe, we should select the UDP conn.
1416        let selected = policy.select_connection(ns_ip, &state, opp_enc, &connections);
1417        assert!(selected.is_some());
1418        assert_eq!(selected.unwrap().protocol, Protocol::Udp);
1419
1420        // Update the state to reflect that we completed the connection, but haven't
1421        // received a response.
1422        state.complete_connection(ns_ip, Protocol::Tls);
1423
1424        // In this case we should still select the UDP conn.
1425        let selected = policy.select_connection(ns_ip, &state, opp_enc, &connections);
1426        assert!(selected.is_some());
1427        assert_eq!(selected.unwrap().protocol, Protocol::Udp);
1428    }
1429
1430    #[tokio::test]
1431    async fn test_select_connection_opportunistic_enc_enabled_stale_probe() {
1432        let policy = ConnectionPolicy::default();
1433        let connections = [
1434            mock_connection(Protocol::Udp),
1435            mock_connection(Protocol::Tcp),
1436            // No pre-existing encrypted protocol connection is available.
1437        ];
1438
1439        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1440        let mut state = NameServerTransportState::default();
1441        let opp_enc_config = OpportunisticEncryptionConfig {
1442            persistence_period: Duration::from_secs(10),
1443            ..OpportunisticEncryptionConfig::default()
1444        };
1445        let opp_enc = &OpportunisticEncryption::Enabled {
1446            config: opp_enc_config.clone(),
1447        };
1448
1449        // Update the state to reflect that we have successfully probed this NS.
1450        state.complete_connection(ns_ip, Protocol::Tls);
1451        state.response_received(ns_ip, Protocol::Tls);
1452        // And then update the last response time to be too stale for consideration.
1453        let stale_time =
1454            SystemTime::now() - opp_enc_config.persistence_period - Duration::from_secs(1);
1455        state.set_last_response(ns_ip, Protocol::Tls, stale_time);
1456
1457        // When opportunistic encryption is enabled, but there are no encrypted connections available,
1458        // and our probe state indicates success that is too stale, we should select an unencrypted
1459        // connection since the probe is no longer considered recent.
1460        let selected = policy.select_connection(ns_ip, &state, opp_enc, &connections);
1461        assert!(selected.is_some());
1462        assert_eq!(selected.unwrap().protocol, Protocol::Udp);
1463    }
1464
1465    #[tokio::test]
1466    async fn test_select_connection_opportunistic_enc_enabled_good_probe() {
1467        let policy = ConnectionPolicy::default();
1468        let connections = [
1469            mock_connection(Protocol::Udp),
1470            mock_connection(Protocol::Tcp),
1471            // No pre-existing encrypted protocol connection is available.
1472        ];
1473
1474        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1475        let mut state = NameServerTransportState::default();
1476        let opp_enc = &OpportunisticEncryption::Enabled {
1477            config: OpportunisticEncryptionConfig::default(),
1478        };
1479
1480        // Update the state to reflect that we have successfully probed this NS within
1481        // the persistence period and received a response.
1482        state.complete_connection(ns_ip, Protocol::Tls);
1483        state.response_received(ns_ip, Protocol::Tls);
1484
1485        // When opportunistic encryption is enabled, but there are no encrypted connections available,
1486        // and our probe state indicates a recent enough success, we should return `None` so that
1487        // we make a new encrypted connection.
1488        let selected = policy.select_connection(ns_ip, &state, opp_enc, &connections);
1489        assert!(selected.is_none());
1490    }
1491
1492    #[tokio::test]
1493    async fn test_select_connection_config_opportunistic_enc_disabled() {
1494        let mut policy = ConnectionPolicy::default();
1495
1496        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1497        let configs = NameServerConfig::opportunistic_encryption(ns_ip).connections;
1498
1499        let state = NameServerTransportState::default();
1500        let opp_enc = OpportunisticEncryption::Disabled;
1501
1502        // When opportunistic encryption is disabled, and disable_udp isn't active,
1503        // we should select the UDP config.
1504        let selected = policy.select_connection_config(ns_ip, &state, &opp_enc, &configs);
1505        assert!(selected.is_some());
1506        assert_eq!(selected.unwrap().protocol, ProtocolConfig::Udp);
1507
1508        // When opportunistic encryption is disabled, and disable_udp is active,
1509        // we should select the TCP config.
1510        policy.disable_udp = true;
1511        let selected = policy.select_connection_config(ns_ip, &state, &opp_enc, &configs);
1512        assert!(selected.is_some());
1513        assert_eq!(selected.unwrap().protocol, ProtocolConfig::Tcp);
1514    }
1515
1516    #[tokio::test]
1517    async fn test_select_connection_config_opportunistic_enc_enabled_no_state() {
1518        let mut policy = ConnectionPolicy::default();
1519        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1520        let configs = NameServerConfig::opportunistic_encryption(ns_ip).connections;
1521
1522        let state = NameServerTransportState::default();
1523        let opp_enc = &OpportunisticEncryption::Enabled {
1524            config: OpportunisticEncryptionConfig::default(),
1525        };
1526
1527        // When opportunistic encryption is enabled, but we have no probe state,
1528        // we should select the UDP config (default protocol ordering).
1529        let selected = policy.select_connection_config(ns_ip, &state, opp_enc, &configs);
1530        assert!(selected.is_some());
1531        assert_eq!(selected.unwrap().protocol, ProtocolConfig::Udp);
1532
1533        // When opportunistic encryption is enabled, but we have no probe state,
1534        // and disable_udp is active, we should select the TCP config.
1535        policy.disable_udp = true;
1536        let selected = policy.select_connection_config(ns_ip, &state, opp_enc, &configs);
1537        assert!(selected.is_some());
1538        assert_eq!(selected.unwrap().protocol, ProtocolConfig::Tcp);
1539    }
1540
1541    #[tokio::test]
1542    async fn test_select_connection_config_opportunistic_enc_enabled_failed_probe() {
1543        let policy = ConnectionPolicy::default();
1544        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1545        let configs = NameServerConfig::opportunistic_encryption(ns_ip).connections;
1546
1547        let mut state = NameServerTransportState::default();
1548        let opp_enc = &OpportunisticEncryption::Enabled {
1549            config: OpportunisticEncryptionConfig::default(),
1550        };
1551
1552        // Update the state to reflect that we failed a previous probe attempt.
1553        state.error_received(
1554            ns_ip,
1555            Protocol::Tls,
1556            &NetError::from(io::Error::new(
1557                io::ErrorKind::ConnectionRefused,
1558                "nameserver refused TLS connection",
1559            )),
1560        );
1561
1562        // When opportunistic encryption is enabled, but our probe state indicates a failure,
1563        // we should select the UDP config.
1564        let selected = policy.select_connection_config(ns_ip, &state, opp_enc, &configs);
1565        assert!(selected.is_some());
1566        assert_eq!(selected.unwrap().protocol, ProtocolConfig::Udp);
1567    }
1568
1569    #[tokio::test]
1570    async fn test_select_connection_config_opportunistic_enc_enabled_stale_probe() {
1571        let policy = ConnectionPolicy::default();
1572        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1573        let configs = NameServerConfig::opportunistic_encryption(ns_ip).connections;
1574
1575        let mut state = NameServerTransportState::default();
1576        let opp_enc_config = OpportunisticEncryptionConfig {
1577            persistence_period: Duration::from_secs(10),
1578            ..OpportunisticEncryptionConfig::default()
1579        };
1580        let opp_enc = &OpportunisticEncryption::Enabled {
1581            config: opp_enc_config.clone(),
1582        };
1583
1584        // Update the state to reflect that we have successfully probed this NS.
1585        state.complete_connection(ns_ip, Protocol::Tls);
1586        state.response_received(ns_ip, Protocol::Tls);
1587        // And then update the last response time to be too stale for consideration.
1588        let stale_time =
1589            SystemTime::now() - opp_enc_config.persistence_period - Duration::from_secs(1);
1590        state.set_last_response(ns_ip, Protocol::Tls, stale_time);
1591
1592        // When opportunistic encryption is enabled, but our probe state indicates success that is too stale,
1593        // we should select an unencrypted config since the probe is no longer considered recent.
1594        let selected = policy.select_connection_config(ns_ip, &state, opp_enc, &configs);
1595        assert!(selected.is_some());
1596        assert_eq!(selected.unwrap().protocol, ProtocolConfig::Udp);
1597    }
1598
1599    #[tokio::test]
1600    async fn test_select_connection_config_opportunistic_enc_enabled_good_probe() {
1601        let policy = ConnectionPolicy::default();
1602        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1603        let configs = NameServerConfig::opportunistic_encryption(ns_ip).connections;
1604
1605        let mut state = NameServerTransportState::default();
1606        let opp_enc = &OpportunisticEncryption::Enabled {
1607            config: OpportunisticEncryptionConfig::default(),
1608        };
1609
1610        // Update the state to reflect that we have successfully probed this NS within
1611        // the persistence period and received a response.
1612        state.complete_connection(ns_ip, Protocol::Tls);
1613        state.response_received(ns_ip, Protocol::Tls);
1614
1615        // When opportunistic encryption is enabled, and our probe state indicates a recent enough success,
1616        // we should select the encrypted config with highest priority.
1617        let selected = policy.select_connection_config(ns_ip, &state, opp_enc, &configs);
1618        assert!(selected.is_some());
1619        assert!(matches!(
1620            selected.unwrap().protocol,
1621            ProtocolConfig::Tls { .. }
1622        ));
1623    }
1624
1625    #[tokio::test]
1626    async fn test_opportunistic_probe() {
1627        subscribe();
1628
1629        // Enable opportunistic encryption
1630        let cx = PoolContext::new(ResolverOpts::default(), TlsConfig::new().unwrap())
1631            .with_opportunistic_encryption()
1632            .with_probe_budget(10);
1633
1634        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1635        let mock_provider = MockProvider::default();
1636        assert!(
1637            test_connected_mut_client(ns_ip, Arc::new(cx), &mock_provider)
1638                .await
1639                .is_ok()
1640        );
1641
1642        let recorded_calls = mock_provider.new_connection_calls();
1643        // We should have made two new connection calls.
1644        assert_eq!(recorded_calls.len(), 2);
1645        let (ips, protocols): (Vec<IpAddr>, Vec<ProtocolConfig>) =
1646            recorded_calls.into_iter().unzip();
1647        // All connections should be to the expected NS IP.
1648        assert!(ips.iter().all(|ip| *ip == ns_ip));
1649        // We should have made connections for both the UDP protocol, and the encrypted probe protocol.
1650        let protocols = protocols
1651            .iter()
1652            .map(ProtocolConfig::to_protocol)
1653            .collect::<Vec<_>>();
1654        assert!(protocols.contains(&Protocol::Udp));
1655        assert!(protocols.contains(&Protocol::Tls));
1656    }
1657
1658    #[tokio::test]
1659    async fn test_opportunistic_probe_skip_in_progress() {
1660        subscribe();
1661
1662        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1663        let cx = PoolContext::new(ResolverOpts::default(), TlsConfig::new().unwrap())
1664            .with_opportunistic_encryption()
1665            .with_probe_budget(10);
1666
1667        // Set up state to show an in-flight connection already initiated
1668        cx.transport_state()
1669            .await
1670            .initiate_connection(ns_ip, Protocol::Tls);
1671
1672        let mock_provider = MockProvider::default();
1673        assert!(
1674            test_connected_mut_client(ns_ip, Arc::new(cx), &mock_provider)
1675                .await
1676                .is_ok()
1677        );
1678
1679        let recorded_calls = mock_provider.new_connection_calls();
1680        // We should have made only one connection call (UDP), no probe because one is already in-flight
1681        assert_eq!(recorded_calls.len(), 1);
1682        let (ip, protocol) = &recorded_calls[0];
1683        assert_eq!(*ip, ns_ip);
1684        assert_eq!(protocol.to_protocol(), Protocol::Udp);
1685    }
1686
1687    #[tokio::test]
1688    async fn test_opportunistic_probe_skip_recent_failure() {
1689        subscribe();
1690
1691        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1692        let cx = PoolContext::new(ResolverOpts::default(), TlsConfig::new().unwrap())
1693            .with_opportunistic_encryption()
1694            .with_probe_budget(10);
1695
1696        // Set up state to show a recent failure within the damping period
1697        cx.transport_state().await.error_received(
1698            ns_ip,
1699            Protocol::Tls,
1700            &NetError::from(io::Error::new(
1701                io::ErrorKind::ConnectionRefused,
1702                "connection refused",
1703            )),
1704        );
1705
1706        let mock_provider = MockProvider::default();
1707        assert!(
1708            test_connected_mut_client(ns_ip, Arc::new(cx), &mock_provider)
1709                .await
1710                .is_ok()
1711        );
1712
1713        let recorded_calls = mock_provider.new_connection_calls();
1714        // We should have made only one connection call (UDP), no probe due to recent failure
1715        assert_eq!(recorded_calls.len(), 1);
1716        let (ip, protocol) = &recorded_calls[0];
1717        assert_eq!(*ip, ns_ip);
1718        assert_eq!(protocol.to_protocol(), Protocol::Udp);
1719    }
1720
1721    #[tokio::test]
1722    async fn test_opportunistic_probe_stale_failure() {
1723        subscribe();
1724
1725        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1726        let mut cx = PoolContext::new(ResolverOpts::default(), TlsConfig::new().unwrap())
1727            .with_probe_budget(10);
1728        let opp_enc_config = OpportunisticEncryptionConfig {
1729            damping_period: Duration::from_secs(5),
1730            ..OpportunisticEncryptionConfig::default()
1731        };
1732        cx.opportunistic_encryption = OpportunisticEncryption::Enabled {
1733            config: opp_enc_config.clone(),
1734        };
1735
1736        // Set up state to show an old failure outside the damping period.
1737        {
1738            let mut state = cx.transport_state().await;
1739            let old_failure_time =
1740                SystemTime::now() - opp_enc_config.damping_period - Duration::from_secs(1);
1741            state.set_failure_time(ns_ip, Protocol::Tls, old_failure_time);
1742        }
1743
1744        let mock_provider = MockProvider::default();
1745        assert!(
1746            test_connected_mut_client(ns_ip, Arc::new(cx), &mock_provider)
1747                .await
1748                .is_ok()
1749        );
1750
1751        let recorded_calls = mock_provider.new_connection_calls();
1752        // We should have made two connection calls (UDP + TLS probe) because the failure is old
1753        assert_eq!(recorded_calls.len(), 2);
1754        let protocols = recorded_calls
1755            .iter()
1756            .map(|(_, protocol)| protocol.to_protocol())
1757            .collect::<Vec<_>>();
1758        assert!(protocols.contains(&Protocol::Udp));
1759        assert!(protocols.contains(&Protocol::Tls));
1760    }
1761
1762    #[tokio::test]
1763    async fn test_opportunistic_probe_skip_no_budget() {
1764        subscribe();
1765
1766        let ns_ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
1767        let cx = PoolContext::new(ResolverOpts::default(), TlsConfig::new().unwrap())
1768            .with_opportunistic_encryption();
1769        let mock_provider = MockProvider::default();
1770        // Set budget to 0 to simulate exhausted probe budget
1771        assert!(
1772            test_connected_mut_client(ns_ip, Arc::new(cx), &mock_provider)
1773                .await
1774                .is_ok()
1775        );
1776
1777        let recorded_calls = mock_provider.new_connection_calls();
1778        // We should have made only one connection call (UDP), no probe due to exhausted budget
1779        assert_eq!(recorded_calls.len(), 1);
1780        let (ip, protocol) = &recorded_calls[0];
1781        assert_eq!(*ip, ns_ip);
1782        assert_eq!(protocol.to_protocol(), Protocol::Udp);
1783    }
1784
1785    fn mock_connection(protocol: Protocol) -> ConnectionState<MockProvider> {
1786        ConnectionState::new(MockClientHandle::default(), protocol)
1787    }
1788
1789    #[cfg(feature = "metrics")]
1790    #[test]
1791    fn test_opportunistic_probe_metrics_success() {
1792        subscribe();
1793        let recorder = DebuggingRecorder::new();
1794        let snapshotter = recorder.snapshotter();
1795        let initial_budget = 10;
1796
1797        with_local_recorder(&recorder, || {
1798            let runtime = tokio::runtime::Builder::new_current_thread()
1799                .enable_all()
1800                .build()
1801                .unwrap();
1802
1803            runtime.block_on(async {
1804                assert!(
1805                    test_connected_mut_client(
1806                        IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)),
1807                        Arc::new(
1808                            PoolContext::new(ResolverOpts::default(), TlsConfig::new().unwrap())
1809                                .with_opportunistic_encryption()
1810                                .with_probe_budget(initial_budget),
1811                        ),
1812                        &MockProvider::default(),
1813                    )
1814                    .await
1815                    .is_ok()
1816                );
1817            });
1818        });
1819
1820        #[allow(clippy::mutable_key_type)]
1821        let map = snapshotter.snapshot().into_hashmap();
1822
1823        // We should have registered 1 TLS protocol probe attempt.
1824        let protocol = vec![Label::new("protocol", "tls")];
1825        assert_counter_eq(&map, PROBE_ATTEMPTS_TOTAL, protocol.clone(), 1);
1826        // And seen 1 probe duration observation.
1827        assert_histogram_sample_count_eq(
1828            &map,
1829            PROBE_DURATION_SECONDS,
1830            protocol.clone(),
1831            1,
1832            Unit::Seconds,
1833        );
1834
1835        // We should have registered 1 TLS protocol probe success.
1836        assert_counter_eq(&map, PROBE_SUCCESSES_TOTAL, protocol.clone(), 1);
1837
1838        // We should have registered 0 TLS protocol probe errors.
1839        assert_counter_eq(&map, PROBE_ERRORS_TOTAL, protocol, 0);
1840
1841        // The budget should be back to the initial value now that the probe completed.
1842        assert_gauge_eq(&map, PROBE_BUDGET_TOTAL, vec![], initial_budget);
1843    }
1844
1845    #[cfg(feature = "metrics")]
1846    #[test]
1847    fn test_opportunistic_probe_metrics_budget_exhausted() {
1848        subscribe();
1849        let recorder = DebuggingRecorder::new();
1850        let snapshotter = recorder.snapshotter();
1851
1852        with_local_recorder(&recorder, || {
1853            let runtime = tokio::runtime::Builder::new_current_thread()
1854                .enable_all()
1855                .build()
1856                .unwrap();
1857
1858            runtime.block_on(async {
1859                assert!(
1860                    test_connected_mut_client(
1861                        IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)),
1862                        Arc::new(
1863                            PoolContext::new(ResolverOpts::default(), TlsConfig::new().unwrap())
1864                                .with_opportunistic_encryption(),
1865                        ),
1866                        &MockProvider::default(),
1867                    )
1868                    .await
1869                    .is_ok()
1870                );
1871            });
1872        });
1873
1874        #[allow(clippy::mutable_key_type)]
1875        let map = snapshotter.snapshot().into_hashmap();
1876
1877        // The budget metric should confirm that there's no budget.
1878        assert_gauge_eq(&map, PROBE_BUDGET_TOTAL, vec![], 0);
1879
1880        // We should not have registered a probe attempt.
1881        let protocol = vec![Label::new("protocol", "tls")];
1882        assert_counter_eq(&map, PROBE_ATTEMPTS_TOTAL, protocol.clone(), 0);
1883        // Or seen a probe duration observation.
1884        assert_histogram_sample_count_eq(&map, PROBE_DURATION_SECONDS, protocol, 0, Unit::Seconds);
1885    }
1886
1887    #[cfg(feature = "metrics")]
1888    #[test]
1889    fn test_opportunistic_probe_metrics_connection_error() {
1890        subscribe();
1891        let recorder = DebuggingRecorder::new();
1892        let snapshotter = recorder.snapshotter();
1893        let initial_budget = 10;
1894
1895        with_local_recorder(&recorder, || {
1896            let runtime = tokio::runtime::Builder::new_current_thread()
1897                .enable_all()
1898                .build()
1899                .unwrap();
1900
1901            runtime.block_on(async {
1902                let _ = test_connected_mut_client(
1903                    IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)),
1904                    Arc::new(
1905                        PoolContext::new(ResolverOpts::default(), TlsConfig::new().unwrap())
1906                            .with_opportunistic_encryption()
1907                            .with_probe_budget(initial_budget),
1908                    ),
1909                    // Configure a mock provider that always produces an error when new connections are requested.
1910                    &MockProvider {
1911                        new_connection_error: Some(NetError::from(io::Error::new(
1912                            io::ErrorKind::ConnectionRefused,
1913                            "connection refused",
1914                        ))),
1915                        ..MockProvider::default()
1916                    },
1917                )
1918                .await;
1919            });
1920        });
1921
1922        #[allow(clippy::mutable_key_type)]
1923        let map = snapshotter.snapshot().into_hashmap();
1924
1925        // We should have registered 1 TLS protocol probe attempt.
1926        let protocol = vec![Label::new("protocol", "tls")];
1927        assert_counter_eq(&map, PROBE_ATTEMPTS_TOTAL, protocol.clone(), 1);
1928        // And seen 1 probe duration observation.
1929        assert_histogram_sample_count_eq(
1930            &map,
1931            PROBE_DURATION_SECONDS,
1932            protocol.clone(),
1933            1,
1934            Unit::Seconds,
1935        );
1936
1937        // We should have registered 1 TLS protocol probe error.
1938        assert_counter_eq(&map, PROBE_ERRORS_TOTAL, protocol.clone(), 1);
1939
1940        // We shouldn't have registered any TLS protocol probe successes due to the
1941        // mock new connection error.
1942        assert_counter_eq(&map, PROBE_SUCCESSES_TOTAL, protocol, 0);
1943
1944        // The budget should be back to the initial value now that the probe completed.
1945        assert_gauge_eq(&map, PROBE_BUDGET_TOTAL, vec![], initial_budget);
1946    }
1947
1948    #[cfg(feature = "metrics")]
1949    #[test]
1950    fn test_opportunistic_probe_metrics_connection_timeout_error() {
1951        subscribe();
1952        let recorder = DebuggingRecorder::new();
1953        let snapshotter = recorder.snapshotter();
1954        let initial_budget = 10;
1955
1956        with_local_recorder(&recorder, || {
1957            let runtime = tokio::runtime::Builder::new_current_thread()
1958                .enable_all()
1959                .build()
1960                .unwrap();
1961
1962            runtime.block_on(async {
1963                let _ = test_connected_mut_client(
1964                    IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)),
1965                    Arc::new(
1966                        PoolContext::new(ResolverOpts::default(), TlsConfig::new().unwrap())
1967                            .with_opportunistic_encryption()
1968                            .with_probe_budget(initial_budget),
1969                    ),
1970                    // Configure a mock provider that always produces a Timeout error when new connections are requested.
1971                    &MockProvider {
1972                        new_connection_error: Some(NetError::Timeout),
1973                        ..MockProvider::default()
1974                    },
1975                )
1976                .await;
1977            });
1978        });
1979
1980        #[allow(clippy::mutable_key_type)]
1981        let map = snapshotter.snapshot().into_hashmap();
1982
1983        // We should have registered 1 TLS protocol probe attempt.
1984        let protocol = vec![Label::new("protocol", "tls")];
1985        assert_counter_eq(&map, PROBE_ATTEMPTS_TOTAL, protocol.clone(), 1);
1986        // And seen 1 probe duration observation.
1987        assert_histogram_sample_count_eq(
1988            &map,
1989            PROBE_DURATION_SECONDS,
1990            protocol.clone(),
1991            1,
1992            Unit::Seconds,
1993        );
1994
1995        // We should have registered 1 TLS protocol probe timeout.
1996        assert_counter_eq(&map, PROBE_TIMEOUTS_TOTAL, protocol.clone(), 1);
1997
1998        // We shouldn't have registered a more general probe error.
1999        assert_counter_eq(&map, PROBE_ERRORS_TOTAL, protocol.clone(), 0);
2000
2001        // We shouldn't have registered any TLS protocol probe successes due to the
2002        // mock new connection error.
2003        assert_counter_eq(&map, PROBE_SUCCESSES_TOTAL, protocol, 0);
2004
2005        // The budget should be back to the initial value now that the probe completed.
2006        assert_gauge_eq(&map, PROBE_BUDGET_TOTAL, vec![], initial_budget);
2007    }
2008
2009    /// Construct a nameserver appropriate for opportunistic encryption and assert connected_mut_client
2010    /// returns Ok.
2011    ///
2012    /// Behind the scenes this may provoke probing behaviour that the calling test can observe via
2013    /// the `MockProvider`'s recorded calls.
2014    async fn test_connected_mut_client(
2015        ns_ip: IpAddr,
2016        cx: Arc<PoolContext>,
2017        provider: &MockProvider,
2018    ) -> Result<(), NetError> {
2019        let name_server = NameServer::new(
2020            [],
2021            NameServerConfig::opportunistic_encryption(ns_ip),
2022            &ResolverOpts::default(),
2023            provider.clone(),
2024        );
2025
2026        match name_server
2027            .connected_mut_client(ConnectionPolicy::default(), &cx)
2028            .await
2029        {
2030            Ok(_) => Ok(()),
2031            Err((e, _)) => Err(e),
2032        }
2033    }
2034}
2035
2036#[cfg(all(test, feature = "metrics"))]
2037mod resolver_metrics_tests {
2038    use std::net::{IpAddr, Ipv4Addr};
2039
2040    use metrics::{Label, with_local_recorder};
2041    use metrics_util::debugging::DebuggingRecorder;
2042    use mock_provider::MockProvider;
2043    use test_support::assert_counter_eq;
2044    use test_support::subscribe;
2045
2046    use super::*;
2047    use crate::connection_provider::TlsConfig;
2048    use crate::metrics::OUTGOING_QUERIES_TOTAL;
2049
2050    #[test]
2051    fn test_outgoing_query_protocol_metrics_udp() {
2052        subscribe();
2053        let recorder = DebuggingRecorder::new();
2054        let snapshotter = recorder.snapshotter();
2055
2056        with_local_recorder(&recorder, || {
2057            let runtime = tokio::runtime::Builder::new_current_thread()
2058                .enable_all()
2059                .build()
2060                .unwrap();
2061
2062            runtime.block_on(async {
2063                let options = ResolverOpts {
2064                    enable_per_name_server_metrics: true,
2065                    ..ResolverOpts::default()
2066                };
2067                let config = NameServerConfig::udp(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)));
2068                let name_server = Arc::new(NameServer::new(
2069                    [],
2070                    config,
2071                    &options,
2072                    MockProvider::default(),
2073                ));
2074
2075                let cx = Arc::new(PoolContext::new(options, TlsConfig::new().unwrap()));
2076                let name = Name::parse("www.example.com.", None).unwrap();
2077                let _ = name_server
2078                    .send(
2079                        DnsRequest::from_query(
2080                            Query::query(name.clone(), RecordType::A),
2081                            DnsRequestOptions::default(),
2082                        ),
2083                        ConnectionPolicy::default(),
2084                        &cx,
2085                    )
2086                    .await;
2087            });
2088        });
2089
2090        #[allow(clippy::mutable_key_type)]
2091        let map = snapshotter.snapshot().into_hashmap();
2092
2093        // We should have registered 1 UDP protocol query.
2094        let protocol = vec![Label::new("protocol", "udp")];
2095        assert_counter_eq(&map, OUTGOING_QUERIES_TOTAL, protocol, 1);
2096    }
2097
2098    #[test]
2099    fn test_outgoing_query_protocol_metrics_tcp() {
2100        subscribe();
2101        let recorder = DebuggingRecorder::new();
2102        let snapshotter = recorder.snapshotter();
2103
2104        with_local_recorder(&recorder, || {
2105            let runtime = tokio::runtime::Builder::new_current_thread()
2106                .enable_all()
2107                .build()
2108                .unwrap();
2109
2110            runtime.block_on(async {
2111                let options = ResolverOpts::default();
2112                let config = NameServerConfig::tcp(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)));
2113                let name_server = Arc::new(NameServer::new(
2114                    [],
2115                    config,
2116                    &options,
2117                    MockProvider::default(),
2118                ));
2119
2120                let cx = Arc::new(PoolContext::new(options, TlsConfig::new().unwrap()));
2121                let name = Name::parse("www.example.com.", None).unwrap();
2122                let _ = name_server
2123                    .send(
2124                        DnsRequest::from_query(
2125                            Query::query(name.clone(), RecordType::A),
2126                            DnsRequestOptions::default(),
2127                        ),
2128                        ConnectionPolicy::default(),
2129                        &cx,
2130                    )
2131                    .await;
2132            });
2133        });
2134
2135        #[allow(clippy::mutable_key_type)]
2136        let map = snapshotter.snapshot().into_hashmap();
2137
2138        // We should have registered 1 TCP protocol query.
2139        let protocol = vec![Label::new("protocol", "tcp")];
2140        assert_counter_eq(&map, OUTGOING_QUERIES_TOTAL, protocol, 1);
2141    }
2142
2143    #[cfg(feature = "__tls")]
2144    #[test]
2145    fn test_outgoing_query_protocol_metrics_tls() {
2146        subscribe();
2147        let recorder = DebuggingRecorder::new();
2148        let snapshotter = recorder.snapshotter();
2149
2150        with_local_recorder(&recorder, || {
2151            let runtime = tokio::runtime::Builder::new_current_thread()
2152                .enable_all()
2153                .build()
2154                .unwrap();
2155
2156            runtime.block_on(async {
2157                let options = ResolverOpts::default();
2158                let config = NameServerConfig::tls(
2159                    IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)),
2160                    "dns.google".into(),
2161                );
2162                let name_server = Arc::new(NameServer::new(
2163                    [],
2164                    config,
2165                    &options,
2166                    MockProvider::default(),
2167                ));
2168
2169                let cx = Arc::new(PoolContext::new(options, TlsConfig::new().unwrap()));
2170                let name = Name::parse("www.example.com.", None).unwrap();
2171                let _ = name_server
2172                    .send(
2173                        DnsRequest::from_query(
2174                            Query::query(name.clone(), RecordType::A),
2175                            DnsRequestOptions::default(),
2176                        ),
2177                        ConnectionPolicy::default(),
2178                        &cx,
2179                    )
2180                    .await;
2181            });
2182        });
2183
2184        #[allow(clippy::mutable_key_type)]
2185        let map = snapshotter.snapshot().into_hashmap();
2186
2187        // We should have registered 1 TLS protocol query.
2188        let protocol = vec![Label::new("protocol", "tls")];
2189        assert_counter_eq(&map, OUTGOING_QUERIES_TOTAL, protocol, 1);
2190    }
2191
2192    #[test]
2193    fn test_name_server_query_success_metrics() {
2194        subscribe();
2195        let recorder = DebuggingRecorder::new();
2196        let snapshotter = recorder.snapshotter();
2197
2198        with_local_recorder(&recorder, || {
2199            let runtime = tokio::runtime::Builder::new_current_thread()
2200                .enable_all()
2201                .build()
2202                .unwrap();
2203
2204            runtime.block_on(async {
2205                let options = ResolverOpts {
2206                    enable_per_name_server_metrics: true,
2207                    ..ResolverOpts::default()
2208                };
2209                let config = NameServerConfig::udp(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)));
2210                let mock_provider = MockProvider::default();
2211                let name_server = Arc::new(NameServer::new([], config, &options, mock_provider));
2212
2213                let cx = Arc::new(PoolContext::new(options, TlsConfig::new().unwrap()));
2214                let name = Name::parse("www.example.com.", None).unwrap();
2215                let _ = name_server
2216                    .send(
2217                        DnsRequest::from_query(
2218                            Query::query(name.clone(), RecordType::A),
2219                            DnsRequestOptions::default(),
2220                        ),
2221                        ConnectionPolicy::default(),
2222                        &cx,
2223                    )
2224                    .await;
2225            });
2226        });
2227
2228        #[allow(clippy::mutable_key_type)]
2229        let map = snapshotter.snapshot().into_hashmap();
2230
2231        use crate::metrics::NAME_SERVER_QUERY_RESPONSES;
2232        let labels = vec![
2233            Label::new("addr", "8.8.8.8"),
2234            Label::new("protocol", "udp"),
2235            Label::new("status", "success"),
2236        ];
2237        assert_counter_eq(&map, NAME_SERVER_QUERY_RESPONSES, labels, 1);
2238    }
2239
2240    #[test]
2241    fn test_name_server_query_connection_failure_metrics() {
2242        subscribe();
2243        let recorder = DebuggingRecorder::new();
2244        let snapshotter = recorder.snapshotter();
2245
2246        with_local_recorder(&recorder, || {
2247            let runtime = tokio::runtime::Builder::new_current_thread()
2248                .enable_all()
2249                .build()
2250                .unwrap();
2251
2252            runtime.block_on(async {
2253                let options = ResolverOpts {
2254                    enable_per_name_server_metrics: true,
2255                    ..ResolverOpts::default()
2256                };
2257                let config = NameServerConfig::udp(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)));
2258                let mock_provider = MockProvider {
2259                    new_connection_error: Some(NetError::Io(Arc::new(std::io::Error::new(
2260                        std::io::ErrorKind::ConnectionRefused,
2261                        "connection refused",
2262                    )))),
2263                    ..MockProvider::default()
2264                };
2265                let name_server = Arc::new(NameServer::new([], config, &options, mock_provider));
2266
2267                let cx = Arc::new(PoolContext::new(options, TlsConfig::new().unwrap()));
2268                let name = Name::parse("www.example.com.", None).unwrap();
2269                let _ = name_server
2270                    .send(
2271                        DnsRequest::from_query(
2272                            Query::query(name.clone(), RecordType::A),
2273                            DnsRequestOptions::default(),
2274                        ),
2275                        ConnectionPolicy::default(),
2276                        &cx,
2277                    )
2278                    .await;
2279            });
2280        });
2281
2282        #[allow(clippy::mutable_key_type)]
2283        let map = snapshotter.snapshot().into_hashmap();
2284
2285        use crate::metrics::NAME_SERVER_QUERY_RESPONSES;
2286        let labels = vec![
2287            Label::new("addr", "8.8.8.8"),
2288            Label::new("protocol", "udp"),
2289            Label::new("status", "io_connection_refused"),
2290        ];
2291        assert_counter_eq(&map, NAME_SERVER_QUERY_RESPONSES, labels, 1);
2292    }
2293
2294    #[test]
2295    fn test_name_server_query_response_code_failure_metrics() {
2296        subscribe();
2297        let recorder = DebuggingRecorder::new();
2298        let snapshotter = recorder.snapshotter();
2299
2300        with_local_recorder(&recorder, || {
2301            let runtime = tokio::runtime::Builder::new_current_thread()
2302                .enable_all()
2303                .build()
2304                .unwrap();
2305
2306            runtime.block_on(async {
2307                let options = ResolverOpts {
2308                    enable_per_name_server_metrics: true,
2309                    ..ResolverOpts::default()
2310                };
2311                let config = NameServerConfig::udp(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)));
2312                let mock_provider = MockProvider::default();
2313                mock_provider
2314                    .send_outcomes
2315                    .lock()
2316                    .push_back(Some(NetError::Dns(DnsError::ResponseCode(
2317                        ResponseCode::ServFail,
2318                    ))));
2319                let name_server = Arc::new(NameServer::new([], config, &options, mock_provider));
2320
2321                let cx = Arc::new(PoolContext::new(options, TlsConfig::new().unwrap()));
2322                let name = Name::parse("www.example.com.", None).unwrap();
2323                let _ = name_server
2324                    .send(
2325                        DnsRequest::from_query(
2326                            Query::query(name.clone(), RecordType::A),
2327                            DnsRequestOptions::default(),
2328                        ),
2329                        ConnectionPolicy::default(),
2330                        &cx,
2331                    )
2332                    .await;
2333            });
2334        });
2335
2336        #[allow(clippy::mutable_key_type)]
2337        let map = snapshotter.snapshot().into_hashmap();
2338
2339        use crate::metrics::NAME_SERVER_QUERY_RESPONSES;
2340        let labels = vec![
2341            Label::new("addr", "8.8.8.8"),
2342            Label::new("protocol", "udp"),
2343            Label::new("status", "dns_response_code_servfail"),
2344        ];
2345        assert_counter_eq(&map, NAME_SERVER_QUERY_RESPONSES, labels, 1);
2346    }
2347
2348    #[test]
2349    fn test_name_server_query_no_records_success_metrics() {
2350        subscribe();
2351        let recorder = DebuggingRecorder::new();
2352        let snapshotter = recorder.snapshotter();
2353
2354        with_local_recorder(&recorder, || {
2355            let runtime = tokio::runtime::Builder::new_current_thread()
2356                .enable_all()
2357                .build()
2358                .unwrap();
2359
2360            runtime.block_on(async {
2361                let options = ResolverOpts {
2362                    enable_per_name_server_metrics: true,
2363                    ..ResolverOpts::default()
2364                };
2365                let config = NameServerConfig::udp(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)));
2366                let name = Name::parse("www.example.com.", None).unwrap();
2367                let query = Query::query(name.clone(), RecordType::A);
2368                let mock_provider = MockProvider::default();
2369                mock_provider
2370                    .send_outcomes
2371                    .lock()
2372                    .push_back(Some(NetError::Dns(DnsError::NoRecordsFound(
2373                        NoRecords::new(query, ResponseCode::NXDomain),
2374                    ))));
2375                let name_server = Arc::new(NameServer::new([], config, &options, mock_provider));
2376
2377                let cx = Arc::new(PoolContext::new(options, TlsConfig::new().unwrap()));
2378                let _ = name_server
2379                    .send(
2380                        DnsRequest::from_query(
2381                            Query::query(name.clone(), RecordType::A),
2382                            DnsRequestOptions::default(),
2383                        ),
2384                        ConnectionPolicy::default(),
2385                        &cx,
2386                    )
2387                    .await;
2388            });
2389        });
2390
2391        #[allow(clippy::mutable_key_type)]
2392        let map = snapshotter.snapshot().into_hashmap();
2393
2394        use crate::metrics::NAME_SERVER_QUERY_RESPONSES;
2395        let labels = vec![
2396            Label::new("addr", "8.8.8.8"),
2397            Label::new("protocol", "udp"),
2398            Label::new("status", "dns_no_records"),
2399        ];
2400        assert_counter_eq(&map, NAME_SERVER_QUERY_RESPONSES, labels, 1);
2401    }
2402
2403    #[test]
2404    fn test_name_server_query_metrics_aggregation() {
2405        subscribe();
2406        let recorder = DebuggingRecorder::new();
2407        let snapshotter = recorder.snapshotter();
2408
2409        with_local_recorder(&recorder, || {
2410            let runtime = tokio::runtime::Builder::new_current_thread()
2411                .enable_all()
2412                .build()
2413                .unwrap();
2414
2415            runtime.block_on(async {
2416                let name = Name::parse("www.example.com.", None).unwrap();
2417                let options = ResolverOpts {
2418                    enable_per_name_server_metrics: false,
2419                    ..Default::default()
2420                };
2421                let config = NameServerConfig::udp(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)));
2422                let mock_provider = MockProvider::default();
2423                let name_server = Arc::new(NameServer::new([], config, &options, mock_provider));
2424
2425                let cx = Arc::new(PoolContext::new(options, TlsConfig::new().unwrap()));
2426                let _ = name_server
2427                    .send(
2428                        DnsRequest::from_query(
2429                            Query::query(name.clone(), RecordType::A),
2430                            DnsRequestOptions::default(),
2431                        ),
2432                        ConnectionPolicy::default(),
2433                        &cx,
2434                    )
2435                    .await;
2436            });
2437        });
2438
2439        #[allow(clippy::mutable_key_type)]
2440        let map = snapshotter.snapshot().into_hashmap();
2441
2442        use crate::metrics::NAME_SERVER_QUERY_RESPONSES;
2443        let labels = vec![
2444            // No address label.
2445            Label::new("protocol", "udp"),
2446            Label::new("status", "success"),
2447        ];
2448        assert_counter_eq(&map, NAME_SERVER_QUERY_RESPONSES, labels, 1);
2449    }
2450}
2451
2452#[cfg(all(test, any(feature = "metrics", feature = "__tls")))]
2453mod reconnect_tests {
2454    use std::collections::VecDeque;
2455    use std::io;
2456    use std::net::{IpAddr, Ipv4Addr};
2457    use std::sync::Arc;
2458
2459    use parking_lot::Mutex as SyncMutex;
2460    use test_support::subscribe;
2461
2462    use super::mock_provider::MockProvider;
2463    use super::{ConnectionPolicy, NameServer};
2464    use crate::config::{NameServerConfig, ResolverOpts};
2465    use crate::connection_provider::TlsConfig;
2466    use crate::name_server_pool::PoolContext;
2467    use crate::net::NetError;
2468    use crate::proto::op::{DnsRequest, DnsRequestOptions, Query};
2469    use crate::proto::rr::{Name, RecordType};
2470
2471    fn connection_closed() -> NetError {
2472        NetError::from(io::Error::from(io::ErrorKind::ConnectionReset))
2473    }
2474
2475    fn query() -> DnsRequest {
2476        DnsRequest::from_query(
2477            Query::query(
2478                Name::parse("www.example.com.", None).unwrap(),
2479                RecordType::A,
2480            ),
2481            DnsRequestOptions::default(),
2482        )
2483    }
2484
2485    /// Build a name server whose connections replay `outcomes` across the sends they receive.
2486    fn name_server_with(
2487        outcomes: VecDeque<Option<NetError>>,
2488    ) -> (
2489        Arc<NameServer<MockProvider>>,
2490        MockProvider,
2491        Arc<PoolContext>,
2492    ) {
2493        let provider = MockProvider {
2494            send_outcomes: Arc::new(SyncMutex::new(outcomes)),
2495            ..MockProvider::default()
2496        };
2497        let options = ResolverOpts::default();
2498        let config = NameServerConfig::udp(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)));
2499        let ns = Arc::new(NameServer::new([], config, &options, provider.clone()));
2500        let cx = Arc::new(PoolContext::new(options, TlsConfig::new().unwrap()));
2501        (ns, provider, cx)
2502    }
2503
2504    /// A reused pooled connection that the peer closed while idle should be
2505    /// transparently reconnected rather than surfacing the error to the caller.
2506    #[tokio::test]
2507    async fn reconnects_once_when_reused_connection_was_closed() {
2508        subscribe();
2509
2510        // Establish (ok), the reused send fails connection-closed, and the
2511        // post-reconnect send succeeds (empty queue falls through to success).
2512        let (ns, provider, cx) =
2513            name_server_with(VecDeque::from([None, Some(connection_closed())]));
2514
2515        ns.clone()
2516            .send(query(), ConnectionPolicy::default(), &cx)
2517            .await
2518            .expect("initial query establishes the connection");
2519        ns.clone()
2520            .send(query(), ConnectionPolicy::default(), &cx)
2521            .await
2522            .expect("stale reused connection is transparently reconnected");
2523
2524        // The initial connection plus the post-failure reconnect.
2525        assert_eq!(provider.new_connection_calls().len(), 2);
2526    }
2527
2528    /// A connection-closed error on a brand-new (non-reused) connection is a real
2529    /// failure, not a stale-pool artifact: it must propagate without a retry.
2530    #[tokio::test]
2531    async fn does_not_retry_when_a_fresh_connection_fails_closed() {
2532        subscribe();
2533
2534        let (ns, provider, cx) = name_server_with(VecDeque::from([Some(connection_closed())]));
2535
2536        ns.send(query(), ConnectionPolicy::default(), &cx)
2537            .await
2538            .expect_err("a fresh connection failure must not be retried");
2539
2540        assert_eq!(provider.new_connection_calls().len(), 1);
2541    }
2542}
2543
2544#[cfg(all(test, any(feature = "metrics", feature = "__tls")))]
2545mod mock_provider {
2546    use std::collections::VecDeque;
2547    use std::future::Future;
2548    use std::io;
2549    use std::pin::Pin;
2550    use std::task::{Context, Poll};
2551
2552    use futures_util::stream::once;
2553    use futures_util::{Stream, future};
2554    use tokio::net::UdpSocket;
2555
2556    use super::*;
2557    use crate::config::ProtocolConfig;
2558    use crate::net::runtime::TokioTime;
2559    use crate::net::runtime::iocompat::AsyncIoTokioAsStd;
2560    use crate::proto::op::Message;
2561    use crate::proto::rr::rdata::NULL;
2562    use crate::proto::rr::{RData, Record};
2563
2564    /// `MockProvider` is a `ConnectionProvider` that uses a synchronous runtime provider.
2565    ///
2566    /// It also tracks calls to `new_connection`, exposing the arguments provided as
2567    /// `new_connection_calls` for test to interrogate. The optional `new_connection_error`
2568    /// `ProtoError` can be set to have `new_connection()` return a future that will error
2569    /// when polled, mocking a connection failure.
2570    #[derive(Clone)]
2571    pub(super) struct MockProvider {
2572        pub(super) runtime: MockSyncRuntimeProvider,
2573        pub(super) new_connection_calls: Arc<SyncMutex<Vec<(IpAddr, ProtocolConfig)>>>,
2574        pub(super) new_connection_error: Option<NetError>,
2575        /// Per-send outcomes, consumed front-to-back across all connections this
2576        /// provider hands out: `Some(err)` fails that send, `None` (or an empty
2577        /// queue) succeeds.
2578        pub(super) send_outcomes: Arc<SyncMutex<VecDeque<Option<NetError>>>>,
2579    }
2580
2581    impl MockProvider {
2582        pub(super) fn new_connection_calls(&self) -> Vec<(IpAddr, ProtocolConfig)> {
2583            self.new_connection_calls.lock().clone()
2584        }
2585    }
2586
2587    impl ConnectionProvider for MockProvider {
2588        type Conn = MockClientHandle;
2589        type FutureConn = Pin<Box<dyn Send + Future<Output = Result<Self::Conn, NetError>>>>;
2590        type RuntimeProvider = MockSyncRuntimeProvider;
2591
2592        fn new_connection(
2593            &self,
2594            ip: IpAddr,
2595            config: &ConnectionConfig,
2596            _cx: &PoolContext,
2597        ) -> Result<Self::FutureConn, NetError> {
2598            self.new_connection_calls
2599                .lock()
2600                .push((ip, config.protocol.clone()));
2601
2602            Ok(Box::pin(future::ready(match &self.new_connection_error {
2603                Some(err) => Err(err.clone()),
2604                None => Ok(MockClientHandle {
2605                    send_outcomes: self.send_outcomes.clone(),
2606                }),
2607            })))
2608        }
2609
2610        fn runtime_provider(&self) -> &Self::RuntimeProvider {
2611            &self.runtime
2612        }
2613    }
2614
2615    impl Default for MockProvider {
2616        fn default() -> Self {
2617            Self {
2618                runtime: MockSyncRuntimeProvider,
2619                new_connection_calls: Arc::new(SyncMutex::new(Vec::new())),
2620                new_connection_error: None,
2621                send_outcomes: Arc::new(SyncMutex::new(VecDeque::new())),
2622            }
2623        }
2624    }
2625
2626    /// `MockClientHandle` is a `DnsHandle` that uses a synchronous runtime provider.
2627    ///
2628    /// Its `send` method replays the provider's scripted `send_outcomes`: a `Some(err)`
2629    /// entry fails that send, while `None` or an exhausted queue yields a `NoError` response.
2630    #[derive(Clone, Default)]
2631    pub(super) struct MockClientHandle {
2632        send_outcomes: Arc<SyncMutex<VecDeque<Option<NetError>>>>,
2633    }
2634
2635    impl DnsHandle for MockClientHandle {
2636        type Response = Pin<Box<dyn Stream<Item = Result<DnsResponse, NetError>> + Send>>;
2637        type Runtime = MockSyncRuntimeProvider;
2638
2639        fn send(&self, request: DnsRequest) -> Self::Response {
2640            if let Some(Some(err)) = self.send_outcomes.lock().pop_front() {
2641                return Box::pin(once(future::ready(Err(err))));
2642            }
2643            let mut response = Message::response(request.id, request.op_code);
2644            response.metadata.response_code = ResponseCode::NoError;
2645            response.add_queries(request.queries.clone());
2646            if let Some(query) = request.queries.first() {
2647                response.add_answer(Record::from_rdata(
2648                    query.name.clone(),
2649                    0,
2650                    RData::NULL(NULL::with(vec![0])),
2651                ));
2652            }
2653            Box::pin(once(future::ready(Ok(
2654                DnsResponse::from_message(response).unwrap()
2655            ))))
2656        }
2657    }
2658
2659    /// `MockSyncRuntimeProvider` is a `RuntimeProvider` that creates `MockSyncHandle` instances.
2660    ///
2661    /// Trait methods other than `create_handle` are not implemented.
2662    #[derive(Clone)]
2663    pub(super) struct MockSyncRuntimeProvider;
2664
2665    impl RuntimeProvider for MockSyncRuntimeProvider {
2666        type Handle = MockSyncHandle;
2667        type Timer = TokioTime;
2668        type Udp = UdpSocket;
2669        type Tcp = AsyncIoTokioAsStd<tokio::net::TcpStream>;
2670
2671        fn create_handle(&self) -> Self::Handle {
2672            MockSyncHandle
2673        }
2674
2675        #[allow(clippy::unimplemented)]
2676        fn connect_tcp(
2677            &self,
2678            _server_addr: std::net::SocketAddr,
2679            _bind_addr: Option<std::net::SocketAddr>,
2680            _timeout: Option<Duration>,
2681        ) -> Pin<Box<dyn Future<Output = Result<Self::Tcp, io::Error>> + Send>> {
2682            unimplemented!();
2683        }
2684
2685        #[allow(clippy::unimplemented)]
2686        fn bind_udp(
2687            &self,
2688            _local_addr: std::net::SocketAddr,
2689            _server_addr: std::net::SocketAddr,
2690        ) -> Pin<Box<dyn Future<Output = Result<Self::Udp, io::Error>> + Send>> {
2691            unimplemented!();
2692        }
2693    }
2694
2695    /// `MockSyncHandle` is a `Spawn` implementation that polls task futures synchronously.
2696    ///
2697    /// Provided futures will be polled until completion, allowing tests to avoid needing to
2698    /// coordinate with background tasks to determine their completion state.
2699    #[derive(Clone)]
2700    pub(super) struct MockSyncHandle;
2701
2702    impl Spawn for MockSyncHandle {
2703        fn spawn_bg(&mut self, future: impl Future<Output = ()> + Send + 'static) {
2704            // Instead of spawning the future as a background task, poll it synchronously
2705            // until completion.
2706            let waker = futures_util::task::noop_waker();
2707            let mut context = Context::from_waker(&waker);
2708            let mut future = Box::pin(future);
2709
2710            loop {
2711                match future.as_mut().poll(&mut context) {
2712                    Poll::Ready(_) => break,
2713                    Poll::Pending => continue,
2714                }
2715            }
2716        }
2717    }
2718}