]> git.ipfire.org Git - thirdparty/pdns.git/blobdiff - regression-tests.recursor-dnssec/test_RecDnstap.py
Merge pull request #13423 from phonedph1/patch-3
[thirdparty/pdns.git] / regression-tests.recursor-dnssec / test_RecDnstap.py
index c94c1b5c2eeae912c057560d3a5fe843da1d3731..5856a1bb00cc35d35bc448274c8b0a186a5c2701 100644 (file)
@@ -1,14 +1,13 @@
-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
@@ -18,19 +17,17 @@ FSTRM_CONTROL_FINISH = 0x05
 
 # 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')
@@ -42,21 +39,22 @@ def checkDnstapBase(testinstance, dnstap, protocol, initiator):
     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'))
@@ -66,9 +64,31 @@ def checkDnstapQuery(testinstance, dnstap, protocol, query, initiator='127.0.0.1
     # 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'))
@@ -79,9 +99,9 @@ def checkDnstapNoExtra(testinstance, dnstap):
     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'))
@@ -101,7 +121,7 @@ def fstrm_get_control_frame_type(data):
     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'
@@ -122,7 +142,7 @@ def fstrm_read_and_dispatch_control_frame(conn):
     (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
@@ -150,13 +170,13 @@ def fstrm_handle_bidir_connection(conn, on_data):
 
 
 
-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):
@@ -167,37 +187,46 @@ 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()
 
@@ -207,7 +236,6 @@ class TestRecursorDNSTap(RecursorTest):
         listener.setDaemon(True)
         listener.start()
 
-
         confdir = os.path.join('configs', cls._confdir)
         cls.createConfigDir(confdir)
 
@@ -258,51 +286,186 @@ class DNSTapDefaultTest(TestRecursorDNSTap):
     _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)