]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Merge pull request #7496 from rgacogne/auth-catch-invalid-slave-soa auth-4.2.0-beta1
authoraerique <aerique@xs4all.nl>
Wed, 27 Feb 2019 09:52:52 +0000 (10:52 +0100)
committerGitHub <noreply@github.com>
Wed, 27 Feb 2019 09:52:52 +0000 (10:52 +0100)
auth: Catch exception when parsing a zone's SOA in getUnfreshSlaveInfos()

84 files changed:
docs/backends/remote.rst
docs/changelog/4.2.rst
docs/http-api/statistics.rst
docs/http-api/swagger/authoritative-api-swagger.yaml
docs/lua-records/functions.rst
docs/lua-records/reference/misc.rst
docs/manpages/ixfrdist.yml.5.rst
modules/remotebackend/remotebackend.cc
modules/remotebackend/remotebackend.hh
modules/remotebackend/test-remotebackend.cc
modules/remotebackend/unittest.rb
pdns/common_startup.cc
pdns/dnsdist-console.cc
pdns/dnsdist-ecs.cc
pdns/dnsdist-ecs.hh
pdns/dnsdist-lua-actions.cc
pdns/dnsdist-lua-rules.cc
pdns/dnsdist-lua.cc
pdns/dnsdist-snmp.cc
pdns/dnsdist-web.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/DNSDIST-MIB.txt
pdns/dnsdistdist/configure.ac
pdns/dnsdistdist/dnsdist-rules.hh
pdns/dnsdistdist/docs/reference/config.rst
pdns/dnsdistdist/docs/rules-actions.rst
pdns/dnssecinfra.cc
pdns/filterpo.cc
pdns/filterpo.hh
pdns/ixfr.cc
pdns/ixfrdist.cc
pdns/ixfrdist.example.yml
pdns/json.cc
pdns/lua-base4.cc
pdns/lua-record.cc
pdns/minicurl.cc
pdns/minicurl.hh
pdns/misc.cc
pdns/misc.hh
pdns/pdns_recursor.cc
pdns/rec-lua-conf.cc
pdns/rec-lua-conf.hh
pdns/rec-snmp.cc
pdns/rec_channel_rec.cc
pdns/recpacketcache.cc
pdns/recpacketcache.hh
pdns/recursor_cache.cc
pdns/recursor_cache.hh
pdns/recursordist/RECURSOR-MIB.txt
pdns/recursordist/docs/lua-config/rpz.rst
pdns/recursordist/docs/lua-scripting/dq.rst
pdns/recursordist/docs/lua-scripting/functions.rst
pdns/recursordist/docs/settings.rst
pdns/recursordist/negcache.cc
pdns/recursordist/negcache.hh
pdns/recursordist/test-syncres_cc.cc
pdns/reczones.cc
pdns/responsestats-auth.cc
pdns/responsestats.cc
pdns/responsestats.hh
pdns/rpzloader.cc
pdns/rpzloader.hh
pdns/speedtest.cc
pdns/syncres.cc
pdns/syncres.hh
pdns/test-dnsdist_cc.cc
pdns/test-ixfr_cc.cc
pdns/test-recpacketcache_cc.cc
pdns/ws-api.cc
pdns/ws-auth.cc
regression-tests.api/test_Servers.py
regression-tests.api/test_Zones.py
regression-tests.dnsdist/.gitignore
regression-tests.dnsdist/dnsdisttests.py
regression-tests.dnsdist/runtests
regression-tests.dnsdist/test_Advanced.py
regression-tests.nobackend/counters/command
regression-tests.recursor-dnssec/basicDNSSEC.py
regression-tests.recursor-dnssec/recursortests.py
regression-tests.recursor-dnssec/test_Flags.py
regression-tests.recursor-dnssec/test_Lua.py
regression-tests.recursor-dnssec/test_RPZ.py
regression-tests.recursor-dnssec/test_TTL.py [new file with mode: 0644]

index b00d372b3ab97710eae9f0cccfd9aa1c6d86ec14..54d0a03ba0b347fa91866cf8c47f8a6b7d5e0993 100644 (file)
@@ -1508,6 +1508,51 @@ Response:
 
     {"result":[{"qtype":"A", "qname":"www.example.com", "content":"203.0.113.2", "ttl": 60}]}
 
+
+``getUpdatedMasters``
+~~~~~~~~~~~~~~~~~
+
+Used to find out any updates to master domains. This is used to trigger notifications in master mode.
+
+-  Mandatory: no
+-  Parameters: none
+-  Reply: array of DomainInfo
+
+Example JSON/RPC
+''''''''''''''''
+
+Query:
+
+::
+
+    {"method": "getUpdatedMasters", "parameters": {}}
+
+Response:
+
+::
+
+    {"result":[{"id":1,"zone":"unit.test.","masters":["10.0.0.1"],"notified_serial":2,"serial":2,"last_check":1464693331,"kind":"master"}]}
+
+Example HTTP/RPC
+''''''''''''''''
+
+Query:
+
+.. code-block:: http
+
+    GET /dnsapi/getUpdatedMasters HTTP/1.1
+
+Response:
+
+.. code-block:: http
+
+    HTTP/1.1 200 OK
+    Content-Type: text/javascript; charset=utf-8
+    Content-Length: 135
+    {"result":[{"id":1,"zone":"unit.test.","masters":["10.0.0.1"],"notified_serial":2,"serial":2,"last_check":1464693331,"kind":"master"}]}
+
+
+
 Examples
 --------
 
index 2e069cf1c9ad25d07da72c6a44bbf4d7f4b63f8f..567ccb76de33e840145180bc4acb89d99049f00e 100644 (file)
@@ -881,3 +881,10 @@ Changelogs for 4.2.x
     :tickets: 6028
 
     Forbid label compression in ALIAS wire format.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 7359
+    :tickets: 7357
+
+    API: Add response-by-qtype and response-by-rcode on /statistics endpoint
index c17e1d097920b838c30aca87d145ae850bf5568a..23975cd514d9eb01d79f48eaed021209c07c14c2 100644 (file)
@@ -9,6 +9,19 @@ Endpoints
 
 Objects
 -------
+
+The Statistics endpoint returns an array of objects that can be StatisticItem, MapStatisticItem or RingStatisticItem :
+
 .. openapi:: swagger/authoritative-api-swagger.yaml
   :definitions: StatisticItem
 
+.. openapi:: swagger/authoritative-api-swagger.yaml
+  :definitions: MapStatisticItem
+
+.. openapi:: swagger/authoritative-api-swagger.yaml
+  :definitions: RingStatisticItem
+
+Both MapStatisticItem and RingStatisticItem objects contains an array of SimpleStatisticItem
+
+.. openapi:: swagger/authoritative-api-swagger.yaml
+  :definitions: SimpleStatisticItem
\ No newline at end of file
index bb199f302d8b0194cf3a2eed4d59fa348e13d3a3..8eed2164f4e41a3a8993beb616588ec78389c817 100644 (file)
@@ -389,7 +389,7 @@ paths:
   '/servers/{server_id}/statistics':
     get:
       summary: 'Query statistics.'
-      description: 'Query PowerDNS internal statistics. Returns a list of BaseStatisticItem derived elements.'
+      description: 'Query PowerDNS internal statistics.'
       operationId: getStats
       tags:
         - stats
@@ -405,10 +405,6 @@ paths:
           schema:
             type: array
             items:
-            # these can be commented because the swagger code generator fails on them
-            # and replaced with
-            # type: string
-            # or something like that
             - $ref: '#/definitions/StatisticItem'
             - $ref: '#/definitions/MapStatisticItem'
             - $ref: '#/definitions/RingStatisticItem'
@@ -436,6 +432,11 @@ paths:
           required: true
           description: 'Maximum number of entries to return'
           type: integer
+        - name: object_type
+          in: query
+          required: false
+          description: 'Type of data to search for, one of “all”, “zone”, “record”, “comment”'
+          type: string
       responses:
         '200':
           description: Returns a JSON array with results
@@ -1064,69 +1065,62 @@ definitions:
         type: string
         description: 'The value of setting name'
 
-  BaseStatisticItem:
-    title: BaseStatisticItem
+  SimpleStatisticItem:
+    title: SimpleStatisticItem
+    type: object
     properties:
       name:
-        type: string
-        description: 'The name of this item (e.g. ‘uptime’)'
+          type: string
+          description: 'Item name'
+      value:
+          type: string
+          description: 'Item value'
 
   StatisticItem:
     title: StatisticItem
-    allOf:
-    - $ref: "#/definitions/BaseStatisticItem"
-    - properties:
-        type:
-          enum: [StatisticItem]
-          description: 'set to "StatisticItem"'
-        value:
-          type: string
-          description: 'The value of item'
+    properties:
+      name:
+        type: string
+        description: 'Item name'
+      type:
+        type: string
+        description: 'set to "StatisticItem"'
+      value:
+        type: string
+        description: 'Item value'
 
   MapStatisticItem:
     title: MapStatisticItem
-    allOf:
-    - $ref: "#/definitions/BaseStatisticItem"
-    - properties:
-        type:
-          enum: [MapStatisticItem]
-          description: 'set to "MapStatisticItem"'
-        value:
-          type: array
-          description: 'named statistic values'
-          items:
-            type: array
-            properties:
-              name:
-                type: string
-                description: 'item name'
-              value:
-                type: string
-                description: 'item value'
+    properties:
+      name:
+        type: string
+        description: 'Item name'
+      type:
+        type: string
+        description: 'Set to "MapStatisticItem"'
+      value:
+        type: array
+        description: 'Named values'
+        items:
+          $ref: '#/definitions/SimpleStatisticItem'
 
   RingStatisticItem:
     title: RingStatisticItem
-    allOf:
-    - $ref: "#/definitions/BaseStatisticItem"
-    - properties:
-        type:
-          enum: [RingStatisticItem]
-          description: 'set to "RingStatisticItem"'
-        size:
-          type: integer
-          description: 'for RingStatisticItem objects, the size of the ring'
-        value:
-          type: array
-          description: 'named ring statistic values'
-          items:
-            type: array
-            properties:
-              name:
-                type: string
-                description: 'item name'
-              value:
-                type: string
-                description: 'item value'
+    properties:
+      name:
+        type: string
+        description: 'Item name'
+      type:
+        type: string
+        description: 'Set to "RingStatisticItem"'
+      size:
+        type: integer
+        description: 'Ring size'
+      value:
+        type: array
+        description: 'Named values'
+        items:
+          $ref: '#/definitions/SimpleStatisticItem'
 
   SearchResultZone:
     title: SearchResultZone
index b655cb144974aca2a4e341ecebbc43cc42c0f06d..d180c76b5230121c603884c8c24baad117ede1e6 100644 (file)
@@ -69,6 +69,7 @@ Record creation functions
   - ``backupSelector``: used to pick the IP address from list of all candidates if all addresses are down. Choices include 'pickclosest', 'random', 'hashed', 'all' (default to 'random').
   - ``source``: Source IP address to check from
   - ``stringmatch``: check ``url`` for this string, only declare 'up' if found
+  - ``useragent``: Set the HTTP "User-Agent" header in the requests. By default it is set to "PowerDNS Authoritative Server"
 
   An example of IP address sets:
 
index 247f25049f3f6c4fa6365edc8dfe0947d20f6fb6..89e1f0f6ec52c76278f0a1f6957728eabc24b8d3 100644 (file)
@@ -20,3 +20,9 @@ Other functions
    - `pdns.loglevels.Notice`
    - `pdns.loglevels.Warning`
    - `pdns.loglevels.Error`
+
+.. function:: pdnsrandom([maximum])
+
+  Get a random number.
+
+  :param int maximum: The largest number to return. This is 2^32-1 by default.
index 6ad3fd132f80342f65dfb0a4e3ac114c7e1e6138..22b48d80f5707f5904129cd3ca5df00552d2f30d 100644 (file)
@@ -54,6 +54,11 @@ Options
   Entries without a netmask will be interpreted as a single address.
   By default, the ACL is set is ``127.0.0.0/8`` and ``::1/128``.
 
+:axfr-max-records:
+  Maximum number of records allowed in an AXFR transaction requested by :program:`ixfrdist`.
+  This may prevent untrusted sources from using all the process memory.
+  By default, this setting is ``0``, which means "unlimited".
+
 :axfr-timeout:
   Timeout in seconds an AXFR transaction requested by :program:`ixfrdist` may take.
   Increase this when the network to the authoritative servers is slow or the domains are very large and you experience timeouts.
index e90ac21f078011f9a9e1d586e15682c1ef584dca..f3a35157250c6d48807a40e978053a595ef8b1de 100644 (file)
@@ -908,6 +908,27 @@ void RemoteBackend::getAllDomains(vector<DomainInfo> *domains, bool include_disa
   }
 }
 
+void RemoteBackend::getUpdatedMasters(vector<DomainInfo>* domains)
+{
+  Json query = Json::object{
+   { "method", "getUpdatedMasters" },
+   { "parameters", Json::object{ } },
+  };
+
+  Json answer;
+  if (this->send(query) == false || this->recv(answer) == false)
+    return;
+
+  if (answer["result"].is_array() == false)
+    return;
+
+  for(const auto& row: answer["result"].array_items()) {
+    DomainInfo di;
+    this->parseDomainInfo(row, di);
+    domains->push_back(di);
+  }
+}
+
 DNSBackend *RemoteBackend::maker()
 {
    try {
index 7972e1ced6ac2d9291120944260227f404c6fda6..0c64a7deedf4a25a2ad667129a0969ce93af1daf 100644 (file)
@@ -186,6 +186,7 @@ class RemoteBackend : public DNSBackend
   bool searchRecords(const string &pattern, int maxResults, vector<DNSResourceRecord>& result) override;
   bool searchComments(const string &pattern, int maxResults, vector<Comment>& result) override;
   void getAllDomains(vector<DomainInfo> *domains, bool include_disabled=false) override;
+  void getUpdatedMasters(vector<DomainInfo>* domains) override;
 
   static DNSBackend *maker();
 
index 3049127ef514647091c9f7d1de2ffa374c796689..266e79d0a7dc0a9e7899ce6db4505c74ee3634e0 100644 (file)
@@ -323,4 +323,21 @@ BOOST_AUTO_TEST_CASE(test_method_directBackendCmd) {
    BOOST_CHECK_EQUAL(be->directBackendCmd("PING 1234"), "PING 1234");
 }
 
+BOOST_AUTO_TEST_CASE(test_method_getUpdatedMasters) {
+   DomainInfo di;
+   BOOST_TEST_MESSAGE("Testing getUpdatedMasters method");
+   vector<DomainInfo> result;
+
+   be->getUpdatedMasters(&result);
+
+   BOOST_CHECK(result.size() > 0);
+
+   di = result[0];
+   BOOST_CHECK_EQUAL(di.zone.toString(), "master.test.");
+   BOOST_CHECK_EQUAL(di.serial, 2);
+   BOOST_CHECK_EQUAL(di.notified_serial, 2);
+   BOOST_CHECK_EQUAL(di.kind, DomainInfo::Master);
+   BOOST_CHECK_EQUAL(di.backend, be);
+}
+
 BOOST_AUTO_TEST_SUITE_END();
index 2f69350bd4c01354865e1451263ef9fab4b4a0ee..27ab3d50bc0e30864ce9acb2ff90d3210f44a870 100644 (file)
@@ -171,6 +171,17 @@ class Handler
                :kind => 'native'
        }]
      end
+     if args["name"] == "master.test."
+       return [{
+               :id => 2,
+               :zone => "master.test.",
+               :masters => ["10.0.0.1"],
+               :notified_serial => $notified_serial,
+               :serial => $notified_serial,
+               :last_check => Time.now.to_i,
+               :kind => 'master'
+       }]
+     end
      [false]
    end
 
@@ -252,5 +263,9 @@ class Handler
    def do_getalldomains(args)
      [do_getdomaininfo({'name'=>'unit.test.'})]
    end
+
+   def do_getupdatedmasters()
+     [do_getdomaininfo({'name'=>'master.test.'})]
+   end
 end
 
index ccdfbe8b25b429e9e29cc7e7e1e45e6d14ce7836..e6c487169aaafa32ab024f3e32ac4c42ea2e1165 100644 (file)
@@ -317,6 +317,7 @@ void declareStats(void)
 
   S.declare("uptime", "Uptime of process in seconds", uptimeOfProcess);
   S.declare("real-memory-usage", "Actual unique use of memory in bytes (approx)", getRealMemoryUsage);
+  S.declare("special-memory-usage", "Actual unique use of memory in bytes (approx)", getSpecialMemoryUsage);
   S.declare("fd-usage", "Number of open filedescriptors", getOpenFileDescriptors);
 #ifdef __linux__
   S.declare("udp-recvbuf-errors", "UDP 'recvbuf' errors", udpErrorStats);
index 01458399d6f946a76c0c0be8e09f496739aeb986..80e31b10d1c1f633d43b98439fc01a893f7eb37f 100644 (file)
@@ -374,7 +374,9 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "DSTPortRule", true, "port", "matches questions received to the destination port specified" },
   { "dumpStats", true, "", "print all statistics we gather" },
   { "dynBlockRulesGroup", true, "", "return a new DynBlockRulesGroup object" },
+  { "EDNSVersionRule", true, "version", "matches queries with the specified EDNS version" },
   { "EDNSOptionRule", true, "optcode", "matches queries with the specified EDNS0 option present" },
+  { "ERCodeAction", true, "ercode", "Reply immediately by turning the query into a response with the specified EDNS extended rcode" },
   { "ERCodeRule", true, "rcode", "matches responses with the specified extended rcode (EDNS0)" },
   { "exceedNXDOMAINs", true, "rate, seconds", "get set of addresses that exceed `rate` NXDOMAIN/s over `seconds` seconds" },
   { "exceedQRate", true, "rate, seconds", "get set of address that exceed `rate` queries/s over `seconds` seconds" },
@@ -423,6 +425,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "QNameRule", true, "qname", "matches queries with the specified qname" },
   { "QNameWireLengthRule", true, "min, max", "matches if the qname's length on the wire is less than `min` or more than `max` bytes" },
   { "QTypeRule", true, "qtype", "matches queries with the specified qtype" },
+  { "RCodeAction", true, "rcode", "Reply immediately by turning the query into a response with the specified rcode" },
   { "RCodeRule", true, "rcode", "matches responses with the specified rcode" },
   { "RegexRule", true, "regex", "matches the query name against the supplied regex" },
   { "registerDynBPFFilter", true, "DynBPFFilter", "register this dynamic BPF filter into the web interface so that its counters are displayed" },
@@ -437,6 +440,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
   { "setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us" },
   { "setAddEDNSToSelfGeneratedResponses", true, "add", "set whether to add EDNS to self-generated responses, provided that the initial query had EDNS" },
+  { "setAllowEmptyResponse", true, "allow", "Set to true (defaults to false) to allow empty responses (qdcount=0) with a NoError or NXDomain rcode (default) from backends" },
   { "setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API" },
   { "setConsoleACL", true, "{netmask, netmask}", "replace the console ACL set with these netmasks" },
   { "setConsoleConnectionsLogging", true, "enabled", "whether to log the opening and closing of console connections" },
index dbb6cad589d85d2c0fa0e8a8bb7e94d6e3fd418a..56422e48a8a992ca5cceb60be62598c072924144 100644 (file)
@@ -244,12 +244,12 @@ void generateECSOption(const ComboAddress& source, string& res, uint16_t ECSPref
   generateEDNSOption(EDNSOptionCode::ECS, payload, res);
 }
 
-void generateOptRR(const std::string& optRData, string& res, uint16_t udpPayloadSize, bool dnssecOK)
+void generateOptRR(const std::string& optRData, string& res, uint16_t udpPayloadSize, uint8_t ednsrcode, bool dnssecOK)
 {
   const uint8_t name = 0;
   dnsrecordheader dh;
   EDNS0Record edns0;
-  edns0.extRCode = 0;
+  edns0.extRCode = ednsrcode;
   edns0.version = 0;
   edns0.extFlags = dnssecOK ? htons(EDNS_HEADER_FLAG_DO) : 0;
 
@@ -358,7 +358,7 @@ static bool addEDNSWithECS(char* const packet, size_t const packetSize, uint16_t
   /* we need to add a EDNS0 RR with one EDNS0 ECS option, fixing the AR count */
   string EDNSRR;
   struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(packet);
-  generateOptRR(newECSOption, EDNSRR, g_EdnsUDPPayloadSize, false);
+  generateOptRR(newECSOption, EDNSRR, g_EdnsUDPPayloadSize, 0, false);
 
   /* does it fit in the existing buffer? */
   if (packetSize - *len <= EDNSRR.size()) {
@@ -626,14 +626,14 @@ int rewriteResponseWithoutEDNSOption(const std::string& initialPacket, const uin
   return 0;
 }
 
-bool addEDNS(dnsheader* dh, uint16_t& len, const size_t size, bool dnssecOK, uint16_t payloadSize)
+bool addEDNS(dnsheader* dh, uint16_t& len, const size_t size, bool dnssecOK, uint16_t payloadSize, uint8_t ednsrcode)
 {
   if (dh->arcount != 0) {
     return false;
   }
 
   std::string optRecord;
-  generateOptRR(std::string(), optRecord, payloadSize, dnssecOK);
+  generateOptRR(std::string(), optRecord, payloadSize, ednsrcode, dnssecOK);
 
   if (optRecord.size() >= size || (size - optRecord.size()) < len) {
     return false;
@@ -679,7 +679,7 @@ bool addEDNSToQueryTurnedResponse(DNSQuestion& dq)
 
   if (g_addEDNSToSelfGeneratedResponses) {
     /* now we need to add a new OPT record */
-    return addEDNS(dq.dh, dq.len, dq.size, dnssecOK, g_PayloadSizeSelfGenAnswers);
+    return addEDNS(dq.dh, dq.len, dq.size, dnssecOK, g_PayloadSizeSelfGenAnswers, dq.ednsRCode);
   }
 
   /* otherwise we are just fine */
index dd0a7829312d06a1f8730b59fed1d34ee00ff017..7c3739f443c2546c46101c4935169c9207c48496 100644 (file)
@@ -26,13 +26,13 @@ extern uint16_t g_PayloadSizeSelfGenAnswers;
 
 int rewriteResponseWithoutEDNS(const std::string& initialPacket, vector<uint8_t>& newContent);
 int locateEDNSOptRR(const std::string& packet, uint16_t * optStart, size_t * optLen, bool * last);
-void generateOptRR(const std::string& optRData, string& res, uint16_t udpPayloadSize, bool dnssecOK);
+void generateOptRR(const std::string& optRData, string& res, uint16_t udpPayloadSize, uint8_t ednsrcode, bool dnssecOK);
 void generateECSOption(const ComboAddress& source, string& res, uint16_t ECSPrefixLength);
 int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove);
 int rewriteResponseWithoutEDNSOption(const std::string& initialPacket, const uint16_t optionCodeToSkip, vector<uint8_t>& newContent);
 int getEDNSOptionsStart(const char* packet, const size_t offset, const size_t len, uint16_t* optRDPosition, size_t * remaining);
 bool isEDNSOptionInOpt(const std::string& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind, size_t* optContentStart = nullptr, uint16_t* optContentLen = nullptr);
-bool addEDNS(dnsheader* dh, uint16_t& len, const size_t size, bool dnssecOK, uint16_t payloadSize);
+bool addEDNS(dnsheader* dh, uint16_t& len, const size_t size, bool dnssecOK, uint16_t payloadSize, uint8_t ednsrcode);
 bool addEDNSToQueryTurnedResponse(DNSQuestion& dq);
 
 bool handleEDNSClientSubnet(DNSQuestion& dq, bool* ednsAdded, bool* ecsAdded, bool preserveTrailingData);
index 43fa9aa3c1918ffd1b30927b3aa810242a74a9dd..55c7251e0aaca0866ec83c78e1ffc17cdf89c5bf 100644 (file)
@@ -310,6 +310,26 @@ private:
   uint8_t d_rcode;
 };
 
+class ERCodeAction : public DNSAction
+{
+public:
+  ERCodeAction(uint8_t rcode) : d_rcode(rcode) {}
+  DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
+  {
+    dq->dh->rcode = (d_rcode & 0xF);
+    dq->ednsRCode = ((d_rcode & 0xFFF0) >> 4);
+    dq->dh->qr = true; // for good measure
+    return Action::HeaderModify;
+  }
+  string toString() const override
+  {
+    return "set ercode "+ERCode::to_s(d_rcode);
+  }
+
+private:
+  uint8_t d_rcode;
+};
+
 class TCAction : public DNSAction
 {
 public:
@@ -462,7 +482,7 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, string* ruleresult) c
   dq->dh->ancount = htons(dq->dh->ancount);
 
   if (hadEDNS) {
-    addEDNS(dq->dh, dq->len, dq->size, dnssecOK, g_PayloadSizeSelfGenAnswers);
+    addEDNS(dq->dh, dq->len, dq->size, dnssecOK, g_PayloadSizeSelfGenAnswers, 0);
   }
 
   return Action::HeaderModify;
@@ -486,7 +506,7 @@ public:
     generateEDNSOption(d_code, mac, optRData);
 
     string res;
-    generateOptRR(optRData, res, g_EdnsUDPPayloadSize, false);
+    generateOptRR(optRData, res, g_EdnsUDPPayloadSize, 0, false);
 
     if ((dq->size - dq->len) < res.length())
       return Action::None;
@@ -1163,6 +1183,10 @@ void setupLuaActions()
       return std::shared_ptr<DNSAction>(new RCodeAction(rcode));
     });
 
+  g_lua.writeFunction("ERCodeAction", [](uint8_t rcode) {
+      return std::shared_ptr<DNSAction>(new ERCodeAction(rcode));
+    });
+
   g_lua.writeFunction("SkipCacheAction", []() {
       return std::shared_ptr<DNSAction>(new SkipCacheAction);
     });
index ef02dcffcc3efebab4424e31b0da40618c6b8931..04fe83acbdb7f5b49f61e59fbda1a0176f3a3610 100644 (file)
@@ -418,6 +418,10 @@ void setupLuaRules()
       return std::shared_ptr<DNSRule>(new ERCodeRule(rcode));
     });
 
+  g_lua.writeFunction("EDNSVersionRule", [](uint8_t version) {
+      return std::shared_ptr<DNSRule>(new EDNSVersionRule(version));
+    });
+
   g_lua.writeFunction("EDNSOptionRule", [](uint16_t optcode) {
       return std::shared_ptr<DNSRule>(new EDNSOptionRule(optcode));
     });
index bcbfeaa972ef27978374d6c65817f2933c26509a..c7e03da7b3bdafb2f965ba23744860a83e0a2687 100644 (file)
@@ -1765,6 +1765,8 @@ void setupLuaConfig(bool client)
         }
 #endif
       });
+
+  g_lua.writeFunction("setAllowEmptyResponse", [](bool allow) { g_allowEmptyResponse=allow; });
 }
 
 vector<std::function<void(void)>> setupLua(bool client, const std::string& config)
index 9eadeb2505c8838b1333f923dda47d6521a4283c..c0957d4e6f437673a8779f19383c50f151092e73 100644 (file)
@@ -48,6 +48,7 @@ static const oid dynBlockedOID[] = { DNSDIST_STATS_OID, 35 };
 static const oid dynBlockedNMGSizeOID[] = { DNSDIST_STATS_OID, 36 };
 static const oid ruleServFailOID[] = { DNSDIST_STATS_OID, 37 };
 static const oid securityStatusOID[] = { DNSDIST_STATS_OID, 38 };
+static const oid specialMemoryUsageOID[] = { DNSDIST_STATS_OID, 39 };
 
 static std::unordered_map<oid, DNSDistStats::entry_t> s_statsMap;
 
@@ -576,12 +577,13 @@ DNSDistSNMPAgent::DNSDistSNMPAgent(const std::string& name, const std::string& m
   registerFloatStat("latencyAvg10000", latencyAvg10000OID, OID_LENGTH(latencyAvg10000OID), &g_stats.latencyAvg10000);
   registerFloatStat("latencyAvg1000000", latencyAvg1000000OID, OID_LENGTH(latencyAvg1000000OID), &g_stats.latencyAvg1000000);
   registerGauge64Stat("uptime", uptimeOID, OID_LENGTH(uptimeOID), &uptimeOfProcess);
-  registerGauge64Stat("realMemoryUsage", realMemoryUsageOID, OID_LENGTH(realMemoryUsageOID), &getRealMemoryUsage);
+  registerGauge64Stat("specialMemoryUsage", specialMemoryUsageOID, OID_LENGTH(specialMemoryUsageOID), &getSpecialMemoryUsage);
   registerGauge64Stat("cpuUserMSec", cpuUserMSecOID, OID_LENGTH(cpuUserMSecOID), &getCPUTimeUser);
   registerGauge64Stat("cpuSysMSec", cpuSysMSecOID, OID_LENGTH(cpuSysMSecOID), &getCPUTimeSystem);
   registerGauge64Stat("fdUsage", fdUsageOID, OID_LENGTH(fdUsageOID), &getOpenFileDescriptors);
   registerGauge64Stat("dynBlockedNMGSize", dynBlockedNMGSizeOID, OID_LENGTH(dynBlockedNMGSizeOID), [](const std::string&) { return g_dynblockNMG.getLocal()->size(); });
   registerGauge64Stat("securityStatus", securityStatusOID, OID_LENGTH(securityStatusOID), [](const std::string&) { return g_stats.securityStatus.load(); });
+  registerGauge64Stat("realMemoryUsage", realMemoryUsageOID, OID_LENGTH(realMemoryUsageOID), &getRealMemoryUsage);
 
 
   netsnmp_table_registration_info* table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
index 8cd977384f867510b2c38eeaed5b98d1bc786c93..9cb51a622b17d5f1b37fb251402a9737284746f7 100644 (file)
@@ -323,6 +323,8 @@ static void connectionThread(int sock, ComboAddress remote)
         };
 
         for(const auto& e : g_stats.entries) {
+          if (e.first == "special-memory-usage")
+            continue; // Too expensive for get-all
           if(const auto& val = boost::get<DNSDistStats::stat_t*>(&e.second))
             obj.insert({e.first, (double)(*val)->load()});
           else if (const auto& dval = boost::get<double*>(&e.second))
@@ -405,6 +407,8 @@ static void connectionThread(int sock, ComboAddress remote)
 
         std::ostringstream output;
         for (const auto& e : g_stats.entries) {
+          if (e.first == "special-memory-usage")
+            continue; // Too expensive for get-all
           std::string metricName = std::get<0>(e);
 
           // Prometheus suggest using '_' instead of '-'
@@ -663,6 +667,9 @@ static void connectionThread(int sock, ComboAddress remote)
 
       Json::array doc;
       for(const auto& item : g_stats.entries) {
+        if (item.first == "special-memory-usage")
+          continue; // Too expensive for get-all
+
         if(const auto& val = boost::get<DNSDistStats::stat_t*>(&item.second)) {
           doc.push_back(Json::object {
               { "type", "StatisticItem" },
@@ -697,6 +704,7 @@ static void connectionThread(int sock, ComboAddress remote)
       typedef boost::variant<bool, double, std::string> configentry_t;
       std::vector<std::pair<std::string, configentry_t> > configEntries {
         { "acl", g_ACL.getLocal()->toString() },
+        { "allow-empty-response", g_allowEmptyResponse },
         { "control-socket", g_serverControl.toStringWithPort() },
         { "ecs-override", g_ECSOverride },
         { "ecs-source-prefix-v4", (double) g_ECSSourcePrefixV4 },
index b8519f6f9d7bd1954d9befb0c0bb0c5f172022a6..f2dfa39b218c0f3700345d9c20a9ece48c06d811 100644 (file)
@@ -87,6 +87,7 @@ uint16_t g_maxOutstanding{10240};
 bool g_verboseHealthChecks{false};
 uint32_t g_staleCacheEntriesTTL{0};
 bool g_syslog{true};
+bool g_allowEmptyResponse{false};
 
 GlobalStateHolder<NetmaskGroup> g_ACL;
 string g_outputBuffer;
@@ -160,7 +161,7 @@ try
   dh->ancount = dh->arcount = dh->nscount = 0;
 
   if (hadEDNS) {
-    addEDNS(dh, *len, responseSize, z & EDNS_HEADER_FLAG_DO, payloadSize);
+    addEDNS(dh, *len, responseSize, z & EDNS_HEADER_FLAG_DO, payloadSize, 0);
   }
 }
 catch(...)
@@ -222,7 +223,7 @@ bool responseContentMatches(const char* response, const uint16_t responseLen, co
   }
 
   if (dh->qdcount == 0) {
-    if (dh->rcode != RCode::NoError && dh->rcode != RCode::NXDomain) {
+    if ((dh->rcode != RCode::NoError && dh->rcode != RCode::NXDomain) || g_allowEmptyResponse) {
       return true;
     }
     else {
@@ -2708,8 +2709,8 @@ try
       tcpBindsCount++;
     }
     else {
-      delete cs;
       errlog("Error while setting up TLS on local address '%s', exiting", cs->local.toStringWithPort());
+      delete cs;
       _exit(EXIT_FAILURE);
     }
   }
index 45f8d4706ecc31b8ce59ccb49aaad74c6f00baf7..64b3cce8c1f7f0bda0dc8e524e57847c942e27e2 100644 (file)
@@ -79,6 +79,7 @@ struct DNSQuestion
   unsigned int consumed{0};
   uint16_t len;
   uint16_t ecsPrefixLength;
+  uint8_t ednsRCode;
   boost::optional<uint32_t> tempFailureTTL;
   const bool tcp;
   const struct timespec* queryTime;
@@ -256,6 +257,7 @@ struct DNSDistStats
     {"latency-avg1000000", &latencyAvg1000000},
     {"uptime", uptimeOfProcess},
     {"real-memory-usage", getRealMemoryUsage},
+    {"special-memory-usage", getSpecialMemoryUsage},
     {"noncompliant-queries", &nonCompliantQueries},
     {"noncompliant-responses", &nonCompliantResponses},
     {"rdqueries", &rdQueries},
@@ -960,6 +962,7 @@ extern bool g_useTCPSinglePipe;
 extern std::atomic<uint16_t> g_downstreamTCPCleanupInterval;
 extern size_t g_udpVectorSize;
 extern bool g_preserveTrailingData;
+extern bool g_allowEmptyResponse;
 
 #ifdef HAVE_EBPF
 extern shared_ptr<BPFFilter> g_defaultBPFFilter;
index 9d1f5026920064fad0b9e86daa13e3b485ef8913..4f1f0bc2930aa71a2e2a864a7511c9264476ab69 100644 (file)
@@ -326,6 +326,14 @@ ruleServFail OBJECT-TYPE
        "Number of ServFail responses returned because of a rule"
     ::= { stats 37 }
 
+specialMemoryUsage OBJECT-TYPE
+    SYNTAX CounterBasedGauge64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+       "Memory usage (more precise but expensive to retrieve)"
+    ::= { stats 38 }
+
 securityStatus OBJECT-TYPE
     SYNTAX CounterBasedGauge64
     MAX-ACCESS read-only
@@ -667,6 +675,7 @@ dnsdistGroup OBJECT-GROUP
         latencyAVG1000000,
         uptime,
         realMemoryUsage,
+        specialMemoryUsage,
         nonCompliantQueries,
         nonCompliantResponses,
         rdQueries,
index 1ed08b9999dc3a057fe319a386e087de3fe010a4..e28decf08c9104febc94fd993911b94bbf436ec1 100644 (file)
@@ -52,10 +52,12 @@ AC_SUBST([YAHTTP_LIBS], ['$(top_builddir)/ext/yahttp/yahttp/libyahttp.la'])
 PDNS_WITH_LUA([mandatory])
 PDNS_CHECK_LUA_HPP
 
+AM_CONDITIONAL([HAVE_GNUTLS], [false])
+AM_CONDITIONAL([HAVE_LIBSSL], [false])
 DNSDIST_ENABLE_DNS_OVER_TLS
-DNSDIST_WITH_GNUTLS
-DNSDIST_WITH_LIBSSL
 AS_IF([test "x$enable_dns_over_tls" != "xno"], [
+  DNSDIST_WITH_GNUTLS
+  DNSDIST_WITH_LIBSSL
   AS_IF([test "$HAVE_LIBSSL" = "1"], [
     # we need libcrypto if libssl is enabled
     PDNS_CHECK_LIBCRYPTO
@@ -160,16 +162,15 @@ AS_IF([test "x$NET_SNMP_LIBS" != "x"],
   [AC_MSG_NOTICE([SNMP: yes])],
   [AC_MSG_NOTICE([SNMP: no])]
 )
-AS_IF([test "x$enable_dns_over_tls" != "xno"],
-  [AC_MSG_NOTICE([DNS over TLS: yes])],
+AS_IF([test "x$enable_dns_over_tls" != "xno"], [
+  AC_MSG_NOTICE([DNS over TLS: yes])
+  AS_IF([test "x$GNUTLS_LIBS" != "x"],
+    [AC_MSG_NOTICE([GnuTLS: yes])],
+    [AC_MSG_NOTICE([GnuTLS: no])])
+  AS_IF([test "x$LIBSSL_LIBS" != "x"],
+    [AC_MSG_NOTICE([OpenSSL: yes])],
+    [AC_MSG_NOTICE([OpenSSL: no])])
+  ],
   [AC_MSG_NOTICE([DNS over TLS: no])]
 )
-AS_IF([test "x$GNUTLS_LIBS" != "x"],
-  [AC_MSG_NOTICE([GnuTLS: yes])],
-  [AC_MSG_NOTICE([GnuTLS: no])]
-)
-AS_IF([test "x$LIBSSL_LIBS" != "x"],
-  [AC_MSG_NOTICE([OpenSSL: yes])],
-  [AC_MSG_NOTICE([OpenSSL: no])]
-)
 AC_MSG_NOTICE([])
index f343f4f5db82b7356553aa5baf9a50a991184c8d..e3fd36a04de1cd3b0856936a38d52aed0da289cb 100644 (file)
@@ -882,6 +882,49 @@ private:
   uint8_t d_extrcode;  // upper bits in EDNS0 record
 };
 
+class EDNSVersionRule : public DNSRule
+{
+public:
+  EDNSVersionRule(uint8_t version) : d_version(version)
+  {
+  }
+  bool matches(const DNSQuestion* dq) const override
+  {
+    uint16_t optStart;
+    size_t optLen = 0;
+    bool last = false;
+    const char * packet = reinterpret_cast<const char*>(dq->dh);
+    std::string packetStr(packet, dq->len);
+    int res = locateEDNSOptRR(packetStr, &optStart, &optLen, &last);
+    if (res != 0) {
+      // no EDNS OPT RR
+      return false;
+    }
+
+    // root label (1), type (2), class (2), ttl (4) + rdlen (2)
+    if (optLen < 11) {
+      return false;
+    }
+
+    if (optStart < dq->len && packetStr.at(optStart) != 0) {
+      // OPT RR Name != '.'
+      return false;
+    }
+    EDNS0Record edns0;
+    static_assert(sizeof(EDNS0Record) == sizeof(uint32_t), "sizeof(EDNS0Record) must match sizeof(uint32_t) AKA RR TTL size");
+    // copy out 4-byte "ttl" (really the EDNS0 record), after root label (1) + type (2) + class (2).
+    memcpy(&edns0, packet + optStart + 5, sizeof edns0);
+
+    return d_version < edns0.version;
+  }
+  string toString() const override
+  {
+    return "ednsversion>"+std::to_string(d_version);
+  }
+private:
+  uint8_t d_version;
+};
+
 class EDNSOptionRule : public DNSRule
 {
 public:
index 77a7fa976d1fd3039459be8aaee7fc8885b843f1..64711abad3f2e9224b3b250362087d1379a34b09 100644 (file)
@@ -997,6 +997,12 @@ Other functions
   If this function exists, it is called every second to so regular tasks.
   This can be used for e.g. :doc:`Dynamic Blocks <../guides/dynblocks>`.
 
+.. function: setAllowEmptyResponse()
+
+  .. versionadded:: 1.4.0
+
+  Set to true (defaults to false) to allow empty responses (qdcount=0) with a NoError or NXDomain rcode (default) from backends. dnsdist drops these responses by default because it can't match them against the initial query since they don't contain the qname, qtype and qclass, and therefore the risk of collision is much higher than with regular responses.
+
 TLSContext
 ~~~~~~~~~~
 
index 160deb4c6b01bd84c9473b927d01e8face87997f..4ae2cafd8c3cf8fe85950a2308a51f626424291c 100644 (file)
@@ -278,7 +278,7 @@ For Rules related to the incoming query:
 
   Add a Rule and Action to the existing rules.
 
-  :param DNSrule rule: A DNSRule, e.g. an :func:`allRule` or a compounded bunch of rules using e.g. :func:`AndRule`
+  :param DNSrule rule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
   :param action: The action to take
   :param table options: A table with key: value pairs with options.
 
@@ -361,7 +361,7 @@ For Rules related to responses:
 
   Add a Rule and Action for responses to the existing rules.
 
-  :param DNSRule: A DNSRule, e.g. an :func:`allRule` or a compounded bunch of rules using e.g. :func:`AndRule`
+  :param DNSRule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
   :param action: The action to take
   :param table options: A table with key: value pairs with options.
 
@@ -415,7 +415,7 @@ Functions for manipulating Cache Hit Respone Rules:
 
   Add a Rule and ResponseAction for Cache Hits to the existing rules.
 
-  :param DNSRule: A DNSRule, e.g. an :func:`allRule` or a compounded bunch of rules using e.g. :func:`AndRule`
+  :param DNSRule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
   :param action: The action to take
   :param table options: A table with key: value pairs with options.
 
@@ -472,7 +472,7 @@ Functions for manipulating Self-Answered Response Rules:
 
   Add a Rule and Action for Self-Answered queries to the existing rules.
 
-  :param DNSRule: A DNSRule, e.g. an :func:`allRule` or a compounded bunch of rules using e.g. :func:`AndRule`
+  :param DNSRule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
   :param action: The action to take
 
 .. function:: mvSelfAnsweredResponseRule(from, to)
@@ -547,6 +547,35 @@ These ``DNSRule``\ s be one of the following items:
 
   Matches queries with the DO flag set
 
+.. function:: DSTPortRule(port)
+
+  Matches questions received to the destination port.
+
+  :param int port: Match destination port.
+
+.. function:: EDNSOptionRule(optcode)
+
+  .. versionadded:: 1.4.0
+
+  Matches queries or responses with the specified EDNS option present.
+  ``optcode`` is specified as an integer, or a constant such as `EDNSOptionCode.ECS`.
+
+.. function:: EDNSVersionRule(version)
+
+  .. versionadded:: 1.4.0
+
+  Matches queries or responses with an OPT record whose EDNS version is greater than the specified EDNS version.
+
+  :param int version: The EDNS version to match on
+
+.. function:: ERCodeRule(rcode)
+
+  Matches queries or responses with the specified ``rcode``.
+  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
+  The full 16bit RCode will be matched. If no EDNS OPT RR is present, the upper 12 bits are treated as 0.
+
+  :param int rcode: The RCODE to match on
+
 .. function:: MaxQPSIPRule(qps[, v4Mask[, v6Mask[, burst[, expiration[, cleanupDelay[, scanFraction]]]]]])
   .. versionchanged:: 1.3.1
     Added the optional parameters ``expiration``, ``cleanupDelay`` and ``scanFraction``.
@@ -640,21 +669,6 @@ These ``DNSRule``\ s be one of the following items:
 
   :param int rcode: The RCODE to match on
 
-.. function:: ERCodeRule(rcode)
-
-  Matches queries or responses with the specified ``rcode``.
-  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
-  The full 16bit RCode will be matched. If no EDNS OPT RR is present, the upper 12 bits are treated as 0.
-
-  :param int rcode: The RCODE to match on
-
-.. function:: EDNSOptionRule(optcode)
-
-  .. versionadded:: 1.3.3
-
-  Matches queries or responses with the specified EDNS option present.
-  ``optcode`` is specified as an integer, or a constant such as `EDNSOptionCode.ECS`.
-
 .. function:: RDRule()
 
   .. versionadded:: 1.2.0
@@ -731,12 +745,6 @@ These ``DNSRule``\ s be one of the following items:
 
   :param bool tcp: Match TCP traffic. Default is true.
 
-.. function:: DSTPortRule(port)
-
-  Matches questions received to the destination port.
-
-  :param int port: Match destination port.
-
 .. function:: TrailingDataRule()
 
   Matches if the query has trailing data.
@@ -856,6 +864,16 @@ The following actions exist.
   :param int v4: The IPv4 netmask length
   :param int v6: The IPv6 netmask length
 
+
+.. function:: ERCodeAction(rcode)
+
+  .. versionadded:: 1.4.0
+
+  Reply immediately by turning the query into a response with the specified EDNS extended ``rcode``.
+  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
+
+  :param int rcode: The extended RCODE to respond with.
+
 .. function:: LogAction([filename[, binary[, append[, buffered]]]])
 
   Log a line for each query, to the specified ``file`` if any, to the console (require verbose) otherwise.
@@ -926,7 +944,7 @@ The following actions exist.
 
 .. function:: RCodeAction(rcode)
 
-  Reply immediatly by turning the query into a response with the specified ``rcode``.
+  Reply immediately by turning the query into a response with the specified ``rcode``.
   ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
 
   :param int rcode: The RCODE to respond with.
index 186463d740832b90fabfc82c634118f23866472d..8d9f9da625ad46adaa2e0e333d884f5c9ec00899 100644 (file)
@@ -52,15 +52,16 @@ using namespace boost::assign;
 shared_ptr<DNSCryptoKeyEngine> DNSCryptoKeyEngine::makeFromISCFile(DNSKEYRecordContent& drc, const char* fname)
 {
   string sline, isc;
-  FILE *fp=fopen(fname, "r");
+  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(fname, "r"), fclose);
   if(!fp) {
     throw runtime_error("Unable to read file '"+string(fname)+"' for generating DNS Private Key");
   }
   
-  while(stringfgets(fp, sline)) {
+  while(stringfgets(fp.get(), sline)) {
     isc += sline;
   }
-  fclose(fp);
+  fp.reset();
+
   shared_ptr<DNSCryptoKeyEngine> dke = makeFromISCString(drc, isc);
   if(!dke->checkKey()) {
     throw runtime_error("Invalid DNS Private Key in file '"+string(fname));
index dc7fc5e375777e4fefee5d62044fbc7cf1bdc79a..c3585ce031a3be21ab9ba01139c5ea29f7e650c2 100644 (file)
@@ -210,18 +210,18 @@ void DNSFilterEngine::Zone::addResponseTrigger(const Netmask& nm, Policy&& pol)
   d_postpolAddr.insert(nm).second=std::move(pol);
 }
 
-void DNSFilterEngine::Zone::addQNameTrigger(const DNSName& n, Policy&& pol)
+void DNSFilterEngine::Zone::addQNameTrigger(const DNSName& n, Policy&& pol, bool ignoreDuplicate)
 {
   auto it = d_qpolName.find(n);
 
   if (it != d_qpolName.end()) {
     auto& existingPol = it->second;
 
-    if (pol.d_kind != PolicyKind::Custom) {
+    if (pol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
       throw std::runtime_error("Adding a QName-based filter policy of kind " + getKindToString(pol.d_kind) + " but a policy of kind " + getKindToString(existingPol.d_kind) + " already exists for the following QName: " + n.toLogString());
     }
 
-    if (existingPol.d_kind != PolicyKind::Custom) {
+    if (existingPol.d_kind != PolicyKind::Custom && ignoreDuplicate) {
       throw std::runtime_error("Adding a QName-based filter policy of kind " + getKindToString(existingPol.d_kind) + " but there was already an existing policy for the following QName: " + n.toLogString());
     }
 
index 22900cb37d73e2e3b6d1c3bb153aae5db1f35c41..3d5aea5d86c42500414f098dc983e970edc92158 100644 (file)
@@ -158,7 +158,7 @@ public:
     void dump(FILE * fp) const;
 
     void addClientTrigger(const Netmask& nm, Policy&& pol);
-    void addQNameTrigger(const DNSName& nm, Policy&& pol);
+    void addQNameTrigger(const DNSName& nm, Policy&& pol, bool ignoreDuplicate=false);
     void addNSTrigger(const DNSName& dn, Policy&& pol);
     void addNSIPTrigger(const Netmask& nm, Policy&& pol);
     void addResponseTrigger(const Netmask& nm, Policy&& pol);
index c6b2137532c625f273add805ca43a9658f9ae63e..0a7dca9f5638829b783a9dd3af2c686c01c0ad2e 100644 (file)
@@ -59,6 +59,12 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > processIXFRRecords(const Co
     // the serial of this SOA record is the serial of the
     // zone before the removals and updates of this sequence
     if (sr->d_st.serial == masterSOA->d_st.serial) {
+      if (records.size() == 2) {
+        // if the entire update is two SOAs records with the same
+        // serial, this is actually an empty AXFR!
+        return {{remove, records}};
+      }
+
       // if it's the final SOA, there is nothing for us to see
       break;
     }
index 1ba8ffed0c0213ca501fbb887e5c0e65b2777cc7..f49e0fae56228e7b77dd6c547589e048680eca81 100644 (file)
@@ -271,7 +271,7 @@ static void updateCurrentZoneInfo(const DNSName& domain, std::shared_ptr<ixfrinf
   // FIXME: also report zone size?
 }
 
-void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& axfrTimeout, const uint16_t& soaRetry) {
+void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& axfrTimeout, const uint16_t& soaRetry, const uint32_t axfrMaxRecords) {
   setThreadName("ixfrdist/update");
   std::map<DNSName, time_t> lastCheck;
 
@@ -377,7 +377,7 @@ void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& a
       records_t records;
       try {
         AXFRRetriever axfr(master, domain, tt, &local);
-        unsigned int nrecords=0;
+        uint32_t nrecords=0;
         Resolver::res_t nop;
         vector<DNSRecord> chunk;
         time_t t_start = time(nullptr);
@@ -397,6 +397,9 @@ void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& a
               soaTTL = dr.d_ttl;
             }
           }
+          if (axfrMaxRecords != 0 && nrecords > axfrMaxRecords) {
+            throw PDNSException("Received more than " + std::to_string(axfrMaxRecords) + " records in AXFR, aborted");
+          }
           axfr_now = time(nullptr);
           if (axfr_now - t_start > axfrTimeout) {
             g_stats.incrementAXFRFailures(domain);
@@ -975,6 +978,16 @@ static bool parseAndCheckConfig(const string& configpath, YAML::Node& config) {
     config["keep"] = 20;
   }
 
+  if (config["axfr-max-records"]) {
+    try {
+      config["axfr-max-records"].as<uint32_t>();
+    } catch (const runtime_error &e) {
+      g_log<<Logger::Error<<"Unable to read 'axfr-max-records' value: "<<e.what()<<endl;
+    }
+  } else {
+    config["axfr-max-records"] = 0;
+  }
+
   if (config["axfr-timeout"]) {
     try {
       config["axfr-timeout"].as<uint16_t>();
@@ -1324,7 +1337,8 @@ int main(int argc, char** argv) {
       config["work-dir"].as<string>(),
       config["keep"].as<uint16_t>(),
       config["axfr-timeout"].as<uint16_t>(),
-      config["failed-soa-retry"].as<uint16_t>());
+      config["failed-soa-retry"].as<uint16_t>(),
+      config["axfr-max-records"].as<uint32_t>());
 
   vector<std::thread> tcpHandlers;
   tcpHandlers.reserve(config["tcp-in-threads"].as<uint16_t>());
index e7c8c4e7d723182f05f822b868d0f4604ca17de6..b995cb5ef1c45aac8dc060c2cf83e9e57719ec15 100644 (file)
@@ -28,6 +28,12 @@ acl:
   - '127.0.0.0/8'
   - '::1'
 
+# Maximum number of records allowed in a single zone. ixfrdist will abort the
+# zone transfer from the master when more than this number of records have been
+# received. A value of 0 (the default) means unlimited
+#
+axfr-max-records: 0
+
 # Timeout in seconds an AXFR transaction requested by ixfrdist may take.
 # Increase this when the network to the authoritative servers is slow or the
 # domains are very large and you experience timeouts. Set to 20 by default or
index 34354655fccead21c9e6aec9664a49a007c1b1a4..b1847d06a31fe527d4b1eb1ea7f9d825878c0b69 100644 (file)
@@ -46,7 +46,11 @@ int intFromJson(const Json container, const std::string& key, const int default_
   if (val.is_number()) {
     return val.int_value();
   } else if (val.is_string()) {
-    return std::stoi(val.string_value());
+    try {
+      return std::stoi(val.string_value());
+    } catch (std::out_of_range&) {
+      throw JsonException("Value for key '" + string(key) + "' is out of range");
+    }
   } else {
     // TODO: check if value really isn't present
     return default_value;
@@ -59,7 +63,11 @@ double doubleFromJson(const Json container, const std::string& key)
   if (val.is_number()) {
     return val.number_value();
   } else if (val.is_string()) {
-    return std::stod(val.string_value());
+    try {
+      return std::stod(val.string_value());
+    } catch (std::out_of_range&) {
+      throw JsonException("Value for key '" + string(key) + "' is out of range");
+    }
   } else {
     throw JsonException("Key '" + string(key) + "' not an Integer or not present");
   }
index 34be790dad9d03554059b42258485a67077f6457..39dbb391c3abdcad7f65e15dbd12bb6e32459f25 100644 (file)
@@ -10,6 +10,7 @@
 #include "namespaces.hh"
 #include "ednssubnet.hh"
 #include "lua-base4.hh"
+#include "dns_random.hh"
 
 BaseLua4::BaseLua4() {
 }
@@ -190,6 +191,7 @@ void BaseLua4::prepareContext() {
 
   // pdnsload
   d_lw->writeFunction("pdnslog", [](const std::string& msg, boost::optional<int> loglevel) { g_log << (Logger::Urgency)loglevel.get_value_or(Logger::Warning) << msg<<endl; });
+  d_lw->writeFunction("pdnsrandom", [](boost::optional<uint32_t> maximum) { return dns_random(maximum.get_value_or(0xffffffff)); });
 
   // certain constants
   d_pd.push_back({"PASS", (int)PolicyDecision::PASS});
index 429069e4e2d76baf993facfe6b3fcfcb9b00e4ef..cef57880230e12f3f3b338d3a76653ed582fdc69 100644 (file)
@@ -1,3 +1,4 @@
+#include "version.hh"
 #include "ext/luawrapper/include/LuaContext.hpp"
 #include "lua-auth4.hh"
 #include <thread>
@@ -184,7 +185,11 @@ void IsUpOracle::checkURLThread(ComboAddress rem, std::string url, const opts_t&
   setDown(rem, url, opts);
   for(bool first=true;;first=false) {
     try {
-      MiniCurl mc;
+      string useragent = productName();
+      if (opts.count("useragent")) {
+        useragent = opts.at("useragent");
+      }
+      MiniCurl mc(useragent);
 
       string content;
       if(opts.count("source")) {
index bd80f92cbb1f751f243342c8739942ae5e2bb237..915eeecb133d7d58553c142cdd82897ee0b01129 100644 (file)
 #include <curl/curl.h>
 #include <stdexcept>
 
-MiniCurl::MiniCurl()
+MiniCurl::MiniCurl(const string& useragent)
 {
   d_curl = curl_easy_init();
+  if (d_curl != nullptr) {
+    curl_easy_setopt(d_curl, CURLOPT_USERAGENT, useragent.c_str());
+  }
 }
 
 MiniCurl::~MiniCurl()
index 0218754f3d9873e9ea86b4d9a646e46e486ef506..7e913a897de2b16f00e5e109b6b17d0756b3bb7d 100644 (file)
@@ -31,7 +31,7 @@
 class MiniCurl
 {
 public:
-  MiniCurl();
+  MiniCurl(const string& useragent="MiniCurl/0.0");
   ~MiniCurl();
   MiniCurl& operator=(const MiniCurl&) = delete;
   std::string getURL(const std::string& str, const ComboAddress* rem=0, const ComboAddress* src=0);
index 9293a49242e3615bc6b66f02af83d06d5f847533..b9863ed966e901715d6bed02f61b25befb319fc2 100644 (file)
@@ -866,11 +866,12 @@ bool stringfgets(FILE* fp, std::string& line)
 bool readFileIfThere(const char* fname, std::string* line)
 {
   line->clear();
-  FILE* fp = fopen(fname, "r");
+  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(fname, "r"), fclose);
   if(!fp)
     return false;
-  stringfgets(fp, *line);
-  fclose(fp);
+  stringfgets(fp.get(), *line);
+  fp.reset();
+
   return true;
 }
 
@@ -1240,7 +1241,27 @@ uint64_t getOpenFileDescriptors(const std::string&)
 uint64_t getRealMemoryUsage(const std::string&)
 {
 #ifdef __linux__
-  ifstream ifs("/proc/"+std::to_string(getpid())+"/smaps");
+  ifstream ifs("/proc/self/statm");
+  if(!ifs)
+    return 0;
+
+  uint64_t size, resident, shared, text, lib, data;
+  ifs >> size >> resident >> shared >> text >> lib >> data;
+
+  return data * getpagesize();
+#else
+  struct rusage ru;
+  if (getrusage(RUSAGE_SELF, &ru) != 0)
+    return 0;
+  return ru.ru_maxrss * 1024;
+#endif
+}
+
+
+uint64_t getSpecialMemoryUsage(const std::string&)
+{
+#ifdef __linux__
+  ifstream ifs("/proc/self/smaps");
   if(!ifs)
     return 0;
   string line;
index f04d7bb72c5252db766a311311b224c4e3e225bf..f57a010bc6041bbed5047595444ba26aef5a59e6 100644 (file)
@@ -539,6 +539,7 @@ bool setCloseOnExec(int sock);
 uint64_t udpErrorStats(const std::string& str);
 
 uint64_t getRealMemoryUsage(const std::string&);
+uint64_t getSpecialMemoryUsage(const std::string&);
 uint64_t getOpenFileDescriptors(const std::string&);
 uint64_t getCPUTimeUser(const std::string&);
 uint64_t getCPUTimeSystem(const std::string&);
index f4842e9e7c8d02d104872d64e5f2aecb73e18565..1a278125bf5264eaa221d50d8664c6567c63e17e 100644 (file)
@@ -1044,7 +1044,6 @@ static void startDoResolve(void *p)
         maxanswersize = min(static_cast<uint16_t>(edo.d_packetsize >= 512 ? edo.d_packetsize : 512), g_udpTruncationThreshold);
       }
       ednsOpts = edo.d_options;
-      haveEDNS=true;
       maxanswersize -= 11; // EDNS header size
 
       for (const auto& o : edo.d_options) {
@@ -3634,6 +3633,7 @@ static int serviceMain(int argc, char*argv[])
   SyncRes::s_nopacketcache = ::arg().mustDo("disable-packetcache");
 
   SyncRes::s_maxnegttl=::arg().asNum("max-negative-ttl");
+  SyncRes::s_maxbogusttl=::arg().asNum("max-cache-bogus-ttl");
   SyncRes::s_maxcachettl=max(::arg().asNum("max-cache-ttl"), 15);
   SyncRes::s_packetcachettl=::arg().asNum("packetcache-ttl");
   // Cap the packetcache-servfail-ttl to the packetcache-ttl
@@ -4222,6 +4222,7 @@ int main(int argc, char **argv)
     ::arg().set("hint-file", "If set, load root hints from this file")="";
     ::arg().set("max-cache-entries", "If set, maximum number of entries in the main cache")="1000000";
     ::arg().set("max-negative-ttl", "maximum number of seconds to keep a negative cached entry in memory")="3600";
+    ::arg().set("max-cache-bogus-ttl", "maximum number of seconds to keep a Bogus (positive or negative) cached entry in memory")="3600";
     ::arg().set("max-cache-ttl", "maximum number of seconds to keep a cached entry in memory")="86400";
     ::arg().set("packetcache-ttl", "maximum number of seconds to keep a cached entry in packetcache")="3600";
     ::arg().set("max-packetcache-entries", "maximum number of entries to keep in the packetcache")="500000";
index a94c992a2f38f33c521d7bba18689b4225c23642..ea1d56ed6c9d42934aa71b5df2d93fe682c1580e 100644 (file)
@@ -51,31 +51,36 @@ typename C::value_type::second_type constGet(const C& c, const std::string& name
   return iter->second;
 }
 
+typedef std::unordered_map<std::string, boost::variant<bool, uint32_t, std::string > > rpzOptions_t;
 
-static void parseRPZParameters(const std::unordered_map<string,boost::variant<uint32_t, string> >& have, std::string& polName, boost::optional<DNSFilterEngine::Policy>& defpol, uint32_t& maxTTL, size_t& zoneSizeHint)
+static void parseRPZParameters(rpzOptions_t& have, std::string& polName, boost::optional<DNSFilterEngine::Policy>& defpol, bool& defpolOverrideLocal, uint32_t& maxTTL, size_t& zoneSizeHint)
 {
   if(have.count("policyName")) {
-    polName = boost::get<std::string>(constGet(have, "policyName"));
+    polName = boost::get<std::string>(have["policyName"]);
   }
   if(have.count("defpol")) {
     defpol=DNSFilterEngine::Policy();
-    defpol->d_kind = (DNSFilterEngine::PolicyKind)boost::get<uint32_t>(constGet(have, "defpol"));
+    defpol->d_kind = (DNSFilterEngine::PolicyKind)boost::get<uint32_t>(have["defpol"]);
     defpol->d_name = std::make_shared<std::string>(polName);
     if(defpol->d_kind == DNSFilterEngine::PolicyKind::Custom) {
       defpol->d_custom.push_back(DNSRecordContent::mastermake(QType::CNAME, QClass::IN,
-                                                              boost::get<string>(constGet(have,"defcontent"))));
+                                                              boost::get<string>(have["defcontent"])));
 
       if(have.count("defttl"))
-        defpol->d_ttl = static_cast<int32_t>(boost::get<uint32_t>(constGet(have, "defttl")));
+        defpol->d_ttl = static_cast<int32_t>(boost::get<uint32_t>(have["defttl"]));
       else
         defpol->d_ttl = -1; // get it from the zone
     }
+
+    if (have.count("defpolOverrideLocalData")) {
+      defpolOverrideLocal = boost::get<bool>(have["defpolOverrideLocalData"]);
+    }
   }
   if(have.count("maxTTL")) {
-    maxTTL = boost::get<uint32_t>(constGet(have, "maxTTL"));
+    maxTTL = boost::get<uint32_t>(have["maxTTL"]);
   }
   if(have.count("zoneSizeHint")) {
-    zoneSizeHint = static_cast<size_t>(boost::get<uint32_t>(constGet(have, "zoneSizeHint")));
+    zoneSizeHint = static_cast<size_t>(boost::get<uint32_t>(have["zoneSizeHint"]));
   }
 }
 
@@ -186,23 +191,24 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
   };
   Lua.writeVariable("Policy", pmap);
 
-  Lua.writeFunction("rpzFile", [&lci](const string& filename, const boost::optional<std::unordered_map<string,boost::variant<uint32_t, string>>>& options) {
+  Lua.writeFunction("rpzFile", [&lci](const string& filename, boost::optional<rpzOptions_t> options) {
       try {
         boost::optional<DNSFilterEngine::Policy> defpol;
+        bool defpolOverrideLocal = true;
         std::string polName("rpzFile");
         std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
         uint32_t maxTTL = std::numeric_limits<uint32_t>::max();
         if(options) {
           auto& have = *options;
           size_t zoneSizeHint = 0;
-          parseRPZParameters(have, polName, defpol, maxTTL, zoneSizeHint);
+          parseRPZParameters(have, polName, defpol, defpolOverrideLocal, maxTTL, zoneSizeHint);
           if (zoneSizeHint > 0) {
             zone->reserve(zoneSizeHint);
           }
         }
         g_log<<Logger::Warning<<"Loading RPZ from file '"<<filename<<"'"<<endl;
         zone->setName(polName);
-        loadRPZFromFile(filename, zone, defpol, maxTTL);
+        loadRPZFromFile(filename, zone, defpol, defpolOverrideLocal, maxTTL);
         lci.dfe.addZone(zone);
         g_log<<Logger::Warning<<"Done loading RPZ from file '"<<filename<<"'"<<endl;
       }
@@ -211,9 +217,10 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
       }
     });
 
-  Lua.writeFunction("rpzMaster", [&lci, &delayedThreads](const boost::variant<string, std::vector<std::pair<int, string> > >& masters_, const string& zoneName, const boost::optional<std::unordered_map<string,boost::variant<uint32_t, string>>>& options) {
+  Lua.writeFunction("rpzMaster", [&lci, &delayedThreads](const boost::variant<string, std::vector<std::pair<int, string> > >& masters_, const string& zoneName, boost::optional<rpzOptions_t> options) {
 
       boost::optional<DNSFilterEngine::Policy> defpol;
+      bool defpolOverrideLocal = true;
       std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
       TSIGTriplet tt;
       uint32_t refresh=0;
@@ -242,40 +249,40 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
         if (options) {
           auto& have = *options;
           size_t zoneSizeHint = 0;
-          parseRPZParameters(have, polName, defpol, maxTTL, zoneSizeHint);
+          parseRPZParameters(have, polName, defpol, defpolOverrideLocal, maxTTL, zoneSizeHint);
           if (zoneSizeHint > 0) {
             zone->reserve(zoneSizeHint);
           }
 
           if(have.count("tsigname")) {
-            tt.name=DNSName(toLower(boost::get<string>(constGet(have, "tsigname"))));
-            tt.algo=DNSName(toLower(boost::get<string>(constGet(have, "tsigalgo"))));
-            if(B64Decode(boost::get<string>(constGet(have, "tsigsecret")), tt.secret))
+            tt.name=DNSName(toLower(boost::get<string>(have["tsigname"])));
+            tt.algo=DNSName(toLower(boost::get<string>(have[ "tsigalgo"])));
+            if(B64Decode(boost::get<string>(have[ "tsigsecret"]), tt.secret))
               throw std::runtime_error("TSIG secret is not valid Base-64 encoded");
           }
 
           if(have.count("refresh")) {
-            refresh = boost::get<uint32_t>(constGet(have,"refresh"));
+            refresh = boost::get<uint32_t>(have["refresh"]);
           }
 
           if(have.count("maxReceivedMBytes")) {
-            maxReceivedXFRMBytes = static_cast<size_t>(boost::get<uint32_t>(constGet(have,"maxReceivedMBytes")));
+            maxReceivedXFRMBytes = static_cast<size_t>(boost::get<uint32_t>(have["maxReceivedMBytes"]));
           }
 
           if(have.count("localAddress")) {
-            localAddress = ComboAddress(boost::get<string>(constGet(have,"localAddress")));
+            localAddress = ComboAddress(boost::get<string>(have["localAddress"]));
           }
 
           if(have.count("axfrTimeout")) {
-            axfrTimeout = static_cast<uint16_t>(boost::get<uint32_t>(constGet(have, "axfrTimeout")));
+            axfrTimeout = static_cast<uint16_t>(boost::get<uint32_t>(have["axfrTimeout"]));
           }
 
           if(have.count("seedFile")) {
-            seedFile = boost::get<std::string>(constGet(have, "seedFile"));
+            seedFile = boost::get<std::string>(have["seedFile"]);
           }
 
           if(have.count("dumpFile")) {
-            dumpFile = boost::get<std::string>(constGet(have, "dumpFile"));
+            dumpFile = boost::get<std::string>(have["dumpFile"]);
           }
         }
 
@@ -297,7 +304,7 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
         if (!seedFile.empty()) {
           g_log<<Logger::Info<<"Pre-loading RPZ zone "<<zoneName<<" from seed file '"<<seedFile<<"'"<<endl;
           try {
-            sr = loadRPZFromFile(seedFile, zone, defpol, maxTTL);
+            sr = loadRPZFromFile(seedFile, zone, defpol, defpolOverrideLocal, maxTTL);
 
             if (zone->getDomain() != domain) {
               throw PDNSException("The RPZ zone " + zoneName + " loaded from the seed file (" + zone->getDomain().toString() + ") does not match the one passed in parameter (" + domain.toString() + ")");
@@ -321,7 +328,7 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
         exit(1);  // FIXME proper exit code?
       }
 
-      delayedThreads.rpzMasterThreads.push_back(std::make_tuple(masters, defpol, maxTTL, zoneIdx, tt, maxReceivedXFRMBytes, localAddress, axfrTimeout, sr, dumpFile));
+      delayedThreads.rpzMasterThreads.push_back(std::make_tuple(masters, defpol, defpolOverrideLocal, maxTTL, zoneIdx, tt, maxReceivedXFRMBytes, localAddress, axfrTimeout, sr, dumpFile));
     });
 
   typedef vector<pair<int,boost::variant<string, vector<pair<int, string> > > > > argvec_t;
@@ -518,7 +525,7 @@ void startLuaConfigDelayedThreads(const luaConfigDelayedThreads& delayedThreads,
 {
   for (const auto& rpzMaster : delayedThreads.rpzMasterThreads) {
     try {
-      std::thread t(RPZIXFRTracker, std::get<0>(rpzMaster), std::get<1>(rpzMaster), std::get<2>(rpzMaster), std::get<3>(rpzMaster), std::get<4>(rpzMaster), std::get<5>(rpzMaster) * 1024 * 1024, std::get<6>(rpzMaster), std::get<7>(rpzMaster), std::get<8>(rpzMaster), std::get<9>(rpzMaster), generation);
+      std::thread t(RPZIXFRTracker, std::get<0>(rpzMaster), std::get<1>(rpzMaster), std::get<2>(rpzMaster), std::get<3>(rpzMaster), std::get<4>(rpzMaster), std::get<5>(rpzMaster), std::get<6>(rpzMaster) * 1024 * 1024, std::get<7>(rpzMaster), std::get<8>(rpzMaster), std::get<9>(rpzMaster), std::get<10>(rpzMaster), generation);
       t.detach();
     }
     catch(const std::exception& e) {
index 6ce143076820e1fc2ea1c554327eb0fce07d0005..4323bd0c6cc9b5bbde4750bba9a2bb54b3856ff2 100644 (file)
@@ -69,7 +69,7 @@ extern GlobalStateHolder<LuaConfigItems> g_luaconfs;
 
 struct luaConfigDelayedThreads
 {
-  std::vector<std::tuple<std::vector<ComboAddress>, boost::optional<DNSFilterEngine::Policy>, uint32_t, size_t, TSIGTriplet, size_t, ComboAddress, uint16_t, std::shared_ptr<SOARecordContent>, std::string> > rpzMasterThreads;
+  std::vector<std::tuple<std::vector<ComboAddress>, boost::optional<DNSFilterEngine::Policy>, bool, uint32_t, size_t, TSIGTriplet, size_t, ComboAddress, uint16_t, std::shared_ptr<SOARecordContent>, std::string> > rpzMasterThreads;
 };
 
 void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads);
index 8fa346b74d2189e1c12493426ebc3916cd167994..e73d7e7d23114fab22f191e8080c7e034ef38f3c 100644 (file)
@@ -113,6 +113,7 @@ static const oid emptyQueriesOID[] = { RECURSOR_STATS_OID, 94 };
 static const oid dnssecAuthenticDataQueriesOID[] = { RECURSOR_STATS_OID, 95 };
 static const oid dnssecCheckDisabledQueriesOID[] = { RECURSOR_STATS_OID, 96 };
 static const oid variableResponsesOID[] = { RECURSOR_STATS_OID, 97 };
+static const oid specialMemoryUsageOID[] = { RECURSOR_STATS_OID, 98 };
 
 static std::unordered_map<oid, std::string> s_statsMap;
 
@@ -302,6 +303,7 @@ RecursorSNMPAgent::RecursorSNMPAgent(const std::string& name, const std::string&
   registerCounter64Stat("policy-result-nodata", policyResultNodataOID, OID_LENGTH(policyResultNodataOID));
   registerCounter64Stat("policy-result-truncate", policyResultTruncateOID, OID_LENGTH(policyResultTruncateOID));
   registerCounter64Stat("policy-result-custom", policyResultCustomOID, OID_LENGTH(policyResultCustomOID));
+  registerCounter64Stat("special-memory-usage", specialMemoryUsageOID, OID_LENGTH(specialMemoryUsageOID));
 
 #endif /* HAVE_NET_SNMP */
 }
index 0259c6875832a926e240e8f58c16b257765ff087..277d341063c5f4055f39e517dd112597ca4488dc 100644 (file)
@@ -113,9 +113,11 @@ map<string,string> getAllStatsMap()
     ret.insert(make_pair(atomic.first, std::to_string(atomic.second->load())));
   }
 
-  for(const auto& the64bitmembers :  d_get64bitmembers) { 
+  for(const auto& the64bitmembers :  d_get64bitmembers) {
     if(the64bitmembers.first == "cache-bytes" || the64bitmembers.first=="packetcache-bytes")
       continue; // too slow for 'get-all'
+    if(the64bitmembers.first == "special-memory-usage")
+      continue; // too slow for 'get-all'
     ret.insert(make_pair(the64bitmembers.first, std::to_string(the64bitmembers.second())));
   }
   Lock l(&d_dynmetricslock);
@@ -173,14 +175,13 @@ string doGetParameter(T begin, T end)
 
 static uint64_t dumpNegCache(NegCache& negcache, int fd)
 {
-  FILE* fp=fdopen(dup(fd), "w");
+  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(dup(fd), "w"), fclose);
   if(!fp) { // dup probably failed
     return 0;
   }
   uint64_t ret;
-  fprintf(fp, "; negcache dump from thread follows\n;\n");
-  ret = negcache.dumpToFile(fp);
-  fclose(fp);
+  fprintf(fp.get(), "; negcache dump from thread follows\n;\n");
+  ret = negcache.dumpToFile(fp.get());
   return ret;
 }
 
@@ -307,14 +308,13 @@ string doDumpRPZ(T begin, T end)
     return "Error opening dump file for writing: "+string(strerror(errno))+"\n";
   }
 
-  FILE* fp = fdopen(fd, "w");
+  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(fd, "w"), fclose);
   if (!fp) {
     close(fd);
     return "Error converting file descriptor: "+string(strerror(errno))+"\n";
   }
 
-  zone->dump(fp);
-  fclose(fp);
+  zone->dump(fp.get());
 
   return "done\n";
 }
@@ -1024,7 +1024,8 @@ void registerAllStats()
 
   addGetStat("uptime", calculateUptime);
   addGetStat("real-memory-usage", boost::bind(getRealMemoryUsage, string()));
-  addGetStat("fd-usage", boost::bind(getOpenFileDescriptors, string()));  
+  addGetStat("special-memory-usage", boost::bind(getSpecialMemoryUsage, string()));
+  addGetStat("fd-usage", boost::bind(getOpenFileDescriptors, string()));
 
   //  addGetStat("query-rate", getQueryRate);
   addGetStat("user-msec", getUserTimeMsec);
index 29262159e6990626974b4bc5930d72a050c47d39..e474b0d218b3d093e34653616f2fa58522a9d42b 100644 (file)
@@ -150,13 +150,6 @@ bool RecursorPacketCache::getResponsePacket(unsigned int tag, const std::string&
 }
 
 
-void RecursorPacketCache::insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, uint16_t ecsBegin, uint16_t ecsEnd)
-{
-  vState valState;
-  boost::optional<RecProtoBufMessage> pb(boost::none);
-  insertResponsePacket(tag, qhash, std::move(query), qname, qtype, qclass, std::move(responsePacket), now, ttl, valState, ecsBegin, ecsEnd, std::move(pb));
-}
-
 void RecursorPacketCache::insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, const vState& valState, uint16_t ecsBegin, uint16_t ecsEnd, boost::optional<RecProtoBufMessage>&& protobufMessage)
 {
   auto& idx = d_packetCache.get<HashTag>();
@@ -226,11 +219,11 @@ void RecursorPacketCache::doPruneTo(unsigned int maxCached)
 
 uint64_t RecursorPacketCache::doDump(int fd)
 {
-  FILE* fp=fdopen(dup(fd), "w");
+  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(dup(fd), "w"), fclose);
   if(!fp) { // dup probably failed
     return 0;
   }
-  fprintf(fp, "; main packet cache dump from thread follows\n;\n");
+  fprintf(fp.get(), "; main packet cache dump from thread follows\n;\n");
   const auto& sidx=d_packetCache.get<1>();
 
   uint64_t count=0;
@@ -238,13 +231,12 @@ uint64_t RecursorPacketCache::doDump(int fd)
   for(auto i=sidx.cbegin(); i != sidx.cend(); ++i) {
     count++;
     try {
-      fprintf(fp, "%s %" PRId64 " %s  ; tag %d\n", i->d_name.toString().c_str(), static_cast<int64_t>(i->d_ttd - now), DNSRecordContent::NumberToType(i->d_type).c_str(), i->d_tag);
+      fprintf(fp.get(), "%s %" PRId64 " %s  ; tag %d\n", i->d_name.toString().c_str(), static_cast<int64_t>(i->d_ttd - now), DNSRecordContent::NumberToType(i->d_type).c_str(), i->d_tag);
     }
     catch(...) {
-      fprintf(fp, "; error printing '%s'\n", i->d_name.empty() ? "EMPTY" : i->d_name.toString().c_str());
+      fprintf(fp.get(), "; error printing '%s'\n", i->d_name.empty() ? "EMPTY" : i->d_name.toString().c_str());
     }
   }
-  fclose(fp);
   return count;
 
 }
index 36a76c650a1183320a382e2241099a498479a818..395d9101c8db2b762a7dc8b2130574d891f7213c 100644 (file)
@@ -56,7 +56,6 @@ public:
   bool getResponsePacket(unsigned int tag, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, uint32_t* qhash);
   bool getResponsePacket(unsigned int tag, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, uint16_t* ecsBegin, uint16_t* ecsEnd, RecProtoBufMessage* protobufMessage);
   bool getResponsePacket(unsigned int tag, const std::string& queryPacket, DNSName& qname, uint16_t* qtype, uint16_t* qclass, time_t now, std::string* responsePacket, uint32_t* age, vState* valState, uint32_t* qhash, uint16_t* ecsBegin, uint16_t* ecsEnd, RecProtoBufMessage* protobufMessage);
-  void insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, uint16_t ecsBegin, uint16_t ecsEnd);
   void insertResponsePacket(unsigned int tag, uint32_t qhash, std::string&& query, const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& responsePacket, time_t now, uint32_t ttl, const vState& valState, uint16_t ecsBegin, uint16_t ecsEnd, boost::optional<RecProtoBufMessage>&& protobufMessage);
   void doPruneTo(unsigned int maxSize=250000);
   uint64_t doDump(int fd);
index 7dea10c3921cfeba81d8518629f6fd9a7227bd97..7e0bf054ce2b5c4193c5bdd03cdd289492588fce 100644 (file)
@@ -404,7 +404,7 @@ bool MemRecursorCache::doAgeCache(time_t now, const DNSName& name, uint16_t qtyp
   return false;
 }
 
-bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName &qname, const QType& qt, const ComboAddress& who, bool requireAuth, vState newState)
+bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName &qname, const QType& qt, const ComboAddress& who, bool requireAuth, vState newState, boost::optional<time_t> capTTD)
 {
   bool updated = false;
   uint16_t qtype = qt.getCode();
@@ -415,6 +415,9 @@ bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName &qname,
     }
 
     entry->d_state = newState;
+    if (capTTD) {
+      entry->d_ttd = std::min(entry->d_ttd, *capTTD);
+    }
     return true;
   }
 
@@ -427,6 +430,9 @@ bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName &qname,
       continue;
 
     i->d_state = newState;
+    if (capTTD) {
+      i->d_ttd = std::min(i->d_ttd, *capTTD);
+    }
     updated = true;
 
     if(qtype != QType::ANY && qtype != QType::ADDR) // normally if we have a hit, we are done
@@ -438,11 +444,11 @@ bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName &qname,
 
 uint64_t MemRecursorCache::doDump(int fd)
 {
-  FILE* fp=fdopen(dup(fd), "w");
+  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(dup(fd), "w"), fclose);
   if(!fp) { // dup probably failed
     return 0;
   }
-  fprintf(fp, "; main record cache dump from thread follows\n;\n");
+  fprintf(fp.get(), "; main record cache dump from thread follows\n;\n");
   const auto& sidx=d_cache.get<SequencedTag>();
 
   uint64_t count=0;
@@ -451,23 +457,22 @@ uint64_t MemRecursorCache::doDump(int fd)
     for(const auto j : i.d_records) {
       count++;
       try {
-        fprintf(fp, "%s %" PRId64 " IN %s %s ; (%s) auth=%i %s\n", i.d_qname.toString().c_str(), static_cast<int64_t>(i.d_ttd - now), DNSRecordContent::NumberToType(i.d_qtype).c_str(), j->getZoneRepresentation().c_str(), vStates[i.d_state], i.d_auth, i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str());
+        fprintf(fp.get(), "%s %" PRId64 " IN %s %s ; (%s) auth=%i %s\n", i.d_qname.toString().c_str(), static_cast<int64_t>(i.d_ttd - now), DNSRecordContent::NumberToType(i.d_qtype).c_str(), j->getZoneRepresentation().c_str(), vStates[i.d_state], i.d_auth, i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str());
       }
       catch(...) {
-        fprintf(fp, "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str());
+        fprintf(fp.get(), "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str());
       }
     }
     for(const auto &sig : i.d_signatures) {
       count++;
       try {
-        fprintf(fp, "%s %" PRId64 " IN RRSIG %s ; %s\n", i.d_qname.toString().c_str(), static_cast<int64_t>(i.d_ttd - now), sig->getZoneRepresentation().c_str(), i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str());
+        fprintf(fp.get(), "%s %" PRId64 " IN RRSIG %s ; %s\n", i.d_qname.toString().c_str(), static_cast<int64_t>(i.d_ttd - now), sig->getZoneRepresentation().c_str(), i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str());
       }
       catch(...) {
-        fprintf(fp, "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str());
+        fprintf(fp.get(), "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str());
       }
     }
   }
-  fclose(fp);
   return count;
 }
 
index 9b4f3f6e077ef0e3a7bf0a081685f8f681ff1565..e9a44c86c747c4edd173fa3d9402b4e47e343a91 100644 (file)
@@ -64,7 +64,7 @@ public:
 
   int doWipeCache(const DNSName& name, bool sub, uint16_t qtype=0xffff);
   bool doAgeCache(time_t now, const DNSName& name, uint16_t qtype, uint32_t newTTL);
-  bool updateValidationStatus(time_t now, const DNSName &qname, const QType& qt, const ComboAddress& who, bool requireAuth, vState newState);
+  bool updateValidationStatus(time_t now, const DNSName &qname, const QType& qt, const ComboAddress& who, bool requireAuth, vState newState, boost::optional<time_t> capTTD);
 
   uint64_t cacheHits, cacheMisses;
 
@@ -89,7 +89,7 @@ private:
     DNSName d_qname;
     Netmask d_netmask;
     mutable vState d_state;
-    time_t d_ttd;
+    mutable time_t d_ttd;
     uint16_t d_qtype;
     bool d_auth;
   };
index 23f4287a81e7f2655b4b6bb33d535f44138574c0..2af4621d08d6daefe3406205d5f5e20e586246ba 100644 (file)
@@ -809,6 +809,14 @@ variableResponses OBJECT-TYPE
         "Number of variable responses"
     ::= { stats 97 }
 
+specialMemoryUsage OBJECT-TYPE
+    SYNTAX CounterBasedGauge64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Memory usage (more precise bbut expensive to retrieve)"
+    ::= { stats 98 }
+
 ---
 --- Traps / Notifications
 ---
@@ -927,6 +935,7 @@ recGroup OBJECT-GROUP
         noednsOutqueries,
         uptime,
         realMemoryUsage,
+        specialMemoryUsage,
         fdUsage,
         userMsec,
         sysMsec,
index 6f6d832a942a7bb0009bceea5e4e30d389f489ee..3b833eb2ec69309703d1c4b05aaa2335f2137824 100644 (file)
@@ -18,16 +18,31 @@ An RPZ can be loaded from file or slaved from a master. To load from file, use f
 
 .. code-block:: Lua
 
-    rpzFile("dblfilename", {defpol=Policy.Custom, defcontent="badserver.example.com"})
+    rpzFile("dblfilename")
 
 To slave from a master and start IXFR to get updates, use for example:
 
 .. code-block:: Lua
 
-    rpzMaster("192.0.2.4", "policy.rpz", {defpol=Policy.Drop})
+    rpzMaster("192.0.2.4", "policy.rpz")
 
 In this example, 'policy.rpz' denotes the name of the zone to query for.
 
+The action to be taken on a match is defined by the zone itself, but in some cases it might be interesting to be able to override it, and always apply the same action
+regardless of the one specified in the RPZ zone. To load from file and override the default action with a custom CNAME to badserver.example.com., use for example:
+
+.. code-block:: Lua
+
+    rpzFile("dblfilename", {defpol=Policy.Custom, defcontent="badserver.example.com"})
+
+To instead drop all queries matching a rule, while slaving from a master:
+
+.. code-block:: Lua
+
+    rpzMaster("192.0.2.4", "policy.rpz", {defpol=Policy.Drop})
+
+Note that since 4.2.0, it is possible for the override policy specified via 'defpol' to no longer be applied to local data entries present in the zone by setting the 'defpolOverrideLocalData' parameter to false.
+
 As of version 4.2.0, the first parameter of :func:`rpzMaster` can be a list of addresses for failover:
 
     rpzMaster({"192.0.2.4","192.0.2.5:5301"}, "policy.rpz", {defpol=Policy.Drop})
@@ -61,13 +76,20 @@ RPZ settings
 
 These options can be set in the ``settings`` of both :func:`rpzMaster` and :func:`rpzFile`.
 
+defcontent
+^^^^^^^^^^
+CNAME field to return in case of defpol=Policy.Custom
+
 defpol
 ^^^^^^
 Default policy: `Policy.Custom`_, `Policy.Drop`_, `Policy.NXDOMAIN`_, `Policy.NODATA`_, `Policy.Truncate`_, `Policy.NoAction`_.
 
-defcontent
-^^^^^^^^^^
-CNAME field to return in case of defpol=Policy.Custom
+defpolOverrideLocalData
+^^^^^^^^^^^^^^^^^^^^^^^
+.. versionadded:: 4.2.0
+  Before 4.2.0 local data entries are always overridden by the default policy.
+
+Whether local data entries should be overridden by the default policy. Default is true.
 
 defttl
 ^^^^^^
index 7847d870608b489f4658e5dc89a13424d0c0647f..dc9a1fcdef9dc1089277788e56e2be7678e03e4c 100644 (file)
@@ -147,7 +147,17 @@ The DNSQuestion object contains at least the following fields:
 
      :param int type: The type of record to add, can be ``pdns.AAAA`` etc.
      :param str content: The content of the record, will be parsed into wireformat based on the ``type``
-     :param int ttl: The TTL in seconds for this record
+     :param int ttl: The TTL in seconds for this record, defaults to 3600
+     :param DNSName name: The name of this record, defaults to :attr:`DNSQuestion.qname`
+
+  .. method:: DNSQuestion:addRecord(type, content, place, [ttl, name])
+
+     Add a record of ``type`` with ``content`` in section ``place``.
+
+     :param int type: The type of record to add, can be ``pdns.AAAA`` etc.
+     :param str content: The content of the record, will be parsed into wireformat based on the ``type``
+     :param int place: The section to place the record, see :attr:`DNSRecord.place`
+     :param int ttl: The TTL in seconds for this record, defaults to 3600
      :param DNSName name: The name of this record, defaults to :attr:`DNSQuestion.qname`
 
   .. method:: DNSQuestion:addPolicyTag(tag)
index 356b2f34d93ad1474e91c44e860711c058e1fd63..df6ca553c554371dd2b2ee4ebb8b327d7f0a3ace 100644 (file)
@@ -15,3 +15,9 @@ These are some functions that don't really have a place in one of the other cate
 .. function:: getRecursorThreadId() -> int
 
   returns an unsigned integer identifying the thread handling the current request.
+
+.. function:: pdnsrandom([maximum])
+
+  Get a random number.
+
+  :param int maximum: The largest number to return. This is 2^32 by default.
index 5d44334fdbc4aabcc267eb68570542a470ce9047..2e6200539e7abdd4a5f7eb0ec9b14a575cc15a66 100644 (file)
@@ -712,6 +712,17 @@ Path to a lua file to manipulate the Recursor's answers. See :doc:`lua-scripting
 The interval between calls to the Lua user defined `maintenance()` function in seconds.
 See :ref:`hooks-maintenance-callback`
 
+.. _setting-max-cache-bogus-ttl:
+
+``max-cache-bogus-ttl``
+-----------------------
+.. versionadded:: 4.2.0
+
+-  Integer
+-  Default: 3600
+
+Maximum number of seconds to cache an item in the DNS cache (negative or positive) if its DNSSEC validation failed, no matter what the original TTL specified, to reduce the impact of a broken domain.
+
 .. _setting-max-cache-entries:
 
 ``max-cache-entries``
index 6552486b7f11a073885f13f65e335e0d78e2c58d..0f5325c33a4f0fe51f51226267b7d299562313ca 100644 (file)
@@ -111,11 +111,14 @@ void NegCache::add(const NegCacheEntry& ne) {
  * \param qtype The type of the entry to replace
  * \param newState The new validation state
  */
-void NegCache::updateValidationStatus(const DNSName& qname, const QType& qtype, const vState newState) {
+void NegCache::updateValidationStatus(const DNSName& qname, const QType& qtype, const vState newState, boost::optional<uint32_t> capTTD) {
   auto range = d_negcache.equal_range(tie(qname, qtype));
 
   if (range.first != range.second) {
     range.first->d_validationState = newState;
+    if (capTTD) {
+      range.first->d_ttd = std::min(range.first->d_ttd, *capTTD);
+    }
   }
 }
 
index b78951e21d68fd2af5b3748eaaacbcc477442905..63eae63054efbe4facfe44366877ab1a5cbe9766 100644 (file)
@@ -49,7 +49,7 @@ class NegCache : public boost::noncopyable {
       DNSName d_name;                     // The denied name
       QType d_qtype;                      // The denied type
       DNSName d_auth;                     // The denying name (aka auth)
-      uint32_t d_ttd;                     // Timestamp when this entry should die
+      mutable uint32_t d_ttd;             // Timestamp when this entry should die
       recordsAndSignatures authoritySOA;  // The upstream SOA record and RRSIGs
       recordsAndSignatures DNSSECRecords; // The upstream NSEC(3) and RRSIGs
       mutable vState d_validationState{Indeterminate};
@@ -59,7 +59,7 @@ class NegCache : public boost::noncopyable {
     };
 
     void add(const NegCacheEntry& ne);
-    void updateValidationStatus(const DNSName& qname, const QType& qtype, const vState newState);
+    void updateValidationStatus(const DNSName& qname, const QType& qtype, const vState newState, boost::optional<uint32_t> capTTD);
     bool get(const DNSName& qname, const QType& qtype, const struct timeval& now, const NegCacheEntry** ne, bool typeMustMatch=false);
     bool getRootNXTrust(const DNSName& qname, const struct timeval& now, const NegCacheEntry** ne);
     uint64_t count(const DNSName& qname) const;
index eb38f362c57a98b98a239291952b195e2664727a..8736f32aee43dac397c1f50c1e6487d43c2a8dbb 100644 (file)
@@ -121,6 +121,7 @@ static void init(bool debug=false)
   SyncRes::s_maxtotusec = 1000*7000;
   SyncRes::s_maxdepth = 40;
   SyncRes::s_maxnegttl = 3600;
+  SyncRes::s_maxbogusttl = 3600;
   SyncRes::s_maxcachettl = 86400;
   SyncRes::s_packetcachettl = 3600;
   SyncRes::s_packetcacheservfailttl = 60;
@@ -1852,6 +1853,8 @@ BOOST_AUTO_TEST_CASE(test_root_nx_trust) {
       return 0;
     });
 
+  SyncRes::s_maxnegttl = 3600;
+
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
@@ -1862,7 +1865,8 @@ BOOST_AUTO_TEST_CASE(test_root_nx_trust) {
   ret.clear();
   res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-  BOOST_CHECK_EQUAL(ret.size(), 1);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK_LE(ret[0].d_ttl, SyncRes::s_maxnegttl);
   /* one for target1 and one for the entire TLD */
   BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 2);
 
@@ -1926,8 +1930,8 @@ BOOST_AUTO_TEST_CASE(test_root_nx_trust_specific) {
   res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_REQUIRE_EQUAL(ret.size(), 1);
-  BOOST_REQUIRE(ret[0].d_type == QType::A);
   BOOST_CHECK_EQUAL(ret[0].d_name, target2);
+  BOOST_REQUIRE(ret[0].d_type == QType::A);
   BOOST_CHECK(getRR<ARecordContent>(ret[0])->getCA() == ComboAddress("192.0.2.2"));
 
   BOOST_CHECK_EQUAL(SyncRes::t_sstorage.negcache.size(), 1);
@@ -4414,7 +4418,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_no_rrsig) {
         char addr[] = "a.root-servers.net.";
         for (char idx = 'a'; idx <= 'm'; idx++) {
           addr[0] = idx;
-          addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600);
+          addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 86400);
         }
 
         /* No RRSIG */
@@ -4436,6 +4440,9 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_no_rrsig) {
       return 0;
     });
 
+  SyncRes::s_maxcachettl = 86400;
+  SyncRes::s_maxbogusttl = 3600;
+
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
@@ -4451,6 +4458,10 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_no_rrsig) {
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 13);
+  /* check that we capped the TTL to max-cache-bogus-ttl */
+  for (const auto& record : ret) {
+    BOOST_CHECK_LE(record.d_ttl, SyncRes::s_maxbogusttl);
+  }
   BOOST_CHECK_EQUAL(queriesCount, 1);
 }
 
@@ -9321,6 +9332,76 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_negcache_validity) {
   BOOST_CHECK_EQUAL(queriesCount, 4);
 }
 
+BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_negcache_bogus_validity) {
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("com.");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys);
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+  const time_t fixedNow = sr->getNow().tv_sec;
+
+  sr->setAsyncCallback([target,&queriesCount,keys,fixedNow](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* res, bool* chained) {
+      queriesCount++;
+
+      DNSName auth = domain;
+      auth.chopOff();
+
+      if (type == QType::DS || type == QType::DNSKEY) {
+        return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
+      }
+      else {
+        setLWResult(res, RCode::NoError, true, false, true);
+        addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 86400);
+        addRRSIG(keys, res->d_records, domain, 86400);
+        addNSECRecordToLW(domain, DNSName("z."), { QType::NSEC, QType::RRSIG }, 86400, res->d_records);
+        /* no RRSIG */
+        return 1;
+      }
+
+      return 0;
+    });
+
+  SyncRes::s_maxnegttl = 3600;
+  SyncRes::s_maxbogusttl = 360;
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_REQUIRE_EQUAL(ret.size(), 3);
+  BOOST_CHECK_EQUAL(queriesCount, 4);
+
+  /* check that the entry has been negatively cached but not longer than s_maxbogusttl */
+  const NegCache::NegCacheEntry* ne = nullptr;
+  BOOST_CHECK_EQUAL(SyncRes::t_sstorage.negcache.size(), 1);
+  BOOST_REQUIRE_EQUAL(SyncRes::t_sstorage.negcache.get(target, QType(QType::A), sr->getNow(), &ne), true);
+  BOOST_CHECK_EQUAL(ne->d_ttd, fixedNow + SyncRes::s_maxbogusttl);
+  BOOST_CHECK_EQUAL(ne->d_validationState, Bogus);
+  BOOST_CHECK_EQUAL(ne->authoritySOA.records.size(), 1);
+  BOOST_CHECK_EQUAL(ne->authoritySOA.signatures.size(), 1);
+  BOOST_CHECK_EQUAL(ne->DNSSECRecords.records.size(), 1);
+  BOOST_CHECK_EQUAL(ne->DNSSECRecords.signatures.size(), 0);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_REQUIRE_EQUAL(ret.size(), 3);
+  BOOST_CHECK_EQUAL(queriesCount, 4);
+}
+
 BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_cache_validity) {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
@@ -9390,7 +9471,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_secure) {
     Validation is optional, and the first query does not ask for it,
     so the answer is cached as Indeterminate.
     The second query asks for validation, answer should be marked as
-    Secure.
+    Secure, after just-in-time validation.
   */
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
@@ -9549,7 +9630,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_bogus) {
       else {
         if (domain == target && type == QType::A) {
           setLWResult(res, 0, true, false, true);
-          addRecordToLW(res, target, QType::A, "192.0.2.1");
+          addRecordToLW(res, target, QType::A, "192.0.2.1", DNSResourceRecord::ANSWER, 86400);
           /* no RRSIG */
           return 1;
         }
@@ -9558,6 +9639,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_bogus) {
       return 0;
     });
 
+  SyncRes::s_maxbogusttl = 3600;
+
   vector<DNSRecord> ret;
   /* first query does not require validation */
   sr->setDNSSECValidationRequested(false);
@@ -9567,6 +9650,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_bogus) {
   BOOST_REQUIRE_EQUAL(ret.size(), 1);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::A);
+    BOOST_CHECK_EQUAL(record.d_ttl, 86400);
   }
   BOOST_CHECK_EQUAL(queriesCount, 1);
 
@@ -9577,9 +9661,26 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_bogus) {
   res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  /* check that we correctly capped the TTD for a Bogus record after
+     just-in-time validation */
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  for (const auto& record : ret) {
+    BOOST_CHECK(record.d_type == QType::A);
+    BOOST_CHECK_EQUAL(record.d_ttl, SyncRes::s_maxbogusttl);
+  }
+  BOOST_CHECK_EQUAL(queriesCount, 3);
+
+  ret.clear();
+  /* third time also _does_ require validation, so we
+     can check that the cache has been updated */
+  sr->setDNSSECValidationRequested(true);
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 1);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::A);
+    BOOST_CHECK_EQUAL(record.d_ttl, SyncRes::s_maxbogusttl);
   }
   BOOST_CHECK_EQUAL(queriesCount, 3);
 }
@@ -9763,13 +9864,13 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_bogus) {
       else {
         if (domain == target && type == QType::A) {
           setLWResult(res, 0, true, false, true);
-          addRecordToLW(res, target, QType::CNAME, cnameTarget.toString());
-          addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1");
+          addRecordToLW(res, target, QType::CNAME, cnameTarget.toString(), DNSResourceRecord::ANSWER, 86400);
+          addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1", DNSResourceRecord::ANSWER, 86400);
           /* no RRSIG */
           return 1;
         } else if (domain == cnameTarget && type == QType::A) {
           setLWResult(res, 0, true, false, true);
-          addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1");
+          addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1", DNSResourceRecord::ANSWER, 86400);
           /* no RRSIG */
           return 1;
         }
@@ -9778,6 +9879,9 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_bogus) {
       return 0;
     });
 
+  SyncRes::s_maxbogusttl = 60;
+  SyncRes::s_maxnegttl = 3600;
+
   vector<DNSRecord> ret;
   /* first query does not require validation */
   sr->setDNSSECValidationRequested(false);
@@ -9787,6 +9891,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_bogus) {
   BOOST_REQUIRE_EQUAL(ret.size(), 2);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::CNAME || record.d_type == QType::A);
+    BOOST_CHECK_EQUAL(record.d_ttl, 86400);
   }
   BOOST_CHECK_EQUAL(queriesCount, 2);
 
@@ -9798,8 +9903,25 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_bogus) {
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 2);
+  /* check that we correctly capped the TTD for a Bogus record after
+     just-in-time validation */
+  for (const auto& record : ret) {
+    BOOST_CHECK(record.d_type == QType::CNAME || record.d_type == QType::A);
+    BOOST_CHECK_EQUAL(record.d_ttl, SyncRes::s_maxbogusttl);
+  }
+  BOOST_CHECK_EQUAL(queriesCount, 5);
+
+  ret.clear();
+  /* and a third time to make sure that the validation status (and TTL!)
+     was properly updated in the cache */
+  sr->setDNSSECValidationRequested(true);
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2);
   for (const auto& record : ret) {
     BOOST_CHECK(record.d_type == QType::CNAME || record.d_type == QType::A);
+    BOOST_CHECK_EQUAL(record.d_ttl, SyncRes::s_maxbogusttl);
   }
   BOOST_CHECK_EQUAL(queriesCount, 5);
 }
@@ -10134,8 +10256,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_bogus) {
       }
       else {
         setLWResult(res, RCode::NoError, true, false, true);
-        addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
-        addRRSIG(keys, res->d_records, domain, 300);
+        addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 86400);
+        addRRSIG(keys, res->d_records, domain, 86400);
         /* no denial */
         return 1;
       }
@@ -10143,6 +10265,10 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_bogus) {
       return 0;
     });
 
+  SyncRes::s_maxbogusttl = 60;
+  SyncRes::s_maxnegttl = 3600;
+  const auto now = sr->getNow().tv_sec;
+
   vector<DNSRecord> ret;
   /* first query does not require validation */
   sr->setDNSSECValidationRequested(false);
@@ -10150,6 +10276,11 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_bogus) {
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate);
   BOOST_REQUIRE_EQUAL(ret.size(), 2);
+  for (const auto& record : ret) {
+    if (record.d_type == QType::SOA) {
+      BOOST_CHECK_EQUAL(record.d_ttl, SyncRes::s_maxnegttl);
+    }
+  }
   BOOST_CHECK_EQUAL(queriesCount, 1);
   const NegCache::NegCacheEntry* ne = nullptr;
   BOOST_CHECK_EQUAL(SyncRes::t_sstorage.negcache.size(), 1);
@@ -10157,6 +10288,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_bogus) {
   BOOST_CHECK_EQUAL(ne->d_validationState, Indeterminate);
   BOOST_CHECK_EQUAL(ne->authoritySOA.records.size(), 1);
   BOOST_CHECK_EQUAL(ne->authoritySOA.signatures.size(), 1);
+  BOOST_CHECK_EQUAL(ne->d_ttd, now + SyncRes::s_maxnegttl);
   BOOST_CHECK_EQUAL(ne->DNSSECRecords.records.size(), 0);
   BOOST_CHECK_EQUAL(ne->DNSSECRecords.signatures.size(), 0);
 
@@ -10167,11 +10299,35 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_bogus) {
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 2);
+  for (const auto& record : ret) {
+    BOOST_CHECK_EQUAL(record.d_ttl, SyncRes::s_maxbogusttl);
+  }
+  BOOST_CHECK_EQUAL(queriesCount, 4);
+  BOOST_REQUIRE_EQUAL(SyncRes::t_sstorage.negcache.get(target, QType(QType::A), sr->getNow(), &ne), true);
+  BOOST_CHECK_EQUAL(ne->d_validationState, Bogus);
+  BOOST_CHECK_EQUAL(ne->authoritySOA.records.size(), 1);
+  BOOST_CHECK_EQUAL(ne->authoritySOA.signatures.size(), 1);
+  BOOST_CHECK_EQUAL(ne->d_ttd, now + SyncRes::s_maxbogusttl);
+  BOOST_CHECK_EQUAL(ne->DNSSECRecords.records.size(), 0);
+  BOOST_CHECK_EQUAL(ne->DNSSECRecords.signatures.size(), 0);
+
+  ret.clear();
+  /* third one _does_ not require validation, we just check that
+     the cache (status and TTL) has been correctly updated */
+  sr->setDNSSECValidationRequested(false);
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2);
+  for (const auto& record : ret) {
+    BOOST_CHECK_EQUAL(record.d_ttl, SyncRes::s_maxbogusttl);
+  }
   BOOST_CHECK_EQUAL(queriesCount, 4);
   BOOST_REQUIRE_EQUAL(SyncRes::t_sstorage.negcache.get(target, QType(QType::A), sr->getNow(), &ne), true);
   BOOST_CHECK_EQUAL(ne->d_validationState, Bogus);
   BOOST_CHECK_EQUAL(ne->authoritySOA.records.size(), 1);
   BOOST_CHECK_EQUAL(ne->authoritySOA.signatures.size(), 1);
+  BOOST_CHECK_EQUAL(ne->d_ttd, now + SyncRes::s_maxbogusttl);
   BOOST_CHECK_EQUAL(ne->DNSSECRecords.records.size(), 0);
   BOOST_CHECK_EQUAL(ne->DNSSECRecords.signatures.size(), 0);
 }
index e2625c4e30b7ca252bee480a61d73239102be37c..45bcb814a2445f830545af3336b7c426fdeeaf96 100644 (file)
@@ -382,14 +382,11 @@ std::shared_ptr<SyncRes::domainmap_t> parseAuthAndForwards()
   if(!::arg()["forward-zones-file"].empty()) {
     g_log<<Logger::Warning<<"Reading zone forwarding information from '"<<::arg()["forward-zones-file"]<<"'"<<endl;
     SyncRes::AuthDomain ad;
-    FILE *rfp=fopen(::arg()["forward-zones-file"].c_str(), "r");
-
-    if(!rfp) {
+    auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(::arg()["forward-zones-file"].c_str(), "r"), fclose);
+    if(!fp) {
       throw PDNSException("Error opening forward-zones-file '"+::arg()["forward-zones-file"]+"': "+stringerror());
     }
 
-    shared_ptr<FILE> fp=shared_ptr<FILE>(rfp, fclose);
-    
     string line;
     int linenum=0;
     uint64_t before = newMap->size();
index 2112dce2a6cc58a996f7c6cc2aaf98a483de18df..61b4c6b0087f43990b45f14690ec7e67da161365 100644 (file)
@@ -52,5 +52,5 @@ void ResponseStats::submitResponse(DNSPacket &p, bool udpOrTCP) {
     }
   }
 
-  submitResponse(p.qtype.getCode(), buf.length(), udpOrTCP);
+  submitResponse(p.qtype.getCode(), buf.length(), p.d.rcode, udpOrTCP);
 }
index 64ec3abe457410568d1d6fb3206ddac500a8b231..93b2fca51102ea7c6662331e16d813ee9d1cfe78 100644 (file)
@@ -8,7 +8,7 @@
 
 #include "dnsparser.hh"
 
-ResponseStats::ResponseStats() :   d_qtypecounters(new std::atomic<unsigned long>[65536])
+ResponseStats::ResponseStats() :   d_qtypecounters(new std::atomic<unsigned long>[65536]), d_rcodecounters(new std::atomic<unsigned long>[256])
 {
   d_sizecounters.push_back(make_pair(20,0));
   d_sizecounters.push_back(make_pair(40,0));
@@ -21,6 +21,8 @@ ResponseStats::ResponseStats() :   d_qtypecounters(new std::atomic<unsigned long
   d_sizecounters.push_back(make_pair(std::numeric_limits<uint16_t>::max(),0));
   for(unsigned int n =0 ; n < 65535; ++n)
     d_qtypecounters[n] = 0;
+  for(unsigned int n =0 ; n < 256; ++n)
+    d_rcodecounters[n] = 0;
 }
 
 ResponseStats g_rs;
@@ -28,9 +30,15 @@ ResponseStats g_rs;
 static bool pcomp(const pair<uint16_t, uint64_t>&a , const pair<uint16_t, uint64_t>&b)
 {
   return a.first < b.first;
-} 
+}
+
+void ResponseStats::submitResponse(uint16_t qtype,uint16_t respsize, uint8_t rcode, bool udpOrTCP)
+{
+    d_rcodecounters[rcode]++;
+    submitResponse(qtype, respsize, udpOrTCP);
+}
 
-void ResponseStats::submitResponse(uint16_t qtype, uint16_t respsize, bool udpOrTCP) 
+void ResponseStats::submitResponse(uint16_t qtype,uint16_t respsize, bool udpOrTCP)
 {
   d_qtypecounters[qtype]++;
   pair<uint16_t, uint64_t> s(respsize, 0);
@@ -63,6 +71,18 @@ map<uint16_t, uint64_t> ResponseStats::getSizeResponseCounts()
   return ret;
 }
 
+map<uint8_t, uint64_t> ResponseStats::getRCodeResponseCounts()
+{
+  map<uint8_t, uint64_t> ret;
+  uint64_t count;
+  for(unsigned int i = 0 ; i < 256 ; ++i) {
+    count= d_rcodecounters[i];
+    if(count)
+      ret[i]=count;
+  }
+  return ret;
+}
+
 string ResponseStats::getQTypeReport()
 {
   typedef map<uint16_t, uint64_t> qtypenums_t;
@@ -74,4 +94,3 @@ string ResponseStats::getQTypeReport()
   }
   return os.str();
 }
-
index c82cf05c1412cc4dfcada08cf2ccd03fc087595a..884d49d0f20934029ca8673cd9fb7013546b3e71 100644 (file)
@@ -30,12 +30,15 @@ public:
 
   void submitResponse(DNSPacket &p, bool udpOrTCP);
   void submitResponse(uint16_t qtype, uint16_t respsize, bool udpOrTCP);
+  void submitResponse(uint16_t qtype, uint16_t respsize, uint8_t rcode, bool udpOrTCP);
   map<uint16_t, uint64_t> getQTypeResponseCounts();
   map<uint16_t, uint64_t> getSizeResponseCounts();
+  map<uint8_t, uint64_t> getRCodeResponseCounts();
   string getQTypeReport();
 
 private:
   boost::scoped_array<std::atomic<unsigned long>> d_qtypecounters;
+  boost::scoped_array<std::atomic<unsigned long>> d_rcodecounters;
   typedef vector<pair<uint16_t, uint64_t> > sizecounters_t;
   sizecounters_t d_sizecounters;
 };
index 8dc17311c5010d25a1fce100dab5dc6b0d95e5e1..df08141db65b747654da4075a88dd4206232bfff 100644 (file)
@@ -60,7 +60,7 @@ static Netmask makeNetmaskFromRPZ(const DNSName& name)
   return Netmask(v6);
 }
 
-void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zone> zone, bool addOrRemove, boost::optional<DNSFilterEngine::Policy> defpol, uint32_t maxTTL)
+static void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zone> zone, bool addOrRemove, boost::optional<DNSFilterEngine::Policy> defpol, bool defpolOverrideLocal, uint32_t maxTTL)
 {
   static const DNSName drop("rpz-drop."), truncate("rpz-tcp-only."), noaction("rpz-passthru.");
   static const DNSName rpzClientIP("rpz-client-ip"), rpzIP("rpz-ip"),
@@ -68,6 +68,7 @@ void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zon
   static const std::string rpzPrefix("rpz-");
 
   DNSFilterEngine::Policy pol;
+  bool defpolApplied = false;
 
   if(dr.d_class != QClass::IN) {
     return;
@@ -81,6 +82,7 @@ void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zon
     auto crcTarget=crc->getTarget();
     if(defpol) {
       pol=*defpol;
+      defpolApplied = true;
     }
     else if(crcTarget.isRoot()) {
       // cerr<<"Wants NXDOMAIN for "<<dr.d_name<<": ";
@@ -121,8 +123,9 @@ void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zon
     }
   }
   else {
-    if (defpol) {
+    if (defpol && defpolOverrideLocal) {
       pol=*defpol;
+      defpolApplied = true;
     }
     else {
       pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
@@ -131,7 +134,7 @@ void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zon
     }
   }
 
-  if (!defpol || defpol->d_ttl < 0) {
+  if (!defpolApplied || defpol->d_ttl < 0) {
     pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, dr.d_ttl));
   } else {
     pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, static_cast<uint32_t>(pol.d_ttl)));
@@ -169,14 +172,19 @@ void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zon
     else
       zone->rmNSIPTrigger(nm, std::move(pol));
   } else {
-    if(addOrRemove)
-      zone->addQNameTrigger(dr.d_name, std::move(pol));
-    else
+    if(addOrRemove) {
+      /* if we did override the existing policy with the default policy,
+         we might turn two A or AAAA into a CNAME, which would trigger
+         an exception. Let's just ignore it. */
+      zone->addQNameTrigger(dr.d_name, std::move(pol), defpolApplied);
+    }
+    else {
       zone->rmQNameTrigger(dr.d_name, std::move(pol));
+    }
   }
 }
 
-static shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone> zone, boost::optional<DNSFilterEngine::Policy> defpol, uint32_t maxTTL, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, uint16_t axfrTimeout)
+static shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone> zone, boost::optional<DNSFilterEngine::Policy> defpol, bool defpolOverrideLocal, uint32_t maxTTL, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, uint16_t axfrTimeout)
 {
   g_log<<Logger::Warning<<"Loading RPZ zone '"<<zoneName<<"' from "<<master.toStringWithPort()<<endl;
   if(!tt.name.empty())
@@ -206,7 +214,7 @@ static shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master
        continue;
       }
 
-      RPZRecordToPolicy(dr, zone, true, defpol, maxTTL);
+      RPZRecordToPolicy(dr, zone, true, defpol, defpolOverrideLocal, maxTTL);
       nrecords++;
     } 
     axfrNow = time(nullptr);
@@ -223,7 +231,7 @@ static shared_ptr<SOARecordContent> loadRPZFromServer(const ComboAddress& master
 }
 
 // this function is silent - you do the logging
-std::shared_ptr<SOARecordContent> loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, boost::optional<DNSFilterEngine::Policy> defpol, uint32_t maxTTL)
+std::shared_ptr<SOARecordContent> loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, boost::optional<DNSFilterEngine::Policy> defpol, bool defpolOverrideLocal, uint32_t maxTTL)
 {
   shared_ptr<SOARecordContent> sr = nullptr;
   ZoneParserTNG zpt(fname);
@@ -244,7 +252,7 @@ std::shared_ptr<SOARecordContent> loadRPZFromFile(const std::string& fname, std:
       }
       else {
        dr.d_name=dr.d_name.makeRelative(domain);
-       RPZRecordToPolicy(dr, zone, true, defpol, maxTTL);
+       RPZRecordToPolicy(dr, zone, true, defpol, defpolOverrideLocal, maxTTL);
       }
     }
     catch(const PDNSException& pe) {
@@ -297,35 +305,33 @@ static bool dumpZoneToDisk(const DNSName& zoneName, const std::shared_ptr<DNSFil
     return false;
   }
 
-  FILE * fp = fdopen(fd, "w+");
-  if (fp == nullptr) {
+  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(fd, "w+"), fclose);
+  if (!fp) {
     close(fd);
     g_log<<Logger::Warning<<"Unable to open a file pointer to dump the content of the RPZ zone "<<zoneName.toLogString()<<endl;
     return false;
   }
-
   fd = -1;
 
   try {
-    newZone->dump(fp);
+    newZone->dump(fp.get());
   }
   catch(const std::exception& e) {
     g_log<<Logger::Warning<<"Error while dumping the content of the RPZ zone "<<zoneName.toLogString()<<": "<<e.what()<<endl;
-    fclose(fp);
     return false;
   }
 
-  if (fflush(fp) != 0) {
+  if (fflush(fp.get()) != 0) {
     g_log<<Logger::Warning<<"Error while flushing the content of the RPZ zone "<<zoneName.toLogString()<<" to the dump file: "<<strerror(errno)<<endl;
     return false;
   }
 
-  if (fsync(fileno(fp)) != 0) {
+  if (fsync(fileno(fp.get())) != 0) {
     g_log<<Logger::Warning<<"Error while syncing the content of the RPZ zone "<<zoneName.toLogString()<<" to the dump file: "<<strerror(errno)<<endl;
     return false;
   }
 
-  if (fclose(fp) != 0) {
+  if (fclose(fp.release()) != 0) {
     g_log<<Logger::Warning<<"Error while writing the content of the RPZ zone "<<zoneName.toLogString()<<" to the dump file: "<<strerror(errno)<<endl;
     return false;
   }
@@ -338,7 +344,7 @@ static bool dumpZoneToDisk(const DNSName& zoneName, const std::shared_ptr<DNSFil
   return true;
 }
 
-void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DNSFilterEngine::Policy> defpol, uint32_t maxTTL, size_t zoneIdx, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, const uint16_t axfrTimeout, std::shared_ptr<SOARecordContent> sr, std::string dumpZoneFileName, uint64_t configGeneration)
+void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DNSFilterEngine::Policy> defpol, bool defpolOverrideLocal, uint32_t maxTTL, size_t zoneIdx, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, const uint16_t axfrTimeout, std::shared_ptr<SOARecordContent> sr, std::string dumpZoneFileName, uint64_t configGeneration)
 {
   setThreadName("pdns-r/RPZIXFR");
   bool isPreloaded = sr != nullptr;
@@ -360,7 +366,7 @@ void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DN
     std::shared_ptr<DNSFilterEngine::Zone> newZone = std::make_shared<DNSFilterEngine::Zone>(*oldZone);
     for (const auto& master : masters) {
       try {
-        sr = loadRPZFromServer(master, zoneName, newZone, defpol, maxTTL, tt, maxReceivedBytes, localAddress, axfrTimeout);
+        sr = loadRPZFromServer(master, zoneName, newZone, defpol, defpolOverrideLocal, maxTTL, tt, maxReceivedBytes, localAddress, axfrTimeout);
         if(refresh == 0) {
           refresh = sr->d_st.refresh;
         }
@@ -473,7 +479,7 @@ void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DN
        else {
           totremove++;
          g_log<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had removal of "<<rr.d_name<<" from RPZ zone "<<zoneName<<endl;
-         RPZRecordToPolicy(rr, newZone, false, defpol, maxTTL);
+         RPZRecordToPolicy(rr, newZone, false, defpol, defpolOverrideLocal, maxTTL);
        }
       }
 
@@ -490,7 +496,7 @@ void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DN
        else {
           totadd++;
          g_log<<(g_logRPZChanges ? Logger::Info : Logger::Debug)<<"Had addition of "<<rr.d_name<<" to RPZ zone "<<zoneName<<endl;
-         RPZRecordToPolicy(rr, newZone, true, defpol, maxTTL);
+         RPZRecordToPolicy(rr, newZone, true, defpol, defpolOverrideLocal, maxTTL);
        }
       }
     }
index b167ee7f19a2d0217ff33daa1d6a043112a85b1a..7a2047a2269338e664df61619e9ad76725b871bd 100644 (file)
@@ -26,9 +26,8 @@
 
 extern bool g_logRPZChanges;
 
-std::shared_ptr<SOARecordContent> loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, boost::optional<DNSFilterEngine::Policy> defpol, uint32_t maxTTL);
-void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zone> zone, bool addOrRemove, boost::optional<DNSFilterEngine::Policy> defpol, uint32_t maxTTL);
-void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DNSFilterEngine::Policy> defpol, uint32_t maxTTL, size_t zoneIdx, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, const uint16_t axfrTimeout, shared_ptr<SOARecordContent> sr, std::string dumpZoneFileName, uint64_t configGeneration);
+std::shared_ptr<SOARecordContent> loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, boost::optional<DNSFilterEngine::Policy> defpol, bool defpolOverrideLocal, uint32_t maxTTL);
+void RPZIXFRTracker(const std::vector<ComboAddress>& masters, boost::optional<DNSFilterEngine::Policy> defpol, bool defpolOverrideLocal, uint32_t maxTTL, size_t zoneIdx, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, const uint16_t axfrTimeout, shared_ptr<SOARecordContent> sr, std::string dumpZoneFileName, uint64_t configGeneration);
 
 struct rpzStats
 {
index d613f1126409018814b37310bf58fa2154f4b2ee..1a504ef30232bb6826d8606fbf7bba7075f36688 100644 (file)
@@ -552,12 +552,11 @@ struct BigDNSPacketRefTest
 
 struct TCacheComp
 {
-  bool operator()(const pair<string, QType>& a, const pair<string, QType>& b) const
+  bool operator()(const pair<DNSName, QType>& a, const pair<DNSName, QType>& b) const
   {
-    int cmp=strcasecmp(a.first.c_str(), b.first.c_str());
-    if(cmp < 0)
+    if(a.first < b.first)
       return true;
-    if(cmp > 0)
+    if(b.first < a.first)
       return false;
 
     return a.second < b.second;
index 52d3e1b659e2eb24b02f732346f2d7de8937e843..3cf44f16a37127c762b864f709fe2e8894691d76 100644 (file)
@@ -49,6 +49,7 @@ string SyncRes::s_serverID;
 SyncRes::LogMode SyncRes::s_lm;
 
 unsigned int SyncRes::s_maxnegttl;
+unsigned int SyncRes::s_maxbogusttl;
 unsigned int SyncRes::s_maxcachettl;
 unsigned int SyncRes::s_maxqperq;
 unsigned int SyncRes::s_maxtotusec;
@@ -355,27 +356,26 @@ bool SyncRes::doOOBResolve(const DNSName &qname, const QType &qtype, vector<DNSR
 
 uint64_t SyncRes::doEDNSDump(int fd)
 {
-  FILE* fp=fdopen(dup(fd), "w");
+  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(dup(fd), "w"), fclose);
   if (!fp) {
     return 0;
   }
   uint64_t count = 0;
 
-  fprintf(fp,"; edns from thread follows\n;\n");
+  fprintf(fp.get(),"; edns from thread follows\n;\n");
   for(const auto& eds : t_sstorage.ednsstatus) {
     count++;
-    fprintf(fp, "%s\t%d\t%s", eds.first.toString().c_str(), (int)eds.second.mode, ctime(&eds.second.modeSetAt));
+    fprintf(fp.get(), "%s\t%d\t%s", eds.first.toString().c_str(), (int)eds.second.mode, ctime(&eds.second.modeSetAt));
   }
-  fclose(fp);
   return count;
 }
 
 uint64_t SyncRes::doDumpNSSpeeds(int fd)
 {
-  FILE* fp=fdopen(dup(fd), "w");
+  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(dup(fd), "w"), fclose);
   if(!fp)
     return 0;
-  fprintf(fp, "; nsspeed dump from thread follows\n;\n");
+  fprintf(fp.get(), "; nsspeed dump from thread follows\n;\n");
   uint64_t count=0;
 
   for(const auto& i : t_sstorage.nsSpeeds)
@@ -383,25 +383,24 @@ uint64_t SyncRes::doDumpNSSpeeds(int fd)
     count++;
 
     // an <empty> can appear hear in case of authoritative (hosted) zones
-    fprintf(fp, "%s -> ", i.first.toLogString().c_str());
+    fprintf(fp.get(), "%s -> ", i.first.toLogString().c_str());
     for(const auto& j : i.second.d_collection)
     {
       // typedef vector<pair<ComboAddress, DecayingEwma> > collection_t;
-      fprintf(fp, "%s/%f ", j.first.toString().c_str(), j.second.peek());
+      fprintf(fp.get(), "%s/%f ", j.first.toString().c_str(), j.second.peek());
     }
-    fprintf(fp, "\n");
+    fprintf(fp.get(), "\n");
   }
-  fclose(fp);
   return count;
 }
 
 uint64_t SyncRes::doDumpThrottleMap(int fd)
 {
-  FILE* fp=fdopen(dup(fd), "w");
+  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(dup(fd), "w"), fclose);
   if(!fp)
     return 0;
-  fprintf(fp, "; throttle map dump follows\n");
-  fprintf(fp, "; remote IP\tqname\tqtype\tcount\tttd\n");
+  fprintf(fp.get(), "; throttle map dump follows\n");
+  fprintf(fp.get(), "; remote IP\tqname\tqtype\tcount\tttd\n");
   uint64_t count=0;
 
   const auto& throttleMap = t_sstorage.throttle.getThrottleMap();
@@ -409,9 +408,9 @@ uint64_t SyncRes::doDumpThrottleMap(int fd)
   {
     count++;
     // remote IP, dns name, qtype, count, ttd
-    fprintf(fp, "%s\t%s\t%d\t%u\t%s", i.first.get<0>().toString().c_str(), i.first.get<1>().toLogString().c_str(), i.first.get<2>(), i.second.count, ctime(&i.second.ttd));
+    fprintf(fp.get(), "%s\t%s\t%d\t%u\t%s", i.first.get<0>().toString().c_str(), i.first.get<1>().toLogString().c_str(), i.first.get<2>(), i.second.count, ctime(&i.second.ttd));
   }
-  fclose(fp);
+
   return count;
 }
 
@@ -912,6 +911,16 @@ DNSName SyncRes::getBestNSNamesFromCache(const DNSName &qname, const QType& qtyp
   return subdomain;
 }
 
+void SyncRes::updateValidationStatusInCache(const DNSName &qname, const QType& qt, bool aa, vState newState) const
+{
+  if (newState == Bogus) {
+    t_RC->updateValidationStatus(d_now.tv_sec, qname, qt, d_cacheRemote, aa, newState, s_maxbogusttl + d_now.tv_sec);
+  }
+  else {
+    t_RC->updateValidationStatus(d_now.tv_sec, qname, qt, d_cacheRemote, aa, newState, boost::none);
+  }
+}
+
 bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>& ret, unsigned int depth, int &res, vState& state, bool wasAuthZone, bool wasForwardRecurse)
 {
   string prefix;
@@ -931,6 +940,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
   vector<std::shared_ptr<RRSIGRecordContent>> signatures;
   vector<std::shared_ptr<DNSRecord>> authorityRecs;
   bool wasAuth;
+  uint32_t capTTL = std::numeric_limits<uint32_t>::max();
   /* we don't require auth data for forward-recurse lookups */
   if(t_RC->get(d_now.tv_sec, qname, QType(QType::CNAME), !wasForwardRecurse && d_requireAuthData, &cset, d_cacheRemote, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &state, &wasAuth) > 0) {
 
@@ -958,7 +968,10 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
             state = SyncRes::validateRecordsWithSigs(depth, qname, QType(QType::CNAME), qname, cset, signatures);
             if (state != Indeterminate) {
               LOG(prefix<<qname<<": got Indeterminate state from the CNAME cache, new validation result is "<<vStates[state]<<endl);
-              t_RC->updateValidationStatus(d_now.tv_sec, qname, QType(QType::CNAME), d_cacheRemote, d_requireAuthData, state);
+              if (state == Bogus) {
+                capTTL = s_maxbogusttl;
+              }
+              updateValidationStatusInCache(qname, QType(QType::CNAME), wasAuth, state);
             }
           }
         }
@@ -966,7 +979,9 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
         LOG(prefix<<qname<<": Found cache CNAME hit for '"<< qname << "|CNAME" <<"' to '"<<j->d_content->getZoneRepresentation()<<"', validation state is "<<vStates[state]<<endl);
 
         DNSRecord dr=*j;
-        dr.d_ttl-=d_now.tv_sec;
+        dr.d_ttl -= d_now.tv_sec;
+        dr.d_ttl = std::min(dr.d_ttl, capTTL);
+        const uint32_t ttl = dr.d_ttl;
         ret.reserve(ret.size() + 1 + signatures.size() + authorityRecs.size());
         ret.push_back(dr);
 
@@ -974,7 +989,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
           DNSRecord sigdr;
           sigdr.d_type=QType::RRSIG;
           sigdr.d_name=qname;
-          sigdr.d_ttl=j->d_ttl - d_now.tv_sec;
+          sigdr.d_ttl=ttl;
           sigdr.d_content=signature;
           sigdr.d_place=DNSResourceRecord::ANSWER;
           sigdr.d_class=QClass::IN;
@@ -983,7 +998,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
 
         for(const auto& rec : authorityRecs) {
           DNSRecord authDR(*rec);
-          authDR.d_ttl=j->d_ttl - d_now.tv_sec;
+          authDR.d_ttl=ttl;
           ret.push_back(authDR);
         }
 
@@ -1105,7 +1120,11 @@ void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry* ne,
   }
   if (state != Indeterminate) {
     /* validation succeeded, let's update the cache entry so we don't have to validate again */
-    t_sstorage.negcache.updateValidationStatus(ne->d_name, ne->d_qtype, state);
+    boost::optional<uint32_t> capTTD = boost::none;
+    if (state == Bogus) {
+      capTTD = d_now.tv_sec + s_maxbogusttl;
+    }
+    t_sstorage.negcache.updateValidationStatus(ne->d_name, ne->d_qtype, state, capTTD);
   }
 }
 
@@ -1165,6 +1184,10 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
     if (!wasAuthZone && shouldValidate() && state == Indeterminate) {
       LOG(prefix<<qname<<": got Indeterminate state for records retrieved from the negative cache, validating.."<<endl);
       computeNegCacheValidationStatus(ne, qname, qtype, res, state, depth);
+
+      if (state != cachedState && state == Bogus) {
+        sttl = std::min(sttl, s_maxbogusttl);
+      }
     }
 
     // Transplant SOA to the returned packet
@@ -1184,6 +1207,7 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
   vector<std::shared_ptr<RRSIGRecordContent>> signatures;
   vector<std::shared_ptr<DNSRecord>> authorityRecs;
   uint32_t ttl=0;
+  uint32_t capTTL = std::numeric_limits<uint32_t>::max();
   bool wasCachedAuth;
   if(t_RC->get(d_now.tv_sec, sqname, sqt, !wasForwardRecurse && d_requireAuthData, &cset, d_cacheRemote, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &cachedState, &wasCachedAuth) > 0) {
 
@@ -1212,7 +1236,10 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
 
       if (cachedState != Indeterminate) {
         LOG(prefix<<qname<<": got Indeterminate state from the cache, validation result is "<<vStates[cachedState]<<endl);
-        t_RC->updateValidationStatus(d_now.tv_sec, sqname, sqt, d_cacheRemote, d_requireAuthData, cachedState);
+        if (cachedState == Bogus) {
+          capTTL = s_maxbogusttl;
+        }
+        updateValidationStatusInCache(sqname, sqt, wasCachedAuth, cachedState);
       }
     }
 
@@ -1226,7 +1253,9 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
 
       if(j->d_ttl>(unsigned int) d_now.tv_sec) {
         DNSRecord dr=*j;
-        ttl = (dr.d_ttl-=d_now.tv_sec);
+        dr.d_ttl -= d_now.tv_sec;
+        dr.d_ttl = std::min(dr.d_ttl, capTTL);
+        ttl = dr.d_ttl;
         ret.push_back(dr);
         LOG("[ttl="<<dr.d_ttl<<"] ");
         found=true;
@@ -2366,6 +2395,13 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
       }
     }
 
+    if (recordState == Bogus) {
+      /* this is a TTD by now, be careful */
+      for(auto& record : i->second.records) {
+        record.d_ttl = std::min(record.d_ttl, static_cast<uint32_t>(s_maxbogusttl + d_now.tv_sec));
+      }
+    }
+
     /* We don't need to store NSEC3 records in the positive cache because:
        - we don't allow direct NSEC3 queries
        - denial of existence proofs in wildcard expanded positive responses are stored in authorityRecs
@@ -2448,7 +2484,6 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
       ne.d_qtype = QType(0); // this encodes 'whole record'
       ne.d_auth = rec.d_name;
       harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
-      ne.d_ttd = d_now.tv_sec + lowestTTL;
 
       if (state == Secure) {
         dState denialState = getDenialValidationState(ne, state, NXDOMAIN, false);
@@ -2458,6 +2493,11 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         ne.d_validationState = state;
       }
 
+      if (ne.d_validationState == Bogus) {
+        lowestTTL = min(lowestTTL, s_maxbogusttl);
+      }
+
+      ne.d_ttd = d_now.tv_sec + lowestTTL;
       /* if we get an NXDomain answer with a CNAME, let's not cache the
          target, even the server was authoritative for it,
          and do an additional query for the CNAME target.
@@ -2495,7 +2535,6 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
       LOG(prefix<<qname<<": answer is in: resolved to '"<< rec.d_content->getZoneRepresentation()<<"|"<<DNSRecordContent::NumberToType(rec.d_type)<<"'"<<endl);
 
       done=true;
-      ret.push_back(rec);
 
       if (state == Secure && needWildcardProof) {
         /* We have a positive answer synthetized from a wildcard, we need to check that we have
@@ -2521,13 +2560,15 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
           }
           else {
             LOG(d_prefix<<"Invalid denial in wildcard expanded positive response found for "<<qname<<", returning Bogus, res="<<res<<endl);
+            rec.d_ttl = std::min(rec.d_ttl, s_maxbogusttl);
           }
 
           updateValidationState(state, st);
           /* we already stored the record with a different validation status, let's fix it */
-          t_RC->updateValidationStatus(d_now.tv_sec, qname, qtype, d_cacheRemote, lwr.d_aabit, st);
+          updateValidationStatusInCache(qname, qtype, lwr.d_aabit, st);
         }
       }
+      ret.push_back(rec);
     }
     else if((rec.d_type==QType::RRSIG || rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3) && rec.d_place==DNSResourceRecord::ANSWER) {
       if(rec.d_type != QType::RRSIG || rec.d_name == qname)
@@ -2588,7 +2629,6 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
       }
       else {
         rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
-        ret.push_back(rec);
 
         NegCache::NegCacheEntry ne;
         ne.d_auth = rec.d_name;
@@ -2596,7 +2636,6 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         ne.d_name = qname;
         ne.d_qtype = qtype;
         harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
-        ne.d_ttd = d_now.tv_sec + lowestTTL;
 
         if (state == Secure) {
           dState denialState = getDenialValidationState(ne, state, NXQTYPE, false);
@@ -2605,11 +2644,19 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
           ne.d_validationState = state;
         }
 
+        if (ne.d_validationState == Bogus) {
+          lowestTTL = min(lowestTTL, s_maxbogusttl);
+          rec.d_ttl = min(rec.d_ttl, s_maxbogusttl);
+        }
+        ne.d_ttd = d_now.tv_sec + lowestTTL;
+
         if(!wasVariable()) {
           if(qtype.getCode()) {  // prevents us from blacking out a whole domain
             t_sstorage.negcache.add(ne);
           }
         }
+
+        ret.push_back(rec);
         negindic=true;
       }
     }
index 53825ac81ed7568872079cccee3f40048610fb3f..a19f21e54e6d9ead23d52b2238e582f1690e5f08 100644 (file)
@@ -693,6 +693,7 @@ public:
   static unsigned int s_maxtotusec;
   static unsigned int s_maxdepth;
   static unsigned int s_maxnegttl;
+  static unsigned int s_maxbogusttl;
   static unsigned int s_maxcachettl;
   static unsigned int s_packetcachettl;
   static unsigned int s_packetcacheservfailttl;
@@ -789,6 +790,7 @@ private:
   vState getTA(const DNSName& zone, dsmap_t& ds);
   bool haveExactValidationStatus(const DNSName& domain);
   vState getValidationStatus(const DNSName& subdomain, bool allowIndeterminate=true);
+  void updateValidationStatusInCache(const DNSName &qname, const QType& qt, bool aa, vState newState) const;
 
   bool lookForCut(const DNSName& qname, unsigned int depth, const vState existingState, vState& newState);
   void computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned int depth);
index 4af4ba2398be9608c5954f7f3da766e0a9623e8a..872592eadbf377c0ace3a900acb9cab799bc0eb3 100644 (file)
@@ -1533,8 +1533,10 @@ BOOST_AUTO_TEST_CASE(test_isEDNSOptionInOpt) {
 
     bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
     BOOST_CHECK_EQUAL(found, true);
-    BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + /* option code */ 2 + /* option length */ 2);
-    BOOST_CHECK_EQUAL(optContentLen, sizeOfECSContent);
+    if (found == true) {
+      BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + /* option code */ 2 + /* option length */ 2);
+      BOOST_CHECK_EQUAL(optContentLen, sizeOfECSContent);
+    }
 
     /* truncated packet */
     query.resize(query.size() - 1);
@@ -1554,8 +1556,10 @@ BOOST_AUTO_TEST_CASE(test_isEDNSOptionInOpt) {
 
     bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
     BOOST_CHECK_EQUAL(found, true);
-    BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + sizeOfCookieOption + /* option code */ 2 + /* option length */ 2);
-    BOOST_CHECK_EQUAL(optContentLen, sizeOfECSContent);
+    if (found == true) {
+      BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + sizeOfCookieOption + /* option code */ 2 + /* option length */ 2);
+      BOOST_CHECK_EQUAL(optContentLen, sizeOfECSContent);
+    }
 
     /* truncated packet */
     query.resize(query.size() - 1);
index bab31a11c50754501fd5caa2a226247e8cdcaa37..00d795e78f15acd63fabeb718207deee05706bdc 100644 (file)
@@ -211,7 +211,14 @@ BOOST_AUTO_TEST_CASE(test_ixfr_same_serial) {
 
   auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA));
 
-  BOOST_CHECK_EQUAL(ret.size(), 0);
+  // this is actually an empty AXFR
+  BOOST_CHECK_EQUAL(ret.size(), 1);
+  // nothing in the deletion part then
+  BOOST_CHECK_EQUAL(ret.at(0).first.size(), 0);
+  // and the two SOAs in the addition part
+  BOOST_CHECK_EQUAL(ret.at(0).second.size(), 2);
+  BOOST_CHECK_EQUAL(ret.at(0).second.at(0).d_type, QType(QType::SOA).getCode());
+  BOOST_CHECK_EQUAL(ret.at(0).second.at(1).d_type, QType(QType::SOA).getCode());
 }
 
 BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_records) {
index 365187108d41faa60fc8def77faa93259d5aa699..5e856e1be4248a84ce5280789df96285e44aee99 100644 (file)
@@ -41,16 +41,16 @@ BOOST_AUTO_TEST_CASE(test_recPacketCacheSimple) {
   pw.commit();
   string rpacket((const char*)&packet[0], packet.size());
 
-  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, 0, 0);
+  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, Indeterminate, 0, 0, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 1);
   rpc.doPruneTo(0);
   BOOST_CHECK_EQUAL(rpc.size(), 0);
-  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, 0, 0);
+  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, Indeterminate, 0, 0, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 1);
   rpc.doWipePacketCache(qname);
   BOOST_CHECK_EQUAL(rpc.size(), 0);
 
-  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, 0, 0);
+  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, Indeterminate, 0, 0, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 1);
   uint32_t qhash2 = 0;
   bool found = rpc.getResponsePacket(tag, qpacket, time(nullptr), &fpacket, &age, &qhash2);
@@ -133,11 +133,11 @@ BOOST_AUTO_TEST_CASE(test_recPacketCache_Tags) {
   BOOST_CHECK(r1packet != r2packet);
 
   /* inserting a response for tag1 */
-  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, 0, 0);
+  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, Indeterminate, 0, 0, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 1);
 
   /* inserting a different response for tag2, should not override the first one */
-  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, 0, 0);
+  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, Indeterminate, 0, 0, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 2);
 
   /* remove all responses from the cache */
@@ -145,10 +145,10 @@ BOOST_AUTO_TEST_CASE(test_recPacketCache_Tags) {
   BOOST_CHECK_EQUAL(rpc.size(), 0);
 
   /* reinsert both */
-  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, 0, 0);
+  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, Indeterminate, 0, 0, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 1);
 
-  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, 0, 0);
+  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, Indeterminate, 0, 0, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 2);
 
   /* remove the responses by qname, should remove both */
@@ -156,7 +156,7 @@ BOOST_AUTO_TEST_CASE(test_recPacketCache_Tags) {
   BOOST_CHECK_EQUAL(rpc.size(), 0);
 
   /* insert the response for tag1 */
-  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, 0, 0);
+  rpc.insertResponsePacket(tag1, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r1packet), time(0), ttd, Indeterminate, 0, 0, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 1);
 
   /* we can retrieve it */
@@ -175,7 +175,7 @@ BOOST_AUTO_TEST_CASE(test_recPacketCache_Tags) {
   BOOST_CHECK_EQUAL(temphash, qhash);
 
   /* adding a response for the second tag */
-  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, 0, 0);
+  rpc.insertResponsePacket(tag2, qhash, string(qpacket), qname, QType::A, QClass::IN, string(r2packet), time(0), ttd, Indeterminate, 0, 0, boost::none);
   BOOST_CHECK_EQUAL(rpc.size(), 2);
 
   /* We still get the correct response for the first tag */
index 54776cd1e3b89bf0691ed2bfa9954d79c2272ec8..6106d25f3329d76107fc6134f991ee1347be0e22 100644 (file)
@@ -165,6 +165,7 @@ void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
 
   auto resp_qtype_stats = g_rs.getQTypeResponseCounts();
   auto resp_size_stats = g_rs.getSizeResponseCounts();
+  auto resp_rcode_stats = g_rs.getRCodeResponseCounts();
 
   Json::array doc;
   for(const auto& item : general_stats) {
@@ -188,7 +189,7 @@ void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
 
     doc.push_back(Json::object {
       { "type", "MapStatisticItem" },
-      { "name", "queries-by-qtype" },
+      { "name", "response-by-qtype" },
       { "value", values },
     });
   }
@@ -212,6 +213,24 @@ void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
     });
   }
 
+  {
+    Json::array values;
+    for(const auto& item : resp_rcode_stats) {
+      if (item.second == 0)
+        continue;
+      values.push_back(Json::object {
+        { "name", RCode::to_s(item.first) },
+        { "value", std::to_string(item.second) },
+      });
+    }
+
+    doc.push_back(Json::object {
+      { "type", "MapStatisticItem" },
+      { "name", "response-by-rcode" },
+      { "value", values },
+    });
+  }
+
 #ifndef RECURSOR
   for(const auto& ringName : S.listRings()) {
     Json::array values;
index b2ab3a2d86d064bca416182840d19c68036923b7..eb6a9707b723a2c41a708a1c86055352ac22d411 100644 (file)
@@ -603,17 +603,17 @@ static void throwUnableToSecure(const DNSName& zonename) {
 }
 
 static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo& di, const DNSName& zonename, const Json document) {
-  string zonemaster;
+  vector<string> zonemaster;
   bool shouldRectify = false;
   for(auto value : document["masters"].array_items()) {
     string master = value.string_value();
     if (master.empty())
       throw ApiException("Master can not be an empty string");
-    zonemaster += master + " ";
+    zonemaster.push_back(master);
   }
 
-  if (zonemaster != "") {
-    di.backend->setMaster(zonename, zonemaster);
+  if (zonemaster.size()) {
+    di.backend->setMaster(zonename, boost::join(zonemaster, ","));
   }
   if (document["kind"].is_string()) {
     di.backend->setKind(zonename, DomainInfo::stringToKind(stringFromJson(document, "kind")));
@@ -2058,9 +2058,20 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
 
   string q = req->getvars["q"];
   string sMax = req->getvars["max"];
+  string sObjectType = req->getvars["object_type"];
+
   int maxEnts = 100;
   int ents = 0;
 
+  // the following types of data can be searched for using the api
+  enum class ObjectType
+  {
+    ALL,
+    ZONE,
+    RECORD,
+    COMMENT
+  } objectType;
+
   if (q.empty())
     throw ApiException("Query q can't be blank");
   if (!sMax.empty())
@@ -2068,6 +2079,19 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
   if (maxEnts < 1)
     throw ApiException("Maximum entries must be larger than 0");
 
+  if (sObjectType.empty())
+    objectType = ObjectType::ALL;
+  else if (sObjectType == "all")
+    objectType = ObjectType::ALL;
+  else if (sObjectType == "zone")
+    objectType = ObjectType::ZONE;
+  else if (sObjectType == "record")
+    objectType = ObjectType::RECORD;
+  else if (sObjectType == "comment")
+    objectType = ObjectType::COMMENT;
+  else
+    throw ApiException("object_type must be one of the following options: all, zone, record, comment");
+
   SimpleMatch sm(q,true);
   UeberBackend B;
   vector<DomainInfo> domains;
@@ -2081,7 +2105,7 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
 
   for(const DomainInfo di: domains)
   {
-    if (ents < maxEnts && sm.match(di.zone)) {
+    if ((objectType == ObjectType::ALL || objectType == ObjectType::ZONE) && ents < maxEnts && sm.match(di.zone)) {
       doc.push_back(Json::object {
         { "object_type", "zone" },
         { "zone_id", apiZoneNameToId(di.zone) },
@@ -2092,7 +2116,7 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
     zoneIdZone[di.id] = di; // populate cache
   }
 
-  if (B.searchRecords(q, maxEnts, result_rr))
+  if ((objectType == ObjectType::ALL || objectType == ObjectType::RECORD) && B.searchRecords(q, maxEnts, result_rr))
   {
     for(const DNSResourceRecord& rr: result_rr)
     {
@@ -2115,7 +2139,7 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
     }
   }
 
-  if (B.searchComments(q, maxEnts, result_c))
+  if ((objectType == ObjectType::ALL || objectType == ObjectType::COMMENT) && B.searchComments(q, maxEnts, result_c))
   {
     for(const Comment &c: result_c)
     {
index 400a6e47fd49cb87ee040b7e8b416e741afb1351..80835f999ed644c77096861e1aed81cefb2e7af2 100644 (file)
@@ -43,14 +43,17 @@ class Servers(ApiTestCase):
         self.assertIn('uptime', [e['name'] for e in data])
         if is_auth():
             print(data)
-            qtype_stats, respsize_stats, queries_stats = None, None, None
+            qtype_stats, respsize_stats, queries_stats, rcode_stats = None, None, None, None
             for elem in data:
-                if elem['type'] == 'MapStatisticItem' and elem['name'] == 'queries-by-qtype':
+                if elem['type'] == 'MapStatisticItem' and elem['name'] == 'response-by-qtype':
                     qtype_stats = elem['value']
                 elif elem['type'] == 'MapStatisticItem' and elem['name'] == 'response-sizes':
                     respsize_stats = elem['value']
                 elif elem['type'] == 'RingStatisticItem' and elem['name'] == 'queries':
                     queries_stats = elem['value']
+                elif elem['type'] == 'MapStatisticItem' and elem['name'] == 'response-by-rcode':
+                    rcode_stats = elem['value']
             self.assertIn('A', [e['name'] for e in qtype_stats])
             self.assertIn('60', [e['name'] for e in respsize_stats])
             self.assertIn('example.com/A', [e['name'] for e in queries_stats])
+            self.assertIn('No Error', [e['name'] for e in rcode_stats])
index 9c72fdc10926e3425f76dfc7684b9766969c8451..ff756bad0a33c776e90192ea14a9779c36386803 100644 (file)
@@ -1475,6 +1475,29 @@ $ORIGIN %NAME%
         self.assertNotEquals(serverset['records'], [])
         self.assertEquals(serverset['comments'], [])
 
+    def test_zone_comment_out_of_range_modified_at(self):
+        # Test if comments on an rrset stay intact if the rrset is replaced
+        name, payload, zone = self.create_zone()
+        rrset = {
+            'changetype': 'replace',
+            'name': name,
+            'type': 'NS',
+            'comments': [
+                {
+                    'account': 'test1',
+                    'content': 'oh hi there',
+                    'modified_at': '4294967297'
+                }
+            ]
+        }
+        payload = {'rrsets': [rrset]}
+        r = self.session.patch(
+            self.url("/api/v1/servers/localhost/zones/" + name),
+            data=json.dumps(payload),
+            headers={'content-type': 'application/json'})
+        self.assertEquals(r.status_code, 422)
+        self.assertIn("Value for key 'modified_at' is out of range", r.json()['error'])
+
     def test_zone_comment_stay_intact(self):
         # Test if comments on an rrset stay intact if the rrset is replaced
         name, payload, zone = self.create_zone()
@@ -1654,6 +1677,36 @@ $ORIGIN %NAME%
              u'ttl': 3600, u'type': u'SOA', u'name': name},
         ])
 
+    def test_search_rr_exact_zone_filter_type_zone(self):
+        name = unique_zone_name()
+        data_type = "zone"
+        self.create_zone(name=name, serial=22, soa_edit_api='')
+        r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=" + name.rstrip('.') + "&object_type=" + data_type))
+        self.assert_success_json(r)
+        print(r.json())
+        self.assertEquals(r.json(), [
+            {u'object_type': u'zone', u'name': name, u'zone_id': name},
+        ])
+
+    def test_search_rr_exact_zone_filter_type_record(self):
+        name = unique_zone_name()
+        data_type = "record"
+        self.create_zone(name=name, serial=22, soa_edit_api='')
+        r = self.session.get(self.url("/api/v1/servers/localhost/search-data?q=" + name.rstrip('.') + "&object_type=" + data_type))
+        self.assert_success_json(r)
+        print(r.json())
+        self.assertEquals(r.json(), [
+            {u'content': u'ns1.example.com.',
+             u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False,
+             u'ttl': 3600, u'type': u'NS', u'name': name},
+            {u'content': u'ns2.example.com.',
+             u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False,
+             u'ttl': 3600, u'type': u'NS', u'name': name},
+            {u'content': u'a.misconfigured.powerdns.server. hostmaster.'+name+' 22 10800 3600 604800 3600',
+             u'zone_id': name, u'zone': name, u'object_type': u'record', u'disabled': False,
+             u'ttl': 3600, u'type': u'SOA', u'name': name},
+        ])
+
     def test_search_rr_substring(self):
         name = unique_zone_name()
         search = name[5:-5]
index 8502da2174c4eb02ad840e10549a5ef2ca7993a0..c970bcaf655aee4855dda45e103e84bcbaac611a 100644 (file)
@@ -2,17 +2,16 @@
 /*.xml
 /*.pid
 /*.pyc
-dnsdist_*.conf
-DNSCryptResolver*
-.dnsdist_history
-.history
-dnsdist.log
+/DNSCryptResolver*
+/.dnsdist_history
+/.history
 /*_pb2.py
 /__pycache__/
-ca.key
-ca.pem
-ca.srl
-server.chain
-server.csr
-server.key
-server.pem
+/ca.key
+/ca.pem
+/ca.srl
+/server.chain
+/server.csr
+/server.key
+/server.pem
+/configs
\ No newline at end of file
index bda4b93212bb46f2ea448640aed1f3d98ba60581..e1df6cfa7df44a5631334af0776505a88560a587 100644 (file)
@@ -46,7 +46,6 @@ class DNSDistTest(unittest.TestCase):
     _dnsdistStartupDelay = 2.0
     _dnsdist = None
     _responsesCounter = {}
-    _shutUp = True
     _config_template = """
     """
     _config_params = ['_testServerPort']
@@ -69,16 +68,16 @@ class DNSDistTest(unittest.TestCase):
         cls._TCPResponder.start()
 
     @classmethod
-    def startDNSDist(cls, shutUp=True):
+    def startDNSDist(cls):
         print("Launching dnsdist..")
-        conffile = 'dnsdist_test.conf'
+        confFile = os.path.join('configs', 'dnsdist_%s.conf' % (cls.__name__))
         params = tuple([getattr(cls, param) for param in cls._config_params])
         print(params)
-        with open(conffile, 'w') as conf:
+        with open(confFile, 'w') as conf:
             conf.write("-- Autogenerated by dnsdisttests.py\n")
             conf.write(cls._config_template % params)
 
-        dnsdistcmd = [os.environ['DNSDISTBIN'], '-C', conffile,
+        dnsdistcmd = [os.environ['DNSDISTBIN'], '-C', confFile,
                       '-l', '%s:%d' % (cls._dnsDistListeningAddr, cls._dnsDistPort) ]
         for acl in cls._acl:
             dnsdistcmd.extend(['--acl', acl])
@@ -90,14 +89,13 @@ class DNSDistTest(unittest.TestCase):
             output = subprocess.check_output(testcmd, stderr=subprocess.STDOUT, close_fds=True)
         except subprocess.CalledProcessError as exc:
             raise AssertionError('dnsdist --check-config failed (%d): %s' % (exc.returncode, exc.output))
-        if output != b'Configuration \'dnsdist_test.conf\' OK!\n':
+        expectedOutput = ('Configuration \'%s\' OK!\n' % (confFile)).encode()
+        if output != expectedOutput:
             raise AssertionError('dnsdist --check-config failed: %s' % output)
 
-        if shutUp:
-            with open(os.devnull, 'w') as fdDevNull:
-                cls._dnsdist = subprocess.Popen(dnsdistcmd, close_fds=True, stdout=fdDevNull)
-        else:
-            cls._dnsdist = subprocess.Popen(dnsdistcmd, close_fds=True)
+        logFile = os.path.join('configs', 'dnsdist_%s.log' % (cls.__name__))
+        with open(logFile, 'w') as fdLog:
+          cls._dnsdist = subprocess.Popen(dnsdistcmd, close_fds=True, stdout=fdLog, stderr=fdLog)
 
         if 'DNSDIST_FAST_TESTS' in os.environ:
             delay = 0.5
@@ -121,7 +119,7 @@ class DNSDistTest(unittest.TestCase):
     def setUpClass(cls):
 
         cls.startResponders()
-        cls.startDNSDist(cls._shutUp)
+        cls.startDNSDist()
         cls.setUpSockets()
 
         print("Launching tests..")
index 8802191cc3900eff312f16f9225218215ac2bf99..45e34dfbe7a8bcd9f860735e1b1f235657e4c708 100755 (executable)
@@ -20,6 +20,8 @@ pip install -r requirements.txt
 protoc -I=../pdns/ --python_out=. ../pdns/dnsmessage.proto
 protoc -I=../pdns/ --python_out=. ../pdns/dnstap.proto
 
+mkdir -p configs
+
 if [ -z "${DNSDISTBIN}" ]; then
   DNSDISTBIN=$(ls ../pdns/dnsdistdist/dnsdist-*/dnsdist)
 fi
@@ -39,6 +41,12 @@ openssl x509 -req -days 1 -CA ca.pem -CAkey ca.key -CAcreateserial -in server.cs
 # Generate a chain
 cat server.pem ca.pem >> server.chain
 
-nosetests --with-xunit $@
+if ! nosetests --with-xunit $@; then
+    for log in configs/*.log; do
+        echo "=== ${log} ==="
+        cat "${log}"
+    done
+    false
+fi
 
 rm ca.key ca.pem ca.srl server.csr server.key server.pem server.chain
index 441c73f5821d6aa8b11a2b698b8b3cbbe8bce0e0..04adcc0e066e07c092cfc0d22d7455f8eafb6587 100644 (file)
@@ -1714,3 +1714,120 @@ class TestAdvancedEDNSOptionRule(DNSDistTest):
         receivedQuery.id = query.id
         self.assertEquals(query, receivedQuery)
         self.assertEquals(receivedResponse, response)
+
+class TestAdvancedAllowHeaderOnly(DNSDistTest):
+
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    setAllowEmptyResponse(true)
+    """
+
+    def testHeaderOnlyRefused(self):
+        """
+        Advanced: Header-only refused response
+        """
+        name = 'header-only-refused-response.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        response.set_rcode(dns.rcode.REFUSED)
+        response.question = []
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            self.assertTrue(receivedQuery)
+            receivedQuery.id = query.id
+            self.assertEquals(query, receivedQuery)
+            self.assertEquals(receivedResponse, response)
+
+    def testHeaderOnlyNoErrorResponse(self):
+        """
+        Advanced: Header-only NoError response should be allowed
+        """
+        name = 'header-only-noerror-response.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        response.question = []
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            self.assertTrue(receivedQuery)
+            receivedQuery.id = query.id
+            self.assertEquals(query, receivedQuery)
+            self.assertEquals(receivedResponse, response)
+
+    def testHeaderOnlyNXDResponse(self):
+        """
+        Advanced: Header-only NXD response should be allowed
+        """
+        name = 'header-only-nxd-response.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        response.set_rcode(dns.rcode.NXDOMAIN)
+        response.question = []
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            self.assertTrue(receivedQuery)
+            receivedQuery.id = query.id
+            self.assertEquals(query, receivedQuery)
+            self.assertEquals(receivedResponse, response)
+
+class TestAdvancedEDNSVersionnRule(DNSDistTest):
+
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    addAction(EDNSVersionRule(0), ERCodeAction(dnsdist.BADVERS))
+    """
+
+    def testDropped(self):
+        """
+        Advanced: A question with ECS version larger than 0 is dropped
+        """
+
+        name = 'ednsversionrule.advanced.tests.powerdns.com.'
+
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=1)
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.BADVERS)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None)
+            self.assertEquals(receivedResponse, expectedResponse)
+
+    def testNoEDNS0Pass(self):
+        """
+        Advanced: A question with ECS version 0 goes through
+        """
+
+        name = 'ednsversionrule.advanced.tests.powerdns.com.'
+
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        response = dns.message.make_response(query)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            receivedQuery.id = query.id
+            self.assertEquals(query, receivedQuery)
+            self.assertEquals(receivedResponse, response)
+
+    def testReplied(self):
+        """
+        Advanced: A question without ECS goes through
+        """
+
+        name = 'ednsoptionrule.advanced.tests.powerdns.com.'
+
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        response = dns.message.make_response(query)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            receivedQuery.id = query.id
+            self.assertEquals(query, receivedQuery)
+            self.assertEquals(receivedResponse, response)
index c3938cbca7d409df0f61df93f8aea8622963ec4f..6475101104601d44467d513ebd0b37b23c58cd5a 100755 (executable)
@@ -27,7 +27,7 @@ $SDIG ::1 $port example.com SOA >&2 >/dev/null
 $SDIG ::1 $port example.com SOA tcp >&2 >/dev/null
 
 $PDNSCONTROL --config-name= --no-config --socket-dir=./ 'show *' | \
-  tr ',' '\n'| grep -v -E '(user-msec|sys-msec|uptime|udp-noport-errors|udp-in-errors|real-memory-usage|udp-recvbuf-errors|udp-sndbuf-errors|-hit|-miss|fd-usage|latency)' | LC_ALL=C sort
+  tr ',' '\n'| grep -v -E '(user-msec|sys-msec|uptime|udp-noport-errors|udp-in-errors|real-memory-usage|special-memory-usage|udp-recvbuf-errors|udp-sndbuf-errors|-hit|-miss|fd-usage|latency)' | LC_ALL=C sort
 
 kill $(cat pdns*.pid)
 rm pdns*.pid
index a630fac178c8fb5f1759ee7b1be8693fd2488a69..e754dffa8488eb312ad6533b4cedc5669797b95f 100644 (file)
@@ -11,16 +11,6 @@ class BasicDNSSEC(RecursorTest):
         confdir = os.path.join('configs', cls._confdir)
         cls.wipeRecursorCache(confdir)
 
-    @classmethod
-    def sendQuery(self, name, rdtype, useTCP=False):
-        """Helper function that creates the query"""
-        msg = dns.message.make_query(name, rdtype, want_dnssec=True)
-        msg.flags |= dns.flags.AD
-
-        if useTCP:
-            return self.sendTCPQuery(msg)
-        return self.sendUDPQuery(msg)
-
     def testSecureAnswer(self):
         res = self.sendQuery('ns.secure.example.', 'A')
         expected = dns.rrset.from_text('ns.secure.example.', 0, dns.rdataclass.IN, 'A', '{prefix}.10'.format(prefix=self._PREFIX))
index 491aa895470442a9c49174ea4721996e1b9893fb..46ca97c4677ba9b3d873d324d4f184d0f8e29f1d 100644 (file)
@@ -804,3 +804,22 @@ distributor-threads=1""".format(confdir=confdir,
         print(expectedResponse)
         print(response)
         self.assertEquals(response, expectedResponse)
+
+    @classmethod
+    def sendQuery(cls, name, rdtype, useTCP=False):
+        """Helper function that creates the query"""
+        msg = dns.message.make_query(name, rdtype, want_dnssec=True)
+        msg.flags |= dns.flags.AD
+
+        if useTCP:
+            return cls.sendTCPQuery(msg)
+        return cls.sendUDPQuery(msg)
+
+    def createQuery(self, name, rdtype, flags, ednsflags):
+        """Helper function that creates the query with the specified flags.
+        The flags need to be strings (no checking is performed atm)"""
+        msg = dns.message.make_query(name, rdtype)
+        msg.flags = dns.flags.from_text(flags)
+        msg.flags += dns.flags.from_text('RD')
+        msg.use_edns(edns=0, ednsflags=dns.flags.edns_from_text(ednsflags))
+        return msg
index 22b63c89d891db5ebe0f9c660ed7d53dbc01d740..a550b9001d52aa13d16c59605779120b6da1938f 100644 (file)
@@ -76,15 +76,6 @@ class TestFlags(RecursorTest):
             cls._recursor = recursor
             cls.tearDownRecursor()
 
-    def createQuery(self, name, rdtype, flags, ednsflags):
-        """Helper function that creates the query with the specified flags.
-        The flags need to be strings (no checking is performed atm)"""
-        msg = dns.message.make_query(name, rdtype)
-        msg.flags = dns.flags.from_text(flags)
-        msg.flags += dns.flags.from_text('RD')
-        msg.use_edns(edns=0, ednsflags=dns.flags.edns_from_text(ednsflags))
-        return msg
-
     def getQueryForSecure(self, flags='', ednsflags=''):
         return self.createQuery('ns1.example.', 'A', flags, ednsflags)
 
index e126d91d02cf4272a8e4cade6c922e2d980b9176..f000bcf7a9f243c9205b771b2fd70a316a068481 100644 (file)
@@ -468,3 +468,30 @@ class DNS64Test(RecursorTest):
             self.assertEqual(len(res.answer), 2)
             self.assertEqual(len(res.authority), 0)
             self.assertResponseMatches(query, expected, res)
+
+
+class PDNSRandomTest(RecursorTest):
+    """Tests if pdnsrandom works"""
+
+    _confdir = 'pdnsrandom'
+    _config_template = """
+    """
+    _lua_dns_script_file = """
+    function preresolve (dq)
+      dq.rcode = pdns.NOERROR
+      dq:addAnswer(pdns.TXT, pdnsrandom())
+      return true
+    end
+    """
+
+    def testRandom(self):
+        query = dns.message.make_query('whatever.example.', 'TXT')
+
+        ans = set()
+
+        ret = self.sendUDPQuery(query)
+        ans.add(ret.answer[0])
+        ret = self.sendUDPQuery(query)
+        ans.add(ret.answer[0])
+
+        self.assertEqual(len(ans), 2)
index 39f8e410c4168b40ffcfd5f980b5a5bc0aaa462b..beedf56db07e0ea1ae4c51b8e01d5f910a3fa60d 100644 (file)
@@ -118,6 +118,12 @@ class RPZServer(object):
                     dns.rrset.from_text('drop.example.zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.CNAME, 'rpz-drop.'),
                     dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
                     ]
+            elif newSerial == 8:
+                # this one is a bit special too, we are answering with a full AXFR and the new zone is empty
+                records = [
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial),
+                    dns.rrset.from_text('zone.rpz.', 60, dns.rdataclass.IN, dns.rdatatype.SOA, 'ns.zone.rpz. hostmaster.zone.rpz. %d 3600 3600 3600 1' % newSerial)
+                    ]
 
         response.answer = records
         return (newSerial, response)
@@ -176,19 +182,7 @@ class RPZServer(object):
                 print('Error in RPZ socket: %s' % str(e))
                 sock.close()
 
-rpzServerPort = 4250
-rpzServer = RPZServer(rpzServerPort)
-
 class RPZRecursorTest(RecursorTest):
-    """
-    This test makes sure that we correctly update RPZ zones via AXFR then IXFR
-    """
-
-    global rpzServerPort
-    _lua_config_file = """
-    -- The first server is a bogus one, to test that we correctly fail over to the second one
-    rpzMaster({'127.0.0.1:9999', '127.0.0.1:%d'}, 'zone.rpz.', { refresh=1 })
-    """ % (rpzServerPort)
     _wsPort = 8042
     _wsTimeout = 2
     _wsPassword = 'secretpassword'
@@ -212,22 +206,8 @@ webserver-port=%d
 webserver-address=127.0.0.1
 webserver-password=%s
 api-key=%s
+log-rpz-changes=yes
 """ % (_confdir, _wsPort, _wsPassword, _apiKey)
-    _xfrDone = 0
-
-    @classmethod
-    def generateRecursorConfig(cls, confdir):
-        authzonepath = os.path.join(confdir, 'example.zone')
-        with open(authzonepath, 'w') as authzone:
-            authzone.write("""$ORIGIN example.
-@ 3600 IN SOA {soa}
-a 3600 IN A 192.0.2.42
-b 3600 IN A 192.0.2.42
-c 3600 IN A 192.0.2.42
-d 3600 IN A 192.0.2.42
-e 3600 IN A 192.0.2.42
-""".format(soa=cls._SOA))
-        super(RPZRecursorTest, cls).generateRecursorConfig(confdir)
 
     @classmethod
     def setUpClass(cls):
@@ -283,6 +263,16 @@ e 3600 IN A 192.0.2.42
             self.assertRcodeEqual(res, dns.rcode.NOERROR)
             self.assertEqual(len(res.answer), 0)
 
+    def checkNXD(self, qname, qtype='A'):
+        query = dns.message.make_query(qname, qtype, want_dnssec=True)
+        query.flags |= dns.flags.CD
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
+            self.assertEqual(len(res.answer), 0)
+            self.assertEqual(len(res.authority), 1)
+
     def checkTruncated(self, qname, qtype='A'):
         query = dns.message.make_query(qname, qtype, want_dnssec=True)
         query.flags |= dns.flags.CD
@@ -308,6 +298,66 @@ e 3600 IN A 192.0.2.42
             res = sender(query)
             self.assertEqual(res, None)
 
+    def checkRPZStats(self, serial, recordsCount, fullXFRCount, totalXFRCount):
+        headers = {'x-api-key': self._apiKey}
+        url = 'http://127.0.0.1:' + str(self._wsPort) + '/api/v1/servers/localhost/rpzstatistics'
+        r = requests.get(url, headers=headers, timeout=self._wsTimeout)
+        self.assertTrue(r)
+        self.assertEquals(r.status_code, 200)
+        self.assertTrue(r.json())
+        content = r.json()
+        self.assertIn('zone.rpz.', content)
+        zone = content['zone.rpz.']
+        for key in ['last_update', 'records', 'serial', 'transfers_failed', 'transfers_full', 'transfers_success']:
+            self.assertIn(key, zone)
+
+        self.assertEquals(zone['serial'], serial)
+        self.assertEquals(zone['records'], recordsCount)
+        self.assertEquals(zone['transfers_full'], fullXFRCount)
+        self.assertEquals(zone['transfers_success'], totalXFRCount)
+
+rpzServerPort = 4250
+rpzServer = RPZServer(rpzServerPort)
+
+class RPZXFRRecursorTest(RPZRecursorTest):
+    """
+    This test makes sure that we correctly update RPZ zones via AXFR then IXFR
+    """
+
+    global rpzServerPort
+    _lua_config_file = """
+    -- The first server is a bogus one, to test that we correctly fail over to the second one
+    rpzMaster({'127.0.0.1:9999', '127.0.0.1:%d'}, 'zone.rpz.', { refresh=1 })
+    """ % (rpzServerPort)
+    _confdir = 'RPZXFR'
+    _wsPort = 8042
+    _wsTimeout = 2
+    _wsPassword = 'secretpassword'
+    _apiKey = 'secretapikey'
+    _config_template = """
+auth-zones=example=configs/%s/example.zone
+webserver=yes
+webserver-port=%d
+webserver-address=127.0.0.1
+webserver-password=%s
+api-key=%s
+""" % (_confdir, _wsPort, _wsPassword, _apiKey)
+    _xfrDone = 0
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        authzonepath = os.path.join(confdir, 'example.zone')
+        with open(authzonepath, 'w') as authzone:
+            authzone.write("""$ORIGIN example.
+@ 3600 IN SOA {soa}
+a 3600 IN A 192.0.2.42
+b 3600 IN A 192.0.2.42
+c 3600 IN A 192.0.2.42
+d 3600 IN A 192.0.2.42
+e 3600 IN A 192.0.2.42
+""".format(soa=cls._SOA))
+        super(RPZRecursorTest, cls).generateRecursorConfig(confdir)
+
     def waitUntilCorrectSerialIsLoaded(self, serial, timeout=5):
         global rpzServer
 
@@ -327,24 +377,6 @@ e 3600 IN A 192.0.2.42
 
         raise AssertionError("Waited %d seconds for the serial to be updated to %d but the serial is still %d" % (timeout, serial, currentSerial))
 
-    def checkRPZStats(self, serial, recordsCount, fullXFRCount, totalXFRCount):
-        headers = {'x-api-key': self._apiKey}
-        url = 'http://127.0.0.1:' + str(self._wsPort) + '/api/v1/servers/localhost/rpzstatistics'
-        r = requests.get(url, headers=headers, timeout=self._wsTimeout)
-        self.assertTrue(r)
-        self.assertEquals(r.status_code, 200)
-        self.assertTrue(r.json())
-        content = r.json()
-        self.assertIn('zone.rpz.', content)
-        zone = content['zone.rpz.']
-        for key in ['last_update', 'records', 'serial', 'transfers_failed', 'transfers_full', 'transfers_success']:
-            self.assertIn(key, zone)
-
-        self.assertEquals(zone['serial'], serial)
-        self.assertEquals(zone['records'], recordsCount)
-        self.assertEquals(zone['transfers_full'], fullXFRCount)
-        self.assertEquals(zone['transfers_success'], totalXFRCount)
-
     def testRPZ(self):
         # first zone, only a should be blocked
         self.waitUntilCorrectSerialIsLoaded(1)
@@ -410,3 +442,204 @@ e 3600 IN A 192.0.2.42
         # check non-custom policies
         self.checkTruncated('tc.example.')
         self.checkDropped('drop.example.')
+
+        # eighth zone, all entries should be gone
+        self.waitUntilCorrectSerialIsLoaded(8)
+        self.checkRPZStats(8, 0, 3, self._xfrDone)
+        self.checkNotBlocked('a.example.')
+        self.checkNotBlocked('b.example.')
+        self.checkNotBlocked('c.example.')
+        self.checkNotBlocked('d.example.')
+        self.checkNotBlocked('e.example.')
+        self.checkNXD('f.example.')
+        self.checkNXD('tc.example.')
+        self.checkNXD('drop.example.')
+
+class RPZFileRecursorTest(RPZRecursorTest):
+    """
+    This test makes sure that we correctly load RPZ zones from a file
+    """
+
+    _confdir = 'RPZFile'
+    _wsPort = 8042
+    _wsTimeout = 2
+    _wsPassword = 'secretpassword'
+    _apiKey = 'secretapikey'
+    _lua_config_file = """
+    rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz." })
+    """ % (_confdir)
+    _config_template = """
+auth-zones=example=configs/%s/example.zone
+webserver=yes
+webserver-port=%d
+webserver-address=127.0.0.1
+webserver-password=%s
+api-key=%s
+""" % (_confdir, _wsPort, _wsPassword, _apiKey)
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        authzonepath = os.path.join(confdir, 'example.zone')
+        with open(authzonepath, 'w') as authzone:
+            authzone.write("""$ORIGIN example.
+@ 3600 IN SOA {soa}
+a 3600 IN A 192.0.2.42
+b 3600 IN A 192.0.2.42
+c 3600 IN A 192.0.2.42
+d 3600 IN A 192.0.2.42
+e 3600 IN A 192.0.2.42
+z 3600 IN A 192.0.2.42
+""".format(soa=cls._SOA))
+
+        rpzFilePath = os.path.join(confdir, 'zone.rpz')
+        with open(rpzFilePath, 'w') as rpzZone:
+            rpzZone.write("""$ORIGIN zone.rpz.
+@ 3600 IN SOA {soa}
+a.example.zone.rpz. 60 IN A 192.0.2.42
+a.example.zone.rpz. 60 IN A 192.0.2.43
+a.example.zone.rpz. 60 IN TXT "some text"
+drop.example.zone.rpz. 60 IN CNAME rpz-drop.
+z.example.zone.rpz. 60 IN A 192.0.2.1
+tc.example.zone.rpz. 60 IN CNAME rpz-tcp-only.
+""".format(soa=cls._SOA))
+        super(RPZFileRecursorTest, cls).generateRecursorConfig(confdir)
+
+    def testRPZ(self):
+        self.checkCustom('a.example.', 'A', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.42', '192.0.2.43'))
+        self.checkCustom('a.example.', 'TXT', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'TXT', '"some text"'))
+        self.checkBlocked('z.example.')
+        self.checkNotBlocked('b.example.')
+        self.checkNotBlocked('c.example.')
+        self.checkNotBlocked('d.example.')
+        self.checkNotBlocked('e.example.')
+        # check that the policy is disabled for AD=1 queries
+        self.checkNotBlocked('z.example.', True)
+        # check non-custom policies
+        self.checkTruncated('tc.example.')
+        self.checkDropped('drop.example.')
+
+class RPZFileDefaultPolRecursorTest(RPZRecursorTest):
+    """
+    This test makes sure that we correctly load RPZ zones from a file with a default policy
+    """
+
+    _confdir = 'RPZFileDefaultPolicy'
+    _wsPort = 8042
+    _wsTimeout = 2
+    _wsPassword = 'secretpassword'
+    _apiKey = 'secretapikey'
+    _lua_config_file = """
+    rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", defpol=Policy.NoAction })
+    """ % (_confdir)
+    _config_template = """
+auth-zones=example=configs/%s/example.zone
+webserver=yes
+webserver-port=%d
+webserver-address=127.0.0.1
+webserver-password=%s
+api-key=%s
+""" % (_confdir, _wsPort, _wsPassword, _apiKey)
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        authzonepath = os.path.join(confdir, 'example.zone')
+        with open(authzonepath, 'w') as authzone:
+            authzone.write("""$ORIGIN example.
+@ 3600 IN SOA {soa}
+a 3600 IN A 192.0.2.42
+b 3600 IN A 192.0.2.42
+c 3600 IN A 192.0.2.42
+d 3600 IN A 192.0.2.42
+drop 3600 IN A 192.0.2.42
+e 3600 IN A 192.0.2.42
+z 3600 IN A 192.0.2.42
+""".format(soa=cls._SOA))
+
+        rpzFilePath = os.path.join(confdir, 'zone.rpz')
+        with open(rpzFilePath, 'w') as rpzZone:
+            rpzZone.write("""$ORIGIN zone.rpz.
+@ 3600 IN SOA {soa}
+a.example.zone.rpz. 60 IN A 192.0.2.42
+drop.example.zone.rpz. 60 IN CNAME rpz-drop.
+z.example.zone.rpz. 60 IN A 192.0.2.1
+tc.example.zone.rpz. 60 IN CNAME rpz-tcp-only.
+""".format(soa=cls._SOA))
+        super(RPZFileDefaultPolRecursorTest, cls).generateRecursorConfig(confdir)
+
+    def testRPZ(self):
+        # local data entries are overridden by default
+        self.checkCustom('a.example.', 'A', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.42'))
+        self.checkNoData('a.example.', 'TXT')
+        # will not be blocked because the default policy overrides local data entries by default
+        self.checkNotBlocked('z.example.')
+        self.checkNotBlocked('b.example.')
+        self.checkNotBlocked('c.example.')
+        self.checkNotBlocked('d.example.')
+        self.checkNotBlocked('e.example.')
+        # check non-local policies, they should be overridden by the default policy
+        self.checkNXD('tc.example.', 'A')
+        self.checkNotBlocked('drop.example.')
+
+class RPZFileDefaultPolNotOverrideLocalRecursorTest(RPZRecursorTest):
+    """
+    This test makes sure that we correctly load RPZ zones from a file with a default policy, not overriding local data entries
+    """
+
+    _confdir = 'RPZFileDefaultPolicyNotOverrideLocal'
+    _wsPort = 8042
+    _wsTimeout = 2
+    _wsPassword = 'secretpassword'
+    _apiKey = 'secretapikey'
+    _lua_config_file = """
+    rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", defpol=Policy.NoAction, defpolOverrideLocalData=false })
+    """ % (_confdir)
+    _config_template = """
+auth-zones=example=configs/%s/example.zone
+webserver=yes
+webserver-port=%d
+webserver-address=127.0.0.1
+webserver-password=%s
+api-key=%s
+""" % (_confdir, _wsPort, _wsPassword, _apiKey)
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        authzonepath = os.path.join(confdir, 'example.zone')
+        with open(authzonepath, 'w') as authzone:
+            authzone.write("""$ORIGIN example.
+@ 3600 IN SOA {soa}
+a 3600 IN A 192.0.2.42
+b 3600 IN A 192.0.2.42
+c 3600 IN A 192.0.2.42
+d 3600 IN A 192.0.2.42
+drop 3600 IN A 192.0.2.42
+e 3600 IN A 192.0.2.42
+z 3600 IN A 192.0.2.42
+""".format(soa=cls._SOA))
+
+        rpzFilePath = os.path.join(confdir, 'zone.rpz')
+        with open(rpzFilePath, 'w') as rpzZone:
+            rpzZone.write("""$ORIGIN zone.rpz.
+@ 3600 IN SOA {soa}
+a.example.zone.rpz. 60 IN A 192.0.2.42
+a.example.zone.rpz. 60 IN A 192.0.2.43
+a.example.zone.rpz. 60 IN TXT "some text"
+drop.example.zone.rpz. 60 IN CNAME rpz-drop.
+z.example.zone.rpz. 60 IN A 192.0.2.1
+tc.example.zone.rpz. 60 IN CNAME rpz-tcp-only.
+""".format(soa=cls._SOA))
+        super(RPZFileDefaultPolNotOverrideLocalRecursorTest, cls).generateRecursorConfig(confdir)
+
+    def testRPZ(self):
+        # local data entries will not be overridden by the default polic
+        self.checkCustom('a.example.', 'A', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.42', '192.0.2.43'))
+        self.checkCustom('a.example.', 'TXT', dns.rrset.from_text('a.example.', 0, dns.rdataclass.IN, 'TXT', '"some text"'))
+        # will be blocked because the default policy does not override local data entries
+        self.checkBlocked('z.example.')
+        self.checkNotBlocked('b.example.')
+        self.checkNotBlocked('c.example.')
+        self.checkNotBlocked('d.example.')
+        self.checkNotBlocked('e.example.')
+        # check non-local policies, they should be overridden by the default policy
+        self.checkNXD('tc.example.', 'A')
+        self.checkNotBlocked('drop.example.')
diff --git a/regression-tests.recursor-dnssec/test_TTL.py b/regression-tests.recursor-dnssec/test_TTL.py
new file mode 100644 (file)
index 0000000..6825157
--- /dev/null
@@ -0,0 +1,30 @@
+import dns
+import os
+from recursortests import RecursorTest
+
+class testBogusMaxTTL(RecursorTest):
+    _confdir = 'BogusMaxTTL'
+
+    _config_template = """dnssec=validate
+max-cache-bogus-ttl=5"""
+
+    @classmethod
+    def setUp(cls):
+        confdir = os.path.join('configs', cls._confdir)
+        cls.wipeRecursorCache(confdir)
+
+    def testBogusCheckDisabled(self):
+        # first query with CD=0, so we should get a ServFail
+        query = self.createQuery('ted.bogus.example.', 'A', 'AD', 'DO')
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
+
+        # then with CD=1 so we should get the A + RRSIG
+        # check that we correctly applied the maximum TTL when caching Bogus entries
+        query = self.createQuery('ted.bogus.example.', 'A', 'AD CD', 'DO')
+        res = self.sendUDPQuery(query)
+        self.assertMessageHasFlags(res, ['CD', 'QR', 'RA', 'RD'], ['DO'])
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertEquals(len(res.answer), 2)
+        for ans in res.answer:
+            self.assertLessEqual(ans.ttl, 5)