pub struct ControlSocket { /* private fields */ }Expand description
A connection to Tor’s control interface.
ControlSocket provides async communication with Tor’s control port using
either TCP or Unix domain sockets. It handles the low-level details of the
control protocol including message framing and response parsing.
§Conceptual Role
This is the transport layer for all control protocol communication. It:
- Manages the underlying TCP or Unix socket connection
- Formats outgoing messages with proper CRLF termination
- Parses incoming responses according to the control protocol spec
- Tracks connection state and timing
§What This Type Does NOT Do
- Authentication (see
crate::auth) - High-level command abstractions (see
crate::Controller) - Event handling or subscription management
- Connection pooling or automatic reconnection
§Invariants
- After successful construction, the socket is connected and ready for I/O
- The socket remains valid until explicitly dropped or an I/O error occurs
- Messages sent are automatically terminated with CRLF if not already present
§Thread Safety
ControlSocket is Send but not Sync. The internal buffers require
exclusive access for reading and writing. For concurrent access:
use std::sync::Arc;
use tokio::sync::Mutex;
use stem_rs::ControlSocket;
let socket = ControlSocket::connect_port("127.0.0.1:9051".parse()?).await?;
let shared = Arc::new(Mutex::new(socket));
// Clone Arc for each task
let s1 = shared.clone();
tokio::spawn(async move {
let mut sock = s1.lock().await;
// Use socket exclusively
});§Example
use stem_rs::ControlSocket;
let mut socket = ControlSocket::connect_port("127.0.0.1:9051".parse()?).await?;
// Send a command
socket.send("GETINFO version").await?;
// Receive the response
let response = socket.recv().await?;
if response.is_ok() {
println!("Tor version: {}", response.content());
}Implementations§
Source§impl ControlSocket
impl ControlSocket
Sourcepub async fn connect_port(addr: SocketAddr) -> Result<Self, Error>
pub async fn connect_port(addr: SocketAddr) -> Result<Self, Error>
Connects to Tor’s control port via TCP.
Establishes a TCP connection to the specified address, which should be
Tor’s ControlPort (typically 127.0.0.1:9051 or as configured in torrc).
§Preconditions
- Tor must be running with a ControlPort configured
- The address must be reachable from this host
- No firewall rules blocking the connection
§Postconditions
- On success: Returns a connected socket ready for I/O
- The connection time is recorded for later retrieval
§Arguments
addr- The socket address to connect to (IP and port)
§Errors
Returns Error::Socket if:
- The connection is refused (Tor not running or wrong port)
- The address is unreachable (network error)
- The connection times out
- Any other I/O error occurs
§Example
use stem_rs::ControlSocket;
use std::net::SocketAddr;
// Connect to default control port
let addr: SocketAddr = "127.0.0.1:9051".parse().unwrap();
let socket = ControlSocket::connect_port(addr).await?;
// Or parse directly
let socket = ControlSocket::connect_port("127.0.0.1:9051".parse()?).await?;Sourcepub async fn connect_unix(path: &Path) -> Result<Self, Error>
pub async fn connect_unix(path: &Path) -> Result<Self, Error>
Connects to Tor’s control socket via Unix domain socket.
Establishes a Unix domain socket connection to the specified path, which
should be Tor’s ControlSocket (typically /var/run/tor/control or as
configured in torrc).
§Platform Support
This method is only available on Unix-like systems (Linux, macOS, BSD).
On other platforms, it returns Error::Socket
with ErrorKind::Unsupported.
§Preconditions
- Tor must be running with a ControlSocket configured
- The socket file must exist and be accessible
- The current process must have permission to connect to the socket
§Postconditions
- On success: Returns a connected socket ready for I/O
- The connection time is recorded for later retrieval
§Arguments
path- Path to the Unix domain socket file
§Errors
Returns Error::Socket if:
- The socket file does not exist
- Permission denied (insufficient privileges)
- The path is not a socket
- Platform does not support Unix sockets
- Any other I/O error occurs
§Example
use stem_rs::ControlSocket;
use std::path::Path;
// Connect to default control socket
let socket = ControlSocket::connect_unix(Path::new("/var/run/tor/control")).await?;
// Or a custom path
let socket = ControlSocket::connect_unix(Path::new("/tmp/tor/control")).await?;Sourcepub async fn send(&mut self, message: &str) -> Result<(), Error>
pub async fn send(&mut self, message: &str) -> Result<(), Error>
Sends a message to the control socket.
Formats and sends a control protocol message to Tor. The message is
automatically terminated with CRLF (\r\n) if not already present.
§Protocol Format
Messages are sent as: MESSAGE\r\n
For multi-line data, use the + prefix format manually or use the
higher-level Controller API.
§Preconditions
- The socket must be connected (not closed)
- For authenticated commands, authentication must have succeeded
§Postconditions
- On success: The message has been written and flushed to the socket
- The socket remains ready for subsequent operations
§Arguments
message- The control protocol message to send (without CRLF)
§Errors
Returns Error::Socket if:
- The socket has been closed or disconnected
- A write error occurs
- The flush operation fails
§Example
use stem_rs::ControlSocket;
let mut socket = ControlSocket::connect_port("127.0.0.1:9051".parse()?).await?;
// Send PROTOCOLINFO (no auth required)
socket.send("PROTOCOLINFO 1").await?;
// Send GETINFO (requires auth)
socket.send("GETINFO version").await?;
// CRLF is added automatically, but explicit is also fine
socket.send("GETINFO config-file\r\n").await?;Sourcepub async fn recv(&mut self) -> Result<ControlMessage, Error>
pub async fn recv(&mut self) -> Result<ControlMessage, Error>
Receives a message from the control socket.
Reads and parses a complete control protocol response from Tor. This method blocks until a complete message is received, handling single-line, multi-line, and data responses according to the protocol specification.
§Protocol Format
Responses follow the format: STATUS[DIVIDER]CONTENT\r\n
Where DIVIDER indicates the response type:
(space): Final line of response-(hyphen): Continuation line (more lines follow)+(plus): Data block follows, terminated by.\r\n
§Response Types
Single-line response:
250 OK\r\nMulti-line response:
250-version=0.4.7.1\r\n
250-config-file=/etc/tor/torrc\r\n
250 OK\r\nData response:
250+getinfo/names=\r\n
accounting/bytes -- Number of bytes...\r\n
accounting/enabled -- Is accounting enabled?\r\n
.\r\n
250 OK\r\n§Postconditions
- On success: Returns a complete
ControlMessagewith all response lines - The socket remains ready for subsequent operations
§Errors
Returns an error if:
Error::SocketClosed: The socket was closed before a complete message was receivedError::Protocol: The response is malformed:- Line too short (less than 4 characters)
- Invalid status code (not a 3-digit number)
- Inconsistent status codes across lines
- Invalid divider character
§Example
use stem_rs::ControlSocket;
let mut socket = ControlSocket::connect_port("127.0.0.1:9051".parse()?).await?;
socket.send("PROTOCOLINFO 1").await?;
let response = socket.recv().await?;
if response.is_ok() {
// Single-line content
println!("First line: {}", response.content());
// All lines joined
println!("Full response: {}", response.all_content());
} else {
println!("Error {}: {}", response.status_code, response.content());
}Sourcepub fn is_alive(&self) -> bool
pub fn is_alive(&self) -> bool
Checks if the socket connection is alive.
Returns whether the socket is believed to be connected. Note that this is a best-effort check and may not detect all disconnection scenarios until an actual I/O operation is attempted.
§Limitations
This method currently always returns true as the underlying socket
state is not actively monitored. A disconnection will only be detected
when send or recv fails.
For reliable disconnection detection, continuously poll with
recv or use the higher-level
Controller which handles this automatically.
§Returns
true if the socket is believed to be connected, false otherwise.
§Example
use stem_rs::ControlSocket;
let socket = ControlSocket::connect_port("127.0.0.1:9051".parse()?).await?;
if socket.is_alive() {
println!("Socket is connected");
}Sourcepub fn connection_time(&self) -> Instant
pub fn connection_time(&self) -> Instant
Returns the time when the socket connection was established.
This returns the Instant when the socket was successfully connected,
which can be used to calculate connection duration or implement
connection timeouts.
§Returns
The Instant when connect_port or
connect_unix successfully completed.
§Example
use stem_rs::ControlSocket;
use std::time::Duration;
let socket = ControlSocket::connect_port("127.0.0.1:9051".parse()?).await?;
// ... do some work ...
let connected_for = socket.connection_time().elapsed();
println!("Connected for {:?}", connected_for);
// Implement a connection timeout
if socket.connection_time().elapsed() > Duration::from_secs(3600) {
println!("Connection has been open for over an hour");
}