]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add regression tests for backend discovery
authorRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 8 Feb 2022 09:37:17 +0000 (10:37 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 22 Feb 2022 09:00:59 +0000 (10:00 +0100)
regression-tests.dnsdist/configServer.conf
regression-tests.dnsdist/dnsdisttests.py
regression-tests.dnsdist/test_BackendDiscovery.py [new file with mode: 0644]

index f1aa4c7feddbb3ff07fe529033afbf40108b29ff..b1e9e012b775a7e7e16df041fcb52364ba784cc6 100644 (file)
@@ -18,3 +18,4 @@ subjectAltName = @alt_names
 [alt_names]
 DNS.1 = tls.tests.dnsdist.org
 DNS.2 = powerdns.com
+IP.3 = 127.0.0.1
index 99b1cecf7893714cf5879eb8a8c74b15ee01ed6a..5da02e030b2c8a1906e304c7f3d327e61394155a 100644 (file)
@@ -297,7 +297,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
       conn.close()
 
     @classmethod
-    def TCPResponder(cls, port, fromQueue, toQueue, trailingDataResponse=False, multipleResponses=False, callback=None, tlsContext=None, multipleConnections=False):
+    def TCPResponder(cls, port, fromQueue, toQueue, trailingDataResponse=False, multipleResponses=False, callback=None, tlsContext=None, multipleConnections=False, listeningAddr='127.0.0.1'):
         # trailingDataResponse=True means "ignore trailing data".
         # Other values are either False (meaning "raise an exception")
         # or are interpreted as a response RCODE for queries with trailing data.
@@ -307,7 +307,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
         try:
-            sock.bind(("127.0.0.1", port))
+            sock.bind((listeningAddr, port))
         except socket.error as e:
             print("Error binding in the TCP responder: %s" % str(e))
             sys.exit(1)
@@ -339,9 +339,14 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
     @classmethod
     def handleDoHConnection(cls, config, conn, fromQueue, toQueue, trailingDataResponse, multipleResponses, callback, tlsContext, useProxyProtocol):
         ignoreTrailing = trailingDataResponse is True
-        h2conn = h2.connection.H2Connection(config=config)
-        h2conn.initiate_connection()
-        conn.sendall(h2conn.data_to_send())
+        try:
+          h2conn = h2.connection.H2Connection(config=config)
+          h2conn.initiate_connection()
+          conn.sendall(h2conn.data_to_send())
+        except ssl.SSLEOFError as e:
+          print("Unexpected EOF: %s" % (e))
+          return
+
         dnsData = {}
 
         if useProxyProtocol:
diff --git a/regression-tests.dnsdist/test_BackendDiscovery.py b/regression-tests.dnsdist/test_BackendDiscovery.py
new file mode 100644 (file)
index 0000000..5bfbc79
--- /dev/null
@@ -0,0 +1,200 @@
+#!/usr/bin/env python
+import base64
+import dns
+import threading
+import time
+import ssl
+
+from dnsdisttests import DNSDistTest
+
+class TestBackendDiscovery(DNSDistTest):
+    _noSVCBackendPort = 10600
+    _svcNoUpgradeBackendPort = 10601
+    _svcUpgradeDoTBackendPort = 10602
+    _svcUpgradeDoHBackendPort = 10603
+    _svcUpgradeDoTBackendDifferentAddrPort1 = 10604
+    _svcUpgradeDoTBackendDifferentAddrPort2 = 10605
+    _svcUpgradeDoTUnreachableBackendPort = 10606
+    _upgradedBackendsPool = 'upgraded'
+
+    _consoleKey = DNSDistTest.generateConsoleKey()
+    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+    _config_params = ['_consoleKeyB64', '_consolePort', '_noSVCBackendPort', '_svcNoUpgradeBackendPort', '_svcUpgradeDoTBackendPort', '_upgradedBackendsPool', '_svcUpgradeDoHBackendPort', '_svcUpgradeDoTBackendDifferentAddrPort1', '_svcUpgradeDoTBackendDifferentAddrPort2', '_svcUpgradeDoTUnreachableBackendPort']
+    _config_template = """
+    setKey("%s")
+    controlSocket("127.0.0.1:%d")
+
+    setMaxTCPClientThreads(1)
+
+    -- no SVCB
+    newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
+
+    -- SVCB record but no upgrade path available
+    newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
+
+    -- SVCB upgrade to DoT, same address, keep the backend, different pool
+    newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradePool='%s', autoUpgradeKeep=true}:setUp()
+
+    -- SVCB upgrade to DoH, same address, do not keep the backend, same pool
+    newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
+
+    -- SVCB upgrade to DoT, different address, certificate is valid for the initial address
+    newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
+
+    -- SVCB upgrade to DoT, different address, certificate is NOT valid for the initial address
+    newServer{address="127.0.0.2:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
+
+    -- SVCB upgrade to DoT but upgraded port is not reachable
+    newServer{address="127.0.0.1:%s", caStore='ca.pem', autoUpgrade=true, autoUpgradeKeep=false}:setUp()
+    """
+    _verboseMode = True
+
+    def NoSVCCallback(request):
+        return dns.message.make_response(request).to_wire()
+
+    def NoUpgradePathCallback(request):
+        response = dns.message.make_response(request)
+        rrset = dns.rrset.from_text(request.question[0].name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.SVCB,
+                                    '1 no-upgrade. alpn="h3"')
+        response.answer.append(rrset)
+        return response.to_wire()
+
+    def UpgradeDoTCallback(request):
+        response = dns.message.make_response(request)
+        rrset = dns.rrset.from_text(request.question[0].name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.SVCB,
+                                    '1 tls.tests.dnsdist.org. alpn="dot" port=10652 ipv4hint=127.0.0.1')
+        response.answer.append(rrset)
+        return response.to_wire()
+
+    def UpgradeDoHCallback(request):
+        response = dns.message.make_response(request)
+        rrset = dns.rrset.from_text(request.question[0].name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.SVCB,
+                                    '1 tls.tests.dnsdist.org. alpn="h2" port=10653 ipv4hint=127.0.0.1 key7="/dns-query{?dns}"')
+        response.answer.append(rrset)
+        return response.to_wire()
+
+    def UpgradeDoTDifferentAddr1Callback(request):
+        response = dns.message.make_response(request)
+        rrset = dns.rrset.from_text(request.question[0].name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.SVCB,
+                                    '1 tls.tests.dnsdist.org. alpn="dot" port=10654 ipv4hint=127.0.0.2')
+        response.answer.append(rrset)
+        return response.to_wire()
+
+    def UpgradeDoTDifferentAddr2Callback(request):
+        response = dns.message.make_response(request)
+        rrset = dns.rrset.from_text(request.question[0].name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.SVCB,
+                                    '1 tls.tests.dnsdist.org. alpn="dot" port=10655 ipv4hint=127.0.0.1')
+        response.answer.append(rrset)
+        return response.to_wire()
+
+    def UpgradeDoTUnreachableCallback(request):
+        response = dns.message.make_response(request)
+        rrset = dns.rrset.from_text(request.question[0].name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.SVCB,
+                                    '1 tls.tests.dnsdist.org. alpn="dot" port=10656 ipv4hint=127.0.0.1')
+        response.answer.append(rrset)
+        return response.to_wire()
+
+    @classmethod
+    def startResponders(cls):
+        tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+        tlsContext.load_cert_chain('server.chain', 'server.key')
+
+        TCPNoSVCResponder = threading.Thread(name='TCP no SVC Responder', target=cls.TCPResponder, args=[cls._noSVCBackendPort, cls._toResponderQueue, cls._fromResponderQueue, True, False, cls.NoSVCCallback])
+        TCPNoSVCResponder.setDaemon(True)
+        TCPNoSVCResponder.start()
+
+        TCPNoUpgradeResponder = threading.Thread(name='TCP no upgrade Responder', target=cls.TCPResponder, args=[cls._svcNoUpgradeBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.NoUpgradePathCallback])
+        TCPNoUpgradeResponder.setDaemon(True)
+        TCPNoUpgradeResponder.start()
+
+        TCPUpgradeToDoTResponder = threading.Thread(name='TCP upgrade to DoT Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTCallback])
+        TCPUpgradeToDoTResponder.setDaemon(True)
+        TCPUpgradeToDoTResponder.start()
+        # and the corresponding DoT responder
+        UpgradedDoTResponder = threading.Thread(name='DoT upgraded Responder', target=cls.TCPResponder, args=[10652, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
+        UpgradedDoTResponder.setDaemon(True)
+        UpgradedDoTResponder.start()
+
+        TCPUpgradeToDoHResponder = threading.Thread(name='TCP upgrade to DoH Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoHBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoHCallback])
+        TCPUpgradeToDoHResponder.setDaemon(True)
+        TCPUpgradeToDoHResponder.start()
+        # and the corresponding DoH responder
+        UpgradedDOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[10653, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
+        UpgradedDOHResponder.setDaemon(True)
+        UpgradedDOHResponder.start()
+
+        TCPUpgradeToDoTDifferentAddrResponder = threading.Thread(name='TCP upgrade to DoT different addr 1 Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTBackendDifferentAddrPort1, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTDifferentAddr1Callback])
+        TCPUpgradeToDoTDifferentAddrResponder.setDaemon(True)
+        TCPUpgradeToDoTDifferentAddrResponder.start()
+        # and the corresponding DoT responder
+        UpgradedDoTResponder = threading.Thread(name='DoT upgraded different addr 1 Responder', target=cls.TCPResponder, args=[10654, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext, False, '127.0.0.2'])
+        UpgradedDoTResponder.setDaemon(True)
+        UpgradedDoTResponder.start()
+
+        TCPUpgradeToDoTDifferentAddrResponder = threading.Thread(name='TCP upgrade to DoT different addr 2 Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTBackendDifferentAddrPort2, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTDifferentAddr2Callback, None, False, '127.0.0.2'])
+        TCPUpgradeToDoTDifferentAddrResponder.setDaemon(True)
+        TCPUpgradeToDoTDifferentAddrResponder.start()
+        # and the corresponding DoT responder
+        UpgradedDoTResponder = threading.Thread(name='DoT upgraded different addr 2 Responder', target=cls.TCPResponder, args=[10655, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext, False])
+        UpgradedDoTResponder.setDaemon(True)
+        UpgradedDoTResponder.start()
+
+        TCPUpgradeToUnreachableDoTResponder = threading.Thread(name='TCP upgrade to unreachable DoT Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTUnreachableBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTUnreachableCallback])
+        TCPUpgradeToUnreachableDoTResponder.setDaemon(True)
+        TCPUpgradeToUnreachableDoTResponder.start()
+        # and NO corresponding DoT responder
+        # this is not a mistake!
+
+        # enough time for discovery to happen
+        time.sleep(5)
+
+    def testBackendUpgrade(self):
+        """
+        Backend Discovery: Upgrade
+        """
+        output = self.sendConsoleCommand('showServers()')
+        print(output)
+
+        backends = {}
+        for line in output.splitlines(False):
+            if line.startswith('#') or line.startswith('All'):
+                continue
+            tokens = line.split()
+            self.assertTrue(len(tokens) == 12 or len(tokens) == 13)
+            self.assertEquals(tokens[2], 'UP')
+            pool = ''
+            if len(tokens) == 13:
+                pool = tokens[12]
+            backends[tokens[1]] = pool
+
+        expected = {
+            '127.0.0.1:10600': '',
+            '127.0.0.1:10601': '',
+            '127.0.0.1:10602': '',
+            # 10603 has been upgraded to 10653 and removed
+            # 10604 has been upgraded to 10654 and removed
+            '127.0.0.2:10605': '',
+            '127.0.0.1:10606': '',
+            '127.0.0.1:10652': 'upgraded',
+            '127.0.0.1:10653': '',
+            '127.0.0.2:10654': ''
+        }
+        self.assertEquals(backends, expected)