mtop/
ping.rs

1use crate::check::{Timing, TimingBuilder};
2use mtop_client::dns::{DefaultDnsClient, DnsClient, Name, RecordClass, RecordType, ResponseCode};
3use std::sync::atomic::{AtomicBool, Ordering};
4use std::sync::Arc;
5use std::time::{Duration, Instant};
6use tokio::time;
7use tracing::{Instrument, Level};
8
9/// Repeatedly make DNS requests to resolve a domain name.
10#[derive(Debug)]
11pub struct DnsPinger {
12    client: DefaultDnsClient,
13    interval: Duration,
14    stop: Arc<AtomicBool>,
15}
16
17impl DnsPinger {
18    /// Create a new `DnsPinger` that uses the provided client to repeatedly resolve a domain
19    /// name. `interval` is the amount of time to wait between each DNS query.
20    pub fn new(client: DefaultDnsClient, interval: Duration, stop: Arc<AtomicBool>) -> Self {
21        Self { client, interval, stop }
22    }
23
24    /// Perform DNS queries for a particular domain name up to `count` times or until stopped
25    /// via the `stop` flag if `count` is `0` (indicating "infinite" queries).
26    pub async fn run(&self, name: Name, rtype: RecordType, rclass: RecordClass, count: u64) -> Bundle {
27        let mut timing_builder = TimingBuilder::default();
28        let mut total = 0;
29        let mut protocol_errors = 0;
30        let mut fatal_errors = 0;
31
32        let mut interval = time::interval(self.interval);
33
34        while !self.stop.load(Ordering::Acquire) && (count == 0 || total < count) {
35            let _ = interval.tick().await;
36            // Create our own Instant to measure the time taken to perform the query since
37            // the one emitted by the interval isn't _immediately_ when the future resolves
38            // and so skews the measurement of queries.
39            let start = Instant::now();
40
41            match self
42                .client
43                .resolve(name.clone(), rtype, rclass)
44                .instrument(tracing::span!(Level::INFO, "client.resolve"))
45                .await
46            {
47                Ok(r) => {
48                    let min_ttl = r.answers().iter().map(|a| a.ttl()).min().unwrap_or(0);
49                    let elapsed = start.elapsed();
50                    timing_builder.add(elapsed);
51
52                    // Warn if the name couldn't be resolved correctly. This is different from
53                    // an error like a timeout since we did actually get a response for the query
54                    if r.flags().get_response_code() != ResponseCode::NoError {
55                        tracing::warn!(
56                            id = %r.id(),
57                            name = %name,
58                            response_code = ?r.flags().get_response_code(),
59                            num_questions = r.questions().len(),
60                            num_answers = r.answers().len(),
61                            num_authority = r.authority().len(),
62                            num_extra = r.extra().len(),
63                            min_ttl = min_ttl,
64                            elapsed = ?elapsed,
65                        );
66                        protocol_errors += 1;
67                    } else {
68                        tracing::info!(
69                            id = %r.id(),
70                            name = %name,
71                            response_code = ?r.flags().get_response_code(),
72                            num_questions = r.questions().len(),
73                            num_answers = r.answers().len(),
74                            num_authority = r.authority().len(),
75                            num_extra = r.extra().len(),
76                            min_ttl = min_ttl,
77                            elapsed = ?elapsed,
78                        );
79                    }
80                }
81                Err(e) => {
82                    tracing::error!(message = "failed to resolve", name = %name, err = %e);
83                    fatal_errors += 1;
84                }
85            }
86
87            total += 1;
88        }
89
90        let timing = timing_builder.build();
91        Bundle {
92            timing,
93            total,
94            protocol_errors,
95            fatal_errors,
96        }
97    }
98}
99
100#[derive(Debug, Default, Clone, Eq, PartialEq)]
101pub struct Bundle {
102    pub timing: Timing,
103    pub total: u64,
104    pub protocol_errors: u64,
105    pub fatal_errors: u64,
106}