Commit dcdd8c59 authored by eta's avatar eta
Browse files

Improve top-level arti-client documentation, add example code

This overhauls the top-level `arti-client` documentation significantly:

- the "Using arti-client" section walks the user through all of the
  necessary steps to initiate a Torified TCP connection, and then
  provides a code example
  - this example is also available as `examples/readme.rs`; it's not run
    as a doctest, since it involves connecting to Tor
  - a "More advanced usage" subheading provides information about stream
    isolation (and can potentially be used for other interesting
    features once we get them).
- a new "Multiple runtime support" section was added to explain the
  purpose and usage of the `tor-rtcompat` crate
- the section on design and privacy considerations was removed; this is
  probably okay to keep in a README, but users of the crate aren't going
  to be interested in this (at least I don't think)

(also, the doc comment for `arti_client::Error` was fixed to make actual
sense)
parent 3c82666d
Loading
Loading
Loading
Loading
+46 −0
Original line number Diff line number Diff line
use anyhow::Result;
use arti_client::{TorClient, TorClientConfig};
use tokio_crate as tokio;

use futures::io::{AsyncReadExt, AsyncWriteExt};

#[tokio::main]
async fn main() -> Result<()> {
    // Arti uses the `tracing` crate for logging. Install a handler for this, to print Arti's logs.
    tracing_subscriber::fmt::init();

    // The client config includes things like where to store persistent Tor network state.
    // The "sane defaults" provided are the same as the Arti standalone application, and save data
    // to a conventional place depending on operating system (for example, ~/.local/share/arti
    // on Linux platforms)
    let config = TorClientConfig::sane_defaults()?;
    // Arti needs an async runtime handle to spawn async tasks.
    let rt = tor_rtcompat::tokio::current_runtime()?;

    eprintln!("connecting to Tor...");

    // We now let the Arti client start and bootstrap a connection to the network.
    // (This takes a while to gather the necessary consensus state, etc.)
    let tor_client = TorClient::bootstrap(rt, config).await?;

    eprintln!("connecting to example.com...");

    let mut stream = tor_client.connect(("example.com", 80), None).await?;

    eprintln!("sending request...");

    stream
        .write_all(b"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n")
        .await?;

    stream.flush().await?;

    eprintln!("reading response...");

    let mut buf = Vec::new();
    stream.read_to_end(&mut buf).await?;

    println!("{}", String::from_utf8_lossy(&buf));

    Ok(())
}
+1 −1
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
use thiserror::Error;
use tor_rtcompat::TimeoutError;

/// An error originating from the tor-dirclient crate.
/// Represents errors that can occur while doing Tor operations.
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum Error {
+99 −15
Original line number Diff line number Diff line
@@ -24,24 +24,108 @@
//! general, are not the least bit stable.  If you use this code,
//! please expect your software to break on a regular basis.
//!
//! ## Design considerations, privacy considerations.
//!
//! As we build the APIs for this crate, we've been aiming for
//! simplicity and safety: we want it to be as easy as possible to use
//! `arti-client`, while trying to make certain kinds of privacy or security
//! violation hard to write accidentally.
//!
//! Privacy isn't just a drop-in feature, however.  There are still
//! plenty of ways to accidentally leak information, even if you're
//! anonymizing your connections over Tor.  We'll try to document
//! those in a user's guide at some point as Arti becomes more mature.
//!
//! # Using `arti-client`
//!
//! The `arti-client` crate provides an async Rust API.  It is
//! compatible with the `tokio` and `async_std` asynchronous backends.
//! The main entry point for this crate is the [`TorClient`], an object that lets you make
//! connections over the Tor network.
//!
//! Calling [`TorClient::bootstrap`] establishes a connection to the Tor network, pulling in
//! necessary state about network consensus as required. This state gets persisted to the
//! locations specified in the [`TorClientConfig`].
//!
//! A client can then be used to make connections over Tor with [`TorClient::connect`], which
//! accepts anything implementing [`IntoTorAddr`]. This returns a [`DataStream`], an anonymised
//! TCP stream type that implements [`AsyncRead`](futures::io::AsyncRead) and
//! [`AsyncWrite`](futures::io::AsyncWrite), as well as the Tokio versions of those traits if
//! the `tokio` crate feature is enabled.
//!
//! The [`TorAddr`] type is intended to ensure that DNS lookups are done via the Tor network
//! instead of locally. Doing local DNS resolution can leak information about which hostnames you're
//! connecting to to your local DNS resolver (i.e. your ISP), so it's much better to let Arti
//! do it for you to maintain privacy.
//!
//! If you really want to connect to a raw IP address and know what you're doing, take a look at
//! [`TorAddr::dangerously_from`] -- but be careful!
//!
//! ## Example: making connections over Tor
//!
//! ```no_run
//! # use anyhow::Result;
//! # use arti_client::{TorClient, TorClientConfig};
//! # use tokio_crate as tokio;
//! # #[tokio::main]
//! # async fn main() -> Result<()> {
//! // The client config includes things like where to store persistent Tor network state.
//! let config = TorClientConfig::sane_defaults()?;
//! // Arti needs a handle to an async runtime in order to spawn async tasks.
//! // (See "Multiple runtime support" below.)
//! let rt = tor_rtcompat::tokio::current_runtime()?;
//!
//! // Start the Arti client, and let it bootstrap a connection to the Tor network.
//! // (This takes a while to gather the necessary consensus state, etc.)
//! let tor_client = TorClient::bootstrap(rt, config).await?;
//!
//! // Initiate a connection over Tor to example.com, port 80.
//! let mut stream = tor_client.connect(("example.com", 80), None).await?;
//!
//! use futures::io::{AsyncReadExt, AsyncWriteExt};
//!
//! // Write out an HTTP request.
//! stream
//!     .write_all(b"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n")
//!     .await?;
//!
//! // IMPORTANT: Make sure the request was written.
//! // Arti buffers data by default due to the design of the Tor protocol, so flushing the
//! // buffer is usually required.
//! stream.flush().await?;
//!
//! // Read and print the result.
//! let mut buf = Vec::new();
//! stream.read_to_end(&mut buf).await?;
//!
//! println!("{}", String::from_utf8_lossy(&buf));
//! #
//! #    Ok(())
//! # }
//! ```
//!
//! ## More advanced usage
//!
//! This version of Arti includes basic support for "stream isolation": the ability to ensure that
//! different TCP connections ('streams') go over different Tor circuits (and thus different exit
//! nodes, making them originate from different IP addresses).
//!
//! This is useful to avoid deanonymising
//! users by correlation: for example, you might want a Tor connection to your bank and a Tor
//! connection to an online forum to use different circuits, to avoid the possibility of the two
//! identities being linked by having the same source IP.
//!
//! Streams can be isolated in two ways:
//!
//! - by calling [`TorClient::isolated_client`], which returns a new [`TorClient`] whose streams
//!   will use a different circuit
//! - by generating [`IsolationToken`]s, and passing them in via [`ConnectPrefs`] to
//!   [`TorClient::connect`].
//!
//! # Multiple runtime support
//!
//! Arti uses the [`tor_rtcompat`] crate to support multiple asynchronous runtimes; currently,
//! both [Tokio](https://tokio.rs) and [async-std](https://async.rs) are supported.
//!
//! Functions in this crate, like [`TorClient::bootstrap`],
//! will expect a type that implements [`tor_rtcompat::Runtime`], which can be obtained:
//!
//! - for Tokio:
//!   - by calling [`tor_rtcompat::tokio::current_runtime`], if a Tokio reactor is already running
//!   - by calling [`tor_rtcompat::tokio::create_runtime`], to start a new reactor if one is not
//!     already running
//!   - by manually creating a [`TokioRuntimeHandle`](tor_rtcompat::tokio::TokioRuntimeHandle) from
//!     an existing Tokio runtime handle
//! - for async-std:
//!   - by calling [`tor_rtcompat::async_std::current_runtime`], which will create a runtime or
//!     retrieve the existing one, if one has already been started
//!
//! TODO: Good examples here once the crate setup API is more simple.
//!
//! # Feature flags
//!