[tor-commits] [ooni-probe/master] Added subclassed t.n.client.Resolver() to overcome query port randomization.

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


commit 5e748acc7f787d37274cbdab9a57b317919f2fa9
Author: Isis Lovecruft <isis at patternsinthevoid.net>
Date:   Sat Jun 16 09:21:53 2012 -0700

    Added subclassed t.n.client.Resolver() to overcome query port randomization.
---
 ooni/plugins/dnstamper.py |   75 ++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 68 insertions(+), 7 deletions(-)

diff --git a/ooni/plugins/dnstamper.py b/ooni/plugins/dnstamper.py
index 34aaa01..ca0e2e3 100644
--- a/ooni/plugins/dnstamper.py
+++ b/ooni/plugins/dnstamper.py
@@ -28,8 +28,9 @@
 
 import os
 
-from twisted.names import client
+from twisted.names import client, dns
 from twisted.internet import reactor
+from twisted.internet.error import CannotListenError
 from twisted.internet.protocol import Factory, Protocol
 from twisted.python import usage
 from twisted.plugin import IPlugin
@@ -39,7 +40,7 @@ from ooni.plugoo.assets import Asset
 from ooni.plugoo.tests import ITest, OONITest
 from ooni import log
 
-class Top1MAsset(Asset):
+class AlexaAsset(Asset):
     """
     Class for parsing the Alexa top-1m.txt as an asset.
     """
@@ -54,9 +55,60 @@ 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'],
+                     ['localservers', 'l', False, 'Also test local servers'],
+                     ['port', 'p', None, 'Local UDP port to send queries over'],
                      ['usereverse', 'r', False, 'Also try reverse DNS resolves'],
                      ['resume', 's', 0, 'Resume at this index in the asset file']]
 
+class DNSTamperResolver(client.Resolver):
+    """
+    Twisted by default issues DNS queries over cryptographically random
+    UDP ports to mitigate the Berstein/Kaminsky attack on limited DNS
+    Transaction ID numbers.[1][2][3]
+
+    This is fine, unless the client has external restrictions which require
+    DNS queries to be conducted over UDP port 53. Twisted does not provide
+    an easy way to change this, ergo subclassing client.Resolver.[4] It 
+    would perhaps be wise to patch twisted.names.client and request a merge
+    into upstream.
+
+    [1] https://twistedmatrix.com/trac/ticket/3342
+    [2] http://blog.netherlabs.nl/articles/2008/07/09/ \
+        some-thoughts-on-the-recent-dns-vulnerability
+    [3] http://www.blackhat.com/presentations/bh-dc-09/Kaminsky/ \
+        BlackHat-DC-09-Kaminsky-DNS-Critical-Infrastructure.pdf
+    [4] http://comments.gmane.org/gmane.comp.python.twisted/22794
+    """
+    def __init__(self):
+        super(DNSTamperResolver, self).__init__()
+        #client.Resolver.__init__(self)
+
+        if self.local_options['port']:
+            self.port = self.local_options['port']
+        else:
+            self.port = '53'
+
+    def _connectedProtocol(self):
+        """
+        Return a new DNSDatagramProtocol bound to a specific port
+        rather than the default cryptographically-random port.
+        """
+        if 'protocol' in self.__dict__:
+            return self.protocol
+        proto = dns.DNSDatagramProtocol(self)
+        
+        ## XXX We may need to remove the while loop, which was 
+        ## originally implemented to safeguard against attempts to
+        ## bind to the same random port twice...but then the code
+        ## would be blocking...
+        while True:
+            try:
+                self._reactor.listenUDP(self.port, proto)
+            except error.CannotListenError:
+                pass
+            else:
+                return proto
+
 class DNSTamperTest(OONITest):
     implements(IPlugin, ITest)
 
@@ -66,13 +118,20 @@ class DNSTamperTest(OONITest):
     options = DNSTamperArgs
     blocking = False
     
+    if self.local_options['localservers']:
+        ## client.createResolver() turns None into '/etc/resolv.conf' 
+        ## on posix systems, ignored on Windows.
+        self.resolvconf = None
+    else:
+        self.resolvconf = ''
+
     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)})
+                    assets.update({'asset': AlexaAsset(assetf)})
                 else:
                     assets.update({'asset': Asset(assetf)})
             elif self.local_options['testservers']:
@@ -88,7 +147,7 @@ class DNSTamperTest(OONITest):
         def got_result(result):
             log.msg('Resolved %s through %s to %s' 
                     % (hostname, nameserver, result))
-            reactor.stop()
+            #reactor.stop()
             return {'resolved': True,
                     'domain': hostname,
                     'nameserver': nameserver,
@@ -96,13 +155,14 @@ class DNSTamperTest(OONITest):
 
         def got_error(err):
             log.msg(err.printTraceback())
-            reactor.stop()
+            #reactor.stop()
             return {'resolved': False,
                     'domain': hostname,
                     'nameserver': nameserver,
                     'address': err}
 
-        res = client.createResolver(servers=[(nameserver, 53)])
+        res = client.createResolver(resolvconf=self.resolvconf, 
+                                    servers=[(nameserver, 53)])
         d = res.getHostByName(hostname)
         d.addCallbacks(got_result, got_error)
         return d
@@ -116,7 +176,8 @@ class DNSTamperTest(OONITest):
         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)])
+        res = client.createResolver(resolvconf=self.resolvconf, 
+                                    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))





More information about the tor-commits mailing list