Measurement Developer API

nettest Module

class ooni.nettest.NetTest(net_test_loader, report)[source]

Bases: object

director = None
doneNetTest(result)[source]
doneReport(report_results)[source]

This will get called every time a report is done and therefore a measurement is done.

The state for the NetTest is informed of the fact that another task has reached the done state.

generateMeasurements()[source]

This is a generator that yields measurements and registers the callbacks for when a measurement is successful or has failed.

initializeInputProcessor(*args, **kwargs)[source]
makeMeasurement(test_instance, test_method, test_input=None)[source]

Creates a new instance of :class:ooni.tasks.Measurement and add’s it’s callbacks and errbacks.

Args:
test_class:
a subclass of :class:ooni.nettest.NetTestCase
test_method:
a string that represents the method to be called on test_class
test_input:
optional argument that represents the input to be passed to the NetTestCase
class ooni.nettest.NetTestCase[source]

Bases: object

This is the base of the OONI nettest universe. When you write a nettest you will subclass this object.

  • inputs: can be set to a static set of inputs. All the tests (the methods starting with the “test” prefix) will be run once per input. At every run the _input_ attribute of the TestCase instance will be set to the value of the current iteration over inputs. Any python iterable object can be set to inputs.

  • inputFile: attribute should be set to an array containing the command line argument that should be used as the input file. Such array looks like this:

    ["commandlinearg", "c", "default value" "The description"]

    The second value of such arrray is the shorthand for the command line arg. The user will then be able to specify inputs to the test via:

    ooniprobe mytest.py --commandlinearg path/to/file.txt

    or

    ooniprobe mytest.py -c path/to/file.txt

  • inputProcessor: should be set to a function that takes as argument a filename and it will return the input to be passed to the test instance.

  • name: should be set to the name of the test.

  • author: should contain the name and contact details for the test author. The format for such string is as follows:

    The Name <email@example.com>

  • version: is the version string of the test.

  • requiresRoot: set to True if the test must be run as root.

  • usageOptions: a subclass of twisted.python.usage.Options for processing

    of command line arguments

  • localOptions: contains the parsed command line arguments.

Quirks: Every class that is prefixed with test must return a twisted.internet.defer.Deferred.

author = 'Jane Doe <foo@example.com>'
baseFlags = None
baseParameters = None
description = 'Sorry, this test has no description :('
displaySummary(summary)[source]

This gets called after the test has run to allow printing out of a summary of the test run.

getInputProcessor()[source]

This method must be called after all options are validated by _checkValidOptions and _checkRequiredOptions, which ensure that if the inputFile is a required option it will be present.

We check to see if it’s possible to have an input file and if the user has specified such file.

If the operations to be done here are network related or blocking, they should be wrapped in a deferred. That is the return value of this method should be a twisted.internet.defer.Deferred.

Returns:
a generator that will yield one item from the file based on the inputProcessor.
inputFile = None
inputFileSpecified
Returns:
True
when inputFile is supported and is specified
False
when input is either not support or not specified
inputFilename = None
inputProcessor(filename)[source]

You may replace this with your own custom input processor. It takes as input a file name.

An inputProcessor is an iterator that will yield one item from the file and takes as argument a filename.

This can be useful when you have some input data that is in a certain format and you want to set the input attribute of the test to something that you will be able to properly process.

For example you may wish to have an input processor that will allow you to ignore comments in files. This can be easily achieved like so:

fp = open(filename)
for x in fp.xreadlines():
    if x.startswith("#"):
        continue
    yield x.strip()
fp.close()

Other fun stuff is also possible.

inputs = None
localOptions = {}
name = 'This test is nameless'
optParameters = None
postProcessor(measurements)[source]

Subclass this to do post processing tasks that are to occur once all the test methods have been called once per input. postProcessing works exactly like test methods, in the sense that anything that gets written to the object self.report[] will be added to the final test report. You should also place in this method any logic that is required for generating the summary.

report = {}
requiredOptions = []
requiredTestHelpers = {}
requirements()[source]

Place in here logic that will be executed before the test is to be run. If some condition is not met then you should raise an exception.

requiresRoot = False
requiresTor = False
setUp()[source]

Place here your logic to be executed when the test is being setup.

usageOptions

alias of Options

version = '0.0.0'
class ooni.nettest.NetTestLoader(options, test_file=None, test_string=None)[source]

Bases: object

checkOptions()[source]

Call processTest and processOptions methods of each NetTestCase

collector = None
inputFiles
loadNetTestFile(net_test_file)[source]

Load NetTest from a file.

loadNetTestString(net_test_string)[source]

Load NetTest from a string. WARNING input to this function MUST be sanitized and NEVER take untrusted input. Failure to do so will result in code exec.

net_test_string:

a string that contains the net test to be run.
method_prefix = 'test'
reportID = None
requiredTestHelpers
requiresTor = False
setupTestCases(test_cases)[source]

Creates all the necessary test_cases (a list of tuples containing the NetTestCase (test_class, test_method))

example:

[(test_classA, test_method1), (test_classA, test_method2), (test_classA, test_method3), (test_classA, test_method4), (test_classA, test_method5),

(test_classB, test_method1), (test_classB, test_method2)]

Note: the inputs must be valid for test_classA and test_classB.

net_test_file:
is either a file path or a file like object that will be used to generate the test_cases.
testDetails
usageOptions
class ooni.nettest.NetTestState(allTasksDone)[source]

Bases: object

allTasksScheduled()[source]

This should be called once all the tasks that need to run have been scheduled.

XXX this is ghetto. The reason for which we are calling allTasksDone inside of the allTasksScheduled method is called after all tasks are done, then we will run into a race condition. The race is that we don’t end up checking that all the tasks are complete because no task is to be scheduled.

checkAllTasksDone()[source]
taskCreated()[source]
taskDone()[source]

This is called every time a task has finished running.

exception ooni.nettest.NoTestCasesFound[source]

Bases: exceptions.Exception

ooni.nettest.getArguments(test_class)[source]
ooni.nettest.getNetTestInformation(net_test_file)[source]

Returns a dict containing:

{

‘id’: the test filename excluding the .py extension, ‘name’: the full name of the test, ‘description’: the description of the test, ‘version’: version number of this test, ‘arguments’: a dict containing as keys the supported arguments and as

values the argument description.

}

ooni.nettest.getOption(opt_parameter, required_options, type='text')[source]
Arguments:
usage_options: a list as should be the optParameters of an UsageOptions
class.
required_options: a list containing the strings of the options that are
required.

type: a string containing the type of the option.

Returns:
a dict containing
{
‘description’: the description of the option, ‘default’: the default value of the option, ‘required’: True|False if the option is required or not, ‘type’: the type of the option (‘text’ or ‘file’)

}

ooni.nettest.getTestClassFromFile(net_test_file)[source]

Will return the first class that is an instance of NetTestCase.

XXX this means that if inside of a test there are more than 1 test case
then we will only run the first one.
ooni.nettest.test_class_name_to_name(test_class_name)[source]

settings Module

class ooni.settings.OConfig[source]

Bases: object

check_incoherences(configuration)[source]
check_tor(*args, **kwargs)[source]

Called only when we must start tor by director.start

data_directory
data_directory_candidates
embedded_settings(category, option)[source]
get_data_file_path(file_name)[source]
initialize_ooni_home(custom_home=None)[source]
log_incoherences(incoherences)[source]
ooni_home
read_config_file(check_incoherences=False)[source]
set_paths()[source]
usr_share_path
var_lib_path

oonicli Module

class ooni.oonicli.Options[source]

Bases: twisted.python.usage.Options

compData = <twisted.python.usage.Completions object>
longdesc = 'ooniprobe loads and executes a suite or a set of suites of network tests. These are loaded from modules, packages and files listed on the command line'
optFlags = [['help', 'h'], ['resume', 'r'], ['no-collector', 'n'], ['no-geoip', 'g'], ['list', 's'], ['printdeck', 'p'], ['verbose', 'v']]
optParameters = [['reportfile', 'o', None, 'report file name'], ['testdeck', 'i', None, 'Specify as input a test deck: a yaml file containing the tests to run and their arguments'], ['collector', 'c', None, 'Address of the collector of test results. This option should not be used, but you should always use a bouncer.'], ['bouncer', 'b', 'httpo://nkvphnp3p6agi5qq.onion', 'Address of the bouncer for test helpers.'], ['logfile', 'l', None, 'log file name'], ['pcapfile', 'O', None, 'pcap file name'], ['configfile', 'f', None, 'Specify a path to the ooniprobe configuration file'], ['datadir', 'd', None, 'Specify a path to the ooniprobe data directory'], ['annotations', 'a', None, 'Annotate the report with a key:value[, key:value] format.']]
opt_spew()[source]

Print an insanely verbose log of everything that happens. Useful when debugging freezes or locks in complex code.

opt_version()[source]

Display the ooniprobe version and exit.

parseArgs(*args)[source]
synopsis = 'sphinx-build [options] [path to test].py\n '
tracer = None
ooni.oonicli.parseOptions()[source]
ooni.oonicli.runWithDirector(logging=True, start_tor=True, check_incoherences=True)[source]

Instance the director, parse command line options and start an ooniprobe test!

reporter Module

class ooni.reporter.OONIBReportLog(file_name=None)[source]

Bases: object

Used to keep track of report creation on a collector backend.

closed(report_file)[source]
create_report_log()[source]
created(report_file, collector_address, report_id)[source]
creation_failed(report_file, collector_address)[source]
edit_log(*args, **kwds)[source]
get_report_log()[source]
incomplete(report_file)[source]
not_created(report_file)[source]
reports_in_progress
reports_incomplete
reports_to_upload
run(f, *arg, **kw)[source]
class ooni.reporter.OONIBReporter(test_details, collector_address)[source]

Bases: ooni.reporter.OReporter

createReport(*args, **kwargs)[source]

Creates a report on the oonib collector.

finish()[source]
validateCollectorAddress()[source]

Will raise :class:ooni.errors.InvalidOONIBCollectorAddress an exception if the oonib reporter is not valid.

writeReportEntry(*args, **kwargs)[source]
class ooni.reporter.OReporter(test_details)[source]

Bases: object

createReport()[source]

Override this with your own logic to implement tests.

finish()[source]
writeReportEntry(entry)[source]

Takes as input an entry and writes a report for it.

class ooni.reporter.OSafeDumper(stream, default_style=None, default_flow_style=None, canonical=None, indent=None, width=None, allow_unicode=None, line_break=None, encoding=None, explicit_start=None, explicit_end=None, version=None, tags=None)[source]

Bases: yaml.emitter.Emitter, yaml.serializer.Serializer, ooni.reporter.OSafeRepresenter, yaml.resolver.Resolver

This is a modification of the YAML Safe Dumper to use our own Safe Representer that supports complex numbers.

class ooni.reporter.OSafeRepresenter(default_style=None, default_flow_style=None)[source]

Bases: yaml.representer.SafeRepresenter

This is a custom YAML representer that allows us to represent reports safely. It extends the SafeRepresenter to be able to also represent complex numbers and scapy packet.

represent_complex(data)[source]
represent_data(data)[source]

This is very hackish. There is for sure a better way either by using the add_multi_representer or add_representer, the issue though lies in the fact that Scapy packets are metaclasses that leads to yaml.representer.get_classobj_bases to not be able to properly get the base of class of a Scapy packet. XXX fully debug this problem

yaml_representers = {<type 'int'>: <unbound method SafeRepresenter.represent_int>, <type 'unicode'>: <unbound method SafeRepresenter.represent_unicode>, <type 'long'>: <unbound method SafeRepresenter.represent_long>, <type 'float'>: <unbound method SafeRepresenter.represent_float>, <type 'NoneType'>: <unbound method SafeRepresenter.represent_none>, <type 'dict'>: <unbound method SafeRepresenter.represent_dict>, <type 'bool'>: <unbound method SafeRepresenter.represent_bool>, <type 'datetime.date'>: <unbound method SafeRepresenter.represent_date>, <type 'complex'>: <unbound method OSafeRepresenter.represent_complex>, <type 'tuple'>: <unbound method SafeRepresenter.represent_list>, <type 'list'>: <unbound method SafeRepresenter.represent_list>, <type 'datetime.datetime'>: <unbound method SafeRepresenter.represent_datetime>, <type 'set'>: <unbound method SafeRepresenter.represent_set>, <type 'str'>: <unbound method SafeRepresenter.represent_str>, None: <unbound method SafeRepresenter.represent_undefined>}
class ooni.reporter.Report(test_details, report_filename, reportEntryManager, collector_address=None)[source]

Bases: object

close()[source]

Close the report by calling it’s finish method.

Returns:
a :class:twisted.internet.defer.DeferredList that will fire when all the reports have been closed.
open()[source]

This will create all the reports that need to be created and fires the created callback of the reporter whose report got created.

open_oonib_reporter()[source]
write(measurement)[source]

Will return a deferred that will fire once the report for the specified measurement have been written to all the reporters.

Args:

measurement:
an instance of :class:ooni.tasks.Measurement
Returns:
a deferred that will fire once all the report entries have been written or errbacks when no more reporters
class ooni.reporter.YAMLReporter(test_details, report_destination='.', report_filename=None)[source]

Bases: ooni.reporter.OReporter

These are useful functions for reporting to YAML format.

report_destination:
the destination directory of the report
createReport()[source]

Writes the report header and fire callbacks on self.created

finish()[source]
writeReportEntry(entry)[source]
ooni.reporter.collector_supported(collector_address)[source]
ooni.reporter.createPacketReport(packet_list)[source]

Takes as input a packet a list.

Returns a dict containing a dict with the packet summary and the raw packet.

ooni.reporter.safe_dump(data, stream=None, **kw)[source]

Safely dump to a yaml file the specified data.