[tor-commits] [ooni-probe/master] Completed attempt to use reverse DNS to account for geoIP load balancing, added option in ooni-conf to disable this feature.

art at torproject.org art at torproject.org
Wed Apr 4 18:05:50 UTC 2012


commit 1f5c521ebe1bededb16d70082ae4f4617df0785e
Author: Isis Lovecruft <isis at patternsinthevoid.net>
Date:   Sat Mar 24 01:07:41 2012 -0700

    Completed attempt to use reverse DNS to account for geoIP load balancing, added option in ooni-conf to disable this feature.
---
 HACKING            |    2 +-
 ooni-probe.conf    |    7 +++-
 plugoo/reports.py  |    2 +-
 tests/dnstamper.py |  110 ++++++++++++++++++++++++++++++++++++++++++++--------
 4 files changed, 102 insertions(+), 19 deletions(-)

diff --git a/HACKING b/HACKING
index 57b6c9b..4451882 100644
--- a/HACKING
+++ b/HACKING
@@ -170,7 +170,7 @@ Method definitions inside of class are separated by a single blank line.
 Encoding
 ........
 
-Always use UTF-8 encoding. This can be specified by add the encoding cookie
+Always use UTF-8 encoding. This can be specified by adding the encoding cookie
 to the beginning of your python files:
 
     # -*- coding: UTF-8 -*-
diff --git a/ooni-probe.conf b/ooni-probe.conf
index 8702353..161a008 100644
--- a/ooni-probe.conf
+++ b/ooni-probe.conf
@@ -31,7 +31,12 @@ dns_experiment = top-1m.txt
 dns_experiment_dns = dns_servers.txt
 
 # This is the control known good DNS server
-dns_control_server = 8.8.8.8
+dns_control_server = 91.191.136.152
+
+# Specify whether the dnstamper test should attempt to remove 
+# GeoIP-based false positives by doing a reverse DNS resolve
+# on positive results.
+dns_reverse_lookup = true
 
 ### traceroute testing related config parameters
 
diff --git a/plugoo/reports.py b/plugoo/reports.py
index 90de498..009e86b 100644
--- a/plugoo/reports.py
+++ b/plugoo/reports.py
@@ -42,7 +42,7 @@ class Report:
             import paramiko
         except:
             self.scp = None
-            self.logger.warn("Could not import paramiko. SCP will not be disabled")
+            self.logger.warn("Could not import paramiko. SCP will be disabled")
 
     def __call__(self, data):
         """
diff --git a/tests/dnstamper.py b/tests/dnstamper.py
index 4064831..4fc3c71 100644
--- a/tests/dnstamper.py
+++ b/tests/dnstamper.py
@@ -1,9 +1,38 @@
+# -*- coding: utf-8 -*-
+"""
+    dnstamper
+    *********
+
+    This test resolves DNS for a list of domain names, one per line, in the 
+    file specified in the ooni-config under the setting "dns_experiment". If 
+    the file is top-1m.txt, the test will be run using Amazon's list of top 
+    one million domains. The experimental dns servers to query should
+    be specified one per line in assets/dns_servers.txt.
+
+    The test reports censorship if the cardinality of the intersection of
+    the query result set from the control server and the query result set
+    from the experimental server is zero, which is to say, if the two sets 
+    have no matching results whatsoever.
+
+    NOTE: This test frequently results in false positives due to GeoIP-based
+    load balancing on major global sites such as google, facebook, and
+    youtube, etc.
+
+    :copyright: (c) 2012 Arturo Filastò, Isis Lovecruft
+    :license: see LICENSE for more details
+"""
+
+try:
+    from dns import resolver, reversename
+except:
+    print "Error: dnspython is not installed! (http://www.dnspython.org/)"
 try:
-    from dns import resolver
+    import gevent
 except:
-    print "Error dnspython is not installed! (http://www.dnspython.org/)"
-import gevent
+    print "Error: gevent is not installed! (http://www.gevent.org/)"
+
 import os
+
 import plugoo
 from plugoo.assets import Asset
 from plugoo.tests import Test
@@ -12,6 +41,9 @@ __plugoo__ = "DNST"
 __desc__ = "DNS censorship detection test"
 
 class Top1MAsset(Asset):
+    """
+    Class for parsing top-1m.txt as an asset.
+    """
     def __init__(self, file=None):
         self = Asset.__init__(self, file)
     
@@ -20,11 +52,18 @@ class Top1MAsset(Asset):
         return line.split(',')[1].replace('\n','')
 
 class DNSTAsset(Asset):
+    """
+    Creates DNS testing specific Assets.
+    """
     def __init__(self, file=None):
         self = Asset.__init__(self, file)
 
 class DNST(Test):
     def lookup(self, hostname, ns):
+        """
+        Resolves a hostname through a DNS nameserver, ns, to the corresponding 
+        IP address(es).
+        """
         res = resolver.Resolver(configure=False)
         res.nameservers = [ns]
         answer = res.query(hostname)
@@ -36,42 +75,81 @@ class DNST(Test):
 
         return ret
 
+    def reverse_lookup(self, ip, ns):
+        """
+        Attempt to do a reverse DNS lookup to determine if the control and exp
+        sets from a positive result resolve to the same domain, in order to
+        remove false positives due to GeoIP load balancing.
+        """
+        res = resolver.Resolver(configure=False)
+        res.nameservers = [ns]
+        n = reversename.from_address(ip)
+        revn = res.query(n, "PTR").__iter__().next().to_text()[:-1]
+
+        return revn
+
     def experiment(self, *a, **kw):
+        """
+        Compares the lookup() sets of the control and experiment groups.
+        """
         # this is just a dirty hack
         address = kw['data'][0]
         ns = kw['data'][1]
 
         config = self.config
+        ctrl_ns = config.tests.dns_control_server
 
         print "ADDRESS: %s" % address
         print "NAMESERVER: %s" % ns
 
         exp = self.lookup(address, ns)
-        control = self.lookup(address, config.tests.dns_control_server)
+        control = self.lookup(address, ctrl_ns)
+        
+        result = []
 
         if len(set(exp) & set(control)) > 0:
             print "Address %s has not tampered with on DNS server %s\n" % (address, ns)
-            return (address, ns, False)
+            result = (address, ns, exp, control, False)
+            return result
         else:
-            print "Address %s has possibly been tampered on %s:\nDNS resolution through %s yeilds:\n%s\nAlthough the control group DNS servers resolve to:\n%s\n" % (address, ns, ns, exp, control)
-            return (address, ns, exp, control, True)
+            print "Address %s has possibly been tampered on %s:\nDNS resolution through %s yeilds:\n%s\nAlthough the control group DNS servers resolve to:\n%s" % (address, ns, ns, exp, control)
+            result = (address, ns, exp, control, True)
+
+            if config.tests.dns_reverse_lookup:
+                
+                exprevn = [self.reverse_lookup(ip, ns) for ip in exp]
+                ctrlrevn = [self.reverse_lookup(ip, ctrl_ns) 
+                            for ip in control]
+                
+                if len(set(exprevn) & set(ctrlrevn)) > 0:
+                    print "Further testing has eliminated this as a false positive."
+                else:
+                    print "Reverse DNS on the results returned by %s returned:\n%s\nWhich does not match the expected domainname:\n%s\n" % (ns, exprevn, ctrlrevn)
+                return result
+
+            else:
+                print "\n"
+                return result
 
 def run(ooni):
-    """Run the test
+    """
+    Run the test.
     """
     config = ooni.config
     urls = []
 
-    dns_experiment = Top1MAsset(os.path.join(config.main.assetdir, \
-                                            config.tests.dns_experiment))
-    dns_experiment_dns = DNSTAsset(os.path.join(config.main.assetdir, \
+    if (config.tests.dns_experiment == "top-1m.txt"):
+        dns_experiment = Top1MAsset(os.path.join(config.main.assetdir,
+                                                 config.tests.dns_experiment))
+    else:
+        dns_experiment = DNSTAsset(os.path.join(config.main.assetdir,
+                                                config.tests.dns_experiment))
+    dns_experiment_dns = DNSTAsset(os.path.join(config.main.assetdir,
                                                 config.tests.dns_experiment_dns))
 
     assets = [dns_experiment, dns_experiment_dns]
 
     dnstest = DNST(ooni)
-    ooni.logger.info("starting test")
-    dnstest.run(assets)
-    ooni.logger.info("finished")
-
-
+    ooni.logger.info("Beginning dnstamper test...")
+    dnstest.run(assets, {'index': 1})
+    ooni.logger.info("Dnstamper test completed!")





More information about the tor-commits mailing list