Skip to main content

arti/
dns.rs

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
7use futures::lock::Mutex;
8use futures::stream::StreamExt;
9use hickory_proto::op::{Message, OpCode, Query, ResponseCode};
10use hickory_proto::rr::{DNSClass, Name, RData, Record, RecordType, rdata};
11use hickory_proto::serialize::binary::{BinDecodable, BinEncodable};
12use std::collections::HashMap;
13use std::net::{IpAddr, SocketAddr};
14use std::sync::Arc;
15use tor_rtcompat::{SpawnExt, UdpProvider};
16use tracing::{debug, error, info, warn};
17
18use arti_client::{Error, HasKind, StreamPrefs, TorClient};
19use safelog::sensitive as sv;
20use tor_config::Listen;
21use tor_error::{error_report, warn_report};
22use tor_rtcompat::{Runtime, UdpSocket};
23
24use anyhow::{Result, anyhow};
25
26use crate::proxy::port_info;
27
28/// Maximum length for receiving a single datagram
29const 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)]
36struct DnsIsolationKey(usize, IpAddr);
37
38impl 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)]
58struct DnsCacheKey(DnsIsolationKey, Vec<Query>);
59
60/// Target for a DNS response
61#[derive(Debug, Clone)]
62struct 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.
72async fn do_query<R>(
73    tor_client: &TorClient<R>,
74    query: &Query,
75    prefs: &StreamPrefs,
76) -> Result<Vec<Record>, ResponseCode>
77where
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
91    let mut a = Vec::new();
92    let mut ptr = Vec::new();
93
94    match query.query_class() {
95        DNSClass::IN => {
96            match query.query_type() {
97                typ @ RecordType::A | typ @ RecordType::AAAA => {
98                    let mut name = query.name().clone();
99                    // name would be "torproject.org." without this
100                    name.set_fqdn(false);
101                    let res = tor_client
102                        .resolve_with_prefs(&name.to_utf8(), prefs)
103                        .await
104                        .map_err(err_conv)?;
105                    for ip in res {
106                        a.push((query.name().clone(), ip, typ));
107                    }
108                }
109                RecordType::PTR => {
110                    let addr = query
111                        .name()
112                        .parse_arpa_name()
113                        .map_err(|_| ResponseCode::FormErr)?
114                        .addr();
115                    let res = tor_client
116                        .resolve_ptr_with_prefs(addr, prefs)
117                        .await
118                        .map_err(err_conv)?;
119                    for domain in res {
120                        let domain = Name::from_utf8(domain).map_err(|_| ResponseCode::ServFail)?;
121                        ptr.push((query.name().clone(), domain));
122                    }
123                }
124                _ => {
125                    return Err(ResponseCode::NotImp);
126                }
127            }
128        }
129        _ => {
130            return Err(ResponseCode::NotImp);
131        }
132    }
133    for (name, ip, typ) in a {
134        match (ip, typ) {
135            (IpAddr::V4(v4), RecordType::A) => {
136                answers.push(Record::from_rdata(name, 3600, RData::A(rdata::A(v4))));
137            }
138            (IpAddr::V6(v6), RecordType::AAAA) => {
139                answers.push(Record::from_rdata(name, 3600, RData::AAAA(rdata::AAAA(v6))));
140            }
141            _ => (),
142        }
143    }
144    for (ptr, name) in ptr {
145        answers.push(Record::from_rdata(ptr, 3600, RData::PTR(rdata::PTR(name))));
146    }
147
148    Ok(answers)
149}
150
151/// Given a datagram containing a DNS query, resolve the query over
152/// the Tor network and send the response back.
153async fn handle_dns_req<R, U>(
154    tor_client: &TorClient<R>,
155    socket_id: usize,
156    packet: &[u8],
157    addr: SocketAddr,
158    socket: Arc<U>,
159    current_requests: &Mutex<HashMap<DnsCacheKey, Vec<DnsResponseTarget<U>>>>,
160) -> Result<()>
161where
162    R: Runtime,
163    U: UdpSocket,
164{
165    // if we can't parse the request, don't try to answer it.
166    let query = Message::from_bytes(packet)?;
167    let id = query.metadata.id;
168    let queries = query.queries;
169    let isolation = DnsIsolationKey(socket_id, addr.ip());
170
171    let request_id = {
172        let request_id = DnsCacheKey(isolation.clone(), queries.clone());
173
174        let response_target = DnsResponseTarget { id, addr, socket };
175
176        let mut current_requests = current_requests.lock().await;
177
178        let req = current_requests.entry(request_id.clone()).or_default();
179        req.push(response_target);
180
181        if req.len() > 1 {
182            debug!("Received a query already being served");
183            return Ok(());
184        }
185        debug!("Received a new query");
186
187        request_id
188    };
189
190    let mut response: Message;
191    // According to rfc9619 there should only be 1 query per request. This constraint would
192    // need to be relaxed for things like DNS cookies.
193    if queries.len() != 1 {
194        response = Message::error_msg(id, OpCode::Query, ResponseCode::FormErr);
195    } else {
196        let mut prefs = StreamPrefs::new();
197        prefs.set_isolation(isolation);
198
199        response = match do_query(tor_client, &queries[0], &prefs).await {
200            Ok(answers) => {
201                let mut response = Message::response(id, OpCode::Query);
202                response.metadata.recursion_desired = query.metadata.recursion_desired;
203                response.metadata.recursion_available = true;
204                response.add_queries(queries).add_answers(answers);
205                // TODO maybe add some edns?
206                response
207            }
208            Err(error_type) => Message::error_msg(id, OpCode::Query, error_type),
209        };
210    }
211
212    // remove() should never return None, but just in case
213    let targets = current_requests
214        .lock()
215        .await
216        .remove(&request_id)
217        .unwrap_or_default();
218
219    for target in targets {
220        response.metadata.id = target.id;
221        // ignore errors, we want to reply to everybody
222        let response = match response.to_bytes() {
223            Ok(r) => r,
224            Err(e) => {
225                // The response message probably contains the query DNS name, and the error
226                // might well do so too.  (Many variants of hickory_proto's ProtoErrorKind
227                // contain domain names.)  Digging into these to be more useful is tiresome,
228                // so just mark the whole response message, and error, as sensitive.
229                error_report!(e, "Failed to serialize DNS packet: {:?}", sv(&response));
230                continue;
231            }
232        };
233        let _ = target.socket.send(&response, &target.addr).await;
234    }
235    Ok(())
236}
237
238/// A DNS proxy server that can run indefinitely.
239#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
240#[must_use]
241pub(crate) struct DnsProxy<R: Runtime> {
242    /// A list of bound UDP sockets.
243    udp_sockets: Vec<<R as UdpProvider>::UdpSocket>,
244    /// A tor client to handle DNS requests.
245    tor_client: Arc<TorClient<R>>,
246}
247
248/// Bind to a set of DNS ports, and return a new DnsProxy.
249///
250/// Takes no action until `run_dns_proxy` is called.
251#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
252pub(crate) async fn bind_dns_resolver<R: Runtime>(
253    runtime: R,
254    tor_client: Arc<TorClient<R>>,
255    listen: Listen,
256) -> Result<DnsProxy<R>> {
257    if !listen.is_loopback_only() {
258        warn!(
259            "Configured to listen for DNS on non-local addresses. This is usually insecure! We recommend listening on localhost only."
260        );
261    }
262
263    let mut listeners = Vec::new();
264
265    // Try to bind to the DNS ports.
266    match listen.ip_addrs() {
267        Ok(addrgroups) => {
268            for addrgroup in addrgroups {
269                let mut any_success: bool = false;
270                for addr in addrgroup {
271                    // NOTE: Our logs here displays the local address. We allow this, since
272                    // knowing the address is basically essential for diagnostics.
273                    match runtime.bind(&addr).await {
274                        Ok(listener) => {
275                            let bound_addr = listener.local_addr()?;
276                            info!("Listening on {:?}.", bound_addr);
277                            listeners.push(listener);
278                            any_success = true;
279                        }
280                        #[cfg(unix)]
281                        Err(ref e) if e.raw_os_error() == Some(libc::EAFNOSUPPORT) => {
282                            warn_report!(e, "Address family not supported {}", addr);
283                        }
284                        Err(ref e) => {
285                            return Err(anyhow!("Can't listen on {}: {e}", addr));
286                        }
287                    }
288                }
289
290                if !any_success {
291                    return Err(anyhow!("All addresses failed to bind in a group"));
292                }
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
309impl<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.
335async fn run_dns_resolver_with_listeners<R: Runtime>(
336    runtime: R,
337    tor_client: Arc<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}