1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use dbus::blocking::Connection;
use dbus::MethodErr;
use std::net::{IpAddr, Ipv6Addr};
use std::time::Duration;
use thiserror::Error;

use generate_dbus_resolve1::OrgFreedesktopResolve1Manager;

#[derive(Debug)]
pub enum Query {
    Address(IpAddr),
    Domain(String),
}

#[derive(Error, Debug)]
pub enum DnsCheckError {
    #[error("DBus reported {0}: {1}")]
    DBus(String, String),
    #[error("NXDOMAIN {0}")]
    NxDomain(String),
    #[error("Something went wrong")]
    Unknown,
}

impl From<MethodErr> for DnsCheckError {
    fn from(e: MethodErr) -> Self {
        if e.errorname()
            .starts_with("org.freedesktop.resolve1.DnsError.NXDOMAIN")
        {
            DnsCheckError::NxDomain(e.description().to_string())
        } else {
            DnsCheckError::DBus(e.errorname().to_string(), e.description().to_string())
        }
    }
}

impl From<dbus::Error> for DnsCheckError {
    fn from(error: dbus::Error) -> Self {
        DnsCheckError::from(MethodErr::from(error))
    }
}

pub fn lookup(source: &str, query: &Query) -> Result<bool, DnsCheckError> {
    println!("Source: {:?}, Query: {:?}", source, query);

    let conn = Connection::new_system()?;
    let proxy = conn.with_proxy(
        "org.freedesktop.resolve1",
        "/org/freedesktop/resolve1",
        Duration::from_secs(30),
    );

    let queryhost = match query {
        Query::Domain(d) => format!("{}.", d),
        Query::Address(ip) => format_ip(&ip),
    };

    let hostname = format!("{}{}.", queryhost, source);

    println!("Querying: {}", hostname);

    type DBusDnsResponse = (Vec<(i32, i32, Vec<u8>)>, String, u64);
    let result: Result<DBusDnsResponse, DnsCheckError> = proxy
        .resolve_hostname(0, &hostname, libc::AF_INET, 0)
        .map_err(From::from);

    println!("Result: {:?}", result);

    result.map_or_else(
        |error| match error {
            DnsCheckError::NxDomain(_) => Ok(false),
            e => Err(e),
        },
        |r| Ok(!r.0.is_empty()),
    )
}

fn format_ip(ip: &IpAddr) -> String {
    match ip {
        IpAddr::V4(v4) => format!(
            "{}.{}.{}.{}.",
            v4.octets()[3],
            v4.octets()[2],
            v4.octets()[1],
            v4.octets()[0]
        ),
        IpAddr::V6(v6) => format_v6(v6),
    }
}

fn format_v6(ip: &Ipv6Addr) -> String {
    ip.octets()
        .iter()
        .flat_map(|o| vec![o >> 4, o & 0xF])
        .map(|d| format!("{:x}", d))
        .fold("".to_owned(), |a: String, d: String| format!("{}.{}", d, a))
}