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::{
10
    Message, Query, header::MessageType, op_code::OpCode, response_code::ResponseCode,
11
};
12
use hickory_proto::rr::{DNSClass, Name, RData, Record, RecordType, rdata};
13
use hickory_proto::serialize::binary::{BinDecodable, BinEncodable};
14
use std::collections::HashMap;
15
use std::net::{IpAddr, SocketAddr};
16
use std::sync::Arc;
17
use tor_rtcompat::{SpawnExt, UdpProvider};
18
use tracing::{debug, error, info, warn};
19

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

            
26
use anyhow::{Result, anyhow};
27

            
28
use crate::proxy::port_info;
29

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

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

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

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

            
53
    fn enables_long_lived_circuits(&self) -> bool {
54
        false
55
    }
56
}
57

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

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

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

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

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

            
154
    Ok(answers)
155
}
156

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

            
178
    let request_id = {
179
        let request_id = DnsCacheKey(isolation.clone(), queries.to_vec());
180

            
181
        let response_target = DnsResponseTarget { id, addr, socket };
182

            
183
        let mut current_requests = current_requests.lock().await;
184

            
185
        let req = current_requests.entry(request_id.clone()).or_default();
186
        req.push(response_target);
187

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

            
194
        request_id
195
    };
196

            
197
    let mut prefs = StreamPrefs::new();
198
    prefs.set_isolation(isolation);
199

            
200
    let mut response = match do_query(tor_client, queries, &prefs).await {
201
        Ok(answers) => {
202
            let mut response = Message::new();
203
            response
204
                .set_message_type(MessageType::Response)
205
                .set_op_code(OpCode::Query)
206
                .set_recursion_desired(query.recursion_desired())
207
                .set_recursion_available(true)
208
                .add_queries(query.take_queries())
209
                .add_answers(answers);
210
            // TODO maybe add some edns?
211
            response
212
        }
213
        Err(error_type) => Message::error_msg(id, OpCode::Query, error_type),
214
    };
215

            
216
    // remove() should never return None, but just in case
217
    let targets = current_requests
218
        .lock()
219
        .await
220
        .remove(&request_id)
221
        .unwrap_or_default();
222

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

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

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

            
268
    let mut listeners = Vec::new();
269

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

            
303
    Ok(DnsProxy {
304
        tor_client,
305
        udp_sockets: listeners,
306
    })
307
}
308

            
309
impl<R: Runtime> DnsProxy<R> {
310
    /// Run indefinitely, receiving incoming DNS requests and processing them.
311
    pub(crate) async fn run_dns_proxy(self) -> Result<()> {
312
        let DnsProxy {
313
            tor_client,
314
            udp_sockets,
315
        } = self;
316
        run_dns_resolver_with_listeners(tor_client.runtime().clone(), tor_client, udp_sockets).await
317
    }
318

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

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

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

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

            
391
    Ok(())
392
}