commit a65b6ebb7e90a4350b1a214189196d11a8371eda Author: Arturo Filastò art@fuffa.org Date: Mon Nov 26 22:49:14 2012 +0100
Add support for starting Tor via txtorcon * XXX would like to be able to know if Tor is censored and add it to the report, this can easily be done if running launch_tor with a timeout. This is provided by this patch: https://github.com/meejah/txtorcon/pull/24 --- ooni/config.py | 10 ++++- ooni/runner.py | 97 +++++++++++++++++++++++++++++++++++++++++++++++++---- ooni/utils/net.py | 26 ++++++++++++++ ooniprobe.conf | 9 ++++- 4 files changed, 132 insertions(+), 10 deletions(-)
diff --git a/ooni/config.py b/ooni/config.py index 69842b6..edfb196 100644 --- a/ooni/config.py +++ b/ooni/config.py @@ -65,7 +65,13 @@ def loadConfigFile(): advanced = Storage() for k, v in configuration['advanced'].items(): advanced[k] = v - return basic, privacy, advanced + + # Process the tor configuration options + tor = Storage() + for k, v in configuration['tor'].items(): + tor[k] = v + + return basic, privacy, advanced, tor
class TestFilenameNotSet(Exception): pass @@ -85,7 +91,7 @@ def generateReportFilenames():
if not basic: # Here we make sure that we instance the config file attributes only once - basic, privacy, advanced = loadConfigFile() + basic, privacy, advanced, tor = loadConfigFile()
if not resume_filename: resume_filename = os.path.join(get_root_path(), 'ooniprobe.resume') diff --git a/ooni/runner.py b/ooni/runner.py index 6ea4a86..6940ec6 100644 --- a/ooni/runner.py +++ b/ooni/runner.py @@ -21,12 +21,20 @@ from twisted.internet import defer from twisted.trial.runner import filenameToModule from twisted.internet import reactor, threads
+from txtorcon import TorProtocolFactory, TorConfig +from txtorcon import TorState, launch_tor + +from ooni import config + +from ooni.reporter import OONIBReporter, YAMLReporter +from ooni.reporter import OONIBReportCreationFailed + from ooni.inputunit import InputUnitFactory from ooni.nettest import NetTestCase, NoPostProcessor
-from ooni import reporter, config - -from ooni.utils import log, checkForRoot, NotRootError, Storage +from ooni.utils import log, checkForRoot +from ooni.utils import NotRootError, Storage +from ooni.utils.net import randomFreePort
def processTest(obj): """ @@ -374,10 +382,10 @@ def runTestCases(test_cases, options, cmd_line_options):
if cmd_line_options['collector']: log.msg("Using remote collector, please be patient while we create the report.") - oreporter = reporter.OONIBReporter(cmd_line_options) + oreporter = OONIBReporter(cmd_line_options) else: log.msg("Reporting to file %s" % config.reports.yamloo) - oreporter = reporter.YAMLReporter(cmd_line_options) + oreporter = YAMLReporter(cmd_line_options)
try: input_unit_factory = InputUnitFactory(test_inputs) @@ -386,7 +394,7 @@ def runTestCases(test_cases, options, cmd_line_options):
try: yield oreporter.createReport(options) - except reporter.OONIBReportCreationFailed: + except OONIBReportCreationFailed: log.err("Error in creating new report") raise except Exception, e: @@ -421,6 +429,63 @@ def runTestCases(test_cases, options, cmd_line_options): except Exception: log.exception("Problem in running test")
+class UnableToStartTor(Exception): + pass + +def startTor(): + + @defer.inlineCallbacks + def state_complete(state): + config.tor_state = state + log.msg("Successfully bootstrapped Tor") + log.debug("We now have the following circuits: ") + for circuit in state.circuits.values(): + log.debug(" * %s" % circuit) + config.tor.socks_port = yield state.protocol.get_conf("SocksPort") + config.tor.control_port = yield state.protocol.get_conf("ControlPort") + + def setup_failed(arg): + log.exception(arg) + raise UnableTorStartTor + + def setup_complete(proto): + """ + Called when we read from stdout that Tor has reached 100%. + """ + log.debug("Building a TorState") + state = TorState(proto.tor_protocol) + state.post_bootstrap.addCallback(state_complete) + state.post_bootstrap.addErrback(setup_failed) + return state.post_bootstrap + + def updates(prog, tag, summary): + log.msg("%d%%: %s" % (prog, summary)) + + tor_config = TorConfig() + if config.tor.control_port: + tor_config.ControlPort = config.tor.control_port + else: + control_port = int(randomFreePort()) + tor_config.ControlPort = control_port + config.tor.control_port = control_port + + if config.tor.socks_port: + tor_config.SocksPort = config.tor.socks_port + else: + socks_port = int(randomFreePort()) + tor_config.SocksPort = socks_port + config.tor.socks_port = socks_port + + tor_config.save() + + log.debug("Setting control port as %s" % tor_config.ControlPort) + log.debug("Setting SOCKS port as %s" % tor_config.SocksPort) + + d = launch_tor(tor_config, reactor, progress_updates=updates) + d.addCallback(setup_complete) + d.addErrback(setup_failed) + return d + def runTest(cmd_line_options): config.cmd_line_options = cmd_line_options config.generateReportFilenames() @@ -436,6 +501,7 @@ def runTest(cmd_line_options):
classes = findTestClassesFromFile(cmd_line_options['test']) test_cases, options = loadTestsAndOptions(classes, cmd_line_options) + if config.privacy.includepcap: from ooni.utils.txscapy import ScapyFactory, ScapySniffer try: @@ -451,4 +517,21 @@ def runTest(cmd_line_options): sniffer = ScapySniffer(config.reports.pcap) config.scapyFactory.registerProtocol(sniffer)
- return runTestCases(test_cases, options, cmd_line_options) + # If we should start Tor then start it. Once Tor has started we will make + # sure. If not we assume that it is already running + if config.advanced.start_tor: + def tor_startup_failed(failure): + log.err(failure) + + def tor_started(): + return runTestCases(test_cases, + options, cmd_line_options) + + d = startTor() + d.addCallback(tor_started) + d.addErrback(tor_startup_failed) + return d + + else: + return runTestCases(test_cases, + options, cmd_line_options) diff --git a/ooni/utils/net.py b/ooni/utils/net.py index df98412..12d1939 100644 --- a/ooni/utils/net.py +++ b/ooni/utils/net.py @@ -10,6 +10,8 @@ # see attached LICENCE file
import sys +import socket +from random import randint
from zope.interface import implements from twisted.internet import protocol, defer @@ -123,6 +125,30 @@ def getIfaces(platform_name=None): else: raise UnsupportedPlatform
+def randomFreePort(addr="127.0.0.1"): + """ + Args: + + addr (str): the IP address to attempt to bind to. + + Returns an int representing the free port number at the moment of calling + + Note: there is no guarantee that some other application will attempt to + bind to this port once this function has been called. + """ + free = False + while not free: + port = randint(1024, 65535) + s = socket.socket() + try: + s.bind((addr, port)) + free = True + except: + pass + s.close() + return port + + def checkInterfaces(ifaces=None, timeout=1): """ @param ifaces: diff --git a/ooniprobe.conf b/ooniprobe.conf index 5fa0743..6777fde 100644 --- a/ooniprobe.conf +++ b/ooniprobe.conf @@ -23,9 +23,16 @@ advanced: geoip_data_dir: /usr/share/GeoIP/ debug: true threadpool_size: 10 - tor_socksport: 9050 + + tor_binary: '/usr/sbin/tor' # For auto detection interface: auto # Of specify a specific interface #interface: wlan0 + start_tor: true +tor: + #socks_port: 9050 + #control_port: 9051 + # Specify the absolute path to the Tor bridges to use for testing + bridges: bridges.list
tor-commits@lists.torproject.org