libdnscheck/
lib.rs

1use std::fmt::{Display, Formatter};
2use std::net::{IpAddr, Ipv6Addr};
3use std::{fmt, io};
4
5use dns_lookup::{getaddrinfo, LookupErrorKind};
6use log::*;
7use thiserror::Error;
8
9#[cfg(dbus)]
10use crate::dbus::lookup_dbus;
11#[cfg(dbus)]
12use crate::DnsCheckError::{NoDBus, NoResolved};
13
14#[cfg(all(feature = "dbus", target_os = "linux"))]
15mod dbus;
16
17#[derive(Debug, Copy, Clone)]
18pub enum Query<'a> {
19    Address(IpAddr),
20    Domain(&'a str),
21}
22
23impl Display for Query<'_> {
24    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
25        match self {
26            Query::Address(addr) => {
27                write!(f, "{}", addr)
28            }
29            Query::Domain(domain) => {
30                write!(f, "{}", domain)
31            }
32        }
33    }
34}
35
36#[derive(Error, Debug)]
37pub enum DnsCheckError {
38    #[error("DBus reported {0}: {1}")]
39    DBus(String, String),
40    #[error("DBus support is missing")]
41    NoDBus,
42    #[error("systemd-resolved not found: {0}")]
43    NoResolved(#[source] anyhow::Error),
44    #[error("NXDOMAIN {0}")]
45    NxDomain(String),
46    #[error("Something went wrong: {0}")]
47    Unknown(#[from] anyhow::Error),
48}
49
50impl From<io::Error> for DnsCheckError {
51    fn from(e: io::Error) -> Self {
52        DnsCheckError::Unknown(e.into())
53    }
54}
55
56pub struct DnsListMembership {
57    pub name: String,
58    pub list: String,
59    pub found: bool,
60}
61
62pub fn lookup(source: &str, query: &Query) -> Result<DnsListMembership, DnsCheckError> {
63    #[cfg(dbus)]
64    match lookup_dbus(source, query) {
65        Ok(r) => return Ok(r),
66        Err(NoDBus) => {
67            warn!("DBus not compiled in, falling back to internal resolution")
68        }
69        Err(NoResolved(e)) => {
70            warn!("DBus resolution failed: {:?}", e)
71        }
72        Err(e) => return Err(e),
73    };
74
75    lookup_dns(source, query)
76}
77
78fn lookup_dns(source: &str, query: &Query) -> Result<DnsListMembership, DnsCheckError> {
79    let queryhost = match query {
80        Query::Domain(d) => format!("{}.", d),
81        Query::Address(ip) => format_ip(ip),
82    };
83
84    let hostname = format!("{}{}.", queryhost, source);
85
86    let mut addrinfo = match getaddrinfo(Some(&hostname), None, None) {
87        Ok(a) => Ok(a),
88        Err(e) => match e.kind() {
89            LookupErrorKind::NoName => {
90                return Ok(DnsListMembership {
91                    name: query.to_string(),
92                    list: format!("{:?}", source),
93                    found: false,
94                })
95            }
96            _ => Err(DnsCheckError::Unknown(io::Error::from(e).into())),
97        },
98    }?;
99
100    Ok(DnsListMembership {
101        name: source.to_string(),
102        list: format!("{}", query),
103        found: addrinfo.next().is_some(),
104    })
105}
106
107fn format_ip(ip: &IpAddr) -> String {
108    match ip {
109        IpAddr::V4(v4) => format!(
110            "{}.{}.{}.{}.",
111            v4.octets()[3],
112            v4.octets()[2],
113            v4.octets()[1],
114            v4.octets()[0]
115        ),
116        IpAddr::V6(v6) => format_v6(v6),
117    }
118}
119
120fn format_v6(ip: &Ipv6Addr) -> String {
121    ip.octets()
122        .iter()
123        .flat_map(|o| vec![o >> 4, o & 0xF])
124        .map(|d| format!("{:x}", d))
125        .fold("".to_owned(), |a: String, d: String| format!("{}.{}", d, a))
126}
127
128pub fn count_lists(
129    queries: &[Query],
130    sources: &[&str],
131) -> Result<Vec<DnsListMembership>, DnsCheckError> {
132    queries
133        .iter()
134        .flat_map(|query| sources.iter().map(move |&source| lookup(source, query)))
135        .collect()
136}