[tor-commits] [ooni-probe/master] Rewrote captive portal tests and added requisite options to the .conf file.

art at torproject.org art at torproject.org
Mon Jul 9 14:39:04 UTC 2012


commit 3002dd3a1afbe92d02734fb5120aa80b6c8c38eb
Author: Isis Lovecruft <isis at patternsinthevoid.net>
Date:   Sun Apr 8 02:10:38 2012 -0700

    Rewrote captive portal tests and added requisite options to the .conf file.
---
 ooni-probe.conf        |   10 +++
 tests/captiveportal.py |  191 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 201 insertions(+), 0 deletions(-)

diff --git a/ooni-probe.conf b/ooni-probe.conf
index 161a008..a744fcf 100644
--- a/ooni-probe.conf
+++ b/ooni-probe.conf
@@ -38,6 +38,16 @@ dns_control_server = 91.191.136.152
 # on positive results.
 dns_reverse_lookup = true
 
+### captiveportal testing configuration parameters
+
+# The default User Agent that ooni-probe should send for
+# HTTP requests (pretend we're a Windows box running FF10): 
+default_ua = Mozilla/5.0 (Windows NT 6.1; WOW64; rv:10.0.2) Gecko/20100101 Firefox/10.0.2
+
+# This is the list of captive portal tests, in the format:
+# test_name, experiment_url, control_result, control_code
+captive_portal = captive_portal_tests.txt  
+
 ### traceroute testing related config parameters
 
 # This is the list of ips to traceroute to
diff --git a/tests/captiveportal.py b/tests/captiveportal.py
new file mode 100644
index 0000000..324d3cc
--- /dev/null
+++ b/tests/captiveportal.py
@@ -0,0 +1,191 @@
+# -*- coding: utf-8 -*-
+"""
+    captiveportal
+    *************
+
+    This test is a collection of tests to detect the presence of a
+    captive portal. Code is taken, in part from the old ooni-probe,
+    which was written by Jacob Appelbaum and Arturo Filastò.
+
+    :copyright: (c) 2012 Isis Lovecruft
+    :license: see LICENSE for more details
+"""
+import os
+import re
+import urllib2
+from urlparse import urlparse
+
+from plugoo.assets import Asset
+from plugoo.tests import Test
+
+__plugoo__ = "captiveportal"
+__desc__ = "Captive portal detection test"
+
+class CaptivePortalAsset(Asset):
+    """
+    Parses captive_portal_test.txt into an Asset.
+    """
+    def __init__(self, file=None):
+        self = Asset.__init__(self, file)
+
+    def parse_line(self, line):
+        self = Asset.parse_line(self, line)
+        return line.replace('\n', '').split(', ')
+
+    '''
+    def next_asset(self):
+        self = Asset.next_asset(self)
+        with self.fh as fh:
+            asset_list = []
+            lines = fh.readlines()
+            for line in lines:
+                parsed_line = self.parse_line(line)
+                if parsed_line:
+                    asset_list.append(parsed_line)
+                else:
+                    fh.seek(0)
+                    raise StopIteration
+            return asset_list
+    '''
+
+class CaptivePortal(Test):
+    """
+    Compares content and status codes of HTTP responses, and attempts
+    to determine if content has been altered.
+
+    TODO: compare headers
+    """
+    def __init__(self, ooni):
+        Test.__init__(self, ooni, name='test')
+        self.default_ua = ooni.config.tests.default_ua
+
+    def http_fetch(self, url, headers=None):
+        """
+        Parses an HTTP url, fetches it, and returns a urllib2 response
+        object.
+        """
+        url = urlparse(url).geturl()
+        request = urllib2.Request(url, None, headers)
+        response = urllib2.urlopen(request)
+        return response
+ 
+    def http_content_match_fuzzy_opt(self, experimental_url, control_result,
+                                     fuzzy=False):
+        """
+        Makes an HTTP request on port 80 for experimental_url, then
+        compares the response_content of experimental_url with the
+        control_result. Optionally, if the fuzzy parameter is set to
+        True, the response_content is compared with a regex of the
+        control_result. If the response_content from the
+        experimental_url and the control_result match, returns True
+        with the HTTP status code, False if otherwise.
+        """
+        log = self.logger
+        default_ua = self.default_ua
+
+        response = self.http_fetch(experimental_url, 
+                                   headers={'User-Agent': default_ua})
+        response_content = response.read()
+        response_code = response.code
+        if response_content is not None:
+            if fuzzy:
+                pattern = re.compile(control_result)
+                match = pattern.search(response_content)
+                if not match:
+                    log.info("Fuzzy HTTP content comparison of experiment" \
+                                    " URL '%s' and the expected control result" \
+                                    " do not match." % experimental_url)
+                    return False, response_code
+                else:
+                    log.info("Fuzzy HTTP content comparison of experiment" \
+                                 " URL '%s' and the expected control result" \
+                                 " yielded a match." % experimental_url)
+                    return True, response_code
+            else:
+                if str(response_content) != str(control_result):
+                    log.info("HTTP content comparison of experiment URL" \
+                                 " '%s' and the expected control result" \
+                                 " do not match." % experimental_url)
+                    return False, response_code
+                else:
+                    return True, response_code
+        else:
+            log.warn("HTTP connection appears to have failed.")
+        return False, False
+    
+    def http_status_code_match(self, experiment_code, control_code):
+        """
+        Compare two HTTP status codes, returns True if they match.
+        """
+        if int(experiment_code) != int(control_code):
+            return False
+        return True
+
+    def http_status_code_no_match(self, experiment_code, control_code):
+        """
+        Compare two HTTP status codes, returns True if they do not match.
+        """
+        if self.http_status_code_match(experiment_code, control_code):
+            return False
+        return True
+
+    def experiment(self, *a, **kw):
+        """
+        Compares the content and status code of the HTTP response for
+        experiment_url with the control_result and control_code 
+        respectively. If the status codes match, but the experimental 
+        content and control_result do not match, fuzzy matching is enabled
+        to determine if the control_result is at least included somewhere
+        in the experimental content. Returns True if matches are found,
+        and False if otherwise.
+        """
+        test_name = kw['data'][0] 
+        experiment_url = kw['data'][1]
+        control_result = kw['data'][2]
+        control_code = kw['data'][3]
+
+        cm = self.http_content_match_fuzzy_opt
+        sm = self.http_status_code_match
+
+        log = self.logger
+        log.info("Running the %s test..." % test_name)
+        
+        content_match, experiment_code = cm(experiment_url, control_result)
+        status_match = sm(experiment_code, control_code)
+
+        if status_match and content_match:
+            log.info("The %s test was unable to detect a captive portal."
+                     % test_name)
+            return True
+        elif status_match and not content_match:
+            log.info("The %s test detected mismatched content, retrying with " \
+                         "fuzzy match enabled." % test_name)
+            content_fuzzy_match, experiment_code = cm(experiment_url, 
+                                                      control_result,
+                                                      fuzzy=True)
+            if content_fuzzy_match:
+                return True
+            else:
+                return False
+        else:
+            log.info("The %s test shows that your network is filtered, possibly " \
+                         "due to a captive portal." % test_name)
+            return False
+
+        return False
+
+def run(ooni):
+    """
+    Run the CaptivePortal(Test).
+    """
+    config = ooni.config
+    log = ooni.logger
+    assets = [CaptivePortalAsset(os.path.join(config.main.assetdir, 
+                                              config.tests.captive_portal))]
+
+    captiveportal = CaptivePortal(ooni)
+    log.info("Starting captive portal test...")
+    captiveportal.run(assets, {'index': 1})
+    log.info("Captive portal test finished!")
+
+





More information about the tor-commits mailing list