Examples

In the examples/ sub-directory are a few different mostly-simple ways of using txtorcon. They all show how to set up a connection and then wait for and use various information from Tor.

hello_darkweb.py

Download the example.

This is a minimal (but still working) hidden-service set up using the endpoint parsers (these are Twisted IPlugin implementations; see the documentation for more). It even shows Tor’s progress messages on the console.

#!/usr/bin/env python

# This shows how to leverage the endpoints API to get a new hidden
# service up and running quickly. You can pass along this API to your
# users by accepting endpoint strings as per Twisted recommendations.
#
# http://twistedmatrix.com/documents/current/core/howto/endpoints.html#maximizing-the-return-on-your-endpoint-investment
#
# note that only the progress-updates needs the "import txtorcon" --
# you do still need it installed so that Twisted finds the endpoint
# parser plugin but code without knowledge of txtorcon can still
# launch a Tor instance using it. cool!

from __future__ import print_function
from twisted.internet import endpoints, defer
from twisted.internet.task import react
from twisted.web import server, static, resource
import txtorcon


@defer.inlineCallbacks
def main(reactor):
    root = resource.Resource()
    root.putChild('', static.Data(
        "<html>Hello, hidden-service world!</html>",
        'text/html')
    )
    ep = endpoints.serverFromString(reactor, "onion:80")
    txtorcon.IProgressProvider(ep).add_progress_listener(
        lambda percent, tag, msg: print(msg)
    )
    port = yield ep.listen(server.Site(root))
    print("Our address {}".format(port))
    yield defer.Deferred()  # wait forever; this Deferred never fires
react(main)

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.

#!/usr/bin/env python

#
# 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 import reactor
from zope.interface import implements

import txtorcon


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


class PortFilterAttacher:
    implements(txtorcon.IStreamAttacher)

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

    def attach_stream(self, stream, circuits):
        """
        IStreamAttacher API
        """
        if stream.target_port in self.disallow_ports:
            print "Disallowing", stream, "to port", stream.target_port
            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


def do_setup(state):
    print "Connected to a Tor version", state.protocol.version

    state.set_attacher(PortFilterAttacher(), reactor)

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


def setup_failed(arg):
    print "SETUP FAILED", arg
    reactor.stop()


d = txtorcon.build_local_tor_connection(reactor)
d.addCallback(do_setup).addErrback(setup_failed)
reactor.run()

launch_tor.py

Download the example. Set up a tor configuration and launch a slave Tor. This takes care of the setting Tor’s notion ownership so that when the control connection goes away, so does the running Tor.

#!/usr/bin/env python

# Launch a slave Tor by first making a TorConfig object.

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


@inlineCallbacks
def main(reactor):
    config = txtorcon.TorConfig()
    config.OrPort = 1234
    config.SocksPort = 9999
    try:
        yield txtorcon.launch_tor(config, reactor, stdout=stdout)

    except RuntimeError as e:
        print "Error:", e
        return

    proto = config.protocol
    print "Connected to Tor version", proto.version

    state = yield txtorcon.TorState.from_protocol(proto)
    print "This Tor has PID", state.tor_pid
    print "This Tor has the following %d Circuits:" % len(state.circuits)
    for c in state.circuits.values():
        print c

    print "Changing our config (SOCKSPort=9876)"
    config.SOCKSPort = 9876
    yield config.save()

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


if __name__ == '__main__':
    react(main)

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; fairly similar to launch_tor_with_hiddenservice.py but more things are automated.

#!/usr/bin/env python

# Here we set up a Twisted Web server and then launch a slave 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.internet import reactor
from twisted.web import server, resource
from twisted.internet.endpoints import serverFromString

import txtorcon


class Simple(resource.Resource):
    isLeaf = True

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


def setup_failed(arg):
    print "SETUP FAILED", arg


def setup_complete(port):
    # the port we get back should implement this (as well as IListeningPort)
    port = txtorcon.IHiddenService(port)
    print "I have set up a hidden service, advertised at:",
    print "http://%s:%d" % (port.getHost().onion_uri, port.getHost().onion_port)
    print "locally listening on", port.local_address.getHost()
    print "Will stop in 60 seconds..."

    def blam(x):
        print "%d..." % x
    reactor.callLater(50, blam, 10)
    reactor.callLater(55, blam, 5)
    reactor.callLater(56, blam, 4)
    reactor.callLater(57, blam, 3)
    reactor.callLater(58, blam, 2)
    reactor.callLater(59, blam, 1)
    reactor.callLater(60, reactor.stop)


def progress(percent, tag, message):
    bar = int(percent / 10)
    print '[%s%s] %s' % ('#' * bar, '.' * (10 - bar), message)

# several ways to proceed here and what they mean:
#
# ep0:
#    launch a new Tor instance, configure a hidden service on some
#    port and pubish descriptor for port 80
# ep1:
#    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.
# ep2:
#    all the same as ep1, except we launch a new Tor (because no
#    "controlPort=9051")
#

ep0 = "onion:80"
ep1 = "onion:80:controlPort=9051:localPort=8080:socksPort=9089:hiddenServiceDir=/home/human/src/txtorcon/hidserv"
ep2 = "onion:80:localPort=8080:socksPort=9089:hiddenServiceDir=/home/human/src/txtorcon/hidserv"

hs_endpoint = serverFromString(reactor, ep0)
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())
d = hs_endpoint.listen(site)
d.addCallback(setup_complete)
d.addErrback(setup_failed)

reactor.run()

launch_tor_with_hiddenservice.py

Download the example. A more complicated version of the launch_tor.py example where we also set up a Twisted Web server in the process and have the slave Tor set up a hidden service configuration pointing to it.

#!/usr/bin/env python

# Here we set up a Twisted Web server and then launch a slave tor
# with a configured hidden service directed at the Web server we set
# up.

import tempfile
import functools

from twisted.internet import reactor
from twisted.internet.endpoints import TCP4ServerEndpoint
from twisted.web import server, resource

import txtorcon


class Simple(resource.Resource):
    isLeaf = True

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


def updates(prog, tag, summary):
    print "%d%%: %s" % (prog, summary)


def setup_complete(config, proto):
    print "Protocol completed"

    onion_address = config.HiddenServices[0].hostname

    print "I have a hidden (web) service running at:"
    print "http://%s (port %d)" % (onion_address, hs_public_port)
    print "The temporary directory for it is at:", config.HiddenServices[0].dir
    print
    print "For example, you should be able to visit it via:"
    print "  torsocks lynx http://%s" % onion_address


def setup_failed(arg):
    print "SETUP FAILED", arg
    reactor.stop()

hs_port = 9876
hs_public_port = 80
hs_temp = tempfile.mkdtemp(prefix='torhiddenservice')

# register something to clean up our tempdir
reactor.addSystemEventTrigger(
    'before', 'shutdown',
    functools.partial(
        txtorcon.util.delete_file_or_tree,
        hs_temp
    )
)

# configure the hidden service we want.
# obviously, we'd want a more-persistent place to keep the hidden
# service directory for a "real" setup. If the directory is empty at
# startup as here, Tor creates new keys etcetera (which IS the .onion
# address). That is, every time you run this script you get a new
# hidden service URI, which is probably not what you want.
# The launch_tor method adds other needed config directives to give
# us a minimal config.
config = txtorcon.TorConfig()
config.SOCKSPort = 0
config.ORPort = 9089
config.HiddenServices = [
    txtorcon.HiddenService(
        config,
        hs_temp,
        ["%d 127.0.0.1:%d" % (hs_public_port, hs_port)]
    )
]
config.save()

# next we set up our service to listen on hs_port which is forwarded
# (via the HiddenService options) from the hidden service address on
# port hs_public_port
site = server.Site(Simple())
hs_endpoint = TCP4ServerEndpoint(reactor, hs_port, interface='127.0.0.1')
hs_endpoint.listen(site)

# we've got our Twisted service listening locally and our options
# ready to go, so we now launch Tor. Once it's done (see above
# callbacks) we print out the .onion URI and then do "nothing"
# (i.e. let the Web server do its thing). Note that the way we've set
# up the slave Tor process, when we close the connection to it tor
# will exit.

d = txtorcon.launch_tor(config, reactor, progress_updates=updates)
d.addCallback(functools.partial(setup_complete, config))
d.addErrback(setup_failed)
reactor.run()

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 import reactor
import txtorcon


def logCircuit(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 logStream(stream, state):
    circ = ''
    if stream.circuit:
        path = '->'.join(map(lambda x: 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,
        state
    )
    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 __init__(self, state):
        self.state = state

    def stream_attach(self, stream, circuit):
        logStream(stream, self.state)

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

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

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


def setup(state):
    log.msg('Connected to a Tor version %s' % state.protocol.version)

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

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

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

    log.msg('Existing circuits:')
    for c in state.circuits.values():
        logCircuit(c)


def setup_failed(arg):
    print "SETUP FAILED", arg
    log.err(arg)
    reactor.stop()


log.startLogging(sys.stdout)

d = txtorcon.build_local_tor_connection(reactor)
d.addCallback(setup).addErrback(setup_failed)
reactor.run()

circuit_for_next_stream.py

Download the example. This creates a custom stream specified via router names on the command-line and then attaches the next new stream the controller sees to this circuit and exits. A decent custom-circuit example, and a little simpler than the following example (attach_streams_by_country).

#!/usr/bin/env python

#
# This allows you to create a particular circuit, which is then used
# for the very next (non-Tor-internal) stream created. The use-case
# here might be something like, "I'm going to connect a long-lived
# stream in a moment *cough*IRC*cough*, so I'd like a circuit through
# high-uptime nodes"
#

import sys
import functools
import random

from twisted.python import log
from twisted.internet import reactor
from zope.interface import implements

import txtorcon


class MyStreamListener(txtorcon.StreamListenerMixin):

    def stream_new(self, stream):
        print "new stream:", stream.id, stream.target_host

    def stream_succeeded(self, stream):
        print "successful stream:", stream.id, stream.target_host


class MyAttacher(txtorcon.CircuitListenerMixin, txtorcon.StreamListenerMixin):
    implements(txtorcon.IStreamAttacher)

    def __init__(self, state):
        self.state = state
        # the circuit which we will use to attach the next stream to
        self.circuit = None

    def set_circuit(self, circuit):
        self.circuit = circuit

    def circuit_built(self, circuit):
        "ICircuitListener"

        if self.circuit is None:
            return

        if circuit != self.circuit:
            return

        print "Circuit built, awaiting next stream."

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

        if self.circuit is not None:
            print "Attaching", stream, "to", self.circuit
            return self.circuit

        # let Tor connect this stream how it likes
        return None

    def stream_attach(self, stream, circuit):
        print "stream", stream.id, "attached to circuit", circuit.id,
        print "with path:", '->'.join(map(lambda x: x.location.countrycode,
                                          circuit.path))
        if self.circuit is circuit:
            print "...so we're done."
            reactor.stop()


def do_setup(path, state):
    print "Connected to a Tor version", state.protocol.version

    attacher = MyAttacher(state)
    state.set_attacher(attacher, reactor)
    state.add_circuit_listener(attacher)
    state.add_stream_listener(attacher)

    print "Existing state when we connected:"
    print "Streams:"
    for s in state.streams.values():
        print ' ', s

    print
    print "General-purpose circuits:"
    for c in filter(lambda x: x.purpose == 'GENERAL', state.circuits.values()):
        path = '->'.join(map(lambda x: x.location.countrycode, c.path))
        print ' ', c.id, path

    print "Building our Circuit:", path
    real_path = []
    try:
        for name in path:
            print name
            if name == 'X':
                if len(real_path) == 0:
                    g = random.choice(state.entry_guards.values())
                    real_path.append(g)

                else:
                    g = random.choice(state.routers.values())
                    real_path.append(g)

            else:
                real_path.append(state.routers[name])

    except KeyError, e:
        print "Couldn't find router:", e
        sys.exit(1)

    print "...using routers:", real_path
    d = state.build_circuit(real_path)
    d.addCallback(attacher.set_circuit).addErrback(log.err)
    return d


def setup_failed(arg):
    print "Setup Failed:", arg.getErrorMessage()
    reactor.stop()

if len(sys.argv) == 1:
    print "usage: %s router [router] [router] ..." % sys.argv[0]
    print
    print "       You may use X for a router name, in which case a random one will"
    print "       be selected (a random one of your entry guards if its in the first"
    print "       position)."
    sys.exit(1)

path = sys.argv[1:]

d = txtorcon.build_local_tor_connection(reactor)
d.addCallback(functools.partial(do_setup, path)).addErrback(setup_failed)
reactor.run()

attach_streams_by_country.py

Download the example. This is one of the more complicated examples. It uses a custom Stream attacher (via IStreamAttacher) to only attach Streams to a Circuit with an exit node in the same country as the server to which the Stream is going (as determined by GeoIP). Caveat: the DNS lookups go via a Tor-assigned stream, so for sites which use DNS trickery to get you to a “close” server, this won’t be as interesting. For bonus points, if there is no Circuit exiting in the correct country, one is created before the Stream is attached.

#!/usr/bin/env python

#
# This uses a custom txtorcon.IStreamAttacher to force streams to use
# circuits that exit in the same country (as supplied by GeoIP) and
# builds such a circuit if one isn't available yet.
#
# Note that you can do something very similar to this with Tor's
# config file as well by setting something like:
#
# ExitNodes {us},{ca}
#
# ...in your torrc. The above just exits from those countries, not
# the one in which the Web server is located, however. So, this is a
# little redundant, but gives you the idea of how to do these sorts
# of things.
#
# Another thing to note is that the DNS lookup is a stream before the
# name is looked up, so the DNS lookup may occur from whatever stream
# Tor chose for that (we return None, which causes the attacher to
# tell Tor to attach that stream itself). This presents a problem for
# sites which optimize the server they deliver based on DNS -- if you
# lookup from X you'll get a server near/in X, which for our next
# step will make "the site" appear to be there.
#
# The only "solution" for this would be to do the lookup locally, but
# that defeats the purpose of Tor.
#

import random

from twisted.python import log
from twisted.internet import reactor, defer
from zope.interface import implements

import txtorcon


class MyStreamListener(txtorcon.StreamListenerMixin):

    def stream_new(self, stream):
        print "new stream:", stream.id, stream.target_host

    def stream_succeeded(self, stream):
        print "successful stream:", stream.id, stream.target_host

    def stream_attach(self, stream, circuit):
        print "stream", stream.id, " attached to circuit", circuit.id,
        print "with path:", '->'.join(map(lambda x: x.location.countrycode,
                                          circuit.path))


class MyAttacher(txtorcon.CircuitListenerMixin):
    implements(txtorcon.IStreamAttacher)

    def __init__(self, state):
        # pointer to our TorState object
        self.state = state
        # circuits for which we are awaiting completion so we can
        # finish our attachment to them.
        self.waiting_circuits = []

    def waiting_on(self, circuit):
        for (circid, d, stream_cc) in self.waiting_circuits:
            if circuit.id == circid:
                return True
        return False

    def circuit_extend(self, circuit, router):
        "ICircuitListener"
        if circuit.purpose != 'GENERAL':
            return
        # only output for circuits we're waiting on
        if self.waiting_on(circuit):
            path = '->'.join(map(lambda x: x.location.countrycode,
                                 circuit.path))
            print "  circuit %d (%s). Path now %s" % (circuit.id,
                                                      router.id_hex,
                                                      path)

    def circuit_built(self, circuit):
        "ICircuitListener"
        if circuit.purpose != 'GENERAL':
            return

        path = '->'.join(map(lambda r: r.location.countrycode,
                             circuit.path))
        print "circuit built", circuit.id, path
        for (circid, d, stream_cc) in self.waiting_circuits:
            if circid == circuit.id:
                self.waiting_circuits.remove((circid, d, stream_cc))
                d.callback(circuit)

    def circuit_failed(self, circuit, kw):
        if self.waiting_on(circuit):
            print "A circuit we requested", circuit.id,
            print "has failed. Reason:", kw['REASON']

            circid, d, stream_cc = None, None, None
            for x in self.waiting_circuits:
                if x[0] == circuit.id:
                    circid, d, stream_cc = x
            if d is None:
                raise Exception("Expected to find circuit.")

            self.waiting_circuits.remove((circid, d, stream_cc))
            print "Trying a new circuit build for", circid
            self.request_circuit_build(stream_cc, d)

    def attach_stream(self, stream, circuits):
        """
        IStreamAttacher API
        """
        if stream.target_host not in self.state.addrmap.addr:
            print "No AddrMap entry for", stream.target_host,
            print "so I don't know where it exits; get Tor to attach stream."
            return None

        ip = str(self.state.addrmap.addr[stream.target_host].ip)
        stream_cc = txtorcon.util.NetLocation(ip).countrycode
        print "Stream to", ip, "exiting in", stream_cc

        if stream_cc is None:
            # returning None tells TorState to ask Tor to select a
            # circuit instead
            print "   unknown country, Tor will assign stream"
            return None

        for circ in circuits.values():
            if circ.state != 'BUILT' or circ.purpose != 'GENERAL':
                continue

            circuit_cc = circ.path[-1].location.countrycode
            if circuit_cc is None:
                print "warning: don't know where circuit", circ.id, "exits"

            if circuit_cc == stream_cc:
                print "  found suitable circuit:", circ
                return circ

        # if we get here, we haven't found a circuit that exits in
        # the country GeoIP claims our target server is in, so we
        # need to build one.
        print "Didn't find a circuit, building one"

        # we need to return a Deferred which will callback with our
        # circuit, however built_circuit only callbacks with the
        # message from Tor saying it heard about our request. So when
        # that happens, we push our real Deferred into the
        # waiting_circuits list which will get pop'd at some point
        # when the circuit_built() listener callback happens.

        d = defer.Deferred()
        self.request_circuit_build(stream_cc, d)
        return d

    def request_circuit_build(self, stream_cc, deferred_to_callback):
        # for exits, we can select from any router that's in the
        # correct country.
        last = filter(lambda x: x.location.countrycode == stream_cc,
                      self.state.routers.values())

        # start with an entry guard, put anything in the middle and
        # put one of our exits at the end.
        path = [random.choice(self.state.entry_guards.values()),
                random.choice(self.state.routers.values()),
                random.choice(last)]

        print "  requesting a circuit:", '->'.join(map(lambda r:
                                                       r.location.countrycode,
                                                       path))

        class AppendWaiting:
            def __init__(self, attacher, d, stream_cc):
                self.attacher = attacher
                self.d = d
                self.stream_cc = stream_cc

            def __call__(self, circ):
                """
                return from build_circuit is a Circuit. However, we
                want to wait until it is built before we can issue an
                attach on it and callback to the Deferred we issue
                here.
                """
                print "  my circuit is in progress", circ.id
                self.attacher.waiting_circuits.append((circ.id, self.d,
                                                       self.stream_cc))

        d = self.state.build_circuit(path)
        d.addCallback(AppendWaiting(self, deferred_to_callback, stream_cc))
        d.addErrback(log.err)
        return d


def do_setup(state):
    print "Connected to a Tor version", state.protocol.version

    attacher = MyAttacher(state)
    state.set_attacher(attacher, reactor)
    state.add_circuit_listener(attacher)

    state.add_stream_listener(MyStreamListener())

    print "Existing state when we connected:"
    print "Streams:"
    for s in state.streams.values():
        print ' ', s

    print
    print "General-purpose circuits:"
    for c in filter(lambda x: x.purpose == 'GENERAL', state.circuits.values()):
        print ' ', c.id, '->'.join(map(lambda x: x.location.countrycode,
                                       c.path))


def setup_failed(arg):
    print "SETUP FAILED", arg
    reactor.stop()

d = txtorcon.build_local_tor_connection(reactor)
d.addCallback(do_setup).addErrback(setup_failed)
reactor.run()

schedule_bandwidth.py

Download the example. This is pretty similar to a feature Tor already has and is basically useless as-is since what it does is toggle the amount of relay bandwidth you’re willing to carry from 0 to 20KiB/s every 20 minutes. A slightly-more-entertaining way to illustate config changes. (This is useless because your relay takes at least an hour to appear in the consensus).

#!/usr/bin/env python

# Here, we do something possible-useful and schedule changes to the
# "BandWidthRate" and optionally "BandWidthBurst" settings in Tor.

import datetime
from twisted.internet import reactor
from twisted.internet.interfaces import IReactorTime
from txtorcon import build_local_tor_connection, TorConfig


class BandwidthUpdater:

    def __init__(self, config, scheduler):
        self.bandwidth = 0
        self.config = config
        self.scheduler = IReactorTime(scheduler)
        self.generator = self.next_update()

    def next_update(self):
        """
        Generator that gives out the next time to do a bandwidth update,
        as well as what the new bandwidth value should be. Here, we toggle
        the bandwidth every 20 minutes.
        """

        while True:
            if self.bandwidth:
                self.bandwidth = 0
                self.burst = 0
            else:
                self.bandwidth = 20 * 1024 * 1024
                self.burst = self.bandwidth
            yield (datetime.datetime.now() + datetime.timedelta(minutes=20),
                   self.bandwidth, self.burst)

    def do_update(self):
        x = self.generator.next()
        future = x[0]
        self.new_bandwidth = x[1]
        self.new_burst = x[2]

        tm = (future - datetime.datetime.now()).seconds
        self.scheduler.callLater(tm, self.really_update)
        print "waiting", tm, "seconds to adjust bandwidth"

    def really_update(self):
        print "setting bandwidth + burst to", self.new_bandwidth, self.new_burst
        self.config.set_config('BandWidthBurst', self.new_burst,
                               'BandWidthRate', self.new_bandwidth)
        self.doUpdate()


def setup_complete(conf):
    print "Connected."
    bwup = BandwidthUpdater(conf, reactor)
    bwup.do_update()


def setup_failed(arg):
    print "SETUP FAILED", arg
    reactor.stop()


def bootstrap(proto):
    config = TorConfig(proto)
    config.post_bootstrap.addCallback(setup_complete).addErrback(setup_failed)
    print "Connection is live, bootstrapping config..."


d = build_local_tor_connection(reactor, build_state=False,
                               wait_for_proto=False)
d.addCallback(bootstrap).addErrback(setup_failed)

reactor.run()

dump_config.py

Download the example. Very simple read-only use of txtorcon.TorConfig

#!/usr/bin/env python

# Simple usage example of TorConfig

import sys
import types
from twisted.internet import reactor
from txtorcon import build_local_tor_connection, TorConfig, DEFAULT_VALUE


def setup_complete(config):
    print "Got config"
    keys = config.config.keys()
    keys.sort()
    defaults = []
    for k in keys:
        if k == 'HiddenServices':
            for hs in config.config[k]:
                for xx in ['dir', 'version', 'authorize_client']:
                    if getattr(hs, xx):
                        print 'HiddenService%s %s' % (xx.capitalize(),
                                                      getattr(hs, xx))
                for port in hs.ports:
                    print 'HiddenServicePort', port
            continue

        v = getattr(config, k)
        if isinstance(v, types.ListType):
            for val in v:
                if val != DEFAULT_VALUE:
                    print k, val

        elif v == DEFAULT_VALUE:
            defaults.append(k)

        else:
            print k, v

    if 'defaults' in sys.argv:
        print "Set to default value:"
        for k in defaults:
            print "# %s" % k

    reactor.stop()


def setup_failed(arg):
    print "SETUP FAILED", arg
    reactor.stop()


def bootstrap(c):
    conf = TorConfig(c)
    conf.post_bootstrap.addCallback(setup_complete).addErrback(setup_failed)
    print "Connection is live, bootstrapping state..."


d = build_local_tor_connection(reactor, build_state=False,
                               wait_for_proto=False)
# do not use addCallbacks() here, in case bootstrap has an error
d.addCallback(bootstrap).addErrback(setup_failed)

reactor.run()

monitor.py

Download the example.

Use a plain txtorcon.TorControlProtocol instance to listen for SETEVNET updates. In this case marginally useful, as it listens for logging things INFO, NOTICE, WARN, 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 reactor
import txtorcon


def log(msg):
    print msg


def setup(proto):
    print "Connected to a Tor version", proto.version
    for event in ['INFO', 'NOTICE', 'WARN', 'ERR']:
        proto.add_event_listener(event, log)
    proto.get_info('status/version/current', 'version').addCallback(log)


def setup_failed(arg):
    print "SETUP FAILED", arg
    reactor.stop()

d = txtorcon.build_local_tor_connection(reactor, build_state=False)
d.addCallback(setup).addErrback(setup_failed)
reactor.run()

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 Relay Descriptor
# 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,

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


@inlineCallbacks
def main(reactor):
    proto = yield txtorcon.build_local_tor_connection(reactor, build_state=False)

    or_nickname = "moria1"
    print "Trying to get decriptor information about", or_nickname
    # If the fingerprint is used in place of nickname then, desc/id/<OR identity>
    # should be used.
    descriptor_info = yield proto.get_info('desc/name/' + or_nickname)

    descriptor_info = descriptor_info['desc/name/' + or_nickname]
    try:
        from stem.descriptor.server_descriptor import RelayDescriptor
        relay_info = RelayDescriptor(descriptor_info)
        print "The relay's fingerprint is:", relay_info.fingerprint
        print "Time in UTC when the descriptor was made:", relay_info.published
    except ImportError as e:
        print "Error:", e


if __name__ == '__main__':
    react(main)

circuit_failure_rates.py

Download the example.

#!/usr/bin/env python

#
# This example uses ICircuitListener to monitor how many circuits have
# failed since the monitor started up. If this figure is more than 50%,
# a warning-level message is logged.
#
# Like the :ref:`stream_circuit_logger.py` example, we also log all new
# circuits.
#

import functools
import sys
import time
from twisted.internet import reactor, task
from twisted.python import usage
import txtorcon


class Options(usage.Options):
    """
    command-line options we understand
    """

    optParameters = [
        ['failed', 'f', 0, 'Starting value for number of failed circuits.',
         int],
        ['built', 'b', 0,
         'Starting value for the total number of built cicuits.', int],
        ['connect', 'c', None, 'Tor control socket to connect to in '
         'host:port format, like "localhost:9051" (the default).'],
        ['delay', 'n', 60, 'Seconds to wait between status updates.', int]]

    def __init__(self):
        usage.Options.__init__(self)
        self['guards'] = []
        self.docs['guard'] = 'Specify the name, built and failed rates ' \
            'like "SomeTorNode,10,42". Can be specified multiple times.'

    def opt_guard(self, value):
        name, built, failed = value.split(',')
        self['guards'].append((name, int(built), int(failed)))


class CircuitFailureWatcher(txtorcon.CircuitListenerMixin):

    built_circuits = 0
    failed_circuits = 0
    percent = 0.0
    failed_circuit_ids = []
    per_guard_built = {}
    per_guard_failed = {}

    def print_update(self):
        print time.ctime(reactor.seconds()) + ': ' + self.information()

    def update_percent(self):
        self.percent = 100.0 * (float(self.failed_circuits) /
                                float(self.built_circuits +
                                      self.failed_circuits))
        if self.percent > 50.0:
            print 'WARNING: %02.1f percent of all routes' % self.percent
            print ' have failed: %d failed, %d built' % (self.failed_circuits,
                                                         self.built_circuits)

    def information(self):
        rtn = '%02.1f%% of all circuits' % self.percent
        rtn += 'have failed: %d failed, %d built' % (self.failed_circuits,
                                                     self.built_circuits)
        for g in self.per_guard_built.keys():
            per_guard_percent = 100.0 * (self.per_guard_failed[g] /
                                         (self.per_guard_built[g] +
                                          self.per_guard_failed[g]))
            current = ' '
            for guard in self.state.entry_guards.values():
                if g == guard.name or g == guard.id_hex:
                    current = '*'
                    break
            rtn = rtn + '\n %s %s: %d built, %d failed: %02.1f%%' % \
                (current,
                 g,
                 self.per_guard_built[g],
                 self.per_guard_failed[g],
                 per_guard_percent)
        return rtn

    def circuit_built(self, circuit):
        """ICircuitListener API"""
        # older tor versions will have empty build_flags
        if 'ONEHOP_TUNNEL' in circuit.build_flags:
            return

        if circuit.purpose == 'GENERAL':
            if len(circuit.path) > 0:
                if circuit.path[0] not in self.state.entry_guards.values():
                    print "WEIRD: first circuit hop not in entry guards:",
                    print circuit, circuit.path, circuit.purpose
                    return

            self.built_circuits += 1
            self.update_percent()

            if len(circuit.path) != 3 and len(circuit.path) != 4:
                print "WEIRD: circuit has odd pathlength:",
                print circuit, circuit.path
            try:
                self.per_guard_built[circuit.path[0].unique_name] += 1.0
            except KeyError:
                self.per_guard_built[circuit.path[0].unique_name] = 1.0
                self.per_guard_failed[circuit.path[0].unique_name] = 0.0

    def circuit_failed(self, circuit, kw):
        """ICircuitListener API"""

        if kw['REASON'] != 'MEASUREMENT_EXPIRED':
            return

        # older tor versions will have empty build_flags
        if 'ONEHOP_TUNNEL' in circuit.build_flags:
            return

        if circuit.purpose == 'GENERAL':
            if len(circuit.path) > 1:
                if circuit.path[0] not in self.state.entry_guards.values():
                    # note that single-hop circuits are built for various
                    # internal reasons (and it seems they somtimes use
                    # GENERAL anyway)
                    print "WEIRD: first circuit hop not in entry guards:",
                    print circuit, circuit.path
                    return

            self.failed_circuits += 1
            print "failed", circuit.id
            if circuit.id not in self.failed_circuit_ids:
                self.failed_circuit_ids.append(circuit.id)
            else:
                print "WARNING: duplicate message for", circuit

            if len(circuit.path) > 0:
                try:
                    self.per_guard_failed[circuit.path[0].unique_name] += 1.0
                except KeyError:
                    self.per_guard_failed[circuit.path[0].unique_name] = 1.0
                    self.per_guard_built[circuit.path[0].unique_name] = 0.0

            self.update_percent()


def setup(options, listener, state):
    print 'Connected to a Tor version', state.protocol.version,
    print 'at', state.protocol.transport.addr

    listener.failed_circuits = int(options['failed'])
    listener.built_circuits = int(options['built'])
    listener.state = state  # FIXME use ctor (ditto for options, probably)
    for name, built, failed in options['guards']:
        listener.per_guard_built[name] = float(built)
        listener.per_guard_failed[name] = float(failed)

    for circ in filter(lambda x: x.purpose == 'GENERAL',
                       state.circuits.values()):
        if circ.state == 'BUILT':
            listener.circuit_built(circ)
    state.add_circuit_listener(listener)
    # print an update every minute
    task.LoopingCall(listener.print_update).start(options['delay'])


def setup_failed(arg):
    print "SETUP FAILED", arg
    print arg
    reactor.stop()


options = Options()
try:
    options.parseOptions(sys.argv[1:])
except usage.UsageError:
    print "This monitors circuit failure rates on multi-hop PURPOSE_GENERAL circuits only."
    print "Tor internally uses other circuit types or GENERAL single-hop circuits for"
    print "internal use and we try to ignore these."
    print
    print "Every minute, the summary is printed out. For each entry-guard your Tor is"
    print "currently using, a separate count and summary is printed."
    print
    print "Nothing is saved to disc. If you wish to start again with the same totals"
    print "as a previous run, use the options below. On exit, a command-line suitable"
    print "to do this is printed."
    print
    print options.getUsage()
    sys.exit(-1)


def on_shutdown(listener, *args):
    print '\nTo carry on where you left off, run:'
    print '  %s --failed %d --built %d' % (sys.argv[0],
                                           listener.failed_circuits,
                                           listener.built_circuits),
    for name in listener.per_guard_built.keys():
        print '--guard %s,%d,%d' % (name, listener.per_guard_built[name],
                                    listener.per_guard_failed[name]),
    print

listener = CircuitFailureWatcher()

reactor.addSystemEventTrigger('before', 'shutdown',
                              functools.partial(on_shutdown, listener))

if options['connect']:
    host, port = options['connect'].split(':')
    port = int(port)
    print 'Connecting to %s:%i...' % (host, port)
    d = txtorcon.build_local_tor_connection(reactor, host=host, port=port)
else:
    d = txtorcon.build_local_tor_connection(reactor)
d.addCallback(functools.partial(setup, options, listener))
d.addErrback(setup_failed)

reactor.run()

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)