[tor-commits] [ooni-probe/master] Create DNS Test template

art at torproject.org art at torproject.org
Tue Nov 20 18:59:13 UTC 2012


commit 2c17bf159b448de42603d9904d95e3df154fd952
Author: Arturo Filastò <art at fuffa.org>
Date:   Tue Nov 20 19:37:25 2012 +0100

    Create DNS Test template
    * Use such template for DNS Tamper test
---
 nettests/core/dnstamper.py |  217 +++++++-------------------------------------
 ooni/templates/dnst.py     |  108 ++++++++++++++++++++++
 2 files changed, 142 insertions(+), 183 deletions(-)

diff --git a/nettests/core/dnstamper.py b/nettests/core/dnstamper.py
index bc57f62..be3281a 100644
--- a/nettests/core/dnstamper.py
+++ b/nettests/core/dnstamper.py
@@ -18,11 +18,9 @@
 import pdb
 
 from twisted.python import usage
-
 from twisted.internet import defer
-from twisted.names import client, dns
-from twisted.names.client import Resolver
-from twisted.names.error import DNSQueryRefusedError
+
+from ooni.templates import dnst
 
 from ooni import nettest
 from ooni.utils import log
@@ -36,12 +34,12 @@ class UsageOptions(usage.Options):
                         'file containing list of DNS resolvers to test against']
                     ]
 
-class DNSTamperTest(nettest.NetTestCase):
+class DNSTamperTest(dnst.DNSTest):
 
     name = "DNS tamper"
     description = "DNS censorship detection test"
-    version = "0.2"
-    lookupTimeout = [1]
+    version = "0.3"
+    authors = "Arturo Filastò, Isis Lovecruft"
     requirements = None
 
     inputFile = ['file', 'f', None,
@@ -51,16 +49,6 @@ class DNSTamperTest(nettest.NetTestCase):
     requiredOptions = ['backend', 'backendport', 'file', 'testresolvers']
 
     def setUp(self):
-        self.report['test_lookups'] = {}
-        self.report['test_reverse'] = {}
-        self.report['control_lookup'] = []
-        self.report['a_lookups'] = {}
-        self.report['tampering'] = {}
-
-        self.test_a_lookups = {}
-        self.control_a_lookups = []
-        self.control_reverse = None
-        self.test_reverse = {}
 
         if not self.localOptions['testresolvers']:
             self.test_resolvers = ['8.8.8.8']
@@ -74,74 +62,14 @@ class DNSTamperTest(nettest.NetTestCase):
 
         self.test_resolvers = [x.strip() for x in fp.readlines()]
         fp.close()
+        self.control_dns_server = (self.localOptions['backend'],
+            int(self.localOptions['backendport']))
 
-    def process_a_answers(self, message, resolver_address):
-        log.msg("Processing A answers for %s" % resolver_address)
-        log.debug("These are the answers I got %s" % message.answers)
-
-        all_a = []
-        a_a = []
-
-        for answer in message.answers:
-            if answer.type is 1:
-                # A type query
-                r = answer.payload.dottedQuad()
-                self.report['a_lookups'][resolver_address] = r
-                a_a.append(r)
-            lookup = str(answer.payload)
-            all_a.append(lookup)
-
-        if resolver_address == 'control':
-            self.report['control_server'] = self.localOptions['backend']
-            self.report['control_lookup'] = all_a
-            self.control_a_lookups = a_a
-        else:
-            self.test_a_lookups[resolver_address] = a_a
-            self.report['test_lookups'][resolver_address] = all_a
-
-        log.msg("Done")
-
-    def process_ptr_answers(self, answers, resolver):
-        log.msg("Processing PTR answers for %s" % resolver)
-        name = None
-
-        for answer in answers[0]:
-            if answer.type is 12:
-                # PTR type
-                name = str(answer.payload.name)
+        self.report['test_resolvers'] = self.test_resolvers
+        self.report['control_resolver'] = self.control_dns_server
 
-        if resolver == 'control':
-            self.control_reverse = name
-            self.report['control_reverse'] = name
-        else:
-            self.test_reverse[resolver] = name
-            self.report['test_reverse'][resolver] = name
-
-    def ptr_lookup_error(self, failure, resolver):
-        log.msg("There was an error in PTR lookup %s" % resolver)
-        log.err(failure)
-
-        if resolver == 'control':
-            self.report['control_reverse'] = None
-        else:
-            self.report['test_reverse'][resolver] = None
-
-    def a_lookup_error(self, failure, resolver):
-        log.msg("There was an error in A lookup %s" % resolver)
-        log.err(failure)
-
-        if failure.type is DNSQueryRefusedError:
-            self.report['tampering'][resolver] = 'connection-refused'
-        elif failure.type is defer.TimeoutError:
-            self.report['tampering'][resolver] = 'timeout'
-
-        if resolver == 'control':
-            self.report['control_lookup'] = None
-        else:
-            self.report['test_lookups'][resolver] = None
-            self.test_a_lookups[resolver] = None
-
-    def test_lookup(self):
+    @defer.inlineCallbacks
+    def test_a_queries(self):
         """
         We perform an A lookup on the DNS test servers for the domains to be
         tested and an A lookup on the known good DNS server.
@@ -163,115 +91,38 @@ class DNSTamperTest(nettest.NetTestCase):
         log.msg("Doing the test lookups on %s" % self.input)
         list_of_ds = []
         hostname = self.input
-        dns_query = [dns.Query(hostname, dns.IN, dns.A)]
-        dns_server = [(self.localOptions['backend'],
-                       self.localOptions['backendport'])]
 
-        resolver = Resolver(servers=dns_server)
+        self.report['tampering'] = {}
 
-        control_d = resolver.queryUDP(dns_query, timeout=self.lookupTimeout)
-        control_d.addCallback(self.process_a_answers, 'control')
-        control_d.addErrback(self.a_lookup_error, 'control')
+        control_answers = yield self.performALookup(hostname, self.control_dns_server)
 
         for test_resolver in self.test_resolvers:
             log.msg("Going for %s" % test_resolver)
-            dns_server = [(test_resolver, 53)]
-
-            resolver = Resolver(servers=dns_server)
-
-            d = resolver.queryUDP(dns_query, timeout=self.lookupTimeout)
-            d.addCallback(self.process_a_answers, test_resolver)
-            d.addErrback(self.a_lookup_error, test_resolver)
-
-            # This is required to cancel the delayed calls of the
-            # twisted.names.client resolver
-            list_of_ds.append(d)
+            test_dns_server = (test_resolver, 53)
 
-        list_of_ds.append(control_d)
-        dl = defer.DeferredList(list_of_ds)
-        dl.addCallback(self.do_reverse_lookups)
-        dl.addBoth(self.compare_results)
-        return dl
+            experiment_answers = yield self.performALookup(hostname, test_dns_server)
+            log.debug("Got these answers %s" % experiment_answers)
 
-    def reverse_lookup(self, address, resolver):
-        query = [dns.Query(hostname, dns.IN, dns.PTR)]
-        ptr = '.'.join(address.split('.')[::-1]) + '.in-addr.arpa'
-        r = resolver.queryUDP(query, timeout=self.lookupTimeout)
-        return r
-
-    def do_reverse_lookups(self, result):
-        """
-        Take a resolved address in the form "176.139.79.178.in-addr.arpa." and
-        attempt to reverse the domain with both the control and test DNS
-        servers to see if they match.
-
-        :param result:
-            A resolved domain name.
-        """
-        log.msg("Doing the reverse lookups %s" % self.input)
-        list_of_ds = []
-        dns_server = [(self.localOptions['backend'],
-                       self.localOptions['backendport'])]
-
-        resolver = Resolver(servers=dns_server)
-
-        test_reverse = self.reverse_lookup(self.control_a_lookups[0], resolver,
-                timeout=self.lookupTimeout)
-
-        test_reverse.addCallback(self.process_ptr_answers, 'control')
-        test_reverse.addErrback(self.ptr_lookup_error, 'control')
-
-        list_of_ds.append(test_reverse)
-
-        for test_resolver in self.test_resolvers:
-            try:
-                ip = self.test_a_lookups[test_resolver][0]
-            except:
-                break
-
-            d = self.reverse_lookup(ip, res)
-            d.addCallback(self.process_ptr_answers, test_resolver)
-            d.addErrback(self.ptr_lookup_error, test_resolver)
-            list_of_ds.append(d)
-
-        dl = defer.DeferredList(list_of_ds)
-        return dl
-
-    def compare_results(self, *arg, **kw):
-        """
-        Take the set intersection of two test result sets. If the intersection
-        is greater than zero (there are matching addresses in both sets) then
-        the no censorship is reported. Else, if no IP addresses match other
-        addresses, then we mark it as a censorship event.
-        """
-        log.msg("Comparing results for %s" % self.input)
-        log.msg(self.test_a_lookups)
-
-        for test, test_a_lookups in self.test_a_lookups.items():
-            log.msg("Now doing %s | %s" % (test, test_a_lookups))
-            if not test_a_lookups:
-                self.report['tampering'][test] = 'unknown'
+            if not experiment_answers:
+                log.err("Got no response, perhaps the DNS resolver is down?")
+                self.report['tampering'][test_resolver] = 'no_answer'
                 continue
 
-            if set(test_a_lookups) & set(self.control_a_lookups):
+            log.debug("Comparing %s with %s" % (experiment_answers, control_answers))
+            if set(experiment_answers) & set(control_answers):
                 log.msg("Address has not tampered with on DNS server")
-                self.report['tampering'][test] = False
-
-            elif self.control_reverse and set([self.control_reverse]) \
-                    & set([self.report['test_reverse'][test]]):
-                log.msg("Further testing has eliminated false positives")
-                self.report['tampering'][test] = 'reverse-match'
-
+                self.report['tampering'][test_resolver] = False
             else:
-                log.msg("Reverse DNS on the results returned by returned")
-                log.msg("which does not match the expected domainname")
-                self.report['tampering'][test] = True
-
-        if len(self.test_a_lookups) == len(self.test_resolvers):
-            return
-        else:
-            missing_tests = len(self.test_a_lookups)
-            missing_resolvers = len(self.test_resolvers)
-            log.msg("Still missing %s resolvers and %s tests" %
-                    (missing_tests, missing_resolvers))
+                log.msg("Trying to do reverse lookup")
+
+                experiment_reverse = yield self.performPTRLookup(experiment_answers[0], test_dns_server)
+                control_reverse = yield self.performPTRLookup(control_answers[0], self.control_dns_server)
+
+                if experiment_reverse == control_reverse:
+                    log.msg("Further testing has eliminated false positives")
+                    self.report['tampering'][test_resolver] = 'reverse_match'
+                else:
+                    log.msg("Reverse DNS on the results returned by returned")
+                    log.msg("which does not match the expected domainname")
+                    self.report['tampering'][test_resolver] = True
 
diff --git a/ooni/templates/dnst.py b/ooni/templates/dnst.py
new file mode 100644
index 0000000..7a6eb37
--- /dev/null
+++ b/ooni/templates/dnst.py
@@ -0,0 +1,108 @@
+# -*- encoding: utf-8 -*-
+#
+# :authors: Arturo Filastò
+# :licence: see LICENSE
+
+from twisted.internet import defer
+from twisted.names import client, dns
+from twisted.names.client import Resolver
+
+from twisted.names.error import DNSQueryRefusedError
+
+from ooni.utils import log
+from ooni.nettest import NetTestCase
+
+class DNSTest(NetTestCase):
+    name = "Base DNS Test"
+    version = 0.1
+
+    requiresRoot = False
+    queryTimeout = [1]
+
+    def _setUp(self):
+        self.report['queries'] = []
+
+    def performPTRLookup(self, address, dns_server):
+        """
+        Does a reverse DNS lookup on the input ip address
+
+        :address: the IP Address as a dotted quad to do a reverse lookup on.
+
+        :dns_server: is the dns_server that should be used for the lookup as a
+                     tuple of ip port (ex. ("127.0.0.1", 53))
+        """
+        ptr = '.'.join(address.split('.')[::-1]) + '.in-addr.arpa'
+        query = [dns.Query(ptr, dns.IN, dns.PTR)]
+        def gotResponse(message):
+            answers = []
+            name = None
+            for answer in message.answers:
+                if answer.type is 12:
+                    name = answer.payload.name
+
+            result = {}
+            result['query_type'] = 'PTR'
+            result['query'] = repr(query)
+            result['answers'] = answers
+            result['name'] = name
+            self.report['queries'].append(result)
+            return name
+
+        def gotError(failure):
+            log.exception(failure)
+            result = {}
+            result['query_type'] = 'PTR'
+            result['query'] = repr(query)
+            result['error'] = str(failure)
+            return None
+
+        resolver = Resolver(servers=[dns_server])
+        d = resolver.queryUDP(query, timeout=self.queryTimeout)
+        d.addCallback(gotResponse)
+        d.addErrback(gotError)
+        return d
+
+    def performALookup(self, hostname, dns_server):
+        """
+        Performs an A lookup and returns an array containg all the dotted quad
+        IP addresses in the response.
+
+        :hostname: is the hostname to perform the A lookup on
+
+        :dns_server: is the dns_server that should be used for the lookup as a
+                     tuple of ip port (ex. ("127.0.0.1", 53))
+        """
+        query = [dns.Query(hostname, dns.IN, dns.A)]
+        def gotResponse(message):
+            addrs = []
+            answers = []
+            for answer in message.answers:
+                if answer.type is 1:
+                    addr = answer.payload.dottedQuad()
+                    addrs.append(addr)
+                # We store the resource record and the answer payload in a
+                # tuple
+                r = (repr(answer), repr(answer.payload))
+                answers.append(r)
+            result = {}
+            result['query_type'] = 'A'
+            result['query'] = repr(query)
+            result['answers'] = answers
+            result['addrs'] = addrs
+            self.report['queries'].append(result)
+            return addrs
+
+        def gotError(failure):
+            log.exception(failure)
+            result = {}
+            result['query_type'] = 'A'
+            result['query'] = repr(query)
+            result['error'] = str(failure)
+            return None
+
+        resolver = Resolver(servers=[dns_server])
+        d = resolver.queryUDP(query, timeout=self.queryTimeout)
+        d.addCallback(gotResponse)
+        d.addErrback(gotError)
+        return d
+





More information about the tor-commits mailing list