| Crates.io | stem-rs |
| lib.rs | stem-rs |
| version | 1.2.2 |
| created_at | 2025-12-31 17:23:14.362998+00 |
| updated_at | 2026-01-17 19:16:03.904875+00 |
| description | A complete Rust library for Tor control protocol โ build privacy-focused applications with type-safe, async-first APIs |
| homepage | https://stem.tn3w.dev |
| repository | https://github.com/tn3w/stem-rs |
| max_upload_size | |
| id | 2015033 |
| size | 2,355,985 |
Build privacy-focused applications with type-safe, async-first APIs
๐ Website โข ๐ Documentation โข ๐ Tutorials โข ๐ Quick Start โข ๐ก Examples
stem-rs is a Rust implementation of Stem, the Python library for interacting with Tor's control protocol. It provides idiomatic, type-safe Rust APIs while maintaining complete functional parity with Python Stem.
Whether you're building privacy tools, monitoring Tor relays, managing circuits, or creating onion services โ stem-rs gives you the building blocks you need with the safety guarantees Rust provides.
use stem_rs::{Controller, Error};
#[tokio::main]
async fn main() -> Result<(), Error> {
// Connect to Tor's control port
let mut ctrl = Controller::from_port("127.0.0.1:9051".parse()?).await?;
// Authenticate (auto-detects method)
ctrl.authenticate(None).await?;
// Query Tor version
let version = ctrl.get_version().await?;
println!("Connected to Tor {}", version);
Ok(())
}
๐ Control SocketConnect to Tor via TCP or Unix domain sockets with full async I/O powered by Tokio.
|
๐ AuthenticationAll authentication methods with automatic detection and secure credential handling.
|
๐๏ธ Controller APIHigh-level interface for complete Tor interaction.
|
๐ Descriptor ParsingComplete parsing for all Tor descriptor types.
|
๐ก Event HandlingSubscribe to real-time Tor events with strongly-typed event structs.
|
๐ช Exit PolicyParse and evaluate relay exit policies.
|
Add stem-rs to your Cargo.toml:
[dependencies]
stem-rs = "1.2"
tokio = { version = "1", features = ["full"] }
Or install via cargo:
cargo add stem-rs tokio --features tokio/full
Add to your torrc:
ControlPort 9051
CookieAuthentication 1
Or for password authentication:
ControlPort 9051
HashedControlPassword 16:872860B76453A77D60CA2BB8C1A7042072093276A3D701AD684053EC4C
Generate a hashed password:
tor --hash-password "your-password"
stem-rs uses feature flags to allow you to compile only what you need, reducing compile time and binary size.
By default, all features are enabled:
[dependencies]
stem-rs = "1.2" # Includes all features
For a minimal build with just the core functionality:
[dependencies]
stem-rs = { version = "1.2", default-features = false }
This includes: socket communication, authentication, protocol parsing, utilities, and version handling.
| Feature | Description | Dependencies |
|---|---|---|
full |
All features (default) | All features below |
controller |
High-level Controller API | events |
descriptors |
Tor descriptor parsing | client, exit-policy |
events |
Event subscription and handling | None |
exit-policy |
Exit policy parsing and evaluation | None |
client |
ORPort relay communication | None |
interpreter |
Interactive Tor control interpreter | controller, events |
compression |
Gzip decompression for descriptors | None |
Controller only (no descriptor parsing):
[dependencies]
stem-rs = { version = "1.2", default-features = false, features = ["controller"] }
Descriptors only (offline analysis):
[dependencies]
stem-rs = { version = "1.2", default-features = false, features = ["descriptors"] }
Controller + Descriptors (most common):
[dependencies]
stem-rs = { version = "1.2", default-features = false, features = ["controller", "descriptors"] }
Approximate compile time reductions with feature flags:
Binary size reductions follow similar patterns.
use stem_rs::{Controller, Error};
#[tokio::main]
async fn main() -> Result<(), Error> {
// Connect via TCP
let mut ctrl = Controller::from_port("127.0.0.1:9051".parse()?).await?;
// Or via Unix socket
// let mut ctrl = Controller::from_socket_file(Path::new("/var/run/tor/control")).await?;
// Auto-detect authentication method
ctrl.authenticate(None).await?;
// Or use password
// ctrl.authenticate(Some("my_password")).await?;
println!("Connected!");
Ok(())
}
// Get Tor version
let version = ctrl.get_version().await?;
println!("Tor {}", version);
// Get process ID
let pid = ctrl.get_pid().await?;
println!("PID: {}", pid);
// Query arbitrary info
let traffic_read = ctrl.get_info("traffic/read").await?;
let traffic_written = ctrl.get_info("traffic/written").await?;
println!("Traffic: {} read, {} written", traffic_read, traffic_written);
// Get configuration
let socks_ports = ctrl.get_conf("SocksPort").await?;
for port in socks_ports {
println!("SOCKS port: {}", port);
}
use stem_rs::CircStatus;
// List all circuits
let circuits = ctrl.get_circuits().await?;
for circuit in circuits {
if circuit.status == CircStatus::Built {
println!("Circuit {} ({} hops):", circuit.id, circuit.path.len());
for relay in &circuit.path {
println!(" โ {} ({:?})", relay.fingerprint, relay.nickname);
}
}
}
// Create a new circuit
let circuit_id = ctrl.new_circuit(None).await?;
println!("Created circuit: {}", circuit_id);
// Close a circuit
ctrl.close_circuit(circuit_id).await?;
use stem_rs::StreamStatus;
// List all streams
let streams = ctrl.get_streams().await?;
for stream in streams {
println!("Stream {} โ {}:{} ({:?})",
stream.id,
stream.target_host,
stream.target_port,
stream.status
);
}
use stem_rs::EventType;
// Subscribe to events
ctrl.set_events(&[
EventType::Bw, // Bandwidth
EventType::Circ, // Circuits
EventType::Stream, // Streams
EventType::Notice, // Log messages
]).await?;
// Process events
loop {
let event = ctrl.recv_event().await?;
match event {
ParsedEvent::Bandwidth(bw) => {
println!("BW: {} read, {} written", bw.read, bw.written);
}
ParsedEvent::Circuit(circ) => {
println!("Circuit {}: {:?}", circ.id, circ.status);
}
ParsedEvent::Log(log) => {
println!("[{}] {}", log.runlevel, log.message);
}
_ => {}
}
}
use stem_rs::Signal;
// Request new identity (new circuits)
ctrl.signal(Signal::Newnym).await?;
// Clear DNS cache
ctrl.signal(Signal::ClearDnsCache).await?;
// Reload configuration
ctrl.signal(Signal::Reload).await?;
// Graceful shutdown
ctrl.signal(Signal::Shutdown).await?;
// Create ephemeral hidden service (v3 onion)
let response = ctrl.create_ephemeral_hidden_service(
&[(80, "127.0.0.1:8080")], // Map port 80 to local 8080
"NEW", // Generate new key
"ED25519-V3", // Use v3 onion (recommended)
&[], // No special flags
).await?;
println!("Hidden service: {}.onion", response.service_id);
println!("Private key: {:?}", response.private_key);
// Remove hidden service
ctrl.remove_ephemeral_hidden_service(&response.service_id).await?;
use stem_rs::descriptor::{
ServerDescriptor, Microdescriptor, NetworkStatusDocument,
Descriptor, DigestHash, DigestEncoding,
download_consensus, download_server_descriptors,
};
// Download and parse consensus
let consensus = download_consensus(None).await?;
println!("Valid until: {}", consensus.valid_until);
println!("Relays: {}", consensus.routers.len());
// Parse server descriptor
let content = std::fs::read_to_string("cached-descriptors")?;
let descriptor = ServerDescriptor::parse(&content)?;
println!("Relay: {} ({})", descriptor.nickname, descriptor.fingerprint);
println!("Bandwidth: {} avg, {} burst",
descriptor.bandwidth_avg, descriptor.bandwidth_burst);
// Compute digest
let digest = descriptor.digest(DigestHash::Sha1, DigestEncoding::Hex)?;
println!("Digest: {}", digest);
use stem_rs::exit_policy::ExitPolicy;
use std::net::IpAddr;
let policy = ExitPolicy::parse("accept *:80, accept *:443, reject *:*")?;
// Check if traffic is allowed
let addr: IpAddr = "93.184.216.34".parse()?;
if policy.can_exit_to(addr, 443) {
println!("HTTPS traffic allowed");
}
// Get policy summary
println!("Policy: {}", policy.summary());
use stem_rs::Version;
let version = ctrl.get_version().await?;
// Compare versions
let min_version = Version::parse("0.4.0.0")?;
if version >= min_version {
println!("Tor {} supports required features", version);
}
| Module | Description |
|---|---|
controller |
High-level Tor control interface |
socket |
Low-level control socket communication |
auth |
Authentication methods and protocol info |
descriptor |
Tor descriptor parsing (server, micro, consensus, hidden service) |
events |
Event types and real-time handling |
exit_policy |
Exit policy parsing and evaluation |
version |
Version parsing and comparison |
client |
Direct ORPort relay communication |
interpreter |
Interactive Tor control interpreter |
util |
Validation utilities (fingerprints, nicknames, etc.) |
stem-rs is designed with security as a priority:
unsafe code# Run unit tests
cargo test
# Run with integration tests (requires running Tor)
cargo test --features integration
# Run extensive tests
cargo test --features extensive
stem-rs maintains functional parity with Python Stem while providing Rust's safety guarantees:
| Feature | Python Stem | stem-rs |
|---|---|---|
| Control Protocol | โ | โ |
| All Auth Methods | โ | โ |
| Descriptor Parsing | โ | โ |
| Event Handling | โ | โ |
| Exit Policy | โ | โ |
| Hidden Services | โ | โ |
| Type Safety | โ | โ |
| Memory Safety | โ | โ |
| Async/Await | โ | โ |
| Zero-cost Abstractions | โ | โ |
Copyright 2026 stem-rs contributors
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Contributions are welcome! Please feel free to submit issues and pull requests.
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)Website โข Documentation โข Tutorials โข crates.io โข GitHub โข Python Stem
Built with ๐ฆ by the stem-rs contributors