txtorcon
txtorcon is a Twisted-based Tor controller client, with state-tracking and configuration abstractions. As of version 24.8.0, it allows for launching new Tor instances or connecting to existing ones, using Tor as a client via SOCKS5, setting up Onion services, and monitoring live state and configuration. The library follows calendar versioning (YY.MM.0) and typically has frequent releases for new features and bug fixes, often aligned with Twisted's release schedule.
Common errors
-
Tor unexpectedly disconnected while running: GETINFO md/id/...
cause In versions prior to 23.11.0, receiving overly long `GETINFO` responses (e.g., for certain relay descriptors) could cause Twisted's line receiver to exceed its buffer, leading to an unexpected disconnect or hang.fixUpgrade `txtorcon` to version 23.11.0 or newer. This release addresses the handling of long lines in the control protocol. -
AttributeError: 'TorControlProtocol' object has no attribute 'on_disconnect'
cause You are attempting to use the `on_disconnect` callback on a `TorControlProtocol` instance, which was deprecated in `txtorcon` v19.1.0.fixReplace usage of `protocol.on_disconnect` with `protocol.when_disconnected`. The `when_disconnected` method returns a Deferred that fires when the protocol disconnects. -
SyntaxError: invalid syntax (on Python 2.x) or other Python 3 specific errors
cause `txtorcon` dropped support for Python 2 entirely in version 23.0.0, and newer features are Python 3-only.fixEnsure your project is running on Python 3.8 or newer. Update your environment and codebase to be fully Python 3 compliant.
Warnings
- breaking Python 2 support was completely dropped in `txtorcon` v23.0.0. Earlier versions (v20.0.0) had deprecated it.
- deprecated The `TorControlProtocol.on_disconnect` method was deprecated in v19.1.0.
- gotcha Older versions (before v23.11.0) could experience `GETINFO` commands hanging or causing unexpected Tor disconnections if the response from Tor contained ultra-long lines (over 16KiB). This was due to limitations in Twisted's `LineOnlyReceiver`.
- gotcha The default HTTPS policy for `twisted.web.client.Agent` instances used by `txtorcon` changed in v23.5.0 to align with `twisted.web.client.Agent`'s default. While generally safer, if you had custom or less strict TLS contexts, you might need to re-evaluate.
- deprecated Several dependencies like `six`, `incremental`, and `ipaddress` (for Python 2 backport) have been removed in recent versions (v24.8.0, v23.11.0, v23.5.0 respectively). While removal isn't a breaking change for `txtorcon` itself, projects relying on these implicitly via `txtorcon` might need to add them if they still target older Python versions or specific setups.
Install
-
pip install txtorcon
Imports
- txtorcon
import txtorcon
- connect
from txtorcon.controller import connect
from txtorcon import connect
- launch
from txtorcon.torconfig import launch_tor
from txtorcon import launch
- UNIXClientEndpoint
from twisted.internet.endpoints import UNIXClientEndpoint
- react
from twisted.internet.task import react
- inlineCallbacks
from twisted.internet.defer import inlineCallbacks
Quickstart
import os
from twisted.internet.task import react
from twisted.internet.defer import inlineCallbacks, ensureDeferred
from twisted.internet.endpoints import UNIXClientEndpoint, TCP4ClientEndpoint
import treq
import txtorcon
@react
@inlineCallbacks
def main(reactor):
# Connect to a running Tor instance, e.g., Tor Browser Bundle's control port (default 9151)
# Or a system-wide Tor daemon (default 9051, or /var/run/tor/control)
# You can also use txtorcon.launch(reactor) to start a new Tor process managed by txtorcon.
# Example: Connect to a Unix socket (common for system Tor)
control_endpoint = UNIXClientEndpoint(reactor, '/var/run/tor/control')
# Example: Connect to a TCP port (common for TBB or custom Tor setup)
# control_endpoint = TCP4ClientEndpoint(reactor, 'localhost', 9151)
# Password for Tor control port, if required. Use environment variable for security.
# For a newly launched Tor, this is usually not needed immediately.
password = os.environ.get('TOR_CONTROL_PASSWORD', '')
try:
tor = yield txtorcon.connect(
reactor,
control_endpoint,
password_function=lambda: password
)
print(f"Connected to Tor version {tor.version}")
url = u'https://www.torproject.org:443'
print(f"Downloading {repr(url)} via Tor...")
# Use tor.web_agent() to make requests over Tor's general circuit
resp = yield treq.get(url, agent=tor.web_agent())
body_data = yield resp.text()
print(f"Got {len(body_data)} bytes from {url}:")
print(body_data[:200] + ('...' if len(body_data) > 200 else '')) # Print first 200 chars
print("\nCreating a new Tor circuit...")
state = yield tor.create_state()
circ = yield state.build_circuit()
yield circ.when_built()
print(f"New circuit built with path: {' -> '.join([r.ip for r in circ.path])}")
except Exception as e:
print(f"An error occurred: {e}")
# Handle specific connection errors or Tor failures
finally:
# It's good practice to disconnect or shut down Tor if launched by txtorcon
if hasattr(tor, 'shutdown') and callable(tor.shutdown):
print("Shutting down Tor (if launched by txtorcon)...")
yield ensureDeferred(tor.shutdown())
else:
print("Not shutting down Tor, it was an external instance.")