Examples

The examples are grouped by functionality and serve as mini-HOWTOs – if you have a use-case that is missing, it may be useful to add an example, so please file a bug.

All files are in the examples/ sub-directory and are ready to run, usually with defaults designed to work with Tor Browser Bundle (localhost:9151).

The examples use default_control_port() to determine how to connect which you can override with an environment variable: TX_CONTROL_PORT. So e.g. export TX_CONTROL_PORT=9050 to run the examples again a system-wide Tor daemon.

Web: clients

web_client.py

Download the example.

Uses twisted.web.client to download a Web page using a twisted.web.client.Agent, via any circuit Tor chooses.

# this example shows how to use Twisted's web client with Tor via
# txtorcon

from twisted.internet.defer import inlineCallbacks
from twisted.internet.task import react
from twisted.internet.endpoints import TCP4ClientEndpoint
from twisted.web.client import readBody

import txtorcon
from txtorcon.util import default_control_port


@react
@inlineCallbacks
def main(reactor):
    # use port 9051 for system tor instances, or:
    # ep = UNIXClientEndpoint(reactor, '/var/run/tor/control')
    # ep = UNIXClientEndpoint(reactor, '/var/run/tor/control')
    ep = TCP4ClientEndpoint(reactor, '127.0.0.1', default_control_port())
    tor = yield txtorcon.connect(reactor, ep)
    print("Connected to {tor} via localhost:{port}".format(
        tor=tor,
        port=default_control_port(),
    ))

    # create a web.Agent that will talk via Tor. If the socks port
    # given isn't yet configured, this will do so. It may also be
    # None, which means "the first configured SOCKSPort"
    # agent = tor.web_agent(u'9999')
    agent = tor.web_agent()
    uri = b'http://surely-this-has-not-been-registered-and-is-invalid.com'
    uri = b'https://www.torproject.org'
    uri = b'http://fjblvrw2jrxnhtg67qpbzi45r7ofojaoo3orzykesly2j3c2m3htapid.onion/'  # txtorcon documentation
    print("Downloading {}".format(uri))
    resp = yield agent.request(b'GET', uri)

    print("Response has {} bytes".format(resp.length))
    body = yield readBody(resp)
    print("received body ({} bytes)".format(len(body)))
    print("{}\n[...]\n{}\n".format(body[:200], body[-200:]))

web_client_treq.py

Download the example.

Uses treq to download a Web page via Tor.

# just copying over most of "carml checkpypi" because it's a good
# example of "I want a stream over *this* circuit".

from twisted.internet.defer import inlineCallbacks
from twisted.internet.task import react
from twisted.internet.endpoints import TCP4ClientEndpoint

import txtorcon
from txtorcon.util import default_control_port

try:
    import treq
except ImportError:
    print("To use this example, please install 'treq':")
    print("pip install treq")
    raise SystemExit(1)


@react
@inlineCallbacks
def main(reactor):
    ep = TCP4ClientEndpoint(reactor, '127.0.0.1', default_control_port())
    # ep = UNIXClientEndpoint(reactor, '/var/run/tor/control')
    tor = yield txtorcon.connect(reactor, ep)
    print("Connected:", tor)

    resp = yield treq.get(
        'https://www.torproject.org:443',
        agent=tor.web_agent(),
    )

    print("Retrieving {} bytes".format(resp.length))
    data = yield resp.text()
    print("Got {} bytes:\n{}\n[...]{}".format(
        len(data),
        data[:120],
        data[-120:],
    ))

web_client_custom_circuit.py

Download the example.

Builds a custom circuit, and then uses twisted.web.client to download a Web page using the circuit created.

# this example shows how to use specific circuits over Tor (with
# Twisted's web client or with a custom protocol)
#
# NOTE WELL: this functionality is for advanced use-cases and if you
# do anything "special" to select your circuit hops you risk making it
# easy to de-anonymize this (and all other) Tor circuits.

from twisted.internet.protocol import Protocol, Factory
from twisted.internet.defer import inlineCallbacks, Deferred
from twisted.internet.task import react
from twisted.internet.endpoints import TCP4ClientEndpoint
from twisted.web.client import readBody

import txtorcon
from txtorcon.util import default_control_port


@react
@inlineCallbacks
def main(reactor):
    # use port 9051 for system tor instances, or:
    # ep = UNIXClientEndpoint(reactor, '/var/run/tor/control')
    ep = TCP4ClientEndpoint(reactor, '127.0.0.1', default_control_port())
    tor = yield txtorcon.connect(reactor, ep)
    print("Connected:", tor)

    config = yield tor.get_config()
    state = yield tor.create_state()
    socks = config.socks_endpoint(reactor)

    # create a custom circuit; in this case we're just letting Tor
    # decide the path but you *can* select a path (again: for advanced
    # use cases that will probably de-anonymize you)
    circ = yield state.build_circuit()
    print("Building a circuit:", circ)

    # at this point, the circuit will be "under way" but may not yet
    # be in BUILT state -- and hence usable. So, we wait. (Just for
    # demo purposes: the underlying connect will wait too)
    yield circ.when_built()
    print("Circuit is ready:", circ)

    if True:
        # create a web.Agent that will use this circuit (or fail)
        agent = circ.web_agent(reactor, socks)

        uri = 'https://www.torproject.org'
        print("Downloading {}".format(uri))
        resp = yield agent.request('GET', uri)

        print("Response has {} bytes".format(resp.length))
        body = yield readBody(resp)
        print("received body ({} bytes)".format(len(body)))
        print("{}\n[...]\n{}\n".format(body[:200], body[-200:]))

    if True:
        # make a plain TCP connection to a thing
        ep = circ.stream_via(reactor, 'torproject.org', 80, config.socks_endpoint(reactor))

        d = Deferred()

        class ToyWebRequestProtocol(Protocol):

            def connectionMade(self):
                print("Connected via {}".format(self.transport.getHost()))
                self.transport.write(
                    'GET http://torproject.org/ HTTP/1.1\r\n'
                    'Host: torproject.org\r\n'
                    '\r\n'
                )

            def dataReceived(self, d):
                print("  received {} bytes".format(len(d)))

            def connectionLost(self, reason):
                print("disconnected: {}".format(reason.value))
                d.callback(None)

        yield ep.connect(Factory.forProtocol(ToyWebRequestProtocol))  # returns "proto"
        yield d
        print("All done, closing the circuit")
        yield circ.close()

Starting Tor

launch_tor.py

Download the example. Launch a new Tor instance. This takes care of setting Tor’s notion ownership so that when the control connection goes away the running Tor exits.

"""
Launch a private Tor instance.
"""

import sys
import txtorcon
from twisted.web.client import readBody
from twisted.internet.task import react
from twisted.internet.defer import inlineCallbacks


@react
@inlineCallbacks
def main(reactor):
    # note that you can pass a few options as kwargs
    # (e.g. data_directory=, or socks_port= ). For other torrc
    # changes, see below.
    tor = yield txtorcon.launch(
        reactor,
        data_directory="./tordata",
        stdout=sys.stdout,
        socks_port='unix:/tmp/tor2/socks',
    )
    # tor = yield txtorcon.connect(
    #     reactor,
    #     clientFromString(reactor, "unix:/var/run/tor/control"),
    # )
    print("Connected to Tor version '{}'".format(tor.protocol.version))

    config = yield tor.get_config()
    state = yield tor.create_state()
    # or state = yield txtorcon.TorState.from_protocol(tor.protocol)

    print("This Tor has PID {}".format(state.tor_pid))
    print("This Tor has the following {} Circuits:".format(len(state.circuits)))
    for c in state.circuits.values():
        print("  {}".format(c))

    endpoint_d = config.socks_endpoint(reactor, u'unix:/tmp/tor2/socks')
    agent = tor.web_agent(socks_endpoint=endpoint_d)
    uri = b'https://www.torproject.org'
    print("Downloading {}".format(uri))
    resp = yield agent.request(b'GET', uri)
    print("Response has {} bytes".format(resp.length))
    body = yield readBody(resp)
    print("received body ({} bytes)".format(len(body)))
    print("{}\n[...]\n{}\n".format(body[:200], body[-200:]))

    # SOCKSPort is 'really' a list of SOCKS ports in Tor now, so we
    # have to set it to a list ... :/
    print("Changing our config (SOCKSPort=9876)")
    # config.SOCKSPort = ['unix:/tmp/foo/bar']
    config.SOCKSPort = ['9876']
    yield config.save()

    print("Querying to see it changed:")
    socksport = yield tor.protocol.get_conf("SOCKSPort")
    print("SOCKSPort", socksport)

launch_tor_endpoint.py

Download the example. Using the txtorcon.TCP4HiddenServiceEndpoint class to start up a Tor with a hidden service pointed to an IStreamServerEndpoint.

# Here we set up a Twisted Web server and then launch our own tor with
# a configured hidden service directed at the Web server we set
# up. This uses serverFromString to translate the "onion" endpoint
# descriptor into a TCPHiddenServiceEndpoint object...

from twisted.web import server, resource
from twisted.internet.defer import inlineCallbacks
from twisted.internet.task import react, deferLater
from twisted.internet.endpoints import serverFromString

import txtorcon


class Simple(resource.Resource):
    """
    A really simple Web site.
    """
    isLeaf = True

    def render_GET(self, request):
        return "<html>Hello, world! I'm a hidden service!</html>"


@react
@inlineCallbacks
def main(reactor):
    # several ways to proceed here and what they mean:
    #
    # "onion:80":
    #    launch a new Tor instance, configure a hidden service on some
    #    port and pubish descriptor for port 80
    #
    # "onion:80:controlPort=9051:localPort=8080:socksPort=9089:hiddenServiceDir=/home/human/src/txtorcon/hidserv":
    #    connect to existing Tor via control-port 9051, configure a hidden
    #    service listening locally on 8080, publish a descriptor for port
    #    80 and use an explicit hiddenServiceDir (where "hostname" and
    #    "private_key" files are put by Tor). We set SOCKS port
    #    explicitly, too.
    #
    # "onion:80:localPort=8080:socksPort=9089:hiddenServiceDir=/home/human/src/txtorcon/hidserv":
    #    all the same as above, except we launch a new Tor (because no
    #    "controlPort=9051")

    ep = "onion:80:controlPort=9051:localPort=8080:socksPort=9089:hiddenServiceDir=/home/human/src/txtorcon/hidserv"
    ep = "onion:80:localPort=8080:socksPort=9089:hiddenServiceDir=/home/human/src/txtorcon/hidserv"
    ep = "onion:80"
    hs_endpoint = serverFromString(reactor, ep)

    def progress(percent, tag, message):
        bar = int(percent / 10)
        print("[{}{}] {}".format("#" * bar, "." * (10 - bar), message))
    txtorcon.IProgressProvider(hs_endpoint).add_progress_listener(progress)

    # create our Web server and listen on the endpoint; this does the
    # actual launching of (or connecting to) tor.
    site = server.Site(Simple())
    port = yield hs_endpoint.listen(site)
    # XXX new accessor in newer API
    hs = port.onion_service

    # "port" is an IAddress implementor, in this case TorOnionAddress
    # so you can get most useful information from it -- but you can
    # also access .onion_service (see below)
    print(
        "I have set up a hidden service, advertised at:\n"
        "http://{host}:{port}\n"
        "locally listening on {local_address}\n"
        "Will stop in 60 seconds...".format(
            host=port.getHost().onion_uri,  # or hs.hostname
            port=port.public_port,
            # port.local_address will be a twisted.internet.tcp.Port
            # or a twisted.internet.unix.Port -- both have .getHost()
            local_address=port.local_address.getHost(),
        )
    )

    # if you prefer, hs (port.onion_service) is an instance providing
    # IOnionService (there's no way to do authenticated services via
    # endpoints yet, but if there was then this would implement
    # IOnionClients instead)
    print("private key:\n{}".format(hs.private_key))

    def sleep(s):
        return deferLater(reactor, s, lambda: None)

    yield sleep(50)
    for i in range(10):
        print("Stopping in {}...".format(10 - i))
        yield sleep(1)

Circuits and Streams

disallow_streams_by_port.py

Download the example. An example using IStreamAttacher which is very simple and does just what it sounds like: never attaches Streams exiting to a port in the “disallowed” list (it also explicitly closes them). Note that Tor already has this feature; this is just to illustrate how to use IStreamAttacher and that you may close streams.

XXX keep this one?

#
# This uses a very simple custom txtorcon.IStreamAttacher to disallow
# certain streams based solely on their port; by default it closes
# all streams on port 80 or 25 without ever attaching them to a
# circuit.
#
# For a more complex IStreamAttacher example, see
# attach_streams_by_country.py
#

from twisted.python import log
from twisted.internet.task import react
from twisted.internet.defer import inlineCallbacks, Deferred
from twisted.internet.endpoints import clientFromString
from zope.interface import implementer

import txtorcon


@implementer(txtorcon.IStreamAttacher)
class PortFilterAttacher:

    def __init__(self, state):
        self.state = state
        self.disallow_ports = [80, 25]
        print(
            "Disallowing all streams to ports: {ports}".format(
                ports=",".join(map(str, self.disallow_ports)),
            )
        )

    def attach_stream(self, stream, circuits):
        """
        IStreamAttacher API
        """

        def stream_closed(x):
            print("Stream closed:", x)

        if stream.target_port in self.disallow_ports:
            print(
                "Disallowing {stream} to port {stream.target_port}".format(
                    stream=stream,
                )
            )
            d = self.state.close_stream(stream)
            d.addCallback(stream_closed)
            d.addErrback(log.err)
            return txtorcon.TorState.DO_NOT_ATTACH

        # Ask Tor to assign stream to a circuit by itself
        return None


@react
@inlineCallbacks
def main(reactor):
    control_ep = clientFromString(reactor, "tcp:localhost:9051")
    tor = yield txtorcon.connect(reactor, control_ep)
    print("Connected to a Tor version={version}".format(
        version=tor.protocol.version,
    ))
    state = yield tor.create_state()
    yield state.set_attacher(PortFilterAttacher(state), reactor)

    print("Existing streams:")
    for s in state.streams.values():
        print("  ", s)
    yield Deferred()

stream_circuit_logger.py

Download the example. For listening to changes in the Circuit and State objects, this example is the easiest to understand as it just prints out (some of) the events that happen. Run this, then visit some Web sites via Tor to see what’s going on.

#!/usr/bin/env python

# This uses an IStreamListener and an ICircuitListener to log all
# built circuits and all streams that succeed.

import sys
from twisted.python import log
from twisted.internet.task import react
from twisted.internet.defer import inlineCallbacks, Deferred
import txtorcon


def log_circuit(circuit):
    path = '->'.join(map(lambda x: str(x.location.countrycode), circuit.path))
    log.msg('Circuit %d (%s) is %s for purpose "%s"' %
            (circuit.id, path, circuit.state, circuit.purpose))


def log_stream(stream):
    circ = ''
    if stream.circuit:
        path = '->'.join(map(lambda x: str(x.location.countrycode), stream.circuit.path))
        circ = ' via circuit %d (%s)' % (stream.circuit.id, path)
    proc = txtorcon.util.process_from_address(
        stream.source_addr,
        stream.source_port,
    )
    if proc:
        proc = ' from process "%s"' % (proc, )

    elif stream.source_addr == '(Tor_internal)':
        proc = ' for Tor internal use'

    else:
        proc = ' from remote "%s:%s"' % (str(stream.source_addr),
                                         str(stream.source_port))
    log.msg('Stream %d to %s:%d attached%s%s' %
            (stream.id, stream.target_host, stream.target_port, circ, proc))


class StreamCircuitLogger(txtorcon.StreamListenerMixin,
                          txtorcon.CircuitListenerMixin):

    def stream_attach(self, stream, circuit):
        log_stream(stream)

    def stream_failed(self, stream, reason='', remote_reason='', **kw):
        print('Stream %d failed because "%s"' % (stream.id, remote_reason))

    def circuit_built(self, circuit):
        log_circuit(circuit)

    def circuit_failed(self, circuit, **kw):
        log.msg('Circuit %d failed "%s"' % (circuit.id, kw['REASON']))


@react
@inlineCallbacks
def main(reactor):
    log.startLogging(sys.stdout)

    tor = yield txtorcon.connect(reactor)
    log.msg('Connected to a Tor version %s' % tor.protocol.version)
    state = yield tor.create_state()

    listener = StreamCircuitLogger()
    state.add_circuit_listener(listener)
    state.add_stream_listener(listener)

    tor.protocol.add_event_listener('STATUS_GENERAL', log.msg)
    tor.protocol.add_event_listener('STATUS_SERVER', log.msg)
    tor.protocol.add_event_listener('STATUS_CLIENT', log.msg)

    log.msg('Existing state when we connected:')
    for s in state.streams.values():
        log_stream(s)

    log.msg('Existing circuits:')
    for c in state.circuits.values():
        log_circuit(c)
    yield Deferred()

Events

monitor.py

Download the example.

Use a plain txtorcon.TorControlProtocol instance to listen for some simple events – in this case marginally useful, as it listens for logging at level INFO, NOTICE, WARN and ERR.

#!/usr/bin/env python

# Just listens for a few EVENTs from Tor (INFO NOTICE WARN ERR) and
# prints out the contents, so functions like a log monitor.

from twisted.internet import task, defer
from twisted.internet.endpoints import UNIXClientEndpoint
import txtorcon


@task.react
@defer.inlineCallbacks
def main(reactor):
    ep = UNIXClientEndpoint(reactor, '/var/run/tor/control')
    tor = yield txtorcon.connect(reactor, ep)

    def log(msg):
        print(msg)
    print("Connected to a Tor version", tor.protocol.version)
    for event in ['INFO', 'NOTICE', 'WARN', 'ERR']:
        tor.protocol.add_event_listener(event, log)
    is_current = yield tor.protocol.get_info('status/version/current')
    version = yield tor.protocol.get_info('version')
    print("Version '{}', is_current={}".format(version, is_current['status/version/current']))
    yield defer.Deferred()

Miscellaneous

stem_relay_descriptor.py

Download the example.

Get information about a relay descriptor with the help of Stem’s Relay Descriptor class. We need to specify the nickname or the fingerprint to get back the details.

#!/usr/bin/env python

# This shows how to get the detailed information about a
# relay descriptor and parse it into Stem's RelayDescriptor
# class. More about the class can be read from
#
# https://stem.torproject.org/api/descriptor/server_descriptor.html#stem.descriptor.server_descriptor.RelayDescriptor
#
# We need to pass the nickname or the fingerprint of the onion router
# for which we need the the descriptor information.
#
# Also you need to configure Tor to actually download these
# descriptors -- by default Tor only downloads "microdescriptors"
# (whose information is already available live via txtorcon.Router
# instances). Set "UseMicrodescriptors 0" to download "full" descriptors

from twisted.internet.task import react
from twisted.internet.defer import inlineCallbacks
import txtorcon

try:
    from stem.descriptor.server_descriptor import RelayDescriptor
except ImportError:
    print("You must install 'stem' to use this example:")
    print("  pip install stem")
    raise SystemExit(1)


@react
@inlineCallbacks
def main(reactor):
    tor = yield txtorcon.connect(reactor)

    or_nickname = "moria1"
    print("Trying to get decriptor information about '{}'".format(or_nickname))
    # If the fingerprint is used in place of nickname then, desc/id/<OR identity>
    # should be used.
    try:
        descriptor_info = yield tor.protocol.get_info('desc/name/' + or_nickname)
    except txtorcon.TorProtocolError:
        print("No information found. Enable descriptor downloading by setting:")
        print("  UseMicrodescritors 0")
        print("In your torrc")
        raise SystemExit(1)

    descriptor_info = descriptor_info.values()[0]
    relay_info = RelayDescriptor(descriptor_info)
    print("The relay's fingerprint is: {}".format(relay_info.fingerprint))
    print("Time in UTC when the descriptor was made: {}".format(relay_info.published))

txtorcon.tac

Download the example

Create your own twisted Service for deploying using twistd.

import functools
from os.path import dirname
import sys
from tempfile import mkdtemp

import txtorcon

from twisted.application import service, internet
from twisted.internet import reactor
from twisted.internet.endpoints import TCP4ClientEndpoint
from twisted.python import log
from twisted.web import static, server
from zope.interface import implements


class TorService(service.Service):
    implements(service.IService)
    directory = dirname(__file__)
    port = 8080

    def __init__(self):
        self.torfactory = txtorcon.TorProtocolFactory()
        self.connection = TCP4ClientEndpoint(reactor, 'localhost', 9052)
        self.resource = server.Site(static.File(self.directory))

    def startService(self):
        service.Service.startService(self)

        reactor.listenTCP(self.port, self.resource)
        self._bootstrap().addCallback(self._complete)

    def _bootstrap(self):
        self.config = txtorcon.TorConfig()
        self.config.HiddenServices = [
            txtorcon.HiddenService(self.config, mkdtemp(),
                                   ['%d 127.0.0.1:%d' % (80, self.port)])
        ]
        self.config.save()
        return txtorcon.launch_tor(self.config, reactor,
                                   progress_updates=self._updates,
                                   tor_binary='tor')

    def _updates(self, prog, tag, summary):
        log.msg('%d%%: %s' % (prog, summary))

    def _complete(self, proto):
        log.msg(self.config.HiddenServices[0].hostname)

application = service.Application("Txtorcon Application")
torservice = TorService()
torservice.setServiceParent(application)