1
//! DNS streams
2

            
3
pub(crate) mod resolver;
4

            
5
use resolver::{DnsResolver, LookupAnswers, LookupError, RecordData};
6

            
7
use tor_cell::relaycell::msg::Resolved;
8
use tor_proto::stream::{IncomingStream, IncomingStreamRequest};
9

            
10
use tracing::trace;
11

            
12
/// The fake TTL to send back with every RESOLVED response.
13
///
14
/// This is a mitigation against the DNS cache oracle problem
15
/// that the original C Tor implementation suffered from.
16
///
17
/// See <https://gitlab.torproject.org/tpo/core/tor/-/issues/40979>
18
const FAKE_TTL_SECONDS: u32 = 60;
19

            
20
/// Handle an incoming DNS stream
21
///
22
/// Performs the DNS lookup, and closes `incoming` by sending back a RESOLVED message.
23
///
24
/// Returns an error if `incoming` is not a RESOLVE stream.
25
pub(crate) async fn handle_resolve(
26
    incoming: IncomingStream,
27
    mut resolver: DnsResolver,
28
) -> anyhow::Result<()> {
29
    let resolve = match incoming.request() {
30
        IncomingStreamRequest::Resolve(r) => r,
31
        s => return Err(anyhow::anyhow!("expected RESOLVE but got {s:?}")),
32
    };
33
    let res = resolver.resolve(resolve.query()).await;
34

            
35
    let resolved = lookup_to_resolved(res);
36
    trace!(cell=?resolved, "sending RESOLVED");
37

            
38
    incoming.resolve(resolved).await?;
39

            
40
    Ok(())
41
}
42

            
43
/// Build a [`Resolved`] message out of a lookup response.
44
///
45
/// Each of the returned answers will have the TTL set to a fake hard-coded value,
46
/// to avoid leaking the DNS cache insertion timestamps to the client.
47
//
48
// TODO(relay): we might want to return random fake TTLs instead
49
fn lookup_to_resolved(lookup_res: Result<LookupAnswers<RecordData>, LookupError>) -> Resolved {
50
    match lookup_res {
51
        Ok(answers) => {
52
            let mut resolved = Resolved::new_empty();
53
            if let Some(ans) = answers.first() {
54
                resolved.add_answer(ans.clone().into(), FAKE_TTL_SECONDS);
55
            }
56

            
57
            // TODO(relay): try to fit more answers in the RESOLVED
58

            
59
            resolved
60
        }
61
        Err(e) => Resolved::new_err(e.is_transient(), FAKE_TTL_SECONDS),
62
    }
63
}