-import dns
-import dnsmessage_pb2
+import errno
import os
import socket
import struct
import sys
import threading
-import time
-
import dns
import dnstap_pb2
+from unittest import SkipTest
+from recursortests import RecursorTest
FSTRM_CONTROL_ACCEPT = 0x01
FSTRM_CONTROL_START = 0x02
# Python2/3 compatibility hacks
try:
- from queue import Queue
+ from queue import Queue
except ImportError:
- from Queue import Queue
+ from Queue import Queue
try:
- range = xrange
+ range = xrange
except NameError:
- pass
+ pass
-from nose import SkipTest
-from recursortests import RecursorTest
-def checkDnstapBase(testinstance, dnstap, protocol, initiator):
+def checkDnstapBase(testinstance, dnstap, protocol, initiator, responder, response_port=53):
testinstance.assertTrue(dnstap)
testinstance.assertTrue(dnstap.HasField('identity'))
#testinstance.assertEqual(dnstap.identity, b'a.server')
testinstance.assertTrue(dnstap.message.HasField('socket_protocol'))
testinstance.assertEqual(dnstap.message.socket_protocol, protocol)
testinstance.assertTrue(dnstap.message.HasField('socket_family'))
- testinstance.assertEquals(dnstap.message.socket_family, dnstap_pb2.INET)
+ testinstance.assertEqual(dnstap.message.socket_family, dnstap_pb2.INET)
#
- # We cannot check the query address and port since we only log outgoing queries via dnstap
+ # The query address and port are from the the recursor, we don't know the port
#
- #testinstance.assertTrue(dnstap.message.HasField('query_address'))
- #testinstance.assertEquals(socket.inet_ntop(socket.AF_INET, dnstap.message.query_address), initiator)
+ testinstance.assertTrue(dnstap.message.HasField('query_address'))
+ testinstance.assertEqual(socket.inet_ntop(socket.AF_INET, dnstap.message.query_address), initiator)
+ testinstance.assertTrue(dnstap.message.HasField('query_port'))
testinstance.assertTrue(dnstap.message.HasField('response_address'))
- testinstance.assertEquals(socket.inet_ntop(socket.AF_INET, dnstap.message.response_address), initiator)
+ testinstance.assertEqual(socket.inet_ntop(socket.AF_INET, dnstap.message.response_address), responder)
testinstance.assertTrue(dnstap.message.HasField('response_port'))
- testinstance.assertEquals(dnstap.message.response_port, 53)
+ testinstance.assertEqual(dnstap.message.response_port, response_port)
-def checkDnstapQuery(testinstance, dnstap, protocol, query, initiator='127.0.0.1'):
- testinstance.assertEquals(dnstap.message.type, dnstap_pb2.Message.RESOLVER_QUERY)
- checkDnstapBase(testinstance, dnstap, protocol, initiator)
+def checkDnstapQuery(testinstance, dnstap, protocol, initiator, responder):
+ testinstance.assertEqual(dnstap.message.type, dnstap_pb2.Message.RESOLVER_QUERY)
+ checkDnstapBase(testinstance, dnstap, protocol, initiator, responder)
testinstance.assertTrue(dnstap.message.HasField('query_time_sec'))
testinstance.assertTrue(dnstap.message.HasField('query_time_nsec'))
# We cannot compare the incoming query with the outgoing one
# The IDs and some other fields will be different
#
- wire_message = dns.message.from_wire(dnstap.message.query_message)
+ #wire_message = dns.message.from_wire(dnstap.message.query_message)
#testinstance.assertEqual(wire_message, query)
+def checkDnstapNOD(testinstance, dnstap, protocol, initiator, responder, response_port, query_zone):
+ testinstance.assertEqual(dnstap.message.type, dnstap_pb2.Message.CLIENT_QUERY)
+ checkDnstapBase(testinstance, dnstap, protocol, initiator, responder, response_port)
+
+ testinstance.assertTrue(dnstap.message.HasField('query_time_sec'))
+ testinstance.assertTrue(dnstap.message.HasField('query_time_nsec'))
+
+ testinstance.assertTrue(dnstap.message.HasField('query_zone'))
+ testinstance.assertEqual(dns.name.from_wire(dnstap.message.query_zone, 0)[0].to_text(), query_zone)
+
+def checkDnstapUDR(testinstance, dnstap, protocol, initiator, responder, response_port, query_zone):
+ testinstance.assertEqual(dnstap.message.type, dnstap_pb2.Message.RESOLVER_RESPONSE)
+ checkDnstapBase(testinstance, dnstap, protocol, initiator, responder, response_port)
+
+ testinstance.assertTrue(dnstap.message.HasField('query_time_sec'))
+ testinstance.assertTrue(dnstap.message.HasField('query_time_nsec'))
+
+ testinstance.assertTrue(dnstap.message.HasField('query_zone'))
+ testinstance.assertEqual(dns.name.from_wire(dnstap.message.query_zone, 0)[0].to_text(), query_zone)
+
+ testinstance.assertTrue(dnstap.message.HasField('response_message'))
+ wire_message = dns.message.from_wire(dnstap.message.response_message)
def checkDnstapExtra(testinstance, dnstap, expected):
testinstance.assertTrue(dnstap.HasField('extra'))
testinstance.assertFalse(dnstap.HasField('extra'))
-def checkDnstapResponse(testinstance, dnstap, protocol, response, initiator='127.0.0.1'):
- testinstance.assertEquals(dnstap.message.type, dnstap_pb2.Message.RESOLVER_RESPONSE)
- checkDnstapBase(testinstance, dnstap, protocol, initiator)
+def checkDnstapResponse(testinstance, dnstap, protocol, response, initiator, responder):
+ testinstance.assertEqual(dnstap.message.type, dnstap_pb2.Message.RESOLVER_RESPONSE)
+ checkDnstapBase(testinstance, dnstap, protocol, initiator, responder)
testinstance.assertTrue(dnstap.message.HasField('query_time_sec'))
testinstance.assertTrue(dnstap.message.HasField('query_time_nsec'))
return t
-def fstrm_make_control_frame_reply(cft, data):
+def fstrm_make_control_frame_reply(cft):
if cft == FSTRM_CONTROL_READY:
# Reply with ACCEPT frame and content-type
contenttype = b'protobuf:dnstap.Dnstap'
(datalen,) = struct.unpack("!L", data)
data = conn.recv(datalen)
cft = fstrm_get_control_frame_type(data)
- reply = fstrm_make_control_frame_reply(cft, data)
+ reply = fstrm_make_control_frame_reply(cft)
if reply:
conn.send(reply)
return cft
-class DNSTapServerParams:
- def __init__(self, port):
- self.queue = Queue()
- self.port = port
+class DNSTapServerParams(object):
+ def __init__(self, path):
+ self.queue = Queue()
+ self.path = path
-DNSTapServerParameters = DNSTapServerParams(4243)
+DNSTapServerParameters = DNSTapServerParams("/tmp/dnstap.sock")
DNSTapListeners = []
class TestRecursorDNSTap(RecursorTest):
fstrm_handle_bidir_connection(conn, lambda data: \
param.queue.put(data, True, timeout=2.0))
except socket.error as e:
- if e.errno == 9:
+ if e.errno in (errno.EBADF, errno.EPIPE):
break
- printf("Unexpected socket error %d", e)
+ sys.stderr.write("Unexpected socket error %s\n" % str(e))
sys.exit(1)
- conn.close()
+ except exception as e:
+ sys.stderr.write("Unexpected socket error %s\n" % str(e))
+ sys.exit(1)
+ conn.close()
@classmethod
def FrameStreamUnixListenerMain(cls, param):
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
- sock.bind(("127.0.0.1", param.port))
+ try:
+ os.remove(param.path)
+ except:
+ pass
+ sock.bind(param.path)
+ sock.listen(100)
except socket.error as e:
- print("Error binding in the framestream listener: %s" % str(e))
+ sys.stderr.write("Error binding/listening in the framestream listener: %s\n" % str(e))
sys.exit(1)
DNSTapListeners.append(sock)
- sock.listen(100)
while True:
- (conn, _) = sock.accept()
- print("Accepting connection")
- listener = threading.Thread(name='DNSTap Worker', target=cls.FrameStreamUnixListener, args=[conn, param])
- listener.setDaemon(True)
- listener.start()
-
+ try:
+ (conn, addr) = sock.accept()
+ listener = threading.Thread(name='DNSTap Worker', target=cls.FrameStreamUnixListener, args=[conn, param])
+ listener.setDaemon(True)
+ listener.start()
+ except socket.error as e:
+ if e.errno != errno.EBADF:
+ sys.stderr.write("Socket error on accept: %s\n" % str(e))
+ else:
+ break
sock.close()
@classmethod
def setUpClass(cls):
-
- if os.environ.get("NODNSTAPTESTS") == "1":
- raise SkipTest("Not Yet Supported")
+ if os.environ.get("NODNSTAPTESTS") == "1":
+ raise SkipTest("Not Yet Supported")
cls.setUpSockets()
listener.setDaemon(True)
listener.start()
-
confdir = os.path.join('configs', cls._confdir)
cls.createConfigDir(confdir)
_config_template = """
auth-zones=example=configs/%s/example.zone""" % _confdir
_lua_config_file = """
- dnstapFrameStreamServer({"127.0.0.1:%d"})
- """ % (DNSTapServerParameters.port)
+dnstapFrameStreamServer({"%s"})
+ """ % DNSTapServerParameters.path
def getFirstDnstap(self):
- data = DNSTapServerParameters.queue.get(True, timeout=2.0)
+ try:
+ data = DNSTapServerParameters.queue.get(True, timeout=2.0)
+ except:
+ data = False
self.assertTrue(data)
dnstap = dnstap_pb2.Dnstap()
dnstap.ParseFromString(data)
return dnstap
def testA(self):
-
name = 'www.example.org.'
query = dns.message.make_query(name, 'A', want_dnssec=True)
query.flags |= dns.flags.RD
res = self.sendUDPQuery(query)
+ self.assertNotEqual(res, None)
- # check the DNSTap messages corresponding to the UDP query and answer
# check the dnstap message corresponding to the UDP query
dnstap = self.getFirstDnstap()
- checkDnstapQuery(self, dnstap, dnstap_pb2.UDP, query, '127.0.0.8')
+ checkDnstapQuery(self, dnstap, dnstap_pb2.UDP, '127.0.0.1', '127.0.0.8')
# We don't expect a response
checkDnstapNoExtra(self, dnstap)
class DNSTapLogNoQueriesTest(TestRecursorDNSTap):
+
+ _confdir = 'DNSTapLogNoQueries'
+ _config_template = """
+auth-zones=example=configs/%s/example.zone""" % _confdir
+ _lua_config_file = """
+dnstapFrameStreamServer({"%s"}, {logQueries=false})
+ """ % (DNSTapServerParameters.path)
+
+ def testA(self):
+ name = 'www.example.org.'
+ query = dns.message.make_query(name, 'A', want_dnssec=True)
+ query.flags |= dns.flags.RD
+ res = self.sendUDPQuery(query)
+ self.assertNotEqual(res, None)
+
+ # We don't expect anything
+ self.assertTrue(DNSTapServerParameters.queue.empty())
+
+class DNSTapLogNODTest(TestRecursorDNSTap):
"""
This test makes sure that we correctly export outgoing queries over DNSTap.
It must be improved and setup env so we can check for incoming responses, but makes sure for now
that the recursor at least connects to the DNSTap server.
"""
- _confdir = 'DNSTapLogNoQueries'
+ _confdir = 'DNSTapLogNODQueries'
_config_template = """
-auth-zones=example=configs/%s/example.zone""" % _confdir
+new-domain-tracking=yes
+new-domain-history-dir=configs/%s/nod
+unique-response-tracking=yes
+unique-response-history-dir=configs/%s/udr
+auth-zones=example=configs/%s/example.zone""" % (_confdir, _confdir, _confdir)
_lua_config_file = """
- dnstapFrameStreamServer({"127.0.0.1:%d"}, {logQueries=false})
- """ % (DNSTapServerParameters.port)
+dnstapNODFrameStreamServer({"%s"})
+ """ % (DNSTapServerParameters.path)
+
+ @classmethod
+ def generateRecursorConfig(cls, confdir):
+ for directory in ["nod", "udr"]:
+ path = os.path.join('configs', cls._confdir, directory)
+ cls.createConfigDir(path)
+ super(DNSTapLogNODTest, cls).generateRecursorConfig(confdir)
+
+ def getFirstDnstap(self):
+ try:
+ data = DNSTapServerParameters.queue.get(True, timeout=2.0)
+ except:
+ data = False
+ self.assertTrue(data)
+ dnstap = dnstap_pb2.Dnstap()
+ dnstap.ParseFromString(data)
+ return dnstap
def testA(self):
name = 'www.example.org.'
query = dns.message.make_query(name, 'A', want_dnssec=True)
query.flags |= dns.flags.RD
res = self.sendUDPQuery(query)
+ self.assertNotEqual(res, None)
- # We don't expect anything
- self.assertTrue(DNSTapServerParameters.queue.empty())
+ # check the dnstap message corresponding to the UDP query
+ dnstap = self.getFirstDnstap()
+
+ checkDnstapNOD(self, dnstap, dnstap_pb2.UDP, '127.0.0.1', '127.0.0.1', 5300, name)
+ # We don't expect a response
+ checkDnstapNoExtra(self, dnstap)
+
+class DNSTapLogUDRTest(TestRecursorDNSTap):
+
+ _confdir = 'DNSTapLogUDRResponses'
+ _config_template = """
+new-domain-tracking=yes
+new-domain-history-dir=configs/%s/nod
+unique-response-tracking=yes
+unique-response-history-dir=configs/%s/udr
+auth-zones=example=configs/%s/example.zone""" % (_confdir, _confdir, _confdir)
+ _lua_config_file = """
+dnstapNODFrameStreamServer({"%s"}, {logNODs=false, logUDRs=true})
+ """ % (DNSTapServerParameters.path)
+
+ @classmethod
+ def generateRecursorConfig(cls, confdir):
+ for directory in ["nod", "udr"]:
+ path = os.path.join('configs', cls._confdir, directory)
+ cls.createConfigDir(path)
+ super(DNSTapLogUDRTest, cls).generateRecursorConfig(confdir)
+
+ def getFirstDnstap(self):
+ try:
+ data = DNSTapServerParameters.queue.get(True, timeout=2.0)
+ except:
+ data = False
+ self.assertTrue(data)
+ dnstap = dnstap_pb2.Dnstap()
+ dnstap.ParseFromString(data)
+ return dnstap
+
+ def testA(self):
+ name = 'types.example.'
+ query = dns.message.make_query(name, 'A', want_dnssec=True)
+ query.flags |= dns.flags.RD
+ res = self.sendUDPQuery(query)
+ self.assertNotEqual(res, None)
+
+ # check the dnstap message corresponding to the UDP query
+ dnstap = self.getFirstDnstap()
+ checkDnstapUDR(self, dnstap, dnstap_pb2.UDP, '127.0.0.1', '127.0.0.1', 5300, name)
+ # We don't expect a rpasesponse
+ checkDnstapNoExtra(self, dnstap)
+
+class DNSTapLogNODUDRTest(TestRecursorDNSTap):
+
+ _confdir = 'DNSTapLogNODUDRs'
+ _config_template = """
+new-domain-tracking=yes
+new-domain-history-dir=configs/%s/nod
+unique-response-tracking=yes
+unique-response-history-dir=configs/%s/udr
+auth-zones=example=configs/%s/example.zone""" % (_confdir, _confdir, _confdir)
+ _lua_config_file = """
+dnstapNODFrameStreamServer({"%s"}, {logNODs=true, logUDRs=true})
+ """ % (DNSTapServerParameters.path)
+
+ @classmethod
+ def generateRecursorConfig(cls, confdir):
+ for directory in ["nod", "udr"]:
+ path = os.path.join('configs', cls._confdir, directory)
+ cls.createConfigDir(path)
+ super(DNSTapLogNODUDRTest, cls).generateRecursorConfig(confdir)
+
+ def getFirstDnstap(self):
+ try:
+ data = DNSTapServerParameters.queue.get(True, timeout=2.0)
+ except:
+ data = False
+ self.assertTrue(data)
+ dnstap = dnstap_pb2.Dnstap()
+ dnstap.ParseFromString(data)
+ return dnstap
+
+ def testA(self):
+ name = 'types.example.'
+ query = dns.message.make_query(name, 'A', want_dnssec=True)
+ query.flags |= dns.flags.RD
+ res = self.sendUDPQuery(query)
+ self.assertNotEqual(res, None)
+
+ dnstap = self.getFirstDnstap()
+ checkDnstapUDR(self, dnstap, dnstap_pb2.UDP, '127.0.0.1', '127.0.0.1', 5300, name)
+
+ dnstap = self.getFirstDnstap()
+ checkDnstapNOD(self, dnstap, dnstap_pb2.UDP, '127.0.0.1', '127.0.0.1', 5300, name)
+
+ checkDnstapNoExtra(self, dnstap)