示例#1
0
    def __init__(self):
        self.netTests = []
        self.activeNetTests = []

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = self

        self.reportEntryManager = ReportEntryManager()
        self.reportEntryManager.director = self

        self.successfulMeasurements = 0
        self.failedMeasurements = 0

        self.totalMeasurements = 0

        # The cumulative runtime of all the measurements
        self.totalMeasurementRuntime = 0

        self.failures = []

        self.torControlProtocol = None

        # This deferred is fired once all the measurements and their reporting
        # tasks are completed.
        self.allTestsDone = defer.Deferred()
        self.sniffer = None
示例#2
0
    def __init__(self):
        self.activeNetTests = []

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = self

        self.reportEntryManager = ReportEntryManager()
        self.reportEntryManager.director = self
        # Link the TaskManager's by least available slots.
        self.measurementManager.child = self.reportEntryManager
        # Notify the parent when tasks complete # XXX deadlock!?
        self.reportEntryManager.parent = self.measurementManager

        self.successfulMeasurements = 0
        self.failedMeasurements = 0

        self.totalMeasurements = 0

        # The cumulative runtime of all the measurements
        self.totalMeasurementRuntime = 0

        self.failures = []

        self.torControlProtocol = None

        # This deferred is fired once all the measurements and their reporting
        # tasks are completed.
        self.allTestsDone = defer.Deferred()
        self.sniffers = {}
示例#3
0
    def setUp(self):
        mock_director = MockDirector()

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = mock_director

        self.measurementManager.concurrency = 10
        self.measurementManager.retries = 2

        self.measurementManager.start()

        self.mockNetTest = MockNetTest()
示例#4
0
    def __init__(self):
        self.activeNetTests = []

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = self

        self.reportEntryManager = ReportEntryManager()
        self.reportEntryManager.director = self
        # Link the TaskManager's by least available slots.
        self.measurementManager.child = self.reportEntryManager
        # Notify the parent when tasks complete # XXX deadlock!?
        self.reportEntryManager.parent = self.measurementManager

        self.successfulMeasurements = 0
        self.failedMeasurements = 0

        self.totalMeasurements = 0

        # The cumulative runtime of all the measurements
        self.totalMeasurementRuntime = 0

        self.failures = []

        self.torControlProtocol = None

        # This deferred is fired once all the measurements and their reporting
        # tasks are completed.
        self.allTestsDone = defer.Deferred()
        self.sniffer = None
示例#5
0
    def __init__(self):
        self.netTests = []
        self.activeNetTests = []

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = self

        self.reportEntryManager = ReportEntryManager()
        self.reportEntryManager.director = self

        self.successfulMeasurements = 0
        self.failedMeasurements = 0

        self.totalMeasurements = 0

        # The cumulative runtime of all the measurements
        self.totalMeasurementRuntime = 0

        self.failures = []

        self.torControlProtocol = None

        # This deferred is fired once all the measurements and their reporting
        # tasks are completed.
        self.allTestsDone = defer.Deferred()
        self.sniffer = None
示例#6
0
    def setUp(self):
        mock_director = MockDirector()

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = mock_director

        self.measurementManager.concurrency = 10
        self.measurementManager.retries = 2

        self.measurementManager.start()

        self.mockNetTest = MockNetTest()
示例#7
0
    def __init__(self):
        self.netTests = []
        self.activeNetTests = []

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = self

        self.reportEntryManager = ReportEntryManager()
        self.reportEntryManager.director = self

        self.successfulMeasurements = 0
        self.failedMeasurements = 0

        self.totalMeasurements = 0

        # The cumulative runtime of all the measurements
        self.totalMeasurementRuntime = 0

        self.failures = []

        self.torControlProtocol = None
示例#8
0
    def __init__(self):
        self.activeNetTests = []
        self.activeDecks = []

        self.activeMeasurements = {}

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = self

        self.reportEntryManager = ReportEntryManager()
        self.reportEntryManager.director = self
        # Link the TaskManager's by least available slots.
        self.measurementManager.child = self.reportEntryManager
        # Notify the parent when tasks complete # XXX deadlock!?
        self.reportEntryManager.parent = self.measurementManager

        self.successfulMeasurements = 0
        self.failedMeasurements = 0

        self.totalMeasurements = 0

        # The cumulative runtime of all the measurements
        self.totalMeasurementRuntime = 0

        self.failures = []

        # This deferred is fired once all the measurements and their reporting
        # tasks are completed.
        self.allTestsDone = defer.Deferred()
        self.sniffers = {}

        self.input_store = input_store
        self.deck_store = deck_store

        self._reset_director_state()
        self._reset_tor_state()

        self._subscribers = []
示例#9
0
class TestMeasurementManager(unittest.TestCase):
    def setUp(self):
        mock_director = MockDirector()

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = mock_director

        self.measurementManager.concurrency = 10
        self.measurementManager.retries = 2

        self.measurementManager.start()

        self.mockNetTest = MockNetTest()

    def test_schedule_and_net_test_notified(self, number=1):
        # XXX we should probably be inheriting from the base test class
        mock_task = MockSuccessMeasurement(self.mockNetTest)
        self.measurementManager.schedule(mock_task)

        @mock_task.done.addCallback
        def done(res):
            self.assertEqual(self.mockNetTest.successes,
                             [42])

            self.assertEqual(len(self.mockNetTest.successes), 1)

        return mock_task.done

    def test_schedule_failing_one_measurement(self):
        mock_task = MockFailMeasurement(self.mockNetTest)
        self.measurementManager.schedule(mock_task)

        @mock_task.done.addErrback
        def done(failure):
            self.assertEqual(self.measurementManager.failures, 3)
            # self.assertEqual(len(self.measurementManager.failures), 3)

            self.assertEqual(failure, mockFailure)
            self.assertEqual(len(self.mockNetTest.successes), 0)

        return mock_task.done
示例#10
0
class TestMeasurementManager(unittest.TestCase):
    def setUp(self):
        mock_director = MockDirector()

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = mock_director

        self.measurementManager.concurrency = 10
        self.measurementManager.retries = 2

        self.measurementManager.start()

        self.mockNetTest = MockNetTest()

    def test_schedule_and_net_test_notified(self, number=1):
        # XXX we should probably be inheriting from the base test class
        mock_task = MockSuccessMeasurement(self.mockNetTest)
        self.measurementManager.schedule(mock_task)

        @mock_task.done.addCallback
        def done(res):
            self.assertEqual(self.mockNetTest.successes, [42])

            self.assertEqual(len(self.mockNetTest.successes), 1)

        return mock_task.done

    def test_schedule_failing_one_measurement(self):
        mock_task = MockFailMeasurement(self.mockNetTest)
        self.measurementManager.schedule(mock_task)

        @mock_task.done.addErrback
        def done(failure):
            self.assertEqual(self.measurementManager.failures, 3)
            # self.assertEqual(len(self.measurementManager.failures), 3)

            self.assertEqual(failure, mockFailure)
            self.assertEqual(len(self.mockNetTest.successes), 0)

        return mock_task.done
示例#11
0
class Director(object):
    """
    Singleton object responsible for coordinating the Measurements Manager
    and the Reporting Manager.

    How this all looks like is as follows:

    +------------------------------------------------+
    |                   Director                     |<--+
    +------------------------------------------------+   |
        ^                                ^               |
        |        Measurement             |               |
    +---------+  [---------]    +--------------------+   |
    |         |                 | MeasurementManager |   |
    | NetTest |  [---------]    +--------------------+   |
    |         |                 | [----------------] |   |
    +---------+  [---------]    | [----------------] |   |
        |                       | [----------------] |   |
        |                       +--------------------+   |
        v                                                |
    +---------+   ReportEntry                            |
    |         |   [---------]    +--------------------+  |
    |  Report |                  | ReportEntryManager |  |
    |         |   [---------]    +--------------------+  |
    +---------+                  | [----------------] |  |
                  [---------]    | [----------------] |--
                                 | [----------------] |
                                 +--------------------+

    [------------] are Tasks

    +------+
    |      |  are TaskManagers
    +------+
    |      |
    +------+

    +------+
    |      |  are general purpose objects
    +------+

    """

    _scheduledTests = 0
    # Only list NetTests belonging to these categories
    categories = ['blocking', 'manipulation']

    def __init__(self):
        self.activeNetTests = []

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = self

        self.reportEntryManager = ReportEntryManager()
        self.reportEntryManager.director = self
        # Link the TaskManager's by least available slots.
        self.measurementManager.child = self.reportEntryManager
        # Notify the parent when tasks complete # XXX deadlock!?
        self.reportEntryManager.parent = self.measurementManager

        self.successfulMeasurements = 0
        self.failedMeasurements = 0

        self.totalMeasurements = 0

        # The cumulative runtime of all the measurements
        self.totalMeasurementRuntime = 0

        self.failures = []

        self.torControlProtocol = None

        # This deferred is fired once all the measurements and their reporting
        # tasks are completed.
        self.allTestsDone = defer.Deferred()
        self.sniffers = {}

    def getNetTests(self):
        nettests = {}

        def is_nettest(filename):
            return not filename == '__init__.py' and filename.endswith('.py')

        for category in self.categories:
            dirname = os.path.join(config.nettest_directory, category)
            # print path to all filenames.
            for filename in os.listdir(dirname):
                if is_nettest(filename):
                    net_test_file = os.path.join(dirname, filename)
                    try:
                        nettest = getNetTestInformation(net_test_file)
                    except:
                        log.err("Error processing %s" % filename)
                        continue
                    nettest['category'] = category.replace('/', '')

                    if nettest['id'] in nettests:
                        log.err(
                            "Found a two tests with the same name %s, %s" %
                            (net_test_file, nettests[nettest['id']]['path']))
                    else:
                        category = dirname.replace(config.nettest_directory,
                                                   '')
                        nettests[nettest['id']] = nettest

        return nettests

    @defer.inlineCallbacks
    def start(self, start_tor=False, check_incoherences=True):
        self.netTests = self.getNetTests()

        if start_tor:
            if check_incoherences:
                yield config.check_tor()
            if config.advanced.start_tor:
                yield self.startTor()
            elif config.tor.control_port:
                log.msg("Connecting to Tor Control Port...")
                yield self.getTorState()

        if config.global_options['no-geoip']:
            aux = [False]
            if config.global_options.get('annotations') is not None:
                annotations = [
                    k.lower()
                    for k in config.global_options['annotations'].keys()
                ]
                aux = map(lambda x: x in annotations,
                          ["city", "country", "asn"])
            if not all(aux):
                log.msg(
                    "You should add annotations for the country, city and ASN")
        else:
            yield config.probe_ip.lookup()

    @property
    def measurementSuccessRatio(self):
        if self.totalMeasurements == 0:
            return 0

        return self.successfulMeasurements / self.totalMeasurements

    @property
    def measurementFailureRatio(self):
        if self.totalMeasurements == 0:
            return 0

        return self.failedMeasurements / self.totalMeasurements

    @property
    def measurementSuccessRate(self):
        """
        The speed at which tests are succeeding globally.

        This means that fast tests that perform a lot of measurements will
        impact this value quite heavily.
        """
        if self.totalMeasurementRuntime == 0:
            return 0

        return self.successfulMeasurements / self.totalMeasurementRuntime

    @property
    def measurementFailureRate(self):
        """
        The speed at which tests are failing globally.
        """
        if self.totalMeasurementRuntime == 0:
            return 0

        return self.failedMeasurements / self.totalMeasurementRuntime

    def measurementTimedOut(self, measurement):
        """
        This gets called every time a measurement times out independenty from
        the fact that it gets re-scheduled or not.
        """
        pass

    def measurementStarted(self, measurement):
        self.totalMeasurements += 1

    def measurementSucceeded(self, result, measurement):
        log.debug("Successfully completed measurement: %s" % measurement)
        self.totalMeasurementRuntime += measurement.runtime
        self.successfulMeasurements += 1
        measurement.result = result
        test_name = test_class_name_to_name(measurement.testInstance.name)
        if test_name in self.sniffers:
            sniffer = self.sniffers[test_name]
            config.scapyFactory.unRegisterProtocol(sniffer)
            sniffer.close()
            del self.sniffers[test_name]
        return measurement

    def measurementFailed(self, failure, measurement):
        log.debug("Failed doing measurement: %s" % measurement)
        self.totalMeasurementRuntime += measurement.runtime

        self.failedMeasurements += 1
        measurement.result = failure
        return measurement

    def reporterFailed(self, failure, net_test):
        """
        This gets called every time a reporter is failing and has been removed
        from the reporters of a NetTest.
        Once a report has failed to be created that net_test will never use the
        reporter again.

        XXX hook some logic here.
        note: failure contains an extra attribute called failure.reporter
        """
        pass

    def netTestDone(self, net_test):
        self.activeNetTests.remove(net_test)
        if len(self.activeNetTests) == 0:
            self.allTestsDone.callback(None)

    @defer.inlineCallbacks
    def startNetTest(self,
                     net_test_loader,
                     report_filename,
                     collector_address=None):
        """
        Create the Report for the NetTest and start the report NetTest.

        Args:
            net_test_loader:
                an instance of :class:ooni.nettest.NetTestLoader
        """
        if self.allTestsDone.called:
            self.allTestsDone = defer.Deferred()

        if config.privacy.includepcap:
            self.startSniffing(net_test_loader.testDetails)

        report = Report(net_test_loader.testDetails, report_filename,
                        self.reportEntryManager, collector_address)

        net_test = NetTest(net_test_loader, report)
        net_test.director = self

        yield net_test.report.open()

        yield net_test.initializeInputProcessor()
        try:
            self.activeNetTests.append(net_test)
            self.measurementManager.schedule(net_test.generateMeasurements())

            yield net_test.done
            yield report.close()
        finally:
            self.netTestDone(net_test)

    def startSniffing(self, testDetails):
        """ Start sniffing with Scapy. Exits if required privileges (root) are not
        available.
        """
        from ooni.utils.txscapy import ScapySniffer, ScapyFactory

        if config.scapyFactory is None:
            config.scapyFactory = ScapyFactory(config.advanced.interface)

        if not config.reports.pcap:
            prefix = 'report'
        else:
            prefix = config.reports.pcap
        filename = config.global_options[
            'reportfile'] if 'reportfile' in config.global_options.keys(
            ) else None
        filename_pcap = generate_filename(testDetails,
                                          filename=filename,
                                          prefix=prefix,
                                          extension='pcap')
        if len(self.sniffers) > 0:
            pcap_filenames = set(sniffer.pcapwriter.filename
                                 for sniffer in self.sniffers.values())
            pcap_filenames.add(filename_pcap)
            log.msg(
                "pcap files %s can be messed up because several netTests are being executed in parallel."
                % ','.join(pcap_filenames))

        sniffer = ScapySniffer(filename_pcap)
        self.sniffers[testDetails['test_name']] = sniffer
        config.scapyFactory.registerProtocol(sniffer)
        log.msg("Starting packet capture to: %s" % filename_pcap)

    @defer.inlineCallbacks
    def getTorState(self):
        connection = TCP4ClientEndpoint(reactor, '127.0.0.1',
                                        config.tor.control_port)
        config.tor_state = yield build_tor_connection(connection)

    def startTor(self):
        """ Starts Tor
        Launches a Tor with :param: socks_port :param: control_port
        :param: tor_binary set in ooniprobe.conf
        """
        log.msg("Starting Tor...")

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

            socks_port = yield state.protocol.get_conf("SocksPort")
            control_port = yield state.protocol.get_conf("ControlPort")

            config.tor.socks_port = int(socks_port.values()[0])
            config.tor.control_port = int(control_port.values()[0])

        def setup_failed(failure):
            log.exception(failure)
            raise errors.UnableToStartTor

        def setup_complete(proto):
            """
            Called when we read from stdout that Tor has reached 100%.
            """
            log.debug("Building a TorState")
            config.tor.protocol = proto
            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

        if config.tor.socks_port:
            tor_config.SocksPort = config.tor.socks_port

        if config.tor.data_dir:
            data_dir = os.path.expanduser(config.tor.data_dir)

            if not os.path.exists(data_dir):
                log.msg("%s does not exist. Creating it." % data_dir)
                os.makedirs(data_dir)
            tor_config.DataDirectory = data_dir

        if config.tor.bridges:
            tor_config.UseBridges = 1
            if config.advanced.obfsproxy_binary:
                tor_config.ClientTransportPlugin = (
                    'obfs2,obfs3 exec %s managed' %
                    config.advanced.obfsproxy_binary)
            bridges = []
            with open(config.tor.bridges) as f:
                for bridge in f:
                    if 'obfs' in bridge:
                        if config.advanced.obfsproxy_binary:
                            bridges.append(bridge.strip())
                    else:
                        bridges.append(bridge.strip())
            tor_config.Bridge = bridges

        if config.tor.torrc:
            for i in config.tor.torrc.keys():
                setattr(tor_config, i, config.tor.torrc[i])

        if os.geteuid() == 0:
            tor_config.User = pwd.getpwuid(os.geteuid()).pw_name

        tor_config.save()

        if not hasattr(tor_config, 'ControlPort'):
            control_port = int(randomFreePort())
            tor_config.ControlPort = control_port
            config.tor.control_port = control_port

        if not hasattr(tor_config, 'SocksPort'):
            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)

        if config.advanced.tor_binary:
            d = launch_tor(tor_config,
                           reactor,
                           tor_binary=config.advanced.tor_binary,
                           progress_updates=updates)
        else:
            d = launch_tor(tor_config, reactor, progress_updates=updates)
        d.addCallback(setup_complete)
        d.addErrback(setup_failed)
        return d
示例#12
0
class Director(object):
    """
    Singleton object responsible for coordinating the Measurements Manager
    and the Reporting Manager.

    How this all looks like is as follows:

    +------------------------------------------------+
    |                   Director                     |<--+
    +------------------------------------------------+   |
        ^                                ^               |
        |        Measurement             |               |
    +---------+  [---------]    +--------------------+   |
    |         |                 | MeasurementManager |   |
    | NetTest |  [---------]    +--------------------+   |
    |         |                 | [----------------] |   |
    +---------+  [---------]    | [----------------] |   |
        |                       | [----------------] |   |
        |                       +--------------------+   |
        v                                                |
    +---------+   ReportEntry                            |
    |         |   [---------]    +--------------------+  |
    |  Report |                  | ReportEntryManager |  |
    |         |   [---------]    +--------------------+  |
    +---------+                  | [----------------] |  |
                  [---------]    | [----------------] |--
                                 | [----------------] |
                                 +--------------------+

    [------------] are Tasks

    +------+
    |      |  are TaskManagers
    +------+
    |      |
    +------+

    +------+
    |      |  are general purpose objects
    +------+

    """

    _scheduledTests = 0
    # Only list NetTests belonging to these categories
    categories = ['blocking', 'manipulation', 'third_party']

    def __init__(self):
        self.activeNetTests = []

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = self

        self.reportEntryManager = ReportEntryManager()
        self.reportEntryManager.director = self
        # Link the TaskManager's by least available slots.
        self.measurementManager.child = self.reportEntryManager
        # Notify the parent when tasks complete # XXX deadlock!?
        self.reportEntryManager.parent = self.measurementManager

        self.successfulMeasurements = 0
        self.failedMeasurements = 0

        self.totalMeasurements = 0

        # The cumulative runtime of all the measurements
        self.totalMeasurementRuntime = 0

        self.failures = []

        # This deferred is fired once all the measurements and their reporting
        # tasks are completed.
        self.allTestsDone = defer.Deferred()
        self.sniffers = {}

        self.input_store = input_store
        self.deck_store = deck_store

        self._reset_director_state()
        self._reset_tor_state()

        self._subscribers = []

    def subscribe(self, handler):
        self._subscribers.append(handler)

    def unsubscribe(self, handler):
        self._subscribers.remove(handler)

    def notify(self, event):
        for handler in self._subscribers:
            try:
                handler(event)
            except Exception as exc:
                log.err("Failed to run handler")
                log.exception(exc)

    def _reset_director_state(self):
        self._director_state = 'not-running'
        self._director_starting = defer.Deferred()
        self._director_starting.addErrback(self._director_startup_failure)
        self._director_starting.addCallback(self._director_startup_success)

    def _director_startup_failure(self, failure):
        self._reset_director_state()
        self.notify(DirectorEvent("error", "Failed to start the director"))
        return failure

    def _director_startup_success(self, result):
        self._director_state = 'running'
        self.notify(
            DirectorEvent("success", "Successfully started the director"))
        return result

    def _reset_tor_state(self):
        # This can be either 'not-running', 'starting' or 'running'
        self._tor_state = 'not-running'
        self._tor_starting = defer.Deferred()
        self._tor_starting.addErrback(self._tor_startup_failure)
        self._tor_starting.addCallback(self._tor_startup_success)

    def _tor_startup_failure(self, failure):
        log.err("Failed to start tor")
        log.exception(failure)
        self._reset_tor_state()
        self.notify(DirectorEvent("error", "Failed to start Tor"))
        return failure

    def _tor_startup_success(self, result):
        log.msg("Tor has started")
        self._tor_state = 'running'
        self.notify(DirectorEvent("success", "Successfully started Tor"))
        return result

    def getNetTests(self):
        nettests = {}

        def is_nettest(filename):
            return not filename == '__init__.py' and filename.endswith('.py')

        for category in self.categories:
            dirname = os.path.join(config.nettest_directory, category)
            # print path to all filenames.
            for filename in os.listdir(dirname):
                if is_nettest(filename):
                    net_test_file = os.path.join(dirname, filename)
                    try:
                        nettest = getNetTestInformation(net_test_file)
                    except:
                        log.err("Error processing %s" % filename)
                        continue
                    nettest['category'] = category.replace('/', '')

                    if nettest['id'] in nettests:
                        log.err(
                            "Found a two tests with the same name %s, %s" %
                            (net_test_file, nettests[nettest['id']]['path']))
                    else:
                        category = dirname.replace(config.nettest_directory,
                                                   '')
                        nettests[nettest['id']] = nettest

        return nettests

    @defer.inlineCallbacks
    def _start(self, start_tor, check_incoherences, create_input_store):
        self.netTests = self.getNetTests()

        if start_tor:
            yield self.start_tor(check_incoherences)

        no_geoip = config.global_options.get('no-geoip', False)
        if no_geoip:
            aux = [False]
            if config.global_options.get('annotations') is not None:
                annotations = [
                    k.lower()
                    for k in config.global_options['annotations'].keys()
                ]
                aux = map(lambda x: x in annotations,
                          ["city", "country", "asn"])
            if not all(aux):
                log.msg(
                    "You should add annotations for the country, city and ASN")
        else:
            yield probe_ip.lookup()
            self.notify(DirectorEvent("success", "Looked up probe IP"))

        self.notify(DirectorEvent("success", "Running system tasks"))
        yield run_system_tasks(no_input_store=not create_input_store)
        self.notify(DirectorEvent("success", "Ran system tasks"))

    @defer.inlineCallbacks
    def start(self,
              start_tor=False,
              check_incoherences=True,
              create_input_store=True):
        self._director_state = 'starting'
        try:
            yield self._start(start_tor, check_incoherences,
                              create_input_store)
            self._director_starting.callback(self._director_state)
        except Exception as exc:
            self._director_starting.errback(Failure(exc))
            raise

    @property
    def measurementSuccessRatio(self):
        if self.totalMeasurements == 0:
            return 0

        return self.successfulMeasurements / self.totalMeasurements

    @property
    def measurementFailureRatio(self):
        if self.totalMeasurements == 0:
            return 0

        return self.failedMeasurements / self.totalMeasurements

    @property
    def measurementSuccessRate(self):
        """
        The speed at which tests are succeeding globally.

        This means that fast tests that perform a lot of measurements will
        impact this value quite heavily.
        """
        if self.totalMeasurementRuntime == 0:
            return 0

        return self.successfulMeasurements / self.totalMeasurementRuntime

    @property
    def measurementFailureRate(self):
        """
        The speed at which tests are failing globally.
        """
        if self.totalMeasurementRuntime == 0:
            return 0

        return self.failedMeasurements / self.totalMeasurementRuntime

    def measurementTimedOut(self, measurement):
        """
        This gets called every time a measurement times out independenty from
        the fact that it gets re-scheduled or not.
        """
        pass

    def measurementStarted(self, measurement):
        self.totalMeasurements += 1

    def measurementSucceeded(self, result, measurement):
        log.debug("Successfully completed measurement: %s" % measurement)
        self.totalMeasurementRuntime += measurement.runtime
        self.successfulMeasurements += 1
        measurement.result = result
        test_name = normalizeTestName(measurement.testInstance.name)
        if test_name in self.sniffers:
            sniffer = self.sniffers[test_name]
            config.scapyFactory.unRegisterProtocol(sniffer)
            sniffer.close()
            del self.sniffers[test_name]
        return measurement

    def measurementFailed(self, failure, measurement):
        log.debug("Failed doing measurement: %s" % measurement)
        self.totalMeasurementRuntime += measurement.runtime

        self.failedMeasurements += 1
        measurement.result = failure
        return measurement

    def netTestDone(self, net_test):
        self.notify(
            DirectorEvent(
                "success", "Successfully ran test {0}".format(
                    net_test.testDetails['test_name'])))
        self.activeNetTests.remove(net_test)
        if len(self.activeNetTests) == 0:
            self.allTestsDone.callback(None)

    @defer.inlineCallbacks
    def start_net_test_loader(self,
                              net_test_loader,
                              report_filename,
                              collector_client=None,
                              no_yamloo=False,
                              test_details=None,
                              measurement_id=None):
        """
        Create the Report for the NetTest and start the report NetTest.

        Args:
            net_test_loader:
                an instance of :class:ooni.nettest.NetTestLoader
        """
        if test_details is None:
            test_details = net_test_loader.getTestDetails()
        test_cases = net_test_loader.getTestCases()

        if self.allTestsDone.called:
            self.allTestsDone = defer.Deferred()

        if config.privacy.includepcap or config.global_options.get(
                'pcapfile', None):
            self.start_sniffing(test_details)
        report = Report(test_details, report_filename, self.reportEntryManager,
                        collector_client, no_yamloo, measurement_id)

        yield report.open()
        net_test = NetTest(test_cases, test_details, report)
        net_test.director = self

        yield net_test.initialize()
        try:
            self.activeNetTests.append(net_test)
            self.measurementManager.schedule(net_test.generateMeasurements())

            yield net_test.done
            yield report.close()
        finally:
            self.netTestDone(net_test)

    def start_sniffing(self, test_details):
        """ Start sniffing with Scapy. Exits if required privileges (root) are not
        available.
        """
        from ooni.utils.txscapy import ScapySniffer, ScapyFactory

        if config.scapyFactory is None:
            config.scapyFactory = ScapyFactory(config.advanced.interface)

        # XXX this is dumb option to have in the ooniprobe.conf. Drop it in
        # the future.
        prefix = config.reports.pcap
        if prefix is None:
            prefix = 'report'

        filename_pcap = config.global_options.get('pcapfile', None)
        if filename_pcap is None:
            filename_pcap = generate_filename(test_details,
                                              prefix=prefix,
                                              extension='pcap')
        if len(self.sniffers) > 0:
            pcap_filenames = set(sniffer.pcapwriter.filename
                                 for sniffer in self.sniffers.values())
            pcap_filenames.add(filename_pcap)
            log.msg(
                "pcap files %s can be messed up because several netTests are being executed in parallel."
                % ','.join(pcap_filenames))

        sniffer = ScapySniffer(filename_pcap)
        self.sniffers[test_details['test_name']] = sniffer
        config.scapyFactory.registerProtocol(sniffer)
        log.msg("Starting packet capture to: %s" % filename_pcap)

    @defer.inlineCallbacks
    def start_tor(self, check_incoherences=False):
        """ Starts Tor
        Launches a Tor with :param: socks_port :param: control_port
        :param: tor_binary set in ooniprobe.conf
        """
        if self._tor_state == 'running':
            log.debug("Tor is already running")
            defer.returnValue(self._tor_state)
        elif self._tor_state == 'starting':
            log.debug("Tor is starting")
            yield self._tor_starting
            defer.returnValue(self._tor_state)

        log.msg("Starting Tor")
        self._tor_state = 'starting'
        if check_incoherences:
            try:
                yield config.check_tor()
            except Exception as exc:
                self._tor_starting.errback(Failure(exc))
                raise exc

        if config.advanced.start_tor and config.tor_state is None:
            tor_config = get_tor_config()

            try:
                yield start_tor(tor_config)
                self._tor_starting.callback(self._tor_state)
            except Exception as exc:
                log.err("Failed to start tor")
                log.exception(exc)
                self._tor_starting.errback(Failure(exc))

        elif config.tor.control_port and config.tor_state is None:
            try:
                yield connect_to_control_port()
                self._tor_starting.callback(self._tor_state)
            except Exception as exc:
                self._tor_starting.errback(Failure(exc))
        else:
            # This happens when we require tor to not be started and the
            # socks port is set.
            self._tor_starting.callback(self._tor_state)
示例#13
0
class Director(object):
    """
    Singleton object responsible for coordinating the Measurements Manager
    and the Reporting Manager.

    How this all looks like is as follows:

    +------------------------------------------------+
    |                   Director                     |<--+
    +------------------------------------------------+   |
        ^                                ^               |
        |        Measurement             |               |
    +---------+  [---------]    +--------------------+   |
    |         |                 | MeasurementManager |   |
    | NetTest |  [---------]    +--------------------+   |
    |         |                 | [----------------] |   |
    +---------+  [---------]    | [----------------] |   |
        |                       | [----------------] |   |
        |                       +--------------------+   |
        v                                                |
    +---------+   ReportEntry                            |
    |         |   [---------]    +--------------------+  |
    |  Report |                  | ReportEntryManager |  |
    |         |   [---------]    +--------------------+  |
    +---------+                  | [----------------] |  |
                  [---------]    | [----------------] |--
                                 | [----------------] |
                                 +--------------------+

    [------------] are Tasks

    +------+
    |      |  are TaskManagers
    +------+
    |      |
    +------+

    +------+
    |      |  are general purpose objects
    +------+

    """

    _scheduledTests = 0
    # Only list NetTests belonging to these categories
    categories = ['blocking', 'manipulation']

    def __init__(self):
        self.activeNetTests = []

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = self

        self.reportEntryManager = ReportEntryManager()
        self.reportEntryManager.director = self
        # Link the TaskManager's by least available slots.
        self.measurementManager.child = self.reportEntryManager
        # Notify the parent when tasks complete # XXX deadlock!?
        self.reportEntryManager.parent = self.measurementManager

        self.successfulMeasurements = 0
        self.failedMeasurements = 0

        self.totalMeasurements = 0

        # The cumulative runtime of all the measurements
        self.totalMeasurementRuntime = 0

        self.failures = []

        self.torControlProtocol = None

        # This deferred is fired once all the measurements and their reporting
        # tasks are completed.
        self.allTestsDone = defer.Deferred()
        self.sniffers = {}

    def getNetTests(self):
        nettests = {}

        def is_nettest(filename):
            return not filename == '__init__.py' and filename.endswith('.py')

        for category in self.categories:
            dirname = os.path.join(config.nettest_directory, category)
            # print path to all filenames.
            for filename in os.listdir(dirname):
                if is_nettest(filename):
                    net_test_file = os.path.join(dirname, filename)
                    try:
                        nettest = getNetTestInformation(net_test_file)
                    except:
                        log.err("Error processing %s" % filename)
                        continue
                    nettest['category'] = category.replace('/', '')

                    if nettest['id'] in nettests:
                        log.err("Found a two tests with the same name %s, %s" %
                                (net_test_file,
                                 nettests[nettest['id']]['path']))
                    else:
                        category = dirname.replace(config.nettest_directory,
                                                   '')
                        nettests[nettest['id']] = nettest

        return nettests

    @defer.inlineCallbacks
    def start(self, start_tor=False, check_incoherences=True):
        self.netTests = self.getNetTests()

        if start_tor:
            if check_incoherences:
                yield config.check_tor()
            if config.advanced.start_tor and config.tor_state is None:
                yield self.startTor()
            elif config.tor.control_port and config.tor_state is None:
                log.msg("Connecting to Tor Control Port...")
                yield self.getTorState()

        if config.global_options['no-geoip']:
            aux = [False]
            if config.global_options.get('annotations') is not None:
                annotations = [k.lower() for k in config.global_options['annotations'].keys()]
                aux = map(lambda x: x in annotations, ["city", "country", "asn"])
            if not all(aux):
                log.msg("You should add annotations for the country, city and ASN")
        else:
            yield config.probe_ip.lookup()

    @property
    def measurementSuccessRatio(self):
        if self.totalMeasurements == 0:
            return 0

        return self.successfulMeasurements / self.totalMeasurements

    @property
    def measurementFailureRatio(self):
        if self.totalMeasurements == 0:
            return 0

        return self.failedMeasurements / self.totalMeasurements

    @property
    def measurementSuccessRate(self):
        """
        The speed at which tests are succeeding globally.

        This means that fast tests that perform a lot of measurements will
        impact this value quite heavily.
        """
        if self.totalMeasurementRuntime == 0:
            return 0

        return self.successfulMeasurements / self.totalMeasurementRuntime

    @property
    def measurementFailureRate(self):
        """
        The speed at which tests are failing globally.
        """
        if self.totalMeasurementRuntime == 0:
            return 0

        return self.failedMeasurements / self.totalMeasurementRuntime

    def measurementTimedOut(self, measurement):
        """
        This gets called every time a measurement times out independenty from
        the fact that it gets re-scheduled or not.
        """
        pass

    def measurementStarted(self, measurement):
        self.totalMeasurements += 1

    def measurementSucceeded(self, result, measurement):
        log.debug("Successfully completed measurement: %s" % measurement)
        self.totalMeasurementRuntime += measurement.runtime
        self.successfulMeasurements += 1
        measurement.result = result
        test_name = test_class_name_to_name(measurement.testInstance.name)
        if test_name in self.sniffers:
            sniffer = self.sniffers[test_name]
            config.scapyFactory.unRegisterProtocol(sniffer)
            sniffer.close()
            del self.sniffers[test_name]
        return measurement

    def measurementFailed(self, failure, measurement):
        log.debug("Failed doing measurement: %s" % measurement)
        self.totalMeasurementRuntime += measurement.runtime

        self.failedMeasurements += 1
        measurement.result = failure
        return measurement

    def reporterFailed(self, failure, net_test):
        """
        This gets called every time a reporter is failing and has been removed
        from the reporters of a NetTest.
        Once a report has failed to be created that net_test will never use the
        reporter again.

        XXX hook some logic here.
        note: failure contains an extra attribute called failure.reporter
        """
        pass

    def netTestDone(self, net_test):
        self.activeNetTests.remove(net_test)
        if len(self.activeNetTests) == 0:
            self.allTestsDone.callback(None)

    @defer.inlineCallbacks
    def startNetTest(self, net_test_loader, report_filename,
                     collector_address=None):
        """
        Create the Report for the NetTest and start the report NetTest.

        Args:
            net_test_loader:
                an instance of :class:ooni.nettest.NetTestLoader
        """
        if self.allTestsDone.called:
            self.allTestsDone = defer.Deferred()

        if config.privacy.includepcap:
            self.startSniffing(net_test_loader.testDetails)

        report = Report(net_test_loader.testDetails, report_filename,
                        self.reportEntryManager, collector_address)

        net_test = NetTest(net_test_loader, report)
        net_test.director = self

        yield net_test.report.open()

        yield net_test.initializeInputProcessor()
        try:
            self.activeNetTests.append(net_test)
            self.measurementManager.schedule(net_test.generateMeasurements())

            yield net_test.done
            yield report.close()
        finally:
            self.netTestDone(net_test)

    def startSniffing(self, testDetails):
        """ Start sniffing with Scapy. Exits if required privileges (root) are not
        available.
        """
        from ooni.utils.txscapy import ScapySniffer, ScapyFactory

        if config.scapyFactory is None:
            config.scapyFactory = ScapyFactory(config.advanced.interface)

        if not config.reports.pcap:
            prefix = 'report'
        else:
            prefix = config.reports.pcap
        filename = config.global_options['reportfile'] if 'reportfile' in config.global_options.keys() else None
        filename_pcap = generate_filename(testDetails, filename=filename, prefix=prefix, extension='pcap')
        if len(self.sniffers) > 0:
            pcap_filenames = set(sniffer.pcapwriter.filename for sniffer in self.sniffers.values())
            pcap_filenames.add(filename_pcap)
            log.msg("pcap files %s can be messed up because several netTests are being executed in parallel." %
                    ','.join(pcap_filenames))

        sniffer = ScapySniffer(filename_pcap)
        self.sniffers[testDetails['test_name']] = sniffer
        config.scapyFactory.registerProtocol(sniffer)
        log.msg("Starting packet capture to: %s" % filename_pcap)

    @defer.inlineCallbacks
    def getTorState(self):
        connection = TCP4ClientEndpoint(reactor, '127.0.0.1',
                                        config.tor.control_port)
        config.tor_state = yield build_tor_connection(connection)

    def startTor(self):
        """ Starts Tor
        Launches a Tor with :param: socks_port :param: control_port
        :param: tor_binary set in ooniprobe.conf
        """
        log.msg("Starting Tor...")

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

            socks_port = yield state.protocol.get_conf("SocksPort")
            control_port = yield state.protocol.get_conf("ControlPort")

            config.tor.socks_port = int(socks_port.values()[0])
            config.tor.control_port = int(control_port.values()[0])

        def setup_failed(failure):
            log.exception(failure)
            raise errors.UnableToStartTor

        def setup_complete(proto):
            """
            Called when we read from stdout that Tor has reached 100%.
            """
            log.debug("Building a TorState")
            config.tor.protocol = proto
            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

        if config.tor.socks_port:
            tor_config.SocksPort = config.tor.socks_port

        if config.tor.data_dir:
            data_dir = os.path.expanduser(config.tor.data_dir)

            if not os.path.exists(data_dir):
                log.msg("%s does not exist. Creating it." % data_dir)
                os.makedirs(data_dir)
            tor_config.DataDirectory = data_dir

        if config.tor.bridges:
            tor_config.UseBridges = 1
            if config.advanced.obfsproxy_binary:
                tor_config.ClientTransportPlugin = (
                    'obfs2,obfs3 exec %s managed' %
                    config.advanced.obfsproxy_binary
                )
            bridges = []
            with open(config.tor.bridges) as f:
                for bridge in f:
                    if 'obfs' in bridge:
                        if config.advanced.obfsproxy_binary:
                            bridges.append(bridge.strip())
                    else:
                        bridges.append(bridge.strip())
            tor_config.Bridge = bridges

        if config.tor.torrc:
            for i in config.tor.torrc.keys():
                setattr(tor_config, i, config.tor.torrc[i])

        if os.geteuid() == 0:
            tor_config.User = pwd.getpwuid(os.geteuid()).pw_name

        tor_config.save()

        if not hasattr(tor_config, 'ControlPort'):
            control_port = int(randomFreePort())
            tor_config.ControlPort = control_port
            config.tor.control_port = control_port

        if not hasattr(tor_config, 'SocksPort'):
            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)

        if config.advanced.tor_binary:
            d = launch_tor(tor_config, reactor,
                           tor_binary=config.advanced.tor_binary,
                           progress_updates=updates)
        else:
            d = launch_tor(tor_config, reactor,
                           progress_updates=updates)
        d.addCallback(setup_complete)
        d.addErrback(setup_failed)
        return d
示例#14
0
class Director(object):
    """
    Singleton object responsible for coordinating the Measurements Manager and the
    Reporting Manager.

    How this all looks like is as follows:

    +------------------------------------------------+
    |                   Director                     |<--+
    +------------------------------------------------+   |
        ^                                ^               |
        |        Measurement             |               |
    +---------+  [---------]    +--------------------+   |
    |         |                 | MeasurementManager |   |
    | NetTest |  [---------]    +--------------------+   |
    |         |                 | [----------------] |   |
    +---------+  [---------]    | [----------------] |   |
        |                       | [----------------] |   |
        |                       +--------------------+   |
        v                                                |
    +---------+   ReportEntry                            |
    |         |   [---------]    +--------------------+  |
    |  Report |                  | ReportEntryManager |  |
    |         |   [---------]    +--------------------+  |
    +---------+                  | [----------------] |  |
                  [---------]    | [----------------] |--
                                 | [----------------] |
                                 +--------------------+

    [------------] are Tasks

    +------+
    |      |  are TaskManagers
    +------+
    |      |
    +------+

    +------+
    |      |  are general purpose objects
    +------+

    """
    _scheduledTests = 0

    def __init__(self):
        self.netTests = []
        self.activeNetTests = []

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = self

        self.reportEntryManager = ReportEntryManager()
        self.reportEntryManager.director = self

        self.successfulMeasurements = 0
        self.failedMeasurements = 0

        self.totalMeasurements = 0

        # The cumulative runtime of all the measurements
        self.totalMeasurementRuntime = 0

        self.failures = []

        self.torControlProtocol = None

        # This deferred is fired once all the measurements and their reporting
        # tasks are completed.
        self.allTestsDone = defer.Deferred()
        self.sniffer = None

    @defer.inlineCallbacks
    def start(self):

        if config.advanced.start_tor:
            log.msg("Starting Tor...")
            yield self.startTor()

        config.probe_ip = geoip.ProbeIP()
        yield config.probe_ip.lookup()

    @property
    def measurementSuccessRatio(self):
        if self.totalMeasurements == 0:
            return 0

        return self.successfulMeasurements / self.totalMeasurements

    @property
    def measurementFailureRatio(self):
        if self.totalMeasurements == 0:
            return 0

        return self.failedMeasurements / self.totalMeasurements

    @property
    def measurementSuccessRate(self):
        """
        The speed at which tests are succeeding globally.

        This means that fast tests that perform a lot of measurements will
        impact this value quite heavily.
        """
        if self.totalMeasurementRuntime == 0:
            return 0

        return self.successfulMeasurements / self.totalMeasurementRuntime

    @property
    def measurementFailureRate(self):
        """
        The speed at which tests are failing globally.
        """
        if self.totalMeasurementRuntime == 0:
            return 0

        return self.failedMeasurements / self.totalMeasurementRuntime

    def measurementTimedOut(self, measurement):
        """
        This gets called every time a measurement times out independenty from
        the fact that it gets re-scheduled or not.
        """
        pass

    def measurementStarted(self, measurement):
        self.totalMeasurements += 1

    def measurementSucceeded(self, measurement):
        log.msg("Successfully completed measurement: %s" % measurement)
        self.totalMeasurementRuntime += measurement.runtime
        self.successfulMeasurements += 1
        return measurement.testInstance.report

    def measurementFailed(self, failure, measurement):
        log.msg("Failed doing measurement: %s" % measurement)
        self.totalMeasurementRuntime += measurement.runtime

        self.failedMeasurements += 1
        self.failures.append((failure, measurement))
        return None

    def reporterFailed(self, failure, net_test):
        """
        This gets called every time a reporter is failing and has been removed
        from the reporters of a NetTest.
        Once a report has failed to be created that net_test will never use the
        reporter again.

        XXX hook some logic here.
        note: failure contains an extra attribute called failure.reporter
        """
        pass

    def netTestDone(self, result, net_test):
        self.activeNetTests.remove(net_test)
        if len(self.activeNetTests) == 0:
            self.allTestsDone.callback(None)
            self.allTestsDone = defer.Deferred()

    @defer.inlineCallbacks
    def startNetTest(self, _, net_test_loader, reporters):
        """
        Create the Report for the NetTest and start the report NetTest.

        Args:
            net_test_loader:
                an instance of :class:ooni.nettest.NetTestLoader

            _: #XXX very dirty hack
        """

        if config.privacy.includepcap:
            log.msg("Starting")
            if not config.reports.pcap:
                config.reports.pcap = config.generatePcapFilename(net_test_loader.testDetails)
            self.startSniffing()

        report = Report(reporters, self.reportEntryManager)

        net_test = NetTest(net_test_loader, report)
        net_test.director = self

        yield net_test.report.open()

        self.measurementManager.schedule(net_test.generateMeasurements())

        self.activeNetTests.append(net_test)

        net_test.done.addBoth(report.close)
        net_test.done.addBoth(self.netTestDone, net_test)

        yield net_test.done

    def startSniffing(self):
        """ Start sniffing with Scapy. Exits if required privileges (root) are not
        available.
        """
        from ooni.utils.txscapy import ScapyFactory, ScapySniffer
        try:
            checkForRoot()
        except errors.InsufficientPrivileges:
            print "[!] Includepcap options requires root priviledges to run"
            print "    you should run ooniprobe as root or disable the options in ooniprobe.conf"
            reactor.stop()
            sys.exit(1)

        print "Starting sniffer"
        config.scapyFactory = ScapyFactory(config.advanced.interface)

        if os.path.exists(config.reports.pcap):
            print "Report PCAP already exists with filename %s" % config.reports.pcap
            print "Renaming files with such name..."
            pushFilenameStack(config.reports.pcap)

        if self.sniffer:
            config.scapyFactory.unRegisterProtocol(self.sniffer)
        self.sniffer = ScapySniffer(config.reports.pcap)
        config.scapyFactory.registerProtocol(self.sniffer)

    def startTor(self):
        """ Starts Tor
        Launches a Tor with :param: socks_port :param: control_port
        :param: tor_binary set in ooniprobe.conf
        """
        @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)

            socks_port = yield state.protocol.get_conf("SocksPort")
            control_port = yield state.protocol.get_conf("ControlPort")

            config.tor.socks_port = int(socks_port.values()[0])
            config.tor.control_port = int(control_port.values()[0])

            log.debug("Obtained our IP address from a Tor Relay %s" % config.probe_ip)

        def setup_failed(failure):
            log.exception(failure)
            raise errors.UnableToStartTor

        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.debug("%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

        if config.tor.data_dir:
            data_dir = os.path.expanduser(config.tor.data_dir)

            if not os.path.exists(data_dir):
                log.msg("%s does not exist. Creating it." % data_dir)
                os.makedirs(data_dir)
            tor_config.DataDirectory = data_dir

        tor_config.save()

        log.debug("Setting control port as %s" % tor_config.ControlPort)
        log.debug("Setting SOCKS port as %s" % tor_config.SocksPort)

        if config.advanced.tor_binary:
            d = launch_tor(tor_config, reactor,
                           tor_binary=config.advanced.tor_binary,
                           progress_updates=updates)
        else:
            d = launch_tor(tor_config, reactor,
                           progress_updates=updates)

        d.addCallback(setup_complete)
        d.addErrback(setup_failed)
        return d
示例#15
0
class Director(object):
    """
    Singleton object responsible for coordinating the Measurements Manager
    and the Reporting Manager.

    How this all looks like is as follows:

    +------------------------------------------------+
    |                   Director                     |<--+
    +------------------------------------------------+   |
        ^                                ^               |
        |        Measurement             |               |
    +---------+  [---------]    +--------------------+   |
    |         |                 | MeasurementManager |   |
    | NetTest |  [---------]    +--------------------+   |
    |         |                 | [----------------] |   |
    +---------+  [---------]    | [----------------] |   |
        |                       | [----------------] |   |
        |                       +--------------------+   |
        v                                                |
    +---------+   ReportEntry                            |
    |         |   [---------]    +--------------------+  |
    |  Report |                  | ReportEntryManager |  |
    |         |   [---------]    +--------------------+  |
    +---------+                  | [----------------] |  |
                  [---------]    | [----------------] |--
                                 | [----------------] |
                                 +--------------------+

    [------------] are Tasks

    +------+
    |      |  are TaskManagers
    +------+
    |      |
    +------+

    +------+
    |      |  are general purpose objects
    +------+

    """

    _scheduledTests = 0
    # Only list NetTests belonging to these categories
    categories = ['blocking', 'manipulation', 'third_party']

    def __init__(self):
        self.activeNetTests = []
        self.activeDecks = []

        self.activeMeasurements = {}

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = self

        self.reportEntryManager = ReportEntryManager()
        self.reportEntryManager.director = self
        # Link the TaskManager's by least available slots.
        self.measurementManager.child = self.reportEntryManager
        # Notify the parent when tasks complete # XXX deadlock!?
        self.reportEntryManager.parent = self.measurementManager

        self.successfulMeasurements = 0
        self.failedMeasurements = 0

        self.totalMeasurements = 0

        # The cumulative runtime of all the measurements
        self.totalMeasurementRuntime = 0

        self.failures = []

        # This deferred is fired once all the measurements and their reporting
        # tasks are completed.
        self.allTestsDone = defer.Deferred()
        self.sniffers = {}

        self.input_store = input_store
        self.deck_store = deck_store

        self._reset_director_state()
        self._reset_tor_state()

        self._subscribers = []

    def subscribe(self, handler):
        self._subscribers.append(handler)

    def unsubscribe(self, handler):
        self._subscribers.remove(handler)

    def notify(self, event):
        for handler in self._subscribers:
            try:
                handler(event)
            except Exception as exc:
                log.err("Failed to run handler")
                log.exception(exc)

    def _reset_director_state(self):
        self._director_state = 'not-running'
        self._director_starting = defer.Deferred()
        self._director_starting.addErrback(self._director_startup_failure)
        self._director_starting.addCallback(self._director_startup_success)

    def _director_startup_failure(self, failure):
        self._reset_director_state()
        self.notify(DirectorEvent("error",
                                  "Failed to start the director"))
        return failure

    def _director_startup_success(self, result):
        self._director_state = 'running'
        self.notify(DirectorEvent("success",
                                  "Successfully started the director"))
        return result

    def _reset_tor_state(self):
        # This can be either 'not-running', 'starting' or 'running'
        self._tor_state = 'not-running'
        self._tor_starting = defer.Deferred()
        self._tor_starting.addErrback(self._tor_startup_failure)
        self._tor_starting.addCallback(self._tor_startup_success)

    def _tor_startup_failure(self, failure):
        log.err("Failed to start tor")
        log.exception(failure)
        self._reset_tor_state()
        self.notify(DirectorEvent("error",
                                  "Failed to start Tor"))
        return failure

    def _tor_startup_success(self, result):
        log.msg("Tor has started")
        self._tor_state = 'running'
        self.notify(DirectorEvent("success",
                                  "Successfully started Tor"))
        return result

    def getNetTests(self):
        nettests = {}

        def is_nettest(filename):
            return not filename == '__init__.py' and filename.endswith('.py')

        for category in self.categories:
            dirname = os.path.join(config.nettest_directory, category)
            # print path to all filenames.
            for filename in os.listdir(dirname):
                if is_nettest(filename):
                    net_test_file = os.path.join(dirname, filename)
                    try:
                        nettest = getNetTestInformation(net_test_file)
                    except:
                        log.err("Error processing %s" % filename)
                        continue
                    nettest['category'] = category.replace('/', '')

                    if nettest['id'] in nettests:
                        log.err("Found a two tests with the same name %s, %s" %
                                (net_test_file,
                                 nettests[nettest['id']]['path']))
                    else:
                        category = dirname.replace(config.nettest_directory,
                                                   '')
                        nettests[nettest['id']] = nettest

        return nettests

    @defer.inlineCallbacks
    def _start(self, start_tor, check_incoherences, create_input_store):
        self.netTests = self.getNetTests()

        if start_tor:
            yield self.start_tor(check_incoherences)

        no_geoip = config.global_options.get('no-geoip', False)
        if no_geoip:
            aux = [False]
            if config.global_options.get('annotations') is not None:
                annotations = [k.lower() for k in config.global_options['annotations'].keys()]
                aux = map(lambda x: x in annotations, ["city", "country", "asn"])
            if not all(aux):
                log.msg("You should add annotations for the country, city and ASN")
        else:
            yield probe_ip.lookup()
            self.notify(DirectorEvent("success", "Looked up probe IP"))

        self.notify(DirectorEvent("success", "Running system tasks"))
        yield run_system_tasks(no_input_store=not create_input_store)
        self.notify(DirectorEvent("success", "Ran system tasks"))

    @defer.inlineCallbacks
    def start(self, start_tor=False, check_incoherences=True,
              create_input_store=True):
        self._director_state = 'starting'
        try:
            yield self._start(start_tor, check_incoherences, create_input_store)
            self._director_starting.callback(self._director_state)
        except Exception as exc:
            self._director_starting.errback(Failure(exc))
            raise

    @property
    def measurementSuccessRatio(self):
        if self.totalMeasurements == 0:
            return 0

        return self.successfulMeasurements / self.totalMeasurements

    @property
    def measurementFailureRatio(self):
        if self.totalMeasurements == 0:
            return 0

        return self.failedMeasurements / self.totalMeasurements

    @property
    def measurementSuccessRate(self):
        """
        The speed at which tests are succeeding globally.

        This means that fast tests that perform a lot of measurements will
        impact this value quite heavily.
        """
        if self.totalMeasurementRuntime == 0:
            return 0

        return self.successfulMeasurements / self.totalMeasurementRuntime

    @property
    def measurementFailureRate(self):
        """
        The speed at which tests are failing globally.
        """
        if self.totalMeasurementRuntime == 0:
            return 0

        return self.failedMeasurements / self.totalMeasurementRuntime

    def measurementTimedOut(self, measurement):
        """
        This gets called every time a measurement times out independenty from
        the fact that it gets re-scheduled or not.
        """
        pass

    def measurementStarted(self, measurement):
        self.totalMeasurements += 1

    def measurementSucceeded(self, result, measurement):
        log.debug("Successfully completed measurement: %s" % measurement)
        self.totalMeasurementRuntime += measurement.runtime
        self.successfulMeasurements += 1
        measurement.result = result
        test_name = normalizeTestName(measurement.testInstance.name)
        if test_name in self.sniffers:
            sniffer = self.sniffers[test_name]
            config.scapyFactory.unRegisterProtocol(sniffer)
            sniffer.close()
            del self.sniffers[test_name]
        return measurement

    def measurementFailed(self, failure, measurement):
        log.debug("Failed doing measurement: %s" % measurement)
        self.totalMeasurementRuntime += measurement.runtime

        self.failedMeasurements += 1
        measurement.result = failure
        return measurement

    def deckStarted(self, deck_id, from_schedule):
        log.debug("Starting {0} ({1})".format(deck_id,
                                              'scheduled' if from_schedule
                                              else 'user-run'))
        self.activeDecks.append((deck_id, from_schedule))

    def deckFinished(self, deck_id, from_schedule):
        log.debug("Finished {0} ({1})".format(deck_id,
                                              'scheduled' if from_schedule
                                              else 'user-run'))
        try:
            self.activeDecks.remove((deck_id, from_schedule))
        except ValueError:
            log.error("Completed deck {0} is not actually running".format(
                deck_id))

    def isDeckRunning(self, deck_id, from_schedule):
        """
        :param deck_id: the ID of the deck to check if it's running
        :param from_schedule:  True if we want to know the status of a
        scheduled deck run False for user initiated runs.
        :return: True if the deck is running False otherwise
        """
        return (deck_id, from_schedule) in self.activeDecks

    def netTestDone(self, net_test):
        self.notify(DirectorEvent("success",
                                  "Successfully ran test {0}".format(
                                      net_test.testDetails['test_name'])))
        self.activeNetTests.remove(net_test)
        if len(self.activeNetTests) == 0:
            self.allTestsDone.callback(None)

    @defer.inlineCallbacks
    def start_net_test_loader(self, net_test_loader, report_filename,
                              collector_client=None, no_yamloo=False,
                              test_details=None, measurement_id=None):
        """
        Create the Report for the NetTest and start the report NetTest.

        Args:
            net_test_loader:
                an instance of :class:ooni.nettest.NetTestLoader
        """
        if test_details is None:
            test_details = net_test_loader.getTestDetails()
        test_cases = net_test_loader.getTestCases()

        if self.allTestsDone.called:
            self.allTestsDone = defer.Deferred()

        if config.privacy.includepcap or config.global_options.get('pcapfile', None):
            self.start_sniffing(test_details)
        report = Report(test_details, report_filename,
                        self.reportEntryManager,
                        collector_client,
                        no_yamloo,
                        measurement_id)

        yield report.open()
        net_test = NetTest(test_cases, test_details, report)
        net_test.director = self

        yield net_test.initialize()
        try:
            self.activeNetTests.append(net_test)
            if measurement_id:
                self.activeMeasurements[measurement_id] = net_test
            self.measurementManager.schedule(net_test.generateMeasurements())

            yield net_test.done
            yield report.close()
        finally:
            if measurement_id:
                del self.activeMeasurements[measurement_id]
            self.netTestDone(net_test)

    def start_sniffing(self, test_details):
        """ Start sniffing with Scapy. Exits if required privileges (root) are not
        available.
        """
        from ooni.utils.txscapy import ScapySniffer, ScapyFactory

        if config.scapyFactory is None:
            config.scapyFactory = ScapyFactory(config.advanced.interface)

        # XXX this is dumb option to have in the ooniprobe.conf. Drop it in
        # the future.
        prefix = config.reports.pcap
        if prefix is None:
            prefix = 'report'

        filename_pcap = config.global_options.get('pcapfile', None)
        if filename_pcap is None:
            filename_pcap = generate_filename(test_details,
                                              prefix=prefix,
                                              extension='pcap')
        if len(self.sniffers) > 0:
            pcap_filenames = set(sniffer.pcapwriter.filename for sniffer in self.sniffers.values())
            pcap_filenames.add(filename_pcap)
            log.msg("pcap files %s can be messed up because several netTests are being executed in parallel." %
                    ','.join(pcap_filenames))

        sniffer = ScapySniffer(filename_pcap)
        self.sniffers[test_details['test_name']] = sniffer
        config.scapyFactory.registerProtocol(sniffer)
        log.msg("Starting packet capture to: %s" % filename_pcap)


    @defer.inlineCallbacks
    def start_tor(self, check_incoherences=False):
        """ Starts Tor
        Launches a Tor with :param: socks_port :param: control_port
        :param: tor_binary set in ooniprobe.conf
        """
        if self._tor_state == 'running':
            log.debug("Tor is already running")
            defer.returnValue(self._tor_state)
        elif self._tor_state == 'starting':
            log.debug("Tor is starting")
            yield self._tor_starting
            defer.returnValue(self._tor_state)

        log.msg("Starting Tor")
        self._tor_state = 'starting'
        if check_incoherences:
            try:
                yield config.check_tor()
            except Exception as exc:
                self._tor_starting.errback(Failure(exc))
                raise exc

        if config.advanced.start_tor and config.tor_state is None:
            tor_config = get_tor_config()

            try:
                yield start_tor(tor_config)
                self._tor_starting.callback(self._tor_state)
            except Exception as exc:
                log.err("Failed to start tor")
                log.exception(exc)
                self._tor_starting.errback(Failure(exc))

        elif config.tor.control_port and config.tor_state is None:
            try:
                yield connect_to_control_port()
                self._tor_starting.callback(self._tor_state)
            except Exception as exc:
                self._tor_starting.errback(Failure(exc))
        else:
            # This happens when we require tor to not be started and the
            # socks port is set.
            self._tor_starting.callback(self._tor_state)
示例#16
0
class Director(object):
    """
    Singleton object responsible for coordinating the Measurements Manager and the
    Reporting Manager.

    How this all looks like is as follows:

    +------------------------------------------------+
    |                   Director                     |<--+
    +------------------------------------------------+   |
        ^                                ^               |
        |        Measurement             |               |
    +---------+  [---------]    +--------------------+   |
    |         |                 | MeasurementManager |   |
    | NetTest |  [---------]    +--------------------+   |
    |         |                 | [----------------] |   |
    +---------+  [---------]    | [----------------] |   |
        |                       | [----------------] |   |
        |                       +--------------------+   |
        v                                                |
    +---------+   ReportEntry                            |
    |         |   [---------]    +--------------------+  |
    |  Report |                  | ReportEntryManager |  |
    |         |   [---------]    +--------------------+  |
    +---------+                  | [----------------] |  |
                  [---------]    | [----------------] |--
                                 | [----------------] |
                                 +--------------------+

    [------------] are Tasks

    +------+
    |      |  are TaskManagers
    +------+
    |      |
    +------+

    +------+
    |      |  are general purpose objects
    +------+

    """
    _scheduledTests = 0
    # Only list NetTests belonging to these categories
    categories = ['blocking', 'manipulation']

    def __init__(self):
        self.activeNetTests = []

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = self

        self.reportEntryManager = ReportEntryManager()
        self.reportEntryManager.director = self
        # Link the TaskManager's by least available slots.
        self.measurementManager.child = self.reportEntryManager
        # Notify the parent when tasks complete # XXX deadlock!?
        self.reportEntryManager.parent = self.measurementManager

        self.successfulMeasurements = 0
        self.failedMeasurements = 0

        self.totalMeasurements = 0

        # The cumulative runtime of all the measurements
        self.totalMeasurementRuntime = 0

        self.failures = []

        self.torControlProtocol = None

        # This deferred is fired once all the measurements and their reporting
        # tasks are completed.
        self.allTestsDone = defer.Deferred()
        self.sniffer = None

    def getNetTests(self):
        nettests = {}
        def is_nettest(filename):
            return not filename == '__init__.py' \
                    and filename.endswith('.py')

        for category in self.categories:
            dirname = os.path.join(config.nettest_directory, category)
            # print path to all filenames.
            for filename in os.listdir(dirname):
                if is_nettest(filename):
                    net_test_file = os.path.join(dirname, filename)
                    nettest = getNetTestInformation(net_test_file)

                    if nettest['id'] in nettests:
                        log.err("Found a two tests with the same name %s, %s" %
                                (nettest_path, nettests[nettest['id']]['path']))
                    else:
                        category = dirname.replace(config.nettest_directory, '')
                        nettests[nettest['id']] = nettest

        return nettests

    @defer.inlineCallbacks
    def start(self):
        self.netTests = self.getNetTests()

        if config.advanced.start_tor:
            log.msg("Starting Tor...")
            yield self.startTor()

        config.probe_ip = geoip.ProbeIP()
        yield config.probe_ip.lookup()

    @property
    def measurementSuccessRatio(self):
        if self.totalMeasurements == 0:
            return 0

        return self.successfulMeasurements / self.totalMeasurements

    @property
    def measurementFailureRatio(self):
        if self.totalMeasurements == 0:
            return 0

        return self.failedMeasurements / self.totalMeasurements

    @property
    def measurementSuccessRate(self):
        """
        The speed at which tests are succeeding globally.

        This means that fast tests that perform a lot of measurements will
        impact this value quite heavily.
        """
        if self.totalMeasurementRuntime == 0:
            return 0

        return self.successfulMeasurements / self.totalMeasurementRuntime

    @property
    def measurementFailureRate(self):
        """
        The speed at which tests are failing globally.
        """
        if self.totalMeasurementRuntime == 0:
            return 0

        return self.failedMeasurements / self.totalMeasurementRuntime

    def measurementTimedOut(self, measurement):
        """
        This gets called every time a measurement times out independenty from
        the fact that it gets re-scheduled or not.
        """
        pass

    def measurementStarted(self, measurement):
        self.totalMeasurements += 1

    def measurementSucceeded(self, measurement):
        log.msg("Successfully completed measurement: %s" % measurement)
        self.totalMeasurementRuntime += measurement.runtime
        self.successfulMeasurements += 1
        return measurement

    def measurementFailed(self, failure, measurement):
        log.msg("Failed doing measurement: %s" % measurement)
        self.totalMeasurementRuntime += measurement.runtime

        self.failedMeasurements += 1
        self.failures.append((failure, measurement))
        return measurement

    def reporterFailed(self, failure, net_test):
        """
        This gets called every time a reporter is failing and has been removed
        from the reporters of a NetTest.
        Once a report has failed to be created that net_test will never use the
        reporter again.

        XXX hook some logic here.
        note: failure contains an extra attribute called failure.reporter
        """
        pass

    def netTestDone(self, net_test):
        self.activeNetTests.remove(net_test)
        if len(self.activeNetTests) == 0:
            self.allTestsDone.callback(None)
            self.allTestsDone = defer.Deferred()

    @defer.inlineCallbacks
    def startNetTest(self, net_test_loader, reporters):
        """
        Create the Report for the NetTest and start the report NetTest.

        Args:
            net_test_loader:
                an instance of :class:ooni.nettest.NetTestLoader
        """

        if config.privacy.includepcap:
            if not config.reports.pcap:
                config.reports.pcap = config.generatePcapFilename(net_test_loader.testDetails)
            self.startSniffing()

        report = Report(reporters, self.reportEntryManager)

        net_test = NetTest(net_test_loader, report)
        net_test.director = self

        yield net_test.report.open()

        yield net_test.initializeInputProcessor()
        self.measurementManager.schedule(net_test.generateMeasurements())

        self.activeNetTests.append(net_test)

        yield net_test.done
        yield report.close()

        self.netTestDone(net_test)

    def startSniffing(self):
        """ Start sniffing with Scapy. Exits if required privileges (root) are not
        available.
        """
        from ooni.utils.txscapy import ScapyFactory, ScapySniffer
        config.scapyFactory = ScapyFactory(config.advanced.interface)

        if os.path.exists(config.reports.pcap):
            log.msg("Report PCAP already exists with filename %s" % config.reports.pcap)
            log.msg("Renaming files with such name...")
            pushFilenameStack(config.reports.pcap)

        if self.sniffer:
            config.scapyFactory.unRegisterProtocol(self.sniffer)
        self.sniffer = ScapySniffer(config.reports.pcap)
        config.scapyFactory.registerProtocol(self.sniffer)
        log.msg("Starting packet capture to: %s" % config.reports.pcap)

    def startTor(self):
        """ Starts Tor
        Launches a Tor with :param: socks_port :param: control_port
        :param: tor_binary set in ooniprobe.conf
        """
        @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)

            socks_port = yield state.protocol.get_conf("SocksPort")
            control_port = yield state.protocol.get_conf("ControlPort")

            config.tor.socks_port = int(socks_port.values()[0])
            config.tor.control_port = int(control_port.values()[0])

            log.debug("Obtained our IP address from a Tor Relay %s" % config.probe_ip)

        def setup_failed(failure):
            log.exception(failure)
            raise errors.UnableToStartTor

        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.debug("%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

        if config.tor.data_dir:
            data_dir = os.path.expanduser(config.tor.data_dir)

            if not os.path.exists(data_dir):
                log.msg("%s does not exist. Creating it." % data_dir)
                os.makedirs(data_dir)
            tor_config.DataDirectory = data_dir

        if config.tor.bridges:
            tor_config.UseBridges = 1
            if config.advanced.obfsproxy_binary:
                tor_config.ClientTransportPlugin = \
                        'obfs2,obfs3 exec %s managed' % \
                        config.advanced.obfsproxy_binary
            bridges = []
            with open(config.tor.bridges) as f:
                for bridge in f:
                    if 'obfs' in bridge:
                        if config.advanced.obfsproxy_binary:
                            bridges.append(bridge.strip())
                    else:
                        bridges.append(bridge.strip())
            tor_config.Bridge = bridges

        tor_config.save()

        log.debug("Setting control port as %s" % tor_config.ControlPort)
        log.debug("Setting SOCKS port as %s" % tor_config.SocksPort)

        if config.advanced.tor_binary:
            d = launch_tor(tor_config, reactor,
                           tor_binary=config.advanced.tor_binary,
                           progress_updates=updates)
        else:
            d = launch_tor(tor_config, reactor,
                           progress_updates=updates)

        d.addCallback(setup_complete)
        d.addErrback(setup_failed)
        return d
示例#17
0
class Director(object):
    """
    Singleton object responsible for coordinating the Measurements Manager and the
    Reporting Manager.

    How this all looks like is as follows:

    +------------------------------------------------+
    |                   Director                     |<--+
    +------------------------------------------------+   |
        ^                                ^               |
        |        Measurement             |               |
    +---------+  [---------]    +--------------------+   |
    |         |                 | MeasurementManager |   |
    | NetTest |  [---------]    +--------------------+   |
    |         |                 | [----------------] |   |
    +---------+  [---------]    | [----------------] |   |
        |                       | [----------------] |   |
        |                       +--------------------+   |
        v                                                |
    +---------+   ReportEntry                            |
    |         |   [---------]    +--------------------+  |
    |  Report |                  | ReportEntryManager |  |
    |         |   [---------]    +--------------------+  |
    +---------+                  | [----------------] |  |
                  [---------]    | [----------------] |--
                                 | [----------------] |
                                 +--------------------+

    [------------] are Tasks

    +------+
    |      |  are TaskManagers
    +------+
    |      |
    +------+

    +------+
    |      |  are general purpose objects
    +------+

    """
    _scheduledTests = 0

    def __init__(self):
        self.netTests = []
        self.activeNetTests = []

        self.measurementManager = MeasurementManager()
        self.measurementManager.director = self

        self.reportEntryManager = ReportEntryManager()
        self.reportEntryManager.director = self

        self.successfulMeasurements = 0
        self.failedMeasurements = 0

        self.totalMeasurements = 0

        # The cumulative runtime of all the measurements
        self.totalMeasurementRuntime = 0

        self.failures = []

        self.torControlProtocol = None

        # This deferred is fired once all the measurements and their reporting
        # tasks are completed.
        self.allTestsDone = defer.Deferred()
        self.sniffer = None

    @defer.inlineCallbacks
    def start(self):

        if config.advanced.start_tor:
            log.msg("Starting Tor...")
            yield self.startTor()

        config.probe_ip = geoip.ProbeIP()
        yield config.probe_ip.lookup()

    @property
    def measurementSuccessRatio(self):
        if self.totalMeasurements == 0:
            return 0

        return self.successfulMeasurements / self.totalMeasurements

    @property
    def measurementFailureRatio(self):
        if self.totalMeasurements == 0:
            return 0

        return self.failedMeasurements / self.totalMeasurements

    @property
    def measurementSuccessRate(self):
        """
        The speed at which tests are succeeding globally.

        This means that fast tests that perform a lot of measurements will
        impact this value quite heavily.
        """
        if self.totalMeasurementRuntime == 0:
            return 0

        return self.successfulMeasurements / self.totalMeasurementRuntime

    @property
    def measurementFailureRate(self):
        """
        The speed at which tests are failing globally.
        """
        if self.totalMeasurementRuntime == 0:
            return 0

        return self.failedMeasurements / self.totalMeasurementRuntime

    def measurementTimedOut(self, measurement):
        """
        This gets called every time a measurement times out independenty from
        the fact that it gets re-scheduled or not.
        """
        pass

    def measurementStarted(self, measurement):
        self.totalMeasurements += 1

    def measurementSucceeded(self, measurement):
        log.msg("Successfully completed measurement: %s" % measurement)
        self.totalMeasurementRuntime += measurement.runtime
        self.successfulMeasurements += 1
        return measurement.testInstance.report

    def measurementFailed(self, failure, measurement):
        log.msg("Failed doing measurement: %s" % measurement)
        self.totalMeasurementRuntime += measurement.runtime

        self.failedMeasurements += 1
        self.failures.append((failure, measurement))
        return None

    def reporterFailed(self, failure, net_test):
        """
        This gets called every time a reporter is failing and has been removed
        from the reporters of a NetTest.
        Once a report has failed to be created that net_test will never use the
        reporter again.

        XXX hook some logic here.
        note: failure contains an extra attribute called failure.reporter
        """
        pass

    def netTestDone(self, result, net_test):
        self.activeNetTests.remove(net_test)
        if len(self.activeNetTests) == 0:
            self.allTestsDone.callback(None)
            self.allTestsDone = defer.Deferred()

    @defer.inlineCallbacks
    def startNetTest(self, _, net_test_loader, reporters):
        """
        Create the Report for the NetTest and start the report NetTest.

        Args:
            net_test_loader:
                an instance of :class:ooni.nettest.NetTestLoader

            _: #XXX very dirty hack
        """

        if config.privacy.includepcap:
            log.msg("Starting")
            if not config.reports.pcap:
                config.reports.pcap = config.generatePcapFilename(
                    net_test_loader.testDetails)
            self.startSniffing()

        report = Report(reporters, self.reportEntryManager)

        net_test = NetTest(net_test_loader, report)
        net_test.director = self

        yield net_test.report.open()

        self.measurementManager.schedule(net_test.generateMeasurements())

        self.activeNetTests.append(net_test)

        net_test.done.addBoth(report.close)
        net_test.done.addBoth(self.netTestDone, net_test)

        yield net_test.done

    def startSniffing(self):
        """ Start sniffing with Scapy. Exits if required privileges (root) are not
        available.
        """
        from ooni.utils.txscapy import ScapyFactory, ScapySniffer
        try:
            checkForRoot()
        except errors.InsufficientPrivileges:
            print "[!] Includepcap options requires root priviledges to run"
            print "    you should run ooniprobe as root or disable the options in ooniprobe.conf"
            reactor.stop()
            sys.exit(1)

        print "Starting sniffer"
        config.scapyFactory = ScapyFactory(config.advanced.interface)

        if os.path.exists(config.reports.pcap):
            print "Report PCAP already exists with filename %s" % config.reports.pcap
            print "Renaming files with such name..."
            pushFilenameStack(config.reports.pcap)

        if self.sniffer:
            config.scapyFactory.unRegisterProtocol(self.sniffer)
        self.sniffer = ScapySniffer(config.reports.pcap)
        config.scapyFactory.registerProtocol(self.sniffer)

    def startTor(self):
        """ Starts Tor
        Launches a Tor with :param: socks_port :param: control_port
        :param: tor_binary set in ooniprobe.conf
        """
        @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)

            socks_port = yield state.protocol.get_conf("SocksPort")
            control_port = yield state.protocol.get_conf("ControlPort")

            config.tor.socks_port = int(socks_port.values()[0])
            config.tor.control_port = int(control_port.values()[0])

            log.debug("Obtained our IP address from a Tor Relay %s" %
                      config.probe_ip)

        def setup_failed(failure):
            log.exception(failure)
            raise errors.UnableToStartTor

        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.debug("%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

        if config.tor.data_dir:
            data_dir = os.path.expanduser(config.tor.data_dir)

            if not os.path.exists(data_dir):
                log.msg("%s does not exist. Creating it." % data_dir)
                os.makedirs(data_dir)
            tor_config.DataDirectory = data_dir

        tor_config.save()

        log.debug("Setting control port as %s" % tor_config.ControlPort)
        log.debug("Setting SOCKS port as %s" % tor_config.SocksPort)

        if config.advanced.tor_binary:
            d = launch_tor(tor_config,
                           reactor,
                           tor_binary=config.advanced.tor_binary,
                           progress_updates=updates)
        else:
            d = launch_tor(tor_config, reactor, progress_updates=updates)

        d.addCallback(setup_complete)
        d.addErrback(setup_failed)
        return d