1
//! Implement a simple DNS resolver that relay request over Tor.
2
//!
3
//! A resolver is created with [`bind_dns_resolver()`], which opens a set of listener ports.
4
//! `DnsProxy::run_dns_proxy` then listens for
5
//! DNS requests, and sends back replies in response.
6

            
7
use futures::lock::Mutex;
8
use futures::stream::StreamExt;
9
use hickory_proto::op::{Message, OpCode, Query, ResponseCode};
10
use hickory_proto::rr::{DNSClass, Name, RData, Record, RecordType, rdata};
11
use hickory_proto::serialize::binary::{BinDecodable, BinEncodable};
12
use std::collections::HashMap;
13
use std::net::{IpAddr, SocketAddr};
14
use std::sync::Arc;
15
use tor_rtcompat::{SpawnExt, UdpProvider};
16
use tracing::{debug, error, info, warn};
17

            
18
use arti_client::{Error, HasKind, StreamPrefs, TorClient};
19
use safelog::sensitive as sv;
20
use tor_config::Listen;
21
use tor_error::{error_report, warn_report};
22
use tor_rtcompat::{Runtime, UdpSocket};
23

            
24
use anyhow::{Result, anyhow};
25

            
26
use crate::proxy::port_info;
27

            
28
/// Maximum length for receiving a single datagram
29
const MAX_DATAGRAM_SIZE: usize = 1536;
30

            
31
/// A Key used to isolate dns requests.
32
///
33
/// Composed of an usize (representing which listener socket accepted
34
/// the connection and the source IpAddr of the client)
35
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36
struct DnsIsolationKey(usize, IpAddr);
37

            
38
impl arti_client::isolation::IsolationHelper for DnsIsolationKey {
39
    fn compatible_same_type(&self, other: &Self) -> bool {
40
        self == other
41
    }
42

            
43
    fn join_same_type(&self, other: &Self) -> Option<Self> {
44
        if self == other {
45
            Some(self.clone())
46
        } else {
47
            None
48
        }
49
    }
50

            
51
    fn enables_long_lived_circuits(&self) -> bool {
52
        false
53
    }
54
}
55

            
56
/// Identifier for a DNS request, composed of its source IP and transaction ID
57
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
58
struct DnsCacheKey(DnsIsolationKey, Vec<Query>);
59

            
60
/// Target for a DNS response
61
#[derive(Debug, Clone)]
62
struct DnsResponseTarget<U> {
63
    /// Transaction ID
64
    id: u16,
65
    /// Address of the client
66
    addr: SocketAddr,
67
    /// Socket to send the response through
68
    socket: Arc<U>,
69
}
70

            
71
/// Run a DNS query over tor, returning either a list of answers, or a DNS error code.
72
async fn do_query<R>(
73
    tor_client: &TorClient<R>,
74
    queries: &[Query],
75
    prefs: &StreamPrefs,
76
) -> Result<Vec<Record>, ResponseCode>
77
where
78
    R: Runtime,
79
{
80
    let mut answers = Vec::new();
81

            
82
    let err_conv = |error: Error| {
83
        if tor_error::ErrorKind::RemoteHostNotFound == error.kind() {
84
            // NoError without any body is considered to be NODATA as per rfc2308 section-2.2
85
            ResponseCode::NoError
86
        } else {
87
            ResponseCode::ServFail
88
        }
89
    };
90
    for query in queries {
91
        let mut a = Vec::new();
92
        let mut ptr = Vec::new();
93

            
94
        // TODO if there are N questions, this would take N rtt to answer. By joining all futures it
95
        // could take only 1 rtt, but having more than 1 question is actually very rare.
96
        match query.query_class() {
97
            DNSClass::IN => {
98
                match query.query_type() {
99
                    typ @ RecordType::A | typ @ RecordType::AAAA => {
100
                        let mut name = query.name().clone();
101
                        // name would be "torproject.org." without this
102
                        name.set_fqdn(false);
103
                        let res = tor_client
104
                            .resolve_with_prefs(&name.to_utf8(), prefs)
105
                            .await
106
                            .map_err(err_conv)?;
107
                        for ip in res {
108
                            a.push((query.name().clone(), ip, typ));
109
                        }
110
                    }
111
                    RecordType::PTR => {
112
                        let addr = query
113
                            .name()
114
                            .parse_arpa_name()
115
                            .map_err(|_| ResponseCode::FormErr)?
116
                            .addr();
117
                        let res = tor_client
118
                            .resolve_ptr_with_prefs(addr, prefs)
119
                            .await
120
                            .map_err(err_conv)?;
121
                        for domain in res {
122
                            let domain =
123
                                Name::from_utf8(domain).map_err(|_| ResponseCode::ServFail)?;
124
                            ptr.push((query.name().clone(), domain));
125
                        }
126
                    }
127
                    _ => {
128
                        return Err(ResponseCode::NotImp);
129
                    }
130
                }
131
            }
132
            _ => {
133
                return Err(ResponseCode::NotImp);
134
            }
135
        }
136
        for (name, ip, typ) in a {
137
            match (ip, typ) {
138
                (IpAddr::V4(v4), RecordType::A) => {
139
                    answers.push(Record::from_rdata(name, 3600, RData::A(rdata::A(v4))));
140
                }
141
                (IpAddr::V6(v6), RecordType::AAAA) => {
142
                    answers.push(Record::from_rdata(name, 3600, RData::AAAA(rdata::AAAA(v6))));
143
                }
144
                _ => (),
145
            }
146
        }
147
        for (ptr, name) in ptr {
148
            answers.push(Record::from_rdata(ptr, 3600, RData::PTR(rdata::PTR(name))));
149
        }
150
    }
151

            
152
    Ok(answers)
153
}
154

            
155
/// Given a datagram containing a DNS query, resolve the query over
156
/// the Tor network and send the response back.
157
#[allow(clippy::cognitive_complexity)] // TODO: Refactor
158
async fn handle_dns_req<R, U>(
159
    tor_client: &TorClient<R>,
160
    socket_id: usize,
161
    packet: &[u8],
162
    addr: SocketAddr,
163
    socket: Arc<U>,
164
    current_requests: &Mutex<HashMap<DnsCacheKey, Vec<DnsResponseTarget<U>>>>,
165
) -> Result<()>
166
where
167
    R: Runtime,
168
    U: UdpSocket,
169
{
170
    // if we can't parse the request, don't try to answer it.
171
    let query = Message::from_bytes(packet)?;
172
    let id = query.metadata.id;
173
    let queries = query.queries;
174
    let isolation = DnsIsolationKey(socket_id, addr.ip());
175

            
176
    let request_id = {
177
        let request_id = DnsCacheKey(isolation.clone(), queries.clone());
178

            
179
        let response_target = DnsResponseTarget { id, addr, socket };
180

            
181
        let mut current_requests = current_requests.lock().await;
182

            
183
        let req = current_requests.entry(request_id.clone()).or_default();
184
        req.push(response_target);
185

            
186
        if req.len() > 1 {
187
            debug!("Received a query already being served");
188
            return Ok(());
189
        }
190
        debug!("Received a new query");
191

            
192
        request_id
193
    };
194

            
195
    let mut prefs = StreamPrefs::new();
196
    prefs.set_isolation(isolation);
197

            
198
    let mut response = match do_query(tor_client, &queries, &prefs).await {
199
        Ok(answers) => {
200
            let mut response = Message::response(id, OpCode::Query);
201
            response.metadata.recursion_desired = query.metadata.recursion_desired;
202
            response.metadata.recursion_available = true;
203
            response.add_queries(queries).add_answers(answers);
204
            // TODO maybe add some edns?
205
            response
206
        }
207
        Err(error_type) => Message::error_msg(id, OpCode::Query, error_type),
208
    };
209

            
210
    // remove() should never return None, but just in case
211
    let targets = current_requests
212
        .lock()
213
        .await
214
        .remove(&request_id)
215
        .unwrap_or_default();
216

            
217
    for target in targets {
218
        response.metadata.id = target.id;
219
        // ignore errors, we want to reply to everybody
220
        let response = match response.to_bytes() {
221
            Ok(r) => r,
222
            Err(e) => {
223
                // The response message probably contains the query DNS name, and the error
224
                // might well do so too.  (Many variants of hickory_proto's ProtoErrorKind
225
                // contain domain names.)  Digging into these to be more useful is tiresome,
226
                // so just mark the whole response message, and error, as sensitive.
227
                error_report!(e, "Failed to serialize DNS packet: {:?}", sv(&response));
228
                continue;
229
            }
230
        };
231
        let _ = target.socket.send(&response, &target.addr).await;
232
    }
233
    Ok(())
234
}
235

            
236
/// A DNS proxy server that can run indefinitely.
237
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
238
#[must_use]
239
pub(crate) struct DnsProxy<R: Runtime> {
240
    /// A list of bound UDP sockets.
241
    udp_sockets: Vec<<R as UdpProvider>::UdpSocket>,
242
    /// A tor client to handle DNS requests.
243
    tor_client: Arc<TorClient<R>>,
244
}
245

            
246
/// Bind to a set of DNS ports, and return a new DnsProxy.
247
///
248
/// Takes no action until `run_dns_proxy` is called.
249
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
250
#[allow(clippy::cognitive_complexity)] // TODO: Refactor
251
pub(crate) async fn bind_dns_resolver<R: Runtime>(
252
    runtime: R,
253
    tor_client: Arc<TorClient<R>>,
254
    listen: Listen,
255
) -> Result<DnsProxy<R>> {
256
    if !listen.is_loopback_only() {
257
        warn!(
258
            "Configured to listen for DNS on non-local addresses. This is usually insecure! We recommend listening on localhost only."
259
        );
260
    }
261

            
262
    let mut listeners = Vec::new();
263

            
264
    // Try to bind to the DNS ports.
265
    match listen.ip_addrs() {
266
        Ok(addrgroups) => {
267
            for addrgroup in addrgroups {
268
                for addr in addrgroup {
269
                    // NOTE: Our logs here displays the local address. We allow this, since
270
                    // knowing the address is basically essential for diagnostics.
271
                    match runtime.bind(&addr).await {
272
                        Ok(listener) => {
273
                            let bound_addr = listener.local_addr()?;
274
                            info!("Listening on {:?}.", bound_addr);
275
                            listeners.push(listener);
276
                        }
277
                        #[cfg(unix)]
278
                        Err(ref e) if e.raw_os_error() == Some(libc::EAFNOSUPPORT) => {
279
                            warn_report!(e, "Address family not supported {}", addr);
280
                        }
281
                        Err(ref e) => {
282
                            return Err(anyhow!("Can't listen on {}: {e}", addr));
283
                        }
284
                    }
285
                }
286
                // TODO: We are supposed to fail if all addresses in a group fail.
287
            }
288
        }
289
        Err(e) => warn_report!(e, "Invalid listen spec"),
290
    }
291
    // We weren't able to bind any ports: There's nothing to do.
292
    if listeners.is_empty() {
293
        error!("Couldn't open any DNS listeners.");
294
        return Err(anyhow!("Couldn't open any DNS listeners"));
295
    }
296

            
297
    Ok(DnsProxy {
298
        tor_client,
299
        udp_sockets: listeners,
300
    })
301
}
302

            
303
impl<R: Runtime> DnsProxy<R> {
304
    /// Run indefinitely, receiving incoming DNS requests and processing them.
305
    pub(crate) async fn run_dns_proxy(self) -> Result<()> {
306
        let DnsProxy {
307
            tor_client,
308
            udp_sockets,
309
        } = self;
310
        run_dns_resolver_with_listeners(tor_client.runtime().clone(), tor_client, udp_sockets).await
311
    }
312

            
313
    /// Return a list of the port addresses that we have bound.
314
    pub(crate) fn port_info(&self) -> Result<Vec<port_info::Port>> {
315
        Ok(self
316
            .udp_sockets
317
            .iter()
318
            .map(|socket| {
319
                socket.local_addr().map(|address| port_info::Port {
320
                    protocol: port_info::SupportedProtocol::DnsUdp,
321
                    address: address.into(),
322
                })
323
            })
324
            .collect::<Result<Vec<_>, _>>()?)
325
    }
326
}
327

            
328
/// Inner task: Receive incoming DNS requests and process them.
329
async fn run_dns_resolver_with_listeners<R: Runtime>(
330
    runtime: R,
331
    tor_client: Arc<TorClient<R>>,
332
    listeners: Vec<<R as tor_rtcompat::UdpProvider>::UdpSocket>,
333
) -> Result<()> {
334
    let mut incoming = futures::stream::select_all(
335
        listeners
336
            .into_iter()
337
            .map(|socket| {
338
                futures::stream::unfold(Arc::new(socket), |socket| async {
339
                    let mut packet = [0; MAX_DATAGRAM_SIZE];
340
                    let packet = socket
341
                        .recv(&mut packet)
342
                        .await
343
                        .map(|(size, remote)| (packet, size, remote, socket.clone()));
344
                    Some((packet, socket))
345
                })
346
            })
347
            .enumerate()
348
            .map(|(listener_id, incoming_packet)| {
349
                Box::pin(incoming_packet.map(move |packet| (packet, listener_id)))
350
            }),
351
    );
352

            
353
    let pending_requests = Arc::new(Mutex::new(HashMap::new()));
354
    while let Some((packet, id)) = incoming.next().await {
355
        let (packet, size, addr, socket) = match packet {
356
            Ok(packet) => packet,
357
            Err(err) => {
358
                // TODO move crate::socks::accept_err_is_fatal somewhere else and use it here?
359
                warn_report!(err, "Incoming datagram failed");
360
                continue;
361
            }
362
        };
363

            
364
        let client_ref = tor_client.clone();
365
        runtime.spawn({
366
            let pending_requests = pending_requests.clone();
367
            async move {
368
                let res = handle_dns_req(
369
                    &client_ref,
370
                    id,
371
                    &packet[..size],
372
                    addr,
373
                    socket,
374
                    &pending_requests,
375
                )
376
                .await;
377
                if let Err(e) = res {
378
                    // TODO: warn_report does not work on anyhow::Error.
379
                    warn!("connection exited with error: {}", tor_error::Report(e));
380
                }
381
            }
382
        })?;
383
    }
384

            
385
    Ok(())
386
}