ControlSocket

Struct ControlSocket 

Source
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

Source

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?;
Source

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?;
Source

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?;
Source

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\n

Multi-line response:

250-version=0.4.7.1\r\n
250-config-file=/etc/tor/torrc\r\n
250 OK\r\n

Data 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 ControlMessage with 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 received
  • Error::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());
}
Source

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");
}
Source

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");
}

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.