spftrace 0.3.0

Utility for tracing SPF queries
// spftrace – utility for tracing SPF queries
// Copyright © 2022–2023 David Bürgin <dbuergin@gluet.ch>
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation, either version 3 of the License, or (at your option) any later
// version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <https://www.gnu.org/licenses/>.

use async_trait::async_trait;
use std::{
    error::Error,
    net::{IpAddr, Ipv4Addr, Ipv6Addr},
    time::Duration,
};
use tokio::time::{self, Instant};
use trust_dns_resolver::{
    config::{ResolverConfig, ResolverOpts},
    system_conf, TokioAsyncResolver,
};
use viaspf::lookup::{Lookup, LookupError, LookupResult, Name};

/// Single-use, timeout Trust-DNS resolver.
///
/// We have disabled viaspf’s `tokio-timeout` feature and implemented timeout
/// per DNS query here, in order to only generate complete, not truncated
/// traces.
pub struct Resolver {
    pub inner: TokioAsyncResolver,
    pub deadline: Instant,
}

impl Resolver {
    pub fn new(timeout: Duration) -> Self {
        let (config, opts) = Default::default();

        Self::new_internal(timeout, config, opts)
    }

    pub fn new_with_system_config(timeout: Duration) -> Result<Self, Box<dyn Error>> {
        let (config, opts) = system_conf::read_system_conf()?;

        Ok(Self::new_internal(timeout, config, opts))
    }

    fn new_internal(timeout: Duration, config: ResolverConfig, mut opts: ResolverOpts) -> Self {
        opts.timeout = timeout;

        let inner = TokioAsyncResolver::tokio(config, opts);
        let deadline = Instant::now() + timeout;

        Self { inner, deadline }
    }
}

#[async_trait]
impl Lookup for Resolver {
    async fn lookup_a(&self, name: &Name) -> LookupResult<Vec<Ipv4Addr>> {
        time::timeout_at(self.deadline, self.inner.lookup_a(name))
            .await
            .map_err(|_| LookupError::Timeout)?
    }

    async fn lookup_aaaa(&self, name: &Name) -> LookupResult<Vec<Ipv6Addr>> {
        time::timeout_at(self.deadline, self.inner.lookup_aaaa(name))
            .await
            .map_err(|_| LookupError::Timeout)?
    }

    async fn lookup_mx(&self, name: &Name) -> LookupResult<Vec<Name>> {
        time::timeout_at(self.deadline, self.inner.lookup_mx(name))
            .await
            .map_err(|_| LookupError::Timeout)?
    }

    async fn lookup_txt(&self, name: &Name) -> LookupResult<Vec<String>> {
        time::timeout_at(self.deadline, self.inner.lookup_txt(name))
            .await
            .map_err(|_| LookupError::Timeout)?
    }

    async fn lookup_ptr(&self, ip: IpAddr) -> LookupResult<Vec<Name>> {
        time::timeout_at(self.deadline, self.inner.lookup_ptr(ip))
            .await
            .map_err(|_| LookupError::Timeout)?
    }
}