dns_resolver/
main.rs

1#![warn(clippy::missing_docs_in_private_items)]
2//! # dns-resolver
3//! Use Tor to make a DNS over TCP request for a hostname, and get IP addresses back
4//!
5//! ### Intro
6//! This is a project intended to illustrate how Arti can be used to tunnel
7//! arbitrary TCP traffic. Here, a DNS client implementation has been hand crafted
8//! to illustrate custom made protocols being able to be used seamlessly over Tor
9//!
10//! ### Usage
11//! Simply run the program:
12//! `cargo run <hostname-to-look-up>`
13//!
14//! The program will then attempt to create a new Tor connection, craft the DNS
15//! query, and send it to a DNS server (right now, Cloudflare's 1.1.1.1)
16//!
17//! The response is then decoded into a struct and pretty printed to the user
18//!
19//! ### Note on DNS
20//! The DNS implementation showcased is not really meant for production. It is just
21//! a quick series of hacks to show you how, if you do have a very custom protocol
22//! that you need tunnelled over Tor, to use that protocol with Arti. For actually
23//! tunneling DNS requests over Tor, it is recommended to use a more tried-and-tested
24//! crate.
25//!
26//! For more information on DNS, you can read [RFC 1035](https://datatracker.ietf.org/doc/html/rfc1035)
27//! or [this educational guide](https://mislove.org/teaching/cs4700/spring11/handouts/project1-primer.pdf)
28use crate::dns::{AsBytes, FromBytes, Response};
29use arti_client::{TorClient, TorClientConfig};
30use std::env;
31use tokio::io::{AsyncReadExt, AsyncWriteExt};
32use tracing::debug;
33
34mod dns;
35
36#[tokio::main]
37async fn main() {
38    // Start logging messages
39    tracing_subscriber::fmt::init();
40    // Get and check CLI arguments
41    let args: Vec<String> = env::args().collect();
42    if args.len() != 2 {
43        eprintln!("Usage: dns-resolver <hostname-to-lookup>");
44        return;
45    }
46    // Create the default TorClientConfig and create a TorClient
47    let config = TorClientConfig::default();
48    let tor_client = TorClient::create_bootstrapped(config).await.unwrap();
49    debug!("Connecting to 1.1.1.1 port 53 for DNS over TCP lookup");
50    let mut stream = tor_client.connect(crate::dns::DNS_SERVER).await.unwrap();
51    // We now have a TcpStream analogue to use
52    match crate::dns::build_query(args[1].as_str()) {
53        Ok(query) => {
54            let req = query.as_bytes(); // Get raw bytes representation
55            stream.write_all(req.as_slice()).await.unwrap();
56            // Flushing ensures we actually send data over network right then instead
57            // of waiting for buffer to fill up
58            stream.flush().await.unwrap();
59            debug!("Awaiting response...");
60            let mut buf: Vec<u8> = Vec::new();
61            // Read the response
62            stream.read_to_end(&mut buf).await.unwrap();
63            // Interpret the response
64            match Response::from_bytes(&buf) {
65                Ok(resp) => println!("{}", resp),
66                Err(_) => eprintln!("No valid response!"),
67            };
68        }
69        Err(_) => tracing::error!("Invalid domain name entered!"),
70    };
71}