Overview
stem 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 gives you the building blocks you need with the safety guarantees Rust provides.
use ;
async
✨ Features
🔌 Control Socket
Connect to Tor via TCP or Unix domain sockets with full async I/O powered by Tokio.
- TCP port connections (
127.0.0.1:9051) - Unix domain sockets (
/var/run/tor/control) - Non-blocking async operations
- Automatic reconnection handling
🔐 Authentication
All authentication methods with automatic detection and secure credential handling.
- SAFECOOKIE — Challenge-response (recommended)
- COOKIE — File-based authentication
- PASSWORD — HashedControlPassword
- NONE — Open control port
🎛️ Controller API
High-level interface for complete Tor interaction.
- Query configuration and status
- Send signals (NEWNYM, RELOAD, etc.)
- Create, extend, and close circuits
- Attach and manage streams
- Create ephemeral hidden services
- Map addresses for custom routing
📄 Descriptor Parsing
Complete parsing for all Tor descriptor types.
- Server Descriptors — Full relay metadata
- Microdescriptors — Compact client-side info
- Consensus Documents — Network status
- Extra-Info — Bandwidth statistics
- Hidden Service — v2 and v3 descriptors
- Bandwidth Files — Authority measurements
📡 Event Handling
Subscribe to real-time Tor events with strongly-typed event structs.
- Bandwidth monitoring (
BW,CIRC_BW) - Circuit lifecycle (
CIRC,CIRC_MINOR) - Stream tracking (
STREAM,STREAM_BW) - Log messages (
DEBUG→ERR) - Status updates (
STATUS_*) - Hidden service events (
HS_DESC)
🚪 Exit Policy
Parse and evaluate relay exit policies.
- Full exit policy parsing
- IPv4 and IPv6 support
- CIDR notation for address ranges
- Port range evaluation
- Policy summarization
🚀 Quick Start
Add stem to your Cargo.toml:
[]
= "1.2"
= { = "1", = ["full"] }
Or install via cargo:
Enable Tor's Control Port
Add to your torrc:
ControlPort 9051
CookieAuthentication 1
Or for password authentication:
ControlPort 9051
HashedControlPassword 16:872860B76453A77D60CA2BB8C1A7042072093276A3D701AD684053EC4C
Generate a hashed password:
🎛️ Feature Flags
stem uses feature flags to allow you to compile only what you need, reducing compile time and binary size.
Default Features
By default, all features are enabled:
[]
= "1.2" # Includes all features
Minimal Build
For a minimal build with just the core functionality:
[]
= { = "1.2", = false }
This includes: socket communication, authentication, protocol parsing, utilities, and version handling.
Available Features
| 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 |
Custom Feature Combinations
Controller only (no descriptor parsing):
[]
= { = "1.2", = false, = ["controller"] }
Descriptors only (offline analysis):
[]
= { = "1.2", = false, = ["descriptors"] }
Controller + Descriptors (most common):
[]
= { = "1.2", = false, = ["controller", "descriptors"] }
Compile Time Improvements
Approximate compile time reductions with feature flags:
- Minimal build: ~40% faster (excludes descriptors, controller, events)
- Controller-only: ~30% faster (excludes descriptor parsing)
- Descriptors-only: ~20% faster (excludes controller, events)
Binary size reductions follow similar patterns.
💡 Examples
Connect and Authenticate
use ;
async
Query Information
// Get Tor version
let version = ctrl.get_version.await?;
println!;
// Get process ID
let pid = ctrl.get_pid.await?;
println!;
// Query arbitrary info
let traffic_read = ctrl.get_info.await?;
let traffic_written = ctrl.get_info.await?;
println!;
// Get configuration
let socks_ports = ctrl.get_conf.await?;
for port in socks_ports
Circuit Management
use CircStatus;
// List all circuits
let circuits = ctrl.get_circuits.await?;
for circuit in circuits
// Create a new circuit
let circuit_id = ctrl.new_circuit.await?;
println!;
// Close a circuit
ctrl.close_circuit.await?;
Stream Management
use StreamStatus;
// List all streams
let streams = ctrl.get_streams.await?;
for stream in streams
Event Subscription
use EventType;
// Subscribe to events
ctrl.set_events.await?;
// Process events
loop
Send Signals
use Signal;
// Request new identity (new circuits)
ctrl.signal.await?;
// Clear DNS cache
ctrl.signal.await?;
// Reload configuration
ctrl.signal.await?;
// Graceful shutdown
ctrl.signal.await?;
Hidden Services
// Create ephemeral hidden service (v3 onion)
let response = ctrl.create_ephemeral_hidden_service.await?;
println!;
println!;
// Remove hidden service
ctrl.remove_ephemeral_hidden_service.await?;
Descriptor Parsing
use ;
// Download and parse consensus
let consensus = download_consensus.await?;
println!;
println!;
// Parse server descriptor
let content = read_to_string?;
let descriptor = parse?;
println!;
println!;
// Compute digest
let digest = descriptor.digest?;
println!;
Exit Policy Evaluation
use ExitPolicy;
use IpAddr;
let policy = parse?;
// Check if traffic is allowed
let addr: IpAddr = "93.184.216.34".parse?;
if policy.can_exit_to
// Get policy summary
println!;
Version Comparison
use Version;
let version = ctrl.get_version.await?;
// Compare versions
let min_version = parse?;
if version >= min_version
📦 Module Reference
| 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.) |
🔒 Security
stem is designed with security as a priority:
- 100% Safe Rust — No
unsafecode - Constant-time comparison — For authentication tokens and cookies
- Memory clearing — Sensitive data cleared after use
- Input validation — Prevents protocol injection attacks
- Signature verification — Optional cryptographic validation for descriptors
⚡ Performance
- Async-first — Built on Tokio for high-performance async I/O
- Zero-copy parsing — Efficient descriptor parsing where possible
- Event streaming — Non-blocking real-time event handling
- Connection pooling — Efficient socket management
🛠️ Requirements
- Rust 1.70+
- Tokio runtime
- Tor instance with control port enabled
🧪 Testing
# Run unit tests
# Run with integration tests (requires running Tor)
# Run extensive tests
📊 Comparison with Python Stem
stem maintains functional parity with Python Stem while providing Rust's safety guarantees:
| Feature | Python Stem | stem |
|---|---|---|
| Control Protocol | ✅ | ✅ |
| All Auth Methods | ✅ | ✅ |
| Descriptor Parsing | ✅ | ✅ |
| Event Handling | ✅ | ✅ |
| Exit Policy | ✅ | ✅ |
| Hidden Services | ✅ | ✅ |
| Type Safety | ❌ | ✅ |
| Memory Safety | ❌ | ✅ |
| Async/Await | ❌ | ✅ |
| Zero-cost Abstractions | ❌ | ✅ |
📄 License
Copyright 2026 stem 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.
🤝 Contributing
Contributions are welcome! Please feel free to submit issues and pull requests.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request