"Fossies" - the Fresh Open Source Software Archive

Member "pdns-auth-4.2.0/regression-tests.dnsdist/test_DynBlocks.py" (27 Aug 2019, 40388 Bytes) of package /linux/misc/dns/pdns-auth-4.2.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "test_DynBlocks.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 4.1.13_vs_4.2.0.

    1 #!/usr/bin/env python
    2 import base64
    3 import json
    4 import requests
    5 import time
    6 import dns
    7 from dnsdisttests import DNSDistTest
    8 try:
    9   range = xrange
   10 except NameError:
   11   pass
   12 
   13 class DynBlocksTest(DNSDistTest):
   14 
   15     _webTimeout = 2.0
   16     _webServerPort = 8083
   17     _webServerBasicAuthPassword = 'secret'
   18     _webServerAPIKey = 'apisecret'
   19 
   20     def doTestDynBlockViaAPI(self, range, reason, minSeconds, maxSeconds, minBlocks, maxBlocks):
   21         headers = {'x-api-key': self._webServerAPIKey}
   22         url = 'http://127.0.0.1:' + str(self._webServerPort) + '/jsonstat?command=dynblocklist'
   23         r = requests.get(url, headers=headers, timeout=self._webTimeout)
   24         self.assertTrue(r)
   25         self.assertEquals(r.status_code, 200)
   26 
   27         content = r.json()
   28         self.assertIsNotNone(content)
   29         self.assertIn(range, content)
   30 
   31         values = content[range]
   32         for key in ['reason', 'seconds', 'blocks', 'action']:
   33             self.assertIn(key, values)
   34 
   35         self.assertEqual(values['reason'], reason)
   36         self.assertGreaterEqual(values['seconds'], minSeconds)
   37         self.assertLessEqual(values['seconds'], maxSeconds)
   38         self.assertGreaterEqual(values['blocks'], minBlocks)
   39         self.assertLessEqual(values['blocks'], maxBlocks)
   40 
   41     def doTestQRate(self, name, testViaAPI=True):
   42         query = dns.message.make_query(name, 'A', 'IN')
   43         response = dns.message.make_response(query)
   44         rrset = dns.rrset.from_text(name,
   45                                     60,
   46                                     dns.rdataclass.IN,
   47                                     dns.rdatatype.A,
   48                                     '192.0.2.1')
   49         response.answer.append(rrset)
   50 
   51         allowed = 0
   52         sent = 0
   53         for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
   54             (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
   55             sent = sent + 1
   56             if receivedQuery:
   57                 receivedQuery.id = query.id
   58                 self.assertEquals(query, receivedQuery)
   59                 self.assertEquals(response, receivedResponse)
   60                 allowed = allowed + 1
   61             else:
   62                 # the query has not reached the responder,
   63                 # let's clear the response queue
   64                 self.clearToResponderQueue()
   65 
   66         # we might be already blocked, but we should have been able to send
   67         # at least self._dynBlockQPS queries
   68         self.assertGreaterEqual(allowed, self._dynBlockQPS)
   69 
   70         if allowed == sent:
   71             # wait for the maintenance function to run
   72             time.sleep(2)
   73 
   74         # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
   75         (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
   76         self.assertEquals(receivedResponse, None)
   77 
   78         if testViaAPI:
   79             self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, (sent-allowed)+1, (sent-allowed)+1)
   80 
   81         # wait until we are not blocked anymore
   82         time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
   83 
   84         # this one should succeed
   85         (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
   86         receivedQuery.id = query.id
   87         self.assertEquals(query, receivedQuery)
   88         self.assertEquals(response, receivedResponse)
   89 
   90         # again, over TCP this time
   91         allowed = 0
   92         sent = 0
   93         for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
   94             (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
   95             sent = sent + 1
   96             if receivedQuery:
   97                 receivedQuery.id = query.id
   98                 self.assertEquals(query, receivedQuery)
   99                 self.assertEquals(response, receivedResponse)
  100                 allowed = allowed + 1
  101             else:
  102                 # the query has not reached the responder,
  103                 # let's clear the response queue
  104                 self.clearToResponderQueue()
  105 
  106         # we might be already blocked, but we should have been able to send
  107         # at least self._dynBlockQPS queries
  108         self.assertGreaterEqual(allowed, self._dynBlockQPS)
  109 
  110         if allowed == sent:
  111             # wait for the maintenance function to run
  112             time.sleep(2)
  113 
  114         # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
  115         (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
  116         self.assertEquals(receivedResponse, None)
  117 
  118         # wait until we are not blocked anymore
  119         time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
  120 
  121         # this one should succeed
  122         (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
  123         receivedQuery.id = query.id
  124         self.assertEquals(query, receivedQuery)
  125         self.assertEquals(response, receivedResponse)
  126 
  127     def doTestQRateRCode(self, name, rcode):
  128         query = dns.message.make_query(name, 'A', 'IN')
  129         response = dns.message.make_response(query)
  130         rrset = dns.rrset.from_text(name,
  131                                     60,
  132                                     dns.rdataclass.IN,
  133                                     dns.rdatatype.A,
  134                                     '192.0.2.1')
  135         response.answer.append(rrset)
  136         expectedResponse = dns.message.make_response(query)
  137         expectedResponse.set_rcode(rcode)
  138 
  139         allowed = 0
  140         sent = 0
  141         for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
  142             (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
  143             sent = sent + 1
  144             if receivedQuery:
  145                 receivedQuery.id = query.id
  146                 self.assertEquals(query, receivedQuery)
  147                 self.assertEquals(receivedResponse, response)
  148                 allowed = allowed + 1
  149             else:
  150                 self.assertEquals(receivedResponse, expectedResponse)
  151                 # the query has not reached the responder,
  152                 # let's clear the response queue
  153                 self.clearToResponderQueue()
  154 
  155         # we might be already blocked, but we should have been able to send
  156         # at least self._dynBlockQPS queries
  157         self.assertGreaterEqual(allowed, self._dynBlockQPS)
  158 
  159         if allowed == sent:
  160             # wait for the maintenance function to run
  161             time.sleep(2)
  162 
  163         # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
  164         (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
  165         self.assertEquals(receivedResponse, expectedResponse)
  166 
  167         # wait until we are not blocked anymore
  168         time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
  169 
  170         # this one should succeed
  171         (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
  172         receivedQuery.id = query.id
  173         self.assertEquals(query, receivedQuery)
  174         self.assertEquals(response, receivedResponse)
  175 
  176         allowed = 0
  177         sent = 0
  178         # again, over TCP this time
  179         for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
  180             (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
  181             sent = sent + 1
  182             if receivedQuery:
  183                 receivedQuery.id = query.id
  184                 self.assertEquals(query, receivedQuery)
  185                 self.assertEquals(receivedResponse, response)
  186                 allowed = allowed + 1
  187             else:
  188                 self.assertEquals(receivedResponse, expectedResponse)
  189                 # the query has not reached the responder,
  190                 # let's clear the response queue
  191                 self.clearToResponderQueue()
  192 
  193         # we might be already blocked, but we should have been able to send
  194         # at least self._dynBlockQPS queries
  195         self.assertGreaterEqual(allowed, self._dynBlockQPS)
  196 
  197         if allowed == sent:
  198             # wait for the maintenance function to run
  199             time.sleep(2)
  200 
  201         # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
  202         (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
  203         self.assertEquals(receivedResponse, expectedResponse)
  204 
  205         # wait until we are not blocked anymore
  206         time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
  207 
  208         # this one should succeed
  209         (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
  210         receivedQuery.id = query.id
  211         self.assertEquals(query, receivedQuery)
  212         self.assertEquals(response, receivedResponse)
  213 
  214     def doTestResponseByteRate(self, name):
  215         query = dns.message.make_query(name, 'A', 'IN')
  216         response = dns.message.make_response(query)
  217         response.answer.append(dns.rrset.from_text_list(name,
  218                                                        60,
  219                                                        dns.rdataclass.IN,
  220                                                        dns.rdatatype.A,
  221                                                        ['192.0.2.1', '192.0.2.2', '192.0.2.3', '192.0.2.4']))
  222         response.answer.append(dns.rrset.from_text(name,
  223                                                    60,
  224                                                    dns.rdataclass.IN,
  225                                                    dns.rdatatype.AAAA,
  226                                                    '2001:DB8::1'))
  227 
  228         allowed = 0
  229         sent = 0
  230 
  231         print(time.time())
  232 
  233         for _ in range(int(self._dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
  234             (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
  235             sent = sent + len(response.to_wire())
  236             if receivedQuery:
  237                 receivedQuery.id = query.id
  238                 self.assertEquals(query, receivedQuery)
  239                 self.assertEquals(response, receivedResponse)
  240                 allowed = allowed + len(response.to_wire())
  241             else:
  242                 # the query has not reached the responder,
  243                 # let's clear the response queue
  244                 self.clearToResponderQueue()
  245                 # and stop right there, otherwise we might
  246                 # wait for so long that the dynblock is gone
  247                 # by the time we finished
  248                 break
  249 
  250         # we might be already blocked, but we should have been able to send
  251         # at least self._dynBlockBytesPerSecond bytes
  252         print(allowed)
  253         print(sent)
  254         print(time.time())
  255         self.assertGreaterEqual(allowed, self._dynBlockBytesPerSecond)
  256 
  257         print(self.sendConsoleCommand("showDynBlocks()"))
  258         print(self.sendConsoleCommand("grepq(\"\")"))
  259         print(time.time())
  260 
  261         if allowed == sent:
  262             # wait for the maintenance function to run
  263             print("Waiting for the maintenance function to run")
  264             time.sleep(2)
  265 
  266         print(self.sendConsoleCommand("showDynBlocks()"))
  267         print(self.sendConsoleCommand("grepq(\"\")"))
  268         print(time.time())
  269 
  270         # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
  271         (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
  272         self.assertEquals(receivedResponse, None)
  273 
  274         print(self.sendConsoleCommand("showDynBlocks()"))
  275         print(self.sendConsoleCommand("grepq(\"\")"))
  276         print(time.time())
  277 
  278         # wait until we are not blocked anymore
  279         time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
  280 
  281         print(self.sendConsoleCommand("showDynBlocks()"))
  282         print(self.sendConsoleCommand("grepq(\"\")"))
  283         print(time.time())
  284 
  285         # this one should succeed
  286         (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
  287         receivedQuery.id = query.id
  288         self.assertEquals(query, receivedQuery)
  289         self.assertEquals(response, receivedResponse)
  290 
  291         # again, over TCP this time
  292         allowed = 0
  293         sent = 0
  294         for _ in range(int(self._dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
  295             (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
  296             sent = sent + len(response.to_wire())
  297             if receivedQuery:
  298                 receivedQuery.id = query.id
  299                 self.assertEquals(query, receivedQuery)
  300                 self.assertEquals(response, receivedResponse)
  301                 allowed = allowed + len(response.to_wire())
  302             else:
  303                 # the query has not reached the responder,
  304                 # let's clear the response queue
  305                 self.clearToResponderQueue()
  306                 # and stop right there, otherwise we might
  307                 # wait for so long that the dynblock is gone
  308                 # by the time we finished
  309                 break
  310 
  311         # we might be already blocked, but we should have been able to send
  312         # at least self._dynBlockBytesPerSecond bytes
  313         self.assertGreaterEqual(allowed, self._dynBlockBytesPerSecond)
  314 
  315         if allowed == sent:
  316             # wait for the maintenance function to run
  317             time.sleep(2)
  318 
  319         # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
  320         (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
  321         self.assertEquals(receivedResponse, None)
  322 
  323         # wait until we are not blocked anymore
  324         time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
  325 
  326         # this one should succeed
  327         (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
  328         receivedQuery.id = query.id
  329         self.assertEquals(query, receivedQuery)
  330         self.assertEquals(response, receivedResponse)
  331 
  332     def doTestRCodeRate(self, name, rcode):
  333         query = dns.message.make_query(name, 'A', 'IN')
  334         response = dns.message.make_response(query)
  335         rrset = dns.rrset.from_text(name,
  336                                     60,
  337                                     dns.rdataclass.IN,
  338                                     dns.rdatatype.A,
  339                                     '192.0.2.1')
  340         response.answer.append(rrset)
  341         expectedResponse = dns.message.make_response(query)
  342         expectedResponse.set_rcode(rcode)
  343 
  344         # start with normal responses
  345         for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
  346             (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
  347             receivedQuery.id = query.id
  348             self.assertEquals(query, receivedQuery)
  349             self.assertEquals(response, receivedResponse)
  350 
  351         # wait for the maintenance function to run
  352         time.sleep(2)
  353 
  354         # we should NOT be dropped!
  355         (_, receivedResponse) = self.sendUDPQuery(query, response)
  356         self.assertEquals(receivedResponse, response)
  357 
  358         # now with rcode!
  359         sent = 0
  360         allowed = 0
  361         for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
  362             (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
  363             sent = sent + 1
  364             if receivedQuery:
  365                 receivedQuery.id = query.id
  366                 self.assertEquals(query, receivedQuery)
  367                 self.assertEquals(expectedResponse, receivedResponse)
  368                 allowed = allowed + 1
  369             else:
  370                 # the query has not reached the responder,
  371                 # let's clear the response queue
  372                 self.clearToResponderQueue()
  373 
  374         # we might be already blocked, but we should have been able to send
  375         # at least self._dynBlockQPS queries
  376         self.assertGreaterEqual(allowed, self._dynBlockQPS)
  377 
  378         if allowed == sent:
  379             # wait for the maintenance function to run
  380             time.sleep(2)
  381 
  382         # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
  383         (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
  384         self.assertEquals(receivedResponse, None)
  385 
  386         # wait until we are not blocked anymore
  387         time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
  388 
  389         # this one should succeed
  390         (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
  391         receivedQuery.id = query.id
  392         self.assertEquals(query, receivedQuery)
  393         self.assertEquals(response, receivedResponse)
  394 
  395         # again, over TCP this time
  396         # start with normal responses
  397         for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
  398             (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
  399             receivedQuery.id = query.id
  400             self.assertEquals(query, receivedQuery)
  401             self.assertEquals(response, receivedResponse)
  402 
  403         # wait for the maintenance function to run
  404         time.sleep(2)
  405 
  406         # we should NOT be dropped!
  407         (_, receivedResponse) = self.sendUDPQuery(query, response)
  408         self.assertEquals(receivedResponse, response)
  409 
  410         # now with rcode!
  411         sent = 0
  412         allowed = 0
  413         for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
  414             (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
  415             sent = sent + 1
  416             if receivedQuery:
  417                 receivedQuery.id = query.id
  418                 self.assertEquals(query, receivedQuery)
  419                 self.assertEquals(expectedResponse, receivedResponse)
  420                 allowed = allowed + 1
  421             else:
  422                 # the query has not reached the responder,
  423                 # let's clear the response queue
  424                 self.clearToResponderQueue()
  425 
  426         # we might be already blocked, but we should have been able to send
  427         # at least self._dynBlockQPS queries
  428         self.assertGreaterEqual(allowed, self._dynBlockQPS)
  429 
  430         if allowed == sent:
  431         # wait for the maintenance function to run
  432             time.sleep(2)
  433 
  434         # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
  435         (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
  436         self.assertEquals(receivedResponse, None)
  437 
  438         # wait until we are not blocked anymore
  439         time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
  440 
  441         # this one should succeed
  442         (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
  443         receivedQuery.id = query.id
  444         self.assertEquals(query, receivedQuery)
  445         self.assertEquals(response, receivedResponse)
  446 
  447 class TestDynBlockQPS(DynBlocksTest):
  448 
  449     _dynBlockQPS = 10
  450     _dynBlockPeriod = 2
  451     _dynBlockDuration = 5
  452     _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
  453     _config_template = """
  454     function maintenance()
  455         addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
  456     end
  457     newServer{address="127.0.0.1:%s"}
  458     webserver("127.0.0.1:%s", "%s", "%s")
  459     """
  460 
  461     def testDynBlocksQRate(self):
  462         """
  463         Dyn Blocks: QRate
  464         """
  465         name = 'qrate.dynblocks.tests.powerdns.com.'
  466         self.doTestQRate(name)
  467 
  468 class TestDynBlockGroupQPS(DynBlocksTest):
  469 
  470     _dynBlockQPS = 10
  471     _dynBlockPeriod = 2
  472     _dynBlockDuration = 5
  473     _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
  474     _config_template = """
  475     local dbr = dynBlockRulesGroup()
  476     dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
  477 
  478     function maintenance()
  479         dbr:apply()
  480     end
  481     newServer{address="127.0.0.1:%s"}
  482     webserver("127.0.0.1:%s", "%s", "%s")
  483     """
  484 
  485     def testDynBlocksQRate(self):
  486         """
  487         Dyn Blocks (Group): QRate
  488         """
  489         name = 'qrate.group.dynblocks.tests.powerdns.com.'
  490         self.doTestQRate(name)
  491 
  492 
  493 class TestDynBlockQPSRefused(DynBlocksTest):
  494 
  495     _dynBlockQPS = 10
  496     _dynBlockPeriod = 2
  497     _dynBlockDuration = 5
  498     _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
  499     _config_template = """
  500     function maintenance()
  501         addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
  502     end
  503     setDynBlocksAction(DNSAction.Refused)
  504     newServer{address="127.0.0.1:%s"}
  505     """
  506 
  507     def testDynBlocksQRate(self):
  508         """
  509         Dyn Blocks: QRate refused
  510         """
  511         name = 'qraterefused.dynblocks.tests.powerdns.com.'
  512         self.doTestQRateRCode(name, dns.rcode.REFUSED)
  513 
  514 class TestDynBlockGroupQPSRefused(DynBlocksTest):
  515 
  516     _dynBlockQPS = 10
  517     _dynBlockPeriod = 2
  518     _dynBlockDuration = 5
  519     _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
  520     _config_template = """
  521     local dbr = dynBlockRulesGroup()
  522     dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
  523 
  524     function maintenance()
  525         dbr:apply()
  526     end
  527     setDynBlocksAction(DNSAction.Refused)
  528     newServer{address="127.0.0.1:%s"}
  529     """
  530 
  531     def testDynBlocksQRate(self):
  532         """
  533         Dyn Blocks (Group): QRate refused
  534         """
  535         name = 'qraterefused.group.dynblocks.tests.powerdns.com.'
  536         self.doTestQRateRCode(name, dns.rcode.REFUSED)
  537 
  538 class TestDynBlockQPSActionRefused(DynBlocksTest):
  539 
  540     _dynBlockQPS = 10
  541     _dynBlockPeriod = 2
  542     _dynBlockDuration = 5
  543     _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
  544     _config_template = """
  545     function maintenance()
  546         addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Refused)
  547     end
  548     setDynBlocksAction(DNSAction.Drop)
  549     newServer{address="127.0.0.1:%s"}
  550     """
  551 
  552     def testDynBlocksQRate(self):
  553         """
  554         Dyn Blocks: QRate refused (action)
  555         """
  556         name = 'qrateactionrefused.dynblocks.tests.powerdns.com.'
  557         self.doTestQRateRCode(name, dns.rcode.REFUSED)
  558 
  559 class TestDynBlockQPSActionNXD(DynBlocksTest):
  560 
  561     _dynBlockQPS = 10
  562     _dynBlockPeriod = 2
  563     _dynBlockDuration = 5
  564     _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
  565     _config_template = """
  566     function maintenance()
  567         addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Nxdomain)
  568     end
  569     setDynBlocksAction(DNSAction.Drop)
  570     newServer{address="127.0.0.1:%s"}
  571     """
  572 
  573     def testDynBlocksQRate(self):
  574         """
  575         Dyn Blocks: QRate NXD (action)
  576         """
  577         name = 'qrateactionnxd.dynblocks.tests.powerdns.com.'
  578         self.doTestQRateRCode(name, dns.rcode.NXDOMAIN)
  579 
  580 class TestDynBlockGroupQPSActionRefused(DynBlocksTest):
  581 
  582     _dynBlockQPS = 10
  583     _dynBlockPeriod = 2
  584     _dynBlockDuration = 5
  585     _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
  586     _config_template = """
  587     local dbr = dynBlockRulesGroup()
  588     dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Refused)
  589 
  590     function maintenance()
  591         dbr:apply()
  592     end
  593     setDynBlocksAction(DNSAction.Drop)
  594     newServer{address="127.0.0.1:%s"}
  595     """
  596 
  597     def testDynBlocksQRate(self):
  598         """
  599         Dyn Blocks (group): QRate refused (action)
  600         """
  601         name = 'qrateactionrefused.group.dynblocks.tests.powerdns.com.'
  602         self.doTestQRateRCode(name, dns.rcode.REFUSED)
  603 
  604 class TestDynBlockQPSActionTruncated(DNSDistTest):
  605 
  606     _dynBlockQPS = 10
  607     _dynBlockPeriod = 2
  608     _dynBlockDuration = 5
  609     _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
  610     _config_template = """
  611     function maintenance()
  612         addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Truncate)
  613     end
  614     setDynBlocksAction(DNSAction.Drop)
  615     newServer{address="127.0.0.1:%s"}
  616     """
  617 
  618     def testDynBlocksQRate(self):
  619         """
  620         Dyn Blocks: QRate truncated (action)
  621         """
  622         name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.'
  623         query = dns.message.make_query(name, 'A', 'IN')
  624         response = dns.message.make_response(query)
  625         rrset = dns.rrset.from_text(name,
  626                                     60,
  627                                     dns.rdataclass.IN,
  628                                     dns.rdatatype.A,
  629                                     '192.0.2.1')
  630         response.answer.append(rrset)
  631         truncatedResponse = dns.message.make_response(query)
  632         truncatedResponse.flags |= dns.flags.TC
  633 
  634         allowed = 0
  635         sent = 0
  636         for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
  637             (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
  638             sent = sent + 1
  639             if receivedQuery:
  640                 receivedQuery.id = query.id
  641                 self.assertEquals(query, receivedQuery)
  642                 self.assertEquals(receivedResponse, response)
  643                 allowed = allowed + 1
  644             else:
  645                 self.assertEquals(receivedResponse, truncatedResponse)
  646                 # the query has not reached the responder,
  647                 # let's clear the response queue
  648                 self.clearToResponderQueue()
  649 
  650         # we might be already truncated, but we should have been able to send
  651         # at least self._dynBlockQPS queries
  652         self.assertGreaterEqual(allowed, self._dynBlockQPS)
  653 
  654         if allowed == sent:
  655             # wait for the maintenance function to run
  656             time.sleep(2)
  657 
  658         # we should now be 'truncated' for up to self._dynBlockDuration + self._dynBlockPeriod
  659         (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
  660         self.assertEquals(receivedResponse, truncatedResponse)
  661 
  662         # check over TCP, which should not be truncated
  663         (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
  664 
  665         self.assertEquals(query, receivedQuery)
  666         self.assertEquals(receivedResponse, response)
  667 
  668         # wait until we are not blocked anymore
  669         time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
  670 
  671         # this one should succeed
  672         (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
  673         receivedQuery.id = query.id
  674         self.assertEquals(query, receivedQuery)
  675         self.assertEquals(response, receivedResponse)
  676 
  677         allowed = 0
  678         sent = 0
  679         # again, over TCP this time, we should never get truncated!
  680         for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
  681             (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
  682             sent = sent + 1
  683             self.assertEquals(query, receivedQuery)
  684             self.assertEquals(receivedResponse, response)
  685             receivedQuery.id = query.id
  686             allowed = allowed + 1
  687 
  688         self.assertEquals(allowed, sent)
  689 
  690 class TestDynBlockServFails(DynBlocksTest):
  691 
  692     _dynBlockQPS = 10
  693     _dynBlockPeriod = 2
  694     _dynBlockDuration = 5
  695     _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
  696     _config_template = """
  697     function maintenance()
  698         addDynBlocks(exceedServFails(%d, %d), "Exceeded servfail rate", %d)
  699     end
  700     newServer{address="127.0.0.1:%s"}
  701     """
  702 
  703     def testDynBlocksServFailRate(self):
  704         """
  705         Dyn Blocks: Server Failure Rate
  706         """
  707         name = 'servfailrate.dynblocks.tests.powerdns.com.'
  708         self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
  709 
  710 class TestDynBlockWhitelist(DynBlocksTest):
  711 
  712     _dynBlockQPS = 10
  713     _dynBlockPeriod = 2
  714     _dynBlockDuration = 5
  715     _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
  716     _config_template = """
  717     whitelisted = false
  718     function maintenance()
  719         toBlock = exceedQRate(%d, %d)
  720         for addr, count in pairs(toBlock) do
  721             if addr:toString() == "127.0.0.1" then
  722                 whitelisted = true
  723                 toBlock[addr] = nil
  724             end
  725         end
  726         addDynBlocks(toBlock, "Exceeded query rate", %d)
  727     end
  728 
  729     function spoofrule(dq)
  730         if (whitelisted)
  731         then
  732                 return DNSAction.Spoof, "192.0.2.42"
  733         else
  734                 return DNSAction.None, ""
  735         end
  736     end
  737     addAction("whitelisted-test.dynblocks.tests.powerdns.com.", LuaAction(spoofrule))
  738 
  739     newServer{address="127.0.0.1:%s"}
  740     """
  741 
  742     def testWhitelisted(self):
  743         """
  744         Dyn Blocks: Whitelisted from the dynamic blocks
  745         """
  746         name = 'whitelisted.dynblocks.tests.powerdns.com.'
  747         query = dns.message.make_query(name, 'A', 'IN')
  748         response = dns.message.make_response(query)
  749         rrset = dns.rrset.from_text(name,
  750                                     60,
  751                                     dns.rdataclass.IN,
  752                                     dns.rdatatype.A,
  753                                     '192.0.2.1')
  754         response.answer.append(rrset)
  755 
  756         allowed = 0
  757         sent = 0
  758         for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
  759             (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
  760             sent = sent + 1
  761             if receivedQuery:
  762                 receivedQuery.id = query.id
  763                 self.assertEquals(query, receivedQuery)
  764                 self.assertEquals(response, receivedResponse)
  765                 allowed = allowed + 1
  766             else:
  767                 # the query has not reached the responder,
  768                 # let's clear the response queue
  769                 self.clearToResponderQueue()
  770 
  771         # we should not have been blocked
  772         self.assertEqual(allowed, sent)
  773 
  774         # wait for the maintenance function to run
  775         time.sleep(2)
  776 
  777         # we should still not be blocked
  778         (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
  779         receivedQuery.id = query.id
  780         self.assertEquals(query, receivedQuery)
  781         self.assertEquals(receivedResponse, receivedResponse)
  782 
  783         # check that we would have been blocked without the whitelisting
  784         name = 'whitelisted-test.dynblocks.tests.powerdns.com.'
  785         query = dns.message.make_query(name, 'A', 'IN')
  786         # dnsdist set RA = RD for spoofed responses
  787         query.flags &= ~dns.flags.RD
  788         expectedResponse = dns.message.make_response(query)
  789         rrset = dns.rrset.from_text(name,
  790                                     60,
  791                                     dns.rdataclass.IN,
  792                                     dns.rdatatype.A,
  793                                     '192.0.2.42')
  794         expectedResponse.answer.append(rrset)
  795         (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
  796         self.assertEquals(receivedResponse, expectedResponse)
  797 
  798 class TestDynBlockGroupServFails(DynBlocksTest):
  799 
  800     _dynBlockQPS = 10
  801     _dynBlockPeriod = 2
  802     _dynBlockDuration = 5
  803     _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
  804     _config_template = """
  805     local dbr = dynBlockRulesGroup()
  806     dbr:setRCodeRate(DNSRCode.SERVFAIL, %d, %d, "Exceeded query rate", %d)
  807 
  808     function maintenance()
  809         dbr:apply()
  810     end
  811 
  812     newServer{address="127.0.0.1:%s"}
  813     """
  814 
  815     def testDynBlocksServFailRate(self):
  816         """
  817         Dyn Blocks (group): Server Failure Rate
  818         """
  819         name = 'servfailrate.group.dynblocks.tests.powerdns.com.'
  820         self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
  821 
  822 class TestDynBlockResponseBytes(DynBlocksTest):
  823 
  824     _dynBlockBytesPerSecond = 200
  825     _dynBlockPeriod = 2
  826     _dynBlockDuration = 5
  827     _consoleKey = DNSDistTest.generateConsoleKey()
  828     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
  829     _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
  830     _config_template = """
  831     setKey("%s")
  832     controlSocket("127.0.0.1:%s")
  833     function maintenance()
  834         addDynBlocks(exceedRespByterate(%d, %d), "Exceeded response byterate", %d)
  835     end
  836     newServer{address="127.0.0.1:%s"}
  837     """
  838 
  839     def testDynBlocksResponseByteRate(self):
  840         """
  841         Dyn Blocks: Response Byte Rate
  842         """
  843         name = 'responsebyterate.dynblocks.tests.powerdns.com.'
  844         self.doTestResponseByteRate(name)
  845 
  846 class TestDynBlockGroupResponseBytes(DynBlocksTest):
  847 
  848     _dynBlockBytesPerSecond = 200
  849     _dynBlockPeriod = 2
  850     _dynBlockDuration = 5
  851     _consoleKey = DNSDistTest.generateConsoleKey()
  852     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
  853     _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
  854     _config_template = """
  855     setKey("%s")
  856     controlSocket("127.0.0.1:%s")
  857     local dbr = dynBlockRulesGroup()
  858     dbr:setResponseByteRate(%d, %d, "Exceeded query rate", %d)
  859 
  860     function maintenance()
  861         dbr:apply()
  862     end
  863 
  864     newServer{address="127.0.0.1:%s"}
  865     """
  866 
  867     def testDynBlocksResponseByteRate(self):
  868         """
  869         Dyn Blocks (group) : Response Byte Rate
  870         """
  871         name = 'responsebyterate.group.dynblocks.tests.powerdns.com.'
  872         self.doTestResponseByteRate(name)
  873 
  874 class TestDynBlockGroupExcluded(DynBlocksTest):
  875 
  876     _dynBlockQPS = 10
  877     _dynBlockPeriod = 2
  878     _dynBlockDuration = 5
  879     _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
  880     _config_template = """
  881     local dbr = dynBlockRulesGroup()
  882     dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
  883     dbr:excludeRange("127.0.0.1/32")
  884 
  885     function maintenance()
  886         dbr:apply()
  887     end
  888 
  889     newServer{address="127.0.0.1:%s"}
  890     """
  891 
  892     def testExcluded(self):
  893         """
  894         Dyn Blocks (group) : Excluded from the dynamic block rules
  895         """
  896         name = 'excluded.group.dynblocks.tests.powerdns.com.'
  897         query = dns.message.make_query(name, 'A', 'IN')
  898         response = dns.message.make_response(query)
  899         rrset = dns.rrset.from_text(name,
  900                                     60,
  901                                     dns.rdataclass.IN,
  902                                     dns.rdatatype.A,
  903                                     '192.0.2.1')
  904         response.answer.append(rrset)
  905 
  906         allowed = 0
  907         sent = 0
  908         for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
  909             (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
  910             sent = sent + 1
  911             if receivedQuery:
  912                 receivedQuery.id = query.id
  913                 self.assertEquals(query, receivedQuery)
  914                 self.assertEquals(response, receivedResponse)
  915                 allowed = allowed + 1
  916             else:
  917                 # the query has not reached the responder,
  918                 # let's clear the response queue
  919                 self.clearToResponderQueue()
  920 
  921         # we should not have been blocked
  922         self.assertEqual(allowed, sent)
  923 
  924         # wait for the maintenance function to run
  925         time.sleep(2)
  926 
  927         # we should still not be blocked
  928         (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
  929         receivedQuery.id = query.id
  930         self.assertEquals(query, receivedQuery)
  931         self.assertEquals(receivedResponse, receivedResponse)
  932 
  933 class TestDynBlockGroupNoOp(DynBlocksTest):
  934 
  935     _dynBlockQPS = 10
  936     _dynBlockPeriod = 2
  937     _dynBlockDuration = 5
  938     _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
  939     _config_template = """
  940     local dbr = dynBlockRulesGroup()
  941     dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.NoOp)
  942 
  943     function maintenance()
  944         dbr:apply()
  945     end
  946 
  947     newServer{address="127.0.0.1:%s"}
  948     webserver("127.0.0.1:%s", "%s", "%s")
  949     """
  950 
  951     def testNoOp(self):
  952         """
  953         Dyn Blocks (group) : NoOp
  954         """
  955         name = 'noop.group.dynblocks.tests.powerdns.com.'
  956         query = dns.message.make_query(name, 'A', 'IN')
  957         response = dns.message.make_response(query)
  958         rrset = dns.rrset.from_text(name,
  959                                     60,
  960                                     dns.rdataclass.IN,
  961                                     dns.rdatatype.A,
  962                                     '192.0.2.1')
  963         response.answer.append(rrset)
  964 
  965         allowed = 0
  966         sent = 0
  967         for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
  968             (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
  969             sent = sent + 1
  970             if receivedQuery:
  971                 receivedQuery.id = query.id
  972                 self.assertEquals(query, receivedQuery)
  973                 self.assertEquals(response, receivedResponse)
  974                 allowed = allowed + 1
  975             else:
  976                 # the query has not reached the responder,
  977                 # let's clear the response queue
  978                 self.clearToResponderQueue()
  979 
  980         # a dynamic rule should have been inserted, but the queries should still go on
  981         self.assertEqual(allowed, sent)
  982 
  983         # wait for the maintenance function to run
  984         time.sleep(2)
  985 
  986         # the rule should still be present, but the queries pass through anyway
  987         (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
  988         receivedQuery.id = query.id
  989         self.assertEquals(query, receivedQuery)
  990         self.assertEquals(receivedResponse, receivedResponse)
  991 
  992         # check that the rule has been inserted
  993         self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent)
  994 
  995 class TestDynBlockGroupWarning(DynBlocksTest):
  996 
  997     _dynBlockWarningQPS = 5
  998     _dynBlockQPS = 20
  999     _dynBlockPeriod = 2
 1000     _dynBlockDuration = 5
 1001     _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_dynBlockWarningQPS', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
 1002     _config_template = """
 1003     local dbr = dynBlockRulesGroup()
 1004     dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Drop, %d)
 1005 
 1006     function maintenance()
 1007         dbr:apply()
 1008     end
 1009 
 1010     newServer{address="127.0.0.1:%s"}
 1011     webserver("127.0.0.1:%s", "%s", "%s")
 1012     """
 1013 
 1014     def testWarning(self):
 1015         """
 1016         Dyn Blocks (group) : Warning
 1017         """
 1018         name = 'warning.group.dynblocks.tests.powerdns.com.'
 1019         query = dns.message.make_query(name, 'A', 'IN')
 1020         response = dns.message.make_response(query)
 1021         rrset = dns.rrset.from_text(name,
 1022                                     60,
 1023                                     dns.rdataclass.IN,
 1024                                     dns.rdatatype.A,
 1025                                     '192.0.2.1')
 1026         response.answer.append(rrset)
 1027 
 1028         allowed = 0
 1029         sent = 0
 1030         for _ in range((self._dynBlockWarningQPS * self._dynBlockPeriod) + 1):
 1031             (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
 1032             sent = sent + 1
 1033             if receivedQuery:
 1034                 receivedQuery.id = query.id
 1035                 self.assertEquals(query, receivedQuery)
 1036                 self.assertEquals(response, receivedResponse)
 1037                 allowed = allowed + 1
 1038             else:
 1039                 # the query has not reached the responder,
 1040                 # let's clear the response queue
 1041                 self.clearToResponderQueue()
 1042 
 1043         # a dynamic rule should have been inserted, but the queries should
 1044         # still go on because we are still at warning level
 1045         self.assertEqual(allowed, sent)
 1046 
 1047         # wait for the maintenance function to run
 1048         time.sleep(2)
 1049 
 1050         # the rule should still be present, but the queries pass through anyway
 1051         (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
 1052         receivedQuery.id = query.id
 1053         self.assertEquals(query, receivedQuery)
 1054         self.assertEquals(receivedResponse, receivedResponse)
 1055 
 1056         # check that the rule has been inserted
 1057         self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent)
 1058 
 1059         self.doTestQRate(name)