commit eccaec864777ea75dfea5f48db63468cc378a6c4 Author: Arturo Filastò art@fuffa.org Date: Mon Mar 24 15:21:13 2014 +0100
Add support for adding tor version to the bridge_reachability report.
Fail with a good error code if the version is unsupported for the given bridge. --- ooni/director.py | 4 ++- ooni/managers.py | 7 ++-- ooni/nettests/blocking/bridge_reachability.py | 33 +++++++++---------- ooni/tests/test_onion.py | 8 +++++ ooni/utils/__init__.py | 3 -- ooni/utils/onion.py | 43 +++++++++++++++++++++++++ 6 files changed, 74 insertions(+), 24 deletions(-)
diff --git a/ooni/director.py b/ooni/director.py index ada1b82..b95639a 100644 --- a/ooni/director.py +++ b/ooni/director.py @@ -208,7 +208,6 @@ class Director(object): self.activeNetTests.remove(net_test) if len(self.activeNetTests) == 0: self.allTestsDone.callback(None) - self.allTestsDone = defer.Deferred()
@defer.inlineCallbacks def startNetTest(self, net_test_loader, reporters): @@ -219,6 +218,9 @@ class Director(object): net_test_loader: an instance of :class:ooni.nettest.NetTestLoader """ + if self.allTestsDone.called: + self.allTestsDone = defer.Deferred() + if config.privacy.includepcap: if not config.reports.pcap: config.reports.pcap = config.generate_pcap_filename(net_test_loader.testDetails) diff --git a/ooni/managers.py b/ooni/managers.py index f6af282..cd05d42 100644 --- a/ooni/managers.py +++ b/ooni/managers.py @@ -30,6 +30,8 @@ class TaskManager(object): to be re-run once all the currently scheduled tasks have run. """ log.err("Task %s has failed %s times" % (task, task.failures)) + if config.advanced.debug: + log.exception(failure)
self._active_tasks.remove(task) self.failures = self.failures + 1 @@ -81,10 +83,11 @@ class TaskManager(object): """ self._active_tasks.remove(task)
- self._fillSlots() - # Fires the done deferred when the task has completed task.done.callback(result) + + self._fillSlots() + self.succeeded(result, task)
@property diff --git a/ooni/nettests/blocking/bridge_reachability.py b/ooni/nettests/blocking/bridge_reachability.py index 892ed02..9e6bb9e 100644 --- a/ooni/nettests/blocking/bridge_reachability.py +++ b/ooni/nettests/blocking/bridge_reachability.py @@ -9,7 +9,7 @@ from twisted.internet import defer, reactor, error
import txtorcon
-from ooni.utils import log +from ooni.utils import log, onion from ooni import nettest
class UsageOptions(usage.Options): @@ -35,8 +35,11 @@ class BridgeReachability(nettest.NetTestCase): self.tor_progress = 0 self.timeout = int(self.localOptions['timeout'])
+ self.report['error'] = None + self.report['success'] = None self.report['timeout'] = self.timeout self.report['transport_name'] = 'vanilla' + self.report['tor_version'] = str(onion.tor_details['version']) self.report['tor_progress'] = 0 self.report['tor_progress_tag'] = None self.report['tor_progress_summary'] = None @@ -90,36 +93,30 @@ class BridgeReachability(nettest.NetTestCase): print "Failing bridges: %s" % failing_bridges
def test_full_tor_connection(self): - def getTransport(address): - """ - If the address of the bridge starts with a valid c identifier then - we consider it to be a bridge. - Returns: - The transport_name if it's a transport. - None if it's not a obfsproxy bridge. - """ - transport_name = address.split(' ')[0] - transport_name_chars = string.ascii_letters + string.digits - if all(c in transport_name_chars for c in transport_name): - return transport_name - else: - return None - config = txtorcon.TorConfig() config.ControlPort = random.randint(2**14, 2**16) config.SocksPort = random.randint(2**14, 2**16)
- transport_name = getTransport(self.bridge) + transport_name = onion.transport_name(self.bridge) if transport_name and self.pyobfsproxy_bin: config.ClientTransportPlugin = "%s exec %s managed" % (transport_name, self.pyobfsproxy_bin) self.report['transport_name'] = transport_name self.report['bridge_address'] = self.bridge.split(' ')[1] elif transport_name and not self.pyobfsproxy_bin: log.err("Unable to test bridge because pyobfsproxy is not installed") - self.report['success'] = None + self.report['error'] = 'missing-pyobfsproxy' return else: self.report['bridge_address'] = self.bridge.split(' ')[0] + + if transport_name and transport_name == 'scramblesuit' and \ + onion.TorVersion('0.2.5') > onion.tor_details['version']: + self.report['error'] = 'unsupported-tor-version' + return + elif transport_name and \ + onion.TorVersion('0.2.4.1') > onion.tor_details['version']: + self.report['error'] = 'unsupported-tor-version' + return
config.Bridge = self.bridge config.UseBridges = 1 diff --git a/ooni/tests/test_onion.py b/ooni/tests/test_onion.py new file mode 100644 index 0000000..9830235 --- /dev/null +++ b/ooni/tests/test_onion.py @@ -0,0 +1,8 @@ +from twisted.trial import unittest +from ooni.utils import onion + +class TestOnion(unittest.TestCase): + def test_tor_details(self): + assert isinstance(onion.tor_details, dict) + assert onion.tor_details['version'] + assert onion.tor_details['binary'] diff --git a/ooni/utils/__init__.py b/ooni/utils/__init__.py index 1b6a8e9..3f47bd1 100644 --- a/ooni/utils/__init__.py +++ b/ooni/utils/__init__.py @@ -82,7 +82,6 @@ def randomStr(length, num=True): chars += string.digits return ''.join(random.choice(chars) for x in range(length))
- def pushFilenameStack(filename): """ Takes as input a target filename and checks to see if a file by such name @@ -103,5 +102,3 @@ def pushFilenameStack(filename): new_filename = "%s.%s" % (c_filename, new_idx) os.rename(f, new_filename) os.rename(filename, filename+".1") - - diff --git a/ooni/utils/onion.py b/ooni/utils/onion.py new file mode 100644 index 0000000..91d8a2d --- /dev/null +++ b/ooni/utils/onion.py @@ -0,0 +1,43 @@ +import string +import subprocess +from distutils.version import LooseVersion + +from txtorcon.util import find_tor_binary + +class TorVersion(LooseVersion): + pass + +def tor_version(): + tor_binary = find_tor_binary() + if not tor_binary: + return None + try: + proc = subprocess.Popen((tor_binary, '--version'), + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: + pass + else: + stdout, _ = proc.communicate() + if proc.poll() == 0 and stdout != '': + return TorVersion(stdout.strip().split(' ')[2]) + return None + +def transport_name(address): + """ + If the address of the bridge starts with a valid c identifier then + we consider it to be a bridge. + Returns: + The transport_name if it's a transport. + None if it's not a obfsproxy bridge. + """ + transport_name = address.split(' ')[0] + transport_name_chars = string.ascii_letters + string.digits + if all(c in transport_name_chars for c in transport_name): + return transport_name + else: + return None + +tor_details = { + 'binary': find_tor_binary(), + 'version': tor_version() +}