[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