Giter VIP home page Giter VIP logo

measurement-kit's Introduction

Measurement Kit

(deprecated) Network measurement engine

As of 2020-03-15, Measurement Kit is deprecated. This date has been chosen arbitrarily such that we could write:

Friends, OONItarians, developers, lend me your ears;
I come to bury Measurement Kit, not to praise it.
The bugs that software have live after them;
The good is oft interred with their remote branches;

And, of course, some good, old piece of art is also in order:

cesaricidio

The rewrite of Measurement Kit in Go has been going on for quite some time now as ooni/probe-engine. As part of this rewrite, we considered all the use cases addressed by Measurement Kit, as documented by issue #1913.

We will most likely never release v0.11.0 of Measurement Kit. We will keep maintaining the 0.10.x version until 2021-03-14 (which is another interesting date). In the following, we'll discuss what options you have for replacing Measurement Kit in your use case. The current README reflects the situation "here and now". We are still working to provide a smooth upgrade path. Please, let us know if the upgrade path we have designed is troubling you by voting/contributing to the issues indicated below.

(The content of the old README.md is still available as OREADME.md.)

Changes in the settings JSON

The ooni/probe-engine implementation exposes similar APIs to Measurement Kit and specifically honours the data format of Measurement Kit v0.10.11. There should be no differences in the emitted events. There are however some differences in the settings as discussed below.

You should now add the following three keys to the settings JSON:

{
  "assets_dir": "",
  "state_dir": "",
  "temp_dir": ""
}

where assets_dir is the directory where to store assets, e.g. GeoIP databases; state_dir is the directory where to store the authentication information used with OONI orchestra; temp_dir is the directory where to store temporary files. If these three keys are not present, the test will fail during the startup phase (i.e. it will run for a very short time and you will see a bunch of failure.startup events emitted).

Also, the Go code does recognize all the settings recognized by Measurement Kit, but we have only implemented the settings required by OONI. All the other settings, when used, cause a failure during the experiment startup phase. If a not implemented setting is causing you issues, let us know by voting in the corresponding bug tracking issue.

Android

In your app/build.gradle file, replace

  implementation "org.openobservatory.measurement_kit:android-libs:$version"

with

  implementation "org.ooni:oonimkall:$version"

(The new package is called oonimkall because it's a OONI probe-engine based implementation of the mkall API for iOS. In turn, mkall means that we are bundling together all the MK APIs (i.e. the API for running experiments and all the ancillary APIs). For historical reasons android-libs was named before we defined the concept of mkall and was never renamed.)

The following differences apply between android-libs and oonimkall:

  1. the import path is oonimkall and you can use it directly as a scope for the classes, rather than doing import oonimkall.Foo;

  2. the MKAsyncTask class is replaced by oonimkall.Task and the MKAsyncTask.start factory is replaced by oonimkall.Oonimkall.startTask;

  3. the MKGeoIPLookupResults and MKGeoIPLookupTask classes are replaced by oonimkall.GeoLookupResults and oonimkall.GeoLookupTask;

  4. the MKOrchestraResults and MKOrchestraTask classes cannot be replaced, because it seems we are moving away from the orchestra model and, in going forward, we will only use orchestra internally inside of probe-engine to authenticate probes when they fetch input;

  5. the MKReporterResults and MKReporterTask classes are replaced by oonimkall.CollectorResults and oonimkall.CollectorTask;

  6. the MKResourcesManager class cannot be replaced, because the new code manages resources differently, by downloading them when needed into the assets_dir directory mentioned above;

  7. the MKVersion class cannot be replaced because version pinning in Go makes it much simpler to know which version of what software we compile;

  8. oonimkall throws Exception in much more cases than the code in android-libs that instead was using RuntimeException (using the latter was actually an anti-pattern and we are fixing it with the new code).

The following diff shows how to update code that runs an experiment, which is probably the most common use case of android-libs:

--- MK.java	2020-04-10 11:54:53.973521643 +0200
+++ PE.java	2020-04-10 11:55:50.915205613 +0200
@@ -1,10 +1,8 @@
 package com.example.something;
 
-import io.ooni.mk.MKAsyncTask;
-
 public class Example {
-    public static void run(settings String) {
-        MKAsyncTask task = MKAsyncTask.start(settings);
+    public static void run(settings String) throws Exception {
+        oonimkall.Task task = oonimkall.Oonimkall.startTask(settings);
         for (!task.isDone()) {
             String event = task.waitForNextEvent();
             System.out.println(event);

The most striking difference is that the function to start a task will explicitly throw Exception on failure. The old code would instead throw RuntimeException, as mentioned above. The required settings have slightly changed, as discussed above.

iOS

In your Podfile replace

    pod 'mkall', :git => 'https://github.com/measurement-kit/mkall-ios.git',
                 :tag => '$version'

with

    pod 'oonimkall', :podspec => 'https://dl.bintray.com/ooni/ios/oonimkall-$version.podspec'

The changes are similar to the ones described above for Android except that the oonimkall. prefix is Oonimkall for iOS. The following diff shows how you should be upgrading your MKAsyncTask code:

--- MK.m	2020-04-10 12:06:14.252573662 +0200
+++ PE.m	2020-04-10 12:08:18.520924676 +0200
@@ -1,11 +1,15 @@
 #import <Foundation/Foundation.h>
 
-#import <mkall/MKAsyncTask.h>
+#import <oonimkall/Oonimkall.h>
 
-void run(NSDictionary *settings) {
-    MKAsyncTask *task = [MKAsyncTask start:settings];
-    while (![task done]) {
-        NSDictionary *ev = [task waitForNextEvent];
+NSError *run(NSString *settings) {
+    NSError *error = nil;
+    OonimkallTask *task = OonimkallStartTask(settings, &error);
+    if (error != nil) {
+        return error;
+    }
+    while (![task isDone]) {
+        NSString *ev = [task waitForNextEvent];
         if (ev == nil) {
             continue;
         }

The most striking differences are the following. First, the function that starts a task now fails explicitly (e.g., if the settings are not valid JSON). Second, the new code takes in input and emits in output serialized JSONs rather than NSDictionary *. You are welcome to adapt code from MKAsyncTask to reimplement the previous behaviour. Also, remember that some extra mandatory settings are required, as described above.

Command Line

The miniooni binary mostly has the same CLI of the measurement_kit binary you could build from this repository. The following list describes the main differences between the two command line interfaces:

  • miniooni by default appends measurements to report.jsonl while measurement_kit uses a file name including the experiment name and the datetime when the experiment was started;

  • miniooni uses the -i, --input <input> flag to uniformly provide input for every experiment, while in measurement_kit different experiments use different command line flags after the experiment name (e.g., the -u <URL> flag is used by MK's Web Connectivity);

  • miniooni allows you to specify a proxy (e.g. Tor, Psiphon) with -P, --proxy <URL> that will be used for interacting with OONI services, but no such option exists in measurement_kit;

  • miniooni does not yet implement -s, --list that lists all the available experiments;

  • miniooni does not implement -l, --logfile <path> but you can use output redirection and tee to save logs anyway;

  • miniooni does not implement --ca-bundle-path <path>, --version, --geoip-country-path <path>, --geoip-asn-path <path>, because these resources are now downloaded and managed automatically;

  • miniooni does not implement --no-resolver-lookup;

  • miniooni writes state at $HOME/.miniooni.

We automatically build miniooni for windows/amd64, linux/amd64, and darwin/amd64 at every commit. The Linux build is static and does not depend on any external shared library. You can find the builds by looking into the GitHub actions of probe-engine and selecting for cli-windows, cli-linux, or cli-darwin. If you want us to attach such binaries to every release, please upvote the related issue.

Shared Library

The libooniffi package of ooni/probe-engine is a drop-in replacement for the Measurement Kit FFI API. The new API is defined by the ooniffi.h header. It is ABI compatible with MK's API. The only required change is to replace the mk_ prefix with ooniffi_. This diff shows the changes you typically need:

--- MK.c	2020-04-10 12:32:13.582783743 +0200
+++ PE.c	2020-04-10 12:32:36.633038633 +0200
@@ -1,19 +1,19 @@
 #include <stdio.h>
 
-#include <measurement_kit/ffi.h>
+#include <ooniffi.h>
 
 void run(const char *settings) {
-    mk_task_t *task = mk_task_start(settings);
+    ooniffi_task_t *task = ooniffi_task_start(settings);
     if (task == NULL) {
         return;
     }
-    while (!mk_task_is_done(task)) {
-        mk_event_t *event = mk_task_wait_for_next_event(task);
+    while (!ooniffi_task_is_done(task)) {
+        ooniffi_event_t *event = ooniffi_task_wait_for_next_event(task);
         if (event == NULL) {
             continue;
         }
-        printf("%s\n", mk_event_serialization(event));
-        mk_event_destroy(event);
+        printf("%s\n", ooniffi_event_serialization(event));
+        ooniffi_event_destroy(event);
     }
-    mk_task_destroy(task);
+    ooniffi_task_destroy(task);
 }

Of course, you also need to take into account the changes to the settings documented above.

You can generate your own builds with:

# macOS from macOS or Linux from Linux
go build -v -tags nomk -ldflags='-s -w' -buildmode c-shared -o libooniffi.so ./libooniffi
rm libooniffi.h  # not needed
cp libooniffi/ooniffi.h .  # use this header

# Windows from Linux or macOS with mingw-w64 installed
export CGO_ENABLED=1 GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc
go build -v -tags nomk -ldflags='-s -w' -buildmode c-shared -o libooniffi.dll ./libooniffi
rm libooniffi.h  # not needed
cp libooniffi/ooniffi.h .  # use this header

Let us know if you want us to automatically publish libooniffi dynamic libraries by upvoting the related issue.

measurement-kit's People

Contributors

anadahz avatar antoniolangiu avatar bassosimone avatar darkk avatar davideallavena avatar elevenfive avatar hellais avatar helloimcarmine avatar joelanders avatar lorenzoprimi avatar neheb avatar readmecritic avatar rschulman avatar willscott avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

measurement-kit's Issues

Move headers to include/ to simplify integration

To integrate libight on a iOS application one needs the library (say libight.a or libight.dylib including all the iOS architectures) and the headers. In this respect, it makes sense to put the headers in a folder name after the library (e.g., ight/) as many libraries do (think, e.g., at <event2/dns.h>).

To do this, in turn, we should modify the repository and put all the headers in include/ight so that headers including other headers can do this using the idiom #include <ight/foo/bar.hpp> which is valid both when the library is not installed and when it is installed.

Find a way to separate the logs of different tests

This is needed to move forward the development of the iOS application. Functionally speaking, it should work that logs produced by different tests goes to different channels that one can retrieve later. I have yet to think at how this could be implemented in libight.

Add SOCKS5 support to libight

To support sending TCP connections over Tor we need to support the SOCKS5 (or SOCKS4a) protocol in libight.

This functionality should be exposed in two manners:

  1. Directly via the Connection object

  2. By passing a special Options keys to ight.net.http

Make std::function usage robust with respect to self-destruction of their closure

This problem was first exposed by fix 8c992d7 of #110. When a std::function assigns to itself and the code is compiled using gcc, the closure is immediately destroyed. If the closure is used to keep safe the object that owns the std::function, this leads to use after free with possible crash.

The following minimal example (derived a longer example of this behavior) shows the problem:

#include <functional>
#include <memory>

struct Foo {
    std::function<void()> func;
};

static Foo *make_foo() {
    std::shared_ptr<Foo> foobar{new Foo};
    foobar->func = [foobar]() { foobar->func = nullptr; };
    return foobar.get();
}

int main() {
    make_foo()->func();
}

(make_foo() only returns a pointer to foobar which is however not destroyed at end of make_foo() scope because it is kept alive by the capture list of the lambda assigned to foobar->func.)

Compiled with g++ -Wall -std=c++11 minimal.cpp -o minimal and executed with `valgrind ./minimal, this leads to the following memory errors:

==5873== Memcheck, a memory error detector
==5873== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==5873== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==5873== Command: ./minimal
==5873== 
==5873== Invalid free() / delete / delete[] / realloc()
==5873==    at 0x4C2C2BC: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5873==    by 0x40106B: std::_Function_base::_Base_manager<make_foo()::{lambda()#1}>::_M_destroy(std::_Any_data&, std::integral_constant<bool, false>) (in /tmp/minimal)
==5873==    by 0x400F25: std::_Function_base::_Base_manager<make_foo()::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<make_foo()::{lambda()#1}> const&, std::_Manager_operation) (in /tmp/minimal)
==5873==    by 0x4012A4: std::function<void ()>::operator=(decltype(nullptr)) (in /tmp/minimal)
==5873==    by 0x400BB6: make_foo()::{lambda()#1}::operator()() const (in /tmp/minimal)
==5873==    by 0x400E93: std::_Function_handler<void (), make_foo()::{lambda()#1}>::_M_invoke(std::_Any_data const&) (in /tmp/minimal)
==5873==    by 0x401343: std::function<void ()>::operator()() const (in /tmp/minimal)
==5873==    by 0x400C98: main (in /tmp/minimal)
==5873==  Address 0x5a1c100 is 0 bytes inside a block of size 16 free'd
==5873==    at 0x4C2C2BC: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5873==    by 0x40106B: std::_Function_base::_Base_manager<make_foo()::{lambda()#1}>::_M_destroy(std::_Any_data&, std::integral_constant<bool, false>) (in /tmp/minimal)
==5873==    by 0x400F25: std::_Function_base::_Base_manager<make_foo()::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<make_foo()::{lambda()#1}> const&, std::_Manager_operation) (in /tmp/minimal)
==5873==    by 0x4010F2: std::_Function_base::~_Function_base() (in /tmp/minimal)
==5873==    by 0x401125: std::function<void ()>::~function() (in /tmp/minimal)
==5873==    by 0x4014E9: Foo::~Foo() (in /tmp/minimal)
==5873==    by 0x401953: std::_Sp_counted_ptr<Foo*, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (in /tmp/minimal)
==5873==    by 0x4013DB: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (in /tmp/minimal)
==5873==    by 0x401258: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() (in /tmp/minimal)
==5873==    by 0x40115D: std::__shared_ptr<Foo, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() (in /tmp/minimal)
==5873==    by 0x401177: std::shared_ptr<Foo>::~shared_ptr() (in /tmp/minimal)
==5873==    by 0x400BD1: make_foo()::{lambda()#1}::~make_foo() (in /tmp/minimal)
==5873== 
==5873== Invalid write of size 8
==5873==    at 0x4012A9: std::function<void ()>::operator=(decltype(nullptr)) (in /tmp/minimal)
==5873==    by 0x400BB6: make_foo()::{lambda()#1}::operator()() const (in /tmp/minimal)
==5873==    by 0x400E93: std::_Function_handler<void (), make_foo()::{lambda()#1}>::_M_invoke(std::_Any_data const&) (in /tmp/minimal)
==5873==    by 0x401343: std::function<void ()>::operator()() const (in /tmp/minimal)
==5873==    by 0x400C98: main (in /tmp/minimal)
==5873==  Address 0x5a1c050 is 16 bytes inside a block of size 32 free'd
==5873==    at 0x4C2C2BC: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5873==    by 0x40195B: std::_Sp_counted_ptr<Foo*, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (in /tmp/minimal)
==5873==    by 0x4013DB: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (in /tmp/minimal)
==5873==    by 0x401258: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() (in /tmp/minimal)
==5873==    by 0x40115D: std::__shared_ptr<Foo, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() (in /tmp/minimal)
==5873==    by 0x401177: std::shared_ptr<Foo>::~shared_ptr() (in /tmp/minimal)
==5873==    by 0x400BD1: make_foo()::{lambda()#1}::~make_foo() (in /tmp/minimal)
==5873==    by 0x401063: std::_Function_base::_Base_manager<make_foo()::{lambda()#1}>::_M_destroy(std::_Any_data&, std::integral_constant<bool, false>) (in /tmp/minimal)
==5873==    by 0x400F25: std::_Function_base::_Base_manager<make_foo()::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<make_foo()::{lambda()#1}> const&, std::_Manager_operation) (in /tmp/minimal)
==5873==    by 0x4012A4: std::function<void ()>::operator=(decltype(nullptr)) (in /tmp/minimal)
==5873==    by 0x400BB6: make_foo()::{lambda()#1}::operator()() const (in /tmp/minimal)
==5873==    by 0x400E93: std::_Function_handler<void (), make_foo()::{lambda()#1}>::_M_invoke(std::_Any_data const&) (in /tmp/minimal)
==5873== 
==5873== Invalid write of size 8
==5873==    at 0x4012B5: std::function<void ()>::operator=(decltype(nullptr)) (in /tmp/minimal)
==5873==    by 0x400BB6: make_foo()::{lambda()#1}::operator()() const (in /tmp/minimal)
==5873==    by 0x400E93: std::_Function_handler<void (), make_foo()::{lambda()#1}>::_M_invoke(std::_Any_data const&) (in /tmp/minimal)
==5873==    by 0x401343: std::function<void ()>::operator()() const (in /tmp/minimal)
==5873==    by 0x400C98: main (in /tmp/minimal)
==5873==  Address 0x5a1c058 is 24 bytes inside a block of size 32 free'd
==5873==    at 0x4C2C2BC: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5873==    by 0x40195B: std::_Sp_counted_ptr<Foo*, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (in /tmp/minimal)
==5873==    by 0x4013DB: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (in /tmp/minimal)
==5873==    by 0x401258: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() (in /tmp/minimal)
==5873==    by 0x40115D: std::__shared_ptr<Foo, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() (in /tmp/minimal)
==5873==    by 0x401177: std::shared_ptr<Foo>::~shared_ptr() (in /tmp/minimal)
==5873==    by 0x400BD1: make_foo()::{lambda()#1}::~make_foo() (in /tmp/minimal)
==5873==    by 0x401063: std::_Function_base::_Base_manager<make_foo()::{lambda()#1}>::_M_destroy(std::_Any_data&, std::integral_constant<bool, false>) (in /tmp/minimal)
==5873==    by 0x400F25: std::_Function_base::_Base_manager<make_foo()::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<make_foo()::{lambda()#1}> const&, std::_Manager_operation) (in /tmp/minimal)
==5873==    by 0x4012A4: std::function<void ()>::operator=(decltype(nullptr)) (in /tmp/minimal)
==5873==    by 0x400BB6: make_foo()::{lambda()#1}::operator()() const (in /tmp/minimal)
==5873==    by 0x400E93: std::_Function_handler<void (), make_foo()::{lambda()#1}>::_M_invoke(std::_Any_data const&) (in /tmp/minimal)
==5873== 
==5873== 
==5873== HEAP SUMMARY:
==5873==     in use at exit: 0 bytes in 0 blocks
==5873==   total heap usage: 3 allocs, 4 frees, 72 bytes allocated
==5873== 
==5873== All heap blocks were freed -- no leaks are possible
==5873== 
==5873== For counts of detected and suppressed errors, rerun with: -v
==5873== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)

This situation can be easily fixed as follows (and this is, by the way, the fix applied in #110):

--- minimal.cpp 2015-03-30 15:29:36.304735886 +0200
+++ fix1.cpp    2015-03-30 15:33:52.815008088 +0200
@@ -7,7 +7,10 @@

 static Foo *make_foo() {
     std::shared_ptr<Foo> foobar{new Foo};
-    foobar->func = [foobar]() { foobar->func = nullptr; };
+    foobar->func = [foobar]() {
+        auto func = foobar->func;
+        foobar->func = nullptr;
+    };
     return foobar.get();
 }

Which compiled as g++ -Wall -std=c++11 fix1.cpp -o fix1 and executed as ./fix1 leads to:

==5902== Memcheck, a memory error detector
==5902== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==5902== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==5902== Command: ./fix1
==5902== 
==5902== 
==5902== HEAP SUMMARY:
==5902==     in use at exit: 0 bytes in 0 blocks
==5902==   total heap usage: 4 allocs, 4 frees, 88 bytes allocated
==5902== 
==5902== All heap blocks were freed -- no leaks are possible
==5902== 
==5902== For counts of detected and suppressed errors, rerun with: -v
==5902== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

However the previous fix is error prone to implement because one has to manually inspect and fix the invocation of all functions. A better fix could be a the following (or, even better, a refined version of the following that completely wraps std::function):

--- minimal.cpp 2015-03-30 15:29:36.304735886 +0200
+++ better-fix.cpp  2015-03-30 15:42:02.510892429 +0200
@@ -1,8 +1,22 @@
 #include <functional>
 #include <memory>

+template <typename T> class Slot {
+  private:
+    std::function<T> func;
+  public:
+    template <class Callable> Slot& operator=(Callable lambda) {
+        func = lambda;
+        return *this;
+    }
+    void operator()() {
+        auto saved = func;
+        func();
+    }
+};
+
 struct Foo {
-    std::function<void()> func;
+    Slot<void()> func;
 };

 static Foo *make_foo() {

Which compiled as g++ -std=c++11 -Wall better-fix.cpp -o better-fix and run as valgrind ./better-fix leads to:

==5927== Memcheck, a memory error detector
==5927== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==5927== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==5927== Command: ./better-fix
==5927== 
==5927== 
==5927== HEAP SUMMARY:
==5927==     in use at exit: 0 bytes in 0 blocks
==5927==   total heap usage: 4 allocs, 4 frees, 88 bytes allocated
==5927== 
==5927== All heap blocks were freed -- no leaks are possible
==5927== 
==5927== For counts of detected and suppressed errors, rerun with: -v
==5927== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Automatically run regress tests through Valgrind

It should be useful to run regress tests through Valgrind. To this end, we should create a script that compiles libight using ./configure --disable-shared and than run each regress test script using valgrind --error-exitcode=1 so that, if there is a memory error or a leak, make check fails.

Make a list of OONI tests to be implemented using libight.

net/connection.cpp should properly route exceptions

When a callback of IghtConnection is executed, say the on_data callback, exceptions should be catched by the the code of IghtConnection so that the exception is correctly routed to the on_error callback, typically causing the connection itself to be closed.

I wonder what we should do if the on_error callback itself fails. My take is that we should either filter the error or let it propagate. I'm quite against calling on_error again, because it seems to me that the semantic of such a callback is that it will be called at most once (i.e., it could surprise people if it is called more than once).

Memory leak in async.cpp

[Moved from measurement-kit/measurement-kit-project-management#8]

Preparing #133, I noticed this memory leak that occurs in async.cpp code (this run in particular sits on top of #133).

Since there are no leaks when running individual OONI tests and since this is a memory leak and since we use many (perhaps too many) shared pointers, I am inclined to think that this is circular reference issue. Anyway, this is what Valgrind says about it:

test #1: http: BODY
test #1: http: received body chunk...
test #1: http: BODY
test #1: http: received body chunk...
test #1: http: END
test #1: http: we have reached end of response
test #1: net_test: tearing down
test #1: net_test: written entry
test #1: net_test: reached end of input
async: test stopped
test complete: 120671888
async: bottom of loop thread
async: loop thread locked
async: empty
async: thread detached
all tests completed
async: exiting from thread
===============================================================================
test cases: 1 | 1 passed
assertions: - none -

==4019== 
==4019== HEAP SUMMARY:
==4019==     in use at exit: 2,864,310 bytes in 14,724 blocks
==4019==   total heap usage: 330,961 allocs, 316,237 frees, 36,759,700 bytes allocated
==4019== 
==4019== 1 bytes in 1 blocks are possibly lost in loss record 2 of 1,119
==4019==    at 0x4C2C100: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4019==    by 0x40C6D1: _M_clone (functional:1878)
==4019==    by 0x40C6D1: std::_Function_base::_Base_manager<run_http_invalid_request_line(ight::common::async::Async&)::{lambda(char const*)#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<run_http_invalid_request_line(ight::common::async::Async&)::{lambda(char const*)#1}> const&, std::_Manager_operation) (functional:1914)
==4019==    by 0x41FBB3: std::function<void (char const*)>::function(std::function<void (char const*)> const&) (functional:2410)
==4019==    by 0x41FC2E: operator= (functional:2243)
==4019==    by 0x41FC2E: set_logger (log.hpp:69)
==4019==    by 0x41FC2E: ight::common::net_test::NetTest::set_log_function(std::function<void (char const*)>) (net_test.hpp:39)
==4019==    by 0x416141: run_http_invalid_request_line(ight::common::async::Async&) (async.cpp:43)
==4019==    by 0x4168F9: ____C_A_T_C_H____T_E_S_T____76() (async.cpp:98)
==4019==    by 0x43BBA4: invoke (catch.hpp:5507)
==4019==    by 0x43BBA4: invoke (catch.hpp:6389)
==4019==    by 0x43BBA4: runCurrentTest (catch.hpp:5131)
==4019==    by 0x43BBA4: runTest (catch.hpp:5001)
==4019==    by 0x43BBA4: Catch::Runner::runTests() (catch.hpp:5275)
==4019==    by 0x43C7F9: Catch::Session::run() (catch.hpp:5395)
==4019==    by 0x4081DE: run (catch.hpp:5378)
==4019==    by 0x4081DE: main (catch.hpp:8819)
==4019== 
==4019== 32,808 bytes in 1 blocks are possibly lost in loss record 1,104 of 1,119
==4019==    at 0x4C2C100: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4019==    by 0x45F797: make_logger (log.hpp:74)
==4019==    by 0x45F797: NetTest (net_test.hpp:31)
==4019==    by 0x45F797: ight::ooni::net_test::NetTest::NetTest(std::string, std::map<std::string, std::string, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >) (net_test.cpp:9)
==4019==    by 0x415F47: HTTPTest (http_test.hpp:28)
==4019==    by 0x415F47: HTTPTest (http_test.hpp:34)
==4019==    by 0x415F47: HTTPInvalidRequestLine (http_invalid_request_line.hpp:38)
==4019==    by 0x415F47: run_http_invalid_request_line(ight::common::async::Async&) (async.cpp:38)
==4019==    by 0x4168F9: ____C_A_T_C_H____T_E_S_T____76() (async.cpp:98)
==4019==    by 0x43BBA4: invoke (catch.hpp:5507)
==4019==    by 0x43BBA4: invoke (catch.hpp:6389)
==4019==    by 0x43BBA4: runCurrentTest (catch.hpp:5131)
==4019==    by 0x43BBA4: runTest (catch.hpp:5001)
==4019==    by 0x43BBA4: Catch::Runner::runTests() (catch.hpp:5275)
==4019==    by 0x43C7F9: Catch::Session::run() (catch.hpp:5395)
==4019==    by 0x4081DE: run (catch.hpp:5378)
==4019==    by 0x4081DE: main (catch.hpp:8819)
==4019== 
==4019== 73,883 (48 direct, 73,835 indirect) bytes in 1 blocks are definitely lost in loss record 1,107 of 1,119
==4019==    at 0x4C2C100: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4019==    by 0x43A072: allocate (new_allocator.h:104)
==4019==    by 0x43A072: allocate (alloc_traits.h:357)
==4019==    by 0x43A072: _M_get_node (stl_tree.h:385)
==4019==    by 0x43A072: std::_Rb_tree_node<std::pair<std::string const, std::string> >* std::_Rb_tree<std::string, std::pair<std::string const, std::string>, std::_Select1st<std::pair<std::string const, std::string> >, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >::_M_create_node<std::pair<std::string const, std::string> const&>(std::pair<std::string const, std::string> const&) (stl_tree.h:417)
==4019==    by 0x43A12D: _M_clone_node (stl_tree.h:445)
==4019==    by 0x43A12D: std::_Rb_tree<std::string, std::pair<std::string const, std::string>, std::_Select1st<std::pair<std::string const, std::string> >, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >::_M_copy(std::_Rb_tree_node<std::pair<std::string const, std::string> > const*, std::_Rb_tree_node<std::pair<std::string const, std::string> >*) (stl_tree.h:1207)
==4019==    by 0x45F9E7: _Rb_tree (stl_tree.h:676)
==4019==    by 0x45F9E7: map (stl_map.h:183)
==4019==    by 0x45F9E7: ight::ooni::net_test::NetTest::NetTest(std::string, std::map<std::string, std::string, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >) (net_test.cpp:9)
==4019==    by 0x43A3E9: DNSTest (dns_test.hpp:37)
==4019==    by 0x43A3E9: ight::ooni::dns_injection::DNSInjection::DNSInjection(std::string, std::map<std::string, std::string, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >) (dns_injection.hpp:38)
==4019==    by 0x41672C: run_dns_injection (async.cpp:52)
==4019==    by 0x41672C: ____C_A_T_C_H____T_E_S_T____76() (async.cpp:97)
==4019==    by 0x43BBA4: invoke (catch.hpp:5507)
==4019==    by 0x43BBA4: invoke (catch.hpp:6389)
==4019==    by 0x43BBA4: runCurrentTest (catch.hpp:5131)
==4019==    by 0x43BBA4: runTest (catch.hpp:5001)
==4019==    by 0x43BBA4: Catch::Runner::runTests() (catch.hpp:5275)
==4019==    by 0x43C7F9: Catch::Session::run() (catch.hpp:5395)
==4019==    by 0x4081DE: run (catch.hpp:5378)
==4019==    by 0x4081DE: main (catch.hpp:8819)
==4019== 
==4019== 206,236 (3,552 direct, 202,684 indirect) bytes in 4 blocks are definitely lost in loss record 1,112 of 1,119
==4019==    at 0x4C2C100: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4019==    by 0x41696E: run_tcp_connect (async.cpp:67)
==4019==    by 0x41696E: ____C_A_T_C_H____T_E_S_T____76() (async.cpp:100)
==4019==    by 0x43BBA4: invoke (catch.hpp:5507)
==4019==    by 0x43BBA4: invoke (catch.hpp:6389)
==4019==    by 0x43BBA4: runCurrentTest (catch.hpp:5131)
==4019==    by 0x43BBA4: runTest (catch.hpp:5001)
==4019==    by 0x43BBA4: Catch::Runner::runTests() (catch.hpp:5275)
==4019==    by 0x43C7F9: Catch::Session::run() (catch.hpp:5395)
==4019==    by 0x4081DE: run (catch.hpp:5378)
==4019==    by 0x4081DE: main (catch.hpp:8819)
==4019== 
==4019== 229,670 (2,664 direct, 227,006 indirect) bytes in 3 blocks are definitely lost in loss record 1,113 of 1,119
==4019==    at 0x4C2C100: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4019==    by 0x416714: run_dns_injection (async.cpp:53)
==4019==    by 0x416714: ____C_A_T_C_H____T_E_S_T____76() (async.cpp:97)
==4019==    by 0x43BBA4: invoke (catch.hpp:5507)
==4019==    by 0x43BBA4: invoke (catch.hpp:6389)
==4019==    by 0x43BBA4: runCurrentTest (catch.hpp:5131)
==4019==    by 0x43BBA4: runTest (catch.hpp:5001)
==4019==    by 0x43BBA4: Catch::Runner::runTests() (catch.hpp:5275)
==4019==    by 0x43C7F9: Catch::Session::run() (catch.hpp:5395)
==4019==    by 0x4081DE: run (catch.hpp:5378)
==4019==    by 0x4081DE: main (catch.hpp:8819)
==4019== 
==4019== 257,298 (48 direct, 257,250 indirect) bytes in 1 blocks are definitely lost in loss record 1,114 of 1,119
==4019==    at 0x4C2C100: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4019==    by 0x4E5058F: std::_Rb_tree<boost::shared_ptr<YAML::detail::node>, boost::shared_ptr<YAML::detail::node>, std::_Identity<boost::shared_ptr<YAML::detail::node> >, std::less<boost::shared_ptr<YAML::detail::node> >, std::allocator<boost::shared_ptr<YAML::detail::node> > >::_M_insert_(std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, boost::shared_ptr<YAML::detail::node> const&) (in /usr/lib/x86_64-linux-gnu/libyaml-cpp.so.0.5.1)
==4019==    by 0x4E4FDF4: YAML::detail::memory::merge(YAML::detail::memory const&) (in /usr/lib/x86_64-linux-gnu/libyaml-cpp.so.0.5.1)
==4019==    by 0x4E4FE39: YAML::detail::memory_holder::merge(YAML::detail::memory_holder&) (in /usr/lib/x86_64-linux-gnu/libyaml-cpp.so.0.5.1)
==4019==    by 0x46072B: AssignNode (impl.h:270)
==4019==    by 0x46072B: operator= (impl.h:242)
==4019==    by 0x46072B: operator= (entry.hpp:17)
==4019==    by 0x46072B: ight::ooni::net_test::NetTest::begin(std::function<void ()>) (net_test.cpp:96)
==4019==    by 0x43F5F7: ight::common::async::Async::loop_thread(ight::common::pointer::SharedPointer<ight::common::async::AsyncState>) (async.cpp:72)
==4019==    by 0x4404FE: _M_invoke<0ul> (functional:1700)
==4019==    by 0x4404FE: operator() (functional:1688)
==4019==    by 0x4404FE: std::thread::_Impl<std::_Bind_simple<void (*(ight::common::pointer::SharedPointer<ight::common::async::AsyncState>))(ight::common::pointer::SharedPointer<ight::common::async::AsyncState>)> >::_M_run() (thread:115)
==4019==    by 0x55BBE2F: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20)
==4019==    by 0x5D356A9: start_thread (pthread_create.c:333)
==4019==    by 0x6052EEC: clone (clone.S:109)
==4019== 
==4019== 257,298 (48 direct, 257,250 indirect) bytes in 1 blocks are definitely lost in loss record 1,115 of 1,119
==4019==    at 0x4C2C100: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4019==    by 0x4E5058F: std::_Rb_tree<boost::shared_ptr<YAML::detail::node>, boost::shared_ptr<YAML::detail::node>, std::_Identity<boost::shared_ptr<YAML::detail::node> >, std::less<boost::shared_ptr<YAML::detail::node> >, std::allocator<boost::shared_ptr<YAML::detail::node> > >::_M_insert_(std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, boost::shared_ptr<YAML::detail::node> const&) (in /usr/lib/x86_64-linux-gnu/libyaml-cpp.so.0.5.1)
==4019==    by 0x4E4FDF4: YAML::detail::memory::merge(YAML::detail::memory const&) (in /usr/lib/x86_64-linux-gnu/libyaml-cpp.so.0.5.1)
==4019==    by 0x4E4FE39: YAML::detail::memory_holder::merge(YAML::detail::memory_holder&) (in /usr/lib/x86_64-linux-gnu/libyaml-cpp.so.0.5.1)
==4019==    by 0x459A23: ight::ooni::http_test::HTTPTest::request(std::map<std::string, std::string, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >, std::map<std::string, std::string, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >, std::string, std::function<void (ight::common::error::Error, ight::protocols::http::Response&&)>&&)::{lambda(ight::common::error::Error, ight::protocols::http::Response&&)#1}::operator()(ight::common::error::Error, ight::protocols::http::Response&&) const (impl.h:327)
==4019==    by 0x4572B0: emit_end (http.hpp:550)
==4019==    by 0x4572B0: operator() (http.hpp:642)
==4019==    by 0x4572B0: std::_Function_handler<void (), ight::protocols::http::Request::Request(std::map<std::string, std::string, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >, std::map<std::string, std::string, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >, std::string, std::function<void (ight::common::error::Error, ight::protocols::http::Response&&)>&&, ight::common::pointer::SharedPointer<ight::common::log::Logger>, std::set<ight::protocols::http::Request*, std::less<ight::protocols::http::Request*>, std::allocator<ight::protocols::http::Request*> >*)::{lambda()#2}::operator()() const::{lambda()#4}>::_M_invoke(std::_Any_data const&) (functional:2039)
==4019==    by 0x470D49: ight::protocols::http::ResponseParserImpl::do_message_complete(http_parser*) (http.cpp:140)
==4019==    by 0x475F00: http_parser_execute (http_parser.c:1917)
==4019==    by 0x4706FA: operator() (http.cpp:149)
==4019==    by 0x4706FA: std::_Function_handler<bool (iovec*), ight::protocols::http::ResponseParserImpl::parse()::{lambda(iovec*)#1}>::_M_invoke(std::_Any_data const&, iovec*) (functional:2025)
==4019==    by 0x46EFB0: operator() (functional:2439)
==4019==    by 0x46EFB0: foreach (buffer.hpp:143)
==4019==    by 0x46EFB0: parse (http.cpp:159)
==4019==    by 0x46EFB0: feed (http.cpp:246)
==4019==    by 0x46EFB0: ight::protocols::http::ResponseParser::feed(ight::common::pointer::SharedPointer<ight::net::buffer::Buffer>) (http.cpp:358)
==4019==    by 0x456AD4: operator() (http.hpp:242)
==4019==    by 0x456AD4: std::_Function_handler<void (ight::common::pointer::SharedPointer<ight::net::buffer::Buffer>), ight::protocols::http::Stream::connection_ready()::{lambda(ight::common::pointer::SharedPointer<ight::net::buffer::Buffer>)#1}>::_M_invoke(std::_Any_data const&, ight::common::pointer::SharedPointer<ight::net::buffer::Buffer>) (functional:2039)
==4019==    by 0x47D847: std::function<void (ight::common::pointer::SharedPointer<ight::net::buffer::Buffer>)>::operator()(ight::common::pointer::SharedPointer<ight::net::buffer::Buffer>) const (functional:2439)
==4019== 
==4019== 635,788 (3,712 direct, 632,076 indirect) bytes in 4 blocks are definitely lost in loss record 1,117 of 1,119
==4019==    at 0x4C2C100: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4019==    by 0x415D0E: run_http_invalid_request_line(ight::common::async::Async&) (async.cpp:39)
==4019==    by 0x416903: ____C_A_T_C_H____T_E_S_T____76() (async.cpp:99)
==4019==    by 0x43BBA4: invoke (catch.hpp:5507)
==4019==    by 0x43BBA4: invoke (catch.hpp:6389)
==4019==    by 0x43BBA4: runCurrentTest (catch.hpp:5131)
==4019==    by 0x43BBA4: runTest (catch.hpp:5001)
==4019==    by 0x43BBA4: Catch::Runner::runTests() (catch.hpp:5275)
==4019==    by 0x43C7F9: Catch::Session::run() (catch.hpp:5395)
==4019==    by 0x4081DE: run (catch.hpp:5378)
==4019==    by 0x4081DE: main (catch.hpp:8819)
==4019== 
==4019== 1,133,959 (3,712 direct, 1,130,247 indirect) bytes in 4 blocks are definitely lost in loss record 1,119 of 1,119
==4019==    at 0x4C2C100: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4019==    by 0x415D0E: run_http_invalid_request_line(ight::common::async::Async&) (async.cpp:39)
==4019==    by 0x4168F9: ____C_A_T_C_H____T_E_S_T____76() (async.cpp:98)
==4019==    by 0x43BBA4: invoke (catch.hpp:5507)
==4019==    by 0x43BBA4: invoke (catch.hpp:6389)
==4019==    by 0x43BBA4: runCurrentTest (catch.hpp:5131)
==4019==    by 0x43BBA4: runTest (catch.hpp:5001)
==4019==    by 0x43BBA4: Catch::Runner::runTests() (catch.hpp:5275)
==4019==    by 0x43C7F9: Catch::Session::run() (catch.hpp:5395)
==4019==    by 0x4081DE: run (catch.hpp:5378)
==4019==    by 0x4081DE: main (catch.hpp:8819)
==4019== 
==4019== LEAK SUMMARY:
==4019==    definitely lost: 13,784 bytes in 18 blocks
==4019==    indirectly lost: 2,780,348 bytes in 14,690 blocks
==4019==      possibly lost: 32,809 bytes in 2 blocks
==4019==    still reachable: 37,369 bytes in 14 blocks
==4019==         suppressed: 0 bytes in 0 blocks
==4019== Reachable blocks (those to which a pointer was found) are not shown.
==4019== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==4019== 
==4019== For counts of detected and suppressed errors, rerun with: -v
==4019== ERROR SUMMARY: 9 errors from 9 contexts (suppressed: 0 from 0)

Avoid std::make_shared<X> to construct SharedPointer<X> objects

The SharedPointer<> documentation (and the way in which SharedPointer<> is used) suggest to use std::make_shared<Foo> to create a smart pointer to an instance of Foo. As a comment to SharedPointer<> notes, using std::make_shared<> like this is safe as long as SharedPointer<> does not add new attributes or classes to its parent class std::shared_ptr otherwise there is the risk of object slicing.

A better approach (in the sense that the code becomes more robust because there is no chance that object slicing is introduced) is the following: each time SharedPointer<Foo> can be initialized on construction do SharedPointer<Foo> bar = new Foo(); otherwise, if SharedPointer<Foo> bar do something like bar.reset(new Foo()).

Allow easy measurement-kit integration on Android

#1. Compile libight for Android

The first problem to solve is how to compile libight for Android. One option is to use the Android.mk and Application.mk files, as we do today. However, I'd rather use the autotools-based build system, to avoid maintaining two build systems and because this is in any case the way to go to compile Tor (which sooner or later we'll need to integrate, since Tor is needed to publish ooni reports).

A quick research (thanks, @alessandro40, for doing that) indicates that there are ways to convince the Android build system to compile something the autotools way. We should investigate further.

Regardless the way we choose, there is the problem that IIRC we could not cross-compile libight for Android using clang because the linker failed to find some libyaml-cpp symbols.

I guess we should just try with gcc and see what happens. By the way, switching to gcc we'll also remove the need to maintain a separate libevent repository for Android, since the latest stable version of libevent cross-compiles for Android using gcc.

Update [2015-11-11]: this part has now been done and merged into master.
#2. Integrate Java code with C code

Android applications are written in Java whereas libight is written in C++. The standard way to interface Java code with C/C++ code is JNI. SWIG helps a lot because it auto-generates JNI code from C/C++ headers.

Therefore, SWIG may be the way to go. I recall that one can register Java callbacks for C objects using a SWIG feature called directors but I don't know if this also extends to the std::function<> callbacks that we use so extensively; nor I know whether SWIG supports C++11. These are things that we should check after we have successfully compiled libight for Android... a good starting point could be this document describing the status of SWIG support for C++11.

Another, orthogonal option (again thanks for @alessandro40 for preliminary research) is to do like Orbot; compile a binary that is saved in the resources directory and then execute it. If we follow this alternative approach (which I like less than the previous because I'd rather follow the same approach on both iOS and Android), we need to create some extra software that provides a main() for libight and interacts in some way with its caller. As a side note, it's not recommended to fork on Android.

Update [2015-11-11]: Working on this in #200. I am not currently using SWIG and have designed a rather simple JNI API that avoids mixed Java/C++ objects for simplicity.

Transport: implement the << operator

We should implement the << operator for Transport. Such operator should receive in input std::string, SharedPointer<Buffer> and Buffer as the send() method does.

The implementation of this operator makes the Transport API more nice because it allows one to chain multiple write operations.

In connection.cpp raise exception rather than returning -1, use << if possible

In the interest of making code more compact and expressive (and also considering issue #32), the functions of IghtConnection such as puts should raise an exception on failure (as opposed to returning -1).

Moreover, the << operator should be overridden for IghtConnection so that we can chain many pieces of data to be sent in just one call.

test/foobar.py assume that libneubot is installed

All the python-based tests in the test/ directory make the assumption that libneubot (so libight) is installed.

Therefore, one cannot run the python-based tests unless he/she installs libneubot/libight on his/her system.

Even when using async "test complete" must be a callback of the test

As discussed with @lorenzoPrimi during the review of ooni/probe-ios#14, it would be handy if the "test ended" callback was a callback of the test rather than a callback of the Async object. This way, in fact, it would be possible to bind the lambda closure to the self object, while it's more convoluted to do the same when the "test ended" is a callback of Async, because when you set the callback the ObjectiveC part of the test typically does not exist yet.

Find a way to generate code coverage reports

It would be nice to have reports on what is the current code coverage of libight. The unittesting framework we chose (Catch) does not seem to support this natively, but it looks like it is something that can be done with clang --coverage.

Doing a little bit of research I came up with these links that may be useful:

Alternatively expcov also seems promising:

We should add libevent to src/ext

To compile on Android, we also need to compile (and statically link to) Libevent. There is on GitHub a repository that added the necessary patches needed to compile Libevent on Android:

https://github.com/ventureresearch/libevent

The base branch of such repository is patches-2.0, which seems to be the maintenance branch of Libevent 2.0.x.

We should fork ventureresearch/libevent (just in case we need to apply extra local changes) and then we should merge the patches/2.0 branch of libevent/libevent (or viceversa) to ensure that our patches/2.0 branch contains both the latest patches and the patches to compile on Android. Then we should add our fork, as a subrepository, in src/ext/libevent.

Unrelated to Android and with lower priority, we should also fix, for consistency, the ./configure script not to exit if Libevent is not found on the system; now, in fact, in such case we can compile and link with the Libevent that is shipped in src/ext/libevent.

Relicense the tree using the BSD license

We concluded that the library should use the BSD license (two-clause flavor), so that it can be easily used on Android, iOS both by Neubot and by OONI.

I will take care of switching the code (most of which is copyrighted by me) to the BSD license.

Error improvements

During the review of #175, @anadahz suggested to further enhance the common::Error class to allow to store into the error more context information such as the name of the class that triggered the error, the file name, the line. I think this can be implemented using macros (or inline functions?).

Improve the build script for iOS

  1. make sure that this build script cross-compiles the library and all its dependencies for all the architectures supported by iOS (that is, both arm and x86 for the emulator)
  2. make sure that running the build script one ends up with all the needed headers and libraries installed into the proper place
  3. optionally (depending on how much this is complex and needed), in the previous step teach the build script to produce a .framework which is probably even easier to integrate

Adapt net/connection.h's regress test to use Catch

The regress test of net/connection.h is the last libight test that does not use Catch (and is the last libight test to require interaction with the test program as well). This test (implemented by test/echo_client_evbuf.cpp) should be refactored (or probably rewritten) to use Catch.

Refactor Catch tests to have one executable per module

We currently compile a single Catch-based executable per module, but this is surprising for one running tests from automake because it appears that only a single test is run.

It is much less surprising if we have a single executable per tested module, so that make check output indicates that N tests passed.

Improvements for Tor controller

@0xPoly reviewed #179 and provided useful comments:

  • What will happen if tor is started on a port that is too low? (<1024)
  • What happens if internet connection is lost for a long period of time? Will you be able to restart tor when internet is back or will measurement kit have to be restarted?
  • possible enhancement: ability to ask tor for a fresh circuit/new identity

Simplified handling of compile time dependencies

As discussed in #84, compile time dependencies should be packages into one (or possibly more) digitally signed tarballs, created from cloned git repositories and downloadable from the OONI website. This should be done to avoid a developer the burden of cloning large repositories such as boost repos and libevent that in fact are not needed.

This means that, in particular, we should move (large?) compile time dependencies away from src/ext and put them into such tarball. In turn the build system should be simplified to remove the code that falls back to builtin dependencies if these are not found in the system.

After this change: building on Debian

After this change, to build libight for a system like Debian one has to follow these steps:

  1. install the compile-time dependencies (libevent, libssl, http-parser, boost, yaml-cpp)
  2. run the usual ./configure && make && make install

After this change: building for iOS

Conversely, to build libight say for iOS one has to:

  1. clone a build repository containing a set of build scripts and perhaps libight as a subrepository
  2. invoke a build script that downloads the dependencies tarballs, verifies the digital signatures, compiles the dependencies and installs them at a specific directory
  3. run a script that runs libight ./configure $options && make && make install where $options tell ./configure where to locate the dependencies

Observations

  1. Tor is not a library so we need in any case to put it below src/ext
  2. libutp (which will be needed to implement a Neubot test) is typically statically linked therefore we should put it below src/ext as well
  3. maybe we can keep http-parser in src/ext as well, since it is small

It should be possible to reenter ight_loop() after an exception terminated it

As seen in #64 (what a nice number!) when ight_loop() is exited due to an exception it is not possible to reenter it, because we use libevent and libevent is not exception-aware. Therefore the second invocation of the event loop is seen by libevent as a tentative to re-enter the loop, which instead was exited due to the exception.

So, we should modify ight_loop() such that it is possible to call ight_loop() again after the first loop is terminated by an exception.

This is mainly useful with regress tests, in which tests may be terminated by exceptions. Otherwise, as seen in #64, after one test is terminated by an exception, subsequent tests fail because it is not possible to reenter the event loop.

Add support for geoip

We should add support for geoip, so that we can hide the IP address of clients in certain cases.

Integrate Tor

[Probably not our top priority at the moment, however, adding this issue so I am sure that I do not lose my notes and to state in advance what I think we should do.]

We need to integrate Tor with libight to submit ooni reports (and to enable the possibility of using Tor during ooni and other tests). We can use as blueprint projects such as Orbot, OnionBrowser and CPAProxy.

The first step to do this is to compile Tor. All the projects mentioned above provide scripts that set the proper environment variables and then use the GNU build system (autoreconf, ./configure and make). We should do the same (also because we already integrate libevent which is also a dependency of Tor). The way to go is the following: put Tor and its dependencies in src/ext and massage the build system so that we end up with all the libraries (libight.a, libevent.a, libor.a and so on) and the headers we need into a specific directory. The integrator, then, only needs to copy this directory to the proper place to pull libight, its headers and its dependencies.

The second step concerns how to run Tor. On iOS systems OnionBrowser and CPAProxy execute the Tor process in a separate thread. On Android, Orbot includes the Tor executable as a resource and executes it using the shell (see also #85). To start off, we should probably follow the same approach on all platforms, for simplicity, that is we should run Tor in a separate thread. In C++11 this task should be easy enough using std::async.

A related task is to decide how to configure the instance of Tor that we run. IIRC all the projects mentioned above (and for sure CPAProxy) instruct Tor to open a control connection and use such control connection. It is not completely clear yet to me the purpose of this, and we should investigate whether we need to do that in our use case (for example, do we need a control connection to cleanly tear down the Tor thread?).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.