commit 276aeea3a524018df7a4535add3e2ad83f755e33 Author: Isis Lovecruft isis@torproject.org Date: Sun Nov 4 12:23:09 2012 +0000
* Moving bridget around to be better integrated with the new ooni structure. --- ooni/bridget/__init__.py | 14 - ooni/bridget/custodiet.py | 421 --------------------- ooni/bridget/tests/__init__.py | 14 - ooni/bridget/tests/bridget.py | 499 ------------------------- ooni/bridget/tests/echo.py | 205 ----------- ooni/bridget/tests/tls-handshake.py | 32 -- ooni/bridget/utils/__init__.py | 1 - ooni/bridget/utils/inputs.py | 174 --------- ooni/bridget/utils/interface.py | 54 --- ooni/bridget/utils/log.py | 98 ----- ooni/bridget/utils/nodes.py | 176 --------- ooni/bridget/utils/onion.py | 686 ----------------------------------- ooni/bridget/utils/reports.py | 144 -------- ooni/bridget/utils/tests.py | 141 ------- ooni/bridget/utils/work.py | 147 -------- 15 files changed, 0 insertions(+), 2806 deletions(-)
diff --git a/ooni/bridget/__init__.py b/ooni/bridget/__init__.py deleted file mode 100644 index 4648d77..0000000 --- a/ooni/bridget/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -#-*- coding: utf-8 -*- - -#import os, sys -#import copy_reg - -## Hack to set the proper sys.path. Overcomes the export PYTHONPATH pain. -#sys.path[:] = map(os.path.abspath, sys.path) -#sys.path.insert(0, os.path.abspath(os.getcwd())) - -## This is a hack to overcome a bug in python -#from ooni.utils.hacks import patched_reduce_ex -#copy_reg._reduce_ex = patched_reduce_ex - -__all__ = ['custodiet'] diff --git a/ooni/bridget/custodiet.py b/ooni/bridget/custodiet.py deleted file mode 100755 index 8cbcfce..0000000 --- a/ooni/bridget/custodiet.py +++ /dev/null @@ -1,421 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -# -# custodiet -# ********* -# -# "...quis custodiet ipsos custodes?" -# - Juvenal, Satires VI.347-348 (circa 2nd Century, C.E.) -# -# "'Hand me the Custodian,' Goodchild demands, inserting the waiflike -# robot into Bambara's opened navel. 'Providing conscience for those who -# have none.' Goodchild and the other Breen government agents disappear -# into the surrounding desert in a vehicle, kicking up cloud of white dust. -# Bambara awakens, and, patting the dust from his clothing, turns to -# greet a one-armed child. 'Hi, my name's Bambara; I'm a -# thirty-six-year-old Virgo and a former killer, who's hobbies include -# performing recreational autopsies, defecating, and drinking rum. I've -# recently been given a conscience, and would very much like to help you.' -# Cut to Bambara and the child, now with one of Bambara's arms, leaving -# a surgical clinic." -# - AeonFlux, "The Purge" (sometime in the late 90s) -# -# :copyright: (c) 2012 Isis Lovecruft -# :license: see LICENSE for more details. -# :version: 0.1.0-beta -# - -# ooniprobe.py imports -import sys -from signal import SIGTERM, signal -from pprint import pprint - -from twisted.python import usage -from twisted.internet import reactor -from twisted.plugin import getPlugins - -from zope.interface.verify import verifyObject -from zope.interface.exceptions import BrokenImplementation -from zope.interface.exceptions import BrokenMethodImplementation - -from ooni.bridget.tests import bridget -from ooni.bridget.utils import log, tests, work, reports -from ooni.bridget.utils.interface import ITest -from ooni.utils.logo import getlogo - -# runner.py imports -import os -import types -import time -import inspect -import yaml - -from twisted.internet import defer, reactor -from twisted.python import reflect, failure, usage -from twisted.python import log as tlog - -from twisted.trial import unittest -from twisted.trial.runner import TrialRunner, TestLoader -from twisted.trial.runner import isPackage, isTestCase, ErrorHolder -from twisted.trial.runner import filenameToModule, _importFromFile - -from ooni import nettest -from ooni.inputunit import InputUnitFactory -from ooni.nettest import InputTestSuite -from ooni.plugoo import tests as oonitests -from ooni.reporter import ReporterFactory -from ooni.utils import log, geodata, date -from ooni.utils.legacy import LegacyOONITest -from ooni.utils.legacy import start_legacy_test, adapt_legacy_test - - -__version__ = "0.1.0-beta" - - -#def retrieve_plugoo(): -# """ -# Get all the plugins that implement the ITest interface and get the data -# associated to them into a dict. -# """ -# interface = ITest -# d = {} -# error = False -# for p in getPlugins(interface, plugins): -# try: -# verifyObject(interface, p) -# d[p.shortName] = p -# except BrokenImplementation, bi: -# print "Plugin Broken" -# print bi -# error = True -# if error != False: -# print "Plugin Loaded!" -# return d -# -#plugoo = retrieve_plugoo() - -""" - -ai to watch over which tests to run - custodiet - - * runTest() or getPrefixMethodNames() to run the tests in order for each - test (esp. the tcp and icmp parts) to be oonicompat we should use the - test_icmp_ping API framework for those. - - * should handle calling - -tests to run: - echo - syn - fin - conn - tls - tor -need fakebridge - canary - -""" - -def runTest(test, options, global_options, reactor=reactor): - """ - Run an OONI probe test by name. - - @param test: a string specifying the test name as specified inside of - shortName. - - @param options: the local options to be passed to the test. - - @param global_options: the global options for OONI - """ - parallelism = int(global_options['parallelism']) - worker = work.Worker(parallelism, reactor=reactor) - test_class = plugoo[test].__class__ - report = reports.Report(test, global_options['output']) - - log_to_stdout = True - if global_options['quiet']: - log_to_stdout = False - - log.start(log_to_stdout, - global_options['log'], - global_options['verbosity']) - - resume = 0 - if not options: - options = {} - if 'resume' in options: - resume = options['resume'] - - test = test_class(options, global_options, report, reactor=reactor) - if test.tool: - test.runTool() - return True - - if test.ended: - print "Ending test" - return None - - wgen = work.WorkGenerator(test, - dict(options), - start=resume) - for x in wgen: - worker.push(x) - -class MainOptions(usage.Options): - tests = [bridget, ] - subCommands = [] - for test in tests: - print test - testopt = getattr(test, 'options') - subCommands.append([test, None, testopt, "Run the %s test" % test]) - - optFlags = [ - ['quiet', 'q', "Don't log to stdout"] - ] - - optParameters = [ - ['parallelism', 'n', 10, "Specify the number of parallel tests to run"], - #['target-node', 't', 'localhost:31415', 'Select target node'], - ['output', 'o', 'bridge.log', "Specify output report file"], - ['reportfile', 'o', 'bridge.log', "Specify output log file"], - ['verbosity', 'v', 1, "Specify the logging level"], - ] - - def opt_version(self): - """ - Display OONI version and exit. - """ - print "OONI version:", __version__ - sys.exit(0) - - def __str__(self): - """ - Hack to get the sweet ascii art into the help output and replace the - strings "Commands" with "Tests". - """ - return getlogo() + '\n' + self.getSynopsis() + '\n' + \ - self.getUsage(width=None).replace("Commands:", "Tests:") - - - -def isTestCase(thing): - try: - return issubclass(thing, unittest.TestCase) - except TypeError: - return False - -def isLegacyTest(obj): - """ - Returns True if the test in question is written using the OONITest legacy - class. - We do this for backward compatibility of the OONIProbe API. - """ - try: - if issubclass(obj, oonitests.OONITest) and not obj == oonitests.OONITest: - return True - else: - return False - except TypeError: - return False - -def processTest(obj, config): - """ - Process the parameters and :class:`twisted.python.usage.Options` of a - :class:`ooni.nettest.Nettest`. - - :param obj: - An uninstantiated old test, which should be a subclass of - :class:`ooni.plugoo.tests.OONITest`. - :param config: - A configured and instantiated :class:`twisted.python.usage.Options` - class. - """ - - inputFile = obj.inputFile - - if obj.optParameters or inputFile: - if not obj.optParameters: - obj.optParameters = [] - - if inputFile: - obj.optParameters.append(inputFile) - - class Options(usage.Options): - optParameters = obj.optParameters - - options = Options() - options.parseOptions(config['subArgs']) - obj.localOptions = options - - if inputFile: - obj.inputFile = options[inputFile[0]] - try: - tmp_obj = obj() - tmp_obj.getOptions() - except usage.UsageError: - options.opt_help() - - return obj - -def findTestClassesFromConfig(config): - """ - Takes as input the command line config parameters and returns the test - case classes. - If it detects that a certain test class is using the old OONIProbe format, - then it will adapt it to the new testing system. - - :param config: - A configured and instantiated :class:`twisted.python.usage.Options` - class. - :return: - A list of class objects found in a file or module given on the - commandline. - """ - - filename = config['test'] - classes = [] - - module = filenameToModule(filename) - for name, val in inspect.getmembers(module): - if isTestCase(val): - classes.append(processTest(val, config)) - elif isLegacyTest(val): - classes.append(adapt_legacy_test(val, config)) - return classes - -def makeTestCases(klass, tests, methodPrefix): - """ - Takes a class some tests and returns the test cases. methodPrefix is how - the test case functions should be prefixed with. - """ - - cases = [] - for test in tests: - cases.append(klass(methodPrefix+test)) - return cases - -def loadTestsAndOptions(classes, config): - """ - Takes a list of classes and returns their testcases and options. - Legacy tests will be adapted. - """ - - methodPrefix = 'test' - suiteFactory = InputTestSuite - options = [] - testCases = [] - names = [] - - _old_klass_type = LegacyOONITest - - for klass in classes: - if isinstance(klass, _old_klass_type): - try: - cases = start_legacy_test(klass) - #cases.callback() - if cases: - print cases - return [], [] - testCases.append(cases) - except Exception, e: - log.err(e) - else: - try: - opts = klass.local_options - options.append(opts) - except AttributeError, ae: - options.append([]) - log.err(ae) - elif not isinstance(klass, _old_klass_type): - tests = reflect.prefixedMethodNames(klass, methodPrefix) - if tests: - cases = makeTestCases(klass, tests, methodPrefix) - testCases.append(cases) - try: - k = klass() - opts = k.getOptions() - options.append(opts) - except AttributeError, ae: - options.append([]) - log.err(ae) - else: - try: - raise RuntimeError, "Class is some strange type!" - except RuntimeError, re: - log.err(re) - - return testCases, options - -class ORunner(object): - """ - This is a specialized runner used by the ooniprobe command line tool. - I am responsible for reading the inputs from the test files and splitting - them in input units. I also create all the report instances required to run - the tests. - """ - def __init__(self, cases, options=None, config=None, *arg, **kw): - self.baseSuite = InputTestSuite - self.cases = cases - self.options = options - - try: - assert len(options) != 0, "Length of options is zero!" - except AssertionError, ae: - self.inputs = [] - log.err(ae) - else: - try: - first = options.pop(0) - except: - first = {} - if 'inputs' in first: - self.inputs = options['inputs'] - else: - log.msg("Could not find inputs!") - log.msg("options[0] = %s" % first) - self.inputs = [None] - - try: - reportFile = open(config['reportfile'], 'a+') - except: - filename = 'report_'+date.timestamp()+'.yaml' - reportFile = open(filename, 'a+') - self.reporterFactory = ReporterFactory(reportFile, - testSuite=self.baseSuite(self.cases)) - - def runWithInputUnit(self, inputUnit): - idx = 0 - result = self.reporterFactory.create() - - for inputs in inputUnit: - result.reporterFactory = self.reporterFactory - - suite = self.baseSuite(self.cases) - suite.input = inputs - suite(result, idx) - - # XXX refactor all of this index bullshit to avoid having to pass - # this index around. Probably what I want to do is go and make - # changes to report to support the concept of having multiple runs - # of the same test. - # We currently need to do this addition in order to get the number - # of times the test cases that have run inside of the test suite. - idx += (suite._idx - idx) - - result.done() - - def run(self): - self.reporterFactory.options = self.options - for inputUnit in InputUnitFactory(self.inputs): - self.runWithInputUnit(inputUnit) - -if __name__ == "__main__": - config = Options() - config.parseOptions() - - if not config.subCommand: - config.opt_help() - signal(SIGTERM) - #sys.exit(1) - - runTest(config.subCommand, config.subOptions, config) - reactor.run() diff --git a/ooni/bridget/tests/__init__.py b/ooni/bridget/tests/__init__.py deleted file mode 100644 index 9ecc88d..0000000 --- a/ooni/bridget/tests/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: UTF-8 -# -# bridget/tests/__init__.py -# ************************* -# -# "...quis custodiet ipsos custodes?" -# - Juvenal, Satires VI.347-348 (circa 2nd Century, C.E.) -# -# :copyright: (c) 2012 Isis Lovecruft -# :license: see LICENSE for more details. -# :version: 0.1.0-beta -# - -all = ['bridget'] diff --git a/ooni/bridget/tests/bridget.py b/ooni/bridget/tests/bridget.py deleted file mode 100644 index a334747..0000000 --- a/ooni/bridget/tests/bridget.py +++ /dev/null @@ -1,499 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -# -# +-----------+ -# | BRIDGET | -# | +--------------------------------------------+ -# +--------| Use a Tor process to test making a Tor | -# | connection to a list of bridges or relays. | -# +--------------------------------------------+ -# -# :authors: Isis Lovecruft, Arturo Filasto -# :licence: see included LICENSE -# :version: 0.1.0-alpha - -from __future__ import with_statement -from functools import partial -from random import randint - -import os -import sys - -from twisted.python import usage -from twisted.plugin import IPlugin -from twisted.internet import defer, error, reactor -from zope.interface import implements - -from ooni.utils import log, date -from ooni.utils.config import ValueChecker - -from ooni.plugoo.tests import ITest, OONITest -from ooni.plugoo.assets import Asset, MissingAssetException -from ooni.utils.onion import TxtorconImportError -from ooni.utils.onion import PTNoBridgesException, PTNotFoundException - -try: - from ooni.utils.onion import parse_data_dir -except: - log.msg("Please go to /ooni/lib and do 'make txtorcon' to run this test!") - -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 BridgetArgs(usage.Options): - """Commandline options.""" - allowed = "Port to use for Tor's %s, must be between 1024 and 65535." - sock_check = ValueChecker(allowed % "SocksPort").port_check - ctrl_check = ValueChecker(allowed % "ControlPort").port_check - - optParameters = [ - ['bridges', 'b', None, - 'File listing bridge IP:ORPorts to test'], - ['relays', 'f', None, - 'File listing relay IPs to test'], - ['socks', 's', 9049, None, sock_check], - ['control', 'c', 9052, None, ctrl_check], - ['torpath', 'p', None, - 'Path to the Tor binary to use'], - ['datadir', 'd', None, - 'Tor DataDirectory to use'], - ['transport', 't', None, - 'Tor ClientTransportPlugin'], - ['resume', 'r', 0, - 'Resume at this index']] - optFlags = [['random', 'x', 'Use random ControlPort and SocksPort']] - - def postOptions(self): - if not self['bridges'] and not self['relays']: - raise MissingAssetException( - "Bridget can't run without bridges or relays to test!") - if self['transport']: - ValueChecker.uid_check( - "Can't run bridget as root with pluggable transports!") - if not self['bridges']: - raise PTNoBridgesException - if self['socks'] or self['control']: - if self['random']: - raise RandomPortException - if self['datadir']: - ValueChecker.dir_check(self['datadir']) - if self['torpath']: - ValueChecker.file_check(self['torpath']) - -class BridgetAsset(Asset): - """Class for parsing bridget Assets ignoring commented out lines.""" - def __init__(self, file=None): - self = Asset.__init__(self, file) - - def parse_line(self, line): - if line.startswith('#'): - return - else: - return line.replace('\n','') - -class BridgetTest(OONITest): - """ - XXX fill me in - - :ivar config: - An :class:`ooni.lib.txtorcon.TorConfig` instance. - :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 transport: - String defining the Tor's ClientTransportPlugin, for testing - a bridge's pluggable transport functionality. - :ivar tor_binary: - Path to the Tor binary to use, e.g. '/usr/sbin/tor' - """ - implements(IPlugin, ITest) - - shortName = "bridget" - description = "Use a Tor process to test connecting to bridges or relays" - requirements = None - options = BridgetArgs - blocking = False - - def initialize(self): - """ - Extra initialization steps. We only want one child Tor process - 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 - - 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','')) - - def __count_remaining__(which): - total, reach, unreach = map(lambda x: which[x], - ['all', 'reachable', 'unreachable']) - count = len(total) - reach() - unreach() - return count - - ## XXX should we do report['bridges_up'].append(self.bridges['current']) - self.bridges = {} - self.bridges['all'], self.bridges['up'], self.bridges['down'] = \ - ([] for i in range(3)) - self.bridges['reachable'] = lambda: len(self.bridges['up']) - self.bridges['unreachable'] = lambda: len(self.bridges['down']) - self.bridges['remaining'] = lambda: __count_remaining__(self.bridges) - self.bridges['current'] = None - self.bridges['pt_type'] = None - self.bridges['use_pt'] = False - - self.relays = {} - self.relays['all'], self.relays['up'], self.relays['down'] = \ - ([] for i in range(3)) - self.relays['reachable'] = lambda: len(self.relays['up']) - self.relays['unreachable'] = lambda: len(self.relays['down']) - self.relays['remaining'] = lambda: __count_remaining__(self.relays) - self.relays['current'] = None - - if self.local_options: - try: - from ooni.lib.txtorcon import TorConfig - except ImportError: - raise TxtorconImportError - else: - self.config = TorConfig() - finally: - options = self.local_options - - if options['bridges']: - self.config.UseBridges = 1 - __make_asset_list__(options['bridges'], self.bridges['all']) - if options['relays']: - ## first hop must be in TorState().guards - self.config.EntryNodes = ','.join(relay_list) - __make_asset_list__(options['relays'], self.relays['all']) - 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 = randint(1024, 2**16) - self.control_port = randint(1024, 2**16) - if options['torpath']: - self.tor_binary = options['torpath'] - if options['datadir']: - self.data_directory = parse_data_dir(options['datadir']) - if options['transport']: - ## ClientTransportPlugin transport exec pathtobinary [options] - ## XXX we need a better way to deal with all PTs - log.msg("Using ClientTransportPlugin %s" % options['transport']) - self.bridges['use_pt'] = True - [self.bridges['pt_type'], pt_exec] = \ - options['transport'].split(' ', 1) - - if self.bridges['pt_type'] == "obfs2": - self.config.ClientTransportPlugin = \ - self.bridges['pt_type'] + " " + pt_exec - else: - raise PTNotFoundException - - self.config.SocksPort = self.socks_port - self.config.ControlPort = self.control_port - self.config.CookieAuthentication = 1 - - def __load_assets__(self): - """ - Load bridges and/or relays from files given in user options. Bridges - 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. - - We cannot use the Asset model, because that model calls - self.experiment() with the current Assets, which would be one relay - and one bridge, then it gives the defer.Deferred returned from - self.experiment() to self.control(), which means that, for each - (bridge, relay) pair, experiment gets called again, which instantiates - an additional Tor process that attempts to bind to the same - ports. Thus, additionally instantiated Tor processes return with - RuntimeErrors, which break the final defer.chainDeferred.callback(), - sending it into the errback chain. - """ - assets = {} - if self.local_options: - if self.local_options['bridges']: - assets.update({'bridge': - BridgetAsset(self.local_options['bridges'])}) - if self.local_options['relays']: - assets.update({'relay': - BridgetAsset(self.local_options['relays'])}) - return assets - - def experiment(self, args): - """ - if bridges: - 1. configure first bridge line - 2a. configure data_dir, if it doesn't exist - 2b. write torrc to a tempfile in data_dir - 3. start tor } if any of these - 4. remove bridges which are public relays } fail, add current - 5. SIGHUP for each bridge } bridge to unreach- - } able bridges. - if relays: - 1a. configure the data_dir, if it doesn't exist - 1b. write torrc to a tempfile in data_dir - 2. start tor - 3. remove any of our relays which are already part of current - circuits - 4a. attach CustomCircuit() to self.state - 4b. RELAY_EXTEND for each relay } if this fails, add - } current relay to list - } of unreachable relays - 5. - if bridges and relays: - 1. configure first bridge line - 2a. configure data_dir if it doesn't exist - 2b. write torrc to a tempfile in data_dir - 3. start tor - 4. remove bridges which are public relays - 5. remove any of our relays which are already part of current - circuits - 6a. attach CustomCircuit() to self.state - 6b. for each bridge, build three circuits, with three - relays each - 6c. RELAY_EXTEND for each relay } if this fails, add - } current relay to list - } of unreachable relays - - :param args: - The :class:`BridgetAsset` line currently being used. Except that it - in Bridget it doesn't, so it should be ignored and avoided. - """ - try: - from ooni.utils import process - from ooni.utils.onion import remove_public_relays, start_tor - from ooni.utils.onion import start_tor_filter_nodes - from ooni.utils.onion import setup_fail, setup_done - from ooni.utils.onion import CustomCircuit - from ooni.utils.timer import deferred_timeout, TimeoutError - from ooni.lib.txtorcon import TorConfig, TorState - except ImportError: - raise TxtorconImportError - except TxtorconImportError, tie: - log.err(tie) - sys.exit() - - def reconfigure_done(state, bridges): - """ - Append :ivar:`bridges['current']` to the list - :ivar:`bridges['up']. - """ - log.msg("Reconfiguring with 'Bridge %s' successful" - % bridges['current']) - bridges['up'].append(bridges['current']) - return state - - def reconfigure_fail(state, bridges): - """ - Append :ivar:`bridges['current']` to the list - :ivar:`bridges['down']. - """ - log.msg("Reconfiguring TorConfig with parameters %s failed" - % state) - bridges['down'].append(bridges['current']) - return state - - @defer.inlineCallbacks - def reconfigure_bridge(state, bridges): - """ - Rewrite the Bridge line in our torrc. If use of pluggable - transports was specified, rewrite the line as: - Bridge <transport_type> <IP>:<ORPort> - Otherwise, rewrite in the standard form: - Bridge <IP>:<ORPort> - - :param state: - A fully bootstrapped instance of - :class:`ooni.lib.txtorcon.TorState`. - :param bridges: - A dictionary of bridges containing the following keys: - - bridges['remaining'] :: A function returning and int for the - number of remaining bridges to test. - bridges['current'] :: A string containing the <IP>:<ORPort> - of the current bridge. - bridges['use_pt'] :: A boolean, True if we're testing - bridges with a pluggable transport; - False otherwise. - bridges['pt_type'] :: If :ivar:`bridges['use_pt'] is True, - this is a string containing the type - of pluggable transport to test. - :return: - :param:`state` - """ - log.msg("Current Bridge: %s" % bridges['current']) - log.msg("We now have %d bridges remaining to test..." - % bridges['remaining']()) - try: - if bridges['use_pt'] is False: - controller_response = yield state.protocol.set_conf( - 'Bridge', bridges['current']) - elif bridges['use_pt'] and bridges['pt_type'] is not None: - controller_reponse = yield state.protocol.set_conf( - 'Bridge', bridges['pt_type'] +' '+ bridges['current']) - else: - raise PTNotFoundException - - if controller_response == 'OK': - finish = yield reconfigure_done(state, bridges) - else: - log.err("SETCONF for %s responded with error:\n %s" - % (bridges['current'], controller_response)) - finish = yield reconfigure_fail(state, bridges) - - defer.returnValue(finish) - - except Exception, e: - log.err("Reconfiguring torrc with Bridge line %s failed:\n%s" - % (bridges['current'], e)) - defer.returnValue(None) - - def attacher_extend_circuit(attacher, deferred, router): - ## XXX todo write me - ## state.attacher.extend_circuit - raise NotImplemented - #attacher.extend_circuit - - def state_attach(state, path): - log.msg("Setting up custom circuit builder...") - attacher = CustomCircuit(state) - state.set_attacher(attacher, reactor) - state.add_circuit_listener(attacher) - return state - - ## OLD - #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 state_attach_fail(state): - log.err("Attaching custom circuit builder failed: %s" % state) - - log.msg("Bridget: initiating test ... ") ## Start the experiment - - ## if we've at least one bridge, and our config has no 'Bridge' line - if self.bridges['remaining']() >= 1 \ - and not 'Bridge' in self.config.config: - - ## configure our first bridge line - self.bridges['current'] = self.bridges['all'][0] - self.config.Bridge = self.bridges['current'] - ## avoid starting several - self.config.save() ## processes - assert self.config.config.has_key('Bridge'), "No Bridge Line" - - ## start tor and remove bridges which are public relays - from ooni.utils.onion import start_tor_filter_nodes - state = start_tor_filter_nodes(reactor, self.config, - self.control_port, self.tor_binary, - self.data_directory, self.bridges) - #controller = defer.Deferred() - #controller.addCallback(singleton_semaphore, tor) - #controller.addErrback(setup_fail) - #bootstrap = defer.gatherResults([controller, filter_bridges], - # consumeErrors=True) - - if state is not None: - log.debug("state:\n%s" % state) - log.debug("Current callbacks on TorState():\n%s" - % state.callbacks) - - ## if we've got more bridges - if self.bridges['remaining']() >= 2: - #all = [] - for bridge in self.bridges['all'][1:]: - self.bridges['current'] = bridge - #new = defer.Deferred() - #new.addCallback(reconfigure_bridge, state, self.bridges) - #all.append(new) - #check_remaining = defer.DeferredList(all, consumeErrors=True) - #state.chainDeferred(check_remaining) - state.addCallback(reconfigure_bridge, self.bridges) - - if self.relays['remaining']() > 0: - while self.relays['remaining']() >= 3: - #path = list(self.relays.pop() for i in range(3)) - #log.msg("Trying path %s" % '->'.join(map(lambda node: - # node, path))) - self.relays['current'] = self.relays['all'].pop() - for circ in state.circuits.values(): - for node in circ.path: - if node == self.relays['current']: - self.relays['up'].append(self.relays['current']) - if len(circ.path) < 3: - try: - ext = attacher_extend_circuit(state.attacher, circ, - self.relays['current']) - ext.addCallback(attacher_extend_circuit_done, - state.attacher, circ, - self.relays['current']) - except Exception, e: - log.err("Extend circuit failed: %s" % e) - else: - continue - - #state.callback(all) - #self.reactor.run() - return state - - def startTest(self, args): - """ - Local override of :meth:`OONITest.startTest` to bypass calling - self.control. - - :param args: - The current line of :class:`Asset`, not used but kept for - compatibility reasons. - :return: - A fired deferred which callbacks :meth:`experiment` and - :meth:`OONITest.finished`. - """ - self.start_time = date.now() - self.d = self.experiment(args) - self.d.addErrback(log.err) - self.d.addCallbacks(self.finished, log.err) - return self.d - -## So that getPlugins() can register the Test: -#bridget = BridgetTest(None, None, None) - -## ISIS' NOTES -## ----------- -## TODO: -## x cleanup documentation -## x add DataDirectory option -## x check if bridges are public relays -## o take bridge_desc file as input, also be able to give same -## format as output -## x Add asynchronous timeout for deferred, so that we don't wait -## o Add assychronous timout for deferred, so that we don't wait -## forever for bridges that don't work. diff --git a/ooni/bridget/tests/echo.py b/ooni/bridget/tests/echo.py deleted file mode 100644 index 7f3217a..0000000 --- a/ooni/bridget/tests/echo.py +++ /dev/null @@ -1,205 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# +---------+ -# | echo.py | -# +---------+ -# A simple ICMP-8 ping test. -# -# :author: Isis Lovecruft -# :version: 0.0.1-pre-alpha -# :license: (c) 2012 Isis Lovecruft -# see attached LICENCE file -# - -import os -import sys - -from pprint import pprint - -from twisted.internet import reactor -from twisted.plugin import IPlugin -from twisted.python import usage -from ooni.nettest import NetTestCase -from ooni.utils import log, Storage -from ooni.utils.net import PermissionsError, IfaceError - -try: - from scapy.all import sr1, IP, ICMP ## XXX v4/v6? - from ooni.lib import txscapy - from ooni.lib.txscapy import txsr, txsend - from ooni.templates.scapyt import ScapyTest -except: - log.msg("This test requires scapy, see www.secdev.org/projects/scapy") - -## xxx TODO: move these to a utility function for determining OSes -LINUX=sys.platform.startswith("linux") -OPENBSD=sys.platform.startswith("openbsd") -FREEBSD=sys.platform.startswith("freebsd") -NETBSD=sys.platform.startswith("netbsd") -DARWIN=sys.platform.startswith("darwin") -SOLARIS=sys.platform.startswith("sunos") -WINDOWS=sys.platform.startswith("win32") - -class EchoTest(ScapyTest): - """ - xxx fill me in - """ - name = 'echo' - author = 'Isis Lovecruft isis@torproject.org' - description = 'A simple ICMP-8 test to see if a host is reachable.' - version = '0.0.1' - inputFile = ['file', 'f', None, 'File of list of IPs to ping'] - requirements = None - report = Storage() - - optParameters = [ - ['interface', 'i', None, 'Network interface to use'], - ['count', 'c', 5, 'Number of packets to send', int], - ['size', 's', 56, 'Number of bytes to send in ICMP data field', int], - ['ttl', 'l', 25, 'Set the IP Time to Live', int], - ['timeout', 't', 2, 'Seconds until timeout if no response', int], - ['pcap', 'p', None, 'Save pcap to this file'], - ['receive', 'r', True, 'Receive response packets'] - ] - - def setUpClass(self, *a, **kw): - ''' - :ivar ifaces: - Struct returned from getifaddrs(3) and turned into a tuple in the - form (*ifa_name, AF_FAMILY, *ifa_addr) - ''' - super(EchoTest, self).__init__(*a, **kw) - - ## allow subclasses which register/implement external classes - ## to define their own reactor without overrides: - if not hasattr(super(EchoTest, self), 'reactor'): - log.debug("%s test: Didn't find reactor!" % self.name) - self.reactor = reactor - - if self.localOptions: - log.debug("%s localOptions found" % self.name) - log.debug("%s test options: %s" % (self.name, self.subOptions)) - self.local_options = self.localOptions.parseOptions(self.subOptions) - for key, value in self.local_options: - log.debug("Set attribute %s[%s] = %s" % (self.name, key, value)) - setattr(self, key, value) - - ## xxx is this now .subOptions? - #self.inputFile = self.localOptions['file'] - self.timeout *= 1000 ## convert to milliseconds - - if not self.interface: - log.msg("No network interface specified!") - log.debug("OS detected: %s" % sys.platform) - if LINUX or OPENBSD or NETBSD or FREEBSD or DARWIN or SOLARIS: - from twisted.internet.test import _posixifaces - log.msg("Attempting to discover network interfaces...") - ifaces = _posixifaces._interfaces() - elif WINDOWS: - from twisted.internet.test import _win32ifaces - log.msg("Attempting to discover network interfaces...") - ifaces = _win32ifaces._interfaces() - else: - log.debug("Client OS %s not accounted for!" % sys.platform) - log.debug("Unable to discover network interfaces...") - ifaces = [('lo', '')] - - ## found = {'eth0': '1.1.1.1'} - found = [{i[0]: i[2]} for i in ifaces if i[0] != 'lo'] - log.info("Found interfaces:\n%s" % pprint(found)) - self.interfaces = self.tryInterfaces(found) - else: - ## xxx need a way to check that iface exists, is up, and - ## we have permissions on it - log.debug("Our interface has been set to %s" % self.interface) - - if self.pcap: - try: - self.pcapfile = open(self.pcap, 'a+') - except: - log.msg("Unable to write to pcap file %s" % self.pcap) - self.pcapfile = None - - try: - assert os.path.isfile(self.file) - fp = open(self.file, 'r') - except Exception, e: - hosts = ['8.8.8.8', '38.229.72.14'] - log.err(e) - else: - self.inputs = self.inputProcessor(fp) - self.removePorts(hosts) - - log.debug("Initialization of %s test completed with:\n%s" - % (self.name, ''.join(self.__dict__))) - - @staticmethod - def inputParser(inputs): - log.debug("Removing possible ports from host addresses...") - log.debug("Initial inputs:\n%s" % pprint(inputs)) - - assert isinstance(inputs, list) - hosts = [h.rsplit(':', 1)[0] for h in inputs] - log.debug("Inputs converted to:\n%s" % hosts) - - return hosts - - def tryInterfaces(self, ifaces): - try: - from scapy.all import sr1 ## we want this check to be blocking - except: - log.msg("This test requires scapy: www.secdev.org/projects/scapy") - raise SystemExit - - ifup = {} - while ifaces: - for ifname, ifaddr in ifaces: - log.debug("Currently testing network capabilities of interface" - + "%s by sending a packet to our address %s" - % (ifname, ifaddr)) - try: - pkt = IP(dst=ifaddr)/ICMP() - ans, unans = sr(pkt, iface=ifname, timeout=self.timeout) - except Exception, e: - raise PermissionsError if e.find("Errno 1") else log.err(e) - else: - ## xxx i think this logic might be wrong - log.debug("Interface test packet\n%s\n\n%s" - % (pkt.summary(), pkt.show2())) - if ans.summary(): - log.info("Received answer for test packet on interface" - +"%s :\n%s" % (ifname, ans.summary())) - ifup.update(ifname, ifaddr) - else: - log.info("Our interface test packet was unanswered:\n%s" - % unans.summary()) - - if len(ifup) > 0: - log.msg("Discovered the following working network interfaces: %s" - % ifup) - return ifup - else: - raise IfaceError("Could not find a working network interface.") - - def buildPackets(self): - log.debug("self.input is %s" % self.input) - log.debug("self.hosts is %s" % self.hosts) - for addr in self.input: - packet = IP(dst=self.input)/ICMP() - self.request.append(packet) - return packet - - def test_icmp(self): - if self.recieve: - self.buildPackets() - all = [] - for packet in self.request: - d = self.sendReceivePackets(packets=packet) - all.append(d) - self.response.update({packet: d}) - d_list = defer.DeferredList(all) - return d_list - else: - d = self.sendPackets() - return d diff --git a/ooni/bridget/tests/tls-handshake.py b/ooni/bridget/tests/tls-handshake.py deleted file mode 100644 index eba950e..0000000 --- a/ooni/bridget/tests/tls-handshake.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python - -import subprocess -from subprocess import PIPE -serverport = "129.21.124.215:443" -# a subset of those from firefox -ciphers = [ - "ECDHE-ECDSA-AES256-SHA", - "ECDHE-RSA-AES256-SHA", - "DHE-RSA-CAMELLIA256-SHA", - "DHE-DSS-CAMELLIA256-SHA", - "DHE-RSA-AES256-SHA", - "DHE-DSS-AES256-SHA", - "ECDH-ECDSA-AES256-CBC-SHA", - "ECDH-RSA-AES256-CBC-SHA", - "CAMELLIA256-SHA", - "AES256-SHA", - "ECDHE-ECDSA-RC4-SHA", - "ECDHE-ECDSA-AES128-SHA", - "ECDHE-RSA-RC4-SHA", - "ECDHE-RSA-AES128-SHA", - "DHE-RSA-CAMELLIA128-SHA", - "DHE-DSS-CAMELLIA128-SHA" -] -def checkBridgeConnection(host, port) - cipher_arg = ":".join(ciphers) - cmd = ["openssl", "s_client", "-connect", "%s:%s" % (host,port)] - cmd += ["-cipher", cipher_arg] - proc = subprocess.Popen(cmd, stdout=PIPE, stderr=PIPE,stdin=PIPE) - out, error = proc.communicate() - success = "Cipher is DHE-RSA-AES256-SHA" in out - return success diff --git a/ooni/bridget/utils/__init__.py b/ooni/bridget/utils/__init__.py deleted file mode 100644 index 92893d6..0000000 --- a/ooni/bridget/utils/__init__.py +++ /dev/null @@ -1 +0,0 @@ -all = ['inputs', 'log', 'onion', 'tests', 'interface', 'nodes', 'reports', 'work'] diff --git a/ooni/bridget/utils/inputs.py b/ooni/bridget/utils/inputs.py deleted file mode 100644 index fe058cc..0000000 --- a/ooni/bridget/utils/inputs.py +++ /dev/null @@ -1,174 +0,0 @@ -#-*- coding: utf-8 -*- -# -# inputs.py -# ********* -# -# "...quis custodiet ipsos custodes?" -# - Juvenal, Satires VI.347-348 (circa 2nd Century, C.E.) -# -# :copyright: (c) 2012 Isis Lovecruft -# :license: see LICENSE for more details. -# :version: 0.1.0-beta -# - -#from types import FunctionType, FileType -import types - -from ooni.bridget import log -from ooni.utils import date, Storage - -class InputFile: - """ - This is a class describing a file used to store Tor bridge or relays - inputs. It is a python iterator object, allowing it to be efficiently - looped. - - This class should not be used directly, but rather its subclasses, - BridgeFile and RelayFile should be used instead. - """ - - def __init__(self, file, **kw): - """ - ## This is an InputAsset file, created because you tried to pass a - ## non-existent filename to a test. - ## - ## To use this file, place one input to be tested per line. Each - ## test takes different inputs. Lines which are commented out with - ## a '#' are not used. - """ - self.file = file - self.eof = False - self.all = Storage() - - for key, value in input_dict: - self.all[key] = value - - try: - self.handler = open(self.file, 'r') - except IOError: - with open(self.file, 'w') as explain: - for line in self.__init__.__doc__: - explain.writeline(line) - self.handler = open(self.file, 'r') - try: - assert isinstance(self.handler, file), "That's not a file!" - except AssertionError, ae: - log.err(ae) - - # def __handler__(self): - # """ - # Attempt to open InputFile.file and check that it is actually a file. - # If it's not, create it and add an explaination for how InputFile files - # should be used. - - # :return: - # A :type:`file` which has been opened in read-only mode. - # """ - # try: - # handler = open(self.file, 'r') - # except IOError, ioerror: ## not the hacker <(A)3 - # log.err(ioerror) - # explanation = ( - # with open(self.file, 'w') as explain: - # for line in explanation: - # explain.writeline(line) - # handler = open(self.file, 'r') - # try: - # assert isinstance(handler, file), "That's not a file!" - # except AssertionError, ae: - # log.err(ae) - # else: - # return handler - - def __iter__(next, StopIteration): - """ - Returns the next input from the file. - """ - #return self.next() - return self - - def len(self): - """ - Returns the number of the lines in the InputFile. - """ - with open(self.file, 'r') as input_file: - lines = input_file.readlines() - for number, line in enumerate(lines): - self.input_dict[number] = line - return number + 1 - - def next(self): - try: - return self.next_input() - except: - raise StopIteration - - def next_input(self): - """ - Return the next input. - """ - line = self.handler.readline() - if line: - parsed_line = self.parse_line(line) - if parsed_line: - return parsed_line - else: - self.fh.seek(0) - raise StopIteration - - def default_parser(self, line): - """ - xxx fill me in - """ - if not line.startswith('#'): - return line.replace('\n', '') - else: - return False - - def parse_line(self, line): - """ - Override this method if you need line by line parsing of an Asset. - - The default parsing action is to ignore lines which are commented out - with a '#', and to strip the newline character from the end of the - line. - - If the line was commented out return an empty string instead. - - If a subclass Foo incorporates another class Bar, when Bar is not - also a subclass of InputFile, and Bar.parse_line() exists, then - do not overwrite Bar's parse_line method. - """ - assert not hasattr(super(InputFile, self), 'parse_line') - - if self.parser is None: - if not line.startswith('#'): - return line.replace('\n', '') - else: - return '' - else: - try: - assert isinstance(self.parser, FunctionType),"Not a function!" - except AssertionError, ae: - log.err(ae) - else: - return self.parser(line) - -class BridgeFile(InputFile): - """ - xxx fill me in - """ - def __init__(self, **kw): - super(BridgeFile, self).__init__(**kw) - -class MissingInputException(Exception): - """ - - Raised when an :class:`InputFile` necessary for running the Test is - missing. - - """ - def __init__(self, error_message): - print error_message - import sys - return sys.exit() diff --git a/ooni/bridget/utils/interface.py b/ooni/bridget/utils/interface.py deleted file mode 100644 index aa55436..0000000 --- a/ooni/bridget/utils/interface.py +++ /dev/null @@ -1,54 +0,0 @@ -from zope.interface import implements, Interface, Attribute - -class ITest(Interface): - """ - This interface represents an OONI test. It fires a deferred on completion. - """ - - shortName = Attribute("""A short user facing description for this test""") - description = Attribute("""A string containing a longer description for the test""") - - requirements = Attribute("""What is required to run this this test, for example raw socket access or UDP or TCP""") - - options = Attribute("""These are the arguments to be passed to the test for it's execution""") - - blocking = Attribute("""True or False, stating if the test should be run in a thread or not.""") - - def control(experiment_result, args): - """ - @param experiment_result: The result returned by the experiment method. - - @param args: the keys of this dict are the names of the assets passed in - from load_assets. The value is one item of the asset. - - Must return a dict containing what should be written to the report. - Anything returned by control ends up inside of the YAMLOONI report. - """ - - def experiment(args): - """ - Perform all the operations that are necessary to running a test. - - @param args: the keys of this dict are the names of the assets passed in - from load_assets. The value is one item of the asset. - - Must return a dict containing the values to be passed to control. - """ - - def load_assets(): - """ - Load the assets that should be passed to the Test. These are the inputs - to the OONI test. - Must return a dict that has as keys the asset names and values the - asset contents. - If the test does not have any assets it should return an empty dict. - """ - - def end(): - """ - This can be called at any time to terminate the execution of all of - these test instances. - - What this means is that no more test instances with new parameters will - be created. A report will be written. - """ diff --git a/ooni/bridget/utils/log.py b/ooni/bridget/utils/log.py deleted file mode 100644 index eef50d8..0000000 --- a/ooni/bridget/utils/log.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -OONI logging facility. -""" -from sys import stderr, stdout - -from twisted.python import log, util -from twisted.python.failure import Failure - -def _get_log_level(level): - english = ['debug', 'info', 'warn', 'err', 'crit'] - - levels = dict(zip(range(len(english)), english)) - number = dict(zip(english, range(len(english)))) - - if not level: - return number['info'] - else: - ve = "Unknown log level: %s\n" % level - ve += "Allowed levels: %s\n" % [word for word in english] - - if type(level) is int: - if 0 <= level <= 4: - return level - elif type(level) is str: - if number.has_key(level.lower()): - return number[level] - else: - raise ValueError, ve - else: - raise ValueError, ve - -class OONITestFailure(Failure): - """ - For handling Exceptions asynchronously. - - Can be given an Exception as an argument, else will use the - most recent Exception from the current stack frame. - """ - def __init__(self, exception=None, _type=None, - _traceback=None, _capture=False): - Failure.__init__(self, exc_type=_type, - exc_tb=_traceback, captureVars=_capture) - -class OONILogObserver(log.FileLogObserver): - """ - Supports logging level verbosity. - """ - def __init__(self, logfile, verb=None): - log.FileLogObserver.__init__(self, logfile) - self.level = _get_log_level(verb) if verb is not None else 1 - assert type(self.level) is int - - def emit(self, eventDict): - if 'logLevel' in eventDict: - msgLvl = _get_log_level(eventDict['logLevel']) - assert type(msgLvl) is int - ## only log our level and higher - if self.level <= msgLvl: - text = log.textFromEventDict(eventDict) - else: - text = None - else: - text = log.textFromEventDict(eventDict) - - if text is None: - return - - timeStr = self.formatTime(eventDict['time']) - fmtDict = {'system': eventDict['system'], - 'text': text.replace('\n','\n\t')} - msgStr = log._safeFormat("[%(system)s] %(text)s\n", fmtDict) - - util.untilConcludes(self.write, timeStr + " " + msgStr) - util.untilConcludes(self.flush) - -def start(logfile=None, verbosity=None): - if log.defaultObserver: - verbosity = _get_log_level(verbosity) - - ## Always log to file, keep level at info - file = open(logfile, 'a') if logfile else stderr - OONILogObserver(file, "info").start() - - log.msg("Starting OONI...") - -def debug(message, level="debug", **kw): - print "[%s] %s" % (level, message) - ## If we want debug messages in the logfile: - #log.msg(message, logLevel=level, **kw) - -def msg(message, level="info", **kw): - log.msg(message, logLevel=level, **kw) - -def err(message, level="err", **kw): - log.err(logLevel=level, **kw) - -def fail(message, exception, level="crit", **kw): - log.failure(message, OONITestFailure(exception, **kw), logLevel=level) diff --git a/ooni/bridget/utils/nodes.py b/ooni/bridget/utils/nodes.py deleted file mode 100644 index 155f183..0000000 --- a/ooni/bridget/utils/nodes.py +++ /dev/null @@ -1,176 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -""" - nodes - ***** - - This contains all the code related to Nodes - both network and code execution. - - :copyright: (c) 2012 by Arturo Filastò, Isis Lovecruft - :license: see LICENSE for more details. - -""" - -import os -from binascii import hexlify - -try: - import paramiko -except: - print "Error: module paramiko is not installed." -from pprint import pprint -import sys -import socks -import xmlrpclib - -class Node(object): - def __init__(self, address, port): - self.address = address - self.port = port - -class LocalNode(object): - def __init__(self): - pass - -""" -[]: node = NetworkNode("192.168.0.112", 5555, "SOCKS5") -[]: node_socket = node.wrap_socket() -""" -class NetworkNode(Node): - def __init__(self, address, port, node_type="SOCKS5", auth_creds=None): - self.node = Node(address,port) - - # XXX support for multiple types - # node type (SOCKS proxy, HTTP proxy, GRE tunnel, ...) - self.node_type = node_type - # type-specific authentication credentials - self.auth_creds = auth_creds - - def _get_socksipy_socket(self, proxy_type, auth_creds): - import socks - s = socks.socksocket() - # auth_creds[0] -> username - # auth_creds[1] -> password - s.setproxy(proxy_type, self.node.address, self.node.port, - self.auth_creds[0], self.auth_creds[1]) - return s - - def _get_socket_wrapper(self): - if (self.node_type.startswith("SOCKS")): # SOCKS proxies - if (self.node_type != "SOCKS5"): - proxy_type = socks.PROXY_TYPE_SOCKS5 - elif (self.node_type != "SOCKS4"): - proxy_type = socks.PROXY_TYPE_SOCKS4 - else: - print "We don't know this proxy type." - sys.exit(1) - - return self._get_socksipy_socket(proxy_type) - elif (self.node_type == "HTTP"): # HTTP proxies - return self._get_socksipy_socket(PROXY_TYPE_HTTP) - else: # Unknown proxies - print "We don't know this proxy type." - sys.exit(1) - - def wrap_socket(self): - return self._get_socket_wrapper() - -class CodeExecNode(Node): - def __init__(self, address, port, node_type, auth_creds): - self.node = Node(address,port) - - # node type (SSH proxy, etc.) - self.node_type = node_type - # type-specific authentication credentials - self.auth_creds = auth_creds - - def add_unit(self): - pass - - def get_status(self): - pass - -class PlanetLab(CodeExecNode): - def __init__(self, address, auth_creds, ooni): - self.auth_creds = auth_creds - - self.config = ooni.utils.config - self.logger = ooni.logger - self.name = "PlanetLab" - - def _api_auth(self): - api_server = xmlrpclib.ServerProxy('https://www.planet-lab.org/PLCAPI/') - auth = {} - ## should be changed to separate node.conf file - auth['Username'] = self.config.main.pl_username - auth['AuthString'] = self.config.main.pl_password - auth['AuthMethod'] = "password" - authorized = api_server.AuthCheck(auth) - - if authorized: - print 'We are authorized!' - return auth - else: - print 'Authorization failed. Please check your settings for pl_username and pl_password in the ooni-probe.conf file.' - - def _search_for_nodes(self, node_filter=None): - api_server = xmlrpclib.ServerProxy('https://www.planet-lab.org/PLCAPI/', allow_none=True) - node_filter = {'hostname': '*.cert.org.cn'} - return_fields = ['hostname', 'site_id'] - all_nodes = api_server.GetNodes(self.api_auth(), node_filter, boot_state_filter) - pprint(all_nodes) - return all_nodes - - def _add_nodes_to_slice(self): - api_server = xmlrpclib.ServerProxy('https://www.planet-lab.org/PLCAPI/', allow_none=True) - all_nodes = self.search_for_nodes() - for node in all_nodes: - api_server.AddNode(self.api_auth(), node['site_id'], all_nodes) - print 'Adding nodes %s' % node['hostname'] - - def _auth_login(slicename, machinename): - """Attempt to authenticate to the given PL node, slicename and - machinename, using any of the private keys in ~/.ssh/ """ - - agent = paramiko.Agent() - agent_keys = agent.get_keys() - if len(agent_keys) == 0: - return - - for key in agent_keys: - print 'Trying ssh-agent key %s' % hexlify(key.get_fingerprint()), - try: - paramiko.transport.auth_publickey(machinename, slicename) - print 'Public key authentication to PlanetLab node %s successful.' % machinename, - return - except paramiko.SSHException: - print 'Public key authentication to PlanetLab node %s failed.' % machinename, - - def _get_command(): - pass - - def ssh_and_run_(slicename, machinename, command): - """Attempt to make a standard OpenSSH client to PL node, and run - commands from a .conf file.""" - - ## needs a way to specify 'ssh -l <slicename> <machinename>' - ## with public key authentication. - - command = PlanetLab.get_command() - - client = paramiko.SSHClient() - client.load_system_host_keys() - client.connect(machinename) - - stdin, stdout, stderr = client.exec_command(command) - - def send_files_to_node(directory, files): - """Attempt to rsync a tree to the PL node.""" - pass - - def add_unit(): - pass - - def get_status(): - pass diff --git a/ooni/bridget/utils/onion.py b/ooni/bridget/utils/onion.py deleted file mode 100644 index 9d4cae7..0000000 --- a/ooni/bridget/utils/onion.py +++ /dev/null @@ -1,686 +0,0 @@ -# -# onion.py -# ---------- -# Utilities for working with Tor. -# -# This code is largely taken from txtorcon and its documentation, and as such -# any and all credit should go to Meejah. Minor adjustments have been made to -# use OONI's logging system, and to build custom circuits without actually -# attaching streams. -# -# :author: Meejah, Isis Lovecruft -# :license: see included LICENSE file -# :copyright: copyright (c) 2012 The Tor Project, Inc. -# :version: 0.1.0-alpha -# -# XXX TODO add report keys for onion methods - -import random -import sys - -from twisted.internet import defer -from zope.interface import implements - -from ooni.lib.txtorcon import CircuitListenerMixin, IStreamAttacher -from ooni.lib.txtorcon import TorState, TorConfig -from ooni.utils import log -from ooni.utils.timer import deferred_timeout, TimeoutError - -def parse_data_dir(data_dir): - """ - Parse a string that a has been given as a DataDirectory and determine - its absolute path on the filesystem. - - :param data_dir: - A directory for Tor's DataDirectory, to be parsed. - :return: - The absolute path of :param:data_dir. - """ - from os import path, getcwd - import sys - - try: - assert isinstance(data_dir, str), \ - "Parameter type(data_dir) must be str" - except AssertionError, ae: - log.err(ae) - - if data_dir.startswith('~'): - data_dir = path.expanduser(data_dir) - elif data_dir.startswith('/'): - data_dir = path.join(getcwd(), data_dir) - elif data_dir.startswith('./'): - data_dir = path.abspath(data_dir) - else: - data_dir = path.join(getcwd(), data_dir) - - try: - assert path.isdir(data_dir), "Could not find %s" % data_dir - except AssertionError, ae: - log.err(ae) - sys.exit() - else: - return data_dir - -def write_torrc(conf, data_dir=None): - """ - Create a torrc in our data_dir. If we don't yet have a data_dir, create a - temporary one. Any temporary files or folders are added to delete_list. - - :param conf: - A :class:`ooni.lib.txtorcon.TorConfig` object, with all configuration - values saved. - :param data_dir: - The Tor DataDirectory to use. - :return: torrc, data_dir, delete_list - """ - try: - from os import write, close - from tempfile import mkstemp, mkdtemp - except ImportError, ie: - log.err(ie) - - delete_list = [] - - if data_dir is None: - data_dir = mkdtemp(prefix='bridget-tordata') - delete_list.append(data_dir) - conf.DataDirectory = data_dir - - (fd, torrc) = mkstemp(dir=data_dir) - delete_list.append(torrc) - write(fd, conf.create_torrc()) - close(fd) - - return torrc, data_dir, delete_list - -def delete_files_or_dirs(delete_list): - """ - Given a list of files or directories to delete, delete all and suppress - all errors. - - :param delete_list: - A list of files or directories to delete. - """ - try: - from os import unlink - from shutil import rmtree - except ImportError, ie: - log.err(ie) - - for temp in delete_list: - try: - unlink(temp) - except OSError: - rmtree(temp, ignore_errors=True) - -def remove_node_from_list(node, list): - for item in list: ## bridges don't match completely - if item.startswith(node): ## due to the :<port>. - try: - log.msg("Removing %s because it is a public relay" % node) - list.remove(item) - except ValueError, ve: - log.err(ve) - -def remove_public_relays(state, bridges): - """ - Remove bridges from our bridge list which are also listed as public - relays. This must be called after Tor has fully bootstrapped and we have a - :class:`ooni.lib.txtorcon.TorState` with the - :attr:`ooni.lib.txtorcon.TorState.routers` attribute assigned. - - XXX Does state.router.values() have all of the relays in the consensus, or - just the ones we know about so far? - - XXX FIXME: There is a problem in that Tor needs a Bridge line to already be - configured in order to bootstrap. However, after bootstrapping, we grab the - microdescriptors of all the relays and check if any of our bridges are - listed as public relays. Because of this, the first bridge does not get - checked for being a relay. - """ - IPs = map(lambda addr: addr.split(':',1)[0], bridges['all']) - both = set(state.routers.values()).intersection(IPs) - - if len(both) > 0: - try: - updated = map(lambda node: remove_node_from_list(node), both) - log.debug("Bridges in both: %s" % both) - log.debug("Updated = %s" % updated) - #if not updated: - # defer.returnValue(state) - #else: - # defer.returnValue(state) - return state - except Exception, e: - log.err("Removing public relays %s from bridge list failed:\n%s" - % (both, e)) - -def setup_done(proto): - log.msg("Setup Complete") - state = TorState(proto.tor_protocol) - state.post_bootstrap.addCallback(state_complete) - state.post_bootstrap.addErrback(setup_fail) - -def setup_fail(proto): - log.msg("Setup Failed:\n%s" % proto) - return proto - #reactor.stop() - -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 updates(_progress, _tag, _summary): - """Log updates on the Tor bootstrapping process.""" - log.msg("%d%%: %s" % (_progress, _summary)) - -def bootstrap(ctrl): - """ - Bootstrap Tor from an instance of - :class:`ooni.lib.txtorcon.TorControlProtocol`. - """ - conf = TorConfig(ctrl) - conf.post_bootstrap.addCallback(setup_done).addErrback(setup_fail) - log.msg("Tor process connected, bootstrapping ...") - -def start_tor(reactor, config, control_port, tor_binary, data_dir, - report=None, progress=updates, - process_cb=None, process_eb=None): - """ - Use a txtorcon.TorConfig() instance, config, to write a torrc to a - tempfile in our DataDirectory, data_dir. If data_dir is None, a temp - directory will be created. Finally, 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. - - :param reactor: - An instance of class:`twisted.internet.reactor`. - :param config: - An instance of class:`txtorcon.TorConfig` with all torrc options - already configured. ivar:`config.ControlPort`, - ivar:`config.SocksPort`, ivar:`config.CookieAuthentication`, should - already be set, as well as ivar:`config.UseBridges` and - ivar:`config.Bridge` if bridges are to be used. - ivar:`txtorcon.DataDirectory` does not need to be set. - :param control_port: - The port number to use for Tor's ControlPort. - :param tor_binary: - The full path to the Tor binary to use. - :param data_dir: - The directory to use as Tor's DataDirectory. - :param report: - The class:`ooni.plugoo.reports.Report` instance. - :param progress: - A non-blocking function to handle bootstrapping updates, which takes - three parameters: _progress, _tag, and _summary. - :param process_cb: - The function to callback to after - class:`ooni.lib.txtorcon.TorProcessProtocol` returns with the fully - bootstrapped Tor process. - :param process_eb: - The function to errback to if - class:`ooni.lib.txtorcon.TorProcessProtocol` fails. - :return: - The result of the callback of a - class:`ooni.lib.txtorcon.TorProcessProtocol` which callbacks with a - class:`txtorcon.TorControlProtocol` as .protocol. - """ - try: - from functools import partial - from twisted.internet.endpoints import TCP4ClientEndpoint - from ooni.lib.txtorcon import TorProtocolFactory - from ooni.lib.txtorcon import TorProcessProtocol - except ImportError, ie: - log.err(ie) - - ## TODO: add option to specify an already existing torrc, which - ## will require prior parsing to enforce necessary lines - (torrc, data_dir, to_delete) = write_torrc(config, data_dir) - - log.msg("Starting Tor ...") - log.msg("Using the following as our torrc:\n%s" % config.create_torrc()) - if report is None: - report = {'torrc': config.create_torrc()} - else: - report.update({'torrc': config.create_torrc()}) - - end_point = TCP4ClientEndpoint(reactor, 'localhost', control_port) - connection_creator = partial(end_point.connect, TorProtocolFactory()) - process_protocol = TorProcessProtocol(connection_creator, progress) - process_protocol.to_delete = to_delete - - if process_cb is not None and process_eb is not None: - process_protocol.connected_cb.addCallbacks(process_cb, process_eb) - - reactor.addSystemEventTrigger('before', 'shutdown', - partial(delete_files_or_dirs, to_delete)) - try: - transport = reactor.spawnProcess(process_protocol, - tor_binary, - args=(tor_binary,'-f',torrc), - env={'HOME': data_dir}, - path=data_dir) - transport.closeStdin() - except RuntimeError, e: - log.err("Starting Tor failed:") - process_protocol.connected_cb.errback(e) - except NotImplementedError, e: - url = "http://starship.python.net/crew/mhammond/win32/Downloads.html" - log.msg("Running bridget on Windows requires pywin32: %s" % url) - process_protocol.connected_cb.errback(e) - - return process_protocol.connected_cb - -@defer.inlineCallbacks -def start_tor_filter_nodes(reactor, config, control_port, tor_binary, - data_dir, bridges): - """ - Bootstrap a Tor process and return a fully-setup - :class:`ooni.lib.txtorcon.TorState`. Then search for our bridges - to test in the list of known public relays, - :ivar:`ooni.lib.txtorcon.TorState.routers`, and remove any bridges - which are known public relays. - - :param reactor: - The :class:`twisted.internet.reactor`. - :param config: - An instance of :class:`ooni.lib.txtorcon.TorConfig`. - :param control_port: - The port to use for Tor's ControlPort. If already configured in - the TorConfig instance, this can be given as - TorConfig.config.ControlPort. - :param tor_binary: - The full path to the Tor binary to execute. - :param data_dir: - The full path to the directory to use as Tor's DataDirectory. - :param bridges: - A dictionary which has a key 'all' which is a list of bridges to - test connecting to, e.g.: - bridges['all'] = ['1.1.1.1:443', '22.22.22.22:9001'] - :return: - A fully initialized :class:`ooni.lib.txtorcon.TorState`. - """ - setup = yield start_tor(reactor, config, control_port, - tor_binary, data_dir, - process_cb=setup_done, process_eb=setup_fail) - filter_nodes = yield remove_public_relays(setup, bridges) - defer.returnValue(filter_nodes) - -@defer.inlineCallbacks -def start_tor_with_timer(reactor, config, control_port, tor_binary, data_dir, - bridges, timeout): - """ - Start bootstrapping a Tor process wrapped with an instance of the class - decorator :func:`ooni.utils.timer.deferred_timeout` and complete callbacks - to either :func:`setup_done` or :func:`setup_fail`. Return a fully-setup - :class:`ooni.lib.txtorcon.TorState`. Then search for our bridges to test - in the list of known public relays, - :ivar:`ooni.lib.txtorcon.TorState.routers`, and remove any bridges which - are listed as known public relays. - - :param reactor: - The :class:`twisted.internet.reactor`. - :param config: - An instance of :class:`ooni.lib.txtorcon.TorConfig`. - :param control_port: - The port to use for Tor's ControlPort. If already configured in - the TorConfig instance, this can be given as - TorConfig.config.ControlPort. - :param tor_binary: - The full path to the Tor binary to execute. - :param data_dir: - The full path to the directory to use as Tor's DataDirectory. - :param bridges: - A dictionary which has a key 'all' which is a list of bridges to - test connecting to, e.g.: - bridges['all'] = ['1.1.1.1:443', '22.22.22.22:9001'] - :param timeout: - The number of seconds to attempt to bootstrap the Tor process before - raising a :class:`ooni.utils.timer.TimeoutError`. - :return: - If the timeout limit is not exceeded, return a fully initialized - :class:`ooni.lib.txtorcon.TorState`, else return None. - """ - error_msg = "Bootstrapping has exceeded the timeout limit..." - with_timeout = deferred_timeout(timeout, e=error_msg)(start_tor) - try: - setup = yield with_timeout(reactor, config, control_port, tor_binary, - data_dir, process_cb=setup_done, - process_eb=setup_fail) - except TimeoutError, te: - log.err(te) - defer.returnValue(None) - #except Exception, e: - # log.err(e) - # defer.returnValue(None) - else: - state = yield remove_public_relays(setup, bridges) - defer.returnValue(state) - -@defer.inlineCallbacks -def start_tor_filter_nodes_with_timer(reactor, config, control_port, - tor_binary, data_dir, bridges, timeout): - """ - Start bootstrapping a Tor process wrapped with an instance of the class - decorator :func:`ooni.utils.timer.deferred_timeout` and complete callbacks - to either :func:`setup_done` or :func:`setup_fail`. Then, filter our list - of bridges to remove known public relays by calling back to - :func:`remove_public_relays`. Return a fully-setup - :class:`ooni.lib.txtorcon.TorState`. Then search for our bridges to test - in the list of known public relays, - :ivar:`ooni.lib.txtorcon.TorState.routers`, and remove any bridges which - are listed as known public relays. - - :param reactor: - The :class:`twisted.internet.reactor`. - :param config: - An instance of :class:`ooni.lib.txtorcon.TorConfig`. - :param control_port: - The port to use for Tor's ControlPort. If already configured in - the TorConfig instance, this can be given as - TorConfig.config.ControlPort. - :param tor_binary: - The full path to the Tor binary to execute. - :param data_dir: - The full path to the directory to use as Tor's DataDirectory. - :param bridges: - A dictionary which has a key 'all' which is a list of bridges to - test connecting to, e.g.: - bridges['all'] = ['1.1.1.1:443', '22.22.22.22:9001'] - :param timeout: - The number of seconds to attempt to bootstrap the Tor process before - raising a :class:`ooni.utils.timer.TimeoutError`. - :return: - If the timeout limit is not exceeded, return a fully initialized - :class:`ooni.lib.txtorcon.TorState`, else return None. - """ - error_msg = "Bootstrapping has exceeded the timeout limit..." - with_timeout = deferred_timeout(timeout, e=error_msg)(start_tor_filter_nodes) - try: - state = yield with_timeout(reactor, config, control_port, - tor_binary, data_dir, bridges) - except TimeoutError, te: - log.err(te) - defer.returnValue(None) - #except Exception, e: - # log.err(e) - # defer.returnValue(None) - else: - defer.returnValue(state) - -class CustomCircuit(CircuitListenerMixin): - """ - Utility class for controlling circuit building. See - 'attach_streams_by_country.py' in the txtorcon documentation. - - :param state: - A fully bootstrapped instance of :class:`ooni.lib.txtorcon.TorState`. - :param relays: - A dictionary containing a key 'all', which is a list of relays to - test connecting to. - :ivar waiting_circuits: - The list of circuits which we are waiting to attach to. You shouldn't - need to touch this. - """ - implements(IStreamAttacher) - - def __init__(self, state, relays=None): - self.state = state - self.waiting_circuits = [] - self.relays = relays - - def waiting_on(self, circuit): - """ - Whether or not we are waiting on the given circuit before attaching to - it. - - :param circuit: - An item from :ivar:`ooni.lib.txtorcon.TorState.circuits`. - :return: - True if we are waiting on the circuit, False if not waiting. - """ - for (circid, d) in self.waiting_circuits: - if circuit.id == circid: - return True - return False - - def circuit_extend(self, circuit, router): - "ICircuitListener" - if circuit.purpose != 'GENERAL': - return - if self.waiting_on(circuit): - log.msg("Circuit %d (%s)" % (circuit.id, router.id_hex)) - - def circuit_built(self, circuit): - "ICircuitListener" - if circuit.purpose != 'GENERAL': - return - log.msg("Circuit %s built ..." % circuit.id) - 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)) - d.callback(circuit) - - def circuit_failed(self, circuit, reason): - """ - If building a circuit has failed, try to remove it from our list of - :ivar:`waiting_circuits`, else request to build it. - - :param circuit: - An item from :ivar:`ooni.lib.txtorcon.TorState.circuits`. - :param reason: - A :class:`twisted.python.fail.Failure` instance. - :return: - None - """ - if self.waiting_on(circuit): - log.msg("Circuit %s failed for reason %s" % (circuit.id, reason)) - circid, d = None, None - for c in self.waiting_circuits: - if c[0] == circuit.id: - circid, d = c - if d is None: - raise Exception("Expected to find circuit.") - - self.waiting_circuits.remove((circid, d)) - log.msg("Trying to build a circuit for %s" % circid) - self.request_circuit_build(d) - - def check_circuit_route(self, router): - """ - Check if a relay is a hop in one of our already built circuits. - - :param router: - An item from the list - :func:`ooni.lib.txtorcon.TorState.routers.values()`. - """ - for circ in self.state.circuits.values(): - if router in circ.path: - #router.update() ## XXX can i use without args? no. - TorInfo.dump(self) - - def request_circuit_build(self, deferred, path=None): - """ - Request a custom circuit. - - :param deferred: - A :class:`twisted.internet.defer.Deferred` for this circuit. - :param path: - A list of router ids to build a circuit from. The length of this - list must be at least three. - """ - if path is None: - - pick = self.relays['all'].pop - n = self.state.entry_guards.values() - choose = random.choice - - first, middle, last = (None for i in range(3)) - - if self.relays['remaining']() >= 3: - first, middle, last = (pick() for i in range(3)) - elif self.relays['remaining']() < 3: - first = choose(n) - middle = pick() - if self.relays['remaining'] == 2: - middle, last = (pick() for i in range(2)) - elif self.relay['remaining'] == 1: - middle = pick() - last = choose(n) - else: - log.msg("Qu'est-que fuque?") - else: - middle, last = (random.choice(self.state.routers.values()) - for i in range(2)) - - path = [first, middle, last] - - else: - assert isinstance(path, list), \ - "Circuit path must be a list of relays!" - assert len(path) >= 3, \ - "Circuit path must be at least three hops!" - - log.msg("Requesting a circuit: %s" - % '->'.join(map(lambda node: node, path))) - - class AppendWaiting: - def __init__(self, attacher, deferred): - self.attacher = attacher - self.d = deferred - 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 ..." % circ.id) - self.attacher.waiting_circuits.append((circ.id, self.d)) - - return self.state.build_circuit(path).addCallback( - AppendWaiting(self, deferred)).addErrback( - log.err) - -class TxtorconImportError(ImportError): - """ - Raised when ooni.lib.txtorcon cannot be imported from. Checks our current - working directory and the path given to see if txtorcon has been - initialized via /ooni/lib/Makefile. - """ - from os import getcwd, path - - cwd, tx = getcwd(), 'lib/txtorcon/torconfig.py' - try: - log.msg("Unable to import from ooni.lib.txtorcon") - if cwd.endswith('ooni'): - check = path.join(cwd, tx) - elif cwd.endswith('utils'): - check = path.join(cwd, '../'+tx) - else: - check = path.join(cwd, 'ooni/'+tx) - assert path.isfile(check) - except: - log.msg("Error: Some OONI libraries are missing!") - log.msg("Please go to /ooni/lib/ and do "make all"") - -class PTNoBridgesException(Exception): - """Raised when a pluggable transport is specified, but not 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() - -@defer.inlineCallbacks -def __start_tor_with_timer__(reactor, config, control_port, tor_binary, - data_dir, bridges=None, relays=None, timeout=None, - retry=None): - """ - A wrapper for :func:`start_tor` which wraps the bootstrapping of a Tor - process and its connection to a reactor with a - :class:`twisted.internet.defer.Deferred` class decorator utility, - :func:`ooni.utils.timer.deferred_timeout`, and a mechanism for resets. - - ## XXX fill me in - """ - raise NotImplementedError - - class RetryException(Exception): - pass - - import sys - from ooni.utils.timer import deferred_timeout, TimeoutError - - def __make_var__(old, default, _type): - if old is not None: - assert isinstance(old, _type) - new = old - else: - new = default - return new - - reactor = reactor - timeout = __make_var__(timeout, 120, int) - retry = __make_var__(retry, 1, int) - - with_timeout = deferred_timeout(timeout)(start_tor) - - @defer.inlineCallbacks - def __start_tor__(rc=reactor, cf=config, cp=control_port, tb=tor_binary, - dd=data_dir, br=bridges, rl=relays, cb=setup_done, - eb=setup_fail, af=remove_public_relays, retry=retry): - try: - setup = yield with_timeout(rc,cf,cp,tb,dd) - except TimeoutError: - retry -= 1 - defer.returnValue(retry) - else: - if setup.callback: - setup = yield cb(setup) - elif setup.errback: - setup = yield eb(setup) - else: - setup = setup - - if br is not None: - state = af(setup,br) - else: - state = setup - defer.returnValue(state) - - @defer.inlineCallbacks - def __try_until__(tries): - result = yield __start_tor__() - try: - assert isinstance(result, int) - except AssertionError: - defer.returnValue(result) - else: - if result >= 0: - tried = yield __try_until__(result) - defer.returnValue(tried) - else: - raise RetryException - try: - tried = yield __try_until__(retry) - except RetryException: - log.msg("All retry attempts to bootstrap Tor have timed out.") - log.msg("Exiting ...") - defer.returnValue(sys.exit()) - else: - defer.returnValue(tried) diff --git a/ooni/bridget/utils/reports.py b/ooni/bridget/utils/reports.py deleted file mode 100644 index ae67b13..0000000 --- a/ooni/bridget/utils/reports.py +++ /dev/null @@ -1,144 +0,0 @@ -from __future__ import with_statement - -import os -import yaml - -import itertools -from ooni.utils import log, date, net - -class Report: - """This is the ooni-probe reporting mechanism. It allows - reporting to multiple destinations and file formats. - - :scp the string of <host>:<port> of an ssh server - - :yaml the filename of a the yaml file to write - - :file the filename of a simple txt file to write - - :tcp the <host>:<port> of a TCP server that will just listen for - inbound connection and accept a stream of data (think of it - as a `nc -l -p <port> > filename.txt`) - """ - def __init__(self, testname=None, file="report.log", - scp=None, - tcp=None): - - self.testname = testname - self.file = file - self.tcp = tcp - self.scp = scp - #self.config = ooni.config.report - - #if self.config.timestamp: - # tmp = self.file.split('.') - # self.file = '.'.join(tmp[:-1]) + "-" + \ - # datetime.now().isoformat('-') + '.' + \ - # tmp[-1] - # print self.file - - self.scp = None - self.write_header() - - def write_header(self): - pretty_date = date.pretty_date() - header = "# OONI Probe Report for Test %s\n" % self.testname - header += "# %s\n\n" % pretty_date - self._write_to_report(header) - # XXX replace this with something proper - address = net.getClientAddress() - test_details = {'start_time': str(date.now()), - 'asn': address['asn'], - 'test_name': self.testname, - 'addr': address['ip']} - self(test_details) - - def _write_to_report(self, dump): - reports = [] - - if self.file: - reports.append("file") - - if self.tcp: - reports.append("tcp") - - if self.scp: - reports.append("scp") - - #XXX make this non blocking - for report in reports: - self.send_report(dump, report) - - def __call__(self, data): - """ - This should be invoked every time you wish to write some - data to the reporting system - """ - dump = yaml.dump([data]) - self._write_to_report(dump) - - def file_report(self, data): - """ - This reports to a file in YAML format - """ - with open(self.file, 'a+') as f: - f.write(data) - - def send_report(self, data, type): - """ - This sends the report using the - specified type. - """ - #print "Reporting %s to %s" % (data, type) - log.msg("Reporting to %s" % type) - getattr(self, type+"_report").__call__(data) - -class NewReport(object): - filename = 'report.log' - startTime = None - endTime = None - testName = None - ipAddr = None - asnAddr = None - - def _open(): - self.fp = open(self.filename, 'a+') - - @property - def header(): - pretty_date = date.pretty_date() - report_header = "# OONI Probe Report for Test %s\n" % self.testName - report_header += "# %s\n\n" % pretty_date - test_details = {'start_time': self.startTime, - 'asn': asnAddr, - 'test_name': self.testName, - 'addr': ipAddr} - report_header += yaml.dump([test_details]) - return report_header - - def create(): - """ - Create a new report by writing it's header. - """ - self.fp = open(self.filename, 'w+') - self.fp.write(self.header) - - def exists(): - """ - Returns False if the file does not exists. - """ - return os.path.exists(self.filename) - - def write(data): - """ - Write a report to the file. - - :data: python data structure to be written to report. - """ - if not self.exists(): - self.create() - else: - self._open() - yaml_encoded_data = yaml.dump([data]) - self.fp.write(yaml_encoded_data) - self.fp.close() diff --git a/ooni/bridget/utils/tests.py b/ooni/bridget/utils/tests.py deleted file mode 100644 index ea4be0b..0000000 --- a/ooni/bridget/utils/tests.py +++ /dev/null @@ -1,141 +0,0 @@ -import os -import yaml -from zope.interface import Interface, Attribute - -import logging -import itertools -from twisted.internet import reactor, defer, threads -## XXX why is this imported and not used? -from twisted.python import failure - -from ooni.utils import log, date -from ooni.plugoo import assets, work -from ooni.plugoo.reports import Report -from ooni.plugoo.interface import ITest - -class OONITest(object): - """ - This is the base class for writing OONI Tests. - - It should be used in conjunction with the ITest Interface. It allows the - developer to benefit from OONIs reporting system and command line argument - parsing system. - """ - name = "oonitest" - # By default we set this to False, meaning that we don't block - blocking = False - reactor = reactor - tool = False - ended = False - - def __init__(self, local_options, global_options, report, ooninet=None, - reactor=reactor): - # These are the options that are read through the tests suboptions - self.local_options = local_options - # These are the options global to all of OONI - self.global_options = global_options - self.report = report - #self.ooninet = ooninet - self.reactor = reactor - self.result = {} - self.initialize() - self.assets = self.load_assets() - - def initialize(self): - """ - Override this method if you are interested in having some extra - behavior when your test class is instantiated. - """ - pass - - def load_assets(self): - """ - This method should be overriden by the test writer to provide the - logic for loading their assets. - """ - return {} - - def __repr__(self): - return "<OONITest %s %s %s>" % (self.local_options, - self.global_options, - self.assets) - - def end(self): - """ - State that the current test should finish. - """ - self.ended = True - - def finished(self, return_value): - """ - The Test has finished running, we must now calculate the test runtime - and add all time data to the report. - """ - #self.ooninet.report(result) - self.end_time = date.now() - result = self.result - result['start_time'] = str(self.start_time) - result['end_time'] = str(self.end_time) - result['run_time'] = str(self.end_time - self.start_time) - result['return_value'] = return_value - log.msg("FINISHED %s" % result) - self.report(result) - return result - - def _do_experiment(self, args): - """ - A wrapper around the launch of experiment. - If we are running a blocking test experiment will be run in a thread if - not we expect it to return a Deferred. - - @param args: the asset line(s) that we are working on. - - returns a deferred. - """ - if self.blocking: - self.d = threads.deferToThread(self.experiment, args) - else: - self.d = self.experiment(args) - - self.d.addCallback(self.control, args) - self.d.addCallback(self.finished) - self.d.addErrback(self.finished) - return self.d - - def control(self, result, args): - """ - Run the control. - - @param result: what was returned by experiment. - - @param args: the asset(s) lines that we are working on. - """ - log.msg("Doing control") - return result - - def experiment(self, args): - """ - Run the experiment. This sample implementation returns a deferred, - making it a non-blocking test. - - @param args: the asset(s) lines that we are working on. - """ - log.msg("Doing experiment") - d = defer.Deferred() - return d - - def startTest(self, args): - """ - This method is invoked by the worker to start the test with one line of - the asset file. - - @param args: the asset(s) lines that we are working on. - """ - self.start_time = date.now() - - if self.shortName: - log.msg("Starting test %s" % self.shortName) - else: - log.msg("Starting test %s" % self.__class__) - - return self._do_experiment(args) diff --git a/ooni/bridget/utils/work.py b/ooni/bridget/utils/work.py deleted file mode 100644 index c329c20..0000000 --- a/ooni/bridget/utils/work.py +++ /dev/null @@ -1,147 +0,0 @@ -# -*- coding: UTF-8 -""" - work.py - ********** - - This contains all code related to generating - Units of Work and processing it. - - :copyright: (c) 2012 by Arturo Filastò. - :license: see LICENSE for more details. - -""" -import itertools -import yaml -from datetime import datetime - -from zope.interface import Interface, Attribute - -from twisted.python import failure -from twisted.internet import reactor, defer - -class Worker(object): - """ - This is the core of OONI. It takes as input Work Units and - runs them concurrently. - """ - def __init__(self, maxconcurrent=10, reactor=reactor): - """ - @param maxconcurrent: how many test instances should be run - concurrently. - """ - self.reactor = reactor - self.maxconcurrent = maxconcurrent - self._running = 0 - self._queued = [] - - def _run(self, r): - """ - Check if we should start another test because we are below maximum - concurrency. - - This function is called every time a test finishes running. - - @param r: the return value of a previous test. - """ - if self._running > 0: - self._running -= 1 - - if self._running < self.maxconcurrent and self._queued: - workunit, d = self._queued.pop(0) - asset, test, idx = workunit - while test.ended and workunit: - try: - workunit, d = self._queued.pop(0) - asset, test, idx = workunit - except: - workunit = None - - if not test.ended: - self._running += 1 - actuald = test.startTest(asset).addBoth(self._run) - - if isinstance(r, failure.Failure): - # XXX probably we should be doing something to retry test running - r.trap() - - if self._running == 0 and not self._queued: - self.reactor.stop() - - return r - - def push(self, workunit): - """ - Add a test to the test queue and run it if we are not maxed out on - concurrency. - - @param workunit: a tuple containing the (asset, test, idx), where asset - is the line of the asset(s) we are working on, test - is an instantiated test and idx is the index we are - currently at. - """ - if self._running < self.maxconcurrent: - asset, test, idx = workunit - if not test.ended: - self._running += 1 - return test.startTest(asset).addBoth(self._run) - - d = defer.Deferred() - self._queued.append((workunit, d)) - return d - -class WorkGenerator(object): - """ - Factory responsible for creating units of work. - - This shall be run on the machine running OONI-cli. The returned WorkUnits - can either be run locally or on a remote OONI Node or Network Node. - """ - size = 10 - - def __init__(self, test, arguments=None, start=None): - self.Test = test - - if self.Test.assets and self.Test.assets.values()[0]: - self.assetGenerator = itertools.product(*self.Test.assets.values()) - else: - self.assetGenerator = None - - self.assetNames = self.Test.assets.keys() - - self.idx = 0 - self.end = False - if start: - self.skip(start) - - def __iter__(self): - return self - - def skip(self, start): - """ - Skip the first x number of lines of the asset. - - @param start: int how many items we should skip. - """ - for j in xrange(0, start-1): - for i in xrange(0, self.size): - self.assetGenerator.next() - self.idx += 1 - - def next(self): - if self.end: - raise StopIteration - - if not self.assetGenerator: - self.end = True - return ({}, self.Test, self.idx) - - try: - asset = self.assetGenerator.next() - ret = {} - for i, v in enumerate(asset): - ret[self.assetNames[i]] = v - except StopIteration: - raise StopIteration - - self.idx += 1 - return (ret, self.Test, self.idx)
tor-commits@lists.torproject.org