[tor-commits] [ooni-probe/master] Still need to fix problem with getHostByName() only returned one address

isis at torproject.org isis at torproject.org
Thu Sep 13 13:04:15 UTC 2012


commit 891d16a70e59ea3a4324822a0b602743613530b1
Author: Isis Lovecruft <isis at patternsinthevoid.net>
Date:   Fri Jun 15 17:55:32 2012 -0700

    Still need to fix problem with getHostByName() only returned one address
---
 ooni/plugins/dnstamper.py |  195 +++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 195 insertions(+), 0 deletions(-)

diff --git a/ooni/plugins/dnstamper.py b/ooni/plugins/dnstamper.py
new file mode 100644
index 0000000..34aaa01
--- /dev/null
+++ b/ooni/plugins/dnstamper.py
@@ -0,0 +1,195 @@
+# -*- 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
+
+    TODO: 
+    * Switch to using Twisted's DNS builtins instead of dnspython
+    * 
+"""
+
+import os
+
+from twisted.names import client
+from twisted.internet import reactor
+from twisted.internet.protocol import Factory, Protocol
+from twisted.python import usage
+from twisted.plugin import IPlugin
+from zope.interface import implements
+
+from ooni.plugoo.assets import Asset
+from ooni.plugoo.tests import ITest, OONITest
+from ooni import log
+
+class Top1MAsset(Asset):
+    """
+    Class for parsing the Alexa top-1m.txt as 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.split(',')[1].replace('\n','')
+
+class DNSTamperArgs(usage.Options):
+    optParameters = [['asset', 'a', None, 'Asset file of hostnames to resolve'],
+                     ['controlserver', 'c', '8.8.8.8', 'Known good DNS server'],
+                     ['testservers', 't', None, 'Asset file of DNS servers to test'],
+                     ['usereverse', 'r', False, 'Also try reverse DNS resolves'],
+                     ['resume', 's', 0, 'Resume at this index in the asset file']]
+
+class DNSTamperTest(OONITest):
+    implements(IPlugin, ITest)
+
+    shortName = "dnstamper"
+    description = "DNS censorship detection test"
+    requirements = None
+    options = DNSTamperArgs
+    blocking = False
+    
+    def load_assets(self):
+        assets = {}
+        if self.local_options:
+            if self.local_options['asset']:
+                assetf = self.local_options['asset']
+                if assetf == 'top-1m.txt':
+                    assets.update({'asset': Top1MAsset(assetf)})
+                else:
+                    assets.update({'asset': Asset(assetf)})
+            elif self.local_options['testservers']:
+                assets.update({'testservers': 
+                               Asset(self.local_options['testservers'])})
+        return assets
+
+    def lookup(self, hostname, nameserver):
+        """
+        Resolves a hostname through a DNS nameserver to the corresponding
+        IP addresses.
+        """
+        def got_result(result):
+            log.msg('Resolved %s through %s to %s' 
+                    % (hostname, nameserver, result))
+            reactor.stop()
+            return {'resolved': True,
+                    'domain': hostname,
+                    'nameserver': nameserver,
+                    'address': result}
+
+        def got_error(err):
+            log.msg(err.printTraceback())
+            reactor.stop()
+            return {'resolved': False,
+                    'domain': hostname,
+                    'nameserver': nameserver,
+                    'address': err}
+
+        res = client.createResolver(servers=[(nameserver, 53)])
+        d = res.getHostByName(hostname)
+        d.addCallbacks(got_result, got_error)
+        return d
+
+        ## XXX MAY ALSO BE:
+        #answer = res.getAddress(servers=[('nameserver', 53)])
+
+    def reverse_lookup(self, address, nameserver):
+        """
+        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 = client.createResolver(servers=[(nameserver, 53)])
+        ptr = '.'.join(addr.split('.')[::-1]) + '.in-addr.arpa'
+        d = res.lookupPointer(ptr)
+        d.addCallback(lambda (ans, auth, add): util.println(ans[0].payload.name))
+        d.addErrback(log.err)
+        d.addBoth(lambda r: reactor.stop())
+        return d
+        
+    def experiment(self, args):
+        """
+        Compares the lookup() sets of the control and experiment groups.
+        """
+        test_server = self.local_options['testservers']
+        hostname = args['asset']
+        exp_address = self.lookup(hostname, test_server)
+
+        #return {'control': control_server,
+        #        'domain': args['asset'],
+        #        'experiment_address': address}
+
+        if self.local_options['usereverse']:
+            exp_reversed = self.reverse_lookup(exp_address, test_server)
+            return exp_address, hostname, test_server, exp_reversed
+        else:
+            return exp_address, hostname, test_server, False
+
+    def control(self, experiment_result):
+        (exp_address, hostname, test_server, exp_reversed) = experiment_result
+        control_server = self.local_options['controlserver']
+        ctrl_address = self.lookup(hostname, control_server)
+        
+        ## XXX getHostByName() appears to be returning only one IP...
+
+        if len(set(exp_address) & set(ctrl_address)) > 0:
+            log.msg("Address %s has not tampered with on DNS server %s" 
+                    % (hostname, test_server))
+            return {'hostname': hostname,
+                    'test-nameserver': test_server,
+                    'test-address': exp_address,
+                    'control-nameserver': control_server,
+                    'control-address': ctrl_address,
+                    'tampering-detected': False}
+        else:
+            log.msg("Address %s has possibly been tampered on %s:"
+                    % (hostname, test_server))
+            log.msg("DNS resolution through testserver %s yeilds: %s"
+                    % (test_server, exp_address))
+            log.msg("However, DNS resolution through controlserver %s yeilds: %s"
+                    % (control_server, ctrl_address))
+
+            if self.local_options['usereverse']:
+                ctrl_reversed = self.reverse_lookup(experiment_result, control_server)
+                if len(set(ctrl_reversed) & set(exp_reversed)) > 0:
+                    log.msg("Further testing has eliminated false positives")
+                else:
+                    log.msg("Reverse DNS on the results returned by %s returned:"
+                            % (test_server))
+                    log.msg("%s" % exp_reversed)
+                    log.msg("which does not match the expected domainname: %s"
+                            % ctrl_reversed)
+                return {'hostname': hostname,
+                        'test-nameserver': test_server,
+                        'test-address': exp_address,
+                        'test-reversed': exp_reversed,
+                        'control-nameserver': control_server,
+                        'control-address': ctrl_address,
+                        'control-reversed': ctrl_reversed,
+                        'tampering-detected': True}
+            else:
+                return {'hostname': hostname,
+                        'test-nameserver': test_server,
+                        'test-address': exp_address,
+                        'control-nameserver': control_server,
+                        'control-address': ctrl_address,
+                        'tampering-detected': False}
+
+dnstamper = DNSTamperTest(None, None, None)





More information about the tor-commits mailing list