[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [ooni-probe/master] * Refactored bridget completely.
commit 6f52ec0c73671e5b1c417cc1b7a0d63266cd8b9c
Author: Isis Lovecruft <isis@xxxxxxxxxxxxxx>
Date: Wed Sep 19 02:04:55 2012 +0000
* Refactored bridget completely.
* Broke up most of experiment() into tiny functions.
* Added custom exceptions and error handlers.
* Added more commandline option type enforcers, with custom handlers.
* Function to drop any privileges if using pluggable transports.
* The generated torrc now includes a Bridge line written to the file
descriptor, if using bridges.
* The Bridge line properly handles pluggable transports.
TODO: Still need to fix the CustomCircuit class and the attacher.
---
ooni/plugins/bridget.py | 652 ++++++++++++++++++++++++++---------------------
ooni/utils/circuit.py | 50 ++--
2 files changed, 380 insertions(+), 322 deletions(-)
diff --git a/ooni/plugins/bridget.py b/ooni/plugins/bridget.py
index f22741b..1ed4957 100644
--- a/ooni/plugins/bridget.py
+++ b/ooni/plugins/bridget.py
@@ -3,77 +3,163 @@
#
# +-----------+
# | BRIDGET |
-# | +----------------------------------------------+
-# +--------| Use a slave Tor process to test making a Tor |
-# | connection to a list of bridges or relays. |
-# +----------------------------------------------+
+# | +--------------------------------------------+
+# +--------| Use a Tor process to test making a Tor |
+# | connection to a list of bridges or relays. |
+# +--------------------------------------------+
#
-# :authors: Arturo Filasto, Isis Lovecruft
+# :authors: Isis Lovecruft, Arturo Filasto
# :licence: see included LICENSE
# :version: 0.1.0-alpha
-from __future__ import with_statement
-from functools import wraps
-from os import getcwd
-from os.path import isfile
-from os.path import join as pj
-from twisted.python import usage
-from twisted.plugin import IPlugin
-from twisted.internet import defer, error, reactor
-from zope.interface import implements
+from __future__ import with_statement
+from functools import wraps, partial
+from twisted.python import usage
+from twisted.plugin import IPlugin
+from twisted.internet import defer, error, reactor
+from twisted.internet.endpoints import TCP4ClientEndpoint
+from zope.interface import implements
+from ooni.utils import log
+from ooni.plugoo.tests import ITest, OONITest
+from ooni.plugoo.assets import Asset
+
+import tempfile
+import os
import random
+import shutil
import signal
import sys
-from ooni.utils import log
-from ooni.plugoo.tests import ITest, OONITest
-from ooni.plugoo.assets import Asset
-
-
-def portCheck(number):
- number = int(number)
- if number not in range(1024, 65535):
- raise ValueError("Port out of range")
-
-portCheckAllowed = "must be between 1024 and 65535."
-sockCheck, ctrlCheck = portCheck, portCheck
-sockCheck.coerceDoc = "Port to use for Tor's SocksPort, " + portCheckAllowed
-ctrlCheck.coerceDoc = "Port to use for Tor's ControlPort, " + portCheckAllowed
-
+def timer(secs, e=None):
+ def decorator(func):
+ def _timer(signum, frame):
+ raise TimeoutError, e
+ def wrapper(*args, **kwargs):
+ signal.signal(signal.SIGALRM, _timer)
+ signal.alarm(secs)
+ try:
+ res = func(*args, **kwargs)
+ finally:
+ signal.alarm(0)
+ return res
+ return wraps(func)(wrapper)
+ return decorator
+
+
+class MissingAssetException(Exception):
+ """Raised when neither there are neither bridges nor relays to test."""
+ def __init__(self):
+ log.msg("Bridget can't run without bridges or relays to test!")
+ return sys.exit()
+
+class PTNoBridgesException(Exception):
+ """Raised when a pluggable transport is specified, but no bridges."""
+ def __init__(self):
+ log.msg("Pluggable transport requires the bridges option")
+ return sys.exit()
+
+class PTNotFoundException(Exception):
+ def __init__(self, transport_type):
+ m = "Pluggable Transport type %s was unaccounted " % transport_type
+ m += "for, please contact isis(at)torproject(dot)org and it will "
+ m += "get included."
+ log.msg("%s" % m)
+ return sys.exit()
+
+class ValueChecker(object):
+ def port_check(self, number):
+ """Check that given ports are in the allowed range."""
+ number = int(number)
+ if number not in range(1024, 65535):
+ raise ValueError("Port out of range")
+
+ sock_check, ctrl_check = port_check, port_check
+ port_check_allowed = "must be between 1024 and 65535."
+ sock_check.coerceDoc = "Port to use for Tor's SocksPort, " + port_check_allowed
+ ctrl_check.coerceDoc = "Port to use for Tor's ControlPort, " + port_check_allowed
+
+ def uid_check(pluggable_transport):
+ """Check that we are not root when trying to use pluggable transports."""
+ uid, gid = os.getuid(), os.getgid()
+ if uid == 0 and gid == 0:
+ log.msg("Error: Running bridget as root with --transport=%s not allowed."
+ % pluggable_transport)
+ log.msg("Dropping privileges to normal user...")
+ os.setgid(1000)
+ os.setuid(1000)
+
+ def dir_check(d):
+ """Check that the given directory exists."""
+ if not os.isdir(d):
+ raise ValueError("%s doesn't exist, or has wrong permissions" % d)
+
+ def file_check(f):
+ if not os.isfile(f):
+ raise ValueError("%s does not exist, or has wrong permissions" % f)
+
+class RandomPortException(Exception):
+ """Raised when using a random port conflicts with configured ports."""
+ def __init__(self):
+ log.msg("Unable to use random and specific ports simultaneously")
+ return sys.exit()
+
+class TimeoutError(Exception):
+ """Raised when a timer runs out."""
+ pass
+
+class TxtorconImportError(ImportError):
+ """Raised when /ooni/lib/txtorcon cannot be imported from."""
+ cwd, tx = os.getcwd(), 'lib/txtorcon/torconfig.py'
+ try:
+ log.msg("Unable to import from ooni.lib.txtorcon")
+ if cwd.endswith('ooni'):
+ check = os.path.join(cwd, tx)
+ else:
+ check = os.path.join(cwd, 'ooni/'+tx)
+ assert isfile(check)
+ except:
+ log.msg("Error: Some OONI libraries are missing!")
+ log.msg("Please go to /ooni/lib/ and do \"make all\"")
class BridgetArgs(usage.Options):
+ """Commandline options."""
+ vc = ValueChecker()
+
optParameters = [
['bridges', 'b', None,
'File listing bridge IP:ORPorts to test'],
['relays', 'f', None,
'File listing relay IPs to test'],
- ['socks', 's', 9049, None, portCheck],
- ['control', 'c', 9052, None, portCheck],
+ ['socks', 's', 9049, None, vc.sock_check],
+ ['control', 'c', 9052, None, vc.ctrl_check],
['torpath', 'p', None,
'Path to the Tor binary to use'],
['datadir', 'd', None,
'Tor DataDirectory to use'],
- ['transport', 't', None,
+ ['transport', 't', None,
'Tor ClientTransportPlugin'],
['resume', 'r', 0,
'Resume at this index']]
- optFlags = [
- ['random', 'x', 'Use random ControlPort and SocksPort']]
+ optFlags = [['random', 'x', 'Use random ControlPort and SocksPort']]
def postOptions(self):
- if self['transport'] and not self['bridges']:
- e = "Pluggable transport requires the bridges option"
- raise usage.UsageError, e
- if self['socks'] and self['control']:
+ if not self['bridges'] and not self['relays']:
+ raise MissingAssetException
+ if self['transport']:
+ vc.uid_check(self['transport'])
+ if not self['bridges']:
+ raise PTNoBridgesException
+ if self['socks'] or self['control']:
if self['random']:
- e = "Unable to use random and specific ports simultaneously"
- raise usage.usageError, e
+ raise RandomPortException
+ if self['datadir']:
+ vc.dir_check(self['datadir'])
+ if self['torpath']:
+ vc.file_check(self['torpath'])
class BridgetAsset(Asset):
- """
- Class for parsing bridget Assets ignoring commented out lines.
- """
+ """Class for parsing bridget Assets ignoring commented out lines."""
def __init__(self, file=None):
self = Asset.__init__(self, file)
@@ -89,17 +175,15 @@ class BridgetTest(OONITest):
:ivar config:
An :class:`ooni.lib.txtorcon.TorConfig` instance.
- :ivar relay_list:
- A list of all provided relays to test. We have to do this because
- txtorcon.TorState().entry_guards won't build a custom circuit if the
- first hop isn't in the torrc's EntryNodes.
- :ivar bridge_list:
+ :ivar relays:
+ A list of all provided relays to test.
+ :ivar bridges:
A list of all provided bridges to test.
:ivar socks_port:
Integer for Tor's SocksPort.
:ivar control_port:
Integer for Tor's ControlPort.
- :ivar plug_transport:
+ :ivar transport:
String defining the Tor's ClientTransportPlugin, for testing
a bridge's pluggable transport functionality.
:ivar tor_binary:
@@ -116,99 +200,83 @@ class BridgetTest(OONITest):
def initialize(self):
"""
Extra initialization steps. We only want one child Tor process
- running, so we need to deal with the creation of TorConfig() only
- once, before the experiment runs.
+ running, so we need to deal with most of the TorConfig() only once,
+ before the experiment runs.
"""
self.socks_port = 9049
self.control_port = 9052
+ self.circuit_timeout = 90
self.tor_binary = '/usr/sbin/tor'
self.data_directory = None
- self.circuit_timeout = 90
+ self.use_pt = False
+ self.pt_type = None
+
+ self.bridges, self.bridges_up, self.bridges_down = ([] for i in range(3))
+ self.bridges_remaining = lambda: len(self.bridges)
+ self.bridges_down_count = lambda: len(self.bridges_down)
+ self.current_bridge = None
+
+ self.relays, self.relays_up, self.relays_down = ([] for i in range(3))
+ self.relays_remaining = lambda: len(self.relays)
+ self.relays_down_count = lambda: len(self.relays_down)
+ self.current_relay = None
+
+ def make_asset_list(opt, lst):
+ log.msg("Loading information from %s ..." % opt)
+ with open(opt) as opt_file:
+ for line in opt_file.readlines():
+ if line.startswith('#'):
+ continue
+ else:
+ lst.append(line.replace('\n',''))
if self.local_options:
- options = self.local_options
-
- if not options['bridges'] and not options['relays']:
- self.suicide = True
- return
-
try:
from ooni.lib.txtorcon import TorConfig
except ImportError:
- log.msg ("Bridget: Unable to import from ooni.lib.txtorcon")
- wd, tx = getcwd(), 'lib/txtorcon/torconfig.py'
- chk = pj(wd,tx) if wd.endswith('ooni') else pj(wd,'ooni/'+tx)
- try:
- assert isfile(chk)
- except:
- log.msg("Error: Some OONI libraries are missing!")
- log.msg("Please go to /ooni/lib/ and do \"make all\"")
+ raise TxtorconImportError
- self.config = TorConfig()
+ options = self.local_options
+ config = self.config = TorConfig()
if options['bridges']:
self.config.UseBridges = 1
-
+ make_asset_list(options['bridges'], self.bridges)
if options['relays']:
- ## Stupid hack for testing only relays:
- ## Tor doesn't use EntryNodes when UseBridges is enabled, but
- ## config.state.entry_guards needs to include the first hop to
- ## build a custom circuit.
+ ## first hop must be in TorState().entry_guards to build circuits
self.config.EntryNodes = ','.join(relay_list)
-
+ make_asset_list(options['relays'], self.relays)
if options['socks']:
self.socks_port = options['socks']
-
if options['control']:
self.control_port = options['control']
-
if options['random']:
log.msg("Using randomized ControlPort and SocksPort ...")
self.socks_port = random.randint(1024, 2**16)
self.control_port = random.randint(1024, 2**16)
-
if options['torpath']:
self.tor_binary = options['torpath']
- if options['datadir']:
- self.config.DataDirectory = options['datadir']
+ if self.local_options['datadir']:
+ self.data_directory = local_options['datadir']
+ else:
+ self.data_directory = None
if options['transport']:
- ## ClientTransportPlugin transport socks4|socks5 IP:PORT
+ self.use_pt = True
+ log.msg("Using ClientTransportPlugin %s" % options['transport'])
+ [self.pt_type, pt_exec] = options['transport'].split(' ', 1)
+
## ClientTransportPlugin transport exec path-to-binary [options]
- if not options['bridges']:
- e = "You must use the bridge option to test a transport."
- raise usage.UsageError("%s" % e)
+ ## XXX we need a better way to deal with all PTs
+ if self.pt_type == "obfs2":
+ config.ClientTransportPlugin = self.pt_type + " " + pt_exec
else:
- ## XXX fixme there's got to be a better way to check the exec
- ##
- ## we could use:
- ## os.setgid( NEW_GID )
- ## os.setuid( NEW_UID )
- ## to drop any and all privileges
- assert type(options['transport']) is str
- [self.pt_type,
- self.pt_exec] = options['transport'].split(' ', 1)
- log.msg("Using ClientTransportPlugin %s %s"
- % (self.pt_type, self.pt_exec))
- self.pt_use = True
-
- ## XXX fixme we need a better way to deal with all PTs
- if self.pt_type == "obfs2":
- self.ctp = self.pt_type +" "+ self.pt_exec
- else:
- m = "Pluggable Transport type %s was " % self.pt_type
- m += "unaccounted for, please contact isis (at) "
- m += "torproject (dot) org, with info and it'll get "
- m += "included."
- log.msg("%s" % m)
- self.ctp = None
- else:
- self.pt_use = False
+ raise PTNotFoundException
- self.config.SocksPort = self.socks_port
- self.config.ControlPort = self.control_port
- self.config.save()
+ config.SocksPort = self.socks_port
+ config.ControlPort = self.control_port
+ config.CookieAuthentication = 1
def load_assets(self):
"""
@@ -216,32 +284,14 @@ class BridgetTest(OONITest):
should be given in the form IP:ORport. We don't want to load these as
assets, because it's inefficient to start a Tor process for each one.
"""
- assets = {}
- self.bridge_list = []
- self.relay_list = []
-
- ## XXX fix me
- ## we should probably find a more memory nice way to load addresses,
- ## in case the files are really large
+ assets = {}
if self.local_options:
-
- def make_asset_list(opt, lst):
- log.msg("Loading information from %s ..." % opt)
- with open(opt) as opt_file:
- for line in opt_file.readlines():
- if line.startswith('#'):
- continue
- else:
- lst.append(line.replace('\n',''))
-
if self.local_options['bridges']:
- make_asset_list(self.local_options['bridges'],
- self.bridge_list)
- assets.update({'bridges': self.bridge_list})
+ assets.update({'bridge':
+ BridgetAsset(self.local_options['bridges'])})
if self.local_options['relays']:
- make_asset_list(self.local_options['relays'],
- self.relay_list)
- assets.update({'relays': self.relay_list})
+ assets.update({'relay':
+ BridgetAsset(self.local_options['relays'])})
return assets
def experiment(self, args):
@@ -249,55 +299,69 @@ class BridgetTest(OONITest):
XXX fill me in
:param args:
- The :class:`ooni.plugoo.asset.Asset <Asset>` line currently being
- used.
- :meth launch_tor:
- Returns a Deferred which callbacks with a
- :class:`ooni.lib.txtorcon.torproto.TorProcessProtocol
- <TorProcessProtocol>` connected to the fully-bootstrapped Tor;
- this has a :class:`ooni.lib.txtorcon.torcontol.TorControlProtocol
- <TorControlProtocol>` instance as .protocol.
+ The :class:`BridgetAsset` line currently being used.
"""
try:
- from ooni.lib.txtorcon import CircuitListenerMixin, IStreamAttacher
- from ooni.lib.txtorcon import TorProtocolFactory, TorConfig, TorState
- from ooni.lib.txtorcon import DEFAULT_VALUE, launch_tor
+ from ooni.utils import circuit
+ from ooni.lib.txtorcon import TorProcessProtocol
+ from ooni.lib.txtorcon import TorProtocolFactory
+ from ooni.lib.txtorcon import TorConfig, TorState
except ImportError:
- log.msg("Error: Unable to import from ooni.lib.txtorcon")
- wd, tx = getcwd(), 'lib/txtorcon/torconfig.py'
- chk = pj(wd,tx) if wd.endswith('ooni') else pj(wd,'ooni/'+tx)
- try:
- assert isfile(chk)
- except:
- log.msg("Error: Some OONI libraries are missing!")
- log.msg(" Please go to /ooni/lib/ and do \"make all\"")
- return sys.exit()
+ raise TxtorconImportError
+ except TxtorconImportError:
+ ## XXX is this something we should add to the reactor?
+ sys.exit()
def bootstrap(ctrl):
"""
Launch a Tor process with the TorConfig instance returned from
- initialize().
+ initialize() and write_torrc().
"""
conf = TorConfig(ctrl)
conf.post_bootstrap.addCallback(setup_done).addErrback(setup_fail)
log.msg("Tor process connected, bootstrapping ...")
- def reconf_controller(conf, bridge):
- ## if bridges and relays, use one bridge then build a circuit
- ## from three relays
- conf.Bridge = ""
- ## XXX do we need a SIGHUP to restart?
+ def delete_temp(delete_list):
+ """
+ Given a list of files or directories to delete, delete all and
+ suppress all errors.
+ """
+ for temp in delete_list:
+ try:
+ os.unlink(temp)
+ except OSError:
+ shutil.rmtree(temp, ignore_errors=True)
+
+ @timer(self.circuit_timeout)
+ def reconfigure_bridge(state, bridge, use_pt=False, pt_type=None):
+ """Rewrite the Bridge line in our torrc."""
+ log.msg("Current Bridge: %s" % bridge)
+ if use_pt is False:
+ new = state.protocol.set_conf('Bridge', bridge)
+ elif use_pt and pt_type is not None:
+ new = state.protocol.set_conf('Bridge', pt_type +' '+ bridge)
+ else:
+ raise PTNotFoundException
+ return new
- ## XXX see txtorcon.TorControlProtocol.add_event_listener we
- ## may not need full CustomCircuit class
+ def reconfigure_fail(*param):
+ log.msg("Reconfiguring TorConfig with parameters %s failed" % param)
+ reactor.stop()
- ## if bridges only, try one bridge at a time, but don't build
- ## circuits, just return
- ## if relays only, build circuits from relays
+ def remove_relays(state, bridge_list):
+ """
+ Remove bridges from our bridge list which are also listed as
+ public relays.
+ """
+ ips = map(lambda addr: addr.split(':',1)[0], bridge_list)
+ both = set(state.relays.values()).intersection(ips)
+ if len(both) > 0:
+ for bridge in both:
+ bridge_list.remove(bridge)
+ return state
- def reconf_fail(args):
- log.msg("Reconfiguring Tor config with args %s failed" % args)
- reactor.stop()
+ def remove_relays_fail(state):
+ log.msg("Unable to remove public relays from the bridge list.")
def setup_fail(args):
log.msg("Setup Failed.")
@@ -310,152 +374,148 @@ class BridgetTest(OONITest):
state.post_bootstrap.addCallback(state_complete).addErrback(setup_fail)
report.update({'success': args})
+ def start_tor(reactor, update, torrc, to_delete, control_port,
+ tor_binary, data_directory):
+ """
+ Create a TCP4ClientEndpoint at our control_port, and connect
+ it to our reactor and a spawned Tor process. Compare with
+ :meth:`txtorcon.launch_tor` for differences.
+ """
+ ## XXX do we need self.reactor?
+ end_point = TCP4ClientEndpoint(reactor, 'localhost', control_port)
+ connection_creator = partial(end_point.connect, TorProtocolFactory())
+ process_protocol = TorProcessProtocol(connection_creator, updates)
+ process_protocol.to_delete = to_delete
+ reactor.addSystemEventTrigger('before', 'shutdown',
+ partial(delete_temp, to_delete))
+ try:
+ transport = reactor.spawnProcess(process_protocol,
+ tor_binary,
+ args=(tor_binary,'-f',torrc),
+ env={'HOME': data_directory},
+ path=data_directory)
+ transport.closeStdin()
+ except RuntimeError, e:
+ process_protocol.connected_cb.errback(e)
+ finally:
+ return process_protocol.connected_cb
+
+ def state_complete(state):
+ """Called when we've got a TorState."""
+ log.msg("We've completely booted up a Tor version %s at PID %d"
+ % (state.protocol.version, state.tor_pid))
+
+ log.msg("This Tor has the following %d Circuits:"
+ % len(state.circuits))
+ for circ in state.circuits.values():
+ log.msg("%s" % circ)
+
+ return state
+
+ def state_attach(state, relay_list):
+ log.msg("Setting up custom circuit builder...")
+ attacher = CustomCircuit(state)
+ state.set_attacher(attacher, reactor)
+ state.add_circuit_listener(attacher)
+
+ for circ in state.circuits.values():
+ for relay in circ.path:
+ try:
+ relay_list.remove(relay)
+ except KeyError:
+ continue
+ ## XXX how do we attach to circuits with bridges?
+ d = defer.Deferred()
+ attacher.request_circuit_build(d)
+ return d
+
def updates(prog, tag, summary):
log.msg("%d%%: %s" % (prog, summary))
- if not self.circuit_timeout:
- self.circuit_timeout = 90
-
- class TimeoutError(Exception):
- pass
-
- def stupid_timer(secs, e=None):
- def decorator(func):
- def _timer(signum, frame):
- raise TimeoutError, e
- def wrapper(*args, **kwargs):
- signal.signal(signal.SIGALRM, _timer)
- signal.alarm(secs)
- try:
- res = func(*args, **kwargs)
- finally:
- signal.alarm(0)
- return res
- return wraps(func)(wrapper)
- return decorator
+ def write_torrc(conf, data_dir=None):
+ """
+ Create a torrc in our data_directory. If we don't yet have a
+ data_directory, create a temporary one. Any temporary files or
+ folders are added to delete_list.
+
+ :return: delete_list, data_dir, torrc
+ """
+ delete_list = []
+
+ if data_dir is None:
+ data_dir = tempfile.mkdtemp(prefix='bridget-tordata')
+ delete_list.append(data_dir)
+ conf.DataDirectory = data_dir
+ #conf.__OwningControllerProcess = os.getpid()
+
+ (fd, torrc) = tempfile.mkstemp(dir=data_dir)
+ delete_list.append(torrc)
+ os.write(fd, conf.create_torrc())
+ os.close(fd)
+ return torrc, data_dir, delete_list
- @stupid_timer(self.circuit_timeout)
- def _launch_tor_and_report_bridge_status(_config,
- _reactor,
- _updates,
- _binary,
- _callback,
- _callback_arg,
- _errback):
- """The grossest Python function you've ever seen."""
-
- log.msg("Starting Tor ...")
- ## :return: a Deferred which callbacks with a TorProcessProtocol
- ## connected to the fully-bootstrapped Tor; this has a
- ## txtorcon.TorControlProtocol instance as .protocol.
- proc_proto = launch_tor(_config, _reactor, progress_updates=_updates,
- tor_binary=_binary).addCallback(_callback,
- _callback_arg).addErrback(_errback)
- return proc_proto
- ## now build circuits
-
-
-
- if len(args) == 0:
- log.msg("Bridget can't run without bridges or relays to test!")
- log.msg("Exiting ...")
- return sys.exit()
- else:
- log.msg("Bridget: initiating test ... ")
- self.london_bridge = []
- self.working_bridge = []
- if len(self.bridge_list) >= 1:
- self.untested_bridge_count = len(self.bridge_list)
- self.burnt_bridge_count = len(self.london_bridge)
- self.current_bridge = None
+ log.msg("Bridget: initiating test ... ")
+
+ while self.bridges_remaining() > 0:
+ self.current_bridge = self.bridges.pop()
+ #current_ip = self.current_bridge.split(':', 1)[0]
+ #print "CURRENT BRIDGE IP %s" % current_ip
+
+ if not self.config.config.has_key('Bridge'):
+ self.config.Bridge = self.current_bridge
+ (torrc, self.data_directory, to_delete) = write_torrc(
+ self.config, self.data_directory)
+
+ log.msg("Starting Tor ...")
+ log.msg("Using the following as our torrc:\n%s"
+ % self.config.create_torrc())
+ report = {'tor_config': self.config.create_torrc()}
+
+ state = start_tor(reactor, updates, torrc, to_delete,
+ self.control_port, self.tor_binary,
+ self.data_directory)
+ state.addCallback(setup_done)
+ state.addErrback(setup_fail)
+ state.addCallback(remove_relays, self.bridges)
+ state.addErrback(remove_relays_fail)
+ state.addCallback(state_attach, self.bridges)
+ ## XXX write state_attach_fail()
+ state.addErrback(state_attach_fail)
- ## while bridges are in the bucket
- while self.config.UseBridges and self.untested_bridge_count > 0:
+ ## XXX see txtorcon.TorControlProtocol.add_event_listener we
+ ## may not need full CustomCircuit class
+ ## o if bridges and relays, use one bridge then build a circuit
+ ## from three relays
+ ## o if bridges only, try one bridge at a time, but don't build
+ ## circuits, just return
+ ## o if relays only, build circuits from relays
+ else:
+ log.msg("We now have %d untested bridges..."
+ % self.bridges_remaining())
try:
- ## if the user wanted to use pluggable transports
- assert self.pt_use is True
- except AssertionError as no_use_pt:
- ## if the user didn't want to use pluggable transports
- log.msg("Configuring Bridge line without pluggable transport")
- log.msg("%s" % no_use_pt)
- self.pt_good = False
+ state.addCallback(reconfigure_bridge, self.current_bridge,
+ self.use_pt, self.pt_type)
+ state.addErrback(reconfigure_fail)
+ except TimeoutError:
+ log.msg("Adding %s to unreachable bridges..."
+ % self.current_bridge)
+ self.bridges_down.append(self.current_bridge)
else:
- ## user wanted PT, and we recognized transport
- if self.ctp is not None:
- try:
- assert self.ctp is str
- except AssertionError as not_a_string:
- log.msg("Error: ClientTransportPlugin string unusable: %s"
- % not_a_string)
- log.msg(" Exiting ...")
- sys.exit()
- else:
- ## configure the transport
- self.config.ClientTransportPlugin = self.ctp
- self.pt_good = True
- ## user wanted PT, but we didn't recognize it
- else:
- log.msg("Error: Unable to use ClientTransportPlugin %s %s"
- % (self.pt_type, self.pt_exec))
- log.msg(" Exiting...")
- sys.exit()
- ## whether or not we're using a pluggable transport, we need
- ## to set the Bridge line
- finally:
- log.msg("We now have %d bridges in our list..."
- % self.untested_bridge_count)
- self.current_bridge = self.bridge_list.pop()
- self.untested_bridge_count -= 1
-
- log.msg("Current Bridge: %s" % self.current_bridge)
-
- if self.pt_good:
- self.config.Bridge = self.pt_type +" "+ self.current_bridge
- else:
- self.config.Bridge = self.current_bridge
-
- log.msg("Using the following as our torrc:\n%s"
- % self.config.create_torrc())
- report = {'tor_config': self.config.create_torrc()}
- self.config.save()
+ log.msg("Adding %s to reachable bridges..."
+ % self.current_bridge)
+ self.bridges_up.append(self.current_bridge)
- try:
- _launch_tor_and_report_bridge_status(self.config,
- reactor,
- updates,
- self.tor_binary,
- bootstrap,
- self.config,
- setup_fail)
- except TimeoutError:
- log.msg("Adding %s to bad bridges..." % self.current_bridge)
- self.london_bridge.append(self.current_bridge)
- else:
- log.msg("Adding %s to good bridges..." % self.current_bridge)
- self.working_bridge.append(self.current_bridge)
+ reactor.run()
+ ## now build circuits
- d = defer.Deferred()
- d.addCallback(log.msg, 'Working Bridges:\n%s\nUnreachable Bridges:\n%s\n'
- % (self.working_bridge, self.london_bridge))
- return d
-
- def control(self, exp_res):
- exp_res
## So that getPlugins() can register the Test:
bridget = BridgetTest(None, None, None)
## ISIS' NOTES
## -----------
-## self.config.save() only needs to be called if Tor is already running.
-##
-## to test gid, uid, and euid:
-## with open('/proc/self/state') as uidfile:
-## print uidfile.read(1000)
-##
## TODO:
-## o add option for any kwarg=arg self.config setting
## o cleanup documentation
## x add DataDirectory option
## o check if bridges are public relays
diff --git a/ooni/utils/circuit.py b/ooni/utils/circuit.py
index bb1ab11..0c77dd1 100644
--- a/ooni/utils/circuit.py
+++ b/ooni/utils/circuit.py
@@ -13,6 +13,9 @@
from zope.interface import implements
from ooni.lib.txtorcon import CircuitListenerMixin, IStreamAttacher
+from ooni.utils import log
+
+import random
class CustomCircuit(CircuitListenerMixin):
@@ -28,7 +31,7 @@ class CustomCircuit(CircuitListenerMixin):
def waiting_on(self, circuit):
for (circid, d) in self.waiting_circuits:
if circuit.id == circid:
- return true
+ return True
return False
def circuit_extend(self, circuit, router):
@@ -36,8 +39,7 @@ class CustomCircuit(CircuitListenerMixin):
if circuit.purpose != 'GENERAL':
return
if self.waiting_on(circuit):
- log.msg("Circuit %d (%s)"
- % (circuit.id, router.id_hex))
+ log.msg("Circuit %d (%s)" % (circuit.id, router.id_hex))
def circuit_built(self, circuit):
"ICircuitListener"
@@ -47,7 +49,7 @@ class CustomCircuit(CircuitListenerMixin):
log.msg("Full path of %s: %s" % (circuit.id, circuit.path))
for (circid, d) in self.waiting_circuits:
if circid == circuit.id:
- self.waiting_circuits.remove(circid, d)
+ self.waiting_circuits.remove((circid, d))
d.callback(circuit)
def circuit_failed(self, circuit, reason):
@@ -57,7 +59,7 @@ class CustomCircuit(CircuitListenerMixin):
circid, d = None, None
for x in self.waiting_circuits:
if x[0] == circuit.id:
- circid, d, stream_cc = x
+ circid, d = x
if d is None:
raise Exception("Expected to find circuit.")
@@ -71,36 +73,32 @@ class CustomCircuit(CircuitListenerMixin):
TorInfo.dump(self)
def request_circuit_build(self, deferred):
- entries = self.state.entry_guards.value()
- relays = self.state.routers.values()
- log.msg("We have these nodes listed as entry guards:")
- log.msg("%s" % entries)
- log.msg("We have these nodes listed as relays:")
- log.msg("%s" % relays)
- path = [random.choice(entries),
- random.choice(relays),
- random.choice(relays)]
+ if self.state.relays_remaining() > 0:
+ first, middle,last = (self.state.relays.pop()
+ for i in range(3))
+ else:
+ first = random.choice(self.state.entry_guards.values())
+ middle, last = (random.choice(self.state.routers.values())
+ for i in range(2))
+ path = [first, middle, last]
+
log.msg("Requesting a circuit: %s"
- % '-->'.join(map(lambda x: x.location.countrycode,
- path)))
+ % '->'.join(map(lambda node: node, path)))
class AppendWaiting:
def __init__(self, attacher, deferred):
self.attacher = attacher
self.d = deferred
-
- def __call__(self, circuit):
+ 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.
"""
- log.msg("Circuit %s is in progress ..." % circuit.id)
- self.attacher.waiting_circuits.append((circuit.id,
- self.d))
-
- fin = self.state.build_circuit(path)
- fin.addCallback(AppendWaiting(self, deferred_to_callback))
- fin.addErrback(log.err)
- return fin
+ log.msg("Circuit %s is in progress ..." % circ.id)
+ self.attacher.waiting_circuits.append((circ.id, self.d))
+
+ return self.state.build_circuit(path).addCallback(
+ AppendWaiting(self, deferred_to_callback)).addErrback(
+ log.err)
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits