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
¶
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
¶
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
¶
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
¶
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
¶
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
¶
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)